Merge commit '72ba207f11c889ef80da7f23f8848c57d780cc66' into dev-release
diff --git a/build.gradle b/build.gradle
index d9be6a4..041431e 100644
--- a/build.gradle
+++ b/build.gradle
@@ -11,6 +11,12 @@
 import tasks.DownloadDependency
 import tasks.GetJarsFromConfiguration
 import utils.Utils
+import org.gradle.api.tasks.testing.logging.TestExceptionFormat
+import org.gradle.api.tasks.testing.logging.TestLogEvent
+
+import java.nio.charset.StandardCharsets
+import java.nio.file.Files
+import java.nio.file.StandardOpenOption
 
 buildscript {
     repositories {
@@ -1848,43 +1854,47 @@
     outputs.dir r8LibTestPath
 }
 
+def shouldRetrace() {
+    return project.hasProperty('r8lib') || project.hasProperty('r8lib_no_deps')
+}
+
+def retrace(Throwable exception) {
+    def out = new StringBuffer()
+    def err = new StringBuffer()
+    def command = "python tools/retrace.py --quiet"
+    def header = "RETRACED STACKTRACE";
+    if (System.getenv('BUILDBOT_BUILDERNAME') != null
+            && !System.getenv('BUILDBOT_BUILDERNAME').endsWith("_release")) {
+        header += ": (${command} --commit_hash ${System.getenv('BUILDBOT_REVISION')})";
+    }
+    out.append("\n--------------------------------------\n")
+    out.append("${header}\n")
+    out.append("--------------------------------------\n")
+    Process process = command.execute()
+    def processIn = new PrintStream(process.getOut())
+    process.consumeProcessOutput(out, err)
+    exception.printStackTrace(processIn)
+    processIn.flush()
+    processIn.close()
+    def errorDuringRetracing = process.waitFor() != 0
+    if (errorDuringRetracing) {
+        out.append("ERROR DURING RETRACING\n")
+        out.append(err.toString())
+    }
+    if (project.hasProperty('print_obfuscated_stacktraces') || errorDuringRetracing) {
+        out.append("\n\n--------------------------------------\n")
+        out.append("OBFUSCATED STACKTRACE\n")
+        out.append("--------------------------------------\n")
+    }
+    return out.toString()
+}
+
 def printStackTrace(TestResult result) {
     filterStackTraces(result)
-    if (project.hasProperty('r8lib') || project.hasProperty('r8lib_no_deps')) {
-        def out = new StringBuffer()
-        def err = new StringBuffer()
-        def command = "python tools/retrace.py --quiet"
-        def header = "RETRACED STACKTRACE";
-        if (System.getenv('BUILDBOT_BUILDERNAME') != null
-                && !System.getenv('BUILDBOT_BUILDERNAME').endsWith("_release")) {
-            header += ": (${command} --commit_hash ${System.getenv('BUILDBOT_REVISION')})";
-        }
-        out.append("\n--------------------------------------\n")
-        out.append("${header}\n")
-        out.append("--------------------------------------\n")
-        Process process = command.execute()
-        def processIn = new PrintStream(process.getOut())
-        process.consumeProcessOutput(out, err)
-        result.exception.printStackTrace(processIn)
-        processIn.flush()
-        processIn.close()
-        def errorDuringRetracing = process.waitFor() != 0
-        if (errorDuringRetracing) {
-            out.append("ERROR DURING RETRACING\n")
-            out.append(err.toString())
-        }
-        if (project.hasProperty('print_obfuscated_stacktraces') || errorDuringRetracing) {
-            out.append("\n\n--------------------------------------\n")
-            out.append("OBFUSCATED STACKTRACE\n")
-            out.append("--------------------------------------\n")
-        } else {
-          result.exceptions.clear()
-        }
-        def exception = new Exception(out.toString())
+    if (shouldRetrace()) {
+        def exception = new Exception(retrace(result.exception))
         exception.setStackTrace([] as StackTraceElement[])
         result.exceptions.add(0, exception)
-    } else {
-        result.exception.printStackTrace()
     }
 }
 
@@ -1894,6 +1904,7 @@
     }
 }
 
+// It would be nice to do this in a non-destructive way...
 def filterStackTrace(Throwable exception) {
     if (!project.hasProperty('print_full_stacktraces')) {
         def elements = []
@@ -1911,10 +1922,136 @@
     }
 }
 
+def printAllStackTracesToFile(List<Throwable> exceptions, File out) {
+    new PrintStream(new FileOutputStream(out)).withCloseable {printer ->
+        exceptions.forEach { it.printStackTrace(printer) }
+    }
+}
+
+def ensureDir(File dir) {
+    dir.mkdirs()
+    return dir
+}
+
+def getTestReportEntryDir(reportDir, testDesc) {
+    return ensureDir(reportDir.toPath()
+            .resolve(testDesc.getClassName())
+            .resolve(testDesc.getName())
+            .toFile())
+}
+
+def getTestResultEntryOutputFile(reportDir, testDesc, fileName) {
+    def dir = getTestReportEntryDir(reportDir, testDesc).toPath()
+    return dir.resolve(fileName).toFile()
+}
+
+def getGitBranchName() {
+    def out = new StringBuilder()
+    def err = new StringBuilder()
+    def proc = "git rev-parse --abbrev-ref HEAD".execute()
+    proc.waitForProcessOutput(out, err)
+    return out
+}
+
+def setUpNewTestReporting(Test task) {
+    // Hide all test events from the console, they are written to the report.
+    task.testLogging { events = [] }
+
+    def testReportOutput = project.hasProperty('test_report_output')
+            ? file(project.hasProperty('test_report_output'))
+            : file("${buildDir}/testreport")
+    def index = testReportOutput.toPath().resolve("index.html").toFile()
+
+    task.beforeSuite { desc ->
+        if (!desc.parent) {
+            // Start by printing a link to the test report for easy access.
+            println "Test report being written to:"
+            println "file://${index}"
+
+            def branch = getGitBranchName()
+            def title = "${desc} @ ${branch}"
+
+            // TODO(b/186607794): Support resuming testing based on the existing report.
+            delete testReportOutput
+            testReportOutput.mkdirs()
+            index << "<html><head><title>${title}</title></head>"
+            index << "<body><h1>${title}</h1>"
+            index << "<p>Run on: ${new Date()}</p>"
+            index << "<p>Git branch: ${branch}</p>"
+            index << "<p><a href=\"file://${testReportOutput}\">Test directories</a></p>"
+            index << "<h2>Failing tests (reload to refresh)</h2><ul>"
+        }
+    }
+
+    task.afterSuite { desc, result ->
+        if (!desc.parent) {
+            // Update the final test results in the index.
+            index << "</ul>"
+            index << "<h2>Tests finished: ${result.resultType.name()}</h2><ul>"
+            index << "<li>Number of tests: ${result.testCount}"
+            index << "<li>Failing tests: ${result.failedTestCount}"
+            index << "<li>Successful tests: ${result.successfulTestCount}"
+            index << "<li>Skipped tests: ${result.skippedTestCount}"
+            index << "</ul></body></html>"
+        }
+    }
+
+    // Events to stdout/err are appended to the files in the test directories.
+    task.onOutput { desc, event ->
+        getTestResultEntryOutputFile(testReportOutput, desc, event.getDestination().name()) <<
+                event.getMessage()
+    }
+
+    task.afterTest { desc, result ->
+        if (result.getTestCount() != 1) {
+            throw new IllegalStateException("Unexpected test with more than one result: ${desc}")
+        }
+        def testReportPath = getTestReportEntryDir(testReportOutput, desc)
+        // Emit the result type status in a file of the same name: SUCCESS, FAILURE or SKIPPED.
+        getTestResultEntryOutputFile(testReportOutput, desc, result.getResultType().name()) <<
+                result.getResultType().name()
+        // Emit the test time.
+        getTestResultEntryOutputFile(testReportOutput, desc, "time") <<
+                "${result.getEndTime() - result.getStartTime()}"
+        // For failed tests, update the index and emit stack trace information.
+        if (result.resultType == TestResult.ResultType.FAILURE) {
+            def title = "${desc.className}.${desc.name}"
+            index << "<li><a href=\"file://${testReportPath}\">${title}</a></li>"
+            if (!result.exceptions.isEmpty()) {
+                printAllStackTracesToFile(
+                        result.exceptions,
+                        getTestResultEntryOutputFile(
+                                testReportOutput,
+                                desc,
+                                "exceptions-raw.txt"))
+                filterStackTraces(result)
+                printAllStackTracesToFile(
+                        result.exceptions,
+                        getTestResultEntryOutputFile(
+                                testReportOutput,
+                                desc,
+                                "exceptions-filtered.txt"))
+                if (shouldRetrace()) {
+                    def out = getTestResultEntryOutputFile(
+                            testReportOutput,
+                            desc,
+                            "exceptions-retraced.txt")
+                    result.exceptions.forEach { out << retrace(it) }
+                }
+            }
+        }
+    }
+}
+
 def testTimes = [:]
 def numberOfTestTimesToPrint = 40
 
-test {
+test { task ->
+    def newTestingReport = project.hasProperty('testing-report')
+    if (newTestingReport) {
+        setUpNewTestReporting(task)
+    }
+
     if (project.hasProperty('generate_golden_files_to')) {
         systemProperty 'generate_golden_files_to', project.property('generate_golden_files_to')
         assert project.hasProperty('HEAD_sha1')
@@ -1927,13 +2064,11 @@
         systemProperty 'test_git_HEAD_sha1', project.property('HEAD_sha1')
     }
 
-    dependsOn buildLibraryDesugarConversions
-    dependsOn getJarsFromSupportLibs
-    // R8.jar is required for running bootstrap tests.
-    dependsOn r8
-    testLogging.exceptionFormat = 'full'
-    if (project.hasProperty('print_test_stdout')) {
-        testLogging.showStandardStreams = true
+    if (!newTestingReport) {
+        testLogging.exceptionFormat = 'full'
+        if (project.hasProperty('print_test_stdout')) {
+            testLogging.showStandardStreams = true
+        }
     }
     if (project.hasProperty('dex_vm') && project.property('dex_vm') != 'default') {
         println "NOTE: Running with non default vm: " + project.property('dex_vm')
@@ -1958,39 +2093,42 @@
         systemProperty 'desugar_jdk_libs', project.property('desugar_jdk_libs')
     }
 
-    if (project.hasProperty('print_times') || project.hasProperty('one_line_per_test')) {
-        afterTest { desc, result ->
-            def executionTime = (result.endTime - result.startTime) / 1000
-            testTimes["${desc.name} [${desc.className}]"] = executionTime
-        }
-        afterSuite { desc, result ->
-          // parent is null if all tests are done.
-          if (desc.parent == null) {
-            def sortedTimes = testTimes.sort({e1, e2 -> e2.value <=> e1.value})
-            sortedTimes.eachWithIndex{key, value, i ->
-                if (i < numberOfTestTimesToPrint) println "$key: $value"}
-          }
-        }
-    }
-
-    if (project.hasProperty('one_line_per_test')) {
-        beforeTest { desc ->
-            println "Start executing test ${desc.name} [${desc.className}]"
+    if (!newTestingReport) {
+        if (project.hasProperty('print_times') || project.hasProperty('one_line_per_test')) {
+            afterTest { desc, result ->
+                def executionTime = (result.endTime - result.startTime) / 1000
+                testTimes["${desc.name} [${desc.className}]"] = executionTime
+            }
+            afterSuite { desc, result ->
+                // parent is null if all tests are done.
+                if (desc.parent == null) {
+                    def sortedTimes = testTimes.sort({ e1, e2 -> e2.value <=> e1.value })
+                    sortedTimes.eachWithIndex { key, value, i ->
+                        if (i < numberOfTestTimesToPrint) println "$key: $value"
+                    }
+                }
+            }
         }
 
-        afterTest { desc, result ->
-            if (result.resultType == TestResult.ResultType.FAILURE) {
-                printStackTrace(result)
+        if (project.hasProperty('one_line_per_test')) {
+            beforeTest { desc ->
+                println "Start executing test ${desc.name} [${desc.className}]"
             }
-            if (project.hasProperty('update_test_timestamp')) {
-                file(project.getProperty('update_test_timestamp')).text = new Date().getTime()
+
+            afterTest { desc, result ->
+                if (result.resultType == TestResult.ResultType.FAILURE) {
+                    printStackTrace(result)
+                }
+                if (project.hasProperty('update_test_timestamp')) {
+                    file(project.getProperty('update_test_timestamp')).text = new Date().getTime()
+                }
+                println "Done executing test ${desc.name} [${desc.className}] with result: ${result.resultType}"
             }
-            println "Done executing test ${desc.name} [${desc.className}] with result: ${result.resultType}"
-        }
-    } else {
-        afterTest { desc, result ->
-            if (result.resultType == TestResult.ResultType.FAILURE) {
-                printStackTrace(result)
+        } else {
+            afterTest { desc, result ->
+                if (result.resultType == TestResult.ResultType.FAILURE) {
+                    printStackTrace(result)
+                }
             }
         }
     }
diff --git a/src/main/java/com/android/tools/r8/D8.java b/src/main/java/com/android/tools/r8/D8.java
index b1c7df4..81a8f82 100644
--- a/src/main/java/com/android/tools/r8/D8.java
+++ b/src/main/java/com/android/tools/r8/D8.java
@@ -324,7 +324,7 @@
                 appView,
                 marker == null ? null : ImmutableList.copyOf(markers),
                 appView.graphLens(),
-                InitClassLens.getDefault(),
+                InitClassLens.getThrowingInstance(),
                 namingLens,
                 proguardMapSupplier)
             .write(executor);
@@ -394,7 +394,7 @@
             appView,
             null,
             GraphLens.getIdentityLens(),
-            InitClassLens.getDefault(),
+            InitClassLens.getThrowingInstance(),
             desugaringLens,
             null,
             convertedCfFiles)
diff --git a/src/main/java/com/android/tools/r8/DexFileMergerHelper.java b/src/main/java/com/android/tools/r8/DexFileMergerHelper.java
index 4d1c0dc..d5f8787 100644
--- a/src/main/java/com/android/tools/r8/DexFileMergerHelper.java
+++ b/src/main/java/com/android/tools/r8/DexFileMergerHelper.java
@@ -106,7 +106,7 @@
                 appView,
                 markers,
                 GraphLens.getIdentityLens(),
-                InitClassLens.getDefault(),
+                InitClassLens.getThrowingInstance(),
                 NamingLens.getIdentityLens(),
                 null);
         writer.write(executor);
diff --git a/src/main/java/com/android/tools/r8/DexSplitterHelper.java b/src/main/java/com/android/tools/r8/DexSplitterHelper.java
index 7896d57..d71cabc 100644
--- a/src/main/java/com/android/tools/r8/DexSplitterHelper.java
+++ b/src/main/java/com/android/tools/r8/DexSplitterHelper.java
@@ -113,7 +113,7 @@
                   appView,
                   markers,
                   GraphLens.getIdentityLens(),
-                  InitClassLens.getDefault(),
+                  InitClassLens.getThrowingInstance(),
                   NamingLens.getIdentityLens(),
                   null,
                   consumer)
diff --git a/src/main/java/com/android/tools/r8/L8Command.java b/src/main/java/com/android/tools/r8/L8Command.java
index af10925..ec9955d 100644
--- a/src/main/java/com/android/tools/r8/L8Command.java
+++ b/src/main/java/com/android/tools/r8/L8Command.java
@@ -333,7 +333,7 @@
       if (isShrinking()) {
         l8CfConsumer = new InMemoryJarContent();
         R8Command.Builder r8Builder =
-            new CompatProguardCommandBuilder(true, getReporter())
+            R8Command.builder(getReporter())
                 .addProgramResourceProvider((ProgramResourceProvider) l8CfConsumer)
                 .setSynthesizedClassesPrefix(
                     libraryConfiguration.getSynthesizedLibraryClassesPackagePrefix())
diff --git a/src/main/java/com/android/tools/r8/bisect/Bisect.java b/src/main/java/com/android/tools/r8/bisect/Bisect.java
index 6a21762..7406a28 100644
--- a/src/main/java/com/android/tools/r8/bisect/Bisect.java
+++ b/src/main/java/com/android/tools/r8/bisect/Bisect.java
@@ -193,7 +193,7 @@
             AppView.createForD8(AppInfo.createInitialAppInfo(app)),
             null,
             GraphLens.getIdentityLens(),
-            InitClassLens.getDefault(),
+            InitClassLens.getThrowingInstance(),
             NamingLens.getIdentityLens(),
             null);
     writer.write(executor);
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 1db8182..eee2f72 100644
--- a/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java
+++ b/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java
@@ -641,12 +641,10 @@
     }
     // At least one method needs a jumbo string in which case we construct a thread local mapping
     // for all code objects and write the processed results into that map.
+    // TODO(b/181636450): Reconsider the code mapping setup now that synthetics are never duplicated
+    //  in outputs.
     Map<DexEncodedMethod, DexCode> codeMapping = new IdentityHashMap<>();
     for (DexProgramClass clazz : classes) {
-      // TODO(b/181636450): Reconsider the code mapping setup now that synthetics are never
-      //  duplicated in outputs.
-      boolean isSharedSynthetic =
-          appView.getSyntheticItems().getSynthesizingContextTypes(clazz.getType()).size() > 1;
       clazz.forEachMethod(
           method -> {
             DexCode code =
@@ -655,12 +653,10 @@
                     application.dexItemFactory,
                     options.testing.forceJumboStringProcessing);
             codeMapping.put(method, code);
-            if (!isSharedSynthetic) {
-              // If the class is not a shared class the mapping now has ownership of the methods
-              // code object. This ensures freeing of code resources once the map entry is cleared
-              // and also ensures that we don't end up using the incorrect code pointer again later!
-              method.removeCode();
-            }
+            // The mapping now has ownership of the methods code object. This ensures freeing of
+            // code resources once the map entry is cleared and also ensures that we don't end up
+            // using the incorrect code pointer again later!
+            method.removeCode();
           });
     }
     return MethodToCodeObjectMapping.fromMapBacking(codeMapping);
diff --git a/src/main/java/com/android/tools/r8/dex/FileWriter.java b/src/main/java/com/android/tools/r8/dex/FileWriter.java
index 9b7c9da..42bd66b 100644
--- a/src/main/java/com/android/tools/r8/dex/FileWriter.java
+++ b/src/main/java/com/android/tools/r8/dex/FileWriter.java
@@ -653,8 +653,7 @@
     }
   }
 
-  private void writeEncodedMethods(
-      Iterable<DexEncodedMethod> unsortedMethods, boolean isSharedSynthetic) {
+  private void writeEncodedMethods(Iterable<DexEncodedMethod> unsortedMethods) {
     List<DexEncodedMethod> methods = IterableUtils.toNewArrayList(unsortedMethods);
     methods.sort(
         (a, b) ->
@@ -675,7 +674,7 @@
         dest.putUleb128(mixedSectionOffsets.getOffsetFor(code));
         // Writing the methods starts to take up memory so we are going to flush the
         // code objects since they are no longer necessary after this.
-        codeMapping.clearCode(method, isSharedSynthetic);
+        codeMapping.clearCode(method);
       }
     }
   }
@@ -689,10 +688,8 @@
     dest.putUleb128(clazz.getMethodCollection().numberOfVirtualMethods());
     writeEncodedFields(clazz.staticFields());
     writeEncodedFields(clazz.instanceFields());
-    boolean isSharedSynthetic =
-        appInfo.getSyntheticItems().getSynthesizingContextTypes(clazz.getType()).size() > 1;
-    writeEncodedMethods(clazz.directMethods(), isSharedSynthetic);
-    writeEncodedMethods(clazz.virtualMethods(), isSharedSynthetic);
+    writeEncodedMethods(clazz.directMethods());
+    writeEncodedMethods(clazz.virtualMethods());
   }
 
   private void addStaticFieldValues(DexProgramClass clazz) {
diff --git a/src/main/java/com/android/tools/r8/dex/IndexedItemCollection.java b/src/main/java/com/android/tools/r8/dex/IndexedItemCollection.java
index 273bf3b..1de1144 100644
--- a/src/main/java/com/android/tools/r8/dex/IndexedItemCollection.java
+++ b/src/main/java/com/android/tools/r8/dex/IndexedItemCollection.java
@@ -106,7 +106,7 @@
   }
 
   default InitClassLens getInitClassLens() {
-    return InitClassLens.getDefault();
+    return InitClassLens.getThrowingInstance();
   }
 
   default DexString getRenamedName(DexMethod method) {
diff --git a/src/main/java/com/android/tools/r8/dex/MethodToCodeObjectMapping.java b/src/main/java/com/android/tools/r8/dex/MethodToCodeObjectMapping.java
index feeb64b..5541398 100644
--- a/src/main/java/com/android/tools/r8/dex/MethodToCodeObjectMapping.java
+++ b/src/main/java/com/android/tools/r8/dex/MethodToCodeObjectMapping.java
@@ -13,7 +13,7 @@
 
   public abstract DexCode getCode(DexEncodedMethod method);
 
-  public abstract void clearCode(DexEncodedMethod method, boolean isSharedSynthetic);
+  public abstract void clearCode(DexEncodedMethod method);
 
   public abstract boolean verifyCodeObjects(Collection<DexCode> codes);
 
@@ -37,11 +37,8 @@
     }
 
     @Override
-    public void clearCode(DexEncodedMethod method, boolean isSharedSynthetic) {
-      // When using methods directly any shared class needs to maintain its methods as read-only.
-      if (!isSharedSynthetic) {
-        method.removeCode();
-      }
+    public void clearCode(DexEncodedMethod method) {
+      method.removeCode();
     }
 
     @Override
@@ -64,7 +61,7 @@
     }
 
     @Override
-    public void clearCode(DexEncodedMethod method, boolean isSharedSynthetic) {
+    public void clearCode(DexEncodedMethod method) {
       // We can safely clear the thread local pointer to even shared methods.
       codes.put(method, null);
     }
diff --git a/src/main/java/com/android/tools/r8/dex/VirtualFile.java b/src/main/java/com/android/tools/r8/dex/VirtualFile.java
index fa17d7c..072fab7 100644
--- a/src/main/java/com/android/tools/r8/dex/VirtualFile.java
+++ b/src/main/java/com/android/tools/r8/dex/VirtualFile.java
@@ -329,11 +329,10 @@
       Collection<DexProgramClass> synthetics = new ArrayList<>();
       // Assign dedicated virtual files for all program classes.
       for (DexProgramClass clazz : appView.appInfo().classes()) {
-        Collection<DexType> contexts =
-            appView.getSyntheticItems().getSynthesizingContextTypes(clazz.getType());
         // TODO(b/181636450): Simplify this making use of the assumption that synthetics are never
         //  duplicated.
-        if (!combineSyntheticClassesWithPrimaryClass || contexts.isEmpty()) {
+        if (!combineSyntheticClassesWithPrimaryClass
+            || !appView.getSyntheticItems().isSyntheticClass(clazz)) {
           VirtualFile file =
               new VirtualFile(
                   virtualFiles.size(),
@@ -353,13 +352,14 @@
         }
       }
       for (DexProgramClass synthetic : synthetics) {
-        for (DexType context :
-            appView.getSyntheticItems().getSynthesizingContextTypes(synthetic.getType())) {
-          DexProgramClass inputType = appView.definitionForProgramType(context);
-          VirtualFile file = files.get(inputType);
-          file.addClass(synthetic);
-          file.commitTransaction();
-        }
+        Collection<DexType> synthesizingContexts =
+            appView.getSyntheticItems().getSynthesizingContextTypes(synthetic.getType());
+        assert synthesizingContexts.size() == 1;
+        DexProgramClass inputType =
+            appView.definitionForProgramType(synthesizingContexts.iterator().next());
+        VirtualFile file = files.get(inputType);
+        file.addClass(synthetic);
+        file.commitTransaction();
       }
       return virtualFiles;
     }
diff --git a/src/main/java/com/android/tools/r8/diagnostic/internal/MissingDefinitionInfoUtils.java b/src/main/java/com/android/tools/r8/diagnostic/internal/MissingDefinitionInfoUtils.java
index 34f2f2c..224b2a4 100644
--- a/src/main/java/com/android/tools/r8/diagnostic/internal/MissingDefinitionInfoUtils.java
+++ b/src/main/java/com/android/tools/r8/diagnostic/internal/MissingDefinitionInfoUtils.java
@@ -59,7 +59,8 @@
               methodReference, other.asMissingClass().getClassReference());
         }
         if (other.isMissingField()) {
-          MethodReferenceUtils.compare(methodReference, other.asMissingField().getFieldReference());
+          return MethodReferenceUtils.compare(
+              methodReference, other.asMissingField().getFieldReference());
         }
         return MethodReferenceUtils.compare(
             methodReference, other.asMissingMethod().getMethodReference());
@@ -86,16 +87,21 @@
 
   public static void writeDiagnosticMessage(
       StringBuilder builder, MissingDefinitionInfo missingDefinitionInfo) {
-    builder.append("Missing class ");
     MissingDefinitionInfoUtils.accept(
         missingDefinitionInfo,
-        missingClassInfo -> builder.append(missingClassInfo.getClassReference().getTypeName()),
+        missingClassInfo ->
+            builder
+                .append("Missing class ")
+                .append(missingClassInfo.getClassReference().getTypeName()),
         missingFieldInfo ->
-            builder.append(
-                FieldReferenceUtils.toSourceString(missingFieldInfo.getFieldReference())),
+            builder
+                .append("Missing field ")
+                .append(FieldReferenceUtils.toSourceString(missingFieldInfo.getFieldReference())),
         missingMethodInfo ->
-            builder.append(
-                MethodReferenceUtils.toSourceString(missingMethodInfo.getMethodReference())));
+            builder
+                .append("Missing method ")
+                .append(
+                    MethodReferenceUtils.toSourceString(missingMethodInfo.getMethodReference())));
     writeReferencedFromSuffix(builder, missingDefinitionInfo);
   }
 
@@ -119,14 +125,15 @@
                   missingDefinitionMethodContext.getMethodReference(),
                   getMethodReferenceComparator()));
     }
-    assert classContext.isSet() || fieldContext.isSet() || methodContext.isSet();
+    // TODO(b/186506586): Reenable assert once trace references also provide contextual information.
+    // assert classContext.isSet() || fieldContext.isSet() || methodContext.isSet();
     if (fieldContext.isSet()) {
       writeReferencedFromSuffix(
           builder, missingDefinitionInfo, FieldReferenceUtils.toSourceString(fieldContext.get()));
     } else if (methodContext.isSet()) {
       writeReferencedFromSuffix(
           builder, missingDefinitionInfo, MethodReferenceUtils.toSourceString(methodContext.get()));
-    } else {
+    } else if (classContext.isSet()) {
       writeReferencedFromSuffix(builder, missingDefinitionInfo, classContext.get().getTypeName());
     }
   }
diff --git a/src/main/java/com/android/tools/r8/diagnostic/internal/MissingFieldInfoImpl.java b/src/main/java/com/android/tools/r8/diagnostic/internal/MissingFieldInfoImpl.java
new file mode 100644
index 0000000..216080d
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/diagnostic/internal/MissingFieldInfoImpl.java
@@ -0,0 +1,47 @@
+// Copyright (c) 2021, 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.diagnostic.internal;
+
+import com.android.tools.r8.diagnostic.MissingDefinitionContext;
+import com.android.tools.r8.diagnostic.MissingDefinitionInfo;
+import com.android.tools.r8.diagnostic.MissingFieldInfo;
+import com.android.tools.r8.references.FieldReference;
+import java.util.Collection;
+
+public class MissingFieldInfoImpl extends MissingDefinitionInfoBase implements MissingFieldInfo {
+
+  private final FieldReference fieldReference;
+
+  private MissingFieldInfoImpl(
+      FieldReference fieldReference, Collection<MissingDefinitionContext> referencedFromContexts) {
+    super(referencedFromContexts);
+    this.fieldReference = fieldReference;
+  }
+
+  public static Builder builder() {
+    return new Builder();
+  }
+
+  @Override
+  public FieldReference getFieldReference() {
+    return fieldReference;
+  }
+
+  public static class Builder extends MissingDefinitionInfoBase.Builder {
+
+    private FieldReference fieldReference;
+
+    private Builder() {}
+
+    public Builder setField(FieldReference fieldReference) {
+      this.fieldReference = fieldReference;
+      return this;
+    }
+
+    public MissingDefinitionInfo build() {
+      return new MissingFieldInfoImpl(fieldReference, referencedFromContextsBuilder.build());
+    }
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/diagnostic/internal/MissingMethodInfoImpl.java b/src/main/java/com/android/tools/r8/diagnostic/internal/MissingMethodInfoImpl.java
new file mode 100644
index 0000000..5be14dc
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/diagnostic/internal/MissingMethodInfoImpl.java
@@ -0,0 +1,48 @@
+// Copyright (c) 2021, 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.diagnostic.internal;
+
+import com.android.tools.r8.diagnostic.MissingDefinitionContext;
+import com.android.tools.r8.diagnostic.MissingDefinitionInfo;
+import com.android.tools.r8.diagnostic.MissingMethodInfo;
+import com.android.tools.r8.references.MethodReference;
+import java.util.Collection;
+
+public class MissingMethodInfoImpl extends MissingDefinitionInfoBase implements MissingMethodInfo {
+
+  private final MethodReference methodReference;
+
+  private MissingMethodInfoImpl(
+      MethodReference methodReference,
+      Collection<MissingDefinitionContext> referencedFromContexts) {
+    super(referencedFromContexts);
+    this.methodReference = methodReference;
+  }
+
+  public static Builder builder() {
+    return new Builder();
+  }
+
+  @Override
+  public MethodReference getMethodReference() {
+    return methodReference;
+  }
+
+  public static class Builder extends MissingDefinitionInfoBase.Builder {
+
+    private MethodReference methodReference;
+
+    private Builder() {}
+
+    public Builder setMethod(MethodReference methodReference) {
+      this.methodReference = methodReference;
+      return this;
+    }
+
+    public MissingDefinitionInfo build() {
+      return new MissingMethodInfoImpl(methodReference, referencedFromContextsBuilder.build());
+    }
+  }
+}
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 1cd36de..b3c5308 100644
--- a/src/main/java/com/android/tools/r8/graph/AppView.java
+++ b/src/main/java/com/android/tools/r8/graph/AppView.java
@@ -116,7 +116,7 @@
     this.dontWarnConfiguration = DontWarnConfiguration.create(options().getProguardConfiguration());
     this.wholeProgramOptimizations = wholeProgramOptimizations;
     this.graphLens = GraphLens.getIdentityLens();
-    this.initClassLens = InitClassLens.getDefault();
+    this.initClassLens = InitClassLens.getThrowingInstance();
     this.rewritePrefix = mapper;
 
     if (enableWholeProgramOptimizations() && options().callSiteOptimizationOptions().isEnabled()) {
@@ -128,12 +128,7 @@
 
     this.libraryMethodSideEffectModelCollection = new LibraryMethodSideEffectModelCollection(this);
     this.libraryMemberOptimizer = new LibraryMemberOptimizer(this);
-
-    if (enableWholeProgramOptimizations() && options().protoShrinking().isProtoShrinkingEnabled()) {
-      this.protoShrinker = new ProtoShrinker(withLiveness());
-    } else {
-      this.protoShrinker = null;
-    }
+    this.protoShrinker = ProtoShrinker.create(withLiveness());
   }
 
   public boolean verifyMainThread() {
diff --git a/src/main/java/com/android/tools/r8/graph/ClassAccessFlags.java b/src/main/java/com/android/tools/r8/graph/ClassAccessFlags.java
index be553ac6..19f0fdf 100644
--- a/src/main/java/com/android/tools/r8/graph/ClassAccessFlags.java
+++ b/src/main/java/com/android/tools/r8/graph/ClassAccessFlags.java
@@ -58,6 +58,11 @@
     super(originalFlags, modifiedFlags);
   }
 
+  public static ClassAccessFlags createPublicFinalSynthetic() {
+    return new ClassAccessFlags(
+        Constants.ACC_PUBLIC | Constants.ACC_FINAL | Constants.ACC_SYNTHETIC);
+  }
+
   public static ClassAccessFlags fromSharedAccessFlags(int access) {
     assert (access & SHARED_FLAGS) == access;
     assert SHARED_FLAGS == DEX_FLAGS;
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 65bc4fb..7095940 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedField.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedField.java
@@ -5,7 +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.android.tools.r8.kotlin.KotlinMetadataUtils.NO_KOTLIN_INFO;
+import static com.android.tools.r8.kotlin.KotlinMetadataUtils.getNoKotlinInfo;
 
 import com.android.tools.r8.dex.MixedSectionCollection;
 import com.android.tools.r8.graph.GenericSignature.FieldTypeSignature;
@@ -38,7 +38,7 @@
   private FieldTypeSignature genericSignature;
 
   private FieldOptimizationInfo optimizationInfo = DefaultFieldOptimizationInfo.getInstance();
-  private KotlinFieldLevelInfo kotlinMemberInfo = NO_KOTLIN_INFO;
+  private KotlinFieldLevelInfo kotlinMemberInfo = getNoKotlinInfo();
 
   private static void specify(StructuralSpecification<DexEncodedField, ?> spec) {
     spec.withItem(DexEncodedField::getReference)
@@ -135,17 +135,22 @@
   }
 
   @Override
-  public KotlinFieldLevelInfo getKotlinMemberInfo() {
+  public KotlinFieldLevelInfo getKotlinInfo() {
     return kotlinMemberInfo;
   }
 
   @Override
+  public void clearKotlinInfo() {
+    kotlinMemberInfo = getNoKotlinInfo();
+  }
+
+  @Override
   public FieldAccessFlags getAccessFlags() {
     return accessFlags;
   }
 
   public void setKotlinMemberInfo(KotlinFieldLevelInfo kotlinMemberInfo) {
-    assert this.kotlinMemberInfo == NO_KOTLIN_INFO;
+    assert this.kotlinMemberInfo == getNoKotlinInfo();
     this.kotlinMemberInfo = kotlinMemberInfo;
   }
 
diff --git a/src/main/java/com/android/tools/r8/graph/DexEncodedMember.java b/src/main/java/com/android/tools/r8/graph/DexEncodedMember.java
index 0074842..f596146 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedMember.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedMember.java
@@ -24,7 +24,9 @@
     this.d8R8Synthesized = d8R8Synthesized;
   }
 
-  public abstract KotlinMemberLevelInfo getKotlinMemberInfo();
+  public abstract KotlinMemberLevelInfo getKotlinInfo();
+
+  public abstract void clearKotlinInfo();
 
   public DexType getHolderType() {
     return getReference().getHolderType();
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 d387d77..60c0ec6 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
@@ -10,7 +10,7 @@
 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.graph.DexProgramClass.asProgramClassOrNull;
-import static com.android.tools.r8.kotlin.KotlinMetadataUtils.NO_KOTLIN_INFO;
+import static com.android.tools.r8.kotlin.KotlinMetadataUtils.getNoKotlinInfo;
 import static com.android.tools.r8.utils.ConsumerUtils.emptyConsumer;
 
 import com.android.tools.r8.cf.CfVersion;
@@ -160,7 +160,7 @@
   private MethodOptimizationInfo optimizationInfo = DefaultMethodOptimizationInfo.DEFAULT_INSTANCE;
   private CallSiteOptimizationInfo callSiteOptimizationInfo = CallSiteOptimizationInfo.bottom();
   private CfVersion classFileVersion;
-  private KotlinMethodLevelInfo kotlinMemberInfo = NO_KOTLIN_INFO;
+  private KotlinMethodLevelInfo kotlinMemberInfo = getNoKotlinInfo();
   /** Generic signature information if the attribute is present in the input */
   private MethodTypeSignature genericSignature;
 
@@ -626,10 +626,15 @@
   }
 
   @Override
-  public KotlinMethodLevelInfo getKotlinMemberInfo() {
+  public KotlinMethodLevelInfo getKotlinInfo() {
     return kotlinMemberInfo;
   }
 
+  @Override
+  public void clearKotlinInfo() {
+    kotlinMemberInfo = getNoKotlinInfo();
+  }
+
   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
@@ -640,10 +645,14 @@
     // 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;
+    assert this.kotlinMemberInfo == getNoKotlinInfo();
     this.kotlinMemberInfo = kotlinMemberInfo;
   }
 
+  public void clearKotlinMemberInfo() {
+    kotlinMemberInfo = getNoKotlinInfo();
+  }
+
   public boolean isKotlinFunction() {
     return kotlinMemberInfo.isFunction();
   }
@@ -1252,16 +1261,13 @@
       DexMethod libraryMethod,
       List<Pair<DexType, DexMethod>> extraDispatchCases,
       AppView<?> appView) {
-    MethodAccessFlags accessFlags =
-        MethodAccessFlags.fromSharedAccessFlags(
-            Constants.ACC_SYNTHETIC | Constants.ACC_STATIC | Constants.ACC_PUBLIC, false);
     CfCode code =
         new EmulateInterfaceSyntheticCfCodeProvider(
                 interfaceType, companionMethod, libraryMethod, extraDispatchCases, appView)
             .generateCfCode();
     return new DexEncodedMethod(
         newMethod,
-        accessFlags,
+        MethodAccessFlags.createPublicStaticSynthetic(),
         MethodTypeSignature.noSignature(),
         DexAnnotationSet.empty(),
         ParameterAnnotationsList.empty(),
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 92659f8..c4affd3 100644
--- a/src/main/java/com/android/tools/r8/graph/DexLibraryClass.java
+++ b/src/main/java/com/android/tools/r8/graph/DexLibraryClass.java
@@ -11,6 +11,8 @@
 import com.android.tools.r8.kotlin.KotlinClassLevelInfo;
 import com.android.tools.r8.origin.Origin;
 import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
 import java.util.List;
 import java.util.Set;
 import java.util.function.Consumer;
@@ -68,6 +70,10 @@
     assert kind == Kind.CF : "Invalid kind " + kind + " for library-path class " + type;
   }
 
+  public static Builder builder(DexItemFactory dexItemFactory) {
+    return new Builder(dexItemFactory);
+  }
+
   public static DexLibraryClass asLibraryClassOrNull(DexClass clazz) {
     return clazz != null ? clazz.asLibraryClass() : null;
   }
@@ -152,4 +158,77 @@
         ? appView.options().libraryInterfacesMayHaveStaticInitialization
         : !appView.dexItemFactory().libraryClassesWithoutStaticInitialization.contains(type);
   }
+
+  public static class Builder {
+
+    // Required.
+    private DexType type;
+    private ClassAccessFlags accessFlags;
+
+    // Optional.
+    private Origin origin = Origin.unknown();
+    private DexType superType;
+    private DexTypeList interfaces = DexTypeList.empty();
+    private DexString sourceFile = null;
+    private NestHostClassAttribute nestHost = null;
+    private List<NestMemberClassAttribute> nestMembers = Collections.emptyList();
+    private EnclosingMethodAttribute enclosingMember = null;
+    private List<InnerClassAttribute> innerClasses = Collections.emptyList();
+    private ClassSignature classSignature = ClassSignature.noSignature();
+    private DexAnnotationSet annotations = DexAnnotationSet.empty();
+    private DexEncodedField[] staticFields = DexEncodedField.EMPTY_ARRAY;
+    private DexEncodedField[] instanceFields = DexEncodedField.EMPTY_ARRAY;
+    private DexEncodedMethod[] directMethods = DexEncodedMethod.EMPTY_ARRAY;
+    private DexEncodedMethod[] virtualMethods = DexEncodedMethod.EMPTY_ARRAY;
+    private boolean skipNameValidationForTesting;
+
+    private Builder(DexItemFactory dexItemFactory) {
+      this.superType = dexItemFactory.objectType;
+      this.skipNameValidationForTesting = dexItemFactory.getSkipNameValidationForTesting();
+    }
+
+    public Builder setAccessFlags(ClassAccessFlags accessFlags) {
+      this.accessFlags = accessFlags;
+      return this;
+    }
+
+    public Builder setDirectMethods(Collection<DexEncodedMethod> directMethods) {
+      this.directMethods = directMethods.toArray(DexEncodedMethod.EMPTY_ARRAY);
+      return this;
+    }
+
+    public Builder setType(DexType type) {
+      this.type = type;
+      return this;
+    }
+
+    public DexLibraryClass build() {
+      assert validate();
+      return new DexLibraryClass(
+          type,
+          ProgramResource.Kind.CF,
+          origin,
+          accessFlags,
+          superType,
+          interfaces,
+          sourceFile,
+          nestHost,
+          nestMembers,
+          enclosingMember,
+          innerClasses,
+          classSignature,
+          annotations,
+          staticFields,
+          instanceFields,
+          directMethods,
+          virtualMethods,
+          skipNameValidationForTesting);
+    }
+
+    private boolean validate() {
+      assert type != null;
+      assert accessFlags != null;
+      return true;
+    }
+  }
 }
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 31a24ff..1a2bc2b 100644
--- a/src/main/java/com/android/tools/r8/graph/DexProgramClass.java
+++ b/src/main/java/com/android/tools/r8/graph/DexProgramClass.java
@@ -3,7 +3,7 @@
 // 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 static com.android.tools.r8.kotlin.KotlinMetadataUtils.getNoKotlinInfo;
 import static com.google.common.base.Predicates.alwaysTrue;
 
 import com.android.tools.r8.ProgramResource;
@@ -49,7 +49,7 @@
   private final ProgramResource.Kind originKind;
   private CfVersion initialClassFileVersion = null;
   private boolean deprecated = false;
-  private KotlinClassLevelInfo kotlinInfo = NO_KOTLIN_INFO;
+  private KotlinClassLevelInfo kotlinInfo = getNoKotlinInfo();
 
   private final ChecksumSupplier checksumSupplier;
 
@@ -415,7 +415,7 @@
 
   public void setKotlinInfo(KotlinClassLevelInfo kotlinInfo) {
     assert kotlinInfo != null;
-    assert this.kotlinInfo == NO_KOTLIN_INFO || kotlinInfo == NO_KOTLIN_INFO;
+    assert this.kotlinInfo == getNoKotlinInfo() || kotlinInfo == getNoKotlinInfo();
     this.kotlinInfo = kotlinInfo;
   }
 
diff --git a/src/main/java/com/android/tools/r8/graph/GenericSignaturePartialTypeArgumentApplier.java b/src/main/java/com/android/tools/r8/graph/GenericSignaturePartialTypeArgumentApplier.java
index 2c17201..dfa8a95 100644
--- a/src/main/java/com/android/tools/r8/graph/GenericSignaturePartialTypeArgumentApplier.java
+++ b/src/main/java/com/android/tools/r8/graph/GenericSignaturePartialTypeArgumentApplier.java
@@ -26,10 +26,6 @@
   private final Set<String> introducedClassTypeVariables = new HashSet<>();
   private final Set<String> introducedMethodTypeVariables = new HashSet<>();
 
-  // Wildcards can only be called be used in certain positions:
-  // https://docs.oracle.com/javase/tutorial/java/generics/wildcards.html
-  private boolean canUseWildcardInArguments = true;
-
   private GenericSignaturePartialTypeArgumentApplier(
       Map<String, DexType> substitutions, DexType objectType) {
     this.substitutions = substitutions;
@@ -37,10 +33,9 @@
   }
 
   public static GenericSignaturePartialTypeArgumentApplier build(
-      AppView<?> appView, ClassSignature classSignature, Map<String, DexType> substitutions) {
+      DexType objectType, ClassSignature classSignature, Map<String, DexType> substitutions) {
     GenericSignaturePartialTypeArgumentApplier applier =
-        new GenericSignaturePartialTypeArgumentApplier(
-            substitutions, appView.dexItemFactory().objectType);
+        new GenericSignaturePartialTypeArgumentApplier(substitutions, objectType);
     classSignature.formalTypeParameters.forEach(
         parameter -> applier.introducedClassTypeVariables.add(parameter.name));
     return applier;
@@ -97,11 +92,7 @@
     if (interfaceSignatures.isEmpty()) {
       return interfaceSignatures;
     }
-    canUseWildcardInArguments = false;
-    List<ClassTypeSignature> map =
-        ListUtils.mapOrElse(interfaceSignatures, this::visitSuperInterface);
-    canUseWildcardInArguments = true;
-    return map;
+    return ListUtils.mapOrElse(interfaceSignatures, this::visitSuperInterface);
   }
 
   @Override
@@ -109,7 +100,9 @@
     if (typeArguments.isEmpty()) {
       return typeArguments;
     }
-    return ListUtils.mapOrElse(typeArguments, this::visitFieldTypeSignature);
+    // Wildcards can only be called be used in certain positions:
+    // https://docs.oracle.com/javase/tutorial/java/generics/wildcards.html
+    return ListUtils.mapOrElse(typeArguments, arg -> visitFieldTypeSignature(arg, true));
   }
 
   @Override
@@ -172,14 +165,16 @@
 
   @Override
   public ClassTypeSignature visitSuperClass(ClassTypeSignature classTypeSignature) {
-    canUseWildcardInArguments = false;
-    ClassTypeSignature visit = classTypeSignature.visit(this);
-    canUseWildcardInArguments = true;
-    return visit;
+    return classTypeSignature.visit(this);
   }
 
   @Override
   public FieldTypeSignature visitFieldTypeSignature(FieldTypeSignature fieldSignature) {
+    return visitFieldTypeSignature(fieldSignature, false);
+  }
+
+  private FieldTypeSignature visitFieldTypeSignature(
+      FieldTypeSignature fieldSignature, boolean canUseWildcardInArguments) {
     if (fieldSignature.isStar()) {
       return fieldSignature;
     } else if (fieldSignature.isClassTypeSignature()) {
diff --git a/src/main/java/com/android/tools/r8/graph/GenericSignatureTypeVariableRemover.java b/src/main/java/com/android/tools/r8/graph/GenericSignatureTypeVariableRemover.java
index f33c9c3..23500e9 100644
--- a/src/main/java/com/android/tools/r8/graph/GenericSignatureTypeVariableRemover.java
+++ b/src/main/java/com/android/tools/r8/graph/GenericSignatureTypeVariableRemover.java
@@ -37,7 +37,7 @@
     }
     GenericSignaturePartialTypeArgumentApplier genericSignatureTypeArgumentApplier =
         GenericSignaturePartialTypeArgumentApplier.build(
-            appView, clazz.getClassSignature(), substitutions);
+            appView.dexItemFactory().objectType, clazz.getClassSignature(), substitutions);
     clazz.setClassSignature(
         genericSignatureTypeArgumentApplier.visitClassSignature(clazz.getClassSignature()));
     clazz
diff --git a/src/main/java/com/android/tools/r8/graph/GraphLens.java b/src/main/java/com/android/tools/r8/graph/GraphLens.java
index 88c0bc7..88d0852 100644
--- a/src/main/java/com/android/tools/r8/graph/GraphLens.java
+++ b/src/main/java/com/android/tools/r8/graph/GraphLens.java
@@ -346,6 +346,26 @@
     return lookupMethod(method, null, null).getReference();
   }
 
+  public final MethodLookupResult lookupInvokeDirect(DexMethod method, ProgramMethod context) {
+    return lookupMethod(method, context.getReference(), Type.DIRECT);
+  }
+
+  public final MethodLookupResult lookupInvokeInterface(DexMethod method, ProgramMethod context) {
+    return lookupMethod(method, context.getReference(), Type.INTERFACE);
+  }
+
+  public final MethodLookupResult lookupInvokeStatic(DexMethod method, ProgramMethod context) {
+    return lookupMethod(method, context.getReference(), Type.STATIC);
+  }
+
+  public final MethodLookupResult lookupInvokeSuper(DexMethod method, ProgramMethod context) {
+    return lookupMethod(method, context.getReference(), Type.SUPER);
+  }
+
+  public final MethodLookupResult lookupInvokeVirtual(DexMethod method, ProgramMethod context) {
+    return lookupMethod(method, context.getReference(), Type.VIRTUAL);
+  }
+
   /** Lookup a rebound or non-rebound method reference using the current graph lens. */
   public abstract MethodLookupResult lookupMethod(DexMethod method, DexMethod context, Type type);
 
diff --git a/src/main/java/com/android/tools/r8/graph/InitClassLens.java b/src/main/java/com/android/tools/r8/graph/InitClassLens.java
index e3e7fd2..2b74f9f 100644
--- a/src/main/java/com/android/tools/r8/graph/InitClassLens.java
+++ b/src/main/java/com/android/tools/r8/graph/InitClassLens.java
@@ -13,8 +13,8 @@
     return new Builder();
   }
 
-  public static DefaultInitClassLens getDefault() {
-    return DefaultInitClassLens.getInstance();
+  public static ThrowingInitClassLens getThrowingInstance() {
+    return ThrowingInitClassLens.getInstance();
   }
 
   public abstract DexField getInitClassField(DexType clazz);
diff --git a/src/main/java/com/android/tools/r8/graph/ProgramField.java b/src/main/java/com/android/tools/r8/graph/ProgramField.java
index 42d2453..3e2a276 100644
--- a/src/main/java/com/android/tools/r8/graph/ProgramField.java
+++ b/src/main/java/com/android/tools/r8/graph/ProgramField.java
@@ -5,6 +5,7 @@
 package com.android.tools.r8.graph;
 
 import com.android.tools.r8.dex.IndexedItemCollection;
+import com.android.tools.r8.kotlin.KotlinMemberLevelInfo;
 
 public class ProgramField extends DexClassAndField
     implements ProgramMember<DexEncodedField, DexField> {
@@ -57,4 +58,14 @@
     assert holder.isProgramClass();
     return holder.asProgramClass();
   }
+
+  @Override
+  public KotlinMemberLevelInfo getKotlinInfo() {
+    return getDefinition().getKotlinInfo();
+  }
+
+  @Override
+  public void clearKotlinInfo() {
+    getDefinition().clearKotlinInfo();
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/graph/ProgramMember.java b/src/main/java/com/android/tools/r8/graph/ProgramMember.java
index fc55509..1234378 100644
--- a/src/main/java/com/android/tools/r8/graph/ProgramMember.java
+++ b/src/main/java/com/android/tools/r8/graph/ProgramMember.java
@@ -4,6 +4,8 @@
 
 package com.android.tools.r8.graph;
 
+import com.android.tools.r8.kotlin.KotlinMemberLevelInfo;
+
 public interface ProgramMember<D extends DexEncodedMember<D, R>, R extends DexMember<D, R>>
     extends ProgramDefinition {
 
@@ -18,4 +20,8 @@
   DexProgramClass getHolder();
 
   DexType getHolderType();
+
+  KotlinMemberLevelInfo getKotlinInfo();
+
+  void clearKotlinInfo();
 }
diff --git a/src/main/java/com/android/tools/r8/graph/ProgramMethod.java b/src/main/java/com/android/tools/r8/graph/ProgramMethod.java
index a8817dc..617f620 100644
--- a/src/main/java/com/android/tools/r8/graph/ProgramMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/ProgramMethod.java
@@ -9,6 +9,7 @@
 import com.android.tools.r8.ir.code.Position;
 import com.android.tools.r8.ir.conversion.LensCodeRewriterUtils;
 import com.android.tools.r8.ir.conversion.MethodProcessor;
+import com.android.tools.r8.kotlin.KotlinMemberLevelInfo;
 import com.android.tools.r8.logging.Log;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.position.MethodPosition;
@@ -94,6 +95,16 @@
     return holder.asProgramClass();
   }
 
+  @Override
+  public KotlinMemberLevelInfo getKotlinInfo() {
+    return getDefinition().getKotlinInfo();
+  }
+
+  @Override
+  public void clearKotlinInfo() {
+    getDefinition().clearKotlinMemberInfo();
+  }
+
   public MethodPosition getPosition() {
     return getDefinition().getPosition();
   }
diff --git a/src/main/java/com/android/tools/r8/graph/DefaultInitClassLens.java b/src/main/java/com/android/tools/r8/graph/ThrowingInitClassLens.java
similarity index 71%
rename from src/main/java/com/android/tools/r8/graph/DefaultInitClassLens.java
rename to src/main/java/com/android/tools/r8/graph/ThrowingInitClassLens.java
index ec2f4c6..730a873 100644
--- a/src/main/java/com/android/tools/r8/graph/DefaultInitClassLens.java
+++ b/src/main/java/com/android/tools/r8/graph/ThrowingInitClassLens.java
@@ -6,13 +6,13 @@
 
 import com.android.tools.r8.errors.Unreachable;
 
-public class DefaultInitClassLens extends InitClassLens {
+public class ThrowingInitClassLens extends InitClassLens {
 
-  private static final DefaultInitClassLens INSTANCE = new DefaultInitClassLens();
+  private static final ThrowingInitClassLens INSTANCE = new ThrowingInitClassLens();
 
-  private DefaultInitClassLens() {}
+  private ThrowingInitClassLens() {}
 
-  public static DefaultInitClassLens getInstance() {
+  public static ThrowingInitClassLens getInstance() {
     return INSTANCE;
   }
 
diff --git a/src/main/java/com/android/tools/r8/graph/UseRegistry.java b/src/main/java/com/android/tools/r8/graph/UseRegistry.java
index 8f64090..565511a 100644
--- a/src/main/java/com/android/tools/r8/graph/UseRegistry.java
+++ b/src/main/java/com/android/tools/r8/graph/UseRegistry.java
@@ -51,7 +51,9 @@
     registerInvokeStatic(method);
   }
 
-  public abstract void registerNewInstance(DexType type);
+  public void registerNewInstance(DexType type) {
+    registerTypeReference(type);
+  }
 
   public abstract void registerStaticFieldRead(DexField field);
 
@@ -67,7 +69,9 @@
 
   public abstract void registerTypeReference(DexType type);
 
-  public abstract void registerInstanceOf(DexType type);
+  public void registerInstanceOf(DexType type) {
+    registerTypeReference(type);
+  }
 
   public void registerConstClass(
       DexType type, ListIterator<? extends CfOrDexInstruction> iterator) {
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoKotlinMetadata.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoKotlinMetadata.java
index 7783f20..5e0a18e 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoKotlinMetadata.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoKotlinMetadata.java
@@ -26,7 +26,7 @@
 
   private boolean verifyNoUnexpectedKotlinMemberInfo(DexProgramClass clazz) {
     assert Streams.stream(clazz.members())
-        .allMatch(member -> member.getKotlinMemberInfo().isNoKotlinInformation());
+        .allMatch(member -> member.getKotlinInfo().isNoKotlinInformation());
     return true;
   }
 
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/SyntheticItemsPolicy.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/SyntheticItemsPolicy.java
index 6a84212..753ac5d 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/SyntheticItemsPolicy.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/SyntheticItemsPolicy.java
@@ -10,7 +10,6 @@
 import com.android.tools.r8.horizontalclassmerging.MultiClassSameReferencePolicy;
 import com.android.tools.r8.horizontalclassmerging.policies.SyntheticItemsPolicy.ClassKind;
 import com.android.tools.r8.synthesis.SyntheticItems;
-import com.android.tools.r8.synthesis.SyntheticNaming.SyntheticKind;
 
 public class SyntheticItemsPolicy extends MultiClassSameReferencePolicy<ClassKind> {
 
@@ -34,19 +33,9 @@
       return ClassKind.NOT_SYNTHETIC;
     }
 
-    // Do not allow merging synthetics that are not lambdas.
-    if (!syntheticItems.isNonLegacySynthetic(clazz)
-        || syntheticItems.getNonLegacySyntheticKind(clazz) != SyntheticKind.LAMBDA) {
-      return ineligibleForClassMerging();
-    }
-
-    // Allow merging Java lambdas with Java lambdas.
-    if (appView.options().horizontalClassMergerOptions().isJavaLambdaMergingEnabled()) {
-      return ClassKind.SYNTHETIC;
-    }
-
-    // Java lambda merging is disabled.
-    return ineligibleForClassMerging();
+    return syntheticItems.isEligibleForClassMerging(clazz)
+        ? ClassKind.SYNTHETIC
+        : ineligibleForClassMerging();
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedExtensionRegistryShrinker.java b/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedExtensionRegistryShrinker.java
index bf81f7e..be5a107 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedExtensionRegistryShrinker.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedExtensionRegistryShrinker.java
@@ -4,11 +4,13 @@
 
 package com.android.tools.r8.ir.analysis.proto;
 
+import static com.android.tools.r8.graph.DexProgramClass.asProgramClassOrNull;
 
 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.DexField;
+import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.FieldAccessInfo;
 import com.android.tools.r8.graph.FieldAccessInfoCollection;
@@ -25,13 +27,17 @@
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.shaking.DefaultTreePrunerConfiguration;
 import com.android.tools.r8.shaking.Enqueuer;
+import com.android.tools.r8.shaking.Enqueuer.Mode;
 import com.android.tools.r8.shaking.KeepInfoCollection;
 import com.android.tools.r8.shaking.TreePrunerConfiguration;
 import com.android.tools.r8.utils.Timing;
 import com.android.tools.r8.utils.collections.SortedProgramMethodSet;
 import com.google.common.collect.Sets;
 import java.util.ArrayList;
+import java.util.Collections;
+import java.util.IdentityHashMap;
 import java.util.List;
+import java.util.Map;
 import java.util.Set;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.ExecutorService;
@@ -71,8 +77,7 @@
   private final AppView<AppInfoWithLiveness> appView;
   private final ProtoReferences references;
 
-  private final Set<DexType> classesWithRemovedExtensionFields = Sets.newIdentityHashSet();
-  private final Set<DexField> removedExtensionFields = Sets.newIdentityHashSet();
+  private final Map<DexType, Map<DexField, Mode>> removedExtensionFields = new IdentityHashMap<>();
 
   GeneratedExtensionRegistryShrinker(
       AppView<AppInfoWithLiveness> appView, ProtoReferences references) {
@@ -92,14 +97,16 @@
    * of these methods and replace the reads of these fields by null.
    */
   public TreePrunerConfiguration run(Enqueuer.Mode mode) {
-    forEachDeadProtoExtensionField(this::recordDeadProtoExtensionField);
+    forEachDeadProtoExtensionField(field -> recordDeadProtoExtensionField(field, mode));
     appView.appInfo().getFieldAccessInfoCollection().removeIf((field, info) -> wasRemoved(field));
     return createTreePrunerConfiguration(mode);
   }
 
-  private void recordDeadProtoExtensionField(DexField field) {
-    classesWithRemovedExtensionFields.add(field.holder);
-    removedExtensionFields.add(field);
+  private void recordDeadProtoExtensionField(DexField field, Enqueuer.Mode mode) {
+    assert mode.isInitialTreeShaking() || mode.isFinalTreeShaking();
+    removedExtensionFields
+        .computeIfAbsent(field.getHolderType(), ignore -> new IdentityHashMap<>())
+        .put(field, mode);
   }
 
   private TreePrunerConfiguration createTreePrunerConfiguration(Enqueuer.Mode mode) {
@@ -124,7 +131,7 @@
    */
   public void rewriteCode(DexEncodedMethod method, IRCode code) {
     if (method.isClassInitializer()
-        && classesWithRemovedExtensionFields.contains(method.getHolderType())
+        && removedExtensionFields.containsKey(method.getHolderType())
         && code.metadata().mayHaveStaticPut()) {
       rewriteClassInitializer(code);
     }
@@ -133,7 +140,7 @@
   private void rewriteClassInitializer(IRCode code) {
     List<StaticPut> toBeRemoved = new ArrayList<>();
     for (StaticPut staticPut : code.<StaticPut>instructions(Instruction::isStaticPut)) {
-      if (removedExtensionFields.contains(staticPut.getField())) {
+      if (wasRemoved(staticPut.getField())) {
         toBeRemoved.add(staticPut);
       }
     }
@@ -147,7 +154,9 @@
   }
 
   public boolean wasRemoved(DexField field) {
-    return removedExtensionFields.contains(field);
+    return removedExtensionFields
+        .getOrDefault(field.getHolderType(), Collections.emptyMap())
+        .containsKey(field);
   }
 
   public void postOptimizeGeneratedExtensionRegistry(
@@ -155,7 +164,7 @@
       throws ExecutionException {
     timing.begin("[Proto] Post optimize generated extension registry");
     SortedProgramMethodSet wave =
-        SortedProgramMethodSet.create(this::forEachFindLiteExtensionByNumberMethod);
+        SortedProgramMethodSet.create(this::forEachMethodThatRequiresPostOptimization);
     OneTimeMethodProcessor methodProcessor = OneTimeMethodProcessor.create(wave, appView);
     methodProcessor.forEachWaveWithExtension(
         (method, methodProcessingContext) ->
@@ -168,6 +177,34 @@
     timing.end();
   }
 
+  private void forEachMethodThatRequiresPostOptimization(Consumer<ProgramMethod> consumer) {
+    forEachClassInitializerWithRemovedExtensionFields(consumer, Enqueuer.Mode.FINAL_TREE_SHAKING);
+    forEachFindLiteExtensionByNumberMethod(consumer);
+  }
+
+  private void forEachClassInitializerWithRemovedExtensionFields(
+      Consumer<ProgramMethod> consumer, Enqueuer.Mode modeOfInterest) {
+    Set<DexType> classesWithRemovedExtensionFieldsInModeOfInterest = Sets.newIdentityHashSet();
+    removedExtensionFields
+        .values()
+        .forEach(
+            removedExtensionFieldsForHolder ->
+                removedExtensionFieldsForHolder.forEach(
+                    (field, mode) -> {
+                      if (mode == modeOfInterest) {
+                        classesWithRemovedExtensionFieldsInModeOfInterest.add(
+                            field.getHolderType());
+                      }
+                    }));
+    classesWithRemovedExtensionFieldsInModeOfInterest.forEach(
+        type -> {
+          DexProgramClass clazz = asProgramClassOrNull(appView.appInfo().definitionFor(type));
+          if (clazz != null && clazz.hasClassInitializer()) {
+            consumer.accept(clazz.getProgramClassInitializer());
+          }
+        });
+  }
+
   private void forEachFindLiteExtensionByNumberMethod(Consumer<ProgramMethod> consumer) {
     appView
         .appInfo()
@@ -183,9 +220,10 @@
             });
   }
 
-  public void handleFailedOrUnknownFieldResolution(DexField fieldReference, ProgramMethod context) {
-    if (references.isFindLiteExtensionByNumberMethod(context)) {
-      removedExtensionFields.add(fieldReference);
+  public void handleFailedOrUnknownFieldResolution(
+      DexField fieldReference, ProgramMethod context, Enqueuer.Mode mode) {
+    if (mode.isTreeShaking() && references.isFindLiteExtensionByNumberMethod(context)) {
+      recordDeadProtoExtensionField(fieldReference, mode);
     }
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/proto/ProtoEnqueuerUseRegistry.java b/src/main/java/com/android/tools/r8/ir/analysis/proto/ProtoEnqueuerUseRegistry.java
index 2a6be81..17758fa 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/proto/ProtoEnqueuerUseRegistry.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/proto/ProtoEnqueuerUseRegistry.java
@@ -59,14 +59,22 @@
    * not trace any static-get instructions in every implementation of dynamicMethod() that accesses
    * an 'INSTANCE' or a 'DEFAULT_INSTANCE' field.
    *
+   * <p>The motivation for this is that the proto shrinker will optimize the proto schemas in each
+   * dynamicMethod() after the second round of tree shaking. This is done by {@link
+   * GeneratedMessageLiteShrinker#postOptimizeDynamicMethods}. If we traced all static field reads
+   * as the {@link DefaultEnqueuerUseRegistry} we would end up retaining the types that are
+   * references from the non-optimized proto schemas, but which do not end up being referenced from
+   * the final optimized proto schemas.
+   *
    * <p>The static-get instructions that remain after the proto schema has been optimized will be
    * traced manually by {@link ProtoEnqueuerExtension#tracePendingInstructionsInDynamicMethods}.
    */
   @Override
   public void registerStaticFieldRead(DexField field) {
     if (references.isDynamicMethod(getContextMethod())
+        && field.getHolderType() != getContextHolder().getType()
         && isStaticFieldReadForProtoSchemaDefinition(field)) {
-      enqueuer.addDeadProtoTypeCandidate(field.holder);
+      enqueuer.addDeadProtoTypeCandidate(field.getHolderType());
       return;
     }
     super.registerStaticFieldRead(field);
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/proto/ProtoShrinker.java b/src/main/java/com/android/tools/r8/ir/analysis/proto/ProtoShrinker.java
index a0f75d7..c83f118 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/proto/ProtoShrinker.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/proto/ProtoShrinker.java
@@ -10,6 +10,7 @@
 import com.android.tools.r8.ir.analysis.proto.schema.ProtoFieldTypeFactory;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.StringDiagnostic;
 import com.google.common.collect.Sets;
 import java.util.Set;
 
@@ -26,9 +27,8 @@
 
   private Set<DexType> deadProtoTypes = Sets.newIdentityHashSet();
 
-  public ProtoShrinker(AppView<AppInfoWithLiveness> appView) {
+  public ProtoShrinker(AppView<AppInfoWithLiveness> appView, ProtoReferences references) {
     ProtoFieldTypeFactory factory = new ProtoFieldTypeFactory();
-    ProtoReferences references = new ProtoReferences(appView.dexItemFactory());
     this.decoder = new RawMessageInfoDecoder(factory, references);
     this.factory = factory;
     this.generatedExtensionRegistryShrinker =
@@ -54,6 +54,26 @@
     this.references = references;
   }
 
+  public static ProtoShrinker create(AppView<AppInfoWithLiveness> appView) {
+    if (!appView.enableWholeProgramOptimizations()
+        || !appView.options().protoShrinking().isProtoShrinkingEnabled()) {
+      return null;
+    }
+
+    ProtoReferences references = new ProtoReferences(appView.dexItemFactory());
+    if (appView.definitionFor(references.generatedMessageLiteType) == null) {
+      appView
+          .reporter()
+          .warning(
+              new StringDiagnostic(
+                  "Ignoring -shrinkunusedprotofields since the protobuf-lite runtime is missing"));
+      appView.options().protoShrinking().disable();
+      return null;
+    }
+
+    return new ProtoShrinker(appView, references);
+  }
+
   public Set<DexType> getDeadProtoTypes() {
     return deadProtoTypes;
   }
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 6fe8c6f..d076960 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
@@ -83,6 +83,26 @@
       return dexOpcodeRange;
     }
 
+    public boolean isDirect() {
+      return this == DIRECT;
+    }
+
+    public boolean isInterface() {
+      return this == INTERFACE;
+    }
+
+    public boolean isStatic() {
+      return this == STATIC;
+    }
+
+    public boolean isSuper() {
+      return this == SUPER;
+    }
+
+    public boolean isVirtual() {
+      return this == VIRTUAL;
+    }
+
     public MethodHandleType toMethodHandle(DexMethod targetMethod) {
       switch (this) {
         case STATIC:
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/itf/EmulatedInterfaceProcessor.java b/src/main/java/com/android/tools/r8/ir/desugar/itf/EmulatedInterfaceProcessor.java
index e8f4756..e8dc257 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/itf/EmulatedInterfaceProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/itf/EmulatedInterfaceProcessor.java
@@ -3,6 +3,8 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.ir.desugar.itf;
 
+import static com.android.tools.r8.ir.desugar.itf.InterfaceMethodRewriter.emulateInterfaceLibraryMethod;
+
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.ClassAccessFlags;
 import com.android.tools.r8.graph.DexAnnotationSet;
@@ -214,7 +216,7 @@
           emulationMethods.add(
               DexEncodedMethod.toEmulateDispatchLibraryMethod(
                   method.getHolderType(),
-                  rewriter.emulateInterfaceLibraryMethod(method),
+                  emulateInterfaceLibraryMethod(method, rewriter.factory),
                   companionMethod,
                   libraryMethod,
                   extraDispatchCases,
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceMethodRewriter.java b/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceMethodRewriter.java
index 180c2aa..ea1d102 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceMethodRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceMethodRewriter.java
@@ -744,7 +744,7 @@
       assert !defaultMethod.getAccessFlags().isAbstract();
       instructions.replaceCurrentInstruction(
           new InvokeStatic(
-              emulateInterfaceLibraryMethod(defaultMethod),
+              emulateInterfaceLibraryMethod(defaultMethod, factory),
               invokeMethod.outValue(),
               invokeMethod.arguments()));
     }
@@ -773,7 +773,8 @@
     return false;
   }
 
-  DexMethod emulateInterfaceLibraryMethod(DexClassAndMethod method) {
+  public static DexMethod emulateInterfaceLibraryMethod(
+      DexClassAndMethod method, DexItemFactory factory) {
     return factory.createMethod(
         getEmulateLibraryInterfaceClassType(method.getHolderType(), factory),
         factory.prependTypeToProto(method.getHolderType(), method.getProto()),
@@ -786,7 +787,7 @@
         + ";";
   }
 
-  static DexType getEmulateLibraryInterfaceClassType(DexType type, DexItemFactory factory) {
+  public static DexType getEmulateLibraryInterfaceClassType(DexType type, DexItemFactory factory) {
     assert type.isClassType();
     String descriptor = type.descriptor.toString();
     String elTypeDescriptor = getEmulateLibraryInterfaceClassDescriptor(descriptor);
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 a48292a..436f996 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
@@ -908,11 +908,7 @@
                     .verifySyntheticLambdaProperty(
                         context.getHolder(),
                         lambdaClass ->
-                            appView.appInfo().hasPinnedInstanceInitializer(lambdaClass.getType())
-                                || appView
-                                    .options()
-                                    .horizontalClassMergerOptions()
-                                    .isJavaLambdaMergingEnabled(),
+                            appView.appInfo().hasPinnedInstanceInitializer(lambdaClass.getType()),
                         nonLambdaClass -> true)
             : "Unexpected observable side effects from lambda `" + context.toSourceString() + "`";
       }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/string/StringBuilderOptimizer.java b/src/main/java/com/android/tools/r8/ir/optimize/string/StringBuilderOptimizer.java
index ca49a7d..33f4ba5 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/string/StringBuilderOptimizer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/string/StringBuilderOptimizer.java
@@ -167,10 +167,14 @@
     }
     if (histogramOfLengthOfPartialResult != null) {
       Log.info(getClass(), "------ histogram of StringBuilder partial result lengths ------");
-      histogramOfLengthOfPartialResult.forEach((length, count) -> {
-        Log.info(getClass(),
-            "%s: %s (%s)", length, StringUtils.times("*", Math.min(count, 53)), count);
-      });
+      histogramOfLengthOfPartialResult.forEach(
+          (length, count) ->
+              Log.info(
+                  getClass(),
+                  "%s: %s (%s)",
+                  length,
+                  StringUtils.times("*", Math.min(count, 53)),
+                  count));
     }
   }
 
@@ -712,20 +716,7 @@
           builder, optimizationConfiguration)) {
         return null;
       }
-      String result = StringUtils.join("", contents);
-      int estimate = estimateSizeReduction(contents);
-      return estimate > result.length() ? result : null;
-    }
-
-    private int estimateSizeReduction(List<String> contents) {
-      int result = 8; // builder initialization
-      for (String content : contents) {
-        result += 4; // builder append()
-        // If a certain string is only used as part of the resulting string, it will be gone.
-        result += (int) (content.length() * 0.5); // Magic number of that chance: 50%.
-      }
-      result += 4; // builder toString()
-      return result;
+      return StringUtils.join("", contents);
     }
 
     void removeTrivialBuilders() {
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinAnnotationArgumentInfo.java b/src/main/java/com/android/tools/r8/kotlin/KotlinAnnotationArgumentInfo.java
index 953766c..883be66 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinAnnotationArgumentInfo.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinAnnotationArgumentInfo.java
@@ -10,14 +10,13 @@
 import com.android.tools.r8.kotlin.Kotlin.ClassClassifiers;
 import com.android.tools.r8.naming.NamingLens;
 import com.android.tools.r8.shaking.EnqueuerMetadataTraceable;
-import com.android.tools.r8.utils.Box;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
 import java.util.ArrayList;
 import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
-import kotlinx.metadata.KmAnnotation;
+import java.util.function.Consumer;
 import kotlinx.metadata.KmAnnotationArgument;
 import kotlinx.metadata.KmAnnotationArgument.AnnotationValue;
 import kotlinx.metadata.KmAnnotationArgument.ArrayValue;
@@ -29,7 +28,8 @@
   private static final Map<String, KotlinAnnotationArgumentInfo> EMPTY_ARGUMENTS =
       ImmutableMap.of();
 
-  abstract KmAnnotationArgument<?> rewrite(AppView<?> appView, NamingLens namingLens);
+  abstract boolean rewrite(
+      Consumer<KmAnnotationArgument<?>> consumer, AppView<?> appView, NamingLens namingLens);
 
   private static KotlinAnnotationArgumentInfo createArgument(
       KmAnnotationArgument<?> arg, DexItemFactory factory) {
@@ -75,9 +75,13 @@
     }
 
     @Override
-    KmAnnotationArgument<?> rewrite(AppView<?> appView, NamingLens namingLens) {
-      return new KClassValue(
-          value.toRenamedBinaryNameOrDefault(appView, namingLens, ClassClassifiers.anyName));
+    boolean rewrite(
+        Consumer<KmAnnotationArgument<?>> consumer, AppView<?> appView, NamingLens namingLens) {
+      return value.toRenamedBinaryNameOrDefault(
+          rewrittenValue -> consumer.accept(new KClassValue(rewrittenValue)),
+          appView,
+          namingLens,
+          ClassClassifiers.anyName);
     }
   }
 
@@ -103,10 +107,14 @@
     }
 
     @Override
-    KmAnnotationArgument<?> rewrite(AppView<?> appView, NamingLens namingLens) {
-      return new EnumValue(
-          enumClassName.toRenamedBinaryNameOrDefault(appView, namingLens, ClassClassifiers.anyName),
-          enumEntryName);
+    boolean rewrite(
+        Consumer<KmAnnotationArgument<?>> consumer, AppView<?> appView, NamingLens namingLens) {
+      return enumClassName.toRenamedBinaryNameOrDefault(
+          rewrittenEnumClassName ->
+              consumer.accept(new EnumValue(rewrittenEnumClassName, enumEntryName)),
+          appView,
+          namingLens,
+          ClassClassifiers.anyName);
     }
   }
 
@@ -130,13 +138,16 @@
     }
 
     @Override
-    KmAnnotationArgument<?> rewrite(AppView<?> appView, NamingLens namingLens) {
-      Box<KmAnnotation> rewrittenAnnotation = new Box<>();
-      value.rewrite(rewrittenAnnotation::set, appView, namingLens);
-      if (rewrittenAnnotation.isSet()) {
-        return new AnnotationValue(rewrittenAnnotation.get());
-      }
-      return null;
+    boolean rewrite(
+        Consumer<KmAnnotationArgument<?>> consumer, AppView<?> appView, NamingLens namingLens) {
+      return value.rewrite(
+          rewrittenAnnotation -> {
+            if (rewrittenAnnotation != null) {
+              consumer.accept(new AnnotationValue(rewrittenAnnotation));
+            }
+          },
+          appView,
+          namingLens);
     }
   }
 
@@ -170,16 +181,23 @@
     }
 
     @Override
-    KmAnnotationArgument<?> rewrite(AppView<?> appView, NamingLens namingLens) {
+    boolean rewrite(
+        Consumer<KmAnnotationArgument<?>> consumer, AppView<?> appView, NamingLens namingLens) {
       List<KmAnnotationArgument<?>> rewrittenArguments = new ArrayList<>();
+      boolean rewritten = false;
       for (KotlinAnnotationArgumentInfo kotlinAnnotationArgumentInfo : value) {
-        KmAnnotationArgument<?> rewrittenArg =
-            kotlinAnnotationArgumentInfo.rewrite(appView, namingLens);
-        if (rewrittenArg != null) {
-          rewrittenArguments.add(rewrittenArg);
-        }
+        rewritten |=
+            kotlinAnnotationArgumentInfo.rewrite(
+                rewrittenArg -> {
+                  if (rewrittenArg != null) {
+                    rewrittenArguments.add(rewrittenArg);
+                  }
+                },
+                appView,
+                namingLens);
       }
-      return new ArrayValue(rewrittenArguments);
+      consumer.accept(new ArrayValue(rewrittenArguments));
+      return rewritten;
     }
   }
 
@@ -201,8 +219,10 @@
     }
 
     @Override
-    KmAnnotationArgument<?> rewrite(AppView<?> appView, NamingLens namingLens) {
-      return argument;
+    boolean rewrite(
+        Consumer<KmAnnotationArgument<?>> consumer, AppView<?> appView, NamingLens namingLens) {
+      consumer.accept(argument);
+      return false;
     }
   }
 }
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinAnnotationInfo.java b/src/main/java/com/android/tools/r8/kotlin/KotlinAnnotationInfo.java
index 45adb46..fd036d8 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinAnnotationInfo.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinAnnotationInfo.java
@@ -9,6 +9,7 @@
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.naming.NamingLens;
 import com.android.tools.r8.shaking.EnqueuerMetadataTraceable;
+import com.android.tools.r8.utils.BooleanBox;
 import com.android.tools.r8.utils.DescriptorUtils;
 import com.google.common.collect.ImmutableList;
 import java.util.LinkedHashMap;
@@ -48,26 +49,38 @@
     return builder.build();
   }
 
-  public void rewrite(
+  boolean rewrite(
       KmVisitorProviders.KmAnnotationVisitorProvider visitorProvider,
       AppView<?> appView,
       NamingLens namingLens) {
-    String renamedDescriptor =
-        annotationType.toRenamedDescriptorOrDefault(appView, namingLens, null);
-    if (renamedDescriptor == null) {
-      // The type has been pruned
-      return;
-    }
-    String classifier = DescriptorUtils.descriptorToKotlinClassifier(renamedDescriptor);
-    Map<String, KmAnnotationArgument<?>> rewrittenArguments = new LinkedHashMap<>();
-    arguments.forEach(
-        (key, arg) -> {
-          KmAnnotationArgument<?> rewrittenArg = arg.rewrite(appView, namingLens);
-          if (rewrittenArg != null) {
-            rewrittenArguments.put(key, rewrittenArg);
-          }
-        });
-    visitorProvider.get(new KmAnnotation(classifier, rewrittenArguments));
+    BooleanBox rewritten = new BooleanBox(false);
+    rewritten.or(
+        annotationType.toRenamedDescriptorOrDefault(
+            renamedDescriptor -> {
+              if (renamedDescriptor == null) {
+                // The type has been pruned
+                rewritten.set(true);
+                return;
+              }
+              String classifier = DescriptorUtils.descriptorToKotlinClassifier(renamedDescriptor);
+              Map<String, KmAnnotationArgument<?>> rewrittenArguments = new LinkedHashMap<>();
+              arguments.forEach(
+                  (key, arg) ->
+                      rewritten.or(
+                          arg.rewrite(
+                              rewrittenArg -> {
+                                if (rewrittenArg != null) {
+                                  rewrittenArguments.put(key, rewrittenArg);
+                                }
+                              },
+                              appView,
+                              namingLens)));
+              visitorProvider.get(new KmAnnotation(classifier, rewrittenArguments));
+            },
+            appView,
+            namingLens,
+            null));
+    return rewritten.get();
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinClassInfo.java b/src/main/java/com/android/tools/r8/kotlin/KotlinClassInfo.java
index f10902f..e440049 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinClassInfo.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinClassInfo.java
@@ -17,6 +17,7 @@
 import com.android.tools.r8.graph.DexString;
 import com.android.tools.r8.naming.NamingLens;
 import com.android.tools.r8.utils.DescriptorUtils;
+import com.android.tools.r8.utils.Pair;
 import com.android.tools.r8.utils.Reporter;
 import com.google.common.collect.ImmutableList;
 import java.util.HashMap;
@@ -84,13 +85,18 @@
   }
 
   public static KotlinClassInfo create(
-      KmClass kmClass,
+      KotlinClassMetadata.Class metadata,
       String packageName,
       int[] metadataVersion,
       DexClass hostClass,
-      DexItemFactory factory,
-      Reporter reporter,
+      AppView<?> appView,
       Consumer<DexEncodedMethod> keepByteCode) {
+    DexItemFactory factory = appView.dexItemFactory();
+    Reporter reporter = appView.reporter();
+    KmClass kmClass = metadata.toKmClass();
+    KotlinJvmSignatureExtensionInformation extensionInformation =
+        KotlinJvmSignatureExtensionInformation.readInformationFromMessage(
+            metadata, appView.options());
     Map<String, DexEncodedField> fieldMap = new HashMap<>();
     for (DexEncodedField field : hostClass.fields()) {
       fieldMap.put(toJvmFieldSignature(field.getReference()).asString(), field);
@@ -100,9 +106,12 @@
       methodMap.put(toJvmMethodSignature(method.getReference()).asString(), method);
     }
     ImmutableList.Builder<KotlinConstructorInfo> notBackedConstructors = ImmutableList.builder();
+    int constructorIndex = 0;
     for (KmConstructor kmConstructor : kmClass.getConstructors()) {
+      boolean readConstructorSignature =
+          extensionInformation.hasJvmMethodSignatureExtensionForConstructor(constructorIndex++);
       KotlinConstructorInfo constructorInfo =
-          KotlinConstructorInfo.create(kmConstructor, factory, reporter);
+          KotlinConstructorInfo.create(kmConstructor, factory, reporter, readConstructorSignature);
       JvmMethodSignature signature = JvmExtensionsKt.getSignature(kmConstructor);
       if (signature != null) {
         DexEncodedMethod method = methodMap.get(signature.asString());
@@ -116,7 +125,7 @@
     }
     KotlinDeclarationContainerInfo container =
         KotlinDeclarationContainerInfo.create(
-            kmClass, methodMap, fieldMap, factory, reporter, keepByteCode);
+            kmClass, methodMap, fieldMap, factory, reporter, keepByteCode, extensionInformation);
     setCompanionObject(kmClass, hostClass, reporter);
     return new KotlinClassInfo(
         kmClass.getFlags(),
@@ -185,7 +194,7 @@
     }
     for (DexEncodedField field : hostClass.fields()) {
       if (field.getReference().name.toString().equals(companionObjectName)) {
-        field.setKotlinMemberInfo(new KotlinCompanionInfo());
+        field.setKotlinMemberInfo(new KotlinCompanionInfo(companionObjectName));
         return;
       }
     }
@@ -204,7 +213,8 @@
   }
 
   @Override
-  public KotlinClassHeader rewrite(DexClass clazz, AppView<?> appView, NamingLens namingLens) {
+  public Pair<KotlinClassHeader, Boolean> rewrite(
+      DexClass clazz, AppView<?> appView, NamingLens namingLens) {
     KmClass kmClass = new KmClass();
     // TODO(b/154348683): Set flags.
     kmClass.setFlags(flags);
@@ -214,88 +224,110 @@
     // 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.
+    boolean rewritten = !originalDescriptor.equals(rewrittenDescriptor);
     kmClass.setName(
-        originalDescriptor.equals(rewrittenDescriptor)
+        !rewritten
             ? this.name
             : DescriptorUtils.getBinaryNameFromDescriptor(rewrittenDescriptor.toString()));
     // Find a companion object.
     for (DexEncodedField field : clazz.fields()) {
-      if (field.getKotlinMemberInfo().isCompanion()) {
-        field
-            .getKotlinMemberInfo()
-            .asCompanion()
-            .rewrite(kmClass, field.getReference(), namingLens);
+      if (field.getKotlinInfo().isCompanion()) {
+        rewritten |=
+            field.getKotlinInfo().asCompanion().rewrite(kmClass, field.getReference(), namingLens);
       }
     }
     // Take all not backed constructors because we will never find them in definitions.
     for (KotlinConstructorInfo constructorInfo : constructorsWithNoBacking) {
-      constructorInfo.rewrite(kmClass, null, appView, namingLens);
+      rewritten |= 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);
+      if (method.getKotlinInfo().isConstructor()) {
+        KotlinConstructorInfo constructorInfo = method.getKotlinInfo().asConstructor();
+        rewritten |= constructorInfo.rewrite(kmClass, method, appView, namingLens);
       }
     }
     // Rewrite functions, type-aliases and type-parameters.
-    declarationContainerInfo.rewrite(
-        kmClass::visitFunction,
-        kmClass::visitProperty,
-        kmClass::visitTypeAlias,
-        clazz,
-        appView,
-        namingLens);
+    rewritten |=
+        declarationContainerInfo.rewrite(
+            kmClass::visitFunction,
+            kmClass::visitProperty,
+            kmClass::visitTypeAlias,
+            clazz,
+            appView,
+            namingLens);
     // Rewrite type parameters.
     for (KotlinTypeParameterInfo typeParameter : typeParameters) {
-      typeParameter.rewrite(kmClass::visitTypeParameter, appView, namingLens);
+      rewritten |= typeParameter.rewrite(kmClass::visitTypeParameter, appView, namingLens);
     }
     // Rewrite super types.
     for (KotlinTypeInfo superType : superTypes) {
       // Ensure the rewritten super type is not this type.
       if (clazz.getType() != superType.rewriteType(appView.graphLens())) {
-        superType.rewrite(kmClass::visitSupertype, appView, namingLens);
+        rewritten |= superType.rewrite(kmClass::visitSupertype, appView, namingLens);
+      } else {
+        rewritten = true;
       }
     }
     // Rewrite nested classes.
     for (KotlinTypeReference nestedClass : nestedClasses) {
-      String nestedDescriptor = nestedClass.toRenamedBinaryNameOrDefault(appView, namingLens, null);
-      if (nestedDescriptor != null) {
-        // 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 = nestedDescriptor.lastIndexOf(DescriptorUtils.INNER_CLASS_SEPARATOR);
-        kmClass.visitNestedClass(nestedDescriptor.substring(innerClassIndex + 1));
-      }
+      rewritten |=
+          nestedClass.toRenamedBinaryNameOrDefault(
+              nestedDescriptor -> {
+                if (nestedDescriptor != null) {
+                  // 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 =
+                      nestedDescriptor.lastIndexOf(DescriptorUtils.INNER_CLASS_SEPARATOR);
+                  kmClass.visitNestedClass(nestedDescriptor.substring(innerClassIndex + 1));
+                }
+              },
+              appView,
+              namingLens,
+              null);
     }
     // Rewrite sealed sub classes.
     for (KotlinTypeReference sealedSubClass : sealedSubClasses) {
-      String sealedDescriptor =
-          sealedSubClass.toRenamedBinaryNameOrDefault(appView, namingLens, null);
-      if (sealedDescriptor != null) {
-        kmClass.visitSealedSubclass(
-            sealedDescriptor.replace(
-                DescriptorUtils.INNER_CLASS_SEPARATOR, DescriptorUtils.JAVA_PACKAGE_SEPARATOR));
-      }
+      rewritten |=
+          sealedSubClass.toRenamedBinaryNameOrDefault(
+              sealedDescriptor -> {
+                if (sealedDescriptor != null) {
+                  kmClass.visitSealedSubclass(
+                      sealedDescriptor.replace(
+                          DescriptorUtils.INNER_CLASS_SEPARATOR,
+                          DescriptorUtils.JAVA_PACKAGE_SEPARATOR));
+                }
+              },
+              appView,
+              namingLens,
+              null);
     }
     // TODO(b/154347404): Understand enum entries.
     kmClass.getEnumEntries().addAll(enumEntries);
-    versionRequirements.rewrite(kmClass::visitVersionRequirement);
+    rewritten |= versionRequirements.rewrite(kmClass::visitVersionRequirement);
     JvmClassExtensionVisitor extensionVisitor =
         (JvmClassExtensionVisitor) kmClass.visitExtensions(JvmClassExtensionVisitor.TYPE);
     extensionVisitor.visitModuleName(moduleName);
     if (anonymousObjectOrigin != null) {
-      String renamedAnon =
-          anonymousObjectOrigin.toRenamedBinaryNameOrDefault(appView, namingLens, null);
-      if (renamedAnon != null) {
-        extensionVisitor.visitAnonymousObjectOriginName(renamedAnon);
-      }
+      rewritten |=
+          anonymousObjectOrigin.toRenamedBinaryNameOrDefault(
+              renamedAnon -> {
+                if (renamedAnon != null) {
+                  extensionVisitor.visitAnonymousObjectOriginName(renamedAnon);
+                }
+              },
+              appView,
+              namingLens,
+              null);
     }
-    localDelegatedProperties.rewrite(
-        extensionVisitor::visitLocalDelegatedProperty, appView, namingLens);
+    rewritten |=
+        localDelegatedProperties.rewrite(
+            extensionVisitor::visitLocalDelegatedProperty, appView, namingLens);
     extensionVisitor.visitEnd();
     KotlinClassMetadata.Class.Writer writer = new KotlinClassMetadata.Class.Writer();
     kmClass.accept(writer);
-    return writer.write().getHeader();
+    return Pair.create(writer.write().getHeader(), rewritten);
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinClassLevelInfo.java b/src/main/java/com/android/tools/r8/kotlin/KotlinClassLevelInfo.java
index 1d968d2..34c60de 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinClassLevelInfo.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinClassLevelInfo.java
@@ -8,6 +8,7 @@
 import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.naming.NamingLens;
 import com.android.tools.r8.shaking.EnqueuerMetadataTraceable;
+import com.android.tools.r8.utils.Pair;
 import kotlinx.metadata.jvm.KotlinClassHeader;
 
 public interface KotlinClassLevelInfo extends EnqueuerMetadataTraceable {
@@ -56,7 +57,8 @@
     return null;
   }
 
-  KotlinClassHeader rewrite(DexClass clazz, AppView<?> appView, NamingLens namingLens);
+  Pair<KotlinClassHeader, Boolean> rewrite(
+      DexClass clazz, AppView<?> appView, NamingLens namingLens);
 
   String getPackageName();
 
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 6fc09f5..bfabd6c 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinClassMetadataReader.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinClassMetadataReader.java
@@ -3,8 +3,8 @@
 // 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.INVALID_KOTLIN_INFO;
-import static com.android.tools.r8.kotlin.KotlinMetadataUtils.NO_KOTLIN_INFO;
+import static com.android.tools.r8.kotlin.KotlinMetadataUtils.getInvalidKotlinInfo;
+import static com.android.tools.r8.kotlin.KotlinMetadataUtils.getNoKotlinInfo;
 import static com.android.tools.r8.kotlin.KotlinSyntheticClassInfo.getFlavour;
 
 import com.android.tools.r8.graph.AppView;
@@ -19,7 +19,6 @@
 import com.android.tools.r8.graph.DexValue;
 import com.android.tools.r8.graph.DexValue.DexValueArray;
 import com.android.tools.r8.kotlin.KotlinSyntheticClassInfo.Flavour;
-import com.android.tools.r8.utils.Reporter;
 import com.android.tools.r8.utils.StringDiagnostic;
 import java.util.IdentityHashMap;
 import java.util.Map;
@@ -27,49 +26,49 @@
 import kotlinx.metadata.InconsistentKotlinMetadataException;
 import kotlinx.metadata.jvm.KotlinClassHeader;
 import kotlinx.metadata.jvm.KotlinClassMetadata;
+import kotlinx.metadata.jvm.KotlinClassMetadata.FileFacade;
+import kotlinx.metadata.jvm.KotlinClassMetadata.MultiFileClassFacade;
+import kotlinx.metadata.jvm.KotlinClassMetadata.MultiFileClassPart;
 import kotlinx.metadata.jvm.KotlinClassMetadata.SyntheticClass;
 
 public final class KotlinClassMetadataReader {
 
   public static KotlinClassLevelInfo getKotlinInfo(
-      Kotlin kotlin,
-      DexClass clazz,
-      DexItemFactory factory,
-      Reporter reporter,
-      Consumer<DexEncodedMethod> keepByteCode) {
-    DexAnnotation meta = clazz.annotations().getFirstMatching(factory.kotlinMetadataType);
-    if (meta != null) {
-      return getKotlinInfo(kotlin, clazz, factory, reporter, keepByteCode, meta);
-    }
-    return NO_KOTLIN_INFO;
+      DexClass clazz, AppView<?> appView, Consumer<DexEncodedMethod> keepByteCode) {
+    DexAnnotation meta =
+        clazz.annotations().getFirstMatching(appView.dexItemFactory().kotlinMetadataType);
+    return meta != null ? getKotlinInfo(clazz, appView, keepByteCode, meta) : getNoKotlinInfo();
   }
 
   public static KotlinClassLevelInfo getKotlinInfo(
-      Kotlin kotlin,
       DexClass clazz,
-      DexItemFactory factory,
-      Reporter reporter,
+      AppView<?> appView,
       Consumer<DexEncodedMethod> keepByteCode,
       DexAnnotation annotation) {
     try {
+      Kotlin kotlin = appView.dexItemFactory().kotlin;
       KotlinClassMetadata kMetadata = toKotlinClassMetadata(kotlin, annotation.annotation);
-      return createKotlinInfo(kotlin, clazz, kMetadata, factory, reporter, keepByteCode);
+      return createKotlinInfo(kotlin, clazz, kMetadata, appView, keepByteCode);
     } catch (ClassCastException | InconsistentKotlinMetadataException | MetadataError e) {
-      reporter.info(
-          new StringDiagnostic(
-              "Class "
-                  + clazz.type.toSourceString()
-                  + " has malformed kotlin.Metadata: "
-                  + e.getMessage()));
-      return INVALID_KOTLIN_INFO;
+      appView
+          .reporter()
+          .info(
+              new StringDiagnostic(
+                  "Class "
+                      + clazz.type.toSourceString()
+                      + " has malformed kotlin.Metadata: "
+                      + e.getMessage()));
+      return getInvalidKotlinInfo();
     } catch (Throwable e) {
-      reporter.info(
-          new StringDiagnostic(
-              "Unexpected error while reading "
-                  + clazz.type.toSourceString()
-                  + "'s kotlin.Metadata: "
-                  + e.getMessage()));
-      return INVALID_KOTLIN_INFO;
+      appView
+          .reporter()
+          .info(
+              new StringDiagnostic(
+                  "Unexpected error while reading "
+                      + clazz.type.toSourceString()
+                      + "'s kotlin.Metadata: "
+                      + e.getMessage()));
+      return getNoKotlinInfo();
     }
   }
 
@@ -132,46 +131,34 @@
       Kotlin kotlin,
       DexClass clazz,
       KotlinClassMetadata kMetadata,
-      DexItemFactory factory,
-      Reporter reporter,
+      AppView<?> appView,
       Consumer<DexEncodedMethod> keepByteCode) {
     String packageName = kMetadata.getHeader().getPackageName();
     int[] metadataVersion = kMetadata.getHeader().getMetadataVersion();
     if (kMetadata instanceof KotlinClassMetadata.Class) {
       return KotlinClassInfo.create(
-          ((KotlinClassMetadata.Class) kMetadata).toKmClass(),
+          (KotlinClassMetadata.Class) kMetadata,
           packageName,
           metadataVersion,
           clazz,
-          factory,
-          reporter,
+          appView,
           keepByteCode);
     } else if (kMetadata instanceof KotlinClassMetadata.FileFacade) {
       // e.g., B.kt becomes class `BKt`
       return KotlinFileFacadeInfo.create(
-          (KotlinClassMetadata.FileFacade) kMetadata,
-          packageName,
-          metadataVersion,
-          clazz,
-          factory,
-          reporter,
-          keepByteCode);
+          (FileFacade) kMetadata, packageName, metadataVersion, clazz, appView, keepByteCode);
     } else if (kMetadata instanceof KotlinClassMetadata.MultiFileClassFacade) {
       // multi-file class with the same @JvmName.
       return KotlinMultiFileClassFacadeInfo.create(
-          (KotlinClassMetadata.MultiFileClassFacade) kMetadata,
-          packageName,
-          metadataVersion,
-          factory);
+          (MultiFileClassFacade) kMetadata, packageName, metadataVersion, appView.dexItemFactory());
     } else if (kMetadata instanceof KotlinClassMetadata.MultiFileClassPart) {
       // A single file, which is part of multi-file class.
       return KotlinMultiFileClassPartInfo.create(
-          (KotlinClassMetadata.MultiFileClassPart) kMetadata,
+          (MultiFileClassPart) kMetadata,
           packageName,
           metadataVersion,
           clazz,
-          factory,
-          reporter,
+          appView,
           keepByteCode);
     } else if (kMetadata instanceof KotlinClassMetadata.SyntheticClass) {
       return KotlinSyntheticClassInfo.create(
@@ -180,8 +167,7 @@
           metadataVersion,
           clazz,
           kotlin,
-          factory,
-          reporter);
+          appView);
     } else {
       throw new MetadataError("unsupported 'k' value: " + kMetadata.getHeader().getKind());
     }
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 54b8054..5f5b696 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinClassifierInfo.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinClassifierInfo.java
@@ -48,7 +48,7 @@
     }
   }
 
-  abstract void rewrite(KmTypeVisitor visitor, AppView<?> appView, NamingLens namingLens);
+  abstract boolean rewrite(KmTypeVisitor visitor, AppView<?> appView, NamingLens namingLens);
 
   public DexType rewriteType(GraphLens graphLens) {
     return null;
@@ -65,16 +65,20 @@
     }
 
     @Override
-    void rewrite(KmTypeVisitor visitor, AppView<?> appView, NamingLens namingLens) {
-      String descriptor =
-          type.toRenamedDescriptorOrDefault(appView, namingLens, ClassClassifiers.anyDescriptor);
-      // For local or anonymous classes, the classifier is prefixed with '.' and inner classes are
-      // separated with '$'.
-      if (isLocalOrAnonymous) {
-        visitor.visitClass("." + DescriptorUtils.getBinaryNameFromDescriptor(descriptor));
-      } else {
-        visitor.visitClass(DescriptorUtils.descriptorToKotlinClassifier(descriptor));
-      }
+    boolean rewrite(KmTypeVisitor visitor, AppView<?> appView, NamingLens namingLens) {
+      return type.toRenamedDescriptorOrDefault(
+          descriptor -> {
+            // For local or anonymous classes, the classifier is prefixed with '.' and inner classes
+            // are separated with '$'.
+            if (isLocalOrAnonymous) {
+              visitor.visitClass("." + DescriptorUtils.getBinaryNameFromDescriptor(descriptor));
+            } else {
+              visitor.visitClass(DescriptorUtils.descriptorToKotlinClassifier(descriptor));
+            }
+          },
+          appView,
+          namingLens,
+          ClassClassifiers.anyDescriptor);
     }
 
     @Override
@@ -97,8 +101,9 @@
     }
 
     @Override
-    void rewrite(KmTypeVisitor visitor, AppView<?> appView, NamingLens namingLens) {
+    boolean rewrite(KmTypeVisitor visitor, AppView<?> appView, NamingLens namingLens) {
       visitor.visitTypeParameter(typeId);
+      return false;
     }
 
     @Override
@@ -116,8 +121,9 @@
     }
 
     @Override
-    void rewrite(KmTypeVisitor visitor, AppView<?> appView, NamingLens namingLens) {
+    boolean rewrite(KmTypeVisitor visitor, AppView<?> appView, NamingLens namingLens) {
       visitor.visitTypeAlias(typeAlias);
+      return false;
     }
 
     @Override
@@ -134,8 +140,9 @@
     }
 
     @Override
-    void rewrite(KmTypeVisitor visitor, AppView<?> appView, NamingLens namingLens) {
+    boolean rewrite(KmTypeVisitor visitor, AppView<?> appView, NamingLens namingLens) {
       visitor.visitClass(classifier);
+      return false;
     }
 
     @Override
@@ -152,8 +159,9 @@
     }
 
     @Override
-    void rewrite(KmTypeVisitor visitor, AppView<?> appView, NamingLens namingLens) {
+    boolean rewrite(KmTypeVisitor visitor, AppView<?> appView, NamingLens namingLens) {
       visitor.visitTypeAlias(classifier);
+      return false;
     }
 
     @Override
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinCompanionInfo.java b/src/main/java/com/android/tools/r8/kotlin/KotlinCompanionInfo.java
index 1e19f1a..f90ed3a 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinCompanionInfo.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinCompanionInfo.java
@@ -13,6 +13,12 @@
 // Structure around a kotlin companion object that can be assigned to a field.
 public class KotlinCompanionInfo implements KotlinFieldLevelInfo {
 
+  private final String companionObjectFieldName;
+
+  public KotlinCompanionInfo(String companionObjectFieldName) {
+    this.companionObjectFieldName = companionObjectFieldName;
+  }
+
   @Override
   public boolean isCompanion() {
     return true;
@@ -23,10 +29,11 @@
     return this;
   }
 
-  public void rewrite(KmClassVisitor visitor, DexField field, NamingLens lens) {
+  boolean rewrite(KmClassVisitor visitor, DexField field, NamingLens lens) {
     DexString dexString = lens.lookupName(field);
     String finalName = dexString.toString();
     visitor.visitCompanionObject(finalName);
+    return !finalName.equals(companionObjectFieldName);
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinConstructorInfo.java b/src/main/java/com/android/tools/r8/kotlin/KotlinConstructorInfo.java
index 2e4ee98..dd82d58 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinConstructorInfo.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinConstructorInfo.java
@@ -41,28 +41,42 @@
   }
 
   public static KotlinConstructorInfo create(
-      KmConstructor kmConstructor, DexItemFactory factory, Reporter reporter) {
+      KmConstructor kmConstructor,
+      DexItemFactory factory,
+      Reporter reporter,
+      boolean readConstructorSignature) {
     return new KotlinConstructorInfo(
         kmConstructor.getFlags(),
         KotlinValueParameterInfo.create(kmConstructor.getValueParameters(), factory, reporter),
         KotlinVersionRequirementInfo.create(kmConstructor.getVersionRequirements()),
-        KotlinJvmMethodSignatureInfo.create(JvmExtensionsKt.getSignature(kmConstructor), factory));
+        readConstructorSignature
+            ? KotlinJvmMethodSignatureInfo.create(
+                JvmExtensionsKt.getSignature(kmConstructor), factory)
+            : null);
   }
 
-  public void rewrite(
+  boolean rewrite(
       KmClass kmClass, DexEncodedMethod method, AppView<?> 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);
+    boolean rewritten = false;
     if (signature != null) {
-      JvmExtensionsKt.setSignature(kmConstructor, signature.rewrite(method, appView, namingLens));
+      rewritten =
+          signature.rewrite(
+              rewrittenSignature -> JvmExtensionsKt.setSignature(kmConstructor, rewrittenSignature),
+              method,
+              appView,
+              namingLens);
     }
     for (KotlinValueParameterInfo valueParameterInfo : valueParameters) {
-      valueParameterInfo.rewrite(kmConstructor::visitValueParameter, appView, namingLens);
+      rewritten |=
+          valueParameterInfo.rewrite(kmConstructor::visitValueParameter, appView, namingLens);
     }
-    versionRequirements.rewrite(kmConstructor::visitVersionRequirement);
+    rewritten |= versionRequirements.rewrite(kmConstructor::visitVersionRequirement);
     kmClass.getConstructors().add(kmConstructor);
+    return rewritten;
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinContractInfo.java b/src/main/java/com/android/tools/r8/kotlin/KotlinContractInfo.java
index fd0d60d..2afb66e 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinContractInfo.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinContractInfo.java
@@ -49,17 +49,19 @@
     forEachApply(effects, effect -> effect::trace, definitionSupplier);
   }
 
-  public void rewrite(
+  boolean rewrite(
       KmVisitorProviders.KmContractVisitorProvider visitorProvider,
       AppView<?> appView,
       NamingLens namingLens) {
     if (this == NO_EFFECT) {
-      return;
+      return false;
     }
+    boolean rewritten = false;
     KmContractVisitor kmContractVisitor = visitorProvider.get();
     for (KotlinEffectInfo effect : effects) {
-      effect.rewrite(kmContractVisitor::visitEffect, appView, namingLens);
+      rewritten |= effect.rewrite(kmContractVisitor::visitEffect, appView, namingLens);
     }
     kmContractVisitor.visitEnd();
+    return rewritten;
   }
 }
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinDeclarationContainerInfo.java b/src/main/java/com/android/tools/r8/kotlin/KotlinDeclarationContainerInfo.java
index 7447bbd..eb654b5 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinDeclarationContainerInfo.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinDeclarationContainerInfo.java
@@ -55,8 +55,10 @@
       Map<String, DexEncodedField> fieldSignatureMap,
       DexItemFactory factory,
       Reporter reporter,
-      Consumer<DexEncodedMethod> keepByteCode) {
+      Consumer<DexEncodedMethod> keepByteCode,
+      KotlinJvmSignatureExtensionInformation extensionInformation) {
     ImmutableList.Builder<KotlinFunctionInfo> notBackedFunctions = ImmutableList.builder();
+    int functionCounter = 0;
     for (KmFunction kmFunction : container.getFunctions()) {
       JvmMethodSignature signature = JvmExtensionsKt.getSignature(kmFunction);
       if (signature == null) {
@@ -64,7 +66,11 @@
         continue;
       }
       KotlinFunctionInfo kotlinFunctionInfo =
-          KotlinFunctionInfo.create(kmFunction, factory, reporter);
+          KotlinFunctionInfo.create(
+              kmFunction,
+              factory,
+              reporter,
+              extensionInformation.hasJvmMethodSignatureExtensionForFunction(functionCounter++));
       DexEncodedMethod method = methodSignatureMap.get(signature.asString());
       if (method == null) {
         notBackedFunctions.add(kotlinFunctionInfo);
@@ -149,7 +155,7 @@
     return builder.build();
   }
 
-  public void rewrite(
+  boolean rewrite(
       KmVisitorProviders.KmFunctionVisitorProvider functionProvider,
       KmVisitorProviders.KmPropertyVisitorProvider propertyProvider,
       KmVisitorProviders.KmTypeAliasVisitorProvider typeAliasProvider,
@@ -157,28 +163,26 @@
       AppView<?> appView,
       NamingLens namingLens) {
     // Type aliases only have a representation here, so we can generate them directly.
+    boolean rewritten = false;
     for (KotlinTypeAliasInfo typeAlias : typeAliases) {
-      typeAlias.rewrite(typeAliasProvider, appView, namingLens);
+      rewritten |= 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()) {
+      if (field.getKotlinInfo().isProperty()) {
         properties
             .computeIfAbsent(
-                field.getKotlinMemberInfo().asFieldProperty(), ignored -> new KotlinPropertyGroup())
+                field.getKotlinInfo().asProperty(), ignored -> new KotlinPropertyGroup())
             .setBackingField(field);
       }
     }
     for (DexEncodedMethod method : clazz.methods()) {
-      if (method.getKotlinMemberInfo().isFunction()) {
-        method
-            .getKotlinMemberInfo()
-            .asFunction()
-            .rewrite(functionProvider, method, appView, namingLens);
+      if (method.getKotlinInfo().isFunction()) {
+        method.getKotlinInfo().asFunction().rewrite(functionProvider, method, appView, namingLens);
         continue;
       }
-      KotlinPropertyInfo kotlinPropertyInfo = method.getKotlinMemberInfo().asProperty();
+      KotlinPropertyInfo kotlinPropertyInfo = method.getKotlinInfo().asProperty();
       if (kotlinPropertyInfo == null) {
         continue;
       }
@@ -193,21 +197,24 @@
     }
     for (KotlinPropertyInfo kotlinPropertyInfo : properties.keySet()) {
       KotlinPropertyGroup kotlinPropertyGroup = properties.get(kotlinPropertyInfo);
-      kotlinPropertyInfo.rewrite(
-          propertyProvider,
-          kotlinPropertyGroup.backingField,
-          kotlinPropertyGroup.getter,
-          kotlinPropertyGroup.setter,
-          appView,
-          namingLens);
+      rewritten |=
+          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);
+      rewritten |= notBackedFunction.rewrite(functionProvider, null, appView, namingLens);
     }
     for (KotlinPropertyInfo notBackedProperty : propertiesWithNoBacking) {
-      notBackedProperty.rewrite(propertyProvider, null, null, null, appView, namingLens);
+      rewritten |=
+          notBackedProperty.rewrite(propertyProvider, null, null, null, appView, namingLens);
     }
+    return rewritten;
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinEffectExpressionInfo.java b/src/main/java/com/android/tools/r8/kotlin/KotlinEffectExpressionInfo.java
index d6664d1..bc7cda0 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinEffectExpressionInfo.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinEffectExpressionInfo.java
@@ -85,25 +85,27 @@
     forEachApply(orArguments, arg -> arg::trace, definitionSupplier);
   }
 
-  public void rewrite(
+  boolean rewrite(
       KmEffectExpressionVisitorProvider provider, AppView<?> appView, NamingLens namingLens) {
     if (this == NO_EXPRESSION) {
-      return;
+      return false;
     }
     KmEffectExpressionVisitor visitor = provider.get();
     visitor.visit(flags, parameterIndex);
     if (constantValue != null) {
       visitor.visitConstantValue(constantValue.getValue());
     }
+    boolean rewritten = false;
     if (isInstanceType != null) {
-      isInstanceType.rewrite(visitor::visitIsInstanceType, appView, namingLens);
+      rewritten |= isInstanceType.rewrite(visitor::visitIsInstanceType, appView, namingLens);
     }
     for (KotlinEffectExpressionInfo andArgument : andArguments) {
-      andArgument.rewrite(visitor::visitAndArgument, appView, namingLens);
+      rewritten |= andArgument.rewrite(visitor::visitAndArgument, appView, namingLens);
     }
     for (KotlinEffectExpressionInfo orArgument : orArguments) {
-      orArgument.rewrite(visitor::visitAndArgument, appView, namingLens);
+      rewritten |= orArgument.rewrite(visitor::visitAndArgument, appView, namingLens);
     }
     visitor.visitEnd();
+    return rewritten;
   }
 }
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinEffectInfo.java b/src/main/java/com/android/tools/r8/kotlin/KotlinEffectInfo.java
index 3b1c748..e581526 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinEffectInfo.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinEffectInfo.java
@@ -51,12 +51,18 @@
     conclusion.trace(definitionSupplier);
   }
 
-  void rewrite(KmEffectVisitorProvider visitorProvider, AppView<?> appView, NamingLens namingLens) {
+  boolean rewrite(
+      KmEffectVisitorProvider visitorProvider, AppView<?> appView, NamingLens namingLens) {
     KmEffectVisitor kmEffectVisitor = visitorProvider.get(type, invocationKind);
-    conclusion.rewrite(kmEffectVisitor::visitConclusionOfConditionalEffect, appView, namingLens);
+    boolean rewritten =
+        conclusion.rewrite(
+            kmEffectVisitor::visitConclusionOfConditionalEffect, appView, namingLens);
     for (KotlinEffectExpressionInfo constructorArgument : constructorArguments) {
-      constructorArgument.rewrite(kmEffectVisitor::visitConstructorArgument, appView, namingLens);
+      rewritten |=
+          constructorArgument.rewrite(
+              kmEffectVisitor::visitConstructorArgument, appView, namingLens);
     }
     kmEffectVisitor.visitEnd();
+    return rewritten;
   }
 }
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinFieldLevelInfo.java b/src/main/java/com/android/tools/r8/kotlin/KotlinFieldLevelInfo.java
index acf2d64..26a35ba 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinFieldLevelInfo.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinFieldLevelInfo.java
@@ -6,19 +6,4 @@
 
 public interface KotlinFieldLevelInfo extends KotlinMemberLevelInfo {
 
-  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/KotlinFileFacadeInfo.java b/src/main/java/com/android/tools/r8/kotlin/KotlinFileFacadeInfo.java
index b012758..7981195 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinFileFacadeInfo.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinFileFacadeInfo.java
@@ -8,9 +8,8 @@
 import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexDefinitionSupplier;
 import com.android.tools.r8.graph.DexEncodedMethod;
-import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.naming.NamingLens;
-import com.android.tools.r8.utils.Reporter;
+import com.android.tools.r8.utils.Pair;
 import java.util.function.Consumer;
 import kotlinx.metadata.KmPackage;
 import kotlinx.metadata.jvm.KotlinClassHeader;
@@ -36,12 +35,14 @@
       String packageName,
       int[] metadataVersion,
       DexClass clazz,
-      DexItemFactory factory,
-      Reporter reporter,
+      AppView<?> appView,
       Consumer<DexEncodedMethod> keepByteCode) {
+    KmPackage kmPackage = kmFileFacade.toKmPackage();
+    KotlinJvmSignatureExtensionInformation extensionInformation =
+        KotlinJvmSignatureExtensionInformation.readInformationFromMessage(
+            kmFileFacade, appView.options());
     return new KotlinFileFacadeInfo(
-        KotlinPackageInfo.create(
-            kmFileFacade.toKmPackage(), clazz, factory, reporter, keepByteCode),
+        KotlinPackageInfo.create(kmPackage, clazz, appView, keepByteCode, extensionInformation),
         packageName,
         metadataVersion);
   }
@@ -57,12 +58,13 @@
   }
 
   @Override
-  public KotlinClassHeader rewrite(DexClass clazz, AppView<?> appView, NamingLens namingLens) {
-    KotlinClassMetadata.FileFacade.Writer writer = new KotlinClassMetadata.FileFacade.Writer();
+  public Pair<KotlinClassHeader, Boolean> rewrite(
+      DexClass clazz, AppView<?> appView, NamingLens namingLens) {
     KmPackage kmPackage = new KmPackage();
-    packageInfo.rewrite(kmPackage, clazz, appView, namingLens);
+    boolean rewritten = packageInfo.rewrite(kmPackage, clazz, appView, namingLens);
+    KotlinClassMetadata.FileFacade.Writer writer = new KotlinClassMetadata.FileFacade.Writer();
     kmPackage.accept(writer);
-    return writer.write().getHeader();
+    return Pair.create(writer.write().getHeader(), rewritten);
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinFlexibleTypeUpperBoundInfo.java b/src/main/java/com/android/tools/r8/kotlin/KotlinFlexibleTypeUpperBoundInfo.java
index 91413c7..f2a6eb4 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinFlexibleTypeUpperBoundInfo.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinFlexibleTypeUpperBoundInfo.java
@@ -62,15 +62,16 @@
         flexibleTypeUpperBound.getTypeFlexibilityId());
   }
 
-  public void rewrite(
+  boolean rewrite(
       KmVisitorProviders.KmFlexibleUpperBoundVisitorProvider visitorProvider,
       AppView<?> appView,
       NamingLens namingLens) {
     if (this == NO_FLEXIBLE_UPPER_BOUND) {
       // Nothing to do.
-      return;
+      return false;
     }
-    super.rewrite(flags -> visitorProvider.get(flags, typeFlexibilityId), appView, namingLens);
+    return super.rewrite(
+        flags -> visitorProvider.get(flags, typeFlexibilityId), appView, namingLens);
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinFunctionInfo.java b/src/main/java/com/android/tools/r8/kotlin/KotlinFunctionInfo.java
index 008177e..e6a249c 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinFunctionInfo.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinFunctionInfo.java
@@ -73,7 +73,10 @@
   }
 
   static KotlinFunctionInfo create(
-      KmFunction kmFunction, DexItemFactory factory, Reporter reporter) {
+      KmFunction kmFunction,
+      DexItemFactory factory,
+      Reporter reporter,
+      boolean readMethodSignature) {
     boolean isCrossInline = false;
     List<KotlinValueParameterInfo> valueParameters =
         KotlinValueParameterInfo.create(kmFunction.getValueParameters(), factory, reporter);
@@ -90,7 +93,9 @@
         KotlinTypeInfo.create(kmFunction.getReceiverParameterType(), factory, reporter),
         valueParameters,
         KotlinTypeParameterInfo.create(kmFunction.getTypeParameters(), factory, reporter),
-        KotlinJvmMethodSignatureInfo.create(JvmExtensionsKt.getSignature(kmFunction), factory),
+        readMethodSignature
+            ? KotlinJvmMethodSignatureInfo.create(JvmExtensionsKt.getSignature(kmFunction), factory)
+            : null,
         getlambdaClassOrigin(kmFunction, factory),
         KotlinVersionRequirementInfo.create(kmFunction.getVersionRequirements()),
         KotlinContractInfo.create(kmFunction.getContract(), factory, reporter),
@@ -106,46 +111,60 @@
     return null;
   }
 
-  public void rewrite(
+  public String getName() {
+    return name;
+  }
+
+  boolean rewrite(
       KmVisitorProviders.KmFunctionVisitorProvider visitorProvider,
       DexEncodedMethod method,
       AppView<?> appView,
       NamingLens namingLens) {
     // TODO(b/154348683): Check method for flags to pass in.
+    boolean rewritten = false;
     String finalName = this.name;
     if (method != null) {
       String methodName = method.getReference().name.toString();
       String rewrittenName = namingLens.lookupName(method.getReference()).toString();
       if (!methodName.equals(rewrittenName)) {
+        rewritten = true;
         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);
+    rewritten |= returnType.rewrite(kmFunction::visitReturnType, appView, namingLens);
     for (KotlinValueParameterInfo valueParameterInfo : valueParameters) {
-      valueParameterInfo.rewrite(kmFunction::visitValueParameter, appView, namingLens);
+      rewritten |= valueParameterInfo.rewrite(kmFunction::visitValueParameter, appView, namingLens);
     }
     for (KotlinTypeParameterInfo typeParameterInfo : typeParameters) {
-      typeParameterInfo.rewrite(kmFunction::visitTypeParameter, appView, namingLens);
+      rewritten |= typeParameterInfo.rewrite(kmFunction::visitTypeParameter, appView, namingLens);
     }
     if (receiverParameterType != null) {
-      receiverParameterType.rewrite(kmFunction::visitReceiverParameterType, appView, namingLens);
+      rewritten |=
+          receiverParameterType.rewrite(
+              kmFunction::visitReceiverParameterType, appView, namingLens);
     }
-    versionRequirements.rewrite(kmFunction::visitVersionRequirement);
+    rewritten |= versionRequirements.rewrite(kmFunction::visitVersionRequirement);
     JvmFunctionExtensionVisitor extensionVisitor =
         (JvmFunctionExtensionVisitor) kmFunction.visitExtensions(JvmFunctionExtensionVisitor.TYPE);
     if (signature != null && extensionVisitor != null) {
-      extensionVisitor.visit(signature.rewrite(method, appView, namingLens));
+      rewritten |= signature.rewrite(extensionVisitor::visit, method, appView, namingLens);
     }
     if (lambdaClassOrigin != null && extensionVisitor != null) {
-      String lambdaClassOriginName =
-          lambdaClassOrigin.toRenamedBinaryNameOrDefault(appView, namingLens, null);
-      if (lambdaClassOriginName != null) {
-        extensionVisitor.visitLambdaClassOriginName(lambdaClassOriginName);
-      }
+      rewritten |=
+          lambdaClassOrigin.toRenamedBinaryNameOrDefault(
+              lambdaClassOriginName -> {
+                if (lambdaClassOriginName != null) {
+                  extensionVisitor.visitLambdaClassOriginName(lambdaClassOriginName);
+                }
+              },
+              appView,
+              namingLens,
+              null);
     }
-    contract.rewrite(kmFunction::visitContract, appView, namingLens);
+    rewritten |= contract.rewrite(kmFunction::visitContract, appView, namingLens);
+    return rewritten;
   }
 
   @Override
@@ -162,10 +181,6 @@
     return receiverParameterType != null;
   }
 
-  public KotlinJvmMethodSignatureInfo getSignature() {
-    return signature;
-  }
-
   @Override
   public void trace(DexDefinitionSupplier definitionSupplier) {
     forEachApply(valueParameters, param -> param::trace, definitionSupplier);
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinJvmFieldSignatureInfo.java b/src/main/java/com/android/tools/r8/kotlin/KotlinJvmFieldSignatureInfo.java
index 2606807..340275a 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinJvmFieldSignatureInfo.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinJvmFieldSignatureInfo.java
@@ -10,6 +10,8 @@
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.naming.NamingLens;
 import com.android.tools.r8.shaking.EnqueuerMetadataTraceable;
+import com.android.tools.r8.utils.Box;
+import java.util.function.Consumer;
 import kotlinx.metadata.jvm.JvmFieldSignature;
 
 /**
@@ -36,19 +38,27 @@
         KotlinTypeReference.fromDescriptor(fieldSignature.getDesc(), factory));
   }
 
-  public JvmFieldSignature rewrite(
-      DexEncodedField field, AppView<?> appView, NamingLens namingLens) {
+  boolean rewrite(
+      Consumer<JvmFieldSignature> consumer,
+      DexEncodedField field,
+      AppView<?> appView,
+      NamingLens namingLens) {
+    boolean rewritten = false;
     String finalName = name;
     if (field != null) {
       String fieldName = field.getReference().name.toString();
       String rewrittenName = namingLens.lookupName(field.getReference()).toString();
       if (!fieldName.equals(rewrittenName)) {
+        rewritten = true;
         finalName = rewrittenName;
       }
     }
     String defValue = appView.dexItemFactory().objectType.toDescriptorString();
-    return new JvmFieldSignature(
-        finalName, type.toRenamedDescriptorOrDefault(appView, namingLens, defValue));
+    Box<String> renamedDescriptor = new Box<>();
+    rewritten |=
+        type.toRenamedDescriptorOrDefault(renamedDescriptor::set, appView, namingLens, defValue);
+    consumer.accept(new JvmFieldSignature(finalName, renamedDescriptor.get()));
+    return rewritten;
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinJvmMethodSignatureInfo.java b/src/main/java/com/android/tools/r8/kotlin/KotlinJvmMethodSignatureInfo.java
index b0314be..5cfa5ac 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinJvmMethodSignatureInfo.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinJvmMethodSignatureInfo.java
@@ -15,6 +15,7 @@
 import com.android.tools.r8.utils.DescriptorUtils;
 import com.google.common.collect.ImmutableList;
 import java.util.List;
+import java.util.function.Consumer;
 import kotlinx.metadata.jvm.JvmMethodSignature;
 
 /**
@@ -50,50 +51,59 @@
     if (methodSignature == null) {
       return null;
     }
-    String kotlinDescriptor = methodSignature.getDesc();
-    if (!KotlinMetadataUtils.isValidMethodDescriptor(kotlinDescriptor)) {
+    String name = methodSignature.getName();
+    String descriptor = methodSignature.getDesc();
+    if (!KotlinMetadataUtils.isValidMethodDescriptor(descriptor)) {
       // If the method descriptor is invalid, keep it as invalid.
-      return new KotlinJvmMethodSignatureInfo(methodSignature.getName(), kotlinDescriptor);
+      return new KotlinJvmMethodSignatureInfo(methodSignature.getName(), descriptor);
     }
-    String returnTypeDescriptor = DescriptorUtils.getReturnTypeDescriptor(kotlinDescriptor);
+    String returnTypeDescriptor = DescriptorUtils.getReturnTypeDescriptor(descriptor);
     KotlinTypeReference returnType =
         KotlinTypeReference.fromDescriptor(returnTypeDescriptor, factory);
-    String[] descriptors = DescriptorUtils.getArgumentTypeDescriptors(kotlinDescriptor);
+    String[] descriptors = DescriptorUtils.getArgumentTypeDescriptors(descriptor);
     if (descriptors.length == 0) {
-      return new KotlinJvmMethodSignatureInfo(
-          methodSignature.getName(), returnType, EMPTY_PARAMETERS_LIST);
+      return new KotlinJvmMethodSignatureInfo(name, returnType, EMPTY_PARAMETERS_LIST);
     }
     ImmutableList.Builder<KotlinTypeReference> parameters = ImmutableList.builder();
-    for (String descriptor : descriptors) {
-      parameters.add(KotlinTypeReference.fromDescriptor(descriptor, factory));
+    for (String paramDescriptor : descriptors) {
+      parameters.add(KotlinTypeReference.fromDescriptor(paramDescriptor, factory));
     }
-    return new KotlinJvmMethodSignatureInfo(
-        methodSignature.getName(), returnType, parameters.build());
+    return new KotlinJvmMethodSignatureInfo(name, returnType, parameters.build());
   }
 
-  public JvmMethodSignature rewrite(
-      DexEncodedMethod method, AppView<?> appView, NamingLens namingLens) {
+  boolean rewrite(
+      Consumer<JvmMethodSignature> consumer,
+      DexEncodedMethod method,
+      AppView<?> appView,
+      NamingLens namingLens) {
     if (invalidDescriptor != null) {
-      return new JvmMethodSignature(name, invalidDescriptor);
+      consumer.accept(new JvmMethodSignature(name, invalidDescriptor));
+      return false;
     }
     assert returnType != null;
     String finalName = name;
+    boolean rewritten = false;
     if (method != null) {
       String methodName = method.getReference().name.toString();
       String rewrittenName = namingLens.lookupName(method.getReference()).toString();
       if (!methodName.equals(rewrittenName)) {
         finalName = rewrittenName;
+        rewritten = true;
       }
     }
     StringBuilder descBuilder = new StringBuilder();
     descBuilder.append("(");
     String defValue = appView.dexItemFactory().objectType.toDescriptorString();
     for (KotlinTypeReference parameter : parameters) {
-      descBuilder.append(parameter.toRenamedDescriptorOrDefault(appView, namingLens, defValue));
+      rewritten |=
+          parameter.toRenamedDescriptorOrDefault(
+              descBuilder::append, appView, namingLens, defValue);
     }
     descBuilder.append(")");
-    descBuilder.append(returnType.toRenamedDescriptorOrDefault(appView, namingLens, defValue));
-    return new JvmMethodSignature(finalName, descBuilder.toString());
+    rewritten |=
+        returnType.toRenamedDescriptorOrDefault(descBuilder::append, appView, namingLens, defValue);
+    consumer.accept(new JvmMethodSignature(finalName, descBuilder.toString()));
+    return rewritten;
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinJvmSignatureExtensionInformation.java b/src/main/java/com/android/tools/r8/kotlin/KotlinJvmSignatureExtensionInformation.java
new file mode 100644
index 0000000..98ebaed
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinJvmSignatureExtensionInformation.java
@@ -0,0 +1,175 @@
+// Copyright (c) 2021, 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.utils.InternalOptions;
+import com.android.tools.r8.utils.ListUtils;
+import com.android.tools.r8.utils.ReflectionHelper;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import kotlin.Pair;
+import kotlinx.metadata.internal.metadata.ProtoBuf;
+import kotlinx.metadata.internal.metadata.jvm.JvmProtoBuf;
+import kotlinx.metadata.jvm.KotlinClassMetadata;
+import kotlinx.metadata.jvm.KotlinClassMetadata.FileFacade;
+import kotlinx.metadata.jvm.KotlinClassMetadata.MultiFileClassPart;
+import kotlinx.metadata.jvm.KotlinClassMetadata.SyntheticClass;
+
+// Due to kotlin-metadata-jvm library synthesizing jvm method signatures from type-information
+// we do an extra check to figure out if we should model the signature. If we model it, we will
+// add name and descriptor information to the string pool for the proto message, so it is
+// better to avoid it.
+// https://github.com/Kotlin/kotlinx.reflect.lite/blob/46d47f118f9846166b6b8f8212bdbc822fe2f634/src/main/java/org/jetbrains/kotlin/serialization/jvm/JvmProtoBufUtil.kt
+
+public class KotlinJvmSignatureExtensionInformation {
+
+  private final Set<Integer> noExtensionIndicesForFunctions;
+  private final Set<Integer> noExtensionIndicesForConstructors;
+
+  private static final KotlinJvmSignatureExtensionInformation EMPTY = builder().build();
+
+  private KotlinJvmSignatureExtensionInformation(
+      Set<Integer> noExtensionIndicesForFunctions, Set<Integer> noExtensionIndicesForConstructors) {
+    this.noExtensionIndicesForFunctions = noExtensionIndicesForFunctions;
+    this.noExtensionIndicesForConstructors = noExtensionIndicesForConstructors;
+  }
+
+  public static KotlinJvmSignatureExtensionInformation readInformationFromMessage(
+      FileFacade fileFacadeMetadata, InternalOptions options) {
+    return readPackageDataFromMessage(fileFacadeMetadata, options);
+  }
+
+  public static KotlinJvmSignatureExtensionInformation readInformationFromMessage(
+      MultiFileClassPart classPart, InternalOptions options) {
+    return readPackageDataFromMessage(classPart, options);
+  }
+
+  private static KotlinJvmSignatureExtensionInformation readPackageDataFromMessage(
+      Object object, InternalOptions options) {
+    try {
+      Pair<?, ProtoBuf.Package> kotlinPairData =
+          ReflectionHelper.performReflection(
+              object,
+              ReflectionHelper.builder()
+                  .readField("packageData$delegate")
+                  .setSetAccessible(true)
+                  .done()
+                  .readMethod("getValue")
+                  .setSetAccessible(true)
+                  .done()
+                  .build());
+      return builder().visit(kotlinPairData.getSecond()).build();
+    } catch (Exception e) {
+      options.warningReadingKotlinMetadataReflective();
+      return empty();
+    }
+  }
+
+  public static KotlinJvmSignatureExtensionInformation readInformationFromMessage(
+      SyntheticClass syntheticClass, InternalOptions options) {
+    try {
+      Pair<?, ProtoBuf.Function> kotlinPairData =
+          ReflectionHelper.performReflection(
+              syntheticClass,
+              ReflectionHelper.builder()
+                  .readField("functionData$delegate")
+                  .setSetAccessible(true)
+                  .done()
+                  .readMethod("getValue")
+                  .setSetAccessible(true)
+                  .done()
+                  .build());
+      if (kotlinPairData == null) {
+        return empty();
+      }
+      return builder().visit(kotlinPairData.getSecond(), 0).build();
+    } catch (Exception e) {
+      options.warningReadingKotlinMetadataReflective();
+      return empty();
+    }
+  }
+
+  public static KotlinJvmSignatureExtensionInformation readInformationFromMessage(
+      KotlinClassMetadata.Class kMetadata, InternalOptions options) {
+    try {
+      Pair<?, ProtoBuf.Class> kotlinPairData =
+          ReflectionHelper.performReflection(
+              kMetadata,
+              ReflectionHelper.builder()
+                  .readField("classData$delegate")
+                  .setSetAccessible(true)
+                  .done()
+                  .readMethod("getValue")
+                  .setSetAccessible(true)
+                  .done()
+                  .build());
+      return builder().visit(kotlinPairData.getSecond()).build();
+    } catch (Exception e) {
+      options.warningReadingKotlinMetadataReflective();
+      return empty();
+    }
+  }
+
+  public boolean hasJvmMethodSignatureExtensionForFunction(int index) {
+    return !noExtensionIndicesForFunctions.contains(index);
+  }
+
+  public boolean hasJvmMethodSignatureExtensionForConstructor(int index) {
+    return !noExtensionIndicesForConstructors.contains(index);
+  }
+
+  public static KotlinJvmSignatureExtensionInformationBuilder builder() {
+    return new KotlinJvmSignatureExtensionInformationBuilder();
+  }
+
+  public static KotlinJvmSignatureExtensionInformation empty() {
+    return EMPTY;
+  }
+
+  private static class KotlinJvmSignatureExtensionInformationBuilder {
+
+    private final Set<Integer> noExtensionIndicesForFunctions = new HashSet<>();
+    private final Set<Integer> noExtensionIndicesForConstructors = new HashSet<>();
+
+    private KotlinJvmSignatureExtensionInformation build() {
+      return new KotlinJvmSignatureExtensionInformation(
+          noExtensionIndicesForFunctions, noExtensionIndicesForConstructors);
+    }
+
+    private KotlinJvmSignatureExtensionInformationBuilder visit(ProtoBuf.Class clazz) {
+      visitFunctions(clazz.getFunctionList());
+      visitConstructors(clazz.getConstructorList());
+      return this;
+    }
+
+    private KotlinJvmSignatureExtensionInformationBuilder visit(ProtoBuf.Package pkg) {
+      visitFunctions(pkg.getFunctionList());
+      return this;
+    }
+
+    private void visitFunctions(List<ProtoBuf.Function> functions) {
+      ListUtils.forEachWithIndex(functions, this::visit);
+    }
+
+    public KotlinJvmSignatureExtensionInformationBuilder visit(
+        ProtoBuf.Function function, int index) {
+      if (!function.hasExtension(JvmProtoBuf.methodSignature)) {
+        noExtensionIndicesForFunctions.add(index);
+      }
+      return this;
+    }
+
+    private void visitConstructors(List<ProtoBuf.Constructor> constructors) {
+      ListUtils.forEachWithIndex(
+          constructors,
+          (constructor, index) -> {
+            if (!constructor.hasExtension(JvmProtoBuf.constructorSignature)) {
+              noExtensionIndicesForConstructors.add(index);
+            }
+          });
+    }
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinLambdaInfo.java b/src/main/java/com/android/tools/r8/kotlin/KotlinLambdaInfo.java
index 01a7c2a..7a4fc78 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinLambdaInfo.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinLambdaInfo.java
@@ -30,13 +30,21 @@
   }
 
   static KotlinLambdaInfo create(
-      DexClass clazz, KmLambda lambda, DexItemFactory factory, Reporter reporter) {
+      DexClass clazz,
+      KmLambda lambda,
+      DexItemFactory factory,
+      Reporter reporter,
+      KotlinJvmSignatureExtensionInformation extensionInformation) {
     if (lambda == null) {
       assert false;
       return null;
     }
     KotlinFunctionInfo kotlinFunctionInfo =
-        KotlinFunctionInfo.create(lambda.function, factory, reporter);
+        KotlinFunctionInfo.create(
+            lambda.function,
+            factory,
+            reporter,
+            extensionInformation.hasJvmMethodSignatureExtensionForFunction(0));
     JvmMethodSignature signature = JvmExtensionsKt.getSignature(lambda.function);
     if (signature != null) {
       for (DexEncodedMethod method : clazz.methods()) {
@@ -60,7 +68,7 @@
     }
     DexEncodedMethod backing = null;
     for (DexEncodedMethod method : clazz.methods()) {
-      if (method.getKotlinMemberInfo() == function) {
+      if (method.getKotlinInfo() == function) {
         backing = method;
         break;
       }
@@ -69,12 +77,10 @@
       appView
           .options()
           .reporter
-          .info(
-              KotlinMetadataDiagnostic.lambdaBackingNotFound(clazz.type, function.getSignature()));
+          .info(KotlinMetadataDiagnostic.lambdaBackingNotFound(clazz.type, function.getName()));
       return false;
     }
-    function.rewrite(visitorProvider.get()::visitFunction, backing, appView, namingLens);
-    return true;
+    return function.rewrite(visitorProvider.get()::visitFunction, backing, appView, namingLens);
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinLocalDelegatedPropertyInfo.java b/src/main/java/com/android/tools/r8/kotlin/KotlinLocalDelegatedPropertyInfo.java
index fca17cf..9548bde 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinLocalDelegatedPropertyInfo.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinLocalDelegatedPropertyInfo.java
@@ -52,10 +52,12 @@
     forEachApply(propertyInfos, prop -> prop::trace, definitionSupplier);
   }
 
-  public void rewrite(
+  boolean rewrite(
       KmPropertyVisitorProvider visitorProvider, AppView<?> appView, NamingLens namingLens) {
+    boolean rewritten = false;
     for (KotlinPropertyInfo propertyInfo : propertyInfos) {
-      propertyInfo.rewrite(visitorProvider, null, null, null, appView, namingLens);
+      rewritten |= propertyInfo.rewrite(visitorProvider, null, null, null, appView, namingLens);
     }
+    return rewritten;
   }
 }
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinMemberLevelInfo.java b/src/main/java/com/android/tools/r8/kotlin/KotlinMemberLevelInfo.java
index f7312ad..41f23c1 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinMemberLevelInfo.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinMemberLevelInfo.java
@@ -11,4 +11,36 @@
   default boolean isNoKotlinInformation() {
     return false;
   }
+
+  default boolean isCompanion() {
+    return false;
+  }
+
+  default KotlinCompanionInfo asCompanion() {
+    return null;
+  }
+
+  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/KotlinMetadataDiagnostic.java b/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataDiagnostic.java
index 3415e13..6c4c963 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataDiagnostic.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataDiagnostic.java
@@ -74,13 +74,12 @@
             + StringUtils.stacktraceAsString(t));
   }
 
-  static KotlinMetadataDiagnostic lambdaBackingNotFound(
-      DexType type, KotlinJvmMethodSignatureInfo signatureInfo) {
+  static KotlinMetadataDiagnostic lambdaBackingNotFound(DexType type, String functionName) {
     return new KotlinMetadataDiagnostic(
         Origin.unknown(),
         Position.UNKNOWN,
         "The lambda function "
-            + signatureInfo.toString()
+            + functionName
             + " could no longer be found in "
             + type.toSourceString()
             + " . The method is most likely pruned and would require a specific keep rule to keep"
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataEnqueuerExtension.java b/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataEnqueuerExtension.java
index 4eef6cb..6422c69 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataEnqueuerExtension.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataEnqueuerExtension.java
@@ -5,7 +5,7 @@
 package com.android.tools.r8.kotlin;
 
 import static com.android.tools.r8.kotlin.KotlinClassMetadataReader.hasKotlinClassMetadataAnnotation;
-import static com.android.tools.r8.kotlin.KotlinMetadataUtils.NO_KOTLIN_INFO;
+import static com.android.tools.r8.kotlin.KotlinMetadataUtils.getNoKotlinInfo;
 
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexClass;
@@ -67,24 +67,19 @@
                   && clazz.hasClassInitializer()) {
                 feedback.classInitializerMayBePostponed(clazz.getClassInitializer());
               }
-              clazz.setKotlinInfo(NO_KOTLIN_INFO);
+              clazz.setKotlinInfo(getNoKotlinInfo());
               clazz.removeAnnotations(
                   annotation -> annotation.getAnnotationType() == kotlinMetadataType);
             } else {
               clazz.setKotlinInfo(
                   KotlinClassMetadataReader.getKotlinInfo(
-                      appView.dexItemFactory().kotlin,
-                      clazz,
-                      appView.dexItemFactory(),
-                      appView.options().reporter,
-                      method -> keepByteCodeFunctions.add(method.getReference())));
+                      clazz, appView, method -> keepByteCodeFunctions.add(method.getReference())));
               if (clazz.getEnclosingMethodAttribute() != null
                   && clazz.getEnclosingMethodAttribute().getEnclosingMethod() != null) {
                 localOrAnonymousClasses.add(clazz);
               }
             }
           });
-      appView.setCfByteCodePassThrough(keepByteCodeFunctions);
       for (DexProgramClass localOrAnonymousClass : localOrAnonymousClasses) {
         EnclosingMethodAttribute enclosingAttribute =
             localOrAnonymousClass.getEnclosingMethodAttribute();
@@ -97,12 +92,13 @@
         DexEncodedMethod method = holder.lookupMethod(enclosingAttribute.getEnclosingMethod());
         // If we cannot lookup the method, the conservative choice is keep the byte code.
         if (method == null
-            || (method.getKotlinMemberInfo().isFunction()
-                && method.getKotlinMemberInfo().asFunction().hasCrossInlineParameter())) {
+            || (method.getKotlinInfo().isFunction()
+                && method.getKotlinInfo().asFunction().hasCrossInlineParameter())) {
           localOrAnonymousClass.forEachProgramMethod(
               m -> keepByteCodeFunctions.add(m.getReference()));
         }
       }
+      appView.setCfByteCodePassThrough(keepByteCodeFunctions);
     } else {
       assert verifyKotlinMetadataModeledForAllClasses(enqueuer, keepMetadata);
     }
@@ -112,10 +108,7 @@
           clazz.getKotlinInfo().trace(definitionsForContext(clazz));
           clazz.forEachProgramMember(
               member ->
-                  member
-                      .getDefinition()
-                      .getKotlinMemberInfo()
-                      .trace(definitionsForContext(member)));
+                  member.getDefinition().getKotlinInfo().trace(definitionsForContext(member)));
         });
   }
 
@@ -127,7 +120,7 @@
           assert !hasKotlinClassMetadataAnnotation(clazz, definitionsForContext(clazz))
               || !keepMetadata
               || !enqueuer.isPinned(clazz.type)
-              || clazz.getKotlinInfo() != NO_KOTLIN_INFO;
+              || clazz.getKotlinInfo() != getNoKotlinInfo();
         });
     return true;
   }
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 4a5904e..d22a2b4 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataRewriter.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataRewriter.java
@@ -3,8 +3,8 @@
 // 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.INVALID_KOTLIN_INFO;
-import static com.android.tools.r8.kotlin.KotlinMetadataUtils.NO_KOTLIN_INFO;
+import static com.android.tools.r8.kotlin.KotlinMetadataUtils.getInvalidKotlinInfo;
+import static com.android.tools.r8.kotlin.KotlinMetadataUtils.getNoKotlinInfo;
 
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexAnnotation;
@@ -20,7 +20,7 @@
 import com.android.tools.r8.graph.DexValue.DexValueString;
 import com.android.tools.r8.naming.NamingLens;
 import com.android.tools.r8.utils.ConsumerUtils;
-import com.android.tools.r8.utils.Reporter;
+import com.android.tools.r8.utils.Pair;
 import com.android.tools.r8.utils.ThreadUtils;
 import java.util.ArrayList;
 import java.util.List;
@@ -101,15 +101,15 @@
         appView.appInfo().classes(),
         clazz -> {
           KotlinClassLevelInfo kotlinInfo = clazz.getKotlinInfo();
-          DexAnnotation oldMeta = clazz.annotations().getFirstMatching(rewrittenMetadataType);
-          if (kotlinInfo == INVALID_KOTLIN_INFO) {
+          if (kotlinInfo == getInvalidKotlinInfo()) {
             // Maintain invalid kotlin info for classes.
             return;
           }
+          DexAnnotation oldMeta = clazz.annotations().getFirstMatching(rewrittenMetadataType);
           // TODO(b/181103083): Consider removing if rewrittenMetadataType
           //  != factory.kotlinMetadataType
           if (oldMeta == null
-              || kotlinInfo == NO_KOTLIN_INFO
+              || kotlinInfo == getNoKotlinInfo()
               || (appView.appInfo().hasLiveness()
                   && !appView.withLiveness().appInfo().isPinned(clazz.type))) {
             // Remove @Metadata in DexAnnotation when there is no kotlin info and the type is not
@@ -131,8 +131,6 @@
     if (lens.isIdentityLens()) {
       return;
     }
-    final Kotlin kotlin = factory.kotlin;
-    final Reporter reporter = appView.options().reporter;
     final WriteMetadataFieldInfo writeMetadataFieldInfo = WriteMetadataFieldInfo.rewriteAll();
     ThreadUtils.processItems(
         appView.appInfo().classes(),
@@ -141,10 +139,10 @@
           if (metadata == null) {
             return;
           }
-          final KotlinClassLevelInfo kotlinInfo =
+          KotlinClassLevelInfo kotlinInfo =
               KotlinClassMetadataReader.getKotlinInfo(
-                  kotlin, clazz, factory, reporter, ConsumerUtils.emptyConsumer(), metadata);
-          if (kotlinInfo == NO_KOTLIN_INFO) {
+                  clazz, appView, ConsumerUtils.emptyConsumer(), metadata);
+          if (kotlinInfo == getNoKotlinInfo()) {
             return;
           }
           writeKotlinInfoToAnnotation(clazz, kotlinInfo, metadata, writeMetadataFieldInfo);
@@ -158,10 +156,15 @@
       DexAnnotation oldMeta,
       WriteMetadataFieldInfo writeMetadataFieldInfo) {
     try {
-      KotlinClassHeader kotlinClassHeader = kotlinInfo.rewrite(clazz, appView, lens);
+      Pair<KotlinClassHeader, Boolean> kotlinClassHeader = kotlinInfo.rewrite(clazz, appView, lens);
+      // TODO(b/185756596): Remove when special handling is no longer needed.
+      if (!kotlinClassHeader.getSecond() && !appView.enableWholeProgramOptimizations()) {
+        // No rewrite occurred and the data is the same as before.
+        return;
+      }
       DexAnnotation newMeta =
           createKotlinMetadataAnnotation(
-              kotlinClassHeader,
+              kotlinClassHeader.getFirst(),
               kotlinInfo.getPackageName(),
               getMaxVersion(METADATA_VERSION_1_4, kotlinInfo.getMetadataVersion()),
               writeMetadataFieldInfo);
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataUtils.java b/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataUtils.java
index fad37c6..7eb45f6 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataUtils.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataUtils.java
@@ -19,6 +19,7 @@
 import com.android.tools.r8.shaking.ProguardKeepRule;
 import com.android.tools.r8.shaking.ProguardKeepRuleType;
 import com.android.tools.r8.utils.DescriptorUtils;
+import com.android.tools.r8.utils.Pair;
 import kotlinx.metadata.KmExtensionType;
 import kotlinx.metadata.KmProperty;
 import kotlinx.metadata.KmPropertyExtensionVisitor;
@@ -30,8 +31,8 @@
 
 public class KotlinMetadataUtils {
 
-  public static final NoKotlinInfo NO_KOTLIN_INFO = new NoKotlinInfo("NO_KOTLIN_INFO");
-  public static final NoKotlinInfo INVALID_KOTLIN_INFO = new NoKotlinInfo("INVALID_KOTLIN_INFO");
+  private static final NoKotlinInfo NO_KOTLIN_INFO = new NoKotlinInfo("NO_KOTLIN_INFO");
+  private static final NoKotlinInfo INVALID_KOTLIN_INFO = new NoKotlinInfo("INVALID_KOTLIN_INFO");
 
   private static class NoKotlinInfo
       implements KotlinClassLevelInfo, KotlinFieldLevelInfo, KotlinMethodLevelInfo {
@@ -48,7 +49,8 @@
     }
 
     @Override
-    public KotlinClassHeader rewrite(DexClass clazz, AppView<?> appView, NamingLens namingLens) {
+    public Pair<KotlinClassHeader, Boolean> rewrite(
+        DexClass clazz, AppView<?> appView, NamingLens namingLens) {
       throw new Unreachable("Should never be called");
     }
 
@@ -73,6 +75,14 @@
     }
   }
 
+  public static NoKotlinInfo getNoKotlinInfo() {
+    return NO_KOTLIN_INFO;
+  }
+
+  public static NoKotlinInfo getInvalidKotlinInfo() {
+    return INVALID_KOTLIN_INFO;
+  }
+
   static JvmFieldSignature toJvmFieldSignature(DexField field) {
     return new JvmFieldSignature(field.name.toString(), field.type.toDescriptorString());
   }
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinMethodLevelInfo.java b/src/main/java/com/android/tools/r8/kotlin/KotlinMethodLevelInfo.java
index a43e677..c7ecdf8 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinMethodLevelInfo.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinMethodLevelInfo.java
@@ -6,27 +6,4 @@
 
 public interface KotlinMethodLevelInfo extends KotlinMemberLevelInfo {
 
-  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
index ec31d79..7088c7f 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinMultiFileClassFacadeInfo.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinMultiFileClassFacadeInfo.java
@@ -11,6 +11,7 @@
 import com.android.tools.r8.graph.DexDefinitionSupplier;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.naming.NamingLens;
+import com.android.tools.r8.utils.Pair;
 import com.google.common.collect.ImmutableList;
 import java.util.ArrayList;
 import java.util.List;
@@ -55,17 +56,25 @@
   }
 
   @Override
-  public KotlinClassHeader rewrite(DexClass clazz, AppView<?> appView, NamingLens namingLens) {
+  public Pair<KotlinClassHeader, Boolean> rewrite(
+      DexClass clazz, AppView<?> appView, NamingLens namingLens) {
+    List<String> partClassNameStrings = new ArrayList<>(partClassNames.size());
+    boolean rewritten = false;
+    for (KotlinTypeReference partClassName : partClassNames) {
+      rewritten |=
+          partClassName.toRenamedBinaryNameOrDefault(
+              binaryName -> {
+                if (binaryName != null) {
+                  partClassNameStrings.add(binaryName);
+                }
+              },
+              appView,
+              namingLens,
+              null);
+    }
     KotlinClassMetadata.MultiFileClassFacade.Writer writer =
         new KotlinClassMetadata.MultiFileClassFacade.Writer();
-    List<String> partClassNameStrings = new ArrayList<>(partClassNames.size());
-    for (KotlinTypeReference partClassName : partClassNames) {
-      String binaryName = partClassName.toRenamedBinaryNameOrDefault(appView, namingLens, null);
-      if (binaryName != null) {
-        partClassNameStrings.add(binaryName);
-      }
-    }
-    return writer.write(partClassNameStrings).getHeader();
+    return Pair.create(writer.write(partClassNameStrings).getHeader(), rewritten);
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinMultiFileClassPartInfo.java b/src/main/java/com/android/tools/r8/kotlin/KotlinMultiFileClassPartInfo.java
index 6b99d15..24cb385 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinMultiFileClassPartInfo.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinMultiFileClassPartInfo.java
@@ -8,9 +8,8 @@
 import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexDefinitionSupplier;
 import com.android.tools.r8.graph.DexEncodedMethod;
-import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.naming.NamingLens;
-import com.android.tools.r8.utils.Reporter;
+import com.android.tools.r8.utils.Pair;
 import java.util.function.Consumer;
 import kotlinx.metadata.KmPackage;
 import kotlinx.metadata.jvm.KotlinClassHeader;
@@ -42,12 +41,15 @@
       String packageName,
       int[] metadataVersion,
       DexClass clazz,
-      DexItemFactory factory,
-      Reporter reporter,
+      AppView<?> appView,
       Consumer<DexEncodedMethod> keepByteCode) {
+    KmPackage kmPackage = classPart.toKmPackage();
+    KotlinJvmSignatureExtensionInformation extensionInformation =
+        KotlinJvmSignatureExtensionInformation.readInformationFromMessage(
+            classPart, appView.options());
     return new KotlinMultiFileClassPartInfo(
         classPart.getFacadeClassName(),
-        KotlinPackageInfo.create(classPart.toKmPackage(), clazz, factory, reporter, keepByteCode),
+        KotlinPackageInfo.create(kmPackage, clazz, appView, keepByteCode, extensionInformation),
         packageName,
         metadataVersion);
   }
@@ -63,13 +65,14 @@
   }
 
   @Override
-  public KotlinClassHeader rewrite(DexClass clazz, AppView<?> appView, NamingLens namingLens) {
+  public Pair<KotlinClassHeader, Boolean> rewrite(
+      DexClass clazz, AppView<?> appView, NamingLens namingLens) {
+    KmPackage kmPackage = new KmPackage();
+    boolean rewritten = packageInfo.rewrite(kmPackage, clazz, appView, 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();
+    return Pair.create(writer.write(facadeClassName).getHeader(), rewritten);
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinPackageInfo.java b/src/main/java/com/android/tools/r8/kotlin/KotlinPackageInfo.java
index 41ff17d..d28e7c3 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinPackageInfo.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinPackageInfo.java
@@ -12,10 +12,8 @@
 import com.android.tools.r8.graph.DexDefinitionSupplier;
 import com.android.tools.r8.graph.DexEncodedField;
 import com.android.tools.r8.graph.DexEncodedMethod;
-import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.naming.NamingLens;
 import com.android.tools.r8.shaking.EnqueuerMetadataTraceable;
-import com.android.tools.r8.utils.Reporter;
 import java.util.HashMap;
 import java.util.Map;
 import java.util.function.Consumer;
@@ -42,9 +40,9 @@
   public static KotlinPackageInfo create(
       KmPackage kmPackage,
       DexClass clazz,
-      DexItemFactory factory,
-      Reporter reporter,
-      Consumer<DexEncodedMethod> keepByteCode) {
+      AppView<?> appView,
+      Consumer<DexEncodedMethod> keepByteCode,
+      KotlinJvmSignatureExtensionInformation extensionInformation) {
     Map<String, DexEncodedField> fieldMap = new HashMap<>();
     for (DexEncodedField field : clazz.fields()) {
       fieldMap.put(toJvmFieldSignature(field.getReference()).asString(), field);
@@ -56,26 +54,36 @@
     return new KotlinPackageInfo(
         JvmExtensionsKt.getModuleName(kmPackage),
         KotlinDeclarationContainerInfo.create(
-            kmPackage, methodMap, fieldMap, factory, reporter, keepByteCode),
+            kmPackage,
+            methodMap,
+            fieldMap,
+            appView.dexItemFactory(),
+            appView.reporter(),
+            keepByteCode,
+            extensionInformation),
         KotlinLocalDelegatedPropertyInfo.create(
-            JvmExtensionsKt.getLocalDelegatedProperties(kmPackage), factory, reporter));
+            JvmExtensionsKt.getLocalDelegatedProperties(kmPackage),
+            appView.dexItemFactory(),
+            appView.reporter()));
   }
 
-  public void rewrite(
-      KmPackage kmPackage, DexClass clazz, AppView<?> appView, NamingLens namingLens) {
-    containerInfo.rewrite(
-        kmPackage::visitFunction,
-        kmPackage::visitProperty,
-        kmPackage::visitTypeAlias,
-        clazz,
-        appView,
-        namingLens);
+  boolean rewrite(KmPackage kmPackage, DexClass clazz, AppView<?> appView, NamingLens namingLens) {
+    boolean rewritten =
+        containerInfo.rewrite(
+            kmPackage::visitFunction,
+            kmPackage::visitProperty,
+            kmPackage::visitTypeAlias,
+            clazz,
+            appView,
+            namingLens);
     JvmPackageExtensionVisitor extensionVisitor =
         (JvmPackageExtensionVisitor) kmPackage.visitExtensions(JvmPackageExtensionVisitor.TYPE);
-    localDelegatedProperties.rewrite(
-        extensionVisitor::visitLocalDelegatedProperty, appView, namingLens);
+    rewritten |=
+        localDelegatedProperties.rewrite(
+            extensionVisitor::visitLocalDelegatedProperty, appView, namingLens);
     extensionVisitor.visitModuleName(moduleName);
     extensionVisitor.visitEnd();
+    return rewritten;
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinPropertyInfo.java b/src/main/java/com/android/tools/r8/kotlin/KotlinPropertyInfo.java
index 6fceb06..9d0597d 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinPropertyInfo.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinPropertyInfo.java
@@ -12,11 +12,14 @@
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.naming.NamingLens;
+import com.android.tools.r8.utils.Box;
 import com.android.tools.r8.utils.Reporter;
 import java.util.List;
 import kotlinx.metadata.KmProperty;
 import kotlinx.metadata.KmPropertyVisitor;
 import kotlinx.metadata.jvm.JvmExtensionsKt;
+import kotlinx.metadata.jvm.JvmFieldSignature;
+import kotlinx.metadata.jvm.JvmMethodSignature;
 import kotlinx.metadata.jvm.JvmPropertyExtensionVisitor;
 
 // Holds information about KmProperty
@@ -109,16 +112,6 @@
   }
 
   @Override
-  public boolean isFieldProperty() {
-    return true;
-  }
-
-  @Override
-  public KotlinPropertyInfo asFieldProperty() {
-    return this;
-  }
-
-  @Override
   public boolean isProperty() {
     return true;
   }
@@ -140,7 +133,7 @@
     return setterSignature;
   }
 
-  void rewrite(
+  boolean rewrite(
       KmVisitorProviders.KmPropertyVisitorProvider visitorProvider,
       DexEncodedField field,
       DexEncodedMethod getter,
@@ -150,32 +143,52 @@
     // TODO(b/154348683): Flags again.
     KmPropertyVisitor kmProperty = visitorProvider.get(flags, name, getterFlags, setterFlags);
     // TODO(b/154348149): ReturnType could have been merged to a subtype.
+    boolean rewritten = false;
     if (returnType != null) {
-      returnType.rewrite(kmProperty::visitReturnType, appView, namingLens);
+      rewritten = returnType.rewrite(kmProperty::visitReturnType, appView, namingLens);
     }
     if (receiverParameterType != null) {
-      receiverParameterType.rewrite(kmProperty::visitReceiverParameterType, appView, namingLens);
+      rewritten |=
+          receiverParameterType.rewrite(
+              kmProperty::visitReceiverParameterType, appView, namingLens);
     }
     if (setterParameter != null) {
-      setterParameter.rewrite(kmProperty::visitSetterParameter, appView, namingLens);
+      rewritten |= setterParameter.rewrite(kmProperty::visitSetterParameter, appView, namingLens);
     }
     for (KotlinTypeParameterInfo typeParameter : typeParameters) {
-      typeParameter.rewrite(kmProperty::visitTypeParameter, appView, namingLens);
+      rewritten |= typeParameter.rewrite(kmProperty::visitTypeParameter, appView, namingLens);
     }
-    versionRequirements.rewrite(kmProperty::visitVersionRequirement);
+    rewritten |= versionRequirements.rewrite(kmProperty::visitVersionRequirement);
     JvmPropertyExtensionVisitor extensionVisitor =
         (JvmPropertyExtensionVisitor) kmProperty.visitExtensions(JvmPropertyExtensionVisitor.TYPE);
     if (extensionVisitor != null) {
+      Box<JvmFieldSignature> rewrittenFieldSignature = new Box<>();
+      if (fieldSignature != null) {
+        rewritten |=
+            fieldSignature.rewrite(rewrittenFieldSignature::set, field, appView, namingLens);
+      }
+      Box<JvmMethodSignature> rewrittenGetterSignature = new Box<>();
+      if (getterSignature != null) {
+        rewritten |=
+            getterSignature.rewrite(rewrittenGetterSignature::set, getter, appView, namingLens);
+      }
+      Box<JvmMethodSignature> rewrittenSetterSignature = new Box<>();
+      if (setterSignature != null) {
+        rewritten |=
+            setterSignature.rewrite(rewrittenSetterSignature::set, setter, appView, namingLens);
+      }
       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));
+          rewrittenFieldSignature.get(),
+          rewrittenGetterSignature.get(),
+          rewrittenSetterSignature.get());
       if (syntheticMethodForAnnotations != null) {
-        extensionVisitor.visitSyntheticMethodForAnnotations(
-            syntheticMethodForAnnotations.rewrite(null, appView, namingLens));
+        rewritten |=
+            syntheticMethodForAnnotations.rewrite(
+                extensionVisitor::visitSyntheticMethodForAnnotations, null, appView, namingLens);
       }
     }
+    return rewritten;
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinSyntheticClassInfo.java b/src/main/java/com/android/tools/r8/kotlin/KotlinSyntheticClassInfo.java
index 36bf5cf..08e95b3 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinSyntheticClassInfo.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinSyntheticClassInfo.java
@@ -7,9 +7,8 @@
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexDefinitionSupplier;
-import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.naming.NamingLens;
-import com.android.tools.r8.utils.Reporter;
+import com.android.tools.r8.utils.Pair;
 import kotlinx.metadata.KmLambda;
 import kotlinx.metadata.jvm.KotlinClassHeader;
 import kotlinx.metadata.jvm.KotlinClassMetadata;
@@ -45,15 +44,17 @@
       int[] metadataVersion,
       DexClass clazz,
       Kotlin kotlin,
-      DexItemFactory factory,
-      Reporter reporter) {
-    KmLambda lambda = null;
-    if (syntheticClass.isLambda()) {
-      lambda = syntheticClass.toKmLambda();
-      assert lambda != null;
-    }
+      AppView<?> appView) {
+    KmLambda lambda = syntheticClass.toKmLambda();
+    assert lambda == null || syntheticClass.isLambda();
+    KotlinJvmSignatureExtensionInformation extensionInformation =
+        KotlinJvmSignatureExtensionInformation.readInformationFromMessage(
+            syntheticClass, appView.options());
     return new KotlinSyntheticClassInfo(
-        lambda != null ? KotlinLambdaInfo.create(clazz, lambda, factory, reporter) : null,
+        lambda != null
+            ? KotlinLambdaInfo.create(
+                clazz, lambda, appView.dexItemFactory(), appView.reporter(), extensionInformation)
+            : null,
         getFlavour(syntheticClass, clazz, kotlin),
         packageName,
         metadataVersion);
@@ -82,15 +83,16 @@
   }
 
   @Override
-  public KotlinClassHeader rewrite(DexClass clazz, AppView<?> appView, NamingLens namingLens) {
+  public Pair<KotlinClassHeader, Boolean> rewrite(
+      DexClass clazz, AppView<?> appView, NamingLens namingLens) {
     Writer writer = new Writer();
+    boolean rewritten = false;
     if (lambda != null) {
       KmLambda kmLambda = new KmLambda();
-      if (lambda.rewrite(() -> kmLambda, clazz, appView, namingLens)) {
-        kmLambda.accept(writer);
-      }
+      rewritten = lambda.rewrite(() -> kmLambda, clazz, appView, namingLens);
+      kmLambda.accept(writer);
     }
-    return writer.write().getHeader();
+    return Pair.create(writer.write().getHeader(), rewritten);
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinTypeAliasInfo.java b/src/main/java/com/android/tools/r8/kotlin/KotlinTypeAliasInfo.java
index 3ecbc2c..c4e307b 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinTypeAliasInfo.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinTypeAliasInfo.java
@@ -58,20 +58,23 @@
         KotlinVersionRequirementInfo.create(alias.getVersionRequirements()));
   }
 
-  void rewrite(
+  boolean rewrite(
       KmVisitorProviders.KmTypeAliasVisitorProvider visitorProvider,
       AppView<?> appView,
       NamingLens namingLens) {
     KmTypeAliasVisitor kmTypeAliasVisitor = visitorProvider.get(flags, name);
-    underlyingType.rewrite(kmTypeAliasVisitor::visitUnderlyingType, appView, namingLens);
-    expandedType.rewrite(kmTypeAliasVisitor::visitExpandedType, appView, namingLens);
+    boolean rewritten =
+        underlyingType.rewrite(kmTypeAliasVisitor::visitUnderlyingType, appView, namingLens);
+    rewritten |= expandedType.rewrite(kmTypeAliasVisitor::visitExpandedType, appView, namingLens);
     for (KotlinTypeParameterInfo typeParameter : typeParameters) {
-      typeParameter.rewrite(kmTypeAliasVisitor::visitTypeParameter, appView, namingLens);
+      rewritten |=
+          typeParameter.rewrite(kmTypeAliasVisitor::visitTypeParameter, appView, namingLens);
     }
     for (KotlinAnnotationInfo annotation : annotations) {
-      annotation.rewrite(kmTypeAliasVisitor::visitAnnotation, appView, namingLens);
+      rewritten |= annotation.rewrite(kmTypeAliasVisitor::visitAnnotation, appView, namingLens);
     }
-    versionRequirements.rewrite(kmTypeAliasVisitor::visitVersionRequirement);
+    rewritten |= versionRequirements.rewrite(kmTypeAliasVisitor::visitVersionRequirement);
+    return rewritten;
   }
 
   @Override
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 7f068f6..9503404 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinTypeInfo.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinTypeInfo.java
@@ -79,34 +79,42 @@
     return arguments.build();
   }
 
-  public void rewrite(
+  boolean rewrite(
       KmVisitorProviders.KmTypeVisitorProvider visitorProvider,
       AppView<?> appView,
       NamingLens namingLens) {
     // TODO(b/154348683): Check for correct flags
     KmTypeVisitor kmTypeVisitor = visitorProvider.get(flags);
-    classifier.rewrite(kmTypeVisitor, appView, namingLens);
+    boolean rewritten = classifier.rewrite(kmTypeVisitor, appView, namingLens);
     if (abbreviatedType != null) {
-      abbreviatedType.rewrite(kmTypeVisitor::visitAbbreviatedType, appView, namingLens);
+      rewritten |=
+          abbreviatedType.rewrite(kmTypeVisitor::visitAbbreviatedType, appView, namingLens);
     }
     if (outerType != null) {
-      outerType.rewrite(kmTypeVisitor::visitOuterType, appView, namingLens);
+      rewritten |= outerType.rewrite(kmTypeVisitor::visitOuterType, appView, namingLens);
     }
     for (KotlinTypeProjectionInfo argument : arguments) {
-      argument.rewrite(
-          kmTypeVisitor::visitArgument, kmTypeVisitor::visitStarProjection, appView, namingLens);
+      rewritten |=
+          argument.rewrite(
+              kmTypeVisitor::visitArgument,
+              kmTypeVisitor::visitStarProjection,
+              appView,
+              namingLens);
     }
-    flexibleTypeUpperBound.rewrite(kmTypeVisitor::visitFlexibleTypeUpperBound, appView, namingLens);
+    rewritten |=
+        flexibleTypeUpperBound.rewrite(
+            kmTypeVisitor::visitFlexibleTypeUpperBound, appView, namingLens);
     if (annotations.isEmpty()) {
-      return;
+      return rewritten;
     }
     JvmTypeExtensionVisitor extensionVisitor =
         (JvmTypeExtensionVisitor) kmTypeVisitor.visitExtensions(JvmTypeExtensionVisitor.TYPE);
     if (extensionVisitor != null) {
       for (KotlinAnnotationInfo annotation : annotations) {
-        annotation.rewrite(extensionVisitor::visitAnnotation, appView, namingLens);
+        rewritten |= annotation.rewrite(extensionVisitor::visitAnnotation, appView, namingLens);
       }
     }
+    return rewritten;
   }
 
   @Override
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 d8457ed..21aeaa1 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinTypeParameterInfo.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinTypeParameterInfo.java
@@ -84,23 +84,28 @@
     return builder.build();
   }
 
-  void rewrite(
+  boolean rewrite(
       KmVisitorProviders.KmTypeParameterVisitorProvider visitorProvider,
       AppView<?> appView,
       NamingLens namingLens) {
     KmTypeParameterVisitor kmTypeParameterVisitor = visitorProvider.get(flags, name, id, variance);
+    boolean rewritten = false;
     for (KotlinTypeInfo originalUpperBound : originalUpperBounds) {
-      originalUpperBound.rewrite(kmTypeParameterVisitor::visitUpperBound, appView, namingLens);
+      rewritten |=
+          originalUpperBound.rewrite(kmTypeParameterVisitor::visitUpperBound, appView, namingLens);
     }
     if (annotations.isEmpty()) {
-      return;
+      return rewritten;
     }
     JvmTypeParameterExtensionVisitor extensionVisitor =
         (JvmTypeParameterExtensionVisitor)
             kmTypeParameterVisitor.visitExtensions(JvmTypeParameterExtensionVisitor.TYPE);
-    for (KotlinAnnotationInfo annotation : annotations) {
-      annotation.rewrite(extensionVisitor::visitAnnotation, appView, namingLens);
+    if (extensionVisitor != null) {
+      for (KotlinAnnotationInfo annotation : annotations) {
+        rewritten |= annotation.rewrite(extensionVisitor::visitAnnotation, appView, namingLens);
+      }
     }
+    return rewritten;
   }
 
   @Override
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 1f2ce01..3f83e53 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinTypeProjectionInfo.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinTypeProjectionInfo.java
@@ -35,7 +35,7 @@
     return variance == null && typeInfo == null;
   }
 
-  public void rewrite(
+  boolean rewrite(
       KmVisitorProviders.KmTypeProjectionVisitorProvider visitorProvider,
       KmVisitorProviders.KmTypeStarProjectionVisitorProvider starProjectionProvider,
       AppView<?> appView,
@@ -45,6 +45,7 @@
     } else {
       typeInfo.rewrite(flags -> visitorProvider.get(flags, variance), appView, namingLens);
     }
+    return false;
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinTypeReference.java b/src/main/java/com/android/tools/r8/kotlin/KotlinTypeReference.java
index 2e1ddf9..4d0d990 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinTypeReference.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinTypeReference.java
@@ -12,6 +12,7 @@
 import com.android.tools.r8.naming.NamingLens;
 import com.android.tools.r8.shaking.EnqueuerMetadataTraceable;
 import com.android.tools.r8.utils.DescriptorUtils;
+import java.util.function.Consumer;
 
 /**
  * To account for invalid type references in kotlin metadata, the class KotlinTypeReference will
@@ -60,34 +61,48 @@
     return new KotlinTypeReference(unknownValue);
   }
 
-  String toRenamedDescriptorOrDefault(
-      AppView<?> appView, NamingLens namingLens, String defaultValue) {
+  boolean toRenamedDescriptorOrDefault(
+      Consumer<String> rewrittenConsumer,
+      AppView<?> appView,
+      NamingLens namingLens,
+      String defaultValue) {
     if (unknown != null) {
-      return unknown;
+      rewrittenConsumer.accept(unknown);
+      return false;
     }
     assert known != null;
     DexType rewrittenType = toRewrittenTypeOrNull(appView, known);
     if (rewrittenType == null) {
-      return defaultValue;
+      rewrittenConsumer.accept(defaultValue);
+      return true;
     }
-    return namingLens.lookupDescriptor(rewrittenType).toString();
+    String renamedString = namingLens.lookupDescriptor(rewrittenType).toString();
+    rewrittenConsumer.accept(renamedString);
+    return !known.toDescriptorString().equals(renamedString);
   }
 
-  String toRenamedBinaryNameOrDefault(
-      AppView<?> appView, NamingLens namingLens, String defaultValue) {
+  boolean toRenamedBinaryNameOrDefault(
+      Consumer<String> rewrittenConsumer,
+      AppView<?> appView,
+      NamingLens namingLens,
+      String defaultValue) {
     if (unknown != null) {
       // Unknown values are always on the input form, so we can just return it.
-      return unknown;
+      rewrittenConsumer.accept(unknown);
+      return false;
     }
-    String descriptor = toRenamedDescriptorOrDefault(appView, namingLens, defaultValue);
-    if (descriptor == null) {
-      return null;
-    }
-    if (descriptor.equals(defaultValue)) {
-      // We assume that the default value passed in is already a binary name.
-      return descriptor;
-    }
-    return DescriptorUtils.getBinaryNameFromDescriptor(descriptor);
+    return toRenamedDescriptorOrDefault(
+        descriptor -> {
+          // We assume that the default value passed in is already a binary name.
+          if (descriptor == null || descriptor.equals(defaultValue)) {
+            rewrittenConsumer.accept(descriptor);
+          } else {
+            rewrittenConsumer.accept(DescriptorUtils.getBinaryNameFromDescriptor(descriptor));
+          }
+        },
+        appView,
+        namingLens,
+        defaultValue);
   }
 
   private static DexType toRewrittenTypeOrNull(AppView<?> appView, DexType type) {
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinValueParameterInfo.java b/src/main/java/com/android/tools/r8/kotlin/KotlinValueParameterInfo.java
index db54d35..293c35a 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinValueParameterInfo.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinValueParameterInfo.java
@@ -66,16 +66,18 @@
     return builder.build();
   }
 
-  void rewrite(
+  boolean rewrite(
       KmVisitorProviders.KmValueParameterVisitorProvider visitorProvider,
       AppView<?> appView,
       NamingLens namingLens) {
     KmValueParameterVisitor kmValueParameterVisitor = visitorProvider.get(flags, name);
-    type.rewrite(kmValueParameterVisitor::visitType, appView, namingLens);
+    boolean rewritten = type.rewrite(kmValueParameterVisitor::visitType, appView, namingLens);
     if (varargElementType != null) {
-      varargElementType.rewrite(
-          kmValueParameterVisitor::visitVarargElementType, appView, namingLens);
+      rewritten |=
+          varargElementType.rewrite(
+              kmValueParameterVisitor::visitVarargElementType, appView, namingLens);
     }
+    return rewritten;
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinVersionRequirementInfo.java b/src/main/java/com/android/tools/r8/kotlin/KotlinVersionRequirementInfo.java
index 9530f4c..3ab63f9 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinVersionRequirementInfo.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinVersionRequirementInfo.java
@@ -34,13 +34,14 @@
     return new KotlinVersionRequirementInfo(builder.build());
   }
 
-  public void rewrite(KmVisitorProviders.KmVersionRequirementVisitorProvider visitorProvider) {
+  boolean rewrite(KmVisitorProviders.KmVersionRequirementVisitorProvider visitorProvider) {
     if (this == NO_VERSION_REQUIREMENTS) {
-      return;
+      return false;
     }
     for (KotlinVersionRequirementPoint versionRequirement : versionRequirements) {
       versionRequirement.rewrite(visitorProvider.get());
     }
+    return false;
   }
 
   private static class KotlinVersionRequirementPoint {
diff --git a/src/main/java/com/android/tools/r8/retrace/Retrace.java b/src/main/java/com/android/tools/r8/retrace/Retrace.java
index faf8616..63d0098 100644
--- a/src/main/java/com/android/tools/r8/retrace/Retrace.java
+++ b/src/main/java/com/android/tools/r8/retrace/Retrace.java
@@ -11,7 +11,6 @@
 import com.android.tools.r8.Keep;
 import com.android.tools.r8.Version;
 import com.android.tools.r8.retrace.RetraceCommand.Builder;
-import com.android.tools.r8.retrace.internal.PlainStackTraceLineParser;
 import com.android.tools.r8.retrace.internal.RetraceAbortException;
 import com.android.tools.r8.retrace.internal.RetracerImpl;
 import com.android.tools.r8.retrace.internal.StackTraceRegularExpressionParser;
@@ -248,9 +247,7 @@
       timing.begin("Report result");
       StringRetrace stringRetrace =
           new StringRetrace(
-              options.getRegularExpression() == null
-                  ? new PlainStackTraceLineParser()
-                  : new StackTraceRegularExpressionParser(options.getRegularExpression()),
+              new StackTraceRegularExpressionParser(options.getRegularExpression()),
               StackTraceElementProxyRetracer.createDefault(retracer),
               diagnosticsHandler,
               options.isVerbose());
diff --git a/src/main/java/com/android/tools/r8/retrace/RetraceCommand.java b/src/main/java/com/android/tools/r8/retrace/RetraceCommand.java
index 66819e7..76d6b77 100644
--- a/src/main/java/com/android/tools/r8/retrace/RetraceCommand.java
+++ b/src/main/java/com/android/tools/r8/retrace/RetraceCommand.java
@@ -25,10 +25,14 @@
       List<String> stackTrace,
       Consumer<List<String>> retracedStackTraceConsumer,
       boolean isVerbose) {
-    options =
-        new RetraceOptions(regularExpression, diagnosticsHandler, proguardMapProducer, isVerbose);
     this.stackTrace = stackTrace;
     this.retracedStackTraceConsumer = retracedStackTraceConsumer;
+    this.options =
+        RetraceOptions.builder(diagnosticsHandler)
+            .setRegularExpression(regularExpression)
+            .setProguardMapProducer(proguardMapProducer)
+            .setVerbose(isVerbose)
+            .build();
 
     assert this.stackTrace != null;
     assert this.retracedStackTraceConsumer != null;
diff --git a/src/main/java/com/android/tools/r8/retrace/RetraceOptions.java b/src/main/java/com/android/tools/r8/retrace/RetraceOptions.java
index 4784f09..47d8c74 100644
--- a/src/main/java/com/android/tools/r8/retrace/RetraceOptions.java
+++ b/src/main/java/com/android/tools/r8/retrace/RetraceOptions.java
@@ -21,7 +21,7 @@
   private final DiagnosticsHandler diagnosticsHandler;
   private final ProguardMapProducer proguardMapProducer;
 
-  RetraceOptions(
+  private RetraceOptions(
       String regularExpression,
       DiagnosticsHandler diagnosticsHandler,
       ProguardMapProducer proguardMapProducer,
@@ -112,6 +112,9 @@
       if (this.proguardMapProducer == null) {
         throw new RuntimeException("ProguardMapSupplier not specified");
       }
+      if (this.regularExpression == null) {
+        throw new RuntimeException("Regular expression not specified");
+      }
       return new RetraceOptions(
           regularExpression, diagnosticsHandler, proguardMapProducer, isVerbose);
     }
diff --git a/src/main/java/com/android/tools/r8/retrace/internal/PlainStackTraceLineParser.java b/src/main/java/com/android/tools/r8/retrace/internal/PlainStackTraceLineParser.java
deleted file mode 100644
index ddaf675..0000000
--- a/src/main/java/com/android/tools/r8/retrace/internal/PlainStackTraceLineParser.java
+++ /dev/null
@@ -1,199 +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.retrace.internal;
-
-import static com.android.tools.r8.retrace.internal.RetraceUtils.firstCharFromIndex;
-import static com.android.tools.r8.retrace.internal.RetraceUtils.firstNonWhiteSpaceCharacterFromIndex;
-
-import com.android.tools.r8.retrace.StackTraceLineParser;
-import com.android.tools.r8.retrace.internal.StackTraceElementStringProxy.ClassNameType;
-import com.android.tools.r8.retrace.internal.StackTraceElementStringProxy.StackTraceElementStringProxyBuilder;
-import com.android.tools.r8.utils.DescriptorUtils;
-
-public final class PlainStackTraceLineParser
-    implements StackTraceLineParser<String, StackTraceElementStringProxy> {
-
-  public PlainStackTraceLineParser() {}
-
-  @Override
-  public StackTraceElementStringProxy parse(String stackTraceLine) {
-    return parseLine(stackTraceLine);
-  }
-
-  /**
-   * Captures a stack trace line of the following formats:
-   *
-   * <ul>
-   *   <li>com.android.r8.R8Exception
-   *   <li>com.android.r8.R8Exception: Problem when compiling program
-   *   <li>Caused by: com.android.r8.R8InnerException: You have to write the program first
-   *   <li>com.android.r8.R8InnerException: You have to write the program first
-   * </ul>
-   *
-   * <p>This will also contains false positives, such as
-   *
-   * <pre>
-   *   W( 8207) VFY: unable to resolve static method 11: Lprivateinterfacemethods/I$-CC;....
-   * </pre>
-   *
-   * <p>The only invalid chars for type-identifiers for a java type-name is ';', '[' and '/', so we
-   * cannot really disregard the above line.
-   *
-   * <p>Caused by and Suppressed seems to not change based on locale, so we use these as markers.
-   */
-  private static class ExceptionLine {
-
-    private static final String CAUSED_BY = "Caused by: ";
-    private static final String SUPPRESSED = "Suppressed: ";
-
-    private static StackTraceElementStringProxy tryParse(String line) {
-      if (line.isEmpty()) {
-        return null;
-      }
-      int firstNonWhiteSpaceChar = firstNonWhiteSpaceCharacterFromIndex(line, 0);
-      String description = "";
-      if (line.startsWith(CAUSED_BY, firstNonWhiteSpaceChar)) {
-        description = CAUSED_BY;
-      } else if (line.startsWith(SUPPRESSED, firstNonWhiteSpaceChar)) {
-        description = SUPPRESSED;
-      }
-      int exceptionStartIndex = firstNonWhiteSpaceChar + description.length();
-      int messageStartIndex = firstCharFromIndex(line, exceptionStartIndex, ':');
-      String className = line.substring(exceptionStartIndex, messageStartIndex);
-      if (!DescriptorUtils.isValidJavaType(className)) {
-        return null;
-      }
-      return StackTraceElementStringProxy.builder(line)
-          .registerClassName(exceptionStartIndex, messageStartIndex, ClassNameType.TYPENAME)
-          .build();
-    }
-  }
-
-  /**
-   * Captures a stack trace line on the following form
-   *
-   * <ul>
-   *   <li>at dalvik.system.NativeStart.main(NativeStart.java:99)
-   *   <li>at dalvik.system.NativeStart.main(:99)
-   *   <li>at dalvik.system.NativeStart.main(Foo.java:)
-   *   <li>at dalvik.system.NativeStart.main(Native Method)
-   *   <li>at classloader/named_module@version/foo.bar.baz(:20)
-   *   <li>at classloader//foo.bar.baz(:20)
-   * </ul>
-   *
-   * <p>Empirical evidence suggests that the "at" string is never localized.
-   */
-  private static class AtLine {
-
-    private static StackTraceElementStringProxy tryParse(String line) {
-      // Check that the line is indented with some amount of white space.
-      if (line.length() == 0 || !Character.isWhitespace(line.charAt(0))) {
-        return null;
-      }
-      // Find the first non-white space character and check that we have the sequence 'a', 't', ' '.
-      int firstNonWhiteSpace = firstNonWhiteSpaceCharacterFromIndex(line, 0);
-      if (firstNonWhiteSpace + 2 >= line.length()
-          || line.charAt(firstNonWhiteSpace) != 'a'
-          || line.charAt(firstNonWhiteSpace + 1) != 't'
-          || line.charAt(firstNonWhiteSpace + 2) != ' ') {
-        return null;
-      }
-      int classClassLoaderOrModuleStartIndex =
-          firstNonWhiteSpaceCharacterFromIndex(line, firstNonWhiteSpace + 2);
-      if (classClassLoaderOrModuleStartIndex >= line.length()
-          || classClassLoaderOrModuleStartIndex != firstNonWhiteSpace + 3) {
-        return null;
-      }
-      int parensStart = firstCharFromIndex(line, classClassLoaderOrModuleStartIndex, '(');
-      if (parensStart >= line.length()) {
-        return null;
-      }
-      int parensEnd = line.lastIndexOf(')');
-      if (parensEnd <= parensStart) {
-        return null;
-      }
-      if (firstNonWhiteSpaceCharacterFromIndex(line, parensEnd) == line.length()) {
-        return null;
-      }
-      int methodSeparator = line.lastIndexOf('.', parensStart);
-      if (methodSeparator <= classClassLoaderOrModuleStartIndex) {
-        return null;
-      }
-      int classStartIndex = classClassLoaderOrModuleStartIndex;
-      int classLoaderOrModuleEndIndex =
-          firstCharFromIndex(line, classClassLoaderOrModuleStartIndex, '/');
-      if (classLoaderOrModuleEndIndex < methodSeparator) {
-        int moduleEndIndex = firstCharFromIndex(line, classLoaderOrModuleEndIndex + 1, '/');
-        if (moduleEndIndex < methodSeparator) {
-          // The stack trace contains both a class loader and module
-          classStartIndex = moduleEndIndex + 1;
-        } else {
-          classStartIndex = classLoaderOrModuleEndIndex + 1;
-        }
-      }
-      StackTraceElementStringProxyBuilder builder =
-          StackTraceElementStringProxy.builder(line)
-              .registerClassName(classStartIndex, methodSeparator, ClassNameType.TYPENAME)
-              .registerMethodName(methodSeparator + 1, parensStart);
-      // Check if we have a filename and position.
-      int separatorIndex = line.lastIndexOf(':', parensEnd);
-      if (separatorIndex > -1 && separatorIndex < parensEnd) {
-        builder.registerSourceFile(parensStart + 1, separatorIndex);
-        builder.registerLineNumber(separatorIndex + 1, parensEnd);
-      } else {
-        builder.registerSourceFile(parensStart + 1, parensEnd);
-      }
-      return builder.build();
-    }
-  }
-
-  static class CircularReferenceLine {
-
-    private static final String CIRCULAR_REFERENCE = "[CIRCULAR REFERENCE:";
-
-    static StackTraceElementStringProxy tryParse(String line) {
-      // Check that the line is indented with some amount of white space.
-      if (line.length() == 0 || !Character.isWhitespace(line.charAt(0))) {
-        return null;
-      }
-      // Find the first non-white space character and check that we have the sequence
-      // '[CIRCULAR REFERENCE:Exception]'.
-      int firstNonWhiteSpace = firstNonWhiteSpaceCharacterFromIndex(line, 0);
-      if (!line.startsWith(CIRCULAR_REFERENCE, firstNonWhiteSpace)) {
-        return null;
-      }
-      int exceptionStartIndex = firstNonWhiteSpace + CIRCULAR_REFERENCE.length();
-      int lastBracketPosition = firstCharFromIndex(line, exceptionStartIndex, ']');
-      if (lastBracketPosition == line.length()) {
-        return null;
-      }
-      int onlyWhitespaceFromLastBracket =
-          firstNonWhiteSpaceCharacterFromIndex(line, lastBracketPosition + 1);
-      if (onlyWhitespaceFromLastBracket != line.length()) {
-        return null;
-      }
-      return StackTraceElementStringProxy.builder(line)
-          .registerClassName(exceptionStartIndex, lastBracketPosition, ClassNameType.TYPENAME)
-          .build();
-    }
-  }
-
-  private StackTraceElementStringProxy parseLine(String line) {
-    // Most lines are 'at lines' so attempt to parse it first.
-    StackTraceElementStringProxy parsedLine = AtLine.tryParse(line);
-    if (parsedLine != null) {
-      return parsedLine;
-    }
-    parsedLine = ExceptionLine.tryParse(line);
-    if (parsedLine != null) {
-      return parsedLine;
-    }
-    parsedLine = CircularReferenceLine.tryParse(line);
-    if (parsedLine != null) {
-      return parsedLine;
-    }
-    return StackTraceElementStringProxy.builder(line).build();
-  }
-}
diff --git a/src/main/java/com/android/tools/r8/shaking/AnnotationRemover.java b/src/main/java/com/android/tools/r8/shaking/AnnotationRemover.java
index a561761..f17d49b 100644
--- a/src/main/java/com/android/tools/r8/shaking/AnnotationRemover.java
+++ b/src/main/java/com/android/tools/r8/shaking/AnnotationRemover.java
@@ -18,6 +18,8 @@
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.GraphLens;
 import com.android.tools.r8.graph.InnerClassAttribute;
+import com.android.tools.r8.kotlin.KotlinMemberLevelInfo;
+import com.android.tools.r8.kotlin.KotlinPropertyInfo;
 import com.android.tools.r8.utils.InternalOptions;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.Sets;
@@ -191,33 +193,55 @@
       stripAttributes(clazz);
       clazz.setAnnotations(
           clazz.annotations().rewrite(annotation -> rewriteAnnotation(clazz, annotation)));
-      clazz.forEachMethod(method -> processMethod(method, clazz));
-      clazz.forEachField(field -> processField(field, clazz));
+      // Kotlin properties are split over fields and methods. Check if any is pinned before pruning
+      // the information.
+      Set<KotlinPropertyInfo> pinnedKotlinProperties = Sets.newIdentityHashSet();
+      clazz.forEachMethod(method -> processMethod(method, clazz, pinnedKotlinProperties));
+      clazz.forEachField(field -> processField(field, clazz, pinnedKotlinProperties));
+      clazz.forEachProgramMember(
+          member -> {
+            KotlinMemberLevelInfo kotlinInfo = member.getKotlinInfo();
+            if (kotlinInfo.isProperty()
+                && !pinnedKotlinProperties.contains(kotlinInfo.asProperty())) {
+              member.clearKotlinInfo();
+            }
+          });
     }
   }
 
-  private void processMethod(DexEncodedMethod method, DexProgramClass clazz) {
+  private void processMethod(
+      DexEncodedMethod method,
+      DexProgramClass clazz,
+      Set<KotlinPropertyInfo> pinnedKotlinProperties) {
     method.setAnnotations(
         method.annotations().rewrite(annotation -> rewriteAnnotation(method, annotation)));
     method.parameterAnnotationsList =
         method.parameterAnnotationsList.keepIf(this::filterParameterAnnotations);
-    if (appView
-        .getKeepInfo()
-        .getMethodInfo(method, clazz)
-        .isAllowSignatureAttributeRemovalAllowed(options)) {
+    KeepMethodInfo methodInfo = appView.getKeepInfo().getMethodInfo(method, clazz);
+    if (methodInfo.isAllowSignatureAttributeRemovalAllowed(options)) {
       method.clearGenericSignature();
     }
+    if (!methodInfo.isPinned() && method.getKotlinInfo().isFunction()) {
+      method.clearKotlinMemberInfo();
+    }
+    if (methodInfo.isPinned() && method.getKotlinInfo().isProperty()) {
+      pinnedKotlinProperties.add(method.getKotlinInfo().asProperty());
+    }
   }
 
-  private void processField(DexEncodedField field, DexProgramClass clazz) {
+  private void processField(
+      DexEncodedField field,
+      DexProgramClass clazz,
+      Set<KotlinPropertyInfo> pinnedKotlinProperties) {
     field.setAnnotations(
         field.annotations().rewrite(annotation -> rewriteAnnotation(field, annotation)));
-    if (appView
-        .getKeepInfo()
-        .getFieldInfo(field, clazz)
-        .isAllowSignatureAttributeRemovalAllowed(options)) {
+    KeepFieldInfo fieldInfo = appView.getKeepInfo().getFieldInfo(field, clazz);
+    if (fieldInfo.isAllowSignatureAttributeRemovalAllowed(options)) {
       field.clearGenericSignature();
     }
+    if (fieldInfo.isPinned() && field.getKotlinInfo().isProperty()) {
+      pinnedKotlinProperties.add(field.getKotlinInfo().asProperty());
+    }
   }
 
   private DexAnnotation rewriteAnnotation(DexDefinition holder, DexAnnotation original) {
diff --git a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
index 7c2162c..d377362 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -6,6 +6,8 @@
 import static com.android.tools.r8.graph.DexProgramClass.asProgramClassOrNull;
 import static com.android.tools.r8.graph.FieldAccessInfoImpl.MISSING_FIELD_ACCESS_INFO;
 import static com.android.tools.r8.ir.desugar.LambdaDescriptor.isLambdaMetafactoryMethod;
+import static com.android.tools.r8.ir.desugar.itf.InterfaceMethodRewriter.emulateInterfaceLibraryMethod;
+import static com.android.tools.r8.ir.desugar.itf.InterfaceMethodRewriter.getEmulateLibraryInterfaceClassType;
 import static com.android.tools.r8.ir.optimize.enums.UnboxedEnumMemberRelocator.ENUM_UNBOXING_UTILITY_CLASS_SUFFIX;
 import static com.android.tools.r8.naming.IdentifierNameStringUtils.identifyIdentifier;
 import static com.android.tools.r8.naming.IdentifierNameStringUtils.isReflectionMethod;
@@ -24,6 +26,7 @@
 import com.android.tools.r8.experimental.graphinfo.GraphConsumer;
 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.ClassDefinition;
 import com.android.tools.r8.graph.ClasspathOrLibraryClass;
 import com.android.tools.r8.graph.ClasspathOrLibraryDefinition;
@@ -57,13 +60,16 @@
 import com.android.tools.r8.graph.FieldAccessInfoCollectionImpl;
 import com.android.tools.r8.graph.FieldAccessInfoImpl;
 import com.android.tools.r8.graph.FieldResolutionResult;
+import com.android.tools.r8.graph.GenericSignature.MethodTypeSignature;
 import com.android.tools.r8.graph.GenericSignatureEnqueuerAnalysis;
 import com.android.tools.r8.graph.InnerClassAttribute;
 import com.android.tools.r8.graph.LookupLambdaTarget;
 import com.android.tools.r8.graph.LookupTarget;
+import com.android.tools.r8.graph.MethodAccessFlags;
 import com.android.tools.r8.graph.MethodAccessInfoCollection;
 import com.android.tools.r8.graph.NestMemberClassAttribute;
 import com.android.tools.r8.graph.ObjectAllocationInfoCollectionImpl;
+import com.android.tools.r8.graph.ParameterAnnotationsList;
 import com.android.tools.r8.graph.ProgramDefinition;
 import com.android.tools.r8.graph.ProgramDerivedContext;
 import com.android.tools.r8.graph.ProgramField;
@@ -116,6 +122,7 @@
 import com.android.tools.r8.utils.Action;
 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.OptionalBool;
 import com.android.tools.r8.utils.Pair;
 import com.android.tools.r8.utils.SetUtils;
@@ -178,6 +185,10 @@
     GENERATE_MAIN_DEX_LIST,
     WHY_ARE_YOU_KEEPING;
 
+    public boolean isTreeShaking() {
+      return isInitialTreeShaking() || isFinalTreeShaking();
+    }
+
     public boolean isInitialTreeShaking() {
       return this == INITIAL_TREE_SHAKING;
     }
@@ -223,6 +234,7 @@
   // Don't hold a direct pointer to app info (use appView).
   private AppInfoWithClassHierarchy appInfo;
   private final AppView<AppInfoWithClassHierarchy> appView;
+  private final DexItemFactory dexItemFactory;
   private final ExecutorService executorService;
   private SubtypingInfo subtypingInfo;
   private final InternalOptions options;
@@ -404,6 +416,7 @@
     InternalOptions options = appView.options();
     this.appInfo = appView.appInfo();
     this.appView = appView.withClassHierarchy();
+    this.dexItemFactory = appView.dexItemFactory();
     this.executorService = executorService;
     this.subtypingInfo = subtypingInfo;
     this.forceProguardCompatibility = options.forceProguardCompatibility;
@@ -1495,7 +1508,8 @@
 
       // Record field reference for generated extension registry shrinking.
       appView.withGeneratedExtensionRegistryShrinker(
-          shrinker -> shrinker.handleFailedOrUnknownFieldResolution(fieldReference, currentMethod));
+          shrinker ->
+              shrinker.handleFailedOrUnknownFieldResolution(fieldReference, currentMethod, mode));
       return;
     }
 
@@ -3255,6 +3269,9 @@
         assert false;
       }
     }
+    if (mode.isInitialTreeShaking()) {
+      libraryClasses.addAll(synthesizeDesugaredLibraryClasses());
+    }
 
     // Add just referenced non-program types. We can't replace the program classes at this point as
     // they are needed in tree pruning.
@@ -3391,6 +3408,56 @@
     return true;
   }
 
+  private List<DexLibraryClass> synthesizeDesugaredLibraryClasses() {
+    List<DexLibraryClass> synthesizedClasses = new ArrayList<>();
+    Map<DexType, DexType> emulateLibraryInterface =
+        options.desugaredLibraryConfiguration.getEmulateLibraryInterface();
+    emulateLibraryInterface
+        .keySet()
+        .forEach(
+            interfaceType -> {
+              DexClass interfaceClass = appView.definitionFor(interfaceType);
+              if (interfaceClass == null) {
+                appView
+                    .reporter()
+                    .error(
+                        new StringDiagnostic(
+                            "The interface "
+                                + interfaceType.getTypeName()
+                                + " is missing, but is required for Java 8+ API desugaring."));
+                return;
+              }
+
+              DexType emulateInterfaceType =
+                  getEmulateLibraryInterfaceClassType(interfaceType, dexItemFactory);
+              assert appView.definitionFor(emulateInterfaceType) == null;
+
+              List<DexEncodedMethod> emulateInterfaceClassMethods =
+                  ListUtils.newArrayList(
+                      builder ->
+                          interfaceClass.forEachClassMethodMatching(
+                              DexEncodedMethod::isDefaultMethod,
+                              method ->
+                                  builder.accept(
+                                      new DexEncodedMethod(
+                                          emulateInterfaceLibraryMethod(method, dexItemFactory),
+                                          MethodAccessFlags.createPublicStaticSynthetic(),
+                                          MethodTypeSignature.noSignature(),
+                                          DexAnnotationSet.empty(),
+                                          ParameterAnnotationsList.empty(),
+                                          null,
+                                          true))));
+
+              synthesizedClasses.add(
+                  DexLibraryClass.builder(dexItemFactory)
+                      .setAccessFlags(ClassAccessFlags.createPublicFinalSynthetic())
+                      .setDirectMethods(emulateInterfaceClassMethods)
+                      .setType(emulateInterfaceType)
+                      .build());
+            });
+    return synthesizedClasses;
+  }
+
   private void synthesizeLibraryConversionWrappers(SyntheticAdditions additions) {
     if (desugaredLibraryWrapperAnalysis == null) {
       return;
diff --git a/src/main/java/com/android/tools/r8/shaking/MissingClasses.java b/src/main/java/com/android/tools/r8/shaking/MissingClasses.java
index aa87461..3e8ad60 100644
--- a/src/main/java/com/android/tools/r8/shaking/MissingClasses.java
+++ b/src/main/java/com/android/tools/r8/shaking/MissingClasses.java
@@ -6,7 +6,6 @@
 
 import static com.android.tools.r8.ir.desugar.DesugaredLibraryAPIConverter.DESCRIPTOR_VIVIFIED_PREFIX;
 import static com.android.tools.r8.ir.desugar.DesugaredLibraryRetargeter.getRetargetPackageAndClassPrefixDescriptor;
-import static com.android.tools.r8.ir.desugar.itf.InterfaceMethodRewriter.EMULATE_LIBRARY_CLASS_NAME_SUFFIX;
 import static com.android.tools.r8.utils.collections.IdentityHashSetFromMap.newProgramDerivedContextSet;
 
 import com.android.tools.r8.diagnostic.MissingDefinitionsDiagnostic;
@@ -282,8 +281,6 @@
         AppView<?> appView) {
       DexItemFactory dexItemFactory = appView.dexItemFactory();
       InternalOptions options = appView.options();
-      DexString emulatedLibraryClassNameSuffix =
-          dexItemFactory.createString(EMULATE_LIBRARY_CLASS_NAME_SUFFIX + ";");
       DexString retargetPackageAndClassPrefixDescriptor =
           dexItemFactory.createString(
               getRetargetPackageAndClassPrefixDescriptor(options.desugaredLibraryConfiguration));
@@ -291,8 +288,7 @@
       return type -> {
         DexString descriptor = type.getDescriptor();
         return descriptor.startsWith(retargetPackageAndClassPrefixDescriptor)
-            || descriptor.startsWith(vivifiedClassNamePrefix)
-            || descriptor.endsWith(emulatedLibraryClassNameSuffix);
+            || descriptor.startsWith(vivifiedClassNamePrefix);
       };
     }
 
diff --git a/src/main/java/com/android/tools/r8/shaking/RootSetUtils.java b/src/main/java/com/android/tools/r8/shaking/RootSetUtils.java
index e78f53b..72a0a0b 100644
--- a/src/main/java/com/android/tools/r8/shaking/RootSetUtils.java
+++ b/src/main/java/com/android/tools/r8/shaking/RootSetUtils.java
@@ -488,16 +488,17 @@
           break;
         }
         // In compat mode traverse all direct methods in the hierarchy.
-        if (currentClass == clazz || options.forceProguardCompatibility) {
-          currentClass
-              .directMethods()
-              .forEach(
-                  method -> {
-                    DexDefinition precondition =
-                        testAndGetPrecondition(method, preconditionSupplier);
-                    markMethod(method, memberKeepRules, methodsMarked, rule, precondition, ifRule);
-                  });
-        }
+        currentClass
+            .getMethodCollection()
+            .forEachDirectMethodMatching(
+                method ->
+                    currentClass == clazz
+                        || (method.isStatic() && !method.isPrivate() && !method.isInitializer())
+                        || options.forceProguardCompatibility,
+                method -> {
+                  DexDefinition precondition = testAndGetPrecondition(method, preconditionSupplier);
+                  markMethod(method, memberKeepRules, methodsMarked, rule, precondition, ifRule);
+                });
         currentClass
             .virtualMethods()
             .forEach(
diff --git a/src/main/java/com/android/tools/r8/synthesis/CommittedSyntheticsCollection.java b/src/main/java/com/android/tools/r8/synthesis/CommittedSyntheticsCollection.java
index a201d6a..f8c56fa 100644
--- a/src/main/java/com/android/tools/r8/synthesis/CommittedSyntheticsCollection.java
+++ b/src/main/java/com/android/tools/r8/synthesis/CommittedSyntheticsCollection.java
@@ -3,15 +3,20 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.synthesis;
 
+import static java.util.Collections.emptyList;
+
 import com.android.tools.r8.graph.DexApplication;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.GraphLens.NonIdentityGraphLens;
 import com.android.tools.r8.graph.PrunedItems;
-import com.android.tools.r8.utils.BooleanBox;
+import com.android.tools.r8.utils.IterableUtils;
 import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.ImmutableSet;
-import com.google.common.collect.Sets;
+import com.google.common.collect.Iterables;
+import java.util.ArrayList;
 import java.util.Collection;
+import java.util.IdentityHashMap;
+import java.util.List;
 import java.util.Map;
 import java.util.Set;
 import java.util.function.Consumer;
@@ -26,10 +31,9 @@
 
   static class Builder {
     private final CommittedSyntheticsCollection parent;
-    private ImmutableMap.Builder<DexType, SyntheticProgramClassReference> newNonLegacyClasses =
-        null;
-    private ImmutableMap.Builder<DexType, SyntheticMethodReference> newNonLegacyMethods = null;
-    private ImmutableMap.Builder<DexType, LegacySyntheticReference> newLegacyClasses = null;
+    private Map<DexType, List<SyntheticProgramClassReference>> newNonLegacyClasses = null;
+    private Map<DexType, List<SyntheticMethodReference>> newNonLegacyMethods = null;
+    private Map<DexType, List<LegacySyntheticReference>> newLegacyClasses = null;
     private ImmutableSet.Builder<DexType> newSyntheticInputs = null;
 
     public Builder(CommittedSyntheticsCollection parent) {
@@ -49,9 +53,11 @@
 
     public Builder addNonLegacyClass(SyntheticProgramClassReference reference) {
       if (newNonLegacyClasses == null) {
-        newNonLegacyClasses = ImmutableMap.builder();
+        newNonLegacyClasses = new IdentityHashMap<>();
       }
-      newNonLegacyClasses.put(reference.getHolder(), reference);
+      newNonLegacyClasses
+          .computeIfAbsent(reference.getHolder(), ignore -> new ArrayList<>())
+          .add(reference);
       return this;
     }
 
@@ -61,30 +67,57 @@
 
     public Builder addNonLegacyMethod(SyntheticMethodReference reference) {
       if (newNonLegacyMethods == null) {
-        newNonLegacyMethods = ImmutableMap.builder();
+        newNonLegacyMethods = new IdentityHashMap<>();
       }
-      newNonLegacyMethods.put(reference.getHolder(), reference);
+      newNonLegacyMethods
+          .computeIfAbsent(reference.getHolder(), ignore -> new ArrayList<>())
+          .add(reference);
       return this;
     }
 
     public Builder addLegacyClasses(Map<DexType, LegacySyntheticDefinition> classes) {
       if (newLegacyClasses == null) {
-        newLegacyClasses = ImmutableMap.builder();
+        newLegacyClasses = new IdentityHashMap<>();
       }
-      classes.forEach((type, item) -> newLegacyClasses.put(type, item.toReference()));
+      classes.forEach(
+          (type, item) ->
+              newLegacyClasses
+                  .computeIfAbsent(type, ignore -> new ArrayList<>())
+                  .add(item.toReference()));
       return this;
     }
 
-    public Builder addLegacyClass(LegacySyntheticReference item) {
+    public Builder addLegacyClass(LegacySyntheticReference reference) {
       if (newLegacyClasses == null) {
-        newLegacyClasses = ImmutableMap.builder();
+        newLegacyClasses = new IdentityHashMap<>();
       }
-      newLegacyClasses.put(item.getHolder(), item);
+      newLegacyClasses
+          .computeIfAbsent(reference.getHolder(), ignore -> new ArrayList<>())
+          .add(reference);
       return this;
     }
 
-    Builder addToSyntheticInputs() {
-      newSyntheticInputs = ImmutableSet.builder();
+    public Builder addSyntheticInput(DexType syntheticInput) {
+      if (newSyntheticInputs == null) {
+        newSyntheticInputs = ImmutableSet.builder();
+      }
+      newSyntheticInputs.add(syntheticInput);
+      return this;
+    }
+
+    Builder collectSyntheticInputs() {
+      if (newSyntheticInputs == null) {
+        newSyntheticInputs = ImmutableSet.builder();
+      }
+      if (newNonLegacyClasses != null) {
+        newSyntheticInputs.addAll(newNonLegacyClasses.keySet());
+      }
+      if (newNonLegacyMethods != null) {
+        newSyntheticInputs.addAll(newNonLegacyMethods.keySet());
+      }
+      if (newLegacyClasses != null) {
+        newSyntheticInputs.addAll(newLegacyClasses.keySet());
+      }
       return this;
     }
 
@@ -92,31 +125,30 @@
       if (newNonLegacyClasses == null && newNonLegacyMethods == null && newLegacyClasses == null) {
         return parent;
       }
-      ImmutableMap<DexType, SyntheticProgramClassReference> allNonLegacyClasses =
-          newNonLegacyClasses == null
-              ? parent.nonLegacyClasses
-              : newNonLegacyClasses.putAll(parent.nonLegacyClasses).build();
-      ImmutableMap<DexType, SyntheticMethodReference> allNonLegacyMethods =
-          newNonLegacyMethods == null
-              ? parent.nonLegacyMethods
-              : newNonLegacyMethods.putAll(parent.nonLegacyMethods).build();
-      ImmutableMap<DexType, LegacySyntheticReference> allLegacyClasses =
-          newLegacyClasses == null
-              ? parent.legacyTypes
-              : newLegacyClasses.putAll(parent.legacyTypes).build();
+      ImmutableMap<DexType, List<SyntheticProgramClassReference>> allNonLegacyClasses =
+          merge(newNonLegacyClasses, parent.nonLegacyClasses);
+      ImmutableMap<DexType, List<SyntheticMethodReference>> allNonLegacyMethods =
+          merge(newNonLegacyMethods, parent.nonLegacyMethods);
+      ImmutableMap<DexType, List<LegacySyntheticReference>> allLegacyClasses =
+          merge(newLegacyClasses, parent.legacyTypes);
       ImmutableSet<DexType> allSyntheticInputs =
-          newSyntheticInputs == null
-              ? parent.syntheticInputs
-              : newSyntheticInputs
-                  .addAll(allNonLegacyClasses.keySet())
-                  .addAll(allNonLegacyMethods.keySet())
-                  .addAll(allLegacyClasses.keySet())
-                  .build();
+          newSyntheticInputs == null ? parent.syntheticInputs : newSyntheticInputs.build();
       return new CommittedSyntheticsCollection(
           allLegacyClasses, allNonLegacyMethods, allNonLegacyClasses, allSyntheticInputs);
     }
   }
 
+  private static <T> ImmutableMap<DexType, List<T>> merge(
+      Map<DexType, List<T>> newSynthetics, ImmutableMap<DexType, List<T>> oldSynthetics) {
+    if (newSynthetics == null) {
+      return oldSynthetics;
+    }
+    oldSynthetics.forEach(
+        (type, elements) ->
+            newSynthetics.computeIfAbsent(type, ignore -> new ArrayList<>()).addAll(elements));
+    return ImmutableMap.copyOf(newSynthetics);
+  }
+
   private static final CommittedSyntheticsCollection EMPTY =
       new CommittedSyntheticsCollection(
           ImmutableMap.of(), ImmutableMap.of(), ImmutableMap.of(), ImmutableSet.of());
@@ -126,31 +158,42 @@
    *
    * <p>TODO(b/158159959): Remove legacy support.
    */
-  private final ImmutableMap<DexType, LegacySyntheticReference> legacyTypes;
+  private final ImmutableMap<DexType, List<LegacySyntheticReference>> legacyTypes;
 
   /** Mapping from synthetic type to its synthetic method item description. */
-  private final ImmutableMap<DexType, SyntheticMethodReference> nonLegacyMethods;
+  private final ImmutableMap<DexType, List<SyntheticMethodReference>> nonLegacyMethods;
 
   /** Mapping from synthetic type to its synthetic class item description. */
-  private final ImmutableMap<DexType, SyntheticProgramClassReference> nonLegacyClasses;
+  private final ImmutableMap<DexType, List<SyntheticProgramClassReference>> nonLegacyClasses;
 
   /** Set of synthetic types that were present in the input. */
-  private final ImmutableSet<DexType> syntheticInputs;
+  public final ImmutableSet<DexType> syntheticInputs;
 
   public CommittedSyntheticsCollection(
-      ImmutableMap<DexType, LegacySyntheticReference> legacyTypes,
-      ImmutableMap<DexType, SyntheticMethodReference> nonLegacyMethods,
-      ImmutableMap<DexType, SyntheticProgramClassReference> nonLegacyClasses,
+      ImmutableMap<DexType, List<LegacySyntheticReference>> legacyTypes,
+      ImmutableMap<DexType, List<SyntheticMethodReference>> nonLegacyMethods,
+      ImmutableMap<DexType, List<SyntheticProgramClassReference>> nonLegacyClasses,
       ImmutableSet<DexType> syntheticInputs) {
     this.legacyTypes = legacyTypes;
     this.nonLegacyMethods = nonLegacyMethods;
     this.nonLegacyClasses = nonLegacyClasses;
     this.syntheticInputs = syntheticInputs;
-    assert legacyTypes.size() + nonLegacyMethods.size() + nonLegacyClasses.size()
-        == Sets.union(
-                Sets.union(nonLegacyMethods.keySet(), nonLegacyClasses.keySet()),
-                legacyTypes.keySet())
-            .size();
+    assert verifySyntheticInputsSubsetOfSynthetics();
+  }
+
+  private boolean verifySyntheticInputsSubsetOfSynthetics() {
+    Set<DexType> synthetics =
+        ImmutableSet.<DexType>builder()
+            .addAll(legacyTypes.keySet())
+            .addAll(nonLegacyMethods.keySet())
+            .addAll(nonLegacyClasses.keySet())
+            .build();
+    syntheticInputs.forEach(
+        syntheticInput -> {
+          assert synthetics.contains(syntheticInput)
+              : "Expected " + syntheticInput.toSourceString() + " to be a synthetic";
+        });
+    return true;
   }
 
   public static CommittedSyntheticsCollection empty() {
@@ -161,10 +204,6 @@
     return new Builder(this);
   }
 
-  Builder builderForSyntheticInputs() {
-    return new Builder(this).addToSyntheticInputs();
-  }
-
   boolean isEmpty() {
     boolean empty =
         legacyTypes.isEmpty() && nonLegacyMethods.isEmpty() && nonLegacyClasses.isEmpty();
@@ -188,24 +227,26 @@
     return syntheticInputs.contains(type);
   }
 
-  public ImmutableMap<DexType, LegacySyntheticReference> getLegacyTypes() {
+  public ImmutableMap<DexType, List<LegacySyntheticReference>> getLegacyTypes() {
     return legacyTypes;
   }
 
-  public ImmutableMap<DexType, SyntheticMethodReference> getNonLegacyMethods() {
+  public List<LegacySyntheticReference> getLegacyTypes(DexType type) {
+    return legacyTypes.getOrDefault(type, emptyList());
+  }
+
+  public ImmutableMap<DexType, List<SyntheticMethodReference>> getNonLegacyMethods() {
     return nonLegacyMethods;
   }
 
-  public ImmutableMap<DexType, SyntheticProgramClassReference> getNonLegacyClasses() {
+  public ImmutableMap<DexType, List<SyntheticProgramClassReference>> getNonLegacyClasses() {
     return nonLegacyClasses;
   }
 
-  public SyntheticReference<?, ?, ?> getNonLegacyItem(DexType type) {
-    SyntheticMethodReference reference = nonLegacyMethods.get(type);
-    if (reference != null) {
-      return reference;
-    }
-    return nonLegacyClasses.get(type);
+  public Iterable<SyntheticReference<?, ?, ?>> getNonLegacyItems(DexType type) {
+    return Iterables.concat(
+        nonLegacyClasses.getOrDefault(type, emptyList()),
+        nonLegacyMethods.getOrDefault(type, emptyList()));
   }
 
   public void forEachSyntheticInput(Consumer<DexType> fn) {
@@ -213,8 +254,8 @@
   }
 
   public void forEachNonLegacyItem(Consumer<SyntheticReference<?, ?, ?>> fn) {
-    nonLegacyMethods.forEach((t, r) -> fn.accept(r));
-    nonLegacyClasses.forEach((t, r) -> fn.accept(r));
+    nonLegacyMethods.values().forEach(r -> r.forEach(fn));
+    nonLegacyClasses.values().forEach(r -> r.forEach(fn));
   }
 
   CommittedSyntheticsCollection pruneItems(PrunedItems prunedItems) {
@@ -223,30 +264,37 @@
       return this;
     }
     Builder builder = CommittedSyntheticsCollection.empty().builder();
-    BooleanBox changed = new BooleanBox(false);
-    legacyTypes.forEach(
-        (type, item) -> {
-          if (removed.contains(type)) {
-            changed.set();
-          } else {
-            builder.addLegacyClass(item);
-          }
-        });
-    for (SyntheticMethodReference reference : nonLegacyMethods.values()) {
+    boolean changed = false;
+    for (LegacySyntheticReference reference : IterableUtils.flatten(legacyTypes.values())) {
       if (removed.contains(reference.getHolder())) {
-        changed.set();
+        changed = true;
+      } else {
+        builder.addLegacyClass(reference);
+      }
+    }
+    for (SyntheticMethodReference reference : IterableUtils.flatten(nonLegacyMethods.values())) {
+      if (removed.contains(reference.getHolder())) {
+        changed = true;
       } else {
         builder.addNonLegacyMethod(reference);
       }
     }
-    for (SyntheticProgramClassReference reference : nonLegacyClasses.values()) {
+    for (SyntheticProgramClassReference reference :
+        IterableUtils.flatten(nonLegacyClasses.values())) {
       if (removed.contains(reference.getHolder())) {
-        changed.set();
+        changed = true;
       } else {
         builder.addNonLegacyClass(reference);
       }
     }
-    return changed.isTrue() ? builder.build() : this;
+    for (DexType syntheticInput : syntheticInputs) {
+      if (removed.contains(syntheticInput)) {
+        changed = true;
+      } else {
+        builder.addSyntheticInput(syntheticInput);
+      }
+    }
+    return changed ? builder.build() : this;
   }
 
   CommittedSyntheticsCollection rewriteWithLens(NonIdentityGraphLens lens) {
@@ -265,22 +313,25 @@
     return rewrittenItems.build();
   }
 
-  private static <R extends Rewritable<R>> ImmutableMap<DexType, R> rewriteItems(
-      Map<DexType, R> items, NonIdentityGraphLens lens) {
-    ImmutableMap.Builder<DexType, R> rewrittenItems = ImmutableMap.builder();
-    for (R reference : items.values()) {
+  private <R extends Rewritable<R>> ImmutableMap<DexType, List<R>> rewriteItems(
+      Map<DexType, List<R>> items, NonIdentityGraphLens lens) {
+    Map<DexType, List<R>> rewrittenItems = new IdentityHashMap<>();
+    for (R reference : IterableUtils.flatten(items.values())) {
       R rewritten = reference.rewrite(lens);
       if (rewritten != null) {
-        rewrittenItems.put(rewritten.getHolder(), rewritten);
+        rewrittenItems
+            .computeIfAbsent(rewritten.getHolder(), ignore -> new ArrayList<>())
+            .add(rewritten);
       }
     }
-    return rewrittenItems.build();
+    return ImmutableMap.copyOf(rewrittenItems);
   }
 
   boolean verifyTypesAreInApp(DexApplication application) {
     assert verifyTypesAreInApp(application, legacyTypes.keySet());
     assert verifyTypesAreInApp(application, nonLegacyMethods.keySet());
     assert verifyTypesAreInApp(application, nonLegacyClasses.keySet());
+    assert verifyTypesAreInApp(application, syntheticInputs);
     return true;
   }
 
diff --git a/src/main/java/com/android/tools/r8/synthesis/SyntheticFinalization.java b/src/main/java/com/android/tools/r8/synthesis/SyntheticFinalization.java
index 5fa5498..46bb432 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SyntheticFinalization.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticFinalization.java
@@ -27,12 +27,12 @@
 import com.android.tools.r8.shaking.MainDexInfo;
 import com.android.tools.r8.synthesis.SyntheticNaming.SyntheticKind;
 import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.IterableUtils;
 import com.android.tools.r8.utils.SetUtils;
 import com.android.tools.r8.utils.collections.BidirectionalManyToOneRepresentativeHashMap;
 import com.android.tools.r8.utils.collections.BidirectionalManyToOneRepresentativeMap;
 import com.android.tools.r8.utils.collections.MutableBidirectionalManyToOneRepresentativeMap;
 import com.android.tools.r8.utils.structural.RepresentativeMap;
-import com.google.common.collect.ImmutableCollection;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.ImmutableSet;
@@ -92,6 +92,15 @@
         BidirectionalManyToOneRepresentativeHashMap.newIdentityHashMap();
     private final Map<DexType, DexType> typeMap = new IdentityHashMap<>();
 
+    boolean isEmpty() {
+      if (typeMap.isEmpty()) {
+        assert fieldMap.isEmpty();
+        assert methodMap.isEmpty();
+        return true;
+      }
+      return false;
+    }
+
     void move(DexType from, DexType to) {
       DexType old = typeMap.put(from, to);
       assert old == null || old == to;
@@ -196,19 +205,24 @@
 
   public static void finalizeWithLiveness(AppView<AppInfoWithLiveness> appView) {
     Result result = appView.getSyntheticItems().computeFinalSynthetics(appView);
-    appView.setAppInfo(appView.appInfo().rebuildWithLiveness(result.commit));
     appView.setAppInfo(appView.appInfo().rebuildWithMainDexInfo(result.mainDexInfo));
-    appView.rewriteWithLens(result.lens);
+    if (result.lens != null) {
+      appView.rewriteWithLensAndApplication(result.lens, result.commit.getApplication().asDirect());
+    } else {
+      assert result.commit.getApplication() == appView.appInfo().app();
+    }
+    appView.setAppInfo(appView.appInfo().rebuildWithLiveness(result.commit));
     appView.pruneItems(result.prunedItems);
   }
 
   Result computeFinalSynthetics(AppView<?> appView) {
     assert verifyNoNestedSynthetics();
+    assert verifyOneSyntheticPerSyntheticClass();
     DexApplication application;
     Builder lensBuilder = new Builder();
-    ImmutableMap.Builder<DexType, SyntheticMethodReference> finalMethodsBuilder =
+    ImmutableMap.Builder<DexType, List<SyntheticMethodReference>> finalMethodsBuilder =
         ImmutableMap.builder();
-    ImmutableMap.Builder<DexType, SyntheticProgramClassReference> finalClassesBuilder =
+    ImmutableMap.Builder<DexType, List<SyntheticProgramClassReference>> finalClassesBuilder =
         ImmutableMap.builder();
     Set<DexType> derivedMainDexTypes = Sets.newIdentityHashSet();
     {
@@ -217,16 +231,19 @@
           buildLensAndProgram(
               appView,
               computeEquivalences(
-                  appView, committed.getNonLegacyMethods().values(), generators, lensBuilder),
+                  appView, committed.getNonLegacyMethods(), generators, lensBuilder),
               computeEquivalences(
-                  appView, committed.getNonLegacyClasses().values(), generators, lensBuilder),
+                  appView, committed.getNonLegacyClasses(), generators, lensBuilder),
               lensBuilder,
-              (clazz, reference) -> finalClassesBuilder.put(clazz.getType(), reference),
-              (clazz, reference) -> finalMethodsBuilder.put(clazz.getType(), reference),
+              (clazz, reference) ->
+                  finalClassesBuilder.put(clazz.getType(), ImmutableList.of(reference)),
+              (clazz, reference) ->
+                  finalMethodsBuilder.put(clazz.getType(), ImmutableList.of(reference)),
               derivedMainDexTypes);
     }
-    ImmutableMap<DexType, SyntheticMethodReference> finalMethods = finalMethodsBuilder.build();
-    ImmutableMap<DexType, SyntheticProgramClassReference> finalClasses =
+    ImmutableMap<DexType, List<SyntheticMethodReference>> finalMethods =
+        finalMethodsBuilder.build();
+    ImmutableMap<DexType, List<SyntheticProgramClassReference>> finalClasses =
         finalClassesBuilder.build();
 
     Set<DexType> prunedSynthetics = Sets.newIdentityHashSet();
@@ -266,7 +283,7 @@
   private <R extends SyntheticReference<R, D, ?>, D extends SyntheticDefinition<R, D, ?>>
       Map<DexType, EquivalenceGroup<D>> computeEquivalences(
           AppView<?> appView,
-          ImmutableCollection<R> references,
+          ImmutableMap<DexType, List<R>> references,
           Map<String, NumberGenerator> generators,
           Builder lensBuilder) {
     boolean intermediate = appView.options().intermediate;
@@ -306,6 +323,32 @@
     return true;
   }
 
+  private boolean verifyOneSyntheticPerSyntheticClass() {
+    Set<DexType> seen = Sets.newIdentityHashSet();
+    committed
+        .getLegacyTypes()
+        .forEach(
+            (type, references) -> {
+              assert seen.add(type);
+              assert references.size() == 1;
+            });
+    committed
+        .getNonLegacyClasses()
+        .forEach(
+            (type, references) -> {
+              assert seen.add(type);
+              assert references.size() == 1;
+            });
+    committed
+        .getNonLegacyMethods()
+        .forEach(
+            (type, references) -> {
+              assert seen.add(type);
+              assert references.size() == 1;
+            });
+    return true;
+  }
+
   private static DexApplication buildLensAndProgram(
       AppView<?> appView,
       Map<DexType, EquivalenceGroup<SyntheticMethodDefinition>> syntheticMethodGroups,
@@ -316,8 +359,7 @@
       Set<DexType> derivedMainDexSynthetics) {
     DexApplication application = appView.appInfo().app();
     MainDexInfo mainDexInfo = appView.appInfo().getMainDexInfo();
-    List<DexProgramClass> newProgramClasses = new ArrayList<>();
-    Set<DexType> pruned = Sets.newIdentityHashSet();
+    Set<DexProgramClass> pruned = Sets.newIdentityHashSet();
 
     TreeFixerBase treeFixer =
         new TreeFixerBase(appView) {
@@ -353,7 +395,7 @@
           assert representativeClass.getMethodCollection().size() == 1;
           for (SyntheticMethodDefinition member : syntheticGroup.getMembers()) {
             if (member != representative) {
-              pruned.add(member.getHolder().getType());
+              pruned.add(member.getHolder());
               deduplicatedClasses.add(member.getHolder());
             }
             if (member.getContext().isDerivedFromMainDexList(mainDexInfo)) {
@@ -371,9 +413,8 @@
               representative.getKind(), representative.getHolder(), context, appView);
           for (SyntheticProgramClassDefinition member : syntheticGroup.getMembers()) {
             DexProgramClass memberClass = member.getHolder();
-            DexType memberType = memberClass.getType();
             if (member != representative) {
-              pruned.add(memberType);
+              pruned.add(memberClass);
               deduplicatedClasses.add(memberClass);
             }
             if (member.getContext().isDerivedFromMainDexList(mainDexInfo)) {
@@ -382,21 +423,31 @@
           }
         });
 
-    for (DexProgramClass clazz : application.classes()) {
-      if (!pruned.contains(clazz.type)) {
-        newProgramClasses.add(clazz);
+    // Only create a new application if anything changed.
+    if (lensBuilder.isEmpty()) {
+      assert deduplicatedClasses.isEmpty();
+      assert pruned.isEmpty();
+    } else {
+      if (!pruned.isEmpty()) {
+        List<DexProgramClass> newProgramClasses = new ArrayList<>();
+        for (DexProgramClass clazz : application.classes()) {
+          if (!pruned.contains(clazz)) {
+            newProgramClasses.add(clazz);
+          }
+        }
+        assert newProgramClasses.size() < application.classes().size();
+        application = application.builder().replaceProgramClasses(newProgramClasses).build();
       }
+
+      // Assert that the non-representatives have been removed from the app.
+      assert verifyNonRepresentativesRemovedFromApplication(application, syntheticClassGroups);
+      assert verifyNonRepresentativesRemovedFromApplication(application, syntheticMethodGroups);
+
+      DexApplication.Builder<?> builder = application.builder();
+      treeFixer.fixupClasses(deduplicatedClasses);
+      builder.replaceProgramClasses(treeFixer.fixupClasses(application.classes()));
+      application = builder.build();
     }
-    application = application.builder().replaceProgramClasses(newProgramClasses).build();
-
-    // Assert that the non-representatives have been removed from the app.
-    assert verifyNonRepresentativesRemovedFromApplication(application, syntheticClassGroups);
-    assert verifyNonRepresentativesRemovedFromApplication(application, syntheticMethodGroups);
-
-    DexApplication.Builder<?> builder = application.builder();
-    treeFixer.fixupClasses(deduplicatedClasses);
-    builder.replaceProgramClasses(treeFixer.fixupClasses(application.classes()));
-    application = builder.build();
 
     // Add the synthesized from after repackaging which changed class definitions.
     final DexApplication appForLookup = application;
@@ -653,9 +704,10 @@
   }
 
   private <R extends SyntheticReference<R, D, ?>, D extends SyntheticDefinition<R, D, ?>>
-      Map<DexType, D> lookupDefinitions(AppView<?> appView, Collection<R> references) {
+      Map<DexType, D> lookupDefinitions(
+          AppView<?> appView, ImmutableMap<DexType, List<R>> references) {
     Map<DexType, D> definitions = new IdentityHashMap<>(references.size());
-    for (R reference : references) {
+    for (R reference : IterableUtils.flatten(references.values())) {
       D definition = reference.lookupDefinition(appView::definitionFor);
       if (definition == null) {
         // We expect pruned definitions to have been removed.
diff --git a/src/main/java/com/android/tools/r8/synthesis/SyntheticItems.java b/src/main/java/com/android/tools/r8/synthesis/SyntheticItems.java
index 6a539f0..befe594 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SyntheticItems.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticItems.java
@@ -23,8 +23,10 @@
 import com.android.tools.r8.graph.PrunedItems;
 import com.android.tools.r8.synthesis.SyntheticFinalization.Result;
 import com.android.tools.r8.synthesis.SyntheticNaming.SyntheticKind;
+import com.android.tools.r8.utils.IterableUtils;
 import com.android.tools.r8.utils.ListUtils;
 import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Iterables;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
@@ -36,7 +38,6 @@
 import java.util.function.Function;
 import java.util.function.Predicate;
 import java.util.function.Supplier;
-import java.util.stream.Collectors;
 
 public class SyntheticItems implements SyntheticDefinitionsProvider {
 
@@ -122,8 +123,7 @@
       // If the compilation is in intermediate mode the synthetics should just be passed through.
       return;
     }
-    CommittedSyntheticsCollection.Builder builder =
-        synthetics.committed.builderForSyntheticInputs();
+    CommittedSyntheticsCollection.Builder builder = synthetics.committed.builder();
     // TODO(b/158159959): Consider identifying synthetics in the input reader to speed this up.
     for (DexProgramClass clazz : appView.appInfo().classes()) {
       SyntheticMarker marker = SyntheticMarker.stripMarkerFromClass(clazz, appView);
@@ -138,7 +138,7 @@
             new SyntheticProgramClassDefinition(marker.getKind(), marker.getContext(), clazz));
       }
     }
-    CommittedSyntheticsCollection committed = builder.build();
+    CommittedSyntheticsCollection committed = builder.collectSyntheticInputs().build();
     if (committed.isEmpty()) {
       return;
     }
@@ -184,15 +184,6 @@
     return baseDefinitionFor.apply(type);
   }
 
-  public boolean verifyNonLegacySyntheticsAreCommitted() {
-    assert pending.nonLegacyDefinitions.isEmpty()
-        : "Uncommitted synthetics: "
-            + pending.nonLegacyDefinitions.keySet().stream()
-                .map(DexType::getName)
-                .collect(Collectors.joining(", "));
-    return true;
-  }
-
   public boolean hasPendingSyntheticClasses() {
     return !pending.isEmpty();
   }
@@ -233,20 +224,27 @@
     return isCommittedSynthetic(type) || isPendingSynthetic(type);
   }
 
-  public SyntheticKind getNonLegacySyntheticKind(DexProgramClass clazz) {
-    assert isNonLegacySynthetic(clazz);
-    SyntheticReference<?, ?, ?> reference = committed.getNonLegacyItem(clazz.getType());
-    if (reference == null) {
-      SyntheticDefinition<?, ?, ?> definition = pending.nonLegacyDefinitions.get(clazz.getType());
-      if (definition != null) {
-        reference = definition.toReference();
-      }
+  public boolean isEligibleForClassMerging(DexProgramClass clazz) {
+    assert isSyntheticClass(clazz);
+    return isSyntheticLambda(clazz);
+  }
+
+  // TODO(b/186211926): Allow merging of legacy synthetics.
+  private boolean isSyntheticLambda(DexProgramClass clazz) {
+    if (!isNonLegacySynthetic(clazz)) {
+      return false;
     }
-    if (reference != null) {
-      return reference.getKind();
+    Iterable<SyntheticReference<?, ?, ?>> references = committed.getNonLegacyItems(clazz.getType());
+    if (!Iterables.isEmpty(references)) {
+      assert Iterables.size(references) == 1;
+      return references.iterator().next().getKind() == SyntheticKind.LAMBDA;
+    }
+    SyntheticDefinition<?, ?, ?> definition = pending.nonLegacyDefinitions.get(clazz.getType());
+    if (definition != null) {
+      return definition.getKind() == SyntheticKind.LAMBDA;
     }
     assert false;
-    return null;
+    return false;
   }
 
   public boolean isSubjectToKeepRules(DexProgramClass clazz) {
@@ -280,36 +278,32 @@
     return true;
   }
 
-  private List<SynthesizingContext> getSynthesizingContexts(DexType type) {
-    SyntheticReference<?, ?, ?> reference = committed.getNonLegacyItem(type);
-    if (reference != null) {
-      return Collections.singletonList(reference.getContext());
+  private void forEachSynthesizingContext(DexType type, Consumer<SynthesizingContext> consumer) {
+    for (SyntheticReference<?, ?, ?> reference : committed.getNonLegacyItems(type)) {
+      consumer.accept(reference.getContext());
     }
     SyntheticDefinition<?, ?, ?> definition = pending.nonLegacyDefinitions.get(type);
     if (definition != null) {
-      return Collections.singletonList(definition.getContext());
+      consumer.accept(definition.getContext());
     }
-    return Collections.emptyList();
+  }
+
+  private List<SynthesizingContext> getSynthesizingContexts(DexType type) {
+    return ListUtils.newImmutableList(builder -> forEachSynthesizingContext(type, builder));
   }
 
   public Collection<DexType> getSynthesizingContextTypes(DexType type) {
-    SyntheticReference<?, ?, ?> reference = committed.getNonLegacyItem(type);
-    if (reference != null) {
-      return Collections.singletonList(reference.getContext().getSynthesizingContextType());
-    }
-    SyntheticDefinition<?, ?, ?> definition = pending.nonLegacyDefinitions.get(type);
-    if (definition != null) {
-      return Collections.singletonList(definition.getContext().getSynthesizingContextType());
-    }
-    LegacySyntheticReference legacyReference = committed.getLegacyTypes().get(type);
-    if (legacyReference != null) {
-      return legacyReference.getContexts();
+    ImmutableList.Builder<DexType> builder = ImmutableList.builder();
+    forEachSynthesizingContext(
+        type, synthesizingContext -> builder.add(synthesizingContext.getSynthesizingContextType()));
+    for (LegacySyntheticReference legacyReference : committed.getLegacyTypes(type)) {
+      builder.addAll(legacyReference.getContexts());
     }
     LegacySyntheticDefinition legacyDefinition = pending.legacyClasses.get(type);
     if (legacyDefinition != null) {
-      return legacyDefinition.getContexts();
+      builder.addAll(legacyDefinition.getContexts());
     }
-    return Collections.emptyList();
+    return builder.build();
   }
 
   // TODO(b/180091213): Implement this and remove client provided the oracle.
@@ -330,14 +324,12 @@
       DexProgramClass clazz,
       Predicate<DexProgramClass> ifIsLambda,
       Predicate<DexProgramClass> ifNotLambda) {
-    SyntheticReference<?, ?, ?> reference = committed.getNonLegacyItem(clazz.getType());
-    if (reference == null) {
-      SyntheticDefinition<?, ?, ?> definition = pending.nonLegacyDefinitions.get(clazz.getType());
-      if (definition != null) {
-        reference = definition.toReference();
-      }
+    Iterable<SyntheticReference<?, ?, ?>> references = committed.getNonLegacyItems(clazz.getType());
+    SyntheticDefinition<?, ?, ?> definition = pending.nonLegacyDefinitions.get(clazz.getType());
+    if (definition != null) {
+      references = Iterables.concat(references, IterableUtils.singleton(definition.toReference()));
     }
-    if (reference != null && reference.getKind() == SyntheticKind.LAMBDA) {
+    if (Iterables.any(references, reference -> reference.getKind() == SyntheticKind.LAMBDA)) {
       assert ifIsLambda.test(clazz);
     } else {
       assert ifNotLambda.test(clazz);
@@ -355,6 +347,7 @@
         context, appView.appInfoForDesugaring().getClassToFeatureSplitMap());
   }
 
+  /** Used to find the synthesizing context for a new synthetic that is about to be created. */
   private SynthesizingContext getSynthesizingContext(
       ProgramDefinition context, ClassToFeatureSplitMap featureSplits) {
     DexType contextType = context.getContextType();
@@ -362,9 +355,15 @@
     if (existingDefinition != null) {
       return existingDefinition.getContext();
     }
-    SyntheticReference<?, ?, ?> existingReference = committed.getNonLegacyItem(contextType);
-    if (existingReference != null) {
-      return existingReference.getContext();
+    Iterable<SyntheticReference<?, ?, ?>> existingReferences =
+        committed.getNonLegacyItems(contextType);
+    if (!Iterables.isEmpty(existingReferences)) {
+      // Use a deterministic synthesizing context from the set of contexts.
+      return IterableUtils.min(
+              existingReferences,
+              (existingReference, other) ->
+                  existingReference.getReference().compareTo(other.getReference()))
+          .getContext();
     }
     // This context is not nested in an existing synthetic context so create a new "leaf" context.
     FeatureSplit featureSplit = featureSplits.getFeatureSplit(context, this);
diff --git a/src/main/java/com/android/tools/r8/synthesis/SyntheticMethodReference.java b/src/main/java/com/android/tools/r8/synthesis/SyntheticMethodReference.java
index 9c19e81..b6bb218 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SyntheticMethodReference.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticMethodReference.java
@@ -54,7 +54,7 @@
   @Override
   SyntheticMethodReference internalRewrite(
       SynthesizingContext rewrittenContext, NonIdentityGraphLens lens) {
-    DexMethod rewritten = lens.lookupMethod(method);
+    DexMethod rewritten = lens.getRenamedMethodSignature(method);
     // If the reference has been non-trivially rewritten the compiler has changed it and it can no
     // longer be considered a synthetic. The context may or may not have changed.
     if (method != rewritten && !lens.isSimpleRenaming(method, rewritten)) {
diff --git a/src/main/java/com/android/tools/r8/tracereferences/Formatter.java b/src/main/java/com/android/tools/r8/tracereferences/Formatter.java
index b572f26..fd188c0 100644
--- a/src/main/java/com/android/tools/r8/tracereferences/Formatter.java
+++ b/src/main/java/com/android/tools/r8/tracereferences/Formatter.java
@@ -76,20 +76,14 @@
   protected abstract void printTypeFooter();
 
   void format(TraceReferencesResult result) {
-    print(
-        result.types,
-        result.keepPackageNames,
-        result.fields,
-        result.methods,
-        result.missingDefinition);
+    print(result.types, result.keepPackageNames, result.fields, result.methods);
   }
 
   private void print(
       Set<TracedClass> types,
       Set<PackageReference> keepPackageNames,
       Map<ClassReference, Set<TracedField>> fields,
-      Map<ClassReference, Set<TracedMethod>> methods,
-      Set<Object> missingDefinition) {
+      Map<ClassReference, Set<TracedMethod>> methods) {
     List<TracedClass> sortedTypes = new ArrayList<>(types);
     sortedTypes.sort(Comparator.comparing(tracedClass -> tracedClass.getReference().getTypeName()));
     for (TracedClass type : sortedTypes) {
@@ -97,7 +91,7 @@
           methods.getOrDefault(type.getReference(), Collections.emptySet());
       Set<TracedField> fieldsForClass =
           fields.getOrDefault(type.getReference(), Collections.emptySet());
-      if (missingDefinition.contains(type.getReference())) {
+      if (type.isMissingDefinition()) {
         continue;
       }
       printTypeHeader(type);
diff --git a/src/main/java/com/android/tools/r8/tracereferences/MissingDefinitionsDiagnostic.java b/src/main/java/com/android/tools/r8/tracereferences/MissingDefinitionsDiagnostic.java
deleted file mode 100644
index 1d4e46a..0000000
--- a/src/main/java/com/android/tools/r8/tracereferences/MissingDefinitionsDiagnostic.java
+++ /dev/null
@@ -1,99 +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.tracereferences;
-
-import com.android.tools.r8.Diagnostic;
-import com.android.tools.r8.Keep;
-import com.android.tools.r8.origin.Origin;
-import com.android.tools.r8.position.Position;
-import com.android.tools.r8.references.ClassReference;
-import com.android.tools.r8.references.FieldReference;
-import com.android.tools.r8.references.MethodReference;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Set;
-
-@Keep
-public class MissingDefinitionsDiagnostic implements Diagnostic {
-
-  private final Set<ClassReference> missingClasses;
-  private final Set<FieldReference> missingFields;
-  private final Set<MethodReference> missingMethods;
-
-  MissingDefinitionsDiagnostic(
-      Set<ClassReference> missingClasses,
-      Set<FieldReference> missingFields,
-      Set<MethodReference> missingMethods) {
-    this.missingClasses = missingClasses;
-    this.missingFields = missingFields;
-    this.missingMethods = missingMethods;
-  }
-
-  @Override
-  public Origin getOrigin() {
-    return Origin.unknown();
-  }
-
-  @Override
-  public Position getPosition() {
-    return Position.UNKNOWN;
-  }
-
-  private <T> void appendSorted(StringBuilder builder, Set<T> missing) {
-    missing.stream()
-        .map(Object::toString)
-        .sorted()
-        .forEach(item -> builder.append("  ").append(item).append(System.lineSeparator()));
-  }
-
-  public Set<ClassReference> getMissingClasses() {
-    return missingClasses;
-  }
-
-  public Set<FieldReference> getMissingFields() {
-    return missingFields;
-  }
-
-  public Set<MethodReference> getMissingMethods() {
-    return missingMethods;
-  }
-
-  @Override
-  public String getDiagnosticMessage() {
-    StringBuilder builder = new StringBuilder("Tracereferences found ");
-    List<String> components = new ArrayList<>();
-    if (missingClasses.size() > 0) {
-      components.add("" + missingClasses.size() + " classe(s)");
-    }
-    if (missingFields.size() > 0) {
-      components.add("" + missingFields.size() + " field(s)");
-    }
-    if (missingMethods.size() > 0) {
-      components.add("" + missingMethods.size() + " method(s)");
-    }
-    assert components.size() > 0;
-    for (int i = 0; i < components.size(); i++) {
-      if (i != 0) {
-        builder.append(i < components.size() - 1 ? ", " : " and ");
-      }
-      builder.append(components.get(i));
-    }
-    builder.append(" without definition");
-    builder.append(System.lineSeparator());
-    builder.append(System.lineSeparator());
-    if (missingClasses.size() > 0) {
-      builder.append("Classe(s) without definition:" + System.lineSeparator());
-      appendSorted(builder, missingClasses);
-    }
-    if (missingFields.size() > 0) {
-      builder.append("Field(s) without definition:" + System.lineSeparator());
-      appendSorted(builder, missingFields);
-    }
-    if (missingMethods.size() > 0) {
-      builder.append("Method(s) without definition:" + System.lineSeparator());
-      appendSorted(builder, missingMethods);
-    }
-    return builder.toString();
-  }
-}
diff --git a/src/main/java/com/android/tools/r8/tracereferences/TraceReferencesCommand.java b/src/main/java/com/android/tools/r8/tracereferences/TraceReferencesCommand.java
index b11a901..1c98765 100644
--- a/src/main/java/com/android/tools/r8/tracereferences/TraceReferencesCommand.java
+++ b/src/main/java/com/android/tools/r8/tracereferences/TraceReferencesCommand.java
@@ -329,7 +329,7 @@
       return this;
     }
 
-    Builder setConsumer(TraceReferencesConsumer consumer) {
+    public Builder setConsumer(TraceReferencesConsumer consumer) {
       this.consumer = consumer;
       return this;
     }
diff --git a/src/main/java/com/android/tools/r8/tracereferences/TraceReferencesResult.java b/src/main/java/com/android/tools/r8/tracereferences/TraceReferencesResult.java
index e40f3d7..e0ce86a 100644
--- a/src/main/java/com/android/tools/r8/tracereferences/TraceReferencesResult.java
+++ b/src/main/java/com/android/tools/r8/tracereferences/TraceReferencesResult.java
@@ -16,25 +16,22 @@
 import java.util.Map;
 import java.util.Set;
 
-class TraceReferencesResult {
+public class TraceReferencesResult {
 
   final Set<TracedClass> types;
   final Map<ClassReference, Set<TracedField>> fields;
   final Map<ClassReference, Set<TracedMethod>> methods;
   final Set<PackageReference> keepPackageNames;
-  final Set<Object> missingDefinition;
 
   TraceReferencesResult(
       Set<TracedClass> types,
       Map<ClassReference, Set<TracedField>> fields,
       Map<ClassReference, Set<TracedMethod>> methods,
-      Set<PackageReference> keepPackageNames,
-      Set<Object> missingDefinition) {
+      Set<PackageReference> keepPackageNames) {
     this.types = types;
     this.fields = fields;
     this.methods = methods;
     this.keepPackageNames = keepPackageNames;
-    this.missingDefinition = missingDefinition;
   }
 
   static Builder builder() {
@@ -45,33 +42,23 @@
     private final Set<TracedClass> types = new HashSet<>();
     private final Map<ClassReference, Set<TracedField>> fields = new HashMap<>();
     private final Map<ClassReference, Set<TracedMethod>> methods = new HashMap<>();
-    private final Set<Object> missingDefinition = new HashSet<>();
     private final Set<PackageReference> keepPackageNames = new HashSet<>();
 
     @Override
     public void acceptType(TracedClass tracedClass, DiagnosticsHandler handler) {
       types.add(tracedClass);
-      if (tracedClass.isMissingDefinition()) {
-        this.missingDefinition.add(tracedClass.getReference());
-      }
     }
 
     @Override
     public void acceptField(TracedField tracedField, DiagnosticsHandler handler) {
       FieldReference field = tracedField.getReference();
       fields.computeIfAbsent(field.getHolderClass(), k -> new HashSet<>()).add(tracedField);
-      if (tracedField.isMissingDefinition()) {
-        this.missingDefinition.add(field);
-      }
     }
 
     @Override
     public void acceptMethod(TracedMethod tracedMethod, DiagnosticsHandler handler) {
       MethodReference method = tracedMethod.getReference();
       methods.computeIfAbsent(method.getHolderClass(), k -> new HashSet<>()).add(tracedMethod);
-      if (tracedMethod.isMissingDefinition()) {
-        this.missingDefinition.add(method);
-      }
     }
 
     @Override
@@ -83,13 +70,7 @@
     public void finished(DiagnosticsHandler handler) {}
 
     TraceReferencesResult build() {
-      missingDefinition.forEach(
-          missingDefinition -> {
-            assert missingDefinition instanceof ClassReference
-                || missingDefinition instanceof FieldReference
-                || missingDefinition instanceof MethodReference;
-          });
-      return new TraceReferencesResult(types, fields, methods, keepPackageNames, missingDefinition);
+      return new TraceReferencesResult(types, fields, methods, keepPackageNames);
     }
   }
 }
diff --git a/src/main/java/com/android/tools/r8/tracereferences/Tracer.java b/src/main/java/com/android/tools/r8/tracereferences/Tracer.java
index 16a6bfc..1dc8da6 100644
--- a/src/main/java/com/android/tools/r8/tracereferences/Tracer.java
+++ b/src/main/java/com/android/tools/r8/tracereferences/Tracer.java
@@ -3,15 +3,18 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.tracereferences;
 
-import static com.android.tools.r8.graph.DexProgramClass.asProgramClassOrNull;
 
 import com.android.tools.r8.DiagnosticsHandler;
 import com.android.tools.r8.dex.ApplicationReader;
+import com.android.tools.r8.diagnostic.internal.MissingClassInfoImpl;
+import com.android.tools.r8.diagnostic.internal.MissingDefinitionsDiagnosticImpl;
+import com.android.tools.r8.diagnostic.internal.MissingFieldInfoImpl;
+import com.android.tools.r8.diagnostic.internal.MissingMethodInfoImpl;
 import com.android.tools.r8.features.ClassToFeatureSplitMap;
 import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
 import com.android.tools.r8.graph.DexAnnotation;
-import com.android.tools.r8.graph.DexCallSite;
 import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexClassAndField;
 import com.android.tools.r8.graph.DexClassAndMethod;
 import com.android.tools.r8.graph.DexEncodedField;
 import com.android.tools.r8.graph.DexEncodedMethod;
@@ -20,13 +23,16 @@
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.DexTypeList;
 import com.android.tools.r8.graph.DexValue;
 import com.android.tools.r8.graph.DexValue.DexValueArray;
-import com.android.tools.r8.graph.DirectMappedDexApplication;
+import com.android.tools.r8.graph.GraphLens;
+import com.android.tools.r8.graph.GraphLens.FieldLookupResult;
+import com.android.tools.r8.graph.GraphLens.MethodLookupResult;
+import com.android.tools.r8.graph.InitClassLens;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.graph.ResolutionResult;
 import com.android.tools.r8.graph.UseRegistry;
-import com.android.tools.r8.ir.desugar.LambdaDescriptor;
 import com.android.tools.r8.references.ClassReference;
 import com.android.tools.r8.references.FieldReference;
 import com.android.tools.r8.references.MethodReference;
@@ -45,8 +51,8 @@
 import com.android.tools.r8.utils.Timing;
 import java.io.IOException;
 import java.util.HashSet;
-import java.util.List;
 import java.util.Set;
+import java.util.function.Predicate;
 
 class Tracer {
 
@@ -160,13 +166,21 @@
 
   static class TracedClassImpl extends TracedReferenceBase<ClassReference, ClassAccessFlags>
       implements TracedClass {
+    private TracedClassImpl(DexType type) {
+      this(type, null);
+    }
+
     private TracedClassImpl(DexType reference, DexClass definition) {
       super(
-          Reference.classFromDescriptor(reference.toDescriptorString()),
+          reference.asClassReference(),
           definition != null ? new ClassAccessFlagsImpl(definition.getAccessFlags()) : null,
           definition == null);
     }
 
+    private TracedClassImpl(DexClass clazz) {
+      this(clazz.getType(), clazz);
+    }
+
     @Override
     public String getKindName() {
       return "type";
@@ -180,16 +194,21 @@
 
   static class TracedFieldImpl extends TracedReferenceBase<FieldReference, FieldAccessFlags>
       implements TracedField {
+    private TracedFieldImpl(DexField field) {
+      this(field, null);
+    }
+
     private TracedFieldImpl(DexField reference, DexEncodedField definition) {
       super(
-          Reference.field(
-              Reference.classFromDescriptor(reference.holder.toDescriptorString()),
-              reference.name.toString(),
-              Reference.typeFromDescriptor(reference.type.toDescriptorString())),
+          reference.asFieldReference(),
           definition != null ? new FieldAccessFlagsImpl(definition.getAccessFlags()) : null,
           definition == null);
     }
 
+    private TracedFieldImpl(DexClassAndField field) {
+      this(field.getReference(), field.getDefinition());
+    }
+
     @Override
     public String getKindName() {
       return "field";
@@ -203,6 +222,10 @@
 
   static class TracedMethodImpl extends TracedReferenceBase<MethodReference, MethodAccessFlags>
       implements TracedMethod {
+    private TracedMethodImpl(DexMethod reference) {
+      this(reference, null);
+    }
+
     private TracedMethodImpl(DexMethod reference, DexEncodedMethod definition) {
       super(
           reference.asMethodReference(),
@@ -210,6 +233,10 @@
           definition == null);
     }
 
+    private TracedMethodImpl(DexClassAndMethod method) {
+      this(method.getReference(), method.getDefinition());
+    }
+
     @Override
     public String getKindName() {
       return "method";
@@ -221,59 +248,86 @@
     }
   }
 
-  private final Set<String> descriptors;
-  private final DiagnosticsHandler diagnostics;
-  private final DirectMappedDexApplication application;
   private final AppInfoWithClassHierarchy appInfo;
+  private final DiagnosticsHandler diagnostics;
+  private final GraphLens graphLens;
+  private final InitClassLens initClassLens;
+  private final Predicate<DexType> targetPredicate;
 
-  Tracer(Set<String> descriptors, AndroidApp inputApp, DiagnosticsHandler diagnostics)
+  Tracer(Set<String> targetDescriptors, AndroidApp inputApp, DiagnosticsHandler diagnostics)
       throws IOException {
-    this.descriptors = descriptors;
-    this.diagnostics = diagnostics;
-    InternalOptions options = new InternalOptions();
-    application = new ApplicationReader(inputApp, options, Timing.empty()).read().toDirect();
-    appInfo =
+    this(
         AppInfoWithClassHierarchy.createInitialAppInfoWithClassHierarchy(
-            application,
+            new ApplicationReader(inputApp, new InternalOptions(), Timing.empty())
+                .read()
+                .toDirect(),
             ClassToFeatureSplitMap.createEmptyClassToFeatureSplitMap(),
-            MainDexInfo.none());
+            MainDexInfo.none()),
+        diagnostics,
+        GraphLens.getIdentityLens(),
+        InitClassLens.getThrowingInstance(),
+        type -> targetDescriptors.contains(type.toDescriptorString()));
+  }
+
+  private Tracer(
+      AppInfoWithClassHierarchy appInfo,
+      DiagnosticsHandler diagnostics,
+      GraphLens graphLens,
+      InitClassLens initClassLens,
+      Predicate<DexType> targetPredicate) {
+    this.appInfo = appInfo;
+    this.diagnostics = diagnostics;
+    this.graphLens = graphLens;
+    this.initClassLens = initClassLens;
+    this.targetPredicate = targetPredicate;
   }
 
   void run(TraceReferencesConsumer consumer) {
-    UseCollector useCollector = new UseCollector(appInfo.dexItemFactory(), consumer, diagnostics);
-    for (DexProgramClass clazz : application.classes()) {
-      useCollector.setContext(clazz);
+    UseCollector useCollector = new UseCollector(appInfo, consumer, diagnostics, targetPredicate);
+    for (DexProgramClass clazz : appInfo.classes()) {
       useCollector.registerSuperType(clazz, clazz.superType);
-      for (DexType implementsType : clazz.interfaces.values) {
+      for (DexType implementsType : clazz.getInterfaces()) {
         useCollector.registerSuperType(clazz, implementsType);
       }
-      clazz.forEachProgramMethod(useCollector::registerMethod);
       clazz.forEachField(useCollector::registerField);
+      clazz.forEachProgramMethod(
+          method -> {
+            useCollector.registerMethod(method);
+            useCollector.traceCode(method, graphLens, initClassLens);
+          });
     }
     consumer.finished(diagnostics);
     useCollector.reportMissingDefinitions();
   }
 
-  class UseCollector extends UseRegistry {
+  // The graph lens is intentionally only made accessible to the MethodUseCollector, since the
+  // graph lens should only be applied to the code.
+  static class UseCollector {
 
-    private DexItemFactory factory;
+    private final AppInfoWithClassHierarchy appInfo;
+    private final DexItemFactory factory;
     private final TraceReferencesConsumer consumer;
-    private DexProgramClass context;
     private final DiagnosticsHandler diagnostics;
+    private final Predicate<DexType> targetPredicate;
+
     private final Set<ClassReference> missingClasses = new HashSet<>();
     private final Set<FieldReference> missingFields = new HashSet<>();
     private final Set<MethodReference> missingMethods = new HashSet<>();
 
     UseCollector(
-        DexItemFactory factory, TraceReferencesConsumer consumer, DiagnosticsHandler diagnostics) {
-      super(factory);
-      this.factory = factory;
+        AppInfoWithClassHierarchy appInfo,
+        TraceReferencesConsumer consumer,
+        DiagnosticsHandler diagnostics,
+        Predicate<DexType> targetPredicate) {
+      this.appInfo = appInfo;
+      this.factory = appInfo.dexItemFactory();
       this.consumer = consumer;
       this.diagnostics = diagnostics;
+      this.targetPredicate = targetPredicate;
     }
 
     private boolean isTargetType(DexType type) {
-      return descriptors.contains(type.toDescriptorString());
+      return targetPredicate.test(type);
     }
 
     private void addType(DexType type) {
@@ -284,71 +338,57 @@
       if (type.isPrimitiveType() || type.isVoidType()) {
         return;
       }
+      assert type.isClassType();
+      addClassType(type);
+    }
+
+    private void addTypes(DexTypeList types) {
+      types.forEach(this::addType);
+    }
+
+    private void addClassType(DexType type) {
+      assert type.isClassType();
       DexClass clazz = appInfo.definitionFor(type);
-      TracedClassImpl tracedClass = new TracedClassImpl(type, clazz);
-      checkMissingDefinition(tracedClass);
-      if (isTargetType(type) || tracedClass.isMissingDefinition()) {
+      if (clazz != null) {
+        addClass(clazz);
+      } else {
+        TracedClassImpl tracedClass = new TracedClassImpl(type);
+        collectMissingClass(tracedClass);
         consumer.acceptType(tracedClass, diagnostics);
-        if (!tracedClass.isMissingDefinition()
-            && clazz.accessFlags.isVisibilityDependingOnPackage()) {
+      }
+    }
+
+    private void addClass(DexClass clazz) {
+      if (isTargetType(clazz.getType())) {
+        TracedClassImpl tracedClass = new TracedClassImpl(clazz);
+        consumer.acceptType(tracedClass, diagnostics);
+        if (clazz.getAccessFlags().isVisibilityDependingOnPackage()) {
           consumer.acceptPackage(
-              Reference.packageFromString(clazz.type.getPackageName()), diagnostics);
+              Reference.packageFromString(clazz.getType().getPackageName()), diagnostics);
         }
       }
     }
 
-    private void addField(DexField field) {
-      addType(field.type);
-      DexEncodedField baseField = appInfo.resolveField(field).getResolvedField();
-      if (baseField != null && baseField.getHolderType() != field.holder) {
-        field = baseField.getReference();
-      }
-      addType(field.holder);
-      TracedFieldImpl tracedField = new TracedFieldImpl(field, baseField);
-      checkMissingDefinition(tracedField);
-      if (isTargetType(field.holder) || tracedField.isMissingDefinition()) {
-        consumer.acceptField(tracedField, diagnostics);
-        if (!tracedField.isMissingDefinition()
-            && baseField.accessFlags.isVisibilityDependingOnPackage()) {
-          consumer.acceptPackage(
-              Reference.packageFromString(baseField.getHolderType().getPackageName()), diagnostics);
-        }
-      }
-    }
+    private void addSuperMethodFromTarget(DexClassAndMethod method) {
+      assert !method.isProgramMethod();
+      assert isTargetType(method.getHolderType());
 
-    private void addMethod(DexMethod method) {
-      addType(method.holder);
-      for (DexType parameterType : method.proto.parameters.values) {
-        addType(parameterType);
-      }
-      addType(method.proto.returnType);
-      DexClass holder = appInfo.definitionForHolder(method);
-      DexEncodedMethod definition = method.lookupOnClass(holder);
-      TracedMethodImpl tracedMethod = new TracedMethodImpl(method, definition);
-      if (isTargetType(method.holder) || tracedMethod.isMissingDefinition()) {
+      // There should be no need to register the types referenced from the method signature:
+      // - The return type and the parameter types are registered when visiting the source method
+      //   that overrides this target method,
+      // - The holder type is registered from visiting the extends/implements clause of the sub
+      //   class.
+
+      TracedMethodImpl tracedMethod = new TracedMethodImpl(method);
+      if (isTargetType(method.getHolderType())) {
         consumer.acceptMethod(tracedMethod, diagnostics);
-        checkMissingDefinition(tracedMethod);
-        if (!tracedMethod.isMissingDefinition()
-            && definition.accessFlags.isVisibilityDependingOnPackage()) {
+        if (method.getAccessFlags().isVisibilityDependingOnPackage()) {
           consumer.acceptPackage(
-              Reference.packageFromString(definition.getHolderType().getPackageName()),
-              diagnostics);
+              Reference.packageFromString(method.getHolderType().getPackageName()), diagnostics);
         }
       }
     }
 
-    private void checkMissingDefinition(TracedClassImpl tracedClass) {
-      collectMissing(tracedClass, missingClasses);
-    }
-
-    private void checkMissingDefinition(TracedFieldImpl tracedField) {
-      collectMissing(tracedField, missingFields);
-    }
-
-    private void checkMissingDefinition(TracedMethodImpl tracedMethod) {
-      collectMissing(tracedMethod, missingMethods);
-    }
-
     private <R, T extends TracedReferenceBase<R, ?>> void collectMissing(
         T tracedReference, Set<R> missingCollection) {
       if (tracedReference.isMissingDefinition()) {
@@ -356,163 +396,255 @@
       }
     }
 
+    private void collectMissingClass(TracedClassImpl tracedClass) {
+      assert tracedClass.isMissingDefinition();
+      collectMissing(tracedClass, missingClasses);
+    }
+
+    private void collectMissingField(TracedFieldImpl tracedField) {
+      assert tracedField.isMissingDefinition();
+      collectMissing(tracedField, missingFields);
+    }
+
+    private void collectMissingMethod(TracedMethodImpl tracedMethod) {
+      assert tracedMethod.isMissingDefinition();
+      collectMissing(tracedMethod, missingMethods);
+    }
+
     private void reportMissingDefinitions() {
       if (missingClasses.size() > 0 || missingFields.size() > 0 || missingMethods.size() > 0) {
-        diagnostics.error(
-            new MissingDefinitionsDiagnostic(missingClasses, missingFields, missingMethods));
+        MissingDefinitionsDiagnosticImpl.Builder diagnosticBuilder =
+            MissingDefinitionsDiagnosticImpl.builder();
+        missingClasses.forEach(
+            classReference ->
+                diagnosticBuilder.addMissingDefinitionInfo(
+                    MissingClassInfoImpl.builder().setClass(classReference).build()));
+        missingFields.forEach(
+            fieldReference ->
+                diagnosticBuilder.addMissingDefinitionInfo(
+                    MissingFieldInfoImpl.builder().setField(fieldReference).build()));
+        missingMethods.forEach(
+            methodReference ->
+                diagnosticBuilder.addMissingDefinitionInfo(
+                    MissingMethodInfoImpl.builder().setMethod(methodReference).build()));
+        diagnostics.error(diagnosticBuilder.build());
       }
     }
 
-    public void setContext(DexProgramClass context) {
-      this.context = context;
-    }
-
-    @Override
-    public void registerInitClass(DexType clazz) {
-      addType(clazz);
-    }
-
-    @Override
-    public void registerInvokeVirtual(DexMethod method) {
-      if (method.holder.isArrayType()) {
-        addType(method.holder);
-        return;
-      }
-      ResolutionResult resolutionResult = appInfo.unsafeResolveMethodDueToDexFormat(method);
-      DexEncodedMethod target =
-          resolutionResult.isVirtualTarget() ? resolutionResult.getSingleTarget() : null;
-      if (target != null && target.getReference() != method) {
-        addType(method.holder);
-        addMethod(target.getReference());
-      } else {
-        addMethod(method);
-      }
-    }
-
-    @Override
-    public void registerInvokeDirect(DexMethod method) {
-      addMethod(method);
-    }
-
-    @Override
-    public void registerInvokeStatic(DexMethod method) {
-      DexEncodedMethod target = appInfo.unsafeResolveMethodDueToDexFormat(method).getSingleTarget();
-      if (target != null && target.getReference() != method) {
-        addType(method.holder);
-        addMethod(target.getReference());
-      } else {
-        addMethod(method);
-      }
-    }
-
-    @Override
-    public void registerInvokeInterface(DexMethod method) {
-      registerInvokeVirtual(method);
-    }
-
-    @Override
-    public void registerInvokeSuper(DexMethod method) {
-      DexClassAndMethod superTarget = appInfo.lookupSuperTarget(method, context);
-      if (superTarget != null) {
-        addMethod(superTarget.getReference());
-      } else {
-        addMethod(method);
-      }
-    }
-
-    @Override
-    public void registerInstanceFieldWrite(DexField field) {
-      addField(field);
-    }
-
-    @Override
-    public void registerInstanceFieldRead(DexField field) {
-      addField(field);
-    }
-
-    @Override
-    public void registerNewInstance(DexType type) {
-      addType(type);
-    }
-
-    @Override
-    public void registerStaticFieldRead(DexField field) {
-      addField(field);
-    }
-
-    @Override
-    public void registerStaticFieldWrite(DexField field) {
-      addField(field);
-    }
-
-    @Override
-    public void registerTypeReference(DexType type) {
-      addType(type);
-    }
-
-    @Override
-    public void registerInstanceOf(DexType type) {
-      addType(type);
-    }
-
     private void registerField(DexEncodedField field) {
-      registerTypeReference(field.getReference().type);
+      addType(field.getType());
     }
 
     private void registerMethod(ProgramMethod method) {
-      DexClassAndMethod superTarget =
-          appInfo
-              .resolveMethodOn(method.getHolder(), method.getReference())
-              .lookupInvokeSpecialTarget(context, appInfo);
-      if (superTarget != null) {
-        addMethod(superTarget.getReference());
-      }
-      for (DexType type : method.getDefinition().getParameters()) {
-        registerTypeReference(type);
-      }
+      addTypes(method.getParameters());
+      addType(method.getReturnType());
       for (DexAnnotation annotation : method.getDefinition().annotations().annotations) {
-        if (annotation.annotation.type == appInfo.dexItemFactory().annotationThrows) {
+        if (annotation.getAnnotationType() == appInfo.dexItemFactory().annotationThrows) {
           DexValueArray dexValues = annotation.annotation.elements[0].value.asDexValueArray();
           for (DexValue dexValType : dexValues.getValues()) {
-            registerTypeReference(dexValType.asDexValueType().value);
+            addType(dexValType.asDexValueType().value);
           }
         }
       }
-      registerTypeReference(method.getDefinition().returnType());
-      method.registerCodeReferences(this);
+
+      DexClassAndMethod superTarget =
+          appInfo
+              .resolveMethodOn(method.getHolder(), method.getReference())
+              .lookupInvokeSpecialTarget(method.getHolder(), appInfo);
+      if (superTarget != null
+          && !superTarget.isProgramMethod()
+          && isTargetType(superTarget.getHolderType())) {
+        addSuperMethodFromTarget(superTarget);
+      }
+    }
+
+    private void traceCode(ProgramMethod method, GraphLens graphLens, InitClassLens initClassLens) {
+      method.registerCodeReferences(new MethodUseCollector(method, graphLens, initClassLens));
     }
 
     private void registerSuperType(DexProgramClass clazz, DexType superType) {
-      registerTypeReference(superType);
+      addType(superType);
       // If clazz overrides any methods in superType, we should keep those as well.
       clazz.forEachMethod(
           method -> {
-            ResolutionResult resolutionResult =
-                appInfo.resolveMethodOn(
-                    superType, method.getReference(), superType != clazz.superType);
-            DexEncodedMethod dexEncodedMethod = resolutionResult.getSingleTarget();
-            if (dexEncodedMethod != null) {
-              addMethod(dexEncodedMethod.getReference());
+            DexClassAndMethod resolvedMethod =
+                appInfo
+                    .resolveMethodOn(superType, method.getReference(), superType != clazz.superType)
+                    .getResolutionPair();
+            if (resolvedMethod != null
+                && !resolvedMethod.isProgramMethod()
+                && isTargetType(resolvedMethod.getHolderType())) {
+              addSuperMethodFromTarget(resolvedMethod);
             }
           });
     }
 
-    @Override
-    public void registerCallSite(DexCallSite callSite) {
-      super.registerCallSite(callSite);
+    class MethodUseCollector extends UseRegistry {
 
-      // For Lambda's, in order to find the correct use, we need to register the method for the
-      // functional interface.
-      List<DexType> directInterfaces = LambdaDescriptor.getInterfaces(callSite, appInfo);
-      if (directInterfaces != null) {
-        for (DexType directInterface : directInterfaces) {
-          DexProgramClass clazz = asProgramClassOrNull(appInfo.definitionFor(directInterface));
-          if (clazz != null) {
-            clazz.forEachProgramVirtualMethodMatching(
-                definition -> definition.getReference().name.equals(callSite.methodName),
-                this::registerMethod);
-          }
+      private final ProgramMethod context;
+      private final GraphLens graphLens;
+      private final InitClassLens initClassLens;
+
+      public MethodUseCollector(
+          ProgramMethod context, GraphLens graphLens, InitClassLens initClassLens) {
+        super(appInfo.dexItemFactory());
+        this.context = context;
+        this.graphLens = graphLens;
+        this.initClassLens = initClassLens;
+      }
+
+      // Method references.
+
+      @Override
+      public void registerInvokeDirect(DexMethod method) {
+        MethodLookupResult lookupResult = graphLens.lookupInvokeDirect(method, context);
+        assert lookupResult.getType().isDirect();
+        DexMethod rewrittenMethod = lookupResult.getReference();
+        DexClass holder = appInfo.definitionFor(rewrittenMethod.getHolderType());
+        handleRewrittenMethodReference(
+            rewrittenMethod, rewrittenMethod.lookupMemberOnClass(holder));
+      }
+
+      @Override
+      public void registerInvokeInterface(DexMethod method) {
+        MethodLookupResult lookupResult = graphLens.lookupInvokeInterface(method, context);
+        assert lookupResult.getType().isInterface();
+        handleInvokeWithDynamicDispatch(lookupResult);
+      }
+
+      @Override
+      public void registerInvokeStatic(DexMethod method) {
+        MethodLookupResult lookupResult = graphLens.lookupInvokeStatic(method, context);
+        assert lookupResult.getType().isStatic();
+        DexMethod rewrittenMethod = lookupResult.getReference();
+        DexClassAndMethod resolvedMethod =
+            appInfo.unsafeResolveMethodDueToDexFormat(rewrittenMethod).getResolutionPair();
+        handleRewrittenMethodReference(rewrittenMethod, resolvedMethod);
+      }
+
+      @Override
+      public void registerInvokeSuper(DexMethod method) {
+        MethodLookupResult lookupResult = graphLens.lookupInvokeSuper(method, context);
+        assert lookupResult.getType().isSuper();
+        DexMethod rewrittenMethod = lookupResult.getReference();
+        DexClassAndMethod superTarget = appInfo.lookupSuperTarget(rewrittenMethod, context);
+        handleRewrittenMethodReference(rewrittenMethod, superTarget);
+      }
+
+      @Override
+      public void registerInvokeVirtual(DexMethod method) {
+        MethodLookupResult lookupResult = graphLens.lookupInvokeVirtual(method, context);
+        assert lookupResult.getType().isVirtual();
+        handleInvokeWithDynamicDispatch(lookupResult);
+      }
+
+      private void handleInvokeWithDynamicDispatch(MethodLookupResult lookupResult) {
+        DexMethod method = lookupResult.getReference();
+        if (method.getHolderType().isArrayType()) {
+          assert lookupResult.getType().isVirtual();
+          addType(method.getHolderType());
+          return;
         }
+        assert lookupResult.getType().isInterface() || lookupResult.getType().isVirtual();
+        ResolutionResult resolutionResult =
+            lookupResult.getType().isInterface()
+                ? appInfo.resolveMethodOnInterface(method)
+                : appInfo.resolveMethodOnClass(method);
+        DexClassAndMethod resolvedMethod =
+            resolutionResult.isVirtualTarget() ? resolutionResult.getResolutionPair() : null;
+        handleRewrittenMethodReference(method, resolvedMethod);
+      }
+
+      private void handleRewrittenMethodReference(
+          DexMethod method, DexClassAndMethod resolvedMethod) {
+        assert resolvedMethod == null || resolvedMethod.getReference().match(method);
+        addType(method.getHolderType());
+        addTypes(method.getParameters());
+        addType(method.getReturnType());
+        if (resolvedMethod != null) {
+          if (isTargetType(resolvedMethod.getHolderType())) {
+            if (resolvedMethod.getHolderType() != method.getHolderType()) {
+              addType(resolvedMethod.getHolderType());
+            }
+            TracedMethodImpl tracedMethod = new TracedMethodImpl(resolvedMethod);
+            consumer.acceptMethod(tracedMethod, diagnostics);
+            if (resolvedMethod.getAccessFlags().isVisibilityDependingOnPackage()) {
+              consumer.acceptPackage(
+                  Reference.packageFromString(resolvedMethod.getHolderType().getPackageName()),
+                  diagnostics);
+            }
+          }
+        } else {
+          TracedMethodImpl tracedMethod = new TracedMethodImpl(method);
+          collectMissingMethod(tracedMethod);
+          consumer.acceptMethod(tracedMethod, diagnostics);
+        }
+      }
+
+      // Field references.
+
+      @Override
+      public void registerInitClass(DexType clazz) {
+        DexType rewrittenClass = graphLens.lookupType(clazz);
+        DexField clinitField = initClassLens.getInitClassField(rewrittenClass);
+        handleRewrittenFieldReference(clinitField);
+      }
+
+      @Override
+      public void registerInstanceFieldRead(DexField field) {
+        handleFieldAccess(field);
+      }
+
+      @Override
+      public void registerInstanceFieldWrite(DexField field) {
+        handleFieldAccess(field);
+      }
+
+      @Override
+      public void registerStaticFieldRead(DexField field) {
+        handleFieldAccess(field);
+      }
+
+      @Override
+      public void registerStaticFieldWrite(DexField field) {
+        handleFieldAccess(field);
+      }
+
+      private void handleFieldAccess(DexField field) {
+        FieldLookupResult lookupResult = graphLens.lookupFieldResult(field);
+        handleRewrittenFieldReference(lookupResult.getReference());
+      }
+
+      private void handleRewrittenFieldReference(DexField field) {
+        addType(field.getHolderType());
+        addType(field.getType());
+
+        DexClassAndField resolvedField = appInfo.resolveField(field).getResolutionPair();
+        if (resolvedField != null) {
+          if (isTargetType(resolvedField.getHolderType())) {
+            if (resolvedField.getHolderType() != field.getHolderType()) {
+              addClass(resolvedField.getHolder());
+            }
+            TracedFieldImpl tracedField = new TracedFieldImpl(resolvedField);
+            consumer.acceptField(tracedField, diagnostics);
+            if (resolvedField.getAccessFlags().isVisibilityDependingOnPackage()) {
+              consumer.acceptPackage(
+                  Reference.packageFromString(resolvedField.getHolderType().getPackageName()),
+                  diagnostics);
+            }
+          }
+        } else {
+          TracedFieldImpl tracedField = new TracedFieldImpl(field);
+          collectMissingField(tracedField);
+          consumer.acceptField(tracedField, diagnostics);
+        }
+      }
+
+      // Type references.
+
+      @Override
+      public void registerTypeReference(DexType type) {
+        addType(graphLens.lookupType(type));
       }
     }
   }
diff --git a/src/main/java/com/android/tools/r8/utils/InternalOptions.java b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
index 2cb1e9e..d868ed1 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -81,6 +81,7 @@
 import java.util.Map;
 import java.util.Set;
 import java.util.TreeSet;
+import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.function.BiConsumer;
 import java.util.function.BiFunction;
 import java.util.function.BiPredicate;
@@ -815,6 +816,8 @@
   /** A set of dexitems we have reported missing to dedupe warnings. */
   private final Set<DexItem> reportedMissingForDesugaring = Sets.newConcurrentHashSet();
 
+  private final AtomicBoolean reportedErrorReadingKotlinMetadataReflectively =
+      new AtomicBoolean(false);
   private final Set<DexItem> invalidLibraryClasses = Sets.newConcurrentHashSet();
 
   public RuntimeException errorMissingNestHost(DexClass clazz) {
@@ -903,6 +906,16 @@
     }
   }
 
+  public void warningReadingKotlinMetadataReflective() {
+    if (reportedErrorReadingKotlinMetadataReflectively.compareAndSet(false, true)) {
+      reporter.warning(
+          new StringDiagnostic(
+              "Could not read the kotlin metadata message reflectively which indicates the"
+                  + " compiler running in the context of a Security Manager. Not being able to"
+                  + " read the kotlin metadata will have a negative effect oncode size"));
+    }
+  }
+
   public void warningInvalidLibrarySuperclassForDesugar(
       Origin origin,
       DexType libraryType,
@@ -1176,7 +1189,6 @@
         !Version.isDevelopmentVersion()
             || System.getProperty("com.android.tools.r8.disableHorizontalClassMerging") == null;
     public boolean enableConstructorMerging = true;
-    public boolean enableJavaLambdaMerging = true;
 
     public int maxGroupSize = 30;
 
@@ -1192,10 +1204,6 @@
       this.enable = enable;
     }
 
-    public void enableJavaLambdaMerging() {
-      enableJavaLambdaMerging = true;
-    }
-
     public int getMaxGroupSize() {
       return maxGroupSize;
     }
@@ -1211,10 +1219,6 @@
     public boolean isEnabled() {
       return enable;
     }
-
-    public boolean isJavaLambdaMergingEnabled() {
-      return enableJavaLambdaMerging;
-    }
   }
 
   public static class ProtoShrinkingOptions {
@@ -1228,6 +1232,14 @@
     // See b/174530756 for more details.
     public boolean enableProtoEnumSwitchMapShrinking = true;
 
+    public void disable() {
+      enableGeneratedExtensionRegistryShrinking = false;
+      enableGeneratedMessageLiteShrinking = false;
+      enableGeneratedMessageLiteBuilderShrinking = false;
+      traverseOneOfAndRepeatedProtoFields = false;
+      enableEnumLiteProtoShrinking = false;
+    }
+
     public boolean enableRemoveProtoEnumSwitchMap() {
       return isProtoShrinkingEnabled() && enableProtoEnumSwitchMapShrinking;
     }
diff --git a/src/main/java/com/android/tools/r8/utils/IterableUtils.java b/src/main/java/com/android/tools/r8/utils/IterableUtils.java
index edf9879..1a61b44 100644
--- a/src/main/java/com/android/tools/r8/utils/IterableUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/IterableUtils.java
@@ -8,6 +8,7 @@
 import com.google.common.collect.Iterators;
 import java.util.ArrayList;
 import java.util.Collections;
+import java.util.Comparator;
 import java.util.Iterator;
 import java.util.List;
 import java.util.function.BiFunction;
@@ -74,6 +75,16 @@
     return iterable.iterator().hasNext();
   }
 
+  public static <T> T min(Iterable<T> iterable, Comparator<T> comparator) {
+    T min = null;
+    for (T element : iterable) {
+      if (min == null || comparator.compare(element, min) < 0) {
+        min = element;
+      }
+    }
+    return min;
+  }
+
   public static <T> int size(Iterable<T> iterable) {
     int result = 0;
     for (T element : iterable) {
@@ -104,7 +115,7 @@
     return Iterables.concat(singleton(t), iterable);
   }
 
-  public static <T> T flatten(T init, BiFunction<T, T, T> combine, Iterable<? extends T> iterable) {
+  public static <T> T reduce(T init, BiFunction<T, T, T> combine, Iterable<? extends T> iterable) {
     T v = init;
     for (T t : iterable) {
       v = combine.apply(v, t);
@@ -113,7 +124,7 @@
   }
 
   public static int sumInt(Iterable<Integer> iterable) {
-    return flatten(0, Integer::sum, iterable);
+    return reduce(0, Integer::sum, iterable);
   }
 
   public static <F> int sumInt(Iterable<F> iterable, Function<? super F, Integer> fn) {
@@ -121,6 +132,10 @@
     return sumInt(integers);
   }
 
+  public static <T> Iterable<T> flatten(Iterable<? extends Iterable<T>> iterable) {
+    return flatMap(iterable, Function.identity());
+  }
+
   public static <T, U> Iterable<U> flatMap(
       Iterable<T> iterable, Function<? super T, Iterable<U>> map) {
     return Iterables.concat(Iterables.transform(iterable, map::apply));
diff --git a/src/main/java/com/android/tools/r8/utils/ListUtils.java b/src/main/java/com/android/tools/r8/utils/ListUtils.java
index 62a45dd..e7c8e46 100644
--- a/src/main/java/com/android/tools/r8/utils/ListUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/ListUtils.java
@@ -4,6 +4,7 @@
 
 package com.android.tools.r8.utils;
 
+import com.google.common.collect.ImmutableList;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.List;
@@ -140,6 +141,12 @@
     return list;
   }
 
+  public static <T> ImmutableList<T> newImmutableList(ForEachable<T> forEachable) {
+    ImmutableList.Builder<T> builder = ImmutableList.builder();
+    forEachable.forEach(builder::add);
+    return builder.build();
+  }
+
   public static <T> Optional<T> removeFirstMatch(List<T> list, Predicate<T> element) {
     int index = firstIndexMatching(list, element);
     if (index >= 0) {
diff --git a/src/main/java/com/android/tools/r8/utils/Pair.java b/src/main/java/com/android/tools/r8/utils/Pair.java
index c2f3489..24007cc 100644
--- a/src/main/java/com/android/tools/r8/utils/Pair.java
+++ b/src/main/java/com/android/tools/r8/utils/Pair.java
@@ -55,4 +55,8 @@
   public String toString() {
     return "Pair{" + first + ", " + second + '}';
   }
+
+  public static <T, S> Pair<T, S> create(T t, S s) {
+    return new Pair<>(t, s);
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/utils/ReflectionHelper.java b/src/main/java/com/android/tools/r8/utils/ReflectionHelper.java
new file mode 100644
index 0000000..5e1cdbe
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/utils/ReflectionHelper.java
@@ -0,0 +1,171 @@
+// Copyright (c) 2021, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.utils;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.function.Consumer;
+
+public class ReflectionHelper {
+
+  @SuppressWarnings("unchecked")
+  public static <T> T performReflection(Object object, ReflectiveOperation<?> operation)
+      throws Exception {
+    return (T) operation.compute(object);
+  }
+
+  public static ReflectiveOperationSequenceBuilder builder() {
+    return new ReflectiveOperationSequenceBuilder();
+  }
+
+  public enum DeclaredType {
+    FIELD,
+    METHOD
+  }
+
+  public abstract static class ReflectiveOperation<Member> {
+
+    final Class<?> classForDeclaration;
+    final String declaredMember;
+    final Consumer<Member> modifier;
+    final ReflectiveOperation<?> nextOperation;
+
+    private ReflectiveOperation(
+        Class<?> classForDeclaration,
+        String declaredMember,
+        ReflectiveOperation<?> nextOperation,
+        Consumer<Member> modifier) {
+      this.classForDeclaration = classForDeclaration;
+      this.declaredMember = declaredMember;
+      this.nextOperation = nextOperation;
+      this.modifier = modifier;
+    }
+
+    public abstract Object compute(Object object) throws Exception;
+  }
+
+  public static class ReflectiveMethodOperation extends ReflectiveOperation<Method> {
+
+    private ReflectiveMethodOperation(
+        Class<?> classForDeclaration,
+        String declaredMember,
+        ReflectiveOperation<?> nextOperation,
+        Consumer<Method> modifier) {
+      super(classForDeclaration, declaredMember, nextOperation, modifier);
+    }
+
+    @Override
+    public Object compute(Object object) throws Exception {
+      Class<?> clazz = classForDeclaration == null ? object.getClass() : classForDeclaration;
+      Method declaredMethod = clazz.getDeclaredMethod(declaredMember);
+      modifier.accept(declaredMethod);
+      // The reflection helper do not support arguments at this point.
+      Object returnValue = declaredMethod.invoke(object);
+      return nextOperation != null ? nextOperation.compute(returnValue) : returnValue;
+    }
+  }
+
+  public static class ReflectiveFieldOperation extends ReflectiveOperation<Field> {
+
+    private ReflectiveFieldOperation(
+        Class<?> classForDeclaration,
+        String declaredMember,
+        ReflectiveOperation<?> nextOperation,
+        Consumer<Field> modifier) {
+      super(classForDeclaration, declaredMember, nextOperation, modifier);
+    }
+
+    @Override
+    public Object compute(Object object) throws Exception {
+      Class<?> clazz = classForDeclaration == null ? object.getClass() : classForDeclaration;
+      Field declaredField = clazz.getDeclaredField(declaredMember);
+      modifier.accept(declaredField);
+      Object fieldValue = declaredField.get(object);
+      return nextOperation != null ? nextOperation.compute(fieldValue) : fieldValue;
+    }
+  }
+
+  public static class ReflectiveOperationSequenceBuilder {
+
+    List<ReflectiveOperationBuilder> reflectiveOperationBuilderList = new ArrayList<>();
+
+    public ReflectiveOperationBuilder readMethod(String declaredMember) {
+      return add(declaredMember, DeclaredType.METHOD);
+    }
+
+    public ReflectiveOperationBuilder readField(String declaredMember) {
+      return add(declaredMember, DeclaredType.FIELD);
+    }
+
+    private ReflectiveOperationBuilder add(String declaredMember, DeclaredType declaredType) {
+      ReflectiveOperationBuilder reflectiveOperationBuilder =
+          new ReflectiveOperationBuilder(declaredMember, declaredType, this);
+      reflectiveOperationBuilderList.add(reflectiveOperationBuilder);
+      return reflectiveOperationBuilder;
+    }
+
+    public ReflectiveOperation<?> build() {
+      assert !reflectiveOperationBuilderList.isEmpty();
+      ReflectiveOperation<?> lastOperation = null;
+      for (int i = reflectiveOperationBuilderList.size() - 1; i >= 0; i--) {
+        lastOperation = reflectiveOperationBuilderList.get(i).build(lastOperation);
+      }
+      return lastOperation;
+    }
+  }
+
+  public static class ReflectiveOperationBuilder {
+
+    private final String declaredMember;
+    private final DeclaredType declaredType;
+    private boolean setAccessible = false;
+    private final ReflectiveOperationSequenceBuilder sequenceBuilder;
+
+    private ReflectiveOperationBuilder(
+        String declaredMember,
+        DeclaredType declaredType,
+        ReflectiveOperationSequenceBuilder sequenceBuilder) {
+      this.declaredMember = declaredMember;
+      this.declaredType = declaredType;
+      this.sequenceBuilder = sequenceBuilder;
+    }
+
+    public ReflectiveOperationBuilder setSetAccessible(boolean setAccessible) {
+      this.setAccessible = setAccessible;
+      return this;
+    }
+
+    public ReflectiveOperationSequenceBuilder done() {
+      return sequenceBuilder;
+    }
+
+    private ReflectiveOperation<?> build(ReflectiveOperation<?> nextOperation) {
+      if (declaredType == DeclaredType.FIELD) {
+        return new ReflectiveFieldOperation(
+            null,
+            declaredMember,
+            nextOperation,
+            field -> {
+              if (setAccessible) {
+                field.setAccessible(true);
+              }
+            });
+      } else {
+        assert declaredType == DeclaredType.METHOD;
+        return new ReflectiveMethodOperation(
+            null,
+            declaredMember,
+            nextOperation,
+            method -> {
+              if (setAccessible) {
+                method.setAccessible(true);
+              }
+            });
+      }
+    }
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/utils/Reporter.java b/src/main/java/com/android/tools/r8/utils/Reporter.java
index 4e476e3..65103ac 100644
--- a/src/main/java/com/android/tools/r8/utils/Reporter.java
+++ b/src/main/java/com/android/tools/r8/utils/Reporter.java
@@ -37,6 +37,13 @@
           || diagnosticsClassName.equals(diagnostic.getClass().getTypeName())) {
         return to;
       }
+      // Some diagnostics are exposed as interfaces, and implemented by internal implementations.
+      for (Class<?> clazz : diagnostic.getClass().getInterfaces()) {
+        if (diagnosticsClassName.equals(clazz.getSimpleName())
+            || diagnosticsClassName.equals(clazz.getTypeName())) {
+          return to;
+        }
+      }
       return level;
     }
   }
diff --git a/src/main/keep.txt b/src/main/keep.txt
index f3e6dd4..e03ef61 100644
--- a/src/main/keep.txt
+++ b/src/main/keep.txt
@@ -29,3 +29,20 @@
 
 # TODO(b/176783536): Avoid need to use -dontwarn.
 -include dontwarn.txt
+
+# TODO(b/185756596): Remove when no longer needed
+-keep class com.android.tools.r8.jetbrains.kotlinx.metadata.jvm.KotlinClassMetadata$FileFacade {
+  com.android.tools.r8.jetbrains.kotlin.Lazy packageData$delegate;
+}
+-keep class com.android.tools.r8.jetbrains.kotlinx.metadata.jvm.KotlinClassMetadata$Class {
+  com.android.tools.r8.jetbrains.kotlin.Lazy classData$delegate;
+}
+-keep class com.android.tools.r8.jetbrains.kotlinx.metadata.jvm.KotlinClassMetadata$SyntheticClass {
+  com.android.tools.r8.jetbrains.kotlin.Lazy functionData$delegate;
+}
+-keep class com.android.tools.r8.jetbrains.kotlinx.metadata.jvm.KotlinClassMetadata$MultiFileClassPart {
+  com.android.tools.r8.jetbrains.kotlin.Lazy packageData$delegate;
+}
+-keep class com.android.tools.r8.jetbrains.kotlin.SafePublicationLazyImpl {
+  java.lang.Object getValue();
+}
diff --git a/src/test/java/com/android/tools/r8/D8TestCompileResult.java b/src/test/java/com/android/tools/r8/D8TestCompileResult.java
index e36ff2d..7e8e572 100644
--- a/src/test/java/com/android/tools/r8/D8TestCompileResult.java
+++ b/src/test/java/com/android/tools/r8/D8TestCompileResult.java
@@ -4,7 +4,6 @@
 package com.android.tools.r8;
 
 import com.android.tools.r8.ToolHelper.ProcessResult;
-import com.android.tools.r8.errors.Unimplemented;
 import com.android.tools.r8.utils.AndroidApp;
 import java.util.Set;
 
@@ -29,7 +28,7 @@
 
   @Override
   public Set<String> getMainDexClasses() {
-    throw new Unimplemented();
+    return state.getMainDexClasses();
   }
 
   @Override
diff --git a/src/test/java/com/android/tools/r8/DiagnosticsChecker.java b/src/test/java/com/android/tools/r8/DiagnosticsChecker.java
index 73d1d3a..bec8829 100644
--- a/src/test/java/com/android/tools/r8/DiagnosticsChecker.java
+++ b/src/test/java/com/android/tools/r8/DiagnosticsChecker.java
@@ -22,6 +22,8 @@
 import java.util.function.Consumer;
 
 // Helper to check that a particular error occurred.
+// TODO(b/186505526): Prefer TestDiagnosticMessages.
+@Deprecated
 public class DiagnosticsChecker implements DiagnosticsHandler {
 
   public List<Diagnostic> errors = new ArrayList<>();
@@ -60,26 +62,10 @@
         diagnostics.stream().anyMatch(d -> d.getDiagnosticMessage().contains(snippet)));
   }
 
-  private static void checkNotContains(String snippet, List<Diagnostic> diagnostics) {
-    List<String> messages = ListUtils.map(diagnostics, Diagnostic::getDiagnosticMessage);
-    System.out.println("Expecting no match for '" + snippet + "'");
-    System.out.println("Diagnostics messages:\n" + messages);
-    assertTrue(
-        "Expected to *not* find snippet '"
-            + snippet
-            + "' in error messages:\n"
-            + String.join("\n", messages),
-        diagnostics.stream().noneMatch(d -> d.getDiagnosticMessage().contains(snippet)));
-  }
-
   public static void checkContains(Collection<String> snippets, List<Diagnostic> diagnostics) {
     snippets.forEach(snippet -> checkContains(snippet, diagnostics));
   }
 
-  public static void checkNotContains(Collection<String> snippets, List<Diagnostic> diagnostics) {
-    snippets.forEach(snippet -> checkNotContains(snippet, diagnostics));
-  }
-
   public void checkErrorsContains(String snippet) {
     checkContains(snippet, errors);
   }
@@ -109,19 +95,6 @@
     }
   }
 
-  public static void checkErrorDiagnostics(
-      Consumer<DiagnosticsChecker> checker, FailingRunner runner)
-      throws CompilationFailedException {
-    DiagnosticsChecker handler = new DiagnosticsChecker();
-    try {
-      runner.run(handler);
-      fail("Failure expected");
-    } catch (CompilationFailedException e) {
-      checker.accept(handler);
-      throw e;
-    }
-  }
-
   public static void checkDiagnostics(Consumer<DiagnosticsChecker> checker, FailingRunner runner)
       throws CompilationFailedException {
     DiagnosticsChecker handler = new DiagnosticsChecker();
diff --git a/src/test/java/com/android/tools/r8/L8CommandTest.java b/src/test/java/com/android/tools/r8/L8CommandTest.java
index 8bb9f30..eb44743 100644
--- a/src/test/java/com/android/tools/r8/L8CommandTest.java
+++ b/src/test/java/com/android/tools/r8/L8CommandTest.java
@@ -30,6 +30,7 @@
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
+import org.hamcrest.Matcher;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
@@ -47,6 +48,10 @@
     parameters.assertNoneRuntime();
   }
 
+  protected final Matcher<Diagnostic> cfL8NotSupportedDiagnostic =
+      diagnosticMessage(
+          containsString("L8 does not support shrinking when generating class files"));
+
   @Test(expected = CompilationFailedException.class)
   public void emptyBuilder() throws Throwable {
     verifyEmptyCommand(L8Command.builder().build());
@@ -192,8 +197,7 @@
           "--classfile");
       fail("Expected failure");
     } catch (CompilationFailedException e) {
-      diagnostics.assertErrorsMatch(
-          diagnosticMessage(containsString("not support shrinking when generating class files")));
+      diagnostics.assertErrorsMatch(cfL8NotSupportedDiagnostic);
     }
   }
 
@@ -300,8 +304,7 @@
       addProguardConfigurationString(diagnostics, ClassFileConsumer.emptyConsumer());
       fail("Expected failure");
     } catch (CompilationFailedException e) {
-      diagnostics.assertErrorsMatch(
-          diagnosticMessage(containsString("not support shrinking when generating class files")));
+      diagnostics.assertErrorsMatch(cfL8NotSupportedDiagnostic);
     }
   }
 
@@ -345,8 +348,7 @@
       addProguardConfigurationFile(diagnostics, ClassFileConsumer.emptyConsumer());
       fail("Expected failure");
     } catch (CompilationFailedException e) {
-      diagnostics.assertErrorsMatch(
-          diagnosticMessage(containsString("not support shrinking when generating class files")));
+      diagnostics.assertErrorsMatch(cfL8NotSupportedDiagnostic);
     }
   }
 
diff --git a/src/test/java/com/android/tools/r8/MarkersTest.java b/src/test/java/com/android/tools/r8/MarkersTest.java
index 2ab8a16..f1baee5 100644
--- a/src/test/java/com/android/tools/r8/MarkersTest.java
+++ b/src/test/java/com/android/tools/r8/MarkersTest.java
@@ -91,14 +91,11 @@
             markerTool(Tool.R8),
             markerCompilationMode(compilationMode),
             markerMinApi(apiLevel),
-            markerR8Mode("compatibility"));
+            markerR8Mode("full"));
     Matcher<Marker> d8Matcher =
         allOf(markerTool(Tool.D8), markerCompilationMode(compilationMode), markerMinApi(apiLevel));
-    if (shrinkDesugaredLibrary) {
-      assertMarkersMatch(markers, ImmutableList.of(l8Matcher, r8Matcher));
-    } else {
-      assertMarkersMatch(markers, ImmutableList.of(l8Matcher, d8Matcher));
-    }
+    assertMarkersMatch(
+        markers, ImmutableList.of(l8Matcher, shrinkDesugaredLibrary ? r8Matcher : d8Matcher));
   }
 
   @Test
diff --git a/src/test/java/com/android/tools/r8/R8TestBuilder.java b/src/test/java/com/android/tools/r8/R8TestBuilder.java
index 19b414a..d85d8d9 100644
--- a/src/test/java/com/android/tools/r8/R8TestBuilder.java
+++ b/src/test/java/com/android/tools/r8/R8TestBuilder.java
@@ -309,6 +309,11 @@
     return self();
   }
 
+  public T allowUnnecessaryDontWarnWildcards() {
+    return addOptionsModification(
+        options -> options.testing.allowUnnecessaryDontWarnWildcards = true);
+  }
+
   public T allowUnusedDontWarnKotlinReflectJvmInternal() {
     addOptionsModification(
         options ->
diff --git a/src/test/java/com/android/tools/r8/TestBase.java b/src/test/java/com/android/tools/r8/TestBase.java
index e638abf..0577d2f 100644
--- a/src/test/java/com/android/tools/r8/TestBase.java
+++ b/src/test/java/com/android/tools/r8/TestBase.java
@@ -4,9 +4,11 @@
 
 package com.android.tools.r8;
 
+import static com.android.tools.r8.DiagnosticsMatcher.diagnosticMessage;
 import static com.android.tools.r8.TestBuilder.getTestingAnnotations;
 import static com.android.tools.r8.utils.InternalOptions.ASM_VERSION;
 import static com.google.common.collect.Lists.cartesianProduct;
+import static org.hamcrest.CoreMatchers.containsString;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
 
@@ -119,6 +121,7 @@
 import java.util.stream.Stream;
 import java.util.zip.ZipEntry;
 import java.util.zip.ZipOutputStream;
+import org.hamcrest.Matcher;
 import org.junit.AfterClass;
 import org.junit.BeforeClass;
 import org.junit.Rule;
@@ -1810,4 +1813,9 @@
         extractor, ClassReader.SKIP_CODE | ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES);
     return extractor.getClassFileVersion();
   }
+
+  // Default messages
+  protected final Matcher<Diagnostic> cfD8NotSupportedDiagnostic =
+      diagnosticMessage(
+          containsString("Compiling to Java class files with D8 is not officially supported"));
 }
diff --git a/src/test/java/com/android/tools/r8/TestCompileResult.java b/src/test/java/com/android/tools/r8/TestCompileResult.java
index 97b1a5c..f7395d1 100644
--- a/src/test/java/com/android/tools/r8/TestCompileResult.java
+++ b/src/test/java/com/android/tools/r8/TestCompileResult.java
@@ -379,6 +379,11 @@
     return self();
   }
 
+  public CR assertWarningThatMatches(Matcher<Diagnostic> matcher) {
+    getDiagnosticMessages().assertWarningThatMatches(matcher);
+    return self();
+  }
+
   public CR assertAllWarningMessagesMatch(Matcher<String> matcher) {
     getDiagnosticMessages().assertHasWarnings().assertAllWarningsMatch(diagnosticMessage(matcher));
     return self();
diff --git a/src/test/java/com/android/tools/r8/TestDiagnosticMessagesImpl.java b/src/test/java/com/android/tools/r8/TestDiagnosticMessagesImpl.java
index b52db8d..713578f 100644
--- a/src/test/java/com/android/tools/r8/TestDiagnosticMessagesImpl.java
+++ b/src/test/java/com/android/tools/r8/TestDiagnosticMessagesImpl.java
@@ -57,11 +57,7 @@
 
   @Override
   public void warning(Diagnostic warning) {
-    // When testing D8 with class file output this warning is always emitted. Discard this, as
-    // for tests this is not relevant.
-    if (!warning.equals("Compiling to Java class files with D8 is not officially supported")) {
-      warnings.add(warning);
-    }
+    warnings.add(warning);
   }
 
   @Override
diff --git a/src/test/java/com/android/tools/r8/ToolHelper.java b/src/test/java/com/android/tools/r8/ToolHelper.java
index c3949d9..5a9dba5 100644
--- a/src/test/java/com/android/tools/r8/ToolHelper.java
+++ b/src/test/java/com/android/tools/r8/ToolHelper.java
@@ -2113,7 +2113,7 @@
         Executors.newSingleThreadExecutor(),
         appView,
         GraphLens.getIdentityLens(),
-        InitClassLens.getDefault(),
+        InitClassLens.getThrowingInstance(),
         NamingLens.getIdentityLens(),
         options,
         null);
diff --git a/src/test/java/com/android/tools/r8/dex/DebugByteCodeWriterTest.java b/src/test/java/com/android/tools/r8/dex/DebugByteCodeWriterTest.java
index 330535e..9954a41 100644
--- a/src/test/java/com/android/tools/r8/dex/DebugByteCodeWriterTest.java
+++ b/src/test/java/com/android/tools/r8/dex/DebugByteCodeWriterTest.java
@@ -52,7 +52,7 @@
         appView,
         GraphLens.getIdentityLens(),
         NamingLens.getIdentityLens(),
-        InitClassLens.getDefault(),
+        InitClassLens.getThrowingInstance(),
         new LensCodeRewriterUtils(appView),
         Collections.emptyList(),
         Collections.emptyList(),
diff --git a/src/test/java/com/android/tools/r8/dex/SharedClassWritingTest.java b/src/test/java/com/android/tools/r8/dex/SharedClassWritingTest.java
deleted file mode 100644
index 1584f41..0000000
--- a/src/test/java/com/android/tools/r8/dex/SharedClassWritingTest.java
+++ /dev/null
@@ -1,243 +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.dex;
-
-import com.android.tools.r8.ByteDataView;
-import com.android.tools.r8.DexFilePerClassFileConsumer;
-import com.android.tools.r8.DiagnosticsHandler;
-import com.android.tools.r8.code.ConstString;
-import com.android.tools.r8.code.Instruction;
-import com.android.tools.r8.code.ReturnVoid;
-import com.android.tools.r8.graph.AppInfo;
-import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.ClassAccessFlags;
-import com.android.tools.r8.graph.DexAnnotationSet;
-import com.android.tools.r8.graph.DexApplication;
-import com.android.tools.r8.graph.DexCode;
-import com.android.tools.r8.graph.DexCode.Try;
-import com.android.tools.r8.graph.DexCode.TryHandler;
-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.DexProgramClass;
-import com.android.tools.r8.graph.DexString;
-import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.graph.DexTypeList;
-import com.android.tools.r8.graph.DirectMappedDexApplication;
-import com.android.tools.r8.graph.GenericSignature.ClassSignature;
-import com.android.tools.r8.graph.GenericSignature.MethodTypeSignature;
-import com.android.tools.r8.graph.GraphLens;
-import com.android.tools.r8.graph.InitClassLens;
-import com.android.tools.r8.graph.LazyLoadedDexApplication;
-import com.android.tools.r8.graph.MethodAccessFlags;
-import com.android.tools.r8.graph.ParameterAnnotationsList;
-import com.android.tools.r8.naming.NamingLens;
-import com.android.tools.r8.origin.SynthesizedOrigin;
-import com.android.tools.r8.utils.DescriptorUtils;
-import com.android.tools.r8.utils.InternalOptions;
-import com.android.tools.r8.utils.Reporter;
-import com.android.tools.r8.utils.StringUtils;
-import com.android.tools.r8.utils.ThreadUtils;
-import com.android.tools.r8.utils.Timing;
-import com.google.common.collect.Sets;
-import java.io.IOException;
-import java.nio.ByteBuffer;
-import java.util.ArrayDeque;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.Deque;
-import java.util.List;
-import java.util.Set;
-import java.util.concurrent.ExecutionException;
-import java.util.concurrent.ExecutorService;
-import org.junit.Assert;
-import org.junit.Before;
-import org.junit.Test;
-
-public class SharedClassWritingTest {
-
-  private final static String PREFIX = "A";
-  private final static int NUMBER_OF_FILES = 500;
-
-  DexItemFactory dexItemFactory = new DexItemFactory();
-
-  private DexString[] strings;
-
-  @Before
-  public void generateStringArray() {
-    strings = new DexString[Constants.MAX_NON_JUMBO_INDEX + 100];
-    for (int i = 0; i < strings.length; i++) {
-      // Format i as string with common prefix and leading 0's so that they are in the array
-      // in lexicographic order.
-      String string = PREFIX + StringUtils.zeroPrefix(i, 8);
-      strings[i] = dexItemFactory.createString(string);
-    }
-  }
-
-  private DexEncodedMethod makeMethod(DexType holder, int stringCount, int startOffset) {
-    assert stringCount + startOffset < strings.length;
-    Instruction[] instructions = new Instruction[stringCount + 1];
-    for (int i = 0; i < stringCount; i++) {
-      instructions[i] = new ConstString(0, strings[startOffset + i]);
-    }
-    instructions[stringCount] = new ReturnVoid();
-    DexCode code = new DexCode(1, 0, 0, instructions, new Try[0], new TryHandler[0], null);
-    return new DexEncodedMethod(
-        dexItemFactory.createMethod(
-            holder, dexItemFactory.createProto(dexItemFactory.voidType), "theMethod"),
-        MethodAccessFlags.fromSharedAccessFlags(Constants.ACC_PUBLIC, false),
-        MethodTypeSignature.noSignature(),
-        DexAnnotationSet.empty(),
-        ParameterAnnotationsList.empty(),
-        code);
-  }
-
-  private DexProgramClass makeClass(
-      InternalOptions options,
-      String name,
-      int stringCount,
-      int startOffset,
-      Collection<DexProgramClass> synthesizedFrom) {
-    String desc = DescriptorUtils.javaTypeToDescriptor(name);
-    DexType type = dexItemFactory.createType(desc);
-    DexProgramClass programClass =
-        new DexProgramClass(
-            type,
-            null,
-            new SynthesizedOrigin("test", getClass()),
-            ClassAccessFlags.fromSharedAccessFlags(Constants.ACC_PUBLIC),
-            dexItemFactory.objectType,
-            DexTypeList.empty(),
-            null,
-            null,
-            Collections.emptyList(),
-            null,
-            Collections.emptyList(),
-            ClassSignature.noSignature(),
-            DexAnnotationSet.empty(),
-            DexEncodedField.EMPTY_ARRAY,
-            DexEncodedField.EMPTY_ARRAY,
-            DexEncodedMethod.EMPTY_ARRAY,
-            new DexEncodedMethod[] {makeMethod(type, stringCount, startOffset)},
-            false,
-            DexProgramClass::invalidChecksumRequest);
-    return programClass;
-  }
-
-  // TODO(b/181636450): Reconsider this test as it no longer reflects the compiler synthetics.
-  @Test
-  public void manyFilesWithSharedSynthesizedClass() throws ExecutionException, IOException {
-    InternalOptions options = new InternalOptions(dexItemFactory, new Reporter());
-
-    // Create classes that all reference enough strings to overflow the index, but are all
-    // at different offsets in the strings array. This ensures we trigger multiple rounds of
-    // rewrites.
-    List<DexProgramClass> classes = new ArrayList<>();
-    for (int i = 0; i < NUMBER_OF_FILES; i++) {
-      classes.add(
-          makeClass(
-              options,
-              "Class" + i,
-              Constants.MAX_NON_JUMBO_INDEX - 1,
-              i % 100,
-              Collections.emptyList()));
-    }
-
-    // Create a shared class that references strings above the maximum.
-    DexProgramClass sharedSynthesizedClass =
-        makeClass(options, "SharedSynthesized", 100, Constants.MAX_NON_JUMBO_INDEX - 1, classes);
-
-    LazyLoadedDexApplication.Builder builder =
-        DirectMappedDexApplication.builder(options, Timing.empty());
-    builder.addSynthesizedClass(sharedSynthesizedClass);
-    classes.forEach(builder::addProgramClass);
-    DexApplication application = builder.build();
-    AppView<AppInfo> appView = AppView.createForD8(AppInfo.createInitialAppInfo(application));
-    classes.forEach(
-        c -> appView.getSyntheticItems().addLegacySyntheticClass(sharedSynthesizedClass, c));
-
-    CollectInfoConsumer consumer = new CollectInfoConsumer();
-    options.programConsumer = consumer;
-    ApplicationWriter writer =
-        new ApplicationWriter(
-            appView,
-            null,
-            GraphLens.getIdentityLens(),
-            InitClassLens.getDefault(),
-            NamingLens.getIdentityLens(),
-            null);
-    ExecutorService executorService = ThreadUtils.getExecutorService(options);
-    writer.write(executorService);
-    List<Set<String>> generatedDescriptors = consumer.getDescriptors();
-    // Check all files present.
-    Assert.assertEquals(NUMBER_OF_FILES, generatedDescriptors.size());
-    // And each file contains two classes of which one is the shared one.
-    for (Set<String> classDescriptors : generatedDescriptors) {
-      Assert.assertEquals(2, classDescriptors.size());
-      Assert
-          .assertTrue(classDescriptors.contains(sharedSynthesizedClass.type.toDescriptorString()));
-    }
-  }
-
-  private static class CollectInfoConsumer implements DexFilePerClassFileConsumer {
-
-    private final List<Set<String>> descriptors = new ArrayList<>();
-
-    private final Deque<ByteBuffer> freeBuffers = new ArrayDeque<>();
-    private final Set<ByteBuffer> activeBuffers = Sets.newIdentityHashSet();
-
-    @Override
-    public ByteBuffer acquireByteBuffer(int capacity) {
-      synchronized (freeBuffers) {
-        ByteBuffer buffer = freeBuffers.pollFirst();
-        // Ensure the buffer has sufficient capacity, eg, skip buffers that are too small.
-        if (buffer != null && buffer.capacity() < capacity) {
-          List<ByteBuffer> small = new ArrayList<>(freeBuffers.size());
-          do {
-            small.add(buffer);
-            buffer = freeBuffers.pollFirst();
-          } while (buffer != null && buffer.capacity() < capacity);
-          freeBuffers.addAll(small);
-        }
-        if (buffer == null) {
-          buffer = ByteBuffer.allocate(capacity);
-        }
-        assert !activeBuffers.contains(buffer);
-        activeBuffers.add(buffer);
-        return buffer;
-      }
-    }
-
-    @Override
-    public void releaseByteBuffer(ByteBuffer buffer) {
-      synchronized (freeBuffers) {
-        assert activeBuffers.contains(buffer);
-        activeBuffers.remove(buffer);
-        buffer.position(0);
-        freeBuffers.offerFirst(buffer);
-      }
-    }
-
-    @Override
-    public void accept(
-        String primaryClassDescriptor,
-        ByteDataView data,
-        Set<String> descriptors,
-        DiagnosticsHandler handler) {
-      addDescriptors(descriptors);
-    }
-
-    synchronized void addDescriptors(Set<String> descriptors) {
-      this.descriptors.add(descriptors);
-    }
-
-    public List<Set<String>> getDescriptors() {
-      return descriptors;
-    }
-
-    @Override
-    public void finished(DiagnosticsHandler handler) {}
-  }
-}
diff --git a/src/test/java/com/android/tools/r8/diagnosticinspector/AbsentDiagnosticSubject.java b/src/test/java/com/android/tools/r8/diagnosticinspector/AbsentDiagnosticSubject.java
deleted file mode 100644
index f94c8f4..0000000
--- a/src/test/java/com/android/tools/r8/diagnosticinspector/AbsentDiagnosticSubject.java
+++ /dev/null
@@ -1,17 +0,0 @@
-// Copyright (c) 2021, 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.diagnosticinspector;
-
-import static org.junit.Assert.fail;
-
-import com.android.tools.r8.errors.Unreachable;
-
-public class AbsentDiagnosticSubject implements DiagnosticSubject {
-  @Override
-  public FoundMissingDefinitionsDiagnosticSubject assertIsMissingDefinitionsDiagnostic() {
-    fail("Expected MissingDefinitionsDiagnostic, but was absent");
-    throw new Unreachable();
-  }
-}
diff --git a/src/test/java/com/android/tools/r8/diagnosticinspector/DiagnosticSubject.java b/src/test/java/com/android/tools/r8/diagnosticinspector/DiagnosticSubject.java
index f7123eb..c560843 100644
--- a/src/test/java/com/android/tools/r8/diagnosticinspector/DiagnosticSubject.java
+++ b/src/test/java/com/android/tools/r8/diagnosticinspector/DiagnosticSubject.java
@@ -7,4 +7,6 @@
 public interface DiagnosticSubject {
 
   FoundMissingDefinitionsDiagnosticSubject assertIsMissingDefinitionsDiagnostic();
+
+  FoundStringDiagnosticSubject assertIsStringDiagnostic();
 }
diff --git a/src/test/java/com/android/tools/r8/diagnosticinspector/FoundDiagnosticSubject.java b/src/test/java/com/android/tools/r8/diagnosticinspector/FoundDiagnosticSubject.java
index 427bef0..42dff98 100644
--- a/src/test/java/com/android/tools/r8/diagnosticinspector/FoundDiagnosticSubject.java
+++ b/src/test/java/com/android/tools/r8/diagnosticinspector/FoundDiagnosticSubject.java
@@ -9,6 +9,7 @@
 
 import com.android.tools.r8.Diagnostic;
 import com.android.tools.r8.diagnostic.MissingDefinitionsDiagnostic;
+import com.android.tools.r8.utils.StringDiagnostic;
 
 public class FoundDiagnosticSubject<D extends Diagnostic> implements DiagnosticSubject {
 
@@ -27,4 +28,10 @@
     assertThat(diagnostic, diagnosticType(MissingDefinitionsDiagnostic.class));
     return new FoundMissingDefinitionsDiagnosticSubject((MissingDefinitionsDiagnostic) diagnostic);
   }
+
+  @Override
+  public FoundStringDiagnosticSubject assertIsStringDiagnostic() {
+    assertThat(diagnostic, diagnosticType(StringDiagnostic.class));
+    return new FoundStringDiagnosticSubject((StringDiagnostic) diagnostic);
+  }
 }
diff --git a/src/test/java/com/android/tools/r8/diagnosticinspector/FoundMissingDefinitionsDiagnosticSubject.java b/src/test/java/com/android/tools/r8/diagnosticinspector/FoundMissingDefinitionsDiagnosticSubject.java
index 84e3f4b..28c8fbd 100644
--- a/src/test/java/com/android/tools/r8/diagnosticinspector/FoundMissingDefinitionsDiagnosticSubject.java
+++ b/src/test/java/com/android/tools/r8/diagnosticinspector/FoundMissingDefinitionsDiagnosticSubject.java
@@ -13,7 +13,11 @@
 import com.android.tools.r8.diagnostic.MissingDefinitionContext;
 import com.android.tools.r8.diagnostic.MissingDefinitionInfo;
 import com.android.tools.r8.diagnostic.MissingDefinitionsDiagnostic;
+import com.android.tools.r8.diagnostic.MissingFieldInfo;
+import com.android.tools.r8.diagnostic.MissingMethodInfo;
 import com.android.tools.r8.references.ClassReference;
+import com.android.tools.r8.references.FieldReference;
+import com.android.tools.r8.references.MethodReference;
 import com.android.tools.r8.references.Reference;
 import java.util.Arrays;
 import java.util.HashMap;
@@ -24,15 +28,26 @@
     extends FoundDiagnosticSubject<MissingDefinitionsDiagnostic> {
 
   private final Map<ClassReference, MissingClassInfo> missingClasses = new HashMap<>();
+  private final Map<FieldReference, MissingFieldInfo> missingFields = new HashMap<>();
+  private final Map<MethodReference, MissingMethodInfo> missingMethods = new HashMap<>();
 
   public FoundMissingDefinitionsDiagnosticSubject(MissingDefinitionsDiagnostic diagnostic) {
     super(diagnostic);
     diagnostic.getMissingDefinitions().stream()
-        .filter(MissingDefinitionInfo::isMissingClass)
-        .map(MissingDefinitionInfo::asMissingClass)
         .forEach(
-            missingClassInfo ->
-                missingClasses.put(missingClassInfo.getClassReference(), missingClassInfo));
+            missingDefinitionInfo -> {
+              if (missingDefinitionInfo.isMissingClass()) {
+                MissingClassInfo missingClassInfo = missingDefinitionInfo.asMissingClass();
+                missingClasses.put(missingClassInfo.getClassReference(), missingClassInfo);
+              } else if (missingDefinitionInfo.isMissingField()) {
+                MissingFieldInfo missingFieldInfo = missingDefinitionInfo.asMissingField();
+                missingFields.put(missingFieldInfo.getFieldReference(), missingFieldInfo);
+              } else {
+                assert missingDefinitionInfo.isMissingMethod();
+                MissingMethodInfo missingMethodInfo = missingDefinitionInfo.asMissingMethod();
+                missingMethods.put(missingMethodInfo.getMethodReference(), missingMethodInfo);
+              }
+            });
   }
 
   public FoundMissingDefinitionsDiagnosticSubject assertHasMessage(String expectedMessage) {
@@ -62,8 +77,65 @@
         missingClassInfoSubject -> missingClassInfoSubject.assertExactContexts(expectedContexts));
   }
 
+  public FoundMissingDefinitionsDiagnosticSubject assertIsAllMissingClasses(Class<?>... classes) {
+    for (Class<?> clazz : classes) {
+      assertIsMissingClass(clazz);
+    }
+    return assertNumberOfMissingClasses(classes.length);
+  }
+
+  public FoundMissingDefinitionsDiagnosticSubject assertIsMissingField(
+      FieldReference fieldReference) {
+    assertTrue(missingFields.containsKey(fieldReference));
+    return this;
+  }
+
+  public FoundMissingDefinitionsDiagnosticSubject assertIsAllMissingFields(
+      FieldReference... fieldReferences) {
+    for (FieldReference fieldReference : fieldReferences) {
+      assertIsMissingField(fieldReference);
+    }
+    return assertNumberOfMissingFields(fieldReferences.length);
+  }
+
+  public FoundMissingDefinitionsDiagnosticSubject assertIsMissingMethod(
+      MethodReference methodReference) {
+    assertTrue(missingMethods.containsKey(methodReference));
+    return this;
+  }
+
+  public FoundMissingDefinitionsDiagnosticSubject assertIsAllMissingMethods(
+      MethodReference... methodReferences) {
+    for (MethodReference methodReference : methodReferences) {
+      assertIsMissingMethod(methodReference);
+    }
+    return assertNumberOfMissingMethods(methodReferences.length);
+  }
+
+  public FoundMissingDefinitionsDiagnosticSubject assertNoMissingClasses() {
+    return assertNumberOfMissingClasses(0);
+  }
+
+  public FoundMissingDefinitionsDiagnosticSubject assertNoMissingFields() {
+    return assertNumberOfMissingFields(0);
+  }
+
+  public FoundMissingDefinitionsDiagnosticSubject assertNoMissingMethods() {
+    return assertNumberOfMissingMethods(0);
+  }
+
   public FoundMissingDefinitionsDiagnosticSubject assertNumberOfMissingClasses(int expected) {
-    assertEquals(expected, getDiagnostic().getMissingDefinitions().size());
+    assertEquals(expected, missingClasses.size());
+    return this;
+  }
+
+  public FoundMissingDefinitionsDiagnosticSubject assertNumberOfMissingFields(int expected) {
+    assertEquals(expected, missingFields.size());
+    return this;
+  }
+
+  public FoundMissingDefinitionsDiagnosticSubject assertNumberOfMissingMethods(int expected) {
+    assertEquals(expected, missingMethods.size());
     return this;
   }
 
diff --git a/src/test/java/com/android/tools/r8/diagnosticinspector/FoundStringDiagnosticSubject.java b/src/test/java/com/android/tools/r8/diagnosticinspector/FoundStringDiagnosticSubject.java
new file mode 100644
index 0000000..fb90421
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/diagnosticinspector/FoundStringDiagnosticSubject.java
@@ -0,0 +1,21 @@
+// Copyright (c) 2021, 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.diagnosticinspector;
+
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.utils.StringDiagnostic;
+
+public class FoundStringDiagnosticSubject extends FoundDiagnosticSubject<StringDiagnostic> {
+
+  public FoundStringDiagnosticSubject(StringDiagnostic diagnostic) {
+    super(diagnostic);
+  }
+
+  public FoundStringDiagnosticSubject assertHasMessage(String expectedMessage) {
+    assertEquals(expectedMessage, getDiagnostic().getDiagnosticMessage());
+    return this;
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/graph/genericsignature/GenericSignaturePartialTypeArgumentApplierTest.java b/src/test/java/com/android/tools/r8/graph/genericsignature/GenericSignaturePartialTypeArgumentApplierTest.java
new file mode 100644
index 0000000..f3cee7b
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/graph/genericsignature/GenericSignaturePartialTypeArgumentApplierTest.java
@@ -0,0 +1,82 @@
+// Copyright (c) 2021, 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.genericsignature;
+
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestDiagnosticMessages;
+import com.android.tools.r8.TestDiagnosticMessagesImpl;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.GenericSignature;
+import com.android.tools.r8.graph.GenericSignature.ClassSignature;
+import com.android.tools.r8.graph.GenericSignature.MethodTypeSignature;
+import com.android.tools.r8.graph.GenericSignaturePartialTypeArgumentApplier;
+import com.android.tools.r8.origin.Origin;
+import com.google.common.collect.ImmutableMap;
+import java.util.Map;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class GenericSignaturePartialTypeArgumentApplierTest extends TestBase {
+
+  private final TestParameters parameters;
+  private final DexItemFactory itemFactory = new DexItemFactory();
+  private final DexType objectType = itemFactory.objectType;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withNoneRuntime().build();
+  }
+
+  public GenericSignaturePartialTypeArgumentApplierTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testVariablesInOuterPosition() {
+    runTest(
+            ImmutableMap.of("T", objectType, "R", objectType),
+            "(TT;)TR;",
+            "(Ljava/lang/Object;)Ljava/lang/Object;")
+        .assertNoMessages();
+  }
+
+  @Test
+  public void testVariablesInInnerPosition() {
+    runTest(
+            ImmutableMap.of("T", objectType, "R", objectType),
+            "(LList<TT;>;)LList<TR;>;",
+            "(LList<*>;)LList<*>;")
+        .assertNoMessages();
+  }
+
+  private TestDiagnosticMessages runTest(
+      Map<String, DexType> substitutions,
+      String initialSignature,
+      String expectedRewrittenSignature) {
+    GenericSignaturePartialTypeArgumentApplier argumentApplier =
+        GenericSignaturePartialTypeArgumentApplier.build(
+            objectType, ClassSignature.noSignature(), substitutions);
+    TestDiagnosticMessagesImpl diagnosticsHandler = new TestDiagnosticMessagesImpl();
+    MethodTypeSignature methodTypeSignature =
+        argumentApplier.visitMethodSignature(
+            GenericSignature.parseMethodSignature(
+                "foo", initialSignature, Origin.unknown(), itemFactory, diagnosticsHandler));
+    diagnosticsHandler.assertNoMessages();
+    String rewrittenSignature =
+        argumentApplier.visitMethodSignature(methodTypeSignature).toString();
+    assertEquals(expectedRewrittenSignature, rewrittenSignature);
+    GenericSignature.parseMethodSignature(
+        "foo", rewrittenSignature, Origin.unknown(), itemFactory, diagnosticsHandler);
+    return diagnosticsHandler;
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/graph/genericsignature/GenericSignaturePrunedOuterTest.java b/src/test/java/com/android/tools/r8/graph/genericsignature/GenericSignaturePrunedOuterTest.java
index 608b8d3..ebca08d 100644
--- a/src/test/java/com/android/tools/r8/graph/genericsignature/GenericSignaturePrunedOuterTest.java
+++ b/src/test/java/com/android/tools/r8/graph/genericsignature/GenericSignaturePrunedOuterTest.java
@@ -55,20 +55,9 @@
   public void checkSignatures(CodeInspector inspector) {
     checkSignature(
         inspector.clazz(Bar.class.getTypeName() + "$1"),
-        "L"
-            + binaryName(Foo.class)
-            + "<"
-            + descriptor(Object.class)
-            + descriptor(Main.class)
-            + ">;");
+        "L" + binaryName(Foo.class) + "<*" + descriptor(Main.class) + ">;");
     checkSignature(
-        inspector.clazz(Bar.class.getTypeName() + "$2"),
-        "L"
-            + binaryName(Foo.class)
-            + "<"
-            + descriptor(Object.class)
-            + descriptor(Object.class)
-            + ">;");
+        inspector.clazz(Bar.class.getTypeName() + "$2"), "L" + binaryName(Foo.class) + "<**>;");
   }
 
   private void checkSignature(ClassSubject classSubject, String expectedSignature) {
diff --git a/src/test/java/com/android/tools/r8/internal/ClankDepsTest.java b/src/test/java/com/android/tools/r8/internal/ClankDepsTest.java
index cebfab3..82570e9 100644
--- a/src/test/java/com/android/tools/r8/internal/ClankDepsTest.java
+++ b/src/test/java/com/android/tools/r8/internal/ClankDepsTest.java
@@ -41,8 +41,9 @@
         .addDontWarn("dalvik.system.VMStack")
         .addDontWarn("zzz.com.facebook.litho.R$id")
         .addDontWarn("com.google.android.libraries.elements.R$id")
-        .allowUnusedProguardConfigurationRules()
         .allowUnusedDontWarnPatterns()
+        .allowUnusedProguardConfigurationRules()
+        .allowUnnecessaryDontWarnWildcards()
         .setMinApi(AndroidApiLevel.N)
         .compileWithExpectedDiagnostics(TestDiagnosticMessages::assertOnlyInfos);
   }
diff --git a/src/test/java/com/android/tools/r8/jasmin/InvalidDebugInfoTests.java b/src/test/java/com/android/tools/r8/jasmin/InvalidDebugInfoTests.java
index edef7bf..36c9ba5 100644
--- a/src/test/java/com/android/tools/r8/jasmin/InvalidDebugInfoTests.java
+++ b/src/test/java/com/android/tools/r8/jasmin/InvalidDebugInfoTests.java
@@ -8,7 +8,6 @@
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 
-import com.android.tools.r8.Diagnostic;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestDiagnosticMessages;
 import com.android.tools.r8.TestParameters;
@@ -20,7 +19,6 @@
 import com.android.tools.r8.utils.StringUtils;
 import com.google.common.collect.ImmutableList;
 import java.util.List;
-import org.hamcrest.Matcher;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
@@ -49,10 +47,6 @@
     options.testing.forceIRForCfToCfDesugar = true;
   }
 
-  private final Matcher<Diagnostic> cfNotSupportedDiagnostic =
-      diagnosticMessage(
-          containsString("Compiling to Java class files with D8 is not officially supported"));
-
   private void assertInvalidTypeMessage(TestDiagnosticMessages diagnostics) {
     assertInvalidInfoMessages(diagnostics, "Attempt to define local of type");
   }
@@ -64,7 +58,7 @@
   private void assertInvalidInfoMessages(TestDiagnosticMessages diagnostics, String message) {
     if (parameters.isCfRuntime()) {
       diagnostics.assertNoErrors();
-      diagnostics.assertWarningsMatch(cfNotSupportedDiagnostic);
+      diagnostics.assertWarningsMatch(cfD8NotSupportedDiagnostic);
     } else {
       diagnostics.assertOnlyInfos();
     }
@@ -79,7 +73,7 @@
   private void assertNoMessages(TestDiagnosticMessages diagnostics) {
     if (parameters.isCfRuntime()) {
       diagnostics.assertOnlyWarnings();
-      diagnostics.assertWarningsMatch(cfNotSupportedDiagnostic);
+      diagnostics.assertWarningsMatch(cfD8NotSupportedDiagnostic);
     } else {
       diagnostics.assertNoMessages();
     }
diff --git a/src/test/java/com/android/tools/r8/kotlin/coroutines/KotlinxCoroutinesTestRunner.java b/src/test/java/com/android/tools/r8/kotlin/coroutines/KotlinxCoroutinesTestRunner.java
index 70ae682..11ad201 100644
--- a/src/test/java/com/android/tools/r8/kotlin/coroutines/KotlinxCoroutinesTestRunner.java
+++ b/src/test/java/com/android/tools/r8/kotlin/coroutines/KotlinxCoroutinesTestRunner.java
@@ -92,7 +92,12 @@
                 "org.junit.runners.model.Statement",
                 "org.junit.runners.model.TestTimedOutException")
             .compile()
-            .inspect(inspector -> assertEqualMetadata(new CodeInspector(BASE_LIBRARY), inspector))
+            .inspect(
+                inspector ->
+                    assertEqualMetadata(
+                        new CodeInspector(BASE_LIBRARY),
+                        inspector,
+                        (addedStrings, addedNonInitStrings) -> {}))
             .writeToZip();
     Path testJar = compileTestSources(baseJar);
     runTestsInJar(testJar, baseJar);
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/KotlinMetadataTestBase.java b/src/test/java/com/android/tools/r8/kotlin/metadata/KotlinMetadataTestBase.java
index e9324e9..996dc79 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/KotlinMetadataTestBase.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/KotlinMetadataTestBase.java
@@ -3,11 +3,13 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.kotlin.metadata;
 
+import static com.android.tools.r8.utils.FunctionUtils.ignoreArgument;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
 import static junit.framework.TestCase.assertNotNull;
 import static org.hamcrest.CoreMatchers.anyOf;
 import static org.hamcrest.CoreMatchers.equalTo;
 import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNull;
 
 import com.android.tools.r8.KotlinTestBase;
@@ -15,9 +17,19 @@
 import com.android.tools.r8.TestCompileResult;
 import com.android.tools.r8.kotlin.KotlinMetadataWriter;
 import com.android.tools.r8.utils.DescriptorUtils;
+import com.android.tools.r8.utils.IntBox;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.android.tools.r8.utils.codeinspector.FoundClassSubject;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.function.BiConsumer;
+import java.util.stream.Collectors;
 import junit.framework.TestCase;
 import kotlinx.metadata.jvm.KotlinClassHeader;
 import kotlinx.metadata.jvm.KotlinClassMetadata;
@@ -31,7 +43,6 @@
   static final String PKG = KotlinMetadataTestBase.class.getPackage().getName();
   static final String PKG_PREFIX = DescriptorUtils.getBinaryNameFromJavaType(PKG);
 
-  static final String KT_ANY = "Lkotlin/Any;";
   static final String KT_ARRAY = "Lkotlin/Array;";
   static final String KT_CHAR_SEQUENCE = "Lkotlin/CharSequence;";
   static final String KT_STRING = "Lkotlin/String;";
@@ -44,8 +55,15 @@
   static final String KT_COMPARABLE = "Lkotlin/Comparable;";
 
   public void assertEqualMetadata(
-      CodeInspector originalInspector, CodeInspector rewrittenInspector) {
-    for (FoundClassSubject clazzSubject : originalInspector.allClasses()) {
+      CodeInspector originalInspector,
+      CodeInspector rewrittenInspector,
+      BiConsumer<Integer, Integer> addedStringsInspector) {
+    IntBox addedStrings = new IntBox();
+    IntBox addedNonInitStrings = new IntBox();
+    for (FoundClassSubject clazzSubject :
+        originalInspector.allClasses().stream()
+            .sorted(Comparator.comparing(FoundClassSubject::getFinalName))
+            .collect(Collectors.toList())) {
       ClassSubject r8Clazz = rewrittenInspector.clazz(clazzSubject.getOriginalName());
       assertThat(r8Clazz, isPresent());
       KotlinClassMetadata originalMetadata = clazzSubject.getKotlinClassMetadata();
@@ -58,14 +76,47 @@
       KotlinClassHeader originalHeader = originalMetadata.getHeader();
       KotlinClassHeader rewrittenHeader = rewrittenMetadata.getHeader();
       TestCase.assertEquals(originalHeader.getKind(), rewrittenHeader.getKind());
-      // TODO(b/154199572): Should we check for meta-data version?
-      TestCase.assertEquals(originalHeader.getPackageName(), rewrittenHeader.getPackageName());
-      // We cannot assert equality of the data since it may be ordered differently. Instead we use
-      // the KotlinMetadataWriter.
+
+      // We cannot assert equality of the data since it may be ordered differently. However, we
+      // will check for the changes to the string pool and then validate the same parsing
+      // by using the KotlinMetadataWriter.
+      Map<String, List<String>> descriptorToNames = new HashMap<>();
+      clazzSubject.forAllMethods(
+          method ->
+              descriptorToNames
+                  .computeIfAbsent(
+                      method.getFinalSignature().toDescriptor(), ignoreArgument(ArrayList::new))
+                  .add(method.getFinalName()));
+      HashSet<String> originalStrings = new HashSet<>(Arrays.asList(originalHeader.getData2()));
+      HashSet<String> rewrittenStrings = new HashSet<>(Arrays.asList(rewrittenHeader.getData2()));
+      rewrittenStrings.forEach(
+          rewrittenString -> {
+            if (originalStrings.contains(rewrittenString)) {
+              return;
+            }
+            addedStrings.increment();
+            // The init is not needed by if we cannot lookup the descriptor in the table, we have
+            // to emit it and that adds <init>.
+            if (rewrittenString.equals("<init>")) {
+              return;
+            }
+            // We have decided to keep invalid signatures, but they will end up in the string pool
+            // when we emit them. The likely cause of them not being there in the first place seems
+            // to be that they are not correctly written in the type table.
+            if (rewrittenString.equals("L;") || rewrittenString.equals("(L;)V")) {
+              return;
+            }
+            System.out.println(clazzSubject.toString() + ": " + rewrittenString);
+            addedNonInitStrings.increment();
+          });
+      System.out.flush();
+      assertEquals(originalHeader.getPackageName(), rewrittenHeader.getPackageName());
+
       String expected = KotlinMetadataWriter.kotlinMetadataToString("", originalMetadata);
       String actual = KotlinMetadataWriter.kotlinMetadataToString("", rewrittenMetadata);
-      TestCase.assertEquals(expected, actual);
+      assertEquals(expected, actual);
     }
+    addedStringsInspector.accept(addedStrings.get(), addedNonInitStrings.get());
   }
 
   public static void verifyExpectedWarningsFromKotlinReflectAndStdLib(
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteDelegatedPropertyTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteDelegatedPropertyTest.java
index 3752a9e..ffe8b3a 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteDelegatedPropertyTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteDelegatedPropertyTest.java
@@ -60,7 +60,6 @@
         .assertSuccessWithOutput(EXPECTED_MAIN);
   }
 
-
   @Test
   public void testMetadataForLib() throws Exception {
     Path outputJar =
@@ -77,7 +76,8 @@
                 inspector ->
                     assertEqualMetadata(
                         new CodeInspector(jars.getForConfiguration(kotlinc, targetVersion)),
-                        inspector))
+                        inspector,
+                        (addedStrings, addedNonInitStrings) -> {}))
             .writeToZip();
     testForJvm()
         .addRunClasspathFiles(getKotlinStdlibJar(kotlinc), getKotlinReflectJar(kotlinc))
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewritePassThroughTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewritePassThroughTest.java
index 137d6f5..80578a3 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewritePassThroughTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewritePassThroughTest.java
@@ -7,10 +7,17 @@
 import static com.android.tools.r8.ToolHelper.getKotlinAnnotationJar;
 import static com.android.tools.r8.ToolHelper.getKotlinStdlibJar;
 import static org.hamcrest.CoreMatchers.equalTo;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assume.assumeFalse;
+import static org.junit.Assume.assumeTrue;
 
+import com.android.tools.r8.KotlinCompilerTool.KotlinCompilerVersion;
 import com.android.tools.r8.KotlinTestParameters;
 import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.ToolHelper.KotlinTargetVersion;
+import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.shaking.ProguardKeepAttributes;
+import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import java.util.Collection;
 import org.junit.Test;
@@ -23,8 +30,11 @@
   @Parameterized.Parameters(name = "{0}, {1}")
   public static Collection<Object[]> data() {
     return buildParameters(
-        getTestParameters().withCfRuntimes().build(),
-        getKotlinTestParameters().withAllCompilersAndTargetVersions().build());
+        getTestParameters().withCfRuntimes().withNoneRuntime().build(),
+        getKotlinTestParameters()
+            .withAllCompilers()
+            .withTargetVersion(KotlinTargetVersion.JAVA_8)
+            .build());
   }
 
   private final TestParameters parameters;
@@ -35,8 +45,33 @@
     this.parameters = parameters;
   }
 
+  public int getExpectedAddedCount() {
+    if (kotlinParameters.getCompiler().is(KotlinCompilerVersion.KOTLINC_1_3_72)) {
+      return 597;
+    } else if (kotlinParameters.getCompiler().is(KotlinCompilerVersion.KOTLINC_1_4_20)) {
+      return 685;
+    } else if (kotlinParameters.getCompiler().is(KotlinCompilerVersion.KOTLINC_1_5_0_M2)) {
+      return 694;
+    } else {
+      throw new Unreachable("Should not compile in this configuration");
+    }
+  }
+
+  public int getExpectedNonInitAddedCount() {
+    if (kotlinParameters.getCompiler().is(KotlinCompilerVersion.KOTLINC_1_3_72)) {
+      return 327;
+    } else if (kotlinParameters.getCompiler().is(KotlinCompilerVersion.KOTLINC_1_4_20)) {
+      return 413;
+    } else if (kotlinParameters.getCompiler().is(KotlinCompilerVersion.KOTLINC_1_5_0_M2)) {
+      return 417;
+    } else {
+      throw new Unreachable("Should not compile in this configuration");
+    }
+  }
+
   @Test
   public void testKotlinStdLib() throws Exception {
+    assumeFalse(parameters.isNoneRuntime());
     testForR8(parameters.getBackend())
         .addProgramFiles(getKotlinStdlibJar(kotlinc), getKotlinAnnotationJar(kotlinc))
         .setMinApi(parameters.getApiLevel())
@@ -48,6 +83,33 @@
         .assertAllWarningMessagesMatch(equalTo("Resource 'META-INF/MANIFEST.MF' already exists."))
         .inspect(
             inspector ->
-                assertEqualMetadata(new CodeInspector(getKotlinStdlibJar(kotlinc)), inspector));
+                assertEqualMetadata(
+                    new CodeInspector(getKotlinStdlibJar(kotlinc)),
+                    inspector,
+                    (addedStrings, addedNonInitStrings) -> {
+                      assertEquals(getExpectedAddedCount(), addedStrings.intValue());
+                      assertEquals(getExpectedNonInitAddedCount(), addedNonInitStrings.intValue());
+                    }));
+  }
+
+  @Test
+  public void testKotlinStdLibD8() throws Exception {
+    assumeTrue(parameters.isNoneRuntime());
+    testForD8(Backend.DEX)
+        .addProgramFiles(getKotlinStdlibJar(kotlinc), getKotlinAnnotationJar(kotlinc))
+        .setMinApi(AndroidApiLevel.B)
+        // Enable record desugaring support to force a non-identity naming lens
+        .addOptionsModification(
+            options -> options.testing.enableExperimentalRecordDesugaring = true)
+        .compile()
+        .inspect(
+            inspector ->
+                assertEqualMetadata(
+                    new CodeInspector(getKotlinStdlibJar(kotlinc)),
+                    inspector,
+                    (addedStrings, addedNonInitStrings) -> {
+                      assertEquals(0, addedStrings.intValue());
+                      assertEquals(0, addedNonInitStrings.intValue());
+                    }));
   }
 }
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewritePrunedObjectsTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewritePrunedObjectsTest.java
index 2de9622..e59e318 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewritePrunedObjectsTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewritePrunedObjectsTest.java
@@ -26,13 +26,17 @@
 @RunWith(Parameterized.class)
 public class MetadataRewritePrunedObjectsTest extends KotlinMetadataTestBase {
 
-  private final String EXPECTED = StringUtils.lines("42");
+  private final String EXPECTED = StringUtils.lines("42", "0", "Goodbye World");
   private static final String PKG_LIB = PKG + ".pruned_lib";
   private static final String PKG_APP = PKG + ".pruned_app";
 
   private static final KotlinCompileMemoizer libJars =
       getCompileMemoizer(
-          getKotlinFileInTest(DescriptorUtils.getBinaryNameFromJavaType(PKG_LIB), "lib"));
+              getKotlinFileInTest(DescriptorUtils.getBinaryNameFromJavaType(PKG_LIB), "lib"))
+          .configure(
+              kotlinCompilerTool -> {
+                kotlinCompilerTool.addClasspathFiles(ToolHelper.getClassPathForTests());
+              });
   private final TestParameters parameters;
 
   @Parameterized.Parameters(name = "{0}, {1}")
@@ -70,8 +74,12 @@
     Path libJar =
         testForR8(parameters.getBackend())
             .addProgramFiles(libJars.getForConfiguration(kotlinc, targetVersion))
-            .addClasspathFiles(ToolHelper.getKotlinStdlibJar(kotlinc))
-            .addKeepRules("-keep class " + PKG_LIB + ".Sub { <init>(); *** kept(); }")
+            .enableInliningAnnotations()
+            .addClasspathFiles(
+                ToolHelper.getKotlinStdlibJar(kotlinc), ToolHelper.getKotlinAnnotationJar(kotlinc))
+            .addKeepRules(
+                "-keep class " + PKG_LIB + ".Sub { <init>(); *** kept(); *** keptProperty; }")
+            .addKeepClassAndMembersRules(PKG_LIB + ".SubUser")
             .addKeepRuntimeVisibleAnnotations()
             .noMinification()
             .compile()
@@ -98,6 +106,13 @@
     KmClassSubject kmClass = sub.getKmClass();
     assertThat(kmClass, isPresent());
     assertEquals(0, kmClass.getSuperTypes().size());
+    // Ensure that we do not prune the constructors.
+    assertEquals(1, kmClass.getConstructors().size());
+    // Assert that we have removed the metadata for a function that is removed.
     assertThat(kmClass.kmFunctionWithUniqueName("notKept"), not(isPresent()));
+    assertThat(kmClass.kmFunctionWithUniqueName("keptWithoutPinning"), not(isPresent()));
+    // Check that we have not pruned the property information for a kept field.
+    assertThat(kmClass.kmPropertyWithUniqueName("keptProperty"), isPresent());
+    assertThat(kmClass.kmPropertyWithUniqueName("notExposedProperty"), not(isPresent()));
   }
 }
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/pruned_app/main.kt b/src/test/java/com/android/tools/r8/kotlin/metadata/pruned_app/main.kt
index fb35a60..1ba0b77 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/pruned_app/main.kt
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/pruned_app/main.kt
@@ -5,7 +5,10 @@
 package com.android.tools.r8.kotlin.metadata.pruned_app
 
 import com.android.tools.r8.kotlin.metadata.pruned_lib.Sub
+import com.android.tools.r8.kotlin.metadata.pruned_lib.SubUser
 
 fun main() {
-  println(Sub().kept())
+  val sub = Sub()
+  println(sub.kept())
+  SubUser().use(sub)
 }
\ No newline at end of file
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/pruned_lib/lib.kt b/src/test/java/com/android/tools/r8/kotlin/metadata/pruned_lib/lib.kt
index 3ceaa4d..a7672fd 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/pruned_lib/lib.kt
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/pruned_lib/lib.kt
@@ -4,17 +4,42 @@
 
 package com.android.tools.r8.kotlin.metadata.pruned_lib
 
-// The Base class will be removed during
+import com.android.tools.r8.NeverInline
+
+// The Base class will be removed during compilation
 open class Base
 
 class Sub : Base() {
 
+  var notExposedProperty : Int = 42
+  var keptProperty : String = "Hello World";
+
   fun notKept() : Boolean {
     return true
   }
 
+  @NeverInline
+  fun keptWithoutPinning() : Int {
+    if (System.currentTimeMillis() == 0L) {
+      return 41;
+    }
+    return 42;
+  }
+
   fun kept() : Int {
-    return 42
+    if (System.currentTimeMillis() > 0) {
+      notExposedProperty = 0
+      keptProperty = "Goodbye World"
+    }
+    return keptWithoutPinning()
   }
 }
 
+class SubUser {
+
+  @NeverInline
+  fun use(s : Sub) {
+    println(s.notExposedProperty)
+    println(s.keptProperty)
+  }
+}
\ No newline at end of file
diff --git a/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java b/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java
index f37cc64..36ebfd8 100644
--- a/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java
+++ b/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java
@@ -878,7 +878,7 @@
             AppView.createForD8(AppInfo.createInitialAppInfo(application)),
             null,
             GraphLens.getIdentityLens(),
-            InitClassLens.getDefault(),
+            InitClassLens.getThrowingInstance(),
             NamingLens.getIdentityLens(),
             null);
     ExecutorService executor = ThreadUtils.getExecutorService(options);
diff --git a/src/test/java/com/android/tools/r8/maindexlist/MainDexSourceAndClassRetentionTest.java b/src/test/java/com/android/tools/r8/maindexlist/MainDexSourceAndClassRetentionTest.java
new file mode 100644
index 0000000..74cc91d
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/maindexlist/MainDexSourceAndClassRetentionTest.java
@@ -0,0 +1,122 @@
+// Copyright (c) 2021, 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.maindexlist;
+
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.references.ClassReference;
+import com.android.tools.r8.references.Reference;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.google.common.collect.ImmutableSet;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class MainDexSourceAndClassRetentionTest extends TestBase {
+
+  private final TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters()
+        .withDexRuntimes()
+        .withApiLevelsEndingAtExcluding(apiLevelWithNativeMultiDexSupport())
+        .build();
+  }
+
+  public MainDexSourceAndClassRetentionTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testMainDex() throws Exception {
+    List<ClassReference> mainDexList =
+        testForMainDexListGenerator(temp)
+            .addInnerClasses(getClass())
+            .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.B))
+            .addMainDexRules(
+                "-keep class " + Main.class.getTypeName() + " {",
+                "  public static void main(java.lang.String[]);",
+                "}")
+            .run()
+            .getMainDexList();
+    // TODO(b/186090713): {Foo, BAR} and {Source,Class}RetentionAnnotation should not be included.
+    assertEquals(
+        ImmutableSet.of(
+            Reference.classFromClass(Foo.class),
+            Reference.classFromClass(Bar.class),
+            Reference.classFromClass(Main.class),
+            Reference.classFromClass(ClassRetentionAnnotation.class),
+            Reference.classFromClass(SourceRetentionAnnotation.class)),
+        new HashSet<>(mainDexList));
+  }
+
+  @Test
+  public void testD8() throws Exception {
+    Set<String> mainDexClasses =
+        testForD8(temp)
+            .addInnerClasses(getClass())
+            .addLibraryFiles(ToolHelper.getMostRecentAndroidJar())
+            .setMinApi(parameters.getApiLevel())
+            .collectMainDexClasses()
+            .addMainDexRules(
+                "-keep class " + Main.class.getTypeName() + " {",
+                "  public static void main(java.lang.String[]);",
+                "}")
+            .compile()
+            .getMainDexClasses();
+    // TODO(b/186090713): {Foo, BAR} and {Source,Class}RetentionAnnotation should not be included.
+    assertEquals(
+        ImmutableSet.of(
+            typeName(Foo.class),
+            typeName(Bar.class),
+            typeName(Main.class),
+            typeName(ClassRetentionAnnotation.class),
+            typeName(SourceRetentionAnnotation.class)),
+        mainDexClasses);
+  }
+
+  public enum Foo {
+    TEST;
+  }
+
+  public enum Bar {
+    TEST;
+  }
+
+  @Retention(RetentionPolicy.SOURCE)
+  @Target(ElementType.TYPE)
+  public @interface SourceRetentionAnnotation {
+
+    Foo value();
+  }
+
+  @Retention(RetentionPolicy.CLASS)
+  @Target(ElementType.TYPE)
+  public @interface ClassRetentionAnnotation {
+
+    Bar value();
+  }
+
+  @SourceRetentionAnnotation(Foo.TEST)
+  @ClassRetentionAnnotation(Bar.TEST)
+  public static class Main {
+
+    public static void main(String[] args) {}
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/naming/retrace/StackTrace.java b/src/test/java/com/android/tools/r8/naming/retrace/StackTrace.java
index 0cc94d7..3b0b052 100644
--- a/src/test/java/com/android/tools/r8/naming/retrace/StackTrace.java
+++ b/src/test/java/com/android/tools/r8/naming/retrace/StackTrace.java
@@ -301,19 +301,14 @@
   }
 
   public StackTrace retraceAllowExperimentalMapping(String map) {
-    return retrace(map, null, true);
+    return retrace(map, true);
   }
 
   public StackTrace retrace(String map) {
-    return retrace(map, null, true);
+    return retrace(map, true);
   }
 
-  public StackTrace retrace(String map, String regularExpression) {
-    return retrace(map, regularExpression, true);
-  }
-
-  public StackTrace retrace(
-      String map, String regularExpression, boolean allowExperimentalMapping) {
+  public StackTrace retrace(String map, boolean allowExperimentalMapping) {
     class Box {
       List<String> result;
     }
@@ -325,7 +320,6 @@
                 stackTraceLines.stream()
                     .map(line -> line.originalLine)
                     .collect(Collectors.toList()))
-            .setRegularExpression(regularExpression)
             .setRetracedStackTraceConsumer(retraced -> box.result = retraced)
             .build(),
         allowExperimentalMapping);
diff --git a/src/test/java/com/android/tools/r8/regress/b152973695/CompileToInvalidFileTest.java b/src/test/java/com/android/tools/r8/regress/b152973695/CompileToInvalidFileTest.java
index 745d166..7d3d07b 100644
--- a/src/test/java/com/android/tools/r8/regress/b152973695/CompileToInvalidFileTest.java
+++ b/src/test/java/com/android/tools/r8/regress/b152973695/CompileToInvalidFileTest.java
@@ -6,7 +6,6 @@
 
 import static com.android.tools.r8.DiagnosticsMatcher.diagnosticMessage;
 import static org.hamcrest.CoreMatchers.containsString;
-import static org.hamcrest.CoreMatchers.equalTo;
 import static org.junit.Assert.fail;
 
 import com.android.tools.r8.ClassFileConsumer;
@@ -82,9 +81,7 @@
 
   private void checkDiagnostics(TestDiagnosticMessages diagnostics, boolean isD8) {
     if (classFileConsumer && isD8) {
-      diagnostics.assertWarningsMatch(
-          diagnosticMessage(
-              equalTo("Compiling to Java class files with D8 is not officially supported")));
+      diagnostics.assertWarningsMatch(cfD8NotSupportedDiagnostic);
     } else {
       diagnostics.assertOnlyErrors();
     }
diff --git a/src/test/java/com/android/tools/r8/retrace/RetraceTests.java b/src/test/java/com/android/tools/r8/retrace/RetraceTests.java
index d7a13c8..220bea7 100644
--- a/src/test/java/com/android/tools/r8/retrace/RetraceTests.java
+++ b/src/test/java/com/android/tools/r8/retrace/RetraceTests.java
@@ -4,12 +4,10 @@
 
 package com.android.tools.r8.retrace;
 
-import static com.android.tools.r8.retrace.internal.StackTraceRegularExpressionParser.DEFAULT_REGULAR_EXPRESSION;
 import static junit.framework.TestCase.assertEquals;
 import static org.hamcrest.CoreMatchers.containsString;
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.junit.Assert.fail;
-import static org.junit.Assume.assumeFalse;
 import static org.junit.Assume.assumeTrue;
 
 import com.android.tools.r8.TestBase;
@@ -61,7 +59,6 @@
 import java.util.Collection;
 import java.util.List;
 import java.util.function.Consumer;
-import org.junit.Ignore;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
@@ -70,19 +67,16 @@
 @RunWith(Parameterized.class)
 public class RetraceTests extends TestBase {
 
-  @Parameters(name = "{0}, use regular expression: {1}, external: {2}")
+  @Parameters(name = "{0}, external: {1}")
   public static Collection<Object[]> data() {
-    return buildParameters(
-        getTestParameters().withCfRuntimes().build(), BooleanUtils.values(), BooleanUtils.values());
+    return buildParameters(getTestParameters().withCfRuntimes().build(), BooleanUtils.values());
   }
 
   private final TestParameters testParameters;
-  private final boolean useRegExpParsing;
   private final boolean external;
 
-  public RetraceTests(TestParameters parameters, boolean useRegExpParsing, boolean external) {
+  public RetraceTests(TestParameters parameters, boolean external) {
     this.testParameters = parameters;
-    this.useRegExpParsing = useRegExpParsing;
     this.external = external;
   }
 
@@ -154,8 +148,7 @@
     List<ActualBotStackTraceBase> stackTraces =
         ImmutableList.of(new ActualIdentityStackTrace(), new ActualRetraceBotStackTrace());
     for (ActualBotStackTraceBase stackTrace : stackTraces) {
-      runRetraceTest(stackTrace)
-          .assertWarningsCount(useRegExpParsing ? 0 : stackTrace.expectedWarnings());
+      runRetraceTest(stackTrace).assertNoWarnings();
     }
   }
 
@@ -193,7 +186,7 @@
   public void testCircularReferenceStackTrace() throws Exception {
     // Proguard retrace (and therefore the default regular expression) will not retrace circular
     // reference exceptions.
-    assumeFalse(useRegExpParsing);
+    assumeTrue("b/178599214", false);
     runRetraceTest(new CircularReferenceStackTrace());
   }
 
@@ -203,9 +196,8 @@
   }
 
   @Test
-  @Ignore("b/170293908")
   public void testBootLoaderAndNamedModulesStackTrace() throws Exception {
-    assumeFalse(useRegExpParsing);
+    assumeTrue("b/170293908", false);
     runRetraceTest(new NamedModuleStackTrace());
   }
 
@@ -287,7 +279,6 @@
   private TestDiagnosticMessagesImpl runRetraceTest(
       StackTraceForTest stackTraceForTest, boolean allowExperimentalMapping) throws Exception {
     if (external) {
-      assumeTrue(useRegExpParsing);
       assumeTrue(testParameters.isCfRuntime());
       // The external dependency is built on top of R8Lib. If test.py is run with
       // no r8lib, do not try and run the external R8 Retrace since it has not been built.
@@ -327,7 +318,6 @@
           RetraceCommand.builder(diagnosticsHandler)
               .setProguardMapProducer(ProguardMapProducer.fromString(stackTraceForTest.mapping()))
               .setStackTrace(stackTraceForTest.obfuscatedStackTrace())
-              .setRegularExpression(useRegExpParsing ? DEFAULT_REGULAR_EXPRESSION : null)
               .setRetracedStackTraceConsumer(
                   retraced ->
                       assertEquals(
diff --git a/src/test/java/com/android/tools/r8/retrace/RetraceVerboseTests.java b/src/test/java/com/android/tools/r8/retrace/RetraceVerboseTests.java
index bcca0af..8f8e049 100644
--- a/src/test/java/com/android/tools/r8/retrace/RetraceVerboseTests.java
+++ b/src/test/java/com/android/tools/r8/retrace/RetraceVerboseTests.java
@@ -4,9 +4,8 @@
 
 package com.android.tools.r8.retrace;
 
-import static com.android.tools.r8.retrace.internal.StackTraceRegularExpressionParser.DEFAULT_REGULAR_EXPRESSION;
 import static junit.framework.TestCase.assertEquals;
-import static org.junit.Assume.assumeFalse;
+import static org.junit.Assume.assumeTrue;
 
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestDiagnosticMessagesImpl;
@@ -16,7 +15,6 @@
 import com.android.tools.r8.retrace.stacktraces.FoundMethodVerboseStackTrace;
 import com.android.tools.r8.retrace.stacktraces.StackTraceForTest;
 import com.android.tools.r8.retrace.stacktraces.VerboseUnknownStackTrace;
-import com.android.tools.r8.utils.BooleanUtils;
 import java.util.Collection;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -26,16 +24,12 @@
 @RunWith(Parameterized.class)
 public class RetraceVerboseTests extends TestBase {
 
-  @Parameters(name = "{0}, use regular expression: {1}")
+  @Parameters(name = "{0}")
   public static Collection<Object[]> data() {
-    return buildParameters(getTestParameters().withNoneRuntime().build(), BooleanUtils.values());
+    return buildParameters(getTestParameters().withNoneRuntime().build());
   }
 
-  private final boolean useRegExpParsing;
-
-  public RetraceVerboseTests(TestParameters parameters, boolean useRegExpParsing) {
-    this.useRegExpParsing = useRegExpParsing;
-  }
+  public RetraceVerboseTests(TestParameters parameters) {}
 
   @Test
   public void testFoundMethod() {
@@ -54,23 +48,20 @@
 
   @Test
   public void testAmbiguousMissingLineVerbose() {
-    // TODO(b/169346455): Enable when separated parser.
-    assumeFalse(useRegExpParsing);
+    assumeTrue("b/169346455", false);
     runRetraceTest(new AmbiguousWithSignatureVerboseStackTrace());
   }
 
-  private TestDiagnosticMessagesImpl runRetraceTest(StackTraceForTest stackTraceForTest) {
+  private void runRetraceTest(StackTraceForTest stackTraceForTest) {
     TestDiagnosticMessagesImpl diagnosticsHandler = new TestDiagnosticMessagesImpl();
     RetraceCommand retraceCommand =
         RetraceCommand.builder(diagnosticsHandler)
             .setProguardMapProducer(ProguardMapProducer.fromString(stackTraceForTest.mapping()))
             .setStackTrace(stackTraceForTest.obfuscatedStackTrace())
-            .setRegularExpression(useRegExpParsing ? DEFAULT_REGULAR_EXPRESSION : null)
             .setVerbose(true)
             .setRetracedStackTraceConsumer(
                 retraced -> assertEquals(stackTraceForTest.retracedStackTrace(), retraced))
             .build();
     Retrace.run(retraceCommand);
-    return diagnosticsHandler;
   }
 }
diff --git a/src/test/java/com/android/tools/r8/retrace/SourceFileTest.java b/src/test/java/com/android/tools/r8/retrace/SourceFileTest.java
index ff4296d..9e2b2df 100644
--- a/src/test/java/com/android/tools/r8/retrace/SourceFileTest.java
+++ b/src/test/java/com/android/tools/r8/retrace/SourceFileTest.java
@@ -4,7 +4,6 @@
 
 package com.android.tools.r8.retrace;
 
-import static com.android.tools.r8.retrace.internal.StackTraceRegularExpressionParser.DEFAULT_REGULAR_EXPRESSION;
 import static org.hamcrest.CoreMatchers.containsString;
 import static org.junit.Assert.assertEquals;
 
@@ -13,10 +12,9 @@
 import com.android.tools.r8.R8TestRunResult;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.naming.retrace.StackTrace;
-import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
-import java.util.List;
 import java.util.function.BiConsumer;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -27,18 +25,15 @@
 public class SourceFileTest extends TestBase {
 
   private final TestParameters parameters;
-  private final boolean useRegularExpression;
   private static final String FILE_NAME = "foobarbaz.java";
 
-  @Parameters(name = "{0}, useRegularExpression: {1}")
-  public static List<Object[]> data() {
-    return buildParameters(
-        getTestParameters().withAllRuntimesAndApiLevels().build(), BooleanUtils.values());
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
   }
 
-  public SourceFileTest(TestParameters parameters, boolean useRegularExpression) {
+  public SourceFileTest(TestParameters parameters) {
     this.parameters = parameters;
-    this.useRegularExpression = useRegularExpression;
   }
 
   @Test
@@ -106,9 +101,7 @@
             : r8FullTestBuilder.run(parameters.getRuntime(), Main.class);
     runResult.assertFailureWithErrorThatMatches(containsString("Hello World!"));
     StackTrace originalStackTrace = runResult.getOriginalStackTrace();
-    StackTrace retracedStackTrace =
-        originalStackTrace.retrace(
-            runResult.proguardMap(), useRegularExpression ? DEFAULT_REGULAR_EXPRESSION : null);
+    StackTrace retracedStackTrace = originalStackTrace.retrace(runResult.proguardMap());
     runResult.inspectFailure(inspector -> consumer.accept(retracedStackTrace, inspector));
   }
 
diff --git a/src/test/java/com/android/tools/r8/shaking/keep/IndirectKeepStaticMethodTest.java b/src/test/java/com/android/tools/r8/shaking/keep/IndirectKeepStaticMethodTest.java
new file mode 100644
index 0000000..3e20dcd
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/keep/IndirectKeepStaticMethodTest.java
@@ -0,0 +1,59 @@
+// Copyright (c) 2021, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.shaking.keep;
+
+import com.android.tools.r8.NoVerticalClassMerging;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class IndirectKeepStaticMethodTest extends TestBase {
+
+  private final TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public IndirectKeepStaticMethodTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void test() throws Exception {
+    testForR8(parameters.getBackend())
+        .addProgramClasses(A.class, B.class)
+        .addKeepRules("-keep class " + B.class.getTypeName() + " {", "  static void m();", "}")
+        .enableNoVerticalClassMergingAnnotations()
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .addRunClasspathFiles(buildOnDexRuntime(parameters, Main.class))
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines("A");
+  }
+
+  static class Main {
+
+    public static void main(String[] args) {
+      B.m();
+    }
+  }
+
+  @NoVerticalClassMerging
+  static class A {
+
+    static void m() {
+      System.out.println("A");
+    }
+  }
+
+  static class B extends A {}
+}
diff --git a/src/test/java/com/android/tools/r8/tracereferences/TraceReferencesCommandTest.java b/src/test/java/com/android/tools/r8/tracereferences/TraceReferencesCommandTest.java
index 5138617..02deedd 100644
--- a/src/test/java/com/android/tools/r8/tracereferences/TraceReferencesCommandTest.java
+++ b/src/test/java/com/android/tools/r8/tracereferences/TraceReferencesCommandTest.java
@@ -21,7 +21,9 @@
 import com.android.tools.r8.references.Reference;
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.DescriptorUtils;
+import com.android.tools.r8.utils.FieldReferenceUtils;
 import com.android.tools.r8.utils.FileUtils;
+import com.android.tools.r8.utils.MethodReferenceUtils;
 import com.android.tools.r8.utils.StringUtils;
 import com.android.tools.r8.utils.ZipUtils.ZipBuilder;
 import com.google.common.collect.ImmutableList;
@@ -469,9 +471,11 @@
     assertEquals(1, diagnosticsChecker.errors.size());
     assertEquals(0, diagnosticsChecker.warnings.size());
     assertEquals(0, diagnosticsChecker.infos.size());
-    diagnosticsChecker.checkErrorsContains(Reference.classFromClass(Target.class).toString());
-    diagnosticsChecker.checkErrorsContains(Reference.fieldFromField(field).toString());
-    diagnosticsChecker.checkErrorsContains(Reference.methodFromMethod(method).toString());
+    diagnosticsChecker.checkErrorsContains(Reference.classFromClass(Target.class).getTypeName());
+    diagnosticsChecker.checkErrorsContains(
+        FieldReferenceUtils.toSourceString(Reference.fieldFromField(field)));
+    diagnosticsChecker.checkErrorsContains(
+        MethodReferenceUtils.toSourceString(Reference.methodFromMethod(method)));
   }
 
   @Test
@@ -570,8 +574,15 @@
     assertThat(
         baosErr.toString(Charsets.UTF_8.name()),
         containsString(
-            "Warning: Tracereferences found 1 classe(s), 1 field(s) and 1 method(s) without"
-                + " definition"));
+            StringUtils.lines(
+                "Warning: Missing class " + Target.class.getTypeName(),
+                "Missing field "
+                    + FieldReferenceUtils.toSourceString(
+                        FieldReferenceUtils.fieldFromField(Target.class, "field")),
+                "Missing method "
+                    + MethodReferenceUtils.toSourceString(
+                        MethodReferenceUtils.methodFromMethod(
+                            Target.class, "method", int.class)))));
     assertEquals(0, baosOut.size());
   }
 
@@ -613,8 +624,15 @@
     assertThat(
         baosOut.toString(Charsets.UTF_8.name()),
         containsString(
-            "Info: Tracereferences found 1 classe(s), 1 field(s) and 1 method(s) without"
-                + " definition"));
+            StringUtils.lines(
+                "Info: Missing class " + Target.class.getTypeName(),
+                "Missing field "
+                    + FieldReferenceUtils.toSourceString(
+                        FieldReferenceUtils.fieldFromField(Target.class, "field")),
+                "Missing method "
+                    + MethodReferenceUtils.toSourceString(
+                        MethodReferenceUtils.methodFromMethod(
+                            Target.class, "method", int.class)))));
   }
 
   private void checkTargetPartlyMissing(DiagnosticsChecker diagnosticsChecker) {
@@ -629,8 +647,10 @@
     assertEquals(1, diagnosticsChecker.errors.size());
     assertEquals(0, diagnosticsChecker.warnings.size());
     assertEquals(0, diagnosticsChecker.infos.size());
-    diagnosticsChecker.checkErrorsContains(Reference.fieldFromField(field).toString());
-    diagnosticsChecker.checkErrorsContains(Reference.methodFromMethod(method).toString());
+    diagnosticsChecker.checkErrorsContains(
+        FieldReferenceUtils.toSourceString(Reference.fieldFromField(field)));
+    diagnosticsChecker.checkErrorsContains(
+        MethodReferenceUtils.toSourceString(Reference.methodFromMethod(method)));
   }
 
   @Test
diff --git a/src/test/java/com/android/tools/r8/tracereferences/TraceReferencesDiagnosticTest.java b/src/test/java/com/android/tools/r8/tracereferences/TraceReferencesDiagnosticTest.java
index 4f456f6..292271d 100644
--- a/src/test/java/com/android/tools/r8/tracereferences/TraceReferencesDiagnosticTest.java
+++ b/src/test/java/com/android/tools/r8/tracereferences/TraceReferencesDiagnosticTest.java
@@ -3,14 +3,12 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.tracereferences;
 
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 
 import com.android.tools.r8.CompilationFailedException;
-import com.android.tools.r8.DiagnosticsChecker;
 import com.android.tools.r8.DiagnosticsHandler;
 import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestDiagnosticMessagesImpl;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.ToolHelper;
@@ -20,10 +18,7 @@
 import com.android.tools.r8.utils.StringDiagnostic;
 import com.android.tools.r8.utils.StringUtils;
 import com.android.tools.r8.utils.ZipUtils.ZipBuilder;
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.ImmutableSet;
 import java.nio.file.Path;
-import java.util.List;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
@@ -66,68 +61,44 @@
                 ToolHelper.getClassFileForTestClass(Source.class))
             .build();
 
-    String prefix = "  Lcom/android/tools/r8/tracereferences/TraceReferencesDiagnosticTest$";
-    List<String> snippets =
-        ImmutableList.of(
-            "Tracereferences found 3 classe(s), 2 field(s) and 4 method(s) without definition",
-            StringUtils.lines(
-                "Classe(s) without definition:",
-                prefix + "Target1;",
-                prefix + "Target2;",
-                prefix + "Target3;"),
-            StringUtils.lines(
-                "Field(s) without definition:",
-                prefix + "Target;missingField1:I",
-                prefix + "Target;missingField2:I"),
-            StringUtils.lines(
-                "Method(s) without definition:",
-                prefix + "Target1;<init>()V",
-                prefix + "Target2;<init>()V",
-                prefix + "Target3;<init>()V",
-                prefix + "Target;missingMethod()V"));
+    TestDiagnosticMessagesImpl testDiagnosticMessages = new TestDiagnosticMessagesImpl();
     try {
-      DiagnosticsChecker.checkErrorDiagnostics(
-          checker -> {
-            DiagnosticsChecker.checkContains(snippets, checker.errors);
-            try {
-              assertEquals(1, checker.errors.size());
-              assertTrue(checker.errors.get(0) instanceof MissingDefinitionsDiagnostic);
-              MissingDefinitionsDiagnostic diagnostic =
-                  (MissingDefinitionsDiagnostic) checker.errors.get(0);
-              assertEquals(
-                  diagnostic.getMissingClasses(),
-                  ImmutableSet.of(
-                      Reference.classFromClass(Target1.class),
-                      Reference.classFromClass(Target2.class),
-                      Reference.classFromClass(Target3.class)));
-              assertEquals(
-                  diagnostic.getMissingFields(),
-                  ImmutableSet.of(
-                      Reference.fieldFromField(Target.class.getField("missingField1")),
-                      Reference.fieldFromField(Target.class.getField("missingField2"))));
-              assertEquals(
-                  diagnostic.getMissingMethods(),
-                  ImmutableSet.of(
-                      Reference.methodFromMethod(Target1.class.getDeclaredConstructor()),
-                      Reference.methodFromMethod(Target2.class.getDeclaredConstructor()),
-                      Reference.methodFromMethod(Target3.class.getDeclaredConstructor()),
-                      Reference.methodFromMethod(Target.class.getMethod("missingMethod"))));
-            } catch (ReflectiveOperationException e) {
-              fail("Unexpected exception");
-            }
-          },
-          handler ->
-              TraceReferences.run(
-                  TraceReferencesCommand.builder(handler)
-                      .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.P))
-                      .addSourceFiles(sourceJar)
-                      .addTargetFiles(targetJar)
-                      .setConsumer(TraceReferencesConsumer.emptyConsumer())
-                      .build()));
+      TraceReferences.run(
+          TraceReferencesCommand.builder(testDiagnosticMessages)
+              .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.P))
+              .addSourceFiles(sourceJar)
+              .addTargetFiles(targetJar)
+              .setConsumer(TraceReferencesConsumer.emptyConsumer())
+              .build());
       fail("Unexpected success");
     } catch (CompilationFailedException e) {
       // Expected.
     }
+
+    testDiagnosticMessages.inspectErrors(
+        diagnostic ->
+            diagnostic
+                .assertIsMissingDefinitionsDiagnostic()
+                .assertHasMessage(
+                    StringUtils.joinLines(
+                        "Missing class " + Target1.class.getTypeName(),
+                        "Missing method void " + Target1.class.getTypeName() + ".<init>()",
+                        "Missing class " + Target2.class.getTypeName(),
+                        "Missing method void " + Target2.class.getTypeName() + ".<init>()",
+                        "Missing class " + Target3.class.getTypeName(),
+                        "Missing method void " + Target3.class.getTypeName() + ".<init>()",
+                        "Missing field int " + Target.class.getTypeName() + ".missingField1",
+                        "Missing field int " + Target.class.getTypeName() + ".missingField2",
+                        "Missing method void " + Target.class.getTypeName() + ".missingMethod()"))
+                .assertIsAllMissingClasses(Target1.class, Target2.class, Target3.class)
+                .assertIsAllMissingFields(
+                    Reference.fieldFromField(Target.class.getField("missingField1")),
+                    Reference.fieldFromField(Target.class.getField("missingField2")))
+                .assertIsAllMissingMethods(
+                    Reference.methodFromMethod(Target1.class.getDeclaredConstructor()),
+                    Reference.methodFromMethod(Target2.class.getDeclaredConstructor()),
+                    Reference.methodFromMethod(Target3.class.getDeclaredConstructor()),
+                    Reference.methodFromMethod(Target.class.getMethod("missingMethod"))));
   }
 
   @Test
@@ -161,52 +132,35 @@
                 ToolHelper.getClassFileForTestClass(Source.class))
             .build();
 
-    String prefix = "  Lcom/android/tools/r8/tracereferences/TraceReferencesDiagnosticTest$";
-    List<String> snippets =
-        ImmutableList.of(
-            "Tracereferences found 2 field(s) and 1 method(s) without definition",
-            StringUtils.lines(
-                "Field(s) without definition:",
-                prefix + "Target;missingField1:I",
-                prefix + "Target;missingField2:I"),
-            StringUtils.lines("Method(s) without definition:", prefix + "Target;missingMethod()V"));
+    TestDiagnosticMessagesImpl testDiagnosticMessages = new TestDiagnosticMessagesImpl();
     try {
-      DiagnosticsChecker.checkErrorDiagnostics(
-          checker -> {
-            DiagnosticsChecker.checkContains(snippets, checker.errors);
-            DiagnosticsChecker.checkNotContains(
-                ImmutableList.of("Classe(s) without definition:"), checker.errors);
-            try {
-              assertEquals(1, checker.errors.size());
-              assertTrue(checker.errors.get(0) instanceof MissingDefinitionsDiagnostic);
-              MissingDefinitionsDiagnostic diagnostic =
-                  (MissingDefinitionsDiagnostic) checker.errors.get(0);
-              assertEquals(diagnostic.getMissingClasses(), ImmutableSet.of());
-              assertEquals(
-                  diagnostic.getMissingFields(),
-                  ImmutableSet.of(
-                      Reference.fieldFromField(Target.class.getField("missingField1")),
-                      Reference.fieldFromField(Target.class.getField("missingField2"))));
-              assertEquals(
-                  diagnostic.getMissingMethods(),
-                  ImmutableSet.of(
-                      Reference.methodFromMethod(Target.class.getMethod("missingMethod"))));
-            } catch (ReflectiveOperationException e) {
-              fail("Unexpected exception");
-            }
-          },
-          handler ->
-              TraceReferences.run(
-                  TraceReferencesCommand.builder(handler)
-                      .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.P))
-                      .addSourceFiles(sourceJar)
-                      .addTargetFiles(targetJar)
-                      .setConsumer(TraceReferencesConsumer.emptyConsumer())
-                      .build()));
+      TraceReferences.run(
+          TraceReferencesCommand.builder(testDiagnosticMessages)
+              .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.P))
+              .addSourceFiles(sourceJar)
+              .addTargetFiles(targetJar)
+              .setConsumer(TraceReferencesConsumer.emptyConsumer())
+              .build());
       fail("Unexpected success");
     } catch (CompilationFailedException e) {
       // Expected.
     }
+
+    testDiagnosticMessages.inspectErrors(
+        diagnostic ->
+            diagnostic
+                .assertIsMissingDefinitionsDiagnostic()
+                .assertHasMessage(
+                    StringUtils.joinLines(
+                        "Missing field int " + Target.class.getTypeName() + ".missingField1",
+                        "Missing field int " + Target.class.getTypeName() + ".missingField2",
+                        "Missing method void " + Target.class.getTypeName() + ".missingMethod()"))
+                .assertNoMissingClasses()
+                .assertIsAllMissingFields(
+                    Reference.fieldFromField(Target.class.getField("missingField1")),
+                    Reference.fieldFromField(Target.class.getField("missingField2")))
+                .assertIsAllMissingMethods(
+                    Reference.methodFromMethod(Target.class.getMethod("missingMethod"))));
   }
 
   @Test
@@ -234,46 +188,35 @@
                 ToolHelper.getClassFileForTestClass(Source.class))
             .build();
 
-    String prefix = "  Lcom/android/tools/r8/tracereferences/TraceReferencesDiagnosticTest$";
-    List<String> snippets =
-        ImmutableList.of(
-            "Tracereferences found 1 method(s) without definition",
-            StringUtils.lines("Method(s) without definition:", prefix + "Target;missingMethod()V"));
+    TestDiagnosticMessagesImpl testDiagnosticMessages = new TestDiagnosticMessagesImpl();
     try {
-      DiagnosticsChecker.checkErrorDiagnostics(
-          checker -> {
-            DiagnosticsChecker.checkContains(snippets, checker.errors);
-            try {
-              assertEquals(1, checker.errors.size());
-              assertTrue(checker.errors.get(0) instanceof MissingDefinitionsDiagnostic);
-              MissingDefinitionsDiagnostic diagnostic =
-                  (MissingDefinitionsDiagnostic) checker.errors.get(0);
-              assertEquals(diagnostic.getMissingClasses(), ImmutableSet.of());
-              assertEquals(diagnostic.getMissingFields(), ImmutableSet.of());
-              assertEquals(
-                  diagnostic.getMissingMethods(),
-                  ImmutableSet.of(
-                      Reference.methodFromMethod(Target.class.getMethod("missingMethod"))));
-            } catch (ReflectiveOperationException e) {
-              fail("Unexpected exception");
-            }
-          },
-          handler ->
-              TraceReferences.run(
-                  TraceReferencesCommand.builder(handler)
-                      .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.P))
-                      .addSourceFiles(sourceJar)
-                      .addTargetFiles(targetJar)
-                      .setConsumer(TraceReferencesConsumer.emptyConsumer())
-                      .build()));
+      TraceReferences.run(
+          TraceReferencesCommand.builder(testDiagnosticMessages)
+              .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.P))
+              .addSourceFiles(sourceJar)
+              .addTargetFiles(targetJar)
+              .setConsumer(TraceReferencesConsumer.emptyConsumer())
+              .build());
       fail("Unexpected success");
     } catch (CompilationFailedException e) {
       // Expected.
     }
+
+    testDiagnosticMessages.inspectErrors(
+        diagnostic ->
+            diagnostic
+                .assertIsMissingDefinitionsDiagnostic()
+                .assertHasMessage(
+                    "Missing method void " + Target.class.getTypeName() + ".missingMethod()")
+                .assertNoMissingClasses()
+                .assertNoMissingFields()
+                .assertIsAllMissingMethods(
+                    Reference.methodFromMethod(Target.class.getMethod("missingMethod"))));
   }
 
   static class FailingConsumer implements TraceReferencesConsumer {
     private final String where;
+    private boolean reported;
 
     FailingConsumer(String where) {
       this.where = where;
@@ -281,29 +224,33 @@
 
     @Override
     public void acceptType(TracedClass tracedClass, DiagnosticsHandler handler) {
-      if (where.equals("acceptType")) {
+      if (!reported && where.equals("acceptType")) {
         handler.error(new StringDiagnostic("Error in " + where));
+        reported = true;
       }
     }
 
     @Override
     public void acceptField(TracedField tracedField, DiagnosticsHandler handler) {
-      if (where.equals("acceptField")) {
+      if (!reported && where.equals("acceptField")) {
         handler.error(new StringDiagnostic("Error in " + where));
+        reported = true;
       }
     }
 
     @Override
     public void acceptMethod(TracedMethod tracedMethod, DiagnosticsHandler handler) {
-      if (where.equals("acceptMethod")) {
+      if (!reported && where.equals("acceptMethod")) {
         handler.error(new StringDiagnostic("Error in " + where));
+        reported = true;
       }
     }
 
     @Override
     public void finished(DiagnosticsHandler handler) {
-      if (where.equals("finished")) {
+      if (!reported && where.equals("finished")) {
         handler.error(new StringDiagnostic("Error in " + where));
+        reported = true;
       }
     }
   }
@@ -328,21 +275,23 @@
             .build();
 
     for (String where : new String[] {"acceptType", "acceptField", "acceptMethod", "finished"}) {
+      TestDiagnosticMessagesImpl testDiagnosticMessages = new TestDiagnosticMessagesImpl();
       try {
-        DiagnosticsChecker.checkErrorsContains(
-            "Error in " + where,
-            handler ->
-                TraceReferences.run(
-                    TraceReferencesCommand.builder(handler)
-                        .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.P))
-                        .addSourceFiles(sourceJar)
-                        .addTargetFiles(targetJar)
-                        .setConsumer(new FailingConsumer(where))
-                        .build()));
+        TraceReferences.run(
+            TraceReferencesCommand.builder(testDiagnosticMessages)
+                .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.P))
+                .addSourceFiles(sourceJar)
+                .addTargetFiles(targetJar)
+                .setConsumer(new FailingConsumer(where))
+                .build());
         fail("Unexpected success");
       } catch (CompilationFailedException e) {
         // Expected.
       }
+
+      testDiagnosticMessages.inspectErrors(
+          diagnostic ->
+              diagnostic.assertIsStringDiagnostic().assertHasMessage("Error in " + where));
     }
   }
 
diff --git a/src/test/java/com/android/tools/r8/utils/Smali.java b/src/test/java/com/android/tools/r8/utils/Smali.java
index 26e85e5..eb269c1 100644
--- a/src/test/java/com/android/tools/r8/utils/Smali.java
+++ b/src/test/java/com/android/tools/r8/utils/Smali.java
@@ -118,7 +118,7 @@
               AppView.createForD8(AppInfo.createInitialAppInfo(dexApp)),
               null,
               GraphLens.getIdentityLens(),
-              InitClassLens.getDefault(),
+              InitClassLens.getThrowingInstance(),
               NamingLens.getIdentityLens(),
               null);
       writer.write(executor);
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentKmClassSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentKmClassSubject.java
index a58d5ee..d87e74e 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentKmClassSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentKmClassSubject.java
@@ -21,6 +21,11 @@
   }
 
   @Override
+  public List<KmConstructorSubject> getConstructors() {
+    return null;
+  }
+
+  @Override
   public boolean isPresent() {
     return false;
   }
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundKmClassSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundKmClassSubject.java
index 51d7152..cc9bfd0 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundKmClassSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundKmClassSubject.java
@@ -37,6 +37,13 @@
   }
 
   @Override
+  public List<KmConstructorSubject> getConstructors() {
+    return kmClass.getConstructors().stream()
+        .map(constructor -> new FoundKmConstructorSubject(codeInspector, constructor))
+        .collect(Collectors.toList());
+  }
+
+  @Override
   public CodeInspector codeInspector() {
     return codeInspector;
   }
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundKmConstructorSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundKmConstructorSubject.java
new file mode 100644
index 0000000..19a783f
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundKmConstructorSubject.java
@@ -0,0 +1,53 @@
+// Copyright (c) 2021, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.utils.codeinspector;
+
+import java.util.List;
+import java.util.stream.Collectors;
+import kotlinx.metadata.KmConstructor;
+import kotlinx.metadata.jvm.JvmExtensionsKt;
+import kotlinx.metadata.jvm.JvmMethodSignature;
+
+public class FoundKmConstructorSubject extends KmConstructorSubject {
+
+  private final CodeInspector codeInspector;
+  private final KmConstructor kmConstructor;
+
+  FoundKmConstructorSubject(CodeInspector codeInspector, KmConstructor kmConstructor) {
+    this.codeInspector = codeInspector;
+    this.kmConstructor = kmConstructor;
+  }
+
+  @Override
+  public JvmMethodSignature signature() {
+    JvmExtensionsKt.getSignature(this.kmConstructor);
+    return null;
+  }
+
+  @Override
+  public List<KmValueParameterSubject> valueParameters() {
+    return kmConstructor.getValueParameters().stream()
+        .map(kmValueParameter -> new KmValueParameterSubject(codeInspector, kmValueParameter))
+        .collect(Collectors.toList());
+  }
+
+  @Override
+  public boolean isPresent() {
+    return true;
+  }
+
+  @Override
+  public boolean isRenamed() {
+    // TODO(b/151194869): need to know the corresponding DexEncodedMethod.
+    return false;
+  }
+
+  @Override
+  public boolean isSynthetic() {
+    // TODO(b/151194785): This should return `true` conditionally if we start synthesizing @Metadata
+    //   from scratch.
+    return false;
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/KmClassSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/KmClassSubject.java
index 753f02a..9d776d3 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/KmClassSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/KmClassSubject.java
@@ -12,6 +12,8 @@
 
   public abstract DexClass getDexClass();
 
+  public abstract List<KmConstructorSubject> getConstructors();
+
   public abstract List<String> getSuperTypeDescriptors();
 
   public abstract List<ClassSubject> getSuperTypes();
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/KmConstructorSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/KmConstructorSubject.java
new file mode 100644
index 0000000..7a2e468
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/KmConstructorSubject.java
@@ -0,0 +1,14 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.utils.codeinspector;
+
+import java.util.List;
+import kotlinx.metadata.jvm.JvmMethodSignature;
+
+public abstract class KmConstructorSubject extends Subject {
+
+  public abstract JvmMethodSignature signature();
+
+  public abstract List<KmValueParameterSubject> valueParameters();
+}
diff --git a/tools/test.py b/tools/test.py
index 6f3fe83..685cbf2 100755
--- a/tools/test.py
+++ b/tools/test.py
@@ -170,6 +170,14 @@
   result.add_option('--print-times', '--print_times',
       help='Print the execution time of the slowest tests..',
       default=False, action='store_true')
+  result.add_option(
+      '--testing-report',
+      help='Use the custom testing report output format',
+      default=False, action='store_true')
+  result.add_option(
+      '--stacktrace',
+      help='Pass --stacktrace to the gradle run',
+      default=False, action='store_true')
   return result.parse_args()
 
 def archive_failures():
@@ -214,7 +222,11 @@
       shutil.copyfile(library_jar, desugar_jdk_libs)
       print('Desugared library for test in ' + desugar_jdk_libs)
 
-  gradle_args = ['--stacktrace']
+  gradle_args = []
+
+  if options.stacktrace or utils.is_bot():
+    gradle_args.append('--stacktrace')
+
   if utils.is_bot():
     # Bots don't like dangling processes.
     gradle_args.append('--no-daemon')
@@ -301,7 +313,8 @@
     gradle_args.append('-Pdesugar_jdk_json_dir=' + desugar_jdk_json_dir)
   if desugar_jdk_libs:
     gradle_args.append('-Pdesugar_jdk_libs=' + desugar_jdk_libs)
-
+  if options.testing_report:
+    gradle_args.append('-Ptesting-report')
 
   # Build an R8 with dependencies for bootstrapping tests before adding test sources.
   gradle_args.append('r8WithDeps')