Merge commit '75b469b340688d1e870d6b996c41354f38301f58' into dev-release
diff --git a/src/main/java/com/android/tools/r8/ClassFileConsumer.java b/src/main/java/com/android/tools/r8/ClassFileConsumer.java
index e124571..807fd6e 100644
--- a/src/main/java/com/android/tools/r8/ClassFileConsumer.java
+++ b/src/main/java/com/android/tools/r8/ClassFileConsumer.java
@@ -160,10 +160,12 @@
         try (ZipOutputStream out = new ZipOutputStream(Files.newOutputStream(archive, options))) {
           for (ProgramResource resource : resources) {
             assert resource.getClassDescriptors().size() == 1;
-            String className = resource.getClassDescriptors().iterator().next();
-            String entryName = getClassFileName(className);
-            byte[] bytes = ByteStreams.toByteArray(closer.register(resource.getByteStream()));
-            ZipUtils.writeToZipStream(out, entryName, bytes, ZipEntry.DEFLATED);
+            if (resource.getClassDescriptors() != null) {
+              String className = resource.getClassDescriptors().iterator().next();
+              String entryName = getClassFileName(className);
+              byte[] bytes = ByteStreams.toByteArray(closer.register(resource.getByteStream()));
+              ZipUtils.writeToZipStream(out, entryName, bytes, ZipEntry.DEFLATED);
+            }
           }
           for (DataEntryResource dataResource : dataResources) {
             String entryName = dataResource.getName();
diff --git a/src/main/java/com/android/tools/r8/D8.java b/src/main/java/com/android/tools/r8/D8.java
index ed74e0b..36adabe 100644
--- a/src/main/java/com/android/tools/r8/D8.java
+++ b/src/main/java/com/android/tools/r8/D8.java
@@ -217,7 +217,6 @@
               null,
               options,
               marker == null ? null : ImmutableList.copyOf(markers),
-              app.getChecksums(),
               GraphLense.getIdentityLense(),
               PrefixRewritingNamingLens.createPrefixRewritingNamingLens(options, rewritePrefix),
               null)
diff --git a/src/main/java/com/android/tools/r8/DexFileMergerHelper.java b/src/main/java/com/android/tools/r8/DexFileMergerHelper.java
index e728f6d..1ed8cac 100644
--- a/src/main/java/com/android/tools/r8/DexFileMergerHelper.java
+++ b/src/main/java/com/android/tools/r8/DexFileMergerHelper.java
@@ -100,7 +100,6 @@
                 null,
                 options,
                 markers,
-                null,
                 GraphLense.getIdentityLense(),
                 NamingLens.getIdentityLens(),
                 null);
diff --git a/src/main/java/com/android/tools/r8/DexSplitterHelper.java b/src/main/java/com/android/tools/r8/DexSplitterHelper.java
index 595e1e3..2dee6ef 100644
--- a/src/main/java/com/android/tools/r8/DexSplitterHelper.java
+++ b/src/main/java/com/android/tools/r8/DexSplitterHelper.java
@@ -101,7 +101,6 @@
                   null,
                   options,
                   markers,
-                  null,
                   GraphLense.getIdentityLense(),
                   NamingLens.getIdentityLens(),
                   null,
diff --git a/src/main/java/com/android/tools/r8/GenerateLintFiles.java b/src/main/java/com/android/tools/r8/GenerateLintFiles.java
index 3bd76a6..8ce6eed 100644
--- a/src/main/java/com/android/tools/r8/GenerateLintFiles.java
+++ b/src/main/java/com/android/tools/r8/GenerateLintFiles.java
@@ -21,6 +21,7 @@
 import com.android.tools.r8.graph.DexLibraryClass;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.DexProgramClass.ChecksumSupplier;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.DirectMappedDexApplication;
 import com.android.tools.r8.graph.GraphLense;
@@ -155,7 +156,9 @@
     DexEncodedMethod[] virtualMethodsArray = new DexEncodedMethod[virtualMethods.size()];
     directMethods.toArray(directMethodsArray);
     virtualMethods.toArray(virtualMethodsArray);
-    builder.addProgramClass(
+    assert !options.encodeChecksums;
+    ChecksumSupplier checksumSupplier = DexProgramClass::invalidChecksumRequest;
+    DexProgramClass programClass =
         new DexProgramClass(
             clazz.type,
             null,
@@ -173,7 +176,9 @@
             DexEncodedField.EMPTY_ARRAY,
             directMethodsArray,
             virtualMethodsArray,
-            false));
+            false,
+            checksumSupplier);
+    builder.addProgramClass(programClass);
   }
 
   public static class SupportedMethods {
diff --git a/src/main/java/com/android/tools/r8/L8Command.java b/src/main/java/com/android/tools/r8/L8Command.java
index 656fee1..1837cd3 100644
--- a/src/main/java/com/android/tools/r8/L8Command.java
+++ b/src/main/java/com/android/tools/r8/L8Command.java
@@ -163,8 +163,10 @@
     }
 
     public boolean isShrinking() {
+      // TODO(b/143431384): Re-enable shrinking.
+      return false;
       // Answers true if keep rules, even empty, are provided.
-       return !proguardConfigStrings.isEmpty() || !proguardConfigFiles.isEmpty();
+      // return !proguardConfigStrings.isEmpty() || !proguardConfigFiles.isEmpty();
     }
 
     @Override
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java
index c56ea8a..56f69f7 100644
--- a/src/main/java/com/android/tools/r8/R8.java
+++ b/src/main/java/com/android/tools/r8/R8.java
@@ -202,7 +202,6 @@
                 appView,
                 options,
                 Collections.singletonList(marker),
-                null,
                 graphLense,
                 namingLens,
                 proguardMapSupplier)
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 6b386c5..1d09319 100644
--- a/src/main/java/com/android/tools/r8/bisect/Bisect.java
+++ b/src/main/java/com/android/tools/r8/bisect/Bisect.java
@@ -186,8 +186,7 @@
     StringConsumer proguardMapConsumer = options.proguardMapConsumer;
     AndroidAppConsumers compatSink = new AndroidAppConsumers(options);
     ApplicationWriter writer =
-        new ApplicationWriter(
-            app, null, options, null, null, null, NamingLens.getIdentityLens(), null);
+        new ApplicationWriter(app, null, options, null, null, NamingLens.getIdentityLens(), null);
     writer.write(executor);
     options.signalFinishedToConsumers();
     compatSink.build().writeToDirectory(output, OutputMode.DexIndexed);
diff --git a/src/main/java/com/android/tools/r8/dex/ApplicationReader.java b/src/main/java/com/android/tools/r8/dex/ApplicationReader.java
index cfe4d9f..5629efe 100644
--- a/src/main/java/com/android/tools/r8/dex/ApplicationReader.java
+++ b/src/main/java/com/android/tools/r8/dex/ApplicationReader.java
@@ -10,6 +10,7 @@
 
 import com.android.tools.r8.ClassFileResourceProvider;
 import com.android.tools.r8.DataResourceProvider;
+import com.android.tools.r8.OutputMode;
 import com.android.tools.r8.ProgramResource;
 import com.android.tools.r8.ProgramResource.Kind;
 import com.android.tools.r8.ProgramResourceProvider;
@@ -42,6 +43,7 @@
 import com.android.tools.r8.utils.Timing;
 import java.io.IOException;
 import java.io.InputStream;
+import java.nio.file.Paths;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.List;
@@ -59,7 +61,6 @@
   private final DexItemFactory itemFactory;
   private final Timing timing;
   private final AndroidApp inputApp;
