Merge commit 'b84da85ab9d8569f7f8ee41238c527434a9f2c14' into dev-release
diff --git a/build.gradle b/build.gradle
index 12d14c9..92e940a 100644
--- a/build.gradle
+++ b/build.gradle
@@ -284,7 +284,6 @@
 def r8LibPath = "$buildDir/libs/r8lib.jar"
 def r8LibExludeDepsPath = "$buildDir/libs/r8lib-exclude-deps.jar"
 def r8LibGeneratedKeepRulesPath = "$buildDir/generated/keep.txt"
-def r8LibGeneratedKeepRulesExcludeDepsPath = "$buildDir/generated/keep-exclude-deps.txt"
 def r8LibTestPath = "$buildDir/classes/r8libtest"
 def java11ClassFiles = "build/classes/java/mainJava11"
 
@@ -402,6 +401,7 @@
     "third_party": [
         "benchmarks/kotlin-benches",
         "chrome/chrome_180917_ffbaa8",
+        "chrome/chrome_200430",
         "classlib",
         "cf_segments",
         "desugar/desugar_20180308",
@@ -695,33 +695,9 @@
     }
 }
 
-static configureRelocations(ShadowJar task) {
-    task.relocate('com.google.common', 'com.android.tools.r8.com.google.common')
-    task.relocate('com.google.gson', 'com.android.tools.r8.com.google.gson')
-    task.relocate('com.google.thirdparty', 'com.android.tools.r8.com.google.thirdparty')
-    task.relocate('joptsimple', 'com.android.tools.r8.joptsimple')
-    task.relocate('org.objectweb.asm', 'com.android.tools.r8.org.objectweb.asm')
-    task.relocate('it.unimi.dsi.fastutil', 'com.android.tools.r8.it.unimi.dsi.fastutil')
-    task.relocate('kotlin', 'com.android.tools.r8.jetbrains.kotlin')
-    task.relocate('kotlinx', 'com.android.tools.r8.jetbrains.kotlinx')
-    task.relocate('org.jetbrains', 'com.android.tools.r8.org.jetbrains')
-    task.relocate('org.intellij', 'com.android.tools.r8.org.intellij')
-}
-
-task repackageDepsNoRelocate(type: ShadowJar) {
-    configurations = [project.configurations.runtimeClasspath]
-    mergeServiceFiles(it)
-    exclude { it.getRelativePath().getPathString() == "module-info.class" }
-    exclude { it.getRelativePath().getPathString().startsWith("META-INF/maven/") }
-    baseName 'deps-not-relocated'
-}
-
 task repackageDeps(type: ShadowJar) {
     configurations = [project.configurations.runtimeClasspath]
     mergeServiceFiles(it)
-    if (!project.hasProperty('lib_no_relocate')) {
-        configureRelocations(it)
-    }
     exclude { it.getRelativePath().getPathString() == "module-info.class" }
     exclude { it.getRelativePath().getPathString().startsWith("META-INF/maven/") }
     baseName 'deps'
@@ -730,9 +706,6 @@
 task repackageSources(type: ShadowJar) {
     from sourceSets.main.output
     mergeServiceFiles(it)
-    if (!project.hasProperty('lib_no_relocate')) {
-        configureRelocations(it)
-    }
     baseName 'sources'
 }
 
@@ -740,100 +713,161 @@
     dependsOn compileMainWithJava11
     from file(java11ClassFiles)
     mergeServiceFiles(it)
-    if (!project.hasProperty('lib_no_relocate')) {
-        configureRelocations(it)
-    }
     baseName 'sources11'
 }
 
-task r8WithRelocatedDeps(type: ShadowJar) {
-    from consolidatedLicense.outputs.files
-    baseName 'r8_with_relocated_deps'
-    classifier = null
-    version = null
-    manifest {
-        attributes 'Main-Class': 'com.android.tools.r8.SwissArmyKnife'
+def r8CreateTask(name, baseNameName, sources, includeSwissArmyKnife) {
+    return tasks.create("r8Create${name}", ShadowJar) {
+        from consolidatedLicense.outputs.files
+        from sources
+        baseName baseNameName
+        classifier = null
+        version = null
+        if (includeSwissArmyKnife) {
+            manifest {
+                attributes 'Main-Class': 'com.android.tools.r8.SwissArmyKnife'
+            }
+        }
+        exclude "META-INF/*.kotlin_module"
+        exclude "**/*.kotlin_metadata"
     }
-    from repackageSources.outputs.files
-    from repackageDeps.outputs.files
-    configureRelocations(it)
-    exclude "META-INF/*.kotlin_module"
-    exclude "**/*.kotlin_metadata"
 }
 
-task r8WithRelocatedDeps11(type: ShadowJar) {
-    from consolidatedLicense.outputs.files
-    baseName 'r8_with_relocated_deps_11'
-    classifier = null
-    version = null
-    manifest {
-        attributes 'Main-Class': 'com.android.tools.r8.SwissArmyKnife'
+def r8RelocateTask(r8Task, output) {
+    return tasks.create("r8Relocate_${r8Task.name}", Exec) {
+        dependsOn r8WithDeps
+        dependsOn r8Task
+        outputs.file output
+        workingDir = projectDir
+        inputs.files r8Task.outputs.files + r8WithDeps.outputs.files
+        commandLine baseR8CommandLine([
+                "relocator",
+                "--input",
+                r8Task.outputs.files[0],
+                "--output",
+                output,
+                "--map",
+                "com.google.common->com.android.tools.r8.com.google.common",
+                "--map",
+                "com.google.gson->com.android.tools.r8.com.google.gson",
+                "--map",
+                "com.google.thirdparty->com.android.tools.r8.com.google.thirdparty",
+                "--map",
+                "joptsimple->com.android.tools.r8.joptsimple",
+                "--map",
+                "org.objectweb.asm->com.android.tools.r8.org.objectweb.asm",
+                "--map",
+                "it.unimi.dsi.fastutil->com.android.tools.r8.it.unimi.dsi.fastutil",
+                "--map",
+                "kotlin->com.android.tools.r8.jetbrains.kotlin",
+                "--map",
+                "kotlinx->com.android.tools.r8.jetbrains.kotlinx",
+                "--map",
+                "org.jetbrains->com.android.tools.r8.org.jetbrains",
+                "--map",
+                "org.intellij->com.android.tools.r8.org.intellij"
+        ])
     }
-    from repackageSources11.outputs.files
-    from repackageDeps.outputs.files
-    configureRelocations(it)
-    exclude "META-INF/*.kotlin_module"
-    exclude "**/*.kotlin_metadata"
 }
 
-task r8WithoutDeps(type: ShadowJar) {
-    from consolidatedLicense.outputs.files
-    baseName 'r8_without_deps'
-    classifier = null
-    version = null
-    manifest {
-        attributes 'Main-Class': 'com.android.tools.r8.SwissArmyKnife'
-    }
-    from sourceSets.main.output
+task r8WithDeps {
+    dependsOn repackageSources
+    dependsOn repackageDeps
+    def r8Task = r8CreateTask(
+            'WithDeps',
+            'r8_with_deps',
+            repackageSources.outputs.files + repackageDeps.outputs.files,
+            true)
+    dependsOn r8Task
+    outputs.files r8Task.outputs.files
 }
 
-task R8(type: ShadowJar) {
-    from consolidatedLicense.outputs.files
-    baseName 'r8'
-    classifier = null
-    version = null
-    manifest {
-        attributes 'Main-Class': 'com.android.tools.r8.SwissArmyKnife'
-    }
-    // In order to build without dependencies, pass the exclude_deps property using:
-    // gradle -Pexclude_deps R8
-    if (!project.hasProperty('exclude_deps')) {
-        from repackageSources.outputs.files
-        from repackageDeps.outputs.files
-    } else {
-        from sourceSets.main.output
-    }
-    exclude "META-INF/*.kotlin_module"
-    exclude "**/*.kotlin_metadata"
+task r8WithDeps11 {
+    dependsOn repackageSources11
+    dependsOn repackageDeps
+    def r8Task = r8CreateTask(
+            'WithDeps11',
+            'r8_with_deps_11',
+            repackageSources11.outputs.files + repackageDeps.outputs.files,
+            true)
+    dependsOn r8Task
+    outputs.files r8Task.outputs.files
 }
 
-task R8NoManifestNoDeps(type: ShadowJar) {
-    from consolidatedLicense.outputs.files
-    baseName 'r8nomanifest-exclude-deps'
-    classifier = null
-    version = null
-    from sourceSets.main.output
+task r8WithRelocatedDeps {
+    def output = "${buildDir}/libs/r8_with_relocated_deps.jar"
+    dependsOn r8RelocateTask(r8WithDeps, output)
+    outputs.file output
 }
 
-task R8NoManifest(type: ShadowJar) {
-    from consolidatedLicense.outputs.files
-    baseName 'r8nomanifest'
-    classifier = null
-    version = null
-    // In order to build without dependencies, pass the exclude_deps property using:
-    // gradle -Pexclude_deps R8
-    if (!project.hasProperty('exclude_deps')) {
-        from repackageSources.outputs.files
-        from repackageDeps.outputs.files
-    } else {
-        from sourceSets.main.output
-    }
-    exclude "META-INF/*.kotlin_module"
-    exclude "**/*.kotlin_metadata"
+task r8WithRelocatedDeps11 {
+    def output = "${buildDir}/libs/r8_with_relocated_deps_11.jar"
+    dependsOn r8RelocateTask(r8WithDeps11, output)
+    outputs.file output
+}
+
+task r8WithoutDeps {
+    dependsOn repackageSources
+    def r8Task = r8CreateTask(
+            'WithoutDeps',
+            'r8_without_deps',
+            repackageSources.outputs.files,
+            true)
+    dependsOn r8Task
+    outputs.files r8Task.outputs.files
+}
+
+task r8(type: Copy) {
+    def r8Task = project.hasProperty("exclude_deps")
+            ? r8WithoutDeps : r8WithRelocatedDeps
+    dependsOn r8Task
+    from r8Task.outputs.files[0]
+    into file("${buildDir}/libs")
+    rename { String fileName -> "r8.jar" }
+    outputs.file "${buildDir}/libs/r8.jar"
+}
+
+task r8NoManifestWithoutDeps {
+    dependsOn repackageSources
+    def r8Task = r8CreateTask(
+            'NoManifestWithoutDeps',
+            'r8_no_manifest_without_deps',
+            repackageSources.outputs.files,
+            false)
+    dependsOn r8Task
+    outputs.files r8Task.outputs.files
+}
+
+task r8NoManifestWithDeps {
+    dependsOn repackageSources
+    def r8Task = r8CreateTask(
+            'NoManifestWithDeps',
+            'r8_no_manifest_with_deps',
+            repackageSources.outputs.files + repackageDeps.outputs.files,
+            false)
+    dependsOn r8Task
+    outputs.files r8Task.outputs.files
+}
+
+task r8NoManifestWithRelocatedDeps {
+    def output = "${buildDir}/libs/r8_no_manifest_with_relocated_deps.jar"
+    dependsOn r8RelocateTask(r8NoManifestWithDeps, output)
+    outputs.file output
+}
+
+task r8NoManifest(type: Copy) {
+    def r8Task = project.hasProperty("exclude_deps")
+            ? r8NoManifestWithoutDeps : r8NoManifestWithRelocatedDeps
+    dependsOn r8Task
+    from r8Task.outputs.files[0]
+    into file("${buildDir}/libs")
+    rename { String fileName -> "r8_no_manifest.jar" }
+    outputs.file "${buildDir}/libs/r8_no_manifest.jar"
 }
 
 task D8(type: ShadowJar) {
-    from R8.outputs.files
+    dependsOn r8
+    from r8.outputs.files[0]
     baseName 'd8'
     manifest {
         attributes 'Main-Class': 'com.android.tools.r8.D8'
@@ -841,10 +875,10 @@
 }
 
 def baseR8CommandLine(args = []) {
-    // Execute r8 commands against a stable r8 with relocated dependencies.
+    // Execute r8 commands against a stable r8 with dependencies.
     // TODO(b/139725780): See if we can remove or lower the heap size (-Xmx6g).
     return [org.gradle.internal.jvm.Jvm.current().getJavaExecutable(),
-            "-Xmx8g", "-ea", "-jar", r8WithRelocatedDeps.outputs.files[0]] + args
+            "-Xmx8g", "-ea", "-jar", r8WithDeps.outputs.files[0]] + args
 }
 
 def r8CfCommandLine(input, output, pgConfs = [], args = ["--release"], libs = []) {
@@ -877,36 +911,57 @@
     destinationDir file('build/libs')
 }
 
-task testJar(type: ShadowJar, dependsOn: [testClasses, buildLibraryDesugarConversions]) {
-    baseName = "r8tests"
+task testJarSources(type: ShadowJar, dependsOn: [testClasses, buildLibraryDesugarConversions]) {
+    baseName = "r8testsbase"
     from sourceSets.test.output
     // We only want to include tests that use R8 when generating keep rules for applymapping.
     include "com/android/tools/r8/**"
     include "dalvik/**"
 }
 
+task testJar(type: Exec) {
+    dependsOn r8WithDeps
+    dependsOn testJarSources
+    def output = "$buildDir/libs/r8tests.jar"
+    outputs.file output
+    workingDir = projectDir
+    inputs.files ([testJarSources.outputs, r8WithDeps.outputs])
+    commandLine baseR8CommandLine([
+            "relocator",
+            "--input",
+            testJarSources.outputs.files[0],
+            "--output",
+            output,
+            "--map",
+            "kotlinx.metadata->com.android.tools.r8.jetbrains.kotlinx.metadata"
+    ])
+}
+
 task generateR8LibKeepRules(type: Exec) {
     doFirst {
         // TODO(b/154785341): We should remove this.
         standardOutput new FileOutputStream(r8LibGeneratedKeepRulesPath)
     }
-    dependsOn R8NoManifest
     dependsOn r8WithRelocatedDeps
+    dependsOn r8NoManifestWithDeps
     dependsOn testJar
     dependsOn downloadOpenJDKrt
-    inputs.files ([r8WithRelocatedDeps.outputs, R8NoManifest.outputs, testJar.outputs])
+    inputs.files ([
+            r8WithRelocatedDeps.outputs,
+            r8NoManifestWithDeps.outputs,
+            testJar.outputs])
     outputs.file r8LibGeneratedKeepRulesPath
     commandLine baseR8CommandLine([
             "printuses",
             "--keeprules-allowobfuscation",
             "third_party/openjdk/openjdk-rt-1.8/rt.jar",
-            R8NoManifest.outputs.files[0],
+            r8NoManifestWithDeps.outputs.files[0],
             testJar.outputs.files[0]])
     workingDir = projectDir
 }
 
 task R8LibApiOnly {
-    dependsOn r8LibCreateTask("Api", ["src/main/keep.txt"], R8NoManifest, r8LibPath)
+    dependsOn r8LibCreateTask("Api", ["src/main/keep.txt"], r8NoManifest, r8LibPath)
     outputs.file r8LibPath
 }
 
@@ -916,7 +971,7 @@
             ["src/main/keep.txt",
              "src/main/keep-applymapping.txt",
              generateR8LibKeepRules.outputs.files[0]],
-            R8NoManifest,
+            r8NoManifestWithRelocatedDeps,
             r8LibPath,
     ).dependsOn(generateR8LibKeepRules)
     outputs.file r8LibPath
@@ -926,11 +981,11 @@
     dependsOn r8LibCreateTask(
             "NoDeps",
             ["src/main/keep.txt", "src/main/keep-applymapping.txt"],
-            R8NoManifestNoDeps,
+            r8NoManifestWithoutDeps,
             r8LibExludeDepsPath,
             "--release",
-            repackageDepsNoRelocate.outputs.files
-    ).dependsOn(repackageDepsNoRelocate)
+            repackageDeps.outputs.files
+    ).dependsOn(repackageDeps)
     outputs.file r8LibExludeDepsPath
 }
 
@@ -1739,7 +1794,7 @@
 task buildR8LibCfTestDeps(type: Exec) {
     def outputPath = "build/libs/r8libtestdeps-cf.jar"
     dependsOn downloadDeps
-    dependsOn R8NoManifest
+    dependsOn r8NoManifest
     dependsOn R8Lib
     dependsOn generateR8TestKeepRules
     dependsOn testJar
@@ -1754,8 +1809,8 @@
             testJar.outputs.files[0],
             outputPath,
             [generateR8TestKeepRules.outputs.files[0]],
-            ["--debug", "--classpath", R8NoManifest.outputs.files[0]],
-            R8NoManifest.outputs.files + addedLibraries)
+            ["--debug", "--classpath", r8NoManifest.outputs.files[0]],
+            r8NoManifest.outputs.files + addedLibraries)
     workingDir = projectDir
     outputs.file outputPath
 }
@@ -1823,7 +1878,7 @@
     dependsOn buildLibraryDesugarConversions
     dependsOn getJarsFromSupportLibs
     // R8.jar is required for running bootstrap tests.
-    dependsOn R8
+    dependsOn r8
     testLogging.exceptionFormat = 'full'
     if (project.hasProperty('print_test_stdout')) {
         testLogging.showStandardStreams = true
diff --git a/src/library_desugar/desugar_jdk_libs.json b/src/library_desugar/desugar_jdk_libs.json
index aadb671..07765bb 100644
--- a/src/library_desugar/desugar_jdk_libs.json
+++ b/src/library_desugar/desugar_jdk_libs.json
@@ -1,6 +1,8 @@
 {
-  "configuration_format_version": 3,
-  "version": "0.11.1",
+  "configuration_format_version": 4,
+  "group_id" : "com.tools.android",
+  "artifact_id" : "desugar_jdk_libs",
+  "version": "0.11.2",
   "required_compilation_api_level": 26,
   "synthesized_library_classes_package_prefix": "j$.",
   "library_flags": [
diff --git a/src/library_desugar/desugar_jdk_libs_comments.md b/src/library_desugar/desugar_jdk_libs_comments.md
index f08da27..2ba5908 100644
--- a/src/library_desugar/desugar_jdk_libs_comments.md
+++ b/src/library_desugar/desugar_jdk_libs_comments.md
@@ -1,4 +1,4 @@
-# Description of the core library configuration file
+# Description of the desugared library configuration file
 
 ## Version
 
@@ -7,27 +7,33 @@
 Non-backward compatible changes to the desugared library increase the version number, and such
 library cannot be compiled without upgrading R8/D8 to the latest version.
 
-The second field `version` holds the version of the content for the configuration. This number
+The fields `group_id` and `artifact_id` are maven-coordinated ids for the desugared library
+configuration file.
+
+The field `version` holds the version of the content for the configuration. This number
 must be updated each time the configuration is changed.
 
+A unique identifier is generated for the desugared library configuration using
+`group_id:artifact_id:version`.
+
 ## Required compilation API level
 
-The third field `required_compilation_api_level` encodes the minimal Android API level required for
+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.
 
 ## Library and program flags
 
-The fourth and fifth fields are `library_flags` and `program_flags`. They include the set of flags
-required for respectively the library and the program using the desugared library compilation. The
-sets of flags are different depending on the min API level used. 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 fields `library_flags` and `program_flags` include the set of flags required for respectively
+the library and the program using the desugared library compilation. The sets of flags are
+different depending on the min API level used. 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.
 
-For example, let's say the `program_flags` have entries for `api_level_below_or_equal` 20, 24 and 26.
-If compiling the program for min API 24, R8/D8 will use both the set of flags for API 24 and 26
-(24 <= 24, 24 <= 26 but !(24 <= 20)).
+For example, let's say the `program_flags` have entries for `api_level_below_or_equal` 20, 24 and
+26. If compiling the program for min API 24, R8/D8 will use both the set of flags for API 24 and
+26 (24 <= 24, 24 <= 26 but !(24 <= 20)).
 
 ## Extra keep rules
 
diff --git a/src/main/java/com/android/tools/r8/BaseCompilerCommandParser.java b/src/main/java/com/android/tools/r8/BaseCompilerCommandParser.java
index 410441b..586c4f1 100644
--- a/src/main/java/com/android/tools/r8/BaseCompilerCommandParser.java
+++ b/src/main/java/com/android/tools/r8/BaseCompilerCommandParser.java
@@ -36,17 +36,23 @@
           "                          # is the default handling of javac assertion code when",
           "                          # generating class file format.");
 
-  void parsePositiveIntArgument(
-      B builder, String flag, String argument, Origin origin, Consumer<Integer> setter) {
+  public static void parsePositiveIntArgument(
+      Consumer<Diagnostic> errorConsumer,
+      String flag,
+      String argument,
+      Origin origin,
+      Consumer<Integer> setter) {
     int value;
     try {
       value = Integer.parseInt(argument);
     } catch (NumberFormatException e) {
-      builder.error(new StringDiagnostic("Invalid argument to " + flag + ": " + argument, origin));
+      errorConsumer.accept(
+          new StringDiagnostic("Invalid argument to " + flag + ": " + argument, origin));
       return;
     }
     if (value < 1) {
-      builder.error(new StringDiagnostic("Invalid argument to " + flag + ": " + argument, origin));
+      errorConsumer.accept(
+          new StringDiagnostic("Invalid argument to " + flag + ": " + argument, origin));
       return;
     }
     setter.accept(value);
diff --git a/src/main/java/com/android/tools/r8/D8.java b/src/main/java/com/android/tools/r8/D8.java
index 3c132e0..e8c6bb1 100644
--- a/src/main/java/com/android/tools/r8/D8.java
+++ b/src/main/java/com/android/tools/r8/D8.java
@@ -239,6 +239,7 @@
       if (marker != null && hasClassResources) {
         markers.add(marker);
       }
+      Marker.checkCompatibleDesugaredLibrary(markers, options.reporter);
 
       InspectorImpl.runInspections(options.outputInspections, app);
       if (options.isGeneratingClassFiles()) {
@@ -250,7 +251,7 @@
                 GraphLense.getIdentityLense(),
                 NamingLens.getIdentityLens(),
                 null)
-            .write(options.getClassFileConsumer(), executor);
+            .write(options.getClassFileConsumer());
       } else {
         NamingLens namingLens;
         DexApplication finalApp = app;
diff --git a/src/main/java/com/android/tools/r8/D8Command.java b/src/main/java/com/android/tools/r8/D8Command.java
index 2f6197f..164c572 100644
--- a/src/main/java/com/android/tools/r8/D8Command.java
+++ b/src/main/java/com/android/tools/r8/D8Command.java
@@ -3,6 +3,8 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8;
 
+import static com.android.tools.r8.utils.InternalOptions.DETERMINISTIC_DEBUGGING;
+
 import com.android.tools.r8.AssertionsConfiguration.AssertionTransformation;
 import com.android.tools.r8.errors.DexFileOverflowDiagnostic;
 import com.android.tools.r8.graph.DexItemFactory;
@@ -422,8 +424,10 @@
 
     internal.outputInspections = InspectorImpl.wrapInspections(getOutputInspections());
 
-    assert internal.threadCount == ThreadUtils.NOT_SPECIFIED;
-    internal.threadCount = getThreadCount();
+    if (!DETERMINISTIC_DEBUGGING) {
+      assert internal.threadCount == ThreadUtils.NOT_SPECIFIED;
+      internal.threadCount = getThreadCount();
+    }
 
     return internal;
   }
diff --git a/src/main/java/com/android/tools/r8/D8CommandParser.java b/src/main/java/com/android/tools/r8/D8CommandParser.java
index aad3468..3c7fdd2 100644
--- a/src/main/java/com/android/tools/r8/D8CommandParser.java
+++ b/src/main/java/com/android/tools/r8/D8CommandParser.java
@@ -252,12 +252,13 @@
           builder.error(
               new StringDiagnostic("Cannot set multiple " + MIN_API_FLAG + " options", origin));
         } else {
-          parsePositiveIntArgument(builder, MIN_API_FLAG, nextArg, origin, builder::setMinApiLevel);
+          parsePositiveIntArgument(
+              builder::error, MIN_API_FLAG, nextArg, origin, builder::setMinApiLevel);
           hasDefinedApiLevel = true;
         }
       } else if (arg.equals(THREAD_COUNT_FLAG)) {
         parsePositiveIntArgument(
-            builder, THREAD_COUNT_FLAG, nextArg, origin, builder::setThreadCount);
+            builder::error, THREAD_COUNT_FLAG, nextArg, origin, builder::setThreadCount);
       } else if (arg.equals("--intermediate")) {
         builder.setIntermediate(true);
       } else if (arg.equals("--no-desugaring")) {
diff --git a/src/main/java/com/android/tools/r8/GenerateLintFiles.java b/src/main/java/com/android/tools/r8/GenerateLintFiles.java
index bf55656..7d5b31e 100644
--- a/src/main/java/com/android/tools/r8/GenerateLintFiles.java
+++ b/src/main/java/com/android/tools/r8/GenerateLintFiles.java
@@ -37,7 +37,6 @@
 import com.android.tools.r8.utils.FileUtils;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.Reporter;
-import com.android.tools.r8.utils.ThreadUtils;
 import com.android.tools.r8.utils.Timing;
 import com.google.common.collect.Sets;
 import java.io.File;
@@ -326,7 +325,7 @@
     ClassFileConsumer consumer =
         new ClassFileConsumer.ArchiveConsumer(
             lintFile(compilationApiLevel, minApiLevel, FileUtils.JAR_EXTENSION));
-    writer.write(consumer, ThreadUtils.getExecutorService(options));
+    writer.write(consumer);
     consumer.finished(options.reporter);
   }
 
diff --git a/src/main/java/com/android/tools/r8/GenerateMainDexList.java b/src/main/java/com/android/tools/r8/GenerateMainDexList.java
index a647cf5..42290d2 100644
--- a/src/main/java/com/android/tools/r8/GenerateMainDexList.java
+++ b/src/main/java/com/android/tools/r8/GenerateMainDexList.java
@@ -7,12 +7,13 @@
 
 import com.android.tools.r8.dex.ApplicationReader;
 import com.android.tools.r8.experimental.graphinfo.GraphConsumer;
-import com.android.tools.r8.graph.AppInfoWithSubtyping;
+import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
 import com.android.tools.r8.graph.AppServices;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DirectMappedDexApplication;
+import com.android.tools.r8.graph.SubtypingInfo;
 import com.android.tools.r8.shaking.Enqueuer;
 import com.android.tools.r8.shaking.EnqueuerFactory;
 import com.android.tools.r8.shaking.MainDexClasses;
@@ -48,14 +49,16 @@
     try {
       DirectMappedDexApplication application =
           new ApplicationReader(app, options, timing).read(executor).toDirect();
-      AppView<? extends AppInfoWithSubtyping> appView =
-          AppView.createForR8(new AppInfoWithSubtyping(application), options);
+      AppView<? extends AppInfoWithClassHierarchy> appView =
+          AppView.createForR8(new AppInfoWithClassHierarchy(application), options);
       appView.setAppServices(AppServices.builder(appView).build());
 
       MainDexListBuilder.checkForAssumedLibraryTypes(appView.appInfo());
 
+      SubtypingInfo subtypingInfo = new SubtypingInfo(application.allClasses(), application);
+
       RootSet mainDexRootSet =
-          new RootSetBuilder(appView, application, options.mainDexKeepRules).run(executor);
+          new RootSetBuilder(appView, subtypingInfo, options.mainDexKeepRules).run(executor);
 
       GraphConsumer graphConsumer = options.mainDexKeptGraphConsumer;
       WhyAreYouKeepingConsumer whyAreYouKeepingConsumer = null;
@@ -64,7 +67,8 @@
         graphConsumer = whyAreYouKeepingConsumer;
       }
 
-      Enqueuer enqueuer = EnqueuerFactory.createForMainDexTracing(appView, graphConsumer);
+      Enqueuer enqueuer =
+          EnqueuerFactory.createForMainDexTracing(appView, subtypingInfo, graphConsumer);
       Set<DexProgramClass> liveTypes = enqueuer.traceMainDex(mainDexRootSet, executor, timing);
       // LiveTypes is the result.
       MainDexClasses mainDexClasses = new MainDexListBuilder(liveTypes, application).run();
diff --git a/src/main/java/com/android/tools/r8/L8.java b/src/main/java/com/android/tools/r8/L8.java
index 50a4a70..cf9684c 100644
--- a/src/main/java/com/android/tools/r8/L8.java
+++ b/src/main/java/com/android/tools/r8/L8.java
@@ -136,7 +136,7 @@
               GraphLense.getIdentityLense(),
               PrefixRewritingNamingLens.createPrefixRewritingNamingLens(appView),
               null)
-          .write(options.getClassFileConsumer(), executor);
+          .write(options.getClassFileConsumer());
       options.printWarnings();
     } catch (ExecutionException e) {
       throw unwrapExecutionException(e);
diff --git a/src/main/java/com/android/tools/r8/L8Command.java b/src/main/java/com/android/tools/r8/L8Command.java
index 7741651..ff26467 100644
--- a/src/main/java/com/android/tools/r8/L8Command.java
+++ b/src/main/java/com/android/tools/r8/L8Command.java
@@ -3,6 +3,8 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8;
 
+import static com.android.tools.r8.utils.InternalOptions.DETERMINISTIC_DEBUGGING;
+
 import com.android.tools.r8.AssertionsConfiguration.AssertionTransformation;
 import com.android.tools.r8.ProgramResource.Kind;
 import com.android.tools.r8.errors.DexFileOverflowDiagnostic;
@@ -189,8 +191,10 @@
         new AssertionConfigurationWithDefault(
             AssertionTransformation.DISABLE, getAssertionsConfiguration());
 
-    assert internal.threadCount == ThreadUtils.NOT_SPECIFIED;
-    internal.threadCount = getThreadCount();
+    if (!DETERMINISTIC_DEBUGGING) {
+      assert internal.threadCount == ThreadUtils.NOT_SPECIFIED;
+      internal.threadCount = getThreadCount();
+    }
 
     return internal;
   }
diff --git a/src/main/java/com/android/tools/r8/L8CommandParser.java b/src/main/java/com/android/tools/r8/L8CommandParser.java
index 90db2c6..0802127 100644
--- a/src/main/java/com/android/tools/r8/L8CommandParser.java
+++ b/src/main/java/com/android/tools/r8/L8CommandParser.java
@@ -139,7 +139,8 @@
           builder.error(
               new StringDiagnostic("Cannot set multiple " + MIN_API_FLAG + " options", origin));
         } else {
-          parsePositiveIntArgument(builder, MIN_API_FLAG, nextArg, origin, builder::setMinApiLevel);
+          parsePositiveIntArgument(
+              builder::error, MIN_API_FLAG, nextArg, origin, builder::setMinApiLevel);
           hasDefinedApiLevel = true;
         }
       } else if (arg.equals("--lib")) {
@@ -150,7 +151,7 @@
         builder.addDesugaredLibraryConfiguration(StringResource.fromFile(Paths.get(nextArg)));
       } else if (arg.equals(THREAD_COUNT_FLAG)) {
         parsePositiveIntArgument(
-            builder, THREAD_COUNT_FLAG, nextArg, origin, builder::setThreadCount);
+            builder::error, THREAD_COUNT_FLAG, nextArg, origin, builder::setThreadCount);
       } else if (arg.startsWith("--")) {
         if (!tryParseAssertionArgument(builder, arg, origin)) {
           builder.error(new StringDiagnostic("Unknown option: " + arg, origin));
diff --git a/src/main/java/com/android/tools/r8/PrintSeeds.java b/src/main/java/com/android/tools/r8/PrintSeeds.java
index 41c399f..7c2302a 100644
--- a/src/main/java/com/android/tools/r8/PrintSeeds.java
+++ b/src/main/java/com/android/tools/r8/PrintSeeds.java
@@ -6,10 +6,11 @@
 import static com.android.tools.r8.utils.ExceptionUtils.unwrapExecutionException;
 
 import com.android.tools.r8.dex.ApplicationReader;
-import com.android.tools.r8.graph.AppInfoWithSubtyping;
+import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
 import com.android.tools.r8.graph.AppServices;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DirectMappedDexApplication;
+import com.android.tools.r8.graph.SubtypingInfo;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.shaking.Enqueuer;
 import com.android.tools.r8.shaking.EnqueuerFactory;
@@ -86,13 +87,14 @@
     try {
       DirectMappedDexApplication application =
           new ApplicationReader(command.getInputApp(), options, timing).read(executor).toDirect();
-      AppView<? extends AppInfoWithSubtyping> appView =
-          AppView.createForR8(new AppInfoWithSubtyping(application), options);
+      AppView<? extends AppInfoWithClassHierarchy> appView =
+          AppView.createForR8(new AppInfoWithClassHierarchy(application), options);
       appView.setAppServices(AppServices.builder(appView).build());
+      SubtypingInfo subtypingInfo = new SubtypingInfo(application.allClasses(), application);
       RootSet rootSet =
-          new RootSetBuilder(appView, application, options.getProguardConfiguration().getRules())
+          new RootSetBuilder(appView, subtypingInfo, options.getProguardConfiguration().getRules())
               .run(executor);
-      Enqueuer enqueuer = EnqueuerFactory.createForInitialTreeShaking(appView);
+      Enqueuer enqueuer = EnqueuerFactory.createForInitialTreeShaking(appView, subtypingInfo);
       AppInfoWithLiveness appInfo =
           enqueuer.traceApplication(
               rootSet, options.getProguardConfiguration().getDontWarnPatterns(), executor, timing);
diff --git a/src/main/java/com/android/tools/r8/PrintUses.java b/src/main/java/com/android/tools/r8/PrintUses.java
index 51b6ed7..0b4f28b 100644
--- a/src/main/java/com/android/tools/r8/PrintUses.java
+++ b/src/main/java/com/android/tools/r8/PrintUses.java
@@ -4,7 +4,7 @@
 package com.android.tools.r8;
 
 import com.android.tools.r8.dex.ApplicationReader;
-import com.android.tools.r8.graph.AppInfoWithSubtyping;
+import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
 import com.android.tools.r8.graph.DexAnnotation;
 import com.android.tools.r8.graph.DexApplication;
 import com.android.tools.r8.graph.DexCallSite;
@@ -74,7 +74,7 @@
   private Set<DexType> noObfuscationTypes = Sets.newIdentityHashSet();
   private Set<String> keepPackageNames = Sets.newHashSet();
   private final DirectMappedDexApplication application;
-  private final AppInfoWithSubtyping appInfo;
+  private final AppInfoWithClassHierarchy appInfo;
   private int errors;
 
   class UseCollector extends UseRegistry {
@@ -355,7 +355,7 @@
     InternalOptions options = new InternalOptions();
     application =
         new ApplicationReader(inputApp, options, new Timing("PrintUses")).read().toDirect();
-    appInfo = new AppInfoWithSubtyping(application);
+    appInfo = new AppInfoWithClassHierarchy(application);
   }
 
   private void analyze() {
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java
index 61c4930..30201ce 100644
--- a/src/main/java/com/android/tools/r8/R8.java
+++ b/src/main/java/com/android/tools/r8/R8.java
@@ -12,7 +12,7 @@
 import com.android.tools.r8.dex.Marker.Tool;
 import com.android.tools.r8.errors.CompilationError;
 import com.android.tools.r8.experimental.graphinfo.GraphConsumer;
-import com.android.tools.r8.graph.AppInfoWithSubtyping;
+import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
 import com.android.tools.r8.graph.AppServices;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.AppliedGraphLens;
@@ -212,7 +212,7 @@
       if (options.isGeneratingClassFiles()) {
         new CfApplicationWriter(
                 application, appView, options, marker, graphLense, namingLens, proguardMapSupplier)
-            .write(options.getClassFileConsumer(), executorService);
+            .write(options.getClassFileConsumer());
       } else {
         new ApplicationWriter(
                 application,
@@ -273,8 +273,8 @@
       // Now that the dex-application is fully loaded, close any internal archive providers.
       inputApp.closeInternalArchiveProviders();
 
-      AppView<AppInfoWithSubtyping> appView =
-          AppView.createForR8(new AppInfoWithSubtyping(application), options);
+      AppView<AppInfoWithClassHierarchy> appView =
+          AppView.createForR8(new AppInfoWithClassHierarchy(application), options);
       appView.setAppServices(AppServices.builder(appView).build());
 
       // Up-front check for valid library setup.
@@ -334,11 +334,11 @@
                     options.itemFactory, AndroidApiLevel.getAndroidApiLevel(options.minApiLevel)));
           }
         }
-
+        SubtypingInfo subtypingInfo = new SubtypingInfo(application.allClasses(), application);
         appView.setRootSet(
             new RootSetBuilder(
                     appView,
-                    application,
+                    subtypingInfo,
                     Iterables.concat(
                         options.getProguardConfiguration().getRules(), synthesizedProguardRules))
                 .run(executorService));
@@ -346,7 +346,7 @@
         AnnotationRemover.Builder annotationRemoverBuilder =
             options.isShrinking() ? AnnotationRemover.builder() : null;
         AppView<AppInfoWithLiveness> appViewWithLiveness =
-            runEnqueuer(annotationRemoverBuilder, executorService, appView);
+            runEnqueuer(annotationRemoverBuilder, executorService, appView, subtypingInfo);
         application = appViewWithLiveness.appInfo().app().asDirect();
         assert appView.rootSet().verifyKeptFieldsAreAccessedAndLive(appViewWithLiveness.appInfo());
         assert appView.rootSet().verifyKeptMethodsAreTargetedAndLive(appViewWithLiveness.appInfo());
@@ -414,11 +414,14 @@
       if (!options.mainDexKeepRules.isEmpty()) {
         assert appView.graphLense().isIdentityLense();
         // Find classes which may have code executed before secondary dex files installation.
+        SubtypingInfo subtypingInfo =
+            new SubtypingInfo(appView.appInfo().app().asDirect().allClasses(), appView);
         mainDexRootSet =
-            new RootSetBuilder(appView, application, options.mainDexKeepRules).run(executorService);
+            new RootSetBuilder(appView, subtypingInfo, options.mainDexKeepRules)
+                .run(executorService);
         // Live types is the tracing result.
         Set<DexProgramClass> mainDexBaseClasses =
-            EnqueuerFactory.createForMainDexTracing(appView)
+            EnqueuerFactory.createForMainDexTracing(appView, subtypingInfo)
                 .traceMainDex(mainDexRootSet, executorService, timing);
         // Calculate the automatic main dex list according to legacy multidex constraints.
         mainDexClasses = new MainDexListBuilder(mainDexBaseClasses, application).run();
@@ -469,72 +472,75 @@
         new NestReducer(appViewWithLiveness).run(executorService);
         timing.end();
       }
-      if (options.enableHorizontalClassMerging) {
-        timing.begin("HorizontalStaticClassMerger");
-        StaticClassMerger staticClassMerger =
-            new StaticClassMerger(appViewWithLiveness, options, mainDexClasses);
-        NestedGraphLense lens = staticClassMerger.run();
-        if (lens != null) {
-          boolean changed = appView.setGraphLense(lens);
-          assert changed;
-          appViewWithLiveness.setAppInfo(
-              appViewWithLiveness.appInfo().rewrittenWithLens(application.asDirect(), lens));
-        }
-        timing.end();
-      }
-      if (options.enableVerticalClassMerging) {
-        timing.begin("VerticalClassMerger");
-        VerticalClassMerger verticalClassMerger =
-            new VerticalClassMerger(
-                application, appViewWithLiveness, executorService, timing, mainDexClasses);
-        VerticalClassMergerGraphLense lens = verticalClassMerger.run();
-        if (lens != null) {
-          boolean changed = appView.setGraphLense(lens);
-          assert changed;
-          appView.setVerticallyMergedClasses(verticalClassMerger.getMergedClasses());
-          application = application.asDirect().rewrittenWithLens(lens);
-          lens.initializeCacheForLookupMethodInAllContexts();
-          appViewWithLiveness.setAppInfo(
-              appViewWithLiveness.appInfo().rewrittenWithLens(application.asDirect(), lens));
-          lens.unsetCacheForLookupMethodInAllContexts();
-        }
-        timing.end();
-      }
-      if (options.enableArgumentRemoval) {
-        SubtypingInfo subtypingInfo = appViewWithLiveness.appInfo().computeSubtypingInfo();
-        if (options.enableUnusedArgumentRemoval) {
-          timing.begin("UnusedArgumentRemoval");
-          UnusedArgumentsGraphLense lens =
-              new UnusedArgumentsCollector(
-                      appViewWithLiveness,
-                      new MethodPoolCollection(appViewWithLiveness, subtypingInfo))
-                  .run(executorService, timing);
+
+      if (options.getProguardConfiguration().isOptimizing()) {
+        if (options.enableHorizontalClassMerging) {
+          timing.begin("HorizontalStaticClassMerger");
+          StaticClassMerger staticClassMerger =
+              new StaticClassMerger(appViewWithLiveness, options, mainDexClasses);
+          NestedGraphLense lens = staticClassMerger.run();
           if (lens != null) {
             boolean changed = appView.setGraphLense(lens);
             assert changed;
-            assert application.asDirect().verifyNothingToRewrite(appView, lens);
             appViewWithLiveness.setAppInfo(
                 appViewWithLiveness.appInfo().rewrittenWithLens(application.asDirect(), lens));
           }
           timing.end();
         }
-        if (options.enableUninstantiatedTypeOptimization) {
-          timing.begin("UninstantiatedTypeOptimization");
-          UninstantiatedTypeOptimizationGraphLense lens =
-              new UninstantiatedTypeOptimization(appViewWithLiveness)
-                  .run(
-                      new MethodPoolCollection(appViewWithLiveness, subtypingInfo),
-                      executorService,
-                      timing);
+        if (options.enableVerticalClassMerging) {
+          timing.begin("VerticalClassMerger");
+          VerticalClassMerger verticalClassMerger =
+              new VerticalClassMerger(
+                  application, appViewWithLiveness, executorService, timing, mainDexClasses);
+          VerticalClassMergerGraphLense lens = verticalClassMerger.run();
           if (lens != null) {
             boolean changed = appView.setGraphLense(lens);
             assert changed;
-            assert application.asDirect().verifyNothingToRewrite(appView, lens);
+            appView.setVerticallyMergedClasses(verticalClassMerger.getMergedClasses());
+            application = application.asDirect().rewrittenWithLens(lens);
+            lens.initializeCacheForLookupMethodInAllContexts();
             appViewWithLiveness.setAppInfo(
                 appViewWithLiveness.appInfo().rewrittenWithLens(application.asDirect(), lens));
+            lens.unsetCacheForLookupMethodInAllContexts();
           }
           timing.end();
         }
+        if (options.enableArgumentRemoval) {
+          SubtypingInfo subtypingInfo = appViewWithLiveness.appInfo().computeSubtypingInfo();
+          {
+            timing.begin("UnusedArgumentRemoval");
+            UnusedArgumentsGraphLense lens =
+                new UnusedArgumentsCollector(
+                        appViewWithLiveness,
+                        new MethodPoolCollection(appViewWithLiveness, subtypingInfo))
+                    .run(executorService, timing);
+            if (lens != null) {
+              boolean changed = appView.setGraphLense(lens);
+              assert changed;
+              assert application.asDirect().verifyNothingToRewrite(appView, lens);
+              appViewWithLiveness.setAppInfo(
+                  appViewWithLiveness.appInfo().rewrittenWithLens(application.asDirect(), lens));
+            }
+            timing.end();
+          }
+          if (options.enableUninstantiatedTypeOptimization) {
+            timing.begin("UninstantiatedTypeOptimization");
+            UninstantiatedTypeOptimizationGraphLense lens =
+                new UninstantiatedTypeOptimization(appViewWithLiveness)
+                    .run(
+                        new MethodPoolCollection(appViewWithLiveness, subtypingInfo),
+                        executorService,
+                        timing);
+            if (lens != null) {
+              boolean changed = appView.setGraphLense(lens);
+              assert changed;
+              assert application.asDirect().verifyNothingToRewrite(appView, lens);
+              appViewWithLiveness.setAppInfo(
+                  appViewWithLiveness.appInfo().rewrittenWithLens(application.asDirect(), lens));
+            }
+            timing.end();
+          }
+        }
       }
 
       // None of the optimizations above should lead to the creation of type lattice elements.
@@ -594,7 +600,7 @@
           appViewWithLiveness.appInfo().getEnumValueInfoMapCollection();
 
       if (!options.mainDexKeepRules.isEmpty()) {
-        appView.setAppInfo(new AppInfoWithSubtyping(application));
+        appView.setAppInfo(new AppInfoWithClassHierarchy(application));
         // No need to build a new main dex root set
         assert mainDexRootSet != null;
         GraphConsumer mainDexKeptGraphConsumer = options.mainDexKeptGraphConsumer;
@@ -605,7 +611,10 @@
         }
 
         Enqueuer enqueuer =
-            EnqueuerFactory.createForMainDexTracing(appView, mainDexKeptGraphConsumer);
+            EnqueuerFactory.createForMainDexTracing(
+                appView,
+                new SubtypingInfo(application.allClasses(), application),
+                mainDexKeptGraphConsumer);
         // Find classes which may have code executed before secondary dex files installation.
         // Live types is the tracing result.
         Set<DexProgramClass> mainDexBaseClasses =
@@ -638,7 +647,7 @@
             executorService);
       }
 
-      appView.setAppInfo(new AppInfoWithSubtyping(application));
+      appView.setAppInfo(new AppInfoWithClassHierarchy(application));
 
       if (options.shouldRerunEnqueuer()) {
         timing.begin("Post optimization code stripping");
@@ -654,7 +663,11 @@
           }
 
           Enqueuer enqueuer =
-              EnqueuerFactory.createForFinalTreeShaking(appView, keptGraphConsumer, missingClasses);
+              EnqueuerFactory.createForFinalTreeShaking(
+                  appView,
+                  new SubtypingInfo(application.allClasses(), application),
+                  keptGraphConsumer,
+                  missingClasses);
           appView.setAppInfo(
               enqueuer
                   .traceApplication(
@@ -786,8 +799,6 @@
         namingLens = NamingLens.getIdentityLens();
       }
 
-      ProguardMapSupplier proguardMapSupplier;
-
       timing.begin("Line number remapping");
       // When line number optimization is turned off the identity mapping for line numbers is
       // used. We still run the line number optimizer to collect line numbers and inline frame
@@ -795,7 +806,6 @@
       ClassNameMapper classNameMapper =
           LineNumberOptimizer.run(appView, application, inputApp, namingLens);
       timing.end();
-      proguardMapSupplier = ProguardMapSupplier.fromClassNameMapper(classNameMapper, options);
 
       // If a method filter is present don't produce output since the application is likely partial.
       if (options.hasMethodsFilter()) {
@@ -846,7 +856,7 @@
           appView.initClassLens(),
           PrefixRewritingNamingLens.createPrefixRewritingNamingLens(appView, namingLens),
           options,
-          proguardMapSupplier);
+          ProguardMapSupplier.create(classNameMapper, options));
 
       options.printWarnings();
     } catch (ExecutionException e) {
@@ -863,9 +873,10 @@
   private AppView<AppInfoWithLiveness> runEnqueuer(
       AnnotationRemover.Builder annotationRemoverBuilder,
       ExecutorService executorService,
-      AppView<AppInfoWithSubtyping> appView)
+      AppView<AppInfoWithClassHierarchy> appView,
+      SubtypingInfo subtypingInfo)
       throws ExecutionException {
-    Enqueuer enqueuer = EnqueuerFactory.createForInitialTreeShaking(appView);
+    Enqueuer enqueuer = EnqueuerFactory.createForInitialTreeShaking(appView, subtypingInfo);
     enqueuer.setAnnotationRemoverBuilder(annotationRemoverBuilder);
     if (appView.options().enableInitializedClassesInInstanceMethodsAnalysis) {
       enqueuer.registerAnalysis(new InitializedClassesInInstanceMethodsAnalysis(appView));
@@ -897,7 +908,7 @@
       RootSet rootSet,
       Supplier<Iterable<DexProgramClass>> classes,
       WhyAreYouKeepingConsumer whyAreYouKeepingConsumer,
-      AppView<? extends AppInfoWithSubtyping> appView,
+      AppView<? extends AppInfoWithClassHierarchy> appView,
       Enqueuer enqueuer,
       boolean forMainDex,
       InternalOptions options,
@@ -921,11 +932,17 @@
     // If there is no kept-graph info, re-run the enqueueing to compute it.
     if (whyAreYouKeepingConsumer == null) {
       whyAreYouKeepingConsumer = new WhyAreYouKeepingConsumer(null);
+      SubtypingInfo subtypingInfo =
+          new SubtypingInfo(appView.appInfo().app().asDirect().allClasses(), appView);
       if (forMainDex) {
-        enqueuer = EnqueuerFactory.createForMainDexTracing(appView, whyAreYouKeepingConsumer);
+        enqueuer =
+            EnqueuerFactory.createForMainDexTracing(
+                appView, subtypingInfo, whyAreYouKeepingConsumer);
         enqueuer.traceMainDex(rootSet, executorService, timing);
       } else {
-        enqueuer = EnqueuerFactory.createForWhyAreYouKeeping(appView, whyAreYouKeepingConsumer);
+        enqueuer =
+            EnqueuerFactory.createForWhyAreYouKeeping(
+                appView, subtypingInfo, whyAreYouKeepingConsumer);
         enqueuer.traceApplication(
             rootSet,
             options.getProguardConfiguration().getDontWarnPatterns(),
diff --git a/src/main/java/com/android/tools/r8/R8CommandParser.java b/src/main/java/com/android/tools/r8/R8CommandParser.java
index f04f937..d31606d 100644
--- a/src/main/java/com/android/tools/r8/R8CommandParser.java
+++ b/src/main/java/com/android/tools/r8/R8CommandParser.java
@@ -200,12 +200,12 @@
               new StringDiagnostic("Cannot set multiple " + MIN_API_FLAG + " options", argsOrigin));
         } else {
           parsePositiveIntArgument(
-              builder, MIN_API_FLAG, nextArg, argsOrigin, builder::setMinApiLevel);
+              builder::error, MIN_API_FLAG, nextArg, argsOrigin, builder::setMinApiLevel);
           state.hasDefinedApiLevel = true;
         }
       } else if (arg.equals(THREAD_COUNT_FLAG)) {
         parsePositiveIntArgument(
-            builder, THREAD_COUNT_FLAG, nextArg, argsOrigin, builder::setThreadCount);
+            builder::error, THREAD_COUNT_FLAG, nextArg, argsOrigin, builder::setThreadCount);
       } else if (arg.equals("--no-tree-shaking")) {
         builder.setDisableTreeShaking(true);
       } else if (arg.equals("--no-minification")) {
diff --git a/src/main/java/com/android/tools/r8/StringConsumer.java b/src/main/java/com/android/tools/r8/StringConsumer.java
index 4c091a5..15c2dce 100644
--- a/src/main/java/com/android/tools/r8/StringConsumer.java
+++ b/src/main/java/com/android/tools/r8/StringConsumer.java
@@ -196,7 +196,6 @@
       super.accept(string, handler);
       try {
         writer.write(string);
-        writer.flush();
       } catch (IOException e) {
         handler.error(new ExceptionDiagnostic(e, origin));
       }
diff --git a/src/main/java/com/android/tools/r8/SwissArmyKnife.java b/src/main/java/com/android/tools/r8/SwissArmyKnife.java
index 89aa64e..6bf5af9 100644
--- a/src/main/java/com/android/tools/r8/SwissArmyKnife.java
+++ b/src/main/java/com/android/tools/r8/SwissArmyKnife.java
@@ -8,6 +8,7 @@
 import com.android.tools.r8.compatproguard.CompatProguard;
 import com.android.tools.r8.dexfilemerger.DexFileMerger;
 import com.android.tools.r8.dexsplitter.DexSplitter;
+import com.android.tools.r8.relocator.RelocatorCommandLine;
 import java.util.Arrays;
 
 /**
@@ -82,6 +83,9 @@
       case "backportedmethods":
         BackportedMethodList.main(shift(args));
         break;
+      case "relocator":
+        RelocatorCommandLine.main(shift((args)));
+        break;
       default:
         runDefault(args);
         break;
diff --git a/src/main/java/com/android/tools/r8/cf/CfPrinter.java b/src/main/java/com/android/tools/r8/cf/CfPrinter.java
index ccd1686..87c4b30 100644
--- a/src/main/java/com/android/tools/r8/cf/CfPrinter.java
+++ b/src/main/java/com/android/tools/r8/cf/CfPrinter.java
@@ -56,6 +56,7 @@
 import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexMethodHandle;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.ir.code.If;
 import com.android.tools.r8.ir.code.MemberType;
@@ -391,6 +392,9 @@
     builder.append(opcodeName(Opcodes.INVOKEDYNAMIC)).append(' ');
     builder.append(invoke.getCallSite().methodName);
     builder.append(invoke.getCallSite().methodProto.toDescriptorString());
+    DexMethodHandle bootstrapMethod = invoke.getCallSite().bootstrapMethod;
+    builder.append(", bsm:");
+    appendMethod(bootstrapMethod.asMethod());
   }
 
   public void print(CfFrame frame) {
diff --git a/src/main/java/com/android/tools/r8/cf/TypeVerificationHelper.java b/src/main/java/com/android/tools/r8/cf/TypeVerificationHelper.java
index 93973c3..b98c4cb 100644
--- a/src/main/java/com/android/tools/r8/cf/TypeVerificationHelper.java
+++ b/src/main/java/com/android/tools/r8/cf/TypeVerificationHelper.java
@@ -228,7 +228,7 @@
       InstructionIterator it = code.instructionIterator();
       Instruction instruction = null;
       // Set the out-value types of each argument based on the method signature.
-      int argumentIndex = code.method.accessFlags.isStatic() ? 0 : -1;
+      int argumentIndex = code.method().accessFlags.isStatic() ? 0 : -1;
       while (it.hasNext()) {
         instruction = it.next();
         if (!instruction.isArgument()) {
@@ -237,12 +237,12 @@
         TypeInfo argumentType;
         if (argumentIndex < 0) {
           argumentType =
-              code.method.isInstanceInitializer()
-                  ? new ThisInstanceInfo(instruction.asArgument(), code.method.holder())
-                  : createInitializedType(code.method.holder());
+              code.method().isInstanceInitializer()
+                  ? new ThisInstanceInfo(instruction.asArgument(), code.method().holder())
+                  : createInitializedType(code.method().holder());
         } else {
           argumentType =
-              createInitializedType(code.method.method.proto.parameters.values[argumentIndex]);
+              createInitializedType(code.method().method.proto.parameters.values[argumentIndex]);
         }
         Value outValue = instruction.outValue();
         if (outValue.outType().isObject()) {
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 8f01356..458bc23 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
@@ -62,7 +62,8 @@
       case METHOD_TYPE:
         return Type.getMethodType(value.asDexValueMethodType().getValue().toDescriptorString(lens));
       case STRING:
-        return value.asDexValueString().getValue();
+        DexString innerValue = value.asDexValueString().getValue();
+        return innerValue == null ? null : innerValue.toString();
       case TYPE:
         return Type.getType(lens.lookupDescriptor(value.asDexValueType().value).toString());
       default:
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 79b4362..8ee86f3 100644
--- a/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java
+++ b/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java
@@ -38,9 +38,9 @@
 import com.android.tools.r8.graph.InnerClassAttribute;
 import com.android.tools.r8.graph.ObjectToOffsetMapping;
 import com.android.tools.r8.graph.ParameterAnnotationsList;
-import com.android.tools.r8.naming.ClassNameMapper;
 import com.android.tools.r8.naming.NamingLens;
 import com.android.tools.r8.naming.ProguardMapSupplier;
+import com.android.tools.r8.naming.ProguardMapSupplier.ProguardMapId;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.utils.DescriptorUtils;
 import com.android.tools.r8.utils.ExceptionUtils;
@@ -230,17 +230,17 @@
 
   public void write(ExecutorService executorService) throws IOException, ExecutionException {
     application.timing.begin("DexApplication.write");
-    ProguardMapSupplier.ProguardMapAndId proguardMapAndId = null;
+    ProguardMapId proguardMapId = null;
     if (proguardMapSupplier != null && options.proguardMapConsumer != null) {
-      proguardMapAndId = proguardMapSupplier.getProguardMapAndId();
+      proguardMapId = proguardMapSupplier.writeProguardMap();
     }
 
     // If we do have a map then we're called from R8. In that case we have exactly one marker.
-    assert proguardMapAndId == null || (markers != null && markers.size() == 1);
+    assert proguardMapId == null || (markers != null && markers.size() == 1);
 
     if (markers != null && !markers.isEmpty()) {
-      if (proguardMapAndId != null) {
-        markers.get(0).setPgMapId(proguardMapAndId.id);
+      if (proguardMapId != null) {
+        markers.get(0).setPgMapId(proguardMapId.get());
       }
       markerStrings = new ArrayList<>(markers.size());
       for (Marker marker : markers) {
@@ -338,13 +338,7 @@
       // Fail if there are pending errors, e.g., the program consumers may have reported errors.
       options.reporter.failIfPendingErrors();
       // Supply info to all additional resource consumers.
-      supplyAdditionalConsumers(
-          application,
-          appView,
-          graphLense,
-          namingLens,
-          options,
-          proguardMapAndId == null ? null : proguardMapAndId.map);
+      supplyAdditionalConsumers(application, appView, graphLense, namingLens, options);
     } finally {
       application.timing.end();
     }
@@ -355,20 +349,13 @@
       AppView<?> appView,
       GraphLense graphLense,
       NamingLens namingLens,
-      InternalOptions options,
-      String proguardMapContent) {
+      InternalOptions options) {
     if (options.configurationConsumer != null) {
       ExceptionUtils.withConsumeResourceHandler(
           options.reporter, options.configurationConsumer,
           options.getProguardConfiguration().getParsedConfiguration());
       ExceptionUtils.withFinishedResourceHandler(options.reporter, options.configurationConsumer);
     }
-    if (proguardMapContent != null) {
-      assert validateProguardMapParses(proguardMapContent);
-      ExceptionUtils.withConsumeResourceHandler(
-          options.reporter, options.proguardMapConsumer, proguardMapContent);
-      ExceptionUtils.withFinishedResourceHandler(options.reporter, options.proguardMapConsumer);
-    }
     if (options.mainDexListConsumer != null) {
       ExceptionUtils.withConsumeResourceHandler(
           options.reporter, options.mainDexListConsumer, writeMainDexList(application, namingLens));
@@ -465,16 +452,6 @@
     }
   }
 
-  private static boolean validateProguardMapParses(String content) {
-    try {
-      ClassNameMapper.mapperFromString(content);
-    } catch (IOException e) {
-      e.printStackTrace();
-      return false;
-    }
-    return true;
-  }
-
   private void insertAttributeAnnotations() {
     // Convert inner-class attributes to DEX annotations
     for (DexProgramClass clazz : application.classes()) {
diff --git a/src/main/java/com/android/tools/r8/dex/Marker.java b/src/main/java/com/android/tools/r8/dex/Marker.java
index 0878119..1484f02 100644
--- a/src/main/java/com/android/tools/r8/dex/Marker.java
+++ b/src/main/java/com/android/tools/r8/dex/Marker.java
@@ -5,30 +5,36 @@
 
 import com.android.tools.r8.CompilationMode;
 import com.android.tools.r8.graph.DexString;
+import com.android.tools.r8.utils.Reporter;
+import com.android.tools.r8.utils.StringDiagnostic;
+import com.google.gson.JsonArray;
 import com.google.gson.JsonElement;
 import com.google.gson.JsonObject;
 import com.google.gson.JsonParser;
 import com.google.gson.JsonSyntaxException;
 import java.util.Comparator;
+import java.util.HashSet;
 import java.util.Map.Entry;
+import java.util.Set;
 
-/**
- * Abstraction for hidden dex marker intended for the main dex file.
- */
+/** Abstraction for hidden dex marker intended for the main dex file. */
 public class Marker {
 
   public static final String VERSION = "version";
   public static final String MIN_API = "min-api";
+  public static final String DESUGARED_LIBRARY_IDENTIFIERS = "desugared-library-identifiers";
   public static final String SHA1 = "sha-1";
   public static final String COMPILATION_MODE = "compilation-mode";
   public static final String HAS_CHECKSUMS = "has-checksums";
   public static final String PG_MAP_ID = "pg-map-id";
   public static final String R8_MODE = "r8-mode";
+  private static final String NO_LIBRARY_DESUGARING = "<no-library-desugaring>";
 
   public enum Tool {
     D8,
     R8,
-    L8;
+    L8,
+    Relocator;
 
     public static Tool[] valuesR8andD8() {
       return new Tool[] {Tool.D8, Tool.R8};
@@ -53,6 +59,48 @@
     this.jsonObject = jsonObject;
   }
 
+  public static void checkCompatibleDesugaredLibrary(Set<Marker> markers, Reporter reporter) {
+    if (markers.size() <= 1) {
+      return;
+    }
+    // In L8 compilation, the compilation has two markers, a L8 marker, which has a desugared
+    // library property, and either a D8 or a R8 marker, which has no desugared library property.
+    // In other compilations, the desugared library versions have to be consistent.
+    Set<String> desugaredLibraryIdentifiers = new HashSet<>();
+    for (Marker marker : markers) {
+      if (marker.tool == Tool.L8) {
+        assert marker.getDesugaredLibraryIdentifiers().length > 0;
+        assert markers.stream()
+            .allMatch(m -> m.tool == Tool.L8 || m.getDesugaredLibraryIdentifiers().length == 0);
+      } else {
+        String[] identifiers = marker.getDesugaredLibraryIdentifiers();
+        String identifier;
+        switch (identifiers.length) {
+          case 0:
+            identifier = NO_LIBRARY_DESUGARING;
+            break;
+          case 1:
+            identifier = identifiers[0];
+            break;
+          default:
+            // To be implemented once D8/R8 compilation supports multiple desugared libraries.
+            throw reporter.fatalError(
+                new StringDiagnostic(
+                    "Merging program compiled with multiple desugared libraries."));
+        }
+        desugaredLibraryIdentifiers.add(identifier);
+      }
+    }
+
+    if (desugaredLibraryIdentifiers.size() > 1) {
+      reporter.error(
+          new StringDiagnostic(
+              "The compilation is merging inputs with different desugared library desugaring "
+                  + desugaredLibraryIdentifiers
+                  + ", which may lead to unexpected runtime errors."));
+    }
+  }
+
   public Tool getTool() {
     return tool;
   }
@@ -69,6 +117,10 @@
     return tool == Tool.L8;
   }
 
+  public boolean isRelocator() {
+    return tool == Tool.Relocator;
+  }
+
   public String getVersion() {
     return jsonObject.get(VERSION).getAsString();
   }
@@ -89,6 +141,28 @@
     return this;
   }
 
+  public String[] getDesugaredLibraryIdentifiers() {
+    if (jsonObject.has(DESUGARED_LIBRARY_IDENTIFIERS)) {
+      JsonArray array = jsonObject.get(DESUGARED_LIBRARY_IDENTIFIERS).getAsJsonArray();
+      String[] identifiers = new String[array.size()];
+      for (int i = 0; i < array.size(); i++) {
+        identifiers[i] = array.get(i).getAsString();
+      }
+      return identifiers;
+    }
+    return new String[0];
+  }
+
+  public Marker setDesugaredLibraryIdentifiers(String... identifiers) {
+    assert !jsonObject.has(DESUGARED_LIBRARY_IDENTIFIERS);
+    JsonArray jsonIdentifiers = new JsonArray();
+    for (String identifier : identifiers) {
+      jsonIdentifiers.add(identifier);
+    }
+    jsonObject.add(DESUGARED_LIBRARY_IDENTIFIERS, jsonIdentifiers);
+    return this;
+  }
+
   public String getSha1() {
     return jsonObject.get(SHA1).getAsString();
   }
@@ -143,8 +217,7 @@
   public String toString() {
     // In order to make printing of markers deterministic we sort the entries by key.
     final JsonObject sortedJson = new JsonObject();
-    jsonObject.entrySet()
-        .stream()
+    jsonObject.entrySet().stream()
         .sorted(Comparator.comparing(Entry::getKey))
         .forEach(entry -> sortedJson.add(entry.getKey(), entry.getValue()));
     return PREFIX + tool + sortedJson;
@@ -186,7 +259,7 @@
 
   private static Marker internalParse(Tool tool, String str) {
     try {
-      JsonElement result =  new JsonParser().parse(str);
+      JsonElement result = new JsonParser().parse(str);
       if (result.isJsonObject()) {
         return new Marker(tool, result.getAsJsonObject());
       }
diff --git a/src/main/java/com/android/tools/r8/features/FeatureSplitConfiguration.java b/src/main/java/com/android/tools/r8/features/FeatureSplitConfiguration.java
index a122ad7..4a4db11 100644
--- a/src/main/java/com/android/tools/r8/features/FeatureSplitConfiguration.java
+++ b/src/main/java/com/android/tools/r8/features/FeatureSplitConfiguration.java
@@ -107,11 +107,21 @@
     return result;
   }
 
+  public boolean inBaseOrSameFeatureAs(DexProgramClass clazz, DexProgramClass context) {
+    FeatureSplit split = javaTypeToFeatureSplitMapping.get(clazz.type.toSourceString());
+    return split == null
+        || split == javaTypeToFeatureSplitMapping.get(context.type.toSourceString());
+  }
+
   public boolean isInFeature(DexProgramClass clazz) {
     return javaTypeToFeatureSplitMapping.containsKey(
         DescriptorUtils.descriptorToJavaType(clazz.type.toDescriptorString()));
   }
 
+  public boolean isInBase(DexProgramClass clazz) {
+    return !isInFeature(clazz);
+  }
+
   public boolean inSameFeatureOrBase(DexMethod a, DexMethod b){
     return inSameFeatureOrBase(a.holder, b.holder);
   }
diff --git a/src/main/java/com/android/tools/r8/graph/AccessControl.java b/src/main/java/com/android/tools/r8/graph/AccessControl.java
index d775cb3..2c05df2 100644
--- a/src/main/java/com/android/tools/r8/graph/AccessControl.java
+++ b/src/main/java/com/android/tools/r8/graph/AccessControl.java
@@ -3,6 +3,9 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.graph;
 
+import com.android.tools.r8.features.FeatureSplitConfiguration;
+import com.android.tools.r8.utils.OptionalBool;
+
 /**
  * Definitions of access control routines.
  *
@@ -11,14 +14,22 @@
  */
 public class AccessControl {
 
-  public static boolean isClassAccessible(DexClass clazz, DexProgramClass context) {
-    if (clazz.accessFlags.isPublic()) {
-      return true;
+  public static OptionalBool isClassAccessible(
+      DexClass clazz,
+      DexProgramClass context,
+      FeatureSplitConfiguration featureSplitConfiguration) {
+    if (!clazz.isPublic() && !clazz.getType().isSamePackage(context.getType())) {
+      return OptionalBool.FALSE;
     }
-    return clazz.getType().isSamePackage(context.getType());
+    if (featureSplitConfiguration != null
+        && clazz.isProgramClass()
+        && !featureSplitConfiguration.inBaseOrSameFeatureAs(clazz.asProgramClass(), context)) {
+      return OptionalBool.UNKNOWN;
+    }
+    return OptionalBool.TRUE;
   }
 
-  public static boolean isMethodAccessible(
+  public static OptionalBool isMethodAccessible(
       DexEncodedMethod method,
       DexClass holder,
       DexProgramClass context,
@@ -26,35 +37,40 @@
     return isMemberAccessible(method.accessFlags, holder, context, appInfo);
   }
 
-  public static boolean isFieldAccessible(
+  public static OptionalBool isFieldAccessible(
       DexEncodedField field,
       DexClass holder,
       DexProgramClass context,
-      AppInfoWithSubtyping appInfo) {
+      AppInfoWithClassHierarchy appInfo) {
     return isMemberAccessible(field.accessFlags, holder, context, appInfo);
   }
 
-  private static boolean isMemberAccessible(
+  private static OptionalBool isMemberAccessible(
       AccessFlags<?> memberFlags,
       DexClass holder,
       DexProgramClass context,
       AppInfoWithClassHierarchy appInfo) {
-    if (!isClassAccessible(holder, context)) {
-      return false;
+    OptionalBool classAccessibility =
+        isClassAccessible(holder, context, appInfo.options().featureSplitConfiguration);
+    if (classAccessibility.isFalse()) {
+      return OptionalBool.FALSE;
     }
     if (memberFlags.isPublic()) {
-      return true;
+      return classAccessibility;
     }
     if (memberFlags.isPrivate()) {
-      return isNestMate(holder, context);
+      if (!isNestMate(holder, context)) {
+        return OptionalBool.FALSE;
+      }
+      return classAccessibility;
     }
     if (holder.getType().isSamePackage(context.getType())) {
-      return true;
+      return classAccessibility;
     }
-    if (!memberFlags.isProtected()) {
-      return false;
+    if (memberFlags.isProtected() && appInfo.isSubtype(context.getType(), holder.getType())) {
+      return classAccessibility;
     }
-    return appInfo.isSubtype(context.getType(), holder.getType());
+    return OptionalBool.FALSE;
   }
 
   private static boolean isNestMate(DexClass clazz, DexProgramClass context) {
diff --git a/src/main/java/com/android/tools/r8/graph/AppInfo.java b/src/main/java/com/android/tools/r8/graph/AppInfo.java
index 8b63cbf..ef53aaa 100644
--- a/src/main/java/com/android/tools/r8/graph/AppInfo.java
+++ b/src/main/java/com/android/tools/r8/graph/AppInfo.java
@@ -578,16 +578,6 @@
     return null;
   }
 
-  public boolean hasSubtyping() {
-    assert checkIfObsolete();
-    return false;
-  }
-
-  public AppInfoWithSubtyping withSubtyping() {
-    assert checkIfObsolete();
-    return null;
-  }
-
   public boolean hasLiveness() {
     assert checkIfObsolete();
     return false;
diff --git a/src/main/java/com/android/tools/r8/graph/AppInfoWithSubtyping.java b/src/main/java/com/android/tools/r8/graph/AppInfoWithSubtyping.java
deleted file mode 100644
index 794745f..0000000
--- a/src/main/java/com/android/tools/r8/graph/AppInfoWithSubtyping.java
+++ /dev/null
@@ -1,45 +0,0 @@
-// Copyright (c) 2017, the R8 project authors. Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-package com.android.tools.r8.graph;
-
-import java.util.Collection;
-
-public class AppInfoWithSubtyping extends AppInfoWithClassHierarchy {
-
-  public AppInfoWithSubtyping(DirectMappedDexApplication application) {
-    this(application, application.allClasses());
-  }
-
-  public AppInfoWithSubtyping(
-      DirectMappedDexApplication application, Collection<DexClass> classes) {
-    super(application);
-  }
-
-  protected AppInfoWithSubtyping(AppInfoWithSubtyping previous) {
-    super(previous);
-    assert app() instanceof DirectMappedDexApplication;
-  }
-
-  private DirectMappedDexApplication getDirectApplication() {
-    // TODO(herhut): Remove need for cast.
-    return (DirectMappedDexApplication) app();
-  }
-
-  public Iterable<DexLibraryClass> libraryClasses() {
-    assert checkIfObsolete();
-    return getDirectApplication().libraryClasses();
-  }
-
-  @Override
-  public boolean hasSubtyping() {
-    assert checkIfObsolete();
-    return true;
-  }
-
-  @Override
-  public AppInfoWithSubtyping withSubtyping() {
-    assert checkIfObsolete();
-    return this;
-  }
-}
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 ef76217..484c3e1 100644
--- a/src/main/java/com/android/tools/r8/graph/AppView.java
+++ b/src/main/java/com/android/tools/r8/graph/AppView.java
@@ -130,6 +130,11 @@
     return new AppView<>(appInfo, WholeProgramOptimizations.OFF, options, mapper);
   }
 
+  public static <T extends AppInfo> AppView<T> createForRelocator(
+      T appInfo, InternalOptions options) {
+    return new AppView<>(appInfo, WholeProgramOptimizations.OFF, options);
+  }
+
   public AbstractValueFactory abstractValueFactory() {
     return abstractValueFactory;
   }
@@ -418,13 +423,6 @@
         : null;
   }
 
-  @SuppressWarnings("unchecked")
-  public AppView<AppInfoWithSubtyping> withSubtyping() {
-    return appInfo.hasSubtyping()
-        ? (AppView<AppInfoWithSubtyping>) this
-        : null;
-  }
-
   public AppView<AppInfoWithLiveness> withLiveness() {
     @SuppressWarnings("unchecked")
     AppView<AppInfoWithLiveness> appViewWithLiveness = (AppView<AppInfoWithLiveness>) this;
@@ -432,8 +430,8 @@
   }
 
   public OptionalBool isSubtype(DexType subtype, DexType supertype) {
-    return appInfo().hasSubtyping()
-        ? OptionalBool.of(appInfo().withSubtyping().isSubtype(subtype, supertype))
+    return appInfo().hasLiveness()
+        ? OptionalBool.of(appInfo().withLiveness().isSubtype(subtype, supertype))
         : OptionalBool.unknown();
   }
 }
diff --git a/src/main/java/com/android/tools/r8/graph/AppliedGraphLens.java b/src/main/java/com/android/tools/r8/graph/AppliedGraphLens.java
index a05883a..158bf1f 100644
--- a/src/main/java/com/android/tools/r8/graph/AppliedGraphLens.java
+++ b/src/main/java/com/android/tools/r8/graph/AppliedGraphLens.java
@@ -28,7 +28,7 @@
       new IdentityHashMap<>();
 
   public AppliedGraphLens(
-      AppView<? extends AppInfoWithSubtyping> appView, Iterable<DexProgramClass> classes) {
+      AppView<? extends AppInfoWithClassHierarchy> appView, Iterable<DexProgramClass> classes) {
     this.appView = appView;
 
     for (DexProgramClass clazz : classes) {
diff --git a/src/main/java/com/android/tools/r8/graph/AssemblyWriter.java b/src/main/java/com/android/tools/r8/graph/AssemblyWriter.java
index 968b1c9..59b3c8b 100644
--- a/src/main/java/com/android/tools/r8/graph/AssemblyWriter.java
+++ b/src/main/java/com/android/tools/r8/graph/AssemblyWriter.java
@@ -10,8 +10,7 @@
 import com.android.tools.r8.ir.conversion.OneTimeMethodProcessor;
 import com.android.tools.r8.ir.optimize.info.OptimizationFeedbackIgnore;
 import com.android.tools.r8.kotlin.Kotlin;
-import com.android.tools.r8.kotlin.KotlinClassMetadataReader;
-import com.android.tools.r8.kotlin.KotlinInfo;
+import com.android.tools.r8.kotlin.KotlinMetadataWriter;
 import com.android.tools.r8.naming.ClassNameMapper;
 import com.android.tools.r8.naming.MemberNaming.FieldSignature;
 import com.android.tools.r8.utils.CfgPrinter;
@@ -28,7 +27,7 @@
   private final boolean writeFields;
   private final boolean writeAnnotations;
   private final boolean writeIR;
-  private final AppInfoWithSubtyping appInfo;
+  private final AppInfoWithClassHierarchy appInfo;
   private final Kotlin kotlin;
   private final Timing timing = new Timing("AssemblyWriter");
 
@@ -40,7 +39,7 @@
     this.writeAnnotations = allInfo;
     this.writeIR = writeIR;
     if (writeIR) {
-      this.appInfo = new AppInfoWithSubtyping(application.toDirect());
+      this.appInfo = new AppInfoWithClassHierarchy(application.toDirect());
       if (options.programConsumer == null) {
         // Use class-file backend, since the CF frontend for testing does not support desugaring of
         // synchronized methods for the DEX backend (b/109789541).
@@ -149,13 +148,13 @@
     if (writeAnnotations) {
       if (!annotations.isEmpty()) {
         ps.println("# Annotations:");
+        String prefix = "#  ";
         for (DexAnnotation annotation : annotations.annotations) {
           if (annotation.annotation.type == kotlin.metadata.kotlinMetadataType) {
             assert clazz != null : "Kotlin metadata is a class annotation";
-            writeKotlinMetadata(clazz, annotation, ps);
+            KotlinMetadataWriter.writeKotlinMetadataAnnotation(prefix, annotation, ps, kotlin);
           } else {
             String annotationString = annotation.toString();
-            String prefix = "#  ";
             ps.print(
                 new BufferedReader(new StringReader(annotationString))
                     .lines()
@@ -168,14 +167,6 @@
     }
   }
 
-  private void writeKotlinMetadata(
-      DexProgramClass clazz, DexAnnotation annotation, PrintStream ps) {
-    assert annotation.annotation.type == kotlin.metadata.kotlinMetadataType;
-    KotlinInfo kotlinInfo =
-        KotlinClassMetadataReader.createKotlinInfo(kotlin, clazz, annotation.annotation);
-    ps.println(kotlinInfo.toString("#  "));
-  }
-
   @Override
   void writeClassFooter(DexProgramClass clazz, PrintStream ps) {
 
diff --git a/src/main/java/com/android/tools/r8/graph/BottomUpClassHierarchyTraversal.java b/src/main/java/com/android/tools/r8/graph/BottomUpClassHierarchyTraversal.java
index 9cdac51..1c7bf88 100644
--- a/src/main/java/com/android/tools/r8/graph/BottomUpClassHierarchyTraversal.java
+++ b/src/main/java/com/android/tools/r8/graph/BottomUpClassHierarchyTraversal.java
@@ -10,7 +10,9 @@
   private final SubtypingInfo subtypingInfo;
 
   private BottomUpClassHierarchyTraversal(
-      AppView<? extends AppInfoWithSubtyping> appView, SubtypingInfo subtypingInfo, Scope scope) {
+      AppView<? extends AppInfoWithClassHierarchy> appView,
+      SubtypingInfo subtypingInfo,
+      Scope scope) {
     super(appView, scope);
     this.subtypingInfo = subtypingInfo;
   }
@@ -20,7 +22,7 @@
    * classes) that are reachable from a given set of sources.
    */
   public static BottomUpClassHierarchyTraversal<DexClass> forAllClasses(
-      AppView<? extends AppInfoWithSubtyping> appView, SubtypingInfo subtypingInfo) {
+      AppView<? extends AppInfoWithClassHierarchy> appView, SubtypingInfo subtypingInfo) {
     return new BottomUpClassHierarchyTraversal<>(appView, subtypingInfo, Scope.ALL_CLASSES);
   }
 
@@ -29,7 +31,7 @@
    * given set of sources.
    */
   public static BottomUpClassHierarchyTraversal<DexProgramClass> forProgramClasses(
-      AppView<? extends AppInfoWithSubtyping> appView, SubtypingInfo subtypingInfo) {
+      AppView<? extends AppInfoWithClassHierarchy> appView, SubtypingInfo subtypingInfo) {
     return new BottomUpClassHierarchyTraversal<>(
         appView, subtypingInfo, Scope.ONLY_PROGRAM_CLASSES);
   }
diff --git a/src/main/java/com/android/tools/r8/graph/ClassHierarchyTraversal.java b/src/main/java/com/android/tools/r8/graph/ClassHierarchyTraversal.java
index 550a96c..83b09c3 100644
--- a/src/main/java/com/android/tools/r8/graph/ClassHierarchyTraversal.java
+++ b/src/main/java/com/android/tools/r8/graph/ClassHierarchyTraversal.java
@@ -41,7 +41,7 @@
     }
   }
 
-  final AppView<? extends AppInfoWithSubtyping> appView;
+  final AppView<? extends AppInfoWithClassHierarchy> appView;
   final Scope scope;
 
   final Set<DexClass> visited = new HashSet<>();
@@ -49,7 +49,7 @@
 
   boolean excludeInterfaces = false;
 
-  ClassHierarchyTraversal(AppView<? extends AppInfoWithSubtyping> appView, Scope scope) {
+  ClassHierarchyTraversal(AppView<? extends AppInfoWithClassHierarchy> appView, Scope scope) {
     this.appView = appView;
     this.scope = scope;
   }
diff --git a/src/main/java/com/android/tools/r8/graph/DexCallSite.java b/src/main/java/com/android/tools/r8/graph/DexCallSite.java
index 3249abf..8c76ca1 100644
--- a/src/main/java/com/android/tools/r8/graph/DexCallSite.java
+++ b/src/main/java/com/android/tools/r8/graph/DexCallSite.java
@@ -5,6 +5,7 @@
 
 import com.android.tools.r8.dex.IndexedItemCollection;
 import com.android.tools.r8.dex.MixedSectionCollection;
+import com.android.tools.r8.errors.CompilationError;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.DexValue.DexValueMethodHandle;
 import com.android.tools.r8.graph.DexValue.DexValueMethodType;
@@ -68,7 +69,7 @@
     if (bsmHandle.getTag() != Opcodes.H_INVOKESTATIC
         && bsmHandle.getTag() != Opcodes.H_NEWINVOKESPECIAL) {
       // JVM9 §4.7.23 note: Tag must be InvokeStatic or NewInvokeSpecial.
-      throw new Unreachable("Bootstrap handle invalid: tag == " + bsmHandle.getTag());
+      throw new CompilationError("Bootstrap handle invalid: tag == " + bsmHandle.getTag());
     }
     // Resolve the bootstrap method.
     DexMethodHandle bootstrapMethod = DexMethodHandle.fromAsmHandle(bsmHandle, application, clazz);
diff --git a/src/main/java/com/android/tools/r8/graph/DexClass.java b/src/main/java/com/android/tools/r8/graph/DexClass.java
index 32e1d61..444459f 100644
--- a/src/main/java/com/android/tools/r8/graph/DexClass.java
+++ b/src/main/java/com/android/tools/r8/graph/DexClass.java
@@ -6,7 +6,7 @@
 import com.android.tools.r8.dex.MixedSectionCollection;
 import com.android.tools.r8.errors.CompilationError;
 import com.android.tools.r8.errors.Unreachable;
-import com.android.tools.r8.kotlin.KotlinInfo;
+import com.android.tools.r8.kotlin.KotlinClassLevelInfo;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.OptionalBool;
@@ -794,11 +794,7 @@
   }
 
   /** Returns kotlin class info if the class is synthesized by kotlin compiler. */
-  public abstract KotlinInfo getKotlinInfo();
-
-  public final boolean hasKotlinInfo() {
-    return getKotlinInfo() != null;
-  }
+  public abstract KotlinClassLevelInfo getKotlinInfo();
 
   public boolean hasInstanceFields() {
     return instanceFields.length > 0;
diff --git a/src/main/java/com/android/tools/r8/graph/DexClassAndMethod.java b/src/main/java/com/android/tools/r8/graph/DexClassAndMethod.java
index 6c05f12..f77d55b 100644
--- a/src/main/java/com/android/tools/r8/graph/DexClassAndMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/DexClassAndMethod.java
@@ -49,7 +49,7 @@
     return holder;
   }
 
-  public DexEncodedMethod getMethod() {
+  public DexEncodedMethod getDefinition() {
     return method;
   }
 
diff --git a/src/main/java/com/android/tools/r8/graph/DexClasspathClass.java b/src/main/java/com/android/tools/r8/graph/DexClasspathClass.java
index ba47985..1e85690 100644
--- a/src/main/java/com/android/tools/r8/graph/DexClasspathClass.java
+++ b/src/main/java/com/android/tools/r8/graph/DexClasspathClass.java
@@ -8,7 +8,7 @@
 import com.android.tools.r8.dex.IndexedItemCollection;
 import com.android.tools.r8.dex.MixedSectionCollection;
 import com.android.tools.r8.errors.Unreachable;
-import com.android.tools.r8.kotlin.KotlinInfo;
+import com.android.tools.r8.kotlin.KotlinClassLevelInfo;
 import com.android.tools.r8.origin.Origin;
 import java.util.List;
 import java.util.function.Supplier;
@@ -86,8 +86,8 @@
   }
 
   @Override
-  public KotlinInfo getKotlinInfo() {
-    throw new Unreachable("Kotlin into n classpath class is not supported yet.");
+  public KotlinClassLevelInfo getKotlinInfo() {
+    throw new Unreachable("Kotlin info on classpath class is not supported yet.");
   }
 
   @Override
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 53d2835..bb3405a 100644
--- a/src/main/java/com/android/tools/r8/graph/DexDebugEventBuilder.java
+++ b/src/main/java/com/android/tools/r8/graph/DexDebugEventBuilder.java
@@ -59,7 +59,7 @@
   private int startLine = NO_LINE_INFO;
 
   public DexDebugEventBuilder(IRCode code, InternalOptions options) {
-    this.method = code.method;
+    this.method = code.method();
     this.factory = options.itemFactory;
     this.options = options;
   }
diff --git a/src/main/java/com/android/tools/r8/graph/DexEncodedField.java b/src/main/java/com/android/tools/r8/graph/DexEncodedField.java
index 0d5c50d..43536b9 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedField.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedField.java
@@ -4,6 +4,7 @@
 package com.android.tools.r8.graph;
 
 import static com.android.tools.r8.ir.analysis.type.Nullability.maybeNull;
+import static com.android.tools.r8.kotlin.KotlinMetadataUtils.NO_KOTLIN_INFO;
 
 import com.android.tools.r8.dex.IndexedItemCollection;
 import com.android.tools.r8.dex.MixedSectionCollection;
@@ -16,7 +17,7 @@
 import com.android.tools.r8.ir.optimize.info.DefaultFieldOptimizationInfo;
 import com.android.tools.r8.ir.optimize.info.FieldOptimizationInfo;
 import com.android.tools.r8.ir.optimize.info.MutableFieldOptimizationInfo;
-import com.android.tools.r8.kotlin.KotlinMemberInfo;
+import com.android.tools.r8.kotlin.KotlinFieldLevelInfo;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.google.common.collect.Sets;
 
@@ -28,7 +29,7 @@
   private DexValue staticValue;
 
   private FieldOptimizationInfo optimizationInfo = DefaultFieldOptimizationInfo.getInstance();
-  private KotlinMemberInfo kotlinMemberInfo = KotlinMemberInfo.getNoKotlinMemberInfo();
+  private KotlinFieldLevelInfo kotlinMemberInfo = NO_KOTLIN_INFO;
 
   public DexEncodedField(
       DexField field,
@@ -67,23 +68,15 @@
     optimizationInfo = info;
   }
 
-  public KotlinMemberInfo getKotlinMemberInfo() {
+  public KotlinFieldLevelInfo getKotlinMemberInfo() {
     return kotlinMemberInfo;
   }
 
-  public void setKotlinMemberInfo(KotlinMemberInfo kotlinMemberInfo) {
-    assert this.kotlinMemberInfo == KotlinMemberInfo.getNoKotlinMemberInfo();
+  public void setKotlinMemberInfo(KotlinFieldLevelInfo kotlinMemberInfo) {
+    assert this.kotlinMemberInfo == NO_KOTLIN_INFO;
     this.kotlinMemberInfo = kotlinMemberInfo;
   }
 
-  public boolean isKotlinBackingField() {
-    return kotlinMemberInfo.memberKind.isBackingField();
-  }
-
-  public boolean isKotlinBackingFieldForCompanionObject() {
-    return kotlinMemberInfo.memberKind.isBackingFieldForCompanionObject();
-  }
-
   @Override
   public void collectIndexedItems(
       IndexedItemCollection indexedItems, DexMethod method, int instructionOffset) {
@@ -129,6 +122,10 @@
     return this;
   }
 
+  public boolean isEnum() {
+    return accessFlags.isEnum();
+  }
+
   public boolean isFinal() {
     return accessFlags.isFinal();
   }
@@ -210,7 +207,7 @@
           && singleValue.asSingleFieldValue().getField() == field) {
         return null;
       }
-      if (singleValue.isMaterializableInContext(appView, code.method.holder())) {
+      if (singleValue.isMaterializableInContext(appView, code.method().holder())) {
         TypeElement type = TypeElement.fromDexType(field.type, maybeNull(), appView);
         return singleValue.createMaterializingInstruction(
             appView, code, TypeAndLocalInfoSupplier.create(type, local));
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 28a0da2..816a515 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
@@ -9,6 +9,7 @@
 import static com.android.tools.r8.graph.DexEncodedMethod.CompilationState.PROCESSED_INLINING_CANDIDATE_SAME_PACKAGE;
 import static com.android.tools.r8.graph.DexEncodedMethod.CompilationState.PROCESSED_INLINING_CANDIDATE_SUBCLASS;
 import static com.android.tools.r8.graph.DexEncodedMethod.CompilationState.PROCESSED_NOT_INLINING_CANDIDATE;
+import static com.android.tools.r8.kotlin.KotlinMetadataUtils.NO_KOTLIN_INFO;
 
 import com.android.tools.r8.cf.code.CfConstNull;
 import com.android.tools.r8.cf.code.CfConstString;
@@ -55,7 +56,7 @@
 import com.android.tools.r8.ir.synthetic.FieldAccessorSourceCode;
 import com.android.tools.r8.ir.synthetic.ForwardMethodSourceCode;
 import com.android.tools.r8.ir.synthetic.SynthesizedCode;
-import com.android.tools.r8.kotlin.KotlinMemberInfo;
+import com.android.tools.r8.kotlin.KotlinMethodLevelInfo;
 import com.android.tools.r8.logging.Log;
 import com.android.tools.r8.naming.ClassNameMapper;
 import com.android.tools.r8.naming.MemberNaming.MethodSignature;
@@ -139,7 +140,7 @@
   private MethodOptimizationInfo optimizationInfo = DefaultMethodOptimizationInfo.DEFAULT_INSTANCE;
   private CallSiteOptimizationInfo callSiteOptimizationInfo = CallSiteOptimizationInfo.BOTTOM;
   private int classFileVersion;
-  private KotlinMemberInfo kotlinMemberInfo = KotlinMemberInfo.getNoKotlinMemberInfo();
+  private KotlinMethodLevelInfo kotlinMemberInfo = NO_KOTLIN_INFO;
 
   private DexEncodedMethod defaultInterfaceMethodImplementation = null;
 
@@ -256,6 +257,10 @@
     assert parameterAnnotationsList != null;
   }
 
+  public DexTypeList parameters() {
+    return method.proto.parameters;
+  }
+
   public DexType returnType() {
     return method.proto.returnType;
   }
@@ -412,42 +417,30 @@
     return accessFlags.isSynthetic();
   }
 
-  public KotlinMemberInfo getKotlinMemberInfo() {
+  public KotlinMethodLevelInfo getKotlinMemberInfo() {
     return kotlinMemberInfo;
   }
 
-  public void setKotlinMemberInfo(KotlinMemberInfo kotlinMemberInfo) {
-    if (this.kotlinMemberInfo == KotlinMemberInfo.getNoKotlinMemberInfo()) {
-      // Initial setup or structure-changing optimizations that just need to copy metadata from the
-      // old instance of DexEncodedMethod to the new one.
-      this.kotlinMemberInfo = kotlinMemberInfo;
-    } else {
-      // Structure-changing optimizations, such as (vertical|horizontal) merger or inliner, that
-      // may need to redefine what this method is. Simply, the method merged/inlined by optimization
-      // is no longer what it used to be; it's safe to ignore metadata of that method, since it is
-      // not asked to be kept. But, the nature of the current one is not changed, hence keeping the
-      // original one as-is.
-      // E.g., originally the current method is extension function, and new information, say, from
-      // an inlinee, is extension property. Being merged here means:
-      //   * That inlinee is not an extension property anymore. We can ignore metadata from it.
-      //   * This method is still an extension function, just with a bigger body.
-    }
+  public void setKotlinMemberInfo(KotlinMethodLevelInfo kotlinMemberInfo) {
+    // Structure-changing optimizations, such as (vertical|horizontal) merger or inliner, that
+    // may need to redefine what this method is. Simply, the method merged/inlined by optimization
+    // is no longer what it used to be; it's safe to ignore metadata of that method, since it is
+    // not asked to be kept. But, the nature of the current one is not changed, hence keeping the
+    // original one as-is.
+    // E.g., originally the current method is extension function, and new information, say, from
+    // an inlinee, is extension property. Being merged here means:
+    //   * That inlinee is not an extension property anymore. We can ignore metadata from it.
+    //   * This method is still an extension function, just with a bigger body.
+    assert this.kotlinMemberInfo == NO_KOTLIN_INFO;
+    this.kotlinMemberInfo = kotlinMemberInfo;
   }
 
   public boolean isKotlinFunction() {
-    return kotlinMemberInfo.memberKind.isFunction();
+    return kotlinMemberInfo.isFunction();
   }
 
   public boolean isKotlinExtensionFunction() {
-    return kotlinMemberInfo.memberKind.isExtensionFunction();
-  }
-
-  public boolean isKotlinProperty() {
-    return kotlinMemberInfo.memberKind.isProperty();
-  }
-
-  public boolean isKotlinExtensionProperty() {
-    return kotlinMemberInfo.memberKind.isExtensionProperty();
+    return kotlinMemberInfo.isFunction() && kotlinMemberInfo.asFunction().isExtensionFunction();
   }
 
   public boolean isOnlyInlinedIntoNestMembers() {
@@ -457,7 +450,7 @@
   public boolean isInliningCandidate(
       DexEncodedMethod container,
       Reason inliningReason,
-      AppInfoWithSubtyping appInfo,
+      AppInfoWithClassHierarchy appInfo,
       WhyAreYouNotInliningReporter whyAreYouNotInliningReporter) {
     checkIfObsolete();
     return isInliningCandidate(
@@ -467,7 +460,7 @@
   public boolean isInliningCandidate(
       DexType containerType,
       Reason inliningReason,
-      AppInfoWithSubtyping appInfo,
+      AppInfoWithClassHierarchy appInfo,
       WhyAreYouNotInliningReporter whyAreYouNotInliningReporter) {
     checkIfObsolete();
 
@@ -1250,7 +1243,6 @@
 
   public void copyMetadata(DexEncodedMethod from) {
     checkIfObsolete();
-    setKotlinMemberInfo(from.kotlinMemberInfo);
     if (from.classFileVersion > classFileVersion) {
       upgradeClassFileVersion(from.getClassFileVersion());
     }
@@ -1273,7 +1265,7 @@
     private Code code;
     private CompilationState compilationState;
     private MethodOptimizationInfo optimizationInfo;
-    private KotlinMemberInfo kotlinMemberInfo;
+    private KotlinMethodLevelInfo kotlinMemberInfo;
     private final int classFileVersion;
     private boolean d8R8Synthesized;
 
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 ffa702a..7275598 100644
--- a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
+++ b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
@@ -434,7 +434,8 @@
   public final DexType nestConstructorType = createStaticallyKnownType(nestConstructorDescriptor);
 
   public final DexString enumUnboxingUtilityDescriptor =
-      createString("L" + EnumUnboxingRewriter.ENUM_UNBOXING_UTILITY_CLASS_NAME + ";");
+      createString(
+          "Lcom/android/tools/r8/" + EnumUnboxingRewriter.ENUM_UNBOXING_UTILITY_CLASS_NAME + ";");
   public final DexType enumUnboxingUtilityType =
       createStaticallyKnownType(enumUnboxingUtilityDescriptor);
 
@@ -2086,7 +2087,7 @@
                   // Don't reason at the level of interfaces in D8.
                   return ClassTypeElement.create(type, nullability, Collections.emptySet());
                 }
-                assert appView.appInfo().hasSubtyping();
+                assert appView.appInfo().hasClassHierarchy();
                 if (appView.isInterface(type).isTrue()) {
                   return ClassTypeElement.create(
                       objectType, nullability, Collections.singleton(type));
@@ -2096,7 +2097,7 @@
                 // and compute the least upper bound of two interface sets. Hence, lazy
                 // computations. Most likely during lattice join. See {@link
                 // ClassTypeElement#getInterfaces}.
-                return ClassTypeElement.create(type, nullability, appView.withSubtyping());
+                return ClassTypeElement.create(type, nullability, appView.withClassHierarchy());
               }
               assert type.isArrayType();
               return ArrayTypeElement.create(finalMemberType, nullability);
@@ -2105,7 +2106,7 @@
   }
 
   public Set<DexType> getOrComputeLeastUpperBoundOfImplementedInterfaces(
-      DexType type, AppView<? extends AppInfoWithSubtyping> appView) {
+      DexType type, AppView<? extends AppInfoWithClassHierarchy> appView) {
     return classTypeInterfaces.computeIfAbsent(
         type,
         t -> {
diff --git a/src/main/java/com/android/tools/r8/graph/DexLibraryClass.java b/src/main/java/com/android/tools/r8/graph/DexLibraryClass.java
index 4b0e1fb..35d8146 100644
--- a/src/main/java/com/android/tools/r8/graph/DexLibraryClass.java
+++ b/src/main/java/com/android/tools/r8/graph/DexLibraryClass.java
@@ -8,7 +8,7 @@
 import com.android.tools.r8.dex.IndexedItemCollection;
 import com.android.tools.r8.dex.MixedSectionCollection;
 import com.android.tools.r8.errors.Unreachable;
-import com.android.tools.r8.kotlin.KotlinInfo;
+import com.android.tools.r8.kotlin.KotlinClassLevelInfo;
 import com.android.tools.r8.origin.Origin;
 import java.util.Arrays;
 import java.util.List;
@@ -114,8 +114,8 @@
   }
 
   @Override
-  public KotlinInfo getKotlinInfo() {
-    return null;
+  public KotlinClassLevelInfo getKotlinInfo() {
+    throw new Unreachable("We should never consider metadata for library classes");
   }
 
   @Override
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 9085643..bda291b 100644
--- a/src/main/java/com/android/tools/r8/graph/DexProgramClass.java
+++ b/src/main/java/com/android/tools/r8/graph/DexProgramClass.java
@@ -3,12 +3,14 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.graph;
 
+import static com.android.tools.r8.kotlin.KotlinMetadataUtils.NO_KOTLIN_INFO;
+
 import com.android.tools.r8.ProgramResource;
 import com.android.tools.r8.ProgramResource.Kind;
 import com.android.tools.r8.dex.IndexedItemCollection;
 import com.android.tools.r8.dex.MixedSectionCollection;
 import com.android.tools.r8.errors.CompilationError;
-import com.android.tools.r8.kotlin.KotlinInfo;
+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;
@@ -36,7 +38,7 @@
   private final ProgramResource.Kind originKind;
   private final Collection<DexProgramClass> synthesizedFrom;
   private int initialClassFileVersion = -1;
-  private KotlinInfo kotlinInfo = null;
+  private KotlinClassLevelInfo kotlinInfo = NO_KOTLIN_INFO;
 
   private final ChecksumSupplier checksumSupplier;
 
@@ -252,12 +254,13 @@
   }
 
   @Override
-  public KotlinInfo getKotlinInfo() {
+  public KotlinClassLevelInfo getKotlinInfo() {
     return kotlinInfo;
   }
 
-  public void setKotlinInfo(KotlinInfo kotlinInfo) {
-    assert this.kotlinInfo == null || kotlinInfo == null;
+  public void setKotlinInfo(KotlinClassLevelInfo kotlinInfo) {
+    assert kotlinInfo != null;
+    assert this.kotlinInfo == NO_KOTLIN_INFO || kotlinInfo == NO_KOTLIN_INFO;
     this.kotlinInfo = kotlinInfo;
   }
 
diff --git a/src/main/java/com/android/tools/r8/graph/DexTypeList.java b/src/main/java/com/android/tools/r8/graph/DexTypeList.java
index 58b6afd..2272c98 100644
--- a/src/main/java/com/android/tools/r8/graph/DexTypeList.java
+++ b/src/main/java/com/android/tools/r8/graph/DexTypeList.java
@@ -7,6 +7,7 @@
 import com.android.tools.r8.dex.MixedSectionCollection;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.naming.NamingLens;
+import com.android.tools.r8.utils.ArrayUtils;
 import java.util.Arrays;
 
 public class DexTypeList extends DexItem {
@@ -28,6 +29,10 @@
     this.values = values;
   }
 
+  public boolean contains(DexType type) {
+    return ArrayUtils.contains(values, type);
+  }
+
   @Override
   public int hashCode() {
     return Arrays.hashCode(values);
diff --git a/src/main/java/com/android/tools/r8/graph/DexValue.java b/src/main/java/com/android/tools/r8/graph/DexValue.java
index bab3c75..96b6b5a 100644
--- a/src/main/java/com/android/tools/r8/graph/DexValue.java
+++ b/src/main/java/com/android/tools/r8/graph/DexValue.java
@@ -11,6 +11,9 @@
 import com.android.tools.r8.dex.MixedSectionCollection;
 import com.android.tools.r8.errors.Unreachable;
 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.AbstractValueFactory;
+import com.android.tools.r8.ir.analysis.value.UnknownValue;
 import com.android.tools.r8.ir.code.BasicBlock.ThrowingInfo;
 import com.android.tools.r8.ir.code.ConstInstruction;
 import com.android.tools.r8.ir.code.ConstString;
@@ -264,6 +267,8 @@
     return null;
   }
 
+  public abstract AbstractValue toAbstractValue(AbstractValueFactory factory);
+
   static DexValue fromAsmBootstrapArgument(
       Object value, JarApplicationReader application, DexType clazz) {
     if (value instanceof Integer) {
@@ -352,7 +357,7 @@
 
   /** Returns an instruction that can be used to materialize this {@link DexValue} (or null). */
   public ConstInstruction asConstInstruction(
-      AppView<? extends AppInfoWithSubtyping> appView, IRCode code, DebugLocalInfo local) {
+      AppView<? extends AppInfoWithClassHierarchy> appView, IRCode code, DebugLocalInfo local) {
     return null;
   }
 
@@ -413,6 +418,11 @@
     public DexValueNumber asDexValueNumber() {
       return this;
     }
+
+    @Override
+    public AbstractValue toAbstractValue(AbstractValueFactory factory) {
+      return factory.createSingleNumberValue(getRawValue());
+    }
   }
 
   public static class DexValueByte extends DexValueNumber {
@@ -494,7 +504,7 @@
 
     @Override
     public ConstInstruction asConstInstruction(
-        AppView<? extends AppInfoWithSubtyping> appView, IRCode code, DebugLocalInfo local) {
+        AppView<? extends AppInfoWithClassHierarchy> appView, IRCode code, DebugLocalInfo local) {
       return code.createIntConstant(value, local);
     }
   }
@@ -576,7 +586,7 @@
 
     @Override
     public ConstInstruction asConstInstruction(
-        AppView<? extends AppInfoWithSubtyping> appView, IRCode code, DebugLocalInfo local) {
+        AppView<? extends AppInfoWithClassHierarchy> appView, IRCode code, DebugLocalInfo local) {
       return code.createIntConstant(value, local);
     }
   }
@@ -662,7 +672,7 @@
 
     @Override
     public ConstInstruction asConstInstruction(
-        AppView<? extends AppInfoWithSubtyping> appView, IRCode code, DebugLocalInfo local) {
+        AppView<? extends AppInfoWithClassHierarchy> appView, IRCode code, DebugLocalInfo local) {
       return code.createIntConstant(value, local);
     }
   }
@@ -744,7 +754,7 @@
 
     @Override
     public ConstInstruction asConstInstruction(
-        AppView<? extends AppInfoWithSubtyping> appView, IRCode code, DebugLocalInfo local) {
+        AppView<? extends AppInfoWithClassHierarchy> appView, IRCode code, DebugLocalInfo local) {
       return code.createIntConstant(value, local);
     }
   }
@@ -826,7 +836,7 @@
 
     @Override
     public ConstInstruction asConstInstruction(
-        AppView<? extends AppInfoWithSubtyping> appView, IRCode code, DebugLocalInfo local) {
+        AppView<? extends AppInfoWithClassHierarchy> appView, IRCode code, DebugLocalInfo local) {
       return code.createLongConstant(value, local);
     }
   }
@@ -894,7 +904,7 @@
 
     @Override
     public ConstInstruction asConstInstruction(
-        AppView<? extends AppInfoWithSubtyping> appView, IRCode code, DebugLocalInfo local) {
+        AppView<? extends AppInfoWithClassHierarchy> appView, IRCode code, DebugLocalInfo local) {
       return code.createFloatConstant(value, local);
     }
 
@@ -982,7 +992,7 @@
 
     @Override
     public ConstInstruction asConstInstruction(
-        AppView<? extends AppInfoWithSubtyping> appView, IRCode code, DebugLocalInfo local) {
+        AppView<? extends AppInfoWithClassHierarchy> appView, IRCode code, DebugLocalInfo local) {
       return code.createDoubleConstant(value, local);
     }
 
@@ -1110,7 +1120,7 @@
 
     @Override
     public ConstInstruction asConstInstruction(
-        AppView<? extends AppInfoWithSubtyping> appView, IRCode code, DebugLocalInfo local) {
+        AppView<? extends AppInfoWithClassHierarchy> appView, IRCode code, DebugLocalInfo local) {
       TypeElement type = TypeElement.stringClassType(appView, definitelyNotNull());
       Value outValue = code.createValue(type, local);
       ConstString instruction =
@@ -1126,6 +1136,11 @@
       // Assuming that strings do not have side-effects.
       return false;
     }
+
+    @Override
+    public AbstractValue toAbstractValue(AbstractValueFactory factory) {
+      return factory.createSingleStringValue(value);
+    }
   }
 
   public static class DexItemBasedValueString extends NestedDexValue<DexReference> {
@@ -1168,7 +1183,7 @@
 
     @Override
     public ConstInstruction asConstInstruction(
-        AppView<? extends AppInfoWithSubtyping> appView, IRCode code, DebugLocalInfo local) {
+        AppView<? extends AppInfoWithClassHierarchy> appView, IRCode code, DebugLocalInfo local) {
       TypeElement type = TypeElement.stringClassType(appView, definitelyNotNull());
       Value outValue = code.createValue(type, local);
       DexItemBasedConstString instruction =
@@ -1183,6 +1198,13 @@
     }
 
     @Override
+    public AbstractValue toAbstractValue(AbstractValueFactory factory) {
+      // TODO(b/150835624): Update once there is an abstract value to represent dex item based
+      //  strings.
+      return UnknownValue.getInstance();
+    }
+
+    @Override
     public void writeTo(DexOutputBuffer dest, ObjectToOffsetMapping mapping) {
       throw new Unreachable(
           "DexItemBasedValueString values should always be rewritten into DexValueString");
@@ -1215,6 +1237,11 @@
     public boolean isDexValueType() {
       return true;
     }
+
+    @Override
+    public AbstractValue toAbstractValue(AbstractValueFactory factory) {
+      return UnknownValue.getInstance();
+    }
   }
 
   static public class DexValueField extends NestedDexValue<DexField> {
@@ -1243,6 +1270,11 @@
     public DexValueField asDexValueField() {
       return this;
     }
+
+    @Override
+    public AbstractValue toAbstractValue(AbstractValueFactory factory) {
+      return UnknownValue.getInstance();
+    }
   }
 
   static public class DexValueMethod extends NestedDexValue<DexMethod> {
@@ -1271,6 +1303,11 @@
     public DexValueMethod asDexValueMethod() {
       return this;
     }
+
+    @Override
+    public AbstractValue toAbstractValue(AbstractValueFactory factory) {
+      return UnknownValue.getInstance();
+    }
   }
 
   static public class DexValueEnum extends NestedDexValue<DexField> {
@@ -1299,6 +1336,11 @@
     public DexValueEnum asDexValueEnum() {
       return this;
     }
+
+    @Override
+    public AbstractValue toAbstractValue(AbstractValueFactory factory) {
+      return UnknownValue.getInstance();
+    }
   }
 
   static public class DexValueMethodType extends NestedDexValue<DexProto> {
@@ -1327,6 +1369,11 @@
         IndexedItemCollection indexedItems, DexMethod method, int instructionOffset) {
       value.collectIndexedItems(indexedItems, method, instructionOffset);
     }
+
+    @Override
+    public AbstractValue toAbstractValue(AbstractValueFactory factory) {
+      return UnknownValue.getInstance();
+    }
   }
 
   static public class DexValueArray extends DexValue {
@@ -1372,6 +1419,11 @@
     }
 
     @Override
+    public AbstractValue toAbstractValue(AbstractValueFactory factory) {
+      return UnknownValue.getInstance();
+    }
+
+    @Override
     public Object asAsmEncodedObject() {
       throw new Unreachable("No ASM conversion for DexValueArray");
     }
@@ -1446,6 +1498,11 @@
     }
 
     @Override
+    public AbstractValue toAbstractValue(AbstractValueFactory factory) {
+      return UnknownValue.getInstance();
+    }
+
+    @Override
     public void writeTo(DexOutputBuffer dest, ObjectToOffsetMapping mapping) {
       writeHeader(DexValueKind.ANNOTATION, 0, dest);
       FileWriter.writeEncodedAnnotation(value, dest, mapping);
@@ -1566,7 +1623,7 @@
 
     @Override
     public ConstInstruction asConstInstruction(
-        AppView<? extends AppInfoWithSubtyping> appView, IRCode code, DebugLocalInfo local) {
+        AppView<? extends AppInfoWithClassHierarchy> appView, IRCode code, DebugLocalInfo local) {
       return code.createConstNull(local);
     }
   }
@@ -1652,7 +1709,7 @@
 
     @Override
     public ConstInstruction asConstInstruction(
-        AppView<? extends AppInfoWithSubtyping> appView, IRCode code, DebugLocalInfo local) {
+        AppView<? extends AppInfoWithClassHierarchy> appView, IRCode code, DebugLocalInfo local) {
       return code.createIntConstant(BooleanUtils.intValue(value), local);
     }
   }
@@ -1683,5 +1740,10 @@
         IndexedItemCollection indexedItems, DexMethod method, int instructionOffset) {
       value.collectIndexedItems(indexedItems, method, instructionOffset);
     }
+
+    @Override
+    public AbstractValue toAbstractValue(AbstractValueFactory factory) {
+      return UnknownValue.getInstance();
+    }
   }
 }
diff --git a/src/main/java/com/android/tools/r8/graph/LookupCompletenessHelper.java b/src/main/java/com/android/tools/r8/graph/LookupCompletenessHelper.java
index 64fa1a6..5a8f39f 100644
--- a/src/main/java/com/android/tools/r8/graph/LookupCompletenessHelper.java
+++ b/src/main/java/com/android/tools/r8/graph/LookupCompletenessHelper.java
@@ -40,7 +40,7 @@
 
   void checkDexClassAndMethod(DexClassAndMethod classAndMethod) {
     checkClass(classAndMethod.getHolder());
-    checkMethod(classAndMethod.getMethod());
+    checkMethod(classAndMethod.getDefinition());
   }
 
   LookupResultCollectionState computeCollectionState(
diff --git a/src/main/java/com/android/tools/r8/graph/ResolutionResult.java b/src/main/java/com/android/tools/r8/graph/ResolutionResult.java
index fbecfef..fd1781d 100644
--- a/src/main/java/com/android/tools/r8/graph/ResolutionResult.java
+++ b/src/main/java/com/android/tools/r8/graph/ResolutionResult.java
@@ -3,12 +3,12 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.graph;
 
-import com.android.tools.r8.errors.CompilationError;
 import com.android.tools.r8.graph.LookupResult.LookupResultSuccess.LookupResultCollectionState;
 import com.android.tools.r8.ir.desugar.LambdaDescriptor;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.shaking.InstantiatedObject;
 import com.android.tools.r8.utils.Box;
+import com.android.tools.r8.utils.OptionalBool;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
@@ -59,10 +59,10 @@
     return isSingleResolution() ? asSingleResolution().getResolvedMethod() : null;
   }
 
-  public abstract boolean isAccessibleFrom(
+  public abstract OptionalBool isAccessibleFrom(
       DexProgramClass context, AppInfoWithClassHierarchy appInfo);
 
-  public abstract boolean isAccessibleForVirtualDispatchFrom(
+  public abstract OptionalBool isAccessibleForVirtualDispatchFrom(
       DexProgramClass context, AppInfoWithClassHierarchy appInfo);
 
   public abstract boolean isVirtualTarget();
@@ -154,15 +154,19 @@
     }
 
     @Override
-    public boolean isAccessibleFrom(DexProgramClass context, AppInfoWithClassHierarchy appInfo) {
+    public OptionalBool isAccessibleFrom(
+        DexProgramClass context, AppInfoWithClassHierarchy appInfo) {
       return AccessControl.isMethodAccessible(
           resolvedMethod, initialResolutionHolder, context, appInfo);
     }
 
     @Override
-    public boolean isAccessibleForVirtualDispatchFrom(
+    public OptionalBool isAccessibleForVirtualDispatchFrom(
         DexProgramClass context, AppInfoWithClassHierarchy appInfo) {
-      return resolvedMethod.isVirtualMethod() && isAccessibleFrom(context, appInfo);
+      if (resolvedMethod.isVirtualMethod()) {
+        return isAccessibleFrom(context, appInfo);
+      }
+      return OptionalBool.FALSE;
     }
 
     @Override
@@ -180,7 +184,7 @@
     public DexEncodedMethod lookupInvokeSpecialTarget(
         DexProgramClass context, AppInfoWithClassHierarchy appInfo) {
       // If the resolution is non-accessible then no target exists.
-      if (isAccessibleFrom(context, appInfo)) {
+      if (isAccessibleFrom(context, appInfo).isPossiblyTrue()) {
         return internalInvokeSpecialOrSuper(
             context, appInfo, (sup, sub) -> isSuperclass(sup, sub, appInfo));
       }
@@ -205,16 +209,13 @@
     @Override
     public DexEncodedMethod lookupInvokeSuperTarget(
         DexProgramClass context, AppInfoWithClassHierarchy appInfo) {
-      // TODO(b/147848950): Investigate and remove the Compilation error. It could compile to
-      //  throw IAE.
       if (resolvedMethod.isInstanceInitializer()
-          || (appInfo.hasSubtyping()
-              && initialResolutionHolder != context
-              && !isSuperclass(initialResolutionHolder, context, appInfo.withSubtyping()))) {
-        throw new CompilationError(
-            "Illegal invoke-super to " + resolvedMethod.toSourceString(), context.getOrigin());
+          || (initialResolutionHolder != context
+              && !isSuperclass(initialResolutionHolder, context, appInfo))) {
+        // If the target is <init> or not on a super class then the call is invalid.
+        return null;
       }
-      if (isAccessibleFrom(context, appInfo)) {
+      if (isAccessibleFrom(context, appInfo).isPossiblyTrue()) {
         return internalInvokeSpecialOrSuper(context, appInfo, (sup, sub) -> true);
       }
       return null;
@@ -233,7 +234,7 @@
     @Override
     public DexEncodedMethod lookupInvokeStaticTarget(DexProgramClass context,
         AppInfoWithClassHierarchy appInfo) {
-      if (!isAccessibleFrom(context, appInfo)) {
+      if (isAccessibleFrom(context, appInfo).isFalse()) {
         return null;
       }
       if (resolvedMethod.isStatic()) {
@@ -254,7 +255,7 @@
     @Override
     public DexEncodedMethod lookupInvokeDirectTarget(
         DexProgramClass context, AppInfoWithClassHierarchy appInfo) {
-      if (!isAccessibleFrom(context, appInfo)) {
+      if (isAccessibleFrom(context, appInfo).isFalse()) {
         return null;
       }
       if (resolvedMethod.isDirectMethod()) {
@@ -313,7 +314,7 @@
       if (target == null) {
         DexClassAndMethod result = appInfo.lookupMaximallySpecificMethod(initialType, method);
         if (result != null) {
-          target = result.getMethod();
+          target = result.getDefinition();
         }
       }
       if (target == null) {
@@ -341,7 +342,7 @@
 
     private static boolean isSuperclass(
         DexClass sup, DexClass sub, AppInfoWithClassHierarchy appInfo) {
-      return sup != sub && appInfo.isSubtype(sub.type, sup.type);
+      return appInfo.isStrictSubtypeOf(sub.type, sup.type);
     }
 
     @Override
@@ -353,7 +354,7 @@
       // Check that the initial resolution holder is accessible from the context.
       assert appInfo.isSubtype(initialResolutionHolder.type, resolvedHolder.type)
           : initialResolutionHolder.type + " is not a subtype of " + resolvedHolder.type;
-      if (context != null && !isAccessibleFrom(context, appInfo)) {
+      if (context != null && isAccessibleFrom(context, appInfo).isFalse()) {
         return LookupResult.createFailedResult();
       }
       if (resolvedMethod.isPrivateMethod()) {
@@ -466,7 +467,7 @@
         DexClassAndMethod target,
         boolean holderIsInterface,
         Map<DexEncodedMethod, DexClassAndMethod> result) {
-      DexEncodedMethod targetMethod = target.getMethod();
+      DexEncodedMethod targetMethod = target.getDefinition();
       assert !targetMethod.isPrivateMethod();
       if (holderIsInterface) {
         // Add default interface methods to the list of targets.
@@ -726,14 +727,15 @@
     }
 
     @Override
-    public boolean isAccessibleFrom(DexProgramClass context, AppInfoWithClassHierarchy appInfo) {
-      return true;
+    public OptionalBool isAccessibleFrom(
+        DexProgramClass context, AppInfoWithClassHierarchy appInfo) {
+      return OptionalBool.TRUE;
     }
 
     @Override
-    public boolean isAccessibleForVirtualDispatchFrom(
+    public OptionalBool isAccessibleForVirtualDispatchFrom(
         DexProgramClass context, AppInfoWithClassHierarchy appInfo) {
-      return true;
+      return OptionalBool.TRUE;
     }
 
     @Override
@@ -760,14 +762,15 @@
     }
 
     @Override
-    public boolean isAccessibleFrom(DexProgramClass context, AppInfoWithClassHierarchy appInfo) {
-      return false;
+    public OptionalBool isAccessibleFrom(
+        DexProgramClass context, AppInfoWithClassHierarchy appInfo) {
+      return OptionalBool.FALSE;
     }
 
     @Override
-    public boolean isAccessibleForVirtualDispatchFrom(
+    public OptionalBool isAccessibleForVirtualDispatchFrom(
         DexProgramClass context, AppInfoWithClassHierarchy appInfo) {
-      return false;
+      return OptionalBool.FALSE;
     }
 
     @Override
diff --git a/src/main/java/com/android/tools/r8/graph/SubtypingInfo.java b/src/main/java/com/android/tools/r8/graph/SubtypingInfo.java
index 6c6e3ba..662ccb0 100644
--- a/src/main/java/com/android/tools/r8/graph/SubtypingInfo.java
+++ b/src/main/java/com/android/tools/r8/graph/SubtypingInfo.java
@@ -14,6 +14,7 @@
 import java.util.HashSet;
 import java.util.IdentityHashMap;
 import java.util.Map;
+import java.util.Objects;
 import java.util.Set;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.ConcurrentSkipListSet;
@@ -46,6 +47,18 @@
     populateSubtypeMap(classes, definitions::definitionFor, factory);
   }
 
+  public boolean verifyEquals(Collection<DexClass> classes, DexDefinitionSupplier definitions) {
+    SubtypingInfo subtypingInfo = new SubtypingInfo(classes, definitions);
+    assert typeInfo.equals(subtypingInfo.typeInfo);
+    assert subtypeMap.keySet().equals(subtypingInfo.subtypeMap.keySet());
+    subtypeMap.forEach(
+        (key, value) -> {
+          assert subtypingInfo.subtypeMap.get(key).equals(value);
+        });
+    assert missingClasses.equals(subtypingInfo.missingClasses);
+    return true;
+  }
+
   private void populateSuperType(
       Map<DexType, Set<DexType>> map,
       DexType superType,
@@ -239,6 +252,20 @@
     }
 
     @Override
+    public int hashCode() {
+      return Objects.hash(type, directSubtypes);
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+      if (!(obj instanceof TypeInfo)) {
+        return false;
+      }
+      TypeInfo other = (TypeInfo) obj;
+      return other.type == type && other.directSubtypes.equals(directSubtypes);
+    }
+
+    @Override
     public String toString() {
       return "TypeInfo{" + type + ", level:" + hierarchyLevel + "}";
     }
diff --git a/src/main/java/com/android/tools/r8/graph/TopDownClassHierarchyTraversal.java b/src/main/java/com/android/tools/r8/graph/TopDownClassHierarchyTraversal.java
index 72e07e1..613db50 100644
--- a/src/main/java/com/android/tools/r8/graph/TopDownClassHierarchyTraversal.java
+++ b/src/main/java/com/android/tools/r8/graph/TopDownClassHierarchyTraversal.java
@@ -10,7 +10,7 @@
     extends ClassHierarchyTraversal<T, TopDownClassHierarchyTraversal<T>> {
 
   private TopDownClassHierarchyTraversal(
-      AppView<? extends AppInfoWithSubtyping> appView, Scope scope) {
+      AppView<? extends AppInfoWithClassHierarchy> appView, Scope scope) {
     super(appView, scope);
   }
 
@@ -19,7 +19,7 @@
    * classes) that are reachable from a given set of sources.
    */
   public static TopDownClassHierarchyTraversal<DexClass> forAllClasses(
-      AppView<? extends AppInfoWithSubtyping> appView) {
+      AppView<? extends AppInfoWithClassHierarchy> appView) {
     return new TopDownClassHierarchyTraversal<>(appView, Scope.ALL_CLASSES);
   }
 
@@ -28,7 +28,7 @@
    * given set of sources.
    */
   public static TopDownClassHierarchyTraversal<DexLibraryClass> forLibraryClasses(
-      AppView<? extends AppInfoWithSubtyping> appView) {
+      AppView<? extends AppInfoWithClassHierarchy> appView) {
     return new TopDownClassHierarchyTraversal<>(appView, Scope.ONLY_LIBRARY_CLASSES);
   }
 
@@ -37,7 +37,7 @@
    * reachable from a given set of sources.
    */
   public static TopDownClassHierarchyTraversal<DexClass> forLibraryAndClasspathClasses(
-      AppView<? extends AppInfoWithSubtyping> appView) {
+      AppView<? extends AppInfoWithClassHierarchy> appView) {
     return new TopDownClassHierarchyTraversal<>(appView, Scope.ONLY_LIBRARY_AND_CLASSPATH_CLASSES);
   }
 
@@ -46,7 +46,7 @@
    * given set of sources.
    */
   public static TopDownClassHierarchyTraversal<DexProgramClass> forProgramClasses(
-      AppView<? extends AppInfoWithSubtyping> appView) {
+      AppView<? extends AppInfoWithClassHierarchy> appView) {
     return new TopDownClassHierarchyTraversal<>(appView, Scope.ONLY_PROGRAM_CLASSES);
   }
 
diff --git a/src/main/java/com/android/tools/r8/graph/analysis/InitializedClassesInInstanceMethodsAnalysis.java b/src/main/java/com/android/tools/r8/graph/analysis/InitializedClassesInInstanceMethodsAnalysis.java
index 7ecd527..04dbef2 100644
--- a/src/main/java/com/android/tools/r8/graph/analysis/InitializedClassesInInstanceMethodsAnalysis.java
+++ b/src/main/java/com/android/tools/r8/graph/analysis/InitializedClassesInInstanceMethodsAnalysis.java
@@ -4,7 +4,7 @@
 
 package com.android.tools.r8.graph.analysis;
 
-import com.android.tools.r8.graph.AppInfoWithSubtyping;
+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.DexEncodedMethod;
@@ -19,11 +19,11 @@
   // A simple structure that stores the result of the analysis.
   public static class InitializedClassesInInstanceMethods {
 
-    private final AppView<? extends AppInfoWithSubtyping> appView;
+    private final AppView<? extends AppInfoWithClassHierarchy> appView;
     private final Map<DexType, DexType> mapping;
 
     private InitializedClassesInInstanceMethods(
-        AppView<? extends AppInfoWithSubtyping> appView, Map<DexType, DexType> mapping) {
+        AppView<? extends AppInfoWithClassHierarchy> appView, Map<DexType, DexType> mapping) {
       this.appView = appView;
       this.mapping = mapping;
     }
@@ -31,7 +31,7 @@
     public boolean isClassDefinitelyLoadedInInstanceMethodsOn(DexType subject, DexType context) {
       // If `subject` is kept, then it is instantiated by reflection, which means that the analysis
       // has not seen all allocation sites. In that case, we conservatively return false.
-      AppInfoWithSubtyping appInfo = appView.appInfo();
+      AppInfoWithClassHierarchy appInfo = appView.appInfo();
       if (appInfo.hasLiveness() && appInfo.withLiveness().isPinned(subject)) {
         return false;
       }
@@ -50,14 +50,14 @@
     }
   }
 
-  private final AppView<? extends AppInfoWithSubtyping> appView;
+  private final AppView<? extends AppInfoWithClassHierarchy> appView;
 
   // If the mapping contains an entry `X -> Y`, then the type Y is guaranteed to be initialized in
   // all instance methods of X.
   private final Map<DexType, DexType> mapping = new IdentityHashMap<>();
 
   public InitializedClassesInInstanceMethodsAnalysis(
-      AppView<? extends AppInfoWithSubtyping> appView) {
+      AppView<? extends AppInfoWithClassHierarchy> appView) {
     this.appView = appView;
   }
 
@@ -73,7 +73,7 @@
     }
 
     // Record that the enclosing class is guaranteed to be initialized at the allocation site.
-    AppInfoWithSubtyping appInfo = appView.appInfo();
+    AppInfoWithClassHierarchy appInfo = appView.appInfo();
     DexType guaranteedToBeInitialized = context.holder();
     DexType existingGuaranteedToBeInitialized =
         mapping.getOrDefault(key, guaranteedToBeInitialized);
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 94ee9cf..e825ba4 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
@@ -97,7 +97,7 @@
   }
 
   public boolean isClassDefinitelyLoadedBeforeInstruction(DexType type, Instruction instruction) {
-    DexType context = code.method.holder();
+    DexType context = code.method().holder();
     BasicBlock block = instruction.getBlock();
 
     // Visit the instructions in `block` prior to `instruction`.
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/DeterminismAnalysis.java b/src/main/java/com/android/tools/r8/ir/analysis/DeterminismAnalysis.java
index 9ae6c86..5452f50 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/DeterminismAnalysis.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/DeterminismAnalysis.java
@@ -37,7 +37,7 @@
       }
       if (instr.isInvokeMethod()) {
         DexEncodedMethod target =
-            instr.asInvokeMethod().lookupSingleTarget(appView, code.method.holder());
+            instr.asInvokeMethod().lookupSingleTarget(appView, code.method().holder());
         if (target != null && target.getOptimizationInfo().returnValueOnlyDependsOnArguments()) {
           continue;
         }
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/InitializedClassesOnNormalExitAnalysis.java b/src/main/java/com/android/tools/r8/ir/analysis/InitializedClassesOnNormalExitAnalysis.java
index 406c9e6..215f2a1 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/InitializedClassesOnNormalExitAnalysis.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/InitializedClassesOnNormalExitAnalysis.java
@@ -36,7 +36,7 @@
   public static Set<DexType> computeInitializedClassesOnNormalExit(
       AppView<AppInfoWithLiveness> appView, IRCode code) {
     DominatorTree dominatorTree = new DominatorTree(code, Assumption.MAY_HAVE_UNREACHABLE_BLOCKS);
-    Visitor visitor = new Visitor(appView, code.method.holder());
+    Visitor visitor = new Visitor(appView, code.method().holder());
     for (BasicBlock dominator : dominatorTree.normalExitDominatorBlocks()) {
       if (dominator.hasCatchHandlers()) {
         // When determining which classes that are guaranteed to be initialized from a given
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/TypeChecker.java b/src/main/java/com/android/tools/r8/ir/analysis/TypeChecker.java
index f8a621d..8c8eeb9 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/TypeChecker.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/TypeChecker.java
@@ -4,7 +4,7 @@
 
 package com.android.tools.r8.ir.analysis;
 
-import com.android.tools.r8.graph.AppInfoWithSubtyping;
+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.DexEncodedMethod;
@@ -30,9 +30,9 @@
  */
 public class TypeChecker {
 
-  private final AppView<? extends AppInfoWithSubtyping> appView;
+  private final AppView<? extends AppInfoWithClassHierarchy> appView;
 
-  public TypeChecker(AppView<? extends AppInfoWithSubtyping> appView) {
+  public TypeChecker(AppView<? extends AppInfoWithClassHierarchy> appView) {
     this.appView = appView;
   }
 
@@ -43,7 +43,7 @@
           return false;
         }
       } else if (instruction.isReturn()) {
-        if (!check(instruction.asReturn(), code.method)) {
+        if (!check(instruction.asReturn(), code.method())) {
           return false;
         }
       } else if (instruction.isStaticPut()) {
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/ValueMayDependOnEnvironmentAnalysis.java b/src/main/java/com/android/tools/r8/ir/analysis/ValueMayDependOnEnvironmentAnalysis.java
index f417d65..fbb6735 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/ValueMayDependOnEnvironmentAnalysis.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/ValueMayDependOnEnvironmentAnalysis.java
@@ -11,6 +11,7 @@
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.ir.analysis.value.AbstractValue;
 import com.android.tools.r8.ir.code.ArrayPut;
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.Instruction;
@@ -80,7 +81,7 @@
   public ValueMayDependOnEnvironmentAnalysis(AppView<?> appView, IRCode code) {
     this.appView = appView;
     this.code = code;
-    this.context = code.method.holder();
+    this.context = code.method().holder();
   }
 
   public boolean valueMayDependOnEnvironment(Value value) {
@@ -109,8 +110,13 @@
       if (isConstantArrayThroughoutMethod(root, assumedNotToDependOnEnvironment)) {
         return false;
       }
-      if (root.getAbstractValue(appView, context).isSingleEnumValue()) {
-        return false;
+      AbstractValue abstractValue = root.getAbstractValue(appView, context);
+      if (abstractValue.isSingleFieldValue()) {
+        DexEncodedField field =
+            appView.definitionFor(abstractValue.asSingleFieldValue().getField());
+        if (field != null && field.isEnum()) {
+          return false;
+        }
       }
       if (isNewInstanceWithoutEnvironmentDependentFields(root, assumedNotToDependOnEnvironment)) {
         return false;
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/escape/EscapeAnalysis.java b/src/main/java/com/android/tools/r8/ir/analysis/escape/EscapeAnalysis.java
index 43e484d..b1cac80 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/escape/EscapeAnalysis.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/escape/EscapeAnalysis.java
@@ -137,8 +137,8 @@
           }
         }
       }
-      if (!configuration.isLegitimateEscapeRoute(appView, this, user, code.method.method)
-          && isDirectlyEscaping(user, code.method.method, arguments)) {
+      if (!configuration.isLegitimateEscapeRoute(appView, this, user, code.method().method)
+          && isDirectlyEscaping(user, code.method().method, arguments)) {
         if (stoppingCriterion.test(user)) {
           return true;
         }
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldAccessAnalysis.java b/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldAccessAnalysis.java
index 1e8bb52..7c148a4 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldAccessAnalysis.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldAccessAnalysis.java
@@ -74,7 +74,7 @@
         DexEncodedField encodedField = appView.appInfo().resolveField(fieldInstruction.getField());
         if (encodedField != null && encodedField.isProgramField(appView)) {
           if (fieldAssignmentTracker != null) {
-            fieldAssignmentTracker.recordFieldAccess(fieldInstruction, encodedField, code.method);
+            fieldAssignmentTracker.recordFieldAccess(fieldInstruction, encodedField, code.method());
           }
           if (fieldBitAccessAnalysis != null) {
             fieldBitAccessAnalysis.recordFieldAccess(fieldInstruction, encodedField, feedback);
@@ -85,7 +85,7 @@
         DexProgramClass clazz = asProgramClassOrNull(appView.definitionFor(newInstance.clazz));
         if (clazz != null) {
           if (fieldAssignmentTracker != null) {
-            fieldAssignmentTracker.recordAllocationSite(newInstance, clazz, code.method);
+            fieldAssignmentTracker.recordAllocationSite(newInstance, clazz, code.method());
           }
         }
       }
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/StaticFieldValueAnalysis.java b/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/StaticFieldValueAnalysis.java
index e2ab677..f488267 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/StaticFieldValueAnalysis.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/StaticFieldValueAnalysis.java
@@ -4,7 +4,9 @@
 
 package com.android.tools.r8.ir.analysis.fieldvalueanalysis;
 
-import static com.android.tools.r8.ir.code.Opcodes.*;
+import static com.android.tools.r8.ir.code.Opcodes.ARRAY_PUT;
+import static com.android.tools.r8.ir.code.Opcodes.INVOKE_DIRECT;
+import static com.android.tools.r8.ir.code.Opcodes.STATIC_PUT;
 
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexEncodedField;
@@ -17,8 +19,8 @@
 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.AbstractValueFactory;
-import com.android.tools.r8.ir.analysis.value.SingleEnumValue;
-import com.android.tools.r8.ir.analysis.value.UnknownValue;
+import com.android.tools.r8.ir.analysis.value.ObjectState;
+import com.android.tools.r8.ir.analysis.value.SingleFieldValue;
 import com.android.tools.r8.ir.code.ArrayPut;
 import com.android.tools.r8.ir.code.FieldInstruction;
 import com.android.tools.r8.ir.code.IRCode;
@@ -28,6 +30,8 @@
 import com.android.tools.r8.ir.code.Value;
 import com.android.tools.r8.ir.optimize.ClassInitializerDefaultsOptimization.ClassInitializerDefaultsResult;
 import com.android.tools.r8.ir.optimize.info.OptimizationFeedback;
+import com.android.tools.r8.ir.optimize.info.field.InstanceFieldArgumentInitializationInfo;
+import com.android.tools.r8.ir.optimize.info.field.InstanceFieldInitializationInfoCollection;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.utils.Timing;
 
@@ -100,10 +104,9 @@
   void updateFieldOptimizationInfo(DexEncodedField field, FieldInstruction fieldPut, Value value) {
     // Abstract value.
     Value root = value.getAliasedValue();
-    AbstractValue abstractValue = computeAbstractValue(root);
+    AbstractValue abstractValue = root.getAbstractValue(appView, clazz.type);
     if (abstractValue.isUnknown()) {
-      feedback.recordFieldHasAbstractValue(
-          field, appView, appView.abstractValueFactory().createSingleFieldValue(field.field));
+      feedback.recordFieldHasAbstractValue(field, appView, computeSingleFieldValue(field, root));
     } else {
       feedback.recordFieldHasAbstractValue(field, appView, abstractValue);
     }
@@ -124,33 +127,29 @@
     }
   }
 
-  private AbstractValue computeAbstractValue(Value value) {
+  private SingleFieldValue computeSingleFieldValue(DexEncodedField field, Value value) {
     assert !value.hasAliasedValue();
-    if (clazz.isEnum()) {
-      SingleEnumValue singleEnumValue = getSingleEnumValue(value);
-      if (singleEnumValue != null) {
-        return singleEnumValue;
-      }
+    SingleFieldValue result = computeSingleEnumFieldValue(value);
+    if (result != null) {
+      return result;
     }
-    if (!value.isPhi()) {
-      return value.definition.getAbstractValue(appView, clazz.type);
-    }
-    return UnknownValue.getInstance();
+    return appView
+        .abstractValueFactory()
+        .createSingleFieldValue(field.field, computeObjectState(value));
   }
 
   /**
    * If {@param value} is defined by a new-instance instruction that instantiates the enclosing enum
    * class, and the value is assigned into exactly one static enum field on the enclosing enum
-   * class, then returns a {@link SingleEnumValue} instance. Otherwise, returns {@code null}.
+   * class, then returns a {@link SingleFieldValue} instance. Otherwise, returns {@code null}.
    *
    * <p>Note that enum constructors also store the newly instantiated enums in the {@code $VALUES}
    * array field on the enum. Therefore, this code also allows {@param value} to be stored into an
    * array as long as the array is identified as being the {@code $VALUES} array.
    */
-  private SingleEnumValue getSingleEnumValue(Value value) {
-    assert clazz.isEnum();
+  private SingleFieldValue computeSingleEnumFieldValue(Value value) {
     assert !value.hasAliasedValue();
-    if (value.isPhi() || !value.definition.isNewInstance()) {
+    if (!clazz.isEnum() || !value.isDefinedByInstructionSatisfying(Instruction::isNewInstance)) {
       return null;
     }
 
@@ -202,7 +201,53 @@
       return null;
     }
 
-    return appView.abstractValueFactory().createSingleEnumValue(enumField.field);
+    return appView
+        .abstractValueFactory()
+        .createSingleFieldValue(enumField.field, computeObjectState(value));
+  }
+
+  private ObjectState computeObjectState(Value value) {
+    assert !value.hasAliasedValue();
+    if (!value.isDefinedByInstructionSatisfying(Instruction::isNewInstance)) {
+      return ObjectState.empty();
+    }
+
+    NewInstance newInstance = value.definition.asNewInstance();
+    InvokeDirect uniqueConstructorInvoke =
+        newInstance.getUniqueConstructorInvoke(appView.dexItemFactory());
+    if (uniqueConstructorInvoke == null) {
+      return ObjectState.empty();
+    }
+
+    DexEncodedMethod singleTarget = uniqueConstructorInvoke.lookupSingleTarget(appView, clazz.type);
+    if (singleTarget == null) {
+      return ObjectState.empty();
+    }
+
+    InstanceFieldInitializationInfoCollection initializationInfos =
+        singleTarget.getOptimizationInfo().getInstanceInitializerInfo().fieldInitializationInfos();
+    if (initializationInfos.isEmpty()) {
+      return ObjectState.empty();
+    }
+
+    ObjectState.Builder builder = ObjectState.builder();
+    initializationInfos.forEach(
+        appView,
+        (field, initializationInfo) -> {
+          if (!appView.appInfo().isInstanceFieldWrittenOnlyInInstanceInitializers(field)) {
+            return;
+          }
+          if (initializationInfo.isArgumentInitializationInfo()) {
+            InstanceFieldArgumentInitializationInfo argumentInitializationInfo =
+                initializationInfo.asArgumentInitializationInfo();
+            Value argument =
+                uniqueConstructorInvoke.getArgument(argumentInitializationInfo.getArgumentIndex());
+            builder.recordFieldHasValue(field, argument.getAbstractValue(appView, clazz.type));
+          } else if (initializationInfo.isSingleValue()) {
+            builder.recordFieldHasValue(field, initializationInfo.asSingleValue());
+          }
+        });
+    return builder.build();
   }
 
   private boolean isEnumValuesArray(Value value) {
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedMessageLiteBuilderShrinker.java b/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedMessageLiteBuilderShrinker.java
index 2f152e8..f073108 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedMessageLiteBuilderShrinker.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedMessageLiteBuilderShrinker.java
@@ -5,7 +5,6 @@
 package com.android.tools.r8.ir.analysis.proto;
 
 import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
-import com.android.tools.r8.graph.AppInfoWithSubtyping;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexMethod;
@@ -52,13 +51,13 @@
 
 public class GeneratedMessageLiteBuilderShrinker {
 
-  private final AppView<? extends AppInfoWithSubtyping> appView;
+  private final AppView<? extends AppInfoWithClassHierarchy> appView;
   private final ProtoReferences references;
 
   private final Map<DexProgramClass, DexEncodedMethod> builders = new IdentityHashMap<>();
 
   GeneratedMessageLiteBuilderShrinker(
-      AppView<? extends AppInfoWithSubtyping> appView, ProtoReferences references) {
+      AppView<? extends AppInfoWithClassHierarchy> appView, ProtoReferences references) {
     this.appView = appView;
     this.references = references;
   }
@@ -117,7 +116,7 @@
   }
 
   public static void addInliningHeuristicsForBuilderInlining(
-      AppView<? extends AppInfoWithSubtyping> appView,
+      AppView<? extends AppInfoWithClassHierarchy> appView,
       SubtypingInfo subtypingInfo,
       PredicateSet<DexType> alwaysClassInline,
       Set<DexType> neverMerge,
@@ -279,7 +278,7 @@
 
   private static class RootSetExtension {
 
-    private final AppView<? extends AppInfoWithSubtyping> appView;
+    private final AppView<? extends AppInfoWithClassHierarchy> appView;
     private final ProtoReferences references;
 
     private final PredicateSet<DexType> alwaysClassInline;
@@ -289,7 +288,7 @@
     private final Set<DexMethod> bypassClinitforInlining;
 
     RootSetExtension(
-        AppView<? extends AppInfoWithSubtyping> appView,
+        AppView<? extends AppInfoWithClassHierarchy> appView,
         PredicateSet<DexType> alwaysClassInline,
         Set<DexType> neverMerge,
         Set<DexMethod> alwaysInline,
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/sideeffect/ClassInitializerSideEffectAnalysis.java b/src/main/java/com/android/tools/r8/ir/analysis/sideeffect/ClassInitializerSideEffectAnalysis.java
index 0d500e9..b3aaf9c 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/sideeffect/ClassInitializerSideEffectAnalysis.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/sideeffect/ClassInitializerSideEffectAnalysis.java
@@ -39,7 +39,7 @@
    */
   public static ClassInitializerSideEffect classInitializerCanBePostponed(
       AppView<?> appView, IRCode code) {
-    DexType context = code.method.holder();
+    DexType context = code.method().holder();
     OptionalBool controlFlowMayDependOnEnvironment = OptionalBool.unknown();
     boolean mayHaveSideEffects = false;
 
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 4af45c6..4821b2b 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
@@ -5,7 +5,7 @@
 
 import static com.android.tools.r8.ir.analysis.type.Nullability.maybeNull;
 
-import com.android.tools.r8.graph.AppInfoWithSubtyping;
+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.DexType;
@@ -88,7 +88,7 @@
   }
 
   @Override
-  public boolean isBasedOnMissingClass(AppView<? extends AppInfoWithSubtyping> appView) {
+  public boolean isBasedOnMissingClass(AppView<? extends AppInfoWithClassHierarchy> appView) {
     return memberTypeLattice.isBasedOnMissingClass(appView);
   }
 
@@ -129,7 +129,7 @@
 
   @Override
   public ArrayTypeElement fixupClassTypeReferences(
-      Function<DexType, DexType> mapping, AppView<? extends AppInfoWithSubtyping> appView) {
+      Function<DexType, DexType> mapping, AppView<? extends AppInfoWithClassHierarchy> appView) {
     if (memberTypeLattice.isReferenceType()) {
       TypeElement substitutedMemberType =
           memberTypeLattice.fixupClassTypeReferences(mapping, appView);
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 8a47047..e71ef7d 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
@@ -4,7 +4,7 @@
 package com.android.tools.r8.ir.analysis.type;
 
 import com.android.tools.r8.errors.CompilationError;
-import com.android.tools.r8.graph.AppInfoWithSubtyping;
+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;
@@ -28,7 +28,7 @@
   // Least upper bound of interfaces that this class type is implementing.
   // Lazily computed on demand via DexItemFactory, where the canonicalized set will be maintained.
   private Set<DexType> lazyInterfaces;
-  private AppView<? extends AppInfoWithSubtyping> appView;
+  private AppView<? extends AppInfoWithClassHierarchy> appView;
   // On-demand link between other nullability-variants.
   private final NullabilityVariants<ClassTypeElement> variants;
   private final DexType type;
@@ -42,7 +42,9 @@
   }
 
   public static ClassTypeElement create(
-      DexType classType, Nullability nullability, AppView<? extends AppInfoWithSubtyping> appView) {
+      DexType classType,
+      Nullability nullability,
+      AppView<? extends AppInfoWithClassHierarchy> appView) {
     assert appView != null;
     return NullabilityVariants.create(
         nullability,
@@ -54,7 +56,7 @@
       Nullability nullability,
       Set<DexType> interfaces,
       NullabilityVariants<ClassTypeElement> variants,
-      AppView<? extends AppInfoWithSubtyping> appView) {
+      AppView<? extends AppInfoWithClassHierarchy> appView) {
     super(nullability);
     assert classType.isClassType();
     assert interfaces != null || appView != null;
@@ -99,9 +101,8 @@
     return variants.getOrCreateElement(nullability, this::createVariant);
   }
 
-
   @Override
-  public boolean isBasedOnMissingClass(AppView<? extends AppInfoWithSubtyping> appView) {
+  public boolean isBasedOnMissingClass(AppView<? extends AppInfoWithClassHierarchy> appView) {
     return appView.appInfo().isMissingOrHasMissingSuperType(getClassType())
         || getInterfaces().stream()
             .anyMatch(type -> appView.appInfo().isMissingOrHasMissingSuperType(type));
@@ -145,7 +146,7 @@
 
   @Override
   public TypeElement fixupClassTypeReferences(
-      Function<DexType, DexType> mapping, AppView<? extends AppInfoWithSubtyping> appView) {
+      Function<DexType, DexType> mapping, AppView<? extends AppInfoWithClassHierarchy> appView) {
     DexType mappedType = mapping.apply(type);
     if (mappedType.isPrimitiveType()) {
       return PrimitiveTypeElement.fromDexType(mappedType, false);
@@ -197,7 +198,7 @@
 
   ClassTypeElement join(ClassTypeElement other, AppView<?> appView) {
     Nullability nullability = nullability().join(other.nullability());
-    if (!appView.appInfo().hasSubtyping()) {
+    if (!appView.enableWholeProgramOptimizations()) {
       assert lazyInterfaces != null && lazyInterfaces.isEmpty();
       assert other.lazyInterfaces != null && other.lazyInterfaces.isEmpty();
       return ClassTypeElement.create(
@@ -209,7 +210,7 @@
     }
     DexType lubType =
         computeLeastUpperBoundOfClasses(
-            appView.appInfo().withSubtyping(), getClassType(), other.getClassType());
+            appView.appInfo().withClassHierarchy(), getClassType(), other.getClassType());
     Set<DexType> c1lubItfs = getInterfaces();
     Set<DexType> c2lubItfs = other.getInterfaces();
     Set<DexType> lubItfs = null;
@@ -217,7 +218,8 @@
       lubItfs = c1lubItfs;
     }
     if (lubItfs == null) {
-      lubItfs = computeLeastUpperBoundOfInterfaces(appView.withSubtyping(), c1lubItfs, c2lubItfs);
+      lubItfs =
+          computeLeastUpperBoundOfInterfaces(appView.withClassHierarchy(), c1lubItfs, c2lubItfs);
     }
     return ClassTypeElement.create(lubType, nullability, lubItfs);
   }
@@ -238,7 +240,7 @@
   }
 
   public static DexType computeLeastUpperBoundOfClasses(
-      AppInfoWithSubtyping appInfo, DexType type1, DexType type2) {
+      AppInfoWithClassHierarchy appInfo, DexType type1, DexType type2) {
     // Compiling R8 with R8, this hits more than 1/3 of cases.
     if (type1 == type2) {
       return type1;
@@ -282,7 +284,7 @@
   }
 
   public static Set<DexType> computeLeastUpperBoundOfInterfaces(
-      AppView<? extends AppInfoWithSubtyping> appView, Set<DexType> s1, Set<DexType> s2) {
+      AppView<? extends AppInfoWithClassHierarchy> appView, Set<DexType> s1, Set<DexType> s2) {
     if (s1.isEmpty() || s2.isEmpty()) {
       return Collections.emptySet();
     }
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/type/DestructivePhiTypeUpdater.java b/src/main/java/com/android/tools/r8/ir/analysis/type/DestructivePhiTypeUpdater.java
index 6c1136f..29ca247 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/type/DestructivePhiTypeUpdater.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/type/DestructivePhiTypeUpdater.java
@@ -4,7 +4,7 @@
 
 package com.android.tools.r8.ir.analysis.type;
 
-import com.android.tools.r8.graph.AppInfoWithSubtyping;
+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.ir.code.BasicBlock;
@@ -20,15 +20,15 @@
 
 public class DestructivePhiTypeUpdater {
 
-  private final AppView<? extends AppInfoWithSubtyping> appView;
+  private final AppView<? extends AppInfoWithClassHierarchy> appView;
   private final Function<DexType, DexType> mapping;
 
-  public DestructivePhiTypeUpdater(AppView<? extends AppInfoWithSubtyping> appView) {
+  public DestructivePhiTypeUpdater(AppView<? extends AppInfoWithClassHierarchy> appView) {
     this(appView, appView.graphLense()::lookupType);
   }
 
   public DestructivePhiTypeUpdater(
-      AppView<? extends AppInfoWithSubtyping> appView, Function<DexType, DexType> mapping) {
+      AppView<? extends AppInfoWithClassHierarchy> appView, Function<DexType, DexType> mapping) {
     this.appView = appView;
     this.mapping = mapping;
   }
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/type/TypeAnalysis.java b/src/main/java/com/android/tools/r8/ir/analysis/type/TypeAnalysis.java
index ffd23cc..0e3f404 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/type/TypeAnalysis.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/type/TypeAnalysis.java
@@ -3,8 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.ir.analysis.type;
 
-
-import com.android.tools.r8.graph.AppInfoWithSubtyping;
+import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexType;
@@ -157,12 +156,12 @@
   }
 
   public static DexType getRefinedReceiverType(
-      AppView<? extends AppInfoWithSubtyping> appView, InvokeMethodWithReceiver invoke) {
+      AppView<? extends AppInfoWithClassHierarchy> appView, InvokeMethodWithReceiver invoke) {
     return getRefinedReceiverType(appView, invoke.getInvokedMethod(), invoke.getReceiver());
   }
 
   public static DexType getRefinedReceiverType(
-      AppView<? extends AppInfoWithSubtyping> appView, DexMethod method, Value receiver) {
+      AppView<? extends AppInfoWithClassHierarchy> appView, DexMethod method, Value receiver) {
     TypeElement lattice = receiver.getDynamicUpperBoundType(appView);
     DexType staticReceiverType = method.holder;
     if (lattice.isClassType()) {
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 ecc7340..10f5ece 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
@@ -4,7 +4,7 @@
 package com.android.tools.r8.ir.analysis.type;
 
 import com.android.tools.r8.errors.Unreachable;
-import com.android.tools.r8.graph.AppInfoWithSubtyping;
+import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexDefinitionSupplier;
 import com.android.tools.r8.graph.DexItemFactory;
@@ -68,7 +68,7 @@
   }
 
   public TypeElement fixupClassTypeReferences(
-      Function<DexType, DexType> mapping, AppView<? extends AppInfoWithSubtyping> appView) {
+      Function<DexType, DexType> mapping, AppView<? extends AppInfoWithClassHierarchy> appView) {
     return this;
   }
 
@@ -228,7 +228,7 @@
    * @return {@code} true if this type is based on a missing class.
    * @param appView
    */
-  public boolean isBasedOnMissingClass(AppView<? extends AppInfoWithSubtyping> appView) {
+  public boolean isBasedOnMissingClass(AppView<? extends AppInfoWithClassHierarchy> appView) {
     return false;
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/value/AbstractValue.java b/src/main/java/com/android/tools/r8/ir/analysis/value/AbstractValue.java
index 404faff..9939980 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/value/AbstractValue.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/value/AbstractValue.java
@@ -44,14 +44,6 @@
     return null;
   }
 
-  public boolean isSingleEnumValue() {
-    return false;
-  }
-
-  public SingleEnumValue asSingleEnumValue() {
-    return null;
-  }
-
   public boolean isSingleFieldValue() {
     return false;
   }
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/value/AbstractValueFactory.java b/src/main/java/com/android/tools/r8/ir/analysis/value/AbstractValueFactory.java
index 80ca063..6583c9b 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/value/AbstractValueFactory.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/value/AbstractValueFactory.java
@@ -13,9 +13,6 @@
 
   private ConcurrentHashMap<DexType, SingleConstClassValue> singleConstClassValues =
       new ConcurrentHashMap<>();
-  private ConcurrentHashMap<DexField, SingleEnumValue> singleEnumValues = new ConcurrentHashMap<>();
-  private ConcurrentHashMap<DexField, SingleFieldValue> singleFieldValues =
-      new ConcurrentHashMap<>();
   private ConcurrentHashMap<Long, SingleNumberValue> singleNumberValues = new ConcurrentHashMap<>();
   private ConcurrentHashMap<DexString, SingleStringValue> singleStringValues =
       new ConcurrentHashMap<>();
@@ -24,12 +21,10 @@
     return singleConstClassValues.computeIfAbsent(type, SingleConstClassValue::new);
   }
 
-  public SingleEnumValue createSingleEnumValue(DexField field) {
-    return singleEnumValues.computeIfAbsent(field, SingleEnumValue::new);
-  }
-
-  public SingleFieldValue createSingleFieldValue(DexField field) {
-    return singleFieldValues.computeIfAbsent(field, SingleFieldValue::new);
+  public SingleFieldValue createSingleFieldValue(DexField field, ObjectState state) {
+    return state.isEmpty()
+        ? new SingleStatelessFieldValue(field)
+        : new SingleStatefulFieldValue(field, state);
   }
 
   public SingleNumberValue createSingleNumberValue(long value) {
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/value/EmptyObjectState.java b/src/main/java/com/android/tools/r8/ir/analysis/value/EmptyObjectState.java
new file mode 100644
index 0000000..48b1d9c
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/analysis/value/EmptyObjectState.java
@@ -0,0 +1,46 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.analysis.value;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexEncodedField;
+import com.android.tools.r8.graph.GraphLense;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+
+public class EmptyObjectState extends ObjectState {
+
+  private static final EmptyObjectState INSTANCE = new EmptyObjectState();
+
+  private EmptyObjectState() {}
+
+  public static EmptyObjectState getInstance() {
+    return INSTANCE;
+  }
+
+  @Override
+  public AbstractValue getAbstractFieldValue(DexEncodedField field) {
+    return UnknownValue.getInstance();
+  }
+
+  @Override
+  public boolean isEmpty() {
+    return true;
+  }
+
+  @Override
+  public ObjectState rewrittenWithLens(AppView<AppInfoWithLiveness> appView, GraphLense lens) {
+    return this;
+  }
+
+  @Override
+  public boolean equals(Object o) {
+    return this == o;
+  }
+
+  @Override
+  public int hashCode() {
+    return System.identityHashCode(this);
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/value/NonEmptyObjectState.java b/src/main/java/com/android/tools/r8/ir/analysis/value/NonEmptyObjectState.java
new file mode 100644
index 0000000..0732b83
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/analysis/value/NonEmptyObjectState.java
@@ -0,0 +1,68 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.analysis.value;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexEncodedField;
+import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.graph.GraphLense;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import java.util.IdentityHashMap;
+import java.util.Map;
+
+public class NonEmptyObjectState extends ObjectState {
+
+  private final Map<DexField, AbstractValue> state;
+
+  /** Intentionally package private, use {@link ObjectState.Builder}. */
+  NonEmptyObjectState(Map<DexField, AbstractValue> state) {
+    assert !state.isEmpty();
+    assert state.values().stream().noneMatch(AbstractValue::isUnknown);
+    this.state = state;
+  }
+
+  @Override
+  public AbstractValue getAbstractFieldValue(DexEncodedField field) {
+    return state.getOrDefault(field.field, UnknownValue.getInstance());
+  }
+
+  @Override
+  public boolean isEmpty() {
+    return false;
+  }
+
+  @Override
+  public ObjectState rewrittenWithLens(AppView<AppInfoWithLiveness> appView, GraphLense lens) {
+    Map<DexField, AbstractValue> rewrittenState = new IdentityHashMap<>();
+    state.forEach(
+        (field, value) ->
+            rewrittenState.put(lens.lookupField(field), value.rewrittenWithLens(appView, lens)));
+    return new NonEmptyObjectState(rewrittenState);
+  }
+
+  @Override
+  public boolean equals(Object o) {
+    if (getClass() != o.getClass()) {
+      return false;
+    }
+    NonEmptyObjectState other = (NonEmptyObjectState) o;
+    if (state.size() != other.state.size()) {
+      return false;
+    }
+    for (DexField dexField : state.keySet()) {
+      AbstractValue localValue = state.get(dexField);
+      AbstractValue otherValue = other.state.get(dexField);
+      if (!localValue.equals(otherValue)) {
+        return false;
+      }
+    }
+    return true;
+  }
+
+  @Override
+  public int hashCode() {
+    return state.hashCode();
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/value/ObjectState.java b/src/main/java/com/android/tools/r8/ir/analysis/value/ObjectState.java
new file mode 100644
index 0000000..066b305
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/analysis/value/ObjectState.java
@@ -0,0 +1,53 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.analysis.value;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexEncodedField;
+import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.graph.GraphLense;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import java.util.IdentityHashMap;
+import java.util.Map;
+
+public abstract class ObjectState {
+
+  public static Builder builder() {
+    return new Builder();
+  }
+
+  public static ObjectState empty() {
+    return EmptyObjectState.getInstance();
+  }
+
+  public abstract AbstractValue getAbstractFieldValue(DexEncodedField field);
+
+  public abstract boolean isEmpty();
+
+  public abstract ObjectState rewrittenWithLens(
+      AppView<AppInfoWithLiveness> appView, GraphLense lens);
+
+  @Override
+  public abstract boolean equals(Object o);
+
+  @Override
+  public abstract int hashCode();
+
+  public static class Builder {
+
+    private final Map<DexField, AbstractValue> state = new IdentityHashMap<>();
+
+    public void recordFieldHasValue(DexEncodedField field, AbstractValue abstractValue) {
+      if (!abstractValue.isUnknown()) {
+        assert !state.containsKey(field.field);
+        state.put(field.field, abstractValue);
+      }
+    }
+
+    public ObjectState build() {
+      return state.isEmpty() ? empty() : new NonEmptyObjectState(state);
+    }
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/value/SingleConstClassValue.java b/src/main/java/com/android/tools/r8/ir/analysis/value/SingleConstClassValue.java
index 3f5640d..b45ec56 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/value/SingleConstClassValue.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/value/SingleConstClassValue.java
@@ -6,9 +6,9 @@
 
 import static com.android.tools.r8.ir.analysis.type.Nullability.definitelyNotNull;
 import static com.android.tools.r8.ir.analysis.type.TypeElement.classClassType;
-import static com.android.tools.r8.optimize.MemberRebindingAnalysis.isClassTypeVisibleFromContext;
 
-import com.android.tools.r8.graph.AppInfoWithSubtyping;
+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.DebugLocalInfo;
 import com.android.tools.r8.graph.DexClass;
@@ -62,7 +62,9 @@
 
   @Override
   public Instruction createMaterializingInstruction(
-      AppView<? extends AppInfoWithSubtyping> appView, IRCode code, TypeAndLocalInfoSupplier info) {
+      AppView<? extends AppInfoWithClassHierarchy> appView,
+      IRCode code,
+      TypeAndLocalInfoSupplier info) {
     TypeElement typeLattice = info.getOutType();
     DebugLocalInfo debugLocalInfo = info.getLocalInfo();
     assert typeLattice.isClassType();
@@ -72,18 +74,22 @@
     Value returnedValue =
         code.createValue(classClassType(appView, definitelyNotNull()), debugLocalInfo);
     ConstClass instruction = new ConstClass(returnedValue, type);
-    assert !instruction.instructionMayHaveSideEffects(appView, code.method.holder());
+    assert !instruction.instructionMayHaveSideEffects(appView, code.method().holder());
     return instruction;
   }
 
   @Override
-  public boolean isMaterializableInContext(AppView<?> appView, DexType context) {
+  public boolean isMaterializableInContext(AppView<AppInfoWithLiveness> appView, DexType context) {
     DexType baseType = type.toBaseType(appView.dexItemFactory());
     if (baseType.isClassType()) {
       DexClass clazz = appView.definitionFor(type);
       return clazz != null
           && clazz.isResolvable(appView)
-          && isClassTypeVisibleFromContext(appView, context, clazz);
+          && AccessControl.isClassAccessible(
+                  clazz,
+                  appView.definitionFor(context).asProgramClass(),
+                  appView.options().featureSplitConfiguration)
+              .isTrue();
     }
     assert baseType.isPrimitiveType();
     return true;
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/value/SingleEnumValue.java b/src/main/java/com/android/tools/r8/ir/analysis/value/SingleEnumValue.java
deleted file mode 100644
index 4beb525..0000000
--- a/src/main/java/com/android/tools/r8/ir/analysis/value/SingleEnumValue.java
+++ /dev/null
@@ -1,48 +0,0 @@
-// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-
-package com.android.tools.r8.ir.analysis.value;
-
-import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.DexField;
-import com.android.tools.r8.graph.EnumValueInfoMapCollection.EnumValueInfoMap;
-import com.android.tools.r8.graph.GraphLense;
-import com.android.tools.r8.shaking.AppInfoWithLiveness;
-
-public class SingleEnumValue extends SingleFieldValue {
-
-  /** Intentionally package private, use {@link AbstractValueFactory} instead. */
-  SingleEnumValue(DexField field) {
-    super(field);
-  }
-
-  @Override
-  public boolean isSingleEnumValue() {
-    return true;
-  }
-
-  @Override
-  public SingleEnumValue asSingleEnumValue() {
-    return this;
-  }
-
-  @Override
-  public String toString() {
-    return "SingleEnumValue(" + getField().toSourceString() + ")";
-  }
-
-  @Override
-  public SingleValue rewrittenWithLens(AppView<AppInfoWithLiveness> appView, GraphLense lens) {
-    DexField field = getField();
-    EnumValueInfoMap unboxedEnumInfo = appView.unboxedEnums().getEnumValueInfoMap(field.type);
-    if (unboxedEnumInfo != null) {
-      // Return the ordinal of the unboxed enum.
-      assert unboxedEnumInfo.hasEnumValueInfo(field);
-      return appView
-          .abstractValueFactory()
-          .createSingleNumberValue(unboxedEnumInfo.getEnumValueInfo(field).convertToInt());
-    }
-    return appView.abstractValueFactory().createSingleEnumValue(lens.lookupField(field));
-  }
-}
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 855694a..46261f5 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
@@ -5,14 +5,15 @@
 package com.android.tools.r8.ir.analysis.value;
 
 import static com.android.tools.r8.ir.analysis.type.Nullability.maybeNull;
-import static com.android.tools.r8.optimize.MemberRebindingAnalysis.isMemberVisibleFromOriginalContext;
 
-import com.android.tools.r8.graph.AppInfoWithSubtyping;
+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.DexEncodedField;
 import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.EnumValueInfoMapCollection.EnumValueInfoMap;
 import com.android.tools.r8.graph.GraphLense;
 import com.android.tools.r8.ir.analysis.type.ClassTypeElement;
 import com.android.tools.r8.ir.analysis.type.TypeElement;
@@ -23,11 +24,10 @@
 import com.android.tools.r8.ir.code.Value;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 
-public class SingleFieldValue extends SingleValue {
+public abstract class SingleFieldValue extends SingleValue {
 
-  private final DexField field;
+  final DexField field;
 
-  /** Intentionally package private, use {@link AbstractValueFactory} instead. */
   SingleFieldValue(DexField field) {
     this.field = field;
   }
@@ -36,6 +36,8 @@
     return field;
   }
 
+  public abstract ObjectState getState();
+
   public boolean mayHaveFinalizeMethodDirectlyOrIndirectly(AppView<AppInfoWithLiveness> appView) {
     DexType fieldType = field.type;
     if (fieldType.isClassType()) {
@@ -58,23 +60,16 @@
   }
 
   @Override
-  public boolean equals(Object o) {
-    return this == o;
-  }
+  public abstract boolean equals(Object o);
 
   @Override
-  public int hashCode() {
-    return System.identityHashCode(this);
-  }
-
-  @Override
-  public String toString() {
-    return "SingleFieldValue(" + field.toSourceString() + ")";
-  }
+  public abstract int hashCode();
 
   @Override
   public Instruction createMaterializingInstruction(
-      AppView<? extends AppInfoWithSubtyping> appView, IRCode code, TypeAndLocalInfoSupplier info) {
+      AppView<? extends AppInfoWithClassHierarchy> appView,
+      IRCode code,
+      TypeAndLocalInfoSupplier info) {
     TypeElement type = TypeElement.fromDexType(field.type, maybeNull(), appView);
     assert type.lessThanOrEqual(info.getOutType(), appView);
     Value outValue = code.createValue(type, info.getLocalInfo());
@@ -82,10 +77,13 @@
   }
 
   @Override
-  public boolean isMaterializableInContext(AppView<?> appView, DexType context) {
-    DexEncodedField encodedField = appView.appInfo().resolveField(field);
-    return isMemberVisibleFromOriginalContext(
-        appView, context, encodedField.holder(), encodedField.accessFlags);
+  public boolean isMaterializableInContext(AppView<AppInfoWithLiveness> appView, DexType context) {
+    return AccessControl.isFieldAccessible(
+            appView.appInfo().resolveField(field),
+            appView.definitionForHolder(field),
+            appView.definitionFor(context).asProgramClass(),
+            appView.appInfo())
+        .isTrue();
   }
 
   @Override
@@ -108,9 +106,15 @@
 
   @Override
   public SingleValue rewrittenWithLens(AppView<AppInfoWithLiveness> appView, GraphLense lens) {
-    DexField rewrittenField = lens.lookupField(field);
-    assert !appView.unboxedEnums().containsEnum(field.holder)
-        || !appView.appInfo().resolveField(rewrittenField).accessFlags.isEnum();
-    return appView.abstractValueFactory().createSingleFieldValue(rewrittenField);
+    AbstractValueFactory factory = appView.abstractValueFactory();
+    EnumValueInfoMap unboxedEnumInfo = appView.unboxedEnums().getEnumValueInfoMap(field.type);
+    if (unboxedEnumInfo != null) {
+      // Return the ordinal of the unboxed enum.
+      assert unboxedEnumInfo.hasEnumValueInfo(field);
+      return factory.createSingleNumberValue(
+          unboxedEnumInfo.getEnumValueInfo(field).convertToInt());
+    }
+    return factory.createSingleFieldValue(
+        lens.lookupField(field), getState().rewrittenWithLens(appView, lens));
   }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/value/SingleNumberValue.java b/src/main/java/com/android/tools/r8/ir/analysis/value/SingleNumberValue.java
index e2f12fe..1982181 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/value/SingleNumberValue.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/value/SingleNumberValue.java
@@ -4,7 +4,7 @@
 
 package com.android.tools.r8.ir.analysis.value;
 
-import com.android.tools.r8.graph.AppInfoWithSubtyping;
+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.DexType;
@@ -67,7 +67,9 @@
 
   @Override
   public Instruction createMaterializingInstruction(
-      AppView<? extends AppInfoWithSubtyping> appView, IRCode code, TypeAndLocalInfoSupplier info) {
+      AppView<? extends AppInfoWithClassHierarchy> appView,
+      IRCode code,
+      TypeAndLocalInfoSupplier info) {
     TypeElement typeLattice = info.getOutType();
     DebugLocalInfo debugLocalInfo = info.getLocalInfo();
     assert !typeLattice.isReferenceType() || value == 0;
@@ -78,7 +80,7 @@
   }
 
   @Override
-  public boolean isMaterializableInContext(AppView<?> appView, DexType context) {
+  public boolean isMaterializableInContext(AppView<AppInfoWithLiveness> appView, DexType context) {
     return true;
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/value/SingleStatefulFieldValue.java b/src/main/java/com/android/tools/r8/ir/analysis/value/SingleStatefulFieldValue.java
new file mode 100644
index 0000000..e9b7fde
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/analysis/value/SingleStatefulFieldValue.java
@@ -0,0 +1,44 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.analysis.value;
+
+import com.android.tools.r8.graph.DexField;
+import java.util.Objects;
+
+public class SingleStatefulFieldValue extends SingleFieldValue {
+
+  private final ObjectState state;
+
+  /** Intentionally package private, use {@link AbstractValueFactory} instead. */
+  SingleStatefulFieldValue(DexField field, ObjectState state) {
+    super(field);
+    assert !state.isEmpty();
+    this.state = state;
+  }
+
+  @Override
+  public ObjectState getState() {
+    return state;
+  }
+
+  @Override
+  public String toString() {
+    return "SingleStatefulFieldValue(" + field.toSourceString() + ")";
+  }
+
+  @Override
+  public boolean equals(Object o) {
+    if (getClass() != o.getClass()) {
+      return false;
+    }
+    SingleStatefulFieldValue singleFieldValue = (SingleStatefulFieldValue) o;
+    return field == singleFieldValue.field && state.equals(singleFieldValue.state);
+  }
+
+  @Override
+  public int hashCode() {
+    return Objects.hash(field, state);
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/value/SingleStatelessFieldValue.java b/src/main/java/com/android/tools/r8/ir/analysis/value/SingleStatelessFieldValue.java
new file mode 100644
index 0000000..80de8a3
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/analysis/value/SingleStatelessFieldValue.java
@@ -0,0 +1,39 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.analysis.value;
+
+import com.android.tools.r8.graph.DexField;
+
+public class SingleStatelessFieldValue extends SingleFieldValue {
+
+  /** Intentionally package private, use {@link AbstractValueFactory} instead. */
+  SingleStatelessFieldValue(DexField field) {
+    super(field);
+  }
+
+  @Override
+  public ObjectState getState() {
+    return ObjectState.empty();
+  }
+
+  @Override
+  public String toString() {
+    return "SingleStatelessFieldValue(" + field.toSourceString() + ")";
+  }
+
+  @Override
+  public boolean equals(Object o) {
+    if (getClass() != o.getClass()) {
+      return false;
+    }
+    SingleStatelessFieldValue singleFieldValue = (SingleStatelessFieldValue) o;
+    return field == singleFieldValue.field;
+  }
+
+  @Override
+  public int hashCode() {
+    return field.hashCode();
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/value/SingleStringValue.java b/src/main/java/com/android/tools/r8/ir/analysis/value/SingleStringValue.java
index 1280264..01f80cc 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/value/SingleStringValue.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/value/SingleStringValue.java
@@ -7,7 +7,7 @@
 import static com.android.tools.r8.ir.analysis.type.Nullability.definitelyNotNull;
 import static com.android.tools.r8.ir.analysis.type.TypeElement.stringClassType;
 
-import com.android.tools.r8.graph.AppInfoWithSubtyping;
+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.DexString;
@@ -62,7 +62,7 @@
 
   @Override
   public Instruction createMaterializingInstruction(
-      AppView<? extends AppInfoWithSubtyping> appView,
+      AppView<? extends AppInfoWithClassHierarchy> appView,
       IRCode code,
       TypeAndLocalInfoSupplier info) {
     TypeElement typeLattice = info.getOutType();
@@ -81,7 +81,7 @@
   }
 
   @Override
-  public boolean isMaterializableInContext(AppView<?> appView, DexType context) {
+  public boolean isMaterializableInContext(AppView<AppInfoWithLiveness> appView, DexType context) {
     return true;
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/value/SingleValue.java b/src/main/java/com/android/tools/r8/ir/analysis/value/SingleValue.java
index 1b3e59a..61426a0 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/value/SingleValue.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/value/SingleValue.java
@@ -4,7 +4,7 @@
 
 package com.android.tools.r8.ir.analysis.value;
 
-import com.android.tools.r8.graph.AppInfoWithSubtyping;
+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.GraphLense;
@@ -36,9 +36,12 @@
    * #isMaterializableInContext}.
    */
   public abstract Instruction createMaterializingInstruction(
-      AppView<? extends AppInfoWithSubtyping> appView, IRCode code, TypeAndLocalInfoSupplier info);
+      AppView<? extends AppInfoWithClassHierarchy> appView,
+      IRCode code,
+      TypeAndLocalInfoSupplier info);
 
-  public abstract boolean isMaterializableInContext(AppView<?> appView, DexType context);
+  public abstract boolean isMaterializableInContext(
+      AppView<AppInfoWithLiveness> appView, DexType context);
 
   public abstract boolean isMaterializableInAllContexts(AppView<?> appView);
 
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 0994dc0..a1ed1d7 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
@@ -90,7 +90,7 @@
 
   @Override
   public boolean canBeDeadCode(AppView<?> appView, IRCode code) {
-    return !instructionMayHaveSideEffects(appView, code.method.holder());
+    return !instructionMayHaveSideEffects(appView, code.method().holder());
   }
 
   @Override
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 cd29ec9..37daada 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
@@ -192,7 +192,7 @@
 
   @Override
   public boolean canBeDeadCode(AppView<?> appView, IRCode code) {
-    return !instructionMayHaveSideEffects(appView, code.method.holder());
+    return !instructionMayHaveSideEffects(appView, code.method().holder());
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/Assume.java b/src/main/java/com/android/tools/r8/ir/code/Assume.java
index 9fb9f27..e5316e4 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Assume.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Assume.java
@@ -177,12 +177,12 @@
     if (assumption.isAssumeDynamicType()) {
       outType = asAssumeDynamicType().assumption.getDynamicUpperBoundType();
     }
-    if (appView.appInfo().hasSubtyping()) {
+    if (appView.appInfo().hasLiveness()) {
       if (outType.isClassType()
           && root.getType().isClassType()
           && appView
               .appInfo()
-              .withSubtyping()
+              .withLiveness()
               .inDifferentHierarchy(
                   outType.asClassType().getClassType(),
                   root.getType().asClassType().getClassType())) {
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 4bd58c4..26b0acd 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
@@ -1886,7 +1886,6 @@
     for (int i = 0; i < prevCatchTargets.size(); i++) {
       int prevCatchTarget = prevCatchTargets.get(i);
       DexType prevCatchGuard = prevCatchGuards.get(i);
-      // TODO(sgjesse): Check sub-types of guards. Will require AppInfoWithSubtyping.
       if (newCatchGuards.contains(prevCatchGuard)) {
         continue;
       }
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 32c5602..b4ceb0d 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
@@ -7,7 +7,7 @@
 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.AppInfoWithSubtyping;
+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.DexString;
@@ -206,17 +206,9 @@
   }
 
   @Override
-  public Value insertConstNullInstruction(IRCode code, InternalOptions options) {
-    ConstNumber constNumberInstruction = code.createConstNull();
-    // Note that we only keep position info for throwing instructions in release mode.
-    constNumberInstruction.setPosition(options.debug ? current.getPosition() : Position.none());
-    add(constNumberInstruction);
-    return constNumberInstruction.outValue();
-  }
-
-  @Override
-  public Value insertConstIntInstruction(IRCode code, InternalOptions options, int value) {
-    ConstNumber constNumberInstruction = code.createIntConstant(value);
+  public Value insertConstNumberInstruction(
+      IRCode code, InternalOptions options, long value, TypeElement type) {
+    ConstNumber constNumberInstruction = code.createNumberConstant(value, type);
     // Note that we only keep position info for throwing instructions in release mode.
     constNumberInstruction.setPosition(options.debug ? current.getPosition() : Position.none());
     add(constNumberInstruction);
@@ -280,7 +272,7 @@
 
   @Override
   public void replaceCurrentInstructionWithThrowNull(
-      AppView<? extends AppInfoWithSubtyping> appView,
+      AppView<? extends AppInfoWithClassHierarchy> appView,
       IRCode code,
       ListIterator<BasicBlock> blockIterator,
       Set<BasicBlock> blocksToRemove,
@@ -506,9 +498,9 @@
       Set<BasicBlock> blocksToRemove,
       DexType downcast) {
     assert blocksToRemove != null;
-    DexType codeHolder = code.method.holder();
-    DexType inlineeHolder = inlinee.method.holder();
-    if (codeHolder != inlineeHolder && inlinee.method.isOnlyInlinedIntoNestMembers()) {
+    DexType codeHolder = code.method().holder();
+    DexType inlineeHolder = inlinee.method().holder();
+    if (codeHolder != inlineeHolder && inlinee.method().isOnlyInlinedIntoNestMembers()) {
       // Should rewrite private calls to virtual calls.
       assert NestUtils.sameNest(codeHolder, inlineeHolder, appView);
       NestUtils.rewriteNestCallsForInlining(inlinee, codeHolder, appView);
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 bb5788b..c2d5232 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
@@ -139,7 +139,7 @@
 
   @Override
   public boolean canBeDeadCode(AppView<?> appView, IRCode code) {
-    return !instructionMayHaveSideEffects(appView, code.method.holder());
+    return !instructionMayHaveSideEffects(appView, code.method().holder());
   }
 
   @Override
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 18f8326..888536f 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
@@ -232,10 +232,6 @@
     assert isFieldGet();
     DexEncodedField field = appView.appInfo().resolveField(getField());
     if (field != null) {
-      DexClass holder = appView.definitionFor(field.holder());
-      if (holder != null && holder.isLibraryClass() && field.isStatic() && field.isFinal()) {
-        return appView.abstractValueFactory().createSingleFieldValue(field.field);
-      }
       return field.getOptimizationInfo().getAbstractValue();
     }
     return UnknownValue.getInstance();
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 064842a..53b9336 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
@@ -6,7 +6,7 @@
 import static com.android.tools.r8.ir.analysis.type.Nullability.definitelyNotNull;
 
 import com.android.tools.r8.errors.Unreachable;
-import com.android.tools.r8.graph.AppInfoWithSubtyping;
+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.DexEncodedMethod;
@@ -104,7 +104,7 @@
   // use odd instruction numbers for the insertion of moves during spilling.
   public static final int INSTRUCTION_NUMBER_DELTA = 2;
 
-  public final DexEncodedMethod method;
+  private final DexEncodedMethod method;
 
   public LinkedList<BasicBlock> blocks;
   public final ValueNumberGenerator valueNumberGenerator;
@@ -145,6 +145,10 @@
     return metadata;
   }
 
+  public DexEncodedMethod method() {
+    return method;
+  }
+
   public BasicBlock entryBlock() {
     return blocks.getFirst();
   }
@@ -566,7 +570,8 @@
     return true;
   }
 
-  public boolean hasNoVerticallyMergedClasses(AppView<? extends AppInfoWithSubtyping> appView) {
+  public boolean hasNoVerticallyMergedClasses(
+      AppView<? extends AppInfoWithClassHierarchy> appView) {
     VerticallyMergedClasses verticallyMergedClasses = appView.verticallyMergedClasses();
     if (verticallyMergedClasses == null) {
       return true;
@@ -783,7 +788,7 @@
     for (BasicBlock block : blocks) {
       assert block.consistentBlockInstructions(
           argumentsAllowed,
-          options.debug || method.getOptimizationInfo().isReachabilitySensitive());
+          options.debug || method().getOptimizationInfo().isReachabilitySensitive());
       argumentsAllowed = false;
     }
     return true;
@@ -1060,12 +1065,13 @@
       }
     }
     assert arguments.size()
-        == method.method.getArity() + ((method.accessFlags.isStatic() || ignoreReceiver) ? 0 : 1);
+        == method().method.getArity()
+            + ((method().accessFlags.isStatic() || ignoreReceiver) ? 0 : 1);
     return arguments;
   }
 
   public Value getThis() {
-    if (method.accessFlags.isStatic()) {
+    if (method().accessFlags.isStatic()) {
       return null;
     }
     Instruction firstArg = entryBlock().iterator().nextUntil(Instruction::isArgument);
@@ -1083,14 +1089,20 @@
     return createValue(typeLattice, null);
   }
 
+  public ConstNumber createNumberConstant(long value, TypeElement type) {
+    return createNumberConstant(value, type, null);
+  }
+
+  public ConstNumber createNumberConstant(long value, TypeElement type, DebugLocalInfo local) {
+    return new ConstNumber(createValue(type, local), value);
+  }
+
   public ConstNumber createDoubleConstant(double value, DebugLocalInfo local) {
-    Value out = createValue(TypeElement.getDouble(), local);
-    return new ConstNumber(out, Double.doubleToLongBits(value));
+    return createNumberConstant(Double.doubleToLongBits(value), TypeElement.getDouble(), local);
   }
 
   public ConstNumber createFloatConstant(float value, DebugLocalInfo local) {
-    Value out = createValue(TypeElement.getFloat(), local);
-    return new ConstNumber(out, Float.floatToIntBits(value));
+    return createNumberConstant(Float.floatToIntBits(value), TypeElement.getFloat(), local);
   }
 
   public ConstNumber createIntConstant(int value) {
@@ -1098,13 +1110,11 @@
   }
 
   public ConstNumber createIntConstant(int value, DebugLocalInfo local) {
-    Value out = createValue(TypeElement.getInt(), local);
-    return new ConstNumber(out, value);
+    return createNumberConstant(value, TypeElement.getInt(), local);
   }
 
   public ConstNumber createLongConstant(long value, DebugLocalInfo local) {
-    Value out = createValue(TypeElement.getLong(), local);
-    return new ConstNumber(out, value);
+    return createNumberConstant(value, TypeElement.getLong(), local);
   }
 
   public ConstString createStringConstant(AppView<?> appView, DexString value) {
@@ -1131,13 +1141,11 @@
   }
 
   public ConstNumber createConstNull() {
-    Value out = createValue(TypeElement.getNull());
-    return new ConstNumber(out, 0);
+    return createNumberConstant(0, TypeElement.getNull());
   }
 
   public ConstNumber createConstNull(DebugLocalInfo local) {
-    Value out = createValue(TypeElement.getNull(), local);
-    return new ConstNumber(out, 0);
+    return createNumberConstant(0, TypeElement.getNull(), local);
   }
 
   public boolean doAllThrowingInstructionsHavePositions() {
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 03ca81d..b9cc832 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,11 +5,12 @@
 package com.android.tools.r8.ir.code;
 
 import com.android.tools.r8.errors.Unimplemented;
-import com.android.tools.r8.graph.AppInfoWithSubtyping;
+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.DexString;
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.utils.InternalOptions;
 import java.util.ListIterator;
 import java.util.NoSuchElementException;
@@ -29,13 +30,9 @@
   }
 
   @Override
-  public Value insertConstNullInstruction(IRCode code, InternalOptions options) {
-    return instructionIterator.insertConstNullInstruction(code, options);
-  }
-
-  @Override
-  public Value insertConstIntInstruction(IRCode code, InternalOptions options, int value) {
-    return instructionIterator.insertConstIntInstruction(code, options, value);
+  public Value insertConstNumberInstruction(
+      IRCode code, InternalOptions options, long value, TypeElement type) {
+    return instructionIterator.insertConstNumberInstruction(code, options, value, type);
   }
 
   @Override
@@ -62,7 +59,7 @@
 
   @Override
   public void replaceCurrentInstructionWithThrowNull(
-      AppView<? extends AppInfoWithSubtyping> appView,
+      AppView<? extends AppInfoWithClassHierarchy> appView,
       IRCode code,
       ListIterator<BasicBlock> blockIterator,
       Set<BasicBlock> blocksToRemove,
diff --git a/src/main/java/com/android/tools/r8/ir/code/InstanceGet.java b/src/main/java/com/android/tools/r8/ir/code/InstanceGet.java
index 0740db4..3050da7 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InstanceGet.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InstanceGet.java
@@ -127,7 +127,7 @@
     // * IncompatibleClassChangeError (instance-* instruction for static fields)
     // * IllegalAccessError (not visible from the access context)
     // * NullPointerException (null receiver)
-    return !instructionMayHaveSideEffects(appView, code.method.holder());
+    return !instructionMayHaveSideEffects(appView, code.method().holder());
   }
 
   @Override
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 58bcd28..fd76ad3 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
@@ -142,7 +142,7 @@
     // * IllegalAccessError (not visible from the access context)
     // * NullPointerException (null receiver)
     // * not read at all
-    boolean haveSideEffects = instructionMayHaveSideEffects(appView, code.method.holder());
+    boolean haveSideEffects = instructionMayHaveSideEffects(appView, code.method().holder());
     assert appView.enableWholeProgramOptimizations() || haveSideEffects
         : "Expected instance-put instruction to have side effects in D8";
     return !haveSideEffects;
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 371438e..a546ee3 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,11 +5,12 @@
 package com.android.tools.r8.ir.code;
 
 import com.android.tools.r8.errors.Unimplemented;
-import com.android.tools.r8.graph.AppInfoWithSubtyping;
+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.DexString;
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.utils.InternalOptions;
 import com.google.common.collect.Sets;
 import java.util.ListIterator;
@@ -61,9 +62,17 @@
     // Intentionally empty.
   }
 
-  Value insertConstNullInstruction(IRCode code, InternalOptions options);
+  default Value insertConstNullInstruction(IRCode code, InternalOptions options) {
+    return insertConstNumberInstruction(code, options, 0, TypeElement.getNull());
+  }
 
-  Value insertConstIntInstruction(IRCode code, InternalOptions options, int value);
+  default Value insertConstIntInstruction(IRCode code, InternalOptions options, int value) {
+    return insertConstNumberInstruction(code, options, value, TypeElement.getInt());
+  }
+
+  // This method can be used for any numeric constant, but also for null (value 0, null type).
+  Value insertConstNumberInstruction(
+      IRCode code, InternalOptions options, long value, TypeElement type);
 
   Value insertConstStringInstruction(AppView<?> appView, IRCode code, DexString value);
 
@@ -75,7 +84,7 @@
   /**
    * Replace the current instruction with null throwing instructions.
    *
-   * @param appView with subtype info through which we can test if the guard is subtype of NPE.
+   * @param appView with hierarchy info through which we can test if the guard is subtype of NPE.
    * @param code the IR code for the block this iterator originates from.
    * @param blockIterator basic block iterator used to iterate the blocks.
    * @param blocksToRemove set passed where blocks that were detached from the graph, but not
@@ -87,7 +96,7 @@
    * @param affectedValues set passed where values depending on detached blocks will be added.
    */
   void replaceCurrentInstructionWithThrowNull(
-      AppView<? extends AppInfoWithSubtyping> appView,
+      AppView<? extends AppInfoWithClassHierarchy> appView,
       IRCode code,
       ListIterator<BasicBlock> blockIterator,
       Set<BasicBlock> blocksToRemove,
diff --git a/src/main/java/com/android/tools/r8/ir/code/Invoke.java b/src/main/java/com/android/tools/r8/ir/code/Invoke.java
index 05e80d1..8fad9af 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Invoke.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Invoke.java
@@ -195,12 +195,12 @@
     if (outType.isPrimitiveType()) {
       return false;
     }
-    if (appView.appInfo().hasSubtyping()) {
+    if (appView.appInfo().hasLiveness()) {
       if (outType.isClassType()
           && root.getType().isClassType()
           && appView
               .appInfo()
-              .withSubtyping()
+              .withLiveness()
               .inDifferentHierarchy(
                   outType.asClassType().getClassType(),
                   root.getType().asClassType().getClassType())) {
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeCustom.java b/src/main/java/com/android/tools/r8/ir/code/InvokeCustom.java
index 9ee1988..c3fec64 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokeCustom.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokeCustom.java
@@ -63,11 +63,11 @@
   @Override
   public TypeElement evaluate(AppView<?> appView) {
     TypeElement returnType = super.evaluate(appView);
-    if (!appView.appInfo().hasSubtyping()) {
+    if (!appView.appInfo().hasLiveness()) {
       return returnType;
     }
     List<DexType> lambdaInterfaces =
-        LambdaDescriptor.getInterfaces(callSite, appView.appInfo().withSubtyping());
+        LambdaDescriptor.getInterfaces(callSite, appView.appInfo().withClassHierarchy());
     if (lambdaInterfaces == null || lambdaInterfaces.isEmpty()) {
       return returnType;
     }
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeDirect.java b/src/main/java/com/android/tools/r8/ir/code/InvokeDirect.java
index d6aaf36..e492db1 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokeDirect.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokeDirect.java
@@ -217,7 +217,7 @@
 
   @Override
   public boolean canBeDeadCode(AppView<?> appView, IRCode code) {
-    DexEncodedMethod method = code.method;
+    DexEncodedMethod method = code.method();
     if (instructionMayHaveSideEffects(appView, method.holder())) {
       return false;
     }
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 7c34ec5..78d3a69 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
@@ -104,7 +104,6 @@
           && !appView
               .appInfo()
               .isSubtype(refinedReceiverLowerBound.type, refinedReceiverUpperBound.type)) {
-        // We cannot trust the lower bound.
         refinedReceiverLowerBound = null;
       }
     }
@@ -128,7 +127,7 @@
     }
     Set<DexEncodedMethod> result = Sets.newIdentityHashSet();
     lookupResult.forEach(
-        methodTarget -> result.add(methodTarget.getMethod()),
+        methodTarget -> result.add(methodTarget.getDefinition()),
         lambda -> {
           // TODO(b/150277553): Support lambda targets.
         });
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 a5c4e81..5ed1539 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
@@ -83,6 +83,7 @@
         DexType refinedReceiverType =
             TypeAnalysis.getRefinedReceiverType(appViewWithLiveness, this);
         assert receiverLowerBoundType.getClassType() == refinedReceiverType
+                || appView.options().testing.allowTypeErrors
                 || receiverLowerBoundType.isBasedOnMissingClass(appViewWithLiveness)
                 || upperBoundAssumedByCallSiteOptimizationAndNoLongerInstantiated(
                     appViewWithLiveness, refinedReceiverType, receiverLowerBoundType.getClassType())
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 41fac8c..4006bd7 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
@@ -184,7 +184,7 @@
 
   @Override
   public boolean canBeDeadCode(AppView<?> appView, IRCode code) {
-    return !instructionMayHaveSideEffects(appView, code.method.holder());
+    return !instructionMayHaveSideEffects(appView, code.method().holder());
   }
 
   @Override
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 b285dd6..3f5a1ab 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
@@ -197,7 +197,7 @@
 
   @Override
   public boolean canBeDeadCode(AppView<?> appView, IRCode code) {
-    return !instructionMayHaveSideEffects(appView, code.method.holder());
+    return !instructionMayHaveSideEffects(appView, code.method().holder());
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeStatic.java b/src/main/java/com/android/tools/r8/ir/code/InvokeStatic.java
index 01a6a76..9a9f727 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokeStatic.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokeStatic.java
@@ -215,6 +215,6 @@
 
   @Override
   public boolean canBeDeadCode(AppView<?> appView, IRCode code) {
-    return !instructionMayHaveSideEffects(appView, code.method.holder());
+    return !instructionMayHaveSideEffects(appView, code.method().holder());
   }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeVirtual.java b/src/main/java/com/android/tools/r8/ir/code/InvokeVirtual.java
index 92eb384..b9a0765 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokeVirtual.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokeVirtual.java
@@ -205,6 +205,6 @@
 
   @Override
   public boolean canBeDeadCode(AppView<?> appView, IRCode code) {
-    return !instructionMayHaveSideEffects(appView, code.method.holder());
+    return !instructionMayHaveSideEffects(appView, code.method().holder());
   }
 }
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 15700bf..d73f8df 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,11 +4,12 @@
 
 package com.android.tools.r8.ir.code;
 
-import com.android.tools.r8.graph.AppInfoWithSubtyping;
+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.DexString;
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.utils.InternalOptions;
 import java.util.ListIterator;
 import java.util.Set;
@@ -42,13 +43,9 @@
   }
 
   @Override
-  public Value insertConstNullInstruction(IRCode code, InternalOptions options) {
-    return currentBlockIterator.insertConstNullInstruction(code, options);
-  }
-
-  @Override
-  public Value insertConstIntInstruction(IRCode code, InternalOptions options, int value) {
-    return currentBlockIterator.insertConstIntInstruction(code, options, value);
+  public Value insertConstNumberInstruction(
+      IRCode code, InternalOptions options, long value, TypeElement type) {
+    return currentBlockIterator.insertConstNumberInstruction(code, options, value, type);
   }
 
   @Override
@@ -70,7 +67,7 @@
 
   @Override
   public void replaceCurrentInstructionWithThrowNull(
-      AppView<? extends AppInfoWithSubtyping> appView,
+      AppView<? extends AppInfoWithClassHierarchy> 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 e9cd95f..9f21f17 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
@@ -80,7 +80,8 @@
 
   @Override
   public boolean canBeDeadCode(AppView<?> appView, IRCode code) {
-    return !(appView.options().debug || code.method.getOptimizationInfo().isReachabilitySensitive())
+    return !(appView.options().debug
+            || code.method().getOptimizationInfo().isReachabilitySensitive())
         && appView.options().isGeneratingDex();
   }
 
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 87643df..fd6cf65 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
@@ -151,6 +151,6 @@
 
   @Override
   public boolean canBeDeadCode(AppView<?> appView, IRCode code) {
-    return !instructionMayHaveSideEffects(appView, code.method.holder());
+    return !instructionMayHaveSideEffects(appView, code.method().holder());
   }
 }
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 1090554..e30cdf1 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
@@ -165,7 +165,7 @@
     }
 
     // Verify that the instruction does not lead to an IllegalAccessError.
-    if (appView.appInfo().hasSubtyping()
+    if (appView.appInfo().hasLiveness()
         && !isMemberVisibleFromOriginalContext(
             appView, context, definition.type, definition.accessFlags)) {
       return true;
@@ -196,7 +196,7 @@
 
   @Override
   public boolean canBeDeadCode(AppView<?> appView, IRCode code) {
-    return !instructionMayHaveSideEffects(appView, code.method.holder());
+    return !instructionMayHaveSideEffects(appView, code.method().holder());
   }
 
   public void markNoSpilling() {
diff --git a/src/main/java/com/android/tools/r8/ir/code/Phi.java b/src/main/java/com/android/tools/r8/ir/code/Phi.java
index 26e808f..a9365d8 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Phi.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Phi.java
@@ -6,7 +6,7 @@
 import com.android.tools.r8.cf.TypeVerificationHelper;
 import com.android.tools.r8.errors.CompilationError;
 import com.android.tools.r8.errors.InvalidDebugInfoException;
-import com.android.tools.r8.graph.AppInfoWithSubtyping;
+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.DexMethod;
@@ -407,7 +407,8 @@
   }
 
   @Override
-  public TypeElement getDynamicUpperBoundType(AppView<? extends AppInfoWithSubtyping> appView) {
+  public TypeElement getDynamicUpperBoundType(
+      AppView<? extends AppInfoWithClassHierarchy> appView) {
     Set<Phi> reachablePhis = SetUtils.newIdentityHashSet(this);
     Deque<Phi> worklist = DequeUtils.newArrayDeque(this);
     while (!worklist.isEmpty()) {
diff --git a/src/main/java/com/android/tools/r8/ir/code/StaticGet.java b/src/main/java/com/android/tools/r8/ir/code/StaticGet.java
index 84a86f2..de66cba 100644
--- a/src/main/java/com/android/tools/r8/ir/code/StaticGet.java
+++ b/src/main/java/com/android/tools/r8/ir/code/StaticGet.java
@@ -74,12 +74,12 @@
     if (outType.isPrimitiveType()) {
       return false;
     }
-    if (appView.appInfo().hasSubtyping()) {
+    if (appView.appInfo().hasLiveness()) {
       if (outType.isClassType()
           && root.getType().isClassType()
           && appView
               .appInfo()
-              .withSubtyping()
+              .withLiveness()
               .inDifferentHierarchy(
                   outType.asClassType().getClassType(),
                   root.getType().asClassType().getClassType())) {
@@ -153,7 +153,7 @@
     // * IncompatibleClassChangeError (static-* instruction for instance fields)
     // * IllegalAccessError (not visible from the access context)
     // * side-effects in <clinit>
-    return !instructionMayHaveSideEffects(appView, code.method.holder());
+    return !instructionMayHaveSideEffects(appView, code.method().holder());
   }
 
   @Override
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 7249d94..78d4de9 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
@@ -139,7 +139,7 @@
     // * IllegalAccessError (not visible from the access context)
     // * side-effects in <clinit>
     // * not read _globally_
-    boolean haveSideEffects = instructionMayHaveSideEffects(appView, code.method.holder());
+    boolean haveSideEffects = instructionMayHaveSideEffects(appView, code.method().holder());
     assert appView.enableWholeProgramOptimizations() || haveSideEffects
         : "Expected static-put instruction to have side effects in D8";
     return !haveSideEffects;
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 87ee96f..bbe7f7f 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
@@ -14,7 +14,7 @@
 
 import com.android.tools.r8.dex.Constants;
 import com.android.tools.r8.errors.Unreachable;
-import com.android.tools.r8.graph.AppInfoWithSubtyping;
+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.DexClass;
@@ -1132,7 +1132,8 @@
     return type;
   }
 
-  public TypeElement getDynamicUpperBoundType(AppView<? extends AppInfoWithSubtyping> appView) {
+  public TypeElement getDynamicUpperBoundType(
+      AppView<? extends AppInfoWithClassHierarchy> appView) {
     Value root = getAliasedValue();
     if (root.isPhi()) {
       assert getSpecificAliasedValue(
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/CallGraphBuilderBase.java b/src/main/java/com/android/tools/r8/ir/conversion/CallGraphBuilderBase.java
index 92b23eb..49ea06a 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/CallGraphBuilderBase.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/CallGraphBuilderBase.java
@@ -222,10 +222,11 @@
                     lookupResult
                         .asLookupResultSuccess()
                         .forEach(
-                            methodTarget -> targets.add(methodTarget.getMethod()),
+                            methodTarget -> targets.add(methodTarget.getDefinition()),
                             lambdaTarget ->
                                 // The call target will ultimately be the implementation method.
-                                targets.add(lambdaTarget.getImplementationMethod().getMethod()));
+                                targets.add(
+                                    lambdaTarget.getImplementationMethod().getDefinition()));
                     return targets;
                   }
                 }
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 ae2f9d7..7b15fcc 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
@@ -120,6 +120,7 @@
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Executors;
+import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.function.Consumer;
 import java.util.function.Function;
 import java.util.function.Supplier;
@@ -186,8 +187,9 @@
   private List<Action> onWaveDoneActions = null;
 
   private final List<DexString> neverMergePrefixes;
-  boolean seenNotNeverMergePrefix = false;
-  boolean seenNeverMergePrefix = false;
+  // Use AtomicBoolean to satisfy TSAN checking (see b/153714743).
+  AtomicBoolean seenNotNeverMergePrefix = new AtomicBoolean();
+  AtomicBoolean seenNeverMergePrefix = new AtomicBoolean();
 
   /**
    * The argument `appView` is used to determine if whole program optimizations are allowed or not
@@ -614,12 +616,12 @@
               continue;
             }
             if (method.holder().descriptor.startsWith(neverMergePrefix)) {
-              seenNeverMergePrefix = true;
+              seenNeverMergePrefix.getAndSet(true);
             } else {
-              seenNotNeverMergePrefix = true;
+              seenNotNeverMergePrefix.getAndSet(true);
             }
             // Don't mix.
-            if (seenNeverMergePrefix && seenNotNeverMergePrefix) {
+            if (seenNeverMergePrefix.get() && seenNotNeverMergePrefix.get()) {
               StringBuilder message = new StringBuilder();
               message
                   .append("Merging dex file containing classes with prefix")
@@ -813,7 +815,7 @@
               outliner.applyOutliningCandidate(code);
               printMethod(code, "IR after outlining (SSA)", null);
               removeDeadCodeAndFinalizeIR(
-                  code.method, code, OptimizationFeedbackIgnore.getInstance(), Timing.empty());
+                  code.method(), code, OptimizationFeedbackIgnore.getInstance(), Timing.empty());
             },
             executorService);
         feedback.updateVisibleOptimizationInfo();
@@ -1123,7 +1125,7 @@
   // TODO(b/140766440): Convert all sub steps an implementer of CodeOptimization
   private Timing optimize(
       IRCode code, OptimizationFeedback feedback, MethodProcessor methodProcessor) {
-    DexEncodedMethod method = code.method;
+    DexEncodedMethod method = code.method();
     DexProgramClass holder = asProgramClassOrNull(appView.definitionForHolder(method));
     assert holder != null;
 
@@ -1216,7 +1218,7 @@
 
     if (memberValuePropagation != null) {
       timing.begin("Propagate member values");
-      memberValuePropagation.rewriteWithConstantValues(code, method.holder());
+      memberValuePropagation.run(code);
       timing.end();
     }
 
@@ -1626,7 +1628,7 @@
       timing.end();
     }
 
-    if (appView.appInfo().withLiveness().isPinned(code.method.method)) {
+    if (appView.appInfo().withLiveness().isPinned(code.method().method)) {
       return;
     }
 
@@ -1634,15 +1636,15 @@
     if (method.isInitializer()) {
       if (method.isClassInitializer()) {
         StaticFieldValueAnalysis.run(
-            appView, code, classInitializerDefaultsResult, feedback, code.method, timing);
+            appView, code, classInitializerDefaultsResult, feedback, code.method(), timing);
       } else {
         instanceFieldInitializationInfos =
             InstanceFieldValueAnalysis.run(
-                appView, code, classInitializerDefaultsResult, feedback, code.method, timing);
+                appView, code, classInitializerDefaultsResult, feedback, code.method(), timing);
       }
     }
     methodOptimizationInfoCollector.collectMethodOptimizationInfo(
-        code.method,
+        code.method(),
         code,
         feedback,
         dynamicTypeOptimization,
@@ -1778,11 +1780,11 @@
       return;
     }
     // Only constructors.
-    if (!code.method.isInstanceInitializer()) {
+    if (!code.method().isInstanceInitializer()) {
       return;
     }
     // Only constructors with certain signatures.
-    DexTypeList paramTypes = code.method.method.proto.parameters;
+    DexTypeList paramTypes = code.method().method.proto.parameters;
     if (paramTypes.size() != 3 ||
         paramTypes.values[0] != options.itemFactory.doubleType ||
         paramTypes.values[1] != options.itemFactory.doubleType ||
@@ -1964,7 +1966,7 @@
       printer.end("cfg");
     }
     if (options.extensiveLoggingFilter.size() > 0
-        && options.extensiveLoggingFilter.contains(code.method.method.toSourceString())) {
+        && options.extensiveLoggingFilter.contains(code.method().method.toSourceString())) {
       String current = code.toString();
       System.out.println();
       System.out.println("-----------------------------------------------------------------------");
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 21228a9..e6f5e43 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
@@ -31,7 +31,7 @@
 import static com.android.tools.r8.ir.code.Opcodes.STATIC_PUT;
 
 import com.android.tools.r8.errors.Unreachable;
-import com.android.tools.r8.graph.AppInfoWithSubtyping;
+import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexCallSite;
 import com.android.tools.r8.graph.DexClass;
@@ -100,12 +100,12 @@
 
 public class LensCodeRewriter {
 
-  private final AppView<? extends AppInfoWithSubtyping> appView;
+  private final AppView<? extends AppInfoWithClassHierarchy> appView;
 
   private final EnumUnboxer enumUnboxer;
   private final Map<DexProto, DexProto> protoFixupCache = new ConcurrentHashMap<>();
 
-  LensCodeRewriter(AppView<? extends AppInfoWithSubtyping> appView, EnumUnboxer enumUnboxer) {
+  LensCodeRewriter(AppView<? extends AppInfoWithClassHierarchy> appView, EnumUnboxer enumUnboxer) {
     this.appView = appView;
     this.enumUnboxer = enumUnboxer;
   }
@@ -499,7 +499,7 @@
               if (ret.isReturnVoid()) {
                 break;
               }
-              DexType returnType = code.method.method.proto.returnType;
+              DexType returnType = code.method().method.proto.returnType;
               Value retValue = ret.returnValue();
               DexType initialType =
                   retValue.getType().isPrimitiveType()
@@ -551,10 +551,10 @@
         && initialValue.definition.asConstNumber().isZero()
         && defaultValueHasChanged(oldType, newType)) {
       iterator.previous();
-      // TODO(b/150188380): Add API to insert a const instruction with a type lattice.
-      Value rewrittenDefaultValue = iterator.insertConstIntInstruction(code, appView.options(), 0);
+      Value rewrittenDefaultValue =
+          iterator.insertConstNumberInstruction(
+              code, appView.options(), 0, defaultValueLatticeElement(newType));
       iterator.next();
-      rewrittenDefaultValue.setType(defaultValueLatticeElement(newType));
       return rewrittenDefaultValue;
     }
     return initialValue;
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/TypeConstraintResolver.java b/src/main/java/com/android/tools/r8/ir/conversion/TypeConstraintResolver.java
index 58f7155..8279864 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/TypeConstraintResolver.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/TypeConstraintResolver.java
@@ -152,7 +152,7 @@
                       + ", its imprecise type is: "
                       + stillImprecise.get(0).getType(),
                   code.origin,
-                  new MethodPosition(code.method.method)));
+                  new MethodPosition(code.method().method)));
     }
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/BackportedMethodRewriter.java b/src/main/java/com/android/tools/r8/ir/desugar/BackportedMethodRewriter.java
index 706cfa0..e9aed7f 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/BackportedMethodRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/BackportedMethodRewriter.java
@@ -159,7 +159,7 @@
       if (provider.requiresGenerationOfCode()) {
         DexMethod newMethod = provider.provideMethod(appView);
         methodProviders.putIfAbsent(newMethod, provider);
-        holders.add(code.method.holder());
+        holders.add(code.method().holder());
       }
     }
     if (!affectedValues.isEmpty()) {
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/ClassProcessor.java b/src/main/java/com/android/tools/r8/ir/desugar/ClassProcessor.java
index 82c7c6a..56c1ff5 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/ClassProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/ClassProcessor.java
@@ -300,7 +300,7 @@
       return;
     }
 
-    DexEncodedMethod target = virtualDispatchTarget.getMethod();
+    DexEncodedMethod target = virtualDispatchTarget.getDefinition();
     DexClass targetHolder = virtualDispatchTarget.getHolder();
     // Don-t forward if the target is explicitly marked as 'dont-rewrite'
     if (dontRewrite(targetHolder, target)) {
@@ -330,7 +330,7 @@
       DexClassAndMethod result =
           appView.appInfo().lookupMaximallySpecificMethod(libraryHolder, method);
       if (result != null && rewriter.isEmulatedInterface(result.getHolder().type)) {
-        addForward.accept(result.getHolder(), result.getMethod());
+        addForward.accept(result.getHolder(), result.getDefinition());
       }
     }
   }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryAPIConverter.java b/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryAPIConverter.java
index 12e4c5e..b9913cc 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryAPIConverter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryAPIConverter.java
@@ -106,14 +106,14 @@
 
   public void desugar(IRCode code) {
 
-    if (wrapperSynthesizor.hasSynthesized(code.method.holder())) {
+    if (wrapperSynthesizor.hasSynthesized(code.method().holder())) {
       return;
     }
 
     if (!canGenerateWrappersAndCallbacks()) {
-      assert validateCallbackWasGeneratedInEnqueuer(code.method);
+      assert validateCallbackWasGeneratedInEnqueuer(code.method());
     } else {
-      registerCallbackIfRequired(code.method);
+      registerCallbackIfRequired(code.method());
     }
 
     ListIterator<BasicBlock> blockIterator = code.listIterator();
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryConfiguration.java b/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryConfiguration.java
index baa6317..1590484 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryConfiguration.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryConfiguration.java
@@ -32,6 +32,7 @@
   private final AndroidApiLevel requiredCompilationAPILevel;
   private final boolean libraryCompilation;
   private final String synthesizedLibraryClassesPackagePrefix;
+  private final String identifier;
   private final Map<String, String> rewritePrefix;
   private final Map<DexType, DexType> emulateLibraryInterface;
   private final Map<DexString, Map<DexType, DexType>> retargetCoreLibMember;
@@ -50,6 +51,7 @@
         AndroidApiLevel.B,
         true,
         FALL_BACK_SYNTHESIZED_CLASSES_PACKAGE_PREFIX,
+        "testingOnlyVersion",
         prefix,
         ImmutableMap.of(),
         ImmutableMap.of(),
@@ -64,6 +66,7 @@
         AndroidApiLevel.B,
         false,
         FALL_BACK_SYNTHESIZED_CLASSES_PACKAGE_PREFIX,
+        null,
         ImmutableMap.of(),
         ImmutableMap.of(),
         ImmutableMap.of(),
@@ -77,6 +80,7 @@
       AndroidApiLevel requiredCompilationAPILevel,
       boolean libraryCompilation,
       String packagePrefix,
+      String identifier,
       Map<String, String> rewritePrefix,
       Map<DexType, DexType> emulateLibraryInterface,
       Map<DexString, Map<DexType, DexType>> retargetCoreLibMember,
@@ -87,6 +91,7 @@
     this.requiredCompilationAPILevel = requiredCompilationAPILevel;
     this.libraryCompilation = libraryCompilation;
     this.synthesizedLibraryClassesPackagePrefix = packagePrefix;
+    this.identifier = identifier;
     this.rewritePrefix = rewritePrefix;
     this.emulateLibraryInterface = emulateLibraryInterface;
     this.retargetCoreLibMember = retargetCoreLibMember;
@@ -114,6 +119,10 @@
     return synthesizedLibraryClassesPackagePrefix;
   }
 
+  public String getIdentifier() {
+    return identifier;
+  }
+
   public Map<String, String> getRewritePrefix() {
     return rewritePrefix;
   }
@@ -164,6 +173,7 @@
     private boolean libraryCompilation = false;
     private String synthesizedLibraryClassesPackagePrefix =
         FALL_BACK_SYNTHESIZED_CLASSES_PACKAGE_PREFIX;
+    private String identifier;
     private Map<String, String> rewritePrefix = new HashMap<>();
     private Map<DexType, DexType> emulateLibraryInterface = new HashMap<>();
     private Map<DexString, Map<DexType, DexType>> retargetCoreLibMember = new IdentityHashMap<>();
@@ -177,8 +187,12 @@
     }
 
     public Builder setSynthesizedLibraryClassesPackagePrefix(String prefix) {
-      String replace = prefix.replace('.', '/');
-      this.synthesizedLibraryClassesPackagePrefix = replace;
+      this.synthesizedLibraryClassesPackagePrefix = prefix.replace('.', '/');
+      return this;
+    }
+
+    public Builder setDesugaredLibraryIdentifier(String identifier) {
+      this.identifier = identifier;
       return this;
     }
 
@@ -268,6 +282,7 @@
           requiredCompilationAPILevel,
           libraryCompilation,
           synthesizedLibraryClassesPackagePrefix,
+          identifier,
           ImmutableMap.copyOf(rewritePrefix),
           ImmutableMap.copyOf(emulateLibraryInterface),
           ImmutableMap.copyOf(retargetCoreLibMember),
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryConfigurationParser.java b/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryConfigurationParser.java
index b66432e..a659cc6 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryConfigurationParser.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryConfigurationParser.java
@@ -19,7 +19,7 @@
 
 public class DesugaredLibraryConfigurationParser {
 
-  private static final int MAX_SUPPORTED_VERSION = 3;
+  private static final int MAX_SUPPORTED_VERSION = 4;
 
   private final DesugaredLibraryConfiguration.Builder configurationBuilder;
   private final Reporter reporter;
@@ -53,14 +53,14 @@
     JsonParser parser = new JsonParser();
     JsonObject jsonConfig = parser.parse(jsonConfigString).getAsJsonObject();
 
-    int version = jsonConfig.get("configuration_format_version").getAsInt();
-    if (version > MAX_SUPPORTED_VERSION) {
+    int formatVersion = jsonConfig.get("configuration_format_version").getAsInt();
+    if (formatVersion > MAX_SUPPORTED_VERSION) {
       throw reporter.fatalError(
           new StringDiagnostic(
               "Unsupported desugared library configuration version, please upgrade the D8/R8"
                   + " compiler."));
     }
-    if (version == 1) {
+    if (formatVersion == 1) {
       reporter.warning(
           new StringDiagnostic(
               "You are using an experimental version of the desugared library configuration, "
@@ -68,6 +68,19 @@
                   + "production releases and to fix this warning."));
     }
 
+    String version = jsonConfig.get("version").getAsString();
+    String groupID;
+    String artifactID;
+    if (formatVersion < 4) {
+      groupID = "com.tools.android";
+      artifactID = "desugar_jdk_libs";
+    } else {
+      groupID = jsonConfig.get("group_id").getAsString();
+      artifactID = jsonConfig.get("artifact_id").getAsString();
+    }
+    String identifier = String.join(":", groupID, artifactID, version);
+    configurationBuilder.setDesugaredLibraryIdentifier(identifier);
+
     if (jsonConfig.has("synthesized_library_classes_package_prefix")) {
       configurationBuilder.setSynthesizedLibraryClassesPackagePrefix(
           jsonConfig.get("synthesized_library_classes_package_prefix").getAsString());
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryRetargeter.java b/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryRetargeter.java
index 64825c1..ecf9d2e 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryRetargeter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryRetargeter.java
@@ -135,7 +135,7 @@
             appView
                 .appInfo()
                 .withClassHierarchy()
-                .lookupSuperTarget(invoke.getInvokedMethod(), code.method.holder());
+                .lookupSuperTarget(invoke.getInvokedMethod(), code.method().holder());
         // Final methods can be rewritten as a normal invoke.
         if (dexEncodedMethod != null && !dexEncodedMethod.isFinal()) {
           DexMethod retargetMethod =
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/InterfaceMethodRewriter.java b/src/main/java/com/android/tools/r8/ir/desugar/InterfaceMethodRewriter.java
index 9bdc9b0..6467824 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/InterfaceMethodRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/InterfaceMethodRewriter.java
@@ -315,7 +315,7 @@
               DexEncodedMethod dexEncodedMethod =
                   appView
                       .appInfo()
-                      .lookupSuperTarget(invokeSuper.getInvokedMethod(), code.method.holder());
+                      .lookupSuperTarget(invokeSuper.getInvokedMethod(), code.method().holder());
               if (dexEncodedMethod != null) {
                 DexClass dexClass = appView.definitionFor(dexEncodedMethod.holder());
                 if (dexClass != null && dexClass.isLibraryClass()) {
@@ -393,7 +393,7 @@
                 // This is a invoke-direct call to a virtual method.
                 instructions.replaceCurrentInstruction(
                     new InvokeStatic(
-                        defaultAsMethodOfCompanionClass(virtualTarget.getMethod().method),
+                        defaultAsMethodOfCompanionClass(virtualTarget.getDefinition().method),
                         invokeDirect.outValue(),
                         invokeDirect.arguments()));
               } else {
@@ -448,7 +448,7 @@
       DexClassAndMethod result =
           appView.appInfo().lookupMaximallySpecificMethod(dexClass, invokedMethod);
       if (result != null) {
-        singleTarget = result.getMethod();
+        singleTarget = result.getDefinition();
       }
     }
     if (singleTarget == null) {
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/LambdaClass.java b/src/main/java/com/android/tools/r8/ir/desugar/LambdaClass.java
index 87c3424..88d21d4 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
@@ -592,9 +592,9 @@
     @Override
     DexEncodedMethod ensureAccessibility(boolean allowMethodModification) {
       // We already found the static method to be called, just relax its accessibility.
-      target.getMethod().accessFlags.unsetPrivate();
+      target.getDefinition().accessFlags.unsetPrivate();
       if (target.getHolder().isInterface()) {
-        target.getMethod().accessFlags.setPublic();
+        target.getDefinition().accessFlags.setPublic();
       }
       return null;
     }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/LambdaDescriptor.java b/src/main/java/com/android/tools/r8/ir/desugar/LambdaDescriptor.java
index 2ac43f0..bfc9362 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/LambdaDescriptor.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/LambdaDescriptor.java
@@ -253,24 +253,25 @@
     return descriptor == MATCH_FAILED ? null : descriptor;
   }
 
+  public static boolean isLambdaMetafactoryMethod(DexCallSite callSite, DexItemFactory factory) {
+    if (!callSite.bootstrapMethod.type.isInvokeStatic()) {
+      return false;
+    }
+    return factory.isLambdaMetafactoryMethod(callSite.bootstrapMethod.asMethod());
+  }
+
   /**
    * Matches call site for lambda metafactory invocation pattern and returns extracted match
    * information, or MATCH_FAILED if match failed.
    */
   static LambdaDescriptor infer(
       DexCallSite callSite, AppInfoWithClassHierarchy appInfo, DexType invocationContext) {
-    // We expect bootstrap method to be either `metafactory` or `altMetafactory` method
-    // of `java.lang.invoke.LambdaMetafactory` class. Both methods are static.
-    if (!callSite.bootstrapMethod.type.isInvokeStatic()) {
+    if (!isLambdaMetafactoryMethod(callSite, appInfo.dexItemFactory())) {
       return LambdaDescriptor.MATCH_FAILED;
     }
 
     DexItemFactory factory = appInfo.dexItemFactory();
     DexMethod bootstrapMethod = callSite.bootstrapMethod.asMethod();
-    if (!factory.isLambdaMetafactoryMethod(bootstrapMethod)) {
-      // It is not a lambda, thus no need to manage this call site.
-      return LambdaDescriptor.MATCH_FAILED;
-    }
 
     // 'Method name' operand of the invoke-custom instruction represents
     // the name of the functional interface main method.
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/PrefixRewritingMapper.java b/src/main/java/com/android/tools/r8/ir/desugar/PrefixRewritingMapper.java
index 6f5c3f2..9624c2a 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/PrefixRewritingMapper.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/PrefixRewritingMapper.java
@@ -167,7 +167,7 @@
     }
 
     private DexType lookup(DexType type, DexString prefixToMatch, Map<DexString, DexString> map) {
-      // TODO: We could use tries instead of looking-up everywhere.
+      // TODO(b/154800164): We could use tries instead of looking-up everywhere.
       for (DexString prefix : map.keySet()) {
         if (prefixToMatch.startsWith(prefix)) {
           DexString rewrittenTypeDescriptor =
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/TwrCloseResourceRewriter.java b/src/main/java/com/android/tools/r8/ir/desugar/TwrCloseResourceRewriter.java
index e477fe3..6f3aed5 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/TwrCloseResourceRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/TwrCloseResourceRewriter.java
@@ -99,7 +99,7 @@
           new InvokeStatic(twrCloseResourceMethod, null, invoke.inValues()));
 
       // Mark as a class referencing utility class.
-      referencingClasses.add(appInfo.definitionFor(code.method.holder()).asProgramClass());
+      referencingClasses.add(appInfo.definitionFor(code.method().holder()).asProgramClass());
     }
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/CallSiteOptimizationInfoPropagator.java b/src/main/java/com/android/tools/r8/ir/optimize/CallSiteOptimizationInfoPropagator.java
index 35895b7..312ae5d 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/CallSiteOptimizationInfoPropagator.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/CallSiteOptimizationInfoPropagator.java
@@ -6,8 +6,10 @@
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexMethodHandle;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.ResolutionResult;
+import com.android.tools.r8.graph.ResolutionResult.SingleResolutionResult;
 import com.android.tools.r8.ir.analysis.type.TypeAnalysis;
 import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.analysis.value.AbstractValue;
@@ -19,10 +21,12 @@
 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.InvokeCustom;
 import com.android.tools.r8.ir.code.InvokeMethod;
 import com.android.tools.r8.ir.code.Value;
 import com.android.tools.r8.ir.conversion.CodeOptimization;
 import com.android.tools.r8.ir.conversion.PostOptimization;
+import com.android.tools.r8.ir.desugar.LambdaDescriptor;
 import com.android.tools.r8.ir.optimize.info.CallSiteOptimizationInfo;
 import com.android.tools.r8.ir.optimize.info.ConcreteCallSiteOptimizationInfo;
 import com.android.tools.r8.logging.Log;
@@ -76,7 +80,7 @@
     if (mode != Mode.COLLECT) {
       return;
     }
-    DexEncodedMethod context = code.method;
+    DexEncodedMethod context = code.method();
     for (Instruction instruction : code.instructions()) {
       if (!instruction.isInvokeMethod() && !instruction.isInvokeCustom()) {
         continue;
@@ -109,18 +113,28 @@
           recordArgumentsIfNecessary(target, invoke.inValues());
         }
       }
-      // TODO(b/129458850): if lambda desugaring happens before IR processing, seeing invoke-custom
-      //  means we can't find matched methods in the app, hence safe to ignore (only for DEX).
       if (instruction.isInvokeCustom()) {
-        // Conservatively register argument info for all possible lambda implemented methods.
-        Collection<DexEncodedMethod> targets =
-            appView.appInfo().lookupLambdaImplementedMethods(
-                instruction.asInvokeCustom().getCallSite());
-        if (targets == null || targets.isEmpty() || hasLibraryOverrides(targets)) {
+        InvokeCustom invokeCustom = instruction.asInvokeCustom();
+        // The bootstrap method for lambda allocation is always runtime internal.
+        if (LambdaDescriptor.isLambdaMetafactoryMethod(
+            invokeCustom.getCallSite(), appView.dexItemFactory())) {
           continue;
         }
-        for (DexEncodedMethod target : targets) {
-          recordArgumentsIfNecessary(target, instruction.inValues());
+        // In other cases, if the bootstrap method is program declared it will be called. The call
+        // is with runtime provided arguments so ensure that the call-site info is TOP.
+        DexMethodHandle bootstrapMethod = invokeCustom.getCallSite().bootstrapMethod;
+        SingleResolutionResult resolution =
+            appView
+                .appInfo()
+                .resolveMethod(
+                    bootstrapMethod.asMethod().holder,
+                    bootstrapMethod.asMethod(),
+                    bootstrapMethod.isInterface)
+                .asSingleResolution();
+        if (resolution != null && resolution.getResolvedHolder().isProgramClass()) {
+          resolution
+              .getResolvedMethod()
+              .joinCallSiteOptimizationInfo(CallSiteOptimizationInfo.TOP, appView);
         }
       }
     }
@@ -208,7 +222,7 @@
       return;
     }
     // TODO(b/139246447): Assert no BOTTOM left.
-    if (!callSiteOptimizationInfo.hasUsefulOptimizationInfo(appView, code.method)) {
+    if (!callSiteOptimizationInfo.hasUsefulOptimizationInfo(appView, code.method())) {
       return;
     }
     Set<Value> affectedValues = Sets.newIdentityHashSet();
@@ -231,7 +245,7 @@
       if (abstractValue.isSingleValue()) {
         assert appView.options().enablePropagationOfConstantsAtCallSites;
         SingleValue singleValue = abstractValue.asSingleValue();
-        if (singleValue.isMaterializableInContext(appView, code.method.holder())) {
+        if (singleValue.isMaterializableInContext(appView, code.method().holder())) {
           Instruction replacement =
               singleValue.createMaterializingInstruction(appView, code, instr);
           replacement.setPosition(instr.getPosition());
@@ -282,9 +296,14 @@
         }
       }
     }
-    assert argumentsSeen == code.method.method.getArity() + (code.method.isStatic() ? 0 : 1)
-        : "args: " + argumentsSeen + " != "
-            + "arity: " + code.method.method.getArity() + ", static: " + code.method.isStatic();
+    assert argumentsSeen == code.method().method.getArity() + (code.method().isStatic() ? 0 : 1)
+        : "args: "
+            + argumentsSeen
+            + " != "
+            + "arity: "
+            + code.method().method.getArity()
+            + ", static: "
+            + code.method().isStatic();
     // After packed Argument instructions, add Assume<?> and constant instructions.
     assert !iterator.peekPrevious().isArgument();
     iterator.previous();
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 eb36e39..3dbe05d 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
@@ -15,6 +15,7 @@
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DebugLocalInfo;
 import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexEncodedField;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexItemFactory.ThrowableMethods;
@@ -27,6 +28,8 @@
 import com.android.tools.r8.ir.analysis.type.TypeAnalysis;
 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.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;
@@ -75,6 +78,7 @@
 import com.android.tools.r8.ir.conversion.IRConverter;
 import com.android.tools.r8.ir.optimize.controlflow.SwitchCaseAnalyzer;
 import com.android.tools.r8.ir.regalloc.LinearScanRegisterAllocator;
+import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.InternalOutputMode;
 import com.android.tools.r8.utils.LongInterval;
@@ -250,7 +254,10 @@
         // Check for the patterns 'if (x == null) throw null' and
         // 'if (x == null) throw new NullPointerException()'.
         if (instruction.isIf()) {
-          if (appView.dexItemFactory().objectsMethods.isRequireNonNullMethod(code.method.method)) {
+          if (appView
+              .dexItemFactory()
+              .objectsMethods
+              .isRequireNonNullMethod(code.method().method)) {
             continue;
           }
 
@@ -305,7 +312,7 @@
 
           boolean canDetachValueIsNullTarget = true;
           for (Instruction i : valueIsNullTarget.instructionsBefore(throwInstruction)) {
-            if (!i.isBlockLocalInstructionWithoutSideEffects(appView, code.method.holder())) {
+            if (!i.isBlockLocalInstructionWithoutSideEffects(appView, code.method().holder())) {
               canDetachValueIsNullTarget = false;
               break;
             }
@@ -511,7 +518,7 @@
     int selfRecursionFanOut = 0;
     Instruction lastSelfRecursiveCall = null;
     for (Instruction i : code.instructions()) {
-      if (i.isInvokeMethod() && i.asInvokeMethod().getInvokedMethod() == code.method.method) {
+      if (i.isInvokeMethod() && i.asInvokeMethod().getInvokedMethod() == code.method().method) {
         selfRecursionFanOut++;
         lastSelfRecursiveCall = i;
       }
@@ -1126,7 +1133,7 @@
     BasicBlock defaultTarget = theSwitch.fallthroughBlock();
     SwitchCaseEliminator eliminator = null;
     BasicBlockBehavioralSubsumption behavioralSubsumption =
-        new BasicBlockBehavioralSubsumption(appView, code.method.holder());
+        new BasicBlockBehavioralSubsumption(appView, code.method().holder());
 
     // Compute the set of switch cases that can be removed.
     int alwaysHitCase = -1;
@@ -1251,7 +1258,7 @@
         }
 
         // Check if the invoked method is known to return one of its arguments.
-        DexEncodedMethod target = invoke.lookupSingleTarget(appView, code.method.holder());
+        DexEncodedMethod target = invoke.lookupSingleTarget(appView, code.method().holder());
         if (target != null && target.getOptimizationInfo().returnsArgument()) {
           int argumentIndex = target.getOptimizationInfo().getReturnedArgument();
           // Replace the out value of the invoke with the argument and ignore the out value.
@@ -1373,7 +1380,7 @@
     // If the cast type is not accessible in the current context, we should not remove the cast
     // in order to preserve IllegalAccessError. Note that JVM and ART behave differently: see
     // {@link com.android.tools.r8.ir.optimize.checkcast.IllegalAccessErrorTest}.
-    if (!isTypeVisibleFromContext(appView, code.method.holder(), castType)) {
+    if (!isTypeVisibleFromContext(appView, code.method().holder(), castType)) {
       return RemoveCheckCastInstructionIfTrivialResult.NO_REMOVALS;
     }
 
@@ -1430,7 +1437,7 @@
       InstanceOf instanceOf, InstructionListIterator it, IRCode code) {
     // If the instance-of type is not accessible in the current context, we should not remove the
     // instance-of instruction in order to preserve IllegalAccessError.
-    if (!isTypeVisibleFromContext(appView, code.method.holder(), instanceOf.type())) {
+    if (!isTypeVisibleFromContext(appView, code.method().holder(), instanceOf.type())) {
       return false;
     }
 
@@ -2357,6 +2364,7 @@
                 if (dominatorTree.dominatedBy(block, candidate.definition.getBlock())
                     && shareCatchHandlers(instruction, candidate.definition)) {
                   instruction.outValue().replaceUsers(candidate);
+                  candidate.uniquePhiUsers().forEach(Phi::removeTrivialPhi);
                   eliminated = true;
                   iterator.removeOrReplaceByDebugLocalRead();
                   break;  // Don't try any more candidates.
@@ -2497,14 +2505,39 @@
               }
             }
           } else {
-            DexType context = code.method.holder();
+            DexType context = code.method().holder();
             AbstractValue abstractValue = lhs.getAbstractValue(appView, context);
-            if (abstractValue.isSingleConstClassValue() || abstractValue.isSingleFieldValue()) {
+            if (abstractValue.isSingleConstClassValue()) {
               AbstractValue otherAbstractValue = rhs.getAbstractValue(appView, context);
-              if (abstractValue == otherAbstractValue) {
-                simplifyIfWithKnownCondition(code, block, theIf, 0);
-              } else if (otherAbstractValue.isSingleEnumValue()) {
-                simplifyIfWithKnownCondition(code, block, theIf, 1);
+              if (otherAbstractValue.isSingleConstClassValue()) {
+                SingleConstClassValue singleConstClassValue =
+                    abstractValue.asSingleConstClassValue();
+                SingleConstClassValue otherSingleConstClassValue =
+                    otherAbstractValue.asSingleConstClassValue();
+                simplifyIfWithKnownCondition(
+                    code,
+                    block,
+                    theIf,
+                    BooleanUtils.intValue(
+                        singleConstClassValue.getType() != otherSingleConstClassValue.getType()));
+              }
+            } else if (abstractValue.isSingleFieldValue()) {
+              AbstractValue otherAbstractValue = rhs.getAbstractValue(appView, context);
+              if (otherAbstractValue.isSingleFieldValue()) {
+                SingleFieldValue singleFieldValue = abstractValue.asSingleFieldValue();
+                SingleFieldValue otherSingleFieldValue = otherAbstractValue.asSingleFieldValue();
+                if (singleFieldValue.getField() == otherSingleFieldValue.getField()) {
+                  simplifyIfWithKnownCondition(code, block, theIf, 0);
+                } else {
+                  DexEncodedField field = appView.definitionFor(singleFieldValue.getField());
+                  if (field != null && field.isEnum()) {
+                    DexEncodedField otherField =
+                        appView.definitionFor(otherSingleFieldValue.getField());
+                    if (otherField != null && otherField.isEnum()) {
+                      simplifyIfWithKnownCondition(code, block, theIf, 1);
+                    }
+                  }
+                }
               }
             }
           }
@@ -2789,7 +2822,7 @@
 
         InvokeMethod invoke = insn.asInvokeMethod();
         DexEncodedMethod singleTarget =
-            invoke.lookupSingleTarget(appView.withLiveness(), code.method.holder());
+            invoke.lookupSingleTarget(appView.withLiveness(), code.method().holder());
         if (singleTarget == null || !singleTarget.getOptimizationInfo().neverReturnsNormally()) {
           continue;
         }
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 c98c262..691de34 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
@@ -87,7 +87,7 @@
   }
 
   public void canonicalize(AppView<?> appView, IRCode code) {
-    DexEncodedMethod method = code.method;
+    DexEncodedMethod method = code.method();
     DexType context = method.holder();
     Object2ObjectLinkedOpenCustomHashMap<Instruction, List<Value>> valuesDefinedByConstant =
         new Object2ObjectLinkedOpenCustomHashMap<>(
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java b/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java
index d9773d9..db75756 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java
@@ -3,6 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.ir.optimize;
 
+import static com.android.tools.r8.graph.DexProgramClass.asProgramClassOrNull;
 import static com.android.tools.r8.ir.optimize.inliner.InlinerUtils.addMonitorEnterValue;
 import static com.android.tools.r8.ir.optimize.inliner.InlinerUtils.collectAllMonitorEnterValues;
 
@@ -170,8 +171,12 @@
     if (options.featureSplitConfiguration != null
         && !options.featureSplitConfiguration.inSameFeatureOrBase(
             singleTarget.method, method.method)) {
-      whyAreYouNotInliningReporter.reportInliningAcrossFeatureSplit();
-      return false;
+      // Still allow inlining if we inline from the base into a feature.
+      DexClass clazz = asProgramClassOrNull(appView.definitionFor(singleTarget.method.holder));
+      if (!options.featureSplitConfiguration.isInBase(clazz.asProgramClass())) {
+        whyAreYouNotInliningReporter.reportInliningAcrossFeatureSplit();
+        return false;
+      }
     }
 
     Set<Reason> validInliningReasons = options.testing.validInliningReasons;
@@ -446,7 +451,7 @@
     // Allow inlining a constructor into a constructor of the same class, as the constructor code
     // is expected to adhere to the VM specification.
     DexType callerMethodHolder = method.holder();
-    DexType calleeMethodHolder = inlinee.method.holder();
+    DexType calleeMethodHolder = inlinee.method().holder();
     // Calling a constructor on the same class from a constructor can always be inlined.
     if (method.isInstanceInitializer() && callerMethodHolder == calleeMethodHolder) {
       return true;
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/DynamicTypeOptimization.java b/src/main/java/com/android/tools/r8/ir/optimize/DynamicTypeOptimization.java
index d518ef6..eac97c1 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/DynamicTypeOptimization.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/DynamicTypeOptimization.java
@@ -78,7 +78,8 @@
               TypeElement.fromDexType(invokedMethod.holder, definitelyNotNull(), appView);
           dynamicLowerBoundType = null;
         } else {
-          DexEncodedMethod singleTarget = invoke.lookupSingleTarget(appView, code.method.holder());
+          DexEncodedMethod singleTarget =
+              invoke.lookupSingleTarget(appView, code.method().holder());
           if (singleTarget == null) {
             continue;
           }
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 76ca615..62b5ab3 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
@@ -110,7 +110,7 @@
               }
             });
 
-    DexType context = code.method.holder();
+    DexType context = code.method().holder();
     // Collect invocations along with arguments.
     for (BasicBlock block : code.blocks) {
       for (Instruction current : block.getInstructions()) {
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 d579a34..fdb59ae 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
@@ -9,7 +9,7 @@
 import com.android.tools.r8.androidapi.AvailableApiExceptions;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.AccessFlags;
-import com.android.tools.r8.graph.AppInfoWithSubtyping;
+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.DexEncodedMethod;
@@ -22,6 +22,7 @@
 import com.android.tools.r8.ir.analysis.ClassInitializationAnalysis;
 import com.android.tools.r8.ir.analysis.proto.ProtoInliningReasonStrategy;
 import com.android.tools.r8.ir.analysis.type.Nullability;
+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.CatchHandlers.CatchHandler;
@@ -60,6 +61,7 @@
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.IteratorUtils;
 import com.android.tools.r8.utils.ListUtils;
+import com.android.tools.r8.utils.SetUtils;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Sets;
@@ -596,7 +598,7 @@
     }
 
     InlineeWithReason buildInliningIR(
-        AppView<? extends AppInfoWithSubtyping> appView,
+        AppView<? extends AppInfoWithClassHierarchy> appView,
         InvokeMethod invoke,
         DexEncodedMethod context,
         InliningIRProvider inliningIRProvider,
@@ -737,7 +739,7 @@
         }
       }
 
-      if (inliningIRProvider.shouldApplyCodeRewritings(code.method)) {
+      if (inliningIRProvider.shouldApplyCodeRewritings(code.method())) {
         assert lensCodeRewriter != null;
         lensCodeRewriter.rewrite(code, target);
       }
@@ -1043,13 +1045,14 @@
           }
 
           classInitializationAnalysis.notifyCodeHasChanged();
-          applyAssumersToInlinee(code, inlinee.code, blockIterator, block);
+          postProcessInlineeBlocks(code, inlinee.code, blockIterator, block);
 
           // The synthetic and bridge flags are maintained only if the inlinee has also these flags.
-          if (context.accessFlags.isBridge() && !inlinee.code.method.accessFlags.isBridge()) {
+          if (context.accessFlags.isBridge() && !inlinee.code.method().accessFlags.isBridge()) {
             context.accessFlags.demoteFromBridge();
           }
-          if (context.accessFlags.isSynthetic() && !inlinee.code.method.accessFlags.isSynthetic()) {
+          if (context.accessFlags.isSynthetic()
+              && !inlinee.code.method().accessFlags.isSynthetic()) {
             context.accessFlags.demoteFromSynthetic();
           }
 
@@ -1117,62 +1120,87 @@
     return null;
   }
 
-  private void applyAssumersToInlinee(
+  /** Applies member rebinding to the inlinee and inserts assume instructions. */
+  private void postProcessInlineeBlocks(
       IRCode code, IRCode inlinee, ListIterator<BasicBlock> blockIterator, BasicBlock block) {
-    boolean assumersEnabled =
-        appView.options().enableNonNullTracking
-            || appView.options().enableDynamicTypeOptimization
-            || appView.options().testing.forceAssumeNoneInsertion;
-    if (assumersEnabled) {
-      BasicBlock state = IteratorUtils.peekNext(blockIterator);
-
-      Set<BasicBlock> inlineeBlocks = Sets.newIdentityHashSet();
-      inlineeBlocks.addAll(inlinee.blocks);
-
-      // Introduce aliases only to the inlinee blocks.
-      if (appView.options().testing.forceAssumeNoneInsertion) {
-        applyAssumerToInlinee(
-            new AliasIntroducer(appView), code, block, blockIterator, inlineeBlocks);
-      }
-
-      // Add non-null IRs only to the inlinee blocks.
-      if (appView.options().enableNonNullTracking) {
-        Consumer<BasicBlock> splitBlockConsumer = inlineeBlocks::add;
-        Assumer nonNullTracker = new NonNullTracker(appView, splitBlockConsumer);
-        applyAssumerToInlinee(nonNullTracker, code, block, blockIterator, inlineeBlocks);
-      }
-
-      // Add dynamic type assumptions only to the inlinee blocks.
-      if (appView.options().enableDynamicTypeOptimization) {
-        applyAssumerToInlinee(
-            new DynamicTypeOptimization(appView), code, block, blockIterator, inlineeBlocks);
-      }
-
-      // Restore the old state of the iterator.
-      while (blockIterator.hasPrevious() && blockIterator.previous() != state) {
-        // Do nothing.
-      }
-      assert IteratorUtils.peekNext(blockIterator) == state;
+    InternalOptions options = appView.options();
+    boolean skip =
+        !(options.enableDynamicTypeOptimization
+            || options.enableNonNullTracking
+            || options.enableValuePropagation
+            || options.testing.forceAssumeNoneInsertion);
+    if (skip) {
+      return;
     }
+
+    BasicBlock state = IteratorUtils.peekNext(blockIterator);
+
+    Set<BasicBlock> inlineeBlocks = SetUtils.newIdentityHashSet(inlinee.blocks);
+
+    // Run member value propagation on the inlinee blocks.
+    if (options.enableValuePropagation) {
+      rewindBlockIteratorToFirstInlineeBlock(blockIterator, block);
+      applyMemberValuePropagationToInlinee(code, blockIterator, block, inlineeBlocks);
+    }
+
+    // Introduce aliases only to the inlinee blocks.
+    if (options.testing.forceAssumeNoneInsertion) {
+      applyAssumerToInlinee(
+          new AliasIntroducer(appView), code, blockIterator, block, inlineeBlocks);
+    }
+
+    // Add non-null IRs only to the inlinee blocks.
+    if (options.enableNonNullTracking) {
+      Consumer<BasicBlock> splitBlockConsumer = inlineeBlocks::add;
+      Assumer nonNullTracker = new NonNullTracker(appView, splitBlockConsumer);
+      applyAssumerToInlinee(nonNullTracker, code, blockIterator, block, inlineeBlocks);
+    }
+
+    // Add dynamic type assumptions only to the inlinee blocks.
+    if (options.enableDynamicTypeOptimization) {
+      applyAssumerToInlinee(
+          new DynamicTypeOptimization(appView), code, blockIterator, block, inlineeBlocks);
+    }
+    // Restore the old state of the iterator.
+    rewindBlockIteratorToFirstInlineeBlock(blockIterator, state);
     // TODO(b/72693244): need a test where refined env in inlinee affects the caller.
   }
 
   private void applyAssumerToInlinee(
       Assumer assumer,
       IRCode code,
-      BasicBlock block,
       ListIterator<BasicBlock> blockIterator,
+      BasicBlock block,
       Set<BasicBlock> inlineeBlocks) {
-    // Move the cursor back to where the first inlinee block was added.
-    while (blockIterator.hasPrevious() && blockIterator.previous() != block) {
-      // Do nothing.
-    }
-    assert IteratorUtils.peekNext(blockIterator) == block;
-
+    rewindBlockIteratorToFirstInlineeBlock(blockIterator, block);
     assumer.insertAssumeInstructionsInBlocks(code, blockIterator, inlineeBlocks::contains);
     assert !blockIterator.hasNext();
   }
 
+  private void applyMemberValuePropagationToInlinee(
+      IRCode code,
+      ListIterator<BasicBlock> blockIterator,
+      BasicBlock block,
+      Set<BasicBlock> inlineeBlocks) {
+    assert IteratorUtils.peekNext(blockIterator) == block;
+    Set<Value> affectedValues = Sets.newIdentityHashSet();
+    new MemberValuePropagation(appView)
+        .run(code, blockIterator, affectedValues, inlineeBlocks::contains);
+    if (!affectedValues.isEmpty()) {
+      new TypeAnalysis(appView).narrowing(affectedValues);
+    }
+    assert !blockIterator.hasNext();
+  }
+
+  private void rewindBlockIteratorToFirstInlineeBlock(
+      ListIterator<BasicBlock> blockIterator, BasicBlock firstInlineeBlock) {
+    // Move the cursor back to where the first inlinee block was added.
+    while (blockIterator.hasPrevious() && blockIterator.previous() != firstInlineeBlock) {
+      // Do nothing.
+    }
+    assert IteratorUtils.peekNext(blockIterator) == firstInlineeBlock;
+  }
+
   public static boolean verifyNoMethodsInlinedDueToSingleCallSite(AppView<?> appView) {
     for (DexProgramClass clazz : appView.appInfo().classes()) {
       for (DexEncodedMethod method : clazz.methods()) {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/MemberPoolCollection.java b/src/main/java/com/android/tools/r8/ir/optimize/MemberPoolCollection.java
index 46ac1ce..4f699e6 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/MemberPoolCollection.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/MemberPoolCollection.java
@@ -111,7 +111,6 @@
 
   abstract Runnable computeMemberPoolForClass(DexClass clazz);
 
-  // TODO(jsjeon): maybe be part of AppInfoWithSubtyping?
   private Set<DexClass> getAllSuperTypesInclusive(
       DexClass subject, Predicate<DexClass> stoppingCriterion) {
     Set<DexClass> superTypes = new HashSet<>();
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 bf7aa9b..e6dcbbe 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
@@ -5,6 +5,7 @@
 
 import static com.android.tools.r8.graph.DexProgramClass.asProgramClassOrNull;
 import static com.android.tools.r8.ir.analysis.type.Nullability.maybeNull;
+import static com.google.common.base.Predicates.alwaysTrue;
 
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexDefinition;
@@ -45,6 +46,7 @@
 import com.google.common.collect.Sets;
 import java.util.ListIterator;
 import java.util.Set;
+import java.util.function.Predicate;
 
 public class MemberValuePropagation {
 
@@ -209,7 +211,7 @@
       if (current.isStaticGet()) {
         StaticGet staticGet = current.asStaticGet();
         replaceInstructionByInitClassIfPossible(
-            staticGet, staticGet.getField().holder, code, iterator, code.method.holder());
+            staticGet, staticGet.getField().holder, code, iterator, code.method().holder());
       }
       replacement.setPosition(position);
       if (block.hasCatchHandlers()) {
@@ -359,34 +361,65 @@
       return;
     }
 
-    // Check if a this value is known const.
-    Instruction replacement =
-        target.valueAsConstInstruction(code, current.outValue().getLocalInfo(), appView);
-    if (replacement != null) {
-      BasicBlock block = current.getBlock();
-      DexType context = code.method.holder();
-      Position position = current.getPosition();
-
-      // All usages are replaced by the replacement value.
-      affectedValues.addAll(current.outValue().affectedValues());
-      current.outValue().replaceUsers(replacement.outValue());
-
-      // To preserve side effects, original field-get is replaced by an explicit null-check, if
-      // the field-get instruction may only fail with an NPE, or the field-get remains as-is.
-      if (current.isInstanceGet()) {
-        replaceInstructionByNullCheckIfPossible(current, iterator, context);
-      } else {
-        replaceInstructionByInitClassIfPossible(current, target.holder(), code, iterator, context);
+    AbstractValue abstractValue;
+    if (appView.appInfo().isFieldWrittenByFieldPutInstruction(target)) {
+      abstractValue = target.getOptimizationInfo().getAbstractValue();
+      if (abstractValue.isUnknown() && !target.isStatic()) {
+        AbstractValue abstractReceiverValue =
+            current.asInstanceGet().object().getAbstractValue(appView, code.method().holder());
+        if (abstractReceiverValue.isSingleFieldValue()) {
+          abstractValue =
+              abstractReceiverValue.asSingleFieldValue().getState().getAbstractFieldValue(target);
+        }
       }
+    } else if (target.isStatic()) {
+      // This is guaranteed to read the static value of the field.
+      abstractValue = target.getStaticValue().toAbstractValue(appView.abstractValueFactory());
+      // Verify that the optimization info is consistent with the static value.
+      assert target.getOptimizationInfo().getAbstractValue().isUnknown()
+          || !target.hasExplicitStaticValue()
+          || abstractValue == target.getOptimizationInfo().getAbstractValue();
+    } else {
+      // This is guaranteed to read the default value of the field.
+      abstractValue = appView.abstractValueFactory().createSingleNumberValue(0);
+    }
 
-      // Insert the definition of the replacement.
-      replacement.setPosition(position);
-      if (block.hasCatchHandlers()) {
-        iterator.split(code, blocks).listIterator(code).add(replacement);
-      } else {
-        iterator.add(replacement);
+    if (abstractValue.isSingleValue()) {
+      SingleValue singleValue = abstractValue.asSingleValue();
+      if (singleValue.isSingleFieldValue()
+          && singleValue.asSingleFieldValue().getField() == field) {
+        return;
       }
-      feedback.markFieldAsPropagated(target);
+      if (singleValue.isMaterializableInContext(appView, code.method().holder())) {
+        BasicBlock block = current.getBlock();
+        DexType context = code.method().holder();
+        Position position = current.getPosition();
+
+        // All usages are replaced by the replacement value.
+        Instruction replacement =
+            singleValue.createMaterializingInstruction(appView, code, current);
+        affectedValues.addAll(current.outValue().affectedValues());
+        current.outValue().replaceUsers(replacement.outValue());
+
+        // To preserve side effects, original field-get is replaced by an explicit null-check, if
+        // the field-get instruction may only fail with an NPE, or the field-get remains as-is.
+        if (current.isInstanceGet()) {
+          replaceInstructionByNullCheckIfPossible(current, iterator, context);
+        } else {
+          assert current.isStaticGet();
+          replaceInstructionByInitClassIfPossible(
+              current, target.holder(), code, iterator, context);
+        }
+
+        // Insert the definition of the replacement.
+        replacement.setPosition(position);
+        if (block.hasCatchHandlers()) {
+          iterator.split(code, blocks).listIterator(code).add(replacement);
+        } else {
+          iterator.add(replacement);
+        }
+        feedback.markFieldAsPropagated(target);
+      }
     }
   }
 
@@ -460,7 +493,7 @@
       return;
     }
 
-    replaceInstructionByNullCheckIfPossible(current, iterator, code.method.holder());
+    replaceInstructionByNullCheckIfPossible(current, iterator, code.method().holder());
   }
 
   private void replaceStaticPutByInitClassIfNeverRead(
@@ -475,7 +508,7 @@
     }
 
     replaceInstructionByInitClassIfPossible(
-        current, field.holder(), code, iterator, code.method.holder());
+        current, field.holder(), code, iterator, code.method().holder());
   }
 
   /**
@@ -483,25 +516,39 @@
    *
    * <p>Also assigns value ranges to values where possible.
    */
-  public void rewriteWithConstantValues(IRCode code, DexType callingContext) {
+  public void run(IRCode code) {
     IRMetadata metadata = code.metadata();
     if (!metadata.mayHaveFieldInstruction() && !metadata.mayHaveInvokeMethod()) {
       return;
     }
-
     Set<Value> affectedValues = Sets.newIdentityHashSet();
-    ListIterator<BasicBlock> blocks = code.listIterator();
-    while (blocks.hasNext()) {
-      BasicBlock block = blocks.next();
+    run(code, code.listIterator(), affectedValues, alwaysTrue());
+    if (!affectedValues.isEmpty()) {
+      new TypeAnalysis(appView).narrowing(affectedValues);
+    }
+    assert code.isConsistentSSA();
+  }
+
+  public void run(
+      IRCode code,
+      ListIterator<BasicBlock> blockIterator,
+      Set<Value> affectedValues,
+      Predicate<BasicBlock> blockTester) {
+    DexType context = code.method().holder();
+    while (blockIterator.hasNext()) {
+      BasicBlock block = blockIterator.next();
+      if (!blockTester.test(block)) {
+        continue;
+      }
       InstructionListIterator iterator = block.listIterator(code);
       while (iterator.hasNext()) {
         Instruction current = iterator.next();
         if (current.isInvokeMethod()) {
           rewriteInvokeMethodWithConstantValues(
-              code, callingContext, affectedValues, blocks, iterator, current.asInvokeMethod());
+              code, context, affectedValues, blockIterator, iterator, current.asInvokeMethod());
         } else if (current.isFieldGet()) {
           rewriteFieldGetWithConstantValues(
-              code, affectedValues, blocks, iterator, current.asFieldInstruction());
+              code, affectedValues, blockIterator, iterator, current.asFieldInstruction());
         } else if (current.isInstancePut()) {
           replaceInstancePutByNullCheckIfNeverRead(code, iterator, current.asInstancePut());
         } else if (current.isStaticPut()) {
@@ -509,9 +556,5 @@
         }
       }
     }
-    if (!affectedValues.isEmpty()) {
-      new TypeAnalysis(appView).narrowing(affectedValues);
-    }
-    assert code.isConsistentSSA();
   }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/NestUtils.java b/src/main/java/com/android/tools/r8/ir/optimize/NestUtils.java
index 508e394..3cd05c5 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/NestUtils.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/NestUtils.java
@@ -46,7 +46,7 @@
     InstructionListIterator iterator = code.instructionListIterator();
     DexClass callerHolderClass = appView.definitionFor(callerHolder);
     assert callerHolderClass != null;
-    assert code.method.holder() != callerHolder;
+    assert code.method().holder() != callerHolder;
     while (iterator.hasNext()) {
       Instruction instruction = iterator.next();
       if (instruction.isInvokeDirect()) {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/NonNullTracker.java b/src/main/java/com/android/tools/r8/ir/optimize/NonNullTracker.java
index bc294fa..662eb39 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/NonNullTracker.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/NonNullTracker.java
@@ -89,7 +89,8 @@
           InvokeMethod invoke = current.asInvokeMethod();
           DexMethod invokedMethod = invoke.getInvokedMethod();
 
-          DexEncodedMethod singleTarget = invoke.lookupSingleTarget(appView, code.method.holder());
+          DexEncodedMethod singleTarget =
+              invoke.lookupSingleTarget(appView, code.method().holder());
           if (singleTarget != null) {
             MethodOptimizationInfo optimizationInfo = singleTarget.getOptimizationInfo();
 
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/Outliner.java b/src/main/java/com/android/tools/r8/ir/optimize/Outliner.java
index d9ebcf6..6a05911 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/Outliner.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/Outliner.java
@@ -1184,7 +1184,7 @@
         ListIterator<BasicBlock> blocksIterator,
         BasicBlock block,
         List<Integer> toRemove) {
-      super(code.method, block);
+      super(code.method(), block);
       this.code = code;
       this.blocksIterator = blocksIterator;
       this.toRemove = toRemove;
@@ -1280,9 +1280,9 @@
     assert outlineMethodIdentifierGenerator == null;
     outlineMethodIdentifierGenerator =
         code -> {
-          assert !code.method.getCode().isOutlineCode();
+          assert !code.method().getCode().isOutlineCode();
           for (BasicBlock block : code.blocks) {
-            new OutlineMethodIdentifier(code.method, block, candidateMap).process();
+            new OutlineMethodIdentifier(code.method(), block, candidateMap).process();
           }
         };
   }
@@ -1295,8 +1295,8 @@
   }
 
   public void identifyOutlineSites(IRCode code) {
-    assert !code.method.getCode().isOutlineCode();
-    DexClass clazz = asProgramClassOrNull(appView.definitionFor(code.method.holder()));
+    assert !code.method().getCode().isOutlineCode();
+    DexClass clazz = asProgramClassOrNull(appView.definitionFor(code.method().holder()));
     assert clazz != null;
     if (clazz == null) {
       return;
@@ -1307,7 +1307,7 @@
     }
 
     for (BasicBlock block : code.blocks) {
-      new OutlineSiteIdentifier(code.method, block).process();
+      new OutlineSiteIdentifier(code.method(), block).process();
     }
   }
 
@@ -1405,7 +1405,7 @@
   }
 
   public void applyOutliningCandidate(IRCode code) {
-    assert !code.method.getCode().isOutlineCode();
+    assert !code.method().getCode().isOutlineCode();
     ListIterator<BasicBlock> blocksIterator = code.listIterator();
     while (blocksIterator.hasNext()) {
       BasicBlock block = blocksIterator.next();
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/RedundantFieldLoadElimination.java b/src/main/java/com/android/tools/r8/ir/optimize/RedundantFieldLoadElimination.java
index a7e4875..ccb328e 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/RedundantFieldLoadElimination.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/RedundantFieldLoadElimination.java
@@ -69,7 +69,7 @@
 
   public RedundantFieldLoadElimination(AppView<?> appView, IRCode code) {
     this.appView = appView;
-    this.method = code.method;
+    this.method = code.method();
     this.code = code;
   }
 
@@ -105,7 +105,7 @@
     private final SingleValue value;
 
     private MaterializableValue(SingleValue value) {
-      assert value.isMaterializableInContext(appView, method.holder());
+      assert value.isMaterializableInContext(appView.withLiveness(), method.holder());
       this.value = value;
     }
 
@@ -113,7 +113,7 @@
     public void eliminateRedundantRead(InstructionListIterator it, FieldInstruction redundant) {
       affectedValues.addAll(redundant.value().affectedValues());
       it.replaceCurrentInstruction(
-          value.createMaterializingInstruction(appView.withSubtyping(), code, redundant));
+          value.createMaterializingInstruction(appView.withClassHierarchy(), code, redundant));
     }
   }
 
@@ -374,7 +374,7 @@
             activeState.putNonFinalInstanceField(fieldAndObject, new ExistingValue(value));
           } else if (info.isSingleValue()) {
             SingleValue value = info.asSingleValue();
-            if (value.isMaterializableInContext(appView, method.holder())) {
+            if (value.isMaterializableInContext(appView.withLiveness(), method.holder())) {
               Value object = invoke.getReceiver().getAliasedValue();
               FieldAndObject fieldAndObject = new FieldAndObject(field.field, object);
               activeState.putNonFinalInstanceField(fieldAndObject, new MaterializableValue(value));
@@ -397,7 +397,7 @@
       // that we are conservative.
       activeState.removeNonFinalInstanceFields(field);
     } else if (instruction.isStaticPut()) {
-      if (field.holder != code.method.holder()) {
+      if (field.holder != code.method().holder()) {
         // Accessing a static field on a different object could cause <clinit> to run which
         // could modify any static field on any other object.
         activeState.clearNonFinalStaticFields();
@@ -405,7 +405,7 @@
         activeState.removeNonFinalStaticField(field);
       }
     } else if (instruction.isStaticGet()) {
-      if (field.holder != code.method.holder()) {
+      if (field.holder != code.method().holder()) {
         // Accessing a static field on a different object could cause <clinit> to run which
         // could modify any static field on any other object.
         activeState.clearNonFinalStaticFields();
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 3b427cb..c60da19 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
@@ -39,7 +39,7 @@
       return;
     }
     Set<Value> affectedValues = Sets.newIdentityHashSet();
-    DexType context = code.method.holder();
+    DexType context = code.method().holder();
     ClassInitializationAnalysis classInitializationAnalysis =
         new ClassInitializationAnalysis(appView, code);
     for (BasicBlock block : code.blocks) {
@@ -68,8 +68,9 @@
           ConstClass constClass = new ConstClass(value, type);
           it.replaceCurrentInstruction(constClass);
           if (appView.options().isGeneratingClassFiles()) {
-            code.method.upgradeClassFileVersion(
-                appView.options().requiredCfVersionForConstClassInstructions());
+            code.method()
+                .upgradeClassFileVersion(
+                    appView.options().requiredCfVersionForConstClassInstructions());
           }
         }
       }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/ServiceLoaderRewriter.java b/src/main/java/com/android/tools/r8/ir/optimize/ServiceLoaderRewriter.java
index 3285e2a..a2d21c3 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/ServiceLoaderRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/ServiceLoaderRewriter.java
@@ -174,7 +174,7 @@
               service -> {
                 DexEncodedMethod addedMethod = createSynthesizedMethod(service, classes);
                 if (appView.options().isGeneratingClassFiles()) {
-                  addedMethod.upgradeClassFileVersion(code.method.getClassFileVersion());
+                  addedMethod.upgradeClassFileVersion(code.method().getClassFileVersion());
                 }
                 return addedMethod;
               });
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/UninstantiatedTypeOptimization.java b/src/main/java/com/android/tools/r8/ir/optimize/UninstantiatedTypeOptimization.java
index 6b854e6..055f575 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/UninstantiatedTypeOptimization.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/UninstantiatedTypeOptimization.java
@@ -7,7 +7,7 @@
 import static com.android.tools.r8.ir.optimize.UninstantiatedTypeOptimization.Strategy.ALLOW_ARGUMENT_REMOVAL;
 import static com.android.tools.r8.ir.optimize.UninstantiatedTypeOptimization.Strategy.DISALLOW_ARGUMENT_REMOVAL;
 
-import com.android.tools.r8.graph.AppInfoWithSubtyping;
+import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexEncodedField;
 import com.android.tools.r8.graph.DexEncodedMethod;
@@ -351,7 +351,8 @@
         Instruction instruction = instructionIterator.next();
         if (instruction.throwsOnNullInput()) {
           Value couldBeNullValue = instruction.getNonNullInput();
-          if (isThrowNullCandidate(couldBeNullValue, instruction, appView, code.method.holder())) {
+          if (isThrowNullCandidate(
+              couldBeNullValue, instruction, appView, code.method().holder())) {
             if (instruction.isInstanceGet() || instruction.isInstancePut()) {
               ++numberOfInstanceGetOrInstancePutWithNullReceiver;
             } else if (instruction.isInvokeMethodWithReceiver()) {
@@ -403,7 +404,7 @@
   private static boolean isThrowNullCandidate(
       Value couldBeNullValue,
       Instruction current,
-      AppView<? extends AppInfoWithSubtyping> appView,
+      AppView<? extends AppInfoWithClassHierarchy> appView,
       DexType context) {
     if (!couldBeNullValue.isAlwaysNull(appView)) {
       return false;
@@ -450,7 +451,7 @@
       IRCode code,
       AssumeDynamicTypeRemover assumeDynamicTypeRemover,
       Set<Value> affectedValues) {
-    DexType context = code.method.holder();
+    DexType context = code.method().holder();
     DexField field = instruction.getField();
     DexType fieldType = field.type;
     if (fieldType.isAlwaysNull(appView)) {
@@ -506,7 +507,7 @@
       AssumeDynamicTypeRemover assumeDynamicTypeRemover,
       Set<BasicBlock> blocksToBeRemoved,
       Set<Value> affectedValues) {
-    DexEncodedMethod target = invoke.lookupSingleTarget(appView, code.method.holder());
+    DexEncodedMethod target = invoke.lookupSingleTarget(appView, code.method().holder());
     if (target == null) {
       return;
     }
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 9b443bc..798b820 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
@@ -203,13 +203,13 @@
         // Assess eligibility of instance and class.
         EligibilityStatus status = processor.isInstanceEligible();
         if (status != EligibilityStatus.ELIGIBLE) {
-          logEligibilityStatus(code.method, root, status);
+          logEligibilityStatus(code.method(), root, status);
           // This root will never be inlined.
           rootsIterator.remove();
           continue;
         }
         status = processor.isClassAndUsageEligible();
-        logEligibilityStatus(code.method, root, status);
+        logEligibilityStatus(code.method(), root, status);
         if (status != EligibilityStatus.ELIGIBLE) {
           // This root will never be inlined.
           rootsIterator.remove();
@@ -220,7 +220,7 @@
         InstructionOrPhi ineligibleUser = processor.areInstanceUsersEligible(defaultOracle);
         if (ineligibleUser != null) {
           // This root may succeed if users change in future.
-          logIneligibleUser(code.method, root, ineligibleUser);
+          logIneligibleUser(code.method(), root, ineligibleUser);
           continue;
         }
 
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 7f0ad04..9a76a99 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
@@ -52,7 +52,7 @@
 import com.android.tools.r8.ir.optimize.info.initializer.InstanceInitializerInfo;
 import com.android.tools.r8.ir.optimize.inliner.InliningIRProvider;
 import com.android.tools.r8.ir.optimize.inliner.NopWhyAreYouNotInliningReporter;
-import com.android.tools.r8.kotlin.KotlinInfo;
+import com.android.tools.r8.kotlin.KotlinClassLevelInfo;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.utils.ListUtils;
 import com.android.tools.r8.utils.Pair;
@@ -506,7 +506,8 @@
             continue;
           }
 
-          DexEncodedMethod singleTarget = invoke.lookupSingleTarget(appView, code.method.holder());
+          DexEncodedMethod singleTarget =
+              invoke.lookupSingleTarget(appView, code.method().holder());
           if (singleTarget == null || !indirectMethodCallsOnInstance.contains(singleTarget)) {
             throw new IllegalClassInlinerStateException();
           }
@@ -1191,11 +1192,8 @@
     DexType inlineeHolder = inlinee.holder();
     DexClass inlineeClass = appView.definitionFor(inlineeHolder);
     assert inlineeClass != null;
-
-    KotlinInfo kotlinInfo = inlineeClass.getKotlinInfo();
-    return kotlinInfo != null &&
-        kotlinInfo.isSyntheticClass() &&
-        kotlinInfo.asSyntheticClass().isLambda();
+    KotlinClassLevelInfo kotlinInfo = inlineeClass.getKotlinInfo();
+    return kotlinInfo.isSyntheticClass() && kotlinInfo.asSyntheticClass().isLambda();
   }
 
   private void markSizeForInlining(InvokeMethod invoke, DexEncodedMethod inlinee) {
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 d878322..cf9c904 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
@@ -188,7 +188,7 @@
         // If dependencies is null, it means the enum is not eligible (It has been marked as
         // unboxable by this thread or another one), so we do not need to record dependencies.
         if (dependencies != null) {
-          dependencies.add(code.method);
+          dependencies.add(code.method());
         }
       }
     }
@@ -266,7 +266,7 @@
           eligibleEnums.add(type);
         }
       } else if (use.isReturn()) {
-        DexType returnType = code.method.method.proto.returnType;
+        DexType returnType = code.method().method.proto.returnType;
         if (enumsUnboxingCandidates.containsKey(returnType)) {
           eligibleEnums.add(returnType);
         }
@@ -394,7 +394,7 @@
         return Reason.INVALID_INVOKE_ON_ARRAY;
       }
       DexEncodedMethod encodedSingleTarget =
-          invokeMethod.lookupSingleTarget(appView, code.method.holder());
+          invokeMethod.lookupSingleTarget(appView, code.method().holder());
       if (encodedSingleTarget == null) {
         return Reason.INVALID_INVOKE;
       }
@@ -405,7 +405,7 @@
       }
       if (dexClass.isProgramClass()) {
         if (dexClass.isEnum() && encodedSingleTarget.isInstanceInitializer()) {
-          if (code.method.holder() == dexClass.type && code.method.isClassInitializer()) {
+          if (code.method().holder() == dexClass.type && code.method().isClassInitializer()) {
             // The enum instance initializer is allowed to be called only from the enum clinit.
             return Reason.ELIGIBLE;
           } else {
@@ -446,8 +446,8 @@
         return Reason.ELIGIBLE;
       } else if (singleTarget == factory.enumMethods.constructor) {
         // Enum constructor call is allowed only if first call of an enum initializer.
-        if (code.method.isInstanceInitializer()
-            && code.method.holder() == enumClass.type
+        if (code.method().isInstanceInitializer()
+            && code.method().holder() == enumClass.type
             && isFirstInstructionAfterArguments(invokeMethod, code)) {
           return Reason.ELIGIBLE;
         }
@@ -543,7 +543,7 @@
 
     // Return is used for valueOf methods.
     if (instruction.isReturn()) {
-      DexType returnType = code.method.method.proto.returnType;
+      DexType returnType = code.method().method.proto.returnType;
       if (returnType != enumClass.type && returnType.toBaseType(factory) != enumClass.type) {
         return Reason.IMPLICIT_UP_CAST_IN_RETURN;
       }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingCandidateAnalysis.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingCandidateAnalysis.java
index 8f228b9..5412037 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingCandidateAnalysis.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingCandidateAnalysis.java
@@ -44,6 +44,9 @@
     }
     removeEnumsInAnnotations();
     removePinnedCandidates();
+    if (appView.options().protoShrinking().isProtoShrinkingEnabled()) {
+      enumToUnboxCandidates.remove(appView.protoShrinker().references.methodToInvokeType);
+    }
     return enumToUnboxCandidates;
   }
 
@@ -131,7 +134,8 @@
 
   private void removeEnumsInAnnotations() {
     for (DexProgramClass clazz : appView.appInfo().classes()) {
-      if (appView.appInfo().isSubtype(clazz.type, factory.annotationType)) {
+      if (clazz.isAnnotation()) {
+        assert clazz.interfaces.contains(appView.dexItemFactory().annotationType);
         removeEnumsInAnnotation(clazz);
       }
     }
@@ -141,12 +145,10 @@
     // Browse annotation values types in search for enum.
     // Each annotation value is represented by a virtual method.
     for (DexEncodedMethod method : clazz.virtualMethods()) {
-      DexProto proto = method.method.proto;
-      assert proto.parameters.isEmpty();
-      DexType valueType = proto.returnType.toBaseType(appView.appInfo().dexItemFactory());
-      if (valueType.isClassType()
-          && enumToUnboxCandidates.containsKey(valueType)
-          && appView.appInfo().isSubtype(valueType, appView.appInfo().dexItemFactory().enumType)) {
+      assert method.parameters().isEmpty()
+          || appView.options().testing.allowInjectedAnnotationMethods;
+      DexType valueType = method.returnType().toBaseType(appView.dexItemFactory());
+      if (enumToUnboxCandidates.containsKey(valueType)) {
         enumUnboxer.reportFailure(valueType, Reason.ANNOTATION);
         enumToUnboxCandidates.remove(valueType);
       }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/ConcreteCallSiteOptimizationInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/ConcreteCallSiteOptimizationInfo.java
index 791c889..f4d341b 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/ConcreteCallSiteOptimizationInfo.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/ConcreteCallSiteOptimizationInfo.java
@@ -6,7 +6,7 @@
 import static com.android.tools.r8.ir.analysis.type.Nullability.definitelyNotNull;
 import static com.android.tools.r8.ir.analysis.type.Nullability.maybeNull;
 
-import com.android.tools.r8.graph.AppInfoWithSubtyping;
+import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.ir.analysis.type.Nullability;
@@ -153,7 +153,7 @@
   }
 
   public static CallSiteOptimizationInfo fromArguments(
-      AppView<? extends AppInfoWithSubtyping> appView,
+      AppView<? extends AppInfoWithClassHierarchy> appView,
       DexEncodedMethod method,
       List<Value> inValues) {
     boolean allowConstantPropagation = appView.options().enablePropagationOfConstantsAtCallSites;
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/LibraryOptimizationInfoInitializerFeedback.java b/src/main/java/com/android/tools/r8/ir/optimize/info/LibraryOptimizationInfoInitializerFeedback.java
new file mode 100644
index 0000000..ace2aa6
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/LibraryOptimizationInfoInitializerFeedback.java
@@ -0,0 +1,25 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.optimize.info;
+
+import com.android.tools.r8.graph.DexEncodedField;
+import com.android.tools.r8.ir.analysis.value.AbstractValue;
+
+public class LibraryOptimizationInfoInitializerFeedback extends OptimizationFeedbackSimple {
+
+  private static LibraryOptimizationInfoInitializerFeedback INSTANCE =
+      new LibraryOptimizationInfoInitializerFeedback();
+
+  LibraryOptimizationInfoInitializerFeedback() {}
+
+  public static LibraryOptimizationInfoInitializerFeedback getInstance() {
+    return INSTANCE;
+  }
+
+  public void recordLibraryFieldHasAbstractValue(
+      DexEncodedField field, AbstractValue abstractValue) {
+    field.getMutableOptimizationInfo().setAbstractValue(abstractValue);
+  }
+}
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 da135b0..14a3800 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
@@ -715,7 +715,7 @@
     return alwaysTriggerExpectedEffectBeforeAnythingElse(
         code,
         (instruction, it) -> {
-          DexType context = code.method.holder();
+          DexType context = code.method().holder();
           if (instruction.definitelyTriggersClassInitialization(
               clazz, context, appView, DIRECTLY, AnalysisAssumption.INSTRUCTION_DOES_NOT_THROW)) {
             // In order to preserve class initialization semantic, the exception must not be caught
@@ -848,7 +848,7 @@
           if (isInstantiationOfNullPointerException(instr, it, appView.dexItemFactory())) {
             it.next(); // Skip call to NullPointerException.<init>.
             return InstructionEffect.NO_EFFECT;
-          } else if (instr.throwsNpeIfValueIsNull(value, appView, code.method.holder())) {
+          } else if (instr.throwsNpeIfValueIsNull(value, appView, code.method().holder())) {
             // In order to preserve NPE semantic, the exception must not be caught by any handler.
             // Therefore, we must ignore this instruction if it is covered by a catch handler.
             // Note: this is a conservative approach where we consider that any catch handler could
@@ -857,7 +857,7 @@
               // We found a NPE check on the value.
               return InstructionEffect.DESIRED_EFFECT;
             }
-          } else if (instr.instructionMayHaveSideEffects(appView, code.method.holder())) {
+          } else if (instr.instructionMayHaveSideEffects(appView, code.method().holder())) {
             // If the current instruction is const-string, this could load the parameter name.
             // Just make sure it is indeed not throwing.
             if (instr.isConstString() && !instr.instructionInstanceCanThrow()) {
@@ -1198,7 +1198,7 @@
       }
     }
     if (facts.length() > 0) {
-      feedback.setNonNullParamOnNormalExits(code.method, facts);
+      feedback.setNonNullParamOnNormalExits(code.method(), facts);
     }
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/MutableFieldOptimizationInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/MutableFieldOptimizationInfo.java
index 118c285..3237c20 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/MutableFieldOptimizationInfo.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/MutableFieldOptimizationInfo.java
@@ -4,7 +4,7 @@
 
 package com.android.tools.r8.ir.optimize.info;
 
-import com.android.tools.r8.graph.AppInfoWithSubtyping;
+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.GraphLense;
@@ -35,7 +35,7 @@
   private TypeElement dynamicUpperBoundType = null;
 
   public MutableFieldOptimizationInfo fixupClassTypeReferences(
-      Function<DexType, DexType> mapping, AppView<? extends AppInfoWithSubtyping> appView) {
+      Function<DexType, DexType> mapping, AppView<? extends AppInfoWithClassHierarchy> appView) {
     if (dynamicUpperBoundType != null) {
       dynamicUpperBoundType = dynamicUpperBoundType.fixupClassTypeReferences(mapping, appView);
     }
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 c03c8e0d..8942ea1 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
@@ -23,7 +23,7 @@
 
   private static OptimizationFeedbackSimple INSTANCE = new OptimizationFeedbackSimple();
 
-  private OptimizationFeedbackSimple() {}
+  OptimizationFeedbackSimple() {}
 
   public static OptimizationFeedbackSimple getInstance() {
     return INSTANCE;
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/UpdatableMethodOptimizationInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/UpdatableMethodOptimizationInfo.java
index 5ad6c86..c1012ca 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/UpdatableMethodOptimizationInfo.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/UpdatableMethodOptimizationInfo.java
@@ -4,7 +4,7 @@
 
 package com.android.tools.r8.ir.optimize.info;
 
-import com.android.tools.r8.graph.AppInfoWithSubtyping;
+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.GraphLense;
@@ -145,7 +145,7 @@
   }
 
   public UpdatableMethodOptimizationInfo fixupClassTypeReferences(
-      Function<DexType, DexType> mapping, AppView<? extends AppInfoWithSubtyping> appView) {
+      Function<DexType, DexType> mapping, AppView<? extends AppInfoWithClassHierarchy> appView) {
     if (returnsObjectWithUpperBoundType != null) {
       returnsObjectWithUpperBoundType =
           returnsObjectWithUpperBoundType.fixupClassTypeReferences(mapping, appView);
@@ -411,7 +411,7 @@
   }
 
   void markReturnsAbstractValue(AbstractValue value) {
-    assert !abstractReturnValue.isSingleValue() || abstractReturnValue.asSingleValue() == value
+    assert !abstractReturnValue.isSingleValue() || abstractReturnValue.equals(value)
         : "return single value changed from " + abstractReturnValue + " to " + value;
     abstractReturnValue = value;
   }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/field/InstanceFieldTypeInitializationInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/field/InstanceFieldTypeInitializationInfo.java
index ec77b22..3bfce16 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/field/InstanceFieldTypeInitializationInfo.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/field/InstanceFieldTypeInitializationInfo.java
@@ -63,10 +63,11 @@
     return new InstanceFieldTypeInitializationInfo(
         dynamicLowerBoundType != null
             ? dynamicLowerBoundType
-                .fixupClassTypeReferences(lens::lookupType, appView.withSubtyping())
+                .fixupClassTypeReferences(lens::lookupType, appView.withClassHierarchy())
                 .asClassType()
             : null,
-        dynamicUpperBoundType.fixupClassTypeReferences(lens::lookupType, appView.withSubtyping()));
+        dynamicUpperBoundType.fixupClassTypeReferences(
+            lens::lookupType, appView.withClassHierarchy()));
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/lambda/LambdaGroup.java b/src/main/java/com/android/tools/r8/ir/optimize/lambda/LambdaGroup.java
index 041616c..b80283b 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/lambda/LambdaGroup.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/lambda/LambdaGroup.java
@@ -5,7 +5,7 @@
 package com.android.tools.r8.ir.optimize.lambda;
 
 import com.android.tools.r8.errors.Unreachable;
-import com.android.tools.r8.graph.AppInfoWithSubtyping;
+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;
@@ -156,7 +156,7 @@
   public abstract Strategy getCodeStrategy();
 
   public abstract ThrowingConsumer<DexClass, LambdaStructureError> lambdaClassValidator(
-      Kotlin kotlin, AppInfoWithSubtyping appInfo);
+      Kotlin kotlin, AppInfoWithClassHierarchy appInfo);
 
   // Package for a lambda group class to be created in.
   protected abstract String getTypePackage();
@@ -164,7 +164,7 @@
   protected abstract String getGroupSuffix();
 
   final DexProgramClass synthesizeClass(
-      AppView<? extends AppInfoWithSubtyping> appView, OptimizationFeedback feedback) {
+      AppView<? extends AppInfoWithClassHierarchy> appView, OptimizationFeedback feedback) {
     assert classType == null;
     assert verifyLambdaIds(true);
     List<LambdaInfo> lambdas = Lists.newArrayList(this.lambdas.values());
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/lambda/LambdaGroupClassBuilder.java b/src/main/java/com/android/tools/r8/ir/optimize/lambda/LambdaGroupClassBuilder.java
index 2c4315c..697638f 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/lambda/LambdaGroupClassBuilder.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/lambda/LambdaGroupClassBuilder.java
@@ -4,7 +4,7 @@
 
 package com.android.tools.r8.ir.optimize.lambda;
 
-import com.android.tools.r8.graph.AppInfoWithSubtyping;
+import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.ClassAccessFlags;
 import com.android.tools.r8.graph.DexAnnotationSet;
@@ -35,7 +35,7 @@
   }
 
   public final DexProgramClass synthesizeClass(
-      AppView<? extends AppInfoWithSubtyping> appView, OptimizationFeedback feedback) {
+      AppView<? extends AppInfoWithClassHierarchy> appView, OptimizationFeedback feedback) {
     DexType groupClassType = group.getGroupClassType();
     DexType superClassType = getSuperClassType();
     DexProgramClass programClass =
@@ -79,7 +79,7 @@
   protected abstract DexEncodedField[] buildInstanceFields();
 
   protected abstract DexEncodedField[] buildStaticFields(
-      AppView<? extends AppInfoWithSubtyping> appView, OptimizationFeedback feedback);
+      AppView<? extends AppInfoWithClassHierarchy> appView, OptimizationFeedback feedback);
 
   protected abstract DexTypeList buildInterfaces();
 }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/lambda/LambdaMerger.java b/src/main/java/com/android/tools/r8/ir/optimize/lambda/LambdaMerger.java
index 0bdaa57..63d2c1d 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/lambda/LambdaMerger.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/lambda/LambdaMerger.java
@@ -249,8 +249,7 @@
         .filter(cls -> !appView.appInfo().isPinned(cls.type))
         .filter(
             cls ->
-                cls.hasKotlinInfo()
-                    && cls.getKotlinInfo().isSyntheticClass()
+                cls.getKotlinInfo().isSyntheticClass()
                     && cls.getKotlinInfo().asSyntheticClass().isLambda()
                     && KotlinLambdaGroupIdFactory.hasValidAnnotations(kotlin, cls)
                     && (appView.options().featureSplitConfiguration == null
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/JStyleLambdaGroup.java b/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/JStyleLambdaGroup.java
index f868855..4aafb60 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/JStyleLambdaGroup.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/JStyleLambdaGroup.java
@@ -5,7 +5,7 @@
 package com.android.tools.r8.ir.optimize.lambda.kotlin;
 
 import com.android.tools.r8.code.ReturnVoid;
-import com.android.tools.r8.graph.AppInfoWithSubtyping;
+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;
@@ -114,7 +114,7 @@
 
   @Override
   public ThrowingConsumer<DexClass, LambdaStructureError> lambdaClassValidator(
-      Kotlin kotlin, AppInfoWithSubtyping appInfo) {
+      Kotlin kotlin, AppInfoWithClassHierarchy appInfo) {
     return new ClassValidator(kotlin, appInfo);
   }
 
@@ -155,7 +155,7 @@
 
   // Specialized class validator.
   private class ClassValidator extends KotlinLambdaClassValidator {
-    ClassValidator(Kotlin kotlin, AppInfoWithSubtyping appInfo) {
+    ClassValidator(Kotlin kotlin, AppInfoWithClassHierarchy appInfo) {
       super(kotlin, JStyleLambdaGroup.this, appInfo);
     }
 
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/JStyleLambdaGroupIdFactory.java b/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/JStyleLambdaGroupIdFactory.java
index 8f26df4..e392112 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/JStyleLambdaGroupIdFactory.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/JStyleLambdaGroupIdFactory.java
@@ -24,7 +24,7 @@
     boolean accessRelaxed =
         appView.options().getProguardConfiguration().isAccessModificationAllowed();
 
-    assert lambda.hasKotlinInfo() && lambda.getKotlinInfo().isSyntheticClass();
+    assert lambda.getKotlinInfo().isSyntheticClass();
     assert lambda.getKotlinInfo().asSyntheticClass().isJavaStyleLambda();
 
     checkAccessFlags("class access flags", lambda.accessFlags,
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KStyleLambdaGroup.java b/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KStyleLambdaGroup.java
index 0295106..cc9061a 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KStyleLambdaGroup.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KStyleLambdaGroup.java
@@ -7,7 +7,7 @@
 import com.android.tools.r8.code.Const16;
 import com.android.tools.r8.code.Const4;
 import com.android.tools.r8.code.ReturnVoid;
-import com.android.tools.r8.graph.AppInfoWithSubtyping;
+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;
@@ -120,7 +120,7 @@
 
   @Override
   public ThrowingConsumer<DexClass, LambdaStructureError> lambdaClassValidator(
-      Kotlin kotlin, AppInfoWithSubtyping appInfo) {
+      Kotlin kotlin, AppInfoWithClassHierarchy appInfo) {
     return new ClassValidator(kotlin, appInfo);
   }
 
@@ -161,7 +161,7 @@
 
   // Specialized class validator.
   private final class ClassValidator extends KotlinLambdaClassValidator {
-    ClassValidator(Kotlin kotlin, AppInfoWithSubtyping appInfo) {
+    ClassValidator(Kotlin kotlin, AppInfoWithClassHierarchy appInfo) {
       super(kotlin, KStyleLambdaGroup.this, appInfo);
     }
 
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KStyleLambdaGroupIdFactory.java b/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KStyleLambdaGroupIdFactory.java
index eaf154c..ea39dce 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KStyleLambdaGroupIdFactory.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KStyleLambdaGroupIdFactory.java
@@ -24,7 +24,7 @@
     boolean accessRelaxed =
         appView.options().getProguardConfiguration().isAccessModificationAllowed();
 
-    assert lambda.hasKotlinInfo() && lambda.getKotlinInfo().isSyntheticClass();
+    assert lambda.getKotlinInfo().isSyntheticClass();
     assert lambda.getKotlinInfo().asSyntheticClass().isKotlinStyleLambda();
 
     checkAccessFlags("class access flags", lambda.accessFlags,
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KotlinLambdaClassValidator.java b/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KotlinLambdaClassValidator.java
index 3e38f67..a956f84 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KotlinLambdaClassValidator.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KotlinLambdaClassValidator.java
@@ -16,7 +16,7 @@
 import com.android.tools.r8.code.ReturnVoid;
 import com.android.tools.r8.code.SputObject;
 import com.android.tools.r8.errors.Unreachable;
-import com.android.tools.r8.graph.AppInfoWithSubtyping;
+import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
 import com.android.tools.r8.graph.Code;
 import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexEncodedField;
@@ -54,9 +54,10 @@
 
   final Kotlin kotlin;
   private final KotlinLambdaGroup group;
-  private final AppInfoWithSubtyping appInfo;
+  private final AppInfoWithClassHierarchy appInfo;
 
-  KotlinLambdaClassValidator(Kotlin kotlin, KotlinLambdaGroup group, AppInfoWithSubtyping appInfo) {
+  KotlinLambdaClassValidator(
+      Kotlin kotlin, KotlinLambdaGroup group, AppInfoWithClassHierarchy appInfo) {
     this.kotlin = kotlin;
     this.group = group;
     this.appInfo = appInfo;
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KotlinLambdaGroupClassBuilder.java b/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KotlinLambdaGroupClassBuilder.java
index 9f14329..dfd62d3 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KotlinLambdaGroupClassBuilder.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KotlinLambdaGroupClassBuilder.java
@@ -6,7 +6,7 @@
 
 import static com.android.tools.r8.ir.analysis.type.Nullability.definitelyNotNull;
 
-import com.android.tools.r8.graph.AppInfoWithSubtyping;
+import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.ClassAccessFlags;
 import com.android.tools.r8.graph.DexAnnotation;
@@ -223,7 +223,7 @@
 
   @Override
   protected DexEncodedField[] buildStaticFields(
-      AppView<? extends AppInfoWithSubtyping> appView, OptimizationFeedback feedback) {
+      AppView<? extends AppInfoWithClassHierarchy> appView, OptimizationFeedback feedback) {
     if (!group.isStateless()) {
       return DexEncodedField.EMPTY_ARRAY;
     }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KotlinLambdaGroupIdFactory.java b/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KotlinLambdaGroupIdFactory.java
index 91ff482..c8e08c1 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KotlinLambdaGroupIdFactory.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KotlinLambdaGroupIdFactory.java
@@ -34,7 +34,7 @@
       AppView<AppInfoWithLiveness> appView, Kotlin kotlin, DexClass lambda)
       throws LambdaStructureError {
 
-    assert lambda.hasKotlinInfo() && lambda.getKotlinInfo().isSyntheticClass();
+    assert lambda.getKotlinInfo().isSyntheticClass();
     if (lambda.getKotlinInfo().asSyntheticClass().isKotlinStyleLambda()) {
       return KStyleLambdaGroupIdFactory.INSTANCE.validateAndCreate(appView, kotlin, lambda);
     }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/library/BooleanMethodOptimizer.java b/src/main/java/com/android/tools/r8/ir/optimize/library/BooleanMethodOptimizer.java
index 16d514c..76e81ac 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/library/BooleanMethodOptimizer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/library/BooleanMethodOptimizer.java
@@ -70,7 +70,7 @@
       InvokeMethod invoke,
       Set<Value> affectedValues) {
     Value argument = invoke.arguments().get(0);
-    AbstractValue abstractValue = argument.getAbstractValue(appView, code.method.holder());
+    AbstractValue abstractValue = argument.getAbstractValue(appView, code.method().holder());
     if (abstractValue.isSingleNumberValue()) {
       instructionIterator.replaceCurrentInstructionWithStaticGet(
           appView,
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/library/LibraryMemberOptimizer.java b/src/main/java/com/android/tools/r8/ir/optimize/library/LibraryMemberOptimizer.java
index 46a1d7c..6566187 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/library/LibraryMemberOptimizer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/library/LibraryMemberOptimizer.java
@@ -42,7 +42,8 @@
     register(new ObjectMethodOptimizer(appView));
     register(new ObjectsMethodOptimizer(appView));
     register(new StringMethodOptimizer(appView));
-    if (appView.appInfo().hasSubtyping() && appView.options().enableDynamicTypeOptimization) {
+    if (appView.enableWholeProgramOptimizations()
+        && appView.options().enableDynamicTypeOptimization) {
       // Subtyping is required to prove the enum class is a subtype of java.lang.Enum.
       register(new EnumMethodOptimizer(appView));
     }
@@ -55,7 +56,7 @@
 
     LibraryOptimizationInfoInitializer libraryOptimizationInfoInitializer =
         new LibraryOptimizationInfoInitializer(appView);
-    libraryOptimizationInfoInitializer.run();
+    libraryOptimizationInfoInitializer.run(finalLibraryFields);
     modeledLibraryTypes.addAll(libraryOptimizationInfoInitializer.getModeledLibraryTypes());
   }
 
@@ -113,7 +114,7 @@
       Instruction instruction = instructionIterator.next();
       if (instruction.isInvokeMethod()) {
         InvokeMethod invoke = instruction.asInvokeMethod();
-        DexEncodedMethod singleTarget = invoke.lookupSingleTarget(appView, code.method.holder());
+        DexEncodedMethod singleTarget = invoke.lookupSingleTarget(appView, code.method().holder());
         if (singleTarget != null) {
           optimizeInvoke(code, instructionIterator, invoke, singleTarget, affectedValues);
         }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/library/LibraryOptimizationInfoInitializer.java b/src/main/java/com/android/tools/r8/ir/optimize/library/LibraryOptimizationInfoInitializer.java
index 4cd152f..a677b43 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/library/LibraryOptimizationInfoInitializer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/library/LibraryOptimizationInfoInitializer.java
@@ -5,29 +5,36 @@
 package com.android.tools.r8.ir.optimize.library;
 
 import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexEncodedField;
 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.DexType;
-import com.android.tools.r8.ir.optimize.info.OptimizationFeedbackSimple;
+import com.android.tools.r8.ir.analysis.value.AbstractValueFactory;
+import com.android.tools.r8.ir.analysis.value.ObjectState;
+import com.android.tools.r8.ir.optimize.info.LibraryOptimizationInfoInitializerFeedback;
 import com.google.common.collect.Sets;
 import java.util.BitSet;
 import java.util.Set;
 
 public class LibraryOptimizationInfoInitializer {
 
+  private final AbstractValueFactory abstractValueFactory;
   private final AppView<?> appView;
   private final DexItemFactory dexItemFactory;
 
-  private final OptimizationFeedbackSimple feedback = OptimizationFeedbackSimple.getInstance();
+  private final LibraryOptimizationInfoInitializerFeedback feedback =
+      LibraryOptimizationInfoInitializerFeedback.getInstance();
   private final Set<DexType> modeledLibraryTypes = Sets.newIdentityHashSet();
 
   LibraryOptimizationInfoInitializer(AppView<?> appView) {
+    this.abstractValueFactory = appView.abstractValueFactory();
     this.appView = appView;
     this.dexItemFactory = appView.dexItemFactory();
   }
 
-  void run() {
+  void run(Set<DexEncodedField> finalLibraryFields) {
+    modelStaticFinalLibraryFields(finalLibraryFields);
     modelLibraryMethodsReturningNonNull();
     modelLibraryMethodsReturningReceiver();
     modelRequireNonNullMethods();
@@ -37,6 +44,15 @@
     return modeledLibraryTypes;
   }
 
+  private void modelStaticFinalLibraryFields(Set<DexEncodedField> finalLibraryFields) {
+    for (DexEncodedField field : finalLibraryFields) {
+      if (field.isStatic()) {
+        feedback.recordLibraryFieldHasAbstractValue(
+            field, abstractValueFactory.createSingleFieldValue(field.field, ObjectState.empty()));
+      }
+    }
+  }
+
   private void modelLibraryMethodsReturningNonNull() {
     for (DexMethod method : dexItemFactory.libraryMethodsReturningNonNull) {
       DexEncodedMethod definition = lookupMethod(method);
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/library/StringMethodOptimizer.java b/src/main/java/com/android/tools/r8/ir/optimize/library/StringMethodOptimizer.java
index 0713076..1283bf4 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/library/StringMethodOptimizer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/library/StringMethodOptimizer.java
@@ -47,7 +47,7 @@
   private void optimizeEquals(
       IRCode code, InstructionListIterator instructionIterator, InvokeMethod invoke) {
     if (appView.appInfo().hasLiveness()) {
-      DexType context = code.method.holder();
+      DexType context = code.method().holder();
       Value first = invoke.arguments().get(0).getAliasedValue();
       Value second = invoke.arguments().get(1).getAliasedValue();
       if (isPrunedClassNameComparison(first, second, context)
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/staticizer/StaticizingProcessor.java b/src/main/java/com/android/tools/r8/ir/optimize/staticizer/StaticizingProcessor.java
index c451bd7..845a01c 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/staticizer/StaticizingProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/staticizer/StaticizingProcessor.java
@@ -353,7 +353,7 @@
       OptimizationFeedback feedback) {
     return (code, methodProcessor) ->
         converter.collectOptimizationInfo(
-            code.method,
+            code.method(),
             code,
             ClassInitializerDefaultsResult.empty(),
             feedback,
@@ -362,7 +362,7 @@
   }
 
   private void removeCandidateInstantiation(IRCode code, MethodProcessor methodProcessor) {
-    CandidateInfo candidateInfo = hostClassInits.get(code.method);
+    CandidateInfo candidateInfo = hostClassInits.get(code.method());
     assert candidateInfo != null;
 
     // Find and remove instantiation and its users.
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/string/StringOptimizer.java b/src/main/java/com/android/tools/r8/ir/optimize/string/StringOptimizer.java
index cc8a24f..ae94194 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/string/StringOptimizer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/string/StringOptimizer.java
@@ -299,7 +299,7 @@
   // Find Class#get*Name() with a constant-class and replace it with a const-string if possible.
   public void rewriteClassGetName(AppView<?> appView, IRCode code) {
     // Conflict with {@link CodeRewriter#collectClassInitializerDefaults}.
-    if (code.method.isClassInitializer()) {
+    if (code.method().isClassInitializer()) {
       return;
     }
     Set<Value> affectedValues = Sets.newIdentityHashSet();
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 0f6f6c9..7eddfcc 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
@@ -206,7 +206,7 @@
     // There are no linked values prior to register allocation.
     assert noLinkedValues();
     assert code.isConsistentSSA();
-    if (this.code.method.accessFlags.isBridge() && implementationIsBridge(this.code)) {
+    if (this.code.method().accessFlags.isBridge() && implementationIsBridge(this.code)) {
       transformBridgeMethod();
     }
     computeNeedsRegister();
@@ -225,7 +225,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.method().getOptimizationInfo().isReachabilitySensitive()) {
       InstructionListIterator it = code.instructionListIterator();
       while (it.hasNext()) {
         Instruction instruction = it.next();
@@ -604,7 +604,7 @@
     if (intervals == null) {
       throw new CompilationError(
           "Unexpected attempt to get register for a value without a register in method `"
-              + code.method.method.toSourceString()
+              + code.method().method.toSourceString()
               + "`.",
           code.origin);
     }
@@ -1634,8 +1634,8 @@
     // Set all free positions for possible registers to max integer.
     RegisterPositions freePositions = new RegisterPositions(registerConstraint + 1);
 
-    if ((options().debug || code.method.getOptimizationInfo().isReachabilitySensitive())
-        && !code.method.accessFlags.isStatic()) {
+    if ((options().debug || code.method().getOptimizationInfo().isReachabilitySensitive())
+        && !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
       // the input register.
@@ -2495,7 +2495,7 @@
     // overwritten can therefore lead to verification errors. If we could be targeting one of these
     // VMs we block the receiver register throughout the method.
     if ((options().canHaveThisTypeVerifierBug() || options().canHaveThisJitCodeDebuggingBug())
-        && !code.method.accessFlags.isStatic()) {
+        && !code.method().accessFlags.isStatic()) {
       for (Instruction instruction : code.entryBlock().getInstructions()) {
         if (instruction.isArgument() && instruction.outValue().isThis()) {
           Value thisValue = instruction.outValue();
@@ -2625,7 +2625,7 @@
             }
           }
         }
-        if (options.debug || code.method.getOptimizationInfo().isReachabilitySensitive()) {
+        if (options.debug || code.method().getOptimizationInfo().isReachabilitySensitive()) {
           // 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();
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 e04f07c..9f51daa 100644
--- a/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java
+++ b/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java
@@ -40,7 +40,7 @@
 import com.google.common.collect.ImmutableMap.Builder;
 import java.io.PrintWriter;
 import java.io.StringWriter;
-import java.util.concurrent.ExecutorService;
+import java.util.Optional;
 import org.objectweb.asm.AnnotationVisitor;
 import org.objectweb.asm.ClassReader;
 import org.objectweb.asm.ClassWriter;
@@ -88,24 +88,21 @@
     this.proguardMapSupplier = proguardMapSupplier;
   }
 
-  public void write(ClassFileConsumer consumer, ExecutorService executor) {
+  public void write(ClassFileConsumer consumer) {
     application.timing.begin("CfApplicationWriter.write");
     try {
-      writeApplication(consumer, executor);
+      writeApplication(consumer);
     } finally {
       application.timing.end();
     }
   }
 
-  private void writeApplication(ClassFileConsumer consumer, ExecutorService executor) {
-    ProguardMapSupplier.ProguardMapAndId proguardMapAndId = null;
+  private void writeApplication(ClassFileConsumer consumer) {
     if (proguardMapSupplier != null && options.proguardMapConsumer != null) {
-      proguardMapAndId = proguardMapSupplier.getProguardMapAndId();
-      if (proguardMapAndId != null) {
-        marker.setPgMapId(proguardMapAndId.id);
-      }
+      marker.setPgMapId(proguardMapSupplier.writeProguardMap().get());
     }
-    String markerString = marker.toString();
+    Optional<String> markerString =
+        marker.isRelocator() ? Optional.empty() : Optional.of(marker.toString());
     for (DexProgramClass clazz : application.classes()) {
       if (clazz.getSynthesizedFrom().isEmpty()
           || options.isDesugaredLibraryCompilation()
@@ -116,18 +113,16 @@
       }
     }
     ApplicationWriter.supplyAdditionalConsumers(
-        application,
-        appView,
-        graphLense,
-        namingLens,
-        options,
-        proguardMapAndId == null ? null : proguardMapAndId.map);
+        application, appView, graphLense, namingLens, options);
   }
 
-  private void writeClass(DexProgramClass clazz, ClassFileConsumer consumer, String markerString) {
+  private void writeClass(
+      DexProgramClass clazz, ClassFileConsumer consumer, Optional<String> markerString) {
     ClassWriter writer = new ClassWriter(0);
-    int markerStringPoolIndex = writer.newConst(markerString);
-    assert markerStringPoolIndex == MARKER_STRING_CONSTANT_POOL_INDEX;
+    if (markerString.isPresent()) {
+      int markerStringPoolIndex = writer.newConst(markerString.get());
+      assert markerStringPoolIndex == MARKER_STRING_CONSTANT_POOL_INDEX;
+    }
     String sourceDebug = getSourceDebugExtension(clazz.annotations());
     writer.visitSource(clazz.sourceFile != null ? clazz.sourceFile.toString() : null, sourceDebug);
     int version = getClassFileVersion(clazz);
diff --git a/src/main/java/com/android/tools/r8/kotlin/KmVisitorProviders.java b/src/main/java/com/android/tools/r8/kotlin/KmVisitorProviders.java
new file mode 100644
index 0000000..1ac680a
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/kotlin/KmVisitorProviders.java
@@ -0,0 +1,98 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.kotlin;
+
+import kotlinx.metadata.KmAnnotation;
+import kotlinx.metadata.KmFunctionVisitor;
+import kotlinx.metadata.KmLambdaVisitor;
+import kotlinx.metadata.KmPropertyVisitor;
+import kotlinx.metadata.KmTypeAliasVisitor;
+import kotlinx.metadata.KmTypeParameterVisitor;
+import kotlinx.metadata.KmTypeVisitor;
+import kotlinx.metadata.KmValueParameterVisitor;
+import kotlinx.metadata.KmVariance;
+
+/**
+ * The reason for having these visitor providers is to make the separation of concern a bit easier
+ * while also working with the kotlinx.metadata visitors as shown by the following example:
+ *
+ * <p>Say we have the following structure KotlinTypeInfo: { TypeProjects:
+ * [KotlinTypeProjectionInfo(StarProjection)] }
+ *
+ * <p>Now the KmTypeVisitor (we use to generate the KotlinTypeInfo, has a visitProjection(int flags,
+ * KmVariance variance) generator that will return a new KmTypeVisitor, however, if the projection
+ * is a star projection, the generator visitStarProjection() should be used.
+ *
+ * <p>The information about the projection being a star projection is contained in the
+ * KotlinTypeProjectionInfo. As a result, KotlinTypeInfo should query the object for picking the
+ * right generator, the KotlinTypeProjectionInfo should return a KmTypeProjection object, or we
+ * simply capture the generators lazily (by these providers), such that the object with all the
+ * information can decide when/what object to create.
+ *
+ * <p>Another benefit of this approach than using the build in visitors is that shared structures,
+ * such as KotlinAnnotationInfo that can be on type-aliases, functions and properties will not have
+ * to take in three different type of visitors.
+ */
+public class KmVisitorProviders {
+
+  @FunctionalInterface
+  public interface KmAnnotationVisitorProvider {
+
+    void get(KmAnnotation annotation);
+  }
+
+  @FunctionalInterface
+  public interface KmFunctionVisitorProvider {
+
+    KmFunctionVisitor get(int flags, String name);
+  }
+
+  public interface KmLambdaVisitorProvider {
+
+    KmLambdaVisitor get();
+  }
+
+  @FunctionalInterface
+  public interface KmPropertyVisitorProvider {
+
+    KmPropertyVisitor get(int flags, String name, int getterFlags, int setterFlags);
+  }
+
+  @FunctionalInterface
+  public interface KmTypeAliasVisitorProvider {
+
+    KmTypeAliasVisitor get(int flags, String name);
+  }
+
+  @FunctionalInterface
+  public interface KmTypeParameterVisitorProvider {
+
+    KmTypeParameterVisitor get(int flags, String name, int id, KmVariance variance);
+  }
+
+  @FunctionalInterface
+  public interface KmTypeProjectionVisitorProvider {
+
+    KmTypeVisitor get(int flags, KmVariance variance);
+  }
+
+  @FunctionalInterface
+  public interface KmTypeStarProjectionVisitorProvider {
+
+    void get();
+  }
+
+  @FunctionalInterface
+  public interface KmTypeVisitorProvider {
+
+    KmTypeVisitor get(int flags);
+  }
+
+  @FunctionalInterface
+  public interface KmValueParameterVisitorProvider {
+
+    KmValueParameterVisitor get(int flags, String name);
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/kotlin/Kotlin.java b/src/main/java/com/android/tools/r8/kotlin/Kotlin.java
index 6fe2c18..18b3533 100644
--- a/src/main/java/com/android/tools/r8/kotlin/Kotlin.java
+++ b/src/main/java/com/android/tools/r8/kotlin/Kotlin.java
@@ -4,7 +4,7 @@
 
 package com.android.tools.r8.kotlin;
 
-import com.android.tools.r8.DiagnosticsHandler;
+import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.graph.DexItemFactory;
@@ -44,6 +44,7 @@
   public static final class ClassClassifiers {
 
     public static final String arrayBinaryName = NAME + "/Array";
+    public static final String anyName = NAME + "/Any";
   }
 
   // Mappings from JVM types to Kotlin types (of type DexType)
@@ -193,7 +194,7 @@
   }
 
   // Calculates kotlin info for a class.
-  public KotlinInfo getKotlinInfo(DexClass clazz, DiagnosticsHandler reporter) {
-    return KotlinClassMetadataReader.getKotlinInfo(this, clazz, reporter);
+  public KotlinClassLevelInfo getKotlinInfo(DexClass clazz, AppView<?> appView) {
+    return KotlinClassMetadataReader.getKotlinInfo(this, clazz, appView);
   }
 }
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinAnnotationInfo.java b/src/main/java/com/android/tools/r8/kotlin/KotlinAnnotationInfo.java
new file mode 100644
index 0000000..47ed582
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinAnnotationInfo.java
@@ -0,0 +1,63 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.kotlin;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexString;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.naming.NamingLens;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.utils.DescriptorUtils;
+import com.google.common.collect.ImmutableList;
+import java.util.List;
+import java.util.Map;
+import kotlinx.metadata.KmAnnotation;
+import kotlinx.metadata.KmAnnotationArgument;
+
+// Holds information about a KmAnnotation
+public class KotlinAnnotationInfo {
+
+  private static final List<KotlinAnnotationInfo> EMPTY_ANNOTATIONS = ImmutableList.of();
+
+  private final DexType annotationType;
+  // TODO(b/155053894): Model KmAnnotationArgument.
+  private final Map<String, KmAnnotationArgument<?>> arguments;
+
+  private KotlinAnnotationInfo(
+      DexType annotationType, Map<String, KmAnnotationArgument<?>> arguments) {
+    this.annotationType = annotationType;
+    this.arguments = arguments;
+  }
+
+  private static KotlinAnnotationInfo create(KmAnnotation annotation, AppView<?> appView) {
+    String descriptor = DescriptorUtils.getDescriptorFromClassBinaryName(annotation.getClassName());
+    DexType type = appView.dexItemFactory().createType(descriptor);
+    return new KotlinAnnotationInfo(type, annotation.getArguments());
+  }
+
+  static List<KotlinAnnotationInfo> create(List<KmAnnotation> annotations, AppView<?> appView) {
+    if (annotations.isEmpty()) {
+      return EMPTY_ANNOTATIONS;
+    }
+    ImmutableList.Builder<KotlinAnnotationInfo> builder = ImmutableList.builder();
+    for (KmAnnotation annotation : annotations) {
+      builder.add(create(annotation, appView));
+    }
+    return builder.build();
+  }
+
+  public void rewrite(
+      KmVisitorProviders.KmAnnotationVisitorProvider visitorProvider,
+      AppView<AppInfoWithLiveness> appView,
+      NamingLens namingLens) {
+    if (appView.appInfo().wasPruned(annotationType)) {
+      return;
+    }
+    DexString descriptor = namingLens.lookupDescriptor(annotationType);
+    String classifier = DescriptorUtils.descriptorToKotlinClassifier(descriptor.toString());
+    KmAnnotation annotation = new KmAnnotation(classifier, arguments);
+    visitorProvider.get(annotation);
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinClass.java b/src/main/java/com/android/tools/r8/kotlin/KotlinClass.java
deleted file mode 100644
index 8be3f22..0000000
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinClass.java
+++ /dev/null
@@ -1,201 +0,0 @@
-// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-
-package com.android.tools.r8.kotlin;
-
-import static com.android.tools.r8.kotlin.Kotlin.addKotlinPrefix;
-import static com.android.tools.r8.kotlin.KotlinMetadataSynthesizer.toKmType;
-import static kotlinx.metadata.Flag.IS_SEALED;
-
-import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.DexClass;
-import com.android.tools.r8.graph.DexEncodedField;
-import com.android.tools.r8.graph.DexEncodedMethod;
-import com.android.tools.r8.graph.DexField;
-import com.android.tools.r8.graph.DexProgramClass;
-import com.android.tools.r8.graph.DexString;
-import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.graph.InnerClassAttribute;
-import com.android.tools.r8.graph.SubtypingInfo;
-import com.android.tools.r8.naming.NamingLens;
-import com.android.tools.r8.shaking.AppInfoWithLiveness;
-import java.util.List;
-import kotlinx.metadata.KmClass;
-import kotlinx.metadata.KmConstructor;
-import kotlinx.metadata.KmType;
-import kotlinx.metadata.jvm.KotlinClassHeader;
-import kotlinx.metadata.jvm.KotlinClassMetadata;
-
-public class KotlinClass extends KotlinInfo<KotlinClassMetadata.Class> {
-
-  KmClass kmClass;
-
-  DexField companionObject = null;
-  DexProgramClass hostClass = null;
-
-  static KotlinClass fromKotlinClassMetadata(
-      KotlinClassMetadata kotlinClassMetadata, DexClass clazz) {
-    assert kotlinClassMetadata instanceof KotlinClassMetadata.Class;
-    KotlinClassMetadata.Class kClass = (KotlinClassMetadata.Class) kotlinClassMetadata;
-    return new KotlinClass(kClass, clazz);
-  }
-
-  private KotlinClass(KotlinClassMetadata.Class metadata, DexClass clazz) {
-    super(metadata, clazz);
-  }
-
-  void foundCompanionObject(DexEncodedField companionObject) {
-    // Companion cannot be nested. If this class is a host (and about to store a field that holds
-    // a companion object), it should not have a host class.
-    assert hostClass == null;
-    this.companionObject = companionObject.field;
-  }
-
-  boolean hasCompanionObject() {
-    return companionObject != null;
-  }
-
-  DexType getCompanionObjectType() {
-    return hasCompanionObject() ? companionObject.type : null;
-  }
-
-  void linkHostClass(DexProgramClass hostClass) {
-    // Companion cannot be nested. If this class is a companion object (and about to link to its
-    // host class), it should not have a companion object.
-    assert companionObject == null;
-    this.hostClass = hostClass;
-  }
-
-  @Override
-  void processMetadata(KotlinClassMetadata.Class metadata) {
-    kmClass = metadata.toKmClass();
-  }
-
-  @Override
-  void rewrite(AppView<AppInfoWithLiveness> appView, SubtypingInfo subtypingInfo, NamingLens lens) {
-    KotlinMetadataSynthesizer synthesizer = new KotlinMetadataSynthesizer(appView, lens, this);
-    if (appView.options().enableKotlinMetadataRewritingForRenamedClasses
-        && lens.lookupType(clazz.type, appView.dexItemFactory()) != clazz.type) {
-      String renamedClassifier = synthesizer.toRenamedClassifier(clazz.type);
-      if (renamedClassifier != null) {
-        assert !kmClass.getName().equals(renamedClassifier);
-        kmClass.setName(renamedClassifier);
-      }
-    }
-
-    // Rewriting upward hierarchy.
-    List<KmType> superTypes = kmClass.getSupertypes();
-    superTypes.clear();
-    for (DexType itfType : clazz.interfaces.values) {
-      // TODO(b/129925954): Use GenericSignature.ClassSignature#superInterfaceSignatures
-      KmType kmType = synthesizer.toRenamedKmType(itfType, null, null, getTypeParameters());
-      if (kmType != null) {
-        superTypes.add(kmType);
-      }
-    }
-    assert clazz.superType != null;
-    if (clazz.superType != appView.dexItemFactory().objectType) {
-      // TODO(b/129925954): Use GenericSignature.ClassSignature#superClassSignature
-      KmType kmTypeForSupertype =
-          synthesizer.toRenamedKmType(clazz.superType, null, null, getTypeParameters());
-      if (kmTypeForSupertype != null) {
-        superTypes.add(kmTypeForSupertype);
-      }
-    } else if (clazz.isInterface()) {
-      superTypes.add(toKmType(addKotlinPrefix("Any;")));
-    }
-
-    // Rewriting downward hierarchies: nested, including companion class.
-    // Note that `kotlinc` uses these nested classes to determine which classes to look up when
-    // resolving declarations in the companion object, e.g., Host.Companion.prop and Host.prop.
-    // Thus, users (in particular, library developers) should keep InnerClasses and EnclosingMethod
-    // attributes if declarations in the companion need to be exposed.
-    List<String> nestedClasses = kmClass.getNestedClasses();
-    nestedClasses.clear();
-    for (InnerClassAttribute innerClassAttribute : clazz.getInnerClasses()) {
-      // Skip InnerClass attribute for itself.
-      // Otherwise, an inner class would have itself as a nested class.
-      if (clazz.getInnerClassAttributeForThisClass() == innerClassAttribute) {
-        continue;
-      }
-      DexString renamedInnerName = lens.lookupInnerName(innerClassAttribute, appView.options());
-      if (renamedInnerName != null) {
-        nestedClasses.add(renamedInnerName.toString());
-      }
-    }
-
-    // Rewriting downward hierarchies: sealed.
-    List<String> sealedSubclasses = kmClass.getSealedSubclasses();
-    sealedSubclasses.clear();
-    if (IS_SEALED.invoke(kmClass.getFlags())) {
-      for (DexType subtype : subtypingInfo.allImmediateSubtypes(clazz.type)) {
-        String classifier = synthesizer.toRenamedClassifier(subtype);
-        if (classifier != null) {
-          sealedSubclasses.add(classifier);
-        }
-      }
-    }
-
-    if (!appView.options().enableKotlinMetadataRewritingForMembers) {
-      return;
-    }
-
-    // Rewriting constructors.
-    List<KmConstructor> constructors = kmClass.getConstructors();
-    constructors.clear();
-    for (DexEncodedMethod method : clazz.directMethods()) {
-      if (!method.isInstanceInitializer()) {
-        continue;
-      }
-      KmConstructor constructor = synthesizer.toRenamedKmConstructor(clazz, method);
-      if (constructor != null) {
-        constructors.add(constructor);
-      }
-    }
-
-    // Rewriting companion object if any.
-    if (kmClass.getCompanionObject() != null && hasCompanionObject()) {
-      kmClass.setCompanionObject(lens.lookupName(companionObject).toString());
-    }
-
-    // TODO(b/151193864): enum entries
-
-    rewriteDeclarationContainer(synthesizer);
-  }
-
-  @Override
-  KotlinClassHeader createHeader() {
-    KotlinClassMetadata.Class.Writer writer = new KotlinClassMetadata.Class.Writer();
-    kmClass.accept(writer);
-    return writer.write().getHeader();
-  }
-
-  @Override
-  public Kind getKind() {
-    return Kind.Class;
-  }
-
-  @Override
-  public boolean isClass() {
-    return true;
-  }
-
-  @Override
-  public KotlinClass asClass() {
-    return this;
-  }
-
-  @Override
-  public String toString(String indent) {
-    StringBuilder sb = new StringBuilder(indent);
-    appendKmSection(
-        indent,
-        "Metadata.Class",
-        sb,
-        newIndent -> {
-          appendKmClass(newIndent, sb, kmClass);
-        });
-    return sb.toString();
-  }
-}
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinClassFacade.java b/src/main/java/com/android/tools/r8/kotlin/KotlinClassFacade.java
deleted file mode 100644
index 2a60521..0000000
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinClassFacade.java
+++ /dev/null
@@ -1,87 +0,0 @@
-// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-
-package com.android.tools.r8.kotlin;
-
-import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.DexClass;
-import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.graph.SubtypingInfo;
-import com.android.tools.r8.naming.NamingLens;
-import com.android.tools.r8.shaking.AppInfoWithLiveness;
-import com.android.tools.r8.utils.DescriptorUtils;
-import com.android.tools.r8.utils.StringUtils;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.ListIterator;
-import kotlinx.metadata.jvm.KotlinClassHeader;
-import kotlinx.metadata.jvm.KotlinClassMetadata;
-
-public final class KotlinClassFacade extends KotlinInfo<KotlinClassMetadata.MultiFileClassFacade> {
-
-  // TODO(b/151194869): is it better to maintain List<DexType>?
-  List<String> partClassNames;
-
-  static KotlinClassFacade fromKotlinClassMetadata(
-      KotlinClassMetadata kotlinClassMetadata, DexClass clazz) {
-    assert kotlinClassMetadata instanceof KotlinClassMetadata.MultiFileClassFacade;
-    KotlinClassMetadata.MultiFileClassFacade multiFileClassFacade =
-        (KotlinClassMetadata.MultiFileClassFacade) kotlinClassMetadata;
-    return new KotlinClassFacade(multiFileClassFacade, clazz);
-  }
-
-  private KotlinClassFacade(KotlinClassMetadata.MultiFileClassFacade metadata, DexClass clazz) {
-    super(metadata, clazz);
-  }
-
-  @Override
-  void processMetadata(KotlinClassMetadata.MultiFileClassFacade metadata) {
-    // Part Class names are stored in `d1`, which is immutable. Make a copy instead.
-    partClassNames = new ArrayList<>(metadata.getPartClassNames());
-    // No API to explore metadata details, hence nothing further to do.
-  }
-
-  @Override
-  void rewrite(AppView<AppInfoWithLiveness> appView, SubtypingInfo subtypingInfo, NamingLens lens) {
-    ListIterator<String> partClassIterator = partClassNames.listIterator();
-    KotlinMetadataSynthesizer synthesizer = new KotlinMetadataSynthesizer(appView, lens, this);
-    while (partClassIterator.hasNext()) {
-      String partClassName = partClassIterator.next();
-      partClassIterator.remove();
-      DexType partClassType = appView.dexItemFactory().createType(
-          DescriptorUtils.getDescriptorFromClassBinaryName(partClassName));
-      String renamedPartClassName = synthesizer.toRenamedBinaryName(partClassType);
-      if (renamedPartClassName != null) {
-        partClassIterator.add(renamedPartClassName);
-      }
-    }
-  }
-
-  @Override
-  KotlinClassHeader createHeader() {
-    KotlinClassMetadata.MultiFileClassFacade.Writer writer =
-        new KotlinClassMetadata.MultiFileClassFacade.Writer();
-    return writer.write(partClassNames).getHeader();
-  }
-
-  @Override
-  public Kind getKind() {
-    return Kind.Facade;
-  }
-
-  @Override
-  public boolean isClassFacade() {
-    return true;
-  }
-
-  @Override
-  public KotlinClassFacade asClassFacade() {
-    return this;
-  }
-
-  @Override
-  public String toString(String indent) {
-    return indent + "MetaData.MultiFileClassFacade(" + StringUtils.join(partClassNames, ", ") + ")";
-  }
-}
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinClassInfo.java b/src/main/java/com/android/tools/r8/kotlin/KotlinClassInfo.java
new file mode 100644
index 0000000..b2f71cb
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinClassInfo.java
@@ -0,0 +1,271 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.kotlin;
+
+import static com.android.tools.r8.kotlin.KotlinMetadataUtils.toJvmFieldSignature;
+import static com.android.tools.r8.kotlin.KotlinMetadataUtils.toJvmMethodSignature;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexEncodedField;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexString;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.naming.NamingLens;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.utils.DescriptorUtils;
+import com.google.common.collect.ImmutableList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import kotlinx.metadata.KmClass;
+import kotlinx.metadata.KmConstructor;
+import kotlinx.metadata.KmType;
+import kotlinx.metadata.jvm.JvmExtensionsKt;
+import kotlinx.metadata.jvm.JvmMethodSignature;
+import kotlinx.metadata.jvm.KotlinClassHeader;
+import kotlinx.metadata.jvm.KotlinClassMetadata;
+
+public class KotlinClassInfo implements KotlinClassLevelInfo {
+
+  private final int flags;
+  private final String name;
+  private final String moduleName;
+  private final List<KotlinConstructorInfo> constructorsWithNoBacking;
+  private final KotlinDeclarationContainerInfo declarationContainerInfo;
+  private final List<KotlinTypeParameterInfo> typeParameters;
+  private final List<KotlinTypeInfo> superTypes;
+  private final List<DexType> sealedSubClasses;
+  private final List<DexType> nestedClasses;
+  // TODO(b/154347404): Understand enum entries.
+  private final List<String> enumEntries;
+  private final DexType anonymousObjectOrigin;
+
+  public KotlinClassInfo(
+      int flags,
+      String name,
+      String moduleName,
+      KotlinDeclarationContainerInfo declarationContainerInfo,
+      List<KotlinTypeParameterInfo> typeParameters,
+      List<KotlinConstructorInfo> constructorsWithNoBacking,
+      List<KotlinTypeInfo> superTypes,
+      List<DexType> sealedSubClasses,
+      List<DexType> nestedClasses,
+      List<String> enumEntries,
+      DexType anonymousObjectOrigin) {
+    this.flags = flags;
+    this.name = name;
+    this.moduleName = moduleName;
+    this.declarationContainerInfo = declarationContainerInfo;
+    this.typeParameters = typeParameters;
+    this.constructorsWithNoBacking = constructorsWithNoBacking;
+    this.superTypes = superTypes;
+    this.sealedSubClasses = sealedSubClasses;
+    this.nestedClasses = nestedClasses;
+    this.enumEntries = enumEntries;
+    this.anonymousObjectOrigin = anonymousObjectOrigin;
+  }
+
+  public static KotlinClassInfo create(KmClass kmClass, DexClass hostClass, AppView<?> appView) {
+    Map<String, DexEncodedField> fieldMap = new HashMap<>();
+    for (DexEncodedField field : hostClass.fields()) {
+      fieldMap.put(toJvmFieldSignature(field.field).asString(), field);
+    }
+    Map<String, DexEncodedMethod> methodMap = new HashMap<>();
+    for (DexEncodedMethod method : hostClass.methods()) {
+      methodMap.put(toJvmMethodSignature(method.method).asString(), method);
+    }
+    ImmutableList.Builder<KotlinConstructorInfo> notBackedConstructors = ImmutableList.builder();
+    for (KmConstructor kmConstructor : kmClass.getConstructors()) {
+      KotlinConstructorInfo constructorInfo = KotlinConstructorInfo.create(kmConstructor, appView);
+      JvmMethodSignature signature = JvmExtensionsKt.getSignature(kmConstructor);
+      if (signature != null) {
+        DexEncodedMethod method = methodMap.get(signature.asString());
+        if (method != null) {
+          method.setKotlinMemberInfo(constructorInfo);
+          continue;
+        }
+      }
+      // We could not find a definition for the constructor - add it to ensure the same output.
+      notBackedConstructors.add(constructorInfo);
+    }
+    KotlinDeclarationContainerInfo container =
+        KotlinDeclarationContainerInfo.create(kmClass, methodMap, fieldMap, appView);
+    setCompanionObject(kmClass, hostClass, appView);
+    return new KotlinClassInfo(
+        kmClass.getFlags(),
+        kmClass.name,
+        JvmExtensionsKt.getModuleName(kmClass),
+        container,
+        KotlinTypeParameterInfo.create(kmClass.getTypeParameters(), appView),
+        notBackedConstructors.build(),
+        getSuperTypes(kmClass.getSupertypes(), appView),
+        getSealedSubClasses(hostClass, kmClass.getSealedSubclasses(), appView),
+        getNestedClasses(hostClass, kmClass.getNestedClasses(), appView),
+        kmClass.getEnumEntries(),
+        getAnonymousObjectOrigin(kmClass, appView));
+  }
+
+  private static DexType getAnonymousObjectOrigin(KmClass kmClass, AppView<?> appView) {
+    String anonymousObjectOriginName = JvmExtensionsKt.getAnonymousObjectOriginName(kmClass);
+    if (anonymousObjectOriginName != null) {
+      return appView
+          .dexItemFactory()
+          .createType(DescriptorUtils.getDescriptorFromClassBinaryName(anonymousObjectOriginName));
+    }
+    return null;
+  }
+
+  private static List<DexType> getNestedClasses(
+      DexClass clazz, List<String> nestedClasses, AppView<?> appView) {
+    ImmutableList.Builder<DexType> nestedTypes = ImmutableList.builder();
+    for (String nestedClass : nestedClasses) {
+      String binaryName =
+          clazz.type.toBinaryName() + DescriptorUtils.INNER_CLASS_SEPARATOR + nestedClass;
+      DexType nestedType =
+          appView
+              .dexItemFactory()
+              .createType(DescriptorUtils.getDescriptorFromClassBinaryName(binaryName));
+      nestedTypes.add(nestedType);
+    }
+    return nestedTypes.build();
+  }
+
+  private static List<DexType> getSealedSubClasses(
+      DexClass clazz, List<String> sealedSubclasses, AppView<?> appView) {
+    ImmutableList.Builder<DexType> sealedTypes = ImmutableList.builder();
+    for (String sealedSubClass : sealedSubclasses) {
+      String binaryName =
+          sealedSubClass.replace(
+              DescriptorUtils.JAVA_PACKAGE_SEPARATOR, DescriptorUtils.INNER_CLASS_SEPARATOR);
+      DexType sealedType =
+          appView
+              .dexItemFactory()
+              .createType(DescriptorUtils.getDescriptorFromClassBinaryName(binaryName));
+      sealedTypes.add(sealedType);
+    }
+    return sealedTypes.build();
+  }
+
+  private static List<KotlinTypeInfo> getSuperTypes(List<KmType> superTypes, AppView<?> appView) {
+    ImmutableList.Builder<KotlinTypeInfo> superTypeInfos = ImmutableList.builder();
+    for (KmType superType : superTypes) {
+      superTypeInfos.add(KotlinTypeInfo.create(superType, appView));
+    }
+    return superTypeInfos.build();
+  }
+
+  private static void setCompanionObject(KmClass kmClass, DexClass hostClass, AppView<?> appView) {
+    String companionObjectName = kmClass.getCompanionObject();
+    if (companionObjectName == null) {
+      return;
+    }
+    for (DexEncodedField field : hostClass.fields()) {
+      if (field.field.name.toString().equals(companionObjectName)) {
+        field.setKotlinMemberInfo(new KotlinCompanionInfo());
+        return;
+      }
+    }
+    appView
+        .options()
+        .reporter
+        .warning(KotlinMetadataDiagnostic.missingCompanionObject(hostClass, companionObjectName));
+  }
+
+  @Override
+  public boolean isClass() {
+    return true;
+  }
+
+  @Override
+  public KotlinClassInfo asClass() {
+    return this;
+  }
+
+  @Override
+  public KotlinClassHeader rewrite(
+      DexClass clazz, AppView<AppInfoWithLiveness> appView, NamingLens namingLens) {
+    KmClass kmClass = new KmClass();
+    // TODO(b/154348683): Set flags.
+    kmClass.setFlags(flags);
+    // Set potentially renamed class name.
+    DexString originalDescriptor = clazz.type.descriptor;
+    DexString rewrittenDescriptor = namingLens.lookupDescriptor(clazz.type);
+    // If the original descriptor equals the rewritten descriptor, we pick the original name
+    // to preserve potential errors in the original name. As an example, the kotlin stdlib has
+    // name: .kotlin/collections/CollectionsKt___CollectionsKt$groupingBy$1, which seems incorrect.
+    kmClass.setName(
+        originalDescriptor.equals(rewrittenDescriptor)
+            ? this.name
+            : KotlinMetadataUtils.kotlinNameFromDescriptor(rewrittenDescriptor));
+    // Find a companion object.
+    for (DexEncodedField field : clazz.fields()) {
+      if (field.getKotlinMemberInfo().isCompanion()) {
+        field.getKotlinMemberInfo().asCompanion().rewrite(kmClass, field.field, namingLens);
+      }
+    }
+    // Take all not backed constructors because we will never find them in definitions.
+    for (KotlinConstructorInfo constructorInfo : constructorsWithNoBacking) {
+      constructorInfo.rewrite(kmClass, null, appView, namingLens);
+    }
+    // Find all constructors.
+    for (DexEncodedMethod method : clazz.methods()) {
+      if (method.getKotlinMemberInfo().isConstructor()) {
+        KotlinConstructorInfo constructorInfo = method.getKotlinMemberInfo().asConstructor();
+        constructorInfo.rewrite(kmClass, method, appView, namingLens);
+      }
+    }
+    // Rewrite functions, type-aliases and type-parameters.
+    declarationContainerInfo.rewrite(
+        kmClass::visitFunction,
+        kmClass::visitProperty,
+        kmClass::visitTypeAlias,
+        clazz,
+        appView,
+        namingLens);
+    // Rewrite type parameters.
+    for (KotlinTypeParameterInfo typeParameter : typeParameters) {
+      typeParameter.rewrite(kmClass::visitTypeParameter, appView, namingLens);
+    }
+    // Rewrite super types.
+    for (KotlinTypeInfo superType : superTypes) {
+      superType.rewrite(kmClass::visitSupertype, appView, namingLens);
+    }
+    // Rewrite nested classes.
+    for (DexType nestedClass : nestedClasses) {
+      if (appView.definitionFor(nestedClass) != null) {
+        String descriptor =
+            KotlinMetadataUtils.kotlinNameFromDescriptor(namingLens.lookupDescriptor(nestedClass));
+        // If the class is a nested class, it should be on the form Foo.Bar$Baz, where Baz is the
+        // name we should record.
+        int innerClassIndex = descriptor.lastIndexOf(DescriptorUtils.INNER_CLASS_SEPARATOR);
+        kmClass.visitNestedClass(descriptor.substring(innerClassIndex + 1));
+      }
+    }
+    // Rewrite sealed sub classes.
+    for (DexType sealedSubClass : sealedSubClasses) {
+      if (appView.definitionFor(sealedSubClass) != null) {
+        String descriptor =
+            KotlinMetadataUtils.kotlinNameFromDescriptor(
+                namingLens.lookupDescriptor(sealedSubClass));
+        kmClass.visitSealedSubclass(
+            descriptor.replace(
+                DescriptorUtils.INNER_CLASS_SEPARATOR, DescriptorUtils.JAVA_PACKAGE_SEPARATOR));
+      }
+    }
+    // TODO(b/154347404): Understand enum entries.
+    kmClass.getEnumEntries().addAll(enumEntries);
+
+    JvmExtensionsKt.setModuleName(kmClass, moduleName);
+    if (anonymousObjectOrigin != null) {
+      JvmExtensionsKt.setAnonymousObjectOriginName(
+          kmClass, KotlinMetadataUtils.kotlinNameFromDescriptor(anonymousObjectOrigin.descriptor));
+    }
+
+    KotlinClassMetadata.Class.Writer writer = new KotlinClassMetadata.Class.Writer();
+    kmClass.accept(writer);
+    return writer.write().getHeader();
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinClassLevelInfo.java b/src/main/java/com/android/tools/r8/kotlin/KotlinClassLevelInfo.java
new file mode 100644
index 0000000..76bfb00
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinClassLevelInfo.java
@@ -0,0 +1,57 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.kotlin;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.naming.NamingLens;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import kotlinx.metadata.jvm.KotlinClassHeader;
+
+public interface KotlinClassLevelInfo {
+
+  default boolean isClass() {
+    return false;
+  }
+
+  default KotlinClassInfo asClass() {
+    return null;
+  }
+
+  default boolean isFileFacade() {
+    return false;
+  }
+
+  default KotlinFileFacadeInfo asFileFacade() {
+    return null;
+  }
+
+  default boolean isMultiFileFacade() {
+    return false;
+  }
+
+  default KotlinMultiFileClassFacadeInfo asMultiFileFacade() {
+    return null;
+  }
+
+  default boolean isMultiFileClassPart() {
+    return false;
+  }
+
+  default KotlinMultiFileClassPartInfo asMultiFileClassPart() {
+    return null;
+  }
+
+  default boolean isSyntheticClass() {
+    return false;
+  }
+
+  default KotlinSyntheticClassInfo asSyntheticClass() {
+    return null;
+  }
+
+  KotlinClassHeader rewrite(
+      DexClass clazz, AppView<AppInfoWithLiveness> appView, NamingLens namingLens);
+}
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinClassMetadataReader.java b/src/main/java/com/android/tools/r8/kotlin/KotlinClassMetadataReader.java
index e49d99d..13ae5f5 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinClassMetadataReader.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinClassMetadataReader.java
@@ -1,9 +1,11 @@
-// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.kotlin;
 
-import com.android.tools.r8.DiagnosticsHandler;
+import static com.android.tools.r8.kotlin.KotlinMetadataUtils.NO_KOTLIN_INFO;
+
+import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexAnnotation;
 import com.android.tools.r8.graph.DexAnnotationElement;
 import com.android.tools.r8.graph.DexClass;
@@ -20,28 +22,38 @@
 
 public final class KotlinClassMetadataReader {
 
-  static KotlinInfo getKotlinInfo(
-      Kotlin kotlin,
-      DexClass clazz,
-      DiagnosticsHandler reporter) {
+  public static KotlinClassLevelInfo getKotlinInfo(
+      Kotlin kotlin, DexClass clazz, AppView<?> appView) {
     DexAnnotation meta = clazz.annotations().getFirstMatching(kotlin.metadata.kotlinMetadataType);
     if (meta != null) {
       try {
-        return createKotlinInfo(kotlin, clazz, meta.annotation);
+        return createKotlinInfo(kotlin, clazz, meta.annotation, appView);
       } catch (ClassCastException | InconsistentKotlinMetadataException | MetadataError e) {
-        reporter.info(
-            new StringDiagnostic("Class " + clazz.type.toSourceString()
-                + " has malformed kotlin.Metadata: " + e.getMessage()));
+        appView
+            .options()
+            .reporter
+            .info(
+                new StringDiagnostic(
+                    "Class "
+                        + clazz.type.toSourceString()
+                        + " has malformed kotlin.Metadata: "
+                        + e.getMessage()));
       } catch (Throwable e) {
-        reporter.info(
-            new StringDiagnostic("Unexpected error while reading " + clazz.type.toSourceString()
-                + "'s kotlin.Metadata: " + e.getMessage()));
+        appView
+            .options()
+            .reporter
+            .info(
+                new StringDiagnostic(
+                    "Unexpected error while reading "
+                        + clazz.type.toSourceString()
+                        + "'s kotlin.Metadata: "
+                        + e.getMessage()));
       }
     }
-    return null;
+    return NO_KOTLIN_INFO;
   }
 
-  private static KotlinClassMetadata toKotlinClassMetadata(
+  public static KotlinClassMetadata toKotlinClassMetadata(
       Kotlin kotlin, DexEncodedAnnotation metadataAnnotation) {
     Map<DexString, DexAnnotationElement> elementMap = new IdentityHashMap<>();
     for (DexAnnotationElement element : metadataAnnotation.elements) {
@@ -72,23 +84,28 @@
     return KotlinClassMetadata.read(header);
   }
 
-  public static KotlinInfo createKotlinInfo(
-      Kotlin kotlin, DexClass clazz, DexEncodedAnnotation metadataAnnotation) {
+  public static KotlinClassLevelInfo createKotlinInfo(
+      Kotlin kotlin, DexClass clazz, DexEncodedAnnotation metadataAnnotation, AppView<?> appView) {
     KotlinClassMetadata kMetadata = toKotlinClassMetadata(kotlin, metadataAnnotation);
 
     if (kMetadata instanceof KotlinClassMetadata.Class) {
-      return KotlinClass.fromKotlinClassMetadata(kMetadata, clazz);
+      return KotlinClassInfo.create(
+          ((KotlinClassMetadata.Class) kMetadata).toKmClass(), clazz, appView);
     } else if (kMetadata instanceof KotlinClassMetadata.FileFacade) {
       // e.g., B.kt becomes class `BKt`
-      return KotlinFile.fromKotlinClassMetadata(kMetadata, clazz);
+      return KotlinFileFacadeInfo.create(
+          (KotlinClassMetadata.FileFacade) kMetadata, clazz, appView);
     } else if (kMetadata instanceof KotlinClassMetadata.MultiFileClassFacade) {
       // multi-file class with the same @JvmName.
-      return KotlinClassFacade.fromKotlinClassMetadata(kMetadata, clazz);
+      return KotlinMultiFileClassFacadeInfo.create(
+          (KotlinClassMetadata.MultiFileClassFacade) kMetadata, appView);
     } else if (kMetadata instanceof KotlinClassMetadata.MultiFileClassPart) {
       // A single file, which is part of multi-file class.
-      return KotlinClassPart.fromKotlinClassMetadata(kMetadata, clazz);
+      return KotlinMultiFileClassPartInfo.create(
+          (KotlinClassMetadata.MultiFileClassPart) kMetadata, clazz, appView);
     } else if (kMetadata instanceof KotlinClassMetadata.SyntheticClass) {
-      return KotlinSyntheticClass.fromKotlinClassMetadata(kMetadata, kotlin, clazz);
+      return KotlinSyntheticClassInfo.create(
+          (KotlinClassMetadata.SyntheticClass) kMetadata, clazz, kotlin, appView);
     } else {
       throw new MetadataError("unsupported 'k' value: " + kMetadata.getHeader().getKind());
     }
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinClassPart.java b/src/main/java/com/android/tools/r8/kotlin/KotlinClassPart.java
deleted file mode 100644
index f0d15a4..0000000
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinClassPart.java
+++ /dev/null
@@ -1,97 +0,0 @@
-// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-
-package com.android.tools.r8.kotlin;
-
-import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.DexClass;
-import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.graph.SubtypingInfo;
-import com.android.tools.r8.naming.NamingLens;
-import com.android.tools.r8.shaking.AppInfoWithLiveness;
-import com.android.tools.r8.utils.DescriptorUtils;
-import kotlinx.metadata.KmPackage;
-import kotlinx.metadata.jvm.KotlinClassHeader;
-import kotlinx.metadata.jvm.KotlinClassMetadata;
-
-public final class KotlinClassPart extends KotlinInfo<KotlinClassMetadata.MultiFileClassPart> {
-
-  KmPackage kmPackage;
-  // TODO(b/151194869): is it better to maintain DexType?
-  String facadeClassName;
-
-  static KotlinClassPart fromKotlinClassMetadata(
-      KotlinClassMetadata kotlinClassMetadata, DexClass clazz) {
-    assert kotlinClassMetadata instanceof KotlinClassMetadata.MultiFileClassPart;
-    KotlinClassMetadata.MultiFileClassPart multiFileClassPart =
-        (KotlinClassMetadata.MultiFileClassPart) kotlinClassMetadata;
-    return new KotlinClassPart(multiFileClassPart, clazz);
-  }
-
-  private KotlinClassPart(KotlinClassMetadata.MultiFileClassPart metadata, DexClass clazz) {
-    super(metadata, clazz);
-  }
-
-  @Override
-  void processMetadata(KotlinClassMetadata.MultiFileClassPart metadata) {
-    kmPackage = metadata.toKmPackage();
-    facadeClassName = metadata.getFacadeClassName();
-  }
-
-  @Override
-  void rewrite(AppView<AppInfoWithLiveness> appView, SubtypingInfo subtypingInfo, NamingLens lens) {
-    DexType facadeClassType = appView.dexItemFactory().createType(
-        DescriptorUtils.getDescriptorFromClassBinaryName(facadeClassName));
-    KotlinMetadataSynthesizer synthesizer = new KotlinMetadataSynthesizer(appView, lens, this);
-    facadeClassName = synthesizer.toRenamedBinaryName(facadeClassType);
-    if (!appView.options().enableKotlinMetadataRewritingForMembers) {
-      return;
-    }
-    rewriteDeclarationContainer(synthesizer);
-  }
-
-  @Override
-  KotlinClassHeader createHeader() {
-    if (facadeClassName != null) {
-      KotlinClassMetadata.MultiFileClassPart.Writer writer =
-          new KotlinClassMetadata.MultiFileClassPart.Writer();
-      kmPackage.accept(writer);
-      return writer.write(facadeClassName).getHeader();
-    } else {
-      // It's no longer part of multi-file class.
-      KotlinClassMetadata.FileFacade.Writer writer = new KotlinClassMetadata.FileFacade.Writer();
-      kmPackage.accept(writer);
-      return writer.write().getHeader();
-    }
-  }
-
-  @Override
-  public Kind getKind() {
-    return Kind.Part;
-  }
-
-  @Override
-  public boolean isClassPart() {
-    return true;
-  }
-
-  @Override
-  public KotlinClassPart asClassPart() {
-    return this;
-  }
-
-  @Override
-  public String toString(String indent) {
-    StringBuilder sb = new StringBuilder(indent);
-    appendKmSection(
-        indent,
-        "Metadata.MultiFileClassPart",
-        sb,
-        newIndent -> {
-          appendKeyValue(newIndent, "facadeClassName", sb, facadeClassName);
-          appendKmPackage(newIndent, sb, kmPackage);
-        });
-    return sb.toString();
-  }
-}
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinClassifierInfo.java b/src/main/java/com/android/tools/r8/kotlin/KotlinClassifierInfo.java
index c7c3701..86db718 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinClassifierInfo.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinClassifierInfo.java
@@ -4,46 +4,129 @@
 
 package com.android.tools.r8.kotlin;
 
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexString;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.kotlin.Kotlin.ClassClassifiers;
+import com.android.tools.r8.naming.NamingLens;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.utils.DescriptorUtils;
 import kotlinx.metadata.KmClassifier;
+import kotlinx.metadata.KmClassifier.TypeAlias;
+import kotlinx.metadata.KmClassifier.TypeParameter;
+import kotlinx.metadata.KmTypeVisitor;
 
-// Provides access to information about a kotlin classifier
-public class KotlinClassifierInfo {
+public abstract class KotlinClassifierInfo {
 
-  private static boolean isClass(KmClassifier classifier) {
-    return classifier instanceof KmClassifier.Class;
-  }
-
-  private static KmClassifier.Class getClassClassifier(KmClassifier classifier) {
-    return (KmClassifier.Class) classifier;
-  }
-
-  private static boolean isTypeAlias(KmClassifier classifier) {
-    return classifier instanceof KmClassifier.TypeAlias;
-  }
-
-  private static KmClassifier.TypeAlias getTypeAlias(KmClassifier classifier) {
-    return (KmClassifier.TypeAlias) classifier;
-  }
-
-  private static boolean isTypeParameter(KmClassifier classifier) {
-    return classifier instanceof KmClassifier.TypeParameter;
-  }
-
-  private static KmClassifier.TypeParameter getTypeParameter(KmClassifier classifier) {
-    return (KmClassifier.TypeParameter) classifier;
-  }
-
-  public static boolean equals(KmClassifier one, KmClassifier other) {
-    if (isClass(one)) {
-      return isClass(other)
-          && getClassClassifier(one).getName().equals(getClassClassifier(other).getName());
+  public static KotlinClassifierInfo create(KmClassifier classifier, AppView<?> appView) {
+    if (classifier instanceof KmClassifier.Class) {
+      String typeName = ((KmClassifier.Class) classifier).getName();
+      // If this name starts with '.', it represents a local class or an anonymous object. This is
+      // used by the Kotlin compiler to prevent lookup of this name in the resolution:
+      // .kotlin/random/FallbackThreadLocalRandom$implStorage$1
+      if (typeName.startsWith(".")) {
+        return new KotlinUnknownClassClassifierInfo(typeName);
+      }
+      String descriptor = DescriptorUtils.getDescriptorFromKotlinClassifier(typeName);
+      if (DescriptorUtils.isClassDescriptor(descriptor)) {
+        DexType type = appView.dexItemFactory().createType(descriptor);
+        return new KotlinClassClassifierInfo(type);
+      } else {
+        return new KotlinUnknownClassClassifierInfo(typeName);
+      }
+    } else if (classifier instanceof KmClassifier.TypeAlias) {
+      return new KotlinTypeAliasClassifierInfo(((TypeAlias) classifier).getName());
+    } else if (classifier instanceof KmClassifier.TypeParameter) {
+      return new KotlinTypeParameterClassifierInfo(((TypeParameter) classifier).getId());
+    } else {
+      appView
+          .options()
+          .reporter
+          .warning(KotlinMetadataDiagnostic.unknownClassifier(classifier.toString()));
+      return new KotlinUnknownClassifierInfo(classifier.toString());
     }
-    if (isTypeAlias(one)) {
-      return isTypeAlias(other)
-          && getTypeAlias(one).getName().equals(getTypeAlias(other).getName());
+  }
+
+  abstract void rewrite(
+      KmTypeVisitor visitor, AppView<AppInfoWithLiveness> appView, NamingLens namingLens);
+
+  public static class KotlinClassClassifierInfo extends KotlinClassifierInfo {
+
+    private final DexType type;
+
+    private KotlinClassClassifierInfo(DexType type) {
+      this.type = type;
     }
-    assert isTypeParameter(one);
-    return isTypeParameter(other)
-        && getTypeParameter(one).getId() == getTypeParameter(other).getId();
+
+    @Override
+    void rewrite(
+        KmTypeVisitor visitor, AppView<AppInfoWithLiveness> appView, NamingLens namingLens) {
+      String classifier;
+      if (appView.appInfo().wasPruned(type)) {
+        classifier = ClassClassifiers.anyName;
+      } else {
+        DexString descriptor = namingLens.lookupDescriptor(type);
+        classifier = DescriptorUtils.descriptorToKotlinClassifier(descriptor.toString());
+      }
+      visitor.visitClass(classifier);
+    }
+  }
+
+  public static class KotlinTypeParameterClassifierInfo extends KotlinClassifierInfo {
+
+    private final int typeId;
+
+    private KotlinTypeParameterClassifierInfo(int typeId) {
+      this.typeId = typeId;
+    }
+
+    @Override
+    void rewrite(
+        KmTypeVisitor visitor, AppView<AppInfoWithLiveness> appView, NamingLens namingLens) {
+      visitor.visitTypeParameter(typeId);
+    }
+  }
+
+  public static class KotlinTypeAliasClassifierInfo extends KotlinClassifierInfo {
+
+    private final String typeAlias;
+
+    private KotlinTypeAliasClassifierInfo(String typeAlias) {
+      this.typeAlias = typeAlias;
+    }
+
+    @Override
+    void rewrite(
+        KmTypeVisitor visitor, AppView<AppInfoWithLiveness> appView, NamingLens namingLens) {
+      visitor.visitTypeAlias(typeAlias);
+    }
+  }
+
+  public static class KotlinUnknownClassClassifierInfo extends KotlinClassifierInfo {
+    private final String classifier;
+
+    private KotlinUnknownClassClassifierInfo(String classifier) {
+      this.classifier = classifier;
+    }
+
+    @Override
+    void rewrite(
+        KmTypeVisitor visitor, AppView<AppInfoWithLiveness> appView, NamingLens namingLens) {
+      visitor.visitClass(classifier);
+    }
+  }
+
+  public static class KotlinUnknownClassifierInfo extends KotlinClassifierInfo {
+    private final String classifier;
+
+    private KotlinUnknownClassifierInfo(String classifier) {
+      this.classifier = classifier;
+    }
+
+    @Override
+    void rewrite(
+        KmTypeVisitor visitor, AppView<AppInfoWithLiveness> appView, NamingLens namingLens) {
+      visitor.visitTypeAlias(classifier);
+    }
   }
 }
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinCompanionInfo.java b/src/main/java/com/android/tools/r8/kotlin/KotlinCompanionInfo.java
new file mode 100644
index 0000000..2f97b4f
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinCompanionInfo.java
@@ -0,0 +1,30 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.kotlin;
+
+import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.graph.DexString;
+import com.android.tools.r8.naming.NamingLens;
+import kotlinx.metadata.KmClassVisitor;
+
+// Structure around a kotlin companion object that can be assigned to a field.
+public class KotlinCompanionInfo implements KotlinFieldLevelInfo {
+
+  @Override
+  public boolean isCompanion() {
+    return true;
+  }
+
+  @Override
+  public KotlinCompanionInfo asCompanion() {
+    return this;
+  }
+
+  public void rewrite(KmClassVisitor visitor, DexField field, NamingLens lens) {
+    DexString dexString = lens.lookupName(field);
+    String finalName = dexString.toString();
+    visitor.visitCompanionObject(finalName);
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinConstructorInfo.java b/src/main/java/com/android/tools/r8/kotlin/KotlinConstructorInfo.java
new file mode 100644
index 0000000..3e9ee04
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinConstructorInfo.java
@@ -0,0 +1,69 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.kotlin;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.naming.NamingLens;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import java.util.List;
+import kotlinx.metadata.KmClass;
+import kotlinx.metadata.KmConstructor;
+import kotlinx.metadata.jvm.JvmExtensionsKt;
+
+// Holds information about a KmConstructor object.
+public class KotlinConstructorInfo implements KotlinMethodLevelInfo {
+
+  // Information from original KmValueParameter(s) if available.
+  private final int flags;
+  // Information about the value parameters.
+  private final List<KotlinValueParameterInfo> valueParameterInfos;
+  // Information about the signature.
+  private final KotlinJvmMethodSignatureInfo signature;
+
+  private KotlinConstructorInfo(
+      int flags,
+      List<KotlinValueParameterInfo> valueParameterInfos,
+      KotlinJvmMethodSignatureInfo signature) {
+    this.flags = flags;
+    this.valueParameterInfos = valueParameterInfos;
+    this.signature = signature;
+  }
+
+  public static KotlinConstructorInfo create(KmConstructor kmConstructor, AppView<?> appView) {
+    return new KotlinConstructorInfo(
+        kmConstructor.getFlags(),
+        KotlinValueParameterInfo.create(kmConstructor.getValueParameters(), appView),
+        KotlinJvmMethodSignatureInfo.create(JvmExtensionsKt.getSignature(kmConstructor), appView));
+  }
+
+  public void rewrite(
+      KmClass kmClass,
+      DexEncodedMethod method,
+      AppView<AppInfoWithLiveness> appView,
+      NamingLens namingLens) {
+    // Note that JvmExtensionsKt.setSignature does not have an overload for KmConstructorVisitor,
+    // thus we rely on creating the KmConstructor manually.
+    // TODO(b/154348683): Check for special flags to pass in.
+    KmConstructor kmConstructor = new KmConstructor(flags);
+    if (signature != null) {
+      JvmExtensionsKt.setSignature(kmConstructor, signature.rewrite(method, appView, namingLens));
+    }
+    for (KotlinValueParameterInfo valueParameterInfo : valueParameterInfos) {
+      valueParameterInfo.rewrite(kmConstructor::visitValueParameter, appView, namingLens);
+    }
+    kmClass.getConstructors().add(kmConstructor);
+  }
+
+  @Override
+  public boolean isConstructor() {
+    return true;
+  }
+
+  @Override
+  public KotlinConstructorInfo asConstructor() {
+    return this;
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinDeclarationContainerInfo.java b/src/main/java/com/android/tools/r8/kotlin/KotlinDeclarationContainerInfo.java
new file mode 100644
index 0000000..d1b2913
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinDeclarationContainerInfo.java
@@ -0,0 +1,207 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.kotlin;
+
+import static com.android.tools.r8.kotlin.KotlinMetadataUtils.isValidMethodDescriptor;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexEncodedField;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.kotlin.KotlinMetadataUtils.KmPropertyProcessor;
+import com.android.tools.r8.naming.NamingLens;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.google.common.collect.ImmutableList;
+import java.util.IdentityHashMap;
+import java.util.List;
+import java.util.Map;
+import kotlinx.metadata.KmDeclarationContainer;
+import kotlinx.metadata.KmFunction;
+import kotlinx.metadata.KmProperty;
+import kotlinx.metadata.KmTypeAlias;
+import kotlinx.metadata.jvm.JvmExtensionsKt;
+import kotlinx.metadata.jvm.JvmMethodSignature;
+
+// Holds information about KmDeclarationContainer
+public class KotlinDeclarationContainerInfo {
+
+  private final List<KotlinTypeAliasInfo> typeAliases;
+  // The functions in notBackedFunctions are KmFunctions where we could not find a representative.
+  private final List<KotlinFunctionInfo> functionsWithNoBacking;
+  // The properties in propertiesWithNoBacking are KmProperties where we could not find a getter,
+  // setter or backing field.
+  private final List<KotlinPropertyInfo> propertiesWithNoBacking;
+
+  private KotlinDeclarationContainerInfo(
+      List<KotlinTypeAliasInfo> typeAliases,
+      List<KotlinFunctionInfo> functionsWithNoBacking,
+      List<KotlinPropertyInfo> propertiesWithNoBacking) {
+    this.typeAliases = typeAliases;
+    this.functionsWithNoBacking = functionsWithNoBacking;
+    this.propertiesWithNoBacking = propertiesWithNoBacking;
+  }
+
+  public static KotlinDeclarationContainerInfo create(
+      KmDeclarationContainer container,
+      Map<String, DexEncodedMethod> methodSignatureMap,
+      Map<String, DexEncodedField> fieldSignatureMap,
+      AppView<?> appView) {
+    ImmutableList.Builder<KotlinFunctionInfo> notBackedFunctions = ImmutableList.builder();
+    for (KmFunction kmFunction : container.getFunctions()) {
+      JvmMethodSignature signature = JvmExtensionsKt.getSignature(kmFunction);
+      if (signature == null) {
+        assert false;
+        continue;
+      }
+      KotlinFunctionInfo kotlinFunctionInfo = KotlinFunctionInfo.create(kmFunction, appView);
+      DexEncodedMethod method = methodSignatureMap.get(signature.asString());
+      if (method == null) {
+        notBackedFunctions.add(kotlinFunctionInfo);
+        if (!isValidMethodDescriptor(signature.getDesc())) {
+          // TODO(b/155536535): Enable this assert.
+          // appView
+          //     .options()
+          //     .reporter
+          //     .info(KotlinMetadataDiagnostic.invalidMethodDescriptor(signature.asString()));
+        } else {
+          // TODO(b/154348568): Enable the assertion below.
+          // assert false : "Could not find method with signature " + signature.asString();
+        }
+        continue;
+      }
+      method.setKotlinMemberInfo(kotlinFunctionInfo);
+    }
+
+    ImmutableList.Builder<KotlinPropertyInfo> notBackedProperties = ImmutableList.builder();
+    for (KmProperty kmProperty : container.getProperties()) {
+      KotlinPropertyInfo kotlinPropertyInfo = KotlinPropertyInfo.create(kmProperty, appView);
+      KmPropertyProcessor propertyProcessor = new KmPropertyProcessor(kmProperty);
+      boolean hasBacking = false;
+      if (propertyProcessor.fieldSignature() != null) {
+        DexEncodedField field =
+            fieldSignatureMap.get(propertyProcessor.fieldSignature().asString());
+        if (field != null) {
+          hasBacking = true;
+          field.setKotlinMemberInfo(kotlinPropertyInfo);
+        }
+      }
+      if (propertyProcessor.getterSignature() != null) {
+        DexEncodedMethod method =
+            methodSignatureMap.get(propertyProcessor.getterSignature().asString());
+        if (method != null) {
+          hasBacking = true;
+          method.setKotlinMemberInfo(kotlinPropertyInfo);
+        }
+      }
+      if (propertyProcessor.setterSignature() != null) {
+        DexEncodedMethod method =
+            methodSignatureMap.get(propertyProcessor.setterSignature().asString());
+        if (method != null) {
+          hasBacking = true;
+          method.setKotlinMemberInfo(kotlinPropertyInfo);
+        }
+      }
+      if (!hasBacking) {
+        notBackedProperties.add(kotlinPropertyInfo);
+      }
+    }
+    return new KotlinDeclarationContainerInfo(
+        getTypeAliases(container.getTypeAliases(), appView),
+        notBackedFunctions.build(),
+        notBackedProperties.build());
+  }
+
+  private static List<KotlinTypeAliasInfo> getTypeAliases(
+      List<KmTypeAlias> aliases, AppView<?> appView) {
+    ImmutableList.Builder<KotlinTypeAliasInfo> builder = ImmutableList.builder();
+    for (KmTypeAlias alias : aliases) {
+      builder.add(KotlinTypeAliasInfo.create(alias, appView));
+    }
+    return builder.build();
+  }
+
+  public void rewrite(
+      KmVisitorProviders.KmFunctionVisitorProvider functionProvider,
+      KmVisitorProviders.KmPropertyVisitorProvider propertyProvider,
+      KmVisitorProviders.KmTypeAliasVisitorProvider typeAliasProvider,
+      DexClass clazz,
+      AppView<AppInfoWithLiveness> appView,
+      NamingLens namingLens) {
+    // Type aliases only have a representation here, so we can generate them directly.
+    for (KotlinTypeAliasInfo typeAlias : typeAliases) {
+      typeAlias.rewrite(typeAliasProvider, appView, namingLens);
+    }
+    // For properties, we need to combine potentially a field, setter and getter.
+    Map<KotlinPropertyInfo, KotlinPropertyGroup> properties = new IdentityHashMap<>();
+    for (DexEncodedField field : clazz.fields()) {
+      if (field.getKotlinMemberInfo().isFieldProperty()) {
+        properties
+            .computeIfAbsent(
+                field.getKotlinMemberInfo().asFieldProperty(), ignored -> new KotlinPropertyGroup())
+            .setBackingField(field);
+      }
+    }
+    for (DexEncodedMethod method : clazz.methods()) {
+      if (method.getKotlinMemberInfo().isFunction()) {
+        method
+            .getKotlinMemberInfo()
+            .asFunction()
+            .rewrite(functionProvider, method, appView, namingLens);
+        continue;
+      }
+      KotlinPropertyInfo kotlinPropertyInfo = method.getKotlinMemberInfo().asProperty();
+      if (kotlinPropertyInfo == null) {
+        continue;
+      }
+      KotlinPropertyGroup kotlinPropertyGroup =
+          properties.computeIfAbsent(kotlinPropertyInfo, ignored -> new KotlinPropertyGroup());
+      if (method.method.proto.returnType == appView.dexItemFactory().voidType) {
+        // This is a setter.
+        kotlinPropertyGroup.setSetter(method);
+      } else {
+        kotlinPropertyGroup.setGetter(method);
+      }
+    }
+    for (KotlinPropertyInfo kotlinPropertyInfo : properties.keySet()) {
+      KotlinPropertyGroup kotlinPropertyGroup = properties.get(kotlinPropertyInfo);
+      kotlinPropertyInfo.rewrite(
+          propertyProvider,
+          kotlinPropertyGroup.backingField,
+          kotlinPropertyGroup.getter,
+          kotlinPropertyGroup.setter,
+          appView,
+          namingLens);
+    }
+    // Add all not backed functions and properties.
+    for (KotlinFunctionInfo notBackedFunction : functionsWithNoBacking) {
+      notBackedFunction.rewrite(functionProvider, null, appView, namingLens);
+    }
+    for (KotlinPropertyInfo notBackedProperty : propertiesWithNoBacking) {
+      notBackedProperty.rewrite(propertyProvider, null, null, null, appView, namingLens);
+    }
+  }
+
+  public static class KotlinPropertyGroup {
+
+    private DexEncodedField backingField = null;
+    private DexEncodedMethod setter = null;
+    private DexEncodedMethod getter = null;
+
+    void setBackingField(DexEncodedField backingField) {
+      assert this.backingField == null;
+      this.backingField = backingField;
+    }
+
+    void setGetter(DexEncodedMethod getter) {
+      assert this.getter == null;
+      this.getter = getter;
+    }
+
+    void setSetter(DexEncodedMethod setter) {
+      assert this.setter == null;
+      this.setter = setter;
+    }
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinFieldLevelInfo.java b/src/main/java/com/android/tools/r8/kotlin/KotlinFieldLevelInfo.java
new file mode 100644
index 0000000..7df7afb
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinFieldLevelInfo.java
@@ -0,0 +1,24 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.kotlin;
+
+public interface KotlinFieldLevelInfo {
+
+  default boolean isCompanion() {
+    return false;
+  }
+
+  default KotlinCompanionInfo asCompanion() {
+    return null;
+  }
+
+  default boolean isFieldProperty() {
+    return false;
+  }
+
+  default KotlinPropertyInfo asFieldProperty() {
+    return null;
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinFile.java b/src/main/java/com/android/tools/r8/kotlin/KotlinFile.java
deleted file mode 100644
index ae7eb41..0000000
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinFile.java
+++ /dev/null
@@ -1,79 +0,0 @@
-// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-
-package com.android.tools.r8.kotlin;
-
-import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.DexClass;
-import com.android.tools.r8.graph.SubtypingInfo;
-import com.android.tools.r8.naming.NamingLens;
-import com.android.tools.r8.shaking.AppInfoWithLiveness;
-import kotlinx.metadata.KmPackage;
-import kotlinx.metadata.jvm.KotlinClassHeader;
-import kotlinx.metadata.jvm.KotlinClassMetadata;
-
-public final class KotlinFile extends KotlinInfo<KotlinClassMetadata.FileFacade> {
-
-  KmPackage kmPackage;
-
-  static KotlinFile fromKotlinClassMetadata(
-      KotlinClassMetadata kotlinClassMetadata, DexClass clazz) {
-    assert kotlinClassMetadata instanceof KotlinClassMetadata.FileFacade;
-    KotlinClassMetadata.FileFacade fileFacade =
-        (KotlinClassMetadata.FileFacade) kotlinClassMetadata;
-    return new KotlinFile(fileFacade, clazz);
-  }
-
-  private KotlinFile(KotlinClassMetadata.FileFacade metadata, DexClass clazz) {
-    super(metadata, clazz);
-  }
-
-  @Override
-  void processMetadata(KotlinClassMetadata.FileFacade metadata) {
-    kmPackage = metadata.toKmPackage();
-  }
-
-  @Override
-  void rewrite(AppView<AppInfoWithLiveness> appView, SubtypingInfo subtypingInfo, NamingLens lens) {
-    if (!appView.options().enableKotlinMetadataRewritingForMembers) {
-      return;
-    }
-    rewriteDeclarationContainer(new KotlinMetadataSynthesizer(appView, lens, this));
-  }
-
-  @Override
-  KotlinClassHeader createHeader() {
-    KotlinClassMetadata.FileFacade.Writer writer = new KotlinClassMetadata.FileFacade.Writer();
-    kmPackage.accept(writer);
-    return writer.write().getHeader();
-  }
-
-  @Override
-  public Kind getKind() {
-    return Kind.File;
-  }
-
-  @Override
-  public boolean isFile() {
-    return true;
-  }
-
-  @Override
-  public KotlinFile asFile() {
-    return this;
-  }
-
-  @Override
-  public String toString(String indent) {
-    StringBuilder sb = new StringBuilder(indent);
-    appendKmSection(
-        indent,
-        "Metadata.FileFacade",
-        sb,
-        newIndent -> {
-          appendKmPackage(newIndent, sb, kmPackage);
-        });
-    return sb.toString();
-  }
-}
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinFileFacadeInfo.java b/src/main/java/com/android/tools/r8/kotlin/KotlinFileFacadeInfo.java
new file mode 100644
index 0000000..ee2f81d
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinFileFacadeInfo.java
@@ -0,0 +1,50 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.kotlin;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.naming.NamingLens;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import kotlinx.metadata.KmPackage;
+import kotlinx.metadata.jvm.KotlinClassHeader;
+import kotlinx.metadata.jvm.KotlinClassMetadata;
+import kotlinx.metadata.jvm.KotlinClassMetadata.FileFacade;
+
+// Holds information about Metadata.FileFacade
+public class KotlinFileFacadeInfo implements KotlinClassLevelInfo {
+
+  private final KotlinPackageInfo packageInfo;
+
+  private KotlinFileFacadeInfo(KotlinPackageInfo packageInfo) {
+    this.packageInfo = packageInfo;
+  }
+
+  public static KotlinFileFacadeInfo create(
+      FileFacade kmFileFacade, DexClass clazz, AppView<?> appView) {
+    return new KotlinFileFacadeInfo(
+        KotlinPackageInfo.create(kmFileFacade.toKmPackage(), clazz, appView));
+  }
+
+  @Override
+  public boolean isFileFacade() {
+    return true;
+  }
+
+  @Override
+  public KotlinFileFacadeInfo asFileFacade() {
+    return this;
+  }
+
+  @Override
+  public KotlinClassHeader rewrite(
+      DexClass clazz, AppView<AppInfoWithLiveness> appView, NamingLens namingLens) {
+    KotlinClassMetadata.FileFacade.Writer writer = new KotlinClassMetadata.FileFacade.Writer();
+    KmPackage kmPackage = new KmPackage();
+    packageInfo.rewrite(kmPackage, clazz, appView, namingLens);
+    kmPackage.accept(writer);
+    return writer.write().getHeader();
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinFunctionInfo.java b/src/main/java/com/android/tools/r8/kotlin/KotlinFunctionInfo.java
new file mode 100644
index 0000000..c1170a1
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinFunctionInfo.java
@@ -0,0 +1,129 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.kotlin;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.naming.NamingLens;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.utils.DescriptorUtils;
+import java.util.List;
+import kotlinx.metadata.KmFunction;
+import kotlinx.metadata.KmFunctionVisitor;
+import kotlinx.metadata.jvm.JvmExtensionsKt;
+import kotlinx.metadata.jvm.JvmFunctionExtensionVisitor;
+
+// Holds information about KmFunction
+public final class KotlinFunctionInfo implements KotlinMethodLevelInfo {
+  // Original flags
+  private final int flags;
+  // Original name;
+  private final String name;
+  // Information from original KmValueParameter(s) if available.
+  private final List<KotlinValueParameterInfo> valueParameters;
+  // Information from original KmFunction.returnType. Null if this is from a KmConstructor.
+  public final KotlinTypeInfo returnType;
+  // Information from original KmFunction.receiverType. Null if this is from a KmConstructor.
+  private final KotlinTypeInfo receiverParameterType;
+  // Information about original type parameters. Null if this is from a KmConstructor.
+  private final List<KotlinTypeParameterInfo> typeParameters;
+  // Information about the signature
+  private final KotlinJvmMethodSignatureInfo signature;
+  // Information about the lambdaClassOrigin.
+  private final DexType lambdaClassOrigin;
+
+  private KotlinFunctionInfo(
+      int flags,
+      String name,
+      KotlinTypeInfo returnType,
+      KotlinTypeInfo receiverParameterType,
+      List<KotlinValueParameterInfo> valueParameters,
+      List<KotlinTypeParameterInfo> typeParameters,
+      KotlinJvmMethodSignatureInfo signature,
+      DexType lambdaClassOrigin) {
+    this.flags = flags;
+    this.name = name;
+    this.returnType = returnType;
+    this.receiverParameterType = receiverParameterType;
+    this.valueParameters = valueParameters;
+    this.typeParameters = typeParameters;
+    this.signature = signature;
+    this.lambdaClassOrigin = lambdaClassOrigin;
+  }
+
+  static KotlinFunctionInfo create(KmFunction kmFunction, AppView<?> appView) {
+    return new KotlinFunctionInfo(
+        kmFunction.getFlags(),
+        kmFunction.getName(),
+        KotlinTypeInfo.create(kmFunction.getReturnType(), appView),
+        KotlinTypeInfo.create(kmFunction.getReceiverParameterType(), appView),
+        KotlinValueParameterInfo.create(kmFunction.getValueParameters(), appView),
+        KotlinTypeParameterInfo.create(kmFunction.getTypeParameters(), appView),
+        KotlinJvmMethodSignatureInfo.create(JvmExtensionsKt.getSignature(kmFunction), appView),
+        getlambdaClassOrigin(kmFunction, appView));
+  }
+
+  private static DexType getlambdaClassOrigin(KmFunction kmFunction, AppView<?> appView) {
+    String lambdaClassOriginName = JvmExtensionsKt.getLambdaClassOriginName(kmFunction);
+    if (lambdaClassOriginName != null) {
+      return appView
+          .dexItemFactory()
+          .createType(DescriptorUtils.getDescriptorFromClassBinaryName(lambdaClassOriginName));
+    }
+    return null;
+  }
+
+  public void rewrite(
+      KmVisitorProviders.KmFunctionVisitorProvider visitorProvider,
+      DexEncodedMethod method,
+      AppView<AppInfoWithLiveness> appView,
+      NamingLens namingLens) {
+    // TODO(b/154348683): Check method for flags to pass in.
+    String finalName = this.name;
+    if (method != null) {
+      String methodName = method.method.name.toString();
+      String rewrittenName = namingLens.lookupName(method.method).toString();
+      if (!methodName.equals(rewrittenName)) {
+        finalName = rewrittenName;
+      }
+    }
+    KmFunctionVisitor kmFunction = visitorProvider.get(flags, finalName);
+    // TODO(b/154348149): ReturnType could have been merged to a subtype.
+    returnType.rewrite(kmFunction::visitReturnType, appView, namingLens);
+    for (KotlinValueParameterInfo valueParameterInfo : valueParameters) {
+      valueParameterInfo.rewrite(kmFunction::visitValueParameter, appView, namingLens);
+    }
+    for (KotlinTypeParameterInfo typeParameterInfo : typeParameters) {
+      typeParameterInfo.rewrite(kmFunction::visitTypeParameter, appView, namingLens);
+    }
+    if (receiverParameterType != null) {
+      receiverParameterType.rewrite(kmFunction::visitReceiverParameterType, appView, namingLens);
+    }
+    JvmFunctionExtensionVisitor extensionVisitor =
+        (JvmFunctionExtensionVisitor) kmFunction.visitExtensions(JvmFunctionExtensionVisitor.TYPE);
+    if (signature != null && extensionVisitor != null) {
+      extensionVisitor.visit(signature.rewrite(method, appView, namingLens));
+    }
+    if (lambdaClassOrigin != null && extensionVisitor != null) {
+      extensionVisitor.visitLambdaClassOriginName(
+          KotlinMetadataUtils.kotlinNameFromDescriptor(lambdaClassOrigin.descriptor));
+    }
+  }
+
+  @Override
+  public boolean isFunction() {
+    return true;
+  }
+
+  @Override
+  public KotlinFunctionInfo asFunction() {
+    return this;
+  }
+
+  public boolean isExtensionFunction() {
+    return receiverParameterType != null;
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinInfoCollector.java b/src/main/java/com/android/tools/r8/kotlin/KotlinInfoCollector.java
index 9d89660..94f9b12 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinInfoCollector.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinInfoCollector.java
@@ -5,12 +5,7 @@
 
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexApplication;
-import com.android.tools.r8.graph.DexProgramClass;
-import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.utils.Reporter;
 import com.android.tools.r8.utils.ThreadUtils;
-import java.util.Map;
-import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.ExecutorService;
 
@@ -22,40 +17,10 @@
       return;
     }
     Kotlin kotlin = appView.dexItemFactory().kotlin;
-    Reporter reporter = appView.options().reporter;
-    Map<DexProgramClass, DexProgramClass> companionToHostMap = new ConcurrentHashMap<>();
     ThreadUtils.processItems(
         application.classes(),
         programClass -> {
-          KotlinInfo kotlinInfo = kotlin.getKotlinInfo(programClass, reporter);
-          programClass.setKotlinInfo(kotlinInfo);
-          KotlinMemberInfo.markKotlinMemberInfo(programClass, kotlinInfo, reporter);
-          // Store a companion type to revisit.
-          if (kotlinInfo != null
-              && kotlinInfo.isClass()
-              && kotlinInfo.asClass().hasCompanionObject()) {
-            DexType companionType = kotlinInfo.asClass().getCompanionObjectType();
-            DexProgramClass companionClass = appView.definitionForProgramType(companionType);
-            if (companionClass != null) {
-              companionToHostMap.put(companionClass, programClass);
-            }
-          }
-        },
-        executorService);
-    // TODO(b/151194869): if we can guarantee that Companion classes are visited ahead and their
-    //  KotlinInfo is created before processing host classes, below could be hoisted to 1st pass.
-    //  Maybe name-based filtering? E.g., classes whose name ends with "$Companion" v.s. not?
-    ThreadUtils.processItems(
-        companionToHostMap.keySet(),
-        companionClass -> {
-          KotlinInfo kotlinInfo = companionClass.getKotlinInfo();
-          if (kotlinInfo != null && kotlinInfo.isClass()) {
-            DexProgramClass hostClass = companionToHostMap.get(companionClass);
-            assert hostClass != null;
-            kotlinInfo.asClass().linkHostClass(hostClass);
-            // Revisit host class's members with declarations in the companion object.
-            KotlinMemberInfo.markKotlinMemberInfo(hostClass, kotlinInfo, reporter);
-          }
+          programClass.setKotlinInfo(kotlin.getKotlinInfo(programClass, appView));
         },
         executorService);
   }
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinJvmFieldSignatureInfo.java b/src/main/java/com/android/tools/r8/kotlin/KotlinJvmFieldSignatureInfo.java
new file mode 100644
index 0000000..0dae9cf
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinJvmFieldSignatureInfo.java
@@ -0,0 +1,53 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.kotlin;
+
+import static com.android.tools.r8.kotlin.KotlinMetadataUtils.toRenamedDescriptorOrDefault;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexEncodedField;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.naming.NamingLens;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import kotlinx.metadata.jvm.JvmFieldSignature;
+
+/**
+ * The JvmSignature for a method or property does not always correspond to the actual signature, see
+ * b/154201250. We therefore need to model the signature as well.
+ */
+public class KotlinJvmFieldSignatureInfo {
+
+  private final DexType type;
+  private final String name;
+
+  private KotlinJvmFieldSignatureInfo(String name, DexType type) {
+    this.name = name;
+    this.type = type;
+  }
+
+  public static KotlinJvmFieldSignatureInfo create(
+      JvmFieldSignature fieldSignature, AppView<?> appView) {
+    if (fieldSignature == null) {
+      return null;
+    }
+    return new KotlinJvmFieldSignatureInfo(
+        fieldSignature.getName(), appView.dexItemFactory().createType(fieldSignature.getDesc()));
+  }
+
+  public JvmFieldSignature rewrite(
+      DexEncodedField field, AppView<AppInfoWithLiveness> appView, NamingLens namingLens) {
+    String finalName = name;
+    if (field != null) {
+      String fieldName = field.field.name.toString();
+      String rewrittenName = namingLens.lookupName(field.field).toString();
+      if (!fieldName.equals(rewrittenName)) {
+        finalName = rewrittenName;
+      }
+    }
+    String defValue = appView.dexItemFactory().objectType.toDescriptorString();
+    return new JvmFieldSignature(
+        finalName, toRenamedDescriptorOrDefault(type, appView, namingLens, defValue));
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinJvmMethodSignatureInfo.java b/src/main/java/com/android/tools/r8/kotlin/KotlinJvmMethodSignatureInfo.java
new file mode 100644
index 0000000..354364a
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinJvmMethodSignatureInfo.java
@@ -0,0 +1,80 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.kotlin;
+
+import static com.android.tools.r8.kotlin.KotlinMetadataUtils.toRenamedDescriptorOrDefault;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.naming.NamingLens;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.utils.DescriptorUtils;
+import com.google.common.collect.ImmutableList;
+import java.util.List;
+import kotlinx.metadata.jvm.JvmMethodSignature;
+
+/**
+ * The JvmSignature for a method or property does not always correspond to the actual signature, see
+ * b/154201250. We therefore need to model the signature as well.
+ */
+public class KotlinJvmMethodSignatureInfo {
+
+  private static final List<DexType> EMPTY_PARAMETERS_LIST = ImmutableList.of();
+
+  private final String name;
+  private final DexType returnType;
+  private final List<DexType> parameters;
+
+  private KotlinJvmMethodSignatureInfo(String name, DexType returnType, List<DexType> parameters) {
+    this.name = name;
+    this.returnType = returnType;
+    this.parameters = parameters;
+  }
+
+  public static KotlinJvmMethodSignatureInfo create(
+      JvmMethodSignature methodSignature, AppView<?> appView) {
+    if (methodSignature == null) {
+      return null;
+    }
+    String kotlinDescriptor = methodSignature.getDesc();
+    String returnTypeDescriptor = DescriptorUtils.getReturnTypeDescriptor(kotlinDescriptor);
+    DexItemFactory factory = appView.dexItemFactory();
+    DexType returnType = factory.createType(returnTypeDescriptor);
+    String[] descriptors = DescriptorUtils.getArgumentTypeDescriptors(kotlinDescriptor);
+    if (descriptors.length == 0) {
+      return new KotlinJvmMethodSignatureInfo(
+          methodSignature.getName(), returnType, EMPTY_PARAMETERS_LIST);
+    }
+    ImmutableList.Builder<DexType> parameters = ImmutableList.builder();
+    for (String descriptor : descriptors) {
+      parameters.add(factory.createType(descriptor));
+    }
+    return new KotlinJvmMethodSignatureInfo(
+        methodSignature.getName(), returnType, parameters.build());
+  }
+
+  public JvmMethodSignature rewrite(
+      DexEncodedMethod method, AppView<AppInfoWithLiveness> appView, NamingLens namingLens) {
+    String finalName = name;
+    if (method != null) {
+      String methodName = method.method.name.toString();
+      String rewrittenName = namingLens.lookupName(method.method).toString();
+      if (!methodName.equals(rewrittenName)) {
+        finalName = rewrittenName;
+      }
+    }
+    StringBuilder descBuilder = new StringBuilder();
+    descBuilder.append("(");
+    String defValue = appView.dexItemFactory().objectType.toDescriptorString();
+    for (DexType parameter : parameters) {
+      descBuilder.append(toRenamedDescriptorOrDefault(parameter, appView, namingLens, defValue));
+    }
+    descBuilder.append(")");
+    descBuilder.append(toRenamedDescriptorOrDefault(returnType, appView, namingLens, defValue));
+    return new JvmMethodSignature(finalName, descBuilder.toString());
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinLambdaInfo.java b/src/main/java/com/android/tools/r8/kotlin/KotlinLambdaInfo.java
new file mode 100644
index 0000000..107d197
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinLambdaInfo.java
@@ -0,0 +1,64 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.kotlin;
+
+import static com.android.tools.r8.kotlin.KotlinMetadataUtils.toJvmMethodSignature;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.naming.NamingLens;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import kotlinx.metadata.KmLambda;
+import kotlinx.metadata.KmLambdaVisitor;
+import kotlinx.metadata.jvm.JvmExtensionsKt;
+import kotlinx.metadata.jvm.JvmMethodSignature;
+
+// Holds information about a KmLambda
+public class KotlinLambdaInfo {
+
+  private final KotlinFunctionInfo function;
+
+  private KotlinLambdaInfo(KotlinFunctionInfo function) {
+    this.function = function;
+  }
+
+  static KotlinLambdaInfo create(DexClass clazz, KmLambda lambda, AppView<?> appView) {
+    if (lambda == null) {
+      assert false;
+      return null;
+    }
+    JvmMethodSignature signature = JvmExtensionsKt.getSignature(lambda.function);
+    if (signature == null) {
+      assert false;
+      return null;
+    }
+    for (DexEncodedMethod method : clazz.methods()) {
+      if (toJvmMethodSignature(method.method).asString().equals(signature.asString())) {
+        KotlinFunctionInfo kotlinFunctionInfo = KotlinFunctionInfo.create(lambda.function, appView);
+        method.setKotlinMemberInfo(kotlinFunctionInfo);
+        return new KotlinLambdaInfo(kotlinFunctionInfo);
+      }
+    }
+    // TODO(b/155536535): Resolve this assert for NestTreeShakeJarVerificationTest.
+    // assert false;
+    return null;
+  }
+
+  boolean rewrite(
+      KmVisitorProviders.KmLambdaVisitorProvider visitorProvider,
+      DexClass clazz,
+      AppView<AppInfoWithLiveness> appView,
+      NamingLens namingLens) {
+    for (DexEncodedMethod method : clazz.methods()) {
+      if (method.getKotlinMemberInfo() == function) {
+        KmLambdaVisitor kmLambdaVisitor = visitorProvider.get();
+        function.rewrite(kmLambdaVisitor::visitFunction, method, appView, namingLens);
+        return true;
+      }
+    }
+    return false;
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinMemberInfo.java b/src/main/java/com/android/tools/r8/kotlin/KotlinMemberInfo.java
deleted file mode 100644
index 142aef3..0000000
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinMemberInfo.java
+++ /dev/null
@@ -1,411 +0,0 @@
-// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-package com.android.tools.r8.kotlin;
-
-import static com.android.tools.r8.kotlin.KotlinMemberInfo.MemberKind.CONSTRUCTOR;
-import static com.android.tools.r8.kotlin.KotlinMemberInfo.MemberKind.EXTENSION_FUNCTION;
-import static com.android.tools.r8.kotlin.KotlinMemberInfo.MemberKind.EXTENSION_PROPERTY_GETTER;
-import static com.android.tools.r8.kotlin.KotlinMemberInfo.MemberKind.EXTENSION_PROPERTY_SETTER;
-import static com.android.tools.r8.kotlin.KotlinMemberInfo.MemberKind.FUNCTION;
-import static com.android.tools.r8.kotlin.KotlinMemberInfo.MemberKind.PROPERTY_GETTER;
-import static com.android.tools.r8.kotlin.KotlinMemberInfo.MemberKind.PROPERTY_SETTER;
-import static com.android.tools.r8.kotlin.KotlinMetadataJvmExtensionUtils.getJvmMethodSignature;
-import static com.android.tools.r8.kotlin.KotlinMetadataJvmExtensionUtils.toJvmFieldSignature;
-import static com.android.tools.r8.kotlin.KotlinMetadataJvmExtensionUtils.toJvmMethodSignature;
-import static com.android.tools.r8.kotlin.KotlinMetadataSynthesizer.isExtension;
-
-import com.android.tools.r8.graph.DexClass;
-import com.android.tools.r8.graph.DexEncodedField;
-import com.android.tools.r8.graph.DexEncodedMethod;
-import com.android.tools.r8.kotlin.KotlinMetadataJvmExtensionUtils.KmPropertyProcessor;
-import com.android.tools.r8.utils.Reporter;
-import com.google.common.collect.ImmutableList;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import kotlinx.metadata.KmConstructor;
-import kotlinx.metadata.KmDeclarationContainer;
-import kotlinx.metadata.KmFunction;
-import kotlinx.metadata.KmProperty;
-import kotlinx.metadata.KmTypeParameter;
-import kotlinx.metadata.KmValueParameter;
-import kotlinx.metadata.jvm.JvmMethodSignature;
-
-// Provides access to field/method-level Kotlin information.
-public abstract class KotlinMemberInfo {
-
-  private static final List<KotlinValueParameterInfo> EMPTY_VALUE_PARAM_INFO = ImmutableList.of();
-  static final List<KotlinTypeParameterInfo> EMPTY_TYPE_PARAM_INFO = ImmutableList.of();
-
-  private static final KotlinMemberInfo NO_KOTLIN_MEMBER_INFO = new NoKotlinMemberInfo();
-
-  public static KotlinMemberInfo getNoKotlinMemberInfo() {
-    return NO_KOTLIN_MEMBER_INFO;
-  }
-
-  public final MemberKind memberKind;
-  // Original member flags. May be necessary to keep Kotlin-specific flag, e.g., suspend function.
-  final int flags;
-
-  private KotlinMemberInfo(MemberKind memberKind, int flags) {
-    this.memberKind = memberKind;
-    this.flags = flags;
-  }
-
-  public boolean isFunctionInfo() {
-    return false;
-  }
-
-  public KotlinFunctionInfo asFunctionInfo() {
-    return null;
-  }
-
-  public boolean isFieldInfo() {
-    return false;
-  }
-
-  public KotlinFieldInfo asFieldInfo() {
-    return null;
-  }
-
-  public boolean isPropertyInfo() {
-    return false;
-  }
-
-  public KotlinPropertyInfo asPropertyInfo() {
-    return null;
-  }
-
-  private static class NoKotlinMemberInfo extends KotlinMemberInfo {
-
-    private NoKotlinMemberInfo() {
-      super(MemberKind.NONE, 0);
-    }
-  }
-
-  public static class KotlinFunctionInfo extends KotlinMemberInfo {
-
-    // Information from original KmValueParameter(s) if available.
-    final List<KotlinValueParameterInfo> valueParameterInfos;
-    // Information from original KmFunction.returnType. Null if this is from a KmConstructor.
-    public final KotlinTypeInfo returnType;
-    // Information from original KmFunction.receiverType. Null if this is from a KmConstructor.
-    final KotlinTypeInfo receiverParameterType;
-    // Information about original type parameters. Null if this is from a KmConstructor.
-    final List<KotlinTypeParameterInfo> kotlinTypeParameterInfo;
-
-    private KotlinFunctionInfo(
-        MemberKind memberKind,
-        int flags,
-        KotlinTypeInfo returnType,
-        KotlinTypeInfo receiverParameterType,
-        List<KotlinValueParameterInfo> valueParameterInfos,
-        List<KotlinTypeParameterInfo> kotlinTypeParameterInfo) {
-      super(memberKind, flags);
-      assert memberKind.isFunction() || memberKind.isConstructor();
-      this.returnType = returnType;
-      this.receiverParameterType = receiverParameterType;
-      this.valueParameterInfos = valueParameterInfos;
-      this.kotlinTypeParameterInfo = kotlinTypeParameterInfo;
-    }
-
-    KotlinValueParameterInfo getValueParameterInfo(int i) {
-      if (valueParameterInfos.isEmpty()) {
-        return null;
-      }
-      if (i < 0 || i >= valueParameterInfos.size()) {
-        return null;
-      }
-      return valueParameterInfos.get(i);
-    }
-
-    @Override
-    public boolean isFunctionInfo() {
-      return true;
-    }
-
-    @Override
-    public KotlinFunctionInfo asFunctionInfo() {
-      return this;
-    }
-  }
-
-  public static class KotlinFieldInfo extends KotlinMemberInfo {
-
-    // Original property name for (extension) property. Otherwise, null.
-    final String propertyName;
-
-    private KotlinFieldInfo(MemberKind memberKind, int flags, String propertyName) {
-      super(memberKind, flags);
-      this.propertyName = propertyName;
-    }
-
-    @Override
-    public boolean isFieldInfo() {
-      return true;
-    }
-
-    @Override
-    public KotlinFieldInfo asFieldInfo() {
-      return this;
-    }
-  }
-
-  public static class KotlinPropertyInfo extends KotlinMemberInfo {
-
-    // Original getter flags. E.g., for property getter.
-    final int getterFlags;
-
-    // Original setter flags. E.g., for property setter.
-    final int setterFlags;
-
-    // Original property name for (extension) property. Otherwise, null.
-    final String propertyName;
-
-    // Original return type information. This should never be NULL (even for setters without field).
-    final KotlinTypeInfo returnType;
-
-    // Information from original KmValueParameter if available.
-    final KotlinValueParameterInfo valueParameterInfo;
-
-    private KotlinPropertyInfo(
-        MemberKind memberKind,
-        int flags,
-        int getterFlags,
-        int setterFlags,
-        String propertyName,
-        KotlinTypeInfo returnType,
-        KotlinValueParameterInfo valueParameterInfo) {
-      super(memberKind, flags);
-      this.getterFlags = getterFlags;
-      this.setterFlags = setterFlags;
-      this.propertyName = propertyName;
-      this.returnType = returnType;
-      this.valueParameterInfo = valueParameterInfo;
-    }
-
-    @Override
-    public KotlinPropertyInfo asPropertyInfo() {
-      return this;
-    }
-
-    @Override
-    public boolean isPropertyInfo() {
-      return true;
-    }
-  }
-
-  private static KotlinFunctionInfo createFunctionInfoFromConstructor(KmConstructor kmConstructor) {
-    return new KotlinFunctionInfo(
-        CONSTRUCTOR,
-        kmConstructor.getFlags(),
-        null,
-        null,
-        getValueParameters(kmConstructor.getValueParameters()),
-        EMPTY_TYPE_PARAM_INFO);
-  }
-
-  private static KotlinFunctionInfo createFunctionInfo(
-      MemberKind memberKind, KmFunction kmFunction) {
-    assert memberKind.isFunction();
-    KotlinTypeInfo returnTypeInfo = KotlinTypeInfo.create(kmFunction.getReturnType());
-    KotlinTypeInfo receiverParameterTypeInfo =
-        KotlinTypeInfo.create(kmFunction.getReceiverParameterType());
-    return new KotlinFunctionInfo(
-        memberKind,
-        kmFunction.getFlags(),
-        returnTypeInfo,
-        receiverParameterTypeInfo,
-        getValueParameters(kmFunction.getValueParameters()),
-        getTypeParameters(kmFunction.getTypeParameters()));
-  }
-
-  private static List<KotlinValueParameterInfo> getValueParameters(
-      List<KmValueParameter> parameters) {
-    if (parameters.isEmpty()) {
-      return EMPTY_VALUE_PARAM_INFO;
-    }
-    ImmutableList.Builder<KotlinValueParameterInfo> builder = ImmutableList.builder();
-    for (KmValueParameter kmValueParameter : parameters) {
-      builder.add(KotlinValueParameterInfo.fromKmValueParameter(kmValueParameter));
-    }
-    return builder.build();
-  }
-
-  private static List<KotlinTypeParameterInfo> getTypeParameters(List<KmTypeParameter> parameters) {
-    if (parameters.isEmpty()) {
-      return EMPTY_TYPE_PARAM_INFO;
-    }
-    ImmutableList.Builder<KotlinTypeParameterInfo> builder = ImmutableList.builder();
-    for (KmTypeParameter kmTypeParameter : parameters) {
-      builder.add(KotlinTypeParameterInfo.fromKmTypeParameter(kmTypeParameter));
-    }
-    return builder.build();
-  }
-
-  private static KotlinFieldInfo createFieldInfo(MemberKind memberKind, KmProperty kmProperty) {
-    assert memberKind.isBackingField() || memberKind.isBackingFieldForCompanionObject();
-    return new KotlinFieldInfo(memberKind, kmProperty.getFlags(), kmProperty.getName());
-  }
-
-  private static KotlinPropertyInfo createPropertyInfo(
-      MemberKind memberKind, KmProperty kmProperty) {
-    assert memberKind.isProperty();
-    return new KotlinPropertyInfo(
-        memberKind,
-        kmProperty.getFlags(),
-        kmProperty.getGetterFlags(),
-        kmProperty.getSetterFlags(),
-        kmProperty.getName(),
-        KotlinTypeInfo.create(kmProperty.getReturnType()),
-        KotlinValueParameterInfo.fromKmValueParameter(kmProperty.getSetterParameter()));
-  }
-
-  public enum MemberKind {
-    NONE,
-
-    CONSTRUCTOR,
-    FUNCTION,
-    EXTENSION_FUNCTION,
-
-    COMPANION_OBJECT_BACKING_FIELD,
-    PROPERTY_BACKING_FIELD,
-    PROPERTY_GETTER,
-    PROPERTY_SETTER,
-    PROPERTY_ANNOTATIONS,
-
-    // No backing field for extension property.
-    EXTENSION_PROPERTY_GETTER,
-    EXTENSION_PROPERTY_SETTER,
-    EXTENSION_PROPERTY_ANNOTATIONS;
-
-    public boolean isConstructor() {
-      return this == CONSTRUCTOR;
-    }
-
-    public boolean isFunction() {
-      return this == FUNCTION || isExtensionFunction();
-    }
-
-    public boolean isExtensionFunction() {
-      return this == EXTENSION_FUNCTION;
-    }
-
-    public boolean isBackingField() {
-      return this == PROPERTY_BACKING_FIELD;
-    }
-
-    public boolean isBackingFieldForCompanionObject() {
-      return this == COMPANION_OBJECT_BACKING_FIELD;
-    }
-
-    public boolean isProperty() {
-      return isBackingField()
-          || isBackingFieldForCompanionObject()
-          || this == PROPERTY_GETTER
-          || this == PROPERTY_SETTER
-          || this == PROPERTY_ANNOTATIONS
-          || isExtensionProperty();
-    }
-
-    public boolean isExtensionProperty() {
-      return this == EXTENSION_PROPERTY_GETTER
-          || this == EXTENSION_PROPERTY_SETTER
-          || this == EXTENSION_PROPERTY_ANNOTATIONS;
-    }
-  }
-
-  static void markKotlinMemberInfo(DexClass clazz, KotlinInfo kotlinInfo, Reporter reporter) {
-    if (kotlinInfo == null || !kotlinInfo.hasDeclarations()) {
-      return;
-    }
-
-    Map<String, KmConstructor> kmConstructorMap = new HashMap<>();
-    Map<String, KmFunction> kmFunctionMap = new HashMap<>();
-    Map<String, KmProperty> kmPropertyFieldMap = new HashMap<>();
-    Map<String, KmProperty> kmPropertyGetterMap = new HashMap<>();
-    Map<String, KmProperty> kmPropertySetterMap = new HashMap<>();
-
-    KmDeclarationContainer kmDeclarationContainer = kotlinInfo.getDeclarations();
-    String companionObject = null;
-    if (kotlinInfo.isClass()) {
-      companionObject = kotlinInfo.asClass().kmClass.getCompanionObject();
-      kotlinInfo
-          .asClass()
-          .kmClass
-          .getConstructors()
-          .forEach(
-              kmConstructor -> {
-                JvmMethodSignature methodSignature = getJvmMethodSignature(kmConstructor, reporter);
-                if (methodSignature != null) {
-                  kmConstructorMap.put(methodSignature.asString(), kmConstructor);
-                }
-              });
-    }
-    kmDeclarationContainer
-        .getFunctions()
-        .forEach(
-            kmFunction -> {
-              JvmMethodSignature methodSignature = getJvmMethodSignature(kmFunction, reporter);
-              if (methodSignature != null) {
-                kmFunctionMap.put(methodSignature.asString(), kmFunction);
-              }
-            });
-    kmDeclarationContainer.getProperties().forEach(kmProperty -> {
-      KmPropertyProcessor propertyProcessor = new KmPropertyProcessor(kmProperty, reporter);
-      if (propertyProcessor.fieldSignature() != null) {
-        kmPropertyFieldMap.put(propertyProcessor.fieldSignature().asString(), kmProperty);
-      }
-      if (propertyProcessor.getterSignature() != null) {
-        kmPropertyGetterMap.put(propertyProcessor.getterSignature().asString(), kmProperty);
-      }
-      if (propertyProcessor.setterSignature() != null) {
-        kmPropertySetterMap.put(propertyProcessor.setterSignature().asString(), kmProperty);
-      }
-      // TODO(b/151194869): property annotations
-    });
-
-    for (DexEncodedField field : clazz.fields()) {
-      if (companionObject != null && companionObject.equals(field.field.name.toString())) {
-        assert kotlinInfo.isClass();
-        kotlinInfo.asClass().foundCompanionObject(field);
-        continue;
-      }
-      String key = toJvmFieldSignature(field.field).asString();
-      if (kmPropertyFieldMap.containsKey(key)) {
-        KmProperty kmProperty = kmPropertyFieldMap.get(key);
-        field.setKotlinMemberInfo(
-            createFieldInfo(
-                clazz == kotlinInfo.clazz
-                    ? MemberKind.PROPERTY_BACKING_FIELD
-                    : MemberKind.COMPANION_OBJECT_BACKING_FIELD,
-                kmProperty));
-      }
-    }
-
-    for (DexEncodedMethod method : clazz.methods()) {
-      String key = toJvmMethodSignature(method.method).asString();
-      if (kmConstructorMap.containsKey(key)) {
-        // Interestingly we cannot assert that the method is a jvm initializer, because the jvm
-        // signature can be a different.
-        method.setKotlinMemberInfo(createFunctionInfoFromConstructor(kmConstructorMap.get(key)));
-      } else if (kmFunctionMap.containsKey(key)) {
-        KmFunction kmFunction = kmFunctionMap.get(key);
-        method.setKotlinMemberInfo(
-            createFunctionInfo(
-                isExtension(kmFunction) ? EXTENSION_FUNCTION : FUNCTION, kmFunction));
-      } else if (kmPropertyGetterMap.containsKey(key)) {
-        KmProperty kmProperty = kmPropertyGetterMap.get(key);
-        method.setKotlinMemberInfo(
-            createPropertyInfo(
-                isExtension(kmProperty) ? EXTENSION_PROPERTY_GETTER : PROPERTY_GETTER, kmProperty));
-      } else if (kmPropertySetterMap.containsKey(key)) {
-        KmProperty kmProperty = kmPropertySetterMap.get(key);
-        method.setKotlinMemberInfo(
-            createPropertyInfo(
-                isExtension(kmProperty) ? EXTENSION_PROPERTY_SETTER : PROPERTY_SETTER, kmProperty));
-      }
-    }
-  }
-}
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataDiagnostic.java b/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataDiagnostic.java
index da33940..83947d7 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataDiagnostic.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataDiagnostic.java
@@ -6,8 +6,10 @@
 
 import com.android.tools.r8.Diagnostic;
 import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.position.Position;
+import com.android.tools.r8.utils.StringUtils;
 
 public class KotlinMetadataDiagnostic implements Diagnostic {
 
@@ -36,14 +38,39 @@
     return message;
   }
 
-  static KotlinMetadataDiagnostic messageInvalidUnderlyingType(DexClass clazz, String typeAlias) {
+  static KotlinMetadataDiagnostic missingCompanionObject(
+      DexClass clazz, String companionObjectName) {
     return new KotlinMetadataDiagnostic(
         clazz.getOrigin(),
         Position.UNKNOWN,
-        "The type alias "
-            + typeAlias
-            + " in class "
-            + clazz.type.getName()
-            + " has an invalid underlying type. The type-alias is removed from the output.");
+        "The companion object "
+            + companionObjectName
+            + " could not be found in class "
+            + clazz.type.getName());
+  }
+
+  static KotlinMetadataDiagnostic unknownClassifier(String classifier) {
+    return new KotlinMetadataDiagnostic(
+        Origin.unknown(),
+        Position.UNKNOWN,
+        "The classifier " + classifier + " is unknown and cannot be parsed");
+  }
+
+  static KotlinMetadataDiagnostic invalidMethodDescriptor(String nameAndDescriptor) {
+    return new KotlinMetadataDiagnostic(
+        Origin.unknown(),
+        Position.UNKNOWN,
+        "Invalid descriptor (deserialized from Kotlin @Metadata): " + nameAndDescriptor);
+  }
+
+  static KotlinMetadataDiagnostic unexpectedErrorWhenRewriting(DexType type, Throwable t) {
+    return new KotlinMetadataDiagnostic(
+        Origin.unknown(),
+        Position.UNKNOWN,
+        "Unexpected error during rewriting of Kotlin metadata for class '"
+            + type.toSourceString()
+            + "':"
+            + StringUtils.LINE_SEPARATOR
+            + StringUtils.stacktraceAsString(t));
   }
 }
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataJvmExtensionUtils.java b/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataJvmExtensionUtils.java
deleted file mode 100644
index 855a71f..0000000
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataJvmExtensionUtils.java
+++ /dev/null
@@ -1,259 +0,0 @@
-// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-package com.android.tools.r8.kotlin;
-
-import static com.android.tools.r8.kotlin.Kotlin.addKotlinPrefix;
-
-import com.android.tools.r8.errors.InvalidDescriptorException;
-import com.android.tools.r8.graph.DexField;
-import com.android.tools.r8.graph.DexMethod;
-import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.utils.DescriptorUtils;
-import com.android.tools.r8.utils.Reporter;
-import com.android.tools.r8.utils.StringDiagnostic;
-import com.android.tools.r8.utils.StringUtils;
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.ImmutableMap;
-import java.util.Arrays;
-import java.util.Map;
-import java.util.stream.Collectors;
-import java.util.stream.IntStream;
-import kotlinx.metadata.KmConstructor;
-import kotlinx.metadata.KmConstructorExtensionVisitor;
-import kotlinx.metadata.KmConstructorVisitor;
-import kotlinx.metadata.KmExtensionType;
-import kotlinx.metadata.KmFunction;
-import kotlinx.metadata.KmProperty;
-import kotlinx.metadata.KmPropertyExtensionVisitor;
-import kotlinx.metadata.KmPropertyVisitor;
-import kotlinx.metadata.jvm.JvmConstructorExtensionVisitor;
-import kotlinx.metadata.jvm.JvmExtensionsKt;
-import kotlinx.metadata.jvm.JvmFieldSignature;
-import kotlinx.metadata.jvm.JvmMethodSignature;
-import kotlinx.metadata.jvm.JvmPropertyExtensionVisitor;
-
-class KotlinMetadataJvmExtensionUtils {
-
-  // Mappings from Kotlin types to JVM types (of String)
-  private static final Map<String, String> knownTypeConversion =
-      // See {@link org.jetbrains.kotlin.metadata.jvm.deserialization.ClassMapperLite}
-      ImmutableMap.<String, String>builder()
-          // Boxed primitives and arrays
-          .put(addKotlinPrefix("Boolean;"), "Z")
-          .put(addKotlinPrefix("BooleanArray;"), "[Z")
-          .put(addKotlinPrefix("Byte;"), "B")
-          .put(addKotlinPrefix("ByteArray;"), "[B")
-          .put(addKotlinPrefix("Char;"), "C")
-          .put(addKotlinPrefix("CharArray;"), "[C")
-          .put(addKotlinPrefix("Short;"), "S")
-          .put(addKotlinPrefix("ShortArray;"), "[S")
-          .put(addKotlinPrefix("Int;"), "I")
-          .put(addKotlinPrefix("IntArray;"), "[I")
-          .put(addKotlinPrefix("Long;"), "J")
-          .put(addKotlinPrefix("LongArray;"), "[J")
-          .put(addKotlinPrefix("Float;"), "F")
-          .put(addKotlinPrefix("FloatArray;"), "[F")
-          .put(addKotlinPrefix("Double;"), "D")
-          .put(addKotlinPrefix("DoubleArray;"), "[D")
-          // Other intrinsics
-          .put(addKotlinPrefix("Unit;"), "V")
-          .put(addKotlinPrefix("Any;"), "Ljava/lang/Object;")
-          .put(addKotlinPrefix("Nothing;"), "Ljava/lang/Void;")
-          .putAll(ImmutableList.of(
-              "String", "CharSequence", "Throwable", "Cloneable", "Number", "Comparable", "Enum")
-                  .stream().collect(Collectors.toMap(
-                      t -> addKotlinPrefix(t + ";"),
-                      t -> "Ljava/lang/" + t + ";")))
-          // Collections
-          .putAll(ImmutableList.of("Iterator", "Collection", "List", "Set", "Map", "ListIterator")
-              .stream().collect(Collectors.toMap(
-                  t -> addKotlinPrefix("collections/" + t + ";"),
-                  t -> "Ljava/util/" + t + ";")))
-          .putAll(ImmutableList.of("Iterator", "Collection", "List", "Set", "Map", "ListIterator")
-              .stream().collect(Collectors.toMap(
-                  t -> addKotlinPrefix("collections/Mutable" + t + ";"),
-                  t -> "Ljava/util/" + t + ";")))
-          .put(addKotlinPrefix("collections/Iterable;"), "Ljava/lang/Iterable;")
-          .put(addKotlinPrefix("collections/MutableIterable;"), "Ljava/lang/Iterable;")
-          .put(addKotlinPrefix("collections/Map.Entry;"), "Ljava/util/Map$Entry;")
-          .put(addKotlinPrefix("collections/MutableMap.MutableEntry;"), "Ljava/util/Map$Entry;")
-          // .../FunctionN -> .../jvm/functions/FunctionN
-          .putAll(
-              IntStream.rangeClosed(0, 22).boxed().collect(Collectors.toMap(
-                  i -> addKotlinPrefix("Function" + i + ";"),
-                  i -> addKotlinPrefix("jvm/functions/Function" + i + ";"))))
-          .build();
-
-  // TODO(b/151195430): remove backward type conversions.
-  private static String remapKotlinType(String type) {
-    if (knownTypeConversion.containsKey(type)) {
-      return knownTypeConversion.get(type);
-    }
-    return type;
-  }
-
-  // TODO(b/151195430): remove backward type conversions.
-  // Kotlin @Metadata deserialization has plain "kotlin", which will be relocated in r8lib.
-  // See b/70169921#comment57 for more details.
-  // E.g., desc: (Labc/xyz/C;Lkotlin/Function1;)kotlin/Unit
-  // remapped desc would be: (Labc/xyz/C;Lkotlin/jvm/functions/Function1;)V
-  private static String remapKotlinTypeInDesc(String desc, Reporter reporter) {
-    if (desc == null) {
-      return null;
-    }
-    if (desc.isEmpty()) {
-      return desc;
-    }
-    String[] parameterTypes;
-    try {
-      parameterTypes = DescriptorUtils.getArgumentTypeDescriptors(desc);
-      for (int i = 0; i < parameterTypes.length; i++) {
-        parameterTypes[i] = remapKotlinType(parameterTypes[i]);
-      }
-    } catch (InvalidDescriptorException e) {
-      // JvmMethodSignature from @Metadata is not 100% reliable (due to its own optimization using
-      // map, relocation in r8lib, etc.)
-      reporter.info(
-          new StringDiagnostic(
-              "Invalid descriptor (deserialized from Kotlin @Metadata): " + desc));
-      return desc;
-    }
-    int index = desc.indexOf(')');
-    assert 0 < index && index < desc.length() : desc;
-    String returnType = remapKotlinType(desc.substring(index + 1));
-    return "(" + StringUtils.join(Arrays.asList(parameterTypes), "") + ")" + returnType;
-  }
-
-  static JvmFieldSignature toJvmFieldSignature(DexField field) {
-    return new JvmFieldSignature(field.name.toString(), field.type.toDescriptorString());
-  }
-
-  static JvmMethodSignature toJvmMethodSignature(DexMethod method) {
-    StringBuilder descBuilder = new StringBuilder();
-    descBuilder.append("(");
-    for (DexType argType : method.proto.parameters.values) {
-      descBuilder.append(argType.toDescriptorString());
-    }
-    descBuilder.append(")");
-    descBuilder.append(method.proto.returnType.toDescriptorString());
-    return new JvmMethodSignature(method.name.toString(), descBuilder.toString());
-  }
-
-  static class KmConstructorProcessor {
-    private JvmMethodSignature signature = null;
-
-    KmConstructorProcessor(KmConstructor kmConstructor, Reporter reporter) {
-      kmConstructor.accept(new KmConstructorVisitor() {
-        @Override
-        public KmConstructorExtensionVisitor visitExtensions(KmExtensionType type) {
-          if (type != JvmConstructorExtensionVisitor.TYPE) {
-            return null;
-          }
-          return new JvmConstructorExtensionVisitor() {
-            @Override
-            public void visit(JvmMethodSignature desc) {
-              assert signature == null : signature.asString();
-              signature = desc;
-            }
-          };
-        }
-      });
-      if (signature != null) {
-        String remappedDesc = remapKotlinTypeInDesc(signature.getDesc(), reporter);
-        if (remappedDesc != null && !remappedDesc.equals(signature.getDesc())) {
-          signature = new JvmMethodSignature(signature.getName(), remappedDesc);
-        }
-      }
-    }
-
-    JvmMethodSignature signature() {
-      return signature;
-    }
-  }
-
-  // Custom name via @JvmName("..."). Otherwise, null.
-  static JvmMethodSignature getJvmMethodSignature(KmConstructor kmConstructor, Reporter reporter) {
-    return remapJvmMethodSignature(JvmExtensionsKt.getSignature(kmConstructor), reporter);
-  }
-
-  // Custom name via @JvmName("..."). Otherwise, null.
-  static JvmMethodSignature getJvmMethodSignature(KmFunction kmFunction, Reporter reporter) {
-    return remapJvmMethodSignature(JvmExtensionsKt.getSignature(kmFunction), reporter);
-  }
-
-  private static JvmMethodSignature remapJvmMethodSignature(
-      JvmMethodSignature signature, Reporter reporter) {
-    if (signature != null) {
-      String remappedDesc = remapKotlinTypeInDesc(signature.getDesc(), reporter);
-      if (remappedDesc != null && !remappedDesc.equals(signature.getDesc())) {
-        signature = new JvmMethodSignature(signature.getName(), remappedDesc);
-      }
-    }
-    return signature;
-  }
-
-  static class KmPropertyProcessor {
-    private JvmFieldSignature fieldSignature = null;
-    // Custom getter via @get:JvmName("..."). Otherwise, null.
-    private JvmMethodSignature getterSignature = null;
-    // Custom getter via @set:JvmName("..."). Otherwise, null.
-    private JvmMethodSignature setterSignature = null;
-
-    KmPropertyProcessor(KmProperty kmProperty, Reporter reporter) {
-      kmProperty.accept(new KmPropertyVisitor() {
-        @Override
-        public KmPropertyExtensionVisitor visitExtensions(KmExtensionType type) {
-          if (type != JvmPropertyExtensionVisitor.TYPE) {
-            return null;
-          }
-          return new JvmPropertyExtensionVisitor() {
-            @Override
-            public void visit(
-                int flags,
-                JvmFieldSignature fieldDesc,
-                JvmMethodSignature getterDesc,
-                JvmMethodSignature setterDesc) {
-              assert fieldSignature == null : fieldSignature.asString();
-              fieldSignature = fieldDesc;
-              assert getterSignature == null : getterSignature.asString();
-              getterSignature = getterDesc;
-              assert setterSignature == null : setterSignature.asString();
-              setterSignature = setterDesc;
-            }
-          };
-        }
-      });
-      if (fieldSignature != null) {
-        String remappedDesc = remapKotlinType(fieldSignature.getDesc());
-        if (remappedDesc != null && !remappedDesc.equals(fieldSignature.getDesc())) {
-          fieldSignature = new JvmFieldSignature(fieldSignature.getName(), remappedDesc);
-        }
-      }
-      if (getterSignature != null) {
-        String remappedDesc = remapKotlinTypeInDesc(getterSignature.getDesc(), reporter);
-        if (remappedDesc != null && !remappedDesc.equals(getterSignature.getDesc())) {
-          getterSignature = new JvmMethodSignature(getterSignature.getName(), remappedDesc);
-        }
-      }
-      if (setterSignature != null) {
-        String remappedDesc = remapKotlinTypeInDesc(setterSignature.getDesc(), reporter);
-        if (remappedDesc != null && !remappedDesc.equals(setterSignature.getDesc())) {
-          setterSignature = new JvmMethodSignature(setterSignature.getName(), remappedDesc);
-        }
-      }
-    }
-
-    JvmFieldSignature fieldSignature() {
-      return fieldSignature;
-    }
-
-    JvmMethodSignature getterSignature() {
-      return getterSignature;
-    }
-
-    JvmMethodSignature setterSignature() {
-      return setterSignature;
-    }
-  }
-}
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataRewriter.java b/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataRewriter.java
index 1c597dc..b3e0eb4 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataRewriter.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataRewriter.java
@@ -3,18 +3,20 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.kotlin;
 
+import static com.android.tools.r8.kotlin.KotlinMetadataUtils.NO_KOTLIN_INFO;
+
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexAnnotation;
 import com.android.tools.r8.graph.DexAnnotationElement;
 import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexEncodedAnnotation;
 import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.DexValue;
 import com.android.tools.r8.graph.DexValue.DexValueArray;
 import com.android.tools.r8.graph.DexValue.DexValueInt;
 import com.android.tools.r8.graph.DexValue.DexValueString;
-import com.android.tools.r8.graph.SubtypingInfo;
 import com.android.tools.r8.naming.NamingLens;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.utils.ThreadUtils;
@@ -39,7 +41,10 @@
   }
 
   public static void removeKotlinMetadataFromRenamedClass(AppView<?> appView, DexType type) {
-    DexClass clazz = appView.definitionFor(type);
+    // TODO(b/154294232): Seems like this should never be called. Either we explicitly keep the
+    //  class and allow obfuscation - in which we should not remove it, or we should never have
+    //  metadata in the first place.
+    DexProgramClass clazz = appView.definitionForProgramType(type);
     if (clazz == null) {
       return;
     }
@@ -52,7 +57,7 @@
     // Clear associated {@link KotlinInfo} to avoid accidentally deserialize it back to
     // DexAnnotation we've just removed above.
     if (clazz.isProgramClass()) {
-      clazz.asProgramClass().setKotlinInfo(null);
+      clazz.asProgramClass().setKotlinInfo(NO_KOTLIN_INFO);
     }
   }
 
@@ -64,35 +69,29 @@
   public void run(ExecutorService executorService) throws ExecutionException {
     // TODO(b/152283077): Don't disable the assert.
     appView.appInfo().disableDefinitionForAssert();
-    SubtypingInfo subtypingInfo = appView.appInfo().computeSubtypingInfo();
     ThreadUtils.processItems(
         appView.appInfo().classes(),
         clazz -> {
-          KotlinInfo<?> kotlinInfo = clazz.getKotlinInfo();
-          if (kotlinInfo != null) {
-            // If @Metadata is still associated, this class should not be renamed
-            // (by {@link ClassNameMinifier} of course).
-            // Or, we start maintaining @Metadata for renamed classes.
-            // TODO(b/151194540): if this option is settled down, this assertion is meaningless.
-            assert lens.lookupType(clazz.type, appView.dexItemFactory()) == clazz.type
-                    || appView.options().enableKotlinMetadataRewritingForRenamedClasses
-                : clazz.toSourceString()
-                    + " != "
-                    + lens.lookupType(clazz.type, appView.dexItemFactory());
-
-            DexAnnotation oldMeta =
-                clazz.annotations().getFirstMatching(kotlin.metadata.kotlinMetadataType);
-            // If @Metadata is already gone, e.g., by {@link AnnotationRemover} if type Metadata is
-            // determined as dead (e.g., due to no keep rule), nothing to do.
-            if (oldMeta == null) {
-              return;
+          KotlinClassLevelInfo kotlinInfo = clazz.getKotlinInfo();
+          DexAnnotation oldMeta =
+              clazz.annotations().getFirstMatching(kotlin.metadata.kotlinMetadataType);
+          if (kotlinInfo == NO_KOTLIN_INFO) {
+            // TODO(b/154346948): Track invalid meta-data objects such that we can enable this
+            // assert oldMeta == null;
+            return;
+          }
+          if (oldMeta != null) {
+            try {
+              KotlinClassHeader kotlinClassHeader = kotlinInfo.rewrite(clazz, appView, lens);
+              DexAnnotation newMeta = createKotlinMetadataAnnotation(kotlinClassHeader);
+              clazz.setAnnotations(
+                  clazz.annotations().rewrite(anno -> anno == oldMeta ? newMeta : anno));
+            } catch (Throwable t) {
+              appView
+                  .options()
+                  .reporter
+                  .warning(KotlinMetadataDiagnostic.unexpectedErrorWhenRewriting(clazz.type, t));
             }
-
-            kotlinInfo.rewrite(appView, subtypingInfo, lens);
-
-            DexAnnotation newMeta = createKotlinMetadataAnnotation(kotlinInfo.createHeader());
-            clazz.setAnnotations(
-                clazz.annotations().rewrite(anno -> anno == oldMeta ? newMeta : anno));
           }
         },
         executorService);
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataSynthesizer.java b/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataSynthesizer.java
deleted file mode 100644
index 2779111..0000000
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataSynthesizer.java
+++ /dev/null
@@ -1,859 +0,0 @@
-// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-package com.android.tools.r8.kotlin;
-
-import static com.android.tools.r8.kotlin.KotlinMemberInfo.EMPTY_TYPE_PARAM_INFO;
-import static com.android.tools.r8.kotlin.KotlinMetadataJvmExtensionUtils.toJvmFieldSignature;
-import static com.android.tools.r8.kotlin.KotlinMetadataJvmExtensionUtils.toJvmMethodSignature;
-import static com.android.tools.r8.kotlin.KotlinMetadataSynthesizerUtils.populateKmTypeFromSignature;
-import static com.android.tools.r8.kotlin.KotlinMetadataSynthesizerUtils.toClassifier;
-import static com.android.tools.r8.utils.DescriptorUtils.descriptorToKotlinClassifier;
-import static com.android.tools.r8.utils.DescriptorUtils.getBinaryNameFromDescriptor;
-import static kotlinx.metadata.Flag.Property.IS_VAR;
-import static kotlinx.metadata.FlagsKt.flagsOf;
-
-import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.DebugLocalInfo;
-import com.android.tools.r8.graph.DexClass;
-import com.android.tools.r8.graph.DexEncodedField;
-import com.android.tools.r8.graph.DexEncodedMethod;
-import com.android.tools.r8.graph.DexField;
-import com.android.tools.r8.graph.DexMethod;
-import com.android.tools.r8.graph.DexProto;
-import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.graph.GenericSignature;
-import com.android.tools.r8.graph.GenericSignature.FieldTypeSignature;
-import com.android.tools.r8.graph.GenericSignature.FormalTypeParameter;
-import com.android.tools.r8.graph.GenericSignature.MethodTypeSignature;
-import com.android.tools.r8.graph.GenericSignature.TypeSignature;
-import com.android.tools.r8.graph.InnerClassAttribute;
-import com.android.tools.r8.kotlin.KotlinMemberInfo.KotlinFunctionInfo;
-import com.android.tools.r8.kotlin.KotlinMemberInfo.KotlinPropertyInfo;
-import com.android.tools.r8.kotlin.KotlinMetadataSynthesizerUtils.AddKotlinAnyType;
-import com.android.tools.r8.kotlin.KotlinMetadataSynthesizerUtils.KmVisitorOption;
-import com.android.tools.r8.naming.NamingLens;
-import com.android.tools.r8.shaking.AppInfoWithLiveness;
-import com.android.tools.r8.utils.Box;
-import com.google.common.collect.ImmutableList;
-import java.util.List;
-import java.util.function.Consumer;
-import kotlinx.metadata.KmClassifier;
-import kotlinx.metadata.KmConstructor;
-import kotlinx.metadata.KmFunction;
-import kotlinx.metadata.KmProperty;
-import kotlinx.metadata.KmType;
-import kotlinx.metadata.KmTypeParameter;
-import kotlinx.metadata.KmTypeProjection;
-import kotlinx.metadata.KmValueParameter;
-import kotlinx.metadata.KmVariance;
-import kotlinx.metadata.jvm.JvmExtensionsKt;
-
-class KotlinMetadataSynthesizer {
-
-  final AppView<AppInfoWithLiveness> appView;
-  final NamingLens lens;
-  private final List<KmTypeParameter> classTypeParameters;
-
-  public KotlinMetadataSynthesizer(
-      AppView<AppInfoWithLiveness> appView, NamingLens lens, KotlinInfo<?> kotlinInfo) {
-    this.appView = appView;
-    this.lens = lens;
-    this.classTypeParameters = kotlinInfo.getTypeParameters();
-  }
-
-  static boolean isExtension(KmFunction kmFunction) {
-    return kmFunction.getReceiverParameterType() != null;
-  }
-
-  static boolean isExtension(KmProperty kmProperty) {
-    return kmProperty.getReceiverParameterType() != null;
-  }
-
-  static KmType toKmType(String descriptor) {
-    KmType kmType = new KmType(flagsOf());
-    kmType.visitClass(descriptorToKotlinClassifier(descriptor));
-    return kmType;
-  }
-
-  private DexType toRenamedType(DexType type) {
-    // For library or classpath class, synthesize @Metadata always.
-    // For a program class, make sure it is live.
-    if (!appView.appInfo().isNonProgramTypeOrLiveProgramType(type)) {
-      return null;
-    }
-    DexType renamedType = lens.lookupType(type, appView.dexItemFactory());
-    // For library or classpath class, we should not have renamed it.
-    DexClass clazz = appView.definitionFor(type);
-    assert clazz == null || clazz.isProgramClass() || renamedType == type
-        : type.toSourceString() + " -> " + renamedType.toSourceString();
-    return renamedType;
-  }
-
-  String toRenamedBinaryName(DexType type) {
-    DexType renamedType = toRenamedType(type);
-    if (renamedType == null) {
-      return null;
-    }
-    return getBinaryNameFromDescriptor(renamedType.toDescriptorString());
-  }
-
-  String toRenamedClassifier(DexType type) {
-    type = type.isClassType() ? toRenamedType(type) : type;
-    if (type == null) {
-      return null;
-    }
-    return toClassifier(type, appView.dexItemFactory());
-  }
-
-  // TODO(b/148654451): Canonicalization?
-  KmType toRenamedKmType(
-      DexType type,
-      TypeSignature typeSignature,
-      KotlinTypeInfo originalKotlinTypeInfo,
-      List<KmTypeParameter> typeParameters) {
-    if (originalKotlinTypeInfo != null && originalKotlinTypeInfo.isTypeAlias()) {
-      KmType kmType = new KmType(flagsOf());
-      kmType.visitTypeAlias(originalKotlinTypeInfo.asTypeAlias().getName());
-      return kmType;
-    }
-    return toRenamedKmTypeWithClassifier(
-        type, originalKotlinTypeInfo, typeSignature, typeParameters);
-  }
-
-  private KmType toRenamedKmTypeWithClassifierForFieldSignature(
-      KotlinTypeInfo originalTypeInfo,
-      FieldTypeSignature fieldSignature,
-      List<KmTypeParameter> typeParameters) {
-    Box<KmType> kmTypeBox = new Box<>();
-    populateKmTypeFromSignature(
-        fieldSignature,
-        originalTypeInfo,
-        (kmVisitorOption) -> {
-          assert kmVisitorOption == KmVisitorOption.VISIT_NEW;
-          KmType value = new KmType(flagsOf());
-          kmTypeBox.set(value);
-          return value;
-        },
-        typeParameters,
-        appView.dexItemFactory(),
-        AddKotlinAnyType.ADD);
-    return kmTypeBox.get();
-  }
-
-  private KmType toRenamedKmTypeWithClassifier(
-      DexType type,
-      KotlinTypeInfo originalTypeInfo,
-      TypeSignature typeSignature,
-      List<KmTypeParameter> typeParameters) {
-    if (typeSignature != null && typeSignature.isFieldTypeSignature()) {
-      KmType renamedKmType =
-          toRenamedKmTypeWithClassifierForFieldSignature(
-              originalTypeInfo, typeSignature.asFieldTypeSignature(), typeParameters);
-      if (renamedKmType != null) {
-        return renamedKmType;
-      }
-    }
-    String classifier = toRenamedClassifier(type);
-    if (classifier == null) {
-      return null;
-    }
-    // Seems like no flags for KmType are ever set, thus passing in flagsOf() seems ok.
-    KmType renamedKmType = new KmType(flagsOf());
-    renamedKmType.visitClass(classifier);
-    // TODO(b/151194164): Can be generalized too, like ArrayTypeSignature.Converter ?
-    // E.g., java.lang.String[] -> KmType(kotlin/Array, KmTypeProjection(OUT, kotlin/String))
-    if (type.isArrayType() && !type.isPrimitiveArrayType()) {
-      DexType elementType = type.toArrayElementType(appView.dexItemFactory());
-      KmType argumentType = toRenamedKmTypeWithClassifier(elementType, null, null, typeParameters);
-      KmVariance variance =
-          originalTypeInfo != null && originalTypeInfo.isObjectArray()
-              ? originalTypeInfo.getArguments().get(0).variance
-              : KmVariance.INVARIANT;
-      if (variance == null) {
-        variance = KmVariance.INVARIANT;
-      }
-      renamedKmType.getArguments().add(new KmTypeProjection(variance, argumentType));
-    }
-    return renamedKmType;
-  }
-
-  KmConstructor toRenamedKmConstructor(DexClass clazz, DexEncodedMethod method) {
-    // Make sure it is an instance initializer and live.
-    if (!method.isInstanceInitializer()
-        || !appView.appInfo().liveMethods.contains(method.method)) {
-      return null;
-    }
-    // Take access flags from metadata.
-    KotlinFunctionInfo kotlinFunctionInfo = method.getKotlinMemberInfo().asFunctionInfo();
-    int flags;
-    List<KotlinTypeParameterInfo> originalTypeParameterInfo;
-    if (kotlinFunctionInfo != null) {
-      flags = kotlinFunctionInfo.flags;
-      originalTypeParameterInfo = kotlinFunctionInfo.kotlinTypeParameterInfo;
-    } else {
-      flags = method.accessFlags.getAsKotlinFlags();
-      originalTypeParameterInfo = EMPTY_TYPE_PARAM_INFO;
-    }
-    KmConstructor kmConstructor = new KmConstructor(flags);
-    JvmExtensionsKt.setSignature(kmConstructor, toJvmMethodSignature(method.method));
-    MethodTypeSignature signature = GenericSignature.Parser.toMethodTypeSignature(method, appView);
-    List<KmTypeParameter> typeParameters =
-        convertFormalTypeParameters(
-            originalTypeParameterInfo,
-            signature.getFormalTypeParameters(),
-            kmTypeParameter -> {
-              assert false : "KmConstructor cannot have additional type parameters";
-            });
-    List<KmValueParameter> parameters = kmConstructor.getValueParameters();
-    if (!populateKmValueParameters(method, signature, parameters, typeParameters)) {
-      return null;
-    }
-    // For inner, non-static classes, the type-parameter for the receiver should not have a
-    // value-parameter:
-    // val myInner : OuterNestedInner = nested.Inner(1)
-    // Will have value-parameters for the constructor:
-    // #  constructors: KmConstructor[
-    // #    KmConstructor{
-    // #      flags: 6,
-    // #      valueParameters: KmValueParameter[
-    // #        KmValueParameter{
-    // #          flags: 0,
-    // #          name: x,
-    // #          type: KmType{
-    // #            flags: 0,
-    // #            classifier: Class(name=kotlin/Int),
-    // #            arguments: KmTypeProjection[],
-    // #            abbreviatedType: null,
-    // #            outerType: null,
-    // #            raw: false,
-    // #            annotations: KmAnnotion[],
-    // #          },
-    // #          varargElementType: null,
-    // #        }
-    // #      ],
-    // #     signature: <init>(Lcom/android/tools/r8/kotlin/metadata/typealias_lib/Outer$Nested;I)V,
-    // #    }
-    // #  ],
-    // A bit weird since the signature obviously have two value-parameters.
-    List<InnerClassAttribute> innerClasses = clazz.getInnerClasses();
-    if (!parameters.isEmpty() && !innerClasses.isEmpty()) {
-      DexType immediateOuterType = null;
-      for (InnerClassAttribute innerClass : innerClasses) {
-        if (innerClass.getInner() == clazz.type) {
-          immediateOuterType = innerClass.getOuter();
-          break;
-        }
-      }
-      if (immediateOuterType != null) {
-        String classifier = toRenamedClassifier(immediateOuterType);
-        KmType potentialReceiver = parameters.get(0).getType();
-        if (potentialReceiver != null
-            && potentialReceiver.classifier instanceof KmClassifier.Class
-            && ((KmClassifier.Class) potentialReceiver.classifier).getName().equals(classifier)) {
-          parameters.remove(0);
-        }
-      }
-    }
-    return kmConstructor;
-  }
-
-  KmFunction toRenamedKmFunction(DexEncodedMethod method) {
-    // For library overrides, synthesize @Metadata always.
-    // For regular methods, make sure it is live or pinned.
-    if (!method.isLibraryMethodOverride().isTrue()
-        && !appView.appInfo().isPinned(method.method)
-        && !appView.appInfo().liveMethods.contains(method.method)) {
-      return null;
-    }
-    DexMethod renamedMethod = lens.lookupMethod(method.method, appView.dexItemFactory());
-    // For a library method override, we should not have renamed it.
-    assert !method.isLibraryMethodOverride().isTrue() || renamedMethod.name == method.method.name
-        : method.toSourceString() + " -> " + renamedMethod.toSourceString();
-    // TODO(b/151194869): Should we keep kotlin-specific flags only while synthesizing the base
-    //  value from general JVM flags?
-    KotlinFunctionInfo kotlinMemberInfo = method.getKotlinMemberInfo().asFunctionInfo();
-    assert kotlinMemberInfo != null;
-    int flag =
-        appView.appInfo().isPinned(method.method)
-            ? kotlinMemberInfo.flags
-            : method.accessFlags.getAsKotlinFlags();
-    KmFunction kmFunction = new KmFunction(flag, renamedMethod.name.toString());
-    JvmExtensionsKt.setSignature(kmFunction, toJvmMethodSignature(renamedMethod));
-
-    // TODO(b/129925954): Should this be integrated as part of DexDefinition instead of parsing it
-    //  on demand? That may require utils to map internal encoding of signature back to
-    //  corresponding backend definitions, like DexAnnotation (DEX) or Signature attribute (CF).
-    MethodTypeSignature signature = GenericSignature.Parser.toMethodTypeSignature(method, appView);
-    List<KmTypeParameter> methodTypeParameters = kmFunction.getTypeParameters();
-    DexProto proto = method.method.proto;
-    DexType returnType = proto.returnType;
-    TypeSignature returnSignature = signature.returnType().typeSignature();
-    List<KmTypeParameter> allTypeParameters =
-        convertFormalTypeParameters(
-            kotlinMemberInfo.kotlinTypeParameterInfo,
-            signature.getFormalTypeParameters(),
-            methodTypeParameters::add);
-    KmType kmReturnType =
-        toRenamedKmType(
-            returnType, returnSignature, kotlinMemberInfo.returnType, allTypeParameters);
-    if (kmReturnType == null) {
-      return null;
-    }
-    kmFunction.setReturnType(kmReturnType);
-    if (method.isKotlinExtensionFunction()) {
-      assert proto.parameters.values.length > 0 : method.method.toSourceString();
-      DexType receiverType = proto.parameters.values[0];
-      TypeSignature receiverSignature = signature.getParameterTypeSignature(0);
-      KmType kmReceiverType =
-          toRenamedKmType(
-              receiverType,
-              receiverSignature,
-              kotlinMemberInfo.receiverParameterType,
-              allTypeParameters);
-      if (kmReceiverType == null) {
-        return null;
-      }
-      kmFunction.setReceiverParameterType(kmReceiverType);
-    }
-
-    if (!populateKmValueParameters(
-        method, signature, kmFunction.getValueParameters(), allTypeParameters)) {
-      return null;
-    }
-    return kmFunction;
-  }
-
-  private List<KmTypeParameter> convertFormalTypeParameters(
-      List<KotlinTypeParameterInfo> originalTypeParameterInfo,
-      List<FormalTypeParameter> parameters,
-      Consumer<KmTypeParameter> addedFromParameters) {
-    return KotlinMetadataSynthesizerUtils.convertFormalTypeParameters(
-        classTypeParameters,
-        originalTypeParameterInfo,
-        parameters,
-        appView.dexItemFactory(),
-        addedFromParameters);
-  }
-
-  private boolean populateKmValueParameters(
-      DexEncodedMethod method,
-      MethodTypeSignature signature,
-      List<KmValueParameter> parameters,
-      List<KmTypeParameter> typeParameters) {
-    KotlinFunctionInfo kotlinFunctionInfo = method.getKotlinMemberInfo().asFunctionInfo();
-    if (kotlinFunctionInfo == null) {
-      return false;
-    }
-    boolean isExtension = method.isKotlinExtensionFunction();
-    for (int i = isExtension ? 1 : 0; i < method.method.proto.parameters.values.length; i++) {
-      DexType parameterType = method.method.proto.parameters.values[i];
-      DebugLocalInfo debugLocalInfo = method.getParameterInfo().get(i);
-      String parameterName = debugLocalInfo != null ? debugLocalInfo.name.toString() : ("p" + i);
-      KotlinValueParameterInfo valueParameterInfo =
-          kotlinFunctionInfo.getValueParameterInfo(isExtension ? i - 1 : i);
-      TypeSignature parameterTypeSignature = signature.getParameterTypeSignature(i);
-      KmValueParameter kmValueParameter =
-          toRewrittenKmValueParameter(
-              valueParameterInfo,
-              parameterType,
-              parameterTypeSignature,
-              parameterName,
-              typeParameters);
-      if (kmValueParameter == null) {
-        return false;
-      }
-      parameters.add(kmValueParameter);
-    }
-    return true;
-  }
-
-  private KmValueParameter toRewrittenKmValueParameter(
-      KotlinValueParameterInfo valueParameterInfo,
-      DexType parameterType,
-      TypeSignature parameterTypeSignature,
-      String candidateParameterName,
-      List<KmTypeParameter> typeParameters) {
-    int flag = valueParameterInfo != null ? valueParameterInfo.flag : flagsOf();
-    String name = valueParameterInfo != null ? valueParameterInfo.name : candidateParameterName;
-    KmValueParameter kmValueParameter = new KmValueParameter(flag, name);
-    KotlinTypeInfo originalKmTypeInfo = valueParameterInfo != null ? valueParameterInfo.type : null;
-    KmType kmParamType =
-        toRenamedKmType(parameterType, parameterTypeSignature, originalKmTypeInfo, typeParameters);
-    if (kmParamType == null) {
-      return null;
-    }
-    kmValueParameter.setType(kmParamType);
-    if (valueParameterInfo != null) {
-      JvmExtensionsKt.getAnnotations(kmParamType).addAll(valueParameterInfo.annotations);
-    }
-    if (valueParameterInfo != null && valueParameterInfo.isVararg) {
-      // TODO(b/152389234): Test for arrays in varargs.
-      if (!parameterType.isArrayType()) {
-        return null;
-      }
-      // vararg x: T gets translated to x: Array<out T>
-      DexType elementType = parameterType.toArrayElementType(appView.dexItemFactory());
-      TypeSignature elementSignature =
-          parameterTypeSignature != null
-              ? parameterTypeSignature.toArrayElementTypeSignature(appView) : null;
-      KmType kmElementType = toRenamedKmType(elementType, elementSignature, null, typeParameters);
-      if (kmElementType == null) {
-        return null;
-      }
-      kmValueParameter.setVarargElementType(kmElementType);
-    }
-
-    return kmValueParameter;
-  }
-
-  /**
-   * A group of a field, and methods that correspond to a Kotlin property.
-   *
-   * <p>
-   * va(l|r) prop: T
-   *
-   * is converted to a backing field with signature `T prop` if necessary. If the property is a pure
-   * computation, e.g., return `this` or access to the status of other properties, a field is not
-   * needed for the property.
-   * <p>
-   * Depending on property visibility, getter is defined with signature `T getProp()` (or `isProp`
-   * for boolean property).
-   * <p>
-   * If it's editable, i.e., declared as `var`, setter is defined with signature `void setProp(T)`.
-   * <p>
-   * Yet another addition, `void prop$annotations()`, seems(?) to be used to store associated
-   * annotations.
-   *
-   * <p>
-   * Currently, it's unclear how users can selectively keep each. Assuming every piece is kept by
-   * more general rules, such as keeping non-private members, we expect to find those as a whole.
-   * When rewriting a corresponding property, each information is used to update corresponding part,
-   * e.g., field to update the return type of the property, getter to update jvmMethodSignature of
-   * getter, and so on.
-   */
-  static class KmPropertyGroup {
-
-    final int flags;
-    final String name;
-    final DexEncodedField field;
-    private final DexEncodedMethod getter;
-    private final KotlinPropertyInfo getterInfo;
-    private final DexEncodedMethod setter;
-    private final KotlinPropertyInfo setterInfo;
-    final DexEncodedMethod annotations;
-    final boolean isExtension;
-    private final List<KmTypeParameter> classTypeParameters;
-
-    private KmPropertyGroup(
-        int flags,
-        String name,
-        DexEncodedField field,
-        DexEncodedMethod getter,
-        KotlinPropertyInfo getterInfo,
-        DexEncodedMethod setter,
-        KotlinPropertyInfo setterInfo,
-        DexEncodedMethod annotations,
-        boolean isExtension,
-        List<KmTypeParameter> classTypeParameters) {
-      this.flags = flags;
-      this.name = name;
-      this.field = field;
-      this.getter = getter;
-      this.getterInfo = getterInfo;
-      this.setter = setter;
-      this.setterInfo = setterInfo;
-      this.annotations = annotations;
-      this.isExtension = isExtension;
-      this.classTypeParameters = classTypeParameters;
-    }
-
-    static Builder builder(int flags, String name, List<KmTypeParameter> classTypeParameters) {
-      return new Builder(flags, name, classTypeParameters);
-    }
-
-    static class Builder {
-
-      private final int flags;
-      private final String name;
-      private DexEncodedField field;
-      private DexEncodedMethod getter;
-      private KotlinPropertyInfo getterInfo;
-      private DexEncodedMethod setter;
-      private KotlinPropertyInfo setterInfo;
-      private DexEncodedMethod annotations;
-      private List<KmTypeParameter> classTypeParameters;
-
-      private boolean isExtensionGetter;
-      private boolean isExtensionSetter;
-      private boolean isExtensionAnnotations;
-
-      private Builder(int flags, String name, List<KmTypeParameter> classTypeParameters) {
-        this.flags = flags;
-        this.name = name;
-        this.classTypeParameters = classTypeParameters;
-      }
-
-      Builder foundBackingField(DexEncodedField field) {
-        this.field = field;
-        return this;
-      }
-
-      Builder foundGetter(DexEncodedMethod getter, KotlinPropertyInfo propertyInfo) {
-        this.getter = getter;
-        this.getterInfo = propertyInfo;
-        return this;
-      }
-
-      Builder foundSetter(DexEncodedMethod setter, KotlinPropertyInfo propertyInfo) {
-        this.setter = setter;
-        this.setterInfo = propertyInfo;
-        return this;
-      }
-
-      Builder foundAnnotations(DexEncodedMethod annotations) {
-        this.annotations = annotations;
-        return this;
-      }
-
-      Builder isExtensionGetter() {
-        this.isExtensionGetter = true;
-        return this;
-      }
-
-      Builder isExtensionSetter() {
-        this.isExtensionSetter = true;
-        return this;
-      }
-
-      Builder isExtensionAnnotations() {
-        this.isExtensionAnnotations = true;
-        return this;
-      }
-
-      KmPropertyGroup build() {
-        boolean isExtension = isExtensionGetter || isExtensionSetter || isExtensionAnnotations;
-        // If this is an extension property, everything should be an extension.
-        if (isExtension) {
-          if (getter != null && !isExtensionGetter) {
-            return null;
-          }
-          if (setter != null && !isExtensionSetter) {
-            return null;
-          }
-          if (annotations != null && !isExtensionAnnotations) {
-            return null;
-          }
-        }
-        return new KmPropertyGroup(
-            flags,
-            name,
-            field,
-            getter,
-            getterInfo,
-            setter,
-            setterInfo,
-            annotations,
-            isExtension,
-            classTypeParameters);
-      }
-    }
-
-    private String setFieldForProperty(
-        KmProperty kmProperty,
-        KmType defaultPropertyType,
-        AppView<AppInfoWithLiveness> appView,
-        NamingLens lens,
-        KotlinMetadataSynthesizer synthesizer) {
-      DexField renamedField = lens.lookupField(field.field, appView.dexItemFactory());
-      if (kmProperty.getReturnType() == defaultPropertyType) {
-        KmType kmPropertyType =
-            synthesizer.toRenamedKmType(field.field.type, null, null, this.classTypeParameters);
-        if (kmPropertyType != null) {
-          kmProperty.setReturnType(kmPropertyType);
-        }
-      }
-      JvmExtensionsKt.setFieldSignature(kmProperty, toJvmFieldSignature(renamedField));
-      return appView.appInfo().isPinned(field.field) ? null : renamedField.name.toString();
-    }
-
-    private boolean setReceiverParameterTypeForExtensionProperty(
-        KmProperty kmProperty,
-        DexEncodedMethod method,
-        AppView<AppInfoWithLiveness> appView,
-        KotlinMetadataSynthesizer synthesizer) {
-      if (!isExtension) {
-        return true;
-      }
-      MethodTypeSignature signature =
-          GenericSignature.Parser.toMethodTypeSignature(method, appView);
-      // TODO(b/152599446): Update with type parameters for the receiver.
-      List<KmTypeParameter> typeParameters =
-          KotlinMetadataSynthesizerUtils.convertFormalTypeParameters(
-              classTypeParameters,
-              KotlinMemberInfo.EMPTY_TYPE_PARAM_INFO,
-              signature.getFormalTypeParameters(),
-              appView.dexItemFactory(),
-              kmTypeParameter -> {});
-      DexType receiverType = method.method.proto.parameters.values[0];
-      TypeSignature receiverSignature = signature.getParameterTypeSignature(0);
-      KmType kmReceiverType =
-          synthesizer.toRenamedKmType(receiverType, receiverSignature, null, typeParameters);
-      if (kmProperty.getReceiverParameterType() != null) {
-        // If the receiver type for the extension property is set already make sure it's consistent.
-        return KotlinMetadataSynthesizerUtils.hasEqualClassifier(
-            kmReceiverType, kmProperty.getReceiverParameterType());
-      }
-      kmProperty.setReceiverParameterType(kmReceiverType);
-      return true;
-    }
-
-    private String setGetterForProperty(
-        KmProperty kmProperty,
-        KmType defaultPropertyType,
-        AppView<AppInfoWithLiveness> appView,
-        NamingLens lens,
-        KotlinMetadataSynthesizer synthesizer) {
-      if (checkGetterCriteria() == GetterSetterCriteria.NOT_AVAILABLE) {
-        // Property without getter.
-        // Even though a getter does not exist, `kotlinc` still set getter flags and use them to
-        // determine when to direct field access v.s. getter calls for property resolution.
-        if (field != null) {
-          kmProperty.setGetterFlags(field.accessFlags.getAsKotlinFlags());
-        }
-        return kmProperty.getName();
-      }
-      assert checkGetterCriteria() == GetterSetterCriteria.MET;
-      assert getter != null;
-      DexProto proto = getter.method.proto;
-      assert proto.parameters.size() == (isExtension ? 1 : 0)
-          : "checkGetterCriteria: " + this.toString();
-      MethodTypeSignature signature =
-          GenericSignature.Parser.toMethodTypeSignature(getter, appView);
-      // TODO(b/152599446): Update with type parameters for the getter.
-      List<KmTypeParameter> typeParameters =
-          KotlinMetadataSynthesizerUtils.convertFormalTypeParameters(
-              this.classTypeParameters,
-              ImmutableList.of(),
-              signature.getFormalTypeParameters(),
-              appView.dexItemFactory(),
-              kmTypeParameter -> {});
-      DexType returnType = proto.returnType;
-      TypeSignature returnSignature = signature.returnType().typeSignature();
-      KmType kmPropertyType =
-          synthesizer.toRenamedKmType(
-              returnType, returnSignature, getterInfo.returnType, typeParameters);
-      if (kmProperty.getReturnType() == defaultPropertyType) {
-        // The property type is not set yet.
-        kmProperty.setReturnType(kmPropertyType);
-      } else if (!KotlinMetadataSynthesizerUtils.hasEqualClassifier(
-          kmPropertyType, kmProperty.getReturnType())) {
-        // If property type is set already (via backing field), make sure it's consistent.
-        return null;
-      }
-      DexMethod renamedGetter = lens.lookupMethod(getter.method, appView.dexItemFactory());
-      kmProperty.setGetterFlags(getter.accessFlags.getAsKotlinFlags());
-      JvmExtensionsKt.setGetterSignature(kmProperty, toJvmMethodSignature(renamedGetter));
-      return appView.appInfo().isPinned(getter.method) ? null : renamedGetter.name.toString();
-    }
-
-    private String setSetterForProperty(
-        KmProperty kmProperty,
-        KmType defaultPropertyType,
-        AppView<AppInfoWithLiveness> appView,
-        NamingLens lens,
-        KotlinMetadataSynthesizer synthesizer) {
-      GetterSetterCriteria criteria = checkSetterCriteria();
-      if (criteria == GetterSetterCriteria.VIOLATE) {
-        return kmProperty.getName();
-      }
-      if (criteria == GetterSetterCriteria.NOT_AVAILABLE) {
-        if (field != null && IS_VAR.invoke(flags)) {
-          // Editable property without setter.
-          // Even though a setter does not exist, `kotlinc` still set setter flags and use them to
-          // determine when to direct field access v.s. setter calls for property resolution.
-          kmProperty.setSetterFlags(field.accessFlags.getAsKotlinFlags());
-        }
-        return kmProperty.getName();
-      }
-      assert criteria == GetterSetterCriteria.MET;
-      assert setter != null;
-      DexProto proto = setter.method.proto;
-      assert proto.parameters.size() == (isExtension ? 2 : 1)
-          : "checkSetterCriteria: " + this.toString();
-      MethodTypeSignature signature =
-          GenericSignature.Parser.toMethodTypeSignature(setter, appView);
-      // TODO(b/152599446): Update with type parameters for the setter.
-      List<KmTypeParameter> typeParameters =
-          KotlinMetadataSynthesizerUtils.convertFormalTypeParameters(
-              classTypeParameters,
-              ImmutableList.of(),
-              signature.getFormalTypeParameters(),
-              appView.dexItemFactory(),
-              kmTypeParameter -> {});
-      int valueIndex = isExtension ? 1 : 0;
-      DexType valueType = proto.parameters.values[valueIndex];
-      TypeSignature valueSignature = signature.getParameterTypeSignature(valueIndex);
-      KmType kmPropertyType =
-          synthesizer.toRenamedKmType(
-              valueType, valueSignature, setterInfo.returnType, typeParameters);
-      if (kmProperty.getReturnType() == defaultPropertyType) {
-        // The property type is not set yet.
-        kmProperty.setReturnType(kmPropertyType);
-      } else {
-        // If property type is set already make sure it's consistent.
-        if (!KotlinMetadataSynthesizerUtils.hasEqualClassifier(
-            kmPropertyType, kmProperty.getReturnType())) {
-          return null;
-        }
-      }
-      assert setter.getKotlinMemberInfo().isPropertyInfo();
-      KotlinValueParameterInfo valueParameterInfo =
-          setter.getKotlinMemberInfo().asPropertyInfo().valueParameterInfo;
-      KmValueParameter kmValueParameter =
-          synthesizer.toRewrittenKmValueParameter(
-              valueParameterInfo, valueType, valueSignature, "value", typeParameters);
-      if (kmValueParameter != null) {
-        kmProperty.setSetterParameter(kmValueParameter);
-      }
-      DexMethod renamedSetter = lens.lookupMethod(setter.method, appView.dexItemFactory());
-      kmProperty.setSetterFlags(setterInfo.setterFlags);
-      JvmExtensionsKt.setSetterSignature(kmProperty, toJvmMethodSignature(renamedSetter));
-      return appView.appInfo().isPinned(setter.method) ? null : renamedSetter.name.toString();
-    }
-
-    KmProperty toRenamedKmProperty(KotlinMetadataSynthesizer synthesizer) {
-      AppView<AppInfoWithLiveness> appView = synthesizer.appView;
-      NamingLens lens = synthesizer.lens;
-      KmProperty kmProperty = new KmProperty(flags, name, flagsOf(), flagsOf());
-
-      // Set default values
-      KmType defaultPropertyType = new KmType(flagsOf());
-      kmProperty.setReturnType(defaultPropertyType);
-
-      String renamedPropertyName = name;
-      if (getter != null) {
-        if (checkGetterCriteria() == GetterSetterCriteria.VIOLATE) {
-          return null;
-        }
-        if (!setReceiverParameterTypeForExtensionProperty(
-            kmProperty, getter, appView, synthesizer)) {
-          return null;
-        }
-        renamedPropertyName =
-            setGetterForProperty(kmProperty, defaultPropertyType, appView, lens, synthesizer);
-      }
-      if (setter != null) {
-        if (checkSetterCriteria() == GetterSetterCriteria.VIOLATE) {
-          return null;
-        }
-        if (!setReceiverParameterTypeForExtensionProperty(
-            kmProperty, setter, appView, synthesizer)) {
-          return null;
-        }
-        String renamedName =
-            setSetterForProperty(kmProperty, defaultPropertyType, appView, lens, synthesizer);
-        if (renamedPropertyName != null) {
-          renamedPropertyName = renamedName;
-        }
-      }
-      // Setting the property type from the field has to be done after the getter, otherwise we
-      // may potentially loose type-argument information.
-      if (field != null) {
-        String renamedName =
-            setFieldForProperty(kmProperty, defaultPropertyType, appView, lens, synthesizer);
-        if (renamedPropertyName != null) {
-          renamedPropertyName = renamedName;
-        }
-      }
-      // Rename the property name if and only if none of participating members is pinned, and
-      // any of them is indeed renamed (to a new name).
-      if (renamedPropertyName != null) {
-        kmProperty.setName(renamedPropertyName);
-      }
-      return kmProperty;
-    }
-
-    enum GetterSetterCriteria {
-      NOT_AVAILABLE,
-      MET,
-      VIOLATE
-    }
-
-    // Getter should look like:
-    //   1) T getProp(); for regular property, or
-    //   2) T getProp(R); for extension property, where
-    // T is a property type, and R is a receiver type.
-    private GetterSetterCriteria checkGetterCriteria() {
-      if (getter == null) {
-        return GetterSetterCriteria.NOT_AVAILABLE;
-      }
-      // Property type will be checked against a backing field type if any.
-      // And if they're different, we won't synthesize KmProperty for that.
-      // Checking parameter size.
-      if (isExtension) {
-        return getter.method.proto.parameters.size() == 1
-            ? GetterSetterCriteria.MET : GetterSetterCriteria.VIOLATE;
-      } else {
-        return getter.method.proto.parameters.size() == 0
-            ? GetterSetterCriteria.MET : GetterSetterCriteria.VIOLATE;
-      }
-    }
-
-    // Setter should look like:
-    //   1) void setProp(T); for regular property, or
-    //   2) void setProp(R, T); for extension property, where
-    // T is a property type, and R is a receiver type.
-    private GetterSetterCriteria checkSetterCriteria() {
-      if (setter == null) {
-        return GetterSetterCriteria.NOT_AVAILABLE;
-      }
-      if (!setter.method.proto.returnType.isVoidType()) {
-        return GetterSetterCriteria.VIOLATE;
-      }
-      // Property type will be checked against a backing field type if any.
-      // And if they're different, we won't synthesize KmProperty for that.
-      // Plus, receiver type will be checked, too, against a getter.
-      if (isExtension) {
-        return setter.method.proto.parameters.size() == 2
-            ? GetterSetterCriteria.MET : GetterSetterCriteria.VIOLATE;
-      } else {
-        return setter.method.proto.parameters.size() == 1
-            ? GetterSetterCriteria.MET : GetterSetterCriteria.VIOLATE;
-      }
-    }
-
-    @Override
-    public String toString() {
-      StringBuilder builder = new StringBuilder();
-      builder.append("KmPropertyGroup {").append(System.lineSeparator());
-      builder.append("  name: ").append(name).append(System.lineSeparator());
-      builder.append("  isExtension: ").append(isExtension).append(System.lineSeparator());
-      if (field != null) {
-        builder.append("  field: ")
-            .append(field.toSourceString())
-            .append(System.lineSeparator());
-      }
-      if (getter != null) {
-        builder
-            .append("  getter: ")
-            .append(getter.toSourceString())
-            .append(System.lineSeparator());
-      }
-      if (setter != null) {
-        builder
-            .append("  setter: ")
-            .append(setter.toSourceString())
-            .append(System.lineSeparator());
-      }
-      if (annotations != null) {
-        builder
-            .append("  annotations: ")
-            .append(annotations.toSourceString())
-            .append(System.lineSeparator());
-      }
-      builder.append("}");
-      return builder.toString();
-    }
-  }
-}
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataSynthesizerUtils.java b/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataSynthesizerUtils.java
deleted file mode 100644
index e773cef..0000000
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataSynthesizerUtils.java
+++ /dev/null
@@ -1,290 +0,0 @@
-// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-
-package com.android.tools.r8.kotlin;
-
-import static com.android.tools.r8.kotlin.Kotlin.NAME;
-import static com.android.tools.r8.utils.DescriptorUtils.descriptorToKotlinClassifier;
-import static kotlinx.metadata.FlagsKt.flagsOf;
-
-import com.android.tools.r8.graph.DexItemFactory;
-import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.graph.GenericSignature.ArrayTypeSignature;
-import com.android.tools.r8.graph.GenericSignature.ClassTypeSignature;
-import com.android.tools.r8.graph.GenericSignature.FieldTypeSignature;
-import com.android.tools.r8.graph.GenericSignature.FormalTypeParameter;
-import com.android.tools.r8.graph.GenericSignature.TypeVariableSignature;
-import com.android.tools.r8.kotlin.Kotlin.ClassClassifiers;
-import com.google.common.collect.ImmutableList;
-import java.util.List;
-import java.util.function.Consumer;
-import java.util.function.Function;
-import kotlinx.metadata.KmType;
-import kotlinx.metadata.KmTypeParameter;
-import kotlinx.metadata.KmTypeVisitor;
-import kotlinx.metadata.KmVariance;
-
-class KotlinMetadataSynthesizerUtils {
-
-  // The KmVisitorOption is used for star projections, otherwise we will have late-init failures
-  // due to visitStarProjection adding an argument.
-  enum KmVisitorOption {
-    VISIT_NEW,
-    VISIT_PARENT
-  }
-
-  // The AddKotlinAnyType is carrying around information regarding the need for adding the trivial
-  // type Kotlin/Any. The information is not consistently added, for example, for upper bounds
-  // the trivial bound is not recorded.
-  public enum AddKotlinAnyType {
-    ADD,
-    DISREGARD
-  }
-
-  static void populateKmTypeFromSignature(
-      FieldTypeSignature typeSignature,
-      KotlinTypeInfo originalTypeInfo,
-      Function<KmVisitorOption, KmTypeVisitor> typeVisitor,
-      List<KmTypeParameter> allTypeParameters,
-      DexItemFactory factory,
-      AddKotlinAnyType addAny) {
-    if (typeSignature.isClassTypeSignature()) {
-      populateKmTypeFromClassTypeSignature(
-          typeSignature.asClassTypeSignature(),
-          originalTypeInfo,
-          typeVisitor,
-          allTypeParameters,
-          factory,
-          addAny);
-    } else if (typeSignature.isArrayTypeSignature()) {
-      populateKmTypeFromArrayTypeSignature(
-          typeSignature.asArrayTypeSignature(),
-          originalTypeInfo,
-          typeVisitor,
-          allTypeParameters,
-          factory,
-          addAny);
-    } else if (typeSignature.isTypeVariableSignature()) {
-      populateKmTypeFromTypeVariableSignature(
-          typeSignature.asTypeVariableSignature(), typeVisitor, allTypeParameters);
-    } else {
-      assert typeSignature.isStar();
-      typeVisitor.apply(KmVisitorOption.VISIT_PARENT).visitStarProjection();
-    }
-  }
-
-  private static void populateKmTypeFromTypeVariableSignature(
-      TypeVariableSignature typeSignature,
-      Function<KmVisitorOption, KmTypeVisitor> typeVisitor,
-      List<KmTypeParameter> allTypeParameters) {
-    for (KmTypeParameter typeParameter : allTypeParameters) {
-      if (typeParameter
-          .getName()
-          .equals(typeSignature.asTypeVariableSignature().getTypeVariable())) {
-        typeVisitor.apply(KmVisitorOption.VISIT_NEW).visitTypeParameter(typeParameter.getId());
-        return;
-      }
-    }
-  }
-
-  private static void populateKmTypeFromArrayTypeSignature(
-      ArrayTypeSignature typeSignature,
-      KotlinTypeInfo originalTypeInfo,
-      Function<KmVisitorOption, KmTypeVisitor> typeVisitor,
-      List<KmTypeParameter> allTypeParameters,
-      DexItemFactory factory,
-      AddKotlinAnyType addAny) {
-    ArrayTypeSignature arrayTypeSignature = typeSignature.asArrayTypeSignature();
-    if (!arrayTypeSignature.elementSignature().isFieldTypeSignature()) {
-      return;
-    }
-    KmTypeVisitor kmType = typeVisitor.apply(KmVisitorOption.VISIT_NEW);
-    kmType.visitClass(ClassClassifiers.arrayBinaryName);
-    KotlinTypeProjectionInfo projectionInfo =
-        originalTypeInfo == null ? null : originalTypeInfo.getArgumentOrNull(0);
-    populateKmTypeFromSignature(
-        arrayTypeSignature.elementSignature().asFieldTypeSignature(),
-        projectionInfo == null ? null : projectionInfo.typeInfo,
-        (kmVisitorOption) -> {
-          if (kmVisitorOption == KmVisitorOption.VISIT_PARENT) {
-            assert originalTypeInfo.getArguments().size() == 1
-                && originalTypeInfo.getArguments().get(0).isStarProjection();
-            return kmType;
-          } else {
-            // TODO(b/152886451): Variance is only NULL when star projection. If that is the case
-            //  we should just apply starProjection.
-            return kmType.visitArgument(
-                flagsOf(),
-                projectionInfo == null || projectionInfo.variance == null
-                    ? KmVariance.INVARIANT
-                    : projectionInfo.variance);
-          }
-        },
-        allTypeParameters,
-        factory,
-        addAny);
-  }
-
-  private static void populateKmTypeFromClassTypeSignature(
-      ClassTypeSignature typeSignature,
-      KotlinTypeInfo originalTypeInfo,
-      Function<KmVisitorOption, KmTypeVisitor> typeVisitor,
-      List<KmTypeParameter> allTypeParameters,
-      DexItemFactory factory,
-      AddKotlinAnyType addAny) {
-    // No need to record the trivial argument.
-    if (addAny == AddKotlinAnyType.DISREGARD && factory.objectType == typeSignature.type()) {
-      return;
-    }
-    KmTypeVisitor kmType = typeVisitor.apply(KmVisitorOption.VISIT_NEW);
-    kmType.visitClass(toClassifier(typeSignature.type(), factory));
-    for (int i = 0; i < typeSignature.typeArguments().size(); i++) {
-      FieldTypeSignature typeArgument = typeSignature.typeArguments().get(i);
-      KotlinTypeProjectionInfo projectionInfo =
-          originalTypeInfo == null ? null : originalTypeInfo.getArgumentOrNull(i);
-      populateKmTypeFromSignature(
-          typeArgument,
-          projectionInfo == null ? null : projectionInfo.typeInfo,
-          (kmVisitorOption) -> {
-            if (kmVisitorOption == KmVisitorOption.VISIT_PARENT) {
-              assert projectionInfo == null || projectionInfo.isStarProjection();
-              return kmType;
-            } else {
-              // TODO(b/152886451): Variance is only NULL when star projection. If that is the case
-              //  we should just apply starProjection.
-              return kmType.visitArgument(
-                  flagsOf(),
-                  projectionInfo == null || projectionInfo.variance == null
-                      ? KmVariance.INVARIANT
-                      : projectionInfo.variance);
-            }
-          },
-          allTypeParameters,
-          factory,
-          addAny);
-    }
-  }
-
-  static String toClassifier(DexType type, DexItemFactory factory) {
-    // E.g., V -> kotlin/Unit, J -> kotlin/Long, [J -> kotlin/LongArray
-    if (factory.kotlin.knownTypeConversion.containsKey(type)) {
-      DexType convertedType = factory.kotlin.knownTypeConversion.get(type);
-      assert convertedType != null;
-      return descriptorToKotlinClassifier(convertedType.toDescriptorString());
-    }
-    // E.g., [Ljava/lang/String; -> kotlin/Array
-    if (type.isArrayType()) {
-      return NAME + "/Array";
-    }
-    return descriptorToKotlinClassifier(type.toDescriptorString());
-  }
-
-  /**
-   * Utility method building up all type-parameters from {@code classTypeParameters} combined with
-   * {@code parameters}, where the consumer {@code addedFromParameters} is called for every
-   * conversion of {@Code FormalTypeParameter} to {@Code KmTypeParameter}.
-   *
-   * <pre>
-   *  classTypeParameters: [KmTypeParameter(T), KmTypeParameter(S)]
-   *  parameters: [FormalTypeParameter(R)]
-   *  result: [KmTypeParameter(T), KmTypeParameter(S), KmTypeParameter(R)].
-   * </pre>
-   *
-   * @param classTypeParameters
-   * @param originalTypeParameterInfo
-   * @param parameters
-   * @param factory
-   * @param addedFromParameters
-   * @return
-   */
-  static List<KmTypeParameter> convertFormalTypeParameters(
-      List<KmTypeParameter> classTypeParameters,
-      List<KotlinTypeParameterInfo> originalTypeParameterInfo,
-      List<FormalTypeParameter> parameters,
-      DexItemFactory factory,
-      Consumer<KmTypeParameter> addedFromParameters) {
-    if (parameters.isEmpty()) {
-      return classTypeParameters;
-    }
-    ImmutableList.Builder<KmTypeParameter> builder = ImmutableList.builder();
-    builder.addAll(classTypeParameters);
-    // Assign type-variables ids to names. All generic signatures has been minified at this point,
-    // but it may be that type-variables are used before we can see them (is that allowed?).
-    for (int i = 0; i < parameters.size(); i++) {
-      FormalTypeParameter parameter = parameters.get(i);
-      int flags =
-          originalTypeParameterInfo.size() > i
-              ? originalTypeParameterInfo.get(i).getFlags()
-              : flagsOf();
-      KmVariance variance =
-          originalTypeParameterInfo.size() > i
-              ? originalTypeParameterInfo.get(i).getVariance()
-              : KmVariance.INVARIANT;
-      KmTypeParameter element =
-          new KmTypeParameter(
-              flags,
-              parameter.getName(),
-              getNewId(originalTypeParameterInfo, parameter.getName(), i),
-              variance);
-      builder.add(element);
-      addedFromParameters.accept(element);
-    }
-    ImmutableList<KmTypeParameter> allTypeParameters = builder.build();
-    for (int i = 0; i < parameters.size(); i++) {
-      FormalTypeParameter parameter = parameters.get(i);
-      KmTypeParameter kmTypeParameter = allTypeParameters.get(classTypeParameters.size() + i);
-      visitUpperBound(parameter.getClassBound(), allTypeParameters, kmTypeParameter, factory);
-      if (parameter.getInterfaceBounds() != null) {
-        for (FieldTypeSignature interfaceBound : parameter.getInterfaceBounds()) {
-          visitUpperBound(interfaceBound, allTypeParameters, kmTypeParameter, factory);
-        }
-      }
-    }
-    return allTypeParameters;
-  }
-
-  // Tries to pick the id from the type-parameter name. If no such exist, compute the highest id and
-  // add the index of the current argument (to ensure unique naming in sequence).
-  private static int getNewId(
-      List<KotlinTypeParameterInfo> typeParameterInfos, String typeVariable, int currentId) {
-    int maxId = -1;
-    for (KotlinTypeParameterInfo typeParameterInfo : typeParameterInfos) {
-      if (typeParameterInfo.getName().equals(typeVariable)) {
-        return typeParameterInfo.getId();
-      }
-      maxId = Math.max(maxId, typeParameterInfo.getId());
-    }
-    return maxId + 1 + currentId;
-  }
-
-  private static void visitUpperBound(
-      FieldTypeSignature typeSignature,
-      List<KmTypeParameter> allTypeParameters,
-      KmTypeParameter parameter,
-      DexItemFactory factory) {
-    if (typeSignature.isUnknown()) {
-      return;
-    }
-    populateKmTypeFromSignature(
-        typeSignature,
-        null,
-        (kmVisitorOption) -> {
-          assert kmVisitorOption == KmVisitorOption.VISIT_NEW;
-          return parameter.visitUpperBound(flagsOf());
-        },
-        allTypeParameters,
-        factory,
-        AddKotlinAnyType.DISREGARD);
-  }
-
-  public static boolean hasEqualClassifier(KmType one, KmType other) {
-    if (one == null && other == null) {
-      return true;
-    }
-    if (one == null || other == null) {
-      return false;
-    }
-    return KotlinClassifierInfo.equals(one.classifier, other.classifier);
-  }
-}
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataUtils.java b/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataUtils.java
new file mode 100644
index 0000000..277201c
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataUtils.java
@@ -0,0 +1,135 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.kotlin;
+
+import com.android.tools.r8.errors.InvalidDescriptorException;
+import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexField;
+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.naming.NamingLens;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.utils.DescriptorUtils;
+import kotlinx.metadata.KmExtensionType;
+import kotlinx.metadata.KmProperty;
+import kotlinx.metadata.KmPropertyExtensionVisitor;
+import kotlinx.metadata.KmPropertyVisitor;
+import kotlinx.metadata.jvm.JvmFieldSignature;
+import kotlinx.metadata.jvm.JvmMethodSignature;
+import kotlinx.metadata.jvm.JvmPropertyExtensionVisitor;
+import kotlinx.metadata.jvm.KotlinClassHeader;
+
+public class KotlinMetadataUtils {
+
+  public static final NoKotlinInfo NO_KOTLIN_INFO = new NoKotlinInfo();
+
+  private static class NoKotlinInfo
+      implements KotlinClassLevelInfo, KotlinFieldLevelInfo, KotlinMethodLevelInfo {
+
+    @Override
+    public KotlinClassHeader rewrite(
+        DexClass clazz, AppView<AppInfoWithLiveness> appView, NamingLens namingLens) {
+      throw new Unreachable("Should never be called");
+    }
+  }
+
+  static JvmFieldSignature toJvmFieldSignature(DexField field) {
+    return new JvmFieldSignature(field.name.toString(), field.type.toDescriptorString());
+  }
+
+  static JvmMethodSignature toJvmMethodSignature(DexMethod method) {
+    StringBuilder descBuilder = new StringBuilder();
+    descBuilder.append("(");
+    for (DexType argType : method.proto.parameters.values) {
+      descBuilder.append(argType.toDescriptorString());
+    }
+    descBuilder.append(")");
+    descBuilder.append(method.proto.returnType.toDescriptorString());
+    return new JvmMethodSignature(method.name.toString(), descBuilder.toString());
+  }
+
+  static class KmPropertyProcessor {
+    private JvmFieldSignature fieldSignature = null;
+    // Custom getter via @get:JvmName("..."). Otherwise, null.
+    private JvmMethodSignature getterSignature = null;
+    // Custom getter via @set:JvmName("..."). Otherwise, null.
+    private JvmMethodSignature setterSignature = null;
+
+    KmPropertyProcessor(KmProperty kmProperty) {
+      kmProperty.accept(
+          new KmPropertyVisitor() {
+            @Override
+            public KmPropertyExtensionVisitor visitExtensions(KmExtensionType type) {
+              if (type != JvmPropertyExtensionVisitor.TYPE) {
+                return null;
+              }
+              return new JvmPropertyExtensionVisitor() {
+                @Override
+                public void visit(
+                    int flags,
+                    JvmFieldSignature fieldDesc,
+                    JvmMethodSignature getterDesc,
+                    JvmMethodSignature setterDesc) {
+                  assert fieldSignature == null : fieldSignature.asString();
+                  fieldSignature = fieldDesc;
+                  assert getterSignature == null : getterSignature.asString();
+                  getterSignature = getterDesc;
+                  assert setterSignature == null : setterSignature.asString();
+                  setterSignature = setterDesc;
+                }
+              };
+            }
+          });
+    }
+
+    JvmFieldSignature fieldSignature() {
+      return fieldSignature;
+    }
+
+    JvmMethodSignature getterSignature() {
+      return getterSignature;
+    }
+
+    JvmMethodSignature setterSignature() {
+      return setterSignature;
+    }
+  }
+
+  static boolean isValidMethodDescriptor(String methodDescriptor) {
+    try {
+      String[] argDescriptors = DescriptorUtils.getArgumentTypeDescriptors(methodDescriptor);
+      for (String argDescriptor : argDescriptors) {
+        if (argDescriptor.charAt(0) == 'L' && !DescriptorUtils.isClassDescriptor(argDescriptor)) {
+          return false;
+        }
+      }
+      return true;
+    } catch (InvalidDescriptorException e) {
+      return false;
+    }
+  }
+
+  static String toRenamedDescriptorOrDefault(
+      DexType type,
+      AppView<AppInfoWithLiveness> appView,
+      NamingLens namingLens,
+      String defaultValue) {
+    if (appView.appInfo().wasPruned(type)) {
+      return defaultValue;
+    }
+    DexString descriptor = namingLens.lookupDescriptor(type);
+    if (descriptor != null) {
+      return descriptor.toString();
+    }
+    return defaultValue;
+  }
+
+  static String kotlinNameFromDescriptor(DexString descriptor) {
+    return DescriptorUtils.getBinaryNameFromDescriptor(descriptor.toString());
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinInfo.java b/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataWriter.java
similarity index 66%
rename from src/main/java/com/android/tools/r8/kotlin/KotlinInfo.java
rename to src/main/java/com/android/tools/r8/kotlin/KotlinMetadataWriter.java
index ef1d2b6..33a0aa6 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinInfo.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataWriter.java
@@ -1,42 +1,30 @@
-// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
 package com.android.tools.r8.kotlin;
 
 import static com.android.tools.r8.utils.StringUtils.LINE_SEPARATOR;
-import static kotlinx.metadata.Flag.Class.IS_COMPANION_OBJECT;
 
 import com.android.tools.r8.errors.Unreachable;
-import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.DexClass;
-import com.android.tools.r8.graph.DexEncodedField;
-import com.android.tools.r8.graph.DexEncodedMethod;
-import com.android.tools.r8.graph.SubtypingInfo;
-import com.android.tools.r8.kotlin.KotlinMemberInfo.KotlinFieldInfo;
-import com.android.tools.r8.kotlin.KotlinMemberInfo.KotlinPropertyInfo;
-import com.android.tools.r8.kotlin.KotlinMetadataSynthesizer.KmPropertyGroup;
-import com.android.tools.r8.naming.NamingLens;
-import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.graph.DexAnnotation;
 import com.android.tools.r8.utils.Action;
-import com.android.tools.r8.utils.Reporter;
 import com.android.tools.r8.utils.StringUtils;
-import com.google.common.collect.ImmutableList;
+import java.io.PrintStream;
 import java.util.Comparator;
-import java.util.HashMap;
-import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
 import java.util.function.BiConsumer;
 import java.util.function.Consumer;
-import java.util.function.Predicate;
 import java.util.stream.Collectors;
+import kotlinx.metadata.InconsistentKotlinMetadataException;
 import kotlinx.metadata.KmAnnotation;
 import kotlinx.metadata.KmAnnotationArgument;
 import kotlinx.metadata.KmClass;
 import kotlinx.metadata.KmConstructor;
 import kotlinx.metadata.KmDeclarationContainer;
 import kotlinx.metadata.KmFunction;
+import kotlinx.metadata.KmLambda;
 import kotlinx.metadata.KmPackage;
 import kotlinx.metadata.KmProperty;
 import kotlinx.metadata.KmType;
@@ -47,243 +35,121 @@
 import kotlinx.metadata.jvm.JvmExtensionsKt;
 import kotlinx.metadata.jvm.JvmFieldSignature;
 import kotlinx.metadata.jvm.JvmMethodSignature;
-import kotlinx.metadata.jvm.KotlinClassHeader;
 import kotlinx.metadata.jvm.KotlinClassMetadata;
 
-// Provides access to package/class-level Kotlin information.
-public abstract class KotlinInfo<MetadataKind extends KotlinClassMetadata> {
-
-  final DexClass clazz;
-  private static final List<KmTypeParameter> EMPTY_TYPE_PARAMS = ImmutableList.of();
-
-  KotlinInfo(MetadataKind metadata, DexClass clazz) {
-    assert clazz != null;
-    this.clazz = clazz;
-    processMetadata(metadata);
-  }
-
-  // Subtypes will define how to process the given metadata.
-  abstract void processMetadata(MetadataKind metadata);
-
-  // Subtypes will define how to rewrite metadata after shrinking and minification.
-  // Subtypes that represent subtypes of {@link KmDeclarationContainer} can use
-  // {@link #rewriteDeclarationContainer} below.
-  abstract void rewrite(
-      AppView<AppInfoWithLiveness> appView, SubtypingInfo subtypingInfo, NamingLens lens);
-
-  abstract KotlinClassHeader createHeader();
-
-  public final List<KmTypeParameter> getTypeParameters() {
-    if (!this.isClass()) {
-      return EMPTY_TYPE_PARAMS;
-    }
-    return this.asClass().kmClass.getTypeParameters();
-  }
-
-  public enum Kind {
-    Class, File, Synthetic, Part, Facade
-  }
-
-  public abstract Kind getKind();
-
-  public boolean isClass() {
-    return false;
-  }
-
-  public KotlinClass asClass() {
-    return null;
-  }
-
-  public boolean isFile() {
-    return false;
-  }
-
-  public KotlinFile asFile() {
-    return null;
-  }
-
-  public boolean isSyntheticClass() {
-    return false;
-  }
-
-  public KotlinSyntheticClass asSyntheticClass() {
-    return null;
-  }
-
-  public boolean isClassPart() {
-    return false;
-  }
-
-  public KotlinClassPart asClassPart() {
-    return null;
-  }
-
-  public boolean isClassFacade() {
-    return false;
-  }
-
-  public KotlinClassFacade asClassFacade() {
-    return null;
-  }
-
-  boolean hasDeclarations() {
-    return isClass() || isFile() || isClassPart();
-  }
-
-  KmDeclarationContainer getDeclarations() {
-    if (isClass()) {
-      return asClass().kmClass;
-    } else if (isFile()) {
-      return asFile().kmPackage;
-    } else if (isClassPart()) {
-      return asClassPart().kmPackage;
-    } else {
-      throw new Unreachable("Unexpected KotlinInfo: " + this);
-    }
-  }
-
-  // {@link KmClass} and {@link KmPackage} are inherited from {@link KmDeclarationContainer} that
-  // abstract functions and properties. Rewriting of those portions can be unified here.
-  void rewriteDeclarationContainer(KotlinMetadataSynthesizer synthesizer) {
-    assert clazz != null;
-
-    KmDeclarationContainer kmDeclarationContainer = getDeclarations();
-    rewriteFunctions(synthesizer, kmDeclarationContainer.getFunctions());
-    rewriteProperties(synthesizer, kmDeclarationContainer.getProperties());
-    rewriteTypeAliases(synthesizer, kmDeclarationContainer.getTypeAliases());
-  }
-
-  private void rewriteFunctions(KotlinMetadataSynthesizer synthesizer, List<KmFunction> functions) {
-    functions.clear();
-    for (DexEncodedMethod method : clazz.methods()) {
-      if (method.isInitializer()) {
-        continue;
-      }
-      if (method.isKotlinFunction() || method.isKotlinExtensionFunction()) {
-        KmFunction function = synthesizer.toRenamedKmFunction(method);
-        if (function != null) {
-          functions.add(function);
-        }
-      }
-      // TODO(b/151194869): What should we do for methods that fall into this category---no mark?
-    }
-  }
-
-  private void rewriteTypeAliases(
-      KotlinMetadataSynthesizer synthesizer, List<KmTypeAlias> typeAliases) {
-    Iterator<KmTypeAlias> iterator = typeAliases.iterator();
-    while (iterator.hasNext()) {
-      KmTypeAlias typeAlias = iterator.next();
-      KotlinTypeInfo expandedRenamed =
-          KotlinTypeInfo.create(typeAlias.expandedType).toRenamed(synthesizer);
-      if (expandedRenamed == null) {
-        // If the expanded type is pruned, the type-alias is also removed. Type-aliases can refer to
-        // other type-aliases in the underlying type, however, we only remove a type-alias when the
-        // expanded type is removed making it impossible to construct any type that references the
-        // type-alias anyway.
-        // TODO(b/151719926): Add a test for the above.
-        iterator.remove();
-        continue;
-      }
-      typeAlias.setExpandedType(expandedRenamed.asKmType());
-      // Modify the underlying type (right-hand side) of the type-alias.
-      KotlinTypeInfo underlyingRenamed =
-          KotlinTypeInfo.create(typeAlias.underlyingType).toRenamed(synthesizer);
-      if (underlyingRenamed == null) {
-        Reporter reporter = synthesizer.appView.options().reporter;
-        reporter.warning(
-            KotlinMetadataDiagnostic.messageInvalidUnderlyingType(clazz, typeAlias.getName()));
-        iterator.remove();
-        continue;
-      }
-      typeAlias.setUnderlyingType(underlyingRenamed.asKmType());
-    }
-  }
-
-  private void rewriteProperties(
-      KotlinMetadataSynthesizer synthesizer, List<KmProperty> properties) {
-    Map<String, KmPropertyGroup.Builder> propertyGroupBuilderMap = new HashMap<>();
-    // Backing fields for a companion object are declared in its host class.
-    Iterable<DexEncodedField> fields = clazz.fields();
-    Predicate<DexEncodedField> backingFieldTester = DexEncodedField::isKotlinBackingField;
-    List<KmTypeParameter> classTypeParameters = getTypeParameters();
-    if (isClass()) {
-      KotlinClass ktClass = asClass();
-      if (IS_COMPANION_OBJECT.invoke(ktClass.kmClass.getFlags()) && ktClass.hostClass != null) {
-        fields = ktClass.hostClass.fields();
-        backingFieldTester = DexEncodedField::isKotlinBackingFieldForCompanionObject;
-      }
-    }
-    for (DexEncodedField field : fields) {
-      if (backingFieldTester.test(field)) {
-        KotlinFieldInfo kotlinFieldInfo = field.getKotlinMemberInfo().asFieldInfo();
-        assert kotlinFieldInfo != null;
-        String name = kotlinFieldInfo.propertyName;
-        assert name != null;
-        KmPropertyGroup.Builder builder =
-            propertyGroupBuilderMap.computeIfAbsent(
-                name,
-                k -> KmPropertyGroup.builder(kotlinFieldInfo.flags, name, classTypeParameters));
-        builder.foundBackingField(field);
-      }
-    }
-    for (DexEncodedMethod method : clazz.methods()) {
-      if (method.isInitializer()) {
-        continue;
-      }
-      if (method.isKotlinProperty() || method.isKotlinExtensionProperty()) {
-        assert method.getKotlinMemberInfo().isPropertyInfo();
-        KotlinPropertyInfo kotlinPropertyInfo = method.getKotlinMemberInfo().asPropertyInfo();
-        String name = kotlinPropertyInfo.propertyName;
-        assert name != null;
-        KmPropertyGroup.Builder builder =
-            propertyGroupBuilderMap.computeIfAbsent(
-                name,
-                // Hitting here (creating a property builder) after visiting all fields means that
-                // this property doesn't have a backing field. Don't use members' flags.
-                k -> KmPropertyGroup.builder(kotlinPropertyInfo.flags, name, classTypeParameters));
-        switch (kotlinPropertyInfo.memberKind) {
-          case EXTENSION_PROPERTY_GETTER:
-            builder.isExtensionGetter();
-            // fallthrough;
-          case PROPERTY_GETTER:
-            builder.foundGetter(method, kotlinPropertyInfo);
-            break;
-          case EXTENSION_PROPERTY_SETTER:
-            builder.isExtensionSetter();
-            // fallthrough;
-          case PROPERTY_SETTER:
-            builder.foundSetter(method, kotlinPropertyInfo);
-            break;
-          case EXTENSION_PROPERTY_ANNOTATIONS:
-            builder.isExtensionAnnotations();
-            // fallthrough;
-          case PROPERTY_ANNOTATIONS:
-            builder.foundAnnotations(method);
-            break;
-          default:
-            throw new Unreachable("Not a Kotlin property: " + method.getKotlinMemberInfo());
-        }
-      }
-      // TODO(b/151194869): What should we do for methods that fall into this category---no mark?
-    }
-    properties.clear();
-    for (KmPropertyGroup.Builder builder : propertyGroupBuilderMap.values()) {
-      KmPropertyGroup group = builder.build();
-      if (group == null) {
-        continue;
-      }
-      KmProperty property = group.toRenamedKmProperty(synthesizer);
-      if (property != null) {
-        properties.add(property);
-      }
-    }
-  }
-
-  public abstract String toString(String indent);
+public class KotlinMetadataWriter {
 
   static final String INDENT = "  ";
 
+  public static void writeKotlinMetadataAnnotation(
+      String prefix, DexAnnotation annotation, PrintStream ps, Kotlin kotlin) {
+    assert annotation.annotation.type == kotlin.metadata.kotlinMetadataType;
+    try {
+      KotlinClassMetadata kMetadata =
+          KotlinClassMetadataReader.toKotlinClassMetadata(kotlin, annotation.annotation);
+      ps.println(kotlinMetadataToString(prefix, kMetadata));
+    } catch (Throwable ignored) {
+    }
+  }
+
+  public static String kotlinMetadataToString(String prefix, KotlinClassMetadata kMetadata) {
+    if (kMetadata instanceof KotlinClassMetadata.Class) {
+      return kotlinClassMetadataToString((KotlinClassMetadata.Class) kMetadata, prefix);
+    } else if (kMetadata instanceof KotlinClassMetadata.FileFacade) {
+      // e.g., B.kt becomes class `BKt`
+      return kotlinFileFacadeMetadataToString((KotlinClassMetadata.FileFacade) kMetadata, prefix);
+    } else if (kMetadata instanceof KotlinClassMetadata.MultiFileClassFacade) {
+      // multi-file class with the same @JvmName.
+      return kotlinMultiFileClassFacadeMetadataString(
+          (KotlinClassMetadata.MultiFileClassFacade) kMetadata, prefix);
+    } else if (kMetadata instanceof KotlinClassMetadata.MultiFileClassPart) {
+      // A single file, which is part of multi-file class.
+      return kotlinMultiFileClassPartToString(
+          (KotlinClassMetadata.MultiFileClassPart) kMetadata, prefix);
+    } else if (kMetadata instanceof KotlinClassMetadata.SyntheticClass) {
+      return kotlinSyntheticClassToString((KotlinClassMetadata.SyntheticClass) kMetadata, prefix);
+    } else {
+      throw new Unreachable("An error would be thrown before in createKotlinInfo");
+    }
+  }
+
+  private static String kotlinClassMetadataToString(
+      KotlinClassMetadata.Class kMetadata, String indent) {
+    StringBuilder sb = new StringBuilder(indent);
+    KotlinMetadataWriter.appendKmSection(
+        indent,
+        "Metadata.Class",
+        sb,
+        newIndent -> {
+          KotlinMetadataWriter.appendKmClass(newIndent, sb, kMetadata.toKmClass());
+        });
+    return sb.toString();
+  }
+
+  private static String kotlinFileFacadeMetadataToString(
+      KotlinClassMetadata.FileFacade kMetadata, String indent) {
+    StringBuilder sb = new StringBuilder(indent);
+    KotlinMetadataWriter.appendKmSection(
+        indent,
+        "Metadata.FileFacade",
+        sb,
+        newIndent -> {
+          KotlinMetadataWriter.appendKmPackage(newIndent, sb, kMetadata.toKmPackage());
+        });
+    return sb.toString();
+  }
+
+  private static String kotlinMultiFileClassFacadeMetadataString(
+      KotlinClassMetadata.MultiFileClassFacade kMetadata, String indent) {
+    return indent
+        + "MetaData.MultiFileClassFacade("
+        + StringUtils.join(kMetadata.getPartClassNames(), ", ")
+        + ")";
+  }
+
+  private static String kotlinMultiFileClassPartToString(
+      KotlinClassMetadata.MultiFileClassPart kMetadata, String indent) {
+    StringBuilder sb = new StringBuilder(indent);
+    KotlinMetadataWriter.appendKmSection(
+        indent,
+        "Metadata.MultiFileClassPart",
+        sb,
+        newIndent -> {
+          KotlinMetadataWriter.appendKeyValue(
+              newIndent, "facadeClassName", sb, kMetadata.getFacadeClassName());
+          KotlinMetadataWriter.appendKmPackage(newIndent, sb, kMetadata.toKmPackage());
+        });
+    return sb.toString();
+  }
+
+  private static String kotlinSyntheticClassToString(
+      KotlinClassMetadata.SyntheticClass kMetadata, String indent) {
+    StringBuilder sb = new StringBuilder();
+    KotlinMetadataWriter.appendKmSection(
+        indent,
+        "Metadata.SyntheticClass",
+        sb,
+        newIndent -> {
+          try {
+            KmLambda kmLambda = kMetadata.toKmLambda();
+            if (kmLambda != null) {
+              KotlinMetadataWriter.appendKeyValue(
+                  newIndent,
+                  "function",
+                  sb,
+                  nextIndent -> {
+                    KotlinMetadataWriter.appendKmFunction(nextIndent, sb, kmLambda.function);
+                  });
+            } else {
+              KotlinMetadataWriter.appendKeyValue(newIndent, "function", sb, "null");
+            }
+          } catch (InconsistentKotlinMetadataException ex) {
+            appendKeyValue(newIndent, "function", sb, ex.getMessage());
+          }
+        });
+    return sb.toString();
+  }
+
   private static <T> void appendKmHelper(
       String key, StringBuilder sb, Action appendContent, String start, String end) {
     sb.append(key);
@@ -292,7 +158,7 @@
     sb.append(end);
   }
 
-  static <T> void appendKmSection(
+  public static <T> void appendKmSection(
       String indent, String typeDescription, StringBuilder sb, Consumer<String> appendContent) {
     appendKmHelper(
         typeDescription,
@@ -302,7 +168,7 @@
         indent + "}");
   }
 
-  static <T> void appendKmList(
+  private static <T> void appendKmList(
       String indent,
       String typeDescription,
       StringBuilder sb,
@@ -326,18 +192,18 @@
         indent + "]");
   }
 
-  static void appendKeyValue(
+  private static void appendKeyValue(
       String indent, String key, StringBuilder sb, Consumer<String> appendValue) {
     sb.append(indent);
     appendKmHelper(key, sb, () -> appendValue.accept(indent), ": ", "," + LINE_SEPARATOR);
   }
 
-  static void appendKeyValue(String indent, String key, StringBuilder sb, String value) {
+  public static void appendKeyValue(String indent, String key, StringBuilder sb, String value) {
     sb.append(indent);
     appendKmHelper(key, sb, () -> sb.append(value), ": ", "," + LINE_SEPARATOR);
   }
 
-  static void appendKmDeclarationContainer(
+  private static void appendKmDeclarationContainer(
       String indent, StringBuilder sb, KmDeclarationContainer container) {
     appendKeyValue(
         indent,
@@ -349,7 +215,9 @@
               "KmFunction",
               sb,
               container.getFunctions().stream()
-                  .sorted(Comparator.comparing(KmFunction::getName))
+                  .sorted(
+                      Comparator.comparing(
+                          kmFunction -> JvmExtensionsKt.getSignature(kmFunction).asString()))
                   .collect(Collectors.toList()),
               (nextIndent, kmFunction) -> {
                 appendKmFunction(nextIndent, sb, kmFunction);
@@ -365,7 +233,25 @@
               "KmProperty",
               sb,
               container.getProperties().stream()
-                  .sorted(Comparator.comparing(KmProperty::getName))
+                  .sorted(
+                      Comparator.comparing(
+                          kmProperty -> {
+                            JvmMethodSignature signature =
+                                JvmExtensionsKt.getGetterSignature(kmProperty);
+                            if (signature != null) {
+                              return signature.asString();
+                            }
+                            signature = JvmExtensionsKt.getSetterSignature(kmProperty);
+                            if (signature != null) {
+                              return signature.asString();
+                            }
+                            JvmFieldSignature fieldSignature =
+                                JvmExtensionsKt.getFieldSignature(kmProperty);
+                            if (fieldSignature != null) {
+                              return fieldSignature.asString();
+                            }
+                            return kmProperty.getName();
+                          }))
                   .collect(Collectors.toList()),
               (nextIndent, kmProperty) -> {
                 appendKmProperty(nextIndent, sb, kmProperty);
@@ -389,7 +275,7 @@
         });
   }
 
-  static void appendKmPackage(String indent, StringBuilder sb, KmPackage kmPackage) {
+  public static void appendKmPackage(String indent, StringBuilder sb, KmPackage kmPackage) {
     appendKmDeclarationContainer(indent, sb, kmPackage);
     appendKeyValue(indent, "moduleName", sb, JvmExtensionsKt.getModuleName(kmPackage));
     appendKeyValue(
@@ -408,7 +294,7 @@
         });
   }
 
-  static void appendKmClass(String indent, StringBuilder sb, KmClass kmClass) {
+  public static void appendKmClass(String indent, StringBuilder sb, KmClass kmClass) {
     appendKeyValue(indent, "flags", sb, kmClass.getFlags() + "");
     appendKeyValue(indent, "name", sb, kmClass.getName());
     appendKeyValue(
@@ -473,7 +359,11 @@
               newIndent,
               "KmConstructor",
               sb,
-              kmClass.getConstructors(),
+              kmClass.getConstructors().stream()
+                  .sorted(
+                      Comparator.comparing(
+                          kmConstructor -> JvmExtensionsKt.getSignature(kmConstructor).asString()))
+                  .collect(Collectors.toList()),
               (nextIndent, constructor) -> {
                 appendKmConstructor(nextIndent, sb, constructor);
               });
@@ -501,7 +391,7 @@
         });
   }
 
-  private static void appendKmFunction(String indent, StringBuilder sb, KmFunction function) {
+  public static void appendKmFunction(String indent, StringBuilder sb, KmFunction function) {
     appendKmSection(
         indent,
         "KmFunction",
@@ -835,9 +725,4 @@
               });
         });
   }
-
-  @Override
-  public String toString() {
-    return toString("");
-  }
 }
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinMethodLevelInfo.java b/src/main/java/com/android/tools/r8/kotlin/KotlinMethodLevelInfo.java
new file mode 100644
index 0000000..604374e
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinMethodLevelInfo.java
@@ -0,0 +1,32 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.kotlin;
+
+public interface KotlinMethodLevelInfo {
+
+  default boolean isConstructor() {
+    return false;
+  }
+
+  default KotlinConstructorInfo asConstructor() {
+    return null;
+  }
+
+  default boolean isFunction() {
+    return false;
+  }
+
+  default KotlinFunctionInfo asFunction() {
+    return null;
+  }
+
+  default boolean isProperty() {
+    return false;
+  }
+
+  default KotlinPropertyInfo asProperty() {
+    return null;
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinMultiFileClassFacadeInfo.java b/src/main/java/com/android/tools/r8/kotlin/KotlinMultiFileClassFacadeInfo.java
new file mode 100644
index 0000000..02efea2
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinMultiFileClassFacadeInfo.java
@@ -0,0 +1,66 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.kotlin;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexString;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.naming.NamingLens;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.utils.DescriptorUtils;
+import com.google.common.collect.ImmutableList;
+import java.util.ArrayList;
+import java.util.List;
+import kotlinx.metadata.jvm.KotlinClassHeader;
+import kotlinx.metadata.jvm.KotlinClassMetadata;
+import kotlinx.metadata.jvm.KotlinClassMetadata.MultiFileClassFacade;
+
+// Holds information about Metadata.MultiFileClassFace
+public class KotlinMultiFileClassFacadeInfo implements KotlinClassLevelInfo {
+
+  private final List<DexType> partClassNames;
+
+  private KotlinMultiFileClassFacadeInfo(List<DexType> partClassNames) {
+    this.partClassNames = partClassNames;
+  }
+
+  static KotlinMultiFileClassFacadeInfo create(
+      MultiFileClassFacade kmMultiFileClassFacade, AppView<?> appView) {
+    ImmutableList.Builder<DexType> builder = ImmutableList.builder();
+    for (String partClassName : kmMultiFileClassFacade.getPartClassNames()) {
+      String descriptor = DescriptorUtils.getDescriptorFromClassBinaryName(partClassName);
+      DexType type = appView.dexItemFactory().createType(descriptor);
+      builder.add(type);
+    }
+    return new KotlinMultiFileClassFacadeInfo(builder.build());
+  }
+
+  @Override
+  public boolean isMultiFileFacade() {
+    return true;
+  }
+
+  @Override
+  public KotlinMultiFileClassFacadeInfo asMultiFileFacade() {
+    return this;
+  }
+
+  @Override
+  public KotlinClassHeader rewrite(
+      DexClass clazz, AppView<AppInfoWithLiveness> appView, NamingLens namingLens) {
+    KotlinClassMetadata.MultiFileClassFacade.Writer writer =
+        new KotlinClassMetadata.MultiFileClassFacade.Writer();
+    List<String> partClassNameStrings = new ArrayList<>(partClassNames.size());
+    for (DexType partClassName : partClassNames) {
+      if (appView.definitionFor(partClassName) != null) {
+        DexString descriptor = namingLens.lookupDescriptor(partClassName);
+        String classifier = DescriptorUtils.descriptorToKotlinClassifier(descriptor.toString());
+        partClassNameStrings.add(classifier);
+      }
+    }
+    return writer.write(partClassNameStrings).getHeader();
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinMultiFileClassPartInfo.java b/src/main/java/com/android/tools/r8/kotlin/KotlinMultiFileClassPartInfo.java
new file mode 100644
index 0000000..69ea8e4
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinMultiFileClassPartInfo.java
@@ -0,0 +1,54 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.kotlin;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.naming.NamingLens;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import kotlinx.metadata.KmPackage;
+import kotlinx.metadata.jvm.KotlinClassHeader;
+import kotlinx.metadata.jvm.KotlinClassMetadata;
+import kotlinx.metadata.jvm.KotlinClassMetadata.MultiFileClassPart;
+
+// Holds information about Metadata.MultiFileClassPartInfo
+public class KotlinMultiFileClassPartInfo implements KotlinClassLevelInfo {
+
+  private final String facadeClassName;
+  private final KotlinPackageInfo packageInfo;
+
+  private KotlinMultiFileClassPartInfo(String facadeClassName, KotlinPackageInfo packageInfo) {
+    this.facadeClassName = facadeClassName;
+    this.packageInfo = packageInfo;
+  }
+
+  static KotlinMultiFileClassPartInfo create(
+      MultiFileClassPart classPart, DexClass clazz, AppView<?> appView) {
+    return new KotlinMultiFileClassPartInfo(
+        classPart.getFacadeClassName(),
+        KotlinPackageInfo.create(classPart.toKmPackage(), clazz, appView));
+  }
+
+  @Override
+  public boolean isMultiFileClassPart() {
+    return true;
+  }
+
+  @Override
+  public KotlinMultiFileClassPartInfo asMultiFileClassPart() {
+    return this;
+  }
+
+  @Override
+  public KotlinClassHeader rewrite(
+      DexClass clazz, AppView<AppInfoWithLiveness> appView, NamingLens namingLens) {
+    KotlinClassMetadata.MultiFileClassPart.Writer writer =
+        new KotlinClassMetadata.MultiFileClassPart.Writer();
+    KmPackage kmPackage = new KmPackage();
+    packageInfo.rewrite(kmPackage, clazz, appView, namingLens);
+    kmPackage.accept(writer);
+    return writer.write(facadeClassName).getHeader();
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinPackageInfo.java b/src/main/java/com/android/tools/r8/kotlin/KotlinPackageInfo.java
new file mode 100644
index 0000000..c90d673
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinPackageInfo.java
@@ -0,0 +1,60 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.kotlin;
+
+import static com.android.tools.r8.kotlin.KotlinMetadataUtils.toJvmFieldSignature;
+import static com.android.tools.r8.kotlin.KotlinMetadataUtils.toJvmMethodSignature;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexEncodedField;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.naming.NamingLens;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import java.util.HashMap;
+import java.util.Map;
+import kotlinx.metadata.KmPackage;
+import kotlinx.metadata.jvm.JvmExtensionsKt;
+
+// Holds information about a KmPackage object.
+public class KotlinPackageInfo {
+
+  private final String moduleName;
+  private final KotlinDeclarationContainerInfo containerInfo;
+
+  private KotlinPackageInfo(String moduleName, KotlinDeclarationContainerInfo containerInfo) {
+    this.moduleName = moduleName;
+    this.containerInfo = containerInfo;
+  }
+
+  public static KotlinPackageInfo create(KmPackage kmPackage, DexClass clazz, AppView<?> appView) {
+    Map<String, DexEncodedField> fieldMap = new HashMap<>();
+    for (DexEncodedField field : clazz.fields()) {
+      fieldMap.put(toJvmFieldSignature(field.field).asString(), field);
+    }
+    Map<String, DexEncodedMethod> methodMap = new HashMap<>();
+    for (DexEncodedMethod method : clazz.methods()) {
+      methodMap.put(toJvmMethodSignature(method.method).asString(), method);
+    }
+    return new KotlinPackageInfo(
+        JvmExtensionsKt.getModuleName(kmPackage),
+        KotlinDeclarationContainerInfo.create(kmPackage, methodMap, fieldMap, appView));
+  }
+
+  public void rewrite(
+      KmPackage kmPackage,
+      DexClass clazz,
+      AppView<AppInfoWithLiveness> appView,
+      NamingLens namingLens) {
+    containerInfo.rewrite(
+        kmPackage::visitFunction,
+        kmPackage::visitProperty,
+        kmPackage::visitTypeAlias,
+        clazz,
+        appView,
+        namingLens);
+    JvmExtensionsKt.setModuleName(kmPackage, moduleName);
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinPropertyInfo.java b/src/main/java/com/android/tools/r8/kotlin/KotlinPropertyInfo.java
new file mode 100644
index 0000000..a94b6fc
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinPropertyInfo.java
@@ -0,0 +1,157 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.kotlin;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexEncodedField;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.naming.NamingLens;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import java.util.List;
+import kotlinx.metadata.KmProperty;
+import kotlinx.metadata.KmPropertyVisitor;
+import kotlinx.metadata.jvm.JvmExtensionsKt;
+import kotlinx.metadata.jvm.JvmPropertyExtensionVisitor;
+
+// Holds information about KmProperty
+public class KotlinPropertyInfo implements KotlinFieldLevelInfo, KotlinMethodLevelInfo {
+
+  // Original flags.
+  final int flags;
+
+  // Original getter flags. E.g., for property getter.
+  final int getterFlags;
+
+  // Original setter flags. E.g., for property setter.
+  final int setterFlags;
+
+  // Original property name for (extension) property. Otherwise, null.
+  final String name;
+
+  // Original return type information. This should never be NULL (even for setters without field).
+  final KotlinTypeInfo returnType;
+
+  final KotlinTypeInfo receiverParameterType;
+
+  final KotlinValueParameterInfo setterParameter;
+
+  final List<KotlinTypeParameterInfo> typeParameters;
+
+  final int jvmFlags;
+
+  final KotlinJvmFieldSignatureInfo fieldSignature;
+
+  final KotlinJvmMethodSignatureInfo getterSignature;
+
+  final KotlinJvmMethodSignatureInfo setterSignature;
+
+  final KotlinJvmMethodSignatureInfo syntheticMethodForAnnotations;
+
+  private KotlinPropertyInfo(
+      int flags,
+      int getterFlags,
+      int setterFlags,
+      String name,
+      KotlinTypeInfo returnType,
+      KotlinTypeInfo receiverParameterType,
+      KotlinValueParameterInfo setterParameter,
+      List<KotlinTypeParameterInfo> typeParameters,
+      int jvmFlags,
+      KotlinJvmFieldSignatureInfo fieldSignature,
+      KotlinJvmMethodSignatureInfo getterSignature,
+      KotlinJvmMethodSignatureInfo setterSignature,
+      KotlinJvmMethodSignatureInfo syntheticMethodForAnnotations) {
+    this.flags = flags;
+    this.getterFlags = getterFlags;
+    this.setterFlags = setterFlags;
+    this.name = name;
+    this.returnType = returnType;
+    this.receiverParameterType = receiverParameterType;
+    this.setterParameter = setterParameter;
+    this.typeParameters = typeParameters;
+    this.jvmFlags = jvmFlags;
+    this.fieldSignature = fieldSignature;
+    this.getterSignature = getterSignature;
+    this.setterSignature = setterSignature;
+    this.syntheticMethodForAnnotations = syntheticMethodForAnnotations;
+  }
+
+  public static KotlinPropertyInfo create(KmProperty kmProperty, AppView<?> appView) {
+    return new KotlinPropertyInfo(
+        kmProperty.getFlags(),
+        kmProperty.getGetterFlags(),
+        kmProperty.getSetterFlags(),
+        kmProperty.getName(),
+        KotlinTypeInfo.create(kmProperty.getReturnType(), appView),
+        KotlinTypeInfo.create(kmProperty.getReceiverParameterType(), appView),
+        KotlinValueParameterInfo.create(kmProperty.getSetterParameter(), appView),
+        KotlinTypeParameterInfo.create(kmProperty.getTypeParameters(), appView),
+        JvmExtensionsKt.getJvmFlags(kmProperty),
+        KotlinJvmFieldSignatureInfo.create(JvmExtensionsKt.getFieldSignature(kmProperty), appView),
+        KotlinJvmMethodSignatureInfo.create(
+            JvmExtensionsKt.getGetterSignature(kmProperty), appView),
+        KotlinJvmMethodSignatureInfo.create(
+            JvmExtensionsKt.getSetterSignature(kmProperty), appView),
+        KotlinJvmMethodSignatureInfo.create(
+            JvmExtensionsKt.getSyntheticMethodForAnnotations(kmProperty), appView));
+  }
+
+  @Override
+  public boolean isFieldProperty() {
+    return true;
+  }
+
+  @Override
+  public KotlinPropertyInfo asFieldProperty() {
+    return this;
+  }
+
+  @Override
+  public boolean isProperty() {
+    return true;
+  }
+
+  @Override
+  public KotlinPropertyInfo asProperty() {
+    return this;
+  }
+
+  void rewrite(
+      KmVisitorProviders.KmPropertyVisitorProvider visitorProvider,
+      DexEncodedField field,
+      DexEncodedMethod getter,
+      DexEncodedMethod setter,
+      AppView<AppInfoWithLiveness> appView,
+      NamingLens namingLens) {
+    // TODO(b/154348683): Flags again.
+    KmPropertyVisitor kmProperty = visitorProvider.get(flags, name, getterFlags, setterFlags);
+    // TODO(b/154348149): ReturnType could have been merged to a subtype.
+    if (returnType != null) {
+      returnType.rewrite(kmProperty::visitReturnType, appView, namingLens);
+    }
+    if (receiverParameterType != null) {
+      receiverParameterType.rewrite(kmProperty::visitReceiverParameterType, appView, namingLens);
+    }
+    if (setterParameter != null) {
+      setterParameter.rewrite(kmProperty::visitSetterParameter, appView, namingLens);
+    }
+    for (KotlinTypeParameterInfo typeParameter : typeParameters) {
+      typeParameter.rewrite(kmProperty::visitTypeParameter, appView, namingLens);
+    }
+    JvmPropertyExtensionVisitor extensionVisitor =
+        (JvmPropertyExtensionVisitor) kmProperty.visitExtensions(JvmPropertyExtensionVisitor.TYPE);
+    if (extensionVisitor != null) {
+      extensionVisitor.visit(
+          jvmFlags,
+          fieldSignature == null ? null : fieldSignature.rewrite(field, appView, namingLens),
+          getterSignature == null ? null : getterSignature.rewrite(getter, appView, namingLens),
+          setterSignature == null ? null : setterSignature.rewrite(setter, appView, namingLens));
+      if (syntheticMethodForAnnotations != null) {
+        extensionVisitor.visitSyntheticMethodForAnnotations(
+            syntheticMethodForAnnotations.rewrite(null, appView, namingLens));
+      }
+    }
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinSyntheticClass.java b/src/main/java/com/android/tools/r8/kotlin/KotlinSyntheticClass.java
deleted file mode 100644
index ced14b9..0000000
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinSyntheticClass.java
+++ /dev/null
@@ -1,126 +0,0 @@
-// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-
-package com.android.tools.r8.kotlin;
-
-import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.DexClass;
-import com.android.tools.r8.graph.SubtypingInfo;
-import com.android.tools.r8.naming.NamingLens;
-import com.android.tools.r8.shaking.AppInfoWithLiveness;
-import kotlinx.metadata.jvm.KotlinClassHeader;
-import kotlinx.metadata.jvm.KotlinClassMetadata;
-
-public final class KotlinSyntheticClass extends KotlinInfo<KotlinClassMetadata.SyntheticClass> {
-  // TODO(b/151194794): Once converted to internal data structure, this can be gone.
-  private KotlinClassMetadata.SyntheticClass metadata;
-
-  public enum Flavour {
-    KotlinStyleLambda,
-    JavaStyleLambda,
-    Unclassified
-  }
-
-  private final Flavour flavour;
-
-  static KotlinSyntheticClass fromKotlinClassMetadata(
-      KotlinClassMetadata kotlinClassMetadata, Kotlin kotlin, DexClass clazz) {
-    assert kotlinClassMetadata instanceof KotlinClassMetadata.SyntheticClass;
-    KotlinClassMetadata.SyntheticClass syntheticClass =
-        (KotlinClassMetadata.SyntheticClass) kotlinClassMetadata;
-    if (isKotlinStyleLambda(syntheticClass, kotlin, clazz)) {
-      return new KotlinSyntheticClass(Flavour.KotlinStyleLambda, syntheticClass, clazz);
-    } else if (isJavaStyleLambda(syntheticClass, kotlin, clazz)) {
-      return new KotlinSyntheticClass(Flavour.JavaStyleLambda, syntheticClass, clazz);
-    } else {
-      return new KotlinSyntheticClass(Flavour.Unclassified, syntheticClass, clazz);
-    }
-  }
-
-  private KotlinSyntheticClass(
-      Flavour flavour, KotlinClassMetadata.SyntheticClass metadata, DexClass clazz) {
-    super(metadata, clazz);
-    this.flavour = flavour;
-  }
-
-  @Override
-  void processMetadata(KotlinClassMetadata.SyntheticClass metadata) {
-    this.metadata = metadata;
-    if (metadata.isLambda()) {
-      // TODO(b/151194794): Use #toKmLambda to store a mutable model if needed.
-    }
-  }
-
-  @Override
-  void rewrite(AppView<AppInfoWithLiveness> appView, SubtypingInfo subtypingInfo, NamingLens lens) {
-    // TODO(b/151194794): no idea yet!
-    assert lens.lookupType(clazz.type, appView.dexItemFactory()) == clazz.type
-            || appView.options().enableKotlinMetadataRewritingForRenamedClasses
-        : toString();
-  }
-
-  @Override
-  KotlinClassHeader createHeader() {
-    // TODO(b/151194794): may need to update if `rewrite` is implemented.
-    return metadata.getHeader();
-  }
-
-  public boolean isLambda() {
-    return isKotlinStyleLambda() || isJavaStyleLambda();
-  }
-
-  public boolean isKotlinStyleLambda() {
-    return flavour == Flavour.KotlinStyleLambda;
-  }
-
-  public boolean isJavaStyleLambda() {
-    return flavour == Flavour.JavaStyleLambda;
-  }
-
-  @Override
-  public final Kind getKind() {
-    return Kind.Synthetic;
-  }
-
-  @Override
-  public final boolean isSyntheticClass() {
-    return true;
-  }
-
-  @Override
-  public KotlinSyntheticClass asSyntheticClass() {
-    return this;
-  }
-
-  /**
-   * Returns {@code true} if the given {@link DexClass} is a Kotlin-style lambda:
-   *   a class that
-   *     1) is recognized as lambda in its Kotlin metadata;
-   *     2) directly extends kotlin.jvm.internal.Lambda
-   */
-  private static boolean isKotlinStyleLambda(
-      KotlinClassMetadata.SyntheticClass metadata, Kotlin kotlin, DexClass clazz) {
-    return metadata.isLambda()
-        && clazz.superType == kotlin.functional.lambdaType;
-  }
-
-  /**
-   * Returns {@code true} if the given {@link DexClass} is a Java-style lambda:
-   *   a class that
-   *     1) is recognized as lambda in its Kotlin metadata;
-   *     2) doesn't extend any other class;
-   *     3) directly implements only one Java SAM.
-   */
-  private static boolean isJavaStyleLambda(
-      KotlinClassMetadata.SyntheticClass metadata, Kotlin kotlin, DexClass clazz) {
-    return metadata.isLambda()
-        && clazz.superType == kotlin.factory.objectType
-        && clazz.interfaces.size() == 1;
-  }
-
-  @Override
-  public String toString(String indent) {
-    return indent + "Metadata.SyntheticClass { function: " + metadata.toString() + "}";
-  }
-}
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinSyntheticClassInfo.java b/src/main/java/com/android/tools/r8/kotlin/KotlinSyntheticClassInfo.java
new file mode 100644
index 0000000..8dfe761
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinSyntheticClassInfo.java
@@ -0,0 +1,108 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.kotlin;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.naming.NamingLens;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import kotlinx.metadata.InconsistentKotlinMetadataException;
+import kotlinx.metadata.KmLambda;
+import kotlinx.metadata.jvm.KotlinClassHeader;
+import kotlinx.metadata.jvm.KotlinClassMetadata;
+import kotlinx.metadata.jvm.KotlinClassMetadata.SyntheticClass;
+import kotlinx.metadata.jvm.KotlinClassMetadata.SyntheticClass.Writer;
+
+// Holds information about a Metadata.SyntheticClass object.
+public class KotlinSyntheticClassInfo implements KotlinClassLevelInfo {
+
+  private final KotlinLambdaInfo lambda;
+
+  public enum Flavour {
+    KotlinStyleLambda,
+    JavaStyleLambda,
+    Unclassified
+  }
+
+  private final Flavour flavour;
+
+  private KotlinSyntheticClassInfo(KotlinLambdaInfo lambda, Flavour flavour) {
+    this.lambda = lambda;
+    this.flavour = flavour;
+  }
+
+  static KotlinSyntheticClassInfo create(
+      SyntheticClass syntheticClass, DexClass clazz, Kotlin kotlin, AppView<?> appView) {
+    KmLambda lambda = null;
+    if (syntheticClass.isLambda()) {
+      try {
+        lambda = syntheticClass.toKmLambda();
+        assert lambda != null;
+      } catch (InconsistentKotlinMetadataException ex) {
+        // TODO(b/155534905): Gracefully handle these errors by retaining the original object.
+      }
+    }
+    return new KotlinSyntheticClassInfo(
+        lambda != null ? KotlinLambdaInfo.create(clazz, lambda, appView) : null,
+        getFlavour(syntheticClass, clazz, kotlin));
+  }
+
+  public boolean isLambda() {
+    return lambda != null && flavour != Flavour.Unclassified;
+  }
+
+  public boolean isKotlinStyleLambda() {
+    return flavour == Flavour.KotlinStyleLambda;
+  }
+
+  public boolean isJavaStyleLambda() {
+    return flavour == Flavour.JavaStyleLambda;
+  }
+
+  @Override
+  public boolean isSyntheticClass() {
+    return true;
+  }
+
+  @Override
+  public KotlinSyntheticClassInfo asSyntheticClass() {
+    return this;
+  }
+
+  @Override
+  public KotlinClassHeader rewrite(
+      DexClass clazz, AppView<AppInfoWithLiveness> appView, NamingLens namingLens) {
+    Writer writer = new Writer();
+    if (lambda != null) {
+      KmLambda kmLambda = new KmLambda();
+      if (lambda.rewrite(() -> kmLambda, clazz, appView, namingLens)) {
+        kmLambda.accept(writer);
+      }
+    }
+    return writer.write().getHeader();
+  }
+
+  private static Flavour getFlavour(
+      KotlinClassMetadata.SyntheticClass metadata, DexClass clazz, Kotlin kotlin) {
+    // Returns KotlinStyleLambda if the given clazz is a Kotlin-style lambda:
+    //   a class that
+    //     1) is recognized as lambda in its Kotlin metadata;
+    //     2) directly extends kotlin.jvm.internal.Lambda
+    if (metadata.isLambda() && clazz.superType == kotlin.functional.lambdaType) {
+      return Flavour.KotlinStyleLambda;
+    }
+    // Returns JavaStyleLambda if the given clazz is a Java-style lambda:
+    //  a class that
+    //    1) is recognized as lambda in its Kotlin metadata;
+    //    2) doesn't extend any other class;
+    //    3) directly implements only one Java SAM.
+    if (metadata.isLambda()
+        && clazz.superType == kotlin.factory.objectType
+        && clazz.interfaces.size() == 1) {
+      return Flavour.JavaStyleLambda;
+    }
+    return Flavour.Unclassified;
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinTypeAliasInfo.java b/src/main/java/com/android/tools/r8/kotlin/KotlinTypeAliasInfo.java
new file mode 100644
index 0000000..974469c
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinTypeAliasInfo.java
@@ -0,0 +1,65 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.kotlin;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.naming.NamingLens;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import java.util.List;
+import kotlinx.metadata.KmTypeAlias;
+import kotlinx.metadata.KmTypeAliasVisitor;
+
+// Holds information about KmTypeAlias
+public class KotlinTypeAliasInfo {
+
+  private final int flags;
+  private final String name;
+  private final KotlinTypeInfo underlyingType;
+  private final KotlinTypeInfo expandedType;
+  private final List<KotlinTypeParameterInfo> typeParameters;
+  private final List<KotlinAnnotationInfo> annotations;
+
+  private KotlinTypeAliasInfo(
+      int flags,
+      String name,
+      KotlinTypeInfo underlyingType,
+      KotlinTypeInfo expandedType,
+      List<KotlinTypeParameterInfo> typeParameters,
+      List<KotlinAnnotationInfo> annotations) {
+    this.flags = flags;
+    this.name = name;
+    assert underlyingType != null;
+    assert expandedType != null;
+    this.underlyingType = underlyingType;
+    this.expandedType = expandedType;
+    this.typeParameters = typeParameters;
+    this.annotations = annotations;
+  }
+
+  public static KotlinTypeAliasInfo create(KmTypeAlias alias, AppView<?> appView) {
+    return new KotlinTypeAliasInfo(
+        alias.getFlags(),
+        alias.getName(),
+        KotlinTypeInfo.create(alias.underlyingType, appView),
+        KotlinTypeInfo.create(alias.expandedType, appView),
+        KotlinTypeParameterInfo.create(alias.getTypeParameters(), appView),
+        KotlinAnnotationInfo.create(alias.getAnnotations(), appView));
+  }
+
+  void rewrite(
+      KmVisitorProviders.KmTypeAliasVisitorProvider visitorProvider,
+      AppView<AppInfoWithLiveness> appView,
+      NamingLens namingLens) {
+    KmTypeAliasVisitor kmTypeAliasVisitor = visitorProvider.get(flags, name);
+    underlyingType.rewrite(kmTypeAliasVisitor::visitUnderlyingType, appView, namingLens);
+    expandedType.rewrite(kmTypeAliasVisitor::visitExpandedType, appView, namingLens);
+    for (KotlinTypeParameterInfo typeParameter : typeParameters) {
+      typeParameter.rewrite(kmTypeAliasVisitor::visitTypeParameter, appView, namingLens);
+    }
+    for (KotlinAnnotationInfo annotation : annotations) {
+      annotation.rewrite(kmTypeAliasVisitor::visitAnnotation, appView, namingLens);
+    }
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinTypeInfo.java b/src/main/java/com/android/tools/r8/kotlin/KotlinTypeInfo.java
index 75d80b1..f00a544 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinTypeInfo.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinTypeInfo.java
@@ -4,144 +4,100 @@
 
 package com.android.tools.r8.kotlin;
 
-import static kotlinx.metadata.FlagsKt.flagsOf;
-
 import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.kotlin.Kotlin.ClassClassifiers;
+import com.android.tools.r8.naming.NamingLens;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
-import com.android.tools.r8.utils.DescriptorUtils;
 import com.google.common.collect.ImmutableList;
 import java.util.List;
-import kotlinx.metadata.KmClassifier;
 import kotlinx.metadata.KmType;
 import kotlinx.metadata.KmTypeProjection;
 import kotlinx.metadata.KmTypeVisitor;
+import kotlinx.metadata.jvm.JvmExtensionsKt;
+import kotlinx.metadata.jvm.JvmTypeExtensionVisitor;
 
 // Provides access to Kotlin information about a kotlin type.
 public class KotlinTypeInfo {
 
-  static final List<KotlinTypeProjectionInfo> EMPTY_ARGUMENTS = ImmutableList.of();
+  private static final List<KotlinTypeProjectionInfo> EMPTY_ARGUMENTS = ImmutableList.of();
 
-  private final KmClassifier classifier;
+  private final int flags;
+  private final KotlinClassifierInfo classifier;
+  private final KotlinTypeInfo abbreviatedType;
+  private final KotlinTypeInfo outerType;
   private final List<KotlinTypeProjectionInfo> arguments;
+  private final List<KotlinAnnotationInfo> annotations;
+  // TODO(b/154351125): Extend with flexible upper bounds
 
-  private KotlinTypeInfo(KmClassifier classifier, List<KotlinTypeProjectionInfo> arguments) {
+  private KotlinTypeInfo(
+      int flags,
+      KotlinClassifierInfo classifier,
+      KotlinTypeInfo abbreviatedType,
+      KotlinTypeInfo outerType,
+      List<KotlinTypeProjectionInfo> arguments,
+      List<KotlinAnnotationInfo> annotations) {
+    this.flags = flags;
     this.classifier = classifier;
+    this.abbreviatedType = abbreviatedType;
+    this.outerType = outerType;
     this.arguments = arguments;
-  }
-
-  static KotlinTypeInfo create(KmType kmType) {
-    if (kmType == null) {
-      return null;
-    }
-    if (kmType.getArguments().isEmpty()) {
-      return new KotlinTypeInfo(kmType.classifier, EMPTY_ARGUMENTS);
-    }
-    ImmutableList.Builder<KotlinTypeProjectionInfo> arguments = new ImmutableList.Builder<>();
-    for (KmTypeProjection argument : kmType.getArguments()) {
-      arguments.add(KotlinTypeProjectionInfo.create(argument));
-    }
-    return new KotlinTypeInfo(kmType.classifier, arguments.build());
-  }
-
-  public boolean isTypeAlias() {
-    return classifier instanceof KmClassifier.TypeAlias;
-  }
-
-  public KmClassifier.TypeAlias asTypeAlias() {
-    return (KmClassifier.TypeAlias) classifier;
-  }
-
-  public boolean isClass() {
-    return classifier instanceof KmClassifier.Class;
-  }
-
-  public KmClassifier.Class asClass() {
-    return (KmClassifier.Class) classifier;
-  }
-
-  public boolean isTypeParameter() {
-    return classifier instanceof KmClassifier.TypeParameter;
-  }
-
-  public KmClassifier.TypeParameter asTypeParameter() {
-    return (KmClassifier.TypeParameter) classifier;
-  }
-
-  public boolean isObjectArray() {
-    if (isClass()) {
-      KmClassifier.Class classifier = (KmClassifier.Class) this.classifier;
-      return classifier.getName().equals(ClassClassifiers.arrayBinaryName) && arguments.size() == 1;
-    }
-    return false;
+    this.annotations = annotations;
   }
 
   public List<KotlinTypeProjectionInfo> getArguments() {
     return arguments;
   }
 
-  public KotlinTypeProjectionInfo getArgumentOrNull(int index) {
-    List<KotlinTypeProjectionInfo> arguments = getArguments();
-    return arguments.size() > index ? getArguments().get(index) : null;
-  }
-
-  public KotlinTypeInfo toRenamed(KotlinMetadataSynthesizer synthesizer) {
-    DexType originalType = getLiveDexTypeFromClassClassifier(synthesizer.appView);
-    if (isClass() && originalType == null) {
+  static KotlinTypeInfo create(KmType kmType, AppView<?> appView) {
+    if (kmType == null) {
       return null;
     }
-    KmClassifier renamedClassifier = classifier;
-    if (originalType != null) {
-      String typeClassifier = synthesizer.toRenamedClassifier(originalType);
-      if (typeClassifier != null) {
-        renamedClassifier = new KmClassifier.Class(typeClassifier);
+    return new KotlinTypeInfo(
+        kmType.getFlags(),
+        KotlinClassifierInfo.create(kmType.classifier, appView),
+        KotlinTypeInfo.create(kmType.getAbbreviatedType(), appView),
+        KotlinTypeInfo.create(kmType.getOuterType(), appView),
+        getArguments(kmType.getArguments(), appView),
+        KotlinAnnotationInfo.create(JvmExtensionsKt.getAnnotations(kmType), appView));
+  }
+
+  private static List<KotlinTypeProjectionInfo> getArguments(
+      List<KmTypeProjection> projections, AppView<?> appView) {
+    if (projections.isEmpty()) {
+      return EMPTY_ARGUMENTS;
+    }
+    ImmutableList.Builder<KotlinTypeProjectionInfo> arguments = ImmutableList.builder();
+    for (KmTypeProjection projection : projections) {
+      arguments.add(KotlinTypeProjectionInfo.create(projection, appView));
+    }
+    return arguments.build();
+  }
+
+  public void rewrite(
+      KmVisitorProviders.KmTypeVisitorProvider visitorProvider,
+      AppView<AppInfoWithLiveness> appView,
+      NamingLens namingLens) {
+    // TODO(b/154348683): Check for correct flags
+    KmTypeVisitor kmTypeVisitor = visitorProvider.get(flags);
+    classifier.rewrite(kmTypeVisitor, appView, namingLens);
+    if (abbreviatedType != null) {
+      abbreviatedType.rewrite(kmTypeVisitor::visitAbbreviatedType, appView, namingLens);
+    }
+    if (outerType != null) {
+      outerType.rewrite(kmTypeVisitor::visitOuterType, appView, namingLens);
+    }
+    for (KotlinTypeProjectionInfo argument : arguments) {
+      argument.rewrite(
+          kmTypeVisitor::visitArgument, kmTypeVisitor::visitStarProjection, appView, namingLens);
+    }
+    if (annotations.isEmpty()) {
+      return;
+    }
+    JvmTypeExtensionVisitor extensionVisitor =
+        (JvmTypeExtensionVisitor) kmTypeVisitor.visitExtensions(JvmTypeExtensionVisitor.TYPE);
+    if (extensionVisitor != null) {
+      for (KotlinAnnotationInfo annotation : annotations) {
+        annotation.rewrite(extensionVisitor::visitAnnotation, appView, namingLens);
       }
     }
-    if (arguments.isEmpty()) {
-      return renamedClassifier == classifier
-          ? this
-          : new KotlinTypeInfo(renamedClassifier, EMPTY_ARGUMENTS);
-    }
-    ImmutableList.Builder<KotlinTypeProjectionInfo> builder = ImmutableList.builder();
-    for (KotlinTypeProjectionInfo argument : arguments) {
-      builder.add(
-          new KotlinTypeProjectionInfo(
-              argument.variance,
-              argument.typeInfo == null ? null : argument.typeInfo.toRenamed(synthesizer)));
-    }
-    return new KotlinTypeInfo(renamedClassifier, builder.build());
-  }
-
-  private DexType getLiveDexTypeFromClassClassifier(AppView<AppInfoWithLiveness> appView) {
-    if (!isClass()) {
-      return null;
-    }
-    String descriptor = DescriptorUtils.getDescriptorFromKotlinClassifier(asClass().getName());
-    DexType type = appView.dexItemFactory().createType(descriptor);
-    if (appView.appInfo().wasPruned(type)) {
-      return null;
-    }
-    return type;
-  }
-
-  public KmType asKmType() {
-    KmType kmType = new KmType(flagsOf());
-    visit(kmType);
-    return kmType;
-  }
-
-  public void visit(KmTypeVisitor visitor) {
-    if (isClass()) {
-      visitor.visitClass(asClass().getName());
-    } else if (isTypeAlias()) {
-      visitor.visitTypeAlias(asTypeAlias().getName());
-    } else {
-      assert isTypeParameter();
-      visitor.visitTypeParameter(asTypeParameter().getId());
-    }
-    for (KotlinTypeProjectionInfo argument : arguments) {
-      argument.visit(visitor);
-    }
   }
 }
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinTypeParameterInfo.java b/src/main/java/com/android/tools/r8/kotlin/KotlinTypeParameterInfo.java
index b9a86d4..e79ea83 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinTypeParameterInfo.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinTypeParameterInfo.java
@@ -4,45 +4,96 @@
 
 package com.android.tools.r8.kotlin;
 
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.naming.NamingLens;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.google.common.collect.ImmutableList;
+import java.util.List;
+import kotlinx.metadata.KmType;
 import kotlinx.metadata.KmTypeParameter;
+import kotlinx.metadata.KmTypeParameterVisitor;
 import kotlinx.metadata.KmVariance;
+import kotlinx.metadata.jvm.JvmExtensionsKt;
+import kotlinx.metadata.jvm.JvmTypeParameterExtensionVisitor;
 
 // Provides access to Kotlin information about a type-parameter.
 public class KotlinTypeParameterInfo {
 
+  private static final List<KotlinTypeParameterInfo> EMPTY_TYPE_PARAMETERS = ImmutableList.of();
+  private static final List<KotlinTypeInfo> EMPTY_UPPER_BOUNDS = ImmutableList.of();
+
   private final int flags;
   private final int id;
   private final String name;
   private final KmVariance variance;
+  private final List<KotlinTypeInfo> originalUpperBounds;
+  private final List<KotlinAnnotationInfo> annotations;
 
-  private KotlinTypeParameterInfo(int flags, int id, String name, KmVariance variance) {
+  private KotlinTypeParameterInfo(
+      int flags,
+      int id,
+      String name,
+      KmVariance variance,
+      List<KotlinTypeInfo> originalUpperBounds,
+      List<KotlinAnnotationInfo> annotations) {
     this.flags = flags;
     this.id = id;
     this.name = name;
     this.variance = variance;
+    this.originalUpperBounds = originalUpperBounds;
+    this.annotations = annotations;
   }
 
-  static KotlinTypeParameterInfo fromKmTypeParameter(KmTypeParameter kmTypeParameter) {
+  private static KotlinTypeParameterInfo create(
+      KmTypeParameter kmTypeParameter, AppView<?> appView) {
     return new KotlinTypeParameterInfo(
         kmTypeParameter.getFlags(),
         kmTypeParameter.getId(),
         kmTypeParameter.getName(),
-        kmTypeParameter.getVariance());
+        kmTypeParameter.getVariance(),
+        getUpperBounds(kmTypeParameter.getUpperBounds(), appView),
+        KotlinAnnotationInfo.create(JvmExtensionsKt.getAnnotations(kmTypeParameter), appView));
   }
 
-  public int getFlags() {
-    return flags;
+  static List<KotlinTypeParameterInfo> create(
+      List<KmTypeParameter> kmTypeParameters, AppView<?> appView) {
+    if (kmTypeParameters.isEmpty()) {
+      return EMPTY_TYPE_PARAMETERS;
+    }
+    ImmutableList.Builder<KotlinTypeParameterInfo> builder = ImmutableList.builder();
+    for (KmTypeParameter kmTypeParameter : kmTypeParameters) {
+      builder.add(create(kmTypeParameter, appView));
+    }
+    return builder.build();
   }
 
-  public int getId() {
-    return id;
+  private static List<KotlinTypeInfo> getUpperBounds(List<KmType> upperBounds, AppView<?> appView) {
+    if (upperBounds.isEmpty()) {
+      return EMPTY_UPPER_BOUNDS;
+    }
+    ImmutableList.Builder<KotlinTypeInfo> builder = ImmutableList.builder();
+    for (KmType upperBound : upperBounds) {
+      builder.add(KotlinTypeInfo.create(upperBound, appView));
+    }
+    return builder.build();
   }
 
-  public String getName() {
-    return name;
-  }
-
-  public KmVariance getVariance() {
-    return variance;
+  void rewrite(
+      KmVisitorProviders.KmTypeParameterVisitorProvider visitorProvider,
+      AppView<AppInfoWithLiveness> appView,
+      NamingLens namingLens) {
+    KmTypeParameterVisitor kmTypeParameterVisitor = visitorProvider.get(flags, name, id, variance);
+    for (KotlinTypeInfo originalUpperBound : originalUpperBounds) {
+      originalUpperBound.rewrite(kmTypeParameterVisitor::visitUpperBound, appView, namingLens);
+    }
+    if (annotations.isEmpty()) {
+      return;
+    }
+    JvmTypeParameterExtensionVisitor extensionVisitor =
+        (JvmTypeParameterExtensionVisitor)
+            kmTypeParameterVisitor.visitExtensions(JvmTypeParameterExtensionVisitor.TYPE);
+    for (KotlinAnnotationInfo annotation : annotations) {
+      annotation.rewrite(extensionVisitor::visitAnnotation, appView, namingLens);
+    }
   }
 }
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinTypeProjectionInfo.java b/src/main/java/com/android/tools/r8/kotlin/KotlinTypeProjectionInfo.java
index a16c78e..9ab8adc 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinTypeProjectionInfo.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinTypeProjectionInfo.java
@@ -4,10 +4,10 @@
 
 package com.android.tools.r8.kotlin;
 
-import static kotlinx.metadata.FlagsKt.flagsOf;
-
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.naming.NamingLens;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import kotlinx.metadata.KmTypeProjection;
-import kotlinx.metadata.KmTypeVisitor;
 import kotlinx.metadata.KmVariance;
 
 // Provides access to Kotlin information about the type projection of a type (arguments).
@@ -16,27 +16,29 @@
   final KmVariance variance;
   final KotlinTypeInfo typeInfo;
 
-  KotlinTypeProjectionInfo(KmVariance variance, KotlinTypeInfo typeInfo) {
+  private KotlinTypeProjectionInfo(KmVariance variance, KotlinTypeInfo typeInfo) {
     this.variance = variance;
     this.typeInfo = typeInfo;
   }
 
-  static KotlinTypeProjectionInfo create(KmTypeProjection kmTypeProjection) {
+  static KotlinTypeProjectionInfo create(KmTypeProjection kmTypeProjection, AppView<?> appView) {
     return new KotlinTypeProjectionInfo(
-        kmTypeProjection.getVariance(), KotlinTypeInfo.create(kmTypeProjection.getType()));
+        kmTypeProjection.getVariance(), KotlinTypeInfo.create(kmTypeProjection.getType(), appView));
   }
 
-  public boolean isStarProjection() {
+  private boolean isStarProjection() {
     return variance == null && typeInfo == null;
   }
 
-  public void visit(KmTypeVisitor visitor) {
-    KmTypeVisitor kmTypeVisitor = visitor.visitArgument(flagsOf(), variance);
-    // TODO(b/152886451): Check if this check should be before visitor.visitArgument(...).
+  public void rewrite(
+      KmVisitorProviders.KmTypeProjectionVisitorProvider visitorProvider,
+      KmVisitorProviders.KmTypeStarProjectionVisitorProvider starProjectionProvider,
+      AppView<AppInfoWithLiveness> appView,
+      NamingLens namingLens) {
     if (isStarProjection()) {
-      kmTypeVisitor.visitStarProjection();
+      starProjectionProvider.get();
     } else {
-      typeInfo.visit(kmTypeVisitor);
+      typeInfo.rewrite(flags -> visitorProvider.get(flags, variance), appView, namingLens);
     }
   }
 }
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinValueParameterInfo.java b/src/main/java/com/android/tools/r8/kotlin/KotlinValueParameterInfo.java
index e358c50..d092495 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinValueParameterInfo.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinValueParameterInfo.java
@@ -1,54 +1,71 @@
 // Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
+
 package com.android.tools.r8.kotlin;
 
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.naming.NamingLens;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.google.common.collect.ImmutableList;
 import java.util.List;
-import kotlinx.metadata.KmAnnotation;
 import kotlinx.metadata.KmType;
 import kotlinx.metadata.KmValueParameter;
-import kotlinx.metadata.jvm.JvmExtensionsKt;
+import kotlinx.metadata.KmValueParameterVisitor;
 
 // Provides access to Kotlin information about value parameter.
 class KotlinValueParameterInfo {
-  // TODO(b/151193860): When to use original param name v.s. when to *not* use?
+  private static final List<KotlinValueParameterInfo> EMPTY_VALUE_PARAMETERS = ImmutableList.of();
   // Original parameter name.
   final String name;
-  // Original parameter flag, e.g., has default value.
-  final int flag;
-  // Indicates whether the formal parameter is originally `vararg`.
-  final boolean isVararg;
+  // Original parameter flags, e.g., has default value.
+  final int flags;
   // Original information about the type.
   final KotlinTypeInfo type;
-
-  // TODO(b/151194869): Should we treat them as normal annotations? E.g., shrinking and renaming?
-  // Annotations on the type of value parameter.
-  final List<KmAnnotation> annotations;
+  // Indicates whether the formal parameter is originally `vararg`.
+  final KotlinTypeInfo varargElementType;
 
   private KotlinValueParameterInfo(
-      String name,
-      int flag,
-      boolean isVararg,
-      KotlinTypeInfo type,
-      List<KmAnnotation> annotations) {
+      int flags, String name, KotlinTypeInfo type, KotlinTypeInfo varargElementType) {
     this.name = name;
-    this.flag = flag;
-    this.isVararg = isVararg;
+    this.flags = flags;
     this.type = type;
-    this.annotations = annotations;
+    this.varargElementType = varargElementType;
   }
 
-  static KotlinValueParameterInfo fromKmValueParameter(KmValueParameter kmValueParameter) {
+  static KotlinValueParameterInfo create(KmValueParameter kmValueParameter, AppView<?> appView) {
     if (kmValueParameter == null) {
       return null;
     }
     KmType kmType = kmValueParameter.getType();
     return new KotlinValueParameterInfo(
-        kmValueParameter.getName(),
         kmValueParameter.getFlags(),
-        kmValueParameter.getVarargElementType() != null,
-        KotlinTypeInfo.create(kmType),
-        kmType != null ? JvmExtensionsKt.getAnnotations(kmType) : ImmutableList.of());
+        kmValueParameter.getName(),
+        KotlinTypeInfo.create(kmType, appView),
+        KotlinTypeInfo.create(kmValueParameter.getVarargElementType(), appView));
+  }
+
+  static List<KotlinValueParameterInfo> create(
+      List<KmValueParameter> parameters, AppView<?> appView) {
+    if (parameters.isEmpty()) {
+      return EMPTY_VALUE_PARAMETERS;
+    }
+    ImmutableList.Builder<KotlinValueParameterInfo> builder = ImmutableList.builder();
+    for (KmValueParameter parameter : parameters) {
+      builder.add(create(parameter, appView));
+    }
+    return builder.build();
+  }
+
+  void rewrite(
+      KmVisitorProviders.KmValueParameterVisitorProvider visitorProvider,
+      AppView<AppInfoWithLiveness> appView,
+      NamingLens namingLens) {
+    KmValueParameterVisitor kmValueParameterVisitor = visitorProvider.get(flags, name);
+    type.rewrite(kmValueParameterVisitor::visitType, appView, namingLens);
+    if (varargElementType != null) {
+      varargElementType.rewrite(
+          kmValueParameterVisitor::visitVarargElementType, appView, namingLens);
+    }
   }
 }
diff --git a/src/main/java/com/android/tools/r8/naming/ClassNameMapper.java b/src/main/java/com/android/tools/r8/naming/ClassNameMapper.java
index 0aeae73..e98d9b9 100644
--- a/src/main/java/com/android/tools/r8/naming/ClassNameMapper.java
+++ b/src/main/java/com/android/tools/r8/naming/ClassNameMapper.java
@@ -15,6 +15,7 @@
 import com.android.tools.r8.naming.MemberNaming.Signature;
 import com.android.tools.r8.position.Position;
 import com.android.tools.r8.utils.BiMapContainer;
+import com.android.tools.r8.utils.ChainableStringConsumer;
 import com.google.common.collect.BiMap;
 import com.google.common.collect.ImmutableBiMap;
 import com.google.common.collect.ImmutableMap;
@@ -23,16 +24,14 @@
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.InputStreamReader;
-import java.io.StringWriter;
-import java.io.Writer;
 import java.nio.charset.StandardCharsets;
 import java.nio.file.Files;
 import java.nio.file.Path;
-import java.util.ArrayList;
 import java.util.Comparator;
 import java.util.HashMap;
-import java.util.List;
+import java.util.Iterator;
 import java.util.Map;
+import java.util.Map.Entry;
 
 public class ClassNameMapper implements ProguardMap {
 
@@ -113,6 +112,14 @@
     this.classNameMappings = builder.build();
   }
 
+  private ClassNameMapper(ImmutableMap<String, ClassNamingForNameMapper> classNameMappings) {
+    this.classNameMappings = classNameMappings;
+  }
+
+  public Map<String, ClassNamingForNameMapper> getClassNameMappings() {
+    return classNameMappings;
+  }
+
   private Signature canonicalizeSignature(Signature signature) {
     Signature result = signatureMap.get(signature);
     if (result != null) {
@@ -174,26 +181,45 @@
     return classNameMappings.get(obfuscatedName);
   }
 
-  public void write(Writer writer) throws IOException {
-    // Sort classes by their original name such that the generated Proguard map is deterministic
-    // (and easy to navigate manually).
-    List<ClassNamingForNameMapper> classNamingForNameMappers =
-        new ArrayList<>(classNameMappings.values());
-    classNamingForNameMappers.sort(Comparator.comparing(x -> x.originalName));
-    for (ClassNamingForNameMapper naming : classNamingForNameMappers) {
-      naming.write(writer);
+  public boolean isEmpty() {
+    return classNameMappings.isEmpty();
+  }
+
+  public ClassNameMapper sorted() {
+    ImmutableMap.Builder<String, ClassNamingForNameMapper> builder = ImmutableMap.builder();
+    builder.orderEntriesByValue(Comparator.comparing(x -> x.originalName));
+    classNameMappings.forEach(builder::put);
+    return new ClassNameMapper(builder.build());
+  }
+
+  public boolean verifyIsSorted() {
+    Iterator<Entry<String, ClassNamingForNameMapper>> iterator =
+        getClassNameMappings().entrySet().iterator();
+    Iterator<Entry<String, ClassNamingForNameMapper>> sortedIterator =
+        sorted().getClassNameMappings().entrySet().iterator();
+    while (iterator.hasNext()) {
+      Entry<String, ClassNamingForNameMapper> entry = iterator.next();
+      Entry<String, ClassNamingForNameMapper> otherEntry = sortedIterator.next();
+      assert entry.getKey().equals(otherEntry.getKey());
+      assert entry.getValue() == otherEntry.getValue();
+    }
+    return true;
+  }
+
+  public void write(ChainableStringConsumer consumer) {
+    // Classes should be sorted by their original name such that the generated Proguard map is
+    // deterministic (and easy to navigate manually).
+    assert verifyIsSorted();
+    for (ClassNamingForNameMapper naming : getClassNameMappings().values()) {
+      naming.write(consumer);
     }
   }
 
   @Override
   public String toString() {
-    try {
-      StringWriter writer = new StringWriter();
-      write(writer);
-      return writer.toString();
-    } catch (IOException e) {
-      return e.toString();
-    }
+    StringBuilder builder = new StringBuilder();
+    write(ChainableStringConsumer.wrap(builder::append));
+    return builder.toString();
   }
 
   public BiMapContainer<String, String> getObfuscatedToOriginalMapping() {
diff --git a/src/main/java/com/android/tools/r8/naming/ClassNameMinifier.java b/src/main/java/com/android/tools/r8/naming/ClassNameMinifier.java
index be788ab..d7797a4 100644
--- a/src/main/java/com/android/tools/r8/naming/ClassNameMinifier.java
+++ b/src/main/java/com/android/tools/r8/naming/ClassNameMinifier.java
@@ -48,13 +48,14 @@
   private final boolean isAccessModificationAllowed;
   private final Set<String> noObfuscationPrefixes = Sets.newHashSet();
   private final Set<String> usedPackagePrefixes = Sets.newHashSet();
-  private final Set<DexString> usedTypeNames = Sets.newIdentityHashSet();
-
+  private final Set<String> usedTypeNames = Sets.newHashSet();
   private final Map<DexType, DexString> renaming = Maps.newIdentityHashMap();
   private final Map<String, Namespace> states = new HashMap<>();
   private final boolean keepInnerClassStructure;
 
   private final Namespace topLevelState;
+  private final boolean allowMixedCaseNaming;
+  private final Predicate<String> isUsed;
 
   ClassNameMinifier(
       AppView<AppInfoWithLiveness> appView,
@@ -76,11 +77,23 @@
         getPackageBinaryNameFromJavaType(options.getProguardConfiguration().getPackagePrefix()));
 
     states.put("", topLevelState);
+
+    if (options.getProguardConfiguration().hasDontUseMixedCaseClassnames()) {
+      allowMixedCaseNaming = false;
+      isUsed = candidate -> usedTypeNames.contains(candidate.toLowerCase());
+    } else {
+      allowMixedCaseNaming = true;
+      isUsed = usedTypeNames::contains;
+    }
+  }
+
+  private void setUsedTypeName(String typeName) {
+    usedTypeNames.add(allowMixedCaseNaming ? typeName : typeName.toLowerCase());
   }
 
   static class ClassRenaming {
-    protected final Map<String, String> packageRenaming;
-    protected final Map<DexType, DexString> classRenaming;
+    final Map<String, String> packageRenaming;
+    final Map<DexType, DexString> classRenaming;
 
     private ClassRenaming(
         Map<DexType, DexString> classRenaming, Map<String, String> packageRenaming) {
@@ -195,7 +208,7 @@
     renaming.put(type, descriptor);
     registerPackagePrefixesAsUsed(
         getParentPackagePrefix(getClassBinaryNameFromDescriptor(descriptor.toSourceString())));
-    usedTypeNames.add(descriptor);
+    setUsedTypeName(descriptor.toString());
     if (keepInnerClassStructure) {
       DexType outerClass = getOutClassForType(type);
       if (outerClass != null) {
@@ -387,10 +400,9 @@
     }
 
     DexString nextTypeName(DexType type) {
-      DexString candidate =
-          classNamingStrategy.next(type, packagePrefix, this, usedTypeNames::contains);
-      assert !usedTypeNames.contains(candidate);
-      usedTypeNames.add(candidate);
+      DexString candidate = classNamingStrategy.next(type, packagePrefix, this, isUsed);
+      assert !usedTypeNames.contains(candidate.toString());
+      setUsedTypeName(candidate.toString());
       return candidate;
     }
 
@@ -420,7 +432,7 @@
 
   protected interface ClassNamingStrategy {
     DexString next(
-        DexType type, char[] packagePrefix, InternalNamingState state, Predicate<DexString> isUsed);
+        DexType type, char[] packagePrefix, InternalNamingState state, Predicate<String> isUsed);
 
     /**
      * Returns the reserved descriptor for a type. If the type is not allowed to be obfuscated
diff --git a/src/main/java/com/android/tools/r8/naming/ClassNamingForNameMapper.java b/src/main/java/com/android/tools/r8/naming/ClassNamingForNameMapper.java
index fd5df23..5a00fcb 100644
--- a/src/main/java/com/android/tools/r8/naming/ClassNamingForNameMapper.java
+++ b/src/main/java/com/android/tools/r8/naming/ClassNamingForNameMapper.java
@@ -7,12 +7,10 @@
 import com.android.tools.r8.naming.MemberNaming.MethodSignature;
 import com.android.tools.r8.naming.MemberNaming.Signature;
 import com.android.tools.r8.naming.MemberNaming.Signature.SignatureKind;
+import com.android.tools.r8.utils.ChainableStringConsumer;
 import com.android.tools.r8.utils.ThrowingConsumer;
 import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.Maps;
-import java.io.IOException;
-import java.io.StringWriter;
-import java.io.Writer;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
@@ -184,7 +182,7 @@
   }
 
   public final String originalName;
-  private final String renamedName;
+  public final String renamedName;
 
   /**
    * Mapping from the renamed signature to the naming information for a member.
@@ -297,19 +295,11 @@
     return methodMembers.values();
   }
 
-  void write(Writer writer) throws IOException {
-    writer.append(originalName);
-    writer.append(" -> ");
-    writer.append(renamedName);
-    writer.append(":\n");
+  void write(ChainableStringConsumer consumer) {
+    consumer.accept(originalName).accept(" -> ").accept(renamedName).accept(":\n");
 
-    // First print non-method MemberNamings.
-    forAllMemberNaming(
-        m -> {
-          if (!m.isMethodNaming()) {
-            writer.append("    ").append(m.toString()).append('\n');
-          }
-        });
+    // First print field member namings.
+    forAllFieldNaming(m -> consumer.accept("    ").accept(m.toString()).accept("\n"));
 
     // Sort MappedRanges by sequence number to restore construction order (original Proguard-map
     // input).
@@ -319,19 +309,15 @@
     }
     mappedRangesSorted.sort(Comparator.comparingInt(range -> range.sequenceNumber));
     for (MappedRange range : mappedRangesSorted) {
-      writer.append("    ").append(range.toString()).append('\n');
+      consumer.accept("    ").accept(range.toString()).accept("\n");
     }
   }
 
   @Override
   public String toString() {
-    try {
-      StringWriter writer = new StringWriter();
-      write(writer);
-      return writer.toString();
-    } catch (IOException e) {
-      return e.toString();
-    }
+    StringBuilder builder = new StringBuilder();
+    write(ChainableStringConsumer.wrap(builder::append));
+    return builder.toString();
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/naming/MinifiedRenaming.java b/src/main/java/com/android/tools/r8/naming/MinifiedRenaming.java
index 59fee69..f789fe4 100644
--- a/src/main/java/com/android/tools/r8/naming/MinifiedRenaming.java
+++ b/src/main/java/com/android/tools/r8/naming/MinifiedRenaming.java
@@ -141,13 +141,13 @@
   }
 
   @Override
-  void forAllRenamedTypes(Consumer<DexType> consumer) {
+  public void forAllRenamedTypes(Consumer<DexType> consumer) {
     DexReference.filterDexType(DexReference.filterDexReference(renaming.keySet().stream()))
         .forEach(consumer);
   }
 
   @Override
-  <T extends DexItem> Map<String, T> getRenamedItems(
+  public <T extends DexItem> Map<String, T> getRenamedItems(
       Class<T> clazz, Predicate<T> predicate, Function<T, String> namer) {
     return renaming.keySet().stream()
         .filter(item -> (clazz.isInstance(item) && predicate.test(clazz.cast(item))))
diff --git a/src/main/java/com/android/tools/r8/naming/Minifier.java b/src/main/java/com/android/tools/r8/naming/Minifier.java
index d83a0f0..8f1a41a 100644
--- a/src/main/java/com/android/tools/r8/naming/Minifier.java
+++ b/src/main/java/com/android/tools/r8/naming/Minifier.java
@@ -148,11 +148,8 @@
 
     @Override
     public DexString next(
-        DexType type,
-        char[] packagePrefix,
-        InternalNamingState state,
-        Predicate<DexString> isUsed) {
-      DexString candidate = null;
+        DexType type, char[] packagePrefix, InternalNamingState state, Predicate<String> isUsed) {
+      String candidate = null;
       String lastName = null;
       do {
         String newName = nextName(packagePrefix, state, false) + ";";
@@ -170,9 +167,9 @@
         if (newName.endsWith("LR;") || newName.endsWith("/R;")) {
           continue;
         }
-        candidate = factory.createString(newName);
+        candidate = newName;
       } while (candidate == null || isUsed.test(candidate));
-      return candidate;
+      return factory.createString(candidate);
     }
 
     @Override
diff --git a/src/main/java/com/android/tools/r8/naming/NamingLens.java b/src/main/java/com/android/tools/r8/naming/NamingLens.java
index b3694f7..a142cbf 100644
--- a/src/main/java/com/android/tools/r8/naming/NamingLens.java
+++ b/src/main/java/com/android/tools/r8/naming/NamingLens.java
@@ -124,9 +124,9 @@
     return DescriptorUtils.descriptorToInternalName(lookupDescriptor(type).toString());
   }
 
-  abstract void forAllRenamedTypes(Consumer<DexType> consumer);
+  public abstract void forAllRenamedTypes(Consumer<DexType> consumer);
 
-  abstract <T extends DexItem> Map<String, T> getRenamedItems(
+  public abstract <T extends DexItem> Map<String, T> getRenamedItems(
       Class<T> clazz, Predicate<T> predicate, Function<T, String> namer);
 
   /**
@@ -211,12 +211,12 @@
     }
 
     @Override
-    void forAllRenamedTypes(Consumer<DexType> consumer) {
+    public void forAllRenamedTypes(Consumer<DexType> consumer) {
       // Intentionally left empty.
     }
 
     @Override
-    <T extends DexItem> Map<String, T> getRenamedItems(
+    public <T extends DexItem> Map<String, T> getRenamedItems(
         Class<T> clazz, Predicate<T> predicate, Function<T, String> namer) {
       return ImmutableMap.of();
     }
diff --git a/src/main/java/com/android/tools/r8/naming/PrefixRewritingNamingLens.java b/src/main/java/com/android/tools/r8/naming/PrefixRewritingNamingLens.java
index e411211..482750f 100644
--- a/src/main/java/com/android/tools/r8/naming/PrefixRewritingNamingLens.java
+++ b/src/main/java/com/android/tools/r8/naming/PrefixRewritingNamingLens.java
@@ -96,7 +96,8 @@
 
   @Override
   public DexString lookupMethodName(DexCallSite callSite) {
-    if (isRenamed(callSite.bootstrapMethod.rewrittenTarget.holder)) {
+    if (callSite.bootstrapMethod.rewrittenTarget != null
+        && isRenamed(callSite.bootstrapMethod.rewrittenTarget.holder)) {
       // Prefix rewriting does not influence the inner name.
       return callSite.methodName;
     }
diff --git a/src/main/java/com/android/tools/r8/naming/ProguardMapMinifier.java b/src/main/java/com/android/tools/r8/naming/ProguardMapMinifier.java
index 9a30e79..5e23d98 100644
--- a/src/main/java/com/android/tools/r8/naming/ProguardMapMinifier.java
+++ b/src/main/java/com/android/tools/r8/naming/ProguardMapMinifier.java
@@ -405,10 +405,7 @@
 
     @Override
     public DexString next(
-        DexType type,
-        char[] packagePrefix,
-        InternalNamingState state,
-        Predicate<DexString> isUsed) {
+        DexType type, char[] packagePrefix, InternalNamingState state, Predicate<String> isUsed) {
       assert !mappings.containsKey(type);
       assert appView.rootSet().mayBeMinified(type, appView);
       return super.next(
@@ -416,7 +413,7 @@
           packagePrefix,
           state,
           candidate -> {
-            if (mappedNames.contains(candidate.toString())) {
+            if (mappedNames.contains(candidate)) {
               return true;
             }
             return isUsed.test(candidate);
diff --git a/src/main/java/com/android/tools/r8/naming/ProguardMapSupplier.java b/src/main/java/com/android/tools/r8/naming/ProguardMapSupplier.java
index 0cee87f..ec947e8 100644
--- a/src/main/java/com/android/tools/r8/naming/ProguardMapSupplier.java
+++ b/src/main/java/com/android/tools/r8/naming/ProguardMapSupplier.java
@@ -3,16 +3,19 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.naming;
 
+import com.android.tools.r8.DiagnosticsHandler;
+import com.android.tools.r8.StringConsumer;
 import com.android.tools.r8.Version;
-import com.android.tools.r8.graph.DexApplication;
+import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.utils.Box;
+import com.android.tools.r8.utils.ChainableStringConsumer;
+import com.android.tools.r8.utils.ExceptionUtils;
 import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.Reporter;
 import com.android.tools.r8.utils.VersionProperties;
 import com.google.common.hash.Hasher;
 import com.google.common.hash.Hashing;
-import java.io.ByteArrayOutputStream;
 import java.io.IOException;
-import java.io.PrintWriter;
-import java.io.Writer;
 
 public class ProguardMapSupplier {
 
@@ -25,57 +28,57 @@
 
   public static int PG_MAP_ID_LENGTH = 7;
 
-  public static ProguardMapSupplier fromClassNameMapper(
-      ClassNameMapper classNameMapper, InternalOptions options) {
-    return new ProguardMapSupplier(true, classNameMapper, null, null, options);
-  }
-
-  public static ProguardMapSupplier fromNamingLens(
-      NamingLens namingLens, DexApplication dexApplication, InternalOptions options) {
-    return new ProguardMapSupplier(false, null, namingLens, dexApplication, options);
-  }
-
-  public static class ProguardMapAndId {
-    public final String map;
-    public final String id;
-
-    ProguardMapAndId(String map, String id) {
-      assert map != null && id != null;
-      this.map = map;
-      this.id = id;
+  // Truncated murmur hash of the non-whitespace codepoints of the Proguard map (excluding the
+  // marker).
+  public static class ProguardMapId extends Box<String> {
+    private ProguardMapId(String id) {
+      super(id);
+      assert id != null;
+      assert id.length() == PG_MAP_ID_LENGTH;
     }
   }
 
-  public ProguardMapSupplier(
-      boolean useClassNameMapper,
-      ClassNameMapper classNameMapper,
-      NamingLens namingLens,
-      DexApplication application,
-      InternalOptions options) {
-    this.useClassNameMapper = useClassNameMapper;
-    this.classNameMapper = classNameMapper;
-    this.namingLens = namingLens;
-    this.application = application;
-    this.minApiLevel = options.isGeneratingClassFiles() ? null : options.minApiLevel;
-  }
-
-  private final boolean useClassNameMapper;
   private final ClassNameMapper classNameMapper;
-  private final NamingLens namingLens;
-  private final DexApplication application;
-  private final Integer minApiLevel;
+  private final StringConsumer consumer;
+  private final InternalOptions options;
+  private final Reporter reporter;
 
-  public ProguardMapAndId getProguardMapAndId() {
-    String body = getBody();
-    if (body == null || body.trim().length() == 0) {
-      return null;
-    }
-    // Algorithm:
-    // Hash of the non-whitespace codepoints of the input string.
-    Hasher hasher = Hashing.murmur3_32().newHasher();
-    body.codePoints().filter(c -> !Character.isWhitespace(c)).forEach(hasher::putInt);
-    String proguardMapId = hasher.hash().toString().substring(0, PG_MAP_ID_LENGTH);
+  private ProguardMapSupplier(ClassNameMapper classNameMapper, InternalOptions options) {
+    assert classNameMapper != null;
+    assert !classNameMapper.isEmpty();
+    this.classNameMapper = classNameMapper.sorted();
+    this.consumer =
+        InternalOptions.assertionsEnabled()
+            ? new ProguardMapChecker(options.proguardMapConsumer)
+            : options.proguardMapConsumer;
+    this.options = options;
+    this.reporter = options.reporter;
+  }
 
+  public static ProguardMapSupplier create(
+      ClassNameMapper classNameMapper, InternalOptions options) {
+    return classNameMapper.isEmpty() ? null : new ProguardMapSupplier(classNameMapper, options);
+  }
+
+  public ProguardMapId writeProguardMap() {
+    ProguardMapId id = computeProguardMapId();
+    writeMarker(id);
+    writeBody();
+    ExceptionUtils.withFinishedResourceHandler(reporter, consumer);
+    return id;
+  }
+
+  private ProguardMapId computeProguardMapId() {
+    ProguardMapIdBuilder builder = new ProguardMapIdBuilder();
+    classNameMapper.write(builder);
+    return builder.build();
+  }
+
+  private void writeBody() {
+    classNameMapper.write(new ProguardMapWriter());
+  }
+
+  private void writeMarker(ProguardMapId id) {
     StringBuilder builder = new StringBuilder();
     builder.append(
         "# "
@@ -88,44 +91,81 @@
             + ": "
             + Version.LABEL
             + "\n");
-    if (minApiLevel != null) {
-      builder.append("# " + MARKER_KEY_MIN_API + ": " + minApiLevel + "\n");
+    if (options.isGeneratingDex()) {
+      builder.append("# " + MARKER_KEY_MIN_API + ": " + options.minApiLevel + "\n");
     }
     if (Version.isDevelopmentVersion()) {
       builder.append(
           "# " + MARKER_KEY_COMPILER_HASH + ": " + VersionProperties.INSTANCE.getSha() + "\n");
     }
-    builder.append("# " + MARKER_KEY_PG_MAP_ID + ": " + proguardMapId + "\n");
+    builder.append("# " + MARKER_KEY_PG_MAP_ID + ": " + id.get() + "\n");
     // Turn off linting of the mapping file in some build systems.
     builder.append("# common_typos_disable" + "\n");
-    builder.append(body);
-
-    return new ProguardMapAndId(builder.toString(), proguardMapId);
+    consumer.accept(builder.toString(), reporter);
   }
 
-  private String getBody() {
-    if (useClassNameMapper) {
-      assert classNameMapper != null;
-      return classNameMapper.toString();
-    }
-    assert namingLens != null && application != null;
-    // TODO(herhut): Should writing of the proguard-map file be split like this?
-    if (!namingLens.isIdentityLens()) {
-      StringBuilder map = new StringBuilder();
-      new MinifiedNameMapPrinter(application, namingLens).write(map);
-      return map.toString();
-    }
-    if (application.getProguardMap() != null) {
-      ByteArrayOutputStream bytes = new ByteArrayOutputStream();
-      Writer writer = new PrintWriter(bytes);
-      try {
-        application.getProguardMap().write(writer);
-        writer.flush();
-      } catch (IOException e) {
-        throw new RuntimeException("IOException while creating Proguard-map output: " + e);
+  static class ProguardMapIdBuilder implements ChainableStringConsumer {
+
+    private final Hasher hasher = Hashing.murmur3_32().newHasher();
+
+    @Override
+    public ProguardMapIdBuilder accept(String string) {
+      for (int i = 0; i < string.length(); i++) {
+        char c = string.charAt(i);
+        if (!Character.isWhitespace(c)) {
+          hasher.putInt(c);
+        }
       }
-      return bytes.toString();
+      return this;
     }
-    return null;
+
+    public ProguardMapId build() {
+      return new ProguardMapId(hasher.hash().toString().substring(0, PG_MAP_ID_LENGTH));
+    }
+  }
+
+  class ProguardMapWriter implements ChainableStringConsumer {
+
+    @Override
+    public ProguardMapWriter accept(String string) {
+      consumer.accept(string, reporter);
+      return this;
+    }
+  }
+
+  static class ProguardMapChecker implements StringConsumer {
+
+    private final StringConsumer inner;
+    private final StringBuilder contents = new StringBuilder();
+
+    ProguardMapChecker(StringConsumer inner) {
+      if (!InternalOptions.assertionsEnabled()) {
+        // Make sure we never get here without assertions enabled.
+        throw new Unreachable();
+      }
+      this.inner = inner;
+    }
+
+    @Override
+    public void accept(String string, DiagnosticsHandler handler) {
+      inner.accept(string, handler);
+      contents.append(string);
+    }
+
+    @Override
+    public void finished(DiagnosticsHandler handler) {
+      inner.finished(handler);
+      assert validateProguardMapParses(contents.toString());
+    }
+
+    private static boolean validateProguardMapParses(String content) {
+      try {
+        ClassNameMapper.mapperFromString(content);
+      } catch (IOException e) {
+        e.printStackTrace();
+        return false;
+      }
+      return true;
+    }
   }
 }
diff --git a/src/main/java/com/android/tools/r8/optimize/BridgeHoisting.java b/src/main/java/com/android/tools/r8/optimize/BridgeHoisting.java
index d4a5b7e..8e5c3c3 100644
--- a/src/main/java/com/android/tools/r8/optimize/BridgeHoisting.java
+++ b/src/main/java/com/android/tools/r8/optimize/BridgeHoisting.java
@@ -202,8 +202,8 @@
     // Move the bridge method to the super class, and record this in the graph lens.
     DexMethod newMethod =
         appView.dexItemFactory().createMethod(clazz.type, method.proto, method.name);
-    clazz.addVirtualMethod(representative.getMethod().toTypeSubstitutedMethod(newMethod));
-    lensBuilder.move(representative.getMethod().method, newMethod);
+    clazz.addVirtualMethod(representative.getDefinition().toTypeSubstitutedMethod(newMethod));
+    lensBuilder.move(representative.getDefinition().method, newMethod);
 
     // Remove all of the bridges in the subclasses.
     for (DexProgramClass subclass : subclasses) {
@@ -227,7 +227,7 @@
     if (clazz.type.isSamePackage(representative.getHolder().type)) {
       return false;
     }
-    return !representative.getMethod().isPublic();
+    return !representative.getDefinition().isPublic();
   }
 
   static class BridgeHoistingLens extends NestedGraphLense {
diff --git a/src/main/java/com/android/tools/r8/references/PackageReference.java b/src/main/java/com/android/tools/r8/references/PackageReference.java
new file mode 100644
index 0000000..1c54921
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/references/PackageReference.java
@@ -0,0 +1,47 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.references;
+
+import com.android.tools.r8.Keep;
+import com.android.tools.r8.utils.DescriptorUtils;
+import java.util.Objects;
+
+/** Reference to a package. */
+@Keep
+public class PackageReference {
+
+  private final String packageName;
+
+  PackageReference(String packageName) {
+    if (packageName == null) {
+      throw new IllegalArgumentException("Package name cannot be null.");
+    }
+    if (!packageName.isEmpty() && !DescriptorUtils.isValidJavaType(packageName)) {
+      throw new IllegalArgumentException("Package name '" + packageName + "' is not valid.");
+    }
+    this.packageName = packageName;
+  }
+
+  public String getPackageName() {
+    return packageName;
+  }
+
+  @Override
+  public boolean equals(Object o) {
+    if (this == o) {
+      return true;
+    }
+    if (o == null || getClass() != o.getClass()) {
+      return false;
+    }
+    PackageReference that = (PackageReference) o;
+    return packageName.equals(that.packageName);
+  }
+
+  @Override
+  public int hashCode() {
+    return Objects.hash(packageName);
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/references/Reference.java b/src/main/java/com/android/tools/r8/references/Reference.java
index dcf8c68..1071cbd 100644
--- a/src/main/java/com/android/tools/r8/references/Reference.java
+++ b/src/main/java/com/android/tools/r8/references/Reference.java
@@ -22,7 +22,7 @@
  * that allocation is reduced and equality is constant time. Internally, the objects are weakly
  * stored to avoid memory pressure.
  *
- * <p>All reference objects are immutable and can be compared for equality using physical identity.
+ * <p>No guarantees are made on identity and all references must be compared by {@code equals}.
  */
 @Keep
 public final class Reference {
@@ -201,4 +201,15 @@
     Class<?> fieldType = field.getType();
     return field(classFromClass(holderClass), fieldName, typeFromClass(fieldType));
   }
+
+  /** Create a package reference from a string */
+  public static PackageReference packageFromString(String packageName) {
+    // Note, we rely on equality check for packages and do not canonicalize them.
+    return new PackageReference(packageName);
+  }
+
+  /** Create a package from a java.lang.Package */
+  public static PackageReference packageFromPackage(Package pkg) {
+    return new PackageReference(pkg.getName());
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/relocator/Relocator.java b/src/main/java/com/android/tools/r8/relocator/Relocator.java
new file mode 100644
index 0000000..a4caad6
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/relocator/Relocator.java
@@ -0,0 +1,114 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.relocator;
+
+import static com.android.tools.r8.utils.ExceptionUtils.unwrapExecutionException;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.Keep;
+import com.android.tools.r8.dex.ApplicationReader;
+import com.android.tools.r8.dex.Marker;
+import com.android.tools.r8.dex.Marker.Tool;
+import com.android.tools.r8.graph.AppInfo;
+import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
+import com.android.tools.r8.graph.AppServices;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexApplication;
+import com.android.tools.r8.graph.GraphLense;
+import com.android.tools.r8.jar.CfApplicationWriter;
+import com.android.tools.r8.naming.NamingLens;
+import com.android.tools.r8.naming.signature.GenericSignatureRewriter;
+import com.android.tools.r8.utils.AndroidApp;
+import com.android.tools.r8.utils.ExceptionUtils;
+import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.ThreadUtils;
+import com.android.tools.r8.utils.Timing;
+import java.io.IOException;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+
+@Keep
+public class Relocator {
+
+  private Relocator() {}
+
+  /**
+   * Main API entry for Relocator.
+   *
+   * @param command Relocator command.
+   */
+  public static void run(RelocatorCommand command) throws CompilationFailedException {
+    AndroidApp app = command.getApp();
+    InternalOptions options = command.getInternalOptions();
+    ExecutorService executor = ThreadUtils.getExecutorService(options);
+    ExceptionUtils.withCompilationHandler(
+        command.getReporter(),
+        () -> {
+          try {
+            run(command, executor, app, options);
+          } finally {
+            executor.shutdown();
+          }
+        });
+  }
+
+  /**
+   * Main API entry for Relocator with a externally supplied executor service.
+   *
+   * @param command Relocator command.
+   * @param executor executor service from which to get threads for multi-threaded processing.
+   */
+  public static void run(RelocatorCommand command, ExecutorService executor)
+      throws CompilationFailedException {
+    AndroidApp app = command.getApp();
+    InternalOptions options = command.getInternalOptions();
+    ExceptionUtils.withCompilationHandler(
+        command.getReporter(),
+        () -> {
+          run(command, executor, app, options);
+        });
+  }
+
+  private static void run(
+      RelocatorCommand command,
+      ExecutorService executor,
+      AndroidApp inputApp,
+      InternalOptions options)
+      throws IOException {
+    Timing timing = Timing.create("Relocator", options);
+    try {
+      DexApplication app = new ApplicationReader(inputApp, options, timing).read(executor);
+
+      AppInfo appInfo = new AppInfoWithClassHierarchy(app);
+      AppView<?> appView = AppView.createForRelocator(appInfo, options);
+      appView.setAppServices(AppServices.builder(appView).build());
+
+      SimplePackagesRewritingMapper packageRemapper = new SimplePackagesRewritingMapper(appView);
+      NamingLens namingLens = packageRemapper.compute(command.getMapping());
+
+      new GenericSignatureRewriter(appView, packageRemapper.getTypeMappings())
+          .run(appInfo.classes(), executor);
+
+      new CfApplicationWriter(
+              app,
+              appView,
+              options,
+              new Marker(Tool.Relocator),
+              GraphLense.getIdentityLense(),
+              namingLens,
+              null)
+          .write(command.getConsumer());
+      options.printWarnings();
+    } catch (ExecutionException e) {
+      throw unwrapExecutionException(e);
+    } finally {
+      options.signalFinishedToConsumers();
+      // Dump timings.
+      if (options.printTimes) {
+        timing.report();
+      }
+    }
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/relocator/RelocatorCommand.java b/src/main/java/com/android/tools/r8/relocator/RelocatorCommand.java
new file mode 100644
index 0000000..e329c4d
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/relocator/RelocatorCommand.java
@@ -0,0 +1,421 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.relocator;
+
+import com.android.tools.r8.BaseCompilerCommandParser;
+import com.android.tools.r8.ClassFileConsumer;
+import com.android.tools.r8.ClassFileConsumer.ArchiveConsumer;
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.Diagnostic;
+import com.android.tools.r8.DiagnosticsHandler;
+import com.android.tools.r8.Keep;
+import com.android.tools.r8.errors.CompilationError;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.origin.PathOrigin;
+import com.android.tools.r8.references.PackageReference;
+import com.android.tools.r8.references.Reference;
+import com.android.tools.r8.shaking.ProguardConfiguration;
+import com.android.tools.r8.shaking.ProguardPathList;
+import com.android.tools.r8.utils.AbortException;
+import com.android.tools.r8.utils.AndroidApp;
+import com.android.tools.r8.utils.ExceptionDiagnostic;
+import com.android.tools.r8.utils.FlagFile;
+import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.Reporter;
+import com.android.tools.r8.utils.StringDiagnostic;
+import com.android.tools.r8.utils.ThreadUtils;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Iterables;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Map;
+import java.util.Set;
+
+@Keep
+public class RelocatorCommand {
+
+  private static final String THREAD_COUNT_FLAG = "--thread-count";
+
+  private static final Set<String> OPTIONS_WITH_PARAMETER =
+      ImmutableSet.of("--output", "--input", "--map", THREAD_COUNT_FLAG);
+
+  static final String USAGE_MESSAGE =
+      String.join(
+          "\n",
+          Iterables.concat(
+              Arrays.asList(
+                  "The Relocator CLI is EXPERIMENTAL and is subject to change",
+                  "Usage: relocator [options]",
+                  " where options are:",
+                  "  --input <file>          # Input file to remap, class, zip or jar.",
+                  "  --output <file>         # Output result in <outfile>.",
+                  "  --map <from->to>        # Registers a mapping.",
+                  "  --thread-count <number> # A specified number of threads to run with.",
+                  "  --version               # Print the version of d8.",
+                  "  --help                  # Print this message.")));
+
+  private final boolean printHelp;
+  private final boolean printVersion;
+  private final Reporter reporter;
+  private final DexItemFactory factory;
+  private final ClassFileConsumer consumer;
+  private final AndroidApp app;
+  private final ImmutableMap<PackageReference, PackageReference> mapping;
+  private final int threadCount;
+
+  private RelocatorCommand(boolean printHelp, boolean printVersion) {
+    this.printHelp = printHelp;
+    this.printVersion = printVersion;
+    reporter = null;
+    factory = null;
+    consumer = null;
+    app = null;
+    mapping = null;
+    threadCount = ThreadUtils.NOT_SPECIFIED;
+  }
+
+  private RelocatorCommand(
+      ImmutableMap<PackageReference, PackageReference> mapping,
+      AndroidApp app,
+      Reporter reporter,
+      DexItemFactory factory,
+      ClassFileConsumer consumer,
+      int threadCount) {
+    this.printHelp = false;
+    this.printVersion = false;
+    this.mapping = mapping;
+    this.app = app;
+    this.reporter = reporter;
+    this.factory = factory;
+    this.consumer = consumer;
+    this.threadCount = threadCount;
+  }
+
+  public static Builder builder() {
+    return new Builder();
+  }
+
+  public static Builder parse(String[] args, Origin origin) {
+    return Builder.parse(args, origin);
+  }
+
+  public static Builder builder(DiagnosticsHandler diagnosticsHandler) {
+    return new Builder(diagnosticsHandler);
+  }
+
+  public Reporter getReporter() {
+    return reporter;
+  }
+
+  public DexItemFactory getFactory() {
+    return factory;
+  }
+
+  public ClassFileConsumer getConsumer() {
+    return consumer;
+  }
+
+  public int getThreadCount() {
+    return threadCount;
+  }
+
+  public AndroidApp getApp() {
+    return app;
+  }
+
+  public boolean isPrintHelp() {
+    return printHelp;
+  }
+
+  public boolean isPrintVersion() {
+    return printVersion;
+  }
+
+  public InternalOptions getInternalOptions() {
+    // We are using the proguard configuration for adapting resources.
+    InternalOptions options =
+        new InternalOptions(
+            ProguardConfiguration.builder(factory, getReporter())
+                .addKeepAttributePatterns(ImmutableList.of("*"))
+                .addAdaptResourceFilenames(ProguardPathList.builder().addFileName("**").build())
+                .build(),
+            getReporter());
+    assert options.threadCount == ThreadUtils.NOT_SPECIFIED;
+    options.relocatorCompilation = true;
+    options.threadCount = getThreadCount();
+    options.programConsumer = consumer;
+    assert consumer != null;
+    options.dataResourceConsumer = consumer.getDataResourceConsumer();
+    // Set debug to ensure that we are writing all information to the application writer.
+    options.debug = true;
+    // We need to read stack maps since we are not processing anything.
+    options.testing.readInputStackMaps = true;
+    return options;
+  }
+
+  public Map<PackageReference, PackageReference> getMapping() {
+    return mapping;
+  }
+
+  @Keep
+  public static class Builder {
+
+    private final AndroidApp.Builder app;
+    private final Reporter reporter;
+    private final ImmutableMap.Builder<PackageReference, PackageReference> mapping =
+        ImmutableMap.builder();
+    private ClassFileConsumer consumer = null;
+    private int threadCount = ThreadUtils.NOT_SPECIFIED;
+    private boolean printVersion;
+    private boolean printHelp;
+
+    Builder() {
+      this(AndroidApp.builder());
+    }
+
+    Builder(DiagnosticsHandler handler) {
+      this(AndroidApp.builder(new Reporter(handler)));
+    }
+
+    Builder(AndroidApp.Builder builder) {
+      this.app = builder;
+      this.reporter = builder.getReporter();
+    }
+
+    /**
+     * Setting output to a path.
+     *
+     * <p>Setting the output path will override any previous set consumer or any previous set output
+     * path.
+     *
+     * @param outputPath Output path to write output to. A null argument will clear the program
+     *     consumer / output.
+     */
+    public Builder setOutputPath(Path outputPath) {
+      if (outputPath == null) {
+        this.consumer = null;
+        return this;
+      }
+      this.consumer = new ArchiveConsumer(outputPath, true);
+      return this;
+    }
+
+    public Builder setPrintHelp(boolean printHelp) {
+      this.printHelp = printHelp;
+      return this;
+    }
+
+    public Builder setPrintVersion(boolean printVersion) {
+      this.printVersion = printVersion;
+      return this;
+    }
+
+    /** Signal an error. */
+    public void error(Diagnostic diagnostic) {
+      reporter.error(diagnostic);
+    }
+
+    /** Set the number of threads to use for the compilation */
+    public Builder setThreadCount(int threadCount) {
+      if (threadCount <= 0) {
+        reporter.error("Invalid threadCount: " + threadCount);
+      } else {
+        this.threadCount = threadCount;
+      }
+      return this;
+    }
+
+    /** Add program file resources. */
+    public Builder addProgramFiles(Path... files) {
+      return addProgramFiles(Arrays.asList(files));
+    }
+
+    /** Add program file resources. */
+    public Builder addProgramFiles(Collection<Path> files) {
+      guard(
+          () -> {
+            for (Path path : files) {
+              try {
+                app.addProgramFile(path);
+              } catch (CompilationError e) {
+                error(new PathOrigin(path), e);
+              }
+            }
+          });
+      return this;
+    }
+
+    /** Add program file resource. */
+    public Builder addProgramFile(Path file) {
+      guard(
+          () -> {
+            try {
+              app.addProgramFile(file);
+            } catch (CompilationError e) {
+              error(new PathOrigin(file), e);
+            }
+          });
+      return this;
+    }
+
+    public Builder addPackageMapping(PackageReference source, PackageReference destination) {
+      mapping.put(source, destination);
+      return this;
+    }
+
+    /**
+     * Set the program consumer.
+     *
+     * <p>Setting the program consumer will override any previous set consumer or any previous set
+     * output path.
+     *
+     * @param consumer ClassFile consumer to set as current. A null argument will clear the program
+     *     consumer / output.
+     */
+    public Builder setConsumer(ClassFileConsumer consumer) {
+      // Setting an explicit program consumer resets any output-path/mode setup.
+      this.consumer = consumer;
+      return this;
+    }
+
+    private void validate() {
+      if (consumer == null) {
+        reporter.error(new StringDiagnostic("No output path or consumer has been specified"));
+      }
+    }
+
+    public RelocatorCommand build() throws CompilationFailedException {
+      try {
+        if (printHelp || printVersion) {
+          return new RelocatorCommand(printHelp, printVersion);
+        }
+        reporter.failIfPendingErrors();
+        validate();
+        reporter.failIfPendingErrors();
+        DexItemFactory factory = new DexItemFactory();
+        return new RelocatorCommand(
+            mapping.build(), app.build(), reporter, factory, consumer, threadCount);
+      } catch (AbortException e) {
+        throw new CompilationFailedException(e);
+      }
+    }
+
+    // Helper to signify an error.
+    void error(Origin origin, Throwable throwable) {
+      reporter.error(new ExceptionDiagnostic(throwable, origin));
+    }
+
+    // Helper to guard and handle exceptions.
+    void guard(Runnable action) {
+      try {
+        action.run();
+      } catch (CompilationError e) {
+        reporter.error(e.toStringDiagnostic());
+      } catch (AbortException e) {
+        // Error was reported and exception will be thrown by build.
+      }
+    }
+
+    /**
+     * Parse the Relocator command-line.
+     *
+     * <p>Parsing will set the supplied options or their default value if they have any.
+     *
+     * @param args Command-line arguments array.
+     * @param origin Origin description of the command-line arguments.
+     * @return Relocator command builder with state set up according to parsed command line.
+     */
+    public static Builder parse(String[] args, Origin origin) {
+      return parse(args, origin, RelocatorCommand.builder());
+    }
+
+    /**
+     * Parse the Relocator command-line.
+     *
+     * <p>Parsing will set the supplied options or their default value if they have any.
+     *
+     * @param args Command-line arguments array.
+     * @param origin Origin description of the command-line arguments.
+     * @param handler Custom defined diagnostics handler.
+     * @return Relocator command builder with state set up according to parsed command line.
+     */
+    public static Builder parse(String[] args, Origin origin, DiagnosticsHandler handler) {
+      return parse(args, origin, RelocatorCommand.builder(handler));
+    }
+
+    private static Builder parse(String[] args, Origin origin, Builder builder) {
+      String[] expandedArgs = FlagFile.expandFlagFiles(args, builder::error);
+      Path outputPath = null;
+      for (int i = 0; i < expandedArgs.length; i++) {
+        String arg = expandedArgs[i].trim();
+        String nextArg = null;
+        if (OPTIONS_WITH_PARAMETER.contains(arg)) {
+          if (++i < expandedArgs.length) {
+            nextArg = expandedArgs[i];
+          } else {
+            builder.error(
+                new StringDiagnostic("Missing parameter for " + expandedArgs[i - 1] + ".", origin));
+            break;
+          }
+        }
+        if (arg.length() == 0) {
+          continue;
+        }
+        switch (arg) {
+          case "--help":
+            builder.setPrintHelp(true);
+            break;
+          case "--version":
+            builder.setPrintVersion(true);
+            break;
+          case "--output":
+            assert nextArg != null;
+            if (outputPath != null) {
+              builder.error(
+                  new StringDiagnostic(
+                      "Cannot output both to '" + outputPath.toString() + "' and '" + nextArg + "'",
+                      origin));
+              continue;
+            }
+            outputPath = Paths.get(nextArg);
+            break;
+          case "--input":
+            assert nextArg != null;
+            builder.addProgramFile(Paths.get(nextArg));
+            break;
+          case THREAD_COUNT_FLAG:
+            BaseCompilerCommandParser.parsePositiveIntArgument(
+                builder::error, arg, nextArg, origin, builder::setThreadCount);
+            break;
+          case "--map":
+            assert nextArg != null;
+            int separator = nextArg.indexOf("->");
+            if (separator < 0) {
+              builder.error(
+                  new StringDiagnostic("--map " + nextArg + " is not on the form from->to"));
+              continue;
+            }
+            // TODO(b/155047633): Handle invalid package names.
+            builder.addPackageMapping(
+                Reference.packageFromString(nextArg.substring(0, separator)),
+                Reference.packageFromString(nextArg.substring(separator + 2)));
+            break;
+          default:
+            builder.error(new StringDiagnostic("Unknown argument: " + arg, origin));
+        }
+      }
+      if (outputPath == null) {
+        outputPath = Paths.get(".");
+      }
+      builder.setOutputPath(outputPath);
+      return builder;
+    }
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/relocator/RelocatorCommandLine.java b/src/main/java/com/android/tools/r8/relocator/RelocatorCommandLine.java
new file mode 100644
index 0000000..f255aee
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/relocator/RelocatorCommandLine.java
@@ -0,0 +1,43 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.relocator;
+
+import static com.android.tools.r8.relocator.RelocatorCommand.USAGE_MESSAGE;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.Version;
+import com.android.tools.r8.origin.CommandLineOrigin;
+import com.android.tools.r8.utils.ExceptionUtils;
+
+public class RelocatorCommandLine {
+
+  /**
+   * EXPERIMENTAL - The CLI is subject to change. Command-line entry to Relocator.
+   *
+   * <p>See {@link RelocatorCommand#USAGE_MESSAGE} or run {@code relocator --help} for usage
+   * information.
+   */
+  public static void main(String[] args) {
+    if (args.length == 0) {
+      System.err.println(USAGE_MESSAGE);
+      System.exit(ExceptionUtils.STATUS_ERROR);
+    }
+    ExceptionUtils.withMainProgramHandler(() -> run(args));
+  }
+
+  static void run(String[] args) throws CompilationFailedException {
+    RelocatorCommand command =
+        RelocatorCommand.Builder.parse(args, CommandLineOrigin.INSTANCE).build();
+    if (command.isPrintHelp()) {
+      System.out.println(USAGE_MESSAGE);
+      return;
+    }
+    if (command.isPrintVersion()) {
+      System.out.println("Relocator " + Version.getVersionString());
+      return;
+    }
+    Relocator.run(command);
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/relocator/RelocatorDiagnostic.java b/src/main/java/com/android/tools/r8/relocator/RelocatorDiagnostic.java
new file mode 100644
index 0000000..eeff5fa
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/relocator/RelocatorDiagnostic.java
@@ -0,0 +1,39 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.relocator;
+
+import com.android.tools.r8.Diagnostic;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.position.Position;
+
+class RelocatorDiagnostic implements Diagnostic {
+
+  private final String message;
+
+  public RelocatorDiagnostic(String message) {
+    this.message = message;
+  }
+
+  @Override
+  public Origin getOrigin() {
+    return Origin.unknown();
+  }
+
+  @Override
+  public Position getPosition() {
+    return Position.UNKNOWN;
+  }
+
+  @Override
+  public String getDiagnosticMessage() {
+    return message;
+  }
+
+  static RelocatorDiagnostic typeRelocateAmbiguous(DexType type) {
+    return new RelocatorDiagnostic(
+        "Type '" + type.toSourceString() + "' can be relocated by multiple mappings.");
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/relocator/SimplePackagesRewritingMapper.java b/src/main/java/com/android/tools/r8/relocator/SimplePackagesRewritingMapper.java
new file mode 100644
index 0000000..c2f5837
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/relocator/SimplePackagesRewritingMapper.java
@@ -0,0 +1,172 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.relocator;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexCallSite;
+import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.graph.DexItem;
+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.DexType;
+import com.android.tools.r8.graph.InnerClassAttribute;
+import com.android.tools.r8.naming.NamingLens;
+import com.android.tools.r8.references.PackageReference;
+import com.android.tools.r8.utils.DescriptorUtils;
+import com.android.tools.r8.utils.InternalOptions;
+import com.google.common.collect.ImmutableMap;
+import java.util.IdentityHashMap;
+import java.util.Map;
+import java.util.function.Consumer;
+import java.util.function.Function;
+import java.util.function.Predicate;
+
+class SimplePackagesRewritingMapper {
+
+  private final AppView<?> appView;
+  private final Map<DexType, DexString> typeMappings = new IdentityHashMap<>();
+
+  public SimplePackagesRewritingMapper(AppView<?> appView) {
+    this.appView = appView;
+  }
+
+  public NamingLens compute(Map<PackageReference, PackageReference> mapping) {
+    ImmutableMap.Builder<String, String> packingMappings = ImmutableMap.builder();
+    for (PackageReference key : mapping.keySet()) {
+      String source = key.getPackageName();
+      String target = mapping.get(key).getPackageName();
+      if (source.equals(target)) {
+        // No need for relocating identities.
+        continue;
+      }
+      if (source.isEmpty()) {
+        assert !target.isEmpty();
+        target = target + DescriptorUtils.JAVA_PACKAGE_SEPARATOR;
+      }
+      String sourceBinary = DescriptorUtils.getBinaryNameFromJavaType(source);
+      String targetBinary = DescriptorUtils.getBinaryNameFromJavaType(target);
+      packingMappings.put(sourceBinary, targetBinary);
+      DexString sourceDescriptor = appView.dexItemFactory().createString("L" + sourceBinary);
+      DexString targetDescriptor = appView.dexItemFactory().createString("L" + targetBinary);
+      for (DexProgramClass clazz : appView.appInfo().classes()) {
+        DexString descriptor = clazz.type.descriptor;
+        // Check if descriptor can be a prefix.
+        if (descriptor.size <= sourceDescriptor.size) {
+          continue;
+        }
+        // Check if it is either the empty prefix or a fully qualified package.
+        if (sourceDescriptor.size != 1
+            && descriptor.content[sourceDescriptor.size]
+                != DescriptorUtils.DESCRIPTOR_PACKAGE_SEPARATOR) {
+          continue;
+        }
+        // Do a char-by-char comparison of the prefix.
+        if (!descriptor.startsWith(sourceDescriptor)) {
+          continue;
+        }
+        // This type should be mapped.
+        if (typeMappings.containsKey(clazz.type)) {
+          appView.options().reporter.error(RelocatorDiagnostic.typeRelocateAmbiguous(clazz.type));
+          appView.options().reporter.failIfPendingErrors();
+        }
+        DexString relocatedDescriptor =
+            clazz.type.descriptor.withNewPrefix(
+                sourceDescriptor, targetDescriptor, appView.dexItemFactory());
+        typeMappings.put(clazz.type, relocatedDescriptor);
+      }
+    }
+
+    return new RelocatorNamingLens(typeMappings, packingMappings.build(), appView.dexItemFactory());
+  }
+
+  Map<DexType, DexString> getTypeMappings() {
+    return typeMappings;
+  }
+
+  private static class RelocatorNamingLens extends NamingLens {
+
+    private final Map<DexType, DexString> typeMappings;
+    private final Map<String, String> packageMappings;
+    private final DexItemFactory factory;
+
+    private RelocatorNamingLens(
+        Map<DexType, DexString> typeMappings,
+        Map<String, String> packageMappings,
+        DexItemFactory factory) {
+      this.typeMappings = typeMappings;
+      this.packageMappings = packageMappings;
+      this.factory = factory;
+    }
+
+    @Override
+    public String lookupPackageName(String packageName) {
+      return packageMappings.getOrDefault(packageName, packageName);
+    }
+
+    @Override
+    public DexString lookupDescriptor(DexType type) {
+      if (type.isPrimitiveType() || type.isVoidType()) {
+        return type.descriptor;
+      }
+      if (type.isArrayType()) {
+        DexType baseType = type.toBaseType(factory);
+        if (baseType == null || baseType.isPrimitiveType()) {
+          return type.descriptor;
+        }
+        String baseDescriptor = typeMappings.getOrDefault(baseType, baseType.descriptor).toString();
+        return factory.createString(
+            DescriptorUtils.toArrayDescriptor(
+                type.getNumberOfLeadingSquareBrackets(), baseDescriptor));
+      }
+      return typeMappings.getOrDefault(type, type.descriptor);
+    }
+
+    @Override
+    public DexString lookupInnerName(InnerClassAttribute attribute, InternalOptions options) {
+      return attribute.getInnerName();
+    }
+
+    @Override
+    public DexString lookupName(DexMethod method) {
+      return method.name;
+    }
+
+    @Override
+    public DexString lookupMethodName(DexCallSite callSite) {
+      return callSite.methodName;
+    }
+
+    @Override
+    public DexString lookupName(DexField field) {
+      return field.name;
+    }
+
+    @Override
+    public boolean verifyNoOverlap(Map<DexType, DexString> map) {
+      return true;
+    }
+
+    @Override
+    public void forAllRenamedTypes(Consumer<DexType> consumer) {
+      typeMappings.keySet().forEach(consumer);
+    }
+
+    @Override
+    public <T extends DexItem> Map<String, T> getRenamedItems(
+        Class<T> clazz, Predicate<T> predicate, Function<T, String> namer) {
+      return typeMappings.keySet().stream()
+          .filter(item -> (clazz.isInstance(item) && predicate.test(clazz.cast(item))))
+          .map(clazz::cast)
+          .collect(ImmutableMap.toImmutableMap(namer, i -> i));
+    }
+
+    @Override
+    public boolean verifyRenamingConsistentWithResolution(DexMethod item) {
+      return true;
+    }
+  }
+}
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 0de5218..3f657b9 100644
--- a/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
+++ b/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
@@ -8,7 +8,6 @@
 import static com.android.tools.r8.graph.ResolutionResult.SingleResolutionResult.isOverriding;
 
 import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
-import com.android.tools.r8.graph.AppInfoWithSubtyping;
 import com.android.tools.r8.graph.DexCallSite;
 import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexClassAndMethod;
@@ -71,7 +70,8 @@
 import java.util.stream.Collectors;
 
 /** Encapsulates liveness and reachability information for an application. */
-public class AppInfoWithLiveness extends AppInfoWithSubtyping implements InstantiatedSubTypeInfo {
+public class AppInfoWithLiveness extends AppInfoWithClassHierarchy
+    implements InstantiatedSubTypeInfo {
   /** Set of reachable proto types that will be dead code eliminated. */
   private final Set<DexType> deadProtoTypes;
   /** Set of types that are mentioned in the program, but for which no definition exists. */
@@ -278,7 +278,7 @@
   }
 
   public AppInfoWithLiveness(
-      AppInfoWithSubtyping appInfoWithSubtyping,
+      AppInfoWithClassHierarchy appInfoWithSubtyping,
       Set<DexType> deadProtoTypes,
       Set<DexType> missingTypes,
       Set<DexType> liveTypes,
@@ -1129,7 +1129,7 @@
     SingleResolutionResult resolution =
         resolveMethod(initialResolutionHolder, method).asSingleResolution();
     if (resolution == null
-        || !resolution.isAccessibleForVirtualDispatchFrom(invocationClass, this)) {
+        || resolution.isAccessibleForVirtualDispatchFrom(invocationClass, this).isFalse()) {
       return null;
     }
     // If the method is modeled, return the resolution.
@@ -1158,7 +1158,8 @@
     // TODO(b/148769279): Disable lookup single target on lambda's for now.
     if (resolvedHolder.isInterface()
         && resolvedHolder.isProgramClass()
-        && isInstantiatedInterface(resolvedHolder.asProgramClass())) {
+        && objectAllocationInfoCollection.isImmediateInterfaceOfInstantiatedLambda(
+            resolvedHolder.asProgramClass())) {
       singleTargetLookupCache.addToCache(refinedReceiverType, method, null);
       return null;
     }
@@ -1170,7 +1171,6 @@
         refinedLowerBound = refinedLowerBoundClass.asProgramClass();
         // TODO(b/154822960): Check if the lower bound is a subtype of the upper bound.
         if (refinedLowerBound != null && !isSubtype(refinedLowerBound.type, refinedReceiverType)) {
-          // We cannot trust the lower bound, so null it out.
           refinedLowerBound = null;
         }
       }
@@ -1183,7 +1183,7 @@
     if (lookupResult != null && !lookupResult.isIncomplete()) {
       LookupTarget singleTarget = lookupResult.getSingleLookupTarget();
       if (singleTarget != null && singleTarget.isMethodTarget()) {
-        singleMethodTarget = singleTarget.asMethodTarget().getMethod();
+        singleMethodTarget = singleTarget.asMethodTarget().getDefinition();
       }
     }
     if (receiverLowerBoundType == null) {
@@ -1205,11 +1205,11 @@
       if (refinedReceiverClass.isProgramClass()) {
         DexClassAndMethod clazzAndMethod =
             resolution.lookupVirtualDispatchTarget(refinedReceiverClass.asProgramClass(), this);
-        if (clazzAndMethod == null || isPinned(clazzAndMethod.getMethod().method)) {
+        if (clazzAndMethod == null || isPinned(clazzAndMethod.getDefinition().method)) {
           // TODO(b/150640456): We should maybe only consider program methods.
           return DexEncodedMethod.SENTINEL;
         }
-        return clazzAndMethod.getMethod();
+        return clazzAndMethod.getDefinition();
       } else {
         // TODO(b/150640456): We should maybe only consider program methods.
         // If we resolved to a method on the refined receiver in the library, then we report the
diff --git a/src/main/java/com/android/tools/r8/shaking/ConsequentRootSetBuilder.java b/src/main/java/com/android/tools/r8/shaking/ConsequentRootSetBuilder.java
index 2e5edba..8288bde 100644
--- a/src/main/java/com/android/tools/r8/shaking/ConsequentRootSetBuilder.java
+++ b/src/main/java/com/android/tools/r8/shaking/ConsequentRootSetBuilder.java
@@ -3,15 +3,19 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.shaking;
 
-import com.android.tools.r8.graph.AppInfoWithSubtyping;
+import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
 import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.SubtypingInfo;
 
 class ConsequentRootSetBuilder extends RootSetBuilder {
 
   private final Enqueuer enqueuer;
 
-  ConsequentRootSetBuilder(AppView<? extends AppInfoWithSubtyping> appView, Enqueuer enqueuer) {
-    super(appView, appView.appInfo().app(), null);
+  ConsequentRootSetBuilder(
+      AppView<? extends AppInfoWithClassHierarchy> appView,
+      SubtypingInfo subtypingInfo,
+      Enqueuer enqueuer) {
+    super(appView, subtypingInfo, null);
     this.enqueuer = enqueuer;
   }
 
diff --git a/src/main/java/com/android/tools/r8/shaking/DefaultEnqueuerUseRegistry.java b/src/main/java/com/android/tools/r8/shaking/DefaultEnqueuerUseRegistry.java
index c04ddf5..09aafdb 100644
--- a/src/main/java/com/android/tools/r8/shaking/DefaultEnqueuerUseRegistry.java
+++ b/src/main/java/com/android/tools/r8/shaking/DefaultEnqueuerUseRegistry.java
@@ -36,7 +36,7 @@
   }
 
   public DexEncodedMethod getContextMethod() {
-    return context.getMethod();
+    return context.getDefinition();
   }
 
   @Override
@@ -71,22 +71,22 @@
 
   @Override
   public boolean registerInstanceFieldRead(DexField field) {
-    return enqueuer.traceInstanceFieldRead(field, context.getMethod());
+    return enqueuer.traceInstanceFieldRead(field, context.getDefinition());
   }
 
   @Override
   public boolean registerInstanceFieldReadFromMethodHandle(DexField field) {
-    return enqueuer.traceInstanceFieldReadFromMethodHandle(field, context.getMethod());
+    return enqueuer.traceInstanceFieldReadFromMethodHandle(field, context.getDefinition());
   }
 
   @Override
   public boolean registerInstanceFieldWrite(DexField field) {
-    return enqueuer.traceInstanceFieldWrite(field, context.getMethod());
+    return enqueuer.traceInstanceFieldWrite(field, context.getDefinition());
   }
 
   @Override
   public boolean registerInstanceFieldWriteFromMethodHandle(DexField field) {
-    return enqueuer.traceInstanceFieldWriteFromMethodHandle(field, context.getMethod());
+    return enqueuer.traceInstanceFieldWriteFromMethodHandle(field, context.getDefinition());
   }
 
   @Override
@@ -96,43 +96,43 @@
 
   @Override
   public boolean registerStaticFieldRead(DexField field) {
-    return enqueuer.traceStaticFieldRead(field, context.getMethod());
+    return enqueuer.traceStaticFieldRead(field, context.getDefinition());
   }
 
   @Override
   public boolean registerStaticFieldReadFromMethodHandle(DexField field) {
-    return enqueuer.traceStaticFieldReadFromMethodHandle(field, context.getMethod());
+    return enqueuer.traceStaticFieldReadFromMethodHandle(field, context.getDefinition());
   }
 
   @Override
   public boolean registerStaticFieldWrite(DexField field) {
-    return enqueuer.traceStaticFieldWrite(field, context.getMethod());
+    return enqueuer.traceStaticFieldWrite(field, context.getDefinition());
   }
 
   @Override
   public boolean registerStaticFieldWriteFromMethodHandle(DexField field) {
-    return enqueuer.traceStaticFieldWriteFromMethodHandle(field, context.getMethod());
+    return enqueuer.traceStaticFieldWriteFromMethodHandle(field, context.getDefinition());
   }
 
   @Override
   public boolean registerConstClass(DexType type) {
-    return enqueuer.traceConstClass(type, context.getMethod());
+    return enqueuer.traceConstClass(type, context.getDefinition());
   }
 
   @Override
   public boolean registerCheckCast(DexType type) {
-    return enqueuer.traceCheckCast(type, context.getMethod());
+    return enqueuer.traceCheckCast(type, context.getDefinition());
   }
 
   @Override
   public boolean registerTypeReference(DexType type) {
-    return enqueuer.traceTypeReference(type, context.getMethod());
+    return enqueuer.traceTypeReference(type, context.getDefinition());
   }
 
   @Override
   public void registerMethodHandle(DexMethodHandle methodHandle, MethodHandleUse use) {
     super.registerMethodHandle(methodHandle, use);
-    enqueuer.traceMethodHandle(methodHandle, use, context.getMethod());
+    enqueuer.traceMethodHandle(methodHandle, use, context.getDefinition());
   }
 
   @Override
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 4fb2fe2..a1d6312 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -24,7 +24,6 @@
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.experimental.graphinfo.GraphConsumer;
 import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
-import com.android.tools.r8.graph.AppInfoWithSubtyping;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.CfCode;
 import com.android.tools.r8.graph.DexAnnotation;
@@ -177,9 +176,9 @@
   private Set<EnqueuerInvokeAnalysis> invokeAnalyses = Sets.newIdentityHashSet();
 
   // Don't hold a direct pointer to app info (use appView).
-  private AppInfoWithSubtyping appInfo;
-  private final AppView<AppInfoWithSubtyping> appView;
-  private final SubtypingInfo subtypingInfo;
+  private AppInfoWithClassHierarchy appInfo;
+  private final AppView<AppInfoWithClassHierarchy> appView;
+  private SubtypingInfo subtypingInfo;
   private final InternalOptions options;
   private RootSet rootSet;
   private ProguardClassFilter dontWarnPatterns;
@@ -344,15 +343,15 @@
   private final Set<DexProgramClass> classesWithSerializableLambdas = Sets.newIdentityHashSet();
 
   Enqueuer(
-      AppView<? extends AppInfoWithSubtyping> appView,
+      AppView<? extends AppInfoWithClassHierarchy> appView,
+      SubtypingInfo subtypingInfo,
       GraphConsumer keptGraphConsumer,
       Mode mode) {
     assert appView.appServices() != null;
     InternalOptions options = appView.options();
     this.appInfo = appView.appInfo();
-    this.appView = appView.withSubtyping();
-    this.subtypingInfo =
-        new SubtypingInfo(appView.appInfo().app().asDirect().allClasses(), appView);
+    this.appView = appView.withClassHierarchy();
+    this.subtypingInfo = subtypingInfo;
     this.forceProguardCompatibility = options.forceProguardCompatibility;
     this.graphReporter = new GraphReporter(appView, keptGraphConsumer);
     this.mode = mode;
@@ -762,20 +761,10 @@
     DexProgramClass contextHolder = context.getHolder();
     LambdaDescriptor descriptor = LambdaDescriptor.tryInfer(callSite, appInfo, contextHolder);
     if (descriptor == null) {
-      if (!isStringConcat(callSite.bootstrapMethod)) {
-        if (options.reporter != null) {
-          Diagnostic message =
-              new StringDiagnostic(
-                  "Unknown bootstrap method " + callSite.bootstrapMethod, contextHolder.origin);
-          options.reporter.warning(message);
-        }
-      }
-
-      callSites.add(callSite);
       return;
     }
 
-    DexEncodedMethod contextMethod = context.getMethod();
+    DexEncodedMethod contextMethod = context.getDefinition();
     if (lambdaRewriter != null) {
       assert contextMethod.getCode().isCfCode() : "Unexpected input type with lambdas";
       CfCode code = contextMethod.getCode().asCfCode();
@@ -879,7 +868,7 @@
       initClassReferences.put(
           type, computeMinimumRequiredVisibilityForInitClassField(type, currentMethod.getHolder()));
 
-      markTypeAsLive(type, classReferencedFromReporter(currentMethod.getMethod()));
+      markTypeAsLive(type, classReferencedFromReporter(currentMethod.getDefinition()));
       markDirectAndIndirectClassInitializersAsLive(clazz);
       return true;
     }
@@ -956,7 +945,7 @@
 
   boolean traceInvokeDirect(DexMethod invokedMethod, ProgramMethod context) {
     DexProgramClass currentHolder = context.getHolder();
-    DexEncodedMethod currentMethod = context.getMethod();
+    DexEncodedMethod currentMethod = context.getDefinition();
     boolean skipTracing =
         registerDeferredActionForDeadProtoBuilder(
             invokedMethod.holder,
@@ -989,12 +978,12 @@
 
   boolean traceInvokeDirectFromLambda(DexMethod invokedMethod, ProgramMethod context) {
     return traceInvokeDirect(
-        invokedMethod, context, KeepReason.invokedFromLambdaCreatedIn(context.getMethod()));
+        invokedMethod, context, KeepReason.invokedFromLambdaCreatedIn(context.getDefinition()));
   }
 
   private boolean traceInvokeDirect(
       DexMethod invokedMethod, ProgramMethod context, KeepReason reason) {
-    DexEncodedMethod currentMethod = context.getMethod();
+    DexEncodedMethod currentMethod = context.getDefinition();
     if (!registerMethodWithTargetAndContext(directInvokes, invokedMethod, currentMethod)) {
       return false;
     }
@@ -1012,12 +1001,12 @@
 
   boolean traceInvokeInterfaceFromLambda(DexMethod invokedMethod, ProgramMethod context) {
     return traceInvokeInterface(
-        invokedMethod, context, KeepReason.invokedFromLambdaCreatedIn(context.getMethod()));
+        invokedMethod, context, KeepReason.invokedFromLambdaCreatedIn(context.getDefinition()));
   }
 
   private boolean traceInvokeInterface(
       DexMethod method, ProgramMethod context, KeepReason keepReason) {
-    DexEncodedMethod currentMethod = context.getMethod();
+    DexEncodedMethod currentMethod = context.getDefinition();
     if (!registerMethodWithTargetAndContext(interfaceInvokes, method, currentMethod)) {
       return false;
     }
@@ -1035,12 +1024,12 @@
 
   boolean traceInvokeStaticFromLambda(DexMethod invokedMethod, ProgramMethod context) {
     return traceInvokeStatic(
-        invokedMethod, context, KeepReason.invokedFromLambdaCreatedIn(context.getMethod()));
+        invokedMethod, context, KeepReason.invokedFromLambdaCreatedIn(context.getDefinition()));
   }
 
   private boolean traceInvokeStatic(
       DexMethod invokedMethod, ProgramMethod context, KeepReason reason) {
-    DexEncodedMethod currentMethod = context.getMethod();
+    DexEncodedMethod currentMethod = context.getDefinition();
     DexItemFactory dexItemFactory = appView.dexItemFactory();
     if (dexItemFactory.classMethods.isReflectiveClassLookup(invokedMethod)
         || dexItemFactory.atomicFieldUpdaterMethods.isFieldUpdater(invokedMethod)) {
@@ -1072,7 +1061,7 @@
   }
 
   boolean traceInvokeSuper(DexMethod invokedMethod, ProgramMethod context) {
-    DexEncodedMethod currentMethod = context.getMethod();
+    DexEncodedMethod currentMethod = context.getDefinition();
     // We have to revisit super invokes based on the context they are found in. The same
     // method descriptor will hit different targets, depending on the context it is used in.
     DexMethod actualTarget = getInvokeSuperTarget(invokedMethod, currentMethod);
@@ -1093,21 +1082,22 @@
 
   boolean traceInvokeVirtualFromLambda(DexMethod invokedMethod, ProgramMethod context) {
     return traceInvokeVirtual(
-        invokedMethod, context, KeepReason.invokedFromLambdaCreatedIn(context.getMethod()));
+        invokedMethod, context, KeepReason.invokedFromLambdaCreatedIn(context.getDefinition()));
   }
 
   private boolean traceInvokeVirtual(
       DexMethod invokedMethod, ProgramMethod context, KeepReason reason) {
     if (invokedMethod == appView.dexItemFactory().classMethods.newInstance
         || invokedMethod == appView.dexItemFactory().constructorMethods.newInstance) {
-      pendingReflectiveUses.add(context.getMethod());
+      pendingReflectiveUses.add(context.getDefinition());
     } else if (appView.dexItemFactory().classMethods.isReflectiveMemberLookup(invokedMethod)) {
       // Implicitly add -identifiernamestring rule for the Java reflection in use.
       identifierNameStrings.add(invokedMethod);
       // Revisit the current method to implicitly add -keep rule for items with reflective access.
-      pendingReflectiveUses.add(context.getMethod());
+      pendingReflectiveUses.add(context.getDefinition());
     }
-    if (!registerMethodWithTargetAndContext(virtualInvokes, invokedMethod, context.getMethod())) {
+    if (!registerMethodWithTargetAndContext(
+        virtualInvokes, invokedMethod, context.getDefinition())) {
       return false;
     }
     if (Log.ENABLED) {
@@ -1119,7 +1109,7 @@
   }
 
   boolean traceNewInstance(DexType type, ProgramMethod context) {
-    DexEncodedMethod currentMethod = context.getMethod();
+    DexEncodedMethod currentMethod = context.getDefinition();
     boolean skipTracing =
         registerDeferredActionForDeadProtoBuilder(
             type, currentMethod, () -> workList.enqueueTraceNewInstanceAction(type, context));
@@ -1140,7 +1130,7 @@
         type,
         context,
         InstantiationReason.LAMBDA,
-        KeepReason.invokedFromLambdaCreatedIn(context.getMethod()));
+        KeepReason.invokedFromLambdaCreatedIn(context.getDefinition()));
   }
 
   private boolean traceNewInstance(
@@ -1148,7 +1138,7 @@
       ProgramMethod context,
       InstantiationReason instantiationReason,
       KeepReason keepReason) {
-    DexEncodedMethod currentMethod = context.getMethod();
+    DexEncodedMethod currentMethod = context.getDefinition();
     DexProgramClass clazz = getProgramClassOrNull(type);
     if (clazz != null) {
       if (clazz.isAnnotation() || clazz.isInterface()) {
@@ -1938,12 +1928,6 @@
     for (DexType iface : descriptor.interfaces) {
       checkLambdaInterface(iface, context);
       objectAllocationInfoCollection.recordInstantiatedLambdaInterface(iface, descriptor, appInfo);
-      // TODO(b/150277553): Lambdas should be accurately traced and thus not be added here.
-      assert lambdaRewriter == null;
-      DexProgramClass clazz = getProgramClassOrNull(iface);
-      if (clazz != null) {
-        objectAllocationInfoCollection.recordInstantiatedInterface(clazz, appInfo);
-      }
     }
   }
 
@@ -2054,26 +2038,26 @@
     // The validity of the reachable method is checked at the point it becomes "reachable" and is
     // resolved. If the method is private, then the dispatch is not "virtual" and the method is
     // simply marked live on its holder.
-    if (resolutionMethod.getMethod().isPrivateMethod()) {
+    if (resolutionMethod.getDefinition().isPrivateMethod()) {
       markVirtualMethodAsLive(
           resolutionMethod.getHolder(),
-          resolutionMethod.getMethod(),
+          resolutionMethod.getDefinition(),
           graphReporter.reportReachableMethodAsLive(
-              resolutionMethod.getMethod().method, resolutionMethod));
+              resolutionMethod.getDefinition().method, resolutionMethod));
       return;
     }
     // Otherwise, we set the initial holder type to be the holder of the reachable method, which
     // ensures that access will be generally valid.
     SingleResolutionResult resolution =
         new SingleResolutionResult(
-            initialHolder, resolutionMethod.getHolder(), resolutionMethod.getMethod());
+            initialHolder, resolutionMethod.getHolder(), resolutionMethod.getDefinition());
     LookupTarget lookup = resolution.lookupVirtualDispatchTarget(instantiation, appInfo);
     if (lookup != null) {
       markVirtualDispatchTargetAsLive(
           lookup,
           programMethod ->
               graphReporter.reportReachableMethodAsLive(
-                  resolutionMethod.getMethod().method, programMethod));
+                  resolutionMethod.getDefinition().method, programMethod));
     }
   }
 
@@ -2131,7 +2115,7 @@
     if (instantiation.isClass()) {
       // TODO(b/149976493): We need to mark these for lambdas too!
       markOverridesAsLibraryMethodOverrides(
-          instantiation.asClass(), lookup.asMethodTarget().getMethod().method);
+          instantiation.asClass(), lookup.asMethodTarget().getDefinition().method);
     }
   }
 
@@ -2453,7 +2437,8 @@
     markMethodAsTargeted(resolvedHolder, resolvedMethod, reason);
 
     DexProgramClass context = contextOrNull == null ? null : contextOrNull.getHolder();
-    if (contextOrNull != null && !resolution.isAccessibleForVirtualDispatchFrom(context, appInfo)) {
+    if (contextOrNull != null
+        && resolution.isAccessibleForVirtualDispatchFrom(context, appInfo).isFalse()) {
       // Not accessible from this context, so this call will cause a runtime exception.
       return;
     }
@@ -2498,9 +2483,9 @@
   private void markVirtualDispatchTargetAsLive(
       DexClassAndMethod target, Function<ProgramMethod, KeepReasonWitness> reason) {
     ProgramMethod programMethod = target.asProgramMethod();
-    if (programMethod != null && !programMethod.getMethod().isAbstract()) {
+    if (programMethod != null && !programMethod.getDefinition().isAbstract()) {
       markVirtualMethodAsLive(
-          programMethod.getHolder(), programMethod.getMethod(), reason.apply(programMethod));
+          programMethod.getHolder(), programMethod.getDefinition(), reason.apply(programMethod));
     }
   }
 
@@ -2510,7 +2495,7 @@
     if (implementationMethod != null) {
       enqueueMarkMethodLiveAction(
           implementationMethod.getHolder(),
-          implementationMethod.getMethod(),
+          implementationMethod.getDefinition(),
           reason.apply(implementationMethod));
     }
   }
@@ -2678,14 +2663,14 @@
     }
 
     void addLiveMethod(ProgramMethod method) {
-      DexMethod signature = method.getMethod().method;
+      DexMethod signature = method.getDefinition().method;
       assert !liveMethods.containsKey(signature);
       liveMethods.put(signature, method);
     }
 
     void addLiveAndPinnedMethod(ProgramMethod method) {
       addLiveMethod(method);
-      pinnedMethods.add(method.getMethod().method);
+      pinnedMethods.add(method.getDefinition().method);
     }
 
     void amendApplication(Builder appBuilder) {
@@ -2714,9 +2699,9 @@
             fakeReason);
       }
       for (ProgramMethod liveMethod : liveMethods.values()) {
-        assert !enqueuer.targetedMethods.contains(liveMethod.getMethod());
+        assert !enqueuer.targetedMethods.contains(liveMethod.getDefinition());
         DexProgramClass holder = liveMethod.getHolder();
-        DexEncodedMethod method = liveMethod.getMethod();
+        DexEncodedMethod method = liveMethod.getDefinition();
         enqueuer.markMethodAsTargeted(holder, method, fakeReason);
         enqueuer.enqueueMarkMethodLiveAction(holder, method, fakeReason);
       }
@@ -2741,8 +2726,10 @@
     // Now all additions are computed, the application is atomically extended with those additions.
     Builder appBuilder = appInfo.app().asDirect().builder();
     additions.amendApplication(appBuilder);
-    appInfo = new AppInfoWithSubtyping(appBuilder.build());
+    DirectMappedDexApplication app = appBuilder.build();
+    appInfo = new AppInfoWithClassHierarchy(app);
     appView.setAppInfo(appInfo);
+    subtypingInfo = new SubtypingInfo(app.allClasses(), app);
 
     // Finally once all synthesized items "exist" it is now safe to continue tracing. The new work
     // items are enqueued and the fixed point will continue once this subroutine returns.
@@ -2752,7 +2739,7 @@
   private void synthesizeInterfaceMethodBridges(SyntheticAdditions additions) {
     for (ProgramMethod bridge : syntheticInterfaceMethodBridges.values()) {
       DexProgramClass holder = bridge.getHolder();
-      DexEncodedMethod method = bridge.getMethod();
+      DexEncodedMethod method = bridge.getDefinition();
       holder.addVirtualMethod(method);
       additions.addLiveAndPinnedMethod(bridge);
     }
@@ -2811,7 +2798,7 @@
     return true;
   }
 
-  private AppInfoWithLiveness createAppInfo(AppInfoWithSubtyping appInfo) {
+  private AppInfoWithLiveness createAppInfo(AppInfoWithClassHierarchy appInfo) {
     // Once all tracing is done, we generate accessor methods for lambdas.
     // These are assumed to be simple forwarding or access flag updates, thus no further tracing
     // is needed. These cannot be generated as part of lambda synthesis as changing a direct method
@@ -3127,7 +3114,7 @@
             }
           }
           ConsequentRootSetBuilder consequentSetBuilder =
-              new ConsequentRootSetBuilder(appView, this);
+              new ConsequentRootSetBuilder(appView, subtypingInfo, this);
           IfRuleEvaluator ifRuleEvaluator =
               new IfRuleEvaluator(
                   appView,
@@ -3239,7 +3226,7 @@
   }
 
   private ConsequentRootSet computeDelayedInterfaceMethodSyntheticBridges() {
-    RootSetBuilder builder = new RootSetBuilder(appView);
+    RootSetBuilder builder = new RootSetBuilder(appView, subtypingInfo);
     for (DelayedRootSetActionItem delayedRootSetActionItem : rootSet.delayedRootSetActionItems) {
       if (delayedRootSetActionItem.isInterfaceMethodSyntheticBridgeAction()) {
         handleInterfaceMethodSyntheticBridgeAction(
@@ -3255,17 +3242,17 @@
       InterfaceMethodSyntheticBridgeAction action, RootSetBuilder builder) {
     ProgramMethod methodToKeep = action.getMethodToKeep();
     ProgramMethod singleTarget = action.getSingleTarget();
-    DexEncodedMethod singleTargetMethod = singleTarget.getMethod();
+    DexEncodedMethod singleTargetMethod = singleTarget.getDefinition();
     if (rootSet.noShrinking.containsKey(singleTargetMethod.method)) {
       return;
     }
     if (methodToKeep != singleTarget) {
-      assert null == methodToKeep.getHolder().lookupMethod(methodToKeep.getMethod().method);
+      assert null == methodToKeep.getHolder().lookupMethod(methodToKeep.getDefinition().method);
       ProgramMethod old =
-          syntheticInterfaceMethodBridges.put(methodToKeep.getMethod().method, methodToKeep);
+          syntheticInterfaceMethodBridges.put(methodToKeep.getDefinition().method, methodToKeep);
       if (old == null) {
         if (singleTargetMethod.isLibraryMethodOverride().isTrue()) {
-          methodToKeep.getMethod().setLibraryMethodOverride(OptionalBool.TRUE);
+          methodToKeep.getDefinition().setLibraryMethodOverride(OptionalBool.TRUE);
         }
         DexProgramClass singleTargetHolder = singleTarget.getHolder();
         assert singleTargetHolder.isInterface();
@@ -3351,7 +3338,7 @@
       return false;
     }
     DexProgramClass clazz = programMethod.getHolder();
-    DexEncodedMethod method = programMethod.getMethod();
+    DexEncodedMethod method = programMethod.getDefinition();
     assert method.isVirtualMethod();
 
     if (method.isAbstract() || method.isPrivateMethod()) {
diff --git a/src/main/java/com/android/tools/r8/shaking/EnqueuerFactory.java b/src/main/java/com/android/tools/r8/shaking/EnqueuerFactory.java
index a949527..743f0df 100644
--- a/src/main/java/com/android/tools/r8/shaking/EnqueuerFactory.java
+++ b/src/main/java/com/android/tools/r8/shaking/EnqueuerFactory.java
@@ -5,41 +5,49 @@
 package com.android.tools.r8.shaking;
 
 import com.android.tools.r8.experimental.graphinfo.GraphConsumer;
-import com.android.tools.r8.graph.AppInfoWithSubtyping;
+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.SubtypingInfo;
 import com.android.tools.r8.shaking.Enqueuer.Mode;
 import java.util.Set;
 
 public class EnqueuerFactory {
 
   public static Enqueuer createForInitialTreeShaking(
-      AppView<? extends AppInfoWithSubtyping> appView) {
-    return new Enqueuer(appView, null, Mode.INITIAL_TREE_SHAKING);
+      AppView<? extends AppInfoWithClassHierarchy> appView, SubtypingInfo subtypingInfo) {
+    return new Enqueuer(appView, subtypingInfo, null, Mode.INITIAL_TREE_SHAKING);
   }
 
   public static Enqueuer createForFinalTreeShaking(
-      AppView<? extends AppInfoWithSubtyping> appView,
+      AppView<? extends AppInfoWithClassHierarchy> appView,
+      SubtypingInfo subtypingInfo,
       GraphConsumer keptGraphConsumer,
       Set<DexType> initialMissingTypes) {
-    Enqueuer enqueuer = new Enqueuer(appView, keptGraphConsumer, Mode.FINAL_TREE_SHAKING);
+    Enqueuer enqueuer =
+        new Enqueuer(appView, subtypingInfo, keptGraphConsumer, Mode.FINAL_TREE_SHAKING);
     appView.withProtoShrinker(
         shrinker -> enqueuer.setInitialDeadProtoTypes(shrinker.getDeadProtoTypes()));
     enqueuer.setInitialMissingTypes(initialMissingTypes);
     return enqueuer;
   }
 
-  public static Enqueuer createForMainDexTracing(AppView<? extends AppInfoWithSubtyping> appView) {
-    return createForMainDexTracing(appView, null);
+  public static Enqueuer createForMainDexTracing(
+      AppView<? extends AppInfoWithClassHierarchy> appView, SubtypingInfo subtypingInfo) {
+    return createForMainDexTracing(appView, subtypingInfo, null);
   }
 
   public static Enqueuer createForMainDexTracing(
-      AppView<? extends AppInfoWithSubtyping> appView, GraphConsumer keptGraphConsumer) {
-    return new Enqueuer(appView, keptGraphConsumer, Mode.MAIN_DEX_TRACING);
+      AppView<? extends AppInfoWithClassHierarchy> appView,
+      SubtypingInfo subtypingInfo,
+      GraphConsumer keptGraphConsumer) {
+    return new Enqueuer(appView, subtypingInfo, keptGraphConsumer, Mode.MAIN_DEX_TRACING);
   }
 
   public static Enqueuer createForWhyAreYouKeeping(
-      AppView<? extends AppInfoWithSubtyping> appView, GraphConsumer keptGraphConsumer) {
-    return new Enqueuer(appView, keptGraphConsumer, Mode.WHY_ARE_YOU_KEEPING);
+      AppView<? extends AppInfoWithClassHierarchy> appView,
+      SubtypingInfo subtypingInfo,
+      GraphConsumer keptGraphConsumer) {
+    return new Enqueuer(appView, subtypingInfo, keptGraphConsumer, Mode.WHY_ARE_YOU_KEEPING);
   }
 }
diff --git a/src/main/java/com/android/tools/r8/shaking/GraphReporter.java b/src/main/java/com/android/tools/r8/shaking/GraphReporter.java
index 6b222d7..4ff3594 100644
--- a/src/main/java/com/android/tools/r8/shaking/GraphReporter.java
+++ b/src/main/java/com/android/tools/r8/shaking/GraphReporter.java
@@ -249,10 +249,10 @@
 
   public KeepReasonWitness reportReachableMethodAsLive(
       DexMethod overriddenMethod, ProgramMethod derivedMethod) {
-    if (keptGraphConsumer != null && overriddenMethod != derivedMethod.getMethod().method) {
+    if (keptGraphConsumer != null && overriddenMethod != derivedMethod.getDefinition().method) {
       return reportEdge(
           getMethodGraphNode(overriddenMethod),
-          getMethodGraphNode(derivedMethod.getMethod().method),
+          getMethodGraphNode(derivedMethod.getDefinition().method),
           EdgeKind.OverridingMethod);
     }
     return KeepReasonWitness.INSTANCE;
@@ -266,7 +266,7 @@
     if (keptGraphConsumer != null && instantiation.isClass()) {
       return reportEdge(
           getClassGraphNode(instantiation.asClass().type),
-          getMethodGraphNode(derivedMethod.getMethod().method),
+          getMethodGraphNode(derivedMethod.getDefinition().method),
           EdgeKind.IsLibraryMethod);
     }
     return KeepReasonWitness.INSTANCE;
diff --git a/src/main/java/com/android/tools/r8/shaking/IfRuleEvaluator.java b/src/main/java/com/android/tools/r8/shaking/IfRuleEvaluator.java
index 225aff0..d8fbb6d 100644
--- a/src/main/java/com/android/tools/r8/shaking/IfRuleEvaluator.java
+++ b/src/main/java/com/android/tools/r8/shaking/IfRuleEvaluator.java
@@ -5,7 +5,7 @@
 
 import static com.android.tools.r8.graph.DexProgramClass.asProgramClassOrNull;
 
-import com.android.tools.r8.graph.AppInfoWithSubtyping;
+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;
@@ -36,7 +36,7 @@
 
 public class IfRuleEvaluator {
 
-  private final AppView<? extends AppInfoWithSubtyping> appView;
+  private final AppView<? extends AppInfoWithClassHierarchy> appView;
   private final SubtypingInfo subtypingInfo;
   private final Enqueuer enqueuer;
   private final ExecutorService executorService;
@@ -45,7 +45,7 @@
   private final ConsequentRootSetBuilder rootSetBuilder;
 
   IfRuleEvaluator(
-      AppView<? extends AppInfoWithSubtyping> appView,
+      AppView<? extends AppInfoWithClassHierarchy> appView,
       SubtypingInfo subtypingInfo,
       Enqueuer enqueuer,
       ExecutorService executorService,
diff --git a/src/main/java/com/android/tools/r8/shaking/KeepReason.java b/src/main/java/com/android/tools/r8/shaking/KeepReason.java
index dd76e19..62a67ee 100644
--- a/src/main/java/com/android/tools/r8/shaking/KeepReason.java
+++ b/src/main/java/com/android/tools/r8/shaking/KeepReason.java
@@ -26,7 +26,7 @@
   }
 
   static KeepReason instantiatedIn(DexEncodedMethod method) {
-    return new InstatiatedIn(method);
+    return new InstantiatedIn(method);
   }
 
   public static KeepReason invokedViaSuperFrom(DexEncodedMethod from) {
@@ -42,7 +42,7 @@
   }
 
   public static KeepReason invokedFrom(ProgramMethod context) {
-    return invokedFrom(context.getHolder(), context.getMethod());
+    return invokedFrom(context.getHolder(), context.getDefinition());
   }
 
   public static KeepReason invokedFromLambdaCreatedIn(DexEncodedMethod method) {
@@ -97,9 +97,9 @@
     }
   }
 
-  public static class InstatiatedIn extends BasedOnOtherMethod {
+  public static class InstantiatedIn extends BasedOnOtherMethod {
 
-    private InstatiatedIn(DexEncodedMethod method) {
+    private InstantiatedIn(DexEncodedMethod method) {
       super(method);
     }
 
diff --git a/src/main/java/com/android/tools/r8/shaking/MainDexDirectReferenceTracer.java b/src/main/java/com/android/tools/r8/shaking/MainDexDirectReferenceTracer.java
index 15d4e4b..e686f98 100644
--- a/src/main/java/com/android/tools/r8/shaking/MainDexDirectReferenceTracer.java
+++ b/src/main/java/com/android/tools/r8/shaking/MainDexDirectReferenceTracer.java
@@ -5,7 +5,7 @@
 package com.android.tools.r8.shaking;
 
 import com.android.tools.r8.dex.IndexedItemCollection;
-import com.android.tools.r8.graph.AppInfoWithSubtyping;
+import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
 import com.android.tools.r8.graph.DexAnnotationSet;
 import com.android.tools.r8.graph.DexCallSite;
 import com.android.tools.r8.graph.DexClass;
@@ -27,10 +27,11 @@
       new AnnotationDirectReferenceCollector();
   private final DirectReferencesCollector codeDirectReferenceCollector;
 
-  private final AppInfoWithSubtyping appInfo;
+  private final AppInfoWithClassHierarchy appInfo;
   private final Consumer<DexType> consumer;
 
-  public MainDexDirectReferenceTracer(AppInfoWithSubtyping appInfo, Consumer<DexType> consumer) {
+  public MainDexDirectReferenceTracer(
+      AppInfoWithClassHierarchy appInfo, Consumer<DexType> consumer) {
     this.codeDirectReferenceCollector = new DirectReferencesCollector(appInfo.dexItemFactory());
     this.appInfo = appInfo;
     this.consumer = consumer;
@@ -61,7 +62,7 @@
   }
 
   public static boolean hasReferencesOutsideFromCode(
-      AppInfoWithSubtyping appInfo, DexEncodedMethod method, Set<DexType> classes) {
+      AppInfoWithClassHierarchy appInfo, DexEncodedMethod method, Set<DexType> classes) {
 
     BooleanBox result = new BooleanBox();
 
diff --git a/src/main/java/com/android/tools/r8/shaking/MainDexListBuilder.java b/src/main/java/com/android/tools/r8/shaking/MainDexListBuilder.java
index b46840b..fc34925 100644
--- a/src/main/java/com/android/tools/r8/shaking/MainDexListBuilder.java
+++ b/src/main/java/com/android/tools/r8/shaking/MainDexListBuilder.java
@@ -5,7 +5,7 @@
 
 import com.android.tools.r8.errors.CompilationError;
 import com.android.tools.r8.graph.AppInfo;
-import com.android.tools.r8.graph.AppInfoWithSubtyping;
+import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
 import com.android.tools.r8.graph.DexAnnotation;
 import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexEncodedMethod;
@@ -28,7 +28,7 @@
 public class MainDexListBuilder {
 
   private final Set<DexType> roots;
-  private final AppInfoWithSubtyping appInfo;
+  private final AppInfoWithClassHierarchy appInfo;
   private final Map<DexType, Boolean> annotationTypeContainEnum;
   private final DirectMappedDexApplication dexApplication;
   private final MainDexClasses.Builder mainDexClassesBuilder;
@@ -52,7 +52,7 @@
    */
   public MainDexListBuilder(Set<DexProgramClass> roots, DirectMappedDexApplication application) {
     this.dexApplication = application;
-    this.appInfo = new AppInfoWithSubtyping(dexApplication);
+    this.appInfo = new AppInfoWithClassHierarchy(dexApplication);
     // Only consider program classes for the root set.
     this.roots = SetUtils.mapIdentityHashSet(roots, DexProgramClass::getType);
     mainDexClassesBuilder = MainDexClasses.builder(appInfo).addRoots(this.roots);
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardConfiguration.java b/src/main/java/com/android/tools/r8/shaking/ProguardConfiguration.java
index 3598fd8..7d8d88a 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardConfiguration.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardConfiguration.java
@@ -251,8 +251,9 @@
       adaptClassStrings.addPattern(pattern);
     }
 
-    public void addAdaptResourceFilenames(ProguardPathList pattern) {
+    public Builder addAdaptResourceFilenames(ProguardPathList pattern) {
       adaptResourceFilenames.addPattern(pattern);
+      return this;
     }
 
     public void addAdaptResourceFileContents(ProguardPathList pattern) {
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationRule.java b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationRule.java
index edb901b..52c3b4c 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationRule.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationRule.java
@@ -3,7 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.shaking;
 
-import com.android.tools.r8.graph.AppInfoWithSubtyping;
+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.DexProgramClass;
@@ -83,7 +83,7 @@
   }
 
   Iterable<DexProgramClass> relevantCandidatesForRule(
-      AppView<? extends AppInfoWithSubtyping> appView,
+      AppView<? extends AppInfoWithClassHierarchy> appView,
       SubtypingInfo subtypingInfo,
       Iterable<DexProgramClass> defaultValue) {
     if (hasInheritanceClassName() && getInheritanceClassName().hasSpecificType()) {
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardPathList.java b/src/main/java/com/android/tools/r8/shaking/ProguardPathList.java
index edcc4f6..742f2ef 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardPathList.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardPathList.java
@@ -35,7 +35,7 @@
       return this;
     }
 
-    ProguardPathList build() {
+    public ProguardPathList build() {
       List<FileNameMatcher> matchers = this.matchers.build();
       if (matchers. size() > 0) {
         return new PathList(matchers);
diff --git a/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java b/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java
index 2065def..846710b 100644
--- a/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java
+++ b/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java
@@ -8,7 +8,7 @@
 import com.android.tools.r8.dex.Constants;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.AppInfo;
-import com.android.tools.r8.graph.AppInfoWithSubtyping;
+import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.BottomUpClassHierarchyTraversal;
 import com.android.tools.r8.graph.DexAnnotation;
@@ -77,7 +77,7 @@
 
 public class RootSetBuilder {
 
-  private final AppView<? extends AppInfoWithSubtyping> appView;
+  private final AppView<? extends AppInfoWithClassHierarchy> appView;
   private final SubtypingInfo subtypingInfo;
   private final DirectMappedDexApplication application;
   private final Iterable<? extends ProguardConfigurationRule> rules;
@@ -114,18 +114,19 @@
   private final Set<ProguardIfRule> ifRules = Sets.newIdentityHashSet();
 
   public RootSetBuilder(
-      AppView<? extends AppInfoWithSubtyping> appView,
-      DexApplication application,
+      AppView<? extends AppInfoWithClassHierarchy> appView,
+      SubtypingInfo subtypingInfo,
       Iterable<? extends ProguardConfigurationRule> rules) {
     this.appView = appView;
-    this.application = application.asDirect();
-    this.subtypingInfo = new SubtypingInfo(this.application.allClasses(), this.application);
+    this.subtypingInfo = subtypingInfo;
+    this.application = appView.appInfo().app().asDirect();
     this.rules = rules;
     this.options = appView.options();
   }
 
-  public RootSetBuilder(AppView<? extends AppInfoWithSubtyping> appView) {
-    this(appView, appView.appInfo().app(), null);
+  public RootSetBuilder(
+      AppView<? extends AppInfoWithClassHierarchy> appView, SubtypingInfo subtypingInfo) {
+    this(appView, subtypingInfo, null);
   }
 
   void handleMatchedAnnotation(AnnotationMatchResult annotation) {
@@ -571,10 +572,10 @@
               resolutionResult.getResolvedHolder().asProgramClass(),
               resolutionResult.getResolvedMethod());
       ProgramMethod methodToKeep =
-          canInsertForwardingMethod(originalClazz, resolutionMethod.getMethod())
+          canInsertForwardingMethod(originalClazz, resolutionMethod.getDefinition())
               ? new ProgramMethod(
                   originalClazz,
-                  resolutionMethod.getMethod().toForwardingMethod(originalClazz, appView))
+                  resolutionMethod.getDefinition().toForwardingMethod(originalClazz, appView))
               : resolutionMethod;
 
       delayedRootSetActionItems.add(
@@ -591,9 +592,9 @@
                       rule);
                 }
                 DexDefinition precondition =
-                    testAndGetPrecondition(methodToKeep.getMethod(), preconditionSupplier);
+                    testAndGetPrecondition(methodToKeep.getDefinition(), preconditionSupplier);
                 rootSetBuilder.addItemToSets(
-                    methodToKeep.getMethod(), context, rule, precondition, ifRule);
+                    methodToKeep.getDefinition(), context, rule, precondition, ifRule);
               }));
     }
   }
@@ -604,7 +605,7 @@
   }
 
   private void markMatchingOverriddenMethods(
-      AppInfoWithSubtyping appInfoWithSubtyping,
+      AppInfoWithClassHierarchy appInfoWithSubtyping,
       DexClass clazz,
       Collection<ProguardMemberRule> memberKeepRules,
       ProguardConfigurationRule rule,
diff --git a/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java b/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
index 95fa85d..bb90d42 100644
--- a/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
+++ b/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
@@ -344,6 +344,7 @@
     ObjectAllocationInfoCollection allocationInfo = appInfo.getObjectAllocationInfoCollection();
     if (allocationInfo.isInstantiatedDirectly(sourceClass)
         || allocationInfo.isInterfaceWithUnknownSubtypeHierarchy(sourceClass)
+        || allocationInfo.isImmediateInterfaceOfInstantiatedLambda(sourceClass)
         || appInfo.isPinned(sourceClass.type)
         || pinnedTypes.contains(sourceClass.type)
         || appInfo.neverMerge.contains(sourceClass.type)) {
@@ -756,7 +757,7 @@
           Box<Boolean> found = new Box<>(false);
           lookupResult.forEach(
               interfaceTarget -> {
-                if (interfaceTarget.getMethod() == method) {
+                if (interfaceTarget.getDefinition() == method) {
                   return;
                 }
                 DexClass enclosingClass = interfaceTarget.getHolder();
diff --git a/src/main/java/com/android/tools/r8/utils/ChainableConsumer.java b/src/main/java/com/android/tools/r8/utils/ChainableConsumer.java
new file mode 100644
index 0000000..7d69754
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/utils/ChainableConsumer.java
@@ -0,0 +1,10 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.utils;
+
+public interface ChainableConsumer<T> {
+
+  ChainableConsumer<T> accept(T value);
+}
diff --git a/src/main/java/com/android/tools/r8/utils/ChainableStringConsumer.java b/src/main/java/com/android/tools/r8/utils/ChainableStringConsumer.java
new file mode 100644
index 0000000..d1636c5
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/utils/ChainableStringConsumer.java
@@ -0,0 +1,23 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.utils;
+
+import java.util.function.Consumer;
+
+public interface ChainableStringConsumer extends ChainableConsumer<String> {
+
+  @Override
+  ChainableStringConsumer accept(String string);
+
+  static ChainableStringConsumer wrap(Consumer<String> consumer) {
+    return new ChainableStringConsumer() {
+      @Override
+      public ChainableStringConsumer accept(String value) {
+        consumer.accept(value);
+        return this;
+      }
+    };
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/utils/ConsumerUtils.java b/src/main/java/com/android/tools/r8/utils/ConsumerUtils.java
new file mode 100644
index 0000000..0979b27
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/utils/ConsumerUtils.java
@@ -0,0 +1,12 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.utils;
+
+public class ConsumerUtils {
+
+  public static <T> ThrowingConsumer<T, RuntimeException> emptyThrowingConsumer() {
+    return ignore -> {};
+  }
+}
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 3e82b0d..7e34093 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -187,7 +187,6 @@
     enableVerticalClassMerging = false;
     enableEnumUnboxing = false;
     enableUninstantiatedTypeOptimization = false;
-    enableUnusedArgumentRemoval = false;
     outline.enabled = false;
     enableEnumValueOptimization = false;
     enableValuePropagation = false;
@@ -222,14 +221,13 @@
   public boolean enableHorizontalClassMerging = true;
   public boolean enableVerticalClassMerging = true;
   public boolean enableArgumentRemoval = true;
-  public boolean enableUnusedArgumentRemoval = true;
   public boolean enableUnusedInterfaceRemoval = true;
   public boolean enableDevirtualization = true;
   public boolean enableNonNullTracking = true;
   public boolean enableInlining =
       !Version.isDevelopmentVersion()
           || System.getProperty("com.android.tools.r8.disableinlining") == null;
-  public boolean enableEnumUnboxing = false;
+  public boolean enableEnumUnboxing = true;
   // TODO(b/141451716): Evaluate the effect of allowing inlining in the inlinee.
   public boolean applyInliningToInlinee =
       System.getProperty("com.android.tools.r8.applyInliningToInlinee") != null;
@@ -336,6 +334,9 @@
     if (!isGeneratingClassFiles()) {
       marker.setMinApi(minApiLevel);
     }
+    if (desugaredLibraryConfiguration.getIdentifier() != null) {
+      marker.setDesugaredLibraryIdentifiers(desugaredLibraryConfiguration.getIdentifier());
+    }
     if (Version.isDevelopmentVersion()) {
       marker.setSha1(VersionProperties.INSTANCE.getSha());
     }
@@ -365,12 +366,17 @@
     return desugaredLibraryConfiguration.isLibraryCompilation();
   }
 
+  public boolean isRelocatorCompilation() {
+    return relocatorCompilation;
+  }
+
   public boolean shouldBackportMethods() {
     return !hasConsumer() || isGeneratingDex();
   }
 
   public boolean shouldKeepStackMapTable() {
     return isDesugaredLibraryCompilation()
+        || isRelocatorCompilation()
         || getProguardConfiguration().getKeepAttributes().stackMapTable;
   }
 
@@ -675,6 +681,8 @@
   public DesugaredLibraryConfiguration desugaredLibraryConfiguration =
       DesugaredLibraryConfiguration.empty();
 
+  public boolean relocatorCompilation = false;
+
   // If null, no keep rules are recorded.
   // If non null it records desugared library APIs used by the program.
   public StringConsumer desugaredLibraryKeepRuleConsumer = null;
@@ -1099,6 +1107,7 @@
     public boolean addCallEdgesForLibraryInvokes = false;
 
     public boolean allowCheckDiscardedErrors = false;
+    public boolean allowInjectedAnnotationMethods = false;
     public boolean allowTypeErrors =
         !Version.isDevelopmentVersion()
             || System.getProperty("com.android.tools.r8.allowTypeErrors") != null;
@@ -1184,12 +1193,6 @@
     enableNameReflectionOptimization = false;
   }
 
-  @VisibleForTesting
-  public void enableEnumUnboxing() {
-    assert !enableEnumUnboxing;
-    enableEnumUnboxing = true;
-  }
-
   // TODO(b/69963623): Remove this once enabled.
   @VisibleForTesting
   public void enablePropagationOfConstantsAtCallSites() {
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 9c18d3a..8cdce08 100644
--- a/src/main/java/com/android/tools/r8/utils/LineNumberOptimizer.java
+++ b/src/main/java/com/android/tools/r8/utils/LineNumberOptimizer.java
@@ -6,7 +6,7 @@
 import com.android.tools.r8.ResourceException;
 import com.android.tools.r8.cf.code.CfInstruction;
 import com.android.tools.r8.cf.code.CfPosition;
-import com.android.tools.r8.graph.AppInfoWithSubtyping;
+import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.CfCode;
 import com.android.tools.r8.graph.Code;
@@ -261,7 +261,7 @@
   }
 
   public static ClassNameMapper run(
-      AppView<AppInfoWithSubtyping> appView,
+      AppView<AppInfoWithClassHierarchy> appView,
       DexApplication application,
       AndroidApp inputApp,
       NamingLens namingLens) {
@@ -436,7 +436,7 @@
   }
 
   private static boolean verifyMethodsAreKeptDirectlyOrIndirectly(
-      AppView<AppInfoWithSubtyping> appView, List<DexEncodedMethod> methods) {
+      AppView<AppInfoWithClassHierarchy> appView, List<DexEncodedMethod> methods) {
     if (appView.options().isGeneratingClassFiles()) {
       return true;
     }
diff --git a/src/main/java/com/android/tools/r8/utils/StringUtils.java b/src/main/java/com/android/tools/r8/utils/StringUtils.java
index 02e83de..902924a 100644
--- a/src/main/java/com/android/tools/r8/utils/StringUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/StringUtils.java
@@ -4,6 +4,8 @@
 package com.android.tools.r8.utils;
 
 import com.android.tools.r8.errors.Unreachable;
+import java.io.PrintWriter;
+import java.io.StringWriter;
 import java.security.MessageDigest;
 import java.security.NoSuchAlgorithmException;
 import java.util.ArrayList;
@@ -333,4 +335,10 @@
   public static String replaceAll(String subject, String target, String replacement) {
     return subject.replaceAll(Pattern.quote(target), Matcher.quoteReplacement(replacement));
   }
+
+  public static String stacktraceAsString(Throwable throwable) {
+    StringWriter sw = new StringWriter();
+    throwable.printStackTrace(new PrintWriter(sw));
+    return sw.toString();
+  }
 }
diff --git a/src/test/java/com/android/tools/r8/ExternalR8TestBuilder.java b/src/test/java/com/android/tools/r8/ExternalR8TestBuilder.java
index 11e6692..08ce5be 100644
--- a/src/test/java/com/android/tools/r8/ExternalR8TestBuilder.java
+++ b/src/test/java/com/android/tools/r8/ExternalR8TestBuilder.java
@@ -90,9 +90,7 @@
 
       String classPath =
           addR8ExternalDeps
-              ? r8jar.toAbsolutePath().toString()
-                  + CLASSPATH_SEPARATOR
-                  + ToolHelper.DEPS_NOT_RELOCATED
+              ? r8jar.toAbsolutePath().toString() + CLASSPATH_SEPARATOR + ToolHelper.DEPS
               : r8jar.toAbsolutePath().toString();
 
       List<String> command = new ArrayList<>();
diff --git a/src/test/java/com/android/tools/r8/JctfTestSpecifications.java b/src/test/java/com/android/tools/r8/JctfTestSpecifications.java
index 30f6489..899093a 100644
--- a/src/test/java/com/android/tools/r8/JctfTestSpecifications.java
+++ b/src/test/java/com/android/tools/r8/JctfTestSpecifications.java
@@ -1824,6 +1824,9 @@
           .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
diff --git a/src/test/java/com/android/tools/r8/R8RunArtTestsTest.java b/src/test/java/com/android/tools/r8/R8RunArtTestsTest.java
index 74cff92..8ffe6af 100644
--- a/src/test/java/com/android/tools/r8/R8RunArtTestsTest.java
+++ b/src/test/java/com/android/tools/r8/R8RunArtTestsTest.java
@@ -981,7 +981,6 @@
                   LEGACY_RUNTIME))
           // Dex input contains an illegal InvokeSuper in Z.foo() to Y.foo()
           // that R8 will fail to compile.
-          .put("594-invoke-super", TestCondition.match(TestCondition.R8DEX_COMPILER))
           .put("974-verify-interface-super", TestCondition.match(TestCondition.R8DEX_COMPILER))
           // R8 generates too large code in Goto.bigGoto(). b/74327727
           .put("003-omnibus-opcodes", TestCondition.match(TestCondition.D8_AFTER_R8CF_COMPILER))
diff --git a/src/test/java/com/android/tools/r8/R8RunExamplesAndroidOTest.java b/src/test/java/com/android/tools/r8/R8RunExamplesAndroidOTest.java
index a642002..5eb1ad48 100644
--- a/src/test/java/com/android/tools/r8/R8RunExamplesAndroidOTest.java
+++ b/src/test/java/com/android/tools/r8/R8RunExamplesAndroidOTest.java
@@ -102,7 +102,7 @@
         .withOptionConsumer(opts -> opts.enableClassInlining = false)
         .withBuilderTransformation(
             b -> b.addProguardConfiguration(PROGUARD_OPTIONS, Origin.unknown()))
-        .withDexCheck(inspector -> checkLambdaCount(inspector, 115, "lambdadesugaring"))
+        .withDexCheck(inspector -> checkLambdaCount(inspector, 103, "lambdadesugaring"))
         .run();
 
     test("lambdadesugaring", "lambdadesugaring", "LambdaDesugaring")
@@ -142,7 +142,7 @@
         .withOptionConsumer(opts -> opts.enableClassInlining = false)
         .withBuilderTransformation(
             b -> b.addProguardConfiguration(PROGUARD_OPTIONS, Origin.unknown()))
-        .withDexCheck(inspector -> checkLambdaCount(inspector, 115, "lambdadesugaring"))
+        .withDexCheck(inspector -> checkLambdaCount(inspector, 103, "lambdadesugaring"))
         .run();
 
     test("lambdadesugaring", "lambdadesugaring", "LambdaDesugaring")
diff --git a/src/test/java/com/android/tools/r8/R8UnreachableCodeTest.java b/src/test/java/com/android/tools/r8/R8UnreachableCodeTest.java
index 9cb139c..fd9259f 100644
--- a/src/test/java/com/android/tools/r8/R8UnreachableCodeTest.java
+++ b/src/test/java/com/android/tools/r8/R8UnreachableCodeTest.java
@@ -7,7 +7,7 @@
 import static org.junit.Assert.assertEquals;
 
 import com.android.tools.r8.dex.ApplicationReader;
-import com.android.tools.r8.graph.AppInfoWithSubtyping;
+import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexProgramClass;
@@ -44,7 +44,8 @@
     DirectMappedDexApplication application =
         new ApplicationReader(input, options, timing).read(executorService).toDirect();
     IRConverter converter =
-        new IRConverter(AppView.createForR8(new AppInfoWithSubtyping(application), options), null);
+        new IRConverter(
+            AppView.createForR8(new AppInfoWithClassHierarchy(application), options), null);
     converter.optimize();
     DexProgramClass clazz = application.classes().iterator().next();
     assertEquals(4, clazz.directMethods().size());
diff --git a/src/test/java/com/android/tools/r8/TestBase.java b/src/test/java/com/android/tools/r8/TestBase.java
index f172bb12f..7c3ddff 100644
--- a/src/test/java/com/android/tools/r8/TestBase.java
+++ b/src/test/java/com/android/tools/r8/TestBase.java
@@ -22,7 +22,6 @@
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.AppInfo;
 import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
-import com.android.tools.r8.graph.AppInfoWithSubtyping;
 import com.android.tools.r8.graph.AppServices;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexApplication;
@@ -35,6 +34,7 @@
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.DirectMappedDexApplication;
 import com.android.tools.r8.graph.SmaliWriter;
+import com.android.tools.r8.graph.SubtypingInfo;
 import com.android.tools.r8.jasmin.JasminBuilder;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.references.ClassReference;
@@ -587,14 +587,14 @@
     }
   }
 
-  protected static AppView<AppInfoWithSubtyping> computeAppViewWithSubtyping(AndroidApp app)
+  protected static AppView<AppInfoWithClassHierarchy> computeAppViewWithSubtyping(AndroidApp app)
       throws Exception {
     Timing timing = Timing.empty();
     InternalOptions options = new InternalOptions();
     DirectMappedDexApplication application =
         new ApplicationReader(app, options, timing).read().toDirect();
-    AppView<AppInfoWithSubtyping> appView =
-        AppView.createForR8(new AppInfoWithSubtyping(application), options);
+    AppView<AppInfoWithClassHierarchy> appView =
+        AppView.createForR8(new AppInfoWithClassHierarchy(application), options);
     appView.setAppServices(AppServices.builder(appView).build());
     return appView;
   }
@@ -616,18 +616,19 @@
       Function<DexItemFactory, Collection<ProguardConfigurationRule>>
           proguardConfigurationRulesGenerator)
       throws Exception {
-    AppView<AppInfoWithSubtyping> appView = computeAppViewWithSubtyping(app);
+    AppView<AppInfoWithClassHierarchy> appView = computeAppViewWithSubtyping(app);
     // Run the tree shaker to compute an instance of AppInfoWithLiveness.
     ExecutorService executor = Executors.newSingleThreadExecutor();
-    DexApplication application = appView.appInfo().app();
+    DirectMappedDexApplication application = appView.appInfo().app().asDirect();
+    SubtypingInfo subtypingInfo = new SubtypingInfo(application.allClasses(), application);
     RootSet rootSet =
         new RootSetBuilder(
                 appView,
-                application,
+                subtypingInfo,
                 proguardConfigurationRulesGenerator.apply(appView.appInfo().dexItemFactory()))
             .run(executor);
     AppInfoWithLiveness appInfoWithLiveness =
-        EnqueuerFactory.createForInitialTreeShaking(appView)
+        EnqueuerFactory.createForInitialTreeShaking(appView, subtypingInfo)
             .traceApplication(rootSet, ProguardClassFilter.empty(), executor, application.timing);
     // We do not run the tree pruner to ensure that the hierarchy is as designed and not modified
     // due to liveness.
@@ -1481,4 +1482,12 @@
   public static String typeName(Class<?> clazz) {
     return clazz.getTypeName();
   }
+
+  public static AndroidApiLevel apiLevelWithDefaultInterfaceMethodsSupport() {
+    return AndroidApiLevel.N;
+  }
+
+  public static AndroidApiLevel apiLevelWithInvokeCustomSupport() {
+    return AndroidApiLevel.O;
+  }
 }
diff --git a/src/test/java/com/android/tools/r8/TestBuilder.java b/src/test/java/com/android/tools/r8/TestBuilder.java
index 27af1aa..e9ac10d 100644
--- a/src/test/java/com/android/tools/r8/TestBuilder.java
+++ b/src/test/java/com/android/tools/r8/TestBuilder.java
@@ -12,6 +12,7 @@
 import java.util.Collection;
 import java.util.List;
 import java.util.concurrent.ExecutionException;
+import java.util.function.Consumer;
 
 public abstract class TestBuilder<RR extends TestRunResult, T extends TestBuilder<RR, T>> {
 
@@ -38,6 +39,14 @@
     return self();
   }
 
+  public T ifTrue(boolean value, Consumer<T> consumer) {
+    T self = self();
+    if (value) {
+      consumer.accept(self);
+    }
+    return self;
+  }
+
   @Deprecated
   public abstract RR run(String mainClass)
       throws CompilationFailedException, ExecutionException, IOException;
diff --git a/src/test/java/com/android/tools/r8/ToolHelper.java b/src/test/java/com/android/tools/r8/ToolHelper.java
index 080051f..5c50407 100644
--- a/src/test/java/com/android/tools/r8/ToolHelper.java
+++ b/src/test/java/com/android/tools/r8/ToolHelper.java
@@ -160,16 +160,18 @@
 
   public static final Path D8_JAR = Paths.get(LIBS_DIR, "d8.jar");
   public static final Path R8_JAR = Paths.get(LIBS_DIR, "r8.jar");
+  public static final Path R8_WITH_DEPS_JAR = Paths.get(LIBS_DIR, "r8_with_deps.jar");
   public static final Path R8_WITH_RELOCATED_DEPS_JAR =
       Paths.get(LIBS_DIR, "r8_with_relocated_deps.jar");
-  public static final Path R8_WITH_RELOCATED_DEPS_JAR_11 =
+  public static final Path R8_WITH_DEPS_11_JAR = Paths.get(LIBS_DIR, "r8_with_deps_11.jar");
+  public static final Path R8_WITH_RELOCATED_DEPS_11_JAR =
       Paths.get(LIBS_DIR, "r8_with_relocated_deps_11.jar");
   public static final Path R8LIB_JAR = Paths.get(LIBS_DIR, "r8lib.jar");
   public static final Path R8LIB_MAP = Paths.get(LIBS_DIR, "r8lib.jar.map");
   public static final Path R8LIB_EXCLUDE_DEPS_JAR = Paths.get(LIBS_DIR, "r8lib-exclude-deps.jar");
   public static final Path R8LIB_EXCLUDE_DEPS_MAP =
       Paths.get(LIBS_DIR, "r8lib-exclude-deps.jar.map");
-  public static final Path DEPS_NOT_RELOCATED = Paths.get(LIBS_DIR, "deps-not-relocated.jar");
+  public static final Path DEPS = Paths.get(LIBS_DIR, "deps.jar");
 
   public static final Path DESUGAR_LIB_CONVERSIONS =
       Paths.get(LIBS_DIR, "library_desugar_conversions.zip");
diff --git a/src/test/java/com/android/tools/r8/accessrelaxation/ConstructorRelaxationTest.java b/src/test/java/com/android/tools/r8/accessrelaxation/ConstructorRelaxationTest.java
index 11c5285..972d058 100644
--- a/src/test/java/com/android/tools/r8/accessrelaxation/ConstructorRelaxationTest.java
+++ b/src/test/java/com/android/tools/r8/accessrelaxation/ConstructorRelaxationTest.java
@@ -205,9 +205,12 @@
     for (Class clazz : CLASSES) {
       ClassSubject classSubject = codeInspector.clazz(clazz);
       assertThat(classSubject, isPresent());
-      classSubject.getDexClass().forEachMethod(m -> {
-        assertTrue(!m.isInstanceInitializer() || m.isPublicMethod());
-      });
+      classSubject
+          .getDexProgramClass()
+          .forEachMethod(
+              m -> {
+                assertTrue(!m.isInstanceInitializer() || m.isPublicMethod());
+              });
     }
   }
 
diff --git a/src/test/java/com/android/tools/r8/accessrelaxation/InnerClassAttributePublicizerTest.java b/src/test/java/com/android/tools/r8/accessrelaxation/InnerClassAttributePublicizerTest.java
index 9322844..24ba1cd 100644
--- a/src/test/java/com/android/tools/r8/accessrelaxation/InnerClassAttributePublicizerTest.java
+++ b/src/test/java/com/android/tools/r8/accessrelaxation/InnerClassAttributePublicizerTest.java
@@ -54,7 +54,7 @@
     assertThat(classSubject, isPresent());
 
     InnerClassAttribute innerClassAttribute =
-        classSubject.getDexClass().getInnerClassAttributeForThisClass();
+        classSubject.getDexProgramClass().getInnerClassAttributeForThisClass();
     assertNotNull(innerClassAttribute);
 
     ClassAccessFlags accessFlags =
diff --git a/src/test/java/com/android/tools/r8/androidapi/GenerateAvailableApiExceptions.java b/src/test/java/com/android/tools/r8/androidapi/GenerateAvailableApiExceptions.java
index 83821f8..b6a1c72 100644
--- a/src/test/java/com/android/tools/r8/androidapi/GenerateAvailableApiExceptions.java
+++ b/src/test/java/com/android/tools/r8/androidapi/GenerateAvailableApiExceptions.java
@@ -92,14 +92,14 @@
     if (!clazz.isPresent()) {
       return false;
     }
-    if (clazz.getDexClass().type == inspector.getFactory().objectType) {
+    if (clazz.getDexProgramClass().type == inspector.getFactory().objectType) {
       return false;
     }
-    if (clazz.getDexClass().type == inspector.getFactory().throwableType) {
+    if (clazz.getDexProgramClass().type == inspector.getFactory().throwableType) {
       return true;
     }
     return cache.computeIfAbsent(
-        clazz.getDexClass(),
+        clazz.getDexProgramClass(),
         c -> {
           return c.superType != null
               && isThrowable(inspector.clazz(c.superType.toSourceString()), cache, inspector);
diff --git a/src/test/java/com/android/tools/r8/annotations/AnnotationWithInjectedMethodsTest.java b/src/test/java/com/android/tools/r8/annotations/AnnotationWithInjectedMethodsTest.java
new file mode 100644
index 0000000..be3ee34
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/annotations/AnnotationWithInjectedMethodsTest.java
@@ -0,0 +1,176 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.annotations;
+
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.ToolHelper.DexVm.Version;
+import com.android.tools.r8.enumunboxing.EnumUnboxingTestBase;
+import com.android.tools.r8.utils.StringUtils;
+import com.google.common.collect.ImmutableList;
+import java.io.IOException;
+import java.lang.annotation.Annotation;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.Collection;
+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 AnnotationWithInjectedMethodsTest extends EnumUnboxingTestBase {
+
+  private final TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters()
+        .withAllRuntimes()
+        .withApiLevelsStartingAtIncluding(apiLevelWithDefaultInterfaceMethodsSupport())
+        .build();
+  }
+
+  public AnnotationWithInjectedMethodsTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testRuntime() throws Exception {
+    testForRuntime(parameters)
+        .addProgramClasses(getProgramClasses())
+        .addProgramClassFileData(getProgramClassFileData())
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutput(getExpectedOutput());
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    testForR8(parameters.getBackend())
+        .addProgramClasses(getProgramClasses())
+        .addProgramClassFileData(getProgramClassFileData())
+        .addKeepMainRule(Main.class)
+        .addKeepClassAndMembersRules(AnnotationWithInjectedMethod.class)
+        .addKeepRuntimeVisibleAnnotations()
+        .addOptionsModification(options -> options.testing.allowInjectedAnnotationMethods = true)
+        .noMinification()
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutput(getExpectedOutput());
+  }
+
+  private String getExpectedOutput() {
+    ImmutableList.Builder<String> builder = ImmutableList.builder();
+    builder.add(
+        "Foo",
+        "Bar",
+        "Caught IncompleteAnnotationException: "
+            + typeName(AnnotationWithInjectedMethod.class)
+            + " missing element getInstanceData");
+    if (parameters.isCfRuntime()) {
+      builder.add("Caught AssertionError: Too many parameters for an annotation method");
+    } else {
+      builder.add(
+          "Caught IllegalArgumentException: Invalid method for annotation type: "
+              + "public "
+              + (parameters.getRuntime().asDex().getVm().getVersion() == Version.V7_0_0
+                  ? ""
+                  : "default ")
+              + typeName(Data.class)
+              + " "
+              + typeName(AnnotationWithInjectedMethod.class)
+              + ".getInstanceData("
+              + typeName(Data.class)
+              + ")");
+    }
+    return StringUtils.lines(builder.build());
+  }
+
+  private Collection<Class<?>> getProgramClasses() {
+    return ImmutableList.of(Data.class);
+  }
+
+  private Collection<byte[]> getProgramClassFileData() throws IOException {
+    return ImmutableList.of(
+        transformer(Main.class)
+            .replaceAnnotationDescriptor(
+                descriptor(ToBeAnnotationWithInjectedMethod.class),
+                descriptor(AnnotationWithInjectedMethod.class))
+            .transform(),
+        transformer(AnnotationWithInjectedMethod.class)
+            .setAnnotation()
+            .removeInnerClasses()
+            .replaceAnnotationDescriptor(
+                descriptor(ToBeRetention.class), descriptor(Retention.class))
+            .transform());
+  }
+
+  static class Data {
+
+    String value;
+
+    Data(String value) {
+      this.value = value;
+    }
+
+    @Override
+    public String toString() {
+      return value;
+    }
+  }
+
+  @Retention(RetentionPolicy.RUNTIME)
+  @interface ToBeRetention {
+    RetentionPolicy value();
+  }
+
+  @ToBeRetention(RetentionPolicy.RUNTIME)
+  /*@*/ interface AnnotationWithInjectedMethod extends Annotation {
+
+    default Data getInstanceData() {
+      return new Data("Baz");
+    }
+
+    default Data getInstanceData(Data in) {
+      return new Data(in.value);
+    }
+
+    static Data getStaticData() {
+      return new Data("Foo");
+    }
+
+    static Data getStaticData(Data in) {
+      return new Data(in.value);
+    }
+  }
+
+  @Retention(RetentionPolicy.RUNTIME)
+  @interface ToBeAnnotationWithInjectedMethod {}
+
+  @ToBeAnnotationWithInjectedMethod
+  static class Main {
+
+    public static void main(String[] args) {
+      System.out.println(AnnotationWithInjectedMethod.getStaticData());
+      System.out.println(AnnotationWithInjectedMethod.getStaticData(new Data("Bar")));
+
+      try {
+        System.out.println(getAnnotationWithInjectedMethod().getInstanceData());
+      } catch (Throwable e) {
+        System.out.println("Caught " + e.getClass().getSimpleName() + ": " + e.getMessage());
+      }
+
+      try {
+        System.out.println(getAnnotationWithInjectedMethod().getInstanceData(new Data("Qux")));
+      } catch (Throwable e) {
+        System.out.println("Caught " + e.getClass().getSimpleName() + ": " + e.getMessage());
+      }
+    }
+
+    static AnnotationWithInjectedMethod getAnnotationWithInjectedMethod() {
+      return Main.class.getAnnotation(AnnotationWithInjectedMethod.class);
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/bridgeremoval/B77836766.java b/src/test/java/com/android/tools/r8/bridgeremoval/B77836766.java
index 2c46b40..6a0cf4d 100644
--- a/src/test/java/com/android/tools/r8/bridgeremoval/B77836766.java
+++ b/src/test/java/com/android/tools/r8/bridgeremoval/B77836766.java
@@ -151,7 +151,7 @@
     DexCode code = fooFromCls2InAbsCls.getMethod().getCode().asDexCode();
     checkInstructions(code, ImmutableList.of(InvokeVirtual.class, ReturnVoid.class));
     InvokeVirtual invoke = (InvokeVirtual) code.instructions[0];
-    assertEquals(absSubject.getDexClass().type, invoke.getMethod().holder);
+    assertEquals(absSubject.getDexProgramClass().type, invoke.getMethod().holder);
 
     // Cls1#foo has been moved to AbsCls#foo as a result of bridge hoisting.
     MethodSubject fooInCls1 = cls1Subject.method("void", "foo", "java.lang.String");
@@ -162,7 +162,7 @@
     code = fooFromCls1InAbsCls.getMethod().getCode().asDexCode();
     checkInstructions(code, ImmutableList.of(InvokeVirtual.class, ReturnVoid.class));
     invoke = (InvokeVirtual) code.instructions[0];
-    assertEquals(absSubject.getDexClass().type, invoke.getMethod().holder);
+    assertEquals(absSubject.getDexProgramClass().type, invoke.getMethod().holder);
   }
 
   /**
@@ -259,7 +259,7 @@
     DexCode code = barInCls2.getMethod().getCode().asDexCode();
     checkInstructions(code, ImmutableList.of(InvokeVirtual.class, ReturnVoid.class));
     InvokeVirtual invoke = (InvokeVirtual) code.instructions[0];
-    assertEquals(baseSubject.getDexClass().type, invoke.getMethod().holder);
+    assertEquals(baseSubject.getDexProgramClass().type, invoke.getMethod().holder);
 
     // Cls1#foo has been moved to Base#foo as a result of bridge hoisting.
     MethodSubject fooInCls1 = cls1Subject.method("void", "foo", "java.lang.Integer");
@@ -270,7 +270,7 @@
     code = fooInBase.getMethod().getCode().asDexCode();
     checkInstructions(code, ImmutableList.of(InvokeVirtual.class, ReturnVoid.class));
     invoke = (InvokeVirtual) code.instructions[0];
-    assertEquals(baseSubject.getDexClass().type, invoke.getMethod().holder);
+    assertEquals(baseSubject.getDexProgramClass().type, invoke.getMethod().holder);
   }
 
   /**
@@ -355,7 +355,7 @@
     DexCode code = barInSub.getMethod().getCode().asDexCode();
     checkInstructions(code, ImmutableList.of(InvokeVirtual.class, ReturnVoid.class));
     InvokeVirtual invoke = (InvokeVirtual) code.instructions[0];
-    assertEquals(baseSubject.getDexClass().type, invoke.getMethod().holder);
+    assertEquals(baseSubject.getDexProgramClass().type, invoke.getMethod().holder);
   }
 
   /*
@@ -428,7 +428,7 @@
     DexCode code = barInSub.getMethod().getCode().asDexCode();
     checkInstructions(code, ImmutableList.of(InvokeVirtual.class, ReturnVoid.class));
     InvokeVirtual invoke = (InvokeVirtual) code.instructions[0];
-    assertEquals(baseSubject.getDexClass().type, invoke.getMethod().holder);
+    assertEquals(baseSubject.getDexProgramClass().type, invoke.getMethod().holder);
   }
 
   private AndroidApp runAndVerifyOnJvmAndArt(
diff --git a/src/test/java/com/android/tools/r8/bridgeremoval/hoisting/NonSuperclassBridgeHoistingTest.java b/src/test/java/com/android/tools/r8/bridgeremoval/hoisting/NonSuperclassBridgeHoistingTest.java
index 353a9b9..9c4a0d6 100644
--- a/src/test/java/com/android/tools/r8/bridgeremoval/hoisting/NonSuperclassBridgeHoistingTest.java
+++ b/src/test/java/com/android/tools/r8/bridgeremoval/hoisting/NonSuperclassBridgeHoistingTest.java
@@ -55,7 +55,7 @@
   private void inspect(CodeInspector inspector) {
     ClassSubject aClassSubject = inspector.clazz(A.class);
     assertThat(aClassSubject, isPresent());
-    assertEquals(0, aClassSubject.getDexClass().virtualMethods().size());
+    assertEquals(0, aClassSubject.getDexProgramClass().virtualMethods().size());
 
     ClassSubject b1ClassSubject = inspector.clazz(B.class);
     assertThat(b1ClassSubject, isPresent());
diff --git a/src/test/java/com/android/tools/r8/cf/ConstantDynamicHolderTest.java b/src/test/java/com/android/tools/r8/cf/ConstantDynamicHolderTest.java
index 81f50fc..1ec23ca 100644
--- a/src/test/java/com/android/tools/r8/cf/ConstantDynamicHolderTest.java
+++ b/src/test/java/com/android/tools/r8/cf/ConstantDynamicHolderTest.java
@@ -80,7 +80,7 @@
             "main",
             (value, continuation) -> {
               assertEquals("replaced by dynamic null constant", value);
-              continuation.apply(getDynamicConstant());
+              continuation.visitLdcInsn(getDynamicConstant());
             })
         .transform();
   }
diff --git a/src/test/java/com/android/tools/r8/cf/GetClassLdcClassTest.java b/src/test/java/com/android/tools/r8/cf/GetClassLdcClassTest.java
index 2c7d700..07a33ab 100644
--- a/src/test/java/com/android/tools/r8/cf/GetClassLdcClassTest.java
+++ b/src/test/java/com/android/tools/r8/cf/GetClassLdcClassTest.java
@@ -116,7 +116,11 @@
   }
 
   private static int getVersion(CodeInspector inspector, Class<?> clazz) {
-    return inspector.clazz(clazz).getDexClass().asProgramClass().getInitialClassFileVersion();
+    return inspector
+        .clazz(clazz)
+        .getDexProgramClass()
+        .asProgramClass()
+        .getInitialClassFileVersion();
   }
 
   private static void checkVersion(CodeInspector inspector, Class<?> clazz, int version) {
diff --git a/src/test/java/com/android/tools/r8/cf/IdenticalCatchHandlerTest.java b/src/test/java/com/android/tools/r8/cf/IdenticalCatchHandlerTest.java
index a958c3a..e0818bb 100644
--- a/src/test/java/com/android/tools/r8/cf/IdenticalCatchHandlerTest.java
+++ b/src/test/java/com/android/tools/r8/cf/IdenticalCatchHandlerTest.java
@@ -13,9 +13,9 @@
 import com.android.tools.r8.errors.Unimplemented;
 import com.android.tools.r8.graph.CfCode;
 import com.android.tools.r8.graph.Code;
-import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexCode;
 import com.android.tools.r8.graph.DexCode.Try;
+import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
@@ -71,7 +71,7 @@
 
   private int countCatchHandlers(AndroidApp inputApp) throws Exception {
     CodeInspector inspector = new CodeInspector(inputApp);
-    DexClass dexClass = inspector.clazz(TestClass.class).getDexClass();
+    DexProgramClass dexClass = inspector.clazz(TestClass.class).getDexProgramClass();
     Code code = dexClass.virtualMethods().get(0).getCode();
     if (code.isCfCode()) {
       CfCode cfCode = code.asCfCode();
diff --git a/src/test/java/com/android/tools/r8/cf/NonidenticalCatchHandlerTest.java b/src/test/java/com/android/tools/r8/cf/NonidenticalCatchHandlerTest.java
index 5d536cf..fcb172b 100644
--- a/src/test/java/com/android/tools/r8/cf/NonidenticalCatchHandlerTest.java
+++ b/src/test/java/com/android/tools/r8/cf/NonidenticalCatchHandlerTest.java
@@ -13,9 +13,9 @@
 import com.android.tools.r8.errors.Unimplemented;
 import com.android.tools.r8.graph.CfCode;
 import com.android.tools.r8.graph.Code;
-import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexCode;
 import com.android.tools.r8.graph.DexCode.Try;
+import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
@@ -70,7 +70,7 @@
 
   private int countCatchHandlers(AndroidApp inputApp) throws Exception {
     CodeInspector inspector = new CodeInspector(inputApp);
-    DexClass dexClass = inspector.clazz(TestClass.class).getDexClass();
+    DexProgramClass dexClass = inspector.clazz(TestClass.class).getDexProgramClass();
     Code code = dexClass.virtualMethods().get(0).getCode();
     if (code.isCfCode()) {
       CfCode cfCode = code.asCfCode();
diff --git a/src/test/java/com/android/tools/r8/cf/TryRangeTestRunner.java b/src/test/java/com/android/tools/r8/cf/TryRangeTestRunner.java
index f659d1c..aec1329 100644
--- a/src/test/java/com/android/tools/r8/cf/TryRangeTestRunner.java
+++ b/src/test/java/com/android/tools/r8/cf/TryRangeTestRunner.java
@@ -85,7 +85,7 @@
   }
 
   private void processIR(IRCode code) {
-    if (!code.method.qualifiedName().equals(TryRangeTestLimitRange.class.getName() + ".main")) {
+    if (!code.method().qualifiedName().equals(TryRangeTestLimitRange.class.getName() + ".main")) {
       return;
     }
     BasicBlock entryBlock = code.entryBlock();
diff --git a/src/test/java/com/android/tools/r8/cf/methodhandles/InvalidBootstrapMethodHandleTest.java b/src/test/java/com/android/tools/r8/cf/methodhandles/InvalidBootstrapMethodHandleTest.java
new file mode 100644
index 0000000..a815910
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/cf/methodhandles/InvalidBootstrapMethodHandleTest.java
@@ -0,0 +1,188 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.cf.methodhandles;
+
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.junit.Assert.fail;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestRunResult;
+import com.android.tools.r8.utils.StringUtils;
+import com.google.common.collect.ImmutableMap;
+import java.util.List;
+import java.util.Map;
+import java.util.TreeSet;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.objectweb.asm.Handle;
+import org.objectweb.asm.Opcodes;
+
+@RunWith(Parameterized.class)
+public class InvalidBootstrapMethodHandleTest extends TestBase {
+
+  static final String EXPECTED = StringUtils.lines("Called foo!");
+
+  static final Map<String, Integer> HANDLE_TYPES =
+      ImmutableMap.<String, Integer>builder()
+          .put("GETFIELD", Opcodes.H_GETFIELD)
+          .put("GETSTATIC", Opcodes.H_GETSTATIC)
+          .put("PUTFIELD", Opcodes.H_PUTFIELD)
+          .put("PUTSTATIC", Opcodes.H_PUTSTATIC)
+          .put("INVOKEVIRTUAL", Opcodes.H_INVOKEVIRTUAL)
+          .put("INVOKESTATIC", Opcodes.H_INVOKESTATIC)
+          .put("INVOKESPECIAL", Opcodes.H_INVOKESPECIAL)
+          .put("NEWINVOKESPECIAL", Opcodes.H_NEWINVOKESPECIAL)
+          .put("INVOKEINTERFACE", Opcodes.H_INVOKEINTERFACE)
+          .build();
+
+  private final TestParameters parameters;
+  private final String handleTypeString;
+
+  @Parameterized.Parameters(name = "{0}, {1}")
+  public static List<Object[]> data() {
+    return buildParameters(
+        getTestParameters()
+            .withAllRuntimes()
+            .withApiLevelsStartingAtIncluding(apiLevelWithInvokeCustomSupport())
+            .build(),
+        new TreeSet<>(HANDLE_TYPES.keySet()));
+  }
+
+  public InvalidBootstrapMethodHandleTest(TestParameters parameters, String handleTypeString) {
+    this.parameters = parameters;
+    this.handleTypeString = handleTypeString;
+  }
+
+  private int handleTypeOpcode() {
+    return HANDLE_TYPES.get(handleTypeString);
+  }
+
+  @Test
+  public void test() throws Exception {
+    // The compiler will fail on opcodes different from 6 and 8.
+    // Investigate why 8 is supposedly valid.
+    if (parameters.isDexRuntime()
+        && handleTypeOpcode() != Opcodes.H_INVOKESTATIC
+        && handleTypeOpcode() != Opcodes.H_NEWINVOKESPECIAL) {
+      try {
+        testForD8()
+            .addProgramClasses(InvalidBootstrapMethodHandleTestInterface.class)
+            .addProgramClassFileData(getProgramClassFileData())
+            .compileWithExpectedDiagnostics(
+                diagnotics ->
+                    diagnotics.assertErrorMessageThatMatches(
+                        containsString("Bootstrap handle invalid")));
+        fail("Expected compilation to fail");
+      } catch (CompilationFailedException e) {
+        // Expected failure.
+        return;
+      }
+    }
+    TestRunResult<?> result =
+        testForRuntime(parameters)
+            .addProgramClasses(InvalidBootstrapMethodHandleTestInterface.class)
+            .addProgramClassFileData(getProgramClassFileData())
+            .run(parameters.getRuntime(), InvalidBootstrapMethodHandleTestClass.class);
+    // The static target is valid and should run as expected.
+    if (handleTypeOpcode() == Opcodes.H_INVOKESTATIC) {
+      result.assertSuccessWithOutput(EXPECTED);
+      return;
+    }
+    // The invalid targets will trigger an error due to the bootstrap method not being able to be
+    // cast to the expected signature for a bootstrap method. This happens prior to invoking the
+    // target of that handle.
+    if (parameters.isCfRuntime()) {
+      result.assertFailureWithErrorThatThrows(BootstrapMethodError.class);
+      result.assertFailureWithErrorThatMatches(containsString("cannot convert"));
+      result.assertFailureWithErrorThatMatches(
+          containsString("to (Lookup,String,MethodType)Object"));
+      return;
+    }
+    // D8 allows new-invoke-special type during compilation, but it is not accepted by ART.
+    if (handleTypeOpcode() == Opcodes.H_NEWINVOKESPECIAL) {
+      result.assertFailureWithErrorThatMatches(
+          containsString("handle type is not InvokeStatic: 6"));
+    }
+  }
+
+  // Each handle is returned such that the method handle itself is valid and will not cause an
+  // error due to failed handle construction.
+  private Handle getHandle() {
+    String bootstrapSignature =
+        "(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;";
+
+    switch (handleTypeOpcode()) {
+      case Opcodes.H_GETFIELD:
+      case Opcodes.H_PUTFIELD:
+        return new Handle(
+            handleTypeOpcode(),
+            binaryName(InvalidBootstrapMethodHandleTestClass.class),
+            "nonStaticField",
+            "I",
+            false);
+
+      case Opcodes.H_GETSTATIC:
+      case Opcodes.H_PUTSTATIC:
+        return new Handle(
+            handleTypeOpcode(),
+            binaryName(InvalidBootstrapMethodHandleTestClass.class),
+            "staticField",
+            "I",
+            false);
+
+      case Opcodes.H_INVOKESTATIC:
+        return new Handle(
+            handleTypeOpcode(),
+            binaryName(InvalidBootstrapMethodHandleTestClass.class),
+            "staticMethod",
+            bootstrapSignature,
+            handleTypeOpcode() == Opcodes.H_INVOKEINTERFACE);
+
+      case Opcodes.H_INVOKEVIRTUAL:
+      case Opcodes.H_INVOKESPECIAL:
+        return new Handle(
+            handleTypeOpcode(),
+            binaryName(InvalidBootstrapMethodHandleTestClass.class),
+            "virtualMethod",
+            bootstrapSignature,
+            false);
+
+      case Opcodes.H_INVOKEINTERFACE:
+        return new Handle(
+            handleTypeOpcode(),
+            binaryName(InvalidBootstrapMethodHandleTestInterface.class),
+            "virtualMethod",
+            bootstrapSignature,
+            true);
+
+      case Opcodes.H_NEWINVOKESPECIAL:
+        return new Handle(
+            handleTypeOpcode(),
+            binaryName(InvalidBootstrapMethodHandleTestClass.class),
+            "<init>",
+            "()V",
+            false);
+
+      default:
+        throw new RuntimeException("Unexpected handle type");
+    }
+  }
+
+  private byte[] getProgramClassFileData() throws Exception {
+    return transformer(InvalidBootstrapMethodHandleTestClass.class)
+        .transformMethodInsnInMethod(
+            "main",
+            (opcode, owner, name, descriptor, isInterface, visitor) -> {
+              if (opcode == Opcodes.INVOKESTATIC && name.equals("foo")) {
+                visitor.visitInvokeDynamicInsn("foo", "()V", getHandle());
+              } else {
+                visitor.visitMethodInsn(opcode, owner, name, descriptor, isInterface);
+              }
+            })
+        .transform();
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/cf/methodhandles/InvalidBootstrapMethodHandleTestClass.java b/src/test/java/com/android/tools/r8/cf/methodhandles/InvalidBootstrapMethodHandleTestClass.java
new file mode 100644
index 0000000..9040a68
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/cf/methodhandles/InvalidBootstrapMethodHandleTestClass.java
@@ -0,0 +1,56 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.cf.methodhandles;
+
+import java.lang.invoke.CallSite;
+import java.lang.invoke.ConstantCallSite;
+import java.lang.invoke.MethodHandles;
+import java.lang.invoke.MethodType;
+
+interface InvalidBootstrapMethodHandleTestInterface {
+  CallSite virtualMethod(MethodHandles.Lookup caller, String name, MethodType type)
+      throws Exception;
+}
+
+// Test data class for InvalidBootstrapMethodHandleTest.
+// Making this an inner class of the test causes changes to the VMs reflection.
+public class InvalidBootstrapMethodHandleTestClass
+    implements InvalidBootstrapMethodHandleTestInterface {
+
+  // Static field to target.
+  public static int staticField = 42;
+
+  // Non-static field to target.
+  public int nonStaticField = 42;
+
+  // Virtual method to target.
+  public CallSite virtualMethod(MethodHandles.Lookup caller, String name, MethodType type)
+      throws Exception {
+    return new ConstantCallSite(caller.findStatic(caller.lookupClass(), name, type));
+  }
+
+  // Actual valid static bootstrap method.
+  public static CallSite staticMethod(MethodHandles.Lookup caller, String name, MethodType type)
+      throws Exception {
+    return new ConstantCallSite(caller.findStatic(caller.lookupClass(), name, type));
+  }
+
+  // Constructor to target.
+  public InvalidBootstrapMethodHandleTestClass() {}
+
+  // Called by the valid virtual invoke.
+  public static void foo() {
+    System.out.println("Called foo!");
+  }
+
+  public static void main(String[] args) {
+    try {
+      // Rewritten to invoke-dynamic for each handle type.
+      foo();
+    } catch (BootstrapMethodError e) {
+      System.out.println(e.getCause().getMessage());
+      throw e;
+    }
+  }
+}
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 005df04..de3afb5 100644
--- a/src/test/java/com/android/tools/r8/classmerging/KeptTargetsIncompleteDiamondTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/KeptTargetsIncompleteDiamondTest.java
@@ -86,7 +86,7 @@
     LookupResultSuccess lookupResultSuccess = lookupResult.asLookupResultSuccess();
     Set<String> targets = new HashSet<>();
     lookupResultSuccess.forEach(
-        methodTarget -> targets.add(methodTarget.asMethodTarget().getMethod().qualifiedName()),
+        methodTarget -> targets.add(methodTarget.asMethodTarget().getDefinition().qualifiedName()),
         lambdaTarget -> {
           assert false;
         });
@@ -126,7 +126,7 @@
     LookupResultSuccess lookupResultSuccess = lookupResult.asLookupResultSuccess();
     Set<String> targets = new HashSet<>();
     lookupResultSuccess.forEach(
-        methodTarget -> targets.add(methodTarget.asMethodTarget().getMethod().qualifiedName()),
+        methodTarget -> targets.add(methodTarget.asMethodTarget().getDefinition().qualifiedName()),
         lambdaTarget -> {
           assert false;
         });
@@ -165,7 +165,7 @@
     LookupResultSuccess lookupResultSuccess = lookupResult.asLookupResultSuccess();
     Set<String> targets = new HashSet<>();
     lookupResultSuccess.forEach(
-        methodTarget -> targets.add(methodTarget.asMethodTarget().getMethod().qualifiedName()),
+        methodTarget -> targets.add(methodTarget.asMethodTarget().getDefinition().qualifiedName()),
         lambdaTarget -> {
           assert false;
         });
@@ -202,7 +202,7 @@
     LookupResultSuccess lookupResultSuccess = lookupResult.asLookupResultSuccess();
     Set<String> targets = new HashSet<>();
     lookupResultSuccess.forEach(
-        methodTarget -> targets.add(methodTarget.asMethodTarget().getMethod().qualifiedName()),
+        methodTarget -> targets.add(methodTarget.asMethodTarget().getDefinition().qualifiedName()),
         lambdaTarget -> {
           assert false;
         });
@@ -241,7 +241,7 @@
     LookupResultSuccess lookupResultSuccess = lookupResult.asLookupResultSuccess();
     Set<String> targets = new HashSet<>();
     lookupResultSuccess.forEach(
-        methodTarget -> targets.add(methodTarget.asMethodTarget().getMethod().qualifiedName()),
+        methodTarget -> targets.add(methodTarget.asMethodTarget().getDefinition().qualifiedName()),
         lambdaTarget -> {
           assert false;
         });
diff --git a/src/test/java/com/android/tools/r8/classmerging/VerticalClassMergerSuperCallInStaticTest.java b/src/test/java/com/android/tools/r8/classmerging/VerticalClassMergerSuperCallInStaticTest.java
index dafc7db..cab2a18 100644
--- a/src/test/java/com/android/tools/r8/classmerging/VerticalClassMergerSuperCallInStaticTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/VerticalClassMergerSuperCallInStaticTest.java
@@ -72,7 +72,7 @@
         .transformMethodInsnInMethod(
             "callSuper",
             (opcode, owner, name, descriptor, isInterface, continuation) -> {
-              continuation.apply(
+              continuation.visitMethodInsn(
                   INVOKESPECIAL,
                   DescriptorUtils.getBinaryNameFromJavaType(Base.class.getTypeName()),
                   name,
diff --git a/src/test/java/com/android/tools/r8/desugar/DefaultMethodWithAccessTest.java b/src/test/java/com/android/tools/r8/desugar/DefaultMethodWithAccessTest.java
index 0c778d0..6cb4dd0 100644
--- a/src/test/java/com/android/tools/r8/desugar/DefaultMethodWithAccessTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/DefaultMethodWithAccessTest.java
@@ -51,7 +51,7 @@
             .transformMethodInsnInMethod(
                 "access",
                 (opcode, owner, name, descriptor, isInterface, continuation) -> {
-                  continuation.apply(
+                  continuation.visitMethodInsn(
                       name.equals("print") ? Opcodes.INVOKESPECIAL : opcode,
                       owner,
                       name,
diff --git a/src/test/java/com/android/tools/r8/desugar/PrivateMethodsInInterfaceTest.java b/src/test/java/com/android/tools/r8/desugar/PrivateMethodsInInterfaceTest.java
index 564f831..66fbdaa 100644
--- a/src/test/java/com/android/tools/r8/desugar/PrivateMethodsInInterfaceTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/PrivateMethodsInInterfaceTest.java
@@ -47,7 +47,7 @@
         .transformMethodInsnInMethod(
             "foo",
             ((opcode, owner, name, descriptor, isInterface, continuation) -> {
-              continuation.apply(
+              continuation.visitMethodInsn(
                   name.equals("bar") ? Opcodes.INVOKESPECIAL : opcode,
                   owner,
                   name,
@@ -57,7 +57,7 @@
         .transformMethodInsnInMethod(
             "baz",
             ((opcode, owner, name, descriptor, isInterface, continuation) -> {
-              continuation.apply(
+              continuation.visitMethodInsn(
                   name.equals("bar") ? Opcodes.INVOKESPECIAL : opcode,
                   owner,
                   name,
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/EmulatedInterfacesTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/EmulatedInterfacesTest.java
index 451a255..d767975 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/EmulatedInterfacesTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/EmulatedInterfacesTest.java
@@ -84,7 +84,7 @@
   }
 
   private void assertCollectionMethodsPresentWithCorrectDispatch(CodeInspector inspector) {
-    DexClass collectionDispatch = inspector.clazz("j$.util.Collection$-EL").getDexClass();
+    DexClass collectionDispatch = inspector.clazz("j$.util.Collection$-EL").getDexProgramClass();
     for (DexEncodedMethod method : collectionDispatch.methods()) {
       int numCheckCast =
           (int)
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/MergingWithDesugaredLibraryTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/MergingWithDesugaredLibraryTest.java
index eefe0f7..03052cc 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/MergingWithDesugaredLibraryTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/MergingWithDesugaredLibraryTest.java
@@ -4,10 +4,13 @@
 
 package com.android.tools.r8.desugar.desugaredlibrary;
 
-import static org.hamcrest.core.StringContains.containsString;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 
+import com.android.tools.r8.CompilationFailedException;
 import com.android.tools.r8.D8TestCompileResult;
+import com.android.tools.r8.Diagnostic;
 import com.android.tools.r8.TestDiagnosticMessages;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
@@ -16,6 +19,7 @@
 import com.android.tools.r8.utils.AndroidApiLevel;
 import java.nio.file.Path;
 import java.util.ArrayList;
+import java.util.List;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
@@ -39,39 +43,53 @@
 
   @Test
   public void testMergeDesugaredAndNonDesugared() throws Exception {
-    D8TestCompileResult compileResult =
-        testForD8()
-            .addProgramFiles(buildPart1DesugaredLibrary(), buildPart2NoDesugaredLibrary())
-            .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.P))
-            .setMinApi(parameters.getApiLevel())
-            .enableCoreLibraryDesugaring(parameters.getApiLevel())
-            .compile()
-            .addDesugaredCoreLibraryRunClassPath(
-                this::buildDesugaredLibrary, parameters.getApiLevel());
-    // TODO(b/154106502): This should raise a proper warning. The dex files are incompatible,
-    //  so the behavior is undefined regarding desugared types.
-    if (parameters.getApiLevel().getLevel() < AndroidApiLevel.N.getLevel()) {
-      compileResult
-          .run(parameters.getRuntime(), Part1.class)
-          .assertSuccessWithOutputLines(J$_RESULT);
-    } else {
-      compileResult
-          .run(parameters.getRuntime(), Part1.class)
-          .assertSuccessWithOutputLines(JAVA_RESULT);
+    D8TestCompileResult compileResult;
+    try {
+      compileResult =
+          testForD8()
+              .addProgramFiles(buildPart1DesugaredLibrary(), buildPart2NoDesugaredLibrary())
+              .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.P))
+              .setMinApi(parameters.getApiLevel())
+              .enableCoreLibraryDesugaring(parameters.getApiLevel())
+              .compileWithExpectedDiagnostics(this::assertError);
+      assertFalse(expectError());
+    } catch (CompilationFailedException e) {
+      assertTrue(expectError());
+      return;
     }
-    if (parameters.getRuntime().asDex().getMinApiLevel().getLevel()
-        < AndroidApiLevel.N.getLevel()) {
-      compileResult
-          .run(parameters.getRuntime(), Part2.class)
-          .assertFailureWithErrorThatMatches(containsString("java.lang.NoSuchMethodError"));
-    } else {
+    assert !expectError();
+    assert compileResult != null;
+    compileResult.addDesugaredCoreLibraryRunClassPath(
+        this::buildDesugaredLibrary, parameters.getApiLevel());
+    compileResult
+        .run(parameters.getRuntime(), Part1.class)
+        .assertSuccessWithOutputLines(JAVA_RESULT);
+    compileResult
+        .run(parameters.getRuntime(), Part2.class)
+        .assertSuccessWithOutputLines(JAVA_RESULT);
+  }
 
-      compileResult
-          .run(parameters.getRuntime(), Part2.class)
-          .assertSuccessWithOutputLines(JAVA_RESULT);
+  private void assertError(TestDiagnosticMessages m) {
+    List<Diagnostic> errors = m.getErrors();
+    if (expectError()) {
+      assertEquals(1, errors.size());
+      assertTrue(
+          errors.stream()
+              .anyMatch(
+                  w ->
+                      w.getDiagnosticMessage()
+                          .contains(
+                              "The compilation is merging inputs with different"
+                                  + " desugared library desugaring")));
+    } else {
+      assertEquals(0, errors.size());
     }
   }
 
+  private boolean expectError() {
+    return parameters.getApiLevel().getLevel() <= AndroidApiLevel.N.getLevel();
+  }
+
   @Test
   public void testMergeDesugaredAndClassFile() throws Exception {
     D8TestCompileResult compileResult =
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/ConversionIntroduceInterfaceMethodTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/ConversionIntroduceInterfaceMethodTest.java
index 835d1cb..83dc73e 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/ConversionIntroduceInterfaceMethodTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/ConversionIntroduceInterfaceMethodTest.java
@@ -10,7 +10,6 @@
 
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.desugar.desugaredlibrary.DesugaredLibraryTestBase;
-import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.StringUtils;
@@ -99,12 +98,12 @@
                                 "com.android.tools.r8.desugar.desugaredlibrary.conversiontests")
                         && !c.getOriginalName().contains("Executor")
                         && !c.getOriginalName().contains("$-CC")
-                        && !c.getDexClass().isInterface())
+                        && !c.getDexProgramClass().isInterface())
             .collect(toSingle());
     assertEquals(
         "Missing duplicated forEach",
         2,
-        myCollection.getDexClass().virtualMethods().stream()
+        myCollection.getDexProgramClass().virtualMethods().stream()
             .filter(m -> m.method.name.toString().equals("forEach"))
             .count());
   }
diff --git a/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/FullNestOnProgramPathTest.java b/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/FullNestOnProgramPathTest.java
index 55f40c4..e4447b53 100644
--- a/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/FullNestOnProgramPathTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/FullNestOnProgramPathTest.java
@@ -168,7 +168,7 @@
         ImmutableList.of("NeverInline", "OutsideInliningNoAccess", "OutsideInliningWithAccess");
     inspector.forAllClasses(
         classSubject -> {
-          DexClass dexClass = classSubject.getDexClass();
+          DexClass dexClass = classSubject.getDexProgramClass();
           if (!nonNestClasses.contains(dexClass.type.getName())) {
             assertTrue(dexClass.isInANest());
             if (outerClassNames.contains(dexClass.type.getName())) {
diff --git a/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/Java11D8CompilationTest.java b/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/Java11D8CompilationTest.java
index 5c8018e..6fdfe97 100644
--- a/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/Java11D8CompilationTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/Java11D8CompilationTest.java
@@ -32,13 +32,14 @@
   }
 
   private static void assertNoNests(CodeInspector inspector) {
-    assertTrue(inspector.allClasses().stream().noneMatch(subj -> subj.getDexClass().isInANest()));
+    assertTrue(
+        inspector.allClasses().stream().noneMatch(subj -> subj.getDexProgramClass().isInANest()));
   }
 
   @Test
   public void testR8CompiledWithD8() throws Exception {
     testForD8()
-        .addProgramFiles(ToolHelper.R8_WITH_RELOCATED_DEPS_JAR_11)
+        .addProgramFiles(ToolHelper.R8_WITH_RELOCATED_DEPS_11_JAR)
         .compile()
         .inspect(Java11D8CompilationTest::assertNoNests);
   }
diff --git a/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/Java11R8BootstrapTest.java b/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/Java11R8BootstrapTest.java
index 1f6832c..5a788bf 100644
--- a/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/Java11R8BootstrapTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/Java11R8BootstrapTest.java
@@ -64,7 +64,7 @@
   private static Path compileR8(boolean desugar) throws Exception {
     // Shrink R8 11 with R8
     return testForR8(TestBase.getStaticTemp(), Backend.CF)
-        .addProgramFiles(ToolHelper.R8_WITH_RELOCATED_DEPS_JAR_11)
+        .addProgramFiles(ToolHelper.R8_WITH_RELOCATED_DEPS_11_JAR)
         .addKeepRuleFiles(MAIN_KEEP)
         .addOptionsModification(
             options -> options.testing.enableForceNestBasedAccessDesugaringForTest = desugar)
@@ -75,16 +75,18 @@
 
   private static void assertNests(CodeInspector inspector, boolean desugar) {
     if (desugar) {
-      assertTrue(inspector.allClasses().stream().noneMatch(subj -> subj.getDexClass().isInANest()));
+      assertTrue(
+          inspector.allClasses().stream().noneMatch(subj -> subj.getDexProgramClass().isInANest()));
     } else {
-      assertTrue(inspector.allClasses().stream().anyMatch(subj -> subj.getDexClass().isInANest()));
+      assertTrue(
+          inspector.allClasses().stream().anyMatch(subj -> subj.getDexProgramClass().isInANest()));
     }
   }
 
   private Path[] jarsToCompare() {
     return new Path[] {
       ToolHelper.R8_WITH_RELOCATED_DEPS_JAR,
-      ToolHelper.R8_WITH_RELOCATED_DEPS_JAR_11,
+      ToolHelper.R8_WITH_RELOCATED_DEPS_11_JAR,
       r8Lib11NoDesugar,
       r8Lib11Desugar
     };
diff --git a/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/Java11R8CompilationTest.java b/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/Java11R8CompilationTest.java
index a79ccf7..762916e 100644
--- a/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/Java11R8CompilationTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/Java11R8CompilationTest.java
@@ -41,14 +41,15 @@
   private static final Path MAIN_KEEP = Paths.get("src/main/keep.txt");
 
   private static void assertNoNests(CodeInspector inspector) {
-    assertTrue(inspector.allClasses().stream().noneMatch(subj -> subj.getDexClass().isInANest()));
+    assertTrue(
+        inspector.allClasses().stream().noneMatch(subj -> subj.getDexProgramClass().isInANest()));
   }
 
   @Test
   public void testR8CompiledWithR8() throws Exception {
     testForR8(parameters.getBackend())
         .setMinApi(parameters.getApiLevel())
-        .addProgramFiles(ToolHelper.R8_WITH_RELOCATED_DEPS_JAR_11)
+        .addProgramFiles(ToolHelper.R8_WITH_RELOCATED_DEPS_11_JAR)
         .addKeepRuleFiles(MAIN_KEEP)
         .addOptionsModification(opt -> opt.ignoreMissingClasses = true)
         .allowDiagnosticWarningMessages()
diff --git a/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/NestAttributesUpdateTest.java b/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/NestAttributesUpdateTest.java
index e0c7e12..816a233 100644
--- a/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/NestAttributesUpdateTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/NestAttributesUpdateTest.java
@@ -107,7 +107,7 @@
   public static void assertNestAttributesCorrect(CodeInspector inspector) {
     assertTrue(inspector.allClasses().size() > 0);
     for (FoundClassSubject classSubject : inspector.allClasses()) {
-      DexClass clazz = classSubject.getDexClass();
+      DexClass clazz = classSubject.getDexProgramClass();
       if (clazz.isInANest()) {
         if (clazz.isNestHost()) {
           // All members are present with the clazz as host
@@ -116,8 +116,8 @@
             ClassSubject inner = inspector.clazz(PACKAGE_NAME + memberName);
             assertNotNull(
                 "The nest member " + memberName + " of " + clazz.type.getName() + " is missing",
-                inner.getDexClass());
-            assertSame(inner.getDexClass().getNestHost(), clazz.type);
+                inner.getDexProgramClass());
+            assertSame(inner.getDexProgramClass().getNestHost(), clazz.type);
           }
         } else {
           // Nest host is present and with the clazz as member
@@ -125,9 +125,9 @@
           ClassSubject host = inspector.clazz(PACKAGE_NAME + hostName);
           assertNotNull(
               "The nest host " + hostName + " of " + clazz.type.getName() + " is missing",
-              host.getDexClass());
+              host.getDexProgramClass());
           assertTrue(
-              host.getDexClass().getNestMembersClassAttributes().stream()
+              host.getDexProgramClass().getNestMembersClassAttributes().stream()
                   .anyMatch(attr -> attr.getNestMember() == clazz.type));
         }
       }
diff --git a/src/test/java/com/android/tools/r8/desugaring/interfacemethods/DefaultInterfaceMethodDesugaringWithStaticResolutionInvokeVirtualTest.java b/src/test/java/com/android/tools/r8/desugaring/interfacemethods/DefaultInterfaceMethodDesugaringWithStaticResolutionInvokeVirtualTest.java
index a8c5c6f..e2474ca 100644
--- a/src/test/java/com/android/tools/r8/desugaring/interfacemethods/DefaultInterfaceMethodDesugaringWithStaticResolutionInvokeVirtualTest.java
+++ b/src/test/java/com/android/tools/r8/desugaring/interfacemethods/DefaultInterfaceMethodDesugaringWithStaticResolutionInvokeVirtualTest.java
@@ -64,14 +64,14 @@
                 (opcode, owner, name, descriptor, isInterface, continuation) -> {
                   if (invalidInvoke && opcode == Opcodes.INVOKEVIRTUAL) {
                     assertEquals("m", name);
-                    continuation.apply(
+                    continuation.visitMethodInsn(
                         opcode,
                         DescriptorUtils.getBinaryNameFromJavaType(C.class.getTypeName()),
                         name,
                         descriptor,
                         isInterface);
                   } else {
-                    continuation.apply(opcode, owner, name, descriptor, isInterface);
+                    continuation.visitMethodInsn(opcode, owner, name, descriptor, isInterface);
                   }
                 })
             .transform());
diff --git a/src/test/java/com/android/tools/r8/dexfilemerger/DexMergeChecksumsFileWithNoClassesTest.java b/src/test/java/com/android/tools/r8/dexfilemerger/DexMergeChecksumsFileWithNoClassesTest.java
index 57fe4b3..c987370 100644
--- a/src/test/java/com/android/tools/r8/dexfilemerger/DexMergeChecksumsFileWithNoClassesTest.java
+++ b/src/test/java/com/android/tools/r8/dexfilemerger/DexMergeChecksumsFileWithNoClassesTest.java
@@ -65,7 +65,7 @@
     inspector.getMarkers().forEach(m -> assertTrue(m.getHasChecksums()));
     // It may be prudent to check that the dex file also has the encoding string, but that is
     // not easily accessed.
-    inspector.allClasses().forEach(c -> c.getDexClass().asProgramClass().getChecksum());
+    inspector.allClasses().forEach(c -> c.getDexProgramClass().asProgramClass().getChecksum());
   }
 
   public static class TestClass {
diff --git a/src/test/java/com/android/tools/r8/dexfilemerger/NonAsciiClassNameChecksumTest.java b/src/test/java/com/android/tools/r8/dexfilemerger/NonAsciiClassNameChecksumTest.java
index 8e4700b..b91a645 100644
--- a/src/test/java/com/android/tools/r8/dexfilemerger/NonAsciiClassNameChecksumTest.java
+++ b/src/test/java/com/android/tools/r8/dexfilemerger/NonAsciiClassNameChecksumTest.java
@@ -83,7 +83,7 @@
   private void checkIncludesChecksum(CodeInspector inspector, Class<?> clazz) {
     ClassSubject classSubject = inspector.clazz(getTransformedName(clazz));
     assertThat(classSubject, isPresent());
-    assertTrue(classSubject.getDexClass().asProgramClass().getChecksum() > 0);
+    assertTrue(classSubject.getDexProgramClass().asProgramClass().getChecksum() > 0);
   }
 
   static class TaestClass {
diff --git a/src/test/java/com/android/tools/r8/dexsplitter/DexSplitterInlineRegression.java b/src/test/java/com/android/tools/r8/dexsplitter/DexSplitterInlineRegression.java
index 14d1e63..8f6a2d6 100644
--- a/src/test/java/com/android/tools/r8/dexsplitter/DexSplitterInlineRegression.java
+++ b/src/test/java/com/android/tools/r8/dexsplitter/DexSplitterInlineRegression.java
@@ -4,7 +4,10 @@
 
 package com.android.tools.r8.dexsplitter;
 
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
 import static junit.framework.TestCase.assertEquals;
+import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.MatcherAssert.assertThat;
 import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assume.assumeTrue;
@@ -16,13 +19,13 @@
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.ToolHelper.ProcessResult;
+import com.android.tools.r8.utils.ConsumerUtils;
 import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.ThrowingConsumer;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.google.common.collect.ImmutableSet;
 import java.io.IOException;
-import java.util.concurrent.ExecutionException;
 import java.util.function.Consumer;
-import java.util.function.Predicate;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
@@ -46,15 +49,11 @@
 
   @Test
   public void testInliningFromFeature() throws Exception {
-    Predicate<R8TestCompileResult> ensureGetFromFeatureGone =
+    ThrowingConsumer<R8TestCompileResult, Exception> ensureGetFromFeatureGone =
         r8TestCompileResult -> {
           // Ensure that getFromFeature from FeatureClass is inlined into the run method.
-          try {
-            ClassSubject clazz = r8TestCompileResult.inspector().clazz(FeatureClass.class);
-            return clazz.uniqueMethodWithName("getFromFeature").isAbsent();
-          } catch (IOException | ExecutionException ex) {
-            throw new RuntimeException("Found getFromFeature in FeatureClass");
-          }
+          ClassSubject clazz = r8TestCompileResult.inspector().clazz(FeatureClass.class);
+          assertThat(clazz.uniqueMethodWithName("getFromFeature"), not(isPresent()));
         };
     Consumer<R8FullTestBuilder> configurator =
         r8FullTestBuilder -> r8FullTestBuilder.enableMergeAnnotations().noMinification();
@@ -83,8 +82,7 @@
             ImmutableSet.of(BaseSuperClass.class),
             ImmutableSet.of(FeatureClass.class),
             FeatureClass.class,
-            EXPECTED,
-            a -> true,
+            ConsumerUtils.emptyThrowingConsumer(),
             configurator);
 
     assertEquals(processResult.exitCode, 0);
diff --git a/src/test/java/com/android/tools/r8/dexsplitter/DexSplitterMemberValuePropagationRegression.java b/src/test/java/com/android/tools/r8/dexsplitter/DexSplitterMemberValuePropagationRegression.java
new file mode 100644
index 0000000..fa3ae88
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/dexsplitter/DexSplitterMemberValuePropagationRegression.java
@@ -0,0 +1,114 @@
+// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.dexsplitter;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.R8TestBuilder;
+import com.android.tools.r8.R8TestCompileResult;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.ToolHelper.ProcessResult;
+import com.android.tools.r8.utils.ConsumerUtils;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.ThrowingConsumer;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.google.common.collect.ImmutableSet;
+import java.io.IOException;
+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 DexSplitterMemberValuePropagationRegression extends SplitterTestBase {
+
+  public static final String EXPECTED = StringUtils.lines("42");
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection params() {
+    return getTestParameters().withAllRuntimes().build();
+  }
+
+  private final TestParameters parameters;
+
+  public DexSplitterMemberValuePropagationRegression(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testPropagationFromFeature() throws Exception {
+    ThrowingConsumer<R8TestCompileResult, Exception> ensureGetFromFeatureGone =
+        r8TestCompileResult -> {
+          // Ensure that getFromFeature from FeatureClass is inlined into the run method.
+          ClassSubject clazz = r8TestCompileResult.inspector().clazz(FeatureClass.class);
+          assertThat(clazz.uniqueMethodWithName("getFromFeature"), not(isPresent()));
+        };
+    ProcessResult processResult =
+        testDexSplitter(
+            parameters,
+            ImmutableSet.of(BaseSuperClass.class),
+            ImmutableSet.of(FeatureClass.class, FeatureEnum.class),
+            FeatureClass.class,
+            EXPECTED,
+            ensureGetFromFeatureGone,
+            builder -> builder.enableInliningAnnotations().noMinification());
+    // We expect art to fail on this with the dex splitter, see b/122902374
+    assertNotEquals(processResult.exitCode, 0);
+    assertTrue(processResult.stderr.contains("NoClassDefFoundError"));
+  }
+
+  @Test
+  public void testOnR8Splitter() throws IOException, CompilationFailedException {
+    assumeTrue(parameters.isDexRuntime());
+    ProcessResult processResult =
+        testR8Splitter(
+            parameters,
+            ImmutableSet.of(BaseSuperClass.class),
+            ImmutableSet.of(FeatureClass.class, FeatureEnum.class),
+            FeatureClass.class,
+            ConsumerUtils.emptyThrowingConsumer(),
+            R8TestBuilder::enableInliningAnnotations);
+    assertEquals(processResult.exitCode, 0);
+    assertEquals(processResult.stdout, EXPECTED);
+  }
+
+  public abstract static class BaseSuperClass implements RunInterface {
+
+    @NeverInline
+    @Override
+    public void run() {
+      System.out.println(getFromFeature());
+    }
+
+    public abstract Enum<?> getFromFeature();
+  }
+
+  public static class FeatureClass extends BaseSuperClass {
+
+    @NeverInline
+    @Override
+    public Enum<?> getFromFeature() {
+      return FeatureEnum.A;
+    }
+  }
+
+  public enum FeatureEnum {
+    A;
+
+    @Override
+    public String toString() {
+      return "42";
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/dexsplitter/DexSplitterMergeRegression.java b/src/test/java/com/android/tools/r8/dexsplitter/DexSplitterMergeRegression.java
index 25cbe9a..067f920 100644
--- a/src/test/java/com/android/tools/r8/dexsplitter/DexSplitterMergeRegression.java
+++ b/src/test/java/com/android/tools/r8/dexsplitter/DexSplitterMergeRegression.java
@@ -4,13 +4,14 @@
 
 package com.android.tools.r8.dexsplitter;
 
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assume.assumeTrue;
 
 import com.android.tools.r8.CompilationFailedException;
-import com.android.tools.r8.FeatureSplit;
 import com.android.tools.r8.NeverInline;
 import com.android.tools.r8.NeverMerge;
 import com.android.tools.r8.R8FullTestBuilder;
@@ -18,13 +19,13 @@
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.ToolHelper.ProcessResult;
+import com.android.tools.r8.utils.ConsumerUtils;
 import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.ThrowingConsumer;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.google.common.collect.ImmutableSet;
 import java.io.IOException;
-import java.util.concurrent.ExecutionException;
 import java.util.function.Consumer;
-import java.util.function.Predicate;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
@@ -49,16 +50,12 @@
   @Test
   public void testInliningFromFeature() throws Exception {
     // Static merging is based on sorting order, we assert that we merged to the feature.
-    Predicate<R8TestCompileResult> ensureMergingToFeature =
+    ThrowingConsumer<R8TestCompileResult, Exception> ensureMergingToFeature =
         r8TestCompileResult -> {
-          try {
-            ClassSubject clazz = r8TestCompileResult.inspector().clazz(AFeatureWithStatic.class);
-            return clazz.allMethods().size() == 2
-                && clazz.uniqueMethodWithName("getBase42").isPresent()
-                && clazz.uniqueMethodWithName("getFoobar").isPresent();
-          } catch (IOException | ExecutionException ex) {
-            throw new RuntimeException("Failed lookup up AFeatureWithStatic");
-          }
+          ClassSubject clazz = r8TestCompileResult.inspector().clazz(AFeatureWithStatic.class);
+          assertEquals(2, clazz.allMethods().size());
+          assertThat(clazz.uniqueMethodWithName("getBase42"), isPresent());
+          assertThat(clazz.uniqueMethodWithName("getFoobar"), isPresent());
         };
     Consumer<R8FullTestBuilder> configurator =
         r8FullTestBuilder ->
@@ -82,8 +79,7 @@
   }
 
   @Test
-  public void testOnR8Splitter() throws IOException, CompilationFailedException,
-      ExecutionException {
+  public void testOnR8Splitter() throws IOException, CompilationFailedException {
     assumeTrue(parameters.isDexRuntime());
     Consumer<R8FullTestBuilder> configurator =
         r8FullTestBuilder -> r8FullTestBuilder.enableMergeAnnotations().noMinification();
@@ -93,8 +89,7 @@
             ImmutableSet.of(BaseClass.class, BaseWithStatic.class),
             ImmutableSet.of(FeatureClass.class, AFeatureWithStatic.class),
             FeatureClass.class,
-            EXPECTED,
-            a -> true,
+            ConsumerUtils.emptyThrowingConsumer(),
             configurator);
 
     assertEquals(processResult.exitCode, 0);
diff --git a/src/test/java/com/android/tools/r8/dexsplitter/R8SplitterInlineToFeature.java b/src/test/java/com/android/tools/r8/dexsplitter/R8SplitterInlineToFeature.java
new file mode 100644
index 0000000..331403a
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/dexsplitter/R8SplitterInlineToFeature.java
@@ -0,0 +1,98 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.dexsplitter;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static junit.framework.TestCase.assertEquals;
+import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.NeverMerge;
+import com.android.tools.r8.R8FullTestBuilder;
+import com.android.tools.r8.R8TestCompileResult;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.ToolHelper.ProcessResult;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.ThrowingConsumer;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.google.common.collect.ImmutableSet;
+import java.util.function.Consumer;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+/**
+ * We don't allow to inline code from the base into a feature, but we do allow inlining code from
+ * the the base into the feature.
+ */
+@RunWith(Parameterized.class)
+public class R8SplitterInlineToFeature extends SplitterTestBase {
+
+  public static final String EXPECTED = StringUtils.lines("42");
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection params() {
+    return getTestParameters().withDexRuntimes().build();
+  }
+
+  private final TestParameters parameters;
+
+  public R8SplitterInlineToFeature(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testInlining() throws Exception {
+    assumeTrue(parameters.isDexRuntime());
+    Consumer<R8FullTestBuilder> configurator =
+        r8FullTestBuilder -> r8FullTestBuilder.enableMergeAnnotations().noMinification();
+    ThrowingConsumer<R8TestCompileResult, Exception> ensureInlined =
+        r8TestCompileResult -> {
+          // Ensure that isEarly from BaseUtilClass is inlined into the feature
+          ClassSubject clazz = r8TestCompileResult.inspector().clazz(BaseUtilClass.class);
+          assertThat(clazz.uniqueMethodWithName("isEarly"), not(isPresent()));
+        };
+    ProcessResult processResult =
+        testR8Splitter(
+            parameters,
+            ImmutableSet.of(BaseSuperClass.class, BaseUtilClass.class),
+            ImmutableSet.of(FeatureClass.class),
+            FeatureClass.class,
+            ensureInlined,
+            configurator);
+
+    assertEquals(processResult.exitCode, 0);
+    assertEquals(processResult.stdout, StringUtils.lines("42"));
+  }
+
+  @NeverMerge
+  public abstract static class BaseSuperClass implements RunInterface {
+    public void run() {
+      System.out.println(getFromFeature());
+    }
+
+    public abstract String getFromFeature();
+  }
+
+  public static class BaseUtilClass {
+    public static boolean isEarly() {
+      return System.currentTimeMillis() < 2;
+    }
+  }
+
+  public static class FeatureClass extends BaseSuperClass {
+    @Override
+    public String getFromFeature() {
+      if (BaseUtilClass.isEarly()) {
+        return "Very early";
+      } else {
+        return "42";
+      }
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/dexsplitter/SplitterTestBase.java b/src/test/java/com/android/tools/r8/dexsplitter/SplitterTestBase.java
index 1e31022..16291c4 100644
--- a/src/test/java/com/android/tools/r8/dexsplitter/SplitterTestBase.java
+++ b/src/test/java/com/android/tools/r8/dexsplitter/SplitterTestBase.java
@@ -19,6 +19,7 @@
 import com.android.tools.r8.dexsplitter.DexSplitter.Options;
 import com.android.tools.r8.utils.ArchiveResourceProvider;
 import com.android.tools.r8.utils.DescriptorUtils;
+import com.android.tools.r8.utils.ThrowingConsumer;
 import com.android.tools.r8.utils.ZipUtils;
 import com.google.common.collect.ImmutableList;
 import com.google.common.io.ByteStreams;
@@ -32,7 +33,6 @@
 import java.util.List;
 import java.util.Set;
 import java.util.function.Consumer;
-import java.util.function.Predicate;
 import java.util.stream.Collectors;
 import java.util.zip.ZipEntry;
 import java.util.zip.ZipInputStream;
@@ -121,20 +121,19 @@
       TemporaryFolder temp,
       Collection<String> nonJavaFiles,
       boolean ensureClassesInOutput,
-      Class... classes) {
+      Class<?>... classes) {
     addConsumers(builder, outputPath, temp, nonJavaFiles, true, Arrays.asList(classes));
     return builder.build();
   }
 
-  protected ProcessResult testR8Splitter(
+  protected <E extends Throwable> ProcessResult testR8Splitter(
       TestParameters parameters,
       Set<Class<?>> baseClasses,
       Set<Class<?>> featureClasses,
-      Class toRun,
-      String expectedOutput,
-      Predicate<R8TestCompileResult> predicate,
+      Class<?> toRun,
+      ThrowingConsumer<R8TestCompileResult, E> compileResultConsumer,
       Consumer<R8FullTestBuilder> r8TestConfigurator)
-      throws IOException, CompilationFailedException {
+      throws IOException, CompilationFailedException, E {
     Path featureOutput = temp.newFile("feature.zip").toPath();
 
     R8FullTestBuilder r8FullTestBuilder = testForR8(parameters.getBackend());
@@ -158,7 +157,7 @@
     r8TestConfigurator.accept(r8FullTestBuilder);
 
     R8TestCompileResult r8TestCompileResult = r8FullTestBuilder.compile();
-    assertTrue(predicate.test(r8TestCompileResult));
+    compileResultConsumer.accept(r8TestCompileResult);
     Path baseOutput = r8TestCompileResult.writeToZip();
 
     return runFeatureOnArt(toRun, baseOutput, featureOutput, parameters.getRuntime());
@@ -166,15 +165,15 @@
 
   // Compile the passed in classes plus RunInterface and SplitRunner using R8, then split
   // based on the base/feature sets. toRun must implement the BaseRunInterface
-  protected ProcessResult testDexSplitter(
+  protected <E extends Throwable> ProcessResult testDexSplitter(
       TestParameters parameters,
       Set<Class<?>> baseClasses,
       Set<Class<?>> featureClasses,
-      Class toRun,
+      Class<?> toRun,
       String expectedOutput,
-      Predicate<R8TestCompileResult> predicate,
+      ThrowingConsumer<R8TestCompileResult, E> compileResultConsumer,
       Consumer<R8FullTestBuilder> r8TestConfigurator)
-      throws Exception {
+      throws Exception, E {
     List<Class<?>> baseClassesWithRunner =
         ImmutableList.<Class<?>>builder()
             .add(RunInterface.class, SplitRunner.class)
@@ -229,7 +228,7 @@
             .addKeepClassRules(toRun);
     r8TestConfigurator.accept(r8FullTestBuilder);
     R8TestCompileResult r8TestCompileResult = r8FullTestBuilder.compile();
-    assertTrue(predicate.test(r8TestCompileResult));
+    compileResultConsumer.accept(r8TestCompileResult);
     Path fullFiles = r8TestCompileResult.writeToZip();
 
     // Ensure that we can run the program as a unit (i.e., without splitting)
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/AnnotationEnumUnboxingTest.java b/src/test/java/com/android/tools/r8/enumunboxing/AnnotationEnumUnboxingTest.java
index 7cc0930..1cf4e93 100644
--- a/src/test/java/com/android/tools/r8/enumunboxing/AnnotationEnumUnboxingTest.java
+++ b/src/test/java/com/android/tools/r8/enumunboxing/AnnotationEnumUnboxingTest.java
@@ -5,7 +5,10 @@
 
 import com.android.tools.r8.NeverClassInline;
 import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NeverMerge;
+import com.android.tools.r8.NeverPropagateValue;
 import com.android.tools.r8.TestParameters;
+import java.lang.annotation.Annotation;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.util.List;
@@ -41,16 +44,14 @@
         enumKeepRules.toString().equals("none"));
     testForR8(parameters.getBackend())
         .addInnerClasses(AnnotationEnumUnboxingTest.class)
+        .noMinification()
         .addKeepMainRule(Main.class)
         .addKeepRules(enumKeepRules.getKeepRule())
-        .addKeepRules(
-            "-keep @interface"
-                + " com.android.tools.r8.enumunboxing."
-                + "AnnotationEnumUnboxingTest$ClassAnnotationDefault"
-                + " {  }",
-            "-keepattributes *Annotation*")
+        .addKeepClassRules(ClassAnnotationDefault.class)
+        .addKeepRuntimeVisibleAnnotations()
         .enableNeverClassInliningAnnotations()
         .enableInliningAnnotations()
+        .enableMergeAnnotations()
         .addOptionsModification(opt -> enableEnumOptions(opt, enumValueOptimization))
         .allowDiagnosticInfoMessages()
         .setMinApi(parameters.getApiLevel())
@@ -62,9 +63,13 @@
               assertEnumIsBoxed(
                   MyEnumArrayDefault.class, MyEnumArrayDefault.class.getSimpleName(), m);
               assertEnumIsBoxed(MyEnumArray.class, MyEnumArray.class.getSimpleName(), m);
+              assertEnumIsUnboxed(
+                  MyEnumRetMethod2.class, MyEnumRetMethod2.class.getSimpleName(), m);
+              assertEnumIsUnboxed(
+                  MyEnumParamMethod2.class, MyEnumParamMethod2.class.getSimpleName(), m);
             })
         .run(parameters.getRuntime(), Main.class)
-        .assertSuccessWithOutputLines("print", "1", "1", "1", "1", "1", "0", "0", "0");
+        .assertSuccessWithOutputLines("print", "1", "1", "1", "1", "1", "0", "0", "0", "0");
   }
 
   @Retention(RetentionPolicy.RUNTIME)
@@ -78,6 +83,20 @@
     MyEnumArray[] myEnumArray();
   }
 
+  @NeverMerge
+  @Retention(RetentionPolicy.RUNTIME)
+  @interface ClassAnnotation {}
+
+  enum MyEnumParamMethod2 {
+    A,
+    B
+  }
+
+  enum MyEnumRetMethod2 {
+    A,
+    B
+  }
+
   enum MyEnumDefault {
     A,
     B
@@ -111,6 +130,20 @@
     }
   }
 
+  static class ClassAnnotationSub implements ClassAnnotation {
+
+    @NeverInline
+    @NeverPropagateValue
+    MyEnumRetMethod2 enumMethod(MyEnumParamMethod2 param) {
+      return param == MyEnumParamMethod2.A ? MyEnumRetMethod2.A : MyEnumRetMethod2.B;
+    }
+
+    @Override
+    public Class<? extends Annotation> annotationType() {
+      return null;
+    }
+  }
+
   static class Main {
     public static void main(String[] args) {
       new ClassDefault().print();
@@ -124,6 +157,7 @@
       System.out.println(annotation.myEnumArray()[0].ordinal());
       System.out.println(annotation.myEnumArrayDefault()[0].ordinal());
       System.out.println(annotation.myEnumDefault().ordinal());
+      System.out.println(new ClassAnnotationSub().enumMethod(MyEnumParamMethod2.A).ordinal());
     }
   }
 }
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/EnumClinitWithSideEffectsUnboxingTest.java b/src/test/java/com/android/tools/r8/enumunboxing/EnumClinitWithSideEffectsUnboxingTest.java
index 2fbd235..c3e548e 100644
--- a/src/test/java/com/android/tools/r8/enumunboxing/EnumClinitWithSideEffectsUnboxingTest.java
+++ b/src/test/java/com/android/tools/r8/enumunboxing/EnumClinitWithSideEffectsUnboxingTest.java
@@ -1,7 +1,6 @@
 package com.android.tools.r8.enumunboxing;
 
 import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.utils.InternalOptions;
 import java.util.List;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -31,7 +30,6 @@
         .addInnerClasses(EnumClinitWithSideEffectsUnboxingTest.class)
         .addKeepMainRule(TestClass.class)
         .addKeepRules(enumKeepRule.getKeepRule())
-        .addOptionsModification(InternalOptions::enableEnumUnboxing)
         .setMinApi(parameters.getApiLevel())
         .compile()
         .run(parameters.getRuntime(), TestClass.class)
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/EnumInitWithSideEffectsUnboxingTest.java b/src/test/java/com/android/tools/r8/enumunboxing/EnumInitWithSideEffectsUnboxingTest.java
index d353692..b9445c2 100644
--- a/src/test/java/com/android/tools/r8/enumunboxing/EnumInitWithSideEffectsUnboxingTest.java
+++ b/src/test/java/com/android/tools/r8/enumunboxing/EnumInitWithSideEffectsUnboxingTest.java
@@ -1,7 +1,6 @@
 package com.android.tools.r8.enumunboxing;
 
 import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.utils.InternalOptions;
 import java.util.List;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -31,7 +30,6 @@
         .addInnerClasses(EnumInitWithSideEffectsUnboxingTest.class)
         .addKeepMainRule(TestClass.class)
         .addKeepRules(enumKeepRule.getKeepRule())
-        .addOptionsModification(InternalOptions::enableEnumUnboxing)
         .setMinApi(parameters.getApiLevel())
         .compile()
         .run(parameters.getRuntime(), TestClass.class)
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/FailingEnumUnboxingTest.java b/src/test/java/com/android/tools/r8/enumunboxing/FailingEnumUnboxingTest.java
index ad16ee1..0441cbf 100644
--- a/src/test/java/com/android/tools/r8/enumunboxing/FailingEnumUnboxingTest.java
+++ b/src/test/java/com/android/tools/r8/enumunboxing/FailingEnumUnboxingTest.java
@@ -82,12 +82,13 @@
   }
 
   private void assertEnumsAsExpected(CodeInspector inspector) {
-    assertEquals(1, inspector.clazz(EnumInterface.class).getDexClass().interfaces.size());
+    assertEquals(1, inspector.clazz(EnumInterface.class).getDexProgramClass().interfaces.size());
 
     assertTrue(inspector.clazz(EnumStaticField.class).uniqueFieldWithName("X").isPresent());
     assertTrue(inspector.clazz(EnumInstanceField.class).uniqueFieldWithName("a").isPresent());
 
-    assertEquals(5, inspector.clazz(EnumStaticMethod.class).getDexClass().directMethods().size());
+    assertEquals(
+        5, inspector.clazz(EnumStaticMethod.class).getDexProgramClass().directMethods().size());
     assertEquals(1, inspector.clazz(EnumVirtualMethod.class).virtualMethods().size());
   }
 
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/FailingMethodEnumUnboxingTest.java b/src/test/java/com/android/tools/r8/enumunboxing/FailingMethodEnumUnboxingTest.java
index 8e7be57..411a747 100644
--- a/src/test/java/com/android/tools/r8/enumunboxing/FailingMethodEnumUnboxingTest.java
+++ b/src/test/java/com/android/tools/r8/enumunboxing/FailingMethodEnumUnboxingTest.java
@@ -85,9 +85,10 @@
     // Check all as expected (else we test nothing)
 
     assertEquals(
-        1, inspector.clazz(InstanceFieldPutObject.class).getDexClass().instanceFields().size());
+        1,
+        inspector.clazz(InstanceFieldPutObject.class).getDexProgramClass().instanceFields().size());
     assertEquals(
-        1, inspector.clazz(StaticFieldPutObject.class).getDexClass().staticFields().size());
+        1, inspector.clazz(StaticFieldPutObject.class).getDexProgramClass().staticFields().size());
 
     assertTrue(inspector.clazz(FailingPhi.class).uniqueMethodWithName("switchOn").isPresent());
   }
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/FieldPutEnumUnboxingTest.java b/src/test/java/com/android/tools/r8/enumunboxing/FieldPutEnumUnboxingTest.java
index eedc8f1..8ab4bcf 100644
--- a/src/test/java/com/android/tools/r8/enumunboxing/FieldPutEnumUnboxingTest.java
+++ b/src/test/java/com/android/tools/r8/enumunboxing/FieldPutEnumUnboxingTest.java
@@ -56,9 +56,10 @@
             .inspect(
                 i -> {
                   assertEquals(
-                      1, i.clazz(InstanceFieldPut.class).getDexClass().instanceFields().size());
+                      1,
+                      i.clazz(InstanceFieldPut.class).getDexProgramClass().instanceFields().size());
                   assertEquals(
-                      1, i.clazz(StaticFieldPut.class).getDexClass().staticFields().size());
+                      1, i.clazz(StaticFieldPut.class).getDexProgramClass().staticFields().size());
                 });
 
     for (Class<?> input : INPUTS) {
diff --git a/src/test/java/com/android/tools/r8/graph/DexTypeTest.java b/src/test/java/com/android/tools/r8/graph/DexTypeTest.java
index 1b5ffa9..014249b 100644
--- a/src/test/java/com/android/tools/r8/graph/DexTypeTest.java
+++ b/src/test/java/com/android/tools/r8/graph/DexTypeTest.java
@@ -19,7 +19,7 @@
 public class DexTypeTest {
 
   private static DexItemFactory factory;
-  private static AppInfoWithSubtyping appInfo;
+  private static AppInfoWithClassHierarchy appInfo;
 
   @BeforeClass
   public static void makeAppInfo() throws Exception {
@@ -35,7 +35,7 @@
             .read()
             .toDirect();
     factory = options.itemFactory;
-    appInfo = new AppInfoWithSubtyping(application);
+    appInfo = new AppInfoWithClassHierarchy(application);
   }
 
   @Test
diff --git a/src/test/java/com/android/tools/r8/graph/GenericSignatureTest.java b/src/test/java/com/android/tools/r8/graph/GenericSignatureTest.java
index 088b075..5001645 100644
--- a/src/test/java/com/android/tools/r8/graph/GenericSignatureTest.java
+++ b/src/test/java/com/android/tools/r8/graph/GenericSignatureTest.java
@@ -101,7 +101,7 @@
     //
 
     // class <T:GenericSignatureTestClassA<T>.Y>CYY<T extends A<T>.Y> extends CY<T>
-    DexClass clazz = cyy.getDexClass();
+    DexClass clazz = cyy.getDexProgramClass();
     assertNotNull(clazz);
     classSignature = Parser.toClassSignature(clazz, appView);
     assertNotNull(classSignature);
@@ -112,14 +112,14 @@
     assertNull(formalTypeParameter.interfaceBounds);
     assertTrue(formalTypeParameter.classBound.isClassTypeSignature());
     ClassTypeSignature classBoundSignature = formalTypeParameter.classBound.asClassTypeSignature();
-    assertEquals(y.getDexClass().type, classBoundSignature.innerTypeSignature.type);
+    assertEquals(y.getDexProgramClass().type, classBoundSignature.innerTypeSignature.type);
     assertEquals(1, classBoundSignature.typeArguments.size());
     assertEquals(
         "T", classBoundSignature.typeArguments.get(0).asTypeVariableSignature().typeVariable);
 
     assertTrue(classSignature.superInterfaceSignatures.isEmpty());
     classTypeSignature = classSignature.superClassSignature;
-    assertEquals(cy.getDexClass().type, classTypeSignature.type);
+    assertEquals(cy.getDexProgramClass().type, classTypeSignature.type);
     typeArguments = classTypeSignature.typeArguments;
     assertEquals(1, typeArguments.size());
     typeArgument = typeArguments.get(0);
@@ -159,13 +159,13 @@
     FormalTypeParameter methodFormalParameter = methodTypeSignature.formalTypeParameters.get(0);
     assertTrue(methodFormalParameter.classBound.isClassTypeSignature());
     assertEquals(
-        y.getDexClass().getType(),
+        y.getDexProgramClass().getType(),
         methodFormalParameter.classBound.asClassTypeSignature().innerTypeSignature.type);
     assertNotNull(methodFormalParameter.interfaceBounds);
     assertEquals(1, methodFormalParameter.interfaceBounds.size());
     FieldTypeSignature interfaceBound = methodFormalParameter.interfaceBounds.get(0);
     assertTrue(interfaceBound.isClassTypeSignature());
-    assertEquals(i.getDexClass().getType(), interfaceBound.asClassTypeSignature().type);
+    assertEquals(i.getDexProgramClass().getType(), interfaceBound.asClassTypeSignature().type);
 
     // return type: A$Y$YY
     returnType = methodTypeSignature.returnType();
@@ -186,7 +186,7 @@
     assertTrue(elementSignature.isFieldTypeSignature());
     assertTrue(elementSignature.asFieldTypeSignature().isClassTypeSignature());
     classTypeSignature = elementSignature.asFieldTypeSignature().asClassTypeSignature();
-    assertEquals(b.getDexClass().type, classTypeSignature.type);
+    assertEquals(b.getDexProgramClass().type, classTypeSignature.type);
 
     // Function<A$Y$ZZ<TT>, A$Y$YY> convertToYY(Supplier<A$Y$ZZ<TT>>
     MethodSubject convertToYY = zz.uniqueMethodWithName("convertToYY");
@@ -246,26 +246,27 @@
   }
 
   private void check_A_Y(ClassSubject a, ClassSubject y, ClassTypeSignature signature) {
-    assertEquals(a.getDexClass().type, signature.type);
+    assertEquals(a.getDexProgramClass().type, signature.type);
     List<FieldTypeSignature> typeArguments = signature.typeArguments;
     assertEquals(1, typeArguments.size());
     FieldTypeSignature typeArgument = typeArguments.get(0);
     assertTrue(typeArgument.isTypeVariableSignature());
     assertEquals("T", typeArgument.asTypeVariableSignature().typeVariable);
-    assertEquals(y.getDexClass().type, signature.innerTypeSignature.type);
+    assertEquals(y.getDexProgramClass().type, signature.innerTypeSignature.type);
   }
 
   private void check_A_Y_YY(
       ClassSubject a, ClassSubject y, ClassSubject yy, ClassTypeSignature signature) {
     check_A_Y(a, y, signature);
-    assertEquals(yy.getDexClass().type, signature.innerTypeSignature.innerTypeSignature.type);
+    assertEquals(
+        yy.getDexProgramClass().type, signature.innerTypeSignature.innerTypeSignature.type);
   }
 
   private void check_A_Y_ZZ(
       ClassSubject a, ClassSubject y, ClassSubject zz, ClassTypeSignature signature) {
     check_A_Y(a, y, signature);
     ClassTypeSignature innerMost = signature.innerTypeSignature.innerTypeSignature;
-    assertEquals(zz.getDexClass().type, innerMost.type);
+    assertEquals(zz.getDexProgramClass().type, innerMost.type);
     List<FieldTypeSignature> typeArguments = innerMost.typeArguments;
     assertEquals(1, typeArguments.size());
     FieldTypeSignature typeArgument = typeArguments.get(0);
diff --git a/src/test/java/com/android/tools/r8/graph/InvokeFinalTest.java b/src/test/java/com/android/tools/r8/graph/InvokeFinalTest.java
index f72a98f..54f2fa4 100644
--- a/src/test/java/com/android/tools/r8/graph/InvokeFinalTest.java
+++ b/src/test/java/com/android/tools/r8/graph/InvokeFinalTest.java
@@ -63,7 +63,7 @@
             (opcode, owner, name, descriptor, isInterface, continuation) -> {
               // The super call to bar() is already INVOKESPECIAL.
               assertTrue(name.equals("foo") || opcode == INVOKESPECIAL);
-              continuation.apply(INVOKESPECIAL, owner, name, descriptor, isInterface);
+              continuation.visitMethodInsn(INVOKESPECIAL, owner, name, descriptor, isInterface);
             })
         .transform();
   }
diff --git a/src/test/java/com/android/tools/r8/graph/InvokeSuperTest.java b/src/test/java/com/android/tools/r8/graph/InvokeSuperTest.java
index f71538c..1abfea6f 100644
--- a/src/test/java/com/android/tools/r8/graph/InvokeSuperTest.java
+++ b/src/test/java/com/android/tools/r8/graph/InvokeSuperTest.java
@@ -3,12 +3,9 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.graph;
 
-import static org.hamcrest.CoreMatchers.allOf;
 import static org.hamcrest.CoreMatchers.containsString;
 import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
 
-import com.android.tools.r8.CompilationFailedException;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
@@ -114,48 +111,42 @@
             SubClassOfInvokerClass.class)
         .addProgramClassFileData(InvokerClassDump.dumpNonVerifying())
         .run(parameters.getRuntime(), MainClassFailing.class)
-        .apply(this::checkNonVerifyingResult);
+        .apply(r -> checkNonVerifyingResult(r, false));
   }
 
-  private void checkNonVerifyingResult(TestRunResult<?> result) {
+  private void checkNonVerifyingResult(TestRunResult<?> result, boolean isR8) {
     // The input is invalid and any JVM will fail at verification time.
     if (parameters.isCfRuntime()) {
-      result.assertFailureWithErrorThatMatches(containsString(VerifyError.class.getName()));
+      result.assertFailureWithErrorThatThrows(VerifyError.class);
       return;
     }
-    // D8 cannot verify its inputs and the behavior of the compiled output differs.
-    // The failure is due to lambda desugaring on pre-7 and fails at runtime on 7+.
+    // Dex results vary wildly...
     Version version = parameters.getRuntime().asDex().getVm().getVersion();
-    if (version.isOlderThanOrEqual(Version.V6_0_1)) {
-      result.assertFailureWithErrorThatMatches(
-          allOf(containsString("java.lang.NoClassDefFoundError"), containsString("-$$Lambda$")));
-      return;
+    if (!isR8 && version.isOlderThanOrEqual(Version.V4_4_4)) {
+      result.assertFailureWithErrorThatThrows(VerifyError.class);
+    } else if (version == Version.V5_1_1 || version == Version.V6_0_1) {
+      result.assertFailure();
+    } else {
+      result.assertSuccessWithOutputThatMatches(containsString(NoSuchMethodError.class.getName()));
     }
-    result.assertSuccessWithOutputLines(NoSuchMethodError.class.getName());
   }
 
   @Test
   public void testR8NonVerifying() throws Exception {
-    try {
-      testForR8(parameters.getBackend())
-          .addProgramClasses(
-              MainClassFailing.class,
-              Consumer.class,
-              Super.class,
-              SubLevel1.class,
-              SubLevel2.class,
-              SubClassOfInvokerClass.class)
-          .addProgramClassFileData(InvokerClassDump.dumpNonVerifying())
-          .setMinApi(parameters.getApiLevel())
-          .addKeepMainRule(MainClassFailing.class)
-          .compileWithExpectedDiagnostics(
-              diagnostics -> {
-                diagnostics.assertErrorMessageThatMatches(containsString("Illegal invoke-super"));
-              });
-      fail("Expected compilation to fail");
-    } catch (CompilationFailedException e) {
-      // Expected compilation failure.
-    }
+    testForR8(parameters.getBackend())
+        .addProgramClasses(
+            MainClassFailing.class,
+            Consumer.class,
+            Super.class,
+            SubLevel1.class,
+            SubLevel2.class,
+            SubClassOfInvokerClass.class)
+        .addProgramClassFileData(InvokerClassDump.dumpNonVerifying())
+        .setMinApi(parameters.getApiLevel())
+        .addKeepMainRule(MainClassFailing.class)
+        .addOptionsModification(o -> o.testing.allowTypeErrors = true)
+        .run(parameters.getRuntime(), MainClassFailing.class)
+        .apply(r -> checkNonVerifyingResult(r, true));
   }
 
   /** Copy of {@ref java.util.function.Consumer} to allow tests to run on early versions of art. */
@@ -253,7 +244,7 @@
 
   static class MainClassFailing {
 
-    private static void tryInvoke(java.util.function.Consumer<InvokerClass> function) {
+    private static void tryInvoke(Consumer<InvokerClass> function) {
       InvokerClass invoker = new InvokerClass();
       try {
         function.accept(invoker);
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 2438ab2..8524677 100644
--- a/src/test/java/com/android/tools/r8/graph/TargetLookupTest.java
+++ b/src/test/java/com/android/tools/r8/graph/TargetLookupTest.java
@@ -261,7 +261,7 @@
     assertEquals(mOnI1, appInfo.lookupSuperTarget(mOnI1, c1).method);
     assertEquals(mOnI2, appInfo.lookupSuperTarget(mOnI2, c1).method);
 
-    assertEquals(mOnI0, appInfo.lookupSuperTarget(mOnC1, c2).method);
+    assertNull(appInfo.lookupSuperTarget(mOnC1, c2)); // C2 is not a subclass of C1.
     assertEquals(mOnI1, appInfo.lookupSuperTarget(mOnI3, c2).method);
     assertEquals(mOnI2, appInfo.lookupSuperTarget(mOnI4, c2).method);
 
diff --git a/src/test/java/com/android/tools/r8/graph/invokespecial/InvokeSpecialForInvokeVirtualTest.java b/src/test/java/com/android/tools/r8/graph/invokespecial/InvokeSpecialForInvokeVirtualTest.java
index 283f479..eaf7d56 100644
--- a/src/test/java/com/android/tools/r8/graph/invokespecial/InvokeSpecialForInvokeVirtualTest.java
+++ b/src/test/java/com/android/tools/r8/graph/invokespecial/InvokeSpecialForInvokeVirtualTest.java
@@ -51,7 +51,7 @@
             "bar",
             (opcode, owner, name, descriptor, isInterface, continuation) -> {
               assertEquals(INVOKEVIRTUAL, opcode);
-              continuation.apply(INVOKESPECIAL, owner, name, descriptor, isInterface);
+              continuation.visitMethodInsn(INVOKESPECIAL, owner, name, descriptor, isInterface);
             })
         .transform();
   }
diff --git a/src/test/java/com/android/tools/r8/graph/invokespecial/InvokeSpecialForNonDeclaredInvokeVirtualTest.java b/src/test/java/com/android/tools/r8/graph/invokespecial/InvokeSpecialForNonDeclaredInvokeVirtualTest.java
index 1477df4..9b9a8eb 100644
--- a/src/test/java/com/android/tools/r8/graph/invokespecial/InvokeSpecialForNonDeclaredInvokeVirtualTest.java
+++ b/src/test/java/com/android/tools/r8/graph/invokespecial/InvokeSpecialForNonDeclaredInvokeVirtualTest.java
@@ -51,7 +51,7 @@
             "bar",
             (opcode, owner, name, descriptor, isInterface, continuation) -> {
               assertEquals(INVOKEVIRTUAL, opcode);
-              continuation.apply(INVOKESPECIAL, owner, name, descriptor, isInterface);
+              continuation.visitMethodInsn(INVOKESPECIAL, owner, name, descriptor, isInterface);
             })
         .transform();
   }
diff --git a/src/test/java/com/android/tools/r8/graph/invokespecial/InvokeSpecialInterfaceTest.java b/src/test/java/com/android/tools/r8/graph/invokespecial/InvokeSpecialInterfaceTest.java
index 24ae2fc..7a51803 100644
--- a/src/test/java/com/android/tools/r8/graph/invokespecial/InvokeSpecialInterfaceTest.java
+++ b/src/test/java/com/android/tools/r8/graph/invokespecial/InvokeSpecialInterfaceTest.java
@@ -63,7 +63,7 @@
             "bar",
             (opcode, owner, name, descriptor, isInterface, continuation) -> {
               assertEquals(INVOKEVIRTUAL, opcode);
-              continuation.apply(INVOKESPECIAL, owner, name, descriptor, isInterface);
+              continuation.visitMethodInsn(INVOKESPECIAL, owner, name, descriptor, isInterface);
             })
         .transform();
   }
diff --git a/src/test/java/com/android/tools/r8/graph/invokespecial/InvokeSpecialInterfaceWithBridgeTest.java b/src/test/java/com/android/tools/r8/graph/invokespecial/InvokeSpecialInterfaceWithBridgeTest.java
index 647881d..3536adb 100644
--- a/src/test/java/com/android/tools/r8/graph/invokespecial/InvokeSpecialInterfaceWithBridgeTest.java
+++ b/src/test/java/com/android/tools/r8/graph/invokespecial/InvokeSpecialInterfaceWithBridgeTest.java
@@ -53,7 +53,7 @@
             (opcode, owner, name, descriptor, isInterface, continuation) -> {
               assertEquals(INVOKEVIRTUAL, opcode);
               assertEquals(owner, DescriptorUtils.getBinaryNameFromJavaType(B.class.getTypeName()));
-              continuation.apply(INVOKESPECIAL, owner, name, descriptor, isInterface);
+              continuation.visitMethodInsn(INVOKESPECIAL, owner, name, descriptor, isInterface);
             })
         .transform();
   }
diff --git a/src/test/java/com/android/tools/r8/graph/invokespecial/InvokeSpecialMissingInvokeVirtualTest.java b/src/test/java/com/android/tools/r8/graph/invokespecial/InvokeSpecialMissingInvokeVirtualTest.java
index 5343c5a..93a94e9 100644
--- a/src/test/java/com/android/tools/r8/graph/invokespecial/InvokeSpecialMissingInvokeVirtualTest.java
+++ b/src/test/java/com/android/tools/r8/graph/invokespecial/InvokeSpecialMissingInvokeVirtualTest.java
@@ -54,7 +54,7 @@
             (opcode, owner, name, descriptor, isInterface, continuation) -> {
               assertEquals(INVOKEVIRTUAL, opcode);
               assertEquals("notify", name);
-              continuation.apply(
+              continuation.visitMethodInsn(
                   INVOKESPECIAL,
                   DescriptorUtils.getBinaryNameFromJavaType(A.class.getTypeName()),
                   "foo",
diff --git a/src/test/java/com/android/tools/r8/graph/invokespecial/InvokeSpecialOnSameClassTest.java b/src/test/java/com/android/tools/r8/graph/invokespecial/InvokeSpecialOnSameClassTest.java
index 7fae808..5bc6d22 100644
--- a/src/test/java/com/android/tools/r8/graph/invokespecial/InvokeSpecialOnSameClassTest.java
+++ b/src/test/java/com/android/tools/r8/graph/invokespecial/InvokeSpecialOnSameClassTest.java
@@ -56,7 +56,7 @@
         .transformMethodInsnInMethod(
             "bar",
             (opcode, owner, name, descriptor, isInterface, continuation) -> {
-              continuation.apply(INVOKESPECIAL, owner, name, descriptor, isInterface);
+              continuation.visitMethodInsn(INVOKESPECIAL, owner, name, descriptor, isInterface);
             })
         .transform();
   }
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 ecdaa6f..090844e 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
@@ -99,7 +99,7 @@
             (opcode, owner, name, descriptor, isInterface, continuation) -> {
               assertEquals(INVOKESTATIC, opcode);
               assertTrue(isInterface);
-              continuation.apply(opcode, owner, name, descriptor, false);
+              continuation.visitMethodInsn(opcode, owner, name, descriptor, false);
             })
         .transform();
   }
diff --git a/src/test/java/com/android/tools/r8/internal/NestTreeShakeJarVerificationTest.java b/src/test/java/com/android/tools/r8/internal/NestTreeShakeJarVerificationTest.java
index 8166511..2c06bd8 100644
--- a/src/test/java/com/android/tools/r8/internal/NestTreeShakeJarVerificationTest.java
+++ b/src/test/java/com/android/tools/r8/internal/NestTreeShakeJarVerificationTest.java
@@ -26,7 +26,8 @@
         null,
         ImmutableList.of(BASE + DEPLOY_JAR));
     assertEquals(0, filterKotlinMetadata(handler.warnings).count());
-    assertEquals(0, filterKotlinMetadata(handler.infos).count());
+    // TODO(b/155536535): We find bad descriptors. See if we can still resolve them.
+    assertEquals(2, filterKotlinMetadata(handler.infos).count());
   }
 
   private Stream<Diagnostic> filterKotlinMetadata(List<Diagnostic> warnings) {
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 183a37e..ccbfd3a 100644
--- a/src/test/java/com/android/tools/r8/internal/R8GMSCoreLookupTest.java
+++ b/src/test/java/com/android/tools/r8/internal/R8GMSCoreLookupTest.java
@@ -13,7 +13,7 @@
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.dex.ApplicationReader;
-import com.android.tools.r8.graph.AppInfoWithSubtyping;
+import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
 import com.android.tools.r8.graph.AppServices;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexEncodedMethod;
@@ -41,7 +41,7 @@
 
   private static final String APP_DIR = "third_party/gmscore/v5/";
   private DirectMappedDexApplication program;
-  private AppView<? extends AppInfoWithSubtyping> appView;
+  private AppView<? extends AppInfoWithClassHierarchy> appView;
   private SubtypingInfo subtypingInfo;
 
   @Before
@@ -60,12 +60,12 @@
             .read(proguardMap, executorService)
             .toDirect();
     InternalOptions options = new InternalOptions();
-    appView = AppView.createForR8(new AppInfoWithSubtyping(program), options);
+    appView = AppView.createForR8(new AppInfoWithClassHierarchy(program), options);
     appView.setAppServices(AppServices.builder(appView).build());
     subtypingInfo = new SubtypingInfo(program.allClasses(), program);
   }
 
-  private AppInfoWithSubtyping appInfo() {
+  private AppInfoWithClassHierarchy appInfo() {
     return appView.appInfo();
   }
 
@@ -106,7 +106,7 @@
       Counter counter = new Counter();
       lookupResult.forEach(
           target -> {
-            DexEncodedMethod m = target.getMethod();
+            DexEncodedMethod m = target.getDefinition();
             if (m.accessFlags.isAbstract() || !m.accessFlags.isBridge()) {
               counter.inc();
             }
@@ -117,7 +117,7 @@
       Counter counter = new Counter();
       lookupResult.forEach(
           target -> {
-            if (target.getMethod().isAbstract()) {
+            if (target.getDefinition().isAbstract()) {
               counter.inc();
             }
           },
diff --git a/src/test/java/com/android/tools/r8/internal/proto/Proto2BuilderShrinkingTest.java b/src/test/java/com/android/tools/r8/internal/proto/Proto2BuilderShrinkingTest.java
index 23dc574..8c98b46 100644
--- a/src/test/java/com/android/tools/r8/internal/proto/Proto2BuilderShrinkingTest.java
+++ b/src/test/java/com/android/tools/r8/internal/proto/Proto2BuilderShrinkingTest.java
@@ -186,7 +186,8 @@
   }
 
   private void verifyMethodToInvokeValuesAreAbsent(CodeInspector outputInspector) {
-    DexType methodToInvokeType = outputInspector.clazz(METHOD_TO_INVOKE_ENUM).getDexClass().type;
+    DexType methodToInvokeType =
+        outputInspector.clazz(METHOD_TO_INVOKE_ENUM).getDexProgramClass().type;
     for (String main : mains) {
       MethodSubject mainMethodSubject = outputInspector.clazz(main).mainMethod();
       assertThat(mainMethodSubject, isPresent());
diff --git a/src/test/java/com/android/tools/r8/invalid/DuplicateDefinitionsTest.java b/src/test/java/com/android/tools/r8/invalid/DuplicateDefinitionsTest.java
index 60975b5..3c3aee6 100644
--- a/src/test/java/com/android/tools/r8/invalid/DuplicateDefinitionsTest.java
+++ b/src/test/java/com/android/tools/r8/invalid/DuplicateDefinitionsTest.java
@@ -59,11 +59,11 @@
     assertThat(clazz, isPresent());
 
     // There are two direct methods, but only because one is <init>.
-    assertEquals(2, clazz.getDexClass().directMethods().size());
+    assertEquals(2, clazz.getDexProgramClass().directMethods().size());
     assertThat(clazz.method("void", "<init>", ImmutableList.of()), isPresent());
 
     // There is only one virtual method.
-    assertEquals(1, clazz.getDexClass().virtualMethods().size());
+    assertEquals(1, clazz.getDexProgramClass().virtualMethods().size());
   }
 
   @Test
@@ -94,7 +94,7 @@
     assertThat(clazz, isPresent());
 
     // Redundant fields have been removed.
-    assertEquals(1, clazz.getDexClass().instanceFields().size());
-    assertEquals(1, clazz.getDexClass().staticFields().size());
+    assertEquals(1, clazz.getDexProgramClass().instanceFields().size());
+    assertEquals(1, clazz.getDexProgramClass().staticFields().size());
   }
 }
diff --git a/src/test/java/com/android/tools/r8/ir/InlineTest.java b/src/test/java/com/android/tools/r8/ir/InlineTest.java
index a2b9dce..a2246c0 100644
--- a/src/test/java/com/android/tools/r8/ir/InlineTest.java
+++ b/src/test/java/com/android/tools/r8/ir/InlineTest.java
@@ -6,11 +6,13 @@
 
 import static org.junit.Assert.assertEquals;
 
-import com.android.tools.r8.graph.AppInfoWithSubtyping;
+import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
 import com.android.tools.r8.graph.AppServices;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexApplication;
 import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DirectMappedDexApplication;
+import com.android.tools.r8.graph.SubtypingInfo;
 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,18 +49,20 @@
       MethodSubject method,
       List<IRCode> additionalCode)
       throws ExecutionException {
-    AppView<AppInfoWithSubtyping> appView =
-        AppView.createForR8(new AppInfoWithSubtyping(application.asDirect()), options);
+    DirectMappedDexApplication directApp = application.asDirect();
+    AppView<AppInfoWithClassHierarchy> appView =
+        AppView.createForR8(new AppInfoWithClassHierarchy(directApp), options);
     appView.setAppServices(AppServices.builder(appView).build());
     ExecutorService executorService = ThreadUtils.getExecutorService(options);
+    SubtypingInfo subtypingInfo = new SubtypingInfo(directApp.allClasses(), directApp);
     appView.setRootSet(
         new RootSetBuilder(
                 appView,
-                application,
+                subtypingInfo,
                 ImmutableList.of(ProguardKeepRule.defaultKeepAllRule(unused -> {})))
             .run(executorService));
     Timing timing = Timing.empty();
-    Enqueuer enqueuer = EnqueuerFactory.createForInitialTreeShaking(appView);
+    Enqueuer enqueuer = EnqueuerFactory.createForInitialTreeShaking(appView, subtypingInfo);
     appView.setAppInfo(
         enqueuer.traceApplication(
             appView.rootSet(), ProguardClassFilter.empty(), executorService, timing));
diff --git a/src/test/java/com/android/tools/r8/ir/analysis/AnalysisTestBase.java b/src/test/java/com/android/tools/r8/ir/analysis/AnalysisTestBase.java
index ebfb64f..80fef97 100644
--- a/src/test/java/com/android/tools/r8/ir/analysis/AnalysisTestBase.java
+++ b/src/test/java/com/android/tools/r8/ir/analysis/AnalysisTestBase.java
@@ -8,15 +8,10 @@
 
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.dex.ApplicationReader;
-import com.android.tools.r8.graph.AppInfoWithSubtyping;
 import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.DirectMappedDexApplication;
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.Instruction;
 import com.android.tools.r8.utils.AndroidApp;
-import com.android.tools.r8.utils.InternalOptions;
-import com.android.tools.r8.utils.Timing;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.android.tools.r8.utils.codeinspector.MethodSubject;
 import java.util.function.Consumer;
@@ -28,7 +23,6 @@
   protected final TestParameters parameters;
   private final AndroidApp app;
   private final String className;
-  private final InternalOptions options = new InternalOptions();
 
   public AppView<?> appView;
 
@@ -64,9 +58,7 @@
 
   @Before
   public void setup() throws Exception {
-    DirectMappedDexApplication application =
-        new ApplicationReader(app, options, Timing.empty()).read().toDirect();
-    appView = AppView.createForR8(new AppInfoWithSubtyping(application), options);
+    appView = computeAppViewWithLiveness(app);
   }
 
   public void buildAndCheckIR(String methodName, Consumer<IRCode> irInspector) {
diff --git a/src/test/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldBitAccessInfoTest.java b/src/test/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldBitAccessInfoTest.java
index 43c8e17..bf823d4 100644
--- a/src/test/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldBitAccessInfoTest.java
+++ b/src/test/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldBitAccessInfoTest.java
@@ -16,7 +16,7 @@
 import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.dex.ApplicationReader;
-import com.android.tools.r8.graph.AppInfoWithSubtyping;
+import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexEncodedField;
 import com.android.tools.r8.graph.DexEncodedMethod;
@@ -80,7 +80,7 @@
 
   @Test
   public void testOptimizationInfo() throws Exception {
-    AppView<AppInfoWithSubtyping> appView = buildApp();
+    AppView<AppInfoWithClassHierarchy> appView = buildApp();
     OptimizationFeedbackMock feedback = new OptimizationFeedbackMock();
     FieldBitAccessAnalysis fieldBitAccessAnalysis = new FieldBitAccessAnalysis();
     FieldAccessAnalysis fieldAccessAnalysis =
@@ -114,7 +114,7 @@
     }
   }
 
-  private AppView<AppInfoWithSubtyping> buildApp() throws IOException, ExecutionException {
+  private AppView<AppInfoWithClassHierarchy> buildApp() throws IOException, ExecutionException {
     DexItemFactory dexItemFactory = new DexItemFactory();
     InternalOptions options = new InternalOptions(dexItemFactory, new Reporter());
     options.programConsumer =
@@ -133,7 +133,7 @@
                 timing)
             .read()
             .toDirect();
-    return AppView.createForR8(new AppInfoWithSubtyping(application), options);
+    return AppView.createForR8(new AppInfoWithClassHierarchy(application), options);
   }
 
   private DexEncodedField uniqueFieldByName(DexProgramClass clazz, String name) {
diff --git a/src/test/java/com/android/tools/r8/ir/analysis/type/NarrowingWithoutSubtypingTest.java b/src/test/java/com/android/tools/r8/ir/analysis/type/NarrowingWithoutSubtypingTest.java
index 2feb99a..6644af2 100644
--- a/src/test/java/com/android/tools/r8/ir/analysis/type/NarrowingWithoutSubtypingTest.java
+++ b/src/test/java/com/android/tools/r8/ir/analysis/type/NarrowingWithoutSubtypingTest.java
@@ -20,7 +20,7 @@
 
   @Parameters(name = "{0}")
   public static TestParametersCollection data() {
-    return getTestParameters().withDexRuntimes().build();
+    return getTestParameters().withDexRuntimes().withAllApiLevels().build();
   }
 
   public NarrowingWithoutSubtypingTest(TestParameters parameters) {
@@ -36,7 +36,7 @@
               options.testing.enableNarrowingChecksInD8 = true;
               options.testing.noLocalsTableOnInput = true;
             })
-        .setMinApi(parameters.getRuntime())
+        .setMinApi(parameters.getApiLevel())
         .compile()
         .run(parameters.getRuntime(), TestClass.class)
         .assertSuccessWithOutputLines("Hello world!");
diff --git a/src/test/java/com/android/tools/r8/ir/analysis/type/TypeLatticeTest.java b/src/test/java/com/android/tools/r8/ir/analysis/type/TypeLatticeTest.java
index 5d2814c..cfe27c9 100644
--- a/src/test/java/com/android/tools/r8/ir/analysis/type/TypeLatticeTest.java
+++ b/src/test/java/com/android/tools/r8/ir/analysis/type/TypeLatticeTest.java
@@ -15,7 +15,7 @@
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.dex.ApplicationReader;
-import com.android.tools.r8.graph.AppInfoWithSubtyping;
+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.DexType;
@@ -39,7 +39,7 @@
   private static final String INTERRUPT = "Ljava/io/InterruptedIOException;";
 
   private static DexItemFactory factory;
-  private static AppView<AppInfoWithSubtyping> appView;
+  private static AppView<AppInfoWithClassHierarchy> appView;
 
   @BeforeClass
   public static void makeAppInfo() throws Exception {
@@ -60,7 +60,7 @@
             .read()
             .toDirect();
     factory = options.itemFactory;
-    appView = AppView.createForR8(new AppInfoWithSubtyping(application), options);
+    appView = AppView.createForR8(new AppInfoWithClassHierarchy(application), options);
   }
 
   private TopTypeElement top() {
diff --git a/src/test/java/com/android/tools/r8/ir/conversion/PartialCallGraphTest.java b/src/test/java/com/android/tools/r8/ir/conversion/PartialCallGraphTest.java
index 1955c47..1ea4e67 100644
--- a/src/test/java/com/android/tools/r8/ir/conversion/PartialCallGraphTest.java
+++ b/src/test/java/com/android/tools/r8/ir/conversion/PartialCallGraphTest.java
@@ -10,21 +10,15 @@
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
 
-import com.android.tools.r8.dex.ApplicationReader;
-import com.android.tools.r8.graph.AppInfoWithSubtyping;
-import com.android.tools.r8.graph.AppServices;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexEncodedMethod;
-import com.android.tools.r8.graph.DirectMappedDexApplication;
 import com.android.tools.r8.ir.conversion.CallGraph.Node;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
-import com.android.tools.r8.shaking.Enqueuer;
-import com.android.tools.r8.shaking.EnqueuerFactory;
 import com.android.tools.r8.shaking.ProguardConfigurationParser;
-import com.android.tools.r8.shaking.RootSetBuilder;
 import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.Reporter;
 import com.android.tools.r8.utils.ThreadUtils;
 import com.android.tools.r8.utils.Timing;
 import com.google.common.collect.ImmutableList;
@@ -36,33 +30,24 @@
 
 public class PartialCallGraphTest extends CallGraphTestBase {
   private final AppView<AppInfoWithLiveness> appView;
-  private final InternalOptions options = new InternalOptions();
-  private final ExecutorService executorService = ThreadUtils.getExecutorService(options);
+  private final InternalOptions options;
+  private final ExecutorService executorService;
 
   public PartialCallGraphTest() throws Exception {
-    Timing timing = Timing.empty();
     AndroidApp app = testForD8().addProgramClasses(TestClass.class).compile().app;
-    DirectMappedDexApplication application =
-        new ApplicationReader(app, options, timing).read().toDirect();
-    AppView<AppInfoWithSubtyping> appView =
-        AppView.createForR8(new AppInfoWithSubtyping(application), options);
-    appView.setAppServices(AppServices.builder(appView).build());
-    ProguardConfigurationParser parser =
-        new ProguardConfigurationParser(appView.dexItemFactory(), options.reporter);
-    parser.parse(
-        createConfigurationForTesting(
-            ImmutableList.of("-keep class ** { void m1(); void m5(); }")));
-    appView.setRootSet(
-        new RootSetBuilder(
-            appView, application, parser.getConfig().getRules()).run(executorService));
-    Enqueuer enqueuer = EnqueuerFactory.createForInitialTreeShaking(appView);
     this.appView =
-        appView.setAppInfo(
-            enqueuer.traceApplication(
-                appView.rootSet(),
-                parser.getConfig().getDontWarnPatterns(),
-                executorService,
-                timing));
+        computeAppViewWithLiveness(
+            app,
+            factory -> {
+              ProguardConfigurationParser parser =
+                  new ProguardConfigurationParser(factory, new Reporter());
+              parser.parse(
+                  createConfigurationForTesting(
+                      ImmutableList.of("-keep class ** { void m1(); void m5(); }")));
+              return parser.getConfig().getRules();
+            });
+    this.options = appView.options();
+    this.executorService = ThreadUtils.getExecutorService(options);
   }
 
   @Test
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/ConstraintWithTargetTest.java b/src/test/java/com/android/tools/r8/ir/optimize/ConstraintWithTargetTest.java
index 41eba43..81f2ba5 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/ConstraintWithTargetTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/ConstraintWithTargetTest.java
@@ -5,37 +5,45 @@
 
 import static org.junit.Assert.assertEquals;
 
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.ToolHelper;
-import com.android.tools.r8.dex.ApplicationReader;
-import com.android.tools.r8.graph.AppInfoWithSubtyping;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.graph.DirectMappedDexApplication;
 import com.android.tools.r8.ir.optimize.Inliner.Constraint;
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.utils.AndroidApp;
-import com.android.tools.r8.utils.InternalOptions;
-import com.android.tools.r8.utils.Timing;
 import org.junit.BeforeClass;
 import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
 
-public class ConstraintWithTargetTest {
+@RunWith(Parameterized.class)
+public class ConstraintWithTargetTest extends TestBase {
+
   private static DexItemFactory factory;
-  private static AppView<AppInfoWithSubtyping> appView;
+  private static AppView<AppInfoWithLiveness> appView;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withNoneRuntime().build();
+  }
+
+  private final TestParameters parameters;
+
+  public ConstraintWithTargetTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
 
   @BeforeClass
   public static void makeAppInfo() throws Exception {
-    InternalOptions options = new InternalOptions();
-    DirectMappedDexApplication application =
-        new ApplicationReader(
-                AndroidApp.builder().addLibraryFiles(ToolHelper.getDefaultAndroidJar()).build(),
-                options,
-                Timing.empty())
-            .read()
-            .toDirect();
-    factory = options.itemFactory;
-    appView = AppView.createForR8(new AppInfoWithSubtyping(application), options);
+    AndroidApp app = AndroidApp.builder().addLibraryFiles(ToolHelper.getJava8RuntimeJar()).build();
+    appView = computeAppViewWithLiveness(app);
+    factory = appView.dexItemFactory();
   }
 
   private ConstraintWithTarget never() {
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/NonNullTrackerTestBase.java b/src/test/java/com/android/tools/r8/ir/optimize/NonNullTrackerTestBase.java
index 15456be..28ab813 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/NonNullTrackerTestBase.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/NonNullTrackerTestBase.java
@@ -6,7 +6,7 @@
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.dex.ApplicationReader;
-import com.android.tools.r8.graph.AppInfoWithSubtyping;
+import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
 import com.android.tools.r8.graph.AppServices;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DirectMappedDexApplication;
@@ -22,7 +22,8 @@
     InternalOptions options = new InternalOptions();
     DirectMappedDexApplication dexApplication =
         new ApplicationReader(app, options, timing).read().toDirect();
-    AppView<?> appView = AppView.createForD8(new AppInfoWithSubtyping(dexApplication), options);
+    AppView<?> appView =
+        AppView.createForD8(new AppInfoWithClassHierarchy(dexApplication), options);
     appView.setAppServices(AppServices.builder(appView).build());
     return appView;
   }
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/R8InliningTest.java b/src/test/java/com/android/tools/r8/ir/optimize/R8InliningTest.java
index 49718d4..5daa849 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/R8InliningTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/R8InliningTest.java
@@ -374,11 +374,7 @@
               .getMethod()
               .name
               .toString()
-              .equals(EnumUnboxingRewriter.ENUM_UNBOXING_UTILITY_ORDINAL)
-          && !((InvokeInstructionSubject) instruction)
-              .holder()
-              .toString()
-              .contains("java.lang.Enum")) {
+              .equals(EnumUnboxingRewriter.ENUM_UNBOXING_UTILITY_ORDINAL)) {
         ++invokeCount;
       }
     }
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/callsites/CallSiteOptimizationWithInvokeCustomTargetTest.java b/src/test/java/com/android/tools/r8/ir/optimize/callsites/CallSiteOptimizationWithInvokeCustomTargetTest.java
new file mode 100644
index 0000000..1a2d667
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/callsites/CallSiteOptimizationWithInvokeCustomTargetTest.java
@@ -0,0 +1,132 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.optimize.callsites;
+
+import static com.android.tools.r8.references.Reference.methodFromMethod;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.google.common.collect.ImmutableList;
+import java.lang.invoke.CallSite;
+import java.lang.invoke.ConstantCallSite;
+import java.lang.invoke.MethodHandles;
+import java.lang.invoke.MethodType;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+import org.objectweb.asm.Handle;
+import org.objectweb.asm.Opcodes;
+
+@RunWith(Parameterized.class)
+public class CallSiteOptimizationWithInvokeCustomTargetTest extends TestBase {
+
+  private static final String EXPECTED = StringUtils.lines("Hello world!");
+
+  private final TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters()
+        .withAllRuntimes()
+        // Only works when invoke-custom/dynamic are supported and ConstantCallSite defined.
+        .withApiLevelsStartingAtIncluding(apiLevelWithInvokeCustomSupport())
+        .build();
+  }
+
+  public CallSiteOptimizationWithInvokeCustomTargetTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testReference() throws Exception {
+    testForRuntime(parameters)
+        .addProgramClassFileData(getProgramClassFileData())
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutput(EXPECTED);
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    testForR8(parameters.getBackend())
+        .addProgramClassFileData(getProgramClassFileData())
+        .addKeepMainRule(TestClass.class)
+        .setMinApi(parameters.getApiLevel())
+        .addKeepMethodRules(methodFromMethod(TestClass.class.getDeclaredMethod("bar", int.class)))
+        .enableInliningAnnotations()
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutput(EXPECTED)
+        .inspect(
+            inspector -> {
+              ClassSubject clazz = inspector.clazz(TestClass.class);
+              assertThat(clazz.uniqueMethodWithName("bootstrap"), isPresent());
+              assertThat(clazz.uniqueMethodWithName("bar"), isPresent());
+              assertThat(clazz.uniqueMethodWithName("foo"), not(isPresent()));
+            });
+  }
+
+  private List<byte[]> getProgramClassFileData() throws Exception {
+    return ImmutableList.of(
+        transformer(TestClass.class)
+            .transformMethodInsnInMethod(
+                "main",
+                (opcode, owner, name, descriptor, isInterface, visitor) -> {
+                  if (opcode == Opcodes.INVOKESTATIC && name.equals("foo")) {
+                    visitor.visitInvokeDynamicInsn(
+                        "foo",
+                        "(I)V",
+                        new Handle(
+                            Opcodes.H_INVOKESTATIC,
+                            binaryName(TestClass.class),
+                            "bootstrap",
+                            "(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;",
+                            false));
+                  } else {
+                    visitor.visitMethodInsn(opcode, owner, name, descriptor, isInterface);
+                  }
+                })
+            .transform());
+  }
+
+  static class TestClass {
+
+    @NeverInline
+    static CallSite bootstrap(MethodHandles.Lookup lookup, String unused, MethodType type)
+        throws NoSuchMethodException, IllegalAccessException {
+      return lookup != null
+          ? new ConstantCallSite(
+              // Reflective access of bar, needs a keep rule.
+              lookup.findStatic(TestClass.class, "bar", type))
+          : null;
+    }
+
+    // Target of the bootstrap method.
+    static void bar(int i) {
+      if (i == 42) {
+        System.out.println("Hello world!");
+      }
+    }
+
+    // Placeholder. Never called.
+    static void foo(int i) {
+      throw null;
+    }
+
+    public static void main(String[] args) throws Exception {
+      // Direct call to the bootstrap method with constant arguments, triggering call-site opt.
+      bootstrap(null, null, null);
+      // Rewritten to invoke-dynamic foo(I)V, bsm:TestClass::bootstrap
+      TestClass.foo(args.length + 42);
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/callsites/PropagationFromSiblingTest.java b/src/test/java/com/android/tools/r8/ir/optimize/callsites/PropagationFromSiblingTest.java
new file mode 100644
index 0000000..fb3bc2e
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/callsites/PropagationFromSiblingTest.java
@@ -0,0 +1,89 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.optimize.callsites;
+
+import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NeverMerge;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class PropagationFromSiblingTest extends TestBase {
+
+  private final TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public PropagationFromSiblingTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void test() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(PropagationFromSiblingTest.class)
+        .addKeepMainRule(TestClass.class)
+        .addOptionsModification(options -> options.enableUnusedInterfaceRemoval = false)
+        .enableInliningAnnotations()
+        .enableMergeAnnotations()
+        .enableNeverClassInliningAnnotations()
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutputLines("Hello world!");
+  }
+
+  static class TestClass {
+
+    public static void main(String[] args) {
+      getJAsI().m(null); // The null argument is not propagated to A.m() in 2.0.59.
+      new B().m("Hello world!");
+    }
+
+    @NeverInline
+    static I getJAsI() {
+      if (System.currentTimeMillis() > 0) {
+        return new B();
+      }
+      return new C();
+    }
+  }
+
+  interface I {
+    void m(Object o);
+  }
+
+  interface J extends I {}
+
+  @NeverMerge
+  static class A implements I {
+
+    @NeverInline
+    @Override
+    public void m(Object o) {
+      if (o != null) {
+        System.out.println(o.toString());
+      }
+    }
+  }
+
+  @NeverClassInline
+  static class B extends A implements J {}
+
+  static class C implements J {
+
+    @Override
+    public void m(Object o) {}
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/canonicalization/StaticFinalLibraryFieldCanonicalizationTest.java b/src/test/java/com/android/tools/r8/ir/optimize/canonicalization/StaticFinalLibraryFieldCanonicalizationTest.java
index 7da9e81..e46efd5 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/canonicalization/StaticFinalLibraryFieldCanonicalizationTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/canonicalization/StaticFinalLibraryFieldCanonicalizationTest.java
@@ -10,13 +10,11 @@
 
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.utils.BooleanUtils;
+import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.android.tools.r8.utils.codeinspector.InstructionSubject;
 import com.android.tools.r8.utils.codeinspector.MethodSubject;
-import com.google.common.collect.ImmutableSet;
-import java.util.List;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
@@ -26,17 +24,13 @@
 public class StaticFinalLibraryFieldCanonicalizationTest extends TestBase {
 
   private final TestParameters parameters;
-  private final boolean systemHasClassInitializationSideEffects;
 
-  @Parameters(name = "{0}, System has class initialization side effects: {1}")
-  public static List<Object[]> data() {
-    return buildParameters(
-        getTestParameters().withAllRuntimesAndApiLevels().build(), BooleanUtils.values());
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
   }
 
-  public StaticFinalLibraryFieldCanonicalizationTest(
-      TestParameters parameters, boolean systemHasClassInitializationSideEffects) {
-    this.systemHasClassInitializationSideEffects = systemHasClassInitializationSideEffects;
+  public StaticFinalLibraryFieldCanonicalizationTest(TestParameters parameters) {
     this.parameters = parameters;
   }
 
@@ -45,18 +39,11 @@
     testForR8(parameters.getBackend())
         .addProgramClasses(TestClass.class)
         .addKeepMainRule(TestClass.class)
-        .addOptionsModification(
-            options -> {
-              if (!systemHasClassInitializationSideEffects) {
-                options.itemFactory.libraryClassesWithoutStaticInitialization =
-                    ImmutableSet.of(options.itemFactory.createType("Ljava/lang/System;"));
-              }
-            })
         .setMinApi(parameters.getApiLevel())
         .compile()
         .inspect(this::inspect)
         .run(parameters.getRuntime(), TestClass.class)
-        .assertSuccessWithOutputLines("Hello world!");
+        .assertSuccessWithOutputLines("true", "true");
   }
 
   private void inspect(CodeInspector inspector) {
@@ -66,15 +53,20 @@
     MethodSubject mainMethodSubject = testClassSubject.mainMethod();
     assertThat(mainMethodSubject, isPresent());
     assertEquals(
-        systemHasClassInitializationSideEffects || parameters.isCfRuntime() ? 2 : 1,
-        mainMethodSubject.streamInstructions().filter(InstructionSubject::isStaticGet).count());
+        1,
+        mainMethodSubject
+            .streamInstructions()
+            .filter(InstructionSubject::isStaticGet)
+            .map(InstructionSubject::getField)
+            .filter(inspector.getFactory().booleanMembers.TRUE::equals)
+            .count());
   }
 
   static class TestClass {
 
     public static void main(String[] args) {
-      System.out.print("Hello");
-      System.out.println(" world!");
+      System.out.println(Boolean.TRUE);
+      System.out.println(Boolean.TRUE);
     }
   }
 }
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/commonsubexpressionelimination/TrivialPhiAfterCommonSubexpressionEliminationTest.java b/src/test/java/com/android/tools/r8/ir/optimize/commonsubexpressionelimination/TrivialPhiAfterCommonSubexpressionEliminationTest.java
new file mode 100644
index 0000000..c238466
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/commonsubexpressionelimination/TrivialPhiAfterCommonSubexpressionEliminationTest.java
@@ -0,0 +1,48 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.optimize.commonsubexpressionelimination;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class TrivialPhiAfterCommonSubexpressionEliminationTest extends TestBase {
+
+  private final TestParameters parameters;
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public TrivialPhiAfterCommonSubexpressionEliminationTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void test() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(TrivialPhiAfterCommonSubexpressionEliminationTest.class)
+        .addKeepMainRule(TestClass.class)
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutputLines("42");
+  }
+
+  static class TestClass {
+
+    public static void main(String[] args) {
+      int x = args.length;
+      int y = x + 42;
+      int z = x + 42;
+      System.out.println(System.currentTimeMillis() > 0 ? y : z);
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/ifs/ConstClassComparisonTest.java b/src/test/java/com/android/tools/r8/ir/optimize/ifs/ConstClassComparisonTest.java
new file mode 100644
index 0000000..6b8cb68
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/ifs/ConstClassComparisonTest.java
@@ -0,0 +1,83 @@
+// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.optimize.ifs;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.InstructionSubject;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class ConstClassComparisonTest extends TestBase {
+
+  private final TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public ConstClassComparisonTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void test() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(ConstClassComparisonTest.class)
+        .addKeepMainRule(TestClass.class)
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .inspect(this::inspect)
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutputLines("true", "false", "false", "true");
+  }
+
+  private void inspect(CodeInspector inspector) {
+    ClassSubject classSubject = inspector.clazz(TestClass.class);
+    assertThat(classSubject, isPresent());
+
+    MethodSubject mainMethod = classSubject.mainMethod();
+    assertThat(mainMethod, isPresent());
+    assertTrue(mainMethod.streamInstructions().noneMatch(InstructionSubject::isConstClass));
+
+    assertThat(inspector.clazz(A.class), not(isPresent()));
+    assertThat(inspector.clazz(B.class), not(isPresent()));
+  }
+
+  static class TestClass {
+
+    public static void main(String[] args) {
+      System.out.println(getA() == getA());
+      System.out.println(getA() != getA());
+      System.out.println(getA() == getB());
+      System.out.println(getA() != getB());
+    }
+
+    static Class<?> getA() {
+      return A.class;
+    }
+
+    static Class<?> getB() {
+      return B.class;
+    }
+  }
+
+  static class A {}
+
+  static class B {}
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/ifs/EnumAliasComparisonTest.java b/src/test/java/com/android/tools/r8/ir/optimize/ifs/EnumAliasComparisonTest.java
index 5c0f75b..aa60b25 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/ifs/EnumAliasComparisonTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/ifs/EnumAliasComparisonTest.java
@@ -44,7 +44,7 @@
         .compile()
         .inspect(this::inspect)
         .run(parameters.getRuntime(), TestClass.class)
-        .assertSuccessWithOutputLines("true");
+        .assertSuccessWithOutputLines("true", "false", "false", "true", "true", "false");
   }
 
   private void inspect(CodeInspector inspector) {
@@ -67,15 +67,20 @@
   static class TestClass {
 
     public static void main(String[] args) {
-      // Should print "true" since MyEnum.B is an alias of MyEnum.A.
+      System.out.println(MyEnum.A == MyEnum.A);
+      System.out.println(MyEnum.A != MyEnum.A);
       System.out.println(MyEnum.A == MyEnum.B);
+      System.out.println(MyEnum.A != MyEnum.B);
+      System.out.println(MyEnum.A == MyEnum.C);
+      System.out.println(MyEnum.A != MyEnum.C);
     }
   }
 
   enum MyEnum {
-    A;
+    A,
+    B;
 
     // Introduce an alias of MyEnum.A.
-    static MyEnum B = A;
+    static MyEnum C = A;
   }
 }
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/inliner/InlineCatchHandlerWithLibraryTypeTest.java b/src/test/java/com/android/tools/r8/ir/optimize/inliner/InlineCatchHandlerWithLibraryTypeTest.java
index 15e37c1..5874af4 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/inliner/InlineCatchHandlerWithLibraryTypeTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/inliner/InlineCatchHandlerWithLibraryTypeTest.java
@@ -82,7 +82,7 @@
                   type.equals(TEMPLATE_CODE_EXCEPTION_BINARY_NAME)
                       ? getExceptionBinaryName()
                       : type;
-              continuation.apply(start, end, handler, newType);
+              continuation.visitTryCatchBlock(start, end, handler, newType);
             })
         .transform();
   }
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/logging/AndroidLogRemovalTest.java b/src/test/java/com/android/tools/r8/ir/optimize/logging/AndroidLogRemovalTest.java
index 1314274..0031ca2 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/logging/AndroidLogRemovalTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/logging/AndroidLogRemovalTest.java
@@ -51,7 +51,7 @@
                 .transformMethodInsnInMethod(
                     "main",
                     (opcode, owner, name, descriptor, isInterface, continuation) ->
-                        continuation.apply(
+                        continuation.visitMethodInsn(
                             opcode,
                             owner.endsWith("$Log") ? "android/util/Log" : owner,
                             name,
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/fields/singleton/SingletonFieldValuePropagationEnumTest.java b/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/fields/singleton/SingletonFieldValuePropagationEnumTest.java
index 1463a01..6fbed5e 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/fields/singleton/SingletonFieldValuePropagationEnumTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/fields/singleton/SingletonFieldValuePropagationEnumTest.java
@@ -5,8 +5,8 @@
 package com.android.tools.r8.ir.optimize.membervaluepropagation.fields.singleton;
 
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.CoreMatchers.not;
 import static org.hamcrest.MatcherAssert.assertThat;
-import static org.junit.Assert.assertEquals;
 
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
@@ -45,9 +45,7 @@
 
   private void inspect(CodeInspector inspector) {
     ClassSubject charactersClassSubject = inspector.clazz(Characters.class);
-    assertThat(charactersClassSubject, isPresent());
-    // TODO(b/150368955): Field value propagation should cause Characters.value to become dead.
-    assertEquals(1, charactersClassSubject.allInstanceFields().size());
+    assertThat(charactersClassSubject, not(isPresent()));
   }
 
   static class TestClass {
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/fields/singleton/SingletonFieldValuePropagationTest.java b/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/fields/singleton/SingletonFieldValuePropagationTest.java
index 371844e..3c78f04 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/fields/singleton/SingletonFieldValuePropagationTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/fields/singleton/SingletonFieldValuePropagationTest.java
@@ -5,8 +5,8 @@
 package com.android.tools.r8.ir.optimize.membervaluepropagation.fields.singleton;
 
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.CoreMatchers.not;
 import static org.hamcrest.MatcherAssert.assertThat;
-import static org.junit.Assert.assertEquals;
 
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
@@ -45,9 +45,7 @@
 
   private void inspect(CodeInspector inspector) {
     ClassSubject charactersClassSubject = inspector.clazz(Characters.class);
-    assertThat(charactersClassSubject, isPresent());
-    // TODO(b/150368955): Field value propagation should cause Characters.value to become dead.
-    assertEquals(1, charactersClassSubject.allInstanceFields().size());
+    assertThat(charactersClassSubject, not(isPresent()));
   }
 
   static class TestClass {
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/outliner/b111893131/B111893131.java b/src/test/java/com/android/tools/r8/ir/optimize/outliner/b111893131/B111893131.java
index cebcd05..ed54024 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/outliner/b111893131/B111893131.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/outliner/b111893131/B111893131.java
@@ -89,7 +89,7 @@
     CodeInspector inspector = new CodeInspector(app);
     ClassSubject classSubject = inspector.clazz(TestClass.class);
     assertThat(classSubject, isPresent());
-    DexClass clazz = classSubject.getDexClass();
+    DexClass clazz = classSubject.getDexProgramClass();
     clazz.forEachMethod(encodedMethod -> {
       Code code = encodedMethod.getCode();
       assertTrue(code.isDexCode());
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/redundantfieldloadelimination/InstanceFieldLoadsSeparatedByInvokeCustomTest.java b/src/test/java/com/android/tools/r8/ir/optimize/redundantfieldloadelimination/InstanceFieldLoadsSeparatedByInvokeCustomTest.java
index 86745e5..730c399 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/redundantfieldloadelimination/InstanceFieldLoadsSeparatedByInvokeCustomTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/redundantfieldloadelimination/InstanceFieldLoadsSeparatedByInvokeCustomTest.java
@@ -44,7 +44,6 @@
     testForR8(parameters.getBackend())
         .addProgramClassFileData(InstanceFieldLoadsSeparatedByInvokeCustomTestClassGenerator.dump())
         .addKeepAllClassesRule()
-        .allowDiagnosticWarningMessages()
         .setMinApi(parameters.getApiLevel())
         .compile()
         .assertAllWarningMessagesMatch(containsString("Unknown bootstrap method"))
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 b5ad06f..fc87e9b 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
@@ -346,7 +346,7 @@
   private List<String> instanceMethods(ClassSubject clazz) {
     assertNotNull(clazz);
     assertThat(clazz, isPresent());
-    return Streams.stream(clazz.getDexClass().methods())
+    return Streams.stream(clazz.getDexProgramClass().methods())
         .filter(method -> !method.isStatic())
         .map(method -> method.method.toSourceString())
         .sorted()
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/string/StringConcatenationTest.java b/src/test/java/com/android/tools/r8/ir/optimize/string/StringConcatenationTest.java
index 69c6355..cef72dc 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/string/StringConcatenationTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/string/StringConcatenationTest.java
@@ -88,9 +88,10 @@
   }
 
   private void test(TestRunResult result, boolean isR8, boolean isReleaseMode) throws Exception {
-    // TODO(b/114002137): The lack of subtyping made the escape analysis to regard
+    // TODO(b/154899065): The lack of subtyping made the escape analysis to regard
     //    StringBuilder#toString as an alias-introducing instruction.
     //    For now, debug v.s. release mode of D8 have the same result.
+    //    Use library modeling to allow this optimization.
 
     // Smaller is better in general. If the counter part is zero, that means non-string arguments
     // are used, and in that case bigger is better.
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/unusedinterfaces/UnusedInterfaceRemovalTest.java b/src/test/java/com/android/tools/r8/ir/optimize/unusedinterfaces/UnusedInterfaceRemovalTest.java
index d2fa3f8..8b2c2a1 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/unusedinterfaces/UnusedInterfaceRemovalTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/unusedinterfaces/UnusedInterfaceRemovalTest.java
@@ -59,11 +59,13 @@
 
     ClassSubject aClassSubject = inspector.clazz(A.class);
     assertThat(aClassSubject, isPresent());
-    assertEquals(2, aClassSubject.getDexClass().interfaces.size());
+    assertEquals(2, aClassSubject.getDexProgramClass().interfaces.size());
     assertEquals(
-        aClassSubject.getDexClass().interfaces.values[0], iClassSubject.getDexClass().type);
+        aClassSubject.getDexProgramClass().interfaces.values[0],
+        iClassSubject.getDexProgramClass().type);
     assertEquals(
-        aClassSubject.getDexClass().interfaces.values[1], jClassSubject.getDexClass().type);
+        aClassSubject.getDexProgramClass().interfaces.values[1],
+        jClassSubject.getDexProgramClass().type);
   }
 
   static class TestClass {
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/unusedinterfaces/UnusedInterfaceWithDefaultMethodTest.java b/src/test/java/com/android/tools/r8/ir/optimize/unusedinterfaces/UnusedInterfaceWithDefaultMethodTest.java
index 90761db..9a342e1 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/unusedinterfaces/UnusedInterfaceWithDefaultMethodTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/unusedinterfaces/UnusedInterfaceWithDefaultMethodTest.java
@@ -60,9 +60,10 @@
 
     // Verify that J is not considered an unused interface, since it provides an implementation of
     // m() that happens to be used.
-    assertEquals(1, aClassSubject.getDexClass().interfaces.size());
+    assertEquals(1, aClassSubject.getDexProgramClass().interfaces.size());
     assertEquals(
-        jClassSubject.getDexClass().type, aClassSubject.getDexClass().interfaces.values[0]);
+        jClassSubject.getDexProgramClass().type,
+        aClassSubject.getDexProgramClass().interfaces.values[0]);
   }
 
   static class TestClass {
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 bbc6c26..8ea5334 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,7 @@
 
 import com.android.tools.r8.errors.Unimplemented;
 import com.android.tools.r8.graph.AppInfo;
-import com.android.tools.r8.graph.AppInfoWithSubtyping;
+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.DexField;
@@ -50,12 +50,8 @@
     }
 
     @Override
-    public Value insertConstNullInstruction(IRCode code, InternalOptions options) {
-      throw new Unimplemented();
-    }
-
-    @Override
-    public Value insertConstIntInstruction(IRCode code, InternalOptions options, int value) {
+    public Value insertConstNumberInstruction(
+        IRCode code, InternalOptions options, long value, TypeElement type) {
       throw new Unimplemented();
     }
 
@@ -77,7 +73,7 @@
 
     @Override
     public void replaceCurrentInstructionWithThrowNull(
-        AppView<? extends AppInfoWithSubtyping> appView,
+        AppView<? extends AppInfoWithClassHierarchy> appView,
         IRCode code,
         ListIterator<BasicBlock> blockIterator,
         Set<BasicBlock> blocksToRemove,
diff --git a/src/test/java/com/android/tools/r8/jasmin/MemberResolutionTest.java b/src/test/java/com/android/tools/r8/jasmin/MemberResolutionTest.java
index b4dc614..fd893ea 100644
--- a/src/test/java/com/android/tools/r8/jasmin/MemberResolutionTest.java
+++ b/src/test/java/com/android/tools/r8/jasmin/MemberResolutionTest.java
@@ -5,7 +5,6 @@
 
 import static java.util.Collections.emptyList;
 
-import com.android.tools.r8.CompilationFailedException;
 import com.android.tools.r8.R8RunArtTestsTest.CompilerUnderTest;
 import com.android.tools.r8.ThrowingBiFunction;
 import com.android.tools.r8.ToolHelper.DexVm.Version;
@@ -17,7 +16,6 @@
 import com.android.tools.r8.jasmin.JasminBuilder.ClassFileVersion;
 import com.android.tools.r8.utils.ThrowingSupplier;
 import java.util.function.BiConsumer;
-import java.util.function.Predicate;
 import org.junit.Assert;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -293,8 +291,7 @@
         "  invokespecial SubClass/<init>()V",
         "  invokespecial SubClass/aMethod()V",
         "  return");
-    ensureExceptionOrCompilerError(builder, IllegalAccessError.class,
-        compiler -> compiler.equals(CompilerUnderTest.R8));
+    ensureException(builder, IllegalAccessError.class);
   }
 
   @Test
@@ -613,18 +610,14 @@
         (a, m) -> runOnArtR8Raw(a, m, keepMainProguardConfiguration(MAIN_CLASS), null));
   }
 
-  private void ensureExceptionOrCompilerError(JasminBuilder app,
-      Class<? extends Throwable> exception,
-      Predicate<CompilerUnderTest> predicate) throws Exception {
+  private void ensureException(JasminBuilder app, Class<? extends Throwable> exception)
+      throws Exception {
     String name = exception.getSimpleName();
     BiConsumer<ThrowingSupplier<ProcessResult, Exception>, CompilerUnderTest> runtest =
         (process, compiler) -> {
           try {
             ProcessResult result = process.get();
-            Assert.assertFalse(compiler != null && predicate.test(compiler));
             Assert.assertTrue(result.stderr.contains(name));
-          } catch (CompilationFailedException e) {
-            Assert.assertTrue(compiler == null || predicate.test(compiler));
           } catch (Exception e) {
             Assert.fail();
           }
diff --git a/src/test/java/com/android/tools/r8/kotlin/KotlinClassInlinerTest.java b/src/test/java/com/android/tools/r8/kotlin/KotlinClassInlinerTest.java
index 5d55ba2..d8419ca 100644
--- a/src/test/java/com/android/tools/r8/kotlin/KotlinClassInlinerTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/KotlinClassInlinerTest.java
@@ -66,10 +66,11 @@
   }
 
   private static Predicate<DexType> createLambdaCheck(CodeInspector inspector) {
-    Set<DexType> lambdaClasses = inspector.allClasses().stream()
-        .filter(clazz -> isLambda(clazz.getDexClass()))
-        .map(clazz -> clazz.getDexClass().type)
-        .collect(Collectors.toSet());
+    Set<DexType> lambdaClasses =
+        inspector.allClasses().stream()
+            .filter(clazz -> isLambda(clazz.getDexProgramClass()))
+            .map(clazz -> clazz.getDexProgramClass().type)
+            .collect(Collectors.toSet());
     return lambdaClasses::contains;
   }
 
diff --git a/src/test/java/com/android/tools/r8/kotlin/KotlinUnusedArgumentsInLambdasTest.java b/src/test/java/com/android/tools/r8/kotlin/KotlinUnusedArgumentsInLambdasTest.java
index 77de408..782eb68 100644
--- a/src/test/java/com/android/tools/r8/kotlin/KotlinUnusedArgumentsInLambdasTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/KotlinUnusedArgumentsInLambdasTest.java
@@ -10,12 +10,10 @@
 
 import com.android.tools.r8.ToolHelper.KotlinTargetVersion;
 import com.android.tools.r8.utils.BooleanUtils;
-import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.android.tools.r8.utils.codeinspector.MethodSubject;
 import com.google.common.collect.ImmutableList;
 import java.util.Collection;
-import java.util.function.Consumer;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
@@ -28,14 +26,6 @@
     return buildParameters(KotlinTargetVersion.values(), BooleanUtils.values());
   }
 
-  private Consumer<InternalOptions> optionsModifier =
-    o -> {
-      o.enableInlining = true;
-      o.enableLambdaMerging = true;
-      o.enableArgumentRemoval = true;
-      o.enableUnusedArgumentRemoval = true;
-    };
-
   public KotlinUnusedArgumentsInLambdasTest(
       KotlinTargetVersion targetVersion, boolean allowAccessModification) {
     super(targetVersion, allowAccessModification);
@@ -44,35 +34,43 @@
   @Test
   public void testMergingKStyleLambdasAfterUnusedArgumentRemoval() throws Exception {
     final String mainClassName = "unused_arg_in_lambdas_kstyle.MainKt";
-    runTest("unused_arg_in_lambdas_kstyle", mainClassName, optionsModifier, app -> {
-      CodeInspector inspector = new CodeInspector(app);
-      inspector.forAllClasses(classSubject -> {
-        if (classSubject.getOriginalDescriptor().contains("$ks")) {
-          MethodSubject init = classSubject.init(ImmutableList.of("int"));
-          assertThat(init, isPresent());
-          // Arity 2 should appear.
-          assertTrue(init.iterateInstructions(i -> i.isConstNumber(2)).hasNext());
+    runTest(
+        "unused_arg_in_lambdas_kstyle",
+        mainClassName,
+        app -> {
+          CodeInspector inspector = new CodeInspector(app);
+          inspector.forAllClasses(
+              classSubject -> {
+                if (classSubject.getOriginalDescriptor().contains("$ks")) {
+                  MethodSubject init = classSubject.init(ImmutableList.of("int"));
+                  assertThat(init, isPresent());
+                  // Arity 2 should appear.
+                  assertTrue(init.iterateInstructions(i -> i.isConstNumber(2)).hasNext());
 
-          MethodSubject invoke = classSubject.uniqueMethodWithName("invoke");
-          assertThat(invoke, isPresent());
-          assertEquals(2, invoke.getMethod().method.proto.parameters.size());
-        }
-      });
-    });
+                  MethodSubject invoke = classSubject.uniqueMethodWithName("invoke");
+                  assertThat(invoke, isPresent());
+                  assertEquals(2, invoke.getMethod().method.proto.parameters.size());
+                }
+              });
+        });
   }
 
   @Test
   public void testMergingJStyleLambdasAfterUnusedArgumentRemoval() throws Exception {
     final String mainClassName = "unused_arg_in_lambdas_jstyle.MainKt";
-    runTest("unused_arg_in_lambdas_jstyle", mainClassName, optionsModifier, app -> {
-      CodeInspector inspector = new CodeInspector(app);
-      inspector.forAllClasses(classSubject -> {
-        if (classSubject.getOriginalDescriptor().contains("$js")) {
-          MethodSubject get = classSubject.uniqueMethodWithName("get");
-          assertThat(get, isPresent());
-          assertEquals(3, get.getMethod().method.proto.parameters.size());
-        }
-      });
-    });
+    runTest(
+        "unused_arg_in_lambdas_jstyle",
+        mainClassName,
+        app -> {
+          CodeInspector inspector = new CodeInspector(app);
+          inspector.forAllClasses(
+              classSubject -> {
+                if (classSubject.getOriginalDescriptor().contains("$js")) {
+                  MethodSubject get = classSubject.uniqueMethodWithName("get");
+                  assertThat(get, isPresent());
+                  assertEquals(3, get.getMethod().method.proto.parameters.size());
+                }
+              });
+        });
   }
 }
diff --git a/src/test/java/com/android/tools/r8/kotlin/lambda/KotlinLambdaMergingTest.java b/src/test/java/com/android/tools/r8/kotlin/lambda/KotlinLambdaMergingTest.java
index bab791c..6aac431 100644
--- a/src/test/java/com/android/tools/r8/kotlin/lambda/KotlinLambdaMergingTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/lambda/KotlinLambdaMergingTest.java
@@ -42,7 +42,6 @@
   private Consumer<InternalOptions> getOptionsModifier() {
     return opts -> {
       opts.enableClassInlining = false;
-      opts.enableUnusedArgumentRemoval = false;
       // The test checks that the generated lambdas inherit from Function, which is not true if
       // the unused interface removal is enabled.
       opts.enableUnusedInterfaceRemoval = enableUnusedInterfaceRemoval;
@@ -183,16 +182,17 @@
     }
 
     private void initGroupsAndLambdas() {
-      codeInspector.forAllClasses(clazz -> {
-        DexClass dexClass = clazz.getDexClass();
-        if (isLambdaOrGroup(dexClass)) {
-          if (isLambdaGroupClass(dexClass)) {
-            groups.add(dexClass);
-          } else {
-            lambdas.add(dexClass);
-          }
-        }
-      });
+      codeInspector.forAllClasses(
+          clazz -> {
+            DexClass dexClass = clazz.getDexProgramClass();
+            if (isLambdaOrGroup(dexClass)) {
+              if (isLambdaGroupClass(dexClass)) {
+                groups.add(dexClass);
+              } else {
+                lambdas.add(dexClass);
+              }
+            }
+          });
     }
 
     void assertLambdaGroups(Group... groups) {
@@ -303,6 +303,7 @@
     runTest(
         "lambdas_kstyle_trivial",
         mainClassName,
+        "-keepunusedarguments class * extends kotlin.jvm.internal.Lambda { invoke(int, short); }",
         getOptionsModifier(),
         app -> {
           if (enableUnusedInterfaceRemoval) {
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/KotlinMetadataTestBase.java b/src/test/java/com/android/tools/r8/kotlin/metadata/KotlinMetadataTestBase.java
index 5799823..f3feb33 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/KotlinMetadataTestBase.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/KotlinMetadataTestBase.java
@@ -3,9 +3,13 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.kotlin.metadata;
 
+import static org.hamcrest.CoreMatchers.anyOf;
+import static org.hamcrest.CoreMatchers.containsString;
+
 import com.android.tools.r8.ToolHelper.KotlinTargetVersion;
 import com.android.tools.r8.kotlin.AbstractR8KotlinTestBase;
 import com.android.tools.r8.utils.DescriptorUtils;
+import org.hamcrest.Matcher;
 
 abstract class KotlinMetadataTestBase extends AbstractR8KotlinTestBase {
 
@@ -16,6 +20,7 @@
   static final String PKG = KotlinMetadataTestBase.class.getPackage().getName();
   static final String PKG_PREFIX = DescriptorUtils.getBinaryNameFromJavaType(PKG);
 
+  static final String KT_ANY = "Lkotlin/Any;";
   static final String KT_ARRAY = "Lkotlin/Array;";
   static final String KT_CHAR_SEQUENCE = "Lkotlin/CharSequence;";
   static final String KT_STRING = "Lkotlin/String;";
@@ -26,4 +31,8 @@
 
   static final String KT_FUNCTION1 = "Lkotlin/Function1;";
   static final String KT_COMPARABLE = "Lkotlin/Comparable;";
+
+  static Matcher<String> expectedInfoMessagesFromKotlinStdLib() {
+    return anyOf(containsString("Invalid descriptor"), containsString("No VersionRequirement"));
+  }
 }
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInClasspathTypeTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInClasspathTypeTest.java
index e86770c..4e5ae0e 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInClasspathTypeTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInClasspathTypeTest.java
@@ -9,6 +9,7 @@
 import static com.android.tools.r8.utils.codeinspector.Matchers.isRenamed;
 import static org.hamcrest.CoreMatchers.not;
 import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
 
 import com.android.tools.r8.TestParameters;
@@ -147,11 +148,10 @@
     List<ClassSubject> superTypes = kmClass.getSuperTypes();
     assertTrue(superTypes.stream().noneMatch(
         supertype -> supertype.getFinalDescriptor().contains("Impl")));
-    // Can't build ClassSubject with Itf in classpath. Instead, check if the reference to Itf is
-    // not altered via descriptors.
+    // The super types are changed and we should not keep any information about it in the metadata.
     List<String> superTypeDescriptors = kmClass.getSuperTypeDescriptors();
-    assertTrue(superTypeDescriptors.stream().noneMatch(supertype -> supertype.contains("Impl")));
-    assertTrue(superTypeDescriptors.stream().anyMatch(supertype -> supertype.contains("Itf")));
+    assertEquals(1, superTypeDescriptors.size());
+    assertEquals(KT_ANY, superTypeDescriptors.get(0));
   }
 
   @Test
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInExtensionFunctionTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInExtensionFunctionTest.java
index a9f1655..aaf2674 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInExtensionFunctionTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInExtensionFunctionTest.java
@@ -31,6 +31,7 @@
 import java.util.List;
 import java.util.Map;
 import org.junit.BeforeClass;
+import org.junit.Ignore;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
@@ -86,6 +87,7 @@
   }
 
   @Test
+  @Ignore("b/154300326")
   public void testMetadataInExtensionFunction_merged() throws Exception {
     Path libJar =
         testForR8(parameters.getBackend())
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInExtensionPropertyTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInExtensionPropertyTest.java
index afd2ac6..1606cbe 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInExtensionPropertyTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInExtensionPropertyTest.java
@@ -30,6 +30,7 @@
 import java.util.List;
 import java.util.Map;
 import org.junit.BeforeClass;
+import org.junit.Ignore;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
@@ -85,6 +86,7 @@
   }
 
   @Test
+  @Ignore("b/154300326")
   public void testMetadataInExtensionProperty_merged() throws Exception {
     Path libJar =
         testForR8(parameters.getBackend())
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInFunctionTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInFunctionTest.java
index fad1701..60cad3b 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInFunctionTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInFunctionTest.java
@@ -29,6 +29,7 @@
 import java.util.List;
 import java.util.Map;
 import org.junit.BeforeClass;
+import org.junit.Ignore;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
@@ -84,6 +85,7 @@
   }
 
   @Test
+  @Ignore("b/154300326")
   public void testMetadataInFunction_merged() throws Exception {
     Path libJar =
         testForR8(parameters.getBackend())
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInPropertyTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInPropertyTest.java
index e58725d..3847134 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInPropertyTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInPropertyTest.java
@@ -7,11 +7,11 @@
 import static com.android.tools.r8.utils.codeinspector.Matchers.isExtensionProperty;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isRenamed;
+import static junit.framework.TestCase.assertNull;
 import static org.hamcrest.CoreMatchers.not;
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertNull;
 
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.ToolHelper;
@@ -139,23 +139,22 @@
     assertEquals(backingField.getJvmFieldSignatureAsString(), name.fieldSignature().asString());
     assertNotNull(name.getterSignature());
     assertEquals(getterForName.getJvmMethodSignatureAsString(), name.getterSignature().asString());
-    assertNull(name.setterSignature());
+    assertEquals(name.setterSignature().asString(), "setName(Ljava/lang/String;)V");
 
     KmPropertySubject familyName = kmClass.kmPropertyWithUniqueName("familyName");
     assertThat(familyName, isPresent());
     assertThat(familyName, not(isExtensionProperty()));
     // No backing field for property `familyName`
     assertNull(familyName.fieldSignature());
-    assertNotNull(familyName.getterSignature());
-    // No setter for property `familyName`
+    assertEquals(familyName.getterSignature().asString(), "getFamilyName()Ljava/lang/String;");
     assertNull(familyName.setterSignature());
 
     KmPropertySubject age = kmClass.kmPropertyWithUniqueName("age");
     assertThat(age, isPresent());
     assertThat(age, not(isExtensionProperty()));
-    assertNotNull(age.fieldSignature());
-    assertNotNull(age.getterSignature());
-    assertNull(name.setterSignature());
+    assertEquals(age.fieldSignature().asString(), "a:I");
+    assertEquals(age.getterSignature().asString(), "getAge()I");
+    assertEquals(age.setterSignature().asString(), "setAge(I)V");
   }
 
   @Test
@@ -222,9 +221,31 @@
     KmPropertySubject name = kmClass.kmPropertyWithUniqueName("name");
     assertThat(name, isPresent());
     assertThat(name, not(isExtensionProperty()));
-    assertNull(name.fieldSignature());
-    assertNull(name.getterSignature());
-    assertNotNull(name.setterSignature());
+    // Oddly, the signature of field and getter are present even if there is only a setter:
+    // #      KmProperty{
+    // #        flags: 1798,
+    // #        name: name,
+    // #        receiverParameterType: null,
+    // #        returnType: KmType{
+    // #          flags: 0,
+    // #          classifier: Class(name=kotlin/String),
+    // #          arguments: KmTypeProjection[],
+    // #          abbreviatedType: null,
+    // #          outerType: null,
+    // #          raw: false,
+    // #          annotations: KmAnnotion[],
+    // #        },
+    // #        typeParameters: KmTypeParameter[],
+    // #        getterFlags: 6,
+    // #        setterFlags: 6,
+    // #        setterParameter: null,
+    // #        jvmFlags: 0,
+    // #        fieldSignature: name:Ljava/lang/String;,
+    // #        getterSignature: getName()Ljava/lang/String;,
+    // #        setterSignature: setName(Ljava/lang/String;)V,
+    assertEquals(name.fieldSignature().asString(), "name:Ljava/lang/String;");
+    assertEquals(name.getterSignature().asString(), "getName()Ljava/lang/String;");
+    assertEquals(name.setterSignature().asString(), "setName(Ljava/lang/String;)V");
 
     KmPropertySubject familyName = kmClass.kmPropertyWithUniqueName("familyName");
     assertThat(familyName, not(isPresent()));
@@ -232,8 +253,8 @@
     KmPropertySubject age = kmClass.kmPropertyWithUniqueName("age");
     assertThat(age, isPresent());
     assertThat(age, not(isExtensionProperty()));
-    assertNotNull(age.fieldSignature());
-    assertNull(age.getterSignature());
-    assertNotNull(age.setterSignature());
+    assertEquals(age.fieldSignature().asString(), "a:I");
+    assertEquals(age.getterSignature().asString(), "getAge()I");
+    assertEquals(age.setterSignature().asString(), "setAge(I)V");
   }
 }
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInRenamedTypeTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInRenamedTypeTest.java
index 6bfafc4..f474cf1 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInRenamedTypeTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInRenamedTypeTest.java
@@ -105,8 +105,10 @@
         .addKeepAttributes(ProguardKeepAttributes.RUNTIME_VISIBLE_ANNOTATIONS)
         .allowDiagnosticWarningMessages()
         .compile()
-        .assertWarningMessageThatMatches(
-            equalTo("Resource 'META-INF/MANIFEST.MF' already exists."))
+        .assertWarningMessageThatMatches(equalTo("Resource 'META-INF/MANIFEST.MF' already exists."))
+        // TODO(b/155536535): Enable this assert.
+        // .assertInfoMessageThatMatches(expectedInfoMessagesFromKotlinStdLib())
+        .assertNoErrorMessages()
         .inspect(this::inspect);
   }
 
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInTypeAliasTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInTypeAliasTest.java
index b1af80f..26ea8de 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInTypeAliasTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInTypeAliasTest.java
@@ -185,8 +185,8 @@
     // Check that typealias API = Itf has been rewritten correctly.
     KmTypeAliasSubject api = kmPackage.kmTypeAliasWithUniqueName("API");
     assertThat(api, isPresent());
-    assertThat(api.expandedType(), isDexClass(itf.getDexClass()));
-    assertThat(api.underlyingType(), isDexClass(itf.getDexClass()));
+    assertThat(api.expandedType(), isDexClass(itf.getDexProgramClass()));
+    assertThat(api.underlyingType(), isDexClass(itf.getDexProgramClass()));
 
     // Check that the type-alias APIs exist and that the expanded type is renamed.
     KmTypeAliasSubject apIs = kmPackage.kmTypeAliasWithUniqueName("APIs");
@@ -194,7 +194,7 @@
     assertEquals(arrayDescriptor, apIs.expandedType().descriptor());
     assertEquals(1, apIs.expandedType().typeArguments().size());
     KmTypeProjectionSubject expandedArgument = apIs.expandedType().typeArguments().get(0);
-    assertThat(expandedArgument.type(), isDexClass(itf.getDexClass()));
+    assertThat(expandedArgument.type(), isDexClass(itf.getDexProgramClass()));
 
     assertEquals(myAliasedArray.descriptor(packageName), apIs.underlyingType().descriptor());
     assertEquals(1, apIs.underlyingType().typeArguments().size());
@@ -219,15 +219,15 @@
     // typealias Arr1D<K> = Arr<K>
     KmTypeAliasSubject arr1D = kmPackage.kmTypeAliasWithUniqueName("Arr1D");
     assertThat(arr1D, isPresent());
-    assertThat(arr1D.expandedType(), isDexClass(arr.getDexClass()));
+    assertThat(arr1D.expandedType(), isDexClass(arr.getDexProgramClass()));
 
     // typealias Arr2D<K> = Arr1D<Arr1D<K>>
     KmTypeAliasSubject arr2D = kmPackage.kmTypeAliasWithUniqueName("Arr2D");
     assertThat(arr2D, isPresent());
-    assertThat(arr2D.expandedType(), isDexClass(arr.getDexClass()));
+    assertThat(arr2D.expandedType(), isDexClass(arr.getDexProgramClass()));
     assertEquals(1, arr2D.expandedType().typeArguments().size());
     KmTypeProjectionSubject arr2DexpandedArg = arr2D.expandedType().typeArguments().get(0);
-    assertThat(arr2DexpandedArg.type(), isDexClass(arr.getDexClass()));
+    assertThat(arr2DexpandedArg.type(), isDexClass(arr.getDexProgramClass()));
 
     assertEquals(arr1D.descriptor(packageName), arr2D.underlyingType().descriptor());
     assertEquals(1, arr2D.underlyingType().typeArguments().size());
@@ -256,7 +256,7 @@
     assertEquals(intSet.descriptor(packageName), underlyingType.descriptor());
 
     KmTypeSubject expandedType = myMapToSetOfInt.expandedType().typeArguments().get(1).type();
-    assertEquals(intSet.expandedType(), expandedType);
+    assertTrue(intSet.expandedType().equalUpToAbbreviatedType(expandedType));
 
     // Check that the following exist:
     // typealias MyHandler = (Int, Any) -> Unit
@@ -282,6 +282,6 @@
     assertThat(classWithCompanionC, isPresent());
 
     ClassSubject companionClazz = inspector.clazz(packageName + ".ClassWithCompanion$Companion");
-    assertThat(classWithCompanionC.expandedType(), isDexClass(companionClazz.getDexClass()));
+    assertThat(classWithCompanionC.expandedType(), isDexClass(companionClazz.getDexProgramClass()));
   }
 }
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInTypeArgumentsTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInTypeArgumentsTest.java
index a158b99..2e3dd21 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInTypeArgumentsTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInTypeArgumentsTest.java
@@ -213,12 +213,13 @@
     KmTypeParameterSubject methodTypeParameter = funGenericsWithUpperBounds.typeParameters().get(0);
     List<KmTypeSubject> upperBounds = methodTypeParameter.upperBounds();
     assertEquals(2, upperBounds.size());
-    assertThat(upperBounds.get(0), isDexClass(someClass.getDexClass()));
+    assertThat(upperBounds.get(0), isDexClass(someClass.getDexProgramClass()));
     assertEquals(KT_COMPARABLE, upperBounds.get(1).descriptor());
     // Check that the upper bound has a type argument.
     assertEquals(1, upperBounds.get(1).typeArguments().size());
     assertThat(
-        upperBounds.get(1).typeArguments().get(0).type(), isDexClass(someClass.getDexClass()));
+        upperBounds.get(1).typeArguments().get(0).type(),
+        isDexClass(someClass.getDexProgramClass()));
   }
 
   private void inspectCoVariant(CodeInspector inspector) {
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewritePassThroughTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewritePassThroughTest.java
index c120f9d..fcb5958 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewritePassThroughTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewritePassThroughTest.java
@@ -5,6 +5,7 @@
 package com.android.tools.r8.kotlin.metadata;
 
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static junit.framework.TestCase.assertEquals;
 import static junit.framework.TestCase.assertNotNull;
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.junit.Assert.assertNull;
@@ -19,6 +20,7 @@
 import java.io.IOException;
 import java.util.Collection;
 import java.util.concurrent.ExecutionException;
+import kotlinx.metadata.jvm.KotlinClassHeader;
 import kotlinx.metadata.jvm.KotlinClassMetadata;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -50,6 +52,9 @@
         .addKeepRules("-keep class kotlin.Metadata")
         .addKeepAttributes(ProguardKeepAttributes.RUNTIME_VISIBLE_ANNOTATIONS)
         .compile()
+        // TODO(b/155536535): Enable this assert.
+        // .assertAllInfoMessagesMatch(expectedInfoMessagesFromKotlinStdLib())
+        // .assertInfosCount(5)
         .inspect(this::inspect);
   }
 
@@ -59,12 +64,26 @@
       ClassSubject r8Clazz = inspector.clazz(clazzSubject.getOriginalName());
       assertThat(r8Clazz, isPresent());
       KotlinClassMetadata originalMetadata = clazzSubject.getKotlinClassMetadata();
+      KotlinClassMetadata rewrittenMetadata = r8Clazz.getKotlinClassMetadata();
       if (originalMetadata == null) {
-        assertNull(r8Clazz.getKotlinClassMetadata());
+        assertNull(rewrittenMetadata);
         continue;
       }
-      assertNotNull(r8Clazz.getKotlinClassMetadata());
-      // TODO(b/152153136): Extend the test with assertions about metadata equality.
+      assertNotNull(rewrittenMetadata);
+      KotlinClassHeader originalHeader = originalMetadata.getHeader();
+      KotlinClassHeader rewrittenHeader = rewrittenMetadata.getHeader();
+      assertEquals(originalHeader.getKind(), rewrittenHeader.getKind());
+      // TODO(b/154199572): Should we check for meta-data version?
+      assertEquals(originalHeader.getPackageName(), rewrittenHeader.getPackageName());
+      // We cannot assert equality of the data since it may be ordered differently. Instead we use
+      // the KotlinMetadataWriter.
+      // TODO(b/155571455): Deactivating the method call to kotlinMetadataString until resolved.
+      // String expected = KotlinMetadataWriter.kotlinMetadataToString("", originalMetadata);
+      // String actual = KotlinMetadataWriter.kotlinMetadataToString("", rewrittenMetadata);
+      // // TODO(b/155534905): For invalid synthetic class lambdas, we emit null after rewriting.
+      // if (clazzSubject.getKotlinClassMetadata().getHeader().getKind() != 3) {
+      //   assertEquals(expected, actual);
+      // }
     }
   }
 }
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataStripTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataStripTest.java
index 4d66342..94edd94 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataStripTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataStripTest.java
@@ -9,7 +9,6 @@
 import static org.hamcrest.CoreMatchers.not;
 import static org.hamcrest.MatcherAssert.assertThat;
 
-import com.android.tools.r8.KotlinTestBase;
 import com.android.tools.r8.R8TestRunResult;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.ToolHelper;
@@ -24,7 +23,7 @@
 import org.junit.runners.Parameterized;
 
 @RunWith(Parameterized.class)
-public class MetadataStripTest extends KotlinTestBase {
+public class MetadataStripTest extends KotlinMetadataTestBase {
 
   private final TestParameters parameters;
 
@@ -54,11 +53,13 @@
             .addKeepRules("-keep class kotlin.Metadata")
             // TODO(b/151194540): if this option is settled down, this test is meaningless.
             .addOptionsModification(o -> o.enableKotlinMetadataRewritingForRenamedClasses = false)
-            .allowDiagnosticWarningMessages()
+            .allowDiagnosticMessages()
             .setMinApi(parameters.getApiLevel())
             .compile()
             .assertAllWarningMessagesMatch(
                 equalTo("Resource 'META-INF/MANIFEST.MF' already exists."))
+            .assertAllInfoMessagesMatch(expectedInfoMessagesFromKotlinStdLib())
+            .assertNoErrorMessages()
             .run(parameters.getRuntime(), mainClassName);
     CodeInspector inspector = result.inspector();
     ClassSubject clazz = inspector.clazz(mainClassName);
diff --git a/src/test/java/com/android/tools/r8/naming/ClassObfuscationDictionaryDuplicateTest.java b/src/test/java/com/android/tools/r8/naming/ClassObfuscationDictionaryDuplicateTest.java
index 4f3a868..e0ef67d 100644
--- a/src/test/java/com/android/tools/r8/naming/ClassObfuscationDictionaryDuplicateTest.java
+++ b/src/test/java/com/android/tools/r8/naming/ClassObfuscationDictionaryDuplicateTest.java
@@ -40,7 +40,7 @@
 
   @Parameterized.Parameters(name = "{0}")
   public static TestParametersCollection data() {
-    return getTestParameters().withAllRuntimes().build();
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
   }
 
   public ClassObfuscationDictionaryDuplicateTest(TestParameters parameters) {
@@ -61,8 +61,7 @@
         .noTreeShaking()
         .addKeepRules("-classobfuscationdictionary " + dictionary.toString())
         .addKeepMainRule(C.class)
-        .setMinApi(parameters.getRuntime())
-        .compile()
+        .setMinApi(parameters.getApiLevel())
         .run(parameters.getRuntime(), C.class)
         .assertSuccessWithOutput("HELLO WORLD!")
         .inspect(
diff --git a/src/test/java/com/android/tools/r8/naming/DontUseMixedCaseClassNamesExistingClassTest.java b/src/test/java/com/android/tools/r8/naming/DontUseMixedCaseClassNamesExistingClassTest.java
new file mode 100644
index 0000000..b16347d
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/naming/DontUseMixedCaseClassNamesExistingClassTest.java
@@ -0,0 +1,82 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.naming;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.utils.BooleanUtils;
+import com.android.tools.r8.utils.FileUtils;
+import java.io.IOException;
+import java.nio.file.Path;
+import java.util.List;
+import java.util.concurrent.ExecutionException;
+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 DontUseMixedCaseClassNamesExistingClassTest extends TestBase {
+
+  private final TestParameters parameters;
+  private final boolean dontUseMixedCase;
+  private static final String EXPECTED = "A.foo";
+  private static final String FINAL_CLASS_NAME = "DontUseMixedCaseClassNamesExistingClassTest$main";
+
+  @Parameters(name = "{0}, dontusemixedcaseclassnames: {1}")
+  public static List<Object[]> data() {
+    return buildParameters(
+        getTestParameters().withAllRuntimesAndApiLevels().build(), BooleanUtils.values());
+  }
+
+  public DontUseMixedCaseClassNamesExistingClassTest(
+      TestParameters parameters, boolean dontUseMixedCase) {
+    this.parameters = parameters;
+    this.dontUseMixedCase = dontUseMixedCase;
+  }
+
+  @Test
+  public void testR8() throws ExecutionException, CompilationFailedException, IOException {
+    Path dictionary = temp.getRoot().toPath().resolve("dictionary.txt");
+    FileUtils.writeTextFile(dictionary, FINAL_CLASS_NAME);
+    testForR8(parameters.getBackend())
+        .addProgramClasses(A.class, Main.class)
+        .setMinApi(parameters.getApiLevel())
+        .addKeepClassRulesWithAllowObfuscation(A.class)
+        .addKeepMainRule(Main.class)
+        .addKeepRules("-classobfuscationdictionary " + dictionary.toString())
+        .ifTrue(dontUseMixedCase, b -> b.addKeepRules("-dontusemixedcaseclassnames"))
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines(EXPECTED)
+        .inspect(
+            inspector -> {
+              String finalName = Main.class.getPackage().getName() + "." + FINAL_CLASS_NAME;
+              assertEquals(finalName.toLowerCase(), Main.class.getTypeName().toLowerCase());
+              if (dontUseMixedCase) {
+                assertNotEquals(finalName, inspector.clazz(A.class).getFinalName());
+              } else {
+                assertEquals(finalName, inspector.clazz(A.class).getFinalName());
+              }
+            });
+  }
+
+  public static class A { // Will be renamed to main if not -dontusemixedcaseclassnames
+
+    public void foo() {
+      System.out.println("A.foo");
+    }
+  }
+
+  public static class Main {
+
+    public static void main(String[] args) {
+      new A().foo();
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/naming/IdentifierMinifierTest.java b/src/test/java/com/android/tools/r8/naming/IdentifierMinifierTest.java
index 627a059..ad8c526 100644
--- a/src/test/java/com/android/tools/r8/naming/IdentifierMinifierTest.java
+++ b/src/test/java/com/android/tools/r8/naming/IdentifierMinifierTest.java
@@ -148,7 +148,7 @@
     assertEquals(countInABar, renamedYetFoundIdentifierCount);
 
     renamedYetFoundIdentifierCount =
-        countRenamedClassIdentifier(inspector, aClass.getDexClass().staticFields());
+        countRenamedClassIdentifier(inspector, aClass.getDexProgramClass().staticFields());
     assertEquals(countInAFields, renamedYetFoundIdentifierCount);
   }
 
@@ -208,7 +208,7 @@
     assertEquals(0, renamedYetFoundIdentifierCount);
 
     renamedYetFoundIdentifierCount =
-        countRenamedClassIdentifier(inspector, aClass.getDexClass().staticFields());
+        countRenamedClassIdentifier(inspector, aClass.getDexProgramClass().staticFields());
     assertEquals(0, renamedYetFoundIdentifierCount);
   }
 
@@ -229,7 +229,7 @@
     assertEquals(1, renamedYetFoundIdentifierCount);
 
     renamedYetFoundIdentifierCount =
-        countRenamedClassIdentifier(inspector, aClass.getDexClass().staticFields());
+        countRenamedClassIdentifier(inspector, aClass.getDexProgramClass().staticFields());
     assertEquals(2, renamedYetFoundIdentifierCount);
   }
 
diff --git a/src/test/java/com/android/tools/r8/naming/NamingTestBase.java b/src/test/java/com/android/tools/r8/naming/NamingTestBase.java
index f0b3c24..385911c 100644
--- a/src/test/java/com/android/tools/r8/naming/NamingTestBase.java
+++ b/src/test/java/com/android/tools/r8/naming/NamingTestBase.java
@@ -5,11 +5,12 @@
 
 import com.android.tools.r8.DexIndexedConsumer;
 import com.android.tools.r8.ToolHelper;
-import com.android.tools.r8.graph.AppInfoWithSubtyping;
+import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
 import com.android.tools.r8.graph.AppServices;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DirectMappedDexApplication;
+import com.android.tools.r8.graph.SubtypingInfo;
 import com.android.tools.r8.shaking.Enqueuer;
 import com.android.tools.r8.shaking.EnqueuerFactory;
 import com.android.tools.r8.shaking.ProguardConfiguration;
@@ -61,18 +62,20 @@
   protected NamingLens runMinifier(List<Path> configPaths) throws ExecutionException {
     ProguardConfiguration configuration =
         ToolHelper.loadProguardConfiguration(dexItemFactory, configPaths);
+
     InternalOptions options = new InternalOptions(configuration, new Reporter());
     options.programConsumer = DexIndexedConsumer.emptyConsumer();
 
     ExecutorService executor = ThreadUtils.getExecutorService(1);
 
-    AppView<AppInfoWithSubtyping> appView =
-        AppView.createForR8(new AppInfoWithSubtyping(program), options);
+    AppView<AppInfoWithClassHierarchy> appView =
+        AppView.createForR8(new AppInfoWithClassHierarchy(program), options);
+    SubtypingInfo subtypingInfo = new SubtypingInfo(program.allClasses(), program);
     appView.setRootSet(
-        new RootSetBuilder(appView, program, configuration.getRules()).run(executor));
+        new RootSetBuilder(appView, subtypingInfo, configuration.getRules()).run(executor));
     appView.setAppServices(AppServices.builder(appView).build());
 
-    Enqueuer enqueuer = EnqueuerFactory.createForInitialTreeShaking(appView);
+    Enqueuer enqueuer = EnqueuerFactory.createForInitialTreeShaking(appView, subtypingInfo);
     appView.setAppInfo(
         enqueuer.traceApplication(
             appView.rootSet(), configuration.getDontWarnPatterns(), executor, timing));
diff --git a/src/test/java/com/android/tools/r8/naming/NonMemberClassTest.java b/src/test/java/com/android/tools/r8/naming/NonMemberClassTest.java
index 04c39b4..f777cda 100644
--- a/src/test/java/com/android/tools/r8/naming/NonMemberClassTest.java
+++ b/src/test/java/com/android/tools/r8/naming/NonMemberClassTest.java
@@ -117,9 +117,12 @@
       }
       assertEquals(
           expectedNumberOfNonMemberInnerClasses,
-          inspector.allClasses().stream().filter(classSubject ->
-              classSubject.getDexClass().isLocalClass()
-                  || classSubject.getDexClass().isAnonymousClass()).count());
+          inspector.allClasses().stream()
+              .filter(
+                  classSubject ->
+                      classSubject.getDexProgramClass().isLocalClass()
+                          || classSubject.getDexProgramClass().isAnonymousClass())
+              .count());
     }
   }
 
diff --git a/src/test/java/com/android/tools/r8/naming/PackageObfuscationDictionaryDuplicateTest.java b/src/test/java/com/android/tools/r8/naming/PackageObfuscationDictionaryDuplicateTest.java
index f10947e..133e607 100644
--- a/src/test/java/com/android/tools/r8/naming/PackageObfuscationDictionaryDuplicateTest.java
+++ b/src/test/java/com/android/tools/r8/naming/PackageObfuscationDictionaryDuplicateTest.java
@@ -11,7 +11,7 @@
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.naming.keeppackagenames.Top;
-import com.android.tools.r8.naming.packageobfucationdict.A;
+import com.android.tools.r8.naming.packageobfuscationdict.A;
 import com.android.tools.r8.utils.FileUtils;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import java.io.IOException;
diff --git a/src/test/java/com/android/tools/r8/naming/RenameSourceFileRetraceTest.java b/src/test/java/com/android/tools/r8/naming/RenameSourceFileRetraceTest.java
index 4de8e4b..6f5aade 100644
--- a/src/test/java/com/android/tools/r8/naming/RenameSourceFileRetraceTest.java
+++ b/src/test/java/com/android/tools/r8/naming/RenameSourceFileRetraceTest.java
@@ -125,7 +125,7 @@
   private void inspectSourceFileForClass(CodeInspector inspector, Class<?> clazz, String expected) {
     ClassSubject classToBeMinifiedSubject = inspector.clazz(clazz);
     assertThat(classToBeMinifiedSubject, isPresent());
-    DexClass dexClass = classToBeMinifiedSubject.getDexClass();
+    DexClass dexClass = classToBeMinifiedSubject.getDexProgramClass();
     String actualString = dexClass.sourceFile == null ? null : dexClass.sourceFile.toString();
     assertEquals(expected, actualString);
   }
diff --git a/src/test/java/com/android/tools/r8/naming/b155249069/A/A.java b/src/test/java/com/android/tools/r8/naming/b155249069/A/A.java
new file mode 100644
index 0000000..c0690b6
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/naming/b155249069/A/A.java
@@ -0,0 +1,12 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.naming.b155249069.A;
+
+public class A {
+
+  public void foo() {
+    System.out.println("A.A.foo()");
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/naming/b155249069/DontUseMixedCaseClassNamesExistingClassPackageTest.java b/src/test/java/com/android/tools/r8/naming/b155249069/DontUseMixedCaseClassNamesExistingClassPackageTest.java
new file mode 100644
index 0000000..a55ef4b
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/naming/b155249069/DontUseMixedCaseClassNamesExistingClassPackageTest.java
@@ -0,0 +1,82 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.naming.b155249069;
+
+import static junit.framework.TestCase.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.naming.b155249069.package_b.A;
+import com.android.tools.r8.utils.BooleanUtils;
+import com.android.tools.r8.utils.FileUtils;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import java.io.IOException;
+import java.nio.file.Path;
+import java.util.List;
+import java.util.concurrent.ExecutionException;
+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 DontUseMixedCaseClassNamesExistingClassPackageTest extends TestBase {
+
+  private final TestParameters parameters;
+  private final boolean dontUseMixedCase;
+
+  @Parameters(name = "{0}, dontusemixedcaseclassnames: {1}")
+  public static List<Object[]> data() {
+    return buildParameters(
+        getTestParameters().withAllRuntimesAndApiLevels().build(), BooleanUtils.values());
+  }
+
+  public DontUseMixedCaseClassNamesExistingClassPackageTest(
+      TestParameters parameters, boolean dontUseMixedCase) {
+    this.parameters = parameters;
+    this.dontUseMixedCase = dontUseMixedCase;
+  }
+
+  @Test
+  public void testR8() throws ExecutionException, CompilationFailedException, IOException {
+    Path packageDictionary = temp.getRoot().toPath().resolve("packagedictionary.txt");
+    // Suggest the name 'a' for the package, to force a collision with A.A.
+    FileUtils.writeTextFile(packageDictionary, "a");
+    testForR8(parameters.getBackend())
+        .addProgramClasses(Main.class, com.android.tools.r8.naming.b155249069.A.A.class, A.class)
+        .setMinApi(parameters.getApiLevel())
+        // Keep A.A such that the package A is kept.
+        .addKeepClassRules(com.android.tools.r8.naming.b155249069.A.A.class)
+        .addKeepClassRulesWithAllowObfuscation(A.class)
+        .addKeepMainRule(Main.class)
+        .addKeepRules("-packageobfuscationdictionary " + packageDictionary.toString())
+        .ifTrue(dontUseMixedCase, b -> b.addKeepRules("-dontusemixedcaseclassnames"))
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines("A.A.foo()", "package_b.B.foo()")
+        .inspect(
+            inspector -> {
+              ClassSubject aSubject =
+                  inspector.clazz(com.android.tools.r8.naming.b155249069.A.A.class);
+              ClassSubject bSubject = inspector.clazz(A.class);
+              if (dontUseMixedCase) {
+                assertNotEquals(
+                    aSubject.getFinalName().toLowerCase(), bSubject.getFinalName().toLowerCase());
+              } else {
+                assertEquals(
+                    aSubject.getFinalName().toLowerCase(), bSubject.getFinalName().toLowerCase());
+              }
+            });
+  }
+
+  public static class Main {
+
+    public static void main(String[] args) {
+      new com.android.tools.r8.naming.b155249069.A.A().foo();
+      new A().foo();
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/naming/b155249069/package_b/A.java b/src/test/java/com/android/tools/r8/naming/b155249069/package_b/A.java
new file mode 100644
index 0000000..a02a90d
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/naming/b155249069/package_b/A.java
@@ -0,0 +1,12 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.naming.b155249069.package_b;
+
+public class A {
+
+  public void foo() {
+    System.out.println("package_b.B.foo()");
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/naming/packageobfucationdict/A.java b/src/test/java/com/android/tools/r8/naming/packageobfuscationdict/A.java
similarity index 79%
rename from src/test/java/com/android/tools/r8/naming/packageobfucationdict/A.java
rename to src/test/java/com/android/tools/r8/naming/packageobfuscationdict/A.java
index 012140e..67f256b 100644
--- a/src/test/java/com/android/tools/r8/naming/packageobfucationdict/A.java
+++ b/src/test/java/com/android/tools/r8/naming/packageobfuscationdict/A.java
@@ -2,6 +2,6 @@
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
-package com.android.tools.r8.naming.packageobfucationdict;
+package com.android.tools.r8.naming.packageobfuscationdict;
 
 public class A {}
diff --git a/src/test/java/com/android/tools/r8/regress/B76025099.java b/src/test/java/com/android/tools/r8/regress/B76025099.java
index 5c5f494..59cfbe8 100644
--- a/src/test/java/com/android/tools/r8/regress/B76025099.java
+++ b/src/test/java/com/android/tools/r8/regress/B76025099.java
@@ -143,7 +143,7 @@
     FieldAccessInstructionSubject fieldAccessInstruction =
         (FieldAccessInstructionSubject) instruction;
     assertEquals("name", fieldAccessInstruction.name());
-    assertTrue(fieldAccessInstruction.holder().is(impl.getDexClass().type.toString()));
+    assertTrue(fieldAccessInstruction.holder().is(impl.getDexProgramClass().type.toString()));
 
     assertNotNull(findInstructionOrNull(iterator, InstructionSubject::isReturnVoid));
 
diff --git a/src/test/java/com/android/tools/r8/regress/Regress37740372.java b/src/test/java/com/android/tools/r8/regress/Regress37740372.java
index 34e4f3e..8047716 100644
--- a/src/test/java/com/android/tools/r8/regress/Regress37740372.java
+++ b/src/test/java/com/android/tools/r8/regress/Regress37740372.java
@@ -126,7 +126,7 @@
 
   private void assertIsJavaLangObjet(ClassSubject clazz) {
     assertTrue(clazz.getOriginalDescriptor().equals("Ljava/lang/Object;"));
-    assertNull(clazz.getDexClass().superType);
+    assertNull(clazz.getDexProgramClass().superType);
   }
 
   private void checkApplicationOnlyHasJavaLangObject(AndroidApp app) throws Throwable {
diff --git a/src/test/java/com/android/tools/r8/regress/b152800551/FailedStaticizingRegressionTest.java b/src/test/java/com/android/tools/r8/regress/b152800551/StaticizerGetterRewritingRegressionTest.java
similarity index 93%
rename from src/test/java/com/android/tools/r8/regress/b152800551/FailedStaticizingRegressionTest.java
rename to src/test/java/com/android/tools/r8/regress/b152800551/StaticizerGetterRewritingRegressionTest.java
index 7ec8ab0..5162092 100644
--- a/src/test/java/com/android/tools/r8/regress/b152800551/FailedStaticizingRegressionTest.java
+++ b/src/test/java/com/android/tools/r8/regress/b152800551/StaticizerGetterRewritingRegressionTest.java
@@ -14,7 +14,7 @@
 
 // This is a reproduction of b/152800551.
 @RunWith(Parameterized.class)
-public class FailedStaticizingRegressionTest extends TestBase {
+public class StaticizerGetterRewritingRegressionTest extends TestBase {
 
   private static final String EXPECTED =
       StringUtils.lines(
@@ -27,7 +27,7 @@
     return getTestParameters().withAllRuntimesAndApiLevels().build();
   }
 
-  public FailedStaticizingRegressionTest(TestParameters parameters) {
+  public StaticizerGetterRewritingRegressionTest(TestParameters parameters) {
     this.parameters = parameters;
   }
 
diff --git a/src/test/java/com/android/tools/r8/relocator/RelocatorCommandTest.java b/src/test/java/com/android/tools/r8/relocator/RelocatorCommandTest.java
new file mode 100644
index 0000000..56e1f5d
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/relocator/RelocatorCommandTest.java
@@ -0,0 +1,193 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.relocator;
+
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertThrows;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.ClassFileConsumer.ArchiveConsumer;
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.ProgramResourceProvider;
+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.references.PackageReference;
+import com.android.tools.r8.references.Reference;
+import com.android.tools.r8.utils.ArchiveResourceProvider;
+import java.io.IOException;
+import java.nio.file.Path;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class RelocatorCommandTest extends TestBase {
+
+  private static final PackageReference SOURCE = Reference.packageFromString("foo");
+  private static final PackageReference DESTINATION = Reference.packageFromString("bar");
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withNoneRuntime().build();
+  }
+
+  public RelocatorCommandTest(TestParameters parameters) {}
+
+  @Test
+  public void testCommandBuilder() throws CompilationFailedException, IOException {
+    Path input1 = temp.newFile("in1.jar").toPath();
+    Path input2 = temp.newFile("in2.jar").toPath();
+    Path output = temp.newFile("output.jar").toPath();
+    RelocatorCommand command =
+        RelocatorCommand.builder()
+            .setThreadCount(42)
+            .setOutputPath(output)
+            .addProgramFiles(input1, input2)
+            .addPackageMapping(SOURCE, DESTINATION)
+            .build();
+    assertEquals(42, command.getThreadCount());
+    assertNotNull(command.getMapping());
+    assertEquals(DESTINATION, command.getMapping().get(SOURCE));
+    List<ProgramResourceProvider> programResources = command.getApp().getProgramResourceProviders();
+    assertEquals(2, programResources.size());
+    for (ProgramResourceProvider programResourceProvider : programResources) {
+      assertTrue(programResourceProvider instanceof ArchiveResourceProvider);
+    }
+  }
+
+  @Test
+  public void testPrint() throws CompilationFailedException {
+    RelocatorCommand.Builder builder =
+        RelocatorCommand.builder().setPrintHelp(true).setPrintVersion(true);
+    RelocatorCommand command = builder.build();
+    assertTrue(command.isPrintHelp());
+    assertTrue(command.isPrintVersion());
+    assertNull(command.getApp());
+    assertNull(command.getMapping());
+  }
+
+  @Test
+  public void testParser() {}
+
+  @Test
+  public void testInvalidThreadCount() {
+    CompilationFailedException exception =
+        assertThrows(
+            CompilationFailedException.class,
+            () -> {
+              Path input1 = temp.newFile("in1.jar").toPath();
+              Path input2 = temp.newFile("in2.jar").toPath();
+              Path output = temp.newFile("output.jar").toPath();
+              RelocatorCommand.builder()
+                  .setThreadCount(-2)
+                  .setOutputPath(output)
+                  .addProgramFiles(input1, input2)
+                  .addPackageMapping(SOURCE, DESTINATION)
+                  .build();
+            });
+    assertThat(exception.getCause().getMessage(), containsString("Invalid threadCount: -2"));
+  }
+
+  @Test
+  public void testUnknownArgument() {
+    CompilationFailedException exception =
+        assertThrows(
+            CompilationFailedException.class,
+            () ->
+                RelocatorCommand.parse(new String[] {"--unknown-argument"}, Origin.unknown())
+                    .build());
+    assertThat(
+        exception.getCause().getMessage(), containsString("Unknown argument: --unknown-argument"));
+  }
+
+  @Test
+  public void testDuplicateOutputPaths() {
+    CompilationFailedException exception =
+        assertThrows(
+            CompilationFailedException.class,
+            () -> {
+              Path input1 = temp.newFile("in.jar").toPath();
+              RelocatorCommand.parse(
+                      new String[] {
+                        "--output",
+                        "first_output",
+                        "--output",
+                        "another_output",
+                        "--map",
+                        "foo->bar",
+                        "--input",
+                        input1.toString()
+                      },
+                      Origin.unknown())
+                  .build();
+            });
+    assertThat(
+        exception.getCause().getMessage(),
+        containsString("Cannot output both to 'first_output' and 'another_output'"));
+  }
+
+  @Test
+  public void testNoOutput() {
+    CompilationFailedException exception =
+        assertThrows(
+            CompilationFailedException.class,
+            () -> {
+              Path input1 = temp.newFile("in.jar").toPath();
+              RelocatorCommand.builder()
+                  .addProgramFile(input1)
+                  .addPackageMapping(SOURCE, DESTINATION)
+                  .build();
+            });
+    assertThat(
+        exception.getCause().getMessage(),
+        containsString("No output path or consumer has been specified"));
+  }
+
+  @Test
+  public void testConsumer() throws IOException, CompilationFailedException {
+    Path input = temp.newFile("input.jar").toPath();
+    Path output = temp.newFile("output.jar").toPath();
+    ArchiveConsumer programConsumer = new ArchiveConsumer(output);
+    RelocatorCommand command =
+        RelocatorCommand.builder()
+            .setThreadCount(42)
+            .addProgramFiles(input)
+            .setConsumer(programConsumer)
+            .addPackageMapping(SOURCE, DESTINATION)
+            .build();
+    assertEquals(command.getConsumer(), programConsumer);
+  }
+
+  @Test
+  public void testInvalidPackage() {
+    IllegalArgumentException exception =
+        assertThrows(
+            IllegalArgumentException.class,
+            () -> {
+              Path input1 = temp.newFile("in1.jar").toPath();
+              RelocatorCommand.parse(
+                      new String[] {
+                        "--output",
+                        "output",
+                        "--map",
+                        "invalid;package-name->bar",
+                        "--input",
+                        input1.toString()
+                      },
+                      Origin.unknown())
+                  .build();
+            });
+    assertThat(
+        exception.getMessage(), containsString("Package name 'invalid;package-name' is not valid"));
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/relocator/RelocatorNoneClassFileTest.java b/src/test/java/com/android/tools/r8/relocator/RelocatorNoneClassFileTest.java
new file mode 100644
index 0000000..deccd90
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/relocator/RelocatorNoneClassFileTest.java
@@ -0,0 +1,83 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.relocator;
+
+import static junit.framework.TestCase.assertEquals;
+import static junit.framework.TestCase.assertNotNull;
+import static junit.framework.TestCase.assertNull;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.ResourceException;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.references.Reference;
+import com.android.tools.r8.utils.ZipUtils;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.file.Files;
+import java.nio.file.OpenOption;
+import java.nio.file.Path;
+import java.nio.file.StandardOpenOption;
+import java.util.Scanner;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipFile;
+import java.util.zip.ZipOutputStream;
+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 RelocatorNoneClassFileTest extends TestBase {
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withNoneRuntime().build();
+  }
+
+  public RelocatorNoneClassFileTest(TestParameters parameters) {}
+
+  @Test
+  public void testRewritingFiles()
+      throws IOException, CompilationFailedException, ResourceException {
+    File testJar = temp.newFile("test.jar");
+    Path testJarPath = testJar.toPath();
+    OpenOption[] options =
+        new OpenOption[] {StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING};
+    try (ZipOutputStream out = new ZipOutputStream(Files.newOutputStream(testJarPath, options))) {
+      ZipUtils.writeToZipStream(
+          out, "foo/bar/kotlin.kotlin_builtins", "foo.bar.BazImpl".getBytes(), ZipEntry.STORED);
+      ZipUtils.writeToZipStream(
+          out, "foo.bar.kotlin.kotlin_builtins", "foo.bar.BazImpl".getBytes(), ZipEntry.STORED);
+      ZipUtils.writeToZipStream(
+          out,
+          "somepackage/foo/bar/kotlin.kotlin_builtins",
+          "foo.bar.BazImpl".getBytes(),
+          ZipEntry.STORED);
+    }
+    Path relocatedJar = temp.newFile("out.jar").toPath();
+    Relocator.run(
+        RelocatorCommand.builder()
+            .addProgramFile(testJarPath)
+            .setOutputPath(relocatedJar)
+            .addPackageMapping(
+                Reference.packageFromString("foo.bar"), Reference.packageFromString("baz.qux"))
+            .build());
+    ZipFile zip = new ZipFile(relocatedJar.toFile());
+    // We should have relocated foo/bar/kotlin.kotlin_builtins to baz/qux/kotlin.kotlin_builtins
+    assertNull(zip.getEntry("foo/bar/kotlin.kotlin_builtins"));
+    ZipEntry relocatedEntry = zip.getEntry("baz/qux/kotlin.kotlin_builtins");
+    assertNotNull(relocatedEntry);
+    // We should not change the contents of the files, even if it matches the package.
+    InputStream inputStream = zip.getInputStream(relocatedEntry);
+    Scanner scanner = new Scanner(inputStream);
+    assertEquals("foo.bar.BazImpl", scanner.next());
+    // We should (for now at least) not rewrite files if not in a package.
+    assertNotNull(zip.getEntry("foo.bar.kotlin.kotlin_builtins"));
+    assertNotNull(zip.getEntry("somepackage/foo/bar/kotlin.kotlin_builtins"));
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/relocator/RelocatorServiceLoaderTest.java b/src/test/java/com/android/tools/r8/relocator/RelocatorServiceLoaderTest.java
new file mode 100644
index 0000000..96d429c
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/relocator/RelocatorServiceLoaderTest.java
@@ -0,0 +1,152 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.relocator;
+
+import static junit.framework.TestCase.assertEquals;
+import static junit.framework.TestCase.assertFalse;
+import static junit.framework.TestCase.assertNotNull;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.ResourceException;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.references.Reference;
+import com.android.tools.r8.relocator.foo.bar.Baz;
+import com.android.tools.r8.relocator.foo.bar.BazImpl;
+import com.android.tools.r8.relocator.foo.baz.OtherImpl;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.ZipUtils;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.file.Files;
+import java.nio.file.OpenOption;
+import java.nio.file.Path;
+import java.nio.file.StandardOpenOption;
+import java.util.Scanner;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipFile;
+import java.util.zip.ZipOutputStream;
+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 RelocatorServiceLoaderTest extends TestBase {
+
+  private static final String SERVICE_FILE = "META-INF/services/foo.bar.Baz";
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withNoneRuntime().build();
+  }
+
+  public RelocatorServiceLoaderTest(TestParameters parameters) {}
+
+  @Test
+  public void testNotRewritingServiceForNotFoundClass()
+      throws IOException, CompilationFailedException, ResourceException {
+    File testJar = temp.newFile("test.jar");
+    Path testJarPath = testJar.toPath();
+    OpenOption[] options =
+        new OpenOption[] {StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING};
+    try (ZipOutputStream out = new ZipOutputStream(Files.newOutputStream(testJarPath, options))) {
+      ZipUtils.writeToZipStream(
+          out,
+          SERVICE_FILE,
+          StringUtils.lines("foo.bar.BazImpl", "foo.baz.OtherImpl").getBytes(),
+          ZipEntry.STORED);
+    }
+    ZipFile zip = new ZipFile(testJar);
+    assertNotNull(zip.getEntry(SERVICE_FILE));
+    Path relocatedJar = temp.newFile("out.jar").toPath();
+    Relocator.run(
+        RelocatorCommand.builder()
+            .addProgramFile(testJarPath)
+            .setOutputPath(relocatedJar)
+            .addPackageMapping(
+                Reference.packageFromString("foo.bar"), Reference.packageFromString("baz.qux"))
+            .build());
+    zip = new ZipFile(relocatedJar.toFile());
+    ZipEntry serviceEntry = zip.getEntry(SERVICE_FILE);
+    assertNotNull(serviceEntry);
+  }
+
+  @Test
+  public void testRewritingService()
+      throws IOException, CompilationFailedException, ResourceException {
+    File testJar = temp.newFile("test.jar");
+    Path testJarPath = testJar.toPath();
+    OpenOption[] options =
+        new OpenOption[] {StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING};
+    try (ZipOutputStream out = new ZipOutputStream(Files.newOutputStream(testJarPath, options))) {
+      ZipUtils.writeToZipStream(
+          out,
+          SERVICE_FILE,
+          StringUtils.lines("foo.bar.BazImpl", "foo.baz.OtherImpl").getBytes(),
+          ZipEntry.STORED);
+      ZipUtils.writeToZipStream(out, "foo/bar/Baz.class", Baz.dump(), ZipEntry.STORED);
+      ZipUtils.writeToZipStream(out, "foo/bar/BazImpl.class", BazImpl.dump(), ZipEntry.STORED);
+      ZipUtils.writeToZipStream(out, "foo/baz/OtherImpl.class", OtherImpl.dump(), ZipEntry.STORED);
+    }
+    ZipFile zip = new ZipFile(testJar);
+    assertNotNull(zip.getEntry(SERVICE_FILE));
+    Path relocatedJar = temp.newFile("out.jar").toPath();
+    Relocator.run(
+        RelocatorCommand.builder()
+            .addProgramFile(testJarPath)
+            .setOutputPath(relocatedJar)
+            .addPackageMapping(
+                Reference.packageFromString("foo.bar"), Reference.packageFromString("baz.qux"))
+            .build());
+    zip = new ZipFile(relocatedJar.toFile());
+    ZipEntry serviceEntry = zip.getEntry("META-INF/services/baz.qux.Baz");
+    assertNotNull(serviceEntry);
+    InputStream inputStream = zip.getInputStream(serviceEntry);
+    Scanner scanner = new Scanner(inputStream);
+    assertEquals("baz.qux.BazImpl", scanner.next());
+    assertEquals("foo.baz.OtherImpl", scanner.next());
+    assertFalse(scanner.hasNext());
+  }
+
+  @Test
+  public void testRewritingServiceImpl()
+      throws IOException, CompilationFailedException, ResourceException {
+    File testJar = temp.newFile("test.jar");
+    Path testJarPath = testJar.toPath();
+    OpenOption[] options =
+        new OpenOption[] {StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING};
+    try (ZipOutputStream out = new ZipOutputStream(Files.newOutputStream(testJarPath, options))) {
+      ZipUtils.writeToZipStream(
+          out,
+          SERVICE_FILE,
+          StringUtils.lines("foo.bar.BazImpl", "foo.baz.OtherImpl").getBytes(),
+          ZipEntry.STORED);
+      ZipUtils.writeToZipStream(out, "foo/bar/Baz.class", Baz.dump(), ZipEntry.STORED);
+      ZipUtils.writeToZipStream(out, "foo/bar/BazImpl.class", BazImpl.dump(), ZipEntry.STORED);
+      ZipUtils.writeToZipStream(out, "foo/baz/OtherImpl.class", OtherImpl.dump(), ZipEntry.STORED);
+    }
+    ZipFile zip = new ZipFile(testJar);
+    assertNotNull(zip.getEntry(SERVICE_FILE));
+    Path relocatedJar = temp.newFile("out.jar").toPath();
+    Relocator.run(
+        RelocatorCommand.builder()
+            .addProgramFile(testJarPath)
+            .setOutputPath(relocatedJar)
+            .addPackageMapping(
+                Reference.packageFromString("foo.baz"), Reference.packageFromString("baz.qux"))
+            .build());
+    zip = new ZipFile(relocatedJar.toFile());
+    ZipEntry serviceEntry = zip.getEntry("META-INF/services/foo.bar.Baz");
+    assertNotNull(serviceEntry);
+    InputStream inputStream = zip.getInputStream(serviceEntry);
+    Scanner scanner = new Scanner(inputStream);
+    assertEquals("foo.bar.BazImpl", scanner.next());
+    assertEquals("baz.qux.OtherImpl", scanner.next());
+    assertFalse(scanner.hasNext());
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/relocator/RelocatorTest.java b/src/test/java/com/android/tools/r8/relocator/RelocatorTest.java
new file mode 100644
index 0000000..9690ea6
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/relocator/RelocatorTest.java
@@ -0,0 +1,350 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.relocator;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static junit.framework.TestCase.assertFalse;
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertEquals;
+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.ToolHelper;
+import com.android.tools.r8.ToolHelper.ProcessResult;
+import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.references.Reference;
+import com.android.tools.r8.utils.BooleanUtils;
+import com.android.tools.r8.utils.DescriptorUtils;
+import com.android.tools.r8.utils.Pair;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.FoundClassSubject;
+import com.android.tools.r8.utils.codeinspector.FoundFieldSubject;
+import com.android.tools.r8.utils.codeinspector.FoundMethodSubject;
+import com.android.tools.r8.utils.codeinspector.LocalVariableTable;
+import com.android.tools.r8.utils.codeinspector.LocalVariableTable.LocalVariableTableEntry;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import java.io.IOException;
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ExecutionException;
+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 RelocatorTest extends TestBase {
+
+  private final boolean external;
+
+  @Parameters(name = "{0}, external: {1}")
+  public static List<Object[]> data() {
+    return buildParameters(getTestParameters().withNoneRuntime().build(), BooleanUtils.values());
+  }
+
+  public RelocatorTest(TestParameters parameters, boolean external) {
+    this.external = external;
+  }
+
+  @Test
+  public void testRelocatorIdentity()
+      throws IOException, CompilationFailedException, ExecutionException {
+    Path output = temp.newFile("output.jar").toPath();
+    runRelocator(ToolHelper.R8_WITH_DEPS_JAR, new HashMap<>(), output);
+    inspectAllClassesRelocated(ToolHelper.R8_WITH_DEPS_JAR, output, "", "");
+  }
+
+  @Test
+  public void testRelocatorEmptyToSomething() throws Exception {
+    String originalPrefix = "";
+    String newPrefix = "foo.bar.baz";
+    Path output = temp.newFile("output.jar").toPath();
+    Map<String, String> mapping = new HashMap<>();
+    mapping.put(originalPrefix, newPrefix);
+    runRelocator(ToolHelper.R8_WITH_DEPS_JAR, mapping, output);
+    inspectAllClassesRelocated(
+        ToolHelper.R8_WITH_DEPS_JAR, output, originalPrefix, newPrefix + ".");
+  }
+
+  @Test
+  public void testRelocatorSomethingToEmpty() throws IOException {
+    String originalPrefix = "com.android.tools.r8";
+    String newPrefix = "";
+    Path output = temp.newFile("output.jar").toPath();
+    Map<String, String> mapping = new HashMap<>();
+    mapping.put(originalPrefix, newPrefix);
+    // TODO(b/155047618): Fixup the type after rewriting.
+    CompilationFailedException compilationFailedException =
+        assertThrows(
+            CompilationFailedException.class,
+            () -> {
+              runRelocator(ToolHelper.R8_WITH_DEPS_JAR, mapping, output);
+            });
+  }
+
+  @Test
+  public void testRelocateKeepsDebugInfo()
+      throws IOException, CompilationFailedException, ExecutionException {
+    String originalPrefix = "com.android.tools.r8";
+    String newPrefix = "com.android.tools.r8";
+    Path output = temp.newFile("output.jar").toPath();
+    Map<String, String> mapping = new HashMap<>();
+    mapping.put(originalPrefix, newPrefix);
+    runRelocator(ToolHelper.R8_WITH_DEPS_JAR, mapping, output);
+    // Assert that all classes are the same, have the same methods and debug info:
+    CodeInspector originalInspector = new CodeInspector(ToolHelper.R8_WITH_DEPS_JAR);
+    CodeInspector relocatedInspector = new CodeInspector(output);
+    for (FoundClassSubject clazz : originalInspector.allClasses()) {
+      ClassSubject relocatedClass = relocatedInspector.clazz(clazz.getFinalName());
+      assertThat(relocatedClass, isPresent());
+      assertEquals(
+          clazz.getDexProgramClass().sourceFile, relocatedClass.getDexProgramClass().sourceFile);
+      for (FoundMethodSubject originalMethod : clazz.allMethods()) {
+        MethodSubject relocatedMethod = relocatedClass.method(originalMethod.asMethodReference());
+        assertThat(relocatedMethod, isPresent());
+        assertEquals(originalMethod.hasLineNumberTable(), relocatedMethod.hasLineNumberTable());
+        if (originalMethod.hasLineNumberTable()) {
+          // TODO(b/155303677): Figure out why we cannot assert the same lines.
+          // assertEquals(
+          //     originalMethod.getLineNumberTable().getLines().size(),
+          //     relocatedMethod.getLineNumberTable().getLines().size());
+        }
+        assertEquals(
+            originalMethod.hasLocalVariableTable(), relocatedMethod.hasLocalVariableTable());
+        if (originalMethod.hasLocalVariableTable()) {
+          LocalVariableTable originalVariableTable = originalMethod.getLocalVariableTable();
+          LocalVariableTable relocatedVariableTable = relocatedMethod.getLocalVariableTable();
+          assertEquals(originalVariableTable.size(), relocatedVariableTable.size());
+          for (int i = 0; i < originalVariableTable.getEntries().size(); i++) {
+            LocalVariableTableEntry originalEntry = originalVariableTable.get(i);
+            LocalVariableTableEntry relocatedEntry = relocatedVariableTable.get(i);
+            assertEquals(originalEntry.name, relocatedEntry.name);
+            assertEquals(originalEntry.signature, relocatedEntry.signature);
+            assertEquals(originalEntry.type.toString(), relocatedEntry.type.toString());
+          }
+        }
+      }
+    }
+  }
+
+  @Test
+  public void testRelocateAll() throws IOException, CompilationFailedException, ExecutionException {
+    String originalPrefix = "com.android.tools.r8";
+    String newPrefix = "foo.bar.baz";
+    Map<String, String> mapping = new HashMap<>();
+    mapping.put("some.package.that.does.not.exist", "foo");
+    mapping.put(originalPrefix, newPrefix);
+    Path output = temp.newFile("output.jar").toPath();
+    runRelocator(ToolHelper.R8_WITH_DEPS_JAR, mapping, output);
+    inspectAllClassesRelocated(ToolHelper.R8_WITH_DEPS_JAR, output, originalPrefix, newPrefix);
+  }
+
+  @Test
+  public void testOrderingOfPrefixes() throws Exception {
+    String originalPrefix = "com.android";
+    String newPrefix = "foo.bar.baz";
+    Path output = temp.newFile("output.jar").toPath();
+    Map<String, String> mapping = new LinkedHashMap<>();
+    mapping.put(originalPrefix, newPrefix);
+    mapping.put("com.android.tools.r8", "qux");
+    CompilationFailedException exception =
+        assertThrows(
+            CompilationFailedException.class,
+            () -> runRelocator(ToolHelper.R8_WITH_DEPS_JAR, mapping, output));
+    assertThat(
+        exception.getCause().getMessage(),
+        containsString("can be relocated by multiple mappings."));
+  }
+
+  @Test
+  public void testNoReEntry() throws IOException, CompilationFailedException, ExecutionException {
+    // TODO(b/154909222): Check if this is the behavior we would like.
+    String originalPrefix = "com.android";
+    String newPrefix = "foo.bar.baz";
+    Map<String, String> mapping = new LinkedHashMap<>();
+    mapping.put(originalPrefix, newPrefix);
+    mapping.put(newPrefix, "qux");
+    Path output = temp.newFile("output.jar").toPath();
+    runRelocator(ToolHelper.R8_WITH_DEPS_JAR, mapping, output);
+    inspectAllClassesRelocated(ToolHelper.R8_WITH_DEPS_JAR, output, originalPrefix, newPrefix);
+    // Assert that no mappings of com.android.tools.r8 -> qux exists.
+    CodeInspector inspector = new CodeInspector(output);
+    assertFalse(
+        inspector.allClasses().stream().anyMatch(clazz -> clazz.getFinalName().startsWith("qux")));
+  }
+
+  @Test
+  public void testMultiplePackages()
+      throws IOException, ExecutionException, CompilationFailedException {
+    Set<String> seenPackages = new HashSet<>();
+    List<Pair<String, String>> packageMappings = new ArrayList<>();
+    Map<String, String> mapping = new LinkedHashMap<>();
+    CodeInspector inspector = new CodeInspector(ToolHelper.R8_WITH_DEPS_JAR);
+    int packageNameCounter = 0;
+    // Generate a mapping for each package name directly below com.android.tools.r8.
+    for (FoundClassSubject clazz : inspector.allClasses()) {
+      String packageName = clazz.getDexProgramClass().getType().getPackageName();
+      String prefix = "com.android.tools.r8.";
+      if (!packageName.startsWith(prefix)) {
+        continue;
+      }
+      int nextPackageNameIndex = packageName.indexOf('.', prefix.length());
+      if (nextPackageNameIndex > prefix.length()) {
+        String mappedPackageName =
+            prefix + packageName.substring(prefix.length(), nextPackageNameIndex);
+        if (seenPackages.add(mappedPackageName)) {
+          String relocatedPackageName = "number" + packageNameCounter++;
+          packageMappings.add(new Pair<>(mappedPackageName, relocatedPackageName));
+          mapping.put(mappedPackageName, relocatedPackageName);
+        }
+      }
+    }
+    Path output = temp.newFile("output.jar").toPath();
+    runRelocator(ToolHelper.R8_WITH_DEPS_JAR, mapping, output);
+    for (Pair<String, String> packageMapping : packageMappings) {
+      inspectAllClassesRelocated(
+          ToolHelper.R8_WITH_DEPS_JAR,
+          output,
+          packageMapping.getFirst(),
+          packageMapping.getSecond());
+    }
+  }
+
+  @Test
+  public void testPartialPrefix()
+      throws CompilationFailedException, IOException, ExecutionException {
+    String originalPrefix = "com.android.tools.r";
+    String newPrefix = "i_cannot_w";
+    Map<String, String> mapping = new LinkedHashMap<>();
+    mapping.put(originalPrefix, newPrefix);
+    Path output = temp.newFile("output.jar").toPath();
+    runRelocator(ToolHelper.R8_WITH_DEPS_JAR, mapping, output);
+    inspectAllClassesRelocated(ToolHelper.R8_WITH_DEPS_JAR, output, originalPrefix, originalPrefix);
+  }
+
+  @Test
+  public void testBootstrap() throws IOException, CompilationFailedException, ExecutionException {
+    String originalPrefix = "com.android.tools.r8";
+    String newPrefix = "relocated_r8";
+    Map<String, String> mapping = new LinkedHashMap<>();
+    mapping.put(originalPrefix, newPrefix);
+    Path output = temp.newFile("output.jar").toPath();
+    runRelocator(ToolHelper.R8_WITH_DEPS_JAR, mapping, output);
+    // Check that all classes has been remapped.
+    inspectAllClassesRelocated(ToolHelper.R8_WITH_DEPS_JAR, output, originalPrefix, newPrefix);
+    inspectAllSignaturesNotContainingString(output, originalPrefix);
+    // We should be able to call the relocated relocator.
+    Path bootstrapOutput = temp.newFile("bootstrap.jar").toPath();
+    ProcessResult processResult =
+        ToolHelper.runJava(
+            output,
+            newPrefix + ".SwissArmyKnife",
+            "relocator",
+            "--input",
+            output.toString(),
+            "--output",
+            bootstrapOutput.toString(),
+            "--map",
+            newPrefix + "->" + originalPrefix);
+    System.out.println(processResult.stderr);
+    assertEquals(0, processResult.exitCode);
+    inspectAllClassesRelocated(output, bootstrapOutput, newPrefix, originalPrefix);
+    inspectAllSignaturesNotContainingString(bootstrapOutput, newPrefix);
+    // Assert that this is infact an identity transformation.
+    inspectAllClassesRelocated(ToolHelper.R8_WITH_DEPS_JAR, bootstrapOutput, "", "");
+  }
+
+  @Test
+  public void testNest() throws IOException, CompilationFailedException, ExecutionException {
+    String originalPrefix = "com.android.tools.r8";
+    String newPrefix = "com.android.tools.r8";
+    Path output = temp.newFile("output.jar").toPath();
+    Map<String, String> mapping = new HashMap<>();
+    mapping.put(originalPrefix, newPrefix);
+    runRelocator(ToolHelper.R8_WITH_DEPS_11_JAR, mapping, output);
+    // Assert that all classes are the same, have the same methods and nest info.
+    CodeInspector originalInspector = new CodeInspector(ToolHelper.R8_WITH_DEPS_11_JAR);
+    CodeInspector relocatedInspector = new CodeInspector(output);
+    for (FoundClassSubject originalSubject : originalInspector.allClasses()) {
+      ClassSubject relocatedSubject = relocatedInspector.clazz(originalSubject.getFinalName());
+      assertThat(relocatedSubject, isPresent());
+      DexClass originalClass = originalSubject.getDexProgramClass();
+      DexClass relocatedClass = relocatedSubject.getDexProgramClass();
+      assertEquals(originalClass.isNestHost(), relocatedClass.isNestHost());
+      assertEquals(originalClass.isNestMember(), relocatedClass.isNestMember());
+      if (originalClass.isInANest()) {
+        assertEquals(
+            originalClass.getNestHost().descriptor, relocatedClass.getNestHost().descriptor);
+      }
+    }
+  }
+
+  private void runRelocator(Path input, Map<String, String> mapping, Path output)
+      throws CompilationFailedException {
+    if (external) {
+      List<String> args = new ArrayList<>();
+      args.add("--input");
+      args.add(input.toString());
+      args.add("--output");
+      args.add(output.toString());
+      mapping.forEach(
+          (key, value) -> {
+            args.add("--map");
+            args.add(key + "->" + value);
+          });
+      RelocatorCommandLine.run(args.toArray(new String[0]));
+    } else {
+      RelocatorCommand.Builder builder =
+          RelocatorCommand.builder().addProgramFiles(input).setOutputPath(output);
+      mapping.forEach(
+          (key, value) ->
+              builder.addPackageMapping(
+                  Reference.packageFromString(key), Reference.packageFromString(value)));
+      Relocator.run(builder.build());
+    }
+  }
+
+  private void inspectAllClassesRelocated(
+      Path original, Path relocated, String originalPrefix, String newPrefix)
+      throws IOException, ExecutionException {
+    CodeInspector originalInspector = new CodeInspector(original);
+    CodeInspector relocatedInspector = new CodeInspector(relocated);
+    for (FoundClassSubject clazz : originalInspector.allClasses()) {
+      if (originalPrefix.isEmpty()
+          || clazz
+              .getFinalName()
+              .startsWith(originalPrefix + DescriptorUtils.JAVA_PACKAGE_SEPARATOR)) {
+        String relocatedName = newPrefix + clazz.getFinalName().substring(originalPrefix.length());
+        ClassSubject relocatedClass = relocatedInspector.clazz(relocatedName);
+        assertThat(relocatedClass, isPresent());
+      }
+    }
+  }
+
+  private void inspectAllSignaturesNotContainingString(Path relocated, String originalPrefix)
+      throws IOException, ExecutionException {
+    CodeInspector relocatedInspector = new CodeInspector(relocated);
+    for (FoundClassSubject clazz : relocatedInspector.allClasses()) {
+      assertThat(clazz.getFinalSignatureAttribute(), not(containsString(originalPrefix)));
+      for (FoundMethodSubject method : clazz.allMethods()) {
+        assertThat(method.getJvmMethodSignatureAsString(), not(containsString(originalPrefix)));
+      }
+      for (FoundFieldSubject field : clazz.allFields()) {
+        assertThat(field.getJvmFieldSignatureAsString(), not(containsString(originalPrefix)));
+      }
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/relocator/foo/bar/Baz.java b/src/test/java/com/android/tools/r8/relocator/foo/bar/Baz.java
new file mode 100644
index 0000000..52b573e
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/relocator/foo/bar/Baz.java
@@ -0,0 +1,35 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.relocator.foo.bar;
+
+import static org.objectweb.asm.Opcodes.ACC_ABSTRACT;
+import static org.objectweb.asm.Opcodes.ACC_INTERFACE;
+import static org.objectweb.asm.Opcodes.ACC_PUBLIC;
+import static org.objectweb.asm.Opcodes.V1_8;
+
+import org.objectweb.asm.ClassWriter;
+
+public interface Baz {
+
+  // This is dump of the empty interface Baz.
+  static byte[] dump() {
+
+    ClassWriter classWriter = new ClassWriter(0);
+
+    classWriter.visit(
+        V1_8,
+        ACC_PUBLIC | ACC_ABSTRACT | ACC_INTERFACE,
+        "foo/bar/Baz",
+        null,
+        "java/lang/Object",
+        null);
+
+    classWriter.visitSource("Baz.java", null);
+
+    classWriter.visitEnd();
+
+    return classWriter.toByteArray();
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/relocator/foo/bar/BazImpl.java b/src/test/java/com/android/tools/r8/relocator/foo/bar/BazImpl.java
new file mode 100644
index 0000000..971b280
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/relocator/foo/bar/BazImpl.java
@@ -0,0 +1,55 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.relocator.foo.bar;
+
+import static org.objectweb.asm.Opcodes.ACC_PUBLIC;
+import static org.objectweb.asm.Opcodes.ACC_SUPER;
+import static org.objectweb.asm.Opcodes.ALOAD;
+import static org.objectweb.asm.Opcodes.INVOKESPECIAL;
+import static org.objectweb.asm.Opcodes.RETURN;
+import static org.objectweb.asm.Opcodes.V1_8;
+
+import org.objectweb.asm.ClassWriter;
+import org.objectweb.asm.Label;
+import org.objectweb.asm.MethodVisitor;
+
+public class BazImpl implements Baz {
+
+  // This is a dump of the empty BazImpl class.
+  public static byte[] dump() {
+
+    ClassWriter classWriter = new ClassWriter(0);
+    MethodVisitor methodVisitor;
+
+    classWriter.visit(
+        V1_8,
+        ACC_PUBLIC | ACC_SUPER,
+        "foo/bar/BazImpl",
+        null,
+        "java/lang/Object",
+        new String[] {"foo/bar/Baz"});
+
+    classWriter.visitSource("BazImpl.java", null);
+
+    {
+      methodVisitor = classWriter.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
+      methodVisitor.visitCode();
+      Label label0 = new Label();
+      methodVisitor.visitLabel(label0);
+      methodVisitor.visitLineNumber(7, label0);
+      methodVisitor.visitVarInsn(ALOAD, 0);
+      methodVisitor.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
+      methodVisitor.visitInsn(RETURN);
+      Label label1 = new Label();
+      methodVisitor.visitLabel(label1);
+      methodVisitor.visitLocalVariable("this", "Lfoo/bar/BazImpl;", null, label0, label1, 0);
+      methodVisitor.visitMaxs(1, 1);
+      methodVisitor.visitEnd();
+    }
+    classWriter.visitEnd();
+
+    return classWriter.toByteArray();
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/relocator/foo/baz/OtherImpl.java b/src/test/java/com/android/tools/r8/relocator/foo/baz/OtherImpl.java
new file mode 100644
index 0000000..6242f3c
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/relocator/foo/baz/OtherImpl.java
@@ -0,0 +1,56 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.relocator.foo.baz;
+
+import static org.objectweb.asm.Opcodes.ACC_PUBLIC;
+import static org.objectweb.asm.Opcodes.ACC_SUPER;
+import static org.objectweb.asm.Opcodes.ALOAD;
+import static org.objectweb.asm.Opcodes.INVOKESPECIAL;
+import static org.objectweb.asm.Opcodes.RETURN;
+import static org.objectweb.asm.Opcodes.V1_8;
+
+import com.android.tools.r8.relocator.foo.bar.Baz;
+import org.objectweb.asm.ClassWriter;
+import org.objectweb.asm.Label;
+import org.objectweb.asm.MethodVisitor;
+
+public class OtherImpl implements Baz {
+
+  // This is a dump of the empty OtherImpl class.
+  public static byte[] dump() {
+
+    ClassWriter classWriter = new ClassWriter(0);
+    MethodVisitor methodVisitor;
+
+    classWriter.visit(
+        V1_8,
+        ACC_PUBLIC | ACC_SUPER,
+        "foo/baz/OtherImpl",
+        null,
+        "java/lang/Object",
+        new String[] {"foo/bar/Baz"});
+
+    classWriter.visitSource("OtherImpl.java", null);
+
+    {
+      methodVisitor = classWriter.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
+      methodVisitor.visitCode();
+      Label label0 = new Label();
+      methodVisitor.visitLabel(label0);
+      methodVisitor.visitLineNumber(7, label0);
+      methodVisitor.visitVarInsn(ALOAD, 0);
+      methodVisitor.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
+      methodVisitor.visitInsn(RETURN);
+      Label label1 = new Label();
+      methodVisitor.visitLabel(label1);
+      methodVisitor.visitLocalVariable("this", "Lfoo/baz/OtherImpl;", null, label0, label1, 0);
+      methodVisitor.visitMaxs(1, 1);
+      methodVisitor.visitEnd();
+    }
+    classWriter.visitEnd();
+
+    return classWriter.toByteArray();
+  }
+}
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 079a8f5..02cb64e 100644
--- a/src/test/java/com/android/tools/r8/resolution/ArrayTargetLookupTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/ArrayTargetLookupTest.java
@@ -10,7 +10,7 @@
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.dex.ApplicationReader;
-import com.android.tools.r8.graph.AppInfoWithSubtyping;
+import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexType;
@@ -38,7 +38,7 @@
             .build();
     DirectMappedDexApplication application =
         new ApplicationReader(app, options, timing).read().toDirect();
-    AppInfoWithSubtyping appInfo = new AppInfoWithSubtyping(application);
+    AppInfoWithClassHierarchy appInfo = new AppInfoWithClassHierarchy(application);
     DexItemFactory factory = options.itemFactory;
     DexType fooType =
         factory.createType(DescriptorUtils.javaTypeToDescriptor(Foo.class.getTypeName()));
diff --git a/src/test/java/com/android/tools/r8/resolution/InvokeInterfaceOnClassTest.java b/src/test/java/com/android/tools/r8/resolution/InvokeInterfaceOnClassTest.java
index c9d94ad..84a91ca 100644
--- a/src/test/java/com/android/tools/r8/resolution/InvokeInterfaceOnClassTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/InvokeInterfaceOnClassTest.java
@@ -108,14 +108,16 @@
   private static byte[] transformMain() throws Exception {
     String binaryNameForI = Reference.classFromClass(I.class).getBinaryName();
     return transformer(Main.class)
-        .transformMethodInsnInMethod("main",
+        .transformMethodInsnInMethod(
+            "main",
             (opcode, owner, name, descriptor, isInterface, continuation) -> {
               if (owner.equals(binaryNameForI) && name.equals("f")) {
                 assertEquals(Opcodes.INVOKEVIRTUAL, opcode);
                 assertFalse(isInterface);
-                continuation.apply(Opcodes.INVOKEINTERFACE, owner, name, descriptor, true);
+                continuation.visitMethodInsn(
+                    Opcodes.INVOKEINTERFACE, owner, name, descriptor, true);
               } else {
-                continuation.apply(opcode, owner, name, descriptor, isInterface);
+                continuation.visitMethodInsn(opcode, owner, name, descriptor, isInterface);
               }
             })
         .transform();
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 8fccb56..bade483 100644
--- a/src/test/java/com/android/tools/r8/resolution/InvokeSuperCallInStaticTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/InvokeSuperCallInStaticTest.java
@@ -97,7 +97,7 @@
         .transformMethodInsnInMethod(
             "callSuper",
             (opcode, owner, name, descriptor, isInterface, continuation) -> {
-              continuation.apply(
+              continuation.visitMethodInsn(
                   INVOKESPECIAL,
                   DescriptorUtils.getBinaryNameFromJavaType(Base.class.getTypeName()),
                   name,
diff --git a/src/test/java/com/android/tools/r8/resolution/InvokeVirtualOnInterfaceTest.java b/src/test/java/com/android/tools/r8/resolution/InvokeVirtualOnInterfaceTest.java
index d0a4906..3f2fc61 100644
--- a/src/test/java/com/android/tools/r8/resolution/InvokeVirtualOnInterfaceTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/InvokeVirtualOnInterfaceTest.java
@@ -115,9 +115,9 @@
               if (owner.equals(binaryNameForI) && name.equals("f")) {
                 assertEquals(INVOKEINTERFACE, opcode);
                 assertTrue(isInterface);
-                continuation.apply(INVOKEVIRTUAL, owner, name, descriptor, false);
+                continuation.visitMethodInsn(INVOKEVIRTUAL, owner, name, descriptor, false);
               } else {
-                continuation.apply(opcode, owner, name, descriptor, isInterface);
+                continuation.visitMethodInsn(opcode, owner, name, descriptor, isInterface);
               }
             })
         .transform();
diff --git a/src/test/java/com/android/tools/r8/resolution/PrivateInvokeVirtualTest.java b/src/test/java/com/android/tools/r8/resolution/PrivateInvokeVirtualTest.java
index 543ecad..c0f46b5 100644
--- a/src/test/java/com/android/tools/r8/resolution/PrivateInvokeVirtualTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/PrivateInvokeVirtualTest.java
@@ -85,9 +85,10 @@
             (opcode, owner, name, descriptor, isInterface, continuation) -> {
               if (name.contains("compareToHelper")) {
                 assertEquals(Opcodes.INVOKESPECIAL, opcode);
-                continuation.apply(Opcodes.INVOKEVIRTUAL, owner, name, descriptor, isInterface);
+                continuation.visitMethodInsn(
+                    Opcodes.INVOKEVIRTUAL, owner, name, descriptor, isInterface);
               } else {
-                continuation.apply(opcode, owner, name, descriptor, isInterface);
+                continuation.visitMethodInsn(opcode, owner, name, descriptor, isInterface);
               }
             })
         .transform();
diff --git a/src/test/java/com/android/tools/r8/resolution/access/NestInvokeSpecialInterfaceMethodAccessTest.java b/src/test/java/com/android/tools/r8/resolution/access/NestInvokeSpecialInterfaceMethodAccessTest.java
index 18cbb66..67bf3e3 100644
--- a/src/test/java/com/android/tools/r8/resolution/access/NestInvokeSpecialInterfaceMethodAccessTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/access/NestInvokeSpecialInterfaceMethodAccessTest.java
@@ -23,6 +23,7 @@
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.DescriptorUtils;
+import com.android.tools.r8.utils.OptionalBool;
 import com.android.tools.r8.utils.StringUtils;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.android.tools.r8.utils.codeinspector.MethodSubject;
@@ -85,7 +86,7 @@
                           ? DescriptorUtils.getBinaryNameFromJavaType(I.class.getName())
                           : DescriptorUtils.getBinaryNameFromJavaType(A.class.getName());
                   boolean newIsInterface = symbolicReferenceIsDefiningType;
-                  continuation.apply(
+                  continuation.visitMethodInsn(
                       Opcodes.INVOKESPECIAL, newOwner, name, descriptor, newIsInterface);
                 })
             .transform());
@@ -127,7 +128,9 @@
       return;
     }
 
-    assertEquals(inSameNest, resolutionResult.isAccessibleFrom(callerClassDefinition, appInfo));
+    assertEquals(
+        OptionalBool.of(inSameNest),
+        resolutionResult.isAccessibleFrom(callerClassDefinition, appInfo));
     DexEncodedMethod targetSpecial =
         resolutionResult.lookupInvokeSpecialTarget(callerClassDefinition, appInfo);
     DexEncodedMethod targetSuper =
diff --git a/src/test/java/com/android/tools/r8/resolution/access/NestInvokeSpecialInterfaceMethodAccessWithIntermediateTest.java b/src/test/java/com/android/tools/r8/resolution/access/NestInvokeSpecialInterfaceMethodAccessWithIntermediateTest.java
index bae2756..a25f3c6 100644
--- a/src/test/java/com/android/tools/r8/resolution/access/NestInvokeSpecialInterfaceMethodAccessWithIntermediateTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/access/NestInvokeSpecialInterfaceMethodAccessWithIntermediateTest.java
@@ -83,7 +83,8 @@
                       symbolicReferenceIsDefiningType
                           ? DescriptorUtils.getBinaryNameFromJavaType(I.class.getName())
                           : DescriptorUtils.getBinaryNameFromJavaType(J.class.getName());
-                  continuation.apply(Opcodes.INVOKESPECIAL, newOwner, name, descriptor, true);
+                  continuation.visitMethodInsn(
+                      Opcodes.INVOKESPECIAL, newOwner, name, descriptor, true);
                 })
             .transform());
   }
diff --git a/src/test/java/com/android/tools/r8/resolution/access/NestInvokeSpecialMethodAccessTest.java b/src/test/java/com/android/tools/r8/resolution/access/NestInvokeSpecialMethodAccessTest.java
index 58575d8..026055c 100644
--- a/src/test/java/com/android/tools/r8/resolution/access/NestInvokeSpecialMethodAccessTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/access/NestInvokeSpecialMethodAccessTest.java
@@ -21,6 +21,7 @@
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.transformers.ClassFileTransformer;
 import com.android.tools.r8.utils.BooleanUtils;
+import com.android.tools.r8.utils.OptionalBool;
 import com.android.tools.r8.utils.StringUtils;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.android.tools.r8.utils.codeinspector.MethodSubject;
@@ -101,7 +102,9 @@
         definingClassDefinition, resolutionResult.asSingleResolution().getResolvedHolder());
 
     // Verify that the resolved method is accessible if in the same nest.
-    assertEquals(inSameNest, resolutionResult.isAccessibleFrom(callerClassDefinition, appInfo));
+    assertEquals(
+        OptionalBool.of(inSameNest),
+        resolutionResult.isAccessibleFrom(callerClassDefinition, appInfo));
 
     // Verify that looking up the dispatch target returns the defining method.
     DexEncodedMethod targetSpecial =
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 79e8f76..f1e182a 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
@@ -24,6 +24,7 @@
 import com.android.tools.r8.transformers.ClassFileTransformer;
 import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.DescriptorUtils;
+import com.android.tools.r8.utils.OptionalBool;
 import com.android.tools.r8.utils.StringUtils;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.android.tools.r8.utils.codeinspector.MethodSubject;
@@ -86,7 +87,7 @@
                       symbolicReferenceIsDefiningType
                           ? DescriptorUtils.getBinaryNameFromJavaType(A.class.getName())
                           : DescriptorUtils.getBinaryNameFromJavaType(B.class.getName());
-                  continuation.apply(opcode, newOwner, name, descriptor, isInterface);
+                  continuation.visitMethodInsn(opcode, newOwner, name, descriptor, isInterface);
                 })
             .transform());
   }
@@ -133,7 +134,9 @@
         definingClassDefinition, resolutionResult.asSingleResolution().getResolvedHolder());
 
     // Verify that the resolved method is accessible only when in the same nest.
-    assertEquals(inSameNest, resolutionResult.isAccessibleFrom(callerClassDefinition, appInfo));
+    assertEquals(
+        OptionalBool.of(inSameNest),
+        resolutionResult.isAccessibleFrom(callerClassDefinition, appInfo));
 
     // Verify that looking up the dispatch target returns a valid target
     // iff in the same nest and declaredHolder == definingHolder.
diff --git a/src/test/java/com/android/tools/r8/resolution/access/NestInvokeSpecialMethodPublicAccessWithIntermediateTest.java b/src/test/java/com/android/tools/r8/resolution/access/NestInvokeSpecialMethodPublicAccessWithIntermediateTest.java
index db75b6e..4f013bb 100644
--- a/src/test/java/com/android/tools/r8/resolution/access/NestInvokeSpecialMethodPublicAccessWithIntermediateTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/access/NestInvokeSpecialMethodPublicAccessWithIntermediateTest.java
@@ -20,6 +20,7 @@
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.transformers.ClassFileTransformer;
 import com.android.tools.r8.utils.BooleanUtils;
+import com.android.tools.r8.utils.OptionalBool;
 import com.android.tools.r8.utils.StringUtils;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.android.tools.r8.utils.codeinspector.MethodSubject;
@@ -103,7 +104,8 @@
         definingClassDefinition, resolutionResult.asSingleResolution().getResolvedHolder());
 
     // Verify that the resolved method is accessible (it is public).
-    assertTrue(resolutionResult.isAccessibleFrom(callerClassDefinition, appInfo));
+    assertEquals(
+        OptionalBool.TRUE, resolutionResult.isAccessibleFrom(callerClassDefinition, appInfo));
 
     // Verify that looking up the dispatch target returns the defining method.
     DexEncodedMethod targetSpecial =
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 5c1d7a8..15f7edc 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
@@ -17,6 +17,7 @@
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.transformers.ClassFileTransformer;
 import com.android.tools.r8.utils.BooleanUtils;
+import com.android.tools.r8.utils.OptionalBool;
 import com.android.tools.r8.utils.StringUtils;
 import com.google.common.collect.ImmutableList;
 import java.util.Collection;
@@ -70,7 +71,7 @@
         appInfo.definitionFor(buildType(B.class, appInfo.dexItemFactory())).asProgramClass();
     DexMethod bar = buildMethod(A.class.getDeclaredMethod("bar"), appInfo.dexItemFactory());
     ResolutionResult resolutionResult = appInfo.resolveMethod(bar.holder, bar);
-    assertEquals(inSameNest, resolutionResult.isAccessibleFrom(bClass, appInfo));
+    assertEquals(OptionalBool.of(inSameNest), resolutionResult.isAccessibleFrom(bClass, appInfo));
   }
 
   @Test
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 e3ca436..4041e47 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
@@ -17,6 +17,7 @@
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.transformers.ClassFileTransformer;
 import com.android.tools.r8.utils.BooleanUtils;
+import com.android.tools.r8.utils.OptionalBool;
 import com.android.tools.r8.utils.StringUtils;
 import com.google.common.collect.ImmutableList;
 import java.util.Collection;
@@ -72,7 +73,7 @@
         appInfo.definitionFor(buildType(B.class, appInfo.dexItemFactory())).asProgramClass();
     DexMethod bar = buildMethod(A.class.getDeclaredMethod("bar"), appInfo.dexItemFactory());
     ResolutionResult resolutionResult = appInfo.resolveMethod(bar.holder, bar);
-    assertEquals(inSameNest, resolutionResult.isAccessibleFrom(bClass, appInfo));
+    assertEquals(OptionalBool.of(inSameNest), resolutionResult.isAccessibleFrom(bClass, appInfo));
   }
 
   @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 f1c5fa1..8dc4191 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
@@ -17,6 +17,7 @@
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.transformers.ClassFileTransformer;
 import com.android.tools.r8.utils.BooleanUtils;
+import com.android.tools.r8.utils.OptionalBool;
 import com.android.tools.r8.utils.StringUtils;
 import com.google.common.collect.ImmutableList;
 import java.util.Collection;
@@ -72,7 +73,7 @@
         appInfo.definitionFor(buildType(B.class, appInfo.dexItemFactory())).asProgramClass();
     DexMethod bar = buildMethod(A.class.getDeclaredMethod("bar"), appInfo.dexItemFactory());
     ResolutionResult resolutionResult = appInfo.resolveMethod(bar.holder, bar);
-    assertEquals(inSameNest, resolutionResult.isAccessibleFrom(bClass, appInfo));
+    assertEquals(OptionalBool.of(inSameNest), resolutionResult.isAccessibleFrom(bClass, appInfo));
   }
 
   @Test
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 cdf2b22..d6e6ab9 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
@@ -16,6 +16,7 @@
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.transformers.ClassFileTransformer;
 import com.android.tools.r8.utils.BooleanUtils;
+import com.android.tools.r8.utils.OptionalBool;
 import com.android.tools.r8.utils.StringUtils;
 import com.google.common.collect.ImmutableList;
 import java.util.Collection;
@@ -71,7 +72,7 @@
         appInfo.definitionFor(buildType(B.class, appInfo.dexItemFactory())).asProgramClass();
     DexMethod bar = buildMethod(A.class.getDeclaredMethod("bar"), appInfo.dexItemFactory());
     ResolutionResult resolutionResult = appInfo.resolveMethod(bar.holder, bar);
-    assertEquals(inSameNest, resolutionResult.isAccessibleFrom(bClass, appInfo));
+    assertEquals(OptionalBool.of(inSameNest), resolutionResult.isAccessibleFrom(bClass, appInfo));
   }
 
   @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 f2f3309..020ec02 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
@@ -3,7 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.resolution.access;
 
-import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.assertEquals;
 
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
@@ -13,6 +13,7 @@
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.ResolutionResult;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.utils.OptionalBool;
 import com.android.tools.r8.utils.StringUtils;
 import com.google.common.collect.ImmutableList;
 import java.util.Collection;
@@ -56,7 +57,7 @@
         appInfo.definitionFor(buildType(A.class, appInfo.dexItemFactory())).asProgramClass();
     DexMethod bar = buildMethod(A.class.getDeclaredMethod("bar"), appInfo.dexItemFactory());
     ResolutionResult resolutionResult = appInfo.resolveMethod(bar.holder, bar);
-    assertTrue(resolutionResult.isAccessibleFrom(aClass, appInfo));
+    assertEquals(OptionalBool.TRUE, resolutionResult.isAccessibleFrom(aClass, appInfo));
   }
 
   @Test
diff --git a/src/test/java/com/android/tools/r8/resolution/access/indirectfield/IndirectFieldAccessTest.java b/src/test/java/com/android/tools/r8/resolution/access/indirectfield/IndirectFieldAccessTest.java
index 94b3c45..4616af8 100644
--- a/src/test/java/com/android/tools/r8/resolution/access/indirectfield/IndirectFieldAccessTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/access/indirectfield/IndirectFieldAccessTest.java
@@ -3,7 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.resolution.access.indirectfield;
 
-import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.assertEquals;
 
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
@@ -18,6 +18,7 @@
 import com.android.tools.r8.references.Reference;
 import com.android.tools.r8.resolution.access.indirectfield.pkg.C;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.utils.OptionalBool;
 import com.android.tools.r8.utils.StringUtils;
 import com.google.common.collect.ImmutableList;
 import java.util.List;
@@ -60,7 +61,8 @@
     DexClass initialResolutionHolder = appInfo.definitionFor(f.holder);
     DexEncodedField resolutionTarget = appInfo.resolveField(f);
     // TODO(b/145723539): Test access via the resolution result once possible.
-    assertTrue(
+    assertEquals(
+        OptionalBool.TRUE,
         AccessControl.isFieldAccessible(
             resolutionTarget, initialResolutionHolder, cClass, 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 34af60e..bdd8484 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
@@ -3,7 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.resolution.access.indirectmethod;
 
-import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.assertEquals;
 
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
@@ -15,6 +15,7 @@
 import com.android.tools.r8.graph.ResolutionResult;
 import com.android.tools.r8.resolution.access.indirectmethod.pkg.C;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.utils.OptionalBool;
 import com.android.tools.r8.utils.StringUtils;
 import com.google.common.collect.ImmutableList;
 import java.io.IOException;
@@ -61,7 +62,8 @@
         appInfo.definitionFor(buildType(C.class, appInfo.dexItemFactory())).asProgramClass();
     DexMethod bar = buildMethod(B.class.getMethod("foo"), appInfo.dexItemFactory());
     ResolutionResult resolutionResult = appInfo.resolveMethod(bar.holder, bar);
-    assertTrue(resolutionResult.isAccessibleForVirtualDispatchFrom(cClass, appInfo));
+    assertEquals(
+        OptionalBool.TRUE, resolutionResult.isAccessibleForVirtualDispatchFrom(cClass, appInfo));
   }
 
   @Test
diff --git a/src/test/java/com/android/tools/r8/resolution/interfacetargets/DefaultMethodAsOverrideWithLambdaTest.java b/src/test/java/com/android/tools/r8/resolution/interfacetargets/DefaultMethodAsOverrideWithLambdaTest.java
index 1e9ca71..440e077 100644
--- a/src/test/java/com/android/tools/r8/resolution/interfacetargets/DefaultMethodAsOverrideWithLambdaTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/interfacetargets/DefaultMethodAsOverrideWithLambdaTest.java
@@ -64,7 +64,7 @@
     assertTrue(lookupResult.isLookupResultSuccess());
     Set<String> targets = new HashSet<>();
     lookupResult.forEach(
-        target -> targets.add(target.getMethod().qualifiedName()), lambda -> fail());
+        target -> targets.add(target.getDefinition().qualifiedName()), lambda -> fail());
     ImmutableSet<String> expected =
         ImmutableSet.of(
             A.class.getTypeName() + ".bar",
diff --git a/src/test/java/com/android/tools/r8/resolution/interfacetargets/DefaultMethodLambdaTest.java b/src/test/java/com/android/tools/r8/resolution/interfacetargets/DefaultMethodLambdaTest.java
index ccb80a5..edbb580 100644
--- a/src/test/java/com/android/tools/r8/resolution/interfacetargets/DefaultMethodLambdaTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/interfacetargets/DefaultMethodLambdaTest.java
@@ -62,7 +62,7 @@
     assertTrue(lookupResult.isLookupResultSuccess());
     Set<String> targets = new HashSet<>();
     lookupResult.forEach(
-        target -> targets.add(target.getMethod().qualifiedName()), lambda -> fail());
+        target -> targets.add(target.getDefinition().qualifiedName()), lambda -> fail());
     ImmutableSet<String> expected =
         ImmutableSet.of(A.class.getTypeName() + ".bar", I.class.getTypeName() + ".bar");
     assertEquals(expected, targets);
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 b3aae9e..e59009e 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
@@ -66,7 +66,7 @@
     assertTrue(lookupResult.isLookupResultSuccess());
     Set<String> targets = new HashSet<>();
     lookupResult.forEach(
-        target -> targets.add(target.getMethod().qualifiedName()), lambda -> fail());
+        target -> targets.add(target.getDefinition().qualifiedName()), lambda -> fail());
     ImmutableSet<String> expected = ImmutableSet.of(J.class.getTypeName() + ".foo");
     assertEquals(expected, targets);
   }
@@ -109,7 +109,7 @@
     assertTrue(lookupResult.isLookupResultSuccess());
     Set<String> targets = new HashSet<>();
     lookupResult.forEach(
-        target -> targets.add(target.getMethod().qualifiedName()), lambda -> fail());
+        target -> targets.add(target.getDefinition().qualifiedName()), lambda -> fail());
     ImmutableSet<String> expected = ImmutableSet.of(J.class.getTypeName() + ".foo");
     assertEquals(expected, targets);
   }
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 7c50e54..cb44081 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
@@ -64,7 +64,7 @@
     assertTrue(lookupResult.isLookupResultSuccess());
     Set<String> targets = new HashSet<>();
     lookupResult.forEach(
-        target -> targets.add(target.getMethod().qualifiedName()), lambda -> fail());
+        target -> targets.add(target.getDefinition().qualifiedName()), lambda -> fail());
     ImmutableSet<String> expected = ImmutableSet.of(J.class.getTypeName() + ".foo");
     assertEquals(expected, targets);
   }
diff --git a/src/test/java/com/android/tools/r8/resolution/interfacetargets/InvokeInterfaceClInitTest.java b/src/test/java/com/android/tools/r8/resolution/interfacetargets/InvokeInterfaceClInitTest.java
index 06f7e8d..9a17065 100644
--- a/src/test/java/com/android/tools/r8/resolution/interfacetargets/InvokeInterfaceClInitTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/interfacetargets/InvokeInterfaceClInitTest.java
@@ -132,7 +132,7 @@
         .transformMethodInsnInMethod(
             "callClInit",
             (opcode, owner, name, descriptor, isInterface, continuation) ->
-                continuation.apply(opcode, owner, "<clinit>", descriptor, isInterface))
+                continuation.visitMethodInsn(opcode, owner, "<clinit>", descriptor, isInterface))
         .transform();
   }
 
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 c8fad67..81ed494 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
@@ -94,14 +94,14 @@
             "callFooBar",
             (opcode, owner, name, descriptor, isInterface, continuation) -> {
               if (name.equals("notify")) {
-                continuation.apply(
+                continuation.visitMethodInsn(
                     INVOKEINTERFACE,
                     DescriptorUtils.getBinaryNameFromJavaType(I.class.getTypeName()),
                     "bar",
                     descriptor,
                     true);
               } else {
-                continuation.apply(opcode, owner, name, descriptor, isInterface);
+                continuation.visitMethodInsn(opcode, owner, name, descriptor, isInterface);
               }
             })
         .transform();
diff --git a/src/test/java/com/android/tools/r8/resolution/interfacetargets/LambdaMultipleInterfacesTest.java b/src/test/java/com/android/tools/r8/resolution/interfacetargets/LambdaMultipleInterfacesTest.java
index 9cf0cf2..0702c97 100644
--- a/src/test/java/com/android/tools/r8/resolution/interfacetargets/LambdaMultipleInterfacesTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/interfacetargets/LambdaMultipleInterfacesTest.java
@@ -63,7 +63,7 @@
     assertTrue(lookupResult.isLookupResultSuccess());
     Set<String> targets = new HashSet<>();
     lookupResult.forEach(
-        target -> targets.add(target.getMethod().qualifiedName()), lambda -> fail());
+        target -> targets.add(target.getDefinition().qualifiedName()), lambda -> fail());
     ImmutableSet<String> expected =
         ImmutableSet.of(A.class.getTypeName() + ".bar", J.class.getTypeName() + ".bar");
     assertEquals(expected, targets);
diff --git a/src/test/java/com/android/tools/r8/resolution/interfacetargets/MultipleImplementsTest.java b/src/test/java/com/android/tools/r8/resolution/interfacetargets/MultipleImplementsTest.java
index 0013abb..5ad3690 100644
--- a/src/test/java/com/android/tools/r8/resolution/interfacetargets/MultipleImplementsTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/interfacetargets/MultipleImplementsTest.java
@@ -63,7 +63,7 @@
     assertTrue(lookupResult.isLookupResultSuccess());
     Set<String> targets = new HashSet<>();
     lookupResult.forEach(
-        target -> targets.add(target.getMethod().qualifiedName()), lambda -> fail());
+        target -> targets.add(target.getDefinition().qualifiedName()), lambda -> fail());
     ImmutableSet<String> expected =
         ImmutableSet.of(B.class.getTypeName() + ".foo", C.class.getTypeName() + ".foo");
     assertEquals(expected, targets);
diff --git a/src/test/java/com/android/tools/r8/resolution/interfacetargets/SimpleInterfaceInvokeTest.java b/src/test/java/com/android/tools/r8/resolution/interfacetargets/SimpleInterfaceInvokeTest.java
index fbe03e3..4d195e1 100644
--- a/src/test/java/com/android/tools/r8/resolution/interfacetargets/SimpleInterfaceInvokeTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/interfacetargets/SimpleInterfaceInvokeTest.java
@@ -67,7 +67,7 @@
     lookupResult
         .asLookupResultSuccess()
         .forEach(
-            target -> targets.add(target.getMethod().qualifiedName()),
+            target -> targets.add(target.getDefinition().qualifiedName()),
             lambda -> {
               assert false;
             });
diff --git a/src/test/java/com/android/tools/r8/resolution/interfacetargets/SubInterfaceOverridesTest.java b/src/test/java/com/android/tools/r8/resolution/interfacetargets/SubInterfaceOverridesTest.java
index 4ccdb10..51727c7 100644
--- a/src/test/java/com/android/tools/r8/resolution/interfacetargets/SubInterfaceOverridesTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/interfacetargets/SubInterfaceOverridesTest.java
@@ -67,7 +67,7 @@
     lookupResult
         .asLookupResultSuccess()
         .forEach(
-            target -> targets.add(target.getMethod().qualifiedName()),
+            target -> targets.add(target.getDefinition().qualifiedName()),
             lambda -> {
               fail();
             });
diff --git a/src/test/java/com/android/tools/r8/resolution/interfacetargets/SubTypeMissingOverridesTest.java b/src/test/java/com/android/tools/r8/resolution/interfacetargets/SubTypeMissingOverridesTest.java
index d5e8bb3..9c02be6 100644
--- a/src/test/java/com/android/tools/r8/resolution/interfacetargets/SubTypeMissingOverridesTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/interfacetargets/SubTypeMissingOverridesTest.java
@@ -66,7 +66,7 @@
     lookupResult
         .asLookupResultSuccess()
         .forEach(
-            target -> targets.add(target.getMethod().qualifiedName()),
+            target -> targets.add(target.getDefinition().qualifiedName()),
             lambda -> {
               fail();
             });
diff --git a/src/test/java/com/android/tools/r8/resolution/interfacetargets/SubTypeOverridesTest.java b/src/test/java/com/android/tools/r8/resolution/interfacetargets/SubTypeOverridesTest.java
index 23f1c7b..d5d1ce8 100644
--- a/src/test/java/com/android/tools/r8/resolution/interfacetargets/SubTypeOverridesTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/interfacetargets/SubTypeOverridesTest.java
@@ -66,7 +66,7 @@
     lookupResult
         .asLookupResultSuccess()
         .forEach(
-            target -> targets.add(target.getMethod().qualifiedName()),
+            target -> targets.add(target.getDefinition().qualifiedName()),
             lambda -> {
               fail();
             });
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 379b6e8..f0b55f2 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
@@ -75,7 +75,7 @@
     assertTrue(lookupResult.isLookupResultSuccess());
     Set<String> targets = new HashSet<>();
     lookupResult.forEach(
-        target -> targets.add(target.getMethod().qualifiedName()), lambda -> fail());
+        target -> targets.add(target.getDefinition().qualifiedName()), lambda -> fail());
     ImmutableSet<String> expected = ImmutableSet.of(C.class.getTypeName() + ".foo");
     assertEquals(expected, targets);
   }
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 b5f36aa..01d5afb 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
@@ -63,7 +63,7 @@
     assertTrue(lookupResult.isLookupResultSuccess());
     Set<String> targets = new HashSet<>();
     lookupResult.forEach(
-        target -> targets.add(target.getMethod().qualifiedName()), lambda -> fail());
+        target -> targets.add(target.getDefinition().qualifiedName()), lambda -> fail());
     ImmutableSet<String> expected = ImmutableSet.of(D.class.getTypeName() + ".bar");
     assertEquals(expected, targets);
   }
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 2305c93..37d915a 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
@@ -67,7 +67,7 @@
     assertTrue(lookupResult.isLookupResultSuccess());
     Set<String> targets = new HashSet<>();
     lookupResult.forEach(
-        target -> targets.add(target.getMethod().qualifiedName()), lambda -> fail());
+        target -> targets.add(target.getDefinition().qualifiedName()), lambda -> fail());
     ImmutableSet<String> expected =
         ImmutableSet.of(
             D.class.getTypeName() + ".bar");
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 e156624..e8c34d1 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
@@ -79,7 +79,7 @@
     assertTrue(lookupResult.isLookupResultSuccess());
     Set<String> targets = new HashSet<>();
     lookupResult.forEach(
-        target -> targets.add(target.getMethod().qualifiedName()), lambda -> fail());
+        target -> targets.add(target.getDefinition().qualifiedName()), lambda -> fail());
     // TODO(b/148591377): The set should be empty.
     ImmutableSet<String> expected = ImmutableSet.of(AbstractWidening.class.getTypeName() + ".foo");
     assertEquals(expected, targets);
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 d711ba7..338d00a 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
@@ -78,7 +78,7 @@
     assertTrue(lookupResult.isLookupResultSuccess());
     Set<String> targets = new HashSet<>();
     lookupResult.forEach(
-        target -> targets.add(target.getMethod().qualifiedName()), lambda -> fail());
+        target -> targets.add(target.getDefinition().qualifiedName()), lambda -> fail());
     // TODO(b/148591377): The set should be empty.
     ImmutableSet<String> expected = ImmutableSet.of(Abstract.class.getTypeName() + ".foo");
     assertEquals(expected, targets);
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 ec013d6..e7d36e9 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
@@ -66,7 +66,7 @@
     lookupResult
         .asLookupResultSuccess()
         .forEach(
-            target -> targets.add(target.getMethod().qualifiedName()),
+            target -> targets.add(target.getDefinition().qualifiedName()),
             lambda -> {
               fail();
             });
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 8a35389..c15c379 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
@@ -112,7 +112,7 @@
     lookupResult
         .asLookupResultSuccess()
         .forEach(
-            clazzAndMethod -> actual.add(clazzAndMethod.getMethod().method),
+            clazzAndMethod -> actual.add(clazzAndMethod.getDefinition().method),
             lambdaTarget -> {
               assert false;
             });
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 ebbb52f..750c642 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
@@ -63,7 +63,7 @@
     assertTrue(lookupResult.isLookupResultSuccess());
     Set<String> targets = new HashSet<>();
     lookupResult.forEach(
-        target -> targets.add(target.getMethod().qualifiedName()), lambda -> fail());
+        target -> targets.add(target.getDefinition().qualifiedName()), lambda -> fail());
     ImmutableSet<String> expected =
         ImmutableSet.of(
             A.class.getTypeName() + ".foo",
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 4f572ee..8bc102b 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
@@ -63,7 +63,7 @@
     assertTrue(lookupResult.isLookupResultSuccess());
     Set<String> targets = new HashSet<>();
     lookupResult.forEach(
-        target -> targets.add(target.getMethod().qualifiedName()), lambda -> fail());
+        target -> targets.add(target.getDefinition().qualifiedName()), lambda -> fail());
     ImmutableSet<String> expected = ImmutableSet.of(J.class.getTypeName() + ".foo");
     assertEquals(expected, targets);
   }
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 55d4138..469c0ad 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
@@ -63,7 +63,7 @@
     assertTrue(lookupResult.isLookupResultSuccess());
     Set<String> targets = new HashSet<>();
     lookupResult.forEach(
-        target -> targets.add(target.getMethod().qualifiedName()), lambda -> fail());
+        target -> targets.add(target.getDefinition().qualifiedName()), lambda -> fail());
     ImmutableSet<String> expected = ImmutableSet.of(J.class.getTypeName() + ".foo");
     assertEquals(expected, targets);
   }
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 2800d2c..f025e17 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
@@ -66,7 +66,7 @@
     assertTrue(lookupResult.isLookupResultSuccess());
     Set<String> targets = new HashSet<>();
     lookupResult.forEach(
-        target -> targets.add(target.getMethod().qualifiedName()), lambda -> fail());
+        target -> targets.add(target.getDefinition().qualifiedName()), lambda -> fail());
     ImmutableSet<String> expected = ImmutableSet.of(J.class.getTypeName() + ".foo");
     assertEquals(expected, targets);
   }
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 d909591..d8f8c4a 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
@@ -102,14 +102,14 @@
             "main",
             (opcode, owner, name, descriptor, isInterface, continuation) -> {
               if (name.equals("foo")) {
-                continuation.apply(
+                continuation.visitMethodInsn(
                     opcode,
                     DescriptorUtils.getBinaryNameFromJavaType(A.class.getTypeName()),
                     name,
                     descriptor,
                     isInterface);
               } else {
-                continuation.apply(opcode, owner, name, descriptor, isInterface);
+                continuation.visitMethodInsn(opcode, owner, name, descriptor, isInterface);
               }
             })
         .transform();
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 b2c70f9..472f7a1 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 @@
     assertTrue(lookupResult.isLookupResultSuccess());
     Set<String> targets = new HashSet<>();
     lookupResult.forEach(
-        target -> targets.add(target.getMethod().qualifiedName()), lambda -> fail());
+        target -> targets.add(target.getDefinition().qualifiedName()), lambda -> fail());
     ImmutableSet<String> expected = ImmutableSet.of(I.class.getTypeName() + ".foo");
     assertEquals(expected, targets);
   }
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 b2e57b2..d3c1b7a 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
@@ -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.graph.AppInfoWithSubtyping;
+import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexProgramClass;
@@ -94,7 +94,7 @@
     LookupResultSuccess lookupResultSuccess = lookupResult.asLookupResultSuccess();
     Set<String> targets = new HashSet<>();
     lookupResult.forEach(
-        target -> targets.add(target.getMethod().qualifiedName()), lambda -> fail());
+        target -> targets.add(target.getDefinition().qualifiedName()), lambda -> fail());
     Set<String> expected =
         expectedMethodHolders.stream()
             .map(c -> c.getTypeName() + ".foo")
@@ -227,11 +227,11 @@
     //
     // ----- Program -----
     // B extends A { } <-- initial
-    AppView<AppInfoWithSubtyping> appView =
+    AppView<AppInfoWithClassHierarchy> appView =
         computeAppViewWithSubtyping(
             buildClasses(Collections.singletonList(B.class), Arrays.asList(A.class, I.class))
                 .build());
-    AppInfoWithSubtyping appInfo = appView.appInfo();
+    AppInfoWithClassHierarchy appInfo = appView.appInfo();
     DexMethod method = buildNullaryVoidMethod(B.class, "foo", appInfo.dexItemFactory());
     ResolutionResult resolutionResult = appInfo.resolveMethod(method.holder, method);
     DexType typeA = buildType(A.class, appInfo.dexItemFactory());
@@ -251,7 +251,7 @@
     LookupResultSuccess lookupResultSuccess = lookupResult.asLookupResultSuccess();
     Set<String> targets = new HashSet<>();
     lookupResult.forEach(
-        target -> targets.add(target.getMethod().qualifiedName()), lambda -> fail());
+        target -> targets.add(target.getDefinition().qualifiedName()), lambda -> fail());
     Set<String> expected = ImmutableSet.of(A.class.getTypeName() + ".foo");
     assertEquals(expected, targets);
     assertTrue(lookupResultSuccess.isComplete());
@@ -274,7 +274,7 @@
     LookupResultSuccess lookupResultSuccess = lookupResult.asLookupResultSuccess();
     Set<String> targets = new HashSet<>();
     lookupResult.forEach(
-        target -> targets.add(target.getMethod().qualifiedName()), lambda -> fail());
+        target -> targets.add(target.getDefinition().qualifiedName()), lambda -> fail());
     Set<String> expected = ImmutableSet.of(Unrelated.class.getTypeName() + ".foo");
     assertEquals(expected, targets);
     assertTrue(lookupResultSuccess.isIncomplete());
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 e0f4416..477bed8 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
@@ -68,7 +68,7 @@
     assertTrue(lookupResult.isLookupResultSuccess());
     Set<String> targets = new HashSet<>();
     lookupResult.forEach(
-        target -> targets.add(target.getMethod().qualifiedName()), lambda -> fail());
+        target -> targets.add(target.getDefinition().qualifiedName()), lambda -> fail());
     ImmutableSet<String> expected = ImmutableSet.of(Middle.class.getTypeName() + ".clear");
     assertEquals(expected, targets);
   }
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 03425c6..b9f8c90 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
@@ -76,7 +76,7 @@
     assertTrue(lookupResult.isLookupResultSuccess());
     Set<String> targets = new HashSet<>();
     lookupResult.forEach(
-        target -> targets.add(target.getMethod().qualifiedName()), lambda -> fail());
+        target -> targets.add(target.getDefinition().qualifiedName()), lambda -> fail());
     ImmutableSet<String> expected = ImmutableSet.of(ViewModel.class.getTypeName() + ".clear");
     assertEquals(expected, targets);
   }
@@ -170,7 +170,7 @@
     assertTrue(lookupResult.isLookupResultSuccess());
     Set<String> targets = new HashSet<>();
     lookupResult.forEach(
-        target -> targets.add(target.getMethod().qualifiedName()), lambda -> fail());
+        target -> targets.add(target.getDefinition().qualifiedName()), lambda -> fail());
     ImmutableSet<String> expected = ImmutableSet.of(ViewModel.class.getTypeName() + ".clear");
     assertEquals(expected, targets);
   }
@@ -209,14 +209,14 @@
             "main",
             (opcode, owner, name, descriptor, isInterface, continuation) -> {
               if (name.equals("clear")) {
-                continuation.apply(
+                continuation.visitMethodInsn(
                     opcode,
                     DescriptorUtils.getBinaryNameFromJavaType(ViewModel.class.getTypeName()),
                     name,
                     descriptor,
                     isInterface);
               } else {
-                continuation.apply(opcode, owner, name, descriptor, isInterface);
+                continuation.visitMethodInsn(opcode, owner, name, descriptor, isInterface);
               }
             })
         .transform();
@@ -230,9 +230,9 @@
             "run",
             (opcode, owner, name, descriptor, isInterface, continuation) -> {
               if (name.equals("clearBridge")) {
-                continuation.apply(opcode, owner, "clear", descriptor, isInterface);
+                continuation.visitMethodInsn(opcode, owner, "clear", descriptor, isInterface);
               } else {
-                continuation.apply(opcode, owner, name, descriptor, isInterface);
+                continuation.visitMethodInsn(opcode, owner, name, descriptor, isInterface);
               }
             })
         .transform();
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 645fb55..c4fb6d2 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
@@ -67,7 +67,7 @@
     assertTrue(lookupResult.isLookupResultSuccess());
     Set<String> targets = new HashSet<>();
     lookupResult.forEach(
-        target -> targets.add(target.getMethod().qualifiedName()), lambda -> fail());
+        target -> targets.add(target.getDefinition().qualifiedName()), lambda -> fail());
     ImmutableSet<String> expected = ImmutableSet.of(A.class.getTypeName() + ".bar");
     assertEquals(expected, targets);
   }
@@ -102,11 +102,11 @@
             "callOnB",
             (opcode, owner, name, descriptor, isInterface, continuation) -> {
               if (name.equals("foo")) {
-                continuation.apply(opcode, owner, name, descriptor, isInterface);
+                continuation.visitMethodInsn(opcode, owner, name, descriptor, isInterface);
                 return;
               }
               if (modifyOwner.get()) {
-                continuation.apply(
+                continuation.visitMethodInsn(
                     Opcodes.INVOKEVIRTUAL,
                     DescriptorUtils.getBinaryNameFromJavaType(A.class.getTypeName()),
                     name,
@@ -114,7 +114,8 @@
                     isInterface);
                 modifyOwner.set(false);
               } else {
-                continuation.apply(Opcodes.INVOKEVIRTUAL, owner, name, descriptor, isInterface);
+                continuation.visitMethodInsn(
+                    Opcodes.INVOKEVIRTUAL, owner, name, descriptor, isInterface);
               }
             })
         .transform();
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 628c927..b6f838b 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
@@ -67,7 +67,7 @@
     lookupResult
         .asLookupResultSuccess()
         .forEach(
-            target -> targets.add(target.getMethod().qualifiedName()),
+            target -> targets.add(target.getDefinition().qualifiedName()),
             lambda -> {
               fail();
             });
diff --git a/src/test/java/com/android/tools/r8/rewrite/ServiceLoaderRewritingTest.java b/src/test/java/com/android/tools/r8/rewrite/ServiceLoaderRewritingTest.java
index 4749163..5f46a45 100644
--- a/src/test/java/com/android/tools/r8/rewrite/ServiceLoaderRewritingTest.java
+++ b/src/test/java/com/android/tools/r8/rewrite/ServiceLoaderRewritingTest.java
@@ -168,7 +168,7 @@
 
     // Check that we have removed the service configuration from META-INF/services.
     ZipFile zip = new ZipFile(path.toFile());
-    assertNull(zip.getEntry("META-INF/services"));
+    assertNull(zip.getEntry("META-INF/services/" + Service.class.getTypeName()));
   }
 
   @Test
@@ -197,7 +197,7 @@
 
     // Check that we have removed the service configuration from META-INF/services.
     ZipFile zip = new ZipFile(path.toFile());
-    assertNull(zip.getEntry("META-INF/services"));
+    assertNull(zip.getEntry("META-INF/services/" + Service.class.getTypeName()));
   }
 
   @Test
@@ -226,7 +226,7 @@
 
     // Check that we have removed the service configuration from META-INF/services.
     ZipFile zip = new ZipFile(path.toFile());
-    assertNull(zip.getEntry("META-INF/services"));
+    assertNull(zip.getEntry("META-INF/services/" + Service.class.getTypeName()));
   }
 
   @Test
diff --git a/src/test/java/com/android/tools/r8/shaking/AsterisksTest.java b/src/test/java/com/android/tools/r8/shaking/AsterisksTest.java
index 795ec4d..7261198 100644
--- a/src/test/java/com/android/tools/r8/shaking/AsterisksTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/AsterisksTest.java
@@ -99,7 +99,7 @@
     ClassSubject classSubject = codeInspector.clazz(B111974287.class);
     assertThat(classSubject, isPresent());
     assertThat(classSubject, not(isRenamed()));
-    DexClass clazz = classSubject.getDexClass();
+    DexClass clazz = classSubject.getDexProgramClass();
     assertEquals(3, clazz.virtualMethods().size());
     for (DexEncodedMethod encodedMethod : clazz.virtualMethods()) {
       assertTrue(encodedMethod.method.name.toString().startsWith("foo"));
@@ -140,7 +140,7 @@
     ClassSubject classSubject = codeInspector.clazz(B111974287.class);
     assertThat(classSubject, isPresent());
     assertThat(classSubject, not(isRenamed()));
-    DexClass clazz = classSubject.getDexClass();
+    DexClass clazz = classSubject.getDexProgramClass();
     assertEquals(3, clazz.virtualMethods().size());
     for (DexEncodedMethod encodedMethod : clazz.virtualMethods()) {
       assertTrue(encodedMethod.method.name.toString().startsWith("foo"));
@@ -162,7 +162,7 @@
     ClassSubject classSubject = codeInspector.clazz(B111974287.class);
     assertThat(classSubject, isPresent());
     assertThat(classSubject, not(isRenamed()));
-    DexClass clazz = classSubject.getDexClass();
+    DexClass clazz = classSubject.getDexProgramClass();
     assertEquals(3, clazz.virtualMethods().size());
     for (DexEncodedMethod encodedMethod : clazz.virtualMethods()) {
       assertTrue(encodedMethod.method.name.toString().startsWith("foo"));
diff --git a/src/test/java/com/android/tools/r8/shaking/B149831282.java b/src/test/java/com/android/tools/r8/shaking/B149831282.java
index 910d44b..97441cc 100644
--- a/src/test/java/com/android/tools/r8/shaking/B149831282.java
+++ b/src/test/java/com/android/tools/r8/shaking/B149831282.java
@@ -63,13 +63,14 @@
                     "main",
                     (opcode, type, continuation) -> {
                       assertEquals(binaryName(C.class), type);
-                      continuation.apply(opcode, "b149831282/C");
+                      continuation.visitTypeInsn(opcode, "b149831282/C");
                     })
                 .transformMethodInsnInMethod(
                     "main",
                     (opcode, owner, name, descriptor, isInterface, continuation) -> {
                       assertEquals(binaryName(C.class), owner);
-                      continuation.apply(opcode, "b149831282/C", name, descriptor, isInterface);
+                      continuation.visitMethodInsn(
+                          opcode, "b149831282/C", name, descriptor, isInterface);
                     })
                 .transform())
         .addProgramClassFileData(
diff --git a/src/test/java/com/android/tools/r8/shaking/TreeShakingTest.java b/src/test/java/com/android/tools/r8/shaking/TreeShakingTest.java
index 0b246a5..8365345 100644
--- a/src/test/java/com/android/tools/r8/shaking/TreeShakingTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/TreeShakingTest.java
@@ -135,8 +135,10 @@
   }
 
   protected static void checkSameStructure(CodeInspector ref, CodeInspector inspector) {
-    ref.forAllClasses(refClazz -> checkSameStructure(refClazz,
-        inspector.clazz(refClazz.getDexClass().toSourceString())));
+    ref.forAllClasses(
+        refClazz ->
+            checkSameStructure(
+                refClazz, inspector.clazz(refClazz.getDexProgramClass().toSourceString())));
   }
 
   private static void checkSameStructure(ClassSubject refClazz, ClassSubject clazz) {
@@ -149,7 +151,10 @@
     MethodSignature signature = refMethod.getOriginalSignature();
     // Don't check for existence of class initializers, as the code optimization can remove them.
     if (!refMethod.isClassInitializer()) {
-      Assert.assertTrue("Missing Method: " + clazz.getDexClass().toSourceString() + "."
+      Assert.assertTrue(
+          "Missing Method: "
+              + clazz.getDexProgramClass().toSourceString()
+              + "."
               + signature.toString(),
           clazz.method(signature).isPresent());
     }
diff --git a/src/test/java/com/android/tools/r8/shaking/annotations/PrunedOrMergedAnnotationTest.java b/src/test/java/com/android/tools/r8/shaking/annotations/PrunedOrMergedAnnotationTest.java
index 9b11b86..16c7d4f 100644
--- a/src/test/java/com/android/tools/r8/shaking/annotations/PrunedOrMergedAnnotationTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/annotations/PrunedOrMergedAnnotationTest.java
@@ -59,7 +59,7 @@
         .inspect(
             inspector -> {
               assertThat(inspector.clazz(A.class), not(isPresent()));
-              DexType mergedType = inspector.clazz(B.class).getDexClass().type;
+              DexType mergedType = inspector.clazz(B.class).getDexProgramClass().type;
               ClassSubject classC = inspector.clazz(C.class);
               assertThat(classC, isPresent());
               DexEncodedAnnotation annotation =
diff --git a/src/test/java/com/android/tools/r8/shaking/attributes/KeepAttributesDotsTest.java b/src/test/java/com/android/tools/r8/shaking/attributes/KeepAttributesDotsTest.java
index 5a978c6..1caf4f2 100644
--- a/src/test/java/com/android/tools/r8/shaking/attributes/KeepAttributesDotsTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/attributes/KeepAttributesDotsTest.java
@@ -69,12 +69,14 @@
 
   private void inspect(CodeInspector inspector) {
     ClassSubject clazz = inspector.clazz(Main.class);
-    assertTrue(clazz.getDexClass().annotations().isEmpty());
+    assertTrue(clazz.getDexProgramClass().annotations().isEmpty());
     MethodSubject main = clazz.uniqueMethodWithName("main");
     assertTrue(main.getMethod().annotations().isEmpty());
     FieldSubject field = clazz.uniqueFieldWithName("field");
     assertTrue(field.getField().annotations().isEmpty());
-    assertTrue(clazz.getDexClass().sourceFile == null || clazz.getDexClass().sourceFile.size == 0);
+    assertTrue(
+        clazz.getDexProgramClass().sourceFile == null
+            || clazz.getDexProgramClass().sourceFile.size == 0);
     assertNull(main.getLineNumberTable());
     assertTrue(main.getLocalVariableTable().isEmpty());
   }
diff --git a/src/test/java/com/android/tools/r8/shaking/b134858535/EventPublisherTest.java b/src/test/java/com/android/tools/r8/shaking/b134858535/EventPublisherTest.java
index 4ce41ce..29ab0f2 100644
--- a/src/test/java/com/android/tools/r8/shaking/b134858535/EventPublisherTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/b134858535/EventPublisherTest.java
@@ -4,8 +4,6 @@
 
 package com.android.tools.r8.shaking.b134858535;
 
-import static org.hamcrest.CoreMatchers.containsString;
-
 import com.android.tools.r8.CompilationFailedException;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.utils.AndroidApiLevel;
@@ -22,14 +20,14 @@
 
   @Test
   public void testPrivateMethodsInLambdaClass() throws CompilationFailedException {
+    // TODO(b/155534905): Update expectation.
     testForR8(Backend.DEX)
         .addProgramClasses(Main.class, Interface.class)
         .addProgramClassFileData(EventPublisher$bDump.dump())
         .addKeepClassRules(Interface.class)
         .addKeepMainRule(Main.class)
-        .allowDiagnosticInfoMessages()
         .setMinApi(AndroidApiLevel.L)
-        .compile()
-        .assertAllWarningMessagesMatch(containsString("Unrecognized Kotlin lambda"));
+        .compile();
+    // .assertAllWarningMessagesMatch(containsString("Unrecognized Kotlin lambda"));
   }
 }
diff --git a/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking15Test.java b/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking15Test.java
index 49af47a..2b5cd16 100644
--- a/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking15Test.java
+++ b/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking15Test.java
@@ -55,7 +55,7 @@
       ImmutableList.of("pqr", "vw$", "abc", "def", "stu", "ghi", "jkl", "ea", "xyz_", "mno");
 
   private static void checkClassAndMemberInDictionary(ClassSubject clazz) {
-    String name = clazz.getDexClass().type.getName();
+    String name = clazz.getDexProgramClass().type.getName();
     if (!names.contains(name) && !name.equals("Shaking")) {
       throw new AssertionError();
     }
diff --git a/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAnnotationremovalTest.java b/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAnnotationremovalTest.java
index 52a756b..198b253 100644
--- a/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAnnotationremovalTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAnnotationremovalTest.java
@@ -70,25 +70,25 @@
   private void annotationRemovalHasNoInnerClassAnnotations(CodeInspector inspector) {
     ClassSubject outer = inspector.clazz("annotationremoval.OuterClass");
     Assert.assertTrue(outer.isPresent());
-    Assert.assertTrue(outer.getDexClass().getInnerClasses().isEmpty());
+    Assert.assertTrue(outer.getDexProgramClass().getInnerClasses().isEmpty());
     ClassSubject inner = inspector.clazz("annotationremoval.OuterClass$InnerClass");
     Assert.assertTrue(inner.isPresent());
-    Assert.assertNull(inner.getDexClass().getEnclosingMethod());
-    Assert.assertTrue(inner.getDexClass().getInnerClasses().isEmpty());
+    Assert.assertNull(inner.getDexProgramClass().getEnclosingMethod());
+    Assert.assertTrue(inner.getDexProgramClass().getInnerClasses().isEmpty());
     ClassSubject anonymous = inspector.clazz("annotationremoval.OuterClass$1");
     Assert.assertTrue(anonymous.isPresent());
-    Assert.assertNull(anonymous.getDexClass().getEnclosingMethod());
-    Assert.assertTrue(anonymous.getDexClass().getInnerClasses().isEmpty());
+    Assert.assertNull(anonymous.getDexProgramClass().getEnclosingMethod());
+    Assert.assertTrue(anonymous.getDexProgramClass().getInnerClasses().isEmpty());
     ClassSubject local = inspector.clazz("annotationremoval.OuterClass$1LocalMagic");
     Assert.assertTrue(local.isPresent());
-    Assert.assertNull(local.getDexClass().getEnclosingMethod());
-    Assert.assertTrue(local.getDexClass().getInnerClasses().isEmpty());
+    Assert.assertNull(local.getDexProgramClass().getEnclosingMethod());
+    Assert.assertTrue(local.getDexProgramClass().getInnerClasses().isEmpty());
   }
 
   private void annotationRemovalHasAllInnerClassAnnotations(CodeInspector inspector) {
     ClassSubject outer = inspector.clazz("annotationremoval.OuterClass");
     Assert.assertTrue(outer.isPresent());
-    Assert.assertFalse(outer.getDexClass().getInnerClasses().isEmpty());
+    Assert.assertFalse(outer.getDexProgramClass().getInnerClasses().isEmpty());
     ClassSubject inner = inspector.clazz("annotationremoval.OuterClass$InnerClass");
     Assert.assertTrue(inner.isPresent());
     Assert.assertTrue(inner.isMemberClass());
diff --git a/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/ForceProguardCompatibilityTest.java b/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/ForceProguardCompatibilityTest.java
index ee8629f..91cd90c 100644
--- a/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/ForceProguardCompatibilityTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/ForceProguardCompatibilityTest.java
@@ -119,8 +119,7 @@
 
     // The test contains only a member class so the enclosing-method attribute will be null.
     assertEquals(
-        forceProguardCompatibility,
-        !clazz.getDexClass().getInnerClasses().isEmpty());
+        forceProguardCompatibility, !clazz.getDexProgramClass().getInnerClasses().isEmpty());
     assertEquals(forceProguardCompatibility || keepAnnotations,
         clazz.annotation(annotationClass.getCanonicalName()).isPresent());
   }
@@ -447,9 +446,9 @@
     ClassSubject clazz = inspector.clazz(TestKeepAttributes.class);
     assertThat(clazz, isPresent());
     if (innerClasses || enclosingMethod) {
-      assertFalse(clazz.getDexClass().getInnerClasses().isEmpty());
+      assertFalse(clazz.getDexProgramClass().getInnerClasses().isEmpty());
     } else {
-      assertTrue(clazz.getDexClass().getInnerClasses().isEmpty());
+      assertTrue(clazz.getDexProgramClass().getInnerClasses().isEmpty());
     }
   }
 
diff --git a/src/test/java/com/android/tools/r8/shaking/ifrule/ConsequentRootSetWithSatisfiedDependentItemsTest.java b/src/test/java/com/android/tools/r8/shaking/ifrule/ConsequentRootSetWithSatisfiedDependentItemsTest.java
index f65d00b..040b2d4 100644
--- a/src/test/java/com/android/tools/r8/shaking/ifrule/ConsequentRootSetWithSatisfiedDependentItemsTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/ifrule/ConsequentRootSetWithSatisfiedDependentItemsTest.java
@@ -50,7 +50,7 @@
   private void inspect(CodeInspector inspector) {
     ClassSubject aClassSubject = inspector.clazz(A.class);
     assertThat(aClassSubject, isPresent());
-    assertFalse(aClassSubject.getDexClass().isAbstract());
+    assertFalse(aClassSubject.getDexProgramClass().isAbstract());
   }
 
   static class TestClass {
diff --git a/src/test/java/com/android/tools/r8/shaking/ifrule/verticalclassmerging/ExtendsMergedTypeDirectlyTest.java b/src/test/java/com/android/tools/r8/shaking/ifrule/verticalclassmerging/ExtendsMergedTypeDirectlyTest.java
index caa6f59..b6ab4b4 100644
--- a/src/test/java/com/android/tools/r8/shaking/ifrule/verticalclassmerging/ExtendsMergedTypeDirectlyTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/ifrule/verticalclassmerging/ExtendsMergedTypeDirectlyTest.java
@@ -51,7 +51,8 @@
       // Check that TestClass no longer extends C.
       ClassSubject testClassSubject = inspector.clazz(TestClass.class);
       assertThat(testClassSubject, isPresent());
-      assertEquals("java.lang.Object", testClassSubject.getDexClass().superType.toSourceString());
+      assertEquals(
+          "java.lang.Object", testClassSubject.getDexProgramClass().superType.toSourceString());
 
       // Check that C is no longer present.
       assertThat(inspector.clazz(C.class), not(isPresent()));
diff --git a/src/test/java/com/android/tools/r8/shaking/ifrule/verticalclassmerging/ExtendsMergedTypeIndirectlyTest.java b/src/test/java/com/android/tools/r8/shaking/ifrule/verticalclassmerging/ExtendsMergedTypeIndirectlyTest.java
index 7f6a1c6..ad6678c 100644
--- a/src/test/java/com/android/tools/r8/shaking/ifrule/verticalclassmerging/ExtendsMergedTypeIndirectlyTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/ifrule/verticalclassmerging/ExtendsMergedTypeIndirectlyTest.java
@@ -47,6 +47,7 @@
 
     // Verify that TestClass still inherits from B.
     ClassSubject testClassSubject = inspector.clazz(TestClass.class);
-    assertEquals(B.class.getTypeName(), testClassSubject.getDexClass().superType.toSourceString());
+    assertEquals(
+        B.class.getTypeName(), testClassSubject.getDexProgramClass().superType.toSourceString());
   }
 }
diff --git a/src/test/java/com/android/tools/r8/shaking/ifrule/verticalclassmerging/ImplementsMergedTypeDirectlyTest.java b/src/test/java/com/android/tools/r8/shaking/ifrule/verticalclassmerging/ImplementsMergedTypeDirectlyTest.java
index ec06ebc..5c23b99 100644
--- a/src/test/java/com/android/tools/r8/shaking/ifrule/verticalclassmerging/ImplementsMergedTypeDirectlyTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/ifrule/verticalclassmerging/ImplementsMergedTypeDirectlyTest.java
@@ -62,7 +62,7 @@
       // Check that TestClass no longer implements K.
       ClassSubject testClassSubject = inspector.clazz(TestClass.class);
       assertThat(testClassSubject, isPresent());
-      assertTrue(testClassSubject.getDexClass().interfaces.isEmpty());
+      assertTrue(testClassSubject.getDexProgramClass().interfaces.isEmpty());
 
       // Check that K is no longer present.
       assertThat(inspector.clazz(K.class), not(isPresent()));
diff --git a/src/test/java/com/android/tools/r8/shaking/ifrule/verticalclassmerging/ImplementsMergedTypeIndirectlyTest.java b/src/test/java/com/android/tools/r8/shaking/ifrule/verticalclassmerging/ImplementsMergedTypeIndirectlyTest.java
index 2b76994..2f59834 100644
--- a/src/test/java/com/android/tools/r8/shaking/ifrule/verticalclassmerging/ImplementsMergedTypeIndirectlyTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/ifrule/verticalclassmerging/ImplementsMergedTypeIndirectlyTest.java
@@ -63,6 +63,7 @@
 
     // Verify that TestClass still implements J.
     ClassSubject testClassSubject = inspector.clazz(TestClass.class);
-    assertEquals(J.class.getTypeName(), testClassSubject.getDexClass().interfaces.toSourceString());
+    assertEquals(
+        J.class.getTypeName(), testClassSubject.getDexProgramClass().interfaces.toSourceString());
   }
 }
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 fd4a6a7..7ea419c 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
@@ -102,7 +102,8 @@
       if (enableVerticalClassMerging) {
         // Verify that SuperTestClass has been merged into TestClass.
         assertThat(inspector.clazz(SuperTestClass.class), not(isPresent()));
-        assertEquals("java.lang.Object", testClassSubject.getDexClass().superType.toSourceString());
+        assertEquals(
+            "java.lang.Object", testClassSubject.getDexProgramClass().superType.toSourceString());
 
         // Verify that TestClass.field has been removed.
         assertEquals(1, testClassSubject.allFields().size());
diff --git a/src/test/java/com/android/tools/r8/shaking/ifrule/verticalclassmerging/MergedParameterTypeTest.java b/src/test/java/com/android/tools/r8/shaking/ifrule/verticalclassmerging/MergedParameterTypeTest.java
index 99897fc..3961905 100644
--- a/src/test/java/com/android/tools/r8/shaking/ifrule/verticalclassmerging/MergedParameterTypeTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/ifrule/verticalclassmerging/MergedParameterTypeTest.java
@@ -104,7 +104,8 @@
       if (enableVerticalClassMerging) {
         // Verify that SuperTestClass has been merged into TestClass.
         assertThat(inspector.clazz(SuperTestClass.class), not(isPresent()));
-        assertEquals("java.lang.Object", testClassSubject.getDexClass().superType.toSourceString());
+        assertEquals(
+            "java.lang.Object", testClassSubject.getDexProgramClass().superType.toSourceString());
 
         // Verify that TestClass.method has been removed.
         List<FoundMethodSubject> methods =
diff --git a/src/test/java/com/android/tools/r8/shaking/ifrule/verticalclassmerging/MergedReturnTypeTest.java b/src/test/java/com/android/tools/r8/shaking/ifrule/verticalclassmerging/MergedReturnTypeTest.java
index 47f1c08..9173e66 100644
--- a/src/test/java/com/android/tools/r8/shaking/ifrule/verticalclassmerging/MergedReturnTypeTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/ifrule/verticalclassmerging/MergedReturnTypeTest.java
@@ -120,7 +120,8 @@
       if (enableVerticalClassMerging) {
         // Verify that SuperTestClass has been merged into TestClass.
         assertThat(inspector.clazz(SuperTestClass.class), not(isPresent()));
-        assertEquals("java.lang.Object", testClassSubject.getDexClass().superType.toSourceString());
+        assertEquals(
+            "java.lang.Object", testClassSubject.getDexProgramClass().superType.toSourceString());
 
         // Verify that TestClass.method has been removed.
         List<FoundMethodSubject> methods =
diff --git a/src/test/java/com/android/tools/r8/smali/OutlineTest.java b/src/test/java/com/android/tools/r8/smali/OutlineTest.java
index e736ea7..09c1356 100644
--- a/src/test/java/com/android/tools/r8/smali/OutlineTest.java
+++ b/src/test/java/com/android/tools/r8/smali/OutlineTest.java
@@ -865,13 +865,13 @@
     CodeInspector inspector = new CodeInspector(processedApplication);
     ClassSubject clazz = inspector.clazz(OutlineOptions.CLASS_NAME);
     assertTrue(clazz.isPresent());
-    assertEquals(3, clazz.getDexClass().directMethods().size());
+    assertEquals(3, clazz.getDexProgramClass().directMethods().size());
     // Collect the return types of the putlines for the body of method1 and method2.
     List<DexType> r = new ArrayList<>();
-    for (int i = 0; i < clazz.getDexClass().directMethods().size(); i++) {
-      if (clazz.getDexClass().directMethods().get(i).getCode().asDexCode().instructions[0]
+    for (int i = 0; i < clazz.getDexProgramClass().directMethods().size(); i++) {
+      if (clazz.getDexProgramClass().directMethods().get(i).getCode().asDexCode().instructions[0]
           instanceof InvokeVirtual) {
-        r.add(clazz.getDexClass().directMethods().get(i).method.proto.returnType);
+        r.add(clazz.getDexProgramClass().directMethods().get(i).method.proto.returnType);
       }
     }
     assert r.size() == 2;
diff --git a/src/test/java/com/android/tools/r8/smali/SmaliTestBase.java b/src/test/java/com/android/tools/r8/smali/SmaliTestBase.java
index 6aad725..36c7766 100644
--- a/src/test/java/com/android/tools/r8/smali/SmaliTestBase.java
+++ b/src/test/java/com/android/tools/r8/smali/SmaliTestBase.java
@@ -112,7 +112,7 @@
     CodeInspector inspector = new CodeInspector(application);
     ClassSubject clazz = inspector.clazz(className);
     assertTrue(clazz.isPresent());
-    return clazz.getDexClass();
+    return clazz.getDexProgramClass();
   }
 
   protected DexClass getClass(DexApplication application, MethodSignature signature) {
@@ -124,7 +124,7 @@
       CodeInspector inspector = new CodeInspector(appPath);
       ClassSubject clazz = inspector.clazz(className);
       assertTrue(clazz.isPresent());
-      return clazz.getDexClass();
+      return clazz.getDexProgramClass();
     } catch (IOException | ExecutionException e) {
       throw new RuntimeException(e);
     }
diff --git a/src/test/java/com/android/tools/r8/transformers/ClassFileTransformer.java b/src/test/java/com/android/tools/r8/transformers/ClassFileTransformer.java
index 7b0e410..3f1ad69 100644
--- a/src/test/java/com/android/tools/r8/transformers/ClassFileTransformer.java
+++ b/src/test/java/com/android/tools/r8/transformers/ClassFileTransformer.java
@@ -28,6 +28,7 @@
 import java.util.List;
 import java.util.function.Consumer;
 import java.util.stream.Collectors;
+import org.objectweb.asm.AnnotationVisitor;
 import org.objectweb.asm.ClassReader;
 import org.objectweb.asm.ClassVisitor;
 import org.objectweb.asm.ClassWriter;
@@ -336,6 +337,15 @@
             });
   }
 
+  public ClassFileTransformer setAnnotation() {
+    return setAccessFlags(
+        accessFlags -> {
+          assert accessFlags.isAbstract();
+          assert accessFlags.isInterface();
+          accessFlags.setAnnotation();
+        });
+  }
+
   public ClassFileTransformer setBridge(Method method) {
     return setAccessFlags(method, MethodAccessFlags::setBridge);
   }
@@ -401,6 +411,16 @@
     boolean test(int access, String name, String descriptor, String signature, String[] exceptions);
   }
 
+  public ClassFileTransformer removeInnerClasses() {
+    return addClassTransformer(
+        new ClassTransformer() {
+          @Override
+          public void visitInnerClass(String name, String outerName, String innerName, int access) {
+            // Intentionally empty.
+          }
+        });
+  }
+
   public ClassFileTransformer removeMethods(MethodPredicate predicate) {
     return addClassTransformer(
         new ClassTransformer() {
@@ -414,7 +434,7 @@
         });
   }
 
-  /** Abstraction of the MethodVisitor.visitMethodInsn method with its continuation. */
+  /** Abstraction of the MethodVisitor.visitMethodInsn method with its sub visitor. */
   @FunctionalInterface
   public interface MethodInsnTransform {
     void visitMethodInsn(
@@ -423,40 +443,38 @@
         String name,
         String descriptor,
         boolean isInterface,
-        MethodInsnTransformContinuation continuation);
+        MethodVisitor visitor);
   }
 
-  /** Continuation for transforming a method. Will continue with the super visitor if called. */
-  @FunctionalInterface
-  public interface MethodInsnTransformContinuation {
-    void apply(int opcode, String owner, String name, String descriptor, boolean isInterface);
-  }
-
-  /** Abstraction of the MethodVisitor.visitTypeInsn method with its continuation. */
+  /** Abstraction of the MethodVisitor.visitTypeInsn method with its sub visitor. */
   @FunctionalInterface
   public interface TypeInsnTransform {
-    void visitTypeInsn(int opcode, String type, TypeInsnTransformContinuation continuation);
+    void visitTypeInsn(int opcode, String type, MethodVisitor visitor);
   }
 
-  /** Continuation for transforming a method. Will continue with the super visitor if called. */
+  /** Abstraction of the MethodVisitor.visitLdcInsn method with its sub visitor. */
   @FunctionalInterface
-  public interface TypeInsnTransformContinuation {
-    void apply(int opcode, String type);
+  public interface LdcInsnTransform {
+    void visitLdcInsn(Object value, MethodVisitor visitor);
   }
 
+  /** Abstraction of the MethodVisitor.visitTryCatchBlock method with its sub visitor. */
   @FunctionalInterface
   public interface TryCatchBlockTransform {
     void visitTryCatchBlock(
-        Label start,
-        Label end,
-        Label handler,
-        String type,
-        TryCatchBlockTransformContinuation continuation);
+        Label start, Label end, Label handler, String type, MethodVisitor visitor);
   }
 
-  @FunctionalInterface
-  public interface TryCatchBlockTransformContinuation {
-    void apply(Label start, Label end, Label handler, String type);
+  public ClassFileTransformer replaceAnnotationDescriptor(
+      String oldDescriptor, String newDescriptor) {
+    return addClassTransformer(
+        new ClassTransformer() {
+          @Override
+          public AnnotationVisitor visitAnnotation(String descriptor, boolean visible) {
+            return super.visitAnnotation(
+                descriptor.equals(oldDescriptor) ? newDescriptor : descriptor, visible);
+          }
+        });
   }
 
   public ClassFileTransformer replaceClassDescriptorInMethodInstructions(
@@ -508,6 +526,23 @@
         });
   }
 
+  @FunctionalInterface
+  private interface VisitMethodInsnCallback {
+    void visitMethodInsn(
+        int opcode, String owner, String name, String descriptor, boolean isInterface);
+  }
+
+  private MethodVisitor redirectVisitMethodInsn(
+      MethodVisitor visitor, VisitMethodInsnCallback callback) {
+    return new MethodVisitor(ASM7, visitor) {
+      @Override
+      public void visitMethodInsn(
+          int opcode, String owner, String name, String descriptor, boolean isInterface) {
+        callback.visitMethodInsn(opcode, owner, name, descriptor, isInterface);
+      }
+    };
+  }
+
   public ClassFileTransformer transformMethodInsnInMethod(
       String methodName, MethodInsnTransform transform) {
     return addMethodTransformer(
@@ -517,7 +552,12 @@
               int opcode, String owner, String name, String descriptor, boolean isInterface) {
             if (getContext().method.getMethodName().equals(methodName)) {
               transform.visitMethodInsn(
-                  opcode, owner, name, descriptor, isInterface, super::visitMethodInsn);
+                  opcode,
+                  owner,
+                  name,
+                  descriptor,
+                  isInterface,
+                  redirectVisitMethodInsn(this, super::visitMethodInsn));
             } else {
               super.visitMethodInsn(opcode, owner, name, descriptor, isInterface);
             }
@@ -525,6 +565,21 @@
         });
   }
 
+  @FunctionalInterface
+  private interface VisitTypeInsnCallback {
+    void visitTypeInsn(int opcode, String type);
+  }
+
+  private MethodVisitor redirectVisitTypeInsn(
+      MethodVisitor visitor, VisitTypeInsnCallback callback) {
+    return new MethodVisitor(ASM7, visitor) {
+      @Override
+      public void visitTypeInsn(int opcode, String type) {
+        callback.visitTypeInsn(opcode, type);
+      }
+    };
+  }
+
   public ClassFileTransformer transformTypeInsnInMethod(
       String methodName, TypeInsnTransform transform) {
     return addMethodTransformer(
@@ -532,7 +587,8 @@
           @Override
           public void visitTypeInsn(int opcode, String type) {
             if (getContext().method.getMethodName().equals(methodName)) {
-              transform.visitTypeInsn(opcode, type, super::visitTypeInsn);
+              transform.visitTypeInsn(
+                  opcode, type, redirectVisitTypeInsn(this, super::visitTypeInsn));
             } else {
               super.visitTypeInsn(opcode, type);
             }
@@ -540,6 +596,21 @@
         });
   }
 
+  @FunctionalInterface
+  private interface VisitTryCatchBlockCallback {
+    void visitTryCatchBlock(Label start, Label end, Label handler, String type);
+  }
+
+  private MethodVisitor redirectVistiTryCatchBlock(
+      MethodVisitor visitor, VisitTryCatchBlockCallback callback) {
+    return new MethodVisitor(ASM7, visitor) {
+      @Override
+      public void visitTryCatchBlock(Label start, Label end, Label handler, String type) {
+        callback.visitTryCatchBlock(start, end, handler, type);
+      }
+    };
+  }
+
   public ClassFileTransformer transformTryCatchBlock(
       String methodName, TryCatchBlockTransform transform) {
     return addMethodTransformer(
@@ -547,7 +618,12 @@
           @Override
           public void visitTryCatchBlock(Label start, Label end, Label handler, String type) {
             if (getContext().method.getMethodName().equals(methodName)) {
-              transform.visitTryCatchBlock(start, end, handler, type, super::visitTryCatchBlock);
+              transform.visitTryCatchBlock(
+                  start,
+                  end,
+                  handler,
+                  type,
+                  redirectVistiTryCatchBlock(this, super::visitTryCatchBlock));
             } else {
               super.visitTryCatchBlock(start, end, handler, type);
             }
@@ -555,16 +631,18 @@
         });
   }
 
-  /** Abstraction of the MethodVisitor.visitLdcInsn method with its continuation. */
   @FunctionalInterface
-  public interface LdcInsnTransform {
-    void visitLdcInsn(Object value, LdcInsnTransformContinuation continuation);
+  private interface VisitLdcInsnCallback {
+    void visitLdcInsn(Object value);
   }
 
-  /** Continuation for transforming a method. Will continue with the super visitor if called. */
-  @FunctionalInterface
-  public interface LdcInsnTransformContinuation {
-    void apply(Object value);
+  private MethodVisitor redirectVisitLdcInsn(MethodVisitor visitor, VisitLdcInsnCallback callback) {
+    return new MethodVisitor(ASM7, visitor) {
+      @Override
+      public void visitLdcInsn(Object value) {
+        callback.visitLdcInsn(value);
+      }
+    };
   }
 
   public ClassFileTransformer transformLdcInsnInMethod(
@@ -574,7 +652,7 @@
           @Override
           public void visitLdcInsn(Object value) {
             if (getContext().method.getMethodName().equals(methodName)) {
-              transform.visitLdcInsn(value, super::visitLdcInsn);
+              transform.visitLdcInsn(value, redirectVisitLdcInsn(this, super::visitLdcInsn));
             } else {
               super.visitLdcInsn(value);
             }
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentClassSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentClassSubject.java
index 8786f30..92e0e10 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentClassSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentClassSubject.java
@@ -5,7 +5,7 @@
 package com.android.tools.r8.utils.codeinspector;
 
 import com.android.tools.r8.errors.Unreachable;
-import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexProgramClass;
 import java.util.List;
 import java.util.function.Consumer;
 import kotlinx.metadata.jvm.KotlinClassMetadata;
@@ -73,7 +73,7 @@
   }
 
   @Override
-  public DexClass getDexClass() {
+  public DexProgramClass getDexProgramClass() {
     return null;
   }
 
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/ClassSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/ClassSubject.java
index c8c6142..a234c36 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/ClassSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/ClassSubject.java
@@ -4,7 +4,7 @@
 
 package com.android.tools.r8.utils.codeinspector;
 
-import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.naming.MemberNaming.MethodSignature;
 import com.android.tools.r8.references.MethodReference;
 import com.android.tools.r8.references.TypeReference;
@@ -156,7 +156,7 @@
     return dump.toString();
   }
 
-  public abstract DexClass getDexClass();
+  public abstract DexProgramClass getDexProgramClass();
 
   public abstract AnnotationSubject annotation(String name);
 
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundClassSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundClassSubject.java
index fc5c38f..00d00ca 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundClassSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundClassSubject.java
@@ -14,6 +14,7 @@
 import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexProto;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.naming.ClassNamingForNameMapper;
@@ -221,8 +222,9 @@
   }
 
   @Override
-  public DexClass getDexClass() {
-    return dexClass;
+  public DexProgramClass getDexProgramClass() {
+    assert dexClass.isProgramClass();
+    return dexClass.asProgramClass();
   }
 
   public ClassSubject getSuperClass() {
@@ -334,7 +336,7 @@
   }
 
   public TypeSubject asTypeSubject() {
-    return new TypeSubject(codeInspector, getDexClass().type);
+    return new TypeSubject(codeInspector, getDexProgramClass().type);
   }
 
   @Override
@@ -348,7 +350,7 @@
             codeInspector.getFactory().kotlin, annotationSubject.getAnnotation());
     assertTrue(metadata instanceof KotlinClassMetadata.Class);
     KotlinClassMetadata.Class kClass = (KotlinClassMetadata.Class) metadata;
-    return new FoundKmClassSubject(codeInspector, getDexClass(), kClass.toKmClass());
+    return new FoundKmClassSubject(codeInspector, getDexProgramClass(), kClass.toKmClass());
   }
 
   @Override
@@ -364,11 +366,11 @@
         || metadata instanceof KotlinClassMetadata.MultiFileClassPart);
     if (metadata instanceof KotlinClassMetadata.FileFacade) {
       KotlinClassMetadata.FileFacade kFile = (KotlinClassMetadata.FileFacade) metadata;
-      return new FoundKmPackageSubject(codeInspector, getDexClass(), kFile.toKmPackage());
+      return new FoundKmPackageSubject(codeInspector, getDexProgramClass(), kFile.toKmPackage());
     } else {
       KotlinClassMetadata.MultiFileClassPart kPart =
           (KotlinClassMetadata.MultiFileClassPart) metadata;
-      return new FoundKmPackageSubject(codeInspector, getDexClass(), kPart.toKmPackage());
+      return new FoundKmPackageSubject(codeInspector, getDexProgramClass(), kPart.toKmPackage());
     }
   }
 
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundKmClassSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundKmClassSubject.java
index a2185b0..51d7152 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundKmClassSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundKmClassSubject.java
@@ -4,6 +4,7 @@
 package com.android.tools.r8.utils.codeinspector;
 
 import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.kotlin.KotlinMetadataWriter;
 import com.android.tools.r8.utils.DescriptorUtils;
 import java.util.List;
 import java.util.Objects;
@@ -127,4 +128,11 @@
   public CodeInspector getCodeInspector() {
     return codeInspector;
   }
+
+  @Override
+  public String toString() {
+    StringBuilder sb = new StringBuilder();
+    KotlinMetadataWriter.appendKmClass("", sb, kmClass);
+    return sb.toString();
+  }
 }
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundKmPackageSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundKmPackageSubject.java
index d2c4fa6..4c1f418 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundKmPackageSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundKmPackageSubject.java
@@ -4,6 +4,7 @@
 package com.android.tools.r8.utils.codeinspector;
 
 import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.kotlin.KotlinMetadataWriter;
 import kotlinx.metadata.KmDeclarationContainer;
 import kotlinx.metadata.KmPackage;
 
@@ -50,4 +51,11 @@
     //   from scratch.
     return false;
   }
+
+  @Override
+  public String toString() {
+    StringBuilder sb = new StringBuilder();
+    KotlinMetadataWriter.appendKmPackage("", sb, kmPackage);
+    return sb.toString();
+  }
 }
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundKmTypeParameterSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundKmTypeParameterSubject.java
index fb5e909..c8057ce 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundKmTypeParameterSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundKmTypeParameterSubject.java
@@ -69,7 +69,7 @@
     }
     for (int i = 0; i < kmTypeParameter.getUpperBounds().size(); i++) {
       if (!KmTypeSubject.areEqual(
-          kmTypeParameter.getUpperBounds().get(i), other.getUpperBounds().get(i))) {
+          kmTypeParameter.getUpperBounds().get(i), other.getUpperBounds().get(i), true)) {
         return false;
       }
     }
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundMethodSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundMethodSubject.java
index fe574e4..782bbf0 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundMethodSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundMethodSubject.java
@@ -229,6 +229,9 @@
   @Override
   public boolean hasLocalVariableTable() {
     Code code = getMethod().getCode();
+    if (code == null) {
+      return false;
+    }
     if (code.isDexCode()) {
       DexCode dexCode = code.asDexCode();
       if (dexCode.getDebugInfo() != null) {
@@ -254,6 +257,9 @@
   @Override
   public LineNumberTable getLineNumberTable() {
     Code code = getMethod().getCode();
+    if (code == null) {
+      return null;
+    }
     if (code.isDexCode()) {
       return getDexLineNumberTable(code.asDexCode());
     }
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/KmTypeProjectionSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/KmTypeProjectionSubject.java
index 76f3782..3ee66c9 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/KmTypeProjectionSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/KmTypeProjectionSubject.java
@@ -57,6 +57,6 @@
     if (one.getVariance() != other.getVariance()) {
       return false;
     }
-    return KmTypeSubject.areEqual(one.getType(), other.getType());
+    return KmTypeSubject.areEqual(one.getType(), other.getType(), true);
   }
 }
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/KmTypeSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/KmTypeSubject.java
index 44a141a..c9975a3 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/KmTypeSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/KmTypeSubject.java
@@ -84,10 +84,17 @@
     if (!(obj instanceof KmTypeSubject)) {
       return false;
     }
-    return areEqual(this.kmType, ((KmTypeSubject) obj).kmType);
+    return areEqual(this.kmType, ((KmTypeSubject) obj).kmType, true);
   }
 
-  public static boolean areEqual(KmType one, KmType other) {
+  public boolean equalUpToAbbreviatedType(KmTypeSubject other) {
+    if (other == null) {
+      return false;
+    }
+    return areEqual(this.kmType, other.kmType, false);
+  }
+
+  public static boolean areEqual(KmType one, KmType other, boolean checkAbbreviatedType) {
     if (one == null && other == null) {
       return true;
     }
@@ -109,10 +116,11 @@
         return false;
       }
     }
-    if (!areEqual(one.getAbbreviatedType(), other.getAbbreviatedType())) {
+    if (checkAbbreviatedType
+        && !areEqual(one.getAbbreviatedType(), other.getAbbreviatedType(), checkAbbreviatedType)) {
       return false;
     }
-    if (!areEqual(one.getOuterType(), other.getOuterType())) {
+    if (!areEqual(one.getOuterType(), other.getOuterType(), checkAbbreviatedType)) {
       return false;
     }
     // TODO(b/152745540): Add equality for flexibleUpperBoundType.
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/TypeSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/TypeSubject.java
index ae59c47..357c777 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/TypeSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/TypeSubject.java
@@ -41,7 +41,7 @@
   }
 
   public boolean is(ClassSubject type) {
-    return dexType == type.getDexClass().type;
+    return dexType == type.getDexProgramClass().type;
   }
 
   @Override
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/analysis/ProtoApplicationStats.java b/src/test/java/com/android/tools/r8/utils/codeinspector/analysis/ProtoApplicationStats.java
index 28075e6..4d4c84b 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/analysis/ProtoApplicationStats.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/analysis/ProtoApplicationStats.java
@@ -147,7 +147,7 @@
   private void computeStats() {
     for (FoundClassSubject classSubject : inspector.allClasses()) {
       DexType originalType = classSubject.getOriginalDexType(dexItemFactory);
-      if (classSubject.getDexClass().isEnum()) {
+      if (classSubject.getDexProgramClass().isEnum()) {
         enumStats.enums.add(originalType);
       }
 
diff --git a/third_party/chrome/chrome_200430.tar.gz.sha1 b/third_party/chrome/chrome_200430.tar.gz.sha1
new file mode 100644
index 0000000..75c0b5a
--- /dev/null
+++ b/third_party/chrome/chrome_200430.tar.gz.sha1
@@ -0,0 +1 @@
+2c2b6b64f0b073effe22a5048b53ef69dd3a56fc
\ No newline at end of file
diff --git a/tools/chrome_data.py b/tools/chrome_data.py
index cf62ce3..81f2fc7 100644
--- a/tools/chrome_data.py
+++ b/tools/chrome_data.py
@@ -5,9 +5,11 @@
 import os
 import utils
 
+ANDROID_N_API = '24'
 BASE = os.path.join(utils.THIRD_PARTY, 'chrome')
 
 V180917_BASE = os.path.join(BASE, 'chrome_180917_ffbaa8')
+V200430_BASE = os.path.join(BASE, 'chrome_200430')
 
 INPUT_JARS = [
     'out/Release/gen/chrome/android/monochrome_public_apk/monochrome_public_apk.jar',
@@ -246,4 +248,12 @@
         'libraries': [os.path.join(V180917_BASE, path) for path in LIBRARIES],
     },
   },
+  '200430': {
+    'deploy' : {
+        'inputs': [os.path.join(V200430_BASE, 'program.jar')],
+        'pgconf': [os.path.join(V200430_BASE, 'proguard.config')],
+        'libraries': [os.path.join(V200430_BASE, 'library.jar')],
+        'min-api': ANDROID_N_API
+    },
+  },
 }
diff --git a/tools/r8_release.py b/tools/r8_release.py
index acc70ed..ba87676 100755
--- a/tools/r8_release.py
+++ b/tools/r8_release.py
@@ -457,8 +457,9 @@
   if not args.use_existing_work_branch:
     clients = subprocess.check_output('g4 myclients', shell=True)
     if ':%s:' % client_name in clients:
-      print ("Remove the existing '%s' client before continuing, " +
-          "or use option --use-existing-work-branch.") % client_name
+      print ("Remove the existing '%s' client before continuing " +
+             "(force delete: 'g4 citc -d -f %s'), " +
+             "or use option --use-existing-work-branch.") % (client_name, client_name)
       sys.exit(1)
 
 
diff --git a/tools/test.py b/tools/test.py
index a75beb4..d6e4983 100755
--- a/tools/test.py
+++ b/tools/test.py
@@ -240,6 +240,8 @@
     gradle_args.append('--no-daemon')
 
   # Build an R8 with dependencies for bootstrapping tests before adding test sources.
+  gradle_args.append('r8WithDeps')
+  gradle_args.append('r8WithDeps11')
   gradle_args.append('r8WithRelocatedDeps')
   gradle_args.append('r8WithRelocatedDeps11')