Merge commit '875dd27341cfbec28ba37d0a6fa7a93696460d7e' into dev-release
diff --git a/PRESUBMIT.py b/PRESUBMIT.py
index 7c9cae3..a872ae8 100644
--- a/PRESUBMIT.py
+++ b/PRESUBMIT.py
@@ -57,10 +57,10 @@
 def CheckForAddedDisassemble(input_api, output_api):
   results = []
   for (file, line_nr, line) in input_api.RightHandSideLines():
-    if 'disassemble()' in line:
+    if file.LocalPath().endswith('.java') and '.disassemble()' in line:
       results.append(
           output_api.PresubmitError(
-              '%s:%s %s' % (file.LocalPath(), line_nr, line)))
+              'Test call to disassemble\n%s:%s %s' % (file.LocalPath(), line_nr, line)))
   return results
 
 def CheckForCopyRight(input_api, output_api, branch):
diff --git a/build.gradle b/build.gradle
index 693cdba..7429345 100644
--- a/build.gradle
+++ b/build.gradle
@@ -284,6 +284,7 @@
 
 def r8LibPath = "$buildDir/libs/r8lib.jar"
 def r8LibExludeDepsPath = "$buildDir/libs/r8lib-exclude-deps.jar"
+def r8DesugaredPath = "$buildDir/libs/r8desugared.jar"
 def r8LibGeneratedKeepRulesPath = "$buildDir/generated/keep.txt"
 def r8LibTestPath = "$buildDir/classes/r8libtest"
 def java11ClassFiles = "build/classes/java/mainJava11"
@@ -748,7 +749,7 @@
         outputs.file output
         workingDir = projectDir
         inputs.files r8Task.outputs.files + r8WithDeps.outputs.files