-  private final ClassesChecksum checksums = new ClassesChecksum();
 
   public interface ProgramClassConflictResolver {
     DexProgramClass resolveClassConflict(DexProgramClass a, DexProgramClass b);
@@ -107,6 +108,10 @@
       ProgramClassConflictResolver resolver)
       throws IOException, ExecutionException {
     assert verifyMainDexOptionsCompatible(inputApp, options);
+    if (options.dumpInputToFile != null) {
+      inputApp.writeToZip(Paths.get(options.dumpInputToFile), OutputMode.ClassFile);
+      throw options.reporter.fatalError("Dumped compilation inputs to: " + options.dumpInputToFile);
+    }
     timing.begin("DexApplication.read");
     final LazyLoadedDexApplication.Builder builder =
         DexApplication.builder(options, timing, resolver);
@@ -124,10 +129,6 @@
       ClassReader classReader = new ClassReader(executorService, futures);
       JarClassFileReader jcf = classReader.readSources();
       ThreadUtils.awaitFutures(futures);
-      // Merge all the checksum gathered from the class file's CRC as well as the marker
-      // implanted into the dex file.
-      builder.mergeChecksums(jcf.getChecksums());
-      builder.mergeChecksums(classReader.application.options.itemFactory.extractChecksum());
       classReader.initializeLazyClassCollection(builder);
       for (ProgramResourceProvider provider : inputApp.getProgramResourceProviders()) {
         DataResourceProvider dataResourceProvider = provider.getDataResourceProvider();
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 ad763d8..24a33b9 100644
--- a/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java
+++ b/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java
@@ -37,7 +37,6 @@
 import com.android.tools.r8.graph.InnerClassAttribute;
 import com.android.tools.r8.graph.ObjectToOffsetMapping;
 import com.android.tools.r8.graph.ParameterAnnotationsList;
-import com.android.tools.r8.ir.desugar.DesugaredLibraryWrapperSynthesizer;
 import com.android.tools.r8.naming.ClassNameMapper;
 import com.android.tools.r8.naming.NamingLens;
 import com.android.tools.r8.naming.ProguardMapSupplier;
@@ -49,12 +48,10 @@
 import com.android.tools.r8.utils.StringUtils;
 import com.android.tools.r8.utils.ThreadUtils;
 import com.google.common.collect.ImmutableList;
-import com.google.common.collect.ImmutableMap;
-import com.google.common.collect.Maps;
 import com.google.common.collect.ObjectArrays;
-import com.google.common.primitives.Longs;
+import it.unimi.dsi.fastutil.objects.Object2LongMap;
+import it.unimi.dsi.fastutil.objects.Object2LongOpenHashMap;
 import java.io.IOException;
-import java.nio.ByteBuffer;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.HashSet;
@@ -66,7 +63,6 @@
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Future;
 import java.util.stream.Collectors;
-import java.util.zip.CRC32;
 
 public class ApplicationWriter {
 
@@ -77,7 +73,6 @@
   public final InternalOptions options;
   public List<Marker> markers;
   public List<DexString> markerStrings;
-  private final ClassesChecksum checksums;
 
   public DexIndexedConsumer programConsumer;
   public final ProguardMapSupplier proguardMapSupplier;
@@ -144,7 +139,6 @@
       AppView<?> appView,
       InternalOptions options,
       List<Marker> markers,
-      ClassesChecksum checksums,
       GraphLense graphLense,
       NamingLens namingLens,
       ProguardMapSupplier proguardMapSupplier) {
@@ -153,7 +147,6 @@
         appView,
         options,
         markers,
-        checksums,
         graphLense,
         namingLens,
         proguardMapSupplier,
@@ -165,7 +158,6 @@
       AppView<?> appView,
       InternalOptions options,
       List<Marker> markers,
-      ClassesChecksum checksums,
       GraphLense graphLense,
       NamingLens namingLens,
       ProguardMapSupplier proguardMapSupplier,
@@ -176,7 +168,6 @@
     assert options != null;
     this.options = options;
     this.markers = markers;
-    this.checksums = checksums;
     this.graphLense = graphLense;
     this.namingLens = namingLens;
     this.proguardMapSupplier = proguardMapSupplier;
@@ -210,60 +201,17 @@
    * This needs to be done after distribute but before dex string sorting.
    */
   private void encodeChecksums(Iterable<VirtualFile> files) {
-    ImmutableMap<String, Long> inputChecksums = checksums.getChecksums();
-    Map<String, Long> synthesizedChecksums = Maps.newHashMap();
-    for (DexProgramClass clazz : application.classes()) {
-      Collection<DexProgramClass> synthesizedFrom = clazz.getSynthesizedFrom();
-
-      if (synthesizedFrom.isEmpty()) {
-        if (inputChecksums.containsKey(clazz.getType().descriptor.toASCIIString())) {
-          continue;
-        } else {
-          String name = clazz.toSourceString();
-          if (name.contains(DesugaredLibraryWrapperSynthesizer.TYPE_WRAPPER_SUFFIX)
-              || name.contains(DesugaredLibraryWrapperSynthesizer.VIVIFIED_TYPE_WRAPPER_SUFFIX)) {
-            synthesizedChecksums.put(
-                clazz.getType().descriptor.toASCIIString(), (long) name.hashCode());
-          } else {
-            throw new CompilationError(
-                clazz
-                    + " from "
-                    + clazz.origin
-                    + " has no checksum information while checksum encoding is requested");
-          }
-        }
-      }
-
-      // Checksum of synthesized classes are compute based off the depending input. This might
-      // create false positives (ie: unchanged lambda class detected as changed even thought only
-      // an unrelated part from a synthesizedFrom class is changed).
-
-      // Ideally, we should use some hashcode of the dex program class that is deterministic across
-      // compiles.
-      ByteBuffer buffer = ByteBuffer.allocate(synthesizedFrom.size() * Longs.BYTES);
-      for (DexProgramClass from : synthesizedFrom) {
-        buffer.putLong(inputChecksums.get(from.getType().descriptor.toASCIIString()));
-      }
-      CRC32 crc = new CRC32();
-      byte[] array = buffer.array();
-      crc.update(array, 0, array.length);
-      synthesizedChecksums.put(clazz.getType().descriptor.toASCIIString(), crc.getValue());
+    List<DexProgramClass> classes = application.classes();
+    Object2LongMap<String> inputChecksums = new Object2LongOpenHashMap<>(classes.size());
+    for (DexProgramClass clazz : classes) {
+      inputChecksums.put(clazz.getType().descriptor.toASCIIString(), clazz.getChecksum());
     }
-
-    for (VirtualFile f : files) {
+    for (VirtualFile file : files) {
       ClassesChecksum toWrite = new ClassesChecksum();
-      for (String desc : f.getClassDescriptors()) {
-        Long checksum = inputChecksums.get(desc);
-        if (checksum == null) {
-          checksum = synthesizedChecksums.get(desc);
-        }
-
-        // All classes should have a checksum from the inputChecksum (previous marker) or it was
-        // computed eariler in the function. Otherwise, we would have throw an compilation error.
-        assert checksum != null;
-        toWrite.addChecksum(desc, checksum);
+      for (String desc : file.getClassDescriptors()) {
+        toWrite.addChecksum(desc, inputChecksums.getLong(desc));
       }
-      f.injectString(application.dexItemFactory.createString(toWrite.toString()));
+      file.injectString(application.dexItemFactory.createString(toWrite.toJsonString()));
     }
   }
 
@@ -295,7 +243,6 @@
       if (options.encodeChecksums) {
         encodeChecksums(virtualFiles);
       }
-
       application.dexItemFactory.sort(namingLens);
       assert markers == null
           || markers.isEmpty()
diff --git a/src/main/java/com/android/tools/r8/dex/ClassesChecksum.java b/src/main/java/com/android/tools/r8/dex/ClassesChecksum.java
index d5c7a64..07d2176 100644
--- a/src/main/java/com/android/tools/r8/dex/ClassesChecksum.java
+++ b/src/main/java/com/android/tools/r8/dex/ClassesChecksum.java
@@ -1,12 +1,12 @@
 package com.android.tools.r8.dex;
 
 import com.android.tools.r8.graph.DexString;
-import com.google.common.collect.ImmutableMap;
-import com.google.common.collect.Maps;
 import com.google.gson.JsonElement;
 import com.google.gson.JsonObject;
 import com.google.gson.JsonParser;
 import com.google.gson.JsonSyntaxException;
+import it.unimi.dsi.fastutil.objects.Object2LongMap;
+import it.unimi.dsi.fastutil.objects.Object2LongOpenHashMap;
 import java.util.Comparator;
 import java.util.Map;
 
@@ -16,53 +16,47 @@
   private static final char PREFIX_CHAR1 = '~';
   private static final char PREFIX_CHAR2 = '~';
 
+  private Object2LongMap<String> dictionary = null;
 
-  // private final JsonObject dictionary;
-  Map<String, Long> dictionary = Maps.newHashMap();
-
-  /**
-   * Checksum to be inserted.
-   */
   public ClassesChecksum() {
   }
 
-  private ClassesChecksum(JsonObject json) {
-    json.entrySet().forEach(entry ->
-        dictionary.put(entry.getKey(), Long.parseLong(entry.getValue().getAsString(), 16)));
-  }
-
-  public synchronized ClassesChecksum addChecksum(String classDescriptor, Long crc) {
-    dictionary.put(classDescriptor, crc);
-    return this;
-  }
-
-  public synchronized ImmutableMap<String, Long> getChecksums() {
-    return ImmutableMap.copyOf(dictionary);
-  }
-
-  public synchronized ClassesChecksum merge(ClassesChecksum other) {
-    if (other != null) {
-      other.dictionary.entrySet().stream().forEach(entry -> this.dictionary.put(
-          entry.getKey(), entry.getValue()));
+  private void ensureMap() {
+    if (dictionary == null) {
+      dictionary = new Object2LongOpenHashMap<>();
     }
-    return this;
   }
 
-  @Override
-  public synchronized String toString() {
+  private void append(JsonObject json) {
+    ensureMap();
+    json.entrySet()
+        .forEach(
+            entry ->
+                dictionary.put(entry.getKey(), Long.parseLong(entry.getValue().getAsString(), 16)));
+  }
+
+  public void addChecksum(String classDescriptor, long crc) {
+    ensureMap();
+    dictionary.put(classDescriptor, crc);
+  }
+
+  public Object2LongMap<String> getChecksums() {
+    return dictionary;
+  }
+
+  public String toJsonString() {
     // In order to make printing of markers deterministic we sort the entries by key.
     final JsonObject sortedJson = new JsonObject();
-    dictionary.entrySet()
-        .stream()
+    dictionary.object2LongEntrySet().stream()
         .sorted(Comparator.comparing(Map.Entry::getKey))
         .forEach(
-            entry -> sortedJson.addProperty(entry.getKey(), Long.toHexString(entry.getValue())));
+            entry ->
+                sortedJson.addProperty(entry.getKey(), Long.toString(entry.getLongValue(), 16)));
     return "" + PREFIX_CHAR0 + PREFIX_CHAR1 + PREFIX_CHAR2 + sortedJson;
   }
 
-  // Try to parse str as a marker.
-  // Returns null if parsing fails.
-  public static ClassesChecksum parse(DexString dexString) {
+  // Try to parse the string as a marker and append its content if successful.
+  public void tryParseAndAppend(DexString dexString) {
     if (dexString.size > 2
         && dexString.content[0] == PREFIX_CHAR0
         && dexString.content[1] == PREFIX_CHAR1
@@ -71,11 +65,10 @@
       try {
         JsonElement result = new JsonParser().parse(str);
         if (result.isJsonObject()) {
-          return new ClassesChecksum(result.getAsJsonObject());
+          append(result.getAsJsonObject());
         }
       } catch (JsonSyntaxException ignored) {}
     }
-    return null;
   }
 
   public static boolean preceedChecksumMarker(DexString string) {
diff --git a/src/main/java/com/android/tools/r8/dex/DexParser.java b/src/main/java/com/android/tools/r8/dex/DexParser.java
index 28b60a3..e722669 100644
--- a/src/main/java/com/android/tools/r8/dex/DexParser.java
+++ b/src/main/java/com/android/tools/r8/dex/DexParser.java
@@ -40,6 +40,8 @@
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexMethodHandle;
 import com.android.tools.r8.graph.DexMethodHandle.MethodHandleType;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.DexProgramClass.ChecksumSupplier;
 import com.android.tools.r8.graph.DexProto;
 import com.android.tools.r8.graph.DexString;
 import com.android.tools.r8.graph.DexType;
@@ -60,11 +62,11 @@
 import com.android.tools.r8.origin.PathOrigin;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.Pair;
-import com.google.common.collect.ImmutableMap;
 import com.google.common.io.ByteStreams;
 import it.unimi.dsi.fastutil.ints.Int2IntArrayMap;
 import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
 import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
+import it.unimi.dsi.fastutil.objects.Object2LongMap;
 import java.io.ByteArrayOutputStream;
 import java.io.IOException;
 import java.io.InputStream;
@@ -87,7 +89,7 @@
   private int[] stringIDs;
   private final ClassKind classKind;
   private final InternalOptions options;
-  private ImmutableMap<String, Long> checksums;
+  private Object2LongMap<String> checksums;
 
   public static DexSection[] parseMapFrom(Path file) throws IOException {
     return parseMapFrom(Files.newInputStream(file), new PathOrigin(file));
@@ -712,9 +714,10 @@
       DexEncodedMethod[] virtualMethods = DexEncodedMethod.EMPTY_ARRAY;
       AnnotationsDirectory annotationsDirectory = annotationsDirectoryAt(annotationsOffsets[i]);
 
+      Long checksum = null;
       if (checksums != null && classDataOffsets[i] != 0) {
         String desc = type.descriptor.toASCIIString();
-        Long checksum = checksums.get(desc);
+        checksum = checksums.getOrDefault(desc, null);
         if (!options.dexClassChecksumFilter.test(desc, checksum)) {
           continue;
         }
@@ -752,6 +755,10 @@
       AttributesAndAnnotations attrs =
           new AttributesAndAnnotations(type, annotationsDirectory.clazz, options.itemFactory);
 
+      Long finalChecksum = checksum;
+      ChecksumSupplier checksumSupplier =
+          finalChecksum == null ? DexProgramClass::invalidChecksumRequest : c -> finalChecksum;
+
       DexClass clazz =
           classKind.create(
               type,
@@ -770,7 +777,8 @@
               instanceFields,
               directMethods,
               virtualMethods,
-              dexItemFactory.getSkipNameValidationForTesting());
+              dexItemFactory.getSkipNameValidationForTesting(),
+              checksumSupplier);
       classCollection.accept(clazz);  // Update the application object.
     }
   }
@@ -939,23 +947,15 @@
   }
 
   private void populateChecksums() {
-    ClassesChecksum checksums = null;
+    ClassesChecksum parsedChecksums = new ClassesChecksum();
     for (int i = stringIDs.length - 1; i >= 0; i--) {
       DexString value = indexedItems.getString(i);
-      ClassesChecksum checksum = ClassesChecksum.parse(value);
-      if (checksum != null) {
-        if (checksums == null) {
-          checksums = checksum;
-        } else {
-          checksums.merge(checksum);
-        }
-      } else if (ClassesChecksum.preceedChecksumMarker(value)) {
+      if (ClassesChecksum.preceedChecksumMarker(value)) {
         break;
       }
+      parsedChecksums.tryParseAndAppend(value);
     }
-    if (checksums != null) {
-      this.checksums = checksums.getChecksums();
-    }
+    this.checksums = parsedChecksums.getChecksums();
   }
 
   /**
diff --git a/src/main/java/com/android/tools/r8/graph/AssemblyWriter.java b/src/main/java/com/android/tools/r8/graph/AssemblyWriter.java
index e1c8b62..33c8dde 100644
--- a/src/main/java/com/android/tools/r8/graph/AssemblyWriter.java
+++ b/src/main/java/com/android/tools/r8/graph/AssemblyWriter.java
@@ -87,6 +87,7 @@
           ? naming.originalSignatureOf(field.field)
           : FieldSignature.fromDexField(field.field);
       writeAnnotations(field.annotations, ps);
+      ps.print(field.accessFlags + " ");
       ps.println(fieldSignature);
     }
   }
diff --git a/src/main/java/com/android/tools/r8/graph/ClassKind.java b/src/main/java/com/android/tools/r8/graph/ClassKind.java
index 6443795..14fa13e 100644
--- a/src/main/java/com/android/tools/r8/graph/ClassKind.java
+++ b/src/main/java/com/android/tools/r8/graph/ClassKind.java
@@ -4,7 +4,8 @@
 
 package com.android.tools.r8.graph;
 
-import com.android.tools.r8.ProgramResource;
+import com.android.tools.r8.ProgramResource.Kind;
+import com.android.tools.r8.graph.DexProgramClass.ChecksumSupplier;
 import com.android.tools.r8.origin.Origin;
 import java.util.List;
 import java.util.function.Consumer;
@@ -13,13 +14,89 @@
 /** Kind of the application class. Can be program, classpath or library. */
 public enum ClassKind {
   PROGRAM(DexProgramClass::new, DexClass::isProgramClass),
-  CLASSPATH(DexClasspathClass::new, DexClass::isClasspathClass),
-  LIBRARY(DexLibraryClass::new, DexClass::isLibraryClass);
+  CLASSPATH(
+      (type,
+          kind,
+          origin,
+          accessFlags,
+          superType,
+          interfaces,
+          sourceFile,
+          nestHost,
+          nestMembers,
+          enclosingMember,
+          innerClasses,
+          annotations,
+          staticFields,
+          instanceFields,
+          directMethods,
+          virtualMethods,
+          skipNameValidationForTesting,
+          checksumSupplier) -> {
+        return new DexClasspathClass(
+            type,
+            kind,
+            origin,
+            accessFlags,
+            superType,
+            interfaces,
+            sourceFile,
+            nestHost,
+            nestMembers,
+            enclosingMember,
+            innerClasses,
+            annotations,
+            staticFields,
+            instanceFields,
+            directMethods,
+            virtualMethods,
+            skipNameValidationForTesting);
+      },
+      DexClass::isClasspathClass),
+  LIBRARY(
+      (type,
+          kind,
+          origin,
+          accessFlags,
+          superType,
+          interfaces,
+          sourceFile,
+          nestHost,
+          nestMembers,
+          enclosingMember,
+          innerClasses,
+          annotations,
+          staticFields,
+          instanceFields,
+          directMethods,
+          virtualMethods,
+          skipNameValidationForTesting,
+          checksumSupplier) -> {
+        return new DexLibraryClass(
+            type,
+            kind,
+            origin,
+            accessFlags,
+            superType,
+            interfaces,
+            sourceFile,
+            nestHost,
+            nestMembers,
+            enclosingMember,
+            innerClasses,
+            annotations,
+            staticFields,
+            instanceFields,
+            directMethods,
+            virtualMethods,
+            skipNameValidationForTesting);
+      },
+      DexClass::isLibraryClass);
 
   private interface Factory {
     DexClass create(
         DexType type,
-        ProgramResource.Kind kind,
+        Kind kind,
         Origin origin,
         ClassAccessFlags accessFlags,
         DexType superType,
@@ -34,7 +111,8 @@
         DexEncodedField[] instanceFields,
         DexEncodedMethod[] directMethods,
         DexEncodedMethod[] virtualMethods,
-        boolean skipNameValidationForTesting);
+        boolean skipNameValidationForTesting,
+        ChecksumSupplier checksumSupplier);
   }
 
   private final Factory factory;
@@ -47,7 +125,7 @@
 
   public DexClass create(
       DexType type,
-      ProgramResource.Kind kind,
+      Kind kind,
       Origin origin,
       ClassAccessFlags accessFlags,
       DexType superType,
@@ -62,7 +140,8 @@
       DexEncodedField[] instanceFields,
       DexEncodedMethod[] directMethods,
       DexEncodedMethod[] virtualMethods,
-      boolean skipNameValidationForTesting) {
+      boolean skipNameValidationForTesting,
+      ChecksumSupplier checksumSupplier) {
     return factory.create(
         type,
         kind,
@@ -80,7 +159,8 @@
         instanceFields,
         directMethods,
         virtualMethods,
-        skipNameValidationForTesting);
+        skipNameValidationForTesting,
+        checksumSupplier);
   }
 
   public boolean isOfKind(DexClass clazz) {
diff --git a/src/main/java/com/android/tools/r8/graph/DexApplication.java b/src/main/java/com/android/tools/r8/graph/DexApplication.java
index c2375c5..29af63e 100644
--- a/src/main/java/com/android/tools/r8/graph/DexApplication.java
+++ b/src/main/java/com/android/tools/r8/graph/DexApplication.java
@@ -8,7 +8,6 @@
 
 import com.android.tools.r8.DataResourceProvider;
 import com.android.tools.r8.dex.ApplicationReader.ProgramClassConflictResolver;
-import com.android.tools.r8.dex.ClassesChecksum;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.naming.ClassNameMapper;
 import com.android.tools.r8.utils.InternalOptions;
@@ -35,7 +34,6 @@
 
   public final InternalOptions options;
   public final DexItemFactory dexItemFactory;
-  public final ClassesChecksum checksums;
 
   // Information on the lexicographically largest string referenced from code.
   public final DexString highestSortingString;
@@ -45,14 +43,12 @@
       ClassNameMapper proguardMap,
       ImmutableList<DataResourceProvider> dataResourceProviders,
       ImmutableSet<DexType> mainDexList,
-      ClassesChecksum checksums,
       InternalOptions options,
       DexString highestSortingString,
       Timing timing) {
     this.proguardMap = proguardMap;
     this.dataResourceProviders = dataResourceProviders;
     this.mainDexList = mainDexList;
-    this.checksums = checksums;
     this.options = options;
     this.dexItemFactory = options.itemFactory;
     this.highestSortingString = highestSortingString;
@@ -108,10 +104,6 @@
     return classes;
   }
 
-  public ClassesChecksum getChecksums() {
-    return checksums;
-  }
-
   public abstract DexClass definitionFor(DexType type);
 
   public abstract DexProgramClass programDefinitionFor(DexType type);
@@ -137,7 +129,6 @@
 
     public final InternalOptions options;
     public final DexItemFactory dexItemFactory;
-    protected ClassesChecksum checksums;
     ClassNameMapper proguardMap;
     final Timing timing;
 
@@ -150,7 +141,6 @@
       this.dexItemFactory = options.itemFactory;
       this.timing = timing;
       this.synthesizedClasses = new ArrayList<>();
-      this.checksums = null;
     }
 
     abstract T self();
@@ -165,7 +155,6 @@
       dexItemFactory = application.dexItemFactory;
       mainDexList.addAll(application.mainDexList);
       synthesizedClasses = new ArrayList<>();
-      checksums = application.checksums;
     }
 
     public synchronized T setProguardMap(ClassNameMapper proguardMap) {
@@ -224,15 +213,6 @@
       return this;
     }
 
-    public Builder<T> mergeChecksums(ClassesChecksum other) {
-      if (checksums == null) {
-        checksums = other;
-      } else {
-        checksums.merge(other);
-      }
-      return this;
-    }
-
     public abstract DexApplication build();
   }
 
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 ee50ef0..7ba3f89 100644
--- a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
+++ b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
@@ -6,7 +6,6 @@
 import static com.android.tools.r8.ir.analysis.type.ClassTypeLatticeElement.computeLeastUpperBoundOfInterfaces;
 import static com.google.common.base.Predicates.alwaysTrue;
 
-import com.android.tools.r8.dex.ClassesChecksum;
 import com.android.tools.r8.dex.Constants;
 import com.android.tools.r8.dex.Marker;
 import com.android.tools.r8.graph.DexDebugEvent.AdvanceLine;
@@ -49,7 +48,6 @@
 import java.util.IdentityHashMap;
 import java.util.List;
 import java.util.Map;
-import java.util.Objects;
 import java.util.Set;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.function.Consumer;
@@ -1220,11 +1218,6 @@
     return markers;
   }
 
-  public synchronized ClassesChecksum extractChecksum() {
-    return strings.keySet().stream().map(s -> ClassesChecksum.parse(s)).filter(Objects::nonNull)
-        .reduce(null, (s1, s2) -> s1 == null ? s2 : s1.merge(s2));
-  }
-
   synchronized public DexType createType(DexString descriptor) {
     assert !sorted;
     assert descriptor != null;
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 7bfe2a6..92a4963 100644
--- a/src/main/java/com/android/tools/r8/graph/DexProgramClass.java
+++ b/src/main/java/com/android/tools/r8/graph/DexProgramClass.java
@@ -7,6 +7,7 @@
 import com.android.tools.r8.ProgramResource.Kind;
 import com.android.tools.r8.dex.IndexedItemCollection;
 import com.android.tools.r8.dex.MixedSectionCollection;
+import com.android.tools.r8.errors.CompilationError;
 import com.android.tools.r8.kotlin.KotlinInfo;
 import com.android.tools.r8.origin.Origin;
 import java.util.ArrayList;
@@ -20,6 +21,12 @@
 import java.util.function.Supplier;
 
 public class DexProgramClass extends DexClass implements Supplier<DexProgramClass> {
+
+  @FunctionalInterface
+  public interface ChecksumSupplier {
+    long getChecksum(DexProgramClass programClass);
+  }
+
   public static final DexProgramClass[] EMPTY_ARRAY = {};
 
   private static final DexEncodedArray SENTINEL_NOT_YET_COMPUTED =
@@ -31,9 +38,11 @@
   private int initialClassFileVersion = -1;
   private KotlinInfo kotlinInfo = null;
 
+  private final ChecksumSupplier checksumSupplier;
+
   public DexProgramClass(
       DexType type,
-      ProgramResource.Kind originKind,
+      Kind originKind,
       Origin origin,
       ClassAccessFlags accessFlags,
       DexType superType,
@@ -48,7 +57,8 @@
       DexEncodedField[] instanceFields,
       DexEncodedMethod[] directMethods,
       DexEncodedMethod[] virtualMethods,
-      boolean skipNameValidationForTesting) {
+      boolean skipNameValidationForTesting,
+      ChecksumSupplier checksumSupplier) {
     this(
         type,
         originKind,
@@ -67,12 +77,13 @@
         directMethods,
         virtualMethods,
         skipNameValidationForTesting,
+        checksumSupplier,
         Collections.emptyList());
   }
 
   public DexProgramClass(
       DexType type,
-      ProgramResource.Kind originKind,
+      Kind originKind,
       Origin origin,
       ClassAccessFlags accessFlags,
       DexType superType,
@@ -88,6 +99,7 @@
       DexEncodedMethod[] directMethods,
       DexEncodedMethod[] virtualMethods,
       boolean skipNameValidationForTesting,
+      ChecksumSupplier checksumSupplier,
       Collection<DexProgramClass> synthesizedDirectlyFrom) {
     super(
         sourceFile,
@@ -106,8 +118,10 @@
         classAnnotations,
         origin,
         skipNameValidationForTesting);
+    assert checksumSupplier != null;
     assert classAnnotations != null;
     this.originKind = originKind;
+    this.checksumSupplier = checksumSupplier;
     this.synthesizedFrom = new HashSet<>();
     synthesizedDirectlyFrom.forEach(this::addSynthesizedFrom);
   }
@@ -471,4 +485,17 @@
           }
         };
   }
+
+  public static long invalidChecksumRequest(DexProgramClass clazz) {
+    throw new CompilationError(
+        clazz + " has no checksum information while checksum encoding is requested", clazz.origin);
+  }
+
+  public static long checksumFromType(DexProgramClass clazz) {
+    return clazz.type.hashCode();
+  }
+
+  public long getChecksum() {
+    return checksumSupplier.getChecksum(this);
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/graph/DirectMappedDexApplication.java b/src/main/java/com/android/tools/r8/graph/DirectMappedDexApplication.java
index efe63f6..6308a16 100644
--- a/src/main/java/com/android/tools/r8/graph/DirectMappedDexApplication.java
+++ b/src/main/java/com/android/tools/r8/graph/DirectMappedDexApplication.java
@@ -7,7 +7,6 @@
 package com.android.tools.r8.graph;
 
 import com.android.tools.r8.DataResourceProvider;
-import com.android.tools.r8.dex.ClassesChecksum;
 import com.android.tools.r8.graph.LazyLoadedDexApplication.AllClasses;
 import com.android.tools.r8.naming.ClassNameMapper;
 import com.android.tools.r8.utils.InternalOptions;
@@ -41,7 +40,6 @@
       ImmutableList<DexLibraryClass> libraryClasses,
       ImmutableList<DataResourceProvider> dataResourceProviders,
       ImmutableSet<DexType> mainDexList,
-      ClassesChecksum checksums,
       InternalOptions options,
       DexString highestSortingString,
       Timing timing) {
@@ -49,7 +47,6 @@
         proguardMap,
         dataResourceProviders,
         mainDexList,
-        checksums,
         options,
         highestSortingString,
         timing);
@@ -203,7 +200,6 @@
           libraryClasses,
           ImmutableList.copyOf(dataResourceProviders),
           ImmutableSet.copyOf(mainDexList),
-          checksums,
           options,
           highestSortingString,
           timing);
diff --git a/src/main/java/com/android/tools/r8/graph/JarClassFileReader.java b/src/main/java/com/android/tools/r8/graph/JarClassFileReader.java
index 0fcb5b6..da8d8d7 100644
--- a/src/main/java/com/android/tools/r8/graph/JarClassFileReader.java
+++ b/src/main/java/com/android/tools/r8/graph/JarClassFileReader.java
@@ -12,10 +12,10 @@
 import static org.objectweb.asm.Opcodes.V9;
 
 import com.android.tools.r8.ProgramResource.Kind;
-import com.android.tools.r8.dex.ClassesChecksum;
 import com.android.tools.r8.dex.Constants;
 import com.android.tools.r8.errors.CompilationError;
 import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.graph.DexProgramClass.ChecksumSupplier;
 import com.android.tools.r8.graph.DexValue.DexValueAnnotation;
 import com.android.tools.r8.graph.DexValue.DexValueArray;
 import com.android.tools.r8.graph.DexValue.DexValueBoolean;
@@ -75,13 +75,11 @@
 
   private final JarApplicationReader application;
   private final Consumer<DexClass> classConsumer;
-  private final ClassesChecksum checksums;
 
   public JarClassFileReader(
       JarApplicationReader application, Consumer<DexClass> classConsumer) {
     this.application = application;
     this.classConsumer = classConsumer;
-    this.checksums = new ClassesChecksum();
   }
 
   public void read(Origin origin, ClassKind classKind, InputStream input) throws IOException {
@@ -116,8 +114,9 @@
         parsingOptions |= SKIP_DEBUG;
       }
     }
-    reader.accept(new CreateDexClassVisitor(
-        origin, classKind, reader.b, application, classConsumer, checksums), parsingOptions);
+    reader.accept(
+        new CreateDexClassVisitor(origin, classKind, reader.b, application, classConsumer),
+        parsingOptions);
 
     // Read marker.
     if (reader.getItemCount() > CfApplicationWriter.MARKER_STRING_CONSTANT_POOL_INDEX
@@ -136,10 +135,6 @@
     }
   }
 
