Merge commit '77627c90b270921a32af4e2d1c057e4f885e5928' into dev-release
diff --git a/.gitignore b/.gitignore
index e64324a..76604f8 100644
--- a/.gitignore
+++ b/.gitignore
@@ -133,6 +133,10 @@
 third_party/r8.tar.gz
 third_party/r8mappings
 third_party/r8mappings.tar.gz
+third_party/remapper
+third_party/remapper.tar.gz
+third_party/retrace_benchmark
+third_party/retrace_benchmark.tar.gz
 third_party/rhino-1.7.10
 third_party/rhino-1.7.10.tar.gz
 third_party/rhino-android-1.1.1
diff --git a/src/main/java/com/android/tools/r8/BaseCompilerCommand.java b/src/main/java/com/android/tools/r8/BaseCompilerCommand.java
index 8323666..80dcf13 100644
--- a/src/main/java/com/android/tools/r8/BaseCompilerCommand.java
+++ b/src/main/java/com/android/tools/r8/BaseCompilerCommand.java
@@ -6,6 +6,7 @@
 import com.android.tools.r8.errors.CompilationError;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.inspector.Inspector;
 import com.android.tools.r8.ir.desugar.DesugaredLibraryConfiguration;
 import com.android.tools.r8.ir.desugar.DesugaredLibraryConfigurationParser;
 import com.android.tools.r8.origin.Origin;
@@ -14,10 +15,14 @@
 import com.android.tools.r8.utils.FileUtils;
 import com.android.tools.r8.utils.InternalOptions.DesugarState;
 import com.android.tools.r8.utils.Reporter;
+import com.android.tools.r8.utils.ThreadUtils;
 import java.nio.file.Path;
 import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
 import java.util.List;
 import java.util.function.BiPredicate;
+import java.util.function.Consumer;
 import java.util.function.Function;
 
 /**
@@ -41,6 +46,8 @@
   private final boolean optimizeMultidexForLinearAlloc;
   private final BiPredicate<String, Long> dexClassChecksumFilter;
   private final List<AssertionsConfiguration> assertionsConfiguration;
+  private final List<Consumer<Inspector>> outputInspections;
+  private int threadCount;
 
   BaseCompilerCommand(boolean printHelp, boolean printVersion) {
     super(printHelp, printVersion);
@@ -54,6 +61,8 @@
     optimizeMultidexForLinearAlloc = false;
     dexClassChecksumFilter = (name, checksum) -> true;
     assertionsConfiguration = new ArrayList<>();
+    outputInspections = null;
+    threadCount = ThreadUtils.NOT_SPECIFIED;
   }
 
   BaseCompilerCommand(
@@ -67,7 +76,9 @@
       boolean optimizeMultidexForLinearAlloc,
       boolean includeClassesChecksum,
       BiPredicate<String, Long> dexClassChecksumFilter,
-      List<AssertionsConfiguration> assertionsConfiguration) {
+      List<AssertionsConfiguration> assertionsConfiguration,
+      List<Consumer<Inspector>> outputInspections,
+      int threadCount) {
     super(app);
     assert minApiLevel > 0;
     assert mode != null;
@@ -81,6 +92,8 @@
     this.includeClassesChecksum = includeClassesChecksum;
     this.dexClassChecksumFilter = dexClassChecksumFilter;
     this.assertionsConfiguration = assertionsConfiguration;
+    this.outputInspections = outputInspections;
+    this.threadCount = threadCount;
   }
 
   /**
@@ -140,7 +153,16 @@
   }
 
   public List<AssertionsConfiguration> getAssertionsConfiguration() {
-    return assertionsConfiguration;
+    return Collections.unmodifiableList(assertionsConfiguration);
+  }
+
+  public Collection<Consumer<Inspector>> getOutputInspections() {
+    return Collections.unmodifiableList(outputInspections);
+  }
+
+  /** Get the number of threads to use for the compilation. */
+  public int getThreadCount() {
+    return threadCount;
   }
 
   Reporter getReporter() {
@@ -166,6 +188,7 @@
 
     private CompilationMode mode;
     private int minApiLevel = 0;
+    private int threadCount = ThreadUtils.NOT_SPECIFIED;
     protected DesugarState desugarState = DesugarState.ON;
     private List<StringResource> desugaredLibraryConfigurationResources = new ArrayList<>();
     private boolean includeClassesChecksum = false;
@@ -173,6 +196,7 @@
     private boolean optimizeMultidexForLinearAlloc = false;
     private BiPredicate<String, Long> dexClassChecksumFilter = (name, checksum) -> true;
     private List<AssertionsConfiguration> assertionsConfiguration = new ArrayList<>();
+    private List<Consumer<Inspector>> outputInspections = new ArrayList<>();
 
     abstract CompilationMode defaultCompilationMode();
 
@@ -490,6 +514,20 @@
       return self();
     }
 
+    /** Set the number of threads to use for the compilation */
+    B setThreadCount(int threadCount) {
+      if (threadCount <= 0) {
+        getReporter().error("Invalid threadCount: " + threadCount);
+      } else {
+        this.threadCount = threadCount;
+      }
+      return self();
+    }
+
+    int getThreadCount() {
+      return threadCount;
+    }
+
     /** Encodes the checksums into the dex output. */
     public boolean getIncludeClassesChecksum() {
       return includeClassesChecksum;
@@ -552,5 +590,31 @@
       }
       super.validate();
     }
+
+    /**
+     * Add an inspection of the output program.
+     *
+     * <p>On a successful compilation the inspection is guaranteed to be called with inspectors that
+     * combined cover all of the output program. The inspections may be called multiple times with
+     * inspectors that have overlapping content (eg, classes synthesized based on multiple inputs
+     * can lead to this). Any overlapping content will be consistent, e.g., the inspection of type T
+     * will be the same (equality, not identify) as any other inspection of type T.
+     *
+     * <p>There is no guarantee of the order inspections are called or on which thread they are
+     * called.
+     *
+     * <p>The validity of an {@code Inspector} and all of its sub-inspectors, eg,
+     * {@MethodInspector}, is that of the callback. If any inspector object escapes the scope of the
+     * callback, the behavior of that inspector is undefined.
+     *
+     * @param inspection Inspection callback receiving inspectors denoting parts of the output.
+     */
+    public void addOutputInspection(Consumer<Inspector> inspection) {
+      outputInspections.add(inspection);
+    }
+
+    List<Consumer<Inspector>> getOutputInspections() {
+      return outputInspections;
+    }
   }
 }
diff --git a/src/main/java/com/android/tools/r8/BaseCompilerCommandParser.java b/src/main/java/com/android/tools/r8/BaseCompilerCommandParser.java
index dbfe6b7..410441b 100644
--- a/src/main/java/com/android/tools/r8/BaseCompilerCommandParser.java
+++ b/src/main/java/com/android/tools/r8/BaseCompilerCommandParser.java
@@ -12,10 +12,14 @@
 import java.nio.file.Path;
 import java.nio.file.Paths;
 import java.util.Arrays;
+import java.util.function.Consumer;
 
 public class BaseCompilerCommandParser<
     C extends BaseCompilerCommand, B extends BaseCompilerCommand.Builder<C, B>> {
 
+  protected static final String MIN_API_FLAG = "--min-api";
+  protected static final String THREAD_COUNT_FLAG = "--thread-count";
+
   static final Iterable<String> ASSERTIONS_USAGE_MESSAGE =
       Arrays.asList(
           "  --force-enable-assertions[:[<class name>|<package name>...]]",
@@ -32,19 +36,20 @@
           "                          # is the default handling of javac assertion code when",
           "                          # generating class file format.");
 
-  void parseMinApi(B builder, String minApiString, Origin origin) {
-    int minApi;
+  void parsePositiveIntArgument(
+      B builder, String flag, String argument, Origin origin, Consumer<Integer> setter) {
+    int value;
     try {
-      minApi = Integer.parseInt(minApiString);
+      value = Integer.parseInt(argument);
     } catch (NumberFormatException e) {
-      builder.error(new StringDiagnostic("Invalid argument to --min-api: " + minApiString, origin));
+      builder.error(new StringDiagnostic("Invalid argument to " + flag + ": " + argument, origin));
       return;
     }
-    if (minApi < 1) {
-      builder.error(new StringDiagnostic("Invalid argument to --min-api: " + minApiString, origin));
+    if (value < 1) {
+      builder.error(new StringDiagnostic("Invalid argument to " + flag + ": " + argument, origin));
       return;
     }
-    builder.setMinApiLevel(minApi);
+    setter.accept(value);
   }
 
   private static String PACKAGE_ASSERTION_POSTFIX = "...";
diff --git a/src/main/java/com/android/tools/r8/D8.java b/src/main/java/com/android/tools/r8/D8.java
index 1fa0bb0..1ce0024 100644
--- a/src/main/java/com/android/tools/r8/D8.java
+++ b/src/main/java/com/android/tools/r8/D8.java
@@ -16,7 +16,9 @@
 import com.android.tools.r8.graph.DexApplication;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.GraphLense;
+import com.android.tools.r8.graph.InitClassLens;
 import com.android.tools.r8.graph.analysis.ClassInitializerAssertionEnablingAnalysis;
+import com.android.tools.r8.inspector.internal.InspectorImpl;
 import com.android.tools.r8.ir.conversion.IRConverter;
 import com.android.tools.r8.ir.desugar.PrefixRewritingMapper;
 import com.android.tools.r8.ir.optimize.AssertionsRewriter;
@@ -220,6 +222,7 @@
         markers.add(marker);
       }
 
+      InspectorImpl.runInspections(options.outputInspections, app);
       if (options.isGeneratingClassFiles()) {
         new CfApplicationWriter(
                 app,
@@ -237,6 +240,7 @@
                 options,
                 marker == null ? null : ImmutableList.copyOf(markers),
                 GraphLense.getIdentityLense(),
+                InitClassLens.getDefault(),
                 PrefixRewritingNamingLens.createPrefixRewritingNamingLens(appView),
                 null)
             .write(executor);
diff --git a/src/main/java/com/android/tools/r8/D8Command.java b/src/main/java/com/android/tools/r8/D8Command.java
index b7c9347..92f194a 100644
--- a/src/main/java/com/android/tools/r8/D8Command.java
+++ b/src/main/java/com/android/tools/r8/D8Command.java
@@ -6,6 +6,8 @@
 import com.android.tools.r8.AssertionsConfiguration.AssertionTransformation;
 import com.android.tools.r8.errors.DexFileOverflowDiagnostic;
 import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.inspector.Inspector;
+import com.android.tools.r8.inspector.internal.InspectorImpl;
 import com.android.tools.r8.ir.desugar.DesugaredLibraryConfiguration;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.utils.AndroidApiLevel;
@@ -15,10 +17,12 @@
 import com.android.tools.r8.utils.InternalOptions.DesugarState;
 import com.android.tools.r8.utils.Reporter;
 import com.android.tools.r8.utils.StringDiagnostic;
+import com.android.tools.r8.utils.ThreadUtils;
 import java.nio.file.Path;
 import java.util.Collection;
 import java.util.List;
 import java.util.function.BiPredicate;
+import java.util.function.Consumer;
 
 /**
  * Immutable command structure for an invocation of the {@link D8} compiler.
@@ -36,13 +40,6 @@
 @Keep
 public final class D8Command extends BaseCompilerCommand {
 
-  private static class ClasspathInputOrigin extends InputFileOrigin {
-
-    public ClasspathInputOrigin(Path file) {
-      super("classpath input", file);
-    }
-  }
-
   private static class DefaultD8DiagnosticsHandler implements DiagnosticsHandler {
 
     @Override
@@ -229,6 +226,8 @@
           desugaredLibraryKeepRuleConsumer,
           libraryConfiguration,
           getAssertionsConfiguration(),
+          getOutputInspections(),
+          getThreadCount(),
           factory);
     }
   }
@@ -297,6 +296,8 @@
       StringConsumer desugaredLibraryKeepRuleConsumer,
       DesugaredLibraryConfiguration libraryConfiguration,
       List<AssertionsConfiguration> assertionsConfiguration,
+      List<Consumer<Inspector>> outputInspections,
+      int threadCount,
       DexItemFactory factory) {
     super(
         inputApp,
@@ -309,7 +310,9 @@
         optimizeMultidexForLinearAlloc,
         encodeChecksum,
         dexClassChecksumFilter,
-        assertionsConfiguration);
+        assertionsConfiguration,
+        outputInspections,
+        threadCount);
     this.intermediate = intermediate;
     this.desugarGraphConsumer = desugarGraphConsumer;
     this.desugaredLibraryKeepRuleConsumer = desugaredLibraryKeepRuleConsumer;
@@ -379,6 +382,11 @@
         new AssertionConfigurationWithDefault(
             AssertionTransformation.DISABLE, getAssertionsConfiguration());
 
+    internal.outputInspections = InspectorImpl.wrapInspections(getOutputInspections());
+
+    assert internal.threadCount == ThreadUtils.NOT_SPECIFIED;
+    internal.threadCount = getThreadCount();
+
     return internal;
   }
 }
diff --git a/src/main/java/com/android/tools/r8/D8CommandParser.java b/src/main/java/com/android/tools/r8/D8CommandParser.java
index a6827d3..e9df325 100644
--- a/src/main/java/com/android/tools/r8/D8CommandParser.java
+++ b/src/main/java/com/android/tools/r8/D8CommandParser.java
@@ -29,10 +29,11 @@
           "--output",
           "--lib",
           "--classpath",
-          "--min-api",
+          MIN_API_FLAG,
           "--main-dex-list",
           "--main-dex-list-output",
-          "--desugared-lib");
+          "--desugared-lib",
+          THREAD_COUNT_FLAG);
 
   private static final String APK_EXTENSION = ".apk";
   private static final String JAR_EXTENSION = ".jar";
@@ -121,7 +122,10 @@
                   "                          # <file> must be an existing directory or a zip file.",
                   "  --lib <file|jdk-home>   # Add <file|jdk-home> as a library resource.",
                   "  --classpath <file>      # Add <file> as a classpath resource.",
-                  "  --min-api <number>      # Minimum Android API level compatibility, default: "
+                  "  "
+                      + MIN_API_FLAG
+                      + " <number>      "
+                      + "# Minimum Android API level compatibility, default: "
                       + AndroidApiLevel.getDefault().getLevel()
                       + ".",
                   "  --intermediate          # Compile an intermediate result intended for later",
@@ -243,13 +247,17 @@
         builder.setMainDexListOutputPath(Paths.get(nextArg));
       } else if (arg.equals("--optimize-multidex-for-linearalloc")) {
         builder.setOptimizeMultidexForLinearAlloc(true);
-      } else if (arg.equals("--min-api")) {
+      } else if (arg.equals(MIN_API_FLAG)) {
         if (hasDefinedApiLevel) {
-          builder.error(new StringDiagnostic("Cannot set multiple --min-api options", origin));
+          builder.error(
+              new StringDiagnostic("Cannot set multiple " + MIN_API_FLAG + " options", origin));
         } else {
-          parseMinApi(builder, nextArg, origin);
+          parsePositiveIntArgument(builder, MIN_API_FLAG, nextArg, origin, builder::setMinApiLevel);
           hasDefinedApiLevel = true;
         }
+      } else if (arg.equals(THREAD_COUNT_FLAG)) {
+        parsePositiveIntArgument(
+            builder, THREAD_COUNT_FLAG, nextArg, origin, builder::setThreadCount);
       } else if (arg.equals("--intermediate")) {
         builder.setIntermediate(true);
       } else if (arg.equals("--no-desugaring")) {
diff --git a/src/main/java/com/android/tools/r8/DexFileMergerHelper.java b/src/main/java/com/android/tools/r8/DexFileMergerHelper.java
index 2509e12..94ea634 100644
--- a/src/main/java/com/android/tools/r8/DexFileMergerHelper.java
+++ b/src/main/java/com/android/tools/r8/DexFileMergerHelper.java
@@ -13,6 +13,7 @@
 import com.android.tools.r8.graph.DexApplication;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.GraphLense;
+import com.android.tools.r8.graph.InitClassLens;
 import com.android.tools.r8.naming.NamingLens;
 import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.ExceptionUtils;
@@ -102,6 +103,7 @@
                 options,
                 markers,
                 GraphLense.getIdentityLense(),
+                InitClassLens.getDefault(),
                 NamingLens.getIdentityLens(),
                 null);
         writer.write(executor);
diff --git a/src/main/java/com/android/tools/r8/DexSplitterHelper.java b/src/main/java/com/android/tools/r8/DexSplitterHelper.java
index 7ec9143..a9d4644 100644
--- a/src/main/java/com/android/tools/r8/DexSplitterHelper.java
+++ b/src/main/java/com/android/tools/r8/DexSplitterHelper.java
@@ -14,6 +14,7 @@
 import com.android.tools.r8.graph.DexApplication;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.GraphLense;
+import com.android.tools.r8.graph.InitClassLens;
 import com.android.tools.r8.graph.LazyLoadedDexApplication;
 import com.android.tools.r8.naming.ClassNameMapper;
 import com.android.tools.r8.naming.NamingLens;
@@ -82,8 +83,6 @@
           getDistribution(app, featureClassMapping, mapper);
       for (Entry<String, LazyLoadedDexApplication.Builder> entry : applications.entrySet()) {
         DexApplication featureApp = entry.getValue().build();
-        // We use the same factory, reset sorting.
-        featureApp.dexItemFactory.resetSortedIndices();
         assert !options.hasMethodsFilter();
 
         // Run d8 optimize to ensure jumbo strings are handled.
@@ -103,6 +102,7 @@
                   options,
                   markers,
                   GraphLense.getIdentityLense(),
+                  InitClassLens.getDefault(),
                   NamingLens.getIdentityLens(),
                   null,
                   consumer)
diff --git a/src/main/java/com/android/tools/r8/L8Command.java b/src/main/java/com/android/tools/r8/L8Command.java
index 9db1c33..c32fbb7 100644
--- a/src/main/java/com/android/tools/r8/L8Command.java
+++ b/src/main/java/com/android/tools/r8/L8Command.java
@@ -7,6 +7,7 @@
 import com.android.tools.r8.ProgramResource.Kind;
 import com.android.tools.r8.errors.DexFileOverflowDiagnostic;
 import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.inspector.Inspector;
 import com.android.tools.r8.ir.desugar.DesugaredLibraryConfiguration;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.utils.AndroidApp;
@@ -16,11 +17,13 @@
 import com.android.tools.r8.utils.Pair;
 import com.android.tools.r8.utils.Reporter;
 import com.android.tools.r8.utils.StringDiagnostic;
+import com.android.tools.r8.utils.ThreadUtils;
 import java.nio.file.Path;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.List;
+import java.util.function.Consumer;
 
 /** Immutable command structure for an invocation of the {@link L8} library compiler. */
 @Keep
@@ -83,6 +86,8 @@
       Reporter diagnosticsHandler,
       DesugaredLibraryConfiguration libraryConfiguration,
       List<AssertionsConfiguration> assertionsConfiguration,
+      List<Consumer<Inspector>> outputInspections,
+      int threadCount,
       DexItemFactory factory) {
     super(
         inputApp,
@@ -95,7 +100,9 @@
         false,
         false,
         (name, checksum) -> true,
-        assertionsConfiguration);
+        assertionsConfiguration,
+        outputInspections,
+        threadCount);
     this.d8Command = d8Command;
     this.r8Command = r8Command;
     this.libraryConfiguration = libraryConfiguration;
@@ -180,6 +187,9 @@
         new AssertionConfigurationWithDefault(
             AssertionTransformation.DISABLE, getAssertionsConfiguration());
 
+    assert internal.threadCount == ThreadUtils.NOT_SPECIFIED;
+    internal.threadCount = getThreadCount();
+
     return internal;
   }
 
@@ -317,6 +327,8 @@
           getReporter(),
           libraryConfiguration,
           getAssertionsConfiguration(),
+          getOutputInspections(),
+          getThreadCount(),
           factory);
     }
   }
diff --git a/src/main/java/com/android/tools/r8/L8CommandParser.java b/src/main/java/com/android/tools/r8/L8CommandParser.java
index bf35b67..976fb4a 100644
--- a/src/main/java/com/android/tools/r8/L8CommandParser.java
+++ b/src/main/java/com/android/tools/r8/L8CommandParser.java
@@ -19,7 +19,7 @@
 public class L8CommandParser extends BaseCompilerCommandParser<L8Command, L8Command.Builder> {
 
   private static final Set<String> OPTIONS_WITH_PARAMETER =
-      ImmutableSet.of("--output", "--lib", "--min-api", "--desugared-lib");
+      ImmutableSet.of("--output", "--lib", MIN_API_FLAG, "--desugared-lib", THREAD_COUNT_FLAG);
 
   public static void main(String[] args) throws CompilationFailedException {
     L8Command command = parse(args, Origin.root()).build();
@@ -43,7 +43,10 @@
                   "  --output <file>         # Output result in <outfile>.",
                   "                          # <file> must be an existing directory or a zip file.",
                   "  --lib <file|jdk-home>   # Add <file|jdk-home> as a library resource.",
-                  "  --min-api <number>      # Minimum Android API level compatibility, default: "
+                  "  "
+                      + MIN_API_FLAG
+                      + " <number>      "
+                      + "# Minimum Android API level compatibility, default: "
                       + AndroidApiLevel.getDefault().getLevel()
                       + ".",
                   "  --pg-conf <file>        # Proguard configuration <file>.",
@@ -131,11 +134,12 @@
           continue;
         }
         outputPath = Paths.get(nextArg);
-      } else if (arg.equals("--min-api")) {
+      } else if (arg.equals(MIN_API_FLAG)) {
         if (hasDefinedApiLevel) {
-          builder.error(new StringDiagnostic("Cannot set multiple --min-api options", origin));
+          builder.error(
+              new StringDiagnostic("Cannot set multiple " + MIN_API_FLAG + " options", origin));
         } else {
-          parseMinApi(builder, nextArg, origin);
+          parsePositiveIntArgument(builder, MIN_API_FLAG, nextArg, origin, builder::setMinApiLevel);
           hasDefinedApiLevel = true;
         }
       } else if (arg.equals("--lib")) {
@@ -144,6 +148,9 @@
         builder.addProguardConfigurationFiles(Paths.get(nextArg));
       } else if (arg.equals("--desugared-lib")) {
         builder.addDesugaredLibraryConfiguration(StringResource.fromFile(Paths.get(nextArg)));
+      } else if (arg.equals(THREAD_COUNT_FLAG)) {
+        parsePositiveIntArgument(
+            builder, THREAD_COUNT_FLAG, nextArg, origin, builder::setThreadCount);
       } else if (arg.startsWith("--")) {
         if (!tryParseAssertionArgument(builder, arg, origin)) {
           builder.error(new StringDiagnostic("Unknown option: " + arg, origin));
diff --git a/src/main/java/com/android/tools/r8/PrintUses.java b/src/main/java/com/android/tools/r8/PrintUses.java
index d492e67..57f8395 100644
--- a/src/main/java/com/android/tools/r8/PrintUses.java
+++ b/src/main/java/com/android/tools/r8/PrintUses.java
@@ -91,6 +91,12 @@
     }
 
     @Override
+    public boolean registerInitClass(DexType clazz) {
+      addType(clazz);
+      return false;
+    }
+
+    @Override
     public boolean registerInvokeVirtual(DexMethod method) {
       ResolutionResult resolutionResult = appInfo.resolveMethod(method.holder, method);
       DexEncodedMethod target =
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java
index 8817968..5223520 100644
--- a/src/main/java/com/android/tools/r8/R8.java
+++ b/src/main/java/com/android/tools/r8/R8.java
@@ -28,8 +28,10 @@
 import com.android.tools.r8.graph.EnumValueInfoMapCollection;
 import com.android.tools.r8.graph.GraphLense;
 import com.android.tools.r8.graph.GraphLense.NestedGraphLense;
+import com.android.tools.r8.graph.InitClassLens;
 import com.android.tools.r8.graph.analysis.ClassInitializerAssertionEnablingAnalysis;
 import com.android.tools.r8.graph.analysis.InitializedClassesInInstanceMethodsAnalysis;
+import com.android.tools.r8.inspector.internal.InspectorImpl;
 import com.android.tools.r8.ir.analysis.proto.GeneratedExtensionRegistryShrinker;
 import com.android.tools.r8.ir.conversion.IRConverter;
 import com.android.tools.r8.ir.desugar.BackportedMethodRewriter;
@@ -46,9 +48,7 @@
 import com.android.tools.r8.ir.optimize.enums.EnumValueInfoMapCollector;
 import com.android.tools.r8.ir.optimize.info.OptimizationFeedbackSimple;
 import com.android.tools.r8.jar.CfApplicationWriter;
-import com.android.tools.r8.kotlin.Kotlin;
-import com.android.tools.r8.kotlin.KotlinInfo;
-import com.android.tools.r8.kotlin.KotlinMemberInfo;
+import com.android.tools.r8.kotlin.KotlinInfoCollector;
 import com.android.tools.r8.logging.Log;
 import com.android.tools.r8.naming.ClassNameMapper;
 import com.android.tools.r8.naming.Minifier;
@@ -66,6 +66,7 @@
 import com.android.tools.r8.shaking.AbstractMethodRemover;
 import com.android.tools.r8.shaking.AnnotationRemover;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.shaking.ClassInitFieldSynthesizer;
 import com.android.tools.r8.shaking.DefaultTreePrunerConfiguration;
 import com.android.tools.r8.shaking.DiscardedChecker;
 import com.android.tools.r8.shaking.Enqueuer;
@@ -91,7 +92,6 @@
 import com.android.tools.r8.utils.ExceptionUtils;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.LineNumberOptimizer;
-import com.android.tools.r8.utils.Reporter;
 import com.android.tools.r8.utils.SelfRetraceTest;
 import com.android.tools.r8.utils.StringDiagnostic;
 import com.android.tools.r8.utils.ThreadUtils;
@@ -155,7 +155,6 @@
       System.gc();
     }
     timing = Timing.create("R8", options);
-    options.itemFactory.resetSortedIndices();
   }
 
   /**
@@ -197,10 +196,12 @@
       DexApplication application,
       AppView<?> appView,
       GraphLense graphLense,
+      InitClassLens initClassLens,
       NamingLens namingLens,
       InternalOptions options,
       ProguardMapSupplier proguardMapSupplier)
       throws ExecutionException {
+    InspectorImpl.runInspections(options.outputInspections, application);
     try {
       Marker marker = options.getMarker(Tool.R8);
       assert marker != null;
@@ -215,6 +216,7 @@
                 options,
                 Collections.singletonList(marker),
                 graphLense,
+                initClassLens,
                 namingLens,
                 proguardMapSupplier)
             .write(executorService);
@@ -309,7 +311,8 @@
 
         // Compute kotlin info before setting the roots and before
         // kotlin metadata annotation is removed.
-        computeKotlinInfoForProgramClasses(application, appView, executorService);
+        KotlinInfoCollector.computeKotlinInfoForProgramClasses(
+            application, appView, executorService);
 
         // Add synthesized -assumenosideeffects from min api if relevant.
         if (options.isGeneratingDex()) {
@@ -613,9 +616,7 @@
 
       appView.setAppInfo(new AppInfoWithSubtyping(application));
 
-      if (options.isShrinking()
-          || options.isMinifying()
-          || options.getProguardConfiguration().hasApplyMappingFile()) {
+      if (options.shouldRerunEnqueuer()) {
         timing.begin("Post optimization code stripping");
         try {
           GraphConsumer keptGraphConsumer = null;
@@ -699,6 +700,9 @@
               // Remove types that no longer exists from the computed main dex list.
               mainDexClasses = mainDexClasses.prunedCopy(appView.appInfo().withLiveness());
             }
+
+            // Synthesize fields for triggering class initializers.
+            new ClassInitFieldSynthesizer(appViewWithLiveness).run(executorService);
           }
         } finally {
           timing.end();
@@ -819,6 +823,7 @@
           application,
           appView,
           appView.graphLense(),
+          appView.initClassLens(),
           PrefixRewritingNamingLens.createPrefixRewritingNamingLens(appView, namingLens),
           options,
           proguardMapSupplier);
@@ -913,24 +918,6 @@
     }
   }
 
-  private void computeKotlinInfoForProgramClasses(
-      DexApplication application, AppView<?> appView, ExecutorService executorService)
-      throws ExecutionException {
-    if (appView.options().kotlinOptimizationOptions().disableKotlinSpecificOptimizations) {
-      return;
-    }
-    Kotlin kotlin = appView.dexItemFactory().kotlin;
-    Reporter reporter = options.reporter;
-    ThreadUtils.processItems(
-        application.classes(),
-        programClass -> {
-          KotlinInfo kotlinInfo = kotlin.getKotlinInfo(programClass, reporter);
-          programClass.setKotlinInfo(kotlinInfo);
-          KotlinMemberInfo.markKotlinMemberInfo(programClass, kotlinInfo, reporter);
-        },
-        executorService);
-  }
-
   private static boolean verifyNoJarApplicationReaders(List<DexProgramClass> classes) {
     for (DexProgramClass clazz : classes) {
       for (DexEncodedMethod method : clazz.methods()) {
diff --git a/src/main/java/com/android/tools/r8/R8Command.java b/src/main/java/com/android/tools/r8/R8Command.java
index 4051f9c..611bc7a 100644
--- a/src/main/java/com/android/tools/r8/R8Command.java
+++ b/src/main/java/com/android/tools/r8/R8Command.java
@@ -9,6 +9,8 @@
 import com.android.tools.r8.experimental.graphinfo.GraphConsumer;
 import com.android.tools.r8.features.FeatureSplitConfiguration;
 import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.inspector.Inspector;
+import com.android.tools.r8.inspector.internal.InspectorImpl;
 import com.android.tools.r8.ir.desugar.DesugaredLibraryConfiguration;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.origin.PathOrigin;
@@ -29,6 +31,7 @@
 import com.android.tools.r8.utils.InternalOptions.LineNumberOptimization;
 import com.android.tools.r8.utils.Reporter;
 import com.android.tools.r8.utils.StringDiagnostic;
+import com.android.tools.r8.utils.ThreadUtils;
 import com.google.common.collect.ImmutableList;
 import java.io.InputStream;
 import java.nio.file.Path;
@@ -567,7 +570,9 @@
               desugaredLibraryKeepRuleConsumer,
               libraryConfiguration,
               featureSplitConfiguration,
-              getAssertionsConfiguration());
+              getAssertionsConfiguration(),
+              getOutputInspections(),
+              getThreadCount());
 
       return command;
     }
@@ -724,7 +729,9 @@
       StringConsumer desugaredLibraryKeepRuleConsumer,
       DesugaredLibraryConfiguration libraryConfiguration,
       FeatureSplitConfiguration featureSplitConfiguration,
-      List<AssertionsConfiguration> assertionsConfiguration) {
+      List<AssertionsConfiguration> assertionsConfiguration,
+      List<Consumer<Inspector>> outputInspections,
+      int threadCount) {
     super(
         inputApp,
         mode,
@@ -736,7 +743,9 @@
         optimizeMultidexForLinearAlloc,
         encodeChecksum,
         dexClassChecksumFilter,
-        assertionsConfiguration);
+        assertionsConfiguration,
+        outputInspections,
+        threadCount);
     assert proguardConfiguration != null;
     assert mainDexKeepRules != null;
     this.mainDexKeepRules = mainDexKeepRules;
@@ -874,6 +883,8 @@
 
     internal.syntheticProguardRulesConsumer = syntheticProguardRulesConsumer;
 
+    internal.outputInspections = InspectorImpl.wrapInspections(getOutputInspections());
+
     // Default is to remove all javac generated assertion code when generating dex.
     assert internal.assertionsConfiguration == null;
     internal.assertionsConfiguration =
@@ -903,6 +914,9 @@
     internal.desugaredLibraryConfiguration = libraryConfiguration;
     internal.desugaredLibraryKeepRuleConsumer = desugaredLibraryKeepRuleConsumer;
 
+    assert internal.threadCount == ThreadUtils.NOT_SPECIFIED;
+    internal.threadCount = getThreadCount();
+
     return internal;
   }
 
diff --git a/src/main/java/com/android/tools/r8/R8CommandParser.java b/src/main/java/com/android/tools/r8/R8CommandParser.java
index 22ebb5e..84aa83e 100644
--- a/src/main/java/com/android/tools/r8/R8CommandParser.java
+++ b/src/main/java/com/android/tools/r8/R8CommandParser.java
@@ -21,13 +21,14 @@
           "--output",
           "--lib",
           "--classpath",
-          "--min-api",
+          MIN_API_FLAG,
           "--main-dex-rules",
           "--main-dex-list",
           "--main-dex-list-output",
           "--pg-conf",
           "--pg-map-output",
-          "--desugared-lib");
+          "--desugared-lib",
+          THREAD_COUNT_FLAG);
 
   public static void main(String[] args) throws CompilationFailedException {
     R8Command command = parse(args, Origin.root()).build();
@@ -64,7 +65,10 @@
                   "                          # <file> must be an existing directory or a zip file.",
                   "  --lib <file|jdk-home>   # Add <file|jdk-home> as a library resource.",
                   "  --classpath <file>      # Add <file> as a classpath resource.",
-                  "  --min-api <number>      # Minimum Android API level compatibility, default: "
+                  "  "
+                      + MIN_API_FLAG
+                      + " <number>      "
+                      + "# Minimum Android API level compatibility, default: "
                       + AndroidApiLevel.getDefault().getLevel()
                       + ".",
                   "  --pg-conf <file>        # Proguard configuration <file>.",
@@ -190,13 +194,18 @@
         addLibraryArgument(builder, argsOrigin, nextArg);
       } else if (arg.equals("--classpath")) {
         builder.addClasspathFiles(Paths.get(nextArg));
-      } else if (arg.equals("--min-api")) {
+      } else if (arg.equals(MIN_API_FLAG)) {
         if (state.hasDefinedApiLevel) {
-          builder.error(new StringDiagnostic("Cannot set multiple --min-api options", argsOrigin));
+          builder.error(
+              new StringDiagnostic("Cannot set multiple " + MIN_API_FLAG + " options", argsOrigin));
         } else {
-          parseMinApi(builder, nextArg, argsOrigin);
+          parsePositiveIntArgument(
+              builder, MIN_API_FLAG, nextArg, argsOrigin, builder::setMinApiLevel);
           state.hasDefinedApiLevel = true;
         }
+      } else if (arg.equals(THREAD_COUNT_FLAG)) {
+        parsePositiveIntArgument(
+            builder, THREAD_COUNT_FLAG, nextArg, argsOrigin, builder::setThreadCount);
       } else if (arg.equals("--no-tree-shaking")) {
         builder.setDisableTreeShaking(true);
       } else if (arg.equals("--no-minification")) {
diff --git a/src/main/java/com/android/tools/r8/bisect/Bisect.java b/src/main/java/com/android/tools/r8/bisect/Bisect.java
index 1d09319..0e86dc7 100644
--- a/src/main/java/com/android/tools/r8/bisect/Bisect.java
+++ b/src/main/java/com/android/tools/r8/bisect/Bisect.java
@@ -12,6 +12,7 @@
 import com.android.tools.r8.errors.CompilationError;
 import com.android.tools.r8.graph.DexApplication;
 import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.InitClassLens;
 import com.android.tools.r8.naming.NamingLens;
 import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.AndroidAppConsumers;
@@ -87,7 +88,6 @@
         return null;
       }
       state.setPreviousResult(command.apply(app));
-      app.options.itemFactory.resetSortedIndices();
     }
   }
 
@@ -186,7 +186,15 @@
     StringConsumer proguardMapConsumer = options.proguardMapConsumer;
     AndroidAppConsumers compatSink = new AndroidAppConsumers(options);
     ApplicationWriter writer =
-        new ApplicationWriter(app, null, options, null, null, NamingLens.getIdentityLens(), null);
+        new ApplicationWriter(
+            app,
+            null,
+            options,
+            null,
+            null,
+            InitClassLens.getDefault(),
+            NamingLens.getIdentityLens(),
+            null);
     writer.write(executor);
     options.signalFinishedToConsumers();
     compatSink.build().writeToDirectory(output, OutputMode.DexIndexed);
diff --git a/src/main/java/com/android/tools/r8/bisect/BisectState.java b/src/main/java/com/android/tools/r8/bisect/BisectState.java
index 5500637..cd3fe0a 100644
--- a/src/main/java/com/android/tools/r8/bisect/BisectState.java
+++ b/src/main/java/com/android/tools/r8/bisect/BisectState.java
@@ -21,7 +21,6 @@
 import java.nio.file.Files;
 import java.nio.file.Path;
 import java.util.ArrayList;
-import java.util.Comparator;
 import java.util.List;
 import java.util.Map;
 
@@ -324,9 +323,7 @@
 
   private static List<DexProgramClass> getSortedClasses(DexApplication app) {
     List<DexProgramClass> classes = new ArrayList<>(app.classes());
-    app.dexItemFactory.sort(NamingLens.getIdentityLens());
-    classes.sort(Comparator.comparing(DexProgramClass::getType));
-    app.dexItemFactory.resetSortedIndices();
+    classes.sort((a, b) -> a.type.slowCompareTo(b.type, NamingLens.getIdentityLens()));
     return classes;
   }
 
diff --git a/src/main/java/com/android/tools/r8/cf/CfPrinter.java b/src/main/java/com/android/tools/r8/cf/CfPrinter.java
index 9eedd04..ccd1686 100644
--- a/src/main/java/com/android/tools/r8/cf/CfPrinter.java
+++ b/src/main/java/com/android/tools/r8/cf/CfPrinter.java
@@ -23,6 +23,7 @@
 import com.android.tools.r8.cf.code.CfIf;
 import com.android.tools.r8.cf.code.CfIfCmp;
 import com.android.tools.r8.cf.code.CfIinc;
+import com.android.tools.r8.cf.code.CfInitClass;
 import com.android.tools.r8.cf.code.CfInstanceOf;
 import com.android.tools.r8.cf.code.CfInstruction;
 import com.android.tools.r8.cf.code.CfInvoke;
@@ -321,6 +322,12 @@
     appendType(constClass.getType());
   }
 
+  public void print(CfInitClass initClass) {
+    indent();
+    builder.append("initclass ");
+    appendType(initClass.getClassValue());
+  }
+
   public void print(CfReturnVoid ret) {
     print("return");
   }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfArithmeticBinop.java b/src/main/java/com/android/tools/r8/cf/code/CfArithmeticBinop.java
index fc2a080..e9997b0 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfArithmeticBinop.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfArithmeticBinop.java
@@ -6,6 +6,7 @@
 import com.android.tools.r8.cf.CfPrinter;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.InitClassLens;
 import com.android.tools.r8.ir.code.NumericType;
 import com.android.tools.r8.ir.code.ValueType;
 import com.android.tools.r8.ir.conversion.CfSourceCode;
@@ -128,7 +129,7 @@
   }
 
   @Override
-  public void write(MethodVisitor visitor, NamingLens lens) {
+  public void write(MethodVisitor visitor, InitClassLens initClassLens, NamingLens lens) {
     visitor.visitInsn(getAsmOpcode());
   }
 
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfArrayLength.java b/src/main/java/com/android/tools/r8/cf/code/CfArrayLength.java
index 53536d2..85c4773 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfArrayLength.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfArrayLength.java
@@ -5,6 +5,7 @@
 
 import com.android.tools.r8.cf.CfPrinter;
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.InitClassLens;
 import com.android.tools.r8.ir.conversion.CfSourceCode;
 import com.android.tools.r8.ir.conversion.CfState;
 import com.android.tools.r8.ir.conversion.CfState.Slot;
@@ -18,7 +19,7 @@
 public class CfArrayLength extends CfInstruction {
 
   @Override
-  public void write(MethodVisitor visitor, NamingLens lens) {
+  public void write(MethodVisitor visitor, InitClassLens initClassLens, NamingLens lens) {
     visitor.visitInsn(Opcodes.ARRAYLENGTH);
   }
 
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfArrayLoad.java b/src/main/java/com/android/tools/r8/cf/code/CfArrayLoad.java
index 1f667b4..010bcfd 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfArrayLoad.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfArrayLoad.java
@@ -6,6 +6,7 @@
 import com.android.tools.r8.cf.CfPrinter;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.InitClassLens;
 import com.android.tools.r8.ir.code.MemberType;
 import com.android.tools.r8.ir.code.ValueType;
 import com.android.tools.r8.ir.conversion.CfSourceCode;
@@ -55,7 +56,7 @@
   }
 
   @Override
-  public void write(MethodVisitor visitor, NamingLens lens) {
+  public void write(MethodVisitor visitor, InitClassLens initClassLens, NamingLens lens) {
     visitor.visitInsn(getLoadType());
   }
 
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfArrayStore.java b/src/main/java/com/android/tools/r8/cf/code/CfArrayStore.java
index d0eadef..52df928 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfArrayStore.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfArrayStore.java
@@ -6,6 +6,7 @@
 import com.android.tools.r8.cf.CfPrinter;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.InitClassLens;
 import com.android.tools.r8.ir.code.MemberType;
 import com.android.tools.r8.ir.conversion.CfSourceCode;
 import com.android.tools.r8.ir.conversion.CfState;
@@ -53,7 +54,7 @@
   }
 
   @Override
-  public void write(MethodVisitor visitor, NamingLens lens) {
+  public void write(MethodVisitor visitor, InitClassLens initClassLens, NamingLens lens) {
     visitor.visitInsn(getStoreType());
   }
 
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfCheckCast.java b/src/main/java/com/android/tools/r8/cf/code/CfCheckCast.java
index e89c480..19ca32e 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfCheckCast.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfCheckCast.java
@@ -5,6 +5,7 @@
 
 import com.android.tools.r8.cf.CfPrinter;
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.InitClassLens;
 import com.android.tools.r8.graph.UseRegistry;
 import com.android.tools.r8.ir.conversion.CfSourceCode;
 import com.android.tools.r8.ir.conversion.CfState;
@@ -29,7 +30,7 @@
   }
 
   @Override
-  public void write(MethodVisitor visitor, NamingLens lens) {
+  public void write(MethodVisitor visitor, InitClassLens initClassLens, NamingLens lens) {
     visitor.visitTypeInsn(Opcodes.CHECKCAST, lens.lookupInternalName(type));
   }
 
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfCmp.java b/src/main/java/com/android/tools/r8/cf/code/CfCmp.java
index dac3eca..259bfe3 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfCmp.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfCmp.java
@@ -6,6 +6,7 @@
 import com.android.tools.r8.cf.CfPrinter;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.InitClassLens;
 import com.android.tools.r8.ir.code.Cmp;
 import com.android.tools.r8.ir.code.Cmp.Bias;
 import com.android.tools.r8.ir.code.NumericType;
@@ -78,7 +79,7 @@
   }
 
   @Override
-  public void write(MethodVisitor visitor, NamingLens lens) {
+  public void write(MethodVisitor visitor, InitClassLens initClassLens, NamingLens lens) {
     visitor.visitInsn(getAsmOpcode());
   }
 
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfConstClass.java b/src/main/java/com/android/tools/r8/cf/code/CfConstClass.java
index 7f77ade..a203742 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfConstClass.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfConstClass.java
@@ -6,6 +6,7 @@
 import com.android.tools.r8.cf.CfPrinter;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.InitClassLens;
 import com.android.tools.r8.graph.UseRegistry;
 import com.android.tools.r8.ir.conversion.CfSourceCode;
 import com.android.tools.r8.ir.conversion.CfState;
@@ -29,7 +30,7 @@
   }
 
   @Override
-  public void write(MethodVisitor visitor, NamingLens lens) {
+  public void write(MethodVisitor visitor, InitClassLens initClassLens, NamingLens lens) {
     visitor.visitLdcInsn(Type.getObjectType(getInternalName(lens)));
   }
 
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfConstMethodHandle.java b/src/main/java/com/android/tools/r8/cf/code/CfConstMethodHandle.java
index a9a1d73..04e569b 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfConstMethodHandle.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfConstMethodHandle.java
@@ -6,6 +6,7 @@
 import com.android.tools.r8.cf.CfPrinter;
 import com.android.tools.r8.graph.DexMethodHandle;
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.InitClassLens;
 import com.android.tools.r8.graph.UseRegistry;
 import com.android.tools.r8.graph.UseRegistry.MethodHandleUse;
 import com.android.tools.r8.ir.conversion.CfSourceCode;
@@ -29,7 +30,7 @@
   }
 
   @Override
-  public void write(MethodVisitor visitor, NamingLens lens) {
+  public void write(MethodVisitor visitor, InitClassLens initClassLens, NamingLens lens) {
     visitor.visitLdcInsn(handle.toAsmHandle(lens));
   }
 
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfConstMethodType.java b/src/main/java/com/android/tools/r8/cf/code/CfConstMethodType.java
index 063b6fa..a67c3ed 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfConstMethodType.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfConstMethodType.java
@@ -6,6 +6,7 @@
 import com.android.tools.r8.cf.CfPrinter;
 import com.android.tools.r8.graph.DexProto;
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.InitClassLens;
 import com.android.tools.r8.graph.UseRegistry;
 import com.android.tools.r8.ir.conversion.CfSourceCode;
 import com.android.tools.r8.ir.conversion.CfState;
@@ -29,7 +30,7 @@
   }
 
   @Override
-  public void write(MethodVisitor visitor, NamingLens lens) {
+  public void write(MethodVisitor visitor, InitClassLens initClassLens, NamingLens lens) {
     visitor.visitLdcInsn(Type.getType(type.toDescriptorString(lens)));
   }
 
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfConstNull.java b/src/main/java/com/android/tools/r8/cf/code/CfConstNull.java
index 7ad901a..c3be968 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfConstNull.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfConstNull.java
@@ -5,6 +5,7 @@
 
 import com.android.tools.r8.cf.CfPrinter;
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.InitClassLens;
 import com.android.tools.r8.ir.code.ValueType;
 import com.android.tools.r8.ir.conversion.CfSourceCode;
 import com.android.tools.r8.ir.conversion.CfState;
@@ -18,7 +19,7 @@
 public class CfConstNull extends CfInstruction {
 
   @Override
-  public void write(MethodVisitor visitor, NamingLens lens) {
+  public void write(MethodVisitor visitor, InitClassLens initClassLens, NamingLens lens) {
     visitor.visitInsn(Opcodes.ACONST_NULL);
   }
 
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfConstNumber.java b/src/main/java/com/android/tools/r8/cf/code/CfConstNumber.java
index 72e4638..8977303 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfConstNumber.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfConstNumber.java
@@ -6,6 +6,7 @@
 import com.android.tools.r8.cf.CfPrinter;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.InitClassLens;
 import com.android.tools.r8.ir.code.ValueType;
 import com.android.tools.r8.ir.conversion.CfSourceCode;
 import com.android.tools.r8.ir.conversion.CfState;
@@ -55,7 +56,7 @@
   }
 
   @Override
-  public void write(MethodVisitor visitor, NamingLens lens) {
+  public void write(MethodVisitor visitor, InitClassLens initClassLens, NamingLens lens) {
     switch (type) {
       case INT:
         {
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfConstString.java b/src/main/java/com/android/tools/r8/cf/code/CfConstString.java
index d52d901..08ee865 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfConstString.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfConstString.java
@@ -6,6 +6,7 @@
 import com.android.tools.r8.cf.CfPrinter;
 import com.android.tools.r8.graph.DexString;
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.InitClassLens;
 import com.android.tools.r8.ir.conversion.CfSourceCode;
 import com.android.tools.r8.ir.conversion.CfState;
 import com.android.tools.r8.ir.conversion.IRBuilder;
@@ -41,7 +42,7 @@
   }
 
   @Override
-  public void write(MethodVisitor visitor, NamingLens lens) {
+  public void write(MethodVisitor visitor, InitClassLens initClassLens, NamingLens lens) {
     visitor.visitLdcInsn(string.toString());
   }
 
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfDexItemBasedConstString.java b/src/main/java/com/android/tools/r8/cf/code/CfDexItemBasedConstString.java
index 36ba9b4..fbc4939 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfDexItemBasedConstString.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfDexItemBasedConstString.java
@@ -7,6 +7,7 @@
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.DexReference;
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.InitClassLens;
 import com.android.tools.r8.graph.UseRegistry;
 import com.android.tools.r8.ir.conversion.CfSourceCode;
 import com.android.tools.r8.ir.conversion.CfState;
@@ -46,7 +47,7 @@
   }
 
   @Override
-  public void write(MethodVisitor visitor, NamingLens lens) {
+  public void write(MethodVisitor visitor, InitClassLens initClassLens, NamingLens lens) {
     throw new Unreachable(
         "CfDexItemBasedConstString instructions should always be rewritten into CfConstString");
   }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfFieldInstruction.java b/src/main/java/com/android/tools/r8/cf/code/CfFieldInstruction.java
index a000dcf..5812288 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfFieldInstruction.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfFieldInstruction.java
@@ -7,6 +7,7 @@
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.InitClassLens;
 import com.android.tools.r8.graph.UseRegistry;
 import com.android.tools.r8.ir.conversion.CfSourceCode;
 import com.android.tools.r8.ir.conversion.CfState;
@@ -50,7 +51,7 @@
   }
 
   @Override
-  public void write(MethodVisitor visitor, NamingLens lens) {
+  public void write(MethodVisitor visitor, InitClassLens initClassLens, NamingLens lens) {
     String owner = lens.lookupInternalName(field.holder);
     String name = lens.lookupName(declaringField).toString();
     String desc = lens.lookupDescriptor(field.type).toString();
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfFrame.java b/src/main/java/com/android/tools/r8/cf/code/CfFrame.java
index e5a3c19..88d5ece 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfFrame.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfFrame.java
@@ -9,6 +9,7 @@
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.InitClassLens;
 import com.android.tools.r8.ir.conversion.CfSourceCode;
 import com.android.tools.r8.ir.conversion.CfState;
 import com.android.tools.r8.ir.conversion.IRBuilder;
@@ -211,7 +212,7 @@
   }
 
   @Override
-  public void write(MethodVisitor visitor, NamingLens lens) {
+  public void write(MethodVisitor visitor, InitClassLens initClassLens, NamingLens lens) {
     int stackCount = computeStackCount();
     Object[] stackTypes = computeStackTypes(stackCount, lens);
     int localsCount = computeLocalsCount();
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfGoto.java b/src/main/java/com/android/tools/r8/cf/code/CfGoto.java
index efe67ba..412c97c7 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfGoto.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfGoto.java
@@ -5,6 +5,7 @@
 
 import com.android.tools.r8.cf.CfPrinter;
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.InitClassLens;
 import com.android.tools.r8.ir.conversion.CfSourceCode;
 import com.android.tools.r8.ir.conversion.CfState;
 import com.android.tools.r8.ir.conversion.IRBuilder;
@@ -43,7 +44,7 @@
   }
 
   @Override
-  public void write(MethodVisitor visitor, NamingLens lens) {
+  public void write(MethodVisitor visitor, InitClassLens initClassLens, NamingLens lens) {
     visitor.visitJumpInsn(Opcodes.GOTO, target.getLabel());
   }
 
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfIf.java b/src/main/java/com/android/tools/r8/cf/code/CfIf.java
index 1ba9c06..ebc6a98 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfIf.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfIf.java
@@ -6,6 +6,7 @@
 import com.android.tools.r8.cf.CfPrinter;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.InitClassLens;
 import com.android.tools.r8.ir.code.If;
 import com.android.tools.r8.ir.code.If.Type;
 import com.android.tools.r8.ir.code.ValueType;
@@ -68,7 +69,7 @@
   }
 
   @Override
-  public void write(MethodVisitor visitor, NamingLens lens) {
+  public void write(MethodVisitor visitor, InitClassLens initClassLens, NamingLens lens) {
     visitor.visitJumpInsn(getOpcode(), target.getLabel());
   }
 
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfIfCmp.java b/src/main/java/com/android/tools/r8/cf/code/CfIfCmp.java
index 28a59d7..c2f34f7 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfIfCmp.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfIfCmp.java
@@ -6,6 +6,7 @@
 import com.android.tools.r8.cf.CfPrinter;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.InitClassLens;
 import com.android.tools.r8.ir.code.If;
 import com.android.tools.r8.ir.code.If.Type;
 import com.android.tools.r8.ir.code.ValueType;
@@ -68,7 +69,7 @@
   }
 
   @Override
-  public void write(MethodVisitor visitor, NamingLens lens) {
+  public void write(MethodVisitor visitor, InitClassLens initClassLens, NamingLens lens) {
     visitor.visitJumpInsn(getOpcode(), target.getLabel());
   }
 
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfIinc.java b/src/main/java/com/android/tools/r8/cf/code/CfIinc.java
index 92f2094..986d11f 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfIinc.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfIinc.java
@@ -5,6 +5,7 @@
 
 import com.android.tools.r8.cf.CfPrinter;
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.InitClassLens;
 import com.android.tools.r8.ir.code.NumericType;
 import com.android.tools.r8.ir.conversion.CfSourceCode;
 import com.android.tools.r8.ir.conversion.CfState;
@@ -25,7 +26,7 @@
   }
 
   @Override
-  public void write(MethodVisitor visitor, NamingLens lens) {
+  public void write(MethodVisitor visitor, InitClassLens initClassLens, NamingLens lens) {
     visitor.visitIincInsn(var, increment);
   }
 
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfInitClass.java b/src/main/java/com/android/tools/r8/cf/code/CfInitClass.java
new file mode 100644
index 0000000..a355f58
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/cf/code/CfInitClass.java
@@ -0,0 +1,72 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.cf.code;
+
+import com.android.tools.r8.cf.CfPrinter;
+import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.InitClassLens;
+import com.android.tools.r8.graph.UseRegistry;
+import com.android.tools.r8.ir.conversion.CfSourceCode;
+import com.android.tools.r8.ir.conversion.CfState;
+import com.android.tools.r8.ir.conversion.IRBuilder;
+import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
+import com.android.tools.r8.ir.optimize.InliningConstraints;
+import com.android.tools.r8.naming.NamingLens;
+import org.objectweb.asm.MethodVisitor;
+
+public class CfInitClass extends CfInstruction {
+
+  private static final int OPCODE = org.objectweb.asm.Opcodes.GETSTATIC;
+
+  private final DexType clazz;
+
+  public CfInitClass(DexType clazz) {
+    this.clazz = clazz;
+  }
+
+  public DexType getClassValue() {
+    return clazz;
+  }
+
+  public int getOpcode() {
+    return OPCODE;
+  }
+
+  @Override
+  public void write(MethodVisitor visitor, InitClassLens initClassLens, NamingLens lens) {
+    DexField field = initClassLens.getInitClassField(clazz);
+    String owner = lens.lookupInternalName(field.holder);
+    String name = lens.lookupName(field).toString();
+    String desc = lens.lookupDescriptor(field.type).toString();
+    visitor.visitFieldInsn(OPCODE, owner, name, desc);
+  }
+
+  @Override
+  public void print(CfPrinter printer) {
+    printer.print(this);
+  }
+
+  @Override
+  public void registerUse(UseRegistry registry, DexType context) {
+    registry.registerInitClass(clazz);
+  }
+
+  @Override
+  public boolean canThrow() {
+    return true;
+  }
+
+  @Override
+  public void buildIR(IRBuilder builder, CfState state, CfSourceCode code) {
+    int dest = state.push(builder.appView.dexItemFactory().intType).register;
+    builder.addInitClass(dest, clazz);
+  }
+
+  @Override
+  public ConstraintWithTarget inliningConstraint(
+      InliningConstraints inliningConstraints, DexType context) {
+    return inliningConstraints.forInitClass(clazz, context);
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfInstanceOf.java b/src/main/java/com/android/tools/r8/cf/code/CfInstanceOf.java
index d4d2066..5d0997f 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfInstanceOf.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfInstanceOf.java
@@ -5,6 +5,7 @@
 
 import com.android.tools.r8.cf.CfPrinter;
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.InitClassLens;
 import com.android.tools.r8.graph.UseRegistry;
 import com.android.tools.r8.ir.conversion.CfSourceCode;
 import com.android.tools.r8.ir.conversion.CfState;
@@ -38,7 +39,7 @@
   }
 
   @Override
-  public void write(MethodVisitor visitor, NamingLens lens) {
+  public void write(MethodVisitor visitor, InitClassLens initClassLens, NamingLens lens) {
     visitor.visitTypeInsn(Opcodes.INSTANCEOF, lens.lookupInternalName(type));
   }
 
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfInstruction.java b/src/main/java/com/android/tools/r8/cf/code/CfInstruction.java
index 43d209e..d3cc826 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfInstruction.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfInstruction.java
@@ -5,6 +5,7 @@
 
 import com.android.tools.r8.cf.CfPrinter;
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.InitClassLens;
 import com.android.tools.r8.graph.UseRegistry;
 import com.android.tools.r8.ir.conversion.CfSourceCode;
 import com.android.tools.r8.ir.conversion.CfState;
@@ -16,7 +17,7 @@
 
 public abstract class CfInstruction {
 
-  public abstract void write(MethodVisitor visitor, NamingLens lens);
+  public abstract void write(MethodVisitor visitor, InitClassLens initClassLens, NamingLens lens);
 
   public abstract void print(CfPrinter printer);
 
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 6311bfd..1a5dd5c 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
@@ -16,6 +16,7 @@
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.GraphLense;
 import com.android.tools.r8.graph.GraphLense.GraphLenseLookupResult;
+import com.android.tools.r8.graph.InitClassLens;
 import com.android.tools.r8.graph.UseRegistry;
 import com.android.tools.r8.ir.code.Invoke;
 import com.android.tools.r8.ir.code.Invoke.Type;
@@ -69,7 +70,7 @@
   }
 
   @Override
-  public void write(MethodVisitor visitor, NamingLens lens) {
+  public void write(MethodVisitor visitor, InitClassLens initClassLens, NamingLens lens) {
     String owner = lens.lookupInternalName(method.holder);
     String name = lens.lookupName(method).toString();
     String desc = method.proto.toDescriptorString(lens);
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfInvokeDynamic.java b/src/main/java/com/android/tools/r8/cf/code/CfInvokeDynamic.java
index 231c115..ef0ec1b 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfInvokeDynamic.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfInvokeDynamic.java
@@ -18,6 +18,7 @@
 import com.android.tools.r8.graph.DexValue.DexValueMethodType;
 import com.android.tools.r8.graph.DexValue.DexValueString;
 import com.android.tools.r8.graph.DexValue.DexValueType;
+import com.android.tools.r8.graph.InitClassLens;
 import com.android.tools.r8.graph.UseRegistry;
 import com.android.tools.r8.ir.code.ValueType;
 import com.android.tools.r8.ir.conversion.CfSourceCode;
@@ -41,7 +42,7 @@
   }
 
   @Override
-  public void write(MethodVisitor visitor, NamingLens lens) {
+  public void write(MethodVisitor visitor, InitClassLens initClassLens, NamingLens lens) {
     DexMethodHandle bootstrapMethod = callSite.bootstrapMethod;
     List<DexValue> bootstrapArgs = callSite.bootstrapArgs;
     Object[] bsmArgs = new Object[bootstrapArgs.size()];
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfJsrRet.java b/src/main/java/com/android/tools/r8/cf/code/CfJsrRet.java
index 55aea68..7e678dc 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfJsrRet.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfJsrRet.java
@@ -6,6 +6,7 @@
 import com.android.tools.r8.cf.CfPrinter;
 import com.android.tools.r8.errors.CompilationError;
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.InitClassLens;
 import com.android.tools.r8.ir.conversion.CfSourceCode;
 import com.android.tools.r8.ir.conversion.CfState;
 import com.android.tools.r8.ir.conversion.IRBuilder;
@@ -28,7 +29,7 @@
   }
 
   @Override
-  public void write(MethodVisitor visitor, NamingLens lens) {
+  public void write(MethodVisitor visitor, InitClassLens initClassLens, NamingLens lens) {
     throw error();
   }
 
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfLabel.java b/src/main/java/com/android/tools/r8/cf/code/CfLabel.java
index df9d6bd..3901a1a 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfLabel.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfLabel.java
@@ -5,6 +5,7 @@
 
 import com.android.tools.r8.cf.CfPrinter;
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.InitClassLens;
 import com.android.tools.r8.ir.conversion.CfSourceCode;
 import com.android.tools.r8.ir.conversion.CfState;
 import com.android.tools.r8.ir.conversion.IRBuilder;
@@ -41,7 +42,7 @@
   }
 
   @Override
-  public void write(MethodVisitor visitor, NamingLens lens) {
+  public void write(MethodVisitor visitor, InitClassLens initClassLens, NamingLens lens) {
     visitor.visitLabel(getLabel());
   }
 
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfLoad.java b/src/main/java/com/android/tools/r8/cf/code/CfLoad.java
index ae1fd85..80d6a8e 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfLoad.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfLoad.java
@@ -6,6 +6,7 @@
 import com.android.tools.r8.cf.CfPrinter;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.InitClassLens;
 import com.android.tools.r8.ir.code.ValueType;
 import com.android.tools.r8.ir.conversion.CfSourceCode;
 import com.android.tools.r8.ir.conversion.CfState;
@@ -55,7 +56,7 @@
   }
 
   @Override
-  public void write(MethodVisitor visitor, NamingLens lens) {
+  public void write(MethodVisitor visitor, InitClassLens initClassLens, NamingLens lens) {
     visitor.visitVarInsn(getLoadType(), var);
   }
 
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfLogicalBinop.java b/src/main/java/com/android/tools/r8/cf/code/CfLogicalBinop.java
index 1d03017..c2775a0 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfLogicalBinop.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfLogicalBinop.java
@@ -6,6 +6,7 @@
 import com.android.tools.r8.cf.CfPrinter;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.InitClassLens;
 import com.android.tools.r8.ir.code.NumericType;
 import com.android.tools.r8.ir.code.ValueType;
 import com.android.tools.r8.ir.conversion.CfSourceCode;
@@ -103,7 +104,7 @@
   }
 
   @Override
-  public void write(MethodVisitor visitor, NamingLens lens) {
+  public void write(MethodVisitor visitor, InitClassLens initClassLens, NamingLens lens) {
     visitor.visitInsn(getAsmOpcode());
   }
 
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfMonitor.java b/src/main/java/com/android/tools/r8/cf/code/CfMonitor.java
index dc117dd..6386b3c 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfMonitor.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfMonitor.java
@@ -5,6 +5,7 @@
 
 import com.android.tools.r8.cf.CfPrinter;
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.InitClassLens;
 import com.android.tools.r8.ir.code.Monitor.Type;
 import com.android.tools.r8.ir.conversion.CfSourceCode;
 import com.android.tools.r8.ir.conversion.CfState;
@@ -29,7 +30,7 @@
   }
 
   @Override
-  public void write(MethodVisitor visitor, NamingLens lens) {
+  public void write(MethodVisitor visitor, InitClassLens initClassLens, NamingLens lens) {
     visitor.visitInsn(type == Type.ENTER ? Opcodes.MONITORENTER : Opcodes.MONITOREXIT);
   }
 
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfMultiANewArray.java b/src/main/java/com/android/tools/r8/cf/code/CfMultiANewArray.java
index 936f62d..d12d76b 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfMultiANewArray.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfMultiANewArray.java
@@ -5,6 +5,7 @@
 
 import com.android.tools.r8.cf.CfPrinter;
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.InitClassLens;
 import com.android.tools.r8.graph.UseRegistry;
 import com.android.tools.r8.ir.conversion.CfSourceCode;
 import com.android.tools.r8.ir.conversion.CfState;
@@ -34,7 +35,7 @@
   }
 
   @Override
-  public void write(MethodVisitor visitor, NamingLens lens) {
+  public void write(MethodVisitor visitor, InitClassLens initClassLens, NamingLens lens) {
     visitor.visitMultiANewArrayInsn(lens.lookupInternalName(type), dimensions);
   }
 
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfNeg.java b/src/main/java/com/android/tools/r8/cf/code/CfNeg.java
index 254ee77..02b2a83 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfNeg.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfNeg.java
@@ -6,6 +6,7 @@
 import com.android.tools.r8.cf.CfPrinter;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.InitClassLens;
 import com.android.tools.r8.ir.code.NumericType;
 import com.android.tools.r8.ir.code.ValueType;
 import com.android.tools.r8.ir.conversion.CfSourceCode;
@@ -30,7 +31,7 @@
   }
 
   @Override
-  public void write(MethodVisitor visitor, NamingLens lens) {
+  public void write(MethodVisitor visitor, InitClassLens initClassLens, NamingLens lens) {
     visitor.visitInsn(getAsmOpcode());
   }
 
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfNew.java b/src/main/java/com/android/tools/r8/cf/code/CfNew.java
index 46821da..be087af 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfNew.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfNew.java
@@ -5,6 +5,7 @@
 
 import com.android.tools.r8.cf.CfPrinter;
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.InitClassLens;
 import com.android.tools.r8.graph.UseRegistry;
 import com.android.tools.r8.ir.conversion.CfSourceCode;
 import com.android.tools.r8.ir.conversion.CfState;
@@ -28,7 +29,7 @@
   }
 
   @Override
-  public void write(MethodVisitor visitor, NamingLens lens) {
+  public void write(MethodVisitor visitor, InitClassLens initClassLens, NamingLens lens) {
     visitor.visitTypeInsn(Opcodes.NEW, lens.lookupInternalName(type));
   }
 
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfNewArray.java b/src/main/java/com/android/tools/r8/cf/code/CfNewArray.java
index 10504d9..1ce6048 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfNewArray.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfNewArray.java
@@ -6,6 +6,7 @@
 import com.android.tools.r8.cf.CfPrinter;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.InitClassLens;
 import com.android.tools.r8.graph.UseRegistry;
 import com.android.tools.r8.ir.conversion.CfSourceCode;
 import com.android.tools.r8.ir.conversion.CfState;
@@ -63,7 +64,7 @@
   }
 
   @Override
-  public void write(MethodVisitor visitor, NamingLens lens) {
+  public void write(MethodVisitor visitor, InitClassLens initClassLens, NamingLens lens) {
     if (type.isPrimitiveArrayType()) {
       visitor.visitIntInsn(Opcodes.NEWARRAY, getPrimitiveTypeCode());
     } else {
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfNop.java b/src/main/java/com/android/tools/r8/cf/code/CfNop.java
index e124a0b..be9170c 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfNop.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfNop.java
@@ -5,6 +5,7 @@
 
 import com.android.tools.r8.cf.CfPrinter;
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.InitClassLens;
 import com.android.tools.r8.ir.conversion.CfSourceCode;
 import com.android.tools.r8.ir.conversion.CfState;
 import com.android.tools.r8.ir.conversion.IRBuilder;
@@ -17,7 +18,7 @@
 public class CfNop extends CfInstruction {
 
   @Override
-  public void write(MethodVisitor visitor, NamingLens lens) {
+  public void write(MethodVisitor visitor, InitClassLens initClassLens, NamingLens lens) {
     visitor.visitInsn(Opcodes.NOP);
   }
 
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfNumberConversion.java b/src/main/java/com/android/tools/r8/cf/code/CfNumberConversion.java
index c1118e5..dbedc8a 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfNumberConversion.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfNumberConversion.java
@@ -6,6 +6,7 @@
 import com.android.tools.r8.cf.CfPrinter;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.InitClassLens;
 import com.android.tools.r8.ir.code.NumericType;
 import com.android.tools.r8.ir.code.ValueType;
 import com.android.tools.r8.ir.conversion.CfSourceCode;
@@ -40,7 +41,7 @@
   }
 
   @Override
-  public void write(MethodVisitor visitor, NamingLens lens) {
+  public void write(MethodVisitor visitor, InitClassLens initClassLens, NamingLens lens) {
     visitor.visitInsn(this.getAsmOpcode());
   }
 
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfPosition.java b/src/main/java/com/android/tools/r8/cf/code/CfPosition.java
index 035604b..2dac0c6 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfPosition.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfPosition.java
@@ -5,6 +5,7 @@
 
 import com.android.tools.r8.cf.CfPrinter;
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.InitClassLens;
 import com.android.tools.r8.ir.code.Position;
 import com.android.tools.r8.ir.conversion.CfSourceCode;
 import com.android.tools.r8.ir.conversion.CfState;
@@ -25,7 +26,7 @@
   }
 
   @Override
-  public void write(MethodVisitor visitor, NamingLens lens) {
+  public void write(MethodVisitor visitor, InitClassLens initClassLens, NamingLens lens) {
     visitor.visitLineNumber(position.line, label.getLabel());
   }
 
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfReturn.java b/src/main/java/com/android/tools/r8/cf/code/CfReturn.java
index 76475fa..e1b6de1 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfReturn.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfReturn.java
@@ -6,6 +6,7 @@
 import com.android.tools.r8.cf.CfPrinter;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.InitClassLens;
 import com.android.tools.r8.ir.code.ValueType;
 import com.android.tools.r8.ir.conversion.CfSourceCode;
 import com.android.tools.r8.ir.conversion.CfState;
@@ -52,7 +53,7 @@
   }
 
   @Override
-  public void write(MethodVisitor visitor, NamingLens lens) {
+  public void write(MethodVisitor visitor, InitClassLens initClassLens, NamingLens lens) {
     visitor.visitInsn(getOpcode());
   }
 
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfReturnVoid.java b/src/main/java/com/android/tools/r8/cf/code/CfReturnVoid.java
index fd55aa0..0bbe1cd 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfReturnVoid.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfReturnVoid.java
@@ -5,6 +5,7 @@
 
 import com.android.tools.r8.cf.CfPrinter;
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.InitClassLens;
 import com.android.tools.r8.ir.conversion.CfSourceCode;
 import com.android.tools.r8.ir.conversion.CfState;
 import com.android.tools.r8.ir.conversion.IRBuilder;
@@ -22,7 +23,7 @@
   }
 
   @Override
-  public void write(MethodVisitor visitor, NamingLens lens) {
+  public void write(MethodVisitor visitor, InitClassLens initClassLens, NamingLens lens) {
     visitor.visitInsn(Opcodes.RETURN);
   }
 
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfStackInstruction.java b/src/main/java/com/android/tools/r8/cf/code/CfStackInstruction.java
index 5886058..5821c3f 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfStackInstruction.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfStackInstruction.java
@@ -7,6 +7,7 @@
 import com.android.tools.r8.errors.CompilationError;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.InitClassLens;
 import com.android.tools.r8.ir.code.ValueType;
 import com.android.tools.r8.ir.conversion.CfSourceCode;
 import com.android.tools.r8.ir.conversion.CfState;
@@ -74,7 +75,7 @@
   }
 
   @Override
-  public void write(MethodVisitor visitor, NamingLens lens) {
+  public void write(MethodVisitor visitor, InitClassLens initClassLens, NamingLens lens) {
     visitor.visitInsn(opcode.opcode);
   }
 
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfStore.java b/src/main/java/com/android/tools/r8/cf/code/CfStore.java
index 5f8a05d..d560b82 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfStore.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfStore.java
@@ -6,6 +6,7 @@
 import com.android.tools.r8.cf.CfPrinter;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.InitClassLens;
 import com.android.tools.r8.ir.code.ValueType;
 import com.android.tools.r8.ir.conversion.CfSourceCode;
 import com.android.tools.r8.ir.conversion.CfState;
@@ -55,7 +56,7 @@
   }
 
   @Override
-  public void write(MethodVisitor visitor, NamingLens lens) {
+  public void write(MethodVisitor visitor, InitClassLens initClassLens, NamingLens lens) {
     visitor.visitVarInsn(getStoreType(), var);
   }
 
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfSwitch.java b/src/main/java/com/android/tools/r8/cf/code/CfSwitch.java
index 47aff14..bfbe30f 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfSwitch.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfSwitch.java
@@ -5,6 +5,7 @@
 
 import com.android.tools.r8.cf.CfPrinter;
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.InitClassLens;
 import com.android.tools.r8.ir.conversion.CfSourceCode;
 import com.android.tools.r8.ir.conversion.CfState;
 import com.android.tools.r8.ir.conversion.CfState.Slot;
@@ -68,7 +69,7 @@
   }
 
   @Override
-  public void write(MethodVisitor visitor, NamingLens lens) {
+  public void write(MethodVisitor visitor, InitClassLens initClassLens, NamingLens lens) {
     Label[] labels = new Label[targets.size()];
     for (int i = 0; i < targets.size(); i++) {
       labels[i] = targets.get(i).getLabel();
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfThrow.java b/src/main/java/com/android/tools/r8/cf/code/CfThrow.java
index a7609c0..c0c094d 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfThrow.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfThrow.java
@@ -5,6 +5,7 @@
 
 import com.android.tools.r8.cf.CfPrinter;
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.InitClassLens;
 import com.android.tools.r8.ir.conversion.CfSourceCode;
 import com.android.tools.r8.ir.conversion.CfState;
 import com.android.tools.r8.ir.conversion.CfState.Slot;
@@ -23,7 +24,7 @@
   }
 
   @Override
-  public void write(MethodVisitor visitor, NamingLens lens) {
+  public void write(MethodVisitor visitor, InitClassLens initClassLens, NamingLens lens) {
     visitor.visitInsn(Opcodes.ATHROW);
   }
 
diff --git a/src/main/java/com/android/tools/r8/code/DexInitClass.java b/src/main/java/com/android/tools/r8/code/DexInitClass.java
new file mode 100644
index 0000000..9df8285
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/code/DexInitClass.java
@@ -0,0 +1,131 @@
+// 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.code;
+
+import com.android.tools.r8.dex.IndexedItemCollection;
+import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.ObjectToOffsetMapping;
+import com.android.tools.r8.graph.UseRegistry;
+import com.android.tools.r8.ir.code.FieldMemberType;
+import com.android.tools.r8.ir.conversion.IRBuilder;
+import com.android.tools.r8.naming.ClassNameMapper;
+import java.nio.ShortBuffer;
+
+public class DexInitClass extends Base2Format {
+
+  public static final int OPCODE = 0x60;
+  public static final String NAME = "InitClass";
+  public static final String SMALI_NAME = "initclass";
+
+  private final int dest;
+  private final DexType clazz;
+
+  public DexInitClass(int dest, DexType clazz) {
+    assert clazz.isClassType();
+    this.dest = dest;
+    this.clazz = clazz;
+  }
+
+  @Override
+  public void buildIR(IRBuilder builder) {
+    builder.addInitClass(dest, clazz);
+  }
+
+  @Override
+  public void collectIndexedItems(
+      IndexedItemCollection indexedItems, DexMethod method, int instructionOffset) {
+    DexField field = indexedItems.getInitClassLens().getInitClassField(clazz);
+    field.collectIndexedItems(indexedItems, method, instructionOffset);
+  }
+
+  @Override
+  public boolean canThrow() {
+    return true;
+  }
+
+  @Override
+  public String getName() {
+    return NAME;
+  }
+
+  @Override
+  public String getSmaliName() {
+    return SMALI_NAME;
+  }
+
+  @Override
+  public int getOpcode() {
+    throw new Unreachable();
+  }
+
+  private int getOpcode(DexField field) {
+    FieldMemberType type = FieldMemberType.fromDexType(field.type);
+    switch (type) {
+      case INT:
+      case FLOAT:
+        return Sget.OPCODE;
+      case LONG:
+      case DOUBLE:
+        return SgetWide.OPCODE;
+      case OBJECT:
+        return SgetObject.OPCODE;
+      case BOOLEAN:
+        return SgetBoolean.OPCODE;
+      case BYTE:
+        return SgetByte.OPCODE;
+      case CHAR:
+        return SgetChar.OPCODE;
+      case SHORT:
+        return SgetShort.OPCODE;
+      default:
+        throw new Unreachable("Unexpected type: " + type);
+    }
+  }
+
+  @Override
+  public void registerUse(UseRegistry registry) {
+    registry.registerInitClass(clazz);
+  }
+
+  @Override
+  public void write(ShortBuffer buffer, ObjectToOffsetMapping mapping) {
+    DexField field = mapping.getClinitField(clazz);
+    writeFirst(dest, buffer, getOpcode(field));
+    write16BitReference(field, buffer, mapping);
+  }
+
+  @Override
+  public int hashCode() {
+    return ((clazz.hashCode() << 8) | dest) ^ getClass().hashCode();
+  }
+
+  @Override
+  public boolean equals(Object other) {
+    if (other == null || getClass() != other.getClass()) {
+      return false;
+    }
+    DexInitClass initClass = (DexInitClass) other;
+    return dest == initClass.dest && clazz == initClass.clazz;
+  }
+
+  @Override
+  public String toSmaliString(ClassNameMapper naming) {
+    return formatSmaliString("v" + dest + ", " + clazz.toSmaliString());
+  }
+
+  @Override
+  public String toString(ClassNameMapper naming) {
+    StringBuilder builder = new StringBuilder("v").append(dest).append(", ");
+    if (naming == null) {
+      builder.append(clazz.toSourceString());
+    } else {
+      builder.append(naming.originalNameOf(clazz));
+    }
+    return formatString(builder.toString());
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/code/Instruction.java b/src/main/java/com/android/tools/r8/code/Instruction.java
index d549e28..ea056cb 100644
--- a/src/main/java/com/android/tools/r8/code/Instruction.java
+++ b/src/main/java/com/android/tools/r8/code/Instruction.java
@@ -82,7 +82,11 @@
   }
 
   protected void writeFirst(int aa, ShortBuffer dest) {
-    dest.put((short) (((aa & 0xff) << 8) | (getOpcode() & 0xff)));
+    writeFirst(aa, dest, getOpcode());
+  }
+
+  protected void writeFirst(int aa, ShortBuffer dest, int opcode) {
+    dest.put((short) (((aa & 0xff) << 8) | (opcode & 0xff)));
   }
 
   protected void writeFirst(int a, int b, ShortBuffer dest) {
diff --git a/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java b/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java
index 32a4bf7..c4c318c 100644
--- a/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java
+++ b/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java
@@ -34,6 +34,7 @@
 import com.android.tools.r8.graph.DexValue;
 import com.android.tools.r8.graph.EnclosingMethodAttribute;
 import com.android.tools.r8.graph.GraphLense;
+import com.android.tools.r8.graph.InitClassLens;
 import com.android.tools.r8.graph.InnerClassAttribute;
 import com.android.tools.r8.graph.ObjectToOffsetMapping;
 import com.android.tools.r8.graph.ParameterAnnotationsList;
@@ -69,6 +70,7 @@
   public final DexApplication application;
   public final AppView<?> appView;
   public final GraphLense graphLense;
+  public final InitClassLens initClassLens;
   public final NamingLens namingLens;
   public final InternalOptions options;
   private final CodeToKeep desugaredLibraryCodeToKeep;
@@ -80,10 +82,16 @@
 
   private static class SortAnnotations extends MixedSectionCollection {
 
+    private final NamingLens namingLens;
+
+    public SortAnnotations(NamingLens namingLens) {
+      this.namingLens = namingLens;
+    }
+
     @Override
     public boolean add(DexAnnotationSet dexAnnotationSet) {
       // Annotation sets are sorted by annotation types.
-      dexAnnotationSet.sort();
+      dexAnnotationSet.sort(namingLens);
       return true;
     }
 
@@ -141,6 +149,7 @@
       InternalOptions options,
       List<Marker> markers,
       GraphLense graphLense,
+      InitClassLens initClassLens,
       NamingLens namingLens,
       ProguardMapSupplier proguardMapSupplier) {
     this(
@@ -149,6 +158,7 @@
         options,
         markers,
         graphLense,
+        initClassLens,
         namingLens,
         proguardMapSupplier,
         null);
@@ -160,6 +170,7 @@
       InternalOptions options,
       List<Marker> markers,
       GraphLense graphLense,
+      InitClassLens initClassLens,
       NamingLens namingLens,
       ProguardMapSupplier proguardMapSupplier,
       DexIndexedConsumer consumer) {
@@ -171,6 +182,7 @@
     this.desugaredLibraryCodeToKeep = CodeToKeep.createCodeToKeep(options, namingLens);
     this.markers = markers;
     this.graphLense = graphLense;
+    this.initClassLens = initClassLens;
     this.namingLens = namingLens;
     this.proguardMapSupplier = proguardMapSupplier;
     this.programConsumer = consumer;
@@ -238,6 +250,7 @@
       }
     }
     try {
+      // TODO(b/151313715): Move this to the writer threads.
       insertAttributeAnnotations();
 
       // Generate the dex file contents.
@@ -246,21 +259,12 @@
       if (options.encodeChecksums) {
         encodeChecksums(virtualFiles);
       }
-      // TODO(b/149190785): Only sort the live program!
-      if (appView != null) {
-        appView.appInfo().disableDefinitionForAssert();
-      }
-      namingLens.setIsSortingBeforeWriting(true);
-      application.dexItemFactory.sort(namingLens);
-      namingLens.setIsSortingBeforeWriting(false);
-      if (appView != null) {
-        appView.appInfo().enableDefinitionForAssert();
-      }
       assert markers == null
           || markers.isEmpty()
           || application.dexItemFactory.extractMarkers() != null;
 
-      SortAnnotations sortAnnotations = new SortAnnotations();
+      // TODO(b/151313617): Sorting annotations mutates elements so run single threaded on main.
+      SortAnnotations sortAnnotations = new SortAnnotations(namingLens);
       application.classes().forEach((clazz) -> clazz.addDependencies(sortAnnotations));
 
       for (VirtualFile virtualFile : virtualFiles) {
@@ -290,7 +294,8 @@
                       byteBufferProvider = options.getDexIndexedConsumer();
                     }
                   }
-                  ObjectToOffsetMapping objectMapping = virtualFile.computeMapping(application);
+                  ObjectToOffsetMapping objectMapping =
+                      virtualFile.computeMapping(application, namingLens, initClassLens);
                   MethodToCodeObjectMapping codeMapping =
                       rewriteCodeWithJumboStrings(
                           objectMapping, virtualFile.classes(), application);
diff --git a/src/main/java/com/android/tools/r8/dex/FileWriter.java b/src/main/java/com/android/tools/r8/dex/FileWriter.java
index 8b0cd00..a7c3ec0 100644
--- a/src/main/java/com/android/tools/r8/dex/FileWriter.java
+++ b/src/main/java/com/android/tools/r8/dex/FileWriter.java
@@ -21,6 +21,7 @@
 import com.android.tools.r8.graph.DexCode.TryHandler;
 import com.android.tools.r8.graph.DexCode.TryHandler.TypeAddrPair;
 import com.android.tools.r8.graph.DexDebugInfo;
+import com.android.tools.r8.graph.DexDebugInfoForWriting;
 import com.android.tools.r8.graph.DexEncodedAnnotation;
 import com.android.tools.r8.graph.DexEncodedArray;
 import com.android.tools.r8.graph.DexEncodedField;
@@ -41,7 +42,6 @@
 import com.android.tools.r8.graph.IndexedDexItem;
 import com.android.tools.r8.graph.ObjectToOffsetMapping;
 import com.android.tools.r8.graph.ParameterAnnotationsList;
-import com.android.tools.r8.graph.PresortedComparable;
 import com.android.tools.r8.graph.ProgramClassVisitor;
 import com.android.tools.r8.logging.Log;
 import com.android.tools.r8.naming.ClassNameMapper;
@@ -59,10 +59,12 @@
 import it.unimi.dsi.fastutil.objects.Reference2IntMap;
 import java.security.MessageDigest;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.Comparator;
 import java.util.HashMap;
+import java.util.HashSet;
 import java.util.IdentityHashMap;
 import java.util.List;
 import java.util.Map;
@@ -94,6 +96,7 @@
   private final DexOutputBuffer dest;
   private final MixedSectionOffsets mixedSectionOffsets;
   private final CodeToKeep desugaredLibraryCodeToKeep;
+  private final Map<DexProgramClass, DexEncodedArray> staticFieldValues = new IdentityHashMap<>();
 
   public FileWriter(
       ByteBufferProvider provider,
@@ -118,10 +121,11 @@
     if (Log.ENABLED) {
       Log.verbose(FileWriter.class, "Writing encoded annotation @ %08x", dest.position());
     }
+    List<DexAnnotationElement> elements = new ArrayList<>(Arrays.asList(annotation.elements));
+    elements.sort((a, b) -> a.name.slowCompareTo(b.name, mapping.getNamingLens()));
     dest.putUleb128(mapping.getOffsetFor(annotation.type));
-    dest.putUleb128(annotation.elements.length);
-    assert PresortedComparable.isSorted(annotation.elements, (element) -> element.name);
-    for (DexAnnotationElement element : annotation.elements) {
+    dest.putUleb128(elements.size());
+    for (DexAnnotationElement element : elements) {
       dest.putUleb128(mapping.getOffsetFor(element.name));
       element.value.writeTo(dest, mapping);
     }
@@ -132,8 +136,6 @@
     new ProgramClassDependencyCollector(application, mapping.getClasses())
         .run(mapping.getClasses());
 
-    // Ensure everything is sorted.
-    assert mixedSectionOffsets.getClassesWithData().stream().allMatch(DexProgramClass::isSorted);
     // Add the static values for all fields now that we have committed to their sorting.
     mixedSectionOffsets.getClassesWithData().forEach(this::addStaticFieldValues);
 
@@ -172,11 +174,22 @@
 
     // Output the debug_info_items first, as they have no dependencies.
     dest.moveTo(layout.getCodesOffset() + sizeOfCodeItems(codes));
-    writeItems(mixedSectionOffsets.getDebugInfos(), layout::setDebugInfosOffset,
-        this::writeDebugItem);
+    if (mixedSectionOffsets.getDebugInfos().isEmpty()) {
+      layout.setDebugInfosOffset(0);
+    } else {
+      // Ensure deterministic ordering of debug info by sorting consistent with the code objects.
+      layout.setDebugInfosOffset(dest.align(1));
+      Set<DexDebugInfo> seen = new HashSet<>(mixedSectionOffsets.getDebugInfos().size());
+      for (DexCode code : codes) {
+        DexDebugInfoForWriting info = code.getDebugInfoForWriting();
+        if (info != null && seen.add(info)) {
+          writeDebugItem(info);
+        }
+      }
+    }
 
     // Remember the typelist offset for later.
-    layout.setTypeListsOffset(dest.align(4));  // type_list are aligned.
+    layout.setTypeListsOffset(dest.align(4)); // type_list are aligned.
 
     // Now output the code.
     dest.moveTo(layout.getCodesOffset());
@@ -465,7 +478,7 @@
     dest.putInt(mixedSectionOffsets.getOffsetForAnnotationsDirectory(clazz));
     dest.putInt(
         clazz.hasMethodsOrFields() ? mixedSectionOffsets.getOffsetFor(clazz) : Constants.NO_OFFSET);
-    dest.putInt(mixedSectionOffsets.getOffsetFor(clazz.getStaticValues()));
+    dest.putInt(mixedSectionOffsets.getOffsetFor(staticFieldValues.get(clazz)));
   }
 
   private void writeDebugItem(DexDebugInfo debugInfo) {
@@ -551,14 +564,14 @@
   }
 
   private void writeAnnotationSet(DexAnnotationSet set) {
-    assert PresortedComparable.isSorted(set.annotations, (item) -> item.annotation.type)
-        : "Unsorted annotation set: " + set.toSourceString();
     mixedSectionOffsets.setOffsetFor(set, dest.align(4));
     if (Log.ENABLED) {
       Log.verbose(getClass(), "Writing AnnotationSet @ 0x%08x.", dest.position());
     }
-    dest.putInt(set.annotations.length);
-    for (DexAnnotation annotation : set.annotations) {
+    List<DexAnnotation> annotations = new ArrayList<>(Arrays.asList(set.annotations));
+    annotations.sort((a, b) -> a.annotation.type.slowCompareTo(b.annotation.type, namingLens));
+    dest.putInt(annotations.size());
+    for (DexAnnotation annotation : annotations) {
       dest.putInt(mixedSectionOffsets.getOffsetFor(annotation));
     }
   }
@@ -588,9 +601,11 @@
   private void writeAnnotationDirectory(DexAnnotationDirectory annotationDirectory) {
     mixedSectionOffsets.setOffsetForAnnotationsDirectory(annotationDirectory, dest.align(4));
     dest.putInt(mixedSectionOffsets.getOffsetFor(annotationDirectory.getClazzAnnotations()));
-    List<DexEncodedMethod> methodAnnotations = annotationDirectory.getMethodAnnotations();
-    List<DexEncodedMethod> parameterAnnotations = annotationDirectory.getParameterAnnotations();
-    List<DexEncodedField> fieldAnnotations = annotationDirectory.getFieldAnnotations();
+    List<DexEncodedMethod> methodAnnotations =
+        annotationDirectory.sortMethodAnnotations(namingLens);
+    List<DexEncodedMethod> parameterAnnotations =
+        annotationDirectory.sortParameterAnnotations(namingLens);
+    List<DexEncodedField> fieldAnnotations = annotationDirectory.sortFieldAnnotations(namingLens);
     dest.putInt(fieldAnnotations.size());
     dest.putInt(methodAnnotations.size());
     dest.putInt(parameterAnnotations.size());
@@ -602,10 +617,12 @@
         item -> mixedSectionOffsets.getOffsetFor(item.parameterAnnotationsList));
   }
 
-  private void writeEncodedFields(List<DexEncodedField> fields) {
-    assert PresortedComparable.isSorted(fields);
+  private void writeEncodedFields(List<DexEncodedField> unsortedFields) {
+    List<DexEncodedField> fields = new ArrayList<>(unsortedFields);
+    fields.sort((a, b) -> a.field.slowCompareTo(b.field, namingLens));
     int currentOffset = 0;
     for (DexEncodedField field : fields) {
+      assert field.validateDexValue(application.dexItemFactory);
       int nextOffset = mapping.getOffsetFor(field.field);
       assert nextOffset - currentOffset >= 0;
       dest.putUleb128(nextOffset - currentOffset);
@@ -615,8 +632,10 @@
     }
   }
 
-  private void writeEncodedMethods(List<DexEncodedMethod> methods, boolean isSharedSynthetic) {
-    assert PresortedComparable.isSorted(methods);
+  private void writeEncodedMethods(
+      List<DexEncodedMethod> unsortedMethods, boolean isSharedSynthetic) {
+    List<DexEncodedMethod> methods = new ArrayList<>(unsortedMethods);
+    methods.sort((a, b) -> a.method.slowCompareTo(b.method, namingLens));
     int currentOffset = 0;
     for (DexEncodedMethod method : methods) {
       int nextOffset = mapping.getOffsetFor(method.method);
@@ -657,12 +676,12 @@
   }
 
   private void addStaticFieldValues(DexProgramClass clazz) {
-    clazz.computeStaticValues();
     // We have collected the individual components of this array due to the data stored in
     // DexEncodedField#staticValues. However, we have to collect the DexEncodedArray itself
     // here.
-    DexEncodedArray staticValues = clazz.getStaticValues();
+    DexEncodedArray staticValues = clazz.computeStaticValuesArray(namingLens);
     if (staticValues != null) {
+      staticFieldValues.put(clazz, staticValues);
       mixedSectionOffsets.add(staticValues);
     }
   }
diff --git a/src/main/java/com/android/tools/r8/dex/IndexedItemCollection.java b/src/main/java/com/android/tools/r8/dex/IndexedItemCollection.java
index 4da7302..45b8bfe 100644
--- a/src/main/java/com/android/tools/r8/dex/IndexedItemCollection.java
+++ b/src/main/java/com/android/tools/r8/dex/IndexedItemCollection.java
@@ -13,6 +13,7 @@
 import com.android.tools.r8.graph.DexString;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.IndexedDexItem;
+import com.android.tools.r8.graph.InitClassLens;
 
 /**
  * Common interface for constant pools.
@@ -99,6 +100,10 @@
    */
   boolean addMethodHandle(DexMethodHandle methodHandle);
 
+  default InitClassLens getInitClassLens() {
+    return InitClassLens.getDefault();
+  }
+
   default DexString getRenamedName(DexMethod method) {
     return method.name;
   }
diff --git a/src/main/java/com/android/tools/r8/dex/InheritanceClassInDexDistributor.java b/src/main/java/com/android/tools/r8/dex/InheritanceClassInDexDistributor.java
index 37649d3..e623f5e 100644
--- a/src/main/java/com/android/tools/r8/dex/InheritanceClassInDexDistributor.java
+++ b/src/main/java/com/android/tools/r8/dex/InheritanceClassInDexDistributor.java
@@ -10,6 +10,7 @@
 import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.InitClassLens;
 import com.android.tools.r8.naming.NamingLens;
 import com.android.tools.r8.utils.ThreadUtils;
 import com.google.common.collect.Maps;
@@ -69,7 +70,7 @@
 
     public void updateNumbersOfIds() {
       // Use a temporary VirtualFile to evaluate the number of ids in the group.
-      VirtualFile virtualFile = new VirtualFile(0, namingLens);
+      VirtualFile virtualFile = new VirtualFile(0, initClassLens, namingLens);
       // Note: sort not needed.
       for (DexProgramClass clazz : members) {
         virtualFile.addClass(clazz);
@@ -283,6 +284,7 @@
   private final Set<DexProgramClass> classes;
   private final DexApplication app;
   private int dexIndexOffset;
+  private final InitClassLens initClassLens;
   private final NamingLens namingLens;
   private final DirectSubClassesInfo directSubClasses;
 
@@ -290,8 +292,8 @@
       VirtualFile mainDex,
       List<VirtualFile> dexes,
       Set<DexProgramClass> classes,
-      Map<DexProgramClass, String> originalNames,
       int dexIndexOffset,
+      InitClassLens initClassLens,
       NamingLens namingLens,
       DexApplication app,
       ExecutorService executorService) {
@@ -299,6 +301,7 @@
     this.dexes = dexes;
     this.classes = classes;
     this.dexIndexOffset = dexIndexOffset;
+    this.initClassLens = initClassLens;
     this.namingLens = namingLens;
     this.app = app;
     this.executorService = executorService;
@@ -370,7 +373,8 @@
   }
 
   private Collection<VirtualFile> assignGroup(ClassGroup group, List<VirtualFile> dexBlackList) {
-    VirtualFileCycler cycler = new VirtualFileCycler(dexes, namingLens, dexIndexOffset);
+    VirtualFileCycler cycler =
+        new VirtualFileCycler(dexes, initClassLens, namingLens, dexIndexOffset);
     if (group.members.isEmpty()) {
       return Collections.emptyList();
     } else if (group.canFitInOneDex()) {
@@ -418,7 +422,8 @@
     Collections.sort(layers);
 
     Collection<VirtualFile> usedDex = new ArrayList<>();
-    VirtualFileCycler cycler = new VirtualFileCycler(dexes, namingLens, dexIndexOffset);
+    VirtualFileCycler cycler =
+        new VirtualFileCycler(dexes, initClassLens, namingLens, dexIndexOffset);
     // Don't modify input dexBlackList. Think about modifying the input collection considering this
     // is private API.
     Set<VirtualFile> currentBlackList = new HashSet<>(dexBlackList);
diff --git a/src/main/java/com/android/tools/r8/dex/JumboStringRewriter.java b/src/main/java/com/android/tools/r8/dex/JumboStringRewriter.java
index 2936d80..e05af85 100644
--- a/src/main/java/com/android/tools/r8/dex/JumboStringRewriter.java
+++ b/src/main/java/com/android/tools/r8/dex/JumboStringRewriter.java
@@ -284,7 +284,7 @@
         instruction.setOffset(orignalOffset + offsetDelta);
         if (instruction instanceof ConstString) {
           ConstString string = (ConstString) instruction;
-          if (string.getString().compareTo(firstJumboString) >= 0) {
+          if (string.getString().slowCompareTo(firstJumboString) >= 0) {
             ConstStringJumbo jumboString = new ConstStringJumbo(string.AA, string.getString());
             jumboString.setOffset(string.getOffset());
             offsetDelta++;
diff --git a/src/main/java/com/android/tools/r8/dex/VirtualFile.java b/src/main/java/com/android/tools/r8/dex/VirtualFile.java
index 02a6129..a9ff477 100644
--- a/src/main/java/com/android/tools/r8/dex/VirtualFile.java
+++ b/src/main/java/com/android/tools/r8/dex/VirtualFile.java
@@ -18,6 +18,7 @@
 import com.android.tools.r8.graph.DexProto;
 import com.android.tools.r8.graph.DexString;
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.InitClassLens;
 import com.android.tools.r8.graph.ObjectToOffsetMapping;
 import com.android.tools.r8.logging.Log;
 import com.android.tools.r8.naming.ClassNameMapper;
@@ -75,23 +76,29 @@
 
   private final DexProgramClass primaryClass;
 
-  VirtualFile(int id, NamingLens namingLens) {
-    this(id, namingLens, null, null);
+  VirtualFile(int id, InitClassLens initClassLens, NamingLens namingLens) {
+    this(id, initClassLens, namingLens, null, null);
   }
 
-  VirtualFile(int id, NamingLens namingLens, FeatureSplit featureSplit) {
-    this(id, namingLens, null, featureSplit);
-  }
-
-  private VirtualFile(int id, NamingLens namingLens, DexProgramClass primaryClass) {
-    this(id, namingLens, primaryClass, null);
+  VirtualFile(
+      int id, InitClassLens initClassLens, NamingLens namingLens, FeatureSplit featureSplit) {
+    this(id, initClassLens, namingLens, null, featureSplit);
   }
 
   private VirtualFile(
-      int id, NamingLens namingLens, DexProgramClass primaryClass, FeatureSplit featureSplit) {
+      int id, InitClassLens initClassLens, NamingLens namingLens, DexProgramClass primaryClass) {
+    this(id, initClassLens, namingLens, primaryClass, null);
+  }
+
+  private VirtualFile(
+      int id,
+      InitClassLens initClassLens,
+      NamingLens namingLens,
+      DexProgramClass primaryClass,
+      FeatureSplit featureSplit) {
     this.id = id;
-    this.indexedItems = new VirtualFileIndexedItemCollection(namingLens);
-    this.transaction = new IndexedItemTransaction(indexedItems, namingLens);
+    this.indexedItems = new VirtualFileIndexedItemCollection(initClassLens, namingLens);
+    this.transaction = new IndexedItemTransaction(indexedItems, initClassLens, namingLens);
     this.primaryClass = primaryClass;
     this.featureSplit = featureSplit;
   }
@@ -174,10 +181,13 @@
     return prefix;
   }
 
-  public ObjectToOffsetMapping computeMapping(DexApplication application) {
+  public ObjectToOffsetMapping computeMapping(
+      DexApplication application, NamingLens namingLens, InitClassLens initClassLens) {
     assert transaction.isEmpty();
     return new ObjectToOffsetMapping(
         application,
+        namingLens,
+        initClassLens,
         indexedItems.classes,
         indexedItems.protos,
         indexedItems.types,
@@ -273,7 +283,8 @@
       // Assign dedicated virtual files for all program classes.
       for (DexProgramClass clazz : application.classes()) {
         if (!combineSyntheticClassesWithPrimaryClass || clazz.getSynthesizedFrom().isEmpty()) {
-          VirtualFile file = new VirtualFile(virtualFiles.size(), writer.namingLens, clazz);
+          VirtualFile file =
+              new VirtualFile(virtualFiles.size(), writer.initClassLens, writer.namingLens, clazz);
           virtualFiles.add(file);
           file.addClass(clazz);
           files.put(clazz, file);
@@ -306,7 +317,7 @@
       this.options = options;
 
       // Create the primary dex file. The distribution will add more if needed.
-      mainDexFile = new VirtualFile(0, writer.namingLens);
+      mainDexFile = new VirtualFile(0, writer.initClassLens, writer.namingLens);
       assert virtualFiles.isEmpty();
       virtualFiles.add(mainDexFile);
       addMarkers(mainDexFile);
@@ -423,7 +434,8 @@
           featureSplitClasses.entrySet()) {
         // Add a new virtual file, start from index 0 again
         VirtualFile featureFile =
-            new VirtualFile(0, writer.namingLens, featureSplitSetEntry.getKey());
+            new VirtualFile(
+                0, writer.initClassLens, writer.namingLens, featureSplitSetEntry.getKey());
         virtualFiles.add(featureFile);
         addMarkers(featureFile);
         Set<DexProgramClass> featureClasses =
@@ -437,6 +449,7 @@
                 application.dexItemFactory,
                 fillStrategy,
                 0,
+                writer.initClassLens,
                 writer.namingLens)
             .call();
       }
@@ -471,7 +484,7 @@
         assert !virtualFiles.get(0).isEmpty();
         assert virtualFiles.size() == 1;
         // The main dex file is filtered out, so ensure at least one file for the remaining classes.
-        virtualFiles.add(new VirtualFile(1, writer.namingLens));
+        virtualFiles.add(new VirtualFile(1, writer.initClassLens, writer.namingLens));
         filesForDistribution = virtualFiles.subList(1, virtualFiles.size());
         fileIndexOffset = 1;
       }
@@ -480,16 +493,29 @@
           removeFeatureSplitClassesGetMapping();
 
       if (multidexLegacy && options.enableInheritanceClassInDexDistributor) {
-        new InheritanceClassInDexDistributor(mainDexFile, filesForDistribution, classes,
-            originalNames, fileIndexOffset, writer.namingLens, writer.application, executorService)
+        new InheritanceClassInDexDistributor(
+                mainDexFile,
+                filesForDistribution,
+                classes,
+                fileIndexOffset,
+                writer.initClassLens,
+                writer.namingLens,
+                writer.application,
+                executorService)
             .distribute();
       } else {
         // Sort the remaining classes based on the original names.
         // This with make classes from the same package be adjacent.
         classes = sortClassesByPackage(classes, originalNames);
         new PackageSplitPopulator(
-            filesForDistribution, classes, originalNames, application.dexItemFactory,
-            fillStrategy, fileIndexOffset, writer.namingLens)
+                filesForDistribution,
+                classes,
+                originalNames,
+                application.dexItemFactory,
+                fillStrategy,
+                fileIndexOffset,
+                writer.initClassLens,
+                writer.namingLens)
             .call();
       }
       addFeatureSplitFiles(featureSplitClasses, fillStrategy);
@@ -526,6 +552,7 @@
 
   private static class VirtualFileIndexedItemCollection implements IndexedItemCollection {
 
+    private final InitClassLens initClassLens;
     private final NamingLens namingLens;
 
     private final Set<DexProgramClass> classes = Sets.newIdentityHashSet();
@@ -537,10 +564,9 @@
     private final Set<DexCallSite> callSites = Sets.newIdentityHashSet();
     private final Set<DexMethodHandle> methodHandles = Sets.newIdentityHashSet();
 
-    public VirtualFileIndexedItemCollection(
-        NamingLens namingLens) {
+    public VirtualFileIndexedItemCollection(InitClassLens initClassLens, NamingLens namingLens) {
+      this.initClassLens = initClassLens;
       this.namingLens = namingLens;
-
     }
 
     @Override
@@ -596,6 +622,11 @@
     }
 
     @Override
+    public InitClassLens getInitClassLens() {
+      return initClassLens;
+    }
+
+    @Override
     public DexString getRenamedDescriptor(DexType type) {
       return namingLens.lookupDescriptor(type);
     }
@@ -615,6 +646,7 @@
   public static class IndexedItemTransaction implements IndexedItemCollection {
 
     private final VirtualFileIndexedItemCollection base;
+    private final InitClassLens initClassLens;
     private final NamingLens namingLens;
 
     private final Set<DexProgramClass> classes = new LinkedHashSet<>();
@@ -626,9 +658,10 @@
     private final Set<DexCallSite> callSites = new LinkedHashSet<>();
     private final Set<DexMethodHandle> methodHandles = new LinkedHashSet<>();
 
-    private IndexedItemTransaction(VirtualFileIndexedItemCollection base,
-        NamingLens namingLens) {
+    private IndexedItemTransaction(
+        VirtualFileIndexedItemCollection base, InitClassLens initClassLens, NamingLens namingLens) {
       this.base = base;
+      this.initClassLens = initClassLens;
       this.namingLens = namingLens;
     }
 
@@ -685,6 +718,11 @@
     }
 
     @Override
+    public InitClassLens getInitClassLens() {
+      return initClassLens;
+    }
+
+    @Override
     public DexString getRenamedDescriptor(DexType type) {
       return namingLens.lookupDescriptor(type);
     }
@@ -760,6 +798,7 @@
   static class VirtualFileCycler {
 
     private final List<VirtualFile> files;
+    private final InitClassLens initClassLens;
     private final NamingLens namingLens;
 
     private int nextFileId;
@@ -767,8 +806,13 @@
     private Iterator<VirtualFile> activeFiles;
     private FeatureSplit featuresplit;
 
-    VirtualFileCycler(List<VirtualFile> files, NamingLens namingLens, int fileIndexOffset) {
+    VirtualFileCycler(
+        List<VirtualFile> files,
+        InitClassLens initClassLens,
+        NamingLens namingLens,
+        int fileIndexOffset) {
       this.files = files;
+      this.initClassLens = initClassLens;
       this.namingLens = namingLens;
 
       nextFileId = files.size() + fileIndexOffset;
@@ -799,7 +843,8 @@
       if (hasNext()) {
         return activeFiles.next();
       } else {
-        VirtualFile newFile = new VirtualFile(nextFileId++, namingLens, featuresplit);
+        VirtualFile newFile =
+            new VirtualFile(nextFileId++, initClassLens, namingLens, featuresplit);
         files.add(newFile);
         allFilesCyclic = Iterators.cycle(files);
         return newFile;
@@ -830,7 +875,7 @@
     }
 
     VirtualFile addFile() {
-      VirtualFile newFile = new VirtualFile(nextFileId++, namingLens, featuresplit);
+      VirtualFile newFile = new VirtualFile(nextFileId++, initClassLens, namingLens, featuresplit);
       files.add(newFile);
 
       reset();
@@ -875,12 +920,13 @@
         DexItemFactory dexItemFactory,
         FillStrategy fillStrategy,
         int fileIndexOffset,
+        InitClassLens initClassLens,
         NamingLens namingLens) {
       this.classes = new ArrayList<>(classes);
       this.originalNames = originalNames;
       this.dexItemFactory = dexItemFactory;
       this.fillStrategy = fillStrategy;
-      this.cycler = new VirtualFileCycler(files, namingLens, fileIndexOffset);
+      this.cycler = new VirtualFileCycler(files, initClassLens, namingLens, fileIndexOffset);
     }
 
     static boolean coveredByPrefix(String originalName, String currentPrefix) {
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 640b7a1..2ddba3f 100644
--- a/src/main/java/com/android/tools/r8/graph/AppInfo.java
+++ b/src/main/java/com/android/tools/r8/graph/AppInfo.java
@@ -705,10 +705,4 @@
         entry.getKey(),
         entry.getValue());
   }
-
-  // TODO(b/149190785): Remove once fixed.
-  public void enableDefinitionForAssert() {}
-
-  // TODO(b/149190785): Remove once fixed.
-  public void disableDefinitionForAssert() {}
 }
diff --git a/src/main/java/com/android/tools/r8/graph/AppInfoWithSubtyping.java b/src/main/java/com/android/tools/r8/graph/AppInfoWithSubtyping.java
index d1e70ed..5f2b826 100644
--- a/src/main/java/com/android/tools/r8/graph/AppInfoWithSubtyping.java
+++ b/src/main/java/com/android/tools/r8/graph/AppInfoWithSubtyping.java
@@ -381,7 +381,7 @@
       return false;
     }
     assert potentialHolder.isInterface();
-    for (DexEncodedMethod virtualMethod : potentialHolder.virtualMethods) {
+    for (DexEncodedMethod virtualMethod : potentialHolder.virtualMethods()) {
       if (virtualMethod.method.hasSameProtoAndName(method.method)
           && virtualMethod.accessFlags.isSameVisibility(method.accessFlags)) {
         return true;
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 9ed88a6..e504e54 100644
--- a/src/main/java/com/android/tools/r8/graph/AppView.java
+++ b/src/main/java/com/android/tools/r8/graph/AppView.java
@@ -45,6 +45,7 @@
   private final DexItemFactory dexItemFactory;
   private final WholeProgramOptimizations wholeProgramOptimizations;
   private GraphLense graphLense;
+  private InitClassLens initClassLens;
   private final InternalOptions options;
   private RootSet rootSet;
   private final AbstractValueFactory abstractValueFactory = new AbstractValueFactory();
@@ -90,6 +91,7 @@
     this.dexItemFactory = appInfo != null ? appInfo.dexItemFactory() : null;
     this.wholeProgramOptimizations = wholeProgramOptimizations;
     this.graphLense = GraphLense.getIdentityLense();
+    this.initClassLens = InitClassLens.getDefault();
     this.options = options;
     this.rewritePrefix = mapper;
 
@@ -310,6 +312,18 @@
     return false;
   }
 
+  public boolean canUseInitClass() {
+    return options.shouldRerunEnqueuer() && !initClassLens.isFinal();
+  }
+
+  public InitClassLens initClassLens() {
+    return initClassLens;
+  }
+
+  public void setInitClassLens(InitClassLens initClassLens) {
+    this.initClassLens = initClassLens;
+  }
+
   public void setInitializedClassesInInstanceMethods(
       InitializedClassesInInstanceMethods initializedClassesInInstanceMethods) {
     this.initializedClassesInInstanceMethods = initializedClassesInInstanceMethods;
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 4f8fa23..0c4267c 100644
--- a/src/main/java/com/android/tools/r8/graph/CfCode.java
+++ b/src/main/java/com/android/tools/r8/graph/CfCode.java
@@ -202,11 +202,12 @@
       NamingLens namingLens,
       AppView<?> appView,
       int classFileVersion) {
+    InitClassLens initClassLens = appView.initClassLens();
     InternalOptions options = appView.options();
     CfLabel parameterLabel = null;
     if (shouldAddParameterNames(method, appView)) {
       parameterLabel = new CfLabel();
-      parameterLabel.write(visitor, namingLens);
+      parameterLabel.write(visitor, initClassLens, namingLens);
     }
     for (CfInstruction instruction : instructions) {
       if (instruction instanceof CfFrame
@@ -214,7 +215,7 @@
           || (classFileVersion == 50 && !options.shouldKeepStackMapTable()))) {
         continue;
       }
-      instruction.write(visitor, namingLens);
+      instruction.write(visitor, initClassLens, namingLens);
     }
     visitor.visitEnd();
     visitor.visitMaxs(maxStack, maxLocals);
diff --git a/src/main/java/com/android/tools/r8/graph/DefaultInitClassLens.java b/src/main/java/com/android/tools/r8/graph/DefaultInitClassLens.java
new file mode 100644
index 0000000..56a1733
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/graph/DefaultInitClassLens.java
@@ -0,0 +1,23 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.graph;
+
+import com.android.tools.r8.errors.Unreachable;
+
+public class DefaultInitClassLens extends InitClassLens {
+
+  private static final DefaultInitClassLens INSTANCE = new DefaultInitClassLens();
+
+  private DefaultInitClassLens() {}
+
+  public static DefaultInitClassLens getInstance() {
+    return INSTANCE;
+  }
+
+  @Override
+  public DexField getInitClassField(DexType type) {
+    throw new Unreachable("Unexpected InitClass instruction for `" + type.toSourceString() + "`");
+  }
+}
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 bb209b5..7d6b095 100644
--- a/src/main/java/com/android/tools/r8/graph/DefaultUseRegistry.java
+++ b/src/main/java/com/android/tools/r8/graph/DefaultUseRegistry.java
@@ -11,6 +11,11 @@
   }
 
   @Override
+  public boolean registerInitClass(DexType clazz) {
+    return true;
+  }
+
+  @Override
   public boolean registerInvokeVirtual(DexMethod method) {
     return true;
   }
diff --git a/src/main/java/com/android/tools/r8/graph/DexAnnotationDirectory.java b/src/main/java/com/android/tools/r8/graph/DexAnnotationDirectory.java
index 2d0667b..a6b7824 100644
--- a/src/main/java/com/android/tools/r8/graph/DexAnnotationDirectory.java
+++ b/src/main/java/com/android/tools/r8/graph/DexAnnotationDirectory.java
@@ -6,10 +6,9 @@
 import com.android.tools.r8.dex.IndexedItemCollection;
 import com.android.tools.r8.dex.MixedSectionCollection;
 import com.android.tools.r8.errors.Unreachable;
-import com.android.tools.r8.utils.OrderedMergingIterator;
+import com.android.tools.r8.naming.NamingLens;
 import java.util.ArrayList;
 import java.util.List;
-import java.util.function.Function;
 
 public class DexAnnotationDirectory extends DexItem {
 
@@ -22,28 +21,21 @@
   public DexAnnotationDirectory(DexProgramClass clazz) {
     this.clazz = clazz;
     this.classHasOnlyInternalizableAnnotations = clazz.hasOnlyInternalizableAnnotations();
-    assert isSorted(clazz.directMethods());
-    assert isSorted(clazz.virtualMethods());
-    OrderedMergingIterator<DexEncodedMethod, DexMethod> methods =
-        new OrderedMergingIterator<>(clazz.directMethods(), clazz.virtualMethods());
     methodAnnotations = new ArrayList<>();
     parameterAnnotations = new ArrayList<>();
-    while (methods.hasNext()) {
-      DexEncodedMethod method = methods.next();
-      if (!method.annotations().isEmpty()) {
-        methodAnnotations.add(method);
-      }
-      if (!method.parameterAnnotationsList.isEmpty()) {
-        parameterAnnotations.add(method);
-      }
-    }
-    assert isSorted(clazz.staticFields());
-    assert isSorted(clazz.instanceFields());
-    OrderedMergingIterator<DexEncodedField, DexField> fields =
-        new OrderedMergingIterator<>(clazz.staticFields(), clazz.instanceFields());
     fieldAnnotations = new ArrayList<>();
-    while (fields.hasNext()) {
-      DexEncodedField field = fields.next();
+    clazz
+        .getMethodCollection()
+        .forEachMethod(
+            method -> {
+              if (!method.annotations().isEmpty()) {
+                methodAnnotations.add(method);
+              }
+              if (!method.parameterAnnotationsList.isEmpty()) {
+                parameterAnnotations.add(method);
+              }
+            });
+    for (DexEncodedField field : clazz.fields()) {
       if (!field.annotations().isEmpty()) {
         fieldAnnotations.add(field);
       }
@@ -54,15 +46,18 @@
     return clazz.annotations();
   }
 
-  public List<DexEncodedMethod> getMethodAnnotations() {
+  public List<DexEncodedMethod> sortMethodAnnotations(NamingLens namingLens) {
+    methodAnnotations.sort((a, b) -> a.method.slowCompareTo(b.method, namingLens));
     return methodAnnotations;
   }
 
-  public List<DexEncodedMethod> getParameterAnnotations() {
+  public List<DexEncodedMethod> sortParameterAnnotations(NamingLens namingLens) {
+    parameterAnnotations.sort((a, b) -> a.method.slowCompareTo(b.method, namingLens));
     return parameterAnnotations;
   }
 
-  public List<DexEncodedField> getFieldAnnotations() {
+  public List<DexEncodedField> sortFieldAnnotations(NamingLens namingLens) {
+    fieldAnnotations.sort((a, b) -> a.field.slowCompareTo(b.field, namingLens));
     return fieldAnnotations;
   }
 
@@ -106,22 +101,4 @@
   public void collectMixedSectionItems(MixedSectionCollection collection) {
     throw new Unreachable();
   }
-
-  private static <D extends DexEncodedMember<D, R>, R extends DexMember<D, R>> boolean isSorted(
-      List<D> items) {
-    return isSorted(items, DexEncodedMember::toReference);
-  }
-
-  private static <S, T extends Comparable<T>> boolean isSorted(
-      List<S> items, Function<S, T> getter) {
-    T current = null;
-    for (S item : items) {
-      T next = getter.apply(item);
-      if (current != null && current.compareTo(next) >= 0) {
-        return false;
-      }
-      current = next;
-    }
-    return true;
-  }
 }
diff --git a/src/main/java/com/android/tools/r8/graph/DexAnnotationSet.java b/src/main/java/com/android/tools/r8/graph/DexAnnotationSet.java
index dd24ed6..7371902 100644
--- a/src/main/java/com/android/tools/r8/graph/DexAnnotationSet.java
+++ b/src/main/java/com/android/tools/r8/graph/DexAnnotationSet.java
@@ -5,10 +5,10 @@
 
 import com.android.tools.r8.dex.IndexedItemCollection;
 import com.android.tools.r8.dex.MixedSectionCollection;
+import com.android.tools.r8.naming.NamingLens;
 import com.android.tools.r8.utils.ArrayUtils;
 import com.google.common.collect.Sets;
 import java.util.Arrays;
-import java.util.Comparator;
 import java.util.List;
 import java.util.Set;
 import java.util.function.Consumer;
@@ -93,12 +93,13 @@
     return annotations.length == 0;
   }
 
-  public void sort() {
+  public void sort(NamingLens namingLens) {
     if (sorted != UNSORTED) {
       assert sorted == sortedHashCode();
       return;
     }
-    Arrays.sort(annotations, Comparator.comparing(a -> a.annotation.type));
+    Arrays.sort(
+        annotations, (a, b) -> a.annotation.type.slowCompareTo(b.annotation.type, namingLens));
     for (DexAnnotation annotation : annotations) {
       annotation.annotation.sort();
     }
diff --git a/src/main/java/com/android/tools/r8/graph/DexClass.java b/src/main/java/com/android/tools/r8/graph/DexClass.java
index a75fd4e..773a71a 100644
--- a/src/main/java/com/android/tools/r8/graph/DexClass.java
+++ b/src/main/java/com/android/tools/r8/graph/DexClass.java
@@ -10,7 +10,6 @@
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.OptionalBool;
-import com.android.tools.r8.utils.PredicateUtils;
 import com.google.common.base.MoreObjects;
 import com.google.common.base.Predicates;
 import com.google.common.collect.Iterables;
@@ -22,7 +21,6 @@
 import java.util.Iterator;
 import java.util.List;
 import java.util.ListIterator;
-import java.util.Optional;
 import java.util.Set;
 import java.util.function.Consumer;
 import java.util.function.Predicate;
@@ -33,12 +31,6 @@
     void setField(int index, DexEncodedField field);
   }
 
-  public interface MethodSetter {
-    void setMethod(int index, DexEncodedMethod method);
-  }
-
-  private Optional<DexEncodedMethod> cachedClassInitializer = null;
-
   public final Origin origin;
   public DexType type;
   public final ClassAccessFlags accessFlags;
@@ -55,10 +47,7 @@
   protected DexEncodedField[] instanceFields = DexEncodedField.EMPTY_ARRAY;
 
   /** Access has to be synchronized during concurrent collection/writing phase. */
-  protected DexEncodedMethod[] directMethods = DexEncodedMethod.EMPTY_ARRAY;
-
-  /** Access has to be synchronized during concurrent collection/writing phase. */
-  protected DexEncodedMethod[] virtualMethods = DexEncodedMethod.EMPTY_ARRAY;
+  protected final MethodCollection methodCollection;
 
   /** Enclosing context of this class if it is an inner class, null otherwise. */
   private EnclosingMethodAttribute enclosingMethod;
@@ -96,6 +85,7 @@
     this.type = type;
     setStaticFields(staticFields);
     setInstanceFields(instanceFields);
+    this.methodCollection = new MethodCollection(this);
     setDirectMethods(directMethods);
     setVirtualMethods(virtualMethods);
     this.nestHost = nestHost;
@@ -133,12 +123,16 @@
     return Iterables.concat(fields(), methods());
   }
 
-  public Iterable<DexEncodedMethod> methods() {
-    return methods(Predicates.alwaysTrue());
+  public MethodCollection getMethodCollection() {
+    return methodCollection;
   }
 
-  public Iterable<DexEncodedMethod> methods(Predicate<? super DexEncodedMethod> predicate) {
-    return Iterables.concat(directMethods(predicate), virtualMethods(predicate));
+  public Iterable<DexEncodedMethod> methods() {
+    return methodCollection.methods();
+  }
+
+  public Iterable<DexEncodedMethod> methods(Predicate<DexEncodedMethod> predicate) {
+    return methodCollection.methods(predicate);
   }
 
   @Override
@@ -147,145 +141,47 @@
   }
 
   public List<DexEncodedMethod> directMethods() {
-    assert directMethods != null;
-    if (InternalOptions.assertionsEnabled()) {
-      return Collections.unmodifiableList(Arrays.asList(directMethods));
-    }
-    return Arrays.asList(directMethods);
+    return methodCollection.directMethods();
   }
 
   public Iterable<DexEncodedMethod> directMethods(Predicate<? super DexEncodedMethod> predicate) {
-    return Iterables.filter(Arrays.asList(directMethods), predicate::test);
+    return Iterables.filter(directMethods(), predicate::test);
   }
 
   public void appendDirectMethod(DexEncodedMethod method) {
-    cachedClassInitializer = null;
-    DexEncodedMethod[] newMethods = new DexEncodedMethod[directMethods.length + 1];
-    System.arraycopy(directMethods, 0, newMethods, 0, directMethods.length);
-    newMethods[directMethods.length] = method;
-    directMethods = newMethods;
-    assert verifyCorrectnessOfMethodHolder(method);
-    assert verifyNoDuplicateMethods();
+    methodCollection.appendDirectMethod(method);
   }
 
   public void appendDirectMethods(Collection<DexEncodedMethod> methods) {
-    cachedClassInitializer = null;
-    DexEncodedMethod[] newMethods = new DexEncodedMethod[directMethods.length + methods.size()];
-    System.arraycopy(directMethods, 0, newMethods, 0, directMethods.length);
-    int i = directMethods.length;
-    for (DexEncodedMethod method : methods) {
-      newMethods[i] = method;
-      i++;
-    }
-    directMethods = newMethods;
-    assert verifyCorrectnessOfMethodHolders(methods);
-    assert verifyNoDuplicateMethods();
-  }
-
-  public void removeDirectMethod(int index) {
-    cachedClassInitializer = null;
-    DexEncodedMethod[] newMethods = new DexEncodedMethod[directMethods.length - 1];
-    System.arraycopy(directMethods, 0, newMethods, 0, index);
-    System.arraycopy(directMethods, index + 1, newMethods, index, directMethods.length - index - 1);
-    directMethods = newMethods;
+    methodCollection.appendDirectMethods(methods);
   }
 
   public void removeDirectMethod(DexMethod method) {
-    int index = -1;
-    for (int i = 0; i < directMethods.length; i++) {
-      if (method.match(directMethods[i])) {
-        index = i;
-        break;
-      }
-    }
-    if (index >= 0) {
-      removeDirectMethod(index);
-    }
-  }
-
-  public void setDirectMethod(int index, DexEncodedMethod method) {
-    cachedClassInitializer = null;
-    directMethods[index] = method;
-    assert verifyCorrectnessOfMethodHolder(method);
-    assert verifyNoDuplicateMethods();
+    methodCollection.removeDirectMethod(method);
   }
 
   public void setDirectMethods(DexEncodedMethod[] methods) {
-    cachedClassInitializer = null;
-    directMethods = MoreObjects.firstNonNull(methods, DexEncodedMethod.EMPTY_ARRAY);
-    assert verifyCorrectnessOfMethodHolders(directMethods());
-    assert verifyNoDuplicateMethods();
+    methodCollection.setDirectMethods(methods);
   }
 
   public List<DexEncodedMethod> virtualMethods() {
-    assert virtualMethods != null;
-    if (InternalOptions.assertionsEnabled()) {
-      return Collections.unmodifiableList(Arrays.asList(virtualMethods));
-    }
-    return Arrays.asList(virtualMethods);
+    return methodCollection.virtualMethods();
   }
 
   public Iterable<DexEncodedMethod> virtualMethods(Predicate<? super DexEncodedMethod> predicate) {
-    return Iterables.filter(Arrays.asList(virtualMethods), predicate::test);
+    return Iterables.filter(virtualMethods(), predicate::test);
   }
 
   public void appendVirtualMethod(DexEncodedMethod method) {
-    DexEncodedMethod[] newMethods = new DexEncodedMethod[virtualMethods.length + 1];
-    System.arraycopy(virtualMethods, 0, newMethods, 0, virtualMethods.length);
-    newMethods[virtualMethods.length] = method;
-    virtualMethods = newMethods;
-    assert verifyCorrectnessOfMethodHolder(method);
-    assert verifyNoDuplicateMethods();
+    methodCollection.appendVirtualMethod(method);
   }
 
   public void appendVirtualMethods(Collection<DexEncodedMethod> methods) {
-    DexEncodedMethod[] newMethods = new DexEncodedMethod[virtualMethods.length + methods.size()];
-    System.arraycopy(virtualMethods, 0, newMethods, 0, virtualMethods.length);
-    int i = virtualMethods.length;
-    for (DexEncodedMethod method : methods) {
-      newMethods[i] = method;
-      i++;
-    }
-    virtualMethods = newMethods;
-    assert verifyCorrectnessOfMethodHolders(methods);
-    assert verifyNoDuplicateMethods();
-  }
-
-  public void removeVirtualMethod(int index) {
-    DexEncodedMethod[] newMethods = new DexEncodedMethod[virtualMethods.length - 1];
-    System.arraycopy(virtualMethods, 0, newMethods, 0, index);
-    System.arraycopy(
-        virtualMethods, index + 1, newMethods, index, virtualMethods.length - index - 1);
-    virtualMethods = newMethods;
-  }
-
-  public void setVirtualMethod(int index, DexEncodedMethod method) {
-    virtualMethods[index] = method;
-    assert verifyCorrectnessOfMethodHolder(method);
-    assert verifyNoDuplicateMethods();
+    methodCollection.appendVirtualMethods(methods);
   }
 
   public void setVirtualMethods(DexEncodedMethod[] methods) {
-    virtualMethods = MoreObjects.firstNonNull(methods, DexEncodedMethod.EMPTY_ARRAY);
-    assert verifyCorrectnessOfMethodHolders(virtualMethods());
-    assert verifyNoDuplicateMethods();
-  }
-
-  private boolean verifyCorrectnessOfMethodHolder(DexEncodedMethod method) {
-    assert method.method.holder == type
-        : "Expected method `"
-            + method.method.toSourceString()
-            + "` to have holder `"
-            + type.toSourceString()
-            + "`";
-    return true;
-  }
-
-  private boolean verifyCorrectnessOfMethodHolders(Iterable<DexEncodedMethod> methods) {
-    for (DexEncodedMethod method : methods) {
-      assert verifyCorrectnessOfMethodHolder(method);
-    }
-    return true;
+    methodCollection.setVirtualMethods(methods);
   }
 
   private boolean verifyNoAbstractMethodsOnNonAbstractClasses(
@@ -301,75 +197,16 @@
     return true;
   }
 
-  private boolean verifyNoDuplicateMethods() {
-    Set<DexMethod> unique = Sets.newIdentityHashSet();
-    for (DexEncodedMethod method : methods()) {
-      boolean changed = unique.add(method.method);
-      assert changed : "Duplicate method `" + method.method.toSourceString() + "`";
-    }
-    return true;
-  }
-
   public void forEachMethod(Consumer<DexEncodedMethod> consumer) {
-    for (DexEncodedMethod method : directMethods()) {
-      consumer.accept(method);
-    }
-    for (DexEncodedMethod method : virtualMethods()) {
-      consumer.accept(method);
-    }
+    methodCollection.forEachMethod(consumer);
   }
 
-  public DexEncodedMethod[] allMethodsSorted() {
-    int vLen = virtualMethods.length;
-    int dLen = directMethods.length;
-    DexEncodedMethod[] result = new DexEncodedMethod[vLen + dLen];
-    System.arraycopy(virtualMethods, 0, result, 0, vLen);
-    System.arraycopy(directMethods, 0, result, vLen, dLen);
-    Arrays.sort(result,
-        (DexEncodedMethod a, DexEncodedMethod b) -> a.method.slowCompareTo(b.method));
-    return result;
-  }
-
-  public DexEncodedMethod[] directMethodsSorted() {
-    DexEncodedMethod[] result = new DexEncodedMethod[directMethods.length];
-    System.arraycopy(directMethods, 0, result, 0, directMethods.length);
-    Arrays.sort(
-        result, (DexEncodedMethod a, DexEncodedMethod b) -> a.method.slowCompareTo(b.method));
-    return result;
-  }
-
-  public DexEncodedMethod[] virtualMethodsSorted() {
-    DexEncodedMethod[] result = new DexEncodedMethod[virtualMethods.length];
-    System.arraycopy(virtualMethods, 0, result, 0, virtualMethods.length);
-    Arrays.sort(
-        result, (DexEncodedMethod a, DexEncodedMethod b) -> a.method.slowCompareTo(b.method));
-    return result;
+  public List<DexEncodedMethod> allMethodsSorted() {
+    return methodCollection.allMethodsSorted();
   }
 
   public void virtualizeMethods(Set<DexEncodedMethod> privateInstanceMethods) {
-    int vLen = virtualMethods.length;
-    int dLen = directMethods.length;
-    int mLen = privateInstanceMethods.size();
-    assert mLen <= dLen;
-
-    DexEncodedMethod[] newDirectMethods = new DexEncodedMethod[dLen - mLen];
-    int index = 0;
-    for (int i = 0; i < dLen; i++) {
-      DexEncodedMethod encodedMethod = directMethods[i];
-      if (!privateInstanceMethods.contains(encodedMethod)) {
-        newDirectMethods[index++] = encodedMethod;
-      }
-    }
-    assert index == dLen - mLen;
-    setDirectMethods(newDirectMethods);
-
-    DexEncodedMethod[] newVirtualMethods = new DexEncodedMethod[vLen + mLen];
-    System.arraycopy(virtualMethods, 0, newVirtualMethods, 0, vLen);
-    index = vLen;
-    for (DexEncodedMethod encodedMethod : privateInstanceMethods) {
-      newVirtualMethods[index++] = encodedMethod;
-    }
-    setVirtualMethods(newVirtualMethods);
+    methodCollection.virtualizeMethods(privateInstanceMethods);
   }
 
   /**
@@ -561,28 +398,27 @@
 
   /** Find direct method in this class matching {@param method}. */
   public DexEncodedMethod lookupDirectMethod(DexMethod method) {
-    return lookupTarget(directMethods, method);
+    return methodCollection.getDirectMethod(method);
   }
 
   /** Find direct method in this class matching {@param predicate}. */
   public DexEncodedMethod lookupDirectMethod(Predicate<DexEncodedMethod> predicate) {
-    return PredicateUtils.findFirst(directMethods, predicate);
+    return methodCollection.getDirectMethod(predicate);
   }
 
   /** Find virtual method in this class matching {@param method}. */
   public DexEncodedMethod lookupVirtualMethod(DexMethod method) {
-    return lookupTarget(virtualMethods, method);
+    return methodCollection.getVirtualMethod(method);
   }
 
   /** Find virtual method in this class matching {@param predicate}. */
   public DexEncodedMethod lookupVirtualMethod(Predicate<DexEncodedMethod> predicate) {
-    return PredicateUtils.findFirst(virtualMethods, predicate);
+    return methodCollection.getVirtualMethod(predicate);
   }
 
   /** Find method in this class matching {@param method}. */
   public DexEncodedMethod lookupMethod(DexMethod method) {
-    DexEncodedMethod result = lookupDirectMethod(method);
-    return result == null ? lookupVirtualMethod(method) : result;
+    return methodCollection.getMethod(method);
   }
 
   public DexEncodedMethod lookupSignaturePolymorphicMethod(
@@ -592,7 +428,7 @@
     }
     DexEncodedMethod matchingName = null;
     DexEncodedMethod signaturePolymorphicMethod = null;
-    for (DexEncodedMethod method : virtualMethods) {
+    for (DexEncodedMethod method : virtualMethods()) {
       if (method.method.name == methodName) {
         if (matchingName != null) {
           // The jvm spec, section 5.4.3.3 details that there must be exactly one method with the
@@ -701,16 +537,7 @@
   }
 
   public synchronized DexEncodedMethod getClassInitializer() {
-    if (cachedClassInitializer == null) {
-      cachedClassInitializer = Optional.empty();
-      for (DexEncodedMethod directMethod : directMethods) {
-        if (directMethod.isClassInitializer()) {
-          cachedClassInitializer = Optional.of(directMethod);
-          break;
-        }
-      }
-    }
-    return cachedClassInitializer.orElse(null);
+    return methodCollection.getClassInitializer();
   }
 
   public Origin getOrigin() {
@@ -1004,8 +831,7 @@
     assert !isInterface() || virtualMethods().stream().noneMatch(DexEncodedMethod::isFinal);
     assert verifyCorrectnessOfFieldHolders(fields());
     assert verifyNoDuplicateFields();
-    assert verifyCorrectnessOfMethodHolders(methods());
-    assert verifyNoDuplicateMethods();
+    assert methodCollection.verify();
     return true;
   }
 
diff --git a/src/main/java/com/android/tools/r8/graph/DexEncodedAnnotation.java b/src/main/java/com/android/tools/r8/graph/DexEncodedAnnotation.java
index c0c2d6e..f5c5272 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedAnnotation.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedAnnotation.java
@@ -63,7 +63,7 @@
       assert sorted == sortedHashCode();
       return;
     }
-    Arrays.sort(elements, (a, b) -> a.name.compareTo(b.name));
+    Arrays.sort(elements, (a, b) -> a.name.slowCompareTo(b.name));
     for (DexAnnotationElement element : elements) {
       element.value.sort();
     }
diff --git a/src/main/java/com/android/tools/r8/graph/DexEncodedField.java b/src/main/java/com/android/tools/r8/graph/DexEncodedField.java
index e8bd9dc..8a21e7b 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedField.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedField.java
@@ -80,6 +80,10 @@
     return kotlinMemberInfo.memberKind.isBackingField();
   }
 
+  public boolean isKotlinBackingFieldForCompanionObject() {
+    return kotlinMemberInfo.memberKind.isBackingFieldForCompanionObject();
+  }
+
   @Override
   public void collectIndexedItems(
       IndexedItemCollection indexedItems, DexMethod method, int instructionOffset) {
@@ -134,10 +138,18 @@
     return accessFlags.isStatic();
   }
 
+  public boolean isPackagePrivate() {
+    return accessFlags.isPackagePrivate();
+  }
+
   public boolean isPrivate() {
     return accessFlags.isPrivate();
   }
 
+  public boolean isProtected() {
+    return accessFlags.isProtected();
+  }
+
   public boolean isPublic() {
     return accessFlags.isPublic();
   }
@@ -254,4 +266,19 @@
             : DefaultFieldOptimizationInfo.getInstance();
     return result;
   }
+
+  public boolean validateDexValue(DexItemFactory factory) {
+    if (!accessFlags.isStatic() || staticValue == null) {
+      return true;
+    }
+    if (field.type.isPrimitiveType()) {
+      assert staticValue.getType(factory) == field.type
+          : "Static " + field + " has invalid static value " + staticValue + ".";
+    }
+    if (staticValue.isDexValueNull()) {
+      assert field.type.isReferenceType() : "Static " + field + " has invalid null static value.";
+    }
+    // TODO(b/150593449): Support non primitive DexValue (String, enum) and add assertions.
+    return true;
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/graph/DexField.java b/src/main/java/com/android/tools/r8/graph/DexField.java
index 02cb985..d10a2c8 100644
--- a/src/main/java/com/android/tools/r8/graph/DexField.java
+++ b/src/main/java/com/android/tools/r8/graph/DexField.java
@@ -72,11 +72,6 @@
   }
 
   @Override
-  public int compareTo(DexField other) {
-    return sortedCompareTo(other.getSortedIndex());
-  }
-
-  @Override
   public int slowCompareTo(DexField other) {
     int result = holder.slowCompareTo(other.holder);
     if (result != 0) {
@@ -103,19 +98,6 @@
   }
 
   @Override
-  public int layeredCompareTo(DexField other, NamingLens namingLens) {
-    int result = holder.compareTo(other.holder);
-    if (result != 0) {
-      return result;
-    }
-    result = namingLens.lookupName(this).compareTo(namingLens.lookupName(other));
-    if (result != 0) {
-      return result;
-    }
-    return type.compareTo(other.type);
-  }
-
-  @Override
   public boolean match(DexField field) {
     return field.name == name && field.type == type;
   }
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 58b768d..bfaf19c 100644
--- a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
+++ b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
@@ -27,7 +27,6 @@
 import com.android.tools.r8.ir.code.Position;
 import com.android.tools.r8.ir.code.Value;
 import com.android.tools.r8.kotlin.Kotlin;
-import com.android.tools.r8.naming.NamingLens;
 import com.android.tools.r8.utils.ArrayUtils;
 import com.android.tools.r8.utils.LRUCacheTable;
 import com.android.tools.r8.utils.Pair;
@@ -44,7 +43,6 @@
 import it.unimi.dsi.fastutil.ints.Int2ReferenceOpenHashMap;
 import java.util.ArrayList;
 import java.util.Arrays;
-import java.util.Collection;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.IdentityHashMap;
@@ -572,7 +570,7 @@
           createString("makeConcat")
       );
 
-  public final Set<DexMethod> libraryMethodsReturningReceiver =
+  public Set<DexMethod> libraryMethodsReturningReceiver =
       ImmutableSet.<DexMethod>builder()
           .addAll(stringBufferMethods.appendMethods)
           .addAll(stringBuilderMethods.appendMethods)
@@ -1725,39 +1723,6 @@
         );
   }
 
-  private static <S extends PresortedComparable<S>> void assignSortedIndices(Collection<S> items,
-      NamingLens namingLens) {
-    List<S> sorted = new ArrayList<>(items);
-    sorted.sort((a, b) -> a.layeredCompareTo(b, namingLens));
-    int i = 0;
-    for (S value : sorted) {
-      value.setSortedIndex(i++);
-    }
-  }
-
-  synchronized public void sort(NamingLens namingLens) {
-    assert !sorted;
-    assignSortedIndices(strings.values(), namingLens);
-    assignSortedIndices(types.values(), namingLens);
-    assignSortedIndices(fields.values(), namingLens);
-    assignSortedIndices(protos.values(), namingLens);
-    assignSortedIndices(methods.values(), namingLens);
-    sorted = true;
-  }
-
-  synchronized public void resetSortedIndices() {
-    if (!sorted) {
-      return;
-    }
-    // Only used for asserting that we don't use the sorted index after we build the graph.
-    strings.values().forEach(IndexedDexItem::resetSortedIndex);
-    types.values().forEach(IndexedDexItem::resetSortedIndex);
-    fields.values().forEach(IndexedDexItem::resetSortedIndex);
-    protos.values().forEach(IndexedDexItem::resetSortedIndex);
-    methods.values().forEach(IndexedDexItem::resetSortedIndex);
-    sorted = false;
-  }
-
   synchronized public void forAllTypes(Consumer<DexType> f) {
     new ArrayList<>(types.values()).forEach(f);
   }
diff --git a/src/main/java/com/android/tools/r8/graph/DexMethod.java b/src/main/java/com/android/tools/r8/graph/DexMethod.java
index a29eb31..80605e5 100644
--- a/src/main/java/com/android/tools/r8/graph/DexMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/DexMethod.java
@@ -6,17 +6,12 @@
 import com.android.tools.r8.dex.IndexedItemCollection;
 import com.android.tools.r8.errors.CompilationError;
 import com.android.tools.r8.naming.NamingLens;
-import com.google.common.collect.Maps;
-import java.util.Map;
 
 public class DexMethod extends DexMember<DexEncodedMethod, DexMethod> {
 
   public final DexProto proto;
   public final DexString name;
 
-  // Caches used during processing.
-  private Map<DexType, DexEncodedMethod> singleTargetCache;
-
   DexMethod(DexType holder, DexProto proto, DexString name, boolean skipNameValidationForTesting) {
     super(holder);
     this.proto = proto;
@@ -102,11 +97,6 @@
   }
 
   @Override
-  public int compareTo(DexMethod other) {
-    return sortedCompareTo(other.getSortedIndex());
-  }
-
-  @Override
   public int slowCompareTo(DexMethod other) {
     int result = holder.slowCompareTo(other.holder);
     if (result != 0) {
@@ -133,19 +123,6 @@
   }
 
   @Override
-  public int layeredCompareTo(DexMethod other, NamingLens namingLens) {
-    int result = holder.compareTo(other.holder);
-    if (result != 0) {
-      return result;
-    }
-    result = namingLens.lookupName(this).compareTo(namingLens.lookupName(other));
-    if (result != 0) {
-      return result;
-    }
-    return proto.compareTo(other.proto);
-  }
-
-  @Override
   public boolean match(DexMethod method) {
     return method.name == name && method.proto == proto;
   }
@@ -192,21 +169,4 @@
     return name == dexItemFactory.deserializeLambdaMethodName
         && proto == dexItemFactory.deserializeLambdaMethodProto;
   }
-
-  synchronized public void setSingleVirtualMethodCache(
-      DexType receiverType, DexEncodedMethod method) {
-    if (singleTargetCache == null) {
-      singleTargetCache = Maps.newIdentityHashMap();
-    }
-    singleTargetCache.put(receiverType, method);
-  }
-
-  synchronized public boolean isSingleVirtualMethodCached(DexType receiverType) {
-    return singleTargetCache != null && singleTargetCache.containsKey(receiverType);
-  }
-
-  synchronized public DexEncodedMethod getSingleVirtualMethodCache(DexType receiverType) {
-    assert isSingleVirtualMethodCached(receiverType);
-    return singleTargetCache.get(receiverType);
-  }
 }
diff --git a/src/main/java/com/android/tools/r8/graph/DexMethodHandle.java b/src/main/java/com/android/tools/r8/graph/DexMethodHandle.java
index fbe8a9f..a4be032 100644
--- a/src/main/java/com/android/tools/r8/graph/DexMethodHandle.java
+++ b/src/main/java/com/android/tools/r8/graph/DexMethodHandle.java
@@ -339,25 +339,6 @@
     return result;
   }
 
-  @Override
-  public int layeredCompareTo(DexMethodHandle other, NamingLens namingLens) {
-    int result = type.getValue() - other.type.getValue();
-    if (result == 0) {
-      if (isFieldHandle()) {
-        result = asField().layeredCompareTo(other.asField(), namingLens);
-      } else {
-        assert isMethodHandle();
-        result = asMethod().layeredCompareTo(other.asMethod(), namingLens);
-      }
-    }
-    return result;
-  }
-
-  @Override
-  public int compareTo(DexMethodHandle other) {
-    return slowCompareTo(other);
-  }
-
   public Handle toAsmHandle(NamingLens lens) {
     String owner;
     String name;
diff --git a/src/main/java/com/android/tools/r8/graph/DexProgramClass.java b/src/main/java/com/android/tools/r8/graph/DexProgramClass.java
index 78a59c2..221a19e 100644
--- a/src/main/java/com/android/tools/r8/graph/DexProgramClass.java
+++ b/src/main/java/com/android/tools/r8/graph/DexProgramClass.java
@@ -9,13 +9,13 @@
 import com.android.tools.r8.dex.MixedSectionCollection;
 import com.android.tools.r8.errors.CompilationError;
 import com.android.tools.r8.kotlin.KotlinInfo;
+import com.android.tools.r8.naming.NamingLens;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
-import java.util.Comparator;
 import java.util.HashSet;
 import java.util.Iterator;
 import java.util.List;
@@ -34,7 +34,6 @@
       new DexEncodedArray(DexValue.EMPTY_ARRAY);
 
   private final ProgramResource.Kind originKind;
-  private DexEncodedArray staticValues = SENTINEL_NOT_YET_COMPUTED;
   private final Collection<DexProgramClass> synthesizedFrom;
   private int initialClassFileVersion = -1;
   private KotlinInfo kotlinInfo = null;
@@ -160,8 +159,9 @@
       }
       synchronizedCollectAll(indexedItems, staticFields);
       synchronizedCollectAll(indexedItems, instanceFields);
-      synchronizedCollectAll(indexedItems, directMethods);
-      synchronizedCollectAll(indexedItems, virtualMethods);
+      synchronized (methodCollection) {
+        methodCollection.forEachMethod(m -> m.collectIndexedItems(indexedItems));
+      }
     }
   }
 
@@ -192,14 +192,9 @@
     // We only have a class data item if there are methods or fields.
     if (hasMethodsOrFields()) {
       collector.add(this);
-
-      // The ordering of methods and fields may not be deterministic due to concurrency
-      // (see b/116027780).
-      sortMembers();
-      synchronizedCollectAll(collector, directMethods);
-      synchronizedCollectAll(collector, virtualMethods);
-      synchronizedCollectAll(collector, staticFields);
-      synchronizedCollectAll(collector, instanceFields);
+      methodCollection.forEachMethod(m -> m.collectMixedSectionItems(collector));
+      collectAll(collector, staticFields);
+      collectAll(collector, instanceFields);
     }
     annotations().collectMixedSectionItems(collector);
     if (interfaces != null) {
@@ -207,13 +202,6 @@
     }
   }
 
-  private static <T extends DexItem> void synchronizedCollectAll(MixedSectionCollection collection,
-      T[] items) {
-    synchronized (items) {
-      collectAll(collection, items);
-    }
-  }
-
   @Override
   public String toString() {
     return type.toString();
@@ -278,7 +266,7 @@
   }
 
   public boolean hasMethods() {
-    return directMethods.length + virtualMethods.length > 0;
+    return methodCollection.size() > 0;
   }
 
   public boolean hasMethodsOrFields() {
@@ -287,15 +275,13 @@
 
   public boolean hasAnnotations() {
     return !annotations().isEmpty()
-        || hasAnnotations(virtualMethods)
-        || hasAnnotations(directMethods)
+        || hasAnnotations(methodCollection)
         || hasAnnotations(staticFields)
         || hasAnnotations(instanceFields);
   }
 
   boolean hasOnlyInternalizableAnnotations() {
-    return !hasAnnotations(virtualMethods)
-        && !hasAnnotations(directMethods)
+    return !hasAnnotations(methodCollection)
         && !hasAnnotations(staticFields)
         && !hasAnnotations(instanceFields);
   }
@@ -306,9 +292,9 @@
     }
   }
 
-  private boolean hasAnnotations(DexEncodedMethod[] methods) {
+  private boolean hasAnnotations(MethodCollection methods) {
     synchronized (methods) {
-      return Arrays.stream(methods).anyMatch(DexEncodedMethod::hasAnnotation);
+      return methods.hasAnnotations();
     }
   }
 
@@ -320,97 +306,49 @@
     }
   }
 
-  public void computeStaticValues() {
-    // It does not actually hurt to compute this multiple times. So racing on staticValues is OK.
-    if (staticValues == SENTINEL_NOT_YET_COMPUTED) {
-      synchronized (staticFields) {
-        assert PresortedComparable.isSorted(Arrays.asList(staticFields));
-        DexEncodedField[] fields = staticFields;
-        int length = 0;
-        List<DexValue> values = new ArrayList<>(fields.length);
-        for (int i = 0; i < fields.length; i++) {
-          DexEncodedField field = fields[i];
-          DexValue staticValue = field.getStaticValue();
-          assert staticValue != null;
-          values.add(staticValue);
-          if (!staticValue.isDefault(field.field.type)) {
-            length = i + 1;
-          }
-        }
-        staticValues =
-            length > 0
-                ? new DexEncodedArray(values.subList(0, length).toArray(DexValue.EMPTY_ARRAY))
-                : null;
-      }
-    }
-  }
-
-  public boolean isSorted() {
-    return isSorted(virtualMethods)
-        && isSorted(directMethods)
-        && isSorted(staticFields)
-        && isSorted(instanceFields);
-  }
-
-  private static <D extends DexEncodedMember<D, R>, R extends DexMember<D, R>> boolean isSorted(
-      D[] items) {
-    synchronized (items) {
-      D[] sorted = items.clone();
-      Arrays.sort(sorted, Comparator.comparing(DexEncodedMember::toReference));
-      return Arrays.equals(items, sorted);
-    }
-  }
-  public DexEncodedArray getStaticValues() {
-    // The sentinel value is left over for classes that actually have no fields.
-    if (staticValues == SENTINEL_NOT_YET_COMPUTED) {
-      assert !hasMethodsOrFields();
+  public DexEncodedArray computeStaticValuesArray(NamingLens namingLens) {
+    // Fast path to avoid sorting and collection allocation when no non-default values exist.
+    if (!hasNonDefaultStaticFieldValues()) {
       return null;
     }
-    return staticValues;
+    DexEncodedField[] fields = staticFields;
+    Arrays.sort(fields, (a, b) -> a.field.slowCompareTo(b.field, namingLens));
+    int length = 0;
+    List<DexValue> values = new ArrayList<>(fields.length);
+    for (int i = 0; i < fields.length; i++) {
+      DexEncodedField field = fields[i];
+      DexValue staticValue = field.getStaticValue();
+      assert staticValue != null;
+      values.add(staticValue);
+      if (!staticValue.isDefault(field.field.type)) {
+        length = i + 1;
+      }
+    }
+    return length > 0
+        ? new DexEncodedArray(values.subList(0, length).toArray(DexValue.EMPTY_ARRAY))
+        : null;
+  }
+
+  private boolean hasNonDefaultStaticFieldValues() {
+    for (DexEncodedField field : staticFields) {
+      DexValue value = field.getStaticValue();
+      if (value != null && !value.isDefault(field.field.type)) {
+        return true;
+      }
+    }
+    return false;
   }
 
   public void addMethod(DexEncodedMethod method) {
-    if (method.accessFlags.isStatic()
-        || method.accessFlags.isPrivate()
-        || method.accessFlags.isConstructor()) {
-      addDirectMethod(method);
-    } else {
-      addVirtualMethod(method);
-    }
+    methodCollection.addMethod(method);
   }
 
   public void addVirtualMethod(DexEncodedMethod virtualMethod) {
-    assert !virtualMethod.accessFlags.isStatic();
-    assert !virtualMethod.accessFlags.isPrivate();
-    assert !virtualMethod.accessFlags.isConstructor();
-    virtualMethods = Arrays.copyOf(virtualMethods, virtualMethods.length + 1);
-    virtualMethods[virtualMethods.length - 1] = virtualMethod;
+    methodCollection.addVirtualMethod(virtualMethod);
   }
 
-  public void addDirectMethod(DexEncodedMethod staticMethod) {
-    assert staticMethod.accessFlags.isStatic() || staticMethod.accessFlags.isPrivate()
-        || staticMethod.accessFlags.isConstructor();
-    directMethods = Arrays.copyOf(directMethods, directMethods.length + 1);
-    directMethods[directMethods.length - 1] = staticMethod;
-  }
-
-  public void sortMembers() {
-    sortEncodedFields(staticFields);
-    sortEncodedFields(instanceFields);
-    sortEncodedMethods(directMethods);
-    sortEncodedMethods(virtualMethods);
-  }
-
-  private void sortEncodedFields(DexEncodedField[] fields) {
-    synchronized (fields) {
-      Arrays.sort(fields, Comparator.comparing(a -> a.field));
-    }
-  }
-
-  private void sortEncodedMethods(DexEncodedMethod[] methods) {
-    synchronized (methods) {
-      Arrays.sort(methods, Comparator.comparing(a -> a.method));
-    }
+  public void addDirectMethod(DexEncodedMethod directMethod) {
+    methodCollection.addDirectMethod(directMethod);
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/graph/DexProto.java b/src/main/java/com/android/tools/r8/graph/DexProto.java
index 43d6a29..a9a15f3 100644
--- a/src/main/java/com/android/tools/r8/graph/DexProto.java
+++ b/src/main/java/com/android/tools/r8/graph/DexProto.java
@@ -63,11 +63,6 @@
   }
 
   @Override
-  public int compareTo(DexProto other) {
-    return sortedCompareTo(other.getSortedIndex());
-  }
-
-  @Override
   public int slowCompareTo(DexProto other) {
     int result = returnType.slowCompareTo(other.returnType);
     if (result == 0) {
@@ -86,15 +81,6 @@
   }
 
   @Override
-  public int layeredCompareTo(DexProto other, NamingLens namingLens) {
-    int result = returnType.compareTo(other.returnType);
-    if (result == 0) {
-      result = parameters.compareTo(other.parameters);
-    }
-    return result;
-  }
-
-  @Override
   public String toSmaliString() {
     return toDescriptorString();
   }
diff --git a/src/main/java/com/android/tools/r8/graph/DexString.java b/src/main/java/com/android/tools/r8/graph/DexString.java
index f3a829c..03303f6 100644
--- a/src/main/java/com/android/tools/r8/graph/DexString.java
+++ b/src/main/java/com/android/tools/r8/graph/DexString.java
@@ -251,11 +251,6 @@
   }
 
   @Override
-  public int compareTo(DexString other) {
-    return sortedCompareTo(other.getSortedIndex());
-  }
-
-  @Override
   public int slowCompareTo(DexString other) {
     // Compare the bytes, as comparing UTF-8 encoded strings as strings of unsigned bytes gives
     // the same result as comparing the corresponding Unicode strings lexicographically by
@@ -294,12 +289,6 @@
     return slowCompareTo(other);
   }
 
-  @Override
-  public int layeredCompareTo(DexString other, NamingLens lens) {
-    // Strings have no subparts that are already sorted.
-    return slowCompareTo(other);
-  }
-
   private static boolean isValidClassDescriptor(String string) {
     if (string.length() < 3
         || string.charAt(0) != 'L'
diff --git a/src/main/java/com/android/tools/r8/graph/DexType.java b/src/main/java/com/android/tools/r8/graph/DexType.java
index c046e6a..4b8c6d4 100644
--- a/src/main/java/com/android/tools/r8/graph/DexType.java
+++ b/src/main/java/com/android/tools/r8/graph/DexType.java
@@ -175,11 +175,6 @@
   }
 
   @Override
-  public int compareTo(DexType other) {
-    return sortedCompareTo(other.getSortedIndex());
-  }
-
-  @Override
   public int slowCompareTo(DexType other) {
     return descriptor.slowCompareTo(other.descriptor);
   }
@@ -188,14 +183,7 @@
   public int slowCompareTo(DexType other, NamingLens namingLens) {
     DexString thisDescriptor = namingLens.lookupDescriptor(this);
     DexString otherDescriptor = namingLens.lookupDescriptor(other);
-    return thisDescriptor.slowCompareTo(otherDescriptor);
-  }
-
-  @Override
-  public int layeredCompareTo(DexType other, NamingLens namingLens) {
-    DexString thisDescriptor = namingLens.lookupDescriptor(this);
-    DexString otherDescriptor = namingLens.lookupDescriptor(other);
-    return thisDescriptor.compareTo(otherDescriptor);
+    return thisDescriptor.slowCompareTo(otherDescriptor, namingLens);
   }
 
   public boolean isPrimitiveType() {
diff --git a/src/main/java/com/android/tools/r8/graph/DexTypeList.java b/src/main/java/com/android/tools/r8/graph/DexTypeList.java
index a124bc4..58b6afd 100644
--- a/src/main/java/com/android/tools/r8/graph/DexTypeList.java
+++ b/src/main/java/com/android/tools/r8/graph/DexTypeList.java
@@ -9,7 +9,7 @@
 import com.android.tools.r8.naming.NamingLens;
 import java.util.Arrays;
 
-public class DexTypeList extends DexItem implements Comparable<DexTypeList> {
+public class DexTypeList extends DexItem {
 
   private static final DexTypeList theEmptyTypeList = new DexTypeList();
 
@@ -75,23 +75,6 @@
     return builder.toString();
   }
 
-  @Override
-  public int compareTo(DexTypeList other) {
-    for (int i = 0; i <= Math.min(values.length, other.values.length); i++) {
-      if (i == values.length) {
-        return i == other.values.length ? 0 : -1;
-      } else if (i == other.values.length) {
-        return 1;
-      } else {
-        int result = values[i].compareTo(other.values[i]);
-        if (result != 0) {
-          return result;
-        }
-      }
-    }
-    throw new Unreachable();
-  }
-
   public int slowCompareTo(DexTypeList other) {
     for (int i = 0; i <= Math.min(values.length, other.values.length); i++) {
       if (i == values.length) {
diff --git a/src/main/java/com/android/tools/r8/graph/DexValue.java b/src/main/java/com/android/tools/r8/graph/DexValue.java
index 794f7c8..be0cf37 100644
--- a/src/main/java/com/android/tools/r8/graph/DexValue.java
+++ b/src/main/java/com/android/tools/r8/graph/DexValue.java
@@ -304,6 +304,8 @@
     }
   }
 
+  public abstract DexType getType(DexItemFactory factory);
+
   public abstract Object getBoxedValue();
 
   /** Returns an instruction that can be used to materialize this {@link DexValue} (or null). */
@@ -390,6 +392,11 @@
     }
 
     @Override
+    public DexType getType(DexItemFactory factory) {
+      return factory.byteType;
+    }
+
+    @Override
     public long getRawValue() {
       return value;
     }
@@ -463,6 +470,11 @@
     }
 
     @Override
+    public DexType getType(DexItemFactory factory) {
+      return factory.shortType;
+    }
+
+    @Override
     public long getRawValue() {
       return value;
     }
@@ -535,6 +547,11 @@
     }
 
     @Override
+    public DexType getType(DexItemFactory factory) {
+      return factory.charType;
+    }
+
+    @Override
     public long getRawValue() {
       return value;
     }
@@ -611,6 +628,11 @@
     }
 
     @Override
+    public DexType getType(DexItemFactory factory) {
+      return factory.intType;
+    }
+
+    @Override
     public long getRawValue() {
       return value;
     }
@@ -683,6 +705,11 @@
     }
 
     @Override
+    public DexType getType(DexItemFactory factory) {
+      return factory.longType;
+    }
+
+    @Override
     public long getRawValue() {
       return value;
     }
@@ -755,6 +782,11 @@
     }
 
     @Override
+    public DexType getType(DexItemFactory factory) {
+      return factory.floatType;
+    }
+
+    @Override
     public long getRawValue() {
       return Float.floatToIntBits(value);
     }
@@ -833,6 +865,11 @@
     }
 
     @Override
+    public DexType getType(DexItemFactory factory) {
+      return factory.doubleType;
+    }
+
+    @Override
     public long getRawValue() {
       return Double.doubleToRawLongBits(value);
     }
@@ -902,6 +939,11 @@
 
     protected abstract DexValueKind getValueKind();
 
+    @Override
+    public DexType getType(DexItemFactory factory) {
+      throw new Unreachable();
+    }
+
     public T getValue() {
       return value;
     }
@@ -987,6 +1029,11 @@
     }
 
     @Override
+    public DexType getType(DexItemFactory factory) {
+      return factory.stringType;
+    }
+
+    @Override
     public ConstInstruction asConstInstruction(
         AppView<? extends AppInfoWithSubtyping> appView, IRCode code, DebugLocalInfo local) {
       TypeLatticeElement type = TypeLatticeElement.stringClassType(appView, definitelyNotNull());
@@ -1040,6 +1087,11 @@
     }
 
     @Override
+    public DexType getType(DexItemFactory factory) {
+      return factory.stringType;
+    }
+
+    @Override
     public ConstInstruction asConstInstruction(
         AppView<? extends AppInfoWithSubtyping> appView, IRCode code, DebugLocalInfo local) {
       TypeLatticeElement type = TypeLatticeElement.stringClassType(appView, definitelyNotNull());
@@ -1195,6 +1247,11 @@
     }
 
     @Override
+    public DexType getType(DexItemFactory factory) {
+      throw new Unreachable();
+    }
+
+    @Override
     public Object getBoxedValue() {
       throw new Unreachable("No boxed value for DexValueArray");
     }
@@ -1265,6 +1322,11 @@
     }
 
     @Override
+    public DexType getType(DexItemFactory factory) {
+      throw new Unreachable();
+    }
+
+    @Override
     public Object getBoxedValue() {
       throw new Unreachable("No boxed value for DexValueAnnotation");
     }
@@ -1315,6 +1377,11 @@
     }
 
     @Override
+    public DexType getType(DexItemFactory factory) {
+      throw new Unreachable();
+    }
+
+    @Override
     public long getRawValue() {
       return 0;
     }
@@ -1391,6 +1458,11 @@
     }
 
     @Override
+    public DexType getType(DexItemFactory factory) {
+      return factory.booleanType;
+    }
+
+    @Override
     public long getRawValue() {
       return BooleanUtils.longValue(value);
     }
diff --git a/src/main/java/com/android/tools/r8/graph/FinalInitClassLens.java b/src/main/java/com/android/tools/r8/graph/FinalInitClassLens.java
new file mode 100644
index 0000000..4f0a854
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/graph/FinalInitClassLens.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.graph;
+
+import com.android.tools.r8.errors.Unreachable;
+import java.util.Map;
+
+public class FinalInitClassLens extends InitClassLens {
+
+  private final Map<DexType, DexField> mapping;
+
+  FinalInitClassLens(Map<DexType, DexField> mapping) {
+    this.mapping = mapping;
+  }
+
+  @Override
+  public DexField getInitClassField(DexType type) {
+    DexField field = mapping.get(type);
+    if (field != null) {
+      return field;
+    }
+    throw new Unreachable("Unexpected InitClass instruction for `" + type.toSourceString() + "`");
+  }
+
+  @Override
+  public boolean isFinal() {
+    return true;
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/graph/IndexedDexItem.java b/src/main/java/com/android/tools/r8/graph/IndexedDexItem.java
index 68a0e5d..0c115e1 100644
--- a/src/main/java/com/android/tools/r8/graph/IndexedDexItem.java
+++ b/src/main/java/com/android/tools/r8/graph/IndexedDexItem.java
@@ -6,13 +6,8 @@
 import com.android.tools.r8.dex.IndexedItemCollection;
 import com.android.tools.r8.dex.MixedSectionCollection;
 
-/**
- * Subset of dex items that are referenced by some table index.
- */
-public abstract class IndexedDexItem extends CachedHashValueDexItem implements Presorted {
-
-  private static final int SORTED_INDEX_UNKNOWN = -1;
-  private int sortedIndex = SORTED_INDEX_UNKNOWN; // assigned globally after reading.
+/** Subset of dex items that are referenced by some table index. */
+public abstract class IndexedDexItem extends CachedHashValueDexItem {
 
   @Override
   public abstract void collectIndexedItems(IndexedItemCollection indexedItems,
@@ -29,32 +24,7 @@
   // Partial implementation of PresortedComparable.
 
   @Override
-  final public void setSortedIndex(int sortedIndex) {
-    assert sortedIndex > SORTED_INDEX_UNKNOWN;
-    assert this.sortedIndex == SORTED_INDEX_UNKNOWN;
-    this.sortedIndex = sortedIndex;
-  }
-
-  @Override
-  final public int getSortedIndex() {
-    return sortedIndex;
-  }
-
-  @Override
-  final public int sortedCompareTo(int other) {
-    assert sortedIndex > SORTED_INDEX_UNKNOWN
-        : "sortedIndex <= SORTED_INDEX_UKNOWN for: " + this.toString();
-    assert other > SORTED_INDEX_UNKNOWN : "other < SORTED_INDEX_UKNOWN for: " + this.toString();
-    return Integer.compare(sortedIndex, other);
-  }
-
-  @Override
   public void flushCachedValues() {
     super.flushCachedValues();
-    resetSortedIndex();
-  }
-
-  public void resetSortedIndex() {
-    sortedIndex = SORTED_INDEX_UNKNOWN;
   }
 }
diff --git a/src/main/java/com/android/tools/r8/graph/InitClassLens.java b/src/main/java/com/android/tools/r8/graph/InitClassLens.java
new file mode 100644
index 0000000..cac3870
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/graph/InitClassLens.java
@@ -0,0 +1,39 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.graph;
+
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+public abstract class InitClassLens {
+
+  public static Builder builder() {
+    return new Builder();
+  }
+
+  public static DefaultInitClassLens getDefault() {
+    return DefaultInitClassLens.getInstance();
+  }
+
+  public abstract DexField getInitClassField(DexType clazz);
+
+  public boolean isFinal() {
+    return false;
+  }
+
+  public static class Builder {
+
+    private final Map<DexType, DexField> mapping = new ConcurrentHashMap<>();
+
+    public void map(DexType type, DexField field) {
+      assert field.holder == type;
+      mapping.put(type, field);
+    }
+
+    public FinalInitClassLens build() {
+      return new FinalInitClassLens(mapping);
+    }
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/graph/MethodArrayBacking.java b/src/main/java/com/android/tools/r8/graph/MethodArrayBacking.java
new file mode 100644
index 0000000..7800d81a
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/graph/MethodArrayBacking.java
@@ -0,0 +1,300 @@
+// 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;
+
+import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.PredicateUtils;
+import com.android.tools.r8.utils.TraversalContinuation;
+import com.google.common.base.MoreObjects;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Sets;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Set;
+import java.util.function.Function;
+import java.util.function.Predicate;
+
+public class MethodArrayBacking extends MethodCollectionBacking {
+
+  private DexEncodedMethod[] directMethods = DexEncodedMethod.EMPTY_ARRAY;
+  private DexEncodedMethod[] virtualMethods = DexEncodedMethod.EMPTY_ARRAY;
+
+  private boolean belongsToDirectPool(DexEncodedMethod method) {
+    return method.accessFlags.isStatic()
+        || method.accessFlags.isPrivate()
+        || method.accessFlags.isConstructor();
+  }
+
+  private boolean belongsToVirtualPool(DexEncodedMethod method) {
+    return !belongsToDirectPool(method);
+  }
+
+  int size() {
+    return directMethods.length + virtualMethods.length;
+  }
+
+  @Override
+  TraversalContinuation traverse(Function<DexEncodedMethod, TraversalContinuation> fn) {
+    for (DexEncodedMethod method : directMethods) {
+      TraversalContinuation stepResult = fn.apply(method);
+      if (stepResult.shouldBreak()) {
+        return stepResult;
+      }
+    }
+    for (DexEncodedMethod method : virtualMethods) {
+      TraversalContinuation stepResult = fn.apply(method);
+      if (stepResult.shouldBreak()) {
+        return stepResult;
+      }
+    }
+    return TraversalContinuation.CONTINUE;
+  }
+
+  public Iterable<DexEncodedMethod> methods() {
+    return Iterables.concat(Arrays.asList(directMethods), Arrays.asList(virtualMethods));
+  }
+
+  List<DexEncodedMethod> directMethods() {
+    assert directMethods != null;
+    if (InternalOptions.assertionsEnabled()) {
+      return Collections.unmodifiableList(Arrays.asList(directMethods));
+    }
+    return Arrays.asList(directMethods);
+  }
+
+  void appendDirectMethod(DexEncodedMethod method) {
+    DexEncodedMethod[] newMethods = new DexEncodedMethod[directMethods.length + 1];
+    System.arraycopy(directMethods, 0, newMethods, 0, directMethods.length);
+    newMethods[directMethods.length] = method;
+    directMethods = newMethods;
+    assert verifyNoDuplicateMethods();
+  }
+
+  void appendDirectMethods(Collection<DexEncodedMethod> methods) {
+    DexEncodedMethod[] newMethods = new DexEncodedMethod[directMethods.length + methods.size()];
+    System.arraycopy(directMethods, 0, newMethods, 0, directMethods.length);
+    int i = directMethods.length;
+    for (DexEncodedMethod method : methods) {
+      newMethods[i] = method;
+      i++;
+    }
+    directMethods = newMethods;
+    assert verifyNoDuplicateMethods();
+  }
+
+  private void removeDirectMethod(int index) {
+    DexEncodedMethod[] newMethods = new DexEncodedMethod[directMethods.length - 1];
+    System.arraycopy(directMethods, 0, newMethods, 0, index);
+    System.arraycopy(directMethods, index + 1, newMethods, index, directMethods.length - index - 1);
+    directMethods = newMethods;
+  }
+
+  void removeDirectMethod(DexMethod method) {
+    int index = -1;
+    for (int i = 0; i < directMethods.length; i++) {
+      if (method.match(directMethods[i])) {
+        index = i;
+        break;
+      }
+    }
+    if (index >= 0) {
+      removeDirectMethod(index);
+    }
+  }
+
+  void setDirectMethod(int index, DexEncodedMethod method) {
+    directMethods[index] = method;
+    assert verifyNoDuplicateMethods();
+  }
+
+  void setDirectMethods(DexEncodedMethod[] methods) {
+    directMethods = MoreObjects.firstNonNull(methods, DexEncodedMethod.EMPTY_ARRAY);
+    assert verifyNoDuplicateMethods();
+  }
+
+  List<DexEncodedMethod> virtualMethods() {
+    assert virtualMethods != null;
+    if (InternalOptions.assertionsEnabled()) {
+      return Collections.unmodifiableList(Arrays.asList(virtualMethods));
+    }
+    return Arrays.asList(virtualMethods);
+  }
+
+  void appendVirtualMethod(DexEncodedMethod method) {
+    DexEncodedMethod[] newMethods = new DexEncodedMethod[virtualMethods.length + 1];
+    System.arraycopy(virtualMethods, 0, newMethods, 0, virtualMethods.length);
+    newMethods[virtualMethods.length] = method;
+    virtualMethods = newMethods;
+    assert verifyNoDuplicateMethods();
+  }
+
+  void appendVirtualMethods(Collection<DexEncodedMethod> methods) {
+    DexEncodedMethod[] newMethods = new DexEncodedMethod[virtualMethods.length + methods.size()];
+    System.arraycopy(virtualMethods, 0, newMethods, 0, virtualMethods.length);
+    int i = virtualMethods.length;
+    for (DexEncodedMethod method : methods) {
+      newMethods[i] = method;
+      i++;
+    }
+    virtualMethods = newMethods;
+    assert verifyNoDuplicateMethods();
+  }
+
+  void setVirtualMethods(DexEncodedMethod[] methods) {
+    virtualMethods = MoreObjects.firstNonNull(methods, DexEncodedMethod.EMPTY_ARRAY);
+    assert verifyNoDuplicateMethods();
+  }
+
+  void virtualizeMethods(Set<DexEncodedMethod> privateInstanceMethods) {
+    int vLen = virtualMethods.length;
+    int dLen = directMethods.length;
+    int mLen = privateInstanceMethods.size();
+    assert mLen <= dLen;
+
+    DexEncodedMethod[] newDirectMethods = new DexEncodedMethod[dLen - mLen];
+    int index = 0;
+    for (int i = 0; i < dLen; i++) {
+      DexEncodedMethod encodedMethod = directMethods[i];
+      if (!privateInstanceMethods.contains(encodedMethod)) {
+        newDirectMethods[index++] = encodedMethod;
+      }
+    }
+    assert index == dLen - mLen;
+    setDirectMethods(newDirectMethods);
+
+    DexEncodedMethod[] newVirtualMethods = new DexEncodedMethod[vLen + mLen];
+    System.arraycopy(virtualMethods, 0, newVirtualMethods, 0, vLen);
+    index = vLen;
+    for (DexEncodedMethod encodedMethod : privateInstanceMethods) {
+      newVirtualMethods[index++] = encodedMethod;
+    }
+    setVirtualMethods(newVirtualMethods);
+  }
+
+  DexEncodedMethod getDirectMethod(DexMethod method) {
+    for (DexEncodedMethod directMethod : directMethods) {
+      if (method.match(directMethod)) {
+        return directMethod;
+      }
+    }
+    return null;
+  }
+
+  DexEncodedMethod getDirectMethod(Predicate<DexEncodedMethod> predicate) {
+    return PredicateUtils.findFirst(directMethods, predicate);
+  }
+
+  DexEncodedMethod getVirtualMethod(DexMethod method) {
+    for (DexEncodedMethod virtualMethod : virtualMethods) {
+      if (method.match(virtualMethod)) {
+        return virtualMethod;
+      }
+    }
+    return null;
+  }
+
+  DexEncodedMethod getVirtualMethod(Predicate<DexEncodedMethod> predicate) {
+    return PredicateUtils.findFirst(virtualMethods, predicate);
+  }
+
+  DexEncodedMethod getMethod(DexMethod method) {
+    DexEncodedMethod result = getDirectMethod(method);
+    return result == null ? getVirtualMethod(method) : result;
+  }
+
+  void addMethod(DexEncodedMethod method) {
+    if (method.accessFlags.isStatic()
+        || method.accessFlags.isPrivate()
+        || method.accessFlags.isConstructor()) {
+      addDirectMethod(method);
+    } else {
+      addVirtualMethod(method);
+    }
+  }
+
+  void addVirtualMethod(DexEncodedMethod virtualMethod) {
+    assert !virtualMethod.accessFlags.isStatic();
+    assert !virtualMethod.accessFlags.isPrivate();
+    assert !virtualMethod.accessFlags.isConstructor();
+    virtualMethods = Arrays.copyOf(virtualMethods, virtualMethods.length + 1);
+    virtualMethods[virtualMethods.length - 1] = virtualMethod;
+  }
+
+  void addDirectMethod(DexEncodedMethod staticMethod) {
+    assert staticMethod.accessFlags.isStatic()
+        || staticMethod.accessFlags.isPrivate()
+        || staticMethod.accessFlags.isConstructor();
+    directMethods = Arrays.copyOf(directMethods, directMethods.length + 1);
+    directMethods[directMethods.length - 1] = staticMethod;
+  }
+
+  boolean verifyNoDuplicateMethods() {
+    Set<DexMethod> unique = Sets.newIdentityHashSet();
+    forEachMethod(
+        method -> {
+          boolean changed = unique.add(method.method);
+          assert changed : "Duplicate method `" + method.method.toSourceString() + "`";
+        });
+    return true;
+  }
+
+  public DexEncodedMethod replaceDirectMethod(
+      DexMethod method, Function<DexEncodedMethod, DexEncodedMethod> replacement) {
+    for (int i = 0; i < directMethods.length; i++) {
+      DexEncodedMethod directMethod = directMethods[i];
+      if (method.match(directMethod)) {
+        DexEncodedMethod newMethod = replacement.apply(directMethod);
+        assert belongsToDirectPool(newMethod);
+        directMethods[i] = newMethod;
+        return newMethod;
+      }
+    }
+    return null;
+  }
+
+  public DexEncodedMethod replaceDirectMethodWithVirtualMethod(
+      DexMethod method, Function<DexEncodedMethod, DexEncodedMethod> replacement) {
+    for (int i = 0; i < directMethods.length; i++) {
+      DexEncodedMethod directMethod = directMethods[i];
+      if (method.match(directMethod)) {
+        DexEncodedMethod newMethod = replacement.apply(directMethod);
+        assert belongsToVirtualPool(newMethod);
+        removeDirectMethod(i);
+        appendVirtualMethod(newMethod);
+        return newMethod;
+      }
+    }
+    return null;
+  }
+
+  public void replaceMethods(Function<DexEncodedMethod, DexEncodedMethod> replacement) {
+    replaceDirectMethods(replacement);
+    replaceVirtualMethods(replacement);
+  }
+
+  public void replaceDirectMethods(Function<DexEncodedMethod, DexEncodedMethod> replacement) {
+    for (int i = 0; i < directMethods.length; i++) {
+      DexEncodedMethod method = directMethods[i];
+      DexEncodedMethod newMethod = replacement.apply(method);
+      assert newMethod != null;
+      if (method != newMethod) {
+        assert belongsToDirectPool(newMethod);
+        directMethods[i] = newMethod;
+      }
+    }
+  }
+
+  public void replaceVirtualMethods(Function<DexEncodedMethod, DexEncodedMethod> replacement) {
+    for (int i = 0; i < virtualMethods.length; i++) {
+      DexEncodedMethod method = virtualMethods[i];
+      DexEncodedMethod newMethod = replacement.apply(method);
+      if (method != newMethod) {
+        assert belongsToVirtualPool(newMethod);
+        virtualMethods[i] = newMethod;
+      }
+    }
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/graph/MethodCollection.java b/src/main/java/com/android/tools/r8/graph/MethodCollection.java
new file mode 100644
index 0000000..a4f3d5a
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/graph/MethodCollection.java
@@ -0,0 +1,221 @@
+package com.android.tools.r8.graph;
+
+import com.android.tools.r8.utils.IterableUtils;
+import com.android.tools.r8.utils.TraversalContinuation;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.List;
+import java.util.Optional;
+import java.util.Set;
+import java.util.function.Consumer;
+import java.util.function.Function;
+import java.util.function.Predicate;
+
+public class MethodCollection {
+
+  private final DexClass holder;
+  private final MethodArrayBacking backing = new MethodArrayBacking();
+  private Optional<DexEncodedMethod> cachedClassInitializer = null;
+
+  public MethodCollection(DexClass holder) {
+    this.holder = holder;
+  }
+
+  public int size() {
+    return backing.size();
+  }
+
+  public TraversalContinuation traverse(Function<DexEncodedMethod, TraversalContinuation> fn) {
+    return backing.traverse(fn);
+  }
+
+  public void forEachMethod(Consumer<DexEncodedMethod> consumer) {
+    backing.forEachMethod(consumer);
+  }
+
+  public Iterable<DexEncodedMethod> methods() {
+    return backing.methods();
+  }
+
+  public Iterable<DexEncodedMethod> methods(Predicate<DexEncodedMethod> predicate) {
+    return IterableUtils.filter(methods(), predicate);
+  }
+
+  public List<DexEncodedMethod> allMethodsSorted() {
+    List<DexEncodedMethod> sorted = new ArrayList<>(size());
+    forEachMethod(sorted::add);
+    sorted.sort((a, b) -> a.method.slowCompareTo(b.method));
+    return sorted;
+  }
+
+  public List<DexEncodedMethod> directMethods() {
+    return backing.directMethods();
+  }
+
+  public List<DexEncodedMethod> virtualMethods() {
+    return backing.virtualMethods();
+  }
+
+  public DexEncodedMethod getMethod(DexMethod method) {
+    return backing.getMethod(method);
+  }
+
+  public DexEncodedMethod getDirectMethod(DexMethod method) {
+    return backing.getDirectMethod(method);
+  }
+
+  public DexEncodedMethod getDirectMethod(Predicate<DexEncodedMethod> predicate) {
+    return backing.getDirectMethod(predicate);
+  }
+
+  public DexEncodedMethod getVirtualMethod(DexMethod method) {
+    return backing.getVirtualMethod(method);
+  }
+
+  public DexEncodedMethod getVirtualMethod(Predicate<DexEncodedMethod> predicate) {
+    return backing.getVirtualMethod(predicate);
+  }
+
+  public DexEncodedMethod getClassInitializer() {
+    if (cachedClassInitializer == null) {
+      cachedClassInitializer = Optional.empty();
+      for (DexEncodedMethod directMethod : directMethods()) {
+        if (directMethod.isClassInitializer()) {
+          cachedClassInitializer = Optional.of(directMethod);
+          break;
+        }
+      }
+    }
+    return cachedClassInitializer.orElse(null);
+  }
+
+  public void addMethod(DexEncodedMethod method) {
+    backing.addMethod(method);
+  }
+
+  public void addVirtualMethod(DexEncodedMethod virtualMethod) {
+    backing.addVirtualMethod(virtualMethod);
+  }
+
+  public void addDirectMethod(DexEncodedMethod directMethod) {
+    cachedClassInitializer = null;
+    backing.addDirectMethod(directMethod);
+  }
+
+  public DexEncodedMethod replaceDirectMethod(
+      DexMethod method, Function<DexEncodedMethod, DexEncodedMethod> replacement) {
+    cachedClassInitializer = null;
+    return backing.replaceDirectMethod(method, replacement);
+  }
+
+  public void replaceMethods(Function<DexEncodedMethod, DexEncodedMethod> replacement) {
+    backing.replaceMethods(replacement);
+  }
+
+  public void replaceVirtualMethods(Function<DexEncodedMethod, DexEncodedMethod> replacement) {
+    backing.replaceVirtualMethods(replacement);
+  }
+
+  public void replaceDirectMethods(Function<DexEncodedMethod, DexEncodedMethod> replacement) {
+    cachedClassInitializer = null;
+    backing.replaceDirectMethods(replacement);
+  }
+
+  /**
+   * Replace a direct method, if found, by a computed virtual method using the replacement function.
+   *
+   * @param method Direct method to replace if present.
+   * @param replacement Replacement function computing the virtual replacement.
+   * @return Returns the replacement if found, null otherwise.
+   */
+  public DexEncodedMethod replaceDirectMethodWithVirtualMethod(
+      DexMethod method, Function<DexEncodedMethod, DexEncodedMethod> replacement) {
+    // The class initializer can never by converted to a virtual.
+    return backing.replaceDirectMethodWithVirtualMethod(method, replacement);
+  }
+
+  public void appendDirectMethod(DexEncodedMethod method) {
+    assert verifyCorrectnessOfMethodHolder(method);
+    cachedClassInitializer = null;
+    backing.appendDirectMethod(method);
+  }
+
+  public void appendDirectMethods(Collection<DexEncodedMethod> methods) {
+    assert verifyCorrectnessOfMethodHolders(methods);
+    cachedClassInitializer = null;
+    backing.appendDirectMethods(methods);
+  }
+
+  public void removeDirectMethod(DexMethod method) {
+    cachedClassInitializer = null;
+    backing.removeDirectMethod(method);
+  }
+
+  public void setDirectMethods(DexEncodedMethod[] methods) {
+    assert verifyCorrectnessOfMethodHolders(methods);
+    cachedClassInitializer = null;
+    backing.setDirectMethods(methods);
+  }
+
+  public void appendVirtualMethod(DexEncodedMethod method) {
+    assert verifyCorrectnessOfMethodHolder(method);
+    backing.appendVirtualMethod(method);
+  }
+
+  public void appendVirtualMethods(Collection<DexEncodedMethod> methods) {
+    assert verifyCorrectnessOfMethodHolders(methods);
+    backing.appendVirtualMethods(methods);
+  }
+
+  public void setVirtualMethods(DexEncodedMethod[] methods) {
+    assert verifyCorrectnessOfMethodHolders(methods);
+    backing.setVirtualMethods(methods);
+  }
+
+  public void virtualizeMethods(Set<DexEncodedMethod> privateInstanceMethods) {
+    backing.virtualizeMethods(privateInstanceMethods);
+  }
+
+  public boolean hasAnnotations() {
+    return traverse(
+            method ->
+                method.hasAnnotation()
+                    ? TraversalContinuation.BREAK
+                    : TraversalContinuation.CONTINUE)
+        .shouldBreak();
+  }
+
+  public boolean verify() {
+    forEachMethod(
+        method -> {
+          assert verifyCorrectnessOfMethodHolder(method);
+        });
+    assert backing.verifyNoDuplicateMethods();
+    return true;
+  }
+
+  private boolean verifyCorrectnessOfMethodHolder(DexEncodedMethod method) {
+    assert method.method.holder == holder.type
+        : "Expected method `"
+            + method.method.toSourceString()
+            + "` to have holder `"
+            + holder.type.toSourceString()
+            + "`";
+    return true;
+  }
+
+  private boolean verifyCorrectnessOfMethodHolders(DexEncodedMethod[] methods) {
+    if (methods == null) {
+      return true;
+    }
+    return verifyCorrectnessOfMethodHolders(Arrays.asList(methods));
+  }
+
+  private boolean verifyCorrectnessOfMethodHolders(Iterable<DexEncodedMethod> methods) {
+    for (DexEncodedMethod method : methods) {
+      assert verifyCorrectnessOfMethodHolder(method);
+    }
+    return true;
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/graph/MethodCollectionBacking.java b/src/main/java/com/android/tools/r8/graph/MethodCollectionBacking.java
new file mode 100644
index 0000000..b50adf6
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/graph/MethodCollectionBacking.java
@@ -0,0 +1,21 @@
+// 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;
+
+import com.android.tools.r8.utils.TraversalContinuation;
+import java.util.function.Consumer;
+import java.util.function.Function;
+
+public abstract class MethodCollectionBacking {
+
+  abstract TraversalContinuation traverse(Function<DexEncodedMethod, TraversalContinuation> fn);
+
+  void forEachMethod(Consumer<DexEncodedMethod> fn) {
+    traverse(
+        method -> {
+          fn.accept(method);
+          return TraversalContinuation.CONTINUE;
+        });
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/graph/ObjectToOffsetMapping.java b/src/main/java/com/android/tools/r8/graph/ObjectToOffsetMapping.java
index a29ae60..33f3b90 100644
--- a/src/main/java/com/android/tools/r8/graph/ObjectToOffsetMapping.java
+++ b/src/main/java/com/android/tools/r8/graph/ObjectToOffsetMapping.java
@@ -5,14 +5,16 @@
 
 import com.android.tools.r8.dex.Constants;
 import com.android.tools.r8.errors.CompilationError;
+import com.android.tools.r8.naming.NamingLens;
 import it.unimi.dsi.fastutil.objects.Reference2IntLinkedOpenHashMap;
 import it.unimi.dsi.fastutil.objects.Reference2IntMap;
 import it.unimi.dsi.fastutil.objects.Reference2IntMap.Entry;
 import it.unimi.dsi.fastutil.objects.Reference2IntOpenHashMap;
+import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
+import java.util.Comparator;
 import java.util.List;
-import java.util.Map;
 import java.util.function.Consumer;
 import java.util.stream.Collectors;
 
@@ -21,18 +23,25 @@
   private final static int NOT_FOUND = -1;
   private final static int NOT_SET = -2;
 
+  private final NamingLens namingLens;
+  private final InitClassLens initClassLens;
+
+  // Sorted collection of objects mapped to their offsets.
   private final DexProgramClass[] classes;
-  private final Reference2IntMap<DexProto> protos;
-  private final Reference2IntMap<DexType> types;
-  private final Reference2IntMap<DexMethod> methods;
-  private final Reference2IntMap<DexField> fields;
-  private final Reference2IntMap<DexString> strings;
-  private final Reference2IntMap<DexCallSite> callSites;
-  private final Reference2IntMap<DexMethodHandle> methodHandles;
+  private final Reference2IntLinkedOpenHashMap<DexProto> protos;
+  private final Reference2IntLinkedOpenHashMap<DexType> types;
+  private final Reference2IntLinkedOpenHashMap<DexMethod> methods;
+  private final Reference2IntLinkedOpenHashMap<DexField> fields;
+  private final Reference2IntLinkedOpenHashMap<DexString> strings;
+  private final Reference2IntLinkedOpenHashMap<DexCallSite> callSites;
+  private final Reference2IntLinkedOpenHashMap<DexMethodHandle> methodHandles;
+
   private DexString firstJumboString;
 
   public ObjectToOffsetMapping(
       DexApplication application,
+      NamingLens namingLens,
+      InitClassLens initClassLens,
       Collection<DexProgramClass> classes,
       Collection<DexProto> protos,
       Collection<DexType> types,
@@ -50,15 +59,21 @@
     assert strings != null;
     assert callSites != null;
     assert methodHandles != null;
+    assert initClassLens != null;
+    this.namingLens = namingLens;
+    this.initClassLens = initClassLens;
+    this.classes = sortClasses(application, classes, namingLens);
+    this.protos = createSortedMap(protos, compare(namingLens), this::failOnOverflow);
+    this.types = createSortedMap(types, compare(namingLens), this::failOnOverflow);
+    this.methods = createSortedMap(methods, compare(namingLens), this::failOnOverflow);
+    this.fields = createSortedMap(fields, compare(namingLens), this::failOnOverflow);
+    this.strings = createSortedMap(strings, compare(namingLens), this::setFirstJumboString);
+    this.callSites = createSortedMap(callSites, DexCallSite::compareTo, this::failOnOverflow);
+    this.methodHandles = createSortedMap(methodHandles, compare(namingLens), this::failOnOverflow);
+  }
 
-    this.classes = sortClasses(application, classes);
-    this.protos = createMap(protos, this::failOnOverflow);
-    this.types = createMap(types, this::failOnOverflow);
-    this.methods = createMap(methods, this::failOnOverflow);
-    this.fields = createMap(fields, this::failOnOverflow);
-    this.strings = createMap(strings, this::setFirstJumboString);
-    this.callSites = createMap(callSites, this::failOnOverflow);
-    this.methodHandles = createMap(methodHandles, this::failOnOverflow);
+  private static <T extends PresortedComparable<T>> Comparator<T> compare(NamingLens namingLens) {
+    return (a, b) -> a.slowCompareTo(b, namingLens);
   }
 
   private void setFirstJumboString(DexString string) {
@@ -70,14 +85,16 @@
     throw new CompilationError("Index overflow for " + item.getClass());
   }
 
-  private <T extends IndexedDexItem> Reference2IntMap<T> createMap(Collection<T> items,
-      Consumer<T> onUInt16Overflow) {
+  private <T> Reference2IntLinkedOpenHashMap<T> createSortedMap(
+      Collection<T> items, Comparator<T> comparator, Consumer<T> onUInt16Overflow) {
     if (items.isEmpty()) {
       return null;
     }
-    Reference2IntMap<T> map = new Reference2IntLinkedOpenHashMap<>(items.size());
+    // Sort items and compute the offset mapping for each in sorted order.
+    ArrayList<T> sorted = new ArrayList<>(items);
+    sorted.sort(comparator);
+    Reference2IntLinkedOpenHashMap<T> map = new Reference2IntLinkedOpenHashMap<>(items.size());
     map.defaultReturnValue(NOT_FOUND);
-    Collection<T> sorted = items.stream().sorted().collect(Collectors.toList());
     int index = 0;
     for (T item : sorted) {
       if (index == Constants.U16BIT_MAX + 1) {
@@ -135,26 +152,30 @@
   }
 
   private static DexProgramClass[] sortClasses(
-      DexApplication application, Collection<DexProgramClass> classes) {
+      DexApplication application, Collection<DexProgramClass> classes, NamingLens namingLens) {
     // Collect classes in subtyping order, based on a sorted list of classes to start with.
     ProgramClassDepthsMemoized classDepths = new ProgramClassDepthsMemoized(application);
     List<DexProgramClass> sortedClasses =
-        classes
-            .stream()
+        classes.stream()
             .sorted(
                 (x, y) -> {
                   int dx = classDepths.getDepth(x);
                   int dy = classDepths.getDepth(y);
-                  return dx != dy ? dx - dy : x.type.compareTo(y.type);
+                  return dx != dy ? dx - dy : x.type.slowCompareTo(y.type, namingLens);
                 })
             .collect(Collectors.toList());
     return sortedClasses.toArray(DexProgramClass.EMPTY_ARRAY);
   }
 
-  private static <T> Collection<T> keysOrEmpty(Map<T, ?> map) {
+  private static <T> Collection<T> keysOrEmpty(Reference2IntLinkedOpenHashMap<T> map) {
+    // The key-set is deterministic (linked) and inserted in sorted order.
     return map == null ? Collections.emptyList() : map.keySet();
   }
 
+  public NamingLens getNamingLens() {
+    return namingLens;
+  }
+
   public Collection<DexMethod> getMethods() {
     return keysOrEmpty(methods);
   }
@@ -238,4 +259,8 @@
   public int getOffsetFor(DexMethodHandle methodHandle) {
     return getOffsetFor(methodHandle, methodHandles);
   }
+
+  public DexField getClinitField(DexType type) {
+    return initClassLens.getInitClassField(type);
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/graph/Presorted.java b/src/main/java/com/android/tools/r8/graph/Presorted.java
deleted file mode 100644
index 475da13..0000000
--- a/src/main/java/com/android/tools/r8/graph/Presorted.java
+++ /dev/null
@@ -1,16 +0,0 @@
-// Copyright (c) 2016, the R8 project authors. Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-package com.android.tools.r8.graph;
-
-/**
- * Interface for capturing presorted behavior for Dex items.
- */
-public interface Presorted {
-
-  void setSortedIndex(int sortedIndex);
-
-  int getSortedIndex();
-
-  int sortedCompareTo(int other);
-}
diff --git a/src/main/java/com/android/tools/r8/graph/PresortedComparable.java b/src/main/java/com/android/tools/r8/graph/PresortedComparable.java
index 075c187..e4eb4b7 100644
--- a/src/main/java/com/android/tools/r8/graph/PresortedComparable.java
+++ b/src/main/java/com/android/tools/r8/graph/PresortedComparable.java
@@ -4,41 +4,11 @@
 package com.android.tools.r8.graph;
 
 import com.android.tools.r8.naming.NamingLens;
-import java.util.Arrays;
-import java.util.List;
-import java.util.function.Function;
 
-public interface PresortedComparable<T> extends Presorted, Comparable<T> {
+public interface PresortedComparable<T> {
 
-  static <D extends DexEncodedMember<D, R>, R extends DexMember<D, R>> boolean isSorted(
-      List<? extends DexEncodedMember<D, R>> items) {
-    return isSorted(items, DexEncodedMember::toReference);
-  }
-
-  static <S, T extends Comparable<T>> boolean isSorted(S[] items, Function<S, T> getter) {
-    return isSorted(Arrays.asList(items), getter);
-  }
-
-  static <S, T extends Comparable<T>> boolean isSorted(
-      List<? extends S> items, Function<S, T> getter) {
-    T current = null;
-    for (S item : items) {
-      T next = getter.apply(item);
-      if (current != null && current.compareTo(next) >= 0) {
-        return false;
-      }
-      current = next;
-    }
-    return true;
-  }
-
-  // Slow comparison methods that make no use of indices for comparisons. These are used
-  // for sorting operations when reading dex files.
   int slowCompareTo(T other);
   int slowCompareTo(T other, NamingLens namingLens);
-  // Layered comparison methods that make use of indices for subpart comparisons. These rely
-  // on subparts already being sorted and having indices assigned.
-  int layeredCompareTo(T other, NamingLens namingLens);
 
   static <T extends PresortedComparable<T>> int slowCompare(T a, T b) {
     return a.slowCompareTo(b);
diff --git a/src/main/java/com/android/tools/r8/graph/ResolutionResult.java b/src/main/java/com/android/tools/r8/graph/ResolutionResult.java
index f9cea40..5c1611d 100644
--- a/src/main/java/com/android/tools/r8/graph/ResolutionResult.java
+++ b/src/main/java/com/android/tools/r8/graph/ResolutionResult.java
@@ -410,14 +410,11 @@
       // TODO(b/148769279): Remove the check for hasInstantiatedLambdas.
       Box<Boolean> hasInstantiatedLambdas = new Box<>(false);
       InstantiatedSubTypeInfo instantiatedSubTypeInfo =
-          refinedReceiverLowerBound == null
-              ? instantiatedSubTypeInfoWithoutLowerBound(
-                  appInfo, refinedReceiverUpperBound, hasInstantiatedLambdas)
-              : instantiatedSubTypeInfoWithLowerBound(
-                  appInfo,
-                  refinedReceiverUpperBound,
-                  refinedReceiverLowerBound,
-                  hasInstantiatedLambdas);
+          instantiatedSubTypeInfoForInstantiatedType(
+              appInfo,
+              refinedReceiverUpperBound,
+              refinedReceiverLowerBound,
+              hasInstantiatedLambdas);
       LookupResult lookupResult =
           lookupVirtualDispatchTargets(
               context,
@@ -430,37 +427,28 @@
       return lookupResult;
     }
 
-    private InstantiatedSubTypeInfo instantiatedSubTypeInfoWithoutLowerBound(
-        AppInfoWithLiveness appInfo,
-        DexProgramClass refinedReceiverUpperBound,
-        Box<Boolean> hasInstantiatedLambdas) {
-      return (type, subTypeConsumer, callSiteConsumer) -> {
-        appInfo.forEachInstantiatedSubType(
-            refinedReceiverUpperBound.type,
-            subType -> {
-              if (appInfo.hasAnyInstantiatedLambdas(subType)) {
-                hasInstantiatedLambdas.set(true);
-              }
-              subTypeConsumer.accept(subType);
-            },
-            callSiteConsumer);
-      };
-    }
-
-    private InstantiatedSubTypeInfo instantiatedSubTypeInfoWithLowerBound(
+    private InstantiatedSubTypeInfo instantiatedSubTypeInfoForInstantiatedType(
         AppInfoWithLiveness appInfo,
         DexProgramClass refinedReceiverUpperBound,
         DexProgramClass refinedReceiverLowerBound,
         Box<Boolean> hasInstantiatedLambdas) {
-      return (type, subTypeConsumer, callSiteConsumer) -> {
-        List<DexProgramClass> subTypes =
-            appInfo.computeProgramClassRelationChain(
-                refinedReceiverLowerBound, refinedReceiverUpperBound);
-        for (DexProgramClass subType : subTypes) {
-          if (appInfo.hasAnyInstantiatedLambdas(subType)) {
-            hasInstantiatedLambdas.set(true);
-          }
-          subTypeConsumer.accept(subType);
+      return (ignored, subTypeConsumer, callSiteConsumer) -> {
+        Consumer<DexProgramClass> lambdaInstantiatedConsumer =
+            subType -> {
+              subTypeConsumer.accept(subType);
+              if (appInfo.hasAnyInstantiatedLambdas(subType)) {
+                hasInstantiatedLambdas.set(true);
+              }
+            };
+        if (refinedReceiverLowerBound == null) {
+          appInfo.forEachInstantiatedSubType(
+              refinedReceiverUpperBound.type, lambdaInstantiatedConsumer, callSiteConsumer);
+        } else {
+          appInfo.forEachInstantiatedSubTypeInChain(
+              refinedReceiverUpperBound,
+              refinedReceiverLowerBound,
+              lambdaInstantiatedConsumer,
+              callSiteConsumer);
         }
       };
     }
diff --git a/src/main/java/com/android/tools/r8/graph/RewrittenPrototypeDescription.java b/src/main/java/com/android/tools/r8/graph/RewrittenPrototypeDescription.java
index 663524d..dd2cde4 100644
--- a/src/main/java/com/android/tools/r8/graph/RewrittenPrototypeDescription.java
+++ b/src/main/java/com/android/tools/r8/graph/RewrittenPrototypeDescription.java
@@ -4,11 +4,9 @@
 
 package com.android.tools.r8.graph;
 
-import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
 import com.android.tools.r8.ir.code.ConstInstruction;
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.Position;
-import com.android.tools.r8.ir.code.ValueType;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.utils.BooleanUtils;
 import com.google.common.collect.Ordering;
@@ -150,28 +148,6 @@
       return newType == appView.dexItemFactory().voidType;
     }
 
-    public boolean defaultValueHasChanged() {
-      if (newType.isPrimitiveType()) {
-        if (oldType.isPrimitiveType()) {
-          return ValueType.fromDexType(newType) != ValueType.fromDexType(oldType);
-        }
-        return true;
-      } else if (oldType.isPrimitiveType()) {
-        return true;
-      }
-      // All reference types uses null as default value.
-      assert newType.isReferenceType();
-      assert oldType.isReferenceType();
-      return false;
-    }
-
-    public TypeLatticeElement defaultValueLatticeElement(AppView<?> appView) {
-      if (newType.isPrimitiveType()) {
-        return TypeLatticeElement.fromDexType(newType, null, appView);
-      }
-      return TypeLatticeElement.getNull();
-    }
-
     @Override
     public boolean isRewrittenTypeInfo() {
       return true;
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 f452556..2c27763 100644
--- a/src/main/java/com/android/tools/r8/graph/UseRegistry.java
+++ b/src/main/java/com/android/tools/r8/graph/UseRegistry.java
@@ -25,6 +25,8 @@
     this.factory = factory;
   }
 
+  public abstract boolean registerInitClass(DexType type);
+
   public abstract boolean registerInvokeVirtual(DexMethod method);
 
   public abstract boolean registerInvokeDirect(DexMethod method);
diff --git a/src/main/java/com/android/tools/r8/inspector/BooleanValueInspector.java b/src/main/java/com/android/tools/r8/inspector/BooleanValueInspector.java
new file mode 100644
index 0000000..1fba365
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/inspector/BooleanValueInspector.java
@@ -0,0 +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.inspector;
+
+public interface BooleanValueInspector extends ValueInspector {
+  boolean getBooleanValue();
+}
diff --git a/src/main/java/com/android/tools/r8/inspector/ByteValueInspector.java b/src/main/java/com/android/tools/r8/inspector/ByteValueInspector.java
new file mode 100644
index 0000000..718183e
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/inspector/ByteValueInspector.java
@@ -0,0 +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.inspector;
+
+public interface ByteValueInspector extends ValueInspector {
+  byte getByteValue();
+}
diff --git a/src/main/java/com/android/tools/r8/inspector/CharValueInspector.java b/src/main/java/com/android/tools/r8/inspector/CharValueInspector.java
new file mode 100644
index 0000000..d79ee9c
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/inspector/CharValueInspector.java
@@ -0,0 +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.inspector;
+
+public interface CharValueInspector extends ValueInspector {
+  char getCharValue();
+}
diff --git a/src/main/java/com/android/tools/r8/inspector/ClassInspector.java b/src/main/java/com/android/tools/r8/inspector/ClassInspector.java
new file mode 100644
index 0000000..b18dd26
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/inspector/ClassInspector.java
@@ -0,0 +1,22 @@
+// 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.inspector;
+
+import com.android.tools.r8.Keep;
+import com.android.tools.r8.references.ClassReference;
+import java.util.function.Consumer;
+
+/** Inspector for a class or interface definition. */
+@Keep
+public interface ClassInspector {
+
+  /** Get the class reference for the class of this inspector. */
+  ClassReference getClassReference();
+
+  /** Iterate all fields declared in the class/interface (unspecified order). */
+  void forEachField(Consumer<FieldInspector> inspection);
+
+  /** Iterate all methods declared in the class/interface (unspecified order). */
+  void forEachMethod(Consumer<MethodInspector> inspection);
+}
diff --git a/src/main/java/com/android/tools/r8/inspector/DoubleValueInspector.java b/src/main/java/com/android/tools/r8/inspector/DoubleValueInspector.java
new file mode 100644
index 0000000..334f7ac
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/inspector/DoubleValueInspector.java
@@ -0,0 +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.inspector;
+
+public interface DoubleValueInspector extends ValueInspector {
+  double getDoubleValue();
+}
diff --git a/src/main/java/com/android/tools/r8/inspector/FieldInspector.java b/src/main/java/com/android/tools/r8/inspector/FieldInspector.java
new file mode 100644
index 0000000..5d2e656
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/inspector/FieldInspector.java
@@ -0,0 +1,30 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.inspector;
+
+import com.android.tools.r8.Keep;
+import com.android.tools.r8.references.FieldReference;
+import java.util.Optional;
+
+/** Inspector for a field definition. */
+@Keep
+public interface FieldInspector {
+
+  /** Get the field reference for the field of this inspector. */
+  FieldReference getFieldReference();
+
+  /** True if the field is declared static. */
+  boolean isStatic();
+
+  /** True if the field is declared final. */
+  boolean isFinal();
+
+  /**
+   * Returns an inspector for the initial value if it is known by the compiler.
+   *
+   * <p>Note that the determination of the value is best effort, often the value will simply be the
+   * default value for the given type.
+   */
+  Optional<ValueInspector> getInitialValue();
+}
diff --git a/src/main/java/com/android/tools/r8/inspector/FloatValueInspector.java b/src/main/java/com/android/tools/r8/inspector/FloatValueInspector.java
new file mode 100644
index 0000000..cb4c12d
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/inspector/FloatValueInspector.java
@@ -0,0 +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.inspector;
+
+public interface FloatValueInspector extends ValueInspector {
+  float getFloatValue();
+}
diff --git a/src/main/java/com/android/tools/r8/inspector/Inspector.java b/src/main/java/com/android/tools/r8/inspector/Inspector.java
new file mode 100644
index 0000000..880b450
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/inspector/Inspector.java
@@ -0,0 +1,15 @@
+// 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.inspector;
+
+import com.android.tools.r8.Keep;
+import java.util.function.Consumer;
+
+/** Inspector providing access to various parts of an application. */
+@Keep
+public interface Inspector {
+
+  /** Iterate all classes and interfaces defined by the program (order unspecified). */
+  void forEachClass(Consumer<ClassInspector> inspection);
+}
diff --git a/src/main/java/com/android/tools/r8/inspector/IntValueInspector.java b/src/main/java/com/android/tools/r8/inspector/IntValueInspector.java
new file mode 100644
index 0000000..38376c2
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/inspector/IntValueInspector.java
@@ -0,0 +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.inspector;
+
+public interface IntValueInspector extends ValueInspector {
+  int getIntValue();
+}
diff --git a/src/main/java/com/android/tools/r8/inspector/LongValueInspector.java b/src/main/java/com/android/tools/r8/inspector/LongValueInspector.java
new file mode 100644
index 0000000..177a885
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/inspector/LongValueInspector.java
@@ -0,0 +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.inspector;
+
+public interface LongValueInspector extends ValueInspector {
+  long getLongValue();
+}
diff --git a/src/main/java/com/android/tools/r8/inspector/MethodInspector.java b/src/main/java/com/android/tools/r8/inspector/MethodInspector.java
new file mode 100644
index 0000000..41cc8de
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/inspector/MethodInspector.java
@@ -0,0 +1,15 @@
+// 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.inspector;
+
+import com.android.tools.r8.Keep;
+import com.android.tools.r8.references.MethodReference;
+
+/** Inspector for a method definition. */
+@Keep
+public interface MethodInspector {
+
+  /** Get the method reference for the method of this inspector. */
+  MethodReference getMethodReference();
+}
diff --git a/src/main/java/com/android/tools/r8/inspector/ShortValueInspector.java b/src/main/java/com/android/tools/r8/inspector/ShortValueInspector.java
new file mode 100644
index 0000000..fb1d823
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/inspector/ShortValueInspector.java
@@ -0,0 +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.inspector;
+
+public interface ShortValueInspector extends ValueInspector {
+  short getShortValue();
+}
diff --git a/src/main/java/com/android/tools/r8/inspector/StringValueInspector.java b/src/main/java/com/android/tools/r8/inspector/StringValueInspector.java
new file mode 100644
index 0000000..c0b78c4
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/inspector/StringValueInspector.java
@@ -0,0 +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.inspector;
+
+public interface StringValueInspector extends ValueInspector {
+  String getStringValue();
+}
diff --git a/src/main/java/com/android/tools/r8/inspector/ValueInspector.java b/src/main/java/com/android/tools/r8/inspector/ValueInspector.java
new file mode 100644
index 0000000..84f1fe0
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/inspector/ValueInspector.java
@@ -0,0 +1,53 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.inspector;
+
+import com.android.tools.r8.Keep;
+import com.android.tools.r8.references.TypeReference;
+
+/** Inspector for a JVM representable value. */
+@Keep
+public interface ValueInspector {
+
+  /** Get the type reference describing the type of the value. */
+  TypeReference getTypeReference();
+
+  boolean isPrimitive();
+
+  boolean isBooleanValue();
+
+  boolean isByteValue();
+
+  boolean isCharValue();
+
+  boolean isShortValue();
+
+  boolean isIntValue();
+
+  boolean isLongValue();
+
+  boolean isFloatValue();
+
+  boolean isDoubleValue();
+
+  boolean isStringValue();
+
+  BooleanValueInspector asBooleanValue();
+
+  ByteValueInspector asByteValue();
+
+  CharValueInspector asCharValue();
+
+  ShortValueInspector asShortValue();
+
+  IntValueInspector asIntValue();
+
+  LongValueInspector asLongValue();
+
+  FloatValueInspector asFloatValue();
+
+  DoubleValueInspector asDoubleValue();
+
+  StringValueInspector asStringValue();
+}
diff --git a/src/main/java/com/android/tools/r8/inspector/internal/ClassInspectorImpl.java b/src/main/java/com/android/tools/r8/inspector/internal/ClassInspectorImpl.java
new file mode 100644
index 0000000..c2ebc8e
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/inspector/internal/ClassInspectorImpl.java
@@ -0,0 +1,40 @@
+// 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.inspector.internal;
+
+import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.inspector.ClassInspector;
+import com.android.tools.r8.inspector.FieldInspector;
+import com.android.tools.r8.inspector.MethodInspector;
+import com.android.tools.r8.references.ClassReference;
+import com.android.tools.r8.references.Reference;
+import java.util.function.Consumer;
+
+public class ClassInspectorImpl implements ClassInspector {
+
+  private final DexClass clazz;
+  private ClassReference reference = null;
+
+  ClassInspectorImpl(DexClass clazz) {
+    this.clazz = clazz;
+  }
+
+  @Override
+  public ClassReference getClassReference() {
+    if (reference == null) {
+      reference = Reference.classFromDescriptor(clazz.type.toDescriptorString());
+    }
+    return reference;
+  }
+
+  @Override
+  public void forEachField(Consumer<FieldInspector> inspection) {
+    clazz.forEachField(field -> inspection.accept(new FieldInspectorImpl(this, field)));
+  }
+
+  @Override
+  public void forEachMethod(Consumer<MethodInspector> inspection) {
+    clazz.forEachMethod(method -> inspection.accept(new MethodInspectorImpl(this, method)));
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/inspector/internal/FieldInspectorImpl.java b/src/main/java/com/android/tools/r8/inspector/internal/FieldInspectorImpl.java
new file mode 100644
index 0000000..056d652e
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/inspector/internal/FieldInspectorImpl.java
@@ -0,0 +1,52 @@
+// 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.inspector.internal;
+
+import com.android.tools.r8.graph.DexEncodedField;
+import com.android.tools.r8.inspector.FieldInspector;
+import com.android.tools.r8.inspector.ValueInspector;
+import com.android.tools.r8.references.FieldReference;
+import com.android.tools.r8.references.Reference;
+import java.util.Optional;
+
+public class FieldInspectorImpl implements FieldInspector {
+  private final ClassInspectorImpl parent;
+  private final DexEncodedField field;
+  private FieldReference reference = null;
+
+  public FieldInspectorImpl(ClassInspectorImpl parent, DexEncodedField field) {
+    this.parent = parent;
+    this.field = field;
+  }
+
+  @Override
+  public FieldReference getFieldReference() {
+    if (reference == null) {
+      reference =
+          Reference.field(
+              parent.getClassReference(),
+              field.field.name.toString(),
+              Reference.typeFromDescriptor(field.field.type.toDescriptorString()));
+    }
+    return reference;
+  }
+
+  @Override
+  public boolean isStatic() {
+    return field.accessFlags.isStatic();
+  }
+
+  @Override
+  public boolean isFinal() {
+    return field.accessFlags.isFinal();
+  }
+
+  @Override
+  public Optional<ValueInspector> getInitialValue() {
+    if (field.isStatic() && field.getStaticValue() != null) {
+      return Optional.of(new ValueInspectorImpl(field.getStaticValue(), field.field.type));
+    }
+    return Optional.empty();
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/inspector/internal/InspectorImpl.java b/src/main/java/com/android/tools/r8/inspector/internal/InspectorImpl.java
new file mode 100644
index 0000000..c3d8a64
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/inspector/internal/InspectorImpl.java
@@ -0,0 +1,54 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.inspector.internal;
+
+import com.android.tools.r8.graph.DexApplication;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.inspector.ClassInspector;
+import com.android.tools.r8.inspector.Inspector;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.function.Consumer;
+
+public class InspectorImpl implements Inspector {
+
+  // This wrapping appears odd, but allows hooking in inspections on the impl type from tests.
+  public static List<Consumer<InspectorImpl>> wrapInspections(
+      Collection<Consumer<Inspector>> inspections) {
+    if (inspections == null || inspections.isEmpty()) {
+      return Collections.emptyList();
+    }
+    List<Consumer<InspectorImpl>> wrapped = new ArrayList<>(inspections.size());
+    for (Consumer<Inspector> inspection : inspections) {
+      wrapped.add(inspection::accept);
+    }
+    return wrapped;
+  }
+
+  public static void runInspections(
+      List<Consumer<InspectorImpl>> inspections, DexApplication application) {
+    if (inspections == null || inspections.isEmpty()) {
+      return;
+    }
+    InspectorImpl inspector = new InspectorImpl(application);
+    for (Consumer<InspectorImpl> inspection : inspections) {
+      inspection.accept(inspector);
+    }
+  }
+
+  private final DexApplication application;
+
+  public InspectorImpl(DexApplication application) {
+    this.application = application;
+  }
+
+  @Override
+  public void forEachClass(Consumer<ClassInspector> inspection) {
+    for (DexProgramClass clazz : application.classes()) {
+      inspection.accept(new ClassInspectorImpl(clazz));
+    }
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/inspector/internal/MethodInspectorImpl.java b/src/main/java/com/android/tools/r8/inspector/internal/MethodInspectorImpl.java
new file mode 100644
index 0000000..1d868be
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/inspector/internal/MethodInspectorImpl.java
@@ -0,0 +1,41 @@
+// 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.inspector.internal;
+
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.inspector.MethodInspector;
+import com.android.tools.r8.references.MethodReference;
+import com.android.tools.r8.references.Reference;
+import com.android.tools.r8.utils.ListUtils;
+import java.util.Arrays;
+
+public class MethodInspectorImpl implements MethodInspector {
+
+  private final ClassInspectorImpl parent;
+  private final DexEncodedMethod method;
+  private MethodReference reference;
+
+  public MethodInspectorImpl(ClassInspectorImpl parent, DexEncodedMethod method) {
+    this.parent = parent;
+    this.method = method;
+  }
+
+  @Override
+  public MethodReference getMethodReference() {
+    if (reference == null) {
+      reference =
+          Reference.method(
+              parent.getClassReference(),
+              method.method.name.toString(),
+              ListUtils.map(
+                  Arrays.asList(method.method.proto.parameters.values),
+                  param -> Reference.typeFromDescriptor(param.toDescriptorString())),
+              method.method.proto.returnType.isVoidType()
+                  ? null
+                  : Reference.typeFromDescriptor(
+                      method.method.proto.returnType.toDescriptorString()));
+    }
+    return reference;
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/inspector/internal/ValueInspectorImpl.java b/src/main/java/com/android/tools/r8/inspector/internal/ValueInspectorImpl.java
new file mode 100644
index 0000000..8f269d7
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/inspector/internal/ValueInspectorImpl.java
@@ -0,0 +1,198 @@
+// 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.inspector.internal;
+
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.DexValue;
+import com.android.tools.r8.inspector.BooleanValueInspector;
+import com.android.tools.r8.inspector.ByteValueInspector;
+import com.android.tools.r8.inspector.CharValueInspector;
+import com.android.tools.r8.inspector.DoubleValueInspector;
+import com.android.tools.r8.inspector.FloatValueInspector;
+import com.android.tools.r8.inspector.IntValueInspector;
+import com.android.tools.r8.inspector.LongValueInspector;
+import com.android.tools.r8.inspector.ShortValueInspector;
+import com.android.tools.r8.inspector.StringValueInspector;
+import com.android.tools.r8.references.Reference;
+import com.android.tools.r8.references.TypeReference;
+
+public class ValueInspectorImpl
+    implements BooleanValueInspector,
+        ByteValueInspector,
+        CharValueInspector,
+        ShortValueInspector,
+        IntValueInspector,
+        LongValueInspector,
+        FloatValueInspector,
+        DoubleValueInspector,
+        StringValueInspector {
+
+  private final DexValue value;
+  private final DexType type;
+
+  public ValueInspectorImpl(DexValue value, DexType type) {
+    this.value = value;
+    this.type = type;
+  }
+
+  @Override
+  public TypeReference getTypeReference() {
+    return Reference.typeFromDescriptor(type.toDescriptorString());
+  }
+
+  @Override
+  public boolean isPrimitive() {
+    return type.isPrimitiveType();
+  }
+
+  @Override
+  public boolean isBooleanValue() {
+    return type.isBooleanType();
+  }
+
+  @Override
+  public BooleanValueInspector asBooleanValue() {
+    return isBooleanValue() ? this : null;
+  }
+
+  @Override
+  public boolean getBooleanValue() {
+    guard(isBooleanValue());
+    return value.asDexValueBoolean().getValue();
+  }
+
+  @Override
+  public boolean isByteValue() {
+    return type.isByteType();
+  }
+
+  @Override
+  public ByteValueInspector asByteValue() {
+    return isByteValue() ? this : null;
+  }
+
+  @Override
+  public byte getByteValue() {
+    guard(isByteValue());
+    return value.asDexValueByte().getValue();
+  }
+
+  @Override
+  public boolean isCharValue() {
+    return type.isCharType();
+  }
+
+  @Override
+  public CharValueInspector asCharValue() {
+    return isCharValue() ? this : null;
+  }
+
+  @Override
+  public char getCharValue() {
+    guard(isCharValue());
+    return value.asDexValueChar().getValue();
+  }
+
+  @Override
+  public boolean isShortValue() {
+    return type.isShortType();
+  }
+
+  @Override
+  public ShortValueInspector asShortValue() {
+    return isShortValue() ? this : null;
+  }
+
+  @Override
+  public short getShortValue() {
+    guard(isShortValue());
+    return value.asDexValueShort().getValue();
+  }
+
+  @Override
+  public boolean isIntValue() {
+    return type.isIntType();
+  }
+
+  @Override
+  public IntValueInspector asIntValue() {
+    return isIntValue() ? this : null;
+  }
+
+  @Override
+  public int getIntValue() {
+    guard(isIntValue());
+    return value.asDexValueInt().value;
+  }
+
+  @Override
+  public boolean isLongValue() {
+    return type.isLongType();
+  }
+
+  @Override
+  public LongValueInspector asLongValue() {
+    return isLongValue() ? this : null;
+  }
+
+  @Override
+  public long getLongValue() {
+    guard(isLongValue());
+    return value.asDexValueLong().getValue();
+  }
+
+  @Override
+  public boolean isFloatValue() {
+    return type.isFloatType();
+  }
+
+  @Override
+  public FloatValueInspector asFloatValue() {
+    return isFloatValue() ? this : null;
+  }
+
+  @Override
+  public float getFloatValue() {
+    guard(isFloatValue());
+    return value.asDexValueFloat().getValue();
+  }
+
+  @Override
+  public boolean isDoubleValue() {
+    return type.isDoubleType();
+  }
+
+  @Override
+  public DoubleValueInspector asDoubleValue() {
+    return isDoubleValue() ? this : null;
+  }
+
+  @Override
+  public double getDoubleValue() {
+    guard(isDoubleValue());
+    return value.asDexValueDouble().getValue();
+  }
+
+  @Override
+  public boolean isStringValue() {
+    return type.isClassType() && value.isDexValueString();
+  }
+
+  @Override
+  public StringValueInspector asStringValue() {
+    return isStringValue() ? this : null;
+  }
+
+  @Override
+  public String getStringValue() {
+    guard(isStringValue());
+    return value.asDexValueString().getValue().toString();
+  }
+
+  private static void guard(boolean precondition) {
+    if (!precondition) {
+      throw new IllegalStateException("Invalid call on ValueInspector");
+    }
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/ClassInitializationAnalysis.java b/src/main/java/com/android/tools/r8/ir/analysis/ClassInitializationAnalysis.java
index 80c81e9..7270d42 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/ClassInitializationAnalysis.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/ClassInitializationAnalysis.java
@@ -22,6 +22,7 @@
 import com.android.tools.r8.ir.code.DominatorTree.Inclusive;
 import com.android.tools.r8.ir.code.FieldInstruction;
 import com.android.tools.r8.ir.code.IRCode;
+import com.android.tools.r8.ir.code.InitClass;
 import com.android.tools.r8.ir.code.InstanceGet;
 import com.android.tools.r8.ir.code.InstancePut;
 import com.android.tools.r8.ir.code.Instruction;
@@ -233,6 +234,20 @@
 
   public static class InstructionUtils {
 
+    public static boolean forInitClass(
+        InitClass instruction,
+        DexType type,
+        AppView<?> appView,
+        Query mode,
+        AnalysisAssumption assumption) {
+      if (assumption == AnalysisAssumption.NONE) {
+        // Class initialization may fail with ExceptionInInitializerError.
+        return false;
+      }
+      DexClass clazz = appView.definitionFor(instruction.getClassValue());
+      return clazz != null && isTypeInitializedBy(instruction, type, clazz, appView, mode);
+    }
+
     public static boolean forInstanceGet(
         InstanceGet instruction,
         DexType type,
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/DeterminismAnalysis.java b/src/main/java/com/android/tools/r8/ir/analysis/DeterminismAnalysis.java
index b8e6950..0fc8bd0 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/DeterminismAnalysis.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/DeterminismAnalysis.java
@@ -56,16 +56,17 @@
       }
       // isArrayPut and isFieldPut are missed as they don't have out value.
       assert instr.isArgument()
-          || instr.isAssume()
-          || instr.isBinop()
-          || instr.isUnop()
-          || instr.isMonitor()
-          || instr.isMove()
-          || instr.isCheckCast()
-          || instr.isInstanceOf()
-          || instr.isConstInstruction()
-          || instr.isJumpInstruction()
-          || instr.isDebugInstruction()
+              || instr.isAssume()
+              || instr.isBinop()
+              || instr.isInitClass()
+              || instr.isUnop()
+              || instr.isMonitor()
+              || instr.isMove()
+              || instr.isCheckCast()
+              || instr.isInstanceOf()
+              || instr.isConstInstruction()
+              || instr.isJumpInstruction()
+              || instr.isDebugInstruction()
           : "Instruction that impacts determinism: " + instr;
     }
     return true;
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/constant/SparseConditionalConstantPropagation.java b/src/main/java/com/android/tools/r8/ir/analysis/constant/SparseConditionalConstantPropagation.java
index 72ac893..0bb8f21 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/constant/SparseConditionalConstantPropagation.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/constant/SparseConditionalConstantPropagation.java
@@ -117,7 +117,7 @@
       block.deduplicatePhis();
     }
 
-    code.removeAllTrivialPhis();
+    code.removeAllDeadAndTrivialPhis();
   }
 
   private LatticeElement getLatticeElement(Value value) {
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 b06a015..5ddacd6 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
@@ -5,17 +5,13 @@
 package com.android.tools.r8.ir.analysis.fieldaccess;
 
 
-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.DexEncodedMethod;
 import com.android.tools.r8.graph.DexField;
-import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.graph.FieldAccessFlags;
 import com.android.tools.r8.graph.FieldAccessInfo;
 import com.android.tools.r8.graph.FieldAccessInfoCollection;
 import com.android.tools.r8.graph.UseRegistry;
@@ -26,7 +22,6 @@
 import com.android.tools.r8.ir.optimize.info.OptimizationFeedbackDelayed;
 import com.android.tools.r8.ir.optimize.info.OptimizationFeedbackSimple;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
-import com.android.tools.r8.utils.OptionalBool;
 import com.android.tools.r8.utils.ThreadUtils;
 import com.android.tools.r8.utils.Timing;
 import com.google.common.collect.Sets;
@@ -61,7 +56,7 @@
     assert feedback.noUpdatesLeft();
 
     timing.begin("Compute fields of interest");
-    computeFieldsOfInterest(appInfo);
+    computeFieldsOfInterest();
     timing.end(); // Compute fields of interest
 
     if (fieldsOfInterest.isEmpty()) {
@@ -81,40 +76,18 @@
     fieldsOfInterest.forEach(OptimizationFeedbackSimple.getInstance()::markFieldAsDead);
   }
 
-  private void computeFieldsOfInterest(AppInfoWithLiveness appInfo) {
-    DexItemFactory dexItemFactory = appView.dexItemFactory();
-    for (DexProgramClass clazz : appInfo.classes()) {
+  private void computeFieldsOfInterest() {
+    for (DexProgramClass clazz : appView.appInfo().classes()) {
       for (DexEncodedField field : clazz.instanceFields()) {
         if (canOptimizeField(field, appView)) {
           fieldsOfInterest.add(field);
         }
       }
-      OptionalBool mayRequireClinitField = OptionalBool.unknown();
-      for (DexEncodedField field : clazz.staticFields()) {
-        if (canOptimizeField(field, appView)) {
-          if (mayRequireClinitField.isUnknown()) {
-            mayRequireClinitField =
-                OptionalBool.of(clazz.classInitializationMayHaveSideEffects(appView));
+      if (appView.canUseInitClass() || !clazz.classInitializationMayHaveSideEffects(appView)) {
+        for (DexEncodedField field : clazz.staticFields()) {
+          if (canOptimizeField(field, appView)) {
+            fieldsOfInterest.add(field);
           }
-          fieldsOfInterest.add(field);
-        }
-      }
-      if (mayRequireClinitField.isTrue()) {
-        DexField clinitField = dexItemFactory.objectMembers.clinitField;
-        if (clazz.lookupStaticField(dexItemFactory.objectMembers.clinitField) == null) {
-          FieldAccessFlags accessFlags =
-              FieldAccessFlags.fromSharedAccessFlags(
-                  Constants.ACC_SYNTHETIC
-                      | Constants.ACC_FINAL
-                      | Constants.ACC_PUBLIC
-                      | Constants.ACC_STATIC);
-          clazz.appendStaticField(
-              new DexEncodedField(
-                  dexItemFactory.createField(clazz.type, clinitField.type, clinitField.name),
-                  accessFlags,
-                  DexAnnotationSet.empty(),
-                  null));
-          appView.appInfo().invalidateTypeCacheFor(clazz.type);
         }
       }
     }
@@ -224,6 +197,11 @@
     }
 
     @Override
+    public boolean registerInitClass(DexType clazz) {
+      return false;
+    }
+
+    @Override
     public boolean registerInvokeVirtual(DexMethod method) {
       return false;
     }
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/value/SingleFieldValue.java b/src/main/java/com/android/tools/r8/ir/analysis/value/SingleFieldValue.java
index dffd9e0..5479737 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/value/SingleFieldValue.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/value/SingleFieldValue.java
@@ -111,7 +111,7 @@
   public SingleValue rewrittenWithLens(AppView<AppInfoWithLiveness> appView, GraphLense lens) {
     DexField rewrittenField = lens.lookupField(field);
     assert !appView.unboxedEnums().containsEnum(field.holder)
-        || !appView.definitionFor(rewrittenField).accessFlags.isEnum();
+        || !appView.appInfo().resolveField(rewrittenField).accessFlags.isEnum();
     return appView.abstractValueFactory().createSingleFieldValue(rewrittenField);
   }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/code/ArrayLength.java b/src/main/java/com/android/tools/r8/ir/code/ArrayLength.java
index 12ff943..da92bf3 100644
--- a/src/main/java/com/android/tools/r8/ir/code/ArrayLength.java
+++ b/src/main/java/com/android/tools/r8/ir/code/ArrayLength.java
@@ -83,7 +83,8 @@
   }
 
   @Override
-  public boolean instructionMayHaveSideEffects(AppView<?> appView, DexType context) {
+  public boolean instructionMayHaveSideEffects(
+      AppView<?> appView, DexType context, SideEffectAssumption assumption) {
     return instructionInstanceCanThrow(appView, context).isThrowing();
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/code/ArrayPut.java b/src/main/java/com/android/tools/r8/ir/code/ArrayPut.java
index 8ab298a..589710f 100644
--- a/src/main/java/com/android/tools/r8/ir/code/ArrayPut.java
+++ b/src/main/java/com/android/tools/r8/ir/code/ArrayPut.java
@@ -144,7 +144,8 @@
   }
 
   @Override
-  public boolean instructionMayHaveSideEffects(AppView<?> appView, DexType context) {
+  public boolean instructionMayHaveSideEffects(
+      AppView<?> appView, DexType context, SideEffectAssumption assumption) {
     // In debug mode, ArrayPut has a side-effect on the locals.
     if (appView.options().debug) {
       return true;
diff --git a/src/main/java/com/android/tools/r8/ir/code/CheckCast.java b/src/main/java/com/android/tools/r8/ir/code/CheckCast.java
index c28bc4a..a084a0f 100644
--- a/src/main/java/com/android/tools/r8/ir/code/CheckCast.java
+++ b/src/main/java/com/android/tools/r8/ir/code/CheckCast.java
@@ -95,7 +95,8 @@
   }
 
   @Override
-  public boolean instructionMayHaveSideEffects(AppView<?> appView, DexType context) {
+  public boolean instructionMayHaveSideEffects(
+      AppView<?> appView, DexType context, SideEffectAssumption assumption) {
     return instructionInstanceCanThrow(appView, context).isThrowing();
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/code/ConstClass.java b/src/main/java/com/android/tools/r8/ir/code/ConstClass.java
index 1574275..a9089f8 100644
--- a/src/main/java/com/android/tools/r8/ir/code/ConstClass.java
+++ b/src/main/java/com/android/tools/r8/ir/code/ConstClass.java
@@ -135,7 +135,8 @@
   }
 
   @Override
-  public boolean instructionMayHaveSideEffects(AppView<?> appView, DexType context) {
+  public boolean instructionMayHaveSideEffects(
+      AppView<?> appView, DexType context, SideEffectAssumption assumption) {
     return instructionInstanceCanThrow(appView, context).isThrowing();
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/code/DefaultInstructionVisitor.java b/src/main/java/com/android/tools/r8/ir/code/DefaultInstructionVisitor.java
index f2cdc93..fdfbe61 100644
--- a/src/main/java/com/android/tools/r8/ir/code/DefaultInstructionVisitor.java
+++ b/src/main/java/com/android/tools/r8/ir/code/DefaultInstructionVisitor.java
@@ -160,6 +160,11 @@
   }
 
   @Override
+  public T visit(InitClass instruction) {
+    return null;
+  }
+
+  @Override
   public T visit(InstanceGet instruction) {
     return handleFieldInstruction(instruction);
   }
diff --git a/src/main/java/com/android/tools/r8/ir/code/FieldInstruction.java b/src/main/java/com/android/tools/r8/ir/code/FieldInstruction.java
index 366f0cf..507ae25 100644
--- a/src/main/java/com/android/tools/r8/ir/code/FieldInstruction.java
+++ b/src/main/java/com/android/tools/r8/ir/code/FieldInstruction.java
@@ -28,20 +28,6 @@
 
 public abstract class FieldInstruction extends Instruction {
 
-  public enum Assumption {
-    NONE,
-    CLASS_ALREADY_INITIALIZED,
-    RECEIVER_NOT_NULL;
-
-    boolean canAssumeClassIsAlreadyInitialized() {
-      return this == CLASS_ALREADY_INITIALIZED;
-    }
-
-    boolean canAssumeReceiverIsNotNull() {
-      return this == RECEIVER_NOT_NULL;
-    }
-  }
-
   private final DexField field;
 
   protected FieldInstruction(DexField field, Value dest, Value value) {
@@ -76,11 +62,11 @@
 
   @Override
   public AbstractError instructionInstanceCanThrow(AppView<?> appView, DexType context) {
-    return instructionInstanceCanThrow(appView, context, Assumption.NONE);
+    return instructionInstanceCanThrow(appView, context, SideEffectAssumption.NONE);
   }
 
   public AbstractError instructionInstanceCanThrow(
-      AppView<?> appView, DexType context, Assumption assumption) {
+      AppView<?> appView, DexType context, SideEffectAssumption assumption) {
     DexEncodedField resolvedField;
     if (appView.enableWholeProgramOptimizations()) {
       // TODO(b/123857022): Should be possible to use definitionFor().
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 952bd5c..883d49f 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
@@ -1126,27 +1126,32 @@
     return true;
   }
 
-  public boolean removeAllTrivialPhis() {
-    return removeAllTrivialPhis(null, null);
+  public boolean removeAllDeadAndTrivialPhis() {
+    return removeAllDeadAndTrivialPhis(null, null);
   }
 
-  public boolean removeAllTrivialPhis(IRBuilder builder) {
-    return removeAllTrivialPhis(builder, null);
+  public boolean removeAllDeadAndTrivialPhis(IRBuilder builder) {
+    return removeAllDeadAndTrivialPhis(builder, null);
   }
 
-  public boolean removeAllTrivialPhis(Set<Value> affectedValues) {
-    return removeAllTrivialPhis(null, affectedValues);
+  public boolean removeAllDeadAndTrivialPhis(Set<Value> affectedValues) {
+    return removeAllDeadAndTrivialPhis(null, affectedValues);
   }
 
-  public boolean removeAllTrivialPhis(IRBuilder builder, Set<Value> affectedValues) {
-    boolean anyTrivialPhisRemoved = false;
+  public boolean removeAllDeadAndTrivialPhis(IRBuilder builder, Set<Value> affectedValues) {
+    boolean anyPhisRemoved = false;
     for (BasicBlock block : blocks) {
       List<Phi> phis = new ArrayList<>(block.getPhis());
       for (Phi phi : phis) {
-        anyTrivialPhisRemoved |= phi.removeTrivialPhi(builder, affectedValues);
+        if (phi.hasAnyUsers()) {
+          anyPhisRemoved |= phi.removeTrivialPhi(builder, affectedValues);
+        } else {
+          phi.removeDeadPhi();
+          anyPhisRemoved = true;
+        }
       }
     }
-    return anyTrivialPhisRemoved;
+    return anyPhisRemoved;
   }
 
   public int reserveMarkingColor() {
diff --git a/src/main/java/com/android/tools/r8/ir/code/InitClass.java b/src/main/java/com/android/tools/r8/ir/code/InitClass.java
new file mode 100644
index 0000000..8dd260c
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/code/InitClass.java
@@ -0,0 +1,167 @@
+// 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.code;
+
+import static com.android.tools.r8.optimize.MemberRebindingAnalysis.isTypeVisibleFromContext;
+
+import com.android.tools.r8.cf.LoadStoreHelper;
+import com.android.tools.r8.cf.code.CfInitClass;
+import com.android.tools.r8.code.DexInitClass;
+import com.android.tools.r8.dex.Constants;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.ir.analysis.AbstractError;
+import com.android.tools.r8.ir.analysis.ClassInitializationAnalysis;
+import com.android.tools.r8.ir.analysis.ClassInitializationAnalysis.AnalysisAssumption;
+import com.android.tools.r8.ir.analysis.ClassInitializationAnalysis.Query;
+import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.conversion.CfBuilder;
+import com.android.tools.r8.ir.conversion.DexBuilder;
+import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
+import com.android.tools.r8.ir.optimize.InliningConstraints;
+import com.google.common.collect.Sets;
+
+public class InitClass extends Instruction {
+
+  private final DexType clazz;
+
+  public InitClass(Value outValue, DexType clazz) {
+    super(outValue);
+    assert hasOutValue();
+    assert outValue.getTypeLattice().isInt();
+    assert clazz.isClassType();
+    this.clazz = clazz;
+  }
+
+  public DexType getClassValue() {
+    return clazz;
+  }
+
+  @Override
+  public boolean isInitClass() {
+    return true;
+  }
+
+  @Override
+  public InitClass asInitClass() {
+    return this;
+  }
+
+  @Override
+  public TypeLatticeElement evaluate(AppView<?> appView) {
+    return TypeLatticeElement.getInt();
+  }
+
+  @Override
+  public int opcode() {
+    return Opcodes.INIT_CLASS;
+  }
+
+  @Override
+  public <T> T accept(InstructionVisitor<T> visitor) {
+    return visitor.visit(this);
+  }
+
+  @Override
+  public void buildDex(DexBuilder builder) {
+    int dest = builder.allocatedRegister(outValue(), getNumber());
+    builder.add(this, new DexInitClass(dest, clazz));
+  }
+
+  @Override
+  public void buildCf(CfBuilder builder) {
+    builder.add(new CfInitClass(clazz));
+  }
+
+  @Override
+  public boolean definitelyTriggersClassInitialization(
+      DexType clazz,
+      DexType context,
+      AppView<?> appView,
+      Query mode,
+      AnalysisAssumption assumption) {
+    return ClassInitializationAnalysis.InstructionUtils.forInitClass(
+        this, clazz, appView, mode, assumption);
+  }
+
+  @Override
+  public boolean identicalNonValueNonPositionParts(Instruction other) {
+    return other.isInitClass() && clazz == other.asInitClass().clazz;
+  }
+
+  @Override
+  public AbstractError instructionInstanceCanThrow(AppView<?> appView, DexType context) {
+    if (!isTypeVisibleFromContext(appView, context, clazz)) {
+      return AbstractError.top();
+    }
+    if (clazz.classInitializationMayHaveSideEffects(
+        appView,
+        // Types that are a super type of `context` are guaranteed to be initialized already.
+        type -> appView.isSubtype(context, type).isTrue(),
+        Sets.newIdentityHashSet())) {
+      return AbstractError.top();
+    }
+    return AbstractError.bottom();
+  }
+
+  @Override
+  public boolean instructionMayHaveSideEffects(
+      AppView<?> appView, DexType context, SideEffectAssumption assumption) {
+    return instructionInstanceCanThrow(appView, context).isThrowing();
+  }
+
+  @Override
+  public boolean instructionMayTriggerMethodInvocation(AppView<?> appView, DexType context) {
+    if (appView.enableWholeProgramOptimizations()) {
+      // In R8, check if the class initialization of `clazz` or any of its ancestor types may have
+      // side effects.
+      return clazz.classInitializationMayHaveSideEffects(
+          appView,
+          // Types that are a super type of `context` are guaranteed to be initialized already.
+          type -> appView.isSubtype(context, type).isTrue(),
+          Sets.newIdentityHashSet());
+    } else {
+      // In D8, this instruction may trigger class initialization if `clazz` is different from the
+      // current context.
+      return clazz != context;
+    }
+  }
+
+  @Override
+  public boolean instructionTypeCanThrow() {
+    return true;
+  }
+
+  @Override
+  public int maxInValueRegister() {
+    return Constants.U8BIT_MAX;
+  }
+
+  @Override
+  public int maxOutValueRegister() {
+    return Constants.U8BIT_MAX;
+  }
+
+  @Override
+  public ConstraintWithTarget inliningConstraint(
+      InliningConstraints inliningConstraints, DexType invocationContext) {
+    return inliningConstraints.forInitClass(clazz, invocationContext);
+  }
+
+  @Override
+  public void insertLoadAndStores(InstructionListIterator it, LoadStoreHelper helper) {
+    helper.storeOutValue(this, it);
+  }
+
+  @Override
+  public boolean hasInvariantOutType() {
+    return true;
+  }
+
+  @Override
+  public String toString() {
+    return super.toString() + "; " + clazz.toSourceString();
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/code/InstanceFieldInstruction.java b/src/main/java/com/android/tools/r8/ir/code/InstanceFieldInstruction.java
index 94bfdaf..e78d3a7 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InstanceFieldInstruction.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InstanceFieldInstruction.java
@@ -6,7 +6,7 @@
 
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.ir.code.FieldInstruction.Assumption;
+import com.android.tools.r8.ir.code.Instruction.SideEffectAssumption;
 
 public interface InstanceFieldInstruction {
 
@@ -16,10 +16,13 @@
 
   Value object();
 
-  boolean instructionMayHaveSideEffects(AppView<?> appView, DexType context, Assumption assumption);
+  boolean instructionMayHaveSideEffects(
+      AppView<?> appView, DexType context, SideEffectAssumption assumption);
 
   FieldInstruction asFieldInstruction();
 
+  boolean isInstanceFieldInstruction();
+
   boolean isInstanceGet();
 
   InstanceGet asInstanceGet();
diff --git a/src/main/java/com/android/tools/r8/ir/code/InstanceGet.java b/src/main/java/com/android/tools/r8/ir/code/InstanceGet.java
index b070660..6d9b8e9 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InstanceGet.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InstanceGet.java
@@ -115,13 +115,8 @@
   }
 
   @Override
-  public boolean instructionMayHaveSideEffects(AppView<?> appView, DexType context) {
-    return instructionMayHaveSideEffects(appView, context, Assumption.NONE);
-  }
-
-  @Override
   public boolean instructionMayHaveSideEffects(
-      AppView<?> appView, DexType context, Assumption assumption) {
+      AppView<?> appView, DexType context, SideEffectAssumption assumption) {
     return instructionInstanceCanThrow(appView, context, assumption).isThrowing();
   }
 
@@ -161,6 +156,16 @@
   }
 
   @Override
+  public boolean isInstanceFieldInstruction() {
+    return true;
+  }
+
+  @Override
+  public InstanceFieldInstruction asInstanceFieldInstruction() {
+    return this;
+  }
+
+  @Override
   public boolean isInstanceGet() {
     return true;
   }
diff --git a/src/main/java/com/android/tools/r8/ir/code/InstancePut.java b/src/main/java/com/android/tools/r8/ir/code/InstancePut.java
index 00a5f30..5680260 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InstancePut.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InstancePut.java
@@ -114,13 +114,8 @@
   }
 
   @Override
-  public boolean instructionMayHaveSideEffects(AppView<?> appView, DexType context) {
-    return instructionMayHaveSideEffects(appView, context, Assumption.NONE);
-  }
-
-  @Override
   public boolean instructionMayHaveSideEffects(
-      AppView<?> appView, DexType context, Assumption assumption) {
+      AppView<?> appView, DexType context, SideEffectAssumption assumption) {
     if (appView.appInfo().hasLiveness()) {
       AppView<AppInfoWithLiveness> appViewWithLiveness = appView.withLiveness();
       AppInfoWithLiveness appInfoWithLiveness = appViewWithLiveness.appInfo();
@@ -200,6 +195,16 @@
   }
 
   @Override
+  public boolean isInstanceFieldInstruction() {
+    return true;
+  }
+
+  @Override
+  public InstanceFieldInstruction asInstanceFieldInstruction() {
+    return this;
+  }
+
+  @Override
   public boolean isInstancePut() {
     return true;
   }
diff --git a/src/main/java/com/android/tools/r8/ir/code/Instruction.java b/src/main/java/com/android/tools/r8/ir/code/Instruction.java
index b1459be..a56f0a2 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Instruction.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Instruction.java
@@ -571,6 +571,11 @@
   }
 
   public boolean instructionMayHaveSideEffects(AppView<?> appView, DexType context) {
+    return instructionMayHaveSideEffects(appView, context, SideEffectAssumption.NONE);
+  }
+
+  public boolean instructionMayHaveSideEffects(
+      AppView<?> appView, DexType context, SideEffectAssumption assumption) {
     return instructionInstanceCanThrow();
   }
 
@@ -888,6 +893,14 @@
     return null;
   }
 
+  public boolean isInstanceFieldInstruction() {
+    return false;
+  }
+
+  public InstanceFieldInstruction asInstanceFieldInstruction() {
+    return null;
+  }
+
   public boolean isInstanceGet() {
     return false;
   }
@@ -1032,6 +1045,14 @@
     return null;
   }
 
+  public boolean isInitClass() {
+    return false;
+  }
+
+  public InitClass asInitClass() {
+    return null;
+  }
+
   public boolean isAdd() {
     return false;
   }
@@ -1452,4 +1473,18 @@
   public boolean outTypeKnownToBeBoolean(Set<Phi> seen) {
     return false;
   }
+
+  public enum SideEffectAssumption {
+    NONE,
+    CLASS_ALREADY_INITIALIZED,
+    RECEIVER_NOT_NULL;
+
+    boolean canAssumeClassIsAlreadyInitialized() {
+      return this == CLASS_ALREADY_INITIALIZED;
+    }
+
+    boolean canAssumeReceiverIsNotNull() {
+      return this == RECEIVER_NOT_NULL;
+    }
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/code/InstructionVisitor.java b/src/main/java/com/android/tools/r8/ir/code/InstructionVisitor.java
index d7b3a83..4bb29bb 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InstructionVisitor.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InstructionVisitor.java
@@ -64,6 +64,8 @@
 
   T visit(Inc instruction);
 
+  T visit(InitClass instruction);
+
   T visit(InstanceGet instruction);
 
   T visit(InstanceOf instruction);
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeDirect.java b/src/main/java/com/android/tools/r8/ir/code/InvokeDirect.java
index ec0c68f..b4531c3 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokeDirect.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokeDirect.java
@@ -158,14 +158,15 @@
   }
 
   @Override
-  public boolean instructionMayHaveSideEffects(AppView<?> appView, DexType context) {
+  public boolean instructionMayHaveSideEffects(
+      AppView<?> appView, DexType context, SideEffectAssumption assumption) {
     if (appView.options().debug) {
       return true;
     }
 
     // Check if it could throw a NullPointerException as a result of the receiver being null.
     Value receiver = getReceiver();
-    if (receiver.getTypeLattice().isNullable()) {
+    if (!assumption.canAssumeReceiverIsNotNull() && receiver.getTypeLattice().isNullable()) {
       return true;
     }
 
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeMultiNewArray.java b/src/main/java/com/android/tools/r8/ir/code/InvokeMultiNewArray.java
index 831c65f..02e45ef 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokeMultiNewArray.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokeMultiNewArray.java
@@ -171,7 +171,8 @@
   }
 
   @Override
-  public boolean instructionMayHaveSideEffects(AppView<?> appView, DexType context) {
+  public boolean instructionMayHaveSideEffects(
+      AppView<?> appView, DexType context, SideEffectAssumption assumption) {
     // Check if the instruction has a side effect on the locals environment.
     if (hasOutValue() && outValue().hasLocalInfo()) {
       assert appView.options().debug;
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeNewArray.java b/src/main/java/com/android/tools/r8/ir/code/InvokeNewArray.java
index 8378a5d..ce75c7c 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokeNewArray.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokeNewArray.java
@@ -184,7 +184,8 @@
   }
 
   @Override
-  public boolean instructionMayHaveSideEffects(AppView<?> appView, DexType context) {
+  public boolean instructionMayHaveSideEffects(
+      AppView<?> appView, DexType context, SideEffectAssumption assumption) {
     // Check if the instruction has a side effect on the locals environment.
     if (hasOutValue() && outValue().hasLocalInfo()) {
       assert appView.options().debug;
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeStatic.java b/src/main/java/com/android/tools/r8/ir/code/InvokeStatic.java
index da1c8fd..2d29972 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokeStatic.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokeStatic.java
@@ -156,7 +156,8 @@
   }
 
   @Override
-  public boolean instructionMayHaveSideEffects(AppView<?> appView, DexType context) {
+  public boolean instructionMayHaveSideEffects(
+      AppView<?> appView, DexType context, SideEffectAssumption assumption) {
     if (!appView.enableWholeProgramOptimizations()) {
       return true;
     }
@@ -187,22 +188,24 @@
       }
 
       // Verify that the target method does not have side-effects.
-      boolean targetMayHaveSideEffects;
       if (appViewWithLiveness.appInfo().noSideEffects.containsKey(target.method)) {
-        targetMayHaveSideEffects = false;
-      } else {
-        targetMayHaveSideEffects =
-            target.getOptimizationInfo().mayHaveSideEffects()
-                // Verify that calling the target method won't lead to class initialization.
-                || target.method.holder.classInitializationMayHaveSideEffects(
-                    appView,
-                    // Types that are a super type of `context` are guaranteed to be initialized
-                    // already.
-                    type -> appView.isSubtype(context, type).isTrue(),
-                    Sets.newIdentityHashSet());
+        return false;
       }
 
-      return targetMayHaveSideEffects;
+      if (target.getOptimizationInfo().mayHaveSideEffects()) {
+        return true;
+      }
+
+      if (assumption.canAssumeClassIsAlreadyInitialized()) {
+        return false;
+      }
+
+      return target.method.holder.classInitializationMayHaveSideEffects(
+          appView,
+          // Types that are a super type of `context` are guaranteed to be initialized
+          // already.
+          type -> appView.isSubtype(context, type).isTrue(),
+          Sets.newIdentityHashSet());
     }
 
     return true;
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeVirtual.java b/src/main/java/com/android/tools/r8/ir/code/InvokeVirtual.java
index 1ec8873..1218f08 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokeVirtual.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokeVirtual.java
@@ -142,7 +142,8 @@
   }
 
   @Override
-  public boolean instructionMayHaveSideEffects(AppView<?> appView, DexType context) {
+  public boolean instructionMayHaveSideEffects(
+      AppView<?> appView, DexType context, SideEffectAssumption assumption) {
     if (!appView.enableWholeProgramOptimizations()) {
       return true;
     }
@@ -153,7 +154,7 @@
 
     // Check if it could throw a NullPointerException as a result of the receiver being null.
     Value receiver = getReceiver();
-    if (receiver.getTypeLattice().isNullable()) {
+    if (!assumption.canAssumeReceiverIsNotNull() && receiver.getTypeLattice().isNullable()) {
       return true;
     }
 
diff --git a/src/main/java/com/android/tools/r8/ir/code/NewArrayFilledData.java b/src/main/java/com/android/tools/r8/ir/code/NewArrayFilledData.java
index 226b771..afd87ff 100644
--- a/src/main/java/com/android/tools/r8/ir/code/NewArrayFilledData.java
+++ b/src/main/java/com/android/tools/r8/ir/code/NewArrayFilledData.java
@@ -129,7 +129,8 @@
   }
 
   @Override
-  public boolean instructionMayHaveSideEffects(AppView<?> appView, DexType context) {
+  public boolean instructionMayHaveSideEffects(
+      AppView<?> appView, DexType context, SideEffectAssumption assumption) {
     // Treat the instruction as possibly having side-effects if it may throw or the array is used.
     if (instructionInstanceCanThrow(appView, context).isThrowing()
         || src().numberOfAllUsers() > 1) {
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 23ac981..384a27a 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
@@ -141,7 +141,8 @@
   }
 
   @Override
-  public boolean instructionMayHaveSideEffects(AppView<?> appView, DexType context) {
+  public boolean instructionMayHaveSideEffects(
+      AppView<?> appView, DexType context, SideEffectAssumption assumption) {
     DexItemFactory dexItemFactory = appView.dexItemFactory();
     if (!appView.enableWholeProgramOptimizations()) {
       return !(dexItemFactory.libraryTypesAssumedToBePresent.contains(clazz)
diff --git a/src/main/java/com/android/tools/r8/ir/code/Opcodes.java b/src/main/java/com/android/tools/r8/ir/code/Opcodes.java
index 65a09d6..a3dc92b 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Opcodes.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Opcodes.java
@@ -33,43 +33,44 @@
   int GOTO = 24;
   int IF = 25;
   int INC = 26;
-  int INSTANCE_GET = 27;
-  int INSTANCE_OF = 28;
-  int INSTANCE_PUT = 29;
-  int INT_SWITCH = 30;
-  int INVOKE_CUSTOM = 31;
-  int INVOKE_DIRECT = 32;
-  int INVOKE_INTERFACE = 33;
-  int INVOKE_MULTI_NEW_ARRAY = 34;
-  int INVOKE_NEW_ARRAY = 35;
-  int INVOKE_POLYMORPHIC = 36;
-  int INVOKE_STATIC = 37;
-  int INVOKE_SUPER = 38;
-  int INVOKE_VIRTUAL = 39;
-  int LOAD = 40;
-  int MONITOR = 41;
-  int MOVE = 42;
-  int MOVE_EXCEPTION = 43;
-  int MUL = 44;
-  int NEG = 45;
-  int NEW_ARRAY_EMPTY = 46;
-  int NEW_ARRAY_FILLED_DATA = 47;
-  int NEW_INSTANCE = 48;
-  int NOT = 49;
-  int NUMBER_CONVERSION = 50;
-  int OR = 51;
-  int POP = 52;
-  int REM = 53;
-  int RETURN = 54;
-  int SHL = 55;
-  int SHR = 56;
-  int STATIC_GET = 57;
-  int STATIC_PUT = 58;
-  int STORE = 59;
-  int STRING_SWITCH = 60;
-  int SUB = 61;
-  int SWAP = 62;
-  int THROW = 63;
-  int USHR = 64;
-  int XOR = 65;
+  int INIT_CLASS = 27;
+  int INSTANCE_GET = 28;
+  int INSTANCE_OF = 29;
+  int INSTANCE_PUT = 30;
+  int INT_SWITCH = 31;
+  int INVOKE_CUSTOM = 32;
+  int INVOKE_DIRECT = 33;
+  int INVOKE_INTERFACE = 34;
+  int INVOKE_MULTI_NEW_ARRAY = 35;
+  int INVOKE_NEW_ARRAY = 36;
+  int INVOKE_POLYMORPHIC = 37;
+  int INVOKE_STATIC = 38;
+  int INVOKE_SUPER = 39;
+  int INVOKE_VIRTUAL = 40;
+  int LOAD = 41;
+  int MONITOR = 42;
+  int MOVE = 43;
+  int MOVE_EXCEPTION = 44;
+  int MUL = 45;
+  int NEG = 46;
+  int NEW_ARRAY_EMPTY = 47;
+  int NEW_ARRAY_FILLED_DATA = 48;
+  int NEW_INSTANCE = 49;
+  int NOT = 50;
+  int NUMBER_CONVERSION = 51;
+  int OR = 52;
+  int POP = 53;
+  int REM = 54;
+  int RETURN = 55;
+  int SHL = 56;
+  int SHR = 57;
+  int STATIC_GET = 58;
+  int STATIC_PUT = 59;
+  int STORE = 60;
+  int STRING_SWITCH = 61;
+  int SUB = 62;
+  int SWAP = 63;
+  int THROW = 64;
+  int USHR = 65;
+  int XOR = 66;
 }
diff --git a/src/main/java/com/android/tools/r8/ir/code/StaticFieldInstruction.java b/src/main/java/com/android/tools/r8/ir/code/StaticFieldInstruction.java
index 9c5a123..286b470 100644
--- a/src/main/java/com/android/tools/r8/ir/code/StaticFieldInstruction.java
+++ b/src/main/java/com/android/tools/r8/ir/code/StaticFieldInstruction.java
@@ -6,7 +6,7 @@
 
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.ir.code.FieldInstruction.Assumption;
+import com.android.tools.r8.ir.code.Instruction.SideEffectAssumption;
 
 public interface StaticFieldInstruction {
 
@@ -14,7 +14,10 @@
 
   Value outValue();
 
-  boolean instructionMayHaveSideEffects(AppView<?> appView, DexType context, Assumption assumption);
+  boolean instructionMayHaveSideEffects(AppView<?> appView, DexType context);
+
+  boolean instructionMayHaveSideEffects(
+      AppView<?> appView, DexType context, SideEffectAssumption assumption);
 
   FieldInstruction asFieldInstruction();
 
diff --git a/src/main/java/com/android/tools/r8/ir/code/StaticGet.java b/src/main/java/com/android/tools/r8/ir/code/StaticGet.java
index e7c2dee..a3c202b 100644
--- a/src/main/java/com/android/tools/r8/ir/code/StaticGet.java
+++ b/src/main/java/com/android/tools/r8/ir/code/StaticGet.java
@@ -137,12 +137,12 @@
 
   @Override
   public boolean instructionMayHaveSideEffects(AppView<?> appView, DexType context) {
-    return instructionMayHaveSideEffects(appView, context, Assumption.NONE);
+    return instructionMayHaveSideEffects(appView, context, SideEffectAssumption.NONE);
   }
 
   @Override
   public boolean instructionMayHaveSideEffects(
-      AppView<?> appView, DexType context, Assumption assumption) {
+      AppView<?> appView, DexType context, SideEffectAssumption assumption) {
     return instructionInstanceCanThrow(appView, context, assumption).isThrowing();
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/code/StaticPut.java b/src/main/java/com/android/tools/r8/ir/code/StaticPut.java
index 9d25691..e7d2bdc 100644
--- a/src/main/java/com/android/tools/r8/ir/code/StaticPut.java
+++ b/src/main/java/com/android/tools/r8/ir/code/StaticPut.java
@@ -94,13 +94,8 @@
   }
 
   @Override
-  public boolean instructionMayHaveSideEffects(AppView<?> appView, DexType context) {
-    return instructionMayHaveSideEffects(appView, context, Assumption.NONE);
-  }
-
-  @Override
   public boolean instructionMayHaveSideEffects(
-      AppView<?> appView, DexType context, Assumption assumption) {
+      AppView<?> appView, DexType context, SideEffectAssumption assumption) {
     if (appView.appInfo().hasLiveness()) {
       AppView<AppInfoWithLiveness> appViewWithLiveness = appView.withLiveness();
       AppInfoWithLiveness appInfoWithLiveness = appViewWithLiveness.appInfo();
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 25ddb6a..6ff7e2f 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
@@ -282,6 +282,21 @@
       }
     }
 
+    private void processInitClass(DexType type) {
+      DexProgramClass clazz = asProgramClassOrNull(appView.definitionFor(type));
+      if (clazz == null) {
+        assert false;
+        return;
+      }
+      addClassInitializerTarget(clazz);
+    }
+
+    @Override
+    public boolean registerInitClass(DexType clazz) {
+      processInitClass(clazz);
+      return false;
+    }
+
     @Override
     public boolean registerInvokeVirtual(DexMethod method) {
       processInvoke(Invoke.Type.VIRTUAL, method);
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 3c33035..e87f619 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
@@ -71,6 +71,7 @@
 import com.android.tools.r8.ir.code.IRMetadata;
 import com.android.tools.r8.ir.code.If;
 import com.android.tools.r8.ir.code.ImpreciseMemberTypeInstruction;
+import com.android.tools.r8.ir.code.InitClass;
 import com.android.tools.r8.ir.code.InstanceGet;
 import com.android.tools.r8.ir.code.InstanceOf;
 import com.android.tools.r8.ir.code.InstancePut;
@@ -687,7 +688,7 @@
       block.deduplicatePhis();
     }
 
-    ir.removeAllTrivialPhis(this);
+    ir.removeAllDeadAndTrivialPhis(this);
     ir.removeUnreachableBlocks();
 
     // Compute precise types for all values.
@@ -1795,6 +1796,13 @@
     closeCurrentBlock(ret);
   }
 
+  public void addInitClass(int dest, DexType clazz) {
+    Value out = writeRegister(dest, TypeLatticeElement.getInt(), ThrowingInfo.CAN_THROW);
+    InitClass instruction = new InitClass(out, clazz);
+    assert instruction.instructionTypeCanThrow();
+    addInstruction(instruction);
+  }
+
   public void addStaticGet(int dest, DexField field) {
     Value out =
         writeRegister(
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 f4e315b..d968d31 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
@@ -395,11 +395,10 @@
     throw new Unreachable();
   }
 
-  private boolean removeLambdaDeserializationMethods() {
+  private void removeLambdaDeserializationMethods() {
     if (lambdaRewriter != null) {
-      return lambdaRewriter.removeLambdaDeserializationMethods(appView.appInfo().classes());
+      lambdaRewriter.removeLambdaDeserializationMethods(appView.appInfo().classes());
     }
-    return false;
   }
 
   private void desugarNestBasedAccess(Builder<?> builder, ExecutorService executorService)
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java b/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java
index d7cfc6b..06c4bdf 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java
@@ -42,6 +42,7 @@
 import com.android.tools.r8.ir.code.ConstInstruction;
 import com.android.tools.r8.ir.code.ConstMethodHandle;
 import com.android.tools.r8.ir.code.IRCode;
+import com.android.tools.r8.ir.code.InitClass;
 import com.android.tools.r8.ir.code.InstanceGet;
 import com.android.tools.r8.ir.code.InstanceOf;
 import com.android.tools.r8.ir.code.InstancePut;
@@ -61,6 +62,7 @@
 import com.android.tools.r8.ir.code.StaticGet;
 import com.android.tools.r8.ir.code.StaticPut;
 import com.android.tools.r8.ir.code.Value;
+import com.android.tools.r8.ir.code.ValueType;
 import com.android.tools.r8.logging.Log;
 import com.google.common.collect.Sets;
 import java.util.ArrayList;
@@ -138,6 +140,11 @@
               affectedPhis.addAll(newOutValue.uniquePhiUsers());
             }
           }
+        } else if (current.isInitClass()) {
+          InitClass initClass = current.asInitClass();
+          new InstructionReplacer(code, current, iterator, affectedPhis)
+              .replaceInstructionIfTypeChanged(
+                  initClass.getClassValue(), (t, v) -> new InitClass(v, t));
         } else if (current.isInvokeMethod()) {
           InvokeMethod invoke = current.asInvokeMethod();
           DexMethod invokedMethod = invoke.getInvokedMethod();
@@ -195,22 +202,14 @@
                 ArgumentInfo argumentInfo = argumentInfoCollection.getArgumentInfo(i);
                 if (argumentInfo.isRewrittenTypeInfo()) {
                   RewrittenTypeInfo argInfo = argumentInfo.asRewrittenTypeInfo();
-                  Value value = invoke.inValues().get(i);
-                  // When converting types the default value may change (for example default value
-                  // of a reference type is null while default value of int is 0).
-                  if (argInfo.defaultValueHasChanged()
-                      && value.isConstNumber()
-                      && value.definition.asConstNumber().isZero()) {
-                    iterator.previous();
-                    // TODO(b/150188380): Add API to insert a const instruction with a type lattice.
-                    Value rewrittenNull =
-                        iterator.insertConstIntInstruction(code, appView.options(), 0);
-                    iterator.next();
-                    rewrittenNull.setTypeLattice(argInfo.defaultValueLatticeElement(appView));
-                    newInValues.add(rewrittenNull);
-                  } else {
-                    newInValues.add(invoke.inValues().get(i));
-                  }
+                  Value rewrittenValue =
+                      rewriteValueIfDefault(
+                          code,
+                          iterator,
+                          argInfo.getOldType(),
+                          argInfo.getNewType(),
+                          invoke.inValues().get(i));
+                  newInValues.add(rewrittenValue);
                 } else if (!argumentInfo.isRemovedArgumentInfo()) {
                   newInValues.add(invoke.inValues().get(i));
                 }
@@ -311,9 +310,12 @@
             iterator.replaceCurrentInstruction(
                 new InvokeStatic(replacementMethod, null, current.inValues()));
           } else if (actualField != field) {
+            Value rewrittenValue =
+                rewriteValueIfDefault(
+                    code, iterator, field.type, actualField.type, instancePut.value());
             InstancePut newInstancePut =
                 InstancePut.createPotentiallyInvalid(
-                    actualField, instancePut.object(), instancePut.value());
+                    actualField, instancePut.object(), rewrittenValue);
             iterator.replaceCurrentInstruction(newInstancePut);
           }
         } else if (current.isStaticGet()) {
@@ -348,7 +350,10 @@
             iterator.replaceCurrentInstruction(
                 new InvokeStatic(replacementMethod, current.outValue(), current.inValues()));
           } else if (actualField != field) {
-            StaticPut newStaticPut = new StaticPut(staticPut.value(), actualField);
+            Value rewrittenValue =
+                rewriteValueIfDefault(
+                    code, iterator, field.type, actualField.type, staticPut.value());
+            StaticPut newStaticPut = new StaticPut(rewrittenValue, actualField);
             iterator.replaceCurrentInstruction(newStaticPut);
           }
         } else if (current.isCheckCast()) {
@@ -414,6 +419,49 @@
     assert code.hasNoVerticallyMergedClasses(appView);
   }
 
+  // If the initialValue is a default value and its type is rewritten from a reference type to a
+  // primitive type, then the default value type lattice needs to be changed.
+  private Value rewriteValueIfDefault(
+      IRCode code,
+      InstructionListIterator iterator,
+      DexType oldType,
+      DexType newType,
+      Value initialValue) {
+    if (initialValue.isConstNumber()
+        && initialValue.definition.asConstNumber().isZero()
+        && defaultValueHasChanged(oldType, newType)) {
+      iterator.previous();
+      // TODO(b/150188380): Add API to insert a const instruction with a type lattice.
+      Value rewrittenDefaultValue = iterator.insertConstIntInstruction(code, appView.options(), 0);
+      iterator.next();
+      rewrittenDefaultValue.setTypeLattice(defaultValueLatticeElement(newType));
+      return rewrittenDefaultValue;
+    }
+    return initialValue;
+  }
+
+  private boolean defaultValueHasChanged(DexType oldType, DexType newType) {
+    if (newType.isPrimitiveType()) {
+      if (oldType.isPrimitiveType()) {
+        return ValueType.fromDexType(newType) != ValueType.fromDexType(oldType);
+      }
+      return true;
+    } else if (oldType.isPrimitiveType()) {
+      return true;
+    }
+    // All reference types uses null as default value.
+    assert newType.isReferenceType();
+    assert oldType.isReferenceType();
+    return false;
+  }
+
+  private TypeLatticeElement defaultValueLatticeElement(DexType type) {
+    if (type.isPrimitiveType()) {
+      return TypeLatticeElement.fromDexType(type, null, appView);
+    }
+    return TypeLatticeElement.getNull();
+  }
+
   public DexCallSite rewriteCallSite(DexCallSite callSite, DexEncodedMethod context) {
     DexItemFactory dexItemFactory = appView.dexItemFactory();
     DexProto newMethodProto =
@@ -643,6 +691,7 @@
           } else {
             assert current.hasInvariantOutType();
             assert current.isConstClass()
+                || current.isInitClass()
                 || current.isInstanceOf()
                 || (current.isInvokeVirtual()
                     && current.asInvokeVirtual().getInvokedMethod().holder.isArrayType());
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/StringSwitchConverter.java b/src/main/java/com/android/tools/r8/ir/conversion/StringSwitchConverter.java
index 41d1363..20ab05c 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/StringSwitchConverter.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/StringSwitchConverter.java
@@ -113,7 +113,7 @@
         }
       }
       if (changed) {
-        code.removeAllTrivialPhis();
+        code.removeAllDeadAndTrivialPhis();
         code.removeUnreachableBlocks();
       }
     }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/D8NestBasedAccessDesugaring.java b/src/main/java/com/android/tools/r8/ir/desugar/D8NestBasedAccessDesugaring.java
index e4af36c..7b53a35 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/D8NestBasedAccessDesugaring.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/D8NestBasedAccessDesugaring.java
@@ -84,7 +84,6 @@
                   new InvokeStatic(bridge, invokeMethod.outValue(), invokeMethod.arguments()));
             }
           }
-
         } else if (instruction.isFieldInstruction()) {
           DexEncodedField encodedField =
               appView.definitionFor(instruction.asFieldInstruction().getField());
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/LambdaClass.java b/src/main/java/com/android/tools/r8/ir/desugar/LambdaClass.java
index 0831e6d..ec1b05f 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/LambdaClass.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/LambdaClass.java
@@ -615,39 +615,41 @@
       DexMethod implMethod = descriptor.implHandle.asMethod();
       DexClass implMethodHolder = definitionFor(implMethod.holder);
 
-      List<DexEncodedMethod> directMethods = implMethodHolder.directMethods();
-      for (int i = 0; i < directMethods.size(); i++) {
-        DexEncodedMethod encodedMethod = directMethods.get(i);
-        if (implMethod.match(encodedMethod)) {
-          // We need to create a new static method with the same code to be able to safely
-          // relax its accessibility without making it virtual.
-          MethodAccessFlags newAccessFlags = encodedMethod.accessFlags.copy();
-          newAccessFlags.setStatic();
-          newAccessFlags.unsetPrivate();
-          // Always make the method public to provide access when r8 minification is allowed to move
-          // the lambda class accessing this method to another package (-allowaccessmodification).
-          newAccessFlags.setPublic();
-          DexEncodedMethod newMethod =
-              new DexEncodedMethod(
-                  callTarget,
-                  newAccessFlags,
-                  encodedMethod.annotations(),
-                  encodedMethod.parameterAnnotationsList,
-                  encodedMethod.getCode(),
-                  true);
-          newMethod.copyMetadata(encodedMethod);
-          rewriter.originalMethodSignatures.put(callTarget, encodedMethod.method);
+      DexEncodedMethod replacement =
+          implMethodHolder
+              .getMethodCollection()
+              .replaceDirectMethod(
+                  implMethod,
+                  encodedMethod -> {
+                    // We need to create a new static method with the same code to be able to safely
+                    // relax its accessibility without making it virtual.
+                    MethodAccessFlags newAccessFlags = encodedMethod.accessFlags.copy();
+                    newAccessFlags.setStatic();
+                    newAccessFlags.unsetPrivate();
+                    // Always make the method public to provide access when r8 minification is
+                    // allowed to move the lambda class accessing this method to another package
+                    // (-allowaccessmodification).
+                    newAccessFlags.setPublic();
+                    DexEncodedMethod newMethod =
+                        new DexEncodedMethod(
+                            callTarget,
+                            newAccessFlags,
+                            encodedMethod.annotations(),
+                            encodedMethod.parameterAnnotationsList,
+                            encodedMethod.getCode(),
+                            true);
+                    newMethod.copyMetadata(encodedMethod);
+                    rewriter.originalMethodSignatures.put(callTarget, encodedMethod.method);
 
-          DexEncodedMethod.setDebugInfoWithFakeThisParameter(
-              newMethod.getCode(), callTarget.getArity(), rewriter.getAppView());
-          implMethodHolder.setDirectMethod(i, newMethod);
+                    DexEncodedMethod.setDebugInfoWithFakeThisParameter(
+                        newMethod.getCode(), callTarget.getArity(), rewriter.getAppView());
+                    return newMethod;
+                  });
 
-          return newMethod;
-        }
-      }
-      assert false
+      assert replacement != null
           : "Unexpected failure to find direct lambda target for: " + implMethod.qualifiedName();
-      return null;
+
+      return replacement;
     }
   }
 
@@ -673,33 +675,29 @@
 
     private DexEncodedMethod modifyLambdaImplementationMethod(
         DexMethod implMethod, DexClass implMethodHolder) {
-      List<DexEncodedMethod> oldDirectMethods = implMethodHolder.directMethods();
-      for (int i = 0; i < oldDirectMethods.size(); i++) {
-        DexEncodedMethod encodedMethod = oldDirectMethods.get(i);
-        if (implMethod.match(encodedMethod)) {
-          // We need to create a new method with the same code to be able to safely relax its
-          // accessibility and make it virtual.
-          MethodAccessFlags newAccessFlags = encodedMethod.accessFlags.copy();
-          newAccessFlags.unsetPrivate();
-          newAccessFlags.setPublic();
-          DexEncodedMethod newMethod =
-              new DexEncodedMethod(
-                  callTarget,
-                  newAccessFlags,
-                  encodedMethod.annotations(),
-                  encodedMethod.parameterAnnotationsList,
-                  encodedMethod.getCode(),
-                  true);
-          newMethod.copyMetadata(encodedMethod);
-          rewriter.originalMethodSignatures.put(callTarget, encodedMethod.method);
-          // Move the method from the direct methods to the virtual methods set.
-          implMethodHolder.removeDirectMethod(i);
-          implMethodHolder.appendVirtualMethod(newMethod);
-
-          return newMethod;
-        }
-      }
-      return null;
+      return implMethodHolder
+          .getMethodCollection()
+          .replaceDirectMethodWithVirtualMethod(
+              implMethod,
+              encodedMethod -> {
+                assert encodedMethod.isDirectMethod();
+                // We need to create a new method with the same code to be able to safely relax its
+                // accessibility and make it virtual.
+                MethodAccessFlags newAccessFlags = encodedMethod.accessFlags.copy();
+                newAccessFlags.unsetPrivate();
+                newAccessFlags.setPublic();
+                DexEncodedMethod newMethod =
+                    new DexEncodedMethod(
+                        callTarget,
+                        newAccessFlags,
+                        encodedMethod.annotations(),
+                        encodedMethod.parameterAnnotationsList,
+                        encodedMethod.getCode(),
+                        true);
+                newMethod.copyMetadata(encodedMethod);
+                rewriter.originalMethodSignatures.put(callTarget, encodedMethod.method);
+                return newMethod;
+              });
     }
 
     private DexEncodedMethod createSyntheticAccessor(
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 007f170..770f170 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
@@ -172,29 +172,10 @@
   }
 
   /** Remove lambda deserialization methods. */
-  public boolean removeLambdaDeserializationMethods(Iterable<DexProgramClass> classes) {
-    boolean anyRemoved = false;
+  public void removeLambdaDeserializationMethods(Iterable<DexProgramClass> classes) {
     for (DexProgramClass clazz : classes) {
-      // Search for a lambda deserialization method and remove it if found.
-      List<DexEncodedMethod> directMethods = clazz.directMethods();
-      if (directMethods != null) {
-        int methodCount = directMethods.size();
-        for (int i = 0; i < methodCount; i++) {
-          DexEncodedMethod encoded = directMethods.get(i);
-          DexMethod method = encoded.method;
-          if (method.isLambdaDeserializeMethod(getFactory())) {
-            assert encoded.accessFlags.isStatic();
-            assert encoded.accessFlags.isSynthetic();
-            clazz.removeDirectMethod(i);
-            anyRemoved = true;
-
-            // We assume there is only one such method in the class.
-            break;
-          }
-        }
-      }
+      clazz.removeDirectMethod(getFactory().deserializeLambdaMethod);
     }
-    return anyRemoved;
   }
 
   /** Generates lambda classes and adds them to the builder. */
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 4c2a36d..b78757d 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
@@ -375,6 +375,12 @@
     }
 
     @Override
+    public boolean registerInitClass(DexType clazz) {
+      // Nothing to do since we always use a public field for initializing the class.
+      return true;
+    }
+
+    @Override
     public boolean registerInvokeVirtual(DexMethod method) {
       // Calls to class nest mate private methods are targeted by invokeVirtual in jdk11.
       // The spec recommends to do so, but do not enforce it, hence invokeDirect is also registered.
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/AssumeDynamicTypeRemover.java b/src/main/java/com/android/tools/r8/ir/optimize/AssumeDynamicTypeRemover.java
index 0ebe028..6bb9b03 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/AssumeDynamicTypeRemover.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/AssumeDynamicTypeRemover.java
@@ -55,9 +55,6 @@
   public void markUsersForRemoval(Value value) {
     for (Instruction user : value.aliasedUsers()) {
       if (user.isAssumeDynamicType()) {
-        assert value.numberOfAllUsers() == 1
-            : "Expected value flowing into Assume<DynamicTypeAssumption> instruction to have a "
-                + "unique user.";
         markForRemoval(user.asAssumeDynamicType());
       }
     }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java b/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
index 60813ab..f7755d4 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
@@ -26,7 +26,6 @@
 import com.android.tools.r8.ir.analysis.equivalence.BasicBlockBehavioralSubsumption;
 import com.android.tools.r8.ir.analysis.type.TypeAnalysis;
 import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
-import com.android.tools.r8.ir.analysis.type.TypeUtils;
 import com.android.tools.r8.ir.analysis.value.AbstractValue;
 import com.android.tools.r8.ir.code.AlwaysMaterializingNop;
 import com.android.tools.r8.ir.code.ArrayLength;
@@ -208,7 +207,7 @@
     // Therefore, Assume elimination may result in a trivial phi:
     //   z <- phi(x, x)
     if (needToCheckTrivialPhis) {
-      code.removeAllTrivialPhis(valuesThatRequireWidening);
+      code.removeAllDeadAndTrivialPhis(valuesThatRequireWidening);
     }
 
     if (!valuesThatRequireWidening.isEmpty()) {
@@ -273,9 +272,34 @@
           }
 
           Throw throwInstruction = valueIsNullTarget.exit().asThrow();
-          Value exceptionValue = throwInstruction.exception();
-          if (!exceptionValue.isConstZero()
-              && !TypeUtils.isNullPointerException(exceptionValue.getTypeLattice(), appView)) {
+          Value exceptionValue = throwInstruction.exception().getAliasedValue();
+          Value message;
+          if (exceptionValue.isConstZero()) {
+            message = null;
+          } else if (exceptionValue.isDefinedByInstructionSatisfying(Instruction::isNewInstance)) {
+            NewInstance newInstance = exceptionValue.definition.asNewInstance();
+            if (newInstance.clazz != dexItemFactory.npeType) {
+              continue;
+            }
+            if (newInstance.outValue().numberOfAllUsers() != 2) {
+              continue; // Could be mutated before it is thrown.
+            }
+            InvokeDirect constructorCall = newInstance.getUniqueConstructorInvoke(dexItemFactory);
+            if (constructorCall == null) {
+              continue;
+            }
+            DexMethod invokedMethod = constructorCall.getInvokedMethod();
+            if (invokedMethod == dexItemFactory.npeMethods.init) {
+              message = null;
+            } else if (invokedMethod == dexItemFactory.npeMethods.initWithMessage) {
+              if (!appView.options().canUseRequireNonNull()) {
+                continue;
+              }
+              message = constructorCall.getArgument(1);
+            } else {
+              continue;
+            }
+          } else {
             continue;
           }
 
@@ -290,12 +314,26 @@
             continue;
           }
 
+          if (message != null) {
+            Instruction definition = message.definition;
+            if (message.definition.getBlock() == valueIsNullTarget) {
+              it.previous();
+              Instruction entry;
+              do {
+                entry = valueIsNullTarget.getInstructions().removeFirst();
+                it.add(entry);
+              } while (entry != definition);
+              it.next();
+            }
+          }
+
           rewriteIfToRequireNonNull(
               block,
               it,
               ifInstruction,
               ifInstruction.targetFromCondition(1),
               valueIsNullTarget,
+              message,
               throwInstruction.getPosition());
           shouldRemoveUnreachableBlocks = true;
         }
@@ -1221,10 +1259,10 @@
     assumeDynamicTypeRemover.removeMarkedInstructions(blocksToBeRemoved).finish();
     if (!blocksToBeRemoved.isEmpty()) {
       code.removeBlocks(blocksToBeRemoved);
-      code.removeAllTrivialPhis(affectedValues);
+      code.removeAllDeadAndTrivialPhis(affectedValues);
       assert code.getUnreachableBlocks().isEmpty();
     } else if (mayHaveRemovedTrivialPhi || assumeDynamicTypeRemover.mayHaveIntroducedTrivialPhi()) {
-      code.removeAllTrivialPhis(affectedValues);
+      code.removeAllDeadAndTrivialPhis(affectedValues);
     }
     if (!affectedValues.isEmpty()) {
       new TypeAnalysis(appView).narrowing(affectedValues);
@@ -1299,7 +1337,7 @@
     // Removing check-cast may result in a trivial phi:
     // v3 <- phi(v1, v1)
     if (needToRemoveTrivialPhis) {
-      code.removeAllTrivialPhis(affectedValues);
+      code.removeAllDeadAndTrivialPhis(affectedValues);
       if (!affectedValues.isEmpty()) {
         typeAnalysis.narrowing(affectedValues);
       }
@@ -2593,7 +2631,7 @@
     }
 
     if (changed) {
-      code.removeAllTrivialPhis();
+      code.removeAllDeadAndTrivialPhis();
     }
     assert code.isConsistentSSA();
   }
@@ -2922,15 +2960,24 @@
       If theIf,
       BasicBlock target,
       BasicBlock deadTarget,
+      Value message,
       Position position) {
     deadTarget.unlinkSinglePredecessorSiblingsAllowed();
     assert theIf == block.exit();
     iterator.previous();
     Instruction instruction;
     if (appView.options().canUseRequireNonNull()) {
-      DexMethod requireNonNullMethod = appView.dexItemFactory().objectsMethods.requireNonNull;
-      instruction = new InvokeStatic(requireNonNullMethod, null, ImmutableList.of(theIf.lhs()));
+      if (message != null) {
+        DexMethod requireNonNullMethod =
+            appView.dexItemFactory().objectsMethods.requireNonNullWithMessage;
+        instruction =
+            new InvokeStatic(requireNonNullMethod, null, ImmutableList.of(theIf.lhs(), message));
+      } else {
+        DexMethod requireNonNullMethod = appView.dexItemFactory().objectsMethods.requireNonNull;
+        instruction = new InvokeStatic(requireNonNullMethod, null, ImmutableList.of(theIf.lhs()));
+      }
     } else {
+      assert message == null;
       DexMethod getClassMethod = appView.dexItemFactory().objectMembers.getClass;
       instruction = new InvokeVirtual(getClassMethod, null, ImmutableList.of(theIf.lhs()));
     }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/ConstantCanonicalizer.java b/src/main/java/com/android/tools/r8/ir/optimize/ConstantCanonicalizer.java
index 5160e6c..2dff525 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/ConstantCanonicalizer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/ConstantCanonicalizer.java
@@ -257,7 +257,7 @@
       shouldSimplifyIfs |= newConst.outValue().hasUserThatMatches(Instruction::isIf);
     } while (iterator.hasNext());
 
-    shouldSimplifyIfs |= code.removeAllTrivialPhis();
+    shouldSimplifyIfs |= code.removeAllDeadAndTrivialPhis();
 
     if (shouldSimplifyIfs) {
       codeRewriter.simplifyIf(code);
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java b/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java
index f1c5dc9..463bfce 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java
@@ -128,62 +128,6 @@
     return false;
   }
 
-  private boolean canInlineStaticInvoke(
-      InvokeStatic invoke,
-      DexEncodedMethod method,
-      DexEncodedMethod target,
-      ClassInitializationAnalysis classInitializationAnalysis,
-      WhyAreYouNotInliningReporter whyAreYouNotInliningReporter) {
-    // Only proceed with inlining a static invoke if:
-    // - the holder for the target is a subtype of the holder for the method,
-    // - the target method always triggers class initialization of its holder before any other side
-    //   effect (hence preserving class initialization semantics),
-    // - the current method has already triggered the holder for the target method to be
-    //   initialized, or
-    // - there is no non-trivial class initializer.
-    DexType targetHolder = target.method.holder;
-    if (appView.appInfo().isSubtype(method.method.holder, targetHolder)) {
-      return true;
-    }
-    DexClass clazz = appView.definitionFor(targetHolder);
-    assert clazz != null;
-    if (target.getOptimizationInfo().triggersClassInitBeforeAnySideEffect()) {
-      return true;
-    }
-    if (!method.isStatic()) {
-      boolean targetIsGuaranteedToBeInitialized =
-          appView.withInitializedClassesInInstanceMethods(
-              analysis ->
-                  analysis.isClassDefinitelyLoadedInInstanceMethodsOn(
-                      target.method.holder, method.method.holder),
-              false);
-      if (targetIsGuaranteedToBeInitialized) {
-        return true;
-      }
-    }
-    if (classInitializationAnalysis.isClassDefinitelyLoadedBeforeInstruction(
-        target.method.holder, invoke)) {
-      return true;
-    }
-    // Check for class initializer side effects when loading this class, as inlining might remove
-    // the load operation.
-    //
-    // See https://docs.oracle.com/javase/specs/jvms/se9/html/jvms-5.html#jvms-5.5.
-    //
-    // For simplicity, we are conservative and consider all interfaces, not only the ones with
-    // default methods.
-    if (!clazz.classInitializationMayHaveSideEffects(appView)) {
-      return true;
-    }
-
-    if (appView.rootSet().bypassClinitForInlining.contains(target.method)) {
-      return true;
-    }
-
-    whyAreYouNotInliningReporter.reportMustTriggerClassInitialization();
-    return false;
-  }
-
   @Override
   public boolean passesInliningConstraints(
       InvokeMethod invoke,
@@ -391,12 +335,71 @@
       Reason reason,
       ClassInitializationAnalysis classInitializationAnalysis,
       WhyAreYouNotInliningReporter whyAreYouNotInliningReporter) {
-    // Abort inlining attempt if we can not guarantee class for static target has been initialized.
-    if (!canInlineStaticInvoke(
-        invoke, method, singleTarget, classInitializationAnalysis, whyAreYouNotInliningReporter)) {
-      return null;
+    InlineAction action = new InlineAction(singleTarget, invoke, reason);
+    if (isTargetClassInitialized(invoke, method, singleTarget, classInitializationAnalysis)) {
+      return action;
     }
-    return new InlineAction(singleTarget, invoke, reason);
+    if (appView.canUseInitClass()
+        && appView.options().enableInliningOfInvokesWithClassInitializationSideEffects) {
+      action.setShouldSynthesizeInitClass();
+      return action;
+    }
+    whyAreYouNotInliningReporter.reportMustTriggerClassInitialization();
+    return null;
+  }
+
+  private boolean isTargetClassInitialized(
+      InvokeStatic invoke,
+      DexEncodedMethod method,
+      DexEncodedMethod target,
+      ClassInitializationAnalysis classInitializationAnalysis) {
+    // Only proceed with inlining a static invoke if:
+    // - the holder for the target is a subtype of the holder for the method,
+    // - the target method always triggers class initialization of its holder before any other side
+    //   effect (hence preserving class initialization semantics),
+    // - the current method has already triggered the holder for the target method to be
+    //   initialized, or
+    // - there is no non-trivial class initializer.
+    DexType targetHolder = target.method.holder;
+    if (appView.appInfo().isSubtype(method.method.holder, targetHolder)) {
+      return true;
+    }
+    DexClass clazz = appView.definitionFor(targetHolder);
+    assert clazz != null;
+    if (target.getOptimizationInfo().triggersClassInitBeforeAnySideEffect()) {
+      return true;
+    }
+    if (!method.isStatic()) {
+      boolean targetIsGuaranteedToBeInitialized =
+          appView.withInitializedClassesInInstanceMethods(
+              analysis ->
+                  analysis.isClassDefinitelyLoadedInInstanceMethodsOn(
+                      target.method.holder, method.method.holder),
+              false);
+      if (targetIsGuaranteedToBeInitialized) {
+        return true;
+      }
+    }
+    if (classInitializationAnalysis.isClassDefinitelyLoadedBeforeInstruction(
+        target.method.holder, invoke)) {
+      return true;
+    }
+    // Check for class initializer side effects when loading this class, as inlining might remove
+    // the load operation.
+    //
+    // See https://docs.oracle.com/javase/specs/jvms/se9/html/jvms-5.html#jvms-5.5.
+    //
+    // For simplicity, we are conservative and consider all interfaces, not only the ones with
+    // default methods.
+    if (!clazz.classInitializationMayHaveSideEffects(appView)) {
+      return true;
+    }
+
+    if (appView.rootSet().bypassClinitForInlining.contains(target.method)) {
+      return true;
+    }
+
+    return false;
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/IdempotentFunctionCallCanonicalizer.java b/src/main/java/com/android/tools/r8/ir/optimize/IdempotentFunctionCallCanonicalizer.java
index 9cbc694..ebf6e57 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/IdempotentFunctionCallCanonicalizer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/IdempotentFunctionCallCanonicalizer.java
@@ -248,7 +248,7 @@
       }
     }
 
-    code.removeAllTrivialPhis();
+    code.removeAllDeadAndTrivialPhis();
     assert code.isConsistentSSA();
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java b/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java
index c9bf058..d45bbb0 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java
@@ -26,6 +26,7 @@
 import com.android.tools.r8.ir.code.CatchHandlers.CatchHandler;
 import com.android.tools.r8.ir.code.ConstClass;
 import com.android.tools.r8.ir.code.IRCode;
+import com.android.tools.r8.ir.code.InitClass;
 import com.android.tools.r8.ir.code.Instruction;
 import com.android.tools.r8.ir.code.InstructionIterator;
 import com.android.tools.r8.ir.code.InstructionListIterator;
@@ -573,6 +574,7 @@
     public final Invoke invoke;
     final Reason reason;
 
+    private boolean shouldSynthesizeInitClass;
     private boolean shouldSynthesizeNullCheckForReceiver;
 
     InlineAction(DexEncodedMethod target, Invoke invoke, Reason reason) {
@@ -581,7 +583,13 @@
       this.reason = reason;
     }
 
+    void setShouldSynthesizeInitClass() {
+      assert !shouldSynthesizeNullCheckForReceiver;
+      shouldSynthesizeInitClass = true;
+    }
+
     void setShouldSynthesizeNullCheckForReceiver() {
+      assert !shouldSynthesizeInitClass;
       shouldSynthesizeNullCheckForReceiver = true;
     }
 
@@ -599,6 +607,12 @@
       // Build the IR for a yet not processed method, and perform minimal IR processing.
       IRCode code = inliningIRProvider.getInliningIR(invoke, target);
 
+      // Insert a init class instruction if this is needed to preserve class initialization
+      // semantics.
+      if (shouldSynthesizeInitClass) {
+        synthesizeInitClass(code);
+      }
+
       // Insert a null check if this is needed to preserve the implicit null check for the receiver.
       // This is only needed if we do not also insert a monitor-enter instruction, since that will
       // throw a NPE if the receiver is null.
@@ -737,6 +751,21 @@
       return new InlineeWithReason(code, reason);
     }
 
+    private void synthesizeInitClass(IRCode code) {
+      List<Value> arguments = code.collectArguments();
+      BasicBlock entryBlock = code.entryBlock();
+
+      // Insert a new block between the last argument instruction and the first actual instruction
+      // of the method.
+      BasicBlock initClassBlock =
+          entryBlock.listIterator(code, arguments.size()).split(code, 0, null);
+      assert !initClassBlock.hasCatchHandlers();
+
+      InstructionListIterator iterator = initClassBlock.listIterator(code);
+      iterator.setInsertionPosition(entryBlock.exit().getPosition());
+      iterator.add(new InitClass(code.createValue(TypeLatticeElement.getInt()), target.holder()));
+    }
+
     private void synthesizeNullCheckForReceiver(AppView<?> appView, IRCode code) {
       List<Value> arguments = code.collectArguments();
       if (!arguments.isEmpty()) {
@@ -1058,7 +1087,7 @@
     assumeDynamicTypeRemover.finish();
     classInitializationAnalysis.finish();
     code.removeBlocks(blocksToRemove);
-    code.removeAllTrivialPhis();
+    code.removeAllDeadAndTrivialPhis();
     assert code.isConsistentSSA();
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/InliningConstraints.java b/src/main/java/com/android/tools/r8/ir/optimize/InliningConstraints.java
index e10b121..a1307a0 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/InliningConstraints.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/InliningConstraints.java
@@ -126,6 +126,10 @@
     return ConstraintWithTarget.ALWAYS;
   }
 
+  public ConstraintWithTarget forInitClass(DexType clazz, DexType context) {
+    return ConstraintWithTarget.classIsVisible(context, clazz, appView);
+  }
+
   public ConstraintWithTarget forInstanceGet(DexField field, DexType invocationContext) {
     DexField lookup = graphLense.lookupField(field);
     return forFieldInstruction(
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/MemberValuePropagation.java b/src/main/java/com/android/tools/r8/ir/optimize/MemberValuePropagation.java
index 783ed8a..7e25c8a 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/MemberValuePropagation.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/MemberValuePropagation.java
@@ -23,7 +23,7 @@
 import com.android.tools.r8.ir.code.FieldInstruction;
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.IRMetadata;
-import com.android.tools.r8.ir.code.InstanceFieldInstruction;
+import com.android.tools.r8.ir.code.InitClass;
 import com.android.tools.r8.ir.code.InstancePut;
 import com.android.tools.r8.ir.code.Instruction;
 import com.android.tools.r8.ir.code.InstructionListIterator;
@@ -31,7 +31,6 @@
 import com.android.tools.r8.ir.code.InvokeStatic;
 import com.android.tools.r8.ir.code.InvokeVirtual;
 import com.android.tools.r8.ir.code.Position;
-import com.android.tools.r8.ir.code.StaticFieldInstruction;
 import com.android.tools.r8.ir.code.StaticGet;
 import com.android.tools.r8.ir.code.StaticPut;
 import com.android.tools.r8.ir.code.Value;
@@ -211,7 +210,7 @@
       }
       if (current.isStaticGet()) {
         StaticGet staticGet = current.asStaticGet();
-        replaceStaticFieldInstructionByClinitAccessIfPossible(
+        replaceInstructionByInitClassIfPossible(
             staticGet, staticGet.getField().holder, code, iterator, code.method.holder());
       }
       replacement.setPosition(position);
@@ -226,7 +225,7 @@
 
   private void rewriteInvokeMethodWithConstantValues(
       IRCode code,
-      DexType callingContext,
+      DexType context,
       Set<Value> affectedValues,
       ListIterator<BasicBlock> blocks,
       InstructionListIterator iterator,
@@ -236,7 +235,7 @@
     if (!invokedHolder.isClassType()) {
       return;
     }
-    DexEncodedMethod target = current.lookupSingleTarget(appView, callingContext);
+    DexEncodedMethod target = current.lookupSingleTarget(appView, context);
     if (target != null && target.isInstanceInitializer()) {
       // Member value propagation does not apply to constructors. Removing a call to a constructor
       // that is marked as having no side effects could lead to verification errors, due to
@@ -291,15 +290,27 @@
     AbstractValue abstractReturnValue = target.getOptimizationInfo().getAbstractReturnValue();
     if (abstractReturnValue.isSingleValue()) {
       SingleValue singleReturnValue = abstractReturnValue.asSingleValue();
-      if (singleReturnValue.isMaterializableInContext(appView, callingContext)) {
+      if (singleReturnValue.isMaterializableInContext(appView, context)) {
+        BasicBlock block = current.getBlock();
+        Position position = current.getPosition();
+
         Instruction replacement =
             singleReturnValue.createMaterializingInstruction(appView, code, current);
         affectedValues.addAll(current.outValue().affectedValues());
+        current.moveDebugValues(replacement);
         current.outValue().replaceUsers(replacement.outValue());
         current.setOutValue(null);
-        replacement.setPosition(current.getPosition());
-        current.moveDebugValues(replacement);
-        if (current.getBlock().hasCatchHandlers()) {
+
+        if (current.isInvokeMethodWithReceiver()) {
+          replaceInstructionByNullCheckIfPossible(current, iterator, context);
+        } else if (current.isInvokeStatic()) {
+          replaceInstructionByInitClassIfPossible(
+              current, target.holder(), code, iterator, context);
+        }
+
+        // Insert the definition of the replacement.
+        replacement.setPosition(position);
+        if (block.hasCatchHandlers()) {
           iterator.split(code, blocks).listIterator(code).add(replacement);
         } else {
           iterator.add(replacement);
@@ -354,47 +365,45 @@
     Instruction replacement =
         target.valueAsConstInstruction(code, current.outValue().getLocalInfo(), appView);
     if (replacement != null) {
+      BasicBlock block = current.getBlock();
+      DexType context = code.method.holder();
+      Position position = current.getPosition();
+
+      // All usages are replaced by the replacement value.
       affectedValues.addAll(current.outValue().affectedValues());
-      DexType context = code.method.method.holder;
-      if (current.instructionMayHaveSideEffects(appView, context)) {
-        BasicBlock block = current.getBlock();
-        Position position = current.getPosition();
+      current.outValue().replaceUsers(replacement.outValue());
 
-        // All usages are replaced by the replacement value.
-        current.outValue().replaceUsers(replacement.outValue());
-
-        // To preserve side effects, original field-get is replaced by an explicit null-check, if
-        // the field-get instruction may only fail with an NPE, or the field-get remains as-is.
-        if (current.isInstanceGet()) {
-          replaceInstanceFieldInstructionByNullCheckIfPossible(
-              current.asInstanceGet(), iterator, context);
-        } else {
-          replaceStaticFieldInstructionByClinitAccessIfPossible(
-              current.asStaticGet(), target.holder(), code, iterator, context);
-        }
-
-        // Insert the definition of the replacement.
-        replacement.setPosition(position);
-        if (block.hasCatchHandlers()) {
-          iterator.split(code, blocks).listIterator(code).add(replacement);
-        } else {
-          iterator.add(replacement);
-        }
+      // To preserve side effects, original field-get is replaced by an explicit null-check, if
+      // the field-get instruction may only fail with an NPE, or the field-get remains as-is.
+      if (current.isInstanceGet()) {
+        replaceInstructionByNullCheckIfPossible(current, iterator, context);
       } else {
-        iterator.replaceCurrentInstruction(replacement);
+        replaceInstructionByInitClassIfPossible(current, target.holder(), code, iterator, context);
+      }
+
+      // Insert the definition of the replacement.
+      replacement.setPosition(position);
+      if (block.hasCatchHandlers()) {
+        iterator.split(code, blocks).listIterator(code).add(replacement);
+      } else {
+        iterator.add(replacement);
       }
       feedback.markFieldAsPropagated(target);
     }
   }
 
-  private void replaceInstanceFieldInstructionByNullCheckIfPossible(
-      InstanceFieldInstruction instruction, InstructionListIterator iterator, DexType context) {
+  private void replaceInstructionByNullCheckIfPossible(
+      Instruction instruction, InstructionListIterator iterator, DexType context) {
+    assert instruction.isInstanceFieldInstruction() || instruction.isInvokeMethodWithReceiver();
     assert !instruction.hasOutValue() || !instruction.outValue().hasAnyUsers();
     if (instruction.instructionMayHaveSideEffects(
-        appView, context, FieldInstruction.Assumption.RECEIVER_NOT_NULL)) {
+        appView, context, Instruction.SideEffectAssumption.RECEIVER_NOT_NULL)) {
       return;
     }
-    Value receiver = instruction.object();
+    Value receiver =
+        instruction.isInstanceFieldInstruction()
+            ? instruction.asInstanceFieldInstruction().object()
+            : instruction.asInvokeMethodWithReceiver().getReceiver();
     if (receiver.isNeverNull()) {
       iterator.removeOrReplaceByDebugLocalRead();
       return;
@@ -410,6 +419,38 @@
     iterator.replaceCurrentInstruction(replacement);
   }
 
+  private void replaceInstructionByInitClassIfPossible(
+      Instruction instruction,
+      DexType holder,
+      IRCode code,
+      InstructionListIterator iterator,
+      DexType context) {
+    assert instruction.isStaticFieldInstruction() || instruction.isInvokeStatic();
+    if (instruction.instructionMayHaveSideEffects(
+        appView, context, Instruction.SideEffectAssumption.CLASS_ALREADY_INITIALIZED)) {
+      return;
+    }
+    boolean classInitializationMayHaveSideEffects =
+        holder.classInitializationMayHaveSideEffects(
+            appView,
+            // Types that are a super type of `context` are guaranteed to be initialized
+            // already.
+            type -> appView.isSubtype(context, type).isTrue(),
+            Sets.newIdentityHashSet());
+    if (!classInitializationMayHaveSideEffects) {
+      iterator.removeOrReplaceByDebugLocalRead();
+      return;
+    }
+    if (!appView.canUseInitClass()) {
+      return;
+    }
+    DexProgramClass clazz = asProgramClassOrNull(appView.definitionFor(holder));
+    if (clazz != null) {
+      Value dest = code.createValue(TypeLatticeElement.getInt());
+      iterator.replaceCurrentInstruction(new InitClass(dest, clazz.type));
+    }
+  }
+
   private void replaceInstancePutByNullCheckIfNeverRead(
       IRCode code, InstructionListIterator iterator, InstancePut current) {
     DexEncodedField target = appView.appInfo().resolveField(current.getField());
@@ -421,10 +462,10 @@
       return;
     }
 
-    replaceInstanceFieldInstructionByNullCheckIfPossible(current, iterator, code.method.holder());
+    replaceInstructionByNullCheckIfPossible(current, iterator, code.method.holder());
   }
 
-  private void replaceStaticPutByClinitAccessIfNeverRead(
+  private void replaceStaticPutByInitClassIfNeverRead(
       IRCode code, InstructionListIterator iterator, StaticPut current) {
     DexEncodedField field = appView.appInfo().resolveField(current.getField());
     if (field == null || appView.appInfo().isFieldRead(field)) {
@@ -435,32 +476,10 @@
       return;
     }
 
-    replaceStaticFieldInstructionByClinitAccessIfPossible(
+    replaceInstructionByInitClassIfPossible(
         current, field.holder(), code, iterator, code.method.holder());
   }
 
-  private void replaceStaticFieldInstructionByClinitAccessIfPossible(
-      StaticFieldInstruction instruction,
-      DexType holder,
-      IRCode code,
-      InstructionListIterator iterator,
-      DexType context) {
-    if (instruction.instructionMayHaveSideEffects(
-        appView, context, FieldInstruction.Assumption.CLASS_ALREADY_INITIALIZED)) {
-      return;
-    }
-    DexProgramClass clazz = asProgramClassOrNull(appView.definitionFor(holder));
-    if (clazz != null) {
-      DexEncodedField clinitField =
-          clazz.lookupStaticField(appView.dexItemFactory().objectMembers.clinitField);
-      if (clinitField != null) {
-        Value dest = code.createValue(TypeLatticeElement.getInt());
-        StaticGet replacement = new StaticGet(dest, clinitField.field);
-        iterator.replaceCurrentInstruction(replacement);
-      }
-    }
-  }
-
   /**
    * Replace invoke targets and field accesses with constant values where possible.
    *
@@ -488,7 +507,7 @@
         } else if (current.isInstancePut()) {
           replaceInstancePutByNullCheckIfNeverRead(code, iterator, current.asInstancePut());
         } else if (current.isStaticPut()) {
-          replaceStaticPutByClinitAccessIfNeverRead(code, iterator, current.asStaticPut());
+          replaceStaticPutByInitClassIfNeverRead(code, iterator, current.asStaticPut());
         }
       }
     }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/RedundantFieldLoadElimination.java b/src/main/java/com/android/tools/r8/ir/optimize/RedundantFieldLoadElimination.java
index 698818f..de9d4cc 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/RedundantFieldLoadElimination.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/RedundantFieldLoadElimination.java
@@ -16,6 +16,7 @@
 import com.android.tools.r8.ir.code.DominatorTree;
 import com.android.tools.r8.ir.code.FieldInstruction;
 import com.android.tools.r8.ir.code.IRCode;
+import com.android.tools.r8.ir.code.InitClass;
 import com.android.tools.r8.ir.code.InstanceGet;
 import com.android.tools.r8.ir.code.InstancePut;
 import com.android.tools.r8.ir.code.Instruction;
@@ -28,6 +29,7 @@
 import com.android.tools.r8.ir.code.Value;
 import com.android.tools.r8.ir.optimize.info.field.InstanceFieldInitializationInfoCollection;
 import com.android.tools.r8.ir.optimize.info.initializer.InstanceInitializerInfo;
+import com.android.tools.r8.utils.SetUtils;
 import com.google.common.collect.Sets;
 import java.util.ArrayList;
 import java.util.HashMap;
@@ -54,6 +56,8 @@
   private final Set<Value> affectedValues = Sets.newIdentityHashSet();
 
   // Maps keeping track of fields that have an already loaded value at basic block entry.
+  private final Map<BasicBlock, Set<DexType>> activeInitializedClassesAtEntry =
+      new IdentityHashMap<>();
   private final Map<BasicBlock, Map<FieldAndObject, FieldValue>> activeInstanceFieldsAtEntry =
       new IdentityHashMap<>();
   private final Map<BasicBlock, Map<DexField, FieldValue>> activeStaticFieldsAtEntry =
@@ -61,6 +65,7 @@
 
   // Maps keeping track of fields with already loaded values for the current block during
   // elimination.
+  private Set<DexType> activeInitializedClasses;
   private Map<FieldAndObject, FieldValue> activeInstanceFieldValues;
   private Map<DexField, FieldValue> activeStaticFieldValues;
 
@@ -156,6 +161,10 @@
   public void run() {
     DexType context = method.method.holder;
     for (BasicBlock block : dominatorTree.getSortedBlocks()) {
+      activeInitializedClasses =
+          activeInitializedClassesAtEntry.containsKey(block)
+              ? activeInitializedClassesAtEntry.get(block)
+              : Sets.newIdentityHashSet();
       activeInstanceFieldValues =
           activeInstanceFieldsAtEntry.containsKey(block)
               ? activeInstanceFieldsAtEntry.get(block)
@@ -217,6 +226,12 @@
             killActiveFields(staticPut);
             activeStaticFieldValues.put(field, new ExistingValue(staticPut.value()));
           }
+        } else if (instruction.isInitClass()) {
+          InitClass initClass = instruction.asInitClass();
+          assert !initClass.outValue().hasAnyUsers();
+          if (activeInitializedClasses.contains(initClass.getClassValue())) {
+            it.removeOrReplaceByDebugLocalRead();
+          }
         } else if (instruction.isMonitor()) {
           if (instruction.asMonitor().isEnter()) {
             killAllActiveFields();
@@ -271,7 +286,7 @@
               : "Unexpected instruction of type " + instruction.getClass().getTypeName();
         }
       }
-      propagateActiveFieldsFrom(block);
+      propagateActiveStateFrom(block);
     }
     if (!affectedValues.isEmpty()) {
       new TypeAnalysis(appView).narrowing(affectedValues);
@@ -324,17 +339,24 @@
         });
   }
 
-  private void propagateActiveFieldsFrom(BasicBlock block) {
+  private void propagateActiveStateFrom(BasicBlock block) {
     for (BasicBlock successor : block.getSuccessors()) {
       // Allow propagation across exceptional edges, just be careful not to propagate if the
       // throwing instruction is a field instruction.
       if (successor.getPredecessors().size() == 1) {
         if (block.hasCatchSuccessor(successor)) {
           Instruction exceptionalExit = block.exceptionalExit();
-          if (exceptionalExit != null && exceptionalExit.isFieldInstruction()) {
-            killActiveFieldsForExceptionalExit(exceptionalExit.asFieldInstruction());
+          if (exceptionalExit != null) {
+            if (exceptionalExit.isFieldInstruction()) {
+              killActiveFieldsForExceptionalExit(exceptionalExit.asFieldInstruction());
+            } else if (exceptionalExit.isInitClass()) {
+              killActiveInitializedClassesForExceptionalExit(exceptionalExit.asInitClass());
+            }
           }
         }
+        assert !activeInitializedClassesAtEntry.containsKey(successor);
+        activeInitializedClassesAtEntry.put(
+            successor, SetUtils.newIdentityHashSet(activeInitializedClasses));
         assert !activeInstanceFieldsAtEntry.containsKey(successor);
         activeInstanceFieldsAtEntry.put(successor, new HashMap<>(activeInstanceFieldValues));
         assert !activeStaticFieldsAtEntry.containsKey(successor);
@@ -393,4 +415,8 @@
       activeStaticFieldValues.remove(field);
     }
   }
+
+  private void killActiveInitializedClassesForExceptionalExit(InitClass instruction) {
+    activeInitializedClasses.remove(instruction.getClassValue());
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/UninstantiatedTypeOptimization.java b/src/main/java/com/android/tools/r8/ir/optimize/UninstantiatedTypeOptimization.java
index b8ca4bc..8e0858a 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/UninstantiatedTypeOptimization.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/UninstantiatedTypeOptimization.java
@@ -47,7 +47,6 @@
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.IdentityHashMap;
-import java.util.List;
 import java.util.ListIterator;
 import java.util.Map;
 import java.util.Set;
@@ -193,33 +192,34 @@
     }
 
     // Change the return type of direct methods that return an uninstantiated type to void.
-    List<DexEncodedMethod> directMethods = clazz.directMethods();
-    for (int i = 0; i < directMethods.size(); ++i) {
-      DexEncodedMethod encodedMethod = directMethods.get(i);
-      DexMethod method = encodedMethod.method;
-      RewrittenPrototypeDescription prototypeChanges =
-          prototypeChangesPerMethod.getOrDefault(
-              encodedMethod, RewrittenPrototypeDescription.none());
-      ArgumentInfoCollection removedArgumentsInfo = prototypeChanges.getArgumentInfoCollection();
-      DexMethod newMethod = getNewMethodSignature(encodedMethod, prototypeChanges);
-      if (newMethod != method) {
-        Wrapper<DexMethod> wrapper = equivalence.wrap(newMethod);
+    clazz
+        .getMethodCollection()
+        .replaceDirectMethods(
+            encodedMethod -> {
+              DexMethod method = encodedMethod.method;
+              RewrittenPrototypeDescription prototypeChanges =
+                  prototypeChangesPerMethod.getOrDefault(
+                      encodedMethod, RewrittenPrototypeDescription.none());
+              ArgumentInfoCollection removedArgumentsInfo =
+                  prototypeChanges.getArgumentInfoCollection();
+              DexMethod newMethod = getNewMethodSignature(encodedMethod, prototypeChanges);
+              if (newMethod != method) {
+                Wrapper<DexMethod> wrapper = equivalence.wrap(newMethod);
 
-        // TODO(b/110806787): Can be extended to handle collisions by renaming the given
-        // method.
-        if (usedSignatures.add(wrapper)) {
-          clazz.setDirectMethod(
-              i,
-              encodedMethod.toTypeSubstitutedMethod(
-                  newMethod,
-                  removedArgumentsInfo.createParameterAnnotationsRemover(encodedMethod)));
-          methodMapping.put(method, newMethod);
-          if (removedArgumentsInfo.hasRemovedArguments()) {
-            removedArgumentsInfoPerMethod.put(newMethod, removedArgumentsInfo);
-          }
-        }
-      }
-    }
+                // TODO(b/110806787): Can be extended to handle collisions by renaming the given
+                // method.
+                if (usedSignatures.add(wrapper)) {
+                  methodMapping.put(method, newMethod);
+                  if (removedArgumentsInfo.hasRemovedArguments()) {
+                    removedArgumentsInfoPerMethod.put(newMethod, removedArgumentsInfo);
+                  }
+                  return encodedMethod.toTypeSubstitutedMethod(
+                      newMethod,
+                      removedArgumentsInfo.createParameterAnnotationsRemover(encodedMethod));
+                }
+              }
+              return encodedMethod;
+            });
 
     // Change the return type of virtual methods that return an uninstantiated type to void.
     // This is done in two steps. First we change the return type of all methods that override
@@ -227,66 +227,74 @@
     // all supertypes of the current class are always visited prior to the current class.
     // This is important to ensure that a method that used to override a method in its super
     // class will continue to do so after this optimization.
-    List<DexEncodedMethod> virtualMethods = clazz.virtualMethods();
-    for (int i = 0; i < virtualMethods.size(); ++i) {
-      DexEncodedMethod encodedMethod = virtualMethods.get(i);
-      DexMethod method = encodedMethod.method;
-      RewrittenPrototypeDescription prototypeChanges =
-          getPrototypeChanges(encodedMethod, DISALLOW_ARGUMENT_REMOVAL);
-      ArgumentInfoCollection removedArgumentsInfo = prototypeChanges.getArgumentInfoCollection();
-      DexMethod newMethod = getNewMethodSignature(encodedMethod, prototypeChanges);
-      if (newMethod != method) {
-        Wrapper<DexMethod> wrapper = equivalence.wrap(newMethod);
+    clazz
+        .getMethodCollection()
+        .replaceVirtualMethods(
+            encodedMethod -> {
+              DexMethod method = encodedMethod.method;
+              RewrittenPrototypeDescription prototypeChanges =
+                  getPrototypeChanges(encodedMethod, DISALLOW_ARGUMENT_REMOVAL);
+              ArgumentInfoCollection removedArgumentsInfo =
+                  prototypeChanges.getArgumentInfoCollection();
+              DexMethod newMethod = getNewMethodSignature(encodedMethod, prototypeChanges);
+              if (newMethod != method) {
+                Wrapper<DexMethod> wrapper = equivalence.wrap(newMethod);
 
-        boolean isOverrideOfPreviouslyChangedMethodInSuperClass =
-            changedVirtualMethods.getOrDefault(equivalence.wrap(method), ImmutableSet.of()).stream()
-                .anyMatch(other -> appView.appInfo().isSubtype(clazz.type, other));
-        if (isOverrideOfPreviouslyChangedMethodInSuperClass) {
-          assert methodPool.hasSeen(wrapper);
+                boolean isOverrideOfPreviouslyChangedMethodInSuperClass =
+                    changedVirtualMethods
+                        .getOrDefault(equivalence.wrap(method), ImmutableSet.of())
+                        .stream()
+                        .anyMatch(other -> appView.appInfo().isSubtype(clazz.type, other));
+                if (isOverrideOfPreviouslyChangedMethodInSuperClass) {
+                  assert methodPool.hasSeen(wrapper);
 
-          boolean signatureIsAvailable = usedSignatures.add(wrapper);
-          assert signatureIsAvailable;
+                  boolean signatureIsAvailable = usedSignatures.add(wrapper);
+                  assert signatureIsAvailable;
 
-          clazz.setVirtualMethod(
-              i,
-              encodedMethod.toTypeSubstitutedMethod(
-                  newMethod,
-                  removedArgumentsInfo.createParameterAnnotationsRemover(encodedMethod)));
-          methodMapping.put(method, newMethod);
-        }
-      }
-    }
-    for (int i = 0; i < virtualMethods.size(); ++i) {
-      DexEncodedMethod encodedMethod = virtualMethods.get(i);
-      DexMethod method = encodedMethod.method;
-      RewrittenPrototypeDescription prototypeChanges =
-          getPrototypeChanges(encodedMethod, DISALLOW_ARGUMENT_REMOVAL);
-      ArgumentInfoCollection removedArgumentsInfo = prototypeChanges.getArgumentInfoCollection();
-      DexMethod newMethod = getNewMethodSignature(encodedMethod, prototypeChanges);
-      if (newMethod != method) {
-        Wrapper<DexMethod> wrapper = equivalence.wrap(newMethod);
+                  methodMapping.put(method, newMethod);
+                  return encodedMethod.toTypeSubstitutedMethod(
+                      newMethod,
+                      removedArgumentsInfo.createParameterAnnotationsRemover(encodedMethod));
+                }
+              }
+              return encodedMethod;
+            });
+    clazz
+        .getMethodCollection()
+        .replaceVirtualMethods(
+            encodedMethod -> {
+              DexMethod method = encodedMethod.method;
+              RewrittenPrototypeDescription prototypeChanges =
+                  getPrototypeChanges(encodedMethod, DISALLOW_ARGUMENT_REMOVAL);
+              ArgumentInfoCollection removedArgumentsInfo =
+                  prototypeChanges.getArgumentInfoCollection();
+              DexMethod newMethod = getNewMethodSignature(encodedMethod, prototypeChanges);
+              if (newMethod != method) {
+                Wrapper<DexMethod> wrapper = equivalence.wrap(newMethod);
 
-        // TODO(b/110806787): Can be extended to handle collisions by renaming the given
-        //  method. Note that this also requires renaming all of the methods that override this
-        //  method, though.
-        if (!methodPool.hasSeen(wrapper) && usedSignatures.add(wrapper)) {
-          methodPool.seen(wrapper);
+                // TODO(b/110806787): Can be extended to handle collisions by renaming the given
+                //  method. Note that this also requires renaming all of the methods that override
+                // this
+                //  method, though.
+                if (!methodPool.hasSeen(wrapper) && usedSignatures.add(wrapper)) {
+                  methodPool.seen(wrapper);
 
-          clazz.setVirtualMethod(
-              i,
-              encodedMethod.toTypeSubstitutedMethod(
-                  newMethod,
-                  removedArgumentsInfo.createParameterAnnotationsRemover(encodedMethod)));
-          methodMapping.put(method, newMethod);
+                  methodMapping.put(method, newMethod);
 
-          boolean added =
-              changedVirtualMethods
-                  .computeIfAbsent(equivalence.wrap(method), key -> Sets.newIdentityHashSet())
-                  .add(clazz.type);
-          assert added;
-        }
-      }
-    }
+                  boolean added =
+                      changedVirtualMethods
+                          .computeIfAbsent(
+                              equivalence.wrap(method), key -> Sets.newIdentityHashSet())
+                          .add(clazz.type);
+                  assert added;
+
+                  return encodedMethod.toTypeSubstitutedMethod(
+                      newMethod,
+                      removedArgumentsInfo.createParameterAnnotationsRemover(encodedMethod));
+                }
+              }
+              return encodedMethod;
+            });
   }
 
   private RewrittenPrototypeDescription getPrototypeChanges(
@@ -385,7 +393,7 @@
     }
     assumeDynamicTypeRemover.removeMarkedInstructions(blocksToBeRemoved).finish();
     code.removeBlocks(blocksToBeRemoved);
-    code.removeAllTrivialPhis(valuesToNarrow);
+    code.removeAllDeadAndTrivialPhis(valuesToNarrow);
     code.removeUnreachableBlocks();
     if (!valuesToNarrow.isEmpty()) {
       new TypeAnalysis(appView).narrowing(valuesToNarrow);
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/UnusedArgumentsCollector.java b/src/main/java/com/android/tools/r8/ir/optimize/UnusedArgumentsCollector.java
index 7c71bd9..db393ed 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/UnusedArgumentsCollector.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/UnusedArgumentsCollector.java
@@ -35,7 +35,6 @@
 import java.util.BitSet;
 import java.util.HashSet;
 import java.util.IdentityHashMap;
-import java.util.List;
 import java.util.Map;
 import java.util.Set;
 import java.util.concurrent.ExecutionException;
@@ -224,58 +223,64 @@
       signatures.markSignatureAsUsed(method.method);
     }
 
-    List<DexEncodedMethod> directMethods = clazz.directMethods();
-    for (int i = 0; i < directMethods.size(); i++) {
-      DexEncodedMethod method = directMethods.get(i);
+    clazz
+        .getMethodCollection()
+        .replaceDirectMethods(
+            method -> {
 
-      // If this is a method with known resolution issues, then don't remove any unused arguments.
-      if (appView.appInfo().failedResolutionTargets.contains(method.method)) {
-        continue;
-      }
+              // If this is a method with known resolution issues, then don't remove any unused
+              // arguments.
+              if (appView.appInfo().failedResolutionTargets.contains(method.method)) {
+                return method;
+              }
 
-      ArgumentInfoCollection unused = collectUnusedArguments(method);
-      if (unused != null && unused.hasRemovedArguments()) {
-        DexProto newProto = createProtoWithRemovedArguments(method, unused);
-        DexMethod newSignature = signatures.getNewSignature(method, newProto);
-        if (newSignature == null) {
-          assert appView.dexItemFactory().isConstructor(method.method);
-          continue;
-        }
-        DexEncodedMethod newMethod = signatures.removeArguments(method, newSignature, unused);
-        clazz.setDirectMethod(i, newMethod);
-        synchronized (this) {
-          methodMapping.put(method.method, newMethod.method);
-          removedArguments.put(newMethod.method, unused);
-        }
-      }
-    }
+              ArgumentInfoCollection unused = collectUnusedArguments(method);
+              if (unused != null && unused.hasRemovedArguments()) {
+                DexProto newProto = createProtoWithRemovedArguments(method, unused);
+                DexMethod newSignature = signatures.getNewSignature(method, newProto);
+                if (newSignature == null) {
+                  assert appView.dexItemFactory().isConstructor(method.method);
+                  return method;
+                }
+                DexEncodedMethod newMethod =
+                    signatures.removeArguments(method, newSignature, unused);
+                synchronized (this) {
+                  methodMapping.put(method.method, newMethod.method);
+                  removedArguments.put(newMethod.method, unused);
+                }
+                return newMethod;
+              }
+              return method;
+            });
   }
 
   private void processVirtualMethods(DexProgramClass clazz) {
     MemberPool<DexMethod> methodPool = methodPoolCollection.get(clazz);
     GloballyUsedSignatures signatures = new GloballyUsedSignatures(methodPool);
 
-    List<DexEncodedMethod> virtualMethods = clazz.virtualMethods();
-    for (int i = 0; i < virtualMethods.size(); i++) {
-      DexEncodedMethod method = virtualMethods.get(i);
-      ArgumentInfoCollection unused = collectUnusedArguments(method, methodPool);
-      if (unused != null && unused.hasRemovedArguments()) {
-        DexProto newProto = createProtoWithRemovedArguments(method, unused);
-        DexMethod newSignature = signatures.getNewSignature(method, newProto);
+    clazz
+        .getMethodCollection()
+        .replaceVirtualMethods(
+            method -> {
+              ArgumentInfoCollection unused = collectUnusedArguments(method, methodPool);
+              if (unused != null && unused.hasRemovedArguments()) {
+                DexProto newProto = createProtoWithRemovedArguments(method, unused);
+                DexMethod newSignature = signatures.getNewSignature(method, newProto);
 
-        // Double-check that the new method signature is in fact available.
-        assert !methodPool.hasSeenStrictlyAbove(equivalence.wrap(newSignature));
-        assert !methodPool.hasSeenStrictlyBelow(equivalence.wrap(newSignature));
+                // Double-check that the new method signature is in fact available.
+                assert !methodPool.hasSeenStrictlyAbove(equivalence.wrap(newSignature));
+                assert !methodPool.hasSeenStrictlyBelow(equivalence.wrap(newSignature));
 
-        DexEncodedMethod newMethod =
-            signatures.removeArguments(
-                method, signatures.getNewSignature(method, newProto), unused);
-        clazz.setVirtualMethod(i, newMethod);
+                DexEncodedMethod newMethod =
+                    signatures.removeArguments(
+                        method, signatures.getNewSignature(method, newProto), unused);
 
-        methodMapping.put(method.method, newMethod.method);
-        removedArguments.put(newMethod.method, unused);
-      }
-    }
+                methodMapping.put(method.method, newMethod.method);
+                removedArguments.put(newMethod.method, unused);
+                return newMethod;
+              }
+              return method;
+            });
   }
 
   private ArgumentInfoCollection collectUnusedArguments(DexEncodedMethod method) {
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 6707fe1..9b443bc 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
@@ -270,7 +270,7 @@
 
         // Restore normality.
         Set<Value> affectedValues = Sets.newIdentityHashSet();
-        code.removeAllTrivialPhis(affectedValues);
+        code.removeAllDeadAndTrivialPhis(affectedValues);
         if (!affectedValues.isEmpty()) {
           new TypeAnalysis(appView).narrowing(affectedValues);
         }
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 e96b211..5e8b552 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
@@ -8,7 +8,6 @@
 import com.android.tools.r8.graph.DexApplication;
 import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexClass.FieldSetter;
-import com.android.tools.r8.graph.DexClass.MethodSetter;
 import com.android.tools.r8.graph.DexEncodedField;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexField;
@@ -17,6 +16,8 @@
 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.DexValue.DexValueInt;
+import com.android.tools.r8.graph.DexValue.DexValueNull;
 import com.android.tools.r8.graph.EnumValueInfoMapCollection.EnumValueInfoMap;
 import com.android.tools.r8.graph.GraphLense;
 import com.android.tools.r8.graph.GraphLense.NestedGraphLense;
@@ -559,8 +560,7 @@
           assert clazz.instanceFields().size() == 0;
           clearEnumtoUnboxMethods(clazz);
         } else {
-          fixupMethods(clazz.directMethods(), clazz::setDirectMethod);
-          fixupMethods(clazz.virtualMethods(), clazz::setVirtualMethod);
+          clazz.getMethodCollection().replaceMethods(this::fixupMethod);
           fixupFields(clazz.staticFields(), clazz::setStaticField);
           fixupFields(clazz.instanceFields(), clazz::setInstanceField);
         }
@@ -586,19 +586,13 @@
       }
     }
 
-    private void fixupMethods(List<DexEncodedMethod> methods, MethodSetter setter) {
-      if (methods == null) {
-        return;
+    private DexEncodedMethod fixupMethod(DexEncodedMethod encodedMethod) {
+      DexMethod newMethod = fixupMethod(encodedMethod.method);
+      if (newMethod != encodedMethod.method) {
+        lensBuilder.move(encodedMethod.method, newMethod, encodedMethod.isStatic());
+        return encodedMethod.toTypeSubstitutedMethod(newMethod);
       }
-      for (int i = 0; i < methods.size(); i++) {
-        DexEncodedMethod encodedMethod = methods.get(i);
-        DexMethod method = encodedMethod.method;
-        DexMethod newMethod = fixupMethod(method);
-        if (newMethod != method) {
-          lensBuilder.move(method, newMethod, encodedMethod.isStatic());
-          setter.setMethod(i, encodedMethod.toTypeSubstitutedMethod(newMethod));
-        }
-      }
+      return encodedMethod;
     }
 
     private void fixupFields(List<DexEncodedField> fields, FieldSetter setter) {
@@ -612,7 +606,13 @@
         if (newType != field.type) {
           DexField newField = factory.createField(field.holder, newType, field.name);
           lensBuilder.move(field, newField);
-          setter.setField(i, encodedField.toTypeSubstitutedField(newField));
+          DexEncodedField newEncodedField = encodedField.toTypeSubstitutedField(newField);
+          setter.setField(i, newEncodedField);
+          if (encodedField.isStatic() && encodedField.hasExplicitStaticValue()) {
+            assert encodedField.getStaticValue() == DexValueNull.NULL;
+            newEncodedField.setStaticValue(DexValueInt.DEFAULT);
+            // TODO(b/150593449): Support conversion from DexValueEnum to DexValueInt.
+          }
         }
       }
     }
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 84e4fdc..bbf2f22 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
@@ -19,6 +19,7 @@
 import static com.android.tools.r8.ir.code.Opcodes.DIV;
 import static com.android.tools.r8.ir.code.Opcodes.GOTO;
 import static com.android.tools.r8.ir.code.Opcodes.IF;
+import static com.android.tools.r8.ir.code.Opcodes.INIT_CLASS;
 import static com.android.tools.r8.ir.code.Opcodes.INSTANCE_GET;
 import static com.android.tools.r8.ir.code.Opcodes.INSTANCE_OF;
 import static com.android.tools.r8.ir.code.Opcodes.INSTANCE_PUT;
@@ -94,6 +95,7 @@
 import com.android.tools.r8.utils.ListUtils;
 import com.android.tools.r8.utils.MethodSignatureEquivalence;
 import com.android.tools.r8.utils.Pair;
+import com.android.tools.r8.utils.WorkList;
 import com.google.common.base.Equivalence.Wrapper;
 import com.google.common.collect.Sets;
 import java.util.ArrayDeque;
@@ -286,9 +288,6 @@
     List<Value> values = code.collectArguments();
     for (int i = 0; i < values.size(); i++) {
       Value value = values.get(i);
-      if (value.numberOfPhiUsers() > 0) {
-        continue;
-      }
       ParameterUsage usage = collectParameterUsages(i, value);
       if (usage != null) {
         usages.add(usage);
@@ -301,12 +300,23 @@
             : new ParameterUsagesInfo(usages));
   }
 
-  private ParameterUsage collectParameterUsages(int i, Value value) {
-    ParameterUsageBuilder builder = new ParameterUsageBuilder(value, i, dexItemFactory);
-    for (Instruction user : value.aliasedUsers()) {
-      if (!builder.note(user)) {
+  private ParameterUsage collectParameterUsages(int i, Value root) {
+    ParameterUsageBuilder builder = new ParameterUsageBuilder(root, i, dexItemFactory);
+    WorkList<Value> worklist = WorkList.newIdentityWorkList();
+    worklist.addIfNotSeen(root);
+    while (worklist.hasNext()) {
+      Value value = worklist.next();
+      if (value.hasPhiUsers()) {
         return null;
       }
+      for (Instruction user : value.uniqueUsers()) {
+        if (!builder.note(user)) {
+          return null;
+        }
+        if (user.isAssume()) {
+          worklist.addIfNotSeen(user.outValue());
+        }
+      }
     }
     return builder.build();
   }
@@ -442,6 +452,7 @@
           case CONST_STRING:
           case DEX_ITEM_BASED_CONST_STRING:
           case DIV:
+          case INIT_CLASS:
           case INSTANCE_OF:
           case MUL:
           case NEW_ARRAY_EMPTY:
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/lambda/CodeProcessor.java b/src/main/java/com/android/tools/r8/ir/optimize/lambda/CodeProcessor.java
index f10c3fe..1648958 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/lambda/CodeProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/lambda/CodeProcessor.java
@@ -18,6 +18,7 @@
 import com.android.tools.r8.ir.code.ConstMethodType;
 import com.android.tools.r8.ir.code.DefaultInstructionVisitor;
 import com.android.tools.r8.ir.code.IRCode;
+import com.android.tools.r8.ir.code.InitClass;
 import com.android.tools.r8.ir.code.InstanceGet;
 import com.android.tools.r8.ir.code.InstancePut;
 import com.android.tools.r8.ir.code.InstructionListIterator;
@@ -62,6 +63,8 @@
 
     boolean isValidNewInstance(CodeProcessor context, NewInstance invoke);
 
+    boolean isValidInitClass(CodeProcessor context, DexType clazz);
+
     void patch(ApplyStrategy context, NewInstance newInstance);
 
     void patch(ApplyStrategy context, InvokeMethod invoke);
@@ -69,6 +72,8 @@
     void patch(ApplyStrategy context, InstanceGet instanceGet);
 
     void patch(ApplyStrategy context, StaticGet staticGet);
+
+    void patch(ApplyStrategy context, InitClass initClass);
   }
 
   // No-op strategy.
@@ -110,6 +115,11 @@
         }
 
         @Override
+        public boolean isValidInitClass(CodeProcessor context, DexType clazz) {
+          return false;
+        }
+
+        @Override
         public void patch(ApplyStrategy context, NewInstance newInstance) {
           throw new Unreachable();
         }
@@ -128,6 +138,11 @@
         public void patch(ApplyStrategy context, StaticGet staticGet) {
           throw new Unreachable();
         }
+
+        @Override
+        public void patch(ApplyStrategy context, InitClass initClass) {
+          throw new Unreachable();
+        }
       };
 
   public final AppView<AppInfoWithLiveness> appView;
@@ -353,6 +368,21 @@
     return null;
   }
 
+  @Override
+  public Void visit(InitClass initClass) {
+    DexType clazz = initClass.getClassValue();
+    Strategy strategy = strategyProvider.apply(clazz);
+    if (strategy.isValidInitClass(this, clazz)) {
+      if (shouldRewrite(clazz)) {
+        // Only rewrite references to lambda classes if we are outside the class.
+        process(strategy, initClass);
+      }
+    } else {
+      lambdaChecker.accept(clazz);
+    }
+    return null;
+  }
+
   abstract void process(Strategy strategy, InvokeMethod invokeMethod);
 
   abstract void process(Strategy strategy, NewInstance newInstance);
@@ -364,4 +394,6 @@
   abstract void process(Strategy strategy, StaticPut staticPut);
 
   abstract void process(Strategy strategy, StaticGet staticGet);
+
+  abstract void process(Strategy strategy, InitClass initClass);
 }
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 176db10..510c122 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
@@ -20,6 +20,7 @@
 import com.android.tools.r8.graph.classmerging.HorizontallyMergedLambdaClasses;
 import com.android.tools.r8.ir.analysis.type.DestructivePhiTypeUpdater;
 import com.android.tools.r8.ir.code.IRCode;
+import com.android.tools.r8.ir.code.InitClass;
 import com.android.tools.r8.ir.code.InstanceGet;
 import com.android.tools.r8.ir.code.InstancePut;
 import com.android.tools.r8.ir.code.Instruction;
@@ -529,6 +530,11 @@
     void process(Strategy strategy, StaticGet staticGet) {
       queueForProcessing(method);
     }
+
+    @Override
+    void process(Strategy strategy, InitClass initClass) {
+      queueForProcessing(method);
+    }
   }
 
   public final class ApplyStrategy extends CodeProcessor {
@@ -644,6 +650,11 @@
     void process(Strategy strategy, StaticGet staticGet) {
       strategy.patch(this, staticGet);
     }
+
+    @Override
+    void process(Strategy strategy, InitClass initClass) {
+      strategy.patch(this, initClass);
+    }
   }
 
   private final class LambdaMergerOptimizationInfoFixer
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KotlinLambdaGroupCodeStrategy.java b/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KotlinLambdaGroupCodeStrategy.java
index e54d241..81480d6 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KotlinLambdaGroupCodeStrategy.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KotlinLambdaGroupCodeStrategy.java
@@ -15,6 +15,7 @@
 import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
 import com.android.tools.r8.ir.code.CheckCast;
 import com.android.tools.r8.ir.code.ConstNumber;
+import com.android.tools.r8.ir.code.InitClass;
 import com.android.tools.r8.ir.code.InstanceGet;
 import com.android.tools.r8.ir.code.Instruction;
 import com.android.tools.r8.ir.code.InvokeDirect;
@@ -50,10 +51,10 @@
     assert group.containsLambda(lambda);
     // Only support writes to singleton static field named 'INSTANCE' from lambda
     // static class initializer.
-    return field.name == context.kotlin.functional.kotlinStyleLambdaInstanceName &&
-        lambda == field.type &&
-        context.factory.isClassConstructor(context.method.method) &&
-        context.method.method.holder == lambda;
+    return field.name == context.kotlin.functional.kotlinStyleLambdaInstanceName
+        && lambda == field.type
+        && context.factory.isClassConstructor(context.method.method)
+        && context.method.method.holder == lambda;
   }
 
   @Override
@@ -61,8 +62,8 @@
     DexType lambda = field.holder;
     assert group.containsLambda(lambda);
     // Support all reads of singleton static field named 'INSTANCE'.
-    return field.name == context.kotlin.functional.kotlinStyleLambdaInstanceName &&
-        lambda == field.type;
+    return field.name == context.kotlin.functional.kotlinStyleLambdaInstanceName
+        && lambda == field.type;
   }
 
   @Override
@@ -112,6 +113,13 @@
   }
 
   @Override
+  public boolean isValidInitClass(CodeProcessor context, DexType clazz) {
+    assert group.containsLambda(clazz);
+    // Support all init class instructions.
+    return true;
+  }
+
+  @Override
   public void patch(ApplyStrategy context, NewInstance newInstance) {
     DexType oldType = newInstance.clazz;
     DexType newType = group.getGroupClassType();
@@ -202,6 +210,14 @@
     context.recordTypeHasChanged(patchedStaticGet.outValue());
   }
 
+  @Override
+  public void patch(ApplyStrategy context, InitClass initClass) {
+    InitClass pachedInitClass =
+        new InitClass(
+            context.code.createValue(TypeLatticeElement.getInt()), group.getGroupClassType());
+    context.instructions().replaceCurrentInstruction(pachedInitClass);
+  }
+
   private void patchInitializer(CodeProcessor context, InvokeDirect invoke) {
     // Patching includes:
     //  - change of methods
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 8978657..3c99a56 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
@@ -659,6 +659,11 @@
     }
 
     @Override
+    public boolean registerInitClass(DexType clazz) {
+      return registerTypeReference(clazz);
+    }
+
+    @Override
     public boolean registerInvokeVirtual(DexMethod method) {
       return registerMethod(method);
     }
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 8e8d2da..6c714c9 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
@@ -828,6 +828,9 @@
     public boolean isSupportedAppendMethod(InvokeMethod invoke) {
       DexMethod invokedMethod = invoke.getInvokedMethod();
       assert isAppendMethod(invokedMethod);
+      if (invoke.hasOutValue()) {
+        return false;
+      }
       // Any methods other than append(arg) are not trivial since they may change the builder
       // state not monotonically.
       if (invoke.inValues().size() > 2) {
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinClass.java b/src/main/java/com/android/tools/r8/kotlin/KotlinClass.java
index 4a65b7c..a37a42b 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinClass.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinClass.java
@@ -13,7 +13,10 @@
 
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexEncodedField;
 import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexString;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.InnerClassAttribute;
@@ -30,6 +33,9 @@
 
   KmClass kmClass;
 
+  DexField companionObject = null;
+  DexProgramClass hostClass = null;
+
   static KotlinClass fromKotlinClassMetadata(
       KotlinClassMetadata kotlinClassMetadata, DexClass clazz) {
     assert kotlinClassMetadata instanceof KotlinClassMetadata.Class;
@@ -41,6 +47,28 @@
     super(metadata, clazz);
   }
 
+  void foundCompanionObject(DexEncodedField companionObject) {
+    // Companion cannot be nested. If this class is a host (and about to store a field that holds
+    // a companion object), it should not have a host class.
+    assert hostClass == null;
+    this.companionObject = companionObject.field;
+  }
+
+  boolean hasCompanionObject() {
+    return companionObject != null;
+  }
+
+  DexType getCompanionObjectType() {
+    return hasCompanionObject() ? companionObject.type : null;
+  }
+
+  void linkHostClass(DexProgramClass hostClass) {
+    // Companion cannot be nested. If this class is a companion object (and about to link to its
+    // host class), it should not have a companion object.
+    assert companionObject == null;
+    this.hostClass = hostClass;
+  }
+
   @Override
   void processMetadata(KotlinClassMetadata.Class metadata) {
     kmClass = metadata.toKmClass();
@@ -78,10 +106,19 @@
       superTypes.add(toKmType(addKotlinPrefix("Any;")));
     }
 
-    // Rewriting downward hierarchies: nested.
+    // Rewriting downward hierarchies: nested, including companion class.
+    // Note that `kotlinc` uses these nested classes to determine which classes to look up when
+    // resolving declarations in the companion object, e.g., Host.Companion.prop and Host.prop.
+    // Thus, users (in particular, library developers) should keep InnerClasses and EnclosingMethod
+    // attributes if declarations in the companion need to be exposed.
     List<String> nestedClasses = kmClass.getNestedClasses();
     nestedClasses.clear();
     for (InnerClassAttribute innerClassAttribute : clazz.getInnerClasses()) {
+      // Skip InnerClass attribute for itself.
+      // Otherwise, an inner class would have itself as a nested class.
+      if (clazz.getInnerClassAttributeForThisClass() == innerClassAttribute) {
+        continue;
+      }
       DexString renamedInnerName = lens.lookupInnerName(innerClassAttribute, appView.options());
       if (renamedInnerName != null) {
         nestedClasses.add(renamedInnerName.toString());
@@ -103,6 +140,8 @@
     if (!appView.options().enableKotlinMetadataRewritingForMembers) {
       return;
     }
+
+    // Rewriting constructors.
     List<KmConstructor> constructors = kmClass.getConstructors();
     constructors.clear();
     for (DexEncodedMethod method : clazz.directMethods()) {
@@ -115,9 +154,14 @@
       }
     }
 
-    // TODO(b/70169921): enum entries
+    // Rewriting companion object if any.
+    if (kmClass.getCompanionObject() != null && hasCompanionObject()) {
+      kmClass.setCompanionObject(lens.lookupName(companionObject).toString());
+    }
 
-    rewriteDeclarationContainer(kmClass, appView, lens);
+    // TODO(b/151193864): enum entries
+
+    rewriteDeclarationContainer(appView, lens);
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinClassFacade.java b/src/main/java/com/android/tools/r8/kotlin/KotlinClassFacade.java
index 4433017..cc1cd14 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinClassFacade.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinClassFacade.java
@@ -21,7 +21,7 @@
 
 public final class KotlinClassFacade extends KotlinInfo<KotlinClassMetadata.MultiFileClassFacade> {
 
-  // TODO(b/70169921): is it better to maintain List<DexType>?
+  // TODO(b/151194869): is it better to maintain List<DexType>?
   List<String> partClassNames;
 
   static KotlinClassFacade fromKotlinClassMetadata(
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinClassPart.java b/src/main/java/com/android/tools/r8/kotlin/KotlinClassPart.java
index f108252..16223bb 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinClassPart.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinClassPart.java
@@ -19,7 +19,7 @@
 public final class KotlinClassPart extends KotlinInfo<KotlinClassMetadata.MultiFileClassPart> {
 
   KmPackage kmPackage;
-  // TODO(b/70169921): is it better to maintain DexType?
+  // TODO(b/151194869): is it better to maintain DexType?
   String facadeClassName;
 
   static KotlinClassPart fromKotlinClassMetadata(
@@ -48,7 +48,7 @@
     if (!appView.options().enableKotlinMetadataRewritingForMembers) {
       return;
     }
-    rewriteDeclarationContainer(kmPackage, appView, lens);
+    rewriteDeclarationContainer(appView, lens);
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinFile.java b/src/main/java/com/android/tools/r8/kotlin/KotlinFile.java
index bb4dc47..27c9230 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinFile.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinFile.java
@@ -38,7 +38,7 @@
     if (!appView.options().enableKotlinMetadataRewritingForMembers) {
       return;
     }
-    rewriteDeclarationContainer(kmPackage, appView, lens);
+    rewriteDeclarationContainer(appView, lens);
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinInfo.java b/src/main/java/com/android/tools/r8/kotlin/KotlinInfo.java
index e9c1eb6..7c53cba 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinInfo.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinInfo.java
@@ -5,6 +5,7 @@
 package com.android.tools.r8.kotlin;
 
 import static com.android.tools.r8.kotlin.KotlinMetadataSynthesizer.toRenamedKmFunction;
+import static kotlinx.metadata.Flag.Class.IS_COMPANION_OBJECT;
 
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.AppView;
@@ -17,6 +18,7 @@
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.function.Predicate;
 import kotlinx.metadata.KmDeclarationContainer;
 import kotlinx.metadata.KmFunction;
 import kotlinx.metadata.KmProperty;
@@ -93,15 +95,49 @@
     return isClass() || isFile() || isClassPart();
   }
 
+  KmDeclarationContainer getDeclarations() {
+    if (isClass()) {
+      return asClass().kmClass;
+    } else if (isFile()) {
+      return asFile().kmPackage;
+    } else if (isClassPart()) {
+      return asClassPart().kmPackage;
+    } else {
+      throw new Unreachable("Unexpected KotlinInfo: " + this);
+    }
+  }
+
   // {@link KmClass} and {@link KmPackage} are inherited from {@link KmDeclarationContainer} that
   // abstract functions and properties. Rewriting of those portions can be unified here.
-  void rewriteDeclarationContainer(
-      KmDeclarationContainer kmDeclarationContainer,
-      AppView<AppInfoWithLiveness> appView,
-      NamingLens lens) {
+  void rewriteDeclarationContainer(AppView<AppInfoWithLiveness> appView, NamingLens lens) {
     assert clazz != null;
 
+    KmDeclarationContainer kmDeclarationContainer = getDeclarations();
     Map<String, KmPropertyGroup.Builder> propertyGroupBuilderMap = new HashMap<>();
+
+    // Backing fields for a companion object are declared in its host class.
+    Iterable<DexEncodedField> fields = clazz.fields();
+    Predicate<DexEncodedField> backingFieldTester = DexEncodedField::isKotlinBackingField;
+    if (isClass()) {
+      KotlinClass ktClass = asClass();
+      if (IS_COMPANION_OBJECT.invoke(ktClass.kmClass.getFlags()) && ktClass.hostClass != null) {
+        fields = ktClass.hostClass.fields();
+        backingFieldTester = DexEncodedField::isKotlinBackingFieldForCompanionObject;
+      }
+    }
+
+    for (DexEncodedField field : fields) {
+      if (backingFieldTester.test(field)) {
+        String name = field.getKotlinMemberInfo().propertyName;
+        assert name != null;
+        KmPropertyGroup.Builder builder =
+            propertyGroupBuilderMap.computeIfAbsent(
+                name,
+                k -> KmPropertyGroup.builder(field.getKotlinMemberInfo().propertyFlags, name));
+        builder.foundBackingField(field);
+      }
+    }
+
     List<KmFunction> functions = kmDeclarationContainer.getFunctions();
     functions.clear();
     for (DexEncodedMethod method : clazz.methods()) {
@@ -121,19 +157,22 @@
         assert name != null;
         KmPropertyGroup.Builder builder =
             propertyGroupBuilderMap.computeIfAbsent(
-                name, k -> KmPropertyGroup.builder(method.getKotlinMemberInfo().flag, name));
+                name,
+                // Hitting here (creating a property builder) after visiting all fields means that
+                // this property doesn't have a backing field. Don't use members' flags.
+                k -> KmPropertyGroup.builder(method.getKotlinMemberInfo().propertyFlags, name));
         switch (method.getKotlinMemberInfo().memberKind) {
           case EXTENSION_PROPERTY_GETTER:
             builder.isExtensionGetter();
             // fallthrough;
           case PROPERTY_GETTER:
-            builder.foundGetter(method);
+            builder.foundGetter(method, method.getKotlinMemberInfo().flags);
             break;
           case EXTENSION_PROPERTY_SETTER:
             builder.isExtensionSetter();
             // fallthrough;
           case PROPERTY_SETTER:
-            builder.foundSetter(method);
+            builder.foundSetter(method, method.getKotlinMemberInfo().flags);
             break;
           case EXTENSION_PROPERTY_ANNOTATIONS:
             builder.isExtensionAnnotations();
@@ -147,18 +186,7 @@
         continue;
       }
 
-      // TODO(b/70169921): What should we do for methods that fall into this category---no mark?
-    }
-
-    for (DexEncodedField field : clazz.fields()) {
-      if (field.isKotlinBackingField()) {
-        String name = field.getKotlinMemberInfo().propertyName;
-        assert name != null;
-        KmPropertyGroup.Builder builder =
-            propertyGroupBuilderMap.computeIfAbsent(
-                name, k -> KmPropertyGroup.builder(field.getKotlinMemberInfo().flag, name));
-        builder.foundBackingField(field);
-      }
+      // TODO(b/151194869): What should we do for methods that fall into this category---no mark?
     }
 
     List<KmProperty> properties = kmDeclarationContainer.getProperties();
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinInfoCollector.java b/src/main/java/com/android/tools/r8/kotlin/KotlinInfoCollector.java
new file mode 100644
index 0000000..9d89660
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinInfoCollector.java
@@ -0,0 +1,62 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.kotlin;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexApplication;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.utils.Reporter;
+import com.android.tools.r8.utils.ThreadUtils;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+
+public class KotlinInfoCollector {
+  public static void computeKotlinInfoForProgramClasses(
+      DexApplication application, AppView<?> appView, ExecutorService executorService)
+      throws ExecutionException {
+    if (appView.options().kotlinOptimizationOptions().disableKotlinSpecificOptimizations) {
+      return;
+    }
+    Kotlin kotlin = appView.dexItemFactory().kotlin;
+    Reporter reporter = appView.options().reporter;
+    Map<DexProgramClass, DexProgramClass> companionToHostMap = new ConcurrentHashMap<>();
+    ThreadUtils.processItems(
+        application.classes(),
+        programClass -> {
+          KotlinInfo kotlinInfo = kotlin.getKotlinInfo(programClass, reporter);
+          programClass.setKotlinInfo(kotlinInfo);
+          KotlinMemberInfo.markKotlinMemberInfo(programClass, kotlinInfo, reporter);
+          // Store a companion type to revisit.
+          if (kotlinInfo != null
+              && kotlinInfo.isClass()
+              && kotlinInfo.asClass().hasCompanionObject()) {
+            DexType companionType = kotlinInfo.asClass().getCompanionObjectType();
+            DexProgramClass companionClass = appView.definitionForProgramType(companionType);
+            if (companionClass != null) {
+              companionToHostMap.put(companionClass, programClass);
+            }
+          }
+        },
+        executorService);
+    // TODO(b/151194869): if we can guarantee that Companion classes are visited ahead and their
+    //  KotlinInfo is created before processing host classes, below could be hoisted to 1st pass.
+    //  Maybe name-based filtering? E.g., classes whose name ends with "$Companion" v.s. not?
+    ThreadUtils.processItems(
+        companionToHostMap.keySet(),
+        companionClass -> {
+          KotlinInfo kotlinInfo = companionClass.getKotlinInfo();
+          if (kotlinInfo != null && kotlinInfo.isClass()) {
+            DexProgramClass hostClass = companionToHostMap.get(companionClass);
+            assert hostClass != null;
+            kotlinInfo.asClass().linkHostClass(hostClass);
+            // Revisit host class's members with declarations in the companion object.
+            KotlinMemberInfo.markKotlinMemberInfo(hostClass, kotlinInfo, reporter);
+          }
+        },
+        executorService);
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinMemberInfo.java b/src/main/java/com/android/tools/r8/kotlin/KotlinMemberInfo.java
index 50058ec..dfdb28b 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinMemberInfo.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinMemberInfo.java
@@ -7,7 +7,6 @@
 import static com.android.tools.r8.kotlin.KotlinMetadataJvmExtensionUtils.toJvmMethodSignature;
 import static com.android.tools.r8.kotlin.KotlinMetadataSynthesizer.isExtension;
 
-import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexEncodedField;
 import com.android.tools.r8.graph.DexEncodedMethod;
@@ -37,8 +36,12 @@
   }
 
   public final MemberKind memberKind;
-  // Original member flag. May be necessary to keep Kotlin-specific flag, e.g., suspend function.
-  final int flag;
+  // Original member flags. May be necessary to keep Kotlin-specific flag, e.g., suspend function.
+  final int flags;
+  // TODO(b/151194869): better to split into FunctionInfo v.s. PropertyInfo ?
+  // Original property flags. E.g., for property getter, getter flags are stored to `flags`, while
+  // the property's flags are stored here, in case of properties without a backing field.
+  final int propertyFlags;
   // Original property name for (extension) property. Otherwise, null.
   final String propertyName;
   // Information from original KmValueParameter(s) if available. Otherwise, null.
@@ -46,32 +49,40 @@
 
   // Constructor for KmFunction
   private KotlinMemberInfo(
-      MemberKind memberKind, int flag, List<KmValueParameter> kmValueParameters) {
-    this(memberKind, flag, null, kmValueParameters);
+      MemberKind memberKind, int flags, List<KmValueParameter> kmValueParameters) {
+    this(memberKind, flags, 0, null, kmValueParameters);
   }
 
   // Constructor for a backing field and a getter in KmProperty
-  private KotlinMemberInfo(MemberKind memberKind, int flag, String propertyName) {
-    this(memberKind, flag, propertyName, EMPTY_PARAM);
+  private KotlinMemberInfo(
+      MemberKind memberKind, int flags, int propertyFlags, String propertyName) {
+    this(memberKind, flags, propertyFlags, propertyName, EMPTY_PARAM);
   }
 
   // Constructor for a setter in KmProperty
   private KotlinMemberInfo(
       MemberKind memberKind,
-      int flag,
+      int flags,
+      int propertyFlags,
       String propertyName,
       KmValueParameter kmValueParameter) {
-    this(memberKind, flag, propertyName,
+    this(
+        memberKind,
+        flags,
+        propertyFlags,
+        propertyName,
         kmValueParameter != null ? ImmutableList.of(kmValueParameter) : EMPTY_PARAM);
   }
 
   private KotlinMemberInfo(
       MemberKind memberKind,
-      int flag,
+      int flags,
+      int propertyFlags,
       String propertyName,
       List<KmValueParameter> kmValueParameters) {
     this.memberKind = memberKind;
-    this.flag = flag;
+    this.flags = flags;
+    this.propertyFlags = propertyFlags;
     this.propertyName = propertyName;
     assert kmValueParameters != null;
     if (kmValueParameters.isEmpty()) {
@@ -100,6 +111,7 @@
     FUNCTION,
     EXTENSION_FUNCTION,
 
+    COMPANION_OBJECT_BACKING_FIELD,
     PROPERTY_BACKING_FIELD,
     PROPERTY_GETTER,
     PROPERTY_SETTER,
@@ -110,8 +122,6 @@
     EXTENSION_PROPERTY_SETTER,
     EXTENSION_PROPERTY_ANNOTATIONS;
 
-    // TODO(b/70169921): companion
-
     public boolean isFunction() {
       return this == FUNCTION || isExtensionFunction();
     }
@@ -124,8 +134,13 @@
       return this == PROPERTY_BACKING_FIELD;
     }
 
+    public boolean isBackingFieldForCompanionObject() {
+      return this == COMPANION_OBJECT_BACKING_FIELD;
+    }
+
     public boolean isProperty() {
       return isBackingField()
+          || isBackingFieldForCompanionObject()
           || this == PROPERTY_GETTER
           || this == PROPERTY_SETTER
           || this == PROPERTY_ANNOTATIONS
@@ -139,24 +154,17 @@
     }
   }
 
-  public static void markKotlinMemberInfo(
-      DexClass clazz, KotlinInfo kotlinInfo, Reporter reporter) {
+  static void markKotlinMemberInfo(DexClass clazz, KotlinInfo kotlinInfo, Reporter reporter) {
     if (kotlinInfo == null || !kotlinInfo.hasDeclarations()) {
       return;
     }
-    if (kotlinInfo.isClass()) {
-      markKotlinMemberInfo(clazz, kotlinInfo.asClass().kmClass, reporter);
-    } else if (kotlinInfo.isFile()) {
-      markKotlinMemberInfo(clazz, kotlinInfo.asFile().kmPackage, reporter);
-    } else if (kotlinInfo.isClassPart()) {
-      markKotlinMemberInfo(clazz, kotlinInfo.asClassPart().kmPackage, reporter);
-    } else {
-      throw new Unreachable("Unexpected KotlinInfo: " + kotlinInfo);
-    }
-  }
 
-  private static void markKotlinMemberInfo(
-      DexClass clazz, KmDeclarationContainer kmDeclarationContainer, Reporter reporter) {
+    KmDeclarationContainer kmDeclarationContainer = kotlinInfo.getDeclarations();
+    String companionObject = null;
+    if (kotlinInfo.isClass()) {
+      companionObject = kotlinInfo.asClass().kmClass.getCompanionObject();
+    }
+
     Map<String, KmFunction> kmFunctionMap = new HashMap<>();
     Map<String, KmProperty> kmPropertyFieldMap = new HashMap<>();
     Map<String, KmProperty> kmPropertyGetterMap = new HashMap<>();
@@ -179,16 +187,26 @@
       if (propertyProcessor.setterSignature() != null) {
         kmPropertySetterMap.put(propertyProcessor.setterSignature().asString(), kmProperty);
       }
-      // TODO(b/70169921): property annotations
+      // TODO(b/151194869): property annotations
     });
 
     for (DexEncodedField field : clazz.fields()) {
+      if (companionObject != null && companionObject.equals(field.field.name.toString())) {
+        assert kotlinInfo.isClass();
+        kotlinInfo.asClass().foundCompanionObject(field);
+        continue;
+      }
       String key = toJvmFieldSignature(field.field).asString();
       if (kmPropertyFieldMap.containsKey(key)) {
         KmProperty kmProperty = kmPropertyFieldMap.get(key);
         field.setKotlinMemberInfo(
             new KotlinMemberInfo(
-                MemberKind.PROPERTY_BACKING_FIELD, kmProperty.getFlags(), kmProperty.getName()));
+                clazz == kotlinInfo.clazz
+                    ? MemberKind.PROPERTY_BACKING_FIELD
+                    : MemberKind.COMPANION_OBJECT_BACKING_FIELD,
+                kmProperty.getFlags(),
+                kmProperty.getFlags(),
+                kmProperty.getName()));
       }
     }
 
@@ -218,12 +236,16 @@
           method.setKotlinMemberInfo(
               new KotlinMemberInfo(
                   MemberKind.EXTENSION_PROPERTY_GETTER,
+                  kmProperty.getGetterFlags(),
                   kmProperty.getFlags(),
                   kmProperty.getName()));
         } else {
           method.setKotlinMemberInfo(
               new KotlinMemberInfo(
-                  MemberKind.PROPERTY_GETTER, kmProperty.getFlags(), kmProperty.getName()));
+                  MemberKind.PROPERTY_GETTER,
+                  kmProperty.getGetterFlags(),
+                  kmProperty.getFlags(),
+                  kmProperty.getName()));
         }
       } else if (kmPropertySetterMap.containsKey(key)) {
         KmProperty kmProperty = kmPropertySetterMap.get(key);
@@ -231,6 +253,7 @@
           method.setKotlinMemberInfo(
               new KotlinMemberInfo(
                   MemberKind.EXTENSION_PROPERTY_SETTER,
+                  kmProperty.getSetterFlags(),
                   kmProperty.getFlags(),
                   kmProperty.getName(),
                   kmProperty.getSetterParameter()));
@@ -238,6 +261,7 @@
           method.setKotlinMemberInfo(
               new KotlinMemberInfo(
                   MemberKind.PROPERTY_SETTER,
+                  kmProperty.getSetterFlags(),
                   kmProperty.getFlags(),
                   kmProperty.getName(),
                   kmProperty.getSetterParameter()));
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataJvmExtensionUtils.java b/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataJvmExtensionUtils.java
index fadf9cb..474c123 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataJvmExtensionUtils.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataJvmExtensionUtils.java
@@ -87,6 +87,7 @@
                   i -> addKotlinPrefix("jvm/functions/Function" + i + ";"))))
           .build();
 
+  // TODO(b/151195430): remove backward type conversions.
   private static String remapKotlinType(String type) {
     if (knownTypeConversion.containsKey(type)) {
       return knownTypeConversion.get(type);
@@ -94,6 +95,7 @@
     return type;
   }
 
+  // TODO(b/151195430): remove backward type conversions.
   // Kotlin @Metadata deserialization has plain "kotlin", which will be relocated in r8lib.
   // See b/70169921#comment57 for more details.
   // E.g., desc: (Labc/xyz/C;Lkotlin/Function1;)kotlin/Unit
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 9507b32..5d1777f 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataRewriter.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataRewriter.java
@@ -69,7 +69,7 @@
             // If @Metadata is still associated, this class should not be renamed
             // (by {@link ClassNameMinifier} of course).
             // Or, we start maintaining @Metadata for renamed classes.
-            // TODO(b/70169921): if this option is settled down, this assertion is meaningless.
+            // TODO(b/151194540): if this option is settled down, this assertion is meaningless.
             assert lens.lookupType(clazz.type, appView.dexItemFactory()) == clazz.type
                     || appView.options().enableKotlinMetadataRewritingForRenamedClasses
                 : clazz.toSourceString()
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataSynthesizer.java b/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataSynthesizer.java
index ce685b2..43b5cf2 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataSynthesizer.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataSynthesizer.java
@@ -9,6 +9,7 @@
 import static com.android.tools.r8.utils.DescriptorUtils.descriptorToKotlinClassifier;
 import static com.android.tools.r8.utils.DescriptorUtils.getBinaryNameFromDescriptor;
 import static com.android.tools.r8.utils.DescriptorUtils.getDescriptorFromKmType;
+import static kotlinx.metadata.Flag.Property.IS_VAR;
 import static kotlinx.metadata.FlagsKt.flagsOf;
 
 import com.android.tools.r8.graph.AppView;
@@ -115,11 +116,11 @@
     if (classifier == null) {
       return null;
     }
-    // TODO(b/70169921): Mysterious, why attempts to properly set flags bothers kotlinc?
+    // TODO(b/151194869): Mysterious, why attempts to properly set flags bothers kotlinc?
     //   and/or why wiping out flags works for KmType but not KmFunction?!
     KmType kmType = new KmType(flagsOf());
     kmType.visitClass(classifier);
-    // TODO(b/70169921): Can be generalized too, like ArrayTypeSignature.Converter ?
+    // TODO(b/151194164): Can be generalized too, like ArrayTypeSignature.Converter ?
     // E.g., java.lang.String[] -> KmType(kotlin/Array, KmTypeProjection(OUT, kotlin/String))
     if (type.isArrayType() && !type.isPrimitiveArrayType()) {
       DexType elementType = type.toArrayElementType(appView.dexItemFactory());
@@ -176,7 +177,7 @@
         KmType argumentType = typeArgument.asClassTypeSignature().convert(this);
         result.getArguments().add(new KmTypeProjection(KmVariance.INVARIANT, argumentType));
       }
-      // TODO(b/70169921): for TypeVariableSignature, there is KmType::visitTypeParameter.
+      // TODO(b/151194164): for TypeVariableSignature, there is KmType::visitTypeParameter.
       return result;
     }
 
@@ -234,11 +235,11 @@
     // For a library method override, we should not have renamed it.
     assert !method.isLibraryMethodOverride().isTrue() || renamedMethod.name == method.method.name
         : method.toSourceString() + " -> " + renamedMethod.toSourceString();
-    // TODO(b/70169921): Should we keep kotlin-specific flags only while synthesizing the base
+    // TODO(b/151194869): Should we keep kotlin-specific flags only while synthesizing the base
     //  value from general JVM flags?
     int flag =
         appView.appInfo().isPinned(method.method) && method.getKotlinMemberInfo() != null
-            ? method.getKotlinMemberInfo().flag
+            ? method.getKotlinMemberInfo().flags
             : method.accessFlags.getAsKotlinFlags();
     KmFunction kmFunction = new KmFunction(flag, renamedMethod.name.toString());
     JvmExtensionsKt.setSignature(kmFunction, toJvmMethodSignature(renamedMethod));
@@ -369,49 +370,57 @@
    * getter, and so on.
    */
   static class KmPropertyGroup {
-    final int flag;
+    final int flags;
     final String name;
     final DexEncodedField field;
     final DexEncodedMethod getter;
+    final int getterFlags;
     final DexEncodedMethod setter;
+    final int setterFlags;
     final DexEncodedMethod annotations;
     final boolean isExtension;
 
     private KmPropertyGroup(
-        int flag,
+        int flags,
         String name,
         DexEncodedField field,
         DexEncodedMethod getter,
+        int getterFlags,
         DexEncodedMethod setter,
+        int setterFlags,
         DexEncodedMethod annotations,
         boolean isExtension) {
-      this.flag = flag;
+      this.flags = flags;
       this.name = name;
       this.field = field;
       this.getter = getter;
+      this.getterFlags = getterFlags;
       this.setter = setter;
+      this.setterFlags = setterFlags;
       this.annotations = annotations;
       this.isExtension = isExtension;
     }
 
-    static Builder builder(int flag, String name) {
-      return new Builder(flag, name);
+    static Builder builder(int flags, String name) {
+      return new Builder(flags, name);
     }
 
     static class Builder {
-      private final int flag;
+      private final int flags;
       private final String name;
       private DexEncodedField field;
       private DexEncodedMethod getter;
+      private int getterFlags;
       private DexEncodedMethod setter;
+      private int setterFlags;
       private DexEncodedMethod annotations;
 
       private boolean isExtensionGetter;
       private boolean isExtensionSetter;
       private boolean isExtensionAnnotations;
 
-      private Builder(int flag, String name) {
-        this.flag = flag;
+      private Builder(int flags, String name) {
+        this.flags = flags;
         this.name = name;
       }
 
@@ -420,13 +429,15 @@
         return this;
       }
 
-      Builder foundGetter(DexEncodedMethod getter) {
+      Builder foundGetter(DexEncodedMethod getter, int flags) {
         this.getter = getter;
+        this.getterFlags = flags;
         return this;
       }
 
-      Builder foundSetter(DexEncodedMethod setter) {
+      Builder foundSetter(DexEncodedMethod setter, int flags) {
         this.setter = setter;
+        this.setterFlags = flags;
         return this;
       }
 
@@ -464,12 +475,13 @@
             return null;
           }
         }
-        return new KmPropertyGroup(flag, name, field, getter, setter, annotations, isExtension);
+        return new KmPropertyGroup(
+            flags, name, field, getter, getterFlags, setter, setterFlags, annotations, isExtension);
       }
     }
 
     KmProperty toRenamedKmProperty(AppView<AppInfoWithLiveness> appView, NamingLens lens) {
-      KmProperty kmProperty = new KmProperty(flag, name, flagsOf(), flagsOf());
+      KmProperty kmProperty = new KmProperty(flags, name, flagsOf(), flagsOf());
       KmType kmPropertyType = null;
       KmType kmReceiverType = null;
 
@@ -540,8 +552,13 @@
             && renamedPropertyName.equals(name)) {
           renamedPropertyName = renamedGetter.name.toString();
         }
-        kmProperty.setGetterFlags(getter.accessFlags.getAsKotlinFlags());
+        kmProperty.setGetterFlags(getterFlags);
         JvmExtensionsKt.setGetterSignature(kmProperty, toJvmMethodSignature(renamedGetter));
+      } else if (field != null) {
+        // Property without getter.
+        // Even though a getter does not exist, `kotlinc` still set getter flags and use them to
+        // determine when to direct field access v.s. getter calls for property resolution.
+        kmProperty.setGetterFlags(field.accessFlags.getAsKotlinFlags());
       }
 
       criteria = checkSetterCriteria();
@@ -610,8 +627,13 @@
             && renamedPropertyName.equals(name)) {
           renamedPropertyName = renamedSetter.name.toString();
         }
-        kmProperty.setSetterFlags(setter.accessFlags.getAsKotlinFlags());
+        kmProperty.setSetterFlags(setterFlags);
         JvmExtensionsKt.setSetterSignature(kmProperty, toJvmMethodSignature(renamedSetter));
+      } else if (field != null && IS_VAR.invoke(flags)) {
+        // Editable property without setter.
+        // Even though a setter does not exist, `kotlinc` still set setter flags and use them to
+        // determine when to direct field access v.s. setter calls for property resolution.
+        kmProperty.setGetterFlags(field.accessFlags.getAsKotlinFlags());
       }
 
       // If the property type remains null at the end, bail out to synthesize this property.
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinSyntheticClass.java b/src/main/java/com/android/tools/r8/kotlin/KotlinSyntheticClass.java
index 5cc7d2e..1bb17ea 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinSyntheticClass.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinSyntheticClass.java
@@ -12,7 +12,7 @@
 import kotlinx.metadata.jvm.KotlinClassMetadata;
 
 public final class KotlinSyntheticClass extends KotlinInfo<KotlinClassMetadata.SyntheticClass> {
-  // TODO(b/70169921): Once converted to internal data structure, this can be gone.
+  // TODO(b/151194794): Once converted to internal data structure, this can be gone.
   private KotlinClassMetadata.SyntheticClass metadata;
 
   public enum Flavour {
@@ -47,13 +47,13 @@
   void processMetadata(KotlinClassMetadata.SyntheticClass metadata) {
     this.metadata = metadata;
     if (metadata.isLambda()) {
-      // TODO(b/70169921): Use #toKmLambda to store a mutable model if needed.
+      // TODO(b/151194794): Use #toKmLambda to store a mutable model if needed.
     }
   }
 
   @Override
   void rewrite(AppView<AppInfoWithLiveness> appView, NamingLens lens) {
-    // TODO(b/70169921): no idea yet!
+    // TODO(b/151194794): no idea yet!
     assert lens.lookupType(clazz.type, appView.dexItemFactory()) == clazz.type
             || appView.options().enableKotlinMetadataRewritingForRenamedClasses
         : toString();
@@ -61,7 +61,7 @@
 
   @Override
   KotlinClassHeader createHeader() {
-    // TODO(b/70169921): may need to update if `rewrite` is implemented.
+    // TODO(b/151194794): may need to update if `rewrite` is implemented.
     return metadata.getHeader();
   }
 
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinValueParameterInfo.java b/src/main/java/com/android/tools/r8/kotlin/KotlinValueParameterInfo.java
index 0fa9f39..3134b8a 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinValueParameterInfo.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinValueParameterInfo.java
@@ -12,14 +12,14 @@
 
 // Provides access to Kotlin information about value parameter.
 class KotlinValueParameterInfo {
-  // TODO(b/70169921): When to use original param name v.s. when to *not* use?
+  // TODO(b/151193860): When to use original param name v.s. when to *not* use?
   // Original parameter name.
   final String name;
   // Original parameter flag, e.g., has default value.
   final int flag;
   // Indicates whether the formal parameter is originally `vararg`.
   final boolean isVararg;
-  // TODO(b/70169921): Should we treat them as normal annotations? E.g., shrinking and renaming?
+  // TODO(b/151194869): Should we treat them as normal annotations? E.g., shrinking and renaming?
   // Annotations on the type of value parameter.
   final List<KmAnnotation> annotations;
 
diff --git a/src/main/java/com/android/tools/r8/naming/SourceFileRewriter.java b/src/main/java/com/android/tools/r8/naming/SourceFileRewriter.java
index 8f4330c..a5ead29 100644
--- a/src/main/java/com/android/tools/r8/naming/SourceFileRewriter.java
+++ b/src/main/java/com/android/tools/r8/naming/SourceFileRewriter.java
@@ -29,17 +29,26 @@
   public void run() {
     ProguardConfiguration proguardConfiguration = appView.options().getProguardConfiguration();
     String renameSourceFile = proguardConfiguration.getRenameSourceFileAttribute();
+    boolean hasRenameSourceFileAttribute = renameSourceFile != null;
     // Return early if a user wants to keep the current source file attribute as-is.
-    if (renameSourceFile == null && proguardConfiguration.getKeepAttributes().sourceFile) {
+    if (!hasRenameSourceFileAttribute
+        && proguardConfiguration.getKeepAttributes().sourceFile
+        && appView.options().forceProguardCompatibility) {
       return;
     }
-    // Now, the user wants either to remove source file attribute or to rename it.
-    DexString dexRenameSourceFile =
-        renameSourceFile == null
-            ? appView.dexItemFactory().createString("")
-            : appView.dexItemFactory().createString(renameSourceFile);
+    // Now, the user wants either to remove source file attribute or to rename it for non-kept
+    // classes.
+    DexString defaultRenaming = getSourceFileRenaming(proguardConfiguration);
     for (DexClass clazz : appView.appInfo().classes()) {
-      clazz.sourceFile = dexRenameSourceFile;
+      // We only parse sourceFile if -keepattributes SourceFile, but for compat we should add
+      // a source file name, otherwise line positions will not be printed on the JVM or old version
+      // of ART.
+      if (!hasRenameSourceFileAttribute
+          && proguardConfiguration.getKeepAttributes().sourceFile
+          && appView.rootSet().mayNotBeMinified(clazz.type, appView)) {
+        continue;
+      }
+      clazz.sourceFile = defaultRenaming;
       clazz.forEachMethod(encodedMethod -> {
         // Abstract methods do not have code_item.
         if (encodedMethod.shouldNotHaveCode()) {
@@ -66,4 +75,24 @@
       });
     }
   }
+
+  private DexString getSourceFileRenaming(ProguardConfiguration proguardConfiguration) {
+    // If we should not be keeping the source file, null it out.
+    if (!appView.options().forceProguardCompatibility
+        && !proguardConfiguration.getKeepAttributes().sourceFile) {
+      return null;
+    }
+
+    String renamedSourceFileAttribute = proguardConfiguration.getRenameSourceFileAttribute();
+    if (renamedSourceFileAttribute != null) {
+      return appView.dexItemFactory().createString(renamedSourceFileAttribute);
+    }
+
+    // Otherwise, take the smallest size depending on platform. We cannot use NULL since the jvm
+    // and art will write at foo.bar.baz(Unknown Source) without a line-number. Newer version of ART
+    // will report the DEX PC.
+    return appView
+        .dexItemFactory()
+        .createString(appView.options().isGeneratingClassFiles() ? "SourceFile" : "");
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/optimize/ClassAndMemberPublicizer.java b/src/main/java/com/android/tools/r8/optimize/ClassAndMemberPublicizer.java
index b319c6d..4816759 100644
--- a/src/main/java/com/android/tools/r8/optimize/ClassAndMemberPublicizer.java
+++ b/src/main/java/com/android/tools/r8/optimize/ClassAndMemberPublicizer.java
@@ -142,9 +142,7 @@
       lenseBuilder.add(encodedMethod.method);
       accessFlags.promoteToFinal();
       accessFlags.promoteToPublic();
-      // Although the current method became public, it surely has the single virtual target.
-      encodedMethod.method.setSingleVirtualMethodCache(
-          encodedMethod.method.holder, encodedMethod);
+      // The method just became public and is therefore not a library override.
       encodedMethod.setLibraryMethodOverride(OptionalBool.FALSE);
       return true;
     }
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 8a65562..292f616 100644
--- a/src/main/java/com/android/tools/r8/optimize/InvokeSingleTargetExtractor.java
+++ b/src/main/java/com/android/tools/r8/optimize/InvokeSingleTargetExtractor.java
@@ -43,6 +43,11 @@
   }
 
   @Override
+  public boolean registerInitClass(DexType clazz) {
+    return invalid();
+  }
+
+  @Override
   public boolean registerInvokeVirtual(DexMethod method) {
     return setTarget(method, InvokeKind.VIRTUAL);
   }
diff --git a/src/main/java/com/android/tools/r8/retrace/RetraceStackTrace.java b/src/main/java/com/android/tools/r8/retrace/RetraceStackTrace.java
index 89e7c9d..c63a707 100644
--- a/src/main/java/com/android/tools/r8/retrace/RetraceStackTrace.java
+++ b/src/main/java/com/android/tools/r8/retrace/RetraceStackTrace.java
@@ -421,29 +421,29 @@
         List<StackTraceLine> lines,
         String classLoaderName) {
       ClassReference classReference = Reference.classFromTypeName(clazz);
-      retraceBase
-          .retrace(classReference)
-          .lookupMethod(method)
-          .narrowByLine(linePosition)
-          .forEach(
-              methodElement -> {
-                MethodReference methodReference = methodElement.getMethodReference();
-                lines.add(
-                    new AtLine(
-                        startingWhitespace,
-                        at,
-                        classLoaderName,
-                        moduleName,
-                        methodReference.getHolderClass().getTypeName(),
-                        methodReference.getMethodName(),
-                        methodDescriptionFromMethodReference(methodReference, verbose),
-                        retraceBase.retraceSourceFile(
-                            classReference, fileName, methodReference.getHolderClass(), true),
-                        hasLinePosition()
-                            ? methodElement.getOriginalLineNumber(linePosition)
-                            : linePosition,
-                        methodElement.getRetraceMethodResult().isAmbiguous()));
-              });
+      RetraceMethodResult retraceResult = retraceBase.retrace(classReference).lookupMethod(method);
+      if (linePosition != NO_POSITION && linePosition != INVALID_POSITION) {
+        retraceResult = retraceResult.narrowByLine(linePosition);
+      }
+      retraceResult.forEach(
+          methodElement -> {
+            MethodReference methodReference = methodElement.getMethodReference();
+            lines.add(
+                new AtLine(
+                    startingWhitespace,
+                    at,
+                    classLoaderName,
+                    moduleName,
+                    methodReference.getHolderClass().getTypeName(),
+                    methodReference.getMethodName(),
+                    methodDescriptionFromMethodReference(methodReference, verbose),
+                    retraceBase.retraceSourceFile(
+                        classReference, fileName, methodReference.getHolderClass(), true),
+                    hasLinePosition()
+                        ? methodElement.getOriginalLineNumber(linePosition)
+                        : linePosition,
+                    methodElement.getRetraceMethodResult().isAmbiguous()));
+          });
     }
 
     @Override
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 44367e0..7df2fbc 100644
--- a/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
+++ b/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
@@ -44,6 +44,7 @@
 import com.android.tools.r8.utils.ListUtils;
 import com.android.tools.r8.utils.PredicateSet;
 import com.android.tools.r8.utils.SetUtils;
+import com.android.tools.r8.utils.Visibility;
 import com.android.tools.r8.utils.WorkList;
 import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.ImmutableSortedSet;
@@ -165,6 +166,11 @@
   /** Set of const-class references. */
   public final Set<DexType> constClassReferences;
   /**
+   * A map from seen init-class references to the minimum required visibility of the corresponding
+   * static field.
+   */
+  public final Map<DexType, Visibility> initClassReferences;
+  /**
    * All methods and fields whose value *must* never be propagated due to a configuration directive.
    * (testing only).
    */
@@ -184,6 +190,9 @@
 
   final Set<DexType> instantiatedLambdas;
 
+  /* A cache to improve the lookup performance of lookupSingleVirtualTarget */
+  private final SingleTargetLookupCache singleTargetLookupCache = new SingleTargetLookupCache();
+
   // TODO(zerny): Clean up the constructors so we have just one.
   AppInfoWithLiveness(
       DirectMappedDexApplication application,
@@ -225,7 +234,8 @@
       Map<DexField, Int2ReferenceMap<DexField>> switchMaps,
       EnumValueInfoMapCollection enumValueInfoMaps,
       Set<DexType> instantiatedLambdas,
-      Set<DexType> constClassReferences) {
+      Set<DexType> constClassReferences,
+      Map<DexType, Visibility> initClassReferences) {
     super(application);
     this.missingTypes = missingTypes;
     this.liveTypes = liveTypes;
@@ -266,6 +276,7 @@
     this.enumValueInfoMaps = enumValueInfoMaps;
     this.instantiatedLambdas = instantiatedLambdas;
     this.constClassReferences = constClassReferences;
+    this.initClassReferences = initClassReferences;
   }
 
   public AppInfoWithLiveness(
@@ -308,7 +319,8 @@
       Map<DexField, Int2ReferenceMap<DexField>> switchMaps,
       EnumValueInfoMapCollection enumValueInfoMaps,
       Set<DexType> instantiatedLambdas,
-      Set<DexType> constClassReferences) {
+      Set<DexType> constClassReferences,
+      Map<DexType, Visibility> initClassReferences) {
     super(appInfoWithSubtyping);
     this.missingTypes = missingTypes;
     this.liveTypes = liveTypes;
@@ -349,6 +361,7 @@
     this.enumValueInfoMaps = enumValueInfoMaps;
     this.instantiatedLambdas = instantiatedLambdas;
     this.constClassReferences = constClassReferences;
+    this.initClassReferences = initClassReferences;
   }
 
   private AppInfoWithLiveness(AppInfoWithLiveness previous) {
@@ -392,7 +405,8 @@
         previous.switchMaps,
         previous.enumValueInfoMaps,
         previous.instantiatedLambdas,
-        previous.constClassReferences);
+        previous.constClassReferences,
+        previous.initClassReferences);
     copyMetadataFromPrevious(previous);
   }
 
@@ -445,7 +459,8 @@
         previous.switchMaps,
         previous.enumValueInfoMaps,
         previous.instantiatedLambdas,
-        previous.constClassReferences);
+        previous.constClassReferences,
+        previous.initClassReferences);
     copyMetadataFromPrevious(previous);
     assert removedClasses == null || assertNoItemRemoved(previous.pinnedItems, removedClasses);
   }
@@ -494,9 +509,11 @@
     this.switchMaps = switchMaps;
     this.enumValueInfoMaps = enumValueInfoMaps;
     this.constClassReferences = previous.constClassReferences;
+    this.initClassReferences = previous.initClassReferences;
     previous.markObsolete();
   }
 
+  // TODO(b/150736225): Don't disable this assert.
   private boolean dontAssertDefinitionFor = true;
 
   public static AppInfoWithLivenessModifier modifier() {
@@ -504,16 +521,6 @@
   }
 
   @Override
-  public void enableDefinitionForAssert() {
-    dontAssertDefinitionFor = false;
-  }
-
-  @Override
-  public void disableDefinitionForAssert() {
-    dontAssertDefinitionFor = true;
-  }
-
-  @Override
   public DexClass definitionFor(DexType type) {
     DexClass definition = super.definitionFor(type);
     assert dontAssertDefinitionFor
@@ -732,6 +739,10 @@
     return objectAllocationInfoCollection;
   }
 
+  void removeFromSingleTargetLookupCache(DexClass clazz) {
+    singleTargetLookupCache.removeInstantiatedType(clazz.type, this);
+  }
+
   private boolean assertNoItemRemoved(Collection<DexReference> items, Collection<DexType> types) {
     Set<DexType> typeSet = ImmutableSet.copyOf(types);
     for (DexReference item : items) {
@@ -1069,7 +1080,8 @@
         rewriteReferenceKeys(switchMaps, lens::lookupField),
         enumValueInfoMaps.rewrittenWithLens(lens),
         rewriteItems(instantiatedLambdas, lens::lookupType),
-        constClassReferences);
+        rewriteItems(constClassReferences, lens::lookupType),
+        rewriteReferenceKeys(initClassReferences, lens::lookupType));
   }
 
   /**
@@ -1112,31 +1124,6 @@
     }
   }
 
-  private DexEncodedMethod validateSingleVirtualTarget(
-      DexEncodedMethod singleTarget, DexEncodedMethod resolutionResult) {
-    assert resolutionResult.isVirtualMethod();
-
-    if (singleTarget == null || singleTarget == DexEncodedMethod.SENTINEL) {
-      return null;
-    }
-
-    // Art978_virtual_interfaceTest correctly expects an IncompatibleClassChangeError exception
-    // at runtime.
-    if (isInvalidSingleVirtualTarget(singleTarget, resolutionResult)) {
-      return null;
-    }
-
-    return singleTarget;
-  }
-
-  private boolean isInvalidSingleVirtualTarget(
-      DexEncodedMethod singleTarget, DexEncodedMethod resolutionResult) {
-    assert resolutionResult.isVirtualMethod();
-    // Art978_virtual_interfaceTest correctly expects an IncompatibleClassChangeError exception
-    // at runtime.
-    return !singleTarget.accessFlags.isAtLeastAsVisibleAs(resolutionResult.accessFlags);
-  }
-
   /** For mapping invoke virtual instruction to single target method. */
   public DexEncodedMethod lookupSingleVirtualTarget(
       DexMethod method, DexType invocationContext, boolean isInterface) {
@@ -1174,25 +1161,87 @@
       // (it is either primitive or array).
       return null;
     }
+    DexClass initialResolutionHolder = definitionFor(method.holder);
+    if (initialResolutionHolder == null || initialResolutionHolder.isInterface() != isInterface) {
+      return null;
+    }
     DexClass refinedReceiverClass = definitionFor(refinedReceiverType);
     if (refinedReceiverClass == null) {
       // The refined receiver is not defined in the program and we cannot determine the target.
       return null;
     }
+    if (receiverLowerBoundType == null
+        && singleTargetLookupCache.hasCachedItem(refinedReceiverType, method)) {
+      DexEncodedMethod cachedItem =
+          singleTargetLookupCache.getCachedItem(refinedReceiverType, method);
+      return cachedItem;
+    }
     SingleResolutionResult resolution =
-        resolveMethod(method.holder, method, isInterface).asSingleResolution();
+        resolveMethod(initialResolutionHolder, method).asSingleResolution();
     if (resolution == null
         || !resolution.isAccessibleForVirtualDispatchFrom(invocationClass, this)) {
       return null;
     }
     // If the method is modeled, return the resolution.
+    DexEncodedMethod resolvedMethod = resolution.getResolvedMethod();
     if (modeledPredicate.isModeled(resolution.getResolvedHolder().type)) {
       if (resolution.getResolvedHolder().isFinal()
-          || (resolution.getResolvedMethod().isFinal()
-              && resolution.getResolvedMethod().accessFlags.isPublic())) {
-        return resolution.getResolvedMethod();
+          || (resolvedMethod.isFinal() && resolvedMethod.accessFlags.isPublic())) {
+        singleTargetLookupCache.addToCache(refinedReceiverType, method, resolvedMethod);
+        return resolvedMethod;
       }
     }
+    DexEncodedMethod exactTarget =
+        getMethodTargetFromExactRuntimeInformation(
+            refinedReceiverType, receiverLowerBoundType, resolution, refinedReceiverClass);
+    if (exactTarget != null) {
+      // We are not caching single targets here because the cache does not include the
+      // lower bound dimension.
+      return exactTarget == DexEncodedMethod.SENTINEL ? null : exactTarget;
+    }
+    if (refinedReceiverClass.isNotProgramClass()) {
+      // The refined receiver is not defined in the program and we cannot determine the target.
+      singleTargetLookupCache.addToCache(refinedReceiverType, method, null);
+      return null;
+    }
+    DexClass resolvedHolder = resolution.getResolvedHolder();
+    // TODO(b/148769279): Disable lookup single target on lambda's for now.
+    if (resolvedHolder.isInterface()
+        && resolvedHolder.isProgramClass()
+        && hasAnyInstantiatedLambdas(resolvedHolder.asProgramClass())) {
+      singleTargetLookupCache.addToCache(refinedReceiverType, method, null);
+      return null;
+    }
+    DexEncodedMethod singleMethodTarget = null;
+    DexProgramClass refinedLowerBound = null;
+    if (receiverLowerBoundType != null) {
+      DexClass refinedLowerBoundClass = definitionFor(receiverLowerBoundType.getClassType());
+      if (refinedLowerBoundClass != null) {
+        refinedLowerBound = refinedLowerBoundClass.asProgramClass();
+      }
+    }
+    LookupResultSuccess lookupResult =
+        resolution
+            .lookupVirtualDispatchTargets(
+                invocationClass, this, refinedReceiverClass.asProgramClass(), refinedLowerBound)
+            .asLookupResultSuccess();
+    if (lookupResult != null && !lookupResult.isIncomplete()) {
+      LookupTarget singleTarget = lookupResult.getSingleLookupTarget();
+      if (singleTarget != null && singleTarget.isMethodTarget()) {
+        singleMethodTarget = singleTarget.asMethodTarget().getMethod();
+      }
+    }
+    if (receiverLowerBoundType == null) {
+      singleTargetLookupCache.addToCache(refinedReceiverType, method, singleMethodTarget);
+    }
+    return singleMethodTarget;
+  }
+
+  private DexEncodedMethod getMethodTargetFromExactRuntimeInformation(
+      DexType refinedReceiverType,
+      ClassTypeLatticeElement receiverLowerBoundType,
+      SingleResolutionResult resolution,
+      DexClass refinedReceiverClass) {
     // If the lower-bound on the receiver type is the same as the upper-bound, then we have exact
     // runtime type information. In this case, the invoke will dispatch to the resolution result
     // from the runtime type of the receiver.
@@ -1203,7 +1252,7 @@
             resolution.lookupVirtualDispatchTarget(refinedReceiverClass.asProgramClass(), this);
         if (clazzAndMethod == null || isPinned(clazzAndMethod.getMethod().method)) {
           // TODO(b/150640456): We should maybe only consider program methods.
-          return null;
+          return DexEncodedMethod.SENTINEL;
         }
         return clazzAndMethod.getMethod();
       } else {
@@ -1211,56 +1260,16 @@
         // If we resolved to a method on the refined receiver in the library, then we report the
         // method as a single target as well. This is a bit iffy since the library could change
         // implementation, but we use this for library modelling.
-        DexEncodedMethod targetOnReceiver = refinedReceiverClass.lookupVirtualMethod(method);
-        if (targetOnReceiver != null
-            && isOverriding(resolution.getResolvedMethod(), targetOnReceiver)) {
+        DexEncodedMethod resolvedMethod = resolution.getResolvedMethod();
+        DexEncodedMethod targetOnReceiver =
+            refinedReceiverClass.lookupVirtualMethod(resolvedMethod.method);
+        if (targetOnReceiver != null && isOverriding(resolvedMethod, targetOnReceiver)) {
           return targetOnReceiver;
         }
-        return null;
+        return DexEncodedMethod.SENTINEL;
       }
     }
-    if (refinedReceiverClass.isNotProgramClass()) {
-      // The refined receiver is not defined in the program and we cannot determine the target.
-      return null;
-    }
-    DexClass resolvedHolder = resolution.getResolvedHolder();
-    // TODO(b/148769279): Disable lookup single target on lambda's for now.
-    if (resolvedHolder.isInterface()
-        && resolvedHolder.isProgramClass()
-        && hasAnyInstantiatedLambdas(resolvedHolder.asProgramClass())) {
-      return null;
-    }
-
-    if (method.isSingleVirtualMethodCached(refinedReceiverType)) {
-      return method.getSingleVirtualMethodCache(refinedReceiverType);
-    }
-
-    DexProgramClass refinedLowerBound = null;
-    if (receiverLowerBoundType != null) {
-      assert receiverLowerBoundType.isClassType();
-      DexClass refinedLowerBoundClass = definitionFor(receiverLowerBoundType.getClassType());
-      if (refinedLowerBoundClass != null) {
-        refinedLowerBound = refinedLowerBoundClass.asProgramClass();
-      }
-    }
-
-    LookupResultSuccess lookupResult =
-        resolution
-            .lookupVirtualDispatchTargets(
-                invocationClass, this, refinedReceiverClass.asProgramClass(), refinedLowerBound)
-            .asLookupResultSuccess();
-
-    if (lookupResult == null || lookupResult.isIncomplete()) {
-      return null;
-    }
-
-    LookupTarget singleTarget = lookupResult.getSingleLookupTarget();
-    DexEncodedMethod singleMethodTarget = null;
-    if (singleTarget != null && singleTarget.isMethodTarget()) {
-      singleMethodTarget = singleTarget.asMethodTarget().getMethod();
-    }
-    method.setSingleVirtualMethodCache(refinedReceiverType, singleMethodTarget);
-    return singleMethodTarget;
+    return null;
   }
 
   public AppInfoWithLiveness withSwitchMaps(Map<DexField, Int2ReferenceMap<DexField>> switchMaps) {
@@ -1275,12 +1284,6 @@
     return new AppInfoWithLiveness(this, switchMaps, enumValueInfoMaps);
   }
 
-  public void forEachLiveProgramClass(Consumer<DexProgramClass> fn) {
-    for (DexType type : liveTypes) {
-      fn.accept(definitionFor(type).asProgramClass());
-    }
-  }
-
   /**
    * Visit all class definitions of classpath classes that are referenced in the compilation unit.
    *
@@ -1363,14 +1366,32 @@
       if (clazz == null) {
         continue;
       }
-      if (isInstantiatedDirectly(clazz)
-          || isPinned(clazz.type)
-          || hasAnyInstantiatedLambdas(clazz)) {
+      if (isInstantiatedOrPinned(clazz)) {
         subTypeConsumer.accept(clazz);
       }
     }
   }
 
+  public void forEachInstantiatedSubTypeInChain(
+      DexProgramClass refinedReceiverUpperBound,
+      DexProgramClass refinedReceiverLowerBound,
+      Consumer<DexProgramClass> subTypeConsumer,
+      Consumer<LambdaDescriptor> callSiteConsumer) {
+    List<DexProgramClass> subTypes =
+        computeProgramClassRelationChain(refinedReceiverLowerBound, refinedReceiverUpperBound);
+    for (DexProgramClass subType : subTypes) {
+      if (isInstantiatedOrPinned(subType)) {
+        subTypeConsumer.accept(subType);
+      }
+    }
+  }
+
+  private boolean isInstantiatedOrPinned(DexProgramClass clazz) {
+    return isInstantiatedDirectly(clazz)
+        || isPinned(clazz.type)
+        || hasAnyInstantiatedLambdas(clazz);
+  }
+
   public boolean isPinnedNotProgramOrLibraryOverride(DexReference reference) {
     if (isPinned(reference)) {
       return true;
diff --git a/src/main/java/com/android/tools/r8/shaking/AppInfoWithLivenessModifier.java b/src/main/java/com/android/tools/r8/shaking/AppInfoWithLivenessModifier.java
index 75c27fd..99f2d84 100644
--- a/src/main/java/com/android/tools/r8/shaking/AppInfoWithLivenessModifier.java
+++ b/src/main/java/com/android/tools/r8/shaking/AppInfoWithLivenessModifier.java
@@ -36,8 +36,11 @@
     // Instantiated classes.
     ObjectAllocationInfoCollectionImpl objectAllocationInfoCollection =
         appInfo.getMutableObjectAllocationInfoCollection();
-    noLongerInstantiatedClasses.forEach(objectAllocationInfoCollection::markNoLongerInstantiated);
-
+    noLongerInstantiatedClasses.forEach(
+        clazz -> {
+          objectAllocationInfoCollection.markNoLongerInstantiated(clazz);
+          appInfo.removeFromSingleTargetLookupCache(clazz);
+        });
     // Written fields.
     FieldAccessInfoCollectionImpl fieldAccessInfoCollection =
         appInfo.getMutableFieldAccessInfoCollection();
diff --git a/src/main/java/com/android/tools/r8/shaking/ClassInitFieldSynthesizer.java b/src/main/java/com/android/tools/r8/shaking/ClassInitFieldSynthesizer.java
new file mode 100644
index 0000000..60bb9d7
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/shaking/ClassInitFieldSynthesizer.java
@@ -0,0 +1,96 @@
+// 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;
+
+import static com.android.tools.r8.graph.DexProgramClass.asProgramClassOrNull;
+
+import com.android.tools.r8.dex.Constants;
+import com.android.tools.r8.errors.Unreachable;
+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.DexField;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.FieldAccessFlags;
+import com.android.tools.r8.graph.InitClassLens;
+import com.android.tools.r8.utils.ThreadUtils;
+import com.android.tools.r8.utils.Visibility;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+
+public class ClassInitFieldSynthesizer {
+
+  private final AppView<AppInfoWithLiveness> appView;
+  private final DexField clinitField;
+  private final InitClassLens.Builder lensBuilder = InitClassLens.builder();
+
+  public ClassInitFieldSynthesizer(AppView<AppInfoWithLiveness> appView) {
+    this.appView = appView;
+    this.clinitField = appView.dexItemFactory().objectMembers.clinitField;
+  }
+
+  public void run(ExecutorService executorService) throws ExecutionException {
+    ThreadUtils.processItems(
+        appView.appInfo().initClassReferences, this::synthesizeClassInitField, executorService);
+    appView.setInitClassLens(lensBuilder.build());
+  }
+
+  private void synthesizeClassInitField(DexType type, Visibility minimumRequiredVisibility) {
+    DexProgramClass clazz = asProgramClassOrNull(appView.definitionFor(type));
+    if (clazz == null) {
+      assert false;
+      return;
+    }
+    // Use an existing static field if there is one.
+    DexEncodedField encodedClinitField = null;
+    for (DexEncodedField staticField : clazz.staticFields()) {
+      // We need to field to be accessible from the contexts in which it is accessed.
+      if (!isMinimumRequiredVisibility(staticField, minimumRequiredVisibility)) {
+        continue;
+      }
+      // When compiling for dex, we can't use wide fields since we've only allocated a single
+      // register for the out-value of each ClassInit instruction
+      if (staticField.field.type.isWideType()) {
+        continue;
+      }
+      encodedClinitField = staticField;
+      break;
+    }
+    if (encodedClinitField == null) {
+      FieldAccessFlags accessFlags =
+          FieldAccessFlags.fromSharedAccessFlags(
+              Constants.ACC_SYNTHETIC
+                  | Constants.ACC_FINAL
+                  | Constants.ACC_PUBLIC
+                  | Constants.ACC_STATIC);
+      encodedClinitField =
+          new DexEncodedField(
+              appView.dexItemFactory().createField(clazz.type, clinitField.type, clinitField.name),
+              accessFlags,
+              DexAnnotationSet.empty(),
+              null);
+      clazz.appendStaticField(encodedClinitField);
+    }
+    lensBuilder.map(type, encodedClinitField.field);
+  }
+
+  private boolean isMinimumRequiredVisibility(
+      DexEncodedField field, Visibility minimumRequiredVisibility) {
+    if (field.isPublic()) {
+      return true;
+    }
+    switch (minimumRequiredVisibility) {
+      case PROTECTED:
+        return field.isProtected();
+      case PACKAGE_PRIVATE:
+        return field.isPackagePrivate() || field.isProtected();
+      case PUBLIC:
+        return false;
+      default:
+        throw new Unreachable();
+    }
+  }
+}
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 f35fb94..db955c9 100644
--- a/src/main/java/com/android/tools/r8/shaking/DefaultEnqueuerUseRegistry.java
+++ b/src/main/java/com/android/tools/r8/shaking/DefaultEnqueuerUseRegistry.java
@@ -40,6 +40,11 @@
   }
 
   @Override
+  public boolean registerInitClass(DexType clazz) {
+    return enqueuer.traceInitClass(clazz, context);
+  }
+
+  @Override
   public boolean registerInvokeVirtual(DexMethod invokedMethod) {
     return enqueuer.traceInvokeVirtual(invokedMethod, 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 200562d..2bc230c 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -97,6 +97,7 @@
 import com.android.tools.r8.utils.SetUtils;
 import com.android.tools.r8.utils.StringDiagnostic;
 import com.android.tools.r8.utils.Timing;
+import com.android.tools.r8.utils.Visibility;
 import com.android.tools.r8.utils.WorkList;
 import com.google.common.base.Equivalence.Wrapper;
 import com.google.common.collect.ImmutableSet;
@@ -300,6 +301,12 @@
   private final Set<DexType> constClassReferences = Sets.newIdentityHashSet();
 
   /**
+   * A map from seen init-class references to the minimum required visibility of the corresponding
+   * static field.
+   */
+  private final Map<DexType, Visibility> initClassReferences = new IdentityHashMap<>();
+
+  /**
    * A map from annotation classes to annotations that need to be processed should the classes ever
    * become live.
    */
@@ -412,6 +419,9 @@
       reportMissingClass(type);
       return null;
     }
+    if (clazz.isProgramClass()) {
+      return clazz;
+    }
     if (liveNonProgramTypes.add(clazz) && clazz.isLibraryClass()) {
       // TODO(b/149201735): This likely needs to apply to classpath too.
       ensureMethodsContinueToWidenAccess(clazz);
@@ -727,6 +737,65 @@
     return false;
   }
 
+  boolean traceInitClass(DexType type, ProgramMethod currentMethod) {
+    assert type.isClassType();
+
+    Visibility oldMinimumRequiredVisibility = initClassReferences.get(type);
+    if (oldMinimumRequiredVisibility == null) {
+      DexProgramClass clazz = getProgramClassOrNull(type);
+      if (clazz == null) {
+        assert false;
+        return false;
+      }
+
+      initClassReferences.put(
+          type, computeMinimumRequiredVisibilityForInitClassField(type, currentMethod.getHolder()));
+
+      markTypeAsLive(type, classReferencedFromReporter(currentMethod.getMethod()));
+      markDirectAndIndirectClassInitializersAsLive(clazz);
+      return true;
+    }
+
+    if (oldMinimumRequiredVisibility.isPublic()) {
+      return false;
+    }
+
+    Visibility minimumRequiredVisibilityForCurrentMethod =
+        computeMinimumRequiredVisibilityForInitClassField(type, currentMethod.getHolder());
+
+    // There should never be a need to have an InitClass instruction for the enclosing class.
+    assert !minimumRequiredVisibilityForCurrentMethod.isPrivate();
+
+    if (minimumRequiredVisibilityForCurrentMethod.isPublic()) {
+      initClassReferences.put(type, minimumRequiredVisibilityForCurrentMethod);
+      return true;
+    }
+
+    if (oldMinimumRequiredVisibility.isProtected()) {
+      return false;
+    }
+
+    if (minimumRequiredVisibilityForCurrentMethod.isProtected()) {
+      initClassReferences.put(type, minimumRequiredVisibilityForCurrentMethod);
+      return true;
+    }
+
+    assert oldMinimumRequiredVisibility.isPackagePrivate();
+    assert minimumRequiredVisibilityForCurrentMethod.isPackagePrivate();
+    return false;
+  }
+
+  private Visibility computeMinimumRequiredVisibilityForInitClassField(
+      DexType clazz, DexProgramClass context) {
+    if (clazz.isSamePackage(context.type)) {
+      return Visibility.PACKAGE_PRIVATE;
+    }
+    if (appInfo.isStrictSubtypeOf(context.type, clazz)) {
+      return Visibility.PROTECTED;
+    }
+    return Visibility.PUBLIC;
+  }
+
   void traceMethodHandle(
       DexMethodHandle methodHandle, MethodHandleUse use, DexEncodedMethod currentMethod) {
     // If a method handle is not an argument to a lambda metafactory it could flow to a
@@ -2555,7 +2624,8 @@
             SetUtils.mapIdentityHashSet(
                 objectAllocationInfoCollection.unknownInstantiatedInterfaceTypes,
                 DexProgramClass::getType),
-            constClassReferences);
+            constClassReferences,
+            initClassReferences);
     appInfo.markObsolete();
     return appInfoWithLiveness;
   }
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 3a6fcae..15d4e4b 100644
--- a/src/main/java/com/android/tools/r8/shaking/MainDexDirectReferenceTracer.java
+++ b/src/main/java/com/android/tools/r8/shaking/MainDexDirectReferenceTracer.java
@@ -100,6 +100,12 @@
     }
 
     @Override
+    public boolean registerInitClass(DexType clazz) {
+      consumer.accept(clazz);
+      return true;
+    }
+
+    @Override
     public boolean registerInvokeVirtual(DexMethod method) {
       return registerInvoke(method);
     }
diff --git a/src/main/java/com/android/tools/r8/shaking/SingleTargetLookupCache.java b/src/main/java/com/android/tools/r8/shaking/SingleTargetLookupCache.java
new file mode 100644
index 0000000..1691758
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/shaking/SingleTargetLookupCache.java
@@ -0,0 +1,58 @@
+// 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;
+
+import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexType;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+public class SingleTargetLookupCache {
+
+  private Map<DexType, Map<DexMethod, DexEncodedMethod>> cache = new ConcurrentHashMap<>();
+
+  public void addToCache(DexType refinedReceiverType, DexMethod method, DexEncodedMethod target) {
+    assert target != DexEncodedMethod.SENTINEL;
+    Map<DexMethod, DexEncodedMethod> methodCache =
+        cache.computeIfAbsent(refinedReceiverType, ignored -> new ConcurrentHashMap<>());
+    target = target == null ? DexEncodedMethod.SENTINEL : target;
+    assert methodCache.getOrDefault(method, target) == target;
+    methodCache.putIfAbsent(method, target);
+  }
+
+  public void removeInstantiatedType(DexType instantiatedType, AppInfoWithLiveness appInfo) {
+    // Remove all types in the hierarchy related to this type.
+    cache.remove(instantiatedType);
+    DexClass clazz = appInfo.definitionFor(instantiatedType);
+    if (clazz == null) {
+      return;
+    }
+    appInfo.forEachSuperType(clazz, (type, ignore) -> cache.remove(type));
+    appInfo.subtypes(instantiatedType).forEach(cache::remove);
+  }
+
+  public DexEncodedMethod getCachedItem(DexType receiverType, DexMethod method) {
+    Map<DexMethod, DexEncodedMethod> cachedMethods = cache.get(receiverType);
+    if (cachedMethods == null) {
+      return null;
+    }
+    DexEncodedMethod target = cachedMethods.get(method);
+    return target == DexEncodedMethod.SENTINEL ? null : target;
+  }
+
+  public boolean hasCachedItem(DexType receiverType, DexMethod method) {
+    Map<DexMethod, DexEncodedMethod> cachedMethods = cache.get(receiverType);
+    if (cachedMethods == null) {
+      return false;
+    }
+    return cachedMethods.containsKey(method);
+  }
+
+  public void clear() {
+    cache = new ConcurrentHashMap<>();
+  }
+}
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 43110c2..ef0ce63 100644
--- a/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
+++ b/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
@@ -13,7 +13,6 @@
 import com.android.tools.r8.graph.DexApplication;
 import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexClass.FieldSetter;
-import com.android.tools.r8.graph.DexClass.MethodSetter;
 import com.android.tools.r8.graph.DexEncodedField;
 import com.android.tools.r8.graph.DexEncodedMember;
 import com.android.tools.r8.graph.DexEncodedMethod;
@@ -1452,8 +1451,7 @@
     private VerticalClassMergerGraphLense fixupTypeReferences() {
       // Globally substitute merged class types in protos and holders.
       for (DexProgramClass clazz : appInfo.classes()) {
-        fixupMethods(clazz.directMethods(), clazz::setDirectMethod);
-        fixupMethods(clazz.virtualMethods(), clazz::setVirtualMethod);
+        clazz.getMethodCollection().replaceMethods(this::fixupMethod);
         fixupFields(clazz.staticFields(), clazz::setStaticField);
         fixupFields(clazz.instanceFields(), clazz::setInstanceField);
       }
@@ -1467,21 +1465,16 @@
       return lens;
     }
 
-    private void fixupMethods(List<DexEncodedMethod> methods, MethodSetter setter) {
-      if (methods == null) {
-        return;
-      }
-      for (int i = 0; i < methods.size(); i++) {
-        DexEncodedMethod encodedMethod = methods.get(i);
-        DexMethod method = encodedMethod.method;
-        DexMethod newMethod = fixupMethod(method);
-        if (newMethod != method) {
-          if (!lensBuilder.hasOriginalSignatureMappingFor(newMethod)) {
-            lensBuilder.map(method, newMethod).recordMove(method, newMethod);
-          }
-          setter.setMethod(i, encodedMethod.toTypeSubstitutedMethod(newMethod));
+    private DexEncodedMethod fixupMethod(DexEncodedMethod encodedMethod) {
+      DexMethod method = encodedMethod.method;
+      DexMethod newMethod = fixupMethod(method);
+      if (newMethod != method) {
+        if (!lensBuilder.hasOriginalSignatureMappingFor(newMethod)) {
+          lensBuilder.map(method, newMethod).recordMove(method, newMethod);
         }
+        return encodedMethod.toTypeSubstitutedMethod(newMethod);
       }
+      return encodedMethod;
     }
 
     private void fixupFields(List<DexEncodedField> fields, FieldSetter setter) {
@@ -1837,6 +1830,11 @@
     }
 
     @Override
+    public boolean registerInitClass(DexType clazz) {
+      return checkTypeReference(clazz);
+    }
+
+    @Override
     public boolean registerInvokeVirtual(DexMethod method) {
       assert context != null;
       GraphLenseLookupResult lookup =
diff --git a/src/main/java/com/android/tools/r8/utils/DescriptorUtils.java b/src/main/java/com/android/tools/r8/utils/DescriptorUtils.java
index 51ab446..6aca533 100644
--- a/src/main/java/com/android/tools/r8/utils/DescriptorUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/DescriptorUtils.java
@@ -371,6 +371,7 @@
     return 'L' + className.replace(JAVA_PACKAGE_SEPARATOR, INNER_CLASS_SEPARATOR) + ';';
   }
 
+  // TODO(b/151195430): Remove once a new version of kotlinx-metadata is released.
   // Kotlin @Metadata deserialization has plain "kotlin", which will be relocated in r8lib.
   // See b/70169921#comment25 for more details.
   private static String backwardRelocatedName(String name) {
@@ -390,7 +391,7 @@
     kmType.accept(new KmTypeVisitor() {
       @Override
       public void visitClass(String name) {
-        // TODO(b/70169921): Remove this if metadata lib is resilient to namespace relocation.
+        // TODO(b/151195430): Remove this if metadata lib is resilient to namespace relocation.
         //  See b/70169921#comment25 for more details.
         assert descriptor.get() == null : descriptor.get();
         descriptor.set(getDescriptorFromKotlinClassifier(backwardRelocatedName(name)));
@@ -398,7 +399,7 @@
 
       @Override
       public void visitTypeAlias(String name) {
-        // TODO(b/70169921): Remove this if metadata lib is resilient to namespace relocation.
+        // TODO(b/151195430): Remove this if metadata lib is resilient to namespace relocation.
         //  See b/70169921#comment25 for more details.
         assert descriptor.get() == null : descriptor.get();
         descriptor.set(getDescriptorFromKotlinClassifier(backwardRelocatedName(name)));
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 c3df10f..37b38e9 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -32,6 +32,7 @@
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.inspector.internal.InspectorImpl;
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.desugar.DesugaredLibraryConfiguration;
 import com.android.tools.r8.ir.optimize.Inliner;
@@ -58,6 +59,7 @@
 import java.nio.file.Paths;
 import java.util.ArrayList;
 import java.util.Collection;
+import java.util.Collections;
 import java.util.Comparator;
 import java.util.Deque;
 import java.util.HashMap;
@@ -113,6 +115,8 @@
   public DataResourceConsumer dataResourceConsumer;
   public FeatureSplitConfiguration featureSplitConfiguration;
 
+  public List<Consumer<InspectorImpl>> outputInspections = Collections.emptyList();
+
   // Constructor for testing and/or other utilities.
   public InternalOptions() {
     reporter = new Reporter();
@@ -227,6 +231,7 @@
   public boolean applyInliningToInlinee =
       System.getProperty("com.android.tools.r8.applyInliningToInlinee") != null;
   public int applyInliningToInlineeMaxDepth = 0;
+  public boolean enableInliningOfInvokesWithClassInitializationSideEffects = true;
   public boolean enableInliningOfInvokesWithNullableReceivers = true;
   public boolean disableInliningOfLibraryMethodOverrides = true;
   public boolean enableClassInlining = true;
@@ -283,7 +288,7 @@
   public boolean enablePcDebugInfoOutput = false;
 
   // Number of threads to use while processing the dex files.
-  public int numberOfThreads = DETERMINISTIC_DEBUGGING ? 1 : ThreadUtils.NOT_SPECIFIED;
+  public int threadCount = DETERMINISTIC_DEBUGGING ? 1 : ThreadUtils.NOT_SPECIFIED;
   // Print smali disassembly.
   public boolean useSmaliSyntax = false;
   // Verbose output.
@@ -360,6 +365,10 @@
         || getProguardConfiguration().getKeepAttributes().stackMapTable;
   }
 
+  public boolean shouldRerunEnqueuer() {
+    return isShrinking() || isMinifying() || getProguardConfiguration().hasApplyMappingFile();
+  }
+
   public boolean isGeneratingDex() {
     return isGeneratingDexIndexed() || isGeneratingDexFilePerClassFile();
   }
@@ -1220,7 +1229,7 @@
   }
 
   public boolean canUseRequireNonNull() {
-    return isGeneratingClassFiles() || hasMinApi(AndroidApiLevel.K);
+    return isGeneratingDex() && hasMinApi(AndroidApiLevel.K);
   }
 
   public boolean canUseSuppressedExceptions() {
diff --git a/src/main/java/com/android/tools/r8/utils/IterableUtils.java b/src/main/java/com/android/tools/r8/utils/IterableUtils.java
index 64c44b5..ea35dfb 100644
--- a/src/main/java/com/android/tools/r8/utils/IterableUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/IterableUtils.java
@@ -18,4 +18,8 @@
     }
     return -1;
   }
+
+  public static <T> Iterable<T> filter(Iterable<T> methods, Predicate<T> predicate) {
+    return () -> IteratorUtils.filter(methods.iterator(), predicate);
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/utils/OrderedMergingIterator.java b/src/main/java/com/android/tools/r8/utils/OrderedMergingIterator.java
deleted file mode 100644
index 2458a64..0000000
--- a/src/main/java/com/android/tools/r8/utils/OrderedMergingIterator.java
+++ /dev/null
@@ -1,55 +0,0 @@
-// Copyright (c) 2016, the R8 project authors. Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-package com.android.tools.r8.utils;
-
-import com.android.tools.r8.errors.InternalCompilerError;
-import com.android.tools.r8.graph.DexEncodedMember;
-import com.android.tools.r8.graph.DexMember;
-import java.util.Iterator;
-import java.util.List;
-import java.util.NoSuchElementException;
-
-public class OrderedMergingIterator<D extends DexEncodedMember<D, R>, R extends DexMember<D, R>>
-    implements Iterator<D> {
-
-  private final List<D> one;
-  private final List<D> other;
-  private int oneIndex = 0;
-  private int otherIndex = 0;
-
-  public OrderedMergingIterator(List<D> one, List<D> other) {
-    this.one = one;
-    this.other = other;
-  }
-
-  private D getNextChecked(List<D> list, int position) {
-    if (position >= list.size()) {
-      throw new NoSuchElementException();
-    }
-    return list.get(position);
-  }
-
-  @Override
-  public boolean hasNext() {
-    return oneIndex < one.size() || otherIndex < other.size();
-  }
-
-  @Override
-  public D next() {
-    if (oneIndex >= one.size()) {
-      return getNextChecked(other, otherIndex++);
-    }
-    if (otherIndex >= other.size()) {
-      return getNextChecked(one, oneIndex++);
-    }
-    int comparison = one.get(oneIndex).toReference().compareTo(other.get(otherIndex).toReference());
-    if (comparison < 0) {
-      return one.get(oneIndex++);
-    }
-    if (comparison == 0) {
-      throw new InternalCompilerError("Source arrays are not disjoint.");
-    }
-    return other.get(otherIndex++);
-  }
-}
diff --git a/src/main/java/com/android/tools/r8/utils/ThreadUtils.java b/src/main/java/com/android/tools/r8/utils/ThreadUtils.java
index 40232ca..84aa0d0 100644
--- a/src/main/java/com/android/tools/r8/utils/ThreadUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/ThreadUtils.java
@@ -7,6 +7,7 @@
 import java.util.Collection;
 import java.util.Iterator;
 import java.util.List;
+import java.util.Map;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Executors;
@@ -39,6 +40,18 @@
         executorService);
   }
 
+  public static <T, U, E extends Exception> void processItems(
+      Map<T, U> items, ThrowingBiConsumer<T, U, E> consumer, ExecutorService executorService)
+      throws ExecutionException {
+    processItemsWithResults(
+        items.entrySet(),
+        arg -> {
+          consumer.accept(arg.getKey(), arg.getValue());
+          return null;
+        },
+        executorService);
+  }
+
   public static void awaitFutures(Iterable<? extends Future<?>> futures)
       throws ExecutionException {
     Iterator<? extends Future<?>> futureIterator = futures.iterator();
@@ -90,18 +103,23 @@
   static ExecutorService getExecutorServiceForProcessors(int processors) {
     // This heuristic is based on measurements on a 32 core (hyper-threaded) machine.
     int threads = processors <= 2 ? processors : (int) Math.ceil(Integer.min(processors, 16) / 2.0);
+    return getExecutorServiceForThreads(threads);
+  }
+
+  static ExecutorService getExecutorServiceForThreads(int threads) {
+    // Note Executors.newSingleThreadExecutor() is not used when just one thread is used. See
+    // b/67338394.
     return Executors.newWorkStealingPool(threads);
   }
 
   public static ExecutorService getExecutorService(int threads) {
-    // Don't use Executors.newSingleThreadExecutor() when threads == 1, see b/67338394.
     return threads == NOT_SPECIFIED
         ? getExecutorServiceForProcessors(Runtime.getRuntime().availableProcessors())
-        : Executors.newWorkStealingPool(threads);
+        : getExecutorServiceForThreads(threads);
   }
 
   public static ExecutorService getExecutorService(InternalOptions options) {
-    return getExecutorService(options.numberOfThreads);
+    return getExecutorService(options.threadCount);
   }
 
   public static int getNumberOfThreads(ExecutorService service) {
diff --git a/src/main/java/com/android/tools/r8/utils/Visibility.java b/src/main/java/com/android/tools/r8/utils/Visibility.java
new file mode 100644
index 0000000..32ec7ba
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/utils/Visibility.java
@@ -0,0 +1,50 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.utils;
+
+import com.android.tools.r8.errors.Unreachable;
+
+public enum Visibility {
+  PUBLIC,
+  PROTECTED,
+  PRIVATE,
+  PACKAGE_PRIVATE;
+
+  public boolean isPackagePrivate() {
+    return this == PACKAGE_PRIVATE;
+  }
+
+  public boolean isPrivate() {
+    return this == PRIVATE;
+  }
+
+  public boolean isProtected() {
+    return this == PROTECTED;
+  }
+
+  public boolean isPublic() {
+    return this == PUBLIC;
+  }
+
+  @Override
+  public String toString() {
+    switch (this) {
+      case PUBLIC:
+        return "public";
+
+      case PROTECTED:
+        return "protected";
+
+      case PRIVATE:
+        return "private";
+
+      case PACKAGE_PRIVATE:
+        return "package-private";
+
+      default:
+        throw new Unreachable("Unexpected visibility");
+    }
+  }
+}
diff --git a/src/main/keep.txt b/src/main/keep.txt
index b0c58c9..2450f6b 100644
--- a/src/main/keep.txt
+++ b/src/main/keep.txt
@@ -19,7 +19,7 @@
 -keep public class com.android.tools.r8.Version { public static java.lang.String getPreReleaseString(); }
 -keep public class com.android.tools.r8.Version { public static boolean isDevelopmentVersion(); }
 
--keepattributes LineNumberTable, InnerClasses, EnclosingMethod, Exceptions, Signature
+-keepattributes SourceFile, LineNumberTable, InnerClasses, EnclosingMethod, Exceptions, Signature
 
 -keeppackagenames com.android.tools.r8
 
diff --git a/src/test/java/com/android/tools/r8/D8CommandTest.java b/src/test/java/com/android/tools/r8/D8CommandTest.java
index 57fc2ec..8d43d07 100644
--- a/src/test/java/com/android/tools/r8/D8CommandTest.java
+++ b/src/test/java/com/android/tools/r8/D8CommandTest.java
@@ -11,6 +11,7 @@
 import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.assertSame;
 import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
 
 import com.android.sdklib.AndroidVersion;
 import com.android.tools.r8.AssertionsConfiguration.AssertionTransformation;
@@ -24,6 +25,7 @@
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.FileUtils;
+import com.android.tools.r8.utils.ThreadUtils;
 import com.android.tools.r8.utils.ZipUtils;
 import com.google.common.collect.ImmutableList;
 import java.io.IOException;
@@ -592,6 +594,32 @@
         d8Command.getInternalOptions().desugaredLibraryConfiguration.getRewritePrefix().isEmpty());
   }
 
+  @Test
+  public void numThreadsOption() throws Exception {
+    assertEquals(ThreadUtils.NOT_SPECIFIED, parse().getThreadCount());
+    assertEquals(1, parse("--thread-count", "1").getThreadCount());
+    assertEquals(2, parse("--thread-count", "2").getThreadCount());
+    assertEquals(10, parse("--thread-count", "10").getThreadCount());
+  }
+
+  private void numThreadsOptionInvalid(String value) throws Exception {
+    final String expectedErrorContains = "Invalid argument to --thread-count";
+    try {
+      DiagnosticsChecker.checkErrorsContains(
+          expectedErrorContains, handler -> parse(handler, "--thread-count", value));
+      fail("Expected failure");
+    } catch (CompilationFailedException e) {
+      // Expected.
+    }
+  }
+
+  @Test
+  public void numThreadsOptionInvalid() throws Exception {
+    numThreadsOptionInvalid("0");
+    numThreadsOptionInvalid("-1");
+    numThreadsOptionInvalid("two");
+  }
+
   private D8Command parse(String... args) throws CompilationFailedException {
     return D8Command.parse(args, EmbeddedOrigin.INSTANCE).build();
   }
diff --git a/src/test/java/com/android/tools/r8/DiagnosticsChecker.java b/src/test/java/com/android/tools/r8/DiagnosticsChecker.java
index 049cdb7..b62416f 100644
--- a/src/test/java/com/android/tools/r8/DiagnosticsChecker.java
+++ b/src/test/java/com/android/tools/r8/DiagnosticsChecker.java
@@ -6,6 +6,7 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertSame;
 import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
 
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.origin.PathOrigin;
@@ -60,6 +61,7 @@
     DiagnosticsChecker handler = new DiagnosticsChecker();
     try {
       runner.run(handler);
+      fail("Failure expected");
     } catch (CompilationFailedException e) {
       checkContains(snippet, handler.errors);
       throw e;
diff --git a/src/test/java/com/android/tools/r8/L8CommandTest.java b/src/test/java/com/android/tools/r8/L8CommandTest.java
index 060bdbb..705ecff 100644
--- a/src/test/java/com/android/tools/r8/L8CommandTest.java
+++ b/src/test/java/com/android/tools/r8/L8CommandTest.java
@@ -7,6 +7,7 @@
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
 
 import com.android.tools.r8.AssertionsConfiguration.AssertionTransformation;
 import com.android.tools.r8.AssertionsConfiguration.AssertionTransformationScope;
@@ -14,6 +15,7 @@
 import com.android.tools.r8.origin.EmbeddedOrigin;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.ThreadUtils;
 import java.nio.charset.StandardCharsets;
 import java.nio.file.Files;
 import java.nio.file.Path;
@@ -257,7 +259,69 @@
         AssertionTransformation.PASSTHROUGH);
   }
 
+  @Test
+  public void numThreadsOption() throws Exception {
+    assertEquals(
+        ThreadUtils.NOT_SPECIFIED,
+        parse("--desugared-lib", ToolHelper.DESUGAR_LIB_JSON_FOR_TESTING.toString())
+            .getThreadCount());
+    assertEquals(
+        1,
+        parse(
+                "--thread-count",
+                "1",
+                "--desugared-lib",
+                ToolHelper.DESUGAR_LIB_JSON_FOR_TESTING.toString())
+            .getThreadCount());
+    assertEquals(
+        2,
+        parse(
+                "--thread-count",
+                "2",
+                "--desugared-lib",
+                ToolHelper.DESUGAR_LIB_JSON_FOR_TESTING.toString())
+            .getThreadCount());
+    assertEquals(
+        10,
+        parse(
+                "--thread-count",
+                "10",
+                "--desugared-lib",
+                ToolHelper.DESUGAR_LIB_JSON_FOR_TESTING.toString())
+            .getThreadCount());
+  }
+
+  private void numThreadsOptionInvalid(String value) throws Exception {
+    final String expectedErrorContains = "Invalid argument to --thread-count";
+    try {
+      DiagnosticsChecker.checkErrorsContains(
+          expectedErrorContains,
+          handler ->
+              parse(
+                  handler,
+                  "--thread-count",
+                  value,
+                  "--desugared-lib",
+                  ToolHelper.DESUGAR_LIB_JSON_FOR_TESTING.toString()));
+      fail("Expected failure");
+    } catch (CompilationFailedException e) {
+      // Expected.
+    }
+  }
+
+  @Test
+  public void numThreadsOptionInvalid() throws Exception {
+    numThreadsOptionInvalid("0");
+    numThreadsOptionInvalid("-1");
+    numThreadsOptionInvalid("two");
+  }
+
   private L8Command parse(String... args) throws CompilationFailedException {
     return L8Command.parse(args, EmbeddedOrigin.INSTANCE).build();
   }
+
+  private L8Command parse(DiagnosticsHandler handler, String... args)
+      throws CompilationFailedException {
+    return L8Command.parse(args, EmbeddedOrigin.INSTANCE, handler).build();
+  }
 }
diff --git a/src/test/java/com/android/tools/r8/R8CommandTest.java b/src/test/java/com/android/tools/r8/R8CommandTest.java
index 4a703d7..29a9e22 100644
--- a/src/test/java/com/android/tools/r8/R8CommandTest.java
+++ b/src/test/java/com/android/tools/r8/R8CommandTest.java
@@ -9,6 +9,7 @@
 import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.assertSame;
 import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
 
 import com.android.tools.r8.AssertionsConfiguration.AssertionTransformation;
 import com.android.tools.r8.AssertionsConfiguration.AssertionTransformationScope;
@@ -20,6 +21,7 @@
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.FileUtils;
+import com.android.tools.r8.utils.ThreadUtils;
 import com.android.tools.r8.utils.ZipUtils;
 import com.google.common.collect.ImmutableList;
 import java.io.IOException;
@@ -709,6 +711,32 @@
         r8Command.getInternalOptions().desugaredLibraryConfiguration.getRewritePrefix().isEmpty());
   }
 
+  @Test
+  public void numThreadsOption() throws Exception {
+    assertEquals(ThreadUtils.NOT_SPECIFIED, parse().getThreadCount());
+    assertEquals(1, parse("--thread-count", "1").getThreadCount());
+    assertEquals(2, parse("--thread-count", "2").getThreadCount());
+    assertEquals(10, parse("--thread-count", "10").getThreadCount());
+  }
+
+  private void numThreadsOptionInvalid(String value) throws Exception {
+    final String expectedErrorContains = "Invalid argument to --thread-count";
+    try {
+      DiagnosticsChecker.checkErrorsContains(
+          expectedErrorContains, handler -> parse(handler, "--thread-count", value));
+      fail("Expected failure");
+    } catch (CompilationFailedException e) {
+      // Expected.
+    }
+  }
+
+  @Test
+  public void numThreadsOptionInvalid() throws Exception {
+    numThreadsOptionInvalid("0");
+    numThreadsOptionInvalid("-1");
+    numThreadsOptionInvalid("two");
+  }
+
   private R8Command parse(String... args) throws CompilationFailedException {
     return R8Command.parse(args, EmbeddedOrigin.INSTANCE).build();
   }
diff --git a/src/test/java/com/android/tools/r8/R8TestRunResult.java b/src/test/java/com/android/tools/r8/R8TestRunResult.java
index 2ec28b8..9fe91d1 100644
--- a/src/test/java/com/android/tools/r8/R8TestRunResult.java
+++ b/src/test/java/com/android/tools/r8/R8TestRunResult.java
@@ -59,6 +59,16 @@
     return new CodeInspector(app, proguardMap);
   }
 
+  @Override
+  public <E extends Throwable> R8TestRunResult inspectFailure(
+      ThrowingConsumer<CodeInspector, E> consumer) throws IOException, ExecutionException, E {
+    assertFailure();
+    assertNotNull(app);
+    CodeInspector codeInspector = new CodeInspector(app, proguardMap);
+    consumer.accept(codeInspector);
+    return self();
+  }
+
   public <E extends Throwable> R8TestRunResult inspectOriginalStackTrace(
       ThrowingConsumer<StackTrace, E> consumer) throws E {
     consumer.accept(getOriginalStackTrace());
diff --git a/src/test/java/com/android/tools/r8/TestRunResult.java b/src/test/java/com/android/tools/r8/TestRunResult.java
index 46bf0a1..bf3bb9d 100644
--- a/src/test/java/com/android/tools/r8/TestRunResult.java
+++ b/src/test/java/com/android/tools/r8/TestRunResult.java
@@ -142,6 +142,15 @@
     return self();
   }
 
+  public <E extends Throwable> RR inspectFailure(ThrowingConsumer<CodeInspector, E> consumer)
+      throws IOException, ExecutionException, E {
+    assertFailure();
+    assertNotNull(app);
+    CodeInspector inspector = new CodeInspector(app);
+    consumer.accept(inspector);
+    return self();
+  }
+
   public <E extends Throwable> RR inspectStackTrace(ThrowingConsumer<StackTrace, E> consumer)
       throws E {
     consumer.accept(getStackTrace());
diff --git a/src/test/java/com/android/tools/r8/ToolHelper.java b/src/test/java/com/android/tools/r8/ToolHelper.java
index 9c144c8..e184d97 100644
--- a/src/test/java/com/android/tools/r8/ToolHelper.java
+++ b/src/test/java/com/android/tools/r8/ToolHelper.java
@@ -21,6 +21,7 @@
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DirectMappedDexApplication;
 import com.android.tools.r8.graph.GraphLense;
+import com.android.tools.r8.graph.InitClassLens;
 import com.android.tools.r8.naming.NamingLens;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.position.Position;
@@ -248,6 +249,10 @@
         this.shortName = shortName;
       }
 
+      public boolean isDalvik() {
+        return isOlderThanOrEqual(Version.V4_4_4);
+      }
+
       public boolean isLatest() {
         return this == DEFAULT;
       }
@@ -2075,6 +2080,7 @@
         application,
         null,
         GraphLense.getIdentityLense(),
+        InitClassLens.getDefault(),
         NamingLens.getIdentityLens(),
         options,
         null);
diff --git a/src/test/java/com/android/tools/r8/classmerging/InliningAfterStaticClassMergerTest.java b/src/test/java/com/android/tools/r8/classmerging/InliningAfterStaticClassMergerTest.java
index d59f954..e2ee52f 100644
--- a/src/test/java/com/android/tools/r8/classmerging/InliningAfterStaticClassMergerTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/InliningAfterStaticClassMergerTest.java
@@ -8,6 +8,7 @@
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.junit.Assert.assertEquals;
 
+import com.android.tools.r8.NeverPropagateValue;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
@@ -35,6 +36,7 @@
     // Cannot be inlined into TestClass.main() because the static initialization of this class could
     // have side-effects; in order for R8 to be conservative, library classes are treated as if
     // their static initialization could have side-effects.
+    @NeverPropagateValue
     public static String m() {
       return "StaticMergeCandidateA.m()";
     }
@@ -44,6 +46,7 @@
 
     // Can be inlined into TestClass.main() because the static initialization of this class has no
     // side-effects.
+    @NeverPropagateValue
     public static String m() {
       return "StaticMergeCandidateB.m()";
     }
@@ -65,7 +68,7 @@
 
   @Parameters(name = "{0}")
   public static TestParametersCollection data() {
-    return getTestParameters().withAllRuntimes().build();
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
   }
 
   @Test
@@ -82,8 +85,9 @@
             .addKeepMainRule(TestClass.class)
             .addOptionsModification(
                 options -> options.libraryInterfacesMayHaveStaticInitialization = true)
+            .enableMemberValuePropagationAnnotations()
             .noMinification()
-            .setMinApi(parameters.getRuntime())
+            .setMinApi(parameters.getApiLevel())
             .run(parameters.getRuntime(), TestClass.class)
             .assertSuccessWithOutput(expected)
             .inspector();
@@ -94,11 +98,16 @@
             .filter(clazz -> clazz.getOriginalName().contains("StaticMergeCandidate"))
             .collect(Collectors.toList());
     assertEquals(1, classes.size());
-    assertEquals(StaticMergeCandidateA.class.getTypeName(), classes.get(0).getOriginalName());
 
-    // Check that StaticMergeCandidateB.m() has not been moved into StaticMergeCandidateA, because
-    // that would disable inlining of it.
-    assertEquals(1, classes.get(0).allMethods().size());
+    FoundClassSubject clazz = classes.get(0);
+    assertEquals(StaticMergeCandidateA.class.getTypeName(), clazz.getOriginalName());
+
+    // Check that StaticMergeCandidateB.m() has been inlined.
+    assertEquals(0, clazz.allMethods().size());
+
+    // Check that a static field has been synthesized in order to trigger class initialization.
+    assertEquals(1, clazz.allStaticFields().size());
+    assertEquals("$r8$clinit", clazz.allStaticFields().get(0).getOriginalName());
 
     // Check that the test class only has a main method.
     ClassSubject classSubject = inspector.clazz(TestClass.class);
diff --git a/src/test/java/com/android/tools/r8/classmerging/KeptTargetsIncompleteDiamondTest.java b/src/test/java/com/android/tools/r8/classmerging/KeptTargetsIncompleteDiamondTest.java
index 6f2e729..005df04 100644
--- a/src/test/java/com/android/tools/r8/classmerging/KeptTargetsIncompleteDiamondTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/KeptTargetsIncompleteDiamondTest.java
@@ -44,11 +44,12 @@
   private AppView<AppInfoWithLiveness> computeAppViewWithLiveness(
       Class<?> methodToBeKept, Class<?> classToBeKept) throws Exception {
     return computeAppViewWithLiveness(
-        buildClasses(I.class, J.class, K.class, L.class, A.class).build(),
+        buildClasses(I.class, J.class, K.class, L.class, A.class, Main.class).build(),
         factory -> {
           List<ProguardConfigurationRule> rules = new ArrayList<>();
           rules.addAll(buildKeepRuleForClassAndMethods(methodToBeKept, factory));
           rules.addAll(buildKeepRuleForClass(classToBeKept, factory));
+          rules.addAll(buildKeepRuleForClassAndMethods(Main.class, factory));
           return rules;
         });
   }
@@ -265,4 +266,11 @@
   public interface L extends I, K {}
 
   public static class A implements L {}
+
+  public static class Main {
+
+    public static void main(String[] args) {
+      new A();
+    }
+  }
 }
diff --git a/src/test/java/com/android/tools/r8/debug/MinificationTest.java b/src/test/java/com/android/tools/r8/debug/MinificationTest.java
index d1c3ac1..b42d7fa 100644
--- a/src/test/java/com/android/tools/r8/debug/MinificationTest.java
+++ b/src/test/java/com/android/tools/r8/debug/MinificationTest.java
@@ -153,7 +153,7 @@
         checkLine(SOURCE_FILE, 15),
         stepInto(INTELLIJ_FILTER),
         checkMethod(innerClassName, innerMethodName, innerSignature),
-        checkLine(SOURCE_FILE, 8),
+        checkLine(8),
         run());
   }
 
@@ -175,7 +175,7 @@
         breakpoint(innerClassName, innerMethodName, innerSignature),
         run(),
         checkMethod(innerClassName, innerMethodName, innerSignature),
-        checkLine(SOURCE_FILE, 8),
+        checkLine(8),
         run());
   }
 
diff --git a/src/test/java/com/android/tools/r8/debuginfo/DexPcWithDebugInfoForOverloadedMethodsTestRunner.java b/src/test/java/com/android/tools/r8/debuginfo/DexPcWithDebugInfoForOverloadedMethodsTestRunner.java
index b344944..11dff7b 100644
--- a/src/test/java/com/android/tools/r8/debuginfo/DexPcWithDebugInfoForOverloadedMethodsTestRunner.java
+++ b/src/test/java/com/android/tools/r8/debuginfo/DexPcWithDebugInfoForOverloadedMethodsTestRunner.java
@@ -23,12 +23,13 @@
 import com.android.tools.r8.ToolHelper.DexVm.Version;
 import com.android.tools.r8.references.Reference;
 import com.android.tools.r8.retrace.RetraceMethodResult;
+import com.android.tools.r8.shaking.ProguardKeepAttributes;
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.android.tools.r8.utils.codeinspector.FoundMethodSubject;
 import com.android.tools.r8.utils.codeinspector.InstructionSubject;
-import com.android.tools.r8.utils.codeinspector.Matchers.InlinePosition;
+import com.android.tools.r8.utils.codeinspector.Matchers.LinePosition;
 import com.android.tools.r8.utils.codeinspector.MethodSubject;
 import com.google.common.collect.ImmutableList;
 import java.io.IOException;
@@ -41,6 +42,7 @@
 @RunWith(Parameterized.class)
 public class DexPcWithDebugInfoForOverloadedMethodsTestRunner extends TestBase {
 
+  private static final String FILENAME_INLINE = "InlineFunction.kt";
   private static final Class<?> MAIN = DexPcWithDebugInfoForOverloadedMethodsTest.class;
   private static final int MINIFIED_LINE_POSITION = 6;
   private static final String EXPECTED = "java.lang.RuntimeException: overloaded(String)42";
@@ -66,6 +68,7 @@
         .addKeepMainRule(MAIN)
         .addKeepMethodRules(MAIN, "void overloaded(...)")
         .addKeepAttributeLineNumberTable()
+        .addKeepAttributes(ProguardKeepAttributes.SOURCE_FILE)
         .setMinApi(parameters.getApiLevel())
         .addOptionsModification(
             internalOptions -> {
@@ -86,18 +89,20 @@
               MethodSubject throwingSubject =
                   codeInspector.clazz(MAIN).method("void", "overloaded", "java.lang.String");
               assertThat(throwingSubject, isPresent());
-              InlinePosition inlineStack =
-                  InlinePosition.stack(
-                      InlinePosition.create(
+              LinePosition inlineStack =
+                  LinePosition.stack(
+                      LinePosition.create(
                           Reference.methodFromMethod(
                               MAIN.getDeclaredMethod("inlinee", String.class)),
                           MINIFIED_LINE_POSITION,
-                          11),
-                      InlinePosition.create(
+                          11,
+                          FILENAME_INLINE),
+                      LinePosition.create(
                           Reference.methodFromMethod(
                               MAIN.getDeclaredMethod("overloaded", String.class)),
                           MINIFIED_LINE_POSITION,
-                          20));
+                          20,
+                          FILENAME_INLINE));
               RetraceMethodResult retraceResult =
                   throwingSubject
                       .streamInstructions()
diff --git a/src/test/java/com/android/tools/r8/debuginfo/EnsureNoDebugInfoEmittedForPcOnlyTestRunner.java b/src/test/java/com/android/tools/r8/debuginfo/EnsureNoDebugInfoEmittedForPcOnlyTestRunner.java
index c47f16f..ae533a4 100644
--- a/src/test/java/com/android/tools/r8/debuginfo/EnsureNoDebugInfoEmittedForPcOnlyTestRunner.java
+++ b/src/test/java/com/android/tools/r8/debuginfo/EnsureNoDebugInfoEmittedForPcOnlyTestRunner.java
@@ -22,7 +22,7 @@
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.android.tools.r8.utils.codeinspector.InstructionSubject;
-import com.android.tools.r8.utils.codeinspector.Matchers.InlinePosition;
+import com.android.tools.r8.utils.codeinspector.Matchers.LinePosition;
 import com.android.tools.r8.utils.codeinspector.MethodSubject;
 import com.google.common.collect.ImmutableList;
 import java.io.IOException;
@@ -35,6 +35,7 @@
 @RunWith(Parameterized.class)
 public class EnsureNoDebugInfoEmittedForPcOnlyTestRunner extends TestBase {
 
+  private static final String FILENAME_MAIN = "EnsureNoDebugInfoEmittedForPcOnlyTest.java";
   private static final Class<?> MAIN = EnsureNoDebugInfoEmittedForPcOnlyTest.class;
   private static final int INLINED_DEX_PC = 32;
 
@@ -75,20 +76,23 @@
         .inspectStackTrace(
             (stackTrace, codeInspector) -> {
               MethodSubject mainSubject = codeInspector.clazz(MAIN).uniqueMethodWithName("main");
-              InlinePosition inlineStack =
-                  InlinePosition.stack(
-                      InlinePosition.create(
+              LinePosition inlineStack =
+                  LinePosition.stack(
+                      LinePosition.create(
                           Reference.methodFromMethod(MAIN.getDeclaredMethod("a")),
                           INLINED_DEX_PC,
-                          11),
-                      InlinePosition.create(
+                          11,
+                          FILENAME_MAIN),
+                      LinePosition.create(
                           Reference.methodFromMethod(MAIN.getDeclaredMethod("b")),
                           INLINED_DEX_PC,
-                          18),
-                      InlinePosition.create(
+                          18,
+                          FILENAME_MAIN),
+                      LinePosition.create(
                           mainSubject.asFoundMethodSubject().asMethodReference(),
                           INLINED_DEX_PC,
-                          23));
+                          23,
+                          FILENAME_MAIN));
               RetraceMethodResult retraceResult =
                   mainSubject
                       .streamInstructions()
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/r8ondex/R8CompiledThroughDexTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/r8ondex/R8CompiledThroughDexTest.java
index 02623a8..b1a6811 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/r8ondex/R8CompiledThroughDexTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/r8ondex/R8CompiledThroughDexTest.java
@@ -5,7 +5,7 @@
 package com.android.tools.r8.desugar.desugaredlibrary.r8ondex;
 
 import static junit.framework.TestCase.assertEquals;
-import static junit.framework.TestCase.assertTrue;
+import static org.junit.Assert.assertTrue;
 
 import com.android.tools.r8.R8;
 import com.android.tools.r8.TestParameters;
diff --git a/src/test/java/com/android/tools/r8/dex/DebugByteCodeWriterTest.java b/src/test/java/com/android/tools/r8/dex/DebugByteCodeWriterTest.java
index 75ffea7..eeedbab 100644
--- a/src/test/java/com/android/tools/r8/dex/DebugByteCodeWriterTest.java
+++ b/src/test/java/com/android/tools/r8/dex/DebugByteCodeWriterTest.java
@@ -8,7 +8,9 @@
 import com.android.tools.r8.graph.DexDebugInfo;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexString;
+import com.android.tools.r8.graph.InitClassLens;
 import com.android.tools.r8.graph.ObjectToOffsetMapping;
+import com.android.tools.r8.naming.NamingLens;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.Reporter;
 import java.util.Collections;
@@ -21,6 +23,8 @@
     return new ObjectToOffsetMapping(
         DexApplication.builder(new InternalOptions(new DexItemFactory(), new Reporter()), null)
             .build(),
+        NamingLens.getIdentityLens(),
+        InitClassLens.getDefault(),
         Collections.emptyList(),
         Collections.emptyList(),
         Collections.emptyList(),
diff --git a/src/test/java/com/android/tools/r8/dex/JumboStringProcessing.java b/src/test/java/com/android/tools/r8/dex/JumboStringProcessing.java
index 666cb87..945f3c2 100644
--- a/src/test/java/com/android/tools/r8/dex/JumboStringProcessing.java
+++ b/src/test/java/com/android/tools/r8/dex/JumboStringProcessing.java
@@ -25,7 +25,6 @@
 import com.android.tools.r8.graph.DexString;
 import com.android.tools.r8.graph.MethodAccessFlags;
 import com.android.tools.r8.graph.ParameterAnnotationsList;
-import com.android.tools.r8.naming.NamingLens;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
@@ -43,7 +42,6 @@
   public void branching() {
     DexItemFactory factory = new DexItemFactory();
     DexString string = factory.createString("turn into jumbo");
-    factory.sort(NamingLens.getIdentityLens());
     Instruction[] instructions = buildInstructions(string, false);
     DexCode code = jumboStringProcess(factory, string, instructions);
     Instruction[] rewrittenInstructions = code.instructions;
@@ -60,7 +58,6 @@
   public void branching2() {
     DexItemFactory factory = new DexItemFactory();
     DexString string = factory.createString("turn into jumbo");
-    factory.sort(NamingLens.getIdentityLens());
     Instruction[] instructions = buildInstructions(string, true);
     DexCode code = jumboStringProcess(factory, string, instructions);
     Instruction[] rewrittenInstructions = code.instructions;
@@ -142,7 +139,6 @@
 
     DexItemFactory factory = inspector.getFactory();
     DexString string = factory.createString("view must have a tag");
-    factory.sort(NamingLens.getIdentityLens());
     DexCode code = jumboStringProcess(factory, string, instructions);
     Instruction[] rewrittenInstructions = code.instructions;
     assertEquals(289, countJumboStrings(rewrittenInstructions));
diff --git a/src/test/java/com/android/tools/r8/dex/SharedClassWritingTest.java b/src/test/java/com/android/tools/r8/dex/SharedClassWritingTest.java
index 8542a76..817abcd 100644
--- a/src/test/java/com/android/tools/r8/dex/SharedClassWritingTest.java
+++ b/src/test/java/com/android/tools/r8/dex/SharedClassWritingTest.java
@@ -24,6 +24,7 @@
 import com.android.tools.r8.graph.DexTypeList;
 import com.android.tools.r8.graph.DirectMappedDexApplication;
 import com.android.tools.r8.graph.GraphLense;
+import com.android.tools.r8.graph.InitClassLens;
 import com.android.tools.r8.graph.MethodAccessFlags;
 import com.android.tools.r8.graph.ParameterAnnotationsList;
 import com.android.tools.r8.naming.NamingLens;
@@ -155,6 +156,7 @@
             options,
             null,
             GraphLense.getIdentityLense(),
+            InitClassLens.getDefault(),
             NamingLens.getIdentityLens(),
             null);
     ExecutorService executorService = ThreadUtils.getExecutorService(options);
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/EnumUnboxingArrayTest.java b/src/test/java/com/android/tools/r8/enumunboxing/EnumUnboxingArrayTest.java
new file mode 100644
index 0000000..7d6f2ac
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/enumunboxing/EnumUnboxingArrayTest.java
@@ -0,0 +1,174 @@
+// 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.R8TestCompileResult;
+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 EnumUnboxingArrayTest extends EnumUnboxingTestBase {
+
+  private static final Class<?>[] FAILURES = {
+    EnumVarArgs.class,
+    EnumArrayReadWriteNoEscape.class,
+    EnumArrayReadWrite.class,
+    Enum2DimArrayReadWrite.class
+  };
+
+  private final TestParameters parameters;
+  private final boolean enumValueOptimization;
+  private final KeepRule enumKeepRules;
+
+  @Parameters(name = "{0} valueOpt: {1} keep: {2}")
+  public static List<Object[]> data() {
+    return enumUnboxingTestParameters();
+  }
+
+  public EnumUnboxingArrayTest(
+      TestParameters parameters, boolean enumValueOptimization, KeepRule enumKeepRules) {
+    this.parameters = parameters;
+    this.enumValueOptimization = enumValueOptimization;
+    this.enumKeepRules = enumKeepRules;
+  }
+
+  @Test
+  public void testEnumUnboxingFailure() throws Exception {
+    R8TestCompileResult compile =
+        testForR8(parameters.getBackend())
+            .addInnerClasses(EnumUnboxingArrayTest.class)
+            .addKeepMainRules(FAILURES)
+            .enableInliningAnnotations()
+            .enableNeverClassInliningAnnotations()
+            .addKeepRules(enumKeepRules.getKeepRule())
+            .addOptionsModification(opt -> enableEnumOptions(opt, enumValueOptimization))
+            .allowDiagnosticInfoMessages()
+            .setMinApi(parameters.getApiLevel())
+            .compile();
+    for (Class<?> failure : FAILURES) {
+      R8TestRunResult run =
+          compile
+              .inspectDiagnosticMessages(
+                  m ->
+                      assertEnumIsBoxed(
+                          failure.getDeclaredClasses()[0], failure.getSimpleName(), m))
+              .run(parameters.getRuntime(), failure)
+              .assertSuccess();
+      assertLines2By2Correct(run.getStdOut());
+    }
+  }
+
+  static class EnumVarArgs {
+
+    public static void main(String[] args) {
+      System.out.println(sum(MyEnum.A));
+      System.out.println(1);
+      System.out.println(sum(MyEnum.B, MyEnum.C));
+      System.out.println(2);
+    }
+
+    @NeverInline
+    public static int sum(MyEnum... args) {
+      return args.length;
+    }
+
+    @NeverClassInline
+    enum MyEnum {
+      A,
+      B,
+      C;
+    }
+  }
+
+  static class EnumArrayReadWriteNoEscape {
+
+    public static void main(String[] args) {
+      MyEnum[] myEnums = new MyEnum[2];
+      myEnums[1] = MyEnum.C;
+      System.out.println(myEnums[1].ordinal());
+      System.out.println(2);
+      System.out.println(myEnums[0]);
+      System.out.println("null");
+      myEnums[0] = MyEnum.B;
+      System.out.println(myEnums.length);
+      System.out.println(2);
+    }
+
+    @NeverClassInline
+    enum MyEnum {
+      A,
+      B,
+      C;
+    }
+  }
+
+  static class EnumArrayReadWrite {
+
+    public static void main(String[] args) {
+      MyEnum[] myEnums = getArray();
+      System.out.println(myEnums[1].ordinal());
+      System.out.println(2);
+      System.out.println(myEnums[0]);
+      System.out.println("null");
+      myEnums[0] = MyEnum.B;
+      System.out.println(sum(myEnums));
+      System.out.println(2);
+    }
+
+    @NeverInline
+    public static MyEnum[] getArray() {
+      MyEnum[] myEnums = new MyEnum[2];
+      myEnums[1] = MyEnum.C;
+      return myEnums;
+    }
+
+    @NeverInline
+    public static int sum(MyEnum[] args) {
+      return args.length;
+    }
+
+    @NeverClassInline
+    enum MyEnum {
+      A,
+      B,
+      C;
+    }
+  }
+
+  static class Enum2DimArrayReadWrite {
+
+    public static void main(String[] args) {
+      MyEnum[][] myEnums = getArray();
+      System.out.println(myEnums[1][1].ordinal());
+      System.out.println(2);
+      System.out.println(myEnums[0][0].ordinal());
+      System.out.println(1);
+    }
+
+    @NeverInline
+    public static MyEnum[][] getArray() {
+      MyEnum[][] myEnums = new MyEnum[2][2];
+      myEnums[1][1] = MyEnum.C;
+      myEnums[1][0] = MyEnum.A;
+      myEnums[0][0] = MyEnum.B;
+      myEnums[0][1] = MyEnum.A;
+      return myEnums;
+    }
+
+    @NeverClassInline
+    enum MyEnum {
+      A,
+      B,
+      C;
+    }
+  }
+}
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 1b56b1b..e03d352 100644
--- a/src/test/java/com/android/tools/r8/enumunboxing/EnumUnboxingTestBase.java
+++ b/src/test/java/com/android/tools/r8/enumunboxing/EnumUnboxingTestBase.java
@@ -73,6 +73,7 @@
   }
 
   void assertEnumIsUnboxed(Class<?> enumClass, String testName, TestDiagnosticMessages m) {
+    assertTrue(enumClass.isEnum());
     Diagnostic diagnostic = m.getInfos().get(0);
     assertTrue(diagnostic.getDiagnosticMessage().startsWith("Unboxed enums"));
     assertTrue(
@@ -81,6 +82,7 @@
   }
 
   void assertEnumIsBoxed(Class<?> enumClass, String testName, TestDiagnosticMessages m) {
+    assertTrue(enumClass.isEnum());
     Diagnostic diagnostic = m.getInfos().get(1);
     assertTrue(diagnostic.getDiagnosticMessage().startsWith("Boxed enums"));
     assertTrue(
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/FieldPutEnumUnboxingTest.java b/src/test/java/com/android/tools/r8/enumunboxing/FieldPutEnumUnboxingTest.java
index f2ed08b..eedc8f1 100644
--- a/src/test/java/com/android/tools/r8/enumunboxing/FieldPutEnumUnboxingTest.java
+++ b/src/test/java/com/android/tools/r8/enumunboxing/FieldPutEnumUnboxingTest.java
@@ -81,10 +81,12 @@
       C
     }
 
-    MyEnum e;
+    MyEnum e = null;
 
     public static void main(String[] args) {
       InstanceFieldPut fieldPut = new InstanceFieldPut();
+      System.out.println(fieldPut.e == null);
+      System.out.println("true");
       fieldPut.setA();
       System.out.println(fieldPut.e.ordinal());
       System.out.println(0);
@@ -113,9 +115,11 @@
       C
     }
 
-    static MyEnum e;
+    static MyEnum e = null;
 
     public static void main(String[] args) {
+      System.out.println(StaticFieldPut.e == null);
+      System.out.println("true");
       setA();
       System.out.println(StaticFieldPut.e.ordinal());
       System.out.println(0);
diff --git a/src/test/java/com/android/tools/r8/graph/GenericSignatureTest.java b/src/test/java/com/android/tools/r8/graph/GenericSignatureTest.java
index 775f030..33321eb 100644
--- a/src/test/java/com/android/tools/r8/graph/GenericSignatureTest.java
+++ b/src/test/java/com/android/tools/r8/graph/GenericSignatureTest.java
@@ -37,26 +37,30 @@
     AndroidApp app =
         testForD8()
             .debug()
-            .addProgramClassesAndInnerClasses(A.class, B.class, CY.class, CYY.class)
+            .addProgramClassesAndInnerClasses(
+                GenericSignatureTestClassA.class,
+                GenericSignatureTestClassB.class,
+                GenericSignatureTestClassCY.class,
+                GenericSignatureTestClassCYY.class)
             .compile()
             .app;
     AppView<AppInfoWithLiveness> appView = computeAppViewWithLiveness(app);
     DexItemFactory factory = appView.dexItemFactory();
     CodeInspector inspector = new CodeInspector(appView.appInfo().app());
 
-    ClassSubject a = inspector.clazz(A.class);
+    ClassSubject a = inspector.clazz(GenericSignatureTestClassA.class);
     assertThat(a, isPresent());
-    ClassSubject y = inspector.clazz(A.Y.class);
+    ClassSubject y = inspector.clazz(GenericSignatureTestClassA.Y.class);
     assertThat(y, isPresent());
-    ClassSubject yy = inspector.clazz(A.Y.YY.class);
+    ClassSubject yy = inspector.clazz(GenericSignatureTestClassA.Y.YY.class);
     assertThat(yy, isPresent());
-    ClassSubject zz = inspector.clazz(A.Y.ZZ.class);
+    ClassSubject zz = inspector.clazz(GenericSignatureTestClassA.Y.ZZ.class);
     assertThat(zz, isPresent());
-    ClassSubject b = inspector.clazz(B.class);
+    ClassSubject b = inspector.clazz(GenericSignatureTestClassB.class);
     assertThat(b, isPresent());
-    ClassSubject cy = inspector.clazz(CY.class);
+    ClassSubject cy = inspector.clazz(GenericSignatureTestClassCY.class);
     assertThat(cy, isPresent());
-    ClassSubject cyy = inspector.clazz(CYY.class);
+    ClassSubject cyy = inspector.clazz(GenericSignatureTestClassCYY.class);
     assertThat(cyy, isPresent());
 
     DexEncodedMethod method;
@@ -252,7 +256,7 @@
 // and then extended a bit to explore more details, e.g., MethodTypeSignature.
 //
 
-class A<T> {
+class GenericSignatureTestClassA<T> {
   class Y {
 
     class YY {}
@@ -260,7 +264,7 @@
     class ZZ<TT> extends YY {
       public YY yy;
 
-      YY newYY(B... bs) {
+      YY newYY(GenericSignatureTestClassB... bs) {
         return new YY();
       }
 
@@ -301,8 +305,9 @@
   }
 }
 
-class B<T extends A<T>> {}
+class GenericSignatureTestClassB<T extends GenericSignatureTestClassA<T>> {}
 
-class CY<T extends A<T>.Y> {}
+class GenericSignatureTestClassCY<T extends GenericSignatureTestClassA<T>.Y> {}
 
-class CYY<T extends A<T>.Y> extends CY<T> {}
+class GenericSignatureTestClassCYY<T extends GenericSignatureTestClassA<T>.Y>
+    extends GenericSignatureTestClassCY<T> {}
diff --git a/src/test/java/com/android/tools/r8/graph/initializedclasses/InitializedClassesInInstanceMethodsTest.java b/src/test/java/com/android/tools/r8/graph/initializedclasses/InitializedClassesInInstanceMethodsTest.java
index 43015bf..9251ab8 100644
--- a/src/test/java/com/android/tools/r8/graph/initializedclasses/InitializedClassesInInstanceMethodsTest.java
+++ b/src/test/java/com/android/tools/r8/graph/initializedclasses/InitializedClassesInInstanceMethodsTest.java
@@ -30,7 +30,8 @@
 
   @Parameters(name = "{1}, enabled: {0}")
   public static List<Object[]> data() {
-    return buildParameters(BooleanUtils.values(), getTestParameters().withAllRuntimes().build());
+    return buildParameters(
+        BooleanUtils.values(), getTestParameters().withAllRuntimesAndApiLevels().build());
   }
 
   public InitializedClassesInInstanceMethodsTest(
@@ -53,7 +54,7 @@
         .allowAccessModification()
         .enableNeverClassInliningAnnotations()
         .enableInliningAnnotations()
-        .setMinApi(parameters.getRuntime())
+        .setMinApi(parameters.getApiLevel())
         .compile()
         .inspect(this::verifyOutput)
         .run(parameters.getRuntime(), TestClass.class)
@@ -70,13 +71,15 @@
     assertThat(outerClassSubject.uniqueMethodWithName("world"), not(isPresent()));
     assertThat(outerClassSubject.uniqueMethodWithName("exclamationMark"), not(isPresent()));
 
-    int numberOfExpectedAccessibilityBridges =
-        enableInitializedClassesInInstanceMethodsAnalysis ? 0 : 3;
+    int numberOfExpectedAccessibilityBridges = 0;
     assertEquals(
         numberOfExpectedAccessibilityBridges,
         outerClassSubject
             .allMethods(method -> method.getOriginalName().contains("access$"))
             .size());
+    assertEquals(
+        !enableInitializedClassesInInstanceMethodsAnalysis,
+        outerClassSubject.uniqueFieldWithName("$r8$clinit").isPresent());
 
     ClassSubject aClassSubject = inspector.clazz(Outer.A.class);
     assertThat(aClassSubject, isPresent());
diff --git a/src/test/java/com/android/tools/r8/inspection/FieldFlagsAndValueInspectionTest.java b/src/test/java/com/android/tools/r8/inspection/FieldFlagsAndValueInspectionTest.java
new file mode 100644
index 0000000..d64be27
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/inspection/FieldFlagsAndValueInspectionTest.java
@@ -0,0 +1,112 @@
+// 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.inspection;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.inspector.Inspector;
+import com.android.tools.r8.inspector.ValueInspector;
+import com.android.tools.r8.references.Reference;
+import com.android.tools.r8.utils.StringUtils;
+import java.util.Optional;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class FieldFlagsAndValueInspectionTest extends TestBase {
+
+  static final String EXPECTED = StringUtils.lines("30");
+
+  private final TestParameters parameters;
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public FieldFlagsAndValueInspectionTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testD8() throws Exception {
+    if (parameters.isCfRuntime()) {
+      testForJvm()
+          .addProgramClasses(TestClass.class)
+          .run(parameters.getRuntime(), TestClass.class)
+          .assertSuccessWithOutput(EXPECTED);
+      return;
+    }
+    testForD8()
+        .addProgramClasses(TestClass.class)
+        .setMinApi(parameters.getApiLevel())
+        .apply(b -> b.getBuilder().addOutputInspection(inspector -> inspection(inspector, false)))
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutput(EXPECTED);
+    assertFound();
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    testForR8(parameters.getBackend())
+        .addProgramClasses(TestClass.class)
+        .addKeepClassAndMembersRules(TestClass.class)
+        .setMinApi(parameters.getApiLevel())
+        .apply(b -> b.getBuilder().addOutputInspection(inspector -> inspection(inspector, true)))
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutput(EXPECTED);
+    assertFound();
+  }
+
+  private int foundFields = 0;
+
+  private void inspection(Inspector inspector, boolean isR8) {
+    inspector.forEachClass(
+        classInspector -> {
+          classInspector.forEachField(
+              fieldInspector -> {
+                foundFields++;
+                String name = fieldInspector.getFieldReference().getFieldName();
+                assertEquals(name.contains("s"), fieldInspector.isStatic());
+                assertEquals(name.contains("f"), fieldInspector.isFinal());
+                assertEquals(Reference.INT, fieldInspector.getFieldReference().getFieldType());
+                Optional<ValueInspector> value = fieldInspector.getInitialValue();
+                if (fieldInspector.isStatic() && fieldInspector.isFinal()) {
+                  // The static final 'sfi' is static initialized to 2.
+                  assertTrue(value.isPresent());
+                  assertEquals(2, value.get().asIntValue().getIntValue());
+                } else if (fieldInspector.isStatic()) {
+                  // The static 'si' is default initialized to 0 and clinit sets it to 4.
+                  // R8 optimizes that to directly set 4.
+                  assertTrue(value.isPresent());
+                  assertEquals(isR8 ? 4 : 0, value.get().asIntValue().getIntValue());
+                } else {
+                  assertFalse(value.isPresent());
+                }
+              });
+        });
+  }
+
+  private void assertFound() {
+    assertEquals(4, foundFields);
+  }
+
+  static class TestClass {
+    public static final int sfi = 2;
+    public static int si = 4;
+    public final int fi = 8;
+    public int i = 16;
+
+    public static void main(String[] args) {
+      TestClass obj = new TestClass();
+      System.out.println(obj.sfi + obj.si + obj.fi + obj.i);
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/inspection/FieldValueTypesInspectionTest.java b/src/test/java/com/android/tools/r8/inspection/FieldValueTypesInspectionTest.java
new file mode 100644
index 0000000..0031885
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/inspection/FieldValueTypesInspectionTest.java
@@ -0,0 +1,196 @@
+// 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.inspection;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.inspector.Inspector;
+import com.android.tools.r8.inspector.ValueInspector;
+import com.android.tools.r8.references.FieldReference;
+import com.android.tools.r8.references.Reference;
+import com.android.tools.r8.utils.StringUtils;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class FieldValueTypesInspectionTest extends TestBase {
+
+  static final String EXPECTED =
+      StringUtils.lines(
+          "" + TestClass.z,
+          "" + TestClass.b,
+          "" + TestClass.c,
+          "" + TestClass.s,
+          "" + TestClass.i,
+          "" + TestClass.j,
+          "" + TestClass.f,
+          "" + TestClass.d,
+          "foo");
+
+  private final TestParameters parameters;
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public FieldValueTypesInspectionTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testD8() throws Exception {
+    if (parameters.isCfRuntime()) {
+      testForJvm()
+          .addProgramClasses(TestClass.class)
+          .run(parameters.getRuntime(), TestClass.class)
+          .assertSuccessWithOutput(EXPECTED);
+      return;
+    }
+    testForD8()
+        .addProgramClasses(TestClass.class)
+        .setMinApi(parameters.getApiLevel())
+        .apply(b -> b.getBuilder().addOutputInspection(this::inspection))
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutput(EXPECTED);
+    assertFound();
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    testForR8(parameters.getBackend())
+        .addProgramClasses(TestClass.class)
+        .addKeepClassAndMembersRules(TestClass.class)
+        .setMinApi(parameters.getApiLevel())
+        .apply(b -> b.getBuilder().addOutputInspection(this::inspection))
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutput(EXPECTED);
+    assertFound();
+  }
+
+  private int foundFields = 0;
+
+  private void inspection(Inspector inspector) {
+    inspector.forEachClass(
+        classInspector -> {
+          classInspector.forEachField(
+              fieldInspector -> {
+                foundFields++;
+                FieldReference reference = fieldInspector.getFieldReference();
+                ValueInspector value = fieldInspector.getInitialValue().get();
+                assertEquals(reference.getFieldType(), value.getTypeReference());
+                String name = reference.getFieldName();
+                boolean isBoolean = name.equals("z");
+                boolean isByte = name.equals("b");
+                boolean isChar = name.equals("c");
+                boolean isShort = name.equals("s");
+                boolean isInt = name.equals("i");
+                boolean isLong = name.equals("j");
+                boolean isFloat = name.equals("f");
+                boolean isDouble = name.equals("d");
+                boolean isString = name.equals("str");
+                assertEquals(isBoolean, value.isBooleanValue());
+                assertEquals(isByte, value.isByteValue());
+                assertEquals(isChar, value.isCharValue());
+                assertEquals(isShort, value.isShortValue());
+                assertEquals(isInt, value.isIntValue());
+                assertEquals(isLong, value.isLongValue());
+                assertEquals(isFloat, value.isFloatValue());
+                assertEquals(isDouble, value.isDoubleValue());
+                assertEquals(isString, value.isStringValue());
+                if (isBoolean) {
+                  assertEquals(Reference.BOOL, reference.getFieldType());
+                  assertEquals(TestClass.z, value.asBooleanValue().getBooleanValue());
+                } else {
+                  assertNull(value.asBooleanValue());
+                }
+                if (isByte) {
+                  assertEquals(Reference.BYTE, reference.getFieldType());
+                  assertEquals(TestClass.b, value.asByteValue().getByteValue());
+                } else {
+                  assertNull(value.asByteValue());
+                }
+                if (isChar) {
+                  assertEquals(Reference.CHAR, reference.getFieldType());
+                  assertEquals(TestClass.c, value.asCharValue().getCharValue());
+                } else {
+                  assertNull(value.asCharValue());
+                }
+                if (isShort) {
+                  assertEquals(Reference.SHORT, reference.getFieldType());
+                  assertEquals(TestClass.s, value.asShortValue().getShortValue());
+                } else {
+                  assertNull(value.asShortValue());
+                }
+                if (isInt) {
+                  assertEquals(Reference.INT, reference.getFieldType());
+                  assertEquals(TestClass.i, value.asIntValue().getIntValue());
+                } else {
+                  assertNull(value.asIntValue());
+                }
+                if (isLong) {
+                  assertEquals(Reference.LONG, reference.getFieldType());
+                  assertEquals(TestClass.j, value.asLongValue().getLongValue());
+                } else {
+                  assertNull(value.asLongValue());
+                }
+                if (isFloat) {
+                  assertEquals(Reference.FLOAT, reference.getFieldType());
+                  assertEquals(
+                      Float.floatToRawIntBits(TestClass.f),
+                      Float.floatToRawIntBits(value.asFloatValue().getFloatValue()));
+                } else {
+                  assertNull(value.asFloatValue());
+                }
+                if (isDouble) {
+                  assertEquals(Reference.DOUBLE, reference.getFieldType());
+                  assertEquals(
+                      Double.doubleToRawLongBits(TestClass.d),
+                      Double.doubleToRawLongBits(value.asDoubleValue().getDoubleValue()));
+                } else {
+                  assertNull(value.asDoubleValue());
+                }
+                if (isString) {
+                  assertEquals(Reference.classFromClass(String.class), reference.getFieldType());
+                  assertEquals(TestClass.str, value.asStringValue().getStringValue());
+                } else {
+                  assertNull(value.asStringValue());
+                }
+              });
+        });
+  }
+
+  private void assertFound() {
+    assertEquals(9, foundFields);
+  }
+
+  static class TestClass {
+    public static final boolean z = true;
+    public static final byte b = 2;
+    public static final char c = 4;
+    public static final short s = 8;
+    public static final int i = 16;
+    public static final long j = 32L;
+    public static final float f = 64.1F;
+    public static final double d = 128.1D;
+    public static final String str = "foo";
+
+    public static void main(String[] args) {
+      System.out.println(z);
+      System.out.println(b);
+      System.out.println(c);
+      System.out.println(s);
+      System.out.println(i);
+      System.out.println(j);
+      System.out.println(f);
+      System.out.println(d);
+      System.out.println(str);
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/inspection/InspectionApiTest.java b/src/test/java/com/android/tools/r8/inspection/InspectionApiTest.java
new file mode 100644
index 0000000..5c5a6e3
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/inspection/InspectionApiTest.java
@@ -0,0 +1,104 @@
+// 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.inspection;
+
+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.inspector.Inspector;
+import com.android.tools.r8.references.ClassReference;
+import com.android.tools.r8.references.FieldReference;
+import com.android.tools.r8.references.MethodReference;
+import com.android.tools.r8.references.Reference;
+import com.android.tools.r8.utils.StringUtils;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class InspectionApiTest extends TestBase {
+
+  static final String EXPECTED = StringUtils.lines("Hello, world");
+
+  private final TestParameters parameters;
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public InspectionApiTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testD8() throws Exception {
+    if (parameters.isCfRuntime()) {
+      testForJvm()
+          .addProgramClasses(TestClass.class)
+          .run(parameters.getRuntime(), TestClass.class)
+          .assertSuccessWithOutput(EXPECTED);
+      return;
+    }
+    testForD8()
+        .addProgramClasses(TestClass.class)
+        .setMinApi(parameters.getApiLevel())
+        .apply(b -> b.getBuilder().addOutputInspection(this::inspection))
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutput(EXPECTED);
+    assertFound();
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    testForR8(parameters.getBackend())
+        .addProgramClasses(TestClass.class)
+        .addKeepClassAndMembersRules(TestClass.class)
+        .setMinApi(parameters.getApiLevel())
+        .apply(b -> b.getBuilder().addOutputInspection(this::inspection))
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutput(EXPECTED);
+    assertFound();
+  }
+
+  ClassReference foundClass = null;
+  FieldReference foundField = null;
+  MethodReference foundMethod = null;
+
+  private void inspection(Inspector inspector) {
+    inspector.forEachClass(
+        classInspector -> {
+          foundClass = classInspector.getClassReference();
+          classInspector.forEachField(
+              fieldInspector -> {
+                foundField = fieldInspector.getFieldReference();
+              });
+          classInspector.forEachMethod(
+              methodInspector -> {
+                // Ignore clinit (which is removed in R8).
+                if (!methodInspector.getMethodReference().getMethodName().equals("<clinit>")) {
+                  foundMethod = methodInspector.getMethodReference();
+                }
+              });
+        });
+  }
+
+  private void assertFound() throws Exception {
+    assertEquals(Reference.classFromClass(TestClass.class), foundClass);
+    assertEquals(Reference.fieldFromField(TestClass.class.getDeclaredField("foo")), foundField);
+    assertEquals(
+        Reference.methodFromMethod(TestClass.class.getDeclaredMethod("main", String[].class)),
+        foundMethod);
+  }
+
+  static class TestClass {
+    public static int foo = 42;
+
+    public static void main(String[] args) {
+      System.out.println("Hello, world");
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/internal/R8GMSCoreDeterministicTest.java b/src/test/java/com/android/tools/r8/internal/R8GMSCoreDeterministicTest.java
index a16962d..cba792d 100644
--- a/src/test/java/com/android/tools/r8/internal/R8GMSCoreDeterministicTest.java
+++ b/src/test/java/com/android/tools/r8/internal/R8GMSCoreDeterministicTest.java
@@ -41,7 +41,7 @@
               // For this test just do random shuffle.
               options.testing.irOrdering = NondeterministicIROrdering.getInstance();
               // Only use one thread to process to process in the order decided by the callback.
-              options.numberOfThreads = 1;
+              options.threadCount = 1;
               // Ignore the missing classes.
               options.ignoreMissingClasses = true;
               // Store the generated Proguard map.
diff --git a/src/test/java/com/android/tools/r8/internal/YouTubeV1419TreeShakeJarVerificationTest.java b/src/test/java/com/android/tools/r8/internal/YouTubeV1419TreeShakeJarVerificationTest.java
index 48acfdb..97b4db0 100644
--- a/src/test/java/com/android/tools/r8/internal/YouTubeV1419TreeShakeJarVerificationTest.java
+++ b/src/test/java/com/android/tools/r8/internal/YouTubeV1419TreeShakeJarVerificationTest.java
@@ -5,6 +5,7 @@
 
 import static com.android.tools.r8.ToolHelper.isLocalDevelopment;
 import static com.android.tools.r8.ToolHelper.shouldRunSlowTests;
+import static org.hamcrest.CoreMatchers.containsString;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assume.assumeTrue;
 
@@ -13,6 +14,7 @@
 import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.android.tools.r8.utils.codeinspector.analysis.ProtoApplicationStats;
 import org.junit.Test;
@@ -29,7 +31,7 @@
 
   @Parameters(name = "{0}")
   public static TestParametersCollection data() {
-    return getTestParameters().withDexRuntimes().build();
+    return getTestParameters().withNoneRuntime().build();
   }
 
   public YouTubeV1419TreeShakeJarVerificationTest(TestParameters parameters) {
@@ -43,11 +45,21 @@
     assumeTrue(isLocalDevelopment());
     assumeTrue(shouldRunSlowTests());
 
+    LibrarySanitizer librarySanitizer =
+        new LibrarySanitizer(temp).addProguardConfigurationFiles(getKeepRuleFiles()).sanitize();
+
     R8TestCompileResult compileResult =
-        testForR8(parameters.getBackend())
-            .addKeepRuleFiles(getKeepRuleFiles())
+        testForR8(Backend.DEX)
+            .addKeepRuleFiles(librarySanitizer.getSanitizedProguardConfiguration())
+            .addLibraryFiles(librarySanitizer.getSanitizedLibrary())
+            .addMainDexRuleFiles(getMainDexRuleFiles())
+            .allowDiagnosticMessages()
             .allowUnusedProguardConfigurationRules()
-            .compile();
+            .setMinApi(AndroidApiLevel.H_MR2)
+            .compile()
+            .assertAllInfoMessagesMatch(
+                containsString("Proguard configuration rule does not match anything"))
+            .assertAllWarningMessagesMatch(containsString("Ignoring option:"));
 
     if (ToolHelper.isLocalDevelopment()) {
       DexItemFactory dexItemFactory = new DexItemFactory();
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 8d73fad..3b3473a 100644
--- a/src/test/java/com/android/tools/r8/ir/IrInjectionTestBase.java
+++ b/src/test/java/com/android/tools/r8/ir/IrInjectionTestBase.java
@@ -44,7 +44,6 @@
 
   protected DexApplication buildApplication(AndroidApp input, InternalOptions options) {
     try {
-      options.itemFactory.resetSortedIndices();
       return new ApplicationReader(input, options, Timing.empty()).read();
     } catch (IOException | ExecutionException e) {
       throw new RuntimeException(e);
diff --git a/src/test/java/com/android/tools/r8/ir/analysis/sideeffect/SingletonClassInitializerPatternCannotBePostponedTest.java b/src/test/java/com/android/tools/r8/ir/analysis/sideeffect/SingletonClassInitializerPatternCannotBePostponedTest.java
index d29753b..7bc227a 100644
--- a/src/test/java/com/android/tools/r8/ir/analysis/sideeffect/SingletonClassInitializerPatternCannotBePostponedTest.java
+++ b/src/test/java/com/android/tools/r8/ir/analysis/sideeffect/SingletonClassInitializerPatternCannotBePostponedTest.java
@@ -5,6 +5,7 @@
 package com.android.tools.r8.ir.analysis.sideeffect;
 
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.CoreMatchers.not;
 import static org.hamcrest.MatcherAssert.assertThat;
 
 import com.android.tools.r8.TestBase;
@@ -47,9 +48,9 @@
     ClassSubject classSubject = inspector.clazz(A.class);
     assertThat(classSubject, isPresent());
 
-    // A.inlineable() cannot be inlined because it should trigger the class initialization of A,
-    // which should trigger the class initialization of B, which will print "Hello".
-    assertThat(classSubject.uniqueMethodWithName("inlineable"), isPresent());
+    // The field A.INSTANCE has been accessed to allow inlining of A.inlineable().
+    assertThat(classSubject.uniqueFieldWithName("INSTANCE"), isPresent());
+    assertThat(classSubject.uniqueMethodWithName("inlineable"), not(isPresent()));
   }
 
   static class TestClass {
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/R8InliningTest.java b/src/test/java/com/android/tools/r8/ir/optimize/R8InliningTest.java
index a9385a6..3b77ead 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/R8InliningTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/R8InliningTest.java
@@ -15,7 +15,6 @@
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.origin.Origin;
-import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.FileUtils;
 import com.android.tools.r8.utils.ZipUtils;
@@ -50,7 +49,8 @@
 
   @Parameters(name = "{1}, allow access modification: {0}")
   public static Collection<Object[]> data() {
-    return buildParameters(BooleanUtils.values(), getTestParameters().withAllRuntimes().build());
+    return buildParameters(
+        BooleanUtils.values(), getTestParameters().withAllRuntimesAndApiLevels().build());
   }
 
   private final boolean allowAccessModification;
@@ -105,7 +105,7 @@
       commandBuilder.setProguardMapOutputPath(mapFile);
     }
     if (parameters.isDexRuntime()) {
-      commandBuilder.setMinApiLevel(AndroidApiLevel.M.getLevel());
+      commandBuilder.setMinApiLevel(parameters.getApiLevel().getLevel());
     }
     if (allowAccessModification) {
       commandBuilder.addProguardConfiguration(
@@ -136,10 +136,19 @@
     if (parameters.isDexRuntime()) {
       output =
           ToolHelper.runArtNoVerificationErrors(
-              outputDir.resolve(DEFAULT_DEX_FILENAME).toString(), "inlining.Inlining");
+              Collections.singletonList(outputDir.resolve(DEFAULT_DEX_FILENAME).toString()),
+              "inlining.Inlining",
+              builder -> {},
+              parameters.getRuntime().asDex().getVm());
     } else {
       assert parameters.isCfRuntime();
-      output = ToolHelper.runJavaNoVerify(outputDir, "inlining.Inlining").stdout;
+      output =
+          ToolHelper.runJava(
+                  parameters.getRuntime().asCf(),
+                  Collections.singletonList("-noverify"),
+                  Collections.singletonList(outputDir),
+                  "inlining.Inlining")
+              .stdout;
     }
 
     // Compare result with Java to make sure we have the same behavior.
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/UnusedReturnValueTest.java b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/UnusedReturnValueTest.java
new file mode 100644
index 0000000..2a2bb9e
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/UnusedReturnValueTest.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.classinliner;
+
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class UnusedReturnValueTest extends TestBase {
+
+  private final TestParameters parameters;
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public UnusedReturnValueTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void test() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(UnusedReturnValueTest.class)
+        .addKeepMainRule(TestClass.class)
+        .enableInliningAnnotations()
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutputLines("Hello world!");
+  }
+
+  static class TestClass {
+
+    public static void main(String[] args) {
+      Builder builder = new Builder();
+      helper(builder);
+      System.out.println(builder.build());
+    }
+
+    @NeverInline
+    static Builder helper(Builder builder) {
+      builder.message = "Hello world!";
+      if (System.currentTimeMillis() > 0) {
+        return builder;
+      }
+      return null;
+    }
+  }
+
+  static class Builder {
+
+    String message;
+
+    String build() {
+      return message;
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/ifs/IfThrowNullPointerExceptionTest.java b/src/test/java/com/android/tools/r8/ir/optimize/ifs/IfThrowNullPointerExceptionTest.java
index 2264a29..e679e80 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/ifs/IfThrowNullPointerExceptionTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/ifs/IfThrowNullPointerExceptionTest.java
@@ -7,6 +7,7 @@
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assume.assumeTrue;
 
@@ -17,10 +18,11 @@
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.Instruction;
 import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.BooleanUtils;
+import com.android.tools.r8.utils.StringUtils;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.android.tools.r8.utils.codeinspector.MethodSubject;
-import com.google.common.collect.ImmutableList;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
@@ -41,6 +43,15 @@
   }
 
   @Test
+  public void testJVM() throws Exception {
+    assumeTrue(parameters.isCfRuntime());
+    testForJvm()
+        .addTestClasspath()
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutput(getExpectedStdout());
+  }
+
+  @Test
   public void testD8() throws Exception {
     assumeTrue(parameters.isDexRuntime());
     testForD8()
@@ -50,7 +61,7 @@
         .compile()
         .inspect(this::inspect)
         .run(parameters.getRuntime(), TestClass.class)
-        .assertSuccessWithOutputLines("Caught NPE", "Caught NPE");
+        .assertSuccessWithOutput(getExpectedStdout());
   }
 
   @Test
@@ -62,55 +73,104 @@
         .compile()
         .inspect(this::inspect)
         .run(parameters.getRuntime(), TestClass.class)
-        .assertSuccessWithOutputLines("Caught NPE", "Caught NPE");
+        .assertSuccessWithOutput(getExpectedStdout());
   }
 
   private void inspect(CodeInspector inspector) {
     ClassSubject classSubject = inspector.clazz(TestClass.class);
     assertThat(classSubject, isPresent());
+    inspectMethod(inspector, classSubject, "testThrowNPE", false, true);
+    inspectMethod(inspector, classSubject, "testThrowNPEWithMessage", true, canUseRequireNonNull());
+    inspectMethod(inspector, classSubject, "testThrowNull", false, true);
+  }
 
-    for (String methodName : ImmutableList.of("testThrowNPE", "testThrowNull")) {
-      MethodSubject methodSubject = classSubject.uniqueMethodWithName(methodName);
-      assertThat(methodSubject, isPresent());
+  private void inspectMethod(
+      CodeInspector inspector,
+      ClassSubject classSubject,
+      String methodName,
+      boolean isNPEWithMessage,
+      boolean shouldBeOptimized) {
+    MethodSubject methodSubject = classSubject.uniqueMethodWithName(methodName);
+    assertThat(methodSubject, isPresent());
 
-      IRCode code = methodSubject.buildIR();
+    IRCode code = methodSubject.buildIR();
+    if (shouldBeOptimized) {
       assertEquals(1, code.blocks.size());
 
       BasicBlock entryBlock = code.entryBlock();
-      assertEquals(3, entryBlock.getInstructions().size());
+      assertEquals(
+          3 + BooleanUtils.intValue(isNPEWithMessage), entryBlock.getInstructions().size());
       assertTrue(entryBlock.getInstructions().getFirst().isArgument());
       assertTrue(entryBlock.getInstructions().getLast().isReturn());
 
-      Instruction nullCheckInstruction = entryBlock.getInstructions().get(1);
-      if (parameters.isDexRuntime() && parameters.getApiLevel().isLessThan(AndroidApiLevel.K)) {
+      Instruction nullCheckInstruction =
+          entryBlock.getInstructions().get(1 + BooleanUtils.intValue(isNPEWithMessage));
+      if (canUseRequireNonNull()) {
+        assertTrue(nullCheckInstruction.isInvokeStatic());
+        if (isNPEWithMessage) {
+          assertEquals(
+              inspector.getFactory().objectsMethods.requireNonNullWithMessage,
+              nullCheckInstruction.asInvokeStatic().getInvokedMethod());
+        } else {
+          assertEquals(
+              inspector.getFactory().objectsMethods.requireNonNull,
+              nullCheckInstruction.asInvokeStatic().getInvokedMethod());
+        }
+      } else {
+        assertFalse(isNPEWithMessage);
         assertTrue(nullCheckInstruction.isInvokeVirtual());
         assertEquals(
-            "java.lang.Class java.lang.Object.getClass()",
-            nullCheckInstruction.asInvokeVirtual().getInvokedMethod().toSourceString());
-      } else {
-        assertTrue(nullCheckInstruction.isInvokeStatic());
-        assertEquals(
-            "java.lang.Object java.util.Objects.requireNonNull(java.lang.Object)",
-            nullCheckInstruction.asInvokeStatic().getInvokedMethod().toSourceString());
+            inspector.getFactory().objectMembers.getClass,
+            nullCheckInstruction.asInvokeVirtual().getInvokedMethod());
       }
+    } else {
+      assertEquals(3, code.blocks.size());
     }
   }
 
+  private String getExpectedStdout() {
+    if (parameters.isCfRuntime() || canUseRequireNonNull() || isDalvik()) {
+      return StringUtils.lines("Caught NPE: null", "Caught NPE: x was null", "Caught NPE: null");
+    }
+    return StringUtils.lines(
+        "Caught NPE: Attempt to invoke virtual method 'java.lang.Class java.lang.Object.getClass()'"
+            + " on a null object reference",
+        "Caught NPE: x was null",
+        "Caught NPE: Attempt to invoke virtual method 'java.lang.Class java.lang.Object.getClass()'"
+            + " on a null object reference");
+  }
+
+  private boolean canUseRequireNonNull() {
+    return parameters.isDexRuntime()
+        && parameters.getApiLevel().isGreaterThanOrEqualTo(AndroidApiLevel.K);
+  }
+
+  private boolean isDalvik() {
+    return parameters.isDexRuntime()
+        && parameters.getRuntime().asDex().getVm().getVersion().isDalvik();
+  }
+
   static class TestClass {
 
     public static void main(String[] args) {
       testThrowNPE(new Object());
+      testThrowNPEWithMessage(new Object());
       testThrowNull(new Object());
 
       try {
         testThrowNPE(null);
       } catch (NullPointerException e) {
-        System.out.println("Caught NPE");
+        System.out.println("Caught NPE: " + e.getMessage());
+      }
+      try {
+        testThrowNPEWithMessage(null);
+      } catch (NullPointerException e) {
+        System.out.println("Caught NPE: " + e.getMessage());
       }
       try {
         testThrowNull(null);
       } catch (NullPointerException e) {
-        System.out.println("Caught NPE");
+        System.out.println("Caught NPE: " + e.getMessage());
       }
     }
 
@@ -120,6 +180,12 @@
       }
     }
 
+    static void testThrowNPEWithMessage(Object x) {
+      if (x == null) {
+        throw new NullPointerException("x was null");
+      }
+    }
+
     static void testThrowNull(Object x) {
       if (x == null) {
         throw null;
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/inliner/InlineInvokeWithNullableReceiverTest.java b/src/test/java/com/android/tools/r8/ir/optimize/inliner/InlineInvokeWithNullableReceiverTest.java
index d8b26ba..dce7b2d 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/inliner/InlineInvokeWithNullableReceiverTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/inliner/InlineInvokeWithNullableReceiverTest.java
@@ -68,8 +68,7 @@
     assertThat(methodSubject, isPresent());
 
     // A `throw` instruction should have been synthesized into main().
-    if (parameters.isCfRuntime()
-        || parameters.getApiLevel().isGreaterThanOrEqualTo(AndroidApiLevel.K)) {
+    if (canUseRequireNonNull()) {
       assertTrue(methodSubject.streamInstructions().anyMatch(InstructionSubject::isInvokeStatic));
     } else {
       assertTrue(
@@ -92,6 +91,11 @@
     assertThat(otherClassSubject.uniqueMethodWithName("m"), not(isPresent()));
   }
 
+  private boolean canUseRequireNonNull() {
+    return parameters.isDexRuntime()
+        && parameters.getApiLevel().isGreaterThanOrEqualTo(AndroidApiLevel.K);
+  }
+
   static class TestClass {
 
     public static void main(String[] args) {
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/inliner/InliningAfterClassInitializationTest.java b/src/test/java/com/android/tools/r8/ir/optimize/inliner/InliningAfterClassInitializationTest.java
index 24ffad1..f9081c2 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/inliner/InliningAfterClassInitializationTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/inliner/InliningAfterClassInitializationTest.java
@@ -221,6 +221,8 @@
         .addInnerClasses(InliningAfterClassInitializationTest.class)
         .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.P))
         .addKeepMainRule(mainClass)
+        .addOptionsModification(
+            options -> options.enableInliningOfInvokesWithClassInitializationSideEffects = false)
         .enableConstantArgumentAnnotations()
         .enableInliningAnnotations()
         .setMinApi(parameters.getApiLevel())
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/inliner/InliningFromCurrentClassTest.java b/src/test/java/com/android/tools/r8/ir/optimize/inliner/InliningFromCurrentClassTest.java
index 239b944..a7aa579 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/inliner/InliningFromCurrentClassTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/inliner/InliningFromCurrentClassTest.java
@@ -4,7 +4,7 @@
 
 package com.android.tools.r8.ir.optimize.inliner;
 
-import static com.android.tools.r8.utils.codeinspector.CodeMatchers.invokesMethod;
+import static com.android.tools.r8.utils.codeinspector.CodeMatchers.accessesField;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.hamcrest.core.IsNot.not;
@@ -12,14 +12,30 @@
 import com.android.tools.r8.NeverInline;
 import com.android.tools.r8.NeverMerge;
 import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
 import 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.MethodSubject;
 import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
 
+@RunWith(Parameterized.class)
 public class InliningFromCurrentClassTest extends TestBase {
 
+  private final TestParameters parameters;
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public InliningFromCurrentClassTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
   @Test
   public void test() throws Exception {
     String expectedOutput =
@@ -29,17 +45,18 @@
             "In A.inlineable1()",
             "In B.inlineable2()",
             "In C.<clinit>()",
-            "In C.notInlineable()");
+            "In C.inlineableWithInitClass()");
 
     testForJvm().addTestClasspath().run(TestClass.class).assertSuccessWithOutput(expectedOutput);
 
     CodeInspector inspector =
-        testForR8(Backend.DEX)
+        testForR8(parameters.getBackend())
             .addInnerClasses(InliningFromCurrentClassTest.class)
             .addKeepMainRule(TestClass.class)
             .enableInliningAnnotations()
             .enableMergeAnnotations()
-            .run(TestClass.class)
+            .setMinApi(parameters.getApiLevel())
+            .run(parameters.getRuntime(), TestClass.class)
             .assertSuccessWithOutput(expectedOutput)
             .inspector();
 
@@ -52,18 +69,19 @@
     ClassSubject classC = inspector.clazz(C.class);
     assertThat(classC, isPresent());
 
+    MethodSubject testMethod = classB.uniqueMethodWithName("test");
+    assertThat(testMethod, isPresent());
+
     MethodSubject inlineable1Method = classA.uniqueMethodWithName("inlineable1");
     assertThat(inlineable1Method, not(isPresent()));
 
     MethodSubject inlineable2Method = classB.uniqueMethodWithName("inlineable2");
     assertThat(inlineable2Method, not(isPresent()));
 
-    MethodSubject notInlineableMethod = classC.uniqueMethodWithName("notInlineable");
-    assertThat(notInlineableMethod, isPresent());
-
-    MethodSubject testMethod = classB.uniqueMethodWithName("test");
-    assertThat(testMethod, isPresent());
-    assertThat(testMethod, invokesMethod(notInlineableMethod));
+    MethodSubject inlineableWithInitClassMethod =
+        classC.uniqueMethodWithName("inlineableWithInitClass");
+    assertThat(inlineableWithInitClassMethod, not(isPresent()));
+    assertThat(testMethod, accessesField(classC.uniqueFieldWithName("$r8$clinit")));
   }
 
   static class TestClass {
@@ -96,7 +114,7 @@
     static void test() {
       A.inlineable1();
       B.inlineable2();
-      C.notInlineable();
+      C.inlineableWithInitClass();
     }
 
     static void inlineable2() {
@@ -110,8 +128,8 @@
       System.out.println("In C.<clinit>()");
     }
 
-    static void notInlineable() {
-      System.out.println("In C.notInlineable()");
+    static void inlineableWithInitClass() {
+      System.out.println("In C.inlineableWithInitClass()");
     }
   }
 }
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/MemberValuePropagationWithClassInitializationTest.java b/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/MemberValuePropagationWithClassInitializationTest.java
index adc1f0b..50e52e2 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/MemberValuePropagationWithClassInitializationTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/MemberValuePropagationWithClassInitializationTest.java
@@ -66,7 +66,7 @@
     assertThat(bClassSubject, isPresent());
 
     MethodSubject methodSubject = bClassSubject.uniqueMethodWithName("method");
-    assertThat(methodSubject, isPresent());
+    assertThat(methodSubject, not(isPresent()));
 
     // TestClass.missingFieldValuePropagation() and TestClass.missingMethodValuePropagation() are
     // absent.
@@ -85,11 +85,6 @@
             .streamInstructions()
             .filter(InstructionSubject::isStaticGet)
             .anyMatch(x -> x.getField() == clinitFieldSubject.getField().field));
-    assertTrue(
-        mainMethodSubject
-            .streamInstructions()
-            .filter(InstructionSubject::isInvokeStatic)
-            .anyMatch(x -> x.getMethod() == methodSubject.getMethod().method));
   }
 
   static class TestClass {
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/string/StringBuilderWithEscapingAliasTest.java b/src/test/java/com/android/tools/r8/ir/optimize/string/StringBuilderWithEscapingAliasTest.java
new file mode 100644
index 0000000..2c61465
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/string/StringBuilderWithEscapingAliasTest.java
@@ -0,0 +1,54 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.optimize.string;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.StringUtils;
+import com.google.common.collect.Sets;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class StringBuilderWithEscapingAliasTest extends TestBase {
+
+  private final TestParameters parameters;
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public StringBuilderWithEscapingAliasTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void test() throws Exception {
+    testForR8(parameters.getBackend())
+        .addProgramClasses(TestClass.class)
+        .addKeepMainRule(TestClass.class)
+        .addOptionsModification(
+            options ->
+                options.itemFactory.libraryMethodsReturningReceiver = Sets.newIdentityHashSet())
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutput(StringUtils.times(StringUtils.lines("Hello world!"), 2));
+  }
+
+  static class TestClass {
+
+    public static void main(String[] args) {
+      StringBuilder builder = new StringBuilder();
+      StringBuilder alias = builder.append("Hello");
+      builder.append(" world!");
+      System.out.println(builder.toString());
+      System.out.println(alias.toString());
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/string/StringValueOfTest.java b/src/test/java/com/android/tools/r8/ir/optimize/string/StringValueOfTest.java
index f9d9d21..f743bf2 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/string/StringValueOfTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/string/StringValueOfTest.java
@@ -85,12 +85,12 @@
     ClassSubject mainClass = codeInspector.clazz(MAIN);
     MethodSubject mainMethod = mainClass.mainMethod();
     assertThat(mainMethod, isPresent());
-    int expectedCount = isR8 ? 3 : (isRelease ? 5 : 7);
+    int expectedCount = isR8 ? 4 : (isRelease ? 5 : 7);
     assertEquals(expectedCount, countCall(mainMethod, "String", "valueOf"));
     // Due to the different behavior regarding constant canonicalization.
-    expectedCount = isR8 ? (parameters.isCfRuntime() ? 2 : 1) : 1;
+    expectedCount = isR8 ? (parameters.isCfRuntime() ? 3 : 1) : 1;
     assertEquals(expectedCount, countConstNullNumber(mainMethod));
-    expectedCount = isR8 ? (parameters.isCfRuntime() ? 2 : 1) : (isRelease ? 1 : 0);
+    expectedCount = isR8 ? 1 : (isRelease ? 1 : 0);
     assertEquals(expectedCount, countNullStringNumber(mainMethod));
 
     MethodSubject hideNPE = mainClass.uniqueMethodWithName("hideNPE");
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInClasspathTypeTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInClasspathTypeTest.java
index 7b9380f..e86770c 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInClasspathTypeTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInClasspathTypeTest.java
@@ -72,6 +72,25 @@
   }
 
   @Test
+  public void smokeTest() throws Exception {
+    Path baseLibJar = baseLibJarMap.get(targetVersion);
+    Path extLibJar = extLibJarMap.get(targetVersion);
+
+    Path output =
+        kotlinc(parameters.getRuntime().asCf(), KOTLINC, targetVersion)
+            .addClasspathFiles(baseLibJar, extLibJar)
+            .addSourceFiles(getKotlinFileInTest(PKG_PREFIX + "/classpath_app", "main"))
+            .setOutputPath(temp.newFolder().toPath())
+            .compile();
+
+    testForJvm()
+        .addRunClasspathFiles(ToolHelper.getKotlinStdlibJar(), baseLibJar, extLibJar)
+        .addClasspath(output)
+        .run(parameters.getRuntime(), PKG + ".classpath_app.MainKt")
+        .assertSuccessWithOutput(EXPECTED);
+  }
+
+  @Test
   public void testMetadataInClasspathType_merged() throws Exception {
     Path baseLibJar = baseLibJarMap.get(targetVersion);
     Path libJar =
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInCompanionTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInCompanionTest.java
index f77cdb9..7514ff5 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInCompanionTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInCompanionTest.java
@@ -6,18 +6,19 @@
 import static com.android.tools.r8.KotlinCompilerTool.KOTLINC;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isRenamed;
-import static org.hamcrest.CoreMatchers.containsString;
 import static org.hamcrest.CoreMatchers.not;
 import static org.hamcrest.MatcherAssert.assertThat;
-import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
 
 import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.ToolHelper.KotlinTargetVersion;
-import com.android.tools.r8.ToolHelper.ProcessResult;
 import com.android.tools.r8.shaking.ProguardKeepAttributes;
+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.FieldSubject;
 import com.android.tools.r8.utils.codeinspector.KmClassSubject;
 import com.android.tools.r8.utils.codeinspector.KmPropertySubject;
 import com.android.tools.r8.utils.codeinspector.MethodSubject;
@@ -33,6 +34,9 @@
 
 @RunWith(Parameterized.class)
 public class MetadataRewriteInCompanionTest extends KotlinMetadataTestBase {
+  private static final String EXPECTED =
+      StringUtils.lines(
+          "B.Companion::foo", "B.Companion::foo", "B.Companion::foo", "B.Companion::foo");
 
   private final TestParameters parameters;
 
@@ -63,6 +67,24 @@
   }
 
   @Test
+  public void smokeTest() throws Exception {
+    Path libJar = companionLibJarMap.get(targetVersion);
+
+    Path output =
+        kotlinc(parameters.getRuntime().asCf(), KOTLINC, targetVersion)
+            .addClasspathFiles(libJar)
+            .addSourceFiles(getKotlinFileInTest(PKG_PREFIX + "/companion_app", "main"))
+            .setOutputPath(temp.newFolder().toPath())
+            .compile();
+
+    testForJvm()
+        .addRunClasspathFiles(ToolHelper.getKotlinStdlibJar(), libJar)
+        .addClasspath(output)
+        .run(parameters.getRuntime(), PKG + ".companion_app.MainKt")
+        .assertSuccessWithOutput(EXPECTED);
+  }
+
+  @Test
   public void testMetadataInCompanion_kept() throws Exception {
     Path libJar =
         testForR8(parameters.getBackend())
@@ -70,21 +92,27 @@
             // Keep everything
             .addKeepRules("-keep class **.companion_lib.** { *; }")
             .addKeepAttributes(ProguardKeepAttributes.RUNTIME_VISIBLE_ANNOTATIONS)
+            // To keep @JvmField annotation
+            .addKeepAttributes(ProguardKeepAttributes.RUNTIME_INVISIBLE_ANNOTATIONS)
+            // To keep ...$Companion structure
+            .addKeepAttributes(ProguardKeepAttributes.INNER_CLASSES)
+            .addKeepAttributes(ProguardKeepAttributes.ENCLOSING_METHOD)
             .compile()
             .inspect(codeInspector -> inspect(codeInspector, true))
             .writeToZip();
 
-    ProcessResult kotlinTestCompileResult =
+    Path output =
         kotlinc(parameters.getRuntime().asCf(), KOTLINC, targetVersion)
             .addClasspathFiles(libJar)
             .addSourceFiles(getKotlinFileInTest(PKG_PREFIX + "/companion_app", "main"))
             .setOutputPath(temp.newFolder().toPath())
-            // TODO(b/70169921): update to just .compile() once fixed.
-            .compileRaw();
+            .compile();
 
-    // TODO(b/70169921): should be able to compile!
-    assertNotEquals(0, kotlinTestCompileResult.exitCode);
-    assertThat(kotlinTestCompileResult.stderr, containsString("unresolved reference: elt2"));
+    testForJvm()
+        .addRunClasspathFiles(ToolHelper.getKotlinStdlibJar(), libJar)
+        .addClasspath(output)
+        .run(parameters.getRuntime(), PKG + ".companion_app.MainKt")
+        .assertSuccessWithOutput(EXPECTED);
   }
 
   @Test
@@ -99,31 +127,39 @@
             .addKeepRules("-keep class **.I { <methods>; }")
             // Keep getters for B$Companion.(eltN|foo) which will be referenced at the app.
             .addKeepRules("-keepclassmembers class **.B$* { *** get*(...); }")
+            // Keep the companion instance in the B class
+            .addKeepRules("-keepclassmembers class **.B { *** Companion; }")
+            // Keep the name of companion class
+            .addKeepRules("-keepnames class **.*$Companion")
             // No rule for Super, but will be kept and renamed.
             .addKeepAttributes(ProguardKeepAttributes.RUNTIME_VISIBLE_ANNOTATIONS)
+            // To keep @JvmField annotation
+            .addKeepAttributes(ProguardKeepAttributes.RUNTIME_INVISIBLE_ANNOTATIONS)
+            // To keep ...$Companion structure
+            .addKeepAttributes(ProguardKeepAttributes.INNER_CLASSES)
+            .addKeepAttributes(ProguardKeepAttributes.ENCLOSING_METHOD)
             .compile()
             .inspect(codeInspector -> inspect(codeInspector, false))
             .writeToZip();
 
-    ProcessResult kotlinTestCompileResult =
+    Path output =
         kotlinc(parameters.getRuntime().asCf(), KOTLINC, targetVersion)
             .addClasspathFiles(libJar)
             .addSourceFiles(getKotlinFileInTest(PKG_PREFIX + "/companion_app", "main"))
             .setOutputPath(temp.newFolder().toPath())
-            // TODO(b/70169921): update to just .compile() once fixed.
-            .compileRaw();
+            .compile();
 
-    // TODO(b/70169921): should be able to compile!
-    assertNotEquals(0, kotlinTestCompileResult.exitCode);
-    assertThat(kotlinTestCompileResult.stderr, containsString("unresolved reference: elt1"));
-    assertThat(kotlinTestCompileResult.stderr, containsString("unresolved reference: elt2"));
-    assertThat(kotlinTestCompileResult.stderr, containsString("unresolved reference: foo"));
+    testForJvm()
+        .addRunClasspathFiles(ToolHelper.getKotlinStdlibJar(), libJar)
+        .addClasspath(output)
+        .run(parameters.getRuntime(), PKG + ".companion_app.MainKt")
+        .assertSuccessWithOutput(EXPECTED);
   }
 
   private void inspect(CodeInspector inspector, boolean keptAll) {
     final String superClassName = PKG + ".companion_lib.Super";
     final String bClassName = PKG + ".companion_lib.B";
-    final String companionClassName = PKG + ".companion_lib.B$Companion";
+    final String companionClassName = bClassName + "$Companion";
 
     ClassSubject sup = inspector.clazz(superClassName);
     if (keptAll) {
@@ -147,6 +183,16 @@
           supertype -> supertype.getFinalDescriptor().equals(sup.getFinalDescriptor())));
     }
 
+    // The backing field for the property in the companion, with @JvmField
+    FieldSubject elt2 = impl.uniqueFieldWithName("elt2");
+    assertThat(elt2, isPresent());
+    assertThat(elt2, not(isRenamed()));
+
+    FieldSubject companionObject = impl.uniqueFieldWithName("Companion");
+    assertThat(companionObject, isPresent());
+    assertThat(companionObject, not(isRenamed()));
+    assertEquals(companionObject.getFinalName(), kmClass.getCompanionObject());
+
     // Bridge for the property in the companion that needs a backing field.
     MethodSubject elt1Bridge = impl.uniqueMethodWithName("access$getElt1$cp");
     if (keptAll) {
@@ -155,6 +201,7 @@
     } else {
       assertThat(elt1Bridge, isRenamed());
     }
+
     // With @JvmField, no bridge is added.
     MethodSubject elt2Bridge = impl.uniqueMethodWithName("access$getElt2$cp");
     assertThat(elt2Bridge, not(isPresent()));
@@ -164,23 +211,20 @@
     assertThat(fooBridge, not(isPresent()));
 
     ClassSubject companion = inspector.clazz(companionClassName);
-    if (keptAll) {
-      assertThat(companion, isPresent());
-      assertThat(companion, not(isRenamed()));
-    } else {
-      assertThat(companion, isRenamed());
-    }
+    assertThat(companion, isPresent());
+    assertThat(companion, not(isRenamed()));
 
-    // TODO(b/70169921): Assert impl's KmClass points to the correct companion object and class.
+    List<String> nestedClassDescriptors = kmClass.getNestedClassDescriptors();
+    assertEquals(1, nestedClassDescriptors.size());
+    assertEquals(companion.getFinalDescriptor(), nestedClassDescriptors.get(0));
 
     kmClass = companion.getKmClass();
     assertThat(kmClass, isPresent());
 
     KmPropertySubject kmProperty = kmClass.kmPropertyWithUniqueName("elt1");
     assertThat(kmProperty, isPresent());
-    // TODO(b/70169921): property in companion with @JvmField is missing.
     kmProperty = kmClass.kmPropertyWithUniqueName("elt2");
-    assertThat(kmProperty, not(isPresent()));
+    assertThat(kmProperty, isPresent());
     kmProperty = kmClass.kmPropertyWithUniqueName("foo");
     assertThat(kmProperty, isPresent());
 
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInExtensionFunctionTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInExtensionFunctionTest.java
index 85a1869..a9f1655 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInExtensionFunctionTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInExtensionFunctionTest.java
@@ -68,6 +68,24 @@
   }
 
   @Test
+  public void smokeTest() throws Exception {
+    Path libJar = extLibJarMap.get(targetVersion);
+
+    Path output =
+        kotlinc(parameters.getRuntime().asCf(), KOTLINC, targetVersion)
+            .addClasspathFiles(libJar)
+            .addSourceFiles(getKotlinFileInTest(PKG_PREFIX + "/extension_function_app", "main"))
+            .setOutputPath(temp.newFolder().toPath())
+            .compile();
+
+    testForJvm()
+        .addRunClasspathFiles(ToolHelper.getKotlinStdlibJar(), libJar)
+        .addClasspath(output)
+        .run(parameters.getRuntime(), PKG + ".extension_function_app.MainKt")
+        .assertSuccessWithOutput(EXPECTED);
+  }
+
+  @Test
   public void testMetadataInExtensionFunction_merged() throws Exception {
     Path libJar =
         testForR8(parameters.getBackend())
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInExtensionPropertyTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInExtensionPropertyTest.java
index 06fcd3a..afd2ac6 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInExtensionPropertyTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInExtensionPropertyTest.java
@@ -67,6 +67,24 @@
   }
 
   @Test
+  public void smokeTest() throws Exception {
+    Path libJar = extLibJarMap.get(targetVersion);
+
+    Path output =
+        kotlinc(parameters.getRuntime().asCf(), KOTLINC, targetVersion)
+            .addClasspathFiles(libJar)
+            .addSourceFiles(getKotlinFileInTest(PKG_PREFIX + "/extension_property_app", "main"))
+            .setOutputPath(temp.newFolder().toPath())
+            .compile();
+
+    testForJvm()
+        .addRunClasspathFiles(ToolHelper.getKotlinStdlibJar(), libJar)
+        .addClasspath(output)
+        .run(parameters.getRuntime(), PKG + ".extension_property_app.MainKt")
+        .assertSuccessWithOutput(EXPECTED);
+  }
+
+  @Test
   public void testMetadataInExtensionProperty_merged() throws Exception {
     Path libJar =
         testForR8(parameters.getBackend())
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInFunctionTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInFunctionTest.java
index e72c314..fad1701 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInFunctionTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInFunctionTest.java
@@ -66,6 +66,24 @@
   }
 
   @Test
+  public void smokeTest() throws Exception {
+    Path libJar = funLibJarMap.get(targetVersion);
+
+    Path output =
+        kotlinc(parameters.getRuntime().asCf(), KOTLINC, targetVersion)
+            .addClasspathFiles(libJar)
+            .addSourceFiles(getKotlinFileInTest(PKG_PREFIX + "/function_app", "main"))
+            .setOutputPath(temp.newFolder().toPath())
+            .compile();
+
+    testForJvm()
+        .addRunClasspathFiles(ToolHelper.getKotlinStdlibJar(), libJar)
+        .addClasspath(output)
+        .run(parameters.getRuntime(), PKG + ".function_app.MainKt")
+        .assertSuccessWithOutput(EXPECTED);
+  }
+
+  @Test
   public void testMetadataInFunction_merged() throws Exception {
     Path libJar =
         testForR8(parameters.getBackend())
@@ -170,7 +188,7 @@
     KmClassSubject kmClass = sup.getKmClass();
     assertThat(kmClass, isPresent());
 
-    // TODO(b/70169921): would be better to look up function with the original name, "foo".
+    // TODO(b/151194869): would be better to look up function with the original name, "foo".
     KmFunctionSubject kmFunction = kmClass.kmFunctionWithUniqueName(foo.getFinalName());
     assertThat(kmFunction, isPresent());
     assertThat(kmFunction, not(isExtensionFunction()));
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInFunctionWithDefaultValueTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInFunctionWithDefaultValueTest.java
index 0e41755..79ad6e5 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInFunctionWithDefaultValueTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInFunctionWithDefaultValueTest.java
@@ -69,6 +69,24 @@
   }
 
   @Test
+  public void smokeTest() throws Exception {
+    Path libJar = defaultValueLibJarMap.get(targetVersion);
+
+    Path output =
+        kotlinc(parameters.getRuntime().asCf(), KOTLINC, targetVersion)
+            .addClasspathFiles(libJar)
+            .addSourceFiles(getKotlinFileInTest(PKG_PREFIX + "/default_value_app", "main"))
+            .setOutputPath(temp.newFolder().toPath())
+            .compile();
+
+    testForJvm()
+        .addRunClasspathFiles(ToolHelper.getKotlinStdlibJar(), libJar)
+        .addClasspath(output)
+        .run(parameters.getRuntime(), PKG + ".default_value_app.MainKt")
+        .assertSuccessWithOutput(EXPECTED);
+  }
+
+  @Test
   public void testMetadataInFunctionWithDefaultValue() throws Exception {
     Path libJar =
         testForR8(parameters.getBackend())
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInFunctionWithVarargTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInFunctionWithVarargTest.java
index 68714f6..fea7b3e 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInFunctionWithVarargTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInFunctionWithVarargTest.java
@@ -69,6 +69,24 @@
   }
 
   @Test
+  public void smokeTest() throws Exception {
+    Path libJar = varargLibJarMap.get(targetVersion);
+
+    Path output =
+        kotlinc(parameters.getRuntime().asCf(), KOTLINC, targetVersion)
+            .addClasspathFiles(libJar)
+            .addSourceFiles(getKotlinFileInTest(PKG_PREFIX + "/vararg_app", "main"))
+            .setOutputPath(temp.newFolder().toPath())
+            .compile();
+
+    testForJvm()
+        .addRunClasspathFiles(ToolHelper.getKotlinStdlibJar(), libJar)
+        .addClasspath(output)
+        .run(parameters.getRuntime(), PKG + ".vararg_app.MainKt")
+        .assertSuccessWithOutput(EXPECTED);
+  }
+
+  @Test
   public void testMetadataInFunctionWithVararg() throws Exception {
     Path libJar =
         testForR8(parameters.getBackend())
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInLibraryTypeTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInLibraryTypeTest.java
index 0bca5ee..71dd5df 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInLibraryTypeTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInLibraryTypeTest.java
@@ -79,6 +79,15 @@
   }
 
   @Test
+  public void smokeTest() throws Exception {
+    testForJvm()
+        .addRunClasspathFiles(ToolHelper.getKotlinStdlibJar(), baseLibJarMap.get(targetVersion))
+        .addClasspath(extLibJarMap.get(targetVersion), appJarMap.get(targetVersion))
+        .run(parameters.getRuntime(), PKG + ".libtype_app.MainKt")
+        .assertSuccessWithOutput(EXPECTED);
+  }
+
+  @Test
   public void testR8() throws Exception {
     String main = PKG + ".libtype_app.MainKt";
     Path out =
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInMultifileClassTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInMultifileClassTest.java
index 5b9dfed..477c5e6 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInMultifileClassTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInMultifileClassTest.java
@@ -16,10 +16,12 @@
 import static org.junit.Assert.assertTrue;
 
 import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.ToolHelper.KotlinTargetVersion;
 import com.android.tools.r8.ToolHelper.ProcessResult;
 import com.android.tools.r8.shaking.ProguardKeepAttributes;
 import com.android.tools.r8.utils.DescriptorUtils;
+import com.android.tools.r8.utils.StringUtils;
 import com.android.tools.r8.utils.codeinspector.AnnotationSubject;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
@@ -39,6 +41,7 @@
 
 @RunWith(Parameterized.class)
 public class MetadataRewriteInMultifileClassTest extends KotlinMetadataTestBase {
+  private static final String EXPECTED = StringUtils.lines(", 1, 2, 3");
 
   private final TestParameters parameters;
 
@@ -71,6 +74,24 @@
   }
 
   @Test
+  public void smokeTest() throws Exception {
+    Path libJar = multifileLibJarMap.get(targetVersion);
+
+    Path output =
+        kotlinc(parameters.getRuntime().asCf(), KOTLINC, targetVersion)
+            .addClasspathFiles(libJar)
+            .addSourceFiles(getKotlinFileInTest(PKG_PREFIX + "/multifileclass_app", "main"))
+            .setOutputPath(temp.newFolder().toPath())
+            .compile();
+
+    testForJvm()
+        .addRunClasspathFiles(ToolHelper.getKotlinStdlibJar(), libJar)
+        .addClasspath(output)
+        .run(parameters.getRuntime(), PKG + ".multifileclass_app.MainKt")
+        .assertSuccessWithOutput(EXPECTED);
+  }
+
+  @Test
   public void testMetadataInMultifileClass_merged() throws Exception {
     Path libJar =
         testForR8(parameters.getBackend())
@@ -88,9 +109,9 @@
             .addClasspathFiles(libJar)
             .addSourceFiles(getKotlinFileInTest(PKG_PREFIX + "/multifileclass_app", "main"))
             .setOutputPath(temp.newFolder().toPath())
-            // TODO(b/70169921): update to just .compile() once fixed.
+            // TODO(b/151193860): update to just .compile() once fixed.
             .compileRaw();
-    // TODO(b/70169921): should be able to compile!
+    // TODO(b/151193860): should be able to compile!
     assertNotEquals(0, kotlinTestCompileResult.exitCode);
     assertThat(kotlinTestCompileResult.stderr, containsString("unresolved reference: join"));
   }
@@ -133,9 +154,9 @@
             .addClasspathFiles(libJar)
             .addSourceFiles(getKotlinFileInTest(PKG_PREFIX + "/multifileclass_app", "main"))
             .setOutputPath(temp.newFolder().toPath())
-            // TODO(b/70169921): update to just .compile() once fixed.
+            // TODO(b/151193860): update to just .compile() once fixed.
             .compileRaw();
-    // TODO(b/70169921): should be able to compile!
+    // TODO(b/151193860): should be able to compile!
     assertNotEquals(0, kotlinTestCompileResult.exitCode);
     assertThat(kotlinTestCompileResult.stderr, containsString("unresolved reference: join"));
   }
@@ -193,7 +214,7 @@
         kmPackage.kmFunctionExtensionWithUniqueName("commaSeparatedJoinOfInt");
     assertThat(kmFunction, isPresent());
     assertThat(kmFunction, isExtensionFunction());
-    // TODO(b/70169921): Inspect that parameter type has a correct type argument, Int.
-    // TODO(b/70169921): Inspect that the name in KmFunction is still 'join' so that apps can refer.
+    // TODO(b/151193860): Inspect parameter type has a correct type argument, Int.
+    // TODO(b/151193860): Inspect the name in KmFunction is still 'join' so that apps can refer.
   }
 }
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInNestedClassTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInNestedClassTest.java
index 0e734ea..f4530b3 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInNestedClassTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInNestedClassTest.java
@@ -63,6 +63,24 @@
   }
 
   @Test
+  public void smokeTest() throws Exception {
+    Path libJar = nestedLibJarMap.get(targetVersion);
+
+    Path output =
+        kotlinc(parameters.getRuntime().asCf(), KOTLINC, targetVersion)
+            .addClasspathFiles(libJar)
+            .addSourceFiles(getKotlinFileInTest(PKG_PREFIX + "/nested_app", "main"))
+            .setOutputPath(temp.newFolder().toPath())
+            .compile();
+
+    testForJvm()
+        .addRunClasspathFiles(ToolHelper.getKotlinStdlibJar(), libJar)
+        .addClasspath(output)
+        .run(parameters.getRuntime(), PKG + ".nested_app.MainKt")
+        .assertSuccessWithOutput(EXPECTED);
+  }
+
+  @Test
   public void testMetadataInNestedClass() throws Exception {
     Path libJar =
         testForR8(parameters.getBackend())
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInParameterTypeTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInParameterTypeTest.java
index 05acc76..54cd15c 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInParameterTypeTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInParameterTypeTest.java
@@ -61,6 +61,24 @@
   }
 
   @Test
+  public void smokeTest() throws Exception {
+    Path libJar = parameterTypeLibJarMap.get(targetVersion);
+
+    Path output =
+        kotlinc(parameters.getRuntime().asCf(), KOTLINC, targetVersion)
+            .addClasspathFiles(libJar)
+            .addSourceFiles(getKotlinFileInTest(PKG_PREFIX + "/parametertype_app", "main"))
+            .setOutputPath(temp.newFolder().toPath())
+            .compile();
+
+    testForJvm()
+        .addRunClasspathFiles(ToolHelper.getKotlinStdlibJar(), libJar)
+        .addClasspath(output)
+        .run(parameters.getRuntime(), PKG + ".parametertype_app.MainKt")
+        .assertSuccessWithOutput(EXPECTED);
+  }
+
+  @Test
   public void testMetadataInParameterType_renamed() throws Exception {
     Path libJar =
         testForR8(parameters.getBackend())
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInPropertyTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInPropertyTest.java
index 34abe12..e58725d 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInPropertyTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInPropertyTest.java
@@ -10,7 +10,6 @@
 import static org.hamcrest.CoreMatchers.not;
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
 
@@ -18,6 +17,7 @@
 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.StringUtils;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.android.tools.r8.utils.codeinspector.FieldSubject;
@@ -35,6 +35,7 @@
 
 @RunWith(Parameterized.class)
 public class MetadataRewriteInPropertyTest extends KotlinMetadataTestBase {
+  private static final String EXPECTED_GETTER = StringUtils.lines("true", "false", "Hey Jude");
 
   private final TestParameters parameters;
 
@@ -65,6 +66,25 @@
   }
 
   @Test
+  public void smokeTest_getterApp() throws Exception {
+    Path libJar = propertyTypeLibJarMap.get(targetVersion);
+
+    Path output =
+        kotlinc(parameters.getRuntime().asCf(), KOTLINC, targetVersion)
+            .addClasspathFiles(libJar)
+            .addSourceFiles(
+                getKotlinFileInTest(PKG_PREFIX + "/fragile_property_only_getter", "getter_user"))
+            .setOutputPath(temp.newFolder().toPath())
+            .compile();
+
+    testForJvm()
+        .addRunClasspathFiles(ToolHelper.getKotlinStdlibJar(), libJar)
+        .addClasspath(output)
+        .run(parameters.getRuntime(), PKG + ".fragile_property_only_getter.Getter_userKt")
+        .assertSuccessWithOutput(EXPECTED_GETTER);
+  }
+
+  @Test
   public void testMetadataInProperty_getterOnly() throws Exception {
     Path libJar =
         testForR8(parameters.getBackend())
@@ -89,7 +109,7 @@
         .addRunClasspathFiles(ToolHelper.getKotlinStdlibJar(), libJar)
         .addClasspath(output)
         .run(parameters.getRuntime(), PKG + ".fragile_property_only_getter.Getter_userKt")
-        .assertSuccessWithOutputLines("true", "false", "Hey Jude");
+        .assertSuccessWithOutput(EXPECTED_GETTER);
   }
 
   private void inspectGetterOnly(CodeInspector inspector) {
@@ -139,6 +159,25 @@
   }
 
   @Test
+  public void smokeTest_setterApp() throws Exception {
+    Path libJar = propertyTypeLibJarMap.get(targetVersion);
+
+    Path output =
+        kotlinc(parameters.getRuntime().asCf(), KOTLINC, targetVersion)
+            .addClasspathFiles(libJar)
+            .addSourceFiles(
+                getKotlinFileInTest(PKG_PREFIX + "/fragile_property_only_setter", "setter_user"))
+            .setOutputPath(temp.newFolder().toPath())
+            .compile();
+
+    testForJvm()
+        .addRunClasspathFiles(ToolHelper.getKotlinStdlibJar(), libJar)
+        .addClasspath(output)
+        .run(parameters.getRuntime(), PKG + ".fragile_property_only_setter.Setter_userKt")
+        .assertSuccessWithOutputLines();
+  }
+
+  @Test
   public void testMetadataInProperty_setterOnly() throws Exception {
     Path libJar =
         testForR8(parameters.getBackend())
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInPropertyTypeTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInPropertyTypeTest.java
index 4fc0029..ceced0d 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInPropertyTypeTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInPropertyTypeTest.java
@@ -61,6 +61,24 @@
   }
 
   @Test
+  public void smokeTest() throws Exception {
+    Path libJar = propertyTypeLibJarMap.get(targetVersion);
+
+    Path output =
+        kotlinc(parameters.getRuntime().asCf(), KOTLINC, targetVersion)
+            .addClasspathFiles(libJar)
+            .addSourceFiles(getKotlinFileInTest(PKG_PREFIX + "/propertytype_app", "main"))
+            .setOutputPath(temp.newFolder().toPath())
+            .compile();
+
+    testForJvm()
+        .addRunClasspathFiles(ToolHelper.getKotlinStdlibJar(), libJar)
+        .addClasspath(output)
+        .run(parameters.getRuntime(), PKG + ".propertytype_app.MainKt")
+        .assertSuccessWithOutput(EXPECTED);
+  }
+
+  @Test
   public void testMetadataInProperty_renamed() throws Exception {
     Path libJar =
         testForR8(parameters.getBackend())
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInReturnTypeTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInReturnTypeTest.java
index 21fcbb6..09d4a5a 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInReturnTypeTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInReturnTypeTest.java
@@ -61,6 +61,24 @@
   }
 
   @Test
+  public void smokeTest() throws Exception {
+    Path libJar = returnTypeLibJarMap.get(targetVersion);
+
+    Path output =
+        kotlinc(parameters.getRuntime().asCf(), KOTLINC, targetVersion)
+            .addClasspathFiles(libJar)
+            .addSourceFiles(getKotlinFileInTest(PKG_PREFIX + "/returntype_app", "main"))
+            .setOutputPath(temp.newFolder().toPath())
+            .compile();
+
+    testForJvm()
+        .addRunClasspathFiles(ToolHelper.getKotlinStdlibJar(), libJar)
+        .addClasspath(output)
+        .run(parameters.getRuntime(), PKG + ".returntype_app.MainKt")
+        .assertSuccessWithOutput(EXPECTED);
+  }
+
+  @Test
   public void testMetadataInReturnType_renamed() throws Exception {
     Path libJar =
         testForR8(parameters.getBackend())
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInSealedClassTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInSealedClassTest.java
index 373dc40..b09ef7e 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInSealedClassTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInSealedClassTest.java
@@ -69,6 +69,24 @@
   }
 
   @Test
+  public void smokeTest() throws Exception {
+    Path libJar = sealedLibJarMap.get(targetVersion);
+
+    Path output =
+        kotlinc(parameters.getRuntime().asCf(), KOTLINC, targetVersion)
+            .addClasspathFiles(libJar)
+            .addSourceFiles(getKotlinFileInTest(PKG_PREFIX + "/sealed_app", "valid"))
+            .setOutputPath(temp.newFolder().toPath())
+            .compile();
+
+    testForJvm()
+        .addRunClasspathFiles(ToolHelper.getKotlinStdlibJar(), libJar)
+        .addClasspath(output)
+        .run(parameters.getRuntime(), PKG + ".sealed_app.ValidKt")
+        .assertSuccessWithOutput(EXPECTED);
+  }
+
+  @Test
   public void testMetadataInSealedClass_valid() throws Exception {
     Path libJar =
         testForR8(parameters.getBackend())
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInSuperTypeTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInSuperTypeTest.java
index 9de95b4..3c02dac 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInSuperTypeTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInSuperTypeTest.java
@@ -63,6 +63,24 @@
   }
 
   @Test
+  public void smokeTest() throws Exception {
+    Path libJar = superTypeLibJarMap.get(targetVersion);
+
+    Path output =
+        kotlinc(parameters.getRuntime().asCf(), KOTLINC, targetVersion)
+            .addClasspathFiles(libJar)
+            .addSourceFiles(getKotlinFileInTest(PKG_PREFIX + "/supertype_app", "main"))
+            .setOutputPath(temp.newFolder().toPath())
+            .compile();
+
+    testForJvm()
+        .addRunClasspathFiles(ToolHelper.getKotlinStdlibJar(), libJar)
+        .addClasspath(output)
+        .run(parameters.getRuntime(), PKG + ".supertype_app.MainKt")
+        .assertSuccessWithOutput(EXPECTED);
+  }
+
+  @Test
   public void testMetadataInSupertype_merged() throws Exception {
     Path libJar =
         testForR8(parameters.getBackend())
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInTypeAliasTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInTypeAliasTest.java
index 8b9f276..094362c 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInTypeAliasTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInTypeAliasTest.java
@@ -12,9 +12,11 @@
 import static org.junit.Assert.assertNotEquals;
 
 import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.ToolHelper.KotlinTargetVersion;
 import com.android.tools.r8.ToolHelper.ProcessResult;
 import com.android.tools.r8.shaking.ProguardKeepAttributes;
+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.KmPackageSubject;
@@ -30,6 +32,9 @@
 
 @RunWith(Parameterized.class)
 public class MetadataRewriteInTypeAliasTest extends KotlinMetadataTestBase {
+  private static final String EXPECTED =
+      StringUtils.lines("Impl::foo", "Program::foo", "true", "42");
+
   private final TestParameters parameters;
 
   @Parameterized.Parameters(name = "{0} target: {1}")
@@ -59,6 +64,24 @@
   }
 
   @Test
+  public void smokeTest() throws Exception {
+    Path libJar = typeAliasLibJarMap.get(targetVersion);
+
+    Path output =
+        kotlinc(parameters.getRuntime().asCf(), KOTLINC, targetVersion)
+            .addClasspathFiles(libJar)
+            .addSourceFiles(getKotlinFileInTest(PKG_PREFIX + "/typealias_app", "main"))
+            .setOutputPath(temp.newFolder().toPath())
+            .compile();
+
+    testForJvm()
+        .addRunClasspathFiles(ToolHelper.getKotlinStdlibJar(), libJar)
+        .addClasspath(output)
+        .run(parameters.getRuntime(), PKG + ".typealias_app.MainKt")
+        .assertSuccessWithOutput(EXPECTED);
+  }
+
+  @Test
   public void testMetadataInTypeAlias_renamed() throws Exception {
     Path libJar =
         testForR8(parameters.getBackend())
@@ -79,9 +102,9 @@
             .addClasspathFiles(libJar)
             .addSourceFiles(getKotlinFileInTest(PKG_PREFIX + "/typealias_app", "main"))
             .setOutputPath(temp.newFolder().toPath())
-            // TODO(b/70169921): update to just .compile() once fixed.
+            // TODO(b/151194785): update to just .compile() once fixed.
             .compileRaw();
-    // TODO(b/70169921): should be able to compile!
+    // TODO(b/151194785): should be able to compile!
     assertNotEquals(0, kotlinTestCompileResult.exitCode);
     assertThat(
         kotlinTestCompileResult.stderr,
@@ -107,6 +130,6 @@
     // API entry is kept, hence the presence of Metadata.
     KmPackageSubject kmPackage = libKt.getKmPackage();
     assertThat(kmPackage, isPresent());
-    // TODO(b/70169921): need further inspection: many kinds of type appearances in typealias.
+    // TODO(b/151194785): need further inspection: many kinds of type appearances in typealias.
   }
 }
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataStripTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataStripTest.java
index 31cd07d..4d66342 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataStripTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataStripTest.java
@@ -52,7 +52,7 @@
             .addKeepMainRule(mainClassName)
             .addKeepAttributes(ProguardKeepAttributes.RUNTIME_VISIBLE_ANNOTATIONS)
             .addKeepRules("-keep class kotlin.Metadata")
-            // TODO(b/70169921): if this option is settled down, this test is meaningless.
+            // TODO(b/151194540): if this option is settled down, this test is meaningless.
             .addOptionsModification(o -> o.enableKotlinMetadataRewritingForRenamedClasses = false)
             .allowDiagnosticWarningMessages()
             .setMinApi(parameters.getApiLevel())
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/companion_lib/lib.kt b/src/test/java/com/android/tools/r8/kotlin/metadata/companion_lib/lib.kt
index e3da633..8e69219 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/companion_lib/lib.kt
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/companion_lib/lib.kt
@@ -23,6 +23,6 @@
     @JvmField
     val elt2: Super = B()
     val foo: String
-      get() = "B.Companion:foo"
+      get() = "B.Companion::foo"
   }
 }
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 03ab057..2b5270d 100644
--- a/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java
+++ b/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java
@@ -47,6 +47,7 @@
 import com.android.tools.r8.graph.DexTypeList;
 import com.android.tools.r8.graph.DirectMappedDexApplication;
 import com.android.tools.r8.graph.GraphLense;
+import com.android.tools.r8.graph.InitClassLens;
 import com.android.tools.r8.graph.MethodAccessFlags;
 import com.android.tools.r8.graph.ParameterAnnotationsList;
 import com.android.tools.r8.ir.code.CatchHandlers;
@@ -866,6 +867,7 @@
             options,
             null,
             GraphLense.getIdentityLense(),
+            InitClassLens.getDefault(),
             NamingLens.getIdentityLens(),
             null);
     ExecutorService executor = ThreadUtils.getExecutorService(options);
diff --git a/src/test/java/com/android/tools/r8/naming/RenameSourceFileRetraceTest.java b/src/test/java/com/android/tools/r8/naming/RenameSourceFileRetraceTest.java
new file mode 100644
index 0000000..4de8e4b
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/naming/RenameSourceFileRetraceTest.java
@@ -0,0 +1,149 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.naming;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.containsLinePositions;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static junit.framework.TestCase.assertEquals;
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.R8TestBuilder;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.naming.retrace.StackTrace;
+import com.android.tools.r8.naming.testclasses.ClassToBeMinified;
+import com.android.tools.r8.naming.testclasses.Main;
+import com.android.tools.r8.references.MethodReference;
+import com.android.tools.r8.references.Reference;
+import com.android.tools.r8.shaking.ProguardKeepAttributes;
+import com.android.tools.r8.utils.BooleanUtils;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.Matchers.LinePosition;
+import java.io.IOException;
+import java.util.List;
+import java.util.concurrent.ExecutionException;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class RenameSourceFileRetraceTest extends TestBase {
+
+  private static final String FILENAME_RENAME = "FOO";
+  private static final String FILENAME_MAIN = "Main.java";
+  private static final String FILENAME_CLASS_TO_BE_MINIFIED = "ClassToBeMinified.java";
+
+  private final TestParameters parameters;
+  private final boolean isCompat;
+  private final boolean keepSourceFile;
+
+  @Parameters(name = "{0}, is compat: {1}, keep source file attribute: {2}")
+  public static List<Object[]> data() {
+    return buildParameters(
+        getTestParameters().withAllRuntimesAndApiLevels().build(),
+        BooleanUtils.values(),
+        BooleanUtils.values());
+  }
+
+  public RenameSourceFileRetraceTest(
+      TestParameters parameters, Boolean isCompat, Boolean keepSourceFile) {
+    this.parameters = parameters;
+    this.isCompat = isCompat;
+    this.keepSourceFile = keepSourceFile;
+  }
+
+  @Test
+  public void testR8()
+      throws ExecutionException, CompilationFailedException, IOException, NoSuchMethodException {
+    R8TestBuilder<? extends R8TestBuilder<?>> r8TestBuilder =
+        isCompat ? testForR8Compat(parameters.getBackend()) : testForR8(parameters.getBackend());
+    if (keepSourceFile) {
+      r8TestBuilder.addKeepAttributes(ProguardKeepAttributes.SOURCE_FILE);
+    }
+    String minifiedFileName =
+        (keepSourceFile && isCompat) ? FILENAME_CLASS_TO_BE_MINIFIED : getDefaultExpectedName();
+    String mainFileName = keepSourceFile ? FILENAME_MAIN : getDefaultExpectedName();
+    r8TestBuilder
+        .addProgramClasses(ClassToBeMinified.class, Main.class)
+        .addKeepAttributes(ProguardKeepAttributes.LINE_NUMBER_TABLE)
+        .addKeepMainRule(Main.class)
+        .setMinApi(parameters.getApiLevel())
+        .enableInliningAnnotations()
+        .run(parameters.getRuntime(), Main.class)
+        .assertFailureWithErrorThatMatches(containsString("ClassToBeMinified.foo()"))
+        .inspectFailure(inspector -> inspectOutput(inspector, minifiedFileName, mainFileName))
+        .inspectStackTrace(stackTrace -> inspectStackTrace(stackTrace, FILENAME_MAIN));
+  }
+
+  @Test
+  public void testRenameSourceFileR8()
+      throws ExecutionException, CompilationFailedException, IOException, NoSuchMethodException {
+    R8TestBuilder<? extends R8TestBuilder<?>> r8TestBuilder =
+        isCompat ? testForR8Compat(parameters.getBackend()) : testForR8(parameters.getBackend());
+    if (keepSourceFile) {
+      r8TestBuilder.addKeepAttributes(ProguardKeepAttributes.SOURCE_FILE);
+    }
+    String expectedName = getDefaultExpectedName(FILENAME_RENAME);
+    r8TestBuilder
+        .addProgramClasses(ClassToBeMinified.class, Main.class)
+        .addKeepAttributes(ProguardKeepAttributes.LINE_NUMBER_TABLE)
+        .addKeepRules("-renamesourcefileattribute " + FILENAME_RENAME)
+        .addKeepMainRule(Main.class)
+        .setMinApi(parameters.getApiLevel())
+        .enableInliningAnnotations()
+        .run(parameters.getRuntime(), Main.class)
+        .assertFailureWithErrorThatMatches(containsString("ClassToBeMinified.foo()"))
+        .inspectFailure(inspector -> inspectOutput(inspector, expectedName, expectedName))
+        .inspectStackTrace(stackTrace -> inspectStackTrace(stackTrace, expectedName));
+  }
+
+  private String getDefaultExpectedName() {
+    return getDefaultExpectedName(parameters.getBackend() == Backend.CF ? "SourceFile" : "");
+  }
+
+  private String getDefaultExpectedName(String name) {
+    if (!isCompat && !keepSourceFile) {
+      return null;
+    } else {
+      return name;
+    }
+  }
+
+  private void inspectOutput(
+      CodeInspector inspector, String classToBeMinifiedFilename, String mainClassFilename) {
+    inspectSourceFileForClass(inspector, Main.class, mainClassFilename);
+    inspectSourceFileForClass(inspector, ClassToBeMinified.class, classToBeMinifiedFilename);
+  }
+
+  private void inspectSourceFileForClass(CodeInspector inspector, Class<?> clazz, String expected) {
+    ClassSubject classToBeMinifiedSubject = inspector.clazz(clazz);
+    assertThat(classToBeMinifiedSubject, isPresent());
+    DexClass dexClass = classToBeMinifiedSubject.getDexClass();
+    String actualString = dexClass.sourceFile == null ? null : dexClass.sourceFile.toString();
+    assertEquals(expected, actualString);
+  }
+
+  private void inspectStackTrace(StackTrace stackTrace, String mainFileName)
+      throws NoSuchMethodException {
+    if (!keepSourceFile) {
+      return;
+    }
+    assertEquals(2, stackTrace.getStackTraceLines().size());
+    MethodReference classToBeMinifiedFoo =
+        Reference.methodFromMethod(ClassToBeMinified.class.getDeclaredMethod("foo"));
+    MethodReference mainMain =
+        Reference.methodFromMethod(Main.class.getDeclaredMethod("main", String[].class));
+    LinePosition expectedStack =
+        LinePosition.stack(
+            LinePosition.create(classToBeMinifiedFoo, 1, 13, FILENAME_CLASS_TO_BE_MINIFIED),
+            LinePosition.create(mainMain, 1, 10, mainFileName));
+    assertThat(stackTrace, containsLinePositions(expectedStack));
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/naming/retrace/DesugarLambdaRetraceTest.java b/src/test/java/com/android/tools/r8/naming/retrace/DesugarLambdaRetraceTest.java
index 9b2c7c4..a55e1d5 100644
--- a/src/test/java/com/android/tools/r8/naming/retrace/DesugarLambdaRetraceTest.java
+++ b/src/test/java/com/android/tools/r8/naming/retrace/DesugarLambdaRetraceTest.java
@@ -9,10 +9,12 @@
 import static com.android.tools.r8.naming.retrace.StackTrace.isSameExceptForFileNameAndLineNumber;
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assume.assumeTrue;
 
 import com.android.tools.r8.CompilationMode;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.naming.retrace.StackTrace.StackTraceLine;
+import com.android.tools.r8.utils.BooleanUtils;
 import com.google.common.collect.ImmutableList;
 import java.util.Collection;
 import org.junit.Test;
@@ -23,14 +25,16 @@
 @RunWith(Parameterized.class)
 public class DesugarLambdaRetraceTest extends RetraceTestBase {
 
-  @Parameters(name = "{0}, mode: {1}")
+  @Parameters(name = "{0}, mode: {1}, compat: {2}")
   public static Collection<Object[]> data() {
     return buildParameters(
-        getTestParameters().withAllRuntimesAndApiLevels().build(), CompilationMode.values());
+        getTestParameters().withAllRuntimesAndApiLevels().build(),
+        CompilationMode.values(),
+        BooleanUtils.values());
   }
 
-  public DesugarLambdaRetraceTest(TestParameters parameters, CompilationMode mode) {
-    super(parameters, mode);
+  public DesugarLambdaRetraceTest(TestParameters parameters, CompilationMode mode, boolean compat) {
+    super(parameters, mode, compat);
   }
 
   @Override
@@ -122,12 +126,14 @@
 
   @Test
   public void testLineNumberTableOnly() throws Exception {
+    assumeTrue(compat);
     runTest(
         ImmutableList.of("-keepattributes LineNumberTable"), this::checkIsSameExceptForFileName);
   }
 
   @Test
   public void testNoLineNumberTable() throws Exception {
+    assumeTrue(compat);
     runTest(ImmutableList.of(), this::checkIsSameExceptForFileNameAndLineNumber);
   }
 }
diff --git a/src/test/java/com/android/tools/r8/naming/retrace/DesugarStaticInterfaceMethodsRetraceTest.java b/src/test/java/com/android/tools/r8/naming/retrace/DesugarStaticInterfaceMethodsRetraceTest.java
index 1427865..f049056 100644
--- a/src/test/java/com/android/tools/r8/naming/retrace/DesugarStaticInterfaceMethodsRetraceTest.java
+++ b/src/test/java/com/android/tools/r8/naming/retrace/DesugarStaticInterfaceMethodsRetraceTest.java
@@ -10,6 +10,7 @@
 import com.android.tools.r8.CompilationMode;
 import com.android.tools.r8.NeverInline;
 import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.utils.BooleanUtils;
 import com.google.common.collect.ImmutableList;
 import java.util.Collection;
 import org.junit.Test;
@@ -20,14 +21,17 @@
 @RunWith(Parameterized.class)
 public class DesugarStaticInterfaceMethodsRetraceTest extends RetraceTestBase {
 
-  @Parameters(name = "{0}, mode: {1}")
+  @Parameters(name = "{0}, mode: {1}, compat: {2}")
   public static Collection<Object[]> data() {
     return buildParameters(
-        getTestParameters().withAllRuntimesAndApiLevels().build(), CompilationMode.values());
+        getTestParameters().withAllRuntimesAndApiLevels().build(),
+        CompilationMode.values(),
+        BooleanUtils.values());
   }
 
-  public DesugarStaticInterfaceMethodsRetraceTest(TestParameters parameters, CompilationMode mode) {
-    super(parameters, mode);
+  public DesugarStaticInterfaceMethodsRetraceTest(
+      TestParameters parameters, CompilationMode mode, boolean compat) {
+    super(parameters, mode, compat);
   }
 
   @Override
diff --git a/src/test/java/com/android/tools/r8/naming/retrace/InliningRetraceTest.java b/src/test/java/com/android/tools/r8/naming/retrace/InliningRetraceTest.java
index 4ffc7de..48fc320 100644
--- a/src/test/java/com/android/tools/r8/naming/retrace/InliningRetraceTest.java
+++ b/src/test/java/com/android/tools/r8/naming/retrace/InliningRetraceTest.java
@@ -9,10 +9,12 @@
 import static com.android.tools.r8.naming.retrace.StackTrace.isSameExceptForFileNameAndLineNumber;
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assume.assumeTrue;
 
 import com.android.tools.r8.CompilationMode;
 import com.android.tools.r8.ForceInline;
 import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.utils.BooleanUtils;
 import com.google.common.collect.ImmutableList;
 import java.util.Collection;
 import org.junit.Test;
@@ -23,14 +25,16 @@
 @RunWith(Parameterized.class)
 public class InliningRetraceTest extends RetraceTestBase {
 
-  @Parameters(name = "{0}, mode: {1}")
+  @Parameters(name = "{0}, mode: {1}, compat: {2}")
   public static Collection<Object[]> data() {
     return buildParameters(
-        getTestParameters().withAllRuntimesAndApiLevels().build(), CompilationMode.values());
+        getTestParameters().withAllRuntimesAndApiLevels().build(),
+        CompilationMode.values(),
+        BooleanUtils.values());
   }
 
-  public InliningRetraceTest(TestParameters parameters, CompilationMode mode) {
-    super(parameters, mode);
+  public InliningRetraceTest(TestParameters parameters, CompilationMode mode, boolean compat) {
+    super(parameters, mode, compat);
   }
 
   @Override
@@ -54,6 +58,7 @@
 
   @Test
   public void testLineNumberTableOnly() throws Exception {
+    assumeTrue(compat);
     runTest(
         ImmutableList.of("-keepattributes LineNumberTable"),
         (StackTrace actualStackTrace, StackTrace retracedStackTrace) -> {
@@ -64,6 +69,7 @@
 
   @Test
   public void testNoLineNumberTable() throws Exception {
+    assumeTrue(compat);
     runTest(
         ImmutableList.of(),
         (StackTrace actualStackTrace, StackTrace retracedStackTrace) -> {
diff --git a/src/test/java/com/android/tools/r8/naming/retrace/RetraceTestBase.java b/src/test/java/com/android/tools/r8/naming/retrace/RetraceTestBase.java
index 3da28ae..adc5cb8 100644
--- a/src/test/java/com/android/tools/r8/naming/retrace/RetraceTestBase.java
+++ b/src/test/java/com/android/tools/r8/naming/retrace/RetraceTestBase.java
@@ -5,7 +5,7 @@
 package com.android.tools.r8.naming.retrace;
 
 import com.android.tools.r8.CompilationMode;
-import com.android.tools.r8.R8FullTestBuilder;
+import com.android.tools.r8.R8TestBuilder;
 import com.android.tools.r8.R8TestRunResult;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
@@ -18,15 +18,17 @@
 public abstract class RetraceTestBase extends TestBase {
   protected TestParameters parameters;
   protected CompilationMode mode;
+  protected boolean compat;
 
-  public RetraceTestBase(TestParameters parameters, CompilationMode mode) {
+  public RetraceTestBase(TestParameters parameters, CompilationMode mode, boolean compat) {
     this.parameters = parameters;
     this.mode = mode;
+    this.compat = compat;
   }
 
   public StackTrace expectedStackTrace;
 
-  public void configure(R8FullTestBuilder builder) {}
+  public void configure(R8TestBuilder builder) {}
 
   public Collection<Class<?>> getClasses() {
     return ImmutableList.of(getMainClass());
@@ -49,7 +51,7 @@
       throws Exception {
 
     R8TestRunResult result =
-        testForR8(parameters.getBackend())
+        (compat ? testForR8Compat(parameters.getBackend()) : testForR8(parameters.getBackend()))
             .setMode(mode)
             .enableProguardTestOptions()
             .addProgramClasses(getClasses())
diff --git a/src/test/java/com/android/tools/r8/naming/retrace/StackTrace.java b/src/test/java/com/android/tools/r8/naming/retrace/StackTrace.java
index d4b758e..2d5e31b 100644
--- a/src/test/java/com/android/tools/r8/naming/retrace/StackTrace.java
+++ b/src/test/java/com/android/tools/r8/naming/retrace/StackTrace.java
@@ -205,6 +205,10 @@
     return originalStderr;
   }
 
+  public List<StackTraceLine> getStackTraceLines() {
+    return stackTraceLines;
+  }
+
   public static StackTrace extractFromArt(String stderr, DexVm vm) {
     List<StackTraceLine> stackTraceLines = new ArrayList<>();
     List<String> stderrLines = StringUtils.splitLines(stderr);
diff --git a/src/test/java/com/android/tools/r8/naming/retrace/VerticalClassMergingRetraceTest.java b/src/test/java/com/android/tools/r8/naming/retrace/VerticalClassMergingRetraceTest.java
index 6cd268f..46da60b 100644
--- a/src/test/java/com/android/tools/r8/naming/retrace/VerticalClassMergingRetraceTest.java
+++ b/src/test/java/com/android/tools/r8/naming/retrace/VerticalClassMergingRetraceTest.java
@@ -7,12 +7,14 @@
 import static com.android.tools.r8.naming.retrace.StackTrace.isSameExceptForFileNameAndLineNumber;
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assume.assumeTrue;
 
 import com.android.tools.r8.CompilationMode;
 import com.android.tools.r8.NeverInline;
-import com.android.tools.r8.R8FullTestBuilder;
+import com.android.tools.r8.R8TestBuilder;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.naming.retrace.StackTrace.StackTraceLine;
+import com.android.tools.r8.utils.BooleanUtils;
 import com.google.common.collect.ImmutableList;
 import java.util.Collection;
 import java.util.HashSet;
@@ -26,18 +28,21 @@
 public class VerticalClassMergingRetraceTest extends RetraceTestBase {
   private Set<StackTraceLine> haveSeenLines = new HashSet<>();
 
-  @Parameters(name = "{0}, mode: {1}")
+  @Parameters(name = "{0}, mode: {1}, compat: {2}")
   public static Collection<Object[]> data() {
     return buildParameters(
-        getTestParameters().withAllRuntimesAndApiLevels().build(), CompilationMode.values());
+        getTestParameters().withAllRuntimesAndApiLevels().build(),
+        CompilationMode.values(),
+        BooleanUtils.values());
   }
 
-  public VerticalClassMergingRetraceTest(TestParameters parameters, CompilationMode mode) {
-    super(parameters, mode);
+  public VerticalClassMergingRetraceTest(
+      TestParameters parameters, CompilationMode mode, boolean compat) {
+    super(parameters, mode, compat);
   }
 
   @Override
-  public void configure(R8FullTestBuilder builder) {
+  public void configure(R8TestBuilder builder) {
     builder.enableInliningAnnotations();
   }
 
@@ -87,6 +92,7 @@
 
   @Test
   public void testLineNumberTableOnly() throws Exception {
+    assumeTrue(compat);
     runTest(
         ImmutableList.of("-keepattributes LineNumberTable"),
         (StackTrace actualStackTrace, StackTrace retracedStackTrace) -> {
@@ -125,6 +131,7 @@
     // at com.android.tools.r8.naming.retraceproguard.ResourceWrapper.foo(ResourceWrapper.java:0)
     // at com.android.tools.r8.naming.retraceproguard.MainApp.main(MainApp.java:7)
     // since the synthetic bridge belongs to ResourceWrapper.foo.
+    assumeTrue(compat);
     haveSeenLines.clear();
     runTest(
         ImmutableList.of(),
diff --git a/src/test/java/com/android/tools/r8/naming/retraceproguard/DesugarLambdaRetraceTest.java b/src/test/java/com/android/tools/r8/naming/retraceproguard/DesugarLambdaRetraceTest.java
index 42f2857..a527d93 100644
--- a/src/test/java/com/android/tools/r8/naming/retraceproguard/DesugarLambdaRetraceTest.java
+++ b/src/test/java/com/android/tools/r8/naming/retraceproguard/DesugarLambdaRetraceTest.java
@@ -12,6 +12,7 @@
 import com.android.tools.r8.CompilationMode;
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.naming.retraceproguard.StackTrace.StackTraceLine;
+import com.android.tools.r8.utils.BooleanUtils;
 import com.google.common.collect.ImmutableList;
 import java.util.Collection;
 import org.junit.Ignore;
@@ -23,13 +24,14 @@
 @RunWith(Parameterized.class)
 public class DesugarLambdaRetraceTest extends RetraceTestBase {
 
-  @Parameters(name = "Backend: {0}, mode: {1}")
+  @Parameters(name = "Backend: {0}, mode: {1}, compat: {2}")
   public static Collection<Object[]> data() {
-    return buildParameters(ToolHelper.getBackends(), CompilationMode.values());
+    return buildParameters(
+        ToolHelper.getBackends(), CompilationMode.values(), BooleanUtils.values());
   }
 
-  public DesugarLambdaRetraceTest(Backend backend, CompilationMode mode) {
-    super(backend, mode);
+  public DesugarLambdaRetraceTest(Backend backend, CompilationMode mode, boolean compat) {
+    super(backend, mode, compat);
   }
 
   @Override
diff --git a/src/test/java/com/android/tools/r8/naming/retraceproguard/DesugarStaticInterfaceMethodsRetraceTest.java b/src/test/java/com/android/tools/r8/naming/retraceproguard/DesugarStaticInterfaceMethodsRetraceTest.java
index acd5baa..2034f5d 100644
--- a/src/test/java/com/android/tools/r8/naming/retraceproguard/DesugarStaticInterfaceMethodsRetraceTest.java
+++ b/src/test/java/com/android/tools/r8/naming/retraceproguard/DesugarStaticInterfaceMethodsRetraceTest.java
@@ -10,6 +10,7 @@
 import com.android.tools.r8.CompilationMode;
 import com.android.tools.r8.NeverInline;
 import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.utils.BooleanUtils;
 import com.google.common.collect.ImmutableList;
 import java.util.Collection;
 import org.junit.Test;
@@ -20,13 +21,15 @@
 @RunWith(Parameterized.class)
 public class DesugarStaticInterfaceMethodsRetraceTest extends RetraceTestBase {
 
-  @Parameters(name = "Backend: {0}, mode: {1}")
+  @Parameters(name = "Backend: {0}, mode: {1}, compat: {2}")
   public static Collection<Object[]> data() {
-    return buildParameters(ToolHelper.getBackends(), CompilationMode.values());
+    return buildParameters(
+        ToolHelper.getBackends(), CompilationMode.values(), BooleanUtils.values());
   }
 
-  public DesugarStaticInterfaceMethodsRetraceTest(Backend backend, CompilationMode mode) {
-    super(backend, mode);
+  public DesugarStaticInterfaceMethodsRetraceTest(
+      Backend backend, CompilationMode mode, boolean compat) {
+    super(backend, mode, compat);
   }
 
   @Override
diff --git a/src/test/java/com/android/tools/r8/naming/retraceproguard/InliningRetraceTest.java b/src/test/java/com/android/tools/r8/naming/retraceproguard/InliningRetraceTest.java
index 4f84c8f..aafa229 100644
--- a/src/test/java/com/android/tools/r8/naming/retraceproguard/InliningRetraceTest.java
+++ b/src/test/java/com/android/tools/r8/naming/retraceproguard/InliningRetraceTest.java
@@ -8,11 +8,13 @@
 import static com.android.tools.r8.naming.retraceproguard.StackTrace.isSameExceptForFileNameAndLineNumber;
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assume.assumeTrue;
 
 import com.android.tools.r8.CompilationMode;
 import com.android.tools.r8.ForceInline;
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.ToolHelper.DexVm.Version;
+import com.android.tools.r8.utils.BooleanUtils;
 import com.google.common.collect.ImmutableList;
 import java.util.Collection;
 import java.util.Collections;
@@ -28,11 +30,12 @@
   public static Collection<Object[]> data() {
     return ToolHelper.getDexVm().getVersion() == Version.V5_1_1
         ? Collections.emptyList()
-        : buildParameters(ToolHelper.getBackends(), CompilationMode.values());
+        : buildParameters(
+            ToolHelper.getBackends(), CompilationMode.values(), BooleanUtils.values());
   }
 
-  public InliningRetraceTest(Backend backend, CompilationMode mode) {
-    super(backend, mode);
+  public InliningRetraceTest(Backend backend, CompilationMode mode, boolean value) {
+    super(backend, mode, value);
   }
 
   @Override
@@ -57,6 +60,7 @@
 
   @Test
   public void testLineNumberTableOnly() throws Exception {
+    assumeTrue(compat);
     runTest(
         ImmutableList.of("-keepattributes LineNumberTable"),
         (StackTrace actualStackTrace, StackTrace retracedStackTrace) -> {
@@ -67,6 +71,7 @@
 
   @Test
   public void testNoLineNumberTable() throws Exception {
+    assumeTrue(compat);
     runTest(
         ImmutableList.of(),
         (StackTrace actualStackTrace, StackTrace retracedStackTrace) -> {
diff --git a/src/test/java/com/android/tools/r8/naming/retraceproguard/RetraceTestBase.java b/src/test/java/com/android/tools/r8/naming/retraceproguard/RetraceTestBase.java
index 4a877e2..e94483f 100644
--- a/src/test/java/com/android/tools/r8/naming/retraceproguard/RetraceTestBase.java
+++ b/src/test/java/com/android/tools/r8/naming/retraceproguard/RetraceTestBase.java
@@ -5,7 +5,7 @@
 package com.android.tools.r8.naming.retraceproguard;
 
 import com.android.tools.r8.CompilationMode;
-import com.android.tools.r8.R8FullTestBuilder;
+import com.android.tools.r8.R8TestBuilder;
 import com.android.tools.r8.R8TestRunResult;
 import com.android.tools.r8.TestBase;
 import com.google.common.collect.ImmutableList;
@@ -17,15 +17,17 @@
 public abstract class RetraceTestBase extends TestBase {
   protected Backend backend;
   protected CompilationMode mode;
+  protected boolean compat;
 
-  public RetraceTestBase(Backend backend, CompilationMode mode) {
+  public RetraceTestBase(Backend backend, CompilationMode mode, boolean compat) {
     this.backend = backend;
     this.mode = mode;
+    this.compat = compat;
   }
 
   public StackTrace expectedStackTrace;
 
-  public void configure(R8FullTestBuilder builder) {}
+  public void configure(R8TestBuilder builder) {}
 
   public Collection<Class<?>> getClasses() {
     return ImmutableList.of(getMainClass());
@@ -48,7 +50,7 @@
       throws Exception {
 
     R8TestRunResult result =
-        testForR8(backend)
+        (compat ? testForR8Compat(backend) : testForR8(backend))
             .setMode(mode)
             .enableProguardTestOptions()
             .addProgramClasses(getClasses())
diff --git a/src/test/java/com/android/tools/r8/naming/retraceproguard/VerticalClassMergingRetraceTest.java b/src/test/java/com/android/tools/r8/naming/retraceproguard/VerticalClassMergingRetraceTest.java
index 42264a2..2874bf9 100644
--- a/src/test/java/com/android/tools/r8/naming/retraceproguard/VerticalClassMergingRetraceTest.java
+++ b/src/test/java/com/android/tools/r8/naming/retraceproguard/VerticalClassMergingRetraceTest.java
@@ -7,12 +7,14 @@
 import static com.android.tools.r8.naming.retraceproguard.StackTrace.isSameExceptForFileNameAndLineNumber;
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assume.assumeTrue;
 
 import com.android.tools.r8.CompilationMode;
 import com.android.tools.r8.NeverInline;
-import com.android.tools.r8.R8FullTestBuilder;
+import com.android.tools.r8.R8TestBuilder;
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.naming.retraceproguard.StackTrace.StackTraceLine;
+import com.android.tools.r8.utils.BooleanUtils;
 import com.google.common.collect.ImmutableList;
 import java.util.Collection;
 import java.util.HashSet;
@@ -26,17 +28,18 @@
 public class VerticalClassMergingRetraceTest extends RetraceTestBase {
   private Set<StackTraceLine> haveSeenLines = new HashSet<>();
 
-  @Parameters(name = "Backend: {0}, mode: {1}")
+  @Parameters(name = "Backend: {0}, mode: {1}, compat: {2}")
   public static Collection<Object[]> data() {
-    return buildParameters(ToolHelper.getBackends(), CompilationMode.values());
+    return buildParameters(
+        ToolHelper.getBackends(), CompilationMode.values(), BooleanUtils.values());
   }
 
-  public VerticalClassMergingRetraceTest(Backend backend, CompilationMode mode) {
-    super(backend, mode);
+  public VerticalClassMergingRetraceTest(Backend backend, CompilationMode mode, boolean compat) {
+    super(backend, mode, compat);
   }
 
   @Override
-  public void configure(R8FullTestBuilder builder) {
+  public void configure(R8TestBuilder builder) {
     builder.enableInliningAnnotations();
   }
 
@@ -83,6 +86,7 @@
 
   @Test
   public void testLineNumberTableOnly() throws Exception {
+    assumeTrue(compat);
     runTest(
         ImmutableList.of("-keepattributes LineNumberTable"),
         (StackTrace actualStackTrace, StackTrace retracedStackTrace) -> {
@@ -97,6 +101,7 @@
 
   @Test
   public void testNoLineNumberTable() throws Exception {
+    assumeTrue(compat);
     haveSeenLines.clear();
     runTest(
         ImmutableList.of(),
diff --git a/src/test/java/com/android/tools/r8/naming/testclasses/ClassToBeMinified.java b/src/test/java/com/android/tools/r8/naming/testclasses/ClassToBeMinified.java
new file mode 100644
index 0000000..da6f9c0
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/naming/testclasses/ClassToBeMinified.java
@@ -0,0 +1,15 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.naming.testclasses;
+
+import com.android.tools.r8.NeverInline;
+
+public class ClassToBeMinified {
+
+  @NeverInline
+  public static void foo() {
+    throw new RuntimeException("ClassToBeMinified.foo()");
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/naming/testclasses/Main.java b/src/test/java/com/android/tools/r8/naming/testclasses/Main.java
new file mode 100644
index 0000000..1e37d4c
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/naming/testclasses/Main.java
@@ -0,0 +1,12 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.naming.testclasses;
+
+public class Main {
+
+  public static void main(String[] args) {
+    ClassToBeMinified.foo();
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/resolution/singletarget/InstantiatedLowerBoundTest.java b/src/test/java/com/android/tools/r8/resolution/singletarget/InstantiatedLowerBoundTest.java
new file mode 100644
index 0000000..deec1e3
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/resolution/singletarget/InstantiatedLowerBoundTest.java
@@ -0,0 +1,165 @@
+// 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.resolution.singletarget;
+
+import static junit.framework.TestCase.assertEquals;
+import static junit.framework.TestCase.assertNotNull;
+import static junit.framework.TestCase.assertTrue;
+import static org.junit.Assert.assertNull;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.LookupResult;
+import com.android.tools.r8.graph.ResolutionResult;
+import com.android.tools.r8.ir.analysis.type.ClassTypeLatticeElement;
+import com.android.tools.r8.ir.analysis.type.Nullability;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.google.common.collect.Sets;
+import java.util.ArrayList;
+import java.util.Set;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class InstantiatedLowerBoundTest extends TestBase {
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withNoneRuntime().build();
+  }
+
+  public InstantiatedLowerBoundTest(TestParameters parameters) {
+    // Empty to satisfy construction of none-runtime.
+  }
+
+  @Test
+  public void testSingleTargetLowerBoundInstantiated() throws Exception {
+    AppView<AppInfoWithLiveness> appView =
+        computeAppViewWithLiveness(
+            buildClasses(A.class, B.class, Main.class).build(),
+            factory -> new ArrayList<>(buildKeepRuleForClassAndMethods(Main.class, factory)));
+    AppInfoWithLiveness appInfo = appView.appInfo();
+    DexType typeA = buildType(A.class, appInfo.dexItemFactory());
+    DexType typeB = buildType(B.class, appInfo.dexItemFactory());
+    DexType typeMain = buildType(Main.class, appInfo.dexItemFactory());
+    DexMethod fooA = buildNullaryVoidMethod(A.class, "foo", appInfo.dexItemFactory());
+    ClassTypeLatticeElement latticeB =
+        ClassTypeLatticeElement.create(typeB, Nullability.definitelyNotNull(), appView);
+    DexEncodedMethod singleTarget =
+        appInfo.lookupSingleVirtualTarget(fooA, typeMain, false, t -> false, typeA, latticeB);
+    assertNotNull(singleTarget);
+    DexMethod fooB = buildNullaryVoidMethod(B.class, "foo", appInfo.dexItemFactory());
+    assertEquals(fooB, singleTarget.method);
+  }
+
+  @Test
+  public void testSingleTargetLowerBoundInMiddleInstantiated() throws Exception {
+    AppView<AppInfoWithLiveness> appView =
+        computeAppViewWithLiveness(
+            buildClasses(A.class, B.class, C.class, Main.class).build(),
+            factory -> new ArrayList<>(buildKeepRuleForClassAndMethods(Main.class, factory)));
+    AppInfoWithLiveness appInfo = appView.appInfo();
+    DexType typeA = buildType(A.class, appInfo.dexItemFactory());
+    DexType typeB = buildType(B.class, appInfo.dexItemFactory());
+    DexType typeMain = buildType(Main.class, appInfo.dexItemFactory());
+    DexMethod fooA = buildNullaryVoidMethod(A.class, "foo", appInfo.dexItemFactory());
+    ClassTypeLatticeElement latticeB =
+        ClassTypeLatticeElement.create(typeB, Nullability.definitelyNotNull(), appView);
+    DexEncodedMethod singleTarget =
+        appInfo.lookupSingleVirtualTarget(fooA, typeMain, false, t -> false, typeA, latticeB);
+    assertNotNull(singleTarget);
+    DexMethod fooB = buildNullaryVoidMethod(B.class, "foo", appInfo.dexItemFactory());
+    assertEquals(fooB, singleTarget.method);
+  }
+
+  @Test
+  public void testSingleTargetLowerAllInstantiated() throws Exception {
+    AppView<AppInfoWithLiveness> appView =
+        computeAppViewWithLiveness(
+            buildClasses(A.class, B.class, C.class, MainAllInstantiated.class).build(),
+            factory ->
+                new ArrayList<>(
+                    buildKeepRuleForClassAndMethods(MainAllInstantiated.class, factory)));
+    AppInfoWithLiveness appInfo = appView.appInfo();
+    DexType typeA = buildType(A.class, appInfo.dexItemFactory());
+    DexType typeC = buildType(C.class, appInfo.dexItemFactory());
+    DexType typeMain = buildType(MainAllInstantiated.class, appInfo.dexItemFactory());
+    DexMethod fooA = buildNullaryVoidMethod(A.class, "foo", appInfo.dexItemFactory());
+    DexMethod fooB = buildNullaryVoidMethod(B.class, "foo", appInfo.dexItemFactory());
+    DexMethod fooC = buildNullaryVoidMethod(C.class, "foo", appInfo.dexItemFactory());
+    ResolutionResult resolution = appInfo.resolveMethod(typeA, fooA);
+    DexProgramClass context = appView.definitionForProgramType(typeMain);
+    DexProgramClass upperBound = appView.definitionForProgramType(typeA);
+    DexProgramClass lowerBound = appView.definitionForProgramType(typeC);
+    LookupResult lookupResult =
+        resolution.lookupVirtualDispatchTargets(context, appInfo, upperBound, lowerBound);
+    Set<DexMethod> expected = Sets.newIdentityHashSet();
+    expected.add(fooA);
+    expected.add(fooB);
+    expected.add(fooC);
+    assertTrue(lookupResult.isLookupResultSuccess());
+    Set<DexMethod> actual = Sets.newIdentityHashSet();
+    lookupResult
+        .asLookupResultSuccess()
+        .forEach(
+            clazzAndMethod -> actual.add(clazzAndMethod.getMethod().method),
+            lambdaTarget -> {
+              assert false;
+            });
+    assertEquals(expected, actual);
+    ClassTypeLatticeElement latticeC =
+        ClassTypeLatticeElement.create(typeC, Nullability.definitelyNotNull(), appView);
+    DexEncodedMethod singleTarget =
+        appInfo.lookupSingleVirtualTarget(fooA, typeMain, false, t -> false, typeA, latticeC);
+    assertNull(singleTarget);
+  }
+
+  public static class A {
+
+    public void foo() {
+      System.out.println("A.foo");
+    }
+  }
+
+  public static class B extends A {
+
+    @Override
+    public void foo() {
+      System.out.println("B.foo");
+    }
+  }
+
+  public static class C extends B {
+
+    @Override
+    public void foo() {
+      System.out.println("C.foo");
+    }
+  }
+
+  public static class Main {
+
+    public static void main(String[] args) {
+      new B();
+    }
+  }
+
+  public static class MainAllInstantiated {
+
+    public static void main(String[] args) {
+      new A();
+      new B();
+      new C();
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/resolution/singletarget/SuccessAndInvalidLookupTest.java b/src/test/java/com/android/tools/r8/resolution/singletarget/SuccessAndInvalidLookupTest.java
new file mode 100644
index 0000000..870c9cc
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/resolution/singletarget/SuccessAndInvalidLookupTest.java
@@ -0,0 +1,95 @@
+// 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.resolution.singletarget;
+
+import static junit.framework.TestCase.assertEquals;
+import static junit.framework.TestCase.assertNotNull;
+import static junit.framework.TestCase.assertNull;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import java.util.ArrayList;
+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 SuccessAndInvalidLookupTest extends TestBase {
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withNoneRuntime().build();
+  }
+
+  public SuccessAndInvalidLookupTest(TestParameters parameters) {
+    // Empty to satisfy construction of none-runtime.
+  }
+
+  @Test
+  public void testSingleTargetWithInvalidInvokeInterfaceInvoke() throws Exception {
+    AppView<AppInfoWithLiveness> appView =
+        computeAppViewWithLiveness(
+            buildClasses(I.class, A.class, Main.class).build(),
+            factory -> new ArrayList<>(buildKeepRuleForClassAndMethods(Main.class, factory)));
+    AppInfoWithLiveness appInfo = appView.appInfo();
+    DexType typeMain = buildType(Main.class, appInfo.dexItemFactory());
+    DexType typeA = buildType(A.class, appInfo.dexItemFactory());
+    DexMethod fooA = buildNullaryVoidMethod(A.class, "foo", appInfo.dexItemFactory());
+    DexEncodedMethod singleTarget =
+        appInfo.lookupSingleVirtualTarget(fooA, typeMain, false, t -> false, typeA, null);
+    assertNotNull(singleTarget);
+    assertEquals(fooA, singleTarget.method);
+    DexEncodedMethod invalidSingleTarget =
+        appInfo.lookupSingleVirtualTarget(fooA, typeMain, true, t -> false, typeA, null);
+    assertNull(invalidSingleTarget);
+  }
+
+  @Test
+  public void testSingleTargetWithInvalidInvokeVirtualInvoke() throws Exception {
+    AppView<AppInfoWithLiveness> appView =
+        computeAppViewWithLiveness(
+            buildClasses(I.class, A.class, Main.class).build(),
+            factory -> new ArrayList<>(buildKeepRuleForClassAndMethods(Main.class, factory)));
+    AppInfoWithLiveness appInfo = appView.appInfo();
+    DexType typeMain = buildType(Main.class, appInfo.dexItemFactory());
+    DexType typeA = buildType(I.class, appInfo.dexItemFactory());
+    DexMethod fooI = buildNullaryVoidMethod(I.class, "foo", appInfo.dexItemFactory());
+    DexMethod fooA = buildNullaryVoidMethod(A.class, "foo", appInfo.dexItemFactory());
+    DexEncodedMethod singleTarget =
+        appInfo.lookupSingleVirtualTarget(fooI, typeMain, true, t -> false, typeA, null);
+    assertNotNull(singleTarget);
+    assertEquals(fooA, singleTarget.method);
+    DexEncodedMethod invalidSingleTarget =
+        appInfo.lookupSingleVirtualTarget(fooI, typeMain, false, t -> false, typeA, null);
+    assertNull(invalidSingleTarget);
+  }
+
+  public interface I {
+
+    void foo();
+  }
+
+  public static class A implements I {
+
+    @Override
+    public void foo() {
+      System.out.println("A.foo");
+    }
+  }
+
+  public static class Main {
+
+    public static void main(String[] args) {
+      new A();
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/retrace/InlineWithoutNullCheckTest.java b/src/test/java/com/android/tools/r8/retrace/InlineWithoutNullCheckTest.java
index 809a521..3cabc0e 100644
--- a/src/test/java/com/android/tools/r8/retrace/InlineWithoutNullCheckTest.java
+++ b/src/test/java/com/android/tools/r8/retrace/InlineWithoutNullCheckTest.java
@@ -18,6 +18,7 @@
 import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.TestRuntime.CfVm;
 import com.android.tools.r8.naming.retrace.StackTrace;
+import com.android.tools.r8.shaking.ProguardKeepAttributes;
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
@@ -110,6 +111,7 @@
         .addInnerClasses(InlineWithoutNullCheckTest.class)
         .addKeepMainRule(TestClassForInlineMethod.class)
         .enableInliningAnnotations()
+        .addKeepAttributes(ProguardKeepAttributes.SOURCE_FILE)
         .setMinApi(parameters.getApiLevel())
         .compile()
         .inspect(this::checkSomething)
@@ -125,8 +127,6 @@
                     isSameExceptForFileNameAndLineNumber(
                         createStackTraceBuilder()
                             .addWithoutFileNameAndLineNumber(
-                                Result.class, "methodWhichAccessInstanceMethod")
-                            .addWithoutFileNameAndLineNumber(
                                 A.class, "inlineMethodWhichAccessInstanceMethod")
                             .addWithoutFileNameAndLineNumber(TestClassForInlineMethod.class, "main")
                             .build())));
@@ -138,6 +138,7 @@
         .addInnerClasses(InlineWithoutNullCheckTest.class)
         .addKeepMainRule(TestClassForInlineField.class)
         .enableInliningAnnotations()
+        .addKeepAttributes(ProguardKeepAttributes.SOURCE_FILE)
         .setMinApi(parameters.getApiLevel())
         .compile()
         .inspect(this::checkSomething)
@@ -153,8 +154,6 @@
                     isSameExceptForFileNameAndLineNumber(
                         createStackTraceBuilder()
                             .addWithoutFileNameAndLineNumber(
-                                Result.class, "methodWhichAccessInstanceField")
-                            .addWithoutFileNameAndLineNumber(
                                 A.class, "inlineMethodWhichAccessInstanceField")
                             .addWithoutFileNameAndLineNumber(TestClassForInlineField.class, "main")
                             .build())));
@@ -167,6 +166,7 @@
         .addInnerClasses(InlineWithoutNullCheckTest.class)
         .addKeepMainRule(TestClassForInlineStaticField.class)
         .enableInliningAnnotations()
+        .addKeepAttributes(ProguardKeepAttributes.SOURCE_FILE)
         .setMinApi(parameters.getApiLevel())
         .compile()
         .inspect(this::checkSomething)
@@ -183,8 +183,6 @@
                     isSameExceptForFileNameAndLineNumber(
                         createStackTraceBuilder()
                             .addWithoutFileNameAndLineNumber(
-                                Result.class, "methodWhichAccessStaticField")
-                            .addWithoutFileNameAndLineNumber(
                                 A.class, "inlineMethodWhichAccessStaticField")
                             .addWithoutFileNameAndLineNumber(
                                 TestClassForInlineStaticField.class, "main")
@@ -205,8 +203,8 @@
   }
 
   private boolean canUseRequireNonNull() {
-    return parameters.isCfRuntime()
-        || parameters.getApiLevel().isGreaterThanOrEqualTo(AndroidApiLevel.K);
+    return parameters.isDexRuntime()
+        && parameters.getApiLevel().isGreaterThanOrEqualTo(AndroidApiLevel.K);
   }
 
   static class TestClassForInlineMethod {
diff --git a/src/test/java/com/android/tools/r8/retrace/KotlinInlineFunctionInSameFileRetraceTests.java b/src/test/java/com/android/tools/r8/retrace/KotlinInlineFunctionInSameFileRetraceTests.java
index d70f22b..1fa811a 100644
--- a/src/test/java/com/android/tools/r8/retrace/KotlinInlineFunctionInSameFileRetraceTests.java
+++ b/src/test/java/com/android/tools/r8/retrace/KotlinInlineFunctionInSameFileRetraceTests.java
@@ -6,7 +6,7 @@
 import static com.android.tools.r8.Collectors.toSingle;
 import static com.android.tools.r8.KotlinCompilerTool.KOTLINC;
 import static com.android.tools.r8.ToolHelper.getFilesInTestFolderRelativeToClass;
-import static com.android.tools.r8.utils.codeinspector.Matchers.containsInlinePosition;
+import static com.android.tools.r8.utils.codeinspector.Matchers.containsLinePositions;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isInlineFrame;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isInlineStack;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
@@ -26,7 +26,7 @@
 import com.android.tools.r8.naming.retrace.StackTrace;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.android.tools.r8.utils.codeinspector.InstructionSubject;
-import com.android.tools.r8.utils.codeinspector.Matchers.InlinePosition;
+import com.android.tools.r8.utils.codeinspector.Matchers.LinePosition;
 import com.android.tools.r8.utils.codeinspector.MethodSubject;
 import java.io.IOException;
 import java.nio.file.Path;
@@ -40,6 +40,7 @@
 @RunWith(Parameterized.class)
 public class KotlinInlineFunctionInSameFileRetraceTests extends TestBase {
 
+  private static final String FILENAME_INLINE = "InlineFunctionsInSameFile.kt";
   private static final String MAIN = "retrace.InlineFunctionsInSameFileKt";
 
   private final TestParameters parameters;
@@ -97,16 +98,18 @@
         .inspectStackTrace(
             (stackTrace, codeInspector) -> {
               MethodSubject mainSubject = codeInspector.clazz(MAIN).uniqueMethodWithName("main");
-              InlinePosition inlineStack =
-                  InlinePosition.stack(
-                      InlinePosition.create(
+              LinePosition inlineStack =
+                  LinePosition.stack(
+                      LinePosition.create(
                           kotlinInspector
                               .clazz("retrace.InlineFunctionsInSameFileKt")
                               .uniqueMethodWithName("foo")
                               .asFoundMethodSubject(),
                           1,
-                          8),
-                      InlinePosition.create(mainSubject.asFoundMethodSubject(), 1, 43));
+                          8,
+                          FILENAME_INLINE),
+                      LinePosition.create(
+                          mainSubject.asFoundMethodSubject(), 1, 43, FILENAME_INLINE));
               checkInlineInformation(stackTrace, codeInspector, mainSubject, inlineStack);
             });
   }
@@ -115,7 +118,7 @@
       StackTrace stackTrace,
       CodeInspector codeInspector,
       MethodSubject mainSubject,
-      InlinePosition inlineStack) {
+      LinePosition inlineStack) {
     assertThat(mainSubject, isPresent());
     RetraceMethodResult retraceResult =
         mainSubject
@@ -125,6 +128,6 @@
             .retraceLinePosition(codeInspector.retrace());
     assertThat(retraceResult, isInlineFrame());
     assertThat(retraceResult, isInlineStack(inlineStack));
-    assertThat(stackTrace, containsInlinePosition(inlineStack));
+    assertThat(stackTrace, containsLinePositions(inlineStack));
   }
 }
diff --git a/src/test/java/com/android/tools/r8/retrace/KotlinInlineFunctionRetraceTest.java b/src/test/java/com/android/tools/r8/retrace/KotlinInlineFunctionRetraceTest.java
index b4d302b..05b028a 100644
--- a/src/test/java/com/android/tools/r8/retrace/KotlinInlineFunctionRetraceTest.java
+++ b/src/test/java/com/android/tools/r8/retrace/KotlinInlineFunctionRetraceTest.java
@@ -7,7 +7,7 @@
 import static com.android.tools.r8.Collectors.toSingle;
 import static com.android.tools.r8.KotlinCompilerTool.KOTLINC;
 import static com.android.tools.r8.ToolHelper.getFilesInTestFolderRelativeToClass;
-import static com.android.tools.r8.utils.codeinspector.Matchers.containsInlinePosition;
+import static com.android.tools.r8.utils.codeinspector.Matchers.containsLinePositions;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isInlineFrame;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isInlineStack;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
@@ -28,7 +28,7 @@
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.android.tools.r8.utils.codeinspector.FoundMethodSubject;
 import com.android.tools.r8.utils.codeinspector.InstructionSubject;
-import com.android.tools.r8.utils.codeinspector.Matchers.InlinePosition;
+import com.android.tools.r8.utils.codeinspector.Matchers.LinePosition;
 import com.android.tools.r8.utils.codeinspector.MethodSubject;
 import java.io.IOException;
 import java.nio.file.Path;
@@ -43,6 +43,9 @@
 public class KotlinInlineFunctionRetraceTest extends TestBase {
 
   private final TestParameters parameters;
+  // TODO(b/151132660): Fix filename
+  private static final String FILENAME_INLINE_STATIC = "InlineFunctionKt.kt";
+  private static final String FILENAME_INLINE_INSTANCE = "InlineFunction.kt";
 
   @Parameters(name = "{0}")
   public static TestParametersCollection data() {
@@ -93,6 +96,7 @@
   public void testRetraceKotlinInlineStaticFunction()
       throws ExecutionException, CompilationFailedException, IOException {
     String main = "retrace.MainKt";
+    String mainFileName = "Main.kt";
     Path kotlinSources = compilationResults.apply(parameters.getRuntime());
     CodeInspector kotlinInspector = new CodeInspector(kotlinSources);
     testForR8(parameters.getBackend())
@@ -109,10 +113,11 @@
         .inspectStackTrace(
             (stackTrace, codeInspector) -> {
               MethodSubject mainSubject = codeInspector.clazz(main).uniqueMethodWithName("main");
-              InlinePosition inlineStack =
-                  InlinePosition.stack(
-                      InlinePosition.create(inlineExceptionStatic(kotlinInspector), 2, 8),
-                      InlinePosition.create(mainSubject.asFoundMethodSubject(), 2, 15));
+              LinePosition inlineStack =
+                  LinePosition.stack(
+                      LinePosition.create(
+                          inlineExceptionStatic(kotlinInspector), 2, 8, FILENAME_INLINE_STATIC),
+                      LinePosition.create(mainSubject.asFoundMethodSubject(), 2, 15, mainFileName));
               checkInlineInformation(stackTrace, codeInspector, mainSubject, inlineStack);
             });
   }
@@ -121,6 +126,7 @@
   public void testRetraceKotlinInlineInstanceFunction()
       throws ExecutionException, CompilationFailedException, IOException {
     String main = "retrace.MainInstanceKt";
+    String mainFileName = "MainInstance.kt";
     Path kotlinSources = compilationResults.apply(parameters.getRuntime());
     CodeInspector kotlinInspector = new CodeInspector(kotlinSources);
     testForR8(parameters.getBackend())
@@ -137,10 +143,14 @@
         .inspectStackTrace(
             (stackTrace, codeInspector) -> {
               MethodSubject mainSubject = codeInspector.clazz(main).uniqueMethodWithName("main");
-              InlinePosition inlineStack =
-                  InlinePosition.stack(
-                      InlinePosition.create(inlineExceptionInstance(kotlinInspector), 2, 15),
-                      InlinePosition.create(mainSubject.asFoundMethodSubject(), 2, 13));
+              LinePosition inlineStack =
+                  LinePosition.stack(
+                      LinePosition.create(
+                          inlineExceptionInstance(kotlinInspector),
+                          2,
+                          15,
+                          FILENAME_INLINE_INSTANCE),
+                      LinePosition.create(mainSubject.asFoundMethodSubject(), 2, 13, mainFileName));
               checkInlineInformation(stackTrace, codeInspector, mainSubject, inlineStack);
             });
   }
@@ -149,6 +159,7 @@
   public void testRetraceKotlinNestedInlineFunction()
       throws ExecutionException, CompilationFailedException, IOException {
     String main = "retrace.MainNestedKt";
+    String mainFileName = "MainNested.kt";
     Path kotlinSources = compilationResults.apply(parameters.getRuntime());
     CodeInspector kotlinInspector = new CodeInspector(kotlinSources);
     testForR8(parameters.getBackend())
@@ -165,12 +176,13 @@
         .inspectStackTrace(
             (stackTrace, codeInspector) -> {
               MethodSubject mainSubject = codeInspector.clazz(main).uniqueMethodWithName("main");
-              InlinePosition inlineStack =
-                  InlinePosition.stack(
-                      InlinePosition.create(inlineExceptionStatic(kotlinInspector), 3, 8),
+              LinePosition inlineStack =
+                  LinePosition.stack(
+                      LinePosition.create(
+                          inlineExceptionStatic(kotlinInspector), 3, 8, FILENAME_INLINE_STATIC),
                       // TODO(b/146399675): There should be a nested frame on
                       //  retrace.NestedInlineFunctionKt.nestedInline(line 10).
-                      InlinePosition.create(mainSubject.asFoundMethodSubject(), 3, 19));
+                      LinePosition.create(mainSubject.asFoundMethodSubject(), 3, 19, mainFileName));
               checkInlineInformation(stackTrace, codeInspector, mainSubject, inlineStack);
             });
   }
@@ -179,6 +191,7 @@
   public void testRetraceKotlinNestedInlineFunctionOnFirstLine()
       throws ExecutionException, CompilationFailedException, IOException {
     String main = "retrace.MainNestedFirstLineKt";
+    String mainFileName = "MainNestedFirstLine.kt";
     Path kotlinSources = compilationResults.apply(parameters.getRuntime());
     CodeInspector kotlinInspector = new CodeInspector(kotlinSources);
     testForR8(parameters.getBackend())
@@ -195,12 +208,13 @@
         .inspectStackTrace(
             (stackTrace, codeInspector) -> {
               MethodSubject mainSubject = codeInspector.clazz(main).uniqueMethodWithName("main");
-              InlinePosition inlineStack =
-                  InlinePosition.stack(
-                      InlinePosition.create(inlineExceptionStatic(kotlinInspector), 2, 8),
+              LinePosition inlineStack =
+                  LinePosition.stack(
+                      LinePosition.create(
+                          inlineExceptionStatic(kotlinInspector), 2, 8, FILENAME_INLINE_STATIC),
                       // TODO(b/146399675): There should be a nested frame on
                       //  retrace.NestedInlineFunctionKt.nestedInlineOnFirstLine(line 15).
-                      InlinePosition.create(mainSubject.asFoundMethodSubject(), 2, 20));
+                      LinePosition.create(mainSubject.asFoundMethodSubject(), 2, 20, mainFileName));
               checkInlineInformation(stackTrace, codeInspector, mainSubject, inlineStack);
             });
   }
@@ -209,7 +223,7 @@
       StackTrace stackTrace,
       CodeInspector codeInspector,
       MethodSubject mainSubject,
-      InlinePosition inlineStack) {
+      LinePosition inlineStack) {
     assertThat(mainSubject, isPresent());
     RetraceMethodResult retraceResult =
         mainSubject
@@ -219,6 +233,6 @@
             .retraceLinePosition(codeInspector.retrace());
     assertThat(retraceResult, isInlineFrame());
     assertThat(retraceResult, isInlineStack(inlineStack));
-    assertThat(stackTrace, containsInlinePosition(inlineStack));
+    assertThat(stackTrace, containsLinePositions(inlineStack));
   }
 }
diff --git a/src/test/java/com/android/tools/r8/retrace/RetraceTests.java b/src/test/java/com/android/tools/r8/retrace/RetraceTests.java
index 174dd5e..47dc5c3 100644
--- a/src/test/java/com/android/tools/r8/retrace/RetraceTests.java
+++ b/src/test/java/com/android/tools/r8/retrace/RetraceTests.java
@@ -32,6 +32,7 @@
 import com.android.tools.r8.retrace.stacktraces.RetraceAssertionErrorStackTrace;
 import com.android.tools.r8.retrace.stacktraces.StackTraceForTest;
 import com.android.tools.r8.retrace.stacktraces.SuppressedStackTrace;
+import com.android.tools.r8.retrace.stacktraces.UnknownSourceStackTrace;
 import com.android.tools.r8.utils.BooleanUtils;
 import com.google.common.collect.ImmutableList;
 import java.util.Collection;
@@ -172,6 +173,11 @@
     runRetraceTest(new NamedModuleStackTrace());
   }
 
+  @Test
+  public void testUnknownSourceStackTrace() {
+    runRetraceTest(new UnknownSourceStackTrace());
+  }
+
   private TestDiagnosticMessagesImpl runRetraceTest(StackTraceForTest stackTraceForTest) {
     TestDiagnosticMessagesImpl diagnosticsHandler = new TestDiagnosticMessagesImpl();
     RetraceCommand retraceCommand =
diff --git a/src/test/java/com/android/tools/r8/retrace/stacktraces/UnknownSourceStackTrace.java b/src/test/java/com/android/tools/r8/retrace/stacktraces/UnknownSourceStackTrace.java
new file mode 100644
index 0000000..4219979
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/retrace/stacktraces/UnknownSourceStackTrace.java
@@ -0,0 +1,50 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.retrace.stacktraces;
+
+import com.android.tools.r8.utils.StringUtils;
+import java.util.Arrays;
+import java.util.List;
+
+public class UnknownSourceStackTrace implements StackTraceForTest {
+
+  @Override
+  public List<String> obfuscatedStackTrace() {
+    return Arrays.asList(
+        "com.android.tools.r8.CompilationException: foo[parens](Source:3)",
+        "    at a.a.a(Unknown Source)",
+        "    at a.a.a(Unknown Source)",
+        "    at com.android.tools.r8.R8.main(Unknown Source)",
+        "Caused by: com.android.tools.r8.CompilationException: foo[parens](Source:3)",
+        "    at a.a.a(Unknown Source)",
+        "    ... 42 more");
+  }
+
+  @Override
+  public List<String> retracedStackTrace() {
+    return Arrays.asList(
+        "com.android.tools.r8.CompilationException: foo[parens](Source:3)",
+        "    at com.android.tools.r8.R8.bar(R8.java)",
+        "    <OR> at com.android.tools.r8.R8.foo(R8.java)",
+        "    at com.android.tools.r8.R8.bar(R8.java)",
+        "    <OR> at com.android.tools.r8.R8.foo(R8.java)",
+        "    at com.android.tools.r8.R8.main(R8.java)",
+        "Caused by: com.android.tools.r8.CompilationException: foo[parens](Source:3)",
+        "    at com.android.tools.r8.R8.bar(R8.java)",
+        "    <OR> at com.android.tools.r8.R8.foo(R8.java)",
+        "    ... 42 more");
+  }
+
+  @Override
+  public String mapping() {
+    return StringUtils.lines(
+        "com.android.tools.r8.R8 -> a.a:", "  void foo(int) -> a", "  void bar(int, int) -> a");
+  }
+
+  @Override
+  public int expectedWarnings() {
+    return 0;
+  }
+}
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 78a36f2..91f0c4d 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
@@ -49,6 +49,7 @@
   private void configure(InternalOptions options) {
     options.enableEnumValueOptimization = enableOptimization;
     options.enableEnumSwitchMapRemoval = enableOptimization;
+    options.enableEnumUnboxing = false;
   }
 
   @Test
diff --git a/src/test/java/com/android/tools/r8/shaking/FieldReadsJasminTest.java b/src/test/java/com/android/tools/r8/shaking/FieldReadsJasminTest.java
index 397c054..ac18813 100644
--- a/src/test/java/com/android/tools/r8/shaking/FieldReadsJasminTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/FieldReadsJasminTest.java
@@ -252,7 +252,31 @@
         "  getstatic Empty/sField I",
         "  return");
 
-    ensureFieldExistsAndReadOnlyOnce(builder, main, mainMethod, empty, "sField");
+    inspect(
+        builder,
+        inspector ->
+            ensureFieldExistsAndReadOnlyOnce(
+                inspector, main.name, mainMethod.name, empty, "sField", false),
+        inspector -> {
+          ClassSubject emptyClassSubject = inspector.clazz(empty.name);
+          assertThat(emptyClassSubject, isPresent());
+          assertEquals(1, emptyClassSubject.allStaticFields().size());
+
+          FieldSubject clinitFieldSubject = emptyClassSubject.allStaticFields().get(0);
+          assertEquals("$r8$clinit", clinitFieldSubject.getOriginalName());
+
+          ClassSubject mainClassSubject = inspector.clazz(main.name);
+          assertThat(mainClassSubject, isPresent());
+          assertThat(mainClassSubject.mainMethod(), isPresent());
+          assertTrue(
+              mainClassSubject
+                  .mainMethod()
+                  .streamInstructions()
+                  .filter(InstructionSubject::isStaticGet)
+                  .anyMatch(
+                      instruction ->
+                          instruction.getField().equals(clinitFieldSubject.getField().field)));
+        });
   }
 
   @Test
diff --git a/src/test/java/com/android/tools/r8/shaking/ServiceLoaderTest.java b/src/test/java/com/android/tools/r8/shaking/ServiceLoaderTest.java
index 0212a16..e23b80b 100644
--- a/src/test/java/com/android/tools/r8/shaking/ServiceLoaderTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/ServiceLoaderTest.java
@@ -14,6 +14,7 @@
 import static org.junit.Assert.assertTrue;
 
 import com.android.tools.r8.DataEntryResource;
+import com.android.tools.r8.NeverPropagateValue;
 import com.android.tools.r8.R8TestRunResult;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
@@ -48,7 +49,8 @@
 
   @Parameters(name = "{1}, include WorldGreeter: {0}")
   public static List<Object[]> data() {
-    return buildParameters(BooleanUtils.values(), getTestParameters().withAllRuntimes().build());
+    return buildParameters(
+        BooleanUtils.values(), getTestParameters().withAllRuntimesAndApiLevels().build());
   }
 
   public ServiceLoaderTest(boolean includeWorldGreeter, TestParameters parameters) {
@@ -89,7 +91,8 @@
                   options.enableInliningOfInvokesWithNullableReceivers = false;
                 })
             .enableGraphInspector()
-            .setMinApi(parameters.getRuntime())
+            .enableMemberValuePropagationAnnotations()
+            .setMinApi(parameters.getApiLevel())
             .run(parameters.getRuntime(), TestClass.class)
             .assertSuccessWithOutput(expectedOutput);
 
@@ -226,6 +229,7 @@
 
   public static class HelloGreeter implements Greeter {
 
+    @NeverPropagateValue
     @Override
     public String greeting() {
       return "Hello";
diff --git a/src/test/java/com/android/tools/r8/shaking/examples/InliningClassVersionTest.java b/src/test/java/com/android/tools/r8/shaking/examples/InliningClassVersionTest.java
index 8cb9074..e47222b 100644
--- a/src/test/java/com/android/tools/r8/shaking/examples/InliningClassVersionTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/examples/InliningClassVersionTest.java
@@ -9,18 +9,14 @@
 import com.android.tools.r8.ArchiveClassFileProvider;
 import com.android.tools.r8.ByteDataView;
 import com.android.tools.r8.ClassFileConsumer;
-import com.android.tools.r8.OutputMode;
-import com.android.tools.r8.R8Command;
+import com.android.tools.r8.NeverPropagateValue;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.ToolHelper.ProcessResult;
-import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.utils.DescriptorUtils;
 import com.android.tools.r8.utils.InternalOptions;
 import com.google.common.io.ByteStreams;
 import java.nio.file.Path;
-import java.util.Collections;
-import java.util.List;
 import org.junit.Test;
 import org.objectweb.asm.ClassReader;
 import org.objectweb.asm.ClassVisitor;
@@ -40,6 +36,8 @@
   }
 
   private static class Inlinee {
+
+    @NeverPropagateValue
     public static String foo() {
       return "Hello from Inlinee!";
     }
@@ -80,7 +78,13 @@
     assertEquals(OLD_VERSION, getBaseClassVersion(inputJar));
     ProcessResult runInput = run(inputJar);
     assertEquals(0, runInput.exitCode);
-    Path outputJar = runR8(inputJar);
+    Path outputJar =
+        testForR8(Backend.CF)
+            .addProgramFiles(inputJar)
+            .addKeepMainRule(Base.class)
+            .enableMemberValuePropagationAnnotations()
+            .compile()
+            .writeToZip();
     ProcessResult runOutput = run(outputJar);
     assertEquals(runInput.toString(), runOutput.toString());
     assertNotEquals(
@@ -141,19 +145,4 @@
   private ProcessResult run(Path jar) throws Exception {
     return ToolHelper.runJava(jar, Base.class.getName());
   }
-
-  private Path runR8(Path inputJar) throws Exception {
-    List<String> keepRule =
-        Collections.singletonList(
-            "-keep class " + Base.class.getName() + " { public static void main(...); }");
-    Path outputJar = temp.getRoot().toPath().resolve("output.jar");
-    ToolHelper.runR8(
-        R8Command.builder()
-            .addProgramFiles(inputJar)
-            .addLibraryFiles(ToolHelper.getJava8RuntimeJar())
-            .addProguardConfiguration(keepRule, Origin.unknown())
-            .setOutput(outputJar, OutputMode.ClassFile)
-            .build());
-    return outputJar;
-  }
 }
diff --git a/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking13Test.java b/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking13Test.java
index 3401322..5cdc80e 100644
--- a/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking13Test.java
+++ b/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking13Test.java
@@ -12,7 +12,6 @@
 import com.google.common.collect.ImmutableList;
 import java.util.ArrayList;
 import java.util.Collection;
-import java.util.Collections;
 import java.util.Iterator;
 import java.util.List;
 import org.junit.Assert;
@@ -50,7 +49,7 @@
 
   private static void shaking13EnsureFieldWritesCorrect(CodeInspector inspector) {
     ClassSubject mainClass = inspector.clazz("shaking13.Shaking");
-    MethodSubject testMethod = mainClass.method("void", "fieldTest", Collections.emptyList());
+    MethodSubject testMethod = mainClass.uniqueMethodWithName("fieldTest");
     Assert.assertTrue(testMethod.isPresent());
     Iterator<FieldAccessInstructionSubject> iterator =
         testMethod.iterateInstructions(InstructionSubject::isFieldAccess);
diff --git a/src/test/java/com/android/tools/r8/smali/SmaliTestBase.java b/src/test/java/com/android/tools/r8/smali/SmaliTestBase.java
index 8ecb463..6aad725 100644
--- a/src/test/java/com/android/tools/r8/smali/SmaliTestBase.java
+++ b/src/test/java/com/android/tools/r8/smali/SmaliTestBase.java
@@ -62,7 +62,6 @@
 
   protected DexApplication buildApplication(AndroidApp input, InternalOptions options) {
     try {
-      options.itemFactory.resetSortedIndices();
       return new ApplicationReader(input, options, Timing.empty()).read();
     } catch (IOException | ExecutionException e) {
       throw new RuntimeException(e);
diff --git a/src/test/java/com/android/tools/r8/utils/Smali.java b/src/test/java/com/android/tools/r8/utils/Smali.java
index 02aa682..cb9ace0 100644
--- a/src/test/java/com/android/tools/r8/utils/Smali.java
+++ b/src/test/java/com/android/tools/r8/utils/Smali.java
@@ -10,6 +10,7 @@
 import com.android.tools.r8.dex.ApplicationWriter;
 import com.android.tools.r8.graph.DexApplication;
 import com.android.tools.r8.graph.GraphLense;
+import com.android.tools.r8.graph.InitClassLens;
 import com.android.tools.r8.naming.NamingLens;
 import com.android.tools.r8.origin.Origin;
 import com.google.common.collect.ImmutableList;
@@ -117,6 +118,7 @@
               options,
               null,
               GraphLense.getIdentityLense(),
+              InitClassLens.getDefault(),
               NamingLens.getIdentityLens(),
               null);
       writer.write(executor);
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentClassSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentClassSubject.java
index 7331a22..8786f30 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentClassSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentClassSubject.java
@@ -40,6 +40,9 @@
   public void forAllInstanceFields(Consumer<FoundFieldSubject> inspection) {}
 
   @Override
+  public void forAllStaticFields(Consumer<FoundFieldSubject> inspection) {}
+
+  @Override
   public FieldSubject field(String type, String name) {
     return new AbsentFieldSubject();
   }
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentKmClassSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentKmClassSubject.java
index c467da4..a009244 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentKmClassSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentKmClassSubject.java
@@ -122,4 +122,9 @@
   public List<ClassSubject> getSealedSubclasses() {
     return null;
   }
+
+  @Override
+  public String getCompanionObject() {
+    return null;
+  }
 }
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/ClassSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/ClassSubject.java
index b693ad9..c8c6142 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/ClassSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/ClassSubject.java
@@ -118,12 +118,20 @@
 
   public abstract void forAllInstanceFields(Consumer<FoundFieldSubject> inspection);
 
+  public abstract void forAllStaticFields(Consumer<FoundFieldSubject> inspection);
+
   public final List<FoundFieldSubject> allInstanceFields() {
     ImmutableList.Builder<FoundFieldSubject> builder = ImmutableList.builder();
     forAllInstanceFields(builder::add);
     return builder.build();
   }
 
+  public final List<FoundFieldSubject> allStaticFields() {
+    ImmutableList.Builder<FoundFieldSubject> builder = ImmutableList.builder();
+    forAllStaticFields(builder::add);
+    return builder.build();
+  }
+
   public abstract FieldSubject field(String type, String name);
 
   public abstract FieldSubject uniqueFieldWithName(String name);
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/CodeMatchers.java b/src/test/java/com/android/tools/r8/utils/codeinspector/CodeMatchers.java
index 5dacc5a..86a21c1 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/CodeMatchers.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/CodeMatchers.java
@@ -4,6 +4,7 @@
 
 package com.android.tools.r8.utils.codeinspector;
 
+import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.graph.DexMethod;
 import java.util.function.Predicate;
 import org.hamcrest.Description;
@@ -12,6 +13,35 @@
 
 public class CodeMatchers {
 
+  public static Matcher<MethodSubject> accessesField(FieldSubject targetSubject) {
+    if (!targetSubject.isPresent()) {
+      throw new IllegalArgumentException();
+    }
+    DexField target = targetSubject.getField().field;
+    return new TypeSafeMatcher<MethodSubject>() {
+      @Override
+      protected boolean matchesSafely(MethodSubject subject) {
+        if (!subject.isPresent()) {
+          return false;
+        }
+        if (!subject.getMethod().hasCode()) {
+          return false;
+        }
+        return subject.streamInstructions().anyMatch(isFieldAccessWithTarget(target));
+      }
+
+      @Override
+      public void describeTo(Description description) {
+        description.appendText("accesses field `" + target.toSourceString() + "`");
+      }
+
+      @Override
+      public void describeMismatchSafely(final MethodSubject subject, Description description) {
+        description.appendText("method did not");
+      }
+    };
+  }
+
   public static Matcher<MethodSubject> invokesMethod(MethodSubject targetSubject) {
     if (!targetSubject.isPresent()) {
       throw new IllegalArgumentException();
@@ -44,4 +74,8 @@
   public static Predicate<InstructionSubject> isInvokeWithTarget(DexMethod target) {
     return instruction -> instruction.isInvoke() && instruction.getMethod() == target;
   }
+
+  public static Predicate<InstructionSubject> isFieldAccessWithTarget(DexField target) {
+    return instruction -> instruction.isFieldAccess() && instruction.getField() == target;
+  }
 }
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundClassSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundClassSubject.java
index dc65b71..fc5c38f 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundClassSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundClassSubject.java
@@ -122,12 +122,8 @@
 
   @Override
   public void forAllFields(Consumer<FoundFieldSubject> inspection) {
-    CodeInspector.forAll(
-        dexClass.staticFields(),
-        (dexField, clazz) -> new FoundFieldSubject(codeInspector, dexField, clazz),
-        this,
-        inspection);
     forAllInstanceFields(inspection);
+    forAllStaticFields(inspection);
   }
 
   @Override
@@ -140,6 +136,15 @@
   }
 
   @Override
+  public void forAllStaticFields(Consumer<FoundFieldSubject> inspection) {
+    CodeInspector.forAll(
+        dexClass.staticFields(),
+        (dexField, clazz) -> new FoundFieldSubject(codeInspector, dexField, clazz),
+        this,
+        inspection);
+  }
+
+  @Override
   public FieldSubject field(String type, String name) {
     String obfuscatedType = codeInspector.getObfuscatedTypeName(type);
     MemberNaming fieldNaming = null;
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundKmClassSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundKmClassSubject.java
index 7b57397..71b30cb 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundKmClassSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundKmClassSubject.java
@@ -55,7 +55,7 @@
 
   @Override
   public boolean isSynthetic() {
-    // TODO(b/70169921): This should return `true` conditionally if we start synthesizing @Metadata
+    // TODO(b/151194785): This should return `true` conditionally if we start synthesizing @Metadata
     //   from scratch.
     return false;
   }
@@ -110,4 +110,9 @@
         .map(this::getClassSubjectFromDescriptor)
         .collect(Collectors.toList());
   }
+
+  @Override
+  public String getCompanionObject() {
+    return kmClass.getCompanionObject();
+  }
 }
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundKmDeclarationContainerSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundKmDeclarationContainerSubject.java
index 5b3338b..626388f 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundKmDeclarationContainerSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundKmDeclarationContainerSubject.java
@@ -80,7 +80,7 @@
     }
   }
 
-  // TODO(b/70169921): Search both original and renamed names.
+  // TODO(b/151194869): Search both original and renamed names.
   default KmFunctionSubject kmFunctionOrExtensionWithUniqueName(String name, boolean isExtension) {
     KmFunction foundFunction = null;
     for (KmFunction kmFunction : getKmDeclarationContainer().getFunctions()) {
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundKmFunctionSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundKmFunctionSubject.java
index cdf57aa..f86f718 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundKmFunctionSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundKmFunctionSubject.java
@@ -29,13 +29,13 @@
 
   @Override
   public boolean isRenamed() {
-    // TODO(b/70169921): need to know the corresponding DexEncodedMethod.
+    // TODO(b/151194869): need to know the corresponding DexEncodedMethod.
     return false;
   }
 
   @Override
   public boolean isSynthetic() {
-    // TODO(b/70169921): This should return `true` conditionally if we start synthesizing @Metadata
+    // TODO(b/151194785): This should return `true` conditionally if we start synthesizing @Metadata
     //   from scratch.
     return false;
   }
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundKmPackageSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundKmPackageSubject.java
index b4e796c..d2c4fa6 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundKmPackageSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundKmPackageSubject.java
@@ -46,7 +46,7 @@
 
   @Override
   public boolean isSynthetic() {
-    // TODO(b/70169921): This should return `true` conditionally if we start synthesizing @Metadata
+    // TODO(b/151194785): This should return `true` conditionally if we start synthesizing @Metadata
     //   from scratch.
     return false;
   }
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundKmPropertySubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundKmPropertySubject.java
index 3cfe20d..d6bab2d 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundKmPropertySubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundKmPropertySubject.java
@@ -31,14 +31,14 @@
 
   @Override
   public boolean isRenamed() {
-    // TODO(b/70169921): How to determine it is renamed?
+    // TODO(b/151194869): How to determine it is renamed?
     //   backing field renamed? If no backing field exists, then examine getter/setter?
     return false;
   }
 
   @Override
   public boolean isSynthetic() {
-    // TODO(b/70169921): This should return `true` conditionally if we start synthesizing @Metadata
+    // TODO(b/151194785): This should return `true` conditionally if we start synthesizing @Metadata
     //   from scratch.
     return false;
   }
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/KmClassSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/KmClassSubject.java
index 7fb1f9f..3f5be34 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/KmClassSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/KmClassSubject.java
@@ -22,4 +22,6 @@
   public abstract List<String> getSealedSubclassDescriptors();
 
   public abstract List<ClassSubject> getSealedSubclasses();
+
+  public abstract String getCompanionObject();
 }
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/KmTypeSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/KmTypeSubject.java
index 5d7ec81..d671a7d 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/KmTypeSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/KmTypeSubject.java
@@ -23,7 +23,7 @@
     this.kmType = kmType;
   }
 
-  // TODO(b/145824437): This is a dup of DescriptorUtils#getDescriptorFromKmType
+  // TODO(b/151195430): This is a dup of DescriptorUtils#getDescriptorFromKmType
   static String getDescriptorFromKmType(KmType kmType) {
     if (kmType == null) {
       return null;
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/Matchers.java b/src/test/java/com/android/tools/r8/utils/codeinspector/Matchers.java
index 3fb684f..98f6b1e 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/Matchers.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/Matchers.java
@@ -12,6 +12,7 @@
 import com.android.tools.r8.retrace.RetraceMethodResult;
 import com.android.tools.r8.retrace.RetraceMethodResult.Element;
 import com.android.tools.r8.utils.Box;
+import com.android.tools.r8.utils.Visibility;
 import com.google.common.collect.ImmutableList;
 import java.util.List;
 import java.util.stream.Collectors;
@@ -21,33 +22,6 @@
 
 public class Matchers {
 
-  private enum Visibility {
-    PUBLIC,
-    PROTECTED,
-    PRIVATE,
-    PACKAGE_PRIVATE;
-
-    @Override
-    public String toString() {
-      switch (this) {
-        case PUBLIC:
-          return "public";
-
-        case PROTECTED:
-          return "protected";
-
-        case PRIVATE:
-          return "private";
-
-        case PACKAGE_PRIVATE:
-          return "package-private";
-
-        default:
-          throw new Unreachable("Unexpected visibility");
-      }
-    }
-  }
-
   private static String type(Subject subject) {
     String type = "<unknown subject type>";
     if (subject instanceof ClassSubject) {
@@ -428,16 +402,16 @@
     };
   }
 
-  public static Matcher<RetraceMethodResult> isInlineStack(InlinePosition startPosition) {
+  public static Matcher<RetraceMethodResult> isInlineStack(LinePosition startPosition) {
     return new TypeSafeMatcher<RetraceMethodResult>() {
       @Override
       protected boolean matchesSafely(RetraceMethodResult item) {
-        Box<InlinePosition> currentPosition = new Box<>(startPosition);
+        Box<LinePosition> currentPosition = new Box<>(startPosition);
         Box<Boolean> returnValue = new Box<>();
         item.forEach(
             element -> {
               boolean sameMethod;
-              InlinePosition currentInline = currentPosition.get();
+              LinePosition currentInline = currentPosition.get();
               if (currentInline == null) {
                 returnValue.set(false);
                 return;
@@ -492,70 +466,100 @@
     };
   }
 
-  public static Matcher<StackTrace> containsInlinePosition(InlinePosition inlinePosition) {
+  public static Matcher<StackTrace> containsLinePositions(LinePosition linePosition) {
     return new TypeSafeMatcher<StackTrace>() {
       @Override
       protected boolean matchesSafely(StackTrace item) {
-        return containsInlineStack(item, 0, inlinePosition);
+        return containsLinePosition(item, 0, linePosition);
       }
 
       @Override
       public void describeTo(Description description) {
-        description.appendText("cannot be found in stack trace");
+        description.appendText(linePosition + " cannot be found in stack trace");
       }
 
-      private boolean containsInlineStack(
-          StackTrace stackTrace, int index, InlinePosition currentPosition) {
-        if (currentPosition == null) {
+      private boolean containsLinePosition(
+          StackTrace stackTrace, int index, LinePosition linePosition) {
+        if (linePosition == null) {
           return true;
         }
-        if (index >= stackTrace.size()) {
-          return false;
+        Matcher<StackTraceLine> lineMatcher = Matchers.matchesLinePosition(linePosition);
+        for (int i = index; i < stackTrace.getStackTraceLines().size(); i++) {
+          StackTraceLine stackTraceLine = stackTrace.get(i);
+          if (lineMatcher.matches(stackTraceLine)) {
+            return containsLinePosition(stackTrace, index + 1, linePosition.caller);
+          }
         }
-        StackTraceLine stackTraceLine = stackTrace.get(index);
-        boolean resultHere =
-            stackTraceLine.className.equals(currentPosition.getClassName())
-                && stackTraceLine.methodName.equals(currentPosition.getMethodName())
-                && stackTraceLine.lineNumber == currentPosition.originalPosition;
-        if (resultHere && containsInlineStack(stackTrace, index + 1, currentPosition.caller)) {
-          return true;
-        }
-        // Maybe the inline position starts from the top on the next position.
-        return containsInlineStack(stackTrace, index + 1, inlinePosition);
+        return false;
       }
     };
   }
 
-  public static class InlinePosition {
+  public static Matcher<StackTraceLine> matchesLinePosition(LinePosition linePosition) {
+    return new TypeSafeMatcher<StackTraceLine>() {
+
+      @Override
+      protected boolean matchesSafely(StackTraceLine item) {
+        return containsLinePosition(item, linePosition);
+      }
+
+      @Override
+      public void describeTo(Description description) {
+        description.appendText(linePosition + " cannot be found in stack trace");
+      }
+
+      private boolean containsLinePosition(
+          StackTraceLine stackTraceLine, LinePosition currentPosition) {
+        return stackTraceLine.className.equals(currentPosition.getClassName())
+            && stackTraceLine.methodName.equals(currentPosition.getMethodName())
+            && stackTraceLine.lineNumber == currentPosition.originalPosition
+            && stackTraceLine.fileName.equals(currentPosition.filename);
+      }
+    };
+  }
+
+  public static class LinePosition {
     private final MethodReference methodReference;
     private final int minifiedPosition;
     private final int originalPosition;
+    private final String filename;
 
-    private InlinePosition caller;
+    private LinePosition caller;
 
-    private InlinePosition(
-        MethodReference methodReference, int minifiedPosition, int originalPosition) {
+    private LinePosition(
+        MethodReference methodReference,
+        int minifiedPosition,
+        int originalPosition,
+        String filename) {
       this.methodReference = methodReference;
       this.minifiedPosition = minifiedPosition;
       this.originalPosition = originalPosition;
+      this.filename = filename;
     }
 
-    public static InlinePosition create(
-        MethodReference methodReference, int minifiedPosition, int originalPosition) {
-      return new InlinePosition(methodReference, minifiedPosition, originalPosition);
+    public static LinePosition create(
+        MethodReference methodReference,
+        int minifiedPosition,
+        int originalPosition,
+        String filename) {
+      return new LinePosition(methodReference, minifiedPosition, originalPosition, filename);
     }
 
-    public static InlinePosition create(
-        FoundMethodSubject methodSubject, int minifiedPosition, int originalPosition) {
-      return create(methodSubject.asMethodReference(), minifiedPosition, originalPosition);
+    public static LinePosition create(
+        FoundMethodSubject methodSubject,
+        int minifiedPosition,
+        int originalPosition,
+        String filename) {
+      return create(
+          methodSubject.asMethodReference(), minifiedPosition, originalPosition, filename);
     }
 
-    public static InlinePosition stack(InlinePosition... stack) {
+    public static LinePosition stack(LinePosition... stack) {
       setCaller(1, stack);
       return stack[0];
     }
 
-    private static void setCaller(int index, InlinePosition... stack) {
+    private static void setCaller(int index, LinePosition... stack) {
       assert index > 0;
       if (index >= stack.length) {
         return;
@@ -571,5 +575,10 @@
     String getClassName() {
       return methodReference.getHolderClass().getTypeName();
     }
+
+    @Override
+    public String toString() {
+      return getClassName() + "." + getMethodName() + "(" + filename + ":" + originalPosition + ")";
+    }
   }
 }
diff --git a/third_party/remapper.tar.gz.sha1 b/third_party/remapper.tar.gz.sha1
new file mode 100644
index 0000000..6600831
--- /dev/null
+++ b/third_party/remapper.tar.gz.sha1
@@ -0,0 +1 @@
+da2ce26c22a1d787fe436f1ea03d4eee6306bb35
\ No newline at end of file
diff --git a/third_party/retrace_benchmark.tar.gz.sha1 b/third_party/retrace_benchmark.tar.gz.sha1
new file mode 100644
index 0000000..7396a0f
--- /dev/null
+++ b/third_party/retrace_benchmark.tar.gz.sha1
@@ -0,0 +1 @@
+d73cd63dea729786d634bf3503db1795e72a20ec
\ No newline at end of file
diff --git a/tools/compare_apk_sizes.py b/tools/compare_apk_sizes.py
index 76ef1fc..da505c7 100755
--- a/tools/compare_apk_sizes.py
+++ b/tools/compare_apk_sizes.py
@@ -13,10 +13,10 @@
 import sys
 import threading
 import time
+import zipfile
+
 import toolhelper
 import utils
-import zipfile
-import StringIO
 
 USAGE = """%prog [options] app1 app2
   NOTE: This only makes sense if minification is disabled"""
diff --git a/tools/download_all_benchmark_dependencies.py b/tools/download_all_benchmark_dependencies.py
index 1113370..95b92ed 100755
--- a/tools/download_all_benchmark_dependencies.py
+++ b/tools/download_all_benchmark_dependencies.py
@@ -19,11 +19,15 @@
   utils.DownloadFromX20(
       os.path.join(
           utils.THIRD_PARTY, 'benchmarks', 'android-sdk') + '.tar.gz.sha1')
+  utils.DownloadFromX20(
+      os.path.join(utils.THIRD_PARTY, 'remapper') + '.tar.gz.sha1')
   utils.DownloadFromGoogleCloudStorage(utils.SAMPLE_LIBRARIES_SHA_FILE)
   utils.DownloadFromGoogleCloudStorage(utils.OPENSOURCE_APPS_SHA_FILE)
   utils.DownloadFromGoogleCloudStorage(utils.ANDROID_SDK + '.tar.gz.sha1',
                                        bucket='r8-deps-internal',
                                        auth=True)
+  utils.DownloadFromGoogleCloudStorage(
+      os.path.join(utils.THIRD_PARTY, 'retrace_benchmark') + '.tar.gz.sha1')
 
 if __name__ == '__main__':
   sys.exit(Main())
diff --git a/tools/golem.py b/tools/golem.py
index 466c3ab..586ac5e 100755
--- a/tools/golem.py
+++ b/tools/golem.py
@@ -21,6 +21,8 @@
     'proguard',
     'proguardsettings',
     'r8',
+    'remapper',
+    'retrace_benchmarks',
     'sample_libraries',
     'youtube',
 ]
diff --git a/tools/retrace_benchmark.py b/tools/retrace_benchmark.py
new file mode 100755
index 0000000..5e2bd3b
--- /dev/null
+++ b/tools/retrace_benchmark.py
@@ -0,0 +1,83 @@
+#!/usr/bin/env python
+# 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.
+
+import argparse
+import jdk
+import golem
+import os
+import subprocess
+import sys
+import time
+import toolhelper
+import utils
+
+RETRACERS = ['r8', 'proguard', 'remapper']
+
+def parse_arguments(argv):
+  parser = argparse.ArgumentParser(
+                    description = 'Run r8 retrace bootstrap benchmarks.')
+  parser.add_argument('--golem',
+                    help = 'Link in third party dependencies.',
+                    default = False,
+                    action = 'store_true')
+  parser.add_argument('--ignore-java-version',
+                    help='Do not check java version',
+                    default=False,
+                    action='store_true')
+  parser.add_argument('--print-runtimeraw',
+                    metavar='BENCHMARKNAME',
+                    help='Print the line \'<BENCHMARKNAME>(RunTimeRaw):' +
+                        ' <elapsed> ms\' at the end where <elapsed> is' +
+                        ' the elapsed time in milliseconds.')
+  parser.add_argument('--retracer',
+                    help='The retracer to use',
+                    choices=RETRACERS,
+                    required=True)
+  options = parser.parse_args(argv)
+  return options
+
+
+def run_retrace(options, temp):
+  if options.retracer == 'r8':
+    retracer_args = [
+        '-cp', utils.R8LIB_JAR, 'com.android.tools.r8.retrace.Retrace']
+  elif options.retracer == 'proguard':
+    retracer_args = ['-jar',
+                     os.path.join(
+                        utils.THIRD_PARTY,
+                        'proguard',
+                        'proguard6.0.1',
+                        'lib',
+                        'retrace.jar')]
+  elif options.retracer == 'remapper':
+    retracer_args = ['-jar',
+                     os.path.join(
+                        utils.THIRD_PARTY,
+                        'remapper',
+                        'remapper_deploy.jar')]
+  else:
+    assert False, "Unexpected retracer " + options.retracer
+  retrace_args = [jdk.GetJavaExecutable()] + retracer_args + [
+    os.path.join(utils.THIRD_PARTY, 'retrace_benchmark', 'r8lib.jar.map'),
+    os.path.join(utils.THIRD_PARTY, 'retrace_benchmark', 'stacktrace.txt')]
+  utils.PrintCmd(retrace_args)
+  t0 = time.time()
+  subprocess.check_call(
+      retrace_args, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+  t1 = time.time()
+  if options.print_runtimeraw:
+    print('{}(RunTimeRaw): {} ms'
+        .format(options.print_runtimeraw, 1000.0 * (t1 - t0)))
+
+
+if __name__ == '__main__':
+  options = parse_arguments(sys.argv[1:])
+  if options.golem:
+    golem.link_third_party()
+  if not options.ignore_java_version:
+    utils.check_java_version()
+  with utils.TempDir() as temp:
+    run_retrace(options, temp)
+
diff --git a/tools/run_on_app.py b/tools/run_on_app.py
index 16db28d..07de70c 100755
--- a/tools/run_on_app.py
+++ b/tools/run_on_app.py
@@ -167,6 +167,9 @@
                     help='Include timing',
                     default=False,
                     action='store_true')
+  result.add_option('--cpu-list',
+                    help='Run under \'taskset\' with these CPUs. See '
+                         'the \'taskset\' -c option for the format')
 
   return result.parse_args(argv)
 
@@ -575,7 +578,9 @@
             stdout=stdout,
             stderr=stderr,
             timeout=options.timeout,
-            quiet=quiet)
+            quiet=quiet,
+            cmd_prefix=[
+                'taskset', '-c', options.cpu_list] if options.cpu_list else [])
       if exit_code != 0:
         with open(stderr_path) as stderr:
           stderr_text = stderr.read()
diff --git a/tools/toolhelper.py b/tools/toolhelper.py
index 2784ea5..cc1a9cb 100644
--- a/tools/toolhelper.py
+++ b/tools/toolhelper.py
@@ -3,20 +3,24 @@
 # BSD-style license that can be found in the LICENSE file.
 
 import glob
-import gradle
-import jdk
 import subprocess
 from threading import Timer
+
+import gradle
+import jdk
 import utils
 
+
 def run(tool, args, build=None, debug=True,
         profile=False, track_memory_file=None, extra_args=None,
-        stderr=None, stdout=None, return_stdout=False, timeout=0, quiet=False):
+        stderr=None, stdout=None, return_stdout=False, timeout=0, quiet=False,
+        cmd_prefix=[]):
+  cmd = []
+  cmd.extend(cmd_prefix)
   if build is None:
     build, args = extract_build_from_args(args)
   if build:
     gradle.RunGradle(['r8lib' if tool.startswith('r8lib') else 'r8'])
-  cmd = []
   if track_memory_file:
     cmd.extend(['tools/track_memory.sh', track_memory_file])
   cmd.append(jdk.GetJavaExecutable())