-        commandLine baseR8CommandLine([
+        commandLine baseCompilerCommandLine([
                 "relocator",
                 "--input",
                 r8Task.outputs.files[0],
@@ -890,11 +891,21 @@
     }
 }
 
+def baseCompilerCommandLine(compiler, args = []) {
+    // Execute r8 commands against a stable r8 with dependencies.
+    // TODO(b/139725780): See if we can remove or lower the heap size (-Xmx8g).
+    return [org.gradle.internal.jvm.Jvm.current().getJavaExecutable(),
+            "-Xmx8g", "-ea", "-jar", r8WithDeps.outputs.files[0]] + compiler + args
+}
+
 def baseR8CommandLine(args = []) {
     // Execute r8 commands against a stable r8 with dependencies.
-    // TODO(b/139725780): See if we can remove or lower the heap size (-Xmx6g).
-    return [org.gradle.internal.jvm.Jvm.current().getJavaExecutable(),
-            "-Xmx8g", "-ea", "-jar", r8WithDeps.outputs.files[0]] + args
+    return baseCompilerCommandLine("r8", args)
+}
+
+def baseD8CommandLine(args = []) {
+    // Execute r8 commands against a stable r8 with dependencies.
+    return baseCompilerCommandLine("d8", args)
 }
 
 def r8CfCommandLine(input, output, pgConfs = [], args = ["--release"], libs = []) {
@@ -908,6 +919,16 @@
     return baseR8CommandLine(allArgs)
 }
 
+def d8CfCommandLine(input, output, args = ["--release"], libs = []) {
+    def allArgs = [
+            "--classfile",
+            input,
+            "--output", output,
+            "--lib", "third_party/openjdk/openjdk-rt-1.8/rt.jar"
+    ] + args + libs.collectMany { ["--lib", it] }
+    return baseD8CommandLine(allArgs)
+}
+
 def r8LibCreateTask(name, pgConfs = [], r8Task, output, args = ["--release"], libs = []) {
     return tasks.create("r8Lib${name}", Exec) {
         inputs.files ([pgConfs, r8WithRelocatedDeps.outputs, r8Task.outputs, libs])
@@ -942,7 +963,7 @@
     outputs.file output
     workingDir = projectDir
     inputs.files (testJarSources.outputs.files + r8WithDeps.outputs.files)
-    commandLine baseR8CommandLine([
+    commandLine baseCompilerCommandLine([
             "relocator",
             "--input",
             testJarSources.outputs.files[0],
@@ -958,7 +979,7 @@
         // TODO(b/154785341): We should remove this.
         standardOutput new FileOutputStream(r8LibGeneratedKeepRulesPath)
     }
-    // Depend on r8WithDeps for running baseR8CommandLine.
+    // Depend on r8WithDeps for running baseCompilerCommandLine.
     dependsOn r8WithDeps
     dependsOn r8NoManifestWithRelocatedDeps
     dependsOn testJar
@@ -968,7 +989,7 @@
             r8NoManifestWithRelocatedDeps.outputs,
             testJar.outputs])
     outputs.file r8LibGeneratedKeepRulesPath
-    commandLine baseR8CommandLine([
+    commandLine baseCompilerCommandLine([
             "printuses",
             "--keeprules-allowobfuscation",
             "third_party/openjdk/openjdk-rt-1.8/rt.jar",
@@ -1008,6 +1029,18 @@
     outputs.file r8LibExludeDepsPath
 }
 
+task R8Desugared(type: Exec) {
+    dependsOn downloadOpenJDKrt
+    dependsOn r8NoManifestWithRelocatedDeps
+    inputs.files r8NoManifestWithRelocatedDeps.outputs.files
+    commandLine d8CfCommandLine(
+            r8NoManifestWithRelocatedDeps.outputs.files[0],
+            r8DesugaredPath,
+            ["--release"])
+    workingDir = projectDir
+    outputs.file r8DesugaredPath
+}
+
 task sourceJar(type: Jar, dependsOn: classes) {
     classifier = 'src'
     from sourceSets.main.allSource
diff --git a/src/main/java/com/android/tools/r8/D8Command.java b/src/main/java/com/android/tools/r8/D8Command.java
index cf475fa..a34b8ac 100644
--- a/src/main/java/com/android/tools/r8/D8Command.java
+++ b/src/main/java/com/android/tools/r8/D8Command.java
@@ -395,7 +395,7 @@
     // Assert some of R8 optimizations are disabled.
     assert !internal.enableInlining;
     assert !internal.enableClassInlining;
-    assert !internal.enableHorizontalClassMerging;
+    assert !internal.enableStaticClassMerging;
     assert !internal.enableVerticalClassMerging;
     assert !internal.enableClassStaticizer;
     assert !internal.enableEnumValueOptimization;
diff --git a/src/main/java/com/android/tools/r8/GenerateMainDexList.java b/src/main/java/com/android/tools/r8/GenerateMainDexList.java
index bacd375..243b37a 100644
--- a/src/main/java/com/android/tools/r8/GenerateMainDexList.java
+++ b/src/main/java/com/android/tools/r8/GenerateMainDexList.java
@@ -49,13 +49,12 @@
     try {
       DirectMappedDexApplication application =
           new ApplicationReader(app, options, timing).read(executor).toDirect();
-      AppView<? extends AppInfoWithClassHierarchy> appView =
-          AppView.createForR8(new AppInfoWithClassHierarchy(application));
+      AppView<? extends AppInfoWithClassHierarchy> appView = AppView.createForR8(application);
       appView.setAppServices(AppServices.builder(appView).build());
 
       MainDexListBuilder.checkForAssumedLibraryTypes(appView.appInfo());
 
-      SubtypingInfo subtypingInfo = new SubtypingInfo(application.allClasses(), application);
+      SubtypingInfo subtypingInfo = new SubtypingInfo(appView);
 
       RootSet mainDexRootSet =
           new RootSetBuilder(appView, subtypingInfo, options.mainDexKeepRules).run(executor);
@@ -71,7 +70,7 @@
           EnqueuerFactory.createForMainDexTracing(appView, subtypingInfo, graphConsumer);
       Set<DexProgramClass> liveTypes = enqueuer.traceMainDex(mainDexRootSet, executor, timing);
       // LiveTypes is the result.
-      MainDexClasses mainDexClasses = new MainDexListBuilder(liveTypes, application).run();
+      MainDexClasses mainDexClasses = new MainDexListBuilder(liveTypes, appView).run();
 
       List<String> result =
           mainDexClasses.getClasses().stream()
diff --git a/src/main/java/com/android/tools/r8/L8Command.java b/src/main/java/com/android/tools/r8/L8Command.java
index 48f2f7a..cae584b 100644
--- a/src/main/java/com/android/tools/r8/L8Command.java
+++ b/src/main/java/com/android/tools/r8/L8Command.java
@@ -167,7 +167,7 @@
     // Assert some of R8 optimizations are disabled.
     assert !internal.enableInlining;
     assert !internal.enableClassInlining;
-    assert !internal.enableHorizontalClassMerging;
+    assert !internal.enableStaticClassMerging;
     assert !internal.enableVerticalClassMerging;
     assert !internal.enableClassStaticizer;
     assert !internal.enableEnumValueOptimization;
diff --git a/src/main/java/com/android/tools/r8/PrintSeeds.java b/src/main/java/com/android/tools/r8/PrintSeeds.java
index 3f8b70e..b8a426c 100644
--- a/src/main/java/com/android/tools/r8/PrintSeeds.java
+++ b/src/main/java/com/android/tools/r8/PrintSeeds.java
@@ -87,10 +87,9 @@
     try {
       DirectMappedDexApplication application =
           new ApplicationReader(command.getInputApp(), options, timing).read(executor).toDirect();
-      AppView<? extends AppInfoWithClassHierarchy> appView =
-          AppView.createForR8(new AppInfoWithClassHierarchy(application));
+      AppView<AppInfoWithClassHierarchy> appView = AppView.createForR8(application);
       appView.setAppServices(AppServices.builder(appView).build());
-      SubtypingInfo subtypingInfo = new SubtypingInfo(application.allClasses(), application);
+      SubtypingInfo subtypingInfo = new SubtypingInfo(appView);
       RootSet rootSet =
           new RootSetBuilder(appView, subtypingInfo, options.getProguardConfiguration().getRules())
               .run(executor);
diff --git a/src/main/java/com/android/tools/r8/PrintUses.java b/src/main/java/com/android/tools/r8/PrintUses.java
index c94d67c..45ce043 100644
--- a/src/main/java/com/android/tools/r8/PrintUses.java
+++ b/src/main/java/com/android/tools/r8/PrintUses.java
@@ -182,6 +182,12 @@
       return false;
     }
 
+    @Override
+    public boolean registerInstanceOf(DexType type) {
+      addType(type);
+      return false;
+    }
+
     private void addType(DexType type) {
       if (isTargetType(type) && types.add(type)) {
         DexClass clazz = appInfo.definitionFor(type);
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java
index 93f229c..c006eb5 100644
--- a/src/main/java/com/android/tools/r8/R8.java
+++ b/src/main/java/com/android/tools/r8/R8.java
@@ -118,6 +118,7 @@
 import java.io.PrintStream;
 import java.nio.charset.StandardCharsets;
 import java.util.ArrayList;
+import java.util.Collection;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Set;
@@ -268,6 +269,10 @@
     new R8(options).run(app, executor);
   }
 
+  private static DirectMappedDexApplication getDirectApp(AppView<?> appView) {
+    return appView.appInfo().app().asDirect();
+  }
+
   private void run(AndroidApp inputApp, ExecutorService executorService) throws IOException {
     assert options.programConsumer != null;
     if (options.quiet) {
@@ -279,15 +284,17 @@
               "Running R8 version " + Version.LABEL + " with assertions enabled."));
     }
     try {
-      DirectMappedDexApplication application =
-          new ApplicationReader(inputApp, options, timing).read(executorService).toDirect();
+      AppView<AppInfoWithClassHierarchy> appView;
+      {
+        DirectMappedDexApplication application =
+            new ApplicationReader(inputApp, options, timing).read(executorService).toDirect();
 
-      // Now that the dex-application is fully loaded, close any internal archive providers.
-      inputApp.closeInternalArchiveProviders();
+        // Now that the dex-application is fully loaded, close any internal archive providers.
+        inputApp.closeInternalArchiveProviders();
 
-      AppView<AppInfoWithClassHierarchy> appView =
-          AppView.createForR8(new AppInfoWithClassHierarchy(application));
-      appView.setAppServices(AppServices.builder(appView).build());
+        appView = AppView.createForR8(application);
+        appView.setAppServices(AppServices.builder(appView).build());
+      }
 
       // Check for potentially having pass-through of Cf-code for kotlin libraries.
       options.enableCfByteCodePassThrough =
@@ -303,7 +310,7 @@
       InterfaceMethodRewriter.checkForAssumedLibraryTypes(appView.appInfo(), options);
       BackportedMethodRewriter.registerAssumedLibraryTypes(options);
       if (options.enableEnumUnboxing) {
-        if (application.definitionFor(options.itemFactory.enumUnboxingUtilityType) != null) {
+        if (appView.definitionFor(options.itemFactory.enumUnboxingUtilityType) != null) {
           // The enum unboxing utility class can be created only during cf to dex compilation.
           // If this is true, we are recompiling the dex application with R8 (compilation-steps).
           options.enableEnumUnboxing = false;
@@ -318,7 +325,7 @@
       Set<DexType> missingClasses = null;
       try {
         // TODO(b/154849103): Find a better way to determine missing classes.
-        missingClasses = new SubtypingInfo(application.allClasses(), appView).getMissingClasses();
+        missingClasses = new SubtypingInfo(appView).getMissingClasses();
         missingClasses = filterMissingClasses(
             missingClasses, options.getProguardConfiguration().getDontWarnPatterns());
         if (!missingClasses.isEmpty()) {
@@ -351,7 +358,7 @@
                     options.itemFactory, AndroidApiLevel.getAndroidApiLevel(options.minApiLevel)));
           }
         }
-        SubtypingInfo subtypingInfo = new SubtypingInfo(application.allClasses(), application);
+        SubtypingInfo subtypingInfo = new SubtypingInfo(appView);
         appView.setRootSet(
             new RootSetBuilder(
                     appView,
@@ -364,7 +371,6 @@
             options.isShrinking() ? AnnotationRemover.builder() : null;
         AppView<AppInfoWithLiveness> appViewWithLiveness =
             runEnqueuer(annotationRemoverBuilder, executorService, appView, subtypingInfo);
-        application = appViewWithLiveness.appInfo().app().asDirect();
         assert appView.rootSet().verifyKeptFieldsAreAccessedAndLive(appViewWithLiveness.appInfo());
         assert appView.rootSet().verifyKeptMethodsAreTargetedAndLive(appViewWithLiveness.appInfo());
         assert appView.rootSet().verifyKeptTypesAreLive(appViewWithLiveness.appInfo());
@@ -392,15 +398,12 @@
               shrinker -> shrinker.run(Mode.INITIAL_TREE_SHAKING));
 
           TreePruner pruner = new TreePruner(appViewWithLiveness);
-          application = pruner.run(application);
+          DirectMappedDexApplication prunedApp = pruner.run();
 
           if (options.enableEnumUnboxing) {
             DexProgramClass utilityClass =
                 EnumUnboxingRewriter.synthesizeEmptyEnumUnboxingUtilityClass(appView);
-            // We cannot know at this point if the class will be on the main dex list,
-            // updated later. Since this is inserted in the app at this point, we do not need
-            // to use any synthesized class hack and add the class as a program class.
-            application = application.builder().addProgramClass(utilityClass).build();
+            prunedApp = prunedApp.builder().addProgramClass(utilityClass).build();
           }
 
           // Recompute the subtyping information.
@@ -410,7 +413,7 @@
                   .appInfo()
                   .withLiveness()
                   .prunedCopyFrom(
-                      application,
+                      prunedApp,
                       removedClasses,
                       pruner.getMethodsToKeepForConfigurationDebugging()));
           appView.setAppServices(appView.appServices().prunedCopy(removedClasses));
@@ -431,7 +434,7 @@
       }
 
       assert appView.appInfo().hasLiveness();
-      assert verifyNoJarApplicationReaders(application.classes());
+      assert verifyNoJarApplicationReaders(appView.appInfo().classes());
       // Build conservative main dex content after first round of tree shaking. This is used
       // by certain optimizations to avoid introducing additional class references into main dex
       // classes, as that can cause the final number of main dex methods to grow.
@@ -440,8 +443,7 @@
       if (!options.mainDexKeepRules.isEmpty()) {
         assert appView.graphLens().isIdentityLens();
         // Find classes which may have code executed before secondary dex files installation.
-        SubtypingInfo subtypingInfo =
-            new SubtypingInfo(appView.appInfo().app().asDirect().allClasses(), appView);
+        SubtypingInfo subtypingInfo = new SubtypingInfo(appView);
         mainDexRootSet =
             new RootSetBuilder(appView, subtypingInfo, options.mainDexKeepRules)
                 .run(executorService);
@@ -450,7 +452,7 @@
             EnqueuerFactory.createForMainDexTracing(appView, subtypingInfo)
                 .traceMainDex(mainDexRootSet, executorService, timing);
         // Calculate the automatic main dex list according to legacy multidex constraints.
-        mainDexClasses = new MainDexListBuilder(mainDexBaseClasses, application).run();
+        mainDexClasses = new MainDexListBuilder(mainDexBaseClasses, appView).run();
         appView.appInfo().unsetObsolete();
       }
 
@@ -464,7 +466,11 @@
         SubtypingInfo subtypingInfo = appViewWithLiveness.appInfo().computeSubtypingInfo();
         GraphLens publicizedLens =
             ClassAndMemberPublicizer.run(
-                executorService, timing, application, appViewWithLiveness, subtypingInfo);
+                executorService,
+                timing,
+                appViewWithLiveness.appInfo().app(),
+                appViewWithLiveness,
+                subtypingInfo);
         boolean changed = appView.setGraphLens(publicizedLens);
         if (changed) {
           // We can now remove visibility bridges. Note that we do not need to update the
@@ -481,13 +487,8 @@
       if (options.shouldDesugarNests()) {
         timing.begin("NestBasedAccessDesugaring");
         R8NestBasedAccessDesugaring analyzer = new R8NestBasedAccessDesugaring(appViewWithLiveness);
-        NestedPrivateMethodLens lens = analyzer.run(executorService, application.builder());
-        if (lens != null) {
-          boolean changed = appView.setGraphLens(lens);
-          assert changed;
-          appViewWithLiveness.setAppInfo(
-              appViewWithLiveness.appInfo().rewrittenWithLens(application.asDirect(), lens));
-        }
+        NestedPrivateMethodLens lens = analyzer.run(executorService);
+        appView.rewriteWithLens(lens);
         timing.end();
       } else {
         timing.begin("NestReduction");
@@ -504,32 +505,27 @@
 
       if (!isKotlinLibraryCompilationWithInlinePassThrough
           && options.getProguardConfiguration().isOptimizing()) {
-        if (options.enableHorizontalClassMerging) {
+        if (options.enableStaticClassMerging) {
           timing.begin("HorizontalStaticClassMerger");
           StaticClassMerger staticClassMerger =
               new StaticClassMerger(appViewWithLiveness, options, mainDexClasses);
           NestedGraphLens lens = staticClassMerger.run();
-          if (lens != null) {
-            boolean changed = appView.setGraphLens(lens);
-            assert changed;
-            appViewWithLiveness.setAppInfo(
-                appViewWithLiveness.appInfo().rewrittenWithLens(application.asDirect(), lens));
-          }
+          appView.rewriteWithLens(lens);
           timing.end();
         }
         if (options.enableVerticalClassMerging) {
           timing.begin("VerticalClassMerger");
           VerticalClassMerger verticalClassMerger =
               new VerticalClassMerger(
-                  application, appViewWithLiveness, executorService, timing, mainDexClasses);
+                  getDirectApp(appViewWithLiveness),
+                  appViewWithLiveness,
+                  executorService,
+                  timing,
+                  mainDexClasses);
           VerticalClassMergerGraphLens lens = verticalClassMerger.run();
           if (lens != null) {
-            boolean changed = appView.setGraphLens(lens);
-            assert changed;
             appView.setVerticallyMergedClasses(verticalClassMerger.getMergedClasses());
-            application = application.asDirect().rewrittenWithLens(lens);
-            appViewWithLiveness.setAppInfo(
-                appViewWithLiveness.appInfo().rewrittenWithLens(application.asDirect(), lens));
+            appView.rewriteWithLens(lens);
           }
           timing.end();
         }
@@ -542,13 +538,8 @@
                         appViewWithLiveness,
                         new MethodPoolCollection(appViewWithLiveness, subtypingInfo))
                     .run(executorService, timing);
-            if (lens != null) {
-              boolean changed = appView.setGraphLens(lens);
-              assert changed;
-              assert application.asDirect().verifyNothingToRewrite(appView, lens);
-              appViewWithLiveness.setAppInfo(
-                  appViewWithLiveness.appInfo().rewrittenWithLens(application.asDirect(), lens));
-            }
+            assert lens == null || getDirectApp(appView).verifyNothingToRewrite(appView, lens);
+            appView.rewriteWithLens(lens);
             timing.end();
           }
           if (options.enableUninstantiatedTypeOptimization) {
@@ -560,13 +551,8 @@
                         new MethodPoolCollection(appViewWithLiveness, subtypingInfo),
                         executorService,
                         timing);
-            if (lens != null) {
-              boolean changed = appView.setGraphLens(lens);
-              assert changed;
-              assert application.asDirect().verifyNothingToRewrite(appView, lens);
-              appViewWithLiveness.setAppInfo(
-                  appViewWithLiveness.appInfo().rewrittenWithLens(application.asDirect(), lens));
-            }
+            assert lens == null || getDirectApp(appView).verifyNothingToRewrite(appView, lens);
+            appView.rewriteWithLens(lens);
             timing.end();
           }
         }
@@ -585,11 +571,20 @@
 
       appView.setAppServices(appView.appServices().rewrittenWithLens(appView.graphLens()));
 
+      // Collect the already pruned types before creating a new app info without liveness.
+      // TODO: we should avoid removing liveness.
+      Set<DexType> prunedTypes = appView.withLiveness().appInfo().getPrunedTypes();
+
+      // TODO: move to appview.
+      EnumValueInfoMapCollection enumValueInfoMapCollection =
+          appViewWithLiveness.appInfo().getEnumValueInfoMapCollection();
+
       timing.begin("Create IR");
       CfgPrinter printer = options.printCfg ? new CfgPrinter() : null;
       try {
         IRConverter converter = new IRConverter(appView, timing, printer, mainDexClasses);
-        application = converter.optimize(executorService).asDirect();
+        DexApplication application = converter.optimize(executorService).asDirect();
+        appView.setAppInfo(appView.appInfo().rebuild(previous -> application));
       } finally {
         timing.end();
       }
@@ -601,7 +596,7 @@
       // graph lens entirely, though, since it is needed for mapping all field and method signatures
       // back to the original program.
       timing.begin("AppliedGraphLens construction");
-      appView.setGraphLens(new AppliedGraphLens(appView, application.classes()));
+      appView.setGraphLens(new AppliedGraphLens(appView));
       timing.end();
 
       if (options.printCfg) {
@@ -616,15 +611,7 @@
         }
       }
 
-      // Collect the already pruned types before creating a new app info without liveness.
-      Set<DexType> prunedTypes = appView.withLiveness().appInfo().getPrunedTypes();
-
-      // TODO: move to appview.
-      EnumValueInfoMapCollection enumValueInfoMapCollection =
-          appViewWithLiveness.appInfo().getEnumValueInfoMapCollection();
-
       if (!options.mainDexKeepRules.isEmpty()) {
-        appView.setAppInfo(new AppInfoWithClassHierarchy(application));
         // No need to build a new main dex root set
         assert mainDexRootSet != null;
         GraphConsumer mainDexKeptGraphConsumer = options.mainDexKeptGraphConsumer;
@@ -636,15 +623,13 @@
 
         Enqueuer enqueuer =
             EnqueuerFactory.createForMainDexTracing(
-                appView,
-                new SubtypingInfo(application.allClasses(), application),
-                mainDexKeptGraphConsumer);
+                appView, new SubtypingInfo(appView), mainDexKeptGraphConsumer);
         // Find classes which may have code executed before secondary dex files installation.
         // Live types is the tracing result.
         Set<DexProgramClass> mainDexBaseClasses =
             enqueuer.traceMainDex(mainDexRootSet, executorService, timing);
         // Calculate the automatic main dex list according to legacy multidex constraints.
-        mainDexClasses = new MainDexListBuilder(mainDexBaseClasses, application).run();
+        mainDexClasses = new MainDexListBuilder(mainDexBaseClasses, appView).run();
         final MainDexClasses finalMainDexClasses = mainDexClasses;
 
         processWhyAreYouKeepingAndCheckDiscarded(
@@ -671,8 +656,6 @@
             executorService);
       }
 
-      appView.setAppInfo(new AppInfoWithClassHierarchy(application));
-
       if (options.shouldRerunEnqueuer()) {
         timing.begin("Post optimization code stripping");
         try {
@@ -689,7 +672,7 @@
           Enqueuer enqueuer =
               EnqueuerFactory.createForFinalTreeShaking(
                   appView,
-                  new SubtypingInfo(application.allClasses(), application),
+                  new SubtypingInfo(appView),
                   keptGraphConsumer,
                   missingClasses,
                   prunedTypes);
@@ -702,7 +685,7 @@
                       timing)
                   .withEnumValueInfoMaps(enumValueInfoMapCollection));
           // Rerunning the enqueuer should not give rise to any method rewritings.
-          assert enqueuer.buildGraphLens(appView) == appView.graphLens();
+          assert enqueuer.buildGraphLens(appView) == null;
           appView.withGeneratedMessageLiteBuilderShrinker(
               shrinker ->
                   shrinker.rewriteDeadBuilderReferencesFromDynamicMethods(
@@ -717,7 +700,7 @@
                     DefaultTreePrunerConfiguration.getInstance());
 
             TreePruner pruner = new TreePruner(appViewWithLiveness, treePrunerConfiguration);
-            application = pruner.run(application);
+            DirectMappedDexApplication application = pruner.run();
             Set<DexType> removedClasses = pruner.getRemovedClasses();
 
             if (options.usageInformationConsumer != null) {
@@ -789,12 +772,6 @@
         }
       }
 
-      // Add automatic main dex classes to an eventual manual list of classes.
-      if (!options.mainDexKeepRules.isEmpty()) {
-        application =
-            application.builder().addToMainDexList(mainDexClasses.getClasses()).build().asDirect();
-      }
-
       // Perform minification.
       NamingLens namingLens;
       if (options.getProguardConfiguration().hasApplyMappingFile()) {
@@ -818,19 +795,19 @@
       new KotlinMetadataRewriter(appView, namingLens).run(executorService);
       timing.end();
 
-      assert verifyMovedMethodsHaveOriginalMethodPosition(appView, application);
+      assert verifyMovedMethodsHaveOriginalMethodPosition(appView, getDirectApp(appView));
 
       timing.begin("Line number remapping");
       // When line number optimization is turned off the identity mapping for line numbers is
       // used. We still run the line number optimizer to collect line numbers and inline frame
       // information for the mapping file.
       ClassNameMapper classNameMapper =
-          LineNumberOptimizer.run(appView, application, inputApp, namingLens);
+          LineNumberOptimizer.run(appView, getDirectApp(appView), inputApp, namingLens);
       timing.end();
 
       // Overwrite SourceFile if specified. This step should be done after IR conversion.
       timing.begin("Rename SourceFile");
-      new SourceFileRewriter(appView, application).run();
+      new SourceFileRewriter(appView, appView.appInfo().app()).run();
       timing.end();
 
       // If a method filter is present don't produce output since the application is likely partial.
@@ -852,18 +829,33 @@
         assert !options.isShrinking();
       }
 
+      // Add automatic main dex classes to an eventual manual list of classes.
+      if (!options.mainDexKeepRules.isEmpty()) {
+        MainDexClasses finalMainDexClasses = mainDexClasses;
+        appView.setAppInfo(
+            appView
+                .appInfo()
+                .rebuild(
+                    application ->
+                        application
+                            .builder()
+                            .addToMainDexList(finalMainDexClasses.getClasses())
+                            .build()
+                            .asDirect()));
+      }
+
       // Validity checks.
-      assert application.asDirect().verifyCodeObjectsOwners();
-      assert application.classes().stream().allMatch(clazz -> clazz.isValid(options));
+      assert getDirectApp(appView).verifyCodeObjectsOwners();
+      assert appView.appInfo().classes().stream().allMatch(clazz -> clazz.isValid(options));
       if (options.isShrinking()
           || options.isMinifying()
           || options.getProguardConfiguration().hasApplyMappingFile()) {
-        assert appView.rootSet().verifyKeptItemsAreKept(application, appView.appInfo());
+        assert appView.rootSet().verifyKeptItemsAreKept(appView.appInfo().app(), appView.appInfo());
       }
       assert appView
           .graphLens()
           .verifyMappingToOriginalProgram(
-              application.classesWithDeterministicOrder(),
+              appView.appInfo().classesWithDeterministicOrder(),
               new ApplicationReader(inputApp.withoutMainDexList(), options, timing)
                   .read(executorService),
               appView.dexItemFactory());
@@ -883,7 +875,7 @@
       // Generate the resulting application resources.
       writeApplication(
           executorService,
-          application,
+          appView.appInfo().app(),
           appView,
           appView.graphLens(),
           appView.initClassLens(),
@@ -979,7 +971,8 @@
                 options.getProguardConfiguration().getDontWarnPatterns(),
                 executorService,
                 timing));
-    appView.setGraphLens(enqueuer.buildGraphLens(appView));
+    NestedGraphLens lens = enqueuer.buildGraphLens(appView);
+    appView.rewriteWithLens(lens);
     if (InternalOptions.assertionsEnabled()) {
       // Register the dead proto types. These are needed to verify that no new missing types are
       // reported and that no dead proto types are referenced in the generated application.
@@ -1022,8 +1015,7 @@
     // If there is no kept-graph info, re-run the enqueueing to compute it.
     if (whyAreYouKeepingConsumer == null) {
       whyAreYouKeepingConsumer = new WhyAreYouKeepingConsumer(null);
-      SubtypingInfo subtypingInfo =
-          new SubtypingInfo(appView.appInfo().app().asDirect().allClasses(), appView);
+      SubtypingInfo subtypingInfo = new SubtypingInfo(appView);
       if (forMainDex) {
         enqueuer =
             EnqueuerFactory.createForMainDexTracing(
@@ -1052,7 +1044,7 @@
     throw new CompilationError("Discard checks failed.");
   }
 
-  private static boolean verifyNoJarApplicationReaders(List<DexProgramClass> classes) {
+  private static boolean verifyNoJarApplicationReaders(Collection<DexProgramClass> classes) {
     for (DexProgramClass clazz : classes) {
       for (DexEncodedMethod method : clazz.methods()) {
         if (method.getCode() != null) {
diff --git a/src/main/java/com/android/tools/r8/R8Command.java b/src/main/java/com/android/tools/r8/R8Command.java
index 5abbc25..f2d182d 100644
--- a/src/main/java/com/android/tools/r8/R8Command.java
+++ b/src/main/java/com/android/tools/r8/R8Command.java
@@ -842,7 +842,7 @@
             ? LineNumberOptimization.ON
             : LineNumberOptimization.OFF;
 
-    assert internal.enableHorizontalClassMerging || !proguardConfiguration.isOptimizing();
+    assert internal.enableStaticClassMerging || !proguardConfiguration.isOptimizing();
     assert !internal.enableTreeShakingOfLibraryMethodOverrides;
     assert internal.enableVerticalClassMerging || !proguardConfiguration.isOptimizing();
     if (internal.debug) {
@@ -851,7 +851,7 @@
       internal.getProguardConfiguration().getKeepAttributes().localVariableTypeTable = true;
       internal.enableInlining = false;
       internal.enableClassInlining = false;
-      internal.enableHorizontalClassMerging = false;
+      internal.enableStaticClassMerging = false;
       internal.enableVerticalClassMerging = false;
       internal.enableClassStaticizer = false;
       internal.outline.enabled = false;
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfInvoke.java b/src/main/java/com/android/tools/r8/cf/code/CfInvoke.java
index a7949db..510249b 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfInvoke.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfInvoke.java
@@ -280,7 +280,7 @@
   private Type invokeTypeForInvokeSpecialToNonInitMethodOnHolder(
       AppView<?> appView, CfSourceCode code) {
     boolean desugaringEnabled = appView.options().isInterfaceMethodDesugaringEnabled();
-    DexEncodedMethod encodedMethod = lookupMethod(appView, method);
+    DexEncodedMethod encodedMethod = lookupMethodOnHolder(appView, method);
     if (encodedMethod == null) {
       // The method is not defined on the class, we can use super to target. When desugaring
       // default interface methods, it is expected they are targeted with invoke-direct.
@@ -303,11 +303,13 @@
         "Failed to compile unsupported use of invokespecial", code.getOrigin());
   }
 
-  private DexEncodedMethod lookupMethod(AppView<?> appView, DexMethod method) {
+  private DexEncodedMethod lookupMethodOnHolder(AppView<?> appView, DexMethod method) {
     GraphLensLookupResult lookupResult =
         appView.graphLens().lookupMethod(method, method, Type.DIRECT);
     DexMethod rewrittenMethod = lookupResult.getMethod();
-    DexProgramClass clazz = appView.definitionForProgramType(rewrittenMethod.holder);
+    // Directly lookup the program type for holder. This bypasses lookup order as well as looks
+    // directly on the application data, which bypasses and indirection or validation.
+    DexProgramClass clazz = appView.appInfo().unsafeDirectProgramTypeLookup(rewrittenMethod.holder);
     assert clazz != null;
     return clazz.lookupMethod(rewrittenMethod);
   }
diff --git a/src/main/java/com/android/tools/r8/code/InstanceOf.java b/src/main/java/com/android/tools/r8/code/InstanceOf.java
index 4550406..36d7a0b 100644
--- a/src/main/java/com/android/tools/r8/code/InstanceOf.java
+++ b/src/main/java/com/android/tools/r8/code/InstanceOf.java
@@ -49,7 +49,7 @@
 
   @Override
   public void registerUse(UseRegistry registry) {
-    registry.registerTypeReference(getType());
+    registry.registerInstanceOf(getType());
   }
 
   public DexType getType() {
diff --git a/src/main/java/com/android/tools/r8/graph/AppInfo.java b/src/main/java/com/android/tools/r8/graph/AppInfo.java
index 60b8220..ee67876 100644
--- a/src/main/java/com/android/tools/r8/graph/AppInfo.java
+++ b/src/main/java/com/android/tools/r8/graph/AppInfo.java
@@ -150,8 +150,7 @@
     return definition;
   }
 
-  @Override
-  public DexProgramClass definitionForProgramType(DexType type) {
+  public DexProgramClass unsafeDirectProgramTypeLookup(DexType type) {
     return app.programDefinitionFor(type);
   }
 
diff --git a/src/main/java/com/android/tools/r8/graph/AppInfoWithClassHierarchy.java b/src/main/java/com/android/tools/r8/graph/AppInfoWithClassHierarchy.java
index a0fd03b..481af1e 100644
--- a/src/main/java/com/android/tools/r8/graph/AppInfoWithClassHierarchy.java
+++ b/src/main/java/com/android/tools/r8/graph/AppInfoWithClassHierarchy.java
@@ -31,6 +31,7 @@
 import java.util.Set;
 import java.util.function.BiConsumer;
 import java.util.function.BiFunction;
+import java.util.function.Function;
 
 /* Specific subclass of AppInfo designed to support desugaring in D8. Desugaring requires a
  * minimal amount of knowledge in the overall program, provided through classpath. Basic
@@ -57,6 +58,10 @@
     return new AppInfoWithClassHierarchy(appInfo);
   }
 
+  public AppInfoWithClassHierarchy rebuild(Function<DexApplication, DexApplication> fn) {
+    return new AppInfoWithClassHierarchy(fn.apply(app()));
+  }
+
   @Override
   public boolean hasClassHierarchy() {
     assert checkIfObsolete();
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 7b0c80f..3bacf89 100644
--- a/src/main/java/com/android/tools/r8/graph/AppView.java
+++ b/src/main/java/com/android/tools/r8/graph/AppView.java
@@ -5,6 +5,7 @@
 package com.android.tools.r8.graph;
 
 import com.android.tools.r8.graph.DexValue.DexValueString;
+import com.android.tools.r8.graph.GraphLens.NestedGraphLens;
 import com.android.tools.r8.graph.analysis.InitializedClassesInInstanceMethodsAnalysis.InitializedClassesInInstanceMethods;
 import com.android.tools.r8.graph.classmerging.HorizontallyMergedLambdaClasses;
 import com.android.tools.r8.graph.classmerging.MergedClassesCollection;
@@ -124,7 +125,8 @@
     return new AppView<>(appInfo, WholeProgramOptimizations.OFF, mapper);
   }
 
-  public static <T extends AppInfo> AppView<T> createForR8(T appInfo) {
+  public static AppView<AppInfoWithClassHierarchy> createForR8(DexApplication application) {
+    AppInfoWithClassHierarchy appInfo = new AppInfoWithClassHierarchy(application);
     return new AppView<>(
         appInfo, WholeProgramOptimizations.ON, defaultPrefixRewritingMapper(appInfo));
   }
@@ -230,11 +232,6 @@
     return appInfo().definitionFor(type);
   }
 
-  @Override
-  public final DexProgramClass definitionForProgramType(DexType type) {
-    return appInfo.app().programDefinitionFor(type);
-  }
-
   public OptionalBool isInterface(DexType type) {
     assert type.isClassType();
     // Without whole program information we should not assume anything about any other class than
@@ -430,7 +427,7 @@
 
   public boolean validateUnboxedEnumsHaveBeenPruned() {
     for (DexType unboxedEnum : unboxedEnums.enumSet()) {
-      assert definitionForProgramType(unboxedEnum) == null
+      assert appInfo.definitionForWithoutExistenceAssert(unboxedEnum) == null
           : "Enum " + unboxedEnum + " has been unboxed but is still in the program.";
       assert appInfo().withLiveness().wasPruned(unboxedEnum)
           : "Enum " + unboxedEnum + " has been unboxed but was not pruned.";
@@ -471,4 +468,19 @@
   public boolean hasCfByteCodePassThroughMethods() {
     return !cfByteCodePassThrough.isEmpty();
   }
+
+  public void rewriteWithLens(NestedGraphLens lens) {
+    rewriteWithLens(lens, withLiveness());
+  }
+
+  private static void rewriteWithLens(NestedGraphLens lens, AppView<AppInfoWithLiveness> appView) {
+    if (lens == null) {
+      return;
+    }
+    boolean changed = appView.setGraphLens(lens);
+    assert changed;
+    DirectMappedDexApplication application = appView.appInfo().app().asDirect();
+    assert application.verifyWithLens(lens);
+    appView.setAppInfo(appView.appInfo().rewrittenWithLens(application, lens));
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/graph/AppliedGraphLens.java b/src/main/java/com/android/tools/r8/graph/AppliedGraphLens.java
index b602135..90b940a 100644
--- a/src/main/java/com/android/tools/r8/graph/AppliedGraphLens.java
+++ b/src/main/java/com/android/tools/r8/graph/AppliedGraphLens.java
@@ -27,11 +27,10 @@
   private final Map<DexMethod, DexMethod> originalMethodSignaturesForBridges =
       new IdentityHashMap<>();
 
-  public AppliedGraphLens(
-      AppView<? extends AppInfoWithClassHierarchy> appView, Iterable<DexProgramClass> classes) {
+  public AppliedGraphLens(AppView<? extends AppInfoWithClassHierarchy> appView) {
     this.appView = appView;
 
-    for (DexProgramClass clazz : classes) {
+    for (DexProgramClass clazz : appView.appInfo().classes()) {
       // Record original type names.
       {
         DexType type = clazz.type;
diff --git a/src/main/java/com/android/tools/r8/graph/AssemblyWriter.java b/src/main/java/com/android/tools/r8/graph/AssemblyWriter.java
index d10c69f..f1ddad4 100644
--- a/src/main/java/com/android/tools/r8/graph/AssemblyWriter.java
+++ b/src/main/java/com/android/tools/r8/graph/AssemblyWriter.java
@@ -31,7 +31,7 @@
   private final boolean writeAnnotations;
   private final boolean writeIR;
   private final boolean writeCode;
-  private final AppInfoWithClassHierarchy appInfo;
+  private final AppInfo appInfo;
   private final Kotlin kotlin;
   private final Timing timing = new Timing("AssemblyWriter");
 
@@ -48,7 +48,7 @@
     this.writeIR = writeIR;
     this.writeCode = writeCode;
     if (writeIR) {
-      this.appInfo = new AppInfoWithClassHierarchy(application.toDirect());
+      this.appInfo = new AppInfo(application.toDirect());
       if (options.programConsumer == null) {
         // Use class-file backend, since the CF frontend for testing does not support desugaring of
         // synchronized methods for the DEX backend (b/109789541).
diff --git a/src/main/java/com/android/tools/r8/graph/CfCode.java b/src/main/java/com/android/tools/r8/graph/CfCode.java
index 24237bb..16533fe 100644
--- a/src/main/java/com/android/tools/r8/graph/CfCode.java
+++ b/src/main/java/com/android/tools/r8/graph/CfCode.java
@@ -20,8 +20,8 @@
 import com.android.tools.r8.errors.InvalidDebugInfoException;
 import com.android.tools.r8.errors.Unimplemented;
 import com.android.tools.r8.ir.code.IRCode;
+import com.android.tools.r8.ir.code.NumberGenerator;
 import com.android.tools.r8.ir.code.Position;
-import com.android.tools.r8.ir.code.ValueNumberGenerator;
 import com.android.tools.r8.ir.conversion.CfSourceCode;
 import com.android.tools.r8.ir.conversion.IRBuilder;
 import com.android.tools.r8.ir.conversion.MethodProcessor;
@@ -298,7 +298,7 @@
       ProgramMethod context,
       ProgramMethod method,
       AppView<?> appView,
-      ValueNumberGenerator valueNumberGenerator,
+      NumberGenerator valueNumberGenerator,
       Position callerPosition,
       Origin origin,
       MethodProcessor methodProcessor) {
@@ -313,7 +313,7 @@
       ProgramMethod context,
       ProgramMethod method,
       AppView<?> appView,
-      ValueNumberGenerator generator,
+      NumberGenerator valueNumberGenerator,
       Position callerPosition,
       Origin origin,
       MethodProcessor methodProcessor) {
@@ -323,13 +323,13 @@
           context,
           method,
           appView,
-          generator,
+          valueNumberGenerator,
           callerPosition,
           origin,
           methodProcessor);
     } else {
       return internalBuildWithLocals(
-          context, method, appView, generator, callerPosition, origin, methodProcessor);
+          context, method, appView, valueNumberGenerator, callerPosition, origin, methodProcessor);
     }
   }
 
@@ -338,7 +338,7 @@
       ProgramMethod context,
       ProgramMethod method,
       AppView<?> appView,
-      ValueNumberGenerator generator,
+      NumberGenerator valueNumberGenerator,
       Position callerPosition,
       Origin origin,
       MethodProcessor methodProcessor) {
@@ -348,7 +348,7 @@
           context,
           method,
           appView,
-          generator,
+          valueNumberGenerator,
           callerPosition,
           origin,
           methodProcessor);
@@ -359,7 +359,7 @@
           context,
           method,
           appView,
-          generator,
+          valueNumberGenerator,
           callerPosition,
           origin,
           methodProcessor);
@@ -372,7 +372,7 @@
       ProgramMethod context,
       ProgramMethod method,
       AppView<?> appView,
-      ValueNumberGenerator generator,
+      NumberGenerator valueNumberGenerator,
       Position callerPosition,
       Origin origin,
       MethodProcessor methodProcessor) {
@@ -389,7 +389,7 @@
         methodProcessor == null
             ? IRBuilder.create(method, appView, source, origin)
             : IRBuilder.createForInlining(
-                method, appView, source, origin, methodProcessor, generator);
+                method, appView, source, origin, methodProcessor, valueNumberGenerator);
     return builder.build(context);
   }
 
diff --git a/src/main/java/com/android/tools/r8/graph/Code.java b/src/main/java/com/android/tools/r8/graph/Code.java
index 9f384ca..bb11c69 100644
--- a/src/main/java/com/android/tools/r8/graph/Code.java
+++ b/src/main/java/com/android/tools/r8/graph/Code.java
@@ -7,8 +7,8 @@
 import com.android.tools.r8.dex.MixedSectionCollection;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.ir.code.IRCode;
+import com.android.tools.r8.ir.code.NumberGenerator;
 import com.android.tools.r8.ir.code.Position;
-import com.android.tools.r8.ir.code.ValueNumberGenerator;
 import com.android.tools.r8.ir.conversion.MethodProcessor;
 import com.android.tools.r8.ir.optimize.Outliner.OutlineCode;
 import com.android.tools.r8.naming.ClassNameMapper;
@@ -23,7 +23,7 @@
       ProgramMethod context,
       ProgramMethod method,
       AppView<?> appView,
-      ValueNumberGenerator valueNumberGenerator,
+      NumberGenerator valueNumberGenerator,
       Position callerPosition,
       Origin origin,
       MethodProcessor methodProcessor) {
diff --git a/src/main/java/com/android/tools/r8/graph/DefaultUseRegistry.java b/src/main/java/com/android/tools/r8/graph/DefaultUseRegistry.java
index 7d6b095..a267d5b 100644
--- a/src/main/java/com/android/tools/r8/graph/DefaultUseRegistry.java
+++ b/src/main/java/com/android/tools/r8/graph/DefaultUseRegistry.java
@@ -69,4 +69,9 @@
   public boolean registerTypeReference(DexType type) {
     return true;
   }
+
+  @Override
+  public boolean registerInstanceOf(DexType type) {
+    return true;
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/graph/DexCode.java b/src/main/java/com/android/tools/r8/graph/DexCode.java
index 76bead8..ae3fe1e 100644
--- a/src/main/java/com/android/tools/r8/graph/DexCode.java
+++ b/src/main/java/com/android/tools/r8/graph/DexCode.java
@@ -12,8 +12,8 @@
 import com.android.tools.r8.graph.DexCode.TryHandler.TypeAddrPair;
 import com.android.tools.r8.graph.DexDebugEvent.StartLocal;
 import com.android.tools.r8.ir.code.IRCode;
+import com.android.tools.r8.ir.code.NumberGenerator;
 import com.android.tools.r8.ir.code.Position;
-import com.android.tools.r8.ir.code.ValueNumberGenerator;
 import com.android.tools.r8.ir.conversion.DexSourceCode;
 import com.android.tools.r8.ir.conversion.IRBuilder;
 import com.android.tools.r8.ir.conversion.MethodProcessor;
@@ -229,7 +229,7 @@
       ProgramMethod context,
       ProgramMethod method,
       AppView<?> appView,
-      ValueNumberGenerator valueNumberGenerator,
+      NumberGenerator valueNumberGenerator,
       Position callerPosition,
       Origin origin,
       MethodProcessor methodProcessor) {
diff --git a/src/main/java/com/android/tools/r8/graph/DexDefinitionSupplier.java b/src/main/java/com/android/tools/r8/graph/DexDefinitionSupplier.java
index 2ec6dbf..cc91bcd 100644
--- a/src/main/java/com/android/tools/r8/graph/DexDefinitionSupplier.java
+++ b/src/main/java/com/android/tools/r8/graph/DexDefinitionSupplier.java
@@ -6,15 +6,76 @@
 
 public interface DexDefinitionSupplier {
 
-  DexClass definitionFor(DexType type);
-
-  DexProgramClass definitionForProgramType(DexType type);
-
-  default <D extends DexEncodedMember<D, R>, R extends DexMember<D, R>>
-      DexClass definitionForHolder(DexEncodedMember<D, R> member) {
-    return definitionForHolder(member.toReference());
+  /**
+   * Lookup for the definition of a type independent of context.
+   *
+   * <p>This will make use of the compilers program, library and classpath precedence.
+   *
+   * @param type Type to look up the defintion for.
+   * @return Definition of the type or null if no definition exists.
+   */
+  default DexClass contextIndependentDefinitionFor(DexType type) {
+    return definitionFor(type);
   }
 
+  /**
+   * Lookup for the definition of a type from a given context.
+   *
+   * <p>This ensures that a context overrides the usual lookup precedence if looking up itself.
+   *
+   * @param type Type to look up a definition for.
+   * @param context Context from which the lookup is taking place.
+   * @return Definition of the type or null if no definition exists.
+   */
+  default DexClass definitionFor(DexType type, DexProgramClass context) {
+    return type == context.type ? context : contextIndependentDefinitionFor(type);
+  }
+
+  /**
+   * Lookup for the program definition of a type from a given context.
+   *
+   * <p>This ensures that a context overrides the usual lookup precedence if looking up itself.
+   *
+   * @param type Type to look up a definition for.
+   * @param context Context from which the lookup is taking place.
+   * @return Definition of the type if it is a program type or null if not or no definition exists.
+   */
+  default DexProgramClass programDefinitionFor(DexType type, DexProgramClass context) {
+    return DexProgramClass.asProgramClassOrNull(definitionFor(type, context));
+  }
+
+  default <D extends DexEncodedMember<D, R>, R extends DexMember<D, R>>
+      DexClass definitionForHolder(DexEncodedMember<D, R> member, ProgramMethod context) {
+    return definitionForHolder(member.toReference(), context.getHolder());
+  }
+
+  default <D extends DexEncodedMember<D, R>, R extends DexMember<D, R>>
+      DexClass definitionForHolder(DexEncodedMember<D, R> member, DexProgramClass context) {
+    return definitionForHolder(member.toReference(), context);
+  }
+
+  default <D extends DexEncodedMember<D, R>, R extends DexMember<D, R>>
+      DexClass definitionForHolder(DexMember<D, R> member, ProgramMethod context) {
+    return definitionFor(member.holder, context.getHolder());
+  }
+
+  default <D extends DexEncodedMember<D, R>, R extends DexMember<D, R>>
+      DexClass definitionForHolder(DexMember<D, R> member, DexProgramClass context) {
+    return definitionFor(member.holder, context);
+  }
+
+  // Use definitionFor with a context or contextIndependentDefinitionFor without.
+  @Deprecated
+  DexClass definitionFor(DexType type);
+
+  // Use programDefinitionFor with a context.
+  @Deprecated
+  default DexProgramClass definitionForProgramType(DexType type) {
+    return DexProgramClass.asProgramClassOrNull(definitionFor(type));
+  }
+
+  // Use definitionForHolder with a context.
+  @Deprecated
   default <D extends DexEncodedMember<D, R>, R extends DexMember<D, R>>
       DexClass definitionForHolder(DexMember<D, R> member) {
     return definitionFor(member.holder);
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 019d295..cab8542 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
@@ -180,6 +180,10 @@
     obsolete = true;
   }
 
+  public CompilationState getCompilationState() {
+    return compilationState;
+  }
+
   public DexEncodedMethod getDefaultInterfaceMethodImplementation() {
     return defaultInterfaceMethodImplementation;
   }
diff --git a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
index 8b2bd65..aa1d5a9 100644
--- a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
+++ b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
@@ -462,7 +462,7 @@
   public final AssertionErrorMethods assertionErrorMethods = new AssertionErrorMethods();
   public final ClassMethods classMethods = new ClassMethods();
   public final ConstructorMethods constructorMethods = new ConstructorMethods();
-  public final EnumMethods enumMethods = new EnumMethods();
+  public final EnumMembers enumMembers = new EnumMembers();
   public final NullPointerExceptionMethods npeMethods = new NullPointerExceptionMethods();
   public final IllegalArgumentExceptionMethods illegalArgumentExceptionMethods =
       new IllegalArgumentExceptionMethods();
@@ -688,7 +688,7 @@
   // If not, that library method should not be added here because it literally has side effects.
   public Map<DexMethod, Predicate<InvokeMethod>> libraryMethodsWithoutSideEffects =
       Streams.<Pair<DexMethod, Predicate<InvokeMethod>>>concat(
-              Stream.of(new Pair<>(enumMethods.constructor, alwaysTrue())),
+              Stream.of(new Pair<>(enumMembers.constructor, alwaysTrue())),
               Stream.of(new Pair<>(npeMethods.init, alwaysTrue())),
               Stream.of(new Pair<>(npeMethods.initWithMessage, alwaysTrue())),
               Stream.of(new Pair<>(objectMembers.constructor, alwaysTrue())),
@@ -1268,11 +1268,14 @@
     }
   }
 
-  public class EnumMethods {
+  public class EnumMembers {
+
+    public final DexField nameField = createField(enumType, stringType, "name");
+    public final DexField ordinalField = createField(enumType, intType, "ordinal");
 
     public final DexMethod valueOf;
-    public final DexMethod ordinal;
-    public final DexMethod name;
+    public final DexMethod ordinalMethod;
+    public final DexMethod nameMethod;
     public final DexMethod toString;
     public final DexMethod compareTo;
     public final DexMethod equals;
@@ -1283,25 +1286,17 @@
     public final DexMethod finalize =
         createMethod(enumType, createProto(voidType), finalizeMethodName);
 
-    private EnumMethods() {
+    private EnumMembers() {
       valueOf =
           createMethod(
               enumDescriptor,
               valueOfMethodName,
               enumDescriptor,
               new DexString[] {classDescriptor, stringDescriptor});
-      ordinal =
-          createMethod(
-              enumDescriptor,
-              ordinalMethodName,
-              intDescriptor,
-              DexString.EMPTY_ARRAY);
-      name =
-          createMethod(
-              enumDescriptor,
-              nameMethodName,
-              stringDescriptor,
-              DexString.EMPTY_ARRAY);
+      ordinalMethod =
+          createMethod(enumDescriptor, ordinalMethodName, intDescriptor, DexString.EMPTY_ARRAY);
+      nameMethod =
+          createMethod(enumDescriptor, nameMethodName, stringDescriptor, DexString.EMPTY_ARRAY);
       toString =
           createMethod(
               enumDescriptor,
@@ -1321,6 +1316,15 @@
           createMethod(enumDescriptor, hashCodeMethodName, intDescriptor, DexString.EMPTY_ARRAY);
     }
 
+    public void forEachField(Consumer<DexField> fn) {
+      fn.accept(nameField);
+      fn.accept(ordinalField);
+    }
+
+    public boolean isNameOrOrdinalField(DexField field) {
+      return field == nameField || field == ordinalField;
+    }
+
     public boolean isValuesMethod(DexMethod method, DexClass enumClass) {
       assert enumClass.isEnum();
       return method.holder == enumClass.type
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 35d8146..7711eb3 100644
--- a/src/main/java/com/android/tools/r8/graph/DexLibraryClass.java
+++ b/src/main/java/com/android/tools/r8/graph/DexLibraryClass.java
@@ -63,6 +63,10 @@
     assert kind == Kind.CF : "Invalid kind " + kind + " for library-path class " + type;
   }
 
+  public static DexLibraryClass asLibraryClassOrNull(DexClass clazz) {
+    return clazz != null ? clazz.asLibraryClass() : null;
+  }
+
   private static boolean verifyLibraryMethod(DexEncodedMethod method) {
     assert !method.isClassInitializer();
     assert !method.isPrivateMethod();
diff --git a/src/main/java/com/android/tools/r8/graph/DirectMappedDexApplication.java b/src/main/java/com/android/tools/r8/graph/DirectMappedDexApplication.java
index 3a78118..612329c 100644
--- a/src/main/java/com/android/tools/r8/graph/DirectMappedDexApplication.java
+++ b/src/main/java/com/android/tools/r8/graph/DirectMappedDexApplication.java
@@ -80,19 +80,14 @@
   }
 
   @Override
-  public DexProgramClass definitionForProgramType(DexType type) {
-    return programDefinitionFor(type);
-  }
-
-  @Override
   public DexItemFactory dexItemFactory() {
     return dexItemFactory;
   }
 
   @Override
   public DexProgramClass programDefinitionFor(DexType type) {
-    DexClass clazz = definitionFor(type);
-    return clazz instanceof DexProgramClass ? clazz.asProgramClass() : null;
+    // The direct mapped application has no duplicates so this coincides with definitionFor.
+    return DexProgramClass.asProgramClassOrNull(definitionFor(type));
   }
 
   @Override
@@ -120,12 +115,10 @@
     return "DexApplication (direct)";
   }
 
-  public DirectMappedDexApplication rewrittenWithLens(GraphLens lens) {
-    // As a side effect, this will rebuild the program classes and library classes maps.
-    DirectMappedDexApplication rewrittenApplication = builder().build().asDirect();
-    assert rewrittenApplication.mappingIsValid(lens, allClasses.keySet());
-    assert rewrittenApplication.verifyCodeObjectsOwners();
-    return rewrittenApplication;
+  public boolean verifyWithLens(GraphLens lens) {
+    assert mappingIsValid(lens, allClasses.keySet());
+    assert verifyCodeObjectsOwners();
+    return true;
   }
 
   public boolean verifyNothingToRewrite(AppView<?> appView, GraphLens lens) {
@@ -144,6 +137,9 @@
     // original type will point to a definition that was renamed.
     for (DexType type : types) {
       DexType renamed = graphLens.lookupType(type);
+      if (renamed.isIntType()) {
+        continue;
+      }
       if (renamed != type) {
         if (definitionFor(type) == null && definitionFor(renamed) != null) {
           continue;
diff --git a/src/main/java/com/android/tools/r8/graph/LazyCfCode.java b/src/main/java/com/android/tools/r8/graph/LazyCfCode.java
index 6c93947..b0e0312 100644
--- a/src/main/java/com/android/tools/r8/graph/LazyCfCode.java
+++ b/src/main/java/com/android/tools/r8/graph/LazyCfCode.java
@@ -54,8 +54,8 @@
 import com.android.tools.r8.ir.code.If;
 import com.android.tools.r8.ir.code.MemberType;
 import com.android.tools.r8.ir.code.Monitor;
+import com.android.tools.r8.ir.code.NumberGenerator;
 import com.android.tools.r8.ir.code.Position;
-import com.android.tools.r8.ir.code.ValueNumberGenerator;
 import com.android.tools.r8.ir.code.ValueType;
 import com.android.tools.r8.ir.conversion.MethodProcessor;
 import com.android.tools.r8.naming.ClassNameMapper;
@@ -218,7 +218,7 @@
       ProgramMethod context,
       ProgramMethod method,
       AppView<?> appView,
-      ValueNumberGenerator valueNumberGenerator,
+      NumberGenerator valueNumberGenerator,
       Position callerPosition,
       Origin origin,
       MethodProcessor methodProcessor) {
diff --git a/src/main/java/com/android/tools/r8/graph/LazyLoadedDexApplication.java b/src/main/java/com/android/tools/r8/graph/LazyLoadedDexApplication.java
index 7127794..1721860 100644
--- a/src/main/java/com/android/tools/r8/graph/LazyLoadedDexApplication.java
+++ b/src/main/java/com/android/tools/r8/graph/LazyLoadedDexApplication.java
@@ -88,7 +88,7 @@
     return programClasses.get(type);
   }
 
-  public DexLibraryClass libraryDefintionFor(DexType type) {
+  public DexLibraryClass libraryDefinitionFor(DexType type) {
     assert type.isClassType() : "Cannot lookup library definition for type: " + type;
     return libraryClasses.get(type);
   }
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 88c781c..6ff5f1d 100644
--- a/src/main/java/com/android/tools/r8/graph/ProgramMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/ProgramMethod.java
@@ -4,8 +4,8 @@
 package com.android.tools.r8.graph;
 
 import com.android.tools.r8.ir.code.IRCode;
+import com.android.tools.r8.ir.code.NumberGenerator;
 import com.android.tools.r8.ir.code.Position;
-import com.android.tools.r8.ir.code.ValueNumberGenerator;
 import com.android.tools.r8.ir.conversion.MethodProcessor;
 import com.android.tools.r8.logging.Log;
 import com.android.tools.r8.origin.Origin;
@@ -25,7 +25,7 @@
   public IRCode buildInliningIR(
       ProgramMethod context,
       AppView<?> appView,
-      ValueNumberGenerator valueNumberGenerator,
+      NumberGenerator valueNumberGenerator,
       Position callerPosition,
       Origin origin,
       MethodProcessor methodProcessor) {
diff --git a/src/main/java/com/android/tools/r8/graph/SubtypingInfo.java b/src/main/java/com/android/tools/r8/graph/SubtypingInfo.java
index 662ccb0..b73d2bc 100644
--- a/src/main/java/com/android/tools/r8/graph/SubtypingInfo.java
+++ b/src/main/java/com/android/tools/r8/graph/SubtypingInfo.java
@@ -41,7 +41,15 @@
   // Set of missing classes, discovered during subtypeMap computation.
   private final Set<DexType> missingClasses = Sets.newIdentityHashSet();
 
-  public SubtypingInfo(Collection<DexClass> classes, DexDefinitionSupplier definitions) {
+  public SubtypingInfo(AppView<? extends AppInfoWithClassHierarchy> appView) {
+    this(appView.appInfo());
+  }
+
+  public SubtypingInfo(AppInfoWithClassHierarchy appInfo) {
+    this(appInfo.app().asDirect().allClasses(), appInfo);
+  }
+
+  private SubtypingInfo(Collection<DexClass> classes, DexDefinitionSupplier definitions) {
     factory = definitions.dexItemFactory();
     // Recompute subtype map if we have modified the graph.
     populateSubtypeMap(classes, definitions::definitionFor, factory);
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 1e89e13..de4021e 100644
--- a/src/main/java/com/android/tools/r8/graph/UseRegistry.java
+++ b/src/main/java/com/android/tools/r8/graph/UseRegistry.java
@@ -57,6 +57,8 @@
 
   public abstract boolean registerTypeReference(DexType type);
 
+  public abstract boolean registerInstanceOf(DexType type);
+
   public boolean registerConstClass(DexType type) {
     return registerTypeReference(type);
   }
diff --git a/src/main/java/com/android/tools/r8/graph/analysis/EnqueuerCheckCastAnalysis.java b/src/main/java/com/android/tools/r8/graph/analysis/EnqueuerCheckCastAnalysis.java
new file mode 100644
index 0000000..503e74e
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/graph/analysis/EnqueuerCheckCastAnalysis.java
@@ -0,0 +1,12 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.graph.analysis;
+
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.ProgramMethod;
+
+public interface EnqueuerCheckCastAnalysis {
+  void traceCheckCast(DexType type, ProgramMethod context);
+}
diff --git a/src/main/java/com/android/tools/r8/graph/analysis/EnqueuerInstanceOfAnalysis.java b/src/main/java/com/android/tools/r8/graph/analysis/EnqueuerInstanceOfAnalysis.java
new file mode 100644
index 0000000..abda4d1
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/graph/analysis/EnqueuerInstanceOfAnalysis.java
@@ -0,0 +1,12 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.graph.analysis;
+
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.ProgramMethod;
+
+public interface EnqueuerInstanceOfAnalysis {
+  void traceInstanceOf(DexType type, ProgramMethod context);
+}
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldAssignmentTracker.java b/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldAssignmentTracker.java
index 5b1dd91..0df3c80 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldAssignmentTracker.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldAssignmentTracker.java
@@ -202,8 +202,8 @@
   }
 
   private void recordAllFieldPutsProcessed(
-      DexEncodedField field, OptimizationFeedbackDelayed feedback) {
-    DexProgramClass clazz = asProgramClassOrNull(appView.definitionForHolder(field));
+      DexEncodedField field, ProgramMethod context, OptimizationFeedbackDelayed feedback) {
+    DexProgramClass clazz = asProgramClassOrNull(appView.definitionForHolder(field, context));
     if (clazz == null) {
       assert false;
       return;
@@ -282,7 +282,8 @@
     // therefore important that the optimization info has been flushed in advance.
     assert feedback.noUpdatesLeft();
     for (ProgramMethod method : wave) {
-      fieldAccessGraph.markProcessed(method, field -> recordAllFieldPutsProcessed(field, feedback));
+      fieldAccessGraph.markProcessed(
+          method, field -> recordAllFieldPutsProcessed(field, method, feedback));
       objectAllocationGraph.markProcessed(
           method, clazz -> recordAllAllocationsSitesProcessed(clazz, feedback));
     }
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/TrivialFieldAccessReprocessor.java b/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/TrivialFieldAccessReprocessor.java
index dd450d6..f06a1c7 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/TrivialFieldAccessReprocessor.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/TrivialFieldAccessReprocessor.java
@@ -241,5 +241,10 @@
     public boolean registerTypeReference(DexType type) {
       return false;
     }
+
+    @Override
+    public boolean registerInstanceOf(DexType type) {
+      return false;
+    }
   }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/StaticFieldValueAnalysis.java b/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/StaticFieldValueAnalysis.java
index 81df82a..3d8df1a 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/StaticFieldValueAnalysis.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/StaticFieldValueAnalysis.java
@@ -229,7 +229,14 @@
     initializationInfos.forEach(
         appView,
         (field, initializationInfo) -> {
-          if (!appView.appInfo().isInstanceFieldWrittenOnlyInInstanceInitializers(field)) {
+          // If the instance field is not written only in the instance initializer, then we can't
+          // conclude that this field will have a constant value.
+          //
+          // We have special handling for library fields that satisfy the property that they are
+          // only written in their corresponding instance initializers. This is needed since we
+          // don't analyze these instance initializers in the Enqueuer, as they are in the library.
+          if (!appView.appInfo().isInstanceFieldWrittenOnlyInInstanceInitializers(field)
+              && !appView.dexItemFactory().enumMembers.isNameOrOrdinalField(field.toReference())) {
             return;
           }
           if (initializationInfo.isArgumentInitializationInfo()) {
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/framework/intraprocedural/AbstractState.java b/src/main/java/com/android/tools/r8/ir/analysis/framework/intraprocedural/AbstractState.java
new file mode 100644
index 0000000..673411a
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/analysis/framework/intraprocedural/AbstractState.java
@@ -0,0 +1,25 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.analysis.framework.intraprocedural;
+
+import com.android.tools.r8.ir.code.BasicBlock;
+
+/** The abstract state of the dataflow analysis, which is computed for each {@link BasicBlock}. */
+public abstract class AbstractState<StateType extends AbstractState<StateType>>
+    implements TransferFunctionResult<StateType> {
+
+  public abstract StateType join(StateType state);
+
+  @Override
+  public abstract boolean equals(Object other);
+
+  @Override
+  public abstract int hashCode();
+
+  @Override
+  public boolean isAbstractState() {
+    return true;
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/framework/intraprocedural/DataflowAnalysisResult.java b/src/main/java/com/android/tools/r8/ir/analysis/framework/intraprocedural/DataflowAnalysisResult.java
new file mode 100644
index 0000000..68a8a24
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/analysis/framework/intraprocedural/DataflowAnalysisResult.java
@@ -0,0 +1,45 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.analysis.framework.intraprocedural;
+
+import com.android.tools.r8.ir.code.BasicBlock;
+
+/**
+ * The result returned by {@link IntraproceduralDataflowAnalysis#run(BasicBlock)}.
+ *
+ * <p>The result can be either a {@link SuccessfulDataflowAnalysisResult}, which represents the fact
+ * that the dataflow analysis ran to completion and that the (least) fixpoint was computed, or a
+ * {@link FailedDataflowAnalysisResult}, in which case the dataflow analysis was aborted before a
+ * fixpoint was reached.
+ *
+ * <p>The result currently does not hold any data about the result of the analysis, but this would
+ * be natural to add once needed.
+ */
+public abstract class DataflowAnalysisResult {
+
+  public boolean isSuccessfulAnalysisResult() {
+    return false;
+  }
+
+  public boolean isFailedAnalysisResult() {
+    return false;
+  }
+
+  public static class SuccessfulDataflowAnalysisResult extends DataflowAnalysisResult {
+
+    @Override
+    public boolean isSuccessfulAnalysisResult() {
+      return true;
+    }
+  }
+
+  public static class FailedDataflowAnalysisResult extends DataflowAnalysisResult {
+
+    @Override
+    public boolean isFailedAnalysisResult() {
+      return true;
+    }
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/framework/intraprocedural/FailedTransferFunctionResult.java b/src/main/java/com/android/tools/r8/ir/analysis/framework/intraprocedural/FailedTransferFunctionResult.java
new file mode 100644
index 0000000..4341265
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/analysis/framework/intraprocedural/FailedTransferFunctionResult.java
@@ -0,0 +1,24 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.analysis.framework.intraprocedural;
+
+import com.android.tools.r8.errors.Unreachable;
+
+/** Used by the {@link TransferFunction} to signal that the dataflow analysis should be aborted. */
+public class FailedTransferFunctionResult<StateType extends AbstractState<StateType>>
+    implements TransferFunctionResult<StateType> {
+
+  public FailedTransferFunctionResult() {}
+
+  @Override
+  public StateType asAbstractState() {
+    throw new Unreachable();
+  }
+
+  @Override
+  public boolean isFailedTransferResult() {
+    return true;
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/framework/intraprocedural/IntraproceduralDataflowAnalysis.java b/src/main/java/com/android/tools/r8/ir/analysis/framework/intraprocedural/IntraproceduralDataflowAnalysis.java
new file mode 100644
index 0000000..f290cf1
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/analysis/framework/intraprocedural/IntraproceduralDataflowAnalysis.java
@@ -0,0 +1,81 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.analysis.framework.intraprocedural;
+
+import com.android.tools.r8.ir.analysis.framework.intraprocedural.DataflowAnalysisResult.FailedDataflowAnalysisResult;
+import com.android.tools.r8.ir.analysis.framework.intraprocedural.DataflowAnalysisResult.SuccessfulDataflowAnalysisResult;
+import com.android.tools.r8.ir.code.BasicBlock;
+import com.android.tools.r8.ir.code.Instruction;
+import com.android.tools.r8.utils.WorkList;
+import java.util.IdentityHashMap;
+import java.util.Map;
+
+/**
+ * This defines a simple fixpoint solver for running an intraprocedural dataflow analysis.
+ *
+ * <p>The solver computes an {@link AbstractState} for each {@link BasicBlock} using the {@link
+ * TransferFunction} which defines the abstract semantics for each instruction.
+ *
+ * <p>Once the fixpoint is reached the analysis returns a {@link SuccessfulDataflowAnalysisResult}.
+ * If the supplied {@link TransferFunction} returns a {@link FailedTransferFunctionResult} for a
+ * given instruction and abstract state, then the analysis return a {@link
+ * FailedDataflowAnalysisResult}.
+ */
+public class IntraproceduralDataflowAnalysis<StateType extends AbstractState<StateType>> {
+
+  private final StateType bottom;
+
+  // The transfer function that defines the abstract semantics for each instruction.
+  private final TransferFunction<StateType> transfer;
+
+  // The state of the analysis.
+  private final Map<BasicBlock, StateType> blockExitStates = new IdentityHashMap<>();
+
+  public IntraproceduralDataflowAnalysis(StateType bottom, TransferFunction<StateType> transfer) {
+    this.bottom = bottom;
+    this.transfer = transfer;
+  }
+
+  public DataflowAnalysisResult run(BasicBlock root) {
+    return run(WorkList.newIdentityWorkList(root));
+  }
+
+  private DataflowAnalysisResult run(WorkList<BasicBlock> worklist) {
+    while (worklist.hasNext()) {
+      BasicBlock block = worklist.next();
+      // Compute the abstract state upon entry to the basic block, by joining all the predecessor
+      // exit states.
+      StateType state = computeBlockEntryState(block);
+      for (Instruction instruction : block.getInstructions()) {
+        TransferFunctionResult<StateType> transferResult = transfer.apply(instruction, state);
+        if (transferResult.isFailedTransferResult()) {
+          return new FailedDataflowAnalysisResult();
+        }
+        assert transferResult.isAbstractState();
+        state = transferResult.asAbstractState();
+      }
+      // Update the block exit state, and re-enqueue all successor blocks if the abstract state
+      // changed.
+      if (setBlockExitState(block, state)) {
+        worklist.addAll(block.getSuccessors());
+      }
+    }
+    return new SuccessfulDataflowAnalysisResult();
+  }
+
+  private StateType computeBlockEntryState(BasicBlock block) {
+    StateType result = bottom;
+    for (BasicBlock predecessor : block.getPredecessors()) {
+      StateType predecessorState = blockExitStates.getOrDefault(predecessor, bottom);
+      result = result.join(predecessorState);
+    }
+    return result;
+  }
+
+  private boolean setBlockExitState(BasicBlock block, StateType state) {
+    StateType previous = blockExitStates.put(block, state);
+    return !state.equals(previous);
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/framework/intraprocedural/TransferFunction.java b/src/main/java/com/android/tools/r8/ir/analysis/framework/intraprocedural/TransferFunction.java
new file mode 100644
index 0000000..98ede7c
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/analysis/framework/intraprocedural/TransferFunction.java
@@ -0,0 +1,16 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.analysis.framework.intraprocedural;
+
+import com.android.tools.r8.ir.code.Instruction;
+
+/**
+ * A transfer function that defines the abstract semantics of the instructions in the program
+ * according to some abstract state {@link StateType}.
+ */
+public interface TransferFunction<StateType extends AbstractState<StateType>> {
+
+  TransferFunctionResult<StateType> apply(Instruction instruction, StateType state);
+}
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/framework/intraprocedural/TransferFunctionResult.java b/src/main/java/com/android/tools/r8/ir/analysis/framework/intraprocedural/TransferFunctionResult.java
new file mode 100644
index 0000000..183d79a
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/analysis/framework/intraprocedural/TransferFunctionResult.java
@@ -0,0 +1,31 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.analysis.framework.intraprocedural;
+
+import com.android.tools.r8.ir.code.Instruction;
+
+/**
+ * The result of applying the {@link TransferFunction} to an {@link Instruction} and an {@link
+ * AbstractState}.
+ *
+ * <p>The result can either be a new {@link AbstractState} or a failure, in which case the dataflow
+ * analysis is aborted.
+ */
+public interface TransferFunctionResult<StateType extends AbstractState<StateType>> {
+
+  default boolean isAbstractState() {
+    return false;
+  }
+
+  StateType asAbstractState();
+
+  default boolean isFailedTransferResult() {
+    return false;
+  }
+
+  default FailedTransferFunctionResult<StateType> asFailedTransferResult() {
+    return null;
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/code/IRCode.java b/src/main/java/com/android/tools/r8/ir/code/IRCode.java
index 77988b8..476135f 100644
--- a/src/main/java/com/android/tools/r8/ir/code/IRCode.java
+++ b/src/main/java/com/android/tools/r8/ir/code/IRCode.java
@@ -107,8 +107,8 @@
   private final ProgramMethod method;
 
   public LinkedList<BasicBlock> blocks;
-  public final ValueNumberGenerator valueNumberGenerator;
-  public final ValueNumberGenerator basicBlockNumberGenerator;
+  public final NumberGenerator valueNumberGenerator;
+  public final NumberGenerator basicBlockNumberGenerator;
   private int usedMarkingColors = 0;
 
   private boolean numbered = false;
@@ -127,8 +127,8 @@
       InternalOptions options,
       ProgramMethod method,
       LinkedList<BasicBlock> blocks,
-      ValueNumberGenerator valueNumberGenerator,
-      ValueNumberGenerator basicBlockNumberGenerator,
+      NumberGenerator valueNumberGenerator,
+      NumberGenerator basicBlockNumberGenerator,
       IRMetadata metadata,
       Origin origin) {
     assert metadata != null;
diff --git a/src/main/java/com/android/tools/r8/ir/code/NewInstance.java b/src/main/java/com/android/tools/r8/ir/code/NewInstance.java
index 81af63c..3b8fc7f 100644
--- a/src/main/java/com/android/tools/r8/ir/code/NewInstance.java
+++ b/src/main/java/com/android/tools/r8/ir/code/NewInstance.java
@@ -189,7 +189,7 @@
             .resolveMethodOnClass(dexItemFactory.objectMembers.finalize, clazz);
     if (finalizeResolutionResult.isSingleResolution()) {
       DexMethod finalizeMethod = finalizeResolutionResult.getSingleTarget().method;
-      if (finalizeMethod != dexItemFactory.enumMethods.finalize
+      if (finalizeMethod != dexItemFactory.enumMembers.finalize
           && finalizeMethod != dexItemFactory.objectMembers.finalize) {
         return true;
       }
diff --git a/src/main/java/com/android/tools/r8/ir/code/ValueNumberGenerator.java b/src/main/java/com/android/tools/r8/ir/code/NumberGenerator.java
similarity index 91%
rename from src/main/java/com/android/tools/r8/ir/code/ValueNumberGenerator.java
rename to src/main/java/com/android/tools/r8/ir/code/NumberGenerator.java
index 722f30e..5b01dfa 100644
--- a/src/main/java/com/android/tools/r8/ir/code/ValueNumberGenerator.java
+++ b/src/main/java/com/android/tools/r8/ir/code/NumberGenerator.java
@@ -3,7 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.ir.code;
 
-public class ValueNumberGenerator {
+public class NumberGenerator {
   private int nextValueNumber = 0;
 
   public int next() {
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/CallGraphBuilderBase.java b/src/main/java/com/android/tools/r8/ir/conversion/CallGraphBuilderBase.java
index 8119a9c..07783f5 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/CallGraphBuilderBase.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/CallGraphBuilderBase.java
@@ -373,6 +373,11 @@
     }
 
     @Override
+    public boolean registerInstanceOf(DexType type) {
+      return false;
+    }
+
+    @Override
     public void registerCallSite(DexCallSite callSite) {
       registerMethodHandle(
           callSite.bootstrapMethod, MethodHandleUse.NOT_ARGUMENT_TO_LAMBDA_METAFACTORY);
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java b/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java
index c36eb8e..6b7b402 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java
@@ -92,6 +92,7 @@
 import com.android.tools.r8.ir.code.NewInstance;
 import com.android.tools.r8.ir.code.Not;
 import com.android.tools.r8.ir.code.NumberConversion;
+import com.android.tools.r8.ir.code.NumberGenerator;
 import com.android.tools.r8.ir.code.NumericType;
 import com.android.tools.r8.ir.code.Or;
 import com.android.tools.r8.ir.code.Phi;
@@ -107,7 +108,6 @@
 import com.android.tools.r8.ir.code.Throw;
 import com.android.tools.r8.ir.code.Ushr;
 import com.android.tools.r8.ir.code.Value;
-import com.android.tools.r8.ir.code.ValueNumberGenerator;
 import com.android.tools.r8.ir.code.ValueType;
 import com.android.tools.r8.ir.code.ValueTypeConstraint;
 import com.android.tools.r8.ir.code.Xor;
@@ -391,8 +391,8 @@
   private BasicBlock currentBlock = null;
   private int currentInstructionOffset = -1;
 
-  final private ValueNumberGenerator valueNumberGenerator;
-  private final ValueNumberGenerator basicBlockNumberGenerator;
+  private final NumberGenerator valueNumberGenerator;
+  private final NumberGenerator basicBlockNumberGenerator;
   private final ProgramMethod method;
   private ProgramMethod context;
   public final AppView<?> appView;
@@ -437,7 +437,7 @@
         source,
         origin,
         lookupPrototypeChanges(appView, method),
-        new ValueNumberGenerator());
+        new NumberGenerator());
   }
 
   public static IRBuilder createForInlining(
@@ -446,7 +446,7 @@
       SourceCode source,
       Origin origin,
       MethodProcessor processor,
-      ValueNumberGenerator valueNumberGenerator) {
+      NumberGenerator valueNumberGenerator) {
     RewrittenPrototypeDescription protoChanges =
         processor.shouldApplyCodeRewritings(method)
             ? lookupPrototypeChanges(appView, method)
@@ -475,7 +475,7 @@
       SourceCode source,
       Origin origin,
       RewrittenPrototypeDescription prototypeChanges,
-      ValueNumberGenerator valueNumberGenerator) {
+      NumberGenerator valueNumberGenerator) {
     assert source != null;
     assert valueNumberGenerator != null;
     this.method = method;
@@ -484,7 +484,7 @@
     this.origin = origin;
     this.prototypeChanges = prototypeChanges;
     this.valueNumberGenerator = valueNumberGenerator;
-    this.basicBlockNumberGenerator = new ValueNumberGenerator();
+    this.basicBlockNumberGenerator = new NumberGenerator();
   }
 
   public DexEncodedMethod getMethod() {
@@ -2673,7 +2673,7 @@
     return type != NumericType.FLOAT && type != NumericType.DOUBLE && type != NumericType.LONG;
   }
 
-  public ValueNumberGenerator getValueNumberGenerator() {
+  public NumberGenerator getValueNumberGenerator() {
     return valueNumberGenerator;
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
index 3f74b74..6cbf350 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
@@ -739,7 +739,11 @@
     }
     if (enumUnboxer != null) {
       enumUnboxer.finishAnalysis();
-      enumUnboxer.unboxEnums(postMethodProcessorBuilder, executorService, feedback);
+      enumUnboxer.unboxEnums(
+          postMethodProcessorBuilder,
+          executorService,
+          feedback,
+          classStaticizer == null ? Collections.emptySet() : classStaticizer.getCandidates());
     }
     if (!options.debug) {
       new TrivialFieldAccessReprocessor(appView.withLiveness(), postMethodProcessorBuilder)
@@ -1763,6 +1767,11 @@
         || definition.getOptimizationInfo().isReachabilitySensitive()) {
       return false;
     }
+    if (appView.options().enableEnumUnboxing && method.getHolder().isEnum()) {
+      // Although the method is pinned, we compute the inlining constraint for enum unboxing,
+      // but the inliner won't be able to inline the method (marked as pinned).
+      return true;
+    }
     if (appView.appInfo().hasLiveness()
         && appView.appInfo().withLiveness().isPinned(method.getReference())) {
       return false;
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/PostMethodProcessor.java b/src/main/java/com/android/tools/r8/ir/conversion/PostMethodProcessor.java
index bf8fd80..5b1485d 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/PostMethodProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/PostMethodProcessor.java
@@ -64,7 +64,7 @@
 
     private final Collection<CodeOptimization> defaultCodeOptimizations;
     private final LongLivedProgramMethodSetBuilder<?> methodsToReprocess =
-        LongLivedProgramMethodSetBuilder.create();
+        LongLivedProgramMethodSetBuilder.createForIdentitySet();
     private final Map<DexEncodedMethod, Collection<CodeOptimization>> optimizationsMap =
         new IdentityHashMap<>();
 
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/LambdaRewriter.java b/src/main/java/com/android/tools/r8/ir/desugar/LambdaRewriter.java
index 6e41b1a..3709176 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/LambdaRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/LambdaRewriter.java
@@ -347,9 +347,9 @@
     return knownLambdaClasses;
   }
 
-  public GraphLens buildMappingLens(AppView<?> appView) {
+  public NestedGraphLens buildMappingLens(AppView<?> appView) {
     if (originalMethodSignatures.isEmpty()) {
-      return appView.graphLens();
+      return null;
     }
     return new LambdaRewriterLens(
         originalMethodSignatures, appView.graphLens(), appView.dexItemFactory());
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/NestBasedAccessDesugaring.java b/src/main/java/com/android/tools/r8/ir/desugar/NestBasedAccessDesugaring.java
index bbe2ae0..c7f6f2c 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/NestBasedAccessDesugaring.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/NestBasedAccessDesugaring.java
@@ -194,10 +194,16 @@
         DexProgramClass::checksumFromType);
   }
 
+  void synthesizeNestConstructor() {
+    synthesizeNestConstructor(null);
+  }
+
   void synthesizeNestConstructor(DexApplication.Builder<?> builder) {
     if (nestConstructorUsed) {
       appView.appInfo().addSynthesizedClass(nestConstructor);
-      builder.addSynthesizedClass(nestConstructor, true);
+      if (builder != null) {
+        builder.addSynthesizedClass(nestConstructor, true);
+      }
     }
   }
 
@@ -455,6 +461,12 @@
       // Unrelated to access based control.
       return false;
     }
+
+    @Override
+    public boolean registerInstanceOf(DexType type) {
+      // Unrelated to access based control.
+      return false;
+    }
   }
 
   public static final class DexFieldWithAccess {
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/R8NestBasedAccessDesugaring.java b/src/main/java/com/android/tools/r8/ir/desugar/R8NestBasedAccessDesugaring.java
index e9ac116..d275f4a 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/R8NestBasedAccessDesugaring.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/R8NestBasedAccessDesugaring.java
@@ -5,7 +5,6 @@
 package com.android.tools.r8.ir.desugar;
 
 import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.DexApplication;
 import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexProgramClass;
@@ -33,16 +32,14 @@
     super(appView);
   }
 
-  public NestedPrivateMethodLens run(
-      ExecutorService executorService, DexApplication.Builder<?> appBuilder)
-      throws ExecutionException {
+  public NestedPrivateMethodLens run(ExecutorService executorService) throws ExecutionException {
     assert !appView.options().canUseNestBasedAccess()
         || appView.options().testing.enableForceNestBasedAccessDesugaringForTest;
     computeAndProcessNestsConcurrently(executorService);
     NestedPrivateMethodLens.Builder lensBuilder = NestedPrivateMethodLens.builder();
     addDeferredBridgesAndMapMethods(lensBuilder);
     clearNestAttributes();
-    synthesizeNestConstructor(appBuilder);
+    synthesizeNestConstructor();
     return lensBuilder.build(appView, getNestConstructorType());
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/Devirtualizer.java b/src/main/java/com/android/tools/r8/ir/optimize/Devirtualizer.java
index 6fe0808..a078c9b 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/Devirtualizer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/Devirtualizer.java
@@ -121,7 +121,7 @@
             InvokeSuper invoke = current.asInvokeSuper();
             DexEncodedMethod singleTarget = invoke.lookupSingleTarget(appView, context);
             if (singleTarget != null) {
-              DexClass holder = appView.definitionForHolder(singleTarget);
+              DexClass holder = appView.definitionForHolder(singleTarget, context);
               assert holder != null;
               DexMethod invokedMethod = invoke.getInvokedMethod();
               DexEncodedMethod newSingleTarget =
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInliner.java b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInliner.java
index e7104f3..f172fa9 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInliner.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInliner.java
@@ -253,8 +253,10 @@
         }
 
         // Inline the class instance.
+        Set<Value> affectedValues = Sets.newIdentityHashSet();
         try {
-          anyInlinedMethods |= processor.processInlining(code, defaultOracle, inliningIRProvider);
+          anyInlinedMethods |=
+              processor.processInlining(code, affectedValues, defaultOracle, inliningIRProvider);
         } catch (IllegalClassInlinerStateException e) {
           // We introduced a user that we cannot handle in the class inliner as a result of force
           // inlining. Abort gracefully from class inlining without removing the instance.
@@ -270,7 +272,6 @@
         assert inliningIRProvider.verifyIRCacheIsEmpty();
 
         // Restore normality.
-        Set<Value> affectedValues = Sets.newIdentityHashSet();
         code.removeAllDeadAndTrivialPhis(affectedValues);
         if (!affectedValues.isEmpty()) {
           new TypeAnalysis(appView).narrowing(affectedValues);
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java
index acd7477..ec1f830 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java
@@ -392,7 +392,10 @@
   //
   // Returns `true` if at least one method was inlined.
   boolean processInlining(
-      IRCode code, Supplier<InliningOracle> defaultOracle, InliningIRProvider inliningIRProvider)
+      IRCode code,
+      Set<Value> affectedValues,
+      Supplier<InliningOracle> defaultOracle,
+      InliningIRProvider inliningIRProvider)
       throws IllegalClassInlinerStateException {
     // Verify that `eligibleInstance` is not aliased.
     assert eligibleInstance == eligibleInstance.getAliasedValue();
@@ -417,7 +420,7 @@
     anyInlinedMethods |= forceInlineDirectMethodInvocations(code, inliningIRProvider);
     anyInlinedMethods |= forceInlineIndirectMethodInvocations(code, inliningIRProvider);
     removeAliasIntroducingInstructionsLinkedToEligibleInstance();
-    removeMiscUsages(code);
+    removeMiscUsages(code, affectedValues);
     removeFieldReads(code);
     removeFieldWrites();
     removeInstruction(root);
@@ -625,7 +628,7 @@
   }
 
   // Remove miscellaneous users before handling field reads.
-  private void removeMiscUsages(IRCode code) {
+  private void removeMiscUsages(IRCode code, Set<Value> affectedValues) {
     boolean needToRemoveUnreachableBlocks = false;
     for (Instruction user : eligibleInstance.uniqueUsers()) {
       if (user.isInvokeMethod()) {
@@ -702,7 +705,7 @@
     }
 
     if (needToRemoveUnreachableBlocks) {
-      code.removeUnreachableBlocks();
+      affectedValues.addAll(code.removeUnreachableBlocks());
     }
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxer.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxer.java
index c902868..a74b4b6 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxer.java
@@ -51,12 +51,14 @@
 import com.android.tools.r8.ir.conversion.IRConverter;
 import com.android.tools.r8.ir.conversion.PostMethodProcessor;
 import com.android.tools.r8.ir.conversion.PostOptimization;
+import com.android.tools.r8.ir.optimize.Inliner.Constraint;
 import com.android.tools.r8.ir.optimize.info.FieldOptimizationInfo;
 import com.android.tools.r8.ir.optimize.info.MethodOptimizationInfo;
 import com.android.tools.r8.ir.optimize.info.OptimizationFeedback.OptimizationInfoFixer;
 import com.android.tools.r8.ir.optimize.info.OptimizationFeedbackDelayed;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.utils.BooleanUtils;
+import com.android.tools.r8.utils.IntBox;
 import com.android.tools.r8.utils.Reporter;
 import com.android.tools.r8.utils.StringDiagnostic;
 import com.android.tools.r8.utils.collections.ProgramMethodSet;
@@ -67,6 +69,9 @@
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
 import java.util.IdentityHashMap;
 import java.util.List;
 import java.util.Map;
@@ -82,6 +87,7 @@
   // Map the enum candidates with their dependencies, i.e., the methods to reprocess for the given
   // enum if the optimization eventually decides to unbox it.
   private final Map<DexType, ProgramMethodSet> enumsUnboxingCandidates;
+  private final Set<DexType> enumsToUnboxWithPackageRequirement = Sets.newIdentityHashSet();
 
   private EnumUnboxingRewriter enumUnboxerRewriter;
 
@@ -266,7 +272,7 @@
     }
     for (Instruction user : constClass.outValue().uniqueUsers()) {
       if (!(user.isInvokeStatic()
-          && user.asInvokeStatic().getInvokedMethod() == factory.enumMethods.valueOf)) {
+          && user.asInvokeStatic().getInvokedMethod() == factory.enumMembers.valueOf)) {
         markEnumAsUnboxable(
             Reason.CONST_CLASS, appView.definitionForProgramType(constClass.getValue()));
         return;
@@ -327,7 +333,8 @@
   public void unboxEnums(
       PostMethodProcessor.Builder postBuilder,
       ExecutorService executorService,
-      OptimizationFeedbackDelayed feedback)
+      OptimizationFeedbackDelayed feedback,
+      Set<DexType> hostsToAvoidIfPossible)
       throws ExecutionException {
     // At this point the enumsToUnbox are no longer candidates, they will all be unboxed.
     if (enumsUnboxingCandidates.isEmpty()) {
@@ -337,12 +344,13 @@
     // Update keep info on any of the enum methods of the removed classes.
     updatePinnedItems(enumsToUnbox);
     enumUnboxerRewriter = new EnumUnboxingRewriter(appView, enumsToUnbox);
-    NestedGraphLens enumUnboxingLens = new TreeFixer(enumsToUnbox).fixupTypeReferences();
+    Map<DexType, DexType> newMethodLocation =
+        findNewMethodLocationOfUnboxableEnums(enumsToUnbox, hostsToAvoidIfPossible);
+    NestedGraphLens enumUnboxingLens =
+        new TreeFixer(enumsToUnbox).fixupTypeReferences(newMethodLocation);
     appView.setUnboxedEnums(enumUnboxerRewriter.getEnumsToUnbox());
     GraphLens previousLens = appView.graphLens();
-    appView.setGraphLens(enumUnboxingLens);
-    appView.setAppInfo(
-        appView.appInfo().rewrittenWithLens(appView.appInfo().app().asDirect(), enumUnboxingLens));
+    appView.rewriteWithLens(enumUnboxingLens);
     // Update optimization info.
     feedback.fixupOptimizationInfos(
         appView,
@@ -379,6 +387,84 @@
     postBuilder.rewrittenWithLens(appView, previousLens);
   }
 
+  // Some enums may have methods which require to stay in the current package for accessibility,
+  // in this case we find another class than the enum unboxing utility class to host these methods.
+  private Map<DexType, DexType> findNewMethodLocationOfUnboxableEnums(
+      Set<DexType> enumsToUnbox, Set<DexType> hostsToAvoidIfPossible) {
+    if (enumsToUnboxWithPackageRequirement.isEmpty()) {
+      return Collections.emptyMap();
+    }
+    Map<DexType, DexType> newMethodLocationMap = new IdentityHashMap<>();
+    Map<String, DexProgramClass> packageToClassMap =
+        getPackageToClassMapExcluding(enumsToUnbox, hostsToAvoidIfPossible);
+    for (DexType toUnbox : enumsToUnboxWithPackageRequirement) {
+      DexProgramClass packageClass = packageToClassMap.get(toUnbox.getPackageDescriptor());
+      if (packageClass != null) {
+        newMethodLocationMap.put(toUnbox, packageClass.type);
+      }
+    }
+    enumsToUnboxWithPackageRequirement.clear();
+    return newMethodLocationMap;
+  }
+
+  // We are looking for another class in the same package as the unboxed enum to host the unboxed
+  // enum methods. We go through all classes, and for each package where a host is needed, we
+  // select a class.
+  private Map<String, DexProgramClass> getPackageToClassMapExcluding(
+      Set<DexType> enumsToUnbox, Set<DexType> hostsToAvoidIfPossible) {
+    HashSet<String> relevantPackages = new HashSet<>();
+    for (DexType toUnbox : enumsToUnbox) {
+      relevantPackages.add(toUnbox.getPackageDescriptor());
+    }
+
+    Map<String, DexProgramClass> packageToClassMap = new HashMap<>();
+    for (DexProgramClass clazz : appView.appInfo().classes()) {
+      String packageDescriptor = clazz.type.getPackageDescriptor();
+      if (relevantPackages.contains(packageDescriptor) && !enumsToUnbox.contains(clazz.type)) {
+        DexProgramClass previousClass = packageToClassMap.get(packageDescriptor);
+        if (previousClass == null) {
+          packageToClassMap.put(packageDescriptor, clazz);
+        } else {
+          packageToClassMap.put(
+              packageDescriptor, selectHost(clazz, previousClass, hostsToAvoidIfPossible));
+        }
+      }
+    }
+
+    return packageToClassMap;
+  }
+
+  // We are trying to select a host for the enum unboxing methods, but multiple candidates are
+  // available. We need to pick one of the two classes and the result has to be deterministic.
+  // We follow the heuristics, in order:
+  //  1. don't pick a class from hostToAvoidIfPossible if possible
+  //  2. pick the class with the least number of methods
+  //  3. pick the first class name in alphabetical order for determinism.
+  private DexProgramClass selectHost(
+      DexProgramClass class1, DexProgramClass class2, Set<DexType> hostsToAvoidIfPossible) {
+    boolean avoid1 = hostsToAvoidIfPossible.contains(class1.type);
+    boolean avoid2 = hostsToAvoidIfPossible.contains(class2.type);
+    if (avoid1 && !avoid2) {
+      return class2;
+    }
+    if (avoid2 && !avoid1) {
+      return class1;
+    }
+    int size1 = class1.getMethodCollection().size();
+    int size2 = class2.getMethodCollection().size();
+    if (size1 < size2) {
+      return class1;
+    }
+    if (size2 < size1) {
+      return class2;
+    }
+    assert class1.type != class2.type;
+    if (class1.type.slowCompareTo(class2.type) < 0) {
+      return class1;
+    }
+    return class2;
+  }
+
   private void updatePinnedItems(Set<DexType> enumsToUnbox) {
     appView
         .appInfo()
@@ -395,13 +481,92 @@
   }
 
   public void finishAnalysis() {
+    analyzeInitializers();
+    analyzeAccessibility();
+    if (debugLogEnabled) {
+      reportEnumsAnalysis();
+    }
+  }
+
+  private void analyzeAccessibility() {
+    // Unboxing an enum will require to move its methods to a different class, which may impact
+    // accessibility. For a quick analysis we simply reuse the inliner analysis.
+    for (DexType toUnbox : enumsUnboxingCandidates.keySet()) {
+      DexProgramClass enumClass = appView.definitionFor(toUnbox).asProgramClass();
+      Constraint classConstraint = analyzeAccessibilityInClass(enumClass);
+      if (classConstraint == Constraint.NEVER) {
+        markEnumAsUnboxable(Reason.ACCESSIBILITY, enumClass);
+      } else if (classConstraint == Constraint.PACKAGE) {
+        enumsToUnboxWithPackageRequirement.add(toUnbox);
+      }
+    }
+  }
+
+  private Constraint analyzeAccessibilityInClass(DexProgramClass enumClass) {
+    Constraint classConstraint = Constraint.ALWAYS;
+    for (DexEncodedMethod method : enumClass.methods()) {
+      // Enum initializer are analyzed in analyzeInitializers instead.
+      if (!method.isInitializer()) {
+        Constraint methodConstraint = constraintForEnumUnboxing(method);
+        classConstraint = meet(classConstraint, methodConstraint);
+        if (classConstraint == Constraint.NEVER) {
+          return classConstraint;
+        }
+      }
+    }
+    return classConstraint;
+  }
+
+  private Constraint meet(Constraint constraint1, Constraint constraint2) {
+    if (constraint1 == Constraint.NEVER || constraint2 == Constraint.NEVER) {
+      return Constraint.NEVER;
+    }
+    if (constraint1 == Constraint.ALWAYS) {
+      return constraint2;
+    }
+    if (constraint2 == Constraint.ALWAYS) {
+      return constraint1;
+    }
+    assert constraint1 == Constraint.PACKAGE && constraint2 == Constraint.PACKAGE;
+    return Constraint.PACKAGE;
+  }
+
+  public Constraint constraintForEnumUnboxing(DexEncodedMethod method) {
+    // TODO(b/160939354): Use a UseRegistry instead of inlining constraints.
+    assert appView.definitionForProgramType(method.holder()) != null;
+    assert appView.definitionForProgramType(method.holder()).isEnum();
+    assert appView.definitionForProgramType(method.holder()).isEffectivelyFinal(appView);
+    assert appView.definitionForProgramType(method.holder()).superType
+        == appView.dexItemFactory().enumType;
+    assert !appView.definitionForProgramType(method.holder()).isInANest()
+        : "Unboxable enum is in a nest, this should not happen in cf to dex compilation, R8 needs"
+            + " to take care of nest access control when relocating unboxable enums methods";
+    switch (method.getCompilationState()) {
+      case PROCESSED_INLINING_CANDIDATE_ANY:
+        return Constraint.ALWAYS;
+      case PROCESSED_INLINING_CANDIDATE_SAME_NEST:
+        assert false;
+        // fall through
+      case PROCESSED_INLINING_CANDIDATE_SUBCLASS:
+        // There is no such thing as subclass access in this context, enums analyzed here have no
+        // subclasses, inherit directly from java.lang.Enum and are not analyzed if they call
+        // the protected methods in java.lang.Enum and java.lang.Object.
+      case PROCESSED_INLINING_CANDIDATE_SAME_CLASS:
+      case PROCESSED_INLINING_CANDIDATE_SAME_PACKAGE:
+        return Constraint.PACKAGE;
+      default:
+        return Constraint.NEVER;
+    }
+  }
+
+  private void analyzeInitializers() {
     for (DexType toUnbox : enumsUnboxingCandidates.keySet()) {
       DexProgramClass enumClass = appView.definitionForProgramType(toUnbox);
       assert enumClass != null;
 
       // Enum candidates have necessarily only one constructor matching enumMethods.constructor
       // signature.
-      DexEncodedMethod initializer = enumClass.lookupDirectMethod(factory.enumMethods.constructor);
+      DexEncodedMethod initializer = enumClass.lookupDirectMethod(factory.enumMembers.constructor);
       if (initializer == null) {
         // This case typically happens when a programmer uses EnumSet/EnumMap without using the
         // enum keep rules. The code is incorrect in this case (EnumSet/EnumMap won't work).
@@ -418,9 +583,6 @@
         markEnumAsUnboxable(Reason.INVALID_CLINIT, enumClass);
       }
     }
-    if (debugLogEnabled) {
-      reportEnumsAnalysis();
-    }
   }
 
   private Reason instructionAllowEnumUnboxing(
@@ -482,19 +644,19 @@
         return Reason.UNSUPPORTED_LIBRARY_CALL;
       }
       // TODO(b/147860220): EnumSet and EnumMap may be interesting to model.
-      if (singleTarget == factory.enumMethods.compareTo) {
+      if (singleTarget == factory.enumMembers.compareTo) {
         return Reason.ELIGIBLE;
-      } else if (singleTarget == factory.enumMethods.equals) {
+      } else if (singleTarget == factory.enumMembers.equals) {
         return Reason.ELIGIBLE;
-      } else if (singleTarget == factory.enumMethods.name) {
+      } else if (singleTarget == factory.enumMembers.nameMethod) {
         return Reason.ELIGIBLE;
-      } else if (singleTarget == factory.enumMethods.toString) {
+      } else if (singleTarget == factory.enumMembers.toString) {
         return Reason.ELIGIBLE;
-      } else if (singleTarget == factory.enumMethods.ordinal) {
+      } else if (singleTarget == factory.enumMembers.ordinalMethod) {
         return Reason.ELIGIBLE;
-      } else if (singleTarget == factory.enumMethods.hashCode) {
+      } else if (singleTarget == factory.enumMembers.hashCode) {
         return Reason.ELIGIBLE;
-      } else if (singleTarget == factory.enumMethods.constructor) {
+      } else if (singleTarget == factory.enumMembers.constructor) {
         // Enum constructor call is allowed only if first call of an enum initializer.
         if (code.method().isInstanceInitializer()
             && code.method().holder() == enumClass.type
@@ -675,6 +837,7 @@
 
   public enum Reason {
     ELIGIBLE,
+    ACCESSIBILITY,
     ANNOTATION,
     PINNED,
     DOWN_CAST,
@@ -710,7 +873,8 @@
 
   private class TreeFixer {
 
-    private final List<DexEncodedMethod> unboxedEnumsMethods = new ArrayList<>();
+    private final Map<DexType, List<DexEncodedMethod>> unboxedEnumsMethods =
+        new IdentityHashMap<>();
     private final EnumUnboxingLens.Builder lensBuilder = EnumUnboxingLens.builder();
     private final Set<DexType> enumsToUnbox;
 
@@ -718,13 +882,13 @@
       this.enumsToUnbox = enumsToUnbox;
     }
 
-    private NestedGraphLens fixupTypeReferences() {
+    private NestedGraphLens fixupTypeReferences(Map<DexType, DexType> newMethodLocation) {
       assert enumUnboxerRewriter != null;
       // Fix all methods and fields using enums to unbox.
       for (DexProgramClass clazz : appView.appInfo().classes()) {
         if (enumsToUnbox.contains(clazz.type)) {
           assert clazz.instanceFields().size() == 0;
-          // Clear the initializers and move the static methods to the utility class.
+          // Clear the initializers and move the static methods to the new location.
           Set<DexEncodedMethod> methodsToRemove = Sets.newIdentityHashSet();
           clazz
               .methods()
@@ -733,14 +897,22 @@
                     if (m.isInitializer()) {
                       clearEnumToUnboxMethod(m);
                     } else {
-                      unboxedEnumsMethods.add(
-                          fixupEncodedMethodToUtility(m, factory.enumUnboxingUtilityType));
+                      DexType newHolder =
+                          newMethodLocation.getOrDefault(
+                              clazz.type, factory.enumUnboxingUtilityType);
+                      List<DexEncodedMethod> movedMethods =
+                          unboxedEnumsMethods.computeIfAbsent(newHolder, k -> new ArrayList<>());
+                      movedMethods.add(fixupEncodedMethodToUtility(m, newHolder));
                       methodsToRemove.add(m);
                     }
                   });
           clazz.getMethodCollection().removeMethods(methodsToRemove);
         } else {
-          clazz.getMethodCollection().replaceMethods(this::fixupEncodedMethod);
+          IntBox index = new IntBox(0);
+          clazz
+              .getMethodCollection()
+              .replaceMethods(
+                  encodedMethod -> fixupEncodedMethod(encodedMethod, index.getAndIncrement()));
           fixupFields(clazz.staticFields(), clazz::setStaticField);
           fixupFields(clazz.instanceFields(), clazz::setInstanceField);
         }
@@ -751,7 +923,11 @@
       DexProgramClass utilityClass =
           appView.definitionForProgramType(factory.enumUnboxingUtilityType);
       assert utilityClass != null : "Should have been synthesized upfront";
-      utilityClass.addDirectMethods(unboxedEnumsMethods);
+      unboxedEnumsMethods.forEach(
+          (newHolderType, movedMethods) -> {
+            DexProgramClass newHolderClass = appView.definitionFor(newHolderType).asProgramClass();
+            newHolderClass.addDirectMethods(movedMethods);
+          });
       return lensBuilder.build(factory, appView.graphLens(), enumsToUnbox);
     }
 
@@ -777,6 +953,7 @@
       DexProto proto =
           encodedMethod.isStatic() ? method.proto : factory.prependHolderToProto(method);
       DexMethod newMethod = factory.createMethod(newHolder, fixupProto(proto), newMethodName);
+      assert appView.definitionFor(encodedMethod.holder()).lookupMethod(newMethod) == null;
       lensBuilder.move(method, encodedMethod.isStatic(), newMethod, true);
       encodedMethod.accessFlags.promoteToPublic();
       encodedMethod.accessFlags.promoteToStatic();
@@ -785,9 +962,17 @@
       return encodedMethod.toTypeSubstitutedMethod(newMethod);
     }
 
-    private DexEncodedMethod fixupEncodedMethod(DexEncodedMethod encodedMethod) {
-      DexMethod newMethod = fixupMethod(encodedMethod.method);
-      if (newMethod != encodedMethod.method) {
+    private DexEncodedMethod fixupEncodedMethod(DexEncodedMethod encodedMethod, int index) {
+      DexProto newProto = fixupProto(encodedMethod.proto());
+      if (newProto != encodedMethod.proto()) {
+        DexString newMethodName =
+            factory.createString(
+                EnumUnboxingRewriter.ENUM_UNBOXING_UTILITY_METHOD_PREFIX
+                    + index
+                    + "$"
+                    + encodedMethod.getName().toString());
+        DexMethod newMethod = factory.createMethod(encodedMethod.holder(), newProto, newMethodName);
+        assert appView.definitionFor(encodedMethod.holder()).lookupMethod(newMethod) == null;
         boolean isStatic = encodedMethod.isStatic();
         lensBuilder.move(encodedMethod.method, isStatic, newMethod, isStatic);
         return encodedMethod.toTypeSubstitutedMethod(newMethod);
@@ -817,14 +1002,6 @@
       }
     }
 
-    private DexMethod fixupMethod(DexMethod method) {
-      return fixupMethod(method, method.holder, method.name);
-    }
-
-    private DexMethod fixupMethod(DexMethod method, DexType newHolder, DexString newMethodName) {
-      return factory.createMethod(newHolder, fixupProto(method.proto), newMethodName);
-    }
-
     private DexProto fixupProto(DexProto proto) {
       DexType returnType = fixupType(proto.returnType);
       DexType[] arguments = fixupTypes(proto.parameters.values);
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingCandidateAnalysis.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingCandidateAnalysis.java
index 08ba8b4..c4a6bb4 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingCandidateAnalysis.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingCandidateAnalysis.java
@@ -83,7 +83,7 @@
 
   private boolean isStandardEnumInitializer(DexEncodedMethod method) {
     return method.isInstanceInitializer()
-        && method.method.proto == factory.enumMethods.constructor.proto;
+        && method.method.proto == factory.enumMembers.constructor.proto;
   }
 
   // The enum should have the $VALUES static field and only fields directly referencing the enum
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingRewriter.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingRewriter.java
index 1832a2a..087e808 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingRewriter.java
@@ -129,21 +129,21 @@
         DexMethod invokedMethod = invokeMethod.getInvokedMethod();
         DexType enumType = getEnumTypeOrNull(invokeMethod.getReceiver(), convertedEnums);
         if (enumType != null) {
-          if (invokedMethod == factory.enumMethods.ordinal
-              || invokedMethod == factory.enumMethods.hashCode) {
+          if (invokedMethod == factory.enumMembers.ordinalMethod
+              || invokedMethod == factory.enumMembers.hashCode) {
             replaceEnumInvoke(
                 iterator, invokeMethod, ordinalUtilityMethod, m -> synthesizeOrdinalMethod());
             continue;
-          } else if (invokedMethod == factory.enumMethods.equals) {
+          } else if (invokedMethod == factory.enumMembers.equals) {
             replaceEnumInvoke(
                 iterator, invokeMethod, equalsUtilityMethod, m -> synthesizeEqualsMethod());
             continue;
-          } else if (invokedMethod == factory.enumMethods.compareTo) {
+          } else if (invokedMethod == factory.enumMembers.compareTo) {
             replaceEnumInvoke(
                 iterator, invokeMethod, compareToUtilityMethod, m -> synthesizeCompareToMethod());
             continue;
-          } else if (invokedMethod == factory.enumMethods.name
-              || invokedMethod == factory.enumMethods.toString) {
+          } else if (invokedMethod == factory.enumMembers.nameMethod
+              || invokedMethod == factory.enumMembers.toString) {
             DexMethod toStringMethod = computeDefaultToStringUtilityMethod(enumType);
             iterator.replaceCurrentInstruction(
                 new InvokeStatic(
@@ -155,7 +155,7 @@
       } else if (instruction.isInvokeStatic()) {
         InvokeStatic invokeStatic = instruction.asInvokeStatic();
         DexMethod invokedMethod = invokeStatic.getInvokedMethod();
-        if (invokedMethod == factory.enumMethods.valueOf
+        if (invokedMethod == factory.enumMembers.valueOf
             && invokeStatic.inValues().get(0).isConstClass()) {
           DexType enumType =
               invokeStatic.inValues().get(0).getConstInstruction().asConstClass().getValue();
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumValueOptimizer.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumValueOptimizer.java
index 789f4d9..ebe815d 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumValueOptimizer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumValueOptimizer.java
@@ -8,6 +8,7 @@
 
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexEncodedField;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.graph.DexItemFactory;
@@ -18,6 +19,10 @@
 import com.android.tools.r8.graph.EnumValueInfoMapCollection.EnumValueInfoMap;
 import com.android.tools.r8.ir.analysis.type.TypeAnalysis;
 import com.android.tools.r8.ir.analysis.type.TypeElement;
+import com.android.tools.r8.ir.analysis.value.AbstractValue;
+import com.android.tools.r8.ir.analysis.value.ObjectState;
+import com.android.tools.r8.ir.analysis.value.SingleNumberValue;
+import com.android.tools.r8.ir.analysis.value.SingleStringValue;
 import com.android.tools.r8.ir.code.ArrayGet;
 import com.android.tools.r8.ir.code.BasicBlock;
 import com.android.tools.r8.ir.code.BasicBlock.ThrowingInfo;
@@ -72,15 +77,9 @@
       if (current.isInvokeMethodWithReceiver()) {
         InvokeMethodWithReceiver methodWithReceiver = current.asInvokeMethodWithReceiver();
         DexMethod invokedMethod = methodWithReceiver.getInvokedMethod();
-        boolean isOrdinalInvoke = invokedMethod == factory.enumMethods.ordinal;
-        boolean isNameInvoke = invokedMethod == factory.enumMethods.name;
-        boolean isToStringInvoke = invokedMethod == factory.enumMethods.toString;
-
-        // TODO(b/160667929): Re-enable name()/toString() optimizations.
-        if (!isOrdinalInvoke) {
-          continue;
-        }
-
+        boolean isOrdinalInvoke = invokedMethod == factory.enumMembers.ordinalMethod;
+        boolean isNameInvoke = invokedMethod == factory.enumMembers.nameMethod;
+        boolean isToStringInvoke = invokedMethod == factory.enumMembers.toString;
         if (!isOrdinalInvoke && !isNameInvoke && !isToStringInvoke) {
           continue;
         }
@@ -89,57 +88,98 @@
         if (receiver.isPhi()) {
           continue;
         }
-        Instruction definition = receiver.getDefinition();
-        if (!definition.isStaticGet()) {
-          continue;
-        }
-        DexField enumField = definition.asStaticGet().getField();
-        EnumValueInfoMap valueInfoMap =
-            appView.appInfo().withLiveness().getEnumValueInfoMap(enumField.type);
-        if (valueInfoMap == null) {
+
+        StaticGet staticGet = receiver.getDefinition().asStaticGet();
+        if (staticGet == null) {
           continue;
         }
 
-        // The receiver value is identified as being from a constant enum field lookup by the fact
-        // that it is a static-get to a field whose type is the same as the enclosing class (which
-        // is known to be an enum type). An enum may still define a static field using the enum type
-        // so ensure the field is present in the ordinal map for final validation.
-        EnumValueInfo valueInfo = valueInfoMap.getEnumValueInfo(enumField);
-        if (valueInfo == null) {
+        DexField field = staticGet.getField();
+        DexEncodedField definition = field.lookupOnClass(appView.definitionForHolder(field));
+        if (definition == null) {
+          continue;
+        }
+
+        AbstractValue abstractValue = definition.getOptimizationInfo().getAbstractValue();
+        if (!abstractValue.isSingleFieldValue()) {
+          continue;
+        }
+
+        ObjectState objectState = abstractValue.asSingleFieldValue().getState();
+        if (objectState.isEmpty()) {
           continue;
         }
 
         Value outValue = methodWithReceiver.outValue();
         if (isOrdinalInvoke) {
-          iterator.replaceCurrentInstruction(new ConstNumber(outValue, valueInfo.ordinal));
-        } else if (isNameInvoke) {
-          Value newValue =
-              code.createValue(TypeElement.stringClassType(appView, definitelyNotNull()));
-          iterator.replaceCurrentInstruction(
-              new ConstString(
-                  newValue, enumField.name, ThrowingInfo.defaultForConstString(appView.options())));
-          newValue.addAffectedValuesTo(affectedValues);
-        } else {
-          assert isToStringInvoke;
-          DexClass enumClazz = appView.appInfo().definitionFor(enumField.type);
-          if (!enumClazz.accessFlags.isFinal()) {
-            continue;
+          DexField ordinalField = appView.dexItemFactory().enumMembers.ordinalField;
+          DexEncodedField ordinalDefinition =
+              ordinalField.lookupOnClass(appView.definitionForHolder(ordinalField));
+          if (ordinalDefinition != null) {
+            SingleNumberValue ordinalValue =
+                objectState.getAbstractFieldValue(ordinalDefinition).asSingleNumberValue();
+            if (ordinalValue != null) {
+              iterator.replaceCurrentInstruction(
+                  new ConstNumber(outValue, ordinalValue.getValue()));
+            }
           }
-          DexEncodedMethod singleTarget =
-              appView
-                  .appInfo()
-                  .resolveMethodOnClass(factory.objectMembers.toString, valueInfo.type)
-                  .getSingleTarget();
-          if (singleTarget != null && singleTarget.method != factory.enumMethods.toString) {
-            continue;
-          }
-          Value newValue =
-              code.createValue(TypeElement.stringClassType(appView, definitelyNotNull()));
-          iterator.replaceCurrentInstruction(
-              new ConstString(
-                  newValue, enumField.name, ThrowingInfo.defaultForConstString(appView.options())));
-          newValue.addAffectedValuesTo(affectedValues);
+          continue;
         }
+
+        DexField nameField = appView.dexItemFactory().enumMembers.nameField;
+        DexEncodedField nameDefinition =
+            nameField.lookupOnClass(appView.definitionForHolder(nameField));
+        if (nameField == null) {
+          continue;
+        }
+
+        SingleStringValue nameValue =
+            objectState.getAbstractFieldValue(nameDefinition).asSingleStringValue();
+        if (nameValue == null) {
+          continue;
+        }
+
+        if (isNameInvoke) {
+          Value newValue =
+              code.createValue(TypeElement.stringClassType(appView, definitelyNotNull()));
+          iterator.replaceCurrentInstruction(
+              new ConstString(
+                  newValue,
+                  nameValue.getDexString(),
+                  ThrowingInfo.defaultForConstString(appView.options())));
+          newValue.addAffectedValuesTo(affectedValues);
+          continue;
+        }
+
+        assert isToStringInvoke;
+
+        DexClass enumClazz = appView.appInfo().definitionFor(field.type);
+        if (!enumClazz.isFinal()) {
+          continue;
+        }
+
+        EnumValueInfo valueInfo = appView.appInfo().withLiveness().getEnumValueInfo(field);
+        if (valueInfo == null) {
+          continue;
+        }
+
+        DexEncodedMethod singleTarget =
+            appView
+                .appInfo()
+                .resolveMethodOnClass(factory.objectMembers.toString, valueInfo.type)
+                .getSingleTarget();
+        if (singleTarget != null && singleTarget.method != factory.enumMembers.toString) {
+          continue;
+        }
+
+        Value newValue =
+            code.createValue(TypeElement.stringClassType(appView, definitelyNotNull()));
+        iterator.replaceCurrentInstruction(
+            new ConstString(
+                newValue,
+                nameValue.getDexString(),
+                ThrowingInfo.defaultForConstString(appView.options())));
+        newValue.addAffectedValuesTo(affectedValues);
       } else if (current.isArrayLength()) {
         // Rewrites MyEnum.values().length to a constant int.
         Instruction arrayDefinition = current.asArrayLength().array().getAliasedValue().definition;
@@ -148,7 +188,7 @@
           DexProgramClass enumClass = appView.definitionForProgramType(invokedMethod.holder);
           if (enumClass != null
               && enumClass.isEnum()
-              && factory.enumMethods.isValuesMethod(invokedMethod, enumClass)) {
+              && factory.enumMembers.isValuesMethod(invokedMethod, enumClass)) {
             EnumValueInfoMap enumValueInfoMap =
                 appView.appInfo().withLiveness().getEnumValueInfoMap(invokedMethod.holder);
             if (enumValueInfoMap != null) {
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 5542908..b189a57 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
@@ -563,7 +563,7 @@
                   return null;
                 }
                 // java.lang.Enum.<init>() and java.lang.Object.<init>() are considered trivial.
-                if (invokedMethod == dexItemFactory.enumMethods.constructor
+                if (invokedMethod == dexItemFactory.enumMembers.constructor
                     || invokedMethod == dexItemFactory.objectMembers.constructor) {
                   builder.setParent(invokedMethod);
                   break;
@@ -1098,7 +1098,7 @@
             .resolveMethodOnClass(appView.dexItemFactory().objectMembers.finalize, clazz);
     DexEncodedMethod target = resolutionResult.getSingleTarget();
     return target != null
-        && target.method != dexItemFactory.enumMethods.finalize
+        && target.method != dexItemFactory.enumMembers.finalize
         && target.method != dexItemFactory.objectMembers.finalize;
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackSimple.java b/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackSimple.java
index 822e0c5..62edebf 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackSimple.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackSimple.java
@@ -162,7 +162,7 @@
   @Override
   public void setInstanceInitializerInfo(
       DexEncodedMethod method, InstanceInitializerInfo instanceInitializerInfo) {
-    // Ignored.
+    method.getMutableOptimizationInfo().setInstanceInitializerInfo(instanceInitializerInfo);
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/field/InstanceFieldInitializationInfoCollection.java b/src/main/java/com/android/tools/r8/ir/optimize/info/field/InstanceFieldInitializationInfoCollection.java
index d26de6d..5979031 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/field/InstanceFieldInitializationInfoCollection.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/field/InstanceFieldInitializationInfoCollection.java
@@ -47,9 +47,10 @@
       recordInitializationInfo(field.field, info);
     }
 
-    public void recordInitializationInfo(DexField field, InstanceFieldInitializationInfo info) {
+    public Builder recordInitializationInfo(DexField field, InstanceFieldInitializationInfo info) {
       assert !infos.containsKey(field);
       infos.put(field, info);
+      return this;
     }
 
     public InstanceFieldInitializationInfoCollection build() {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/inliner/InliningIRProvider.java b/src/main/java/com/android/tools/r8/ir/optimize/inliner/InliningIRProvider.java
index 85cb117..baf75b5 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/inliner/InliningIRProvider.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/inliner/InliningIRProvider.java
@@ -8,8 +8,8 @@
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.InvokeMethod;
+import com.android.tools.r8.ir.code.NumberGenerator;
 import com.android.tools.r8.ir.code.Position;
-import com.android.tools.r8.ir.code.ValueNumberGenerator;
 import com.android.tools.r8.ir.conversion.MethodProcessor;
 import com.android.tools.r8.origin.Origin;
 import java.util.IdentityHashMap;
@@ -19,7 +19,7 @@
 
   private final AppView<?> appView;
   private final ProgramMethod context;
-  private final ValueNumberGenerator valueNumberGenerator;
+  private final NumberGenerator valueNumberGenerator;
   private final MethodProcessor methodProcessor;
 
   private final Map<InvokeMethod, IRCode> cache = new IdentityHashMap<>();
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/lambda/LambdaMerger.java b/src/main/java/com/android/tools/r8/ir/optimize/lambda/LambdaMerger.java
index 4cba255..a716bd4 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/lambda/LambdaMerger.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/lambda/LambdaMerger.java
@@ -196,7 +196,7 @@
   // from it. In this case we will reprocess method that does not need patching, but it
   // should not be happening very frequently and we ignore possible overhead.
   private final LongLivedProgramMethodSetBuilder<SortedProgramMethodSet> methodsToReprocess =
-      LongLivedProgramMethodSetBuilder.createSorted();
+      LongLivedProgramMethodSetBuilder.createForSortedSet();
 
   private final AppView<AppInfoWithLiveness> appView;
   private final Kotlin kotlin;
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/library/EnumMethodOptimizer.java b/src/main/java/com/android/tools/r8/ir/optimize/library/EnumMethodOptimizer.java
index cdc1793..658fcb5 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/library/EnumMethodOptimizer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/library/EnumMethodOptimizer.java
@@ -38,7 +38,7 @@
       InvokeMethod invoke,
       DexEncodedMethod singleTarget,
       Set<Value> affectedValues) {
-    if (singleTarget.method == appView.dexItemFactory().enumMethods.valueOf
+    if (singleTarget.method == appView.dexItemFactory().enumMembers.valueOf
         && invoke.inValues().get(0).isConstClass()) {
       insertAssumeDynamicType(code, instructionIterator, invoke);
     }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/library/LibraryFieldSynthesis.java b/src/main/java/com/android/tools/r8/ir/optimize/library/LibraryFieldSynthesis.java
new file mode 100644
index 0000000..3f3277f
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/library/LibraryFieldSynthesis.java
@@ -0,0 +1,47 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.optimize.library;
+
+import static com.android.tools.r8.graph.DexLibraryClass.asLibraryClassOrNull;
+
+import com.android.tools.r8.dex.Constants;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexAnnotationSet;
+import com.android.tools.r8.graph.DexEncodedField;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexLibraryClass;
+import com.android.tools.r8.graph.FieldAccessFlags;
+
+/**
+ * This class synthesizes library fields that we rely on for modeling.
+ *
+ * <p>For example, we synthesize the field `java.lang.String java.lang.Enum.name` if it is not
+ * present. We use this to model that the constructor `void java.lang.Enum.<init>(java.lang.String,
+ * int)` initializes `java.lang.String java.lang.Enum.name` to the first argument of the
+ * constructor.
+ */
+public class LibraryFieldSynthesis {
+
+  public static void synthesizeEnumFields(AppView<?> appView) {
+    DexItemFactory dexItemFactory = appView.dexItemFactory();
+    DexLibraryClass enumClass =
+        asLibraryClassOrNull(appView.definitionFor(dexItemFactory.enumType));
+    if (enumClass != null) {
+      dexItemFactory.enumMembers.forEachField(
+          field -> {
+            DexEncodedField definition = enumClass.lookupField(field);
+            if (definition == null) {
+              enumClass.appendInstanceField(
+                  new DexEncodedField(
+                      field,
+                      FieldAccessFlags.fromCfAccessFlags(
+                          Constants.ACC_PRIVATE | Constants.ACC_FINAL),
+                      DexAnnotationSet.empty(),
+                      null));
+            }
+          });
+    }
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/library/LibraryOptimizationInfoInitializer.java b/src/main/java/com/android/tools/r8/ir/optimize/library/LibraryOptimizationInfoInitializer.java
index e7e0fe6..d0412b7 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/library/LibraryOptimizationInfoInitializer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/library/LibraryOptimizationInfoInitializer.java
@@ -11,12 +11,16 @@
 import com.android.tools.r8.graph.DexEncodedField;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexItemFactory.EnumMembers;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.analysis.value.AbstractValueFactory;
 import com.android.tools.r8.ir.analysis.value.ObjectState;
 import com.android.tools.r8.ir.optimize.info.LibraryOptimizationInfoInitializerFeedback;
+import com.android.tools.r8.ir.optimize.info.field.InstanceFieldInitializationInfoCollection;
+import com.android.tools.r8.ir.optimize.info.field.InstanceFieldInitializationInfoFactory;
+import com.android.tools.r8.ir.optimize.info.initializer.NonTrivialInstanceInitializerInfo;
 import com.google.common.collect.Sets;
 import java.util.BitSet;
 import java.util.Set;
@@ -38,6 +42,7 @@
   }
 
   void run(Set<DexEncodedField> finalLibraryFields) {
+    modelInstanceInitializers();
     modelStaticFinalLibraryFields(finalLibraryFields);
     modelLibraryMethodsReturningNonNull();
     modelLibraryMethodsReturningReceiver();
@@ -48,6 +53,28 @@
     return modeledLibraryTypes;
   }
 
+  private void modelInstanceInitializers() {
+    EnumMembers enumMembers = dexItemFactory.enumMembers;
+    DexEncodedMethod enumConstructor = lookupMethod(enumMembers.constructor);
+    if (enumConstructor != null) {
+      LibraryFieldSynthesis.synthesizeEnumFields(appView);
+      InstanceFieldInitializationInfoFactory factory =
+          appView.instanceFieldInitializationInfoFactory();
+      InstanceFieldInitializationInfoCollection fieldInitializationInfos =
+          InstanceFieldInitializationInfoCollection.builder()
+              .recordInitializationInfo(
+                  enumMembers.nameField, factory.createArgumentInitializationInfo(1))
+              .recordInitializationInfo(
+                  enumMembers.ordinalField, factory.createArgumentInitializationInfo(2))
+              .build();
+      feedback.setInstanceInitializerInfo(
+          enumConstructor,
+          NonTrivialInstanceInitializerInfo.builder(fieldInitializationInfos)
+              .setParent(dexItemFactory.objectMembers.constructor)
+              .build());
+    }
+  }
+
   private void modelStaticFinalLibraryFields(Set<DexEncodedField> finalLibraryFields) {
     for (DexEncodedField field : finalLibraryFields) {
       if (field.isStatic()) {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/staticizer/ClassStaticizer.java b/src/main/java/com/android/tools/r8/ir/optimize/staticizer/ClassStaticizer.java
index 323dd11..c4466cd 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/staticizer/ClassStaticizer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/staticizer/ClassStaticizer.java
@@ -113,6 +113,11 @@
     this.converter = converter;
   }
 
+  // This set is an approximation and can be used only for heuristics.
+  public final Set<DexType> getCandidates() {
+    return candidates.keySet();
+  }
+
   // Before doing any usage-based analysis we collect a set of classes that can be
   // candidates for staticizing. This analysis is very simple, but minimizes the
   // set of eligible classes staticizer tracks and thus time and memory it needs.
@@ -274,8 +279,8 @@
       if (instruction.isNewInstance()) {
         // Check the class being initialized against valid staticizing candidates.
         NewInstance newInstance = instruction.asNewInstance();
-        CandidateInfo candidateInfo = processInstantiation(context, iterator, newInstance);
-        if (candidateInfo != null) {
+        CandidateInfo info = processInstantiation(context, iterator, newInstance);
+        if (info != null) {
           alreadyProcessed.addAll(newInstance.outValue().aliasedUsers());
           // For host class initializers having eligible instantiation we also want to
           // ensure that the rest of the initializer consist of code w/o side effects.
@@ -283,15 +288,13 @@
           // effects, otherwise we can still staticize, but cannot remove singleton reads.
           while (iterator.hasNext()) {
             if (!isAllowedInHostClassInitializer(context.getHolderType(), iterator.next(), code)) {
-              candidateInfo.preserveRead.set(true);
+              info.preserveRead.set(true);
               iterator.previous();
               break;
             }
             // Ignore just read instruction.
           }
-          referencedFrom
-              .computeIfAbsent(candidateInfo, ignore -> LongLivedProgramMethodSetBuilder.create())
-              .add(context);
+          addReferencedFrom(info, context);
         }
         continue;
       }
@@ -311,9 +314,7 @@
         // Check the field being read: make sure all usages are valid.
         CandidateInfo info = processStaticFieldRead(instruction.asStaticGet());
         if (info != null) {
-          referencedFrom
-              .computeIfAbsent(info, ignore -> LongLivedProgramMethodSetBuilder.create())
-              .add(context);
+          addReferencedFrom(info, context);
           // If the candidate is still valid, ignore all usages in further analysis.
           Value value = instruction.outValue();
           if (value != null) {
@@ -327,9 +328,7 @@
         // Check if it is a static singleton getter.
         CandidateInfo info = processInvokeStatic(instruction.asInvokeStatic());
         if (info != null) {
-          referencedFrom
-              .computeIfAbsent(info, ignore -> LongLivedProgramMethodSetBuilder.create())
-              .add(context);
+          addReferencedFrom(info, context);
           // If the candidate is still valid, ignore all usages in further analysis.
           Value value = instruction.outValue();
           if (value != null) {
@@ -368,6 +367,13 @@
     }
   }
 
+  private void addReferencedFrom(CandidateInfo info, ProgramMethod context) {
+    LongLivedProgramMethodSetBuilder<?> builder =
+        referencedFrom.computeIfAbsent(
+            info, ignore -> LongLivedProgramMethodSetBuilder.createConcurrentForIdentitySet());
+    builder.add(context);
+  }
+
   private boolean isAllowedInHostClassInitializer(
       DexType host, Instruction insn, IRCode code) {
     return (insn.isStaticPut() && insn.asStaticPut().getField().holder == host) ||
@@ -761,5 +767,10 @@
       }
       return true;
     }
+
+    @Override
+    public boolean registerInstanceOf(DexType type) {
+      return registerTypeReference(type);
+    }
   }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/string/StringBuilderAppendFlowAnalysis.java b/src/main/java/com/android/tools/r8/ir/optimize/string/StringBuilderAppendFlowAnalysis.java
new file mode 100644
index 0000000..0cafbcd
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/string/StringBuilderAppendFlowAnalysis.java
@@ -0,0 +1,172 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.optimize.string;
+
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.ir.analysis.framework.intraprocedural.AbstractState;
+import com.android.tools.r8.ir.analysis.framework.intraprocedural.DataflowAnalysisResult;
+import com.android.tools.r8.ir.analysis.framework.intraprocedural.FailedTransferFunctionResult;
+import com.android.tools.r8.ir.analysis.framework.intraprocedural.IntraproceduralDataflowAnalysis;
+import com.android.tools.r8.ir.analysis.framework.intraprocedural.TransferFunction;
+import com.android.tools.r8.ir.analysis.framework.intraprocedural.TransferFunctionResult;
+import com.android.tools.r8.ir.code.BasicBlock;
+import com.android.tools.r8.ir.code.Instruction;
+import com.android.tools.r8.ir.code.InvokeMethod;
+import com.android.tools.r8.ir.code.InvokeVirtual;
+import com.android.tools.r8.ir.code.Value;
+import com.android.tools.r8.utils.SetUtils;
+import com.google.common.collect.Sets;
+import java.util.Set;
+
+/**
+ * This defines a simple program analysis that determines if there is a path from a call to append()
+ * on a StringBuilder back to itself in the control flow graph.
+ *
+ * <p>The analysis explicitly allows paths from a call to append() back to itself that go through a
+ * call to toString() on the builder. This ensures that we can still optimize builders that are
+ * fully enclosed in a loop: <code>
+ *   while (true) {
+ *     System.out.println(new StringBuilder().append("42").toString());
+ *   }
+ * </code>
+ */
+class StringBuilderAppendFlowAnalysis {
+
+  /**
+   * Returns true if there is a call to {@code append()} on {@param builder}, which is inside a
+   * loop.
+   */
+  static boolean hasAppendInstructionInLoop(
+      Value builder, StringBuilderOptimizationConfiguration configuration) {
+    IntraproceduralDataflowAnalysis<AbstractStateImpl> analysis =
+        new IntraproceduralDataflowAnalysis<>(
+            AbstractStateImpl.bottom(), new TransferFunctionImpl(builder, configuration));
+    DataflowAnalysisResult result = analysis.run(builder.definition.getBlock());
+    return result.isFailedAnalysisResult();
+  }
+
+  /** This defines the state that we keep track of for each {@link BasicBlock}. */
+  private static class AbstractStateImpl extends AbstractState<AbstractStateImpl> {
+
+    private static final AbstractStateImpl BOTTOM = new AbstractStateImpl();
+
+    // The set of invoke instructions that call append(), which is on a path to the current program
+    // point.
+    private final Set<InvokeVirtual> liveAppendInstructions;
+
+    private AbstractStateImpl() {
+      this(Sets.newIdentityHashSet());
+    }
+
+    private AbstractStateImpl(Set<InvokeVirtual> liveAppendInstructions) {
+      this.liveAppendInstructions = liveAppendInstructions;
+    }
+
+    public static AbstractStateImpl bottom() {
+      return BOTTOM;
+    }
+
+    private AbstractStateImpl addLiveAppendInstruction(InvokeVirtual invoke) {
+      Set<InvokeVirtual> newLiveAppendInstructions =
+          SetUtils.newIdentityHashSet(liveAppendInstructions);
+      newLiveAppendInstructions.add(invoke);
+      return new AbstractStateImpl(newLiveAppendInstructions);
+    }
+
+    private boolean isAppendInstructionLive(InvokeVirtual invoke) {
+      return liveAppendInstructions.contains(invoke);
+    }
+
+    @Override
+    public AbstractStateImpl asAbstractState() {
+      return this;
+    }
+
+    @Override
+    public AbstractStateImpl join(AbstractStateImpl state) {
+      if (liveAppendInstructions.isEmpty()) {
+        return state;
+      }
+      if (state.liveAppendInstructions.isEmpty()) {
+        return this;
+      }
+      Set<InvokeVirtual> newLiveAppendInstructions =
+          SetUtils.newIdentityHashSet(liveAppendInstructions, state.liveAppendInstructions);
+      return new AbstractStateImpl(newLiveAppendInstructions);
+    }
+
+    @Override
+    public boolean equals(Object other) {
+      if (other == null || getClass() != other.getClass()) {
+        return false;
+      }
+      AbstractStateImpl state = (AbstractStateImpl) other;
+      return liveAppendInstructions.equals(state.liveAppendInstructions);
+    }
+
+    @Override
+    public int hashCode() {
+      return liveAppendInstructions.hashCode();
+    }
+  }
+
+  /**
+   * This defines the transfer function for the analysis.
+   *
+   * <p>If a call to {@code append()} on the builder is seen, then that invoke instruction is added
+   * to the abstract state.
+   *
+   * <p>If a call to {@code toString()} on the builder i seen, then the abstract state is reset to
+   * bottom.
+   */
+  private static class TransferFunctionImpl implements TransferFunction<AbstractStateImpl> {
+
+    private final Value builder;
+    private final StringBuilderOptimizationConfiguration configuration;
+
+    private TransferFunctionImpl(
+        Value builder, StringBuilderOptimizationConfiguration configuration) {
+      this.builder = builder;
+      this.configuration = configuration;
+    }
+
+    @Override
+    public TransferFunctionResult<AbstractStateImpl> apply(
+        Instruction instruction, AbstractStateImpl state) {
+      if (instruction.isInvokeMethod()) {
+        return apply(state, instruction.asInvokeMethod());
+      }
+      return state;
+    }
+
+    private TransferFunctionResult<AbstractStateImpl> apply(
+        AbstractStateImpl state, InvokeMethod invoke) {
+      if (isAppendOnBuilder(invoke)) {
+        assert invoke.isInvokeVirtual();
+        InvokeVirtual appendInvoke = invoke.asInvokeVirtual();
+        if (state.isAppendInstructionLive(appendInvoke)) {
+          return new FailedTransferFunctionResult<>();
+        }
+        return state.addLiveAppendInstruction(appendInvoke);
+      }
+      if (isToStringOnBuilder(invoke)) {
+        return AbstractStateImpl.bottom();
+      }
+      return state;
+    }
+
+    private boolean isAppendOnBuilder(InvokeMethod invoke) {
+      DexMethod invokedMethod = invoke.getInvokedMethod();
+      return configuration.isAppendMethod(invokedMethod)
+          && invoke.getArgument(0).getAliasedValue() == builder;
+    }
+
+    private boolean isToStringOnBuilder(InvokeMethod invoke) {
+      DexMethod invokedMethod = invoke.getInvokedMethod();
+      return configuration.isToStringMethod(invokedMethod)
+          && invoke.getArgument(0).getAliasedValue() == builder;
+    }
+  }
+}
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 a01be35..cd9fe4e 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
@@ -608,7 +608,7 @@
         assert perInstrState != null;
         BuilderState builderState = perInstrState.get(instr);
         assert builderState != null;
-        String element = toCompileTimeString(builderState);
+        String element = toCompileTimeString(builder, builderState);
         assert element != null;
         Value stringValue =
             code.createValue(
@@ -653,7 +653,7 @@
       if (builderState == null) {
         return false;
       }
-      String element = toCompileTimeString(builderState);
+      String element = toCompileTimeString(builder, builderState);
       if (element == null) {
         return false;
       }
@@ -663,7 +663,7 @@
     // Find the trivial chain of builder-append*-toString.
     // Note that we can't determine a compile-time constant string if there are any ambiguity.
     @VisibleForTesting
-    String toCompileTimeString(BuilderState state) {
+    String toCompileTimeString(Value builder, BuilderState state) {
       boolean continuedForLogging = false;
       LinkedList<String> contents = new LinkedList<>();
       while (state != null) {
@@ -710,6 +710,10 @@
       if (contents.isEmpty()) {
         return null;
       }
+      if (StringBuilderAppendFlowAnalysis.hasAppendInstructionInLoop(
+          builder, optimizationConfiguration)) {
+        return null;
+      }
       String result = StringUtils.join(contents, "");
       int estimate = estimateSizeReduction(contents);
       return estimate > result.length() ? result : null;
diff --git a/src/main/java/com/android/tools/r8/ir/synthetic/AbstractSynthesizedCode.java b/src/main/java/com/android/tools/r8/ir/synthetic/AbstractSynthesizedCode.java
index 9402fbb..da4daec 100644
--- a/src/main/java/com/android/tools/r8/ir/synthetic/AbstractSynthesizedCode.java
+++ b/src/main/java/com/android/tools/r8/ir/synthetic/AbstractSynthesizedCode.java
@@ -13,8 +13,8 @@
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.graph.UseRegistry;
 import com.android.tools.r8.ir.code.IRCode;
+import com.android.tools.r8.ir.code.NumberGenerator;
 import com.android.tools.r8.ir.code.Position;
-import com.android.tools.r8.ir.code.ValueNumberGenerator;
 import com.android.tools.r8.ir.conversion.IRBuilder;
 import com.android.tools.r8.ir.conversion.MethodProcessor;
 import com.android.tools.r8.ir.conversion.SourceCode;
@@ -48,7 +48,7 @@
       ProgramMethod context,
       ProgramMethod method,
       AppView<?> appView,
-      ValueNumberGenerator valueNumberGenerator,
+      NumberGenerator valueNumberGenerator,
       Position callerPosition,
       Origin origin,
       MethodProcessor methodProcessor) {
diff --git a/src/main/java/com/android/tools/r8/ir/synthetic/EnumUnboxingCfCodeProvider.java b/src/main/java/com/android/tools/r8/ir/synthetic/EnumUnboxingCfCodeProvider.java
index 5e9d63f..5a50ca4 100644
--- a/src/main/java/com/android/tools/r8/ir/synthetic/EnumUnboxingCfCodeProvider.java
+++ b/src/main/java/com/android/tools/r8/ir/synthetic/EnumUnboxingCfCodeProvider.java
@@ -68,6 +68,8 @@
             instructions.add(new CfLoad(ValueType.fromDexType(factory.intType), 0));
             instructions.add(new CfConstNumber(enumValueInfo.convertToInt(), ValueType.INT));
             instructions.add(new CfIfCmp(If.Type.NE, ValueType.INT, dest));
+            // TODO(b/160939354): Should use the value passed to the enum constructor, since this
+            //  value may be different from the enum field name.
             instructions.add(new CfConstString(field.name));
             instructions.add(new CfReturn(ValueType.OBJECT));
             instructions.add(dest);
@@ -123,6 +125,8 @@
           (field, enumValueInfo) -> {
             CfLabel dest = new CfLabel();
             instructions.add(new CfLoad(ValueType.fromDexType(factory.stringType), 0));
+            // TODO(b/160939354): Should use the value passed to the enum constructor, since this
+            //  value may be different from the enum field name.
             instructions.add(new CfConstString(field.name));
             instructions.add(
                 new CfInvoke(Opcodes.INVOKEVIRTUAL, factory.stringMembers.equals, false));
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 24947bf..00e689c 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinClassInfo.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinClassInfo.java
@@ -48,6 +48,7 @@
   private final KotlinTypeReference anonymousObjectOrigin;
   private final String packageName;
   private final KotlinLocalDelegatedPropertyInfo localDelegatedProperties;
+  private final int[] metadataVersion;
 
   private KotlinClassInfo(
       int flags,
@@ -63,7 +64,8 @@
       KotlinVersionRequirementInfo versionRequirements,
       KotlinTypeReference anonymousObjectOrigin,
       String packageName,
-      KotlinLocalDelegatedPropertyInfo localDelegatedProperties) {
+      KotlinLocalDelegatedPropertyInfo localDelegatedProperties,
+      int[] metadataVersion) {
     this.flags = flags;
     this.name = name;
     this.moduleName = moduleName;
@@ -78,11 +80,13 @@
     this.anonymousObjectOrigin = anonymousObjectOrigin;
     this.packageName = packageName;
     this.localDelegatedProperties = localDelegatedProperties;
+    this.metadataVersion = metadataVersion;
   }
 
   public static KotlinClassInfo create(
       KmClass kmClass,
       String packageName,
+      int[] metadataVersion,
       DexClass hostClass,
       DexItemFactory factory,
       Reporter reporter,
@@ -129,7 +133,8 @@
         getAnonymousObjectOrigin(kmClass, factory),
         packageName,
         KotlinLocalDelegatedPropertyInfo.create(
-            JvmExtensionsKt.getLocalDelegatedProperties(kmClass), factory, reporter));
+            JvmExtensionsKt.getLocalDelegatedProperties(kmClass), factory, reporter),
+        metadataVersion);
   }
 
   private static KotlinTypeReference getAnonymousObjectOrigin(
@@ -293,6 +298,11 @@
   }
 
   @Override
+  public int[] getMetadataVersion() {
+    return metadataVersion;
+  }
+
+  @Override
   public void trace(DexDefinitionSupplier definitionSupplier) {
     forEachApply(constructorsWithNoBacking, constructor -> constructor::trace, definitionSupplier);
     declarationContainerInfo.trace(definitionSupplier);
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 fdb326e..1d968d2 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinClassLevelInfo.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinClassLevelInfo.java
@@ -59,4 +59,6 @@
   KotlinClassHeader rewrite(DexClass clazz, AppView<?> appView, NamingLens namingLens);
 
   String getPackageName();
+
+  int[] getMetadataVersion();
 }
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 281950c..45cbd27 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinClassMetadataReader.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinClassMetadataReader.java
@@ -112,10 +112,12 @@
       Reporter reporter,
       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(),
           packageName,
+          metadataVersion,
           clazz,
           factory,
           reporter,
@@ -125,6 +127,7 @@
       return KotlinFileFacadeInfo.create(
           (KotlinClassMetadata.FileFacade) kMetadata,
           packageName,
+          metadataVersion,
           clazz,
           factory,
           reporter,
@@ -132,12 +135,16 @@
     } else if (kMetadata instanceof KotlinClassMetadata.MultiFileClassFacade) {
       // multi-file class with the same @JvmName.
       return KotlinMultiFileClassFacadeInfo.create(
-          (KotlinClassMetadata.MultiFileClassFacade) kMetadata, packageName, factory);
+          (KotlinClassMetadata.MultiFileClassFacade) kMetadata,
+          packageName,
+          metadataVersion,
+          factory);
     } else if (kMetadata instanceof KotlinClassMetadata.MultiFileClassPart) {
       // A single file, which is part of multi-file class.
       return KotlinMultiFileClassPartInfo.create(
           (KotlinClassMetadata.MultiFileClassPart) kMetadata,
           packageName,
+          metadataVersion,
           clazz,
           factory,
           reporter,
@@ -146,6 +153,7 @@
       return KotlinSyntheticClassInfo.create(
           (KotlinClassMetadata.SyntheticClass) kMetadata,
           packageName,
+          metadataVersion,
           clazz,
           kotlin,
           factory,
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 886281f..b012758 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinFileFacadeInfo.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinFileFacadeInfo.java
@@ -22,15 +22,19 @@
 
   private final KotlinPackageInfo packageInfo;
   private final String packageName;
+  private final int[] metadataVersion;
 
-  private KotlinFileFacadeInfo(KotlinPackageInfo packageInfo, String packageName) {
+  private KotlinFileFacadeInfo(
+      KotlinPackageInfo packageInfo, String packageName, int[] metadataVersion) {
     this.packageInfo = packageInfo;
     this.packageName = packageName;
+    this.metadataVersion = metadataVersion;
   }
 
   public static KotlinFileFacadeInfo create(
       FileFacade kmFileFacade,
       String packageName,
+      int[] metadataVersion,
       DexClass clazz,
       DexItemFactory factory,
       Reporter reporter,
@@ -38,7 +42,8 @@
     return new KotlinFileFacadeInfo(
         KotlinPackageInfo.create(
             kmFileFacade.toKmPackage(), clazz, factory, reporter, keepByteCode),
-        packageName);
+        packageName,
+        metadataVersion);
   }
 
   @Override
@@ -66,6 +71,11 @@
   }
 
   @Override
+  public int[] getMetadataVersion() {
+    return metadataVersion;
+  }
+
+  @Override
   public void trace(DexDefinitionSupplier definitionSupplier) {
     packageInfo.trace(definitionSupplier);
   }
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 680bd5c..5327873 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataEnqueuerExtension.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataEnqueuerExtension.java
@@ -8,7 +8,6 @@
 import static com.android.tools.r8.kotlin.KotlinMetadataUtils.NO_KOTLIN_INFO;
 import static com.android.tools.r8.utils.FunctionUtils.forEachApply;
 
-import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexDefinitionSupplier;
@@ -132,11 +131,6 @@
     }
 
     @Override
-    public DexProgramClass definitionForProgramType(DexType type) {
-      throw new Unreachable("Should not be called");
-    }
-
-    @Override
     public DexItemFactory dexItemFactory() {
       return baseSupplier.dexItemFactory();
     }
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 5e11d43..ace8717 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataRewriter.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataRewriter.java
@@ -27,6 +27,11 @@
 
 public class KotlinMetadataRewriter {
 
+  // Due to a bug with nested classes and the lookup of RequirementVersion, we bump all metadata
+  // versions to 1.4 if compiled with kotlin 1.3 (1.1.16). For more information, see b/161885097 for
+  // more information.
+  private static final int[] METADATA_VERSION_1_4 = new int[] {1, 4, 0};
+
   private static final class WriteMetadataFieldInfo {
     final boolean writeKind;
     final boolean writeMetadataVersion;
@@ -111,7 +116,10 @@
             KotlinClassHeader kotlinClassHeader = kotlinInfo.rewrite(clazz, appView, lens);
             DexAnnotation newMeta =
                 createKotlinMetadataAnnotation(
-                    kotlinClassHeader, kotlinInfo.getPackageName(), writeMetadataFieldInfo);
+                    kotlinClassHeader,
+                    kotlinInfo.getPackageName(),
+                    getMaxVersion(METADATA_VERSION_1_4, kotlinInfo.getMetadataVersion()),
+                    writeMetadataFieldInfo);
             clazz.setAnnotations(
                 clazz.annotations().rewrite(anno -> anno == oldMeta ? newMeta : anno));
           } catch (Throwable t) {
@@ -136,12 +144,15 @@
   }
 
   private DexAnnotation createKotlinMetadataAnnotation(
-      KotlinClassHeader header, String packageName, WriteMetadataFieldInfo writeMetadataFieldInfo) {
+      KotlinClassHeader header,
+      String packageName,
+      int[] metadataVersion,
+      WriteMetadataFieldInfo writeMetadataFieldInfo) {
     List<DexAnnotationElement> elements = new ArrayList<>();
     if (writeMetadataFieldInfo.writeMetadataVersion) {
       elements.add(
           new DexAnnotationElement(
-              kotlin.metadata.metadataVersion, createIntArray(header.getMetadataVersion())));
+              kotlin.metadata.metadataVersion, createIntArray(metadataVersion)));
     }
     if (writeMetadataFieldInfo.writeByteCodeVersion) {
       elements.add(
@@ -197,4 +208,23 @@
     }
     return new DexValueArray(values);
   }
+
+  // We are not sure that the format is <Major>-<Minor>-<Patch>, the format can be: <Major>-<Minor>.
+  private int[] getMaxVersion(int[] one, int[] other) {
+    assert one.length == 2 || one.length == 3;
+    assert other.length == 2 || other.length == 3;
+    if (one[0] != other[0]) {
+      return one[0] > other[0] ? one : other;
+    }
+    if (one[1] != other[1]) {
+      return one[1] > other[1] ? one : other;
+    }
+    int patchOne = one.length >= 3 ? one[2] : 0;
+    int patchOther = other.length >= 3 ? other[2] : 0;
+    if (patchOne != patchOther) {
+      return patchOne > patchOther ? one : other;
+    }
+    // They are equal up to patch, just return one.
+    return one;
+  }
 }
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 6ef7a0d..fad37c6 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataUtils.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataUtils.java
@@ -58,6 +58,11 @@
     }
 
     @Override
+    public int[] getMetadataVersion() {
+      throw new Unreachable("Should never be called");
+    }
+
+    @Override
     public boolean isNoKotlinInformation() {
       return true;
     }
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 34c5248..ec31d79 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinMultiFileClassFacadeInfo.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinMultiFileClassFacadeInfo.java
@@ -23,20 +23,25 @@
 
   private final List<KotlinTypeReference> partClassNames;
   private final String packageName;
+  private final int[] metadataVersion;
 
   private KotlinMultiFileClassFacadeInfo(
-      List<KotlinTypeReference> partClassNames, String packageName) {
+      List<KotlinTypeReference> partClassNames, String packageName, int[] metadataVersion) {
     this.partClassNames = partClassNames;
     this.packageName = packageName;
+    this.metadataVersion = metadataVersion;
   }
 
   static KotlinMultiFileClassFacadeInfo create(
-      MultiFileClassFacade kmMultiFileClassFacade, String packageName, DexItemFactory factory) {
+      MultiFileClassFacade kmMultiFileClassFacade,
+      String packageName,
+      int[] metadataVersion,
+      DexItemFactory factory) {
     ImmutableList.Builder<KotlinTypeReference> builder = ImmutableList.builder();
     for (String partClassName : kmMultiFileClassFacade.getPartClassNames()) {
       builder.add(KotlinTypeReference.fromBinaryName(partClassName, factory));
     }
-    return new KotlinMultiFileClassFacadeInfo(builder.build(), packageName);
+    return new KotlinMultiFileClassFacadeInfo(builder.build(), packageName, metadataVersion);
   }
 
   @Override
@@ -69,6 +74,11 @@
   }
 
   @Override
+  public int[] getMetadataVersion() {
+    return metadataVersion;
+  }
+
+  @Override
   public void trace(DexDefinitionSupplier definitionSupplier) {
     forEachApply(partClassNames, type -> type::trace, definitionSupplier);
   }
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 ca97d0b..6b99d15 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinMultiFileClassPartInfo.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinMultiFileClassPartInfo.java
@@ -24,17 +24,23 @@
   private final String facadeClassName;
   private final KotlinPackageInfo packageInfo;
   private final String packageName;
+  private final int[] metadataVersion;
 
   private KotlinMultiFileClassPartInfo(
-      String facadeClassName, KotlinPackageInfo packageInfo, String packageName) {
+      String facadeClassName,
+      KotlinPackageInfo packageInfo,
+      String packageName,
+      int[] metadataVersion) {
     this.facadeClassName = facadeClassName;
     this.packageInfo = packageInfo;
     this.packageName = packageName;
+    this.metadataVersion = metadataVersion;
   }
 
   static KotlinMultiFileClassPartInfo create(
       MultiFileClassPart classPart,
       String packageName,
+      int[] metadataVersion,
       DexClass clazz,
       DexItemFactory factory,
       Reporter reporter,
@@ -42,7 +48,8 @@
     return new KotlinMultiFileClassPartInfo(
         classPart.getFacadeClassName(),
         KotlinPackageInfo.create(classPart.toKmPackage(), clazz, factory, reporter, keepByteCode),
-        packageName);
+        packageName,
+        metadataVersion);
   }
 
   @Override
@@ -71,6 +78,11 @@
   }
 
   @Override
+  public int[] getMetadataVersion() {
+    return metadataVersion;
+  }
+
+  @Override
   public void trace(DexDefinitionSupplier definitionSupplier) {
     packageInfo.trace(definitionSupplier);
   }
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 79054f4..41147a5 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinSyntheticClassInfo.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinSyntheticClassInfo.java
@@ -21,6 +21,7 @@
 
   private final KotlinLambdaInfo lambda;
   private final String packageName;
+  private final int[] metadataVersion;
 
   public enum Flavour {
     KotlinStyleLambda,
@@ -30,15 +31,18 @@
 
   private final Flavour flavour;
 
-  private KotlinSyntheticClassInfo(KotlinLambdaInfo lambda, Flavour flavour, String packageName) {
+  private KotlinSyntheticClassInfo(
+      KotlinLambdaInfo lambda, Flavour flavour, String packageName, int[] metadataVersion) {
     this.lambda = lambda;
     this.flavour = flavour;
     this.packageName = packageName;
+    this.metadataVersion = metadataVersion;
   }
 
   static KotlinSyntheticClassInfo create(
       SyntheticClass syntheticClass,
       String packageName,
+      int[] metadataVersion,
       DexClass clazz,
       Kotlin kotlin,
       DexItemFactory factory,
@@ -51,7 +55,8 @@
     return new KotlinSyntheticClassInfo(
         lambda != null ? KotlinLambdaInfo.create(clazz, lambda, factory, reporter) : null,
         getFlavour(syntheticClass, clazz, kotlin),
-        packageName);
+        packageName,
+        metadataVersion);
   }
 
   public boolean isLambda() {
@@ -100,6 +105,11 @@
     return packageName;
   }
 
+  @Override
+  public int[] getMetadataVersion() {
+    return metadataVersion;
+  }
+
   private static Flavour getFlavour(
       KotlinClassMetadata.SyntheticClass metadata, DexClass clazz, Kotlin kotlin) {
     // Returns KotlinStyleLambda if the given clazz is a Kotlin-style lambda:
diff --git a/src/main/java/com/android/tools/r8/optimize/BridgeHoisting.java b/src/main/java/com/android/tools/r8/optimize/BridgeHoisting.java
index 6e0168f..c19bcd2 100644
--- a/src/main/java/com/android/tools/r8/optimize/BridgeHoisting.java
+++ b/src/main/java/com/android/tools/r8/optimize/BridgeHoisting.java
@@ -87,10 +87,7 @@
         .visit(appView.appInfo().classes(), clazz -> processClass(clazz, subtypingInfo));
     if (!lensBuilder.isEmpty()) {
       BridgeHoistingLens lens = lensBuilder.build(appView);
-      boolean changed = appView.setGraphLens(lens);
-      assert changed;
-      appView.setAppInfo(
-          appView.appInfo().rewrittenWithLens(appView.appInfo().app().asDirect(), lens));
+      appView.rewriteWithLens(lens);
     }
   }
 
diff --git a/src/main/java/com/android/tools/r8/optimize/InvokeSingleTargetExtractor.java b/src/main/java/com/android/tools/r8/optimize/InvokeSingleTargetExtractor.java
index 292f616..37df60c 100644
--- a/src/main/java/com/android/tools/r8/optimize/InvokeSingleTargetExtractor.java
+++ b/src/main/java/com/android/tools/r8/optimize/InvokeSingleTargetExtractor.java
@@ -102,6 +102,11 @@
     return invalid();
   }
 
+  @Override
+  public boolean registerInstanceOf(DexType type) {
+    return invalid();
+  }
+
   public enum InvokeKind {
     VIRTUAL,
     STATIC,
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 0689227..70f4a81 100644
--- a/src/main/java/com/android/tools/r8/retrace/Retrace.java
+++ b/src/main/java/com/android/tools/r8/retrace/Retrace.java
@@ -9,6 +9,7 @@
 import com.android.tools.r8.Diagnostic;
 import com.android.tools.r8.DiagnosticsHandler;
 import com.android.tools.r8.Keep;
+import com.android.tools.r8.Version;
 import com.android.tools.r8.naming.ClassNameMapper;
 import com.android.tools.r8.retrace.RetraceCommand.Builder;
 import com.android.tools.r8.retrace.RetraceCommand.ProguardMapProducer;
@@ -62,6 +63,10 @@
       if (help != null) {
         return null;
       }
+      Boolean version = OptionsParsing.tryParseBoolean(context, "--version");
+      if (version != null) {
+        return null;
+      }
       Boolean info = OptionsParsing.tryParseBoolean(context, "--info");
       if (info != null) {
         // This is already set in the diagnostics handler.
@@ -195,7 +200,11 @@
         new RetraceDiagnosticsHandler(new DiagnosticsHandler() {}, printInfo);
     Builder builder = parseArguments(mappedArgs, retraceDiagnosticsHandler);
     if (builder == null) {
-      // --help was an argument to list
+      // --help or --version was an argument to list
+      if (Arrays.asList(mappedArgs).contains("--version")) {
+        System.out.println("Retrace " + Version.getVersionString());
+        return;
+      }
       assert Arrays.asList(mappedArgs).contains("--help");
       System.out.print(USAGE_MESSAGE);
       return;
diff --git a/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java b/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
index 5f68d46..3197121 100644
--- a/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
+++ b/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
@@ -22,6 +22,7 @@
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.DirectMappedDexApplication;
 import com.android.tools.r8.graph.EnumValueInfoMapCollection;
+import com.android.tools.r8.graph.EnumValueInfoMapCollection.EnumValueInfo;
 import com.android.tools.r8.graph.EnumValueInfoMapCollection.EnumValueInfoMap;
 import com.android.tools.r8.graph.FieldAccessInfo;
 import com.android.tools.r8.graph.FieldAccessInfoCollection;
@@ -747,6 +748,12 @@
     return enumValueInfoMaps.getEnumValueInfoMap(enumType);
   }
 
+  public EnumValueInfo getEnumValueInfo(DexField field) {
+    assert checkIfObsolete();
+    EnumValueInfoMap map = enumValueInfoMaps.getEnumValueInfoMap(field.type);
+    return map != null ? map.getEnumValueInfo(field) : null;
+  }
+
   public Int2ReferenceMap<DexField> getSwitchMap(DexField field) {
     assert checkIfObsolete();
     return switchMaps.get(field);
@@ -1356,7 +1363,7 @@
   }
 
   public SubtypingInfo computeSubtypingInfo() {
-    return new SubtypingInfo(app().asDirect().allClasses(), this);
+    return new SubtypingInfo(this);
   }
 
   public boolean mayHaveFinalizeMethodDirectlyOrIndirectly(ClassTypeElement type) {
diff --git a/src/main/java/com/android/tools/r8/shaking/DefaultEnqueuerUseRegistry.java b/src/main/java/com/android/tools/r8/shaking/DefaultEnqueuerUseRegistry.java
index 7c0afe9..9a488d1 100644
--- a/src/main/java/com/android/tools/r8/shaking/DefaultEnqueuerUseRegistry.java
+++ b/src/main/java/com/android/tools/r8/shaking/DefaultEnqueuerUseRegistry.java
@@ -129,6 +129,11 @@
   }
 
   @Override
+  public boolean registerInstanceOf(DexType type) {
+    return enqueuer.traceInstanceOf(type, context);
+  }
+
+  @Override
   public void registerMethodHandle(DexMethodHandle methodHandle, MethodHandleUse use) {
     super.registerMethodHandle(methodHandle, use);
     enqueuer.traceMethodHandle(methodHandle, use, context);
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 281561b..6ed64fe 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -57,7 +57,7 @@
 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.GraphLens;
+import com.android.tools.r8.graph.GraphLens.NestedGraphLens;
 import com.android.tools.r8.graph.InnerClassAttribute;
 import com.android.tools.r8.graph.LookupLambdaTarget;
 import com.android.tools.r8.graph.LookupTarget;
@@ -72,6 +72,8 @@
 import com.android.tools.r8.graph.UseRegistry.MethodHandleUse;
 import com.android.tools.r8.graph.analysis.DesugaredLibraryConversionWrapperAnalysis;
 import com.android.tools.r8.graph.analysis.EnqueuerAnalysis;
+import com.android.tools.r8.graph.analysis.EnqueuerCheckCastAnalysis;
+import com.android.tools.r8.graph.analysis.EnqueuerInstanceOfAnalysis;
 import com.android.tools.r8.graph.analysis.EnqueuerInvokeAnalysis;
 import com.android.tools.r8.ir.analysis.proto.ProtoEnqueuerUseRegistry;
 import com.android.tools.r8.ir.analysis.proto.schema.ProtoEnqueuerExtension;
@@ -184,6 +186,8 @@
 
   private Set<EnqueuerAnalysis> analyses = Sets.newIdentityHashSet();
   private Set<EnqueuerInvokeAnalysis> invokeAnalyses = Sets.newIdentityHashSet();
+  private Set<EnqueuerInstanceOfAnalysis> instanceOfAnalyses = Sets.newIdentityHashSet();
+  private Set<EnqueuerCheckCastAnalysis> checkCastAnalyses = Sets.newIdentityHashSet();
 
   // Don't hold a direct pointer to app info (use appView).
   private AppInfoWithClassHierarchy appInfo;
@@ -436,6 +440,16 @@
     return this;
   }
 
+  public Enqueuer registerInstanceOfAnalysis(EnqueuerInstanceOfAnalysis analysis) {
+    instanceOfAnalyses.add(analysis);
+    return this;
+  }
+
+  public Enqueuer registerCheckCastAnalysis(EnqueuerCheckCastAnalysis analysis) {
+    checkCastAnalyses.add(analysis);
+    return this;
+  }
+
   public void setAnnotationRemoverBuilder(AnnotationRemover.Builder annotationRemoverBuilder) {
     this.annotationRemoverBuilder = annotationRemoverBuilder;
   }
@@ -589,6 +603,12 @@
     return clazz != null && clazz.isProgramClass() ? clazz.asProgramClass() : null;
   }
 
+  private DexProgramClass getProgramClassOrNullFromReflectiveAccess(DexType type) {
+    // This is using appInfo.definitionForWithoutExistenceAssert() to avoid that we report
+    // reflectively accessed types as missing.
+    return asProgramClassOrNull(appInfo().definitionForWithoutExistenceAssert(type));
+  }
+
   private void warnIfLibraryTypeInheritsFromProgramType(DexLibraryClass clazz) {
     if (clazz.superType != null) {
       ensureFromLibraryOrThrow(clazz.superType, clazz);
@@ -899,6 +919,7 @@
   }
 
   boolean traceCheckCast(DexType type, ProgramMethod currentMethod) {
+    checkCastAnalyses.forEach(analysis -> analysis.traceCheckCast(type, currentMethod));
     return traceConstClassOrCheckCast(type, currentMethod);
   }
 
@@ -1021,6 +1042,11 @@
     return true;
   }
 
+  boolean traceInstanceOf(DexType type, ProgramMethod currentMethod) {
+    instanceOfAnalyses.forEach(analysis -> analysis.traceInstanceOf(type, currentMethod));
+    return traceTypeReference(type, currentMethod);
+  }
+
   boolean traceInvokeDirect(DexMethod invokedMethod, ProgramMethod context) {
     boolean skipTracing =
         registerDeferredActionForDeadProtoBuilder(
@@ -1109,7 +1135,7 @@
       pendingReflectiveUses.add(context);
     }
     // See comment in handleJavaLangEnumValueOf.
-    if (invokedMethod == dexItemFactory.enumMethods.valueOf) {
+    if (invokedMethod == dexItemFactory.enumMembers.valueOf) {
       pendingReflectiveUses.add(context);
     }
     // Handling of application services.
@@ -2733,8 +2759,8 @@
     return appInfoWithLiveness;
   }
 
-  public GraphLens buildGraphLens(AppView<?> appView) {
-    return lambdaRewriter != null ? lambdaRewriter.buildMappingLens(appView) : appView.graphLens();
+  public NestedGraphLens buildGraphLens(AppView<?> appView) {
+    return lambdaRewriter != null ? lambdaRewriter.buildMappingLens(appView) : null;
   }
 
   private void keepClassWithRules(DexProgramClass clazz, Set<ProguardKeepRuleBase> rules) {
@@ -2871,12 +2897,15 @@
     }
 
     // Now all additions are computed, the application is atomically extended with those additions.
-    Builder appBuilder = appInfo.app().asDirect().builder();
-    additions.amendApplication(appBuilder);
-    DirectMappedDexApplication app = appBuilder.build();
-    appInfo = new AppInfoWithClassHierarchy(app);
+    appInfo =
+        appInfo.rebuild(
+            app -> {
+              Builder appBuilder = app.asDirect().builder();
+              additions.amendApplication(appBuilder);
+              return appBuilder.build();
+            });
     appView.setAppInfo(appInfo);
-    subtypingInfo = new SubtypingInfo(app.allClasses(), app);
+    subtypingInfo = new SubtypingInfo(appView);
 
     // Finally once all synthesized items "exist" it is now safe to continue tracing. The new work
     // items are enqueued and the fixed point will continue once this subroutine returns.
@@ -3652,7 +3681,7 @@
       handleJavaLangReflectConstructorNewInstance(method, invoke);
       return;
     }
-    if (invokedMethod == dexItemFactory.enumMethods.valueOf) {
+    if (invokedMethod == dexItemFactory.enumMembers.valueOf) {
       handleJavaLangEnumValueOf(method, invoke);
       return;
     }
@@ -3672,11 +3701,7 @@
       return;
     }
     if (identifierItem.isDexType()) {
-      // This is using appView.definitionFor() to avoid that we report reflectively accessed types
-      // as missing.
-      DexProgramClass clazz =
-          asProgramClassOrNull(
-              appInfo().definitionForWithoutExistenceAssert(identifierItem.asDexType()));
+      DexProgramClass clazz = getProgramClassOrNullFromReflectiveAccess(identifierItem.asDexType());
       if (clazz == null) {
         return;
       }
@@ -3756,7 +3781,7 @@
       return;
     }
 
-    DexProgramClass clazz = getProgramClassOrNull(instantiatedType);
+    DexProgramClass clazz = getProgramClassOrNullFromReflectiveAccess(instantiatedType);
     if (clazz == null) {
       return;
     }
@@ -4224,11 +4249,6 @@
     }
 
     @Override
-    public DexProgramClass definitionForProgramType(DexType type) {
-      return enqueuer.getProgramClassOrNull(type);
-    }
-
-    @Override
     public DexItemFactory dexItemFactory() {
       return appView.dexItemFactory();
     }
diff --git a/src/main/java/com/android/tools/r8/shaking/MainDexDirectReferenceTracer.java b/src/main/java/com/android/tools/r8/shaking/MainDexDirectReferenceTracer.java
index 205cf71..0382893 100644
--- a/src/main/java/com/android/tools/r8/shaking/MainDexDirectReferenceTracer.java
+++ b/src/main/java/com/android/tools/r8/shaking/MainDexDirectReferenceTracer.java
@@ -175,6 +175,11 @@
       consumer.accept(type);
       return true;
     }
+
+    @Override
+    public boolean registerInstanceOf(DexType type) {
+      return registerTypeReference(type);
+    }
   }
 
   private class AnnotationDirectReferenceCollector implements IndexedItemCollection {
diff --git a/src/main/java/com/android/tools/r8/shaking/MainDexListBuilder.java b/src/main/java/com/android/tools/r8/shaking/MainDexListBuilder.java
index fc34925..c122cd7 100644
--- a/src/main/java/com/android/tools/r8/shaking/MainDexListBuilder.java
+++ b/src/main/java/com/android/tools/r8/shaking/MainDexListBuilder.java
@@ -6,13 +6,13 @@
 import com.android.tools.r8.errors.CompilationError;
 import com.android.tools.r8.graph.AppInfo;
 import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
+import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexAnnotation;
 import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexProto;
 import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.graph.DirectMappedDexApplication;
 import com.android.tools.r8.utils.SetUtils;
 import java.util.IdentityHashMap;
 import java.util.Map;
@@ -28,9 +28,8 @@
 public class MainDexListBuilder {
 
   private final Set<DexType> roots;
-  private final AppInfoWithClassHierarchy appInfo;
+  private final AppView<? extends AppInfoWithClassHierarchy> appView;
   private final Map<DexType, Boolean> annotationTypeContainEnum;
-  private final DirectMappedDexApplication dexApplication;
   private final MainDexClasses.Builder mainDexClassesBuilder;
 
   public static void checkForAssumedLibraryTypes(AppInfo appInfo) {
@@ -48,17 +47,21 @@
 
   /**
    * @param roots Classes which code may be executed before secondary dex files loading.
-   * @param application the dex appplication.
+   * @param appView the dex appplication.
    */
-  public MainDexListBuilder(Set<DexProgramClass> roots, DirectMappedDexApplication application) {
-    this.dexApplication = application;
-    this.appInfo = new AppInfoWithClassHierarchy(dexApplication);
+  public MainDexListBuilder(
+      Set<DexProgramClass> roots, AppView<? extends AppInfoWithClassHierarchy> appView) {
+    this.appView = appView;
     // Only consider program classes for the root set.
     this.roots = SetUtils.mapIdentityHashSet(roots, DexProgramClass::getType);
-    mainDexClassesBuilder = MainDexClasses.builder(appInfo).addRoots(this.roots);
+    mainDexClassesBuilder = MainDexClasses.builder(appView.appInfo()).addRoots(this.roots);
     annotationTypeContainEnum = new IdentityHashMap<>();
   }
 
+  private AppInfoWithClassHierarchy appInfo() {
+    return appView.appInfo();
+  }
+
   public MainDexClasses run() {
     traceMainDexDirectDependencies();
     traceRuntimeAnnotationsWithEnumForMainDex();
@@ -66,7 +69,7 @@
   }
 
   private void traceRuntimeAnnotationsWithEnumForMainDex() {
-    for (DexProgramClass clazz : dexApplication.classes()) {
+    for (DexProgramClass clazz : appInfo().classes()) {
       DexType dexType = clazz.type;
       if (mainDexClassesBuilder.contains(dexType)) {
         continue;
@@ -91,7 +94,7 @@
   private boolean isAnnotationWithEnum(DexType dexType) {
     Boolean value = annotationTypeContainEnum.get(dexType);
     if (value == null) {
-      DexClass clazz = appInfo.definitionFor(dexType);
+      DexClass clazz = appView.definitionFor(dexType);
       if (clazz == null) {
         // Information is missing lets be conservative.
         value = true;
@@ -102,7 +105,7 @@
         for (DexEncodedMethod method : clazz.virtualMethods()) {
           DexProto proto = method.method.proto;
           if (proto.parameters.isEmpty()) {
-            DexType valueType = proto.returnType.toBaseType(appInfo.dexItemFactory());
+            DexType valueType = proto.returnType.toBaseType(appView.dexItemFactory());
             if (valueType.isClassType()) {
               if (isEnum(valueType)) {
                 value = true;
@@ -122,16 +125,15 @@
 
   private boolean isEnum(DexType valueType) {
     return valueType.isClassType()
-        && appInfo.isSubtype(valueType, appInfo.dexItemFactory().enumType);
+        && appInfo().isSubtype(valueType, appView.dexItemFactory().enumType);
   }
 
   private boolean isAnnotation(DexType valueType) {
-    return appInfo.isSubtype(valueType, appInfo.dexItemFactory().annotationType);
+    return appInfo().isSubtype(valueType, appView.dexItemFactory().annotationType);
   }
 
   private void traceMainDexDirectDependencies() {
-    new MainDexDirectReferenceTracer(appInfo, this::addDirectDependency)
-        .run(roots);
+    new MainDexDirectReferenceTracer(appInfo(), this::addDirectDependency).run(roots);
   }
 
   private void addAnnotationsWithEnum(DexProgramClass clazz) {
@@ -141,7 +143,7 @@
     for (DexEncodedMethod method : clazz.virtualMethods()) {
       DexProto proto = method.method.proto;
       if (proto.parameters.isEmpty()) {
-        DexType valueType = proto.returnType.toBaseType(appInfo.dexItemFactory());
+        DexType valueType = proto.returnType.toBaseType(appView.dexItemFactory());
         if (isEnum(valueType)) {
           addDirectDependency(valueType);
         }
@@ -156,13 +158,13 @@
 
   private void addDirectDependency(DexType type) {
     // Consider only component type of arrays
-    type = type.toBaseType(appInfo.dexItemFactory());
+    type = type.toBaseType(appView.dexItemFactory());
 
     if (!type.isClassType() || mainDexClassesBuilder.contains(type)) {
       return;
     }
 
-    DexClass clazz = appInfo.definitionFor(type);
+    DexClass clazz = appView.definitionFor(type);
     // No library classes in main-dex.
     if (clazz == null || clazz.isNotProgramClass()) {
       return;
diff --git a/src/main/java/com/android/tools/r8/shaking/TreePruner.java b/src/main/java/com/android/tools/r8/shaking/TreePruner.java
index ec2ced1..be956c3 100644
--- a/src/main/java/com/android/tools/r8/shaking/TreePruner.java
+++ b/src/main/java/com/android/tools/r8/shaking/TreePruner.java
@@ -57,7 +57,8 @@
             : UsagePrinter.DONT_PRINT;
   }
 
-  public DirectMappedDexApplication run(DirectMappedDexApplication application) {
+  public DirectMappedDexApplication run() {
+    DirectMappedDexApplication application = appView.appInfo().app().asDirect();
     Timing timing = application.timing;
     timing.begin("Pruning application...");
     DirectMappedDexApplication result;
diff --git a/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java b/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
index c2b3627..1de617d 100644
--- a/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
+++ b/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
@@ -1918,6 +1918,11 @@
     public boolean registerTypeReference(DexType type) {
       return checkTypeReference(type);
     }
+
+    @Override
+    public boolean registerInstanceOf(DexType type) {
+      return checkTypeReference(type);
+    }
   }
 
   protected static class SynthesizedBridgeCode extends AbstractSynthesizedCode {
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 6d85a4e..dfd253d 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -184,7 +184,7 @@
     enableClassStaticizer = false;
     enableDevirtualization = false;
     enableLambdaMerging = false;
-    enableHorizontalClassMerging = false;
+    enableStaticClassMerging = false;
     enableVerticalClassMerging = false;
     enableEnumUnboxing = false;
     enableUninstantiatedTypeOptimization = false;
@@ -217,7 +217,8 @@
   public boolean enableFieldAssignmentTracker = true;
   public boolean enableFieldBitAccessAnalysis =
       System.getProperty("com.android.tools.r8.fieldBitAccessAnalysis") != null;
-  public boolean enableHorizontalClassMerging = true;
+  public boolean enableStaticClassMerging = true;
+  public boolean enableHorizontalClassMerging = false;
   public boolean enableVerticalClassMerging = true;
   public boolean enableArgumentRemoval = true;
   public boolean enableUnusedInterfaceRemoval = true;
diff --git a/src/main/java/com/android/tools/r8/utils/WorkList.java b/src/main/java/com/android/tools/r8/utils/WorkList.java
index 092a4fc..e9bf6cd 100644
--- a/src/main/java/com/android/tools/r8/utils/WorkList.java
+++ b/src/main/java/com/android/tools/r8/utils/WorkList.java
@@ -30,6 +30,12 @@
     return new WorkList<T>(EqualityTest.IDENTITY);
   }
 
+  public static <T> WorkList<T> newIdentityWorkList(T item) {
+    WorkList<T> workList = new WorkList<>(EqualityTest.IDENTITY);
+    workList.addIfNotSeen(item);
+    return workList;
+  }
+
   public static <T> WorkList<T> newIdentityWorkList(Iterable<T> items) {
     WorkList<T> workList = new WorkList<>(EqualityTest.IDENTITY);
     workList.addIfNotSeen(items);
@@ -44,6 +50,10 @@
     }
   }
 
+  public void addAll(Iterable<T> items) {
+    items.forEach(workingList::addLast);
+  }
+
   public void addIfNotSeen(Iterable<T> items) {
     items.forEach(this::addIfNotSeen);
   }
diff --git a/src/main/java/com/android/tools/r8/utils/collections/LongLivedProgramMethodSetBuilder.java b/src/main/java/com/android/tools/r8/utils/collections/LongLivedProgramMethodSetBuilder.java
index 4b4eccb..8297a9f 100644
--- a/src/main/java/com/android/tools/r8/utils/collections/LongLivedProgramMethodSetBuilder.java
+++ b/src/main/java/com/android/tools/r8/utils/collections/LongLivedProgramMethodSetBuilder.java
@@ -17,18 +17,26 @@
 public class LongLivedProgramMethodSetBuilder<T extends ProgramMethodSet> {
 
   private final IntFunction<T> factory;
-  private final Set<DexMethod> methods = Sets.newIdentityHashSet();
+  private final Set<DexMethod> methods;
 
-  private LongLivedProgramMethodSetBuilder(IntFunction<T> factory) {
+  private LongLivedProgramMethodSetBuilder(IntFunction<T> factory, Set<DexMethod> methods) {
     this.factory = factory;
+    this.methods = methods;
   }
 
-  public static LongLivedProgramMethodSetBuilder<?> create() {
-    return new LongLivedProgramMethodSetBuilder<>(ProgramMethodSet::create);
+  public static LongLivedProgramMethodSetBuilder<?> createForIdentitySet() {
+    return new LongLivedProgramMethodSetBuilder<>(
+        ProgramMethodSet::create, Sets.newIdentityHashSet());
   }
 
-  public static LongLivedProgramMethodSetBuilder<SortedProgramMethodSet> createSorted() {
-    return new LongLivedProgramMethodSetBuilder<>(ignore -> SortedProgramMethodSet.create());
+  public static LongLivedProgramMethodSetBuilder<SortedProgramMethodSet> createForSortedSet() {
+    return new LongLivedProgramMethodSetBuilder<>(
+        ignore -> SortedProgramMethodSet.create(), Sets.newIdentityHashSet());
+  }
+
+  public static LongLivedProgramMethodSetBuilder<?> createConcurrentForIdentitySet() {
+    return new LongLivedProgramMethodSetBuilder<>(
+        ignore -> ProgramMethodSet.create(), Sets.newConcurrentHashSet());
   }
 
   public void add(ProgramMethod method) {
diff --git a/src/main/java/com/android/tools/r8/utils/collections/ProgramMethodSet.java b/src/main/java/com/android/tools/r8/utils/collections/ProgramMethodSet.java
index 14d938d..92d51ea 100644
--- a/src/main/java/com/android/tools/r8/utils/collections/ProgramMethodSet.java
+++ b/src/main/java/com/android/tools/r8/utils/collections/ProgramMethodSet.java
@@ -22,7 +22,6 @@
 
   private static final ProgramMethodSet EMPTY = new ProgramMethodSet(ImmutableMap.of());
 
-  private boolean deterministicOrdering = false;
   private Map<DexMethod, ProgramMethod> backing;
 
   ProgramMethodSet(Map<DexMethod, ProgramMethod> backing) {
diff --git a/src/test/java/com/android/tools/r8/R8UnreachableCodeTest.java b/src/test/java/com/android/tools/r8/R8UnreachableCodeTest.java
index e30b788..a022ecf 100644
--- a/src/test/java/com/android/tools/r8/R8UnreachableCodeTest.java
+++ b/src/test/java/com/android/tools/r8/R8UnreachableCodeTest.java
@@ -7,7 +7,6 @@
 import static org.junit.Assert.assertEquals;
 
 import com.android.tools.r8.dex.ApplicationReader;
-import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexProgramClass;
@@ -43,8 +42,7 @@
     options.programConsumer = DexIndexedConsumer.emptyConsumer();
     DirectMappedDexApplication application =
         new ApplicationReader(input, options, timing).read(executorService).toDirect();
-    IRConverter converter =
-        new IRConverter(AppView.createForR8(new AppInfoWithClassHierarchy(application)), null);
+    IRConverter converter = new IRConverter(AppView.createForR8(application), null);
     converter.optimize();
     DexProgramClass clazz = application.classes().iterator().next();
     assertEquals(4, clazz.getMethodCollection().numberOfDirectMethods());
diff --git a/src/test/java/com/android/tools/r8/TestBase.java b/src/test/java/com/android/tools/r8/TestBase.java
index b764451..545ab13 100644
--- a/src/test/java/com/android/tools/r8/TestBase.java
+++ b/src/test/java/com/android/tools/r8/TestBase.java
@@ -32,7 +32,6 @@
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexProto;
 import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.graph.DirectMappedDexApplication;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.graph.SmaliWriter;
 import com.android.tools.r8.graph.SubtypingInfo;
@@ -613,8 +612,7 @@
     DexItemFactory dexItemFactory = new DexItemFactory();
     InternalOptions options = new InternalOptions(keepConfig.apply(dexItemFactory), new Reporter());
     DexApplication dexApplication = readApplicationForDexOutput(app, options);
-    AppView<AppInfoWithClassHierarchy> appView =
-        AppView.createForR8(new AppInfoWithClassHierarchy(dexApplication.toDirect()));
+    AppView<AppInfoWithClassHierarchy> appView = AppView.createForR8(dexApplication.toDirect());
     appView.setAppServices(AppServices.builder(appView).build());
     return appView;
   }
@@ -641,16 +639,15 @@
     AppView<AppInfoWithClassHierarchy> appView = computeAppViewWithSubtyping(app, keepConfig);
     // Run the tree shaker to compute an instance of AppInfoWithLiveness.
     ExecutorService executor = Executors.newSingleThreadExecutor();
-    DirectMappedDexApplication application = appView.appInfo().app().asDirect();
-    SubtypingInfo subtypingInfo = new SubtypingInfo(application.allClasses(), application);
+    SubtypingInfo subtypingInfo = new SubtypingInfo(appView);
     RootSet rootSet =
         new RootSetBuilder(
-                appView, subtypingInfo, application.options.getProguardConfiguration().getRules())
+                appView, subtypingInfo, appView.options().getProguardConfiguration().getRules())
             .run(executor);
     appView.setRootSet(rootSet);
     AppInfoWithLiveness appInfoWithLiveness =
         EnqueuerFactory.createForInitialTreeShaking(appView, subtypingInfo)
-            .traceApplication(rootSet, ProguardClassFilter.empty(), executor, application.timing);
+            .traceApplication(rootSet, ProguardClassFilter.empty(), executor, Timing.empty());
     // We do not run the tree pruner to ensure that the hierarchy is as designed and not modified
     // due to liveness.
     return appView.setAppInfo(appInfoWithLiveness);
diff --git a/src/test/java/com/android/tools/r8/checkdiscarded/B162969014.java b/src/test/java/com/android/tools/r8/checkdiscarded/B162969014.java
new file mode 100644
index 0000000..6eb081f
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/checkdiscarded/B162969014.java
@@ -0,0 +1,108 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.checkdiscarded;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.AssumeNoSideEffects;
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.R8FullTestBuilder;
+import com.android.tools.r8.R8TestCompileResult;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.utils.BooleanUtils;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class B162969014 extends TestBase {
+
+  private final boolean checkLogIsDiscarded;
+  private final TestParameters parameters;
+
+  @Parameterized.Parameters(name = "{1}, checkLogIsDiscarded: {0}")
+  public static List<Object[]> data() {
+    return buildParameters(
+        BooleanUtils.values(), getTestParameters().withAllRuntimesAndApiLevels().build());
+  }
+
+  public B162969014(boolean checkLogIsDiscarded, TestParameters parameters) {
+    this.checkLogIsDiscarded = checkLogIsDiscarded;
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void test() throws Exception {
+    R8TestCompileResult compileResult;
+    try {
+      compileResult =
+          testForR8(parameters.getBackend())
+              .addInnerClasses(B162969014.class)
+              .addKeepMainRule(TestClass.class)
+              .apply(this::applyCheckDiscardedRule)
+              .enableAssumeNoSideEffectsAnnotations()
+              .enableInliningAnnotations()
+              .setMinApi(parameters.getApiLevel())
+              .compile();
+    } catch (CompilationFailedException e) {
+      assertTrue(checkLogIsDiscarded);
+      assertEquals(e.getCause().getMessage(), "Discard checks failed.");
+      return;
+    }
+
+    compileResult
+        .inspect(this::inspect)
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutputLines("foo");
+  }
+
+  private void applyCheckDiscardedRule(R8FullTestBuilder builder) {
+    if (checkLogIsDiscarded) {
+      builder.addKeepRules(
+          "-checkdiscard class " + Log.class.getTypeName() + "{",
+          "  public static void foo();",
+          "  public static void bar();",
+          "}");
+    }
+  }
+
+  private void inspect(CodeInspector inspector) {
+    ClassSubject logClassSubject = inspector.clazz(Log.class);
+    assertThat(logClassSubject, isPresent());
+    assertThat(logClassSubject.uniqueMethodWithName("foo"), isPresent());
+    assertThat(logClassSubject.uniqueMethodWithName("bar"), not(isPresent()));
+  }
+
+  static class TestClass {
+
+    public static void main(String[] args) {
+      Log.foo();
+      Log.bar();
+    }
+  }
+
+  static class Log {
+
+    @NeverInline
+    public static void foo() {
+      System.out.println("foo");
+    }
+
+    @AssumeNoSideEffects
+    @NeverInline
+    public static void bar() {
+      System.out.println("bar");
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/desugaring/lambdanames/PackageDependentLambdaNamesTest.java b/src/test/java/com/android/tools/r8/desugaring/lambdanames/PackageDependentLambdaNamesTest.java
new file mode 100644
index 0000000..934a15b
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugaring/lambdanames/PackageDependentLambdaNamesTest.java
@@ -0,0 +1,117 @@
+// 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.desugaring.lambdanames;
+
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestRunResult;
+import com.android.tools.r8.transformers.ClassFileTransformer;
+import com.android.tools.r8.utils.BooleanUtils;
+import com.android.tools.r8.utils.DescriptorUtils;
+import com.android.tools.r8.utils.StringUtils;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class PackageDependentLambdaNamesTest extends TestBase {
+
+  static final String EXPECTED = StringUtils.lines("Hello, world");
+
+  private final TestParameters parameters;
+  private final boolean samePackage;
+
+  @Parameterized.Parameters(name = "{0}, same-pkg:{1}")
+  public static List<Object[]> data() {
+    return buildParameters(
+        getTestParameters().withAllRuntimes().withAllApiLevels().build(), BooleanUtils.values());
+  }
+
+  public PackageDependentLambdaNamesTest(TestParameters parameters, boolean samePackage) {
+    this.parameters = parameters;
+    this.samePackage = samePackage;
+  }
+
+  @Test
+  public void test() throws Exception {
+    TestRunResult<?> result =
+        testForRuntime(parameters)
+            .addProgramClasses(StringConsumer.class)
+            .addProgramClassFileData(getTestClass(), getA(), getB())
+            .run(parameters.getRuntime(), TestClass.class)
+            .assertSuccessWithOutput(EXPECTED);
+    if (parameters.isDexRuntime()) {
+      result.inspect(
+          inspector -> {
+            // When in the same package we expect the two System.out::print lambdas to be shared.
+            assertEquals(
+                samePackage ? 2 : 3,
+                inspector.allClasses().stream()
+                    .filter(c -> c.isSynthesizedJavaLambdaClass())
+                    .count());
+          });
+    }
+  }
+
+  private byte[] getA() throws Exception {
+    ClassFileTransformer transformer = transformer(A.class);
+    if (!samePackage) {
+      transformer.setClassDescriptor("La/A;");
+    }
+    return transformer.transform();
+  }
+
+  private byte[] getB() throws Exception {
+    ClassFileTransformer transformer = transformer(B.class);
+    if (!samePackage) {
+      transformer.setClassDescriptor("Lb/B;");
+    }
+    return transformer.transform();
+  }
+
+  private byte[] getTestClass() throws Exception {
+    ClassFileTransformer transformer = transformer(TestClass.class);
+    if (!samePackage) {
+      transformer
+          .replaceClassDescriptorInMethodInstructions(
+              DescriptorUtils.javaTypeToDescriptor(A.class.getTypeName()), "La/A;")
+          .replaceClassDescriptorInMethodInstructions(
+              DescriptorUtils.javaTypeToDescriptor(B.class.getTypeName()), "Lb/B;");
+    }
+    return transformer.transform();
+  }
+
+  @FunctionalInterface
+  public interface StringConsumer {
+    void accept(String arg);
+  }
+
+  public static class A {
+    public void hello() {
+      TestClass.apply(System.out::print, "Hello, ");
+    }
+  }
+
+  public static class B {
+    public void world() {
+      TestClass.apply(System.out::print, "world");
+    }
+  }
+
+  public static class TestClass {
+
+    public static void apply(StringConsumer consumer, String arg) {
+      consumer.accept(arg);
+    }
+
+    public static void main(String[] args) {
+      new A().hello();
+      new B().world();
+      apply(System.out::println, "");
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/EnumToStringLibTest.java b/src/test/java/com/android/tools/r8/enumunboxing/EnumToStringLibTest.java
index 83393bf..1a440cb 100644
--- a/src/test/java/com/android/tools/r8/enumunboxing/EnumToStringLibTest.java
+++ b/src/test/java/com/android/tools/r8/enumunboxing/EnumToStringLibTest.java
@@ -65,6 +65,7 @@
             .addKeepMainRule(AlwaysCorrectProgram.class)
             .addKeepMainRule(AlwaysCorrectProgram2.class)
             .addKeepRules(enumKeepRules.getKeepRules())
+            .addKeepAttributeLineNumberTable()
             .addOptionsModification(
                 options -> {
                   options.enableEnumUnboxing = enumUnboxing;
@@ -75,19 +76,26 @@
             .allowDiagnosticInfoMessages(enumUnboxing)
             .setMinApi(parameters.getApiLevel())
             .compile();
-    compile
-        .run(parameters.getRuntime(), AlwaysCorrectProgram.class)
-        .assertSuccessWithOutputLines("0", "1", "2", "0", "1", "2", "0", "1", "2");
+    if (enumKeepRules.isStudio() && enumValueOptimization && enumUnboxing) {
+      // TODO(b/160939354): Enum unboxing synthesizes a toString() method based on field names.
+      compile
+          .run(parameters.getRuntime(), AlwaysCorrectProgram.class)
+          .assertFailureWithErrorThatMatches(containsString("IllegalArgumentException"));
+    } else {
+      compile
+          .run(parameters.getRuntime(), AlwaysCorrectProgram.class)
+          .assertSuccessWithOutputLines("0", "1", "2", "0", "1", "2", "0", "1", "2");
+    }
     if (!enumKeepRules.isSnap() && enumUnboxing) {
-      // TODO(b/160667929): Fix toString and enum unboxing.
+      // TODO(b/160939354): Enum unboxing synthesizes a toString() method based on field names.
       compile
           .run(parameters.getRuntime(), AlwaysCorrectProgram2.class)
           .assertFailureWithErrorThatMatches(containsString("IllegalArgumentException"));
-      return;
+    } else {
+      compile
+          .run(parameters.getRuntime(), AlwaysCorrectProgram2.class)
+          .assertSuccessWithOutputLines("0", "1", "2", "0", "1", "2");
     }
-    compile
-        .run(parameters.getRuntime(), AlwaysCorrectProgram2.class)
-        .assertSuccessWithOutputLines("0", "1", "2", "0", "1", "2");
   }
 
   private void assertEnumFieldsMinified(CodeInspector codeInspector) throws Exception {
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/EnumUnboxingClassStaticizerTest.java b/src/test/java/com/android/tools/r8/enumunboxing/EnumUnboxingClassStaticizerTest.java
index 4f724d3..758ed36 100644
--- a/src/test/java/com/android/tools/r8/enumunboxing/EnumUnboxingClassStaticizerTest.java
+++ b/src/test/java/com/android/tools/r8/enumunboxing/EnumUnboxingClassStaticizerTest.java
@@ -12,6 +12,7 @@
 import com.android.tools.r8.NeverInline;
 import com.android.tools.r8.R8TestRunResult;
 import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.ir.optimize.enums.EnumUnboxingRewriter;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.android.tools.r8.utils.codeinspector.MethodSubject;
 import java.util.List;
@@ -70,7 +71,11 @@
       assertThat(codeInspector.clazz(Companion.class).uniqueMethodWithName("method"), isPresent());
       return;
     }
-    MethodSubject method = codeInspector.clazz(CompanionHost.class).uniqueMethodWithName("method");
+    MethodSubject method =
+        codeInspector
+            .clazz(CompanionHost.class)
+            .uniqueMethodWithName(
+                EnumUnboxingRewriter.ENUM_UNBOXING_UTILITY_METHOD_PREFIX + "1$method");
     assertThat(method, isPresent());
     assertEquals("int", method.getMethod().method.proto.parameters.toString());
   }
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/EnumUnboxingTestBase.java b/src/test/java/com/android/tools/r8/enumunboxing/EnumUnboxingTestBase.java
index 65aee25..02b5a5f 100644
--- a/src/test/java/com/android/tools/r8/enumunboxing/EnumUnboxingTestBase.java
+++ b/src/test/java/com/android/tools/r8/enumunboxing/EnumUnboxingTestBase.java
@@ -42,6 +42,10 @@
       return keepRules;
     }
 
+    public boolean isStudio() {
+      return this == STUDIO;
+    }
+
     public boolean isSnap() {
       return this == SNAP;
     }
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/OverloadingEnumUnboxingTest.java b/src/test/java/com/android/tools/r8/enumunboxing/OverloadingEnumUnboxingTest.java
new file mode 100644
index 0000000..8e206b8
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/enumunboxing/OverloadingEnumUnboxingTest.java
@@ -0,0 +1,141 @@
+// 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.enumunboxing;
+
+import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.R8TestRunResult;
+import com.android.tools.r8.TestParameters;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class OverloadingEnumUnboxingTest extends EnumUnboxingTestBase {
+
+  private final TestParameters parameters;
+  private final boolean enumValueOptimization;
+  private final EnumKeepRules enumKeepRules;
+
+  @Parameters(name = "{0} valueOpt: {1} keep: {2}")
+  public static List<Object[]> data() {
+    return enumUnboxingTestParameters();
+  }
+
+  public OverloadingEnumUnboxingTest(
+      TestParameters parameters, boolean enumValueOptimization, EnumKeepRules enumKeepRules) {
+    this.parameters = parameters;
+    this.enumValueOptimization = enumValueOptimization;
+    this.enumKeepRules = enumKeepRules;
+  }
+
+  @Test
+  public void testEnumUnboxing() throws Exception {
+    Class<?> classToTest = TestClass.class;
+    R8TestRunResult run =
+        testForR8(parameters.getBackend())
+            .addInnerClasses(OverloadingEnumUnboxingTest.class)
+            .addKeepMainRule(classToTest)
+            .addKeepRules(enumKeepRules.getKeepRules())
+            .enableNeverClassInliningAnnotations()
+            .enableInliningAnnotations()
+            .addOptionsModification(opt -> enableEnumOptions(opt, enumValueOptimization))
+            .allowDiagnosticInfoMessages()
+            .setMinApi(parameters.getApiLevel())
+            .compile()
+            .inspectDiagnosticMessages(
+                m -> {
+                  assertEnumIsUnboxed(MyEnum1.class, MyEnum1.class.getSimpleName(), m);
+                  assertEnumIsUnboxed(MyEnum2.class, MyEnum2.class.getSimpleName(), m);
+                })
+            .run(parameters.getRuntime(), classToTest)
+            .assertSuccess();
+    assertLines2By2Correct(run.getStdOut());
+  }
+
+  @SuppressWarnings("SameParameterValue")
+  @NeverClassInline
+  enum MyEnum1 {
+    A,
+    B,
+    C;
+  }
+
+  @NeverClassInline
+  enum MyEnum2 {
+    A,
+    B,
+    C;
+  }
+
+  static class TestClass {
+
+    public static void main(String[] args) {
+      virtualTest();
+      staticTest();
+    }
+
+    @NeverInline
+    private static void staticTest() {
+      staticMethod(42);
+      System.out.println("42");
+      staticMethod(MyEnum1.A);
+      System.out.println("0");
+      staticMethod(MyEnum1.B);
+      System.out.println("1");
+      staticMethod(MyEnum2.A);
+      System.out.println("0");
+      staticMethod(MyEnum2.B);
+      System.out.println("1");
+    }
+
+    @NeverInline
+    private static void virtualTest() {
+      TestClass testClass = new TestClass();
+      testClass.virtualMethod(42);
+      System.out.println("42");
+      testClass.virtualMethod(MyEnum1.A);
+      System.out.println("0");
+      testClass.virtualMethod(MyEnum1.B);
+      System.out.println("1");
+      testClass.virtualMethod(MyEnum2.A);
+      System.out.println("0");
+      testClass.virtualMethod(MyEnum2.B);
+      System.out.println("1");
+    }
+
+    @NeverInline
+    public void virtualMethod(MyEnum1 e1) {
+      System.out.println(e1.ordinal());
+    }
+
+    @NeverInline
+    public void virtualMethod(MyEnum2 e1) {
+      System.out.println(e1.ordinal());
+    }
+
+    @NeverInline
+    public void virtualMethod(int i) {
+      System.out.println(i);
+    }
+
+    @NeverInline
+    public static void staticMethod(MyEnum1 e1) {
+      System.out.println(e1.ordinal());
+    }
+
+    @NeverInline
+    public static void staticMethod(MyEnum2 e1) {
+      System.out.println(e1.ordinal());
+    }
+
+    @NeverInline
+    public static void staticMethod(int i) {
+      System.out.println(i);
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/VirtualMethodsAccessibilityErrorEnumUnboxingTest.java b/src/test/java/com/android/tools/r8/enumunboxing/VirtualMethodsAccessibilityErrorEnumUnboxingTest.java
new file mode 100644
index 0000000..52d8688
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/enumunboxing/VirtualMethodsAccessibilityErrorEnumUnboxingTest.java
@@ -0,0 +1,101 @@
+// 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.enumunboxing;
+
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.objectweb.asm.Opcodes.ACC_PRIVATE;
+import static org.objectweb.asm.Opcodes.ACC_STATIC;
+
+import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.transformers.ClassTransformer;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+import org.objectweb.asm.MethodVisitor;
+
+@RunWith(Parameterized.class)
+public class VirtualMethodsAccessibilityErrorEnumUnboxingTest extends EnumUnboxingTestBase {
+  private final TestParameters parameters;
+  private final boolean enumValueOptimization;
+  private final EnumKeepRules enumKeepRules;
+
+  @Parameters(name = "{0} valueOpt: {1} keep: {2}")
+  public static List<Object[]> data() {
+    return enumUnboxingTestParameters();
+  }
+
+  public VirtualMethodsAccessibilityErrorEnumUnboxingTest(
+      TestParameters parameters, boolean enumValueOptimization, EnumKeepRules enumKeepRules) {
+    this.parameters = parameters;
+    this.enumValueOptimization = enumValueOptimization;
+    this.enumKeepRules = enumKeepRules;
+  }
+
+  @Test
+  public void testEnumUnboxing() throws Exception {
+    byte[] testClassData =
+        transformer(TestClass.class)
+            .addClassTransformer(
+                new ClassTransformer() {
+                  @Override
+                  public MethodVisitor visitMethod(
+                      int access,
+                      String name,
+                      String descriptor,
+                      String signature,
+                      String[] exceptions) {
+                    if (name.equals("privateMethod")) {
+                      return super.visitMethod(
+                          ACC_STATIC | ACC_PRIVATE, name, descriptor, signature, exceptions);
+                    } else {
+                      return super.visitMethod(access, name, descriptor, signature, exceptions);
+                    }
+                  }
+                })
+            .transform();
+    testForR8(parameters.getBackend())
+        .noMinification()
+        .addProgramClasses(MyEnum.class)
+        .addProgramClassFileData(testClassData)
+        .addKeepMainRule(TestClass.class)
+        .addKeepRules(enumKeepRules.getKeepRules())
+        .enableNeverClassInliningAnnotations()
+        .enableInliningAnnotations()
+        .addOptionsModification(opt -> enableEnumOptions(opt, enumValueOptimization))
+        .allowDiagnosticInfoMessages()
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertFailureWithErrorThatMatches(containsString("java.lang.IllegalAccessError"));
+  }
+
+  public static class TestClass {
+    // The method privateMethod will be transformed into a private method.
+    @NeverInline
+    protected static int privateMethod() {
+      return System.currentTimeMillis() > 0 ? 4 : 3;
+    }
+
+    public static void main(String[] args) {
+      System.out.println(MyEnum.A.ordinal());
+      MyEnum.print();
+    }
+  }
+
+  @NeverClassInline
+  enum MyEnum {
+    A,
+    B;
+
+    @NeverInline
+    static void print() {
+      // This should raise an accessibility error, weither the enum is unboxed or not.
+      System.out.println(TestClass.privateMethod());
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/VirtualMethodsEnumUnboxingTest.java b/src/test/java/com/android/tools/r8/enumunboxing/VirtualMethodsEnumUnboxingTest.java
index 2e3466f..3a1fbab 100644
--- a/src/test/java/com/android/tools/r8/enumunboxing/VirtualMethodsEnumUnboxingTest.java
+++ b/src/test/java/com/android/tools/r8/enumunboxing/VirtualMethodsEnumUnboxingTest.java
@@ -1,11 +1,8 @@
 // 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.enumunboxing;
 
-import static org.hamcrest.CoreMatchers.containsString;
-
 import com.android.tools.r8.NeverClassInline;
 import com.android.tools.r8.NeverInline;
 import com.android.tools.r8.R8TestCompileResult;
@@ -19,7 +16,6 @@
 
 @RunWith(Parameterized.class)
 public class VirtualMethodsEnumUnboxingTest extends EnumUnboxingTestBase {
-
   private final TestParameters parameters;
   private final boolean enumValueOptimization;
   private final EnumKeepRules enumKeepRules;
@@ -43,7 +39,6 @@
         testForR8(parameters.getBackend())
             .addInnerClasses(VirtualMethodsEnumUnboxingTest.class)
             .addKeepMainRule(classToTest)
-            .addKeepMainRule(VirtualMethodsFail.class)
             .addKeepRules(enumKeepRules.getKeepRules())
             .enableNeverClassInliningAnnotations()
             .enableInliningAnnotations()
@@ -58,13 +53,15 @@
                   assertEnumIsUnboxed(MyEnumWithCollisions.class, classToTest.getSimpleName(), m);
                   assertEnumIsUnboxed(
                       MyEnumWithPackagePrivateCall.class, classToTest.getSimpleName(), m);
+                  assertEnumIsUnboxed(
+                      MyEnumWithProtectedCall.class, classToTest.getSimpleName(), m);
+                  assertEnumIsUnboxed(
+                      MyEnumWithPackagePrivateFieldAccess.class, classToTest.getSimpleName(), m);
+                  assertEnumIsUnboxed(
+                      MyEnumWithPackagePrivateAndPrivateCall.class, classToTest.getSimpleName(), m);
                 });
     R8TestRunResult run = compile.run(parameters.getRuntime(), classToTest).assertSuccess();
     assertLines2By2Correct(run.getStdOut());
-    // TODO(b/160854837): This test should actually be successful.
-    compile
-        .run(parameters.getRuntime(), VirtualMethodsFail.class)
-        .assertFailureWithErrorThatMatches(containsString("IllegalAccessError"));
   }
 
   @SuppressWarnings("SameParameterValue")
@@ -110,7 +107,6 @@
       printPrivate();
     }
   }
-
   // Use two enums to test collision.
   @NeverClassInline
   enum MyEnum2 {
@@ -160,10 +156,30 @@
   }
 
   @NeverClassInline
-  static class PackagePrivateClass {
+  static class PackagePrivateClassWithPublicMembers {
+    @NeverInline
+    public static void print() {
+      System.out.println("print1");
+    }
+
+    public static int item = System.currentTimeMillis() > 0 ? 42 : 41;
+  }
+
+  @NeverClassInline
+  public static class PublicClassWithPackagePrivateMembers {
     @NeverInline
     static void print() {
-      System.out.println("print");
+      System.out.println("print2");
+    }
+
+    static int item = System.currentTimeMillis() > 0 ? 4 : 1;
+  }
+
+  @NeverClassInline
+  public static class PublicClassWithProtectedMembers {
+    @NeverInline
+    protected static void print() {
+      System.out.println("print3");
     }
   }
 
@@ -175,41 +191,75 @@
 
     @NeverInline
     public static void callPackagePrivate() {
-      PackagePrivateClass.print();
+      PackagePrivateClassWithPublicMembers.print();
+      System.out.println("print1");
+      PublicClassWithPackagePrivateMembers.print();
+      System.out.println("print2");
     }
   }
 
-  static class VirtualMethodsFail {
-    public static void main(String[] args) {
-      testCollisions();
-      testPackagePrivate();
+  @NeverClassInline
+  enum MyEnumWithPackagePrivateAndPrivateCall {
+    A,
+    B,
+    C;
+
+    @NeverInline
+    public static void privateMethod() {
+      System.out.println("private");
     }
 
     @NeverInline
-    private static void testPackagePrivate() {
-      System.out.println(MyEnumWithPackagePrivateCall.A.ordinal());
-      System.out.println(0);
-      MyEnumWithPackagePrivateCall.callPackagePrivate();
-      System.out.println("print");
+    public static void callPackagePrivateAndPrivate() {
+      // This method has "SAME_CLASS" as compilation state, yet,
+      // it also has a package-private call.
+      privateMethod();
+      System.out.println("private");
+      PackagePrivateClassWithPublicMembers.print();
+      System.out.println("print1");
+      PublicClassWithPackagePrivateMembers.print();
+      System.out.println("print2");
     }
+  }
+
+  @NeverClassInline
+  enum MyEnumWithProtectedCall {
+    A,
+    B,
+    C;
 
     @NeverInline
-    private static void testCollisions() {
-      System.out.println(MyEnumWithCollisions.A.get());
-      System.out.println(5);
-      System.out.println(MyEnumWithCollisions.B.get());
-      System.out.println(2);
-      System.out.println(MyEnumWithCollisions.C.get());
-      System.out.println(1);
+    public static void callProtected() {
+      PublicClassWithProtectedMembers.print();
+      System.out.println("print3");
+    }
+  }
+
+  @NeverClassInline
+  enum MyEnumWithPackagePrivateFieldAccess {
+    A,
+    B,
+    C;
+
+    @NeverInline
+    public static void accessPackagePrivate() {
+      System.out.println(PackagePrivateClassWithPublicMembers.item);
+      System.out.println("42");
+      System.out.println(PublicClassWithPackagePrivateMembers.item);
+      System.out.println("4");
     }
   }
 
   static class VirtualMethods {
-
     public static void main(String[] args) {
       testCustomMethods();
       testCustomMethods2();
       testNonPublicMethods();
+      testCollisions();
+      testPackagePrivateMethod();
+      testProtectedAccess();
+      testPackagePrivateAndPrivateMethod();
+      testPackagePrivateAccess();
     }
 
     @NeverInline
@@ -243,5 +293,43 @@
       System.out.println((MyEnum2.A.returnEnum(true).ordinal()));
       System.out.println(1);
     }
+
+    @NeverInline
+    private static void testPackagePrivateMethod() {
+      System.out.println(MyEnumWithPackagePrivateCall.A.ordinal());
+      System.out.println(0);
+      MyEnumWithPackagePrivateCall.callPackagePrivate();
+    }
+
+    @NeverInline
+    private static void testPackagePrivateAndPrivateMethod() {
+      System.out.println(MyEnumWithPackagePrivateAndPrivateCall.A.ordinal());
+      System.out.println(0);
+      MyEnumWithPackagePrivateAndPrivateCall.callPackagePrivateAndPrivate();
+    }
+
+    @NeverInline
+    private static void testPackagePrivateAccess() {
+      System.out.println(MyEnumWithPackagePrivateFieldAccess.A.ordinal());
+      System.out.println(0);
+      MyEnumWithPackagePrivateFieldAccess.accessPackagePrivate();
+    }
+
+    @NeverInline
+    private static void testProtectedAccess() {
+      System.out.println(MyEnumWithProtectedCall.A.ordinal());
+      System.out.println(0);
+      MyEnumWithProtectedCall.callProtected();
+    }
+
+    @NeverInline
+    private static void testCollisions() {
+      System.out.println(MyEnumWithCollisions.A.get());
+      System.out.println(5);
+      System.out.println(MyEnumWithCollisions.B.get());
+      System.out.println(2);
+      System.out.println(MyEnumWithCollisions.C.get());
+      System.out.println(1);
+    }
   }
 }
diff --git a/src/test/java/com/android/tools/r8/graph/DexTypeTest.java b/src/test/java/com/android/tools/r8/graph/DexTypeTest.java
index 014249b..6b2d73e 100644
--- a/src/test/java/com/android/tools/r8/graph/DexTypeTest.java
+++ b/src/test/java/com/android/tools/r8/graph/DexTypeTest.java
@@ -35,7 +35,7 @@
             .read()
             .toDirect();
     factory = options.itemFactory;
-    appInfo = new AppInfoWithClassHierarchy(application);
+    appInfo = AppView.createForR8(application).appInfo();
   }
 
   @Test
diff --git a/src/test/java/com/android/tools/r8/internal/R8GMSCoreLookupTest.java b/src/test/java/com/android/tools/r8/internal/R8GMSCoreLookupTest.java
index ca35303..82f21d4 100644
--- a/src/test/java/com/android/tools/r8/internal/R8GMSCoreLookupTest.java
+++ b/src/test/java/com/android/tools/r8/internal/R8GMSCoreLookupTest.java
@@ -59,10 +59,9 @@
         new ApplicationReader(app, new InternalOptions(), timing)
             .read(proguardMap, executorService)
             .toDirect();
-    InternalOptions options = new InternalOptions();
-    appView = AppView.createForR8(new AppInfoWithClassHierarchy(program));
+    appView = AppView.createForR8(program);
     appView.setAppServices(AppServices.builder(appView).build());
-    subtypingInfo = new SubtypingInfo(program.allClasses(), program);
+    subtypingInfo = new SubtypingInfo(appView);
   }
 
   private AppInfoWithClassHierarchy appInfo() {
diff --git a/src/test/java/com/android/tools/r8/ir/InlineTest.java b/src/test/java/com/android/tools/r8/ir/InlineTest.java
index 2bd2803..cb48232 100644
--- a/src/test/java/com/android/tools/r8/ir/InlineTest.java
+++ b/src/test/java/com/android/tools/r8/ir/InlineTest.java
@@ -14,7 +14,6 @@
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexApplication;
 import com.android.tools.r8.graph.DexItemFactory;
-import com.android.tools.r8.graph.DirectMappedDexApplication;
 import com.android.tools.r8.graph.SubtypingInfo;
 import com.android.tools.r8.ir.code.BasicBlock;
 import com.android.tools.r8.ir.code.IRCode;
@@ -65,12 +64,10 @@
       MethodSubject method,
       List<IRCode> additionalCode)
       throws ExecutionException {
-    DirectMappedDexApplication directApp = application.asDirect();
-    AppView<AppInfoWithClassHierarchy> appView =
-        AppView.createForR8(new AppInfoWithClassHierarchy(directApp));
+    AppView<AppInfoWithClassHierarchy> appView = AppView.createForR8(application.asDirect());
     appView.setAppServices(AppServices.builder(appView).build());
     ExecutorService executorService = ThreadUtils.getExecutorService(options);
-    SubtypingInfo subtypingInfo = new SubtypingInfo(directApp.allClasses(), directApp);
+    SubtypingInfo subtypingInfo = new SubtypingInfo(appView);
     appView.setRootSet(
         new RootSetBuilder(
                 appView,
diff --git a/src/test/java/com/android/tools/r8/ir/IrInjectionTestBase.java b/src/test/java/com/android/tools/r8/ir/IrInjectionTestBase.java
index 83f64ee..a031cf8 100644
--- a/src/test/java/com/android/tools/r8/ir/IrInjectionTestBase.java
+++ b/src/test/java/com/android/tools/r8/ir/IrInjectionTestBase.java
@@ -12,7 +12,7 @@
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.InstructionIterator;
 import com.android.tools.r8.ir.code.InstructionListIterator;
-import com.android.tools.r8.ir.code.ValueNumberGenerator;
+import com.android.tools.r8.ir.code.NumberGenerator;
 import com.android.tools.r8.ir.conversion.IRConverter;
 import com.android.tools.r8.shaking.MainDexClasses;
 import com.android.tools.r8.smali.SmaliBuilder;
@@ -68,7 +68,7 @@
     public final List<IRCode> additionalCode;
     public final AndroidAppConsumers consumers;
 
-    public final ValueNumberGenerator valueNumberGenerator = new ValueNumberGenerator();
+    public final NumberGenerator valueNumberGenerator = new NumberGenerator();
 
     public TestApplication(AppView<?> appView, MethodSubject method) {
       this(appView, method, null);
diff --git a/src/test/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldBitAccessInfoTest.java b/src/test/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldBitAccessInfoTest.java
index 86f4e3d..357fa0e 100644
--- a/src/test/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldBitAccessInfoTest.java
+++ b/src/test/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldBitAccessInfoTest.java
@@ -134,7 +134,7 @@
                 timing)
             .read()
             .toDirect();
-    return AppView.createForR8(new AppInfoWithClassHierarchy(application));
+    return AppView.createForR8(application);
   }
 
   private DexEncodedField uniqueFieldByName(DexProgramClass clazz, String name) {
diff --git a/src/test/java/com/android/tools/r8/ir/analysis/type/TypeLatticeTest.java b/src/test/java/com/android/tools/r8/ir/analysis/type/TypeLatticeTest.java
index 06de00a..5cdd0ca 100644
--- a/src/test/java/com/android/tools/r8/ir/analysis/type/TypeLatticeTest.java
+++ b/src/test/java/com/android/tools/r8/ir/analysis/type/TypeLatticeTest.java
@@ -60,7 +60,7 @@
             .read()
             .toDirect();
     factory = options.itemFactory;
-    appView = AppView.createForR8(new AppInfoWithClassHierarchy(application));
+    appView = AppView.createForR8(application);
   }
 
   private TopTypeElement top() {
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/B161735546.java b/src/test/java/com/android/tools/r8/ir/optimize/B161735546.java
new file mode 100644
index 0000000..ce969b7
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/B161735546.java
@@ -0,0 +1,66 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.optimize;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class B161735546 extends TestBase {
+
+  private final TestParameters parameters;
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public B161735546(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void test() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(B161735546.class)
+        .addKeepMainRule(TestClass.class)
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutputLines("1", "2", "3");
+  }
+
+  static class TestClass {
+    public static void main(String[] args) {
+      new CounterUtils() {}.add(new Counter(), 3);
+    }
+  }
+
+  static class Counter {
+    int i = 0;
+  }
+
+  interface CounterConsumer {
+    void accept(Counter counter);
+  }
+
+  interface CounterUtils {
+    default void add(Counter counter, int i) {
+      if (i == 0) {
+        return;
+      }
+      CounterConsumer continuation =
+          c -> {
+            System.out.println(++counter.i);
+            add(counter, i - 1);
+          };
+      continuation.accept(counter);
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/ConstantRemovalTest.java b/src/test/java/com/android/tools/r8/ir/optimize/ConstantRemovalTest.java
index c139dcf..cdb9264 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/ConstantRemovalTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/ConstantRemovalTest.java
@@ -16,11 +16,11 @@
 import com.android.tools.r8.ir.code.IRMetadata;
 import com.android.tools.r8.ir.code.Instruction;
 import com.android.tools.r8.ir.code.Move;
+import com.android.tools.r8.ir.code.NumberGenerator;
 import com.android.tools.r8.ir.code.NumericType;
 import com.android.tools.r8.ir.code.Position;
 import com.android.tools.r8.ir.code.Return;
 import com.android.tools.r8.ir.code.Value;
-import com.android.tools.r8.ir.code.ValueNumberGenerator;
 import com.android.tools.r8.ir.regalloc.LinearScanRegisterAllocator;
 import com.android.tools.r8.ir.regalloc.LiveIntervals;
 import com.android.tools.r8.origin.Origin;
@@ -70,7 +70,7 @@
     //
     // Then test that peephole optimization realizes that the last const number
     // is needed and the value 10 is *not* still in register 0 at that point.
-    final ValueNumberGenerator basicBlockNumberGenerator = new ValueNumberGenerator();
+    final NumberGenerator basicBlockNumberGenerator = new NumberGenerator();
     BasicBlock block = new BasicBlock();
     block.setNumber(basicBlockNumberGenerator.next());
 
@@ -141,7 +141,7 @@
             options,
             null,
             blocks,
-            new ValueNumberGenerator(),
+            new NumberGenerator(),
             basicBlockNumberGenerator,
             IRMetadata.unknown(),
             Origin.unknown());
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/TrivialGotoEliminationTest.java b/src/test/java/com/android/tools/r8/ir/optimize/TrivialGotoEliminationTest.java
index 69394e0..9fc16f8 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/TrivialGotoEliminationTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/TrivialGotoEliminationTest.java
@@ -22,11 +22,11 @@
 import com.android.tools.r8.ir.code.If;
 import com.android.tools.r8.ir.code.If.Type;
 import com.android.tools.r8.ir.code.Instruction;
+import com.android.tools.r8.ir.code.NumberGenerator;
 import com.android.tools.r8.ir.code.Position;
 import com.android.tools.r8.ir.code.Return;
 import com.android.tools.r8.ir.code.Throw;
 import com.android.tools.r8.ir.code.Value;
-import com.android.tools.r8.ir.code.ValueNumberGenerator;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.InternalOptions;
@@ -62,7 +62,7 @@
     //   throw v0
     // block2:
     //   return
-    final ValueNumberGenerator basicBlockNumberGenerator = new ValueNumberGenerator();
+    final NumberGenerator basicBlockNumberGenerator = new NumberGenerator();
     Position position = Position.testingPosition();
     BasicBlock block2 = new BasicBlock();
     BasicBlock block0 =
@@ -98,7 +98,7 @@
             options,
             null,
             blocks,
-            new ValueNumberGenerator(),
+            new NumberGenerator(),
             basicBlockNumberGenerator,
             IRMetadata.unknown(),
             Origin.unknown());
@@ -126,7 +126,7 @@
     //
     // block3:
     //   goto block3
-    final ValueNumberGenerator basicBlockNumberGenerator = new ValueNumberGenerator();
+    final NumberGenerator basicBlockNumberGenerator = new NumberGenerator();
     Position position = Position.testingPosition();
     BasicBlock block0 = new BasicBlock();
     block0.setNumber(basicBlockNumberGenerator.next());
@@ -185,7 +185,7 @@
             options,
             null,
             blocks,
-            new ValueNumberGenerator(),
+            new NumberGenerator(),
             basicBlockNumberGenerator,
             IRMetadata.unknown(),
             Origin.unknown());
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/devirtualize/DevirtualizerNonNullRewritingTest.java b/src/test/java/com/android/tools/r8/ir/optimize/devirtualize/DevirtualizerNonNullRewritingTest.java
index 12bce34..61d5cf3 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/devirtualize/DevirtualizerNonNullRewritingTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/devirtualize/DevirtualizerNonNullRewritingTest.java
@@ -7,7 +7,8 @@
 import com.android.tools.r8.NeverInline;
 import com.android.tools.r8.NeverMerge;
 import com.android.tools.r8.TestBase;
-import com.android.tools.r8.ToolHelper;
+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;
@@ -16,15 +17,24 @@
 @RunWith(Parameterized.class)
 public class DevirtualizerNonNullRewritingTest extends TestBase {
 
-  private final Backend backend;
+  private final TestParameters parameters;
+  private static final String EXPECTED_OUTPUT = "Hello!";
 
-  @Parameters(name = "Backend: {0}")
-  public static Backend[] data() {
-    return ToolHelper.getBackends();
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
   }
 
-  public DevirtualizerNonNullRewritingTest(Backend backend) {
-    this.backend = backend;
+  public DevirtualizerNonNullRewritingTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testRuntime() throws Exception {
+    testForRuntime(parameters)
+        .addInnerClasses(DevirtualizerNonNullRewritingTest.class)
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutput(EXPECTED_OUTPUT);
   }
 
   /**
@@ -32,17 +42,14 @@
    */
   @Test
   public void test() throws Exception {
-    String expectedOutput = "Hello!";
-
-    testForJvm().addTestClasspath().run(TestClass.class).assertSuccessWithOutput(expectedOutput);
-
-    testForR8(backend)
+    testForR8(parameters.getBackend())
         .addInnerClasses(DevirtualizerNonNullRewritingTest.class)
         .addKeepMainRule(TestClass.class)
         .enableInliningAnnotations()
         .enableMergeAnnotations()
-        .run(TestClass.class)
-        .assertSuccessWithOutput(expectedOutput);
+        .setMinApi(parameters.getApiLevel())
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutput(EXPECTED_OUTPUT);
   }
 
   static class TestClass {
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/devirtualize/InvokeInterfaceToInvokeVirtualTest.java b/src/test/java/com/android/tools/r8/ir/optimize/devirtualize/InvokeInterfaceToInvokeVirtualTest.java
index a75cc4c..efb4422 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/devirtualize/InvokeInterfaceToInvokeVirtualTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/devirtualize/InvokeInterfaceToInvokeVirtualTest.java
@@ -14,7 +14,6 @@
 import com.android.tools.r8.ir.optimize.devirtualize.invokeinterface.A1;
 import com.android.tools.r8.ir.optimize.devirtualize.invokeinterface.I;
 import com.android.tools.r8.ir.optimize.devirtualize.invokeinterface.Main;
-import com.android.tools.r8.utils.StringUtils;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.android.tools.r8.utils.codeinspector.InstructionSubject;
@@ -27,10 +26,11 @@
 public class InvokeInterfaceToInvokeVirtualTest extends TestBase {
 
   private final TestParameters parameters;
+  private final String EXPECTED_OUTPUT = "0";
 
   @Parameterized.Parameters(name = "{0}")
   public static TestParametersCollection data() {
-    return getTestParameters().withAllRuntimes().build();
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
   }
 
   public InvokeInterfaceToInvokeVirtualTest(TestParameters parameters) {
@@ -38,25 +38,24 @@
   }
 
   @Test
+  public void testRuntime() throws Exception {
+    testForRuntime(parameters)
+        .addProgramClasses(I.class, A.class, A0.class, A1.class, Main.class)
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines(EXPECTED_OUTPUT);
+  }
+
+  @Test
   public void listOfInterface() throws Exception {
-    String expectedOutput = StringUtils.lines("0");
-
-    if (parameters.isCfRuntime()) {
-      testForJvm()
-          .addTestClasspath()
-          .run(parameters.getRuntime(), Main.class)
-          .assertSuccessWithOutput(expectedOutput);
-    }
-
     CodeInspector inspector =
         testForR8(parameters.getBackend())
             .addProgramClasses(I.class, A.class, A0.class, A1.class, Main.class)
             .addKeepMainRule(Main.class)
             .addOptionsModification(
                 options -> options.enableInliningOfInvokesWithNullableReceivers = false)
-            .setMinApi(parameters.getRuntime())
+            .setMinApi(parameters.getApiLevel())
             .run(parameters.getRuntime(), Main.class)
-            .assertSuccessWithOutput(expectedOutput)
+            .assertSuccessWithOutputLines(EXPECTED_OUTPUT)
             .inspector();
 
     ClassSubject clazz = inspector.clazz(Main.class);
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/staticizer/EnumCompanionClassStaticizerTest.java b/src/test/java/com/android/tools/r8/ir/optimize/staticizer/EnumCompanionClassStaticizerTest.java
new file mode 100644
index 0000000..a865bc2
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/staticizer/EnumCompanionClassStaticizerTest.java
@@ -0,0 +1,81 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.optimize.staticizer;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.utils.BooleanUtils;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class EnumCompanionClassStaticizerTest extends TestBase {
+
+  private final boolean enableEnumUnboxing;
+  private final TestParameters parameters;
+
+  @Parameterized.Parameters(name = "{1}, enum unboxing: {0}")
+  public static List<Object[]> data() {
+    return buildParameters(
+        BooleanUtils.values(), getTestParameters().withAllRuntimesAndApiLevels().build());
+  }
+
+  public EnumCompanionClassStaticizerTest(boolean enableEnumUnboxing, TestParameters parameters) {
+    this.enableEnumUnboxing = enableEnumUnboxing;
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void test() throws Exception {
+    testForR8(parameters.getBackend())
+        .addProgramClasses(TestClass.class, MyEnum.class, Companion.class)
+        .addKeepMainRule(TestClass.class)
+        .addOptionsModification(options -> options.enableEnumUnboxing = enableEnumUnboxing)
+        .enableNeverClassInliningAnnotations()
+        .enableInliningAnnotations()
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .inspect(this::inspect)
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutputLines("Hello world!");
+  }
+
+  private void inspect(CodeInspector inspector) {
+    ClassSubject companionClassSubject = inspector.clazz(Companion.class);
+    // TODO(b/162798790): Should be absent.
+    assertThat(companionClassSubject, isPresent());
+  }
+
+  static class TestClass {
+
+    public static void main(String[] args) {
+      MyEnum.A.companion.greet();
+    }
+  }
+
+  enum MyEnum {
+    A;
+
+    public static final Companion companion = new Companion();
+  }
+
+  @NeverClassInline
+  static class Companion {
+
+    @NeverInline
+    public void greet() {
+      System.out.println("Hello world!");
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/string/StringBuilderFullyInDoWhileLoopTest.java b/src/test/java/com/android/tools/r8/ir/optimize/string/StringBuilderFullyInDoWhileLoopTest.java
new file mode 100644
index 0000000..0074f01
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/string/StringBuilderFullyInDoWhileLoopTest.java
@@ -0,0 +1,67 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.ir.optimize.string;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+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.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class StringBuilderFullyInDoWhileLoopTest extends TestBase {
+
+  private final TestParameters parameters;
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public StringBuilderFullyInDoWhileLoopTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void test() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(StringBuilderFullyInDoWhileLoopTest.class)
+        .addKeepMainRule(TestClass.class)
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .inspect(this::inspect)
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutputLines("000");
+  }
+
+  private void inspect(CodeInspector inspector) {
+    MethodSubject mainMethodSubject = inspector.clazz(TestClass.class).mainMethod();
+    assertThat(mainMethodSubject, isPresent());
+    assertEquals(
+        parameters.isCfRuntime(),
+        mainMethodSubject
+            .streamInstructions()
+            .anyMatch(instruction -> instruction.isNewInstance("java.lang.StringBuilder")));
+  }
+
+  static class TestClass {
+    public static void main(String[] args) {
+      int i = 0;
+      do {
+        StringBuilder builder = new StringBuilder();
+        builder.append("0");
+        System.out.print(builder.toString());
+        i++;
+      } while (i < 3);
+      System.out.println();
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/string/StringBuilderOptimizerAnalysisTest.java b/src/test/java/com/android/tools/r8/ir/optimize/string/StringBuilderOptimizerAnalysisTest.java
index 761856f..9c1a2b1 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/string/StringBuilderOptimizerAnalysisTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/string/StringBuilderOptimizerAnalysisTest.java
@@ -14,6 +14,7 @@
 import com.android.tools.r8.ir.analysis.AnalysisTestBase;
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.Instruction;
+import com.android.tools.r8.ir.code.InvokeMethod;
 import com.android.tools.r8.ir.code.Value;
 import com.android.tools.r8.ir.optimize.string.StringBuilderOptimizer.BuilderState;
 import java.util.HashSet;
@@ -367,11 +368,13 @@
       boolean expectToSeeMethodToString) {
     boolean seenMethodToString = false;
     for (Map.Entry<Instruction, BuilderState> entry : perBuilderState.entrySet()) {
-      if (entry.getKey().isInvokeMethod()
-        && optimizer.optimizationConfiguration.isToStringMethod(
-            entry.getKey().asInvokeMethod().getInvokedMethod())) {
+      InvokeMethod invoke = entry.getKey().asInvokeMethod();
+      if (invoke != null
+          && optimizer.optimizationConfiguration.isToStringMethod(invoke.getInvokedMethod())) {
         seenMethodToString = true;
-        assertEquals(expectedConstString, optimizer.analysis.toCompileTimeString(entry.getValue()));
+        Value builder = invoke.getArgument(0).getAliasedValue();
+        assertEquals(
+            expectedConstString, optimizer.analysis.toCompileTimeString(builder, entry.getValue()));
       }
     }
     assertEquals(expectToSeeMethodToString, seenMethodToString);
@@ -384,11 +387,12 @@
       boolean expectToSeeMethodToString) {
     boolean seenMethodToString = false;
     for (Map.Entry<Instruction, BuilderState> entry : perBuilderState.entrySet()) {
-      if (entry.getKey().isInvokeMethod()
-          && optimizer.optimizationConfiguration.isToStringMethod(
-              entry.getKey().asInvokeMethod().getInvokedMethod())) {
+      InvokeMethod invoke = entry.getKey().asInvokeMethod();
+      if (invoke != null
+          && optimizer.optimizationConfiguration.isToStringMethod(invoke.getInvokedMethod())) {
         seenMethodToString = true;
-        String computedString = optimizer.analysis.toCompileTimeString(entry.getValue());
+        Value builder = invoke.getArgument(0).getAliasedValue();
+        String computedString = optimizer.analysis.toCompileTimeString(builder, entry.getValue());
         assertNotNull(computedString);
         assertTrue(expectedConstStrings.contains(computedString));
         expectedConstStrings.remove(computedString);
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/string/StringBuilderPartiallyInDoWhileLoopTest.java b/src/test/java/com/android/tools/r8/ir/optimize/string/StringBuilderPartiallyInDoWhileLoopTest.java
new file mode 100644
index 0000000..62becf9
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/string/StringBuilderPartiallyInDoWhileLoopTest.java
@@ -0,0 +1,48 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.ir.optimize.string;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class StringBuilderPartiallyInDoWhileLoopTest extends TestBase {
+
+  private final TestParameters parameters;
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public StringBuilderPartiallyInDoWhileLoopTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void test() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(StringBuilderPartiallyInDoWhileLoopTest.class)
+        .addKeepMainRule(TestClass.class)
+        .setMinApi(parameters.getApiLevel())
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutputLines("000");
+  }
+
+  static class TestClass {
+    public static void main(String[] args) {
+      StringBuilder builder = new StringBuilder();
+      int i = 0;
+      do {
+        builder.append("0");
+        i++;
+      } while (i < 3);
+      System.out.println(builder.toString());
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataPrimitiveTypeRewriteTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataPrimitiveTypeRewriteTest.java
new file mode 100644
index 0000000..76c2894
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataPrimitiveTypeRewriteTest.java
@@ -0,0 +1,130 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.kotlin.metadata;
+
+import static com.android.tools.r8.KotlinCompilerTool.KOTLINC;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresentAndRenamed;
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.hamcrest.CoreMatchers.equalTo;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.JvmTestRunResult;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.ToolHelper.KotlinTargetVersion;
+import com.android.tools.r8.shaking.ProguardKeepAttributes;
+import com.android.tools.r8.utils.DescriptorUtils;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import java.nio.file.Path;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class MetadataPrimitiveTypeRewriteTest extends KotlinMetadataTestBase {
+
+  private final TestParameters parameters;
+  private final String EXPECTED = "Hello World";
+  private static final String PKG_LIB = PKG + ".primitive_type_rewrite_lib";
+  private static final String PKG_APP = PKG + ".primitive_type_rewrite_app";
+
+  @Parameterized.Parameters(name = "{0} target: {1}")
+  public static Collection<Object[]> data() {
+    return buildParameters(
+        getTestParameters().withCfRuntimes().build(), KotlinTargetVersion.values());
+  }
+
+  public MetadataPrimitiveTypeRewriteTest(
+      TestParameters parameters, KotlinTargetVersion targetVersion) {
+    super(targetVersion);
+    this.parameters = parameters;
+  }
+
+  private static Map<KotlinTargetVersion, Path> libJars = new HashMap<>();
+
+  @BeforeClass
+  public static void createLibJar() throws Exception {
+    for (KotlinTargetVersion targetVersion : KotlinTargetVersion.values()) {
+      Path baseLibJar =
+          kotlinc(KOTLINC, targetVersion)
+              .addSourceFiles(
+                  getKotlinFileInTest(DescriptorUtils.getBinaryNameFromJavaType(PKG_LIB), "lib"))
+              .compile();
+      libJars.put(targetVersion, baseLibJar);
+    }
+  }
+
+  @Test
+  public void smokeTest() throws Exception {
+    Path libJar = libJars.get(targetVersion);
+    Path output =
+        kotlinc(parameters.getRuntime().asCf(), KOTLINC, targetVersion)
+            .addClasspathFiles(libJar)
+            .addSourceFiles(
+                getKotlinFileInTest(DescriptorUtils.getBinaryNameFromJavaType(PKG_APP), "main"))
+            .setOutputPath(temp.newFolder().toPath())
+            .compile();
+    testForJvm()
+        .addRunClasspathFiles(ToolHelper.getKotlinStdlibJar(), libJar)
+        .addClasspath(output)
+        .run(parameters.getRuntime(), PKG_APP + ".MainKt")
+        .assertSuccessWithOutputLines(EXPECTED);
+  }
+
+  @Test
+  public void testRenamingRenameUnit() throws Exception {
+    runTest(false);
+  }
+
+  @Test
+  public void testRenamingKeepUnit() throws Exception {
+    runTest(true);
+  }
+
+  private void runTest(boolean keepUnit) throws Exception {
+    Path libJar =
+        testForR8(parameters.getBackend())
+            .addProgramFiles(ToolHelper.getKotlinStdlibJar(), libJars.get(targetVersion))
+            .addKeepAllClassesRuleWithAllowObfuscation()
+            .addKeepRules("-keep class " + PKG_LIB + ".LibKt { *; }")
+            .addKeepRules("-keep class kotlin.Metadata { *; }")
+            .applyIf(keepUnit, b -> b.addKeepRules("-keep class kotlin.Unit { *; }"))
+            .addKeepAttributes(ProguardKeepAttributes.RUNTIME_VISIBLE_ANNOTATIONS)
+            .allowDiagnosticWarningMessages()
+            .compile()
+            .inspect(
+                codeInspector -> {
+                  final ClassSubject clazz = codeInspector.clazz("kotlin.Unit");
+                  assertThat(clazz, isPresentAndRenamed(!keepUnit));
+                })
+            .assertAllWarningMessagesMatch(
+                equalTo("Resource 'META-INF/MANIFEST.MF' already exists."))
+            .writeToZip();
+    Path output =
+        kotlinc(parameters.getRuntime().asCf(), KOTLINC, targetVersion)
+            .addClasspathFiles(libJar)
+            .addSourceFiles(
+                getKotlinFileInTest(DescriptorUtils.getBinaryNameFromJavaType(PKG_APP), "main"))
+            .setOutputPath(temp.newFolder().toPath())
+            .compile();
+    final JvmTestRunResult runResult =
+        testForJvm()
+            .addRunClasspathFiles(ToolHelper.getKotlinStdlibJar(), libJar)
+            .addClasspath(output)
+            .run(parameters.getRuntime(), PKG_APP + ".MainKt");
+    if (keepUnit) {
+      runResult.assertSuccessWithOutputLines(EXPECTED);
+    } else {
+      runResult.assertFailureWithErrorThatMatches(
+          containsString(
+              "java.lang.NoSuchMethodError:"
+                  + " com.android.tools.r8.kotlin.metadata.primitive_type_rewrite_lib.LibKt.foo()"));
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataVersionNumberBumpTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataVersionNumberBumpTest.java
new file mode 100644
index 0000000..e262f66
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataVersionNumberBumpTest.java
@@ -0,0 +1,153 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.kotlin.metadata;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+import static org.objectweb.asm.Opcodes.ASM7;
+
+import com.android.tools.r8.R8FullTestBuilder;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.ToolHelper.KotlinTargetVersion;
+import com.android.tools.r8.graph.DexAnnotationElement;
+import com.android.tools.r8.references.Reference;
+import com.android.tools.r8.shaking.ProguardKeepAttributes;
+import com.android.tools.r8.transformers.ClassTransformer;
+import com.android.tools.r8.utils.StreamUtils;
+import com.android.tools.r8.utils.ZipUtils;
+import com.android.tools.r8.utils.codeinspector.AnnotationSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.FoundClassSubject;
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.function.Consumer;
+import java.util.stream.Collectors;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+import org.objectweb.asm.AnnotationVisitor;
+
+@RunWith(Parameterized.class)
+public class MetadataVersionNumberBumpTest extends KotlinMetadataTestBase {
+
+  private final TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public MetadataVersionNumberBumpTest(TestParameters parameters) {
+    super(KotlinTargetVersion.JAVA_8);
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testLessThan1_4() throws Exception {
+    final R8FullTestBuilder testBuilder = testForR8(parameters.getBackend());
+    rewriteMetadataVersion(testBuilder::addProgramClassFileData, new int[] {1, 1, 16});
+    testBuilder
+        .setMinApi(parameters.getApiLevel())
+        .addKeepAllClassesRule()
+        .addKeepAttributes(ProguardKeepAttributes.RUNTIME_VISIBLE_ANNOTATIONS)
+        .compile()
+        .inspect(inspector -> inspectMetadataVersion(inspector, "1.4.0"));
+  }
+
+  @Test
+  public void testEqualTo1_4() throws Exception {
+    final R8FullTestBuilder testBuilder = testForR8(parameters.getBackend());
+    rewriteMetadataVersion(testBuilder::addProgramClassFileData, new int[] {1, 4, 0});
+    testBuilder
+        .setMinApi(parameters.getApiLevel())
+        .addKeepAllClassesRule()
+        .addKeepAttributes(ProguardKeepAttributes.RUNTIME_VISIBLE_ANNOTATIONS)
+        .compile()
+        .inspect(inspector -> inspectMetadataVersion(inspector, "1.4.0"));
+  }
+
+  @Test
+  public void testGreaterThan1_4() throws Exception {
+    final R8FullTestBuilder testBuilder = testForR8(parameters.getBackend());
+    rewriteMetadataVersion(testBuilder::addProgramClassFileData, new int[] {1, 4, 2});
+    testBuilder
+        .setMinApi(parameters.getApiLevel())
+        .addKeepAllClassesRule()
+        .addKeepAttributes(ProguardKeepAttributes.RUNTIME_VISIBLE_ANNOTATIONS)
+        .compile()
+        .inspect(inspector -> inspectMetadataVersion(inspector, "1.4.2"));
+  }
+
+  private void rewriteMetadataVersion(Consumer<byte[]> rewrittenBytesConsumer, int[] newVersion)
+      throws IOException {
+    ZipUtils.iter(
+        ToolHelper.getKotlinStdlibJar().toString(),
+        ((entry, input) -> {
+          if (!entry.getName().endsWith(".class")) {
+            return;
+          }
+          final byte[] bytes = StreamUtils.StreamToByteArrayClose(input);
+          final byte[] rewrittenBytes =
+              transformMetadataVersion(
+                  entry.getName().substring(0, entry.getName().length() - 6), bytes, newVersion);
+          rewrittenBytesConsumer.accept(rewrittenBytes);
+        }));
+  }
+
+  private byte[] transformMetadataVersion(String descriptor, byte[] bytes, int[] newVersion) {
+    return transformer(bytes, Reference.classFromDescriptor(descriptor))
+        .addClassTransformer(
+            new ClassTransformer() {
+              @Override
+              public AnnotationVisitor visitAnnotation(String descriptor, boolean visible) {
+                if (!descriptor.equals("Lkotlin/Metadata;")) {
+                  return super.visitAnnotation(descriptor, visible);
+                } else {
+                  return new AnnotationVisitor(ASM7, super.visitAnnotation(descriptor, visible)) {
+                    @Override
+                    public void visit(String name, Object value) {
+                      if (name.equals("mv")) {
+                        super.visit(name, newVersion);
+                      } else {
+                        super.visit(name, value);
+                      }
+                    }
+                  };
+                }
+              }
+            })
+        .transform();
+  }
+
+  private void inspectMetadataVersion(CodeInspector inspector, String expectedVersion) {
+    for (FoundClassSubject clazz : inspector.allClasses()) {
+      verifyExpectedVersionForClass(clazz, expectedVersion);
+    }
+  }
+
+  private void verifyExpectedVersionForClass(FoundClassSubject clazz, String expectedVersion) {
+    final AnnotationSubject annotationSubject = clazz.annotation("kotlin.Metadata");
+    // TODO(b/164418977): All classes should have an annotation?
+    if (!annotationSubject.isPresent()) {
+      return;
+    }
+    final DexAnnotationElement[] elements = annotationSubject.getAnnotation().elements;
+    for (DexAnnotationElement element : elements) {
+      if (!element.name.toString().equals("mv")) {
+        continue;
+      }
+      final String version =
+          Arrays.stream(element.value.asDexValueArray().getValues())
+              .map(val -> val.asDexValueInt().value + "")
+              .collect(Collectors.joining("."));
+      assertEquals(expectedVersion, version);
+      return;
+    }
+    fail("Could not find the mv (metadataVersion) element");
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/primitive_type_rewrite_app/main.kt b/src/test/java/com/android/tools/r8/kotlin/metadata/primitive_type_rewrite_app/main.kt
new file mode 100644
index 0000000..adcf477
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/primitive_type_rewrite_app/main.kt
@@ -0,0 +1,11 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.kotlin.metadata.primitive_type_rewrite_app
+
+import com.android.tools.r8.kotlin.metadata.primitive_type_rewrite_lib.foo
+
+fun main() {
+  foo()
+}
\ No newline at end of file
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/primitive_type_rewrite_lib/lib.kt b/src/test/java/com/android/tools/r8/kotlin/metadata/primitive_type_rewrite_lib/lib.kt
new file mode 100644
index 0000000..ec26d64
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/primitive_type_rewrite_lib/lib.kt
@@ -0,0 +1,9 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.kotlin.metadata.primitive_type_rewrite_lib
+
+fun foo() {
+  println("Hello World")
+}
\ 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 984d813..e4e0b53 100644
--- a/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java
+++ b/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java
@@ -30,7 +30,6 @@
 import com.android.tools.r8.errors.CompilationError;
 import com.android.tools.r8.errors.DexFileOverflowDiagnostic;
 import com.android.tools.r8.errors.Unreachable;
-import com.android.tools.r8.graph.AppInfo;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.ClassAccessFlags;
 import com.android.tools.r8.graph.Code;
@@ -808,8 +807,7 @@
     options.minApiLevel = minApi;
     options.intermediate = intermediate;
     DexItemFactory factory = options.itemFactory;
-    AppInfo appInfo = new AppInfo(DexApplication.builder(options, timing).build());
-    AppView<?> appView = AppView.createForR8(appInfo);
+    AppView<?> appView = AppView.createForR8(DexApplication.builder(options, timing).build());
     DexApplication.Builder<?> builder = DexApplication.builder(options, timing);
     for (String clazz : classes) {
       DexString desc = factory.createString(DescriptorUtils.javaTypeToDescriptor(clazz));
diff --git a/src/test/java/com/android/tools/r8/naming/applymapping/ApplyMappingVirtualInvokeTest.java b/src/test/java/com/android/tools/r8/naming/applymapping/ApplyMappingVirtualInvokeTest.java
index cc4e99c..36bf030 100644
--- a/src/test/java/com/android/tools/r8/naming/applymapping/ApplyMappingVirtualInvokeTest.java
+++ b/src/test/java/com/android/tools/r8/naming/applymapping/ApplyMappingVirtualInvokeTest.java
@@ -111,7 +111,7 @@
             options -> {
               options.enableInlining = false;
               options.enableVerticalClassMerging = false;
-              options.enableHorizontalClassMerging = false;
+              options.enableStaticClassMerging = false;
               options.enableClassInlining = false;
             })
         .compile();
diff --git a/src/test/java/com/android/tools/r8/resolution/ArrayTargetLookupTest.java b/src/test/java/com/android/tools/r8/resolution/ArrayTargetLookupTest.java
index 4ad2bed..f13728a 100644
--- a/src/test/java/com/android/tools/r8/resolution/ArrayTargetLookupTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/ArrayTargetLookupTest.java
@@ -11,6 +11,7 @@
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.dex.ApplicationReader;
 import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
+import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexType;
@@ -19,8 +20,6 @@
 import com.android.tools.r8.utils.DescriptorUtils;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.Timing;
-import java.io.IOException;
-import java.util.concurrent.ExecutionException;
 import org.junit.Test;
 
 public class ArrayTargetLookupTest extends TestBase {
@@ -28,7 +27,7 @@
   public static class Foo {}
 
   @Test
-  public void testArrays() throws IOException, ExecutionException {
+  public void testArrays() throws Exception {
     Timing timing = Timing.empty();
     InternalOptions options = new InternalOptions();
     AndroidApp app =
@@ -38,7 +37,7 @@
             .build();
     DirectMappedDexApplication application =
         new ApplicationReader(app, options, timing).read().toDirect();
-    AppInfoWithClassHierarchy appInfo = new AppInfoWithClassHierarchy(application);
+    AppInfoWithClassHierarchy appInfo = AppView.createForR8(application).appInfo();
     DexItemFactory factory = options.itemFactory;
     DexType fooType =
         factory.createType(DescriptorUtils.javaTypeToDescriptor(Foo.class.getTypeName()));
diff --git a/src/test/java/com/android/tools/r8/resolution/virtualtargets/KeptTargetsIncompleteLookupTest.java b/src/test/java/com/android/tools/r8/resolution/virtualtargets/KeptTargetsIncompleteLookupTest.java
index 4332d86..3566d3a 100644
--- a/src/test/java/com/android/tools/r8/resolution/virtualtargets/KeptTargetsIncompleteLookupTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/virtualtargets/KeptTargetsIncompleteLookupTest.java
@@ -88,7 +88,11 @@
     DexMethod method = buildNullaryVoidMethod(initial, "foo", appInfo.dexItemFactory());
     ResolutionResult resolutionResult = appInfo.resolveMethodOnClass(method);
     DexProgramClass context =
-        appView.definitionForProgramType(buildType(Unrelated.class, appInfo.dexItemFactory()));
+        DexProgramClass.asProgramClassOrNull(
+            appView
+                .appInfo()
+                .definitionForWithoutExistenceAssert(
+                    buildType(Unrelated.class, appInfo.dexItemFactory())));
     LookupResult lookupResult = resolutionResult.lookupVirtualDispatchTargets(context, appInfo);
     assertTrue(lookupResult.isLookupResultSuccess());
     LookupResultSuccess lookupResultSuccess = lookupResult.asLookupResultSuccess();
diff --git a/src/test/java/com/android/tools/r8/retrace/RetraceCommandLineTests.java b/src/test/java/com/android/tools/r8/retrace/RetraceCommandLineTests.java
index ec3fcc1..f2dc303 100644
--- a/src/test/java/com/android/tools/r8/retrace/RetraceCommandLineTests.java
+++ b/src/test/java/com/android/tools/r8/retrace/RetraceCommandLineTests.java
@@ -11,6 +11,7 @@
 
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.ToolHelper.ProcessResult;
+import com.android.tools.r8.Version;
 import com.android.tools.r8.retrace.stacktraces.ActualRetraceBotStackTrace;
 import com.android.tools.r8.retrace.stacktraces.ActualRetraceBotStackTraceWithInfo;
 import com.android.tools.r8.retrace.stacktraces.FoundMethodVerboseStackTrace;
@@ -130,6 +131,13 @@
   }
 
   @Test
+  public void testVersion() throws Exception {
+    ProcessResult processResult = runRetraceCommandLine(null, Arrays.asList("--version"));
+    assertEquals(0, processResult.exitCode);
+    assertEquals(StringUtils.lines("Retrace " + Version.getVersionString()), processResult.stdout);
+  }
+
+  @Test
   public void testNonAscii() throws IOException {
     runTest("", SMILEY_EMOJI, false, SMILEY_EMOJI + StringUtils.LINE_SEPARATOR);
   }
diff --git a/src/test/java/com/android/tools/r8/rewrite/enums/EnumOptimizationTest.java b/src/test/java/com/android/tools/r8/rewrite/enums/EnumOptimizationTest.java
index 1bf5f83..ae4947a 100644
--- a/src/test/java/com/android/tools/r8/rewrite/enums/EnumOptimizationTest.java
+++ b/src/test/java/com/android/tools/r8/rewrite/enums/EnumOptimizationTest.java
@@ -20,7 +20,6 @@
 import com.android.tools.r8.utils.codeinspector.MethodSubject;
 import java.util.List;
 import java.util.Objects;
-import org.junit.Assume;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
@@ -75,14 +74,13 @@
     if (enableOptimization) {
       assertOrdinalReplacedWithConst(clazz.uniqueMethodWithName("simple"), 1);
       assertOrdinalReplacedWithConst(clazz.uniqueMethodWithName("local"), 1);
-      // TODO(b/160667929): Re-enable name()/toString() optimizations.
       // String concatenation optimization is enabled for DEX output.
       // Even replaced ordinal is concatenated (and gone).
-      //      if (parameters.isDexRuntime()) {
-      //        assertOrdinalReplacedAndGone(clazz.uniqueMethodWithName("multipleUsages"));
-      //      } else {
-      assertOrdinalReplacedWithConst(clazz.uniqueMethodWithName("multipleUsages"), 1);
-      //      }
+      if (parameters.isDexRuntime()) {
+        assertOrdinalReplacedAndGone(clazz.uniqueMethodWithName("multipleUsages"));
+      } else {
+        assertOrdinalReplacedWithConst(clazz.uniqueMethodWithName("multipleUsages"), 1);
+      }
       assertOrdinalReplacedWithConst(clazz.uniqueMethodWithName("inlined"), 1);
       assertOrdinalReplacedWithConst(clazz.uniqueMethodWithName("inSwitch"), 11);
       assertOrdinalReplacedWithConst(clazz.uniqueMethodWithName("differentTypeStaticField"), 1);
@@ -123,7 +121,6 @@
     assertTrue(clazz.isPresent());
 
     if (enableOptimization) {
-      Assume.assumeTrue("TODO(b/160667929): Re-enable name()/toString() optimizations.", false);
       assertNameReplacedWithConst(clazz.uniqueMethodWithName("simple"), "TWO");
       assertNameReplacedWithConst(clazz.uniqueMethodWithName("local"), "TWO");
       // String concatenation optimization is enabled for DEX output.
@@ -174,7 +171,6 @@
     assertToStringWasNotReplaced(clazz.uniqueMethodWithName("valueWithoutToString"));
 
     if (enableOptimization) {
-      Assume.assumeTrue("TODO(b/160667929): Re-enable name()/toString() optimizations.", false);
       assertToStringReplacedWithConst(clazz.uniqueMethodWithName("noToString"), "TWO");
       assertToStringReplacedWithConst(clazz.uniqueMethodWithName("local"), "TWO");
       assertToStringReplacedWithConst(clazz.uniqueMethodWithName("multipleUsages"), "TWO");
diff --git a/src/test/java/com/android/tools/r8/shaking/R8Shaking2LookupTest.java b/src/test/java/com/android/tools/r8/shaking/R8Shaking2LookupTest.java
index 35f67b2..0040143 100644
--- a/src/test/java/com/android/tools/r8/shaking/R8Shaking2LookupTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/R8Shaking2LookupTest.java
@@ -9,6 +9,7 @@
 
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
+import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.DirectMappedDexApplication;
@@ -31,8 +32,9 @@
   public void readApp() throws IOException, ExecutionException {
     program = ToolHelper.buildApplication(ImmutableList.of(APP_FILE_NAME));
     dexItemFactory = program.dexItemFactory;
-    appInfo = new AppInfoWithClassHierarchy(program);
-    subtypingInfo = new SubtypingInfo(program.allClasses(), program);
+    AppView<AppInfoWithClassHierarchy> appView = AppView.createForR8(program);
+    appInfo = appView.appInfo();
+    subtypingInfo = new SubtypingInfo(appView);
   }
 
   private void validateSubtype(DexType super_type, DexType sub_type) {
diff --git a/src/test/java/com/android/tools/r8/shaking/horizontalclassmerging/IdenticalFieldMembersTest.java b/src/test/java/com/android/tools/r8/shaking/horizontalclassmerging/IdenticalFieldMembersTest.java
new file mode 100644
index 0000000..456c4cb
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/horizontalclassmerging/IdenticalFieldMembersTest.java
@@ -0,0 +1,100 @@
+// 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.shaking.horizontalclassmerging;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.*;
+import com.android.tools.r8.utils.BooleanUtils;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class IdenticalFieldMembersTest extends TestBase {
+  private final TestParameters parameters;
+  private final boolean enableHorizontalClassMerging;
+
+  public IdenticalFieldMembersTest(
+      TestParameters parameters, boolean enableHorizontalClassMerging) {
+    this.parameters = parameters;
+    this.enableHorizontalClassMerging = enableHorizontalClassMerging;
+  }
+
+  @Parameterized.Parameters(name = "{0}, horizontalClassMerging:{1}")
+  public static List<Object[]> data() {
+    return buildParameters(
+        getTestParameters().withAllRuntimesAndApiLevels().build(), BooleanUtils.values());
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(IdenticalFieldMembersTest.class)
+        .addKeepMainRule(Main.class)
+        .addOptionsModification(
+            options -> options.enableHorizontalClassMerging = enableHorizontalClassMerging)
+        .enableInliningAnnotations()
+        .enableNeverClassInliningAnnotations()
+        .setMinApi(parameters.getApiLevel())
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines("foo A", "bar B")
+        .inspect(
+            codeInspector -> {
+              if (enableHorizontalClassMerging) {
+                // TODO(b/163311975): A and B should be merged
+                //
+                //                        Class[] classes = {A.class, B.class};
+                //                        assertEquals(1, Arrays.stream(classes)
+                //                                .filter(a -> codeInspector.clazz(a).isPresent())
+                //                                .count());
+                assertThat(codeInspector.clazz(A.class), isPresent());
+                assertThat(codeInspector.clazz(B.class), isPresent());
+              } else {
+                assertThat(codeInspector.clazz(A.class), isPresent());
+                assertThat(codeInspector.clazz(B.class), isPresent());
+              }
+            });
+  }
+
+  @NeverClassInline
+  public static class A {
+    private String field;
+
+    public A(String v) {
+      this.field = v;
+    }
+
+    @NeverInline
+    public void foo() {
+      System.out.println("foo " + field);
+    }
+  }
+
+  @NeverClassInline
+  public static class B {
+    private String field;
+
+    public B(String v) {
+      this.field = v;
+    }
+
+    @NeverInline
+    public void bar() {
+      System.out.println("bar " + field);
+    }
+  }
+
+  public static class Main {
+    public static void main(String[] args) {
+      A a = new A("A");
+      a.foo();
+      B b = new B("B");
+      b.bar();
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/horizontalclassmerging/NoFieldMembersTest.java b/src/test/java/com/android/tools/r8/shaking/horizontalclassmerging/NoFieldMembersTest.java
new file mode 100644
index 0000000..8dfe240
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/horizontalclassmerging/NoFieldMembersTest.java
@@ -0,0 +1,87 @@
+// 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.shaking.horizontalclassmerging;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.*;
+import com.android.tools.r8.utils.BooleanUtils;
+import java.util.*;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class NoFieldMembersTest extends TestBase {
+  private final TestParameters parameters;
+  private final boolean enableHorizontalClassMerging;
+
+  public NoFieldMembersTest(TestParameters parameters, boolean enableHorizontalClassMerging) {
+    this.parameters = parameters;
+    this.enableHorizontalClassMerging = enableHorizontalClassMerging;
+  }
+
+  @Parameterized.Parameters(name = "{0}, horizontalClassMerging:{1}")
+  public static List<Object[]> data() {
+    return buildParameters(
+        getTestParameters().withAllRuntimesAndApiLevels().build(), BooleanUtils.values());
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(NoFieldMembersTest.class)
+        .addKeepMainRule(Main.class)
+        .addOptionsModification(
+            options -> options.enableHorizontalClassMerging = enableHorizontalClassMerging)
+        .enableInliningAnnotations()
+        .enableNeverClassInliningAnnotations()
+        .setMinApi(parameters.getApiLevel())
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines("foo", "bar")
+        .inspect(
+            codeInspector -> {
+              if (enableHorizontalClassMerging) {
+                // TODO(b/163311975): A and B should be merged
+                //
+                //                        Class[] classes = { A.class, B.class };
+                //                        assertEquals(1, Arrays.stream(classes)
+                //                                .filter(a -> codeInspector.clazz(a).isPresent())
+                //                                .count());
+                assertThat(codeInspector.clazz(A.class), isPresent());
+                assertThat(codeInspector.clazz(B.class), isPresent());
+              } else {
+                assertThat(codeInspector.clazz(A.class), isPresent());
+                assertThat(codeInspector.clazz(B.class), isPresent());
+              }
+            });
+  }
+
+  @NeverClassInline
+  public static class A {
+    @NeverInline
+    public void foo() {
+      System.out.println("foo");
+    }
+  }
+
+  @NeverClassInline
+  public static class B {
+    @NeverInline
+    public void bar() {
+      System.out.println("bar");
+    }
+  }
+
+  public static class Main {
+    public static void main(String[] args) {
+      A a = new A();
+      a.foo();
+      B b = new B();
+      b.bar();
+    }
+  }
+}
diff --git a/tools/compiledump.py b/tools/compiledump.py
index 2dcfeb6..5b92c70 100755
--- a/tools/compiledump.py
+++ b/tools/compiledump.py
@@ -77,6 +77,11 @@
       '--min-api',
       help='Set min-api (default read from dump properties file)',
       default=None)
+  parser.add_argument(
+    '--loop',
+    help='Run the compilation in a loop',
+    default=False,
+    action='store_true')
   return parser
 
 def error(msg):
@@ -201,10 +206,10 @@
 def is_hash(version):
   return len(version) == 40
 
-def run(args, otherargs):
+def run1(out, args, otherargs):
   with utils.TempDir() as temp:
-    if args.temp:
-      temp = args.temp
+    if out:
+      temp = out
       if not os.path.exists(temp):
         os.makedirs(temp)
     dump = read_dump(args, temp)
@@ -273,6 +278,19 @@
         retrace.run(local_map, hash_or_version, stacktrace, is_hash(version))
       return 1
 
+def run(args, otherargs):
+  if (args.loop):
+    count = 1
+    while True:
+      print('Iteration {:03d}'.format(count))
+      out = args.temp
+      if out:
+        out = os.path.join(out, '{:03d}'.format(count))
+      run1(out, args, otherargs)
+      count += 1
+  else:
+    run1(args.temp, args, otherargs)
+
 if __name__ == '__main__':
   (args, otherargs) = make_parser().parse_known_args(sys.argv[1:])
   sys.exit(run(args, otherargs))
diff --git a/tools/r8_release.py b/tools/r8_release.py
index 0612c36..3641f0c 100755
--- a/tools/r8_release.py
+++ b/tools/r8_release.py
@@ -210,7 +210,7 @@
 
     git_message = ("""Update D8 and R8 to %s
 
-Version: master %s
+Version: %s
 This build IS NOT suitable for preview or public release.
 
 Built here: go/r8-releases/raw/%s
@@ -249,7 +249,7 @@
   return release_maven
 
 def git_message_dev(version):
-  return """Update D8 R8 master to %s
+  return """Update D8 R8 to %s
 
 This is a development snapshot, it's fine to use for studio canary build, but
 not for BETA or release, for those we would need a release version of R8