-  public ClassesChecksum getChecksums() {
-    return checksums;
-  }
-
   private static int cleanAccessFlags(int access) {
     // Clear the "synthetic attribute" and "deprecated" attribute-flags if present.
     return access & ~ACC_SYNTHETIC_ATTRIBUTE & ~ACC_DEPRECATED;
@@ -218,22 +213,19 @@
     private final List<DexEncodedMethod> virtualMethods = new ArrayList<>();
     private final Set<Wrapper<DexMethod>> methodSignatures = new HashSet<>();
     private boolean hasReachabilitySensitiveMethod = false;
-    private ClassesChecksum checksums;
 
     public CreateDexClassVisitor(
         Origin origin,
         ClassKind classKind,
         byte[] classCache,
         JarApplicationReader application,
-        Consumer<DexClass> classConsumer,
-        ClassesChecksum checksums) {
+        Consumer<DexClass> classConsumer) {
       super(ASM_VERSION);
       this.origin = origin;
       this.classKind = classKind;
       this.classConsumer = classConsumer;
       this.context.classCache = classCache;
       this.application = application;
-      this.checksums = checksums;
     }
 
     @Override
@@ -419,7 +411,8 @@
               instanceFields.toArray(DexEncodedField.EMPTY_ARRAY),
               directMethods.toArray(DexEncodedMethod.EMPTY_ARRAY),
               virtualMethods.toArray(DexEncodedMethod.EMPTY_ARRAY),
-              application.getFactory().getSkipNameValidationForTesting());
+              application.getFactory().getSkipNameValidationForTesting(),
+              getChecksumSupplier(classKind));
       InnerClassAttribute innerClassAttribute = clazz.getInnerClassAttributeForThisClass();
       // A member class should not be a local or anonymous class.
       if (innerClassAttribute != null && innerClassAttribute.getOuter() != null) {
@@ -453,16 +446,22 @@
         context.owner = clazz;
       }
       if (clazz.isProgramClass()) {
-        clazz.asProgramClass().setInitialClassFileVersion(version);
-        if (application.options.encodeChecksums) {
-          CRC32 crc = new CRC32();
-          crc.update(this.context.classCache, 0, this.context.classCache.length);
-          checksums.addChecksum(type.descriptor.toASCIIString(), crc.getValue());
-        }
+        DexProgramClass programClass = clazz.asProgramClass();
+        programClass.setInitialClassFileVersion(version);
       }
       classConsumer.accept(clazz);
     }
 
+    private ChecksumSupplier getChecksumSupplier(ClassKind classKind) {
+      if (application.options.encodeChecksums && classKind == ClassKind.PROGRAM) {
+        CRC32 crc = new CRC32();
+        crc.update(this.context.classCache, 0, this.context.classCache.length);
+        final long value = crc.getValue();
+        return clazz -> value;
+      }
+      return DexProgramClass::invalidChecksumRequest;
+    }
+
     private void checkName(String name) {
       if (!application.getFactory().getSkipNameValidationForTesting()
           && !DexString.isValidSimpleName(application.options.minApiLevel, name)) {
diff --git a/src/main/java/com/android/tools/r8/graph/LazyLoadedDexApplication.java b/src/main/java/com/android/tools/r8/graph/LazyLoadedDexApplication.java
index 0458321..24ec3c3 100644
--- a/src/main/java/com/android/tools/r8/graph/LazyLoadedDexApplication.java
+++ b/src/main/java/com/android/tools/r8/graph/LazyLoadedDexApplication.java
@@ -8,7 +8,6 @@
 
 import com.android.tools.r8.DataResourceProvider;
 import com.android.tools.r8.dex.ApplicationReader.ProgramClassConflictResolver;
-import com.android.tools.r8.dex.ClassesChecksum;
 import com.android.tools.r8.naming.ClassNameMapper;
 import com.android.tools.r8.utils.ClasspathClassCollection;
 import com.android.tools.r8.utils.InternalOptions;
@@ -36,7 +35,6 @@
       ClasspathClassCollection classpathClasses,
       LibraryClassCollection libraryClasses,
       ImmutableSet<DexType> mainDexList,
-      ClassesChecksum checksum,
       InternalOptions options,
       DexString highestSortingString,
       Timing timing) {
@@ -44,7 +42,6 @@
         proguardMap,
         dataResourceProviders,
         mainDexList,
-        checksum,
         options,
         highestSortingString,
         timing);
@@ -236,7 +233,6 @@
           classpathClasses,
           libraryClasses,
           ImmutableSet.copyOf(mainDexList),
-          checksums,
           options,
           highestSortingString,
           timing);
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 d068677..0bdfaf1 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
@@ -864,7 +864,7 @@
                 // unused out-values.
                 codeRewriter.rewriteMoveResult(code);
                 deadCodeRemover.run(code);
-                codeRewriter.removeAssumeInstructions(code);
+                CodeRewriter.removeAssumeInstructions(appView, code);
                 consumer.accept(code, method);
                 return null;
               }));
@@ -1158,13 +1158,6 @@
       serviceLoaderRewriter.rewrite(code);
     }
 
-    if (classStaticizer != null) {
-      classStaticizer.fixupMethodCode(method, code);
-      assert code.isConsistentSSA();
-    }
-
-    previous = printMethod(code, "IR after class staticizer (SSA)", previous);
-
     if (identifierNameStringMarker != null) {
       identifierNameStringMarker.decoupleIdentifierNameStringsInMethod(method, code);
       assert code.isConsistentSSA();
@@ -1424,17 +1417,11 @@
         appView.callSiteOptimizationInfoPropagator().collectCallSiteOptimizationInfo(code);
       }
 
-      // Compute optimization info summary for the current method unless it is pinned
-      // (in that case we should not be making any assumptions about the behavior of the method).
-      if (!appView.appInfo().withLiveness().isPinned(method.method)) {
-        methodOptimizationInfoCollector
-            .collectMethodOptimizationInfo(method, code, feedback, dynamicTypeOptimization);
-        FieldValueAnalysis.run(appView, code, feedback, method);
-      }
+      collectOptimizationInfo(code, feedback);
     }
 
     if (aliasIntroducer != null || nonNullTracker != null || dynamicTypeOptimization != null) {
-      codeRewriter.removeAssumeInstructions(code);
+      CodeRewriter.removeAssumeInstructions(appView, code);
       assert code.isConsistentSSA();
     }
 
@@ -1470,7 +1457,18 @@
     finalizeIR(method, code, feedback);
   }
 
-  private void finalizeIR(DexEncodedMethod method, IRCode code, OptimizationFeedback feedback) {
+  // Compute optimization info summary for the current method unless it is pinned
+  // (in that case we should not be making any assumptions about the behavior of the method).
+  public void collectOptimizationInfo(IRCode code, OptimizationFeedback feedback) {
+    if (appView.appInfo().withLiveness().isPinned(code.method.method)) {
+      return;
+    }
+    methodOptimizationInfoCollector
+        .collectMethodOptimizationInfo(code.method, code, feedback, dynamicTypeOptimization);
+    FieldValueAnalysis.run(appView, code, feedback, code.method);
+  }
+
+  public void finalizeIR(DexEncodedMethod method, IRCode code, OptimizationFeedback feedback) {
     code.traceBlocks();
     if (options.isGeneratingClassFiles()) {
       finalizeToCf(method, code, feedback);
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/BackportedMethodRewriter.java b/src/main/java/com/android/tools/r8/ir/desugar/BackportedMethodRewriter.java
index bf82f85..5edd98e 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/BackportedMethodRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/BackportedMethodRewriter.java
@@ -17,6 +17,7 @@
 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.DexProgramClass.ChecksumSupplier;
 import com.android.tools.r8.graph.DexProto;
 import com.android.tools.r8.graph.DexString;
 import com.android.tools.r8.graph.DexType;
@@ -48,6 +49,7 @@
 import java.util.IdentityHashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.Objects;
 import java.util.Set;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.ExecutionException;
@@ -232,9 +234,10 @@
               DexAnnotationSet.empty(),
               DexEncodedField.EMPTY_ARRAY,
               DexEncodedField.EMPTY_ARRAY,
-              new DexEncodedMethod[]{dexEncodedMethod},
+              new DexEncodedMethod[] {dexEncodedMethod},
               DexEncodedMethod.EMPTY_ARRAY,
               factory.getSkipNameValidationForTesting(),
+              getChecksumSupplier(dexEncodedMethod),
               referencingClasses);
       boolean addToMainDexList =
           referencingClasses.stream()
@@ -246,6 +249,14 @@
     }
   }
 
+  private ChecksumSupplier getChecksumSupplier(DexEncodedMethod method) {
+    if (!appView.options().encodeChecksums) {
+      return DexProgramClass::invalidChecksumRequest;
+    }
+    long hash = Objects.hash(method, method.getCode());
+    return c -> hash;
+  }
+
   private MethodProvider getMethodProviderOrNull(DexMethod method) {
     DexMethod original = appView.graphLense().getOriginalMethodSignature(method);
     assert original != null;
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryAPIConverter.java b/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryAPIConverter.java
index cbdef95..b7e8d31 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryAPIConverter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryAPIConverter.java
@@ -169,8 +169,12 @@
       }
       DexEncodedMethod dexEncodedMethod = dexClass.lookupVirtualMethod(method);
       if (dexEncodedMethod != null) {
-        if (appView.options().desugaredLibraryConfiguration.getEmulateLibraryInterface()
-            .containsKey(dexClass.type)) {
+        if (appView
+                .options()
+                .desugaredLibraryConfiguration
+                .getEmulateLibraryInterface()
+                .containsKey(dexClass.type)
+            || appView.rewritePrefix.hasRewrittenType(dexClass.type)) {
           return false;
         }
         foundOverrideToRewrite = true;
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryWrapperSynthesizer.java b/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryWrapperSynthesizer.java
index 2bf43f2..e40252f 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryWrapperSynthesizer.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryWrapperSynthesizer.java
@@ -19,6 +19,7 @@
 import com.android.tools.r8.graph.DexLibraryClass;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.DexProgramClass.ChecksumSupplier;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.DexTypeList;
 import com.android.tools.r8.graph.FieldAccessFlags;
@@ -254,9 +255,24 @@
         }, // Conversions methods will be added later.
         virtualMethods,
         factory.getSkipNameValidationForTesting(),
+        getChecksumSupplier(this, clazz.type),
         Collections.emptyList());
   }
 
+  private ChecksumSupplier getChecksumSupplier(DesugaredLibraryWrapperSynthesizer synthesizer, DexType keyType) {
+    return clazz -> {
+      // The synthesized type wrappers are constructed lazily, so their lookup must be delayed
+      // until the point the checksum is requested (at write time). The presence of a wrapper
+      // affects the implementation of the conversion functions, so they must be accounted for in
+      // the checksum.
+      boolean hasWrapper = synthesizer.typeWrappers.containsKey(keyType);
+      boolean hasViviWrapper = synthesizer.vivifiedTypeWrappers.containsKey(keyType);
+      return ((long) clazz.type.hashCode())
+          + 7 * (long) Boolean.hashCode(hasWrapper)
+          + 11 * (long) Boolean.hashCode(hasViviWrapper);
+    };
+  }
+
   private DexEncodedMethod[] synthesizeVirtualMethodsForVivifiedTypeWrapper(
       DexLibraryClass dexClass, DexEncodedField wrapperField) {
     List<DexEncodedMethod> dexMethods = allImplementedMethods(dexClass);
@@ -470,29 +486,37 @@
   void finalizeWrappers(
       DexApplication.Builder<?> builder, IRConverter irConverter, ExecutorService executorService)
       throws ExecutionException {
+    assert verifyAllClassesGenerated();
+    // We register first, then optimize to avoid warnings due to missing parameter types.
+    registerWrappers(builder, typeWrappers);
+    registerWrappers(builder, vivifiedTypeWrappers);
+    finalizeWrappers(irConverter, executorService, typeWrappers, this::generateTypeConversions);
     finalizeWrappers(
-        builder, irConverter, executorService, typeWrappers, this::generateTypeConversions);
-    finalizeWrappers(
-        builder,
         irConverter,
         executorService,
         vivifiedTypeWrappers,
         this::generateVivifiedTypeConversions);
   }
 
+  private void registerWrappers(
+      DexApplication.Builder<?> builder, Map<DexType, Pair<DexType, DexProgramClass>> wrappers) {
+    for (DexType type : wrappers.keySet()) {
+      DexProgramClass pgrmClass = wrappers.get(type).getSecond();
+      assert pgrmClass != null;
+      registerSynthesizedClass(pgrmClass, builder);
+    }
+  }
+
   private void finalizeWrappers(
-      DexApplication.Builder<?> builder,
       IRConverter irConverter,
       ExecutorService executorService,
       Map<DexType, Pair<DexType, DexProgramClass>> wrappers,
       BiConsumer<DexType, DexProgramClass> generateConversions)
       throws ExecutionException {
-    assert verifyAllClassesGenerated();
     for (DexType type : wrappers.keySet()) {
       DexProgramClass pgrmClass = wrappers.get(type).getSecond();
       assert pgrmClass != null;
       generateConversions.accept(type, pgrmClass);
-      registerSynthesizedClass(pgrmClass, builder);
       irConverter.optimizeSynthesizedClass(pgrmClass, executorService);
     }
   }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/InterfaceMethodRewriter.java b/src/main/java/com/android/tools/r8/ir/desugar/InterfaceMethodRewriter.java
index b79a316..9d6e613 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/InterfaceMethodRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/InterfaceMethodRewriter.java
@@ -735,6 +735,7 @@
         emulationMethods.toArray(DexEncodedMethod.EMPTY_ARRAY),
         DexEncodedMethod.EMPTY_ARRAY,
         factory.getSkipNameValidationForTesting(),
+        DexProgramClass::checksumFromType,
         Collections.singletonList(theInterface));
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/InterfaceProcessor.java b/src/main/java/com/android/tools/r8/ir/desugar/InterfaceProcessor.java
index 8a990f2..8b8ef05 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/InterfaceProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/InterfaceProcessor.java
@@ -21,6 +21,7 @@
 import com.android.tools.r8.graph.DexLibraryClass;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.DexProgramClass.ChecksumSupplier;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.DexTypeList;
 import com.android.tools.r8.graph.GraphLense.NestedGraphLense;
@@ -190,10 +191,19 @@
             companionMethods.toArray(DexEncodedMethod.EMPTY_ARRAY),
             DexEncodedMethod.EMPTY_ARRAY,
             rewriter.factory.getSkipNameValidationForTesting(),
+            getChecksumSupplier(iface),
             Collections.singletonList(iface));
     syntheticClasses.put(iface.type, companionClass);
   }
 
+  private ChecksumSupplier getChecksumSupplier(DexProgramClass iface) {
+    if (!appView.options().encodeChecksums) {
+      return DexProgramClass::invalidChecksumRequest;
+    }
+    long checksum = iface.getChecksum();
+    return c -> 7 * checksum;
+  }
+
   List<DexEncodedMethod> process(DexLibraryClass iface, Set<DexProgramClass> callers) {
     assert iface.isInterface();
 
@@ -263,6 +273,7 @@
             dispatchMethods.toArray(DexEncodedMethod.EMPTY_ARRAY),
             DexEncodedMethod.EMPTY_ARRAY,
             rewriter.factory.getSkipNameValidationForTesting(),
+            DexProgramClass::checksumFromType,
             callers);
     syntheticClasses.put(iface.type, dispatchClass);
     return dispatchMethods;
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 a397cd7..40932cb 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
@@ -30,12 +30,15 @@
 import com.android.tools.r8.ir.synthetic.SynthesizedCode;
 import com.android.tools.r8.origin.SynthesizedOrigin;
 import com.google.common.base.Suppliers;
+import com.google.common.primitives.Longs;
+import java.nio.ByteBuffer;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.List;
 import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.function.Supplier;
