Merge commit 'cabf75990652cb25d391888b898667da0ba90fd4' into dev-release
diff --git a/.gitignore b/.gitignore
index b6fb8c3..45852a0 100644
--- a/.gitignore
+++ b/.gitignore
@@ -100,8 +100,6 @@
 third_party/jacoco/0.8.6.tar.gz
 third_party/jasmin
 third_party/jasmin.tar.gz
-third_party/jctf
-third_party/jctf.tar.gz
 third_party/jdwp-tests
 third_party/jdwp-tests.tar.gz
 third_party/jsr223-api-1.0
diff --git a/build.gradle b/build.gradle
index e2ca554..67074c7 100644
--- a/build.gradle
+++ b/build.gradle
@@ -48,8 +48,6 @@
     testngVersion = '6.10'
 }
 
-apply from: 'copyAdditionalJctfCommonFiles.gradle'
-
 repositories {
     maven {
        url 'https://storage.googleapis.com/r8-deps/maven_mirror/'
@@ -176,29 +174,6 @@
         compileClasspath += fileTree(dir: "build/generated/test/proto", include: "*.jar")
         output.resourcesDir = 'build/classes/examplesProto'
     }
-    jctfCommon {
-        java {
-            srcDirs = [
-                'third_party/jctf/Harness/src',
-                'third_party/jctf/LibTests/src/com/google/jctf/test/categories',
-                'third_party/jctf/LibTests/src/com/google/jctf/test/helper',
-                'third_party/jctf/LibTests/src/com/google/jctf/testHelpers',
-                'third_party/jctf/LibTests/src/org',
-                'build/additionalJctfCommonFiles'
-            ]
-        }
-        resources {
-            srcDirs = ['third_party/jctf/LibTests/resources']
-        }
-    }
-    jctfTests {
-        java {
-            srcDirs = [
-                'third_party/jctf/LibTests/src/com/google/jctf/test/lib',
-                // 'third_party/jctf/VMTests/src',
-            ]
-        }
-    }
     kotlinR8TestResources {
         java {
             srcDirs = ['src/test/kotlinR8TestResources']
@@ -286,9 +261,6 @@
     testCompile group: 'org.ow2.asm', name: 'asm-util', version: asmVersion
     testCompile group: 'it.unimi.dsi', name: 'fastutil', version: fastutilVersion
 
-    jctfCommonCompile "junit:junit:$junitVersion"
-    jctfTestsCompile "junit:junit:$junitVersion"
-    jctfTestsCompile sourceSets.jctfCommon.output
     examplesAndroidOCompile group: 'org.ow2.asm', name: 'asm', version: asmVersion
     examplesAndroidPCompile group: 'org.ow2.asm', name: 'asm', version: asmVersion
     // Import Guava for @Nullable annotation
@@ -352,7 +324,6 @@
                 "jacoco/0.8.2",
                 "jacoco/0.8.6",
                 "jasmin",
-                "jctf",
                 "junit",
                 "jdwp-tests",
                 "jsr223-api-1.0",
@@ -379,6 +350,7 @@
                 "proguard/proguard5.2.1",
                 "proguard/proguard6.0.1",
                 "proguard/proguard-7.0.0",
+                "retrace_benchmark",
                 "retrace/binary_compatibility",
                 "r8",
                 "r8-releases/2.0.74",
@@ -709,16 +681,6 @@
     }
 }
 
-compileJctfCommonJava {
-    dependsOn 'copyAdditionalJctfCommonFiles'
-    options.compilerArgs = ['-Xlint:none']
-}
-
-compileJctfTestsJava {
-    dependsOn 'jctfCommonClasses'
-    options.compilerArgs = ['-Xlint:none']
-}
-
 task consolidatedLicense {
     def license = new File(new File(buildDir, 'generatedLicense'), 'LICENSE')
 
@@ -1064,16 +1026,6 @@
     ])
 }
 
-task buildDesugaredLibrary(type: Exec) {
-    def outputDir = "build/libs"
-    def script = "tools/create_jctf_tests.py"
-    inputs.file script
-    outputs.dir outputDir
-    dependsOn downloadDeps
-    commandLine "python3", script
-    workingDir = projectDir
-}
-
 task generateR8LibKeepRules(type: Exec) {
     // Depend on r8WithRelocatedDeps to ensure that we do not have external
     // dependencies crossing the boundary.
@@ -1173,11 +1125,6 @@
     from sourceSets.main.allSource
 }
 
-task jctfCommonJar(type: Jar) {
-    from sourceSets.jctfCommon.output
-    archiveFileName = 'jctfCommon.jar'
-}
-
 artifacts {
     archives sourceJar
 }
@@ -1192,19 +1139,8 @@
     workingDir = projectDir
 }
 
-task createJctfTests(type: Exec) {
-    def outputDir = "build/generated/test/java/com/android/tools/r8/jctf"
-    def script = "tools/create_jctf_tests.py"
-    inputs.file script
-    outputs.dir outputDir
-    dependsOn downloadDeps
-    commandLine "python3", script
-    workingDir = projectDir
-}
-
 compileTestJava {
     dependsOn createArtTests
-    dependsOn createJctfTests
 }
 
 task buildCfSegments(type: Jar, dependsOn: downloadDeps) {
@@ -2315,29 +2251,14 @@
         include "com/android/tools/r8/" + project.getProperty('test_namespace') + "/**"
     }
 
-    if (project.hasProperty('tool')) {
-        if (project.property('tool') == 'r8') {
-            exclude "com/android/tools/r8/jctf/**"
-        } else if (project.property('tool') == 'd8') {
-            if (project.hasProperty('only_jctf')) {
-                include "com/android/tools/r8/jctf/d8/**"
-            } else {
-                // Don't run anything, deprecated
-                println "Running with deprecated tool d8, not running any tests"
-                include ""
-            }
-        } else {
-            assert(project.property('tool') == 'r8cf')
-            assert(project.hasProperty('only_jctf'))
-            include "com/android/tools/r8/jctf/r8cf/**"
-        }
+    if (project.hasProperty('tool') && project.property('tool') == 'd8') {
+        // Don't run anything, deprecated
+        println "Running with deprecated tool d8, not running any tests"
+        include ""
     }
     if (!project.hasProperty('all_tests')) {
         exclude "com/android/tools/r8/art/dx/**"
     }
-    if (!project.hasProperty('jctf') && !project.hasProperty('only_jctf')) {
-        exclude "com/android/tools/r8/jctf/**"
-    }
     if (project.hasProperty('shard_count') ) {
       assert project.hasProperty('shard_number')
       int shard_count = project.getProperty('shard_count') as Integer
@@ -2355,10 +2276,6 @@
           return hash % shard_count != shard_number
       }
     }
-    if (project.hasProperty('jctf_compile_only')) {
-        println "JCTF: compiling only"
-        systemProperty 'jctf_compile_only', '1'
-    }
     if (project.hasProperty('test_dir')) {
         systemProperty 'test_dir', project.property('test_dir')
     }
@@ -2388,8 +2305,6 @@
         dependsOn buildExamples
         dependsOn buildKotlinR8TestResources
         dependsOn buildSmali
-        dependsOn jctfCommonJar
-        dependsOn jctfTestsClasses
         dependsOn buildPreNJdwpTestsJar
         dependsOn buildPreNJdwpTestsDex
         dependsOn compileTestNGRunner
diff --git a/copyAdditionalJctfCommonFiles.gradle b/copyAdditionalJctfCommonFiles.gradle
deleted file mode 100644
index 9cf6dfe..0000000
--- a/copyAdditionalJctfCommonFiles.gradle
+++ /dev/null
@@ -1,220 +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.
-
-
-// The `copyAdditionalJctfCommonFiles` task copies files common to all JCTF test
-// cases into an intermediate directory which is then passed to gradle as a
-// source set.
-//
-// Details:
-//
-// The JCTF source tree consists of individual test cases and other sources
-// common to all test cases. The latter is compiled into a single jar file.
-//
-// The problem is the many common source files are scattered under the same
-// directory where the test cases are and gradle has no simple way to add
-// individual files to a source set.
-//
-// That's why we first copy over the common files into an intermediate directory
-// then pass gradle the source set as a single directory.
-
-task copyAdditionalJctfCommonFiles(type: Copy) {
-    def prefix = 'LibTests/src/com/google/jctf/test/lib/java'
-    def inputDir = 'third_party/jctf'
-    def outputDir = 'build/additionalJctfCommonFiles'
-    doFirst {
-        delete outputDir
-    }
-
-    // All the files containing "@Test" and also the files located in a directory where there is "@Test"
-    // file will be compiled into individual, per-test dex files.
-    // Here we need the files not containing "@Test" which are not siblings of "@Test" files. We compile
-    // them into a common jar file which will be added into each test's dex file.
-    //
-    // The following list is compiled with this script:
-    //
-    // # create list of directories that contain non-"@Test" files but do not contain "@Test" files
-    // dirlist=$(comm -23 \
-    // <(grep -rL "@Test" "third_party/jctf/LibTests/src/com/google/jctf/test/lib" --include=*.java | sed 's;/[^/]*\.java$;;' | sort | uniq) \
-    // <(grep -rl "@Test" "third_party/jctf/LibTests/src/com/google/jctf/test/lib" --include=*.java | sed 's;/[^/]*\.java$;;' | sort | uniq))
-    //
-    // # all the java files from these dirs
-    // (for d in $dirlist; do ls -1 $d/*.java; done) \
-    //   | sort | sed 's,.*test/lib/java/,,' | sed -E "s/(.*)/'\1',/"
-    def files = [
-        'lang/annotation/Annotation/AllTypesAntn2.java',
-        'lang/annotation/Annotation/AllTypesAntn.java',
-        'lang/Character/CharacterData.java',
-        'lang/Character/CharacterUtils.java',
-        'lang/Character/Subset/TestSubset.java',
-        'lang/Class/ClassAnnotationsData.java',
-        'lang/ClassLoader/EmptyCertificate.java',
-        'lang/ClassLoader/EmptyClassLoader.java',
-        'lang/ClassLoader/setPackageAssertionStatusLjava_lang_StringZ/pckg1/pckg11/C01.java',
-        'lang/ClassLoader/setPackageAssertionStatusLjava_lang_StringZ/pckg1/pckg11/pckg111/C01.java',
-        'lang/ClassLoader/setPackageAssertionStatusLjava_lang_StringZ/pckg1/pckg11/pckg111/C02.java',
-        'lang/ClassLoader/setPackageAssertionStatusLjava_lang_StringZ/pckg1/pckg12/C01.java',
-        'lang/Class/PackageAccessible.java',
-        'lang/Class/PackageInstantiable.java',
-        'lang/Enum/EnumMocks.java',
-        'lang/InheritableThreadLocal/TestThread.java',
-        'lang/Number/TestNumber.java',
-        'lang/Package/PackageAnnotationsData.java',
-        'lang/Package/PackageLoader.java',
-        'lang/ProcessBuilder/ProcessBuilderHelper.java',
-        'lang/reflect/AccessibleObject/ChildTestClass.java',
-        'lang/reflect/AccessibleObject/ClassTestAnnotation.java',
-        'lang/reflect/AccessibleObject/DefaultTestAnnotation.java',
-        'lang/reflect/AccessibleObject/Helper.java',
-        'lang/reflect/AccessibleObject/Runtime1TestAnnotation.java',
-        'lang/reflect/AccessibleObject/Runtime2TestAnnotation.java',
-        'lang/reflect/AccessibleObject/Runtime3TestAnnotation.java',
-        'lang/reflect/AccessibleObject/SourceTestAnnotation.java',
-        'lang/reflect/AccessibleObject/TestClass.java',
-        'lang/reflect/Constructor/ConstructorAnnotationsData.java',
-        'lang/reflect/Constructor/ConstructorTestHelper.java',
-        'lang/reflect/Constructor/PrivateClass.java',
-        'lang/reflect/Constructor/PrivateConstructor.java',
-        'lang/reflect/Field/FieldAnnotationsData.java',
-        'lang/reflect/Field/TestExceptionInInitializerError.java',
-        'lang/reflect/Field/TestFinalObjectField.java',
-        'lang/reflect/Field/TestFinalPrimitiveField.java',
-        'lang/reflect/Field/TestObjectField.java',
-        'lang/reflect/Field/TestOtherField.java',
-        'lang/reflect/Field/TestPrimitiveField.java',
-        'lang/reflect/Field/TestStaticFinalObjectField.java',
-        'lang/reflect/Field/TestStaticFinalPrimitiveField.java',
-        'lang/reflect/Field/TestStaticObjectField.java',
-        'lang/reflect/Field/TestStaticPrimitiveField.java',
-        'lang/reflect/Method/AbstractTestMethod.java',
-        'lang/reflect/Method/MethodAnnotationsData.java',
-        'lang/reflect/Method/PrivateClass.java',
-        'lang/reflect/Method/PrivateMethod.java',
-        'lang/reflect/Method/TestMethod.java',
-        'lang/reflect/Method/TestMethodSub.java',
-        'lang/reflect/Proxy/HiddenInterface.java',
-        'lang/reflect/Proxy/NullHandler.java',
-        'lang/ref/MemoryHog.java',
-        'lang/ref/MyReferenceQueue.java',
-        'lang/ref/PhantomReference/MyPhantomReference.java',
-        'lang/ref/SoftReference/MySoftReference.java',
-        'lang/ref/WeakReference/MyWeakReference.java',
-        'lang/Runtime/CountLoads.java',
-        'lang/Runtime/CWD.java',
-        'lang/Runtime/EchoArgs.java',
-        'lang/Runtime/EchoEnv.java',
-        'lang/SecurityManager/CheckingDomain.java',
-        'lang/SecurityManager/SecurityManagerTest.java',
-        'lang/StackTraceElement/ElementData.java',
-        'lang/StackTraceElement/InitTestFixture.java',
-        'lang/StackTraceElement/StaticInitTestFixture.java',
-        'lang/StackTraceElement/TestFixture.java',
-        'lang/StrictMath/FPUtil.java',
-        'lang/StringBuffer/MultiThreadTestHelper.java',
-        'lang/String/String_Character.java',
-        'lang/String/StringHelper.java',
-        'lang/String/String_ISO88591.java',
-        'lang/String/String_UnicodeCodePoint.java',
-        'lang/String/String_USASCII.java',
-        'lang/String/String_UTF16BE.java',
-        'lang/String/String_UTF16.java',
-        'lang/String/String_UTF16LE.java',
-        'lang/String/String_UTF8.java',
-        'lang/System/System_propertyKeys.java',
-        'lang/Thread/CatchThread.java',
-        'lang/Thread/CheckHandler.java',
-        'lang/Thread/CheckRun.java',
-        'lang/ThreadGroup/AccessCheckThread.java',
-        'lang/ThreadGroup/CatchGroup.java',
-        'lang/ThreadGroup/MyThread.java',
-        'lang/ThreadGroup/SMTestCheckAccessThreadGroupThrowSE.java',
-        'lang/ThreadGroup/SMTestCheckAccessThreadGroupWasCalled.java',
-        'lang/ThreadGroup/SMTestCheckAccessThreadThrowSE.java',
-        'lang/ThreadGroup/ThreadGroupHelper.java',
-        'lang/Thread/SlowIncThread.java',
-        'lang/Thread/StepThread.java',
-        'lang/Thread/WaitRun.java',
-        'lang/Throwable/Exception1.java',
-        'lang/Throwable/Exception2.java',
-        'lang/Throwable/Exception3.java',
-        'lang/Throwable/MethodStackFixture.java',
-        'lang/Throwable/MultipleExceptionsFixture.java',
-        'lang/Throwable/MultipleExceptionsStackChecker.java',
-        'util/concurrent/AbstractExecutorService/DirectExecutorService.java',
-        'util/concurrent/AdjustablePolicy.java',
-        'util/concurrent/ArrayBlockingQueue/ArrayBlockingQueueHelper.java',
-        'util/concurrent/CheckedCallable.java',
-        'util/concurrent/CheckedInterruptedCallable.java',
-        'util/concurrent/CheckedInterruptedRunnable.java',
-        'util/concurrent/CheckedRunnable.java',
-        'util/concurrent/ConcurrentHashMap/ConcurrentHashMapHelper.java',
-        'util/concurrent/ConcurrentLinkedQueue/ConcurrentLinkedQueueHelper.java',
-        'util/concurrent/ConcurrentSkipListMap/ConcurrentSkipListMapHelper.java',
-        'util/concurrent/ConcurrentSkipListSet/ConcurrentSkipListSetHelper.java',
-        'util/concurrent/ConcurrentSkipListSet/MyReverseComparator.java',
-        'util/concurrent/CopyOnWriteArrayList/CopyOnWriteArrayListHelper.java',
-        'util/concurrent/CopyOnWriteArraySet/CopyOnWriteArraySetHelper.java',
-        'util/concurrent/CyclicBarrier/MyAction.java',
-        'util/concurrent/DelayQueue/DelayQueueHelper.java',
-        'util/concurrent/DelayQueue/NanoDelay.java',
-        'util/concurrent/DelayQueue/PDelay.java',
-        'util/concurrent/FutureTask/CounterCallable.java',
-        'util/concurrent/FutureTask/PublicFutureTask.java',
-        'util/concurrent/Helper.java',
-        'util/concurrent/InterruptedCallable.java',
-        'util/concurrent/InterruptingCallable.java',
-        'util/concurrent/LinkedBlockingDeque/LinkedBlockingDequeHelper.java',
-        'util/concurrent/LinkedBlockingQueue/LinkedBlockingQueueHelper.java',
-        'util/concurrent/MediumPossiblyInterruptedRunnable.java',
-        'util/concurrent/MediumRunnable.java',
-        'util/concurrent/NoOpCallable.java',
-        'util/concurrent/NoOpREHandler.java',
-        'util/concurrent/NoOpRunnable.java',
-        'util/concurrent/NPETask.java',
-        'util/concurrent/PriorityBlockingQueue/MyReverseComparator.java',
-        'util/concurrent/PriorityBlockingQueue/PriorityBlockingQueueHelper.java',
-        'util/concurrent/RunnableShouldThrow.java',
-        'util/concurrent/ScheduledThreadPoolExecutor/CustomExecutor.java',
-        'util/concurrent/ScheduledThreadPoolExecutor/CustomTask.java',
-        'util/concurrent/ScheduledThreadPoolExecutor/RunnableCounter.java',
-        'util/concurrent/Semaphore/InterruptedLockRunnable.java',
-        'util/concurrent/Semaphore/InterruptibleLockRunnable.java',
-        'util/concurrent/Semaphore/PublicSemaphore.java',
-        'util/concurrent/ShortRunnable.java',
-        'util/concurrent/SimpleThreadFactory.java',
-        'util/concurrent/SmallCallable.java',
-        'util/concurrent/SmallPossiblyInterruptedRunnable.java',
-        'util/concurrent/SmallRunnable.java',
-        'util/concurrent/StringTask.java',
-        'util/concurrent/ThreadPoolExecutor/CustomTask.java',
-        'util/concurrent/ThreadPoolExecutor/CustomTPE.java',
-        'util/concurrent/ThreadPoolExecutor/ExtendedTPE.java',
-        'util/concurrent/ThreadPoolExecutor/FailingThreadFactory.java',
-        'util/concurrent/ThreadShouldThrow.java',
-        'util/concurrent/TrackedCallable.java',
-        'util/concurrent/TrackedLongRunnable.java',
-        'util/concurrent/TrackedNoOpRunnable.java',
-        'util/concurrent/TrackedShortRunnable.java',
-    ]
-    files.each {
-        def dir = new File(it).parent
-        from ("$inputDir/$prefix/$it") {
-            into "$prefix/$dir"
-        }
-
-    }
-
-    def prefixNoPackage = 'LibTests/src'
-    def filesNoPackage = [
-        'DefaultPackageInterface.java',
-        'UnnamedPackageClass.java'
-    ]
-    filesNoPackage.each {
-        from ("$inputDir/$prefixNoPackage/$it") {
-            into "$prefixNoPackage"
-        }
-    }
-
-    into outputDir
-}
diff --git a/infra/config/global/generated/cr-buildbucket.cfg b/infra/config/global/generated/cr-buildbucket.cfg
index 6661615..0323612 100644
--- a/infra/config/global/generated/cr-buildbucket.cfg
+++ b/infra/config/global/generated/cr-buildbucket.cfg
@@ -94,6 +94,8 @@
         '  "builder_group": "internal.client.r8",'
         '  "recipe": "rex",'
         '  "test_options": ['
+        '    "--one_line_per_test",'
+        '    "--archive_failures",'
         '    "--no_internal",'
         '    "--desugared-library",'
         '    "HEAD"'
@@ -127,6 +129,8 @@
         '  "builder_group": "internal.client.r8",'
         '  "recipe": "rex",'
         '  "test_options": ['
+        '    "--one_line_per_test",'
+        '    "--archive_failures",'
         '    "--no_internal",'
         '    "--desugared-library",'
         '    "HEAD",'
@@ -914,80 +918,6 @@
       }
     }
     builders {
-      name: "linux-d8_jctf"
-      swarming_host: "chrome-swarming.appspot.com"
-      swarming_tags: "vpython:native-python-wrapper"
-      dimensions: "cores:8"
-      dimensions: "cpu:x86-64"
-      dimensions: "jctf:true"
-      dimensions: "os:Ubuntu-16.04"
-      dimensions: "pool:luci.r8.ci"
-      exe {
-        cipd_package: "infra_internal/recipe_bundles/chrome-internal.googlesource.com/chrome/tools/build_limited/scripts/slave"
-        cipd_version: "refs/heads/master"
-        cmd: "luciexe"
-      }
-      properties:
-        '{'
-        '  "builder_group": "internal.client.r8",'
-        '  "recipe": "rex",'
-        '  "test_options": ['
-        '    "--no_internal",'
-        '    "--one_line_per_test",'
-        '    "--archive_failures",'
-        '    "--dex_vm=all",'
-        '    "--tool=d8",'
-        '    "--only_jctf"'
-        '  ]'
-        '}'
-      priority: 26
-      execution_timeout_secs: 43200
-      expiration_secs: 126000
-      build_numbers: YES
-      service_account: "r8-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
-      experiments {
-        key: "luci.recipes.use_python3"
-        value: 100
-      }
-    }
-    builders {
-      name: "linux-d8_jctf_release"
-      swarming_host: "chrome-swarming.appspot.com"
-      swarming_tags: "vpython:native-python-wrapper"
-      dimensions: "cores:8"
-      dimensions: "cpu:x86-64"
-      dimensions: "jctf:true"
-      dimensions: "os:Ubuntu-16.04"
-      dimensions: "pool:luci.r8.ci"
-      exe {
-        cipd_package: "infra_internal/recipe_bundles/chrome-internal.googlesource.com/chrome/tools/build_limited/scripts/slave"
-        cipd_version: "refs/heads/master"
-        cmd: "luciexe"
-      }
-      properties:
-        '{'
-        '  "builder_group": "internal.client.r8",'
-        '  "recipe": "rex",'
-        '  "test_options": ['
-        '    "--no_internal",'
-        '    "--one_line_per_test",'
-        '    "--archive_failures",'
-        '    "--dex_vm=all",'
-        '    "--tool=d8",'
-        '    "--only_jctf"'
-        '  ]'
-        '}'
-      priority: 26
-      execution_timeout_secs: 43200
-      expiration_secs: 126000
-      build_numbers: YES
-      service_account: "r8-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
-      experiments {
-        key: "luci.recipes.use_python3"
-        value: 100
-      }
-    }
-    builders {
       name: "linux-dex_default"
       swarming_host: "chrome-swarming.appspot.com"
       swarming_tags: "vpython:native-python-wrapper"
@@ -1198,6 +1128,78 @@
       }
     }
     builders {
+      name: "linux-jdk17"
+      swarming_host: "chrome-swarming.appspot.com"
+      swarming_tags: "vpython:native-python-wrapper"
+      dimensions: "cores:8"
+      dimensions: "cpu:x86-64"
+      dimensions: "normal:true"
+      dimensions: "os:Ubuntu-16.04"
+      dimensions: "pool:luci.r8.ci"
+      exe {
+        cipd_package: "infra_internal/recipe_bundles/chrome-internal.googlesource.com/chrome/tools/build_limited/scripts/slave"
+        cipd_version: "refs/heads/master"
+        cmd: "luciexe"
+      }
+      properties:
+        '{'
+        '  "builder_group": "internal.client.r8",'
+        '  "recipe": "rex",'
+        '  "test_options": ['
+        '    "--runtimes=jdk17",'
+        '    "--tool=r8",'
+        '    "--no_internal",'
+        '    "--one_line_per_test",'
+        '    "--archive_failures"'
+        '  ]'
+        '}'
+      priority: 26
+      execution_timeout_secs: 21600
+      expiration_secs: 126000
+      build_numbers: YES
+      service_account: "r8-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
+      experiments {
+        key: "luci.recipes.use_python3"
+        value: 100
+      }
+    }
+    builders {
+      name: "linux-jdk17_release"
+      swarming_host: "chrome-swarming.appspot.com"
+      swarming_tags: "vpython:native-python-wrapper"
+      dimensions: "cores:8"
+      dimensions: "cpu:x86-64"
+      dimensions: "normal:true"
+      dimensions: "os:Ubuntu-16.04"
+      dimensions: "pool:luci.r8.ci"
+      exe {
+        cipd_package: "infra_internal/recipe_bundles/chrome-internal.googlesource.com/chrome/tools/build_limited/scripts/slave"
+        cipd_version: "refs/heads/master"
+        cmd: "luciexe"
+      }
+      properties:
+        '{'
+        '  "builder_group": "internal.client.r8",'
+        '  "recipe": "rex",'
+        '  "test_options": ['
+        '    "--runtimes=jdk17",'
+        '    "--tool=r8",'
+        '    "--no_internal",'
+        '    "--one_line_per_test",'
+        '    "--archive_failures"'
+        '  ]'
+        '}'
+      priority: 26
+      execution_timeout_secs: 21600
+      expiration_secs: 126000
+      build_numbers: YES
+      service_account: "r8-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
+      experiments {
+        key: "luci.recipes.use_python3"
+        value: 100
+      }
+    }
+    builders {
       name: "linux-jdk8"
       swarming_host: "chrome-swarming.appspot.com"
       swarming_tags: "vpython:native-python-wrapper"
@@ -1488,80 +1490,6 @@
       }
     }
     builders {
-      name: "linux-r8cf_jctf"
-      swarming_host: "chrome-swarming.appspot.com"
-      swarming_tags: "vpython:native-python-wrapper"
-      dimensions: "cores:8"
-      dimensions: "cpu:x86-64"
-      dimensions: "jctf:true"
-      dimensions: "os:Ubuntu-16.04"
-      dimensions: "pool:luci.r8.ci"
-      exe {
-        cipd_package: "infra_internal/recipe_bundles/chrome-internal.googlesource.com/chrome/tools/build_limited/scripts/slave"
-        cipd_version: "refs/heads/master"
-        cmd: "luciexe"
-      }
-      properties:
-        '{'
-        '  "builder_group": "internal.client.r8",'
-        '  "recipe": "rex",'
-        '  "test_options": ['
-        '    "--no_internal",'
-        '    "--one_line_per_test",'
-        '    "--archive_failures",'
-        '    "--dex_vm=all",'
-        '    "--tool=r8cf",'
-        '    "--only_jctf"'
-        '  ]'
-        '}'
-      priority: 26
-      execution_timeout_secs: 43200
-      expiration_secs: 126000
-      build_numbers: YES
-      service_account: "r8-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
-      experiments {
-        key: "luci.recipes.use_python3"
-        value: 100
-      }
-    }
-    builders {
-      name: "linux-r8cf_jctf_release"
-      swarming_host: "chrome-swarming.appspot.com"
-      swarming_tags: "vpython:native-python-wrapper"
-      dimensions: "cores:8"
-      dimensions: "cpu:x86-64"
-      dimensions: "jctf:true"
-      dimensions: "os:Ubuntu-16.04"
-      dimensions: "pool:luci.r8.ci"
-      exe {
-        cipd_package: "infra_internal/recipe_bundles/chrome-internal.googlesource.com/chrome/tools/build_limited/scripts/slave"
-        cipd_version: "refs/heads/master"
-        cmd: "luciexe"
-      }
-      properties:
-        '{'
-        '  "builder_group": "internal.client.r8",'
-        '  "recipe": "rex",'
-        '  "test_options": ['
-        '    "--no_internal",'
-        '    "--one_line_per_test",'
-        '    "--archive_failures",'
-        '    "--dex_vm=all",'
-        '    "--tool=r8cf",'
-        '    "--only_jctf"'
-        '  ]'
-        '}'
-      priority: 26
-      execution_timeout_secs: 43200
-      expiration_secs: 126000
-      build_numbers: YES
-      service_account: "r8-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
-      experiments {
-        key: "luci.recipes.use_python3"
-        value: 100
-      }
-    }
-    builders {
       name: "linux-run-on-app-dump"
       swarming_host: "chrome-swarming.appspot.com"
       swarming_tags: "vpython:native-python-wrapper"
diff --git a/infra/config/global/generated/luci-milo.cfg b/infra/config/global/generated/luci-milo.cfg
index 8bbf1b1..e07a48e 100644
--- a/infra/config/global/generated/luci-milo.cfg
+++ b/infra/config/global/generated/luci-milo.cfg
@@ -41,6 +41,11 @@
     short_name: "jdk11"
   }
   builders {
+    name: "buildbucket/luci.r8.ci/linux-jdk17"
+    category: "R8"
+    short_name: "jdk17"
+  }
+  builders {
     name: "buildbucket/luci.r8.ci/linux-android-4.0.4"
     category: "R8"
     short_name: "4.0.4"
@@ -116,16 +121,6 @@
     short_name: "kotlin_old"
   }
   builders {
-    name: "buildbucket/luci.r8.ci/linux-d8_jctf"
-    category: "jctf"
-    short_name: "d8_jctf"
-  }
-  builders {
-    name: "buildbucket/luci.r8.ci/linux-r8cf_jctf"
-    category: "jctf"
-    short_name: "r8cf_jctf"
-  }
-  builders {
     name: "buildbucket/luci.r8.ci/lib_desugar-archive"
     category: "library_desugar"
     short_name: "archive"
@@ -171,6 +166,11 @@
     short_name: "jdk11"
   }
   builders {
+    name: "buildbucket/luci.r8.ci/linux-jdk17_release"
+    category: "Release|R8"
+    short_name: "jdk17"
+  }
+  builders {
     name: "buildbucket/luci.r8.ci/linux-android-4.0.4_release"
     category: "Release|R8"
     short_name: "4.0.4"
@@ -235,14 +235,4 @@
     category: "Release|R8"
     short_name: "dump"
   }
-  builders {
-    name: "buildbucket/luci.r8.ci/linux-d8_jctf_release"
-    category: "Release|jctf"
-    short_name: "d8_jctf"
-  }
-  builders {
-    name: "buildbucket/luci.r8.ci/linux-r8cf_jctf_release"
-    category: "Release|jctf"
-    short_name: "r8cf_jctf"
-  }
 }
diff --git a/infra/config/global/generated/luci-notify.cfg b/infra/config/global/generated/luci-notify.cfg
index d98d266..c9717b2 100644
--- a/infra/config/global/generated/luci-notify.cfg
+++ b/infra/config/global/generated/luci-notify.cfg
@@ -300,30 +300,6 @@
   }
   builders {
     bucket: "ci"
-    name: "linux-d8_jctf"
-    repository: "https://r8.googlesource.com/r8"
-  }
-}
-notifiers {
-  notifications {
-    on_failure: true
-    on_new_failure: true
-    notify_blamelist {}
-  }
-  builders {
-    bucket: "ci"
-    name: "linux-d8_jctf_release"
-    repository: "https://r8.googlesource.com/r8"
-  }
-}
-notifiers {
-  notifications {
-    on_failure: true
-    on_new_failure: true
-    notify_blamelist {}
-  }
-  builders {
-    bucket: "ci"
     name: "linux-dex_default"
     repository: "https://r8.googlesource.com/r8"
   }
@@ -396,6 +372,30 @@
   }
   builders {
     bucket: "ci"
+    name: "linux-jdk17"
+    repository: "https://r8.googlesource.com/r8"
+  }
+}
+notifiers {
+  notifications {
+    on_failure: true
+    on_new_failure: true
+    notify_blamelist {}
+  }
+  builders {
+    bucket: "ci"
+    name: "linux-jdk17_release"
+    repository: "https://r8.googlesource.com/r8"
+  }
+}
+notifiers {
+  notifications {
+    on_failure: true
+    on_new_failure: true
+    notify_blamelist {}
+  }
+  builders {
+    bucket: "ci"
     name: "linux-jdk8"
     repository: "https://r8.googlesource.com/r8"
   }
@@ -492,30 +492,6 @@
   }
   builders {
     bucket: "ci"
-    name: "linux-r8cf_jctf"
-    repository: "https://r8.googlesource.com/r8"
-  }
-}
-notifiers {
-  notifications {
-    on_failure: true
-    on_new_failure: true
-    notify_blamelist {}
-  }
-  builders {
-    bucket: "ci"
-    name: "linux-r8cf_jctf_release"
-    repository: "https://r8.googlesource.com/r8"
-  }
-}
-notifiers {
-  notifications {
-    on_failure: true
-    on_new_failure: true
-    notify_blamelist {}
-  }
-  builders {
-    bucket: "ci"
     name: "linux-run-on-app-dump"
     repository: "https://r8.googlesource.com/r8"
   }
diff --git a/infra/config/global/generated/luci-scheduler.cfg b/infra/config/global/generated/luci-scheduler.cfg
index cb2a721..58167f9 100644
--- a/infra/config/global/generated/luci-scheduler.cfg
+++ b/infra/config/global/generated/luci-scheduler.cfg
@@ -368,35 +368,6 @@
   }
 }
 job {
-  id: "linux-d8_jctf"
-  realm: "ci"
-  acl_sets: "ci"
-  triggering_policy {
-    kind: GREEDY_BATCHING
-    max_concurrent_invocations: 4
-  }
-  buildbucket {
-    server: "cr-buildbucket.appspot.com"
-    bucket: "ci"
-    builder: "linux-d8_jctf"
-  }
-}
-job {
-  id: "linux-d8_jctf_release"
-  realm: "ci"
-  acl_sets: "ci"
-  triggering_policy {
-    kind: GREEDY_BATCHING
-    max_concurrent_invocations: 4
-    max_batch_size: 1
-  }
-  buildbucket {
-    server: "cr-buildbucket.appspot.com"
-    bucket: "ci"
-    builder: "linux-d8_jctf_release"
-  }
-}
-job {
   id: "linux-dex_default"
   realm: "ci"
   acl_sets: "ci"
@@ -432,6 +403,7 @@
   triggering_policy {
     kind: GREEDY_BATCHING
     max_concurrent_invocations: 1
+    max_batch_size: 1
   }
   buildbucket {
     server: "cr-buildbucket.appspot.com"
@@ -446,6 +418,7 @@
   triggering_policy {
     kind: GREEDY_BATCHING
     max_concurrent_invocations: 1
+    max_batch_size: 1
   }
   buildbucket {
     server: "cr-buildbucket.appspot.com"
@@ -483,6 +456,35 @@
   }
 }
 job {
+  id: "linux-jdk17"
+  realm: "ci"
+  acl_sets: "ci"
+  triggering_policy {
+    kind: GREEDY_BATCHING
+    max_concurrent_invocations: 4
+  }
+  buildbucket {
+    server: "cr-buildbucket.appspot.com"
+    bucket: "ci"
+    builder: "linux-jdk17"
+  }
+}
+job {
+  id: "linux-jdk17_release"
+  realm: "ci"
+  acl_sets: "ci"
+  triggering_policy {
+    kind: GREEDY_BATCHING
+    max_concurrent_invocations: 4
+    max_batch_size: 1
+  }
+  buildbucket {
+    server: "cr-buildbucket.appspot.com"
+    bucket: "ci"
+    builder: "linux-jdk17_release"
+  }
+}
+job {
   id: "linux-jdk8"
   realm: "ci"
   acl_sets: "ci"
@@ -598,35 +600,6 @@
   }
 }
 job {
-  id: "linux-r8cf_jctf"
-  realm: "ci"
-  acl_sets: "ci"
-  triggering_policy {
-    kind: GREEDY_BATCHING
-    max_concurrent_invocations: 4
-  }
-  buildbucket {
-    server: "cr-buildbucket.appspot.com"
-    bucket: "ci"
-    builder: "linux-r8cf_jctf"
-  }
-}
-job {
-  id: "linux-r8cf_jctf_release"
-  realm: "ci"
-  acl_sets: "ci"
-  triggering_policy {
-    kind: GREEDY_BATCHING
-    max_concurrent_invocations: 4
-    max_batch_size: 1
-  }
-  buildbucket {
-    server: "cr-buildbucket.appspot.com"
-    bucket: "ci"
-    builder: "linux-r8cf_jctf_release"
-  }
-}
-job {
   id: "linux-run-on-app-dump"
   realm: "ci"
   acl_sets: "ci"
@@ -700,6 +673,7 @@
   realm: "ci"
   acl_sets: "ci"
   triggers: "linux-android-13.0.0_release"
+  triggers: "linux-jdk17_release"
   gitiles {
     repo: "https://r8.googlesource.com/r8"
     refs: "regexp:refs/heads/([3]\\.[3-9]+(\\.[0-9]+)?|[4-9]\\.[0-9]+(\\.[0-9]+)?)"
@@ -719,14 +693,12 @@
   triggers: "linux-android-7.0.0_release"
   triggers: "linux-android-8.1.0_release"
   triggers: "linux-android-9.0.0_release"
-  triggers: "linux-d8_jctf_release"
   triggers: "linux-dex_default_release"
   triggers: "linux-internal_release"
   triggers: "linux-jdk11_release"
   triggers: "linux-jdk8_release"
   triggers: "linux-jdk9_release"
   triggers: "linux-none_release"
-  triggers: "linux-r8cf_jctf_release"
   triggers: "linux-run-on-app-dump_release"
   triggers: "windows_release"
   gitiles {
@@ -752,16 +724,15 @@
   triggers: "linux-android-7.0.0"
   triggers: "linux-android-8.1.0"
   triggers: "linux-android-9.0.0"
-  triggers: "linux-d8_jctf"
   triggers: "linux-dex_default"
   triggers: "linux-internal"
   triggers: "linux-jdk11"
+  triggers: "linux-jdk17"
   triggers: "linux-jdk8"
   triggers: "linux-jdk9"
   triggers: "linux-kotlin_dev"
   triggers: "linux-kotlin_old"
   triggers: "linux-none"
-  triggers: "linux-r8cf_jctf"
   triggers: "linux-run-on-app-dump"
   triggers: "windows"
   gitiles {
diff --git a/infra/config/global/generated/project.cfg b/infra/config/global/generated/project.cfg
index 974c534..6153ae8 100644
--- a/infra/config/global/generated/project.cfg
+++ b/infra/config/global/generated/project.cfg
@@ -7,7 +7,7 @@
 name: "r8"
 access: "group:all"
 lucicfg {
-  version: "1.30.9"
+  version: "1.30.10"
   package_dir: ".."
   config_dir: "generated"
   entry_point: "main.star"
diff --git a/infra/config/global/main.star b/infra/config/global/main.star
index d6eb25a..303dffe 100755
--- a/infra/config/global/main.star
+++ b/infra/config/global/main.star
@@ -161,7 +161,7 @@
     "--archive_failures"
 ]
 
-def get_dimensions(windows=False, jctf=False, internal=False, normal=False):
+def get_dimensions(windows=False, internal=False, normal=False):
   dimensions = {
     "cores" : "2" if internal else "8",
     "cpu" : "x86-64",
@@ -171,8 +171,6 @@
     dimensions["os"] = "Windows-10"
   else:
     dimensions["os"] = "Ubuntu-16.04"
-  if jctf:
-    dimensions["jctf"] = "true"
   if internal:
     dimensions["internal"] = "true"
   if normal:
@@ -270,6 +268,9 @@
 r8_tester_with_default("linux-jdk8", ["--runtimes=jdk8"])
 r8_tester_with_default("linux-jdk9", ["--runtimes=jdk9"])
 r8_tester_with_default("linux-jdk11", ["--runtimes=jdk11"])
+r8_tester_with_default("linux-jdk17", ["--runtimes=jdk17"],
+    release_trigger=["branch-gitiles-3.3-forward"])
+
 
 r8_tester_with_default("linux-android-4.0.4",
     ["--dex_vm=4.0.4", "--all_tests"])
@@ -304,7 +305,8 @@
         dimensions = get_dimensions(internal=True),
         triggering_policy = scheduler.policy(
             kind = scheduler.GREEDY_BATCHING_KIND,
-            max_concurrent_invocations = 1
+            max_concurrent_invocations = 1,
+            max_batch_size = 1
         ),
         priority = 25,
         properties = {
@@ -336,7 +338,13 @@
 
 def desugared_library():
   for name in ["head", "jdk11_head"]:
-    test_options = ["--no_internal", "--desugared-library", "HEAD"]
+    test_options = [
+        "--one_line_per_test",
+        "--archive_failures",
+        "--no_internal",
+        "--desugared-library",
+        "HEAD"
+    ]
     if "jdk11" in name:
       test_options = test_options + ["--desugared-library-configuration", "jdk11"]
     properties = {
@@ -376,38 +384,12 @@
     }
 )
 
-def jctf():
-  for release in ["", "_release"]:
-    for tool in ["d8", "r8cf"]:
-      properties = {
-          "test_options" : [
-              "--no_internal",
-              "--one_line_per_test",
-              "--archive_failures",
-              "--dex_vm=all",
-              "--tool=" + tool,
-              "--only_jctf"],
-          "builder_group" : "internal.client.r8",
-      }
-      name = "linux-" + tool + "_jctf" + release
-      r8_builder(
-          name,
-          category = "jctf",
-          dimensions = get_dimensions(jctf=True),
-          execution_timeout = time.hour * 12,
-          expiration_timeout = time.hour * 35,
-          properties = properties,
-      )
-jctf()
-
 order_of_categories = [
   "archive",
   "R8",
-  "jctf",
   "library_desugar",
   "Release|archive",
   "Release|R8",
-  "Release|jctf"
 ]
 
 def add_view_entries():
diff --git a/scripts/add-openjdk.sh b/scripts/add-openjdk.sh
index f6b3284..e8714f3 100755
--- a/scripts/add-openjdk.sh
+++ b/scripts/add-openjdk.sh
@@ -23,6 +23,9 @@
 # For ea versions the full version name has a postfix.
 # JDK_VERSION_FULL="${JDK_VERSION}-ea+33"
 
+rm -rf linux
+rm -f linux.tar.gz
+rm -f linux.tar.gz.sha1
 tar xf ~/Downloads/openjdk-${JDK_VERSION_FULL}_linux-x64_bin.tar.gz
 cp -rL jdk-${JDK_VERSION} linux
 cp README.google linux
@@ -31,20 +34,26 @@
 rm -rf linux
 rm linux.tar.gz
 
+rm -rf osx
+rm -f osx.tar.gz
+rm -f osx.tar.gz.sha1
 tar xf ~/Downloads/openjdk-${JDK_VERSION_FULL}_macos-x64_bin.tar.gz
 cp -rL jdk-${JDK_VERSION}.jdk osx
 cp README.google osx
 upload_to_google_storage.py -a --bucket r8-deps osx
-rm -rf osx
 rm -rf jdk-${JDK_VERSION}.jdk
+rm -rf osx
 rm osx.tar.gz
 
+rm -rf windows
+rm -f windows.tar.gz
+rm -f windows.tar.gz.sha1
 unzip ~/Downloads/openjdk-${JDK_VERSION_FULL}_windows-x64_bin.zip
 cp -rL jdk-${JDK_VERSION} windows
 cp README.google windows
 upload_to_google_storage.py -a --bucket r8-deps windows
-rm -rf windows
 rm -rf jdk-${JDK_VERSION}
+rm -rf windows
 rm windows.tar.gz
 
 git add *.sha1
diff --git a/src/library_desugar/desugar_jdk_libs_comments.md b/src/library_desugar/desugar_jdk_libs_legacy_comments.md
similarity index 96%
rename from src/library_desugar/desugar_jdk_libs_comments.md
rename to src/library_desugar/desugar_jdk_libs_legacy_comments.md
index 2ba5908..f2bb6d8 100644
--- a/src/library_desugar/desugar_jdk_libs_comments.md
+++ b/src/library_desugar/desugar_jdk_libs_legacy_comments.md
@@ -1,4 +1,4 @@
-# Description of the desugared library configuration file
+# Description of the legacy desugared library configuration file
 
 ## Version
 
diff --git a/src/library_desugar/jdk11/chm_only_desugar_jdk_libs.json b/src/library_desugar/jdk11/chm_only_desugar_jdk_libs.json
index f240706..5f43015 100644
--- a/src/library_desugar/jdk11/chm_only_desugar_jdk_libs.json
+++ b/src/library_desugar/jdk11/chm_only_desugar_jdk_libs.json
@@ -1,7 +1,7 @@
 {
   "identifier": "com.tools.android:chm_only_desugar_jdk_libs:1.0.12",
   "configuration_format_version": 100,
-  "required_compilation_api_level": 30,
+  "required_compilation_api_level": 26,
   "synthesized_library_classes_package_prefix": "j$.",
   "support_all_callbacks_from_library": false,
   "common_flags": [
diff --git a/src/library_desugar/jdk11/desugar_jdk_libs.json b/src/library_desugar/jdk11/desugar_jdk_libs.json
index c201e1d..41ac1e6 100644
--- a/src/library_desugar/jdk11/desugar_jdk_libs.json
+++ b/src/library_desugar/jdk11/desugar_jdk_libs.json
@@ -245,7 +245,6 @@
         "java.nio.channels.CompletionHandler": "j$.nio.channels.CompletionHandler",
         "java.nio.channels.Desugar": "j$.nio.channels.Desugar",
         "java.nio.file.": "j$.nio.file.",
-        "wrapper." : "j$.wrapper.",
         "jdk.internal.": "j$.jdk.internal.",
         "sun.misc.Desugar": "j$.sun.misc.Desugar",
         "sun.nio.cs.": "j$.sun.nio.cs.",
@@ -253,11 +252,12 @@
         "sun.nio.fs.AbstractFileTypeDetector": "j$.sun.nio.fs.AbstractFileTypeDetector",
         "sun.nio.fs.BasicFileAttributesHolder": "j$.sun.nio.fs.BasicFileAttributesHolder",
         "sun.nio.fs.DynamicFileAttributeView": "j$.sun.nio.fs.DynamicFileAttributeView",
-        "sun.util.PreHashedMap": "j$.sun.util.PreHashedMap"
+        "sun.util.PreHashedMap": "j$.sun.util.PreHashedMap",
+        "wrapper." : "j$.wrapper."
       },
       "rewrite_derived_prefix": {
-        "java.nio.file.attribute.": {
-          "j$.nio.file.attribute.": "java.nio.file.attribute."
+        "java.nio.file.attribute.FileTime": {
+          "j$.nio.file.attribute.FileTime": "java.nio.file.attribute.FileTime"
         },
         "java.io.": {
           "__wrapper__.j$.io.": "j$.io.",
diff --git a/src/library_desugar/jdk11/desugar_jdk_libs_alternative_3.json b/src/library_desugar/jdk11/desugar_jdk_libs_alternative_3.json
index 558386a..57f7c3e 100644
--- a/src/library_desugar/jdk11/desugar_jdk_libs_alternative_3.json
+++ b/src/library_desugar/jdk11/desugar_jdk_libs_alternative_3.json
@@ -55,7 +55,6 @@
       "api_level_below_or_equal": 23,
       "rewrite_prefix": {
         "java.io.DesugarBufferedReader": "j$.io.DesugarBufferedReader",
-        "java.io.UncheckedIOException": "j$.io.UncheckedIOException",
         "java.util.DoubleSummaryStatistics": "j$.util.DoubleSummaryStatistics",
         "java.util.IntSummaryStatistics": "j$.util.IntSummaryStatistics",
         "java.util.LongSummaryStatistics": "j$.util.LongSummaryStatistics",
@@ -70,6 +69,9 @@
         "java.util.function.": "j$.util.function.",
         "java.util.stream.": "j$.util.stream."
       },
+      "maintain_prefix": [
+        "java.io.UncheckedIOException"
+      ],
       "emulate_interface": {
         "java.lang.Iterable": "j$.lang.Iterable",
         "java.util.Collection": "j$.util.Collection",
@@ -223,24 +225,13 @@
   ],
   "library_flags": [
     {
-      "api_level_below_or_equal": 10000,
+      "api_level_below_or_equal": 32,
       "rewrite_prefix": {
         "desugar.": "j$.desugar.",
         "libcore.": "j$.libcore.",
         "java.lang.Desugar": "j$.lang.Desugar",
         "java.lang.ref.Cleaner": "j$.lang.ref.Cleaner",
-        "wrapper." : "j$.wrapper.",
         "sun.security.action.": "j$.sun.security.action."
-      },
-      "rewrite_derived_prefix": {
-        "java.io.": {
-          "__wrapper__.j$.io.": "j$.io.",
-          "__wrapper__.java.io.": "java.io."
-        },
-        "java.nio.": {
-          "__wrapper__.j$.nio.": "j$.nio.",
-          "__wrapper__.java.nio.": "java.nio."
-        }
       }
     },
     {
@@ -267,11 +258,20 @@
         "sun.nio.fs.AbstractFileTypeDetector": "j$.sun.nio.fs.AbstractFileTypeDetector",
         "sun.nio.fs.BasicFileAttributesHolder": "j$.sun.nio.fs.BasicFileAttributesHolder",
         "sun.nio.fs.DynamicFileAttributeView": "j$.sun.nio.fs.DynamicFileAttributeView",
-        "sun.util.PreHashedMap": "j$.sun.util.PreHashedMap"
+        "sun.util.PreHashedMap": "j$.sun.util.PreHashedMap",
+        "wrapper." : "j$.wrapper."
       },
       "rewrite_derived_prefix": {
-        "java.nio.file.attribute.": {
-          "j$.nio.file.attribute.": "java.nio.file.attribute."
+        "java.nio.file.attribute.FileTime": {
+          "j$.nio.file.attribute.FileTime": "java.nio.file.attribute.FileTime"
+        },
+        "java.io.": {
+          "__wrapper__.j$.io.": "j$.io.",
+          "__wrapper__.java.io.": "java.io."
+        },
+        "java.nio.": {
+          "__wrapper__.j$.nio.": "j$.nio.",
+          "__wrapper__.java.nio.": "java.nio."
         }
       },
       "retarget_method": {
diff --git a/src/library_desugar/jdk11/desugar_jdk_libs_human_comments.md b/src/library_desugar/jdk11/desugar_jdk_libs_human_comments.md
new file mode 100644
index 0000000..3fdcd72
--- /dev/null
+++ b/src/library_desugar/jdk11/desugar_jdk_libs_human_comments.md
@@ -0,0 +1,178 @@
+# Description of the human desugared library configuration file
+
+## Version
+
+The field `configuration_format_version` encodes a versioning number internal to
+R8/D8 in the form of an unsigned integer. It allows R8/D8 to know if the file
+given is supported. If the number if greater or equal to 100, the file is
+encoded using the human flags (by opposition to the legacy flags). Human flags
+are not shipped to external users. Human flags can be converted to machine flags
+which are shipped to external users. Users internal to Google are allowed to use
+directly human flags if we can easily update the file without backward
+compatibility issues.
+
+The field `identifier` is the maven-coordinated id for the desugared library
+configuration file.
+
+## Required compilation API level
+
+The field `required_compilation_api_level` encodes the minimal Android API level
+required for the desugared library to be compiled correctly. If the API of
+library used for compilation of the library or a program using the library is
+lower than this level, one has to upgrade the SDK version used to be able to use
+desugared libraries.
+
+## Synthesize prefix
+
+The field `synthesized_library_classes_package_prefix` is used both to prefix
+type names of synthetic classes created during the L8 compilation and for some
+of the rewritings.
+
+## Library callbacks
+
+The field `support_all_callbacks_from_library` is set if D8/R8 should generate
+extra callbacks, i.e., methods that may be called from specific library
+implementations into the program. Setting it to false may lead to invalid
+behavior if the library effectively use one of the callbacks, but reduces code
+size.
+
+## Common, library and program flags
+
+The fields `common_flags`, `library_flags` and `program_flags` include the set
+of rewriting flags required for respectively rewriting both the library and the
+program, only the library or only the program.
+
+The flags are in a list, where each list entry specifies up to which min API
+level the set of flags should be applied. During compilation, R8/D8 adds up all
+the required flags for the min API level specified at compilation.
+
+The following subsections describe each rewriting flag.
+
+### Flag rewrite_prefix
+
+`prefix: rewrittenPrefix`
+D8/R8 identifies any class type matching the prefix, and rewrite such types with
+the new prefix. Types not present as class types are not rewritten. Implicitly,
+all synthetic types derived from the matching type are also rewritten (lambdas
+and backports in the class, etc.).
+
+Example:
+`foo.: f$.`
+A class present with the type foo.Foo will generate a rewrite rule:
+foo.Foo -> f$.Foo. A type present foo.Bar, which is not the type of any class,
+will not generate any rewrite rule.
+
+### Flag rewrite_derived_prefix
+
+`prefix: { fromPrefix: toPrefix }`
+D8/R8 identifies any class type matching the prefix, and rewrite the type with
+the fromPrefix to the type with the toPrefix. This can be useful to generate
+rewrite rules from types not present in the input.
+
+Example:
+`foo.: { f$.: foo. }`
+A class present with the type foo.Foo will generate a rewrite rule:
+f$.Foo -> foo.Foo.
+
+### Flag retarget_method
+
+`methodToRetarget: retargetType`
+D8/R8 identifies all invokes which method resolve to the methodToRetarget, and
+rewrite it to an invoke to the same method but with the retargetType as holder.
+If the method is virtual, this converts the invoke to an invoke-static and adds
+the receiver type as the first parameter.
+
+The retargeting is valid for static methods, private methods and methods
+effectively final (methods with the final keyword, methods on final classes,
+which do not override any other method).
+
+When using the flag, the method, if virtual, is considered as effectively final.
+For retargeting of virtual methods that can be overridden, see
+retarget_method_with_emulated_dispatch.
+
+Example:
+`Foo Bar#foo(Zorg): DesugarBar`
+Any invoke which method resolves into the method with return type Foo, name foo,
+parameter Zorg on the holder Bar, is rewritten to an invoke-static to the same
+method on DesugarBar. If the method is not static, the rewritten method takes an
+extra first parameter of type Bar.
+
+### Flag retarget_method_with_emulated_dispatch
+
+`methodToRetarget: retargetType`
+Essentially the same as retarget_method, but for non effectively final virtual
+method. The flag fails the compilation if the methodToRetarget is static. D8/R8
+generates an emulated dispatch scheme so that the method can be retargeted, but
+the virtual dispatch is still valid and will correctly call the overrides if
+present.
+
+### Flag amend_library_method
+
+`modifiers method`
+For the retarget_method and retarget_method_with_emulated_dispatch flags to
+work, resolution has to find the method to retarget to. In some cases, the
+method is missing because it's not present on the required compilation level
+Android SDK, or because the method is private.
+
+This flag amends the library to introduce the method, so resolution can find it
+and retarget it correctly.
+
+### Flag dont_retarget
+
+`type`
+In classes with such type, invokes are not retargeted with the retarget_method
+and the retarget_method_with_emulated_dispatch flag. In addition, forwarding
+methods required for retarget_method_with_emulated_dispatch are not introduced
+in such classes.
+
+### Flag emulate_interface
+
+`libraryInterface: desugaredLibraryInterface`
+D8/R8 assume the libraryInterface is already in the library, but without the
+default and static methods present on it. It generates a companion class holding
+the code for the default and static methods, and a dispatch class which hold the
+code to support emulated dispatch for the default methods.
+
+### Flag dont_rewrite
+
+`methodNotToRewrite`
+D8/R8 ignroes the methods present here from the emulated interface.
+
+### Flag wrapper_conversion
+
+`type`
+Generate wrappers for the given type, including methods from the type and all
+its super types and interface types. In addition, analyse all invokes resolving
+into the library. If the invoke includes the type as return or parameter type,
+automatically surround the library call with conversion code using wrappers. The
+sequence of instructions with the conversions and the library invoke is outlined
+and shared if possible.
+
+### Flag wrapper_conversion_excluding
+
+`type: [methods]`
+Similar to wrapper_conversion, generate wrappers for the given type but ignore
+the methods listed. This can be used for methods not accessing fields or private
+methods, either to reduce code size or to work around final methods.
+
+### Flag custom_conversion
+
+`type: conversionType`
+Similar to wrapper_conversion, but instead of generating wrappers, rely on hand
+written conversions present on conversionType. The conversions methods must be
+of the form:
+Type convert(RewrittenType)
+RewrittenType convert(Type)
+
+## Extra keep rules
+
+The last field is `extra_keep_rules`, it includes keep rules that are appended
+by L8 when shrinking the desugared library. It includes keep rules related to
+reflection inside the desugared library, related to enum to have EnumSet working
+and to keep the j$ prefix.
+
+## Copyright
+
+Copyright (c) 2022, the R8 project authors. Please see the AUTHORS file for
+details. All rights reserved. Use of this source code is governed by a BSD-style
+license that can be found in the LICENSE file.
\ No newline at end of file
diff --git a/src/main/java/com/android/tools/r8/D8.java b/src/main/java/com/android/tools/r8/D8.java
index 43868c3..26a14b4 100644
--- a/src/main/java/com/android/tools/r8/D8.java
+++ b/src/main/java/com/android/tools/r8/D8.java
@@ -20,6 +20,7 @@
 import com.android.tools.r8.graph.LazyLoadedDexApplication;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.graph.analysis.ClassInitializerAssertionEnablingAnalysis;
+import com.android.tools.r8.horizontalclassmerging.HorizontalClassMerger;
 import com.android.tools.r8.inspector.internal.InspectorImpl;
 import com.android.tools.r8.ir.conversion.IRConverter;
 import com.android.tools.r8.ir.desugar.TypeRewriter;
@@ -276,57 +277,54 @@
       namingLens = PrefixRewritingNamingLens.createPrefixRewritingNamingLens(appView, namingLens);
       namingLens = RecordRewritingNamingLens.createRecordRewritingNamingLens(appView, namingLens);
 
+      if (options.isGeneratingDex()
+          && hasDexResources
+          && hasClassResources
+          && appView.typeRewriter.isRewriting()) {
+        // There are both cf and dex inputs in the program, and rewriting is required for
+        // desugared library only on cf inputs. We cannot easily rewrite part of the program
+        // without iterating again the IR. We fall-back to writing one app with rewriting and
+        // merging it with the other app in rewriteNonDexInputs.
+        timing.begin("Rewrite non-dex inputs");
+        DexApplication app =
+            rewriteNonDexInputs(
+                appView, inputApp, options, executor, timing, appView.appInfo().app(), namingLens);
+        timing.end();
+        appView.setAppInfo(
+            new AppInfo(
+                appView.appInfo().getSyntheticItems().commit(app),
+                appView.appInfo().getMainDexInfo()));
+        namingLens = NamingLens.getIdentityLens();
+      } else if (options.isGeneratingDex() && hasDexResources) {
+        namingLens = NamingLens.getIdentityLens();
+      }
+
+      // Since tracing is not lens aware, this needs to be done prior to synthetic finalization
+      // which will construct a graph lens.
+      if (options.isGeneratingDex() && !options.mainDexKeepRules.isEmpty()) {
+        appView.dexItemFactory().clearTypeElementsCache();
+        MainDexInfo mainDexInfo =
+            new GenerateMainDexList(options)
+                .traceMainDex(
+                    executor, appView.appInfo().app(), appView.appInfo().getMainDexInfo());
+        appView.setAppInfo(appView.appInfo().rebuildWithMainDexInfo(mainDexInfo));
+      }
+
+      finalizeApplication(appView, executor);
+
+      HorizontalClassMerger.createForD8ClassMerging(appView).runIfNecessary(executor, timing);
+
+      new GenericSignatureRewriter(appView, namingLens)
+          .runForD8(appView.appInfo().classes(), executor);
+      new KotlinMetadataRewriter(appView, namingLens).runForD8(executor);
+
       if (options.isGeneratingClassFiles()) {
-        finalizeApplication(appView, executor);
         new CfApplicationWriter(appView, marker, namingLens)
             .write(options.getClassFileConsumer(), inputApp);
       } else {
-        if (!hasDexResources || !hasClassResources || !appView.typeRewriter.isRewriting()) {
-          // All inputs are either dex or cf, or there is nothing to rewrite.
-          namingLens = hasDexResources ? NamingLens.getIdentityLens() : namingLens;
-          new GenericSignatureRewriter(appView, namingLens)
-              .run(appView.appInfo().classes(), executor);
-          new KotlinMetadataRewriter(appView, namingLens).runForD8(executor);
-        } else {
-          // There are both cf and dex inputs in the program, and rewriting is required for
-          // desugared library only on cf inputs. We cannot easily rewrite part of the program
-          // without iterating again the IR. We fall-back to writing one app with rewriting and
-          // merging it with the other app in rewriteNonDexInputs.
-          timing.begin("Rewrite non-dex inputs");
-          DexApplication app =
-              rewriteNonDexInputs(
-                  appView,
-                  inputApp,
-                  options,
-                  executor,
-                  timing,
-                  appView.appInfo().app(),
-                  namingLens);
-          timing.end();
-          appView.setAppInfo(
-              new AppInfo(
-                  appView.appInfo().getSyntheticItems().commit(app),
-                  appView.appInfo().getMainDexInfo()));
-          namingLens = NamingLens.getIdentityLens();
-        }
-
-        // Since tracing is not lens aware, this needs to be done prior to synthetic finalization
-        // which will construct a graph lens.
-        if (!options.mainDexKeepRules.isEmpty()) {
-          appView.dexItemFactory().clearTypeElementsCache();
-          MainDexInfo mainDexInfo =
-              new GenerateMainDexList(options)
-                  .traceMainDex(
-                      executor, appView.appInfo().app(), appView.appInfo().getMainDexInfo());
-          appView.setAppInfo(appView.appInfo().rebuildWithMainDexInfo(mainDexInfo));
-        }
-
-        finalizeApplication(appView, executor);
-
-        if (options.apiModelingOptions().enableStubbingOfClasses && !appView.options().debug) {
+        if (options.apiModelingOptions().enableStubbingOfClasses) {
           new ApiReferenceStubber(appView).run(executor);
         }
-
         new ApplicationWriter(
                 appView,
                 marker == null ? null : ImmutableList.copyOf(markers),
diff --git a/src/main/java/com/android/tools/r8/D8Command.java b/src/main/java/com/android/tools/r8/D8Command.java
index 583c6bb..acbd677 100644
--- a/src/main/java/com/android/tools/r8/D8Command.java
+++ b/src/main/java/com/android/tools/r8/D8Command.java
@@ -10,7 +10,6 @@
 import com.android.tools.r8.dump.DumpOptions;
 import com.android.tools.r8.errors.DexFileOverflowDiagnostic;
 import com.android.tools.r8.graph.DexItemFactory;
-import com.android.tools.r8.horizontalclassmerging.HorizontalClassMerger;
 import com.android.tools.r8.inspector.Inspector;
 import com.android.tools.r8.inspector.internal.InspectorImpl;
 import com.android.tools.r8.ir.desugar.desugaredlibrary.DesugaredLibrarySpecification;
@@ -24,6 +23,7 @@
 import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.AssertionConfigurationWithDefault;
 import com.android.tools.r8.utils.DumpInputFlags;
+import com.android.tools.r8.utils.InternalGlobalSyntheticsProgramProvider;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.InternalOptions.DesugarState;
 import com.android.tools.r8.utils.InternalOptions.HorizontalClassMergerOptions;
@@ -83,6 +83,9 @@
   public static class Builder extends BaseCompilerCommand.Builder<D8Command, Builder> {
 
     private boolean intermediate = false;
+    private GlobalSyntheticsConsumer globalSyntheticsConsumer = null;
+    private List<GlobalSyntheticsResourceProvider> globalSyntheticsResourceProviders =
+        new ArrayList<>();
     private DesugarGraphConsumer desugarGraphConsumer = null;
     private StringConsumer desugaredLibraryKeepRuleConsumer = null;
     private String synthesizedClassPrefix = "";
@@ -145,6 +148,16 @@
     /**
      * Indicate if compilation is to intermediate results, i.e., intended for later merging.
      *
+     * <p>When compiling to intermediate mode, the compiler will avoid sharing of synthetic items,
+     * and instead annotate them as synthetics for possible later merging. For global synthetics,
+     * the compiler will emit these to a separate consumer (see {@code GlobalSyntheticsConsumer}
+     * with the expectation that a later build step will consume them again as part of a
+     * non-intermediate build (see {@code GlobalSyntheticsResourceProvider}. Synthetic items
+     * typically come from the desugaring of various language features, such as lambdas and default
+     * interface methods. Global synthetics are non-local in that many compilation units may
+     * reference the same synthetic. For example, desugaring records requires a global tag to
+     * distinguish the class of all records.
+     *
      * <p>Intermediate mode is implied if compiling results to a "file-per-class-file".
      */
     public Builder setIntermediate(boolean value) {
@@ -153,6 +166,43 @@
     }
 
     /**
+     * Set a consumer for receiving the global synthetic content for the given compilation.
+     *
+     * <p>Note: this consumer is ignored if the compilation is not an "intermediate mode"
+     * compilation.
+     */
+    public Builder setGlobalSyntheticsConsumer(GlobalSyntheticsConsumer globalSyntheticsConsumer) {
+      this.globalSyntheticsConsumer = globalSyntheticsConsumer;
+      return self();
+    }
+
+    /** Add global synthetics resource providers. */
+    public Builder addGlobalSyntheticsResourceProviders(
+        GlobalSyntheticsResourceProvider... providers) {
+      return addGlobalSyntheticsResourceProviders(Arrays.asList(providers));
+    }
+
+    /** Add global synthetics resource providers. */
+    public Builder addGlobalSyntheticsResourceProviders(
+        Collection<GlobalSyntheticsResourceProvider> providers) {
+      providers.forEach(globalSyntheticsResourceProviders::add);
+      return self();
+    }
+
+    /** Add global synthetics resource files. */
+    public Builder addGlobalSyntheticsFiles(Path... files) {
+      return addGlobalSyntheticsFiles(Arrays.asList(files));
+    }
+
+    /** Add global synthetics resource files. */
+    public Builder addGlobalSyntheticsFiles(Collection<Path> files) {
+      for (Path file : files) {
+        addGlobalSyntheticsResourceProviders(new GlobalSyntheticsResourceFile(file));
+      }
+      return self();
+    }
+
+    /**
      * Set a consumer for receiving the keep rules to use when compiling the desugared library for
      * the program being compiled in this compilation.
      *
@@ -295,6 +345,11 @@
       ImmutableList<ProguardConfigurationRule> mainDexKeepRules =
           ProguardConfigurationParser.parse(mainDexRules, factory, getReporter());
 
+      if (!globalSyntheticsResourceProviders.isEmpty()) {
+        addProgramResourceProvider(
+            new InternalGlobalSyntheticsProgramProvider(globalSyntheticsResourceProviders));
+      }
+
       return new D8Command(
           getAppBuilder().build(),
           getMode(),
@@ -304,6 +359,7 @@
           getReporter(),
           getDesugaringState(),
           intermediate,
+          intermediate ? globalSyntheticsConsumer : null,
           isOptimizeMultidexForLinearAlloc(),
           getIncludeClassesChecksum(),
           getDexClassChecksumFilter(),
@@ -327,6 +383,7 @@
   static final String USAGE_MESSAGE = D8CommandParser.USAGE_MESSAGE;
 
   private final boolean intermediate;
+  private final GlobalSyntheticsConsumer globalSyntheticsConsumer;
   private final DesugarGraphConsumer desugarGraphConsumer;
   private final StringConsumer desugaredLibraryKeepRuleConsumer;
   private final DesugaredLibrarySpecification desugaredLibrarySpecification;
@@ -386,6 +443,7 @@
       Reporter diagnosticsHandler,
       DesugarState enableDesugaring,
       boolean intermediate,
+      GlobalSyntheticsConsumer globalSyntheticsConsumer,
       boolean optimizeMultidexForLinearAlloc,
       boolean encodeChecksum,
       BiPredicate<String, Long> dexClassChecksumFilter,
@@ -421,6 +479,7 @@
         mapIdProvider,
         null);
     this.intermediate = intermediate;
+    this.globalSyntheticsConsumer = globalSyntheticsConsumer;
     this.desugarGraphConsumer = desugarGraphConsumer;
     this.desugaredLibraryKeepRuleConsumer = desugaredLibraryKeepRuleConsumer;
     this.desugaredLibrarySpecification = desugaredLibrarySpecification;
@@ -435,6 +494,7 @@
   private D8Command(boolean printHelp, boolean printVersion) {
     super(printHelp, printVersion);
     intermediate = false;
+    globalSyntheticsConsumer = null;
     desugarGraphConsumer = null;
     desugaredLibraryKeepRuleConsumer = null;
     desugaredLibrarySpecification = null;
@@ -469,6 +529,7 @@
     internal.setMinApiLevel(AndroidApiLevel.getAndroidApiLevel(getMinApiLevel()));
     internal.intermediate = intermediate;
     internal.retainCompileTimeAnnotations = intermediate;
+    internal.setGlobalSyntheticsConsumer(globalSyntheticsConsumer);
     internal.desugarGraphConsumer = desugarGraphConsumer;
     internal.mainDexKeepRules = mainDexKeepRules;
     internal.lineNumberOptimization = LineNumberOptimization.OFF;
@@ -478,7 +539,6 @@
     assert !internal.isMinifying();
     assert !internal.passthroughDexCode;
     internal.passthroughDexCode = true;
-    assert internal.neverMergePrefixes.contains("j$.");
 
     // Assert some of R8 optimizations are disabled.
     assert !internal.inlinerOptions().enableInlining;
@@ -516,12 +576,14 @@
     // Disable global optimizations.
     internal.disableGlobalOptimizations();
 
-    // TODO(b/187675788): Enable class merging for synthetics in D8.
     HorizontalClassMergerOptions horizontalClassMergerOptions =
         internal.horizontalClassMergerOptions();
-    horizontalClassMergerOptions.disable();
-    assert !horizontalClassMergerOptions.isEnabled(HorizontalClassMerger.Mode.INITIAL);
-    assert !horizontalClassMergerOptions.isEnabled(HorizontalClassMerger.Mode.FINAL);
+    if (internal.isGeneratingDex()) {
+      horizontalClassMergerOptions.setRestrictToSynthetics();
+    } else {
+      assert internal.isGeneratingClassFiles();
+      horizontalClassMergerOptions.disable();
+    }
 
     internal.setDumpInputFlags(getDumpInputFlags(), skipDump);
     internal.dumpOptions = dumpOptions();
diff --git a/src/main/java/com/android/tools/r8/DesugarGraphConsumer.java b/src/main/java/com/android/tools/r8/DesugarGraphConsumer.java
index 2210490..fd553b3 100644
--- a/src/main/java/com/android/tools/r8/DesugarGraphConsumer.java
+++ b/src/main/java/com/android/tools/r8/DesugarGraphConsumer.java
@@ -10,6 +10,29 @@
 public interface DesugarGraphConsumer {
 
   /**
+   * Callback indicating that the {@code node} is a program input which is part of the current
+   * compilation unit for desugaring.
+   *
+   * <p>Note: this callback is guaranteed to be called on every *program-input* origin that could be
+   * passed as a {@code dependent} in a callback to {@code accept(Orign dependent, Origin
+   * dependency)}. It is also guaranteed to be called before any such call. In effect, this callback
+   * will receive the complete set of program-input origins for the compilation unit that is being
+   * desugared and it can reliably be used to remove any existing and potentially stale edges
+   * pertaining to those origins from a dependency graph maintained in the client.
+   *
+   * <p>Note: this will not receive a callback for classpath origins.
+   *
+   * <p>Note: this callback may be called on multiple threads.
+   *
+   * <p>Note: this callback places no guarantees on order of calls or on duplicate calls.
+   *
+   * @param node Origin of code that is part of the program input in the compilation unit.
+   */
+  default void acceptProgramNode(Origin node) {
+    // Default behavior ignores the node callbacks.
+  }
+
+  /**
    * Callback indicating that code originating from {@code dependency} is needed to correctly
    * desugar code originating from {@code dependent}.
    *
diff --git a/src/main/java/com/android/tools/r8/GlobalSyntheticsConsumer.java b/src/main/java/com/android/tools/r8/GlobalSyntheticsConsumer.java
new file mode 100644
index 0000000..ea8ad11
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/GlobalSyntheticsConsumer.java
@@ -0,0 +1,31 @@
+// Copyright (c) 2022, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8;
+
+/**
+ * Consumer receiving the data representing global synthetics for the program.
+ *
+ * <p>Global synthetic information is only produced as part of D8 intermediate builds (e.g., for
+ * incremental compilation.) The global synthetic information represents desugaring content that may
+ * be duplicated among many intermediate-mode builds and will need to be merged to ensure a valid
+ * final program (i.e., a program that does not contain any duplicate definitions).
+ *
+ * <p>The data obtained for global synthetics must be passed to the subsequent compilation unit that
+ * builds a non-intermediate output. That compilation output can then be packaged as a final
+ * application. It is valid to merge just the globals in such a final step. See {@code
+ * GlobalSyntheticsResourceProvider}.
+ */
+@Keep
+public interface GlobalSyntheticsConsumer {
+
+  /**
+   * Callback to receive the data representing the global synthetics for the program.
+   *
+   * <p>The encoding of the global synthetics is compiler internal and may vary between compiler
+   * versions. The data received here is thus only valid as inputs to the same compiler version.
+   *
+   * @param bytes Opaque encoding of the global synthetics for the program.
+   */
+  void accept(byte[] bytes);
+}
diff --git a/src/main/java/com/android/tools/r8/GlobalSyntheticsResourceFile.java b/src/main/java/com/android/tools/r8/GlobalSyntheticsResourceFile.java
new file mode 100644
index 0000000..598a074
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/GlobalSyntheticsResourceFile.java
@@ -0,0 +1,36 @@
+// Copyright (c) 2022, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8;
+
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.origin.PathOrigin;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.file.Files;
+import java.nio.file.Path;
+
+public class GlobalSyntheticsResourceFile implements GlobalSyntheticsResourceProvider {
+
+  private final Path file;
+  private final Origin origin;
+
+  public GlobalSyntheticsResourceFile(Path file) {
+    this.file = file;
+    this.origin = new PathOrigin(file);
+  }
+
+  @Override
+  public Origin getOrigin() {
+    return origin;
+  }
+
+  @Override
+  public InputStream getByteStream() throws ResourceException {
+    try {
+      return Files.newInputStream(file);
+    } catch (IOException e) {
+      throw new ResourceException(origin, e);
+    }
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/GlobalSyntheticsResourceProvider.java b/src/main/java/com/android/tools/r8/GlobalSyntheticsResourceProvider.java
new file mode 100644
index 0000000..4cfdbb1
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/GlobalSyntheticsResourceProvider.java
@@ -0,0 +1,23 @@
+// Copyright (c) 2022, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8;
+
+import com.android.tools.r8.origin.Origin;
+import java.io.InputStream;
+
+/**
+ * Interface to provide global synthetic information to the compiler.
+ *
+ * <p>The global synthetic information can only be obtained by consuming it from a previous
+ * compilation unit for the same compiler version. See {@code GlobalSyntheticsConsumer}.
+ */
+@Keep
+public interface GlobalSyntheticsResourceProvider {
+
+  /** Get the origin of the global synthetics resource. */
+  Origin getOrigin();
+
+  /** Get the bytes of the global synthetics resource. */
+  InputStream getByteStream() throws ResourceException;
+}
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java
index d479e46..1c8ba99 100644
--- a/src/main/java/com/android/tools/r8/R8.java
+++ b/src/main/java/com/android/tools/r8/R8.java
@@ -406,6 +406,11 @@
           GenericSignatureCorrectnessHelper.createForInitialCheck(appView, genericContextBuilder)
               .run(appView.appInfo().classes());
 
+          // TODO(b/226539525): Implement enum lite proto shrinking as deferred tracing.
+          if (appView.options().protoShrinking().isEnumLiteProtoShrinkingEnabled()) {
+            appView.protoShrinker().enumLiteProtoShrinker.clearDeadEnumLiteMaps();
+          }
+
           TreePruner pruner = new TreePruner(appViewWithLiveness);
           DirectMappedDexApplication prunedApp = pruner.run(executorService);
 
@@ -422,10 +427,6 @@
                   appViewWithLiveness, appViewWithLiveness.appInfo().computeSubtypingInfo())
               .run();
 
-          if (appView.options().protoShrinking().isEnumLiteProtoShrinkingEnabled()) {
-            appView.protoShrinker().enumLiteProtoShrinker.clearDeadEnumLiteMaps();
-          }
-
           AnnotationRemover annotationRemover =
               annotationRemoverBuilder
                   .build(appViewWithLiveness, removedClasses);
@@ -508,7 +509,7 @@
         assert appView.verticallyMergedClasses() != null;
 
         HorizontalClassMerger.createForInitialClassMerging(appViewWithLiveness)
-            .runIfNecessary(runtimeTypeCheckInfo, executorService, timing);
+            .runIfNecessary(executorService, timing, runtimeTypeCheckInfo);
       }
 
       new ProtoNormalizer(appViewWithLiveness).run(executorService, timing);
@@ -751,11 +752,11 @@
       // are always merged.
       HorizontalClassMerger.createForFinalClassMerging(appView)
           .runIfNecessary(
+              executorService,
+              timing,
               classMergingEnqueuerExtensionBuilder != null
                   ? classMergingEnqueuerExtensionBuilder.build(appView.graphLens())
-                  : null,
-              executorService,
-              timing);
+                  : null);
 
       // Perform minification.
       NamingLens namingLens;
@@ -975,7 +976,9 @@
       return true;
     }
     for (DexDebugEvent event : code.getDebugInfo().asEventBasedInfo().events) {
-      assert !event.isSetInlineFrame() || event.asSetInlineFrame().hasOuterPosition(originalMethod);
+      assert !event.isPositionFrame()
+          || event.asSetPositionFrame().getPosition().getOutermostCaller().getMethod()
+              == originalMethod;
     }
     return true;
   }
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 5725955..15bf742 100644
--- a/src/main/java/com/android/tools/r8/androidapi/ApiReferenceStubber.java
+++ b/src/main/java/com/android/tools/r8/androidapi/ApiReferenceStubber.java
@@ -4,6 +4,7 @@
 
 package com.android.tools.r8.androidapi;
 
+import com.android.tools.r8.errors.MissingGlobalSyntheticsConsumerDiagnostic;
 import com.android.tools.r8.graph.AppInfo;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DefaultInstanceInitializerCode;
@@ -233,7 +234,8 @@
     appView
         .appInfo()
         .getSyntheticItems()
-        .ensureFixedClassFromType(
+        .ensureGlobalClass(
+            () -> new MissingGlobalSyntheticsConsumerDiagnostic("API stubbing"),
             kinds -> kinds.API_MODEL_STUB,
             libraryClass.getType(),
             appView,
diff --git a/src/main/java/com/android/tools/r8/cf/CfRegisterAllocator.java b/src/main/java/com/android/tools/r8/cf/CfRegisterAllocator.java
index 28d7592..351a3a1 100644
--- a/src/main/java/com/android/tools/r8/cf/CfRegisterAllocator.java
+++ b/src/main/java/com/android/tools/r8/cf/CfRegisterAllocator.java
@@ -175,8 +175,7 @@
   private ImmutableList<BasicBlock> computeLivenessInformation() {
     ImmutableList<BasicBlock> blocks = code.numberInstructions();
     liveAtEntrySets = code.computeLiveAtEntrySets();
-    LinearScanRegisterAllocator.computeLiveRanges(
-        appView.options(), code, liveAtEntrySets, liveIntervals);
+    LinearScanRegisterAllocator.computeLiveRanges(appView, code, liveAtEntrySets, liveIntervals);
     return blocks;
   }
 
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfArithmeticBinop.java b/src/main/java/com/android/tools/r8/cf/code/CfArithmeticBinop.java
index efad6db..67e44d4 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfArithmeticBinop.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfArithmeticBinop.java
@@ -162,6 +162,11 @@
   }
 
   @Override
+  public int bytecodeSizeUpperBound() {
+    return 1;
+  }
+
+  @Override
   public boolean canThrow() {
     return (type != NumericType.FLOAT && type != NumericType.DOUBLE)
         && (opcode == Opcode.Div || opcode == Opcode.Rem);
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfArrayLength.java b/src/main/java/com/android/tools/r8/cf/code/CfArrayLength.java
index 0af6a11..35a03f2 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfArrayLength.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfArrayLength.java
@@ -40,6 +40,11 @@
   }
 
   @Override
+  public int bytecodeSizeUpperBound() {
+    return 1;
+  }
+
+  @Override
   public int getCompareToId() {
     return Opcodes.ARRAYLENGTH;
   }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfArrayLoad.java b/src/main/java/com/android/tools/r8/cf/code/CfArrayLoad.java
index 44f0e60..dc6a732 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfArrayLoad.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfArrayLoad.java
@@ -89,6 +89,11 @@
   }
 
   @Override
+  public int bytecodeSizeUpperBound() {
+    return 1;
+  }
+
+  @Override
   public void print(CfPrinter printer) {
     printer.print(this);
   }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfArrayStore.java b/src/main/java/com/android/tools/r8/cf/code/CfArrayStore.java
index 68d6b2d..39db93e 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfArrayStore.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfArrayStore.java
@@ -87,6 +87,11 @@
   }
 
   @Override
+  public int bytecodeSizeUpperBound() {
+    return 1;
+  }
+
+  @Override
   public void print(CfPrinter printer) {
     printer.print(this);
   }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfCheckCast.java b/src/main/java/com/android/tools/r8/cf/code/CfCheckCast.java
index 6098e98..edd6603 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfCheckCast.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfCheckCast.java
@@ -92,6 +92,11 @@
   }
 
   @Override
+  public int bytecodeSizeUpperBound() {
+    return 3;
+  }
+
+  @Override
   public void print(CfPrinter printer) {
     printer.print(this);
   }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfCmp.java b/src/main/java/com/android/tools/r8/cf/code/CfCmp.java
index c997da1..5b3c937 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfCmp.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfCmp.java
@@ -112,6 +112,11 @@
   }
 
   @Override
+  public int bytecodeSizeUpperBound() {
+    return 1;
+  }
+
+  @Override
   public void buildIR(IRBuilder builder, CfState state, CfSourceCode code) {
     int right = state.pop().register;
     int left = state.pop().register;
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfConstClass.java b/src/main/java/com/android/tools/r8/cf/code/CfConstClass.java
index c135288..abc6070 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfConstClass.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfConstClass.java
@@ -38,6 +38,9 @@
   }
 
   public CfConstClass(DexType type, boolean ignoreCompatRules) {
+    // Primitive types and void should be retrieved using, for example, java.lang.Integer.TYPE.
+    assert !type.isPrimitiveType();
+    assert !type.isVoidType();
     this.type = type;
     this.ignoreCompatRules = ignoreCompatRules;
   }
@@ -91,6 +94,12 @@
   }
 
   @Override
+  public int bytecodeSizeUpperBound() {
+    // ldc or ldc_w
+    return 3;
+  }
+
+  @Override
   public void print(CfPrinter printer) {
     printer.print(this);
   }
@@ -106,22 +115,6 @@
       case '[':
       case 'L':
         return namingLens.lookupInternalName(rewrittenType);
-      case 'Z':
-        return "java/lang/Boolean/TYPE";
-      case 'B':
-        return "java/lang/Byte/TYPE";
-      case 'S':
-        return "java/lang/Short/TYPE";
-      case 'C':
-        return "java/lang/Character/TYPE";
-      case 'I':
-        return "java/lang/Integer/TYPE";
-      case 'F':
-        return "java/lang/Float/TYPE";
-      case 'J':
-        return "java/lang/Long/TYPE";
-      case 'D':
-        return "java/lang/Double/TYPE";
       default:
         throw new Unreachable("Unexpected type in const-class: " + rewrittenType);
     }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfConstDynamic.java b/src/main/java/com/android/tools/r8/cf/code/CfConstDynamic.java
index 4efa8ae..3416ff7 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfConstDynamic.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfConstDynamic.java
@@ -189,6 +189,12 @@
   }
 
   @Override
+  public int bytecodeSizeUpperBound() {
+    // ldc or ldc_w
+    return 3;
+  }
+
+  @Override
   public void print(CfPrinter printer) {
     printer.print(this);
   }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfConstMethodHandle.java b/src/main/java/com/android/tools/r8/cf/code/CfConstMethodHandle.java
index 5f5837a..48db7ef 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfConstMethodHandle.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfConstMethodHandle.java
@@ -67,6 +67,12 @@
   }
 
   @Override
+  public int bytecodeSizeUpperBound() {
+    // ldc or ldc_w
+    return 3;
+  }
+
+  @Override
   public void print(CfPrinter printer) {
     printer.print(this);
   }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfConstMethodType.java b/src/main/java/com/android/tools/r8/cf/code/CfConstMethodType.java
index 3ef0e8d..855690f 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfConstMethodType.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfConstMethodType.java
@@ -65,6 +65,12 @@
   }
 
   @Override
+  public int bytecodeSizeUpperBound() {
+    // ldc or ldc_w
+    return 3;
+  }
+
+  @Override
   public void print(CfPrinter printer) {
     printer.print(this);
   }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfConstNull.java b/src/main/java/com/android/tools/r8/cf/code/CfConstNull.java
index 4d7dbb7..d58b161 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfConstNull.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfConstNull.java
@@ -40,6 +40,11 @@
   }
 
   @Override
+  public int bytecodeSizeUpperBound() {
+    return 1;
+  }
+
+  @Override
   public int getCompareToId() {
     return Opcodes.ACONST_NULL;
   }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfConstNumber.java b/src/main/java/com/android/tools/r8/cf/code/CfConstNumber.java
index ca9cdf0..b1d9c5b 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfConstNumber.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfConstNumber.java
@@ -36,6 +36,7 @@
   }
 
   public CfConstNumber(long value, ValueType type) {
+    assert !type.isObject() : "Should use CfConstNull";
     this.value = value;
     this.type = type;
   }
@@ -145,6 +146,64 @@
     }
   }
 
+  @Override
+  public int bytecodeSizeUpperBound() {
+    switch (type) {
+      case INT:
+        {
+          int value = getIntValue();
+          if (-1 <= value && value <= 5) {
+            // iconst_0 .. iconst_5
+            return 1;
+          } else if (Byte.MIN_VALUE <= value && value <= Byte.MAX_VALUE) {
+            // bipush byte
+            return 2;
+          } else if (Short.MIN_VALUE <= value && value <= Short.MAX_VALUE) {
+            // sipush byte1 byte2
+            return 3;
+          } else {
+            // ldc or ldc_w
+            return 3;
+          }
+        }
+      case LONG:
+        {
+          long value = getLongValue();
+          if (value == 0 || value == 1) {
+            // lconst_0 .. lconst_1
+            return 1;
+          } else {
+            // ldc or ldc_w
+            return 3;
+          }
+        }
+      case FLOAT:
+        {
+          float value = getFloatValue();
+          if (value == 0 || value == 1 || value == 2) {
+            // fconst_0 .. fconst_2 followed by fneg if negative
+            return isNegativeZeroFloat(value) ? 2 : 1;
+          } else {
+            // ldc or ldc_w
+            return 3;
+          }
+        }
+      case DOUBLE:
+        {
+          double value = getDoubleValue();
+          if (value == 0 || value == 1) {
+            // dconst_0 .. dconst_2 followed by dneg if negative
+            return isNegativeZeroDouble(value) ? 2 : 1;
+          } else {
+            // ldc2_w
+            return 3;
+          }
+        }
+      default:
+        throw new Unreachable("Non supported type in cf backend: " + type);
+    }
+  }
+
   public static boolean isNegativeZeroDouble(double value) {
     return Double.doubleToLongBits(value) == Double.doubleToLongBits(-0.0);
   }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfConstString.java b/src/main/java/com/android/tools/r8/cf/code/CfConstString.java
index 1b46a48..f03e9c7 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfConstString.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfConstString.java
@@ -74,6 +74,12 @@
   }
 
   @Override
+  public int bytecodeSizeUpperBound() {
+    // ldc or ldc_w
+    return 3;
+  }
+
+  @Override
   public void print(CfPrinter printer) {
     printer.print(this);
   }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfDexItemBasedConstString.java b/src/main/java/com/android/tools/r8/cf/code/CfDexItemBasedConstString.java
index 0a6887a..cdc4235 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfDexItemBasedConstString.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfDexItemBasedConstString.java
@@ -82,6 +82,12 @@
   }
 
   @Override
+  public int bytecodeSizeUpperBound() {
+    // ldc or ldc_w
+    return 3;
+  }
+
+  @Override
   public void print(CfPrinter printer) {
     printer.print(this);
   }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfFieldInstruction.java b/src/main/java/com/android/tools/r8/cf/code/CfFieldInstruction.java
index 99c9151..29dc73f 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfFieldInstruction.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfFieldInstruction.java
@@ -122,6 +122,11 @@
   }
 
   @Override
+  public int bytecodeSizeUpperBound() {
+    return 3;
+  }
+
+  @Override
   public void print(CfPrinter printer) {
     printer.print(this);
   }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfFrame.java b/src/main/java/com/android/tools/r8/cf/code/CfFrame.java
index 31bc71c..7f7f08b 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfFrame.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfFrame.java
@@ -404,6 +404,11 @@
     visitor.visitFrame(F_NEW, localsCount, localsTypes, stackCount, stackTypes);
   }
 
+  @Override
+  public int bytecodeSizeUpperBound() {
+    return 0;
+  }
+
   private int computeStackCount() {
     return stack.size();
   }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfGoto.java b/src/main/java/com/android/tools/r8/cf/code/CfGoto.java
index eb82f47..fa3a8a5 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfGoto.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfGoto.java
@@ -76,6 +76,11 @@
   }
 
   @Override
+  public int bytecodeSizeUpperBound() {
+    return 3;
+  }
+
+  @Override
   public void print(CfPrinter printer) {
     printer.print(this);
   }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfIf.java b/src/main/java/com/android/tools/r8/cf/code/CfIf.java
index 9db12ec..833ac44 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfIf.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfIf.java
@@ -104,6 +104,11 @@
   }
 
   @Override
+  public int bytecodeSizeUpperBound() {
+    return 3;
+  }
+
+  @Override
   public boolean isConditionalJump() {
     return true;
   }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfIfCmp.java b/src/main/java/com/android/tools/r8/cf/code/CfIfCmp.java
index 2340a89..c8129a6 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfIfCmp.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfIfCmp.java
@@ -105,6 +105,11 @@
   }
 
   @Override
+  public int bytecodeSizeUpperBound() {
+    return 3;
+  }
+
+  @Override
   public boolean isConditionalJump() {
     return true;
   }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfIinc.java b/src/main/java/com/android/tools/r8/cf/code/CfIinc.java
index 735dde8..1b5763c 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfIinc.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfIinc.java
@@ -64,6 +64,12 @@
   }
 
   @Override
+  public int bytecodeSizeUpperBound() {
+    // iinc or wide iinc
+    return var < 256 && increment < 256 ? 3 : 6;
+  }
+
+  @Override
   public void print(CfPrinter printer) {
     printer.print(this);
   }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfInitClass.java b/src/main/java/com/android/tools/r8/cf/code/CfInitClass.java
index 1092bfe..c307d45 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfInitClass.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfInitClass.java
@@ -82,6 +82,11 @@
   }
 
   @Override
+  public int bytecodeSizeUpperBound() {
+    return 3;
+  }
+
+  @Override
   public void print(CfPrinter printer) {
     printer.print(this);
   }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfInstanceOf.java b/src/main/java/com/android/tools/r8/cf/code/CfInstanceOf.java
index 0f93c4d..0469499 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfInstanceOf.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfInstanceOf.java
@@ -91,6 +91,11 @@
   }
 
   @Override
+  public int bytecodeSizeUpperBound() {
+    return 3;
+  }
+
+  @Override
   public void print(CfPrinter printer) {
     printer.print(this);
   }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfInstruction.java b/src/main/java/com/android/tools/r8/cf/code/CfInstruction.java
index a1b7023..40b9737 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfInstruction.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfInstruction.java
@@ -6,6 +6,7 @@
 import com.android.tools.r8.cf.CfPrinter;
 import com.android.tools.r8.code.CfOrDexInstruction;
 import com.android.tools.r8.code.Instruction;
+import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.CfCode;
 import com.android.tools.r8.graph.CfCompareHelper;
@@ -66,6 +67,10 @@
   public abstract int internalAcceptCompareTo(
       CfInstruction other, CompareToVisitor visitor, CfCompareHelper helper);
 
+  public int bytecodeSizeUpperBound() {
+    throw new Unreachable("Instruction must specify size");
+  }
+
   public final int acceptCompareTo(
       CfInstruction o, CompareToVisitor visitor, CfCompareHelper helper) {
     int diff = visitor.visitInt(getCompareToId(), o.getCompareToId());
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfInvoke.java b/src/main/java/com/android/tools/r8/cf/code/CfInvoke.java
index 9fb383a..8397bc6 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfInvoke.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfInvoke.java
@@ -112,6 +112,11 @@
   }
 
   @Override
+  public int bytecodeSizeUpperBound() {
+    return opcode == Opcodes.INVOKEINTERFACE ? 5 : 3;
+  }
+
+  @Override
   public void print(CfPrinter printer) {
     printer.print(this);
   }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfInvokeDynamic.java b/src/main/java/com/android/tools/r8/cf/code/CfInvokeDynamic.java
index 2082290..a02060b 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfInvokeDynamic.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfInvokeDynamic.java
@@ -92,6 +92,11 @@
         bsmArgs);
   }
 
+  @Override
+  public int bytecodeSizeUpperBound() {
+    return 5;
+  }
+
   private Object decodeBootstrapArgument(DexValue value, NamingLens lens) {
     switch (value.getValueKind()) {
       case DOUBLE:
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfJsrRet.java b/src/main/java/com/android/tools/r8/cf/code/CfJsrRet.java
index 51829ec..bc866cb 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfJsrRet.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfJsrRet.java
@@ -62,6 +62,11 @@
   }
 
   @Override
+  public int bytecodeSizeUpperBound() {
+    throw error();
+  }
+
+  @Override
   public void print(CfPrinter printer) {
     printer.print(this);
   }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfLabel.java b/src/main/java/com/android/tools/r8/cf/code/CfLabel.java
index 1f611b1..9e3b1ee 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfLabel.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfLabel.java
@@ -74,6 +74,11 @@
   }
 
   @Override
+  public int bytecodeSizeUpperBound() {
+    return 0;
+  }
+
+  @Override
   public void buildIR(IRBuilder builder, CfState state, CfSourceCode code) {
     // Intentionally left empty.
   }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfLoad.java b/src/main/java/com/android/tools/r8/cf/code/CfLoad.java
index 4f9a4db..99a32da 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfLoad.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfLoad.java
@@ -88,6 +88,12 @@
   }
 
   @Override
+  public int bytecodeSizeUpperBound() {
+    // xload_0 .. xload_3, xload or wide xload, where x is a, i, f, l or d
+    return var <= 3 ? 1 : ((var < 256) ? 2 : 4);
+  }
+
+  @Override
   public void print(CfPrinter printer) {
     printer.print(this);
   }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfLogicalBinop.java b/src/main/java/com/android/tools/r8/cf/code/CfLogicalBinop.java
index 71e31d8..2407b10 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfLogicalBinop.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfLogicalBinop.java
@@ -137,6 +137,11 @@
   }
 
   @Override
+  public int bytecodeSizeUpperBound() {
+    return 1;
+  }
+
+  @Override
   public void buildIR(IRBuilder builder, CfState state, CfSourceCode code) {
     int right = state.pop().register;
     int left = state.pop().register;
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfMonitor.java b/src/main/java/com/android/tools/r8/cf/code/CfMonitor.java
index 557d380..11df4ee 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfMonitor.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfMonitor.java
@@ -62,6 +62,11 @@
     visitor.visitInsn(getAsmOpcode());
   }
 
+  @Override
+  public int bytecodeSizeUpperBound() {
+    return 1;
+  }
+
   private int getAsmOpcode() {
     return type == Type.ENTER ? Opcodes.MONITORENTER : Opcodes.MONITOREXIT;
   }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfMultiANewArray.java b/src/main/java/com/android/tools/r8/cf/code/CfMultiANewArray.java
index 33fed6e..509e0cf 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfMultiANewArray.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfMultiANewArray.java
@@ -93,6 +93,11 @@
   }
 
   @Override
+  public int bytecodeSizeUpperBound() {
+    return 4;
+  }
+
+  @Override
   public void print(CfPrinter printer) {
     printer.print(this);
   }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfNeg.java b/src/main/java/com/android/tools/r8/cf/code/CfNeg.java
index a2621a8..da12fc5 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfNeg.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfNeg.java
@@ -64,6 +64,11 @@
   }
 
   @Override
+  public int bytecodeSizeUpperBound() {
+    return 1;
+  }
+
+  @Override
   public void print(CfPrinter printer) {
     printer.print(this);
   }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfNew.java b/src/main/java/com/android/tools/r8/cf/code/CfNew.java
index 95deb22..b4c070a 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfNew.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfNew.java
@@ -82,6 +82,11 @@
   }
 
   @Override
+  public int bytecodeSizeUpperBound() {
+    return 3;
+  }
+
+  @Override
   public void print(CfPrinter printer) {
     printer.print(this);
   }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfNewArray.java b/src/main/java/com/android/tools/r8/cf/code/CfNewArray.java
index 50f82d0..5149789 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfNewArray.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfNewArray.java
@@ -128,6 +128,11 @@
   }
 
   @Override
+  public int bytecodeSizeUpperBound() {
+    return 2;
+  }
+
+  @Override
   public void print(CfPrinter printer) {
     printer.print(this);
   }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfNewUnboxedEnum.java b/src/main/java/com/android/tools/r8/cf/code/CfNewUnboxedEnum.java
index 0d283d4..0c75328 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfNewUnboxedEnum.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfNewUnboxedEnum.java
@@ -85,6 +85,11 @@
   }
 
   @Override
+  public int bytecodeSizeUpperBound() {
+    throw new Unreachable();
+  }
+
+  @Override
   public void print(CfPrinter printer) {
     printer.print(this);
   }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfNop.java b/src/main/java/com/android/tools/r8/cf/code/CfNop.java
index 3518f2a..f77241e 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfNop.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfNop.java
@@ -50,6 +50,11 @@
   }
 
   @Override
+  public int bytecodeSizeUpperBound() {
+    return 1;
+  }
+
+  @Override
   public void print(CfPrinter printer) {
     printer.print(this);
   }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfNumberConversion.java b/src/main/java/com/android/tools/r8/cf/code/CfNumberConversion.java
index 13c737d..b8fe9c1 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfNumberConversion.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfNumberConversion.java
@@ -74,6 +74,11 @@
   }
 
   @Override
+  public int bytecodeSizeUpperBound() {
+    return 1;
+  }
+
+  @Override
   public void print(CfPrinter printer) {
     printer.print(this);
   }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfPosition.java b/src/main/java/com/android/tools/r8/cf/code/CfPosition.java
index e7bf24a..6f82c07 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfPosition.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfPosition.java
@@ -63,6 +63,11 @@
   }
 
   @Override
+  public int bytecodeSizeUpperBound() {
+    return 0;
+  }
+
+  @Override
   public void print(CfPrinter printer) {
     printer.print(this);
   }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfRecordFieldValues.java b/src/main/java/com/android/tools/r8/cf/code/CfRecordFieldValues.java
index ec9b7df..f8fad92 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfRecordFieldValues.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfRecordFieldValues.java
@@ -54,6 +54,11 @@
   }
 
   @Override
+  public int bytecodeSizeUpperBound() {
+    throw new Unreachable();
+  }
+
+  @Override
   public CfRecordFieldValues asRecordFieldValues() {
     return this;
   }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfReturn.java b/src/main/java/com/android/tools/r8/cf/code/CfReturn.java
index 68038a4..d06db87 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfReturn.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfReturn.java
@@ -67,6 +67,11 @@
   }
 
   @Override
+  public int bytecodeSizeUpperBound() {
+    return 1;
+  }
+
+  @Override
   public boolean isJump() {
     return true;
   }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfReturnVoid.java b/src/main/java/com/android/tools/r8/cf/code/CfReturnVoid.java
index 9072b97..d492b04 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfReturnVoid.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfReturnVoid.java
@@ -55,6 +55,11 @@
   }
 
   @Override
+  public int bytecodeSizeUpperBound() {
+    return 1;
+  }
+
+  @Override
   public void print(CfPrinter printer) {
     printer.print(this);
   }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfStackInstruction.java b/src/main/java/com/android/tools/r8/cf/code/CfStackInstruction.java
index 289846f..366033d 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfStackInstruction.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfStackInstruction.java
@@ -108,6 +108,11 @@
   }
 
   @Override
+  public int bytecodeSizeUpperBound() {
+    return 1;
+  }
+
+  @Override
   public void print(CfPrinter printer) {
     printer.print(this);
   }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfStore.java b/src/main/java/com/android/tools/r8/cf/code/CfStore.java
index b096223..616ed84 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfStore.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfStore.java
@@ -91,6 +91,12 @@
   }
 
   @Override
+  public int bytecodeSizeUpperBound() {
+    // xstore_0 .. xstore_3, xstore or wide xstore, where x is a, i, f, l or d
+    return var <= 3 ? 1 : ((var < 256) ? 2 : 4);
+  }
+
+  @Override
   public void print(CfPrinter printer) {
     printer.print(this);
   }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfSwitch.java b/src/main/java/com/android/tools/r8/cf/code/CfSwitch.java
index 6321c35..a86bcd1 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfSwitch.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfSwitch.java
@@ -4,6 +4,7 @@
 package com.android.tools.r8.cf.code;
 
 import com.android.tools.r8.cf.CfPrinter;
+import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.CfCode;
 import com.android.tools.r8.graph.CfCompareHelper;
@@ -121,6 +122,20 @@
   }
 
   @Override
+  public int bytecodeSizeUpperBound() {
+    switch (kind) {
+      case LOOKUP:
+        return 8 + keys.length * 8;
+      case TABLE:
+        int min = keys[0];
+        int max = min + targets.size() - 1;
+        return 16 + (max - min + 1) * 4;
+      default:
+        throw new Unreachable();
+    }
+  }
+
+  @Override
   public void print(CfPrinter printer) {
     printer.print(this);
   }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfThrow.java b/src/main/java/com/android/tools/r8/cf/code/CfThrow.java
index fc36a53..6e5eb78 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfThrow.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfThrow.java
@@ -66,6 +66,11 @@
   }
 
   @Override
+  public int bytecodeSizeUpperBound() {
+    return 1;
+  }
+
+  @Override
   public void print(CfPrinter printer) {
     printer.print(this);
   }
diff --git a/src/main/java/com/android/tools/r8/debuginfo/DebugRepresentation.java b/src/main/java/com/android/tools/r8/debuginfo/DebugRepresentation.java
index 7a33b3c..173ddd1 100644
--- a/src/main/java/com/android/tools/r8/debuginfo/DebugRepresentation.java
+++ b/src/main/java/com/android/tools/r8/debuginfo/DebugRepresentation.java
@@ -12,6 +12,7 @@
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexString;
 import com.android.tools.r8.graph.GraphLens;
+import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.naming.NamingLens;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.LebUtils;
@@ -79,18 +80,19 @@
     // (the sum of the normal debug info for all methods sharing the same max pc and param count.)
     Int2ReferenceMap<CostSummary> paramCountToCosts = new Int2ReferenceOpenHashMap<>();
     for (DexProgramClass clazz : file.classes()) {
-      IdentityHashMap<DexString, List<DexEncodedMethod>> overloads =
+      IdentityHashMap<DexString, List<ProgramMethod>> overloads =
           LineNumberOptimizer.groupMethodsByRenamedName(graphLens, namingLens, clazz);
-      for (List<DexEncodedMethod> methods : overloads.values()) {
+      for (List<ProgramMethod> methods : overloads.values()) {
         if (methods.size() != 1) {
           // Never use PC info for overloaded methods. They need distinct lines to disambiguate.
           continue;
         }
-        DexEncodedMethod method = methods.get(0);
-        if (!isPcCandidate(method)) {
+        ProgramMethod method = methods.get(0);
+        DexEncodedMethod definition = method.getDefinition();
+        if (!isPcCandidate(definition)) {
           continue;
         }
-        DexCode code = method.getCode().asDexCode();
+        DexCode code = definition.getCode().asDexCode();
         DexDebugInfo debugInfo = code.getDebugInfo();
         Instruction lastInstruction = getLastExecutableInstruction(code);
         if (lastInstruction == null) {
diff --git a/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java b/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java
index 3bb7190..1e62a10 100644
--- a/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java
+++ b/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java
@@ -20,6 +20,7 @@
 import com.android.tools.r8.debuginfo.DebugRepresentation;
 import com.android.tools.r8.debuginfo.DebugRepresentation.DebugRepresentationPredicate;
 import com.android.tools.r8.dex.FileWriter.ByteBufferResult;
+import com.android.tools.r8.dex.VirtualFile.FilePerInputClassDistributor;
 import com.android.tools.r8.errors.CompilationError;
 import com.android.tools.r8.features.FeatureSplitConfiguration.DataResourceProvidersAndConsumer;
 import com.android.tools.r8.graph.AppServices;
@@ -51,6 +52,7 @@
 import com.android.tools.r8.utils.Box;
 import com.android.tools.r8.utils.DescriptorUtils;
 import com.android.tools.r8.utils.ExceptionUtils;
+import com.android.tools.r8.utils.InternalGlobalSyntheticsProgramConsumer.InternalGlobalSyntheticsDexConsumer;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.OriginalSourceFiles;
 import com.android.tools.r8.utils.PredicateUtils;
@@ -85,8 +87,10 @@
   private final Predicate<DexType> isTypeMissing;
   public List<Marker> markers;
   public List<DexString> markerStrings;
+  public Set<VirtualFile> globalSyntheticFiles;
 
   public DexIndexedConsumer programConsumer;
+  public InternalGlobalSyntheticsDexConsumer globalsSyntheticsConsumer;
 
   private static class SortAnnotations extends MixedSectionCollection {
 
@@ -179,20 +183,47 @@
 
   private List<VirtualFile> distribute(ExecutorService executorService)
       throws ExecutionException, IOException {
+    Collection<DexProgramClass> classes = appView.appInfo().classes();
+    Collection<DexProgramClass> globalSynthetics = new ArrayList<>();
+    if (appView.options().intermediate && appView.options().hasGlobalSyntheticsConsumer()) {
+      Collection<DexProgramClass> allClasses = classes;
+      classes = new ArrayList<>(allClasses.size());
+      for (DexProgramClass clazz : allClasses) {
+        if (appView.getSyntheticItems().isGlobalSyntheticClass(clazz)) {
+          globalSynthetics.add(clazz);
+        } else {
+          classes.add(clazz);
+        }
+      }
+    }
+
     // Distribute classes into dex files.
     VirtualFile.Distributor distributor;
     if (options.isGeneratingDexFilePerClassFile()) {
-      distributor = new VirtualFile.FilePerInputClassDistributor(this,
-          options.getDexFilePerClassFileConsumer().combineSyntheticClassesWithPrimaryClass());
+      distributor =
+          new VirtualFile.FilePerInputClassDistributor(
+              this,
+              classes,
+              options.getDexFilePerClassFileConsumer().combineSyntheticClassesWithPrimaryClass());
     } else if (!options.canUseMultidex()
         && options.mainDexKeepRules.isEmpty()
         && appView.appInfo().getMainDexInfo().isEmpty()
         && options.enableMainDexListCheck) {
-      distributor = new VirtualFile.MonoDexDistributor(this, options);
+      distributor = new VirtualFile.MonoDexDistributor(this, classes, options);
     } else {
-      distributor = new VirtualFile.FillFilesDistributor(this, options, executorService);
+      distributor = new VirtualFile.FillFilesDistributor(this, classes, options, executorService);
     }
-    return distributor.run();
+
+    List<VirtualFile> virtualFiles = distributor.run();
+    if (!globalSynthetics.isEmpty()) {
+      List<VirtualFile> files =
+          new FilePerInputClassDistributor(this, globalSynthetics, false).run();
+      globalSyntheticFiles = new HashSet<>(files);
+      virtualFiles.addAll(globalSyntheticFiles);
+      globalsSyntheticsConsumer =
+          new InternalGlobalSyntheticsDexConsumer(options.getGlobalSyntheticsConsumer());
+    }
+    return virtualFiles;
   }
 
   /**
@@ -330,6 +361,9 @@
                 executorService);
         merger.add(timings);
         merger.end();
+        if (globalsSyntheticsConsumer != null) {
+          globalsSyntheticsConsumer.finished(options.reporter);
+        }
       }
 
       // A consumer can manage the generated keep rules.
@@ -460,7 +494,11 @@
 
     ProgramConsumer consumer;
     ByteBufferProvider byteBufferProvider;
-    if (programConsumer != null) {
+
+    if (globalSyntheticFiles != null && globalSyntheticFiles.contains(virtualFile)) {
+      consumer = globalsSyntheticsConsumer;
+      byteBufferProvider = globalsSyntheticsConsumer;
+    } else if (programConsumer != null) {
       consumer = programConsumer;
       byteBufferProvider = programConsumer;
     } else if (virtualFile.getPrimaryClassDescriptor() != null) {
@@ -777,7 +815,7 @@
                     mapping,
                     application.dexItemFactory,
                     options.testing.forceJumboStringProcessing);
-            method.getDefinition().setCode(rewrittenCode.asCode(), appView);
+            method.setCode(rewrittenCode.asCode(), appView);
           });
     }
   }
diff --git a/src/main/java/com/android/tools/r8/dex/CodeToKeep.java b/src/main/java/com/android/tools/r8/dex/CodeToKeep.java
index 3e33ca8..7bfd947 100644
--- a/src/main/java/com/android/tools/r8/dex/CodeToKeep.java
+++ b/src/main/java/com/android/tools/r8/dex/CodeToKeep.java
@@ -25,6 +25,7 @@
 
   static CodeToKeep createCodeToKeep(InternalOptions options, NamingLens namingLens) {
     if ((!namingLens.hasPrefixRewritingLogic()
+            && options.machineDesugaredLibrarySpecification.getMaintainType().isEmpty()
             && !options.machineDesugaredLibrarySpecification.hasEmulatedInterfaces())
         || options.isDesugaredLibraryCompilation()
         || options.testing.enableExperimentalDesugaredLibraryKeepRuleGenerator) {
@@ -67,6 +68,7 @@
 
     private boolean shouldKeep(DexType type) {
       return namingLens.prefixRewrittenType(type) != null
+          || options.machineDesugaredLibrarySpecification.getMaintainType().contains(type)
           || options.machineDesugaredLibrarySpecification.isCustomConversionRewrittenType(type)
           || options.machineDesugaredLibrarySpecification.isEmulatedInterfaceRewrittenType(type)
           // TODO(b/158632510): This should prefix match on DexString.
diff --git a/src/main/java/com/android/tools/r8/dex/VirtualFile.java b/src/main/java/com/android/tools/r8/dex/VirtualFile.java
index 7d88ac7..827f43bd 100644
--- a/src/main/java/com/android/tools/r8/dex/VirtualFile.java
+++ b/src/main/java/com/android/tools/r8/dex/VirtualFile.java
@@ -307,6 +307,7 @@
     }
 
     public abstract List<VirtualFile> run() throws ExecutionException, IOException;
+
   }
 
   /**
@@ -316,11 +317,15 @@
    * may then be distributed in several individual virtual files.
    */
   public static class FilePerInputClassDistributor extends Distributor {
+    private final Collection<DexProgramClass> classes;
     private final boolean combineSyntheticClassesWithPrimaryClass;
 
-    FilePerInputClassDistributor(ApplicationWriter writer,
+    FilePerInputClassDistributor(
+        ApplicationWriter writer,
+        Collection<DexProgramClass> classes,
         boolean combineSyntheticClassesWithPrimaryClass) {
       super(writer);
+      this.classes = classes;
       this.combineSyntheticClassesWithPrimaryClass = combineSyntheticClassesWithPrimaryClass;
     }
 
@@ -329,7 +334,7 @@
       HashMap<DexProgramClass, VirtualFile> files = new HashMap<>();
       Collection<DexProgramClass> synthetics = new ArrayList<>();
       // Assign dedicated virtual files for all program classes.
-      for (DexProgramClass clazz : appView.appInfo().classes()) {
+      for (DexProgramClass clazz : classes) {
         // TODO(b/181636450): Simplify this making use of the assumption that synthetics are never
         //  duplicated.
         if (!combineSyntheticClassesWithPrimaryClass
@@ -370,9 +375,11 @@
     protected final VirtualFile mainDexFile;
     protected final InternalOptions options;
 
-    DistributorBase(ApplicationWriter writer, InternalOptions options) {
+    DistributorBase(
+        ApplicationWriter writer, Collection<DexProgramClass> classes, InternalOptions options) {
       super(writer);
       this.options = options;
+      this.classes = SetUtils.newIdentityHashSet(classes);
 
       // Create the primary dex file. The distribution will add more if needed.
       mainDexFile = new VirtualFile(0, writer.appView, writer.namingLens);
@@ -380,7 +387,6 @@
       virtualFiles.add(mainDexFile);
       addMarkers(mainDexFile);
 
-      classes = SetUtils.newIdentityHashSet(appView.appInfo().classes());
       originalNames =
           computeOriginalNameMapping(
               classes, appView.graphLens(), appView.appInfo().app().getProguardMap());
@@ -513,9 +519,12 @@
   public static class FillFilesDistributor extends DistributorBase {
     private final ExecutorService executorService;
 
-    FillFilesDistributor(ApplicationWriter writer, InternalOptions options,
+    FillFilesDistributor(
+        ApplicationWriter writer,
+        Collection<DexProgramClass> classes,
+        InternalOptions options,
         ExecutorService executorService) {
-      super(writer, options);
+      super(writer, classes, options);
       this.executorService = executorService;
     }
 
@@ -576,8 +585,9 @@
   }
 
   public static class MonoDexDistributor extends DistributorBase {
-    MonoDexDistributor(ApplicationWriter writer, InternalOptions options) {
-      super(writer, options);
+    MonoDexDistributor(
+        ApplicationWriter writer, Collection<DexProgramClass> classes, InternalOptions options) {
+      super(writer, classes, options);
     }
 
     @Override
diff --git a/src/main/java/com/android/tools/r8/errors/MissingGlobalSyntheticsConsumerDiagnostic.java b/src/main/java/com/android/tools/r8/errors/MissingGlobalSyntheticsConsumerDiagnostic.java
new file mode 100644
index 0000000..ff99d05
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/errors/MissingGlobalSyntheticsConsumerDiagnostic.java
@@ -0,0 +1,34 @@
+// Copyright (c) 2022, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.errors;
+
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.position.Position;
+
+public class MissingGlobalSyntheticsConsumerDiagnostic implements DesugarDiagnostic {
+
+  private final String generatingReason;
+
+  public MissingGlobalSyntheticsConsumerDiagnostic(String generatingReason) {
+    this.generatingReason = generatingReason;
+  }
+
+  @Override
+  public Origin getOrigin() {
+    return Origin.unknown();
+  }
+
+  @Override
+  public Position getPosition() {
+    return Position.UNKNOWN;
+  }
+
+  @Override
+  public String getDiagnosticMessage() {
+    return "Invalid build configuration. "
+        + "Attempt to create a global synthetic for '"
+        + generatingReason
+        + "' without a global-synthetics consumer.";
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/graph/AbstractAccessContexts.java b/src/main/java/com/android/tools/r8/graph/AbstractAccessContexts.java
index 616fcf5..3e180df 100644
--- a/src/main/java/com/android/tools/r8/graph/AbstractAccessContexts.java
+++ b/src/main/java/com/android/tools/r8/graph/AbstractAccessContexts.java
@@ -58,6 +58,10 @@
 
   abstract int getNumberOfAccessContexts();
 
+  public final boolean hasAccesses() {
+    return !isEmpty();
+  }
+
   public boolean isBottom() {
     return false;
   }
@@ -66,7 +70,7 @@
     return false;
   }
 
-  abstract boolean isEmpty();
+  public abstract boolean isEmpty();
 
   public ConcreteAccessContexts asConcrete() {
     return null;
diff --git a/src/main/java/com/android/tools/r8/graph/AppInfoWithClassHierarchy.java b/src/main/java/com/android/tools/r8/graph/AppInfoWithClassHierarchy.java
index 4ecd71c..f5cb1a3 100644
--- a/src/main/java/com/android/tools/r8/graph/AppInfoWithClassHierarchy.java
+++ b/src/main/java/com/android/tools/r8/graph/AppInfoWithClassHierarchy.java
@@ -8,12 +8,6 @@
 import static com.android.tools.r8.utils.TraversalContinuation.doContinue;
 
 import com.android.tools.r8.features.ClassToFeatureSplitMap;
-import com.android.tools.r8.graph.MethodResolutionResult.ArrayCloneMethodResult;
-import com.android.tools.r8.graph.MethodResolutionResult.ClassNotFoundResult;
-import com.android.tools.r8.graph.MethodResolutionResult.IllegalAccessOrNoSuchMethodResult;
-import com.android.tools.r8.graph.MethodResolutionResult.IncompatibleClassResult;
-import com.android.tools.r8.graph.MethodResolutionResult.NoSuchMethodResult;
-import com.android.tools.r8.graph.MethodResolutionResult.SingleResolutionResult;
 import com.android.tools.r8.ir.analysis.type.InterfaceCollection;
 import com.android.tools.r8.ir.analysis.type.InterfaceCollection.Builder;
 import com.android.tools.r8.ir.desugar.LambdaDescriptor;
@@ -21,7 +15,6 @@
 import com.android.tools.r8.shaking.MissingClasses;
 import com.android.tools.r8.synthesis.CommittedItems;
 import com.android.tools.r8.synthesis.SyntheticItems;
-import com.android.tools.r8.utils.ListUtils;
 import com.android.tools.r8.utils.Pair;
 import com.android.tools.r8.utils.TraversalContinuation;
 import com.android.tools.r8.utils.TriConsumer;
@@ -30,12 +23,9 @@
 import com.google.common.collect.Sets;
 import java.util.ArrayDeque;
 import java.util.ArrayList;
-import java.util.Arrays;
 import java.util.Collections;
 import java.util.Deque;
-import java.util.LinkedHashMap;
 import java.util.List;
-import java.util.Map.Entry;
 import java.util.Set;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.ExecutorService;
@@ -540,12 +530,18 @@
    * may be abstract.
    */
   public DexClassAndMethod lookupMaximallySpecificMethod(DexClass clazz, DexMethod method) {
-    return lookupMaximallySpecificTarget(clazz, method);
+    return new MethodResolution(this::definitionFor, dexItemFactory())
+        .lookupMaximallySpecificTarget(clazz, method);
   }
 
-  public DexClassAndMethod lookupMaximallySpecificMethod(
-      LambdaDescriptor lambda, DexMethod method) {
-    return lookupMaximallySpecificTarget(lambda, method);
+  MethodResolutionResult resolveMaximallySpecificTarget(DexClass clazz, DexMethod method) {
+    return new MethodResolution(this::definitionFor, dexItemFactory())
+        .resolveMaximallySpecificTarget(clazz, method);
+  }
+
+  MethodResolutionResult resolveMaximallySpecificTarget(LambdaDescriptor lambda, DexMethod method) {
+    return new MethodResolution(this::definitionFor, dexItemFactory())
+        .resolveMaximallySpecificTarget(lambda, method);
   }
 
   /**
@@ -643,359 +639,111 @@
   }
 
   /**
-   * Implements resolution of a method descriptor.
-   *
-   * <p>This method will query the definition of the holder to decide on which resolution to use. If
-   * the holder is an interface, it delegates to {@link #resolveMethodOnInterface(DexType,
-   * DexMethod)}, otherwise {@link #resolveMethodOnClass(DexMethod, DexType)} is used.
+   * This method will query the definition of the holder to decide on which resolution to use.
    *
    * <p>This is to overcome the shortcoming of the DEX file format that does not allow to encode the
    * kind of a method reference.
    */
   public MethodResolutionResult unsafeResolveMethodDueToDexFormat(DexMethod method) {
     assert checkIfObsolete();
-    DexType holder = method.holder;
-    if (holder.isArrayType()) {
-      return resolveMethodOnArray(holder, method.getProto(), method.getName());
-    }
-    DexClass definition = definitionFor(holder);
-    if (definition == null) {
-      return ClassNotFoundResult.INSTANCE;
-    }
-    return resolveMethodOn(definition, method);
+    return new MethodResolution(this::definitionFor, dexItemFactory())
+        .unsafeResolveMethodDueToDexFormat(method);
   }
 
-  public MethodResolutionResult resolveMethod(DexMethod method, boolean isInterface) {
-    return isInterface
-        ? resolveMethodOnInterface(method.holder, method)
-        : resolveMethodOnClass(method, method.holder);
+  public MethodResolutionResult resolveMethod(DexMethod invokedMethod, boolean isInterface) {
+    assert checkIfObsolete();
+    return resolveMethodOn(invokedMethod.getHolderType(), invokedMethod, isInterface);
   }
 
-  public MethodResolutionResult resolveMethodOn(DexClass holder, DexMethod method) {
-    return resolveMethodOn(holder, method.getProto(), method.getName());
-  }
-
-  public MethodResolutionResult resolveMethodOn(DexClass holder, DexMethodSignature method) {
-    return resolveMethodOn(holder, method.getProto(), method.getName());
+  public MethodResolutionResult resolveMethodOn(DexClass clazz, DexMethod method) {
+    assert checkIfObsolete();
+    return clazz.isInterface()
+        ? resolveMethodOnInterface(clazz, method)
+        : resolveMethodOnClass(clazz, method);
   }
 
   public MethodResolutionResult resolveMethodOn(
-      DexClass holder, DexProto methodProto, DexString methodName) {
-    return holder.isInterface()
-        ? resolveMethodOnInterface(holder, methodProto, methodName)
-        : resolveMethodOnClass(methodProto, methodName, holder);
+      DexClass clazz, DexMethodSignature methodSignature) {
+    assert checkIfObsolete();
+    return clazz.isInterface()
+        ? resolveMethodOnInterface(clazz, methodSignature)
+        : resolveMethodOnClass(clazz, methodSignature);
   }
 
-  /**
-   * Implements resolution of a method descriptor against a target type.
-   *
-   * <p>The boolean isInterface parameter denotes if the method reference is an interface method
-   * reference, and if so method resolution is done according to interface method resolution.
-   *
-   * @param holder Type at which to initiate the resolution.
-   * @param method Method descriptor for resolution (the field method.holder is ignored).
-   * @param isInterface Indicates if resolution is to be done according to class or interface.
-   * @return The result of resolution.
-   */
   public MethodResolutionResult resolveMethodOn(
       DexType holder, DexMethod method, boolean isInterface) {
     assert checkIfObsolete();
     return isInterface
         ? resolveMethodOnInterface(holder, method)
-        : resolveMethodOnClass(method, holder);
+        : resolveMethodOnClass(holder, method);
   }
 
-  /**
-   * Implements resolution of a method descriptor against an array type.
-   *
-   * <p>See <a href="https://docs.oracle.com/javase/specs/jls/se8/html/jls-10.html#jls-10.7">Section
-   * 10.7 of the Java Language Specification</a>. All invokations will have target java.lang.Object
-   * except clone which has no target.
-   */
-  private MethodResolutionResult resolveMethodOnArray(
-      DexType holder, DexProto methodProto, DexString methodName) {
+  public MethodResolutionResult resolveMethodOnClassHolder(DexMethod method) {
     assert checkIfObsolete();
-    assert holder.isArrayType();
-    if (methodName == dexItemFactory().cloneMethodName) {
-      return ArrayCloneMethodResult.INSTANCE;
-    } else {
-      return resolveMethodOnClass(methodProto, methodName, dexItemFactory().objectType);
-    }
+    return resolveMethodOnClass(method.getHolderType(), method);
   }
 
-  public MethodResolutionResult resolveMethodOnClass(DexMethod method) {
-    return resolveMethodOnClass(method.getProto(), method.getName(), method.getHolderType());
-  }
-
-  public MethodResolutionResult resolveMethodOnClass(DexMethod method, DexType holder) {
-    return resolveMethodOnClass(method.getProto(), method.getName(), holder);
-  }
-
-  /**
-   * Implements resolution of a method descriptor against a class type.
-   *
-   * <p>See <a href="https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-5.html#jvms-5.4.3.3">
-   * Section 5.4.3.3 of the JVM Spec</a>.
-   *
-   * <p>The resolved method is not the method that will actually be invoked. Which methods gets
-   * invoked depends on the invoke instruction used. However, it is always safe to rewrite any
-   * invoke on the given descriptor to a corresponding invoke on the resolved descriptor, as the
-   * resolved method is used as basis for dispatch.
-   */
-  public MethodResolutionResult resolveMethodOnClass(
-      DexProto methodProto, DexString methodName, DexType holder) {
+  public MethodResolutionResult resolveMethodOnClass(DexType holder, DexMethod method) {
     assert checkIfObsolete();
-    if (holder.isArrayType()) {
-      return resolveMethodOnArray(holder, methodProto, methodName);
-    }
-    DexClass clazz = definitionFor(holder);
-    if (clazz == null) {
-      return ClassNotFoundResult.INSTANCE;
-    }
-    // Step 1: If holder is an interface, resolution fails with an ICCE. We return null.
-    if (clazz.isInterface()) {
-      return IncompatibleClassResult.INSTANCE;
-    }
-    return resolveMethodOnClass(methodProto, methodName, clazz);
+    return resolveMethodOnClass(holder, method.getProto(), method.getName());
   }
 
-  public MethodResolutionResult resolveMethodOnClass(DexMethodSignature method, DexType holder) {
+  public MethodResolutionResult resolveMethodOnClass(DexType holder, DexMethodSignature signature) {
     assert checkIfObsolete();
-    if (holder.isArrayType()) {
-      return resolveMethodOnArray(holder, method.getProto(), method.getName());
-    }
-    DexClass clazz = definitionFor(holder);
-    if (clazz == null) {
-      return ClassNotFoundResult.INSTANCE;
-    }
-    // Step 1: If holder is an interface, resolution fails with an ICCE. We return null.
-    if (clazz.isInterface()) {
-      return IncompatibleClassResult.INSTANCE;
-    }
-    return resolveMethodOnClass(method, clazz);
-  }
-
-  public MethodResolutionResult resolveMethodOnClass(DexMethod method, DexClass clazz) {
-    return resolveMethodOnClass(method.getProto(), method.getName(), clazz);
-  }
-
-  public MethodResolutionResult resolveMethodOnClass(DexMethodSignature method, DexClass clazz) {
-    return resolveMethodOnClass(method.getProto(), method.getName(), clazz);
+    return resolveMethodOnClass(holder, signature.getProto(), signature.getName());
   }
 
   public MethodResolutionResult resolveMethodOnClass(
-      DexProto methodProto, DexString methodName, DexClass clazz) {
+      DexType holder, DexProto proto, DexString name) {
     assert checkIfObsolete();
-    assert !clazz.isInterface();
-    // Step 2:
-    MethodResolutionResult result =
-        resolveMethodOnClassStep2(clazz, methodProto, methodName, clazz);
-    if (result != null) {
-      return result;
-    }
-    // Finally Step 3:
-    return resolveMethodStep3(clazz, methodProto, methodName);
+    return new MethodResolution(this::definitionFor, dexItemFactory())
+        .resolveMethodOnClass(holder, proto, name);
   }
 
-  /**
-   * Implements step 2 of method resolution on classes as per <a
-   * href="https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-5.html#jvms-5.4.3.3">Section
-   * 5.4.3.3 of the JVM Spec</a>.
-   */
-  private MethodResolutionResult resolveMethodOnClassStep2(
-      DexClass clazz,
-      DexProto methodProto,
-      DexString methodName,
-      DexClass initialResolutionHolder) {
-    // Pt. 1: Signature polymorphic method check.
-    // See also <a href="https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-2.html#jvms-2.9">
-    // Section 2.9 of the JVM Spec</a>.
-    DexEncodedMethod result = clazz.lookupSignaturePolymorphicMethod(methodName, dexItemFactory());
-    if (result != null) {
-      return new SingleResolutionResult(initialResolutionHolder, clazz, result);
-    }
-    // Pt 2: Find a method that matches the descriptor.
-    result = clazz.lookupMethod(methodProto, methodName);
-    if (result != null) {
-      // If the resolved method is private, then it can only be accessed if the symbolic reference
-      // that initiated the resolution was the type at which the method resolved on. If that is not
-      // the case, then the error is either an IllegalAccessError, or in the case where access is
-      // allowed because of nests, a NoSuchMethodError. Which error cannot be determined without
-      // knowing the calling context.
-      if (result.isPrivateMethod() && clazz != initialResolutionHolder) {
-        return new IllegalAccessOrNoSuchMethodResult(initialResolutionHolder, result);
-      }
-      return new SingleResolutionResult(initialResolutionHolder, clazz, result);
-    }
-    // Pt 3: Apply step two to direct superclass of holder.
-    if (clazz.superType != null) {
-      DexClass superClass = definitionFor(clazz.superType);
-      if (superClass != null) {
-        return resolveMethodOnClassStep2(
-            superClass, methodProto, methodName, initialResolutionHolder);
-      }
-    }
-    return null;
-  }
-
-  /**
-   * Implements step 3 of <a
-   * href="https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-5.html#jvms-5.4.3.3">Section
-   * 5.4.3.3 of the JVM Spec</a>. As this is the same for interfaces and classes, we share one
-   * implementation.
-   */
-  private MethodResolutionResult resolveMethodStep3(
-      DexClass clazz, DexProto methodProto, DexString methodName) {
-    MaximallySpecificMethodsBuilder builder = new MaximallySpecificMethodsBuilder();
-    resolveMethodStep3Helper(methodProto, methodName, clazz, builder);
-    return builder.resolve(clazz);
-  }
-
-  MethodResolutionResult resolveMaximallySpecificTarget(DexClass clazz, DexMethod method) {
-    return resolveMaximallySpecificTargetHelper(clazz, method).resolve(clazz);
-  }
-
-  private MaximallySpecificMethodsBuilder resolveMaximallySpecificTargetHelper(
-      DexClass clazz, DexMethod method) {
-    MaximallySpecificMethodsBuilder builder = new MaximallySpecificMethodsBuilder();
-    resolveMethodStep3Helper(method.getProto(), method.getName(), clazz, builder);
-    return builder;
-  }
-
-  MethodResolutionResult resolveMaximallySpecificTarget(LambdaDescriptor lambda, DexMethod method) {
-    return resolveMaximallySpecificTargetHelper(lambda, method).internalResolve(null);
-  }
-
-  private MaximallySpecificMethodsBuilder resolveMaximallySpecificTargetHelper(
-      LambdaDescriptor lambda, DexMethod method) {
-    MaximallySpecificMethodsBuilder builder = new MaximallySpecificMethodsBuilder();
-    resolveMethodStep3Helper(
-        method.getProto(),
-        method.getName(),
-        dexItemFactory().objectType,
-        lambda.interfaces,
-        builder);
-    return builder;
-  }
-
-  // Non-private lookup (ie, not resolution) to find interface targets.
-  DexClassAndMethod lookupMaximallySpecificTarget(DexClass clazz, DexMethod method) {
-    return resolveMaximallySpecificTargetHelper(clazz, method).lookup();
-  }
-
-  // Non-private lookup (ie, not resolution) to find interface targets.
-  DexClassAndMethod lookupMaximallySpecificTarget(LambdaDescriptor lambda, DexMethod method) {
-    return resolveMaximallySpecificTargetHelper(lambda, method).lookup();
-  }
-
-  /** Helper method that builds the set of maximally specific methods. */
-  private void resolveMethodStep3Helper(
-      DexProto methodProto,
-      DexString methodName,
-      DexClass clazz,
-      MaximallySpecificMethodsBuilder builder) {
-    resolveMethodStep3Helper(
-        methodProto, methodName, clazz.superType, Arrays.asList(clazz.interfaces.values), builder);
-  }
-
-  private void resolveMethodStep3Helper(
-      DexProto methodProto,
-      DexString methodName,
-      DexType superType,
-      List<DexType> interfaces,
-      MaximallySpecificMethodsBuilder builder) {
-    for (DexType iface : interfaces) {
-      DexClass definition = definitionFor(iface);
-      if (definition == null) {
-        // Ignore missing interface definitions.
-        continue;
-      }
-      assert definition.isInterface();
-      DexEncodedMethod result = definition.lookupMethod(methodProto, methodName);
-      if (isMaximallySpecificCandidate(result)) {
-        // The candidate is added and doing so will prohibit shadowed methods from being in the set.
-        builder.addCandidate(definition, result, this);
-      } else {
-        // Look at the super-interfaces of this class and keep searching.
-        resolveMethodStep3Helper(methodProto, methodName, definition, builder);
-      }
-    }
-    // Now look at indirect super interfaces.
-    if (superType != null) {
-      DexClass superClass = definitionFor(superType);
-      if (superClass != null) {
-        resolveMethodStep3Helper(methodProto, methodName, superClass, builder);
-      }
-    }
-  }
-
-  /**
-   * A candidate for being a maximally specific method must have neither its private, nor its static
-   * flag set. A candidate may still not be maximally specific, which entails that no subinterfaces
-   * from also contribute with a candidate to the type. That is not determined by this method.
-   */
-  private boolean isMaximallySpecificCandidate(DexEncodedMethod method) {
-    return method != null && !method.accessFlags.isPrivate() && !method.accessFlags.isStatic();
-  }
-
-  public MethodResolutionResult resolveMethodOnInterface(DexMethod method) {
-    return resolveMethodOnInterface(method.holder, method);
-  }
-
-  /**
-   * Implements resolution of a method descriptor against an interface type.
-   *
-   * <p>See <a href="https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-5.html#jvms-5.4.3.3">
-   * Section 5.4.3.4 of the JVM Spec</a>.
-   *
-   * <p>The resolved method is not the method that will actually be invoked. Which methods gets
-   * invoked depends on the invoke instruction used. However, it is always save to rewrite any
-   * invoke on the given descriptor to a corresponding invoke on the resolved descriptor, as the
-   * resolved method is used as basis for dispatch.
-   */
-  public MethodResolutionResult resolveMethodOnInterface(DexType holder, DexMethod desc) {
+  public MethodResolutionResult resolveMethodOnClass(DexClass clazz, DexMethod method) {
     assert checkIfObsolete();
-    if (holder.isArrayType()) {
-      return IncompatibleClassResult.INSTANCE;
-    }
-    // Step 1: Lookup interface.
-    DexClass definition = definitionFor(holder);
-    // If the definition is not an interface, resolution fails with an ICCE. We just return the
-    // empty result here.
-    if (definition == null) {
-      return ClassNotFoundResult.INSTANCE;
-    }
-    if (!definition.isInterface()) {
-      return IncompatibleClassResult.INSTANCE;
-    }
-    return resolveMethodOnInterface(definition, desc);
+    return resolveMethodOnClass(clazz, method.getProto(), method.getName());
   }
 
-  public MethodResolutionResult resolveMethodOnInterface(DexClass definition, DexMethod desc) {
-    return resolveMethodOnInterface(definition, desc.getProto(), desc.getName());
+  public MethodResolutionResult resolveMethodOnClass(DexClass clazz, DexMethodSignature signature) {
+    assert checkIfObsolete();
+    return resolveMethodOnClass(clazz, signature.getProto(), signature.getName());
+  }
+
+  public MethodResolutionResult resolveMethodOnClass(
+      DexClass clazz, DexProto proto, DexString name) {
+    assert checkIfObsolete();
+    return new MethodResolution(this::definitionFor, dexItemFactory())
+        .resolveMethodOnClass(clazz, proto, name);
+  }
+
+  public MethodResolutionResult resolveMethodOnInterfaceHolder(DexMethod method) {
+    assert checkIfObsolete();
+    return resolveMethodOnInterface(method.getHolderType(), method);
+  }
+
+  public MethodResolutionResult resolveMethodOnInterface(DexType holder, DexMethod method) {
+    assert checkIfObsolete();
+    return new MethodResolution(this::definitionFor, dexItemFactory())
+        .resolveMethodOnInterface(holder, method.getProto(), method.getName());
+  }
+
+  public MethodResolutionResult resolveMethodOnInterface(DexClass clazz, DexMethod method) {
+    assert checkIfObsolete();
+    return resolveMethodOnInterface(clazz, method.getProto(), method.getName());
   }
 
   public MethodResolutionResult resolveMethodOnInterface(
-      DexClass definition, DexProto methodProto, DexString methodName) {
+      DexClass clazz, DexMethodSignature methodSignature) {
     assert checkIfObsolete();
-    assert definition.isInterface();
-    // Step 2: Look for exact method on interface.
-    DexEncodedMethod result = definition.lookupMethod(methodProto, methodName);
-    if (result != null) {
-      return new SingleResolutionResult(definition, definition, result);
-    }
-    // Step 3: Look for matching method on object class.
-    DexClass objectClass = definitionFor(dexItemFactory().objectType);
-    if (objectClass == null) {
-      return ClassNotFoundResult.INSTANCE;
-    }
-    result = objectClass.lookupMethod(methodProto, methodName);
-    if (result != null && result.accessFlags.isPublic() && !result.accessFlags.isAbstract()) {
-      return new SingleResolutionResult(definition, objectClass, result);
-    }
-    // Step 3: Look for maximally-specific superinterface methods or any interface definition.
-    //         This is the same for classes and interfaces.
-    return resolveMethodStep3(definition, methodProto, methodName);
+    return resolveMethodOnInterface(clazz, methodSignature.getProto(), methodSignature.getName());
+  }
+
+  public MethodResolutionResult resolveMethodOnInterface(
+      DexClass clazz, DexProto proto, DexString name) {
+    assert checkIfObsolete();
+    return new MethodResolution(this::definitionFor, dexItemFactory())
+        .resolveMethodOnInterface(clazz, proto, name);
   }
 
   /**
@@ -1025,105 +773,4 @@
     assert checkIfObsolete();
     return new FieldResolution(this).resolveFieldOn(clazz, field);
   }
-
-  private static class MaximallySpecificMethodsBuilder {
-
-    // The set of actual maximally specific methods.
-    // This set is linked map so that in the case where a number of methods remain a deterministic
-    // choice can be made. The map is from definition classes to their maximally specific method, or
-    // in the case that a type has a candidate which is shadowed by a subinterface, the map will
-    // map the class to a null entry, thus any addition to the map must check for key containment
-    // prior to writing.
-    LinkedHashMap<DexClass, DexEncodedMethod> maximallySpecificMethods = new LinkedHashMap<>();
-
-    void addCandidate(DexClass holder, DexEncodedMethod method, AppInfo appInfo) {
-      // If this candidate is already a candidate or it is shadowed, then no need to continue.
-      if (maximallySpecificMethods.containsKey(holder)) {
-        return;
-      }
-      maximallySpecificMethods.put(holder, method);
-      // Prune exiting candidates and prohibit future candidates in the super hierarchy.
-      assert holder.isInterface();
-      assert holder.superType == appInfo.dexItemFactory().objectType;
-      for (DexType iface : holder.interfaces.values) {
-        markShadowed(iface, appInfo);
-      }
-    }
-
-    private void markShadowed(DexType type, AppInfo appInfo) {
-      if (type == null) {
-        return;
-      }
-      DexClass clazz = appInfo.definitionFor(type);
-      if (clazz == null) {
-        return;
-      }
-      assert clazz.isInterface();
-      assert clazz.superType == appInfo.dexItemFactory().objectType;
-      // A null entry signifies that the candidate is shadowed blocking future candidates.
-      // If the candidate is already shadowed at this type there is no need to shadow further up.
-      if (maximallySpecificMethods.containsKey(clazz)
-          && maximallySpecificMethods.get(clazz) == null) {
-        return;
-      }
-      maximallySpecificMethods.put(clazz, null);
-      for (DexType iface : clazz.interfaces.values) {
-        markShadowed(iface, appInfo);
-      }
-    }
-
-    DexClassAndMethod lookup() {
-      return internalResolve(null).getResolutionPair();
-    }
-
-    MethodResolutionResult resolve(DexClass initialResolutionHolder) {
-      assert initialResolutionHolder != null;
-      return internalResolve(initialResolutionHolder);
-    }
-
-    private MethodResolutionResult internalResolve(DexClass initialResolutionHolder) {
-      if (maximallySpecificMethods.isEmpty()) {
-        return NoSuchMethodResult.INSTANCE;
-      }
-      // Fast path in the common case of a single method.
-      if (maximallySpecificMethods.size() == 1) {
-        return singleResultHelper(
-            initialResolutionHolder, maximallySpecificMethods.entrySet().iterator().next());
-      }
-      Entry<DexClass, DexEncodedMethod> firstMaximallySpecificMethod = null;
-      List<Entry<DexClass, DexEncodedMethod>> nonAbstractMethods =
-          new ArrayList<>(maximallySpecificMethods.size());
-      for (Entry<DexClass, DexEncodedMethod> entry : maximallySpecificMethods.entrySet()) {
-        DexEncodedMethod method = entry.getValue();
-        if (method == null) {
-          // Ignore shadowed candidates.
-          continue;
-        }
-        if (firstMaximallySpecificMethod == null) {
-          firstMaximallySpecificMethod = entry;
-        }
-        if (method.isNonAbstractVirtualMethod()) {
-          nonAbstractMethods.add(entry);
-        }
-      }
-      // If there are no non-abstract methods, then any candidate will suffice as a target.
-      // For deterministic resolution, we return the first mapped method (of the linked map).
-      if (nonAbstractMethods.isEmpty()) {
-        return singleResultHelper(initialResolutionHolder, firstMaximallySpecificMethod);
-      }
-      // If there is exactly one non-abstract method (a default method) it is the resolution target.
-      if (nonAbstractMethods.size() == 1) {
-        return singleResultHelper(initialResolutionHolder, nonAbstractMethods.get(0));
-      }
-      return IncompatibleClassResult.create(ListUtils.map(nonAbstractMethods, Entry::getValue));
-    }
-
-    private static SingleResolutionResult singleResultHelper(
-        DexClass initialResolutionResult, Entry<DexClass, DexEncodedMethod> entry) {
-      return new SingleResolutionResult(
-          initialResolutionResult != null ? initialResolutionResult : entry.getKey(),
-          entry.getKey(),
-          entry.getValue());
-    }
-  }
 }
diff --git a/src/main/java/com/android/tools/r8/graph/AppView.java b/src/main/java/com/android/tools/r8/graph/AppView.java
index db99838..300092c 100644
--- a/src/main/java/com/android/tools/r8/graph/AppView.java
+++ b/src/main/java/com/android/tools/r8/graph/AppView.java
@@ -697,9 +697,13 @@
     // Even if we can compute isSubtype by having class hierarchy we may not be allowed to ask the
     // question for all code paths in D8. Having the check for liveness ensure that we are in R8
     // territory.
-    return appInfo().hasLiveness()
-        ? OptionalBool.of(appInfo().withLiveness().isSubtype(subtype, supertype))
-        : OptionalBool.unknown();
+    if (hasClassHierarchy()) {
+      return OptionalBool.of(appInfo().withClassHierarchy().isSubtype(subtype, supertype));
+    }
+    if (subtype == supertype || supertype == dexItemFactory().objectType) {
+      return OptionalBool.TRUE;
+    }
+    return OptionalBool.unknown();
   }
 
   public boolean isCfByteCodePassThrough(DexEncodedMethod method) {
diff --git a/src/main/java/com/android/tools/r8/graph/CfCode.java b/src/main/java/com/android/tools/r8/graph/CfCode.java
index 4358a27..910d5f5 100644
--- a/src/main/java/com/android/tools/r8/graph/CfCode.java
+++ b/src/main/java/com/android/tools/r8/graph/CfCode.java
@@ -35,6 +35,8 @@
 import com.android.tools.r8.ir.conversion.ExtraParameter;
 import com.android.tools.r8.ir.conversion.IRBuilder;
 import com.android.tools.r8.ir.conversion.LensCodeRewriterUtils;
+import com.android.tools.r8.ir.conversion.MethodConversionOptions.MutableMethodConversionOptions;
+import com.android.tools.r8.ir.conversion.MethodConversionOptions.ThrowingMethodConversionOptions;
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
 import com.android.tools.r8.ir.optimize.InliningConstraints;
 import com.android.tools.r8.naming.ClassNameMapper;
@@ -60,7 +62,6 @@
 import java.util.List;
 import java.util.ListIterator;
 import java.util.Map;
-import java.util.Map.Entry;
 import java.util.function.BiPredicate;
 import org.objectweb.asm.Label;
 import org.objectweb.asm.MethodVisitor;
@@ -310,6 +311,16 @@
     return estimatedSizeForInlining() * Base5Format.SIZE;
   }
 
+  public int bytecodeSizeUpperBound() {
+    int result = 0;
+    for (CfInstruction instruction : instructions) {
+      int delta = instruction.bytecodeSizeUpperBound();
+      assert delta > 0 || !instruction.emitsIR();
+      result += delta;
+    }
+    return result;
+  }
+
   private int countNonStackOperations(int threshold) {
     int result = 0;
     for (CfInstruction instruction : instructions) {
@@ -443,8 +454,8 @@
     }
     if (parameterLabel != null) {
       assert localVariables.isEmpty();
-      Map<Integer, DebugLocalInfo> parameterInfo = method.getDefinition().getParameterInfo();
-      for (Entry<Integer, DebugLocalInfo> entry : parameterInfo.entrySet()) {
+      Int2ReferenceMap<DebugLocalInfo> parameterInfo = method.getDefinition().getParameterInfo();
+      for (Int2ReferenceMap.Entry<DebugLocalInfo> entry : parameterInfo.int2ReferenceEntrySet()) {
         writeLocalVariableEntry(
             visitor,
             graphLens,
@@ -452,7 +463,7 @@
             entry.getValue(),
             parameterLabel,
             parameterLabel,
-            entry.getKey());
+            entry.getIntKey());
       }
     } else {
       for (LocalVariableInfo local : localVariables) {
@@ -503,10 +514,14 @@
   }
 
   @Override
-  public IRCode buildIR(ProgramMethod method, AppView<?> appView, Origin origin) {
+  public IRCode buildIR(
+      ProgramMethod method,
+      AppView<?> appView,
+      Origin origin,
+      MutableMethodConversionOptions conversionOptions) {
     verifyFramesOrRemove(method, appView, getCodeLens(appView));
     return internalBuildPossiblyWithLocals(
-        method, method, appView, appView.codeLens(), null, null, origin, null);
+        method, method, appView, appView.codeLens(), null, null, origin, null, conversionOptions);
   }
 
   @Override
@@ -531,7 +546,8 @@
         valueNumberGenerator,
         callerPosition,
         origin,
-        protoChanges);
+        protoChanges,
+        new ThrowingMethodConversionOptions(appView.options()));
   }
 
   private void verifyFramesOrRemove(ProgramMethod method, AppView<?> appView, GraphLens codeLens) {
@@ -552,8 +568,9 @@
       NumberGenerator valueNumberGenerator,
       Position callerPosition,
       Origin origin,
-      RewrittenPrototypeDescription protoChanges) {
-    if (!method.getDefinition().keepLocals(appView.options())) {
+      RewrittenPrototypeDescription protoChanges,
+      MutableMethodConversionOptions conversionOptions) {
+    if (!method.keepLocals(appView)) {
       return internalBuild(
           Collections.emptyList(),
           context,
@@ -563,7 +580,8 @@
           valueNumberGenerator,
           callerPosition,
           origin,
-          protoChanges);
+          protoChanges,
+          conversionOptions);
     } else {
       return internalBuildWithLocals(
           context,
@@ -573,7 +591,8 @@
           valueNumberGenerator,
           callerPosition,
           origin,
-          protoChanges);
+          protoChanges,
+          conversionOptions);
     }
   }
 
@@ -586,7 +605,8 @@
       NumberGenerator valueNumberGenerator,
       Position callerPosition,
       Origin origin,
-      RewrittenPrototypeDescription protoChanges) {
+      RewrittenPrototypeDescription protoChanges,
+      MutableMethodConversionOptions conversionOptions) {
     try {
       return internalBuild(
           Collections.unmodifiableList(localVariables),
@@ -597,7 +617,8 @@
           valueNumberGenerator,
           callerPosition,
           origin,
-          protoChanges);
+          protoChanges,
+          conversionOptions);
     } catch (InvalidDebugInfoException e) {
       appView.options().warningInvalidDebugInfo(method, origin, e);
       return internalBuild(
@@ -609,7 +630,8 @@
           valueNumberGenerator,
           callerPosition,
           origin,
-          protoChanges);
+          protoChanges,
+          conversionOptions);
     }
   }
 
@@ -623,7 +645,8 @@
       NumberGenerator valueNumberGenerator,
       Position callerPosition,
       Origin origin,
-      RewrittenPrototypeDescription protoChanges) {
+      RewrittenPrototypeDescription protoChanges,
+      MutableMethodConversionOptions conversionOptions) {
     CfSourceCode source =
         new CfSourceCode(
             this,
@@ -642,7 +665,7 @@
           IRBuilder.createForInlining(
               method, appView, codeLens, source, origin, valueNumberGenerator, protoChanges);
     }
-    return builder.build(context);
+    return builder.build(context, conversionOptions);
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/graph/Code.java b/src/main/java/com/android/tools/r8/graph/Code.java
index 0f4233d..8152305 100644
--- a/src/main/java/com/android/tools/r8/graph/Code.java
+++ b/src/main/java/com/android/tools/r8/graph/Code.java
@@ -12,13 +12,22 @@
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.NumberGenerator;
 import com.android.tools.r8.ir.code.Position;
+import com.android.tools.r8.ir.conversion.MethodConversionOptions.MutableMethodConversionOptions;
 import com.android.tools.r8.naming.ClassNameMapper;
 import com.android.tools.r8.origin.Origin;
 import it.unimi.dsi.fastutil.ints.Int2ReferenceMap;
 
 public abstract class Code extends CachedHashValueDexItem {
 
-  public abstract IRCode buildIR(ProgramMethod method, AppView<?> appView, Origin origin);
+  public final IRCode buildIR(ProgramMethod method, AppView<?> appView, Origin origin) {
+    return buildIR(method, appView, origin, new MutableMethodConversionOptions(appView.options()));
+  }
+
+  public abstract IRCode buildIR(
+      ProgramMethod method,
+      AppView<?> appView,
+      Origin origin,
+      MutableMethodConversionOptions conversionOptions);
 
   public IRCode buildInliningIR(
       ProgramMethod context,
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 a544337..07fd8c4 100644
--- a/src/main/java/com/android/tools/r8/graph/DefaultInstanceInitializerCode.java
+++ b/src/main/java/com/android/tools/r8/graph/DefaultInstanceInitializerCode.java
@@ -24,6 +24,8 @@
 import com.android.tools.r8.ir.code.ValueType;
 import com.android.tools.r8.ir.conversion.IRBuilder;
 import com.android.tools.r8.ir.conversion.LensCodeRewriterUtils;
+import com.android.tools.r8.ir.conversion.MethodConversionOptions.MutableMethodConversionOptions;
+import com.android.tools.r8.ir.conversion.MethodConversionOptions.ThrowingMethodConversionOptions;
 import com.android.tools.r8.ir.conversion.SyntheticStraightLineSourceCode;
 import com.android.tools.r8.naming.ClassNameMapper;
 import com.android.tools.r8.naming.NamingLens;
@@ -66,7 +68,7 @@
 
   public static boolean canonicalizeCodeIfPossible(AppView<?> appView, ProgramMethod method) {
     if (hasDefaultInstanceInitializerCode(method, appView)) {
-      method.getDefinition().setCode(get(), appView);
+      method.setCode(get(), appView);
       return true;
     }
     return false;
@@ -80,7 +82,7 @@
       AppView<?> appView, ProgramMethod method, DexType superType) {
     DexEncodedMethod definition = method.getDefinition();
     assert definition.getCode().isDefaultInstanceInitializerCode();
-    definition.setCode(get().toCfCode(method, appView.dexItemFactory(), superType), appView);
+    method.setCode(get().toCfCode(method, appView.dexItemFactory(), superType), appView);
   }
 
   private static boolean hasDefaultInstanceInitializerCode(
@@ -134,12 +136,16 @@
   }
 
   @Override
-  public IRCode buildIR(ProgramMethod method, AppView<?> appView, Origin origin) {
+  public IRCode buildIR(
+      ProgramMethod method,
+      AppView<?> appView,
+      Origin origin,
+      MutableMethodConversionOptions conversionOptions) {
     DexMethod originalMethod =
         appView.graphLens().getOriginalMethodSignature(method.getReference());
     DefaultInstanceInitializerSourceCode source =
         new DefaultInstanceInitializerSourceCode(originalMethod);
-    return IRBuilder.create(method, appView, source, origin).build(method);
+    return IRBuilder.create(method, appView, source, origin).build(method, conversionOptions);
   }
 
   @Override
@@ -158,7 +164,7 @@
         new DefaultInstanceInitializerSourceCode(originalMethod, callerPosition);
     return IRBuilder.createForInlining(
             method, appView, codeLens, source, origin, valueNumberGenerator, protoChanges)
-        .build(context);
+        .build(context, new ThrowingMethodConversionOptions(appView.options()));
   }
 
   @Override
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 3c3ce77..c99b310 100644
--- a/src/main/java/com/android/tools/r8/graph/DexCode.java
+++ b/src/main/java/com/android/tools/r8/graph/DexCode.java
@@ -13,7 +13,7 @@
 import com.android.tools.r8.dex.MixedSectionCollection;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.DexCode.TryHandler.TypeAddrPair;
-import com.android.tools.r8.graph.DexDebugEvent.SetInlineFrame;
+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.bytecodemetadata.BytecodeInstructionMetadata;
@@ -26,6 +26,8 @@
 import com.android.tools.r8.ir.conversion.DexSourceCode;
 import com.android.tools.r8.ir.conversion.IRBuilder;
 import com.android.tools.r8.ir.conversion.LensCodeRewriterUtils;
+import com.android.tools.r8.ir.conversion.MethodConversionOptions.MutableMethodConversionOptions;
+import com.android.tools.r8.ir.conversion.MethodConversionOptions.ThrowingMethodConversionOptions;
 import com.android.tools.r8.naming.ClassNameMapper;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.utils.ArrayUtils;
@@ -277,6 +279,12 @@
       DexMethod caller, DexMethod callee, DexItemFactory factory) {
     Position callerPosition = SyntheticPosition.builder().setLine(0).setMethod(caller).build();
     EventBasedDebugInfo eventBasedInfo = DexDebugInfo.convertToEventBased(this, factory);
+    Position inlinePosition =
+        SyntheticPosition.builder()
+            .setMethod(caller)
+            .setCallerPosition(callerPosition)
+            .disableLineCheck()
+            .build();
     if (eventBasedInfo == null) {
       // If the method has no debug info we generate a preamble position to denote the inlining.
       // This is consistent with the building IR for inlining which will always ensure the method
@@ -285,22 +293,20 @@
           0,
           new DexString[callee.getArity()],
           new DexDebugEvent[] {
-            new DexDebugEvent.SetInlineFrame(callee, callerPosition), factory.zeroChangeDefaultEvent
+            new SetPositionFrame(inlinePosition), factory.zeroChangeDefaultEvent
           });
     }
     DexDebugEvent[] oldEvents = eventBasedInfo.events;
     DexDebugEvent[] newEvents = new DexDebugEvent[oldEvents.length + 1];
     int i = 0;
-    newEvents[i++] = new DexDebugEvent.SetInlineFrame(callee, callerPosition);
+    newEvents[i++] = new SetPositionFrame(inlinePosition);
     for (DexDebugEvent event : oldEvents) {
-      if (event instanceof SetInlineFrame) {
-        SetInlineFrame oldFrame = (SetInlineFrame) event;
+      if (event instanceof SetPositionFrame) {
+        SetPositionFrame oldFrame = (SetPositionFrame) event;
+        assert oldFrame.getPosition() != null;
         newEvents[i++] =
-            new SetInlineFrame(
-                oldFrame.callee,
-                oldFrame.caller == null
-                    ? callerPosition
-                    : oldFrame.caller.withOutermostCallerPosition(callerPosition));
+            new SetPositionFrame(
+                oldFrame.getPosition().withOutermostCallerPosition(callerPosition));
       } else {
         newEvents[i++] = event;
       }
@@ -366,7 +372,11 @@
   }
 
   @Override
-  public IRCode buildIR(ProgramMethod method, AppView<?> appView, Origin origin) {
+  public IRCode buildIR(
+      ProgramMethod method,
+      AppView<?> appView,
+      Origin origin,
+      MutableMethodConversionOptions conversionOptions) {
     DexSourceCode source =
         new DexSourceCode(
             this,
@@ -374,7 +384,7 @@
             appView.graphLens().getOriginalMethodSignature(method.getReference()),
             null,
             appView.dexItemFactory());
-    return IRBuilder.create(method, appView, source, origin).build(method);
+    return IRBuilder.create(method, appView, source, origin).build(method, conversionOptions);
   }
 
   @Override
@@ -396,7 +406,7 @@
             appView.dexItemFactory());
     return IRBuilder.createForInlining(
             method, appView, codeLens, source, origin, valueNumberGenerator, protoChanges)
-        .build(context);
+        .build(context, new ThrowingMethodConversionOptions(appView.options()));
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/graph/DexDebugEntry.java b/src/main/java/com/android/tools/r8/graph/DexDebugEntry.java
index 2547996..f89353c 100644
--- a/src/main/java/com/android/tools/r8/graph/DexDebugEntry.java
+++ b/src/main/java/com/android/tools/r8/graph/DexDebugEntry.java
@@ -4,11 +4,18 @@
 package com.android.tools.r8.graph;
 
 import com.android.tools.r8.ir.code.Position;
+import com.android.tools.r8.ir.code.Position.OutlineCallerPosition;
+import com.android.tools.r8.ir.code.Position.OutlineCallerPosition.OutlineCallerPositionBuilder;
+import com.android.tools.r8.ir.code.Position.OutlinePosition;
+import com.android.tools.r8.ir.code.Position.PositionBuilder;
+import com.android.tools.r8.ir.code.Position.SourcePosition;
+import com.android.tools.r8.utils.Int2StructuralItemArrayMap;
 import com.android.tools.r8.utils.StringUtils;
 import com.google.common.collect.ImmutableMap;
 import java.util.Map;
 import java.util.SortedSet;
 import java.util.TreeSet;
+import java.util.function.Function;
 
 public class DexDebugEntry {
 
@@ -21,6 +28,9 @@
   public final Map<Integer, DebugLocalInfo> locals;
   public final DexMethod method;
   public final Position callerPosition;
+  public final boolean isOutline;
+  public final DexMethod outlineCallee;
+  public final Int2StructuralItemArrayMap<Position> outlineCallerPositions;
 
   public DexDebugEntry(
       boolean lineEntry,
@@ -31,7 +41,10 @@
       boolean epilogueBegin,
       ImmutableMap<Integer, DebugLocalInfo> locals,
       DexMethod method,
-      Position callerPosition) {
+      Position callerPosition,
+      boolean isOutline,
+      DexMethod outlineCallee,
+      Int2StructuralItemArrayMap<Position> outlineCallerPositions) {
     this.lineEntry = lineEntry;
     this.address = address;
     this.line = line;
@@ -42,6 +55,9 @@
     this.method = method;
     assert method != null;
     this.callerPosition = callerPosition;
+    this.isOutline = isOutline;
+    this.outlineCallee = outlineCallee;
+    this.outlineCallerPositions = outlineCallerPositions;
   }
 
   @Override
@@ -67,6 +83,15 @@
         caller = caller.getCallerPosition();
       }
     }
+    if (isOutline) {
+      builder.append(", isOutline = true");
+    }
+    if (outlineCallee != null) {
+      builder.append(", outlineCallee = ").append(outlineCallee);
+    }
+    if (outlineCallerPositions != null) {
+      builder.append(", outlineCallerPositions = ").append(outlineCallerPositions);
+    }
     if (prologueEnd) {
       builder.append(", prologue_end = true");
     }
@@ -89,4 +114,23 @@
     }
     return builder.toString();
   }
+
+  public Position toPosition(Function<Position, Position> canonicalizeCallerPosition) {
+    PositionBuilder<?, ?> positionBuilder;
+    if (outlineCallee != null) {
+      OutlineCallerPositionBuilder outlineCallerPositionBuilder =
+          OutlineCallerPosition.builder().setOutlineCallee(outlineCallee).setIsOutline(isOutline);
+      outlineCallerPositions.forEach(outlineCallerPositionBuilder::addOutlinePosition);
+      positionBuilder = outlineCallerPositionBuilder;
+    } else if (isOutline) {
+      positionBuilder = OutlinePosition.builder();
+    } else {
+      positionBuilder = SourcePosition.builder().setFile(sourceFile);
+    }
+    return positionBuilder
+        .setLine(line)
+        .setMethod(method)
+        .setCallerPosition(canonicalizeCallerPosition.apply(callerPosition))
+        .build();
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/graph/DexDebugEntryBuilder.java b/src/main/java/com/android/tools/r8/graph/DexDebugEntryBuilder.java
index d8b6a9b..5a4fd84 100644
--- a/src/main/java/com/android/tools/r8/graph/DexDebugEntryBuilder.java
+++ b/src/main/java/com/android/tools/r8/graph/DexDebugEntryBuilder.java
@@ -3,8 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.graph;
 
-import com.android.tools.r8.graph.DexDebugEvent.SetOutlineCallerFrame;
-import com.android.tools.r8.graph.DexDebugEvent.SetOutlineFrame;
+import com.android.tools.r8.graph.DexDebugEvent.SetPositionFrame;
 import com.android.tools.r8.graph.DexDebugInfo.EventBasedDebugInfo;
 import com.android.tools.r8.ir.code.ValueType;
 import com.google.common.collect.ImmutableMap;
@@ -111,18 +110,8 @@
   }
 
   @Override
-  public void visit(DexDebugEvent.SetInlineFrame setInlineFrame) {
-    positionState.visit(setInlineFrame);
-  }
-
-  @Override
-  public void visit(SetOutlineFrame setOutlineFrame) {
-    positionState.visit(setOutlineFrame);
-  }
-
-  @Override
-  public void visit(SetOutlineCallerFrame setOutlineCallerFrame) {
-    positionState.visit(setOutlineCallerFrame);
+  public void visit(SetPositionFrame setPositionFrame) {
+    positionState.visit(setPositionFrame);
   }
 
   @Override
@@ -181,7 +170,10 @@
               pending.epilogueBegin,
               getLocals(),
               pending.method,
-              pending.callerPosition));
+              pending.callerPosition,
+              pending.isOutline,
+              pending.outlineCallee,
+              pending.outlineCallerPositions));
     }
     pending =
         new DexDebugEntry(
@@ -193,7 +185,10 @@
             epilogueBegin,
             null,
             positionState.getCurrentMethod(),
-            positionState.getCurrentCallerPosition());
+            positionState.getCurrentCallerPosition(),
+            positionState.isOutline(),
+            positionState.getOutlineCallee(),
+            positionState.getOutlineCallerPositions());
     prologueEnd = false;
     epilogueBegin = false;
   }
diff --git a/src/main/java/com/android/tools/r8/graph/DexDebugEvent.java b/src/main/java/com/android/tools/r8/graph/DexDebugEvent.java
index 60cc65a..27aacbe 100644
--- a/src/main/java/com/android/tools/r8/graph/DexDebugEvent.java
+++ b/src/main/java/com/android/tools/r8/graph/DexDebugEvent.java
@@ -9,7 +9,6 @@
 import com.android.tools.r8.dex.MixedSectionCollection;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.ir.code.Position;
-import com.android.tools.r8.utils.Int2StructuralItemArrayMap;
 import com.android.tools.r8.utils.structural.CompareToVisitor;
 import com.android.tools.r8.utils.structural.HashingVisitor;
 import com.android.tools.r8.utils.structural.StructuralItem;
@@ -20,9 +19,7 @@
 public abstract class DexDebugEvent extends DexItem implements StructuralItem<DexDebugEvent> {
 
   // Compare ID(s) for virtual debug events.
-  private static final int DBG_SET_INLINE_FRAME_COMPARE_ID = Constants.DBG_LAST_SPECIAL + 1;
-  private static final int DBG_SET_OUTLINE_FRAME_COMPARE_ID = Constants.DBG_LAST_SPECIAL + 2;
-  private static final int DBG_SET_OUTLINE_CALLER_COMPARE_ID = Constants.DBG_LAST_SPECIAL + 3;
+  private static final int DBG_SET_POSITION_FRAME_COMPARE_ID = Constants.DBG_LAST_SPECIAL + 1;
 
   public static final DexDebugEvent[] EMPTY_ARRAY = {};
 
@@ -92,23 +89,11 @@
 
   public abstract void accept(DexDebugEventVisitor visitor);
 
-  public boolean isSetInlineFrame() {
+  public boolean isPositionFrame() {
     return false;
   }
 
-  public boolean isSetOutlineFrame() {
-    return false;
-  }
-
-  public boolean isSetOutlineCallerFrame() {
-    return false;
-  }
-
-  public SetInlineFrame asSetInlineFrame() {
-    return null;
-  }
-
-  public SetOutlineCallerFrame asSetOutlineCallerFrame() {
+  public SetPositionFrame asSetPositionFrame() {
     return null;
   }
 
@@ -575,19 +560,20 @@
     }
   }
 
-  public static class SetInlineFrame extends DexDebugEvent {
+  public static class SetPositionFrame extends DexDebugEvent {
 
-    final DexMethod callee;
-    final Position caller;
+    private final Position position;
 
-    private static void specify(StructuralSpecification<SetInlineFrame, ?> spec) {
-      spec.withItem(e -> e.callee).withNullableItem(e -> e.caller);
+    private static void specify(StructuralSpecification<SetPositionFrame, ?> spec) {
+      spec.withNullableItem(e -> e.position);
     }
 
-    SetInlineFrame(DexMethod callee, Position caller) {
-      assert callee != null;
-      this.callee = callee;
-      this.caller = caller;
+    SetPositionFrame(Position position) {
+      this.position = position;
+    }
+
+    public Position getPosition() {
+      return position;
     }
 
     @Override
@@ -597,140 +583,36 @@
 
     @Override
     public String toString() {
-      return String.format("SET_INLINE_FRAME %s %s", callee, caller);
+      return String.format("SET_POSITION_FRAME %s", position);
     }
 
     @Override
     public int hashCode() {
-      return 31 * callee.hashCode() + Objects.hashCode(caller);
+      return 31 * Objects.hashCode(position);
     }
 
     @Override
     int getCompareToId() {
-      return DBG_SET_INLINE_FRAME_COMPARE_ID;
+      return DBG_SET_POSITION_FRAME_COMPARE_ID;
     }
 
     @Override
     int internalAcceptCompareTo(DexDebugEvent other, CompareToVisitor visitor) {
-      return visitor.visit(this, (SetInlineFrame) other, SetInlineFrame::specify);
+      return visitor.visit(this, (SetPositionFrame) other, SetPositionFrame::specify);
     }
 
     @Override
     void internalAcceptHashing(HashingVisitor visitor) {
-      visitor.visit(this, SetInlineFrame::specify);
+      visitor.visit(this, SetPositionFrame::specify);
     }
 
     @Override
-    public boolean isSetInlineFrame() {
+    public boolean isPositionFrame() {
       return true;
     }
 
     @Override
-    public SetInlineFrame asSetInlineFrame() {
-      return this;
-    }
-
-    public boolean hasOuterPosition(DexMethod method) {
-      return (caller == null && callee == method)
-          || (caller != null && caller.getOutermostCaller().getMethod() == method);
-    }
-  }
-
-  public static class SetOutlineFrame extends DexDebugEvent {
-
-    @Override
-    public String toString() {
-      return "SET_OUTLINE_FRAME";
-    }
-
-    @Override
-    public int hashCode() {
-      return 7;
-    }
-
-    @Override
-    int getCompareToId() {
-      return DBG_SET_OUTLINE_FRAME_COMPARE_ID;
-    }
-
-    @Override
-    int internalAcceptCompareTo(DexDebugEvent other, CompareToVisitor visitor) {
-      return 0;
-    }
-
-    @Override
-    void internalAcceptHashing(HashingVisitor visitor) {
-      // Intentionally empty: no content besides the compare-id
-    }
-
-    @Override
-    public void accept(DexDebugEventVisitor visitor) {
-      visitor.visit(this);
-    }
-  }
-
-  public static class SetOutlineCallerFrame extends DexDebugEvent {
-
-    private final DexMethod outlineCallee;
-    private final Int2StructuralItemArrayMap<Position> outlinePositions;
-
-    private static void specify(StructuralSpecification<SetOutlineCallerFrame, ?> spec) {
-      spec.withItem(e -> e.outlineCallee).withNullableItem(e -> e.outlinePositions);
-    }
-
-    SetOutlineCallerFrame(
-        DexMethod outlineCallee, Int2StructuralItemArrayMap<Position> outlinePositions) {
-      assert outlineCallee != null;
-      assert !outlinePositions.isEmpty();
-      this.outlineCallee = outlineCallee;
-      this.outlinePositions = outlinePositions;
-    }
-
-    public DexMethod getOutlineCallee() {
-      return outlineCallee;
-    }
-
-    public Int2StructuralItemArrayMap<Position> getOutlinePositions() {
-      return outlinePositions;
-    }
-
-    @Override
-    public void accept(DexDebugEventVisitor visitor) {
-      visitor.visit(this);
-    }
-
-    @Override
-    public String toString() {
-      return String.format("SET_OUTLINE_CALLER_FRAME %s %s", outlineCallee, outlinePositions);
-    }
-
-    @Override
-    public int hashCode() {
-      return Objects.hash(outlineCallee, outlinePositions);
-    }
-
-    @Override
-    int getCompareToId() {
-      return DBG_SET_OUTLINE_CALLER_COMPARE_ID;
-    }
-
-    @Override
-    int internalAcceptCompareTo(DexDebugEvent other, CompareToVisitor visitor) {
-      return visitor.visit(this, (SetOutlineCallerFrame) other, SetOutlineCallerFrame::specify);
-    }
-
-    @Override
-    void internalAcceptHashing(HashingVisitor visitor) {
-      visitor.visit(this, SetOutlineCallerFrame::specify);
-    }
-
-    @Override
-    public boolean isSetOutlineCallerFrame() {
-      return true;
-    }
-
-    @Override
-    public SetOutlineCallerFrame asSetOutlineCallerFrame() {
+    public SetPositionFrame asSetPositionFrame() {
       return this;
     }
   }
diff --git a/src/main/java/com/android/tools/r8/graph/DexDebugEventBuilder.java b/src/main/java/com/android/tools/r8/graph/DexDebugEventBuilder.java
index dc90361..e4c29aa 100644
--- a/src/main/java/com/android/tools/r8/graph/DexDebugEventBuilder.java
+++ b/src/main/java/com/android/tools/r8/graph/DexDebugEventBuilder.java
@@ -249,18 +249,14 @@
         || previousPosition.hasCallerPosition()
         || nextPosition.getMethod() == previousPosition.getMethod()
         || optimizingLineNumbers;
-    if (nextPosition.getCallerPosition() != previousPosition.getCallerPosition()
-        || nextPosition.getMethod() != previousPosition.getMethod()) {
-      events.add(
-          factory.createSetInlineFrame(nextPosition.getMethod(), nextPosition.getCallerPosition()));
-    }
-    if (nextPosition.isOutline()) {
-      events.add(factory.createSetOutlineFrame());
-    }
-    if (nextPosition.getOutlineCallee() != null && !nextPosition.getOutlinePositions().isEmpty()) {
-      events.add(
-          factory.createSetOutlineCallerFrame(
-              nextPosition.getOutlineCallee(), nextPosition.getOutlinePositions()));
+    boolean isNewPosition =
+        nextPosition.getCallerPosition() != previousPosition.getCallerPosition()
+            || nextPosition.getMethod() != previousPosition.getMethod();
+    boolean isOutline = nextPosition.isOutline();
+    boolean isOutlineCallee =
+        nextPosition.getOutlineCallee() != null && !nextPosition.getOutlinePositions().isEmpty();
+    if (isNewPosition || isOutline || isOutlineCallee) {
+      events.add(factory.createPositionFrame(nextPosition));
     }
     addDefaultEventWithAdvancePcIfNecessary(lineDelta, pcDelta, events, factory);
   }
diff --git a/src/main/java/com/android/tools/r8/graph/DexDebugEventVisitor.java b/src/main/java/com/android/tools/r8/graph/DexDebugEventVisitor.java
index 165858b..b09f62d 100644
--- a/src/main/java/com/android/tools/r8/graph/DexDebugEventVisitor.java
+++ b/src/main/java/com/android/tools/r8/graph/DexDebugEventVisitor.java
@@ -10,9 +10,7 @@
 import com.android.tools.r8.graph.DexDebugEvent.RestartLocal;
 import com.android.tools.r8.graph.DexDebugEvent.SetEpilogueBegin;
 import com.android.tools.r8.graph.DexDebugEvent.SetFile;
-import com.android.tools.r8.graph.DexDebugEvent.SetInlineFrame;
-import com.android.tools.r8.graph.DexDebugEvent.SetOutlineCallerFrame;
-import com.android.tools.r8.graph.DexDebugEvent.SetOutlineFrame;
+import com.android.tools.r8.graph.DexDebugEvent.SetPositionFrame;
 import com.android.tools.r8.graph.DexDebugEvent.SetPrologueEnd;
 import com.android.tools.r8.graph.DexDebugEvent.StartLocal;
 
@@ -21,11 +19,7 @@
 
   void visit(AdvanceLine advanceLine);
 
-  void visit(SetInlineFrame setInlineFrame);
-
-  void visit(SetOutlineFrame setOutlineFrame);
-
-  void visit(SetOutlineCallerFrame setOutlineCallerFrame);
+  void visit(SetPositionFrame setPositionFrame);
 
   void visit(Default defaultEvent);
 
diff --git a/src/main/java/com/android/tools/r8/graph/DexDebugPositionState.java b/src/main/java/com/android/tools/r8/graph/DexDebugPositionState.java
index 9c5ffd6..10a06d5 100644
--- a/src/main/java/com/android/tools/r8/graph/DexDebugPositionState.java
+++ b/src/main/java/com/android/tools/r8/graph/DexDebugPositionState.java
@@ -11,9 +11,7 @@
 import com.android.tools.r8.graph.DexDebugEvent.RestartLocal;
 import com.android.tools.r8.graph.DexDebugEvent.SetEpilogueBegin;
 import com.android.tools.r8.graph.DexDebugEvent.SetFile;
-import com.android.tools.r8.graph.DexDebugEvent.SetInlineFrame;
-import com.android.tools.r8.graph.DexDebugEvent.SetOutlineCallerFrame;
-import com.android.tools.r8.graph.DexDebugEvent.SetOutlineFrame;
+import com.android.tools.r8.graph.DexDebugEvent.SetPositionFrame;
 import com.android.tools.r8.graph.DexDebugEvent.SetPrologueEnd;
 import com.android.tools.r8.graph.DexDebugEvent.StartLocal;
 import com.android.tools.r8.ir.code.Position;
@@ -51,20 +49,14 @@
   }
 
   @Override
-  public void visit(SetInlineFrame setInlineFrame) {
-    currentMethod = setInlineFrame.callee;
-    currentCallerPosition = setInlineFrame.caller;
-  }
-
-  @Override
-  public void visit(SetOutlineFrame setOutlineFrame) {
-    isOutline = true;
-  }
-
-  @Override
-  public void visit(SetOutlineCallerFrame setOutlineCallerFrame) {
-    outlineCallee = setOutlineCallerFrame.getOutlineCallee();
-    outlineCallerPositions = setOutlineCallerFrame.getOutlinePositions();
+  public void visit(SetPositionFrame setPositionFrame) {
+    assert setPositionFrame.getPosition() != null;
+    Position position = setPositionFrame.getPosition();
+    currentMethod = position.getMethod();
+    currentCallerPosition = position.getCallerPosition();
+    outlineCallee = position.getOutlineCallee();
+    outlineCallerPositions = position.getOutlinePositions();
+    isOutline = position.isOutline();
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
index c33d18d..4196ca2 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
@@ -43,12 +43,9 @@
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.DexAnnotation.AnnotatedKind;
 import com.android.tools.r8.graph.GenericSignature.MethodTypeSignature;
-import com.android.tools.r8.graph.bytecodemetadata.BytecodeMetadataProvider;
 import com.android.tools.r8.graph.proto.ArgumentInfoCollection;
-import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.NumericType;
 import com.android.tools.r8.ir.code.ValueType;
-import com.android.tools.r8.ir.conversion.DexBuilder;
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
 import com.android.tools.r8.ir.optimize.Inliner.Reason;
 import com.android.tools.r8.ir.optimize.NestUtils;
@@ -57,7 +54,6 @@
 import com.android.tools.r8.ir.optimize.info.MethodOptimizationInfoFixer;
 import com.android.tools.r8.ir.optimize.info.MutableMethodOptimizationInfo;
 import com.android.tools.r8.ir.optimize.inliner.WhyAreYouNotInliningReporter;
-import com.android.tools.r8.ir.regalloc.RegisterAllocator;
 import com.android.tools.r8.ir.synthetic.ForwardMethodBuilder;
 import com.android.tools.r8.kotlin.KotlinMethodLevelInfo;
 import com.android.tools.r8.naming.ClassNameMapper;
@@ -81,7 +77,6 @@
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
-import java.util.Map;
 import java.util.function.BiConsumer;
 import java.util.function.BiFunction;
 import java.util.function.Consumer;
@@ -726,24 +721,10 @@
     compilationState = CompilationState.NOT_PROCESSED;
   }
 
-  public void setCode(Code newCode, AppView<?> appView) {
+  public void setCode(Code code, Int2ReferenceMap<DebugLocalInfo> parameterInfo) {
     checkIfObsolete();
-    // If the locals are not kept, we might still need information to satisfy -keepparameternames.
-    // The information needs to be retrieved on the original code object before replacing it.
-    if (code != null && code.isCfCode() && !hasParameterInfo() && !keepLocals(appView.options())) {
-      setParameterInfo(code.collectParameterInfo(this, appView));
-    }
-    code = newCode;
-  }
-
-  public void setCode(
-      IRCode ir,
-      BytecodeMetadataProvider bytecodeMetadataProvider,
-      RegisterAllocator registerAllocator,
-      AppView<?> appView) {
-    checkIfObsolete();
-    DexBuilder builder = new DexBuilder(ir, bytecodeMetadataProvider, registerAllocator);
-    setCode(builder.build(), appView);
+    this.code = code;
+    this.parameterInfo = parameterInfo;
   }
 
   public void unsetCode() {
@@ -751,23 +732,11 @@
     code = null;
   }
 
-  public boolean keepLocals(InternalOptions options) {
-    if (options.testing.noLocalsTableOnInput) {
-      return false;
-    }
-    return options.debug || getOptimizationInfo().isReachabilitySensitive();
-  }
-
-  private void setParameterInfo(Int2ReferenceMap<DebugLocalInfo> parameterInfo) {
-    assert this.parameterInfo == NO_PARAMETER_INFO;
-    this.parameterInfo = parameterInfo;
-  }
-
   public boolean hasParameterInfo() {
     return parameterInfo != NO_PARAMETER_INFO;
   }
 
-  public Map<Integer, DebugLocalInfo> getParameterInfo() {
+  public Int2ReferenceMap<DebugLocalInfo> getParameterInfo() {
     return parameterInfo;
   }
 
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 6259d4c..3e5150c 100644
--- a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
+++ b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
@@ -17,9 +17,7 @@
 import com.android.tools.r8.graph.DexDebugEvent.RestartLocal;
 import com.android.tools.r8.graph.DexDebugEvent.SetEpilogueBegin;
 import com.android.tools.r8.graph.DexDebugEvent.SetFile;
-import com.android.tools.r8.graph.DexDebugEvent.SetInlineFrame;
-import com.android.tools.r8.graph.DexDebugEvent.SetOutlineCallerFrame;
-import com.android.tools.r8.graph.DexDebugEvent.SetOutlineFrame;
+import com.android.tools.r8.graph.DexDebugEvent.SetPositionFrame;
 import com.android.tools.r8.graph.DexDebugEvent.SetPrologueEnd;
 import com.android.tools.r8.graph.DexMethodHandle.MethodHandleType;
 import com.android.tools.r8.ir.analysis.type.ArrayTypeElement;
@@ -38,7 +36,6 @@
 import com.android.tools.r8.synthesis.SyntheticNaming;
 import com.android.tools.r8.utils.ArrayUtils;
 import com.android.tools.r8.utils.DescriptorUtils;
-import com.android.tools.r8.utils.Int2StructuralItemArrayMap;
 import com.android.tools.r8.utils.IterableUtils;
 import com.android.tools.r8.utils.LRUCacheTable;
 import com.android.tools.r8.utils.ListUtils;
@@ -99,8 +96,7 @@
   private final SetEpilogueBegin setEpilogueBegin = new SetEpilogueBegin();
   private final SetPrologueEnd setPrologueEnd = new SetPrologueEnd();
   private final Map<DexString, SetFile> setFiles = new HashMap<>();
-  private final SetOutlineFrame setOutlineFrame = new SetOutlineFrame();
-  private final Map<SetInlineFrame, SetInlineFrame> setInlineFrames = new HashMap<>();
+  private final Map<SetPositionFrame, SetPositionFrame> setInlineFrames = new HashMap<>();
   public final DexDebugEvent.Default zeroChangeDefaultEvent = createDefault(0, 0);
   public final DexDebugEvent.Default oneChangeDefaultEvent = createDefault(1, 1);
 
@@ -536,6 +532,7 @@
   public final FloatMembers floatMembers = new FloatMembers();
   public final IntegerMembers integerMembers = new IntegerMembers();
   public final LongMembers longMembers = new LongMembers();
+  public final VoidMembers voidMembers = new VoidMembers();
   public final ObjectsMethods objectsMethods = new ObjectsMethods();
   public final ObjectMembers objectMembers = new ObjectMembers();
   public final BufferMembers bufferMembers = new BufferMembers();
@@ -744,6 +741,34 @@
     return primitiveToBoxed.get(primitive);
   }
 
+  public BoxedPrimitiveMembers getBoxedMembersForPrimitiveOrVoidType(DexType type) {
+    assert type.isPrimitiveType() || type.isVoidType();
+    char c = (char) type.getDescriptor().content[0];
+    assert c == type.toDescriptorString().charAt(0);
+    switch (c) {
+      case 'B':
+        return byteMembers;
+      case 'C':
+        return charMembers;
+      case 'D':
+        return doubleMembers;
+      case 'F':
+        return floatMembers;
+      case 'I':
+        return integerMembers;
+      case 'J':
+        return longMembers;
+      case 'S':
+        return shortMembers;
+      case 'V':
+        return voidMembers;
+      case 'Z':
+        return booleanMembers;
+      default:
+        throw new Unreachable("Unknown type " + type);
+    }
+  }
+
   public DexType getPrimitiveFromBoxed(DexType boxedPrimitive) {
     return primitiveToBoxed.inverse().get(boxedPrimitive);
   }
@@ -884,6 +909,11 @@
     public void forEachFinalField(Consumer<DexField> consumer) {}
   }
 
+  public abstract static class BoxedPrimitiveMembers extends LibraryMembers {
+
+    public abstract DexField getTypeField();
+  }
+
   public class AndroidOsBuildMembers extends LibraryMembers {
 
     public final DexField BOOTLOADER = createField(androidOsBuildType, stringType, "BOOTLOADER");
@@ -1004,7 +1034,7 @@
         createMethod(androidUtilSparseArrayType, createProto(voidType, intType, objectType), "set");
   }
 
-  public class BooleanMembers extends LibraryMembers {
+  public class BooleanMembers extends BoxedPrimitiveMembers {
 
     public final DexField FALSE = createField(boxedBooleanType, boxedBooleanType, "FALSE");
     public final DexField TRUE = createField(boxedBooleanType, boxedBooleanType, "TRUE");
@@ -1027,9 +1057,16 @@
       consumer.accept(TRUE);
       consumer.accept(TYPE);
     }
+
+    @Override
+    public DexField getTypeField() {
+      return TYPE;
+    }
   }
 
-  public class ByteMembers extends LibraryMembers {
+  public class ByteMembers extends BoxedPrimitiveMembers {
+
+    public final DexField TYPE = createField(boxedByteType, classType, "TYPE");
 
     public final DexMethod byteValue =
         createMethod(boxedByteType, createProto(byteType), "byteValue");
@@ -1039,17 +1076,29 @@
         createMethod(boxedByteType, createProto(boxedByteType, byteType), "valueOf");
 
     private ByteMembers() {}
+
+    @Override
+    public DexField getTypeField() {
+      return TYPE;
+    }
   }
 
-  public class CharMembers extends LibraryMembers {
+  public class CharMembers extends BoxedPrimitiveMembers {
+
+    public final DexField TYPE = createField(boxedCharType, classType, "TYPE");
 
     public final DexMethod toString =
         createMethod(boxedCharType, createProto(stringType), "toString");
 
     private CharMembers() {}
+
+    @Override
+    public DexField getTypeField() {
+      return TYPE;
+    }
   }
 
-  public class FloatMembers extends LibraryMembers {
+  public class FloatMembers extends BoxedPrimitiveMembers {
 
     public final DexField TYPE = createField(boxedFloatType, classType, "TYPE");
 
@@ -1062,6 +1111,11 @@
     public void forEachFinalField(Consumer<DexField> consumer) {
       consumer.accept(TYPE);
     }
+
+    @Override
+    public DexField getTypeField() {
+      return TYPE;
+    }
   }
 
   public class JavaIoFileMembers extends LibraryMembers {
@@ -1207,7 +1261,7 @@
     }
   }
 
-  public class LongMembers extends LibraryMembers {
+  public class LongMembers extends BoxedPrimitiveMembers {
 
     public final DexField TYPE = createField(boxedLongType, classType, "TYPE");
 
@@ -1224,9 +1278,16 @@
     public void forEachFinalField(Consumer<DexField> consumer) {
       consumer.accept(TYPE);
     }
+
+    @Override
+    public DexField getTypeField() {
+      return TYPE;
+    }
   }
 
-  public class DoubleMembers {
+  public class DoubleMembers extends BoxedPrimitiveMembers {
+
+    public final DexField TYPE = createField(boxedDoubleType, classType, "TYPE");
 
     public final DexMethod isNaN;
 
@@ -1241,9 +1302,14 @@
               booleanDescriptor,
               new DexString[] {doubleDescriptor});
     }
+
+    @Override
+    public DexField getTypeField() {
+      return TYPE;
+    }
   }
 
-  public class IntegerMembers extends LibraryMembers {
+  public class IntegerMembers extends BoxedPrimitiveMembers {
 
     public final DexField TYPE = createField(boxedIntType, classType, "TYPE");
 
@@ -1254,6 +1320,11 @@
     public void forEachFinalField(Consumer<DexField> consumer) {
       consumer.accept(TYPE);
     }
+
+    @Override
+    public DexField getTypeField() {
+      return TYPE;
+    }
   }
 
   public class StringConcatFactoryMembers {
@@ -1271,6 +1342,16 @@
             createString("makeConcatWithConstants"));
   }
 
+  public class VoidMembers extends BoxedPrimitiveMembers {
+
+    public final DexField TYPE = createField(voidType, classType, "TYPE");
+
+    @Override
+    public DexField getTypeField() {
+      return TYPE;
+    }
+  }
+
   public class ThrowableMethods {
 
     public final DexMethod addSuppressed;
@@ -1823,12 +1904,19 @@
     }
   }
 
-  public class ShortMembers extends LibraryMembers {
+  public class ShortMembers extends BoxedPrimitiveMembers {
+
+    public final DexField TYPE = createField(boxedShortType, classType, "TYPE");
 
     public final DexMethod toString =
         createMethod(boxedShortType, createProto(stringType), "toString");
 
     private ShortMembers() {}
+
+    @Override
+    public DexField getTypeField() {
+      return TYPE;
+    }
   }
 
   public class StringMembers extends LibraryMembers {
@@ -2806,21 +2894,12 @@
   }
 
   // TODO(tamaskenez) b/69024229 Measure if canonicalization is worth it.
-  public SetInlineFrame createSetInlineFrame(DexMethod callee, Position caller) {
+  public SetPositionFrame createPositionFrame(Position position) {
     synchronized (setInlineFrames) {
-      return setInlineFrames.computeIfAbsent(new SetInlineFrame(callee, caller), p -> p);
+      return setInlineFrames.computeIfAbsent(new SetPositionFrame(position), p -> p);
     }
   }
 
-  public SetOutlineFrame createSetOutlineFrame() {
-    return setOutlineFrame;
-  }
-
-  public SetOutlineCallerFrame createSetOutlineCallerFrame(
-      DexMethod outlineCallee, Int2StructuralItemArrayMap<Position> outlinePositions) {
-    return new SetOutlineCallerFrame(outlineCallee, outlinePositions);
-  }
-
   public boolean isConstructor(DexMethod method) {
     return method.name == constructorMethodName;
   }
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 658c2c0..354cc52 100644
--- a/src/main/java/com/android/tools/r8/graph/DexProgramClass.java
+++ b/src/main/java/com/android/tools/r8/graph/DexProgramClass.java
@@ -19,8 +19,9 @@
 import com.android.tools.r8.kotlin.KotlinClassLevelInfo;
 import com.android.tools.r8.naming.NamingLens;
 import com.android.tools.r8.origin.Origin;
-import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.synthesis.SyntheticMarker;
+import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.OptionalBool;
 import com.android.tools.r8.utils.TraversalContinuation;
 import com.android.tools.r8.utils.structural.Ordered;
 import com.android.tools.r8.utils.structural.StructuralItem;
@@ -52,6 +53,7 @@
   private CfVersion initialClassFileVersion = null;
   private boolean deprecated = false;
   private KotlinClassLevelInfo kotlinInfo = getNoKotlinInfo();
+  private OptionalBool reachabilitySensitive = OptionalBool.unknown();
 
   private final ChecksumSupplier checksumSupplier;
 
@@ -160,6 +162,33 @@
     return this;
   }
 
+  /**
+   * Is the class reachability sensitive.
+   *
+   * <p>A class is reachability sensitive if the
+   * dalvik.annotation.optimization.ReachabilitySensitive annotation is on any field or method. When
+   * that is the case, dead reference elimination is disabled and locals are kept alive for their
+   * entire scope.
+   */
+  public boolean getOrComputeReachabilitySensitive(AppView<?> appView) {
+    if (reachabilitySensitive.isUnknown()) {
+      reachabilitySensitive = OptionalBool.of(internalComputeReachabilitySensitive(appView));
+    }
+    return reachabilitySensitive.isTrue();
+  }
+
+  private boolean internalComputeReachabilitySensitive(AppView<?> appView) {
+    DexItemFactory dexItemFactory = appView.dexItemFactory();
+    for (DexEncodedMember<?, ?> member : members()) {
+      for (DexAnnotation annotation : member.annotations().annotations) {
+        if (annotation.annotation.type == dexItemFactory.annotationReachabilitySensitive) {
+          return true;
+        }
+      }
+    }
+    return false;
+  }
+
   @Override
   public StructuralMapping<DexProgramClass> getStructuralMapping() {
     return DexProgramClass::specify;
@@ -445,13 +474,11 @@
     if (isFinal()) {
       return true;
     }
-    if (appView.enableWholeProgramOptimizations()) {
-      assert appView.appInfo().hasLiveness();
-      AppInfoWithLiveness appInfo = appView.appInfo().withLiveness();
-      if (appInfo.isPinned(type)) {
-        return false;
-      }
-      return !appInfo.isInstantiatedIndirectly(this);
+    if (appView.hasLiveness()) {
+      assert appView.enableWholeProgramOptimizations();
+      InternalOptions options = appView.options();
+      return !appView.getKeepInfo(this).isPinned(options)
+          && !appView.appInfoWithLiveness().isInstantiatedIndirectly(this);
     }
     return false;
   }
@@ -752,25 +779,6 @@
     return deprecated;
   }
 
-  /**
-   * Is the class reachability sensitive.
-   *
-   * <p>A class is reachability sensitive if the
-   * dalvik.annotation.optimization.ReachabilitySensitive annotation is on any field or method. When
-   * that is the case, dead reference elimination is disabled and locals are kept alive for their
-   * entire scope.
-   */
-  public boolean hasReachabilitySensitiveAnnotation(DexItemFactory factory) {
-    for (DexEncodedMember<?, ?> member : members()) {
-      for (DexAnnotation annotation : member.annotations().annotations) {
-        if (annotation.annotation.type == factory.annotationReachabilitySensitive) {
-          return true;
-        }
-      }
-    }
-    return false;
-  }
-
   public static Iterable<DexProgramClass> asProgramClasses(
       Iterable<DexType> types, DexDefinitionSupplier definitions) {
     return () ->
diff --git a/src/main/java/com/android/tools/r8/graph/DexType.java b/src/main/java/com/android/tools/r8/graph/DexType.java
index 72884e9..810874b 100644
--- a/src/main/java/com/android/tools/r8/graph/DexType.java
+++ b/src/main/java/com/android/tools/r8/graph/DexType.java
@@ -337,14 +337,6 @@
     return isDoubleType() || isLongType();
   }
 
-  public boolean isSynthesizedTypeAllowedDuplication() {
-    // If we are desugaring Records, then the r8Record type is mapped back to java.lang.Record, and
-    // java.lang.Record can be duplicated.
-    // If we are not desugaring Records, then the r8Record type can be duplicated instead.
-    return descriptor.toString().equals(DexItemFactory.recordDescriptorString)
-        || descriptor.toString().equals(DexItemFactory.recordTagDescriptorString);
-  }
-
   public boolean isLegacySynthesizedTypeAllowedDuplication() {
     return oldSynthesizedName(toSourceString());
   }
diff --git a/src/main/java/com/android/tools/r8/graph/FieldAccessInfo.java b/src/main/java/com/android/tools/r8/graph/FieldAccessInfo.java
index 2cd2be0..34b3f2b 100644
--- a/src/main/java/com/android/tools/r8/graph/FieldAccessInfo.java
+++ b/src/main/java/com/android/tools/r8/graph/FieldAccessInfo.java
@@ -20,20 +20,32 @@
 
   int getNumberOfWriteContexts();
 
+  AbstractAccessContexts getReadsWithContexts();
+
+  AbstractAccessContexts getWritesWithContexts();
+
   ProgramMethod getUniqueReadContext();
 
+  boolean hasKnownReadContexts();
+
   boolean hasKnownWriteContexts();
 
   void forEachIndirectAccess(Consumer<DexField> consumer);
 
   void forEachIndirectAccessWithContexts(BiConsumer<DexField, ProgramMethodSet> consumer);
 
+  void forEachAccessContext(Consumer<ProgramMethod> consumer);
+
   void forEachReadContext(Consumer<ProgramMethod> consumer);
 
   void forEachWriteContext(Consumer<ProgramMethod> consumer);
 
   boolean hasReflectiveAccess();
 
+  boolean hasReflectiveRead();
+
+  boolean hasReflectiveWrite();
+
   default boolean isAccessedFromMethodHandle() {
     return isReadFromMethodHandle() || isWrittenFromMethodHandle();
   }
@@ -46,6 +58,8 @@
 
   boolean isReadFromMethodHandle();
 
+  boolean isReadOnlyInMethodSatisfying(Predicate<ProgramMethod> predicate);
+
   boolean isWritten();
 
   boolean isWrittenFromMethodHandle();
@@ -54,7 +68,5 @@
 
   boolean isWrittenOnlyInMethodSatisfying(Predicate<ProgramMethod> predicate);
 
-  boolean isReadOnlyInMethodSatisfying(Predicate<ProgramMethod> predicate);
-
   boolean isWrittenOutside(DexEncodedMethod method);
 }
diff --git a/src/main/java/com/android/tools/r8/graph/FieldAccessInfoCollectionImpl.java b/src/main/java/com/android/tools/r8/graph/FieldAccessInfoCollectionImpl.java
index 439ef05..8a8b9da 100644
--- a/src/main/java/com/android/tools/r8/graph/FieldAccessInfoCollectionImpl.java
+++ b/src/main/java/com/android/tools/r8/graph/FieldAccessInfoCollectionImpl.java
@@ -63,6 +63,10 @@
     infos.values().forEach(consumer);
   }
 
+  public void remove(DexField field) {
+    infos.remove(field);
+  }
+
   @Override
   public void removeIf(BiPredicate<DexField, FieldAccessInfoImpl> predicate) {
     infos.entrySet().removeIf(entry -> predicate.test(entry.getKey(), entry.getValue()));
diff --git a/src/main/java/com/android/tools/r8/graph/FieldAccessInfoImpl.java b/src/main/java/com/android/tools/r8/graph/FieldAccessInfoImpl.java
index 0a7d218..8779817 100644
--- a/src/main/java/com/android/tools/r8/graph/FieldAccessInfoImpl.java
+++ b/src/main/java/com/android/tools/r8/graph/FieldAccessInfoImpl.java
@@ -27,8 +27,9 @@
   public static int FLAG_IS_READ_FROM_ANNOTATION = 1 << 0;
   public static int FLAG_IS_READ_FROM_METHOD_HANDLE = 1 << 1;
   public static int FLAG_IS_WRITTEN_FROM_METHOD_HANDLE = 1 << 2;
-  public static int FLAG_HAS_REFLECTIVE_ACCESS = 1 << 3;
-  public static int FLAG_IS_READ_FROM_RECORD_INVOKE_DYNAMIC = 1 << 4;
+  public static int FLAG_HAS_REFLECTIVE_READ = 1 << 3;
+  public static int FLAG_HAS_REFLECTIVE_WRITE = 1 << 4;
+  public static int FLAG_IS_READ_FROM_RECORD_INVOKE_DYNAMIC = 1 << 5;
 
   // A direct reference to the definition of the field.
   private DexField field;
@@ -72,6 +73,7 @@
     return field;
   }
 
+  @Override
   public AbstractAccessContexts getReadsWithContexts() {
     return readsWithContexts;
   }
@@ -80,6 +82,11 @@
     this.readsWithContexts = readsWithContexts;
   }
 
+  @Override
+  public AbstractAccessContexts getWritesWithContexts() {
+    return writesWithContexts;
+  }
+
   public void setWritesWithContexts(AbstractAccessContexts writesWithContexts) {
     this.writesWithContexts = writesWithContexts;
   }
@@ -102,6 +109,11 @@
   }
 
   @Override
+  public boolean hasKnownReadContexts() {
+    return !readsWithContexts.isTop();
+  }
+
+  @Override
   public boolean hasKnownWriteContexts() {
     return !writesWithContexts.isTop();
   }
@@ -169,6 +181,12 @@
   }
 
   @Override
+  public void forEachAccessContext(Consumer<ProgramMethod> consumer) {
+    forEachReadContext(consumer);
+    forEachWriteContext(consumer);
+  }
+
+  @Override
   public void forEachReadContext(Consumer<ProgramMethod> consumer) {
     readsWithContexts.forEachAccessContext(consumer);
   }
@@ -180,17 +198,39 @@
 
   @Override
   public boolean hasReflectiveAccess() {
-    return (flags & FLAG_HAS_REFLECTIVE_ACCESS) != 0;
+    return hasReflectiveRead() || hasReflectiveWrite();
   }
 
-  public void setHasReflectiveAccess() {
-    flags |= FLAG_HAS_REFLECTIVE_ACCESS;
+  @Override
+  public boolean hasReflectiveRead() {
+    return (flags & FLAG_HAS_REFLECTIVE_READ) != 0;
+  }
+
+  public void setHasReflectiveRead() {
+    flags |= FLAG_HAS_REFLECTIVE_READ;
+  }
+
+  @Override
+  public boolean hasReflectiveWrite() {
+    return (flags & FLAG_HAS_REFLECTIVE_WRITE) != 0;
+  }
+
+  public void setHasReflectiveWrite() {
+    flags |= FLAG_HAS_REFLECTIVE_WRITE;
   }
 
   /** Returns true if this field is read by the program. */
   @Override
   public boolean isRead() {
-    return !readsWithContexts.isEmpty()
+    return isReadDirectly() || isReadIndirectly();
+  }
+
+  private boolean isReadDirectly() {
+    return !readsWithContexts.isEmpty();
+  }
+
+  private boolean isReadIndirectly() {
+    return hasReflectiveRead()
         || isReadFromAnnotation()
         || isReadFromMethodHandle()
         || isReadFromRecordInvokeDynamic();
@@ -210,15 +250,15 @@
     return (flags & FLAG_IS_READ_FROM_METHOD_HANDLE) != 0;
   }
 
+  public void setReadFromMethodHandle() {
+    flags |= FLAG_IS_READ_FROM_METHOD_HANDLE;
+  }
+
   @Override
   public boolean isReadFromRecordInvokeDynamic() {
     return (flags & FLAG_IS_READ_FROM_RECORD_INVOKE_DYNAMIC) != 0;
   }
 
-  public void setReadFromMethodHandle() {
-    flags |= FLAG_IS_READ_FROM_METHOD_HANDLE;
-  }
-
   public void setReadFromRecordInvokeDynamic() {
     flags |= FLAG_IS_READ_FROM_RECORD_INVOKE_DYNAMIC;
   }
@@ -227,12 +267,28 @@
     flags &= ~FLAG_IS_READ_FROM_RECORD_INVOKE_DYNAMIC;
   }
 
+  /**
+   * Returns true if this field is only read by methods for which {@param predicate} returns true.
+   */
+  @Override
+  public boolean isReadOnlyInMethodSatisfying(Predicate<ProgramMethod> predicate) {
+    return readsWithContexts.isAccessedOnlyInMethodSatisfying(predicate) && !isReadIndirectly();
+  }
+
   /** Returns true if this field is written by the program. */
   @Override
   public boolean isWritten() {
+    return isWrittenDirectly() || isWrittenIndirectly();
+  }
+
+  private boolean isWrittenDirectly() {
     return !writesWithContexts.isEmpty();
   }
 
+  private boolean isWrittenIndirectly() {
+    return hasReflectiveWrite() || isWrittenFromMethodHandle();
+  }
+
   @Override
   public boolean isWrittenFromMethodHandle() {
     return (flags & FLAG_IS_WRITTEN_FROM_METHOD_HANDLE) != 0;
@@ -256,15 +312,7 @@
    */
   @Override
   public boolean isWrittenOnlyInMethodSatisfying(Predicate<ProgramMethod> predicate) {
-    return writesWithContexts.isAccessedOnlyInMethodSatisfying(predicate);
-  }
-
-  /**
-   * Returns true if this field is only read by methods for which {@param predicate} returns true.
-   */
-  @Override
-  public boolean isReadOnlyInMethodSatisfying(Predicate<ProgramMethod> predicate) {
-    return readsWithContexts.isAccessedOnlyInMethodSatisfying(predicate);
+    return writesWithContexts.isAccessedOnlyInMethodSatisfying(predicate) && !isWrittenIndirectly();
   }
 
   /**
@@ -272,7 +320,7 @@
    */
   @Override
   public boolean isWrittenOutside(DexEncodedMethod method) {
-    return writesWithContexts.isAccessedOutside(method);
+    return writesWithContexts.isAccessedOutside(method) || isWrittenIndirectly();
   }
 
   public boolean recordRead(DexField access, ProgramMethod context) {
diff --git a/src/main/java/com/android/tools/r8/graph/InvalidCode.java b/src/main/java/com/android/tools/r8/graph/InvalidCode.java
index 5363897..2b9de26 100644
--- a/src/main/java/com/android/tools/r8/graph/InvalidCode.java
+++ b/src/main/java/com/android/tools/r8/graph/InvalidCode.java
@@ -5,6 +5,7 @@
 
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.ir.code.IRCode;
+import com.android.tools.r8.ir.conversion.MethodConversionOptions.MutableMethodConversionOptions;
 import com.android.tools.r8.naming.ClassNameMapper;
 import com.android.tools.r8.origin.Origin;
 
@@ -23,7 +24,11 @@
   private InvalidCode() {}
 
   @Override
-  public IRCode buildIR(ProgramMethod method, AppView<?> appView, Origin origin) {
+  public IRCode buildIR(
+      ProgramMethod method,
+      AppView<?> appView,
+      Origin origin,
+      MutableMethodConversionOptions conversionOptions) {
     throw new Unreachable();
   }
 
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 afbda8d..8e5ec1f 100644
--- a/src/main/java/com/android/tools/r8/graph/JarClassFileReader.java
+++ b/src/main/java/com/android/tools/r8/graph/JarClassFileReader.java
@@ -106,6 +106,12 @@
       }
     }
 
+    if (classKind == ClassKind.PROGRAM
+        && application.options.isDesugaring()
+        && application.options.desugarGraphConsumer != null) {
+      application.options.desugarGraphConsumer.acceptProgramNode(origin);
+    }
+
     ClassReader reader = new ClassReader(bytes);
 
     int parsingOptions = SKIP_FRAMES | SKIP_CODE;
diff --git a/src/main/java/com/android/tools/r8/graph/LazyCfCode.java b/src/main/java/com/android/tools/r8/graph/LazyCfCode.java
index b2efb1b..c32d6cb 100644
--- a/src/main/java/com/android/tools/r8/graph/LazyCfCode.java
+++ b/src/main/java/com/android/tools/r8/graph/LazyCfCode.java
@@ -60,6 +60,7 @@
 import com.android.tools.r8.ir.code.Position;
 import com.android.tools.r8.ir.code.Position.SourcePosition;
 import com.android.tools.r8.ir.code.ValueType;
+import com.android.tools.r8.ir.conversion.MethodConversionOptions.MutableMethodConversionOptions;
 import com.android.tools.r8.naming.ClassNameMapper;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.position.MethodPosition;
@@ -67,6 +68,7 @@
 import com.android.tools.r8.position.TextRange;
 import com.android.tools.r8.shaking.ProguardConfiguration;
 import com.android.tools.r8.shaking.ProguardKeepAttributes;
+import com.android.tools.r8.utils.DescriptorUtils;
 import com.android.tools.r8.utils.ExceptionUtils;
 import com.android.tools.r8.utils.InternalOptions;
 import it.unimi.dsi.fastutil.ints.Int2ReferenceAVLTreeMap;
@@ -240,8 +242,12 @@
   }
 
   @Override
-  public IRCode buildIR(ProgramMethod method, AppView<?> appView, Origin origin) {
-    return asCfCode().buildIR(method, appView, origin);
+  public IRCode buildIR(
+      ProgramMethod method,
+      AppView<?> appView,
+      Origin origin,
+      MutableMethodConversionOptions conversionOptions) {
+    return asCfCode().buildIR(method, appView, origin, conversionOptions);
   }
 
   @Override
@@ -959,7 +965,9 @@
 
     @Override
     public void visitMultiANewArrayInsn(String desc, int dims) {
-      if (!application.options.isGeneratingDex()) {
+      InternalOptions options = application.options;
+      if (options.isGeneratingClassFiles()
+          && !options.testing.enableMultiANewArrayDesugaringForClassFiles) {
         instructions.add(new CfMultiANewArray(factory.createType(desc), dims));
         return;
       }
@@ -987,7 +995,18 @@
         visitInsn(Opcodes.IASTORE);
         // ..., count1, ..., dim-array
       }
-      visitLdcInsn(Type.getType(desc.substring(dims)));
+      String baseDesc = desc.substring(dims);
+      if (DescriptorUtils.isPrimitiveDescriptor(baseDesc)) {
+        visitFieldInsn(
+            Opcodes.GETSTATIC,
+            DescriptorUtils.primitiveDescriptorToBoxedInternalName(baseDesc.charAt(0)),
+            "TYPE",
+            "Ljava/lang/Class;");
+      } else if (DescriptorUtils.isVoidDescriptor(baseDesc)) {
+        visitFieldInsn(Opcodes.GETSTATIC, "java/lang/Void", "TYPE", "Ljava/lang/Class;");
+      } else {
+        visitLdcInsn(Type.getType(baseDesc));
+      }
       // ..., dim-array, dim-member-type
       visitInsn(Opcodes.SWAP);
       // ..., dim-member-type, dim-array
diff --git a/src/main/java/com/android/tools/r8/graph/MethodResolution.java b/src/main/java/com/android/tools/r8/graph/MethodResolution.java
new file mode 100644
index 0000000..2f6939e
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/graph/MethodResolution.java
@@ -0,0 +1,418 @@
+// Copyright (c) 2022, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.graph;
+
+import com.android.tools.r8.graph.MethodResolutionResult.ArrayCloneMethodResult;
+import com.android.tools.r8.graph.MethodResolutionResult.ClassNotFoundResult;
+import com.android.tools.r8.graph.MethodResolutionResult.IllegalAccessOrNoSuchMethodResult;
+import com.android.tools.r8.graph.MethodResolutionResult.IncompatibleClassResult;
+import com.android.tools.r8.graph.MethodResolutionResult.NoSuchMethodResult;
+import com.android.tools.r8.graph.MethodResolutionResult.SingleResolutionResult;
+import com.android.tools.r8.ir.desugar.LambdaDescriptor;
+import com.android.tools.r8.utils.ListUtils;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map.Entry;
+import java.util.function.Function;
+
+/**
+ * Implements resolution of a method descriptor against a type.
+ *
+ * <p>See <a href="https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-5.html#jvms-5.4.3.3">
+ * Section 5.4.3.3 of the JVM Spec</a>.
+ */
+public class MethodResolution {
+
+  private final Function<DexType, DexClass> definitionFor;
+  private final DexItemFactory factory;
+
+  public MethodResolution(Function<DexType, DexClass> definitionFor, DexItemFactory factory) {
+    this.definitionFor = definitionFor;
+    this.factory = factory;
+  }
+
+  private DexClass definitionFor(DexType type) {
+    return definitionFor.apply(type);
+  }
+
+  /**
+   * This method will query the definition of the holder to decide on which resolution to use. If
+   * the holder is an interface, it delegates to {@link #resolveMethodOnInterface(DexClass,
+   * DexProto, DexString)}, otherwise {@link #resolveMethodOnClass(DexClass, DexProto, DexString)}
+   * is used.
+   *
+   * <p>This is to overcome the shortcoming of the DEX file format that does not allow to encode the
+   * kind of a method reference.
+   */
+  public MethodResolutionResult unsafeResolveMethodDueToDexFormat(DexMethod method) {
+    DexType holder = method.holder;
+    if (holder.isArrayType()) {
+      return resolveMethodOnArray(holder, method.getProto(), method.getName());
+    }
+    DexClass definition = definitionFor(holder);
+    if (definition == null) {
+      return ClassNotFoundResult.INSTANCE;
+    } else if (definition.isInterface()) {
+      return resolveMethodOnInterface(definition, method.getProto(), method.getName());
+    } else {
+      return resolveMethodOnClass(definition, method.getProto(), method.getName());
+    }
+  }
+
+  /**
+   * Implements resolution of a method descriptor against an array type.
+   *
+   * <p>See <a href="https://docs.oracle.com/javase/specs/jls/se8/html/jls-10.html#jls-10.7">Section
+   * 10.7 of the Java Language Specification</a>. All invokations will have target java.lang.Object
+   * except clone which has no target.
+   */
+  private MethodResolutionResult resolveMethodOnArray(
+      DexType holder, DexProto methodProto, DexString methodName) {
+    assert holder.isArrayType();
+    if (methodName == factory.cloneMethodName) {
+      return ArrayCloneMethodResult.INSTANCE;
+    } else {
+      return resolveMethodOnClass(factory.objectType, methodProto, methodName);
+    }
+  }
+
+  /**
+   * Implements resolution of a method descriptor against a class type.
+   *
+   * <p>See <a href="https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-5.html#jvms-5.4.3.3">
+   * Section 5.4.3.3 of the JVM Spec</a>.
+   *
+   * <p>The resolved method is not the method that will actually be invoked. Which methods gets
+   * invoked depends on the invoke instruction used. However, it is always safe to rewrite any
+   * invoke on the given descriptor to a corresponding invoke on the resolved descriptor, as the
+   * resolved method is used as basis for dispatch.
+   */
+  public MethodResolutionResult resolveMethodOnClass(
+      DexType holder, DexProto methodProto, DexString methodName) {
+    if (holder.isArrayType()) {
+      return resolveMethodOnArray(holder, methodProto, methodName);
+    }
+    DexClass clazz = definitionFor(holder);
+    if (clazz == null) {
+      return ClassNotFoundResult.INSTANCE;
+    }
+    // Step 1: If holder is an interface, resolution fails with an ICCE. We return null.
+    if (clazz.isInterface()) {
+      return IncompatibleClassResult.INSTANCE;
+    }
+    return resolveMethodOnClass(clazz, methodProto, methodName);
+  }
+
+  public MethodResolutionResult resolveMethodOnClass(
+      DexClass clazz, DexProto methodProto, DexString methodName) {
+    assert !clazz.isInterface();
+    // Step 2:
+    MethodResolutionResult result =
+        resolveMethodOnClassStep2(clazz, methodProto, methodName, clazz);
+    if (result != null) {
+      return result;
+    }
+    // Finally Step 3:
+    return resolveMethodStep3(clazz, methodProto, methodName);
+  }
+
+  /**
+   * Implements step 2 of method resolution on classes as per <a
+   * href="https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-5.html#jvms-5.4.3.3">Section
+   * 5.4.3.3 of the JVM Spec</a>.
+   */
+  private MethodResolutionResult resolveMethodOnClassStep2(
+      DexClass clazz,
+      DexProto methodProto,
+      DexString methodName,
+      DexClass initialResolutionHolder) {
+    // Pt. 1: Signature polymorphic method check.
+    // See also <a href="https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-2.html#jvms-2.9">
+    // Section 2.9 of the JVM Spec</a>.
+    DexEncodedMethod result = clazz.lookupSignaturePolymorphicMethod(methodName, factory);
+    if (result != null) {
+      return new SingleResolutionResult(initialResolutionHolder, clazz, result);
+    }
+    // Pt 2: Find a method that matches the descriptor.
+    result = clazz.lookupMethod(methodProto, methodName);
+    if (result != null) {
+      // If the resolved method is private, then it can only be accessed if the symbolic reference
+      // that initiated the resolution was the type at which the method resolved on. If that is not
+      // the case, then the error is either an IllegalAccessError, or in the case where access is
+      // allowed because of nests, a NoSuchMethodError. Which error cannot be determined without
+      // knowing the calling context.
+      if (result.isPrivateMethod() && clazz != initialResolutionHolder) {
+        return new IllegalAccessOrNoSuchMethodResult(initialResolutionHolder, result);
+      }
+      return new SingleResolutionResult(initialResolutionHolder, clazz, result);
+    }
+    // Pt 3: Apply step two to direct superclass of holder.
+    if (clazz.superType != null) {
+      DexClass superClass = definitionFor(clazz.superType);
+      if (superClass != null) {
+        return resolveMethodOnClassStep2(
+            superClass, methodProto, methodName, initialResolutionHolder);
+      }
+    }
+    return null;
+  }
+
+  /**
+   * Implements step 3 of <a
+   * href="https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-5.html#jvms-5.4.3.3">Section
+   * 5.4.3.3 of the JVM Spec</a>. As this is the same for interfaces and classes, we share one
+   * implementation.
+   */
+  private MethodResolutionResult resolveMethodStep3(
+      DexClass clazz, DexProto methodProto, DexString methodName) {
+    MaximallySpecificMethodsBuilder builder =
+        new MaximallySpecificMethodsBuilder(definitionFor, factory);
+    resolveMethodStep3Helper(methodProto, methodName, clazz, builder);
+    return builder.resolve(clazz);
+  }
+
+  MethodResolutionResult resolveMaximallySpecificTarget(DexClass clazz, DexMethod method) {
+    return resolveMaximallySpecificTargetHelper(clazz, method).resolve(clazz);
+  }
+
+  MethodResolutionResult resolveMaximallySpecificTarget(LambdaDescriptor lambda, DexMethod method) {
+    return resolveMaximallySpecificTargetHelper(lambda, method).internalResolve(null);
+  }
+
+  // Non-private lookup (ie, not resolution) to find interface targets.
+  DexClassAndMethod lookupMaximallySpecificTarget(DexClass clazz, DexMethod method) {
+    return resolveMaximallySpecificTargetHelper(clazz, method).lookup();
+  }
+
+  private MaximallySpecificMethodsBuilder resolveMaximallySpecificTargetHelper(
+      DexClass clazz, DexMethod method) {
+    MaximallySpecificMethodsBuilder builder =
+        new MaximallySpecificMethodsBuilder(definitionFor, factory);
+    resolveMethodStep3Helper(method.getProto(), method.getName(), clazz, builder);
+    return builder;
+  }
+
+  private MaximallySpecificMethodsBuilder resolveMaximallySpecificTargetHelper(
+      LambdaDescriptor lambda, DexMethod method) {
+    MaximallySpecificMethodsBuilder builder =
+        new MaximallySpecificMethodsBuilder(definitionFor, factory);
+    resolveMethodStep3Helper(
+        method.getProto(), method.getName(), factory.objectType, lambda.interfaces, builder);
+    return builder;
+  }
+
+  /** Helper method that builds the set of maximally specific methods. */
+  private void resolveMethodStep3Helper(
+      DexProto methodProto,
+      DexString methodName,
+      DexClass clazz,
+      MaximallySpecificMethodsBuilder builder) {
+    resolveMethodStep3Helper(
+        methodProto, methodName, clazz.superType, Arrays.asList(clazz.interfaces.values), builder);
+  }
+
+  private void resolveMethodStep3Helper(
+      DexProto methodProto,
+      DexString methodName,
+      DexType superType,
+      List<DexType> interfaces,
+      MaximallySpecificMethodsBuilder builder) {
+    for (DexType iface : interfaces) {
+      DexClass definition = definitionFor(iface);
+      if (definition == null) {
+        // Ignore missing interface definitions.
+        continue;
+      }
+      assert definition.isInterface();
+      DexEncodedMethod result = definition.lookupMethod(methodProto, methodName);
+      if (isMaximallySpecificCandidate(result)) {
+        // The candidate is added and doing so will prohibit shadowed methods from being in the set.
+        builder.addCandidate(definition, result);
+      } else {
+        // Look at the super-interfaces of this class and keep searching.
+        resolveMethodStep3Helper(methodProto, methodName, definition, builder);
+      }
+    }
+    // Now look at indirect super interfaces.
+    if (superType != null) {
+      DexClass superClass = definitionFor(superType);
+      if (superClass != null) {
+        resolveMethodStep3Helper(methodProto, methodName, superClass, builder);
+      }
+    }
+  }
+
+  /**
+   * A candidate for being a maximally specific method must have neither its private, nor its static
+   * flag set. A candidate may still not be maximally specific, which entails that no subinterfaces
+   * from also contribute with a candidate to the type. That is not determined by this method.
+   */
+  private boolean isMaximallySpecificCandidate(DexEncodedMethod method) {
+    return method != null && !method.accessFlags.isPrivate() && !method.accessFlags.isStatic();
+  }
+
+  /**
+   * Implements resolution of a method descriptor against an interface type.
+   *
+   * <p>See <a href="https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-5.html#jvms-5.4.3.3">
+   * Section 5.4.3.4 of the JVM Spec</a>.
+   *
+   * <p>The resolved method is not the method that will actually be invoked. Which methods gets
+   * invoked depends on the invoke instruction used. However, it is always save to rewrite any
+   * invoke on the given descriptor to a corresponding invoke on the resolved descriptor, as the
+   * resolved method is used as basis for dispatch.
+   */
+  public MethodResolutionResult resolveMethodOnInterface(
+      DexType holder, DexProto proto, DexString methodName) {
+    if (holder.isArrayType()) {
+      return IncompatibleClassResult.INSTANCE;
+    }
+    // Step 1: Lookup interface.
+    DexClass definition = definitionFor(holder);
+    // If the definition is not an interface, resolution fails with an ICCE. We just return the
+    // empty result here.
+    if (definition == null) {
+      return ClassNotFoundResult.INSTANCE;
+    }
+    if (!definition.isInterface()) {
+      return IncompatibleClassResult.INSTANCE;
+    }
+    return resolveMethodOnInterface(definition, proto, methodName);
+  }
+
+  public MethodResolutionResult resolveMethodOnInterface(
+      DexClass definition, DexProto methodProto, DexString methodName) {
+    assert definition.isInterface();
+    // Step 2: Look for exact method on interface.
+    DexEncodedMethod result = definition.lookupMethod(methodProto, methodName);
+    if (result != null) {
+      return new SingleResolutionResult(definition, definition, result);
+    }
+    // Step 3: Look for matching method on object class.
+    DexClass objectClass = definitionFor(factory.objectType);
+    if (objectClass == null) {
+      return ClassNotFoundResult.INSTANCE;
+    }
+    result = objectClass.lookupMethod(methodProto, methodName);
+    if (result != null && result.accessFlags.isPublic() && !result.accessFlags.isAbstract()) {
+      return new SingleResolutionResult(definition, objectClass, result);
+    }
+    // Step 3: Look for maximally-specific superinterface methods or any interface definition.
+    //         This is the same for classes and interfaces.
+    return resolveMethodStep3(definition, methodProto, methodName);
+  }
+
+  static class MaximallySpecificMethodsBuilder {
+
+    // The set of actual maximally specific methods.
+    // This set is linked map so that in the case where a number of methods remain a deterministic
+    // choice can be made. The map is from definition classes to their maximally specific method, or
+    // in the case that a type has a candidate which is shadowed by a subinterface, the map will
+    // map the class to a null entry, thus any addition to the map must check for key containment
+    // prior to writing.
+    private final LinkedHashMap<DexClass, DexEncodedMethod> maximallySpecificMethods =
+        new LinkedHashMap<>();
+    private final Function<DexType, DexClass> definitionFor;
+    private final DexItemFactory factory;
+
+    private MaximallySpecificMethodsBuilder(
+        Function<DexType, DexClass> definitionFor, DexItemFactory factory) {
+      this.definitionFor = definitionFor;
+      this.factory = factory;
+    }
+
+    void addCandidate(DexClass holder, DexEncodedMethod method) {
+      // If this candidate is already a candidate or it is shadowed, then no need to continue.
+      if (maximallySpecificMethods.containsKey(holder)) {
+        return;
+      }
+      maximallySpecificMethods.put(holder, method);
+      // Prune exiting candidates and prohibit future candidates in the super hierarchy.
+      assert holder.isInterface();
+      assert holder.superType == factory.objectType;
+      for (DexType iface : holder.interfaces.values) {
+        markShadowed(iface);
+      }
+    }
+
+    private void markShadowed(DexType type) {
+      if (type == null) {
+        return;
+      }
+      DexClass clazz = definitionFor.apply(type);
+      if (clazz == null) {
+        return;
+      }
+      assert clazz.isInterface();
+      assert clazz.superType == factory.objectType;
+      // A null entry signifies that the candidate is shadowed blocking future candidates.
+      // If the candidate is already shadowed at this type there is no need to shadow further up.
+      if (maximallySpecificMethods.containsKey(clazz)
+          && maximallySpecificMethods.get(clazz) == null) {
+        return;
+      }
+      maximallySpecificMethods.put(clazz, null);
+      for (DexType iface : clazz.interfaces.values) {
+        markShadowed(iface);
+      }
+    }
+
+    DexClassAndMethod lookup() {
+      return internalResolve(null).getResolutionPair();
+    }
+
+    MethodResolutionResult resolve(DexClass initialResolutionHolder) {
+      assert initialResolutionHolder != null;
+      return internalResolve(initialResolutionHolder);
+    }
+
+    private MethodResolutionResult internalResolve(DexClass initialResolutionHolder) {
+      if (maximallySpecificMethods.isEmpty()) {
+        return NoSuchMethodResult.INSTANCE;
+      }
+      // Fast path in the common case of a single method.
+      if (maximallySpecificMethods.size() == 1) {
+        return singleResultHelper(
+            initialResolutionHolder, maximallySpecificMethods.entrySet().iterator().next());
+      }
+      Entry<DexClass, DexEncodedMethod> firstMaximallySpecificMethod = null;
+      List<Entry<DexClass, DexEncodedMethod>> nonAbstractMethods =
+          new ArrayList<>(maximallySpecificMethods.size());
+      for (Entry<DexClass, DexEncodedMethod> entry : maximallySpecificMethods.entrySet()) {
+        DexEncodedMethod method = entry.getValue();
+        if (method == null) {
+          // Ignore shadowed candidates.
+          continue;
+        }
+        if (firstMaximallySpecificMethod == null) {
+          firstMaximallySpecificMethod = entry;
+        }
+        if (method.isNonAbstractVirtualMethod()) {
+          nonAbstractMethods.add(entry);
+        }
+      }
+      // If there are no non-abstract methods, then any candidate will suffice as a target.
+      // For deterministic resolution, we return the first mapped method (of the linked map).
+      if (nonAbstractMethods.isEmpty()) {
+        return singleResultHelper(initialResolutionHolder, firstMaximallySpecificMethod);
+      }
+      // If there is exactly one non-abstract method (a default method) it is the resolution target.
+      if (nonAbstractMethods.size() == 1) {
+        return singleResultHelper(initialResolutionHolder, nonAbstractMethods.get(0));
+      }
+      return IncompatibleClassResult.create(ListUtils.map(nonAbstractMethods, Entry::getValue));
+    }
+
+    private static SingleResolutionResult singleResultHelper(
+        DexClass initialResolutionResult, Entry<DexClass, DexEncodedMethod> entry) {
+      return new SingleResolutionResult(
+          initialResolutionResult != null ? initialResolutionResult : entry.getKey(),
+          entry.getKey(),
+          entry.getValue());
+    }
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/graph/ObjectAllocationInfoCollection.java b/src/main/java/com/android/tools/r8/graph/ObjectAllocationInfoCollection.java
index 745c558..ef1de89 100644
--- a/src/main/java/com/android/tools/r8/graph/ObjectAllocationInfoCollection.java
+++ b/src/main/java/com/android/tools/r8/graph/ObjectAllocationInfoCollection.java
@@ -4,9 +4,12 @@
 
 package com.android.tools.r8.graph;
 
+import com.android.tools.r8.ir.desugar.LambdaDescriptor;
+import com.android.tools.r8.utils.TraversalContinuation;
 import java.util.Set;
 import java.util.function.BiConsumer;
 import java.util.function.Consumer;
+import java.util.function.Function;
 
 /**
  * Provides immutable access to {@link ObjectAllocationInfoCollectionImpl}, which stores the set of
@@ -31,6 +34,12 @@
 
   void forEachInstantiatedLambdaInterfaces(Consumer<DexType> consumer);
 
+  TraversalContinuation<?> traverseInstantiatedSubtypes(
+      DexType type,
+      Function<DexProgramClass, TraversalContinuation<?>> onClass,
+      Function<LambdaDescriptor, TraversalContinuation<?>> onLambda,
+      AppInfo appInfo);
+
   ObjectAllocationInfoCollection rewrittenWithLens(
       DexDefinitionSupplier definitions, GraphLens lens);
 }
diff --git a/src/main/java/com/android/tools/r8/graph/ObjectAllocationInfoCollectionImpl.java b/src/main/java/com/android/tools/r8/graph/ObjectAllocationInfoCollectionImpl.java
index f1a3bda..6b31147 100644
--- a/src/main/java/com/android/tools/r8/graph/ObjectAllocationInfoCollectionImpl.java
+++ b/src/main/java/com/android/tools/r8/graph/ObjectAllocationInfoCollectionImpl.java
@@ -183,6 +183,7 @@
         appInfo);
   }
 
+  @Override
   public TraversalContinuation<?> traverseInstantiatedSubtypes(
       DexType type,
       Function<DexProgramClass, TraversalContinuation<?>> onClass,
diff --git a/src/main/java/com/android/tools/r8/graph/ProgramMethod.java b/src/main/java/com/android/tools/r8/graph/ProgramMethod.java
index 3cb1a4c..37d165b 100644
--- a/src/main/java/com/android/tools/r8/graph/ProgramMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/ProgramMethod.java
@@ -11,12 +11,14 @@
 import com.android.tools.r8.ir.code.NumberGenerator;
 import com.android.tools.r8.ir.code.Position;
 import com.android.tools.r8.ir.conversion.LensCodeRewriterUtils;
+import com.android.tools.r8.ir.conversion.MethodConversionOptions.MutableMethodConversionOptions;
 import com.android.tools.r8.ir.conversion.MethodProcessor;
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
 import com.android.tools.r8.kotlin.KotlinMethodLevelInfo;
 import com.android.tools.r8.logging.Log;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import it.unimi.dsi.fastutil.ints.Int2ReferenceMap;
 
 /** Type representing a method definition in the programs compilation unit and its holder. */
 public final class ProgramMethod extends DexClassAndMethod
@@ -27,8 +29,14 @@
   }
 
   public IRCode buildIR(AppView<?> appView) {
+    return buildIR(appView, new MutableMethodConversionOptions(appView.options()));
+  }
+
+  public IRCode buildIR(AppView<?> appView, MutableMethodConversionOptions conversionOptions) {
     DexEncodedMethod method = getDefinition();
-    return method.hasCode() ? method.getCode().buildIR(this, appView, getOrigin()) : null;
+    return method.hasCode()
+        ? method.getCode().buildIR(this, appView, getOrigin(), conversionOptions)
+        : null;
   }
 
   public IRCode buildInliningIR(
@@ -105,7 +113,7 @@
     MethodAccessFlags accessFlags = getAccessFlags();
     accessFlags.demoteFromAbstract();
     getDefinition().setApiLevelForCode(appView.computedMinApiLevel());
-    getDefinition().setCode(ThrowNullCode.get(), appView);
+    setCode(ThrowNullCode.get(), appView);
     getSimpleFeedback().markProcessed(getDefinition(), ConstraintWithTarget.ALWAYS);
     getSimpleFeedback().unsetOptimizationInfoForThrowNullMethod(this);
   }
@@ -171,4 +179,29 @@
   public KotlinMethodLevelInfo getKotlinInfo() {
     return getDefinition().getKotlinInfo();
   }
+
+  public boolean getOrComputeReachabilitySensitive(AppView<?> appView) {
+    return getHolder().getOrComputeReachabilitySensitive(appView);
+  }
+
+  public void setCode(Code newCode, AppView<?> appView) {
+    // If the locals are not kept, we might still need information to satisfy -keepparameternames.
+    // The information needs to be retrieved on the original code object before replacing it.
+    Code code = getDefinition().getCode();
+    Int2ReferenceMap<DebugLocalInfo> parameterInfo = getDefinition().getParameterInfo();
+    if (code != null
+        && code.isCfCode()
+        && !getDefinition().hasParameterInfo()
+        && !keepLocals(appView)) {
+      parameterInfo = code.collectParameterInfo(getDefinition(), appView);
+    }
+    getDefinition().setCode(newCode, parameterInfo);
+  }
+
+  public boolean keepLocals(AppView<?> appView) {
+    if (appView.testing().noLocalsTableOnInput) {
+      return false;
+    }
+    return appView.options().debug || getOrComputeReachabilitySensitive(appView);
+  }
 }
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 3420b1b..55e6f8c 100644
--- a/src/main/java/com/android/tools/r8/graph/ThrowExceptionCode.java
+++ b/src/main/java/com/android/tools/r8/graph/ThrowExceptionCode.java
@@ -18,6 +18,7 @@
 import com.android.tools.r8.ir.code.NumberGenerator;
 import com.android.tools.r8.ir.code.Position;
 import com.android.tools.r8.ir.conversion.LensCodeRewriterUtils;
+import com.android.tools.r8.ir.conversion.MethodConversionOptions.MutableMethodConversionOptions;
 import com.android.tools.r8.naming.ClassNameMapper;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.utils.structural.HashingVisitor;
@@ -48,7 +49,11 @@
   }
 
   @Override
-  public IRCode buildIR(ProgramMethod method, AppView<?> appView, Origin origin) {
+  public IRCode buildIR(
+      ProgramMethod method,
+      AppView<?> appView,
+      Origin origin,
+      MutableMethodConversionOptions conversionOptions) {
     throw new Unreachable("Should not be called");
   }
 
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 03b811f..5b02c12 100644
--- a/src/main/java/com/android/tools/r8/graph/ThrowNullCode.java
+++ b/src/main/java/com/android/tools/r8/graph/ThrowNullCode.java
@@ -19,6 +19,8 @@
 import com.android.tools.r8.ir.code.Position.SyntheticPosition;
 import com.android.tools.r8.ir.conversion.IRBuilder;
 import com.android.tools.r8.ir.conversion.LensCodeRewriterUtils;
+import com.android.tools.r8.ir.conversion.MethodConversionOptions.MutableMethodConversionOptions;
+import com.android.tools.r8.ir.conversion.MethodConversionOptions.ThrowingMethodConversionOptions;
 import com.android.tools.r8.ir.conversion.SyntheticStraightLineSourceCode;
 import com.android.tools.r8.naming.ClassNameMapper;
 import com.android.tools.r8.naming.NamingLens;
@@ -52,9 +54,13 @@
   }
 
   @Override
-  public IRCode buildIR(ProgramMethod method, AppView<?> appView, Origin origin) {
+  public IRCode buildIR(
+      ProgramMethod method,
+      AppView<?> appView,
+      Origin origin,
+      MutableMethodConversionOptions conversionOptions) {
     ThrowNullSourceCode source = new ThrowNullSourceCode(method);
-    return IRBuilder.create(method, appView, source, origin).build(method);
+    return IRBuilder.create(method, appView, source, origin).build(method, conversionOptions);
   }
 
   @Override
@@ -70,7 +76,7 @@
     ThrowNullSourceCode source = new ThrowNullSourceCode(method, callerPosition);
     return IRBuilder.createForInlining(
             method, appView, codeLens, source, origin, valueNumberGenerator, protoChanges)
-        .build(context);
+        .build(context, new ThrowingMethodConversionOptions(appView.options()));
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/ClassInstanceFieldsMerger.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/ClassInstanceFieldsMerger.java
index 682da64..464db91 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/ClassInstanceFieldsMerger.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/ClassInstanceFieldsMerger.java
@@ -23,21 +23,31 @@
 import java.util.Set;
 import java.util.function.BiConsumer;
 
-public class ClassInstanceFieldsMerger {
+public interface ClassInstanceFieldsMerger {
 
-  private final AppView<? extends AppInfoWithClassHierarchy> appView;
-  private final MergeGroup group;
-  private final Builder lensBuilder;
+  void setClassIdField(DexEncodedField classIdField);
 
-  private DexEncodedField classIdField;
+  DexEncodedField[] merge();
 
-  public ClassInstanceFieldsMerger(
-      AppView<? extends AppInfoWithClassHierarchy> appView,
-      HorizontalClassMergerGraphLens.Builder lensBuilder,
-      MergeGroup group) {
-    this.appView = appView;
-    this.group = group;
-    this.lensBuilder = lensBuilder;
+  static ClassInstanceFieldsMerger create(
+      AppView<?> appView, HorizontalClassMergerGraphLens.Builder lensBuilder, MergeGroup group) {
+    if (appView.hasClassHierarchy()) {
+      return new ClassInstanceFieldsMergerImpl(appView.withClassHierarchy(), lensBuilder, group);
+    } else {
+      assert group.getInstanceFieldMap().isEmpty();
+      appView.options().horizontalClassMergerOptions().isRestrictedToSynthetics();
+      return new ClassInstanceFieldsMerger() {
+        @Override
+        public void setClassIdField(DexEncodedField classIdField) {
+          throw new UnsupportedOperationException("No instance field merging in D8");
+        }
+
+        @Override
+        public DexEncodedField[] merge() {
+          return DexEncodedField.EMPTY_ARRAY;
+        }
+      };
+    }
   }
 
   /**
@@ -50,7 +60,7 @@
    * Bar has fields 'A b' and 'B a'), we make a prepass that matches fields with the same reference
    * type.
    */
-  public static void mapFields(
+  static void mapFields(
       AppView<? extends AppInfoWithClassHierarchy> appView,
       DexProgramClass source,
       DexProgramClass target,
@@ -90,7 +100,7 @@
     }
   }
 
-  private static Map<InstanceFieldInfo, LinkedList<DexEncodedField>> getAvailableFieldsByExactInfo(
+  static Map<InstanceFieldInfo, LinkedList<DexEncodedField>> getAvailableFieldsByExactInfo(
       DexProgramClass target) {
     Map<InstanceFieldInfo, LinkedList<DexEncodedField>> availableFieldsByInfo =
         new LinkedHashMap<>();
@@ -102,10 +112,9 @@
     return availableFieldsByInfo;
   }
 
-  private static Map<InstanceFieldInfo, LinkedList<DexEncodedField>>
-      getAvailableFieldsByRelaxedInfo(
-          AppView<? extends AppInfoWithClassHierarchy> appView,
-          Map<InstanceFieldInfo, LinkedList<DexEncodedField>> availableFieldsByExactInfo) {
+  static Map<InstanceFieldInfo, LinkedList<DexEncodedField>> getAvailableFieldsByRelaxedInfo(
+      AppView<? extends AppInfoWithClassHierarchy> appView,
+      Map<InstanceFieldInfo, LinkedList<DexEncodedField>> availableFieldsByExactInfo) {
     Map<InstanceFieldInfo, LinkedList<DexEncodedField>> availableFieldsByRelaxedInfo =
         new LinkedHashMap<>();
     availableFieldsByExactInfo.forEach(
@@ -118,64 +127,85 @@
     return availableFieldsByRelaxedInfo;
   }
 
-  private void fixAccessFlags(DexEncodedField newField, Collection<DexEncodedField> oldFields) {
-    if (newField.isSynthetic() && Iterables.any(oldFields, oldField -> !oldField.isSynthetic())) {
-      newField.getAccessFlags().demoteFromSynthetic();
-    }
-    if (newField.isFinal() && Iterables.any(oldFields, oldField -> !oldField.isFinal())) {
-      newField.getAccessFlags().demoteFromFinal();
-    }
-  }
+  class ClassInstanceFieldsMergerImpl implements ClassInstanceFieldsMerger {
 
-  public void setClassIdField(DexEncodedField classIdField) {
-    this.classIdField = classIdField;
-  }
+    private final AppView<? extends AppInfoWithClassHierarchy> appView;
+    private final MergeGroup group;
+    private final Builder lensBuilder;
 
-  public DexEncodedField[] merge() {
-    assert group.hasInstanceFieldMap();
-    List<DexEncodedField> newFields = new ArrayList<>();
-    if (classIdField != null) {
-      newFields.add(classIdField);
-    }
-    group
-        .getInstanceFieldMap()
-        .forEachManyToOneMapping(
-            (sourceFields, targetField) ->
-                newFields.add(mergeSourceFieldsToTargetField(targetField, sourceFields)));
-    return newFields.toArray(DexEncodedField.EMPTY_ARRAY);
-  }
+    private DexEncodedField classIdField;
 
-  private DexEncodedField mergeSourceFieldsToTargetField(
-      DexEncodedField targetField, Set<DexEncodedField> sourceFields) {
-    fixAccessFlags(targetField, sourceFields);
-
-    DexEncodedField newField;
-    if (needsRelaxedType(targetField, sourceFields)) {
-      DexType newFieldType =
-          DexTypeUtils.computeLeastUpperBound(
-              appView,
-              Iterables.transform(
-                  Iterables.concat(IterableUtils.singleton(targetField), sourceFields),
-                  DexEncodedField::getType));
-      newField =
-          targetField.toTypeSubstitutedField(
-              appView, targetField.getReference().withType(newFieldType, appView.dexItemFactory()));
-    } else {
-      newField = targetField;
+    private ClassInstanceFieldsMergerImpl(
+        AppView<? extends AppInfoWithClassHierarchy> appView,
+        HorizontalClassMergerGraphLens.Builder lensBuilder,
+        MergeGroup group) {
+      this.appView = appView;
+      this.group = group;
+      this.lensBuilder = lensBuilder;
     }
 
-    lensBuilder.recordNewFieldSignature(
-        Iterables.transform(
-            IterableUtils.append(sourceFields, targetField), DexEncodedField::getReference),
-        newField.getReference(),
-        targetField.getReference());
+    @Override
+    public void setClassIdField(DexEncodedField classIdField) {
+      this.classIdField = classIdField;
+    }
 
-    return newField;
-  }
+    @Override
+    public DexEncodedField[] merge() {
+      assert group.hasInstanceFieldMap();
+      List<DexEncodedField> newFields = new ArrayList<>();
+      if (classIdField != null) {
+        newFields.add(classIdField);
+      }
+      group
+          .getInstanceFieldMap()
+          .forEachManyToOneMapping(
+              (sourceFields, targetField) ->
+                  newFields.add(mergeSourceFieldsToTargetField(targetField, sourceFields)));
+      return newFields.toArray(DexEncodedField.EMPTY_ARRAY);
+    }
 
-  private boolean needsRelaxedType(
-      DexEncodedField targetField, Iterable<DexEncodedField> sourceFields) {
-    return Iterables.any(
-        sourceFields, sourceField -> sourceField.getType() != targetField.getType());
+    private DexEncodedField mergeSourceFieldsToTargetField(
+        DexEncodedField targetField, Set<DexEncodedField> sourceFields) {
+      fixAccessFlags(targetField, sourceFields);
+
+      DexEncodedField newField;
+      if (needsRelaxedType(targetField, sourceFields)) {
+        DexType newFieldType =
+            DexTypeUtils.computeLeastUpperBound(
+                appView,
+                Iterables.transform(
+                    Iterables.concat(IterableUtils.singleton(targetField), sourceFields),
+                    DexEncodedField::getType));
+        newField =
+            targetField.toTypeSubstitutedField(
+                appView,
+                targetField.getReference().withType(newFieldType, appView.dexItemFactory()));
+      } else {
+        newField = targetField;
+      }
+
+      lensBuilder.recordNewFieldSignature(
+          Iterables.transform(
+              IterableUtils.append(sourceFields, targetField), DexEncodedField::getReference),
+          newField.getReference(),
+          targetField.getReference());
+
+      return newField;
+    }
+
+    private void fixAccessFlags(DexEncodedField newField, Collection<DexEncodedField> oldFields) {
+      if (newField.isSynthetic() && Iterables.any(oldFields, oldField -> !oldField.isSynthetic())) {
+        newField.getAccessFlags().demoteFromSynthetic();
+      }
+      if (newField.isFinal() && Iterables.any(oldFields, oldField -> !oldField.isFinal())) {
+        newField.getAccessFlags().demoteFromFinal();
+      }
+    }
+
+    private boolean needsRelaxedType(
+        DexEncodedField targetField, Iterable<DexEncodedField> sourceFields) {
+      return Iterables.any(
+          sourceFields, sourceField -> sourceField.getType() != targetField.getType());
+    }
   }
 }
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/ClassMerger.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/ClassMerger.java
index 303efef..0751f8e 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/ClassMerger.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/ClassMerger.java
@@ -7,7 +7,6 @@
 import static com.google.common.base.Predicates.not;
 
 import com.android.tools.r8.androidapi.ComputedApiLevel;
-import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexDefinition;
@@ -37,6 +36,7 @@
 import it.unimi.dsi.fastutil.objects.Reference2IntOpenHashMap;
 import java.util.ArrayList;
 import java.util.Collection;
+import java.util.Collections;
 import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
@@ -55,7 +55,7 @@
 
   private static final OptimizationFeedback feedback = OptimizationFeedbackSimple.getInstance();
 
-  private final AppView<? extends AppInfoWithClassHierarchy> appView;
+  private final AppView<?> appView;
   private final Mode mode;
   private final MergeGroup group;
   private final DexItemFactory dexItemFactory;
@@ -74,7 +74,7 @@
   private final Collection<VirtualMethodMerger> virtualMethodMergers;
 
   private ClassMerger(
-      AppView<? extends AppInfoWithClassHierarchy> appView,
+      AppView<?> appView,
       IRCodeProvider codeProvider,
       Mode mode,
       HorizontalClassMergerGraphLens.Builder lensBuilder,
@@ -88,7 +88,7 @@
 
     // Field mergers.
     this.classStaticFieldsMerger = new ClassStaticFieldsMerger(appView, lensBuilder, group);
-    this.classInstanceFieldsMerger = new ClassInstanceFieldsMerger(appView, lensBuilder, group);
+    this.classInstanceFieldsMerger = ClassInstanceFieldsMerger.create(appView, lensBuilder, group);
 
     // Method mergers.
     this.classInitializerMerger = ClassInitializerMerger.create(group);
@@ -344,16 +344,12 @@
   }
 
   public static class Builder {
-    private final AppView<? extends AppInfoWithClassHierarchy> appView;
+    private final AppView<?> appView;
     private final IRCodeProvider codeProvider;
-    private Mode mode;
+    private final Mode mode;
     private final MergeGroup group;
 
-    public Builder(
-        AppView<? extends AppInfoWithClassHierarchy> appView,
-        IRCodeProvider codeProvider,
-        MergeGroup group,
-        Mode mode) {
+    public Builder(AppView<?> appView, IRCodeProvider codeProvider, MergeGroup group, Mode mode) {
       this.appView = appView;
       this.codeProvider = codeProvider;
       this.group = group;
@@ -361,6 +357,24 @@
     }
 
     private List<VirtualMethodMerger> createVirtualMethodMergers() {
+      if (!appView.hasClassHierarchy()) {
+        assert getVirtualMethodMergerBuilders().isEmpty();
+        return Collections.emptyList();
+      }
+      Map<DexMethodSignature, VirtualMethodMerger.Builder> virtualMethodMergerBuilders =
+          getVirtualMethodMergerBuilders();
+      if (virtualMethodMergerBuilders.isEmpty()) {
+        return Collections.emptyList();
+      }
+      List<VirtualMethodMerger> virtualMethodMergers =
+          new ArrayList<>(virtualMethodMergerBuilders.size());
+      for (VirtualMethodMerger.Builder builder : virtualMethodMergerBuilders.values()) {
+        virtualMethodMergers.add(builder.build(appView.withClassHierarchy(), group));
+      }
+      return virtualMethodMergers;
+    }
+
+    private Map<DexMethodSignature, VirtualMethodMerger.Builder> getVirtualMethodMergerBuilders() {
       Map<DexMethodSignature, VirtualMethodMerger.Builder> virtualMethodMergerBuilders =
           new LinkedHashMap<>();
       group.forEach(
@@ -372,12 +386,7 @@
                               virtualMethod.getReference().getSignature(),
                               ignore -> new VirtualMethodMerger.Builder())
                           .add(virtualMethod)));
-      List<VirtualMethodMerger> virtualMethodMergers =
-          new ArrayList<>(virtualMethodMergerBuilders.size());
-      for (VirtualMethodMerger.Builder builder : virtualMethodMergerBuilders.values()) {
-        virtualMethodMergers.add(builder.build(appView, group));
-      }
-      return virtualMethodMergers;
+      return virtualMethodMergerBuilders;
     }
 
     private void createClassIdField() {
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMerger.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMerger.java
index c0cb840..6671b6a 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMerger.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMerger.java
@@ -6,11 +6,12 @@
 
 import static com.android.tools.r8.graph.DexClassAndMethod.asProgramMethodOrNull;
 
+import com.android.tools.r8.graph.AppInfo;
 import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
 import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexApplication;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexProgramClass;
-import com.android.tools.r8.graph.DirectMappedDexApplication;
 import com.android.tools.r8.graph.GraphLens.MethodLookupResult;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.graph.PrunedItems;
@@ -20,6 +21,7 @@
 import com.android.tools.r8.shaking.FieldAccessInfoCollectionModifier;
 import com.android.tools.r8.shaking.KeepInfoCollection;
 import com.android.tools.r8.shaking.RuntimeTypeCheckInfo;
+import com.android.tools.r8.synthesis.SyntheticItems;
 import com.android.tools.r8.utils.InternalOptions.HorizontalClassMergerOptions;
 import com.android.tools.r8.utils.ThreadUtils;
 import com.android.tools.r8.utils.Timing;
@@ -47,11 +49,11 @@
     }
   }
 
-  private final AppView<? extends AppInfoWithClassHierarchy> appView;
+  private final AppView<?> appView;
   private final Mode mode;
   private final HorizontalClassMergerOptions options;
 
-  private HorizontalClassMerger(AppView<? extends AppInfoWithClassHierarchy> appView, Mode mode) {
+  private HorizontalClassMerger(AppView<?> appView, Mode mode) {
     this.appView = appView;
     this.mode = mode;
     this.options = appView.options().horizontalClassMergerOptions();
@@ -67,12 +69,26 @@
     return new HorizontalClassMerger(appView, Mode.FINAL);
   }
 
+  public static HorizontalClassMerger createForD8ClassMerging(AppView<?> appView) {
+    assert appView.options().horizontalClassMergerOptions().isRestrictedToSynthetics();
+    return new HorizontalClassMerger(appView, Mode.FINAL);
+  }
+
+  public void runIfNecessary(ExecutorService executorService, Timing timing)
+      throws ExecutionException {
+    runIfNecessary(executorService, timing, null);
+  }
+
   public void runIfNecessary(
-      RuntimeTypeCheckInfo runtimeTypeCheckInfo, ExecutorService executorService, Timing timing)
+      ExecutorService executorService, Timing timing, RuntimeTypeCheckInfo runtimeTypeCheckInfo)
       throws ExecutionException {
     if (options.isEnabled(mode)) {
       timing.begin("HorizontalClassMerger (" + mode.toString() + ")");
-      run(runtimeTypeCheckInfo, executorService, timing);
+      IRCodeProvider codeProvider =
+          appView.hasClassHierarchy()
+              ? IRCodeProvider.create(appView.withClassHierarchy())
+              : IRCodeProvider.createThrowing();
+      run(runtimeTypeCheckInfo, codeProvider, executorService, timing);
 
       // Clear type elements cache after IR building.
       appView.dexItemFactory().clearTypeElementsCache();
@@ -84,10 +100,11 @@
   }
 
   private void run(
-      RuntimeTypeCheckInfo runtimeTypeCheckInfo, ExecutorService executorService, Timing timing)
+      RuntimeTypeCheckInfo runtimeTypeCheckInfo,
+      IRCodeProvider codeProvider,
+      ExecutorService executorService,
+      Timing timing)
       throws ExecutionException {
-    IRCodeProvider codeProvider = new IRCodeProvider(appView);
-
     // Run the policies on all program classes to produce a final grouping.
     List<Policy> policies =
         PolicyScheduler.getPolicies(appView, codeProvider, mode, runtimeTypeCheckInfo);
@@ -121,6 +138,7 @@
 
     SyntheticInitializerConverter syntheticInitializerConverter =
         syntheticInitializerConverterBuilder.build();
+    assert syntheticInitializerConverter.isEmpty() || appView.enableWholeProgramOptimizations();
     syntheticInitializerConverter.convertClassInitializers(executorService);
 
     // Generate the graph lens.
@@ -131,16 +149,34 @@
     HorizontalClassMergerGraphLens horizontalClassMergerGraphLens =
         createLens(mergedClasses, lensBuilder, mode, syntheticArgumentClass);
 
-    assert verifyNoCyclesInInterfaceHierarchies(groups);
-
-    // Prune keep info.
-    KeepInfoCollection keepInfo = appView.getKeepInfo();
-    keepInfo.mutate(mutator -> mutator.removeKeepInfoForMergedClasses(prunedItems));
+    assert verifyNoCyclesInInterfaceHierarchies(appView, groups);
 
     // Must rewrite AppInfoWithLiveness before pruning the merged classes, to ensure that allocation
     // sites, fields accesses, etc. are correctly transferred to the target classes.
-    appView.rewriteWithLensAndApplication(
-        horizontalClassMergerGraphLens, getNewApplication(mergedClasses));
+    DexApplication newApplication = getNewApplication(mergedClasses);
+    if (appView.enableWholeProgramOptimizations()) {
+      // Prune keep info.
+      KeepInfoCollection keepInfo = appView.getKeepInfo();
+      keepInfo.mutate(mutator -> mutator.removeKeepInfoForMergedClasses(prunedItems));
+      assert appView.hasClassHierarchy();
+      appView.rewriteWithLensAndApplication(
+          horizontalClassMergerGraphLens, newApplication.toDirect());
+    } else {
+      assert mode.isFinal();
+      SyntheticItems syntheticItems = appView.appInfo().getSyntheticItems();
+      assert !syntheticItems.hasPendingSyntheticClasses();
+      appView
+          .withoutClassHierarchy()
+          .setAppInfo(
+              new AppInfo(
+                  syntheticItems.commitRewrittenWithLens(
+                      newApplication, horizontalClassMergerGraphLens),
+                  appView
+                      .appInfo()
+                      .getMainDexInfo()
+                      .rewrittenWithLens(syntheticItems, horizontalClassMergerGraphLens)));
+      appView.setGraphLens(horizontalClassMergerGraphLens);
+    }
     codeProvider.setGraphLens(horizontalClassMergerGraphLens);
 
     // Record where the synthesized $r8$classId fields are read and written.
@@ -162,6 +198,10 @@
   private void amendKeepInfo(
       HorizontalClassMergerGraphLens horizontalClassMergerGraphLens,
       List<VirtuallyMergedMethodsKeepInfo> virtuallyMergedMethodsKeepInfos) {
+    if (!appView.enableWholeProgramOptimizations()) {
+      assert virtuallyMergedMethodsKeepInfos.isEmpty();
+      return;
+    }
     appView
         .getKeepInfo()
         .mutate(
@@ -213,6 +253,10 @@
       HorizontalClassMergerGraphLens horizontalClassMergerGraphLens,
       ExecutorService executorService)
       throws ExecutionException {
+    if (!appView.hasClassHierarchy()) {
+      assert verifyNoIncompleteCode(groups, executorService);
+      return;
+    }
     ThreadUtils.processItems(
         groups,
         group -> {
@@ -225,25 +269,47 @@
                 IncompleteHorizontalClassMergerCode code =
                     (IncompleteHorizontalClassMergerCode) method.getDefinition().getCode();
                 method
-                    .getDefinition()
                     .setCode(
-                        code.toCfCode(appView, method, horizontalClassMergerGraphLens), appView);
+                        code.toCfCode(
+                            appView.withClassHierarchy(), method, horizontalClassMergerGraphLens),
+                        appView);
               });
         },
         executorService);
   }
 
-  private DirectMappedDexApplication getNewApplication(HorizontallyMergedClasses mergedClasses) {
+  private boolean verifyNoIncompleteCode(
+      Collection<MergeGroup> groups, ExecutorService executorService) throws ExecutionException {
+    ThreadUtils.processItems(
+        groups,
+        group -> {
+          assert !group
+                  .getTarget()
+                  .methods(
+                      method ->
+                          method.hasCode()
+                              && method.getCode().isIncompleteHorizontalClassMergerCode())
+                  .iterator()
+                  .hasNext()
+              : "Expected no incomplete code";
+        },
+        executorService);
+    return true;
+  }
+
+  private DexApplication getNewApplication(HorizontallyMergedClasses mergedClasses) {
     // In the second round of class merging, we must forcefully remove the merged classes from the
     // application, since we won't run tree shaking before writing the application.
-    DirectMappedDexApplication application = appView.appInfo().app().asDirect();
-    return mode.isInitial()
-        ? application
-        : application
-            .builder()
-            .removeProgramClasses(
-                clazz -> mergedClasses.hasBeenMergedIntoDifferentType(clazz.getType()))
-            .build();
+    if (mode.isInitial()) {
+      return appView.app();
+    } else {
+      return appView
+          .app()
+          .builder()
+          .removeProgramClasses(
+              clazz -> mergedClasses.hasBeenMergedIntoDifferentType(clazz.getType()))
+          .build();
+    }
   }
 
   private List<MergeGroup> getInitialGroups() {
@@ -313,13 +379,16 @@
         .fixupTypeReferences();
   }
 
-  private boolean verifyNoCyclesInInterfaceHierarchies(Collection<MergeGroup> groups) {
+  private static boolean verifyNoCyclesInInterfaceHierarchies(
+      AppView<?> appView, Collection<MergeGroup> groups) {
     for (MergeGroup group : groups) {
       if (group.isClassGroup()) {
         continue;
       }
+      assert appView.hasClassHierarchy();
       DexProgramClass interfaceClass = group.getTarget();
       appView
+          .withClassHierarchy()
           .appInfo()
           .traverseSuperTypes(
               interfaceClass,
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/IRCodeProvider.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/IRCodeProvider.java
index 9f74d8e..166bb1f 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/IRCodeProvider.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/IRCodeProvider.java
@@ -11,31 +11,56 @@
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.code.IRCode;
 
-public class IRCodeProvider {
+public interface IRCodeProvider {
 
-  private final AppView<AppInfo> appViewForConversion;
+  IRCode buildIR(ProgramMethod method);
 
-  public IRCodeProvider(AppView<? extends AppInfoWithClassHierarchy> appView) {
-    // At this point the code rewritings described by repackaging and synthetic finalization have
-    // not been applied to the code objects. These code rewritings will be applied in the
-    // application writer. We therefore simulate that we are in D8, to allow building IR for each of
-    // the class initializers without applying the unapplied code rewritings, to avoid that we apply
-    // the lens more than once to the same piece of code.
-    AppView<AppInfo> appViewForConversion =
-        AppView.createForD8(AppInfo.createInitialAppInfo(appView.appInfo().app()));
-    appViewForConversion.setGraphLens(appView.graphLens());
-    appViewForConversion.setCodeLens(appView.codeLens());
-    this.appViewForConversion = appViewForConversion;
+  void setGraphLens(GraphLens graphLens);
+
+  static IRCodeProvider create(AppView<? extends AppInfoWithClassHierarchy> appView) {
+    return new IRCodeProviderImpl(appView);
   }
 
-  public IRCode buildIR(ProgramMethod method) {
-    return method
-        .getDefinition()
-        .getCode()
-        .buildIR(method, appViewForConversion, method.getOrigin());
+  static IRCodeProvider createThrowing() {
+    return new IRCodeProvider() {
+      @Override
+      public IRCode buildIR(ProgramMethod method) {
+        throw new UnsupportedOperationException("Should never build IR for methods in D8");
+      }
+
+      @Override
+      public void setGraphLens(GraphLens graphLens) {}
+    };
   }
 
-  public void setGraphLens(GraphLens graphLens) {
-    appViewForConversion.setGraphLens(graphLens);
+  class IRCodeProviderImpl implements IRCodeProvider {
+
+    private final AppView<AppInfo> appViewForConversion;
+
+    private IRCodeProviderImpl(AppView<? extends AppInfoWithClassHierarchy> appView) {
+      // At this point the code rewritings described by repackaging and synthetic finalization have
+      // not been applied to the code objects. These code rewritings will be applied in the
+      // application writer. We therefore simulate that we are in D8, to allow building IR for each
+      // of the class initializers without applying the unapplied code rewritings, to avoid that we
+      // apply the lens more than once to the same piece of code.
+      AppView<AppInfo> appViewForConversion =
+          AppView.createForD8(AppInfo.createInitialAppInfo(appView.appInfo().app()));
+      appViewForConversion.setGraphLens(appView.graphLens());
+      appViewForConversion.setCodeLens(appView.codeLens());
+      this.appViewForConversion = appViewForConversion;
+    }
+
+    @Override
+    public IRCode buildIR(ProgramMethod method) {
+      return method
+          .getDefinition()
+          .getCode()
+          .buildIR(method, appViewForConversion, method.getOrigin());
+    }
+
+    @Override
+    public void setGraphLens(GraphLens graphLens) {
+      appViewForConversion.setGraphLens(graphLens);
+    }
   }
 }
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/IncompleteHorizontalClassMergerCode.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/IncompleteHorizontalClassMergerCode.java
index d961949..c9c8e34 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/IncompleteHorizontalClassMergerCode.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/IncompleteHorizontalClassMergerCode.java
@@ -14,6 +14,7 @@
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.graph.UseRegistry;
 import com.android.tools.r8.ir.code.IRCode;
+import com.android.tools.r8.ir.conversion.MethodConversionOptions.MutableMethodConversionOptions;
 import com.android.tools.r8.naming.ClassNameMapper;
 import com.android.tools.r8.origin.Origin;
 
@@ -37,7 +38,11 @@
   // Implement Code.
 
   @Override
-  public IRCode buildIR(ProgramMethod method, AppView<?> appView, Origin origin) {
+  public IRCode buildIR(
+      ProgramMethod method,
+      AppView<?> appView,
+      Origin origin,
+      MutableMethodConversionOptions conversionOptions) {
     throw new Unreachable();
   }
 
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/InstanceInitializerMergerCollection.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/InstanceInitializerMergerCollection.java
index dbf0943..952421e 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/InstanceInitializerMergerCollection.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/InstanceInitializerMergerCollection.java
@@ -16,6 +16,7 @@
 import com.android.tools.r8.utils.collections.ProgramMethodSet;
 import it.unimi.dsi.fastutil.objects.Reference2IntMap;
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.Comparator;
 import java.util.LinkedHashMap;
 import java.util.List;
@@ -37,14 +38,21 @@
   }
 
   public static InstanceInitializerMergerCollection create(
-      AppView<? extends AppInfoWithClassHierarchy> appView,
+      AppView<?> appView,
       Reference2IntMap<DexType> classIdentifiers,
       IRCodeProvider codeProvider,
       MergeGroup group,
       HorizontalClassMergerGraphLens.Builder lensBuilder,
       Mode mode) {
+    if (!appView.hasClassHierarchy()) {
+      assert appView.options().horizontalClassMergerOptions().isRestrictedToSynthetics();
+      assert verifyNoInstanceInitializers(group);
+      return new InstanceInitializerMergerCollection(
+          Collections.emptyList(), Collections.emptyMap());
+    }
     // Create an instance initializer merger for each group of instance initializers that are
     // equivalent.
+    AppView<AppInfoWithClassHierarchy> appViewWithClassHierarchy = appView.withClassHierarchy();
     Map<InstanceInitializerDescription, Builder> buildersByDescription = new LinkedHashMap<>();
     ProgramMethodSet buildersWithoutDescription = ProgramMethodSet.createLinked();
     group.forEach(
@@ -54,7 +62,7 @@
                 instanceInitializer -> {
                   InstanceInitializerDescription description =
                       InstanceInitializerAnalysis.analyze(
-                          appView, codeProvider, group, instanceInitializer);
+                          appViewWithClassHierarchy, codeProvider, group, instanceInitializer);
                   if (description != null) {
                     buildersByDescription
                         .computeIfAbsent(
@@ -62,7 +70,10 @@
                             ignoreKey(
                                 () ->
                                     new InstanceInitializerMerger.Builder(
-                                        appView, classIdentifiers, lensBuilder, mode)))
+                                        appViewWithClassHierarchy,
+                                        classIdentifiers,
+                                        lensBuilder,
+                                        mode)))
                         .addEquivalent(instanceInitializer);
                   } else {
                     buildersWithoutDescription.add(instanceInitializer);
@@ -95,7 +106,7 @@
                       instanceInitializer.getDefinition().getProto(),
                       ignore ->
                           new InstanceInitializerMerger.Builder(
-                              appView, classIdentifiers, lensBuilder, mode))
+                              appViewWithClassHierarchy, classIdentifiers, lensBuilder, mode))
                   .add(instanceInitializer));
       for (InstanceInitializerMerger.Builder builder : buildersByProto.values()) {
         instanceInitializerMergers.addAll(builder.build(group));
@@ -105,7 +116,7 @@
           instanceInitializer ->
               instanceInitializerMergers.addAll(
                   new InstanceInitializerMerger.Builder(
-                          appView, classIdentifiers, lensBuilder, mode)
+                          appViewWithClassHierarchy, classIdentifiers, lensBuilder, mode)
                       .add(instanceInitializer)
                       .build(group)));
     }
@@ -118,6 +129,14 @@
         instanceInitializerMergers, equivalentInstanceInitializerMergers);
   }
 
+  private static boolean verifyNoInstanceInitializers(MergeGroup group) {
+    group.forEach(
+        clazz -> {
+          assert !clazz.programInstanceInitializers().iterator().hasNext();
+        });
+    return true;
+  }
+
   public void forEach(Consumer<InstanceInitializerMerger> consumer) {
     instanceInitializerMergers.forEach(consumer);
     equivalentInstanceInitializerMergers.values().forEach(consumer);
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/MergeGroup.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/MergeGroup.java
index 458f0ba..7c7a68d 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/MergeGroup.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/MergeGroup.java
@@ -14,7 +14,7 @@
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.ProgramField;
-import com.android.tools.r8.shaking.KeepClassInfo;
+import com.android.tools.r8.shaking.KeepInfoCollection;
 import com.android.tools.r8.utils.IterableUtils;
 import com.android.tools.r8.utils.IteratorUtils;
 import com.android.tools.r8.utils.collections.BidirectionalManyToOneHashMap;
@@ -167,17 +167,18 @@
     return new ProgramField(getTarget(), targetField);
   }
 
-  public void selectTarget(AppView<? extends AppInfoWithClassHierarchy> appView) {
+  public void selectTarget(AppView<?> appView) {
     Iterable<DexProgramClass> candidates = Iterables.filter(getClasses(), DexClass::isPublic);
     if (IterableUtils.isEmpty(candidates)) {
       candidates = getClasses();
     }
     Iterator<DexProgramClass> candidateIterator = candidates.iterator();
     DexProgramClass target = IterableUtils.first(candidates);
+    KeepInfoCollection keepInfo = appView.getKeepInfo();
     while (candidateIterator.hasNext()) {
       DexProgramClass current = candidateIterator.next();
-      KeepClassInfo keepClassInfo = appView.getKeepInfo().getClassInfo(current);
-      if (keepClassInfo.isMinificationAllowed(appView.options())) {
+      if (keepInfo != null
+          && keepInfo.getClassInfo(current).isMinificationAllowed(appView.options())) {
         target = current;
         break;
       }
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/PolicyScheduler.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/PolicyScheduler.java
index 5e76e2d..2ff1bfb 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/PolicyScheduler.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/PolicyScheduler.java
@@ -4,6 +4,7 @@
 
 package com.android.tools.r8.horizontalclassmerging;
 
+import com.android.tools.r8.graph.AppInfo;
 import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.ImmediateProgramSubtypingInfo;
@@ -43,6 +44,7 @@
 import com.android.tools.r8.horizontalclassmerging.policies.NoVirtualMethodMerging;
 import com.android.tools.r8.horizontalclassmerging.policies.NoWeakerAccessPrivileges;
 import com.android.tools.r8.horizontalclassmerging.policies.NotMatchedByNoHorizontalClassMerging;
+import com.android.tools.r8.horizontalclassmerging.policies.OnlyClassesWithStaticDefinitions;
 import com.android.tools.r8.horizontalclassmerging.policies.OnlyDirectlyConnectedOrUnrelatedInterfaces;
 import com.android.tools.r8.horizontalclassmerging.policies.PreserveMethodCharacteristics;
 import com.android.tools.r8.horizontalclassmerging.policies.PreventClassMethodAndDefaultMethodCollisions;
@@ -53,7 +55,8 @@
 import com.android.tools.r8.horizontalclassmerging.policies.SameNestHost;
 import com.android.tools.r8.horizontalclassmerging.policies.SameParentClass;
 import com.android.tools.r8.horizontalclassmerging.policies.SyntheticItemsPolicy;
-import com.android.tools.r8.horizontalclassmerging.policies.VerifyPolicyAlwaysSatisfied;
+import com.android.tools.r8.horizontalclassmerging.policies.VerifyMultiClassPolicyAlwaysSatisfied;
+import com.android.tools.r8.horizontalclassmerging.policies.VerifySingleClassPolicyAlwaysSatisfied;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.shaking.RuntimeTypeCheckInfo;
 import com.android.tools.r8.utils.ListUtils;
@@ -63,6 +66,31 @@
 public class PolicyScheduler {
 
   public static List<Policy> getPolicies(
+      AppView<?> appView,
+      IRCodeProvider codeProvider,
+      Mode mode,
+      RuntimeTypeCheckInfo runtimeTypeCheckInfo) {
+    if (appView.hasClassHierarchy()) {
+      return getPoliciesForR8(
+          appView.withClassHierarchy(), codeProvider, mode, runtimeTypeCheckInfo);
+    } else {
+      return getPoliciesForD8(appView.withoutClassHierarchy(), mode);
+    }
+  }
+
+  private static List<Policy> getPoliciesForD8(AppView<AppInfo> appView, Mode mode) {
+    assert mode.isFinal();
+    List<Policy> policies =
+        ImmutableList.<Policy>builder()
+            .addAll(getSingleClassPoliciesForD8(appView, mode))
+            .addAll(getMultiClassPoliciesForD8(appView, mode))
+            .build();
+    policies = appView.options().testing.horizontalClassMergingPolicyRewriter.apply(policies);
+    assert verifyPolicyOrderingConstraints(policies);
+    return policies;
+  }
+
+  private static List<Policy> getPoliciesForR8(
       AppView<? extends AppInfoWithClassHierarchy> appView,
       IRCodeProvider codeProvider,
       Mode mode,
@@ -104,6 +132,16 @@
     return builder.build();
   }
 
+  private static List<SingleClassPolicy> getSingleClassPoliciesForD8(
+      AppView<AppInfo> appView, Mode mode) {
+    ImmutableList.Builder<SingleClassPolicy> builder =
+        ImmutableList.<SingleClassPolicy>builder()
+            .add(new CheckSyntheticClasses(appView))
+            .add(new OnlyClassesWithStaticDefinitions());
+    assert verifySingleClassPoliciesIrrelevantForMergingSyntheticsInD8(appView, mode, builder);
+    return builder.build();
+  }
+
   private static void addRequiredSingleClassPolicies(
       AppView<? extends AppInfoWithClassHierarchy> appView,
       ImmutableList.Builder<SingleClassPolicy> builder) {
@@ -148,7 +186,22 @@
             new NoKotlinMetadata(),
             new NoNativeMethods(),
             new NoServiceLoaders(appView));
-    policies.stream().map(VerifyPolicyAlwaysSatisfied::new).forEach(builder::add);
+    policies.stream().map(VerifySingleClassPolicyAlwaysSatisfied::new).forEach(builder::add);
+    return true;
+  }
+
+  private static boolean verifySingleClassPoliciesIrrelevantForMergingSyntheticsInD8(
+      AppView<AppInfo> appView, Mode mode, ImmutableList.Builder<SingleClassPolicy> builder) {
+    List<SingleClassPolicy> policies =
+        ImmutableList.of(
+            new NoAnnotationClasses(),
+            new NoDirectRuntimeTypeChecks(appView, mode),
+            new NoInterfaces(appView, mode),
+            new NoInnerClasses(),
+            new NoInstanceFieldAnnotations(),
+            new NoKotlinMetadata(),
+            new NoNativeMethods());
+    policies.stream().map(VerifySingleClassPolicyAlwaysSatisfied::new).forEach(builder::add);
     return true;
   }
 
@@ -194,6 +247,22 @@
     return builder.add(new FinalizeMergeGroup(appView, mode)).build();
   }
 
+  private static List<? extends Policy> getMultiClassPoliciesForD8(
+      AppView<AppInfo> appView, Mode mode) {
+    ImmutableList.Builder<MultiClassPolicy> builder = ImmutableList.builder();
+    builder.add(
+        new CheckAbstractClasses(appView),
+        new SameMainDexGroup(appView),
+        new SameNestHost(appView),
+        new SameParentClass(),
+        new SyntheticItemsPolicy(appView, mode),
+        new NoDifferentApiReferenceLevel(appView),
+        new LimitClassGroups(appView));
+    assert verifyMultiClassPoliciesIrrelevantForMergingSyntheticsInD8(appView, mode, builder);
+    builder.add(new FinalizeMergeGroup(appView, mode));
+    return builder.build();
+  }
+
   private static void addRequiredMultiClassPolicies(
       AppView<? extends AppInfoWithClassHierarchy> appView,
       Mode mode,
@@ -234,6 +303,14 @@
         new OnlyDirectlyConnectedOrUnrelatedInterfaces(appView, mode));
   }
 
+  private static boolean verifyMultiClassPoliciesIrrelevantForMergingSyntheticsInD8(
+      AppView<AppInfo> appView, Mode mode, ImmutableList.Builder<MultiClassPolicy> builder) {
+    List<MultiClassPolicy> policies =
+        ImmutableList.of(new SyntheticItemsPolicy(appView, mode), new SameParentClass());
+    policies.stream().map(VerifyMultiClassPolicyAlwaysSatisfied::new).forEach(builder::add);
+    return true;
+  }
+
   private static boolean verifyPolicyOrderingConstraints(List<Policy> policies) {
     // No policies that may split interface groups are allowed to run after the
     // OnlyDirectlyConnectedOrUnrelatedInterfaces policy. This policy ensures that interface merging
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/TreeFixer.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/TreeFixer.java
index 183664a..31753bf 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/TreeFixer.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/TreeFixer.java
@@ -5,7 +5,6 @@
 package com.android.tools.r8.horizontalclassmerging;
 
 import com.android.tools.r8.errors.Unreachable;
-import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DefaultInstanceInitializerCode;
 import com.android.tools.r8.graph.DexEncodedField;
@@ -43,7 +42,7 @@
  */
 class TreeFixer extends TreeFixerBase {
 
-  private final AppView<? extends AppInfoWithClassHierarchy> appView;
+  private final AppView<?> appView;
   private final HorizontallyMergedClasses mergedClasses;
   private final Mode mode;
   private final HorizontalClassMergerGraphLens.Builder lensBuilder;
@@ -55,7 +54,7 @@
       HashBiMap.create();
 
   public TreeFixer(
-      AppView<? extends AppInfoWithClassHierarchy> appView,
+      AppView<?> appView,
       HorizontallyMergedClasses mergedClasses,
       HorizontalClassMergerGraphLens.Builder lensBuilder,
       Mode mode,
@@ -123,17 +122,20 @@
    * </ul>
    */
   public HorizontalClassMergerGraphLens fixupTypeReferences() {
-    Collection<DexProgramClass> classes = appView.appInfo().classesWithDeterministicOrder();
-    Iterables.filter(classes, DexProgramClass::isInterface).forEach(this::fixupInterfaceClass);
-    classes.forEach(this::fixupAttributes);
-    classes.forEach(this::fixupProgramClassSuperTypes);
-    SubtypingForrestForClasses subtypingForrest = new SubtypingForrestForClasses(appView);
-    // TODO(b/170078037): parallelize this code segment.
-    for (DexProgramClass root : subtypingForrest.getProgramRoots()) {
-      subtypingForrest.traverseNodeDepthFirst(root, HashBiMap.create(), this::fixupProgramClass);
-    }
     HorizontalClassMergerGraphLens lens = lensBuilder.build(appView, mergedClasses);
-    new AnnotationFixer(lens).run(appView.appInfo().classes());
+    if (appView.enableWholeProgramOptimizations()) {
+      Collection<DexProgramClass> classes = appView.appInfo().classesWithDeterministicOrder();
+      Iterables.filter(classes, DexProgramClass::isInterface).forEach(this::fixupInterfaceClass);
+      classes.forEach(this::fixupAttributes);
+      classes.forEach(this::fixupProgramClassSuperTypes);
+      SubtypingForrestForClasses subtypingForrest =
+          new SubtypingForrestForClasses(appView.withClassHierarchy());
+      // TODO(b/170078037): parallelize this code segment.
+      for (DexProgramClass root : subtypingForrest.getProgramRoots()) {
+        subtypingForrest.traverseNodeDepthFirst(root, HashBiMap.create(), this::fixupProgramClass);
+      }
+      new AnnotationFixer(lens).run(appView.appInfo().classes());
+    }
     return lens;
   }
 
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/VirtualMethodMerger.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/VirtualMethodMerger.java
index 080df47..1103c8f 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/VirtualMethodMerger.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/VirtualMethodMerger.java
@@ -60,7 +60,7 @@
       SingleResolutionResult resolutionResult =
           appView
               .appInfo()
-              .resolveMethodOnClass(template, group.getSuperType())
+              .resolveMethodOnClass(group.getSuperType(), template)
               .asSingleResolution();
       if (resolutionResult == null || resolutionResult.getResolvedMethod().isAbstract()) {
         // If there is no super method or the method is abstract it should not be called.
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/code/ClassInitializerMerger.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/code/ClassInitializerMerger.java
index fcae6d5..9787686 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/code/ClassInitializerMerger.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/code/ClassInitializerMerger.java
@@ -34,6 +34,7 @@
 import com.android.tools.r8.ir.code.Position;
 import com.android.tools.r8.ir.code.Position.SyntheticPosition;
 import com.android.tools.r8.ir.code.Return;
+import com.android.tools.r8.ir.conversion.MethodConversionOptions.MutableMethodConversionOptions;
 import com.android.tools.r8.naming.ClassNameMapper;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.utils.CfVersionUtils;
@@ -193,7 +194,11 @@
     }
 
     @Override
-    public IRCode buildIR(ProgramMethod method, AppView<?> appView, Origin origin) {
+    public IRCode buildIR(
+        ProgramMethod method,
+        AppView<?> appView,
+        Origin origin,
+        MutableMethodConversionOptions conversionOptions) {
       assert !classInitializers.isEmpty();
 
       Position callerPosition =
@@ -227,7 +232,8 @@
               valueNumberGenerator,
               blockNumberGenerator,
               metadata,
-              origin);
+              origin,
+              conversionOptions);
 
       ListIterator<BasicBlock> blockIterator = code.listIterator();
       InstructionListIterator instructionIterator = blockIterator.next().listIterator(code);
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/code/SyntheticInitializerConverter.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/code/SyntheticInitializerConverter.java
index 446d245..7912f4c 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/code/SyntheticInitializerConverter.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/code/SyntheticInitializerConverter.java
@@ -5,7 +5,6 @@
 package com.android.tools.r8.horizontalclassmerging.code;
 
 import com.android.tools.r8.graph.AppInfo;
-import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.horizontalclassmerging.IRCodeProvider;
@@ -25,13 +24,13 @@
  */
 public class SyntheticInitializerConverter {
 
-  private final AppView<? extends AppInfoWithClassHierarchy> appView;
+  private final AppView<?> appView;
   private final IRCodeProvider codeProvider;
   private final List<ProgramMethod> classInitializers;
   private final List<ProgramMethod> instanceInitializers;
 
   private SyntheticInitializerConverter(
-      AppView<? extends AppInfoWithClassHierarchy> appView,
+      AppView<?> appView,
       IRCodeProvider codeProvider,
       List<ProgramMethod> classInitializers,
       List<ProgramMethod> instanceInitializers) {
@@ -41,8 +40,7 @@
     this.instanceInitializers = instanceInitializers;
   }
 
-  public static Builder builder(
-      AppView<? extends AppInfoWithClassHierarchy> appView, IRCodeProvider codeProvider) {
+  public static Builder builder(AppView<?> appView, IRCodeProvider codeProvider) {
     return new Builder(appView, codeProvider);
   }
 
@@ -88,13 +86,12 @@
 
   public static class Builder {
 
-    private final AppView<? extends AppInfoWithClassHierarchy> appView;
+    private final AppView<?> appView;
     private final IRCodeProvider codeProvider;
     private final List<ProgramMethod> classInitializers = new ArrayList<>();
     private final List<ProgramMethod> instanceInitializers = new ArrayList<>();
 
-    private Builder(
-        AppView<? extends AppInfoWithClassHierarchy> appView, IRCodeProvider codeProvider) {
+    private Builder(AppView<?> appView, IRCodeProvider codeProvider) {
       this.appView = appView;
       this.codeProvider = codeProvider;
     }
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/CheckAbstractClasses.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/CheckAbstractClasses.java
index 41fba55..06874f0 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/CheckAbstractClasses.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/CheckAbstractClasses.java
@@ -4,7 +4,6 @@
 
 package com.android.tools.r8.horizontalclassmerging.policies;
 
-import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.horizontalclassmerging.MultiClassSameReferencePolicy;
@@ -20,7 +19,7 @@
 
   private final InternalOptions options;
 
-  public CheckAbstractClasses(AppView<? extends AppInfoWithClassHierarchy> appView) {
+  public CheckAbstractClasses(AppView<?> appView) {
     this.options = appView.options();
   }
 
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/CheckSyntheticClasses.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/CheckSyntheticClasses.java
index 8c08712..4d2554a 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/CheckSyntheticClasses.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/CheckSyntheticClasses.java
@@ -4,7 +4,6 @@
 
 package com.android.tools.r8.horizontalclassmerging.policies;
 
-import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.horizontalclassmerging.SingleClassPolicy;
@@ -16,7 +15,7 @@
   private final HorizontalClassMergerOptions options;
   private final SyntheticItems syntheticItems;
 
-  public CheckSyntheticClasses(AppView<? extends AppInfoWithClassHierarchy> appView) {
+  public CheckSyntheticClasses(AppView<?> appView) {
     this.options = appView.options().horizontalClassMergerOptions();
     this.syntheticItems = appView.getSyntheticItems();
   }
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/FinalizeMergeGroup.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/FinalizeMergeGroup.java
index 705c8cd..b9c4c3c 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/FinalizeMergeGroup.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/FinalizeMergeGroup.java
@@ -4,7 +4,6 @@
 
 package com.android.tools.r8.horizontalclassmerging.policies;
 
-import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.horizontalclassmerging.HorizontalClassMerger.Mode;
@@ -12,6 +11,7 @@
 import com.android.tools.r8.horizontalclassmerging.MultiClassPolicy;
 import com.android.tools.r8.utils.ListUtils;
 import com.android.tools.r8.utils.SetUtils;
+import com.android.tools.r8.utils.collections.EmptyBidirectionalOneToOneMap;
 import java.util.Collection;
 import java.util.Set;
 
@@ -25,23 +25,30 @@
  */
 public class FinalizeMergeGroup extends MultiClassPolicy {
 
-  private final AppView<? extends AppInfoWithClassHierarchy> appView;
+  private final AppView<?> appView;
   private final Mode mode;
 
-  public FinalizeMergeGroup(AppView<? extends AppInfoWithClassHierarchy> appView, Mode mode) {
+  public FinalizeMergeGroup(AppView<?> appView, Mode mode) {
     this.appView = appView;
     this.mode = mode;
   }
 
   @Override
   public Collection<MergeGroup> apply(MergeGroup group) {
-    if (mode.isInitial() || group.isInterfaceGroup()) {
-      group.selectTarget(appView);
-      group.selectInstanceFieldMap(appView);
+    if (appView.enableWholeProgramOptimizations()) {
+      if (mode.isInitial() || group.isInterfaceGroup()) {
+        group.selectTarget(appView);
+        group.selectInstanceFieldMap(appView.withClassHierarchy());
+      } else {
+        // In the final round of merging each group should be finalized by the
+        // NoInstanceInitializerMerging policy.
+        assert verifyAlreadyFinalized(group);
+      }
     } else {
-      // In the final round of merging each group should be finalized by the
-      // NoInstanceInitializerMerging policy.
-      assert verifyAlreadyFinalized(group);
+      assert !group.hasTarget();
+      assert !group.hasInstanceFieldMap();
+      group.selectTarget(appView);
+      group.setInstanceFieldMap(new EmptyBidirectionalOneToOneMap<>());
     }
     return ListUtils.newLinkedList(group);
   }
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/LimitClassGroups.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/LimitClassGroups.java
index 2752544..55d0472 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/LimitClassGroups.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/LimitClassGroups.java
@@ -4,7 +4,6 @@
 
 package com.android.tools.r8.horizontalclassmerging.policies;
 
-import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.horizontalclassmerging.MergeGroup;
@@ -17,8 +16,11 @@
 
   private final int maxGroupSize;
 
-  public LimitClassGroups(AppView<? extends AppInfoWithClassHierarchy> appView) {
-    maxGroupSize = appView.options().horizontalClassMergerOptions().getMaxClassGroupSize();
+  public LimitClassGroups(AppView<?> appView) {
+    maxGroupSize =
+        appView.enableWholeProgramOptimizations()
+            ? appView.options().horizontalClassMergerOptions().getMaxClassGroupSizeInR8()
+            : appView.options().horizontalClassMergerOptions().getMaxClassGroupSizeInD8();
     assert maxGroupSize >= 2;
   }
 
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoClassInitializerCycles.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoClassInitializerCycles.java
index 79d2a51..fa9bbde 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoClassInitializerCycles.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoClassInitializerCycles.java
@@ -487,7 +487,7 @@
         DexMethod rewrittenMethod =
             appView.graphLens().lookupInvokeDirect(method, getContext()).getReference();
         MethodResolutionResult resolutionResult =
-            appView.appInfo().resolveMethodOnClass(rewrittenMethod);
+            appView.appInfo().resolveMethodOnClassHolder(rewrittenMethod);
         if (resolutionResult.isSingleResolution()
             && resolutionResult.getResolvedHolder().isProgramClass()) {
           enqueueMethod(resolutionResult.getResolvedProgramMethod());
@@ -499,7 +499,7 @@
         DexMethod rewrittenMethod =
             appView.graphLens().lookupInvokeInterface(method, getContext()).getReference();
         DexClassAndMethod resolvedMethod =
-            appView.appInfo().resolveMethodOnInterface(rewrittenMethod).getResolutionPair();
+            appView.appInfo().resolveMethodOnInterfaceHolder(rewrittenMethod).getResolutionPair();
         if (resolvedMethod != null) {
           fail();
         }
@@ -537,7 +537,7 @@
         DexMethod rewrittenMethod =
             appView.graphLens().lookupInvokeVirtual(method, getContext()).getReference();
         DexClassAndMethod resolvedMethod =
-            appView.appInfo().resolveMethodOnClass(rewrittenMethod).getResolutionPair();
+            appView.appInfo().resolveMethodOnClassHolder(rewrittenMethod).getResolutionPair();
         if (resolvedMethod != null) {
           if (!resolvedMethod.getHolder().isEffectivelyFinal(appView)) {
             fail();
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoInterfaces.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoInterfaces.java
index 79b6601..feabe1c 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoInterfaces.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoInterfaces.java
@@ -4,7 +4,6 @@
 
 package com.android.tools.r8.horizontalclassmerging.policies;
 
-import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.horizontalclassmerging.HorizontalClassMerger.Mode;
@@ -16,7 +15,7 @@
   private final Mode mode;
   private final HorizontalClassMergerOptions options;
 
-  public NoInterfaces(AppView<? extends AppInfoWithClassHierarchy> appView, Mode mode) {
+  public NoInterfaces(AppView<?> appView, Mode mode) {
     this.mode = mode;
     this.options = appView.options().horizontalClassMergerOptions();
   }
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoVirtualMethodMerging.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoVirtualMethodMerging.java
index 54868a8..a914cf9 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoVirtualMethodMerging.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoVirtualMethodMerging.java
@@ -108,7 +108,7 @@
     SingleResolutionResult resolutionResult =
         appView
             .appInfo()
-            .resolveMethodOnClass(method.getReference(), superType)
+            .resolveMethodOnClass(superType, method.getReference())
             .asSingleResolution();
     return resolutionResult != null && !resolutionResult.getResolvedMethod().isAbstract();
   }
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/OnlyClassesWithStaticDefinitions.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/OnlyClassesWithStaticDefinitions.java
new file mode 100644
index 0000000..736ede0
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/OnlyClassesWithStaticDefinitions.java
@@ -0,0 +1,23 @@
+// Copyright (c) 2022, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.horizontalclassmerging.policies;
+
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.horizontalclassmerging.SingleClassPolicy;
+import com.google.common.collect.Iterables;
+
+/** Prevent merging of classes that has non-static methods or fields. */
+public class OnlyClassesWithStaticDefinitions extends SingleClassPolicy {
+
+  @Override
+  public boolean canMerge(DexProgramClass program) {
+    return !Iterables.any(program.members(), member -> !member.isStatic());
+  }
+
+  @Override
+  public String getName() {
+    return "OnlyStaticDefinitions";
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/PreventClassMethodAndDefaultMethodCollisions.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/PreventClassMethodAndDefaultMethodCollisions.java
index d25c21d..54bcf57 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/PreventClassMethodAndDefaultMethodCollisions.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/PreventClassMethodAndDefaultMethodCollisions.java
@@ -171,7 +171,7 @@
         if (clazzReserved.contains(signature)) {
           DexMethod template = signature.withHolder(clazz, appView.dexItemFactory());
           SingleResolutionResult result =
-              appView.appInfo().resolveMethodOnClass(template, clazz).asSingleResolution();
+              appView.appInfo().resolveMethodOnClass(clazz, template).asSingleResolution();
           if (result == null || result.getResolvedHolder().isInterface()) {
             category = MethodCategory.KEEP_ABSENT;
           }
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/SameMainDexGroup.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/SameMainDexGroup.java
index 6df343a..82dcbed 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/SameMainDexGroup.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/SameMainDexGroup.java
@@ -4,7 +4,6 @@
 
 package com.android.tools.r8.horizontalclassmerging.policies;
 
-import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.horizontalclassmerging.MultiClassSameReferencePolicy;
@@ -17,7 +16,7 @@
   private final MainDexInfo mainDexInfo;
   private final SyntheticItems synthetics;
 
-  public SameMainDexGroup(AppView<? extends AppInfoWithClassHierarchy> appView) {
+  public SameMainDexGroup(AppView<?> appView) {
     mainDexInfo = appView.appInfo().getMainDexInfo();
     synthetics = appView.getSyntheticItems();
   }
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/SameNestHost.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/SameNestHost.java
index a4ba653..0f6e5ae 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/SameNestHost.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/SameNestHost.java
@@ -4,7 +4,6 @@
 
 package com.android.tools.r8.horizontalclassmerging.policies;
 
-import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexProgramClass;
@@ -15,7 +14,7 @@
 
   private final DexItemFactory dexItemFactory;
 
-  public SameNestHost(AppView<? extends AppInfoWithClassHierarchy> appView) {
+  public SameNestHost(AppView<?> appView) {
     this.dexItemFactory = appView.dexItemFactory();
   }
 
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/SyntheticItemsPolicy.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/SyntheticItemsPolicy.java
index f40b722..95764f9 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/SyntheticItemsPolicy.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/SyntheticItemsPolicy.java
@@ -4,7 +4,6 @@
 
 package com.android.tools.r8.horizontalclassmerging.policies;
 
-import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.horizontalclassmerging.HorizontalClassMerger.Mode;
@@ -22,7 +21,7 @@
   private final Mode mode;
   private final SyntheticItems syntheticItems;
 
-  public SyntheticItemsPolicy(AppView<? extends AppInfoWithClassHierarchy> appView, Mode mode) {
+  public SyntheticItemsPolicy(AppView<?> appView, Mode mode) {
     this.mode = mode;
     this.syntheticItems = appView.getSyntheticItems();
   }
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/VerifyMultiClassPolicyAlwaysSatisfied.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/VerifyMultiClassPolicyAlwaysSatisfied.java
new file mode 100644
index 0000000..7ad1e60
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/VerifyMultiClassPolicyAlwaysSatisfied.java
@@ -0,0 +1,44 @@
+// Copyright (c) 2022, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.horizontalclassmerging.policies;
+
+import com.android.tools.r8.horizontalclassmerging.MergeGroup;
+import com.android.tools.r8.horizontalclassmerging.MultiClassPolicy;
+import com.android.tools.r8.utils.InternalOptions;
+import java.util.Collection;
+import java.util.Collections;
+
+public class VerifyMultiClassPolicyAlwaysSatisfied extends MultiClassPolicy {
+
+  private final MultiClassPolicy policy;
+
+  public VerifyMultiClassPolicyAlwaysSatisfied(MultiClassPolicy policy) {
+    this.policy = policy;
+  }
+
+  @Override
+  public String getName() {
+    return "VerifyMultiClassPolicyAlwaysSatisfied(" + policy.getName() + ")";
+  }
+
+  @Override
+  public boolean shouldSkipPolicy() {
+    return !InternalOptions.assertionsEnabled() || policy.shouldSkipPolicy();
+  }
+
+  @Override
+  public Collection<MergeGroup> apply(MergeGroup group) {
+    assert verifySameAppliedGroup(group);
+    return Collections.singletonList(group);
+  }
+
+  private boolean verifySameAppliedGroup(MergeGroup group) {
+    Collection<MergeGroup> applied = policy.apply(group);
+    assert applied.size() == 1;
+    MergeGroup appliedGroup = applied.iterator().next();
+    assert appliedGroup.size() == group.size() && group.containsAll(appliedGroup);
+    return true;
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/VerifyPolicyAlwaysSatisfied.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/VerifySingleClassPolicyAlwaysSatisfied.java
similarity index 65%
rename from src/main/java/com/android/tools/r8/horizontalclassmerging/policies/VerifyPolicyAlwaysSatisfied.java
rename to src/main/java/com/android/tools/r8/horizontalclassmerging/policies/VerifySingleClassPolicyAlwaysSatisfied.java
index 1fc559c..51d33b7 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/VerifyPolicyAlwaysSatisfied.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/VerifySingleClassPolicyAlwaysSatisfied.java
@@ -6,12 +6,13 @@
 
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.horizontalclassmerging.SingleClassPolicy;
+import com.android.tools.r8.utils.InternalOptions;
 
-public class VerifyPolicyAlwaysSatisfied extends SingleClassPolicy {
+public class VerifySingleClassPolicyAlwaysSatisfied extends SingleClassPolicy {
 
   private final SingleClassPolicy policy;
 
-  public VerifyPolicyAlwaysSatisfied(SingleClassPolicy policy) {
+  public VerifySingleClassPolicyAlwaysSatisfied(SingleClassPolicy policy) {
     this.policy = policy;
   }
 
@@ -23,11 +24,11 @@
 
   @Override
   public String getName() {
-    return "VerifyAlwaysSatisfied(" + policy.getName() + ")";
+    return "VerifySingleClassPolicyAlwaysSatisfied(" + policy.getName() + ")";
   }
 
   @Override
   public boolean shouldSkipPolicy() {
-    return policy.shouldSkipPolicy();
+    return !InternalOptions.assertionsEnabled() || policy.shouldSkipPolicy();
   }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/ClassInitializationAnalysis.java b/src/main/java/com/android/tools/r8/ir/analysis/ClassInitializationAnalysis.java
index a46b2e0..670f74a 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/ClassInitializationAnalysis.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/ClassInitializationAnalysis.java
@@ -431,7 +431,7 @@
       }
       DexMethod method = instruction.getInvokedMethod();
       MethodResolutionResult resolutionResult =
-          appView.appInfo().resolveMethodOnClass(method, method.holder);
+          appView.appInfo().resolveMethodOnClass(method.holder, method);
       if (!resolutionResult.isSingleResolution()) {
         return false;
       }
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/constant/SparseConditionalConstantPropagation.java b/src/main/java/com/android/tools/r8/ir/analysis/constant/SparseConditionalConstantPropagation.java
index cf531ff..0ac3c7e 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/constant/SparseConditionalConstantPropagation.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/constant/SparseConditionalConstantPropagation.java
@@ -78,7 +78,7 @@
       }
     }
     rewriteCode();
-    assert code.isConsistentSSA();
+    assert code.isConsistentSSA(appView);
   }
 
   private void rewriteCode() {
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 bbee604..d05ea55 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
@@ -203,12 +203,10 @@
             }
             assert fieldType.isClassType();
             DynamicType dynamicType =
-                fieldType.isArrayType()
-                    ? DynamicType.unknown()
-                    : WideningUtils.widenDynamicNonReceiverType(
-                        appView,
-                        value.getDynamicType(appView).withNullability(Nullability.maybeNull()),
-                        field.getType());
+                WideningUtils.widenDynamicNonReceiverType(
+                    appView,
+                    value.getDynamicType(appView).withNullability(Nullability.maybeNull()),
+                    field.getType());
             return ConcreteClassTypeFieldState.create(abstractValue, dynamicType);
           }
 
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/TrivialFieldAccessReprocessor.java b/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/TrivialFieldAccessReprocessor.java
index 04b738c..d6ca793 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/TrivialFieldAccessReprocessor.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/TrivialFieldAccessReprocessor.java
@@ -6,6 +6,7 @@
 
 import static com.android.tools.r8.graph.DexProgramClass.asProgramClassOrNull;
 import static com.android.tools.r8.ir.optimize.info.OptimizationFeedback.getSimpleFeedback;
+import static com.android.tools.r8.shaking.ObjectAllocationInfoCollectionUtils.mayHaveFinalizeMethodDirectlyOrIndirectly;
 import static com.android.tools.r8.utils.MapUtils.ignoreKey;
 
 import com.android.tools.r8.code.CfOrDexInstanceFieldRead;
@@ -105,6 +106,9 @@
     constantFields.forEach(this::markFieldAsDead);
     readFields.keySet().forEach(this::markFieldAsDead);
     writtenFields.keySet().forEach(this::markWriteOnlyFieldAsDead);
+
+    // Ensure determinism of method-to-reprocess set.
+    appView.testing().checkDeterminism(postMethodProcessorBuilder::dump);
   }
 
   private void markWriteOnlyFieldAsDead(DexEncodedField field) {
@@ -286,8 +290,7 @@
       ClassTypeElement classType =
           (fieldType.isArrayType() ? fieldType.asArrayType().getBaseType() : fieldType)
               .asClassType();
-      if (classType != null
-          && appView.appInfo().mayHaveFinalizeMethodDirectlyOrIndirectly(classType)) {
+      if (classType != null && mayHaveFinalizeMethodDirectlyOrIndirectly(appView, classType)) {
         return false;
       }
     }
@@ -329,7 +332,8 @@
       if (definition.isStatic() != isStatic
           || appView.isCfByteCodePassThrough(getContext().getDefinition())
           || resolutionResult.isAccessibleFrom(getContext(), appView).isPossiblyFalse()
-          || !resolutionResult.isSingleProgramFieldResolutionResult()) {
+          || !resolutionResult.isSingleProgramFieldResolutionResult()
+          || appView.appInfo().isNeverReprocessMethod(getContext())) {
         recordAccessThatCannotBeOptimized(field, definition);
         return;
       }
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/state/ConcreteClassTypeFieldState.java b/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/state/ConcreteClassTypeFieldState.java
index 7cd6f2e..3f51c25 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/state/ConcreteClassTypeFieldState.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/state/ConcreteClassTypeFieldState.java
@@ -46,13 +46,12 @@
       AbstractValue abstractValue,
       DynamicType dynamicType,
       ProgramField field) {
+    assert field.getType().isClassType();
     this.abstractValue =
         this.abstractValue.joinReference(abstractValue, appView.abstractValueFactory());
     this.dynamicType =
-        field.getType().isArrayType()
-            ? DynamicType.unknown()
-            : WideningUtils.widenDynamicNonReceiverType(
-                appView, this.dynamicType.join(appView, dynamicType), field.getType());
+        WideningUtils.widenDynamicNonReceiverType(
+            appView, this.dynamicType.join(appView, dynamicType), field.getType());
     return isEffectivelyUnknown() ? unknown() : this;
   }
 
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 6accb73..e527832 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
@@ -86,9 +86,14 @@
       if (enumLite != null) {
         DexEncodedField field =
             enumLite.lookupField(createInternalValueMapField(enumLite.getType()));
-        return field != null
-            && appView.appInfo().isStaticFieldWrittenOnlyInEnclosingStaticInitializer(field)
-            && !appView.appInfo().isFieldRead(field);
+        if (field == null) {
+          return false;
+        }
+        if (appView.appInfo().isFieldRead(field)) {
+          return false;
+        }
+        return !appView.appInfo().isFieldWritten(field)
+            || appView.appInfo().isStaticFieldWrittenOnlyInEnclosingStaticInitializer(field);
       }
     }
     return false;
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedExtensionRegistryShrinker.java b/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedExtensionRegistryShrinker.java
index 88d1c47..66d04da 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedExtensionRegistryShrinker.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedExtensionRegistryShrinker.java
@@ -14,6 +14,7 @@
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.FieldAccessInfo;
 import com.android.tools.r8.graph.FieldAccessInfoCollection;
+import com.android.tools.r8.graph.FieldResolutionResult;
 import com.android.tools.r8.graph.ProgramField;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.code.IRCode;
@@ -231,10 +232,18 @@
 
   public boolean isDeadProtoExtensionField(DexField fieldReference) {
     AppInfoWithLiveness appInfo = appView.appInfo();
-    ProgramField field = appInfo.resolveField(fieldReference).getSingleProgramField();
-    return field != null
-        && isDeadProtoExtensionField(
-            field, appInfo.getFieldAccessInfoCollection(), appInfo.getKeepInfo());
+    return isDeadProtoExtensionField(
+        appInfo.resolveField(fieldReference),
+        appInfo.getFieldAccessInfoCollection(),
+        appInfo.getKeepInfo());
+  }
+
+  public boolean isDeadProtoExtensionField(
+      FieldResolutionResult resolutionResult,
+      FieldAccessInfoCollection<?> fieldAccessInfoCollection,
+      KeepInfoCollection keepInfo) {
+    ProgramField field = resolutionResult.getSingleProgramField();
+    return field != null && isDeadProtoExtensionField(field, fieldAccessInfoCollection, keepInfo);
   }
 
   public boolean isDeadProtoExtensionField(
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/proto/schema/LiveProtoFieldObject.java b/src/main/java/com/android/tools/r8/ir/analysis/proto/schema/LiveProtoFieldObject.java
index 4727869..4d10e4f 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/proto/schema/LiveProtoFieldObject.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/proto/schema/LiveProtoFieldObject.java
@@ -8,7 +8,6 @@
 import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.ir.analysis.type.Nullability;
 import com.android.tools.r8.ir.analysis.type.TypeElement;
-import com.android.tools.r8.ir.code.BasicBlock.ThrowingInfo;
 import com.android.tools.r8.ir.code.ConstString;
 import com.android.tools.r8.ir.code.DexItemBasedConstString;
 import com.android.tools.r8.ir.code.IRCode;
@@ -32,7 +31,6 @@
   public Instruction buildIR(AppView<?> appView, IRCode code) {
     Value value =
         code.createValue(TypeElement.stringClassType(appView, Nullability.definitelyNotNull()));
-    ThrowingInfo throwingInfo = ThrowingInfo.defaultForConstString(appView.options());
     if (appView.options().isMinifying()) {
       return new DexItemBasedConstString(value, field, FieldNameComputationInfo.forFieldName());
     }
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 b3976c7..e84fc5a 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
@@ -384,7 +384,7 @@
         }
 
         boolean valueStorageIsLive;
-        if (enqueuer.isFieldLive(valueStorage)) {
+        if (enqueuer.isFieldReferenced(valueStorage)) {
           if (enqueuer.isFieldRead(valueStorage)
               || enqueuer.isFieldWrittenOutsideDefaultConstructor(valueStorage)
               || reachesMapOrRequiredField(protoFieldInfo)) {
@@ -392,15 +392,13 @@
             // (i) optimize field reads into loading the default value of the field or (ii) remove
             // field writes to proto fields that could be read using reflection by the proto
             // library.
-            enqueuer.registerReflectiveFieldAccess(valueStorage.getReference(), dynamicMethod);
+            worklist.enqueueTraceReflectiveFieldAccessAction(valueStorage, dynamicMethod);
           }
           valueStorageIsLive = true;
         } else if (reachesMapOrRequiredField(protoFieldInfo)) {
           // Map/required fields cannot be removed. Therefore, we mark such fields as both read and
           // written such that we cannot optimize any field reads or writes.
-          enqueuer.registerReflectiveFieldAccess(valueStorage.getReference(), dynamicMethod);
-          worklist.enqueueMarkFieldAsReachableAction(
-              valueStorage, dynamicMethod, KeepReason.reflectiveUseIn(dynamicMethod));
+          worklist.enqueueTraceReflectiveFieldAccessAction(valueStorage, dynamicMethod);
           valueStorageIsLive = true;
         } else {
           valueStorageIsLive = false;
@@ -414,7 +412,7 @@
             newlyLiveField = protoFieldInfo.getOneOfCaseField(appView, protoMessageInfo);
           } else if (protoFieldInfo.hasHazzerBitField(protoMessageInfo)) {
             newlyLiveField = protoFieldInfo.getHazzerBitField(appView, protoMessageInfo);
-            enqueuer.registerReflectiveFieldAccess(valueStorage.getReference(), dynamicMethod);
+            worklist.enqueueTraceReflectiveFieldAccessAction(valueStorage, dynamicMethod);
           }
         } else {
           // For one-of fields, mark the one-of field as live if the one-of-case field is live, and
@@ -423,13 +421,13 @@
           if (protoFieldInfo.getType().isOneOf()) {
             ProgramField oneOfCaseField =
                 protoFieldInfo.getOneOfCaseField(appView, protoMessageInfo);
-            if (oneOfCaseField != null && enqueuer.isFieldLive(oneOfCaseField)) {
+            if (oneOfCaseField != null && enqueuer.isFieldReferenced(oneOfCaseField)) {
               newlyLiveField = valueStorage;
             }
           } else if (protoFieldInfo.hasHazzerBitField(protoMessageInfo)) {
             ProgramField hazzerBitField =
                 protoFieldInfo.getHazzerBitField(appView, protoMessageInfo);
-            if (hazzerBitField == null || !enqueuer.isFieldLive(hazzerBitField)) {
+            if (hazzerBitField == null || !enqueuer.isFieldReferenced(hazzerBitField)) {
               continue;
             }
 
@@ -458,15 +456,12 @@
                       && !writer.isStructurallyEqualTo(dynamicMethod);
           if (enqueuer.isFieldWrittenInMethodSatisfying(
               newlyLiveField, neitherDefaultConstructorNorDynamicMethod)) {
-            enqueuer.registerReflectiveFieldRead(newlyLiveField.getReference(), dynamicMethod);
+            worklist.enqueueTraceReflectiveFieldReadAction(newlyLiveField, dynamicMethod);
           }
 
           // Unconditionally register the hazzer and one-of proto fields as written from
           // dynamicMethod().
-          if (enqueuer.registerReflectiveFieldWrite(newlyLiveField.getReference(), dynamicMethod)) {
-            worklist.enqueueMarkFieldAsReachableAction(
-                newlyLiveField, dynamicMethod, KeepReason.reflectiveUseIn(dynamicMethod));
-          }
+          worklist.enqueueTraceReflectiveFieldWriteAction(newlyLiveField, dynamicMethod);
         }
       }
 
@@ -497,7 +492,7 @@
         // schema, and therefore we do need to trace the const-class instructions that will be
         // emitted for it.
         ProgramField valueStorage = protoFieldInfo.getValueStorage(appView, protoMessageInfo);
-        if (valueStorage != null && enqueuer.isFieldLive(valueStorage)) {
+        if (valueStorage != null && enqueuer.isFieldReferenced(valueStorage)) {
           for (ProtoObject object : objects) {
             if (object.isProtoObjectFromStaticGet()) {
               worklist.enqueueTraceStaticFieldRead(
@@ -554,7 +549,7 @@
       return;
     }
 
-    if (!enqueuer.isFieldLive(oneOfCaseField)) {
+    if (!enqueuer.isFieldReferenced(oneOfCaseField)) {
       return;
     }
 
@@ -578,10 +573,7 @@
       return;
     }
 
-    if (enqueuer.registerReflectiveFieldWrite(oneOfField.getReference(), dynamicMethod)) {
-      worklist.enqueueMarkFieldAsReachableAction(
-          oneOfField, dynamicMethod, KeepReason.reflectiveUseIn(dynamicMethod));
-    }
+    worklist.enqueueTraceReflectiveFieldWriteAction(oneOfField, dynamicMethod);
   }
 
   /**
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/type/ArrayTypeElement.java b/src/main/java/com/android/tools/r8/ir/analysis/type/ArrayTypeElement.java
index 78332d5..72cbe7d 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/type/ArrayTypeElement.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/type/ArrayTypeElement.java
@@ -3,8 +3,6 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.ir.analysis.type;
 
-import static com.android.tools.r8.ir.analysis.type.Nullability.maybeNull;
-
 import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexItemFactory;
@@ -85,12 +83,10 @@
   }
 
   @Override
-  public ReferenceTypeElement getOrCreateVariant(Nullability nullability) {
-    ArrayTypeElement variant = variants.get(nullability);
-    if (variant != null) {
-      return variant;
-    }
-    return variants.getOrCreateElement(nullability, this::createVariant);
+  public ArrayTypeElement getOrCreateVariant(Nullability nullability) {
+    return nullability.equals(nullability())
+        ? this
+        : variants.getOrCreateElement(nullability, this::createVariant);
   }
 
   @Override
@@ -151,20 +147,36 @@
   ReferenceTypeElement join(ArrayTypeElement other, AppView<?> appView) {
     Nullability nullability = nullability().join(other.nullability());
     ReferenceTypeElement join =
-        joinMember(this.memberTypeLattice, other.memberTypeLattice, appView, nullability);
+        joinMember(getMemberType(), other.getMemberType(), appView, nullability);
     if (join == null) {
       // Check if other has the right nullability before creating it.
-      if (other.nullability == nullability) {
+      if (other.nullability() == nullability) {
         return other;
       } else {
         return getOrCreateVariant(nullability);
       }
     } else {
-      assert join.nullability == nullability;
+      assert join.nullability() == nullability;
       return join;
     }
   }
 
+  ReferenceTypeElement join(ClassTypeElement other, AppView<?> appView) {
+    return other.join(this, appView);
+  }
+
+  @Override
+  public ReferenceTypeElement join(ReferenceTypeElement other, AppView<?> appView) {
+    if (other.isArrayType()) {
+      return join(other.asArrayType(), appView);
+    }
+    if (other.isClassType()) {
+      return join(other.asClassType(), appView);
+    }
+    assert other.isNullType();
+    return joinNullability(other.nullability());
+  }
+
   private static ReferenceTypeElement joinMember(
       TypeElement aMember, TypeElement bMember, AppView<?> appView, Nullability nullability) {
     if (aMember.equals(bMember)) {
@@ -172,12 +184,14 @@
       return null;
     }
     if (aMember.isArrayType() && bMember.isArrayType()) {
+      TypeElement aMemberMember = aMember.asArrayType().getMemberType();
+      TypeElement bMemberMember = bMember.asArrayType().getMemberType();
       TypeElement join =
           joinMember(
-              aMember.asArrayType().memberTypeLattice,
-              bMember.asArrayType().memberTypeLattice,
+              aMemberMember,
+              bMemberMember,
               appView,
-              maybeNull());
+              aMemberMember.nullability().join(bMemberMember.nullability()));
       return join == null ? null : ArrayTypeElement.create(join, nullability);
     }
     if (aMember.isClassType() && bMember.isClassType()) {
@@ -185,6 +199,20 @@
       return ArrayTypeElement.create(join, nullability);
     }
     if (aMember.isPrimitiveType() || bMember.isPrimitiveType()) {
+      if (appView.enableWholeProgramOptimizations()) {
+        assert appView.hasClassHierarchy();
+        DexItemFactory dexItemFactory = appView.dexItemFactory();
+        InterfaceCollection interfaceCollection =
+            InterfaceCollection.builder()
+                .addKnownInterface(dexItemFactory.cloneableType)
+                .addKnownInterface(dexItemFactory.serializableType)
+                .build();
+        return ClassTypeElement.create(
+            dexItemFactory.objectType,
+            nullability,
+            appView.withClassHierarchy(),
+            interfaceCollection);
+      }
       return objectClassType(appView, nullability);
     }
     return objectArrayType(appView, nullability);
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/type/ClassTypeElement.java b/src/main/java/com/android/tools/r8/ir/analysis/type/ClassTypeElement.java
index 7e54571..3298b8e 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/type/ClassTypeElement.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/type/ClassTypeElement.java
@@ -111,11 +111,9 @@
 
   @Override
   public ClassTypeElement getOrCreateVariant(Nullability nullability) {
-    ClassTypeElement variant = variants.get(nullability);
-    if (variant != null) {
-      return variant;
-    }
-    return variants.getOrCreateElement(nullability, this::createVariant);
+    return nullability.equals(nullability())
+        ? this
+        : variants.getOrCreateElement(nullability, this::createVariant);
   }
 
   @Override
@@ -259,18 +257,39 @@
   }
 
   ClassTypeElement join(ClassTypeElement other, AppView<?> appView) {
-    if (!appView.enableWholeProgramOptimizations()) {
-      assert lazyInterfaces != null;
-      assert lazyInterfaces.isEmpty();
-      assert other.lazyInterfaces != null;
-      assert other.lazyInterfaces.isEmpty();
-      return ClassTypeElement.createForD8(
-          getClassType() == other.getClassType()
-              ? getClassType()
-              : appView.dexItemFactory().objectType,
-          nullability().join(other.nullability()));
+    if (appView.enableWholeProgramOptimizations()) {
+      return joinWithClassHierarchy(other);
+    } else {
+      return joinWithoutClassHierarchy(other.getClassType(), other.nullability(), appView);
     }
-    return joinWithClassHierarchy(other);
+  }
+
+  ReferenceTypeElement join(ArrayTypeElement other, AppView<?> appView) {
+    DexItemFactory dexItemFactory = appView.dexItemFactory();
+    if (appView.enableWholeProgramOptimizations()) {
+      DexType lubType = appView.dexItemFactory().objectType;
+      return joinWithClassHierarchy(
+          lubType,
+          InterfaceCollection.builder()
+              .addKnownInterface(dexItemFactory.cloneableType)
+              .addKnownInterface(dexItemFactory.serializableType)
+              .build(),
+          other.nullability());
+    } else {
+      return joinWithoutClassHierarchy(dexItemFactory.objectType, other.nullability(), appView);
+    }
+  }
+
+  @Override
+  public ReferenceTypeElement join(ReferenceTypeElement other, AppView<?> appView) {
+    if (other.isArrayType()) {
+      return join(other.asArrayType(), appView);
+    }
+    if (other.isClassType()) {
+      return join(other.asClassType(), appView);
+    }
+    assert other.isNullType();
+    return joinNullability(other.nullability());
   }
 
   private ClassTypeElement joinWithClassHierarchy(ClassTypeElement other) {
@@ -278,17 +297,23 @@
     assert appView.enableWholeProgramOptimizations();
     DexType lubType =
         computeLeastUpperBoundOfClasses(appView.appInfo(), getClassType(), other.getClassType());
-    InterfaceCollection c1lubItfs = getInterfaces();
-    InterfaceCollection c2lubItfs = other.getInterfaces();
-    InterfaceCollection lubItfs =
-        c1lubItfs.equals(c2lubItfs)
-            ? c1lubItfs
-            : computeLeastUpperBoundOfInterfaces(appView, c1lubItfs, c2lubItfs);
-    InterfaceCollection lubItfsDefault =
+    return joinWithClassHierarchy(lubType, other.getInterfaces(), other.nullability());
+  }
+
+  private ClassTypeElement joinWithClassHierarchy(
+      DexType lubType, InterfaceCollection otherInterfaces, Nullability nullability) {
+    assert appView != null;
+    assert appView.enableWholeProgramOptimizations();
+    InterfaceCollection interfaces = getInterfaces();
+    InterfaceCollection lubInterfaces =
+        interfaces.equals(otherInterfaces)
+            ? interfaces
+            : computeLeastUpperBoundOfInterfaces(appView, interfaces, otherInterfaces);
+    InterfaceCollection lubInterfacesDefault =
         appView
             .dexItemFactory()
             .getOrComputeLeastUpperBoundOfImplementedInterfaces(lubType, appView);
-    Nullability lubNullability = nullability().join(other.nullability());
+    Nullability lubNullability = nullability().join(nullability);
 
     // If the computed interfaces are identical to the interfaces of `lubType`, then do not include
     // the interfaces in the ClassTypeElement. This canonicalization of interfaces reduces memory,
@@ -296,9 +321,19 @@
     // element does not require any rewriting).
     //
     // From a correctness point of view, both solutions should work.
-    return lubItfs.equals(lubItfsDefault)
+    return lubInterfaces.equals(lubInterfacesDefault)
         ? create(lubType, lubNullability, appView)
-        : create(lubType, lubNullability, appView, lubItfs);
+        : create(lubType, lubNullability, appView, lubInterfaces);
+  }
+
+  ClassTypeElement joinWithoutClassHierarchy(
+      DexType other, Nullability nullability, AppView<?> appView) {
+    assert !appView.enableWholeProgramOptimizations();
+    assert lazyInterfaces != null;
+    assert lazyInterfaces.isEmpty();
+    return createForD8(
+        getClassType() == other ? getClassType() : appView.dexItemFactory().objectType,
+        nullability().join(nullability));
   }
 
   /**
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/type/DynamicTypeWithLowerBound.java b/src/main/java/com/android/tools/r8/ir/analysis/type/DynamicTypeWithLowerBound.java
index 9448ebb..1b1a557 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/type/DynamicTypeWithLowerBound.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/type/DynamicTypeWithLowerBound.java
@@ -65,6 +65,15 @@
   }
 
   @Override
+  public String toString() {
+    return "DynamicTypeWithLowerBound(upperBound="
+        + getDynamicUpperBoundType()
+        + ", lowerBound="
+        + getDynamicLowerBoundType()
+        + ")";
+  }
+
+  @Override
   public DynamicTypeWithLowerBound withNullability(Nullability nullability) {
     if (getDynamicUpperBoundType().nullability() == nullability) {
       return this;
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/type/DynamicTypeWithUpperBound.java b/src/main/java/com/android/tools/r8/ir/analysis/type/DynamicTypeWithUpperBound.java
index 754fb43..5784e9e 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/type/DynamicTypeWithUpperBound.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/type/DynamicTypeWithUpperBound.java
@@ -266,6 +266,11 @@
     return dynamicUpperBoundType.hashCode();
   }
 
+  @Override
+  public String toString() {
+    return "DynamicTypeWithUpperBound(upperBound=" + getDynamicUpperBoundType() + ")";
+  }
+
   private static boolean verifyNotEffectivelyFinalClassType(
       AppView<AppInfoWithLiveness> appView, TypeElement type) {
     if (type.isClassType()) {
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/type/ExactDynamicType.java b/src/main/java/com/android/tools/r8/ir/analysis/type/ExactDynamicType.java
index 8d2db28..0cd29f2 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/type/ExactDynamicType.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/type/ExactDynamicType.java
@@ -73,4 +73,9 @@
   public int hashCode() {
     return getExactClassType().hashCode();
   }
+
+  @Override
+  public String toString() {
+    return "ExactDynamicType(type=" + getExactClassType() + ")";
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/type/InterfaceCollection.java b/src/main/java/com/android/tools/r8/ir/analysis/type/InterfaceCollection.java
index 02c35fd..7f4e28a 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/type/InterfaceCollection.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/type/InterfaceCollection.java
@@ -56,6 +56,10 @@
       return this;
     }
 
+    public Builder addKnownInterface(DexType type) {
+      return addInterface(type, true);
+    }
+
     public InterfaceCollection build() {
       if (interfaces.isEmpty()) {
         return InterfaceCollection.empty();
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/type/NotNullDynamicType.java b/src/main/java/com/android/tools/r8/ir/analysis/type/NotNullDynamicType.java
index 37ed14a..a9830ff 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/type/NotNullDynamicType.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/type/NotNullDynamicType.java
@@ -70,4 +70,9 @@
   public int hashCode() {
     return System.identityHashCode(this);
   }
+
+  @Override
+  public String toString() {
+    return "NotNullDynamicType";
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/type/ReferenceTypeElement.java b/src/main/java/com/android/tools/r8/ir/analysis/type/ReferenceTypeElement.java
index 3577c72..4aa2116 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/type/ReferenceTypeElement.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/type/ReferenceTypeElement.java
@@ -4,6 +4,7 @@
 package com.android.tools.r8.ir.analysis.type;
 
 import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexItemFactory;
 
 public abstract class ReferenceTypeElement extends TypeElement {
@@ -15,7 +16,7 @@
     }
 
     @Override
-    public ReferenceTypeElement getOrCreateVariant(Nullability nullability) {
+    public NullElement getOrCreateVariant(Nullability nullability) {
       return nullability.isNullable() ? NULL_INSTANCE : NULL_BOTTOM_INSTANCE;
     }
 
@@ -33,6 +34,11 @@
     }
 
     @Override
+    public ReferenceTypeElement join(ReferenceTypeElement other, AppView<?> appView) {
+      return other.joinNullability(nullability());
+    }
+
+    @Override
     public String toString() {
       return nullability.toString() + " " + DexItemFactory.nullValueType.toString();
     }
@@ -54,8 +60,8 @@
     }
   }
 
-  private static final ReferenceTypeElement NULL_INSTANCE = NullElement.create();
-  private static final ReferenceTypeElement NULL_BOTTOM_INSTANCE = NullElement.createBottom();
+  private static final NullElement NULL_INSTANCE = NullElement.create();
+  private static final NullElement NULL_BOTTOM_INSTANCE = NullElement.createBottom();
 
   final Nullability nullability;
 
@@ -90,6 +96,8 @@
     return getOrCreateVariant(Nullability.maybeNull());
   }
 
+  public abstract ReferenceTypeElement join(ReferenceTypeElement other, AppView<?> appView);
+
   public ReferenceTypeElement joinNullability(Nullability nullability) {
     return getOrCreateVariant(nullability().join(nullability));
   }
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/type/TypeElement.java b/src/main/java/com/android/tools/r8/ir/analysis/type/TypeElement.java
index 6451a1c..ef0e76e 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/type/TypeElement.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/type/TypeElement.java
@@ -5,7 +5,6 @@
 
 import static java.util.Collections.emptySet;
 
-import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexDefinitionSupplier;
@@ -119,48 +118,24 @@
    * @return {@link TypeElement}, a least upper bound of {@param this} and {@param other}.
    */
   public TypeElement join(TypeElement other, AppView<?> appView) {
-    if (this == other) {
+    if (this == other || other.isBottom()) {
       return this;
     }
     if (isBottom()) {
       return other;
     }
-    if (other.isBottom()) {
-      return this;
-    }
-    if (isTop() || other.isTop()) {
+    if (isTop() || other.isTop() || isPrimitiveType() != other.isPrimitiveType()) {
       return getTop();
     }
     if (isPrimitiveType()) {
-      return other.isPrimitiveType() ? asPrimitiveType().join(other.asPrimitiveType()) : getTop();
-    }
-    if (other.isPrimitiveType()) {
-      // By the above case, !(isPrimitive())
-      return getTop();
+      return asPrimitiveType().join(other.asPrimitiveType());
     }
     // From now on, this and other are precise reference types, i.e., either ArrayType or ClassType.
-    assert isReferenceType() && other.isReferenceType();
-    assert isPreciseType() && other.isPreciseType();
-    Nullability nullabilityJoin = nullability().join(other.nullability());
-    if (isNullType()) {
-      return other.asReferenceType().getOrCreateVariant(nullabilityJoin);
-    }
-    if (other.isNullType()) {
-      return this.asReferenceType().getOrCreateVariant(nullabilityJoin);
-    }
-    if (getClass() != other.getClass()) {
-      return objectClassType(appView, nullabilityJoin);
-    }
-    // From now on, getClass() == other.getClass()
-    if (isArrayType()) {
-      assert other.isArrayType();
-      return asArrayType().join(other.asArrayType(), appView);
-    }
-    if (isClassType()) {
-      assert other.isClassType();
-      return asClassType().join(other.asClassType(), appView);
-    }
-    throw new Unreachable("unless a new type lattice is introduced.");
+    assert isReferenceType();
+    assert isPreciseType();
+    assert other.isReferenceType();
+    assert other.isPreciseType();
+    return asReferenceType().join(other.asReferenceType(), appView);
   }
 
   public static TypeElement join(Iterable<TypeElement> typeLattices, AppView<?> appView) {
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/value/SingleFieldValue.java b/src/main/java/com/android/tools/r8/ir/analysis/value/SingleFieldValue.java
index 19af1c7..1c50dd7 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/value/SingleFieldValue.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/value/SingleFieldValue.java
@@ -29,6 +29,7 @@
 import com.android.tools.r8.ir.optimize.enums.EnumDataMap;
 import com.android.tools.r8.ir.optimize.info.field.InstanceFieldInitializationInfo;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.shaking.ObjectAllocationInfoCollectionUtils;
 import com.android.tools.r8.synthesis.SyntheticItems;
 
 public abstract class SingleFieldValue extends SingleValue {
@@ -59,7 +60,8 @@
     if (fieldType.isClassType()) {
       ClassTypeElement fieldClassType =
           TypeElement.fromDexType(fieldType, maybeNull(), appView).asClassType();
-      return appView.appInfo().mayHaveFinalizeMethodDirectlyOrIndirectly(fieldClassType);
+      return ObjectAllocationInfoCollectionUtils.mayHaveFinalizeMethodDirectlyOrIndirectly(
+          appView, fieldClassType);
     }
     assert fieldType.isArrayType() || fieldType.isPrimitiveType();
     return false;
diff --git a/src/main/java/com/android/tools/r8/ir/code/ArrayGet.java b/src/main/java/com/android/tools/r8/ir/code/ArrayGet.java
index d51e318..82a2ef7 100644
--- a/src/main/java/com/android/tools/r8/ir/code/ArrayGet.java
+++ b/src/main/java/com/android/tools/r8/ir/code/ArrayGet.java
@@ -25,6 +25,7 @@
 import com.android.tools.r8.ir.analysis.value.AbstractValue;
 import com.android.tools.r8.ir.conversion.CfBuilder;
 import com.android.tools.r8.ir.conversion.DexBuilder;
+import com.android.tools.r8.ir.conversion.MethodConversionOptions;
 import com.android.tools.r8.ir.conversion.TypeConstraintResolver;
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
 import com.android.tools.r8.ir.optimize.InliningConstraints;
@@ -113,7 +114,8 @@
   }
 
   @Override
-  public boolean identicalAfterRegisterAllocation(Instruction other, RegisterAllocator allocator) {
+  public boolean identicalAfterRegisterAllocation(
+      Instruction other, RegisterAllocator allocator, MethodConversionOptions conversionOptions) {
     // We cannot share ArrayGet instructions without knowledge of the type of the array input.
     // If multiple primitive array types flow to the same ArrayGet instruction the art verifier
     // gets confused.
diff --git a/src/main/java/com/android/tools/r8/ir/code/ArrayLength.java b/src/main/java/com/android/tools/r8/ir/code/ArrayLength.java
index 3835b3d..a0bf286 100644
--- a/src/main/java/com/android/tools/r8/ir/code/ArrayLength.java
+++ b/src/main/java/com/android/tools/r8/ir/code/ArrayLength.java
@@ -12,6 +12,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.ir.conversion.MethodConversionOptions;
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
 import com.android.tools.r8.ir.optimize.InliningConstraints;
 import com.android.tools.r8.ir.regalloc.RegisterAllocator;
@@ -84,8 +85,9 @@
   }
 
   @Override
-  public boolean identicalAfterRegisterAllocation(Instruction other, RegisterAllocator allocator) {
-    if (super.identicalAfterRegisterAllocation(other, allocator)) {
+  public boolean identicalAfterRegisterAllocation(
+      Instruction other, RegisterAllocator allocator, MethodConversionOptions conversionOptions) {
+    if (super.identicalAfterRegisterAllocation(other, allocator, conversionOptions)) {
       // The array length instruction doesn't carry the element type. The art verifier doesn't
       // allow an array length instruction into which arrays of two different base types can
       // flow. Therefore, as a safe approximation we only consider array length instructions
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 9994dde..aa70b4f 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
@@ -20,6 +20,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.ir.conversion.MethodConversionOptions;
 import com.android.tools.r8.ir.conversion.TypeConstraintResolver;
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
 import com.android.tools.r8.ir.optimize.InliningConstraints;
@@ -173,7 +174,8 @@
   }
 
   @Override
-  public boolean identicalAfterRegisterAllocation(Instruction other, RegisterAllocator allocator) {
+  public boolean identicalAfterRegisterAllocation(
+      Instruction other, RegisterAllocator allocator, MethodConversionOptions conversionOptions) {
     // We cannot share ArrayPut instructions without knowledge of the type of the array input.
     // If multiple primitive array types flow to the same ArrayPut instruction the art verifier
     // gets confused.
diff --git a/src/main/java/com/android/tools/r8/ir/code/BasicBlock.java b/src/main/java/com/android/tools/r8/ir/code/BasicBlock.java
index e5f15bd..b664c13 100644
--- a/src/main/java/com/android/tools/r8/ir/code/BasicBlock.java
+++ b/src/main/java/com/android/tools/r8/ir/code/BasicBlock.java
@@ -120,14 +120,6 @@
   public enum ThrowingInfo {
     NO_THROW,
     CAN_THROW;
-
-    public static ThrowingInfo defaultForConstString(InternalOptions options) {
-      return options.isGeneratingClassFiles() ? NO_THROW : CAN_THROW;
-    }
-
-    public static ThrowingInfo defaultForInstruction(Instruction instruction) {
-      return instruction.instructionTypeCanThrow() ? CAN_THROW : NO_THROW;
-    }
   }
 
   public enum EdgeType {
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 60d87b7..f7a77af 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
@@ -9,7 +9,6 @@
 import static com.android.tools.r8.ir.analysis.type.Nullability.maybeNull;
 import static com.android.tools.r8.ir.code.DominatorTree.Assumption.MAY_HAVE_UNREACHABLE_BLOCKS;
 
-import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DebugLocalInfo;
 import com.android.tools.r8.graph.DexField;
@@ -326,8 +325,7 @@
       removeOrReplaceByDebugLocalRead();
       return true;
     }
-    DexMethod getClassMethod = appView.dexItemFactory().objectMembers.getClass;
-    replaceCurrentInstruction(new InvokeVirtual(getClassMethod, null, ImmutableList.of(receiver)));
+    replaceCurrentInstructionWithNullCheck(appView, receiver);
     return true;
   }
 
@@ -408,6 +406,20 @@
   }
 
   @Override
+  public void replaceCurrentInstructionWithNullCheck(AppView<?> appView, Value object) {
+    if (current == null) {
+      throw new IllegalStateException();
+    }
+
+    assert current.hasUnusedOutValue();
+    assert !block.hasCatchHandlers() || current.instructionTypeCanThrow();
+
+    DexMethod getClassMethod = appView.dexItemFactory().objectMembers.getClass;
+    replaceCurrentInstruction(
+        InvokeVirtual.builder().setMethod(getClassMethod).setSingleArgument(object).build());
+  }
+
+  @Override
   public void replaceCurrentInstructionWithStaticGet(
       AppView<?> appView, IRCode code, DexField field, Set<Value> affectedValues) {
     if (current == null) {
@@ -492,7 +504,7 @@
 
   @Override
   public void replaceCurrentInstructionWithThrowNull(
-      AppView<? extends AppInfoWithClassHierarchy> appView,
+      AppView<?> appView,
       IRCode code,
       ListIterator<BasicBlock> blockIterator,
       Set<BasicBlock> blocksToRemove,
@@ -574,7 +586,7 @@
                 // target.
                 return;
               }
-              if (!appView.appInfo().isSubtype(appView.dexItemFactory().npeType, guard)) {
+              if (appView.isSubtype(appView.dexItemFactory().npeType, guard).isFalse()) {
                 // TODO(christofferqa): Consider updating previous dominator tree instead of
                 //   rebuilding it from scratch.
                 DominatorTree dominatorTree = new DominatorTree(code, MAY_HAVE_UNREACHABLE_BLOCKS);
diff --git a/src/main/java/com/android/tools/r8/ir/code/ConstClass.java b/src/main/java/com/android/tools/r8/ir/code/ConstClass.java
index 716396d..05869bb 100644
--- a/src/main/java/com/android/tools/r8/ir/code/ConstClass.java
+++ b/src/main/java/com/android/tools/r8/ir/code/ConstClass.java
@@ -9,6 +9,7 @@
 import com.android.tools.r8.cf.code.CfConstClass;
 import com.android.tools.r8.dex.Constants;
 import com.android.tools.r8.graph.AccessControl;
+import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexType;
@@ -21,7 +22,6 @@
 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.shaking.AppInfoWithLiveness;
 
 public class ConstClass extends ConstInstruction {
 
@@ -34,6 +34,7 @@
 
   public ConstClass(Value dest, DexType clazz, boolean ignoreCompatRules) {
     super(dest);
+    assert !clazz.isPrimitiveType();
     this.clazz = clazz;
     this.ignoreCompatRules = ignoreCompatRules;
   }
@@ -133,8 +134,9 @@
       return true;
     }
 
-    assert appView.appInfo().hasLiveness();
-    AppView<AppInfoWithLiveness> appViewWithLiveness = appView.withLiveness();
+    assert appView.appInfo().hasClassHierarchy();
+    AppView<? extends AppInfoWithClassHierarchy> appViewWithClassHierarchy =
+        appView.withClassHierarchy();
 
     DexClass clazz = appView.definitionFor(baseType);
     // * Check that the class and its super types are present.
@@ -142,7 +144,8 @@
       return true;
     }
     // * Check that the class is accessible.
-    if (AccessControl.isClassAccessible(clazz, context, appViewWithLiveness).isPossiblyFalse()) {
+    if (AccessControl.isClassAccessible(clazz, context, appViewWithClassHierarchy)
+        .isPossiblyFalse()) {
       return true;
     }
     return false;
@@ -202,7 +205,7 @@
 
   @Override
   public AbstractValue getAbstractValue(
-      AppView<AppInfoWithLiveness> appView, ProgramMethod context) {
+      AppView<? extends AppInfoWithClassHierarchy> appView, ProgramMethod context) {
     if (!instructionMayHaveSideEffects(appView, context)) {
       return appView.abstractValueFactory().createSingleConstClassValue(clazz);
     }
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 4111b17..958816b 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
@@ -16,6 +16,7 @@
 import com.android.tools.r8.code.ConstWide32;
 import com.android.tools.r8.code.ConstWideHigh16;
 import com.android.tools.r8.dex.Constants;
+import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.ProgramMethod;
@@ -27,7 +28,6 @@
 import com.android.tools.r8.ir.analysis.value.AbstractValue;
 import com.android.tools.r8.ir.conversion.CfBuilder;
 import com.android.tools.r8.ir.conversion.DexBuilder;
-import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.utils.InternalOutputMode;
 import com.android.tools.r8.utils.NumberUtils;
 import java.util.Set;
@@ -340,7 +340,7 @@
 
   @Override
   public AbstractValue getAbstractValue(
-      AppView<AppInfoWithLiveness> appView, ProgramMethod context) {
+      AppView<? extends AppInfoWithClassHierarchy> appView, ProgramMethod context) {
     return appView.abstractValueFactory().createSingleNumberValue(value);
   }
 }
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 a3efba8..3df5d13 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
@@ -9,6 +9,7 @@
 import com.android.tools.r8.cf.TypeVerificationHelper;
 import com.android.tools.r8.cf.code.CfConstString;
 import com.android.tools.r8.dex.Constants;
+import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexString;
 import com.android.tools.r8.graph.DexType;
@@ -20,7 +21,6 @@
 import com.android.tools.r8.ir.conversion.CfBuilder;
 import com.android.tools.r8.ir.conversion.DexBuilder;
 import com.android.tools.r8.ir.optimize.DeadCodeRemover.DeadInstructionResult;
-import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import java.io.UTFDataFormatException;
 
 public class ConstString extends ConstInstruction {
@@ -164,7 +164,7 @@
 
   @Override
   public AbstractValue getAbstractValue(
-      AppView<AppInfoWithLiveness> appView, ProgramMethod context) {
+      AppView<? extends AppInfoWithClassHierarchy> appView, ProgramMethod context) {
     if (!instructionInstanceCanThrow()) {
       return appView.abstractValueFactory().createSingleStringValue(value);
     }
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 4dc3985..b7820c1 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
@@ -7,6 +7,7 @@
 import com.android.tools.r8.cf.TypeVerificationHelper;
 import com.android.tools.r8.cf.code.CfDexItemBasedConstString;
 import com.android.tools.r8.dex.Constants;
+import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexReference;
 import com.android.tools.r8.graph.DexType;
@@ -20,7 +21,6 @@
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
 import com.android.tools.r8.ir.optimize.InliningConstraints;
 import com.android.tools.r8.naming.dexitembasedstring.NameComputationInfo;
-import com.android.tools.r8.shaking.AppInfoWithLiveness;
 
 public class DexItemBasedConstString extends ConstInstruction {
 
@@ -165,7 +165,7 @@
 
   @Override
   public AbstractValue getAbstractValue(
-      AppView<AppInfoWithLiveness> appView, ProgramMethod context) {
+      AppView<? extends AppInfoWithClassHierarchy> appView, ProgramMethod context) {
     return appView
         .abstractValueFactory()
         .createSingleDexItemBasedStringValue(item, nameComputationInfo);
diff --git a/src/main/java/com/android/tools/r8/ir/code/FieldGet.java b/src/main/java/com/android/tools/r8/ir/code/FieldGet.java
index aecb3eb..ec663fb 100644
--- a/src/main/java/com/android/tools/r8/ir/code/FieldGet.java
+++ b/src/main/java/com/android/tools/r8/ir/code/FieldGet.java
@@ -5,10 +5,15 @@
 package com.android.tools.r8.ir.code;
 
 import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
 
 public interface FieldGet {
 
   DexField getField();
 
+  TypeElement getOutType();
+
+  boolean hasUsedOutValue();
+
   Value outValue();
 }
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 c65bccc..b8eb09d 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
@@ -3,6 +3,9 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.ir.code;
 
+import static com.android.tools.r8.shaking.ObjectAllocationInfoCollectionUtils.mayHaveFinalizeMethodDirectlyOrIndirectly;
+
+import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexEncodedField;
@@ -199,17 +202,17 @@
       DexItemFactory dexItemFactory = appView.dexItemFactory();
       DexEncodedMethod resolutionResult =
           appInfo
-              .resolveMethodOnClass(dexItemFactory.objectMembers.finalize, clazz)
+              .resolveMethodOnClass(clazz, dexItemFactory.objectMembers.finalize)
               .getSingleTarget();
       return resolutionResult != null && resolutionResult.isProgramMethod(appView);
     }
 
-    return appInfo.mayHaveFinalizeMethodDirectlyOrIndirectly(baseType.asClassType());
+    return mayHaveFinalizeMethodDirectlyOrIndirectly(appView, baseType.asClassType());
   }
 
   @Override
   public AbstractValue getAbstractValue(
-      AppView<AppInfoWithLiveness> appView, ProgramMethod context) {
+      AppView<? extends AppInfoWithClassHierarchy> appView, ProgramMethod context) {
     assert isFieldGet();
     DexEncodedField field = appView.appInfo().resolveField(getField()).getResolvedField();
     if (field != null) {
diff --git a/src/main/java/com/android/tools/r8/ir/code/IRCode.java b/src/main/java/com/android/tools/r8/ir/code/IRCode.java
index 6a237d0..01b08a8 100644
--- a/src/main/java/com/android/tools/r8/ir/code/IRCode.java
+++ b/src/main/java/com/android/tools/r8/ir/code/IRCode.java
@@ -21,6 +21,8 @@
 import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.code.Phi.RegisterReadType;
 import com.android.tools.r8.ir.conversion.IRBuilder;
+import com.android.tools.r8.ir.conversion.MethodConversionOptions;
+import com.android.tools.r8.ir.conversion.MethodConversionOptions.MutableMethodConversionOptions;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.CfgPrinter;
@@ -110,6 +112,7 @@
   public static final int INSTRUCTION_NUMBER_DELTA = 2;
 
   private final ProgramMethod method;
+  private final MutableMethodConversionOptions conversionOptions;
 
   public LinkedList<BasicBlock> blocks;
   public final NumberGenerator valueNumberGenerator;
@@ -135,11 +138,13 @@
       NumberGenerator valueNumberGenerator,
       NumberGenerator basicBlockNumberGenerator,
       IRMetadata metadata,
-      Origin origin) {
+      Origin origin,
+      MutableMethodConversionOptions conversionOptions) {
     assert metadata != null;
     assert options != null;
     assert blocks.size() == basicBlockNumberGenerator.peek();
     this.options = options;
+    this.conversionOptions = conversionOptions;
     this.method = method;
     this.blocks = blocks;
     this.valueNumberGenerator = valueNumberGenerator;
@@ -167,6 +172,14 @@
     return blocks.getFirst();
   }
 
+  public MethodConversionOptions getConversionOptions() {
+    return conversionOptions;
+  }
+
+  public void mutateConversionOptions(Consumer<MutableMethodConversionOptions> mutator) {
+    mutator.accept(conversionOptions);
+  }
+
   /**
    * Compute the set of live values at the entry to each block using a backwards data-flow analysis.
    */
@@ -574,15 +587,15 @@
     }
   }
 
-  public boolean isConsistentSSA() {
-    isConsistentSSABeforeTypesAreCorrect();
+  public boolean isConsistentSSA(AppView<?> appView) {
+    isConsistentSSABeforeTypesAreCorrect(appView);
     assert verifyNoImpreciseOrBottomTypes();
     return true;
   }
 
-  public boolean isConsistentSSABeforeTypesAreCorrect() {
-    assert isConsistentGraph(true);
-    assert consistentBlockInstructions(true);
+  public boolean isConsistentSSABeforeTypesAreCorrect(AppView<?> appView) {
+    assert isConsistentGraph(appView, true);
+    assert consistentBlockInstructions(appView, true);
     assert consistentDefUseChains();
     assert validThrowingInstructions();
     assert noCriticalEdges();
@@ -615,16 +628,16 @@
     return true;
   }
 
-  public boolean isConsistentGraph() {
-    return isConsistentGraph(false);
+  public boolean isConsistentGraph(AppView<?> appView) {
+    return isConsistentGraph(appView, false);
   }
 
-  public boolean isConsistentGraph(boolean ssa) {
+  public boolean isConsistentGraph(AppView<?> appView, boolean ssa) {
     assert noColorsInUse();
     assert consistentBlockNumbering();
     assert consistentPredecessorSuccessors();
     assert consistentCatchHandlers();
-    assert consistentBlockInstructions(ssa);
+    assert consistentBlockInstructions(appView, ssa);
     assert consistentMetadata();
     assert !allThrowingInstructionsHavePositions || computeAllThrowingInstructionsHavePositions();
     return true;
@@ -816,12 +829,12 @@
     return true;
   }
 
-  private boolean consistentBlockInstructions(boolean ssa) {
+  private boolean consistentBlockInstructions(AppView<?> appView, boolean ssa) {
     boolean argumentsAllowed = true;
     for (BasicBlock block : blocks) {
       assert block.consistentBlockInstructions(
           argumentsAllowed,
-          options.debug || method().getOptimizationInfo().isReachabilitySensitive(),
+          options.debug || context().getOrComputeReachabilitySensitive(appView),
           ssa);
       argumentsAllowed = false;
     }
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 21b9df5..9125869 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
@@ -5,7 +5,6 @@
 package com.android.tools.r8.ir.code;
 
 import com.android.tools.r8.errors.Unimplemented;
-import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DebugLocalInfo;
 import com.android.tools.r8.graph.DexField;
@@ -90,6 +89,11 @@
   }
 
   @Override
+  public void replaceCurrentInstructionWithNullCheck(AppView<?> appView, Value object) {
+    instructionIterator.replaceCurrentInstructionWithNullCheck(appView, object);
+  }
+
+  @Override
   public void replaceCurrentInstructionWithStaticGet(
       AppView<?> appView, IRCode code, DexField field, Set<Value> affectedValues) {
     instructionIterator.replaceCurrentInstructionWithStaticGet(
@@ -114,7 +118,7 @@
 
   @Override
   public void replaceCurrentInstructionWithThrowNull(
-      AppView<? extends AppInfoWithClassHierarchy> appView,
+      AppView<?> appView,
       IRCode code,
       ListIterator<BasicBlock> blockIterator,
       Set<BasicBlock> blocksToRemove,
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 e0ca6cd..68d7ffc 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
@@ -26,6 +26,7 @@
 import com.android.tools.r8.ir.analysis.ClassInitializationAnalysis.Query;
 import com.android.tools.r8.ir.conversion.CfBuilder;
 import com.android.tools.r8.ir.conversion.DexBuilder;
+import com.android.tools.r8.ir.conversion.MethodConversionOptions;
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
 import com.android.tools.r8.ir.optimize.InliningConstraints;
 import com.android.tools.r8.ir.regalloc.RegisterAllocator;
@@ -153,8 +154,9 @@
   }
 
   @Override
-  public boolean identicalAfterRegisterAllocation(Instruction other, RegisterAllocator allocator) {
-    if (!super.identicalAfterRegisterAllocation(other, allocator)) {
+  public boolean identicalAfterRegisterAllocation(
+      Instruction other, RegisterAllocator allocator, MethodConversionOptions conversionOptions) {
+    if (!super.identicalAfterRegisterAllocation(other, allocator, conversionOptions)) {
       return false;
     }
 
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 ec25297..feaca12 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
@@ -7,6 +7,7 @@
 import com.android.tools.r8.cf.TypeVerificationHelper;
 import com.android.tools.r8.errors.Unimplemented;
 import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DebugLocalInfo;
 import com.android.tools.r8.graph.DexItemFactory;
@@ -26,12 +27,14 @@
 import com.android.tools.r8.ir.analysis.value.UnknownValue;
 import com.android.tools.r8.ir.conversion.CfBuilder;
 import com.android.tools.r8.ir.conversion.DexBuilder;
+import com.android.tools.r8.ir.conversion.MethodConversionOptions;
 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.ir.regalloc.RegisterAllocator;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.utils.CfgPrinter;
+import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.StringUtils;
 import com.android.tools.r8.utils.StringUtils.BraceType;
 import com.google.common.collect.ImmutableSet;
@@ -178,7 +181,7 @@
   }
 
   public AbstractValue getAbstractValue(
-      AppView<AppInfoWithLiveness> appView, ProgramMethod context) {
+      AppView<? extends AppInfoWithClassHierarchy> appView, ProgramMethod context) {
     assert hasOutValue();
     return UnknownValue.getInstance();
   }
@@ -501,7 +504,8 @@
     return a.outType() == b.outType();
   }
 
-  public boolean identicalAfterRegisterAllocation(Instruction other, RegisterAllocator allocator) {
+  public boolean identicalAfterRegisterAllocation(
+      Instruction other, RegisterAllocator allocator, MethodConversionOptions conversionOptions) {
     if (other.getClass() != getClass()) {
       return false;
     }
@@ -541,8 +545,10 @@
       }
     }
     // Finally check that the dex instructions for the generated code actually are the same.
-    if (allocator.options().isGeneratingDex()
-        && !DexBuilder.identicalInstructionsAfterBuildingDexCode(this, other, allocator)) {
+    InternalOptions options = allocator.options();
+    if (conversionOptions.isGeneratingDex()
+        && !DexBuilder.identicalInstructionsAfterBuildingDexCode(
+            this, other, allocator, conversionOptions)) {
       return false;
     }
     return true;
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 f1eab89..5055072 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
@@ -5,7 +5,6 @@
 package com.android.tools.r8.ir.code;
 
 import com.android.tools.r8.errors.Unimplemented;
-import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DebugLocalInfo;
 import com.android.tools.r8.graph.DexField;
@@ -144,6 +143,8 @@
         appView, code, appView.dexItemFactory().createString(value));
   }
 
+  void replaceCurrentInstructionWithNullCheck(AppView<?> appView, Value object);
+
   void replaceCurrentInstructionWithStaticGet(
       AppView<?> appView, IRCode code, DexField field, Set<Value> affectedValues);
 
@@ -170,7 +171,7 @@
    * @param affectedValues set passed where values depending on detached blocks will be added.
    */
   void replaceCurrentInstructionWithThrowNull(
-      AppView<? extends AppInfoWithClassHierarchy> appView,
+      AppView<?> appView,
       IRCode code,
       ListIterator<BasicBlock> blockIterator,
       Set<BasicBlock> blocksToRemove,
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 06173d4..1d259f6 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
@@ -9,6 +9,7 @@
 import com.android.tools.r8.cf.LoadStoreHelper;
 import com.android.tools.r8.cf.TypeVerificationHelper;
 import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexClassAndMethod;
 import com.android.tools.r8.graph.DexEncodedMethod;
@@ -26,6 +27,7 @@
 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.UnknownValue;
+import com.android.tools.r8.ir.conversion.MethodConversionOptions;
 import com.android.tools.r8.ir.optimize.DefaultInliningOracle;
 import com.android.tools.r8.ir.optimize.Inliner.InlineAction;
 import com.android.tools.r8.ir.optimize.Inliner.Reason;
@@ -187,8 +189,9 @@
       WhyAreYouNotInliningReporter whyAreYouNotInliningReporter);
 
   @Override
-  public boolean identicalAfterRegisterAllocation(Instruction other, RegisterAllocator allocator) {
-    if (!super.identicalAfterRegisterAllocation(other, allocator)) {
+  public boolean identicalAfterRegisterAllocation(
+      Instruction other, RegisterAllocator allocator, MethodConversionOptions conversionOptions) {
+    if (!super.identicalAfterRegisterAllocation(other, allocator, conversionOptions)) {
       return false;
     }
 
@@ -245,7 +248,7 @@
 
   @Override
   public AbstractValue getAbstractValue(
-      AppView<AppInfoWithLiveness> appView, ProgramMethod context) {
+      AppView<? extends AppInfoWithClassHierarchy> appView, ProgramMethod context) {
     assert hasOutValue();
     DexClassAndMethod method = lookupSingleTarget(appView, context);
     if (method != null) {
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeMethodWithReceiver.java b/src/main/java/com/android/tools/r8/ir/code/InvokeMethodWithReceiver.java
index d01bcaa..cdf992b 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokeMethodWithReceiver.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokeMethodWithReceiver.java
@@ -3,6 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.ir.code;
 
+import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexClassAndMethod;
@@ -192,11 +193,12 @@
       return true;
     }
 
-    assert appView.appInfo().hasLiveness();
-    AppView<AppInfoWithLiveness> appViewWithLiveness = appView.withLiveness();
+    assert appView.appInfo().hasClassHierarchy();
+    AppView<? extends AppInfoWithClassHierarchy> appViewWithClassHierarchy =
+        appView.withClassHierarchy();
 
     SingleResolutionResult resolutionResult =
-        appViewWithLiveness
+        appViewWithClassHierarchy
             .appInfo()
             .resolveMethod(getInvokedMethod(), getInterfaceBit())
             .asSingleResolution();
@@ -205,9 +207,7 @@
     }
 
     // Verify that the target method is accessible in the current context.
-    if (resolutionResult
-        .isAccessibleFrom(context, appViewWithLiveness.appInfo())
-        .isPossiblyFalse()) {
+    if (resolutionResult.isAccessibleFrom(context, appViewWithClassHierarchy).isPossiblyFalse()) {
       return true;
     }
 
@@ -215,14 +215,16 @@
       return false;
     }
 
-    DexEncodedMethod resolvedMethod = resolutionResult.getResolvedMethod();
-    if (appViewWithLiveness.appInfo().noSideEffects.containsKey(getInvokedMethod())
-        || appViewWithLiveness.appInfo().noSideEffects.containsKey(resolvedMethod.getReference())) {
-      return false;
+    DexClassAndMethod resolvedMethod = resolutionResult.getResolutionPair();
+    if (appView.hasLiveness()) {
+      if (appView.appInfoWithLiveness().isAssumeNoSideEffectsMethod(getInvokedMethod())
+          || appView.appInfoWithLiveness().isAssumeNoSideEffectsMethod(resolvedMethod)) {
+        return false;
+      }
     }
 
     // Find the target and check if the invoke may have side effects.
-    DexClassAndMethod singleTarget = lookupSingleTarget(appViewWithLiveness, context);
+    DexClassAndMethod singleTarget = lookupSingleTarget(appView, context);
     if (singleTarget == null) {
       return true;
     }
@@ -235,8 +237,10 @@
     }
 
     // Verify that the target method does not have side-effects.
-    if (appViewWithLiveness.appInfo().noSideEffects.containsKey(singleTarget.getReference())) {
-      return false;
+    if (appView.hasLiveness()) {
+      if (appView.appInfoWithLiveness().isAssumeNoSideEffectsMethod(singleTarget)) {
+        return false;
+      }
     }
 
     DexEncodedMethod singleTargetDefinition = singleTarget.getDefinition();
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 63df06a..e750ab3 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
@@ -8,6 +8,7 @@
 import com.android.tools.r8.cf.code.CfMultiANewArray;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.AccessControl;
+import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexType;
@@ -18,7 +19,6 @@
 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.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.utils.LongInterval;
 import java.util.List;
 
@@ -134,8 +134,9 @@
       return true;
     }
 
-    assert appView.appInfo().hasLiveness();
-    AppView<AppInfoWithLiveness> appViewWithLiveness = appView.withLiveness();
+    assert appView.appInfo().hasClassHierarchy();
+    AppView<? extends AppInfoWithClassHierarchy> appViewWithClassHierarchy =
+        appView.withClassHierarchy();
 
     // Check if the type is guaranteed to be present.
     DexClass clazz = appView.definitionFor(baseType);
@@ -149,7 +150,8 @@
     }
 
     // Check if the type is guaranteed to be accessible.
-    if (AccessControl.isClassAccessible(clazz, context, appViewWithLiveness).isPossiblyFalse()) {
+    if (AccessControl.isClassAccessible(clazz, context, appViewWithClassHierarchy)
+        .isPossiblyFalse()) {
       return true;
     }
 
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeNewArray.java b/src/main/java/com/android/tools/r8/ir/code/InvokeNewArray.java
index 636468c..1b356e3 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokeNewArray.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokeNewArray.java
@@ -9,6 +9,7 @@
 import com.android.tools.r8.code.FilledNewArrayRange;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.AccessControl;
+import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexType;
@@ -22,7 +23,6 @@
 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.shaking.AppInfoWithLiveness;
 import java.util.List;
 
 public class InvokeNewArray extends Invoke {
@@ -144,7 +144,7 @@
 
   @Override
   public AbstractValue getAbstractValue(
-      AppView<AppInfoWithLiveness> appView, ProgramMethod context) {
+      AppView<? extends AppInfoWithClassHierarchy> appView, ProgramMethod context) {
     if (!instructionMayHaveSideEffects(appView, context)) {
       int size = inValues.size();
       return StatefulObjectValue.create(
@@ -175,8 +175,9 @@
       return true;
     }
 
-    assert appView.appInfo().hasLiveness();
-    AppView<AppInfoWithLiveness> appViewWithLiveness = appView.withLiveness();
+    assert appView.appInfo().hasClassHierarchy();
+    AppView<? extends AppInfoWithClassHierarchy> appViewWithClassHierarchy =
+        appView.withClassHierarchy();
 
     // Check if the type is guaranteed to be present.
     DexClass clazz = appView.definitionFor(baseType);
@@ -191,7 +192,8 @@
     }
 
     // Check if the type is guaranteed to be accessible.
-    if (AccessControl.isClassAccessible(clazz, context, appViewWithLiveness).isPossiblyFalse()) {
+    if (AccessControl.isClassAccessible(clazz, context, appViewWithClassHierarchy)
+        .isPossiblyFalse()) {
       return true;
     }
 
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 ffb5d2b..093faf1 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
@@ -4,7 +4,6 @@
 
 package com.android.tools.r8.ir.code;
 
-import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DebugLocalInfo;
 import com.android.tools.r8.graph.DexField;
@@ -114,6 +113,11 @@
   }
 
   @Override
+  public void replaceCurrentInstructionWithNullCheck(AppView<?> appView, Value object) {
+    currentBlockIterator.replaceCurrentInstructionWithNullCheck(appView, object);
+  }
+
+  @Override
   public void replaceCurrentInstructionWithStaticGet(
       AppView<?> appView, IRCode code, DexField field, Set<Value> affectedValues) {
     currentBlockIterator.replaceCurrentInstructionWithStaticGet(
@@ -134,7 +138,7 @@
 
   @Override
   public void replaceCurrentInstructionWithThrowNull(
-      AppView<? extends AppInfoWithClassHierarchy> appView,
+      AppView<?> appView,
       IRCode code,
       ListIterator<BasicBlock> blockIterator,
       Set<BasicBlock> blocksToRemove,
diff --git a/src/main/java/com/android/tools/r8/ir/code/MoveException.java b/src/main/java/com/android/tools/r8/ir/code/MoveException.java
index dd74687..efec2dd 100644
--- a/src/main/java/com/android/tools/r8/ir/code/MoveException.java
+++ b/src/main/java/com/android/tools/r8/ir/code/MoveException.java
@@ -84,8 +84,8 @@
   public DeadInstructionResult canBeDeadCode(AppView<?> appView, IRCode code) {
     InternalOptions options = appView.options();
     if (options.debug
-        || code.context().getDefinition().getOptimizationInfo().isReachabilitySensitive()
-        || options.isGeneratingClassFiles()) {
+        || code.context().getOrComputeReachabilitySensitive(appView)
+        || code.getConversionOptions().isGeneratingClassFiles()) {
       return DeadInstructionResult.notDead();
     }
     return DeadInstructionResult.deadIfOutValueIsDead();
diff --git a/src/main/java/com/android/tools/r8/ir/code/NewArrayEmpty.java b/src/main/java/com/android/tools/r8/ir/code/NewArrayEmpty.java
index 3d6fb7d..500766c 100644
--- a/src/main/java/com/android/tools/r8/ir/code/NewArrayEmpty.java
+++ b/src/main/java/com/android/tools/r8/ir/code/NewArrayEmpty.java
@@ -8,6 +8,7 @@
 import com.android.tools.r8.cf.code.CfNewArray;
 import com.android.tools.r8.code.NewArray;
 import com.android.tools.r8.dex.Constants;
+import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.ProgramMethod;
@@ -21,7 +22,6 @@
 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.shaking.AppInfoWithLiveness;
 
 public class NewArrayEmpty extends Instruction {
 
@@ -88,7 +88,7 @@
 
   @Override
   public AbstractValue getAbstractValue(
-      AppView<AppInfoWithLiveness> appView, ProgramMethod context) {
+      AppView<? extends AppInfoWithClassHierarchy> appView, ProgramMethod context) {
     if (!instructionMayHaveSideEffects(appView, context) && size().getType().isInt()) {
       assert !instructionInstanceCanThrow();
       return StatefulObjectValue.create(
diff --git a/src/main/java/com/android/tools/r8/ir/code/NewArrayFilledData.java b/src/main/java/com/android/tools/r8/ir/code/NewArrayFilledData.java
index fb04f03..260689f 100644
--- a/src/main/java/com/android/tools/r8/ir/code/NewArrayFilledData.java
+++ b/src/main/java/com/android/tools/r8/ir/code/NewArrayFilledData.java
@@ -8,6 +8,7 @@
 import com.android.tools.r8.code.FillArrayDataPayload;
 import com.android.tools.r8.dex.Constants;
 import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.analysis.value.AbstractValue;
@@ -17,7 +18,6 @@
 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.shaking.AppInfoWithLiveness;
 import java.util.Arrays;
 
 public class NewArrayFilledData extends Instruction {
@@ -125,7 +125,7 @@
 
   @Override
   public AbstractValue getAbstractValue(
-      AppView<AppInfoWithLiveness> appView, ProgramMethod context) {
+      AppView<? extends AppInfoWithClassHierarchy> appView, ProgramMethod context) {
     if (!instructionMayHaveSideEffects(appView, context) && size <= Integer.MAX_VALUE) {
       assert !instructionInstanceCanThrow();
       return StatefulObjectValue.create(
diff --git a/src/main/java/com/android/tools/r8/ir/code/NewInstance.java b/src/main/java/com/android/tools/r8/ir/code/NewInstance.java
index 3aff847..baae8e5 100644
--- a/src/main/java/com/android/tools/r8/ir/code/NewInstance.java
+++ b/src/main/java/com/android/tools/r8/ir/code/NewInstance.java
@@ -8,6 +8,7 @@
 import com.android.tools.r8.cf.code.CfNew;
 import com.android.tools.r8.dex.Constants;
 import com.android.tools.r8.graph.AccessControl;
+import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexItemFactory;
@@ -158,8 +159,9 @@
           && dexItemFactory.libraryClassesWithoutStaticInitialization.contains(clazz));
     }
 
-    assert appView.appInfo().hasLiveness();
-    AppView<AppInfoWithLiveness> appViewWithLiveness = appView.withLiveness();
+    assert appView.appInfo().hasClassHierarchy();
+    AppView<? extends AppInfoWithClassHierarchy> appViewWithClassHierarchy =
+        appView.withClassHierarchy();
 
     if (clazz.isPrimitiveType() || clazz.isArrayType()) {
       assert false : "Unexpected new-instance instruction with primitive or array type";
@@ -177,21 +179,22 @@
     }
 
     // Verify that the instruction does not lead to an IllegalAccessError.
-    if (AccessControl.isClassAccessible(definition, context, appViewWithLiveness)
+    if (AccessControl.isClassAccessible(definition, context, appViewWithClassHierarchy)
         .isPossiblyFalse()) {
       return true;
     }
 
     // Verify that the new-instance instruction won't lead to class initialization.
-    if (definition.classInitializationMayHaveSideEffectsInContext(appViewWithLiveness, context)) {
+    if (definition.classInitializationMayHaveSideEffectsInContext(
+        appViewWithClassHierarchy, context)) {
       return true;
     }
 
     // Verify that the object does not have a finalizer.
     MethodResolutionResult finalizeResolutionResult =
-        appViewWithLiveness
+        appViewWithClassHierarchy
             .appInfo()
-            .resolveMethodOnClass(dexItemFactory.objectMembers.finalize, clazz);
+            .resolveMethodOnClass(clazz, dexItemFactory.objectMembers.finalize);
     if (finalizeResolutionResult.isSingleResolution()) {
       DexMethod finalizeMethod = finalizeResolutionResult.getSingleTarget().getReference();
       if (finalizeMethod != dexItemFactory.enumMembers.finalize
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 b32a651..b28f281 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
@@ -25,6 +25,7 @@
 import com.android.tools.r8.ir.analysis.ClassInitializationAnalysis.Query;
 import com.android.tools.r8.ir.conversion.CfBuilder;
 import com.android.tools.r8.ir.conversion.DexBuilder;
+import com.android.tools.r8.ir.conversion.MethodConversionOptions;
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
 import com.android.tools.r8.ir.optimize.InliningConstraints;
 import com.android.tools.r8.ir.regalloc.RegisterAllocator;
@@ -149,8 +150,9 @@
   }
 
   @Override
-  public boolean identicalAfterRegisterAllocation(Instruction other, RegisterAllocator allocator) {
-    if (!super.identicalAfterRegisterAllocation(other, allocator)) {
+  public boolean identicalAfterRegisterAllocation(
+      Instruction other, RegisterAllocator allocator, MethodConversionOptions conversionOptions) {
+    if (!super.identicalAfterRegisterAllocation(other, allocator, conversionOptions)) {
       return false;
     }
 
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 cc59793..bb83120 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
@@ -846,7 +846,7 @@
       return UnknownValue.getInstance();
     }
 
-    return root.definition.getAbstractValue(appView.withLiveness(), context);
+    return root.definition.getAbstractValue(appView.withClassHierarchy(), context);
   }
 
   public boolean isDefinedByInstructionSatisfying(Predicate<Instruction> predicate) {
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/CfBuilder.java b/src/main/java/com/android/tools/r8/ir/conversion/CfBuilder.java
index e95d612..4039c58 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/CfBuilder.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/CfBuilder.java
@@ -144,7 +144,8 @@
     this.bytecodeMetadataBuilder = BytecodeMetadata.builder(bytecodeMetadataProvider);
   }
 
-  public CfCode build(DeadCodeRemover deadCodeRemover, MethodConversionOptions conversionOptions) {
+  public CfCode build(DeadCodeRemover deadCodeRemover) {
+    code.traceBlocks();
     computeInitializers();
     TypeVerificationHelper typeVerificationHelper = new TypeVerificationHelper(appView, code);
     typeVerificationHelper.computeVerificationTypes();
@@ -162,7 +163,7 @@
         reachedFixpoint = !phiOptimizations.optimize(code);
       }
     }
-    assert code.isConsistentSSA();
+    assert code.isConsistentSSA(appView);
     // Insert reads for uninitialized read blocks to ensure correct stack maps.
     Set<UninitializedThisLocalRead> uninitializedThisLocalReads =
         insertUninitializedThisLocalReads();
@@ -178,9 +179,9 @@
 
     loadStoreHelper.insertPhiMoves(registerAllocator);
 
-    if (conversionOptions.isPeepholeOptimizationsEnabled()) {
+    if (code.getConversionOptions().isPeepholeOptimizationsEnabled()) {
       for (int i = 0; i < PEEPHOLE_OPTIMIZATION_PASSES; i++) {
-        CodeRewriter.collapseTrivialGotos(code);
+        CodeRewriter.collapseTrivialGotos(appView, code);
         PeepholeOptimizer.removeIdenticalPredecessorBlocks(code, registerAllocator);
         PeepholeOptimizer.shareIdenticalBlockSuffix(
             code, registerAllocator, SUFFIX_SHARING_OVERHEAD);
@@ -189,7 +190,7 @@
 
     rewriteIincPatterns();
 
-    CodeRewriter.collapseTrivialGotos(code);
+    CodeRewriter.collapseTrivialGotos(appView, code);
     DexBuilder.removeRedundantDebugPositions(code);
     CfCode code = buildCfCode();
     assert verifyInvokeInterface(code, appView);
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/DexBuilder.java b/src/main/java/com/android/tools/r8/ir/conversion/DexBuilder.java
index c4ac3ff..2bd4cfa 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/DexBuilder.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/DexBuilder.java
@@ -93,6 +93,7 @@
   private final RegisterAllocator registerAllocator;
 
   private final InternalOptions options;
+  private final MethodConversionOptions conversionOptions;
 
   // List of information about switch payloads that have to be created at the end of the
   // dex code.
@@ -128,20 +129,23 @@
   public DexBuilder(
       IRCode ir,
       BytecodeMetadataProvider bytecodeMetadataProvider,
-      RegisterAllocator registerAllocator) {
-    this(ir, bytecodeMetadataProvider, registerAllocator, registerAllocator.options());
-    assert ir != null;
+      RegisterAllocator registerAllocator,
+      InternalOptions options) {
+    this(ir, bytecodeMetadataProvider, registerAllocator, options, ir.getConversionOptions());
   }
 
-  private DexBuilder(
+  public DexBuilder(
       IRCode ir,
       BytecodeMetadataProvider bytecodeMetadataProvider,
       RegisterAllocator registerAllocator,
-      InternalOptions options) {
+      InternalOptions options,
+      MethodConversionOptions conversionOptions) {
+    assert ir == null || conversionOptions == ir.getConversionOptions();
     this.ir = ir;
     this.bytecodeMetadataBuilder = BytecodeMetadata.builder(bytecodeMetadataProvider);
     this.registerAllocator = registerAllocator;
     this.options = options;
+    this.conversionOptions = conversionOptions;
     if (isBuildingForComparison()) {
       instructionToInfo = new Info[1];
     }
@@ -150,9 +154,15 @@
   public static boolean identicalInstructionsAfterBuildingDexCode(
       com.android.tools.r8.ir.code.Instruction a,
       com.android.tools.r8.ir.code.Instruction b,
-      RegisterAllocator allocator) {
+      RegisterAllocator allocator,
+      MethodConversionOptions conversionOptions) {
     DexBuilder builder =
-        new DexBuilder(null, BytecodeMetadataProvider.empty(), allocator, allocator.options());
+        new DexBuilder(
+            null,
+            BytecodeMetadataProvider.empty(),
+            allocator,
+            allocator.options(),
+            conversionOptions);
     Info infoA = buildInfoForComparison(a, builder);
     Info infoB = buildInfoForComparison(b, builder);
     return infoA.identicalInstructions(infoB, builder);
@@ -647,7 +657,8 @@
 
   public void addReturn(Return ret, Instruction dex) {
     if (nextBlock != null
-        && ret.identicalAfterRegisterAllocation(nextBlock.entry(), registerAllocator)) {
+        && ret.identicalAfterRegisterAllocation(
+            nextBlock.entry(), registerAllocator, conversionOptions)) {
       addNothing(ret);
     } else {
       add(ret, dex);
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/DexSourceCode.java b/src/main/java/com/android/tools/r8/ir/conversion/DexSourceCode.java
index 6dec27c..a8d0ccd 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/DexSourceCode.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/DexSourceCode.java
@@ -44,7 +44,6 @@
 import com.android.tools.r8.ir.code.CanonicalPositions;
 import com.android.tools.r8.ir.code.CatchHandlers;
 import com.android.tools.r8.ir.code.Position;
-import com.android.tools.r8.ir.code.Position.SourcePosition;
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.HashSet;
@@ -262,14 +261,8 @@
     // If this instruction has already been inlined then this.method must be the outermost caller.
     assert entry.callerPosition == null
         || entry.callerPosition.getOutermostCaller().getMethod() == originalMethod;
-
     return canonicalPositions.getCanonical(
-        SourcePosition.builder()
-            .setLine(entry.line)
-            .setFile(entry.sourceFile)
-            .setMethod(entry.method)
-            .setCallerPosition(canonicalPositions.canonicalizeCallerPosition(entry.callerPosition))
-            .build());
+        entry.toPosition(canonicalPositions::canonicalizeCallerPosition));
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java b/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java
index 4d1cfc2..b46ebad 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java
@@ -118,6 +118,7 @@
 import com.android.tools.r8.ir.code.ValueType;
 import com.android.tools.r8.ir.code.ValueTypeConstraint;
 import com.android.tools.r8.ir.code.Xor;
+import com.android.tools.r8.ir.conversion.MethodConversionOptions.MutableMethodConversionOptions;
 import com.android.tools.r8.naming.dexitembasedstring.NameComputationInfo;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.utils.AndroidApiLevel;
@@ -500,7 +501,7 @@
   }
 
   public boolean isDebugMode() {
-    return appView.options().debug || getMethod().getOptimizationInfo().isReachabilitySensitive();
+    return appView.options().debug || getProgramMethod().getOrComputeReachabilitySensitive(appView);
   }
 
   public Int2ReferenceSortedMap<BlockInfo> getCFG() {
@@ -606,7 +607,7 @@
    * @param context Under what context this IRCode is built. Either the current method or caller.
    * @return The list of basic blocks. First block is the main entry.
    */
-  public IRCode build(ProgramMethod context) {
+  public IRCode build(ProgramMethod context, MutableMethodConversionOptions conversionOptions) {
     assert source != null;
     source.setUp();
 
@@ -706,7 +707,8 @@
             valueNumberGenerator,
             basicBlockNumberGenerator,
             metadata,
-            origin);
+            origin,
+            conversionOptions);
 
     // Verify critical edges are split so we have a place to insert phi moves if necessary.
     assert ir.verifySplitCriticalEdges();
@@ -733,11 +735,11 @@
       new TypeAnalysis(appView).narrowing(ir);
     }
 
-    if (appView.options().isStringSwitchConversionEnabled()) {
+    if (conversionOptions.isStringSwitchConversionEnabled()) {
       StringSwitchConverter.convertToStringSwitchInstructions(ir, appView.dexItemFactory());
     }
 
-    assert ir.isConsistentSSA();
+    assert ir.isConsistentSSA(appView);
 
     // Clear the code so we don't build multiple times.
     source.clear();
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 3da2a7e..acadf11 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
@@ -11,16 +11,13 @@
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.AppInfo;
 import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.CfCode;
 import com.android.tools.r8.graph.Code;
 import com.android.tools.r8.graph.DexApplication;
 import com.android.tools.r8.graph.DexApplication.Builder;
 import com.android.tools.r8.graph.DexEncodedMethod;
-import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexString;
-import com.android.tools.r8.graph.DexTypeList;
 import com.android.tools.r8.graph.GraphLens;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.graph.PrunedItems;
@@ -33,18 +30,8 @@
 import com.android.tools.r8.ir.analysis.fieldvalueanalysis.InstanceFieldValueAnalysis;
 import com.android.tools.r8.ir.analysis.fieldvalueanalysis.StaticFieldValueAnalysis;
 import com.android.tools.r8.ir.analysis.fieldvalueanalysis.StaticFieldValues;
-import com.android.tools.r8.ir.analysis.type.TypeElement;
-import com.android.tools.r8.ir.code.AlwaysMaterializingDefinition;
-import com.android.tools.r8.ir.code.AlwaysMaterializingUser;
 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.InvokeStatic;
-import com.android.tools.r8.ir.code.NumericType;
-import com.android.tools.r8.ir.code.Value;
-import com.android.tools.r8.ir.conversion.MethodConversionOptions.DefaultMethodConversionOptions;
-import com.android.tools.r8.ir.conversion.MethodConversionOptions.MutableMethodConversionOptions;
 import com.android.tools.r8.ir.desugar.CfClassSynthesizerDesugaringCollection;
 import com.android.tools.r8.ir.desugar.CfClassSynthesizerDesugaringEventConsumer;
 import com.android.tools.r8.ir.desugar.CfInstructionDesugaringCollection;
@@ -75,7 +62,6 @@
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
 import com.android.tools.r8.ir.optimize.MemberValuePropagation;
 import com.android.tools.r8.ir.optimize.NaturalIntLoopRemover;
-import com.android.tools.r8.ir.optimize.PeepholeOptimizer;
 import com.android.tools.r8.ir.optimize.RedundantFieldLoadAndStoreElimination;
 import com.android.tools.r8.ir.optimize.ReflectionOptimizer;
 import com.android.tools.r8.ir.optimize.ServiceLoaderRewriter;
@@ -92,8 +78,6 @@
 import com.android.tools.r8.ir.optimize.outliner.Outliner;
 import com.android.tools.r8.ir.optimize.string.StringBuilderOptimizer;
 import com.android.tools.r8.ir.optimize.string.StringOptimizer;
-import com.android.tools.r8.ir.regalloc.LinearScanRegisterAllocator;
-import com.android.tools.r8.ir.regalloc.RegisterAllocator;
 import com.android.tools.r8.logging.Log;
 import com.android.tools.r8.naming.IdentifierNameStringMarker;
 import com.android.tools.r8.optimize.argumentpropagation.ArgumentPropagator;
@@ -123,13 +107,10 @@
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.atomic.AtomicBoolean;
-import java.util.function.Supplier;
 import java.util.stream.Collectors;
 
 public class IRConverter {
 
-  private static final int PEEPHOLE_OPTIMIZATION_PASSES = 2;
-
   public final AppView<?> appView;
 
   private final Timing timing;
@@ -487,7 +468,6 @@
       D8CfInstructionDesugaringEventConsumer desugaringEventConsumer,
       D8MethodProcessor methodProcessor,
       InterfaceProcessor interfaceProcessor) {
-    boolean isReachabilitySensitive = clazz.hasReachabilitySensitiveAnnotation(options.itemFactory);
     // When converting all methods on a class always convert <clinit> first.
     ProgramMethod classInitializer = clazz.getProgramClassInitializer();
 
@@ -498,17 +478,12 @@
     //  the need to copy the method list.
     List<ProgramMethod> methods = ListUtils.newArrayList(clazz::forEachProgramMethod);
     if (classInitializer != null) {
-      classInitializer
-          .getDefinition()
-          .getMutableOptimizationInfo()
-          .setReachabilitySensitive(isReachabilitySensitive);
       methodProcessor.processMethod(classInitializer, desugaringEventConsumer);
     }
 
     for (ProgramMethod method : methods) {
       if (!method.getDefinition().isClassInitializer()) {
         DexEncodedMethod definition = method.getDefinition();
-        definition.getMutableOptimizationInfo().setReachabilitySensitive(isReachabilitySensitive);
         methodProcessor.processMethod(method, desugaringEventConsumer);
         if (interfaceProcessor != null) {
           interfaceProcessor.processMethod(method, desugaringEventConsumer);
@@ -642,9 +617,6 @@
     // Desugaring happens in the enqueuer.
     assert instructionDesugaring.isEmpty();
 
-    DexApplication application = appView.appInfo().app();
-
-    computeReachabilitySensitivity(application);
     workaroundAbstractMethodOnNonAbstractClassVerificationBug(executorService);
 
     // The process is in two phases in general.
@@ -849,6 +821,9 @@
       inliner.onLastWaveDone(postMethodProcessorBuilder, executorService, timing);
     }
     openClosedInterfacesAnalysis.onPrimaryOptimizationPassComplete();
+
+    // Ensure determinism of method-to-reprocess set.
+    appView.testing().checkDeterminism(postMethodProcessorBuilder::dump);
   }
 
   public void addWaveDoneAction(com.android.tools.r8.utils.Action action) {
@@ -865,18 +840,6 @@
     return onWaveDoneActions != null;
   }
 
-  private void computeReachabilitySensitivity(DexApplication application) {
-    application
-        .classes()
-        .forEach(
-            c -> {
-              if (c.hasReachabilitySensitiveAnnotation(options.itemFactory)) {
-                c.methods()
-                    .forEach(m -> m.getMutableOptimizationInfo().setReachabilitySensitive(true));
-              }
-            });
-  }
-
   private void processSynthesizedServiceLoaderMethods(
       List<ProgramMethod> serviceLoadMethods, ExecutorService executorService)
       throws ExecutionException {
@@ -901,28 +864,28 @@
 
   /**
    * This will replace the Dex code in the method with the Dex code generated from the provided IR.
-   * <p>
-   * This method is *only* intended for testing, where tests manipulate the IR and need runnable Dex
-   * code.
    *
-   * @param method the method to replace code for
+   * <p>This method is *only* intended for testing, where tests manipulate the IR and need runnable
+   * Dex code.
+   *
    * @param code the IR code for the method
    */
-  public void replaceCodeForTesting(DexEncodedMethod method, IRCode code) {
-    if (Log.ENABLED) {
-      Log.debug(getClass(), "Initial (SSA) flow graph for %s:\n%s", method.toSourceString(), code);
-    }
-    assert code.isConsistentSSA();
+  public void replaceCodeForTesting(IRCode code) {
+    ProgramMethod method = code.context();
+    DexEncodedMethod definition = method.getDefinition();
+    assert code.isConsistentSSA(appView);
     Timing timing = Timing.empty();
     deadCodeRemover.run(code, timing);
-    code.traceBlocks();
-    RegisterAllocator registerAllocator =
-        performRegisterAllocation(
-            code, method, DefaultMethodConversionOptions.getInstance(), timing);
-    method.setCode(code, BytecodeMetadataProvider.empty(), registerAllocator, appView);
+    method.setCode(
+        new IRToDexFinalizer(appView, deadCodeRemover)
+            .finalizeCode(code, BytecodeMetadataProvider.empty(), timing),
+        appView);
     if (Log.ENABLED) {
-      Log.debug(getClass(), "Resulting dex code for %s:\n%s",
-          method.toSourceString(), logCode(options, method));
+      Log.debug(
+          getClass(),
+          "Resulting dex code for %s:\n%s",
+          method.toSourceString(),
+          logCode(options, definition));
     }
   }
 
@@ -1094,8 +1057,6 @@
     ProgramMethod context = code.context();
     DexEncodedMethod method = context.getDefinition();
     DexProgramClass holder = context.getHolder();
-    MutableMethodConversionOptions conversionOptions =
-        new MutableMethodConversionOptions(methodProcessor);
     assert holder != null;
 
     Timing timing = Timing.create(context.toSourceString(), options);
@@ -1123,7 +1084,7 @@
       timing.end();
     }
 
-    boolean isDebugMode = options.debug || method.getOptimizationInfo().isReachabilitySensitive();
+    boolean isDebugMode = options.debug || context.getOrComputeReachabilitySensitive(appView);
 
     if (isDebugMode) {
       codeRewriter.simplifyDebugLocals(code);
@@ -1164,7 +1125,7 @@
     // check. In the latter case, the type checker should be extended to detect the issue such that
     // we will return with a throw-null method above.
     assert code.verifyTypes(appView);
-    assert code.isConsistentSSA();
+    assert code.isConsistentSSA(appView);
 
     openClosedInterfacesAnalysis.analyze(context, code);
 
@@ -1178,7 +1139,6 @@
           ClassInitializerDefaultsResult.empty(),
           feedback,
           methodProcessor,
-          conversionOptions,
           BytecodeMetadataProvider.builder(),
           timing);
       timing.end();
@@ -1199,7 +1159,7 @@
       timing.begin("Decouple identifier-name strings");
       identifierNameStringMarker.decoupleIdentifierNameStringsInMethod(code);
       timing.end();
-      assert code.isConsistentSSA();
+      assert code.isConsistentSSA(appView);
     }
 
     if (memberValuePropagation != null) {
@@ -1282,7 +1242,7 @@
           .optimize(code, feedback, methodProcessor, methodProcessingContext);
       timing.end();
       previous = printMethod(code, "IR after class library method optimizer (SSA)", previous);
-      assert code.isConsistentSSA();
+      assert code.isConsistentSSA(appView);
     }
 
     assert code.verifyTypes(appView);
@@ -1389,7 +1349,7 @@
     // as a result of those simplifications. The following optimizations could reveal more
     // dead code which is removed right before register allocation in performRegisterAllocation.
     deadCodeRemover.run(code, timing);
-    assert code.isConsistentSSA();
+    assert code.isConsistentSSA(appView);
 
     previous = printMethod(code, "IR after dead code removal (SSA)", previous);
 
@@ -1422,7 +1382,7 @@
                       // always uses a force inlining oracle for inlining.
                       -1)));
       timing.end();
-      assert code.isConsistentSSA();
+      assert code.isConsistentSSA(appView);
       assert code.verifyTypes(appView);
     }
 
@@ -1440,7 +1400,7 @@
 
     previous = printMethod(code, "IR after outline handler (SSA)", previous);
 
-    if (stringSwitchRemover != null) {
+    if (code.getConversionOptions().isStringSwitchConversionEnabled()) {
       // Remove string switches prior to canonicalization to ensure that the constants that are
       // being introduced will be canonicalized if possible.
       timing.begin("Remove string switch");
@@ -1474,7 +1434,7 @@
     // Insert code to log arguments if requested.
     if (options.methodMatchesLogArgumentsFilter(method) && !method.isProcessed()) {
       codeRewriter.logArgumentTypes(method, code);
-      assert code.isConsistentSSA();
+      assert code.isConsistentSSA(appView);
     }
 
     previous = printMethod(code, "IR after argument type logging (SSA)", previous);
@@ -1493,7 +1453,6 @@
           classInitializerDefaultsResult,
           feedback,
           methodProcessor,
-          conversionOptions,
           bytecodeMetadataProviderBuilder,
           timing);
       timing.end();
@@ -1503,7 +1462,7 @@
       timing.begin("Remove assume instructions");
       CodeRewriter.removeAssumeInstructions(appView, code);
       timing.end();
-      assert code.isConsistentSSA();
+      assert code.isConsistentSSA(appView);
 
       // TODO(b/214496607): Remove when dynamic types are safe w.r.t. interface assignment rules.
       codeRewriter.rewriteMoveResult(code);
@@ -1517,19 +1476,11 @@
     previous =
         printMethod(code, "IR after computation of optimization info summary (SSA)", previous);
 
-    if (options.canHaveNumberConversionRegisterAllocationBug()) {
-      timing.begin("Check number conversion issue");
-      codeRewriter.workaroundNumberConversionRegisterAllocationBug(code);
-      timing.end();
-    }
-
     printMethod(code, "Optimized IR (SSA)", previous);
     timing.begin("Finalize IR");
     finalizeIR(
-        context,
         code,
         feedback,
-        conversionOptions,
         bytecodeMetadataProviderBuilder.build(),
         timing);
     timing.end();
@@ -1553,14 +1504,13 @@
       ClassInitializerDefaultsResult classInitializerDefaultsResult,
       OptimizationFeedback feedback,
       MethodProcessor methodProcessor,
-      MutableMethodConversionOptions conversionOptions,
       BytecodeMetadataProvider.Builder bytecodeMetadataProviderBuilder,
       Timing timing) {
     appView.withArgumentPropagator(
         argumentPropagator -> argumentPropagator.scan(method, code, methodProcessor, timing));
 
     if (methodProcessor.isPrimaryMethodProcessor()) {
-      enumUnboxer.analyzeEnums(code, conversionOptions);
+      enumUnboxer.analyzeEnums(code, methodProcessor);
     }
 
     if (inliner != null) {
@@ -1624,69 +1574,51 @@
     }
     deadCodeRemover.run(code, timing);
     finalizeIR(
-        code.context(),
         code,
         feedback,
-        DefaultMethodConversionOptions.getInstance(),
         BytecodeMetadataProvider.empty(),
         timing);
   }
 
   public void finalizeIR(
-      ProgramMethod method,
       IRCode code,
       OptimizationFeedback feedback,
-      MethodConversionOptions conversionOptions,
       BytecodeMetadataProvider bytecodeMetadataProvider,
       Timing timing) {
-    code.traceBlocks();
     if (options.isGeneratingClassFiles()) {
-      finalizeToCf(code, feedback, conversionOptions, bytecodeMetadataProvider);
+      finalizeToCf(code, feedback, bytecodeMetadataProvider, timing);
     } else {
       assert options.isGeneratingDex();
-      finalizeToDex(code, feedback, conversionOptions, bytecodeMetadataProvider, timing);
+      finalizeToDex(code, feedback, bytecodeMetadataProvider, timing);
     }
   }
 
   private void finalizeToCf(
       IRCode code,
       OptimizationFeedback feedback,
-      MethodConversionOptions conversionOptions,
-      BytecodeMetadataProvider bytecodeMetadataProvider) {
+      BytecodeMetadataProvider bytecodeMetadataProvider,
+      Timing timing) {
     ProgramMethod method = code.context();
-    assert !method.getDefinition().getCode().isDexCode();
-    CfBuilder builder = new CfBuilder(appView, method, code, bytecodeMetadataProvider);
-    CfCode result = builder.build(deadCodeRemover, conversionOptions);
-    method.getDefinition().setCode(result, appView);
+    method.setCode(
+        new IRToCfFinalizer(appView, deadCodeRemover)
+            .finalizeCode(code, bytecodeMetadataProvider, timing),
+        appView);
     markProcessed(code, feedback);
   }
 
   private void finalizeToDex(
       IRCode code,
       OptimizationFeedback feedback,
-      MethodConversionOptions conversionOptions,
       BytecodeMetadataProvider bytecodeMetadataProvider,
       Timing timing) {
-    DexEncodedMethod method = code.method();
-    // Workaround massive dex2oat memory use for self-recursive methods.
-    CodeRewriter.disableDex2OatInliningForSelfRecursiveMethods(appView, code);
-    // Workaround MAX_INT switch issue.
-    codeRewriter.rewriteSwitchForMaxInt(code);
-    // Perform register allocation.
-    RegisterAllocator registerAllocator =
-        performRegisterAllocation(code, method, conversionOptions, timing);
-    timing.begin("Build DEX code");
-    method.setCode(code, bytecodeMetadataProvider, registerAllocator, appView);
-    timing.end();
-    updateHighestSortingStrings(method);
-    if (Log.ENABLED) {
-      Log.debug(getClass(), "Resulting dex code for %s:\n%s",
-          method.toSourceString(), logCode(options, method));
-    }
-    printMethod(code, "Final IR (non-SSA)", null);
-    timing.begin("Marking processed");
+    ProgramMethod method = code.context();
+    DexEncodedMethod definition = method.getDefinition();
+    method.setCode(
+        new IRToDexFinalizer(appView, deadCodeRemover)
+            .finalizeCode(code, bytecodeMetadataProvider, timing),
+        appView);
     markProcessed(code, feedback);
-    timing.end();
+    updateHighestSortingStrings(definition);
   }
 
   public void markProcessed(IRCode code, OptimizationFeedback feedback) {
@@ -1704,8 +1636,7 @@
       return false;
     }
     DexEncodedMethod definition = method.getDefinition();
-    if (definition.isClassInitializer()
-        || definition.getOptimizationInfo().isReachabilitySensitive()) {
+    if (definition.isClassInitializer() || method.getOrComputeReachabilitySensitive(appView)) {
       return false;
     }
     KeepMethodInfo keepInfo = appView.getKeepInfo(method);
@@ -1727,209 +1658,6 @@
     }
   }
 
-  private RegisterAllocator performRegisterAllocation(
-      IRCode code,
-      DexEncodedMethod method,
-      MethodConversionOptions conversionOptions,
-      Timing timing) {
-    // Always perform dead code elimination before register allocation. The register allocator
-    // does not allow dead code (to make sure that we do not waste registers for unneeded values).
-    assert deadCodeRemover.verifyNoDeadCode(code);
-    materializeInstructionBeforeLongOperationsWorkaround(code);
-    workaroundForwardingInitializerBug(code);
-    timing.begin("Allocate registers");
-    LinearScanRegisterAllocator registerAllocator = new LinearScanRegisterAllocator(appView, code);
-    registerAllocator.allocateRegisters();
-    timing.end();
-    if (options.canHaveExceptionTargetingLoopHeaderBug()) {
-      codeRewriter.workaroundExceptionTargetingLoopHeaderBug(code);
-    }
-    printMethod(code, "After register allocation (non-SSA)", null);
-    if (conversionOptions.isPeepholeOptimizationsEnabled()) {
-      timing.begin("Peephole optimize");
-      for (int i = 0; i < PEEPHOLE_OPTIMIZATION_PASSES; i++) {
-        CodeRewriter.collapseTrivialGotos(code);
-        PeepholeOptimizer.optimize(code, registerAllocator);
-      }
-      timing.end();
-    }
-    timing.begin("Clean up");
-    CodeRewriter.removeUnneededMovesOnExitingPaths(code, registerAllocator);
-    CodeRewriter.collapseTrivialGotos(code);
-    timing.end();
-    if (Log.ENABLED) {
-      Log.debug(getClass(), "Final (non-SSA) flow graph for %s:\n%s",
-          method.toSourceString(), code);
-    }
-    return registerAllocator;
-  }
-
-  private void workaroundForwardingInitializerBug(IRCode code) {
-    if (!options.canHaveForwardingInitInliningBug()) {
-      return;
-    }
-    // Only constructors.
-    if (!code.method().isInstanceInitializer()) {
-      return;
-    }
-    // Only constructors with certain signatures.
-    DexTypeList paramTypes = code.method().getReference().proto.parameters;
-    if (paramTypes.size() != 3 ||
-        paramTypes.values[0] != options.itemFactory.doubleType ||
-        paramTypes.values[1] != options.itemFactory.doubleType ||
-        !paramTypes.values[2].isClassType()) {
-      return;
-    }
-    // Only if the constructor contains a super constructor call taking only parameters as
-    // inputs.
-    for (BasicBlock block : code.blocks) {
-      InstructionListIterator it = block.listIterator(code);
-      Instruction superConstructorCall =
-          it.nextUntil(
-              (i) ->
-                  i.isInvokeDirect()
-                      && i.asInvokeDirect().getInvokedMethod().name
-                          == options.itemFactory.constructorMethodName
-                      && i.asInvokeDirect().arguments().size() == 4
-                      && i.asInvokeDirect().arguments().stream().allMatch(Value::isArgument));
-      if (superConstructorCall != null) {
-        // We force a materializing const instruction in front of the super call to make
-        // sure that there is at least one temporary register in the method. That disables
-        // the inlining that is crashing on these devices.
-        ensureInstructionBefore(code, superConstructorCall, it);
-        break;
-      }
-    }
-  }
-
-  /**
-   * For each block, we look to see if the header matches:
-   *
-   * <pre>
-   *   pseudo-instructions*
-   *   v2 <- long-{mul,div} v0 v1
-   *   pseudo-instructions*
-   *   v5 <- long-{add,sub} v3 v4
-   * </pre>
-   *
-   * where v2 ~=~ v3 or v2 ~=~ v4 (with ~=~ being equal or an alias of) and the block is not a
-   * fallthrough target.
-   */
-  private void materializeInstructionBeforeLongOperationsWorkaround(IRCode code) {
-    if (!options.canHaveDex2OatLinkedListBug()) {
-      return;
-    }
-    DexItemFactory factory = options.itemFactory;
-    final Supplier<DexMethod> javaLangLangSignum =
-        Suppliers.memoize(
-            () ->
-                factory.createMethod(
-                    factory.createString("Ljava/lang/Long;"),
-                    factory.createString("signum"),
-                    factory.intDescriptor,
-                    new DexString[] {factory.longDescriptor}));
-    for (BasicBlock block : code.blocks) {
-      InstructionListIterator it = block.listIterator(code);
-      Instruction firstMaterializing = it.nextUntil(IRConverter::isNotPseudoInstruction);
-      if (!isLongMul(firstMaterializing)) {
-        continue;
-      }
-      Instruction secondMaterializing = it.nextUntil(IRConverter::isNotPseudoInstruction);
-      if (!isLongAddOrSub(secondMaterializing)) {
-        continue;
-      }
-      if (isFallthoughTarget(block)) {
-        continue;
-      }
-      Value outOfMul = firstMaterializing.outValue();
-      for (Value inOfAddOrSub : secondMaterializing.inValues()) {
-        if (isAliasOf(inOfAddOrSub, outOfMul)) {
-          it = block.listIterator(code);
-          it.nextUntil(i -> i == firstMaterializing);
-          Value longValue = firstMaterializing.inValues().get(0);
-          InvokeStatic invokeLongSignum =
-              new InvokeStatic(
-                  javaLangLangSignum.get(), null, Collections.singletonList(longValue));
-          ensureThrowingInstructionBefore(code, firstMaterializing, it, invokeLongSignum);
-          return;
-        }
-      }
-    }
-  }
-
-  private static boolean isAliasOf(Value usedValue, Value definingValue) {
-    while (true) {
-      if (usedValue == definingValue) {
-        return true;
-      }
-      Instruction definition = usedValue.definition;
-      if (definition == null || !definition.isMove()) {
-        return false;
-      }
-      usedValue = definition.asMove().src();
-    }
-  }
-
-  private static boolean isNotPseudoInstruction(Instruction instruction) {
-    return !(instruction.isDebugInstruction() || instruction.isMove());
-  }
-
-  private static boolean isLongMul(Instruction instruction) {
-    return instruction != null
-        && instruction.isMul()
-        && instruction.asBinop().getNumericType() == NumericType.LONG
-        && instruction.outValue() != null;
-  }
-
-  private static boolean isLongAddOrSub(Instruction instruction) {
-    return instruction != null
-        && (instruction.isAdd() || instruction.isSub())
-        && instruction.asBinop().getNumericType() == NumericType.LONG;
-  }
-
-  private static boolean isFallthoughTarget(BasicBlock block) {
-    for (BasicBlock pred : block.getPredecessors()) {
-      if (pred.exit().fallthroughBlock() == block) {
-        return true;
-      }
-    }
-    return false;
-  }
-
-  private void ensureThrowingInstructionBefore(
-      IRCode code, Instruction addBefore, InstructionListIterator it, Instruction instruction) {
-    Instruction check = it.previous();
-    assert addBefore == check;
-    BasicBlock block = check.getBlock();
-    if (block.hasCatchHandlers()) {
-      // Split so the existing instructions retain their handlers and the new instruction has none.
-      BasicBlock split = it.split(code);
-      assert split.hasCatchHandlers();
-      assert !block.hasCatchHandlers();
-      it = block.listIterator(code, block.getInstructions().size() - 1);
-    }
-    instruction.setPosition(addBefore.getPosition());
-    it.add(instruction);
-  }
-
-  private static void ensureInstructionBefore(
-      IRCode code, Instruction addBefore, InstructionListIterator it) {
-    // Force materialize a constant-zero before the long operation.
-    Instruction check = it.previous();
-    assert addBefore == check;
-    // Forced definition of const-zero
-    Value fixitValue = code.createValue(TypeElement.getInt());
-    Instruction fixitDefinition = new AlwaysMaterializingDefinition(fixitValue);
-    fixitDefinition.setBlock(addBefore.getBlock());
-    fixitDefinition.setPosition(addBefore.getPosition());
-    it.add(fixitDefinition);
-    // Forced user of the forced definition to ensure it has a user and thus live range.
-    Instruction fixitUser = new AlwaysMaterializingUser(fixitValue);
-    fixitUser.setBlock(addBefore.getBlock());
-    fixitUser.setPosition(addBefore.getPosition());
-    it.add(fixitUser);
-  }
-
   private void printC1VisualizerHeader(DexEncodedMethod method) {
     if (printer != null) {
       printer.begin("compilation");
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/IRFinalizer.java b/src/main/java/com/android/tools/r8/ir/conversion/IRFinalizer.java
new file mode 100644
index 0000000..c0c6808
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/conversion/IRFinalizer.java
@@ -0,0 +1,26 @@
+// Copyright (c) 2022, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.conversion;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.Code;
+import com.android.tools.r8.graph.bytecodemetadata.BytecodeMetadataProvider;
+import com.android.tools.r8.ir.code.IRCode;
+import com.android.tools.r8.ir.optimize.DeadCodeRemover;
+import com.android.tools.r8.utils.Timing;
+
+public abstract class IRFinalizer<C extends Code> {
+
+  protected final AppView<?> appView;
+  protected final DeadCodeRemover deadCodeRemover;
+
+  public IRFinalizer(AppView<?> appView, DeadCodeRemover deadCodeRemover) {
+    this.appView = appView;
+    this.deadCodeRemover = deadCodeRemover;
+  }
+
+  public abstract C finalizeCode(
+      IRCode code, BytecodeMetadataProvider bytecodeMetadataProvider, Timing timing);
+}
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/IRToCfFinalizer.java b/src/main/java/com/android/tools/r8/ir/conversion/IRToCfFinalizer.java
new file mode 100644
index 0000000..74d0376
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/conversion/IRToCfFinalizer.java
@@ -0,0 +1,27 @@
+// Copyright (c) 2022, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.conversion;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.CfCode;
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.graph.bytecodemetadata.BytecodeMetadataProvider;
+import com.android.tools.r8.ir.code.IRCode;
+import com.android.tools.r8.ir.optimize.DeadCodeRemover;
+import com.android.tools.r8.utils.Timing;
+
+public class IRToCfFinalizer extends IRFinalizer<CfCode> {
+
+  public IRToCfFinalizer(AppView<?> appView, DeadCodeRemover deadCodeRemover) {
+    super(appView, deadCodeRemover);
+  }
+
+  @Override
+  public CfCode finalizeCode(
+      IRCode code, BytecodeMetadataProvider bytecodeMetadataProvider, Timing timing) {
+    ProgramMethod method = code.context();
+    return new CfBuilder(appView, method, code, bytecodeMetadataProvider).build(deadCodeRemover);
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/IRToDexFinalizer.java b/src/main/java/com/android/tools/r8/ir/conversion/IRToDexFinalizer.java
new file mode 100644
index 0000000..4155003
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/conversion/IRToDexFinalizer.java
@@ -0,0 +1,80 @@
+// Copyright (c) 2022, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.conversion;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexCode;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.bytecodemetadata.BytecodeMetadataProvider;
+import com.android.tools.r8.ir.code.IRCode;
+import com.android.tools.r8.ir.optimize.CodeRewriter;
+import com.android.tools.r8.ir.optimize.DeadCodeRemover;
+import com.android.tools.r8.ir.optimize.PeepholeOptimizer;
+import com.android.tools.r8.ir.optimize.RuntimeWorkaroundCodeRewriter;
+import com.android.tools.r8.ir.regalloc.LinearScanRegisterAllocator;
+import com.android.tools.r8.ir.regalloc.RegisterAllocator;
+import com.android.tools.r8.logging.Log;
+import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.Timing;
+
+public class IRToDexFinalizer extends IRFinalizer<DexCode> {
+
+  private static final int PEEPHOLE_OPTIMIZATION_PASSES = 2;
+
+  private final CodeRewriter codeRewriter;
+  private final InternalOptions options;
+
+  public IRToDexFinalizer(AppView<?> appView, DeadCodeRemover deadCodeRemover) {
+    super(appView, deadCodeRemover);
+    this.codeRewriter = deadCodeRemover.getCodeRewriter();
+    this.options = appView.options();
+  }
+
+  @Override
+  public DexCode finalizeCode(
+      IRCode code, BytecodeMetadataProvider bytecodeMetadataProvider, Timing timing) {
+    DexEncodedMethod method = code.method();
+    code.traceBlocks();
+    RuntimeWorkaroundCodeRewriter.workaroundNumberConversionRegisterAllocationBug(code, options);
+    // Workaround massive dex2oat memory use for self-recursive methods.
+    RuntimeWorkaroundCodeRewriter.workaroundDex2OatInliningIssue(appView, code);
+    // Workaround MAX_INT switch issue.
+    RuntimeWorkaroundCodeRewriter.workaroundSwitchMaxIntBug(code, codeRewriter, options);
+    RuntimeWorkaroundCodeRewriter.workaroundDex2OatLinkedListBug(code, options);
+    RuntimeWorkaroundCodeRewriter.workaroundForwardingInitializerBug(code, options);
+    RuntimeWorkaroundCodeRewriter.workaroundExceptionTargetingLoopHeaderBug(code, options);
+    // Perform register allocation.
+    RegisterAllocator registerAllocator = performRegisterAllocation(code, method, timing);
+    return new DexBuilder(code, bytecodeMetadataProvider, registerAllocator, options).build();
+  }
+
+  private RegisterAllocator performRegisterAllocation(
+      IRCode code, DexEncodedMethod method, Timing timing) {
+    // Always perform dead code elimination before register allocation. The register allocator
+    // does not allow dead code (to make sure that we do not waste registers for unneeded values).
+    assert deadCodeRemover.verifyNoDeadCode(code);
+    timing.begin("Allocate registers");
+    LinearScanRegisterAllocator registerAllocator = new LinearScanRegisterAllocator(appView, code);
+    registerAllocator.allocateRegisters();
+    timing.end();
+    if (code.getConversionOptions().isPeepholeOptimizationsEnabled()) {
+      timing.begin("Peephole optimize");
+      for (int i = 0; i < PEEPHOLE_OPTIMIZATION_PASSES; i++) {
+        CodeRewriter.collapseTrivialGotos(appView, code);
+        PeepholeOptimizer.optimize(appView, code, registerAllocator);
+      }
+      timing.end();
+    }
+    timing.begin("Clean up");
+    CodeRewriter.removeUnneededMovesOnExitingPaths(code, registerAllocator);
+    CodeRewriter.collapseTrivialGotos(appView, code);
+    timing.end();
+    if (Log.ENABLED) {
+      Log.debug(
+          getClass(), "Final (non-SSA) flow graph for %s:\n%s", method.toSourceString(), code);
+    }
+    return registerAllocator;
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java b/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java
index 309cf22..2154c8c 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java
@@ -667,7 +667,19 @@
               ConstClass constClass = current.asConstClass();
               new InstructionReplacer(code, current, iterator, affectedPhis)
                   .replaceInstructionIfTypeChanged(
-                      constClass.getValue(), (t, v) -> new ConstClass(v, t), graphLens, codeLens);
+                      constClass.getValue(),
+                      (t, v) ->
+                          t.isPrimitiveType() || t.isVoidType()
+                              ? StaticGet.builder()
+                                  .setField(
+                                      factory
+                                          .getBoxedMembersForPrimitiveOrVoidType(t)
+                                          .getTypeField())
+                                  .setOutValue(v)
+                                  .build()
+                              : new ConstClass(v, t),
+                      graphLens,
+                      codeLens);
             }
             break;
 
@@ -809,7 +821,7 @@
     // Finalize cast and null check insertion.
     interfaceTypeToClassTypeRewriterHelper.processWorklist();
 
-    assert code.isConsistentSSABeforeTypesAreCorrect();
+    assert code.isConsistentSSABeforeTypesAreCorrect(appView);
   }
 
   // Applies the prototype changes of the current method to the argument instructions:
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/MethodConversionOptions.java b/src/main/java/com/android/tools/r8/ir/conversion/MethodConversionOptions.java
index 1dd9510..038a277 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/MethodConversionOptions.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/MethodConversionOptions.java
@@ -4,45 +4,77 @@
 
 package com.android.tools.r8.ir.conversion;
 
+import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.utils.InternalOptions;
+
 public abstract class MethodConversionOptions {
 
+  public abstract boolean isGeneratingClassFiles();
+
+  public final boolean isGeneratingDex() {
+    return !isGeneratingClassFiles();
+  }
+
   public abstract boolean isPeepholeOptimizationsEnabled();
 
+  public abstract boolean isStringSwitchConversionEnabled();
+
   public static class MutableMethodConversionOptions extends MethodConversionOptions {
 
-    private final MethodProcessor methodProcessor;
     private boolean enablePeepholeOptimizations = true;
+    private boolean enableStringSwitchConversion;
+    private boolean isGeneratingClassFiles;
 
-    public MutableMethodConversionOptions(MethodProcessor methodProcessor) {
-      this.methodProcessor = methodProcessor;
+    public MutableMethodConversionOptions(InternalOptions options) {
+      this.enableStringSwitchConversion = options.isStringSwitchConversionEnabled();
+      this.isGeneratingClassFiles = options.isGeneratingClassFiles();
     }
 
-    public void disablePeepholeOptimizations() {
+    public void disablePeepholeOptimizations(MethodProcessor methodProcessor) {
       assert methodProcessor.isPrimaryMethodProcessor();
       enablePeepholeOptimizations = false;
     }
 
+    public void disableStringSwitchConversion() {
+      enableStringSwitchConversion = false;
+    }
+
+    public MutableMethodConversionOptions setIsGeneratingClassFiles(
+        boolean isGeneratingClassFiles) {
+      this.isGeneratingClassFiles = isGeneratingClassFiles;
+      return this;
+    }
+
+    @Override
+    public boolean isGeneratingClassFiles() {
+      return isGeneratingClassFiles;
+    }
+
     @Override
     public boolean isPeepholeOptimizationsEnabled() {
-      assert enablePeepholeOptimizations || methodProcessor.isPrimaryMethodProcessor();
       return enablePeepholeOptimizations;
     }
+
+    @Override
+    public boolean isStringSwitchConversionEnabled() {
+      return enableStringSwitchConversion;
+    }
   }
 
-  public static class DefaultMethodConversionOptions extends MethodConversionOptions {
+  public static class ThrowingMethodConversionOptions extends MutableMethodConversionOptions {
 
-    private static final DefaultMethodConversionOptions INSTANCE =
-        new DefaultMethodConversionOptions();
+    public ThrowingMethodConversionOptions(InternalOptions options) {
+      super(options);
+    }
 
-    private DefaultMethodConversionOptions() {}
-
-    public static DefaultMethodConversionOptions getInstance() {
-      return INSTANCE;
+    @Override
+    public boolean isGeneratingClassFiles() {
+      throw new Unreachable();
     }
 
     @Override
     public boolean isPeepholeOptimizationsEnabled() {
-      return true;
+      throw new Unreachable();
     }
   }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/MethodOptimizationFeedback.java b/src/main/java/com/android/tools/r8/ir/conversion/MethodOptimizationFeedback.java
index 8ad701f..c1a7b55 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/MethodOptimizationFeedback.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/MethodOptimizationFeedback.java
@@ -109,8 +109,6 @@
 
   void unsetNonNullParamOrThrow(ProgramMethod method);
 
-  void unsetReachabilitySensitive(ProgramMethod method);
-
   void unsetReturnedArgument(ProgramMethod method);
 
   void unsetReturnValueOnlyDependsOnArguments(ProgramMethod method);
@@ -135,7 +133,6 @@
       unsetNeverReturnsNormally(method);
       unsetNonNullParamOnNormalExits(method);
       unsetNonNullParamOrThrow(method);
-      unsetReachabilitySensitive(method);
       unsetReturnedArgument(method);
       unsetReturnValueOnlyDependsOnArguments(method);
       unsetSimpleInliningConstraint(method);
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/PostMethodProcessor.java b/src/main/java/com/android/tools/r8/ir/conversion/PostMethodProcessor.java
index ecee91f..780f5d4 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/PostMethodProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/PostMethodProcessor.java
@@ -20,11 +20,13 @@
 import com.android.tools.r8.ir.optimize.info.OptimizationFeedbackDelayed;
 import com.android.tools.r8.logging.Log;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.utils.DeterminismChecker;
 import com.android.tools.r8.utils.ThreadUtils;
 import com.android.tools.r8.utils.Timing;
 import com.android.tools.r8.utils.Timing.TimingMerger;
 import com.android.tools.r8.utils.collections.LongLivedProgramMethodSetBuilder;
 import com.android.tools.r8.utils.collections.ProgramMethodSet;
+import java.io.IOException;
 import java.util.ArrayDeque;
 import java.util.Collection;
 import java.util.Deque;
@@ -142,6 +144,10 @@
           new PartialCallGraphBuilder(appView, methodsToReprocess).build(executorService, timing);
       return new PostMethodProcessor(appView, callGraph);
     }
+
+    public void dump(DeterminismChecker determinismChecker) throws IOException {
+      determinismChecker.accept(methodsToReprocessBuilder::dump);
+    }
   }
 
   private Deque<ProgramMethodSet> createWaves(CallGraph callGraph) {
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/StringSwitchRemover.java b/src/main/java/com/android/tools/r8/ir/conversion/StringSwitchRemover.java
index 845793f..ab6e97d 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/StringSwitchRemover.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/StringSwitchRemover.java
@@ -56,7 +56,7 @@
     this.stringType = TypeElement.stringClassType(appView, definitelyNotNull());
   }
 
-  void run(IRCode code) {
+  public void run(IRCode code) {
     if (!code.metadata().mayHaveStringSwitch()) {
       assert Streams.stream(code.instructions()).noneMatch(Instruction::isStringSwitch);
       return;
@@ -97,7 +97,7 @@
       identifierNameStringMarker.decoupleIdentifierNameStringsInBlocks(code, newBlocksWithStrings);
     }
 
-    assert code.isConsistentSSA();
+    assert code.isConsistentSSA(appView);
   }
 
   // Returns true if minification is enabled and the switch value is guaranteed to be a class name.
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/CfInstructionDesugaringEventConsumer.java b/src/main/java/com/android/tools/r8/ir/desugar/CfInstructionDesugaringEventConsumer.java
index 9aada99..6c0c957 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/CfInstructionDesugaringEventConsumer.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/CfInstructionDesugaringEventConsumer.java
@@ -13,6 +13,7 @@
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.conversion.ClassConverterResult;
 import com.android.tools.r8.ir.conversion.D8MethodProcessor;
+import com.android.tools.r8.ir.desugar.apimodel.ApiInvokeOutlinerDesugaringEventConsumer;
 import com.android.tools.r8.ir.desugar.backports.BackportedMethodDesugaringEventConsumer;
 import com.android.tools.r8.ir.desugar.constantdynamic.ConstantDynamicClass;
 import com.android.tools.r8.ir.desugar.constantdynamic.ConstantDynamicDesugaringEventConsumer;
@@ -57,7 +58,8 @@
         InterfaceMethodDesugaringEventConsumer,
         DesugaredLibraryRetargeterInstructionEventConsumer,
         DesugaredLibraryAPIConverterEventConsumer,
-        ClasspathEmulatedInterfaceSynthesizerEventConsumer {
+        ClasspathEmulatedInterfaceSynthesizerEventConsumer,
+        ApiInvokeOutlinerDesugaringEventConsumer {
 
   public static D8CfInstructionDesugaringEventConsumer createForD8(
       D8MethodProcessor methodProcessor) {
@@ -221,7 +223,6 @@
               info -> {
                 ProgramMethod newDirectMethod = info.getNewDirectMethod();
                 newDirectMethod
-                    .getDefinition()
                     .setCode(info.getVirtualMethod().getDefinition().getCode(), appView);
               });
 
@@ -232,7 +233,6 @@
           .forEach(
               info -> {
                 info.getVirtualMethod()
-                    .getDefinition()
                     .setCode(info.getVirtualMethodCode(), appView);
                 needsProcessing.accept(info.getVirtualMethod());
               });
@@ -268,6 +268,11 @@
       assert synthesizedConstantDynamicClasses.isEmpty();
       return true;
     }
+
+    @Override
+    public void acceptOutlinedMethod(ProgramMethod outlinedMethod, ProgramMethod context) {
+      methodProcessor.scheduleDesugaredMethodForProcessing(outlinedMethod);
+    }
   }
 
   public static class R8CfInstructionDesugaringEventConsumer
@@ -433,7 +438,6 @@
       pendingInvokeSpecialBridges.forEach(
           info ->
               info.getVirtualMethod()
-                  .getDefinition()
                   .setCode(info.getVirtualMethodCode(), appView));
     }
 
@@ -462,5 +466,10 @@
       // Remove all '$deserializeLambda$' methods which are not supported by desugaring.
       LambdaDeserializationMethodRemover.run(appView, classesWithSerializableLambdas);
     }
+
+    @Override
+    public void acceptOutlinedMethod(ProgramMethod outlinedMethod, ProgramMethod context) {
+      // Intentionally empty. The method will be hit by tracing if required.
+    }
   }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/LambdaClass.java b/src/main/java/com/android/tools/r8/ir/desugar/LambdaClass.java
index 27298a9..d01f7fc 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/LambdaClass.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/LambdaClass.java
@@ -72,6 +72,7 @@
   public LambdaDescriptor descriptor;
   public final DexMethod constructor;
   final DexMethod classConstructor;
+  private final DexMethod factoryMethod;
   public final DexField lambdaField;
   public final Target target;
 
@@ -108,6 +109,13 @@
         statelessSingleton
             ? factory.createField(type, type, factory.lambdaInstanceFieldName)
             : null;
+    this.factoryMethod =
+        appView.options().testing.alwaysGenerateLambdaFactoryMethods
+            ? factory.createMethod(
+                type,
+                factory.createProto(type, descriptor.captures.values),
+                factory.createString("create"))
+            : null;
 
     // Synthesize the program class once all fields are set.
     synthesizeLambdaClass(builder, desugarInvoke);
@@ -151,6 +159,15 @@
     return appView.options().createSingletonsForStatelessLambdas && descriptor.isStateless();
   }
 
+  public boolean hasFactoryMethod() {
+    return factoryMethod != null;
+  }
+
+  public DexMethod getFactoryMethod() {
+    assert hasFactoryMethod();
+    return factoryMethod;
+  }
+
   // Synthesize virtual methods.
   private void synthesizeVirtualMethods(
       SyntheticProgramClassBuilder builder, DesugarInvoke desugarInvoke) {
@@ -227,6 +244,17 @@
               .build());
       feedback.classInitializerMayBePostponed(methods.get(1));
     }
+    if (hasFactoryMethod()) {
+      methods.add(
+          DexEncodedMethod.syntheticBuilder()
+              .setMethod(factoryMethod)
+              .setAccessFlags(
+                  MethodAccessFlags.fromSharedAccessFlags(
+                      Constants.ACC_STATIC | Constants.ACC_PUBLIC | Constants.ACC_SYNTHETIC, false))
+              .setCode(LambdaClassFactorySourceCode.build(this))
+              .disableAndroidApiLevelCheck()
+              .build());
+    }
     builder.setDirectMethods(methods);
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/LambdaClassFactorySourceCode.java b/src/main/java/com/android/tools/r8/ir/desugar/LambdaClassFactorySourceCode.java
new file mode 100644
index 0000000..0ca21cf
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/desugar/LambdaClassFactorySourceCode.java
@@ -0,0 +1,40 @@
+// Copyright (c) 2022, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.ir.desugar;
+
+import com.android.tools.r8.cf.code.CfInstruction;
+import com.android.tools.r8.cf.code.CfInvoke;
+import com.android.tools.r8.cf.code.CfLoad;
+import com.android.tools.r8.cf.code.CfNew;
+import com.android.tools.r8.cf.code.CfReturn;
+import com.android.tools.r8.cf.code.CfStackInstruction;
+import com.android.tools.r8.cf.code.CfStackInstruction.Opcode;
+import com.android.tools.r8.graph.CfCode;
+import com.android.tools.r8.ir.code.ValueType;
+import com.google.common.collect.ImmutableList;
+import org.objectweb.asm.Opcodes;
+
+// Source code representing lambda factory method.
+final class LambdaClassFactorySourceCode {
+
+  public static CfCode build(LambdaClass lambda) {
+    int maxStack = 0;
+    int maxLocals = 0;
+    ImmutableList.Builder<CfInstruction> builder = ImmutableList.builder();
+    builder.add(new CfNew(lambda.type)).add(new CfStackInstruction(Opcode.Dup));
+    maxStack += 2;
+    int local = 0;
+    for (int i = 0; i < lambda.constructor.proto.getParameters().size(); i++) {
+      ValueType parameterType = ValueType.fromDexType(lambda.constructor.proto.getParameter(i));
+      builder.add(new CfLoad(parameterType, local));
+      maxStack += parameterType.requiredRegisters();
+      local += parameterType.requiredRegisters();
+      maxLocals = local;
+    }
+    builder
+        .add(new CfInvoke(Opcodes.INVOKESPECIAL, lambda.constructor, false))
+        .add(new CfReturn(ValueType.fromDexType(lambda.type)));
+    return new CfCode(lambda.type, maxStack, maxLocals, builder.build());
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/apimodel/ApiInvokeOutlinerDesugaring.java b/src/main/java/com/android/tools/r8/ir/desugar/apimodel/ApiInvokeOutlinerDesugaring.java
index e642655..06fadd9 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/apimodel/ApiInvokeOutlinerDesugaring.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/apimodel/ApiInvokeOutlinerDesugaring.java
@@ -63,7 +63,9 @@
           methodProcessingContext.createUniqueContext(),
           instruction.asInvoke(),
           computedApiLevel,
-          dexItemFactory);
+          dexItemFactory,
+          eventConsumer,
+          context);
     }
     return null;
   }
@@ -146,14 +148,17 @@
   }
 
   private Collection<CfInstruction> desugarLibraryCall(
-      UniqueContext context,
+      UniqueContext uniqueContext,
       CfInvoke invoke,
       ComputedApiLevel computedApiLevel,
-      DexItemFactory factory) {
+      DexItemFactory factory,
+      ApiInvokeOutlinerDesugaringEventConsumer eventConsumer,
+      ProgramMethod context) {
     DexMethod method = invoke.getMethod();
-    ProgramMethod programMethod =
-        ensureOutlineMethod(context, method, computedApiLevel, factory, invoke);
-    return ImmutableList.of(new CfInvoke(INVOKESTATIC, programMethod.getReference(), false));
+    ProgramMethod outlinedMethod =
+        ensureOutlineMethod(uniqueContext, method, computedApiLevel, factory, invoke);
+    eventConsumer.acceptOutlinedMethod(outlinedMethod, context);
+    return ImmutableList.of(new CfInvoke(INVOKESTATIC, outlinedMethod.getReference(), false));
   }
 
   private ProgramMethod ensureOutlineMethod(
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/apimodel/ApiInvokeOutlinerDesugaringEventConsumer.java b/src/main/java/com/android/tools/r8/ir/desugar/apimodel/ApiInvokeOutlinerDesugaringEventConsumer.java
new file mode 100644
index 0000000..873d5e5
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/desugar/apimodel/ApiInvokeOutlinerDesugaringEventConsumer.java
@@ -0,0 +1,12 @@
+// Copyright (c) 2022, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.desugar.apimodel;
+
+import com.android.tools.r8.graph.ProgramMethod;
+
+public interface ApiInvokeOutlinerDesugaringEventConsumer {
+
+  void acceptOutlinedMethod(ProgramMethod outlinedMethod, ProgramMethod context);
+}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/apiconversion/DesugaredLibraryAPICallbackSynthesizer.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/apiconversion/DesugaredLibraryAPICallbackSynthesizer.java
index 450cdb5..a0f8961 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/apiconversion/DesugaredLibraryAPICallbackSynthesizer.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/apiconversion/DesugaredLibraryAPICallbackSynthesizer.java
@@ -209,7 +209,7 @@
                 () -> context.createUniqueContext(clazz))
             .generateCfCode();
     DexEncodedMethod newMethod = wrapperSynthesizor.newSynthesizedMethod(methodToInstall, cfCode);
-    newMethod.setCode(cfCode, appView);
+    newMethod.setCode(cfCode, DexEncodedMethod.NO_PARAMETER_INFO);
     if (originalMethod.isLibraryMethodOverride().isTrue()) {
       newMethod.setLibraryMethodOverride(OptionalBool.TRUE);
     }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/humanspecification/HumanDesugaredLibrarySpecificationParser.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/humanspecification/HumanDesugaredLibrarySpecificationParser.java
index 2eb550b..5499ffb 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/humanspecification/HumanDesugaredLibrarySpecificationParser.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/humanspecification/HumanDesugaredLibrarySpecificationParser.java
@@ -48,6 +48,7 @@
   static final String WRAPPER_CONVERSION_EXCLUDING_KEY = "wrapper_conversion_excluding";
   static final String CUSTOM_CONVERSION_KEY = "custom_conversion";
   static final String REWRITE_PREFIX_KEY = "rewrite_prefix";
+  static final String MAINTAIN_PREFIX_KEY = "maintain_prefix";
   static final String RETARGET_METHOD_KEY = "retarget_method";
   static final String RETARGET_METHOD_EMULATED_DISPATCH_KEY =
       "retarget_method_with_emulated_dispatch";
@@ -235,6 +236,11 @@
         builder.putRewritePrefix(rewritePrefix.getKey(), rewritePrefix.getValue().getAsString());
       }
     }
+    if (jsonFlagSet.has(MAINTAIN_PREFIX_KEY)) {
+      for (JsonElement maintainPrefix : jsonFlagSet.get(MAINTAIN_PREFIX_KEY).getAsJsonArray()) {
+        builder.putMaintainPrefix(maintainPrefix.getAsString());
+      }
+    }
     if (jsonFlagSet.has(REWRITE_DERIVED_PREFIX_KEY)) {
       for (Map.Entry<String, JsonElement> prefixToMatch :
           jsonFlagSet.get(REWRITE_DERIVED_PREFIX_KEY).getAsJsonObject().entrySet()) {
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/humanspecification/HumanRewritingFlags.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/humanspecification/HumanRewritingFlags.java
index 9eb3ed7..8f4c435 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/humanspecification/HumanRewritingFlags.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/humanspecification/HumanRewritingFlags.java
@@ -24,6 +24,7 @@
 public class HumanRewritingFlags {
 
   private final Map<String, String> rewritePrefix;
+  private final Set<String> maintainPrefix;
   private final Map<String, Map<String, String>> rewriteDerivedPrefix;
   private final Map<DexType, DexType> emulatedInterfaces;
   private final Map<DexMethod, DexType> retargetMethod;
@@ -37,6 +38,7 @@
 
   HumanRewritingFlags(
       Map<String, String> rewritePrefix,
+      Set<String> maintainPrefix,
       Map<String, Map<String, String>> rewriteDerivedPrefix,
       Map<DexType, DexType> emulateLibraryInterface,
       Map<DexMethod, DexType> retargetMethod,
@@ -48,6 +50,7 @@
       Map<DexType, Set<DexMethod>> wrapperConversion,
       Map<DexMethod, MethodAccessFlags> amendLibraryMethod) {
     this.rewritePrefix = rewritePrefix;
+    this.maintainPrefix = maintainPrefix;
     this.rewriteDerivedPrefix = rewriteDerivedPrefix;
     this.emulatedInterfaces = emulateLibraryInterface;
     this.retargetMethod = retargetMethod;
@@ -63,6 +66,7 @@
   public static HumanRewritingFlags empty() {
     return new HumanRewritingFlags(
         ImmutableMap.of(),
+        ImmutableSet.of(),
         ImmutableMap.of(),
         ImmutableMap.of(),
         ImmutableMap.of(),
@@ -84,6 +88,7 @@
         reporter,
         origin,
         rewritePrefix,
+        maintainPrefix,
         rewriteDerivedPrefix,
         emulatedInterfaces,
         retargetMethod,
@@ -100,6 +105,10 @@
     return rewritePrefix;
   }
 
+  public Set<String> getMaintainPrefix() {
+    return maintainPrefix;
+  }
+
   public Map<String, Map<String, String>> getRewriteDerivedPrefix() {
     return rewriteDerivedPrefix;
   }
@@ -153,6 +162,7 @@
     private final Origin origin;
 
     private final Map<String, String> rewritePrefix;
+    private final Set<String> maintainPrefix;
     private final Map<String, Map<String, String>> rewriteDerivedPrefix;
     private final Map<DexType, DexType> emulatedInterfaces;
     private final Map<DexMethod, DexType> retargetMethod;
@@ -169,6 +179,7 @@
           reporter,
           origin,
           new HashMap<>(),
+          Sets.newIdentityHashSet(),
           new HashMap<>(),
           new IdentityHashMap<>(),
           new IdentityHashMap<>(),
@@ -185,6 +196,7 @@
         Reporter reporter,
         Origin origin,
         Map<String, String> rewritePrefix,
+        Set<String> maintainPrefix,
         Map<String, Map<String, String>> rewriteDerivedPrefix,
         Map<DexType, DexType> emulateLibraryInterface,
         Map<DexMethod, DexType> retargetMethod,
@@ -198,6 +210,7 @@
       this.reporter = reporter;
       this.origin = origin;
       this.rewritePrefix = new HashMap<>(rewritePrefix);
+      this.maintainPrefix = Sets.newHashSet(maintainPrefix);
       this.rewriteDerivedPrefix = new HashMap<>(rewriteDerivedPrefix);
       this.emulatedInterfaces = new IdentityHashMap<>(emulateLibraryInterface);
       this.retargetMethod = new IdentityHashMap<>(retargetMethod);
@@ -237,6 +250,11 @@
       return this;
     }
 
+    public Builder putMaintainPrefix(String prefix) {
+      maintainPrefix.add(prefix);
+      return this;
+    }
+
     public Builder putRewriteDerivedPrefix(
         String prefixToMatch, String prefixToRewrite, String rewrittenPrefix) {
       Map<String, String> map =
@@ -322,6 +340,7 @@
       validate();
       return new HumanRewritingFlags(
           ImmutableMap.copyOf(rewritePrefix),
+          ImmutableSet.copyOf(maintainPrefix),
           ImmutableMap.copyOf(rewriteDerivedPrefix),
           ImmutableMap.copyOf(emulatedInterfaces),
           ImmutableMap.copyOf(retargetMethod),
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/machinespecification/MachineDesugaredLibrarySpecification.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/machinespecification/MachineDesugaredLibrarySpecification.java
index 1f52b3d..735d4f0 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/machinespecification/MachineDesugaredLibrarySpecification.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/machinespecification/MachineDesugaredLibrarySpecification.java
@@ -82,6 +82,10 @@
     return rewritingFlags.getRewriteType();
   }
 
+  public Set<DexType> getMaintainType() {
+    return rewritingFlags.getMaintainType();
+  }
+
   public Map<DexType, DexType> getRewriteDerivedTypeOnly() {
     return rewritingFlags.getRewriteDerivedTypeOnly();
   }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/machinespecification/MachineRewritingFlags.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/machinespecification/MachineRewritingFlags.java
index 00a4747..0f1aef3 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/machinespecification/MachineRewritingFlags.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/machinespecification/MachineRewritingFlags.java
@@ -25,6 +25,7 @@
 
   MachineRewritingFlags(
       Map<DexType, DexType> rewriteType,
+      Set<DexType> maintainType,
       Map<DexType, DexType> rewriteDerivedTypeOnly,
       Map<DexMethod, DexMethod> staticRetarget,
       Map<DexMethod, DexMethod> nonEmulatedVirtualRetarget,
@@ -37,6 +38,7 @@
       Map<DexType, CustomConversionDescriptor> customConversions,
       Map<DexMethod, MethodAccessFlags> amendLibraryMethods) {
     this.rewriteType = rewriteType;
+    this.maintainType = maintainType;
     this.rewriteDerivedTypeOnly = rewriteDerivedTypeOnly;
     this.staticRetarget = staticRetarget;
     this.nonEmulatedVirtualRetarget = nonEmulatedVirtualRetarget;
@@ -53,6 +55,8 @@
 
   // Rewrites all the references to the keys as well as synthetic types derived from any key.
   private final Map<DexType, DexType> rewriteType;
+  // Maintains the references in the desugared library dex file.
+  private final Set<DexType> maintainType;
   // Rewrites only synthetic types derived from any key.
   private final Map<DexType, DexType> rewriteDerivedTypeOnly;
 
@@ -88,6 +92,10 @@
     return rewriteType;
   }
 
+  public Set<DexType> getMaintainType() {
+    return maintainType;
+  }
+
   public Map<DexType, DexType> getRewriteDerivedTypeOnly() {
     return rewriteDerivedTypeOnly;
   }
@@ -174,6 +182,7 @@
     Builder() {}
 
     private final Map<DexType, DexType> rewriteType = new IdentityHashMap<>();
+    private final ImmutableSet.Builder<DexType> maintainType = ImmutableSet.builder();
     private final Map<DexType, DexType> rewriteDerivedTypeOnly = new IdentityHashMap<>();
     private final ImmutableMap.Builder<DexMethod, DexMethod> staticRetarget =
         ImmutableMap.builder();
@@ -201,6 +210,11 @@
       rewriteType.put(src, target);
     }
 
+    public void maintainType(DexType type) {
+      assert type != null;
+      maintainType.add(type);
+    }
+
     public void rewriteDerivedTypeOnly(DexType src, DexType target) {
       rewriteDerivedTypeOnly.put(src, target);
     }
@@ -252,6 +266,7 @@
     public MachineRewritingFlags build() {
       return new MachineRewritingFlags(
           rewriteType,
+          maintainType.build(),
           rewriteDerivedTypeOnly,
           staticRetarget.build(),
           nonEmulatedVirtualRetarget.build(),
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/specificationconversion/HumanToMachinePrefixConverter.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/specificationconversion/HumanToMachinePrefixConverter.java
index 87c03e3..78fc59f 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/specificationconversion/HumanToMachinePrefixConverter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/specificationconversion/HumanToMachinePrefixConverter.java
@@ -13,6 +13,7 @@
 import com.android.tools.r8.ir.desugar.desugaredlibrary.machinespecification.MachineRewritingFlags;
 import com.android.tools.r8.utils.DescriptorUtils;
 import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Sets;
 import java.util.Map;
 import java.util.Set;
@@ -25,6 +26,7 @@
   private final String synthesizedPrefix;
   private final boolean libraryCompilation;
   private final Map<DexString, DexString> descriptorPrefix;
+  private final Set<DexString> descriptorMaintainPrefix;
   private final Map<DexString, Map<DexString, DexString>> descriptorDifferentPrefix;
   private final Set<DexString> usedPrefix = Sets.newIdentityHashSet();
 
@@ -38,6 +40,7 @@
     this.synthesizedPrefix = humanSpec.getSynthesizedLibraryClassesPackagePrefix();
     this.libraryCompilation = humanSpec.isLibraryCompilation();
     this.descriptorPrefix = convertRewritePrefix(rewritingFlags.getRewritePrefix());
+    this.descriptorMaintainPrefix = convertMaintainPrefix(rewritingFlags.getMaintainPrefix());
     this.descriptorDifferentPrefix =
         convertRewriteDifferentPrefix(rewritingFlags.getRewriteDerivedPrefix());
   }
@@ -56,6 +59,7 @@
   private void warnIfUnusedPrefix(BiConsumer<String, Set<DexString>> warnConsumer) {
     Set<DexString> prefixes = Sets.newIdentityHashSet();
     prefixes.addAll(descriptorPrefix.keySet());
+    prefixes.addAll(descriptorMaintainPrefix);
     prefixes.addAll(descriptorDifferentPrefix.keySet());
     prefixes.removeAll(usedPrefix);
     warnConsumer.accept("The following prefixes do not match any type: ", prefixes);
@@ -99,6 +103,7 @@
 
   private void registerClassType(DexType type) {
     registerType(type);
+    registerMaintainType(type);
     registerDifferentType(type);
   }
 
@@ -109,6 +114,15 @@
     }
   }
 
+  private void registerMaintainType(DexType type) {
+    DexString prefix = prefixMatching(type, descriptorMaintainPrefix);
+    if (prefix == null) {
+      return;
+    }
+    builder.maintainType(type);
+    usedPrefix.add(prefix);
+  }
+
   private void registerDifferentType(DexType type) {
     DexString prefix = prefixMatching(type, descriptorDifferentPrefix.keySet());
     if (prefix == null) {
@@ -160,6 +174,14 @@
     return mapBuilder.build();
   }
 
+  private ImmutableSet<DexString> convertMaintainPrefix(Set<String> maintainPrefix) {
+    ImmutableSet.Builder<DexString> builder = ImmutableSet.builder();
+    for (String prefix : maintainPrefix) {
+      builder.add(toDescriptorPrefix(prefix));
+    }
+    return builder.build();
+  }
+
   private ImmutableMap<DexString, DexString> convertRewritePrefix(
       Map<String, String> rewritePrefix) {
     ImmutableMap.Builder<DexString, DexString> mapBuilder = ImmutableMap.builder();
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/itf/ClassProcessor.java b/src/main/java/com/android/tools/r8/ir/desugar/itf/ClassProcessor.java
index da7d679..b1c59bc 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
@@ -644,7 +644,7 @@
     AppInfoWithClassHierarchy appInfo = appView.appInfoForDesugaring();
     for (Wrapper<DexMethod> signature : emulatedInterfaceInfo.signatures.signatures) {
       MethodResolutionResult resolutionResult =
-          appInfo.resolveMethodOnClass(signature.get(), clazz);
+          appInfo.resolveMethodOnClass(clazz, signature.get());
       if (resolutionResult.isFailedResolution()) {
         return true;
       }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceDesugaringSyntheticHelper.java b/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceDesugaringSyntheticHelper.java
index dc739bb..3919202 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceDesugaringSyntheticHelper.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceDesugaringSyntheticHelper.java
@@ -98,6 +98,13 @@
     if (isEmulatedInterface(clazz.type)) {
       return true;
     }
+    if (appView
+        .options()
+        .machineDesugaredLibrarySpecification
+        .getMaintainType()
+        .contains(clazz.type)) {
+      return true;
+    }
     return appView.typeRewriter.hasRewrittenType(clazz.type, appView);
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceMethodRewriter.java b/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceMethodRewriter.java
index 2ae7d7d..a9a33cb 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceMethodRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceMethodRewriter.java
@@ -713,7 +713,9 @@
       DexClass clazz, DexMethod invokedMethod, ProgramMethod context) {
     DexClassAndMethod superTarget =
         appView.appInfoForDesugaring().lookupSuperTarget(invokedMethod, context);
-    if (clazz.isInterface() && appView.typeRewriter.hasRewrittenType(clazz.type, appView)) {
+    if (clazz.isInterface()
+        && helper.isInDesugaredLibrary(clazz)
+        && !helper.isEmulatedInterface(clazz.type)) {
       if (superTarget != null && superTarget.getDefinition().isDefaultMethod()) {
         DexClass holder = superTarget.getHolder();
         if (holder.isLibraryClass() && holder.isInterface()) {
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceProcessor.java b/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceProcessor.java
index 5d14741..f963a21 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceProcessor.java
@@ -175,8 +175,8 @@
       DexEncodedMethod.setDebugInfoWithFakeThisParameter(
           code, companion.getReference().getArity(), appView);
     }
-    companion.getDefinition().setCode(code, appView);
-    definition.setCode(InvalidCode.getInstance(), appView);
+    companion.setCode(code, appView);
+    method.setCode(InvalidCode.getInstance(), appView);
   }
 
   private void clearDirectMethods(DexProgramClass iface) {
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/lambda/LambdaInstructionDesugaring.java b/src/main/java/com/android/tools/r8/ir/desugar/lambda/LambdaInstructionDesugaring.java
index d336a16..532909b 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/lambda/LambdaInstructionDesugaring.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/lambda/LambdaInstructionDesugaring.java
@@ -120,6 +120,11 @@
 
     eventConsumer.acceptLambdaClass(lambdaClass, context);
 
+    if (lambdaClass.hasFactoryMethod()) {
+      return ImmutableList.of(
+          new CfInvoke(Opcodes.INVOKESTATIC, lambdaClass.getFactoryMethod(), false));
+    }
+
     if (lambdaClass.isStatelessSingleton()) {
       return ImmutableList.of(
           new CfStaticFieldRead(lambdaClass.lambdaField, lambdaClass.lambdaField));
@@ -143,7 +148,6 @@
     // elements on the stack, we load all the N arguments back onto the stack. At this point, we
     // have the original N arguments on the stack plus the 2 new stack elements.
     localStackAllocator.allocateLocalStack(2);
-
     return replacement;
   }
 
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 3f17800..6a84f09 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
@@ -22,6 +22,7 @@
 import com.android.tools.r8.contexts.CompilationContext.MethodProcessingContext;
 import com.android.tools.r8.dex.Constants;
 import com.android.tools.r8.errors.CompilationError;
+import com.android.tools.r8.errors.MissingGlobalSyntheticsConsumerDiagnostic;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.CfCode;
@@ -239,8 +240,9 @@
             // Will be traced by the enqueuer.
             .disableAndroidApiLevelCheck()
             .build();
-    encodedMethod.setCode(provider.generateCfCode(), appView);
-    return new ProgramMethod(clazz, encodedMethod);
+    ProgramMethod result = new ProgramMethod(clazz, encodedMethod);
+    result.setCode(provider.generateCfCode(), appView);
+    return result;
   }
 
   private DexMethod ensureEqualsRecord(
@@ -372,7 +374,8 @@
     checkRecordTagNotPresent(factory);
     appView
         .getSyntheticItems()
-        .ensureFixedClassFromType(
+        .ensureGlobalClass(
+            () -> new MissingGlobalSyntheticsConsumerDiagnostic("Record desugaring"),
             kinds -> kinds.RECORD_TAG,
             factory.recordType,
             appView,
@@ -462,17 +465,13 @@
     MethodAccessFlags methodAccessFlags =
         MethodAccessFlags.fromSharedAccessFlags(
             Constants.ACC_SYNTHETIC | Constants.ACC_PROTECTED, true);
-    DexEncodedMethod init =
-        DexEncodedMethod.syntheticBuilder()
-            .setMethod(factory.recordMembers.constructor)
-            .setAccessFlags(methodAccessFlags)
-            .setCode(null)
-            // Will be traced by the enqueuer.
-            .disableAndroidApiLevelCheck()
-            .build();
-    init.setCode(
-        new CallObjectInitCfCodeProvider(appView, factory.recordTagType).generateCfCode(), appView);
-    return init;
+    return DexEncodedMethod.syntheticBuilder()
+        .setMethod(factory.recordMembers.constructor)
+        .setAccessFlags(methodAccessFlags)
+        .setCode(new CallObjectInitCfCodeProvider(appView, factory.recordTagType).generateCfCode())
+        // Will be traced by the enqueuer.
+        .disableAndroidApiLevelCheck()
+        .build();
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/AssertionsRewriter.java b/src/main/java/com/android/tools/r8/ir/optimize/AssertionsRewriter.java
index 0da7886..72b8d6a 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/AssertionsRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/AssertionsRewriter.java
@@ -340,7 +340,7 @@
     if (enabled) {
       timing.begin("Rewrite assertions");
       runInternal(method, code);
-      assert code.isConsistentSSA();
+      assert code.isConsistentSSA(appView);
       timing.end();
     }
   }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/BasicBlockInstructionsEquivalence.java b/src/main/java/com/android/tools/r8/ir/optimize/BasicBlockInstructionsEquivalence.java
index b7909df..5373a30 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/BasicBlockInstructionsEquivalence.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/BasicBlockInstructionsEquivalence.java
@@ -8,6 +8,7 @@
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.Instruction;
 import com.android.tools.r8.ir.code.Value;
+import com.android.tools.r8.ir.conversion.MethodConversionOptions;
 import com.android.tools.r8.ir.regalloc.RegisterAllocator;
 import com.google.common.base.Equivalence;
 import java.util.Arrays;
@@ -17,10 +18,12 @@
   private static final int UNKNOW_HASH = -1;
   private static final int MAX_HASH_INSTRUCTIONS = 5;
   private final RegisterAllocator allocator;
+  private final MethodConversionOptions conversionOptions;
   private final int[] hashes;
 
   BasicBlockInstructionsEquivalence(IRCode code, RegisterAllocator allocator) {
     this.allocator = allocator;
+    this.conversionOptions = code.getConversionOptions();
     hashes = new int[code.getCurrentBlockNumber() + 1];
     Arrays.fill(hashes, UNKNOW_HASH);
   }
@@ -34,7 +37,7 @@
     for (int i = 0; i < instructions0.size(); i++) {
       Instruction i0 = instructions0.get(i);
       Instruction i1 = instructions1.get(i);
-      if (!i0.identicalAfterRegisterAllocation(i1, allocator)) {
+      if (!i0.identicalAfterRegisterAllocation(i1, allocator, conversionOptions)) {
         return false;
       }
     }
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 3551295..ef1b959 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
@@ -137,7 +137,7 @@
     }
 
     ProgramMethod context = code.context();
-    if (context.getDefinition().getOptimizationInfo().isReachabilitySensitive()) {
+    if (context.getOrComputeReachabilitySensitive(appView)) {
       return ClassInitializerDefaultsResult.empty();
     }
 
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java b/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
index 204b1a6..e011479 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
@@ -39,7 +39,6 @@
 import com.android.tools.r8.ir.analysis.value.ConstantOrNonConstantNumberValue;
 import com.android.tools.r8.ir.analysis.value.SingleConstClassValue;
 import com.android.tools.r8.ir.analysis.value.SingleFieldValue;
-import com.android.tools.r8.ir.code.AlwaysMaterializingNop;
 import com.android.tools.r8.ir.code.ArrayLength;
 import com.android.tools.r8.ir.code.ArrayPut;
 import com.android.tools.r8.ir.code.Assume;
@@ -158,7 +157,6 @@
   private static final int MAX_FILL_ARRAY_SIZE = 8 * Constants.KILOBYTE;
   // This constant was determined by experimentation.
   private static final int STOP_SHARED_CONSTANT_THRESHOLD = 50;
-  private static final int SELF_RECURSION_LIMIT = 4;
 
   private final AppView<?> appView;
   private final DexItemFactory dexItemFactory;
@@ -387,7 +385,7 @@
         new TypeAnalysis(appView).narrowing(affectedValues);
       }
     }
-    assert code.isConsistentSSA();
+    assert code.isConsistentSSA(appView);
   }
 
   public static boolean isFallthroughBlock(BasicBlock block) {
@@ -489,41 +487,6 @@
     }
   }
 
-  // For method with many self-recursive calls, insert a try-catch to disable inlining.
-  // Marshmallow dex2oat aggressively inlines and eats up all the memory on devices.
-  public static void disableDex2OatInliningForSelfRecursiveMethods(
-      AppView<?> appView, IRCode code) {
-    if (!appView.options().canHaveDex2OatInliningIssue() || code.hasCatchHandlers()) {
-      // Catch handlers disables inlining, so if the method already has catch handlers
-      // there is nothing to do.
-      return;
-    }
-    int selfRecursionFanOut = 0;
-    Instruction lastSelfRecursiveCall = null;
-    for (Instruction i : code.instructions()) {
-      if (i.isInvokeMethod()
-          && i.asInvokeMethod().getInvokedMethod() == code.method().getReference()) {
-        selfRecursionFanOut++;
-        lastSelfRecursiveCall = i;
-      }
-    }
-    if (selfRecursionFanOut > SELF_RECURSION_LIMIT) {
-      assert lastSelfRecursiveCall != null;
-      // Split out the last recursive call in its own block.
-      InstructionListIterator splitIterator =
-          lastSelfRecursiveCall.getBlock().listIterator(code, lastSelfRecursiveCall);
-      splitIterator.previous();
-      BasicBlock newBlock = splitIterator.split(code, 1);
-      // Generate rethrow block.
-      DexType guard = appView.dexItemFactory().throwableType;
-      BasicBlock rethrowBlock =
-          BasicBlock.createRethrowBlock(code, lastSelfRecursiveCall.getPosition(), guard, appView);
-      code.blocks.add(rethrowBlock);
-      // Add catch handler to the block containing the last recursive call.
-      newBlock.appendCatchHandler(rethrowBlock, guard);
-    }
-  }
-
   // TODO(sgjesse); Move this somewhere else, and reuse it for some of the other switch rewritings.
   public abstract static class InstructionBuilder<T> {
     protected int blockNumber;
@@ -663,7 +626,7 @@
    * Covert the switch instruction to a sequence of if instructions checking for a specified set of
    * keys, followed by a new switch with the remaining keys.
    */
-  private void convertSwitchToSwitchAndIfs(
+  void convertSwitchToSwitchAndIfs(
       IRCode code,
       ListIterator<BasicBlock> blocksIterator,
       BasicBlock originalBlock,
@@ -945,57 +908,6 @@
     return rewriteSwitchFull(code, switchCaseAnalyzer);
   }
 
-  public void rewriteSwitchForMaxInt(IRCode code) {
-    if (options.canHaveSwitchMaxIntBug() && code.metadata().mayHaveSwitch()) {
-      // Always rewrite for workaround switch bug.
-      rewriteSwitchForMaxIntOnly(code);
-    }
-  }
-
-  private void rewriteSwitchForMaxIntOnly(IRCode code) {
-    boolean needToSplitCriticalEdges = false;
-    ListIterator<BasicBlock> blocksIterator = code.listIterator();
-    while (blocksIterator.hasNext()) {
-      BasicBlock block = blocksIterator.next();
-      InstructionListIterator iterator = block.listIterator(code);
-      while (iterator.hasNext()) {
-        Instruction instruction = iterator.next();
-        assert !instruction.isStringSwitch();
-        if (instruction.isIntSwitch()) {
-          IntSwitch intSwitch = instruction.asIntSwitch();
-          if (intSwitch.getKey(intSwitch.numberOfKeys() - 1) == Integer.MAX_VALUE) {
-            if (intSwitch.numberOfKeys() == 1) {
-              rewriteSingleKeySwitchToIf(code, block, iterator, intSwitch);
-            } else {
-              IntList newSwitchSequences = new IntArrayList(intSwitch.numberOfKeys() - 1);
-              for (int i = 0; i < intSwitch.numberOfKeys() - 1; i++) {
-                newSwitchSequences.add(intSwitch.getKey(i));
-              }
-              IntList outliers = new IntArrayList(1);
-              outliers.add(Integer.MAX_VALUE);
-              convertSwitchToSwitchAndIfs(
-                  code,
-                  blocksIterator,
-                  block,
-                  iterator,
-                  intSwitch,
-                  ImmutableList.of(newSwitchSequences),
-                  outliers);
-            }
-            needToSplitCriticalEdges = true;
-          }
-        }
-      }
-    }
-
-    // Rewriting of switches introduces new branching structure. It relies on critical edges
-    // being split on the way in but does not maintain this property. We therefore split
-    // critical edges at exit.
-    if (needToSplitCriticalEdges) {
-      code.splitCriticalEdges();
-    }
-  }
-
   private boolean rewriteSwitchFull(IRCode code, SwitchCaseAnalyzer switchCaseAnalyzer) {
     boolean needToRemoveUnreachableBlocks = false;
     ListIterator<BasicBlock> blocksIterator = code.listIterator();
@@ -1041,11 +953,11 @@
     if (!affectedValues.isEmpty()) {
       new TypeAnalysis(appView).narrowing(affectedValues);
     }
-    assert code.isConsistentSSA();
+    assert code.isConsistentSSA(appView);
     return !affectedValues.isEmpty();
   }
 
-  private void rewriteSingleKeySwitchToIf(
+  void rewriteSingleKeySwitchToIf(
       IRCode code, BasicBlock block, InstructionListIterator iterator, IntSwitch theSwitch) {
     // Rewrite the switch to an if.
     int fallthroughBlockIndex = theSwitch.getFallthroughBlockIndex();
@@ -1247,8 +1159,8 @@
    * rewrite fallthrough targets as that would require block reordering and the transformation only
    * makes sense after SSA destruction where there are no phis.
    */
-  public static void collapseTrivialGotos(IRCode code) {
-    assert code.isConsistentGraph();
+  public static void collapseTrivialGotos(AppView<?> appView, IRCode code) {
+    assert code.isConsistentGraph(appView);
     List<BasicBlock> blocksToRemove = new ArrayList<>();
     // Rewrite all non-fallthrough targets to the end of trivial goto chains and remove
     // first round of trivial goto blocks.
@@ -1286,7 +1198,7 @@
       code.removeBlocks(blocksToRemove);
     }
     assert removedTrivialGotos(code);
-    assert code.isConsistentGraph();
+    assert code.isConsistentGraph(appView);
   }
 
   private boolean checkArgumentType(InvokeMethod invoke, int argumentIndex) {
@@ -1375,7 +1287,7 @@
     if (!affectedValues.isEmpty()) {
       new TypeAnalysis(appView).narrowing(affectedValues);
     }
-    assert code.isConsistentSSA();
+    assert code.isConsistentSSA(appView);
     return changed;
   }
 
@@ -1466,7 +1378,7 @@
         typeAnalysis.narrowing(affectedValues);
       }
     }
-    assert code.isConsistentSSA();
+    assert code.isConsistentSSA(appView);
   }
 
   // Returns true if the given check-cast instruction was removed.
@@ -1744,7 +1656,7 @@
         }
       }
     }
-    assert code.isConsistentSSA();
+    assert code.isConsistentSSA(appView);
   }
 
   /**
@@ -1823,7 +1735,7 @@
       }
     }
 
-    assert code.isConsistentSSA();
+    assert code.isConsistentSSA(appView);
   }
 
   // Check if a binop can be represented in the binop/lit8 or binop/lit16 form.
@@ -2006,7 +1918,7 @@
       }
     }
 
-    assert code.isConsistentSSA();
+    assert code.isConsistentSSA(appView);
   }
 
   private void forEachUse(Instruction instruction, Consumer<Value> fn) {
@@ -2331,7 +2243,7 @@
         } while (block != null);
       }
     }
-    assert code.isConsistentSSA();
+    assert code.isConsistentSSA(appView);
   }
 
   // TODO(mikaelpeltier) Manage that from and to instruction do not belong to the same block.
@@ -2556,7 +2468,7 @@
       }
     }
     code.returnMarkingColor(noCandidate);
-    assert code.isConsistentSSA();
+    assert code.isConsistentSSA(appView);
   }
 
   static class ControlFlowSimplificationResult {
@@ -2632,7 +2544,7 @@
     if (!affectedValues.isEmpty()) {
       new TypeAnalysis(appView).narrowing(affectedValues);
     }
-    assert code.isConsistentSSA();
+    assert code.isConsistentSSA(appView);
     return new ControlFlowSimplificationResult(!affectedValues.isEmpty(), simplified);
   }
 
@@ -2988,7 +2900,7 @@
     if (changed) {
       code.removeAllDeadAndTrivialPhis();
     }
-    assert code.isConsistentSSA();
+    assert code.isConsistentSSA(appView);
   }
 
   private static Long2ReferenceMap<List<ConstNumber>> getConstantsByValue(IRCode code) {
@@ -3099,11 +3011,6 @@
   // it with a block throwing a null value (which should result in NPE). Note that this throw is not
   // expected to be ever reached, but is intended to satisfy verifier.
   public void optimizeAlwaysThrowingInstructions(IRCode code) {
-    if (!appView.appInfo().hasLiveness()) {
-      return;
-    }
-
-    AppView<AppInfoWithLiveness> appViewWithLiveness = appView.withLiveness();
     Set<Value> affectedValues = Sets.newIdentityHashSet();
     Set<BasicBlock> blocksToRemove = Sets.newIdentityHashSet();
     ListIterator<BasicBlock> blockIterator = code.listIterator();
@@ -3153,7 +3060,7 @@
               }
             }
             instructionIterator.replaceCurrentInstructionWithThrowNull(
-                appViewWithLiveness, code, blockIterator, blocksToRemove, affectedValues);
+                appView, code, blockIterator, blocksToRemove, affectedValues);
             continue;
           }
         }
@@ -3163,8 +3070,7 @@
         }
 
         InvokeMethod invoke = instruction.asInvokeMethod();
-        DexClassAndMethod singleTarget =
-            invoke.lookupSingleTarget(appView.withLiveness(), code.context());
+        DexClassAndMethod singleTarget = invoke.lookupSingleTarget(appView, code.context());
         if (singleTarget == null) {
           continue;
         }
@@ -3190,7 +3096,7 @@
           instructionIterator.setInsertionPosition(invoke.getPosition());
           instructionIterator.next();
           instructionIterator.replaceCurrentInstructionWithThrowNull(
-              appViewWithLiveness, code, blockIterator, blocksToRemove, affectedValues);
+              appView, code, blockIterator, blocksToRemove, affectedValues);
           instructionIterator.unsetInsertionPosition();
         }
       }
@@ -3203,7 +3109,7 @@
     if (!affectedValues.isEmpty()) {
       new TypeAnalysis(appView).narrowing(affectedValues);
     }
-    assert code.isConsistentSSA();
+    assert code.isConsistentSSA(appView);
   }
 
   /* Identify simple diamond shapes converting boolean true/false to 1/0. We consider the forms:
@@ -3465,7 +3371,7 @@
 
       phiUsers.forEach(Phi::removeTrivialPhi);
     }
-    assert code.isConsistentSSA();
+    assert code.isConsistentSSA(appView);
   }
 
   public void rewriteAssertionErrorTwoArgumentConstructor(IRCode code, InternalOptions options) {
@@ -3514,7 +3420,7 @@
         }
       }
     }
-    assert code.isConsistentSSA();
+    assert code.isConsistentSSA(appView);
   }
 
   /**
@@ -3870,78 +3776,4 @@
       }
     }
   }
-
-  // See comment for InternalOptions.canHaveNumberConversionRegisterAllocationBug().
-  public void workaroundNumberConversionRegisterAllocationBug(IRCode code) {
-    ListIterator<BasicBlock> blocks = code.listIterator();
-    while (blocks.hasNext()) {
-      BasicBlock block = blocks.next();
-      InstructionListIterator it = block.listIterator(code);
-      while (it.hasNext()) {
-        Instruction instruction = it.next();
-        if (instruction.isArithmeticBinop() || instruction.isNeg()) {
-          for (Value value : instruction.inValues()) {
-            // Insert a call to Double.isNaN on each value which come from a number conversion
-            // to double and flows into an arithmetic instruction. This seems to break the traces
-            // in the Dalvik JIT and avoid the bug where the generated ARM code can clobber float
-            // values in a single-precision registers with double values written to
-            // double-precision registers. See b/77496850 for examples.
-            if (!value.isPhi()
-                && value.definition.isNumberConversion()
-                && value.definition.asNumberConversion().to == NumericType.DOUBLE) {
-              InvokeStatic invokeIsNaN =
-                  new InvokeStatic(
-                      dexItemFactory.doubleMembers.isNaN, null, ImmutableList.of(value));
-              invokeIsNaN.setPosition(instruction.getPosition());
-
-              // Insert the invoke before the current instruction.
-              it.previous();
-              BasicBlock blockWithInvokeNaN =
-                  block.hasCatchHandlers() ? it.split(code, blocks) : block;
-              if (blockWithInvokeNaN != block) {
-                // If we split, add the invoke at the end of the original block.
-                it = block.listIterator(code, block.getInstructions().size());
-                it.previous();
-                it.add(invokeIsNaN);
-                // Continue iteration in the split block.
-                block = blockWithInvokeNaN;
-                it = block.listIterator(code);
-              } else {
-                // Otherwise, add it to the current block.
-                it.add(invokeIsNaN);
-              }
-              // Skip over the instruction causing the invoke to be inserted.
-              Instruction temp = it.next();
-              assert temp == instruction;
-            }
-          }
-        }
-      }
-    }
-  }
-
-  // If an exceptional edge could target a conditional-loop header ensure that we have a
-  // materializing instruction on that path to work around a bug in some L x86_64 non-emulator VMs.
-  // See b/111337896.
-  public void workaroundExceptionTargetingLoopHeaderBug(IRCode code) {
-    for (BasicBlock block : code.blocks) {
-      if (block.hasCatchHandlers()) {
-        for (BasicBlock handler : block.getCatchHandlers().getUniqueTargets()) {
-          // We conservatively assume that a block with at least two normal predecessors is a loop
-          // header. If we ever end up computing exact loop headers, use that here instead.
-          // The loop is conditional if it has at least two normal successors.
-          BasicBlock target = handler.endOfGotoChain();
-          if (target != null
-              && target.getPredecessors().size() > 1
-              && target.getNormalPredecessors().size() > 1
-              && target.getNormalSuccessors().size() > 1) {
-            Instruction fixit = new AlwaysMaterializingNop();
-            fixit.setBlock(handler);
-            fixit.setPosition(handler.getPosition());
-            handler.getInstructions().addFirst(fixit);
-          }
-        }
-      }
-    }
-  }
 }
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 912a030..8839c13 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
@@ -270,7 +270,7 @@
       codeRewriter.simplifyIf(code);
     }
 
-    assert code.isConsistentSSA();
+    assert code.isConsistentSSA(appView);
   }
 
   private static void insertCanonicalizedConstant(IRCode code, Instruction canonicalizedConstant) {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/DeadCodeRemover.java b/src/main/java/com/android/tools/r8/ir/optimize/DeadCodeRemover.java
index 5dbfaba..92e653d 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/DeadCodeRemover.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/DeadCodeRemover.java
@@ -40,6 +40,10 @@
     this.codeRewriter = codeRewriter;
   }
 
+  public CodeRewriter getCodeRewriter() {
+    return codeRewriter;
+  }
+
   public void run(IRCode code, Timing timing) {
     timing.begin("Remove dead code");
 
@@ -58,7 +62,7 @@
       }
     } while (codeRewriter.simplifyIf(code).anySimplifications()
         || removeUnneededCatchHandlers(code));
-    assert code.isConsistentSSA();
+    assert code.isConsistentSSA(appView);
 
     timing.end();
   }
@@ -210,7 +214,7 @@
     if (mayHaveIntroducedUnreachableBlocks) {
       code.removeUnreachableBlocks();
     }
-    assert code.isConsistentGraph();
+    assert code.isConsistentGraph(appView);
     return mayHaveIntroducedUnreachableBlocks;
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/Devirtualizer.java b/src/main/java/com/android/tools/r8/ir/optimize/Devirtualizer.java
index 2ef8dd2..cd5b8b2 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/Devirtualizer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/Devirtualizer.java
@@ -315,7 +315,7 @@
     if (!affectedValues.isEmpty()) {
       new TypeAnalysis(appView).narrowing(affectedValues);
     }
-    assert code.isConsistentSSA();
+    assert code.isConsistentSSA(appView);
   }
 
   /** This rebinds invoke-super instructions to their most specific target. */
@@ -369,7 +369,7 @@
     }
 
     SingleResolutionResult resolutionResult =
-        appView.appInfo().resolveMethodOnClass(target).asSingleResolution();
+        appView.appInfo().resolveMethodOnClass(target.getHolderType(), target).asSingleResolution();
     if (resolutionResult == null
         || resolutionResult
             .isAccessibleForVirtualDispatchFrom(context, appView.appInfo())
@@ -385,7 +385,7 @@
     }
 
     SingleResolutionResult newResolutionResult =
-        appView.appInfo().resolveMethodOnClass(target, receiverType).asSingleResolution();
+        appView.appInfo().resolveMethodOnClass(receiverType, target).asSingleResolution();
     if (newResolutionResult == null
         || newResolutionResult
             .isAccessibleForVirtualDispatchFrom(context, appView.appInfo())
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/IdempotentFunctionCallCanonicalizer.java b/src/main/java/com/android/tools/r8/ir/optimize/IdempotentFunctionCallCanonicalizer.java
index 28530d3..4b3bb6f 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/IdempotentFunctionCallCanonicalizer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/IdempotentFunctionCallCanonicalizer.java
@@ -271,7 +271,7 @@
     }
 
     code.removeAllDeadAndTrivialPhis();
-    assert code.isConsistentSSA();
+    assert code.isConsistentSSA(appView);
   }
 
   private boolean isIdempotentLibraryMethodInvoke(InvokeMethod invoke) {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java b/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java
index c458574..577dbf2 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java
@@ -704,7 +704,7 @@
       if (options.testing.inlineeIrModifier != null) {
         options.testing.inlineeIrModifier.accept(code);
       }
-      assert code.isConsistentSSA();
+      assert code.isConsistentSSA(appView);
       return new InlineeWithReason(code, reason);
     }
 
@@ -959,11 +959,11 @@
         if (current.isInvokeMethod()) {
           InvokeMethod invoke = current.asInvokeMethod();
           // TODO(b/142116551): This should be equivalent to invoke.lookupSingleTarget()!
-
+          DexMethod invokedMethod = invoke.getInvokedMethod();
           SingleResolutionResult resolutionResult =
               appView
                   .appInfo()
-                  .resolveMethod(invoke.getInvokedMethod(), invoke.getInterfaceBit())
+                  .resolveMethod(invokedMethod, invoke.getInterfaceBit())
                   .asSingleResolution();
           if (resolutionResult == null
               || resolutionResult.isAccessibleFrom(context, appView).isPossiblyFalse()) {
@@ -1105,7 +1105,7 @@
     classInitializationAnalysis.finish();
     code.removeBlocks(blocksToRemove);
     code.removeAllDeadAndTrivialPhis();
-    assert code.isConsistentSSA();
+    assert code.isConsistentSSA(appView);
   }
 
   private boolean tryInlineMethodWithoutSideEffects(
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/InstructionEquivalence.java b/src/main/java/com/android/tools/r8/ir/optimize/InstructionEquivalence.java
index 2f2523d..68207cd 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/InstructionEquivalence.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/InstructionEquivalence.java
@@ -3,21 +3,25 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.ir.optimize;
 
+import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.Instruction;
 import com.android.tools.r8.ir.code.Value;
+import com.android.tools.r8.ir.conversion.MethodConversionOptions;
 import com.android.tools.r8.ir.regalloc.RegisterAllocator;
 import com.google.common.base.Equivalence;
 
 public class InstructionEquivalence extends Equivalence<Instruction> {
   private final RegisterAllocator allocator;
+  private final MethodConversionOptions conversionOptions;
 
-  InstructionEquivalence(RegisterAllocator allocator) {
+  InstructionEquivalence(RegisterAllocator allocator, IRCode code) {
     this.allocator = allocator;
+    this.conversionOptions = code.getConversionOptions();
   }
 
   @Override
   protected boolean doEquivalent(Instruction a, Instruction b) {
-    return a.identicalAfterRegisterAllocation(b, allocator)
+    return a.identicalAfterRegisterAllocation(b, allocator, conversionOptions)
         && a.getBlock().getCatchHandlers().equals(b.getBlock().getCatchHandlers());
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/MemberValuePropagation.java b/src/main/java/com/android/tools/r8/ir/optimize/MemberValuePropagation.java
index 08edc70..7e494f3 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/MemberValuePropagation.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/MemberValuePropagation.java
@@ -382,6 +382,13 @@
       return;
     }
 
+    if (current.isStaticGet() && current.hasUnusedOutValue()) {
+      // Replace by initclass.
+      iterator.removeOrReplaceCurrentInstructionByInitClassIfPossible(
+          appView, code, field.getHolderType());
+      return;
+    }
+
     if (!mayPropagateValueFor(target)) {
       return;
     }
@@ -510,7 +517,7 @@
     if (!affectedValues.isEmpty()) {
       new TypeAnalysis(appView).narrowing(affectedValues);
     }
-    assert code.isConsistentSSA();
+    assert code.isConsistentSSA(appView);
     assert code.verifyTypes(appView);
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/NaturalIntLoopRemover.java b/src/main/java/com/android/tools/r8/ir/optimize/NaturalIntLoopRemover.java
index 0951ff9..c82a122 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/NaturalIntLoopRemover.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/NaturalIntLoopRemover.java
@@ -41,7 +41,7 @@
     }
     if (loopRemoved) {
       code.removeAllDeadAndTrivialPhis();
-      assert code.isConsistentSSA();
+      assert code.isConsistentSSA(appView);
     }
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/OutlinerImpl.java b/src/main/java/com/android/tools/r8/ir/optimize/OutlinerImpl.java
index f04e328..fa902a6 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/OutlinerImpl.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/OutlinerImpl.java
@@ -59,6 +59,7 @@
 import com.android.tools.r8.ir.code.ValueTypeConstraint;
 import com.android.tools.r8.ir.conversion.IRBuilder;
 import com.android.tools.r8.ir.conversion.IRConverter;
+import com.android.tools.r8.ir.conversion.MethodConversionOptions.MutableMethodConversionOptions;
 import com.android.tools.r8.ir.conversion.SourceCode;
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
 import com.android.tools.r8.ir.optimize.info.OptimizationFeedbackDelayed;
@@ -1794,9 +1795,13 @@
     }
 
     @Override
-    public IRCode buildIR(ProgramMethod method, AppView<?> appView, Origin origin) {
+    public IRCode buildIR(
+        ProgramMethod method,
+        AppView<?> appView,
+        Origin origin,
+        MutableMethodConversionOptions conversionOptions) {
       OutlineSourceCode source = new OutlineSourceCode(outline, method.getReference());
-      return IRBuilder.create(method, appView, source, origin).build(method);
+      return IRBuilder.create(method, appView, source, origin).build(method, conversionOptions);
     }
 
     @Override
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/PeepholeOptimizer.java b/src/main/java/com/android/tools/r8/ir/optimize/PeepholeOptimizer.java
index a8cd620..0b199bc 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/PeepholeOptimizer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/PeepholeOptimizer.java
@@ -3,6 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.ir.optimize;
 
+import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DebugLocalInfo;
 import com.android.tools.r8.ir.code.BasicBlock;
 import com.android.tools.r8.ir.code.ConstNumber;
@@ -36,17 +37,18 @@
   /**
    * Perform optimizations of the code with register assignments provided by the register allocator.
    */
-  public static void optimize(IRCode code, LinearScanRegisterAllocator allocator) {
+  public static void optimize(
+      AppView<?> appView, IRCode code, LinearScanRegisterAllocator allocator) {
     removeIdenticalPredecessorBlocks(code, allocator);
     removeRedundantInstructions(code, allocator);
     shareIdenticalBlockPrefix(code, allocator);
     shareIdenticalBlockSuffix(code, allocator, 0);
-    assert code.isConsistentGraph();
+    assert code.isConsistentGraph(appView);
   }
 
   /** Identify common prefixes in successor blocks and share them. */
   private static void shareIdenticalBlockPrefix(IRCode code, RegisterAllocator allocator) {
-    InstructionEquivalence equivalence = new InstructionEquivalence(allocator);
+    InstructionEquivalence equivalence = new InstructionEquivalence(allocator, code);
     Set<BasicBlock> blocksToBeRemoved = Sets.newIdentityHashSet();
     for (BasicBlock block : code.blocks) {
       shareIdenticalBlockPrefixFromNormalSuccessors(
@@ -244,7 +246,7 @@
     do {
       Map<BasicBlock, BasicBlock> newBlocks = new IdentityHashMap<>();
       for (BasicBlock block : blocks) {
-        InstructionEquivalence equivalence = new InstructionEquivalence(allocator);
+        InstructionEquivalence equivalence = new InstructionEquivalence(allocator, code);
         // Group interesting predecessor blocks by their last instruction.
         Map<Wrapper<Instruction>, List<BasicBlock>> lastInstructionToBlocks = new HashMap<>();
         for (BasicBlock pred : block.getPredecessors()) {
@@ -284,7 +286,7 @@
             BasicBlock pred = predsWithSameLastInstruction.get(i);
             assert pred.exit().isGoto() || pred.exit().isReturn();
             commonSuffixSize =
-                Math.min(commonSuffixSize, sharedSuffixSize(firstPred, pred, allocator));
+                Math.min(commonSuffixSize, sharedSuffixSize(firstPred, pred, allocator, code));
           }
 
           int sizeDelta = overhead - (predsWithSameLastInstruction.size() - 1) * commonSuffixSize;
@@ -403,7 +405,7 @@
   }
 
   private static int sharedSuffixSize(
-      BasicBlock block0, BasicBlock block1, RegisterAllocator allocator) {
+      BasicBlock block0, BasicBlock block1, RegisterAllocator allocator, IRCode code) {
     assert block0.exit().isGoto() || block0.exit().isReturn();
     // If the blocks do not agree on locals at exit then they don't have any shared suffix.
     if (!Objects.equals(localsAtBlockExit(block0), localsAtBlockExit(block1))) {
@@ -415,7 +417,7 @@
     while (it0.hasPrevious() && it1.hasPrevious()) {
       Instruction i0 = it0.previous();
       Instruction i1 = it1.previous();
-      if (!i0.identicalAfterRegisterAllocation(i1, allocator)) {
+      if (!i0.identicalAfterRegisterAllocation(i1, allocator, code.getConversionOptions())) {
         return suffixSize;
       }
       suffixSize++;
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 d758988..5966a8c 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
@@ -413,7 +413,7 @@
     }
     processInstructionsToRemove();
     assumeRemover.removeMarkedInstructions().finish();
-    assert code.isConsistentSSA();
+    assert code.isConsistentSSA(appView);
   }
 
   private void processInstructionsToRemove() {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/ReflectionOptimizer.java b/src/main/java/com/android/tools/r8/ir/optimize/ReflectionOptimizer.java
index 683cf92..879646e 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/ReflectionOptimizer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/ReflectionOptimizer.java
@@ -74,7 +74,7 @@
     if (!affectedValues.isEmpty()) {
       new TypeAnalysis(appView).narrowing(affectedValues);
     }
-    assert code.isConsistentSSA();
+    assert code.isConsistentSSA(appView);
   }
 
   private static BiConsumer<DexType, DexClass> rewriteSingleGetClassOrForNameToConstClass(
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/RuntimeWorkaroundCodeRewriter.java b/src/main/java/com/android/tools/r8/ir/optimize/RuntimeWorkaroundCodeRewriter.java
new file mode 100644
index 0000000..20b26b0
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/RuntimeWorkaroundCodeRewriter.java
@@ -0,0 +1,375 @@
+// Copyright (c) 2022, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.optimize;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexString;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.DexTypeList;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
+import com.android.tools.r8.ir.code.AlwaysMaterializingDefinition;
+import com.android.tools.r8.ir.code.AlwaysMaterializingNop;
+import com.android.tools.r8.ir.code.AlwaysMaterializingUser;
+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.IntSwitch;
+import com.android.tools.r8.ir.code.InvokeStatic;
+import com.android.tools.r8.ir.code.NumericType;
+import com.android.tools.r8.ir.code.Value;
+import com.android.tools.r8.utils.InternalOptions;
+import com.google.common.base.Suppliers;
+import com.google.common.collect.ImmutableList;
+import it.unimi.dsi.fastutil.ints.IntArrayList;
+import it.unimi.dsi.fastutil.ints.IntList;
+import java.util.Collections;
+import java.util.ListIterator;
+import java.util.function.Supplier;
+
+public class RuntimeWorkaroundCodeRewriter {
+
+  private static final int SELF_RECURSION_LIMIT = 4;
+
+  // For method with many self-recursive calls, insert a try-catch to disable inlining.
+  // Marshmallow dex2oat aggressively inlines and eats up all the memory on devices.
+  public static void workaroundDex2OatInliningIssue(AppView<?> appView, IRCode code) {
+    if (!appView.options().canHaveDex2OatInliningIssue() || code.hasCatchHandlers()) {
+      // Catch handlers disables inlining, so if the method already has catch handlers
+      // there is nothing to do.
+      return;
+    }
+    int selfRecursionFanOut = 0;
+    Instruction lastSelfRecursiveCall = null;
+    for (Instruction i : code.instructions()) {
+      if (i.isInvokeMethod()
+          && i.asInvokeMethod().getInvokedMethod() == code.method().getReference()) {
+        selfRecursionFanOut++;
+        lastSelfRecursiveCall = i;
+      }
+    }
+    if (selfRecursionFanOut > SELF_RECURSION_LIMIT) {
+      assert lastSelfRecursiveCall != null;
+      // Split out the last recursive call in its own block.
+      InstructionListIterator splitIterator =
+          lastSelfRecursiveCall.getBlock().listIterator(code, lastSelfRecursiveCall);
+      splitIterator.previous();
+      BasicBlock newBlock = splitIterator.split(code, 1);
+      // Generate rethrow block.
+      DexType guard = appView.dexItemFactory().throwableType;
+      BasicBlock rethrowBlock =
+          BasicBlock.createRethrowBlock(code, lastSelfRecursiveCall.getPosition(), guard, appView);
+      code.blocks.add(rethrowBlock);
+      // Add catch handler to the block containing the last recursive call.
+      newBlock.appendCatchHandler(rethrowBlock, guard);
+    }
+  }
+
+  /**
+   * For each block, we look to see if the header matches:
+   *
+   * <pre>
+   *   pseudo-instructions*
+   *   v2 <- long-{mul,div} v0 v1
+   *   pseudo-instructions*
+   *   v5 <- long-{add,sub} v3 v4
+   * </pre>
+   *
+   * where v2 ~=~ v3 or v2 ~=~ v4 (with ~=~ being equal or an alias of) and the block is not a
+   * fallthrough target.
+   */
+  public static void workaroundDex2OatLinkedListBug(IRCode code, InternalOptions options) {
+    if (!options.canHaveDex2OatLinkedListBug()) {
+      return;
+    }
+    DexItemFactory factory = options.itemFactory;
+    final Supplier<DexMethod> javaLangLangSignum =
+        Suppliers.memoize(
+            () ->
+                factory.createMethod(
+                    factory.createString("Ljava/lang/Long;"),
+                    factory.createString("signum"),
+                    factory.intDescriptor,
+                    new DexString[] {factory.longDescriptor}));
+    for (BasicBlock block : code.blocks) {
+      InstructionListIterator it = block.listIterator(code);
+      Instruction firstMaterializing =
+          it.nextUntil(RuntimeWorkaroundCodeRewriter::isNotPseudoInstruction);
+      if (!isLongMul(firstMaterializing)) {
+        continue;
+      }
+      Instruction secondMaterializing =
+          it.nextUntil(RuntimeWorkaroundCodeRewriter::isNotPseudoInstruction);
+      if (!isLongAddOrSub(secondMaterializing)) {
+        continue;
+      }
+      if (isFallthoughTarget(block)) {
+        continue;
+      }
+      Value outOfMul = firstMaterializing.outValue();
+      for (Value inOfAddOrSub : secondMaterializing.inValues()) {
+        if (isAliasOf(inOfAddOrSub, outOfMul)) {
+          it = block.listIterator(code);
+          it.nextUntil(i -> i == firstMaterializing);
+          Value longValue = firstMaterializing.inValues().get(0);
+          InvokeStatic invokeLongSignum =
+              new InvokeStatic(
+                  javaLangLangSignum.get(), null, Collections.singletonList(longValue));
+          ensureThrowingInstructionBefore(code, firstMaterializing, it, invokeLongSignum);
+          return;
+        }
+      }
+    }
+  }
+
+  // If an exceptional edge could target a conditional-loop header ensure that we have a
+  // materializing instruction on that path to work around a bug in some L x86_64 non-emulator VMs.
+  // See b/111337896.
+  public static void workaroundExceptionTargetingLoopHeaderBug(
+      IRCode code, InternalOptions options) {
+    if (!options.canHaveExceptionTargetingLoopHeaderBug()) {
+      return;
+    }
+    for (BasicBlock block : code.blocks) {
+      if (block.hasCatchHandlers()) {
+        for (BasicBlock handler : block.getCatchHandlers().getUniqueTargets()) {
+          // We conservatively assume that a block with at least two normal predecessors is a loop
+          // header. If we ever end up computing exact loop headers, use that here instead.
+          // The loop is conditional if it has at least two normal successors.
+          BasicBlock target = handler.endOfGotoChain();
+          if (target != null
+              && target.getPredecessors().size() > 1
+              && target.getNormalPredecessors().size() > 1
+              && target.getNormalSuccessors().size() > 1) {
+            Instruction fixit = new AlwaysMaterializingNop();
+            fixit.setBlock(handler);
+            fixit.setPosition(handler.getPosition());
+            handler.getInstructions().addFirst(fixit);
+          }
+        }
+      }
+    }
+  }
+
+  public static void workaroundForwardingInitializerBug(IRCode code, InternalOptions options) {
+    if (!options.canHaveForwardingInitInliningBug()) {
+      return;
+    }
+    // Only constructors.
+    if (!code.method().isInstanceInitializer()) {
+      return;
+    }
+    // Only constructors with certain signatures.
+    DexTypeList paramTypes = code.method().getReference().proto.parameters;
+    if (paramTypes.size() != 3
+        || paramTypes.values[0] != options.itemFactory.doubleType
+        || paramTypes.values[1] != options.itemFactory.doubleType
+        || !paramTypes.values[2].isClassType()) {
+      return;
+    }
+    // Only if the constructor contains a super constructor call taking only parameters as
+    // inputs.
+    for (BasicBlock block : code.blocks) {
+      InstructionListIterator it = block.listIterator(code);
+      Instruction superConstructorCall =
+          it.nextUntil(
+              (i) ->
+                  i.isInvokeDirect()
+                      && i.asInvokeDirect().getInvokedMethod().name
+                          == options.itemFactory.constructorMethodName
+                      && i.asInvokeDirect().arguments().size() == 4
+                      && i.asInvokeDirect().arguments().stream().allMatch(Value::isArgument));
+      if (superConstructorCall != null) {
+        // We force a materializing const instruction in front of the super call to make
+        // sure that there is at least one temporary register in the method. That disables
+        // the inlining that is crashing on these devices.
+        ensureInstructionBefore(code, superConstructorCall, it);
+        break;
+      }
+    }
+  }
+
+  public static void workaroundSwitchMaxIntBug(
+      IRCode code, CodeRewriter codeRewriter, InternalOptions options) {
+    if (options.canHaveSwitchMaxIntBug() && code.metadata().mayHaveSwitch()) {
+      // Always rewrite for workaround switch bug.
+      rewriteSwitchForMaxIntOnly(code, codeRewriter);
+    }
+  }
+
+  private static void rewriteSwitchForMaxIntOnly(IRCode code, CodeRewriter codeRewriter) {
+    boolean needToSplitCriticalEdges = false;
+    ListIterator<BasicBlock> blocksIterator = code.listIterator();
+    while (blocksIterator.hasNext()) {
+      BasicBlock block = blocksIterator.next();
+      InstructionListIterator iterator = block.listIterator(code);
+      while (iterator.hasNext()) {
+        Instruction instruction = iterator.next();
+        assert !instruction.isStringSwitch();
+        if (instruction.isIntSwitch()) {
+          IntSwitch intSwitch = instruction.asIntSwitch();
+          if (intSwitch.getKey(intSwitch.numberOfKeys() - 1) == Integer.MAX_VALUE) {
+            if (intSwitch.numberOfKeys() == 1) {
+              codeRewriter.rewriteSingleKeySwitchToIf(code, block, iterator, intSwitch);
+            } else {
+              IntList newSwitchSequences = new IntArrayList(intSwitch.numberOfKeys() - 1);
+              for (int i = 0; i < intSwitch.numberOfKeys() - 1; i++) {
+                newSwitchSequences.add(intSwitch.getKey(i));
+              }
+              IntList outliers = new IntArrayList(1);
+              outliers.add(Integer.MAX_VALUE);
+              codeRewriter.convertSwitchToSwitchAndIfs(
+                  code,
+                  blocksIterator,
+                  block,
+                  iterator,
+                  intSwitch,
+                  ImmutableList.of(newSwitchSequences),
+                  outliers);
+            }
+            needToSplitCriticalEdges = true;
+          }
+        }
+      }
+    }
+
+    // Rewriting of switches introduces new branching structure. It relies on critical edges
+    // being split on the way in but does not maintain this property. We therefore split
+    // critical edges at exit.
+    if (needToSplitCriticalEdges) {
+      code.splitCriticalEdges();
+    }
+  }
+
+  // See comment for InternalOptions.canHaveNumberConversionRegisterAllocationBug().
+  public static void workaroundNumberConversionRegisterAllocationBug(
+      IRCode code, InternalOptions options) {
+    if (!options.canHaveNumberConversionRegisterAllocationBug()) {
+      return;
+    }
+
+    DexItemFactory dexItemFactory = options.dexItemFactory();
+    ListIterator<BasicBlock> blocks = code.listIterator();
+    while (blocks.hasNext()) {
+      BasicBlock block = blocks.next();
+      InstructionListIterator it = block.listIterator(code);
+      while (it.hasNext()) {
+        Instruction instruction = it.next();
+        if (instruction.isArithmeticBinop() || instruction.isNeg()) {
+          for (Value value : instruction.inValues()) {
+            // Insert a call to Double.isNaN on each value which come from a number conversion
+            // to double and flows into an arithmetic instruction. This seems to break the traces
+            // in the Dalvik JIT and avoid the bug where the generated ARM code can clobber float
+            // values in a single-precision registers with double values written to
+            // double-precision registers. See b/77496850 for examples.
+            if (!value.isPhi()
+                && value.definition.isNumberConversion()
+                && value.definition.asNumberConversion().to == NumericType.DOUBLE) {
+              InvokeStatic invokeIsNaN =
+                  new InvokeStatic(
+                      dexItemFactory.doubleMembers.isNaN, null, ImmutableList.of(value));
+              invokeIsNaN.setPosition(instruction.getPosition());
+
+              // Insert the invoke before the current instruction.
+              it.previous();
+              BasicBlock blockWithInvokeNaN =
+                  block.hasCatchHandlers() ? it.split(code, blocks) : block;
+              if (blockWithInvokeNaN != block) {
+                // If we split, add the invoke at the end of the original block.
+                it = block.listIterator(code, block.getInstructions().size());
+                it.previous();
+                it.add(invokeIsNaN);
+                // Continue iteration in the split block.
+                block = blockWithInvokeNaN;
+                it = block.listIterator(code);
+              } else {
+                // Otherwise, add it to the current block.
+                it.add(invokeIsNaN);
+              }
+              // Skip over the instruction causing the invoke to be inserted.
+              Instruction temp = it.next();
+              assert temp == instruction;
+            }
+          }
+        }
+      }
+    }
+  }
+
+  private static void ensureInstructionBefore(
+      IRCode code, Instruction addBefore, InstructionListIterator it) {
+    // Force materialize a constant-zero before the long operation.
+    Instruction check = it.previous();
+    assert addBefore == check;
+    // Forced definition of const-zero
+    Value fixitValue = code.createValue(TypeElement.getInt());
+    Instruction fixitDefinition = new AlwaysMaterializingDefinition(fixitValue);
+    fixitDefinition.setBlock(addBefore.getBlock());
+    fixitDefinition.setPosition(addBefore.getPosition());
+    it.add(fixitDefinition);
+    // Forced user of the forced definition to ensure it has a user and thus live range.
+    Instruction fixitUser = new AlwaysMaterializingUser(fixitValue);
+    fixitUser.setBlock(addBefore.getBlock());
+    fixitUser.setPosition(addBefore.getPosition());
+    it.add(fixitUser);
+  }
+
+  private static void ensureThrowingInstructionBefore(
+      IRCode code, Instruction addBefore, InstructionListIterator it, Instruction instruction) {
+    Instruction check = it.previous();
+    assert addBefore == check;
+    BasicBlock block = check.getBlock();
+    if (block.hasCatchHandlers()) {
+      // Split so the existing instructions retain their handlers and the new instruction has none.
+      BasicBlock split = it.split(code);
+      assert split.hasCatchHandlers();
+      assert !block.hasCatchHandlers();
+      it = block.listIterator(code, block.getInstructions().size() - 1);
+    }
+    instruction.setPosition(addBefore.getPosition());
+    it.add(instruction);
+  }
+
+  private static boolean isNotPseudoInstruction(Instruction instruction) {
+    return !(instruction.isDebugInstruction() || instruction.isMove());
+  }
+
+  private static boolean isAliasOf(Value usedValue, Value definingValue) {
+    while (true) {
+      if (usedValue == definingValue) {
+        return true;
+      }
+      Instruction definition = usedValue.definition;
+      if (definition == null || !definition.isMove()) {
+        return false;
+      }
+      usedValue = definition.asMove().src();
+    }
+  }
+
+  private static boolean isLongMul(Instruction instruction) {
+    return instruction != null
+        && instruction.isMul()
+        && instruction.asBinop().getNumericType() == NumericType.LONG
+        && instruction.outValue() != null;
+  }
+
+  private static boolean isLongAddOrSub(Instruction instruction) {
+    return instruction != null
+        && (instruction.isAdd() || instruction.isSub())
+        && instruction.asBinop().getNumericType() == NumericType.LONG;
+  }
+
+  private static boolean isFallthoughTarget(BasicBlock block) {
+    for (BasicBlock pred : block.getPredecessors()) {
+      if (pred.exit().fallthroughBlock() == block) {
+        return true;
+      }
+    }
+    return false;
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInliner.java b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInliner.java
index 442cb96..ccbd132 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInliner.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInliner.java
@@ -229,7 +229,7 @@
         assumeRemover.removeMarkedInstructions();
         code.removeAllDeadAndTrivialPhis(affectedValues);
         assumeRemover.finish();
-        assert code.isConsistentSSA();
+        assert code.isConsistentSSA(appView);
         rootsIterator.remove();
         repeat = true;
       }
@@ -253,8 +253,7 @@
       codeRewriter.simplifyControlFlow(code);
       // If a method was inlined we may see more trivial computation/conversion of String.
       boolean isDebugMode =
-          appView.options().debug
-              || method.getDefinition().getOptimizationInfo().isReachabilitySensitive();
+          appView.options().debug || method.getOrComputeReachabilitySensitive(appView);
       if (!isDebugMode) {
         // Reflection/string optimization 3. trivial conversion/computation on const-string
         stringOptimizer.computeTrivialOperationsOnConstString(code);
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 c3c21f1..974bedd 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
@@ -1028,7 +1028,7 @@
     // signature of the invocation resolves to a private or static method.
     // TODO(b/147212189): Why not inline private methods? If access is permitted it is valid.
     MethodResolutionResult resolutionResult =
-        appView.appInfo().resolveMethodOnClass(callee, eligibleClass);
+        appView.appInfo().resolveMethodOnClass(eligibleClass, callee);
     if (resolutionResult.isSingleResolution()
         && !resolutionResult.getSingleTarget().isNonPrivateVirtualMethod()) {
       return false;
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/analysis/TransferFunction.java b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/analysis/TransferFunction.java
index 4d45a08..65c05e6 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/analysis/TransferFunction.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/analysis/TransferFunction.java
@@ -247,7 +247,10 @@
     }
 
     SingleResolutionResult resolutionResult =
-        appView.appInfo().resolveMethodOnClass(invoke.getInvokedMethod()).asSingleResolution();
+        appView
+            .appInfo()
+            .resolveMethodOnClassHolder(invoke.getInvokedMethod())
+            .asSingleResolution();
     if (resolutionResult == null) {
       return state.abandonClassInliningInCurrentContexts(receiverRoot);
     }
@@ -283,7 +286,10 @@
     }
 
     SingleResolutionResult resolutionResult =
-        appView.appInfo().resolveMethodOnInterface(invoke.getInvokedMethod()).asSingleResolution();
+        appView
+            .appInfo()
+            .resolveMethodOnInterfaceHolder(invoke.getInvokedMethod())
+            .asSingleResolution();
     if (resolutionResult == null) {
       return state.abandonClassInliningInCurrentContexts(receiverRoot);
     }
@@ -322,7 +328,10 @@
     }
 
     SingleResolutionResult resolutionResult =
-        appView.appInfo().resolveMethodOnClass(invoke.getInvokedMethod()).asSingleResolution();
+        appView
+            .appInfo()
+            .resolveMethodOnClassHolder(invoke.getInvokedMethod())
+            .asSingleResolution();
     if (resolutionResult == null) {
       return state.abandonClassInliningInCurrentContexts(receiverRoot);
     }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/EmptyEnumUnboxer.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EmptyEnumUnboxer.java
index 23ae546..bde3a44 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/enums/EmptyEnumUnboxer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EmptyEnumUnboxer.java
@@ -13,7 +13,6 @@
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.Phi;
 import com.android.tools.r8.ir.conversion.IRConverter;
-import com.android.tools.r8.ir.conversion.MethodConversionOptions.MutableMethodConversionOptions;
 import com.android.tools.r8.ir.conversion.MethodProcessor;
 import com.android.tools.r8.ir.conversion.PostMethodProcessor.Builder;
 import com.android.tools.r8.ir.optimize.info.OptimizationFeedbackDelayed;
@@ -38,7 +37,7 @@
   }
 
   @Override
-  public void analyzeEnums(IRCode code, MutableMethodConversionOptions conversionOptions) {
+  public void analyzeEnums(IRCode code, MethodProcessor methodProcessor) {
     // Intentionally empty.
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxer.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxer.java
index ae119f6..659d802 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxer.java
@@ -13,7 +13,6 @@
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.Phi;
 import com.android.tools.r8.ir.conversion.IRConverter;
-import com.android.tools.r8.ir.conversion.MethodConversionOptions.MutableMethodConversionOptions;
 import com.android.tools.r8.ir.conversion.MethodProcessor;
 import com.android.tools.r8.ir.conversion.PostMethodProcessor.Builder;
 import com.android.tools.r8.ir.optimize.info.OptimizationFeedbackDelayed;
@@ -35,7 +34,7 @@
   public abstract void prepareForPrimaryOptimizationPass(
       GraphLens graphLensForPrimaryOptimizationPass);
 
-  public abstract void analyzeEnums(IRCode code, MutableMethodConversionOptions conversionOptions);
+  public abstract void analyzeEnums(IRCode code, MethodProcessor methodProcessor);
 
   public abstract void onMethodPruned(ProgramMethod method);
 
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 700c1d7..f455175 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
@@ -75,7 +75,6 @@
 import com.android.tools.r8.ir.code.Return;
 import com.android.tools.r8.ir.code.Value;
 import com.android.tools.r8.ir.conversion.IRConverter;
-import com.android.tools.r8.ir.conversion.MethodConversionOptions.MutableMethodConversionOptions;
 import com.android.tools.r8.ir.conversion.MethodProcessor;
 import com.android.tools.r8.ir.conversion.PostMethodProcessor.Builder;
 import com.android.tools.r8.ir.desugar.LambdaDescriptor;
@@ -236,7 +235,7 @@
   }
 
   @Override
-  public void analyzeEnums(IRCode code, MutableMethodConversionOptions conversionOptions) {
+  public void analyzeEnums(IRCode code, MethodProcessor methodProcessor) {
     Set<DexType> eligibleEnums = Sets.newIdentityHashSet();
     for (BasicBlock block : code.blocks) {
       for (Instruction instruction : block.getInstructions()) {
@@ -307,7 +306,8 @@
       }
     }
     if (methodsDependingOnLibraryModelisation.contains(code.context(), appView.graphLens())) {
-      conversionOptions.disablePeepholeOptimizations();
+      code.mutateConversionOptions(
+          conversionOptions -> conversionOptions.disablePeepholeOptimizations(methodProcessor));
     }
   }
 
@@ -706,6 +706,9 @@
             enumUnboxingLens,
             enumDataMap,
             utilityClasses);
+
+    // Ensure determinism of method-to-reprocess set.
+    appView.testing().checkDeterminism(postMethodProcessorBuilder::dump);
   }
 
   private void updateOptimizationInfos(
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 f223300..bc981ea 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
@@ -120,7 +120,7 @@
     if (unboxedEnumsData.isEmpty()) {
       return Sets.newIdentityHashSet();
     }
-    assert code.isConsistentSSABeforeTypesAreCorrect();
+    assert code.isConsistentSSABeforeTypesAreCorrect(appView);
     ProgramMethod context = code.context();
     Map<Instruction, DexType> convertedEnums = createInitialConvertedEnums(code, prototypeChanges);
     Set<Phi> affectedPhis = Sets.newIdentityHashSet();
@@ -351,7 +351,7 @@
         }
       }
     }
-    assert code.isConsistentSSABeforeTypesAreCorrect();
+    assert code.isConsistentSSABeforeTypesAreCorrect(appView);
     return affectedPhis;
   }
 
@@ -461,7 +461,7 @@
       ProgramMethod checkNotZeroMethod =
           appView
               .appInfo()
-              .resolveMethodOnClass(checkNotZeroMethodReference)
+              .resolveMethodOnClassHolder(checkNotZeroMethodReference)
               .getResolvedProgramMethod();
       if (checkNotZeroMethod != null) {
         EnumUnboxerMethodClassification classification =
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumValueOptimizer.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumValueOptimizer.java
index 293d93f..0d6fa85 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumValueOptimizer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumValueOptimizer.java
@@ -160,7 +160,7 @@
         DexEncodedMethod singleTarget =
             appView
                 .appInfo()
-                .resolveMethodOnClass(factory.objectMembers.toString, enumFieldType.getClassType())
+                .resolveMethodOnClass(enumFieldType.getClassType(), factory.objectMembers.toString)
                 .getSingleTarget();
         if (singleTarget != null && singleTarget.getReference() != factory.enumMembers.toString) {
           continue;
@@ -175,7 +175,7 @@
     if (!affectedValues.isEmpty()) {
       new TypeAnalysis(appView).narrowing(affectedValues);
     }
-    assert code.isConsistentSSA();
+    assert code.isConsistentSSA(appView);
   }
 
   /**
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/code/CheckNotZeroCode.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/code/CheckNotZeroCode.java
index c2cbec9..dd73d7e 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/enums/code/CheckNotZeroCode.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/code/CheckNotZeroCode.java
@@ -17,6 +17,7 @@
 import com.android.tools.r8.ir.code.Instruction;
 import com.android.tools.r8.ir.code.InstructionListIterator;
 import com.android.tools.r8.ir.code.Return;
+import com.android.tools.r8.ir.conversion.MethodConversionOptions.MutableMethodConversionOptions;
 import com.android.tools.r8.ir.optimize.enums.EnumUnboxerImpl;
 import com.android.tools.r8.naming.ClassNameMapper;
 import com.android.tools.r8.origin.Origin;
@@ -41,7 +42,11 @@
   }
 
   @Override
-  public IRCode buildIR(ProgramMethod checkNotZeroMethod, AppView<?> appView, Origin origin) {
+  public IRCode buildIR(
+      ProgramMethod checkNotZeroMethod,
+      AppView<?> appView,
+      Origin origin,
+      MutableMethodConversionOptions conversionOptions) {
     // Build IR from the checkNotNull() method.
     IRCode code = checkNotNullMethod.buildIR(appView);
     InstructionListIterator instructionIterator = code.instructionListIterator();
@@ -82,7 +87,8 @@
         code.valueNumberGenerator,
         code.basicBlockNumberGenerator,
         code.metadata(),
-        checkNotZeroMethod.getOrigin());
+        checkNotZeroMethod.getOrigin(),
+        conversionOptions);
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/DefaultMethodOptimizationInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/DefaultMethodOptimizationInfo.java
index 9faaebc..c9f6819 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/DefaultMethodOptimizationInfo.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/DefaultMethodOptimizationInfo.java
@@ -106,11 +106,6 @@
   }
 
   @Override
-  public boolean isReachabilitySensitive() {
-    return false;
-  }
-
-  @Override
   public boolean returnsArgument() {
     return false;
   }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfo.java
index ffcad46..c36ee4d 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfo.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfo.java
@@ -54,8 +54,6 @@
 
   public abstract boolean hasBeenInlinedIntoSingleCallSite();
 
-  public abstract boolean isReachabilitySensitive();
-
   public abstract boolean returnsArgument();
 
   public abstract int getReturnedArgument();
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 29a843b..8d2e339 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
@@ -965,7 +965,7 @@
     MethodResolutionResult resolutionResult =
         appView
             .appInfo()
-            .resolveMethodOnClass(appView.dexItemFactory().objectMembers.finalize, clazz);
+            .resolveMethodOnClass(clazz, appView.dexItemFactory().objectMembers.finalize);
     DexEncodedMethod target = resolutionResult.getSingleTarget();
     return target != null
         && target.getReference() != dexItemFactory.enumMembers.finalize
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/MutableMethodOptimizationInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/MutableMethodOptimizationInfo.java
index b810885..df68f40 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/MutableMethodOptimizationInfo.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/MutableMethodOptimizationInfo.java
@@ -88,14 +88,9 @@
   private static final int HAS_BEEN_INLINED_INTO_SINGLE_CALL_SITE_FLAG = 0x4;
   private static final int MAY_HAVE_SIDE_EFFECT_FLAG = 0x8;
   private static final int RETURN_VALUE_ONLY_DEPENDS_ON_ARGUMENTS_FLAG = 0x10;
-  private static final int UNUSED_FLAG_1 = 0x20;
-  private static final int NEVER_RETURNS_NORMALLY_FLAG = 0x40;
-  private static final int UNUSED_FLAG_2 = 0x80;
-  private static final int UNUSED_FLAG_3 = 0x100;
-  private static final int UNUSED_FLAG_4 = 0x200;
-  private static final int INITIALIZER_ENABLING_JAVA_ASSERTIONS_FLAG = 0x400;
-  private static final int REACHABILITY_SENSITIVE_FLAG = 0x800;
-  private static final int RETURN_VALUE_HAS_BEEN_PROPAGATED_FLAG = 0x1000;
+  private static final int NEVER_RETURNS_NORMALLY_FLAG = 0x20;
+  private static final int INITIALIZER_ENABLING_JAVA_ASSERTIONS_FLAG = 0x80;
+  private static final int RETURN_VALUE_HAS_BEEN_PROPAGATED_FLAG = 0x100;
 
   private static final int DEFAULT_FLAGS;
 
@@ -114,19 +109,12 @@
     defaultFlags |=
         BooleanUtils.intValue(defaultOptInfo.returnValueOnlyDependsOnArguments())
             * RETURN_VALUE_ONLY_DEPENDS_ON_ARGUMENTS_FLAG;
-    defaultFlags |= 0 * UNUSED_FLAG_1;
     defaultFlags |=
         BooleanUtils.intValue(defaultOptInfo.neverReturnsNormally()) * NEVER_RETURNS_NORMALLY_FLAG;
-    defaultFlags |= 0 * UNUSED_FLAG_2;
-    defaultFlags |= 0 * UNUSED_FLAG_3;
-    defaultFlags |= 0 * UNUSED_FLAG_4;
     defaultFlags |=
         BooleanUtils.intValue(defaultOptInfo.isInitializerEnablingJavaVmAssertions())
             * INITIALIZER_ENABLING_JAVA_ASSERTIONS_FLAG;
     defaultFlags |=
-        BooleanUtils.intValue(defaultOptInfo.isReachabilitySensitive())
-            * REACHABILITY_SENSITIVE_FLAG;
-    defaultFlags |=
         BooleanUtils.intValue(defaultOptInfo.returnValueHasBeenPropagated())
             * RETURN_VALUE_HAS_BEEN_PROPAGATED_FLAG;
     DEFAULT_FLAGS = defaultFlags;
@@ -381,11 +369,6 @@
   }
 
   @Override
-  public boolean isReachabilitySensitive() {
-    return isFlagSet(REACHABILITY_SENSITIVE_FLAG);
-  }
-
-  @Override
   public boolean returnsArgument() {
     return returnedArgument != -1;
   }
@@ -515,14 +498,6 @@
     return isFlagSet(RETURN_VALUE_ONLY_DEPENDS_ON_ARGUMENTS_FLAG);
   }
 
-  public void setReachabilitySensitive(boolean reachabilitySensitive) {
-    setFlag(REACHABILITY_SENSITIVE_FLAG, reachabilitySensitive);
-  }
-
-  void unsetReachabilitySensitive() {
-    clearFlag(REACHABILITY_SENSITIVE_FLAG);
-  }
-
   void setSimpleInliningConstraint(SimpleInliningConstraint constraint) {
     this.simpleInliningConstraint = constraint;
   }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackDelayed.java b/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackDelayed.java
index 8853607..af6cf62 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackDelayed.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackDelayed.java
@@ -364,11 +364,6 @@
   }
 
   @Override
-  public synchronized void unsetReachabilitySensitive(ProgramMethod method) {
-    getMethodOptimizationInfoForUpdating(method).unsetReachabilitySensitive();
-  }
-
-  @Override
   public synchronized void unsetReturnedArgument(ProgramMethod method) {
     getMethodOptimizationInfoForUpdating(method).unsetReturnedArgument();
   }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackIgnore.java b/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackIgnore.java
index d15bb00..3f46ce1 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackIgnore.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackIgnore.java
@@ -176,9 +176,6 @@
   public void unsetNonNullParamOrThrow(ProgramMethod method) {}
 
   @Override
-  public void unsetReachabilitySensitive(ProgramMethod method) {}
-
-  @Override
   public void unsetReturnedArgument(ProgramMethod method) {}
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackSimple.java b/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackSimple.java
index 5b65a8a..7a6a3ce 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackSimple.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackSimple.java
@@ -305,12 +305,6 @@
   }
 
   @Override
-  public void unsetReachabilitySensitive(ProgramMethod method) {
-    withMutableMethodOptimizationInfo(
-        method, MutableMethodOptimizationInfo::unsetReachabilitySensitive);
-  }
-
-  @Override
   public void unsetReturnedArgument(ProgramMethod method) {
     withMutableMethodOptimizationInfo(method, MutableMethodOptimizationInfo::unsetReturnedArgument);
   }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/membervaluepropagation/assume/AssumeInfoLookup.java b/src/main/java/com/android/tools/r8/ir/optimize/membervaluepropagation/assume/AssumeInfoLookup.java
index 85fb76a..b28520c 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/membervaluepropagation/assume/AssumeInfoLookup.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/membervaluepropagation/assume/AssumeInfoLookup.java
@@ -4,6 +4,7 @@
 
 package com.android.tools.r8.ir.optimize.membervaluepropagation.assume;
 
+import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexClassAndMember;
 import com.android.tools.r8.graph.DexClassAndMethod;
@@ -31,10 +32,10 @@
   }
 
   public static AssumeInfo lookupAssumeInfo(
-      AppView<AppInfoWithLiveness> appView, DexClassAndMember<?, ?> member) {
+      AppView<? extends AppInfoWithClassHierarchy> appView, DexClassAndMember<?, ?> member) {
     DexMember<?, ?> reference = member.getReference();
-    ProguardMemberRule assumeNoSideEffectsRule = appView.appInfo().noSideEffects.get(reference);
-    ProguardMemberRule assumeValuesRule = appView.appInfo().assumedValues.get(reference);
+    ProguardMemberRule assumeNoSideEffectsRule = appView.rootSet().noSideEffects.get(reference);
+    ProguardMemberRule assumeValuesRule = appView.rootSet().assumedValues.get(reference);
     if (assumeNoSideEffectsRule == null && assumeValuesRule == null) {
       return null;
     }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/string/StringBuilderOptimizer.java b/src/main/java/com/android/tools/r8/ir/optimize/string/StringBuilderOptimizer.java
index a2ecba8..b851f1e 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/string/StringBuilderOptimizer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/string/StringBuilderOptimizer.java
@@ -18,7 +18,6 @@
 import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.code.Assume;
 import com.android.tools.r8.ir.code.BasicBlock;
-import com.android.tools.r8.ir.code.BasicBlock.ThrowingInfo;
 import com.android.tools.r8.ir.code.ConstNumber;
 import com.android.tools.r8.ir.code.ConstString;
 import com.android.tools.r8.ir.code.DominatorTree;
@@ -85,7 +84,6 @@
 
   private final AppView<?> appView;
   private final DexItemFactory factory;
-  private final ThrowingInfo throwingInfo;
   @VisibleForTesting
   StringConcatenationAnalysis analysis;
   final StringBuilderOptimizationConfiguration optimizationConfiguration;
@@ -108,7 +106,6 @@
   public StringBuilderOptimizer(AppView<? extends AppInfo> appView) {
     this.appView = appView;
     this.factory = appView.dexItemFactory();
-    this.throwingInfo = ThrowingInfo.defaultForConstString(appView.options());
     this.optimizationConfiguration = new DefaultStringBuilderOptimizationConfiguration();
     if (Log.ENABLED && Log.isLoggingEnabledFor(StringBuilderOptimizer.class)) {
       histogramOfLengthOfAppendChains = new Object2IntArrayMap<>();
@@ -819,7 +816,7 @@
       if (!affectedValues.isEmpty()) {
         new TypeAnalysis(appView).narrowing(affectedValues);
       }
-      assert code.isConsistentSSA();
+      assert code.isConsistentSSA(appView);
     }
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/typechecks/CheckCastAndInstanceOfMethodSpecialization.java b/src/main/java/com/android/tools/r8/ir/optimize/typechecks/CheckCastAndInstanceOfMethodSpecialization.java
index 8c6a531..93f6242 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/typechecks/CheckCastAndInstanceOfMethodSpecialization.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/typechecks/CheckCastAndInstanceOfMethodSpecialization.java
@@ -133,7 +133,7 @@
     if (abstractParentReturnValue == abstractReturnValue) {
       // The parent method is already guaranteed to return the same value.
     } else if (isClassAccessible(method.getHolder(), parentMethod, appView).isTrue()) {
-      parentMethodDefinition.setCode(
+      parentMethod.setCode(
           parentMethodDefinition.buildInstanceOfCode(
               method.getHolderType(), abstractParentReturnValue.isTrue(), appView.options()),
           appView);
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 827f316..af1ed03 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
@@ -207,7 +207,7 @@
   public void allocateRegisters() {
     // There are no linked values prior to register allocation.
     assert noLinkedValues();
-    assert code.isConsistentSSA();
+    assert code.isConsistentSSA(appView);
     if (this.code.method().accessFlags.isBridge() && implementationIsBridge(this.code)) {
       transformBridgeMethod();
     }
@@ -216,7 +216,7 @@
     insertRangeInvokeMoves();
     ImmutableList<BasicBlock> blocks = computeLivenessInformation();
     performAllocation();
-    assert code.isConsistentGraph();
+    assert code.isConsistentGraph(appView);
     if (Log.ENABLED) {
       Log.debug(this.getClass(), toString());
     }
@@ -227,7 +227,7 @@
     // and we do not actually want locals information in the output.
     if (options().debug) {
       computeDebugInfo(code, blocks, liveIntervals, this, liveAtEntrySets);
-    } else if (code.method().getOptimizationInfo().isReachabilitySensitive()) {
+    } else if (code.context().getOrComputeReachabilitySensitive(appView)) {
       InstructionListIterator it = code.instructionListIterator();
       while (it.hasNext()) {
         Instruction instruction = it.next();
@@ -1642,7 +1642,7 @@
     // Set all free positions for possible registers to max integer.
     RegisterPositions freePositions = new RegisterPositionsImpl(registerConstraint + 1);
 
-    if ((options().debug || code.method().getOptimizationInfo().isReachabilitySensitive())
+    if ((options().debug || code.context().getOrComputeReachabilitySensitive(appView))
         && !code.method().accessFlags.isStatic()) {
       // If we are generating debug information or if the method is reachability sensitive,
       // we pin the this value register. The debugger expects to always be able to find it in
@@ -2508,11 +2508,7 @@
   }
 
   private static void addLiveRange(
-      Value value,
-      BasicBlock block,
-      int end,
-      List<LiveIntervals> liveIntervals,
-      InternalOptions options) {
+      Value value, BasicBlock block, int end, List<LiveIntervals> liveIntervals, IRCode code) {
     int firstInstructionInBlock = block.entry().getNumber();
     int instructionsSize = block.getInstructions().size() * INSTRUCTION_NUMBER_DELTA;
     int lastInstructionInBlock =
@@ -2548,8 +2544,8 @@
         instructionNumber--;
       }
       intervals.addRange(new LiveRange(instructionNumber, end));
-      assert unconstrainedForCf(intervals.getRegisterLimit(), options);
-      if (options.isGeneratingDex() && !value.isPhi()) {
+      assert unconstrainedForCf(intervals.getRegisterLimit(), code);
+      if (code.getConversionOptions().isGeneratingDex() && !value.isPhi()) {
         int constraint = value.definition.maxOutValueRegister();
         intervals.addUse(new LiveIntervalsUse(instructionNumber, constraint));
       }
@@ -2559,7 +2555,7 @@
   }
 
   private void computeLiveRanges() {
-    computeLiveRanges(options(), code, liveAtEntrySets, liveIntervals);
+    computeLiveRanges(appView, code, liveAtEntrySets, liveIntervals);
     // Art VMs before Android M assume that the register for the receiver never changes its value.
     // This assumption is used during verification. Allowing the receiver register to be
     // overwritten can therefore lead to verification errors. If we could be targeting one of these
@@ -2583,7 +2579,7 @@
 
   /** Compute live ranges based on liveAtEntry sets for all basic blocks. */
   public static void computeLiveRanges(
-      InternalOptions options,
+      AppView<?> appView,
       IRCode code,
       Map<BasicBlock, LiveAtEntrySets> liveAtEntrySets,
       List<LiveIntervals> liveIntervals) {
@@ -2622,7 +2618,7 @@
         if (phiOperands.contains(value)) {
           end--;
         }
-        addLiveRange(value, block, end, liveIntervals, options);
+        addLiveRange(value, block, end, liveIntervals, code);
       }
       InstructionIterator iterator = block.iterator(block.getInstructions().size());
       while (iterator.hasPrevious()) {
@@ -2642,20 +2638,20 @@
                 block,
                 instruction.getNumber() + INSTRUCTION_NUMBER_DELTA - 1,
                 liveIntervals,
-                options);
-            assert !options.isGeneratingClassFiles() || instruction.isArgument()
+                code);
+            assert !code.getConversionOptions().isGeneratingClassFiles() || instruction.isArgument()
                 : "Arguments should be the only potentially unused local in CF";
           }
           live.remove(definition);
         }
         for (Value use : instruction.inValues()) {
           if (use.needsRegister()) {
-            assert unconstrainedForCf(instruction.maxInValueRegister(), options);
+            assert unconstrainedForCf(instruction.maxInValueRegister(), code);
             if (!live.contains(use)) {
               live.add(use);
-              addLiveRange(use, block, instruction.getNumber(), liveIntervals, options);
+              addLiveRange(use, block, instruction.getNumber(), liveIntervals, code);
             }
-            if (options.isGeneratingDex()) {
+            if (code.getConversionOptions().isGeneratingDex()) {
               int inConstraint = instruction.maxInValueRegister();
               LiveIntervals useIntervals = use.getLiveIntervals();
               // Arguments are always kept in their original, incoming register. For every
@@ -2693,11 +2689,11 @@
                   block,
                   getLiveRangeEndOnExceptionalFlow(instruction, use),
                   liveIntervals,
-                  options);
+                  code);
             }
           }
         }
-        if (options.debug || code.method().getOptimizationInfo().isReachabilitySensitive()) {
+        if (appView.options().debug || code.context().getOrComputeReachabilitySensitive(appView)) {
           // In debug mode, or if the method is reachability sensitive, extend the live range
           // to cover the full scope of a local variable (encoded as debug values).
           int number = instruction.getNumber();
@@ -2707,7 +2703,7 @@
             assert use.needsRegister();
             if (!live.contains(use)) {
               live.add(use);
-              addLiveRange(use, block, number, liveIntervals, options);
+              addLiveRange(use, block, number, liveIntervals, code);
             }
           }
         }
@@ -2723,8 +2719,8 @@
     return end;
   }
 
-  private static boolean unconstrainedForCf(int constraint, InternalOptions options) {
-    return !options.isGeneratingClassFiles() || constraint == Constants.U16BIT_MAX;
+  private static boolean unconstrainedForCf(int constraint, IRCode code) {
+    return code.getConversionOptions().isGeneratingDex() || constraint == Constants.U16BIT_MAX;
   }
 
   private void clearUserInfo() {
diff --git a/src/main/java/com/android/tools/r8/ir/synthetic/AbstractSynthesizedCode.java b/src/main/java/com/android/tools/r8/ir/synthetic/AbstractSynthesizedCode.java
index acf4d56..8c28b9e 100644
--- a/src/main/java/com/android/tools/r8/ir/synthetic/AbstractSynthesizedCode.java
+++ b/src/main/java/com/android/tools/r8/ir/synthetic/AbstractSynthesizedCode.java
@@ -18,6 +18,8 @@
 import com.android.tools.r8.ir.code.NumberGenerator;
 import com.android.tools.r8.ir.code.Position;
 import com.android.tools.r8.ir.conversion.IRBuilder;
+import com.android.tools.r8.ir.conversion.MethodConversionOptions.MutableMethodConversionOptions;
+import com.android.tools.r8.ir.conversion.MethodConversionOptions.ThrowingMethodConversionOptions;
 import com.android.tools.r8.ir.conversion.SourceCode;
 import com.android.tools.r8.naming.ClassNameMapper;
 import com.android.tools.r8.origin.Origin;
@@ -39,9 +41,13 @@
   }
 
   @Override
-  public final IRCode buildIR(ProgramMethod method, AppView<?> appView, Origin origin) {
+  public final IRCode buildIR(
+      ProgramMethod method,
+      AppView<?> appView,
+      Origin origin,
+      MutableMethodConversionOptions conversionOptions) {
     return IRBuilder.create(method, appView, getSourceCodeProvider().get(method, null), origin)
-        .build(method);
+        .build(method, conversionOptions);
   }
 
   @Override
@@ -62,7 +68,7 @@
             origin,
             valueNumberGenerator,
             protoChanges)
-        .build(context);
+        .build(context, new ThrowingMethodConversionOptions(appView.options()));
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/synthetic/EnumUnboxingCfCodeProvider.java b/src/main/java/com/android/tools/r8/ir/synthetic/EnumUnboxingCfCodeProvider.java
index 5ef065a..6427f75 100644
--- a/src/main/java/com/android/tools/r8/ir/synthetic/EnumUnboxingCfCodeProvider.java
+++ b/src/main/java/com/android/tools/r8/ir/synthetic/EnumUnboxingCfCodeProvider.java
@@ -54,9 +54,14 @@
       assert returnType == appView.dexItemFactory().stringType;
       instructions.add(new CfConstString(value.asSingleStringValue().getDexString()));
     } else if (value.isSingleNumberValue()) {
-      instructions.add(
-          new CfConstNumber(
-              value.asSingleNumberValue().getValue(), ValueType.fromDexType(returnType)));
+      if (returnType.isReferenceType()) {
+        assert value.isNull();
+        instructions.add(new CfConstNull());
+      } else {
+        instructions.add(
+            new CfConstNumber(
+                value.asSingleNumberValue().getValue(), ValueType.fromDexType(returnType)));
+      }
     } else {
       throw new Unreachable("Only Number and String fields in enums are supported.");
     }
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 1332bf7..7f8d075 100644
--- a/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java
+++ b/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java
@@ -48,6 +48,7 @@
 import com.android.tools.r8.utils.AsmUtils;
 import com.android.tools.r8.utils.ComparatorUtils;
 import com.android.tools.r8.utils.ExceptionUtils;
+import com.android.tools.r8.utils.InternalGlobalSyntheticsProgramConsumer.InternalGlobalSyntheticsCfConsumer;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.OriginalSourceFiles;
 import com.android.tools.r8.utils.PredicateUtils;
@@ -57,6 +58,7 @@
 import java.io.PrintWriter;
 import java.io.StringWriter;
 import java.util.ArrayList;
+import java.util.Collection;
 import java.util.Comparator;
 import java.util.List;
 import java.util.Optional;
@@ -149,36 +151,67 @@
       sourceFileEnvironment = ApplicationWriter.createSourceFileEnvironment(proguardMapId);
     }
     LensCodeRewriterUtils rewriter = new LensCodeRewriterUtils(appView);
-    for (DexProgramClass clazz : application.classes()) {
-      assert SyntheticNaming.verifyNotInternalSynthetic(clazz.getType());
-      try {
-        writeClass(clazz, consumer, rewriter, markerString, sourceFileEnvironment);
-      } catch (ClassTooLargeException e) {
-        throw appView
-            .options()
-            .reporter
-            .fatalError(
-                new ConstantPoolOverflowDiagnostic(
-                    clazz.getOrigin(),
-                    Reference.classFromBinaryName(e.getClassName()),
-                    e.getConstantPoolCount()));
-      } catch (MethodTooLargeException e) {
-        throw appView
-            .options()
-            .reporter
-            .fatalError(
-                new CodeSizeOverflowDiagnostic(
-                    clazz.getOrigin(),
-                    Reference.methodFromDescriptor(
-                        Reference.classFromBinaryName(e.getClassName()).getDescriptor(),
-                        e.getMethodName(),
-                        e.getDescriptor()),
-                    e.getCodeSize()));
+    Collection<DexProgramClass> classes = application.classes();
+    Collection<DexProgramClass> globalSyntheticClasses = new ArrayList<>();
+    if (options.intermediate && options.hasGlobalSyntheticsConsumer()) {
+      Collection<DexProgramClass> allClasses = classes;
+      classes = new ArrayList<>(allClasses.size());
+      for (DexProgramClass clazz : allClasses) {
+        if (appView.getSyntheticItems().isGlobalSyntheticClass(clazz)) {
+          globalSyntheticClasses.add(clazz);
+        } else {
+          classes.add(clazz);
+        }
       }
     }
+    for (DexProgramClass clazz : classes) {
+      writeClassCatchingErrors(clazz, consumer, rewriter, markerString, sourceFileEnvironment);
+    }
+    if (!globalSyntheticClasses.isEmpty()) {
+      InternalGlobalSyntheticsCfConsumer globalsConsumer =
+          new InternalGlobalSyntheticsCfConsumer(options.getGlobalSyntheticsConsumer());
+      for (DexProgramClass clazz : globalSyntheticClasses) {
+        writeClassCatchingErrors(
+            clazz, globalsConsumer, rewriter, markerString, sourceFileEnvironment);
+      }
+      globalsConsumer.finished(options.reporter);
+    }
     ApplicationWriter.supplyAdditionalConsumers(application, appView, namingLens, options);
   }
 
+  private void writeClassCatchingErrors(
+      DexProgramClass clazz,
+      ClassFileConsumer consumer,
+      LensCodeRewriterUtils rewriter,
+      Optional<String> markerString,
+      SourceFileEnvironment sourceFileEnvironment) {
+    assert SyntheticNaming.verifyNotInternalSynthetic(clazz.getType());
+    try {
+      writeClass(clazz, consumer, rewriter, markerString, sourceFileEnvironment);
+    } catch (ClassTooLargeException e) {
+      throw appView
+          .options()
+          .reporter
+          .fatalError(
+              new ConstantPoolOverflowDiagnostic(
+                  clazz.getOrigin(),
+                  Reference.classFromBinaryName(e.getClassName()),
+                  e.getConstantPoolCount()));
+    } catch (MethodTooLargeException e) {
+      throw appView
+          .options()
+          .reporter
+          .fatalError(
+              new CodeSizeOverflowDiagnostic(
+                  clazz.getOrigin(),
+                  Reference.methodFromDescriptor(
+                      Reference.classFromBinaryName(e.getClassName()).getDescriptor(),
+                      e.getMethodName(),
+                      e.getDescriptor()),
+                  e.getCodeSize()));
+    }
+  }
+
   private void writeClass(
       DexProgramClass clazz,
       ClassFileConsumer consumer,
@@ -594,6 +627,7 @@
       MethodVisitor visitor) {
     Code code = method.getDefinition().getCode();
     assert code.isCfWritableCode();
+    assert code.estimatedDexCodeSizeUpperBoundInBytes() > 0;
     code.asCfWritableCode()
         .writeCf(method, classFileVersion, appView, namingLens, rewriter, visitor);
   }
diff --git a/src/main/java/com/android/tools/r8/naming/signature/GenericSignatureRewriter.java b/src/main/java/com/android/tools/r8/naming/signature/GenericSignatureRewriter.java
index bc285b7..e50737d 100644
--- a/src/main/java/com/android/tools/r8/naming/signature/GenericSignatureRewriter.java
+++ b/src/main/java/com/android/tools/r8/naming/signature/GenericSignatureRewriter.java
@@ -38,6 +38,14 @@
     this.contextBuilder = contextBuilder;
   }
 
+  public void runForD8(Iterable<? extends DexProgramClass> classes, ExecutorService executorService)
+      throws ExecutionException {
+    if (namingLens.isIdentityLens()) {
+      return;
+    }
+    run(classes, executorService);
+  }
+
   public void run(Iterable<? extends DexProgramClass> classes, ExecutorService executorService)
       throws ExecutionException {
     // Rewrite signature annotations for applications that are minified or if we have liveness
diff --git a/src/main/java/com/android/tools/r8/optimize/MemberRebindingAnalysis.java b/src/main/java/com/android/tools/r8/optimize/MemberRebindingAnalysis.java
index b933b4d..e13e860 100644
--- a/src/main/java/com/android/tools/r8/optimize/MemberRebindingAnalysis.java
+++ b/src/main/java/com/android/tools/r8/optimize/MemberRebindingAnalysis.java
@@ -116,7 +116,7 @@
         currentResolutionResult =
             appView
                 .appInfo()
-                .resolveMethodOnClass(original, currentResolvedHolder.getSuperType())
+                .resolveMethodOnClass(currentResolvedHolder.getSuperType(), original)
                 .asSingleResolution();
       } else {
         break;
@@ -244,7 +244,7 @@
   }
 
   private MethodResolutionResult resolveMethodOnClass(DexMethod method) {
-    return appView.appInfo().resolveMethodOnClass(method, method.holder);
+    return appView.appInfo().resolveMethodOnClass(method.holder, method);
   }
 
   private MethodResolutionResult resolveMethodOnInterface(DexMethod method) {
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 b7dd7ff..00be161 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
@@ -195,6 +195,9 @@
         .fixupApplication(affectedClasses, executorService, timing);
 
     timing.end();
+
+    // Ensure determinism of method-to-reprocess set.
+    appView.testing().checkDeterminism(postMethodProcessorBuilder::dump);
   }
 
   /**
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/propagation/InterfaceMethodArgumentPropagator.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/propagation/InterfaceMethodArgumentPropagator.java
index b9d6fda..b41cdda 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/propagation/InterfaceMethodArgumentPropagator.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/propagation/InterfaceMethodArgumentPropagator.java
@@ -131,7 +131,7 @@
             interfaceState.forEach(
                 (interfaceMethod, interfaceMethodState) -> {
                   MethodResolutionResult resolutionResult =
-                      appView.appInfo().resolveMethodOnClass(interfaceMethod, subclass);
+                      appView.appInfo().resolveMethodOnClass(subclass, interfaceMethod);
                   if (resolutionResult.isFailedResolution()) {
                     // TODO(b/190154391): Do we need to propagate argument information to the first
                     //  virtual method above the inaccessible method in the class hierarchy?
@@ -146,8 +146,7 @@
                   }
 
                   ProgramMethod resolvedMethod = resolutionResult.getResolvedProgramMethod();
-                  if (resolvedMethod == null
-                      || resolvedMethod.getHolder() == subclass) {
+                  if (resolvedMethod == null || resolvedMethod.getHolder() == subclass) {
                     return;
                   }
 
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/propagation/VirtualDispatchMethodArgumentPropagator.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/propagation/VirtualDispatchMethodArgumentPropagator.java
index 085d4a0..787ae13 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/propagation/VirtualDispatchMethodArgumentPropagator.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/propagation/VirtualDispatchMethodArgumentPropagator.java
@@ -112,7 +112,7 @@
                           appView
                               .appInfo()
                               .resolveMethodOnClass(
-                                  signature, resolutionResult.getResolvedHolder().getSuperType())
+                                  resolutionResult.getResolvedHolder().getSuperType(), signature)
                               .asSingleResolution();
                     }
 
diff --git a/src/main/java/com/android/tools/r8/optimize/bridgehoisting/BridgeHoisting.java b/src/main/java/com/android/tools/r8/optimize/bridgehoisting/BridgeHoisting.java
index 5550d52..1bd92a3 100644
--- a/src/main/java/com/android/tools/r8/optimize/bridgehoisting/BridgeHoisting.java
+++ b/src/main/java/com/android/tools/r8/optimize/bridgehoisting/BridgeHoisting.java
@@ -166,7 +166,7 @@
       DexEncodedMethod definition = subclass.lookupVirtualMethod(method);
       if (definition == null) {
         DexEncodedMethod resolutionTarget =
-            appView.appInfo().resolveMethodOnClass(method, subclass).getSingleTarget();
+            appView.appInfo().resolveMethodOnClass(subclass, method).getSingleTarget();
         if (resolutionTarget == null || resolutionTarget.isAbstract()) {
           // The fact that this class does not declare the bridge (or the bridge is abstract) should
           // not prevent us from hoisting the bridge.
@@ -228,14 +228,13 @@
 
     // The targeted method must be present on the new holder class for this to be feasible.
     MethodResolutionResult resolutionResult =
-        appView.appInfo().resolveMethodOnClass(methodToInvoke, clazz);
+        appView.appInfo().resolveMethodOnClass(clazz, methodToInvoke);
     if (!resolutionResult.isSingleResolution()) {
       return;
     }
 
     // Now update the code of the bridge method chosen as representative.
     representative
-        .getDefinition()
         .setCode(createCodeForVirtualBridge(representative, methodToInvoke), appView);
     feedback.setBridgeInfo(representative.getDefinition(), new VirtualBridgeInfo(methodToInvoke));
 
diff --git a/src/main/java/com/android/tools/r8/optimize/interfaces/collection/OpenClosedInterfacesCollection.java b/src/main/java/com/android/tools/r8/optimize/interfaces/collection/OpenClosedInterfacesCollection.java
index 5b009ba..755c319 100644
--- a/src/main/java/com/android/tools/r8/optimize/interfaces/collection/OpenClosedInterfacesCollection.java
+++ b/src/main/java/com/android/tools/r8/optimize/interfaces/collection/OpenClosedInterfacesCollection.java
@@ -76,6 +76,9 @@
       return false;
     }
     TypeElement dynamicUpperBoundType = dynamicType.getDynamicUpperBoundType(staticType);
+    if (dynamicUpperBoundType.isArrayType()) {
+      return dynamicUpperBoundType.lessThanOrEqualUpToNullability(staticType, appView);
+    }
     if (!dynamicUpperBoundType.isClassType()) {
       // Should not happen, since the dynamic type should be assignable to the static type.
       assert false;
diff --git a/src/main/java/com/android/tools/r8/optimize/proto/ProtoNormalizer.java b/src/main/java/com/android/tools/r8/optimize/proto/ProtoNormalizer.java
index 0213d86..a3f2d38 100644
--- a/src/main/java/com/android/tools/r8/optimize/proto/ProtoNormalizer.java
+++ b/src/main/java/com/android/tools/r8/optimize/proto/ProtoNormalizer.java
@@ -122,11 +122,42 @@
     // Tracks the set of unoptimizable method signatures. These must remain as-is.
     DexMethodSignatureSet unoptimizableSignatures = DexMethodSignatureSet.createConcurrent();
 
-    ThreadUtils.processMethods(
-        appView,
-        method ->
-            computeReservationsFromMethod(
-                method, optimizableParameterLists, reservedParameterLists, unoptimizableSignatures),
+    ThreadUtils.processItems(
+        appView.appInfo().classes(),
+        clazz -> {
+          Map<DexMethodSignature, DexMethodSignatureSet> collisions = new HashMap<>();
+          clazz.forEachProgramMethod(
+              method -> {
+                DexTypeList methodParametersSorted = method.getParameters().getSorted();
+                computeReservationsFromMethod(
+                    method,
+                    methodParametersSorted,
+                    optimizableParameterLists,
+                    reservedParameterLists,
+                    unoptimizableSignatures);
+
+                DexMethodSignature methodSignature = method.getMethodSignature();
+                DexMethodSignature methodSignatureWithSortedParameters =
+                    methodSignature.withParameters(methodParametersSorted, dexItemFactory);
+                collisions
+                    .computeIfAbsent(
+                        methodSignatureWithSortedParameters,
+                        ignoreKey(DexMethodSignatureSet::createLinked))
+                    .add(methodSignature);
+              });
+          collisions.forEach(
+              (methodSignatureWithSortedParameters, methodSignatures) -> {
+                if (methodSignatures.size() > 1) {
+                  methodSignatures.forEach(
+                      methodSignature ->
+                          addUnoptimizableMethod(
+                              methodSignature,
+                              methodSignatureWithSortedParameters.getParameters(),
+                              reservedParameterLists,
+                              unoptimizableSignatures));
+                }
+              });
+        },
         executorService);
 
     // Reserve parameter lists that won't lead to any sharing after normalization. Any method with
@@ -164,28 +195,39 @@
 
   private void computeReservationsFromMethod(
       ProgramMethod method,
+      DexTypeList methodParametersSorted,
       Map<DexTypeList, Set<DexTypeList>> optimizableParameterLists,
       Map<DexTypeList, Set<DexTypeList>> reservedParameterLists,
       DexMethodSignatureSet unoptimizableSignatures) {
     if (isUnoptimizable(method)) {
-      // Record that other optimizable methods with the same set of parameter types should be
-      // rewritten to have the same parameter list as this method.
-      reservedParameterLists
-          .computeIfAbsent(
-              method.getParameters().getSorted(), ignoreKey(Sets::newConcurrentHashSet))
-          .add(method.getParameters());
-
-      // Mark signature as unoptimizable.
-      unoptimizableSignatures.add(method);
+      addUnoptimizableMethod(
+          method.getMethodSignature(),
+          methodParametersSorted,
+          reservedParameterLists,
+          unoptimizableSignatures);
     } else {
       // Record that the method's parameter list can be rewritten into any permutation.
       optimizableParameterLists
-          .computeIfAbsent(
-              method.getParameters().getSorted(), ignoreKey(Sets::newConcurrentHashSet))
+          .computeIfAbsent(methodParametersSorted, ignoreKey(Sets::newConcurrentHashSet))
           .add(method.getParameters());
     }
   }
 
+  private void addUnoptimizableMethod(
+      DexMethodSignature method,
+      DexTypeList methodParametersSorted,
+      Map<DexTypeList, Set<DexTypeList>> reservedParameterLists,
+      DexMethodSignatureSet unoptimizableSignatures) {
+    // Record that other optimizable methods with the same set of parameter types should be
+    // rewritten to have the same parameter list as this method.
+    reservedParameterLists
+        .computeIfAbsent(methodParametersSorted, ignoreKey(Sets::newConcurrentHashSet))
+        .add(method.getParameters());
+
+    // Mark signature as unoptimizable.
+    unoptimizableSignatures.add(method);
+  }
+
   private void computeExtraReservationsFromMethod(
       ProgramMethod method,
       Set<DexTypeList> unoptimizableParameterLists,
diff --git a/src/main/java/com/android/tools/r8/retrace/internal/StackTraceRegularExpressionParser.java b/src/main/java/com/android/tools/r8/retrace/internal/StackTraceRegularExpressionParser.java
index 7877902..ff09bd7 100644
--- a/src/main/java/com/android/tools/r8/retrace/internal/StackTraceRegularExpressionParser.java
+++ b/src/main/java/com/android/tools/r8/retrace/internal/StackTraceRegularExpressionParser.java
@@ -275,9 +275,9 @@
   private static class SourceFileGroup extends RegularExpressionGroup {
 
     static String subExpressionInternal() {
-      String anyNonDigitNonColonChar = "^\\d:";
+      String anyNonDigitNonColonNonWhitespaceChar = "^\\d:\\s";
       String anyNonColonChar = "^:";
-      String colonWithNonDigitSuffix = ":+[" + anyNonDigitNonColonChar + "]";
+      String colonWithNonDigitSuffix = ":+[" + anyNonDigitNonColonNonWhitespaceChar + "]";
       return "((?:(?:(?:" + colonWithNonDigitSuffix + "))|(?:[" + anyNonColonChar + "]))+)?";
     }
 
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 7081da1..8b8e13b 100644
--- a/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
+++ b/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
@@ -1554,45 +1554,6 @@
   public SubtypingInfo computeSubtypingInfo() {
     return SubtypingInfo.create(this);
   }
-
-  public boolean mayHaveFinalizeMethodDirectlyOrIndirectly(ClassTypeElement type) {
-    // Special case for java.lang.Object.
-    if (type.getClassType() == dexItemFactory().objectType) {
-      if (type.getInterfaces().isEmpty()) {
-        // The type java.lang.Object could be any instantiated type. Assume a finalizer exists.
-        return true;
-      }
-      return type.getInterfaces().anyMatch((iface, isKnown) -> mayHaveFinalizer(iface));
-    }
-    return mayHaveFinalizer(type.getClassType());
-  }
-
-  private boolean mayHaveFinalizer(DexType type) {
-    // A type may have an active finalizer if any derived instance has a finalizer.
-    return objectAllocationInfoCollection
-        .traverseInstantiatedSubtypes(
-            type,
-            clazz -> {
-              if (objectAllocationInfoCollection.isInterfaceWithUnknownSubtypeHierarchy(clazz)) {
-                return TraversalContinuation.doBreak();
-              } else {
-                SingleResolutionResult resolution =
-                    resolveMethodOn(clazz, dexItemFactory().objectMembers.finalize)
-                        .asSingleResolution();
-                if (resolution != null && resolution.getResolvedHolder().isProgramClass()) {
-                  return TraversalContinuation.doBreak();
-                }
-              }
-              return TraversalContinuation.doContinue();
-            },
-            lambda -> {
-              // Lambda classes do not have finalizers.
-              return TraversalContinuation.doContinue();
-            },
-            this)
-        .shouldBreak();
-  }
-
   /** Predicate on types that *must* never be merged horizontally. */
   public boolean isNoHorizontalClassMergingOfType(DexType type) {
     return noClassMerging.contains(type) || noHorizontalClassMerging.contains(type);
diff --git a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
index c3950c4..c7917ce 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -124,6 +124,10 @@
 import com.android.tools.r8.shaking.EnqueuerEvent.LiveClassEnqueuerEvent;
 import com.android.tools.r8.shaking.EnqueuerEvent.UnconditionalKeepInfoEvent;
 import com.android.tools.r8.shaking.EnqueuerWorklist.EnqueuerAction;
+import com.android.tools.r8.shaking.EnqueuerWorklist.TraceInstanceFieldReadAction;
+import com.android.tools.r8.shaking.EnqueuerWorklist.TraceInstanceFieldWriteAction;
+import com.android.tools.r8.shaking.EnqueuerWorklist.TraceStaticFieldReadAction;
+import com.android.tools.r8.shaking.EnqueuerWorklist.TraceStaticFieldWriteAction;
 import com.android.tools.r8.shaking.GraphReporter.KeepReasonWitness;
 import com.android.tools.r8.shaking.KeepInfoCollection.MutableKeepInfoCollection;
 import com.android.tools.r8.shaking.RootSetUtils.ConsequentRootSet;
@@ -461,7 +465,7 @@
     InternalOptions options = appView.options();
     this.appInfo = appView.appInfo();
     this.appView = appView.withClassHierarchy();
-    this.deferredTracing = new EnqueuerDeferredTracing();
+    this.deferredTracing = new EnqueuerDeferredTracing(appView, this, mode);
     this.executorService = executorService;
     this.subtypingInfo = subtypingInfo;
     this.forceProguardCompatibility = options.forceProguardCompatibility;
@@ -684,6 +688,10 @@
     return clazz;
   }
 
+  public FieldAccessInfoCollectionImpl getFieldAccessInfoCollection() {
+    return fieldAccessInfoCollection;
+  }
+
   public MutableKeepInfoCollection getKeepInfo() {
     return keepInfo;
   }
@@ -692,6 +700,18 @@
     return keepInfo.getClassInfo(clazz);
   }
 
+  public KeepFieldInfo getKeepInfo(ProgramField field) {
+    return keepInfo.getFieldInfo(field);
+  }
+
+  public ObjectAllocationInfoCollectionImpl getObjectAllocationInfoCollection() {
+    return objectAllocationInfoCollection;
+  }
+
+  public EnqueuerWorklist getWorklist() {
+    return workList;
+  }
+
   private void addLiveNonProgramType(
       ClasspathOrLibraryClass clazz,
       // TODO(b/216576191): Remove when tracking live library members.
@@ -954,22 +974,39 @@
     return registerFieldAccess(field, context, true, false);
   }
 
-  public boolean registerReflectiveFieldRead(DexField field, ProgramMethod context) {
-    return registerFieldAccess(field, context, true, true);
+  public boolean registerReflectiveFieldRead(ProgramField field, ProgramMethod context) {
+    return registerFieldAccess(field.getReference(), context, true, true);
   }
 
   public boolean registerFieldWrite(DexField field, ProgramMethod context) {
     return registerFieldAccess(field, context, false, false);
   }
 
-  public boolean registerReflectiveFieldWrite(DexField field, ProgramMethod context) {
-    return registerFieldAccess(field, context, false, true);
+  public boolean registerReflectiveFieldWrite(ProgramField field, ProgramMethod context) {
+    return registerFieldAccess(field.getReference(), context, false, true);
   }
 
-  public boolean registerReflectiveFieldAccess(DexField field, ProgramMethod context) {
-    boolean changed = registerFieldAccess(field, context, true, true);
-    changed |= registerFieldAccess(field, context, false, true);
-    return changed;
+  public void traceReflectiveFieldAccess(ProgramField field, ProgramMethod context) {
+    deferredTracing.notifyReflectiveFieldAccess(field, context);
+    boolean changed = registerReflectiveFieldRead(field, context);
+    changed |= registerReflectiveFieldWrite(field, context);
+    if (changed) {
+      markFieldAsReachable(field, context, KeepReason.reflectiveUseIn(context));
+    }
+  }
+
+  public void traceReflectiveFieldRead(ProgramField field, ProgramMethod context) {
+    deferredTracing.notifyReflectiveFieldAccess(field, context);
+    if (registerReflectiveFieldRead(field, context)) {
+      markFieldAsReachable(field, context, KeepReason.reflectiveUseIn(context));
+    }
+  }
+
+  public void traceReflectiveFieldWrite(ProgramField field, ProgramMethod context) {
+    deferredTracing.notifyReflectiveFieldAccess(field, context);
+    if (registerReflectiveFieldWrite(field, context)) {
+      markFieldAsReachable(field, context, KeepReason.reflectiveUseIn(context));
+    }
   }
 
   private boolean registerFieldAccess(
@@ -1003,7 +1040,18 @@
       return false;
     }
     if (isReflective) {
-      info.setHasReflectiveAccess();
+      if (isRead) {
+        if (!info.hasReflectiveRead()) {
+          info.setHasReflectiveRead();
+          return true;
+        }
+      } else {
+        if (!info.hasReflectiveWrite()) {
+          info.setHasReflectiveWrite();
+          return true;
+        }
+      }
+      return false;
     }
     return isRead ? info.recordRead(field, context) : info.recordWrite(field, context);
   }
@@ -1188,7 +1236,7 @@
       initClassReferences.put(
           type, computeMinimumRequiredVisibilityForInitClassField(type, currentMethod.getHolder()));
 
-      markTypeAsLive(type, currentMethod);
+      markTypeAsLive(clazz, currentMethod);
       markDirectAndIndirectClassInitializersAsLive(clazz);
       return;
     }
@@ -1487,12 +1535,29 @@
     boolean isWrite() {
       return !isRead();
     }
+
+    EnqueuerAction toEnqueuerAction(
+        DexField fieldReference, ProgramMethod context, FieldAccessMetadata metadata) {
+      switch (this) {
+        case INSTANCE_READ:
+          return new TraceInstanceFieldReadAction(fieldReference, context, metadata);
+        case INSTANCE_WRITE:
+          return new TraceInstanceFieldWriteAction(fieldReference, context, metadata);
+        case STATIC_READ:
+          return new TraceStaticFieldReadAction(fieldReference, context, metadata);
+        case STATIC_WRITE:
+          return new TraceStaticFieldWriteAction(fieldReference, context, metadata);
+        default:
+          throw new Unreachable();
+      }
+    }
   }
 
   static class FieldAccessMetadata {
 
-    private static int FROM_METHOD_HANDLE_MASK = 1;
-    private static int FROM_RECORD_METHOD_HANDLE_MASK = 2;
+    private static int DEFERRED_MASK = 1;
+    private static int FROM_METHOD_HANDLE_MASK = 2;
+    private static int FROM_RECORD_METHOD_HANDLE_MASK = 4;
 
     static FieldAccessMetadata DEFAULT = new FieldAccessMetadata(0);
     static FieldAccessMetadata FROM_METHOD_HANDLE =
@@ -1500,10 +1565,16 @@
     static FieldAccessMetadata FROM_RECORD_METHOD_HANDLE =
         new FieldAccessMetadata(FROM_RECORD_METHOD_HANDLE_MASK);
 
+    private final FieldAccessMetadata deferred;
     private final int flags;
 
-    FieldAccessMetadata(int flags) {
+    private FieldAccessMetadata(int flags) {
       this.flags = flags;
+      this.deferred = isDeferred() ? this : new FieldAccessMetadata(flags | DEFERRED_MASK);
+    }
+
+    boolean isDeferred() {
+      return (flags & DEFERRED_MASK) != 0;
     }
 
     boolean isFromMethodHandle() {
@@ -1513,17 +1584,39 @@
     boolean isFromRecordMethodHandle() {
       return (flags & FROM_RECORD_METHOD_HANDLE_MASK) != 0;
     }
+
+    public FieldAccessMetadata toDeferred() {
+      return deferred;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+      if (this == obj) {
+        return true;
+      }
+      if (obj == null || getClass() != obj.getClass()) {
+        return false;
+      }
+      FieldAccessMetadata metadata = (FieldAccessMetadata) obj;
+      return flags == metadata.flags;
+    }
+
+    @Override
+    public int hashCode() {
+      return flags;
+    }
   }
 
-  private void traceInstanceFieldRead(
+  void traceInstanceFieldRead(
       DexField fieldReference, ProgramMethod currentMethod, FieldAccessMetadata metadata) {
-    if (!registerFieldRead(fieldReference, currentMethod)) {
+    if (!metadata.isDeferred() && !registerFieldRead(fieldReference, currentMethod)) {
       return;
     }
 
     FieldResolutionResult resolutionResult = resolveField(fieldReference, currentMethod);
     if (deferredTracing.deferTracingOfFieldAccess(
         fieldReference, resolutionResult, currentMethod, FieldAccessKind.INSTANCE_READ, metadata)) {
+      assert !metadata.isDeferred();
       return;
     }
 
@@ -1579,9 +1672,9 @@
     traceInstanceFieldWrite(field, currentMethod, FieldAccessMetadata.FROM_METHOD_HANDLE);
   }
 
-  private void traceInstanceFieldWrite(
+  void traceInstanceFieldWrite(
       DexField fieldReference, ProgramMethod currentMethod, FieldAccessMetadata metadata) {
-    if (!registerFieldWrite(fieldReference, currentMethod)) {
+    if (!metadata.isDeferred() && !registerFieldWrite(fieldReference, currentMethod)) {
       return;
     }
 
@@ -1592,6 +1685,7 @@
         currentMethod,
         FieldAccessKind.INSTANCE_WRITE,
         metadata)) {
+      assert !metadata.isDeferred();
       return;
     }
 
@@ -1645,15 +1739,31 @@
     traceStaticFieldRead(field, currentMethod, FieldAccessMetadata.FROM_METHOD_HANDLE);
   }
 
-  private void traceStaticFieldRead(
+  void traceStaticFieldRead(
       DexField fieldReference, ProgramMethod currentMethod, FieldAccessMetadata metadata) {
-    if (!registerFieldRead(fieldReference, currentMethod)) {
+    if (!metadata.isDeferred() && !registerFieldRead(fieldReference, currentMethod)) {
       return;
     }
 
     FieldResolutionResult resolutionResult = resolveField(fieldReference, currentMethod);
+
+    if (appView.options().protoShrinking().enableGeneratedExtensionRegistryShrinking) {
+      // If it is a dead proto extension field, don't trace onwards.
+      boolean skipTracing =
+          appView.withGeneratedExtensionRegistryShrinker(
+              shrinker ->
+                  shrinker.isDeadProtoExtensionField(
+                      resolutionResult, fieldAccessInfoCollection, keepInfo),
+              false);
+      if (skipTracing) {
+        addDeadProtoTypeCandidate(resolutionResult.getSingleProgramField().getHolder());
+        return;
+      }
+    }
+
     if (deferredTracing.deferTracingOfFieldAccess(
         fieldReference, resolutionResult, currentMethod, FieldAccessKind.STATIC_READ, metadata)) {
+      assert !metadata.isDeferred();
       return;
     }
 
@@ -1684,18 +1794,6 @@
             Log.verbose(getClass(), "Register Sget `%s`.", fieldReference);
           }
 
-          // If it is a dead proto extension field, don't trace onwards.
-          boolean skipTracing =
-              appView.withGeneratedExtensionRegistryShrinker(
-                  shrinker ->
-                      shrinker.isDeadProtoExtensionField(
-                          field, fieldAccessInfoCollection, keepInfo),
-                  false);
-          if (skipTracing) {
-            addDeadProtoTypeCandidate(field.getHolder());
-            return;
-          }
-
           if (field.getReference() != fieldReference) {
             // Mark the initial resolution holder as live. Note that this should only be done if
             // the field
@@ -1725,15 +1823,31 @@
     traceStaticFieldWrite(field, currentMethod, FieldAccessMetadata.FROM_METHOD_HANDLE);
   }
 
-  private void traceStaticFieldWrite(
+  void traceStaticFieldWrite(
       DexField fieldReference, ProgramMethod currentMethod, FieldAccessMetadata metadata) {
-    if (!registerFieldWrite(fieldReference, currentMethod)) {
+    if (!metadata.isDeferred() && !registerFieldWrite(fieldReference, currentMethod)) {
       return;
     }
 
     FieldResolutionResult resolutionResult = resolveField(fieldReference, currentMethod);
+
+    if (appView.options().protoShrinking().enableGeneratedExtensionRegistryShrinking) {
+      // If it is a dead proto extension field, don't trace onwards.
+      boolean skipTracing =
+          appView.withGeneratedExtensionRegistryShrinker(
+              shrinker ->
+                  shrinker.isDeadProtoExtensionField(
+                      resolutionResult, fieldAccessInfoCollection, keepInfo),
+              false);
+      if (skipTracing) {
+        addDeadProtoTypeCandidate(resolutionResult.getSingleProgramField().getHolder());
+        return;
+      }
+    }
+
     if (deferredTracing.deferTracingOfFieldAccess(
         fieldReference, resolutionResult, currentMethod, FieldAccessKind.STATIC_WRITE, metadata)) {
+      assert !metadata.isDeferred();
       return;
     }
 
@@ -1764,20 +1878,6 @@
             Log.verbose(getClass(), "Register Sput `%s`.", fieldReference);
           }
 
-          if (appView.options().protoShrinking().enableGeneratedExtensionRegistryShrinking) {
-            // If it is a dead proto extension field, don't trace onwards.
-            boolean skipTracing =
-                appView.withGeneratedExtensionRegistryShrinker(
-                    shrinker ->
-                        shrinker.isDeadProtoExtensionField(
-                            field, fieldAccessInfoCollection, keepInfo),
-                    false);
-            if (skipTracing) {
-              addDeadProtoTypeCandidate(field.getHolder());
-              return;
-            }
-          }
-
           if (field.getReference() != fieldReference) {
             // Mark the initial resolution holder as live. Note that this should only be done if
             // the field
@@ -1867,7 +1967,7 @@
     markTypeAsLive(clazz, graphReporter.reportClassReferencedFrom(clazz, context));
   }
 
-  private void markTypeAsLive(DexProgramClass clazz, KeepReason reason) {
+  void markTypeAsLive(DexProgramClass clazz, KeepReason reason) {
     assert clazz != null;
     markTypeAsLive(
         clazz,
@@ -2222,7 +2322,7 @@
     }
   }
 
-  private void markDirectAndIndirectClassInitializersAsLive(DexProgramClass clazz) {
+  void markDirectAndIndirectClassInitializersAsLive(DexProgramClass clazz) {
     if (clazz.isInterface()) {
       // Accessing a static field or method on an interface does not trigger the class initializer
       // of any parent interfaces.
diff --git a/src/main/java/com/android/tools/r8/shaking/EnqueuerDeferredTracing.java b/src/main/java/com/android/tools/r8/shaking/EnqueuerDeferredTracing.java
index a09a358..b0a03a2 100644
--- a/src/main/java/com/android/tools/r8/shaking/EnqueuerDeferredTracing.java
+++ b/src/main/java/com/android/tools/r8/shaking/EnqueuerDeferredTracing.java
@@ -4,16 +4,75 @@
 
 package com.android.tools.r8.shaking;
 
+import static com.android.tools.r8.graph.DexProgramClass.asProgramClassOrNull;
+import static com.android.tools.r8.shaking.ObjectAllocationInfoCollectionUtils.mayHaveFinalizeMethodDirectlyOrIndirectly;
+import static com.android.tools.r8.utils.MapUtils.ignoreKey;
+
+import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.Code;
 import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.FieldAccessInfo;
+import com.android.tools.r8.graph.FieldAccessInfoCollectionImpl;
 import com.android.tools.r8.graph.FieldResolutionResult;
+import com.android.tools.r8.graph.ProgramField;
 import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.graph.bytecodemetadata.BytecodeMetadataProvider;
+import com.android.tools.r8.ir.code.IRCode;
+import com.android.tools.r8.ir.conversion.IRFinalizer;
+import com.android.tools.r8.ir.conversion.IRToCfFinalizer;
+import com.android.tools.r8.ir.conversion.IRToDexFinalizer;
+import com.android.tools.r8.ir.conversion.MethodConversionOptions.MutableMethodConversionOptions;
+import com.android.tools.r8.ir.optimize.membervaluepropagation.assume.AssumeInfo;
+import com.android.tools.r8.ir.optimize.membervaluepropagation.assume.AssumeInfoLookup;
 import com.android.tools.r8.shaking.Enqueuer.FieldAccessKind;
 import com.android.tools.r8.shaking.Enqueuer.FieldAccessMetadata;
+import com.android.tools.r8.shaking.Enqueuer.Mode;
+import com.android.tools.r8.shaking.EnqueuerWorklist.EnqueuerAction;
+import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.ThreadUtils;
+import com.android.tools.r8.utils.Timing;
+import com.android.tools.r8.utils.collections.ProgramFieldMap;
+import com.android.tools.r8.utils.collections.ProgramFieldSet;
+import com.android.tools.r8.utils.collections.ProgramMethodSet;
+import java.util.IdentityHashMap;
+import java.util.LinkedHashSet;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.ExecutorService;
 
 public class EnqueuerDeferredTracing {
 
+  private final AppView<? extends AppInfoWithClassHierarchy> appView;
+  private final Enqueuer enqueuer;
+  private final Mode mode;
+  private final InternalOptions options;
+
+  // Helper for rewriting code instances at the end of tree shaking.
+  private final EnqueuerDeferredTracingRewriter rewriter;
+
+  // Maps each field to the tracing actions that have been deferred for that field. This allows
+  // enqueuing previously deferred tracing actions into the worklist if a given field cannot be
+  // optimized after all.
+  private final ProgramFieldMap<Set<EnqueuerAction>> deferredEnqueuerActions =
+      ProgramFieldMap.create();
+
+  // A set of fields that are never eligible for pruning.
+  private final ProgramFieldSet ineligibleForPruning = ProgramFieldSet.create();
+
+  EnqueuerDeferredTracing(
+      AppView<? extends AppInfoWithClassHierarchy> appView, Enqueuer enqueuer, Mode mode) {
+    this.appView = appView;
+    this.enqueuer = enqueuer;
+    this.mode = mode;
+    this.options = appView.options();
+    this.rewriter = new EnqueuerDeferredTracingRewriter(appView);
+  }
+
   /**
    * Returns true if the {@link Enqueuer} should not trace the given field reference.
    *
@@ -25,8 +84,123 @@
       DexField fieldReference,
       FieldResolutionResult resolutionResult,
       ProgramMethod context,
-      FieldAccessKind kind,
+      FieldAccessKind accessKind,
       FieldAccessMetadata metadata) {
+    if (!enqueuer.getMode().isFinalTreeShaking()) {
+      return false;
+    }
+
+    ProgramField field = resolutionResult.getSingleProgramField();
+    if (field == null) {
+      return false;
+    }
+
+    // Check if field access is consistent with the field access flags.
+    if (field.getAccessFlags().isStatic() != accessKind.isStatic()) {
+      return enqueueDeferredEnqueuerActions(field);
+    }
+
+    // If the access is from a reachability sensitive method, then bail out.
+    if (context.getHolder().getOrComputeReachabilitySensitive(appView)) {
+      return enqueueDeferredEnqueuerActions(field);
+    }
+
+    if (accessKind.isRead()) {
+      // If the value of the field is not guaranteed to be the default value, even if it is never
+      // assigned, then give up.
+      // TODO(b/205810841): Allow this by handling this in the corresponding IR rewriter.
+      AssumeInfo assumeInfo = AssumeInfoLookup.lookupAssumeInfo(appView, field);
+      if (assumeInfo != null && assumeInfo.hasReturnInfo()) {
+        return enqueueDeferredEnqueuerActions(field);
+      }
+      if (field.getAccessFlags().isStatic() && field.getDefinition().hasExplicitStaticValue()) {
+        return enqueueDeferredEnqueuerActions(field);
+      }
+    }
+
+    if (!isEligibleForPruning(field)) {
+      return enqueueDeferredEnqueuerActions(field);
+    }
+
+    // Field can be removed unless some other field access that has not yet been seen prohibits it.
+    // Record an EnqueuerAction that must be traced if that should happen.
+    EnqueuerAction deferredEnqueuerAction =
+        accessKind.toEnqueuerAction(fieldReference, context, metadata.toDeferred());
+    deferredEnqueuerActions
+        .computeIfAbsent(field, ignoreKey(LinkedHashSet::new))
+        .add(deferredEnqueuerAction);
+
+    // If the field is static, then the field access will trigger the class initializer of the
+    // field's holder. Therefore, we unconditionally trace the class initializer in this case.
+    // The corresponding IR rewriter will rewrite the field access into an init-class instruction.
+    if (accessKind.isStatic()) {
+      KeepReason reason =
+          enqueuer.getGraphReporter().reportClassReferencedFrom(field.getHolder(), context);
+      enqueuer.getWorklist().enqueueTraceTypeReferenceAction(field.getHolder(), reason);
+      enqueuer.getWorklist().enqueueTraceDirectAndIndirectClassInitializers(field.getHolder());
+    }
+
+    return true;
+  }
+
+  public void notifyReflectiveFieldAccess(ProgramField field, ProgramMethod context) {
+    enqueueDeferredEnqueuerActions(field);
+  }
+
+  private boolean isEligibleForPruning(ProgramField field) {
+    FieldAccessInfo info = enqueuer.getFieldAccessInfoCollection().get(field.getReference());
+    if (info.hasReflectiveAccess()
+        || info.isAccessedFromMethodHandle()
+        || info.isReadFromAnnotation()
+        || info.isReadFromRecordInvokeDynamic()
+        || enqueuer.getKeepInfo(field).isPinned(options)) {
+      return false;
+    }
+
+    if (info.isWritten()) {
+      // If the assigned value may have an override of Object#finalize() then give up.
+      // Note that this check depends on the set of instantiated types, and must therefore be rerun
+      // when the enqueuer's fixpoint is reached.
+      if (field.getType().isReferenceType()) {
+        DexType fieldBaseType = field.getType().toBaseType(appView.dexItemFactory());
+        if (fieldBaseType.isClassType()
+            && mayHaveFinalizeMethodDirectlyOrIndirectly(
+                appView, fieldBaseType, enqueuer.getObjectAllocationInfoCollection())) {
+          return false;
+        }
+      }
+    }
+
+    // We always have precise knowledge of field accesses during tracing.
+    assert info.hasKnownReadContexts();
+    assert info.hasKnownWriteContexts();
+
+    DexType fieldType = field.getType();
+
+    // If the field is now both read and written, then we cannot optimize the field unless the field
+    // type is an uninstantiated class type.
+    if (info.getReadsWithContexts().hasAccesses() && info.getWritesWithContexts().hasAccesses()) {
+      if (!fieldType.isClassType()) {
+        return false;
+      }
+      DexProgramClass fieldTypeDefinition = asProgramClassOrNull(appView.definitionFor(fieldType));
+      if (fieldTypeDefinition == null
+          || enqueuer
+              .getObjectAllocationInfoCollection()
+              .isInstantiatedDirectlyOrHasInstantiatedSubtype(fieldTypeDefinition)) {
+        return false;
+      }
+    }
+
+    return !ineligibleForPruning.contains(field);
+  }
+
+  private boolean enqueueDeferredEnqueuerActions(ProgramField field) {
+    Set<EnqueuerAction> actions = deferredEnqueuerActions.remove(field);
+    if (actions != null) {
+      enqueuer.getWorklist().enqueueAll(actions);
+    }
+    ineligibleForPruning.add(field);
     return false;
   }
 
@@ -35,7 +209,14 @@
    * tree shaking.
    */
   public boolean enqueueWorklistActions(EnqueuerWorklist worklist) {
-    return false;
+    return deferredEnqueuerActions.removeIf(
+        (field, worklistActions) -> {
+          if (isEligibleForPruning(field)) {
+            return false;
+          }
+          worklist.enqueueAll(worklistActions);
+          return true;
+        });
   }
 
   /**
@@ -43,6 +224,63 @@
    * that has not been performed (e.g., rewriting of dead field instructions).
    */
   public void rewriteApplication(ExecutorService executorService) throws ExecutionException {
-    // Intentionally empty.
+    FieldAccessInfoCollectionImpl fieldAccessInfoCollection =
+        enqueuer.getFieldAccessInfoCollection();
+    ProgramMethodSet methodsToProcess = ProgramMethodSet.create();
+    Map<DexField, ProgramField> prunedFields = new IdentityHashMap<>();
+    deferredEnqueuerActions.forEach(
+        (field, ignore) -> {
+          FieldAccessInfo accessInfo = fieldAccessInfoCollection.get(field.getReference());
+          prunedFields.put(field.getReference(), field);
+          accessInfo.forEachAccessContext(methodsToProcess::add);
+          accessInfo.forEachIndirectAccess(reference -> prunedFields.put(reference, field));
+        });
+    deferredEnqueuerActions.clear();
+
+    // Rewrite application.
+    Map<DexProgramClass, ProgramMethodSet> initializedClassesWithContexts =
+        new ConcurrentHashMap<>();
+    ThreadUtils.processItems(
+        methodsToProcess,
+        method -> rewriteMethod(method, initializedClassesWithContexts, prunedFields),
+        executorService);
+
+    // Register new InitClass instructions.
+    initializedClassesWithContexts.forEach(
+        (clazz, contexts) ->
+            contexts.forEach(context -> enqueuer.traceInitClass(clazz.getType(), context)));
+    assert enqueuer.getWorklist().isEmpty();
+
+    // Prune field access info collection.
+    prunedFields.values().forEach(field -> fieldAccessInfoCollection.remove(field.getReference()));
+  }
+
+  private void rewriteMethod(
+      ProgramMethod method,
+      Map<DexProgramClass, ProgramMethodSet> initializedClassesWithContexts,
+      Map<DexField, ProgramField> prunedFields) {
+    // Build IR.
+    MutableMethodConversionOptions conversionOptions =
+        mode.isInitialTreeShaking()
+            ? new MutableMethodConversionOptions(options).setIsGeneratingClassFiles(true)
+            : new MutableMethodConversionOptions(options);
+    conversionOptions.disableStringSwitchConversion();
+
+    IRCode ir = method.buildIR(appView, conversionOptions);
+
+    // Rewrite the IR according to the tracing that has been deferred.
+    rewriter.rewriteCode(ir, initializedClassesWithContexts, prunedFields);
+
+    // Run dead code elimination.
+    rewriter.getCodeRewriter().optimizeAlwaysThrowingInstructions(ir);
+    rewriter.getDeadCodeRemover().run(ir, Timing.empty());
+
+    // Finalize to class files or dex.
+    IRFinalizer<?> finalizer =
+        conversionOptions.isGeneratingClassFiles()
+            ? new IRToCfFinalizer(appView, rewriter.getDeadCodeRemover())
+            : new IRToDexFinalizer(appView, rewriter.getDeadCodeRemover());
+    Code newCode = finalizer.finalizeCode(ir, BytecodeMetadataProvider.empty(), Timing.empty());
+    method.setCode(newCode, appView);
   }
 }
diff --git a/src/main/java/com/android/tools/r8/shaking/EnqueuerDeferredTracingRewriter.java b/src/main/java/com/android/tools/r8/shaking/EnqueuerDeferredTracingRewriter.java
new file mode 100644
index 0000000..79165d1
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/shaking/EnqueuerDeferredTracingRewriter.java
@@ -0,0 +1,228 @@
+// Copyright (c) 2022, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.shaking;
+
+import static com.android.tools.r8.ir.code.Opcodes.INSTANCE_GET;
+import static com.android.tools.r8.ir.code.Opcodes.INSTANCE_PUT;
+import static com.android.tools.r8.ir.code.Opcodes.STATIC_GET;
+import static com.android.tools.r8.ir.code.Opcodes.STATIC_PUT;
+import static com.android.tools.r8.utils.MapUtils.ignoreKey;
+
+import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.ProgramField;
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.ir.analysis.type.TypeAnalysis;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
+import com.android.tools.r8.ir.code.BasicBlock;
+import com.android.tools.r8.ir.code.BasicBlockIterator;
+import com.android.tools.r8.ir.code.FieldGet;
+import com.android.tools.r8.ir.code.IRCode;
+import com.android.tools.r8.ir.code.InitClass;
+import com.android.tools.r8.ir.code.InstanceFieldInstruction;
+import com.android.tools.r8.ir.code.InstanceGet;
+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.StaticGet;
+import com.android.tools.r8.ir.code.StaticPut;
+import com.android.tools.r8.ir.code.Value;
+import com.android.tools.r8.ir.optimize.CodeRewriter;
+import com.android.tools.r8.ir.optimize.DeadCodeRemover;
+import com.android.tools.r8.utils.collections.ProgramMethodSet;
+import com.google.common.collect.Sets;
+import java.util.Map;
+import java.util.Set;
+
+public class EnqueuerDeferredTracingRewriter {
+
+  private final AppView<? extends AppInfoWithClassHierarchy> appView;
+  private final CodeRewriter codeRewriter;
+  private final DeadCodeRemover deadCodeRemover;
+
+  EnqueuerDeferredTracingRewriter(AppView<? extends AppInfoWithClassHierarchy> appView) {
+    this.appView = appView;
+    this.codeRewriter = new CodeRewriter(appView);
+    this.deadCodeRemover = new DeadCodeRemover(appView, codeRewriter);
+  }
+
+  public CodeRewriter getCodeRewriter() {
+    return codeRewriter;
+  }
+
+  public DeadCodeRemover getDeadCodeRemover() {
+    return deadCodeRemover;
+  }
+
+  public void rewriteCode(
+      IRCode code,
+      Map<DexProgramClass, ProgramMethodSet> initializedClassesWithContexts,
+      Map<DexField, ProgramField> prunedFields) {
+    // TODO(b/205810841): Consider inserting assume instructions to reduce number of null checks.
+    // TODO(b/205810841): Consider running constant canonicalizer.
+    ProgramMethod context = code.context();
+
+    // Rewrite field instructions that reference a pruned field.
+    Set<Value> affectedValues = Sets.newIdentityHashSet();
+    BasicBlockIterator blockIterator = code.listIterator();
+    while (blockIterator.hasNext()) {
+      BasicBlock block = blockIterator.next();
+      InstructionListIterator instructionIterator = block.listIterator(code);
+      while (instructionIterator.hasNext()) {
+        Instruction instruction = instructionIterator.next();
+        switch (instruction.opcode()) {
+          case INSTANCE_GET:
+            rewriteInstanceGet(
+                code,
+                instructionIterator,
+                instruction.asInstanceGet(),
+                affectedValues,
+                prunedFields);
+            break;
+          case INSTANCE_PUT:
+            rewriteInstancePut(instructionIterator, instruction.asInstancePut(), prunedFields);
+            break;
+          case STATIC_GET:
+            rewriteStaticGet(
+                code,
+                instructionIterator,
+                instruction.asStaticGet(),
+                affectedValues,
+                context,
+                initializedClassesWithContexts,
+                prunedFields);
+            break;
+          case STATIC_PUT:
+            rewriteStaticPut(
+                code,
+                instructionIterator,
+                instruction.asStaticPut(),
+                context,
+                initializedClassesWithContexts,
+                prunedFields);
+            break;
+          default:
+            break;
+        }
+      }
+    }
+    if (!affectedValues.isEmpty()) {
+      new TypeAnalysis(appView).narrowing(affectedValues);
+    }
+  }
+
+  private void rewriteInstanceGet(
+      IRCode code,
+      InstructionListIterator instructionIterator,
+      InstanceGet instanceGet,
+      Set<Value> affectedValues,
+      Map<DexField, ProgramField> prunedFields) {
+    ProgramField prunedField = prunedFields.get(instanceGet.getField());
+    if (prunedField == null) {
+      return;
+    }
+
+    insertDefaultValueForFieldGet(
+        code, instructionIterator, instanceGet, affectedValues, prunedField);
+    removeOrReplaceInstanceFieldInstructionWithNullCheck(instructionIterator, instanceGet);
+  }
+
+  private void rewriteInstancePut(
+      InstructionListIterator instructionIterator,
+      InstancePut instancePut,
+      Map<DexField, ProgramField> prunedFields) {
+    ProgramField prunedField = prunedFields.get(instancePut.getField());
+    if (prunedField == null) {
+      return;
+    }
+
+    removeOrReplaceInstanceFieldInstructionWithNullCheck(instructionIterator, instancePut);
+  }
+
+  private void rewriteStaticGet(
+      IRCode code,
+      InstructionListIterator instructionIterator,
+      StaticGet staticGet,
+      Set<Value> affectedValues,
+      ProgramMethod context,
+      Map<DexProgramClass, ProgramMethodSet> initializedClassesWithContexts,
+      Map<DexField, ProgramField> prunedFields) {
+    ProgramField prunedField = prunedFields.get(staticGet.getField());
+    if (prunedField == null) {
+      return;
+    }
+
+    insertDefaultValueForFieldGet(
+        code, instructionIterator, staticGet, affectedValues, prunedField);
+    removeOrReplaceStaticFieldInstructionByInitClass(
+        code, instructionIterator, context, initializedClassesWithContexts, prunedField);
+  }
+
+  private void rewriteStaticPut(
+      IRCode code,
+      InstructionListIterator instructionIterator,
+      StaticPut staticPut,
+      ProgramMethod context,
+      Map<DexProgramClass, ProgramMethodSet> initializedClassesWithContexts,
+      Map<DexField, ProgramField> prunedFields) {
+    ProgramField prunedField = prunedFields.get(staticPut.getField());
+    if (prunedField == null) {
+      return;
+    }
+
+    removeOrReplaceStaticFieldInstructionByInitClass(
+        code, instructionIterator, context, initializedClassesWithContexts, prunedField);
+  }
+
+  private void insertDefaultValueForFieldGet(
+      IRCode code,
+      InstructionListIterator instructionIterator,
+      FieldGet fieldGet,
+      Set<Value> affectedValues,
+      ProgramField prunedField) {
+    if (fieldGet.hasUsedOutValue()) {
+      instructionIterator.previous();
+      Value replacement =
+          prunedField.getType().isReferenceType()
+              ? instructionIterator.insertConstNullInstruction(code, appView.options())
+              : instructionIterator.insertConstNumberInstruction(
+                  code, appView.options(), 0, fieldGet.getOutType());
+      fieldGet.outValue().replaceUsers(replacement, affectedValues);
+      instructionIterator.next();
+    }
+  }
+
+  private void removeOrReplaceInstanceFieldInstructionWithNullCheck(
+      InstructionListIterator instructionIterator, InstanceFieldInstruction fieldInstruction) {
+    if (fieldInstruction.object().isMaybeNull()) {
+      instructionIterator.replaceCurrentInstructionWithNullCheck(
+          appView, fieldInstruction.object());
+    } else {
+      instructionIterator.removeOrReplaceByDebugLocalRead();
+    }
+  }
+
+  private void removeOrReplaceStaticFieldInstructionByInitClass(
+      IRCode code,
+      InstructionListIterator instructionIterator,
+      ProgramMethod context,
+      Map<DexProgramClass, ProgramMethodSet> initializedClassesWithContexts,
+      ProgramField prunedField) {
+    if (prunedField.getHolder().classInitializationMayHaveSideEffectsInContext(appView, context)) {
+      instructionIterator.replaceCurrentInstruction(
+          InitClass.builder()
+              .setFreshOutValue(code, TypeElement.getInt())
+              .setType(prunedField.getHolderType())
+              .build());
+      initializedClassesWithContexts
+          .computeIfAbsent(prunedField.getHolder(), ignoreKey(ProgramMethodSet::createConcurrent))
+          .add(context);
+    } else {
+      instructionIterator.removeOrReplaceByDebugLocalRead();
+    }
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/shaking/EnqueuerWorklist.java b/src/main/java/com/android/tools/r8/shaking/EnqueuerWorklist.java
index 0efc4b5..a09efe6 100644
--- a/src/main/java/com/android/tools/r8/shaking/EnqueuerWorklist.java
+++ b/src/main/java/com/android/tools/r8/shaking/EnqueuerWorklist.java
@@ -11,12 +11,17 @@
 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.FieldAccessInfo;
 import com.android.tools.r8.graph.ProgramDefinition;
 import com.android.tools.r8.graph.ProgramField;
 import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.shaking.Enqueuer.FieldAccessKind;
+import com.android.tools.r8.shaking.Enqueuer.FieldAccessMetadata;
 import com.android.tools.r8.shaking.GraphReporter.KeepReasonWitness;
 import com.android.tools.r8.utils.Action;
 import com.android.tools.r8.utils.InternalOptions;
+import java.util.Collection;
+import java.util.Objects;
 import java.util.Queue;
 import java.util.concurrent.ConcurrentLinkedQueue;
 
@@ -241,6 +246,19 @@
     }
   }
 
+  static class TraceDirectAndIndirectClassInitializers extends EnqueuerAction {
+    private final DexProgramClass clazz;
+
+    TraceDirectAndIndirectClassInitializers(DexProgramClass clazz) {
+      this.clazz = clazz;
+    }
+
+    @Override
+    public void run(Enqueuer enqueuer) {
+      enqueuer.markDirectAndIndirectClassInitializersAsLive(clazz);
+    }
+  }
+
   static class TraceInvokeDirectAction extends EnqueuerAction {
     private final DexMethod invokedMethod;
     // TODO(b/175854431): Avoid pushing context on worklist.
@@ -302,19 +320,199 @@
     }
   }
 
-  static class TraceStaticFieldReadAction extends EnqueuerAction {
-    private final DexField field;
-    // TODO(b/175854431): Avoid pushing context on worklist.
+  static class TraceReflectiveFieldAccessAction extends EnqueuerAction {
+    private final ProgramField field;
     private final ProgramMethod context;
+    private final FieldAccessKind kind;
 
-    TraceStaticFieldReadAction(DexField field, ProgramMethod context) {
+    TraceReflectiveFieldAccessAction(ProgramField field, ProgramMethod context) {
+      this(field, context, null);
+    }
+
+    TraceReflectiveFieldAccessAction(
+        ProgramField field, ProgramMethod context, FieldAccessKind kind) {
       this.field = field;
       this.context = context;
+      this.kind = kind;
     }
 
     @Override
     public void run(Enqueuer enqueuer) {
-      enqueuer.traceStaticFieldRead(field, context);
+      if (kind != null) {
+        if (kind.isRead()) {
+          enqueuer.traceReflectiveFieldRead(field, context);
+        } else {
+          enqueuer.traceReflectiveFieldWrite(field, context);
+        }
+      } else {
+        enqueuer.traceReflectiveFieldAccess(field, context);
+      }
+    }
+  }
+
+  static class TraceTypeReferenceAction extends EnqueuerAction {
+    private final DexProgramClass clazz;
+    private final KeepReason reason;
+
+    TraceTypeReferenceAction(DexProgramClass clazz, KeepReason reason) {
+      this.clazz = clazz;
+      this.reason = reason;
+    }
+
+    @Override
+    public void run(Enqueuer enqueuer) {
+      enqueuer.markTypeAsLive(clazz, reason);
+    }
+  }
+
+  abstract static class TraceFieldAccessAction extends EnqueuerAction {
+    protected final DexField field;
+    // TODO(b/175854431): Avoid pushing context on worklist.
+    protected final ProgramMethod context;
+    protected final FieldAccessMetadata metadata;
+
+    TraceFieldAccessAction(DexField field, ProgramMethod context, FieldAccessMetadata metadata) {
+      this.field = field;
+      this.context = context;
+      this.metadata = metadata;
+    }
+
+    protected boolean baseEquals(TraceFieldAccessAction action) {
+      return field == action.field
+          && context.isStructurallyEqualTo(action.context)
+          && metadata.equals(action.metadata);
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+      if (this == obj) {
+        return true;
+      }
+      if (obj == null || getClass() != obj.getClass()) {
+        return false;
+      }
+      TraceFieldAccessAction action = (TraceFieldAccessAction) obj;
+      return baseEquals(action);
+    }
+
+    @Override
+    public int hashCode() {
+      return Objects.hash(field, context.getReference(), metadata);
+    }
+  }
+
+  static class TraceInstanceFieldReadAction extends TraceFieldAccessAction {
+
+    TraceInstanceFieldReadAction(
+        DexField field, ProgramMethod context, FieldAccessMetadata metadata) {
+      super(field, context, metadata);
+    }
+
+    @Override
+    public void run(Enqueuer enqueuer) {
+      enqueuer.traceInstanceFieldRead(field, context, metadata);
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+      if (this == obj) {
+        return true;
+      }
+      if (obj == null || getClass() != obj.getClass()) {
+        return false;
+      }
+      TraceInstanceFieldReadAction action = (TraceInstanceFieldReadAction) obj;
+      return baseEquals(action);
+    }
+
+    @Override
+    public int hashCode() {
+      return Objects.hash(field, context.getReference(), metadata);
+    }
+  }
+
+  static class TraceInstanceFieldWriteAction extends TraceFieldAccessAction {
+
+    TraceInstanceFieldWriteAction(
+        DexField field, ProgramMethod context, FieldAccessMetadata metadata) {
+      super(field, context, metadata);
+    }
+
+    @Override
+    public void run(Enqueuer enqueuer) {
+      enqueuer.traceInstanceFieldWrite(field, context, metadata);
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+      if (this == obj) {
+        return true;
+      }
+      if (obj == null || getClass() != obj.getClass()) {
+        return false;
+      }
+      TraceInstanceFieldWriteAction action = (TraceInstanceFieldWriteAction) obj;
+      return baseEquals(action);
+    }
+
+    @Override
+    public int hashCode() {
+      return Objects.hash(field, context.getReference(), metadata);
+    }
+  }
+
+  static class TraceStaticFieldReadAction extends TraceFieldAccessAction {
+
+    TraceStaticFieldReadAction(
+        DexField field, ProgramMethod context, FieldAccessMetadata metadata) {
+      super(field, context, metadata);
+    }
+
+    @Override
+    public void run(Enqueuer enqueuer) {
+      enqueuer.traceStaticFieldRead(field, context, metadata);
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+      if (this == obj) {
+        return true;
+      }
+      if (obj == null || getClass() != obj.getClass()) {
+        return false;
+      }
+      TraceStaticFieldReadAction action = (TraceStaticFieldReadAction) obj;
+      return baseEquals(action);
+    }
+  }
+
+  static class TraceStaticFieldWriteAction extends TraceFieldAccessAction {
+
+    TraceStaticFieldWriteAction(
+        DexField field, ProgramMethod context, FieldAccessMetadata metadata) {
+      super(field, context, metadata);
+    }
+
+    @Override
+    public void run(Enqueuer enqueuer) {
+      enqueuer.traceStaticFieldWrite(field, context, metadata);
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+      if (this == obj) {
+        return true;
+      }
+      if (obj == null || getClass() != obj.getClass()) {
+        return false;
+      }
+      TraceStaticFieldWriteAction action = (TraceStaticFieldWriteAction) obj;
+      return baseEquals(action);
+    }
+
+    @Override
+    public int hashCode() {
+      return Objects.hash(field, context.getReference(), metadata);
     }
   }
 
@@ -340,6 +538,12 @@
 
   abstract EnqueuerWorklist nonPushable();
 
+  final void enqueueAll(Collection<? extends EnqueuerAction> actions) {
+    actions.forEach(this::enqueue);
+  }
+
+  abstract void enqueue(EnqueuerAction action);
+
   abstract boolean enqueueAssertAction(Action assertion);
 
   abstract void enqueueMarkReachableDirectAction(
@@ -377,6 +581,8 @@
   public abstract void enqueueTraceConstClassAction(
       DexType type, ProgramMethod context, boolean ignoreCompatRules);
 
+  public abstract void enqueueTraceDirectAndIndirectClassInitializers(DexProgramClass clazz);
+
   public abstract void enqueueTraceInvokeDirectAction(
       DexMethod invokedMethod, ProgramMethod context);
 
@@ -385,8 +591,19 @@
 
   public abstract void enqueueTraceNewInstanceAction(DexType type, ProgramMethod context);
 
+  public abstract void enqueueTraceReflectiveFieldAccessAction(
+      ProgramField field, ProgramMethod context);
+
+  public abstract void enqueueTraceReflectiveFieldReadAction(
+      ProgramField field, ProgramMethod context);
+
+  public abstract void enqueueTraceReflectiveFieldWriteAction(
+      ProgramField field, ProgramMethod context);
+
   public abstract void enqueueTraceStaticFieldRead(DexField field, ProgramMethod context);
 
+  public abstract void enqueueTraceTypeReferenceAction(DexProgramClass clazz, KeepReason reason);
+
   static class PushableEnqueuerWorkList extends EnqueuerWorklist {
 
     PushableEnqueuerWorkList(Enqueuer enqueuer) {
@@ -399,6 +616,11 @@
     }
 
     @Override
+    void enqueue(EnqueuerAction action) {
+      queue.add(action);
+    }
+
+    @Override
     boolean enqueueAssertAction(Action assertion) {
       if (InternalOptions.assertionsEnabled()) {
         queue.add(new AssertAction(assertion));
@@ -492,6 +714,11 @@
     }
 
     @Override
+    public void enqueueTraceDirectAndIndirectClassInitializers(DexProgramClass clazz) {
+      queue.add(new TraceDirectAndIndirectClassInitializers(clazz));
+    }
+
+    @Override
     public void enqueueTraceInvokeDirectAction(DexMethod invokedMethod, ProgramMethod context) {
       queue.add(new TraceInvokeDirectAction(invokedMethod, context));
     }
@@ -507,8 +734,49 @@
     }
 
     @Override
+    public void enqueueTraceReflectiveFieldAccessAction(ProgramField field, ProgramMethod context) {
+      FieldAccessInfo info = enqueuer.getFieldAccessInfoCollection().get(field.getReference());
+      if (info == null || !info.hasReflectiveAccess()) {
+        queue.add(new TraceReflectiveFieldAccessAction(field, context));
+      }
+    }
+
+    @Override
+    public void enqueueTraceReflectiveFieldReadAction(ProgramField field, ProgramMethod context) {
+      FieldAccessInfo info = enqueuer.getFieldAccessInfoCollection().get(field.getReference());
+      if (info == null || !info.hasReflectiveRead()) {
+        queue.add(
+            new TraceReflectiveFieldAccessAction(
+                field,
+                context,
+                field.getAccessFlags().isStatic()
+                    ? FieldAccessKind.STATIC_READ
+                    : FieldAccessKind.INSTANCE_READ));
+      }
+    }
+
+    @Override
+    public void enqueueTraceReflectiveFieldWriteAction(ProgramField field, ProgramMethod context) {
+      FieldAccessInfo info = enqueuer.getFieldAccessInfoCollection().get(field.getReference());
+      if (info == null || !info.hasReflectiveWrite()) {
+        queue.add(
+            new TraceReflectiveFieldAccessAction(
+                field,
+                context,
+                field.getAccessFlags().isStatic()
+                    ? FieldAccessKind.STATIC_WRITE
+                    : FieldAccessKind.INSTANCE_WRITE));
+      }
+    }
+
+    @Override
     public void enqueueTraceStaticFieldRead(DexField field, ProgramMethod context) {
-      queue.add(new TraceStaticFieldReadAction(field, context));
+      queue.add(new TraceStaticFieldReadAction(field, context, FieldAccessMetadata.DEFAULT));
+    }
+
+    @Override
+    public void enqueueTraceTypeReferenceAction(DexProgramClass clazz, KeepReason reason) {
+      queue.add(new TraceTypeReferenceAction(clazz, reason));
     }
   }
 
@@ -523,6 +791,11 @@
       return this;
     }
 
+    @Override
+    void enqueue(EnqueuerAction action) {
+      throw attemptToEnqueue();
+    }
+
     private Unreachable attemptToEnqueue() {
       throw new Unreachable("Attempt to enqueue an action in a non pushable enqueuer work list.");
     }
@@ -614,6 +887,11 @@
     }
 
     @Override
+    public void enqueueTraceDirectAndIndirectClassInitializers(DexProgramClass clazz) {
+      throw attemptToEnqueue();
+    }
+
+    @Override
     public void enqueueTraceInvokeDirectAction(DexMethod invokedMethod, ProgramMethod context) {
       throw attemptToEnqueue();
     }
@@ -629,8 +907,28 @@
     }
 
     @Override
+    public void enqueueTraceReflectiveFieldAccessAction(ProgramField field, ProgramMethod context) {
+      throw attemptToEnqueue();
+    }
+
+    @Override
+    public void enqueueTraceReflectiveFieldReadAction(ProgramField field, ProgramMethod context) {
+      throw attemptToEnqueue();
+    }
+
+    @Override
+    public void enqueueTraceReflectiveFieldWriteAction(ProgramField field, ProgramMethod context) {
+      throw attemptToEnqueue();
+    }
+
+    @Override
     public void enqueueTraceStaticFieldRead(DexField field, ProgramMethod context) {
       throw attemptToEnqueue();
     }
+
+    @Override
+    public void enqueueTraceTypeReferenceAction(DexProgramClass clazz, KeepReason reason) {
+      throw attemptToEnqueue();
+    }
   }
 }
diff --git a/src/main/java/com/android/tools/r8/shaking/L8TreePruner.java b/src/main/java/com/android/tools/r8/shaking/L8TreePruner.java
index 46e77e8..d462818 100644
--- a/src/main/java/com/android/tools/r8/shaking/L8TreePruner.java
+++ b/src/main/java/com/android/tools/r8/shaking/L8TreePruner.java
@@ -9,7 +9,6 @@
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.ir.desugar.TypeRewriter;
 import com.android.tools.r8.utils.InternalOptions;
-import com.google.common.collect.Sets;
 import java.util.ArrayList;
 import java.util.IdentityHashMap;
 import java.util.List;
@@ -22,25 +21,24 @@
 public class L8TreePruner {
 
   private final InternalOptions options;
-  private final Set<DexType> emulatedInterfaces = Sets.newIdentityHashSet();
-  private final Set<DexType> backports = Sets.newIdentityHashSet();
   private final List<DexType> pruned = new ArrayList<>();
 
   public L8TreePruner(InternalOptions options) {
     this.options = options;
-    backports.addAll(options.machineDesugaredLibrarySpecification.getLegacyBackport().keySet());
-    emulatedInterfaces.addAll(
-        options.machineDesugaredLibrarySpecification.getEmulatedInterfaces().keySet());
   }
 
   public DexApplication prune(DexApplication app, TypeRewriter typeRewriter) {
+    Set<DexType> maintainType = options.machineDesugaredLibrarySpecification.getMaintainType();
+    Set<DexType> emulatedInterfaces =
+        options.machineDesugaredLibrarySpecification.getEmulatedInterfaces().keySet();
     Map<DexType, DexProgramClass> typeMap = new IdentityHashMap<>();
     List<DexProgramClass> toKeep = new ArrayList<>();
     boolean pruneNestMember = false;
     for (DexProgramClass aClass : app.classes()) {
       typeMap.put(aClass.type, aClass);
       if (typeRewriter.hasRewrittenType(aClass.type, null)
-          || emulatedInterfaces.contains(aClass.type)) {
+          || emulatedInterfaces.contains(aClass.type)
+          || maintainType.contains(aClass.type)) {
         toKeep.add(aClass);
       } else {
         pruneNestMember |= aClass.isInANest();
diff --git a/src/main/java/com/android/tools/r8/shaking/ObjectAllocationInfoCollectionUtils.java b/src/main/java/com/android/tools/r8/shaking/ObjectAllocationInfoCollectionUtils.java
new file mode 100644
index 0000000..b7b2513
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/shaking/ObjectAllocationInfoCollectionUtils.java
@@ -0,0 +1,80 @@
+// Copyright (c) 2022, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.shaking;
+
+import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.MethodResolutionResult.SingleResolutionResult;
+import com.android.tools.r8.graph.ObjectAllocationInfoCollection;
+import com.android.tools.r8.ir.analysis.type.ClassTypeElement;
+import com.android.tools.r8.utils.TraversalContinuation;
+
+public class ObjectAllocationInfoCollectionUtils {
+
+  public static boolean mayHaveFinalizeMethodDirectlyOrIndirectly(
+      AppView<AppInfoWithLiveness> appView, ClassTypeElement type) {
+    return mayHaveFinalizeMethodDirectlyOrIndirectly(
+        appView, type, appView.appInfo().getObjectAllocationInfoCollection());
+  }
+
+  public static boolean mayHaveFinalizeMethodDirectlyOrIndirectly(
+      AppView<? extends AppInfoWithClassHierarchy> appView,
+      ClassTypeElement type,
+      ObjectAllocationInfoCollection objectAllocationInfoCollection) {
+    // Special case for java.lang.Object.
+    if (type.getClassType() == appView.dexItemFactory().objectType
+        && !type.getInterfaces().isEmpty()) {
+      return type.getInterfaces()
+          .anyMatch(
+              (iface, isKnown) -> mayHaveFinalizer(appView, objectAllocationInfoCollection, iface));
+    }
+    return mayHaveFinalizeMethodDirectlyOrIndirectly(
+        appView, type.getClassType(), objectAllocationInfoCollection);
+  }
+
+  public static boolean mayHaveFinalizeMethodDirectlyOrIndirectly(
+      AppView<? extends AppInfoWithClassHierarchy> appView,
+      DexType type,
+      ObjectAllocationInfoCollection objectAllocationInfoCollection) {
+    // Special case for java.lang.Object.
+    if (type == appView.dexItemFactory().objectType) {
+      // The type java.lang.Object could be any instantiated type. Assume a finalizer exists.
+      return true;
+    }
+    return mayHaveFinalizer(appView, objectAllocationInfoCollection, type);
+  }
+
+  private static boolean mayHaveFinalizer(
+      AppView<? extends AppInfoWithClassHierarchy> appView,
+      ObjectAllocationInfoCollection objectAllocationInfoCollection,
+      DexType type) {
+    // A type may have an active finalizer if any derived instance has a finalizer.
+    return objectAllocationInfoCollection
+        .traverseInstantiatedSubtypes(
+            type,
+            clazz -> {
+              if (objectAllocationInfoCollection.isInterfaceWithUnknownSubtypeHierarchy(clazz)) {
+                return TraversalContinuation.doBreak();
+              } else {
+                SingleResolutionResult resolution =
+                    appView
+                        .appInfo()
+                        .resolveMethodOn(clazz, appView.dexItemFactory().objectMembers.finalize)
+                        .asSingleResolution();
+                if (resolution != null && resolution.getResolvedHolder().isProgramClass()) {
+                  return TraversalContinuation.doBreak();
+                }
+              }
+              return TraversalContinuation.doContinue();
+            },
+            lambda -> {
+              // Lambda classes do not have finalizers.
+              return TraversalContinuation.doContinue();
+            },
+            appView.appInfo())
+        .isBreak();
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/synthesis/SyntheticFinalization.java b/src/main/java/com/android/tools/r8/synthesis/SyntheticFinalization.java
index 07f65be..ef3cdbd 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SyntheticFinalization.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticFinalization.java
@@ -315,7 +315,7 @@
     // Check that the prefix of each synthetic is never itself synthetic.
     committed.forEachNonLegacyItem(
         item -> {
-          if (item.getKind().allowSyntheticContext()) {
+          if (item.getKind().isGlobal()) {
             return;
           }
           String prefix =
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 48ff52a..cee58c4 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SyntheticItems.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticItems.java
@@ -7,6 +7,7 @@
 
 import com.android.tools.r8.FeatureSplit;
 import com.android.tools.r8.contexts.CompilationContext.UniqueContext;
+import com.android.tools.r8.errors.MissingGlobalSyntheticsConsumerDiagnostic;
 import com.android.tools.r8.features.ClassToFeatureSplitMap;
 import com.android.tools.r8.graph.AppInfo;
 import com.android.tools.r8.graph.AppView;
@@ -278,6 +279,32 @@
     return isSyntheticClass(clazz.type);
   }
 
+  public boolean isGlobalSyntheticClass(DexProgramClass clazz) {
+    SyntheticDefinition<?, ?, ?> definition = pending.nonLegacyDefinitions.get(clazz.type);
+    if (definition != null) {
+      return definition.getKind().isGlobal();
+    }
+    return isGlobalReferences(committed.getNonLegacyClasses().get(clazz.type));
+  }
+
+  private static boolean isGlobalReferences(List<SyntheticProgramClassReference> references) {
+    if (references == null) {
+      return false;
+    }
+    if (references.size() == 1 && references.get(0).getKind().isGlobal()) {
+      return true;
+    }
+    assert verifyNoGlobals(references);
+    return false;
+  }
+
+  private static boolean verifyNoGlobals(List<SyntheticProgramClassReference> references) {
+    for (SyntheticProgramClassReference reference : references) {
+      assert !reference.getKind().isGlobal();
+    }
+    return true;
+  }
+
   public boolean isSyntheticOfKind(DexType type, SyntheticKindSelector kindSelector) {
     SyntheticKind kind = kindSelector.select(naming);
     return pending.containsTypeOfKind(type, kind) || committed.containsTypeOfKind(type, kind);
@@ -769,14 +796,20 @@
     }
   }
 
-  public DexProgramClass ensureFixedClassFromType(
+  public DexProgramClass ensureGlobalClass(
+      Supplier<MissingGlobalSyntheticsConsumerDiagnostic> diagnosticSupplier,
       SyntheticKindSelector kindSelector,
-      DexType contextType,
+      DexType globalType,
       AppView<?> appView,
       Consumer<SyntheticProgramClassBuilder> fn,
       Consumer<DexProgramClass> onCreationConsumer) {
     SyntheticKind kind = kindSelector.select(naming);
-    SynthesizingContext outerContext = SynthesizingContext.fromType(contextType);
+    assert kind.isGlobal();
+    if (appView.options().intermediate && !appView.options().hasGlobalSyntheticsConsumer()) {
+      appView.reporter().fatalError(diagnosticSupplier.get());
+    }
+    // A global type is its own context.
+    SynthesizingContext outerContext = SynthesizingContext.fromType(globalType);
     return internalEnsureFixedProgramClass(kind, fn, onCreationConsumer, outerContext, appView);
   }
 
diff --git a/src/main/java/com/android/tools/r8/synthesis/SyntheticMarker.java b/src/main/java/com/android/tools/r8/synthesis/SyntheticMarker.java
index c21954b..ceb9a37 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SyntheticMarker.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticMarker.java
@@ -147,6 +147,9 @@
 
   private static DexType getSyntheticContextType(
       DexType type, SyntheticKind kind, DexItemFactory factory) {
+    if (kind.isGlobal()) {
+      return type;
+    }
     String prefix = SyntheticNaming.getPrefixForExternalSyntheticType(kind, type);
     return factory.createType(DescriptorUtils.getDescriptorFromClassBinaryName(prefix));
   }
diff --git a/src/main/java/com/android/tools/r8/synthesis/SyntheticNaming.java b/src/main/java/com/android/tools/r8/synthesis/SyntheticNaming.java
index e95c6a1..37e327f 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SyntheticNaming.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticNaming.java
@@ -122,15 +122,15 @@
     }
 
     SyntheticKind forFixedClass(String descriptor) {
-      return register(new SyntheticFixedClassKind(getNextId(), descriptor, false, false));
+      return register(new SyntheticFixedClassKind(getNextId(), descriptor, false));
     }
 
     SyntheticKind forGlobalClass() {
-      return register(new SyntheticFixedClassKind(getNextId(), "", true, true));
+      return register(new SyntheticFixedClassKind(getNextId(), "", true));
     }
 
     SyntheticKind forGlobalClasspathClass() {
-      return register(new SyntheticFixedClassKind(getNextId(), "", false, false));
+      return register(new SyntheticFixedClassKind(getNextId(), "", false));
     }
 
     List<SyntheticKind> getAllKinds() {
@@ -192,7 +192,6 @@
 
     public abstract boolean isMayOverridesNonProgramType();
 
-    public abstract boolean allowSyntheticContext();
   }
 
   private static class SyntheticMethodKind extends SyntheticKind {
@@ -227,10 +226,6 @@
       return false;
     }
 
-    @Override
-    public boolean allowSyntheticContext() {
-      return false;
-    }
   }
 
   private static class SyntheticClassKind extends SyntheticKind {
@@ -268,24 +263,14 @@
       return false;
     }
 
-    @Override
-    public boolean allowSyntheticContext() {
-      return false;
-    }
   }
 
   private static class SyntheticFixedClassKind extends SyntheticClassKind {
     private final boolean mayOverridesNonProgramType;
-    private final boolean allowSyntheticContext;
 
-    private SyntheticFixedClassKind(
-        int id,
-        String descriptor,
-        boolean mayOverridesNonProgramType,
-        boolean allowSyntheticContext) {
+    private SyntheticFixedClassKind(int id, String descriptor, boolean mayOverridesNonProgramType) {
       super(id, descriptor, false);
       this.mayOverridesNonProgramType = mayOverridesNonProgramType;
-      this.allowSyntheticContext = allowSyntheticContext;
     }
 
     @Override
@@ -308,10 +293,6 @@
       return mayOverridesNonProgramType;
     }
 
-    @Override
-    public boolean allowSyntheticContext() {
-      return allowSyntheticContext;
-    }
   }
 
   private static final String SYNTHETIC_CLASS_SEPARATOR = "$$";
@@ -333,6 +314,9 @@
 
   static String getPrefixForExternalSyntheticType(SyntheticKind kind, DexType type) {
     String binaryName = type.toBinaryName();
+    if (kind.isGlobal()) {
+      return binaryName;
+    }
     int index =
         binaryName.lastIndexOf(
             kind.isFixedSuffixSynthetic() ? kind.descriptor : SYNTHETIC_CLASS_SEPARATOR);
diff --git a/src/main/java/com/android/tools/r8/tracereferences/Tracer.java b/src/main/java/com/android/tools/r8/tracereferences/Tracer.java
index 97b2621..c5ffc7a 100644
--- a/src/main/java/com/android/tools/r8/tracereferences/Tracer.java
+++ b/src/main/java/com/android/tools/r8/tracereferences/Tracer.java
@@ -349,8 +349,8 @@
         handleRewrittenMethodResolution(
             method,
             lookupResult.getType().isInterface()
-                ? appInfo().resolveMethodOnInterface(method)
-                : appInfo().resolveMethodOnClass(method));
+                ? appInfo().resolveMethodOnInterfaceHolder(method)
+                : appInfo().resolveMethodOnClassHolder(method));
       }
 
       private void handleRewrittenMethodResolution(
diff --git a/src/main/java/com/android/tools/r8/utils/DescriptorUtils.java b/src/main/java/com/android/tools/r8/utils/DescriptorUtils.java
index bfd8334..7cce06b 100644
--- a/src/main/java/com/android/tools/r8/utils/DescriptorUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/DescriptorUtils.java
@@ -229,6 +229,14 @@
         || c == 'D';
   }
 
+  public static boolean isVoidDescriptor(String descriptor) {
+    return descriptor.length() == 1 && isVoidType(descriptor.charAt(0));
+  }
+
+  public static boolean isVoidType(char c) {
+    return c == 'V';
+  }
+
   public static boolean isArrayDescriptor(String descriptor) {
     if (descriptor.length() < 2) {
       return false;
@@ -270,6 +278,31 @@
     }
   }
 
+  public static String primitiveDescriptorToBoxedInternalName(char primitive) {
+    switch (primitive) {
+      case 'V':
+        return "java/lang/Void";
+      case 'Z':
+        return "java/lang/Boolean";
+      case 'B':
+        return "java/lang/Byte";
+      case 'S':
+        return "java/lang/Short";
+      case 'C':
+        return "java/lang/Character";
+      case 'I':
+        return "java/lang/Integer";
+      case 'J':
+        return "java/lang/Long";
+      case 'F':
+        return "java/lang/Float";
+      case 'D':
+        return "java/lang/Double";
+      default:
+        throw new Unreachable("Unknown type " + primitive);
+    }
+  }
+
   /**
    * Get unqualified class name from its descriptor.
    *
@@ -327,7 +360,7 @@
     return Integer.max(classDescriptor.lastIndexOf("/"), 0) + 1;
   }
 
-   /**
+  /**
    * Get canonical class name from its descriptor.
    *
    * @param classDescriptor a class descriptor i.e. "La/b/C$D;"
diff --git a/src/main/java/com/android/tools/r8/utils/DeterminismChecker.java b/src/main/java/com/android/tools/r8/utils/DeterminismChecker.java
index 4871186..2f95428 100644
--- a/src/main/java/com/android/tools/r8/utils/DeterminismChecker.java
+++ b/src/main/java/com/android/tools/r8/utils/DeterminismChecker.java
@@ -10,12 +10,13 @@
 import java.io.BufferedReader;
 import java.io.BufferedWriter;
 import java.io.Closeable;
+import java.io.FileOutputStream;
 import java.io.IOException;
+import java.io.OutputStreamWriter;
 import java.io.Writer;
 import java.nio.charset.StandardCharsets;
 import java.nio.file.Files;
 import java.nio.file.Path;
-import java.nio.file.StandardOpenOption;
 import java.util.ArrayList;
 import java.util.Comparator;
 import java.util.List;
@@ -44,12 +45,14 @@
               return new LineCallbackChecker(Files.newBufferedReader(log, StandardCharsets.UTF_8));
             } else {
               System.out.println("Writing determinism log: " + log);
+              // Note that Files.newBufferedWriter will cause issues in presence of malformed input
+              // and unmappable character errors, since Files.newBufferedWriter uses the
+              // java.nio.charset.CharsetDecoder default action, which is to report such errors,
+              // instead of dealing with them in java.nio.charset.CharsetDecoder#onMalformedInput.
               BufferedWriter bufferedWriter =
-                  Files.newBufferedWriter(
-                      log,
-                      StandardCharsets.UTF_8,
-                      StandardOpenOption.CREATE,
-                      StandardOpenOption.TRUNCATE_EXISTING);
+                  new BufferedWriter(
+                      new OutputStreamWriter(
+                          new FileOutputStream(log.toFile()), StandardCharsets.UTF_8));
               return new LineCallbackWriter(bufferedWriter);
             }
           }
@@ -82,6 +85,13 @@
     return method.getReference().toSourceString();
   }
 
+  public <E extends Exception> void accept(ThrowingConsumer<LineCallback, E> consumer)
+      throws E, IOException {
+    try (LineCallback callback = callbackFactory.createCallback()) {
+      consumer.accept(callback);
+    }
+  }
+
   public void check(AppView<?> appView) {
     try (LineCallback callback = callbackFactory.createCallback()) {
       List<DexProgramClass> classes = new ArrayList<>(appView.appInfo().classes());
diff --git a/src/main/java/com/android/tools/r8/utils/FileUtils.java b/src/main/java/com/android/tools/r8/utils/FileUtils.java
index 20bde1a..ef26890 100644
--- a/src/main/java/com/android/tools/r8/utils/FileUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/FileUtils.java
@@ -31,6 +31,7 @@
   public static final String KT_EXTENSION = ".kt";
   public static final String MODULE_INFO_CLASS = "module-info.class";
   public static final String MODULES_PREFIX = "/modules";
+  public static final String GLOBAL_SYNTHETIC_EXTENSION = ".global";
 
   public static final boolean isAndroid =
       System.getProperty("java.vm.name").equalsIgnoreCase("Dalvik");
diff --git a/src/main/java/com/android/tools/r8/utils/InternalGlobalSyntheticsProgramConsumer.java b/src/main/java/com/android/tools/r8/utils/InternalGlobalSyntheticsProgramConsumer.java
new file mode 100644
index 0000000..3945a6e
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/utils/InternalGlobalSyntheticsProgramConsumer.java
@@ -0,0 +1,125 @@
+// Copyright (c) 2022, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.utils;
+
+import static com.android.tools.r8.utils.FileUtils.GLOBAL_SYNTHETIC_EXTENSION;
+
+import com.android.tools.r8.ByteDataView;
+import com.android.tools.r8.ClassFileConsumer;
+import com.android.tools.r8.DexFilePerClassFileConsumer;
+import com.android.tools.r8.DiagnosticsHandler;
+import com.android.tools.r8.GlobalSyntheticsConsumer;
+import com.android.tools.r8.ProgramResource.Kind;
+import com.android.tools.r8.Version;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Set;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipOutputStream;
+
+public abstract class InternalGlobalSyntheticsProgramConsumer {
+
+  public static final String COMPILER_INFO_ENTRY_NAME = "compilerinfo";
+  public static final String OUTPUT_KIND_ENTRY_NAME = "kind";
+
+  private final GlobalSyntheticsConsumer consumer;
+  private final List<Pair<String, byte[]>> content = new ArrayList<>();
+
+  public InternalGlobalSyntheticsProgramConsumer(GlobalSyntheticsConsumer consumer) {
+    this.consumer = consumer;
+  }
+
+  public abstract Kind getKind();
+
+  synchronized void addGlobalSynthetic(String descriptor, byte[] data) {
+    add(getGlobalSyntheticFileName(descriptor), data);
+  }
+
+  private void add(String entryName, byte[] data) {
+    content.add(new Pair<>(entryName, data));
+  }
+
+  public void finished(DiagnosticsHandler handler) {
+    // Add meta information.
+    add(COMPILER_INFO_ENTRY_NAME, Version.getVersionString().getBytes(StandardCharsets.UTF_8));
+    add(OUTPUT_KIND_ENTRY_NAME, getKind().toString().getBytes(StandardCharsets.UTF_8));
+
+    // Size estimate to avoid reallocation of the byte output array.
+    final int zipHeaderOverhead = 500;
+    final int zipEntryOverhead = 200;
+    int estimatedZipSize =
+        zipHeaderOverhead
+            + ListUtils.fold(
+                content,
+                0,
+                (acc, pair) ->
+                    acc + pair.getFirst().length() + pair.getSecond().length + zipEntryOverhead);
+    ByteArrayOutputStream baos = new ByteArrayOutputStream(estimatedZipSize);
+    try (ZipOutputStream stream = new ZipOutputStream(baos)) {
+      for (Pair<String, byte[]> pair : content) {
+        ZipUtils.writeToZipStream(stream, pair.getFirst(), pair.getSecond(), ZipEntry.STORED);
+        // Clear out the bytes to avoid three copies when converting the boas.
+        pair.setSecond(null);
+      }
+    } catch (IOException e) {
+      handler.error(new ExceptionDiagnostic(e));
+    }
+    byte[] bytes = baos.toByteArray();
+    consumer.accept(bytes);
+  }
+
+  private static String getGlobalSyntheticFileName(String descriptor) {
+    assert descriptor != null && DescriptorUtils.isClassDescriptor(descriptor);
+    return DescriptorUtils.getClassBinaryNameFromDescriptor(descriptor)
+        + GLOBAL_SYNTHETIC_EXTENSION;
+  }
+
+  public static class InternalGlobalSyntheticsDexConsumer
+      extends InternalGlobalSyntheticsProgramConsumer implements DexFilePerClassFileConsumer {
+
+    public InternalGlobalSyntheticsDexConsumer(GlobalSyntheticsConsumer consumer) {
+      super(consumer);
+    }
+
+    @Override
+    public Kind getKind() {
+      return Kind.DEX;
+    }
+
+    @Override
+    public void accept(
+        String primaryClassDescriptor,
+        ByteDataView data,
+        Set<String> descriptors,
+        DiagnosticsHandler handler) {
+      addGlobalSynthetic(primaryClassDescriptor, data.copyByteData());
+    }
+
+    @Override
+    public boolean combineSyntheticClassesWithPrimaryClass() {
+      return false;
+    }
+  }
+
+  public static class InternalGlobalSyntheticsCfConsumer
+      extends InternalGlobalSyntheticsProgramConsumer implements ClassFileConsumer {
+
+    public InternalGlobalSyntheticsCfConsumer(GlobalSyntheticsConsumer consumer) {
+      super(consumer);
+    }
+
+    @Override
+    public Kind getKind() {
+      return Kind.CF;
+    }
+
+    @Override
+    public void accept(ByteDataView data, String descriptor, DiagnosticsHandler handler) {
+      addGlobalSynthetic(descriptor, data.copyByteData());
+    }
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/utils/InternalGlobalSyntheticsProgramProvider.java b/src/main/java/com/android/tools/r8/utils/InternalGlobalSyntheticsProgramProvider.java
new file mode 100644
index 0000000..6b2d828
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/utils/InternalGlobalSyntheticsProgramProvider.java
@@ -0,0 +1,100 @@
+// Copyright (c) 2022, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.utils;
+
+import com.android.tools.r8.GlobalSyntheticsResourceProvider;
+import com.android.tools.r8.ProgramResource;
+import com.android.tools.r8.ProgramResource.Kind;
+import com.android.tools.r8.ProgramResourceProvider;
+import com.android.tools.r8.ResourceException;
+import com.android.tools.r8.Version;
+import com.android.tools.r8.origin.ArchiveEntryOrigin;
+import com.google.common.io.ByteStreams;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.function.Function;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipInputStream;
+
+public class InternalGlobalSyntheticsProgramProvider implements ProgramResourceProvider {
+
+  private final List<GlobalSyntheticsResourceProvider> providers;
+  private List<ProgramResource> resources = null;
+
+  public InternalGlobalSyntheticsProgramProvider(List<GlobalSyntheticsResourceProvider> providers) {
+    this.providers = providers;
+  }
+
+  @Override
+  public Collection<ProgramResource> getProgramResources() throws ResourceException {
+    if (resources == null) {
+      ensureResources();
+    }
+    return resources;
+  }
+
+  private synchronized void ensureResources() throws ResourceException {
+    if (resources != null) {
+      return;
+    }
+    List<ProgramResource> resources = new ArrayList<>();
+    Set<String> seen = new HashSet<>();
+    for (GlobalSyntheticsResourceProvider provider : providers) {
+      List<Function<Kind, ProgramResource>> delayedResouces = new ArrayList<>();
+      Kind providerKind = null;
+      try (ZipInputStream stream = new ZipInputStream(provider.getByteStream())) {
+        ZipEntry entry;
+        while (null != (entry = stream.getNextEntry())) {
+          String name = entry.getName();
+          if (name.equals(InternalGlobalSyntheticsProgramConsumer.OUTPUT_KIND_ENTRY_NAME)) {
+            providerKind =
+                Kind.valueOf(new String(ByteStreams.toByteArray(stream), StandardCharsets.UTF_8));
+          } else if (name.equals(
+              InternalGlobalSyntheticsProgramConsumer.COMPILER_INFO_ENTRY_NAME)) {
+            String version = new String(ByteStreams.toByteArray(stream), StandardCharsets.UTF_8);
+            if (!Version.getVersionString().equals(version)) {
+              throw new ResourceException(
+                  provider.getOrigin(),
+                  "Outdated or inconsistent global synthetics information."
+                      + "\nGlobal synthetics information version: "
+                      + version
+                      + "\nCompiler version: "
+                      + Version.getVersionString());
+            }
+          } else if (name.endsWith(FileUtils.GLOBAL_SYNTHETIC_EXTENSION) && seen.add(name)) {
+            ArchiveEntryOrigin origin = new ArchiveEntryOrigin(name, provider.getOrigin());
+            String descriptor = guessTypeDescriptor(name);
+            byte[] bytes = ByteStreams.toByteArray(stream);
+            Set<String> descriptors = Collections.singleton(descriptor);
+            delayedResouces.add(
+                kind -> OneShotByteResource.create(kind, origin, bytes, descriptors));
+          }
+        }
+      } catch (IOException e) {
+        throw new ResourceException(provider.getOrigin(), e);
+      }
+      if (providerKind == null) {
+        throw new ResourceException(
+            provider.getOrigin(),
+            "Invalid global synthetics provider does not specify its content kind.");
+      }
+      for (Function<Kind, ProgramResource> fn : delayedResouces) {
+        resources.add(fn.apply(providerKind));
+      }
+    }
+    this.resources = resources;
+  }
+
+  private String guessTypeDescriptor(String name) {
+    String noExt = name.substring(0, name.length() - FileUtils.GLOBAL_SYNTHETIC_EXTENSION.length());
+    String classExt = noExt + FileUtils.CLASS_EXTENSION;
+    return DescriptorUtils.guessTypeDescriptor(classExt);
+  }
+}
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 dcf8dbd..133f721 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -12,6 +12,7 @@
 import com.android.tools.r8.DexFilePerClassFileConsumer;
 import com.android.tools.r8.DexIndexedConsumer;
 import com.android.tools.r8.FeatureSplit;
+import com.android.tools.r8.GlobalSyntheticsConsumer;
 import com.android.tools.r8.MapIdProvider;
 import com.android.tools.r8.ProgramConsumer;
 import com.android.tools.r8.SourceFileProvider;
@@ -137,7 +138,7 @@
   }
 
   public static final CfVersion SUPPORTED_CF_VERSION = CfVersion.V17;
-  public static final CfVersion EXPERIMENTAL_CF_VERSION = CfVersion.V12;
+  public static final CfVersion EXPERIMENTAL_CF_VERSION = CfVersion.V14;
 
   public static final int SUPPORTED_DEX_VERSION =
       AndroidApiLevel.LATEST.getDexVersion().getIntValue();
@@ -164,6 +165,8 @@
   // TODO(zerny): Make this private-final once we have full program-consumer support.
   public ProgramConsumer programConsumer = null;
 
+  private GlobalSyntheticsConsumer globalSyntheticsConsumer = null;
+
   public DataResourceConsumer dataResourceConsumer;
   public FeatureSplitConfiguration featureSplitConfiguration;
   public StartupConfiguration startupConfiguration;
@@ -271,6 +274,7 @@
 
   // Flag to toggle if the prefix based merge restriction should be enforced.
   public boolean enableNeverMergePrefixes = true;
+  // TODO(b/227277105): Control merging with desugared library and maintain prefix.
   public Set<String> neverMergePrefixes = ImmutableSet.of("j$.");
 
   public boolean classpathInterfacesMayHaveStaticInitialization = false;
@@ -444,6 +448,18 @@
     throw new UnsupportedOperationException("Cannot find internal output mode.");
   }
 
+  public boolean hasGlobalSyntheticsConsumer() {
+    return globalSyntheticsConsumer != null;
+  }
+
+  public GlobalSyntheticsConsumer getGlobalSyntheticsConsumer() {
+    return globalSyntheticsConsumer;
+  }
+
+  public void setGlobalSyntheticsConsumer(GlobalSyntheticsConsumer globalSyntheticsConsumer) {
+    this.globalSyntheticsConsumer = globalSyntheticsConsumer;
+  }
+
   public boolean isAndroidPlatform() {
     return minApiLevel == ANDROID_PLATFORM;
   }
@@ -922,6 +938,10 @@
     }
     timing.begin("Load machine specification");
     loadMachineDesugaredLibrarySpecification.accept(timing, app);
+    if (!machineDesugaredLibrarySpecification.getMaintainType().isEmpty()) {
+      // TODO(b/227277105): Control merging with desugared library and maintain prefix.
+      neverMergePrefixes = ImmutableSet.of();
+    }
     timing.end();
   }
 
@@ -1449,10 +1469,14 @@
       this.enable = enable;
     }
 
-    public int getMaxClassGroupSize() {
+    public int getMaxClassGroupSizeInR8() {
       return 30;
     }
 
+    public int getMaxClassGroupSizeInD8() {
+      return 100;
+    }
+
     public int getMaxInterfaceGroupSize() {
       return 100;
     }
@@ -1672,24 +1696,37 @@
     private boolean hasReadCheckDeterminism = false;
     private DeterminismChecker determinismChecker = null;
 
-    public void setDeterminismChecker(DeterminismChecker checker) {
-      determinismChecker = checker;
-    }
-
-    public void checkDeterminism(AppView<?> appView) {
+    private DeterminismChecker getDeterminismChecker() {
       // Lazily read the env-var so that it can be set after options init.
       if (determinismChecker == null && !hasReadCheckDeterminism) {
         hasReadCheckDeterminism = true;
         String dir = System.getProperty("com.android.tools.r8.checkdeterminism");
         if (dir != null) {
-          determinismChecker = DeterminismChecker.createWithFileBacking(Paths.get(dir));
+          setDeterminismChecker(DeterminismChecker.createWithFileBacking(Paths.get(dir)));
         }
       }
+      return determinismChecker;
+    }
+
+    public void setDeterminismChecker(DeterminismChecker checker) {
+      determinismChecker = checker;
+    }
+
+    public void checkDeterminism(AppView<?> appView) {
+      DeterminismChecker determinismChecker = getDeterminismChecker();
       if (determinismChecker != null) {
         determinismChecker.check(appView);
       }
     }
 
+    public <E extends Exception> void checkDeterminism(
+        ThrowingConsumer<DeterminismChecker, E> consumer) {
+      DeterminismChecker determinismChecker = getDeterminismChecker();
+      if (determinismChecker != null) {
+        consumer.acceptWithRuntimeException(determinismChecker);
+      }
+    }
+
     public static void allowExperimentClassFileVersion(InternalOptions options) {
       options.reportedExperimentClassFileVersion.set(true);
     }
@@ -1760,6 +1797,7 @@
     public boolean enableCheckCastAndInstanceOfRemoval = true;
     public boolean enableDeadSwitchCaseElimination = true;
     public boolean enableInvokeSuperToInvokeVirtualRewriting = true;
+    public boolean enableMultiANewArrayDesugaringForClassFiles = false;
     public boolean enableSwitchToIfRewriting = true;
     public boolean enableEnumUnboxingDebugLogs = false;
     public boolean forceRedundantConstNumberRemoval = false;
@@ -1867,6 +1905,8 @@
     public Predicate<DexMethod> cfByteCodePassThrough = null;
 
     public boolean enableExperimentalMapFileVersion = false;
+
+    public boolean alwaysGenerateLambdaFactoryMethods = false;
   }
 
   public MapVersion getMapFileVersion() {
@@ -2077,8 +2117,7 @@
   // and the first register of the result could lead to the wrong exception
   // being thrown on out of bounds.
   public boolean canUseSameArrayAndResultRegisterInArrayGetWide() {
-    assert isGeneratingDex();
-    return getMinApiLevel().isGreaterThan(AndroidApiLevel.O_MR1);
+    return isGeneratingClassFiles() || getMinApiLevel().isGreaterThan(AndroidApiLevel.O_MR1);
   }
 
   // Some Lollipop versions of Art found in the wild perform invalid bounds
@@ -2309,8 +2348,7 @@
   //
   // Fixed in Android Q, see b/120985556.
   public boolean canHaveArtInstanceOfVerifierBug() {
-    assert isGeneratingDex();
-    return getMinApiLevel().isLessThan(AndroidApiLevel.Q);
+    return isGeneratingDex() && getMinApiLevel().isLessThan(AndroidApiLevel.Q);
   }
 
   // Some Art Lollipop version do not deal correctly with long-to-int conversions.
diff --git a/src/main/java/com/android/tools/r8/utils/LineNumberOptimizer.java b/src/main/java/com/android/tools/r8/utils/LineNumberOptimizer.java
index 22932f6..78f612a 100644
--- a/src/main/java/com/android/tools/r8/utils/LineNumberOptimizer.java
+++ b/src/main/java/com/android/tools/r8/utils/LineNumberOptimizer.java
@@ -40,6 +40,7 @@
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.DexValue.DexValueString;
 import com.android.tools.r8.graph.GraphLens;
+import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.code.Position;
 import com.android.tools.r8.ir.code.Position.OutlineCallerPosition;
 import com.android.tools.r8.ir.code.Position.OutlineCallerPosition.OutlineCallerPositionBuilder;
@@ -493,7 +494,7 @@
     for (DexProgramClass clazz : application.classes()) {
       boolean isSyntheticClass = appView.getSyntheticItems().isSyntheticClass(clazz);
 
-      IdentityHashMap<DexString, List<DexEncodedMethod>> methodsByRenamedName =
+      IdentityHashMap<DexString, List<ProgramMethod>> methodsByRenamedName =
           groupMethodsByRenamedName(appView.graphLens(), namingLens, clazz);
 
       // At this point we don't know if we really need to add this class to the builder.
@@ -538,7 +539,7 @@
       List<DexString> renamedMethodNames = new ArrayList<>(methodsByRenamedName.keySet());
       renamedMethodNames.sort(DexString::compareTo);
       for (DexString methodName : renamedMethodNames) {
-        List<DexEncodedMethod> methods = methodsByRenamedName.get(methodName);
+        List<ProgramMethod> methods = methodsByRenamedName.get(methodName);
         if (methods.size() > 1) {
           // If there are multiple methods with the same name (overloaded) then sort them for
           // deterministic behaviour: the algorithm will assign new line numbers in this order.
@@ -566,26 +567,27 @@
             new KotlinInlineFunctionPositionRemapper(
                 appView, positionRemapper, cfLineToMethodMapper);
 
-        for (DexEncodedMethod method : methods) {
-          kotlinRemapper.currentMethod = method;
+        for (ProgramMethod method : methods) {
+          DexEncodedMethod definition = method.getDefinition();
+          kotlinRemapper.currentMethod = definition;
           List<MappedPosition> mappedPositions;
-          Code code = method.getCode();
+          Code code = definition.getCode();
           boolean canUseDexPc =
-              methods.size() == 1 && representation.useDexPcEncoding(clazz, method);
+              methods.size() == 1 && representation.useDexPcEncoding(clazz, definition);
           if (code != null) {
             if (code.isDexCode() && doesContainPositions(code.asDexCode())) {
               if (canUseDexPc) {
                 mappedPositions =
                     optimizeDexCodePositionsForPc(
-                        method, appView, kotlinRemapper, pcBasedDebugInfo);
+                        definition, appView, kotlinRemapper, pcBasedDebugInfo);
               } else {
                 mappedPositions =
                     optimizeDexCodePositions(
-                        method, appView, kotlinRemapper, identityMapping, methods.size() != 1);
+                        definition, appView, kotlinRemapper, identityMapping, methods.size() != 1);
               }
             } else if (code.isCfCode()
                 && doesContainPositions(code.asCfCode())
-                && !appView.isCfByteCodePassThrough(method)) {
+                && !appView.isCfByteCodePassThrough(definition)) {
               mappedPositions = optimizeCfCodePositions(method, kotlinRemapper, appView);
             } else {
               mappedPositions = new ArrayList<>();
@@ -603,7 +605,7 @@
           String obfuscatedName = obfuscatedNameDexString.toString();
 
           List<MappingInformation> methodMappingInfo = new ArrayList<>();
-          if (method.isD8R8Synthesized()) {
+          if (definition.isD8R8Synthesized()) {
             methodMappingInfo.add(CompilerSynthesizedMappingInformation.builder().build());
           }
 
@@ -613,8 +615,8 @@
               && obfuscatedNameDexString == originalMethod.name
               && originalMethod.holder == originalType) {
             assert appView.options().lineNumberOptimization == LineNumberOptimization.OFF
-                || !doesContainPositions(method)
-                || appView.isCfByteCodePassThrough(method);
+                || !doesContainPositions(definition)
+                || appView.isCfByteCodePassThrough(definition);
             continue;
           }
 
@@ -691,8 +693,8 @@
               }
             }
             Range obfuscatedRange;
-            if (method.getCode().isDexCode()
-                && method.getCode().asDexCode().getDebugInfo()
+            if (definition.getCode().isDexCode()
+                && definition.getCode().asDexCode().getDebugInfo()
                     == DexDebugInfoForSingleLineMethod.getInstance()) {
               assert firstPosition.originalLine == lastPosition.originalLine;
               obfuscatedRange = new Range(0, MAX_LINE_NUMBER);
@@ -745,11 +747,11 @@
             }
             i = j;
           }
-          if (method.getCode().isDexCode()
-              && method.getCode().asDexCode().getDebugInfo()
+          if (definition.getCode().isDexCode()
+              && definition.getCode().asDexCode().getDebugInfo()
                   == DexDebugInfoForSingleLineMethod.getInstance()) {
             pcBasedDebugInfo.recordSingleLineFor(
-                method.getCode().asDexCode(), method.getParameters().size());
+                definition.getCode().asDexCode(), method.getParameters().size());
           }
         } // for each method of the group
       } // for each method group, grouped by name
@@ -815,7 +817,7 @@
   }
 
   private static boolean verifyMethodsAreKeptDirectlyOrIndirectly(
-      AppView<?> appView, List<DexEncodedMethod> methods) {
+      AppView<?> appView, List<ProgramMethod> methods) {
     if (appView.options().isGeneratingClassFiles() || !appView.appInfo().hasClassHierarchy()) {
       return true;
     }
@@ -823,9 +825,9 @@
     KeepInfoCollection keepInfo = appView.getKeepInfo();
     boolean allSeenAreInstanceInitializers = true;
     DexString originalName = null;
-    for (DexEncodedMethod method : methods) {
+    for (ProgramMethod method : methods) {
       // We cannot rename instance initializers.
-      if (method.isInstanceInitializer()) {
+      if (method.getDefinition().isInstanceInitializer()) {
         assert allSeenAreInstanceInitializers;
         continue;
       }
@@ -835,7 +837,7 @@
         continue;
       }
       // With desugared library, call-backs names are reserved here.
-      if (method.isLibraryMethodOverride().isTrue()) {
+      if (method.getDefinition().isLibraryMethodOverride().isTrue()) {
         continue;
       }
       // We use the same name for interface names even if it has different types.
@@ -856,8 +858,8 @@
     return true;
   }
 
-  private static int getMethodStartLine(DexEncodedMethod method) {
-    Code code = method.getCode();
+  private static int getMethodStartLine(ProgramMethod method) {
+    Code code = method.getDefinition().getCode();
     if (code == null) {
       return 0;
     }
@@ -878,14 +880,14 @@
 
   // Sort by startline, then DexEncodedMethod.slowCompare.
   // Use startLine = 0 if no debuginfo.
-  private static void sortMethods(List<DexEncodedMethod> methods) {
+  private static void sortMethods(List<ProgramMethod> methods) {
     methods.sort(
         (lhs, rhs) -> {
           int lhsStartLine = getMethodStartLine(lhs);
           int rhsStartLine = getMethodStartLine(rhs);
           int startLineDiff = lhsStartLine - rhsStartLine;
           if (startLineDiff != 0) return startLineDiff;
-          return DexEncodedMethod.slowCompare(lhs, rhs);
+          return DexEncodedMethod.slowCompare(lhs.getDefinition(), rhs.getDefinition());
         });
   }
 
@@ -921,21 +923,22 @@
         });
   }
 
-  public static IdentityHashMap<DexString, List<DexEncodedMethod>> groupMethodsByRenamedName(
+  public static IdentityHashMap<DexString, List<ProgramMethod>> groupMethodsByRenamedName(
       GraphLens graphLens, NamingLens namingLens, DexProgramClass clazz) {
-    IdentityHashMap<DexString, List<DexEncodedMethod>> methodsByRenamedName =
+    IdentityHashMap<DexString, List<ProgramMethod>> methodsByRenamedName =
         new IdentityHashMap<>(clazz.getMethodCollection().size());
-    for (DexEncodedMethod encodedMethod : clazz.methods()) {
+    for (ProgramMethod programMethod : clazz.programMethods()) {
       // Add method only if renamed, moved, or contains positions.
-      DexMethod method = encodedMethod.getReference();
+      DexEncodedMethod definition = programMethod.getDefinition();
+      DexMethod method = programMethod.getReference();
       DexString renamedName = namingLens.lookupName(method);
       if (renamedName != method.name
           || graphLens.getOriginalMethodSignature(method) != method
-          || doesContainPositions(encodedMethod)
-          || encodedMethod.isD8R8Synthesized()) {
+          || doesContainPositions(definition)
+          || definition.isD8R8Synthesized()) {
         methodsByRenamedName
             .computeIfAbsent(renamedName, key -> new ArrayList<>())
-            .add(encodedMethod);
+            .add(programMethod);
       }
     }
     return methodsByRenamedName;
@@ -1117,7 +1120,7 @@
     } else if (state.isOutline()) {
       positionBuilder = OutlinePosition.builder();
     } else {
-      positionBuilder = SourcePosition.builder();
+      positionBuilder = SourcePosition.builder().setFile(state.getCurrentFile());
     }
     return positionBuilder
         .setLine(state.getCurrentLine())
@@ -1206,10 +1209,10 @@
   }
 
   private static List<MappedPosition> optimizeCfCodePositions(
-      DexEncodedMethod method, PositionRemapper positionRemapper, AppView<?> appView) {
+      ProgramMethod method, PositionRemapper positionRemapper, AppView<?> appView) {
     List<MappedPosition> mappedPositions = new ArrayList<>();
     // Do the actual processing for each method.
-    CfCode oldCode = method.getCode().asCfCode();
+    CfCode oldCode = method.getDefinition().getCode().asCfCode();
     List<CfInstruction> oldInstructions = oldCode.getInstructions();
     List<CfInstruction> newInstructions = new ArrayList<>(oldInstructions.size());
     for (CfInstruction oldInstruction : oldInstructions) {
diff --git a/src/main/java/com/android/tools/r8/utils/ProgramClassCollection.java b/src/main/java/com/android/tools/r8/utils/ProgramClassCollection.java
index 8c3e592..a5bbfcb 100644
--- a/src/main/java/com/android/tools/r8/utils/ProgramClassCollection.java
+++ b/src/main/java/com/android/tools/r8/utils/ProgramClassCollection.java
@@ -79,8 +79,7 @@
 
   private static DexProgramClass mergeClasses(
       Reporter reporter, DexProgramClass a, DexProgramClass b) {
-    if (a.type.isLegacySynthesizedTypeAllowedDuplication()
-        || a.type.isSynthesizedTypeAllowedDuplication()) {
+    if (a.type.isLegacySynthesizedTypeAllowedDuplication()) {
       assert assertEqualClasses(a, b);
       return a;
     }
diff --git a/src/main/java/com/android/tools/r8/utils/classhierarchy/MethodOverridesCollector.java b/src/main/java/com/android/tools/r8/utils/classhierarchy/MethodOverridesCollector.java
index bd63c2a..7407b59 100644
--- a/src/main/java/com/android/tools/r8/utils/classhierarchy/MethodOverridesCollector.java
+++ b/src/main/java/com/android/tools/r8/utils/classhierarchy/MethodOverridesCollector.java
@@ -111,7 +111,7 @@
           SingleResolutionResult resolutionResult =
               appView
                   .appInfo()
-                  .resolveMethodOnClass(interfaceMethod, implementer)
+                  .resolveMethodOnClass(implementer, interfaceMethod)
                   .asSingleResolution();
           if (resolutionResult == null || !resolutionResult.getResolvedHolder().isProgramClass()) {
             continue;
diff --git a/src/main/java/com/android/tools/r8/utils/collections/LongLivedProgramMethodSetBuilder.java b/src/main/java/com/android/tools/r8/utils/collections/LongLivedProgramMethodSetBuilder.java
index ce4399b..735146a 100644
--- a/src/main/java/com/android/tools/r8/utils/collections/LongLivedProgramMethodSetBuilder.java
+++ b/src/main/java/com/android/tools/r8/utils/collections/LongLivedProgramMethodSetBuilder.java
@@ -13,7 +13,11 @@
 import com.android.tools.r8.graph.GraphLens;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.utils.DeterminismChecker.LineCallback;
 import com.android.tools.r8.utils.SetUtils;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
 import java.util.Set;
 import java.util.function.IntFunction;
 import java.util.function.Predicate;
@@ -184,4 +188,12 @@
             != null;
     return true;
   }
+
+  public void dump(LineCallback lineCallback) throws IOException {
+    List<DexMethod> sortedMethods = new ArrayList<>(methods);
+    sortedMethods.sort(DexMethod::compareTo);
+    for (DexMethod method : sortedMethods) {
+      lineCallback.onLine(method.toSourceString());
+    }
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/utils/collections/ProgramMemberMap.java b/src/main/java/com/android/tools/r8/utils/collections/ProgramMemberMap.java
index b14dd86..8292f14 100644
--- a/src/main/java/com/android/tools/r8/utils/collections/ProgramMemberMap.java
+++ b/src/main/java/com/android/tools/r8/utils/collections/ProgramMemberMap.java
@@ -71,8 +71,10 @@
     return backing.remove(wrap(member));
   }
 
-  public void removeIf(BiPredicate<K, V> predicate) {
-    backing.entrySet().removeIf(entry -> predicate.test(entry.getKey().get(), entry.getValue()));
+  public boolean removeIf(BiPredicate<K, V> predicate) {
+    return backing
+        .entrySet()
+        .removeIf(entry -> predicate.test(entry.getKey().get(), entry.getValue()));
   }
 
   abstract Wrapper<K> wrap(K member);
diff --git a/src/test/java/com/android/tools/r8/BackportedMethodListTest.java b/src/test/java/com/android/tools/r8/BackportedMethodListTest.java
index 6ed9d46..b2b78d1 100644
--- a/src/test/java/com/android/tools/r8/BackportedMethodListTest.java
+++ b/src/test/java/com/android/tools/r8/BackportedMethodListTest.java
@@ -3,6 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8;
 
+import static com.android.tools.r8.desugar.desugaredlibrary.DesugaredLibraryTestBase.isJDK11DesugaredLibrary;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
@@ -68,21 +69,27 @@
         backports.contains("java/lang/Short#toUnsignedLong(S)J"));
 
     // Java 9, 10 and 11 Optional methods which require Android N or library desugaring.
+    // The methods are not backported in desugared library JDK 11 (already present).
     assertEquals(
-        mode == Mode.LIBRARY_DESUGAR || apiLevel >= AndroidApiLevel.N.getLevel(),
+        (mode == Mode.LIBRARY_DESUGAR && !isJDK11DesugaredLibrary())
+            || apiLevel >= AndroidApiLevel.N.getLevel(),
         backports.contains(
             "java/util/Optional#or(Ljava/util/function/Supplier;)Ljava/util/Optional;"));
     assertEquals(
-        mode == Mode.LIBRARY_DESUGAR || apiLevel >= AndroidApiLevel.N.getLevel(),
+        (mode == Mode.LIBRARY_DESUGAR && !isJDK11DesugaredLibrary())
+            || apiLevel >= AndroidApiLevel.N.getLevel(),
         backports.contains("java/util/OptionalInt#orElseThrow()I"));
     assertEquals(
-        mode == Mode.LIBRARY_DESUGAR || apiLevel >= AndroidApiLevel.N.getLevel(),
+        (mode == Mode.LIBRARY_DESUGAR && !isJDK11DesugaredLibrary())
+            || apiLevel >= AndroidApiLevel.N.getLevel(),
         backports.contains("java/util/OptionalLong#isEmpty()Z"));
 
-    // Java 9, 10 and 11 methods added at API level S.
+    // Java 9, 10 and 11 method added at API level S.
     assertEquals(
         apiLevel < AndroidApiLevel.S.getLevel(),
         backports.contains("java/lang/StrictMath#multiplyExact(JI)J"));
+    // Java 9, 10 and 11 method added at API level S.
+    // The method is not backported in desugared library JDK 11 (already present).
     assertEquals(
         apiLevel < AndroidApiLevel.S.getLevel(),
         backports.contains("java/util/List#copyOf(Ljava/util/Collection;)Ljava/util/List;"));
@@ -97,7 +104,7 @@
     builder
         .addDesugaredLibraryConfiguration(
             StringResource.fromFile(ToolHelper.getDesugarLibJsonForTesting()))
-        .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.P.getLevel()));
+        .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.R.getLevel()));
   }
 
   @Test
diff --git a/src/test/java/com/android/tools/r8/JctfTestSpecifications.java b/src/test/java/com/android/tools/r8/JctfTestSpecifications.java
deleted file mode 100644
index 118f3b1..0000000
--- a/src/test/java/com/android/tools/r8/JctfTestSpecifications.java
+++ /dev/null
@@ -1,2027 +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 com.android.tools.r8;
-
-import static com.android.tools.r8.TestCondition.JAVA_RUNTIME;
-import static com.android.tools.r8.TestCondition.R8DEX_COMPILER;
-import static com.android.tools.r8.TestCondition.R8_COMPILER;
-import static com.android.tools.r8.TestCondition.and;
-import static com.android.tools.r8.TestCondition.any;
-import static com.android.tools.r8.TestCondition.anyDexVm;
-import static com.android.tools.r8.TestCondition.artRuntimesFrom;
-import static com.android.tools.r8.TestCondition.artRuntimesFromAndJava;
-import static com.android.tools.r8.TestCondition.artRuntimesUpTo;
-import static com.android.tools.r8.TestCondition.artRuntimesUpToAndJava;
-import static com.android.tools.r8.TestCondition.cf;
-import static com.android.tools.r8.TestCondition.match;
-import static com.android.tools.r8.TestCondition.runtimes;
-
-import com.android.tools.r8.R8RunArtTestsTest.CompilerUnderTest;
-import com.android.tools.r8.R8RunArtTestsTest.DexTool;
-import com.android.tools.r8.TestCondition.Runtime;
-import com.google.common.collect.ImmutableListMultimap;
-import com.google.common.collect.ImmutableSet;
-import com.google.common.collect.Multimap;
-import java.util.Collection;
-import java.util.Set;
-import java.util.function.BiFunction;
-
-public class JctfTestSpecifications {
-
-  public enum Outcome {
-    PASSES,
-    FAILS_WHEN_RUN,
-    TIMEOUTS_WHEN_RUN,
-    FLAKY_WHEN_RUN
-  }
-
-  public static final Multimap<String, TestCondition> failuresToTriage =
-      new ImmutableListMultimap.Builder<String, TestCondition>()
-          .put("math.BigInteger.nextProbablePrime.BigInteger_nextProbablePrime_A02", anyDexVm())
-          .put("math.BigInteger.ConstructorLjava_lang_String.BigInteger_Constructor_A02", any())
-          .put(
-              "lang.StringBuffer.insertILjava_lang_Object.StringBuffer_insert_A01",
-              match(
-                  runtimes(
-                      Runtime.ART_DEFAULT, Runtime.ART_V9_0_0, Runtime.ART_V8_1_0, Runtime.JAVA)))
-          .put("lang.StringBuffer.serialization.StringBuffer_serialization_A01", anyDexVm())
-          .put(
-              "lang.CloneNotSupportedException.serialization.CloneNotSupportedException_serialization_A01",
-              anyDexVm())
-          .put(
-              "lang.NumberFormatException.serialization.NumberFormatException_serialization_A01",
-              anyDexVm())
-          .put(
-              "lang.StrictMath.roundF.StrictMath_round_A01",
-              match(
-                  runtimes(
-                      Runtime.ART_DEFAULT,
-                      Runtime.ART_V9_0_0,
-                      Runtime.ART_V8_1_0,
-                      Runtime.ART_V7_0_0,
-                      Runtime.JAVA)))
-          .put(
-              "lang.StrictMath.roundD.StrictMath_round_A01",
-              match(
-                  runtimes(
-                      Runtime.ART_DEFAULT,
-                      Runtime.ART_V9_0_0,
-                      Runtime.ART_V8_1_0,
-                      Runtime.ART_V7_0_0,
-                      Runtime.JAVA)))
-          .put("lang.StrictMath.atan2DD.StrictMath_atan2_A01", any())
-          .put("lang.Thread.stop.Thread_stop_A05", any())
-          .put("lang.Thread.resume.Thread_resume_A02", anyDexVm())
-          .put("lang.Thread.suspend.Thread_suspend_A02", anyDexVm())
-          .put("lang.Thread.stop.Thread_stop_A03", any())
-          .put("lang.Thread.interrupt.Thread_interrupt_A03", any())
-          .put("lang.Thread.stop.Thread_stop_A04", any())
-          .put(
-              "lang.Thread.ConstructorLjava_lang_ThreadGroupLjava_lang_RunnableLjava_lang_StringJ.Thread_Constructor_A01",
-              match(
-                  runtimes(
-                      Runtime.ART_DEFAULT,
-                      Runtime.ART_V9_0_0,
-                      Runtime.ART_V8_1_0,
-                      Runtime.ART_V7_0_0,
-                      Runtime.ART_V6_0_1)))
-          .put(
-              "lang.Thread.getUncaughtExceptionHandler.Thread_getUncaughtExceptionHandler_A01",
-              anyDexVm())
-          .put("lang.Thread.getStackTrace.Thread_getStackTrace_A02", anyDexVm())
-          .put("lang.Thread.enumerate_Ljava_lang_Thread.Thread_enumerate_A02", anyDexVm())
-          .put("lang.Thread.countStackFrames.Thread_countStackFrames_A01", any())
-          .put(
-              "lang.Thread.getAllStackTraces.Thread_getAllStackTraces_A01",
-              match(runtimes(Runtime.ART_V7_0_0)))
-          .put("lang.Thread.destroy.Thread_destroy_A01", match(artRuntimesFrom(Runtime.ART_V4_4_4)))
-          .put("lang.Thread.isAlive.Thread_isAlive_A01", anyDexVm())
-          .put("lang.Thread.stopLjava_lang_Throwable.Thread_stop_A04", any())
-          .put("lang.Thread.stopLjava_lang_Throwable.Thread_stop_A03", any())
-          .put("lang.Thread.stopLjava_lang_Throwable.Thread_stop_A05", any())
-          .put("lang.Thread.getPriority.Thread_getPriority_A01", anyDexVm())
-          .put(
-              "lang.Thread.getContextClassLoader.Thread_getContextClassLoader_A03",
-              match(runtimes(Runtime.ART_V7_0_0)))
-          .put("lang.OutOfMemoryError.serialization.OutOfMemoryError_serialization_A01", anyDexVm())
-          .put(
-              "lang.RuntimePermission.ConstructorLjava_lang_StringLjava_lang_String.RuntimePermission_Constructor_A01",
-              anyDexVm())
-          .put(
-              "lang.RuntimePermission.serialization.RuntimePermission_serialization_A01",
-              anyDexVm())
-          .put(
-              "lang.RuntimePermission.ConstructorLjava_lang_StringLjava_lang_String.RuntimePermission_Constructor_A02",
-              anyDexVm())
-          .put(
-              "lang.RuntimePermission.ConstructorLjava_lang_StringLjava_lang_String.RuntimePermission_Constructor_A03",
-              anyDexVm())
-          .put("lang.RuntimePermission.Class.RuntimePermission_class_A17", any())
-          .put(
-              "lang.RuntimePermission.ConstructorLjava_lang_String.RuntimePermission_Constructor_A02",
-              anyDexVm())
-          .put(
-              "lang.RuntimePermission.ConstructorLjava_lang_String.RuntimePermission_Constructor_A03",
-              anyDexVm())
-          .put(
-              "lang.RuntimePermission.ConstructorLjava_lang_String.RuntimePermission_Constructor_A01",
-              anyDexVm())
-          .put("lang.RuntimePermission.Class.RuntimePermission_class_A26", any())
-          .put("lang.RuntimePermission.Class.RuntimePermission_class_A04", any())
-          .put("lang.RuntimePermission.Class.RuntimePermission_class_A03", any())
-          .put("lang.RuntimePermission.Class.RuntimePermission_class_A25", any())
-          .put("lang.RuntimePermission.Class.RuntimePermission_class_A06", any())
-          .put("lang.RuntimePermission.Class.RuntimePermission_class_A21", any())
-          .put("lang.RuntimePermission.Class.RuntimePermission_class_A22", any())
-          .put("lang.RuntimePermission.Class.RuntimePermission_class_A11", any())
-          .put("lang.RuntimePermission.Class.RuntimePermission_class_A08", any())
-          .put("lang.RuntimePermission.Class.RuntimePermission_class_A16", any())
-          .put("lang.RuntimePermission.Class.RuntimePermission_class_A12", any())
-          .put("lang.RuntimePermission.Class.RuntimePermission_class_A24", any())
-          .put("lang.RuntimePermission.Class.RuntimePermission_class_A23", any())
-          .put("lang.RuntimePermission.Class.RuntimePermission_class_A18", any())
-          .put("lang.RuntimePermission.Class.RuntimePermission_class_A19", any())
-          .put("lang.RuntimePermission.Class.RuntimePermission_class_A07", any())
-          .put("lang.RuntimePermission.Class.RuntimePermission_class_A20", any())
-          .put("lang.RuntimePermission.Class.RuntimePermission_class_A15", any())
-          .put("lang.RuntimePermission.Class.RuntimePermission_class_A05", any())
-          .put("lang.RuntimePermission.Class.RuntimePermission_class_A09", any())
-          .put("lang.RuntimePermission.Class.RuntimePermission_class_A10", any())
-          .put("lang.RuntimePermission.Class.RuntimePermission_class_A01", any())
-          .put(
-              "lang.ClassLoader.defineClassLjava_lang_String_BII.ClassLoader_defineClass_A06",
-              anyDexVm())
-          .put("lang.RuntimePermission.Class.RuntimePermission_class_A14", any())
-          .put(
-              "lang.ClassLoader.defineClassLjava_lang_String_BII.ClassLoader_defineClass_A05",
-              anyDexVm())
-          .put(
-              "lang.ClassLoader.defineClassLjava_lang_String_BII.ClassLoader_defineClass_A02",
-              anyDexVm())
-          .put(
-              "lang.ClassLoader.defineClassLjava_lang_String_BII.ClassLoader_defineClass_A04",
-              anyDexVm())
-          .put(
-              "lang.ClassLoader.defineClassLjava_lang_String_BII.ClassLoader_defineClass_A03",
-              anyDexVm())
-          .put(
-              "lang.ClassLoader.defineClassLjava_lang_String_BII.ClassLoader_defineClass_A01",
-              anyDexVm())
-          .put(
-              "lang.ClassLoader.defineClassLjava_lang_String_BII.ClassLoader_defineClass_A07",
-              anyDexVm())
-          .put(
-              "lang.ClassLoader.setPackageAssertionStatusLjava_lang_StringZ.ClassLoader_setPackageAssertionStatus_A02",
-              anyDexVm())
-          .put(
-              "lang.ClassLoader.setPackageAssertionStatusLjava_lang_StringZ.ClassLoader_setPackageAssertionStatus_A01",
-              anyDexVm())
-          .put(
-              "lang.ClassLoader.setPackageAssertionStatusLjava_lang_StringZ.ClassLoader_setPackageAssertionStatus_A03",
-              anyDexVm())
-          .put("lang.ClassLoader.loadClassLjava_lang_StringZ.ClassLoader_loadClass_A03", anyDexVm())
-          .put("lang.ClassLoader.loadClassLjava_lang_StringZ.ClassLoader_loadClass_A01", anyDexVm())
-          .put("lang.ClassLoader.loadClassLjava_lang_StringZ.ClassLoader_loadClass_A04", anyDexVm())
-          .put(
-              "lang.ClassLoader.definePackageLjava_lang_String6Ljava_net_URL.ClassLoader_definePackage_A02",
-              anyDexVm())
-          .put(
-              "lang.ClassLoader.definePackageLjava_lang_String6Ljava_net_URL.ClassLoader_definePackage_A01",
-              anyDexVm())
-          .put(
-              "lang.ClassLoader.definePackageLjava_lang_String6Ljava_net_URL.ClassLoader_definePackage_A03",
-              anyDexVm())
-          .put(
-              "lang.ClassLoader.defineClassLjava_lang_StringLjava_nio_ByteBufferLjava_security_ProtectionDomain.ClassLoader_defineClass_A05",
-              anyDexVm())
-          .put(
-              "lang.ClassLoader.getResourceLjava_lang_String.ClassLoader_getResource_A01",
-              anyDexVm())
-          .put(
-              "lang.ClassLoader.defineClassLjava_lang_StringLjava_nio_ByteBufferLjava_security_ProtectionDomain.ClassLoader_defineClass_A01",
-              anyDexVm())
-          .put(
-              "lang.ClassLoader.defineClassLjava_lang_StringLjava_nio_ByteBufferLjava_security_ProtectionDomain.ClassLoader_defineClass_A02",
-              anyDexVm())
-          .put(
-              "lang.ClassLoader.defineClassLjava_lang_StringLjava_nio_ByteBufferLjava_security_ProtectionDomain.ClassLoader_defineClass_A06",
-              anyDexVm())
-          .put(
-              "lang.ClassLoader.defineClassLjava_lang_StringLjava_nio_ByteBufferLjava_security_ProtectionDomain.ClassLoader_defineClass_A03",
-              anyDexVm())
-          .put(
-              "lang.ClassLoader.defineClassLjava_lang_StringLjava_nio_ByteBufferLjava_security_ProtectionDomain.ClassLoader_defineClass_A07",
-              match(
-                  runtimes(
-                      Runtime.ART_DEFAULT,
-                      Runtime.ART_V9_0_0,
-                      Runtime.ART_V8_1_0,
-                      Runtime.ART_V7_0_0)))
-          .put(
-              "lang.ClassLoader.defineClassLjava_lang_StringLjava_nio_ByteBufferLjava_security_ProtectionDomain.ClassLoader_defineClass_A04",
-              anyDexVm())
-          .put(
-              "lang.ClassLoader.setSignersLjava_lang_Class_Ljava_lang_Object.ClassLoader_setSigners_A01",
-              anyDexVm())
-          .put(
-              "lang.ClassLoader.clearAssertionStatus.ClassLoader_clearAssertionStatus_A01",
-              anyDexVm())
-          .put("lang.ClassLoader.Constructor.ClassLoader_Constructor_A02", anyDexVm())
-          .put(
-              "lang.ClassLoader.getSystemResourceLjava_lang_String.ClassLoader_getSystemResource_A01",
-              anyDexVm())
-          .put(
-              "lang.ClassLoader.getResourcesLjava_lang_String.ClassLoader_getResources_A01",
-              anyDexVm())
-          .put(
-              "lang.ClassLoader.resolveClassLjava_lang_Class.ClassLoader_resolveClass_A02",
-              anyDexVm())
-          .put(
-              "lang.ClassLoader.getResourceAsStreamLjava_lang_String.ClassLoader_getResourceAsStream_A01",
-              anyDexVm())
-          .put(
-              "lang.ClassLoader.findLoadedClassLjava_lang_String.ClassLoader_findLoadedClass_A01",
-              anyDexVm())
-          .put(
-              "lang.ClassLoader.defineClassLjava_lang_String_BIILjava_security_ProtectionDomain.ClassLoader_defineClass_A02",
-              anyDexVm())
-          .put(
-              "lang.ClassLoader.resolveClassLjava_lang_Class.ClassLoader_resolveClass_A01",
-              anyDexVm())
-          .put(
-              "lang.ClassLoader.setDefaultAssertionStatusZ.ClassLoader_setDefaultAssertionStatus_A01",
-              anyDexVm())
-          .put(
-              "lang.ClassLoader.defineClassLjava_lang_String_BIILjava_security_ProtectionDomain.ClassLoader_defineClass_A05",
-              anyDexVm())
-          .put(
-              "lang.ClassLoader.defineClassLjava_lang_String_BIILjava_security_ProtectionDomain.ClassLoader_defineClass_A01",
-              anyDexVm())
-          .put(
-              "lang.ClassLoader.defineClassLjava_lang_String_BIILjava_security_ProtectionDomain.ClassLoader_defineClass_A06",
-              anyDexVm())
-          .put(
-              "lang.ClassLoader.defineClassLjava_lang_String_BIILjava_security_ProtectionDomain.ClassLoader_defineClass_A08",
-              anyDexVm())
-          .put(
-              "lang.ClassLoader.defineClassLjava_lang_String_BIILjava_security_ProtectionDomain.ClassLoader_defineClass_A03",
-              anyDexVm())
-          .put(
-              "lang.ClassLoader.getSystemResourcesLjava_lang_String.ClassLoader_getSystemResources_A01",
-              anyDexVm())
-          .put(
-              "lang.ClassLoader.defineClassLjava_lang_String_BIILjava_security_ProtectionDomain.ClassLoader_defineClass_A07",
-              anyDexVm())
-          .put(
-              "lang.ClassLoader.defineClassLjava_lang_String_BIILjava_security_ProtectionDomain.ClassLoader_defineClass_A09",
-              anyDexVm())
-          .put(
-              "lang.ClassLoader.defineClassLjava_lang_String_BIILjava_security_ProtectionDomain.ClassLoader_defineClass_A04",
-              anyDexVm())
-          .put("lang.ClassLoader.defineClass_BII.ClassLoader_defineClass_A02", anyDexVm())
-          .put(
-              "lang.ClassLoader.getSystemResourceAsStreamLjava_lang_String.ClassLoader_getSystemResourceAsStream_A01",
-              anyDexVm())
-          .put("lang.ClassLoader.getPackages.ClassLoader_getPackages_A01", anyDexVm())
-          .put(
-              "lang.ClassLoader.setClassAssertionStatusLjava_lang_StringZ.ClassLoader_setClassAssertionStatus_A01",
-              any())
-          .put("lang.ClassLoader.defineClass_BII.ClassLoader_defineClass_A03", anyDexVm())
-          .put("lang.ClassLoader.defineClass_BII.ClassLoader_defineClass_A01", anyDexVm())
-          .put("lang.ClassLoader.defineClass_BII.ClassLoader_defineClass_A04", anyDexVm())
-          .put("lang.ClassLoader.getParent.ClassLoader_getParent_A01", anyDexVm())
-          .put(
-              "lang.ClassLoader.setClassAssertionStatusLjava_lang_StringZ.ClassLoader_setClassAssertionStatus_A04",
-              any())
-          .put(
-              "lang.ClassLoader.setClassAssertionStatusLjava_lang_StringZ.ClassLoader_setClassAssertionStatus_A02",
-              anyDexVm())
-          .put("lang.ClassLoader.getParent.ClassLoader_getParent_A02", anyDexVm())
-          .put(
-              "lang.ClassLoader.getSystemClassLoader.ClassLoader_getSystemClassLoader_A02",
-              anyDexVm())
-          .put(
-              "lang.ClassLoader.ConstructorLjava_lang_ClassLoader.ClassLoader_Constructor_A02",
-              anyDexVm())
-          .put(
-              "lang.ClassLoader.findSystemClassLjava_lang_String.ClassLoader_findSystemClass_A04",
-              anyDexVm())
-          .put(
-              "lang.ClassLoader.getPackageLjava_lang_String.ClassLoader_getPackage_A01", anyDexVm())
-          .put(
-              "lang.NoClassDefFoundError.serialization.NoClassDefFoundError_serialization_A01",
-              anyDexVm())
-          .put(
-              "lang.TypeNotPresentException.serialization.TypeNotPresentException_serialization_A01",
-              anyDexVm())
-          .put(
-              "lang.IndexOutOfBoundsException.serialization.IndexOutOfBoundsException_serialization_A01",
-              anyDexVm())
-          .put("lang.Enum.serialization.Enum_serialization_A01", anyDexVm())
-          .put("lang.Enum.ConstructorLjava_lang_StringI.Enum_Constructor_A01", anyDexVm())
-          .put("lang.InternalError.serialization.InternalError_serialization_A01", anyDexVm())
-          .put("lang.Error.serialization.Error_serialization_A01", anyDexVm())
-          .put("lang.Runtime.loadLjava_lang_String.Runtime_load_A02", any())
-          .put("lang.Runtime.loadLjava_lang_String.Runtime_load_A05", any())
-          .put("lang.Runtime.loadLjava_lang_String.Runtime_load_A03", any())
-          .put("lang.Runtime.loadLjava_lang_String.Runtime_load_A04", any())
-          .put("lang.Runtime.exec_Ljava_lang_String.Runtime_exec_A02", anyDexVm())
-          .put("lang.Runtime.exec_Ljava_lang_String.Runtime_exec_A03", anyDexVm())
-          .put("lang.Runtime.exec_Ljava_lang_String.Runtime_exec_A01", anyDexVm())
-          .put("lang.Runtime.loadLibraryLjava_lang_String.Runtime_loadLibrary_A04", any())
-          .put("lang.Runtime.loadLibraryLjava_lang_String.Runtime_loadLibrary_A05", any())
-          .put("lang.Runtime.loadLibraryLjava_lang_String.Runtime_loadLibrary_A03", any())
-          .put("lang.Runtime.execLjava_lang_String.Runtime_exec_A02", anyDexVm())
-          .put("lang.Runtime.execLjava_lang_String.Runtime_exec_A03", anyDexVm())
-          .put("lang.Runtime.loadLibraryLjava_lang_String.Runtime_loadLibrary_A02", any())
-          .put("lang.Runtime.traceMethodCallsZ.Runtime_traceMethodCalls_A01", anyDexVm())
-          .put(
-              "lang.Runtime.addShutdownHookLjava_lang_Thread.Runtime_addShutdownHook_A01",
-              anyDexVm())
-          .put(
-              "lang.Runtime.addShutdownHookLjava_lang_Thread.Runtime_addShutdownHook_A08",
-              anyDexVm())
-          .put("lang.Runtime.execLjava_lang_String.Runtime_exec_A01", anyDexVm())
-          .put(
-              "lang.Runtime.addShutdownHookLjava_lang_Thread.Runtime_addShutdownHook_A03",
-              anyDexVm())
-          .put(
-              "lang.Runtime.addShutdownHookLjava_lang_Thread.Runtime_addShutdownHook_A07",
-              anyDexVm())
-          .put(
-              "lang.Runtime.addShutdownHookLjava_lang_Thread.Runtime_addShutdownHook_A05",
-              anyDexVm())
-          .put(
-              "lang.Runtime.addShutdownHookLjava_lang_Thread.Runtime_addShutdownHook_A06",
-              anyDexVm())
-          .put("lang.Runtime.execLjava_lang_String_Ljava_lang_String.Runtime_exec_A03", anyDexVm())
-          .put("lang.Runtime.execLjava_lang_String_Ljava_lang_String.Runtime_exec_A02", anyDexVm())
-          .put("lang.Runtime.execLjava_lang_String_Ljava_lang_String.Runtime_exec_A01", anyDexVm())
-          .put(
-              "lang.Runtime.removeShutdownHookLjava_lang_Thread.Runtime_removeShutdownHook_A02",
-              anyDexVm())
-          .put("lang.Runtime.exec_Ljava_lang_String_Ljava_lang_String.Runtime_exec_A01", anyDexVm())
-          .put(
-              "lang.Runtime.removeShutdownHookLjava_lang_Thread.Runtime_removeShutdownHook_A01",
-              anyDexVm())
-          .put("lang.Runtime.exec_Ljava_lang_String_Ljava_lang_String.Runtime_exec_A02", anyDexVm())
-          .put(
-              "lang.Runtime.removeShutdownHookLjava_lang_Thread.Runtime_removeShutdownHook_A03",
-              anyDexVm())
-          .put(
-              "lang.Runtime.exec_Ljava_lang_String_Ljava_lang_StringLjava_io_File.Runtime_exec_A01",
-              anyDexVm())
-          .put("lang.Runtime.exec_Ljava_lang_String_Ljava_lang_String.Runtime_exec_A03", anyDexVm())
-          .put(
-              "lang.Runtime.exec_Ljava_lang_String_Ljava_lang_StringLjava_io_File.Runtime_exec_A02",
-              anyDexVm())
-          .put("lang.Runtime.haltI.Runtime_halt_A02", any())
-          .put(
-              "lang.Runtime.exec_Ljava_lang_String_Ljava_lang_StringLjava_io_File.Runtime_exec_A03",
-              anyDexVm())
-          .put("lang.Runtime.haltI.Runtime_halt_A03", any())
-          .put("lang.Runtime.runFinalizersOnExitZ.Runtime_runFinalizersOnExit_A01", anyDexVm())
-          .put("lang.Runtime.haltI.Runtime_halt_A01", any())
-          .put("lang.Runtime.runFinalizersOnExitZ.Runtime_runFinalizersOnExit_A03", anyDexVm())
-          .put(
-              "lang.Runtime.execLjava_lang_String_Ljava_lang_StringLjava_io_File.Runtime_exec_A03",
-              anyDexVm())
-          .put(
-              "lang.Runtime.execLjava_lang_String_Ljava_lang_StringLjava_io_File.Runtime_exec_A01",
-              anyDexVm())
-          .put("lang.Runtime.runFinalizersOnExitZ.Runtime_runFinalizersOnExit_A02", anyDexVm())
-          .put("lang.Runtime.exitI.Runtime_exit_A03", any())
-          .put(
-              "lang.Runtime.execLjava_lang_String_Ljava_lang_StringLjava_io_File.Runtime_exec_A02",
-              anyDexVm())
-          .put("lang.Runtime.exitI.Runtime_exit_A04", any())
-          .put(
-              "lang.NoSuchMethodException.serialization.NoSuchMethodException_serialization_A01",
-              anyDexVm())
-          .put("lang.Runtime.exitI.Runtime_exit_A01", any())
-          .put("lang.Runtime.exitI.Runtime_exit_A02", any())
-          .put(
-              "lang.InstantiationException.serialization.InstantiationException_serialization_A01",
-              anyDexVm())
-          .put("lang.Exception.serialization.Exception_serialization_A01", anyDexVm())
-          .put(
-              "lang.StackOverflowError.serialization.StackOverflowError_serialization_A01",
-              anyDexVm())
-          .put(
-              "lang.NoSuchFieldException.serialization.NoSuchFieldException_serialization_A01",
-              anyDexVm())
-          .put(
-              "lang.NegativeArraySizeException.serialization.NegativeArraySizeException_serialization_A01",
-              anyDexVm())
-          .put(
-              "lang.ArrayIndexOutOfBoundsException.serialization.ArrayIndexOutOfBoundsException_serialization_A01",
-              anyDexVm())
-          .put("lang.VerifyError.serialization.VerifyError_serialization_A01", anyDexVm())
-          .put(
-              "lang.IllegalArgumentException.serialization.IllegalArgumentException_serialization_A01",
-              anyDexVm())
-          .put(
-              "lang.IllegalStateException.serialization.IllegalStateException_serialization_A01",
-              anyDexVm())
-          .put("lang.Double.serialization.Double_serialization_A01", anyDexVm())
-          .put("lang.Double.toStringD.Double_toString_A05", any())
-          .put(
-              "lang.ArithmeticException.serialization.ArithmeticException_serialization_A01",
-              anyDexVm())
-          .put(
-              "lang.ExceptionInInitializerError.serialization.ExceptionInInitializerError_serialization_A01",
-              anyDexVm())
-          .put("lang.ThreadLocal.Class.ThreadLocal_class_A01", anyDexVm())
-          .put("lang.Byte.serialization.Byte_serialization_A01", anyDexVm())
-          .put(
-              "lang.Byte.parseByteLjava_lang_StringI.Byte_parseByte_A02",
-              match(
-                  runtimes(
-                      Runtime.ART_DEFAULT,
-                      Runtime.ART_V9_0_0,
-                      Runtime.ART_V8_1_0,
-                      Runtime.ART_V7_0_0,
-                      Runtime.ART_V6_0_1,
-                      Runtime.ART_V5_1_1,
-                      Runtime.JAVA)))
-          .put(
-              "lang.Byte.valueOfLjava_lang_StringI.Byte_valueOf_A02",
-              match(
-                  runtimes(
-                      Runtime.ART_DEFAULT,
-                      Runtime.ART_V9_0_0,
-                      Runtime.ART_V8_1_0,
-                      Runtime.ART_V7_0_0,
-                      Runtime.ART_V6_0_1,
-                      Runtime.ART_V5_1_1,
-                      Runtime.JAVA)))
-          .put(
-              "lang.Byte.valueOfLjava_lang_String.Byte_ValueOf_A02",
-              match(
-                  runtimes(
-                      Runtime.ART_DEFAULT,
-                      Runtime.ART_V9_0_0,
-                      Runtime.ART_V8_1_0,
-                      Runtime.ART_V7_0_0,
-                      Runtime.ART_V6_0_1,
-                      Runtime.ART_V5_1_1,
-                      Runtime.JAVA)))
-          .put(
-              "lang.Byte.decodeLjava_lang_String.Byte_decode_A04",
-              match(
-                  runtimes(
-                      Runtime.ART_DEFAULT,
-                      Runtime.ART_V9_0_0,
-                      Runtime.ART_V8_1_0,
-                      Runtime.ART_V7_0_0,
-                      Runtime.ART_V6_0_1,
-                      Runtime.ART_V5_1_1,
-                      Runtime.JAVA)))
-          .put("lang.LinkageError.serialization.LinkageError_serialization_A01", anyDexVm())
-          .put(
-              "lang.ClassCastException.serialization.ClassCastException_serialization_A01",
-              anyDexVm())
-          .put(
-              "lang.Byte.ConstructorLjava_lang_String.Byte_Constructor_A02",
-              match(
-                  runtimes(
-                      Runtime.ART_DEFAULT,
-                      Runtime.ART_V9_0_0,
-                      Runtime.ART_V8_1_0,
-                      Runtime.ART_V7_0_0,
-                      Runtime.ART_V6_0_1,
-                      Runtime.ART_V5_1_1,
-                      Runtime.JAVA)))
-          .put(
-              "lang.Byte.parseByteLjava_lang_String.Byte_parseByte_A02",
-              match(
-                  runtimes(
-                      Runtime.ART_DEFAULT,
-                      Runtime.ART_V9_0_0,
-                      Runtime.ART_V8_1_0,
-                      Runtime.ART_V7_0_0,
-                      Runtime.ART_V6_0_1,
-                      Runtime.ART_V5_1_1,
-                      Runtime.JAVA)))
-          .put("lang.NoSuchFieldError.serialization.NoSuchFieldError_serialization_A01", anyDexVm())
-          .put(
-              "lang.UnsupportedOperationException.serialization.UnsupportedOperationException_serialization_A01",
-              anyDexVm())
-          .put(
-              "lang.NoSuchMethodError.serialization.NoSuchMethodError_serialization_A01",
-              anyDexVm())
-          .put(
-              "lang.IllegalMonitorStateException.serialization.IllegalMonitorStateException_serialization_A01",
-              anyDexVm())
-          .put(
-              "lang.StringIndexOutOfBoundsException.serialization.StringIndexOutOfBoundsException_serialization_A01",
-              anyDexVm())
-          .put(
-              "lang.SecurityException.serialization.SecurityException_serialization_A01",
-              anyDexVm())
-          .put(
-              "lang.IllegalAccessError.serialization.IllegalAccessError_serialization_A01",
-              anyDexVm())
-          .put(
-              "lang.ArrayStoreException.serialization.ArrayStoreException_serialization_A01",
-              anyDexVm())
-          .put("lang.UnknownError.serialization.UnknownError_serialization_A01", anyDexVm())
-          .put("lang.Boolean.serialization.Boolean_serialization_A01", anyDexVm())
-          .put(
-              "lang.Integer.valueOfLjava_lang_StringI.Integer_valueOf_A02",
-              match(
-                  runtimes(
-                      Runtime.ART_DEFAULT,
-                      Runtime.ART_V9_0_0,
-                      Runtime.ART_V8_1_0,
-                      Runtime.ART_V7_0_0,
-                      Runtime.ART_V6_0_1,
-                      Runtime.ART_V5_1_1,
-                      Runtime.JAVA)))
-          .put("lang.Integer.serialization.Integer_serialization_A01", anyDexVm())
-          .put(
-              "lang.Integer.parseIntLjava_lang_String.Integer_parseInt_A02",
-              match(
-                  runtimes(
-                      Runtime.ART_DEFAULT,
-                      Runtime.ART_V9_0_0,
-                      Runtime.ART_V8_1_0,
-                      Runtime.ART_V7_0_0,
-                      Runtime.ART_V6_0_1,
-                      Runtime.ART_V5_1_1,
-                      Runtime.JAVA)))
-          .put(
-              "lang.Integer.getIntegerLjava_lang_StringI.Integer_getInteger_A02",
-              match(
-                  runtimes(
-                      Runtime.ART_DEFAULT,
-                      Runtime.ART_V9_0_0,
-                      Runtime.ART_V8_1_0,
-                      Runtime.ART_V7_0_0,
-                      Runtime.ART_V6_0_1,
-                      Runtime.ART_V5_1_1,
-                      Runtime.JAVA)))
-          .put(
-              "lang.Integer.valueOfLjava_lang_String.Integer_valueOf_A02",
-              match(
-                  runtimes(
-                      Runtime.ART_DEFAULT,
-                      Runtime.ART_V9_0_0,
-                      Runtime.ART_V8_1_0,
-                      Runtime.ART_V7_0_0,
-                      Runtime.ART_V6_0_1,
-                      Runtime.ART_V5_1_1,
-                      Runtime.JAVA)))
-          .put(
-              "lang.Integer.decodeLjava_lang_String.Integer_decode_A04",
-              match(
-                  runtimes(
-                      Runtime.ART_DEFAULT,
-                      Runtime.ART_V9_0_0,
-                      Runtime.ART_V8_1_0,
-                      Runtime.ART_V7_0_0,
-                      Runtime.ART_V6_0_1,
-                      Runtime.ART_V5_1_1,
-                      Runtime.JAVA)))
-          .put(
-              "lang.Integer.parseIntLjava_lang_StringI.Integer_parseInt_A02",
-              match(
-                  runtimes(
-                      Runtime.ART_DEFAULT,
-                      Runtime.ART_V9_0_0,
-                      Runtime.ART_V8_1_0,
-                      Runtime.ART_V7_0_0,
-                      Runtime.ART_V6_0_1,
-                      Runtime.ART_V5_1_1,
-                      Runtime.JAVA)))
-          .put(
-              "lang.Integer.getIntegerLjava_lang_StringLjava_lang_Integer.Integer_getInteger_A02",
-              match(
-                  runtimes(
-                      Runtime.ART_DEFAULT,
-                      Runtime.ART_V9_0_0,
-                      Runtime.ART_V8_1_0,
-                      Runtime.ART_V7_0_0,
-                      Runtime.ART_V6_0_1,
-                      Runtime.ART_V5_1_1,
-                      Runtime.JAVA)))
-          .put(
-              "lang.Integer.ConstructorLjava_lang_String.Integer_Constructor_A02",
-              match(
-                  runtimes(
-                      Runtime.ART_DEFAULT,
-                      Runtime.ART_V9_0_0,
-                      Runtime.ART_V8_1_0,
-                      Runtime.ART_V7_0_0,
-                      Runtime.ART_V6_0_1,
-                      Runtime.ART_V5_1_1,
-                      Runtime.JAVA)))
-          .put(
-              "lang.Integer.getIntegerLjava_lang_String.Integer_getInteger_A02",
-              match(
-                  runtimes(
-                      Runtime.ART_DEFAULT,
-                      Runtime.ART_V9_0_0,
-                      Runtime.ART_V8_1_0,
-                      Runtime.ART_V7_0_0,
-                      Runtime.ART_V6_0_1,
-                      Runtime.ART_V5_1_1,
-                      Runtime.JAVA)))
-          .put(
-              "lang.ref.PhantomReference.isEnqueued.PhantomReference_isEnqueued_A01",
-              match(
-                  runtimes(
-                      Runtime.ART_DEFAULT,
-                      Runtime.ART_V7_0_0,
-                      Runtime.ART_V6_0_1,
-                      Runtime.ART_V5_1_1)))
-          .put(
-              "lang.ref.SoftReference.isEnqueued.SoftReference_isEnqueued_A01",
-              match(
-                  runtimes(
-                      Runtime.ART_DEFAULT,
-                      Runtime.ART_V7_0_0,
-                      Runtime.ART_V6_0_1,
-                      Runtime.ART_V5_1_1)))
-          .put("lang.ref.SoftReference.get.SoftReference_get_A01", anyDexVm())
-          .put("lang.ref.SoftReference.clear.SoftReference_clear_A01", cf())
-          .put(
-              "lang.ref.ReferenceQueue.poll.ReferenceQueue_poll_A01",
-              match(
-                  runtimes(
-                      Runtime.ART_DEFAULT,
-                      Runtime.ART_V9_0_0,
-                      Runtime.ART_V8_1_0,
-                      Runtime.ART_V7_0_0,
-                      Runtime.ART_V6_0_1,
-                      Runtime.ART_V5_1_1)))
-          .put(
-              "lang.StackTraceElement.serialization.StackTraceElement_serialization_A01",
-              anyDexVm())
-          .put("lang.ref.WeakReference.get.WeakReference_get_A01", anyDexVm())
-          .put(
-              "lang.StackTraceElement.toString.StackTraceElement_toString_A01",
-              match(runtimes(Runtime.ART_DEFAULT, Runtime.ART_V9_0_0, Runtime.ART_V8_1_0)))
-          .put(
-              "lang.NullPointerException.serialization.NullPointerException_serialization_A01",
-              anyDexVm())
-          .put(
-              "lang.VirtualMachineError.serialization.VirtualMachineError_serialization_A01",
-              anyDexVm())
-          .put(
-              "lang.ClassCircularityError.serialization.ClassCircularityError_serialization_A01",
-              anyDexVm())
-          .put("lang.ThreadDeath.serialization.ThreadDeath_serialization_A01", anyDexVm())
-          .put(
-              "lang.InstantiationError.serialization.InstantiationError_serialization_A01",
-              anyDexVm())
-          .put(
-              "lang.IllegalThreadStateException.serialization.IllegalThreadStateException_serialization_A01",
-              anyDexVm())
-          .put("lang.ProcessBuilder.environment.ProcessBuilder_environment_A05", anyDexVm())
-          .put("lang.ProcessBuilder.environment.ProcessBuilder_environment_A06", anyDexVm())
-          .put("lang.ProcessBuilder.start.ProcessBuilder_start_A05", anyDexVm())
-          .put("lang.ProcessBuilder.start.ProcessBuilder_start_A06", anyDexVm())
-          .put("lang.ClassFormatError.serialization.ClassFormatError_serialization_A01", anyDexVm())
-          .put("lang.Math.cbrtD.Math_cbrt_A01", match(artRuntimesFrom(Runtime.ART_V6_0_1)))
-          .put("lang.Math.powDD.Math_pow_A08", anyDexVm())
-          .put(
-              "lang.IncompatibleClassChangeError.serialization.IncompatibleClassChangeError_serialization_A01",
-              anyDexVm())
-          .put("lang.Float.serialization.Float_serialization_A01", anyDexVm())
-          .put("lang.Float.toStringF.Float_toString_A02", any())
-          .put(
-              "lang.Short.valueOfLjava_lang_StringI.Short_valueOf_A02",
-              match(artRuntimesFromAndJava(Runtime.ART_V5_1_1)))
-          .put(
-              "lang.Short.valueOfLjava_lang_String.Short_valueOf_A02",
-              match(artRuntimesFromAndJava(Runtime.ART_V5_1_1)))
-          .put("lang.Short.serialization.Short_serialization_A01", anyDexVm())
-          .put(
-              "lang.Short.parseShortLjava_lang_String.Short_parseShort_A02",
-              match(artRuntimesFromAndJava(Runtime.ART_V5_1_1)))
-          .put(
-              "lang.Short.decodeLjava_lang_String.Short_decode_A04",
-              match(artRuntimesFromAndJava(Runtime.ART_V5_1_1)))
-          .put(
-              "lang.Short.ConstructorLjava_lang_String.Short_Constructor_A02",
-              match(artRuntimesFromAndJava(Runtime.ART_V5_1_1)))
-          .put(
-              "lang.ClassNotFoundException.serialization.ClassNotFoundException_serialization_A01",
-              anyDexVm())
-          .put(
-              "lang.annotation.AnnotationFormatError.serialization.AnnotationFormatError_serialization_A01",
-              anyDexVm())
-          .put(
-              "lang.Short.parseShortLjava_lang_StringI.Short_parseShort_A02",
-              match(artRuntimesFromAndJava(Runtime.ART_V5_1_1)))
-          .put(
-              "lang.annotation.IncompleteAnnotationException.ConstructorLjava_lang_ClassLjava_lang_String.IncompleteAnnotationException_Constructor_A01",
-              match(
-                  runtimes(
-                      Runtime.ART_DEFAULT, Runtime.ART_V9_0_0, Runtime.ART_V8_1_0, Runtime.JAVA)))
-          .put(
-              "lang.InterruptedException.serialization.InterruptedException_serialization_A01",
-              anyDexVm())
-          .put(
-              "lang.annotation.IncompleteAnnotationException.Class.IncompleteAnnotationException_class_A01",
-              any())
-          .put("lang.annotation.Annotation.Class.Annotation_class_A03", anyDexVm())
-          .put("lang.annotation.Annotation.serialization.Annotation_serialization_A01", anyDexVm())
-          .put(
-              "lang.annotation.Annotation.annotationType.Annotation_annotationType_A01", anyDexVm())
-          .put(
-              "lang.annotation.IncompleteAnnotationException.serialization.IncompleteAnnotationException_serialization_A01",
-              anyDexVm())
-          .put("lang.annotation.Annotation.Class.Annotation_class_A02", any())
-          .put("lang.annotation.Retention.Retention_class_A01", anyDexVm())
-          .put(
-              "lang.annotation.AnnotationTypeMismatchException.Class.AnnotationTypeMismatchException_class_A01",
-              any())
-          .put("lang.Long.serialization.Long_serialization_A01", anyDexVm())
-          .put("lang.ThreadGroup.resume.ThreadGroup_resume_A01", anyDexVm())
-          .put(
-              "lang.AbstractMethodError.serialization.AbstractMethodError_serialization_A01",
-              anyDexVm())
-          .put("lang.RuntimeException.serialization.RuntimeException_serialization_A01", anyDexVm())
-          .put("lang.ThreadGroup.suspend.ThreadGroup_suspend_A01", anyDexVm())
-          .put(
-              "lang.ThreadGroup.ConstructorLjava_lang_ThreadGroupLjava_lang_String.ThreadGroup_Constructor_A03",
-              anyDexVm())
-          .put("lang.ThreadGroup.stop.ThreadGroup_stop_A01", anyDexVm())
-          .put("lang.ThreadGroup.enumerate_Thread.ThreadGroup_enumerate_A01", anyDexVm())
-          .put(
-              "lang.ThreadGroup.ConstructorLjava_lang_ThreadGroupLjava_lang_String.ThreadGroup_Constructor_A04",
-              anyDexVm())
-          .put(
-              "lang.ThreadGroup.parentOfLjava_lang_ThreadGroup.ThreadGroup_parentOf_A01",
-              anyDexVm())
-          .put("lang.ThreadGroup.getMaxPriority.ThreadGroup_getMaxPriority_A02", anyDexVm())
-          .put("lang.ThreadGroup.checkAccess.ThreadGroup_checkAccess_A03", anyDexVm())
-          .put("lang.ThreadGroup.enumerate_ThreadZ.ThreadGroup_enumerate_A01", anyDexVm())
-          .put(
-              "lang.ThreadGroup.uncaughtExceptionLjava_lang_ThreadLjava_lang_Throwable.ThreadGroup_uncaughtException_A01",
-              anyDexVm())
-          .put("lang.ThreadGroup.checkAccess.ThreadGroup_checkAccess_A02", anyDexVm())
-          .put(
-              "lang.ThreadGroup.ConstructorLjava_lang_String.ThreadGroup_Constructor_A04",
-              anyDexVm())
-          .put("lang.ThreadGroup.activeCount.ThreadGroup_activeCount_A01", anyDexVm())
-          .put("lang.ThreadGroup.setMaxPriorityI.ThreadGroup_setMaxPriority_A03", anyDexVm())
-          .put(
-              "lang.ThreadGroup.ConstructorLjava_lang_String.ThreadGroup_Constructor_A03",
-              anyDexVm())
-          .put("lang.ThreadGroup.getParent.ThreadGroup_getParent_A03", anyDexVm())
-          .put("lang.Class.getDeclaredConstructors.Class_getDeclaredConstructors_A02", any())
-          .put("lang.AssertionError.serialization.AssertionError_serialization_A01", anyDexVm())
-          .put("lang.Class.getClassLoader.Class_getClassLoader_A01", anyDexVm())
-          .put("lang.Class.getDeclaringClass.Class_getDeclaringClass_A01", anyDexVm())
-          .put(
-              "lang.Class.getDeclaredFields.Class_getDeclaredFields_A01",
-              match(artRuntimesFrom(Runtime.ART_V5_1_1)))
-          .put("lang.Class.getClassLoader.Class_getClassLoader_A02", anyDexVm())
-          .put("lang.Class.getClassLoader.Class_getClassLoader_A03", anyDexVm())
-          .put("lang.Class.getDeclaredFields.Class_getDeclaredFields_A02", any())
-          .put("lang.Class.getResourceLjava_lang_String.Class_getResource_A01", anyDexVm())
-          .put("lang.Class.getConstructor_Ljava_lang_Class.Class_getConstructor_A03", anyDexVm())
-          .put(
-              "lang.Class.forNameLjava_lang_StringZLjava_lang_ClassLoader.Class_forName_A03", any())
-          .put(
-              "lang.Class.forNameLjava_lang_StringZLjava_lang_ClassLoader.Class_forName_A07",
-              anyDexVm())
-          .put(
-              "lang.Class.forNameLjava_lang_StringZLjava_lang_ClassLoader.Class_forName_A01",
-              anyDexVm())
-          .put("lang.Class.getConstructor_Ljava_lang_Class.Class_getConstructor_A04", any())
-          .put("lang.Class.serialization.Class_serialization_A01", anyDexVm())
-          .put("lang.Class.getMethods.Class_getMethods_A02", any())
-          .put(
-              "lang.Class.getDeclaredMethodLjava_lang_String_Ljava_lang_Class.Class_getDeclaredMethod_A05",
-              any())
-          .put("lang.Class.getClasses.Class_getClasses_A02", any())
-          .put(
-              "lang.Class.getDeclaredMethodLjava_lang_String_Ljava_lang_Class.Class_getDeclaredMethod_A03",
-              anyDexVm())
-          .put(
-              "lang.Class.getClasses.Class_getClasses_A01",
-              match(
-                  runtimes(
-                      Runtime.ART_DEFAULT,
-                      Runtime.ART_V9_0_0,
-                      Runtime.ART_V8_1_0,
-                      Runtime.ART_V7_0_0,
-                      Runtime.ART_V4_4_4,
-                      Runtime.ART_V4_0_4,
-                      Runtime.JAVA)))
-          .put("lang.Class.getProtectionDomain.Class_getProtectionDomain_A01", any())
-          .put("lang.Class.getProtectionDomain.Class_getProtectionDomain_A02", anyDexVm())
-          .put(
-              "lang.Class.getDeclaredMethods.Class_getDeclaredMethods_A01",
-              match(artRuntimesFromAndJava(Runtime.ART_V7_0_0)))
-          .put(
-              "lang.Class.getMethods.Class_getMethods_A01",
-              match(artRuntimesFromAndJava(Runtime.ART_V7_0_0)))
-          .put("lang.Class.getGenericInterfaces.Class_getGenericInterfaces_A04", any())
-          .put("lang.Class.getDeclaredFieldLjava_lang_String.Class_getDeclaredField_A04", any())
-          .put("lang.Class.getDeclaredMethods.Class_getDeclaredMethods_A02", any())
-          .put(
-              "lang.Class.getResourceAsStreamLjava_lang_String.Class_getResourceAsStream_A01",
-              anyDexVm())
-          .put("lang.Class.getGenericInterfaces.Class_getGenericInterfaces_A05", any())
-          .put("lang.Class.getAnnotationLjava_lang_Class.Class_getAnnotation_A01", any())
-          .put("lang.Class.getGenericInterfaces.Class_getGenericInterfaces_A03", any())
-          .put("lang.Class.getDeclaredClasses.Class_getDeclaredClasses_A02", any())
-          .put("lang.Class.desiredAssertionStatus.Class_desiredAssertionStatus_A01", anyDexVm())
-          .put("lang.Class.getPackage.Class_getPackage_A01", anyDexVm())
-          .put("lang.Class.getFieldLjava_lang_String.Class_getField_A04", any())
-          .put("lang.Class.getTypeParameters.Class_getTypeParameters_A02", any())
-          .put("lang.Class.getDeclaredAnnotations.Class_getDeclaredAnnotations_A01", any())
-          .put("lang.Class.getConstructors.Class_getConstructors_A02", any())
-          .put(
-              "lang.Class.isAnnotationPresentLjava_lang_Class.Class_isAnnotationPresent_A01", any())
-          .put("lang.Class.getFields.Class_getFields_A02", any())
-          .put("lang.Class.getGenericSuperclass.Class_getGenericSuperclass_A03", any())
-          .put("lang.Class.getGenericSuperclass.Class_getGenericSuperclass_A04", any())
-          .put("lang.Class.getSigners.Class_getSigners_A01", anyDexVm())
-          .put(
-              "lang.Class.getMethodLjava_lang_String_Ljava_lang_Class.Class_getMethod_A01",
-              match(
-                  runtimes(
-                      Runtime.ART_DEFAULT,
-                      Runtime.ART_V9_0_0,
-                      Runtime.ART_V8_1_0,
-                      Runtime.ART_V7_0_0,
-                      Runtime.ART_V4_4_4,
-                      Runtime.ART_V4_0_4)))
-          .put(
-              "lang.Class.getModifiers.Class_getModifiers_A03", match(runtimes(Runtime.ART_V4_0_4)))
-          .put("lang.Class.newInstance.Class_newInstance_A06", match(runtimes(Runtime.ART_V4_0_4)))
-          .put("lang.Class.getGenericSuperclass.Class_getGenericSuperclass_A01", any())
-          .put("lang.Class.getGenericSuperclass.Class_getGenericSuperclass_A02", any())
-          .put("lang.Class.newInstance.Class_newInstance_A07", any())
-          .put(
-              "lang.Class.getDeclaredConstructor_Ljava_lang_Class.Class_getDeclaredConstructor_A02",
-              anyDexVm())
-          .put("lang.Class.getMethodLjava_lang_String_Ljava_lang_Class.Class_getMethod_A05", any())
-          .put("lang.Class.forNameLjava_lang_String.Class_forName_A01", anyDexVm())
-          .put(
-              "lang.Class.getDeclaredConstructor_Ljava_lang_Class.Class_getDeclaredConstructor_A03",
-              any())
-          .put(
-              "lang.Class.getMethodLjava_lang_String_Ljava_lang_Class.Class_getMethod_A03",
-              anyDexVm())
-          .put("lang.Class.forNameLjava_lang_String.Class_forName_A02", any())
-          .put(
-              "lang.UnsatisfiedLinkError.serialization.UnsatisfiedLinkError_serialization_A01",
-              anyDexVm())
-          .put("lang.Class.getAnnotations.Class_getAnnotations_A01", any())
-          .put(
-              "lang.EnumConstantNotPresentException.serialization.EnumConstantNotPresentException_serialization_A01",
-              anyDexVm())
-          .put("lang.String.toLowerCase.String_toLowerCase_A01", any())
-          .put("lang.String.splitLjava_lang_StringI.String_split_A01", any())
-          .put("lang.String.serialization.String_serialization_A01", anyDexVm())
-          .put("lang.String.regionMatchesZILjava_lang_StringII.String_regionMatches_A01", any())
-          .put("lang.String.valueOfF.String_valueOf_A01", any())
-          .put("lang.String.Constructor_BLjava_nio_charset_Charset.String_Constructor_A01", any())
-          .put("lang.String.concatLjava_lang_String.String_concat_A01", anyDexVm())
-          .put("lang.String.matchesLjava_lang_String.String_matches_A01", anyDexVm())
-          .put(
-              "lang.String.CASE_INSENSITIVE_ORDER.serialization.String_serialization_A01",
-              anyDexVm())
-          .put(
-              "lang.String.getBytesLjava_lang_String.String_getBytes_A14",
-              match(artRuntimesUpTo(Runtime.ART_V7_0_0)))
-          .put("lang.String.splitLjava_lang_String.String_split_A01", any())
-          .put("lang.String.getBytesII_BI.String_getBytes_A03", anyDexVm())
-          .put("lang.String.getBytesII_BI.String_getBytes_A02", anyDexVm())
-          .put("lang.String.toLowerCaseLjava_util_Locale.String_toLowerCase_A01", any())
-          .put("lang.String.Constructor_BIILjava_nio_charset_Charset.String_Constructor_A01", any())
-          .put("lang.String.getBytesLjava_nio_charset_Charset.String_getBytes_A01", anyDexVm())
-          .put("lang.String.valueOfD.String_valueOf_A01", any())
-          .put(
-              "lang.String.getBytesLjava_nio_charset_Charset.String_getBytes_A14",
-              match(artRuntimesUpTo(Runtime.ART_V7_0_0)))
-          .put("lang.Package.isSealed.Package_isSealed_A01", anyDexVm())
-          .put(
-              "lang.Package.getSpecificationVersion.Package_getSpecificationVersion_A01",
-              anyDexVm())
-          .put("lang.Package.getAnnotationLjava_lang_Class.Package_getAnnotation_A01", any())
-          .put(
-              "lang.Package.isAnnotationPresentLjava_lang_Class.Package_isAnnotationPresent_A02",
-              match(and(runtimes(Runtime.ART_V4_0_4), artRuntimesFrom(Runtime.ART_V7_0_0))))
-          .put("lang.Package.getName.Package_getName_A01", anyDexVm())
-          .put(
-              "lang.Package.getImplementationVersion.Package_getImplementationVersion_A01",
-              anyDexVm())
-          .put("lang.Package.getDeclaredAnnotations.Package_getDeclaredAnnotations_A01", any())
-          .put("lang.Package.getSpecificationVendor.Package_getSpecificationVendor_A01", anyDexVm())
-          .put(
-              "lang.Package.getAnnotationLjava_lang_Class.Package_getAnnotation_A02",
-              match(and(artRuntimesFrom(Runtime.ART_V7_0_0), runtimes(Runtime.ART_V4_0_4))))
-          .put(
-              "lang.Package.isCompatibleWithLjava_lang_String.Package_isCompatibleWith_A01",
-              anyDexVm())
-          .put("lang.Package.toString.Package_toString_A01", anyDexVm())
-          .put("lang.Package.getAnnotations.Package_getAnnotations_A01", any())
-          .put(
-              "lang.Package.isAnnotationPresentLjava_lang_Class.Package_isAnnotationPresent_A01",
-              any())
-          .put("lang.Package.getSpecificationTitle.Package_getSpecificationTitle_A01", anyDexVm())
-          .put("lang.Package.getImplementationTitle.Package_getImplementationTitle_A01", anyDexVm())
-          .put("lang.Package.getPackages.Package_getPackages_A01", anyDexVm())
-          .put("lang.Package.hashCode.Package_hashCode_A01", anyDexVm())
-          .put("lang.Package.getPackageLjava_lang_String.Package_getPackage_A01", anyDexVm())
-          .put(
-              "lang.Package.getImplementationVendor.Package_getImplementationVendor_A01",
-              anyDexVm())
-          .put("lang.Package.isSealedLjava_net_URL.Package_isSealed_A01", anyDexVm())
-          .put("lang.StringBuilder.serialization.StringBuilder_serialization_A01", anyDexVm())
-          .put(
-              "lang.SecurityManager.checkReadLjava_io_FileDescriptor.SecurityManager_checkRead_A02",
-              anyDexVm())
-          .put(
-              "lang.SecurityManager.checkAwtEventQueueAccess.SecurityManager_checkAwtEventQueueAccess_A01",
-              anyDexVm())
-          .put(
-              "lang.SecurityManager.checkWriteLjava_lang_String.SecurityManager_checkWrite_A02",
-              anyDexVm())
-          .put("lang.SecurityManager.inClassLoader.SecurityManager_inClassLoader_A01", anyDexVm())
-          .put(
-              "lang.SecurityManager.checkPermissionLjava_security_PermissionLjava_lang_Object.SecurityManager_checkPermission_A02",
-              anyDexVm())
-          .put(
-              "lang.SecurityManager.checkReadLjava_io_FileDescriptor.SecurityManager_checkRead_A01",
-              anyDexVm())
-          .put("lang.SecurityManager.inCheck.SecurityManager_inCheck_A01", anyDexVm())
-          .put(
-              "lang.SecurityManager.currentClassLoader.SecurityManager_currentClassLoader_A02",
-              anyDexVm())
-          .put(
-              "lang.SecurityManager.checkPrintJobAccess.SecurityManager_checkPrintJobAccess_A01",
-              anyDexVm())
-          .put(
-              "lang.SecurityManager.checkWriteLjava_lang_String.SecurityManager_checkWrite_A01",
-              anyDexVm())
-          .put(
-              "lang.SecurityManager.checkPackageAccessLjava_lang_String.SecurityManager_checkPackageAccess_A02",
-              anyDexVm())
-          .put(
-              "lang.SecurityManager.checkAcceptLjava_lang_StringI.SecurityManager_checkAccept_A02",
-              anyDexVm())
-          .put(
-              "lang.SecurityManager.checkPermissionLjava_security_PermissionLjava_lang_Object.SecurityManager_checkPermission_A01",
-              anyDexVm())
-          .put(
-              "lang.SecurityManager.currentClassLoader.SecurityManager_currentClassLoader_A01",
-              anyDexVm())
-          .put(
-              "lang.SecurityManager.checkMulticastLjava_net_InetAddress.SecurityManager_checkMulticast_A02",
-              anyDexVm())
-          .put("lang.SecurityManager.checkListenI.SecurityManager_checkListen_A01", anyDexVm())
-          .put(
-              "lang.SecurityManager.getSecurityContext.SecurityManager_getSecurityContext_A01",
-              anyDexVm())
-          .put(
-              "lang.SecurityManager.checkPackageAccessLjava_lang_String.SecurityManager_checkPackageAccess_A01",
-              anyDexVm())
-          .put(
-              "lang.SecurityManager.checkMemberAccessLjava_lang_ClassI.SecurityManager_checkMemberAccess_A02",
-              anyDexVm())
-          .put(
-              "lang.SecurityManager.checkMulticastLjava_net_InetAddressB.SecurityManager_checkMulticast_A02",
-              anyDexVm())
-          .put(
-              "lang.SecurityManager.checkAcceptLjava_lang_StringI.SecurityManager_checkAccept_A01",
-              anyDexVm())
-          .put(
-              "lang.SecurityManager.checkMulticastLjava_net_InetAddress.SecurityManager_checkMulticast_A01",
-              anyDexVm())
-          .put("lang.SecurityManager.Constructor.SecurityManager_Constructor_A01", anyDexVm())
-          .put(
-              "lang.SecurityManager.getClassContext.SecurityManager_getClassContext_A01",
-              anyDexVm())
-          .put(
-              "lang.SecurityManager.checkMemberAccessLjava_lang_ClassI.SecurityManager_checkMemberAccess_A03",
-              anyDexVm())
-          .put(
-              "lang.SecurityManager.checkDeleteLjava_lang_String.SecurityManager_checkDelete_A01",
-              anyDexVm())
-          .put(
-              "lang.SecurityManager.checkReadLjava_lang_StringLjava_lang_Object.SecurityManager_checkRead_A03",
-              anyDexVm())
-          .put(
-              "lang.SecurityManager.checkMulticastLjava_net_InetAddressB.SecurityManager_checkMulticast_A01",
-              anyDexVm())
-          .put("lang.SecurityManager.checkListenI.SecurityManager_checkListen_A02", any())
-          .put(
-              "lang.SecurityManager.checkAccessLjava_lang_Thread.SecurityManager_checkAccess_A01",
-              anyDexVm())
-          .put(
-              "lang.SecurityManager.checkWriteLjava_io_FileDescriptor.SecurityManager_checkWrite_A02",
-              anyDexVm())
-          .put(
-              "lang.SecurityManager.checkDeleteLjava_lang_String.SecurityManager_checkDelete_A02",
-              anyDexVm())
-          .put(
-              "lang.SecurityManager.checkPropertiesAccess.SecurityManager_checkPropertiesAccess_A01",
-              anyDexVm())
-          .put(
-              "lang.SecurityManager.checkReadLjava_lang_StringLjava_lang_Object.SecurityManager_checkRead_A02",
-              anyDexVm())
-          .put(
-              "lang.SecurityManager.checkAccessLjava_lang_ThreadGroup.SecurityManager_checkAccess_A03",
-              anyDexVm())
-          .put(
-              "lang.SecurityManager.checkAccessLjava_lang_Thread.SecurityManager_checkAccess_A03",
-              anyDexVm())
-          .put(
-              "lang.SecurityManager.checkPackageDefinitionLjava_lang_String.SecurityManager_checkPackageDefinition_A02",
-              anyDexVm())
-          .put(
-              "lang.SecurityManager.checkReadLjava_lang_String.SecurityManager_checkRead_A02",
-              anyDexVm())
-          .put(
-              "lang.SecurityManager.checkWriteLjava_io_FileDescriptor.SecurityManager_checkWrite_A01",
-              anyDexVm())
-          .put(
-              "lang.SecurityManager.checkReadLjava_lang_StringLjava_lang_Object.SecurityManager_checkRead_A01",
-              anyDexVm())
-          .put(
-              "lang.SecurityManager.checkExecLjava_lang_String.SecurityManager_checkExec_A03",
-              anyDexVm())
-          .put(
-              "lang.SecurityManager.checkPackageDefinitionLjava_lang_String.SecurityManager_checkPackageDefinition_A01",
-              anyDexVm())
-          .put(
-              "lang.SecurityManager.checkExecLjava_lang_String.SecurityManager_checkExec_A02",
-              anyDexVm())
-          .put(
-              "lang.SecurityManager.checkCreateClassLoader.SecurityManager_checkCreateClassLoader_A01",
-              anyDexVm())
-          .put(
-              "lang.SecurityManager.checkReadLjava_lang_String.SecurityManager_checkRead_A01",
-              anyDexVm())
-          .put(
-              "lang.SecurityManager.checkAccessLjava_lang_ThreadGroup.SecurityManager_checkAccess_A01",
-              anyDexVm())
-          .put(
-              "lang.SecurityManager.inClassLjava_lang_String.SecurityManager_inClass_A03",
-              anyDexVm())
-          .put(
-              "lang.SecurityManager.checkConnectLjava_lang_StringILjava_lang_Object.SecurityManager_checkConnect_A03",
-              anyDexVm())
-          .put(
-              "lang.SecurityManager.checkExecLjava_lang_String.SecurityManager_checkExec_A01",
-              anyDexVm())
-          .put(
-              "lang.SecurityManager.checkSetFactory.SecurityManager_checkSetFactory_A01",
-              anyDexVm())
-          .put(
-              "lang.SecurityManager.checkConnectLjava_lang_StringILjava_lang_Object.SecurityManager_checkConnect_A04",
-              anyDexVm())
-          .put(
-              "lang.SecurityManager.checkPermissionLjava_security_Permission.SecurityManager_checkPermission_A01",
-              anyDexVm())
-          .put(
-              "lang.SecurityManager.inClassLjava_lang_String.SecurityManager_inClass_A01",
-              anyDexVm())
-          .put(
-              "lang.SecurityManager.inClassLjava_lang_String.SecurityManager_inClass_A02",
-              anyDexVm())
-          .put(
-              "lang.SecurityManager.checkPropertyAccessLjava_lang_String.SecurityManager_checkPropertyAccess_A02",
-              anyDexVm())
-          .put("lang.SecurityManager.checkExitI.SecurityManager_checkExit_A01", anyDexVm())
-          .put(
-              "lang.SecurityManager.checkConnectLjava_lang_StringILjava_lang_Object.SecurityManager_checkConnect_A02",
-              anyDexVm())
-          .put(
-              "lang.SecurityManager.checkConnectLjava_lang_StringILjava_lang_Object.SecurityManager_checkConnect_A01",
-              anyDexVm())
-          .put(
-              "lang.SecurityManager.classLoaderDepth.SecurityManager_classLoaderDepth_A02",
-              anyDexVm())
-          .put(
-              "lang.SecurityManager.classDepthLjava_lang_String.SecurityManager_classDepth_A02",
-              anyDexVm())
-          .put(
-              "lang.SecurityManager.checkPropertyAccessLjava_lang_String.SecurityManager_checkPropertyAccess_A01",
-              anyDexVm())
-          .put(
-              "lang.SecurityManager.checkPropertyAccessLjava_lang_String.SecurityManager_checkPropertyAccess_A03",
-              anyDexVm())
-          .put(
-              "lang.SecurityManager.checkConnectLjava_lang_StringI.SecurityManager_checkConnect_A02",
-              anyDexVm())
-          .put(
-              "lang.SecurityManager.checkLinkLjava_lang_String.SecurityManager_checkLink_A02",
-              anyDexVm())
-          .put(
-              "lang.SecurityManager.classLoaderDepth.SecurityManager_classLoaderDepth_A01",
-              anyDexVm())
-          .put(
-              "lang.SecurityManager.checkPermissionLjava_security_Permission.SecurityManager_checkPermission_A02",
-              anyDexVm())
-          .put(
-              "lang.SecurityManager.currentLoadedClass.SecurityManager_currentLoadedClass_A01",
-              anyDexVm())
-          .put(
-              "lang.SecurityManager.checkSecurityAccessLjava_lang_String.SecurityManager_checkSecurityAccess_A03",
-              anyDexVm())
-          .put(
-              "lang.SecurityManager.checkConnectLjava_lang_StringI.SecurityManager_checkConnect_A03",
-              anyDexVm())
-          .put(
-              "lang.SecurityManager.checkConnectLjava_lang_StringI.SecurityManager_checkConnect_A01",
-              anyDexVm())
-          .put(
-              "lang.SecurityManager.checkTopLevelWindowLjava_lang_Object.SecurityManager_checkTopLevelWindow_A02",
-              anyDexVm())
-          .put(
-              "lang.SecurityManager.currentLoadedClass.SecurityManager_currentLoadedClass_A02",
-              anyDexVm())
-          .put(
-              "lang.SecurityManager.classDepthLjava_lang_String.SecurityManager_classDepth_A01",
-              anyDexVm())
-          .put(
-              "lang.SecurityManager.checkSecurityAccessLjava_lang_String.SecurityManager_checkSecurityAccess_A01",
-              anyDexVm())
-          .put("lang.Throwable.serialization.Throwable_serialization_A01", anyDexVm())
-          .put(
-              "lang.SecurityManager.checkTopLevelWindowLjava_lang_Object.SecurityManager_checkTopLevelWindow_A01",
-              anyDexVm())
-          .put(
-              "lang.SecurityManager.checkLinkLjava_lang_String.SecurityManager_checkLink_A01",
-              anyDexVm())
-          .put("lang.Throwable.getStackTrace.Throwable_getStackTrace_A01", anyDexVm())
-          .put(
-              "lang.SecurityManager.checkSystemClipboardAccess.SecurityManager_checkSystemClipboardAccess_A01",
-              anyDexVm())
-          .put(
-              "lang.SecurityManager.checkSecurityAccessLjava_lang_String.SecurityManager_checkSecurityAccess_A02",
-              anyDexVm())
-          .put(
-              "lang.reflect.ReflectPermission.Constructor_java_lang_String.ReflectPermission_Constructor_A03",
-              anyDexVm())
-          .put(
-              "lang.reflect.MalformedParameterizedTypeException.serialization.MalformedParameterizedTypeException_serialization_A01",
-              anyDexVm())
-          .put(
-              "lang.reflect.ReflectPermission.Constructor_java_lang_StringLjava_lang_String.ReflectPermission_Constructor_A02",
-              anyDexVm())
-          .put(
-              "lang.UnsupportedClassVersionError.serialization.UnsupportedClassVersionError_serialization_A01",
-              anyDexVm())
-          .put(
-              "lang.reflect.ReflectPermission.Constructor_java_lang_String.ReflectPermission_Constructor_A01",
-              any())
-          .put("lang.reflect.ReflectPermission.Class.ReflectPermission_class_A01", anyDexVm())
-          .put("lang.reflect.Proxy.serialization.Proxy_serialization_A01", any())
-          .put(
-              "lang.reflect.ReflectPermission.Constructor_java_lang_StringLjava_lang_String.ReflectPermission_Constructor_A03",
-              anyDexVm())
-          .put(
-              "lang.reflect.ReflectPermission.Constructor_java_lang_String.ReflectPermission_Constructor_A02",
-              anyDexVm())
-          .put("lang.reflect.ReflectPermission.Class.ReflectPermission_class_A02", anyDexVm())
-          .put(
-              "lang.reflect.ReflectPermission.Constructor_java_lang_StringLjava_lang_String.ReflectPermission_Constructor_A01",
-              any())
-          .put(
-              "lang.reflect.Proxy.getInvocationHandlerLjava_lang_Object.Proxy_getInvocationHandler_A02",
-              match(artRuntimesFromAndJava(Runtime.ART_V5_1_1)))
-          .put("lang.reflect.Proxy.Class.Proxy_class_A01", any())
-          .put(
-              "lang.reflect.Proxy.Class.Proxy_class_A02",
-              match(artRuntimesUpTo(Runtime.ART_V4_4_4)))
-          .put(
-              "lang.reflect.Proxy.Class.Proxy_class_A03",
-              match(artRuntimesUpTo(Runtime.ART_V4_4_4)))
-          .put(
-              "lang.reflect.Proxy.getProxyClassLjava_lang_ClassLoader_Ljava_lang_Class.Proxy_getProxyClass_A01",
-              anyDexVm())
-          .put(
-              "lang.reflect.Proxy.getProxyClassLjava_lang_ClassLoader_Ljava_lang_Class.Proxy_getProxyClass_A03",
-              anyDexVm())
-          .put(
-              "lang.reflect.Proxy.h.Proxy_h_A01",
-              match(
-                  runtimes(
-                      Runtime.ART_DEFAULT, Runtime.ART_V9_0_0, Runtime.ART_V8_1_0, Runtime.JAVA)))
-          .put("lang.reflect.Proxy.serialization.Proxy_serialization_A02", any())
-          .put(
-              "lang.reflect.GenericSignatureFormatError.serialization.GenericSignatureFormatError_serialization_A01",
-              anyDexVm())
-          .put(
-              "lang.reflect.Proxy.newProxyInstanceLjava_lang_ClassLoader_Ljava_lang_ClassLjava_lang_reflect_InvocationHandler.Proxy_newProxyInstance_A02",
-              anyDexVm())
-          .put(
-              "lang.reflect.Proxy.ConstructorLjava_lang_reflect_InvocationHandler.Proxy_Constructor_A01",
-              match(
-                  runtimes(
-                      Runtime.ART_DEFAULT, Runtime.ART_V9_0_0, Runtime.ART_V8_1_0, Runtime.JAVA)))
-          .put(
-              "lang.reflect.Proxy.newProxyInstanceLjava_lang_ClassLoader_Ljava_lang_ClassLjava_lang_reflect_InvocationHandler.Proxy_newProxyInstance_A01",
-              anyDexVm())
-          .put("lang.reflect.Modifier.isStrictI.Modifier_isStrict_A01", any())
-          .put("lang.reflect.Method.getGenericReturnType.Method_getGenericReturnType_A03", any())
-          .put("lang.reflect.Method.getGenericReturnType.Method_getGenericReturnType_A02", any())
-          .put("lang.reflect.Method.getAnnotationLjava_lang_Class.Method_getAnnotation_A01", any())
-          .put(
-              "lang.reflect.Method.getGenericExceptionTypes.Method_getGenericExceptionTypes_A02",
-              any())
-          .put("lang.reflect.Method.isBridge.Method_isBridge_A01", any())
-          .put("lang.reflect.Method.isSynthetic.Method_isSynthetic_A01", any())
-          .put("lang.reflect.Method.getGenericReturnType.Method_getGenericReturnType_A04", any())
-          .put(
-              "lang.reflect.Method.getGenericExceptionTypes.Method_getGenericExceptionTypes_A01",
-              any())
-          .put(
-              "lang.reflect.Method.invokeLjava_lang_Object_Ljava_lang_Object.Method_invoke_A07",
-              anyDexVm())
-          .put(
-              "lang.reflect.Method.getGenericExceptionTypes.Method_getGenericExceptionTypes_A04",
-              any())
-          .put("lang.reflect.Method.getTypeParameters.Method_getTypeParameters_A02", any())
-          .put(
-              "lang.reflect.Method.getGenericExceptionTypes.Method_getGenericExceptionTypes_A03",
-              any())
-          .put(
-              "lang.reflect.Method.getDeclaredAnnotations.Method_getDeclaredAnnotations_A01", any())
-          .put(
-              "lang.reflect.Method.getGenericParameterTypes.Method_getGenericParameterTypes_A04",
-              any())
-          .put("lang.reflect.Method.toGenericString.Method_toGenericString_A01", any())
-          .put(
-              "lang.reflect.Method.getGenericParameterTypes.Method_getGenericParameterTypes_A03",
-              any())
-          .put(
-              "lang.reflect.InvocationHandler.invokeLjava_lang_ObjectLjava_lang_reflect_Method_Ljava_lang_Object.InvocationHandler_invoke_A01",
-              match(artRuntimesUpTo(Runtime.ART_V4_4_4)))
-          .put(
-              "lang.reflect.InvocationHandler.invokeLjava_lang_ObjectLjava_lang_reflect_Method_Ljava_lang_Object.InvocationHandler_invoke_A02",
-              anyDexVm())
-          .put("lang.reflect.Method.getDefaultValue.Method_getDefaultValue_A02", any())
-          .put(
-              "lang.reflect.Method.hashCode.Method_hashCode_A01",
-              match(artRuntimesUpTo(Runtime.ART_V4_4_4)))
-          .put("lang.reflect.Method.toString.Method_toString_A01", any())
-          .put(
-              "lang.reflect.Method.getGenericParameterTypes.Method_getGenericParameterTypes_A02",
-              any())
-          .put("lang.reflect.Field.getFloatLjava_lang_Object.Field_getFloat_A05", anyDexVm())
-          .put("lang.reflect.Field.getDeclaringClass.Field_getDeclaringClass_A01", anyDexVm())
-          .put("lang.reflect.Field.getByteLjava_lang_Object.Field_getByte_A05", anyDexVm())
-          .put("lang.reflect.Field.getCharLjava_lang_Object.Field_getChar_A05", anyDexVm())
-          .put("lang.reflect.Field.getBooleanLjava_lang_Object.Field_getBoolean_A05", anyDexVm())
-          .put("lang.reflect.Field.setByteLjava_lang_ObjectB.Field_setByte_A05", anyDexVm())
-          .put("lang.reflect.Field.setByteLjava_lang_ObjectB.Field_setByte_A01", anyDexVm())
-          .put(
-              "lang.reflect.Field.setByteLjava_lang_ObjectB.Field_setByte_A02",
-              match(artRuntimesUpTo(Runtime.ART_V4_4_4)))
-          .put("lang.reflect.Field.setBooleanLjava_lang_ObjectZ.Field_setBoolean_A01", anyDexVm())
-          .put(
-              "lang.reflect.Field.setBooleanLjava_lang_ObjectZ.Field_setBoolean_A02",
-              match(artRuntimesUpTo(Runtime.ART_V4_4_4)))
-          .put("lang.reflect.Field.setCharLjava_lang_ObjectC.Field_setChar_A05", anyDexVm())
-          .put("lang.reflect.Field.isSynthetic.Field_isSynthetic_A01", any())
-          .put("lang.reflect.Field.setBooleanLjava_lang_ObjectZ.Field_setBoolean_A05", anyDexVm())
-          .put("lang.reflect.Field.getType.Field_getType_A01", anyDexVm())
-          .put("lang.reflect.Field.setCharLjava_lang_ObjectC.Field_setChar_A01", anyDexVm())
-          .put(
-              "lang.reflect.Field.setCharLjava_lang_ObjectC.Field_setChar_A02",
-              match(artRuntimesUpTo(Runtime.ART_V4_4_4)))
-          .put("lang.reflect.Field.getDoubleLjava_lang_Object.Field_getDouble_A05", anyDexVm())
-          .put("lang.reflect.Field.setFloatLjava_lang_ObjectF.Field_setFloat_A01", anyDexVm())
-          .put(
-              "lang.reflect.Field.setFloatLjava_lang_ObjectF.Field_setFloat_A02",
-              match(artRuntimesUpTo(Runtime.ART_V4_4_4)))
-          .put("lang.reflect.Field.getAnnotationLjava_lang_Class.Field_getAnnotation_A01", any())
-          .put(
-              "lang.reflect.Field.setIntLjava_lang_ObjectI.Field_setInt_A02",
-              match(artRuntimesUpTo(Runtime.ART_V4_4_4)))
-          .put("lang.reflect.Field.getIntLjava_lang_Object.Field_getInt_A05", anyDexVm())
-          .put("lang.reflect.Field.setFloatLjava_lang_ObjectF.Field_setFloat_A05", anyDexVm())
-          .put("lang.reflect.Field.getShortLjava_lang_Object.Field_getShort_A05", anyDexVm())
-          .put("lang.reflect.Field.getGenericType.Field_getGenericType_A03", any())
-          .put("lang.reflect.Field.getDeclaredAnnotations.Field_getDeclaredAnnotations_A01", any())
-          .put("lang.reflect.Field.getGenericType.Field_getGenericType_A01", anyDexVm())
-          .put("lang.reflect.Field.setIntLjava_lang_ObjectI.Field_setInt_A05", anyDexVm())
-          .put("lang.reflect.Field.getGenericType.Field_getGenericType_A02", any())
-          .put("lang.reflect.Field.toGenericString.Field_toGenericString_A01", anyDexVm())
-          .put("lang.reflect.Field.getGenericType.Field_getGenericType_A04", any())
-          .put("lang.reflect.Field.setIntLjava_lang_ObjectI.Field_setInt_A01", anyDexVm())
-          .put(
-              "lang.reflect.Field.setDoubleLjava_lang_ObjectD.Field_setDouble_A02",
-              match(artRuntimesUpTo(Runtime.ART_V4_4_4)))
-          .put("lang.reflect.Field.setDoubleLjava_lang_ObjectD.Field_setDouble_A05", anyDexVm())
-          .put("lang.reflect.Field.setShortLjava_lang_ObjectS.Field_setShort_A01", anyDexVm())
-          .put(
-              "lang.reflect.Field.setLongLjava_lang_ObjectJ.Field_setLong_A02",
-              match(artRuntimesUpTo(Runtime.ART_V4_4_4)))
-          .put("lang.reflect.Field.setLongLjava_lang_ObjectJ.Field_setLong_A05", anyDexVm())
-          .put("lang.reflect.Field.setLongLjava_lang_ObjectJ.Field_setLong_A01", anyDexVm())
-          .put("lang.reflect.Field.setDoubleLjava_lang_ObjectD.Field_setDouble_A01", anyDexVm())
-          .put(
-              "lang.reflect.Field.setShortLjava_lang_ObjectS.Field_setShort_A02",
-              match(artRuntimesUpTo(Runtime.ART_V4_4_4)))
-          .put("lang.reflect.Field.setShortLjava_lang_ObjectS.Field_setShort_A05", anyDexVm())
-          .put("lang.reflect.Field.getLjava_lang_Object.Field_get_A05", anyDexVm())
-          .put("lang.reflect.Field.getLongLjava_lang_Object.Field_getLong_A05", anyDexVm())
-          .put(
-              "lang.reflect.Field.setLjava_lang_ObjectLjava_lang_Object.Field_set_A02",
-              match(artRuntimesUpTo(Runtime.ART_V4_4_4)))
-          .put("lang.reflect.Field.setLjava_lang_ObjectLjava_lang_Object.Field_set_A05", anyDexVm())
-          .put("lang.reflect.Field.setLjava_lang_ObjectLjava_lang_Object.Field_set_A01", anyDexVm())
-          .put(
-              "lang.reflect.Constructor.newInstance_Ljava_lang_Object.Constructor_newInstance_A06",
-              anyDexVm())
-          .put("lang.reflect.Constructor.isSynthetic.Constructor_isSynthetic_A01", any())
-          .put(
-              "lang.reflect.Constructor.getGenericExceptionTypes.Constructor_getGenericExceptionTypes_A03",
-              any())
-          .put(
-              "lang.reflect.Constructor.getGenericExceptionTypes.Constructor_getGenericExceptionTypes_A02",
-              any())
-          .put(
-              "lang.reflect.Constructor.getGenericExceptionTypes.Constructor_getGenericExceptionTypes_A01",
-              any())
-          .put(
-              "lang.reflect.Constructor.getAnnotationLjava_lang_Class.Constructor_getAnnotation_A01",
-              any())
-          .put(
-              "lang.reflect.Constructor.getDeclaredAnnotations.Constructor_getDeclaredAnnotations_A01",
-              any())
-          .put(
-              "lang.reflect.Constructor.getGenericExceptionTypes.Constructor_getGenericExceptionTypes_A04",
-              any())
-          .put(
-              "lang.reflect.InvocationTargetException.serialization.InvocationTargetException_serialization_A01",
-              anyDexVm())
-          .put(
-              "lang.reflect.Constructor.toGenericString.Constructor_toGenericString_A01",
-              anyDexVm())
-          .put(
-              "lang.reflect.Constructor.getTypeParameters.Constructor_getTypeParameters_A02", any())
-          .put(
-              "lang.reflect.Constructor.getGenericParameterTypes.Constructor_getGenericParameterTypes_A03",
-              any())
-          .put(
-              "lang.reflect.Constructor.getGenericParameterTypes.Constructor_getGenericParameterTypes_A04",
-              any())
-          .put(
-              "lang.reflect.Constructor.getGenericParameterTypes.Constructor_getGenericParameterTypes_A02",
-              any())
-          .put(
-              "lang.reflect.AccessibleObject.setAccessibleZ.AccessibleObject_setAccessible_A03",
-              anyDexVm())
-          .put(
-              "lang.reflect.UndeclaredThrowableException.serialization.UndeclaredThrowableException_serialization_A01",
-              anyDexVm())
-          .put(
-              "lang.reflect.AccessibleObject.setAccessibleZ.AccessibleObject_setAccessible_A02",
-              anyDexVm())
-          .put(
-              "lang.reflect.AccessibleObject.setAccessible_Ljava_lang_reflect_AccessibleObjectZ.AccessibleObject_setAccessible_A03",
-              anyDexVm())
-          .put(
-              "lang.reflect.AccessibleObject.isAnnotationPresentLjava_lang_Class.AccessibleObject_isAnnotationPresent_A01",
-              any())
-          .put(
-              "lang.reflect.AccessibleObject.setAccessible_Ljava_lang_reflect_AccessibleObjectZ.AccessibleObject_setAccessible_A02",
-              anyDexVm())
-          .put(
-              "lang.reflect.AccessibleObject.getAnnotations.AccessibleObject_getAnnotations_A01",
-              any())
-          .put(
-              "lang.reflect.AccessibleObject.getDeclaredAnnotations.AccessibleObject_getDeclaredAnnotations_A01",
-              any())
-          .put(
-              "lang.reflect.AccessibleObject.getAnnotationLjava_lang_Class.AccessibleObject_getAnnotation_A01",
-              any())
-          .put(
-              "lang.IllegalAccessException.serialization.IllegalAccessException_serialization_A01",
-              anyDexVm())
-          .put("lang.Character.getTypeI.Character_getType_A01", any())
-          .put("lang.Character.isDigitI.Character_isDigit_A01", any())
-          .put("lang.Character.getTypeC.Character_getType_A01", any())
-          .put("lang.Character.serialization.Character_serialization_A01", anyDexVm())
-          .put("lang.Character.isDigitC.Character_isDigit_A01", any())
-          .put("lang.Character.digitCI.Character_digit_A01", any())
-          .put("lang.Character.digitII.Character_digit_A01", any())
-          .put("lang.Character.isLowerCaseC.Character_isLowerCase_A01", anyDexVm())
-          .put(
-              "lang.Character.isSpaceCharC.Character_isSpaceChar_A01",
-              match(runtimes(Runtime.ART_V4_0_4)))
-          .put(
-              "lang.Character.isSpaceCharI.Character_isSpaceChar_A01",
-              match(runtimes(Runtime.ART_V4_0_4)))
-          .put(
-              "lang.Character.isWhitespaceC.Character_isWhitespace_A01",
-              match(runtimes(Runtime.ART_V4_0_4)))
-          .put(
-              "lang.Character.isWhitespaceI.Character_isWhitespace_A01",
-              match(runtimes(Runtime.ART_V4_0_4)))
-          .put("lang.Character.getDirectionalityI.Character_getDirectionality_A01", any())
-          .put(
-              "lang.Character.UnicodeBlock.ofC.UnicodeBlock_of_A01",
-              match(artRuntimesFromAndJava(Runtime.ART_V4_4_4)))
-          .put(
-              "lang.Character.UnicodeBlock.ofI.UnicodeBlock_of_A01",
-              match(artRuntimesFromAndJava(Runtime.ART_V4_4_4)))
-          .put("lang.Character.isLowerCaseI.Character_isLowerCase_A01", anyDexVm())
-          .put("lang.Process.waitFor.Process_waitFor_A01", anyDexVm())
-          .put("lang.System.getProperties.System_getProperties_A01", anyDexVm())
-          .put("lang.Process.getErrorStream.Process_getErrorStream_A01", anyDexVm())
-          .put("lang.Character.getDirectionalityC.Character_getDirectionality_A01", any())
-          .put("lang.Process.exitValue.Process_exitValue_A01", anyDexVm())
-          .put("lang.System.loadLjava_lang_String.System_load_A01", anyDexVm())
-          .put("lang.Process.getInputStream.Process_getInputStream_A01", anyDexVm())
-          .put("lang.System.loadLibraryLjava_lang_String.System_loadLibrary_A01", anyDexVm())
-          .put(
-              "lang.System.setSecurityManagerLjava_lang_SecurityManager.System_setSecurityManager_A02",
-              anyDexVm())
-          .put("lang.System.runFinalizersOnExitZ.System_runFinalizersOnExit_A01", anyDexVm())
-          .put("lang.System.getenvLjava_lang_String.System_getenv_A01", anyDexVm())
-          .put("lang.System.getenv.System_getenv_A01", anyDexVm())
-          .put(
-              "lang.System.getPropertyLjava_lang_StringLjava_lang_String.System_getProperty_A01",
-              anyDexVm())
-          .put("lang.System.exitI.System_exit_A01", anyDexVm())
-          .put(
-              "util.concurrent.ArrayBlockingQueue.serialization.ArrayBlockingQueue_serialization_A01",
-              anyDexVm())
-          .put(
-              "lang.System.arraycopyLjava_lang_ObjectILjava_lang_ObjectII.System_arraycopy_A04",
-              anyDexVm())
-          .put(
-              "lang.System.setPropertiesLjava_util_Properties.System_setProperties_A02", anyDexVm())
-          .put("lang.System.clearPropertyLjava_lang_String.System_clearProperty_A02", anyDexVm())
-          .put("lang.System.getPropertyLjava_lang_String.System_getProperty_A01", anyDexVm())
-          .put(
-              "util.concurrent.LinkedBlockingQueue.serialization.LinkedBlockingQueue_serialization_A01",
-              anyDexVm())
-          .put(
-              "util.concurrent.LinkedBlockingDeque.serialization.LinkedBlockingDeque_serialization_A01",
-              anyDexVm())
-          .put(
-              "util.concurrent.ConcurrentLinkedQueue.serialization.ConcurrentLinkedQueue_serialization_A01",
-              anyDexVm())
-          .put(
-              "util.concurrent.SynchronousQueue.serialization.SynchronousQueue_serialization_A01",
-              anyDexVm())
-          .put(
-              "util.concurrent.CopyOnWriteArrayList.serialization.CopyOnWriteArrayList_serialization_A01",
-              anyDexVm())
-          .put(
-              "util.concurrent.CopyOnWriteArrayList.subListII.CopyOnWriteArrayList_subList_A01",
-              any())
-          .put(
-              "util.concurrent.ConcurrentHashMap.serialization.ConcurrentHashMap_serialization_A01",
-              match(artRuntimesFrom(Runtime.ART_V5_1_1)))
-          .put("util.concurrent.ConcurrentHashMap.keySet.ConcurrentHashMap_keySet_A01", anyDexVm())
-          .put(
-              "util.concurrent.Executors.privilegedThreadFactory.Executors_privilegedThreadFactory_A01",
-              any())
-          .put(
-              "util.concurrent.Executors.privilegedCallableLjava_util_concurrent_Callable.Executors_privilegedCallable_A01",
-              anyDexVm())
-          .put(
-              "util.concurrent.CopyOnWriteArraySet.serialization.CopyOnWriteArraySet_serialization_A01",
-              anyDexVm())
-          .put(
-              "util.concurrent.Executors.privilegedCallableUsingCurrentClassLoaderLjava_util_concurrent_Callable.Executors_privilegedCallableUsingCurrentClassLoader_A01",
-              anyDexVm())
-          .put(
-              "util.concurrent.PriorityBlockingQueue.ConstructorLjava_util_Collection.PriorityBlockingQueue_Constructor_A01",
-              any())
-          .put(
-              "util.concurrent.PriorityBlockingQueue.serialization.PriorityBlockingQueue_serialization_A01",
-              anyDexVm())
-          .put(
-              "lang.ThreadGroup.destroy.ThreadGroup_destroy_A01",
-              match(artRuntimesUpTo(Runtime.ART_V6_0_1)))
-          .put("lang.Thread.start.Thread_start_A01", match(runtimes(Runtime.ART_V7_0_0)))
-          .put(
-              "lang.String.getBytesLjava_lang_String.String_getBytes_A02",
-              match(artRuntimesUpTo(Runtime.ART_V7_0_0)))
-          .put(
-              "util.concurrent.CopyOnWriteArrayList.lastIndexOfLjava_lang_ObjectI.CopyOnWriteArrayList_lastIndexOf_A02",
-              match(artRuntimesUpTo(Runtime.ART_V7_0_0)))
-          .put(
-              "util.concurrent.CopyOnWriteArrayList.lastIndexOfLjava_lang_ObjectI.CopyOnWriteArrayList_lastIndexOf_A01",
-              match(artRuntimesUpTo(Runtime.ART_V7_0_0)))
-          .put(
-              "lang.StringBuffer.getCharsII_CI.StringBuffer_getChars_A03",
-              match(artRuntimesUpTo(Runtime.ART_V6_0_1)))
-          .put(
-              "lang.StringBuffer.appendF.StringBuffer_append_A01",
-              match(artRuntimesUpTo(Runtime.ART_V6_0_1)))
-          .put(
-              "lang.StringBuffer.insertI_CII.StringBuffer_insert_A02",
-              match(artRuntimesUpTo(Runtime.ART_V6_0_1)))
-          .put(
-              "lang.StrictMath.scalbDI.StrictMath_scalb_A03",
-              match(artRuntimesUpTo(Runtime.ART_V6_0_1)))
-          .put(
-              "lang.StrictMath.scalbDI.StrictMath_scalb_A01",
-              match(artRuntimesUpTo(Runtime.ART_V6_0_1)))
-          .put(
-              "lang.StrictMath.scalbFI.StrictMath_scalb_A03",
-              match(artRuntimesUpTo(Runtime.ART_V6_0_1)))
-          .put(
-              "lang.StrictMath.scalbFI.StrictMath_scalb_A01",
-              match(artRuntimesUpTo(Runtime.ART_V6_0_1)))
-          .put(
-              "lang.Thread.ConstructorLjava_lang_ThreadGroupLjava_lang_RunnableLjava_lang_StringJ.Thread_Constructor_A07",
-              match(artRuntimesUpTo(Runtime.ART_V6_0_1)))
-          .put(
-              "lang.Thread.ConstructorLjava_lang_ThreadGroupLjava_lang_RunnableLjava_lang_String.Thread_Constructor_A07",
-              match(artRuntimesUpTo(Runtime.ART_V6_0_1)))
-          .put(
-              "lang.Thread.toString.Thread_toString_A01",
-              match(artRuntimesUpTo(Runtime.ART_V6_0_1)))
-          .put("lang.Thread.start.Thread_start_A02", match(artRuntimesUpTo(Runtime.ART_V6_0_1)))
-          .put(
-              "lang.Thread.setPriorityI.Thread_setPriority_A01",
-              match(artRuntimesUpTo(Runtime.ART_V6_0_1)))
-          .put(
-              "lang.ClassLoader.ConstructorLjava_lang_ClassLoader.ClassLoader_Constructor_A01",
-              match(artRuntimesUpTo(Runtime.ART_V6_0_1)))
-          .put(
-              "lang.Enum.compareToLjava_lang_Enum.Enum_compareTo_A03",
-              match(artRuntimesUpTo(Runtime.ART_V6_0_1)))
-          .put("lang.Enum.hashCode.Enum_hashCode_A01", match(artRuntimesUpTo(Runtime.ART_V6_0_1)))
-          .put(
-              "lang.StackTraceElement.hashCode.StackTraceElement_hashCode_A01",
-              match(artRuntimesUpTo(Runtime.ART_V6_0_1)))
-          .put(
-              "lang.ProcessBuilder.environment.ProcessBuilder_environment_A01",
-              match(artRuntimesUpTo(Runtime.ART_V6_0_1)))
-          .put(
-              "lang.ProcessBuilder.environment.ProcessBuilder_environment_A03",
-              match(artRuntimesUpTo(Runtime.ART_V6_0_1)))
-          .put(
-              "lang.Float.toStringF.Float_toString_A04", match(artRuntimesUpTo(Runtime.ART_V6_0_1)))
-          .put(
-              "lang.Float.toStringF.Float_toString_A03", match(artRuntimesUpTo(Runtime.ART_V6_0_1)))
-          .put(
-              "lang.ThreadGroup.getMaxPriority.ThreadGroup_getMaxPriority_A01",
-              match(artRuntimesUpTo(Runtime.ART_V6_0_1)))
-          .put(
-              "lang.ThreadGroup.uncaughtExceptionLjava_lang_ThreadLjava_lang_Throwable.ThreadGroup_uncaughtException_A02",
-              match(artRuntimesUpTo(Runtime.ART_V6_0_1)))
-          .put(
-              "lang.ThreadGroup.list.ThreadGroup_list_A01",
-              match(artRuntimesUpTo(Runtime.ART_V6_0_1)))
-          .put(
-              "lang.ThreadGroup.setMaxPriorityI.ThreadGroup_setMaxPriority_A01",
-              match(artRuntimesUpTo(Runtime.ART_V6_0_1)))
-          .put(
-              "lang.ThreadGroup.setMaxPriorityI.ThreadGroup_setMaxPriority_A04",
-              match(artRuntimesUpTo(Runtime.ART_V6_0_1)))
-          .put(
-              "lang.ThreadGroup.toString.ThreadGroup_toString_A01",
-              match(artRuntimesUpTo(Runtime.ART_V6_0_1)))
-          .put(
-              "lang.Class.getFieldLjava_lang_String.Class_getField_A01",
-              match(artRuntimesUpTo(Runtime.ART_V6_0_1)))
-          .put(
-              "lang.String.replaceCC.String_replace_A01",
-              match(artRuntimesUpTo(Runtime.ART_V6_0_1)))
-          .put(
-              "lang.Package.isCompatibleWithLjava_lang_String.Package_isCompatibleWith_A02",
-              match(artRuntimesUpTo(Runtime.ART_V6_0_1)))
-          .put(
-              "lang.StringBuilder.appendF.StringBuilder_append_A01",
-              match(artRuntimesUpTo(Runtime.ART_V6_0_1)))
-          .put(
-              "lang.StringBuilder.insertIF.StringBuilder_insert_A01",
-              match(artRuntimesUpTo(Runtime.ART_V6_0_1)))
-          .put(
-              "lang.reflect.AccessibleObject.setAccessibleZ.AccessibleObject_setAccessible_A04",
-              match(artRuntimesUpToAndJava(Runtime.ART_V4_4_4)))
-          .put(
-              "lang.reflect.AccessibleObject.setAccessible_Ljava_lang_reflect_AccessibleObjectZ.AccessibleObject_setAccessible_A04",
-              match(artRuntimesUpToAndJava(Runtime.ART_V6_0_1)))
-          .put(
-              "lang.Character.UnicodeBlock.forName_java_lang_String.UnicodeBlock_forName_A03",
-              match(artRuntimesUpTo(Runtime.ART_V6_0_1)))
-          .put(
-              "lang.System.loadLjava_lang_String.System_load_A02",
-              match(artRuntimesUpTo(Runtime.ART_V6_0_1)))
-          .put("lang.Math.hypotDD.Math_hypot_A04", match(artRuntimesUpTo(Runtime.ART_V5_1_1)))
-          .put(
-              "math.BigInteger.probablePrimeIjava_util_Random.BigInteger_probablePrime_A01",
-              match(runtimes(Runtime.ART_V4_0_4)))
-          .put("lang.Math.sqrtD.Math_sqrt_A01", match(runtimes(Runtime.ART_V4_0_4)))
-          .put("lang.StrictMath.cbrtD.StrictMath_cbrt_A01", match(runtimes(Runtime.ART_V4_0_4)))
-          .put("lang.StrictMath.log10D.StrictMath_log10_A01", match(runtimes(Runtime.ART_V4_0_4)))
-          .put("lang.StrictMath.powDD.StrictMath_pow_A01", match(runtimes(Runtime.ART_V4_0_4)))
-          .put("lang.String.indexOfII.String_indexOf_A01", match(runtimes(Runtime.ART_V4_0_4)))
-          .put(
-              "lang.String.indexOfLjava_lang_StringI.String_indexOf_A01",
-              match(runtimes(Runtime.ART_V4_0_4)))
-          .put(
-              "lang.reflect.Array.getByteLjava_lang_ObjectI.Array_getByte_A03",
-              match(runtimes(Runtime.ART_V4_0_4)))
-          .put(
-              "lang.reflect.Array.getDoubleLjava_lang_ObjectI.Array_getDouble_A01",
-              match(runtimes(Runtime.ART_V4_0_4)))
-          .put(
-              "lang.reflect.Array.getDoubleLjava_lang_ObjectI.Array_getDouble_A03",
-              match(runtimes(Runtime.ART_V4_0_4)))
-          .put(
-              "lang.reflect.Array.getFloatLjava_lang_ObjectI.Array_getFloat_A01",
-              match(runtimes(Runtime.ART_V4_0_4)))
-          .put(
-              "lang.reflect.Array.getFloatLjava_lang_ObjectI.Array_getFloat_A03",
-              match(runtimes(Runtime.ART_V4_0_4)))
-          .put(
-              "lang.reflect.Array.getIntLjava_lang_ObjectI.Array_getInt_A01",
-              match(runtimes(Runtime.ART_V4_0_4)))
-          .put(
-              "lang.reflect.Array.getIntLjava_lang_ObjectI.Array_getInt_A03",
-              match(runtimes(Runtime.ART_V4_0_4)))
-          .put(
-              "lang.reflect.Array.getLongLjava_lang_ObjectI.Array_getLong_A01",
-              match(runtimes(Runtime.ART_V4_0_4)))
-          .put(
-              "lang.reflect.Array.getLongLjava_lang_ObjectI.Array_getLong_A03",
-              match(runtimes(Runtime.ART_V4_0_4)))
-          .put(
-              "lang.reflect.Array.getShortLjava_lang_ObjectI.Array_getShort_A03",
-              match(runtimes(Runtime.ART_V4_0_4)))
-          .put(
-              "lang.reflect.Array.setBooleanLjava_lang_ObjectIZ.Array_setBoolean_A03",
-              match(runtimes(Runtime.ART_V4_0_4)))
-          .put(
-              "lang.reflect.Array.setCharLjava_lang_ObjectIC.Array_setChar_A01",
-              match(runtimes(Runtime.ART_V4_0_4)))
-          .put(
-              "lang.reflect.Array.setLjava_lang_ObjectILjava_lang_Object.Array_set_A01",
-              match(runtimes(Runtime.ART_V4_0_4)))
-          .put(
-              "lang.reflect.Array.setLjava_lang_ObjectILjava_lang_Object.Array_set_A03",
-              match(runtimes(Runtime.ART_V4_0_4)))
-          .put(
-              "lang.reflect.Constructor.toString.Constructor_toString_A01",
-              match(runtimes(Runtime.ART_V4_0_4)))
-          .put(
-              "math.BigInteger.modPowLjava_math_BigIntegerLjava_math_Integer.BigInteger_modPow_A01",
-              match(runtimes(Runtime.ART_V4_0_4)))
-          .put(
-              "util.concurrent.LinkedBlockingDeque.drainToLjava_util_CollectionI.LinkedBlockingDeque_drainTo_A01",
-              match(runtimes(Runtime.ART_V4_0_4)))
-          .put(
-              "util.concurrent.LinkedBlockingQueue.drainToLjava_util_CollectionI.LinkedBlockingQueue_drainTo_A01",
-              match(runtimes(Runtime.ART_V4_0_4)))
-          .put("lang.Thread.stopLjava_lang_Throwable.Thread_stop_A02", cf())
-          .put(
-              "lang.AssertionError.ConstructorLjava_lang_Object.AssertionError_Constructor_A01",
-              match(runtimes(Runtime.ART_V4_0_4)))
-          .put("lang.RuntimePermission.Class.RuntimePermission_class_A13", cf())
-          .put("lang.Thread.stopLjava_lang_Throwable.Thread_stop_A01", cf())
-          .put("lang.Runtime.addShutdownHookLjava_lang_Thread.Runtime_addShutdownHook_A02", cf())
-          .put("lang.ThreadGroup.destroy.ThreadGroup_destroy_A04", cf())
-          .put("lang.ThreadGroup.setMaxPriorityI.ThreadGroup_setMaxPriority_A02", cf())
-          .put(
-              "lang.String.replaceFirstLjava_lang_StringLjava_lang_String.String_replaceFirst_A01",
-              cf())
-          .put(
-              "lang.String.replaceAllLjava_lang_StringLjava_lang_String.String_replaceAll_A01",
-              cf())
-          .put("lang.System.inheritedChannel.System_inheritedChannel_A01", cf())
-          // TODO(b/124842490): Failing with change to JDK 9 for the classfile backend.
-          .put("lang.Character.isSpaceCharC.Character_isSpaceChar_A01", cf())
-          .put("lang.Character.isSpaceCharI.Character_isSpaceChar_A01", cf())
-          .put("lang.Character.isWhitespaceI.Character_isWhitespace_A01", cf())
-          .put("lang.Class.getConstructors.Class_getConstructors_A01", cf())
-          .put("lang.Class.getPackage.Class_getPackage_A01", cf())
-          .put(
-              "lang.Class.getResourceAsStreamLjava_lang_String.Class_getResourceAsStream_A01", cf())
-          .put("lang.Class.getResourceLjava_lang_String.Class_getResource_A01", cf())
-          .put(
-              "lang.ClassLoader.defineClassLjava_lang_StringLjava_nio_ByteBufferLjava_security_ProtectionDomain.ClassLoader_defineClass_A01",
-              cf())
-          .put(
-              "lang.ClassLoader.defineClassLjava_lang_String_BII.ClassLoader_defineClass_A01", cf())
-          .put(
-              "lang.ClassLoader.defineClassLjava_lang_String_BIILjava_security_ProtectionDomain.ClassLoader_defineClass_A01",
-              cf())
-          .put("lang.ClassLoader.defineClass_BII.ClassLoader_defineClass_A01", cf())
-          .put(
-              "lang.ClassLoader.definePackageLjava_lang_String6Ljava_net_URL.ClassLoader_definePackage_A03",
-              cf())
-          .put("lang.Package.getImplementationVersion.Package_getImplementationVersion_A01", cf())
-          .put(
-              "lang.SecurityManager.checkAwtEventQueueAccess.SecurityManager_checkAwtEventQueueAccess_A01",
-              cf())
-          .put(
-              "lang.SecurityManager.checkSystemClipboardAccess.SecurityManager_checkSystemClipboardAccess_A01",
-              cf())
-          .put(
-              "lang.SecurityManager.checkTopLevelWindowLjava_lang_Object.SecurityManager_checkTopLevelWindow_A01",
-              cf())
-          .put("lang.StrictMath.toDegreesD.StrictMath_toDegrees_A01", cf())
-          .put("lang.StrictMath.toRadiansD.StrictMath_toRadians_A01", cf())
-          .put("lang.String.matchesLjava_lang_String.String_matches_A01", cf())
-          .put("lang.StringBuffer.append_CII.StringBuffer_append_A03", cf())
-          .put("lang.System.getProperties.System_getProperties_A02", cf())
-          .put("lang.System.getPropertyLjava_lang_String.System_getProperty_A02", cf())
-          .put(
-              "lang.System.getPropertyLjava_lang_StringLjava_lang_String.System_getProperty_A02",
-              cf())
-          .put("lang.Throwable.serialization.Throwable_serialization_A01", cf())
-          .put("lang.ref.ReferenceQueue.poll.ReferenceQueue_poll_A01", cf())
-          .put("lang.ref.ReferenceQueue.remove.ReferenceQueue_remove_A01", cf())
-          .put("lang.ref.ReferenceQueue.removeJ.ReferenceQueue_remove_A01", cf())
-          .put("lang.reflect.Constructor.toGenericString.Constructor_toGenericString_A01", cf())
-          .put("lang.reflect.Field.getGenericType.Field_getGenericType_A01", cf())
-          .put("lang.reflect.Field.toGenericString.Field_toGenericString_A01", cf())
-          .build(); // end of failuresToTriage
-
-  public static final Multimap<String, TestCondition> bugs =
-      new ImmutableListMultimap.Builder<String, TestCondition>()
-          // The following StringBuffer/StringBuilder tests fails because we remove, e.g.,
-          // new StringBuffer(-5) if it is dead code (but it should trow), see b/133745205
-          .put(
-              "lang.StringBuffer.ConstructorLjava_lang_String.StringBuffer_Constructor_A02",
-              match(R8DEX_COMPILER))
-          .put(
-              "lang.StringBuffer.ConstructorLjava_lang_CharSequence.StringBuffer_Constructor_A02",
-              match(R8DEX_COMPILER))
-          .put("lang.StringBuffer.ConstructorI.StringBuffer_Constructor_A02", match(R8DEX_COMPILER))
-          .put(
-              "lang.StringBuilder.ConstructorI.StringBuilder_Constructor_A02",
-              match(R8DEX_COMPILER))
-          .put(
-              "lang.StringBuilder.ConstructorLjava_lang_CharSequence.StringBuilder_Constructor_A02",
-              match(R8DEX_COMPILER))
-          .put(
-              "lang.StringBuilder.ConstructorLjava_lang_String.StringBuilder_Constructor_A02",
-              match(R8DEX_COMPILER))
-          .build();
-
-  public static final Multimap<String, TestCondition> flakyWhenRun =
-      new ImmutableListMultimap.Builder<String, TestCondition>()
-          .put("lang.Object.notifyAll.Object_notifyAll_A03", anyDexVm())
-          .put("lang.Object.notify.Object_notify_A03", anyDexVm())
-          .put(
-              "util.concurrent.ConcurrentSkipListSet.addLjava_lang_Object.ConcurrentSkipListSet_add_A01",
-              any())
-          .put("util.concurrent.SynchronousQueue.ConstructorZ", anyDexVm())
-          .put("lang.Thread.interrupt.Thread_interrupt_A04", anyDexVm())
-          .put(
-              "util.concurrent.SynchronousQueue.ConstructorZ.SynchronousQueue_Constructor_A01",
-              anyDexVm())
-          .put("lang.Thread.getState.Thread_getState_A01", anyDexVm())
-          .put("lang.Thread.join.Thread_join_A01", anyDexVm())
-          .put("lang.ThreadGroup.destroy.ThreadGroup_destroy_A01", match(JAVA_RUNTIME))
-          .put(
-              "util.concurrent.ScheduledThreadPoolExecutor.getTaskCount.ScheduledThreadPoolExecutor_getTaskCount_A01",
-              any())
-          .put(
-              "lang.ref.PhantomReference.clear.PhantomReference_clear_A01",
-              match(artRuntimesUpToAndJava(Runtime.ART_V4_4_4)))
-          .put(
-              "lang.ref.SoftReference.clear.SoftReference_clear_A01",
-              match(artRuntimesUpTo(Runtime.ART_V4_4_4)))
-          .put(
-              "lang.ref.WeakReference.clear.WeakReference_clear_A01",
-              match(and(artRuntimesUpTo(Runtime.ART_V4_4_4), JAVA_RUNTIME)))
-          .put(
-              "lang.ref.PhantomReference.isEnqueued.PhantomReference_isEnqueued_A01",
-              match(
-                  and(
-                      runtimes(Runtime.ART_V9_0_0, Runtime.ART_V8_1_0),
-                      artRuntimesUpTo(Runtime.ART_V4_4_4),
-                      JAVA_RUNTIME)))
-          .put("lang.ref.WeakReference.isEnqueued.WeakReference_isEnqueued_A01", any())
-          .put(
-              "lang.ref.WeakReference.enqueue.WeakReference_enqueue_A01",
-              match(artRuntimesUpTo(Runtime.ART_V4_4_4)))
-          .put(
-              "lang.ref.SoftReference.isEnqueued.SoftReference_isEnqueued_A01",
-              match(
-                  and(
-                      runtimes(Runtime.ART_V9_0_0, Runtime.ART_V8_1_0),
-                      artRuntimesUpTo(Runtime.ART_V4_4_4),
-                      JAVA_RUNTIME)))
-          .put(
-              "lang.ref.SoftReference.enqueue.SoftReference_enqueue_A01",
-              match(and(artRuntimesUpTo(Runtime.ART_V4_4_4), JAVA_RUNTIME)))
-          .put(
-              "lang.ref.ReferenceQueue.poll.ReferenceQueue_poll_A01",
-              match(artRuntimesUpTo(Runtime.ART_V4_4_4)))
-          .put("lang.Runtime.gc.Runtime_gc_A01", cf())
-          .put("lang.Runtime.runFinalizersOnExitZ.Runtime_runFinalizersOnExit_A01", cf())
-          .put(
-              "util.concurrent.AbstractExecutorService.invokeAllLjava_util_CollectionJLjava_util_concurrent_TimeUnit.AbstractExecutorService_invokeAll_A06",
-              match(runtimes(Runtime.ART_V4_0_4)))
-          .put(
-              "util.concurrent.Executors.newCachedThreadPoolLjava_util_concurrent_ThreadFactory.Executors_newCachedThreadPool_A01",
-              anyDexVm())
-          .put(
-              "util.concurrent.Executors.newCachedThreadPool.Executors_newCachedThreadPool_A01",
-              match(runtimes(Runtime.ART_V5_1_1)))
-          .put("lang.ref.SoftReference.get.SoftReference_get_A01", cf())
-          .put("lang.ref.WeakReference.get.WeakReference_get_A01", cf())
-          .build(); // end of flakyWhenRun
-
-  public static final Set<String> hasMissingClasses =
-      ImmutableSet.of(
-          "lang.RuntimePermission.Class.RuntimePermission_class_A01",
-          "lang.RuntimePermission.Class.RuntimePermission_class_A03",
-          "lang.RuntimePermission.Class.RuntimePermission_class_A04",
-          "lang.RuntimePermission.Class.RuntimePermission_class_A05",
-          "lang.RuntimePermission.Class.RuntimePermission_class_A06",
-          "lang.RuntimePermission.Class.RuntimePermission_class_A07",
-          "lang.RuntimePermission.Class.RuntimePermission_class_A08",
-          "lang.RuntimePermission.Class.RuntimePermission_class_A09",
-          "lang.RuntimePermission.Class.RuntimePermission_class_A11",
-          "lang.RuntimePermission.Class.RuntimePermission_class_A12",
-          "lang.RuntimePermission.Class.RuntimePermission_class_A14",
-          "lang.RuntimePermission.Class.RuntimePermission_class_A15",
-          "lang.RuntimePermission.Class.RuntimePermission_class_A19",
-          "lang.RuntimePermission.Class.RuntimePermission_class_A20",
-          "lang.RuntimePermission.Class.RuntimePermission_class_A21",
-          "lang.RuntimePermission.Class.RuntimePermission_class_A22",
-          "lang.RuntimePermission.Class.RuntimePermission_class_A24",
-          "lang.RuntimePermission.Class.RuntimePermission_class_A25",
-          "lang.reflect.Proxy.serialization.Proxy_serialization_A02");
-
-  public static final Multimap<String, TestCondition> timeoutsWhenRun =
-      new ImmutableListMultimap.Builder<String, TestCondition>()
-          .put("lang.Thread.interrupt.Thread_interrupt_A01", anyDexVm())
-          .put("lang.Thread.resume.Thread_resume_A01", anyDexVm())
-          .put("lang.Thread.stop.Thread_stop_A01", anyDexVm())
-          .put("lang.Thread.suspend.Thread_suspend_A01", anyDexVm())
-          .put(
-              "lang.Thread.ConstructorLjava_lang_ThreadGroupLjava_lang_RunnableLjava_lang_StringJ.Thread_Constructor_A04",
-              anyDexVm())
-          .put(
-              "lang.Thread.ConstructorLjava_lang_ThreadGroupLjava_lang_RunnableLjava_lang_StringJ.Thread_Constructor_A03",
-              anyDexVm())
-          .put(
-              "lang.Thread.ConstructorLjava_lang_ThreadGroupLjava_lang_RunnableLjava_lang_StringJ.Thread_Constructor_A05",
-              anyDexVm())
-          .put("lang.Thread.setNameLjava_lang_String.Thread_setName_A02", anyDexVm())
-          .put("lang.Thread.stop.Thread_stop_A02", anyDexVm())
-          .put(
-              "lang.Thread.ConstructorLjava_lang_ThreadGroupLjava_lang_RunnableLjava_lang_String.Thread_Constructor_A02",
-              anyDexVm())
-          .put(
-              "lang.Thread.ConstructorLjava_lang_ThreadGroupLjava_lang_RunnableLjava_lang_String.Thread_Constructor_A03",
-              anyDexVm())
-          .put("lang.Thread.getStackTrace.Thread_getStackTrace_A03", anyDexVm())
-          .put(
-              "lang.Thread.setDefaultUncaughtExceptionHandler.Thread_setDefaultUncaughtExceptionHandler_A02",
-              anyDexVm())
-          .put("lang.Thread.checkAccess.Thread_checkAccess_A01", anyDexVm())
-          .put(
-              "lang.Thread.ConstructorLjava_lang_ThreadGroupLjava_lang_RunnableLjava_lang_String.Thread_Constructor_A04",
-              anyDexVm())
-          .put(
-              "lang.Thread.setUncaughtExceptionHandler.Thread_setUncaughtExceptionHandler_A02",
-              anyDexVm())
-          .put("lang.Thread.stopLjava_lang_Throwable.Thread_stop_A01", anyDexVm())
-          .put("lang.Thread.getAllStackTraces.Thread_getAllStackTraces_A02", anyDexVm())
-          .put(
-              "lang.Thread.setContextClassLoaderLjava_lang_ClassLoader.Thread_setContextClassLoader_A02",
-              anyDexVm())
-          .put("lang.Thread.setPriorityI.Thread_setPriority_A02", anyDexVm())
-          .put("lang.Thread.stopLjava_lang_Throwable.Thread_stop_A02", anyDexVm())
-          .put(
-              "lang.Runtime.execLjava_lang_String_Ljava_lang_StringLjava_io_File.Runtime_exec_A04",
-              anyDexVm())
-          .put("lang.Thread.getContextClassLoader.Thread_getContextClassLoader_A02", anyDexVm())
-          .put("lang.ThreadGroup.suspend.ThreadGroup_suspend_A01", cf())
-          .put("lang.ThreadGroup.suspend.ThreadGroup_suspend_A02", anyDexVm())
-          .put("lang.Thread.setDaemonZ.Thread_setDaemon_A03", anyDexVm())
-          .put("lang.ProcessBuilder.environment.ProcessBuilder_environment_A07", anyDexVm())
-          .put(
-              "lang.Runtime.exec_Ljava_lang_String_Ljava_lang_StringLjava_io_File.Runtime_exec_A04",
-              anyDexVm())
-          .put("lang.Runtime.execLjava_lang_String_Ljava_lang_String.Runtime_exec_A04", anyDexVm())
-          .put("lang.Runtime.exec_Ljava_lang_String.Runtime_exec_A04", anyDexVm())
-          .put("lang.Runtime.execLjava_lang_String.Runtime_exec_A04", anyDexVm())
-          .put("lang.System.clearPropertyLjava_lang_String.System_clearProperty_A03", anyDexVm())
-          .put("lang.System.getSecurityManager.System_getSecurityManager_A01", anyDexVm())
-          .put("lang.System.setInLjava_io_InputStream.System_setIn_A02", anyDexVm())
-          .put("lang.System.setOutLjava_io_PrintStream.System_setOut_A02", anyDexVm())
-          .put("lang.ThreadGroup.destroy.ThreadGroup_destroy_A04", anyDexVm())
-          .put("lang.ThreadGroup.enumerate_ThreadGroupZ.ThreadGroup_enumerate_A03", anyDexVm())
-          .put("lang.ThreadGroup.enumerate_Thread.ThreadGroup_enumerate_A03", anyDexVm())
-          .put("lang.ThreadGroup.enumerate_ThreadZ.ThreadGroup_enumerate_A03", anyDexVm())
-          .put("lang.ThreadGroup.interrupt.ThreadGroup_interrupt_A02", anyDexVm())
-          .put("lang.ThreadGroup.resume.ThreadGroup_resume_A02", anyDexVm())
-          .put("lang.ThreadGroup.setMaxPriorityI.ThreadGroup_setMaxPriority_A02", anyDexVm())
-          .put("lang.Runtime.exec_Ljava_lang_String_Ljava_lang_String.Runtime_exec_A04", anyDexVm())
-          .put("lang.System.getenvLjava_lang_String.System_getenv_A03", anyDexVm())
-          .put(
-              "lang.System.setPropertyLjava_lang_StringLjava_lang_String.System_setProperty_A02",
-              anyDexVm())
-          .put("lang.ThreadGroup.enumerate_ThreadGroup.ThreadGroup_enumerate_A03", anyDexVm())
-          .put("lang.ThreadGroup.getParent.ThreadGroup_getParent_A02", anyDexVm())
-          .put("lang.ThreadGroup.setDaemonZ.ThreadGroup_setDaemon_A02", anyDexVm())
-          .put("lang.ThreadGroup.stop.ThreadGroup_stop_A02", any())
-          .put("lang.Class.getSuperclass.Class_getSuperclass_A01", anyDexVm())
-          .put("lang.System.getenv.System_getenv_A03", anyDexVm())
-          .put("lang.System.inheritedChannel.System_inheritedChannel_A01", anyDexVm())
-          .put(
-              "util.concurrent.ArrayBlockingQueue.containsLjava_lang_Object.ArrayBlockingQueue_contains_A01",
-              anyDexVm())
-          .put(
-              "lang.System.arraycopyLjava_lang_ObjectILjava_lang_ObjectII.System_arraycopy_A03",
-              anyDexVm())
-          .put("lang.System.setErrLjava_io_PrintStream.System_setErr_A02", anyDexVm())
-          .put(
-              "util.concurrent.ArrayBlockingQueue.containsLjava_lang_Object.ArrayBlockingQueue_contains_A01",
-              anyDexVm())
-          .put(
-              "lang.System.setSecurityManagerLjava_lang_SecurityManager.System_setSecurityManager_A01",
-              anyDexVm())
-          .put(
-              "util.concurrent.ArrayBlockingQueue.containsLjava_lang_Object.ArrayBlockingQueue_contains_A01",
-              anyDexVm())
-          .put(
-              "util.concurrent.ArrayBlockingQueue.containsLjava_lang_Object.ArrayBlockingQueue_contains_A01",
-              anyDexVm())
-          .put(
-              "lang.System.setPropertiesLjava_util_Properties.System_setProperties_A01", anyDexVm())
-          .put(
-              "util.concurrent.CopyOnWriteArrayList.ConstructorLjava_util_Collection.CopyOnWriteArrayList_Constructor_A02",
-              anyDexVm())
-          .put("util.concurrent.CyclicBarrier.reset.CyclicBarrier_reset_A03", any())
-          .put("lang.System.clearPropertyLjava_lang_String.System_clearProperty_A01", anyDexVm())
-          .put("lang.System.getenv.System_getenv_A04", anyDexVm())
-          .put("lang.RuntimePermission.Class.RuntimePermission_class_A02", anyDexVm())
-          .put("lang.RuntimePermission.Class.RuntimePermission_class_A13", anyDexVm())
-          .build(); // end of timeoutsWhenRun
-
-  public static final Multimap<String, TestCondition> requiresInliningDisabled =
-      new ImmutableListMultimap.Builder<String, TestCondition>()
-          .put("lang.Throwable.printStackTrace.Throwable_printStackTrace_A01", match(R8_COMPILER))
-          .put("lang.Throwable.printStackTraceLjava_io_PrintWriter.Throwable_printStackTrace_A01",
-              match(R8_COMPILER))
-          .put("lang.Throwable.printStackTraceLjava_io_PrintStream.Throwable_printStackTrace_A01",
-              match(R8_COMPILER))
-          .put("lang.ref.SoftReference.isEnqueued.SoftReference_isEnqueued_A01", match(R8_COMPILER))
-          .put("lang.ref.WeakReference.isEnqueued.WeakReference_isEnqueued_A01", match(R8_COMPILER))
-          .put("lang.StackTraceElement.getMethodName.StackTraceElement_getMethodName_A01",
-              match(R8_COMPILER))
-          .put("lang.Thread.dumpStack.Thread_dumpStack_A01", match(R8_COMPILER))
-          .build();
-
-  public static final Set<String> compilationFailsWithAsmMethodTooLarge = ImmutableSet.of();
-
-  private static boolean testMatch(
-      Multimap<String, TestCondition> testConditions,
-      String name,
-      CompilerUnderTest compilerUnderTest,
-      Runtime runtime,
-      CompilationMode compilationMode) {
-    Collection<TestCondition> entries = testConditions.get(name);
-    for (TestCondition entry : entries) {
-      if (entry.test(DexTool.NONE, compilerUnderTest, runtime, compilationMode)) {
-        return true;
-      }
-    }
-    return false;
-  }
-
-  public static <T> T getExpectedOutcome(
-      String name,
-      CompilerUnderTest compilerUnderTest,
-      Runtime runtime,
-      CompilationMode compilationMode,
-      BiFunction<Outcome, Boolean, T> consumer) {
-
-    Outcome outcome = null;
-
-    if (testMatch(failuresToTriage, name, compilerUnderTest, runtime, compilationMode)) {
-      outcome = Outcome.FAILS_WHEN_RUN;
-    }
-    if (testMatch(bugs, name, compilerUnderTest, runtime, compilationMode)) {
-      outcome = Outcome.FAILS_WHEN_RUN;
-    }
-    if (testMatch(timeoutsWhenRun, name, compilerUnderTest, runtime, compilationMode)) {
-      assert outcome == null;
-      outcome = Outcome.TIMEOUTS_WHEN_RUN;
-    }
-    if (testMatch(flakyWhenRun, name, compilerUnderTest, runtime, compilationMode)) {
-      assert outcome == null;
-      outcome = Outcome.FLAKY_WHEN_RUN;
-    }
-    if (outcome == null) {
-      outcome = Outcome.PASSES;
-    }
-    boolean disableInlining =
-        testMatch(requiresInliningDisabled, name, compilerUnderTest, runtime, compilationMode);
-    return consumer.apply(outcome, disableInlining);
-  }
-}
diff --git a/src/test/java/com/android/tools/r8/KotlinTestBase.java b/src/test/java/com/android/tools/r8/KotlinTestBase.java
index 8dd8a44..d4f443a 100644
--- a/src/test/java/com/android/tools/r8/KotlinTestBase.java
+++ b/src/test/java/com/android/tools/r8/KotlinTestBase.java
@@ -51,6 +51,10 @@
     this.kotlinParameters = kotlinParameters;
   }
 
+  public static CfRuntime getKotlincHostRuntime(TestRuntime runtime) {
+    return runtime.isCf() ? runtime.asCf() : TestRuntime.getCheckedInJdk9();
+  }
+
   protected static List<Path> getKotlinFilesInTestPackage(Package pkg) throws IOException {
     String folder = DescriptorUtils.getBinaryNameFromJavaType(pkg.getName());
     return Files.walk(Paths.get(ToolHelper.TESTS_DIR, "java", folder))
diff --git a/src/test/java/com/android/tools/r8/L8TestBuilder.java b/src/test/java/com/android/tools/r8/L8TestBuilder.java
index 7a42ab1..2ff60ba 100644
--- a/src/test/java/com/android/tools/r8/L8TestBuilder.java
+++ b/src/test/java/com/android/tools/r8/L8TestBuilder.java
@@ -184,7 +184,10 @@
         .inspect(
             inspector ->
                 inspector.forAllClasses(
-                    clazz -> assertTrue(clazz.getFinalName().startsWith("j$."))));
+                    clazz ->
+                        assertTrue(
+                            clazz.getFinalName().startsWith("j$.")
+                                || clazz.getFinalName().startsWith("java."))));
   }
 
   private Collection<Path> getProgramFiles() {
diff --git a/src/test/java/com/android/tools/r8/R8RunArtTestsTest.java b/src/test/java/com/android/tools/r8/R8RunArtTestsTest.java
index 352e12b..c860721 100644
--- a/src/test/java/com/android/tools/r8/R8RunArtTestsTest.java
+++ b/src/test/java/com/android/tools/r8/R8RunArtTestsTest.java
@@ -6,11 +6,8 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.fail;
 
-import com.android.tools.r8.JctfTestSpecifications.Outcome;
 import com.android.tools.r8.TestCondition.Runtime;
 import com.android.tools.r8.TestCondition.RuntimeSet;
-import com.android.tools.r8.TestRuntime.CfVm;
-import com.android.tools.r8.TestRuntime.DexRuntime;
 import com.android.tools.r8.ToolHelper.ArtCommandBuilder;
 import com.android.tools.r8.ToolHelper.DexVm;
 import com.android.tools.r8.ToolHelper.DexVm.Kind;
@@ -36,14 +33,12 @@
 import com.google.common.collect.ObjectArrays;
 import com.google.common.collect.Sets;
 import java.io.File;
-import java.io.FileNotFoundException;
 import java.io.FileOutputStream;
 import java.io.IOException;
 import java.nio.file.Files;
 import java.nio.file.Path;
 import java.nio.file.Paths;
 import java.util.ArrayList;
-import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.HashMap;
@@ -53,7 +48,6 @@
 import java.util.Objects;
 import java.util.Set;
 import java.util.concurrent.ExecutionException;
-import java.util.function.BiFunction;
 import java.util.function.Consumer;
 import java.util.jar.JarEntry;
 import java.util.jar.JarOutputStream;
@@ -110,13 +104,6 @@
           DexVm.Version.V12_0_0,
           DexVm.Version.V13_MASTER);
 
-  // Input jar for jctf tests.
-  private static final String JCTF_COMMON_JAR = "build/libs/jctfCommon.jar";
-
-  // Parent dir for on-the-fly compiled jctf dex output.
-  private static final String JCTF_TESTS_PREFIX = "build/classes/java/jctfTests";
-  private static final String JCTF_TESTS_LIB_PREFIX =
-      JCTF_TESTS_PREFIX + "/com/google/jctf/test/lib";
   private static final String JUNIT_TEST_RUNNER = "org.junit.runner.JUnitCore";
   private static final String JUNIT_JAR = "third_party/junit/junit-4.13-beta-2.jar";
   private static final String HAMCREST_JAR =
@@ -1097,27 +1084,6 @@
       "663-odd-dex-size4" // No input class files
   );
 
-  // Some JCTF test cases require test classes from other tests. These are listed here.
-  private static final Map<String, List<String>> jctfTestsExternalClassFiles =
-      new ImmutableMap.Builder<String, List<String>>()
-          .put("lang.RuntimePermission.Class.RuntimePermission_class_A13",
-              new ImmutableList.Builder<String>()
-                  .add("lang/Thread/stop/Thread_stop_A02.class")
-                  .add("lang/Thread/stopLjava_lang_Throwable/Thread_stop_A02.class")
-                  .build())
-          .put("lang.RuntimePermission.Class.RuntimePermission_class_A02",
-              new ImmutableList.Builder<String>()
-                  .add("lang/Class/getClassLoader/Class_getClassLoader_A03.class")
-                  .add("lang/ClassLoader/getParent/ClassLoader_getParent_A02.class")
-                  .add("lang/Thread/getContextClassLoader/Thread_getContextClassLoader_A02.class")
-                  .add("lang/Runtime/exitI/Runtime_exit_A02.class")
-                  .build())
-          .put("lang.Runtime.exitI.Runtime_exit_A03",
-              new ImmutableList.Builder<String>()
-                  .add("lang/Runtime/exitI/Runtime_exit_A02.class")
-                  .build())
-          .build();
-
   // Tests to skip on some conditions
   private static final Multimap<String, TestCondition> testToSkip =
       new ImmutableListMultimap.Builder<String, TestCondition>()
@@ -1346,65 +1312,6 @@
       this.configuration = configuration;
     }
 
-    TestSpecification(
-        String name,
-        DexTool dexTool,
-        File directory,
-        boolean skipRun,
-        boolean failsOnRun,
-        boolean disableInlining,
-        boolean hasMissingClasses,
-        DexVm dexVm) {
-      this(
-          name,
-          dexTool,
-          directory,
-          skipRun,
-          ToolHelper.isWindows() && dexVm.getKind() == Kind.HOST,
-          false,
-          failsOnRun,
-          false,
-          false,
-          null,
-          false,
-          false,
-          disableInlining,
-          true, // Disable class inlining for JCTF tests.
-          hasMissingClasses,
-          true, // Disable desugaring for JCTF tests.
-          ImmutableList.of(),
-          null);
-    }
-
-    TestSpecification(
-        String name,
-        DexTool dexTool,
-        File directory,
-        boolean skipRun,
-        boolean failsOnRun,
-        boolean disableInlining,
-        boolean hasMissingClasses) {
-      this(
-          name,
-          dexTool,
-          directory,
-          skipRun,
-          false,
-          false,
-          failsOnRun,
-          false,
-          false,
-          null,
-          false,
-          false,
-          disableInlining,
-          true, // Disable class inlining for JCTF tests.
-          hasMissingClasses,
-          true, // Disable desugaring for JCTF tests.
-          ImmutableList.of(),
-          null);
-    }
-
     public File resolveFile(String name) {
       return directory.toPath().resolve(name).toFile();
     }
@@ -1874,75 +1781,6 @@
   }
 
 
-  private ArrayList<File> getJctfTestAuxClassFiles(File classFile) {
-    // Collect additional files from the same directory with file names like
-    // <dir>/<filename_wo_ext>$*.class and <dir>/<filename_wo_ext>_*.class
-    String classFileString = classFile.toString();
-    assert classFileString.endsWith(".class");
-
-    String auxClassFileBase =
-        new File(
-            classFileString.substring(0, classFileString.length() - ".class".length()))
-            .getName();
-
-    ArrayList<File> auxClassFiles = new ArrayList<>();
-
-    File[] files = classFile.getParentFile()
-        .listFiles(
-            (File file) -> isAuxClassFile(file.getName(), auxClassFileBase));
-    if (files != null) {
-      auxClassFiles.addAll(Arrays.asList(files));
-    }
-
-    if (auxClassFileBase.matches(".*[A-Z]\\d\\d")) {
-      // Also collect all the files in this directory that doesn't match this pattern
-      // They will be helper classes defined in one of the test class files but we don't know in
-      // which one, so we just add them to all tests.
-      final int SUFFIX_LENGTH_TO_STRIP = 3; // one letter (usually 'A' and two digits)
-      String testClassFilePattern =
-          auxClassFileBase.substring(0, auxClassFileBase.length() - SUFFIX_LENGTH_TO_STRIP)
-              + "[A-Z]\\d\\d.*\\.class";
-      files = classFile.getParentFile()
-          .listFiles(
-              (File file) -> file.getName().matches(".*\\.class") && !file.getName()
-                  .matches(testClassFilePattern));
-      if (files != null) {
-        auxClassFiles.addAll(Arrays.asList(files));
-      }
-    }
-
-    return auxClassFiles;
-  }
-
-  private static BiFunction<Outcome, Boolean, TestSpecification> jctfOutcomeToSpecification(
-      String name, DexTool dexTool, File resultDir, DexVm dexVm) {
-    return (outcome, noInlining) ->
-        new TestSpecification(
-            name,
-            dexTool,
-            resultDir,
-            outcome == JctfTestSpecifications.Outcome.TIMEOUTS_WHEN_RUN
-                || outcome == JctfTestSpecifications.Outcome.FLAKY_WHEN_RUN,
-            outcome == JctfTestSpecifications.Outcome.FAILS_WHEN_RUN,
-            noInlining,
-            JctfTestSpecifications.hasMissingClasses.contains(name),
-            dexVm);
-  }
-
-  private static BiFunction<Outcome, Boolean, TestSpecification> jctfOutcomeToSpecificationJava(
-      String name, File resultDir) {
-    return (outcome, noInlining) ->
-        new TestSpecification(
-            name,
-            DexTool.NONE,
-            resultDir,
-            outcome == JctfTestSpecifications.Outcome.TIMEOUTS_WHEN_RUN
-                || outcome == JctfTestSpecifications.Outcome.FLAKY_WHEN_RUN,
-            outcome == JctfTestSpecifications.Outcome.FAILS_WHEN_RUN,
-            noInlining,
-            JctfTestSpecifications.hasMissingClasses.contains(name));
-  }
-
   private static Runtime getRuntime(TestRuntime vm) {
     if (vm.isCf()) {
       return Runtime.JAVA;
@@ -1998,321 +1836,6 @@
     }
   }
 
-  protected void runJctfTest(
-      CompilerUnderTest compilerUnderTest, String classFilePath, String fullClassName)
-      throws IOException, CompilationFailedException {
-    VmErrors vmErrors = runJctfTestCore(compilerUnderTest, classFilePath, fullClassName);
-    if (vmErrors.message != null) {
-      throw new RuntimeException(vmErrors.message.toString());
-    }
-  }
-
-  private VmErrors runJctfTestCore(
-      CompilerUnderTest compilerUnderTest, String classFilePath, String fullClassName)
-      throws IOException, CompilationFailedException {
-    VmErrors vmErrors = new VmErrors();
-    List<TestRuntime> vms = new ArrayList<>();
-    if (compilerUnderTest == CompilerUnderTest.R8CF) {
-      // TODO(b/135411839): Run on all java runtimes.
-      vms.add(TestRuntime.getDefaultJavaRuntime());
-    } else {
-      for (DexVm vm : TestParametersBuilder.getAvailableDexVms()) {
-        // TODO(144966342): Disabled for triaging failures
-        if (vm.getVersion() == DexVm.Version.V10_0_0) {
-          System.out.println("Running on 10.0.0 is disabled, see b/144966342");
-          continue;
-        }
-        if (vm.getVersion() == DexVm.Version.V12_0_0
-            || vm.getVersion() == DexVm.Version.V13_MASTER) {
-          System.out.println("Running on 12.0.0 or V13_MASTER is disabled, see b/197078995");
-          continue;
-        }
-        vms.add(new DexRuntime(vm));
-      }
-    }
-
-    CompilerUnderTest firstCompilerUnderTest =
-        compilerUnderTest == CompilerUnderTest.R8_AFTER_D8
-            ? CompilerUnderTest.D8
-            : compilerUnderTest;
-    CompilationMode compilationMode = defaultCompilationMode(compilerUnderTest);
-
-    List<VmSpec> vmSpecs = new ArrayList<>();
-    for (TestRuntime vm : vms) {
-      File resultDir =
-          temp.newFolder(
-              firstCompilerUnderTest.toString().toLowerCase() + "-output-" + vm.toString());
-
-      TestSpecification specification =
-          JctfTestSpecifications.getExpectedOutcome(
-              name,
-              firstCompilerUnderTest,
-              getRuntime(vm),
-              compilationMode,
-              compilerUnderTest == CompilerUnderTest.R8CF
-                  ? jctfOutcomeToSpecificationJava(name, resultDir)
-                  : jctfOutcomeToSpecification(name, DexTool.NONE, resultDir, vm.asDex().getVm()));
-
-      if (!specification.skipTest) {
-        vmSpecs.add(new VmSpec(vm, specification));
-      }
-    }
-
-    if (vmSpecs.isEmpty()) {
-      return vmErrors;
-    }
-
-    File classFile = new File(JCTF_TESTS_PREFIX + "/" + classFilePath);
-    if (!classFile.exists()) {
-      throw new FileNotFoundException(
-          "Class file for Jctf test not found: \"" + classFile.toString() + "\".");
-    }
-
-    ArrayList<File> classFiles = new ArrayList<>();
-    classFiles.add(classFile);
-
-    // some tests need files from other tests
-    int langIndex = fullClassName.indexOf(".java.");
-    assert langIndex >= 0;
-    List<String> externalClassFiles = jctfTestsExternalClassFiles
-        .get(fullClassName.substring(langIndex + ".java.".length()));
-
-    if (externalClassFiles != null) {
-      for (String s : externalClassFiles) {
-        classFiles.add(new File(JCTF_TESTS_LIB_PREFIX + "/java/" + s));
-      }
-    }
-
-    ArrayList<File> allClassFiles = new ArrayList<>();
-
-    for (File f : classFiles) {
-      allClassFiles.add(f);
-      allClassFiles.addAll(getJctfTestAuxClassFiles(f));
-    }
-
-    File jctfCommonFile = new File(JCTF_COMMON_JAR);
-    if (!jctfCommonFile.exists()) {
-      throw new FileNotFoundException(
-          "Jar file of Jctf tests common code not found: \"" + jctfCommonFile.toString() + "\".");
-    }
-
-    File junitFile = new File(JUNIT_JAR);
-    if (!junitFile.exists()) {
-      throw new FileNotFoundException(
-          "Junit Jar not found: \"" + junitFile.toString() + "\".");
-    }
-
-    File hamcrestFile = new File(HAMCREST_JAR);
-    if (!hamcrestFile.exists()) {
-      throw new FileNotFoundException(
-          "Hamcrest Jar not found: \"" + hamcrestFile.toString() + "\".");
-    }
-
-    // allClassFiles may contain duplicated files, that's why the HashSet
-    Set<String> fileNames = new HashSet<>();
-
-    fileNames.addAll(Arrays.asList(
-        jctfCommonFile.getCanonicalPath(),
-        junitFile.getCanonicalPath(),
-        hamcrestFile.getCanonicalPath()
-    ));
-
-    for (File f : allClassFiles) {
-      fileNames.add(f.getCanonicalPath());
-    }
-
-    if (compilerUnderTest == CompilerUnderTest.R8CF) {
-      assert vmSpecs.size() == 1
-          : "Running the same test on multiple JVMs should share the same build.";
-      for (VmSpec vmSpec : vmSpecs) {
-        runJctfTestDoRunOnJava(
-            fileNames, vmSpec.spec, fullClassName, compilationMode, vmSpec.vm.asCf().getVm());
-      }
-      return vmErrors;
-    }
-
-    CompilationOptions compilationOptions = null;
-    File compiledDir = temp.newFolder();
-    for (VmSpec vmSpec : vmSpecs) {
-      CompilationOptions thisOptions = new CompilationOptions(vmSpec.spec);
-      if (compilationOptions == null) {
-        compilationOptions = thisOptions;
-        executeCompilerUnderTest(
-            firstCompilerUnderTest,
-            fileNames,
-            compiledDir.getAbsolutePath(),
-            compilationMode,
-            compilationOptions);
-      } else {
-        // For now compile options don't change across vms.
-        assert compilationOptions.equals(thisOptions);
-      }
-      Files.copy(
-          compiledDir.toPath().resolve("classes.dex"),
-          vmSpec.spec.directory.toPath().resolve("classes.dex"));
-
-      AssertionError vmError = null;
-      try {
-        runJctfTestDoRunOnArt(fileNames, vmSpec.spec, fullClassName, vmSpec.vm.asDex().getVm());
-      } catch (AssertionError e) {
-        vmError = e;
-      }
-      if (vmSpec.spec.failsOnRun && vmError == null) {
-        vmErrors.addShouldHaveFailedError(firstCompilerUnderTest, vmSpec.vm);
-      } else if (!vmSpec.spec.failsOnRun && vmError != null) {
-        vmErrors.addFailedOnRunError(firstCompilerUnderTest, vmSpec.vm, vmError);
-      }
-    }
-
-    if (compilerUnderTest != CompilerUnderTest.R8_AFTER_D8) {
-      return vmErrors;
-    }
-
-    // Second pass (R8), if R8_AFTER_D8.
-    CompilationOptions r8CompilationOptions = null;
-    File r8CompiledDir = temp.newFolder();
-    for (VmSpec vmSpec : vmSpecs) {
-      if (vmSpec.spec.failsOnRun || vmErrors.failedVms.contains(vmSpec.vm)) {
-        continue;
-      }
-      File r8ResultDir = temp.newFolder("r8-output-" + vmSpec.vm.toString());
-      TestSpecification specification =
-          JctfTestSpecifications.getExpectedOutcome(
-              name,
-              CompilerUnderTest.R8_AFTER_D8,
-              getRuntime(vmSpec.vm),
-              CompilationMode.RELEASE,
-              jctfOutcomeToSpecification(name, DexTool.DX, r8ResultDir, vmSpec.vm.asDex().getVm()));
-      if (specification.skipTest) {
-        continue;
-      }
-      CompilationOptions thisOptions = new CompilationOptions(specification);
-      if (r8CompilationOptions == null) {
-        r8CompilationOptions = thisOptions;
-        executeCompilerUnderTest(
-            CompilerUnderTest.R8,
-            Collections.singletonList(compiledDir.toPath().resolve("classes.dex").toString()),
-            r8CompiledDir.getAbsolutePath(),
-            CompilationMode.RELEASE,
-            r8CompilationOptions);
-      } else {
-        // For now compile options don't change across vms.
-        assert r8CompilationOptions.equals(thisOptions);
-      }
-      Files.copy(
-          r8CompiledDir.toPath().resolve("classes.dex"),
-          specification.directory.toPath().resolve("classes.dex"));
-      try {
-        runJctfTestDoRunOnArt(fileNames, specification, fullClassName, vmSpec.vm.asDex().getVm());
-      } catch (AssertionError e) {
-        if (!specification.failsOnRun) {
-          vmErrors.addFailedOnRunError(CompilerUnderTest.R8, vmSpec.vm, e);
-        }
-      }
-    }
-    return vmErrors;
-  }
-
-  private void runJctfTestDoRunOnArt(
-      Collection<String> fileNames,
-      TestSpecification specification,
-      String fullClassName,
-      DexVm dexVm)
-      throws IOException {
-    if (!ToolHelper.artSupported() && !ToolHelper.dealsWithGoldenFiles()) {
-      return;
-    }
-
-    File processedFile;
-
-    // Collect the generated dex files.
-    File[] outputFiles =
-        specification.directory.listFiles((File file) -> file.getName().endsWith(".dex"));
-    assert outputFiles.length == 1;
-    processedFile = outputFiles[0];
-
-    boolean compileOnly = System.getProperty("jctf_compile_only", "0").equals("1");
-    if (compileOnly || specification.skipRun) {
-      if (ToolHelper.isDex2OatSupported()) {
-        // verify dex code instead of running it
-        Path oatFile = temp.getRoot().toPath().resolve("all.oat");
-        ToolHelper.runDex2Oat(processedFile.toPath(), oatFile);
-      }
-      return;
-    }
-
-    ArtCommandBuilder builder = buildArtCommand(processedFile, specification, dexVm);
-    if (dexVm.isNewerThan(DexVm.ART_4_4_4_HOST)) {
-      builder.appendArtOption("-Ximage:/system/non/existent/image.art");
-      builder.appendArtOption("-Xnoimage-dex2oat");
-    }
-    for (String s : ToolHelper.getBootLibs(dexVm)) {
-      builder.appendBootClasspath(new File(s).getCanonicalPath());
-    }
-    builder.setMainClass(JUNIT_TEST_RUNNER);
-    builder.appendProgramArgument(fullClassName);
-
-    try {
-      ToolHelper.runArt(builder);
-    } catch (AssertionError e) {
-      addDexInformationToVerificationError(fileNames, processedFile,
-          specification.resolveFile("classes.dex"), e);
-      throw e;
-    }
-  }
-
-  private void runJctfTestDoRunOnJava(
-      Collection<String> fileNames,
-      TestSpecification specification,
-      String fullClassName,
-      CompilationMode mode,
-      CfVm vm)
-      throws IOException, CompilationFailedException {
-    assert TestParametersBuilder.isSystemJdk(vm);
-    if (JctfTestSpecifications.compilationFailsWithAsmMethodTooLarge.contains(specification.name)) {
-      expectException(org.objectweb.asm.MethodTooLargeException.class);
-    }
-    executeCompilerUnderTest(
-        CompilerUnderTest.R8CF,
-        fileNames,
-        specification.directory.getAbsolutePath(),
-        mode,
-        new CompilationOptions(specification));
-
-    boolean compileOnly = System.getProperty("jctf_compile_only", "0").equals("1");
-
-    if (compileOnly || specification.skipRun) {
-      return;
-    }
-
-    if (specification.failsOnRun) {
-      expectException(AssertionError.class);
-    }
-
-    // Some tests rely on an OutOfMemoryError being thrown (fx MemoryHog.getBigArray()). To ensure
-    // compatible test results locally and externally, we need to synchronize the max heap size when
-    // running the test.
-    ProcessResult result =
-        ToolHelper.runJava(
-            specification.directory.toPath(),
-            "-Xmx" + ToolHelper.BOT_MAX_HEAP_SIZE,
-            JUNIT_TEST_RUNNER,
-            fullClassName);
-
-    if (result.exitCode != 0) {
-      throw new AssertionError(
-          "Test failed on java.\nSTDOUT >>>\n"
-              + result.stdout
-              + "\n<<< STDOUT\nSTDERR >>>\n"
-              + result.stderr
-              + "\n<<< STDERR\n");
-    }
-
-    if (specification.failsOnRun) {
-      System.err.println("Should have failed run with java.");
-    }
-  }
-
   protected void runArtTest(DexVm dexVm, CompilerUnderTest compilerUnderTest) throws Throwable {
     CompilerUnderTest firstCompilerUnderTest =
         compilerUnderTest == CompilerUnderTest.R8_AFTER_D8
diff --git a/src/test/java/com/android/tools/r8/TestBase.java b/src/test/java/com/android/tools/r8/TestBase.java
index f5ddeeb..4daa6a9 100644
--- a/src/test/java/com/android/tools/r8/TestBase.java
+++ b/src/test/java/com/android/tools/r8/TestBase.java
@@ -18,6 +18,7 @@
 import com.android.tools.r8.KotlinCompilerTool.KotlinCompiler;
 import com.android.tools.r8.KotlinCompilerTool.KotlinTargetVersion;
 import com.android.tools.r8.TestRuntime.CfRuntime;
+import com.android.tools.r8.TestRuntime.CfVm;
 import com.android.tools.r8.ToolHelper.ArtCommandBuilder;
 import com.android.tools.r8.ToolHelper.DexVm;
 import com.android.tools.r8.ToolHelper.ProcessResult;
@@ -349,6 +350,10 @@
       TemporaryFolder temp,
       KotlinCompiler kotlinCompiler,
       KotlinTargetVersion kotlinTargetVersion) {
+    // TODO(b/227161720): Kotlinc fails to run on JDK17.
+    if (jdk.isNewerThanOrEqual(CfVm.JDK17)) {
+      jdk = TestRuntime.getCheckedInJdk9();
+    }
     return KotlinCompilerTool.create(jdk, temp, kotlinCompiler, kotlinTargetVersion);
   }
 
@@ -359,6 +364,10 @@
 
   public KotlinCompilerTool kotlinc(
       CfRuntime jdk, KotlinCompiler kotlinCompiler, KotlinTargetVersion kotlinTargetVersion) {
+    // TODO(b/227161720): Kotlinc fails to run on JDK17.
+    if (jdk.isNewerThanOrEqual(CfVm.JDK17)) {
+      jdk = TestRuntime.getCheckedInJdk9();
+    }
     return KotlinCompilerTool.create(jdk, temp, kotlinCompiler, kotlinTargetVersion);
   }
 
diff --git a/src/test/java/com/android/tools/r8/TestRuntime.java b/src/test/java/com/android/tools/r8/TestRuntime.java
index dd19c1e..336ffd1 100644
--- a/src/test/java/com/android/tools/r8/TestRuntime.java
+++ b/src/test/java/com/android/tools/r8/TestRuntime.java
@@ -29,6 +29,11 @@
     JDK9("jdk9", 53),
     JDK10("jdk10", 54),
     JDK11("jdk11", 55),
+    JDK12("jdk12", 56),
+    JDK13("jdk13", 57),
+    JDK14("jdk14", 58),
+    JDK15("jdk15", 59),
+    JDK16("jdk16", 60),
     JDK17("jdk17", 61),
     JDK18("jdk18", 62),
     ;
@@ -53,7 +58,7 @@
     }
 
     public static CfVm last() {
-      return JDK11;
+      return JDK17;
     }
 
     public boolean lessThan(CfVm other) {
@@ -125,14 +130,15 @@
     return new CfRuntime(CfVm.JDK11, getCheckedInJdkHome(CfVm.JDK11));
   }
 
-  // TODO(b/169692487): Add this to 'getCheckedInCfRuntimes' when we start having support for JDK17.
   public static CfRuntime getCheckedInJdk17() {
     return new CfRuntime(CfVm.JDK17, getCheckedInJdkHome(CfVm.JDK17));
   }
 
   public static List<CfRuntime> getCheckedInCfRuntimes() {
     CfRuntime[] jdks =
-        new CfRuntime[] {getCheckedInJdk8(), getCheckedInJdk9(), getCheckedInJdk11()};
+        new CfRuntime[] {
+          getCheckedInJdk8(), getCheckedInJdk9(), getCheckedInJdk11(), getCheckedInJdk17(),
+        };
     Builder<CfRuntime> builder = ImmutableList.builder();
     for (CfRuntime jdk : jdks) {
       if (jdk != null) {
diff --git a/src/test/java/com/android/tools/r8/ToolHelper.java b/src/test/java/com/android/tools/r8/ToolHelper.java
index e0bb322..5b8bb7a 100644
--- a/src/test/java/com/android/tools/r8/ToolHelper.java
+++ b/src/test/java/com/android/tools/r8/ToolHelper.java
@@ -1305,7 +1305,7 @@
     AndroidApp app = command.getInputApp();
     if (app.getLibraryResourceProviders().isEmpty()) {
       // Add the android library matching the minsdk. We filter out junit and testing classes
-      // from the android jar to avoid duplicate classes in art and jctf tests.
+      // from the android jar to avoid duplicate classes in art tests.
       AndroidApp.Builder builder = AndroidApp.builder(app);
       addFilteredAndroidJar(builder, AndroidApiLevel.getAndroidApiLevel(command.getMinApiLevel()));
       app = builder.build();
diff --git a/src/test/java/com/android/tools/r8/annotations/SourceDebugExtensionTest.java b/src/test/java/com/android/tools/r8/annotations/SourceDebugExtensionTest.java
index 14b6376..814e454 100644
--- a/src/test/java/com/android/tools/r8/annotations/SourceDebugExtensionTest.java
+++ b/src/test/java/com/android/tools/r8/annotations/SourceDebugExtensionTest.java
@@ -12,11 +12,10 @@
 import com.android.tools.r8.CompilationMode;
 import com.android.tools.r8.KotlinCompilerTool.KotlinCompiler;
 import com.android.tools.r8.KotlinCompilerTool.KotlinTargetVersion;
+import com.android.tools.r8.KotlinTestBase;
 import com.android.tools.r8.KotlinTestParameters;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.TestRuntime;
-import com.android.tools.r8.TestRuntime.CfRuntime;
 import com.android.tools.r8.retrace.KotlinInlineFunctionRetraceTest;
 import com.android.tools.r8.shaking.ProguardKeepAttributes;
 import com.android.tools.r8.utils.codeinspector.AnnotationSubject;
@@ -52,11 +51,13 @@
 
   @Test
   public void testR8() throws IOException, CompilationFailedException, ExecutionException {
-    CfRuntime cfRuntime =
-        parameters.isCfRuntime() ? parameters.getRuntime().asCf() : TestRuntime.getCheckedInJdk9();
     KotlinCompiler kotlinc = kotlinTestParameters.getCompiler();
     Path kotlinSources =
-        kotlinc(cfRuntime, getStaticTemp(), kotlinc, KotlinTargetVersion.JAVA_8)
+        kotlinc(
+                KotlinTestBase.getKotlincHostRuntime(parameters.getRuntime()),
+                getStaticTemp(),
+                kotlinc,
+                KotlinTargetVersion.JAVA_8)
             .addSourceFiles(
                 getFilesInTestFolderRelativeToClass(
                     KotlinInlineFunctionRetraceTest.class, "kt", ".kt"))
diff --git a/src/test/java/com/android/tools/r8/apimodel/ApiModelOutlineDuplicateMethodTest.java b/src/test/java/com/android/tools/r8/apimodel/ApiModelOutlineDuplicateMethodTest.java
index 2e96c90..e8d6257 100644
--- a/src/test/java/com/android/tools/r8/apimodel/ApiModelOutlineDuplicateMethodTest.java
+++ b/src/test/java/com/android/tools/r8/apimodel/ApiModelOutlineDuplicateMethodTest.java
@@ -16,6 +16,7 @@
 import static org.junit.Assume.assumeFalse;
 import static org.junit.Assume.assumeTrue;
 
+import com.android.tools.r8.CompilationMode;
 import com.android.tools.r8.NeverInline;
 import com.android.tools.r8.SingleTestRunResult;
 import com.android.tools.r8.TestBase;
@@ -25,6 +26,7 @@
 import com.android.tools.r8.ToolHelper.DexVm.Version;
 import com.android.tools.r8.testing.AndroidBuildVersion;
 import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.android.tools.r8.utils.codeinspector.FoundMethodSubject;
 import com.android.tools.r8.utils.codeinspector.MethodSubject;
 import java.lang.reflect.Method;
@@ -62,6 +64,8 @@
         .apply(setMockApiLevelForClass(LibraryClass.class, classApiLevel))
         .apply(setMockApiLevelForDefaultInstanceInitializer(LibraryClass.class, classApiLevel))
         .apply(setMockApiLevelForMethod(addedOn23(), methodApiLevel))
+        // TODO(b/213552119): Remove when enabled by default.
+        .apply(ApiModelingTestHelper::enableApiCallerIdentification)
         .apply(ApiModelingTestHelper::enableOutliningOfMethods)
         .apply(ApiModelingTestHelper::disableStubbingOfClasses);
   }
@@ -72,27 +76,41 @@
   }
 
   @Test
-  public void testD8() throws Exception {
+  public void testD8Debug() throws Exception {
     // TODO(b/197078995): Make this work on 12+.
-    assumeTrue(
-        parameters.isDexRuntime()
-            && parameters.getDexRuntimeVersion().isOlderThan(Version.V12_0_0));
+    assumeFalse(
+        parameters.isCfRuntime()
+            || parameters.getDexRuntimeVersion().isNewerThanOrEqual(Version.V12_0_0));
     testForD8()
+        .setMode(CompilationMode.DEBUG)
         .apply(this::setupTestBuilder)
         .compile()
         .applyIf(addToBootClasspath(), b -> b.addBootClasspathClasses(LibraryClass.class))
         .run(parameters.getRuntime(), Main.class)
         .apply(this::checkOutput)
-        // TODO(b/213552119): Assert that we did not outline any methods.
-        .inspect(ApiModelingTestHelper::assertNoSynthesizedClasses);
+        .inspect(this::inspect);
+  }
+
+  @Test
+  public void testD8Release() throws Exception {
+    assumeFalse(
+        parameters.isCfRuntime()
+            || parameters.getDexRuntimeVersion().isNewerThanOrEqual(Version.V12_0_0));
+    testForD8()
+        .setMode(CompilationMode.RELEASE)
+        .apply(this::setupTestBuilder)
+        .compile()
+        .applyIf(addToBootClasspath(), b -> b.addBootClasspathClasses(LibraryClass.class))
+        .run(parameters.getRuntime(), Main.class)
+        .apply(this::checkOutput)
+        .inspect(this::inspect);
   }
 
   @Test
   public void testR8() throws Exception {
     // TODO(b/197078995): Make this work on 12+.
-    assumeFalse(
-        parameters.isDexRuntime()
-            && parameters.getDexRuntimeVersion().isNewerThanOrEqual(Version.V12_0_0));
+    assumeTrue(
+        parameters.isCfRuntime() || parameters.getDexRuntimeVersion().isOlderThan(Version.V12_0_0));
     testForR8(parameters.getBackend())
         .apply(this::setupTestBuilder)
         .addKeepMainRule(Main.class)
@@ -101,47 +119,44 @@
         .applyIf(addToBootClasspath(), b -> b.addBootClasspathClasses(LibraryClass.class))
         .run(parameters.getRuntime(), Main.class)
         .apply(this::checkOutput)
-        .inspect(
-            inspector -> {
-              int classCount =
-                  parameters.isDexRuntime() && parameters.getApiLevel().isLessThan(methodApiLevel)
-                      ? 4
-                      : 3;
-              assertEquals(classCount, inspector.allClasses().size());
-              Method testMethod = TestClass.class.getDeclaredMethod("test");
-              verifyThat(inspector, parameters, addedOn23())
-                  .isOutlinedFromUntil(testMethod, methodApiLevel);
-              if (parameters.isDexRuntime()
-                  && parameters.getApiLevel().isLessThan(methodApiLevel)) {
-                // Verify that we invoke the synthesized outline, addedOn23, twice.
-                Optional<FoundMethodSubject> synthesizedAddedOn23 =
-                    inspector.allClasses().stream()
-                        .flatMap(clazz -> clazz.allMethods().stream())
-                        .filter(
-                            methodSubject ->
-                                methodSubject.isSynthetic()
-                                    && invokesMethodWithName("addedOn23").matches(methodSubject))
-                        .findFirst();
-                assertTrue(synthesizedAddedOn23.isPresent());
-                MethodSubject testMethodSubject = inspector.method(testMethod);
-                assertThat(testMethodSubject, isPresent());
-                assertEquals(
-                    2,
-                    testMethodSubject
-                        .streamInstructions()
-                        .filter(
-                            instructionSubject -> {
-                              if (!instructionSubject.isInvoke()) {
-                                return false;
-                              }
-                              return instructionSubject
-                                  .getMethod()
-                                  .asMethodReference()
-                                  .equals(synthesizedAddedOn23.get().asMethodReference());
-                            })
-                        .count());
-              }
-            });
+        .inspect(this::inspect);
+  }
+
+  private void inspect(CodeInspector inspector) throws Exception {
+    int classCount =
+        parameters.isDexRuntime() && parameters.getApiLevel().isLessThan(methodApiLevel) ? 4 : 3;
+    assertEquals(classCount, inspector.allClasses().size());
+    Method testMethod = TestClass.class.getDeclaredMethod("test");
+    verifyThat(inspector, parameters, addedOn23()).isOutlinedFromUntil(testMethod, methodApiLevel);
+    if (parameters.isDexRuntime() && parameters.getApiLevel().isLessThan(methodApiLevel)) {
+      // Verify that we invoke the synthesized outline, addedOn23, twice.
+      Optional<FoundMethodSubject> synthesizedAddedOn23 =
+          inspector.allClasses().stream()
+              .flatMap(clazz -> clazz.allMethods().stream())
+              .filter(
+                  methodSubject ->
+                      methodSubject.isSynthetic()
+                          && invokesMethodWithName("addedOn23").matches(methodSubject))
+              .findFirst();
+      assertTrue(synthesizedAddedOn23.isPresent());
+      MethodSubject testMethodSubject = inspector.method(testMethod);
+      assertThat(testMethodSubject, isPresent());
+      assertEquals(
+          2,
+          testMethodSubject
+              .streamInstructions()
+              .filter(
+                  instructionSubject -> {
+                    if (!instructionSubject.isInvoke()) {
+                      return false;
+                    }
+                    return instructionSubject
+                        .getMethod()
+                        .asMethodReference()
+                        .equals(synthesizedAddedOn23.get().asMethodReference());
+                  })
+              .count());
+    }
   }
 
   private void checkOutput(SingleTestRunResult<?> runResult) {
diff --git a/src/test/java/com/android/tools/r8/apimodel/ApiModelOutlineHorizontalMergingTest.java b/src/test/java/com/android/tools/r8/apimodel/ApiModelOutlineHorizontalMergingTest.java
index 497e0fd..fb98821 100644
--- a/src/test/java/com/android/tools/r8/apimodel/ApiModelOutlineHorizontalMergingTest.java
+++ b/src/test/java/com/android/tools/r8/apimodel/ApiModelOutlineHorizontalMergingTest.java
@@ -13,6 +13,7 @@
 import static org.junit.Assume.assumeFalse;
 import static org.junit.Assume.assumeTrue;
 
+import com.android.tools.r8.CompilationMode;
 import com.android.tools.r8.NeverInline;
 import com.android.tools.r8.SingleTestRunResult;
 import com.android.tools.r8.TestBase;
@@ -22,6 +23,7 @@
 import com.android.tools.r8.ToolHelper.DexVm.Version;
 import com.android.tools.r8.testing.AndroidBuildVersion;
 import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.android.tools.r8.utils.codeinspector.FoundMethodSubject;
 import java.util.List;
 import java.util.stream.Collectors;
@@ -72,6 +74,8 @@
         .apply(
             setMockApiLevelForMethod(
                 OtherLibraryClass.class.getMethod("addedOn27"), secondMethodApiLevel))
+        // TODO(b/213552119): Remove when enabled by default.
+        .apply(ApiModelingTestHelper::enableApiCallerIdentification)
         .apply(ApiModelingTestHelper::enableOutliningOfMethods)
         .apply(ApiModelingTestHelper::disableStubbingOfClasses);
   }
@@ -85,12 +89,13 @@
   }
 
   @Test
-  public void testD8() throws Exception {
+  public void testD8Debug() throws Exception {
     // TODO(b/197078995): Make this work on 12+.
     assumeTrue(
         parameters.isDexRuntime()
             && parameters.getDexRuntimeVersion().isOlderThan(Version.V12_0_0));
     testForD8(parameters.getBackend())
+        .setMode(CompilationMode.DEBUG)
         .apply(this::setupTestBuilder)
         .compile()
         .applyIf(
@@ -98,8 +103,37 @@
             b -> b.addBootClasspathClasses(LibraryClass.class, OtherLibraryClass.class))
         .run(parameters.getRuntime(), Main.class)
         .apply(this::checkOutput)
-        // TODO(b/213552119): Assert that we did not outline any methods.
-        .inspect(ApiModelingTestHelper::assertNoSynthesizedClasses);
+        .inspect(
+            inspector -> {
+              // TODO(b/187675788): Update when horizontal merging is enabled for D8 for debug mode.
+              if (parameters.getApiLevel().isLessThan(firstMethodApiLevel)) {
+                // We have generated 4 outlines two having api level 23 and two having api level 27.
+                assertEquals(7, inspector.allClasses().size());
+              } else if (parameters.getApiLevel().isLessThan(secondMethodApiLevel)) {
+                assertEquals(5, inspector.allClasses().size());
+              } else {
+                // No outlining on this api level.
+                assertEquals(3, inspector.allClasses().size());
+              }
+            });
+  }
+
+  @Test
+  public void testD8Release() throws Exception {
+    // TODO(b/197078995): Make this work on 12+.
+    assumeFalse(
+        parameters.isCfRuntime()
+            || parameters.getDexRuntimeVersion().isNewerThanOrEqual(Version.V12_0_0));
+    testForD8(parameters.getBackend())
+        .setMode(CompilationMode.RELEASE)
+        .apply(this::setupTestBuilder)
+        .compile()
+        .applyIf(
+            addToBootClasspath(),
+            b -> b.addBootClasspathClasses(LibraryClass.class, OtherLibraryClass.class))
+        .run(parameters.getRuntime(), Main.class)
+        .apply(this::checkOutput)
+        .inspect(this::inspect);
   }
 
   @Test
@@ -118,64 +152,64 @@
             b -> b.addBootClasspathClasses(LibraryClass.class, OtherLibraryClass.class))
         .run(parameters.getRuntime(), Main.class)
         .apply(this::checkOutput)
-        .inspect(
-            inspector -> {
-              // No need to check further on CF.
-              List<FoundMethodSubject> outlinedAddedOn23 =
-                  inspector.allClasses().stream()
-                      .flatMap(clazz -> clazz.allMethods().stream())
-                      .filter(
-                          methodSubject ->
-                              methodSubject.isSynthetic()
-                                  && invokesMethodWithName("addedOn23").matches(methodSubject))
-                      .collect(Collectors.toList());
-              List<FoundMethodSubject> outlinedAddedOn27 =
-                  inspector.allClasses().stream()
-                      .flatMap(clazz -> clazz.allMethods().stream())
-                      .filter(
-                          methodSubject ->
-                              methodSubject.isSynthetic()
-                                  && invokesMethodWithName("addedOn27").matches(methodSubject))
-                      .collect(Collectors.toList());
-              if (parameters.isCfRuntime()) {
-                assertTrue(outlinedAddedOn23.isEmpty());
-                assertTrue(outlinedAddedOn27.isEmpty());
-                assertEquals(3, inspector.allClasses().size());
-              } else if (parameters.getApiLevel().isLessThan(firstMethodApiLevel)) {
-                // We have generated 4 outlines two having api level 23 and two having api level 27.
-                // Check that the levels are horizontally merged.
-                assertEquals(5, inspector.allClasses().size());
-                assertEquals(2, outlinedAddedOn23.size());
-                assertTrue(
-                    outlinedAddedOn23.stream()
-                        .allMatch(
-                            outline ->
-                                outline.getMethod().getHolderType()
-                                    == outlinedAddedOn23.get(0).getMethod().getHolderType()));
-                assertEquals(2, outlinedAddedOn27.size());
-                assertTrue(
-                    outlinedAddedOn27.stream()
-                        .allMatch(
-                            outline ->
-                                outline.getMethod().getHolderType()
-                                    == outlinedAddedOn27.get(0).getMethod().getHolderType()));
-              } else if (parameters.getApiLevel().isLessThan(secondMethodApiLevel)) {
-                assertTrue(outlinedAddedOn23.isEmpty());
-                assertEquals(4, inspector.allClasses().size());
-                assertEquals(2, outlinedAddedOn27.size());
-                assertTrue(
-                    outlinedAddedOn27.stream()
-                        .allMatch(
-                            outline ->
-                                outline.getMethod().getHolderType()
-                                    == outlinedAddedOn27.get(0).getMethod().getHolderType()));
-              } else {
-                // No outlining on this api level.
-                assertTrue(outlinedAddedOn23.isEmpty());
-                assertTrue(outlinedAddedOn27.isEmpty());
-                assertEquals(3, inspector.allClasses().size());
-              }
-            });
+        .inspect(this::inspect);
+  }
+
+  private void inspect(CodeInspector inspector) {
+    List<FoundMethodSubject> outlinedAddedOn23 =
+        inspector.allClasses().stream()
+            .flatMap(clazz -> clazz.allMethods().stream())
+            .filter(
+                methodSubject ->
+                    methodSubject.isSynthetic()
+                        && invokesMethodWithName("addedOn23").matches(methodSubject))
+            .collect(Collectors.toList());
+    List<FoundMethodSubject> outlinedAddedOn27 =
+        inspector.allClasses().stream()
+            .flatMap(clazz -> clazz.allMethods().stream())
+            .filter(
+                methodSubject ->
+                    methodSubject.isSynthetic()
+                        && invokesMethodWithName("addedOn27").matches(methodSubject))
+            .collect(Collectors.toList());
+    if (parameters.isCfRuntime()) {
+      assertTrue(outlinedAddedOn23.isEmpty());
+      assertTrue(outlinedAddedOn27.isEmpty());
+      assertEquals(3, inspector.allClasses().size());
+    } else if (parameters.getApiLevel().isLessThan(firstMethodApiLevel)) {
+      // We have generated 4 outlines two having api level 23 and two having api level 27.
+      // Check that the levels are horizontally merged.
+      assertEquals(5, inspector.allClasses().size());
+      assertEquals(2, outlinedAddedOn23.size());
+      assertTrue(
+          outlinedAddedOn23.stream()
+              .allMatch(
+                  outline ->
+                      outline.getMethod().getHolderType()
+                          == outlinedAddedOn23.get(0).getMethod().getHolderType()));
+      assertEquals(2, outlinedAddedOn27.size());
+      assertTrue(
+          outlinedAddedOn27.stream()
+              .allMatch(
+                  outline ->
+                      outline.getMethod().getHolderType()
+                          == outlinedAddedOn27.get(0).getMethod().getHolderType()));
+    } else if (parameters.getApiLevel().isLessThan(secondMethodApiLevel)) {
+      assertTrue(outlinedAddedOn23.isEmpty());
+      assertEquals(4, inspector.allClasses().size());
+      assertEquals(2, outlinedAddedOn27.size());
+      assertTrue(
+          outlinedAddedOn27.stream()
+              .allMatch(
+                  outline ->
+                      outline.getMethod().getHolderType()
+                          == outlinedAddedOn27.get(0).getMethod().getHolderType()));
+    } else {
+      // No outlining on this api level.
+      assertTrue(outlinedAddedOn23.isEmpty());
+      assertTrue(outlinedAddedOn27.isEmpty());
+      assertEquals(3, inspector.allClasses().size());
+    }
   }
 
   private void checkOutput(SingleTestRunResult<?> runResult) {
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 89c2f76..2411e94 100644
--- a/src/test/java/com/android/tools/r8/apimodel/ApiModelOutlineMethodAndStubClassTest.java
+++ b/src/test/java/com/android/tools/r8/apimodel/ApiModelOutlineMethodAndStubClassTest.java
@@ -11,6 +11,7 @@
 import static org.junit.Assume.assumeFalse;
 import static org.junit.Assume.assumeTrue;
 
+import com.android.tools.r8.CompilationMode;
 import com.android.tools.r8.SingleTestRunResult;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestCompilerBuilder;
@@ -20,6 +21,7 @@
 import com.android.tools.r8.apimodel.ApiModelMockClassTest.TestClass;
 import com.android.tools.r8.testing.AndroidBuildVersion;
 import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import java.lang.reflect.Method;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -51,6 +53,8 @@
         .addDefaultRuntimeLibrary(parameters)
         .setMinApi(parameters.getApiLevel())
         .addAndroidBuildVersion()
+        // TODO(b/213552119): Remove when enabled by default.
+        .apply(ApiModelingTestHelper::enableApiCallerIdentification)
         .apply(ApiModelingTestHelper::enableStubbingOfClasses)
         .apply(ApiModelingTestHelper::enableOutliningOfMethods)
         .apply(setMockApiLevelForClass(LibraryClass.class, libraryClassLevel))
@@ -64,19 +68,35 @@
   }
 
   @Test
-  public void testD8() throws Exception {
+  public void testD8Debug() throws Exception {
     // TODO(b/197078995): Make this work on 12+.
     assumeTrue(
         parameters.isDexRuntime()
             && parameters.getDexRuntimeVersion().isOlderThan(Version.V12_0_0));
     testForD8(parameters.getBackend())
+        .setMode(CompilationMode.DEBUG)
         .apply(this::setupTestBuilder)
         .compile()
         .applyIf(addToBootClasspath(), b -> b.addBootClasspathClasses(LibraryClass.class))
         .run(parameters.getRuntime(), Main.class)
         .apply(this::checkOutput)
-        // TODO(b/213552119): Assert that we did not outline any methods.
-        .inspect(ApiModelingTestHelper::assertNoSynthesizedClasses);
+        .inspect(this::inspect);
+  }
+
+  @Test
+  public void testD8Release() throws Exception {
+    // TODO(b/197078995): Make this work on 12+.
+    assumeFalse(
+        parameters.isCfRuntime()
+            || parameters.getDexRuntimeVersion().isNewerThanOrEqual(Version.V12_0_0));
+    testForD8(parameters.getBackend())
+        .setMode(CompilationMode.RELEASE)
+        .apply(this::setupTestBuilder)
+        .compile()
+        .applyIf(addToBootClasspath(), b -> b.addBootClasspathClasses(LibraryClass.class))
+        .run(parameters.getRuntime(), Main.class)
+        .apply(this::checkOutput)
+        .inspect(this::inspect);
   }
 
   @Test
@@ -92,13 +112,14 @@
         .applyIf(addToBootClasspath(), b -> b.addBootClasspathClasses(LibraryClass.class))
         .run(parameters.getRuntime(), Main.class)
         .apply(this::checkOutput)
-        .inspect(
-            inspector -> {
-              verifyThat(inspector, parameters, LibraryClass.class).stubbedUntil(libraryClassLevel);
-              verifyThat(inspector, parameters, apiMethod())
-                  .isOutlinedFromUntil(
-                      Main.class.getDeclaredMethod("main", String[].class), libraryMethodLevel);
-            });
+        .inspect(this::inspect);
+  }
+
+  private void inspect(CodeInspector inspector) throws Exception {
+    verifyThat(inspector, parameters, LibraryClass.class).stubbedUntil(libraryClassLevel);
+    verifyThat(inspector, parameters, apiMethod())
+        .isOutlinedFromUntil(
+            Main.class.getDeclaredMethod("main", String[].class), libraryMethodLevel);
   }
 
   private void checkOutput(SingleTestRunResult<?> runResult) {
diff --git a/src/test/java/com/android/tools/r8/apimodel/ApiModelOutlineMethodMissingClassTest.java b/src/test/java/com/android/tools/r8/apimodel/ApiModelOutlineMethodMissingClassTest.java
index 8ee8ad4..cf440c9 100644
--- a/src/test/java/com/android/tools/r8/apimodel/ApiModelOutlineMethodMissingClassTest.java
+++ b/src/test/java/com/android/tools/r8/apimodel/ApiModelOutlineMethodMissingClassTest.java
@@ -16,6 +16,7 @@
 import static org.junit.Assume.assumeFalse;
 import static org.junit.Assume.assumeTrue;
 
+import com.android.tools.r8.CompilationMode;
 import com.android.tools.r8.NeverInline;
 import com.android.tools.r8.SingleTestRunResult;
 import com.android.tools.r8.TestBase;
@@ -25,6 +26,7 @@
 import com.android.tools.r8.ToolHelper.DexVm.Version;
 import com.android.tools.r8.testing.AndroidBuildVersion;
 import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.android.tools.r8.utils.codeinspector.FoundMethodSubject;
 import com.android.tools.r8.utils.codeinspector.MethodSubject;
 import java.lang.reflect.Method;
@@ -69,6 +71,8 @@
                 LibraryClass.class, initialLibraryMockLevel))
         .apply(setMockApiLevelForMethod(addedOn23(), initialLibraryMockLevel))
         .apply(setMockApiLevelForMethod(addedOn27(), finalLibraryMethodLevel))
+        // TODO(b/213552119): Remove when enabled by default.
+        .apply(ApiModelingTestHelper::enableApiCallerIdentification)
         .apply(ApiModelingTestHelper::enableOutliningOfMethods)
         .apply(ApiModelingTestHelper::disableStubbingOfClasses);
   }
@@ -82,19 +86,34 @@
   }
 
   @Test
-  public void testD8() throws Exception {
+  public void testD8Debug() throws Exception {
     // TODO(b/197078995): Make this work on 12+.
     assumeTrue(
         parameters.isDexRuntime()
             && parameters.getDexRuntimeVersion().isOlderThan(Version.V12_0_0));
     testForD8(parameters.getBackend())
+        .setMode(CompilationMode.DEBUG)
         .apply(this::setupTestBuilder)
         .compile()
         .applyIf(addToBootClasspath(), b -> b.addBootClasspathClasses(LibraryClass.class))
         .run(parameters.getRuntime(), Main.class)
         .apply(this::checkOutput)
-        // TODO(b/213552119): Assert that we did not outline any methods.
-        .inspect(ApiModelingTestHelper::assertNoSynthesizedClasses);
+        .inspect(this::inspect);
+  }
+
+  @Test
+  public void testD8Release() throws Exception {
+    assumeFalse(
+        parameters.isCfRuntime()
+            || parameters.getDexRuntimeVersion().isNewerThanOrEqual(Version.V12_0_0));
+    testForD8(parameters.getBackend())
+        .setMode(CompilationMode.RELEASE)
+        .apply(this::setupTestBuilder)
+        .compile()
+        .applyIf(addToBootClasspath(), b -> b.addBootClasspathClasses(LibraryClass.class))
+        .run(parameters.getRuntime(), Main.class)
+        .apply(this::checkOutput)
+        .inspect(this::inspect);
   }
 
   @Test
@@ -111,40 +130,37 @@
         .applyIf(addToBootClasspath(), b -> b.addBootClasspathClasses(LibraryClass.class))
         .run(parameters.getRuntime(), Main.class)
         .apply(this::checkOutput)
-        .inspect(
-            inspector -> {
-              // No need to check further on CF.
-              if (parameters.isCfRuntime()) {
-                assertEquals(3, inspector.allClasses().size());
-                return;
-              }
-              Method testMethod = TestClass.class.getDeclaredMethod("test");
-              MethodSubject testMethodSubject = inspector.method(testMethod);
-              assertThat(testMethodSubject, isPresent());
-              Optional<FoundMethodSubject> synthesizedMissingNotReferenced =
-                  inspector.allClasses().stream()
-                      .flatMap(clazz -> clazz.allMethods().stream())
-                      .filter(
-                          methodSubject ->
-                              methodSubject.isSynthetic()
-                                  && invokesMethodWithName("missingNotReferenced")
-                                      .matches(methodSubject))
-                      .findFirst();
-              assertFalse(synthesizedMissingNotReferenced.isPresent());
-              verifyThat(inspector, parameters, addedOn23()).isNotOutlinedFrom(testMethod);
-              verifyThat(inspector, parameters, addedOn27())
-                  .isOutlinedFromUntil(testMethod, finalLibraryMethodLevel);
-              verifyThat(
-                      inspector,
-                      parameters,
-                      LibraryClass.class.getDeclaredMethod("missingAndReferenced"))
-                  .isNotOutlinedFrom(testMethod);
-              if (parameters.getApiLevel().isLessThan(finalLibraryMethodLevel)) {
-                assertEquals(4, inspector.allClasses().size());
-              } else {
-                assertEquals(3, inspector.allClasses().size());
-              }
-            });
+        .inspect(this::inspect);
+  }
+
+  private void inspect(CodeInspector inspector) throws Exception {
+    // No need to check further on CF.
+    if (parameters.isCfRuntime()) {
+      assertEquals(3, inspector.allClasses().size());
+      return;
+    }
+    Method testMethod = TestClass.class.getDeclaredMethod("test");
+    MethodSubject testMethodSubject = inspector.method(testMethod);
+    assertThat(testMethodSubject, isPresent());
+    Optional<FoundMethodSubject> synthesizedMissingNotReferenced =
+        inspector.allClasses().stream()
+            .flatMap(clazz -> clazz.allMethods().stream())
+            .filter(
+                methodSubject ->
+                    methodSubject.isSynthetic()
+                        && invokesMethodWithName("missingNotReferenced").matches(methodSubject))
+            .findFirst();
+    assertFalse(synthesizedMissingNotReferenced.isPresent());
+    verifyThat(inspector, parameters, addedOn23()).isNotOutlinedFrom(testMethod);
+    verifyThat(inspector, parameters, addedOn27())
+        .isOutlinedFromUntil(testMethod, finalLibraryMethodLevel);
+    verifyThat(inspector, parameters, LibraryClass.class.getDeclaredMethod("missingAndReferenced"))
+        .isNotOutlinedFrom(testMethod);
+    if (parameters.getApiLevel().isLessThan(finalLibraryMethodLevel)) {
+      assertEquals(4, inspector.allClasses().size());
+    } else {
+      assertEquals(3, inspector.allClasses().size());
+    }
   }
 
   private void checkOutput(SingleTestRunResult<?> runResult) {
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 4428a33..d63e01b 100644
--- a/src/test/java/com/android/tools/r8/apimodel/ApiModelOutlineMethodProtectedTest.java
+++ b/src/test/java/com/android/tools/r8/apimodel/ApiModelOutlineMethodProtectedTest.java
@@ -10,6 +10,7 @@
 import static org.junit.Assume.assumeFalse;
 import static org.junit.Assume.assumeTrue;
 
+import com.android.tools.r8.CompilationMode;
 import com.android.tools.r8.NeverClassInline;
 import com.android.tools.r8.NeverInline;
 import com.android.tools.r8.SingleTestRunResult;
@@ -73,6 +74,8 @@
                 .transform())
         .setMinApi(AndroidApiLevel.B)
         .addAndroidBuildVersion(runApiLevel())
+        // TODO(b/213552119): Remove when enabled by default.
+        .apply(ApiModelingTestHelper::enableApiCallerIdentification)
         .apply(setMockApiLevelForClass(LibraryClass.class, classApiLevel))
         .apply(setMockApiLevelForDefaultInstanceInitializer(LibraryClass.class, classApiLevel))
         .apply(
@@ -89,12 +92,28 @@
   }
 
   @Test
-  public void testD8() throws Exception {
+  public void testD8Debug() throws Exception {
     // TODO(b/197078995): Make this work on 12+.
     assumeTrue(
         parameters.isDexRuntime()
             && parameters.getDexRuntimeVersion().isOlderThan(Version.V12_0_0));
     testForD8()
+        .setMode(CompilationMode.DEBUG)
+        .apply(this::setupTestBuilder)
+        .compile()
+        .applyIf(addToBootClasspath(), b -> b.addBootClasspathClasses(LibraryClass.class))
+        .run(parameters.getRuntime(), Main.class)
+        .apply(this::checkOutput);
+  }
+
+  @Test
+  public void testD8Release() throws Exception {
+    // TODO(b/197078995): Make this work on 12+.
+    assumeFalse(
+        parameters.isCfRuntime()
+            || parameters.getDexRuntimeVersion().isNewerThanOrEqual(Version.V12_0_0));
+    testForD8()
+        .setMode(CompilationMode.RELEASE)
         .apply(this::setupTestBuilder)
         .compile()
         .applyIf(addToBootClasspath(), b -> b.addBootClasspathClasses(LibraryClass.class))
diff --git a/src/test/java/com/android/tools/r8/apimodel/ApiModelOutlineMethodUnknownTest.java b/src/test/java/com/android/tools/r8/apimodel/ApiModelOutlineMethodUnknownTest.java
index 2461448..940864c 100644
--- a/src/test/java/com/android/tools/r8/apimodel/ApiModelOutlineMethodUnknownTest.java
+++ b/src/test/java/com/android/tools/r8/apimodel/ApiModelOutlineMethodUnknownTest.java
@@ -8,6 +8,7 @@
 import static org.junit.Assume.assumeFalse;
 import static org.junit.Assume.assumeTrue;
 
+import com.android.tools.r8.CompilationMode;
 import com.android.tools.r8.SingleTestRunResult;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestCompilerBuilder;
@@ -42,6 +43,8 @@
         .setMinApi(parameters.getApiLevel())
         .addAndroidBuildVersion()
         .apply(setMockApiLevelForClass(LibraryClass.class, classApiLevel))
+        // TODO(b/213552119): Remove when enabled by default.
+        .apply(ApiModelingTestHelper::enableApiCallerIdentification)
         .apply(ApiModelingTestHelper::enableOutliningOfMethods)
         .apply(ApiModelingTestHelper::disableStubbingOfClasses);
   }
@@ -52,12 +55,30 @@
   }
 
   @Test
-  public void testD8() throws Exception {
+  public void testD8Debug() throws Exception {
     // TODO(b/197078995): Make this work on 12+.
     assumeTrue(
         parameters.isDexRuntime()
             && parameters.getDexRuntimeVersion().isOlderThan(Version.V12_0_0));
     testForD8()
+        .setMode(CompilationMode.DEBUG)
+        .apply(this::setupTestBuilder)
+        .compile()
+        // Assert that we did not outline any methods.
+        .inspect(ApiModelingTestHelper::assertNoSynthesizedClasses)
+        .applyIf(addToBootClasspath(), b -> b.addBootClasspathClasses(LibraryClass.class))
+        .run(parameters.getRuntime(), Main.class)
+        .apply(this::checkOutput);
+  }
+
+  @Test
+  public void testD8Release() throws Exception {
+    // TODO(b/197078995): Make this work on 12+.
+    assumeFalse(
+        parameters.isCfRuntime()
+            || parameters.getDexRuntimeVersion().isNewerThanOrEqual(Version.V12_0_0));
+    testForD8()
+        .setMode(CompilationMode.RELEASE)
         .apply(this::setupTestBuilder)
         .compile()
         // Assert that we did not outline any methods.
diff --git a/src/test/java/com/android/tools/r8/apimodel/ApiModelOutlinePackagePrivateTest.java b/src/test/java/com/android/tools/r8/apimodel/ApiModelOutlinePackagePrivateTest.java
index 9e8115d..94ae936 100644
--- a/src/test/java/com/android/tools/r8/apimodel/ApiModelOutlinePackagePrivateTest.java
+++ b/src/test/java/com/android/tools/r8/apimodel/ApiModelOutlinePackagePrivateTest.java
@@ -9,6 +9,7 @@
 import static org.junit.Assume.assumeFalse;
 import static org.junit.Assume.assumeTrue;
 
+import com.android.tools.r8.CompilationMode;
 import com.android.tools.r8.D8TestCompileResult;
 import com.android.tools.r8.SingleTestRunResult;
 import com.android.tools.r8.TestBase;
@@ -83,12 +84,31 @@
         .apply(
             setMockApiLevelForMethod(
                 LibraryClass.class.getDeclaredMethod("addedOn10"), methodApiLevel))
+        // TODO(b/213552119): Remove when enabled by default.
+        .apply(ApiModelingTestHelper::enableApiCallerIdentification)
         .apply(ApiModelingTestHelper::enableOutliningOfMethods)
         .apply(ApiModelingTestHelper::disableStubbingOfClasses);
   }
 
   @Test
-  public void testD8() throws Exception {
+  public void testD8Debug() throws Exception {
+    // TODO(b/197078995): Make this work on 12+.
+    assumeTrue(
+        parameters.isDexRuntime()
+            && parameters.getDexRuntimeVersion().isOlderThan(Version.V12_0_0));
+    testForD8()
+        .setMode(CompilationMode.DEBUG)
+        .apply(this::setupTestBuilder)
+        .compile()
+        // Assert that we did not outline any methods.
+        .inspect(ApiModelingTestHelper::assertNoSynthesizedClasses)
+        .applyIf(willInvokeLibraryMethods(), b -> b.addBootClasspathClasses(LibraryClass.class))
+        .run(parameters.getRuntime(), Main.class)
+        .apply(this::checkResultOnBootClassPath);
+  }
+
+  @Test
+  public void testD8Release() throws Exception {
     // TODO(b/197078995): Make this work on 12+.
     assumeTrue(
         parameters.isDexRuntime()
diff --git a/src/test/java/com/android/tools/r8/benchmarks/BenchmarkCollection.java b/src/test/java/com/android/tools/r8/benchmarks/BenchmarkCollection.java
index 5dc4bd9..a8f662f 100644
--- a/src/test/java/com/android/tools/r8/benchmarks/BenchmarkCollection.java
+++ b/src/test/java/com/android/tools/r8/benchmarks/BenchmarkCollection.java
@@ -8,6 +8,7 @@
 import com.android.tools.r8.benchmarks.appdumps.TiviBenchmarks;
 import com.android.tools.r8.benchmarks.desugaredlib.LegacyDesugaredLibraryBenchmark;
 import com.android.tools.r8.benchmarks.helloworld.HelloWorldBenchmark;
+import com.android.tools.r8.benchmarks.retrace.RetraceStackTraceBenchmark;
 import java.io.IOException;
 import java.util.ArrayList;
 import java.util.Collection;
@@ -47,6 +48,7 @@
     HelloWorldBenchmark.configs().forEach(collection::addBenchmark);
     LegacyDesugaredLibraryBenchmark.configs().forEach(collection::addBenchmark);
     TiviBenchmarks.configs().forEach(collection::addBenchmark);
+    RetraceStackTraceBenchmark.configs().forEach(collection::addBenchmark);
     return collection;
   }
 
diff --git a/src/test/java/com/android/tools/r8/benchmarks/BenchmarkDependency.java b/src/test/java/com/android/tools/r8/benchmarks/BenchmarkDependency.java
index 9e18997..4bf5334 100644
--- a/src/test/java/com/android/tools/r8/benchmarks/BenchmarkDependency.java
+++ b/src/test/java/com/android/tools/r8/benchmarks/BenchmarkDependency.java
@@ -33,6 +33,10 @@
     this.name = name;
     this.directoryName = directoryName;
     this.location = location;
+    String firstChar = name.substring(0, 1);
+    if (!firstChar.equals(firstChar.toLowerCase()) || name.contains("_")) {
+      throw new BenchmarkConfigError("Benchmark name should use lowerCamelCase, found: " + name);
+    }
   }
 
   public String getName() {
diff --git a/src/test/java/com/android/tools/r8/benchmarks/retrace/RetraceStackTraceBenchmark.java b/src/test/java/com/android/tools/r8/benchmarks/retrace/RetraceStackTraceBenchmark.java
new file mode 100644
index 0000000..e1f0d55
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/benchmarks/retrace/RetraceStackTraceBenchmark.java
@@ -0,0 +1,86 @@
+// Copyright (c) 2022, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.benchmarks.retrace;
+
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.benchmarks.BenchmarkBase;
+import com.android.tools.r8.benchmarks.BenchmarkConfig;
+import com.android.tools.r8.benchmarks.BenchmarkDependency;
+import com.android.tools.r8.benchmarks.BenchmarkMethod;
+import com.android.tools.r8.benchmarks.BenchmarkTarget;
+import com.android.tools.r8.retrace.ProguardMapProducer;
+import com.android.tools.r8.retrace.Retrace;
+import com.android.tools.r8.retrace.RetraceCommand;
+import com.google.common.collect.ImmutableList;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.List;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+/** Example of setting up a benchmark based on the testing infrastructure. */
+@RunWith(Parameterized.class)
+public class RetraceStackTraceBenchmark extends BenchmarkBase {
+
+  private static final BenchmarkDependency benchmarkDependency =
+      new BenchmarkDependency("retraceBenchmark", "retrace_benchmark", Paths.get("third_party"));
+
+  @Parameters(name = "{0}")
+  public static List<Object[]> data() {
+    return parametersFromConfigs(configs());
+  }
+
+  public RetraceStackTraceBenchmark(BenchmarkConfig config, TestParameters parameters) {
+    super(config, parameters);
+  }
+
+  /** Static method to add benchmarks to the benchmark collection. */
+  public static List<BenchmarkConfig> configs() {
+    return ImmutableList.<BenchmarkConfig>builder()
+        .add(
+            BenchmarkConfig.builder()
+                .setName("RetraceStackTraceWithProguardMap")
+                .setTarget(BenchmarkTarget.R8_NON_COMPAT)
+                .measureRunTime()
+                .setMethod(benchmarkRetrace())
+                .setFromRevision(12266)
+                .measureWarmup()
+                .addDependency(benchmarkDependency)
+                .build())
+        .build();
+  }
+
+  public static BenchmarkMethod benchmarkRetrace() {
+    return environment ->
+        runner(environment.getConfig())
+            .setWarmupIterations(1)
+            .setBenchmarkIterations(4)
+            .reportResultSum()
+            .run(
+                results -> {
+                  Path dependencyRoot = benchmarkDependency.getRoot(environment);
+                  List<String> stackTrace =
+                      Files.readAllLines(dependencyRoot.resolve("stacktrace.txt"));
+                  List<String> retraced = new ArrayList<>();
+                  long start = System.nanoTime();
+                  Retrace.run(
+                      RetraceCommand.builder()
+                          .setProguardMapProducer(
+                              ProguardMapProducer.fromPath(dependencyRoot.resolve("r8lib.jar.map")))
+                          .setStackTrace(stackTrace)
+                          .setRetracedStackTraceConsumer(retraced::addAll)
+                          .build());
+                  long end = System.nanoTime();
+                  // Add a simple check to ensure that we do not, in case of invalid retracing,
+                  // record an optimal benchmark result.
+                  if (retraced.size() < stackTrace.size()) {
+                    throw new RuntimeException("Unexpected missing lines in retraced result");
+                  }
+                  results.addRuntimeResult(end - start);
+                });
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/cf/bootstrap/KotlinCompilerTreeShakingTest.java b/src/test/java/com/android/tools/r8/cf/bootstrap/KotlinCompilerTreeShakingTest.java
index d5e17f4..d7a4cdc 100644
--- a/src/test/java/com/android/tools/r8/cf/bootstrap/KotlinCompilerTreeShakingTest.java
+++ b/src/test/java/com/android/tools/r8/cf/bootstrap/KotlinCompilerTreeShakingTest.java
@@ -8,6 +8,7 @@
 
 import com.android.tools.r8.KotlinCompilerTool.KotlinCompiler;
 import com.android.tools.r8.KotlinCompilerTool.KotlinTargetVersion;
+import com.android.tools.r8.KotlinTestBase;
 import com.android.tools.r8.KotlinTestParameters;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.ToolHelper;
@@ -58,7 +59,7 @@
     // Compile Hello.kt and make sure it works as expected.
     Path classPathBefore =
         kotlinc(
-                parameters.getRuntime().asCf(),
+                KotlinTestBase.getKotlincHostRuntime(parameters.getRuntime()),
                 kotlinTestParameters.getCompiler(),
                 kotlinTestParameters.getTargetVersion())
             .addSourceFiles(HELLO_KT)
diff --git a/src/test/java/com/android/tools/r8/classmerging/KeptTargetsIncompleteDiamondTest.java b/src/test/java/com/android/tools/r8/classmerging/KeptTargetsIncompleteDiamondTest.java
index 51e4d41..e4a1a1a 100644
--- a/src/test/java/com/android/tools/r8/classmerging/KeptTargetsIncompleteDiamondTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/KeptTargetsIncompleteDiamondTest.java
@@ -78,7 +78,8 @@
     // }
     AppView<AppInfoWithLiveness> appView = computeAppViewWithLiveness(I.class, I.class);
     DexMethod method = buildNullaryVoidMethod(I.class, "foo", appView.dexItemFactory());
-    MethodResolutionResult resolutionResult = appView.appInfo().resolveMethodOnInterface(method);
+    MethodResolutionResult resolutionResult =
+        appView.appInfo().resolveMethodOnInterfaceHolder(method);
     DexType typeI = buildType(I.class, appView.dexItemFactory());
     DexType typeL = buildType(L.class, appView.dexItemFactory());
     DexType typeA = buildType(A.class, appView.dexItemFactory());
@@ -119,7 +120,8 @@
     // }
     AppView<AppInfoWithLiveness> appView = computeAppViewWithLiveness(I.class, I.class);
     DexMethod method = buildNullaryVoidMethod(I.class, "foo", appView.dexItemFactory());
-    MethodResolutionResult resolutionResult = appView.appInfo().resolveMethodOnInterface(method);
+    MethodResolutionResult resolutionResult =
+        appView.appInfo().resolveMethodOnInterfaceHolder(method);
     DexType typeI = buildType(I.class, appView.dexItemFactory());
     DexType typeL = buildType(L.class, appView.dexItemFactory());
     DexType typeA = buildType(A.class, appView.dexItemFactory());
@@ -159,7 +161,8 @@
     // }
     AppView<AppInfoWithLiveness> appView = computeAppViewWithLiveness(J.class, J.class);
     DexMethod method = buildNullaryVoidMethod(I.class, "foo", appView.dexItemFactory());
-    MethodResolutionResult resolutionResult = appView.appInfo().resolveMethodOnInterface(method);
+    MethodResolutionResult resolutionResult =
+        appView.appInfo().resolveMethodOnInterfaceHolder(method);
     DexType typeI = buildType(I.class, appView.dexItemFactory());
     DexType typeB = buildType(A.class, appView.dexItemFactory());
     DexProgramClass classI = appView.definitionForProgramType(typeI);
@@ -196,7 +199,8 @@
     // }
     AppView<AppInfoWithLiveness> appView = computeAppViewWithLiveness(J.class, A.class);
     DexMethod method = buildNullaryVoidMethod(I.class, "foo", appView.dexItemFactory());
-    MethodResolutionResult resolutionResult = appView.appInfo().resolveMethodOnInterface(method);
+    MethodResolutionResult resolutionResult =
+        appView.appInfo().resolveMethodOnInterfaceHolder(method);
     DexType typeI = buildType(I.class, appView.dexItemFactory());
     DexType typeB = buildType(A.class, appView.dexItemFactory());
     DexProgramClass classI = appView.definitionForProgramType(typeI);
@@ -235,7 +239,8 @@
     // }
     AppView<AppInfoWithLiveness> appView = computeAppViewWithLiveness(I.class, I.class);
     DexMethod method = buildNullaryVoidMethod(I.class, "foo", appView.dexItemFactory());
-    MethodResolutionResult resolutionResult = appView.appInfo().resolveMethodOnInterface(method);
+    MethodResolutionResult resolutionResult =
+        appView.appInfo().resolveMethodOnInterfaceHolder(method);
     DexType typeI = buildType(I.class, appView.dexItemFactory());
     DexType typeB = buildType(A.class, appView.dexItemFactory());
     DexProgramClass classI = appView.definitionForProgramType(typeI);
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 1ca2f2b..501370c 100644
--- a/src/test/java/com/android/tools/r8/compilerapi/CompilerApiTestCollection.java
+++ b/src/test/java/com/android/tools/r8/compilerapi/CompilerApiTestCollection.java
@@ -9,6 +9,8 @@
 
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.compilerapi.assertionconfiguration.AssertionConfigurationTest;
+import com.android.tools.r8.compilerapi.desugardependencies.DesugarDependenciesTest;
+import com.android.tools.r8.compilerapi.globalsynthetics.GlobalSyntheticsTest;
 import com.android.tools.r8.compilerapi.inputdependencies.InputDependenciesTest;
 import com.android.tools.r8.compilerapi.mapid.CustomMapIdTest;
 import com.android.tools.r8.compilerapi.mockdata.MockClass;
@@ -32,11 +34,13 @@
       ImmutableList.of(
           ApiTestingSetUpTest.ApiTest.class,
           CustomMapIdTest.ApiTest.class,
-          CustomSourceFileTest.ApiTest.class);
+          CustomSourceFileTest.ApiTest.class,
+          AssertionConfigurationTest.ApiTest.class,
+          InputDependenciesTest.ApiTest.class,
+          DesugarDependenciesTest.ApiTest.class);
 
   private static final List<Class<? extends CompilerApiTest>> CLASSES_PENDING_BINARY_COMPATIBILITY =
-      ImmutableList.of(
-          AssertionConfigurationTest.ApiTest.class, InputDependenciesTest.ApiTest.class);
+      ImmutableList.of(GlobalSyntheticsTest.ApiTest.class);
 
   private final TemporaryFolder temp;
 
diff --git a/src/test/java/com/android/tools/r8/compilerapi/desugardependencies/DesugarDependenciesTest.java b/src/test/java/com/android/tools/r8/compilerapi/desugardependencies/DesugarDependenciesTest.java
new file mode 100644
index 0000000..2b83f85
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/compilerapi/desugardependencies/DesugarDependenciesTest.java
@@ -0,0 +1,86 @@
+// Copyright (c) 2022, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.compilerapi.desugardependencies;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.D8;
+import com.android.tools.r8.D8Command;
+import com.android.tools.r8.DesugarGraphConsumer;
+import com.android.tools.r8.DexIndexedConsumer;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.compilerapi.CompilerApiTest;
+import com.android.tools.r8.compilerapi.CompilerApiTestRunner;
+import com.android.tools.r8.origin.Origin;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import org.junit.Test;
+
+public class DesugarDependenciesTest extends CompilerApiTestRunner {
+
+  public DesugarDependenciesTest(TestParameters parameters) {
+    super(parameters);
+  }
+
+  @Override
+  public Class<? extends CompilerApiTest> binaryTestClass() {
+    return ApiTest.class;
+  }
+
+  @Test
+  public void testDesugarDependencies() throws Exception {
+    ApiTest test = new ApiTest(ApiTest.PARAMETERS);
+    runTest(test::run);
+  }
+
+  private interface Runner {
+    void run() throws Exception;
+  }
+
+  private void runTest(Runner test) throws Exception {
+    test.run();
+  }
+
+  public static class ApiTest extends CompilerApiTest {
+
+    public ApiTest(Object parameters) {
+      super(parameters);
+    }
+
+    public void run() throws Exception {
+      D8.run(
+          D8Command.builder()
+              .addClassProgramData(getBytesForClass(getMockClass()), Origin.unknown())
+              .addLibraryFiles(getJava8RuntimeJar())
+              .setProgramConsumer(DexIndexedConsumer.emptyConsumer())
+              .setDesugarGraphConsumer(
+                  new DesugarGraphConsumer() {
+                    private final Set<Origin> desugaringUnit = ConcurrentHashMap.newKeySet();
+
+                    @Override
+                    public void acceptProgramNode(Origin node) {
+                      desugaringUnit.add(node);
+                    }
+
+                    @Override
+                    public void accept(Origin dependent, Origin dependency) {
+                      assertTrue(desugaringUnit.contains(dependent));
+                    }
+
+                    @Override
+                    public void finished() {
+                      // Input unit contains just the mock class.
+                      assertEquals(1, desugaringUnit.size());
+                    }
+                  })
+              .build());
+    }
+
+    @Test
+    public void testDesugarDependencies() throws Exception {
+      run();
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/compilerapi/globalsynthetics/GlobalSyntheticsTest.java b/src/test/java/com/android/tools/r8/compilerapi/globalsynthetics/GlobalSyntheticsTest.java
new file mode 100644
index 0000000..edc42c2
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/compilerapi/globalsynthetics/GlobalSyntheticsTest.java
@@ -0,0 +1,87 @@
+// Copyright (c) 2022, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.compilerapi.globalsynthetics;
+
+import com.android.tools.r8.D8;
+import com.android.tools.r8.D8Command;
+import com.android.tools.r8.DexIndexedConsumer;
+import com.android.tools.r8.GlobalSyntheticsConsumer;
+import com.android.tools.r8.GlobalSyntheticsResourceProvider;
+import com.android.tools.r8.ResourceException;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.compilerapi.CompilerApiTest;
+import com.android.tools.r8.compilerapi.CompilerApiTestRunner;
+import com.android.tools.r8.origin.Origin;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.List;
+import org.junit.Test;
+
+public class GlobalSyntheticsTest extends CompilerApiTestRunner {
+
+  public GlobalSyntheticsTest(TestParameters parameters) {
+    super(parameters);
+  }
+
+  @Override
+  public Class<? extends CompilerApiTest> binaryTestClass() {
+    return ApiTest.class;
+  }
+
+  @Test
+  public void testGlobalSynthetics() throws Exception {
+    new ApiTest(ApiTest.PARAMETERS).run();
+  }
+
+  public static class ApiTest extends CompilerApiTest {
+
+    public ApiTest(Object parameters) {
+      super(parameters);
+    }
+
+    public void run() throws Exception {
+      GlobalSyntheticsResourceProvider provider =
+          new GlobalSyntheticsResourceProvider() {
+            @Override
+            public Origin getOrigin() {
+              return Origin.unknown();
+            }
+
+            @Override
+            public InputStream getByteStream() throws ResourceException {
+              throw new IllegalStateException();
+            }
+          };
+      List<GlobalSyntheticsResourceProvider> providers = new ArrayList<>();
+      // Don't actually add the provider as we don't have any bytes to return.
+      if (false) {
+        providers.add(provider);
+      }
+      D8.run(
+          D8Command.builder()
+              .addClassProgramData(getBytesForClass(getMockClass()), Origin.unknown())
+              .addLibraryFiles(getJava8RuntimeJar())
+              .setProgramConsumer(DexIndexedConsumer.emptyConsumer())
+              .setIntermediate(true)
+              .addGlobalSyntheticsFiles()
+              .addGlobalSyntheticsFiles(new ArrayList<>())
+              .addGlobalSyntheticsResourceProviders()
+              .addGlobalSyntheticsResourceProviders(providers)
+              .setGlobalSyntheticsConsumer(
+                  new GlobalSyntheticsConsumer() {
+                    @Override
+                    public void accept(byte[] bytes) {
+                      // Nothing is actually received here as MockClass does not give rise to
+                      // globals.
+                    }
+                  })
+              .build());
+    }
+
+    @Test
+    public void testGlobalSynthetics() throws Exception {
+      run();
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/BufferedReaderTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/BufferedReaderTest.java
index f79b585..93ea904 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/BufferedReaderTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/BufferedReaderTest.java
@@ -51,13 +51,7 @@
   }
 
   private String expectedOutput() {
-    return StringUtils.lines(
-        "Hello",
-        "Larry",
-        "Page",
-        parameters.getApiLevel().isGreaterThanOrEqualTo(AndroidApiLevel.N)
-            ? "Caught java.io.UncheckedIOException"
-            : "Caught j$.io.UncheckedIOException");
+    return StringUtils.lines("Hello", "Larry", "Page", "Caught java.io.UncheckedIOException");
   }
 
   DesugaredLibrarySpecification configurationAlternative3(
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DefaultMethodOverrideConflictWithLibrary2Test.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DefaultMethodOverrideConflictWithLibrary2Test.java
index e836c76..7d73298 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DefaultMethodOverrideConflictWithLibrary2Test.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DefaultMethodOverrideConflictWithLibrary2Test.java
@@ -3,7 +3,6 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.desugar.desugaredlibrary;
 
-import static org.hamcrest.CoreMatchers.containsString;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
 
@@ -90,12 +89,11 @@
   }
 
   private void checkResult(TestRunResult<?> result) {
-    if (parameters.isCfRuntime() && parameters.getRuntime().asCf().getVm().equals(CfVm.JDK11)) {
-      // TODO(b/145566657): For some reason JDK11 throws AbstractMethodError.
-      result.assertFailureWithErrorThatMatches(containsString(AbstractMethodError.class.getName()));
+    if (parameters.isCfRuntime() && parameters.getRuntime().asCf().isNewerThanOrEqual(CfVm.JDK11)) {
+      // TODO(b/145566657): For some reason JDK11+ throws AbstractMethodError.
+      result.assertFailureWithErrorThatThrows(AbstractMethodError.class);
     } else {
-      result.assertFailureWithErrorThatMatches(
-          containsString(IncompatibleClassChangeError.class.getName()));
+      result.assertFailureWithErrorThatThrows(IncompatibleClassChangeError.class);
     }
   }
 
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/IterableTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/IterableTest.java
index e2cdf17..8fd4cd4 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/IterableTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/IterableTest.java
@@ -8,6 +8,8 @@
 
 import com.android.tools.r8.LibraryDesugaringTestConfiguration;
 import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestRuntime.CfVm;
+import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.StringUtils;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
@@ -91,7 +93,12 @@
           .addProgramFiles(jar)
           .addRunClasspathFiles(buildDesugaredLibraryClassFile(parameters.getApiLevel()))
           .run(parameters.getRuntime(), Main.class)
-          .assertSuccessWithOutput(EXPECTED_OUTPUT);
+          .applyIf(
+              // TODO(b/227161271): Figure out the cause and resolution for this issue.
+              parameters.isCfRuntime(CfVm.JDK17)
+                  && parameters.getApiLevel().equals(AndroidApiLevel.B),
+              r -> r.assertFailureWithErrorThatThrows(ExceptionInInitializerError.class),
+              r -> r.assertSuccessWithOutput(EXPECTED_OUTPUT));
     }
   }
 
diff --git a/src/test/java/com/android/tools/r8/desugar/graph/CompilationDependentSetTest.java b/src/test/java/com/android/tools/r8/desugar/graph/CompilationDependentSetTest.java
new file mode 100644
index 0000000..b0f6682
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/graph/CompilationDependentSetTest.java
@@ -0,0 +1,97 @@
+// Copyright (c) 2022, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.desugar.graph;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.D8TestBuilder;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.google.common.collect.ImmutableSet;
+import java.nio.file.Path;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class CompilationDependentSetTest extends TestBase {
+
+  public interface I {
+    // Empty.
+  }
+
+  public static class A implements I {
+    // Empty.
+  }
+
+  public static class B {
+    // Empty.
+  }
+
+  public static class TestClass {
+
+    public static void main(String[] args) {
+      System.out.println("Hello World!");
+    }
+  }
+
+  // Test runner follows.
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimes().withAllApiLevels().build();
+  }
+
+  private final TestParameters parameters;
+
+  public CompilationDependentSetTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void test() throws Exception {
+    if (parameters.isCfRuntime()) {
+      testForJvm()
+          .addProgramClasses(I.class, A.class, B.class, TestClass.class)
+          .run(parameters.getRuntime(), TestClass.class)
+          .assertSuccessWithOutputLines("Hello World!");
+    } else {
+      Path dexInputForB =
+          testForD8()
+              .addProgramClasses(B.class)
+              .setMinApi(parameters.getApiLevel())
+              .compile()
+              .writeToZip();
+
+      D8TestBuilder builder = testForD8();
+      DesugarGraphTestConsumer consumer = new DesugarGraphTestConsumer();
+      builder.getBuilder().setDesugarGraphConsumer(consumer);
+      Origin originI = DesugarGraphUtils.addClassWithOrigin(I.class, builder);
+      Origin originA = DesugarGraphUtils.addClassWithOrigin(A.class, builder);
+      Origin originTestClass = DesugarGraphUtils.addClassWithOrigin(TestClass.class, builder);
+      builder
+          .addProgramFiles(dexInputForB)
+          .setMinApi(parameters.getApiLevel())
+          .run(parameters.getRuntime(), TestClass.class)
+          .assertSuccessWithOutputLines("Hello World!");
+      // If API level indicates desugaring is needed check the edges are reported.
+      if (parameters.getApiLevel().getLevel() < AndroidApiLevel.N.getLevel()) {
+        assertTrue(consumer.contains(originI, originA));
+        assertEquals(1, consumer.totalEdgeCount());
+      } else {
+        assertEquals(0, consumer.totalEdgeCount());
+      }
+      // Regardless of API the potential inputs are reported.
+      // Note that the DEX input is not a desugaring candidate and thus not included in the unit.
+      assertEquals(
+          ImmutableSet.of(originI, originA, originTestClass),
+          consumer.getDesugaringCompilationUnit());
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/graph/DesugarGraphTestConsumer.java b/src/test/java/com/android/tools/r8/desugar/graph/DesugarGraphTestConsumer.java
index cb0da77..d80d81e 100644
--- a/src/test/java/com/android/tools/r8/desugar/graph/DesugarGraphTestConsumer.java
+++ b/src/test/java/com/android/tools/r8/desugar/graph/DesugarGraphTestConsumer.java
@@ -22,6 +22,9 @@
 
   private boolean finished = false;
 
+  // Set of all origins for the desugaring candidates in the compilation unit.
+  private final Set<Origin> desugaringCompilationUnit = new HashSet<>();
+
   // Map from a dependency to its immediate dependents.
   private final Map<Origin, Set<Origin>> dependents = new HashMap<>();
 
@@ -81,6 +84,16 @@
     return count;
   }
 
+  public Set<Origin> getDesugaringCompilationUnit() {
+    assertTrue(finished);
+    return desugaringCompilationUnit;
+  }
+
+  @Override
+  public synchronized void acceptProgramNode(Origin node) {
+    desugaringCompilationUnit.add(node);
+  }
+
   @Override
   public synchronized void accept(Origin dependent, Origin dependency) {
     assertFalse(finished);
diff --git a/src/test/java/com/android/tools/r8/desugar/graph/InterfaceToImplementingClassDependencyTest.java b/src/test/java/com/android/tools/r8/desugar/graph/InterfaceToImplementingClassDependencyTest.java
index 705410a..6339195 100644
--- a/src/test/java/com/android/tools/r8/desugar/graph/InterfaceToImplementingClassDependencyTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/graph/InterfaceToImplementingClassDependencyTest.java
@@ -33,7 +33,7 @@
   // Note: the dependency of I for the compilation of A exists even when no default methods do.
 
   public interface I {
-    // Emtpy.
+    // Empty.
   }
 
   public static class A implements I {
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 191cc22..13e021d 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
@@ -11,7 +11,7 @@
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
-import com.android.tools.r8.TestRuntime;
+import com.android.tools.r8.TestRuntime.CfVm;
 import com.android.tools.r8.examples.jdk18.jdk8272564.Jdk8272564;
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.InternalOptions.TestingOptions;
@@ -30,9 +30,8 @@
   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/174431251): This should be replaced with .withCfRuntimes(start = jdk17).
     return getTestParameters()
-        .withCustomRuntime(TestRuntime.getCheckedInJdk17())
+        .withCfRuntimesStartingFromIncluding(CfVm.JDK17)
         .withDexRuntimes()
         .withAllApiLevelsAlsoForCf()
         .build();
@@ -119,20 +118,24 @@
     assertJdk8272564NotFixedCode(inspector, 19, 0);
   }
 
+  private boolean isDefaultCfParameters() {
+    return parameters.isCfRuntime() && parameters.getApiLevel().equals(AndroidApiLevel.B);
+  }
+
   @Test
   // See https://bugs.openjdk.java.net/browse/JDK-8272564.
   public void testJdk8272564Compiler() throws Exception {
-    assumeTrue(parameters.isCfRuntime());
+    assumeTrue(isDefaultCfParameters());
     // Ensure that the test is running with CF input from fixing JDK-8272564.
     assertJdk8272564FixedCode(new CodeInspector(Jdk8272564.jar()));
   }
 
   @Test
   public void testJvm() throws Exception {
-    assumeTrue(parameters.isCfRuntime());
+    assumeTrue(isDefaultCfParameters());
     testForJvm()
         .addRunClasspathFiles(Jdk8272564.jar())
-        .run(TestRuntime.getCheckedInJdk17(), Jdk8272564.Main.typeName())
+        .run(parameters.getRuntime(), Jdk8272564.Main.typeName())
         .assertSuccess();
   }
 
@@ -150,6 +153,7 @@
 
   @Test
   public void testR8() throws Exception {
+    assumeTrue(parameters.isDexRuntime() || isDefaultCfParameters());
     // The R8 lens code rewriter rewrites to the code prior to fixing JDK-8272564.
     testForR8(parameters.getBackend())
         .addProgramFiles(Jdk8272564.jar())
diff --git a/src/test/java/com/android/tools/r8/desugar/lambdas/LambdaFactoryTest.java b/src/test/java/com/android/tools/r8/desugar/lambdas/LambdaFactoryTest.java
new file mode 100644
index 0000000..21ed11f
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/lambdas/LambdaFactoryTest.java
@@ -0,0 +1,158 @@
+// Copyright (c) 2022, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.desugar.lambdas;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import com.android.tools.r8.DesugarTestConfiguration;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.synthesis.SyntheticItemsTestUtils;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.InstructionSubject;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class LambdaFactoryTest extends TestBase {
+
+  @Parameter() public TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimes().withAllApiLevelsAlsoForCf().build();
+  }
+
+  private static final String EXPECTED_OUTPUT = StringUtils.lines("1", "2", "3", "4.0", "5");
+
+  private boolean isLambdaFactoryMethod(MethodSubject method) {
+    return method.isSynthetic() && method.isStatic() && method.getFinalName().equals("create");
+  }
+
+  private boolean isInvokingLambdaFactoryMethod(InstructionSubject instruction) {
+    return instruction.isInvokeStatic()
+        && SyntheticItemsTestUtils.isExternalSynthetic(
+            instruction.getMethod().getHolderType().asClassReference())
+        && instruction.getMethod().getName().toString().equals("create");
+  }
+
+  private void inspectDesugared(CodeInspector inspector) {
+    inspector.forAllClasses(
+        clazz -> {
+          if (SyntheticItemsTestUtils.isExternalSynthetic(clazz.getFinalReference())) {
+            assertTrue(clazz.allMethods().stream().anyMatch(this::isLambdaFactoryMethod));
+          }
+        });
+    assertEquals(
+        3,
+        inspector
+            .clazz(TestClass.class)
+            .mainMethod()
+            .streamInstructions()
+            .filter(this::isInvokingLambdaFactoryMethod)
+            .count());
+  }
+
+  private void inspectNotDesugared(CodeInspector inspector) {
+    inspector.forAllClasses(
+        clazz -> {
+          if (SyntheticItemsTestUtils.isExternalSynthetic(clazz.getFinalReference())) {
+            assertTrue(clazz.allMethods().stream().noneMatch(this::isLambdaFactoryMethod));
+          }
+        });
+    assertEquals(
+        0,
+        inspector
+            .clazz(TestClass.class)
+            .mainMethod()
+            .streamInstructions()
+            .filter(this::isInvokingLambdaFactoryMethod)
+            .count());
+  }
+
+  @Test
+  public void testDesugaring() throws Exception {
+    testForDesugaring(
+            parameters,
+            options -> {
+              options.testing.alwaysGenerateLambdaFactoryMethods = true;
+            })
+        .addInnerClasses(getClass())
+        .run(parameters.getRuntime(), TestClass.class)
+        .applyIf(
+            DesugarTestConfiguration::isDesugared,
+            r -> {
+              try {
+                r.inspect(this::inspectDesugared);
+              } catch (Exception e) {
+                fail();
+              }
+            },
+            r -> {
+              try {
+                r.inspect(this::inspectNotDesugared);
+              } catch (Exception e) {
+                fail();
+              }
+            })
+        .assertSuccessWithOutput(EXPECTED_OUTPUT);
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(getClass())
+        .addKeepMainRule(TestClass.class)
+        .setMinApi(parameters.getApiLevel())
+        .run(parameters.getRuntime(), TestClass.class)
+        .inspect(
+            inspector -> {
+              if (parameters.isDexRuntime()) {
+                // Lambdas are fully inlined when desugaring.
+                assertEquals(1, inspector.allClasses().size());
+                assertEquals(1, inspector.clazz(TestClass.class).allMethods().size());
+              }
+            })
+        .assertSuccessWithOutput(EXPECTED_OUTPUT);
+  }
+
+  interface MyConsumer<T> {
+    void create(T o);
+  }
+
+  interface MyTriConsumer<T, U, V> {
+    void accept(T o1, U o2, V o3);
+  }
+
+  static class TestClass {
+
+    public static void greet() {
+      System.out.println("1");
+    }
+
+    public static void greet(MyConsumer<String> consumer) {
+      consumer.create("2");
+    }
+
+    public static void greetTri(long l, double d, String s) {
+      System.out.println(l);
+      System.out.println(d);
+      System.out.println(s);
+    }
+
+    public static void main(String[] args) throws Exception {
+      ((Runnable) TestClass::greet).run();
+      greet(System.out::println);
+      ((MyTriConsumer<Long, Double, String>) TestClass::greetTri).accept(3L, 4.0, "5");
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/records/EmptyRecordAnnotationTest.java b/src/test/java/com/android/tools/r8/desugar/records/EmptyRecordAnnotationTest.java
index 2446b14..1624c29 100644
--- a/src/test/java/com/android/tools/r8/desugar/records/EmptyRecordAnnotationTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/records/EmptyRecordAnnotationTest.java
@@ -5,11 +5,13 @@
 package com.android.tools.r8.desugar.records;
 
 import static com.android.tools.r8.utils.InternalOptions.TestingOptions;
+import static org.junit.Assume.assumeTrue;
 
 import com.android.tools.r8.R8FullTestBuilder;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.TestRuntime.CfRuntime;
+import com.android.tools.r8.TestRuntime.CfVm;
+import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.StringUtils;
 import java.util.List;
 import org.junit.Test;
@@ -36,18 +38,21 @@
 
   @Parameterized.Parameters(name = "{0}")
   public static List<Object[]> data() {
-    // TODO(b/174431251): This should be replaced with .withCfRuntimes(start = jdk16).
     return buildParameters(
         getTestParameters()
-            .withCustomRuntime(CfRuntime.getCheckedInJdk17())
+            .withCfRuntimesStartingFromIncluding(CfVm.JDK17)
             .withDexRuntimes()
             .withAllApiLevelsAlsoForCf()
             .build());
   }
 
+  private boolean isDefaultCfParameters() {
+    return parameters.isCfRuntime() && parameters.getApiLevel().equals(AndroidApiLevel.B);
+  }
+
   @Test
   public void testD8AndJvm() throws Exception {
-    if (parameters.isCfRuntime()) {
+    if (isDefaultCfParameters()) {
       testForJvm()
           .addProgramClassFileData(PROGRAM_DATA)
           .run(parameters.getRuntime(), MAIN_TYPE)
@@ -64,6 +69,7 @@
 
   @Test
   public void testR8() throws Exception {
+    assumeTrue(parameters.isDexRuntime() || isDefaultCfParameters());
     R8FullTestBuilder builder =
         testForR8(parameters.getBackend())
             .addProgramClassFileData(PROGRAM_DATA)
diff --git a/src/test/java/com/android/tools/r8/desugar/records/EmptyRecordTest.java b/src/test/java/com/android/tools/r8/desugar/records/EmptyRecordTest.java
index 7a64c3c..5f1b68f 100644
--- a/src/test/java/com/android/tools/r8/desugar/records/EmptyRecordTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/records/EmptyRecordTest.java
@@ -9,7 +9,7 @@
 import com.android.tools.r8.R8FullTestBuilder;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.TestRuntime.CfRuntime;
+import com.android.tools.r8.TestRuntime.CfVm;
 import com.android.tools.r8.utils.StringUtils;
 import java.util.List;
 import org.junit.Test;
@@ -33,10 +33,9 @@
 
   @Parameterized.Parameters(name = "{0}")
   public static List<Object[]> data() {
-    // TODO(b/174431251): This should be replaced with .withCfRuntimes(start = jdk17).
     return buildParameters(
         getTestParameters()
-            .withCustomRuntime(CfRuntime.getCheckedInJdk17())
+            .withCfRuntimesStartingFromIncluding(CfVm.JDK17)
             .withDexRuntimes()
             .withAllApiLevelsAlsoForCf()
             .build());
diff --git a/src/test/java/com/android/tools/r8/desugar/records/RecordInstanceOfTest.java b/src/test/java/com/android/tools/r8/desugar/records/RecordInstanceOfTest.java
index 1c32b27..743b2b4 100644
--- a/src/test/java/com/android/tools/r8/desugar/records/RecordInstanceOfTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/records/RecordInstanceOfTest.java
@@ -7,7 +7,7 @@
 import com.android.tools.r8.R8FullTestBuilder;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.TestRuntime.CfRuntime;
+import com.android.tools.r8.TestRuntime.CfVm;
 import com.android.tools.r8.utils.InternalOptions.TestingOptions;
 import com.android.tools.r8.utils.StringUtils;
 import java.util.List;
@@ -31,10 +31,9 @@
 
   @Parameterized.Parameters(name = "{0}")
   public static List<Object[]> data() {
-    // TODO(b/174431251): This should be replaced with .withCfRuntimes(start = jdk17).
     return buildParameters(
         getTestParameters()
-            .withCustomRuntime(CfRuntime.getCheckedInJdk17())
+            .withCfRuntimesStartingFromIncluding(CfVm.JDK17)
             .withDexRuntimes()
             .withAllApiLevelsAlsoForCf()
             .build());
diff --git a/src/test/java/com/android/tools/r8/desugar/records/RecordInvokeCustomTest.java b/src/test/java/com/android/tools/r8/desugar/records/RecordInvokeCustomTest.java
index 7207f6d..61ee05b 100644
--- a/src/test/java/com/android/tools/r8/desugar/records/RecordInvokeCustomTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/records/RecordInvokeCustomTest.java
@@ -7,7 +7,7 @@
 import com.android.tools.r8.R8FullTestBuilder;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.TestRuntime.CfRuntime;
+import com.android.tools.r8.TestRuntime.CfVm;
 import com.android.tools.r8.utils.InternalOptions.TestingOptions;
 import com.android.tools.r8.utils.StringUtils;
 import java.util.List;
@@ -48,10 +48,9 @@
 
   @Parameterized.Parameters(name = "{0}")
   public static List<Object[]> data() {
-    // TODO(b/174431251): This should be replaced with .withCfRuntimes(start = jdk17).
     return buildParameters(
         getTestParameters()
-            .withCustomRuntime(CfRuntime.getCheckedInJdk17())
+            .withCfRuntimesStartingFromIncluding(CfVm.JDK17)
             .withDexRuntimes()
             .withAllApiLevelsAlsoForCf()
             .build());
diff --git a/src/test/java/com/android/tools/r8/desugar/records/RecordLibMergeTest.java b/src/test/java/com/android/tools/r8/desugar/records/RecordLibMergeTest.java
index 7c32b98..53eff63 100644
--- a/src/test/java/com/android/tools/r8/desugar/records/RecordLibMergeTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/records/RecordLibMergeTest.java
@@ -7,7 +7,7 @@
 import com.android.tools.r8.R8FullTestBuilder;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.TestRuntime.CfRuntime;
+import com.android.tools.r8.TestRuntime.CfVm;
 import com.android.tools.r8.utils.InternalOptions.TestingOptions;
 import com.android.tools.r8.utils.StringUtils;
 import java.nio.file.Path;
@@ -34,10 +34,9 @@
 
   @Parameterized.Parameters(name = "{0}")
   public static List<Object[]> data() {
-    // TODO(b/174431251): This should be replaced with .withCfRuntimes(start = jdk17).
     return buildParameters(
         getTestParameters()
-            .withCustomRuntime(CfRuntime.getCheckedInJdk17())
+            .withCfRuntimesStartingFromIncluding(CfVm.JDK17)
             .withDexRuntimes()
             .withAllApiLevelsAlsoForCf()
             .build());
diff --git a/src/test/java/com/android/tools/r8/desugar/records/RecordMergeTest.java b/src/test/java/com/android/tools/r8/desugar/records/RecordMergeTest.java
index 8401196..cf3c955 100644
--- a/src/test/java/com/android/tools/r8/desugar/records/RecordMergeTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/records/RecordMergeTest.java
@@ -4,15 +4,25 @@
 
 package com.android.tools.r8.desugar.records;
 
+import static com.android.tools.r8.DiagnosticsMatcher.diagnosticType;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isAbsent;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertThrows;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.CompilationFailedException;
 import com.android.tools.r8.D8TestCompileResult;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.TestRuntime.CfRuntime;
-import com.android.tools.r8.utils.BooleanUtils;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.errors.DuplicateTypesDiagnostic;
+import com.android.tools.r8.errors.MissingGlobalSyntheticsConsumerDiagnostic;
+import com.android.tools.r8.synthesis.globals.GlobalSyntheticsConsumerAndProvider;
 import com.android.tools.r8.utils.InternalOptions.TestingOptions;
 import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import java.nio.file.Path;
-import java.util.List;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
@@ -34,66 +44,93 @@
       StringUtils.lines("Jane Doe", "42", "Jane Doe", "42");
 
   private final TestParameters parameters;
-  private final boolean intermediate;
 
-  public RecordMergeTest(TestParameters parameters, boolean intermediate) {
+  public RecordMergeTest(TestParameters parameters) {
     this.parameters = parameters;
-    this.intermediate = intermediate;
   }
 
-  @Parameterized.Parameters(name = "{0}, intermediate: {1}")
-  public static List<Object[]> data() {
-    // TODO(b/174431251): This should be replaced with .withCfRuntimes(start = jdk17).
-    return buildParameters(
-        getTestParameters()
-            .withCustomRuntime(CfRuntime.getCheckedInJdk17())
-            .withDexRuntimes()
-            .withAllApiLevelsAlsoForCf()
-            .build(),
-        BooleanUtils.values());
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimes().withAllApiLevelsAlsoForCf().build();
+  }
+
+  @Test
+  public void testFailureWithoutGlobalSyntheticsConsumer() throws Exception {
+    assertThrows(
+        CompilationFailedException.class,
+        () ->
+            testForD8(parameters.getBackend())
+                .addProgramClassFileData(PROGRAM_DATA_1)
+                .setMinApi(parameters.getApiLevel())
+                .addOptionsModification(TestingOptions::allowExperimentClassFileVersion)
+                .setIntermediate(true)
+                .compileWithExpectedDiagnostics(
+                    diagnostics ->
+                        diagnostics
+                            .assertOnlyErrors()
+                            .assertErrorsMatch(
+                                diagnosticType(MissingGlobalSyntheticsConsumerDiagnostic.class))));
   }
 
   @Test
   public void testMergeDesugaredInputs() throws Exception {
+    GlobalSyntheticsConsumerAndProvider globals1 = new GlobalSyntheticsConsumerAndProvider();
     Path output1 =
         testForD8(parameters.getBackend())
             .addProgramClassFileData(PROGRAM_DATA_1)
             .setMinApi(parameters.getApiLevel())
             .addOptionsModification(TestingOptions::allowExperimentClassFileVersion)
-            .setIntermediate(intermediate)
+            .setIntermediate(true)
+            .apply(b -> b.getBuilder().setGlobalSyntheticsConsumer(globals1))
             .compile()
+            .inspect(this::assertDoesNotHaveRecordTag)
             .writeToZip();
+
+    GlobalSyntheticsConsumerAndProvider globals2 = new GlobalSyntheticsConsumerAndProvider();
     Path output2 =
         testForD8(parameters.getBackend())
             .addProgramClassFileData(PROGRAM_DATA_2)
             .setMinApi(parameters.getApiLevel())
             .addOptionsModification(TestingOptions::allowExperimentClassFileVersion)
-            .setIntermediate(intermediate)
+            .setIntermediate(true)
+            .apply(b -> b.getBuilder().setGlobalSyntheticsConsumer(globals2))
             .compile()
+            .inspect(this::assertDoesNotHaveRecordTag)
             .writeToZip();
+
+    assertTrue(globals1.hasBytes());
+    assertTrue(globals2.hasBytes());
+
     D8TestCompileResult result =
         testForD8(parameters.getBackend())
             .addProgramFiles(output1, output2)
+            .apply(b -> b.getBuilder().addGlobalSyntheticsResourceProviders(globals1, globals2))
             .setMinApi(parameters.getApiLevel())
             .addOptionsModification(TestingOptions::allowExperimentClassFileVersion)
-            .compile();
+            .compile()
+            .inspect(this::assertHasRecordTag);
+
     result.run(parameters.getRuntime(), MAIN_TYPE_1).assertSuccessWithOutput(EXPECTED_RESULT_1);
     result.run(parameters.getRuntime(), MAIN_TYPE_2).assertSuccessWithOutput(EXPECTED_RESULT_2);
   }
 
   @Test
   public void testMergeDesugaredAndNonDesugaredInputs() throws Exception {
+    GlobalSyntheticsConsumerAndProvider globals1 = new GlobalSyntheticsConsumerAndProvider();
     Path output1 =
         testForD8(parameters.getBackend())
             .addProgramClassFileData(PROGRAM_DATA_1)
             .setMinApi(parameters.getApiLevel())
             .addOptionsModification(TestingOptions::allowExperimentClassFileVersion)
-            .setIntermediate(intermediate)
+            .setIntermediate(true)
+            .apply(b -> b.getBuilder().setGlobalSyntheticsConsumer(globals1))
             .compile()
             .writeToZip();
+
     D8TestCompileResult result =
         testForD8(parameters.getBackend())
             .addProgramFiles(output1)
+            .apply(b -> b.getBuilder().addGlobalSyntheticsResourceProviders(globals1))
             .addProgramClassFileData(PROGRAM_DATA_2)
             .setMinApi(parameters.getApiLevel())
             .addOptionsModification(TestingOptions::allowExperimentClassFileVersion)
@@ -101,4 +138,48 @@
     result.run(parameters.getRuntime(), MAIN_TYPE_1).assertSuccessWithOutput(EXPECTED_RESULT_1);
     result.run(parameters.getRuntime(), MAIN_TYPE_2).assertSuccessWithOutput(EXPECTED_RESULT_2);
   }
+
+  @Test
+  public void testMergeNonIntermediates() throws Exception {
+    Path output1 =
+        testForD8(parameters.getBackend())
+            .addProgramClassFileData(PROGRAM_DATA_1)
+            .setMinApi(parameters.getApiLevel())
+            .addOptionsModification(TestingOptions::allowExperimentClassFileVersion)
+            .compile()
+            .inspect(this::assertHasRecordTag)
+            .writeToZip();
+
+    Path output2 =
+        testForD8(parameters.getBackend())
+            .addProgramClassFileData(PROGRAM_DATA_2)
+            .setMinApi(parameters.getApiLevel())
+            .addOptionsModification(TestingOptions::allowExperimentClassFileVersion)
+            .compile()
+            .inspect(this::assertHasRecordTag)
+            .writeToZip();
+
+    assertThrows(
+        CompilationFailedException.class,
+        () ->
+            testForD8(parameters.getBackend())
+                .addProgramFiles(output1, output2)
+                .setMinApi(parameters.getApiLevel())
+                .addOptionsModification(TestingOptions::allowExperimentClassFileVersion)
+                .compileWithExpectedDiagnostics(
+                    diagnostics ->
+                        diagnostics
+                            .assertOnlyErrors()
+                            .assertErrorsMatch(diagnosticType(DuplicateTypesDiagnostic.class))));
+  }
+
+  private void assertHasRecordTag(CodeInspector inspector) {
+    // Note: this should be asserting on record tag.
+    assertThat(inspector.clazz("java.lang.Record"), isPresent());
+  }
+
+  private void assertDoesNotHaveRecordTag(CodeInspector inspector) {
+    // Note: this should be asserting on record tag.
+    assertThat(inspector.clazz("java.lang.Record"), isAbsent());
+  }
 }
diff --git a/src/test/java/com/android/tools/r8/desugar/records/RecordReflectionTest.java b/src/test/java/com/android/tools/r8/desugar/records/RecordReflectionTest.java
index 82891db..7b1096d 100644
--- a/src/test/java/com/android/tools/r8/desugar/records/RecordReflectionTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/records/RecordReflectionTest.java
@@ -6,7 +6,7 @@
 
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.TestRuntime.CfRuntime;
+import com.android.tools.r8.TestRuntime.CfVm;
 import com.android.tools.r8.utils.InternalOptions.TestingOptions;
 import com.android.tools.r8.utils.StringUtils;
 import java.util.List;
@@ -40,9 +40,8 @@
 
   @Parameterized.Parameters(name = "{0}")
   public static List<Object[]> data() {
-    // TODO(b/174431251): This should be replaced with .withCfRuntimes(start = jdk17).
     return buildParameters(
-        getTestParameters().withCustomRuntime(CfRuntime.getCheckedInJdk17()).build());
+        getTestParameters().withCfRuntimesStartingFromIncluding(CfVm.JDK17).build());
   }
 
   @Test
diff --git a/src/test/java/com/android/tools/r8/desugar/records/RecordWithMembersTest.java b/src/test/java/com/android/tools/r8/desugar/records/RecordWithMembersTest.java
index f22f8cd..eb8ef2d 100644
--- a/src/test/java/com/android/tools/r8/desugar/records/RecordWithMembersTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/records/RecordWithMembersTest.java
@@ -7,7 +7,7 @@
 import com.android.tools.r8.R8FullTestBuilder;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.TestRuntime.CfRuntime;
+import com.android.tools.r8.TestRuntime.CfVm;
 import com.android.tools.r8.utils.InternalOptions.TestingOptions;
 import com.android.tools.r8.utils.StringUtils;
 import java.util.List;
@@ -33,10 +33,9 @@
 
   @Parameterized.Parameters(name = "{0}")
   public static List<Object[]> data() {
-    // TODO(b/174431251): This should be replaced with .withCfRuntimes(start = jdk17).
     return buildParameters(
         getTestParameters()
-            .withCustomRuntime(CfRuntime.getCheckedInJdk17())
+            .withCfRuntimesStartingFromIncluding(CfVm.JDK17)
             .withDexRuntimes()
             .withAllApiLevelsAlsoForCf()
             .build());
diff --git a/src/test/java/com/android/tools/r8/desugar/records/SimpleRecordTest.java b/src/test/java/com/android/tools/r8/desugar/records/SimpleRecordTest.java
index 56dab1e..212098c 100644
--- a/src/test/java/com/android/tools/r8/desugar/records/SimpleRecordTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/records/SimpleRecordTest.java
@@ -4,16 +4,20 @@
 
 package com.android.tools.r8.desugar.records;
 
+import static org.junit.Assume.assumeTrue;
 
+import com.android.tools.r8.GlobalSyntheticsConsumer;
 import com.android.tools.r8.R8FullTestBuilder;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestRuntime.CfRuntime;
+import com.android.tools.r8.TestRuntime.CfVm;
+import com.android.tools.r8.synthesis.globals.GlobalSyntheticsConsumerAndProvider;
+import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.InternalOptions.TestingOptions;
 import com.android.tools.r8.utils.StringUtils;
 import java.nio.file.Path;
 import java.util.List;
-import org.junit.Assume;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
@@ -35,23 +39,30 @@
 
   @Parameterized.Parameters(name = "{0}")
   public static List<Object[]> data() {
-    // TODO(b/174431251): This should be replaced with .withCfRuntimes(start = jdk17).
     return buildParameters(
         getTestParameters()
-            .withCustomRuntime(CfRuntime.getCheckedInJdk17())
-            .withDexRuntimes()
+            .withAllRuntimes()
             .withAllApiLevelsAlsoForCf()
             .build());
   }
 
+  private boolean isCfWithNativeRecordSupport() {
+    return parameters.isCfRuntime()
+        && parameters.asCfRuntime().isNewerThanOrEqual(CfVm.JDK14)
+        && parameters.getApiLevel().equals(AndroidApiLevel.B);
+  }
+
   @Test
-  public void testD8AndJvm() throws Exception {
-    if (parameters.isCfRuntime()) {
-      testForJvm()
-          .addProgramClassFileData(PROGRAM_DATA)
-          .run(parameters.getRuntime(), MAIN_TYPE)
-          .assertSuccessWithOutput(EXPECTED_RESULT);
-    }
+  public void testReference() throws Exception {
+    assumeTrue(isCfWithNativeRecordSupport());
+    testForJvm()
+        .addProgramClassFileData(PROGRAM_DATA)
+        .run(parameters.getRuntime(), MAIN_TYPE)
+        .assertSuccessWithOutput(EXPECTED_RESULT);
+  }
+
+  @Test
+  public void testD8() throws Exception {
     testForD8(parameters.getBackend())
         .addProgramClassFileData(PROGRAM_DATA)
         .setMinApi(parameters.getApiLevel())
@@ -66,10 +77,12 @@
 
   @Test
   public void testD8Intermediate() throws Exception {
-    Assume.assumeTrue(parameters.isDexRuntime());
-    Path path = compileIntermediate();
+    assumeTrue(parameters.isDexRuntime());
+    GlobalSyntheticsConsumerAndProvider globals = new GlobalSyntheticsConsumerAndProvider();
+    Path path = compileIntermediate(globals);
     testForD8()
         .addProgramFiles(path)
+        .apply(b -> b.getBuilder().addGlobalSyntheticsResourceProviders(globals))
         .setMinApi(parameters.getApiLevel())
         .setIncludeClassesChecksum(true)
         .run(parameters.getRuntime(), MAIN_TYPE)
@@ -78,11 +91,13 @@
 
   @Test
   public void testD8IntermediateNoDesugaringInStep2() throws Exception {
-    Assume.assumeTrue(parameters.isDexRuntime());
-    Path path = compileIntermediate();
+    assumeTrue(parameters.isDexRuntime());
+    GlobalSyntheticsConsumerAndProvider globals = new GlobalSyntheticsConsumerAndProvider();
+    Path path = compileIntermediate(globals);
     // In Android Studio they disable desugaring at this point to improve build speed.
     testForD8()
         .addProgramFiles(path)
+        .apply(b -> b.getBuilder().addGlobalSyntheticsResourceProviders(globals))
         .setMinApi(parameters.getApiLevel())
         .setIncludeClassesChecksum(true)
         .disableDesugaring()
@@ -90,19 +105,22 @@
         .assertSuccessWithOutput(EXPECTED_RESULT);
   }
 
-  private Path compileIntermediate() throws Exception {
+  private Path compileIntermediate(GlobalSyntheticsConsumer globalSyntheticsConsumer)
+      throws Exception {
     return testForD8(Backend.DEX)
         .addProgramClassFileData(PROGRAM_DATA)
         .setMinApi(parameters.getApiLevel())
         .addOptionsModification(TestingOptions::allowExperimentClassFileVersion)
         .setIntermediate(true)
         .setIncludeClassesChecksum(true)
+        .apply(b -> b.getBuilder().setGlobalSyntheticsConsumer(globalSyntheticsConsumer))
         .compile()
         .writeToZip();
   }
 
   @Test
   public void testR8() throws Exception {
+    assumeTrue(parameters.isDexRuntime() || isCfWithNativeRecordSupport());
     R8FullTestBuilder builder =
         testForR8(parameters.getBackend())
             .addProgramClassFileData(PROGRAM_DATA)
@@ -129,6 +147,7 @@
 
   @Test
   public void testR8NoMinification() throws Exception {
+    assumeTrue(parameters.isDexRuntime() || isCfWithNativeRecordSupport());
     R8FullTestBuilder builder =
         testForR8(parameters.getBackend())
             .addProgramClassFileData(PROGRAM_DATA)
diff --git a/src/test/java/com/android/tools/r8/desugar/records/UnusedRecordFieldTest.java b/src/test/java/com/android/tools/r8/desugar/records/UnusedRecordFieldTest.java
index 5c520cc..24ae5d7 100644
--- a/src/test/java/com/android/tools/r8/desugar/records/UnusedRecordFieldTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/records/UnusedRecordFieldTest.java
@@ -9,7 +9,7 @@
 import com.android.tools.r8.R8FullTestBuilder;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.TestRuntime.CfRuntime;
+import com.android.tools.r8.TestRuntime.CfVm;
 import com.android.tools.r8.utils.StringUtils;
 import java.util.List;
 import org.junit.Test;
@@ -32,10 +32,9 @@
 
   @Parameterized.Parameters(name = "{0}")
   public static List<Object[]> data() {
-    // TODO(b/174431251): This should be replaced with .withCfRuntimes(start = jdk16).
     return buildParameters(
         getTestParameters()
-            .withCustomRuntime(CfRuntime.getCheckedInJdk17())
+            .withCfRuntimesStartingFromIncluding(CfVm.JDK17)
             .withDexRuntimes()
             .withAllApiLevelsAlsoForCf()
             .build());
diff --git a/src/test/java/com/android/tools/r8/desugar/records/UnusedRecordMethodTest.java b/src/test/java/com/android/tools/r8/desugar/records/UnusedRecordMethodTest.java
index 4c2ddf5..702cc75 100644
--- a/src/test/java/com/android/tools/r8/desugar/records/UnusedRecordMethodTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/records/UnusedRecordMethodTest.java
@@ -9,7 +9,7 @@
 import com.android.tools.r8.R8FullTestBuilder;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.TestRuntime.CfRuntime;
+import com.android.tools.r8.TestRuntime.CfVm;
 import com.android.tools.r8.utils.StringUtils;
 import java.util.List;
 import org.junit.Test;
@@ -32,10 +32,9 @@
 
   @Parameterized.Parameters(name = "{0}")
   public static List<Object[]> data() {
-    // TODO(b/174431251): This should be replaced with .withCfRuntimes(start = jdk16).
     return buildParameters(
         getTestParameters()
-            .withCustomRuntime(CfRuntime.getCheckedInJdk17())
+            .withCfRuntimesStartingFromIncluding(CfVm.JDK17)
             .withDexRuntimes()
             .withAllApiLevelsAlsoForCf()
             .build());
diff --git a/src/test/java/com/android/tools/r8/desugar/records/UnusedRecordReflectionTest.java b/src/test/java/com/android/tools/r8/desugar/records/UnusedRecordReflectionTest.java
index 7317cea..0687934 100644
--- a/src/test/java/com/android/tools/r8/desugar/records/UnusedRecordReflectionTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/records/UnusedRecordReflectionTest.java
@@ -9,7 +9,7 @@
 import com.android.tools.r8.R8FullTestBuilder;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.TestRuntime.CfRuntime;
+import com.android.tools.r8.TestRuntime.CfVm;
 import com.android.tools.r8.utils.StringUtils;
 import java.util.List;
 import org.junit.Test;
@@ -32,10 +32,9 @@
 
   @Parameterized.Parameters(name = "{0}")
   public static List<Object[]> data() {
-    // TODO(b/174431251): This should be replaced with .withCfRuntimes(start = jdk16).
     return buildParameters(
         getTestParameters()
-            .withCustomRuntime(CfRuntime.getCheckedInJdk17())
+            .withCfRuntimesStartingFromIncluding(CfVm.JDK17)
             .withDexRuntimes()
             .withAllApiLevelsAlsoForCf()
             .build());
diff --git a/src/test/java/com/android/tools/r8/desugar/sealed/SealedAttributeTest.java b/src/test/java/com/android/tools/r8/desugar/sealed/SealedAttributeTest.java
index 35293b5..3a5ba83 100644
--- a/src/test/java/com/android/tools/r8/desugar/sealed/SealedAttributeTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/sealed/SealedAttributeTest.java
@@ -12,7 +12,7 @@
 import com.android.tools.r8.CompilationFailedException;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.TestRuntime;
+import com.android.tools.r8.TestRuntime.CfVm;
 import com.android.tools.r8.examples.jdk17.Sealed;
 import com.android.tools.r8.utils.AndroidApiLevel;
 import java.util.List;
@@ -24,17 +24,18 @@
 @RunWith(Parameterized.class)
 public class SealedAttributeTest extends TestBase {
 
+  private final TestParameters parameters;
   private final Backend backend;
 
-  @Parameters(name = "{0}")
+  @Parameters(name = "{0}, backend:{1}")
   public static List<Object[]> data() {
-    // TODO(b/174431251): This should be replaced with .withCfRuntimes(start = jdk17).
     return buildParameters(
-        getTestParameters().withCustomRuntime(TestRuntime.getCheckedInJdk17()).build(),
+        getTestParameters().withCfRuntimesStartingFromIncluding(CfVm.JDK17).build(),
         Backend.values());
   }
 
   public SealedAttributeTest(TestParameters parameters, Backend backend) {
+    this.parameters = parameters;
     this.backend = backend;
   }
 
@@ -43,7 +44,7 @@
     assumeTrue(backend == Backend.CF);
     testForJvm()
         .addRunClasspathFiles(Sealed.jar())
-        .run(TestRuntime.getCheckedInJdk17(), Sealed.Main.typeName())
+        .run(parameters.getRuntime(), Sealed.Main.typeName())
         .assertSuccessWithOutputLines("R8 compiler", "D8 compiler");
   }
 
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/ConstClassEnumUnboxingTest.java b/src/test/java/com/android/tools/r8/enumunboxing/ConstClassEnumUnboxingTest.java
new file mode 100644
index 0000000..8f1d0ab
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/enumunboxing/ConstClassEnumUnboxingTest.java
@@ -0,0 +1,53 @@
+// Copyright (c) 2022, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.enumunboxing;
+
+import com.android.tools.r8.TestParameters;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class ConstClassEnumUnboxingTest extends EnumUnboxingTestBase {
+
+  @Parameter(0)
+  public TestParameters parameters;
+
+  @Parameter(1)
+  public EnumKeepRules enumKeepRules;
+
+  @Parameters(name = "{0}, keep: {1}")
+  public static List<Object[]> data() {
+    return buildParameters(
+        getTestParameters().withAllRuntimesAndApiLevels().build(), getAllEnumKeepRules());
+  }
+
+  @Test
+  public void testEnumUnboxing() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(ConstClassEnumUnboxingTest.class)
+        .addKeepMainRule(Main.class)
+        .addKeepRules(enumKeepRules.getKeepRules())
+        .addEnumUnboxingInspector(inspector -> inspector.assertUnboxed(MyEnum.class))
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines("int");
+  }
+
+  enum MyEnum {
+    A
+  }
+
+  static class Main {
+
+    public static void main(String[] args) {
+      System.out.println(MyEnum.class.getName());
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/NullValuedFieldEnumUnboxingTest.java b/src/test/java/com/android/tools/r8/enumunboxing/NullValuedFieldEnumUnboxingTest.java
new file mode 100644
index 0000000..db8a047
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/enumunboxing/NullValuedFieldEnumUnboxingTest.java
@@ -0,0 +1,63 @@
+// Copyright (c) 2022, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.enumunboxing;
+
+import com.android.tools.r8.TestParameters;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class NullValuedFieldEnumUnboxingTest extends EnumUnboxingTestBase {
+
+  @Parameter(0)
+  public TestParameters parameters;
+
+  @Parameter(1)
+  public EnumKeepRules enumKeepRules;
+
+  @Parameters(name = "{0}, keep: {1}")
+  public static List<Object[]> data() {
+    return buildParameters(
+        getTestParameters().withAllRuntimesAndApiLevels().build(), getAllEnumKeepRules());
+  }
+
+  @Test
+  public void testEnumUnboxing() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(NullValuedFieldEnumUnboxingTest.class)
+        .addKeepMainRule(Main.class)
+        .addKeepRules(enumKeepRules.getKeepRules())
+        .addEnumUnboxingInspector(inspector -> inspector.assertUnboxed(MyEnum.class))
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines("a", "null");
+  }
+
+  enum MyEnum {
+    A("a"),
+    B(null);
+
+    String value;
+
+    MyEnum(String value) {
+      this.value = value;
+    }
+  }
+
+  static class Main {
+
+    public static void main(String[] args) {
+      MyEnum a = System.currentTimeMillis() > 0 ? MyEnum.A : MyEnum.B;
+      MyEnum b = System.currentTimeMillis() > 0 ? MyEnum.B : MyEnum.A;
+      System.out.println(a.value);
+      System.out.println(b.value);
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/VirtualMethodOverrideEnumUnboxingTest.java b/src/test/java/com/android/tools/r8/enumunboxing/VirtualMethodOverrideEnumUnboxingTest.java
index c11a623..9dcd9cb 100644
--- a/src/test/java/com/android/tools/r8/enumunboxing/VirtualMethodOverrideEnumUnboxingTest.java
+++ b/src/test/java/com/android/tools/r8/enumunboxing/VirtualMethodOverrideEnumUnboxingTest.java
@@ -6,7 +6,7 @@
 
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
 import static org.hamcrest.MatcherAssert.assertThat;
-import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.assertFalse;
 
 import com.android.tools.r8.KeepConstantArguments;
 import com.android.tools.r8.NeverClassInline;
@@ -67,7 +67,8 @@
     MethodSubject methodOnB =
         inspector.clazz(B.class).uniqueMethodWithFinalName(methodOnA.getFinalName());
     assertThat(methodOnB, isPresent());
-    assertTrue(methodOnB.streamInstructions().anyMatch(x -> x.asDexInstruction().isInvokeSuper()));
+    // TODO(b/171784168): Should be true.
+    assertFalse(methodOnB.streamInstructions().anyMatch(x -> x.asDexInstruction().isInvokeSuper()));
   }
 
   static class TestClass {
diff --git a/src/test/java/com/android/tools/r8/examples/newarray/NewArrayTestRunner.java b/src/test/java/com/android/tools/r8/examples/newarray/NewArrayTestRunner.java
index d980c97..219dbed 100644
--- a/src/test/java/com/android/tools/r8/examples/newarray/NewArrayTestRunner.java
+++ b/src/test/java/com/android/tools/r8/examples/newarray/NewArrayTestRunner.java
@@ -3,24 +3,34 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.examples.newarray;
 
+import static org.junit.Assume.assumeFalse;
 import static org.junit.Assume.assumeTrue;
 
 import com.android.tools.r8.CompilationMode;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.utils.BooleanUtils;
 import com.google.common.collect.ImmutableList;
 import java.util.List;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
 
 @RunWith(Parameterized.class)
 public class NewArrayTestRunner extends TestBase {
 
-  static final Class<?> CLASS = NewArray.class;
+  private static final Class<?> CLASS = NewArray.class;
 
-  private final TestParameters parameters;
-  private final CompilationMode mode;
+  @Parameter(0)
+  public boolean enableMultiANewArrayDesugaringForClassFiles;
+
+  @Parameter(1)
+  public TestParameters parameters;
+
+  @Parameter(2)
+  public CompilationMode mode;
 
   private static final List<String> EXPECTED =
       ImmutableList.of(
@@ -56,20 +66,19 @@
           "8,8,8,8",
           "2,4,6,8,10,12,14,16,false,0,0,0,0,0.0,0.0,null");
 
-  @Parameterized.Parameters(name = "{0}, {1}")
+  @Parameters(name = "{1}, {2}, force desugaring: {0}")
   public static List<Object[]> data() {
     return buildParameters(
-        getTestParameters().withAllRuntimes().withAllApiLevels().build(), CompilationMode.values());
-  }
-
-  public NewArrayTestRunner(TestParameters parameters, CompilationMode mode) {
-    this.parameters = parameters;
-    this.mode = mode;
+        BooleanUtils.values(),
+        getTestParameters().withAllRuntimesAndApiLevels().build(),
+        CompilationMode.values());
   }
 
   @Test
   public void runReference() throws Exception {
-    assumeTrue(parameters.isCfRuntime() && mode == CompilationMode.DEBUG);
+    assumeFalse(enableMultiANewArrayDesugaringForClassFiles);
+    assumeTrue(parameters.isCfRuntime());
+    assumeTrue(mode == CompilationMode.DEBUG);
     testForJvm(getStaticTemp())
         .addProgramClassesAndInnerClasses(CLASS)
         .run(parameters.getRuntime(), CLASS)
@@ -78,6 +87,7 @@
 
   @Test
   public void testD8() throws Exception {
+    assumeFalse(enableMultiANewArrayDesugaringForClassFiles);
     assumeTrue(parameters.isDexRuntime());
     testForD8()
         .addProgramClassesAndInnerClasses(CLASS)
@@ -89,9 +99,14 @@
 
   @Test
   public void testR8() throws Exception {
+    assumeTrue(parameters.isCfRuntime() || !enableMultiANewArrayDesugaringForClassFiles);
     testForR8(parameters.getBackend())
         .addProgramClassesAndInnerClasses(CLASS)
         .addKeepMainRule(CLASS)
+        .addOptionsModification(
+            options ->
+                options.testing.enableMultiANewArrayDesugaringForClassFiles =
+                    enableMultiANewArrayDesugaringForClassFiles)
         .setMinApi(parameters.getApiLevel())
         .setMode(mode)
         .run(parameters.getRuntime(), CLASS)
diff --git a/src/test/java/com/android/tools/r8/graph/TargetLookupTest.java b/src/test/java/com/android/tools/r8/graph/TargetLookupTest.java
index 06666db..d3e91bb 100644
--- a/src/test/java/com/android/tools/r8/graph/TargetLookupTest.java
+++ b/src/test/java/com/android/tools/r8/graph/TargetLookupTest.java
@@ -96,7 +96,10 @@
     CodeInspector inspector = new CodeInspector(appInfo.app());
     ProgramMethod method = getMethod(inspector, DEFAULT_CLASS_NAME, "int", "x", ImmutableList.of());
     assertFalse(
-        appInfo.resolveMethodOnClass(method.getReference()).getSingleTarget().isVirtualMethod());
+        appInfo
+            .resolveMethodOnClassHolder(method.getReference())
+            .getSingleTarget()
+            .isVirtualMethod());
     assertNull(appInfo.lookupDirectTarget(method.getReference(), method));
     assertNotNull(appInfo.lookupStaticTarget(method.getReference(), method));
 
@@ -178,14 +181,14 @@
 
     assertFalse(
         appInfo
-            .resolveMethodOnClass(methodXOnTestSuper.getReference(), classTestSuper)
+            .resolveMethodOnClass(classTestSuper, methodXOnTestSuper.getReference())
             .getSingleTarget()
             .isVirtualMethod());
     assertNull(
         appInfo
-            .resolveMethodOnClass(methodXOnTestSuper.getReference(), classTest)
+            .resolveMethodOnClass(classTest, methodXOnTestSuper.getReference())
             .getSingleTarget());
-    assertNull(appInfo.resolveMethodOnClass(methodXOnTestReference, classTest).getSingleTarget());
+    assertNull(appInfo.resolveMethodOnClass(classTest, methodXOnTestReference).getSingleTarget());
 
     assertNull(appInfo.lookupDirectTarget(methodXOnTestSuper.getReference(), methodXOnTestSuper));
     assertNull(appInfo.lookupDirectTarget(methodXOnTestReference, methodYOnTest));
diff --git a/src/test/java/com/android/tools/r8/graph/invokestatic/InvokeStaticOnInterfaceTest.java b/src/test/java/com/android/tools/r8/graph/invokestatic/InvokeStaticOnInterfaceTest.java
index a94f02a..61d3d32 100644
--- a/src/test/java/com/android/tools/r8/graph/invokestatic/InvokeStaticOnInterfaceTest.java
+++ b/src/test/java/com/android/tools/r8/graph/invokestatic/InvokeStaticOnInterfaceTest.java
@@ -4,6 +4,7 @@
 
 package com.android.tools.r8.graph.invokestatic;
 
+import static org.hamcrest.CoreMatchers.allOf;
 import static org.hamcrest.CoreMatchers.containsString;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
@@ -48,11 +49,11 @@
             .run(parameters.getRuntime(), Main.class);
     if (parameters.getRuntime().asCf().isNewerThan(CfVm.JDK8)) {
       runResult.assertFailureWithErrorThatMatches(
-          containsString(
-              "java.lang.IncompatibleClassChangeError: Method "
-                  + I.class.getTypeName()
-                  + ".foo()V"
-                  + " must be InterfaceMethodref constant"));
+          allOf(
+              containsString("java.lang.IncompatibleClassChangeError: Method"),
+              // JVM method formatting changed between jdk11 and jdk17
+              containsString(I.class.getTypeName() + ".foo()"),
+              containsString("must be InterfaceMethodref constant")));
     } else {
       runResult.assertSuccessWithOutputLines("Hello World!");
     }
@@ -85,10 +86,11 @@
             .run(parameters.getRuntime(), Main.class);
     if (parameters.getRuntime().asCf().isNewerThan(CfVm.JDK8)) {
       runResult.assertFailureWithErrorThatMatches(
-          containsString(
-              "java.lang.IncompatibleClassChangeError: Method"
-                  + " com.android.tools.r8.graph.invokestatic.InvokeStaticOnInterfaceTest$I.foo()V"
-                  + " must be InterfaceMethodref constant"));
+          allOf(
+              containsString("java.lang.IncompatibleClassChangeError: Method"),
+              containsString(
+                  "com.android.tools.r8.graph.invokestatic.InvokeStaticOnInterfaceTest$I.foo()"),
+              containsString("must be InterfaceMethodref constant")));
     } else {
       runResult.assertSuccessWithOutputLines("Hello World!");
     }
diff --git a/src/test/java/com/android/tools/r8/graph/invokevirtual/InvokeVirtualPrivateBaseWithDefaultDirectInvokeTest.java b/src/test/java/com/android/tools/r8/graph/invokevirtual/InvokeVirtualPrivateBaseWithDefaultDirectInvokeTest.java
index a8a222d..9df5458 100644
--- a/src/test/java/com/android/tools/r8/graph/invokevirtual/InvokeVirtualPrivateBaseWithDefaultDirectInvokeTest.java
+++ b/src/test/java/com/android/tools/r8/graph/invokevirtual/InvokeVirtualPrivateBaseWithDefaultDirectInvokeTest.java
@@ -12,6 +12,7 @@
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.TestRuntime.CfVm;
+import com.android.tools.r8.utils.AndroidApiLevel;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
@@ -31,9 +32,13 @@
     this.parameters = parameters;
   }
 
+  private boolean isDefaultCfParameters() {
+    return parameters.isCfRuntime() && parameters.getApiLevel().equals(AndroidApiLevel.B);
+  }
+
   @Test
   public void testJvm() throws Exception {
-    assumeTrue(parameters.isCfRuntime());
+    assumeTrue(isDefaultCfParameters());
     testForJvm()
         .addInnerClasses(getClass())
         .run(parameters.getRuntime(), Main.class)
@@ -51,6 +56,7 @@
 
   @Test
   public void testR8() throws Exception {
+    assumeTrue(parameters.isDexRuntime() || isDefaultCfParameters());
     testForR8(parameters.getBackend())
         .addInnerClasses(getClass())
         .addKeepMainRule(Main.class)
@@ -70,7 +76,9 @@
         (nonDesugaredCf && parameters.isCfRuntime())
             || parameters.canUseDefaultAndStaticInterfaceMethodsWhenDesugaring();
     // JDK 11 allows this incorrect dispatch for some reason.
-    if (parameters.isCfRuntime(CfVm.JDK11) && isNotDesugared) {
+    if (parameters.isCfRuntime()
+        && parameters.asCfRuntime().isNewerThanOrEqual(CfVm.JDK11)
+        && isNotDesugared) {
       result.assertSuccessWithOutputLines("I::foo");
       return;
     }
diff --git a/src/test/java/com/android/tools/r8/internal/GMSCoreLatestTest.java b/src/test/java/com/android/tools/r8/internal/GMSCoreLatestTest.java
index c9d943e..4ad8b97 100644
--- a/src/test/java/com/android/tools/r8/internal/GMSCoreLatestTest.java
+++ b/src/test/java/com/android/tools/r8/internal/GMSCoreLatestTest.java
@@ -65,9 +65,6 @@
             builder ->
                 builder.addOptionsModification(
                     options -> {
-                      options
-                          .getOpenClosedInterfacesOptions()
-                          .suppressArrayAssignmentsToJavaLangSerializable();
                       options.testing.processingContextsConsumer =
                           id -> assertNull(idsRoundOne.put(id, id));
                     }));
@@ -80,9 +77,6 @@
             builder ->
                 builder.addOptionsModification(
                     options -> {
-                      options
-                          .getOpenClosedInterfacesOptions()
-                          .suppressArrayAssignmentsToJavaLangSerializable();
                       options.testing.processingContextsConsumer =
                           id -> {
                             AssertionUtils.assertNotNull(idsRoundOne.get(id));
diff --git a/src/test/java/com/android/tools/r8/internal/GMSCoreV10Test.java b/src/test/java/com/android/tools/r8/internal/GMSCoreV10Test.java
index 34b77bd..a6277d6 100644
--- a/src/test/java/com/android/tools/r8/internal/GMSCoreV10Test.java
+++ b/src/test/java/com/android/tools/r8/internal/GMSCoreV10Test.java
@@ -63,9 +63,6 @@
             builder ->
                 builder.addOptionsModification(
                     options -> {
-                      options
-                          .getOpenClosedInterfacesOptions()
-                          .suppressArrayAssignmentsToJavaLangSerializable();
                       options.testing.processingContextsConsumer =
                           id -> assertTrue(idsRoundOne.add(id));
                     }));
@@ -78,9 +75,6 @@
             builder ->
                 builder.addOptionsModification(
                     options -> {
-                      options
-                          .getOpenClosedInterfacesOptions()
-                          .suppressArrayAssignmentsToJavaLangSerializable();
                       options.testing.processingContextsConsumer =
                           id -> assertTrue(idsRoundTwo.add(id));
                     }));
@@ -104,9 +98,6 @@
                 builder.addOptionsModification(
                     options -> {
                       options.testing.forceJumboStringProcessing = true;
-                      options
-                          .getOpenClosedInterfacesOptions()
-                          .suppressArrayAssignmentsToJavaLangSerializable();
                     }))
         .runDex2Oat(parameters.getRuntime())
         .assertNoVerificationErrors();
diff --git a/src/test/java/com/android/tools/r8/internal/R8GMSCoreLookupTest.java b/src/test/java/com/android/tools/r8/internal/R8GMSCoreLookupTest.java
index 2f98c5c..956d59e 100644
--- a/src/test/java/com/android/tools/r8/internal/R8GMSCoreLookupTest.java
+++ b/src/test/java/com/android/tools/r8/internal/R8GMSCoreLookupTest.java
@@ -72,11 +72,11 @@
     // Check lookup will produce the same result.
     DexMethod id = method.getReference();
     assertEquals(
-        appInfo().resolveMethodOnClass(method.getReference(), id.holder).getSingleTarget(), method);
+        appInfo().resolveMethodOnClass(id.holder, method.getReference()).getSingleTarget(), method);
 
     // Check lookup targets with include method.
     MethodResolutionResult resolutionResult =
-        appInfo().resolveMethodOnClass(method.getReference(), clazz);
+        appInfo().resolveMethodOnClass(clazz, method.getReference());
     AppInfoWithLiveness appInfo = null; // TODO(b/154881041): Remove or compute liveness.
     LookupResult lookupResult =
         resolutionResult.lookupVirtualDispatchTargets(
diff --git a/src/test/java/com/android/tools/r8/internal/proto/Proto2ShrinkingTest.java b/src/test/java/com/android/tools/r8/internal/proto/Proto2ShrinkingTest.java
index 63dae9c..51acd65 100644
--- a/src/test/java/com/android/tools/r8/internal/proto/Proto2ShrinkingTest.java
+++ b/src/test/java/com/android/tools/r8/internal/proto/Proto2ShrinkingTest.java
@@ -23,6 +23,7 @@
 import com.google.common.collect.ImmutableSet;
 import java.nio.file.Path;
 import java.util.List;
+import org.junit.Ignore;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
diff --git a/src/test/java/com/android/tools/r8/ir/IrInjectionTestBase.java b/src/test/java/com/android/tools/r8/ir/IrInjectionTestBase.java
index 328f9ff..8b89088 100644
--- a/src/test/java/com/android/tools/r8/ir/IrInjectionTestBase.java
+++ b/src/test/java/com/android/tools/r8/ir/IrInjectionTestBase.java
@@ -4,7 +4,6 @@
 package com.android.tools.r8.ir;
 
 import com.android.tools.r8.ToolHelper;
-import com.android.tools.r8.dex.ApplicationReader;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexApplication;
 import com.android.tools.r8.graph.DexEncodedMethod;
@@ -14,7 +13,6 @@
 import com.android.tools.r8.ir.code.InstructionListIterator;
 import com.android.tools.r8.ir.code.NumberGenerator;
 import com.android.tools.r8.ir.conversion.IRConverter;
-import com.android.tools.r8.smali.SmaliBuilder;
 import com.android.tools.r8.smali.SmaliBuilder.MethodSignature;
 import com.android.tools.r8.smali.SmaliTestBase;
 import com.android.tools.r8.utils.AndroidApp;
@@ -26,7 +24,6 @@
 import java.io.IOException;
 import java.util.List;
 import java.util.concurrent.ExecutionException;
-import org.antlr.runtime.RecognitionException;
 
 public class IrInjectionTestBase extends SmaliTestBase {
 
@@ -105,7 +102,7 @@
     public String run() throws IOException {
       Timing timing = Timing.empty();
       IRConverter converter = new IRConverter(appView, timing, null);
-      converter.replaceCodeForTesting(method, code);
+      converter.replaceCodeForTesting(code);
       AndroidApp app = writeDex();
       return runOnArtRaw(app, DEFAULT_MAIN_CLASS_NAME).stdout;
     }
diff --git a/src/test/java/com/android/tools/r8/ir/LinearFlowIteratorTest.java b/src/test/java/com/android/tools/r8/ir/LinearFlowIteratorTest.java
index f191b3b..9d4d133 100644
--- a/src/test/java/com/android/tools/r8/ir/LinearFlowIteratorTest.java
+++ b/src/test/java/com/android/tools/r8/ir/LinearFlowIteratorTest.java
@@ -111,10 +111,9 @@
   public void nextWillContinueThroughGotoBlocks() throws Exception {
     IRCode code = simpleCode();
     InstructionListIterator it = new LinearFlowInstructionListIterator(code, code.entryBlock());
-    it.next(); // Argument
-    it.next(); // ConstNumber 0/NULL
-    it.next(); // ArrayGet
-    assert it.next().isReturn(); // Return
+    assertTrue(it.next().isArgument());
+    assertTrue(it.next().isConstNumber());
+    assertTrue(it.next().isThrow());
   }
 
   @Test
@@ -154,9 +153,8 @@
     InstructionListIterator it = new LinearFlowInstructionListIterator(code, code.blocks.get(1));
     Instruction current = it.previous();
     assertTrue(current.isConstNumber() && current.getOutType().isReferenceType());
-    it.next();
-    current = it.next();
-    assertTrue(current.isArrayGet());
+    assertTrue(it.next().isConstNumber());
+    assertTrue(it.next().isThrow());
   }
 
   @Test
diff --git a/src/test/java/com/android/tools/r8/ir/SplitBlockTest.java b/src/test/java/com/android/tools/r8/ir/SplitBlockTest.java
index 1662269..a376a2e 100644
--- a/src/test/java/com/android/tools/r8/ir/SplitBlockTest.java
+++ b/src/test/java/com/android/tools/r8/ir/SplitBlockTest.java
@@ -87,6 +87,7 @@
     // Try split between all non-argument instructions in the first block.
     for (int i = argumentInstructions; i < firstBlockInstructions; i++) {
       TestApplication test = codeWithoutCatchHandlers();
+      AppView<?> appView = test.appView;
       IRCode code = test.code;
       assertEquals(initialBlockCount, code.blocks.size());
 
@@ -100,7 +101,7 @@
 
       InstructionListIterator iterator = test.listIteratorAt(block, i);
       BasicBlock newBlock = iterator.split(code);
-      assertTrue(code.isConsistentSSA());
+      assertTrue(code.isConsistentSSA(appView));
 
       assertEquals(initialBlockCount + 1, code.blocks.size());
       assertEquals(i + 1, code.entryBlock().getInstructions().size());
@@ -121,6 +122,7 @@
     // Try split out all non-argument instructions in the first block.
     for (int i = argumentInstructions; i < firstBlockInstructions - 1; i++) {
       TestApplication test = codeWithoutCatchHandlers();
+      AppView<?> appView = test.appView;
       IRCode code = test.code;
       assertEquals(initialBlockCount, code.blocks.size());
 
@@ -134,7 +136,7 @@
 
       InstructionListIterator iterator = test.listIteratorAt(block, i);
       BasicBlock newBlock = iterator.split(code, 1);
-      assertTrue(code.isConsistentSSA());
+      assertTrue(code.isConsistentSSA(appView));
 
       assertEquals(initialBlockCount + 2, code.blocks.size());
       assertEquals(i + 1, code.entryBlock().getInstructions().size());
@@ -208,6 +210,7 @@
     // Try split between all instructions in second block.
     for (int i = 1; i < secondBlockInstructions; i++) {
       TestApplication test = codeWithCatchHandlers(codeThrows, twoGuards);
+      AppView<?> appView = test.appView;
       IRCode code = test.code;
       assertEquals(initialBlockCount, code.blocks.size());
 
@@ -217,7 +220,7 @@
 
       InstructionListIterator iterator = test.listIteratorAt(block, i);
       BasicBlock newBlock = iterator.split(code);
-      assertTrue(code.isConsistentSSA());
+      assertTrue(code.isConsistentSSA(appView));
 
       assertEquals(initialBlockCount + 1, code.blocks.size());
       assertEquals(i + 1, code.blocks.get(1).getInstructions().size());
@@ -247,6 +250,7 @@
     // Try split out all instructions in second block.
     for (int i = 1; i < secondBlockInstructions - 1; i++) {
       TestApplication test = codeWithCatchHandlers(codeThrows, twoGuards);
+      AppView<?> appView = test.appView;
       IRCode code = test.code;
       assertEquals(initialBlockCount, code.blocks.size());
 
@@ -256,7 +260,7 @@
 
       InstructionListIterator iterator = test.listIteratorAt(block, i);
       BasicBlock newBlock = iterator.split(code, 1);
-      assertTrue(code.isConsistentSSA());
+      assertTrue(code.isConsistentSSA(appView));
 
       assertEquals(initialBlockCount + 2, code.blocks.size());
       assertEquals(i + 1, code.blocks.get(1).getInstructions().size());
@@ -323,6 +327,7 @@
     // Try split between all non-argument instructions in the first block.
     for (int i = argumentInstructions; i < firstBlockInstructions; i++) {
       TestApplication test = codeWithIf(hitTrueBranch);
+      AppView<?> appView = test.appView;
       IRCode code = test.code;
       assertEquals(initialBlockCount, code.blocks.size());
 
@@ -336,7 +341,7 @@
 
       InstructionListIterator iterator = test.listIteratorAt(block, i);
       BasicBlock newBlock = iterator.split(code);
-      assertTrue(code.isConsistentSSA());
+      assertTrue(code.isConsistentSSA(appView));
 
       assertEquals(initialBlockCount + 1, code.blocks.size());
       assertEquals(i + 1, code.entryBlock().getInstructions().size());
@@ -444,6 +449,7 @@
     // Try split between all non-argument instructions in the first block.
     for (int i = argumentInstructions; i < firstBlockInstructions; i++) {
       TestApplication test = codeWithSwitch(hitCase);
+      AppView<?> appView = test.appView;
       IRCode code = test.code;
       assertEquals(initialBlockCount, code.blocks.size());
 
@@ -457,7 +463,7 @@
 
       InstructionListIterator iterator = test.listIteratorAt(block, i);
       BasicBlock newBlock = iterator.split(code);
-      assertTrue(code.isConsistentSSA());
+      assertTrue(code.isConsistentSSA(appView));
 
       assertEquals(initialBlockCount + 1, code.blocks.size());
       assertEquals(i + 1, code.entryBlock().getInstructions().size());
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/ConstantRemovalTest.java b/src/test/java/com/android/tools/r8/ir/optimize/ConstantRemovalTest.java
index e4ee9bc..1427b24 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/ConstantRemovalTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/ConstantRemovalTest.java
@@ -22,6 +22,7 @@
 import com.android.tools.r8.ir.code.Position.SyntheticPosition;
 import com.android.tools.r8.ir.code.Return;
 import com.android.tools.r8.ir.code.Value;
+import com.android.tools.r8.ir.conversion.MethodConversionOptions.MutableMethodConversionOptions;
 import com.android.tools.r8.ir.regalloc.LinearScanRegisterAllocator;
 import com.android.tools.r8.ir.regalloc.LiveIntervals;
 import com.android.tools.r8.origin.Origin;
@@ -145,8 +146,9 @@
             new NumberGenerator(),
             basicBlockNumberGenerator,
             IRMetadata.unknown(),
-            Origin.unknown());
-    PeepholeOptimizer.optimize(code, new MockLinearScanRegisterAllocator(appView, code));
+            Origin.unknown(),
+            new MutableMethodConversionOptions(options));
+    PeepholeOptimizer.optimize(appView, code, new MockLinearScanRegisterAllocator(appView, code));
 
     // Check that all four constant number instructions remain.
     assertEquals(
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/NonNullTrackerTest.java b/src/test/java/com/android/tools/r8/ir/optimize/NonNullTrackerTest.java
index 19ff50d..9d6d411 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/NonNullTrackerTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/NonNullTrackerTest.java
@@ -65,7 +65,7 @@
     AssumeInserter assumeInserter = new AssumeInserter(appView);
 
     assumeInserter.insertAssumeInstructions(code, Timing.empty());
-    assertTrue(code.isConsistentSSA());
+    assertTrue(code.isConsistentSSA(appView));
     checkCountOfNonNull(code, expectedNumberOfNonNull);
 
     if (testAugmentedIRCode != null) {
@@ -73,7 +73,7 @@
     }
 
     CodeRewriter.removeAssumeInstructions(appView, code);
-    assertTrue(code.isConsistentSSA());
+    assertTrue(code.isConsistentSSA(appView));
     checkCountOfNonNull(code, 0);
   }
 
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/TrivialGotoEliminationTest.java b/src/test/java/com/android/tools/r8/ir/optimize/TrivialGotoEliminationTest.java
index 868036c..7bbc000 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/TrivialGotoEliminationTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/TrivialGotoEliminationTest.java
@@ -28,6 +28,7 @@
 import com.android.tools.r8.ir.code.Return;
 import com.android.tools.r8.ir.code.Throw;
 import com.android.tools.r8.ir.code.Value;
+import com.android.tools.r8.ir.conversion.MethodConversionOptions.MutableMethodConversionOptions;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.InternalOptions;
@@ -53,7 +54,9 @@
   private final IRMetadata metadata = IRMetadata.unknown();
 
   @Test
-  public void trivialGotoInEntryBlock() {
+  public void trivialGotoInEntryBlock() throws Exception {
+    AppView<AppInfo> appView = computeAppView(AndroidApp.builder().build());
+    InternalOptions options = appView.options();
     // Setup silly block structure:
     //
     // block0:
@@ -92,7 +95,6 @@
     // Check that the goto in block0 remains. There was a bug in the trivial goto elimination
     // that ended up removing that goto changing the code to start with the unreachable
     // throw.
-    InternalOptions options = new InternalOptions();
     options.debug = true;
     IRCode code =
         new IRCode(
@@ -102,8 +104,9 @@
             new NumberGenerator(),
             basicBlockNumberGenerator,
             IRMetadata.unknown(),
-            Origin.unknown());
-    CodeRewriter.collapseTrivialGotos(code);
+            Origin.unknown(),
+            new MutableMethodConversionOptions(options));
+    CodeRewriter.collapseTrivialGotos(appView, code);
     assertTrue(code.entryBlock().isTrivialGoto());
     assertTrue(blocks.contains(block0));
     assertTrue(blocks.contains(block1));
@@ -189,8 +192,9 @@
             new NumberGenerator(),
             basicBlockNumberGenerator,
             IRMetadata.unknown(),
-            Origin.unknown());
-    CodeRewriter.collapseTrivialGotos(code);
+            Origin.unknown(),
+            new MutableMethodConversionOptions(options));
+    CodeRewriter.collapseTrivialGotos(appView, code);
     assertTrue(block0.getInstructions().get(1).isIf());
     assertEquals(block1, block0.getInstructions().get(1).asIf().fallthroughBlock());
     assertTrue(blocks.containsAll(ImmutableList.of(block0, block1, block2, block3)));
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/ifs/IfThrowNullPointerExceptionTest.java b/src/test/java/com/android/tools/r8/ir/optimize/ifs/IfThrowNullPointerExceptionTest.java
index d0b977b..53a83d5 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/ifs/IfThrowNullPointerExceptionTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/ifs/IfThrowNullPointerExceptionTest.java
@@ -14,6 +14,7 @@
 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.ir.code.BasicBlock;
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.Instruction;
@@ -47,7 +48,7 @@
     testForJvm()
         .addTestClasspath()
         .run(parameters.getRuntime(), TestClass.class)
-        .assertSuccessWithOutput(getExpectedStdout());
+        .assertSuccessWithOutput(getExpectedStdout(false));
   }
 
   @Test
@@ -60,7 +61,7 @@
         .compile()
         .inspect(this::inspect)
         .run(parameters.getRuntime(), TestClass.class)
-        .assertSuccessWithOutput(getExpectedStdout());
+        .assertSuccessWithOutput(getExpectedStdout(false));
   }
 
   @Test
@@ -72,7 +73,7 @@
         .compile()
         .inspect(this::inspect)
         .run(parameters.getRuntime(), TestClass.class)
-        .assertSuccessWithOutput(getExpectedStdout());
+        .assertSuccessWithOutput(getExpectedStdout(true));
   }
 
   private void inspect(CodeInspector inspector) {
@@ -114,7 +115,21 @@
     }
   }
 
-  private String getExpectedStdout() {
+  private String getExpectedStdout(boolean isR8) {
+    if (parameters.isCfRuntime() && parameters.asCfRuntime().isNewerThanOrEqual(CfVm.JDK17)) {
+      // Newer JVMs have added support for printing the expression and local causing the NPE.
+      if (isR8) {
+        return StringUtils.lines(
+            "Caught NPE: Cannot invoke \"Object.getClass()\" because \"<parameter1>\" is null",
+            "Caught NPE: x was null",
+            "Caught NPE: Cannot invoke \"Object.getClass()\" because \"<parameter1>\" is null");
+      } else {
+        return StringUtils.lines(
+            "Caught NPE: null",
+            "Caught NPE: x was null",
+            "Caught NPE: Cannot throw exception because \"null\" is null");
+      }
+    }
     if (parameters.isCfRuntime() || isDalvik()) {
       return StringUtils.lines("Caught NPE: null", "Caught NPE: x was null", "Caught NPE: null");
     }
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/instanceofremoval/ArrayInstanceOfCloneableAndSerializableTest.java b/src/test/java/com/android/tools/r8/ir/optimize/instanceofremoval/ArrayInstanceOfCloneableAndSerializableTest.java
new file mode 100644
index 0000000..fc8ca51
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/instanceofremoval/ArrayInstanceOfCloneableAndSerializableTest.java
@@ -0,0 +1,92 @@
+// Copyright (c) 2022, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.optimize.instanceofremoval;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.codeinspector.InstructionSubject;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import java.io.Serializable;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class ArrayInstanceOfCloneableAndSerializableTest extends TestBase {
+
+  @Parameter(0)
+  public TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  @Test
+  public void testJvm() throws Exception {
+    assumeTrue(parameters.isCfRuntime());
+    testForJvm()
+        .addTestClasspath()
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines("true", "true");
+  }
+
+  @Test
+  public void testD8() throws Exception {
+    assumeTrue(parameters.isDexRuntime());
+    testForD8()
+        .addProgramClasses(Main.class)
+        .release()
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .inspect(
+            inspector -> {
+              MethodSubject mainMethodSubject = inspector.clazz(Main.class).mainMethod();
+              assertEquals(
+                  2,
+                  mainMethodSubject
+                      .streamInstructions()
+                      .filter(InstructionSubject::isInstanceOf)
+                      .count());
+            })
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines("true", "true");
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    testForR8(parameters.getBackend())
+        .addProgramClasses(Main.class)
+        .addKeepMainRule(Main.class)
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .inspect(
+            inspector -> {
+              MethodSubject mainMethodSubject = inspector.clazz(Main.class).mainMethod();
+              assertTrue(
+                  mainMethodSubject
+                      .streamInstructions()
+                      .noneMatch(InstructionSubject::isInstanceOf));
+            })
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines("true", "true");
+  }
+
+  static class Main {
+
+    public static void main(String[] args) {
+      byte[] bytes = new byte[0];
+      System.out.println(bytes instanceof Cloneable);
+      System.out.println(bytes instanceof Serializable);
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/staticizer/ClassStaticizerTest.java b/src/test/java/com/android/tools/r8/ir/optimize/staticizer/ClassStaticizerTest.java
index 2254355..7688f9b 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/staticizer/ClassStaticizerTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/staticizer/ClassStaticizerTest.java
@@ -334,8 +334,7 @@
             "DIRECT: void movetohost.HostConflictField.<init>()",
             "STATIC: String movetohost.CandidateConflictField.bar(String)",
             "STATIC: String movetohost.CandidateConflictField.foo()",
-            "STATIC: String movetohost.MoveToHostTestClass.next()",
-            "String movetohost.CandidateConflictField.field"),
+            "STATIC: String movetohost.MoveToHostTestClass.next()"),
         references(clazz, "testConflictField", "void"));
 
     assertThat(inspector.clazz(CandidateConflictMethod.class), isPresent());
diff --git a/src/test/java/com/android/tools/r8/ir/regalloc/IdenticalAfterRegisterAllocationTest.java b/src/test/java/com/android/tools/r8/ir/regalloc/IdenticalAfterRegisterAllocationTest.java
index 9e9b1c4..4319780 100644
--- a/src/test/java/com/android/tools/r8/ir/regalloc/IdenticalAfterRegisterAllocationTest.java
+++ b/src/test/java/com/android/tools/r8/ir/regalloc/IdenticalAfterRegisterAllocationTest.java
@@ -13,6 +13,7 @@
 import com.android.tools.r8.ir.code.NumericType;
 import com.android.tools.r8.ir.code.Position;
 import com.android.tools.r8.ir.code.Value;
+import com.android.tools.r8.ir.conversion.MethodConversionOptions.MutableMethodConversionOptions;
 import com.android.tools.r8.utils.InternalOptions;
 import java.util.List;
 import org.junit.Test;
@@ -86,6 +87,8 @@
     assertTrue(value3.needsRegister());
     // value1 and value2 represent different constants and the additions are therefore
     // not equivalent.
-    assertFalse(add0.identicalAfterRegisterAllocation(add1, allocator));
+    assertFalse(
+        add0.identicalAfterRegisterAllocation(
+            add1, allocator, new MutableMethodConversionOptions(allocator.options())));
   }
 }
diff --git a/src/test/java/com/android/tools/r8/ir/regalloc/RegisterMoveSchedulerTest.java b/src/test/java/com/android/tools/r8/ir/regalloc/RegisterMoveSchedulerTest.java
index 24f1672..932c051 100644
--- a/src/test/java/com/android/tools/r8/ir/regalloc/RegisterMoveSchedulerTest.java
+++ b/src/test/java/com/android/tools/r8/ir/regalloc/RegisterMoveSchedulerTest.java
@@ -7,7 +7,6 @@
 
 import com.android.tools.r8.errors.Unimplemented;
 import com.android.tools.r8.graph.AppInfo;
-import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DebugLocalInfo;
 import com.android.tools.r8.graph.DexApplication;
@@ -112,6 +111,11 @@
     }
 
     @Override
+    public void replaceCurrentInstructionWithNullCheck(AppView<?> appView, Value object) {
+      throw new Unimplemented();
+    }
+
+    @Override
     public void replaceCurrentInstructionWithStaticGet(
         AppView<?> appView, IRCode code, DexField field, Set<Value> affectedValues) {
       throw new Unimplemented();
@@ -130,7 +134,7 @@
 
     @Override
     public void replaceCurrentInstructionWithThrowNull(
-        AppView<? extends AppInfoWithClassHierarchy> appView,
+        AppView<?> appView,
         IRCode code,
         ListIterator<BasicBlock> blockIterator,
         Set<BasicBlock> blocksToRemove,
diff --git a/src/test/java/com/android/tools/r8/java_language/pattern_matching_for_instenceof/PattternMatchingForInstanceOfTest.java b/src/test/java/com/android/tools/r8/java_language/pattern_matching_for_instenceof/PattternMatchingForInstanceOfTest.java
index 95cf5b2..e81d20c 100644
--- a/src/test/java/com/android/tools/r8/java_language/pattern_matching_for_instenceof/PattternMatchingForInstanceOfTest.java
+++ b/src/test/java/com/android/tools/r8/java_language/pattern_matching_for_instenceof/PattternMatchingForInstanceOfTest.java
@@ -7,7 +7,7 @@
 import com.android.tools.r8.R8TestBuilder;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.TestRuntime.CfRuntime;
+import com.android.tools.r8.TestRuntime.CfVm;
 import com.android.tools.r8.examples.jdk17.PatternMatchingForInstanceof;
 import com.android.tools.r8.utils.InternalOptions.TestingOptions;
 import com.google.common.collect.ImmutableList;
@@ -31,10 +31,9 @@
 
   @Parameters(name = "{0}")
   public static List<Object[]> data() {
-    // TODO(b/174431251): This should be replaced with .withCfRuntimes(start = jdk17).
     return buildParameters(
         getTestParameters()
-            .withCustomRuntime(CfRuntime.getCheckedInJdk17())
+            .withCfRuntimesStartingFromIncluding(CfVm.JDK17)
             .withDexRuntimes()
             .withAllApiLevelsAlsoForCf()
             .build());
diff --git a/src/test/java/com/android/tools/r8/kotlin/lambda/KotlinLambdaMergerValidationTest.java b/src/test/java/com/android/tools/r8/kotlin/lambda/KotlinLambdaMergerValidationTest.java
index 35087f5..3d0fa60 100644
--- a/src/test/java/com/android/tools/r8/kotlin/lambda/KotlinLambdaMergerValidationTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/lambda/KotlinLambdaMergerValidationTest.java
@@ -10,8 +10,6 @@
 import com.android.tools.r8.KotlinTestBase;
 import com.android.tools.r8.KotlinTestParameters;
 import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.TestRuntime;
-import com.android.tools.r8.TestRuntime.CfRuntime;
 import com.android.tools.r8.utils.DescriptorUtils;
 import java.nio.file.Path;
 import java.util.Collection;
@@ -43,9 +41,8 @@
 
     String pkg = getClass().getPackage().getName();
     String folder = DescriptorUtils.getBinaryNameFromJavaType(pkg);
-    CfRuntime cfRuntime = parameters.getRuntime().asCf();
     Path ktClasses =
-        kotlinc(cfRuntime, kotlinc, targetVersion)
+        kotlinc(getKotlincHostRuntime(parameters.getRuntime()), kotlinc, targetVersion)
             .addSourceFiles(getKotlinFileInTest(folder, "b143165163"))
             .compile();
     testForR8(parameters.getBackend())
@@ -68,10 +65,8 @@
 
     String pkg = getClass().getPackage().getName();
     String folder = DescriptorUtils.getBinaryNameFromJavaType(pkg);
-    CfRuntime cfRuntime =
-        parameters.isCfRuntime() ? parameters.getRuntime().asCf() : TestRuntime.getCheckedInJdk9();
     Path ktClasses =
-        kotlinc(cfRuntime, kotlinc, targetVersion)
+        kotlinc(getKotlincHostRuntime(parameters.getRuntime()), kotlinc, targetVersion)
             .addSourceFiles(getKotlinFileInTest(folder, "b143165163"))
             .compile();
     testForR8(parameters.getBackend())
diff --git a/src/test/java/com/android/tools/r8/kotlin/lambda/b159688129/LambdaGroupGCLimitTest.java b/src/test/java/com/android/tools/r8/kotlin/lambda/b159688129/LambdaGroupGCLimitTest.java
index 89f57c8..e021fe5 100644
--- a/src/test/java/com/android/tools/r8/kotlin/lambda/b159688129/LambdaGroupGCLimitTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/lambda/b159688129/LambdaGroupGCLimitTest.java
@@ -74,7 +74,7 @@
                               mergeGroup ->
                                   mergeGroup.size()
                                       <= defaultHorizontalClassMergerOptions
-                                          .getMaxClassGroupSize()));
+                                          .getMaxClassGroupSizeInR8()));
                 })
             .allowDiagnosticWarningMessages()
             .compile()
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataPrimitiveTypeRewriteTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataPrimitiveTypeRewriteTest.java
index 39d2473..4cbb2e0 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataPrimitiveTypeRewriteTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataPrimitiveTypeRewriteTest.java
@@ -6,6 +6,7 @@
 
 import static com.android.tools.r8.KotlinCompilerTool.KotlinCompilerVersion.MIN_SUPPORTED_VERSION;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresentAndRenamed;
+import static org.hamcrest.CoreMatchers.allOf;
 import static org.hamcrest.CoreMatchers.containsString;
 import static org.hamcrest.CoreMatchers.equalTo;
 import static org.hamcrest.MatcherAssert.assertThat;
@@ -119,9 +120,10 @@
       runResult.assertSuccessWithOutputLines(EXPECTED);
     } else {
       runResult.assertFailureWithErrorThatMatches(
-          containsString(
-              "java.lang.NoSuchMethodError:"
-                  + " com.android.tools.r8.kotlin.metadata.primitive_type_rewrite_lib.LibKt.foo()"));
+          allOf(
+              containsString("java.lang.NoSuchMethodError:"),
+              containsString(
+                  "com.android.tools.r8.kotlin.metadata.primitive_type_rewrite_lib.LibKt.foo()")));
     }
   }
 }
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInlinePropertyTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInlinePropertyTest.java
index 94b410e..c693a41 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInlinePropertyTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInlinePropertyTest.java
@@ -52,7 +52,7 @@
   public void smokeTest() throws Exception {
     Path libJar = libJars.getForConfiguration(kotlinc, targetVersion);
     Path output =
-        kotlinc(parameters.getRuntime().asCf(), kotlinc, targetVersion)
+        kotlinc(getKotlincHostRuntime(parameters.getRuntime()), kotlinc, targetVersion)
             .addClasspathFiles(libJar)
             .addSourceFiles(getKotlinFileInTest(PKG_PREFIX + "/inline_property_app", "main"))
             .setOutputPath(temp.newFolder().toPath())
diff --git a/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java b/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java
index 249e78d..dd40ecf 100644
--- a/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java
+++ b/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java
@@ -59,7 +59,9 @@
 import com.android.tools.r8.ir.code.Position;
 import com.android.tools.r8.ir.code.Position.SyntheticPosition;
 import com.android.tools.r8.ir.code.ValueTypeConstraint;
+import com.android.tools.r8.ir.conversion.DexBuilder;
 import com.android.tools.r8.ir.conversion.IRBuilder;
+import com.android.tools.r8.ir.conversion.MethodConversionOptions.MutableMethodConversionOptions;
 import com.android.tools.r8.ir.conversion.SourceCode;
 import com.android.tools.r8.ir.regalloc.LinearScanRegisterAllocator;
 import com.android.tools.r8.ir.regalloc.RegisterAllocator;
@@ -868,9 +870,16 @@
                 .disableAndroidApiLevelCheck()
                 .build();
         ProgramMethod programMethod = new ProgramMethod(programClass, method);
-        IRCode ir = code.buildIR(programMethod, appView, Origin.unknown());
+        IRCode ir =
+            code.buildIR(
+                programMethod,
+                appView,
+                Origin.unknown(),
+                new MutableMethodConversionOptions(options));
         RegisterAllocator allocator = new LinearScanRegisterAllocator(appView, ir);
-        method.setCode(ir, BytecodeMetadataProvider.empty(), allocator, appView);
+        programMethod.setCode(
+            new DexBuilder(ir, BytecodeMetadataProvider.empty(), allocator, options).build(),
+            appView);
         directMethods[i] = method;
       }
       programClass.getMethodCollection().addDirectMethods(Arrays.asList(directMethods));
diff --git a/src/test/java/com/android/tools/r8/missingclasses/MissingClassReferencedFromFieldAnnotationTest.java b/src/test/java/com/android/tools/r8/missingclasses/MissingClassReferencedFromFieldAnnotationTest.java
index 71e9638..c0c9739 100644
--- a/src/test/java/com/android/tools/r8/missingclasses/MissingClassReferencedFromFieldAnnotationTest.java
+++ b/src/test/java/com/android/tools/r8/missingclasses/MissingClassReferencedFromFieldAnnotationTest.java
@@ -31,13 +31,20 @@
   @Test(expected = CompilationFailedException.class)
   public void testNoRules() throws Exception {
     compileWithExpectedDiagnostics(
-        Main.class, diagnostics -> inspectDiagnosticsWithNoRules(diagnostics, referencedFrom));
+        Main.class,
+        diagnostics -> inspectDiagnosticsWithNoRules(diagnostics, referencedFrom),
+        builder -> builder.addKeepClassAndMembersRules(Main.class));
   }
 
   @Test
   public void testDontWarnMainClass() throws Exception {
     compileWithExpectedDiagnostics(
-        Main.class, TestDiagnosticMessages::assertNoMessages, addDontWarn(Main.class));
+        Main.class,
+        TestDiagnosticMessages::assertNoMessages,
+        builder ->
+            builder
+                .addDontWarn(MissingRuntimeAnnotation.class)
+                .addKeepClassAndMembersRules(Main.class));
   }
 
   @Test
@@ -45,7 +52,10 @@
     compileWithExpectedDiagnostics(
         Main.class,
         TestDiagnosticMessages::assertNoMessages,
-        addDontWarn(MissingRuntimeAnnotation.class));
+        builder ->
+            builder
+                .addDontWarn(MissingRuntimeAnnotation.class)
+                .addKeepClassAndMembersRules(Main.class));
   }
 
   @Test
@@ -53,7 +63,11 @@
     compileWithExpectedDiagnostics(
         Main.class,
         diagnostics -> inspectDiagnosticsWithIgnoreWarnings(diagnostics, referencedFrom),
-        addIgnoreWarnings());
+        builder ->
+            builder
+                .addIgnoreWarnings()
+                .addKeepClassAndMembersRules(Main.class)
+                .allowDiagnosticWarningMessages());
   }
 
   @Override
diff --git a/src/test/java/com/android/tools/r8/naming/applymapping/sourcelibrary/MemberResolutionAsmTest.java b/src/test/java/com/android/tools/r8/naming/applymapping/sourcelibrary/MemberResolutionAsmTest.java
index da28db3..20403fb 100644
--- a/src/test/java/com/android/tools/r8/naming/applymapping/sourcelibrary/MemberResolutionAsmTest.java
+++ b/src/test/java/com/android/tools/r8/naming/applymapping/sourcelibrary/MemberResolutionAsmTest.java
@@ -14,6 +14,7 @@
 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.ToolHelper.DexVm.Version;
 import com.android.tools.r8.naming.applymapping.shared.NoMappingDumps.HasMappingDump;
 import com.android.tools.r8.naming.applymapping.shared.NoMappingDumps.NoMappingDump;
@@ -158,7 +159,9 @@
 
   private String getMethodSignature(String type, String method) {
     if (parameters.isCfRuntime()) {
-      return type + "." + method + "()V";
+      return parameters.asCfRuntime().isNewerThanOrEqual(CfVm.JDK17)
+          ? ("void " + type + "." + method + "()")
+          : (type + "." + method + "()V");
     }
     assert parameters.isDexRuntime();
     Version version = parameters.getRuntime().asDex().getVm().getVersion();
diff --git a/src/test/java/com/android/tools/r8/resolution/ArrayTargetLookupTest.java b/src/test/java/com/android/tools/r8/resolution/ArrayTargetLookupTest.java
index f13728a..7ea76aa 100644
--- a/src/test/java/com/android/tools/r8/resolution/ArrayTargetLookupTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/ArrayTargetLookupTest.java
@@ -50,18 +50,18 @@
         };
     DexEncodedMethod langObjectNotifyMethod =
         appInfo
-            .resolveMethodOnClass(
+            .resolveMethodOnClassHolder(
                 factory.createMethod(fooType, factory.createProto(factory.voidType), "notify"))
             .getSingleTarget();
     for (DexType arrType : arrayTypes) {
       assertNull(
           appInfo
-              .resolveMethodOnClass(
+              .resolveMethodOnClassHolder(
                   factory.createMethod(arrType, factory.createProto(arrType), "clone"))
               .getSingleTarget());
       DexEncodedMethod target =
           appInfo
-              .resolveMethodOnClass(
+              .resolveMethodOnClassHolder(
                   factory.createMethod(arrType, factory.createProto(factory.voidType), "notify"))
               .getSingleTarget();
       assertEquals(langObjectNotifyMethod, target);
diff --git a/src/test/java/com/android/tools/r8/resolution/InvokeSuperCallInStaticTest.java b/src/test/java/com/android/tools/r8/resolution/InvokeSuperCallInStaticTest.java
index 8d5a997..67007d2c 100644
--- a/src/test/java/com/android/tools/r8/resolution/InvokeSuperCallInStaticTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/InvokeSuperCallInStaticTest.java
@@ -59,7 +59,7 @@
             Main.class);
     AppInfoWithLiveness appInfo = appView.appInfo();
     DexMethod method = buildNullaryVoidMethod(Base.class, "collect", appInfo.dexItemFactory());
-    MethodResolutionResult resolutionResult = appInfo.resolveMethodOnClass(method);
+    MethodResolutionResult resolutionResult = appInfo.resolveMethodOnClassHolder(method);
     assertTrue(resolutionResult.isSingleResolution());
     DexProgramClass context =
         appView.definitionForProgramType(buildType(A.class, appInfo.dexItemFactory()));
diff --git a/src/test/java/com/android/tools/r8/resolution/SingleResolutionWithFailingDispatchTest.java b/src/test/java/com/android/tools/r8/resolution/SingleResolutionWithFailingDispatchTest.java
index a5acf15..df001ed 100644
--- a/src/test/java/com/android/tools/r8/resolution/SingleResolutionWithFailingDispatchTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/SingleResolutionWithFailingDispatchTest.java
@@ -49,7 +49,7 @@
   }
 
   private void inspectRunResult(TestRunResult<?> runResult) {
-    if (parameters.isCfRuntime(CfVm.JDK11)) {
+    if (parameters.isCfRuntime() && parameters.asCfRuntime().isNewerThanOrEqual(CfVm.JDK11)) {
       runResult.assertFailureWithErrorThatThrows(AbstractMethodError.class);
     } else {
       runResult.assertFailureWithErrorThatThrows(IncompatibleClassChangeError.class);
diff --git a/src/test/java/com/android/tools/r8/resolution/SingleTargetLookupTest.java b/src/test/java/com/android/tools/r8/resolution/SingleTargetLookupTest.java
index 8c72e58..76cf55a 100644
--- a/src/test/java/com/android/tools/r8/resolution/SingleTargetLookupTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/SingleTargetLookupTest.java
@@ -194,7 +194,7 @@
         buildNullaryVoidMethod(invokeReceiver, methodName, appInfo.dexItemFactory());
     ProgramMethod context =
         appInfo.definitionForProgramType(reference.holder).getProgramDefaultInitializer();
-    Assert.assertNotNull(appInfo.resolveMethodOnClass(reference).getSingleTarget());
+    Assert.assertNotNull(appInfo.resolveMethodOnClassHolder(reference).getSingleTarget());
     DexEncodedMethod singleVirtualTarget =
         appInfo.lookupSingleVirtualTarget(appView, reference, context, false);
     if (singleTargetHolderOrNull == null) {
@@ -209,8 +209,8 @@
   @Test
   public void lookupVirtualTargets() {
     DexMethod method = buildNullaryVoidMethod(invokeReceiver, methodName, appInfo.dexItemFactory());
-    Assert.assertNotNull(appInfo.resolveMethodOnClass(method).getSingleTarget());
-    MethodResolutionResult resolutionResult = appInfo.resolveMethodOnClass(method);
+    Assert.assertNotNull(appInfo.resolveMethodOnClassHolder(method).getSingleTarget());
+    MethodResolutionResult resolutionResult = appInfo.resolveMethodOnClassHolder(method);
     if (resolutionResult.isVirtualTarget()) {
       LookupResult lookupResult =
           resolutionResult.lookupVirtualDispatchTargets(
diff --git a/src/test/java/com/android/tools/r8/resolution/VirtualOverrideOfPrivateStaticMethodTest.java b/src/test/java/com/android/tools/r8/resolution/VirtualOverrideOfPrivateStaticMethodTest.java
index 9a14cad..9de13d2 100644
--- a/src/test/java/com/android/tools/r8/resolution/VirtualOverrideOfPrivateStaticMethodTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/VirtualOverrideOfPrivateStaticMethodTest.java
@@ -11,7 +11,6 @@
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
-import com.android.tools.r8.ToolHelper.DexVm;
 import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.MethodResolutionResult;
@@ -92,7 +91,7 @@
   @Test
   public void resolveTarget() {
     MethodResolutionResult resolutionResult =
-        appInfo.resolveMethodOnClass(methodOnB, methodOnB.holder);
+        appInfo.resolveMethodOnClass(methodOnB.holder, methodOnB);
     DexClass context = appInfo.definitionFor(methodOnB.holder);
     assertTrue(resolutionResult.isIllegalAccessErrorResult(context, appInfo));
   }
diff --git a/src/test/java/com/android/tools/r8/resolution/VirtualOverrideOfPrivateStaticMethodWithVirtualParentTest.java b/src/test/java/com/android/tools/r8/resolution/VirtualOverrideOfPrivateStaticMethodWithVirtualParentTest.java
index 8171081..d8ad8dd 100644
--- a/src/test/java/com/android/tools/r8/resolution/VirtualOverrideOfPrivateStaticMethodWithVirtualParentTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/VirtualOverrideOfPrivateStaticMethodWithVirtualParentTest.java
@@ -108,7 +108,7 @@
   @Test
   public void testResolution() {
     MethodResolutionResult resolutionResult =
-        appInfo.resolveMethodOnClass(methodOnB, methodOnB.holder);
+        appInfo.resolveMethodOnClass(methodOnB.holder, methodOnB);
     assertTrue(resolutionResult.isFailedResolution());
   }
 
diff --git a/src/test/java/com/android/tools/r8/resolution/VirtualOverrideOfStaticMethodWithVirtualParentTest.java b/src/test/java/com/android/tools/r8/resolution/VirtualOverrideOfStaticMethodWithVirtualParentTest.java
index ac0cdef..c40f080 100644
--- a/src/test/java/com/android/tools/r8/resolution/VirtualOverrideOfStaticMethodWithVirtualParentTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/VirtualOverrideOfStaticMethodWithVirtualParentTest.java
@@ -175,7 +175,7 @@
   public void lookupSingleTarget() {
     DexProgramClass bClass = appView.definitionForProgramType(methodOnB.holder);
     MethodResolutionResult resolutionResult =
-        appInfo.resolveMethodOnClass(methodOnB, methodOnB.holder);
+        appInfo.resolveMethodOnClass(methodOnB.holder, methodOnB);
     DexEncodedMethod resolved = resolutionResult.getSingleTarget();
     assertEquals(methodOnA, resolved.getReference());
     assertFalse(resolutionResult.isVirtualTarget());
@@ -188,7 +188,7 @@
   @Test
   public void lookupVirtualTargets() {
     MethodResolutionResult resolutionResult =
-        appInfo.resolveMethodOnClass(methodOnB, methodOnB.holder);
+        appInfo.resolveMethodOnClass(methodOnB.holder, methodOnB);
     DexEncodedMethod resolved = resolutionResult.getSingleTarget();
     assertEquals(methodOnA, resolved.getReference());
     assertFalse(resolutionResult.isVirtualTarget());
diff --git a/src/test/java/com/android/tools/r8/resolution/access/NestInvokeSpecialMethodAccessWithIntermediateTest.java b/src/test/java/com/android/tools/r8/resolution/access/NestInvokeSpecialMethodAccessWithIntermediateTest.java
index 6289967..5638929 100644
--- a/src/test/java/com/android/tools/r8/resolution/access/NestInvokeSpecialMethodAccessWithIntermediateTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/access/NestInvokeSpecialMethodAccessWithIntermediateTest.java
@@ -4,6 +4,7 @@
 package com.android.tools.r8.resolution.access;
 
 import static com.android.tools.r8.TestRuntime.CfVm.JDK11;
+import static com.android.tools.r8.TestRuntime.CfVm.JDK17;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
@@ -186,7 +187,13 @@
         .addProgramClasses(getClasses())
         .addProgramClassFileData(getTransformedClasses())
         .run(parameters.getRuntime(), Main.class)
-        .apply(runResult -> checkExpectedResult(runResult, false));
+        .applyIf(
+            // TODO(b/227160049): Incorrect nest-based access allowed on JDK17!?
+            inSameNest
+                && parameters.isCfRuntime()
+                && parameters.asCfRuntime().isNewerThanOrEqual(JDK17),
+            runResult -> runResult.assertSuccessWithOutputLines("A::bar"),
+            runResult -> checkExpectedResult(runResult, false));
   }
 
   @Test
diff --git a/src/test/java/com/android/tools/r8/resolution/access/NestStaticMethodAccessTest.java b/src/test/java/com/android/tools/r8/resolution/access/NestStaticMethodAccessTest.java
index cbbfa70..f1a35bb 100644
--- a/src/test/java/com/android/tools/r8/resolution/access/NestStaticMethodAccessTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/access/NestStaticMethodAccessTest.java
@@ -74,7 +74,7 @@
     DexProgramClass bClass =
         appInfo.definitionFor(buildType(B.class, appInfo.dexItemFactory())).asProgramClass();
     DexMethod bar = buildMethod(A.class.getDeclaredMethod("bar"), appInfo.dexItemFactory());
-    MethodResolutionResult resolutionResult = appInfo.resolveMethodOnClass(bar);
+    MethodResolutionResult resolutionResult = appInfo.resolveMethodOnClassHolder(bar);
     assertEquals(OptionalBool.of(inSameNest), resolutionResult.isAccessibleFrom(bClass, appInfo));
   }
 
diff --git a/src/test/java/com/android/tools/r8/resolution/access/NestStaticMethodAccessWithIntermediateClassTest.java b/src/test/java/com/android/tools/r8/resolution/access/NestStaticMethodAccessWithIntermediateClassTest.java
index 795f109..d68610e 100644
--- a/src/test/java/com/android/tools/r8/resolution/access/NestStaticMethodAccessWithIntermediateClassTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/access/NestStaticMethodAccessWithIntermediateClassTest.java
@@ -4,6 +4,7 @@
 package com.android.tools.r8.resolution.access;
 
 import static com.android.tools.r8.TestRuntime.CfVm.JDK11;
+import static com.android.tools.r8.TestRuntime.CfVm.JDK17;
 import static org.hamcrest.core.StringContains.containsString;
 import static org.junit.Assert.assertEquals;
 
@@ -72,7 +73,7 @@
     DexProgramClass bClass =
         appInfo.definitionFor(buildType(B.class, appInfo.dexItemFactory())).asProgramClass();
     DexMethod bar = buildMethod(A.class.getDeclaredMethod("bar"), appInfo.dexItemFactory());
-    MethodResolutionResult resolutionResult = appInfo.resolveMethodOnClass(bar);
+    MethodResolutionResult resolutionResult = appInfo.resolveMethodOnClassHolder(bar);
     assertEquals(OptionalBool.of(inSameNest), resolutionResult.isAccessibleFrom(bClass, appInfo));
   }
 
@@ -82,7 +83,13 @@
         .addProgramClasses(getClasses())
         .addProgramClassFileData(getTransformedClasses())
         .run(parameters.getRuntime(), Main.class)
-        .apply(runResult -> checkExpectedResult(runResult, false));
+        .applyIf(
+            // TODO(b/227160049): Incorrect nest-based access allowed on JDK17!?
+            inSameNest
+                && parameters.isCfRuntime()
+                && parameters.asCfRuntime().isNewerThanOrEqual(JDK17),
+            runResult -> runResult.assertSuccessWithOutputLines("A::bar"),
+            runResult -> checkExpectedResult(runResult, false));
   }
 
   @Test
diff --git a/src/test/java/com/android/tools/r8/resolution/access/NestVirtualMethodAccessTest.java b/src/test/java/com/android/tools/r8/resolution/access/NestVirtualMethodAccessTest.java
index 5251469..ccc0430 100644
--- a/src/test/java/com/android/tools/r8/resolution/access/NestVirtualMethodAccessTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/access/NestVirtualMethodAccessTest.java
@@ -76,7 +76,7 @@
     DexProgramClass bClass =
         appInfo.definitionFor(buildType(B.class, appInfo.dexItemFactory())).asProgramClass();
     DexMethod bar = buildMethod(A.class.getDeclaredMethod("bar"), appInfo.dexItemFactory());
-    MethodResolutionResult resolutionResult = appInfo.resolveMethodOnClass(bar);
+    MethodResolutionResult resolutionResult = appInfo.resolveMethodOnClassHolder(bar);
     assertEquals(OptionalBool.of(inSameNest), resolutionResult.isAccessibleFrom(bClass, appInfo));
   }
 
diff --git a/src/test/java/com/android/tools/r8/resolution/access/NestVirtualMethodAccessWithIntermediateClassTest.java b/src/test/java/com/android/tools/r8/resolution/access/NestVirtualMethodAccessWithIntermediateClassTest.java
index 9adae80..f2f7dd9 100644
--- a/src/test/java/com/android/tools/r8/resolution/access/NestVirtualMethodAccessWithIntermediateClassTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/access/NestVirtualMethodAccessWithIntermediateClassTest.java
@@ -4,6 +4,7 @@
 package com.android.tools.r8.resolution.access;
 
 import static com.android.tools.r8.TestRuntime.CfVm.JDK11;
+import static com.android.tools.r8.TestRuntime.CfVm.JDK17;
 import static org.junit.Assert.assertEquals;
 
 import com.android.tools.r8.TestBase;
@@ -71,7 +72,7 @@
     DexProgramClass bClass =
         appInfo.definitionFor(buildType(B.class, appInfo.dexItemFactory())).asProgramClass();
     DexMethod bar = buildMethod(A.class.getDeclaredMethod("bar"), appInfo.dexItemFactory());
-    MethodResolutionResult resolutionResult = appInfo.resolveMethodOnClass(bar);
+    MethodResolutionResult resolutionResult = appInfo.resolveMethodOnClassHolder(bar);
     assertEquals(OptionalBool.of(inSameNest), resolutionResult.isAccessibleFrom(bClass, appInfo));
   }
 
@@ -81,7 +82,13 @@
         .addProgramClasses(getClasses())
         .addProgramClassFileData(getTransformedClasses())
         .run(parameters.getRuntime(), Main.class)
-        .apply(runResult -> checkExpectedResult(runResult, false));
+        .applyIf(
+            // TODO(b/227160049): Incorrect nest-based access allowed on JDK17!?
+            inSameNest
+                && parameters.isCfRuntime()
+                && parameters.asCfRuntime().isNewerThanOrEqual(JDK17),
+            runResult -> runResult.assertSuccessWithOutputLines("A::bar"),
+            runResult -> checkExpectedResult(runResult, false));
   }
 
   @Test
diff --git a/src/test/java/com/android/tools/r8/resolution/access/SelfVirtualMethodAccessTest.java b/src/test/java/com/android/tools/r8/resolution/access/SelfVirtualMethodAccessTest.java
index ea12cad..a91d3ed 100644
--- a/src/test/java/com/android/tools/r8/resolution/access/SelfVirtualMethodAccessTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/access/SelfVirtualMethodAccessTest.java
@@ -59,7 +59,7 @@
     DexProgramClass aClass =
         appInfo.definitionFor(buildType(A.class, appInfo.dexItemFactory())).asProgramClass();
     DexMethod bar = buildMethod(A.class.getDeclaredMethod("bar"), appInfo.dexItemFactory());
-    MethodResolutionResult resolutionResult = appInfo.resolveMethodOnClass(bar);
+    MethodResolutionResult resolutionResult = appInfo.resolveMethodOnClassHolder(bar);
     assertEquals(OptionalBool.TRUE, resolutionResult.isAccessibleFrom(aClass, appInfo));
   }
 
diff --git a/src/test/java/com/android/tools/r8/resolution/access/indirectmethod/IndirectMethodAccessTest.java b/src/test/java/com/android/tools/r8/resolution/access/indirectmethod/IndirectMethodAccessTest.java
index 8341bba..9ae877d 100644
--- a/src/test/java/com/android/tools/r8/resolution/access/indirectmethod/IndirectMethodAccessTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/access/indirectmethod/IndirectMethodAccessTest.java
@@ -65,7 +65,7 @@
     DexProgramClass cClass =
         appInfo.definitionFor(buildType(C.class, appInfo.dexItemFactory())).asProgramClass();
     DexMethod bar = buildMethod(B.class.getMethod("foo"), appInfo.dexItemFactory());
-    MethodResolutionResult resolutionResult = appInfo.resolveMethodOnClass(bar);
+    MethodResolutionResult resolutionResult = appInfo.resolveMethodOnClassHolder(bar);
     assertEquals(
         OptionalBool.TRUE, resolutionResult.isAccessibleForVirtualDispatchFrom(cClass, appInfo));
   }
diff --git a/src/test/java/com/android/tools/r8/resolution/interfacediamonds/AbstractAllTest.java b/src/test/java/com/android/tools/r8/resolution/interfacediamonds/AbstractAllTest.java
index 4911f70..81a0f45 100644
--- a/src/test/java/com/android/tools/r8/resolution/interfacediamonds/AbstractAllTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/interfacediamonds/AbstractAllTest.java
@@ -45,7 +45,7 @@
         buildClasses(CLASSES).addLibraryFile(parameters.getDefaultRuntimeLibrary()).build();
     AppInfoWithLiveness appInfo = computeAppViewWithLiveness(app, Main.class).appInfo();
     DexMethod method = buildNullaryVoidMethod(B.class, "f", appInfo.dexItemFactory());
-    MethodResolutionResult resolutionResult = appInfo.resolveMethodOnClass(method);
+    MethodResolutionResult resolutionResult = appInfo.resolveMethodOnClassHolder(method);
     DexEncodedMethod resolutionTarget = resolutionResult.getSingleTarget();
     // Currently R8 will resolve to L::f as that is the first in the topological search.
     // Resolution may return any of the matches, so it is valid if this expectation changes.
diff --git a/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultLeftAbstractRightTest.java b/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultLeftAbstractRightTest.java
index 47d664f..d708d1e 100644
--- a/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultLeftAbstractRightTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultLeftAbstractRightTest.java
@@ -51,7 +51,7 @@
             .buildWithLiveness()
             .appInfo();
     DexMethod method = buildNullaryVoidMethod(B.class, "f", appInfo.dexItemFactory());
-    MethodResolutionResult resolutionResult = appInfo.resolveMethodOnClass(method);
+    MethodResolutionResult resolutionResult = appInfo.resolveMethodOnClassHolder(method);
     DexEncodedMethod resolutionTarget = resolutionResult.getSingleTarget();
     assertEquals(L.class.getTypeName(), resolutionTarget.getHolderType().toSourceString());
   }
diff --git a/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultRightAbstractLeftTest.java b/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultRightAbstractLeftTest.java
index f6b2df5..8f94ef3 100644
--- a/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultRightAbstractLeftTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultRightAbstractLeftTest.java
@@ -51,7 +51,7 @@
             .buildWithLiveness()
             .appInfo();
     DexMethod method = buildNullaryVoidMethod(B.class, "f", appInfo.dexItemFactory());
-    MethodResolutionResult resolutionResult = appInfo.resolveMethodOnClass(method);
+    MethodResolutionResult resolutionResult = appInfo.resolveMethodOnClassHolder(method);
     DexEncodedMethod resolutionTarget = resolutionResult.getSingleTarget();
     assertEquals(R.class.getTypeName(), resolutionTarget.getHolderType().toSourceString());
   }
diff --git a/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultTopAbstractLeftTest.java b/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultTopAbstractLeftTest.java
index 49edcae..6b31505 100644
--- a/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultTopAbstractLeftTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultTopAbstractLeftTest.java
@@ -53,7 +53,7 @@
                 Main.class)
             .appInfo();
     DexMethod method = buildNullaryVoidMethod(B.class, "f", appInfo.dexItemFactory());
-    MethodResolutionResult resolutionResult = appInfo.resolveMethodOnClass(method);
+    MethodResolutionResult resolutionResult = appInfo.resolveMethodOnClassHolder(method);
     DexEncodedMethod resolutionTarget = resolutionResult.getSingleTarget();
     assertEquals(L.class.getTypeName(), resolutionTarget.getHolderType().toSourceString());
   }
diff --git a/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultTopAbstractRightTest.java b/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultTopAbstractRightTest.java
index 1aa65c6..0b78a07 100644
--- a/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultTopAbstractRightTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultTopAbstractRightTest.java
@@ -53,7 +53,7 @@
                 Main.class)
             .appInfo();
     DexMethod method = buildNullaryVoidMethod(B.class, "f", appInfo.dexItemFactory());
-    MethodResolutionResult resolutionResult = appInfo.resolveMethodOnClass(method);
+    MethodResolutionResult resolutionResult = appInfo.resolveMethodOnClassHolder(method);
     DexEncodedMethod resolutionTarget = resolutionResult.getSingleTarget();
     assertEquals(R.class.getTypeName(), resolutionTarget.getHolderType().toSourceString());
   }
diff --git a/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultTopAndBothTest.java b/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultTopAndBothTest.java
index af70288..7f09e71 100644
--- a/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultTopAndBothTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultTopAndBothTest.java
@@ -53,7 +53,7 @@
             .buildWithLiveness()
             .appInfo();
     DexMethod method = buildNullaryVoidMethod(B.class, "f", appInfo.dexItemFactory());
-    MethodResolutionResult resolutionResult = appInfo.resolveMethodOnClass(method);
+    MethodResolutionResult resolutionResult = appInfo.resolveMethodOnClassHolder(method);
     Set<String> holders = new HashSet<>();
     resolutionResult
         .asFailedResolution()
diff --git a/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultTopAndLeftTest.java b/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultTopAndLeftTest.java
index d2d08a5..0f37198 100644
--- a/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultTopAndLeftTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultTopAndLeftTest.java
@@ -50,7 +50,7 @@
             .buildWithLiveness()
             .appInfo();
     DexMethod method = buildNullaryVoidMethod(B.class, "f", appInfo.dexItemFactory());
-    MethodResolutionResult resolutionResult = appInfo.resolveMethodOnClass(method);
+    MethodResolutionResult resolutionResult = appInfo.resolveMethodOnClassHolder(method);
     DexEncodedMethod resolutionTarget = resolutionResult.getSingleTarget();
     assertEquals(L.class.getTypeName(), resolutionTarget.getHolderType().toSourceString());
   }
diff --git a/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultTopAndRightTest.java b/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultTopAndRightTest.java
index 1b900b4..8d35771 100644
--- a/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultTopAndRightTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultTopAndRightTest.java
@@ -50,7 +50,7 @@
             .buildWithLiveness()
             .appInfo();
     DexMethod method = buildNullaryVoidMethod(B.class, "f", appInfo.dexItemFactory());
-    MethodResolutionResult resolutionResult = appInfo.resolveMethodOnClass(method);
+    MethodResolutionResult resolutionResult = appInfo.resolveMethodOnClassHolder(method);
     DexEncodedMethod resolutionTarget = resolutionResult.getSingleTarget();
     assertEquals(R.class.getTypeName(), resolutionTarget.getHolderType().toSourceString());
   }
diff --git a/src/test/java/com/android/tools/r8/resolution/interfacediamonds/TwoDefaultMethodsWithoutTopTest.java b/src/test/java/com/android/tools/r8/resolution/interfacediamonds/TwoDefaultMethodsWithoutTopTest.java
index 8901542..290dbec 100644
--- a/src/test/java/com/android/tools/r8/resolution/interfacediamonds/TwoDefaultMethodsWithoutTopTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/interfacediamonds/TwoDefaultMethodsWithoutTopTest.java
@@ -58,7 +58,7 @@
               .buildWithLiveness()
               .appInfo();
       DexMethod method = buildNullaryVoidMethod(B.class, "f", appInfo.dexItemFactory());
-      MethodResolutionResult resolutionResult = appInfo.resolveMethodOnClass(method);
+      MethodResolutionResult resolutionResult = appInfo.resolveMethodOnClassHolder(method);
       if (minApi.isLessThan(apiLevelWithDefaultInterfaceMethodsSupport())) {
         // When desugaring a forwarding method throwing ICCE is inserted.
         // Check that the resolved method throws such an exception.
diff --git a/src/test/java/com/android/tools/r8/resolution/interfacetargets/DefaultWithoutTopTest.java b/src/test/java/com/android/tools/r8/resolution/interfacetargets/DefaultWithoutTopTest.java
index 467ca50..8908dce 100644
--- a/src/test/java/com/android/tools/r8/resolution/interfacetargets/DefaultWithoutTopTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/interfacetargets/DefaultWithoutTopTest.java
@@ -63,7 +63,7 @@
             .buildWithLiveness();
     AppInfoWithLiveness appInfo = appView.appInfo();
     DexMethod method = buildNullaryVoidMethod(I.class, "foo", appInfo.dexItemFactory());
-    MethodResolutionResult resolutionResult = appInfo.resolveMethodOnInterface(method);
+    MethodResolutionResult resolutionResult = appInfo.resolveMethodOnInterfaceHolder(method);
     DexProgramClass context =
         appView.definitionForProgramType(buildType(Main.class, appInfo.dexItemFactory()));
     LookupResult lookupResult = resolutionResult.lookupVirtualDispatchTargets(context, appInfo);
@@ -112,7 +112,7 @@
             .buildWithLiveness();
     AppInfoWithLiveness appInfo = appView.appInfo();
     DexMethod method = buildNullaryVoidMethod(I.class, "foo", appInfo.dexItemFactory());
-    MethodResolutionResult resolutionResult = appInfo.resolveMethodOnInterface(method);
+    MethodResolutionResult resolutionResult = appInfo.resolveMethodOnInterfaceHolder(method);
     DexProgramClass context =
         appView.definitionForProgramType(buildType(Main.class, appInfo.dexItemFactory()));
     LookupResult lookupResult = resolutionResult.lookupVirtualDispatchTargets(context, appInfo);
diff --git a/src/test/java/com/android/tools/r8/resolution/interfacetargets/DuplicateImportsTest.java b/src/test/java/com/android/tools/r8/resolution/interfacetargets/DuplicateImportsTest.java
index af2dde1..8122941 100644
--- a/src/test/java/com/android/tools/r8/resolution/interfacetargets/DuplicateImportsTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/interfacetargets/DuplicateImportsTest.java
@@ -63,7 +63,7 @@
             .buildWithLiveness();
     AppInfoWithLiveness appInfo = appView.appInfo();
     DexMethod method = buildNullaryVoidMethod(I.class, "foo", appInfo.dexItemFactory());
-    MethodResolutionResult resolutionResult = appInfo.resolveMethodOnInterface(method);
+    MethodResolutionResult resolutionResult = appInfo.resolveMethodOnInterfaceHolder(method);
     DexProgramClass context =
         appView.definitionForProgramType(buildType(Main.class, appInfo.dexItemFactory()));
     LookupResult lookupResult = resolutionResult.lookupVirtualDispatchTargets(context, appInfo);
diff --git a/src/test/java/com/android/tools/r8/resolution/interfacetargets/InvokeInterfaceWithStaticTargetTest.java b/src/test/java/com/android/tools/r8/resolution/interfacetargets/InvokeInterfaceWithStaticTargetTest.java
index 48c4221..ca5e24e 100644
--- a/src/test/java/com/android/tools/r8/resolution/interfacetargets/InvokeInterfaceWithStaticTargetTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/interfacetargets/InvokeInterfaceWithStaticTargetTest.java
@@ -56,7 +56,7 @@
         AssertionError.class,
         () ->
             appInfo
-                .resolveMethodOnInterface(method)
+                .resolveMethodOnInterfaceHolder(method)
                 .lookupVirtualDispatchTargets(context, appInfo));
   }
 
diff --git a/src/test/java/com/android/tools/r8/resolution/packageprivate/PackagePrivateClasspathWidenTest.java b/src/test/java/com/android/tools/r8/resolution/packageprivate/PackagePrivateClasspathWidenTest.java
index e59ea7a..bff70ca 100644
--- a/src/test/java/com/android/tools/r8/resolution/packageprivate/PackagePrivateClasspathWidenTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/packageprivate/PackagePrivateClasspathWidenTest.java
@@ -74,7 +74,7 @@
             .buildWithLiveness();
     AppInfoWithLiveness appInfo = appView.appInfo();
     DexMethod method = buildNullaryVoidMethod(Abstract.class, "foo", appInfo.dexItemFactory());
-    MethodResolutionResult resolutionResult = appInfo.resolveMethodOnClass(method);
+    MethodResolutionResult resolutionResult = appInfo.resolveMethodOnClassHolder(method);
     DexProgramClass context =
         appView.definitionForProgramType(buildType(Abstract.class, appInfo.dexItemFactory()));
     LookupResult lookupResult = resolutionResult.lookupVirtualDispatchTargets(context, appInfo);
diff --git a/src/test/java/com/android/tools/r8/resolution/packageprivate/PackagePrivateReentryTest.java b/src/test/java/com/android/tools/r8/resolution/packageprivate/PackagePrivateReentryTest.java
index 4b2877d..255fbed 100644
--- a/src/test/java/com/android/tools/r8/resolution/packageprivate/PackagePrivateReentryTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/packageprivate/PackagePrivateReentryTest.java
@@ -59,7 +59,7 @@
             Main.class);
     AppInfoWithLiveness appInfo = appView.appInfo();
     DexMethod method = buildNullaryVoidMethod(A.class, "bar", appInfo.dexItemFactory());
-    MethodResolutionResult resolutionResult = appInfo.resolveMethodOnClass(method);
+    MethodResolutionResult resolutionResult = appInfo.resolveMethodOnClassHolder(method);
     DexProgramClass context =
         appView.definitionForProgramType(buildType(A.class, appInfo.dexItemFactory()));
     LookupResult lookupResult = resolutionResult.lookupVirtualDispatchTargets(context, appInfo);
diff --git a/src/test/java/com/android/tools/r8/resolution/packageprivate/PackagePrivateReentryWithNarrowingTest.java b/src/test/java/com/android/tools/r8/resolution/packageprivate/PackagePrivateReentryWithNarrowingTest.java
index be967fb..f33ee0b 100644
--- a/src/test/java/com/android/tools/r8/resolution/packageprivate/PackagePrivateReentryWithNarrowingTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/packageprivate/PackagePrivateReentryWithNarrowingTest.java
@@ -63,7 +63,7 @@
             Main.class);
     AppInfoWithLiveness appInfo = appView.appInfo();
     DexMethod method = buildNullaryVoidMethod(A.class, "bar", appInfo.dexItemFactory());
-    MethodResolutionResult resolutionResult = appInfo.resolveMethodOnClass(method);
+    MethodResolutionResult resolutionResult = appInfo.resolveMethodOnClassHolder(method);
     DexProgramClass context =
         appView.definitionForProgramType(buildType(A.class, appInfo.dexItemFactory()));
     LookupResult lookupResult = resolutionResult.lookupVirtualDispatchTargets(context, appInfo);
diff --git a/src/test/java/com/android/tools/r8/resolution/packageprivate/PackagePrivateWithDefaultMethod2Test.java b/src/test/java/com/android/tools/r8/resolution/packageprivate/PackagePrivateWithDefaultMethod2Test.java
index 8262061..be8acd3 100644
--- a/src/test/java/com/android/tools/r8/resolution/packageprivate/PackagePrivateWithDefaultMethod2Test.java
+++ b/src/test/java/com/android/tools/r8/resolution/packageprivate/PackagePrivateWithDefaultMethod2Test.java
@@ -73,7 +73,7 @@
             Main.class);
     AppInfoWithLiveness appInfo = appView.appInfo();
     DexMethod method = buildNullaryVoidMethod(A.class, "foo", appInfo.dexItemFactory());
-    MethodResolutionResult resolutionResult = appInfo.resolveMethodOnClass(method);
+    MethodResolutionResult resolutionResult = appInfo.resolveMethodOnClassHolder(method);
     DexProgramClass context =
         appView.definitionForProgramType(buildType(A.class, appInfo.dexItemFactory()));
     LookupResult lookupResult = resolutionResult.lookupVirtualDispatchTargets(context, appInfo);
diff --git a/src/test/java/com/android/tools/r8/resolution/packageprivate/PackagePrivateWithDefaultMethodTest.java b/src/test/java/com/android/tools/r8/resolution/packageprivate/PackagePrivateWithDefaultMethodTest.java
index 876a991..8846bf2 100644
--- a/src/test/java/com/android/tools/r8/resolution/packageprivate/PackagePrivateWithDefaultMethodTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/packageprivate/PackagePrivateWithDefaultMethodTest.java
@@ -67,7 +67,7 @@
             Main.class);
     AppInfoWithLiveness appInfo = appView.appInfo();
     DexMethod method = buildNullaryVoidMethod(A.class, "foo", appInfo.dexItemFactory());
-    MethodResolutionResult resolutionResult = appInfo.resolveMethodOnClass(method);
+    MethodResolutionResult resolutionResult = appInfo.resolveMethodOnClassHolder(method);
     DexProgramClass context =
         appView.definitionForProgramType(buildType(A.class, appInfo.dexItemFactory()));
     assertTrue(resolutionResult.isAccessibleFrom(context, appView).isFalse());
diff --git a/src/test/java/com/android/tools/r8/resolution/packageprivate/WidenAccessOutsidePackageTest.java b/src/test/java/com/android/tools/r8/resolution/packageprivate/WidenAccessOutsidePackageTest.java
index 219c239..da660f2 100644
--- a/src/test/java/com/android/tools/r8/resolution/packageprivate/WidenAccessOutsidePackageTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/packageprivate/WidenAccessOutsidePackageTest.java
@@ -60,7 +60,7 @@
             Main.class);
     AppInfoWithLiveness appInfo = appView.appInfo();
     DexMethod method = buildNullaryVoidMethod(A.class, "bar", appInfo.dexItemFactory());
-    MethodResolutionResult resolutionResult = appInfo.resolveMethodOnClass(method);
+    MethodResolutionResult resolutionResult = appInfo.resolveMethodOnClassHolder(method);
     DexProgramClass context =
         appView.definitionForProgramType(buildType(A.class, appInfo.dexItemFactory()));
     LookupResult lookupResult = resolutionResult.lookupVirtualDispatchTargets(context, appInfo);
diff --git a/src/test/java/com/android/tools/r8/resolution/singletarget/InstantiatedLowerBoundTest.java b/src/test/java/com/android/tools/r8/resolution/singletarget/InstantiatedLowerBoundTest.java
index 7664322..58c64b1 100644
--- a/src/test/java/com/android/tools/r8/resolution/singletarget/InstantiatedLowerBoundTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/singletarget/InstantiatedLowerBoundTest.java
@@ -131,7 +131,7 @@
     DexMethod fooA = buildNullaryVoidMethod(A.class, "foo", appInfo.dexItemFactory());
     DexMethod fooB = buildNullaryVoidMethod(B.class, "foo", appInfo.dexItemFactory());
     DexMethod fooC = buildNullaryVoidMethod(C.class, "foo", appInfo.dexItemFactory());
-    MethodResolutionResult resolution = appInfo.resolveMethodOnClass(fooA);
+    MethodResolutionResult resolution = appInfo.resolveMethodOnClassHolder(fooA);
     DexProgramClass context = appView.definitionForProgramType(typeMain);
     DexProgramClass upperBound = appView.definitionForProgramType(typeA);
     DexProgramClass lowerBound = appView.definitionForProgramType(typeC);
diff --git a/src/test/java/com/android/tools/r8/resolution/virtualtargets/AbstractInMiddleTest.java b/src/test/java/com/android/tools/r8/resolution/virtualtargets/AbstractInMiddleTest.java
index 4da75f6..e856ed2 100644
--- a/src/test/java/com/android/tools/r8/resolution/virtualtargets/AbstractInMiddleTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/virtualtargets/AbstractInMiddleTest.java
@@ -59,7 +59,7 @@
             Main.class);
     AppInfoWithLiveness appInfo = appView.appInfo();
     DexMethod method = buildNullaryVoidMethod(A.class, "foo", appInfo.dexItemFactory());
-    MethodResolutionResult resolutionResult = appInfo.resolveMethodOnClass(method);
+    MethodResolutionResult resolutionResult = appInfo.resolveMethodOnClassHolder(method);
     DexProgramClass context =
         appView.definitionForProgramType(buildType(Main.class, appInfo.dexItemFactory()));
     LookupResult lookupResult = resolutionResult.lookupVirtualDispatchTargets(context, appInfo);
diff --git a/src/test/java/com/android/tools/r8/resolution/virtualtargets/DefaultInterfaceMethodInSubInterfaceSubTypeTest.java b/src/test/java/com/android/tools/r8/resolution/virtualtargets/DefaultInterfaceMethodInSubInterfaceSubTypeTest.java
index d91d19f..665ef74 100644
--- a/src/test/java/com/android/tools/r8/resolution/virtualtargets/DefaultInterfaceMethodInSubInterfaceSubTypeTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/virtualtargets/DefaultInterfaceMethodInSubInterfaceSubTypeTest.java
@@ -62,7 +62,7 @@
             .buildWithLiveness();
     AppInfoWithLiveness appInfo = appView.appInfo();
     DexMethod method = buildNullaryVoidMethod(A.class, "foo", appInfo.dexItemFactory());
-    MethodResolutionResult resolutionResult = appInfo.resolveMethodOnClass(method);
+    MethodResolutionResult resolutionResult = appInfo.resolveMethodOnClassHolder(method);
     DexProgramClass context =
         appView.definitionForProgramType(buildType(Main.class, appInfo.dexItemFactory()));
     LookupResult lookupResult = resolutionResult.lookupVirtualDispatchTargets(context, appInfo);
diff --git a/src/test/java/com/android/tools/r8/resolution/virtualtargets/DefaultInterfaceMethodInSubInterfaceTest.java b/src/test/java/com/android/tools/r8/resolution/virtualtargets/DefaultInterfaceMethodInSubInterfaceTest.java
index 69c8c7a..df0722f 100644
--- a/src/test/java/com/android/tools/r8/resolution/virtualtargets/DefaultInterfaceMethodInSubInterfaceTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/virtualtargets/DefaultInterfaceMethodInSubInterfaceTest.java
@@ -62,7 +62,7 @@
             .buildWithLiveness();
     AppInfoWithLiveness appInfo = appView.appInfo();
     DexMethod method = buildNullaryVoidMethod(A.class, "foo", appInfo.dexItemFactory());
-    MethodResolutionResult resolutionResult = appInfo.resolveMethodOnClass(method);
+    MethodResolutionResult resolutionResult = appInfo.resolveMethodOnClassHolder(method);
     DexProgramClass context =
         appView.definitionForProgramType(buildType(Main.class, appInfo.dexItemFactory()));
     LookupResult lookupResult = resolutionResult.lookupVirtualDispatchTargets(context, appInfo);
diff --git a/src/test/java/com/android/tools/r8/resolution/virtualtargets/DefaultWithoutTopTest.java b/src/test/java/com/android/tools/r8/resolution/virtualtargets/DefaultWithoutTopTest.java
index 65d8316..d4541ac 100644
--- a/src/test/java/com/android/tools/r8/resolution/virtualtargets/DefaultWithoutTopTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/virtualtargets/DefaultWithoutTopTest.java
@@ -63,7 +63,7 @@
             .buildWithLiveness();
     AppInfoWithLiveness appInfo = appView.appInfo();
     DexMethod method = buildNullaryVoidMethod(A.class, "foo", appInfo.dexItemFactory());
-    MethodResolutionResult resolutionResult = appInfo.resolveMethodOnClass(method);
+    MethodResolutionResult resolutionResult = appInfo.resolveMethodOnClassHolder(method);
     DexProgramClass context =
         appView.definitionForProgramType(buildType(Main.class, appInfo.dexItemFactory()));
     LookupResult lookupResult = resolutionResult.lookupVirtualDispatchTargets(context, appInfo);
diff --git a/src/test/java/com/android/tools/r8/resolution/virtualtargets/InvalidResolutionToThisTarget.java b/src/test/java/com/android/tools/r8/resolution/virtualtargets/InvalidResolutionToThisTarget.java
index 10c5b6f..6aac0fb 100644
--- a/src/test/java/com/android/tools/r8/resolution/virtualtargets/InvalidResolutionToThisTarget.java
+++ b/src/test/java/com/android/tools/r8/resolution/virtualtargets/InvalidResolutionToThisTarget.java
@@ -59,7 +59,7 @@
                       Main.class);
               AppInfoWithLiveness appInfo = appView.appInfo();
               DexMethod method = buildNullaryVoidMethod(A.class, "foo", appInfo.dexItemFactory());
-              MethodResolutionResult resolutionResult = appInfo.resolveMethodOnClass(method);
+              MethodResolutionResult resolutionResult = appInfo.resolveMethodOnClassHolder(method);
               assertTrue(resolutionResult.isSingleResolution());
               DexType mainType = buildType(Main.class, appInfo.dexItemFactory());
               DexProgramClass main = appView.definitionForProgramType(mainType);
diff --git a/src/test/java/com/android/tools/r8/resolution/virtualtargets/InvokeVirtualToInterfaceDefinitionTest.java b/src/test/java/com/android/tools/r8/resolution/virtualtargets/InvokeVirtualToInterfaceDefinitionTest.java
index 14f9d2a..a1f6b17 100644
--- a/src/test/java/com/android/tools/r8/resolution/virtualtargets/InvokeVirtualToInterfaceDefinitionTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/virtualtargets/InvokeVirtualToInterfaceDefinitionTest.java
@@ -62,7 +62,7 @@
             .buildWithLiveness();
     AppInfoWithLiveness appInfo = appView.appInfo();
     DexMethod method = buildNullaryVoidMethod(A.class, "foo", appInfo.dexItemFactory());
-    MethodResolutionResult resolutionResult = appInfo.resolveMethodOnClass(method);
+    MethodResolutionResult resolutionResult = appInfo.resolveMethodOnClassHolder(method);
     DexProgramClass context =
         appView.definitionForProgramType(buildType(Main.class, appInfo.dexItemFactory()));
     LookupResult lookupResult = resolutionResult.lookupVirtualDispatchTargets(context, appInfo);
diff --git a/src/test/java/com/android/tools/r8/resolution/virtualtargets/KeptTargetsIncompleteLookupTest.java b/src/test/java/com/android/tools/r8/resolution/virtualtargets/KeptTargetsIncompleteLookupTest.java
index 3b675c7..19f951a 100644
--- a/src/test/java/com/android/tools/r8/resolution/virtualtargets/KeptTargetsIncompleteLookupTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/virtualtargets/KeptTargetsIncompleteLookupTest.java
@@ -87,7 +87,7 @@
             });
     AppInfoWithLiveness appInfo = appView.appInfo();
     DexMethod method = buildNullaryVoidMethod(initial, "foo", appInfo.dexItemFactory());
-    MethodResolutionResult resolutionResult = appInfo.resolveMethodOnClass(method);
+    MethodResolutionResult resolutionResult = appInfo.resolveMethodOnClassHolder(method);
     DexProgramClass context =
         DexProgramClass.asProgramClassOrNull(
             appView
@@ -238,7 +238,7 @@
                 .build());
     AppInfoWithClassHierarchy appInfo = appView.appInfo();
     DexMethod method = buildNullaryVoidMethod(B.class, "foo", appInfo.dexItemFactory());
-    MethodResolutionResult resolutionResult = appInfo.resolveMethodOnClass(method);
+    MethodResolutionResult resolutionResult = appInfo.resolveMethodOnClassHolder(method);
     DexType typeA = buildType(A.class, appInfo.dexItemFactory());
     DexType typeB = buildType(B.class, appInfo.dexItemFactory());
     DexProgramClass classB = appInfo.definitionForProgramType(typeB);
@@ -273,7 +273,7 @@
             Unrelated.class);
     AppInfoWithLiveness appInfo = appView.appInfo();
     DexMethod method = buildNullaryVoidMethod(Unrelated.class, "foo", appInfo.dexItemFactory());
-    MethodResolutionResult resolutionResult = appInfo.resolveMethodOnClass(method);
+    MethodResolutionResult resolutionResult = appInfo.resolveMethodOnClassHolder(method);
     DexProgramClass context =
         appView.definitionForProgramType(buildType(Unrelated.class, appInfo.dexItemFactory()));
     LookupResult lookupResult = resolutionResult.lookupVirtualDispatchTargets(context, appInfo);
diff --git a/src/test/java/com/android/tools/r8/resolution/virtualtargets/PackagePrivateChainTest.java b/src/test/java/com/android/tools/r8/resolution/virtualtargets/PackagePrivateChainTest.java
index 001075d..33bfc01 100644
--- a/src/test/java/com/android/tools/r8/resolution/virtualtargets/PackagePrivateChainTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/virtualtargets/PackagePrivateChainTest.java
@@ -62,7 +62,7 @@
             Main.class);
     AppInfoWithLiveness appInfo = appView.appInfo();
     DexMethod method = buildNullaryVoidMethod(Top.class, "clear", appInfo.dexItemFactory());
-    MethodResolutionResult resolutionResult = appInfo.resolveMethodOnClass(method);
+    MethodResolutionResult resolutionResult = appInfo.resolveMethodOnClassHolder(method);
     DexProgramClass context =
         appView.definitionForProgramType(buildType(TopRunner.class, appInfo.dexItemFactory()));
     LookupResult lookupResult = resolutionResult.lookupVirtualDispatchTargets(context, appInfo);
diff --git a/src/test/java/com/android/tools/r8/resolution/virtualtargets/PackagePrivateFinalOverrideTest.java b/src/test/java/com/android/tools/r8/resolution/virtualtargets/PackagePrivateFinalOverrideTest.java
index 84021e7..28d1cc2 100644
--- a/src/test/java/com/android/tools/r8/resolution/virtualtargets/PackagePrivateFinalOverrideTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/virtualtargets/PackagePrivateFinalOverrideTest.java
@@ -68,7 +68,7 @@
             Main.class);
     AppInfoWithLiveness appInfo = appView.appInfo();
     DexMethod method = buildNullaryVoidMethod(ViewModel.class, "clear", appInfo.dexItemFactory());
-    MethodResolutionResult resolutionResult = appInfo.resolveMethodOnClass(method);
+    MethodResolutionResult resolutionResult = appInfo.resolveMethodOnClassHolder(method);
     DexProgramClass context =
         appView.definitionForProgramType(
             buildType(ViewModelRunner.class, appInfo.dexItemFactory()));
@@ -119,7 +119,7 @@
             Main.class);
     AppInfoWithLiveness appInfo = appView.appInfo();
     DexMethod method = buildNullaryVoidMethod(ViewModel.class, "clear", appInfo.dexItemFactory());
-    MethodResolutionResult resolutionResult = appInfo.resolveMethodOnClass(method);
+    MethodResolutionResult resolutionResult = appInfo.resolveMethodOnClassHolder(method);
     DexProgramClass context =
         appView.definitionForProgramType(buildType(Main.class, appInfo.dexItemFactory()));
     LookupResult lookupResult = resolutionResult.lookupVirtualDispatchTargets(context, appInfo);
@@ -171,7 +171,7 @@
             Main.class);
     AppInfoWithLiveness appInfo = appView.appInfo();
     DexMethod method = buildNullaryVoidMethod(ViewModel.class, "clear", appInfo.dexItemFactory());
-    MethodResolutionResult resolutionResult = appInfo.resolveMethodOnClass(method);
+    MethodResolutionResult resolutionResult = appInfo.resolveMethodOnClassHolder(method);
     DexProgramClass context =
         appView.definitionForProgramType(
             buildType(ViewModelRunnerWithCast.class, appInfo.dexItemFactory()));
diff --git a/src/test/java/com/android/tools/r8/resolution/virtualtargets/PrivateOverrideOfVirtualTargetTest.java b/src/test/java/com/android/tools/r8/resolution/virtualtargets/PrivateOverrideOfVirtualTargetTest.java
index eda5153..24aa358 100644
--- a/src/test/java/com/android/tools/r8/resolution/virtualtargets/PrivateOverrideOfVirtualTargetTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/virtualtargets/PrivateOverrideOfVirtualTargetTest.java
@@ -61,7 +61,7 @@
             Main.class);
     AppInfoWithLiveness appInfo = appView.appInfo();
     DexMethod method = buildNullaryVoidMethod(A.class, "bar", appInfo.dexItemFactory());
-    MethodResolutionResult resolutionResult = appInfo.resolveMethodOnClass(method);
+    MethodResolutionResult resolutionResult = appInfo.resolveMethodOnClassHolder(method);
     DexProgramClass context =
         appView.definitionForProgramType(buildType(B.class, appInfo.dexItemFactory()));
     LookupResult lookupResult = resolutionResult.lookupVirtualDispatchTargets(context, appInfo);
diff --git a/src/test/java/com/android/tools/r8/resolution/virtualtargets/ProtectedDifferentPackageLookupTest.java b/src/test/java/com/android/tools/r8/resolution/virtualtargets/ProtectedDifferentPackageLookupTest.java
index 3a4a369..3ffdf09 100644
--- a/src/test/java/com/android/tools/r8/resolution/virtualtargets/ProtectedDifferentPackageLookupTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/virtualtargets/ProtectedDifferentPackageLookupTest.java
@@ -54,7 +54,7 @@
     AppView<AppInfoWithLiveness> appView = computeAppViewWithLiveness(builder.build(), Main.class);
     AppInfoWithLiveness appInfo = appView.appInfo();
     DexMethod method = buildNullaryVoidMethod(A.class, "foo", appInfo.dexItemFactory());
-    MethodResolutionResult resolutionResult = appInfo.resolveMethodOnClass(method);
+    MethodResolutionResult resolutionResult = appInfo.resolveMethodOnClassHolder(method);
     DexProgramClass context =
         appView.definitionForProgramType(buildType(Main.class, appInfo.dexItemFactory()));
     LookupResult lookupResult = resolutionResult.lookupVirtualDispatchTargets(context, appInfo);
diff --git a/src/test/java/com/android/tools/r8/resolution/virtualtargets/ProtectedSamePackageLookupTest.java b/src/test/java/com/android/tools/r8/resolution/virtualtargets/ProtectedSamePackageLookupTest.java
index 10fb0d9..337ba95 100644
--- a/src/test/java/com/android/tools/r8/resolution/virtualtargets/ProtectedSamePackageLookupTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/virtualtargets/ProtectedSamePackageLookupTest.java
@@ -50,7 +50,7 @@
             PackagePrivateChainTest.Main.class);
     AppInfoWithLiveness appInfo = appView.appInfo();
     DexMethod method = buildNullaryVoidMethod(A.class, "foo", appInfo.dexItemFactory());
-    MethodResolutionResult resolutionResult = appInfo.resolveMethodOnClass(method);
+    MethodResolutionResult resolutionResult = appInfo.resolveMethodOnClassHolder(method);
     DexProgramClass context =
         appView.definitionForProgramType(buildType(Main.class, appInfo.dexItemFactory()));
     LookupResult lookupResult = resolutionResult.lookupVirtualDispatchTargets(context, appInfo);
diff --git a/src/test/java/com/android/tools/r8/resolution/virtualtargets/TargetInDefaultMethodTest.java b/src/test/java/com/android/tools/r8/resolution/virtualtargets/TargetInDefaultMethodTest.java
index 162d3c4..8769eca 100644
--- a/src/test/java/com/android/tools/r8/resolution/virtualtargets/TargetInDefaultMethodTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/virtualtargets/TargetInDefaultMethodTest.java
@@ -63,7 +63,7 @@
             .buildWithLiveness();
     AppInfoWithLiveness appInfo = appView.appInfo();
     DexMethod method = buildNullaryVoidMethod(I.class, "foo", appInfo.dexItemFactory());
-    MethodResolutionResult resolutionResult = appInfo.resolveMethodOnInterface(method);
+    MethodResolutionResult resolutionResult = appInfo.resolveMethodOnInterfaceHolder(method);
     DexProgramClass context =
         appView.definitionForProgramType(buildType(Main.class, appInfo.dexItemFactory()));
     LookupResult lookupResult = resolutionResult.lookupVirtualDispatchTargets(context, appInfo);
diff --git a/src/test/java/com/android/tools/r8/retrace/StackTraceRegularExpressionParserTests.java b/src/test/java/com/android/tools/r8/retrace/StackTraceRegularExpressionParserTests.java
index 2e60c6b..91428db 100644
--- a/src/test/java/com/android/tools/r8/retrace/StackTraceRegularExpressionParserTests.java
+++ b/src/test/java/com/android/tools/r8/retrace/StackTraceRegularExpressionParserTests.java
@@ -1084,6 +1084,57 @@
         });
   }
 
+  /* This is a regression test for b/222749348 */
+  @Test
+  public void testGroups() {
+    runRetraceTest(
+        "(?:(?:.*?%c %m\\(%s(?:: %l)?\\)))",
+        new StackTraceForTest() {
+          @Override
+          public List<String> obfuscatedStackTrace() {
+            return ImmutableList.of(
+                "FOO bar(PG: 1)",
+                "FOO bar(PG : 1)",
+                "FOO bar(PG:1)",
+                "FOO bar(PG:1 )",
+                "FOO bar(PG: 1 )");
+          }
+
+          @Override
+          public String mapping() {
+            return StringUtils.lines(
+                "this.was.Deobfuscated -> FOO:",
+                "    int[] mFontFamily -> a",
+                "    1:3:void someMethod(int):65:67 -> bar");
+          }
+
+          @Override
+          public List<String> retracedStackTrace() {
+            return ImmutableList.of(
+                "this.was.Deobfuscated someMethod(Deobfuscated.java: 65)",
+                "this.was.Deobfuscated someMethod(Deobfuscated.java: 65)",
+                "FOO bar(PG:1)",
+                "FOO bar(PG:1 )",
+                "FOO bar(PG: 1 )");
+          }
+
+          @Override
+          public List<String> retraceVerboseStackTrace() {
+            return ImmutableList.of(
+                "this.was.Deobfuscated void someMethod(int)(Deobfuscated.java: 65)",
+                "this.was.Deobfuscated void someMethod(int)(Deobfuscated.java: 65)",
+                "FOO bar(PG:1)",
+                "FOO bar(PG:1 )",
+                "FOO bar(PG: 1 )");
+          }
+
+          @Override
+          public int expectedWarnings() {
+            return 0;
+          }
+        });
+  }
+
   private TestDiagnosticMessagesImpl runRetraceTest(
       String regularExpression, StackTraceForTest stackTraceForTest) {
     TestDiagnosticMessagesImpl diagnosticsHandler = new TestDiagnosticMessagesImpl();
diff --git a/src/test/java/com/android/tools/r8/rewrite/JavaScriptScriptEngineTest.java b/src/test/java/com/android/tools/r8/rewrite/JavaScriptScriptEngineTest.java
index a9491f4..5e4932e 100644
--- a/src/test/java/com/android/tools/r8/rewrite/JavaScriptScriptEngineTest.java
+++ b/src/test/java/com/android/tools/r8/rewrite/JavaScriptScriptEngineTest.java
@@ -12,6 +12,7 @@
 import com.android.tools.r8.CompilationFailedException;
 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.StringUtils;
 import java.io.IOException;
 import java.util.concurrent.ExecutionException;
@@ -56,22 +57,18 @@
     testForR8(parameters.getBackend())
         .addInnerClasses(JavaScriptScriptEngineTest.class)
         .addKeepMainRule(TestClass.class)
+        .setMinApi(parameters.getApiLevel())
         .applyIf(
             parameters.isDexRuntime(),
-            testBuilder ->
-                testBuilder.addOptionsModification(
-                    options ->
-                        options
-                            .getOpenClosedInterfacesOptions()
-                            .suppressAllOpenInterfacesDueToMissingClasses()))
-        .setMinApi(parameters.getApiLevel())
-        .apply(
-            b -> {
-              if (parameters.isDexRuntime()) {
-                addRhinoForAndroid(b);
-                addKeepRulesForAndroidRhino(b);
-                b.allowDiagnosticWarningMessages();
-              }
+            testBuilder -> {
+              testBuilder.addOptionsModification(
+                  options ->
+                      options
+                          .getOpenClosedInterfacesOptions()
+                          .suppressAllOpenInterfacesDueToMissingClasses());
+              addRhinoForAndroid(testBuilder);
+              addKeepRulesForAndroidRhino(testBuilder);
+              testBuilder.allowDiagnosticWarningMessages();
             })
         .compile()
         .applyIf(
@@ -84,8 +81,13 @@
                             "required for default or static interface methods desugaring"),
                         equalTo("Resource 'META-INF/MANIFEST.MF' already exists."))))
         .run(parameters.getRuntime(), TestClass.class)
-        .assertSuccessWithOutput(
-            parameters.isCfRuntime() ? EXPECTED_NASHORN_OUTPUT : EXPECTED_RHINO_OUTPUT);
+        .applyIf(
+            // TODO(b/227162584): Fails to find any engine on JDK17.
+            parameters.isCfRuntime(CfVm.JDK17),
+            r -> r.assertFailureWithErrorThatThrows(NullPointerException.class),
+            r ->
+                r.assertSuccessWithOutput(
+                    parameters.isCfRuntime() ? EXPECTED_NASHORN_OUTPUT : EXPECTED_RHINO_OUTPUT));
   }
 
   static class TestClass {
diff --git a/src/test/java/com/android/tools/r8/rewrite/ScriptEngineTest.java b/src/test/java/com/android/tools/r8/rewrite/ScriptEngineTest.java
index 7bb21a3..bf0084b 100644
--- a/src/test/java/com/android/tools/r8/rewrite/ScriptEngineTest.java
+++ b/src/test/java/com/android/tools/r8/rewrite/ScriptEngineTest.java
@@ -14,6 +14,7 @@
 import com.android.tools.r8.DataEntryResource;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.TestRuntime.CfVm;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.utils.StreamUtils;
 import com.android.tools.r8.utils.StringUtils;
@@ -103,7 +104,10 @@
         //  comes with "Oracle Nashorn" included.
         .assertSuccessWithOutput(
             parameters.isCfRuntime()
-                ? StringUtils.lines("MyEngine1", "MyEngine2", "Oracle Nashorn")
+                // TODO(b/227162584): It looks like the JS engine is not in the jdk anymore.
+                ? (parameters.isCfRuntime(CfVm.JDK17)
+                    ? StringUtils.lines("MyEngine1", "MyEngine2")
+                    : StringUtils.lines("MyEngine1", "MyEngine2", "Oracle Nashorn"))
                 : StringUtils.lines("Mozilla Rhino", "MyEngine1", "MyEngine2"));
 
     // TODO(b/136633154): On the JVM this should always be there as the service loading is in
diff --git a/src/test/java/com/android/tools/r8/rewrite/assertions/kotlinassertionhandlersimple/AssertionConfigurationAssertionHandlerKotlinSimpleTest.java b/src/test/java/com/android/tools/r8/rewrite/assertions/kotlinassertionhandlersimple/AssertionConfigurationAssertionHandlerKotlinSimpleTest.java
index dc6353d..e460253 100644
--- a/src/test/java/com/android/tools/r8/rewrite/assertions/kotlinassertionhandlersimple/AssertionConfigurationAssertionHandlerKotlinSimpleTest.java
+++ b/src/test/java/com/android/tools/r8/rewrite/assertions/kotlinassertionhandlersimple/AssertionConfigurationAssertionHandlerKotlinSimpleTest.java
@@ -58,10 +58,10 @@
 
   @Override
   protected void configureR8(R8FullTestBuilder builder) {
-    builder.applyIf(
-        !kotlinStdlibAsLibrary
-            && !useJvmAssertions
-            && !kotlinParameters.is(KotlinCompilerVersion.KOTLINC_1_3_72),
-        b -> b.addDontWarn("org.jetbrains.annotations.NotNull"));
+    boolean referencesNotNull =
+        !kotlinParameters.is(KotlinCompilerVersion.KOTLINC_1_3_72)
+            && !kotlinStdlibAsLibrary
+            && !useJvmAssertions;
+    builder.applyIf(referencesNotNull, b -> b.addDontWarn("org.jetbrains.annotations.NotNull"));
   }
 }
diff --git a/src/test/java/com/android/tools/r8/shaking/ClasspathAndProgramSharedSuperTypeTest.java b/src/test/java/com/android/tools/r8/shaking/ClasspathAndProgramSharedSuperTypeTest.java
new file mode 100644
index 0000000..4363f33
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/ClasspathAndProgramSharedSuperTypeTest.java
@@ -0,0 +1,81 @@
+// Copyright (c) 2022, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.shaking;
+
+import static org.hamcrest.core.StringContains.containsString;
+import static org.junit.Assert.assertThrows;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class ClasspathAndProgramSharedSuperTypeTest extends TestBase {
+
+  @Parameter() public TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    // TODO(b/226054007): R8 should maintain classpath type.
+    assertThrows(
+        CompilationFailedException.class,
+        () ->
+            testForR8(parameters.getBackend())
+                .addProgramClasses(SharedSuperType.class, ProgramClass.class, Main.class)
+                .addClasspathClasses(SharedSuperType.class, ClasspathClass.class)
+                .setMinApi(parameters.getApiLevel())
+                .addKeepMainRule(Main.class)
+                .compileWithExpectedDiagnostics(
+                    diagnostics ->
+                        diagnostics.assertErrorMessageThatMatches(
+                            containsString(
+                                "Failed lookup of non-missing type: "
+                                    + typeName(SharedSuperType.class)))));
+  }
+
+  public static class SharedSuperType {
+
+    public void foo() {
+      System.out.println("SharedSuperType::foo");
+    }
+  }
+
+  public static class ClasspathClass extends SharedSuperType {
+
+    @Override
+    public void foo() {
+      System.out.println("ClasspathClass::foo");
+      super.foo();
+    }
+  }
+
+  /* Not referenced in live part of the program */
+  public static class ProgramClass extends SharedSuperType {
+
+    @Override
+    public void foo() {
+      System.out.println("ProgramClass::foo");
+      super.foo();
+    }
+  }
+
+  public static class Main {
+
+    public static void main(String[] args) {
+      new ClasspathClass().foo();
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/InvalidTypesTest.java b/src/test/java/com/android/tools/r8/shaking/InvalidTypesTest.java
index 9d3e55f..604caae 100644
--- a/src/test/java/com/android/tools/r8/shaking/InvalidTypesTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/InvalidTypesTest.java
@@ -8,7 +8,6 @@
 import static org.hamcrest.CoreMatchers.anyOf;
 import static org.hamcrest.CoreMatchers.containsString;
 import static org.hamcrest.CoreMatchers.equalTo;
-import static org.hamcrest.core.IsNot.not;
 
 import com.android.tools.r8.D8TestRunResult;
 import com.android.tools.r8.DXTestRunResult;
@@ -71,11 +70,6 @@
 
           case DX:
           case D8:
-          case R8:
-            if (parameters.isCfRuntime()) {
-              assert compiler == Compiler.R8;
-              return StringUtils.joinLines("Hello!", "Goodbye!", "");
-            }
             switch (parameters.getDexRuntimeVersion()) {
               case V4_0_4:
               case V4_4_4:
@@ -104,6 +98,14 @@
                 throw new Unreachable();
             }
 
+          case R8:
+            return StringUtils.joinLines(
+                "Hello!",
+                "Unexpected outcome of getstatic",
+                "Unexpected outcome of checkcast",
+                "Goodbye!",
+                "");
+
           case PROGUARD:
             return StringUtils.joinLines(
                 "Hello!", "Unexpected outcome of checkcast", "Goodbye!", "");
@@ -123,10 +125,6 @@
       @Override
       public String getExpectedOutput(
           Compiler compiler, TestParameters parameters, boolean useInterface) {
-        if (useInterface) {
-          return StringUtils.joinLines("Hello!", "In verifiable method!", "Goodbye!", "");
-        }
-
         switch (compiler) {
           case R8:
           case PROGUARD:
@@ -135,6 +133,9 @@
             return StringUtils.joinLines("Hello!", "In verifiable method!", "Goodbye!", "");
 
           default:
+            if (useInterface) {
+              return StringUtils.joinLines("Hello!", "In verifiable method!", "Goodbye!", "");
+            }
             // The code fails with a verification error because the verifiableMethod() is being
             // called on UnverifiableClass, which does not verify due to unverifiableMethod().
             return StringUtils.joinLines("Hello!", "");
@@ -329,17 +330,12 @@
         break;
 
       case INVOKE_VERIFIABLE_METHOD_ON_UNVERIFIABLE_CLASS:
-        if (useInterface) {
+        if (useInterface || compiler == Compiler.R8 || compiler == Compiler.PROGUARD) {
           result.assertSuccessWithOutput(getExpectedOutput(compiler));
         } else {
-          if (compiler == Compiler.R8
-              || compiler == Compiler.PROGUARD) {
-            result.assertSuccessWithOutput(getExpectedOutput(compiler));
-          } else {
-            result
-                .assertFailureWithOutput(getExpectedOutput(compiler))
-                .assertFailureWithErrorThatMatches(getMatcherForExpectedError());
-          }
+          result
+              .assertFailureWithOutput(getExpectedOutput(compiler))
+              .assertFailureWithErrorThatMatches(getMatcherForExpectedError(compiler));
         }
         break;
 
@@ -347,18 +343,9 @@
         if (useInterface) {
           result.assertSuccessWithOutput(getExpectedOutput(compiler));
         } else {
-          if (compiler == Compiler.R8) {
-            result
-                .assertFailureWithOutput(getExpectedOutput(compiler))
-                .assertFailureWithErrorThatMatches(
-                    allOf(
-                        containsString("java.lang.NullPointerException"),
-                        not(containsString("java.lang.VerifyError"))));
-          } else {
-            result
-                .assertFailureWithOutput(getExpectedOutput(compiler))
-                .assertFailureWithErrorThatMatches(getMatcherForExpectedError());
-          }
+          result
+              .assertFailureWithOutput(getExpectedOutput(compiler))
+              .assertFailureWithErrorThatMatches(getMatcherForExpectedError(compiler));
         }
         break;
 
@@ -371,7 +358,10 @@
     return mode.getExpectedOutput(compiler, parameters, useInterface);
   }
 
-  private Matcher<String> getMatcherForExpectedError() {
+  private Matcher<String> getMatcherForExpectedError(Compiler compiler) {
+    if (compiler == Compiler.R8 && mode == Mode.INVOKE_UNVERIFIABLE_METHOD) {
+      return containsString("java.lang.NullPointerException");
+    }
     if (parameters.isCfRuntime()) {
       return allOf(
           containsString("java.lang.VerifyError"),
diff --git a/src/test/java/com/android/tools/r8/shaking/allowshrinking/KeepClassFieldsAllowShrinkingCompatibilityTest.java b/src/test/java/com/android/tools/r8/shaking/allowshrinking/KeepClassFieldsAllowShrinkingCompatibilityTest.java
index 314ea33..35d4d6b 100644
--- a/src/test/java/com/android/tools/r8/shaking/allowshrinking/KeepClassFieldsAllowShrinkingCompatibilityTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/allowshrinking/KeepClassFieldsAllowShrinkingCompatibilityTest.java
@@ -4,6 +4,7 @@
 package com.android.tools.r8.shaking.allowshrinking;
 
 import static com.android.tools.r8.utils.codeinspector.CodeMatchers.accessesField;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isAbsent;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresentAndRenamed;
 import static com.android.tools.r8.utils.codeinspector.Matchers.notIf;
@@ -107,7 +108,8 @@
                 //   This does not match the R8 behavior for an unused method, so there may be an
                 //   optimization opportunity here.
                 //   (See KeepClassMethodsAllowShrinkingCompatibilityTest regarding methods).
-                assertThat(aBar, isPresentAndRenamed(allowObfuscation));
+                assertThat(
+                    aBar, shrinker.isR8() ? isAbsent() : isPresentAndRenamed(allowObfuscation));
                 assertThat(inspector.clazz(TestClass.class).mainMethod(), accessesField(aFoo));
                 if (shrinker.isR8()) {
                   assertThat(bFoo, not(isPresent()));
diff --git a/src/test/java/com/android/tools/r8/shaking/b169045091/B169045091.java b/src/test/java/com/android/tools/r8/shaking/b169045091/B169045091.java
index 1c6a438..3a32244 100644
--- a/src/test/java/com/android/tools/r8/shaking/b169045091/B169045091.java
+++ b/src/test/java/com/android/tools/r8/shaking/b169045091/B169045091.java
@@ -99,12 +99,18 @@
     // Test that HelloGreeter.greet() is accessible to TestClass.
     DexMethod helloReference = buildNullaryVoidMethod(HelloGreeter.class, "hello", dexItemFactory);
     assertTrue(
-        appInfo.resolveMethodOnClass(helloReference).isAccessibleFrom(context, appView).isTrue());
+        appInfo
+            .resolveMethodOnClassHolder(helloReference)
+            .isAccessibleFrom(context, appView)
+            .isTrue());
 
     // Test that WorldGreeter.greet() is inaccessible to TestClass.
     DexMethod worldReference = buildNullaryVoidMethod(WorldGreeter.class, "world", dexItemFactory);
     assertTrue(
-        appInfo.resolveMethodOnClass(worldReference).isAccessibleFrom(context, appView).isFalse());
+        appInfo
+            .resolveMethodOnClassHolder(worldReference)
+            .isAccessibleFrom(context, appView)
+            .isFalse());
   }
 
   public static class TestClass {
diff --git a/src/test/java/com/android/tools/r8/shaking/ifrule/verticalclassmerging/MergedFieldTypeTest.java b/src/test/java/com/android/tools/r8/shaking/ifrule/verticalclassmerging/MergedFieldTypeTest.java
index 12c0643..8e4f9f4 100644
--- a/src/test/java/com/android/tools/r8/shaking/ifrule/verticalclassmerging/MergedFieldTypeTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/ifrule/verticalclassmerging/MergedFieldTypeTest.java
@@ -65,12 +65,16 @@
 
       public static void main(String[] args) {
         TestClass obj = new TestClass();
-        if (false) {
+        if (alwaysFalse()) {
           obj.field = new B();
           System.out.println(obj.field);
         }
         System.out.print(obj.get().getClass().getName());
       }
+
+      static boolean alwaysFalse() {
+        return false;
+      }
     }
 
     public MergedFieldTypeWithCollisionTest(
diff --git a/src/test/java/com/android/tools/r8/shaking/keptgraph/KeptSingletonIsNotCyclicTest.java b/src/test/java/com/android/tools/r8/shaking/keptgraph/KeptSingletonIsNotCyclicTest.java
index a14683a..b34d36d 100644
--- a/src/test/java/com/android/tools/r8/shaking/keptgraph/KeptSingletonIsNotCyclicTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/keptgraph/KeptSingletonIsNotCyclicTest.java
@@ -101,13 +101,13 @@
     // TestClass.foo is kept by TestClass.<init>.
     QueryNode testFooFieldNode =
         inspector.field(testFooFieldRef).assertPresent().assertKeptBy(testInit);
-    // The type Foo is not kept by TestClass.<init>, but TestClass.foo.
+    // The type Foo is kept by TestClass.<init> and TestClass.foo.
     QueryNode fooClassNode =
         inspector
             .clazz(fooClassRef)
             .assertRenamed()
             .assertKeptBy(testFooFieldNode)
-            .assertNotKeptBy(testInit);
+            .assertKeptBy(testInit);
     // Foo.<clinit> is kept by Foo
     QueryNode fooClInit = inspector.method(fooClInitRef).assertPresent().assertKeptBy(fooClassNode);
     // The type Foo is also kept by Foo.<clinit>
diff --git a/src/test/java/com/android/tools/r8/synthesis/globals/GlobalSyntheticsConsumerAndProvider.java b/src/test/java/com/android/tools/r8/synthesis/globals/GlobalSyntheticsConsumerAndProvider.java
new file mode 100644
index 0000000..57f5622
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/synthesis/globals/GlobalSyntheticsConsumerAndProvider.java
@@ -0,0 +1,41 @@
+// Copyright (c) 2022, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.synthesis.globals;
+
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+
+import com.android.tools.r8.GlobalSyntheticsConsumer;
+import com.android.tools.r8.GlobalSyntheticsResourceProvider;
+import com.android.tools.r8.ResourceException;
+import com.android.tools.r8.origin.Origin;
+import java.io.ByteArrayInputStream;
+import java.io.InputStream;
+
+public class GlobalSyntheticsConsumerAndProvider
+    implements GlobalSyntheticsConsumer, GlobalSyntheticsResourceProvider {
+
+  private byte[] bytes;
+
+  @Override
+  public void accept(byte[] bytes) {
+    assertNull(this.bytes);
+    assertNotNull(bytes);
+    this.bytes = bytes;
+  }
+
+  @Override
+  public Origin getOrigin() {
+    return Origin.unknown();
+  }
+
+  @Override
+  public InputStream getByteStream() throws ResourceException {
+    return new ByteArrayInputStream(bytes);
+  }
+
+  public boolean hasBytes() {
+    return true;
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/tracereferences/TraceMethodResolutionWithLibraryAndProgramClassTest.java b/src/test/java/com/android/tools/r8/tracereferences/TraceMethodResolutionWithLibraryAndProgramClassTest.java
new file mode 100644
index 0000000..4dcf1cd
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/tracereferences/TraceMethodResolutionWithLibraryAndProgramClassTest.java
@@ -0,0 +1,147 @@
+// Copyright (c) 2022, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.tracereferences;
+
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.DiagnosticsHandler;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.references.MethodReference;
+import com.android.tools.r8.references.Reference;
+import com.android.tools.r8.transformers.ClassFileTransformer.MethodPredicate;
+import com.android.tools.r8.utils.DescriptorUtils;
+import com.android.tools.r8.utils.ZipUtils.ZipBuilder;
+import com.google.common.collect.ImmutableSet;
+import java.nio.file.Path;
+import java.util.HashSet;
+import java.util.Set;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+// This is a regression test for b/226170842.
+@RunWith(Parameterized.class)
+public class TraceMethodResolutionWithLibraryAndProgramClassTest extends TestBase {
+
+  @Parameter() public TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withNoneRuntime().build();
+  }
+
+  static class SeenReferencesConsumer implements TraceReferencesConsumer {
+
+    private final Set<MethodReference> seenMethods = new HashSet<>();
+    private final Set<MethodReference> seenMissingMethods = new HashSet<>();
+
+    @Override
+    public void acceptType(TracedClass tracedClass, DiagnosticsHandler handler) {}
+
+    @Override
+    public void acceptField(TracedField tracedField, DiagnosticsHandler handler) {}
+
+    @Override
+    public void acceptMethod(TracedMethod tracedMethod, DiagnosticsHandler handler) {
+      if (tracedMethod.isMissingDefinition()) {
+        seenMissingMethods.add(tracedMethod.getReference());
+      } else {
+        seenMethods.add(tracedMethod.getReference());
+      }
+    }
+  }
+
+  @Test
+  public void testInvalidResolution() throws Exception {
+    Path dir = temp.newFolder().toPath();
+    Path libJar =
+        ZipBuilder.builder(dir.resolve("lib.jar"))
+            .addFilesRelative(
+                ToolHelper.getClassPathForTests(), ToolHelper.getClassFileForTestClass(A.class))
+            .addBytes(
+                DescriptorUtils.getPathFromJavaType(B.class),
+                transformer(B.class).removeMethods(MethodPredicate.all()).transform())
+            .build();
+    Path targetJar =
+        ZipBuilder.builder(dir.resolve("target.jar"))
+            .addBytes(
+                DescriptorUtils.getPathFromJavaType(A.class),
+                transformer(A.class).removeMethods(MethodPredicate.all()).transform())
+            .addFilesRelative(
+                ToolHelper.getClassPathForTests(), ToolHelper.getClassFileForTestClass(B.class))
+            .build();
+    Path sourceJar =
+        ZipBuilder.builder(dir.resolve("source.jar"))
+            .addFilesRelative(
+                ToolHelper.getClassPathForTests(), ToolHelper.getClassFileForTestClass(Main.class))
+            .build();
+    SeenReferencesConsumer consumer = new SeenReferencesConsumer();
+    TraceReferences.run(
+        TraceReferencesCommand.builder()
+            .addLibraryFiles(ToolHelper.getMostRecentAndroidJar())
+            .addLibraryFiles(libJar)
+            .addTargetFiles(targetJar)
+            .addSourceFiles(sourceJar)
+            .setConsumer(consumer)
+            .build());
+    ImmutableSet<MethodReference> foundSet =
+        ImmutableSet.of(
+            Reference.methodFromMethod(A.class.getMethod("foo")),
+            Reference.methodFromMethod(A.class.getMethod("bar")));
+    ImmutableSet<MethodReference> missingSet =
+        ImmutableSet.of(
+            Reference.methodFromMethod(B.class.getMethod("baz")),
+            Reference.methodFromMethod(B.class.getMethod("qux")));
+    assertEquals(foundSet, consumer.seenMethods);
+    // TODO(b/226170842): Methods should not be missing.
+    assertEquals(missingSet, consumer.seenMissingMethods);
+  }
+
+  // A is added to both library and program, but the program one is missing the methods {foo,bar}
+  public static class A {
+
+    public static boolean foo() {
+      return true;
+    }
+
+    public int bar() {
+      return 42;
+    }
+  }
+
+  // B is added to both library and program, but the library one is missing the methods {baz,qux}
+  public static class B {
+
+    public static boolean baz() {
+      return false;
+    }
+
+    public int qux() {
+      return 42;
+    }
+  }
+
+  public static class Main {
+
+    public static void main(String[] args) {
+      A a = getAInstance(null);
+      B b = getBInstance(null);
+      int value = (A.foo() && B.baz()) ? a.bar() : b.qux();
+    }
+
+    private static A getAInstance(Object o) {
+      return (A) o;
+    }
+
+    private static B getBInstance(Object o) {
+      return (B) o;
+    }
+  }
+}
diff --git a/third_party/binary_compatibility_tests/compiler_api_tests.tar.gz.sha1 b/third_party/binary_compatibility_tests/compiler_api_tests.tar.gz.sha1
index c55bcfe..d9e0fb6 100644
--- a/third_party/binary_compatibility_tests/compiler_api_tests.tar.gz.sha1
+++ b/third_party/binary_compatibility_tests/compiler_api_tests.tar.gz.sha1
@@ -1 +1 @@
-bf40dc3b9e57b7759d8da51fb70d9995af1a188d
\ No newline at end of file
+2b8f463bcc995898411329d9cd55892289d5763e
\ No newline at end of file
diff --git a/third_party/jctf.tar.gz.sha1 b/third_party/jctf.tar.gz.sha1
deleted file mode 100644
index ebad250..0000000
--- a/third_party/jctf.tar.gz.sha1
+++ /dev/null
@@ -1 +0,0 @@
-7104dcb8474c494083a204b6200207b42f6cf935
\ No newline at end of file
diff --git a/third_party/openjdk/jdk-18/linux.tar.gz.sha1 b/third_party/openjdk/jdk-18/linux.tar.gz.sha1
index 3c8efd9..3ee16d6 100644
--- a/third_party/openjdk/jdk-18/linux.tar.gz.sha1
+++ b/third_party/openjdk/jdk-18/linux.tar.gz.sha1
@@ -1 +1 @@
-b545df5778c9025fe105dddcd8b3830d8731137b
\ No newline at end of file
+3685c34b5ad5a901a5cada63f7c7eb5ca3a45a0c
\ No newline at end of file
diff --git a/third_party/openjdk/jdk-18/osx.tar.gz.sha1 b/third_party/openjdk/jdk-18/osx.tar.gz.sha1
index 9699f89..ea720a2 100644
--- a/third_party/openjdk/jdk-18/osx.tar.gz.sha1
+++ b/third_party/openjdk/jdk-18/osx.tar.gz.sha1
@@ -1 +1 @@
-527883f93a157ae249f83f461b0368d87c8fe6a1
\ No newline at end of file
+077a30ec9b354e875da54edce59d44ed480ff3eb
\ No newline at end of file
diff --git a/third_party/openjdk/jdk-18/windows.tar.gz.sha1 b/third_party/openjdk/jdk-18/windows.tar.gz.sha1
index 854167c..4120e12 100644
--- a/third_party/openjdk/jdk-18/windows.tar.gz.sha1
+++ b/third_party/openjdk/jdk-18/windows.tar.gz.sha1
@@ -1 +1 @@
-b1e3cb3be67dd78423a0a96c0214ac97b5edd112
\ No newline at end of file
+98b0f7b4198f2257c57ee68f553f46112eb260e4
\ No newline at end of file
diff --git a/tools/archive_desugar_jdk_libs.py b/tools/archive_desugar_jdk_libs.py
index cc9bee4..2f13541 100755
--- a/tools/archive_desugar_jdk_libs.py
+++ b/tools/archive_desugar_jdk_libs.py
@@ -29,7 +29,7 @@
 import sys
 import utils
 
-VERSION_FILE = 'VERSION.txt'
+VERSION_FILE = 'VERSION_JDK11.txt'
 LIBRARY_NAME = 'desugar_jdk_libs'
 
 def ParseOptions(argv):
@@ -37,7 +37,7 @@
   result.add_option('--variant',
       help='.',
       choices = ['jdk8', 'jdk11'],
-      default='jdk8')
+      default='jdk11')
   result.add_option('--dry-run', '--dry_run',
       help='Running on bot, use third_party dependency.',
       default=False,
@@ -65,7 +65,7 @@
           + version_file + ' is expected to have exactly one line')
     version = lines[0].strip()
     utils.check_basic_semver_version(
-        version, 'in version file ' + version_file_name)
+        version, 'in version file ' + version_file_name, allowPrerelease = True)
     return version
 
 
diff --git a/tools/asmifier.py b/tools/asmifier.py
index 1a8e4ac..2b6a799 100755
--- a/tools/asmifier.py
+++ b/tools/asmifier.py
@@ -25,7 +25,7 @@
   cmd.append('org.objectweb.asm.util.ASMifier')
   cmd.extend(args)
   utils.PrintCmd(cmd)
-  result = subprocess.check_output(cmd)
+  result = subprocess.check_output(cmd).decode('utf-8')
   print(result)
   return result
 
@@ -45,9 +45,9 @@
       help = False
       args.append(arg)
   if help:
-    print "asmifier.py [--no-build] [--no-debug] <classfile>*"
-    print "  --no-build    Don't run R8 dependencies."
-    print "  --no-debug    Don't include local variable information in output."
+    print("asmifier.py [--no-build] [--no-debug] <classfile>*")
+    print("  --no-build    Don't run R8 dependencies.")
+    print("  --no-debug    Don't include local variable information in output.")
     return
   try:
     run(args, build)
diff --git a/tools/create_jctf_tests.py b/tools/create_jctf_tests.py
deleted file mode 100755
index 38e1f9c..0000000
--- a/tools/create_jctf_tests.py
+++ /dev/null
@@ -1,136 +0,0 @@
-#!/usr/bin/env python3
-# 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.
-
-from __future__ import print_function
-from glob import glob
-from itertools import chain
-from os import makedirs
-from os.path import exists, join, dirname
-from shutil import rmtree
-from string import Template
-import os
-import re
-import sys
-
-import utils
-
-JCTFROOT = join(utils.REPO_ROOT, 'third_party', 'jctf')
-DESTINATION_DIR = join(utils.REPO_ROOT, 'build', 'generated', 'test', 'java',
-    'com', 'android', 'tools', 'r8', 'jctf')
-PACKAGE_PREFIX = 'com.google.jctf.test.lib.java.'
-RELATIVE_TESTDIR = join('LibTests', 'src', 'com', 'google', 'jctf', 'test',
-    'lib', 'java')
-TESTDIR = join(JCTFROOT, RELATIVE_TESTDIR)
-TEMPLATE = Template(
-"""// 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.jctf.${compilerUnderTest}.${relativePackage};
-
-import org.junit.Test;
-import com.android.tools.r8.R8RunArtTestsTest;
-
-/**
- * Auto-generated test for the jctf test:
- * ${name}
- *
- * DO NOT EDIT THIS FILE. EDIT THE HERE DOCUMENT TEMPLATE IN
- * tools/create_jctf_tests.py INSTEAD!
- */
-public class ${testClassName} extends R8RunArtTestsTest {
-
-  public ${testClassName}() {
-    super("${nameWithoutPackagePrefix}", DexTool.NONE);
-  }
-
-  @Test
-  public void run${testClassName}() throws Exception {
-    // For testing with other Art VMs than the default set the system property
-    // 'dex_vm' to the desired VM string (e.g. '4.4.4', see ToolHelper.DexVm)
-    runJctfTest(CompilerUnderTest.${compilerUnderTestEnum},
-      "$classFile",
-      "$name"
-    );
-  }
-}
-""")
-
-EXIT_FAILURE = 1
-RE_PACKAGE = re.compile('package\\s+(com[^\\s;]*)')
-
-def fix_long_path(p):
-  if os.name == 'nt':
-    p = ('\\\\?\\' + p).decode('utf-8')
-  return p
-
-def file_contains_string(filepath, search_string):
-  with open(fix_long_path(filepath)) as f:
-    return search_string in f.read()
-
-def read_package_from_java_file(filepath):
-  with open(fix_long_path(filepath)) as f:
-    for line in f:
-      m = RE_PACKAGE.search(line)
-      if m:
-        return m.groups()[0]
-  raise IOError("Can't find package statement in java file: " + filepath)
-
-
-def generate_test(class_name, compiler_under_test, compiler_under_test_enum,
-    relative_package):
-  filename = join(DESTINATION_DIR, compiler_under_test,
-      relative_package.replace('.', os.sep), class_name + '.java')
-  utils.makedirs_if_needed(dirname(filename))
-
-  full_class_name = '{}{}.{}'.format(PACKAGE_PREFIX, relative_package,
-      class_name)
-  contents = TEMPLATE.substitute(
-      compilerUnderTest = compiler_under_test,
-      relativePackage = relative_package,
-      name = full_class_name,
-      testClassName = class_name,
-      compilerUnderTestEnum = compiler_under_test_enum,
-      classFile = full_class_name.replace('.', '/') + '.class',
-      nameWithoutPackagePrefix = '{}.{}'.format(relative_package, class_name))
-
-  with open(fix_long_path(filename), 'w') as f:
-    f.write(contents)
-
-def Main():
-  if not exists(JCTFROOT):
-    print('JCTF test package not found in {}'.format(JCTFROOT),
-        file = sys.stderr)
-    return EXIT_FAILURE
-
-  for tool in ['d8', 'r8']:
-    p = fix_long_path(join(DESTINATION_DIR, tool))
-    if exists(p):
-      rmtree(p)
-    makedirs(p)
-
-  java_files = (chain.from_iterable(glob(join(x[0], '*.java'))
-      for x in os.walk(TESTDIR)))
-
-  dot_java_dot = '.java.'
-
-  for f in java_files:
-    if not file_contains_string(f, '@Test'):
-      continue
-
-    class_name = os.path.splitext(os.path.basename(f))[0]
-    assert class_name.find('-') < 0
-
-    package = read_package_from_java_file(f)
-
-    idx = package.find(dot_java_dot)
-    assert idx >= 0
-    relative_package = package[idx + len(dot_java_dot):]
-
-    generate_test(class_name, 'd8', 'R8_AFTER_D8', relative_package)
-    generate_test(class_name, 'r8cf', 'R8CF', relative_package)
-
-
-if __name__ == '__main__':
-  sys.exit(Main())
diff --git a/tools/find_haning_test.py b/tools/find_haning_test.py
index 786fbcf..3c65317 100755
--- a/tools/find_haning_test.py
+++ b/tools/find_haning_test.py
@@ -5,7 +5,7 @@
 
 import argparse
 import sys
-import urllib
+
 
 def ParseOptions():
   parser = argparse.ArgumentParser(
@@ -14,7 +14,7 @@
 
 def get_started(stdout):
   # Lines look like:
-  # Start executing test runBigInteger_ZERO_A01 [com.android.tools.r8.jctf.r8cf.math.BigInteger.ZERO.BigInteger_ZERO_A01]
+  # Start executing test runBigInteger_ZERO_A01 [com.android.tools.r8.x.r8cf.math.BigInteger.ZERO.BigInteger_ZERO_A01]
   start_lines = []
   for line in stdout:
     if line.startswith('Start executing test'):
@@ -24,7 +24,7 @@
 
 def get_ended(stdout):
   # Lines look like:
-  # Done executing test runBigInteger_subtract_A01 [com.android.tools.r8.jctf.r8cf.math.BigInteger.subtractLjava_math_BigInteger.BigInteger_subtract_A01] with result: SUCCESS
+  # Done executing test runBigInteger_subtract_A01 [com.android.tools.r8.x.r8cf.math.BigInteger.subtractLjava_math_BigInteger.BigInteger_subtract_A01] with result: SUCCESS
   done_lines = []
   for line in stdout:
     if line.startswith('Done executing test'):
diff --git a/tools/test.py b/tools/test.py
index ec4bb00..665ab5f 100755
--- a/tools/test.py
+++ b/tools/test.py
@@ -7,9 +7,6 @@
 # if an argument is given, run only tests with that pattern. This script will
 # force the tests to run, even if no input changed.
 
-import archive_desugar_jdk_libs
-import download_kotlin_dev
-import notify
 import optparse
 import os
 import shutil
@@ -18,7 +15,10 @@
 import time
 import uuid
 
+import archive_desugar_jdk_libs
+import download_kotlin_dev
 import gradle
+import notify
 import utils
 
 if utils.is_python3():
@@ -57,6 +57,7 @@
   'jdk8',
   'jdk9',
   'jdk11',
+  'jdk17',
 ] + [ 'dex-%s' % dexvm for dexvm in ALL_ART_VMS ]
 
 def ParseOptions():
@@ -100,15 +101,6 @@
       help='Tool to run ART tests with: "r8" (default) or "d8" or "r8cf"'
           ' (r8 w/CF-backend). Ignored if "--all_tests" enabled.',
       default=None, choices=["r8", "d8", "r8cf"])
-  result.add_option('--jctf',
-      help='Run JCTF tests with: "r8" (default) or "d8" or "r8cf".',
-      default=False, action='store_true')
-  result.add_option('--only-jctf', '--only_jctf',
-      help='Run only JCTF tests with: "r8" (default) or "d8" or "r8cf".',
-      default=False, action='store_true')
-  result.add_option('--jctf-compile-only', '--jctf_compile_only',
-      help="Don't run, only compile JCTF tests.",
-      default=False, action='store_true')
   result.add_option('--disable-assertions', '--disable_assertions',
       help='Disable assertions when running tests.',
       default=False, action='store_true')
@@ -276,14 +268,8 @@
     gradle_args.append('-Ptool=%s' % options.tool)
   if options.one_line_per_test:
     gradle_args.append('-Pone_line_per_test')
-  if options.jctf:
-    gradle_args.append('-Pjctf')
-  if options.only_jctf:
-    gradle_args.append('-Ponly_jctf')
   if options.test_namespace:
     gradle_args.append('-Ptest_namespace=%s' % options.test_namespace)
-  if options.jctf_compile_only:
-    gradle_args.append('-Pjctf_compile_only')
   if options.disable_assertions:
     gradle_args.append('-Pdisable_assertions')
   if options.with_code_coverage:
@@ -298,14 +284,7 @@
     gradle_args.append('-Pkotlin_compiler_dev')
     download_kotlin_dev.download_newest()
   if os.name == 'nt':
-    # temporary hack
     gradle_args.append('-Pno_internal')
-    gradle_args.append('-x')
-    gradle_args.append('createJctfTests')
-    gradle_args.append('-x')
-    gradle_args.append('jctfCommonJar')
-    gradle_args.append('-x')
-    gradle_args.append('jctfTestsClasses')
   if options.test_dir:
     gradle_args.append('-Ptest_dir=' + options.test_dir)
     if not os.path.exists(options.test_dir):
@@ -407,11 +386,6 @@
           timeout_handler, (timestamp_file, print_stacks_timeout,))
   rotate_test_reports()
 
-  if options.only_jctf:
-    # Note: not setting -Pruntimes will run with all available runtimes.
-    return_code = gradle.RunGradle(gradle_args, throw_on_failure=False)
-    return archive_and_return(return_code, options)
-
   # Now run tests on selected runtime(s).
   if options.runtimes:
     if options.dex_vm != 'default':
@@ -476,7 +450,7 @@
   return return_code
 
 def print_jstacks():
-  processes = subprocess.check_output(['ps', 'aux'])
+  processes = subprocess.check_output(['ps', 'aux']).decode('utf-8')
   for l in processes.splitlines():
     if 'art' in l or 'dalvik' in l:
       print('Running art of dalvik process: \n%s' % l)