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