+import java.util.zip.CRC32;
 
 /**
  * Represents lambda class generated for a lambda descriptor in context of lambda instantiation
@@ -167,7 +170,8 @@
             synthesizeInstanceFields(),
             synthesizeDirectMethods(),
             synthesizeVirtualMethods(mainMethod),
-            rewriter.factory.getSkipNameValidationForTesting());
+            rewriter.factory.getSkipNameValidationForTesting(),
+            LambdaClass::computeChecksumForSynthesizedClass);
     // Optimize main method.
     rewriter.converter.appView.appInfo().addSynthesizedClass(clazz);
     rewriter.converter.optimizeSynthesizedMethod(clazz.lookupVirtualMethod(mainMethod));
@@ -180,6 +184,24 @@
     return clazz;
   }
 
+  private static long computeChecksumForSynthesizedClass(DexProgramClass clazz) {
+    // Checksum of synthesized classes are compute based off the depending input. This might
+    // create false positives (ie: unchanged lambda class detected as changed even thought only
+    // an unrelated part from a synthesizedFrom class is changed).
+
+    // Ideally, we should use some hashcode of the dex program class that is deterministic across
+    // compiles.
+    Collection<DexProgramClass> synthesizedFrom = clazz.getSynthesizedFrom();
+    ByteBuffer buffer = ByteBuffer.allocate(synthesizedFrom.size() * Longs.BYTES);
+    for (DexProgramClass from : synthesizedFrom) {
+      buffer.putLong(from.getChecksum());
+    }
+    CRC32 crc = new CRC32();
+    byte[] array = buffer.array();
+    crc.update(array, 0, array.length);
+    return crc.getValue();
+  }
+
   final DexField getCaptureField(int index) {
     return rewriter.factory.createField(this.type,
         descriptor.captures.values[index], rewriter.factory.createString("f$" + index));
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 dc9c9a0..ff95053 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
@@ -164,7 +164,8 @@
         DexEncodedField.EMPTY_ARRAY,
         DexEncodedMethod.EMPTY_ARRAY,
         DexEncodedMethod.EMPTY_ARRAY,
-        appView.dexItemFactory().getSkipNameValidationForTesting());
+        appView.dexItemFactory().getSkipNameValidationForTesting(),
+        DexProgramClass::checksumFromType);
   }
 
   void synthesizeNestConstructor(DexApplication.Builder<?> builder) {
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/TwrCloseResourceRewriter.java b/src/main/java/com/android/tools/r8/ir/desugar/TwrCloseResourceRewriter.java
index d746bf1..f43c7c6 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/TwrCloseResourceRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/TwrCloseResourceRewriter.java
@@ -152,6 +152,7 @@
             new DexEncodedMethod[] {method},
             DexEncodedMethod.EMPTY_ARRAY,
             appView.dexItemFactory().getSkipNameValidationForTesting(),
+            DexProgramClass::checksumFromType,
             referencingClasses);
 
     // Process created class and method.
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/CallSiteOptimizationInfoPropagator.java b/src/main/java/com/android/tools/r8/ir/optimize/CallSiteOptimizationInfoPropagator.java
index f93d6c5..29fa90f 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/CallSiteOptimizationInfoPropagator.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/CallSiteOptimizationInfoPropagator.java
@@ -177,11 +177,12 @@
       if (originalArg.hasLocalInfo() || !originalArg.getTypeLattice().isReference()) {
         continue;
       }
-      TypeLatticeElement dynamicType = callSiteOptimizationInfo.getDynamicType(argumentsSeen - 1);
-      if (dynamicType == null) {
+      TypeLatticeElement dynamicUpperBoundType =
+          callSiteOptimizationInfo.getDynamicUpperBoundType(argumentsSeen - 1);
+      if (dynamicUpperBoundType == null) {
         continue;
       }
-      if (dynamicType.isDefinitelyNull()) {
+      if (dynamicUpperBoundType.isDefinitelyNull()) {
         ConstNumber nullInstruction = code.createConstNull();
         nullInstruction.setPosition(instr.getPosition());
         affectedValues.addAll(originalArg.affectedValues());
@@ -191,20 +192,20 @@
       }
       // TODO(b/69963623): Handle other kinds of constants, e.g. number, string, or class.
       Value specializedArg;
-      if (dynamicType.strictlyLessThan(originalArg.getTypeLattice(), appView)) {
+      if (dynamicUpperBoundType.strictlyLessThan(originalArg.getTypeLattice(), appView)) {
         specializedArg = code.createValue(originalArg.getTypeLattice());
         affectedValues.addAll(originalArg.affectedValues());
         originalArg.replaceUsers(specializedArg);
         Assume<DynamicTypeAssumption> assumeType =
             Assume.createAssumeDynamicTypeInstruction(
-                dynamicType, null, specializedArg, originalArg, instr, appView);
+                dynamicUpperBoundType, null, specializedArg, originalArg, instr, appView);
         assumeType.setPosition(instr.getPosition());
         assumeInstructions.add(assumeType);
       } else {
         specializedArg = originalArg;
       }
       assert specializedArg != null && specializedArg.getTypeLattice().isReference();
-      if (dynamicType.isDefinitelyNotNull()) {
+      if (dynamicUpperBoundType.isDefinitelyNotNull()) {
         // If we already knew `arg` is never null, e.g., receiver, skip adding non-null.
         if (!specializedArg.getTypeLattice().isDefinitelyNotNull()) {
           Value nonNullArg = code.createValue(
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 a47ba2c..8631814 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
@@ -157,7 +157,7 @@
     this.dexItemFactory = appView.dexItemFactory();
   }
 
-  public void removeAssumeInstructions(IRCode code) {
+  public static void removeAssumeInstructions(AppView<?> appView, IRCode code) {
     // We need to update the types of all values whose definitions depend on a non-null value.
     // This is needed to preserve soundness of the types after the Assume<NonNullAssumption>
     // instructions have been removed.
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/Outliner.java b/src/main/java/com/android/tools/r8/ir/optimize/Outliner.java
index c681571..0795424 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/Outliner.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/Outliner.java
@@ -19,6 +19,7 @@
 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.DexProgramClass.ChecksumSupplier;
 import com.android.tools.r8.graph.DexProto;
 import com.android.tools.r8.graph.DexString;
 import com.android.tools.r8.graph.DexType;
@@ -1353,27 +1354,29 @@
     DexTypeList interfaces = DexTypeList.empty();
     DexString sourceFile = appView.dexItemFactory().createString("outline");
     ClassAccessFlags accessFlags = ClassAccessFlags.fromSharedAccessFlags(Constants.ACC_PUBLIC);
-    DexProgramClass clazz =
-        new DexProgramClass(
-            type,
-            null,
-            new SynthesizedOrigin("outlining", getClass()),
-            accessFlags,
-            superType,
-            interfaces,
-            sourceFile,
-            null,
-            Collections.emptyList(),
-            null,
-            Collections.emptyList(),
-            // TODO: Build dex annotations structure.
-            DexAnnotationSet.empty(),
-            DexEncodedField.EMPTY_ARRAY, // Static fields.
-            DexEncodedField.EMPTY_ARRAY, // Instance fields.
-            direct,
-            DexEncodedMethod.EMPTY_ARRAY, // Virtual methods.
-            appView.dexItemFactory().getSkipNameValidationForTesting());
-    return clazz;
+    assert !appView.options().encodeChecksums;
+    // The outliner is R8 only and checksum is not a supported part of R8 compilation.
+    ChecksumSupplier checksumSupplier = DexProgramClass::invalidChecksumRequest;
+    return new DexProgramClass(
+        type,
+        null,
+        new SynthesizedOrigin("outlining", getClass()),
+        accessFlags,
+        superType,
+        interfaces,
+        sourceFile,
+        null,
+        Collections.emptyList(),
+        null,
+        Collections.emptyList(),
+        // TODO: Build dex annotations structure.
+        DexAnnotationSet.empty(),
+        DexEncodedField.EMPTY_ARRAY, // Static fields.
+        DexEncodedField.EMPTY_ARRAY, // Instance fields.
+        direct,
+        DexEncodedMethod.EMPTY_ARRAY, // Virtual methods.
+        appView.dexItemFactory().getSkipNameValidationForTesting(),
+        checksumSupplier);
   }
 
   private List<Outline> selectOutlines() {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/ServiceLoaderRewriter.java b/src/main/java/com/android/tools/r8/ir/optimize/ServiceLoaderRewriter.java
index 7830f6e..451788f 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/ServiceLoaderRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/ServiceLoaderRewriter.java
@@ -14,6 +14,7 @@
 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.DexProgramClass.ChecksumSupplier;
 import com.android.tools.r8.graph.DexProto;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.DexTypeList;
@@ -184,6 +185,8 @@
     DexType serviceLoaderType =
         appView.dexItemFactory().createType("L" + SERVICE_LOADER_CLASS_NAME + ";");
     if (synthesizedClass == null) {
+      assert !appView.options().encodeChecksums;
+      ChecksumSupplier checksumSupplier = DexProgramClass::invalidChecksumRequest;
       synthesizedClass =
           new DexProgramClass(
               serviceLoaderType,
@@ -203,7 +206,8 @@
               DexEncodedField.EMPTY_ARRAY, // Instance fields.
               DexEncodedMethod.EMPTY_ARRAY,
               DexEncodedMethod.EMPTY_ARRAY, // Virtual methods.
-              appView.dexItemFactory().getSkipNameValidationForTesting());
+              appView.dexItemFactory().getSkipNameValidationForTesting(),
+              checksumSupplier);
       appView.appInfo().addSynthesizedClass(synthesizedClass);
     }
     DexProto proto = appView.dexItemFactory().createProto(appView.dexItemFactory().iteratorType);
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/CallSiteOptimizationInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/CallSiteOptimizationInfo.java
index 8ad2ea0..d82f6c5 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/CallSiteOptimizationInfo.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/CallSiteOptimizationInfo.java
@@ -37,7 +37,9 @@
   }
 
   // The index exactly matches with in values of invocation, i.e., even including receiver.
-  public abstract TypeLatticeElement getDynamicType(int argIndex);
+  public abstract TypeLatticeElement getDynamicUpperBoundType(int argIndex);
+
+  // TODO(b/139246447): dynamic lower bound type?
 
   // TODO(b/69963623): collect constants and if they're all same, propagate it to the callee.
   //   then, we need to re-run unused argument removal?
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/DefaultCallSiteOptimizationInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/DefaultCallSiteOptimizationInfo.java
index 0e48208..e67ec20 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/DefaultCallSiteOptimizationInfo.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/DefaultCallSiteOptimizationInfo.java
@@ -17,7 +17,7 @@
   }
 
   @Override
-  public TypeLatticeElement getDynamicType(int argIndex) {
+  public TypeLatticeElement getDynamicUpperBoundType(int argIndex) {
     return null;
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/MutableCallSiteOptimizationInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/MutableCallSiteOptimizationInfo.java
index 64b6bac..c18624e 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/MutableCallSiteOptimizationInfo.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/MutableCallSiteOptimizationInfo.java
@@ -6,13 +6,13 @@
 import static com.android.tools.r8.ir.analysis.type.Nullability.definitelyNotNull;
 import static com.android.tools.r8.ir.analysis.type.Nullability.maybeNull;
 
+import com.android.tools.r8.graph.AppInfoWithSubtyping;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.ir.analysis.type.Nullability;
 import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
 import com.android.tools.r8.ir.code.Value;
-import com.android.tools.r8.utils.StringUtils;
-import java.util.Arrays;
+import it.unimi.dsi.fastutil.ints.Int2ReferenceArrayMap;
 import java.util.List;
 import java.util.Map;
 import java.util.concurrent.ConcurrentHashMap;
@@ -30,15 +30,11 @@
 
   private static class ArgumentCollection {
 
-    TypeLatticeElement[] dynamicTypes;
+    private final int size;
+    private final Int2ReferenceArrayMap<TypeLatticeElement> dynamicUpperBoundTypes;
 
     private static final ArgumentCollection BOTTOM = new ArgumentCollection() {
       @Override
-      TypeLatticeElement getDynamicType(int index) {
-        return TypeLatticeElement.BOTTOM;
-      }
-
-      @Override
       public int hashCode() {
         return System.identityHashCode(this);
       }
@@ -49,24 +45,21 @@
       }
     };
 
-    private ArgumentCollection() {}
+    // Only used to create a canonical BOTTOM.
+    private ArgumentCollection() {
+      this.size = -1;
+      this.dynamicUpperBoundTypes = null;
+    }
 
     ArgumentCollection(int size) {
-      this.dynamicTypes = new TypeLatticeElement[size];
-      Arrays.fill(this.dynamicTypes, TypeLatticeElement.BOTTOM);
+      this.size = size;
+      this.dynamicUpperBoundTypes = new Int2ReferenceArrayMap<>(size);
     }
 
-    TypeLatticeElement getDynamicType(int index) {
-      assert dynamicTypes != null;
-      assert 0 <= index && index < dynamicTypes.length;
-      return dynamicTypes[index];
-    }
-
-    ArgumentCollection copy() {
-      ArgumentCollection copy = new ArgumentCollection();
-      copy.dynamicTypes = new TypeLatticeElement[this.dynamicTypes.length];
-      System.arraycopy(this.dynamicTypes, 0, copy.dynamicTypes, 0, this.dynamicTypes.length);
-      return copy;
+    TypeLatticeElement getDynamicUpperBoundType(int index) {
+      assert dynamicUpperBoundTypes != null;
+      assert 0 <= index && index < size;
+      return dynamicUpperBoundTypes.getOrDefault(index, null);
     }
 
     ArgumentCollection join(ArgumentCollection other, AppView<?> appView) {
@@ -76,10 +69,21 @@
       if (this == BOTTOM) {
         return other;
       }
-      assert this.dynamicTypes.length == other.dynamicTypes.length;
-      ArgumentCollection result = this.copy();
-      for (int i = 0; i < result.dynamicTypes.length; i++) {
-        result.dynamicTypes[i] = result.dynamicTypes[i].join(other.dynamicTypes[i], appView);
+      assert this.size == other.size;
+      ArgumentCollection result = new ArgumentCollection(this.size);
+      assert result.dynamicUpperBoundTypes != null;
+      for (int i = 0; i < result.size; i++) {
+        TypeLatticeElement thisUpperBoundType = this.getDynamicUpperBoundType(i);
+        if (thisUpperBoundType == null) {
+          // This means the corresponding argument is primitive. The counterpart should be too.
+          assert other.getDynamicUpperBoundType(i) == null;
+          continue;
+        }
+        assert thisUpperBoundType.isReference();
+        TypeLatticeElement otherUpperBoundType = other.getDynamicUpperBoundType(i);
+        assert otherUpperBoundType != null && otherUpperBoundType.isReference();
+        result.dynamicUpperBoundTypes.put(
+            i, thisUpperBoundType.join(otherUpperBoundType, appView));
       }
       return result;
     }
@@ -93,17 +97,20 @@
       if (this == BOTTOM || otherCollection == BOTTOM) {
         return this == BOTTOM && otherCollection == BOTTOM;
       }
-      return Arrays.equals(this.dynamicTypes, otherCollection.dynamicTypes);
+      assert this.dynamicUpperBoundTypes != null;
+      return this.dynamicUpperBoundTypes.equals(otherCollection.dynamicUpperBoundTypes);
     }
 
     @Override
     public int hashCode() {
-      return Arrays.hashCode(dynamicTypes);
+      assert this.dynamicUpperBoundTypes != null;
+      return System.identityHashCode(dynamicUpperBoundTypes);
     }
 
     @Override
     public String toString() {
-      return "(" + StringUtils.join(Arrays.asList(dynamicTypes), ", ") + ")";
+      assert this.dynamicUpperBoundTypes != null;
+      return dynamicUpperBoundTypes.toString();
     }
   }
 
@@ -159,19 +166,21 @@
       if (!staticTypes[i].isReference()) {
         continue;
       }
-      TypeLatticeElement dynamicType = getDynamicType(i);
-      if (dynamicType == null) {
+      TypeLatticeElement dynamicUpperBoundType = getDynamicUpperBoundType(i);
+      if (dynamicUpperBoundType == null) {
         continue;
       }
       // To avoid the full join of type lattices below, separately check if the nullability of
       // arguments is improved, and if so, we can eagerly conclude that we've collected useful
       // call site information for this method.
-      Nullability nullability = dynamicType.nullability();
+      Nullability nullability = dynamicUpperBoundType.nullability();
       if (nullability.isDefinitelyNull()) {
         return true;
       }
+      // TODO(b/139246447): Similar to nullability, if dynamic lower bound type is available,
+      //   we stop here and regard that call sites of this method have useful info.
       // In general, though, we're looking for (strictly) better dynamic types for arguments.
-      if (dynamicType.strictlyLessThan(staticTypes[i], appView)) {
+      if (dynamicUpperBoundType.strictlyLessThan(staticTypes[i], appView)) {
         return true;
       }
     }
@@ -179,12 +188,12 @@
   }
 
   @Override
-  public TypeLatticeElement getDynamicType(int argIndex) {
+  public TypeLatticeElement getDynamicUpperBoundType(int argIndex) {
     assert 0 <= argIndex && argIndex < size;
     if (cachedRepresentative == null) {
       return null;
     }
-    return cachedRepresentative.getDynamicType(argIndex);
+    return cachedRepresentative.getDynamicUpperBoundType(argIndex);
   }
 
   public static boolean hasArgumentsToRecord(List<Value> inValues) {
@@ -198,12 +207,20 @@
   }
 
   public void recordArguments(
-      AppView<?> appView, DexEncodedMethod callingContext, List<Value> inValues) {
+      AppView<? extends AppInfoWithSubtyping> appView,
+      DexEncodedMethod callingContext,
+      List<Value> inValues) {
     assert cachedRepresentative == null;
     assert size == inValues.size();
     ArgumentCollection newCallSiteInfo = new ArgumentCollection(size);
     for (int i = 0; i < size; i++) {
-      newCallSiteInfo.dynamicTypes[i] = inValues.get(i).getTypeLattice();
+      Value arg = inValues.get(i);
+      // TODO(b/69963623): may need different place to store constants.
+      if (arg.getTypeLattice().isPrimitive()) {
+        continue;
+      }
+      assert arg.getTypeLattice().isReference();
+      newCallSiteInfo.dynamicUpperBoundTypes.put(i, arg.getDynamicUpperBoundType(appView));
     }
     assert callingContext != null;
     ArgumentCollection accumulatedArgumentCollection =
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/lambda/LambdaGroup.java b/src/main/java/com/android/tools/r8/ir/optimize/lambda/LambdaGroup.java
index 4ce3033..5c3e91c 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/lambda/LambdaGroup.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/lambda/LambdaGroup.java
@@ -15,6 +15,7 @@
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.ir.optimize.lambda.CodeProcessor.Strategy;
 import com.android.tools.r8.kotlin.Kotlin;
+import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.ThrowingConsumer;
 import com.google.common.collect.Lists;
 import com.google.common.io.BaseEncoding;
@@ -166,13 +167,19 @@
 
   protected abstract String getGroupSuffix();
 
-  final DexProgramClass synthesizeClass(DexItemFactory factory) {
+  final DexProgramClass synthesizeClass(InternalOptions options) {
     assert classType == null;
     assert verifyLambdaIds(true);
     List<LambdaInfo> lambdas = Lists.newArrayList(this.lambdas.values());
-    classType = factory.createType(
-        "L" + getTypePackage() + "-$$LambdaGroup$" + getGroupSuffix() + createHash(lambdas) + ";");
-    return getBuilder(factory).synthesizeClass();
+    classType =
+        options.itemFactory.createType(
+            "L"
+                + getTypePackage()
+                + "-$$LambdaGroup$"
+                + getGroupSuffix()
+                + createHash(lambdas)
+                + ";");
+    return getBuilder(options.itemFactory).synthesizeClass(options);
   }
 
   protected abstract LambdaGroupClassBuilder getBuilder(DexItemFactory factory);
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/lambda/LambdaGroupClassBuilder.java b/src/main/java/com/android/tools/r8/ir/optimize/lambda/LambdaGroupClassBuilder.java
index 4f5f110..e569714 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/lambda/LambdaGroupClassBuilder.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/lambda/LambdaGroupClassBuilder.java
@@ -15,6 +15,7 @@
 import com.android.tools.r8.graph.EnclosingMethodAttribute;
 import com.android.tools.r8.graph.InnerClassAttribute;
 import com.android.tools.r8.origin.SynthesizedOrigin;
+import com.android.tools.r8.utils.InternalOptions;
 import java.util.Collections;
 import java.util.List;
 
@@ -31,28 +32,31 @@
     this.origin = origin;
   }
 
-  public final DexProgramClass synthesizeClass() {
+  public final DexProgramClass synthesizeClass(InternalOptions options) {
     DexType groupClassType = group.getGroupClassType();
     DexType superClassType = getSuperClassType();
-
-    return new DexProgramClass(
-        groupClassType,
-        null,
-        new SynthesizedOrigin(origin, getClass()),
-        buildAccessFlags(),
-        superClassType,
-        buildInterfaces(),
-        factory.createString(origin),
-        null,
-        Collections.emptyList(),
-        buildEnclosingMethodAttribute(),
-        buildInnerClasses(),
-        buildAnnotations(),
-        buildStaticFields(),
-        buildInstanceFields(),
-        buildDirectMethods(),
-        buildVirtualMethods(),
-        factory.getSkipNameValidationForTesting());
+    DexProgramClass programClass =
+        new DexProgramClass(
+            groupClassType,
+            null,
+            new SynthesizedOrigin(origin, getClass()),
+            buildAccessFlags(),
+            superClassType,
+            buildInterfaces(),
+            factory.createString(origin),
+            null,
+            Collections.emptyList(),
+            buildEnclosingMethodAttribute(),
+            buildInnerClasses(),
+            buildAnnotations(),
+            buildStaticFields(),
+            buildInstanceFields(),
+            buildDirectMethods(),
+            buildVirtualMethods(),
+            factory.getSkipNameValidationForTesting(),
+            // The name of the class is based on the hash of the content.
+            DexProgramClass::checksumFromType);
+    return programClass;
   }
 
   protected abstract DexType getSuperClassType();
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 b226521..c759ac6 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
@@ -393,7 +393,7 @@
     for (LambdaGroup group : groups.values()) {
       assert !group.isTrivial() : "No trivial group is expected here.";
       group.compact();
-      DexProgramClass lambdaGroupClass = group.synthesizeClass(factory);
+      DexProgramClass lambdaGroupClass = group.synthesizeClass(appView.options());
       result.put(group, lambdaGroupClass);
 
       // We have to register this new class as a subtype of object.
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 1e7d21a..8682fc1 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
@@ -21,6 +21,7 @@
 import com.android.tools.r8.ir.code.Instruction;
 import com.android.tools.r8.ir.code.InvokeDirect;
 import com.android.tools.r8.ir.code.InvokeMethodWithReceiver;
+import com.android.tools.r8.ir.code.InvokeStatic;
 import com.android.tools.r8.ir.code.NewInstance;
 import com.android.tools.r8.ir.code.StaticGet;
 import com.android.tools.r8.ir.code.StaticPut;
@@ -31,6 +32,7 @@
 import com.android.tools.r8.utils.ListUtils;
 import com.google.common.collect.Lists;
 import com.google.common.collect.Sets;
+import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.HashMap;
 import java.util.List;
@@ -43,21 +45,13 @@
 import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.concurrent.atomic.AtomicInteger;
 import java.util.concurrent.atomic.AtomicReference;
-import java.util.function.BiConsumer;
 import java.util.function.Predicate;
 
 public final class ClassStaticizer {
 
-  final AppView<AppInfoWithLiveness> appView;
-  final DexItemFactory factory;
-  final IRConverter converter;
-
-  private enum Phase {
-    None, Examine, Fixup
-  }
-
-  private Phase phase = Phase.None;
-  private BiConsumer<DexEncodedMethod, IRCode> fixupStrategy = null;
+  private final AppView<AppInfoWithLiveness> appView;
+  private final DexItemFactory factory;
+  private final IRConverter converter;
 
   // Represents a staticizing candidate with all information
   // needed for staticizing.
@@ -71,6 +65,7 @@
     final AtomicInteger instancesCreated = new AtomicInteger();
     final Set<DexEncodedMethod> referencedFrom = Sets.newConcurrentHashSet();
     final AtomicReference<DexEncodedMethod> constructor = new AtomicReference<>();
+    final AtomicReference<DexEncodedMethod> getter = new AtomicReference<>();
 
     CandidateInfo(DexProgramClass candidate, DexEncodedField singletonField) {
       assert candidate != null;
@@ -139,11 +134,9 @@
                 notEligible.add(field.field.type);
               }
 
-              // Let's also assume no methods should take or return a
-              // value of this type.
+              // Don't allow methods that take a value of this type.
               for (DexEncodedMethod method : cls.methods()) {
                 DexProto proto = method.method.proto;
-                notEligible.add(proto.returnType);
                 notEligible.addAll(Arrays.asList(proto.parameters.values));
               }
 
@@ -179,9 +172,6 @@
         }
       }
     });
-
-    // Next phase -- examine code for candidate usages
-    phase = Phase.Examine;
   }
 
   private boolean isPinned(DexClass clazz, DexEncodedField singletonField) {
@@ -220,28 +210,50 @@
   //
   // NOTE: can be called concurrently.
   public final void examineMethodCode(DexEncodedMethod method, IRCode code) {
-    if (phase != Phase.Examine) {
-      return;
-    }
-
     Set<Instruction> alreadyProcessed = Sets.newIdentityHashSet();
 
     CandidateInfo receiverClassCandidateInfo = candidates.get(method.method.holder);
     Value receiverValue = code.getThis(); // NOTE: is null for static methods.
-    if (receiverClassCandidateInfo != null && receiverValue != null) {
-      // We are inside an instance method of candidate class (not an instance initializer
-      // which we will check later), check if all the references to 'this' are valid
-      // (the call will invalidate the candidate if some of them are not valid).
-      analyzeAllValueUsers(
-          receiverClassCandidateInfo, receiverValue, factory.isConstructor(method.method));
+    if (receiverClassCandidateInfo != null) {
+      if (receiverValue != null) {
+        // We are inside an instance method of candidate class (not an instance initializer
+        // which we will check later), check if all the references to 'this' are valid
+        // (the call will invalidate the candidate if some of them are not valid).
+        analyzeAllValueUsers(
+            receiverClassCandidateInfo, receiverValue, factory.isConstructor(method.method));
 
-      // If the candidate is still valid, ignore all instructions
-      // we treat as valid usages on receiver.
-      if (candidates.get(method.method.holder) != null) {
-        alreadyProcessed.addAll(receiverValue.uniqueUsers());
+        // If the candidate is still valid, ignore all instructions
+        // we treat as valid usages on receiver.
+        if (candidates.get(method.method.holder) != null) {
+          alreadyProcessed.addAll(receiverValue.uniqueUsers());
+        }
+      } else {
+        // We are inside a static method of candidate class.
+        // Check if this is a valid getter of the singleton field.
+        if (method.method.proto.returnType == method.method.holder) {
+          List<Instruction> examined = isValidGetter(receiverClassCandidateInfo, code);
+          if (examined != null) {
+            DexEncodedMethod getter = receiverClassCandidateInfo.getter.get();
+            if (getter == null) {
+              receiverClassCandidateInfo.getter.set(method);
+              // Except for static-get and return, iterate other remaining instructions if any.
+              alreadyProcessed.addAll(examined);
+            } else {
+              assert getter != method;
+              // Not sure how to deal with many getters.
+              receiverClassCandidateInfo.invalidate();
+            }
+          } else {
+            // Invalidate the candidate if it has a static method whose return type is a candidate
+            // type but doesn't return the singleton field (in a trivial way).
+            receiverClassCandidateInfo.invalidate();
+          }
+        }
       }
     }
 
+    // TODO(b/143375203): if fully implemented, the following iterator could be:
+    //   InstructionListIterator iterator = code.instructionListIterator();
     ListIterator<Instruction> iterator =
         Lists.newArrayList(code.instructionIterator()).listIterator();
     while (iterator.hasNext()) {
@@ -288,7 +300,21 @@
         CandidateInfo info = processStaticFieldRead(instruction.asStaticGet());
         if (info != null) {
           info.referencedFrom.add(method);
-          // If the candidate still valid, ignore all usages in further analysis.
+          // If the candidate is still valid, ignore all usages in further analysis.
+          Value value = instruction.outValue();
+          if (value != null) {
+            alreadyProcessed.addAll(value.aliasedUsers());
+          }
+        }
+        continue;
+      }
+
+      if (instruction.isInvokeStatic()) {
+        // Check if it is a static singleton getter.
+        CandidateInfo info = processInvokeStatic(instruction.asInvokeStatic());
+        if (info != null) {
+          info.referencedFrom.add(method);
+          // If the candidate is still valid, ignore all usages in further analysis.
           Value value = instruction.outValue();
           if (value != null) {
             alreadyProcessed.addAll(value.aliasedUsers());
@@ -465,6 +491,41 @@
     return fieldAccessed == info.singletonField;
   }
 
+  // Only allow a very trivial pattern: load the singleton field and return it, which looks like:
+  //
+  //   v <- static-get singleton-field
+  //   <assume instructions on v> // (optional)
+  //   return v // or aliased value
+  //
+  // Returns a list of instructions that are examined (as long as the method is a trivial getter).
+  private List<Instruction> isValidGetter(CandidateInfo info, IRCode code) {
+    List<Instruction> instructions = new ArrayList<>();
+    StaticGet staticGet = null;
+    for (Instruction instr : code.instructions()) {
+      if (instr.isStaticGet()) {
+        staticGet = instr.asStaticGet();
+        DexEncodedField fieldAccessed =
+            appView.appInfo().lookupStaticTarget(staticGet.getField().holder, staticGet.getField());
+        if (fieldAccessed != info.singletonField) {
+          return null;
+        }
+        instructions.add(instr);
+        continue;
+      }
+      if (instr.isAssume() || instr.isReturn()) {
+        Value v = instr.inValues().get(0).getAliasedValue();
+        if (v.isPhi() || v.definition != staticGet) {
+          return null;
+        }
+        instructions.add(instr);
+        continue;
+      }
+      // All other instructions are not allowed.
+      return null;
+    }
+    return instructions;
+  }
+
   // Static field get: can be a valid singleton field for a
   // candidate in which case we should check if all the usages of the
   // value read are eligible.
@@ -486,6 +547,23 @@
     return candidateInfo;
   }
 
+  // Static getter: if this invokes a registered getter, treat it as static field get.
+  // That is, we should check if all the usages of the out value are eligible.
+  private CandidateInfo processInvokeStatic(InvokeStatic invoke) {
+    DexType candidateType = invoke.getInvokedMethod().proto.returnType;
+    CandidateInfo candidateInfo = candidates.get(candidateType);
+    if (candidateInfo == null) {
+      return null;
+    }
+
+    if (invoke.hasOutValue()
+        && candidateInfo.getter.get() != null
+        && candidateInfo.getter.get().method == invoke.getInvokedMethod()) {
+      candidateInfo = analyzeAllValueUsers(candidateInfo, invoke.outValue(), false);
+    }
+    return candidateInfo;
+  }
+
   private CandidateInfo analyzeAllValueUsers(
       CandidateInfo candidateInfo, Value value, boolean ignoreSuperClassInitInvoke) {
     assert value != null && value == value.getAliasedValue();
@@ -556,29 +634,7 @@
   //
   public final void staticizeCandidates(
       OptimizationFeedback feedback, ExecutorService executorService) throws ExecutionException {
-    phase = Phase.None; // We are done with processing/examining methods.
-    new StaticizingProcessor(appView, this, executorService).run(feedback);
-  }
-
-  public final void fixupMethodCode(DexEncodedMethod method, IRCode code) {
-    if (phase == Phase.Fixup) {
-      assert fixupStrategy != null;
-      fixupStrategy.accept(method, code);
-    }
-  }
-
-  void setFixupStrategy(BiConsumer<DexEncodedMethod, IRCode> strategy) {
-    assert phase == Phase.None;
-    assert strategy != null;
-    phase = Phase.Fixup;
-    fixupStrategy = strategy;
-  }
-
-  void cleanFixupStrategy() {
-    assert phase == Phase.Fixup;
-    assert fixupStrategy != null;
-    phase = Phase.None;
-    fixupStrategy = null;
+    new StaticizingProcessor(appView, this, converter).run(feedback, executorService);
   }
 
   private class CallSiteReferencesInvalidator extends UseRegistry {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/staticizer/StaticizingProcessor.java b/src/main/java/com/android/tools/r8/ir/optimize/staticizer/StaticizingProcessor.java
index 3956609..fcb2c2f 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/staticizer/StaticizingProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/staticizer/StaticizingProcessor.java
@@ -4,8 +4,6 @@
 
 package com.android.tools.r8.ir.optimize.staticizer;
 
-import static com.android.tools.r8.ir.analysis.type.Nullability.maybeNull;
-
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DebugLocalInfo;
 import com.android.tools.r8.graph.DexClass;
@@ -27,10 +25,11 @@
 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.conversion.CallSiteInformation;
-import com.android.tools.r8.ir.optimize.Outliner;
+import com.android.tools.r8.ir.conversion.IRConverter;
+import com.android.tools.r8.ir.optimize.CodeRewriter;
 import com.android.tools.r8.ir.optimize.info.OptimizationFeedback;
 import com.android.tools.r8.ir.optimize.staticizer.ClassStaticizer.CandidateInfo;
+import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.utils.SetUtils;
 import com.android.tools.r8.utils.ThreadUtils;
@@ -39,6 +38,7 @@
 import com.google.common.collect.Sets;
 import com.google.common.collect.Streams;
 import java.util.ArrayList;
+import java.util.Collection;
 import java.util.IdentityHashMap;
 import java.util.Iterator;
 import java.util.List;
@@ -48,14 +48,18 @@
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Future;
-import java.util.function.BiConsumer;
+import java.util.function.Consumer;
 import java.util.stream.Collectors;
 
+// TODO(b/140766440): Use PostProcessor, instead of having its own post processing.
 final class StaticizingProcessor {
 
   private final AppView<AppInfoWithLiveness> appView;
   private final ClassStaticizer classStaticizer;
-  private final ExecutorService executorService;
+  private final IRConverter converter;
+
+  private final Map<DexEncodedMethod, Collection<Consumer<IRCode>>> processingQueue =
+      new IdentityHashMap<>();
 
   private final Set<DexEncodedMethod> referencingExtraMethods = Sets.newIdentityHashSet();
   private final Map<DexEncodedMethod, CandidateInfo> hostClassInits = new IdentityHashMap<>();
@@ -66,26 +70,32 @@
   StaticizingProcessor(
       AppView<AppInfoWithLiveness> appView,
       ClassStaticizer classStaticizer,
-      ExecutorService executorService) {
+      IRConverter converter) {
     this.appView = appView;
     this.classStaticizer = classStaticizer;
-    this.executorService = executorService;
+    this.converter = converter;
   }
 
-  final void run(OptimizationFeedback feedback) throws ExecutionException {
+  final void run(OptimizationFeedback optimizationFeedback, ExecutorService executorService)
+      throws ExecutionException {
     // Filter out candidates based on the information we collected while examining methods.
     finalEligibilityCheck();
 
     // Prepare interim data.
     prepareCandidates();
 
-    // Process all host class initializers (only remove instantiations).
-    processMethodsConcurrently(
-        hostClassInits.keySet(), this::removeCandidateInstantiation, feedback);
+    // Enqueue all host class initializers (only remove instantiations).
+    enqueueMethodsWithCodeOptimization(
+        hostClassInits.keySet(), this::removeCandidateInstantiation);
 
-    // Process instance methods to be staticized (only remove references to 'this').
-    processMethodsConcurrently(methodsToBeStaticized, this::removeReferencesToThis, feedback);
+    // Enqueue instance methods to be staticized (only remove references to 'this').
+    enqueueMethodsWithCodeOptimization(
+        methodsToBeStaticized, this::removeReferencesToThis);
 
+    // Process queued methods with associated optimizations
+    processMethodsConcurrently(optimizationFeedback, executorService);
+
+    // TODO(b/140767158): Merge the remaining part below.
     // Convert instance methods into static methods with an extra parameter.
     Set<DexEncodedMethod> methods = staticizeMethodSymbols();
 
@@ -94,7 +104,10 @@
     // a result of staticizing.)
     methods.addAll(referencingExtraMethods);
     methods.addAll(hostClassInits.keySet());
-    processMethodsConcurrently(methods, this::rewriteReferences, feedback);
+    enqueueMethodsWithCodeOptimization(methods, this::rewriteReferences);
+
+    // Process queued methods with associated optimizations
+    processMethodsConcurrently(optimizationFeedback, executorService);
   }
 
   private void finalEligibilityCheck() {
@@ -237,50 +250,65 @@
     referencingExtraMethods.removeAll(removedInstanceMethods);
   }
 
+  private void enqueueMethodsWithCodeOptimization(
+      Iterable<DexEncodedMethod> methods, Consumer<IRCode> optimization) {
+    for (DexEncodedMethod method : methods) {
+      processingQueue
+          .computeIfAbsent(
+              method,
+              // Optimization order might matter, hence a collection that preserves orderings.
+              k -> new ArrayList<>())
+          .add(optimization);
+    }
+  }
+
   /**
    * Processes the given methods concurrently using the given strategy.
    *
-   * <p>Note that, when the strategy {@link #rewriteReferences(DexEncodedMethod, IRCode)} is being
-   * applied, it is important that we never inline a method from `methods` which has still not been
-   * reprocessed. This could lead to broken code, because the strategy that rewrites the broken
-   * references is applied *before* inlining (because the broken references in the inlinee are never
-   * rewritten). We currently avoid this situation by processing all the methods concurrently
+   * <p>Note that, when the strategy {@link #rewriteReferences(IRCode)} is being applied, it is
+   * important that we never inline a method from `methods` which has still not been reprocessed.
+   * This could lead to broken code, because the strategy that rewrites the broken references is
+   * applied *before* inlining (because the broken references in the inlinee are never rewritten).
+   * We currently avoid this situation by processing all the methods concurrently
    * (inlining of a method that is processed concurrently is not allowed).
    */
   private void processMethodsConcurrently(
-      Set<DexEncodedMethod> methods,
-      BiConsumer<DexEncodedMethod, IRCode> strategy,
-      OptimizationFeedback feedback)
-      throws ExecutionException {
-    classStaticizer.setFixupStrategy(strategy);
-
+      OptimizationFeedback feedback, ExecutorService executorService) throws ExecutionException {
     List<Future<?>> futures = new ArrayList<>();
-    for (DexEncodedMethod method : methods) {
+    for (DexEncodedMethod method : processingQueue.keySet()) {
       futures.add(
           executorService.submit(
               () -> {
-                classStaticizer.converter.processMethod(
-                    method,
-                    feedback,
-                    methods::contains,
-                    CallSiteInformation.empty(),
-                    Outliner::noProcessing);
+                forEachMethod(method, processingQueue.get(method), feedback);
                 return null; // we want a Callable not a Runnable to be able to throw
               }));
     }
     ThreadUtils.awaitFutures(futures);
-
-    classStaticizer.cleanFixupStrategy();
+    // TODO(b/140767158): No need to clear if we can do every thing in one go.
+    processingQueue.clear();
   }
 
-  private void removeCandidateInstantiation(DexEncodedMethod method, IRCode code) {
-    CandidateInfo candidateInfo = hostClassInits.get(method);
+  // TODO(b/140766440): Should be part or variant of PostProcessor.
+  private void forEachMethod(
+      DexEncodedMethod method,
+      Collection<Consumer<IRCode>> codeOptimizations,
+      OptimizationFeedback feedback) {
+    Origin origin = appView.appInfo().originFor(method.method.holder);
+    IRCode code = method.buildIR(appView, origin);
+    codeOptimizations.forEach(codeOptimization -> codeOptimization.accept(code));
+    converter.collectOptimizationInfo(code, feedback);
+    CodeRewriter.removeAssumeInstructions(appView, code);
+    converter.finalizeIR(method, code, feedback);
+  }
+
+  private void removeCandidateInstantiation(IRCode code) {
+    CandidateInfo candidateInfo = hostClassInits.get(code.method);
     assert candidateInfo != null;
 
     // Find and remove instantiation and its users.
     for (Instruction instruction : code.instructions()) {
-      if (instruction.isNewInstance() &&
-          instruction.asNewInstance().clazz == candidateInfo.candidate.type) {
+      if (instruction.isNewInstance()
+          && instruction.asNewInstance().clazz == candidateInfo.candidate.type) {
         // Remove all usages
         // NOTE: requiring (a) the instance initializer to be trivial, (b) not allowing
         //       candidates with instance fields and (c) requiring candidate to directly
@@ -300,11 +328,11 @@
     assert false : "Must always be able to find and remove the instantiation";
   }
 
-  private void removeReferencesToThis(DexEncodedMethod method, IRCode code) {
+  private void removeReferencesToThis(IRCode code) {
     fixupStaticizedThisUsers(code, code.getThis());
   }
 
-  private void rewriteReferences(DexEncodedMethod method, IRCode code) {
+  private void rewriteReferences(IRCode code) {
     // Process all singleton field reads and rewrite their users.
     List<StaticGet> singletonFieldReads =
         Streams.stream(code.instructionIterator())
@@ -513,7 +541,7 @@
               new StaticGet(
                   code.createValue(
                       TypeLatticeElement.fromDexType(
-                          field.type, maybeNull(), classStaticizer.appView),
+                          field.type, outValue.getTypeLattice().nullability(), appView),
                       outValue.getLocalInfo()),
                   field));
         }
@@ -536,12 +564,13 @@
         if (hostType != null) {
           DexMethod newMethod = factory().createMethod(hostType, method.proto, method.name);
           Value outValue = invoke.outValue();
+          DexType returnType = method.proto.returnType;
           Value newOutValue =
-              method.proto.returnType.isVoidType()
+              returnType.isVoidType()
                   ? null
                   : code.createValue(
                       TypeLatticeElement.fromDexType(
-                          method.proto.returnType, maybeNull(), classStaticizer.appView),
+                          returnType, outValue.getTypeLattice().nullability(), appView),
                       outValue == null ? null : outValue.getLocalInfo());
           it.replaceCurrentInstruction(new InvokeStatic(newMethod, newOutValue, invoke.inValues()));
         }
@@ -588,7 +617,7 @@
       // Consider moving static members from candidate into host.
       DexType hostType = candidate.hostType();
       if (candidateClass.type != hostType) {
-        DexClass hostClass = classStaticizer.appView.definitionFor(hostType);
+        DexClass hostClass = appView.definitionFor(hostType);
         assert hostClass != null;
         if (!classMembersConflict(candidateClass, hostClass)) {
           // Move all members of the candidate class into host class.
@@ -681,6 +710,6 @@
   }
 
   private DexItemFactory factory() {
-    return classStaticizer.factory;
+    return appView.dexItemFactory();
   }
 }
diff --git a/src/main/java/com/android/tools/r8/retrace/RetraceCore.java b/src/main/java/com/android/tools/r8/retrace/RetraceCore.java
index f783cbc..8db2ace 100644
--- a/src/main/java/com/android/tools/r8/retrace/RetraceCore.java
+++ b/src/main/java/com/android/tools/r8/retrace/RetraceCore.java
@@ -408,7 +408,8 @@
     }
 
     private String retracedFileName(String retracedClazz) {
-      if (!UNKNOWN_SOURCEFILE_NAMES.contains(fileName)) {
+      boolean fileNameProbablyChanged = retracedClazz != null && !retracedClazz.startsWith(clazz);
+      if (!UNKNOWN_SOURCEFILE_NAMES.contains(fileName) && !fileNameProbablyChanged) {
         return fileName;
       }
       if (retracedClazz == null) {
@@ -426,13 +427,9 @@
 
     private String getClassSimpleName(String clazz) {
       int lastIndexOfPeriod = clazz.lastIndexOf('.');
-      if (lastIndexOfPeriod > -1) {
-        // Check if we can find a subclass separator.
-        int endIndex = firstCharFromIndex(clazz, lastIndexOfPeriod, '$');
-        return clazz.substring(lastIndexOfPeriod + 1, endIndex);
-      } else {
-        return clazz;
-      }
+      // Check if we can find a subclass separator.
+      int endIndex = firstCharFromIndex(clazz, lastIndexOfPeriod + 1, '$');
+      return clazz.substring(lastIndexOfPeriod + 1, endIndex);
     }
 
     @Override
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 687e7da..aaee831 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -68,7 +68,6 @@
   // Set to true to run compilation in a single thread and without randomly shuffling the input.
   // This makes life easier when running R8 in a debugger.
   public static final boolean DETERMINISTIC_DEBUGGING = false;
-
   public enum LineNumberOptimization {
     OFF,
     ON
@@ -176,6 +175,8 @@
   // To print memory one also have to enable printtimes.
   public boolean printMemory = System.getProperty("com.android.tools.r8.printmemory") != null;
 
+  public String dumpInputToFile = System.getProperty("com.android.tools.r8.dumpinputtofile");
+
   // Flag to toggle if DEX code objects should pass-through without IR processing.
   public boolean passthroughDexCode = false;
   // TODO(b/134705306): Currently allow merging dex files resulting from Java 8 library
diff --git a/src/test/java/com/android/tools/r8/JctfTestSpecifications.java b/src/test/java/com/android/tools/r8/JctfTestSpecifications.java
index 079c89c..8c659b1 100644
--- a/src/test/java/com/android/tools/r8/JctfTestSpecifications.java
+++ b/src/test/java/com/android/tools/r8/JctfTestSpecifications.java
@@ -1772,6 +1772,7 @@
               "util.concurrent.SynchronousQueue.ConstructorZ.SynchronousQueue_Constructor_A01",
               anyDexVm())
           .put("lang.Thread.getState.Thread_getState_A01", anyDexVm())
+          .put("lang.Thread.join.Thread_join_A01", anyDexVm())
           .put(
               "util.concurrent.ScheduledThreadPoolExecutor.getTaskCount.ScheduledThreadPoolExecutor_getTaskCount_A01",
               any())
diff --git a/src/test/java/com/android/tools/r8/L8CommandTest.java b/src/test/java/com/android/tools/r8/L8CommandTest.java
index 7a72d1b..0a55b8a 100644
--- a/src/test/java/com/android/tools/r8/L8CommandTest.java
+++ b/src/test/java/com/android/tools/r8/L8CommandTest.java
@@ -114,6 +114,7 @@
   }
 
   @Test
+  @Ignore("b/143431384: Re-enable shrinking")
   public void addProguardConfigurationString() throws Throwable {
     String keepRule = "-keep class java.time.*";
     List<String> keepRules = new ArrayList<>();
@@ -129,6 +130,7 @@
   }
 
   @Test
+  @Ignore("b/143431384: Re-enable shrinking")
   public void addProguardConfigurationFile() throws Throwable {
     String keepRule = "-keep class java.time.*";
     Path keepRuleFile = temp.newFile("keepRuleFile.txt").toPath();
diff --git a/src/test/java/com/android/tools/r8/TestDiagnosticMessagesImpl.java b/src/test/java/com/android/tools/r8/TestDiagnosticMessagesImpl.java
index 3082da8..8282c77 100644
--- a/src/test/java/com/android/tools/r8/TestDiagnosticMessagesImpl.java
+++ b/src/test/java/com/android/tools/r8/TestDiagnosticMessagesImpl.java
@@ -147,7 +147,6 @@
 
   private TestDiagnosticMessages assertNoMessageThatMatches(
       List<Diagnostic> diagnostics, String tag, Matcher<String> matcher) {
-    assertNotEquals(0, diagnostics.size());
     for (int i = 0; i < diagnostics.size(); i++) {
       String message = diagnostics.get(i).getDiagnosticMessage();
       if (matcher.matches(message)) {
diff --git a/src/test/java/com/android/tools/r8/desugar/corelib/HelloWorldCompiledOnArtTest.java b/src/test/java/com/android/tools/r8/desugar/corelib/HelloWorldCompiledOnArtTest.java
index 044f075..aa511f6 100644
--- a/src/test/java/com/android/tools/r8/desugar/corelib/HelloWorldCompiledOnArtTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/corelib/HelloWorldCompiledOnArtTest.java
@@ -6,18 +6,22 @@
 
 import static com.android.tools.r8.utils.FileUtils.JAR_EXTENSION;
 import static junit.framework.TestCase.assertEquals;
+import static junit.framework.TestCase.assertTrue;
+import static org.hamcrest.core.StringContains.containsString;
 import static org.junit.Assume.assumeTrue;
 
 import com.android.tools.r8.D8;
 import com.android.tools.r8.D8TestBuilder;
 import com.android.tools.r8.D8TestCompileResult;
 import com.android.tools.r8.R8;
+import com.android.tools.r8.TestDiagnosticMessages;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.TestRuntime.CfVm;
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.ToolHelper.DexVm.Version;
 import com.android.tools.r8.ToolHelper.ProcessResult;
+import com.android.tools.r8.desugar.corelib.conversionTests.APIConversionTestBase;
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.StringUtils;
 import java.io.IOException;
@@ -31,7 +35,7 @@
 import org.junit.runners.Parameterized.Parameters;
 
 @RunWith(Parameterized.class)
-public class HelloWorldCompiledOnArtTest extends CoreLibDesugarTestBase {
+public class HelloWorldCompiledOnArtTest extends APIConversionTestBase {
 
   // TODO(b/142621961): Create an abstraction to easily run tests on External DexR8.
   // Manage pathMock in the abstraction.
@@ -124,11 +128,20 @@
     if (parameters.getApiLevel().getLevel() < AndroidApiLevel.O.getLevel()) {
       d8TestBuilder.addProgramFiles(getPathBackport());
     }
-    return d8TestBuilder
-        .setMinApi(parameters.getApiLevel())
-        .enableCoreLibraryDesugaring(parameters.getApiLevel())
-        .compile()
-        .addDesugaredCoreLibraryRunClassPath(this::buildDesugaredLibrary, parameters.getApiLevel())
+    D8TestCompileResult compile =
+        d8TestBuilder
+            .setMinApi(parameters.getApiLevel())
+            .enableCoreLibraryDesugaring(parameters.getApiLevel())
+            .addOptionsModification(opt -> opt.testing.trackDesugaredAPIConversions = true)
+            .compile();
+    TestDiagnosticMessages diagnosticMessages = compile.getDiagnosticMessages();
+    assertTrue(
+        diagnosticMessages.getWarnings().isEmpty()
+            || diagnosticMessages.getWarnings().stream()
+                .noneMatch(x -> x.getDiagnosticMessage().contains("andThen")));
+    return compile
+        .addDesugaredCoreLibraryRunClassPath(
+            this::buildDesugaredLibraryWithConversionExtension, parameters.getApiLevel())
         .withArt6Plus64BitsLib()
         .withArtFrameworks();
   }
diff --git a/src/test/java/com/android/tools/r8/desugar/corelib/JavaUtilFunctionTest.java b/src/test/java/com/android/tools/r8/desugar/corelib/JavaUtilFunctionTest.java
index 2ddacd7..53c9a9d 100644
--- a/src/test/java/com/android/tools/r8/desugar/corelib/JavaUtilFunctionTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/corelib/JavaUtilFunctionTest.java
@@ -103,40 +103,6 @@
         .assertSuccessWithOutput(expectedOutput);
   }
 
-  @Test
-  public void testWrapperWithChecksum() throws Exception {
-    KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters);
-    testForD8()
-        .addInnerClasses(JavaUtilFunctionTest.class)
-        .setMinApi(parameters.getApiLevel())
-        .enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer)
-        .setIncludeClassesChecksum(true) // Compilation fails if some classes are missing checksum.
-        .compile()
-        .inspect(
-            inspector -> {
-              assertEquals(
-                  parameters.getApiLevel().getLevel() >= AndroidApiLevel.N.getLevel() ? 0 : 1,
-                  inspector.allClasses().stream()
-                      .filter(
-                          clazz ->
-                              clazz
-                                  .getFinalName()
-                                  .contains(DesugaredLibraryWrapperSynthesizer.TYPE_WRAPPER_SUFFIX))
-                      .count());
-              assertEquals(
-                  parameters.getApiLevel().getLevel() >= AndroidApiLevel.N.getLevel() ? 0 : 1,
-                  inspector.allClasses().stream()
-                      .filter(
-                          clazz ->
-                              clazz
-                                  .getFinalName()
-                                  .contains(
-                                      DesugaredLibraryWrapperSynthesizer
-                                          .VIVIFIED_TYPE_WRAPPER_SUFFIX))
-                      .count());
-            });
-  }
-
   static class TestClass {
 
     @NeverInline
diff --git a/src/test/java/com/android/tools/r8/desugar/corelib/conversionTests/FunctionConversionTest.java b/src/test/java/com/android/tools/r8/desugar/corelib/conversionTests/FunctionConversionTest.java
index 702eb05..cc44509 100644
--- a/src/test/java/com/android/tools/r8/desugar/corelib/conversionTests/FunctionConversionTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/corelib/conversionTests/FunctionConversionTest.java
@@ -8,6 +8,7 @@
 
 import com.android.tools.r8.TestRuntime.DexRuntime;
 import com.android.tools.r8.ToolHelper.DexVm;
+import com.android.tools.r8.ir.desugar.DesugaredLibraryWrapperSynthesizer;
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.StringUtils;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
@@ -24,6 +25,7 @@
 import java.util.function.LongConsumer;
 import java.util.function.LongSupplier;
 import java.util.stream.Collectors;
+import org.junit.Assert;
 import org.junit.Test;
 
 public class FunctionConversionTest extends APIConversionTestBase {
@@ -67,6 +69,41 @@
         doubleSupplierWrapperClasses.size());
   }
 
+  @Test
+  public void testWrapperWithChecksum() throws Exception {
+    testForD8()
+        .addProgramClasses(
+            Executor.class, Executor.Object1.class, Executor.Object2.class, Executor.Object3.class)
+        .addLibraryClasses(CustomLibClass.class)
+        .setMinApi(AndroidApiLevel.B)
+        .enableCoreLibraryDesugaring(AndroidApiLevel.B)
+        .setIncludeClassesChecksum(true) // Compilation fails if some classes are missing checksum.
+        .compile()
+        .inspect(
+            inspector -> {
+              Assert.assertEquals(
+                  8,
+                  inspector.allClasses().stream()
+                      .filter(
+                          clazz ->
+                              clazz
+                                  .getFinalName()
+                                  .contains(DesugaredLibraryWrapperSynthesizer.TYPE_WRAPPER_SUFFIX))
+                      .count());
+              Assert.assertEquals(
+                  6,
+                  inspector.allClasses().stream()
+                      .filter(
+                          clazz ->
+                              clazz
+                                  .getFinalName()
+                                  .contains(
+                                      DesugaredLibraryWrapperSynthesizer
+                                          .VIVIFIED_TYPE_WRAPPER_SUFFIX))
+                      .count());
+            });
+  }
+
   static class Executor {
 
     public static void main(String[] args) {
@@ -99,6 +136,7 @@
     }
 
     static class Object3 {
+
       private Object2 field;
 
       private Object3(Object2 o) {
diff --git a/src/test/java/com/android/tools/r8/desugar/corelib/corelibjdktests/Jdk11ConcurrentMapTests.java b/src/test/java/com/android/tools/r8/desugar/corelib/corelibjdktests/Jdk11ConcurrentMapTests.java
index f80607a..092b14b 100644
--- a/src/test/java/com/android/tools/r8/desugar/corelib/corelibjdktests/Jdk11ConcurrentMapTests.java
+++ b/src/test/java/com/android/tools/r8/desugar/corelib/corelibjdktests/Jdk11ConcurrentMapTests.java
@@ -7,6 +7,7 @@
 import static com.android.tools.r8.ToolHelper.JDK_TESTS_BUILD_DIR;
 import static com.android.tools.r8.utils.FileUtils.CLASS_EXTENSION;
 import static com.android.tools.r8.utils.FileUtils.JAVA_EXTENSION;
+import static junit.framework.TestCase.assertTrue;
 import static org.hamcrest.CoreMatchers.containsString;
 import static org.hamcrest.CoreMatchers.endsWith;
 import static org.hamcrest.CoreMatchers.not;
@@ -16,9 +17,11 @@
 import com.android.tools.r8.TestRuntime.CfVm;
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.ToolHelper.DexVm.Version;
+import com.android.tools.r8.ir.desugar.DesugaredLibraryWrapperSynthesizer;
 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.CodeInspector;
 import java.nio.file.Path;
 import java.nio.file.Paths;
 import java.util.ArrayList;
@@ -111,6 +114,7 @@
         .enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer)
         .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.P))
         .compile()
+        .inspect(this::assertNoConversions)
         .withArt6Plus64BitsLib()
         .addDesugaredCoreLibraryRunClassPath(
             this::buildDesugaredLibrary,
@@ -122,6 +126,15 @@
             endsWith(StringUtils.lines("ConcurrentModification: SUCCESS")));
   }
 
+  private void assertNoConversions(CodeInspector inspector) {
+    assertTrue(
+        inspector.allClasses().stream()
+            .noneMatch(
+                cl ->
+                    cl.getOriginalName()
+                        .startsWith(DesugaredLibraryWrapperSynthesizer.WRAPPER_PREFIX)));
+  }
+
   private Path[] concurrentHashTestToCompile() {
     // We exclude WhiteBox.class because of Method handles, they are not supported on old devices
     // and the test uses methods not present even on 28.
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 8b40ea9..5b94aac 100644
--- a/src/test/java/com/android/tools/r8/dex/SharedClassWritingTest.java
+++ b/src/test/java/com/android/tools/r8/dex/SharedClassWritingTest.java
@@ -87,49 +87,60 @@
         code);
   }
 
-  private DexProgramClass makeClass(String name, int stringCount, int startOffset,
+  private DexProgramClass makeClass(
+      InternalOptions options,
+      String name,
+      int stringCount,
+      int startOffset,
       Collection<DexProgramClass> synthesizedFrom) {
     String desc = DescriptorUtils.javaTypeToDescriptor(name);
     DexType type = dexItemFactory.createType(desc);
-    return new DexProgramClass(
-        type,
-        null,
-        new SynthesizedOrigin("test", getClass()),
-        ClassAccessFlags.fromSharedAccessFlags(Constants.ACC_PUBLIC),
-        dexItemFactory.objectType,
-        DexTypeList.empty(),
-        null,
-        null,
-        Collections.emptyList(),
-        null,
-        Collections.emptyList(),
-        DexAnnotationSet.empty(),
-        DexEncodedField.EMPTY_ARRAY,
-        DexEncodedField.EMPTY_ARRAY,
-        DexEncodedMethod.EMPTY_ARRAY,
-        new DexEncodedMethod[] {makeMethod(type, stringCount, startOffset)},
-        false,
-        synthesizedFrom);
+    DexProgramClass programClass =
+        new DexProgramClass(
+            type,
+            null,
+            new SynthesizedOrigin("test", getClass()),
+            ClassAccessFlags.fromSharedAccessFlags(Constants.ACC_PUBLIC),
+            dexItemFactory.objectType,
+            DexTypeList.empty(),
+            null,
+            null,
+            Collections.emptyList(),
+            null,
+            Collections.emptyList(),
+            DexAnnotationSet.empty(),
+            DexEncodedField.EMPTY_ARRAY,
+            DexEncodedField.EMPTY_ARRAY,
+            DexEncodedMethod.EMPTY_ARRAY,
+            new DexEncodedMethod[] {makeMethod(type, stringCount, startOffset)},
+            false,
+            DexProgramClass::invalidChecksumRequest,
+            synthesizedFrom);
+    return programClass;
   }
 
   @Test
   public void manyFilesWithSharedSynthesizedClass() throws ExecutionException, IOException {
+    InternalOptions options = new InternalOptions(dexItemFactory, new Reporter());
 
     // Create classes that all reference enough strings to overflow the index, but are all
     // at different offsets in the strings array. This ensures we trigger multiple rounds of
     // rewrites.
     List<DexProgramClass> classes = new ArrayList<>();
     for (int i = 0; i < NUMBER_OF_FILES; i++) {
-      classes.add(makeClass("Class" + i, Constants.MAX_NON_JUMBO_INDEX - 1, i % 100,
-          Collections.emptyList()));
+      classes.add(
+          makeClass(
+              options,
+              "Class" + i,
+              Constants.MAX_NON_JUMBO_INDEX - 1,
+              i % 100,
+              Collections.emptyList()));
     }
 
     // Create a shared class that references strings above the maximum.
-    DexProgramClass sharedSynthesizedClass = makeClass("SharedSynthesized", 100,
-        Constants.MAX_NON_JUMBO_INDEX - 1,
-        classes);
+    DexProgramClass sharedSynthesizedClass =
+        makeClass(options, "SharedSynthesized", 100, Constants.MAX_NON_JUMBO_INDEX - 1, classes);
 
-    InternalOptions options = new InternalOptions(dexItemFactory, new Reporter());
     DexApplication.Builder builder =
         DirectMappedDexApplication.builder(options, new Timing("SharedClassWritingTest"));
     builder.addSynthesizedClass(sharedSynthesizedClass, false);
@@ -144,7 +155,6 @@
             null,
             options,
             null,
-            null,
             GraphLense.getIdentityLense(),
             NamingLens.getIdentityLens(),
             null);
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/NonNullTrackerTest.java b/src/test/java/com/android/tools/r8/ir/optimize/NonNullTrackerTest.java
index 3a4ceda..50742b5 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/NonNullTrackerTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/NonNullTrackerTest.java
@@ -48,7 +48,7 @@
       testAugmentedIRCode.accept(code);
     }
 
-    new CodeRewriter(appView, null).removeAssumeInstructions(code);
+    CodeRewriter.removeAssumeInstructions(appView, code);
     assertTrue(code.isConsistentSSA());
     checkCountOfNonNull(code, 0);
   }
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamictype/InvokeDirectNegativeTest.java b/src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamicupperboundtype/InvokeDirectNegativeTest.java
similarity index 97%
rename from src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamictype/InvokeDirectNegativeTest.java
rename to src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamicupperboundtype/InvokeDirectNegativeTest.java
index d2687fd..fc40fd9 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamictype/InvokeDirectNegativeTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamicupperboundtype/InvokeDirectNegativeTest.java
@@ -1,7 +1,7 @@
 // Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
-package com.android.tools.r8.ir.optimize.callsites.dynamictype;
+package com.android.tools.r8.ir.optimize.callsites.dynamicupperboundtype;
 
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
 import static org.hamcrest.MatcherAssert.assertThat;
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamictype/InvokeDirectPositiveTest.java b/src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamicupperboundtype/InvokeDirectPositiveTest.java
similarity index 97%
rename from src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamictype/InvokeDirectPositiveTest.java
rename to src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamicupperboundtype/InvokeDirectPositiveTest.java
index 0145b82..a1b889c 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamictype/InvokeDirectPositiveTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamicupperboundtype/InvokeDirectPositiveTest.java
@@ -1,7 +1,7 @@
 // Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
-package com.android.tools.r8.ir.optimize.callsites.dynamictype;
+package com.android.tools.r8.ir.optimize.callsites.dynamicupperboundtype;
 
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
 import static org.hamcrest.MatcherAssert.assertThat;
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamictype/InvokeInterfaceNegativeTest.java b/src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamicupperboundtype/InvokeInterfaceNegativeTest.java
similarity index 97%
rename from src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamictype/InvokeInterfaceNegativeTest.java
rename to src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamicupperboundtype/InvokeInterfaceNegativeTest.java
index 3508b62..f309eb8 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamictype/InvokeInterfaceNegativeTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamicupperboundtype/InvokeInterfaceNegativeTest.java
@@ -1,7 +1,7 @@
 // Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
-package com.android.tools.r8.ir.optimize.callsites.dynamictype;
+package com.android.tools.r8.ir.optimize.callsites.dynamicupperboundtype;
 
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
 import static org.hamcrest.MatcherAssert.assertThat;
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamictype/InvokeInterfacePositiveTest.java b/src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamicupperboundtype/InvokeInterfacePositiveTest.java
similarity index 97%
rename from src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamictype/InvokeInterfacePositiveTest.java
rename to src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamicupperboundtype/InvokeInterfacePositiveTest.java
index 8cca30a..7b8818e 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamictype/InvokeInterfacePositiveTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamicupperboundtype/InvokeInterfacePositiveTest.java
@@ -1,7 +1,7 @@
 // Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
-package com.android.tools.r8.ir.optimize.callsites.dynamictype;
+package com.android.tools.r8.ir.optimize.callsites.dynamicupperboundtype;
 
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
 import static org.hamcrest.MatcherAssert.assertThat;
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamictype/InvokeStaticNegativeTest.java b/src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamicupperboundtype/InvokeStaticNegativeTest.java
similarity index 97%
rename from src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamictype/InvokeStaticNegativeTest.java
rename to src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamicupperboundtype/InvokeStaticNegativeTest.java
index fb463a0..b63d571 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamictype/InvokeStaticNegativeTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamicupperboundtype/InvokeStaticNegativeTest.java
@@ -1,7 +1,7 @@
 // Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
-package com.android.tools.r8.ir.optimize.callsites.dynamictype;
+package com.android.tools.r8.ir.optimize.callsites.dynamicupperboundtype;
 
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
 import static org.hamcrest.MatcherAssert.assertThat;
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamictype/InvokeStaticPositiveTest.java b/src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamicupperboundtype/InvokeStaticPositiveTest.java
similarity index 97%
rename from src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamictype/InvokeStaticPositiveTest.java
rename to src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamicupperboundtype/InvokeStaticPositiveTest.java
index 47e8c1c..e8b75c5 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamictype/InvokeStaticPositiveTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamicupperboundtype/InvokeStaticPositiveTest.java
@@ -1,7 +1,7 @@
 // Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
-package com.android.tools.r8.ir.optimize.callsites.dynamictype;
+package com.android.tools.r8.ir.optimize.callsites.dynamicupperboundtype;
 
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
 import static org.hamcrest.MatcherAssert.assertThat;
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamictype/InvokeVirtualNegativeTest.java b/src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamicupperboundtype/InvokeVirtualNegativeTest.java
similarity index 97%
rename from src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamictype/InvokeVirtualNegativeTest.java
rename to src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamicupperboundtype/InvokeVirtualNegativeTest.java
index 9da6912..6902f40 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamictype/InvokeVirtualNegativeTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamicupperboundtype/InvokeVirtualNegativeTest.java
@@ -1,7 +1,7 @@
 // Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
-package com.android.tools.r8.ir.optimize.callsites.dynamictype;
+package com.android.tools.r8.ir.optimize.callsites.dynamicupperboundtype;
 
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
 import static org.hamcrest.MatcherAssert.assertThat;
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamictype/InvokeVirtualPositiveTest.java b/src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamicupperboundtype/InvokeVirtualPositiveTest.java
similarity index 97%
rename from src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamictype/InvokeVirtualPositiveTest.java
rename to src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamicupperboundtype/InvokeVirtualPositiveTest.java
index cc0f93d..5f3dda0 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamictype/InvokeVirtualPositiveTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamicupperboundtype/InvokeVirtualPositiveTest.java
@@ -1,7 +1,7 @@
 // Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
-package com.android.tools.r8.ir.optimize.callsites.dynamictype;
+package com.android.tools.r8.ir.optimize.callsites.dynamicupperboundtype;
 
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
 import static org.hamcrest.MatcherAssert.assertThat;
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/staticizer/ClassStaticizerTest.java b/src/test/java/com/android/tools/r8/ir/optimize/staticizer/ClassStaticizerTest.java
index 99d27fa..d0a4064 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/staticizer/ClassStaticizerTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/staticizer/ClassStaticizerTest.java
@@ -42,9 +42,11 @@
 import com.android.tools.r8.ir.optimize.staticizer.movetohost.MoveToHostTestClass;
 import com.android.tools.r8.ir.optimize.staticizer.trivial.Simple;
 import com.android.tools.r8.ir.optimize.staticizer.trivial.SimpleWithGetter;
+import com.android.tools.r8.ir.optimize.staticizer.trivial.SimpleWithLazyInit;
 import com.android.tools.r8.ir.optimize.staticizer.trivial.SimpleWithParams;
 import com.android.tools.r8.ir.optimize.staticizer.trivial.SimpleWithPhi;
 import com.android.tools.r8.ir.optimize.staticizer.trivial.SimpleWithSideEffects;
+import com.android.tools.r8.ir.optimize.staticizer.trivial.SimpleWithThrowingGetter;
 import com.android.tools.r8.ir.optimize.staticizer.trivial.TrivialTestClass;
 import com.android.tools.r8.naming.MemberNaming.MethodSignature;
 import com.android.tools.r8.utils.InternalOptions;
@@ -79,11 +81,13 @@
         NeverInline.class,
         TrivialTestClass.class,
         Simple.class,
-        SimpleWithSideEffects.class,
-        SimpleWithParams.class,
         SimpleWithGetter.class,
+        SimpleWithLazyInit.class,
+        SimpleWithParams.class,
         SimpleWithPhi.class,
-        SimpleWithPhi.Companion.class
+        SimpleWithPhi.Companion.class,
+        SimpleWithSideEffects.class,
+        SimpleWithThrowingGetter.class
     };
     String javaOutput = runOnJava(main);
     TestRunResult result =
@@ -92,7 +96,7 @@
             .enableInliningAnnotations()
             .addKeepMainRule(main)
             .noMinification()
-            .addKeepRules("-keepattributes InnerClasses,EnclosingMethod")
+            .addKeepAttributes("InnerClasses", "EnclosingMethod")
             .addOptionsModification(this::configure)
             .allowAccessModification()
             .setMinApi(parameters.getApiLevel())
@@ -104,9 +108,9 @@
 
     assertEquals(
         Lists.newArrayList(
-            "STATIC: String trivial.Simple.bar(String)",
-            "STATIC: String trivial.Simple.foo()",
-            "STATIC: String trivial.TrivialTestClass.next()"),
+            "STATIC: String Simple.bar(String)",
+            "STATIC: String Simple.foo()",
+            "STATIC: String TrivialTestClass.next()"),
         references(clazz, "testSimple", "void"));
 
     ClassSubject simple = inspector.clazz(Simple.class);
@@ -115,10 +119,10 @@
 
     assertEquals(
         Lists.newArrayList(
-            "STATIC: String trivial.SimpleWithPhi.bar(String)",
-            "STATIC: String trivial.SimpleWithPhi.foo()",
-            "STATIC: String trivial.SimpleWithPhi.foo()",
-            "STATIC: String trivial.TrivialTestClass.next()"),
+            "STATIC: String SimpleWithPhi.bar(String)",
+            "STATIC: String SimpleWithPhi.foo()",
+            "STATIC: String SimpleWithPhi.foo()",
+            "STATIC: String TrivialTestClass.next()"),
         references(clazz, "testSimpleWithPhi", "void", "int"));
 
     ClassSubject simpleWithPhi = inspector.clazz(SimpleWithPhi.class);
@@ -127,9 +131,9 @@
 
     assertEquals(
         Lists.newArrayList(
-            "STATIC: String trivial.SimpleWithParams.bar(String)",
-            "STATIC: String trivial.SimpleWithParams.foo()",
-            "STATIC: String trivial.TrivialTestClass.next()"),
+            "STATIC: String SimpleWithParams.bar(String)",
+            "STATIC: String SimpleWithParams.foo()",
+            "STATIC: String TrivialTestClass.next()"),
         references(clazz, "testSimpleWithParams", "void"));
 
     ClassSubject simpleWithParams = inspector.clazz(SimpleWithParams.class);
@@ -138,11 +142,11 @@
 
     assertEquals(
         Lists.newArrayList(
-            "STATIC: String trivial.SimpleWithSideEffects.bar(String)",
-            "STATIC: String trivial.SimpleWithSideEffects.foo()",
-            "STATIC: String trivial.TrivialTestClass.next()",
-            "trivial.SimpleWithSideEffects trivial.SimpleWithSideEffects.INSTANCE",
-            "trivial.SimpleWithSideEffects trivial.SimpleWithSideEffects.INSTANCE"),
+            "STATIC: String SimpleWithSideEffects.bar(String)",
+            "STATIC: String SimpleWithSideEffects.foo()",
+            "STATIC: String TrivialTestClass.next()",
+            "SimpleWithSideEffects SimpleWithSideEffects.INSTANCE",
+            "SimpleWithSideEffects SimpleWithSideEffects.INSTANCE"),
         references(clazz, "testSimpleWithSideEffects", "void"));
 
     ClassSubject simpleWithSideEffects = inspector.clazz(SimpleWithSideEffects.class);
@@ -150,19 +154,49 @@
     // As its name implies, its clinit has side effects.
     assertThat(simpleWithSideEffects.clinit(), isPresent());
 
-    // TODO(b/111832046): add support for singleton instance getters.
     assertEquals(
         Lists.newArrayList(
-            "STATIC: String trivial.TrivialTestClass.next()",
-            "VIRTUAL: String trivial.SimpleWithGetter.bar(String)",
-            "VIRTUAL: String trivial.SimpleWithGetter.foo()",
-            "trivial.SimpleWithGetter trivial.SimpleWithGetter.INSTANCE",
-            "trivial.SimpleWithGetter trivial.SimpleWithGetter.INSTANCE"),
+            "STATIC: String SimpleWithGetter.bar(String)",
+            "STATIC: String SimpleWithGetter.foo()",
+            "STATIC: String TrivialTestClass.next()"),
         references(clazz, "testSimpleWithGetter", "void"));
 
     ClassSubject simpleWithGetter = inspector.clazz(SimpleWithGetter.class);
-    assertFalse(instanceMethods(simpleWithGetter).isEmpty());
-    assertThat(simpleWithGetter.clinit(), isPresent());
+    assertTrue(instanceMethods(simpleWithGetter).isEmpty());
+    assertThat(simpleWithGetter.clinit(), not(isPresent()));
+
+    assertEquals(
+        Lists.newArrayList(
+            "STATIC: SimpleWithThrowingGetter SimpleWithThrowingGetter.getInstance()",
+            "STATIC: SimpleWithThrowingGetter SimpleWithThrowingGetter.getInstance()",
+            "STATIC: String TrivialTestClass.next()",
+            "VIRTUAL: String SimpleWithThrowingGetter.bar(String)",
+            "VIRTUAL: String SimpleWithThrowingGetter.foo()"),
+        references(clazz, "testSimpleWithThrowingGetter", "void"));
+
+    ClassSubject simpleWithThrowingGetter = inspector.clazz(SimpleWithThrowingGetter.class);
+    assertFalse(instanceMethods(simpleWithThrowingGetter).isEmpty());
+    assertThat(simpleWithThrowingGetter.clinit(), isPresent());
+
+    // TODO(b/143389508): add support for lazy init in singleton instance getter.
+    assertEquals(
+        Lists.newArrayList(
+            "DIRECT: void SimpleWithLazyInit.<init>()",
+            "DIRECT: void SimpleWithLazyInit.<init>()",
+            "STATIC: String TrivialTestClass.next()",
+            "SimpleWithLazyInit SimpleWithLazyInit.INSTANCE",
+            "SimpleWithLazyInit SimpleWithLazyInit.INSTANCE",
+            "SimpleWithLazyInit SimpleWithLazyInit.INSTANCE",
+            "SimpleWithLazyInit SimpleWithLazyInit.INSTANCE",
+            "SimpleWithLazyInit SimpleWithLazyInit.INSTANCE",
+            "SimpleWithLazyInit SimpleWithLazyInit.INSTANCE",
+            "VIRTUAL: String SimpleWithLazyInit.bar(String)",
+            "VIRTUAL: String SimpleWithLazyInit.foo()"),
+        references(clazz, "testSimpleWithLazyInit", "void"));
+
+    ClassSubject simpleWithLazyInit = inspector.clazz(SimpleWithLazyInit.class);
+    assertFalse(instanceMethods(simpleWithLazyInit).isEmpty());
+    assertThat(simpleWithLazyInit.clinit(), not(isPresent()));
   }
 
   @Test
@@ -316,6 +350,7 @@
             .filter(method -> isTypeOfInterest(method.holder))
             .map(method -> "DIRECT: " + method.toSourceString()))
         .map(txt -> txt.replace("java.lang.", ""))
+        .map(txt -> txt.replace("com.android.tools.r8.ir.optimize.staticizer.trivial.", ""))
         .map(txt -> txt.replace("com.android.tools.r8.ir.optimize.staticizer.", ""))
         .sorted()
         .collect(Collectors.toList());
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/staticizer/trivial/SimpleWithLazyInit.java b/src/test/java/com/android/tools/r8/ir/optimize/staticizer/trivial/SimpleWithLazyInit.java
new file mode 100644
index 0000000..056f452
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/staticizer/trivial/SimpleWithLazyInit.java
@@ -0,0 +1,27 @@
+// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.ir.optimize.staticizer.trivial;
+
+import com.android.tools.r8.NeverInline;
+
+public class SimpleWithLazyInit {
+  private static SimpleWithLazyInit INSTANCE = null;
+
+  static SimpleWithLazyInit getInstance() {
+    if (INSTANCE == null) {
+      INSTANCE = new SimpleWithLazyInit();
+    }
+    return INSTANCE;
+  }
+
+  @NeverInline
+  String foo() {
+    return bar("Simple::foo()");
+  }
+
+  @NeverInline
+  String bar(String other) {
+    return "Simple::bar(" + other + ")";
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/staticizer/trivial/SimpleWithThrowingGetter.java b/src/test/java/com/android/tools/r8/ir/optimize/staticizer/trivial/SimpleWithThrowingGetter.java
new file mode 100644
index 0000000..099d692
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/staticizer/trivial/SimpleWithThrowingGetter.java
@@ -0,0 +1,27 @@
+// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.ir.optimize.staticizer.trivial;
+
+import com.android.tools.r8.NeverInline;
+
+public class SimpleWithThrowingGetter {
+  private static SimpleWithThrowingGetter INSTANCE = new SimpleWithThrowingGetter();
+
+  static SimpleWithThrowingGetter getInstance() {
+    if (System.currentTimeMillis() < 0) {
+      throw new AssertionError("This should not happen!");
+    }
+    return INSTANCE;
+  }
+
+  @NeverInline
+  String foo() {
+    return bar("Simple::foo()");
+  }
+
+  @NeverInline
+  String bar(String other) {
+    return "Simple::bar(" + other + ")";
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/staticizer/trivial/TrivialTestClass.java b/src/test/java/com/android/tools/r8/ir/optimize/staticizer/trivial/TrivialTestClass.java
index 964567c..1dca1e0 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/staticizer/trivial/TrivialTestClass.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/staticizer/trivial/TrivialTestClass.java
@@ -20,6 +20,8 @@
     test.testSimpleWithSideEffects();
     test.testSimpleWithParams();
     test.testSimpleWithGetter();
+    test.testSimpleWithThrowingGetter();
+    test.testSimpleWithLazyInit();
   }
 
   @NeverInline
@@ -60,5 +62,17 @@
     System.out.println(SimpleWithGetter.getInstance().foo());
     System.out.println(SimpleWithGetter.getInstance().bar(next()));
   }
+
+  @NeverInline
+  private void testSimpleWithThrowingGetter() {
+    System.out.println(SimpleWithThrowingGetter.getInstance().foo());
+    System.out.println(SimpleWithThrowingGetter.getInstance().bar(next()));
+  }
+
+  @NeverInline
+  private void testSimpleWithLazyInit() {
+    System.out.println(SimpleWithLazyInit.getInstance().foo());
+    System.out.println(SimpleWithLazyInit.getInstance().bar(next()));
+  }
 }
 
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 fdc4296..6ebd77c 100644
--- a/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java
+++ b/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java
@@ -836,7 +836,7 @@
         method.setCode(ir, allocator, appView);
         directMethods[i] = method;
       }
-      builder.addProgramClass(
+      DexProgramClass programClass =
           new DexProgramClass(
               type,
               null,
@@ -854,7 +854,9 @@
               DexEncodedField.EMPTY_ARRAY,
               directMethods,
               DexEncodedMethod.EMPTY_ARRAY,
-              false));
+              false,
+              DexProgramClass::invalidChecksumRequest);
+      builder.addProgramClass(programClass);
     }
     DirectMappedDexApplication application = builder.build().toDirect();
     ApplicationWriter writer =
@@ -863,7 +865,6 @@
             null,
             options,
             null,
-            null,
             GraphLense.getIdentityLense(),
             NamingLens.getIdentityLens(),
             null);
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 44a973a..77d0846 100644
--- a/src/test/java/com/android/tools/r8/retrace/RetraceTests.java
+++ b/src/test/java/com/android/tools/r8/retrace/RetraceTests.java
@@ -20,6 +20,7 @@
 import com.android.tools.r8.retrace.stacktraces.AmbiguousMissingLineStackTrace;
 import com.android.tools.r8.retrace.stacktraces.AmbiguousStackTrace;
 import com.android.tools.r8.retrace.stacktraces.FileNameExtensionStackTrace;
+import com.android.tools.r8.retrace.stacktraces.InlineFileNameStackTrace;
 import com.android.tools.r8.retrace.stacktraces.InlineNoLineNumberStackTrace;
 import com.android.tools.r8.retrace.stacktraces.InlineWithLineNumbersStackTrace;
 import com.android.tools.r8.retrace.stacktraces.InvalidStackTrace;
@@ -62,6 +63,11 @@
   }
 
   @Test
+  public void testInlineFileNameStackTrace() {
+    runRetraceTest(new InlineFileNameStackTrace());
+  }
+
+  @Test
   public void testNullLineTrace() {
     TestDiagnosticMessagesImpl diagnosticsHandler = new TestDiagnosticMessagesImpl();
     NullStackTrace nullStackTrace = new NullStackTrace();
diff --git a/src/test/java/com/android/tools/r8/retrace/stacktraces/InlineFileNameStackTrace.java b/src/test/java/com/android/tools/r8/retrace/stacktraces/InlineFileNameStackTrace.java
new file mode 100644
index 0000000..65f9824
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/retrace/stacktraces/InlineFileNameStackTrace.java
@@ -0,0 +1,45 @@
+// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.retrace.stacktraces;
+
+import com.android.tools.r8.utils.StringUtils;
+import java.util.Arrays;
+import java.util.List;
+
+public class InlineFileNameStackTrace implements StackTraceForTest {
+
+  @Override
+  public List<String> obfuscatedStackTrace() {
+    return Arrays.asList(
+        "Exception in thread \"main\" java.lang.NullPointerException",
+        "\tat com.android.tools.r8.naming.retrace.Main.main(Main.dummy:3)");
+  }
+
+  @Override
+  public List<String> retracedStackTrace() {
+    return Arrays.asList(
+        "Exception in thread \"main\" java.lang.NullPointerException",
+        "\tat foo.Bar$Baz.baz(Bar.dummy:0)",
+        "\tat Foo$Bar.bar(Foo.dummy:2)",
+        "\tat com.android.tools.r8.naming.retrace.Main$Foo.method1(Main.dummy:8)",
+        "\tat com.android.tools.r8.naming.retrace.Main.main(Main.dummy:7)");
+  }
+
+  @Override
+  public String mapping() {
+    return StringUtils.lines(
+        "com.android.tools.r8.naming.retrace.Main -> com.android.tools.r8.naming.retrace.Main:",
+        "    3:3:void foo.Bar$Baz.baz(long):0:0 -> main",
+        "    3:3:void Foo$Bar.bar(int):2 -> main",
+        "    3:3:void com.android.tools.r8.naming.retrace.Main$Foo.method1(java.lang.String):8:8"
+            + " -> main",
+        "    3:3:void main(java.lang.String[]):7 -> main");
+  }
+
+  @Override
+  public int expectedWarnings() {
+    return 0;
+  }
+}
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 3d1ec2b..0092be2 100644
--- a/src/test/java/com/android/tools/r8/utils/Smali.java
+++ b/src/test/java/com/android/tools/r8/utils/Smali.java
@@ -117,7 +117,6 @@
               null,
               options,
               null,
-              null,
               GraphLense.getIdentityLense(),
               NamingLens.getIdentityLens(),
               null);
diff --git a/tools/internal_test.py b/tools/internal_test.py
index d761a98..9d4bc2b 100755
--- a/tools/internal_test.py
+++ b/tools/internal_test.py
@@ -116,7 +116,7 @@
       '--version=%s' % record['version'],
       '--no-debug',
       '--no-build',
-      '--max-memory=%s' % int(record['oom-threshold'] * 1.1)
+      '--max-memory=%s' % int(record['oom-threshold'] * 1.15)
   ]
 
 def compile_with_memory_min_command(record):
@@ -129,7 +129,7 @@
       '--no-debug',
       '--no-build',
       '--expect-oom',
-      '--max-memory=%s' % int(record['oom-threshold'] * 0.9)
+      '--max-memory=%s' % int(record['oom-threshold'] * 0.85)
   ]
 
 TEST_COMMANDS = [
diff --git a/tools/r8_release.py b/tools/r8_release.py
index 1d3cfbb..3575edf 100755
--- a/tools/r8_release.py
+++ b/tools/r8_release.py
@@ -10,11 +10,124 @@
 import shutil
 import subprocess
 import sys
-import tempfile
-import update_prebuilds_in_android
 import urllib
+
+import update_prebuilds_in_android
 import utils
 
+R8_DEV_BRANCH = '1.7'
+R8_VERSION_FILE = os.path.join(
+    'src', 'main', 'java', 'com', 'android', 'tools', 'r8', 'Version.java')
+
+
+def prepare_release(args):
+  if args.version:
+    print "Cannot manually specify version when making a dev release."
+    sys.exit(1)
+
+  def make_release(args):
+    commithash = args.dev_release
+
+    with utils.TempDir() as temp:
+      subprocess.check_call(['git', 'clone', utils.REPO_SOURCE, temp])
+      with utils.ChangedWorkingDirectory(temp):
+        subprocess.check_call([
+          'git',
+          'new-branch',
+          '--upstream',
+          'origin/%s' % R8_DEV_BRANCH,
+          'dev-release'])
+
+        # Compute the current and new version on the branch.
+        result = None
+        for line in open(R8_VERSION_FILE, 'r'):
+          result = re.match(
+              r'.*LABEL = "%s\.(\d+)\-dev";' % R8_DEV_BRANCH, line)
+          if result:
+            break
+        if not result or not result.group(1):
+          print 'Failed to find version label matching %s(\d+)-dev'\
+                % R8_DEV_BRANCH
+          sys.exit(1)
+        try:
+          patch_version = int(result.group(1))
+        except ValueError:
+          print 'Failed to convert version to integer: %s' % result.group(1)
+
+        old_version = '%s.%s-dev' % (R8_DEV_BRANCH, patch_version)
+        version = '%s.%s-dev' % (R8_DEV_BRANCH, patch_version + 1)
+
+        # Verify that the merge point from master is not empty.
+        merge_diff_output = subprocess.check_output([
+          'git', 'diff', 'HEAD..%s' % commithash])
+        other_diff = version_change_diff(
+            merge_diff_output, old_version, "master")
+        if not other_diff:
+          print 'Merge point from master (%s)' % commithash, \
+            'is the same as exiting release (%s).' % old_version
+          sys.exit(1)
+
+        # Merge the desired commit from master on to the branch.
+        subprocess.check_call([
+          'git', 'merge', '--no-ff', '--no-edit', commithash])
+
+        # Rewrite the version, commit and validate.
+        sed(old_version, version, R8_VERSION_FILE)
+
+        subprocess.check_call([
+          'git', 'commit', '-a', '-m', '"Version %s"' % version])
+
+        version_diff_output = subprocess.check_output([
+          'git', 'diff', '%s..HEAD' % commithash])
+
+        invalid = version_change_diff(version_diff_output, "master", version)
+        if invalid:
+          print "Unexpected diff content for line:"
+          print invalid
+          sys.exit(1)
+
+        # Double check that we want to push the release.
+        if not args.dry_run:
+          input = raw_input('Publish dev release version %s [y/N]:' % version)
+          if input != 'y':
+            print 'Aborting dev release for %s' % version
+            sys.exit(1)
+
+        maybe_check_call(args, [
+          'git', 'push', 'origin', 'HEAD:%s' % R8_DEV_BRANCH])
+        maybe_check_call(args, [
+          'git', 'tag', '-a', version, '-m', '"%s"' % version])
+        maybe_check_call(args, [
+          'git', 'push', 'origin', 'refs/tags/%s' % version])
+
+        return "%s dev version %s from hash %s" % (
+          'DryRun: omitted publish of' if args.dry_run else 'Published',
+          version,
+          commithash)
+
+  return make_release
+
+
+def version_change_diff(diff, old_version, new_version):
+  invalid_line = None
+  for line in diff.splitlines():
+    if line.startswith('-  ') and \
+        line != '-  public static final String LABEL = "%s";' % old_version:
+      invalid_line = line
+    elif line.startswith('+  ') and \
+        line != '+  public static final String LABEL = "%s";' % new_version:
+      invalid_line = line
+  return invalid_line
+
+
+def maybe_check_call(args, cmd):
+  if args.dry_run:
+    print 'DryRun:', ' '.join(cmd)
+  else:
+    print ' '.join(cmd)
+    return subprocess.check_call(cmd)
+
+
 def update_prebuilds(version, checkout):
   update_prebuilds_in_android.main_download('', True, 'lib', checkout, version)
 
@@ -40,10 +153,14 @@
 
 
 def prepare_aosp(args):
+  assert args.version
   assert os.path.exists(args.aosp), "Could not find AOSP path %s" % args.aosp
 
   def release_aosp(options):
     print "Releasing for AOSP"
+    if options.dry_run:
+      return 'DryRun: omitting AOSP release for %s' % options.version
+
     git_message = ("""Update D8 and R8 to %s
 
 Version: master %s
@@ -80,11 +197,15 @@
 
 
 def prepare_studio(args):
+  assert args.version
   assert os.path.exists(args.studio), ("Could not find STUDIO path %s"
                                        % args.studio)
 
   def release_studio(options):
     print "Releasing for STUDIO"
+    if options.dry_run:
+      return 'DryRun: omitting studio release for %s' % options.version
+
     git_message = (git_message_dev(options.version)
                    if 'dev' in options.version
                    else git_message_release(options.version, options.bug))
@@ -107,7 +228,8 @@
 
 def g4_change(version, r8version):
   return subprocess.check_output(
-      'g4 change --desc "Update R8 to version %s %s"' % (version, r8version), shell=True)
+      'g4 change --desc "Update R8 to version %s %s"' % (version, r8version),
+      shell=True)
 
 
 def sed(pattern, replace, path):
@@ -129,7 +251,8 @@
       'blaze run %s' % target, shell=True, stderr=subprocess.STDOUT)
 
 
-def prepare_google3():
+def prepare_google3(args):
+  assert args.version
   # Check if an existing client exists.
   if ':update-r8:' in subprocess.check_output('g4 myclients', shell=True):
     print "Remove the existing 'update-r8' client before continuing."
@@ -137,6 +260,8 @@
 
   def release_google3(options):
     print "Releasing for Google 3"
+    if options.dry_run:
+      return 'DryRun: omitting g3 release for %s' % options.version
 
     google3_base = subprocess.check_output(
         ['p4', 'g4d', '-f', 'update-r8']).rstrip()
@@ -212,8 +337,10 @@
 
 def parse_options():
   result = argparse.ArgumentParser(description='Release r8')
-  result.add_argument('--version',
-                      required=True,
+  group = result.add_mutually_exclusive_group()
+  group.add_argument('--dev-release',
+                      help='The hash to use for the new dev version of R8')
+  group.add_argument('--version',
                       help='The new version of R8 (e.g., 1.4.51)')
   result.add_argument('--no-sync', '--no_sync',
                       default=False,
@@ -233,22 +360,37 @@
                       default=False,
                       action='store_true',
                       help='Release for google 3')
+  result.add_argument('--dry-run',
+                      default=False,
+                      action='store_true',
+                      help='Only perform non-commiting tasks and print others.')
   args = result.parse_args()
-  if not 'dev' in args.version and args.bug == []:
+  if args.version and not 'dev' in args.version and args.bug == []:
     print "When releasing a release version add the list of bugs by using '--bug'"
     sys.exit(1)
 
+  if args.version and not 'dev' in args.version and args.google3:
+    print "You should not roll a release version into google 3"
+    sys.exit(1)
+
   return args
 
 
 def main():
   args = parse_options()
   targets_to_run = []
+
+  if args.dev_release:
+    if args.google3 or args.studio or args.aosp:
+      print 'Cannot create a dev release and roll at the same time.'
+      sys.exit(1)
+    targets_to_run.append(prepare_release(args))
+
   if args.google3 or args.studio:
     utils.check_prodacces()
 
   if args.google3:
-    targets_to_run.append(prepare_google3())
+    targets_to_run.append(prepare_google3(args))
   if args.studio:
     targets_to_run.append(prepare_studio(args))
   if args.aosp:
diff --git a/tools/utils.py b/tools/utils.py
index fb937e6..cede4c7 100644
--- a/tools/utils.py
+++ b/tools/utils.py
@@ -34,6 +34,7 @@
 GENERATED_LICENSE_DIR = os.path.join(BUILD, 'generatedLicense')
 SRC_ROOT = os.path.join(REPO_ROOT, 'src', 'main', 'java')
 TEST_ROOT = os.path.join(REPO_ROOT, 'src', 'test', 'java')
+REPO_SOURCE = 'https://r8.googlesource.com/r8'
 
 D8 = 'd8'
 R8 = 'r8'