Merge commit '1d6b2cf4236058a6792f8c83f265192520e10a22' into dev-release
diff --git a/doc/compilerdump.md b/doc/compilerdump.md
new file mode 100644
index 0000000..2db569a
--- /dev/null
+++ b/doc/compilerdump.md
@@ -0,0 +1,80 @@
+# Compiler-input dumps
+
+The D8 and R8 compilers support generating a compiler-input dump for use in
+reproducing compiler issues.
+
+
+## The content of a dump
+
+The dump contains almost all of the inputs that are given to the compiler as
+part of compilation. In particular, it contains *all* class definitions in the
+form of Java classfiles (i.e., bytecode, not the Java source files).
+In addition to the classfiles, the dump also includes Java resources, the
+compiler type, version, and flags, such as `--debug` or `--release`,
+main-dex lists or rules, and more. For R8 the dump also contains the full
+concatenation of all keep rules.
+
+The dump is a zip file containing the above. You should unzip it and review
+the content locally. The program, classpath and library content will be in
+nested zip files. The remaining content is in plain text files.
+
+
+## Generating a dump
+
+To generate a dump file, run the compiler with the
+`com.android.tools.r8.dumpinputtofile` system property set:
+
+```
+java -cp r8.jar -Dcom.android.tools.r8.dumpinputtofile=mydump.zip com.android.tools.r8.D8 <other-compiler-args>
+```
+
+This will generate a dump file `mydump.zip` and exit the compiler with a
+non-zero exit value printing an error message about the location of the dump
+file that was written.
+
+For some builds, there may be many compilations taking place which cannot be
+easily isolated as individual compilation steps. If so, the system property
+`com.android.tools.r8.dumpinputtodirectory` can be set to a directory instead.
+Doing so will dump the inputs of each compilation to the directory in
+individual zip files and they will be named using a timestamp in an attempt
+to maintain an order. The compiler will compile as usual thus not disrupting
+the build.
+
+### Generating a dump with Gradle and the Android Studio Plugin
+
+To generate a dump from studio, the system property should be set for the
+build command. This can typically be done by amending the command-line gradle
+build-target command. Say your build target is `assembleRelease`, you would run:
+
+```
+./gradlew assembleRelease -Dcom.android.tools.r8.dumpinputtofile=mydump.zip --no-daemon
+```
+
+If the build is a debug build, such as `assembleDebug`, then it will likely be an
+incremental D8 build in which case the best option is to provide a dump
+directory and then locate the particular dump file associated with the
+problematic compilation (if the compilation fails, the interesting dump will
+hopefully be the last dump):
+
+```
+./gradlew assembleDebug -Dcom.android.tools.r8.dumpinputtodirectory=mydumps/ --no-daemon
+```
+
+
+## Reproducing using a dump
+
+To reproduce a compiler issue with a dump use the script `tools/compiledump.py`
+from the R8 repository. Note that the script is *not* a standalone script so you
+will need a full checkout of the R8 project.
+
+Reproducing should then be just:
+```
+./tools/compiledump.py -d mydump.zip
+```
+
+Depending on the compiler version that generated the dump additional flags may
+be needed to specify the compiler and its version. Run `--help` for a list of
+options.
+
+The flags can also be used to override the setting specified in the dump.
+Doing so allows compiling with other compiler versions, or other settings.
diff --git a/src/main/java/com/android/tools/r8/D8.java b/src/main/java/com/android/tools/r8/D8.java
index 4fdf194..cae27a3 100644
--- a/src/main/java/com/android/tools/r8/D8.java
+++ b/src/main/java/com/android/tools/r8/D8.java
@@ -196,7 +196,7 @@
             clazz -> {
               ProgramMethod classInitializer = clazz.getProgramClassInitializer();
               if (classInitializer != null) {
-                analysis.processNewlyLiveMethod(classInitializer);
+                analysis.processNewlyLiveMethod(classInitializer, clazz);
               }
             },
             executor);
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java
index ea95988..1dc771e 100644
--- a/src/main/java/com/android/tools/r8/R8.java
+++ b/src/main/java/com/android/tools/r8/R8.java
@@ -13,7 +13,6 @@
 import com.android.tools.r8.dex.Marker;
 import com.android.tools.r8.dex.Marker.Tool;
 import com.android.tools.r8.errors.CheckDiscardDiagnostic;
-import com.android.tools.r8.errors.CompilationError;
 import com.android.tools.r8.experimental.graphinfo.GraphConsumer;
 import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
 import com.android.tools.r8.graph.AppServices;
@@ -43,7 +42,7 @@
 import com.android.tools.r8.graph.classmerging.StaticallyMergedClasses;
 import com.android.tools.r8.graph.classmerging.VerticallyMergedClasses;
 import com.android.tools.r8.horizontalclassmerging.HorizontalClassMerger;
-import com.android.tools.r8.horizontalclassmerging.HorizontalClassMergerGraphLens;
+import com.android.tools.r8.horizontalclassmerging.HorizontalClassMergerResult;
 import com.android.tools.r8.horizontalclassmerging.HorizontallyMergedClasses;
 import com.android.tools.r8.inspector.internal.InspectorImpl;
 import com.android.tools.r8.ir.conversion.IRConverter;
@@ -96,7 +95,7 @@
 import com.android.tools.r8.shaking.MainDexClasses;
 import com.android.tools.r8.shaking.MainDexListBuilder;
 import com.android.tools.r8.shaking.MainDexTracingResult;
-import com.android.tools.r8.shaking.ProguardClassFilter;
+import com.android.tools.r8.shaking.MissingClasses;
 import com.android.tools.r8.shaking.ProguardConfigurationRule;
 import com.android.tools.r8.shaking.ProguardConfigurationUtils;
 import com.android.tools.r8.shaking.RootSetBuilder;
@@ -122,7 +121,6 @@
 import com.android.tools.r8.utils.Timing;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.Iterables;
-import com.google.common.collect.Sets;
 import com.google.common.io.ByteStreams;
 import java.io.ByteArrayOutputStream;
 import java.io.FileOutputStream;
@@ -252,13 +250,6 @@
     }
   }
 
-  private Set<DexType> filterMissingClasses(Set<DexType> missingClasses,
-      ProguardClassFilter dontWarnPatterns) {
-    Set<DexType> result = new HashSet<>(missingClasses);
-    dontWarnPatterns.filterOutMatches(result);
-    return result;
-  }
-
   static void runForTesting(AndroidApp app, InternalOptions options)
       throws CompilationFailedException {
     ExecutorService executor = ThreadUtils.getExecutorService(options);
@@ -329,35 +320,19 @@
       List<ProguardConfigurationRule> synthesizedProguardRules = new ArrayList<>();
       timing.begin("Strip unused code");
       Set<DexType> classesToRetainInnerClassAttributeFor = null;
-      Set<DexType> missingClasses = null;
       RuntimeTypeCheckInfo.Builder classMergingEnqueuerExtensionBuilder =
           new RuntimeTypeCheckInfo.Builder(appView.dexItemFactory());
       try {
-        // TODO(b/154849103): Find a better way to determine missing classes.
-        missingClasses = new SubtypingInfo(appView).getMissingClasses();
-        missingClasses = filterMissingClasses(
-            missingClasses, options.getProguardConfiguration().getDontWarnPatterns());
-        if (!missingClasses.isEmpty()) {
-          missingClasses.forEach(
-              clazz -> {
-                options.reporter.warning(
-                    new StringDiagnostic("Missing class: " + clazz.toSourceString()));
-              });
-          if (!options.ignoreMissingClasses) {
-            DexType missingClass = missingClasses.iterator().next();
-            if (missingClasses.size() == 1) {
-              throw new CompilationError(
-                  "Compilation can't be completed because the class `"
-                      + missingClass.toSourceString()
-                      + "` is missing.");
-            } else {
-              throw new CompilationError(
-                  "Compilation can't be completed because `" + missingClass.toSourceString()
-                      + "` and " + (missingClasses.size() - 1) + " other classes are missing.");
-            }
-          }
+        // TODO(b/154849103): Remove once reported by the Enqueuer.
+        if (!appView.testing().enableExperimentalMissingClassesReporting) {
+          appView.setAppInfo(
+              appView
+                  .appInfo()
+                  .rebuildWithClassHierarchy(
+                      MissingClasses.builderForInitialMissingClasses()
+                          .addNewMissingClasses(new SubtypingInfo(appView).getMissingClasses())
+                          .reportMissingClasses(options)));
         }
-        options.reporter.failIfPendingErrors();
 
         // Add synthesized -assumenosideeffects from min api if relevant.
         if (options.isGeneratingDex()) {
@@ -392,9 +367,6 @@
         assert appView.rootSet().verifyKeptTypesAreLive(appViewWithLiveness.appInfo());
         assert appView.rootSet().verifyKeptItemsAreKept(appView);
 
-        missingClasses =
-            Sets.union(missingClasses, appViewWithLiveness.appInfo().getMissingTypes());
-
         appView.rootSet().checkAllRulesAreUsed(options);
 
         if (options.proguardSeedsConsumer != null) {
@@ -588,20 +560,26 @@
           HorizontalClassMerger merger = new HorizontalClassMerger(appViewWithLiveness);
           DirectMappedDexApplication.Builder appBuilder =
               appView.appInfo().app().asDirect().builder();
-          HorizontalClassMergerGraphLens lens =
+          HorizontalClassMergerResult horizontalClassMergerResult =
               merger.run(appBuilder, mainDexTracingResult, runtimeTypeCheckInfo);
-          if (lens != null) {
-            DirectMappedDexApplication app = appBuilder.build();
+          if (horizontalClassMergerResult != null) {
+            // Must rewrite AppInfoWithLiveness before pruning the merged classes, to ensure that
+            // allocations sites, fields accesses, etc. are correctly transferred to the target
+            // classes.
+            appView.rewriteWithLensAndApplication(
+                horizontalClassMergerResult.getGraphLens(), appBuilder.build());
+            horizontalClassMergerResult
+                .getFieldAccessInfoCollectionModifier()
+                .modify(appViewWithLiveness);
+
             appView.pruneItems(
                 PrunedItems.builder()
-                    .setPrunedApp(app)
+                    .setPrunedApp(appView.appInfo().app())
                     .addRemovedClasses(appView.horizontallyMergedClasses().getSources())
                     .addNoLongerSyntheticItems(appView.horizontallyMergedClasses().getTargets())
                     .build());
-            appView.rewriteWithLens(lens);
 
-            // Only required for class merging, clear instance to save memory.
-            runtimeTypeCheckInfo = null;
+            mainDexTracingResult = horizontalClassMergerResult.getMainDexTracingResult();
           }
           timing.end();
         } else {
@@ -717,7 +695,6 @@
                   appView,
                   new SubtypingInfo(appView),
                   keptGraphConsumer,
-                  missingClasses,
                   prunedTypes);
           appView.setAppInfo(
               enqueuer.traceApplication(
diff --git a/src/main/java/com/android/tools/r8/dex/DebugBytecodeWriter.java b/src/main/java/com/android/tools/r8/dex/DebugBytecodeWriter.java
index 06dbcfe..2c3e18a 100644
--- a/src/main/java/com/android/tools/r8/dex/DebugBytecodeWriter.java
+++ b/src/main/java/com/android/tools/r8/dex/DebugBytecodeWriter.java
@@ -7,6 +7,7 @@
 import com.android.tools.r8.graph.DexDebugInfo;
 import com.android.tools.r8.graph.DexString;
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.GraphLens;
 import com.android.tools.r8.graph.ObjectToOffsetMapping;
 import com.android.tools.r8.utils.LebUtils;
 import java.nio.ByteBuffer;
@@ -15,12 +16,15 @@
 public class DebugBytecodeWriter {
 
   private final ObjectToOffsetMapping mapping;
+  private final GraphLens graphLens;
   private final DexDebugInfo info;
   private ByteBuffer buffer;
 
-  public DebugBytecodeWriter(DexDebugInfo info, ObjectToOffsetMapping mapping) {
+  public DebugBytecodeWriter(
+      DexDebugInfo info, ObjectToOffsetMapping mapping, GraphLens graphLens) {
     this.info = info;
     this.mapping = mapping;
+    this.graphLens = graphLens;
     // Never allocate a zero-sized buffer, as we need to write the header, and the growth policy
     // requires it to have a positive capacity.
     this.buffer = ByteBuffer.allocate(info.events.length * 5 + 4);
@@ -35,7 +39,7 @@
     }
     // Body.
     for (DexDebugEvent event : info.events) {
-      event.writeOn(this, mapping);
+      event.writeOn(this, mapping, graphLens);
     }
     // Tail.
     putByte(Constants.DBG_END_SEQUENCE);
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 48fded1..2408b27 100644
--- a/src/main/java/com/android/tools/r8/dex/DexParser.java
+++ b/src/main/java/com/android/tools/r8/dex/DexParser.java
@@ -53,6 +53,8 @@
 import com.android.tools.r8.graph.FieldAccessFlags;
 import com.android.tools.r8.graph.GenericSignature;
 import com.android.tools.r8.graph.GenericSignature.ClassSignature;
+import com.android.tools.r8.graph.GenericSignature.FieldTypeSignature;
+import com.android.tools.r8.graph.GenericSignature.MethodTypeSignature;
 import com.android.tools.r8.graph.InnerClassAttribute;
 import com.android.tools.r8.graph.MethodAccessFlags;
 import com.android.tools.r8.graph.OffsetToObjectMapping;
@@ -62,7 +64,6 @@
 import com.android.tools.r8.origin.PathOrigin;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.Pair;
-import com.android.tools.r8.utils.Reporter;
 import com.google.common.io.ByteStreams;
 import it.unimi.dsi.fastutil.ints.Int2IntArrayMap;
 import it.unimi.dsi.fastutil.ints.Int2ReferenceMap;
@@ -636,18 +637,19 @@
         }
       }
       DexAnnotationSet fieldAnnotations = annotationIterator.getNextFor(field);
-      String fieldSignature = DexAnnotation.getSignature(fieldAnnotations, dexItemFactory);
-      if (fieldSignature != null) {
-        fieldAnnotations = fieldAnnotations.getWithout(dexItemFactory.annotationSignature);
+      FieldTypeSignature fieldTypeSignature = FieldTypeSignature.noSignature();
+      if (!options.passthroughDexCode) {
+        String fieldSignature = DexAnnotation.getSignature(fieldAnnotations, dexItemFactory);
+        if (fieldSignature != null) {
+          fieldAnnotations = fieldAnnotations.getWithout(dexItemFactory.annotationSignature);
+          fieldTypeSignature =
+              GenericSignature.parseFieldTypeSignature(
+                  field.name.toString(), fieldSignature, origin, dexItemFactory, options.reporter);
+        }
       }
       fields[i] =
           new DexEncodedField(
-              field,
-              accessFlags,
-              GenericSignature.parseFieldTypeSignature(
-                  field.name.toString(), fieldSignature, origin, dexItemFactory, options.reporter),
-              fieldAnnotations,
-              staticValue);
+              field, accessFlags, fieldTypeSignature, fieldAnnotations, staticValue);
     }
     return fields;
   }
@@ -676,20 +678,25 @@
       DexMethod method = indexedItems.getMethod(methodIndex);
       accessFlags.setConstructor(method, dexItemFactory);
       DexAnnotationSet methodAnnotations = annotationIterator.getNextFor(method);
-      String methodSignature = DexAnnotation.getSignature(methodAnnotations, dexItemFactory);
-      if (methodSignature != null) {
-        methodAnnotations = methodAnnotations.getWithout(dexItemFactory.annotationSignature);
-      }
-      methods[i] =
-          new DexEncodedMethod(
-              method,
-              accessFlags,
+      MethodTypeSignature methodTypeSignature = MethodTypeSignature.noSignature();
+      if (!options.passthroughDexCode) {
+        String methodSignature = DexAnnotation.getSignature(methodAnnotations, dexItemFactory);
+        if (methodSignature != null) {
+          methodAnnotations = methodAnnotations.getWithout(dexItemFactory.annotationSignature);
+          methodTypeSignature =
               GenericSignature.parseMethodSignature(
                   method.name.toString(),
                   methodSignature,
                   origin,
                   dexItemFactory,
-                  options.reporter),
+                  options.reporter);
+        }
+      }
+      methods[i] =
+          new DexEncodedMethod(
+              method,
+              accessFlags,
+              methodTypeSignature,
               methodAnnotations,
               parameterAnnotationsIterator.getNextFor(method),
               code);
@@ -783,8 +790,7 @@
       }
 
       AttributesAndAnnotations attrs =
-          new AttributesAndAnnotations(
-              type, origin, annotationsDirectory.clazz, options.itemFactory, options.reporter);
+          new AttributesAndAnnotations(type, origin, annotationsDirectory.clazz, options);
 
       Long finalChecksum = checksum;
       ChecksumSupplier checksumSupplier =
@@ -1380,15 +1386,12 @@
     }
 
     public AttributesAndAnnotations(
-        DexType type,
-        Origin origin,
-        DexAnnotationSet annotations,
-        DexItemFactory factory,
-        Reporter reporter) {
+        DexType type, Origin origin, DexAnnotationSet annotations, InternalOptions options) {
       this.originalAnnotations = annotations;
       DexType enclosingClass = null;
       DexMethod enclosingMethod = null;
       List<DexType> memberClasses = null;
+      DexItemFactory factory = options.dexItemFactory();
 
       for (int i = 0; i < annotations.annotations.length; i++) {
         DexAnnotation annotation = annotations.annotations[i];
@@ -1415,12 +1418,13 @@
           } else {
             memberClasses.addAll(members);
           }
-        } else if (DexAnnotation.isSignatureAnnotation(annotation, factory)) {
+        } else if (DexAnnotation.isSignatureAnnotation(annotation, factory)
+            && !options.passthroughDexCode) {
           ensureAnnotations(i);
           String signature = DexAnnotation.getSignature(annotation);
           classSignature =
               GenericSignature.parseClassSignature(
-                  type.getName(), signature, origin, factory, reporter);
+                  type.getName(), signature, origin, factory, options.reporter);
         } else {
           copyAnnotation(annotation);
         }
diff --git a/src/main/java/com/android/tools/r8/dex/FileWriter.java b/src/main/java/com/android/tools/r8/dex/FileWriter.java
index 6f2f904..ef6b7cd 100644
--- a/src/main/java/com/android/tools/r8/dex/FileWriter.java
+++ b/src/main/java/com/android/tools/r8/dex/FileWriter.java
@@ -194,7 +194,7 @@
       for (ProgramDexCode code : codes) {
         DexDebugInfoForWriting info = code.getCode().getDebugInfoForWriting();
         if (info != null && seen.add(info)) {
-          writeDebugItem(info);
+          writeDebugItem(info, graphLens);
         }
       }
     }
@@ -487,9 +487,9 @@
     dest.putInt(mixedSectionOffsets.getOffsetFor(staticFieldValues.get(clazz)));
   }
 
-  private void writeDebugItem(DexDebugInfo debugInfo) {
+  private void writeDebugItem(DexDebugInfo debugInfo, GraphLens graphLens) {
     mixedSectionOffsets.setOffsetFor(debugInfo, dest.position());
-    dest.putBytes(new DebugBytecodeWriter(debugInfo, mapping).generate());
+    dest.putBytes(new DebugBytecodeWriter(debugInfo, mapping, graphLens).generate());
   }
 
   private void writeCodeItem(ProgramDexCode code) {
diff --git a/src/main/java/com/android/tools/r8/graph/AccessFlags.java b/src/main/java/com/android/tools/r8/graph/AccessFlags.java
index e47b428..98806f6 100644
--- a/src/main/java/com/android/tools/r8/graph/AccessFlags.java
+++ b/src/main/java/com/android/tools/r8/graph/AccessFlags.java
@@ -204,8 +204,9 @@
     set(Constants.ACC_FINAL);
   }
 
-  public void unsetFinal() {
+  public T unsetFinal() {
     unset(Constants.ACC_FINAL);
+    return self();
   }
 
   public boolean isSynthetic() {
@@ -216,8 +217,9 @@
     set(Constants.ACC_SYNTHETIC);
   }
 
-  public void unsetSynthetic() {
+  public T unsetSynthetic() {
     unset(Constants.ACC_SYNTHETIC);
+    return self();
   }
 
   public void demoteFromSynthetic() {
diff --git a/src/main/java/com/android/tools/r8/graph/AppInfoWithClassHierarchy.java b/src/main/java/com/android/tools/r8/graph/AppInfoWithClassHierarchy.java
index b6100c2..e747aa0 100644
--- a/src/main/java/com/android/tools/r8/graph/AppInfoWithClassHierarchy.java
+++ b/src/main/java/com/android/tools/r8/graph/AppInfoWithClassHierarchy.java
@@ -17,11 +17,14 @@
 import com.android.tools.r8.graph.ResolutionResult.SingleResolutionResult;
 import com.android.tools.r8.ir.desugar.LambdaDescriptor;
 import com.android.tools.r8.shaking.MainDexClasses;
+import com.android.tools.r8.shaking.MissingClasses;
 import com.android.tools.r8.synthesis.CommittedItems;
 import com.android.tools.r8.synthesis.SyntheticItems;
 import com.android.tools.r8.utils.ListUtils;
 import com.android.tools.r8.utils.SetUtils;
 import com.android.tools.r8.utils.TraversalContinuation;
+import com.android.tools.r8.utils.TriConsumer;
+import com.android.tools.r8.utils.TriFunction;
 import com.android.tools.r8.utils.WorkList;
 import com.google.common.collect.Sets;
 import java.util.ArrayDeque;
@@ -33,8 +36,6 @@
 import java.util.List;
 import java.util.Map.Entry;
 import java.util.Set;
-import java.util.function.BiConsumer;
-import java.util.function.BiFunction;
 import java.util.function.Function;
 
 /* Specific subclass of AppInfo designed to support desugaring in D8. Desugaring requires a
@@ -56,24 +57,34 @@
     return new AppInfoWithClassHierarchy(
         SyntheticItems.createInitialSyntheticItems(application),
         classToFeatureSplitMap,
-        mainDexClasses);
+        mainDexClasses,
+        MissingClasses.empty());
   }
 
   private final ClassToFeatureSplitMap classToFeatureSplitMap;
 
+  /** Set of types that are mentioned in the program, but for which no definition exists. */
+  // TODO(b/175659048): Consider hoisting to AppInfo to allow using MissingClasses in D8 desugar.
+  private final MissingClasses missingClasses;
+
   // For AppInfoWithLiveness subclass.
   protected AppInfoWithClassHierarchy(
       CommittedItems committedItems,
       ClassToFeatureSplitMap classToFeatureSplitMap,
-      MainDexClasses mainDexClasses) {
+      MainDexClasses mainDexClasses,
+      MissingClasses missingClasses) {
     super(committedItems, mainDexClasses);
     this.classToFeatureSplitMap = classToFeatureSplitMap;
+    this.missingClasses = missingClasses;
   }
 
   // For desugaring.
   private AppInfoWithClassHierarchy(CreateDesugaringViewOnAppInfo witness, AppInfo appInfo) {
     super(witness, appInfo);
     this.classToFeatureSplitMap = ClassToFeatureSplitMap.createEmptyClassToFeatureSplitMap();
+    // TODO(b/175659048): Migrate the reporting of missing classes in D8 desugar to MissingClasses,
+    //  and use the missing classes from AppInfo instead of MissingClasses.empty().
+    this.missingClasses = MissingClasses.empty();
   }
 
   public static AppInfoWithClassHierarchy createForDesugaring(AppInfo appInfo) {
@@ -82,7 +93,16 @@
   }
 
   public final AppInfoWithClassHierarchy rebuildWithClassHierarchy(CommittedItems commit) {
-    return new AppInfoWithClassHierarchy(commit, getClassToFeatureSplitMap(), getMainDexClasses());
+    return new AppInfoWithClassHierarchy(
+        commit, getClassToFeatureSplitMap(), getMainDexClasses(), getMissingClasses());
+  }
+
+  public final AppInfoWithClassHierarchy rebuildWithClassHierarchy(MissingClasses missingClasses) {
+    return new AppInfoWithClassHierarchy(
+        getSyntheticItems().commit(app()),
+        getClassToFeatureSplitMap(),
+        getMainDexClasses(),
+        missingClasses);
   }
 
   public AppInfoWithClassHierarchy rebuildWithClassHierarchy(
@@ -90,7 +110,8 @@
     return new AppInfoWithClassHierarchy(
         getSyntheticItems().commit(fn.apply(app())),
         getClassToFeatureSplitMap(),
-        getMainDexClasses());
+        getMainDexClasses(),
+        getMissingClasses());
   }
 
   @Override
@@ -104,13 +125,18 @@
     return new AppInfoWithClassHierarchy(
         getSyntheticItems().commitPrunedItems(prunedItems),
         getClassToFeatureSplitMap().withoutPrunedItems(prunedItems),
-        getMainDexClasses().withoutPrunedItems(prunedItems));
+        getMainDexClasses().withoutPrunedItems(prunedItems),
+        getMissingClasses());
   }
 
   public ClassToFeatureSplitMap getClassToFeatureSplitMap() {
     return classToFeatureSplitMap;
   }
 
+  public MissingClasses getMissingClasses() {
+    return missingClasses;
+  }
+
   @Override
   public boolean hasClassHierarchy() {
     assert checkIfObsolete();
@@ -131,7 +157,7 @@
    * result of the traversal is BREAK iff the function returned BREAK.
    */
   public TraversalContinuation traverseSuperTypes(
-      final DexClass clazz, BiFunction<DexType, Boolean, TraversalContinuation> fn) {
+      final DexClass clazz, TriFunction<DexType, DexClass, Boolean, TraversalContinuation> fn) {
     // We do an initial zero-allocation pass over the class super chain as it does not require a
     // worklist/seen-set. Only if the traversal is not aborted and there actually are interfaces,
     // do we continue traversal over the interface types. This is assuming that the second pass
@@ -144,7 +170,7 @@
         if (currentClass.superType == null) {
           break;
         }
-        TraversalContinuation stepResult = fn.apply(currentClass.superType, false);
+        TraversalContinuation stepResult = fn.apply(currentClass.superType, currentClass, false);
         if (stepResult.shouldBreak()) {
           return stepResult;
         }
@@ -163,7 +189,7 @@
       while (currentClass != null) {
         for (DexType iface : currentClass.interfaces.values) {
           if (seen.add(iface)) {
-            TraversalContinuation stepResult = fn.apply(iface, true);
+            TraversalContinuation stepResult = fn.apply(iface, currentClass, true);
             if (stepResult.shouldBreak()) {
               return stepResult;
             }
@@ -183,7 +209,7 @@
       if (definition != null) {
         for (DexType iface : definition.interfaces.values) {
           if (seen.add(iface)) {
-            TraversalContinuation stepResult = fn.apply(iface, true);
+            TraversalContinuation stepResult = fn.apply(iface, definition, true);
             if (stepResult.shouldBreak()) {
               return stepResult;
             }
@@ -200,11 +226,11 @@
    *
    * <p>Same as traverseSuperTypes, but unconditionally visits all.
    */
-  public void forEachSuperType(final DexClass clazz, BiConsumer<DexType, Boolean> fn) {
+  public void forEachSuperType(DexClass clazz, TriConsumer<DexType, DexClass, Boolean> fn) {
     traverseSuperTypes(
         clazz,
-        (type, isInterface) -> {
-          fn.accept(type, isInterface);
+        (superType, subclass, isInterface) -> {
+          fn.accept(superType, subclass, isInterface);
           return CONTINUE;
         });
   }
@@ -241,8 +267,7 @@
     }
     // TODO(b/123506120): Report missing types when the predicate is inconclusive.
     return traverseSuperTypes(
-            clazz,
-            (type, isInterface) -> type == supertype ? BREAK : CONTINUE)
+            clazz, (superType, subclass, isInterface) -> superType == supertype ? BREAK : CONTINUE)
         .shouldBreak();
   }
 
@@ -279,11 +304,13 @@
     if (clazz.isInterface()) {
       interfaces.add(type);
     }
-    forEachSuperType(clazz, (dexType, isInterface) -> {
-      if (isInterface) {
-        interfaces.add(dexType);
-      }
-    });
+    forEachSuperType(
+        clazz,
+        (superType, subclass, isInterface) -> {
+          if (isInterface) {
+            interfaces.add(superType);
+          }
+        });
     return interfaces;
   }
 
diff --git a/src/main/java/com/android/tools/r8/graph/AppView.java b/src/main/java/com/android/tools/r8/graph/AppView.java
index d51e21c..6bedacf 100644
--- a/src/main/java/com/android/tools/r8/graph/AppView.java
+++ b/src/main/java/com/android/tools/r8/graph/AppView.java
@@ -13,6 +13,7 @@
 import com.android.tools.r8.graph.classmerging.StaticallyMergedClasses;
 import com.android.tools.r8.graph.classmerging.VerticallyMergedClasses;
 import com.android.tools.r8.horizontalclassmerging.HorizontallyMergedClasses;
+import com.android.tools.r8.ir.analysis.inlining.SimpleInliningConstraintFactory;
 import com.android.tools.r8.ir.analysis.proto.EnumLiteProtoShrinker;
 import com.android.tools.r8.ir.analysis.proto.GeneratedExtensionRegistryShrinker;
 import com.android.tools.r8.ir.analysis.proto.GeneratedMessageLiteBuilderShrinker;
@@ -67,6 +68,8 @@
   private final InstanceFieldInitializationInfoFactory instanceFieldInitializationInfoFactory =
       new InstanceFieldInitializationInfoFactory();
   private final MethodProcessingId.Factory methodProcessingIdFactory;
+  private final SimpleInliningConstraintFactory simpleInliningConstraintFactory =
+      new SimpleInliningConstraintFactory();
 
   // Desugaring.
   public final PrefixRewritingMapper rewritePrefix;
@@ -187,6 +190,10 @@
     return methodProcessingIdFactory;
   }
 
+  public SimpleInliningConstraintFactory simpleInliningConstraintFactory() {
+    return simpleInliningConstraintFactory;
+  }
+
   public T appInfo() {
     assert !appInfo.hasClassHierarchy() || enableWholeProgramOptimizations();
     return appInfo;
diff --git a/src/main/java/com/android/tools/r8/graph/CfCode.java b/src/main/java/com/android/tools/r8/graph/CfCode.java
index 2e1eeb6..eb83898 100644
--- a/src/main/java/com/android/tools/r8/graph/CfCode.java
+++ b/src/main/java/com/android/tools/r8/graph/CfCode.java
@@ -344,26 +344,34 @@
       Map<Integer, DebugLocalInfo> parameterInfo = method.getDefinition().getParameterInfo();
       for (Entry<Integer, DebugLocalInfo> entry : parameterInfo.entrySet()) {
         writeLocalVariableEntry(
-            visitor, namingLens, entry.getValue(), parameterLabel, parameterLabel, entry.getKey());
+            visitor,
+            graphLens,
+            namingLens,
+            entry.getValue(),
+            parameterLabel,
+            parameterLabel,
+            entry.getKey());
       }
     } else {
       for (LocalVariableInfo local : localVariables) {
         writeLocalVariableEntry(
-            visitor, namingLens, local.local, local.start, local.end, local.index);
+            visitor, graphLens, namingLens, local.local, local.start, local.end, local.index);
       }
     }
   }
 
   private void writeLocalVariableEntry(
       MethodVisitor visitor,
+      GraphLens graphLens,
       NamingLens namingLens,
       DebugLocalInfo info,
       CfLabel start,
       CfLabel end,
       int index) {
+    DexType rewrittenType = graphLens.lookupType(info.type);
     visitor.visitLocalVariable(
         info.name.toString(),
-        namingLens.lookupDescriptor(info.type).toString(),
+        namingLens.lookupDescriptor(rewrittenType).toString(),
         info.signature == null ? null : info.signature.toString(),
         start.getLabel(),
         end.getLabel(),
diff --git a/src/main/java/com/android/tools/r8/graph/Code.java b/src/main/java/com/android/tools/r8/graph/Code.java
index 535c08f..b0d84f9 100644
--- a/src/main/java/com/android/tools/r8/graph/Code.java
+++ b/src/main/java/com/android/tools/r8/graph/Code.java
@@ -57,10 +57,15 @@
     return false;
   }
 
+  public boolean isHorizontalClassMergingCode() {
+    return false;
+  }
+
   public boolean isOutlineCode() {
     return false;
   }
 
+
   /** Estimate the number of IR instructions emitted by buildIR(). */
   public int estimatedSizeForInlining() {
     return Integer.MAX_VALUE;
diff --git a/src/main/java/com/android/tools/r8/graph/DexClass.java b/src/main/java/com/android/tools/r8/graph/DexClass.java
index be3a802..82faf34 100644
--- a/src/main/java/com/android/tools/r8/graph/DexClass.java
+++ b/src/main/java/com/android/tools/r8/graph/DexClass.java
@@ -381,6 +381,10 @@
     assert verifyNoDuplicateFields();
   }
 
+  public void clearInstanceFields() {
+    instanceFields = DexEncodedField.EMPTY_ARRAY;
+  }
+
   private boolean verifyCorrectnessOfFieldHolder(DexEncodedField field) {
     assert field.getHolderType() == type
         : "Expected field `"
diff --git a/src/main/java/com/android/tools/r8/graph/DexClassAndField.java b/src/main/java/com/android/tools/r8/graph/DexClassAndField.java
index c0a3125..bba0f82 100644
--- a/src/main/java/com/android/tools/r8/graph/DexClassAndField.java
+++ b/src/main/java/com/android/tools/r8/graph/DexClassAndField.java
@@ -24,6 +24,10 @@
     return getDefinition().getAccessFlags();
   }
 
+  public DexType getType() {
+    return getReference().getType();
+  }
+
   public boolean isProgramField() {
     return false;
   }
diff --git a/src/main/java/com/android/tools/r8/graph/DexDebugEvent.java b/src/main/java/com/android/tools/r8/graph/DexDebugEvent.java
index 9dba226..b213660 100644
--- a/src/main/java/com/android/tools/r8/graph/DexDebugEvent.java
+++ b/src/main/java/com/android/tools/r8/graph/DexDebugEvent.java
@@ -72,7 +72,8 @@
     internalAcceptHashing(visitor);
   }
 
-  public abstract void writeOn(DebugBytecodeWriter writer, ObjectToOffsetMapping mapping);
+  public abstract void writeOn(
+      DebugBytecodeWriter writer, ObjectToOffsetMapping mapping, GraphLens graphLens);
 
   public abstract void accept(DexDebugEventVisitor visitor);
 
@@ -89,7 +90,8 @@
     public final int delta;
 
     @Override
-    public void writeOn(DebugBytecodeWriter writer, ObjectToOffsetMapping mapping) {
+    public void writeOn(
+        DebugBytecodeWriter writer, ObjectToOffsetMapping mapping, GraphLens graphLens) {
       writer.putByte(Constants.DBG_ADVANCE_PC);
       writer.putUleb128(delta);
     }
@@ -138,7 +140,8 @@
     }
 
     @Override
-    public void writeOn(DebugBytecodeWriter writer, ObjectToOffsetMapping mapping) {
+    public void writeOn(
+        DebugBytecodeWriter writer, ObjectToOffsetMapping mapping, GraphLens graphLens) {
       writer.putByte(Constants.DBG_SET_PROLOGUE_END);
     }
 
@@ -182,7 +185,8 @@
     }
 
     @Override
-    public void writeOn(DebugBytecodeWriter writer, ObjectToOffsetMapping mapping) {
+    public void writeOn(
+        DebugBytecodeWriter writer, ObjectToOffsetMapping mapping, GraphLens graphLens) {
       writer.putByte(Constants.DBG_SET_EPILOGUE_BEGIN);
     }
 
@@ -227,7 +231,8 @@
     }
 
     @Override
-    public void writeOn(DebugBytecodeWriter writer, ObjectToOffsetMapping mapping) {
+    public void writeOn(
+        DebugBytecodeWriter writer, ObjectToOffsetMapping mapping, GraphLens graphLens) {
       writer.putByte(Constants.DBG_ADVANCE_LINE);
       writer.putSleb128(delta);
     }
@@ -294,13 +299,14 @@
     }
 
     @Override
-    public void writeOn(DebugBytecodeWriter writer, ObjectToOffsetMapping mapping) {
+    public void writeOn(
+        DebugBytecodeWriter writer, ObjectToOffsetMapping mapping, GraphLens graphLens) {
       writer.putByte(signature == null
           ? Constants.DBG_START_LOCAL
           : Constants.DBG_START_LOCAL_EXTENDED);
       writer.putUleb128(registerNum);
       writer.putString(name);
-      writer.putType(type);
+      writer.putType(graphLens.lookupType(type));
       if (signature != null) {
         writer.putString(signature);
       }
@@ -364,7 +370,8 @@
     }
 
     @Override
-    public void writeOn(DebugBytecodeWriter writer, ObjectToOffsetMapping mapping) {
+    public void writeOn(
+        DebugBytecodeWriter writer, ObjectToOffsetMapping mapping, GraphLens graphLens) {
       writer.putByte(Constants.DBG_END_LOCAL);
       writer.putUleb128(registerNum);
     }
@@ -410,7 +417,8 @@
     }
 
     @Override
-    public void writeOn(DebugBytecodeWriter writer, ObjectToOffsetMapping mapping) {
+    public void writeOn(
+        DebugBytecodeWriter writer, ObjectToOffsetMapping mapping, GraphLens graphLens) {
       writer.putByte(Constants.DBG_RESTART_LOCAL);
       writer.putUleb128(registerNum);
     }
@@ -456,7 +464,8 @@
     }
 
     @Override
-    public void writeOn(DebugBytecodeWriter writer, ObjectToOffsetMapping mapping) {
+    public void writeOn(
+        DebugBytecodeWriter writer, ObjectToOffsetMapping mapping, GraphLens graphLens) {
       writer.putByte(Constants.DBG_SET_FILE);
       writer.putString(fileName);
     }
@@ -514,7 +523,8 @@
     }
 
     @Override
-    public void writeOn(DebugBytecodeWriter writer, ObjectToOffsetMapping mapping) {
+    public void writeOn(
+        DebugBytecodeWriter writer, ObjectToOffsetMapping mapping, GraphLens graphLens) {
       // CallerPosition will not be written.
     }
 
@@ -574,7 +584,8 @@
     }
 
     @Override
-    public void writeOn(DebugBytecodeWriter writer, ObjectToOffsetMapping mapping) {
+    public void writeOn(
+        DebugBytecodeWriter writer, ObjectToOffsetMapping mapping, GraphLens graphLens) {
       writer.putByte(value);
     }
 
diff --git a/src/main/java/com/android/tools/r8/graph/DexEncodedField.java b/src/main/java/com/android/tools/r8/graph/DexEncodedField.java
index 6839371..fc0eeb0 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedField.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedField.java
@@ -158,6 +158,10 @@
     return field;
   }
 
+  public DexType getType() {
+    return getReference().getType();
+  }
+
   @Override
   public boolean isDexEncodedField() {
     return true;
@@ -216,6 +220,10 @@
     return isStatic();
   }
 
+  public boolean isSynthetic() {
+    return accessFlags.isSynthetic();
+  }
+
   public boolean isVolatile() {
     return accessFlags.isVolatile();
   }
diff --git a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
index fb1ad730..4acd029 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
@@ -207,6 +207,16 @@
     return accessFlags;
   }
 
+  public DexType getArgumentType(int argumentIndex) {
+    if (isStatic()) {
+      return getReference().getParameter(argumentIndex);
+    }
+    if (argumentIndex == 0) {
+      return getHolderType();
+    }
+    return getReference().getParameter(argumentIndex - 1);
+  }
+
   public CompilationState getCompilationState() {
     return compilationState;
   }
@@ -605,6 +615,10 @@
     return (accessFlags.isPrivate() || accessFlags.isConstructor()) && !accessFlags.isStatic();
   }
 
+  public boolean isInstance() {
+    return !isStatic();
+  }
+
   @Override
   public boolean isStatic() {
     checkIfObsolete();
@@ -1553,7 +1567,10 @@
       annotations = from.annotations();
       code = from.code;
       compilationState = CompilationState.NOT_PROCESSED;
-      optimizationInfo = from.optimizationInfo.mutableCopy();
+      optimizationInfo =
+          from.optimizationInfo.isDefaultMethodOptimizationInfo()
+              ? DefaultMethodOptimizationInfo.getInstance()
+              : from.optimizationInfo.mutableCopy();
       kotlinMemberInfo = from.kotlinMemberInfo;
       classFileVersion = from.classFileVersion;
       this.d8R8Synthesized = d8R8Synthesized;
@@ -1568,6 +1585,13 @@
       }
     }
 
+    public Builder fixupOptimizationInfo(Consumer<UpdatableMethodOptimizationInfo> consumer) {
+      if (optimizationInfo.isUpdatableMethodOptimizationInfo()) {
+        consumer.accept(optimizationInfo.asUpdatableMethodOptimizationInfo());
+      }
+      return this;
+    }
+
     public void setAccessFlags(MethodAccessFlags accessFlags) {
       this.accessFlags = accessFlags.copy();
     }
diff --git a/src/main/java/com/android/tools/r8/graph/DexField.java b/src/main/java/com/android/tools/r8/graph/DexField.java
index 70bb89b..e9c2fc5 100644
--- a/src/main/java/com/android/tools/r8/graph/DexField.java
+++ b/src/main/java/com/android/tools/r8/graph/DexField.java
@@ -57,6 +57,11 @@
   }
 
   @Override
+  public <T> T apply(Function<DexField, T> fieldConsumer, Function<DexMethod, T> methodConsumer) {
+    return fieldConsumer.apply(this);
+  }
+
+  @Override
   public <T> T apply(
       Function<DexType, T> classConsumer,
       Function<DexField, T> fieldConsumer,
@@ -170,6 +175,10 @@
     return dexItemFactory.createField(holder, type, name);
   }
 
+  public DexField withType(DexType type, DexItemFactory dexItemFactory) {
+    return dexItemFactory.createField(holder, type, name);
+  }
+
   public FieldReference asFieldReference() {
     return Reference.field(
         Reference.classFromDescriptor(holder.toDescriptorString()),
diff --git a/src/main/java/com/android/tools/r8/graph/DexMember.java b/src/main/java/com/android/tools/r8/graph/DexMember.java
index 790fd33..1c02f3b 100644
--- a/src/main/java/com/android/tools/r8/graph/DexMember.java
+++ b/src/main/java/com/android/tools/r8/graph/DexMember.java
@@ -4,6 +4,7 @@
 package com.android.tools.r8.graph;
 
 import com.google.common.collect.Iterables;
+import java.util.function.Function;
 
 public abstract class DexMember<D extends DexEncodedMember<D, R>, R extends DexMember<D, R>>
     extends DexReference implements NamingLensComparable<R> {
@@ -18,6 +19,9 @@
     this.name = name;
   }
 
+  public abstract <T> T apply(
+      Function<DexField, T> fieldConsumer, Function<DexMethod, T> methodConsumer);
+
   public abstract DexEncodedMember<?, ?> lookupOnClass(DexClass clazz);
 
   public abstract ProgramMember<?, ?> lookupOnProgramClass(DexProgramClass clazz);
diff --git a/src/main/java/com/android/tools/r8/graph/DexMethod.java b/src/main/java/com/android/tools/r8/graph/DexMethod.java
index 8ab4935..e00c1fe 100644
--- a/src/main/java/com/android/tools/r8/graph/DexMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/DexMethod.java
@@ -67,6 +67,11 @@
   }
 
   @Override
+  public <T> T apply(Function<DexField, T> fieldConsumer, Function<DexMethod, T> methodConsumer) {
+    return methodConsumer.apply(this);
+  }
+
+  @Override
   public <T> T apply(
       Function<DexType, T> classConsumer,
       Function<DexField, T> fieldConsumer,
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 71d89ef..2a6ebf5 100644
--- a/src/main/java/com/android/tools/r8/graph/DexProgramClass.java
+++ b/src/main/java/com/android/tools/r8/graph/DexProgramClass.java
@@ -144,16 +144,21 @@
     synthesizedDirectlyFrom.forEach(this::addSynthesizedFrom);
   }
 
-  public void forEachProgramField(Consumer<ProgramField> consumer) {
+  public void forEachProgramField(Consumer<? super ProgramField> consumer) {
     forEachField(field -> consumer.accept(new ProgramField(this, field)));
   }
 
-  public void forEachProgramMethod(Consumer<ProgramMethod> consumer) {
+  public void forEachProgramMember(Consumer<? super ProgramMember<?, ?>> consumer) {
+    forEachProgramField(consumer);
+    forEachProgramMethod(consumer);
+  }
+
+  public void forEachProgramMethod(Consumer<? super ProgramMethod> consumer) {
     forEachProgramMethodMatching(alwaysTrue(), consumer);
   }
 
   public void forEachProgramMethodMatching(
-      Predicate<DexEncodedMethod> predicate, Consumer<ProgramMethod> consumer) {
+      Predicate<DexEncodedMethod> predicate, Consumer<? super ProgramMethod> consumer) {
     methodCollection.forEachMethodMatching(
         predicate, method -> consumer.accept(new ProgramMethod(this, method)));
   }
@@ -180,6 +185,16 @@
         predicate, method -> consumer.accept(new ProgramMethod(this, method)));
   }
 
+  public void forEachProgramInstanceInitializer(Consumer<ProgramMethod> consumer) {
+    forEachProgramInstanceInitializerMatching(alwaysTrue(), consumer);
+  }
+
+  public void forEachProgramInstanceInitializerMatching(
+      Predicate<DexEncodedMethod> predicate, Consumer<ProgramMethod> consumer) {
+    forEachProgramDirectMethodMatching(
+        method -> method.isInstanceInitializer() && predicate.test(method), consumer);
+  }
+
   public void forEachProgramVirtualMethod(Consumer<ProgramMethod> consumer) {
     forEachProgramVirtualMethodMatching(alwaysTrue(), consumer);
   }
diff --git a/src/main/java/com/android/tools/r8/graph/DexType.java b/src/main/java/com/android/tools/r8/graph/DexType.java
index 249439c..376abd6 100644
--- a/src/main/java/com/android/tools/r8/graph/DexType.java
+++ b/src/main/java/com/android/tools/r8/graph/DexType.java
@@ -115,18 +115,21 @@
   }
 
   public boolean isAlwaysNull(AppView<AppInfoWithLiveness> appView) {
-    if (isClassType()) {
-      DexProgramClass clazz = asProgramClassOrNull(appView.definitionFor(this));
-      if (clazz == null) {
-        return false;
-      }
-      if (appView.options().enableUninstantiatedTypeOptimizationForInterfaces) {
-        return !appView.appInfo().isInstantiatedDirectlyOrIndirectly(clazz);
-      } else {
-        return !clazz.isInterface() && !appView.appInfo().isInstantiatedDirectlyOrIndirectly(clazz);
-      }
+    return isAlwaysNull(appView.appInfo());
+  }
+
+  public boolean isAlwaysNull(AppInfoWithLiveness appInfo) {
+    if (!isClassType()) {
+      return false;
     }
-    return false;
+    DexProgramClass clazz = asProgramClassOrNull(appInfo.definitionFor(this));
+    if (clazz == null) {
+      return false;
+    }
+    if (appInfo.options().enableUninstantiatedTypeOptimizationForInterfaces) {
+      return !appInfo.isInstantiatedDirectlyOrIndirectly(clazz);
+    }
+    return !clazz.isInterface() && !appInfo.isInstantiatedDirectlyOrIndirectly(clazz);
   }
 
   public boolean isSamePackage(DexType other) {
diff --git a/src/main/java/com/android/tools/r8/graph/GenericSignatureEnqueuerAnalysis.java b/src/main/java/com/android/tools/r8/graph/GenericSignatureEnqueuerAnalysis.java
index b01445d..a7e9927 100644
--- a/src/main/java/com/android/tools/r8/graph/GenericSignatureEnqueuerAnalysis.java
+++ b/src/main/java/com/android/tools/r8/graph/GenericSignatureEnqueuerAnalysis.java
@@ -12,38 +12,45 @@
 import com.android.tools.r8.graph.GenericSignature.ReturnType;
 import com.android.tools.r8.graph.GenericSignature.TypeSignature;
 import com.android.tools.r8.graph.analysis.EnqueuerAnalysis;
+import com.android.tools.r8.shaking.Enqueuer.EnqueuerDefinitionSupplier;
 import com.android.tools.r8.shaking.EnqueuerWorklist;
 import java.util.List;
 
 public class GenericSignatureEnqueuerAnalysis extends EnqueuerAnalysis {
 
-  private final GenericSignatureVisitor visitor;
+  private final EnqueuerDefinitionSupplier enqueuerDefinitionSupplier;
 
-  public GenericSignatureEnqueuerAnalysis(DexDefinitionSupplier definitionSupplier) {
-    visitor = new GenericSignatureTypeVisitor(definitionSupplier);
+  public GenericSignatureEnqueuerAnalysis(EnqueuerDefinitionSupplier enqueuerDefinitionSupplier) {
+    this.enqueuerDefinitionSupplier = enqueuerDefinitionSupplier;
   }
 
   @Override
   public void processNewlyLiveClass(DexProgramClass clazz, EnqueuerWorklist worklist) {
-    visitor.visitClassSignature(clazz.getClassSignature());
+    new GenericSignatureTypeVisitor(clazz, enqueuerDefinitionSupplier)
+        .visitClassSignature(clazz.getClassSignature());
   }
 
   @Override
-  public void processNewlyLiveField(ProgramField field) {
-    visitor.visitFieldTypeSignature(field.getDefinition().getGenericSignature());
+  public void processNewlyLiveField(ProgramField field, ProgramDefinition context) {
+    new GenericSignatureTypeVisitor(context, enqueuerDefinitionSupplier)
+        .visitFieldTypeSignature(field.getDefinition().getGenericSignature());
   }
 
   @Override
-  public void processNewlyLiveMethod(ProgramMethod method) {
-    visitor.visitMethodSignature(method.getDefinition().getGenericSignature());
+  public void processNewlyLiveMethod(ProgramMethod method, ProgramDefinition context) {
+    new GenericSignatureTypeVisitor(context, enqueuerDefinitionSupplier)
+        .visitMethodSignature(method.getDefinition().getGenericSignature());
   }
 
   private static class GenericSignatureTypeVisitor implements GenericSignatureVisitor {
 
-    private final DexDefinitionSupplier definitionSupplier;
+    private final ProgramDefinition context;
+    private final EnqueuerDefinitionSupplier enqueuerDefinitionSupplier;
 
-    private GenericSignatureTypeVisitor(DexDefinitionSupplier definitionSupplier) {
-      this.definitionSupplier = definitionSupplier;
+    private GenericSignatureTypeVisitor(
+        ProgramDefinition context, EnqueuerDefinitionSupplier enqueuerDefinitionSupplier) {
+      this.context = context;
+      this.enqueuerDefinitionSupplier = enqueuerDefinitionSupplier;
     }
 
     @Override
@@ -82,7 +89,7 @@
     }
 
     private void visitClassTypeSignature(ClassTypeSignature classTypeSignature) {
-      definitionSupplier.definitionFor(classTypeSignature.type);
+      enqueuerDefinitionSupplier.definitionFor(classTypeSignature.type, context);
       classTypeSignature.visit(this);
     }
 
diff --git a/src/main/java/com/android/tools/r8/graph/ObjectAllocationInfoCollectionImpl.java b/src/main/java/com/android/tools/r8/graph/ObjectAllocationInfoCollectionImpl.java
index e88d379..c0cc708 100644
--- a/src/main/java/com/android/tools/r8/graph/ObjectAllocationInfoCollectionImpl.java
+++ b/src/main/java/com/android/tools/r8/graph/ObjectAllocationInfoCollectionImpl.java
@@ -10,6 +10,7 @@
 import com.android.tools.r8.shaking.GraphReporter;
 import com.android.tools.r8.shaking.InstantiationReason;
 import com.android.tools.r8.shaking.KeepReason;
+import com.android.tools.r8.shaking.MissingClasses;
 import com.android.tools.r8.utils.LensUtils;
 import com.android.tools.r8.utils.TraversalContinuation;
 import com.android.tools.r8.utils.WorkList;
@@ -233,7 +234,7 @@
   }
 
   public boolean verifyAllocatedTypesAreLive(
-      Set<DexType> liveTypes, Set<DexType> missingTypes, DexDefinitionSupplier definitions) {
+      Set<DexType> liveTypes, MissingClasses missingClasses, DexDefinitionSupplier definitions) {
     for (DexProgramClass clazz : classesWithAllocationSiteTracking.keySet()) {
       assert liveTypes.contains(clazz.getType());
     }
@@ -244,7 +245,7 @@
       assert liveTypes.contains(iface.getType());
     }
     for (DexType iface : instantiatedLambdas.keySet()) {
-      assert missingTypes.contains(iface)
+      assert missingClasses.contains(iface)
           || definitions.definitionFor(iface).isNotProgramClass()
           || liveTypes.contains(iface);
     }
diff --git a/src/main/java/com/android/tools/r8/graph/ProgramDefinition.java b/src/main/java/com/android/tools/r8/graph/ProgramDefinition.java
index 6c5ff48..c55ad6f 100644
--- a/src/main/java/com/android/tools/r8/graph/ProgramDefinition.java
+++ b/src/main/java/com/android/tools/r8/graph/ProgramDefinition.java
@@ -36,6 +36,14 @@
     return null;
   }
 
+  default boolean isProgramMember() {
+    return false;
+  }
+
+  default ProgramMember<?, ?> asProgramMember() {
+    return null;
+  }
+
   default boolean isProgramMethod() {
     return false;
   }
diff --git a/src/main/java/com/android/tools/r8/graph/ProgramField.java b/src/main/java/com/android/tools/r8/graph/ProgramField.java
index 7721c67..866d230 100644
--- a/src/main/java/com/android/tools/r8/graph/ProgramField.java
+++ b/src/main/java/com/android/tools/r8/graph/ProgramField.java
@@ -37,6 +37,16 @@
   }
 
   @Override
+  public boolean isProgramMember() {
+    return true;
+  }
+
+  @Override
+  public ProgramField asProgramMember() {
+    return this;
+  }
+
+  @Override
   public DexProgramClass getHolder() {
     DexClass holder = super.getHolder();
     assert holder.isProgramClass();
diff --git a/src/main/java/com/android/tools/r8/graph/ProgramMethod.java b/src/main/java/com/android/tools/r8/graph/ProgramMethod.java
index 0765824..33063ba 100644
--- a/src/main/java/com/android/tools/r8/graph/ProgramMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/ProgramMethod.java
@@ -67,6 +67,16 @@
   }
 
   @Override
+  public boolean isProgramMember() {
+    return true;
+  }
+
+  @Override
+  public ProgramMethod asProgramMember() {
+    return this;
+  }
+
+  @Override
   public boolean isProgramMethod() {
     return true;
   }
diff --git a/src/main/java/com/android/tools/r8/graph/analysis/ClassInitializerAssertionEnablingAnalysis.java b/src/main/java/com/android/tools/r8/graph/analysis/ClassInitializerAssertionEnablingAnalysis.java
index eb82383..37e08d2 100644
--- a/src/main/java/com/android/tools/r8/graph/analysis/ClassInitializerAssertionEnablingAnalysis.java
+++ b/src/main/java/com/android/tools/r8/graph/analysis/ClassInitializerAssertionEnablingAnalysis.java
@@ -18,6 +18,7 @@
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexString;
+import com.android.tools.r8.graph.ProgramDefinition;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.optimize.info.OptimizationFeedback;
 import com.google.common.collect.ImmutableList;
@@ -37,7 +38,7 @@
   }
 
   @Override
-  public void processNewlyLiveMethod(ProgramMethod method) {
+  public void processNewlyLiveMethod(ProgramMethod method, ProgramDefinition context) {
     DexEncodedMethod definition = method.getDefinition();
     if (definition.isClassInitializer()) {
       Code code = definition.getCode();
diff --git a/src/main/java/com/android/tools/r8/graph/analysis/DesugaredLibraryConversionWrapperAnalysis.java b/src/main/java/com/android/tools/r8/graph/analysis/DesugaredLibraryConversionWrapperAnalysis.java
index 1fa68dd..4291896 100644
--- a/src/main/java/com/android/tools/r8/graph/analysis/DesugaredLibraryConversionWrapperAnalysis.java
+++ b/src/main/java/com/android/tools/r8/graph/analysis/DesugaredLibraryConversionWrapperAnalysis.java
@@ -7,6 +7,7 @@
 import com.android.tools.r8.graph.DexClasspathClass;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.ProgramDefinition;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.desugar.DesugaredLibraryAPIConverter;
 import com.android.tools.r8.ir.desugar.DesugaredLibraryAPIConverter.Mode;
@@ -29,7 +30,7 @@
   }
 
   @Override
-  public void processNewlyLiveMethod(ProgramMethod method) {
+  public void processNewlyLiveMethod(ProgramMethod method, ProgramDefinition context) {
     converter.registerCallbackIfRequired(method);
   }
 
diff --git a/src/main/java/com/android/tools/r8/graph/analysis/EnqueuerAnalysis.java b/src/main/java/com/android/tools/r8/graph/analysis/EnqueuerAnalysis.java
index 1a00007..9e09724 100644
--- a/src/main/java/com/android/tools/r8/graph/analysis/EnqueuerAnalysis.java
+++ b/src/main/java/com/android/tools/r8/graph/analysis/EnqueuerAnalysis.java
@@ -5,6 +5,7 @@
 package com.android.tools.r8.graph.analysis;
 
 import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.ProgramDefinition;
 import com.android.tools.r8.graph.ProgramField;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.shaking.Enqueuer;
@@ -20,10 +21,10 @@
   public void processNewlyLiveClass(DexProgramClass clazz, EnqueuerWorklist worklist) {}
 
   /** Called when a field is found to be live. */
-  public void processNewlyLiveField(ProgramField field) {}
+  public void processNewlyLiveField(ProgramField field, ProgramDefinition context) {}
 
   /** Called when a method is found to be live. */
-  public void processNewlyLiveMethod(ProgramMethod method) {}
+  public void processNewlyLiveMethod(ProgramMethod method, ProgramDefinition context) {}
 
   /**
    * Called when the Enqueuer reaches a fixpoint. This may happen multiple times, since each
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/ClassInstanceFieldsMerger.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/ClassInstanceFieldsMerger.java
index 96282ee..23cb57c 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/ClassInstanceFieldsMerger.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/ClassInstanceFieldsMerger.java
@@ -4,16 +4,17 @@
 
 package com.android.tools.r8.horizontalclassmerging;
 
+import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexEncodedField;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.horizontalclassmerging.HorizontalClassMergerGraphLens.Builder;
+import com.android.tools.r8.horizontalclassmerging.policies.SameInstanceFields.InstanceFieldInfo;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.utils.IterableUtils;
-import com.android.tools.r8.utils.ListUtils;
 import com.google.common.collect.Iterables;
 import java.util.ArrayList;
 import java.util.Collection;
-import java.util.IdentityHashMap;
 import java.util.LinkedHashMap;
 import java.util.LinkedList;
 import java.util.List;
@@ -21,12 +22,19 @@
 
 public class ClassInstanceFieldsMerger {
 
-  // Map from target class field to all fields which should be merged into that field.
-  private final Map<DexEncodedField, List<DexEncodedField>> fieldMappings = new LinkedHashMap<>();
+  private final AppView<AppInfoWithLiveness> appView;
   private final Builder lensBuilder;
 
+  private DexEncodedField classIdField;
+
+  // Map from target class field to all fields which should be merged into that field.
+  private final Map<DexEncodedField, List<DexEncodedField>> fieldMappings = new LinkedHashMap<>();
+
   public ClassInstanceFieldsMerger(
-      HorizontalClassMergerGraphLens.Builder lensBuilder, MergeGroup group) {
+      AppView<AppInfoWithLiveness> appView,
+      HorizontalClassMergerGraphLens.Builder lensBuilder,
+      MergeGroup group) {
+    this.appView = appView;
     this.lensBuilder = lensBuilder;
     group
         .getTarget()
@@ -34,39 +42,122 @@
         .forEach(field -> fieldMappings.computeIfAbsent(field, ignore -> new ArrayList<>()));
   }
 
+  /**
+   * Adds all fields from {@param clazz} to the class merger. For each field, we must choose which
+   * field on the target class to merge into.
+   *
+   * <p>A field that stores a reference type can be merged into a field that stores a different
+   * reference type. To avoid that we change fields that store a reference type to have type
+   * java.lang.Object when it is not needed (e.g., class Foo has fields 'A a' and 'B b' and class
+   * Bar has fields 'A b' and 'B a'), we make a prepass that matches fields with the same reference
+   * type.
+   */
   public void addFields(DexProgramClass clazz) {
-    Map<DexType, List<DexEncodedField>> availableFields = new IdentityHashMap<>();
-    for (DexEncodedField field : fieldMappings.keySet()) {
-      availableFields.computeIfAbsent(field.type(), ignore -> new LinkedList<>()).add(field);
+    Map<InstanceFieldInfo, LinkedList<DexEncodedField>> availableFieldsByExactInfo =
+        getAvailableFieldsByExactInfo();
+    List<DexEncodedField> needsMerge = new ArrayList<>();
+
+    // Pass 1: Match fields that have the exact same type.
+    for (DexEncodedField oldField : clazz.instanceFields()) {
+      InstanceFieldInfo info = InstanceFieldInfo.createExact(oldField);
+      LinkedList<DexEncodedField> availableFieldsWithExactSameInfo =
+          availableFieldsByExactInfo.get(info);
+      if (availableFieldsWithExactSameInfo == null || availableFieldsWithExactSameInfo.isEmpty()) {
+        needsMerge.add(oldField);
+      } else {
+        DexEncodedField newField = availableFieldsWithExactSameInfo.removeFirst();
+        fieldMappings.get(newField).add(oldField);
+        if (availableFieldsWithExactSameInfo.isEmpty()) {
+          availableFieldsByExactInfo.remove(info);
+        }
+      }
     }
 
-    for (DexEncodedField oldField : clazz.instanceFields()) {
+    // Pass 2: Match fields that do not have the same reference type.
+    Map<InstanceFieldInfo, LinkedList<DexEncodedField>> availableFieldsByRelaxedInfo =
+        getAvailableFieldsByRelaxedInfo(availableFieldsByExactInfo);
+    for (DexEncodedField oldField : needsMerge) {
+      assert oldField.getType().isReferenceType();
       DexEncodedField newField =
-          ListUtils.removeFirstMatch(
-                  availableFields.get(oldField.type()),
-                  field -> field.getAccessFlags().isSameVisibility(oldField.getAccessFlags()))
-              .get();
+          availableFieldsByRelaxedInfo
+              .get(InstanceFieldInfo.createRelaxed(oldField, appView.dexItemFactory()))
+              .removeFirst();
       assert newField != null;
+      assert newField.getType().isReferenceType();
       fieldMappings.get(newField).add(oldField);
     }
   }
 
+  private Map<InstanceFieldInfo, LinkedList<DexEncodedField>> getAvailableFieldsByExactInfo() {
+    Map<InstanceFieldInfo, LinkedList<DexEncodedField>> availableFieldsByInfo =
+        new LinkedHashMap<>();
+    for (DexEncodedField field : fieldMappings.keySet()) {
+      availableFieldsByInfo
+          .computeIfAbsent(InstanceFieldInfo.createExact(field), ignore -> new LinkedList<>())
+          .add(field);
+    }
+    return availableFieldsByInfo;
+  }
+
+  private Map<InstanceFieldInfo, LinkedList<DexEncodedField>> getAvailableFieldsByRelaxedInfo(
+      Map<InstanceFieldInfo, LinkedList<DexEncodedField>> availableFieldsByExactInfo) {
+    Map<InstanceFieldInfo, LinkedList<DexEncodedField>> availableFieldsByRelaxedInfo =
+        new LinkedHashMap<>();
+    availableFieldsByExactInfo.forEach(
+        (info, fields) ->
+            availableFieldsByRelaxedInfo
+                .computeIfAbsent(
+                    info.toInfoWithRelaxedType(appView.dexItemFactory()),
+                    ignore -> new LinkedList<>())
+                .addAll(fields));
+    return availableFieldsByRelaxedInfo;
+  }
+
   private void fixAccessFlags(DexEncodedField newField, Collection<DexEncodedField> oldFields) {
+    if (newField.isSynthetic() && Iterables.any(oldFields, oldField -> !oldField.isSynthetic())) {
+      newField.getAccessFlags().demoteFromSynthetic();
+    }
     if (newField.isFinal() && Iterables.any(oldFields, oldField -> !oldField.isFinal())) {
       newField.getAccessFlags().demoteFromFinal();
     }
   }
 
-  private void mergeFields(DexEncodedField newField, List<DexEncodedField> oldFields) {
-    fixAccessFlags(newField, oldFields);
-    lensBuilder.recordNewFieldSignature(
-        Iterables.transform(
-            IterableUtils.append(oldFields, newField), DexEncodedField::getReference),
-        newField.getReference(),
-        newField.getReference());
+  public void setClassIdField(DexEncodedField classIdField) {
+    this.classIdField = classIdField;
   }
 
-  public void merge() {
-    fieldMappings.forEach(this::mergeFields);
+  public DexEncodedField[] merge() {
+    List<DexEncodedField> newFields = new ArrayList<>();
+    assert classIdField != null;
+    newFields.add(classIdField);
+    fieldMappings.forEach(
+        (targetField, oldFields) ->
+            newFields.add(mergeSourceFieldsToTargetField(targetField, oldFields)));
+    return newFields.toArray(DexEncodedField.EMPTY_ARRAY);
+  }
+
+  private DexEncodedField mergeSourceFieldsToTargetField(
+      DexEncodedField targetField, List<DexEncodedField> oldFields) {
+    fixAccessFlags(targetField, oldFields);
+
+    DexEncodedField newField;
+    DexType targetFieldType = targetField.type();
+    if (!Iterables.all(oldFields, oldField -> oldField.getType() == targetFieldType)) {
+      newField =
+          targetField.toTypeSubstitutedField(
+              targetField
+                  .getReference()
+                  .withType(appView.dexItemFactory().objectType, appView.dexItemFactory()));
+    } else {
+      newField = targetField;
+    }
+
+    lensBuilder.recordNewFieldSignature(
+        Iterables.transform(
+            IterableUtils.append(oldFields, targetField), DexEncodedField::getReference),
+        newField.getReference(),
+        targetField.getReference());
+
+    return newField;
   }
 }
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/ClassMerger.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/ClassMerger.java
index 950cf9a..0a6a9b9 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/ClassMerger.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/ClassMerger.java
@@ -11,6 +11,7 @@
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.CfCode;
 import com.android.tools.r8.graph.DexAnnotationSet;
+import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexEncodedField;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexItemFactory;
@@ -26,7 +27,7 @@
 import com.android.tools.r8.graph.ParameterAnnotationsList;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
-import com.android.tools.r8.shaking.FieldAccessInfoCollectionModifier;
+import com.android.tools.r8.utils.IterableUtils;
 import com.android.tools.r8.utils.MethodSignatureEquivalence;
 import com.google.common.base.Equivalence.Wrapper;
 import com.google.common.collect.Iterables;
@@ -57,7 +58,6 @@
   private final ClassInitializerSynthesizedCode classInitializerSynthesizedCode;
   private final HorizontalClassMergerGraphLens.Builder lensBuilder;
   private final HorizontallyMergedClasses.Builder mergedClassesBuilder;
-  private final FieldAccessInfoCollectionModifier.Builder fieldAccessChangesBuilder;
 
   private final ClassMethodsBuilder classMethodsBuilder = new ClassMethodsBuilder();
   private final Reference2IntMap<DexType> classIdentifiers = new Reference2IntOpenHashMap<>();
@@ -70,7 +70,6 @@
       AppView<AppInfoWithLiveness> appView,
       HorizontalClassMergerGraphLens.Builder lensBuilder,
       HorizontallyMergedClasses.Builder mergedClassesBuilder,
-      FieldAccessInfoCollectionModifier.Builder fieldAccessChangesBuilder,
       MergeGroup group,
       Collection<VirtualMethodMerger> virtualMethodMergers,
       Collection<ConstructorMerger> constructorMergers,
@@ -78,7 +77,6 @@
     this.appView = appView;
     this.lensBuilder = lensBuilder;
     this.mergedClassesBuilder = mergedClassesBuilder;
-    this.fieldAccessChangesBuilder = fieldAccessChangesBuilder;
     this.group = group;
     this.virtualMethodMergers = virtualMethodMergers;
     this.constructorMergers = constructorMergers;
@@ -86,7 +84,7 @@
     this.dexItemFactory = appView.dexItemFactory();
     this.classInitializerSynthesizedCode = classInitializerSynthesizedCode;
     this.classStaticFieldsMerger = new ClassStaticFieldsMerger(appView, lensBuilder, group);
-    this.classInstanceFieldsMerger = new ClassInstanceFieldsMerger(lensBuilder, group);
+    this.classInstanceFieldsMerger = new ClassInstanceFieldsMerger(appView, lensBuilder, group);
 
     buildClassIdentifierMap();
   }
@@ -185,29 +183,25 @@
             merger.merge(
                 classMethodsBuilder,
                 lensBuilder,
-                fieldAccessChangesBuilder,
                 classIdentifiers,
                 syntheticArgumentClass));
   }
 
   void mergeVirtualMethods() {
     virtualMethodMergers.forEach(
-        merger ->
-            merger.merge(
-                classMethodsBuilder, lensBuilder, fieldAccessChangesBuilder, classIdentifiers));
+        merger -> merger.merge(classMethodsBuilder, lensBuilder, classIdentifiers));
     group.forEachSource(clazz -> clazz.getMethodCollection().clearVirtualMethods());
   }
 
   void appendClassIdField() {
-    DexEncodedField encodedField =
+    classInstanceFieldsMerger.setClassIdField(
         new DexEncodedField(
             group.getClassIdField(),
             FieldAccessFlags.fromSharedAccessFlags(
                 Constants.ACC_PUBLIC + Constants.ACC_FINAL + Constants.ACC_SYNTHETIC),
             FieldTypeSignature.noSignature(),
             DexAnnotationSet.empty(),
-            null);
-    group.getTarget().appendInstanceField(encodedField);
+            null));
   }
 
   void mergeStaticFields() {
@@ -238,9 +232,9 @@
     group.forEachSource(
         clazz -> {
           classInstanceFieldsMerger.addFields(clazz);
-          clazz.setInstanceFields(null);
+          clazz.clearInstanceFields();
         });
-    classInstanceFieldsMerger.merge();
+    group.getTarget().setInstanceFields(classInstanceFieldsMerger.merge());
   }
 
   public void mergeGroup(SyntheticArgumentClass syntheticArgumentClass) {
@@ -277,7 +271,8 @@
 
     private Builder setup() {
       DexItemFactory dexItemFactory = appView.dexItemFactory();
-      DexProgramClass target = group.iterator().next();
+      DexProgramClass target =
+          IterableUtils.findOrDefault(group, DexClass::isPublic, group.iterator().next());
       // TODO(b/165498187): ensure the name for the field is fresh
       group.setClassIdField(
           dexItemFactory.createField(
@@ -327,8 +322,7 @@
 
     public ClassMerger build(
         HorizontallyMergedClasses.Builder mergedClassesBuilder,
-        HorizontalClassMergerGraphLens.Builder lensBuilder,
-        FieldAccessInfoCollectionModifier.Builder fieldAccessChangesBuilder) {
+        HorizontalClassMergerGraphLens.Builder lensBuilder) {
       setup();
       List<VirtualMethodMerger> virtualMethodMergers =
           new ArrayList<>(virtualMethodMergerBuilders.size());
@@ -354,7 +348,6 @@
           appView,
           lensBuilder,
           mergedClassesBuilder,
-          fieldAccessChangesBuilder,
           group,
           virtualMethodMergers,
           constructorMergers,
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/ConstructorEntryPointSynthesizedCode.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/ConstructorEntryPointSynthesizedCode.java
index 7d250bb..bbce62b 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/ConstructorEntryPointSynthesizedCode.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/ConstructorEntryPointSynthesizedCode.java
@@ -45,4 +45,9 @@
       registry.registerInvokeDirect(typeConstructor);
     }
   }
+
+  @Override
+  public boolean isHorizontalClassMergingCode() {
+    return true;
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/ConstructorMerger.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/ConstructorMerger.java
index 01d5771..b4e9887 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/ConstructorMerger.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/ConstructorMerger.java
@@ -21,7 +21,6 @@
 import com.android.tools.r8.ir.conversion.ExtraParameter;
 import com.android.tools.r8.ir.conversion.ExtraUnusedNullParameter;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
-import com.android.tools.r8.shaking.FieldAccessInfoCollectionModifier;
 import com.android.tools.r8.utils.ListUtils;
 import com.android.tools.r8.utils.structural.Ordered;
 import it.unimi.dsi.fastutil.ints.Int2ReferenceAVLTreeMap;
@@ -139,7 +138,6 @@
   void merge(
       ClassMethodsBuilder classMethodsBuilder,
       HorizontalClassMergerGraphLens.Builder lensBuilder,
-      FieldAccessInfoCollectionModifier.Builder fieldAccessChangesBuilder,
       Reference2IntMap<DexType> classIdentifiers,
       SyntheticArgumentClass syntheticArgumentClass) {
     // Tree map as must be sorted.
@@ -216,8 +214,5 @@
     lensBuilder.recordNewMethodSignature(bridgeConstructorReference, newConstructorReference);
 
     classMethodsBuilder.addDirectMethod(newConstructor);
-
-    fieldAccessChangesBuilder.fieldWrittenByMethod(
-        group.getClassIdField(), newConstructorReference);
   }
 }
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/FieldMultiset.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/FieldMultiset.java
deleted file mode 100644
index 6cbffcc..0000000
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/FieldMultiset.java
+++ /dev/null
@@ -1,77 +0,0 @@
-// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-
-package com.android.tools.r8.horizontalclassmerging;
-
-import com.android.tools.r8.graph.AccessFlags;
-import com.android.tools.r8.graph.DexEncodedField;
-import com.android.tools.r8.graph.DexProgramClass;
-import com.android.tools.r8.graph.DexType;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.Objects;
-
-public class FieldMultiset {
-
-  private static class VisibilitySignature {
-    private int publicVisible = 0;
-    private int protectedVisible = 0;
-    private int privateVisible = 0;
-    private int packagePrivateVisible = 0;
-
-    public void addAccessModifier(AccessFlags accessFlags) {
-      if (accessFlags.isPublic()) {
-        publicVisible++;
-      } else if (accessFlags.isPrivate()) {
-        privateVisible++;
-      } else if (accessFlags.isPackagePrivate()) {
-        packagePrivateVisible++;
-      } else if (accessFlags.isProtected()) {
-        protectedVisible++;
-      }
-    }
-
-    @Override
-    public boolean equals(Object o) {
-      if (this == o) return true;
-      if (o == null || getClass() != o.getClass()) return false;
-      VisibilitySignature that = (VisibilitySignature) o;
-      return publicVisible == that.publicVisible
-          && protectedVisible == that.protectedVisible
-          && privateVisible == that.privateVisible
-          && packagePrivateVisible == that.packagePrivateVisible;
-    }
-
-    @Override
-    public int hashCode() {
-      return Objects.hash(publicVisible, protectedVisible, privateVisible, packagePrivateVisible);
-    }
-  }
-
-  // This *must* not be an IdentityHashMap, because hash equality does not work for the values.
-  private final Map<DexType, VisibilitySignature> fields = new HashMap<>();
-
-  public FieldMultiset(DexProgramClass clazz) {
-    for (DexEncodedField field : clazz.instanceFields()) {
-      fields
-          .computeIfAbsent(field.type(), ignore -> new VisibilitySignature())
-          .addAccessModifier(field.getAccessFlags());
-    }
-  }
-
-  @Override
-  public int hashCode() {
-    return fields.hashCode();
-  }
-
-  @Override
-  public boolean equals(Object object) {
-    if (object instanceof FieldMultiset) {
-      FieldMultiset other = (FieldMultiset) object;
-      return fields.equals(other.fields);
-    } else {
-      return false;
-    }
-  }
-}
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMerger.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMerger.java
index 5983618..495be54 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMerger.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMerger.java
@@ -10,10 +10,10 @@
 import com.android.tools.r8.horizontalclassmerging.policies.AllInstantiatedOrUninstantiated;
 import com.android.tools.r8.horizontalclassmerging.policies.CheckAbstractClasses;
 import com.android.tools.r8.horizontalclassmerging.policies.DontInlinePolicy;
-import com.android.tools.r8.horizontalclassmerging.policies.DontMergeIntoLessVisible;
 import com.android.tools.r8.horizontalclassmerging.policies.DontMergeSynchronizedClasses;
 import com.android.tools.r8.horizontalclassmerging.policies.IgnoreSynthetics;
 import com.android.tools.r8.horizontalclassmerging.policies.LimitGroups;
+import com.android.tools.r8.horizontalclassmerging.policies.MinimizeFieldCasts;
 import com.android.tools.r8.horizontalclassmerging.policies.NoAnnotations;
 import com.android.tools.r8.horizontalclassmerging.policies.NoClassInitializerWithObservableSideEffects;
 import com.android.tools.r8.horizontalclassmerging.policies.NoClassesOrMembersWithAnnotations;
@@ -34,11 +34,12 @@
 import com.android.tools.r8.horizontalclassmerging.policies.PreventMethodImplementation;
 import com.android.tools.r8.horizontalclassmerging.policies.RespectPackageBoundaries;
 import com.android.tools.r8.horizontalclassmerging.policies.SameFeatureSplit;
-import com.android.tools.r8.horizontalclassmerging.policies.SameFields;
+import com.android.tools.r8.horizontalclassmerging.policies.SameInstanceFields;
 import com.android.tools.r8.horizontalclassmerging.policies.SameNestHost;
 import com.android.tools.r8.horizontalclassmerging.policies.SameParentClass;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.shaking.FieldAccessInfoCollectionModifier;
+import com.android.tools.r8.shaking.KeepInfoCollection;
 import com.android.tools.r8.shaking.MainDexTracingResult;
 import com.android.tools.r8.shaking.RuntimeTypeCheckInfo;
 import com.google.common.collect.ImmutableList;
@@ -49,6 +50,7 @@
 import java.util.List;
 
 public class HorizontalClassMerger {
+
   private final AppView<AppInfoWithLiveness> appView;
 
   public HorizontalClassMerger(AppView<AppInfoWithLiveness> appView) {
@@ -57,7 +59,7 @@
   }
 
   // TODO(b/165577835): replace Collection<DexProgramClass> with MergeGroup
-  public HorizontalClassMergerGraphLens run(
+  public HorizontalClassMergerResult run(
       DirectMappedDexApplication.Builder appBuilder,
       MainDexTracingResult mainDexTracingResult,
       RuntimeTypeCheckInfo runtimeTypeCheckInfo) {
@@ -78,27 +80,51 @@
         new HorizontallyMergedClasses.Builder();
     HorizontalClassMergerGraphLens.Builder lensBuilder =
         new HorizontalClassMergerGraphLens.Builder();
-    FieldAccessInfoCollectionModifier.Builder fieldAccessChangesBuilder =
-        new FieldAccessInfoCollectionModifier.Builder();
+    MainDexTracingResult.Builder mainDexTracingResultBuilder =
+        mainDexTracingResult.extensionBuilder(appView.appInfo());
 
     // Set up a class merger for each group.
     List<ClassMerger> classMergers =
-        initializeClassMergers(
-            mergedClassesBuilder, lensBuilder, fieldAccessChangesBuilder, groups);
+        initializeClassMergers(mergedClassesBuilder, lensBuilder, groups);
     Iterable<DexProgramClass> allMergeClasses =
         Iterables.concat(
             Iterables.transform(classMergers, classMerger -> classMerger.getGroup().getClasses()));
 
     // Merge the classes.
     SyntheticArgumentClass syntheticArgumentClass =
-        new SyntheticArgumentClass.Builder().build(appView, appBuilder, allMergeClasses);
+        new SyntheticArgumentClass.Builder(
+                appBuilder, appView, mainDexTracingResult, mainDexTracingResultBuilder)
+            .build(allMergeClasses);
     applyClassMergers(classMergers, syntheticArgumentClass);
 
-    // Generate the class lens.
+    // Generate the graph lens.
     HorizontallyMergedClasses mergedClasses = mergedClassesBuilder.build();
     appView.setHorizontallyMergedClasses(mergedClasses);
-    return createLens(
-        mergedClasses, lensBuilder, fieldAccessChangesBuilder, syntheticArgumentClass);
+    HorizontalClassMergerGraphLens lens =
+        createLens(mergedClasses, lensBuilder, syntheticArgumentClass);
+
+    // Prune keep info.
+    KeepInfoCollection keepInfo = appView.appInfo().getKeepInfo();
+    keepInfo.mutate(mutator -> mutator.removeKeepInfoForPrunedItems(mergedClasses.getSources()));
+
+    return new HorizontalClassMergerResult(
+        createFieldAccessInfoCollectionModifier(groups), lens, mainDexTracingResultBuilder.build());
+  }
+
+  private FieldAccessInfoCollectionModifier createFieldAccessInfoCollectionModifier(
+      Collection<MergeGroup> groups) {
+    FieldAccessInfoCollectionModifier.Builder builder =
+        new FieldAccessInfoCollectionModifier.Builder();
+    for (MergeGroup group : groups) {
+      DexProgramClass target = group.getTarget();
+      target.forEachProgramInstanceInitializerMatching(
+          definition -> definition.getCode().isHorizontalClassMergingCode(),
+          method -> builder.recordFieldWrittenInContext(group.getClassIdField(), method));
+      target.forEachProgramVirtualMethodMatching(
+          definition -> definition.hasCode() && definition.getCode().isHorizontalClassMergingCode(),
+          method -> builder.recordFieldReadInContext(group.getClassIdField(), method));
+    }
+    return builder.build();
   }
 
   private List<Policy> getPolicies(
@@ -106,7 +132,7 @@
       RuntimeTypeCheckInfo runtimeTypeCheckInfo) {
     return ImmutableList.of(
         new NotMatchedByNoHorizontalClassMerging(appView),
-        new SameFields(),
+        new SameInstanceFields(appView),
         new NoInterfaces(),
         new NoAnnotations(),
         new NoEnums(appView),
@@ -133,12 +159,8 @@
         new SameFeatureSplit(appView),
         new RespectPackageBoundaries(appView),
         new DontMergeSynchronizedClasses(appView),
-        new LimitGroups(appView),
-        // TODO(b/166577694): no policies should be run after this policy, as it would
-        // potentially break tests
-        new DontMergeIntoLessVisible()
-        // TODO: add policies
-        );
+        new MinimizeFieldCasts(),
+        new LimitGroups(appView));
   }
 
   /**
@@ -148,7 +170,6 @@
   private List<ClassMerger> initializeClassMergers(
       HorizontallyMergedClasses.Builder mergedClassesBuilder,
       HorizontalClassMergerGraphLens.Builder lensBuilder,
-      FieldAccessInfoCollectionModifier.Builder fieldAccessChangesBuilder,
       Collection<MergeGroup> groups) {
     List<ClassMerger> classMergers = new ArrayList<>();
 
@@ -156,8 +177,7 @@
     for (MergeGroup group : groups) {
       assert !group.isEmpty();
       ClassMerger merger =
-          new ClassMerger.Builder(appView, group)
-              .build(mergedClassesBuilder, lensBuilder, fieldAccessChangesBuilder);
+          new ClassMerger.Builder(appView, group).build(mergedClassesBuilder, lensBuilder);
       classMergers.add(merger);
     }
 
@@ -179,17 +199,8 @@
   private HorizontalClassMergerGraphLens createLens(
       HorizontallyMergedClasses mergedClasses,
       HorizontalClassMergerGraphLens.Builder lensBuilder,
-      FieldAccessInfoCollectionModifier.Builder fieldAccessChangesBuilder,
       SyntheticArgumentClass syntheticArgumentClass) {
-
-    HorizontalClassMergerGraphLens lens =
-        new TreeFixer(
-                appView,
-                mergedClasses,
-                lensBuilder,
-                fieldAccessChangesBuilder,
-                syntheticArgumentClass)
-            .fixupTypeReferences();
-    return lens;
+    return new TreeFixer(appView, mergedClasses, lensBuilder, syntheticArgumentClass)
+        .fixupTypeReferences();
   }
 }
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMergerGraphLens.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMergerGraphLens.java
index b42cb24..f5fdd6f 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMergerGraphLens.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMergerGraphLens.java
@@ -72,6 +72,22 @@
         .build();
   }
 
+  @Override
+  protected FieldLookupResult internalDescribeLookupField(FieldLookupResult previous) {
+    FieldLookupResult lookup = super.internalDescribeLookupField(previous);
+    if (lookup.getReference() == previous.getReference()) {
+      return lookup;
+    }
+    return FieldLookupResult.builder(this)
+        .setReference(lookup.getReference())
+        .setReboundReference(lookup.getReboundReference())
+        .setCastType(
+            lookup.getReference().getType() != previous.getReference().getType()
+                ? lookupType(previous.getReference().getType())
+                : null)
+        .build();
+  }
+
   public static class Builder {
 
     private final MutableBidirectionalManyToOneRepresentativeMap<DexField, DexField> fieldMap =
@@ -127,12 +143,10 @@
       if (originalFieldSignatures.isEmpty()) {
         fieldMap.put(oldFieldSignature, newFieldSignature);
       } else if (originalFieldSignatures.size() == 1) {
-        fieldMap.put(originalFieldSignatures.iterator().next(), newFieldSignature);
+        fieldMap.put(originalFieldSignatures, newFieldSignature);
       } else {
         assert representative != null;
-        for (DexField originalFieldSignature : originalFieldSignatures) {
-          fieldMap.put(originalFieldSignature, newFieldSignature);
-        }
+        fieldMap.put(originalFieldSignatures, newFieldSignature);
         fieldMap.setRepresentative(newFieldSignature, representative);
       }
     }
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMergerResult.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMergerResult.java
new file mode 100644
index 0000000..75aafb4
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMergerResult.java
@@ -0,0 +1,36 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.horizontalclassmerging;
+
+import com.android.tools.r8.shaking.FieldAccessInfoCollectionModifier;
+import com.android.tools.r8.shaking.MainDexTracingResult;
+
+public class HorizontalClassMergerResult {
+
+  private final FieldAccessInfoCollectionModifier fieldAccessInfoCollectionModifier;
+  private final HorizontalClassMergerGraphLens graphLens;
+  private final MainDexTracingResult mainDexTracingResult;
+
+  HorizontalClassMergerResult(
+      FieldAccessInfoCollectionModifier fieldAccessInfoCollectionModifier,
+      HorizontalClassMergerGraphLens graphLens,
+      MainDexTracingResult mainDexTracingResult) {
+    this.fieldAccessInfoCollectionModifier = fieldAccessInfoCollectionModifier;
+    this.graphLens = graphLens;
+    this.mainDexTracingResult = mainDexTracingResult;
+  }
+
+  public FieldAccessInfoCollectionModifier getFieldAccessInfoCollectionModifier() {
+    return fieldAccessInfoCollectionModifier;
+  }
+
+  public HorizontalClassMergerGraphLens getGraphLens() {
+    return graphLens;
+  }
+
+  public MainDexTracingResult getMainDexTracingResult() {
+    return mainDexTracingResult;
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/MergeGroup.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/MergeGroup.java
index 02e4684..9e4ebf5 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/MergeGroup.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/MergeGroup.java
@@ -45,6 +45,10 @@
     classes.add(clazz);
   }
 
+  public void add(MergeGroup group) {
+    classes.addAll(group.getClasses());
+  }
+
   public void addAll(Collection<DexProgramClass> classes) {
     this.classes.addAll(classes);
   }
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/SimplePolicyExecutor.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/SimplePolicyExecutor.java
index 14b249a..3b96a76 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/SimplePolicyExecutor.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/SimplePolicyExecutor.java
@@ -72,6 +72,10 @@
 
       policy.clear();
 
+      if (linkedGroups.isEmpty()) {
+        break;
+      }
+
       // Any policy should not return any trivial groups.
       assert linkedGroups.stream().allMatch(group -> group.size() >= 2);
     }
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/SyntheticArgumentClass.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/SyntheticArgumentClass.java
index c3aa3c0..ec64e8a 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/SyntheticArgumentClass.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/SyntheticArgumentClass.java
@@ -17,6 +17,8 @@
 import com.android.tools.r8.graph.GenericSignature.ClassSignature;
 import com.android.tools.r8.origin.SynthesizedOrigin;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.shaking.MainDexTracingResult;
+import com.google.common.collect.Iterables;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
@@ -53,13 +55,27 @@
 
   public static class Builder {
 
-    private DexType synthesizeClass(
-        AppView<AppInfoWithLiveness> appView,
+    private final DirectMappedDexApplication.Builder appBuilder;
+    private final AppView<AppInfoWithLiveness> appView;
+    private final MainDexTracingResult mainDexTracingResult;
+    private final MainDexTracingResult.Builder mainDexTracingResultBuilder;
+
+    Builder(
         DirectMappedDexApplication.Builder appBuilder,
+        AppView<AppInfoWithLiveness> appView,
+        MainDexTracingResult mainDexTracingResult,
+        MainDexTracingResult.Builder mainDexTracingResultBuilder) {
+      this.appBuilder = appBuilder;
+      this.appView = appView;
+      this.mainDexTracingResult = mainDexTracingResult;
+      this.mainDexTracingResultBuilder = mainDexTracingResultBuilder;
+    }
+
+    private DexType synthesizeClass(
         DexProgramClass context,
         boolean requiresMainDex,
+        boolean addToMainDexTracingResult,
         int index) {
-
       DexType syntheticClassType =
           appView
               .dexItemFactory()
@@ -92,26 +108,31 @@
 
       appBuilder.addSynthesizedClass(clazz);
       appView.appInfo().addSynthesizedClass(clazz, requiresMainDex);
+      if (addToMainDexTracingResult) {
+        mainDexTracingResultBuilder.addRoot(clazz);
+      }
 
       return clazz.type;
     }
 
-    public SyntheticArgumentClass build(
-        AppView<AppInfoWithLiveness> appView,
-        DirectMappedDexApplication.Builder appBuilder,
-        Iterable<DexProgramClass> mergeClasses) {
-
+    public SyntheticArgumentClass build(Iterable<DexProgramClass> mergeClasses) {
       // Find a fresh name in an existing package.
       DexProgramClass context = mergeClasses.iterator().next();
 
+      // Add to the main dex list if one of the merged classes is in the main dex.
       boolean requiresMainDex = appView.appInfo().getMainDexClasses().containsAnyOf(mergeClasses);
 
+      // Also add as a root to the main dex tracing result if any of the merged classes is a root.
+      // This is needed to satisfy an assertion in the inliner that verifies that we do not inline
+      // methods with references to non-roots into classes that are roots.
+      boolean addToMainDexTracingResult = Iterables.any(mergeClasses, mainDexTracingResult::isRoot);
+
       List<DexType> syntheticArgumentTypes = new ArrayList<>();
       for (int i = 0;
           i < appView.options().horizontalClassMergerOptions().getSyntheticArgumentCount();
           i++) {
         syntheticArgumentTypes.add(
-            synthesizeClass(appView, appBuilder, context, requiresMainDex, i));
+            synthesizeClass(context, requiresMainDex, addToMainDexTracingResult, i));
       }
 
       return new SyntheticArgumentClass(syntheticArgumentTypes);
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/TreeFixer.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/TreeFixer.java
index 87a1e31..c66d5de 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/TreeFixer.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/TreeFixer.java
@@ -14,24 +14,20 @@
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexMethodSignature;
 import com.android.tools.r8.graph.DexProgramClass;
-import com.android.tools.r8.graph.DexProto;
 import com.android.tools.r8.graph.DexString;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.TreeFixerBase;
 import com.android.tools.r8.ir.conversion.ExtraUnusedNullParameter;
 import com.android.tools.r8.shaking.AnnotationFixer;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
-import com.android.tools.r8.shaking.FieldAccessInfoCollectionModifier;
 import com.android.tools.r8.utils.OptionalBool;
 import com.google.common.collect.BiMap;
 import com.google.common.collect.HashBiMap;
 import com.google.common.collect.Iterables;
 import com.google.common.collect.Sets;
 import java.util.Collections;
-import java.util.IdentityHashMap;
 import java.util.LinkedHashSet;
 import java.util.List;
-import java.util.Map;
 import java.util.Optional;
 import java.util.Set;
 
@@ -41,10 +37,8 @@
  * tracked in {@link TreeFixer#lensBuilder}.
  */
 class TreeFixer extends TreeFixerBase {
-  private final Map<DexProto, DexProto> protoFixupCache = new IdentityHashMap<>();
   private final HorizontallyMergedClasses mergedClasses;
   private final HorizontalClassMergerGraphLens.Builder lensBuilder;
-  private final FieldAccessInfoCollectionModifier.Builder fieldAccessChangesBuilder;
   private final AppView<AppInfoWithLiveness> appView;
   private final DexItemFactory dexItemFactory;
   private final SyntheticArgumentClass syntheticArgumentClass;
@@ -55,13 +49,11 @@
       AppView<AppInfoWithLiveness> appView,
       HorizontallyMergedClasses mergedClasses,
       HorizontalClassMergerGraphLens.Builder lensBuilder,
-      FieldAccessInfoCollectionModifier.Builder fieldAccessChangesBuilder,
       SyntheticArgumentClass syntheticArgumentClass) {
     super(appView);
     this.appView = appView;
     this.mergedClasses = mergedClasses;
     this.lensBuilder = lensBuilder;
-    this.fieldAccessChangesBuilder = fieldAccessChangesBuilder;
     this.syntheticArgumentClass = syntheticArgumentClass;
     this.dexItemFactory = appView.dexItemFactory();
   }
@@ -130,7 +122,6 @@
       subtypingForrest.traverseNodeDepthFirst(root, HashBiMap.create(), this::fixupProgramClass);
     }
     HorizontalClassMergerGraphLens lens = lensBuilder.build(appView, mergedClasses);
-    fieldAccessChangesBuilder.build(this::fixupMethodReference).modify(appView);
     new AnnotationFixer(lens).run(appView.appInfo().classes());
     return lens;
   }
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/VirtualMethodEntryPointSynthesizedCode.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/VirtualMethodEntryPointSynthesizedCode.java
index 7428b92..a1aaa6b 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/VirtualMethodEntryPointSynthesizedCode.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/VirtualMethodEntryPointSynthesizedCode.java
@@ -38,4 +38,9 @@
       registry.registerInvokeDirect(mappedMethod);
     }
   }
+
+  @Override
+  public boolean isHorizontalClassMergingCode() {
+    return true;
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/VirtualMethodMerger.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/VirtualMethodMerger.java
index 9a44de7..24a1b84 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/VirtualMethodMerger.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/VirtualMethodMerger.java
@@ -19,7 +19,6 @@
 import com.android.tools.r8.graph.ResolutionResult.SingleResolutionResult;
 import com.android.tools.r8.ir.synthetic.AbstractSynthesizedCode;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
-import com.android.tools.r8.shaking.FieldAccessInfoCollectionModifier;
 import com.android.tools.r8.utils.ListUtils;
 import com.android.tools.r8.utils.OptionalBool;
 import com.android.tools.r8.utils.structural.Ordered;
@@ -200,17 +199,11 @@
   public void merge(
       ClassMethodsBuilder classMethodsBuilder,
       HorizontalClassMergerGraphLens.Builder lensBuilder,
-      FieldAccessInfoCollectionModifier.Builder fieldAccessChangesBuilder,
       Reference2IntMap<DexType> classIdentifiers) {
     assert !methods.isEmpty();
 
-    // Handle nop merges.
-    if (isNop()) {
-      return;
-    }
-
     // Handle trivial merges.
-    if (isTrivial()) {
+    if (isNop() || isTrivial()) {
       mergeTrivial(classMethodsBuilder, lensBuilder);
       return;
     }
@@ -280,7 +273,5 @@
     lensBuilder.recordNewMethodSignature(bridgeMethodReference, newMethodReference);
 
     classMethodsBuilder.addVirtualMethod(newMethod);
-
-    fieldAccessChangesBuilder.fieldReadByMethod(group.getClassIdField(), newMethodReference);
   }
 }
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/DontMergeIntoLessVisible.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/DontMergeIntoLessVisible.java
deleted file mode 100644
index 954a238..0000000
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/DontMergeIntoLessVisible.java
+++ /dev/null
@@ -1,24 +0,0 @@
-// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-
-package com.android.tools.r8.horizontalclassmerging.policies;
-
-import com.android.tools.r8.graph.DexClass;
-import com.android.tools.r8.graph.DexProgramClass;
-import com.android.tools.r8.horizontalclassmerging.MergeGroup;
-import com.android.tools.r8.horizontalclassmerging.MultiClassPolicy;
-import java.util.Collection;
-import java.util.Collections;
-
-public class DontMergeIntoLessVisible extends MultiClassPolicy {
-
-  @Override
-  public Collection<MergeGroup> apply(MergeGroup group) {
-    DexProgramClass clazz = group.removeFirst(DexClass::isPublic);
-    if (clazz != null) {
-      group.addFirst(clazz);
-    }
-    return Collections.singletonList(group);
-  }
-}
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/MinimizeFieldCasts.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/MinimizeFieldCasts.java
new file mode 100644
index 0000000..73e0786
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/MinimizeFieldCasts.java
@@ -0,0 +1,73 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.horizontalclassmerging.policies;
+
+import com.android.tools.r8.graph.DexEncodedField;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.horizontalclassmerging.MergeGroup;
+import com.android.tools.r8.horizontalclassmerging.MultiClassPolicy;
+import com.google.common.collect.HashMultiset;
+import com.google.common.collect.Multiset;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+public class MinimizeFieldCasts extends MultiClassPolicy {
+
+  @Override
+  public final Collection<MergeGroup> apply(MergeGroup group) {
+    // First find all classes that can be merged without changing field types.
+    Map<Multiset<DexType>, MergeGroup> newGroups = new LinkedHashMap<>();
+    group.forEach(clazz -> addExact(clazz, newGroups));
+
+    // Create a single group from all trivial groups.
+    MergeGroup pendingGroup = new MergeGroup();
+    newGroups
+        .values()
+        .removeIf(
+            newGroup -> {
+              if (newGroup.isTrivial()) {
+                pendingGroup.add(newGroup);
+                return true;
+              }
+              return false;
+            });
+
+    if (pendingGroup.isEmpty()) {
+      return newGroups.values();
+    }
+
+    if (!pendingGroup.isTrivial()) {
+      List<MergeGroup> newGroupsIncludingRelaxedGroup = new ArrayList<>(newGroups.values());
+      newGroupsIncludingRelaxedGroup.add(pendingGroup);
+      return newGroupsIncludingRelaxedGroup;
+    }
+
+    MergeGroup smallestNewGroup = null;
+    for (MergeGroup newGroup : newGroups.values()) {
+      if (smallestNewGroup == null || newGroup.size() < smallestNewGroup.size()) {
+        smallestNewGroup = newGroup;
+      }
+    }
+    assert smallestNewGroup != null;
+    smallestNewGroup.add(pendingGroup);
+    return newGroups.values();
+  }
+
+  private void addExact(DexProgramClass clazz, Map<Multiset<DexType>, MergeGroup> groups) {
+    groups.computeIfAbsent(getExactMergeKey(clazz), ignore -> new MergeGroup()).add(clazz);
+  }
+
+  private Multiset<DexType> getExactMergeKey(DexProgramClass clazz) {
+    Multiset<DexType> fieldTypes = HashMultiset.create();
+    for (DexEncodedField field : clazz.instanceFields()) {
+      fieldTypes.add(field.getType());
+    }
+    return fieldTypes;
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/PreserveMethodCharacteristics.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/PreserveMethodCharacteristics.java
index adcde5c..5165bd9 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/PreserveMethodCharacteristics.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/PreserveMethodCharacteristics.java
@@ -9,6 +9,7 @@
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.horizontalclassmerging.MergeGroup;
 import com.android.tools.r8.horizontalclassmerging.MultiClassPolicy;
+import com.android.tools.r8.utils.OptionalBool;
 import com.google.common.collect.Iterables;
 import java.util.ArrayList;
 import java.util.Collection;
@@ -25,15 +26,17 @@
 
   static class MethodCharacteristics {
 
+    private final OptionalBool isLibraryMethodOverride;
     private final int visibilityOrdinal;
 
     private MethodCharacteristics(DexEncodedMethod method) {
+      this.isLibraryMethodOverride = method.isLibraryMethodOverride();
       this.visibilityOrdinal = method.getAccessFlags().getVisibilityOrdinal();
     }
 
     @Override
     public int hashCode() {
-      return visibilityOrdinal;
+      return (visibilityOrdinal << 2) | isLibraryMethodOverride.ordinal();
     }
 
     @Override
@@ -45,7 +48,8 @@
         return false;
       }
       MethodCharacteristics characteristics = (MethodCharacteristics) obj;
-      return visibilityOrdinal == characteristics.visibilityOrdinal;
+      return isLibraryMethodOverride == characteristics.isLibraryMethodOverride
+          && visibilityOrdinal == characteristics.visibilityOrdinal;
     }
   }
 
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/PreventChangingVisibility.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/PreventChangingVisibility.java
deleted file mode 100644
index cd58e10..0000000
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/PreventChangingVisibility.java
+++ /dev/null
@@ -1,77 +0,0 @@
-// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-
-package com.android.tools.r8.horizontalclassmerging.policies;
-
-import com.android.tools.r8.graph.DexEncodedMethod;
-import com.android.tools.r8.graph.DexMethod;
-import com.android.tools.r8.graph.DexProgramClass;
-import com.android.tools.r8.graph.MethodAccessFlags;
-import com.android.tools.r8.horizontalclassmerging.MergeGroup;
-import com.android.tools.r8.horizontalclassmerging.MultiClassPolicy;
-import com.android.tools.r8.utils.MethodSignatureEquivalence;
-import com.google.common.base.Equivalence.Wrapper;
-import com.google.common.collect.Iterables;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-
-public class PreventChangingVisibility extends MultiClassPolicy {
-  public PreventChangingVisibility() {}
-
-  public static class TargetGroup {
-    private final MergeGroup group = new MergeGroup();
-    private final Map<Wrapper<DexMethod>, MethodAccessFlags> methodMap = new HashMap<>();
-
-    public MergeGroup getGroup() {
-      return group;
-    }
-
-    public boolean tryAdd(DexProgramClass clazz) {
-      Map<Wrapper<DexMethod>, MethodAccessFlags> newMethods = new HashMap<>();
-      for (DexEncodedMethod method : clazz.methods()) {
-        Wrapper<DexMethod> methodSignature =
-            MethodSignatureEquivalence.get().wrap(method.getReference());
-        MethodAccessFlags flags = methodMap.get(methodSignature);
-
-        if (flags == null) {
-          newMethods.put(methodSignature, method.getAccessFlags());
-        } else {
-          if (!flags.isSameVisibility(method.getAccessFlags())) {
-            return false;
-          }
-        }
-      }
-
-      methodMap.putAll(newMethods);
-      group.add(clazz);
-      return true;
-    }
-  }
-
-  @Override
-  public Collection<MergeGroup> apply(MergeGroup group) {
-    List<TargetGroup> groups = new ArrayList<>();
-
-    for (DexProgramClass clazz : group) {
-      boolean added = Iterables.any(groups, targetGroup -> targetGroup.tryAdd(clazz));
-      if (!added) {
-        TargetGroup newGroup = new TargetGroup();
-        added = newGroup.tryAdd(clazz);
-        assert added;
-        groups.add(newGroup);
-      }
-    }
-
-    Collection<MergeGroup> newGroups = new ArrayList<>();
-    for (TargetGroup newGroup : groups) {
-      if (!newGroup.getGroup().isTrivial()) {
-        newGroups.add(newGroup.getGroup());
-      }
-    }
-    return newGroups;
-  }
-}
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/SameFields.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/SameFields.java
deleted file mode 100644
index 63134de..0000000
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/SameFields.java
+++ /dev/null
@@ -1,17 +0,0 @@
-// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-
-package com.android.tools.r8.horizontalclassmerging.policies;
-
-import com.android.tools.r8.graph.DexProgramClass;
-import com.android.tools.r8.horizontalclassmerging.FieldMultiset;
-import com.android.tools.r8.horizontalclassmerging.MultiClassSameReferencePolicy;
-
-public class SameFields extends MultiClassSameReferencePolicy<FieldMultiset> {
-
-  @Override
-  public FieldMultiset getMergeKey(DexProgramClass clazz) {
-    return new FieldMultiset(clazz);
-  }
-}
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/SameInstanceFields.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/SameInstanceFields.java
new file mode 100644
index 0000000..13e0388
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/SameInstanceFields.java
@@ -0,0 +1,84 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.horizontalclassmerging.policies;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexEncodedField;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.FieldAccessFlags;
+import com.android.tools.r8.horizontalclassmerging.MultiClassSameReferencePolicy;
+import com.android.tools.r8.horizontalclassmerging.policies.SameInstanceFields.InstanceFieldInfo;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.google.common.collect.HashMultiset;
+import com.google.common.collect.Multiset;
+import java.util.Objects;
+
+public class SameInstanceFields extends MultiClassSameReferencePolicy<Multiset<InstanceFieldInfo>> {
+
+  private final DexItemFactory dexItemFactory;
+
+  public SameInstanceFields(AppView<AppInfoWithLiveness> appView) {
+    this.dexItemFactory = appView.dexItemFactory();
+  }
+
+  @Override
+  public Multiset<InstanceFieldInfo> getMergeKey(DexProgramClass clazz) {
+    Multiset<InstanceFieldInfo> fields = HashMultiset.create();
+    for (DexEncodedField field : clazz.instanceFields()) {
+      fields.add(InstanceFieldInfo.createRelaxed(field, dexItemFactory));
+    }
+    return fields;
+  }
+
+  public static class InstanceFieldInfo {
+
+    private final FieldAccessFlags accessFlags;
+    private final DexType type;
+
+    private InstanceFieldInfo(FieldAccessFlags accessFlags, DexType type) {
+      this.accessFlags =
+          FieldAccessFlags.fromSharedAccessFlags(accessFlags.materialize())
+              .unsetFinal()
+              .unsetSynthetic();
+      this.type = type;
+    }
+
+    public static InstanceFieldInfo createExact(DexEncodedField field) {
+      return new InstanceFieldInfo(field.getAccessFlags(), field.getType());
+    }
+
+    public static InstanceFieldInfo createRelaxed(
+        DexEncodedField field, DexItemFactory dexItemFactory) {
+      return new InstanceFieldInfo(
+          field.getAccessFlags(),
+          field.getType().isReferenceType() ? dexItemFactory.objectType : field.getType());
+    }
+
+    public FieldAccessFlags getAccessFlags() {
+      return accessFlags;
+    }
+
+    public InstanceFieldInfo toInfoWithRelaxedType(DexItemFactory dexItemFactory) {
+      return new InstanceFieldInfo(
+          accessFlags, type.isReferenceType() ? dexItemFactory.objectType : type);
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+      if (obj == null || getClass() != obj.getClass()) {
+        return false;
+      }
+      InstanceFieldInfo info = (InstanceFieldInfo) obj;
+      return accessFlags.materialize() == info.accessFlags.materialize() && type == info.type;
+    }
+
+    @Override
+    public int hashCode() {
+      return Objects.hash(accessFlags, type);
+    }
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/inlining/AlwaysSimpleInliningConstraint.java b/src/main/java/com/android/tools/r8/ir/analysis/inlining/AlwaysSimpleInliningConstraint.java
new file mode 100644
index 0000000..32e01d8
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/analysis/inlining/AlwaysSimpleInliningConstraint.java
@@ -0,0 +1,36 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.analysis.inlining;
+
+import com.android.tools.r8.ir.code.InvokeMethod;
+import it.unimi.dsi.fastutil.ints.IntList;
+
+/** Constraint that is always satisfied. */
+public class AlwaysSimpleInliningConstraint extends SimpleInliningConstraint {
+
+  public static final AlwaysSimpleInliningConstraint INSTANCE =
+      new AlwaysSimpleInliningConstraint();
+
+  private AlwaysSimpleInliningConstraint() {}
+
+  public static AlwaysSimpleInliningConstraint getInstance() {
+    return INSTANCE;
+  }
+
+  @Override
+  public boolean isAlways() {
+    return true;
+  }
+
+  @Override
+  public boolean isSatisfied(InvokeMethod invoke) {
+    return false;
+  }
+
+  @Override
+  public SimpleInliningConstraint rewrittenWithUnboxedArguments(IntList unboxedArgumentIndices) {
+    return this;
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/inlining/BooleanFalseSimpleInliningConstraint.java b/src/main/java/com/android/tools/r8/ir/analysis/inlining/BooleanFalseSimpleInliningConstraint.java
new file mode 100644
index 0000000..a4a6a73
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/analysis/inlining/BooleanFalseSimpleInliningConstraint.java
@@ -0,0 +1,40 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.analysis.inlining;
+
+import com.android.tools.r8.ir.code.InvokeMethod;
+import com.android.tools.r8.ir.code.Value;
+import it.unimi.dsi.fastutil.ints.IntList;
+
+/** Constraint that is satisfied if a specific argument is always false. */
+public class BooleanFalseSimpleInliningConstraint extends SimpleInliningArgumentConstraint {
+
+  private BooleanFalseSimpleInliningConstraint(int argumentIndex) {
+    super(argumentIndex);
+  }
+
+  static BooleanFalseSimpleInliningConstraint create(
+      int argumentIndex, SimpleInliningConstraintFactory witness) {
+    assert witness != null;
+    return new BooleanFalseSimpleInliningConstraint(argumentIndex);
+  }
+
+  @Override
+  public boolean isBooleanFalse() {
+    return true;
+  }
+
+  @Override
+  public boolean isSatisfied(InvokeMethod invoke) {
+    Value argument = getArgument(invoke);
+    assert argument.getType().isInt();
+    return argument.isConstBoolean(false);
+  }
+
+  @Override
+  public SimpleInliningConstraint rewrittenWithUnboxedArguments(IntList unboxedArgumentIndices) {
+    return this;
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/inlining/BooleanTrueSimpleInliningConstraint.java b/src/main/java/com/android/tools/r8/ir/analysis/inlining/BooleanTrueSimpleInliningConstraint.java
new file mode 100644
index 0000000..f69381f
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/analysis/inlining/BooleanTrueSimpleInliningConstraint.java
@@ -0,0 +1,40 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.analysis.inlining;
+
+import com.android.tools.r8.ir.code.InvokeMethod;
+import com.android.tools.r8.ir.code.Value;
+import it.unimi.dsi.fastutil.ints.IntList;
+
+/** Constraint that is satisfied if a specific argument is always true. */
+public class BooleanTrueSimpleInliningConstraint extends SimpleInliningArgumentConstraint {
+
+  private BooleanTrueSimpleInliningConstraint(int argumentIndex) {
+    super(argumentIndex);
+  }
+
+  static BooleanTrueSimpleInliningConstraint create(
+      int argumentIndex, SimpleInliningConstraintFactory witness) {
+    assert witness != null;
+    return new BooleanTrueSimpleInliningConstraint(argumentIndex);
+  }
+
+  @Override
+  public boolean isBooleanTrue() {
+    return true;
+  }
+
+  @Override
+  public boolean isSatisfied(InvokeMethod invoke) {
+    Value argument = getArgument(invoke);
+    assert argument.getType().isInt();
+    return argument.isConstBoolean(true);
+  }
+
+  @Override
+  public SimpleInliningConstraint rewrittenWithUnboxedArguments(IntList unboxedArgumentIndices) {
+    return this;
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/inlining/NeverSimpleInliningConstraint.java b/src/main/java/com/android/tools/r8/ir/analysis/inlining/NeverSimpleInliningConstraint.java
new file mode 100644
index 0000000..9407b7b
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/analysis/inlining/NeverSimpleInliningConstraint.java
@@ -0,0 +1,35 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.analysis.inlining;
+
+import com.android.tools.r8.ir.code.InvokeMethod;
+import it.unimi.dsi.fastutil.ints.IntList;
+
+/** Constraint that is never satisfied. */
+public class NeverSimpleInliningConstraint extends SimpleInliningConstraint {
+
+  public static final NeverSimpleInliningConstraint INSTANCE = new NeverSimpleInliningConstraint();
+
+  private NeverSimpleInliningConstraint() {}
+
+  public static NeverSimpleInliningConstraint getInstance() {
+    return INSTANCE;
+  }
+
+  @Override
+  public boolean isNever() {
+    return true;
+  }
+
+  @Override
+  public boolean isSatisfied(InvokeMethod invoke) {
+    return false;
+  }
+
+  @Override
+  public SimpleInliningConstraint rewrittenWithUnboxedArguments(IntList unboxedArgumentIndices) {
+    return this;
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/inlining/NotNullSimpleInliningConstraint.java b/src/main/java/com/android/tools/r8/ir/analysis/inlining/NotNullSimpleInliningConstraint.java
new file mode 100644
index 0000000..ea41ccb
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/analysis/inlining/NotNullSimpleInliningConstraint.java
@@ -0,0 +1,44 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.analysis.inlining;
+
+import com.android.tools.r8.ir.code.InvokeMethod;
+import com.android.tools.r8.ir.code.Value;
+import it.unimi.dsi.fastutil.ints.IntList;
+
+/** Constraint that is satisfied if a specific argument is always non-null. */
+public class NotNullSimpleInliningConstraint extends SimpleInliningArgumentConstraint {
+
+  private NotNullSimpleInliningConstraint(int argumentIndex) {
+    super(argumentIndex);
+  }
+
+  static NotNullSimpleInliningConstraint create(
+      int argumentIndex, SimpleInliningConstraintFactory witness) {
+    assert witness != null;
+    return new NotNullSimpleInliningConstraint(argumentIndex);
+  }
+
+  @Override
+  public boolean isNotNull() {
+    return true;
+  }
+
+  @Override
+  public boolean isSatisfied(InvokeMethod invoke) {
+    Value argument = getArgument(invoke);
+    assert argument.getType().isReferenceType() : invoke;
+    return argument.isNeverNull();
+  }
+
+  @Override
+  public SimpleInliningConstraint rewrittenWithUnboxedArguments(IntList unboxedArgumentIndices) {
+    if (unboxedArgumentIndices.contains(getArgumentIndex())) {
+      // TODO(b/176067541): Could be refined to an argument-equals-int constraint.
+      return NeverSimpleInliningConstraint.getInstance();
+    }
+    return this;
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/inlining/NullSimpleInliningConstraint.java b/src/main/java/com/android/tools/r8/ir/analysis/inlining/NullSimpleInliningConstraint.java
new file mode 100644
index 0000000..76d075d
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/analysis/inlining/NullSimpleInliningConstraint.java
@@ -0,0 +1,44 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.analysis.inlining;
+
+import com.android.tools.r8.ir.code.InvokeMethod;
+import com.android.tools.r8.ir.code.Value;
+import it.unimi.dsi.fastutil.ints.IntList;
+
+/** Constraint that is satisfied if a specific argument is always null. */
+public class NullSimpleInliningConstraint extends SimpleInliningArgumentConstraint {
+
+  private NullSimpleInliningConstraint(int argumentIndex) {
+    super(argumentIndex);
+  }
+
+  static NullSimpleInliningConstraint create(
+      int argumentIndex, SimpleInliningConstraintFactory witness) {
+    assert witness != null;
+    return new NullSimpleInliningConstraint(argumentIndex);
+  }
+
+  @Override
+  public boolean isNull() {
+    return true;
+  }
+
+  @Override
+  public boolean isSatisfied(InvokeMethod invoke) {
+    Value argument = getArgument(invoke);
+    assert argument.getType().isReferenceType();
+    return argument.getType().isDefinitelyNull();
+  }
+
+  @Override
+  public SimpleInliningConstraint rewrittenWithUnboxedArguments(IntList unboxedArgumentIndices) {
+    if (unboxedArgumentIndices.contains(getArgumentIndex())) {
+      // TODO(b/176067541): Could be refined to an argument-equals-int constraint.
+      return NeverSimpleInliningConstraint.getInstance();
+    }
+    return this;
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/inlining/SimpleInliningArgumentConstraint.java b/src/main/java/com/android/tools/r8/ir/analysis/inlining/SimpleInliningArgumentConstraint.java
new file mode 100644
index 0000000..749d6f4
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/analysis/inlining/SimpleInliningArgumentConstraint.java
@@ -0,0 +1,30 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.analysis.inlining;
+
+import com.android.tools.r8.ir.code.InvokeMethod;
+import com.android.tools.r8.ir.code.Value;
+
+public abstract class SimpleInliningArgumentConstraint extends SimpleInliningConstraint {
+
+  private final int argumentIndex;
+
+  SimpleInliningArgumentConstraint(int argumentIndex) {
+    this.argumentIndex = argumentIndex;
+  }
+
+  Value getArgument(InvokeMethod invoke) {
+    return invoke.getArgument(argumentIndex);
+  }
+
+  int getArgumentIndex() {
+    return argumentIndex;
+  }
+
+  @Override
+  public boolean isArgumentConstraint() {
+    return true;
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/inlining/SimpleInliningConstraint.java b/src/main/java/com/android/tools/r8/ir/analysis/inlining/SimpleInliningConstraint.java
new file mode 100644
index 0000000..b411c8b
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/analysis/inlining/SimpleInliningConstraint.java
@@ -0,0 +1,111 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.analysis.inlining;
+
+import com.android.tools.r8.ir.code.InvokeMethod;
+import com.google.common.collect.ImmutableList;
+import it.unimi.dsi.fastutil.ints.IntList;
+import java.util.function.Supplier;
+
+public abstract class SimpleInliningConstraint {
+
+  public boolean isAlways() {
+    return false;
+  }
+
+  public boolean isArgumentConstraint() {
+    return false;
+  }
+
+  public boolean isBooleanFalse() {
+    return false;
+  }
+
+  public boolean isBooleanTrue() {
+    return false;
+  }
+
+  public boolean isConjunction() {
+    return false;
+  }
+
+  public SimpleInliningConstraintConjunction asConjunction() {
+    return null;
+  }
+
+  public boolean isDisjunction() {
+    return false;
+  }
+
+  public SimpleInliningConstraintDisjunction asDisjunction() {
+    return null;
+  }
+
+  public boolean isNever() {
+    return false;
+  }
+
+  public boolean isNotNull() {
+    return false;
+  }
+
+  public boolean isNull() {
+    return false;
+  }
+
+  public abstract boolean isSatisfied(InvokeMethod invoke);
+
+  public final SimpleInliningConstraint meet(SimpleInliningConstraint other) {
+    if (isAlways()) {
+      return other;
+    }
+    if (other.isAlways()) {
+      return this;
+    }
+    if (isNever() || other.isNever()) {
+      return NeverSimpleInliningConstraint.getInstance();
+    }
+    if (isConjunction()) {
+      return asConjunction().add(other);
+    }
+    if (other.isConjunction()) {
+      return other.asConjunction().add(this);
+    }
+    assert isArgumentConstraint() || isDisjunction();
+    assert other.isArgumentConstraint() || other.isDisjunction();
+    return new SimpleInliningConstraintConjunction(ImmutableList.of(this, other));
+  }
+
+  public final SimpleInliningConstraint lazyMeet(Supplier<SimpleInliningConstraint> supplier) {
+    if (isNever()) {
+      return NeverSimpleInliningConstraint.getInstance();
+    }
+    return meet(supplier.get());
+  }
+
+  public final SimpleInliningConstraint join(SimpleInliningConstraint other) {
+    if (isAlways() || other.isAlways()) {
+      return AlwaysSimpleInliningConstraint.getInstance();
+    }
+    if (isNever()) {
+      return other;
+    }
+    if (other.isNever()) {
+      return this;
+    }
+    if (isDisjunction()) {
+      return asDisjunction().add(other);
+    }
+    if (other.isDisjunction()) {
+      return other.asDisjunction().add(this);
+    }
+    assert isArgumentConstraint() || isConjunction();
+    assert other.isArgumentConstraint() || other.isConjunction();
+    return new SimpleInliningConstraintDisjunction(ImmutableList.of(this, other));
+  }
+
+  public abstract SimpleInliningConstraint rewrittenWithUnboxedArguments(
+      IntList unboxedArgumentIndices);
+}
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/inlining/SimpleInliningConstraintAnalysis.java b/src/main/java/com/android/tools/r8/ir/analysis/inlining/SimpleInliningConstraintAnalysis.java
new file mode 100644
index 0000000..1a7e997
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/analysis/inlining/SimpleInliningConstraintAnalysis.java
@@ -0,0 +1,190 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.analysis.inlining;
+
+import static com.android.tools.r8.ir.code.Opcodes.GOTO;
+import static com.android.tools.r8.ir.code.Opcodes.IF;
+import static com.android.tools.r8.ir.code.Opcodes.RETURN;
+import static com.android.tools.r8.ir.code.Opcodes.THROW;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.ir.code.BasicBlock;
+import com.android.tools.r8.ir.code.IRCode;
+import com.android.tools.r8.ir.code.If;
+import com.android.tools.r8.ir.code.Instruction;
+import com.android.tools.r8.ir.code.InstructionIterator;
+import com.android.tools.r8.ir.code.Value;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.utils.InternalOptions;
+import com.google.common.collect.Sets;
+import java.util.Set;
+
+/**
+ * Analysis that given a method computes a constraint which is satisfied by a concrete call site
+ * only if the method becomes simple after inlining into the concrete call site.
+ *
+ * <p>Examples of simple inlining constraints are:
+ *
+ * <ul>
+ *   <li>Always simple,
+ *   <li>Never simple,
+ *   <li>Simple if argument i is {true, false, null, not-null},
+ *   <li>Simple if argument i is true and argument j is false, or if argument i is false.
+ * </ul>
+ */
+public class SimpleInliningConstraintAnalysis {
+
+  private final SimpleInliningConstraintFactory factory;
+  private final ProgramMethod method;
+  private final InternalOptions options;
+  private final int simpleInliningConstraintThreshold;
+
+  private final Set<BasicBlock> seen = Sets.newIdentityHashSet();
+
+  public SimpleInliningConstraintAnalysis(
+      AppView<AppInfoWithLiveness> appView, ProgramMethod method) {
+    this.factory = appView.simpleInliningConstraintFactory();
+    this.method = method;
+    this.options = appView.options();
+    this.simpleInliningConstraintThreshold = appView.options().simpleInliningConstraintThreshold;
+  }
+
+  public SimpleInliningConstraint analyzeCode(IRCode code) {
+    if (method.getReference().getArity() == 0) {
+      // The method does not have any parameters, so there is no need to analyze the method.
+      return NeverSimpleInliningConstraint.getInstance();
+    }
+
+    if (options.debug) {
+      // Inlining is not enabled in debug mode.
+      return NeverSimpleInliningConstraint.getInstance();
+    }
+
+    // Run a bounded depth-first traversal to collect the path constraints that lead to early
+    // returns.
+    InstructionIterator instructionIterator =
+        code.entryBlock().iterator(code.getNumberOfArguments());
+    return analyzeInstructionsInBlock(code.entryBlock(), 0, instructionIterator);
+  }
+
+  private SimpleInliningConstraint analyzeInstructionsInBlock(BasicBlock block, int depth) {
+    return analyzeInstructionsInBlock(block, depth, block.iterator());
+  }
+
+  private SimpleInliningConstraint analyzeInstructionsInBlock(
+      BasicBlock block, int instructionDepth, InstructionIterator instructionIterator) {
+    // If we reach a block that has already been seen, give up.
+    if (!seen.add(block)) {
+      return NeverSimpleInliningConstraint.getInstance();
+    }
+
+    // Move the instruction iterator forward to the block's jump instruction, while incrementing the
+    // instruction depth of the depth-first traversal.
+    Instruction instruction = instructionIterator.next();
+    while (!instruction.isJumpInstruction()) {
+      assert !instruction.isArgument();
+      assert !instruction.isDebugInstruction();
+      if (!instruction.isAssume()) {
+        instructionDepth += 1;
+      }
+      instruction = instructionIterator.next();
+    }
+
+    // If we have exceeded the threshold, then all paths from this instruction will not lead to any
+    // early exits, so return 'never'.
+    if (instructionDepth > simpleInliningConstraintThreshold) {
+      return NeverSimpleInliningConstraint.getInstance();
+    }
+
+    // Analyze the jump instruction.
+    // TODO(b/132600418): Extend to switch and throw instructions.
+    switch (instruction.opcode()) {
+      case IF:
+        If ifInstruction = instruction.asIf();
+        if (ifInstruction.isZeroTest()) {
+          Value lhs = ifInstruction.lhs().getAliasedValue();
+          if (lhs.isArgument() && !lhs.isThis()) {
+            int argumentIndex = lhs.getDefinition().asArgument().getIndex();
+            DexType argumentType = method.getDefinition().getArgumentType(argumentIndex);
+            int currentDepth = instructionDepth;
+
+            // Compute the constraint for which paths through the true target are guaranteed to exit
+            // early.
+            SimpleInliningConstraint trueTargetConstraint =
+                computeConstraintFromIfZeroTest(
+                        argumentIndex, argumentType, ifInstruction.getType())
+                    // Only recurse into the true target if the constraint from the if-instruction
+                    // is not 'never'.
+                    .lazyMeet(
+                        () ->
+                            analyzeInstructionsInBlock(
+                                ifInstruction.getTrueTarget(), currentDepth));
+
+            // Compute the constraint for which paths through the false target are guaranteed to
+            // exit early.
+            SimpleInliningConstraint fallthroughTargetConstraint =
+                computeConstraintFromIfZeroTest(
+                        argumentIndex, argumentType, ifInstruction.getType().inverted())
+                    // Only recurse into the false target if the constraint from the if-instruction
+                    // is not 'never'.
+                    .lazyMeet(
+                        () ->
+                            analyzeInstructionsInBlock(
+                                ifInstruction.fallthroughBlock(), currentDepth));
+
+            // Paths going through this basic block are guaranteed to exit early if the true target
+            // is guaranteed to exit early or the false target is.
+            return trueTargetConstraint.join(fallthroughTargetConstraint);
+          }
+        }
+        break;
+
+      case GOTO:
+        return analyzeInstructionsInBlock(instruction.asGoto().getTarget(), instructionDepth);
+
+      case RETURN:
+        return AlwaysSimpleInliningConstraint.getInstance();
+
+      case THROW:
+        return block.hasCatchHandlers()
+            ? NeverSimpleInliningConstraint.getInstance()
+            : AlwaysSimpleInliningConstraint.getInstance();
+
+      default:
+        break;
+    }
+
+    // Give up.
+    return NeverSimpleInliningConstraint.getInstance();
+  }
+
+  private SimpleInliningConstraint computeConstraintFromIfZeroTest(
+      int argumentIndex, DexType argumentType, If.Type type) {
+    switch (type) {
+      case EQ:
+        if (argumentType.isReferenceType()) {
+          return factory.createNullConstraint(argumentIndex);
+        }
+        if (argumentType.isBooleanType()) {
+          return factory.createBooleanFalseConstraint(argumentIndex);
+        }
+        return NeverSimpleInliningConstraint.getInstance();
+
+      case NE:
+        if (argumentType.isReferenceType()) {
+          return factory.createNotNullConstraint(argumentIndex);
+        }
+        if (argumentType.isBooleanType()) {
+          return factory.createBooleanTrueConstraint(argumentIndex);
+        }
+        return NeverSimpleInliningConstraint.getInstance();
+
+      default:
+        return NeverSimpleInliningConstraint.getInstance();
+    }
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/inlining/SimpleInliningConstraintConjunction.java b/src/main/java/com/android/tools/r8/ir/analysis/inlining/SimpleInliningConstraintConjunction.java
new file mode 100644
index 0000000..02a8536
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/analysis/inlining/SimpleInliningConstraintConjunction.java
@@ -0,0 +1,80 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.analysis.inlining;
+
+import com.android.tools.r8.ir.code.InvokeMethod;
+import com.android.tools.r8.utils.ListUtils;
+import com.google.common.collect.ImmutableList;
+import it.unimi.dsi.fastutil.ints.IntList;
+import java.util.List;
+
+public class SimpleInliningConstraintConjunction extends SimpleInliningConstraint {
+
+  private final List<SimpleInliningConstraint> constraints;
+
+  public SimpleInliningConstraintConjunction(List<SimpleInliningConstraint> constraints) {
+    assert constraints.size() > 1;
+    assert constraints.stream().noneMatch(SimpleInliningConstraint::isAlways);
+    assert constraints.stream().noneMatch(SimpleInliningConstraint::isConjunction);
+    assert constraints.stream().noneMatch(SimpleInliningConstraint::isNever);
+    this.constraints = constraints;
+  }
+
+  SimpleInliningConstraint add(SimpleInliningConstraint constraint) {
+    assert !constraint.isAlways();
+    assert !constraint.isNever();
+    if (constraint.isConjunction()) {
+      return addAll(constraint.asConjunction());
+    }
+    assert constraint.isArgumentConstraint() || constraint.isDisjunction();
+    return new SimpleInliningConstraintConjunction(
+        ImmutableList.<SimpleInliningConstraint>builder()
+            .addAll(constraints)
+            .add(constraint)
+            .build());
+  }
+
+  public SimpleInliningConstraintConjunction addAll(
+      SimpleInliningConstraintConjunction conjunction) {
+    return new SimpleInliningConstraintConjunction(
+        ImmutableList.<SimpleInliningConstraint>builder()
+            .addAll(constraints)
+            .addAll(conjunction.constraints)
+            .build());
+  }
+
+  @Override
+  public boolean isConjunction() {
+    return true;
+  }
+
+  @Override
+  public SimpleInliningConstraintConjunction asConjunction() {
+    return this;
+  }
+
+  @Override
+  public boolean isSatisfied(InvokeMethod invoke) {
+    for (SimpleInliningConstraint constraint : constraints) {
+      if (!constraint.isSatisfied(invoke)) {
+        return false;
+      }
+    }
+    return true;
+  }
+
+  @Override
+  public SimpleInliningConstraint rewrittenWithUnboxedArguments(IntList unboxedArgumentIndices) {
+    List<SimpleInliningConstraint> rewrittenConstraints =
+        ListUtils.mapOrElse(
+            constraints,
+            constraint -> constraint.rewrittenWithUnboxedArguments(unboxedArgumentIndices),
+            null);
+    if (rewrittenConstraints != null) {
+      return new SimpleInliningConstraintConjunction(rewrittenConstraints);
+    }
+    return this;
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/inlining/SimpleInliningConstraintDisjunction.java b/src/main/java/com/android/tools/r8/ir/analysis/inlining/SimpleInliningConstraintDisjunction.java
new file mode 100644
index 0000000..c069f37
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/analysis/inlining/SimpleInliningConstraintDisjunction.java
@@ -0,0 +1,80 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.analysis.inlining;
+
+import com.android.tools.r8.ir.code.InvokeMethod;
+import com.android.tools.r8.utils.ListUtils;
+import com.google.common.collect.ImmutableList;
+import it.unimi.dsi.fastutil.ints.IntList;
+import java.util.List;
+
+public class SimpleInliningConstraintDisjunction extends SimpleInliningConstraint {
+
+  private final List<SimpleInliningConstraint> constraints;
+
+  public SimpleInliningConstraintDisjunction(List<SimpleInliningConstraint> constraints) {
+    assert constraints.size() > 1;
+    assert constraints.stream().noneMatch(SimpleInliningConstraint::isAlways);
+    assert constraints.stream().noneMatch(SimpleInliningConstraint::isDisjunction);
+    assert constraints.stream().noneMatch(SimpleInliningConstraint::isNever);
+    this.constraints = constraints;
+  }
+
+  SimpleInliningConstraint add(SimpleInliningConstraint constraint) {
+    assert !constraint.isAlways();
+    assert !constraint.isNever();
+    if (constraint.isDisjunction()) {
+      return addAll(constraint.asDisjunction());
+    }
+    assert constraint.isArgumentConstraint() || constraint.isConjunction();
+    return new SimpleInliningConstraintDisjunction(
+        ImmutableList.<SimpleInliningConstraint>builder()
+            .addAll(constraints)
+            .add(constraint)
+            .build());
+  }
+
+  public SimpleInliningConstraintDisjunction addAll(
+      SimpleInliningConstraintDisjunction disjunction) {
+    return new SimpleInliningConstraintDisjunction(
+        ImmutableList.<SimpleInliningConstraint>builder()
+            .addAll(constraints)
+            .addAll(disjunction.constraints)
+            .build());
+  }
+
+  @Override
+  public boolean isDisjunction() {
+    return true;
+  }
+
+  @Override
+  public SimpleInliningConstraintDisjunction asDisjunction() {
+    return this;
+  }
+
+  @Override
+  public boolean isSatisfied(InvokeMethod invoke) {
+    for (SimpleInliningConstraint constraint : constraints) {
+      if (constraint.isSatisfied(invoke)) {
+        return true;
+      }
+    }
+    return false;
+  }
+
+  @Override
+  public SimpleInliningConstraint rewrittenWithUnboxedArguments(IntList unboxedArgumentIndices) {
+    List<SimpleInliningConstraint> rewrittenConstraints =
+        ListUtils.mapOrElse(
+            constraints,
+            constraint -> constraint.rewrittenWithUnboxedArguments(unboxedArgumentIndices),
+            null);
+    if (rewrittenConstraints != null) {
+      return new SimpleInliningConstraintDisjunction(rewrittenConstraints);
+    }
+    return this;
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/inlining/SimpleInliningConstraintFactory.java b/src/main/java/com/android/tools/r8/ir/analysis/inlining/SimpleInliningConstraintFactory.java
new file mode 100644
index 0000000..99603e8
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/analysis/inlining/SimpleInliningConstraintFactory.java
@@ -0,0 +1,86 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.analysis.inlining;
+
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.function.Supplier;
+
+public class SimpleInliningConstraintFactory {
+
+  // Immutable argument constraints for low argument indices to avoid overhead of ConcurrentHashMap.
+  private final BooleanFalseSimpleInliningConstraint[] lowBooleanFalseConstraints =
+      new BooleanFalseSimpleInliningConstraint[5];
+  private final BooleanTrueSimpleInliningConstraint[] lowBooleanTrueConstraints =
+      new BooleanTrueSimpleInliningConstraint[5];
+  private final NotNullSimpleInliningConstraint[] lowNotNullConstraints =
+      new NotNullSimpleInliningConstraint[5];
+  private final NullSimpleInliningConstraint[] lowNullConstraints =
+      new NullSimpleInliningConstraint[5];
+
+  // Argument constraints for high argument indices.
+  private final Map<Integer, BooleanFalseSimpleInliningConstraint> highBooleanFalseConstraints =
+      new ConcurrentHashMap<>();
+  private final Map<Integer, BooleanTrueSimpleInliningConstraint> highBooleanTrueConstraints =
+      new ConcurrentHashMap<>();
+  private final Map<Integer, NotNullSimpleInliningConstraint> highNotNullConstraints =
+      new ConcurrentHashMap<>();
+  private final Map<Integer, NullSimpleInliningConstraint> highNullConstraints =
+      new ConcurrentHashMap<>();
+
+  public SimpleInliningConstraintFactory() {
+    for (int i = 0; i < lowBooleanFalseConstraints.length; i++) {
+      lowBooleanFalseConstraints[i] = BooleanFalseSimpleInliningConstraint.create(i, this);
+    }
+    for (int i = 0; i < lowBooleanTrueConstraints.length; i++) {
+      lowBooleanTrueConstraints[i] = BooleanTrueSimpleInliningConstraint.create(i, this);
+    }
+    for (int i = 0; i < lowNotNullConstraints.length; i++) {
+      lowNotNullConstraints[i] = NotNullSimpleInliningConstraint.create(i, this);
+    }
+    for (int i = 0; i < lowNullConstraints.length; i++) {
+      lowNullConstraints[i] = NullSimpleInliningConstraint.create(i, this);
+    }
+  }
+
+  public BooleanFalseSimpleInliningConstraint createBooleanFalseConstraint(int argumentIndex) {
+    return createArgumentConstraint(
+        argumentIndex,
+        lowBooleanFalseConstraints,
+        highBooleanFalseConstraints,
+        () -> BooleanFalseSimpleInliningConstraint.create(argumentIndex, this));
+  }
+
+  public BooleanTrueSimpleInliningConstraint createBooleanTrueConstraint(int argumentIndex) {
+    return createArgumentConstraint(
+        argumentIndex,
+        lowBooleanTrueConstraints,
+        highBooleanTrueConstraints,
+        () -> BooleanTrueSimpleInliningConstraint.create(argumentIndex, this));
+  }
+
+  public NotNullSimpleInliningConstraint createNotNullConstraint(int argumentIndex) {
+    return createArgumentConstraint(
+        argumentIndex,
+        lowNotNullConstraints,
+        highNotNullConstraints,
+        () -> NotNullSimpleInliningConstraint.create(argumentIndex, this));
+  }
+
+  public NullSimpleInliningConstraint createNullConstraint(int argumentIndex) {
+    return createArgumentConstraint(
+        argumentIndex,
+        lowNullConstraints,
+        highNullConstraints,
+        () -> NullSimpleInliningConstraint.create(argumentIndex, this));
+  }
+
+  private <T extends SimpleInliningArgumentConstraint> T createArgumentConstraint(
+      int argumentIndex, T[] lowConstraints, Map<Integer, T> highConstraints, Supplier<T> fn) {
+    return argumentIndex < lowConstraints.length
+        ? lowConstraints[argumentIndex]
+        : highConstraints.computeIfAbsent(argumentIndex, key -> fn.get());
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/proto/ProtoEnqueuerUseRegistry.java b/src/main/java/com/android/tools/r8/ir/analysis/proto/ProtoEnqueuerUseRegistry.java
index 7dca039..2a6be81 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/proto/ProtoEnqueuerUseRegistry.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/proto/ProtoEnqueuerUseRegistry.java
@@ -4,9 +4,13 @@
 
 package com.android.tools.r8.ir.analysis.proto;
 
+import static com.android.tools.r8.graph.DexProgramClass.asProgramClassOrNull;
+
 import com.android.tools.r8.code.CfOrDexInstruction;
+import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.analysis.proto.schema.ProtoEnqueuerExtension;
@@ -22,7 +26,9 @@
   private final ProtoReferences references;
 
   public ProtoEnqueuerUseRegistry(
-      AppView<?> appView, ProgramMethod currentMethod, Enqueuer enqueuer) {
+      AppView<? extends AppInfoWithClassHierarchy> appView,
+      ProgramMethod currentMethod,
+      Enqueuer enqueuer) {
     super(appView, currentMethod, enqueuer);
     this.references = appView.protoShrinker().references;
   }
@@ -50,17 +56,29 @@
 
   /**
    * Unlike {@link DefaultEnqueuerUseRegistry#registerStaticFieldRead(DexField)}, this method does
-   * not trace any static-get instructions in every implementation of dynamicMethod().
+   * not trace any static-get instructions in every implementation of dynamicMethod() that accesses
+   * an 'INSTANCE' or a 'DEFAULT_INSTANCE' field.
    *
    * <p>The static-get instructions that remain after the proto schema has been optimized will be
    * traced manually by {@link ProtoEnqueuerExtension#tracePendingInstructionsInDynamicMethods}.
    */
   @Override
   public void registerStaticFieldRead(DexField field) {
-    if (references.isDynamicMethod(getContextMethod())) {
+    if (references.isDynamicMethod(getContextMethod())
+        && isStaticFieldReadForProtoSchemaDefinition(field)) {
       enqueuer.addDeadProtoTypeCandidate(field.holder);
       return;
     }
     super.registerStaticFieldRead(field);
   }
+
+  private boolean isStaticFieldReadForProtoSchemaDefinition(DexField field) {
+    if (field == references.getDefaultInstanceField(getContextHolder())) {
+      return true;
+    }
+    DexProgramClass holder = asProgramClassOrNull(appView.definitionFor(field.getHolderType()));
+    return holder != null
+        && holder.getInterfaces().contains(references.enumVerifierType)
+        && field == references.getEnumVerifierInstanceField(holder);
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/proto/ProtoReferences.java b/src/main/java/com/android/tools/r8/ir/analysis/proto/ProtoReferences.java
index 84aabe8..b25f946 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/proto/ProtoReferences.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/proto/ProtoReferences.java
@@ -21,6 +21,7 @@
 
   public final DexType enumLiteType;
   public final DexType enumLiteMapType;
+  public final DexType enumVerifierType;
   public final DexType extendableMessageType;
   public final DexType extensionDescriptorType;
   public final DexType extensionRegistryLiteType;
@@ -42,6 +43,7 @@
   public final MethodToInvokeMembers methodToInvokeMembers;
 
   public final DexString defaultInstanceFieldName;
+  public final DexString instanceFieldName;
   public final DexString internalValueMapFieldName;
   public final DexString dynamicMethodName;
   public final DexString findLiteExtensionByNumberName;
@@ -63,6 +65,7 @@
     // Types.
     enumLiteType = factory.createType("Lcom/google/protobuf/Internal$EnumLite;");
     enumLiteMapType = factory.createType("Lcom/google/protobuf/Internal$EnumLiteMap;");
+    enumVerifierType = factory.createType("Lcom/google/protobuf/Internal$EnumVerifier;");
     extendableMessageType =
         factory.createType("Lcom/google/protobuf/GeneratedMessageLite$ExtendableMessage;");
     extensionDescriptorType =
@@ -85,6 +88,7 @@
 
     // Names.
     defaultInstanceFieldName = factory.createString("DEFAULT_INSTANCE");
+    instanceFieldName = factory.createString("INSTANCE");
     internalValueMapFieldName = factory.createString("internalValueMap");
     dynamicMethodName = factory.createString("dynamicMethod");
     findLiteExtensionByNumberName = factory.createString("findLiteExtensionByNumber");
@@ -129,6 +133,10 @@
     return dexItemFactory.createField(holder.type, holder.type, defaultInstanceFieldName);
   }
 
+  public DexField getEnumVerifierInstanceField(DexProgramClass holder) {
+    return dexItemFactory.createField(holder.type, enumVerifierType, instanceFieldName);
+  }
+
   public boolean isAbstractGeneratedMessageLiteBuilder(DexProgramClass clazz) {
     return clazz.type == generatedMessageLiteBuilderType
         || clazz.type == generatedMessageLiteExtendableBuilderType;
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/proto/schema/ProtoEnqueuerExtension.java b/src/main/java/com/android/tools/r8/ir/analysis/proto/schema/ProtoEnqueuerExtension.java
index d84295c..41ae033 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/proto/schema/ProtoEnqueuerExtension.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/proto/schema/ProtoEnqueuerExtension.java
@@ -15,6 +15,7 @@
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.FieldResolutionResult;
+import com.android.tools.r8.graph.ProgramDefinition;
 import com.android.tools.r8.graph.ProgramField;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.graph.analysis.EnqueuerAnalysis;
@@ -128,7 +129,7 @@
    * ProtoMessageInfo} object, and create a mapping from the holder to it.
    */
   @Override
-  public void processNewlyLiveMethod(ProgramMethod method) {
+  public void processNewlyLiveMethod(ProgramMethod method, ProgramDefinition context) {
     if (references.isFindLiteExtensionByNumberMethod(method.getReference())) {
       findLiteExtensionByNumberMethods.add(method);
       return;
@@ -381,7 +382,7 @@
           // written such that we cannot optimize any field reads or writes.
           enqueuer.registerReflectiveFieldAccess(valueStorage.getReference(), dynamicMethod);
           worklist.enqueueMarkReachableFieldAction(
-              valueStorage, KeepReason.reflectiveUseIn(dynamicMethod));
+              valueStorage, dynamicMethod, KeepReason.reflectiveUseIn(dynamicMethod));
           valueStorageIsLive = true;
         } else {
           valueStorageIsLive = false;
@@ -446,8 +447,7 @@
           // dynamicMethod().
           if (enqueuer.registerReflectiveFieldWrite(newlyLiveField.getReference(), dynamicMethod)) {
             worklist.enqueueMarkReachableFieldAction(
-                newlyLiveField,
-                KeepReason.reflectiveUseIn(dynamicMethod));
+                newlyLiveField, dynamicMethod, KeepReason.reflectiveUseIn(dynamicMethod));
           }
         }
       }
@@ -567,7 +567,7 @@
 
     if (enqueuer.registerReflectiveFieldWrite(oneOfField.getReference(), dynamicMethod)) {
       worklist.enqueueMarkReachableFieldAction(
-          oneOfField, KeepReason.reflectiveUseIn(dynamicMethod));
+          oneOfField, dynamicMethod, KeepReason.reflectiveUseIn(dynamicMethod));
     }
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/code/BasicBlock.java b/src/main/java/com/android/tools/r8/ir/code/BasicBlock.java
index c9377f7..e81d765 100644
--- a/src/main/java/com/android/tools/r8/ir/code/BasicBlock.java
+++ b/src/main/java/com/android/tools/r8/ir/code/BasicBlock.java
@@ -1749,7 +1749,7 @@
     // one new phi to merge the two exception values, and all other phis don't need
     // to be changed.
     for (BasicBlock catchSuccessor : catchSuccessors) {
-      catchSuccessor.splitCriticalExceptionEdges(code, blockIterator::add, options);
+      catchSuccessor.splitCriticalExceptionEdges(code, blockIterator, options);
     }
   }
 
@@ -1767,7 +1767,7 @@
    * and Goto.
    */
   public void splitCriticalExceptionEdges(
-      IRCode code, Consumer<BasicBlock> onNewBlock, InternalOptions options) {
+      IRCode code, ListIterator<BasicBlock> blockIterator, InternalOptions options) {
     List<BasicBlock> predecessors = getMutablePredecessors();
     boolean hasMoveException = entry().isMoveException();
     TypeElement exceptionTypeLattice = null;
@@ -1783,7 +1783,7 @@
       getInstructions().remove(0);
     }
     // Create new predecessor blocks.
-    List<BasicBlock> newPredecessors = new ArrayList<>();
+    List<BasicBlock> newPredecessors = new ArrayList<>(predecessors.size());
     List<Value> values = new ArrayList<>(predecessors.size());
     for (BasicBlock predecessor : predecessors) {
       if (!predecessor.hasCatchSuccessor(this)) {
@@ -1808,7 +1808,7 @@
       newBlock.getMutableSuccessors().add(this);
       newBlock.getMutablePredecessors().add(predecessor);
       predecessor.replaceSuccessor(this, newBlock);
-      onNewBlock.accept(newBlock);
+      blockIterator.add(newBlock);
       assert newBlock.getNumber() >= 0 : "Number must be assigned by `onNewBlock`";
     }
     // Replace the blocks predecessors with the new ones.
diff --git a/src/main/java/com/android/tools/r8/ir/code/BasicBlockInstructionListIterator.java b/src/main/java/com/android/tools/r8/ir/code/BasicBlockInstructionListIterator.java
index 54354f6..ee770e8 100644
--- a/src/main/java/com/android/tools/r8/ir/code/BasicBlockInstructionListIterator.java
+++ b/src/main/java/com/android/tools/r8/ir/code/BasicBlockInstructionListIterator.java
@@ -123,6 +123,26 @@
     metadata.record(instruction);
   }
 
+  @Override
+  public void addThrowingInstructionToPossiblyThrowingBlock(
+      IRCode code,
+      ListIterator<BasicBlock> blockIterator,
+      Instruction instruction,
+      InternalOptions options) {
+    if (block.hasCatchHandlers()) {
+      BasicBlock splitBlock = split(code, blockIterator, false);
+      splitBlock.listIterator(code).add(instruction);
+      assert !block.hasCatchHandlers();
+      assert splitBlock.hasCatchHandlers();
+      block.copyCatchHandlers(code, blockIterator, splitBlock, options);
+      while (IteratorUtils.peekPrevious(blockIterator) != splitBlock) {
+        blockIterator.previous();
+      }
+    } else {
+      add(instruction);
+    }
+  }
+
   /**
    * Replaces the last instruction returned by {@link #next} or {@link #previous} with the specified
    * instruction.
diff --git a/src/main/java/com/android/tools/r8/ir/code/IRCode.java b/src/main/java/com/android/tools/r8/ir/code/IRCode.java
index 8e7236c..01cf2ba 100644
--- a/src/main/java/com/android/tools/r8/ir/code/IRCode.java
+++ b/src/main/java/com/android/tools/r8/ir/code/IRCode.java
@@ -23,6 +23,7 @@
 import com.android.tools.r8.ir.code.Phi.RegisterReadType;
 import com.android.tools.r8.ir.conversion.IRBuilder;
 import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.CfgPrinter;
 import com.android.tools.r8.utils.DequeUtils;
 import com.android.tools.r8.utils.InternalOptions;
@@ -1064,6 +1065,11 @@
     return nextInstructionNumber;
   }
 
+  public int getNumberOfArguments() {
+    return context().getReference().getArity()
+        + BooleanUtils.intValue(!context().getDefinition().isStatic());
+  }
+
   public List<Value> collectArguments() {
     return collectArguments(false);
   }
diff --git a/src/main/java/com/android/tools/r8/ir/code/IRCodeInstructionListIterator.java b/src/main/java/com/android/tools/r8/ir/code/IRCodeInstructionListIterator.java
index d2b9ffb..a96760f 100644
--- a/src/main/java/com/android/tools/r8/ir/code/IRCodeInstructionListIterator.java
+++ b/src/main/java/com/android/tools/r8/ir/code/IRCodeInstructionListIterator.java
@@ -161,6 +161,16 @@
   }
 
   @Override
+  public void addThrowingInstructionToPossiblyThrowingBlock(
+      IRCode code,
+      ListIterator<BasicBlock> blockIterator,
+      Instruction instruction,
+      InternalOptions options) {
+    instructionIterator.addThrowingInstructionToPossiblyThrowingBlock(
+        code, blockIterator, instruction, options);
+  }
+
+  @Override
   public void remove() {
     instructionIterator.remove();
   }
diff --git a/src/main/java/com/android/tools/r8/ir/code/InstructionListIterator.java b/src/main/java/com/android/tools/r8/ir/code/InstructionListIterator.java
index 1acdb98..c4cceea 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InstructionListIterator.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InstructionListIterator.java
@@ -20,6 +20,12 @@
 public interface InstructionListIterator
     extends InstructionIterator, ListIterator<Instruction>, PreviousUntilIterator<Instruction> {
 
+  void addThrowingInstructionToPossiblyThrowingBlock(
+      IRCode code,
+      ListIterator<BasicBlock> blockIterator,
+      Instruction instruction,
+      InternalOptions options);
+
   default void addBefore(Instruction instruction) {
     previous();
     add(instruction);
diff --git a/src/main/java/com/android/tools/r8/ir/code/LinearFlowInstructionListIterator.java b/src/main/java/com/android/tools/r8/ir/code/LinearFlowInstructionListIterator.java
index e6009dd..0fc2768 100644
--- a/src/main/java/com/android/tools/r8/ir/code/LinearFlowInstructionListIterator.java
+++ b/src/main/java/com/android/tools/r8/ir/code/LinearFlowInstructionListIterator.java
@@ -131,6 +131,16 @@
   }
 
   @Override
+  public void addThrowingInstructionToPossiblyThrowingBlock(
+      IRCode code,
+      ListIterator<BasicBlock> blockIterator,
+      Instruction instruction,
+      InternalOptions options) {
+    currentBlockIterator.addThrowingInstructionToPossiblyThrowingBlock(
+        code, blockIterator, instruction, options);
+  }
+
+  @Override
   public void removeOrReplaceByDebugLocalRead() {
     currentBlockIterator.removeOrReplaceByDebugLocalRead();
   }
diff --git a/src/main/java/com/android/tools/r8/ir/code/Value.java b/src/main/java/com/android/tools/r8/ir/code/Value.java
index b842c63..0f0d6e9 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Value.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Value.java
@@ -30,6 +30,7 @@
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.position.MethodPosition;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.LongInterval;
 import com.android.tools.r8.utils.Reporter;
 import com.android.tools.r8.utils.SetUtils;
@@ -778,6 +779,11 @@
     return isConstant() && getConstInstruction().isConstNumber();
   }
 
+  public boolean isConstBoolean(boolean value) {
+    return isConstNumber()
+        && definition.asConstNumber().getRawValue() == BooleanUtils.longValue(value);
+  }
+
   public boolean isConstZero() {
     return isConstNumber() && definition.asConstNumber().isZero();
   }
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java b/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java
index 76e3b0f..b6609b8 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java
@@ -55,6 +55,7 @@
 import com.android.tools.r8.graph.RewrittenPrototypeDescription.RewrittenTypeInfo;
 import com.android.tools.r8.graph.classmerging.VerticallyMergedClasses;
 import com.android.tools.r8.ir.analysis.type.DestructivePhiTypeUpdater;
+import com.android.tools.r8.ir.analysis.type.Nullability;
 import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.analysis.value.SingleNumberValue;
 import com.android.tools.r8.ir.code.Assume;
@@ -64,6 +65,7 @@
 import com.android.tools.r8.ir.code.ConstClass;
 import com.android.tools.r8.ir.code.ConstInstruction;
 import com.android.tools.r8.ir.code.ConstMethodHandle;
+import com.android.tools.r8.ir.code.FieldInstruction;
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.InitClass;
 import com.android.tools.r8.ir.code.InstanceGet;
@@ -92,6 +94,7 @@
 import com.android.tools.r8.ir.optimize.enums.EnumUnboxer;
 import com.android.tools.r8.logging.Log;
 import com.android.tools.r8.optimize.MemberRebindingAnalysis;
+import com.android.tools.r8.utils.InternalOptions;
 import com.google.common.collect.Sets;
 import java.util.ArrayList;
 import java.util.HashSet;
@@ -105,14 +108,15 @@
 public class LensCodeRewriter {
 
   private final AppView<? extends AppInfoWithClassHierarchy> appView;
-
   private final EnumUnboxer enumUnboxer;
   private final LensCodeRewriterUtils helper;
+  private final InternalOptions options;
 
   LensCodeRewriter(AppView<? extends AppInfoWithClassHierarchy> appView, EnumUnboxer enumUnboxer) {
     this.appView = appView;
     this.enumUnboxer = enumUnboxer;
     this.helper = new LensCodeRewriterUtils(appView);
+    this.options = appView.options();
   }
 
   private Value makeOutValue(Instruction insn, IRCode code) {
@@ -125,6 +129,15 @@
     return null;
   }
 
+  private Value makeOutValue(FieldInstruction insn, IRCode code, DexField rewrittenField) {
+    if (insn.hasOutValue()) {
+      Nullability nullability = insn.getOutType().nullability();
+      TypeElement newType = TypeElement.fromDexType(rewrittenField.getType(), nullability, appView);
+      return code.createValue(newType, insn.getLocalInfo());
+    }
+    return null;
+  }
+
   /** Replace type appearances, invoke targets and field accesses with actual definitions. */
   public void rewrite(IRCode code, ProgramMethod method) {
     Set<Phi> affectedPhis =
@@ -137,7 +150,7 @@
     boolean mayHaveUnreachableBlocks = false;
     while (blocks.hasNext()) {
       BasicBlock block = blocks.next();
-      if (block.hasCatchHandlers() && appView.options().enableVerticalClassMerging) {
+      if (block.hasCatchHandlers() && options.enableVerticalClassMerging) {
         boolean anyGuardsRenamed = block.renameGuardsInCatchHandlers(graphLens);
         if (anyGuardsRenamed) {
           mayHaveUnreachableBlocks |= unlinkDeadCatchHandlers(block);
@@ -312,9 +325,7 @@
                                             parameter.getTypeElement(appView, type), null));
                                 assert !instruction.instructionTypeCanThrow();
                                 instruction.setPosition(
-                                    appView.options().debug
-                                        ? invoke.getPosition()
-                                        : Position.none());
+                                    options.debug ? invoke.getPosition() : Position.none());
                                 iterator.add(instruction);
                                 iterator.next();
                                 return instruction.outValue();
@@ -389,11 +400,11 @@
                   graphLens.lookupGetFieldForMethod(rewrittenField, method.getReference());
               Value newOutValue = null;
               if (replacementMethod != null) {
-                newOutValue = makeOutValue(instanceGet, code);
+                newOutValue = makeOutValue(instanceGet, code, rewrittenField);
                 iterator.replaceCurrentInstruction(
                     new InvokeStatic(replacementMethod, newOutValue, instanceGet.inValues()));
               } else if (rewrittenField != field) {
-                newOutValue = makeOutValue(instanceGet, code);
+                newOutValue = makeOutValue(instanceGet, code, rewrittenField);
                 iterator.replaceCurrentInstruction(
                     new InstanceGet(newOutValue, instanceGet.object(), rewrittenField));
               }
@@ -402,15 +413,17 @@
                   TypeElement castType =
                       TypeElement.fromDexType(
                           lookup.getCastType(), newOutValue.getType().nullability(), appView);
+                  Value castOutValue = code.createValue(castType);
+                  newOutValue.replaceUsers(castOutValue);
                   CheckCast checkCast =
                       CheckCast.builder()
                           .setCastType(lookup.getCastType())
-                          .setFreshOutValue(code, castType)
                           .setObject(newOutValue)
+                          .setOutValue(castOutValue)
                           .setPosition(instanceGet)
                           .build();
-                  iterator.add(checkCast);
-                  newOutValue.replaceUsers(checkCast.outValue());
+                  iterator.addThrowingInstructionToPossiblyThrowingBlock(
+                      code, blocks, checkCast, options);
                   affectedPhis.addAll(checkCast.outValue().uniquePhiUsers());
                 } else if (newOutValue.getType() != instanceGet.getOutType()) {
                   affectedPhis.addAll(newOutValue.uniquePhiUsers());
@@ -452,11 +465,11 @@
                   graphLens.lookupGetFieldForMethod(rewrittenField, method.getReference());
               Value newOutValue = null;
               if (replacementMethod != null) {
-                newOutValue = makeOutValue(staticGet, code);
+                newOutValue = makeOutValue(staticGet, code, rewrittenField);
                 iterator.replaceCurrentInstruction(
                     new InvokeStatic(replacementMethod, newOutValue, staticGet.inValues()));
               } else if (rewrittenField != field) {
-                newOutValue = makeOutValue(staticGet, code);
+                newOutValue = makeOutValue(staticGet, code, rewrittenField);
                 iterator.replaceCurrentInstruction(new StaticGet(newOutValue, rewrittenField));
               }
               if (newOutValue != null) {
@@ -464,15 +477,17 @@
                   TypeElement castType =
                       TypeElement.fromDexType(
                           lookup.getCastType(), newOutValue.getType().nullability(), appView);
+                  Value castOutValue = code.createValue(castType);
+                  newOutValue.replaceUsers(castOutValue);
                   CheckCast checkCast =
                       CheckCast.builder()
                           .setCastType(lookup.getCastType())
-                          .setFreshOutValue(code, castType)
                           .setObject(newOutValue)
+                          .setOutValue(castOutValue)
                           .setPosition(staticGet)
                           .build();
-                  iterator.add(checkCast);
-                  newOutValue.replaceUsers(checkCast.outValue());
+                  iterator.addThrowingInstructionToPossiblyThrowingBlock(
+                      code, blocks, checkCast, options);
                   affectedPhis.addAll(checkCast.outValue().uniquePhiUsers());
                 } else if (newOutValue.getType() != staticGet.getOutType()) {
                   affectedPhis.addAll(newOutValue.uniquePhiUsers());
@@ -554,8 +569,7 @@
               MoveException moveException = current.asMoveException();
               new InstructionReplacer(code, current, iterator, affectedPhis)
                   .replaceInstructionIfTypeChanged(
-                      moveException.getExceptionType(),
-                      (t, v) -> new MoveException(v, t, appView.options()));
+                      moveException.getExceptionType(), (t, v) -> new MoveException(v, t, options));
             }
             break;
 
@@ -695,7 +709,7 @@
       iterator.previous();
       Value rewrittenDefaultValue =
           iterator.insertConstNumberInstruction(
-              code, appView.options(), 0, defaultValueLatticeElement(newType));
+              code, options, 0, defaultValueLatticeElement(newType));
       iterator.next();
       return rewrittenDefaultValue;
     }
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/MethodOptimizationFeedback.java b/src/main/java/com/android/tools/r8/ir/conversion/MethodOptimizationFeedback.java
index 44f5990..b1b14fb 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/MethodOptimizationFeedback.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/MethodOptimizationFeedback.java
@@ -7,6 +7,8 @@
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.ir.analysis.inlining.SimpleInliningConstraint;
 import com.android.tools.r8.ir.analysis.type.ClassTypeElement;
 import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.analysis.value.AbstractValue;
@@ -71,5 +73,7 @@
 
   void setNonNullParamOnNormalExits(DexEncodedMethod method, BitSet facts);
 
+  void setSimpleInliningConstraint(ProgramMethod method, SimpleInliningConstraint constraint);
+
   void classInitializerMayBePostponed(DexEncodedMethod method);
 }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java b/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java
index 6e7d98b..4534c9b 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java
@@ -18,6 +18,7 @@
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.graph.ResolutionResult.SingleResolutionResult;
 import com.android.tools.r8.ir.analysis.ClassInitializationAnalysis;
+import com.android.tools.r8.ir.analysis.inlining.SimpleInliningConstraint;
 import com.android.tools.r8.ir.code.BasicBlock;
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.InstancePut;
@@ -203,7 +204,9 @@
       return false;
     }
     assert reason != Reason.FORCE
-        || !inlineeRefersToClassesNotInMainDex(method.getHolderType(), singleTarget);
+            || !inlineeRefersToClassesNotInMainDex(method.getHolderType(), singleTarget)
+        : MainDexDirectReferenceTracer.getFirstReferenceOutsideFromCode(
+            appView.appInfo(), singleTarget, inliner.mainDexClasses.getRoots());
     return true;
   }
 
@@ -223,7 +226,11 @@
     if (code.estimatedSizeForInliningAtMost(instructionLimit)) {
       return true;
     }
-    return false;
+    // Even if the inlinee is big it may become simple after inlining. We therefore check if the
+    // inlinee's simple inlining constraint is satisfied by the invoke.
+    SimpleInliningConstraint simpleInliningConstraint =
+        target.getDefinition().getOptimizationInfo().getSimpleInliningConstraint();
+    return simpleInliningConstraint.isSatisfied(invoke);
   }
 
   private int computeInstructionLimit(InvokeMethod invoke, ProgramMethod candidate) {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/MemberValuePropagation.java b/src/main/java/com/android/tools/r8/ir/optimize/MemberValuePropagation.java
index 615e5ae..39ce729 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/MemberValuePropagation.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/MemberValuePropagation.java
@@ -360,7 +360,9 @@
     }
 
     AbstractValue abstractValue;
-    if (appView.appInfo().isFieldWrittenByFieldPutInstruction(target)) {
+    if (field.getType().isAlwaysNull(appView)) {
+      abstractValue = appView.abstractValueFactory().createSingleNumberValue(0);
+    } else if (appView.appInfo().isFieldWrittenByFieldPutInstruction(target)) {
       abstractValue = target.getOptimizationInfo().getAbstractValue();
       if (abstractValue.isUnknown() && !target.isStatic()) {
         AbstractValue abstractReceiverValue =
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/UninstantiatedTypeOptimization.java b/src/main/java/com/android/tools/r8/ir/optimize/UninstantiatedTypeOptimization.java
index c681eaa..d60ba25 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/UninstantiatedTypeOptimization.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/UninstantiatedTypeOptimization.java
@@ -14,8 +14,6 @@
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexProto;
 import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.graph.FieldAccessInfo;
-import com.android.tools.r8.graph.FieldAccessInfoCollection;
 import com.android.tools.r8.graph.GraphLens.NestedGraphLens;
 import com.android.tools.r8.graph.RewrittenPrototypeDescription;
 import com.android.tools.r8.graph.RewrittenPrototypeDescription.ArgumentInfoCollection;
@@ -97,18 +95,11 @@
 
   public UninstantiatedTypeOptimization strenghtenOptimizationInfo() {
     OptimizationFeedback feedback = OptimizationFeedbackSimple.getInstance();
-    FieldAccessInfoCollection<?> fieldAccessInfoCollection =
-        appView.appInfo().getFieldAccessInfoCollection();
     AbstractValue nullValue = appView.abstractValueFactory().createSingleNumberValue(0);
     for (DexProgramClass clazz : appView.appInfo().classes()) {
       clazz.forEachField(
           field -> {
             if (field.type().isAlwaysNull(appView)) {
-              FieldAccessInfo fieldAccessInfo = fieldAccessInfoCollection.get(field.field);
-              if (fieldAccessInfo != null) {
-                // Clear all writes since each write must write `null` to the field.
-                fieldAccessInfo.asMutable().clearWrites();
-              }
               feedback.recordFieldHasAbstractValue(field, appView, nullValue);
             }
           });
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java
index e080808..143d776 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java
@@ -237,8 +237,11 @@
       for (Instruction user : currentUsers) {
         if (user.isAssume() || user.isCheckCast()) {
           if (user.isCheckCast()) {
+            CheckCast checkCast = user.asCheckCast();
+            // TODO(b/175863158): Allow unsafe casts by rewriting into throw new ClassCastException.
             boolean isCheckCastUnsafe =
-                !appView.appInfo().isSubtype(eligibleClass.type, user.asCheckCast().getType());
+                !checkCast.getType().isClassType()
+                    || !appView.appInfo().isSubtype(eligibleClass.type, checkCast.getType());
             if (isCheckCastUnsafe) {
               return user; // Not eligible.
             }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxer.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxer.java
index 1ce0df3..166022e 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxer.java
@@ -22,7 +22,6 @@
 import com.android.tools.r8.graph.DirectMappedDexApplication;
 import com.android.tools.r8.graph.FieldResolutionResult;
 import com.android.tools.r8.graph.GraphLens;
-import com.android.tools.r8.graph.GraphLens.NestedGraphLens;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.graph.ProgramPackageCollection;
 import com.android.tools.r8.graph.ResolutionResult;
@@ -62,6 +61,7 @@
 import com.android.tools.r8.ir.optimize.info.MethodOptimizationInfo;
 import com.android.tools.r8.ir.optimize.info.OptimizationFeedback.OptimizationInfoFixer;
 import com.android.tools.r8.ir.optimize.info.OptimizationFeedbackDelayed;
+import com.android.tools.r8.ir.optimize.info.UpdatableMethodOptimizationInfo;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.shaking.KeepInfoCollection;
 import com.android.tools.r8.utils.BooleanUtils;
@@ -394,7 +394,7 @@
                 enumClassesToUnbox, enumsToUnboxWithPackageRequirement, appBuilder)
             .build();
     enumUnboxerRewriter = new EnumUnboxingRewriter(appView, enumDataMap, relocator);
-    NestedGraphLens enumUnboxingLens =
+    EnumUnboxingLens enumUnboxingLens =
         new EnumUnboxingTreeFixer(appView, enumsToUnbox, relocator, enumUnboxerRewriter)
             .fixupTypeReferences();
     enumUnboxerRewriter.setEnumUnboxingLens(enumUnboxingLens);
@@ -430,8 +430,9 @@
           public void fixup(DexEncodedMethod method) {
             MethodOptimizationInfo optimizationInfo = method.getOptimizationInfo();
             if (optimizationInfo.isUpdatableMethodOptimizationInfo()) {
-              optimizationInfo
-                  .asUpdatableMethodOptimizationInfo()
+              UpdatableMethodOptimizationInfo updatableOptimizationInfo =
+                  optimizationInfo.asUpdatableMethodOptimizationInfo();
+              updatableOptimizationInfo
                   .fixupClassTypeReferences(appView.graphLens()::lookupType, appView)
                   .fixupAbstractReturnValue(appView, appView.graphLens())
                   .fixupInstanceInitializerInfo(appView, appView.graphLens());
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingLens.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingLens.java
index f230dda..c64e51e 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingLens.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingLens.java
@@ -142,9 +142,7 @@
 
     public EnumUnboxingLens build(
         DexItemFactory dexItemFactory, GraphLens previousLens, Set<DexType> unboxedEnums) {
-      if (typeMap.isEmpty() && newFieldSignatures.isEmpty() && originalMethodSignatures.isEmpty()) {
-        return null;
-      }
+      assert !typeMap.isEmpty();
       return new EnumUnboxingLens(
           typeMap,
           originalMethodSignatures.getInverseOneToOneMap().getForwardMap(),
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingTreeFixer.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingTreeFixer.java
index 92ce5a1..f2f70ca 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingTreeFixer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingTreeFixer.java
@@ -16,9 +16,11 @@
 import com.android.tools.r8.graph.DexString;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.DexValue;
-import com.android.tools.r8.graph.GraphLens;
+import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.OptionalBool;
 import com.google.common.collect.Sets;
+import it.unimi.dsi.fastutil.ints.IntArrayList;
+import it.unimi.dsi.fastutil.ints.IntList;
 import java.util.ArrayList;
 import java.util.IdentityHashMap;
 import java.util.List;
@@ -47,7 +49,7 @@
     this.enumUnboxerRewriter = enumUnboxerRewriter;
   }
 
-  GraphLens.NestedGraphLens fixupTypeReferences() {
+  EnumUnboxingLens fixupTypeReferences() {
     assert enumUnboxerRewriter != null;
     // Fix all methods and fields using enums to unbox.
     for (DexProgramClass clazz : appView.appInfo().classes()) {
@@ -118,11 +120,14 @@
   }
 
   private DexEncodedMethod fixupEncodedMethod(DexEncodedMethod method) {
-    DexProto newProto = fixupProto(method.proto());
+    DexProto oldProto = method.proto();
+    DexProto newProto = fixupProto(oldProto);
     if (newProto == method.proto()) {
       return method;
     }
     assert !method.isClassInitializer();
+    assert !method.isLibraryMethodOverride().isTrue()
+        : "Enum unboxing is changing the signature of a library override in a non unboxed class.";
     // We add the $enumunboxing$ suffix to make sure we do not create a library override.
     String newMethodName =
         method.getName().toString() + (method.isNonPrivateVirtualMethod() ? "$enumunboxing$" : "");
@@ -131,17 +136,28 @@
     int numberOfExtraNullParameters = newMethod.getArity() - method.method.getArity();
     boolean isStatic = method.isStatic();
     lensBuilder.move(method.method, newMethod, isStatic, isStatic, numberOfExtraNullParameters);
-    DexEncodedMethod newEncodedMethod =
-        method.toTypeSubstitutedMethod(
-            newMethod,
-            builder ->
-                builder
-                    .setCompilationState(method.getCompilationState())
-                    .setIsLibraryMethodOverrideIf(
-                        method.isNonPrivateVirtualMethod(), OptionalBool.FALSE));
-    assert !method.isLibraryMethodOverride().isTrue()
-        : "Enum unboxing is changing the signature of a library override in a non unboxed class.";
-    return newEncodedMethod;
+    return method.toTypeSubstitutedMethod(
+        newMethod,
+        builder ->
+            builder
+                .setCompilationState(method.getCompilationState())
+                .setIsLibraryMethodOverrideIf(
+                    method.isNonPrivateVirtualMethod(), OptionalBool.FALSE)
+                .fixupOptimizationInfo(
+                    optimizationInfo -> {
+                      IntList unboxedArgumentIndices = new IntArrayList();
+                      int offset = BooleanUtils.intValue(method.isInstance());
+                      for (int i = 0; i < method.getReference().getArity(); i++) {
+                        if (oldProto.getParameter(i).isReferenceType()
+                            && newProto.getParameter(i).isPrimitiveType()) {
+                          unboxedArgumentIndices.add(i + offset);
+                        }
+                      }
+                      optimizationInfo.setSimpleInliningConstraint(
+                          optimizationInfo
+                              .getSimpleInliningConstraint()
+                              .rewrittenWithUnboxedArguments(unboxedArgumentIndices));
+                    }));
   }
 
   private DexMethod ensureUniqueMethod(DexEncodedMethod encodedMethod, DexMethod newMethod) {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/DefaultMethodOptimizationInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/DefaultMethodOptimizationInfo.java
index b2b10ae..a9854ee 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/DefaultMethodOptimizationInfo.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/DefaultMethodOptimizationInfo.java
@@ -5,6 +5,8 @@
 package com.android.tools.r8.ir.optimize.info;
 
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.ir.analysis.inlining.NeverSimpleInliningConstraint;
+import com.android.tools.r8.ir.analysis.inlining.SimpleInliningConstraint;
 import com.android.tools.r8.ir.analysis.type.ClassTypeElement;
 import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.analysis.value.AbstractValue;
@@ -21,11 +23,11 @@
 
 public class DefaultMethodOptimizationInfo extends MethodOptimizationInfo {
 
-  public static final MethodOptimizationInfo DEFAULT_INSTANCE = new DefaultMethodOptimizationInfo();
+  public static final DefaultMethodOptimizationInfo DEFAULT_INSTANCE =
+      new DefaultMethodOptimizationInfo();
 
   static Set<DexType> UNKNOWN_INITIALIZED_CLASSES_ON_NORMAL_EXIT = ImmutableSet.of();
   static int UNKNOWN_RETURNED_ARGUMENT = -1;
-  static boolean UNKNOWN_NEVER_RETURNS_NULL = false;
   static boolean UNKNOWN_NEVER_RETURNS_NORMALLY = false;
   static AbstractValue UNKNOWN_ABSTRACT_RETURN_VALUE = UnknownValue.getInstance();
   static TypeElement UNKNOWN_TYPE = null;
@@ -42,6 +44,10 @@
 
   private DefaultMethodOptimizationInfo() {}
 
+  public static DefaultMethodOptimizationInfo getInstance() {
+    return DEFAULT_INSTANCE;
+  }
+
   @Override
   public boolean isDefaultMethodOptimizationInfo() {
     return true;
@@ -150,6 +156,11 @@
   }
 
   @Override
+  public SimpleInliningConstraint getSimpleInliningConstraint() {
+    return NeverSimpleInliningConstraint.getInstance();
+  }
+
+  @Override
   public boolean isInitializerEnablingJavaVmAssertions() {
     return UNKNOWN_INITIALIZER_ENABLING_JAVA_ASSERTIONS;
   }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfo.java
index cf7c4ee..7f5d173 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfo.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfo.java
@@ -5,6 +5,7 @@
 package com.android.tools.r8.ir.optimize.info;
 
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.ir.analysis.inlining.SimpleInliningConstraint;
 import com.android.tools.r8.ir.analysis.type.ClassTypeElement;
 import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.analysis.value.AbstractValue;
@@ -77,6 +78,8 @@
 
   public abstract AbstractValue getAbstractReturnValue();
 
+  public abstract SimpleInliningConstraint getSimpleInliningConstraint();
+
   public abstract boolean forceInline();
 
   public abstract boolean neverInline();
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfoCollector.java b/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfoCollector.java
index 4e3fb01..b5dfee6 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfoCollector.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfoCollector.java
@@ -60,6 +60,7 @@
 import com.android.tools.r8.ir.analysis.ClassInitializationAnalysis.AnalysisAssumption;
 import com.android.tools.r8.ir.analysis.DeterminismAnalysis;
 import com.android.tools.r8.ir.analysis.InitializedClassesOnNormalExitAnalysis;
+import com.android.tools.r8.ir.analysis.inlining.SimpleInliningConstraintAnalysis;
 import com.android.tools.r8.ir.analysis.sideeffect.ClassInitializerSideEffectAnalysis;
 import com.android.tools.r8.ir.analysis.sideeffect.ClassInitializerSideEffectAnalysis.ClassInitializerSideEffect;
 import com.android.tools.r8.ir.analysis.type.ClassTypeElement;
@@ -148,6 +149,7 @@
     if (options.enableInlining) {
       identifyInvokeSemanticsForInlining(definition, code, feedback, timing);
     }
+    computeSimpleInliningConstraint(method, code, feedback, timing);
     computeDynamicReturnType(dynamicTypeOptimization, feedback, definition, code, timing);
     computeInitializedClassesOnNormalExit(feedback, definition, code, timing);
     computeInstanceInitializerInfo(
@@ -967,6 +969,21 @@
     return true;
   }
 
+  private void computeSimpleInliningConstraint(
+      ProgramMethod method, IRCode code, OptimizationFeedback feedback, Timing timing) {
+    if (appView.options().enableSimpleInliningConstraints) {
+      timing.begin("Compute simple inlining constraint");
+      computeSimpleInliningConstraint(method, code, feedback);
+      timing.end();
+    }
+  }
+
+  private void computeSimpleInliningConstraint(
+      ProgramMethod method, IRCode code, OptimizationFeedback feedback) {
+    feedback.setSimpleInliningConstraint(
+        method, new SimpleInliningConstraintAnalysis(appView, method).analyzeCode(code));
+  }
+
   private void computeDynamicReturnType(
       DynamicTypeOptimization dynamicTypeOptimization,
       OptimizationFeedback feedback,
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackDelayed.java b/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackDelayed.java
index 50cb793..1236ed6 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackDelayed.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackDelayed.java
@@ -8,6 +8,8 @@
 import com.android.tools.r8.graph.DexEncodedField;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.ir.analysis.inlining.SimpleInliningConstraint;
 import com.android.tools.r8.ir.analysis.type.ClassTypeElement;
 import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.analysis.value.AbstractValue;
@@ -60,6 +62,11 @@
     return info;
   }
 
+  private UpdatableMethodOptimizationInfo getMethodOptimizationInfoForUpdating(
+      ProgramMethod method) {
+    return getMethodOptimizationInfoForUpdating(method.getDefinition());
+  }
+
   @Override
   public void fixupOptimizationInfos(
       AppView<?> appView, ExecutorService executorService, OptimizationInfoFixer fixer)
@@ -283,6 +290,12 @@
   }
 
   @Override
+  public synchronized void setSimpleInliningConstraint(
+      ProgramMethod method, SimpleInliningConstraint constraint) {
+    getMethodOptimizationInfoForUpdating(method).setSimpleInliningConstraint(constraint);
+  }
+
+  @Override
   public synchronized void classInitializerMayBePostponed(DexEncodedMethod method) {
     getMethodOptimizationInfoForUpdating(method).markClassInitializerMayBePostponed();
   }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackIgnore.java b/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackIgnore.java
index 12fda15..299f7ce 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackIgnore.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackIgnore.java
@@ -8,6 +8,8 @@
 import com.android.tools.r8.graph.DexEncodedField;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.ir.analysis.inlining.SimpleInliningConstraint;
 import com.android.tools.r8.ir.analysis.type.ClassTypeElement;
 import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.analysis.value.AbstractValue;
@@ -130,8 +132,11 @@
   public void setNonNullParamOrThrow(DexEncodedMethod method, BitSet facts) {}
 
   @Override
-  public void setNonNullParamOnNormalExits(DexEncodedMethod method, BitSet facts) {
-  }
+  public void setNonNullParamOnNormalExits(DexEncodedMethod method, BitSet facts) {}
+
+  @Override
+  public void setSimpleInliningConstraint(
+      ProgramMethod method, SimpleInliningConstraint constraint) {}
 
   @Override
   public void classInitializerMayBePostponed(DexEncodedMethod method) {}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackSimple.java b/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackSimple.java
index f0d7c28..ed477a6 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackSimple.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackSimple.java
@@ -8,6 +8,8 @@
 import com.android.tools.r8.graph.DexEncodedField;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.ir.analysis.inlining.SimpleInliningConstraint;
 import com.android.tools.r8.ir.analysis.type.ClassTypeElement;
 import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.analysis.value.AbstractValue;
@@ -194,6 +196,12 @@
   }
 
   @Override
+  public void setSimpleInliningConstraint(
+      ProgramMethod method, SimpleInliningConstraint constraint) {
+    // Ignored.
+  }
+
+  @Override
   public void classInitializerMayBePostponed(DexEncodedMethod method) {
     method.getMutableOptimizationInfo().markClassInitializerMayBePostponed();
   }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/UpdatableMethodOptimizationInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/UpdatableMethodOptimizationInfo.java
index 877c72d..03e83b8 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/UpdatableMethodOptimizationInfo.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/UpdatableMethodOptimizationInfo.java
@@ -8,6 +8,8 @@
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.GraphLens;
+import com.android.tools.r8.ir.analysis.inlining.NeverSimpleInliningConstraint;
+import com.android.tools.r8.ir.analysis.inlining.SimpleInliningConstraint;
 import com.android.tools.r8.ir.analysis.type.ClassTypeElement;
 import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.analysis.value.AbstractValue;
@@ -63,6 +65,9 @@
   private BitSet nonNullParamOnNormalExits =
       DefaultMethodOptimizationInfo.NO_NULL_PARAMETER_ON_NORMAL_EXITS_FACTS;
 
+  private SimpleInliningConstraint simpleInliningConstraint =
+      NeverSimpleInliningConstraint.getInstance();
+
   // To reduce the memory footprint of UpdatableMethodOptimizationInfo, all the boolean fields are
   // merged into a flag int field. The various static final FLAG fields indicate which bit is
   // used by each boolean. DEFAULT_FLAGS encodes the default value for efficient instantiation and
@@ -137,6 +142,7 @@
     returnsObjectWithUpperBoundType = template.returnsObjectWithUpperBoundType;
     returnsObjectWithLowerBoundType = template.returnsObjectWithLowerBoundType;
     inlining = template.inlining;
+    simpleInliningConstraint = template.simpleInliningConstraint;
     bridgeInfo = template.bridgeInfo;
     classInlinerEligibility = template.classInlinerEligibility;
     instanceInitializerInfoCollection = template.instanceInitializerInfoCollection;
@@ -322,6 +328,11 @@
   }
 
   @Override
+  public SimpleInliningConstraint getSimpleInliningConstraint() {
+    return simpleInliningConstraint;
+  }
+
+  @Override
   public boolean isInitializerEnablingJavaVmAssertions() {
     return isFlagSet(INITIALIZER_ENABLING_JAVA_ASSERTIONS_FLAG);
   }
@@ -372,6 +383,10 @@
     setFlag(REACHABILITY_SENSITIVE_FLAG, reachabilitySensitive);
   }
 
+  public void setSimpleInliningConstraint(SimpleInliningConstraint constraint) {
+    this.simpleInliningConstraint = constraint;
+  }
+
   void setClassInlinerEligibility(ClassInlinerEligibilityInfo eligibility) {
     this.classInlinerEligibility = eligibility;
   }
@@ -486,7 +501,6 @@
 
   @Override
   public UpdatableMethodOptimizationInfo mutableCopy() {
-    assert this != DefaultMethodOptimizationInfo.DEFAULT_INSTANCE;
     return new UpdatableMethodOptimizationInfo(this);
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/inliner/DefaultInliningReasonStrategy.java b/src/main/java/com/android/tools/r8/ir/optimize/inliner/DefaultInliningReasonStrategy.java
index bb270b9..e6b5afe 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/inliner/DefaultInliningReasonStrategy.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/inliner/DefaultInliningReasonStrategy.java
@@ -51,7 +51,7 @@
       // program.
       return Reason.SIMPLE;
     }
-    if (callSiteInformation.hasSingleCallSite(target)) {
+    if (isSingleCallerInliningTarget(target)) {
       return Reason.SINGLE_CALLER;
     }
     if (isDoubleInliningTarget(target)) {
@@ -60,6 +60,20 @@
     return Reason.SIMPLE;
   }
 
+  private boolean isSingleCallerInliningTarget(ProgramMethod method) {
+    if (!callSiteInformation.hasSingleCallSite(method)) {
+      return false;
+    }
+    if (appView.appInfo().isNeverInlineDueToSingleCallerMethod(method)) {
+      return false;
+    }
+    if (appView.options().testing.validInliningReasons != null
+        && !appView.options().testing.validInliningReasons.contains(Reason.SINGLE_CALLER)) {
+      return false;
+    }
+    return true;
+  }
+
   private boolean isDoubleInliningTarget(ProgramMethod candidate) {
     // 10 is found from measuring.
     if (callSiteInformation.hasDoubleCallSite(candidate)
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataEnqueuerExtension.java b/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataEnqueuerExtension.java
index 189a6b0..f9d2d18 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataEnqueuerExtension.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataEnqueuerExtension.java
@@ -6,7 +6,6 @@
 
 import static com.android.tools.r8.kotlin.KotlinClassMetadataReader.hasKotlinClassMetadataAnnotation;
 import static com.android.tools.r8.kotlin.KotlinMetadataUtils.NO_KOTLIN_INFO;
-import static com.android.tools.r8.utils.FunctionUtils.forEachApply;
 
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexClass;
@@ -17,20 +16,30 @@
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.EnclosingMethodAttribute;
+import com.android.tools.r8.graph.ProgramDefinition;
 import com.android.tools.r8.graph.analysis.EnqueuerAnalysis;
 import com.android.tools.r8.shaking.Enqueuer;
+import com.android.tools.r8.shaking.Enqueuer.EnqueuerDefinitionSupplier;
 import com.google.common.collect.Sets;
 import java.util.Set;
 
 public class KotlinMetadataEnqueuerExtension extends EnqueuerAnalysis {
 
   private final AppView<?> appView;
-  private final DexDefinitionSupplier definitionSupplier;
+  private final EnqueuerDefinitionSupplier enqueuerDefinitionSupplier;
+  private final Set<DexType> prunedTypes;
 
   public KotlinMetadataEnqueuerExtension(
-      AppView<?> appView, DexDefinitionSupplier definitionSupplier, Set<DexType> prunedTypes) {
+      AppView<?> appView,
+      EnqueuerDefinitionSupplier enqueuerDefinitionSupplier,
+      Set<DexType> prunedTypes) {
     this.appView = appView;
-    this.definitionSupplier = new KotlinMetadataDefinitionSupplier(definitionSupplier, prunedTypes);
+    this.enqueuerDefinitionSupplier = enqueuerDefinitionSupplier;
+    this.prunedTypes = prunedTypes;
+  }
+
+  private KotlinMetadataDefinitionSupplier definitionsForContext(ProgramDefinition context) {
+    return new KotlinMetadataDefinitionSupplier(context, enqueuerDefinitionSupplier, prunedTypes);
   }
 
   @Override
@@ -54,7 +63,7 @@
                 KotlinClassMetadataReader.getKotlinInfo(
                     appView.dexItemFactory().kotlin,
                     clazz,
-                    definitionSupplier.dexItemFactory(),
+                    appView.dexItemFactory(),
                     appView.options().reporter,
                     onlyProcessLambdas,
                     method -> keepByteCodeFunctions.add(method.method)));
@@ -72,7 +81,8 @@
         EnclosingMethodAttribute enclosingAttribute =
             localOrAnonymousClass.getEnclosingMethodAttribute();
         DexClass holder =
-            definitionSupplier.definitionForHolder(enclosingAttribute.getEnclosingMethod());
+            definitionsForContext(localOrAnonymousClass)
+                .definitionForHolder(enclosingAttribute.getEnclosingMethod());
         if (holder == null) {
           continue;
         }
@@ -91,11 +101,13 @@
     // Trace through the modeled kotlin metadata.
     enqueuer.forAllLiveClasses(
         clazz -> {
-          clazz.getKotlinInfo().trace(definitionSupplier);
-          forEachApply(
-              clazz.methods(), method -> method.getKotlinMemberInfo()::trace, definitionSupplier);
-          forEachApply(
-              clazz.fields(), field -> field.getKotlinMemberInfo()::trace, definitionSupplier);
+          clazz.getKotlinInfo().trace(definitionsForContext(clazz));
+          clazz.forEachProgramMember(
+              member ->
+                  member
+                      .getDefinition()
+                      .getKotlinMemberInfo()
+                      .trace(definitionsForContext(member)));
         });
   }
 
@@ -104,7 +116,7 @@
     enqueuer.forAllLiveClasses(
         clazz -> {
           // Trace through class and member definitions
-          assert !hasKotlinClassMetadataAnnotation(clazz, definitionSupplier)
+          assert !hasKotlinClassMetadataAnnotation(clazz, definitionsForContext(clazz))
               || !keepMetadata
               || !enqueuer.isPinned(clazz.type)
               || clazz.getKotlinInfo() != NO_KOTLIN_INFO;
@@ -112,14 +124,18 @@
     return true;
   }
 
-  public static class KotlinMetadataDefinitionSupplier implements DexDefinitionSupplier {
+  public class KotlinMetadataDefinitionSupplier implements DexDefinitionSupplier {
 
-    private final DexDefinitionSupplier baseSupplier;
+    private final ProgramDefinition context;
+    private final EnqueuerDefinitionSupplier enqueuerDefinitionSupplier;
     private final Set<DexType> prunedTypes;
 
     private KotlinMetadataDefinitionSupplier(
-        DexDefinitionSupplier baseSupplier, Set<DexType> prunedTypes) {
-      this.baseSupplier = baseSupplier;
+        ProgramDefinition context,
+        EnqueuerDefinitionSupplier enqueuerDefinitionSupplier,
+        Set<DexType> prunedTypes) {
+      this.context = context;
+      this.enqueuerDefinitionSupplier = enqueuerDefinitionSupplier;
       this.prunedTypes = prunedTypes;
     }
 
@@ -131,12 +147,12 @@
       if (prunedTypes != null && prunedTypes.contains(type)) {
         return null;
       }
-      return baseSupplier.definitionFor(type);
+      return enqueuerDefinitionSupplier.definitionFor(type, context);
     }
 
     @Override
     public DexItemFactory dexItemFactory() {
-      return baseSupplier.dexItemFactory();
+      return appView.dexItemFactory();
     }
   }
 }
diff --git a/src/main/java/com/android/tools/r8/repackaging/RepackagingConstraintGraph.java b/src/main/java/com/android/tools/r8/repackaging/RepackagingConstraintGraph.java
index bcbba53..191dc0a 100644
--- a/src/main/java/com/android/tools/r8/repackaging/RepackagingConstraintGraph.java
+++ b/src/main/java/com/android/tools/r8/repackaging/RepackagingConstraintGraph.java
@@ -10,7 +10,6 @@
 import com.android.tools.r8.graph.DexEncodedMember;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexProgramClass;
-import com.android.tools.r8.graph.EnclosingMethodAttribute;
 import com.android.tools.r8.graph.ProgramField;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.graph.ProgramPackage;
@@ -127,11 +126,10 @@
             innerClassAttribute ->
                 registry.registerInnerClassAttribute(clazz, innerClassAttribute));
 
-    // Trace the references from the enclosing method attribute.
-    EnclosingMethodAttribute attr = clazz.getEnclosingMethodAttribute();
-    if (attr != null) {
-      registry.registerEnclosingMethodAttribute(attr);
-    }
+    // Trace the references from the enclosing method and nest attributes.
+    registry.registerEnclosingMethodAttribute(clazz.getEnclosingMethodAttribute());
+    registry.registerNestHostAttribute(clazz.getNestHostClassAttribute());
+    registry.registerNestMemberClassAttributes(clazz.getNestMembersClassAttributes());
   }
 
   private void registerReferencesFromField(ProgramField field) {
diff --git a/src/main/java/com/android/tools/r8/repackaging/RepackagingUseRegistry.java b/src/main/java/com/android/tools/r8/repackaging/RepackagingUseRegistry.java
index 9d5cec5..a6ffc40 100644
--- a/src/main/java/com/android/tools/r8/repackaging/RepackagingUseRegistry.java
+++ b/src/main/java/com/android/tools/r8/repackaging/RepackagingUseRegistry.java
@@ -19,6 +19,8 @@
 import com.android.tools.r8.graph.InitClassLens;
 import com.android.tools.r8.graph.InnerClassAttribute;
 import com.android.tools.r8.graph.MemberResolutionResult;
+import com.android.tools.r8.graph.NestHostClassAttribute;
+import com.android.tools.r8.graph.NestMemberClassAttribute;
 import com.android.tools.r8.graph.ProgramDefinition;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.graph.ResolutionResult;
@@ -26,6 +28,7 @@
 import com.android.tools.r8.graph.UseRegistry;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.utils.InternalOptions;
+import java.util.List;
 import java.util.function.Consumer;
 import java.util.function.Predicate;
 
@@ -233,6 +236,9 @@
   }
 
   public void registerEnclosingMethodAttribute(EnclosingMethodAttribute enclosingMethodAttribute) {
+    if (enclosingMethodAttribute == null) {
+      return;
+    }
     // For references in enclosing method attributes we add an edge from the context to the
     // referenced item even if the item would be accessible from another package, to make sure that
     // we don't split such classes into different packages.
@@ -257,4 +263,26 @@
     innerClassAttribute.forEachType(
         type -> registerTypeAccess(type, clazz -> registerClassTypeAccess(clazz, alwaysTrue())));
   }
+
+  public void registerNestHostAttribute(NestHostClassAttribute nestHostClassAttribute) {
+    if (nestHostClassAttribute == null) {
+      return;
+    }
+    // JVM require nest-members to be in the same package.
+    registerTypeAccess(
+        nestHostClassAttribute.getNestHost(),
+        clazz -> registerClassTypeAccess(clazz, alwaysTrue()));
+  }
+
+  public void registerNestMemberClassAttributes(
+      List<NestMemberClassAttribute> memberClassAttributes) {
+    if (memberClassAttributes == null) {
+      return;
+    }
+    // JVM require nest-members to be in the same package.
+    memberClassAttributes.forEach(
+        nestMember ->
+            registerTypeAccess(
+                nestMember.getNestMember(), clazz -> registerClassTypeAccess(clazz, alwaysTrue())));
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/retrace/internal/RetraceUtils.java b/src/main/java/com/android/tools/r8/retrace/internal/RetraceUtils.java
index 0ca25c7..cd3e12d 100644
--- a/src/main/java/com/android/tools/r8/retrace/internal/RetraceUtils.java
+++ b/src/main/java/com/android/tools/r8/retrace/internal/RetraceUtils.java
@@ -30,8 +30,7 @@
 
 public class RetraceUtils {
 
-  private static final Set<String> UNKNOWN_SOURCEFILE_NAMES =
-      Sets.newHashSet("", "SourceFile", "Unknown", "Unknown Source", "PG");
+  private static final Set<String> KEEP_SOURCEFILE_NAMES = Sets.newHashSet("Native Method");
 
   public static String methodDescriptionFromRetraceMethod(
       RetracedMethod methodReference, boolean appendHolder, boolean verbose) {
@@ -109,24 +108,17 @@
       String minifiedClassName,
       String sourceFile,
       boolean hasRetraceResult) {
-    boolean fileNameProbablyChanged =
-        hasRetraceResult && !retracedClassName.startsWith(minifiedClassName);
-    if (!UNKNOWN_SOURCEFILE_NAMES.contains(sourceFile) && !fileNameProbablyChanged) {
-      // We have no new information, only rewrite filename if it is unknown.
-      // PG-retrace will always rewrite the filename, but that seems a bit to harsh to do.
+    if (!hasRetraceResult || KEEP_SOURCEFILE_NAMES.contains(sourceFile)) {
       return sourceFile;
     }
     String extension = Files.getFileExtension(sourceFile);
-    if (extension.isEmpty()) {
+    String newFileName = getOuterClassSimpleName(retracedClassName);
+    if (newFileName.endsWith("Kt") && (extension.isEmpty() || extension.equals("kt"))) {
+      newFileName = newFileName.substring(0, newFileName.length() - 2);
+      extension = "kt";
+    } else if (extension.isEmpty()) {
       extension = "java";
     }
-    if (!hasRetraceResult) {
-      // We have no mapping but but file name is unknown, so the best we can do is take the
-      // name of the obfuscated clazz.
-      assert minifiedClassName.equals(retracedClassName);
-      return getOuterClassSimpleName(minifiedClassName) + "." + extension;
-    }
-    String newFileName = getOuterClassSimpleName(retracedClassName);
     return newFileName + "." + extension;
   }
 
diff --git a/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java b/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
index 632c74c..855e24c 100644
--- a/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
+++ b/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
@@ -75,8 +75,6 @@
     implements InstantiatedSubTypeInfo {
   /** Set of reachable proto types that will be dead code eliminated. */
   private final Set<DexType> deadProtoTypes;
-  /** Set of types that are mentioned in the program, but for which no definition exists. */
-  private final Set<DexType> missingTypes;
   /**
    * Set of types that are mentioned in the program. We at least need an empty abstract classitem
    * for these.
@@ -136,6 +134,11 @@
   private final Set<DexMethod> forceInline;
   /** All methods that *must* never be inlined due to a configuration directive (testing only). */
   private final Set<DexMethod> neverInline;
+  /**
+   * All methods that *must* never be inlined as a result of having a single caller due to a
+   * configuration directive (testing only).
+   */
+  private final Set<DexMethod> neverInlineDueToSingleCaller;
   /** Items for which to print inlining decisions for (testing only). */
   private final Set<DexMethod> whyAreYouNotInlining;
   /** All methods that may not have any parameters with a constant value removed. */
@@ -190,7 +193,7 @@
       ClassToFeatureSplitMap classToFeatureSplitMap,
       MainDexClasses mainDexClasses,
       Set<DexType> deadProtoTypes,
-      Set<DexType> missingTypes,
+      MissingClasses missingClasses,
       Set<DexType> liveTypes,
       Set<DexMethod> targetedMethods,
       Set<DexMethod> failedResolutionTargets,
@@ -209,6 +212,7 @@
       Set<DexMethod> alwaysInline,
       Set<DexMethod> forceInline,
       Set<DexMethod> neverInline,
+      Set<DexMethod> neverInlineDueToSingleCaller,
       Set<DexMethod> whyAreYouNotInlining,
       Set<DexMethod> keepConstantArguments,
       Set<DexMethod> keepUnusedArguments,
@@ -226,9 +230,8 @@
       Map<DexField, Int2ReferenceMap<DexField>> switchMaps,
       Set<DexType> lockCandidates,
       Map<DexType, Visibility> initClassReferences) {
-    super(syntheticItems, classToFeatureSplitMap, mainDexClasses);
+    super(syntheticItems, classToFeatureSplitMap, mainDexClasses, missingClasses);
     this.deadProtoTypes = deadProtoTypes;
-    this.missingTypes = missingTypes;
     this.liveTypes = liveTypes;
     this.targetedMethods = targetedMethods;
     this.failedResolutionTargets = failedResolutionTargets;
@@ -247,6 +250,7 @@
     this.alwaysInline = alwaysInline;
     this.forceInline = forceInline;
     this.neverInline = neverInline;
+    this.neverInlineDueToSingleCaller = neverInlineDueToSingleCaller;
     this.whyAreYouNotInlining = whyAreYouNotInlining;
     this.keepConstantArguments = keepConstantArguments;
     this.keepUnusedArguments = keepUnusedArguments;
@@ -273,7 +277,7 @@
         previous.getClassToFeatureSplitMap(),
         previous.getMainDexClasses(),
         previous.deadProtoTypes,
-        previous.missingTypes,
+        previous.getMissingClasses(),
         CollectionUtils.mergeSets(previous.liveTypes, committedItems.getCommittedTypes()),
         previous.targetedMethods,
         previous.failedResolutionTargets,
@@ -292,6 +296,7 @@
         previous.alwaysInline,
         previous.forceInline,
         previous.neverInline,
+        previous.neverInlineDueToSingleCaller,
         previous.whyAreYouNotInlining,
         previous.keepConstantArguments,
         previous.keepUnusedArguments,
@@ -317,7 +322,7 @@
         previous.getClassToFeatureSplitMap().withoutPrunedItems(prunedItems),
         previous.getMainDexClasses().withoutPrunedItems(prunedItems),
         previous.deadProtoTypes,
-        previous.missingTypes,
+        previous.getMissingClasses(),
         prunedItems.hasRemovedClasses()
             ? Sets.difference(previous.liveTypes, prunedItems.getRemovedClasses())
             : previous.liveTypes,
@@ -338,6 +343,7 @@
         previous.alwaysInline,
         previous.forceInline,
         previous.neverInline,
+        previous.neverInlineDueToSingleCaller,
         previous.whyAreYouNotInlining,
         previous.keepConstantArguments,
         previous.keepUnusedArguments,
@@ -362,7 +368,7 @@
   private void verify() {
     assert keepInfo.verifyPinnedTypesAreLive(liveTypes);
     assert objectAllocationInfoCollection.verifyAllocatedTypesAreLive(
-        liveTypes, missingTypes, this);
+        liveTypes, getMissingClasses(), this);
   }
 
   private static KeepInfoCollection extendPinnedItems(
@@ -407,9 +413,9 @@
     super(
         previous.getSyntheticItems().commit(previous.app()),
         previous.getClassToFeatureSplitMap(),
-        previous.getMainDexClasses());
+        previous.getMainDexClasses(),
+        previous.getMissingClasses());
     this.deadProtoTypes = previous.deadProtoTypes;
-    this.missingTypes = previous.missingTypes;
     this.liveTypes = previous.liveTypes;
     this.targetedMethods = previous.targetedMethods;
     this.failedResolutionTargets = previous.failedResolutionTargets;
@@ -428,6 +434,7 @@
     this.alwaysInline = previous.alwaysInline;
     this.forceInline = previous.forceInline;
     this.neverInline = previous.neverInline;
+    this.neverInlineDueToSingleCaller = previous.neverInlineDueToSingleCaller;
     this.whyAreYouNotInlining = previous.whyAreYouNotInlining;
     this.keepConstantArguments = previous.keepConstantArguments;
     this.keepUnusedArguments = previous.keepUnusedArguments;
@@ -458,7 +465,7 @@
     DexClass definition = super.definitionFor(type);
     assert definition != null
             || deadProtoTypes.contains(type)
-            || missingTypes.contains(type)
+            || getMissingClasses().contains(type)
             // TODO(b/150693139): Remove these exceptions once fixed.
             || InterfaceMethodRewriter.isCompanionClassType(type)
             || InterfaceMethodRewriter.hasDispatchClassSuffix(type)
@@ -565,6 +572,10 @@
     return neverInline.contains(method);
   }
 
+  public boolean isNeverInlineDueToSingleCallerMethod(ProgramMethod method) {
+    return neverInlineDueToSingleCaller.contains(method.getReference());
+  }
+
   public boolean isWhyAreYouNotInliningMethod(DexMethod method) {
     return whyAreYouNotInlining.contains(method);
   }
@@ -680,10 +691,6 @@
     return deadProtoTypes;
   }
 
-  public Set<DexType> getMissingTypes() {
-    return missingTypes;
-  }
-
   public Int2ReferenceMap<DexField> getSwitchMap(DexField field) {
     assert checkIfObsolete();
     return switchMaps.get(field);
@@ -832,11 +839,31 @@
         && !keepInfo.getMethodInfo(method).isPinned();
   }
 
-  public boolean mayPropagateValueFor(DexReference reference) {
+  public boolean mayPropagateValueFor(DexMember<?, ?> reference) {
     assert checkIfObsolete();
-    return options().enableValuePropagation
-        && !isPinned(reference)
-        && !neverPropagateValue.contains(reference);
+    return reference.apply(this::mayPropagateValueFor, this::mayPropagateValueFor);
+  }
+
+  public boolean mayPropagateValueFor(DexField field) {
+    assert checkIfObsolete();
+    if (!options().enableValuePropagation || neverPropagateValue.contains(field)) {
+      return false;
+    }
+    if (isPinned(field) && !field.getType().isAlwaysNull(this)) {
+      return false;
+    }
+    return true;
+  }
+
+  public boolean mayPropagateValueFor(DexMethod method) {
+    assert checkIfObsolete();
+    if (!options().enableValuePropagation || neverPropagateValue.contains(method)) {
+      return false;
+    }
+    if (isPinned(method) && !method.getReturnType().isAlwaysNull(this)) {
+      return false;
+    }
+    return true;
   }
 
   private boolean isLibraryOrClasspathField(DexEncodedField field) {
@@ -965,7 +992,7 @@
         getClassToFeatureSplitMap().rewrittenWithLens(lens),
         getMainDexClasses().rewrittenWithLens(lens),
         deadProtoTypes,
-        missingTypes,
+        getMissingClasses().commitSyntheticItems(committedItems),
         lens.rewriteTypes(liveTypes),
         lens.rewriteMethods(targetedMethods),
         lens.rewriteMethods(failedResolutionTargets),
@@ -984,6 +1011,7 @@
         lens.rewriteMethods(alwaysInline),
         lens.rewriteMethods(forceInline),
         lens.rewriteMethods(neverInline),
+        lens.rewriteMethods(neverInlineDueToSingleCaller),
         lens.rewriteMethods(whyAreYouNotInlining),
         lens.rewriteMethods(keepConstantArguments),
         lens.rewriteMethods(keepUnusedArguments),
diff --git a/src/main/java/com/android/tools/r8/shaking/DefaultEnqueuerUseRegistry.java b/src/main/java/com/android/tools/r8/shaking/DefaultEnqueuerUseRegistry.java
index 18ae137..f8302d7 100644
--- a/src/main/java/com/android/tools/r8/shaking/DefaultEnqueuerUseRegistry.java
+++ b/src/main/java/com/android/tools/r8/shaking/DefaultEnqueuerUseRegistry.java
@@ -5,6 +5,7 @@
 package com.android.tools.r8.shaking;
 
 import com.android.tools.r8.code.CfOrDexInstruction;
+import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexCallSite;
 import com.android.tools.r8.graph.DexEncodedMethod;
@@ -19,11 +20,16 @@
 
 public class DefaultEnqueuerUseRegistry extends UseRegistry {
 
+  protected final AppView<? extends AppInfoWithClassHierarchy> appView;
   private final ProgramMethod context;
   protected final Enqueuer enqueuer;
 
-  public DefaultEnqueuerUseRegistry(AppView<?> appView, ProgramMethod context, Enqueuer enqueuer) {
+  public DefaultEnqueuerUseRegistry(
+      AppView<? extends AppInfoWithClassHierarchy> appView,
+      ProgramMethod context,
+      Enqueuer enqueuer) {
     super(appView.dexItemFactory());
+    this.appView = appView;
     this.context = context;
     this.enqueuer = enqueuer;
   }
diff --git a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
index e43e72a..72d5af2 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -27,7 +27,6 @@
 import com.android.tools.r8.graph.DexClassAndMethod;
 import com.android.tools.r8.graph.DexClasspathClass;
 import com.android.tools.r8.graph.DexDefinition;
-import com.android.tools.r8.graph.DexDefinitionSupplier;
 import com.android.tools.r8.graph.DexEncodedField;
 import com.android.tools.r8.graph.DexEncodedMember;
 import com.android.tools.r8.graph.DexEncodedMethod;
@@ -205,7 +204,8 @@
   private ProguardClassFilter dontWarnPatterns;
   private final EnqueuerUseRegistryFactory useRegistryFactory;
   private AnnotationRemover.Builder annotationRemoverBuilder;
-  private final EnqueuerDefinitionSupplier enqueuerDefinitionSupplier;
+  private final EnqueuerDefinitionSupplier enqueuerDefinitionSupplier =
+      new EnqueuerDefinitionSupplier(this);
 
   private final FieldAccessInfoCollectionImpl fieldAccessInfoCollection =
       new FieldAccessInfoCollectionImpl();
@@ -261,14 +261,11 @@
   private final Set<DexProgramClass> deadProtoTypeCandidates = Sets.newIdentityHashSet();
 
   /** Set of missing types. */
-  private final Set<DexType> missingTypes = Sets.newIdentityHashSet();
+  private final MissingClasses.Builder missingClassesBuilder;
 
   /** Set of proto types that were found to be dead during the first round of tree shaking. */
   private Set<DexType> initialDeadProtoTypes = Sets.newIdentityHashSet();
 
-  /** Set of types that were found to be missing during the first round of tree shaking. */
-  private Set<DexType> initialMissingTypes;
-
   /** Set of types that was pruned during the first round of tree shaking. */
   private Set<DexType> initialPrunedTypes;
 
@@ -382,10 +379,11 @@
     this.subtypingInfo = subtypingInfo;
     this.forceProguardCompatibility = options.forceProguardCompatibility;
     this.graphReporter = new GraphReporter(appView, keptGraphConsumer);
+    this.missingClassesBuilder = appView.appInfo().getMissingClasses().builder();
     this.mode = mode;
     this.options = options;
     this.useRegistryFactory = createUseRegistryFactory();
-    this.workList = EnqueuerWorklist.createWorklist(appView);
+    this.workList = EnqueuerWorklist.createWorklist();
 
     if (mode.isInitialOrFinalTreeShaking()) {
       if (options.protoShrinking().enableGeneratedMessageLiteShrinking) {
@@ -416,8 +414,6 @@
     } else {
       desugaredLibraryWrapperAnalysis = null;
     }
-
-    enqueuerDefinitionSupplier = new EnqueuerDefinitionSupplier(appView, this);
   }
 
   private AppInfoWithClassHierarchy appInfo() {
@@ -478,11 +474,6 @@
     this.initialDeadProtoTypes = initialDeadProtoTypes;
   }
 
-  public void setInitialMissingTypes(Set<DexType> initialMissingTypes) {
-    assert mode.isFinalTreeShaking();
-    this.initialMissingTypes = initialMissingTypes;
-  }
-
   public void setInitialPrunedTypes(Set<DexType> initialPrunedTypes) {
     assert mode.isFinalTreeShaking();
     this.initialPrunedTypes = initialPrunedTypes;
@@ -497,22 +488,21 @@
     deadProtoTypeCandidates.add(clazz);
   }
 
-  private boolean isProgramClass(DexType type) {
-    return getProgramClassOrNull(type) != null;
-  }
-
-  private void recordReference(DexReference r) {
-    if (r.isDexType()) {
-      recordTypeReference(r.asDexType());
-    } else if (r.isDexField()) {
-      recordFieldReference(r.asDexField());
-    } else {
-      assert r.isDexMethod();
-      recordMethodReference(r.asDexMethod());
+  private void recordCompilerSynthesizedTypeReference(DexType type) {
+    DexClass clazz = appInfo().definitionFor(type);
+    if (clazz == null) {
+      ignoreMissingClass(type);
+    } else if (clazz.isNotProgramClass()) {
+      addLiveNonProgramType(clazz);
     }
   }
 
-  private void recordTypeReference(DexType type) {
+  private void recordTypeReference(DexType type, ProgramDefinition context) {
+    recordTypeReference(type, context, this::reportMissingClass);
+  }
+
+  private void recordTypeReference(
+      DexType type, ProgramDefinition context, Consumer<DexType> missingClassConsumer) {
     if (type == null) {
       return;
     }
@@ -523,47 +513,44 @@
       return;
     }
     // Lookup the definition, ignoring the result. This populates the missing and referenced sets.
-    definitionFor(type);
+    definitionFor(type, context, missingClassConsumer);
   }
 
-  private void recordMethodReference(DexMethod method) {
-    recordTypeReference(method.holder);
-    recordTypeReference(method.proto.returnType);
+  private void recordMethodReference(DexMethod method, ProgramDefinition context) {
+    recordTypeReference(method.holder, context);
+    recordTypeReference(method.proto.returnType, context);
     for (DexType type : method.proto.parameters.values) {
-      recordTypeReference(type);
+      recordTypeReference(type, context);
     }
   }
 
-  private void recordFieldReference(DexField field) {
-    recordTypeReference(field.holder);
-    recordTypeReference(field.type);
+  private void recordFieldReference(DexField field, ProgramDefinition context) {
+    recordTypeReference(field.holder, context);
+    recordTypeReference(field.type, context);
   }
 
-  public DexEncodedMethod definitionFor(DexMethod method) {
-    DexClass clazz = definitionFor(method.holder);
+  public DexEncodedMethod definitionFor(DexMethod method, ProgramDefinition context) {
+    DexClass clazz = definitionFor(method.holder, context);
     if (clazz == null) {
       return null;
     }
     return clazz.lookupMethod(method);
   }
 
-  private DexClass definitionFor(DexType type) {
-    return internalDefinitionFor(type, false);
+  public DexClass definitionFor(DexType type, ProgramDefinition context) {
+    return definitionFor(type, context, this::reportMissingClass);
   }
 
-  private DexClass definitionForFromReflectiveAccess(DexType type) {
-    return internalDefinitionFor(type, true);
+  private DexClass definitionFor(
+      DexType type, ProgramDefinition context, Consumer<DexType> missingClassConsumer) {
+    return internalDefinitionFor(type, context, missingClassConsumer);
   }
 
-  private DexClass internalDefinitionFor(DexType type, boolean fromReflectiveAccess) {
-    DexClass clazz =
-        fromReflectiveAccess
-            ? appView.appInfo().definitionForWithoutExistenceAssert(type)
-            : appView.appInfo().definitionFor(type);
+  private DexClass internalDefinitionFor(
+      DexType type, ProgramDefinition context, Consumer<DexType> missingClassConsumer) {
+    DexClass clazz = appInfo().definitionFor(type);
     if (clazz == null) {
-      if (!fromReflectiveAccess) {
-        reportMissingClass(type);
-      }
+      missingClassConsumer.accept(type);
       return null;
     }
     if (clazz.isNotProgramClass()) {
@@ -630,14 +617,20 @@
     worklist.addLast(definition);
   }
 
-  private DexProgramClass getProgramClassOrNull(DexType type) {
-    DexClass clazz = definitionFor(type);
+  private DexProgramClass getProgramClassOrNull(DexType type, ProgramDefinition context) {
+    DexClass clazz = definitionFor(type, context);
     return clazz != null && clazz.isProgramClass() ? clazz.asProgramClass() : null;
   }
 
-  private DexProgramClass getProgramClassOrNullFromReflectiveAccess(DexType type) {
+  private DexProgramClass getProgramHolderOrNull(
+      DexMember<?, ?> member, ProgramDefinition context) {
+    return getProgramClassOrNull(member.getHolderType(), context);
+  }
+
+  private DexProgramClass getProgramClassOrNullFromReflectiveAccess(
+      DexType type, ProgramDefinition context) {
     // To avoid that we report reflectively accessed types as missing.
-    DexClass clazz = definitionForFromReflectiveAccess(type);
+    DexClass clazz = definitionFor(type, context, this::ignoreMissingClass);
     return clazz != null && clazz.isProgramClass() ? clazz.asProgramClass() : null;
   }
 
@@ -652,7 +645,7 @@
 
   private void warnIfClassExtendsInterfaceOrImplementsClass(DexProgramClass clazz) {
     if (clazz.superType != null) {
-      DexClass superClass = definitionFor(clazz.superType);
+      DexClass superClass = definitionFor(clazz.superType, clazz);
       if (superClass != null && superClass.isInterface()) {
         options.reporter.warning(
             new StringDiagnostic(
@@ -664,7 +657,7 @@
       }
     }
     for (DexType iface : clazz.interfaces.values) {
-      DexClass ifaceClass = definitionFor(iface);
+      DexClass ifaceClass = definitionFor(iface, clazz);
       if (ifaceClass != null && !ifaceClass.isInterface()) {
         options.reporter.warning(
             new StringDiagnostic(
@@ -677,10 +670,6 @@
     }
   }
 
-  private void enqueueRootItems(Map<DexReference, Set<ProguardKeepRuleBase>> items) {
-    items.entrySet().forEach(this::enqueueRootItem);
-  }
-
   private void enqueueRootItems(ItemsWithRules items) {
     items.forEachField(this::enqueueRootField);
     items.forEachMethod(this::enqueueRootMethod);
@@ -705,10 +694,14 @@
   private void enqueueRootClass(DexType type, Set<ProguardKeepRuleBase> rules) {
     DexProgramClass clazz = asProgramClassOrNull(appView.definitionFor(type));
     if (clazz != null) {
-      enqueueRootClass(clazz, rules, null);
+      enqueueRootClass(clazz, rules);
     }
   }
 
+  private void enqueueRootClass(DexProgramClass clazz, Set<ProguardKeepRuleBase> rules) {
+    enqueueRootClass(clazz, rules, null);
+  }
+
   private void enqueueRootClass(
       DexProgramClass clazz, Set<ProguardKeepRuleBase> rules, DexDefinition precondition) {
     keepClassWithRules(clazz, rules);
@@ -732,7 +725,7 @@
               graphReporter.reportCompatKeepDefaultInitializer(defaultInitializer));
         }
         if (clazz.isExternalizable(appView)) {
-          enqueueMarkMethodLiveAction(defaultInitializer, witness);
+          enqueueMarkMethodLiveAction(defaultInitializer, defaultInitializer, witness);
         }
       }
     }
@@ -740,15 +733,20 @@
 
   // TODO(b/123923324): Verify that root items are present.
   private void enqueueRootField(DexField reference, Set<ProguardKeepRuleBase> rules) {
-    DexProgramClass holder = getProgramClassOrNull(reference.holder);
+    DexProgramClass holder =
+        asProgramClassOrNull(appInfo().definitionFor(reference.getHolderType()));
     if (holder != null) {
       ProgramField field = holder.lookupProgramField(reference);
       if (field != null) {
-        enqueueRootField(field, rules, null);
+        enqueueRootField(field, rules);
       }
     }
   }
 
+  private void enqueueRootField(ProgramField field, Set<ProguardKeepRuleBase> rules) {
+    enqueueRootField(field, rules, null);
+  }
+
   private void enqueueRootField(
       ProgramField field, Set<ProguardKeepRuleBase> rules, DexDefinition precondition) {
     keepFieldWithRules(field, rules);
@@ -758,7 +756,8 @@
 
   // TODO(b/123923324): Verify that root items are present.
   private void enqueueRootMethod(DexMethod reference, Set<ProguardKeepRuleBase> rules) {
-    DexProgramClass holder = getProgramClassOrNull(reference.holder);
+    DexProgramClass holder =
+        asProgramClassOrNull(appInfo().definitionFor(reference.getHolderType()));
     if (holder != null) {
       ProgramMethod method = holder.lookupProgramMethod(reference);
       if (method != null) {
@@ -767,6 +766,10 @@
     }
   }
 
+  private void enqueueRootMethod(ProgramMethod method, Set<ProguardKeepRuleBase> rules) {
+    enqueueRootMethod(method, rules, null);
+  }
+
   private void enqueueRootMethod(
       ProgramMethod method, Set<ProguardKeepRuleBase> rules, DexDefinition precondition) {
     keepMethodWithRules(method, rules);
@@ -774,29 +777,18 @@
         method, graphReporter.reportKeepMethod(precondition, rules, method.getDefinition()));
   }
 
-  private void enqueueRootItem(DexDefinition item, Set<ProguardKeepRuleBase> rules) {
+  private void enqueueRootItem(ProgramDefinition item, Set<ProguardKeepRuleBase> rules) {
     internalEnqueueRootItem(item, rules, null);
   }
 
   private void internalEnqueueRootItem(
-      DexDefinition item, Set<ProguardKeepRuleBase> rules, DexDefinition precondition) {
-    if (item.isDexClass()) {
-      DexProgramClass clazz = item.asDexClass().asProgramClass();
-      if (clazz != null) {
-        enqueueRootClass(clazz, rules, precondition);
-      }
-    } else if (item.isDexEncodedField()) {
-      DexEncodedField field = item.asDexEncodedField();
-      DexProgramClass holder = getProgramClassOrNull(field.getHolderType());
-      if (holder != null) {
-        enqueueRootField(new ProgramField(holder, field), rules, precondition);
-      }
-    } else if (item.isDexEncodedMethod()) {
-      DexEncodedMethod method = item.asDexEncodedMethod();
-      DexProgramClass holder = getProgramClassOrNull(method.getHolderType());
-      if (holder != null) {
-        enqueueRootMethod(new ProgramMethod(holder, method), rules, precondition);
-      }
+      ProgramDefinition item, Set<ProguardKeepRuleBase> rules, DexDefinition precondition) {
+    if (item.isProgramClass()) {
+      enqueueRootClass(item.asProgramClass(), rules, precondition);
+    } else if (item.isProgramField()) {
+      enqueueRootField(item.asProgramField(), rules, precondition);
+    } else if (item.isProgramMethod()) {
+      enqueueRootMethod(item.asProgramMethod(), rules, precondition);
     } else {
       throw new IllegalArgumentException(item.toString());
     }
@@ -808,22 +800,23 @@
     // Climb up the class hierarchy. Break out if the definition is not found, or hit the library
     // classes which are kept by definition, or encounter the first non-serializable class.
     while (clazz.isSerializable(appView)) {
-      DexProgramClass superClass = getProgramClassOrNull(clazz.superType);
+      DexProgramClass superClass = getProgramClassOrNull(clazz.superType, clazz);
       if (superClass == null) {
         return;
       }
       clazz = superClass;
     }
     if (clazz.hasDefaultInitializer()) {
-      enqueueMarkMethodLiveAction(clazz.getProgramDefaultInitializer(), reason);
+      enqueueMarkMethodLiveAction(clazz.getProgramDefaultInitializer(), clazz, reason);
     }
   }
 
   // Utility to avoid adding to the worklist if already live.
-  private boolean enqueueMarkMethodLiveAction(ProgramMethod method, KeepReason reason) {
+  private boolean enqueueMarkMethodLiveAction(
+      ProgramMethod method, ProgramDefinition context, KeepReason reason) {
     if (liveMethods.add(method, reason)) {
       assert !method.getDefinition().getOptimizationInfo().forceInline();
-      workList.enqueueMarkMethodLiveAction(method, reason);
+      workList.enqueueMarkMethodLiveAction(method, context);
       return true;
     }
     return false;
@@ -847,7 +840,7 @@
       BiPredicate<DexMethod, ProgramMethod> registration, DexMethod method, ProgramMethod context) {
     DexType baseHolder = method.holder.toBaseType(appView.dexItemFactory());
     if (baseHolder.isClassType()) {
-      markTypeAsLive(baseHolder, clazz -> graphReporter.reportClassReferencedFrom(clazz, context));
+      markTypeAsLive(baseHolder, context);
       return registration.test(method, context);
     }
     return false;
@@ -879,7 +872,7 @@
       DexField field, ProgramMethod context, boolean isRead, boolean isReflective) {
     FieldAccessInfoImpl info = fieldAccessInfoCollection.get(field);
     if (info == null) {
-      DexEncodedField encodedField = resolveField(field).getResolvedField();
+      DexEncodedField encodedField = resolveField(field, context).getResolvedField();
 
       // If the field does not exist, then record this in the mapping, such that we don't have to
       // resolve the field the next time.
@@ -913,7 +906,7 @@
 
   void traceCallSite(DexCallSite callSite, ProgramMethod context) {
     DexProgramClass bootstrapClass =
-        getProgramClassOrNull(callSite.bootstrapMethod.asMethod().holder);
+        getProgramHolderOrNull(callSite.bootstrapMethod.asMethod(), context);
     if (bootstrapClass != null) {
       bootstrapMethods.add(callSite.bootstrapMethod.asMethod());
     }
@@ -996,7 +989,7 @@
     // potential locks on T[].class and S[].class exists.
     DexType baseType = type.toBaseType(appView.dexItemFactory());
     if (baseType.isClassType()) {
-      DexProgramClass baseClass = getProgramClassOrNull(baseType);
+      DexProgramClass baseClass = getProgramClassOrNull(baseType, currentMethod);
       if (baseClass != null && isConstClassMaybeUsedAsLock(currentMethod, iterator)) {
         lockCandidates.add(baseType);
       }
@@ -1046,7 +1039,7 @@
     }
     DexType baseType = type.toBaseType(appView.dexItemFactory());
     if (baseType.isClassType()) {
-      DexProgramClass baseClass = getProgramClassOrNull(baseType);
+      DexProgramClass baseClass = getProgramClassOrNull(baseType, currentMethod);
       if (baseClass != null) {
         // Don't require any constructor, see b/112386012.
         markClassAsInstantiatedWithCompatRule(
@@ -1060,7 +1053,7 @@
 
     Visibility oldMinimumRequiredVisibility = initClassReferences.get(type);
     if (oldMinimumRequiredVisibility == null) {
-      DexProgramClass clazz = getProgramClassOrNull(type);
+      DexProgramClass clazz = getProgramClassOrNull(type, currentMethod);
       if (clazz == null) {
         assert false;
         return;
@@ -1069,7 +1062,7 @@
       initClassReferences.put(
           type, computeMinimumRequiredVisibilityForInitClassField(type, currentMethod.getHolder()));
 
-      markTypeAsLive(type, classReferencedFromReporter(currentMethod));
+      markTypeAsLive(type, currentMethod);
       markDirectAndIndirectClassInitializersAsLive(clazz);
       return;
     }
@@ -1123,7 +1116,7 @@
     // as instantiated.
     if (methodHandle.isMethodHandle() && use != MethodHandleUse.ARGUMENT_TO_LAMBDA_METAFACTORY) {
       DexType type = methodHandle.asMethod().holder;
-      DexProgramClass clazz = getProgramClassOrNull(type);
+      DexProgramClass clazz = getProgramClassOrNull(type, currentMethod);
       if (clazz != null) {
         KeepReason reason = KeepReason.methodHandleReferencedIn(currentMethod);
         if (clazz.isAnnotation()) {
@@ -1139,7 +1132,7 @@
   }
 
   void traceTypeReference(DexType type, ProgramMethod currentMethod) {
-    markTypeAsLive(type, classReferencedFromReporter(currentMethod));
+    markTypeAsLive(type, currentMethod);
   }
 
   void traceInstanceOf(DexType type, ProgramMethod currentMethod) {
@@ -1169,7 +1162,7 @@
   /** Returns true if a deferred action was registered. */
   private boolean registerDeferredActionForDeadProtoBuilder(
       DexType type, ProgramMethod currentMethod, Action action) {
-    DexProgramClass clazz = getProgramClassOrNull(type);
+    DexProgramClass clazz = getProgramClassOrNull(type, currentMethod);
     if (clazz != null) {
       return appView.withGeneratedMessageLiteBuilderShrinker(
           shrinker ->
@@ -1189,7 +1182,6 @@
     if (registerBackportInvoke(invokedMethod, context)) {
       return;
     }
-
     if (!registerMethodWithTargetAndContext(
         methodAccessInfoCollection::registerInvokeDirectInContext, invokedMethod, context)) {
       return;
@@ -1197,7 +1189,7 @@
     if (Log.ENABLED) {
       Log.verbose(getClass(), "Register invokeDirect `%s`.", invokedMethod);
     }
-    handleInvokeOfDirectTarget(invokedMethod, reason);
+    handleInvokeOfDirectTarget(invokedMethod, context, reason);
     invokeAnalyses.forEach(analysis -> analysis.traceInvokeDirect(invokedMethod, context));
   }
 
@@ -1274,7 +1266,7 @@
     if (Log.ENABLED) {
       Log.verbose(getClass(), "Register invokeStatic `%s`.", invokedMethod);
     }
-    handleInvokeOfStaticTarget(invokedMethod, reason);
+    handleInvokeOfStaticTarget(invokedMethod, context, reason);
     invokeAnalyses.forEach(analysis -> analysis.traceInvokeStatic(invokedMethod, context));
   }
 
@@ -1356,7 +1348,7 @@
       ProgramMethod context,
       InstantiationReason instantiationReason,
       KeepReason keepReason) {
-    DexProgramClass clazz = getProgramClassOrNull(type);
+    DexProgramClass clazz = getProgramClassOrNull(type, context);
     if (clazz != null) {
       if (clazz.isAnnotation() || clazz.isInterface()) {
         markTypeAsLive(clazz, graphReporter.registerClass(clazz, keepReason));
@@ -1383,7 +1375,7 @@
     // Must mark the field as targeted even if it does not exist.
     markFieldAsTargeted(fieldReference, currentMethod);
 
-    FieldResolutionResult resolutionResult = resolveField(fieldReference);
+    FieldResolutionResult resolutionResult = resolveField(fieldReference, currentMethod);
     if (resolutionResult.isFailedOrUnknownResolution()) {
       noClassMerging.add(fieldReference.getHolderType());
       return;
@@ -1408,13 +1400,12 @@
     // the field as live, if the holder is an interface.
     if (appView.options().enableUnusedInterfaceRemoval) {
       if (field.getReference() != fieldReference) {
-        markTypeAsLive(
-            field.getHolder(),
-            graphReporter.reportClassReferencedFrom(field.getHolder(), currentMethod));
+        markTypeAsLive(field.getHolder(), currentMethod);
       }
     }
 
-    workList.enqueueMarkReachableFieldAction(field, KeepReason.fieldReferencedIn(currentMethod));
+    workList.enqueueMarkReachableFieldAction(
+        field, currentMethod, KeepReason.fieldReferencedIn(currentMethod));
   }
 
   void traceInstanceFieldWrite(DexField field, ProgramMethod currentMethod) {
@@ -1434,7 +1425,7 @@
     // Must mark the field as targeted even if it does not exist.
     markFieldAsTargeted(fieldReference, currentMethod);
 
-    FieldResolutionResult resolutionResult = resolveField(fieldReference);
+    FieldResolutionResult resolutionResult = resolveField(fieldReference, currentMethod);
     if (resolutionResult.isFailedOrUnknownResolution()) {
       noClassMerging.add(fieldReference.getHolderType());
       return;
@@ -1459,14 +1450,12 @@
     // the field as live, if the holder is an interface.
     if (appView.options().enableUnusedInterfaceRemoval) {
       if (field.getReference() != fieldReference) {
-        markTypeAsLive(
-            field.getHolder(),
-            graphReporter.reportClassReferencedFrom(field.getHolder(), currentMethod));
+        markTypeAsLive(field.getHolder(), currentMethod);
       }
     }
 
     KeepReason reason = KeepReason.fieldReferencedIn(currentMethod);
-    workList.enqueueMarkReachableFieldAction(field, reason);
+    workList.enqueueMarkReachableFieldAction(field, currentMethod, reason);
   }
 
   void traceStaticFieldRead(DexField field, ProgramMethod currentMethod) {
@@ -1483,7 +1472,7 @@
       return;
     }
 
-    FieldResolutionResult resolutionResult = resolveField(fieldReference);
+    FieldResolutionResult resolutionResult = resolveField(fieldReference, currentMethod);
     if (resolutionResult.isFailedOrUnknownResolution()) {
       // Must mark the field as targeted even if it does not exist.
       markFieldAsTargeted(fieldReference, currentMethod);
@@ -1527,7 +1516,7 @@
       markFieldAsTargeted(fieldReference, currentMethod);
     }
 
-    markStaticFieldAsLive(field, KeepReason.fieldReferencedIn(currentMethod));
+    markStaticFieldAsLive(field, currentMethod);
   }
 
   void traceStaticFieldWrite(DexField field, ProgramMethod currentMethod) {
@@ -1544,7 +1533,7 @@
       return;
     }
 
-    FieldResolutionResult resolutionResult = resolveField(fieldReference);
+    FieldResolutionResult resolutionResult = resolveField(fieldReference, currentMethod);
     if (resolutionResult.isFailedOrUnknownResolution()) {
       // Must mark the field as targeted even if it does not exist.
       markFieldAsTargeted(fieldReference, currentMethod);
@@ -1586,12 +1575,7 @@
       markFieldAsTargeted(fieldReference, currentMethod);
     }
 
-    markStaticFieldAsLive(field, KeepReason.fieldReferencedIn(currentMethod));
-  }
-
-  private Function<DexProgramClass, KeepReasonWitness> classReferencedFromReporter(
-      ProgramMethod currentMethod) {
-    return clazz -> graphReporter.reportClassReferencedFrom(clazz, currentMethod);
+    markStaticFieldAsLive(field, currentMethod);
   }
 
   private DexMethod getInvokeSuperTarget(DexMethod method, ProgramMethod currentMethod) {
@@ -1624,98 +1608,96 @@
     return true;
   }
 
-  private void markTypeAsLive(DexType type, KeepReason reason) {
+  private void markTypeAsLive(DexType type, ProgramDefinition context) {
     if (type.isArrayType()) {
-      markTypeAsLive(type.toBaseType(appView.dexItemFactory()), reason);
+      markTypeAsLive(type.toBaseType(appView.dexItemFactory()), context);
       return;
     }
     if (!type.isClassType()) {
       // Ignore primitive types.
       return;
     }
-    DexProgramClass clazz = getProgramClassOrNull(type);
+    DexProgramClass clazz = getProgramClassOrNull(type, context);
+    if (clazz == null) {
+      return;
+    }
+    markTypeAsLive(clazz, context);
+  }
+
+  private void markTypeAsLive(DexType type, ProgramDefinition context, KeepReason reason) {
+    if (type.isArrayType()) {
+      markTypeAsLive(type.toBaseType(appView.dexItemFactory()), context, reason);
+      return;
+    }
+    if (!type.isClassType()) {
+      // Ignore primitive types.
+      return;
+    }
+    DexProgramClass clazz = getProgramClassOrNull(type, context);
     if (clazz == null) {
       return;
     }
     markTypeAsLive(clazz, reason);
   }
 
+  private void markTypeAsLive(DexProgramClass clazz, ProgramDefinition context) {
+    markTypeAsLive(clazz, graphReporter.reportClassReferencedFrom(clazz, context));
+  }
+
   private void markTypeAsLive(DexProgramClass clazz, KeepReason reason) {
     assert clazz != null;
-    markTypeAsLive(clazz, graphReporter.registerClass(clazz, reason));
-  }
-
-  private void markTypeAsLive(DexType type, Function<DexProgramClass, KeepReasonWitness> reason) {
-    if (type.isArrayType()) {
-      markTypeAsLive(type.toBaseType(appView.dexItemFactory()), reason);
-      return;
-    }
-    if (!type.isClassType()) {
-      // Ignore primitive types.
-      return;
-    }
-    DexProgramClass holder = getProgramClassOrNull(type);
-    if (holder == null) {
-      return;
-    }
-    markTypeAsLive(
-        holder,
-        scopedMethodsForLiveTypes.computeIfAbsent(type, ignore -> new ScopedDexMethodSet()),
-        reason.apply(holder));
-  }
-
-  void markTypeAsLive(DexProgramClass clazz, KeepReasonWitness witness) {
     markTypeAsLive(
         clazz,
-        scopedMethodsForLiveTypes.computeIfAbsent(clazz.type, ignore -> new ScopedDexMethodSet()),
-        witness);
+        scopedMethodsForLiveTypes.computeIfAbsent(
+            clazz.getType(), ignore -> new ScopedDexMethodSet()),
+        graphReporter.registerClass(clazz, reason));
   }
 
   private void markTypeAsLive(
-      DexProgramClass holder, ScopedDexMethodSet seen, KeepReasonWitness witness) {
-    if (!liveTypes.add(holder, witness)) {
+      DexProgramClass clazz, ScopedDexMethodSet seen, KeepReasonWitness witness) {
+    if (!liveTypes.add(clazz, witness)) {
       return;
     }
 
     // Mark types in inner-class attributes referenced.
-    for (InnerClassAttribute innerClassAttribute : holder.getInnerClasses()) {
-      recordTypeReference(innerClassAttribute.getInner());
-      recordTypeReference(innerClassAttribute.getOuter());
+    for (InnerClassAttribute innerClassAttribute : clazz.getInnerClasses()) {
+      recordTypeReference(innerClassAttribute.getInner(), clazz, this::ignoreMissingClass);
+      recordTypeReference(innerClassAttribute.getOuter(), clazz, this::ignoreMissingClass);
     }
-    EnclosingMethodAttribute enclosingMethodAttribute = holder.getEnclosingMethodAttribute();
+    EnclosingMethodAttribute enclosingMethodAttribute = clazz.getEnclosingMethodAttribute();
     if (enclosingMethodAttribute != null) {
       DexMethod enclosingMethod = enclosingMethodAttribute.getEnclosingMethod();
       if (enclosingMethod != null) {
-        recordMethodReference(enclosingMethod);
+        recordMethodReference(enclosingMethod, clazz);
       } else {
-        recordTypeReference(enclosingMethodAttribute.getEnclosingClass());
+        recordTypeReference(enclosingMethodAttribute.getEnclosingClass(), clazz);
       }
     }
 
     if (Log.ENABLED) {
-      Log.verbose(getClass(), "Type `%s` has become live.", holder.type);
+      Log.verbose(getClass(), "Type `%s` has become live.", clazz.type);
     }
 
-    KeepReason reason = KeepReason.reachableFromLiveType(holder.type);
+    KeepReason reason = KeepReason.reachableFromLiveType(clazz.type);
 
-    for (DexType iface : holder.interfaces.values) {
-      markInterfaceTypeAsLiveViaInheritanceClause(iface, holder);
+    for (DexType iface : clazz.interfaces.values) {
+      markInterfaceTypeAsLiveViaInheritanceClause(iface, clazz);
     }
 
-    if (holder.superType != null) {
+    if (clazz.superType != null) {
       ScopedDexMethodSet seenForSuper =
           scopedMethodsForLiveTypes.computeIfAbsent(
-              holder.superType, ignore -> new ScopedDexMethodSet());
+              clazz.superType, ignore -> new ScopedDexMethodSet());
       seen.setParent(seenForSuper);
-      markTypeAsLive(holder.superType, reason);
+      markTypeAsLive(clazz.superType, clazz);
     }
 
     // Warn if the class extends an interface or implements a class
-    warnIfClassExtendsInterfaceOrImplementsClass(holder);
+    warnIfClassExtendsInterfaceOrImplementsClass(clazz);
 
     // If this is an interface that has just become live, then report previously seen but unreported
     // implemented-by edges.
-    transitionUnusedInterfaceToLive(holder);
+    transitionUnusedInterfaceToLive(clazz);
 
     // We cannot remove virtual methods defined earlier in the type hierarchy if it is widening
     // access and is defined in an interface:
@@ -1732,30 +1714,30 @@
     // error because their exists an existing implementation (here it is Object.clone()). This is
     // only a problem in the DEX VM. We have to make this check no matter the output because
     // CF libraries can be used by Android apps. See b/136698023 for more information.
-    ensureMethodsContinueToWidenAccess(holder, seen, reason);
+    ensureMethodsContinueToWidenAccess(clazz, seen, reason);
 
-    if (holder.isSerializable(appView)) {
-      enqueueFirstNonSerializableClassInitializer(holder, reason);
+    if (clazz.isSerializable(appView)) {
+      enqueueFirstNonSerializableClassInitializer(clazz, reason);
     }
 
-    processAnnotations(holder, holder);
+    processAnnotations(clazz, clazz);
 
     // If this type has deferred annotations, we have to process those now, too.
-    if (holder.isAnnotation()) {
-      Set<DexAnnotation> annotations = deferredAnnotations.remove(holder.type);
+    if (clazz.isAnnotation()) {
+      Set<DexAnnotation> annotations = deferredAnnotations.remove(clazz.type);
       if (annotations != null && !annotations.isEmpty()) {
-        assert annotations.stream().allMatch(a -> a.annotation.type == holder.type);
-        annotations.forEach(annotation -> processAnnotation(holder, holder, annotation));
+        assert annotations.stream().allMatch(a -> a.annotation.type == clazz.type);
+        annotations.forEach(annotation -> processAnnotation(clazz, clazz, annotation));
       }
     }
 
     rootSet.forEachDependentInstanceConstructor(
-        holder, appView, this::enqueueHolderWithDependentInstanceConstructor);
-    rootSet.forEachDependentStaticMember(holder, appView, this::enqueueDependentMember);
+        clazz, appView, this::enqueueHolderWithDependentInstanceConstructor);
+    rootSet.forEachDependentStaticMember(clazz, appView, this::enqueueDependentMember);
     compatEnqueueHolderIfDependentNonStaticMember(
-        holder, rootSet.getDependentKeepClassCompatRule(holder.getType()));
+        clazz, rootSet.getDependentKeepClassCompatRule(clazz.getType()));
 
-    analyses.forEach(analysis -> analysis.processNewlyLiveClass(holder, workList));
+    analyses.forEach(analysis -> analysis.processNewlyLiveClass(clazz, workList));
   }
 
   private void ensureMethodsContinueToWidenAccess(DexClass clazz) {
@@ -1777,7 +1759,7 @@
 
   private void markInterfaceTypeAsLiveViaInheritanceClause(
       DexType type, DexProgramClass implementer) {
-    DexProgramClass clazz = getProgramClassOrNull(type);
+    DexProgramClass clazz = getProgramClassOrNull(type, implementer);
     if (clazz == null) {
       return;
     }
@@ -1785,7 +1767,7 @@
     if (!appView.options().enableUnusedInterfaceRemoval
         || rootSet.noUnusedInterfaceRemoval.contains(type)
         || mode.isTracingMainDex()) {
-      markTypeAsLive(clazz, graphReporter.reportClassReferencedFrom(clazz, implementer));
+      markTypeAsLive(clazz, implementer);
     } else {
       if (liveTypes.contains(clazz)) {
         // The interface is already live, so make sure to report this implements-edge.
@@ -1807,7 +1789,7 @@
               unusedInterfaceTypes.computeIfAbsent(current, ignore -> Sets.newIdentityHashSet());
           if (implementors.add(implementer)) {
             for (DexType iface : current.interfaces.values) {
-              DexProgramClass definition = getProgramClassOrNull(iface);
+              DexProgramClass definition = getProgramClassOrNull(iface, current);
               if (definition != null) {
                 worklist.addIfNotSeen(definition);
               }
@@ -1820,7 +1802,7 @@
 
   private void enqueueDependentMember(
       DexDefinition precondition,
-      DexEncodedMember<?, ?> consequent,
+      ProgramMember<?, ?> consequent,
       Set<ProguardKeepRuleBase> reasons) {
     internalEnqueueRootItem(consequent, reasons, precondition);
   }
@@ -1831,50 +1813,49 @@
     enqueueKeepRuleInstantiatedType(holder, reasons, instanceInitializer.getDefinition());
   }
 
-  private void processAnnotations(DexProgramClass holder, DexDefinition annotatedItem) {
-    processAnnotations(holder, annotatedItem, annotatedItem.annotations());
+  private void processAnnotations(DexProgramClass holder, ProgramDefinition annotatedItem) {
+    processAnnotations(holder, annotatedItem, annotatedItem.getDefinition().annotations());
   }
 
   private void processAnnotations(
-      DexProgramClass holder, DexDefinition annotatedItem, DexAnnotationSet annotations) {
+      DexProgramClass holder, ProgramDefinition annotatedItem, DexAnnotationSet annotations) {
     processAnnotations(holder, annotatedItem, annotations.annotations);
   }
 
   private void processAnnotations(
-      DexProgramClass holder, DexDefinition annotatedItem, DexAnnotation[] annotations) {
+      DexProgramClass holder, ProgramDefinition annotatedItem, DexAnnotation[] annotations) {
     for (DexAnnotation annotation : annotations) {
       processAnnotation(holder, annotatedItem, annotation);
     }
   }
 
   private void processAnnotation(
-      DexProgramClass holder, DexDefinition annotatedItem, DexAnnotation annotation) {
-    assert annotatedItem == holder
-        || annotatedItem.asDexEncodedMember().getReference().holder == holder.type;
+      DexProgramClass holder, ProgramDefinition annotatedItem, DexAnnotation annotation) {
+    assert annotatedItem == holder || annotatedItem.asProgramMember().getHolder() == holder;
     assert !holder.isDexClass() || holder.asDexClass().isProgramClass();
     DexType type = annotation.annotation.type;
-    recordTypeReference(type);
+    recordTypeReference(type, holder);
     DexClass clazz = appView.definitionFor(type);
     boolean annotationTypeIsLibraryClass = clazz == null || clazz.isNotProgramClass();
     boolean isLive = annotationTypeIsLibraryClass || liveTypes.contains(clazz.asProgramClass());
-    if (!shouldKeepAnnotation(appView, annotatedItem, annotation, isLive)) {
+    if (!shouldKeepAnnotation(appView, annotatedItem.getDefinition(), annotation, isLive)) {
       // Remember this annotation for later.
       if (!annotationTypeIsLibraryClass) {
         deferredAnnotations.computeIfAbsent(type, ignore -> new HashSet<>()).add(annotation);
       }
       return;
     }
-    KeepReason reason = KeepReason.annotatedOn(annotatedItem);
+    KeepReason reason = KeepReason.annotatedOn(annotatedItem.getDefinition());
     graphReporter.registerAnnotation(annotation, reason);
     AnnotationReferenceMarker referenceMarker =
-        new AnnotationReferenceMarker(annotation.annotation.type, appView.dexItemFactory(), reason);
+        new AnnotationReferenceMarker(
+            annotation.annotation.type, holder, appView.dexItemFactory(), reason);
     annotation.annotation.collectIndexedItems(referenceMarker);
   }
 
-  private FieldResolutionResult resolveField(DexField field) {
+  private FieldResolutionResult resolveField(DexField field, ProgramDefinition context) {
     // Record the references in case they are not program types.
-    recordTypeReference(field.holder);
-    recordTypeReference(field.type);
+    recordFieldReference(field, context);
     FieldResolutionResult resolutionResult = appInfo.resolveField(field);
     if (resolutionResult.isFailedOrUnknownResolution()) {
       reportMissingField(field);
@@ -1882,31 +1863,33 @@
     return resolutionResult;
   }
 
-  private SingleResolutionResult resolveMethod(DexMethod method, KeepReason reason) {
+  private SingleResolutionResult resolveMethod(
+      DexMethod method, ProgramDefinition context, KeepReason reason) {
     // Record the references in case they are not program types.
-    recordMethodReference(method);
+    recordMethodReference(method, context);
     ResolutionResult resolutionResult = appInfo.unsafeResolveMethodDueToDexFormat(method);
     if (resolutionResult.isFailedResolution()) {
       reportMissingMethod(method);
-      markFailedResolutionTargets(method, resolutionResult.asFailedResolution(), reason);
+      markFailedResolutionTargets(method, resolutionResult.asFailedResolution(), context, reason);
     }
     return resolutionResult.asSingleResolution();
   }
 
   private SingleResolutionResult resolveMethod(
-      DexMethod method, KeepReason reason, boolean interfaceInvoke) {
+      DexMethod method, ProgramDefinition context, KeepReason reason, boolean interfaceInvoke) {
     // Record the references in case they are not program types.
-    recordMethodReference(method);
+    recordMethodReference(method, context);
     ResolutionResult resolutionResult = appInfo.resolveMethod(method, interfaceInvoke);
     if (resolutionResult.isFailedResolution()) {
       reportMissingMethod(method);
-      markFailedResolutionTargets(method, resolutionResult.asFailedResolution(), reason);
+      markFailedResolutionTargets(method, resolutionResult.asFailedResolution(), context, reason);
     }
     return resolutionResult.asSingleResolution();
   }
 
-  private void handleInvokeOfStaticTarget(DexMethod reference, KeepReason reason) {
-    SingleResolutionResult resolution = resolveMethod(reference, reason);
+  private void handleInvokeOfStaticTarget(
+      DexMethod reference, ProgramDefinition context, KeepReason reason) {
+    SingleResolutionResult resolution = resolveMethod(reference, context, reason);
     if (resolution == null || resolution.getResolvedHolder().isNotProgramClass()) {
       return;
     }
@@ -1948,7 +1931,7 @@
 
       // Mark all class initializers in all super types as live.
       for (DexType superType : current.allImmediateSupertypes()) {
-        DexProgramClass superClass = getProgramClassOrNull(superType);
+        DexProgramClass superClass = getProgramClassOrNull(superType, current);
         if (superClass != null) {
           worklist.addIfNotSeen(superClass);
         }
@@ -2021,15 +2004,17 @@
   }
 
   // Package protected due to entry point from worklist.
-  void markNonStaticDirectMethodAsReachable(DexMethod method, KeepReason reason) {
-    handleInvokeOfDirectTarget(method, reason);
+  void markNonStaticDirectMethodAsReachable(
+      DexMethod method, ProgramDefinition context, KeepReason reason) {
+    handleInvokeOfDirectTarget(method, context, reason);
   }
 
-  private void handleInvokeOfDirectTarget(DexMethod reference, KeepReason reason) {
+  private void handleInvokeOfDirectTarget(
+      DexMethod reference, ProgramDefinition context, KeepReason reason) {
     DexType holder = reference.holder;
-    DexProgramClass clazz = getProgramClassOrNull(holder);
+    DexProgramClass clazz = getProgramClassOrNull(holder, context);
     if (clazz == null) {
-      recordMethodReference(reference);
+      recordMethodReference(reference, context);
       return;
     }
     // TODO(zerny): Is it ok that we lookup in both the direct and virtual pool here?
@@ -2057,30 +2042,30 @@
     // Method). In a class, that would lead to a verification error.
     if (encodedMethod.isNonPrivateVirtualMethod()
         && virtualMethodsTargetedByInvokeDirect.add(encodedMethod.method)) {
-      enqueueMarkMethodLiveAction(method, reason);
+      enqueueMarkMethodLiveAction(method, context, reason);
     }
   }
 
-  private void ensureFromLibraryOrThrow(DexType type, DexClass context) {
+  private void ensureFromLibraryOrThrow(DexType type, DexLibraryClass context) {
     if (mode.isTracingMainDex()) {
       // b/72312389: android.jar contains parts of JUnit and most developers include JUnit in
       // their programs. This leads to library classes extending program classes. When tracing
       // main dex lists we allow this.
       return;
     }
-    DexProgramClass holder = getProgramClassOrNull(type);
-    if (holder == null) {
+    DexProgramClass clazz = asProgramClassOrNull(appInfo().definitionFor(type));
+    if (clazz == null) {
       return;
     }
     if (forceProguardCompatibility) {
       // To ensure that the program works correctly we have to pin all super types and members
       // in the tree.
       KeepReason keepReason = KeepReason.reachableFromLiveType(context.type);
-      keepClassAndAllMembers(holder, keepReason);
+      keepClassAndAllMembers(clazz, keepReason);
       appInfo.forEachSuperType(
-          holder,
-          (dexType, ignored) -> {
-            DexProgramClass superClass = getProgramClassOrNull(dexType);
+          clazz,
+          (superType, subclass, ignored) -> {
+            DexProgramClass superClass = asProgramClassOrNull(appInfo().definitionFor(superType));
             if (superClass != null) {
               keepClassAndAllMembers(superClass, keepReason);
             }
@@ -2096,7 +2081,7 @@
           new StringDiagnostic(
               "Library class "
                   + context.type.toSourceString()
-                  + (holder.isInterface() ? " implements " : " extends ")
+                  + (clazz.isInterface() ? " implements " : " extends ")
                   + "program class "
                   + type.toSourceString());
       if (forceProguardCompatibility) {
@@ -2132,16 +2117,19 @@
         });
   }
 
+  private void ignoreMissingClass(DexType clazz) {
+    missingClassesBuilder.ignoreNewMissingClass(clazz);
+  }
+
   private void reportMissingClass(DexType clazz) {
     assert !mode.isFinalTreeShaking()
+            || missingClassesBuilder.wasAlreadyMissing(clazz)
             || appView.dexItemFactory().isPossiblyCompilerSynthesizedType(clazz)
             || initialDeadProtoTypes.contains(clazz)
-            || initialMissingTypes.contains(clazz)
+            // TODO(b/157107464): See if we can clean this up.
+            || (initialPrunedTypes != null && initialPrunedTypes.contains(clazz))
         : "Unexpected missing class `" + clazz.toSourceString() + "`";
-    boolean newReport = missingTypes.add(clazz);
-    if (Log.ENABLED && newReport) {
-      Log.verbose(Enqueuer.class, "Class `%s` is missing.", clazz);
-    }
+    missingClassesBuilder.addNewMissingClass(clazz);
   }
 
   private void reportMissingMethod(DexMethod method) {
@@ -2164,9 +2152,9 @@
       return;
     }
     markReferencedTypesAsLive(method);
-    processAnnotations(holder, definition);
+    processAnnotations(holder, method);
     definition.parameterAnnotationsList.forEachAnnotation(
-        annotation -> processAnnotation(holder, definition, annotation));
+        annotation -> processAnnotation(holder, method, annotation));
 
     if (Log.ENABLED) {
       Log.verbose(getClass(), "Method `%s` is targeted.", method);
@@ -2255,7 +2243,7 @@
   }
 
   private void checkLambdaInterface(DexType itf, ProgramMethod context) {
-    DexClass clazz = definitionFor(itf);
+    DexClass clazz = definitionFor(itf, context);
     if (clazz == null) {
       StringDiagnostic message =
           new StringDiagnostic(
@@ -2277,7 +2265,7 @@
   private void transitionMethodsForInstantiatedLambda(LambdaDescriptor lambda) {
     transitionMethodsForInstantiatedObject(
         InstantiatedObject.of(lambda),
-        definitionFor(appInfo.dexItemFactory().objectType),
+        appInfo().definitionFor(appInfo.dexItemFactory().objectType),
         lambda.interfaces);
   }
 
@@ -2310,7 +2298,7 @@
         markLibraryAndClasspathMethodOverridesAsLive(instantiation, clazz);
       }
       worklist.addIfNotSeen(Arrays.asList(clazz.interfaces.values));
-      clazz = clazz.superType != null ? definitionFor(clazz.superType) : null;
+      clazz = clazz.superType != null ? appInfo().definitionFor(clazz.superType) : null;
     }
     // The targets for methods on the type and its supertype that are reachable are now marked.
     // In a second step, we look at interfaces. We order the search this way such that a
@@ -2318,7 +2306,7 @@
     // resolution/dispatch.
     while (worklist.hasNext()) {
       DexType type = worklist.next();
-      DexClass iface = definitionFor(type);
+      DexClass iface = appInfo().definitionFor(type);
       if (iface == null) {
         continue;
       }
@@ -2454,14 +2442,10 @@
 
   private void markOverridesAsLibraryMethodOverrides(
       DexProgramClass instantiatedClass, DexMethod libraryMethodOverride) {
-    WorkList<DexType> worklist = WorkList.newIdentityWorkList();
-    worklist.addIfNotSeen(instantiatedClass.type);
+    WorkList<DexProgramClass> worklist = WorkList.newIdentityWorkList();
+    worklist.addIfNotSeen(instantiatedClass);
     while (worklist.hasNext()) {
-      DexType type = worklist.next();
-      DexProgramClass clazz = getProgramClassOrNull(type);
-      if (clazz == null) {
-        continue;
-      }
+      DexProgramClass clazz = worklist.next();
       DexEncodedMethod override = clazz.lookupVirtualMethod(libraryMethodOverride);
       if (override != null) {
         if (override.isLibraryMethodOverride().isTrue()) {
@@ -2469,7 +2453,13 @@
         }
         override.setLibraryMethodOverride(OptionalBool.TRUE);
       }
-      clazz.forEachImmediateSupertype(worklist::addIfNotSeen);
+      clazz.forEachImmediateSupertype(
+          superType -> {
+            DexProgramClass superclass = getProgramClassOrNull(superType, clazz);
+            if (superclass != null) {
+              worklist.addIfNotSeen(superclass);
+            }
+          });
     }
   }
 
@@ -2483,9 +2473,11 @@
       if (reachableFields != null) {
         // TODO(b/120959039): Should the reason this field is reachable come from the set?
         KeepReason reason = KeepReason.reachableFromLiveType(clazz.type);
-        reachableFields.forEach(field -> markInstanceFieldAsLive(field, reason));
+        for (ProgramField field : reachableFields) {
+          markInstanceFieldAsLive(field, clazz, reason);
+        }
       }
-      clazz = getProgramClassOrNull(clazz.superType);
+      clazz = getProgramClassOrNull(clazz.superType, clazz);
     } while (clazz != null && !objectAllocationInfoCollection.isInstantiatedDirectly(clazz));
   }
 
@@ -2518,7 +2510,7 @@
       Set<DexProgramClass> implementedBy = unusedInterfaceTypes.remove(clazz);
       if (implementedBy != null) {
         for (DexProgramClass implementer : implementedBy) {
-          markTypeAsLive(clazz, graphReporter.reportClassReferencedFrom(clazz, implementer));
+          markTypeAsLive(clazz, implementer);
         }
       }
     } else {
@@ -2527,16 +2519,19 @@
   }
 
   private void markFieldAsTargeted(DexField field, ProgramMethod context) {
-    markTypeAsLive(field.type, clazz -> graphReporter.reportClassReferencedFrom(clazz, context));
-    markTypeAsLive(field.holder, clazz -> graphReporter.reportClassReferencedFrom(clazz, context));
+    markTypeAsLive(field.type, context);
+    markTypeAsLive(field.holder, context);
   }
 
-  private void markStaticFieldAsLive(ProgramField field, KeepReason reason) {
+  private void markStaticFieldAsLive(ProgramField field, ProgramMethod context) {
+    markStaticFieldAsLive(field, context, KeepReason.fieldReferencedIn(context));
+  }
+
+  private void markStaticFieldAsLive(
+      ProgramField field, ProgramDefinition context, KeepReason reason) {
     // Mark the type live here, so that the class exists at runtime.
-    markTypeAsLive(
-        field.getHolder(), graphReporter.reportClassReferencedFrom(field.getHolder(), field));
-    markTypeAsLive(
-        field.getReference().type, clazz -> graphReporter.reportClassReferencedFrom(clazz, field));
+    markTypeAsLive(field.getHolder(), field);
+    markTypeAsLive(field.getReference().type, field);
 
     markDirectAndIndirectClassInitializersAsLive(field.getHolder());
 
@@ -2552,7 +2547,7 @@
         Log.verbose(getClass(), "Adding instance field `%s` to live set (static context).", field);
       }
     }
-    processAnnotations(field.getHolder(), field.getDefinition());
+    processAnnotations(field.getHolder(), field);
     liveFields.add(field, reason);
 
     // Add all dependent members to the workqueue.
@@ -2561,16 +2556,17 @@
     checkMemberForSoftPinning(field);
 
     // Notify analyses.
-    analyses.forEach(analysis -> analysis.processNewlyLiveField(field));
+    analyses.forEach(analysis -> analysis.processNewlyLiveField(field, context));
   }
 
-  private void markInstanceFieldAsLive(ProgramField field, KeepReason reason) {
-    markTypeAsLive(field.getHolder(), graphReporter.registerClass(field.getHolder(), reason));
-    markTypeAsLive(field.getReference().type, reason);
+  private void markInstanceFieldAsLive(
+      ProgramField field, ProgramDefinition context, KeepReason reason) {
+    markTypeAsLive(field.getHolder(), field);
+    markTypeAsLive(field.getType(), field);
     if (Log.ENABLED) {
       Log.verbose(getClass(), "Adding instance field `%s` to live set.", field);
     }
-    processAnnotations(field.getHolder(), field.getDefinition());
+    processAnnotations(field.getHolder(), field);
     liveFields.add(field, reason);
 
     // Add all dependent members to the workqueue.
@@ -2579,11 +2575,11 @@
     checkMemberForSoftPinning(field);
 
     // Notify analyses.
-    analyses.forEach(analysis -> analysis.processNewlyLiveField(field));
+    analyses.forEach(analysis -> analysis.processNewlyLiveField(field, context));
   }
 
   private void markDirectStaticOrConstructorMethodAsLive(ProgramMethod method, KeepReason reason) {
-    if (!enqueueMarkMethodLiveAction(method, reason)) {
+    if (!enqueueMarkMethodLiveAction(method, method, reason)) {
       // Already marked live.
       return;
     }
@@ -2601,7 +2597,7 @@
     assert !method.getDefinition().isAbstract()
         || reason.isDueToKeepRule()
         || reason.isDueToReflectiveUse();
-    if (enqueueMarkMethodLiveAction(method, reason)) {
+    if (enqueueMarkMethodLiveAction(method, method, reason)) {
       if (Log.ENABLED) {
         Log.verbose(getClass(), "Adding virtual method `%s` to live set.", method);
       }
@@ -2678,26 +2674,25 @@
   }
 
   // Package protected due to entry point from worklist.
-  void markInstanceFieldAsReachable(ProgramField field, KeepReason reason) {
+  void markInstanceFieldAsReachable(
+      ProgramField field, ProgramDefinition context, KeepReason reason) {
     if (Log.ENABLED) {
       Log.verbose(getClass(), "Marking instance field `%s` as reachable.", field);
     }
 
-    markTypeAsLive(
-        field.getHolder(), graphReporter.reportClassReferencedFrom(field.getHolder(), field));
-    markTypeAsLive(
-        field.getReference().type, clazz -> graphReporter.reportClassReferencedFrom(clazz, field));
-
     // We might have a instance field access that is dispatched to a static field. In such case,
     // we have to keep the static field, so that the dispatch fails at runtime in the same way that
     // it did before. We have to keep the field even if the receiver has no live inhabitants, as
     // field resolution happens before the receiver is inspected.
     if (field.getDefinition().isStatic()) {
-      markStaticFieldAsLive(field, reason);
+      markStaticFieldAsLive(field, context, reason);
     } else if (objectAllocationInfoCollection.isInstantiatedDirectlyOrHasInstantiatedSubtype(
         field.getHolder())) {
-      markInstanceFieldAsLive(field, reason);
+      markInstanceFieldAsLive(field, context, reason);
     } else {
+      markTypeAsLive(field.getHolder(), field);
+      markTypeAsLive(field.getReference().type, field);
+
       // Add the field to the reachable set if the type later becomes instantiated.
       reachableInstanceFields
           .computeIfAbsent(field.getHolder(), ignore -> ProgramFieldSet.create())
@@ -2706,28 +2701,28 @@
   }
 
   private void markVirtualMethodAsReachable(
-      DexMethod method, boolean interfaceInvoke, ProgramMethod contextOrNull, KeepReason reason) {
+      DexMethod method, boolean interfaceInvoke, ProgramDefinition context, KeepReason reason) {
     if (method.holder.isArrayType()) {
       // This is an array type, so the actual class will be generated at runtime. We treat this
       // like an invoke on a direct subtype of java.lang.Object that has no further subtypes.
       // As it has no subtypes, it cannot affect liveness of the program we are processing.
       // Ergo, we can ignore it. We need to make sure that the element type is available, though.
-      markTypeAsLive(method.holder, reason);
+      markTypeAsLive(method.holder, context, reason);
       return;
     }
 
     // Note that all virtual methods derived from library methods are kept regardless of being
     // reachable, so the following only needs to consider reachable targets in the program.
     // TODO(b/70160030): Revise this to support tree shaking library methods on non-escaping types.
-    DexProgramClass holder = getProgramClassOrNull(method.holder);
+    DexProgramClass holder = getProgramClassOrNull(method.holder, context);
     if (holder == null) {
       // TODO(b/139464956): clean this.
       // Ensure that the full proto of the targeted method is referenced.
-      recordMethodReference(method);
+      recordMethodReference(method, context);
       return;
     }
 
-    SingleResolutionResult resolution = resolveMethod(method, reason, interfaceInvoke);
+    SingleResolutionResult resolution = resolveMethod(method, context, reason, interfaceInvoke);
     if (resolution == null) {
       return;
     }
@@ -2756,20 +2751,12 @@
     DexEncodedMethod resolvedMethod = resolution.getResolvedMethod();
     markMethodAsTargeted(new ProgramMethod(resolvedHolder, resolvedMethod), reason);
 
-    DexProgramClass contextHolder = contextOrNull != null ? contextOrNull.getHolder() : null;
-    if (contextOrNull != null
-        && resolution.isAccessibleForVirtualDispatchFrom(contextHolder, appInfo).isFalse()) {
+    DexProgramClass contextHolder = context.getContextClass();
+    if (resolution.isAccessibleForVirtualDispatchFrom(contextHolder, appInfo).isFalse()) {
       // Not accessible from this context, so this call will cause a runtime exception.
       return;
     }
 
-    // If the resolved method is not a virtual target, eg, is static, dispatch will fail too.
-    if (!resolvedMethod.isVirtualMethod()) {
-      // This can only happen when context is null, otherwise the access check above will fail.
-      assert contextOrNull == null;
-      return;
-    }
-
     // The method resolved and is accessible, so currently live overrides become live.
     reachableVirtualTargets.computeIfAbsent(holder, k -> Sets.newIdentityHashSet()).add(method);
 
@@ -2812,16 +2799,20 @@
       LookupLambdaTarget target, Function<ProgramMethod, KeepReasonWitness> reason) {
     ProgramMethod implementationMethod = target.getImplementationMethod().asProgramMethod();
     if (implementationMethod != null) {
-      enqueueMarkMethodLiveAction(implementationMethod, reason.apply(implementationMethod));
+      enqueueMarkMethodLiveAction(
+          implementationMethod, implementationMethod, reason.apply(implementationMethod));
     }
   }
 
   private void markFailedResolutionTargets(
-      DexMethod symbolicMethod, FailedResolutionResult failedResolution, KeepReason reason) {
+      DexMethod symbolicMethod,
+      FailedResolutionResult failedResolution,
+      ProgramDefinition context,
+      KeepReason reason) {
     failedResolutionTargets.add(symbolicMethod);
     failedResolution.forEachFailureDependency(
         method -> {
-          DexProgramClass clazz = getProgramClassOrNull(method.getHolderType());
+          DexProgramClass clazz = getProgramClassOrNull(method.getHolderType(), context);
           if (clazz != null) {
             failedResolutionTargets.add(method.method);
             markMethodAsTargeted(new ProgramMethod(clazz, method), reason);
@@ -2855,7 +2846,7 @@
   // Package protected due to entry point from worklist.
   void markSuperMethodAsReachable(DexMethod reference, ProgramMethod from) {
     KeepReason reason = KeepReason.targetedBySuperFrom(from);
-    SingleResolutionResult resolution = resolveMethod(reference, reason);
+    SingleResolutionResult resolution = resolveMethod(reference, from, reason);
     if (resolution == null) {
       return;
     }
@@ -2873,7 +2864,7 @@
       return;
     }
 
-    DexProgramClass clazz = getProgramClassOrNull(target.getHolderType());
+    DexProgramClass clazz = getProgramClassOrNull(target.getHolderType(), from);
     if (clazz == null) {
       return;
     }
@@ -2955,14 +2946,10 @@
       assert appView.options().getProguardConfiguration().getKeepAllRule() != null;
       ImmutableSet<ProguardKeepRuleBase> keepAllSet =
           ImmutableSet.of(appView.options().getProguardConfiguration().getKeepAllRule());
-      for (DexProgramClass dexProgramClass : appView.appInfo().classes()) {
-        for (DexEncodedMethod method : dexProgramClass.methods()) {
-          this.enqueueRootItem(method, keepAllSet);
-        }
-        for (DexEncodedField field : dexProgramClass.fields()) {
-          this.enqueueRootItem(field, keepAllSet);
-        }
-        this.enqueueRootItem(dexProgramClass, keepAllSet);
+      for (DexProgramClass clazz : appView.appInfo().classes()) {
+        enqueueRootClass(clazz, keepAllSet);
+        clazz.forEachProgramMethod(method -> enqueueRootMethod(method, keepAllSet));
+        clazz.forEachProgramField(field -> enqueueRootField(field, keepAllSet));
       }
     }
     trace(executorService, timing);
@@ -3113,7 +3100,7 @@
       for (ProgramMethod liveMethod : liveMethods.values()) {
         assert !enqueuer.targetedMethods.contains(liveMethod.getDefinition());
         enqueuer.markMethodAsTargeted(liveMethod, fakeReason);
-        enqueuer.enqueueMarkMethodLiveAction(liveMethod, fakeReason);
+        enqueuer.enqueueMarkMethodLiveAction(liveMethod, liveMethod, fakeReason);
       }
       enqueuer.liveNonProgramTypes.addAll(syntheticClasspathClasses.values());
     }
@@ -3277,7 +3264,9 @@
     rootSet.pruneDeadItems(appView, this);
 
     // Ensure references from all hard coded factory items.
-    appView.dexItemFactory().forEachPossiblyCompilerSynthesizedType(this::recordTypeReference);
+    appView
+        .dexItemFactory()
+        .forEachPossiblyCompilerSynthesizedType(this::recordCompilerSynthesizedTypeReference);
 
     // Rebuild a new app only containing referenced types.
     Set<DexLibraryClass> libraryClasses = Sets.newIdentityHashSet();
@@ -3301,7 +3290,6 @@
 
     // Verify the references on the pruned application after type synthesis.
     assert verifyReferences(app);
-    assert verifyMissingTypes();
 
     AppInfoWithLiveness appInfoWithLiveness =
         new AppInfoWithLiveness(
@@ -3309,9 +3297,9 @@
             appInfo.getClassToFeatureSplitMap(),
             appInfo.getMainDexClasses(),
             deadProtoTypes,
-            mode.isFinalTreeShaking()
-                ? Sets.union(initialMissingTypes, missingTypes)
-                : missingTypes,
+            appView.testing().enableExperimentalMissingClassesReporting
+                ? missingClassesBuilder.reportMissingClasses(options)
+                : missingClassesBuilder.ignoreMissingClasses(),
             SetUtils.mapIdentityHashSet(liveTypes.getItems(), DexProgramClass::getType),
             Enqueuer.toDescriptorSet(targetedMethods.getItems()),
             Collections.unmodifiableSet(failedResolutionTargets),
@@ -3331,6 +3319,7 @@
             rootSet.alwaysInline,
             rootSet.forceInline,
             rootSet.neverInline,
+            rootSet.neverInlineDueToSingleCaller,
             rootSet.whyAreYouNotInlining,
             rootSet.keepConstantArguments,
             rootSet.keepUnusedArguments,
@@ -3360,8 +3349,9 @@
         .getKnownLambdaClasses()
         .forEach(
             (type, lambda) -> {
-              DexProgramClass synthesizedClass = getProgramClassOrNull(type);
+              DexProgramClass synthesizedClass = lambda.getOrCreateLambdaClass();
               assert synthesizedClass != null;
+              assert synthesizedClass == appInfo().definitionForWithoutExistenceAssert(type);
               assert liveTypes.contains(synthesizedClass);
               if (synthesizedClass == null) {
                 return;
@@ -3387,22 +3377,6 @@
     }
   }
 
-  private boolean verifyMissingTypes() {
-    if (initialMissingTypes == null) {
-      assert !mode.isFinalTreeShaking();
-      return true;
-    }
-    missingTypes.forEach(
-        missingType -> {
-          assert initialMissingTypes.contains(missingType)
-                  // TODO(b/157107464): See if we can clean this up.
-                  || initialPrunedTypes.contains(missingType)
-                  || missingType.isD8R8SynthesizedClassType()
-              : missingType;
-        });
-    return true;
-  }
-
   private boolean verifyReferences(DexApplication app) {
     WorkList<DexClass> worklist = WorkList.newIdentityWorkList();
     for (DexProgramClass clazz : liveTypes.getItems()) {
@@ -3425,9 +3399,11 @@
     }
     DexClass clazz = app.definitionFor(type);
     if (clazz == null) {
-      assert missingTypes.contains(type) : "Expected type to be in missing types': " + type;
+      assert missingClassesBuilder.contains(type)
+          : "Expected type to be in missing types': " + type;
     } else {
-      assert !missingTypes.contains(type) : "Type with definition also in missing types: " + type;
+      assert !missingClassesBuilder.contains(type)
+          : "Type with definition also in missing types: " + type;
       // Eager assert while the context is still present.
       assert clazz.isProgramClass() || liveNonProgramTypes.contains(clazz)
           : "Expected type to be in live non-program types: " + clazz;
@@ -3653,7 +3629,8 @@
       consequentRootSet.dependentKeepClassCompatRule.forEach(
           (precondition, compatRules) -> {
             assert precondition.isDexType();
-            DexProgramClass preconditionHolder = getProgramClassOrNull(precondition.asDexType());
+            DexProgramClass preconditionHolder =
+                asProgramClassOrNull(appInfo().definitionFor(precondition.asDexType()));
             compatEnqueueHolderIfDependentNonStaticMember(preconditionHolder, compatRules);
           });
     }
@@ -3662,11 +3639,12 @@
   private boolean isLiveProgramReference(DexReference reference) {
     if (reference.isDexType()) {
       DexProgramClass clazz =
-          DexProgramClass.asProgramClassOrNull(definitionFor(reference.asDexType()));
+          DexProgramClass.asProgramClassOrNull(appInfo().definitionFor(reference.asDexType()));
       return clazz != null && isTypeLive(clazz);
     }
     DexMember<?, ?> member = reference.asDexMember();
-    DexProgramClass holder = DexProgramClass.asProgramClassOrNull(definitionFor(member.holder));
+    DexProgramClass holder =
+        DexProgramClass.asProgramClassOrNull(appInfo().definitionFor(member.holder));
     ProgramMember<?, ?> programMember = member.lookupOnProgramClass(holder);
     return programMember != null && isMemberLive(programMember.getDefinition());
   }
@@ -3706,9 +3684,10 @@
         markVirtualMethodAsReachable(
             singleTargetMethod.method,
             singleTargetHolder.isInterface(),
-            null,
+            singleTarget,
             graphReporter.fakeReportShouldNotBeUsed());
-        enqueueMarkMethodLiveAction(singleTarget, graphReporter.fakeReportShouldNotBeUsed());
+        enqueueMarkMethodLiveAction(
+            singleTarget, singleTarget, graphReporter.fakeReportShouldNotBeUsed());
       }
     }
     action.getAction().accept(builder);
@@ -3730,7 +3709,7 @@
       // A virtual method. Mark it as reachable so that subclasses, if instantiated, keep
       // their overrides. However, we don't mark it live, as a keep rule might not imply that
       // the corresponding class is live.
-      markVirtualMethodAsReachable(reference, holder.isInterface(), null, reason);
+      markVirtualMethodAsReachable(reference, holder.isInterface(), target, reason);
       if (holder.isInterface()) {
         // Reachability for default methods is based on live subtypes in general. For keep rules,
         // we need special handling as we essentially might have live subtypes that are outside of
@@ -3742,7 +3721,8 @@
         } else {
           DexEncodedMethod implementation = definition.getDefaultInterfaceMethodImplementation();
           if (implementation != null) {
-            DexProgramClass companion = getProgramClassOrNull(implementation.getHolderType());
+            DexProgramClass companion =
+                asProgramClassOrNull(appInfo().definitionFor(implementation.getHolderType()));
             markTypeAsLive(companion, graphReporter.reportCompanionClass(holder, companion));
             markVirtualMethodAsLive(
                 new ProgramMethod(companion, implementation),
@@ -3759,9 +3739,9 @@
   // Package protected due to entry point from worklist.
   void markFieldAsKept(ProgramField field, KeepReason reason) {
     if (field.getDefinition().isStatic()) {
-      markStaticFieldAsLive(field, reason);
+      markStaticFieldAsLive(field, field, reason);
     } else {
-      markInstanceFieldAsReachable(field, reason);
+      markInstanceFieldAsReachable(field, field, reason);
     }
   }
 
@@ -3832,7 +3812,7 @@
   }
 
   // Package protected due to entry point from worklist.
-  void markMethodAsLive(ProgramMethod method, KeepReason reason) {
+  void markMethodAsLive(ProgramMethod method, ProgramDefinition context) {
     DexProgramClass holder = method.getHolder();
     DexEncodedMethod definition = method.getDefinition();
 
@@ -3853,9 +3833,9 @@
       }
     }
     markParameterAndReturnTypesAsLive(method);
-    processAnnotations(holder, definition);
+    processAnnotations(holder, method);
     definition.parameterAnnotationsList.forEachAnnotation(
-        annotation -> processAnnotation(holder, definition, annotation));
+        annotation -> processAnnotation(holder, method, annotation));
     method.registerCodeReferences(useRegistryFactory.create(appView, method, this));
 
     // Add all dependent members to the workqueue.
@@ -3864,7 +3844,7 @@
     checkMemberForSoftPinning(method);
 
     // Notify analyses.
-    analyses.forEach(analysis -> analysis.processNewlyLiveMethod(method));
+    analyses.forEach(analysis -> analysis.processNewlyLiveMethod(method, context));
   }
 
   private void checkMemberForSoftPinning(ProgramMember<?, ?> member) {
@@ -3884,25 +3864,23 @@
   }
 
   private void markReferencedTypesAsLive(ProgramMethod method) {
-    markTypeAsLive(
-        method.getHolderType(), clazz -> graphReporter.reportClassReferencedFrom(clazz, method));
+    markTypeAsLive(method.getHolderType(), method);
     markParameterAndReturnTypesAsLive(method);
   }
 
   private void markParameterAndReturnTypesAsLive(ProgramMethod method) {
     for (DexType parameterType : method.getDefinition().parameters().values) {
-      markTypeAsLive(
-          parameterType, clazz -> graphReporter.reportClassReferencedFrom(clazz, method));
+      markTypeAsLive(parameterType, method);
     }
-    markTypeAsLive(
-        method.getDefinition().returnType(),
-        clazz -> graphReporter.reportClassReferencedFrom(clazz, method));
+    markTypeAsLive(method.getDefinition().returnType(), method);
   }
 
   private void markClassAsInstantiatedWithReason(DexProgramClass clazz, KeepReason reason) {
     workList.enqueueMarkInstantiatedAction(clazz, null, InstantiationReason.REFLECTION, reason);
     if (clazz.hasDefaultInitializer()) {
-      workList.enqueueMarkReachableDirectAction(clazz.getDefaultInitializer().method, reason);
+      ProgramMethod defaultInitializer = clazz.getProgramDefaultInitializer();
+      workList.enqueueMarkReachableDirectAction(
+          defaultInitializer.getReference(), defaultInitializer, reason);
     }
   }
 
@@ -3918,13 +3896,14 @@
         ProgramMethod defaultInitializer = clazz.getProgramDefaultInitializer();
         workList.enqueueMarkReachableDirectAction(
             defaultInitializer.getReference(),
+            defaultInitializer,
             graphReporter.reportCompatKeepDefaultInitializer(defaultInitializer));
       }
     }
   }
 
   private void markMethodAsLiveWithCompatRule(ProgramMethod method) {
-    enqueueMarkMethodLiveAction(method, graphReporter.reportCompatKeepMethod(method));
+    enqueueMarkMethodLiveAction(method, method, graphReporter.reportCompatKeepMethod(method));
   }
 
   private void handleReflectiveBehavior(ProgramMethod method) {
@@ -3976,7 +3955,8 @@
       assert identifierLookupResult.isTypeResult();
       IdentifierNameStringTypeLookupResult identifierTypeLookupResult =
           identifierLookupResult.asTypeResult();
-      DexProgramClass clazz = getProgramClassOrNullFromReflectiveAccess(referencedItem.asDexType());
+      DexProgramClass clazz =
+          getProgramClassOrNullFromReflectiveAccess(referencedItem.asDexType(), method);
       if (clazz == null) {
         return;
       }
@@ -4001,7 +3981,7 @@
       }
     } else if (referencedItem.isDexField()) {
       DexField field = referencedItem.asDexField();
-      DexProgramClass clazz = getProgramClassOrNull(field.holder);
+      DexProgramClass clazz = getProgramClassOrNullFromReflectiveAccess(field.holder, method);
       if (clazz == null) {
         return;
       }
@@ -4029,7 +4009,8 @@
     } else {
       assert referencedItem.isDexMethod();
       DexMethod targetedMethodReference = referencedItem.asDexMethod();
-      DexProgramClass clazz = getProgramClassOrNull(targetedMethodReference.holder);
+      DexProgramClass clazz =
+          getProgramClassOrNullFromReflectiveAccess(targetedMethodReference.holder, method);
       if (clazz == null) {
         return;
       }
@@ -4064,7 +4045,7 @@
       return;
     }
 
-    DexProgramClass clazz = getProgramClassOrNullFromReflectiveAccess(instantiatedType);
+    DexProgramClass clazz = getProgramClassOrNullFromReflectiveAccess(instantiatedType, method);
     if (clazz == null) {
       return;
     }
@@ -4108,7 +4089,7 @@
       return;
     }
 
-    DexProgramClass clazz = getProgramClassOrNullFromReflectiveAccess(instantiatedType);
+    DexProgramClass clazz = getProgramClassOrNullFromReflectiveAccess(instantiatedType, method);
     if (clazz == null) {
       return;
     }
@@ -4206,7 +4187,7 @@
         continue;
       }
 
-      DexProgramClass clazz = getProgramClassOrNull(type);
+      DexProgramClass clazz = getProgramClassOrNull(type, method);
       if (clazz != null && clazz.isInterface()) {
         // Add this interface to the set of pinned items to ensure that we do not merge the
         // interface into its unique subtype, if any.
@@ -4220,7 +4201,7 @@
         clazz.forEachProgramVirtualMethod(
             virtualMethod -> {
               keepInfo.pinMethod(virtualMethod);
-              markVirtualMethodAsReachable(virtualMethod.getReference(), true, null, reason);
+              markVirtualMethodAsReachable(virtualMethod.getReference(), true, clazz, reason);
             });
       }
     }
@@ -4233,8 +4214,8 @@
     // call this method.
     if (invoke.inValues().get(0).isConstClass()) {
       DexType type = invoke.inValues().get(0).definition.asConstClass().getValue();
-      DexProgramClass clazz = getProgramClassOrNull(type);
-      if (clazz != null && clazz.accessFlags.isEnum()) {
+      DexProgramClass clazz = getProgramClassOrNull(type, method);
+      if (clazz != null && clazz.isEnum()) {
         markEnumValuesAsReachable(clazz, KeepReason.invokedFrom(method));
       }
     }
@@ -4264,16 +4245,17 @@
         return;
       }
 
-      handleServiceInstantiation(serviceType, KeepReason.reflectiveUseIn(method));
+      handleServiceInstantiation(serviceType, method, KeepReason.reflectiveUseIn(method));
     } else {
       KeepReason reason = KeepReason.reflectiveUseIn(method);
       for (DexType serviceType : appView.appServices().allServiceTypes()) {
-        handleServiceInstantiation(serviceType, reason);
+        handleServiceInstantiation(serviceType, method, reason);
       }
     }
   }
 
-  private void handleServiceInstantiation(DexType serviceType, KeepReason reason) {
+  private void handleServiceInstantiation(
+      DexType serviceType, ProgramMethod context, KeepReason reason) {
     List<DexType> serviceImplementationTypes =
         appView.appServices().serviceImplementationsFor(serviceType);
     for (DexType serviceImplementationType : serviceImplementationTypes) {
@@ -4282,7 +4264,8 @@
         continue;
       }
 
-      DexProgramClass serviceImplementationClass = getProgramClassOrNull(serviceImplementationType);
+      DexProgramClass serviceImplementationClass =
+          getProgramClassOrNull(serviceImplementationType, context);
       if (serviceImplementationClass != null && serviceImplementationClass.isProgramClass()) {
         markClassAsInstantiatedWithReason(serviceImplementationClass, reason);
       }
@@ -4403,12 +4386,17 @@
   private class AnnotationReferenceMarker implements IndexedItemCollection {
 
     private final DexItem annotationHolder;
+    private final ProgramDefinition context;
     private final DexItemFactory dexItemFactory;
     private final KeepReason reason;
 
     private AnnotationReferenceMarker(
-        DexItem annotationHolder, DexItemFactory dexItemFactory, KeepReason reason) {
+        DexItem annotationHolder,
+        ProgramDefinition context,
+        DexItemFactory dexItemFactory,
+        KeepReason reason) {
       this.annotationHolder = annotationHolder;
+      this.context = context;
       this.dexItemFactory = dexItemFactory;
       this.reason = reason;
     }
@@ -4420,8 +4408,8 @@
 
     @Override
     public boolean addField(DexField fieldReference) {
-      recordFieldReference(fieldReference);
-      DexProgramClass holder = getProgramClassOrNull(fieldReference.holder);
+      recordFieldReference(fieldReference, context);
+      DexProgramClass holder = getProgramHolderOrNull(fieldReference, context);
       if (holder == null) {
         return false;
       }
@@ -4440,7 +4428,7 @@
                 : fieldAccessInfoCollection.extend(
                     fieldReference, new FieldAccessInfoImpl(fieldReference));
         fieldAccessInfo.setReadFromAnnotation();
-        markStaticFieldAsLive(field, KeepReason.referencedInAnnotation(annotationHolder));
+        markStaticFieldAsLive(field, context, KeepReason.referencedInAnnotation(annotationHolder));
         // When an annotation has a field of an enum type the JVM will use the values() method on
         // that enum class if the field is referenced.
         if (options.isGeneratingClassFiles() && field.getHolder().isEnum()) {
@@ -4449,7 +4437,8 @@
         }
       } else {
         // There is no dispatch on annotations, so only keep what is directly referenced.
-        markInstanceFieldAsReachable(field, KeepReason.referencedInAnnotation(annotationHolder));
+        markInstanceFieldAsReachable(
+            field, context, KeepReason.referencedInAnnotation(annotationHolder));
       }
       return false;
     }
@@ -4457,8 +4446,8 @@
     @Override
     public boolean addMethod(DexMethod method) {
       // Record the references in case they are not program types.
-      recordMethodReference(method);
-      DexProgramClass holder = getProgramClassOrNull(method.holder);
+      recordMethodReference(method, context);
+      DexProgramClass holder = getProgramHolderOrNull(method, context);
       if (holder == null) {
         return false;
       }
@@ -4507,30 +4496,22 @@
       // Annotations can also contain the void type, which is not a class type, so filter it out
       // here.
       if (type != dexItemFactory.voidType) {
-        markTypeAsLive(type, reason);
+        markTypeAsLive(type, context, reason);
       }
       return false;
     }
   }
 
-  public static class EnqueuerDefinitionSupplier implements DexDefinitionSupplier {
+  public static class EnqueuerDefinitionSupplier {
 
     private final Enqueuer enqueuer;
-    private final AppView<?> appView;
 
-    private EnqueuerDefinitionSupplier(AppView<?> appView, Enqueuer enqueuer) {
-      this.appView = appView;
+    EnqueuerDefinitionSupplier(Enqueuer enqueuer) {
       this.enqueuer = enqueuer;
     }
 
-    @Override
-    public DexClass definitionFor(DexType type) {
-      return enqueuer.definitionFor(type);
-    }
-
-    @Override
-    public DexItemFactory dexItemFactory() {
-      return appView.dexItemFactory();
+    public DexClass definitionFor(DexType type, ProgramDefinition context) {
+      return enqueuer.definitionFor(type, context);
     }
   }
 }
diff --git a/src/main/java/com/android/tools/r8/shaking/EnqueuerFactory.java b/src/main/java/com/android/tools/r8/shaking/EnqueuerFactory.java
index 5ff9c97..34a8750 100644
--- a/src/main/java/com/android/tools/r8/shaking/EnqueuerFactory.java
+++ b/src/main/java/com/android/tools/r8/shaking/EnqueuerFactory.java
@@ -15,7 +15,8 @@
 public class EnqueuerFactory {
 
   public static Enqueuer createForInitialTreeShaking(
-      AppView<? extends AppInfoWithClassHierarchy> appView, SubtypingInfo subtypingInfo) {
+      AppView<? extends AppInfoWithClassHierarchy> appView,
+      SubtypingInfo subtypingInfo) {
     return new Enqueuer(appView, subtypingInfo, null, Mode.INITIAL_TREE_SHAKING);
   }
 
@@ -23,19 +24,18 @@
       AppView<? extends AppInfoWithClassHierarchy> appView,
       SubtypingInfo subtypingInfo,
       GraphConsumer keptGraphConsumer,
-      Set<DexType> initialMissingTypes,
       Set<DexType> initialPrunedTypes) {
     Enqueuer enqueuer =
         new Enqueuer(appView, subtypingInfo, keptGraphConsumer, Mode.FINAL_TREE_SHAKING);
     appView.withProtoShrinker(
         shrinker -> enqueuer.setInitialDeadProtoTypes(shrinker.getDeadProtoTypes()));
-    enqueuer.setInitialMissingTypes(initialMissingTypes);
     enqueuer.setInitialPrunedTypes(initialPrunedTypes);
     return enqueuer;
   }
 
   public static Enqueuer createForMainDexTracing(
-      AppView<? extends AppInfoWithClassHierarchy> appView, SubtypingInfo subtypingInfo) {
+      AppView<? extends AppInfoWithClassHierarchy> appView,
+      SubtypingInfo subtypingInfo) {
     return createForMainDexTracing(appView, subtypingInfo, null);
   }
 
diff --git a/src/main/java/com/android/tools/r8/shaking/EnqueuerUseRegistryFactory.java b/src/main/java/com/android/tools/r8/shaking/EnqueuerUseRegistryFactory.java
index 86acd84..c15f8d7 100644
--- a/src/main/java/com/android/tools/r8/shaking/EnqueuerUseRegistryFactory.java
+++ b/src/main/java/com/android/tools/r8/shaking/EnqueuerUseRegistryFactory.java
@@ -4,11 +4,15 @@
 
 package com.android.tools.r8.shaking;
 
+import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.graph.UseRegistry;
 
 public interface EnqueuerUseRegistryFactory {
 
-  UseRegistry create(AppView<?> appView, ProgramMethod currentMethod, Enqueuer enqueuer);
+  UseRegistry create(
+      AppView<? extends AppInfoWithClassHierarchy> appView,
+      ProgramMethod currentMethod,
+      Enqueuer enqueuer);
 }
diff --git a/src/main/java/com/android/tools/r8/shaking/EnqueuerWorklist.java b/src/main/java/com/android/tools/r8/shaking/EnqueuerWorklist.java
index c368c21..0be900c 100644
--- a/src/main/java/com/android/tools/r8/shaking/EnqueuerWorklist.java
+++ b/src/main/java/com/android/tools/r8/shaking/EnqueuerWorklist.java
@@ -4,11 +4,11 @@
 
 package com.android.tools.r8.shaking;
 
-import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.ProgramDefinition;
 import com.android.tools.r8.graph.ProgramField;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.shaking.GraphReporter.KeepReasonWitness;
@@ -22,23 +22,27 @@
   }
 
   static class MarkReachableDirectAction extends EnqueuerAction {
-    final DexMethod target;
-    final KeepReason reason;
+    private final DexMethod target;
+    // TODO(b/175854431): Avoid pushing context on worklist.
+    private final ProgramDefinition context;
+    private final KeepReason reason;
 
-    MarkReachableDirectAction(DexMethod target, KeepReason reason) {
+    MarkReachableDirectAction(DexMethod target, ProgramDefinition context, KeepReason reason) {
       this.target = target;
+      this.context = context;
       this.reason = reason;
     }
 
     @Override
     public void run(Enqueuer enqueuer) {
-      enqueuer.markNonStaticDirectMethodAsReachable(target, reason);
+      enqueuer.markNonStaticDirectMethodAsReachable(target, context, reason);
     }
   }
 
   static class MarkReachableSuperAction extends EnqueuerAction {
-    final DexMethod target;
-    final ProgramMethod context;
+    private final DexMethod target;
+    // TODO(b/175854431): Avoid pushing context on worklist.
+    private final ProgramMethod context;
 
     public MarkReachableSuperAction(DexMethod target, ProgramMethod context) {
       this.target = target;
@@ -52,26 +56,30 @@
   }
 
   static class MarkReachableFieldAction extends EnqueuerAction {
-    final ProgramField field;
-    final KeepReason reason;
+    private final ProgramField field;
+    // TODO(b/175854431): Avoid pushing context on worklist.
+    private final ProgramDefinition context;
+    private final KeepReason reason;
 
-    public MarkReachableFieldAction(ProgramField field, KeepReason reason) {
+    public MarkReachableFieldAction(
+        ProgramField field, ProgramDefinition context, KeepReason reason) {
       this.field = field;
+      this.context = context;
       this.reason = reason;
     }
 
     @Override
     public void run(Enqueuer enqueuer) {
-      enqueuer.markInstanceFieldAsReachable(field, reason);
+      enqueuer.markInstanceFieldAsReachable(field, context, reason);
     }
   }
 
   static class MarkInstantiatedAction extends EnqueuerAction {
-
-    final DexProgramClass target;
-    final ProgramMethod context;
-    final InstantiationReason instantiationReason;
-    final KeepReason keepReason;
+    private final DexProgramClass target;
+    // TODO(b/175854431): Avoid pushing context on worklist.
+    private final ProgramMethod context;
+    private final InstantiationReason instantiationReason;
+    private final KeepReason keepReason;
 
     public MarkInstantiatedAction(
         DexProgramClass target,
@@ -91,8 +99,8 @@
   }
 
   static class MarkAnnotationInstantiatedAction extends EnqueuerAction {
-    final DexProgramClass target;
-    final KeepReasonWitness reason;
+    private final DexProgramClass target;
+    private final KeepReasonWitness reason;
 
     public MarkAnnotationInstantiatedAction(DexProgramClass target, KeepReasonWitness reason) {
       this.target = target;
@@ -106,8 +114,8 @@
   }
 
   static class MarkInterfaceInstantiatedAction extends EnqueuerAction {
-    final DexProgramClass target;
-    final KeepReasonWitness reason;
+    private final DexProgramClass target;
+    private final KeepReasonWitness reason;
 
     public MarkInterfaceInstantiatedAction(DexProgramClass target, KeepReasonWitness reason) {
       this.target = target;
@@ -121,23 +129,24 @@
   }
 
   static class MarkMethodLiveAction extends EnqueuerAction {
-    final ProgramMethod method;
-    final KeepReason reason;
+    private final ProgramMethod method;
+    // TODO(b/175854431): Avoid pushing context on worklist.
+    private final ProgramDefinition context;
 
-    public MarkMethodLiveAction(ProgramMethod method, KeepReason reason) {
+    public MarkMethodLiveAction(ProgramMethod method, ProgramDefinition context) {
       this.method = method;
-      this.reason = reason;
+      this.context = context;
     }
 
     @Override
     public void run(Enqueuer enqueuer) {
-      enqueuer.markMethodAsLive(method, reason);
+      enqueuer.markMethodAsLive(method, context);
     }
   }
 
   static class MarkMethodKeptAction extends EnqueuerAction {
-    final ProgramMethod target;
-    final KeepReason reason;
+    private final ProgramMethod target;
+    private final KeepReason reason;
 
     public MarkMethodKeptAction(ProgramMethod target, KeepReason reason) {
       this.target = target;
@@ -151,8 +160,8 @@
   }
 
   static class MarkFieldKeptAction extends EnqueuerAction {
-    final ProgramField field;
-    final KeepReasonWitness witness;
+    private final ProgramField field;
+    private final KeepReasonWitness witness;
 
     public MarkFieldKeptAction(ProgramField field, KeepReasonWitness witness) {
       this.field = field;
@@ -166,8 +175,9 @@
   }
 
   static class TraceConstClassAction extends EnqueuerAction {
-    final DexType type;
-    final ProgramMethod context;
+    private final DexType type;
+    // TODO(b/175854431): Avoid pushing context on worklist.
+    private final ProgramMethod context;
 
     TraceConstClassAction(DexType type, ProgramMethod context) {
       this.type = type;
@@ -181,8 +191,9 @@
   }
 
   static class TraceInvokeDirectAction extends EnqueuerAction {
-    final DexMethod invokedMethod;
-    final ProgramMethod context;
+    private final DexMethod invokedMethod;
+    // TODO(b/175854431): Avoid pushing context on worklist.
+    private final ProgramMethod context;
 
     TraceInvokeDirectAction(DexMethod invokedMethod, ProgramMethod context) {
       this.invokedMethod = invokedMethod;
@@ -196,8 +207,9 @@
   }
 
   static class TraceNewInstanceAction extends EnqueuerAction {
-    final DexType type;
-    final ProgramMethod context;
+    private final DexType type;
+    // TODO(b/175854431): Avoid pushing context on worklist.
+    private final ProgramMethod context;
 
     TraceNewInstanceAction(DexType type, ProgramMethod context) {
       this.type = type;
@@ -211,8 +223,9 @@
   }
 
   static class TraceStaticFieldReadAction extends EnqueuerAction {
-    final DexField field;
-    final ProgramMethod context;
+    private final DexField field;
+    // TODO(b/175854431): Avoid pushing context on worklist.
+    private final ProgramMethod context;
 
     TraceStaticFieldReadAction(DexField field, ProgramMethod context) {
       this.field = field;
@@ -225,15 +238,12 @@
     }
   }
 
-  private final AppView<?> appView;
   private final Queue<EnqueuerAction> queue = new ArrayDeque<>();
 
-  private EnqueuerWorklist(AppView<?> appView) {
-    this.appView = appView;
-  }
+  private EnqueuerWorklist() {}
 
-  public static EnqueuerWorklist createWorklist(AppView<?> appView) {
-    return new EnqueuerWorklist(appView);
+  public static EnqueuerWorklist createWorklist() {
+    return new EnqueuerWorklist();
   }
 
   public boolean isEmpty() {
@@ -244,16 +254,18 @@
     return queue.poll();
   }
 
-  void enqueueMarkReachableDirectAction(DexMethod method, KeepReason reason) {
-    queue.add(new MarkReachableDirectAction(method, reason));
+  void enqueueMarkReachableDirectAction(
+      DexMethod method, ProgramDefinition context, KeepReason reason) {
+    queue.add(new MarkReachableDirectAction(method, context, reason));
   }
 
   void enqueueMarkReachableSuperAction(DexMethod method, ProgramMethod from) {
     queue.add(new MarkReachableSuperAction(method, from));
   }
 
-  public void enqueueMarkReachableFieldAction(ProgramField field, KeepReason reason) {
-    queue.add(new MarkReachableFieldAction(field, reason));
+  public void enqueueMarkReachableFieldAction(
+      ProgramField field, ProgramDefinition context, KeepReason reason) {
+    queue.add(new MarkReachableFieldAction(field, context, reason));
   }
 
   // TODO(b/142378367): Context is the containing method that is cause of the instantiation.
@@ -280,8 +292,8 @@
     queue.add(new MarkInterfaceInstantiatedAction(clazz, reason));
   }
 
-  void enqueueMarkMethodLiveAction(ProgramMethod method, KeepReason reason) {
-    queue.add(new MarkMethodLiveAction(method, reason));
+  void enqueueMarkMethodLiveAction(ProgramMethod method, ProgramDefinition context) {
+    queue.add(new MarkMethodLiveAction(method, context));
   }
 
   void enqueueMarkMethodKeptAction(ProgramMethod method, KeepReason reason) {
diff --git a/src/main/java/com/android/tools/r8/shaking/FieldAccessInfoCollectionModifier.java b/src/main/java/com/android/tools/r8/shaking/FieldAccessInfoCollectionModifier.java
index 4e9d977..f656a56 100644
--- a/src/main/java/com/android/tools/r8/shaking/FieldAccessInfoCollectionModifier.java
+++ b/src/main/java/com/android/tools/r8/shaking/FieldAccessInfoCollectionModifier.java
@@ -6,91 +6,57 @@
 
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexField;
-import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.FieldAccessInfoCollectionImpl;
 import com.android.tools.r8.graph.FieldAccessInfoImpl;
 import com.android.tools.r8.graph.ProgramMethod;
-import java.util.ArrayList;
-import java.util.Collection;
+import com.android.tools.r8.utils.collections.ProgramMethodSet;
 import java.util.IdentityHashMap;
-import java.util.List;
 import java.util.Map;
-import java.util.function.BiConsumer;
-import java.util.function.Function;
 
 public class FieldAccessInfoCollectionModifier {
 
   static class FieldReferences {
-    final List<DexMethod> writeContexts = new ArrayList<>();
-    final List<DexMethod> readContexts = new ArrayList<>();
-
-    void fixUpMethods(List<DexMethod> methods, Function<DexMethod, DexMethod> fixUpMethod) {
-      for (int i = 0; i < methods.size(); i++) {
-        DexMethod method = methods.get(i);
-        DexMethod newMethod = fixUpMethod.apply(method);
-        if (method != newMethod) {
-          methods.set(i, newMethod);
-        }
-      }
-    }
-
-    void fixUp(Function<DexMethod, DexMethod> fixUpMethod) {
-      fixUpMethods(writeContexts, fixUpMethod);
-      fixUpMethods(readContexts, fixUpMethod);
-    }
+    private final ProgramMethodSet writeContexts = ProgramMethodSet.create();
+    private final ProgramMethodSet readContexts = ProgramMethodSet.create();
   }
 
-  final Map<DexField, FieldReferences> newFieldAccesses;
+  private final Map<DexField, FieldReferences> newFieldAccesses;
 
   FieldAccessInfoCollectionModifier(Map<DexField, FieldReferences> newFieldAccesses) {
     this.newFieldAccesses = newFieldAccesses;
   }
 
-  void forEachFieldAccess(
-      AppView<?> appView,
-      Collection<DexMethod> methods,
-      DexField field,
-      BiConsumer<DexField, ProgramMethod> record) {
-    for (DexMethod method : methods) {
-      ProgramMethod programMethod =
-          appView.definitionFor(method.holder).asProgramClass().lookupProgramMethod(method);
-      record.accept(field, programMethod);
-    }
-  }
-
   public void modify(AppView<AppInfoWithLiveness> appView) {
     FieldAccessInfoCollectionImpl impl = appView.appInfo().getMutableFieldAccessInfoCollection();
     newFieldAccesses.forEach(
         (field, info) -> {
           FieldAccessInfoImpl fieldAccessInfo = new FieldAccessInfoImpl(field);
-          forEachFieldAccess(appView, info.readContexts, field, fieldAccessInfo::recordRead);
-          forEachFieldAccess(appView, info.writeContexts, field, fieldAccessInfo::recordWrite);
+          info.readContexts.forEach(context -> fieldAccessInfo.recordRead(field, context));
+          info.writeContexts.forEach(context -> fieldAccessInfo.recordWrite(field, context));
           impl.extend(field, fieldAccessInfo);
         });
   }
 
   public static class Builder {
-    final Map<DexField, FieldReferences> newFieldAccesses = new IdentityHashMap<>();
+
+    private final Map<DexField, FieldReferences> newFieldAccesses = new IdentityHashMap<>();
 
     public Builder() {}
 
-    public FieldAccessInfoCollectionModifier build(Function<DexMethod, DexMethod> fixupMethod) {
-      for (FieldReferences fieldReference : newFieldAccesses.values()) {
-        fieldReference.fixUp(fixupMethod);
-      }
-      return new FieldAccessInfoCollectionModifier(newFieldAccesses);
-    }
-
-    FieldReferences getFieldReferences(DexField field) {
+    private FieldReferences getFieldReferences(DexField field) {
       return newFieldAccesses.computeIfAbsent(field, ignore -> new FieldReferences());
     }
 
-    public void fieldReadByMethod(DexField field, DexMethod method) {
-      getFieldReferences(field).readContexts.add(method);
+    public void recordFieldReadInContext(DexField field, ProgramMethod context) {
+      getFieldReferences(field).readContexts.add(context);
     }
 
-    public void fieldWrittenByMethod(DexField field, DexMethod method) {
-      getFieldReferences(field).writeContexts.add(method);
+    public void recordFieldWrittenInContext(DexField field, ProgramMethod context) {
+      getFieldReferences(field).writeContexts.add(context);
+    }
+
+    public FieldAccessInfoCollectionModifier build() {
+      return new FieldAccessInfoCollectionModifier(newFieldAccesses);
     }
   }
 }
diff --git a/src/main/java/com/android/tools/r8/shaking/GraphReporter.java b/src/main/java/com/android/tools/r8/shaking/GraphReporter.java
index d78c808..86b0d08 100644
--- a/src/main/java/com/android/tools/r8/shaking/GraphReporter.java
+++ b/src/main/java/com/android/tools/r8/shaking/GraphReporter.java
@@ -26,6 +26,7 @@
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexReference;
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.ProgramDefinition;
 import com.android.tools.r8.graph.ProgramField;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.desugar.InterfaceMethodRewriter;
@@ -202,6 +203,18 @@
   }
 
   public KeepReasonWitness reportClassReferencedFrom(
+      DexProgramClass clazz, ProgramDefinition context) {
+    if (context.isProgramClass()) {
+      return reportClassReferencedFrom(clazz, context.asProgramClass());
+    } else if (context.isProgramField()) {
+      return reportClassReferencedFrom(clazz, context.asProgramField());
+    } else {
+      assert context.isProgramMethod();
+      return reportClassReferencedFrom(clazz, context.asProgramMethod());
+    }
+  }
+
+  public KeepReasonWitness reportClassReferencedFrom(
       DexProgramClass clazz, DexProgramClass implementer) {
     if (keptGraphConsumer != null) {
       ClassGraphNode source = getClassGraphNode(implementer.type);
diff --git a/src/main/java/com/android/tools/r8/shaking/InlineRule.java b/src/main/java/com/android/tools/r8/shaking/InlineRule.java
index 41b6b33..32412d7 100644
--- a/src/main/java/com/android/tools/r8/shaking/InlineRule.java
+++ b/src/main/java/com/android/tools/r8/shaking/InlineRule.java
@@ -18,7 +18,10 @@
   };
 
   public enum Type {
-    ALWAYS, FORCE, NEVER
+    ALWAYS,
+    FORCE,
+    NEVER,
+    NEVER_SINGLE_CALLER
   }
 
   public static class Builder extends ProguardConfigurationRule.Builder<InlineRule, Builder> {
@@ -127,6 +130,8 @@
         return "forceinline";
       case NEVER:
         return "neverinline";
+      case NEVER_SINGLE_CALLER:
+        return "neversinglecaller";
     }
     throw new Unreachable("Unknown inline type " + type);
   }
diff --git a/src/main/java/com/android/tools/r8/shaking/MainDexDirectReferenceTracer.java b/src/main/java/com/android/tools/r8/shaking/MainDexDirectReferenceTracer.java
index 91f9bbc..6f7cade 100644
--- a/src/main/java/com/android/tools/r8/shaking/MainDexDirectReferenceTracer.java
+++ b/src/main/java/com/android/tools/r8/shaking/MainDexDirectReferenceTracer.java
@@ -22,7 +22,7 @@
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.graph.UseRegistry;
-import com.android.tools.r8.utils.BooleanBox;
+import com.android.tools.r8.utils.Box;
 import java.util.Set;
 import java.util.function.Consumer;
 
@@ -68,9 +68,12 @@
 
   public static boolean hasReferencesOutsideFromCode(
       AppInfoWithClassHierarchy appInfo, ProgramMethod method, Set<DexType> classes) {
+    return getFirstReferenceOutsideFromCode(appInfo, method, classes) != null;
+  }
 
-    BooleanBox result = new BooleanBox();
-
+  public static DexProgramClass getFirstReferenceOutsideFromCode(
+      AppInfoWithClassHierarchy appInfo, ProgramMethod method, Set<DexType> classes) {
+    Box<DexProgramClass> result = new Box<>();
     new MainDexDirectReferenceTracer(
             appInfo,
             type -> {
@@ -78,12 +81,11 @@
               if (baseType.isClassType() && !classes.contains(baseType)) {
                 DexClass cls = appInfo.definitionFor(baseType);
                 if (cls != null && cls.isProgramClass()) {
-                  result.set(true);
+                  result.set(cls.asProgramClass());
                 }
               }
             })
         .runOnCode(method);
-
     return result.get();
   }
 
diff --git a/src/main/java/com/android/tools/r8/shaking/MainDexTracingResult.java b/src/main/java/com/android/tools/r8/shaking/MainDexTracingResult.java
index 6383473..3af323c 100644
--- a/src/main/java/com/android/tools/r8/shaking/MainDexTracingResult.java
+++ b/src/main/java/com/android/tools/r8/shaking/MainDexTracingResult.java
@@ -6,8 +6,10 @@
 
 import com.android.tools.r8.graph.AppInfo;
 import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.ProgramDefinition;
+import com.android.tools.r8.utils.SetUtils;
 import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Sets;
 import java.util.Collection;
@@ -23,11 +25,29 @@
 
   public static class Builder {
     public final AppInfo appInfo;
-    public final Set<DexType> roots = Sets.newIdentityHashSet();
-    public final Set<DexType> dependencies = Sets.newIdentityHashSet();
+    public final Set<DexType> roots;
+    public final Set<DexType> dependencies;
 
     private Builder(AppInfo appInfo) {
+      this(appInfo, Sets.newIdentityHashSet(), Sets.newIdentityHashSet());
+    }
+
+    private Builder(AppInfo appInfo, MainDexTracingResult mainDexTracingResult) {
+      this(
+          appInfo,
+          SetUtils.newIdentityHashSet(mainDexTracingResult.getRoots()),
+          SetUtils.newIdentityHashSet(mainDexTracingResult.getDependencies()));
+    }
+
+    private Builder(AppInfo appInfo, Set<DexType> roots, Set<DexType> dependencies) {
       this.appInfo = appInfo;
+      this.roots = roots;
+      this.dependencies = dependencies;
+    }
+
+    public Builder addRoot(DexProgramClass clazz) {
+      roots.add(clazz.getType());
+      return this;
     }
 
     public Builder addRoot(DexType type) {
@@ -141,4 +161,8 @@
   public static Builder builder(AppInfo appInfo) {
     return new Builder(appInfo);
   }
+
+  public Builder extensionBuilder(AppInfo appInfo) {
+    return new Builder(appInfo, this);
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/shaking/MissingClasses.java b/src/main/java/com/android/tools/r8/shaking/MissingClasses.java
new file mode 100644
index 0000000..d3cd164
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/shaking/MissingClasses.java
@@ -0,0 +1,138 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.shaking;
+
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.synthesis.CommittedItems;
+import com.android.tools.r8.utils.InternalOptions;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Sets;
+import java.util.Collection;
+import java.util.Set;
+
+public class MissingClasses {
+
+  private final Set<DexType> missingClasses;
+
+  private MissingClasses(Set<DexType> missingClasses) {
+    this.missingClasses = missingClasses;
+  }
+
+  public Builder builder() {
+    return new Builder(missingClasses);
+  }
+
+  public static Builder builderForInitialMissingClasses() {
+    return new Builder();
+  }
+
+  public static MissingClasses empty() {
+    return new MissingClasses(Sets.newIdentityHashSet());
+  }
+
+  public MissingClasses commitSyntheticItems(CommittedItems committedItems) {
+    return builder()
+        // TODO(b/175542052): Synthetic types should not be reported as missing in the first place.
+        .removeAlreadyMissingClasses(committedItems.getLegacySyntheticTypes())
+        .ignoreMissingClasses();
+  }
+
+  public boolean contains(DexType type) {
+    return missingClasses.contains(type);
+  }
+
+  public static class Builder {
+
+    private final Set<DexType> alreadyMissingClasses;
+    private final Set<DexType> newMissingClasses = Sets.newIdentityHashSet();
+
+    // Set of missing types that are not to be reported as missing. This does not hide reports
+    // if the same type is in newMissingClasses in which case it is reported regardless.
+    private final Set<DexType> newIgnoredMissingClasses = Sets.newIdentityHashSet();
+
+    private Builder() {
+      this(Sets.newIdentityHashSet());
+    }
+
+    private Builder(Set<DexType> alreadyMissingClasses) {
+      this.alreadyMissingClasses = alreadyMissingClasses;
+    }
+
+    public void addNewMissingClass(DexType type) {
+      newMissingClasses.add(type);
+    }
+
+    public Builder addNewMissingClasses(Collection<DexType> types) {
+      newMissingClasses.addAll(types);
+      return this;
+    }
+
+    public void ignoreNewMissingClass(DexType type) {
+      newIgnoredMissingClasses.add(type);
+    }
+
+    public boolean contains(DexType type) {
+      return alreadyMissingClasses.contains(type) || newMissingClasses.contains(type);
+    }
+
+    Builder removeAlreadyMissingClasses(Iterable<DexType> types) {
+      for (DexType type : types) {
+        alreadyMissingClasses.remove(type);
+      }
+      return this;
+    }
+
+    @Deprecated
+    public MissingClasses ignoreMissingClasses() {
+      return build();
+    }
+
+    public MissingClasses reportMissingClasses(InternalOptions options) {
+      Set<DexType> newMissingClassesWithoutDontWarn =
+          options.getProguardConfiguration().getDontWarnPatterns().getNonMatches(newMissingClasses);
+      newMissingClassesWithoutDontWarn.removeAll(
+          getAllowedMissingClasses(options.dexItemFactory()));
+      if (!newMissingClassesWithoutDontWarn.isEmpty()) {
+        MissingClassesDiagnostic diagnostic =
+            new MissingClassesDiagnostic.Builder()
+                .addMissingClasses(newMissingClassesWithoutDontWarn)
+                .setFatal(!options.ignoreMissingClasses)
+                .build();
+        if (options.ignoreMissingClasses) {
+          options.reporter.warning(diagnostic);
+        } else {
+          throw options.reporter.fatalError(diagnostic);
+        }
+      }
+      return build();
+    }
+
+    private static Collection<DexType> getAllowedMissingClasses(DexItemFactory dexItemFactory) {
+      return ImmutableList.of(dexItemFactory.annotationThrows);
+    }
+
+    /** Intentionally private, use {@link Builder#reportMissingClasses(InternalOptions)}. */
+    private MissingClasses build() {
+      // Extend the newMissingClasses set with all other missing classes.
+      //
+      // We also add newIgnoredMissingClasses to newMissingClasses to be able to assert that we have
+      // a closed world after the first round of tree shaking: we should never lookup a class that
+      // was not live or missing during the first round of tree shaking.
+      // See also AppInfoWithLiveness.definitionFor().
+      //
+      // Note: At this point, all missing classes in newMissingClasses have already been reported.
+      // Thus adding newIgnoredMissingClasses to newMissingClasses will not lead to reports for the
+      // classes in newIgnoredMissingClasses.
+      newMissingClasses.addAll(alreadyMissingClasses);
+      newMissingClasses.addAll(newIgnoredMissingClasses);
+      return new MissingClasses(newMissingClasses);
+    }
+
+    public boolean wasAlreadyMissing(DexType type) {
+      return alreadyMissingClasses.contains(type);
+    }
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/shaking/MissingClassesDiagnostic.java b/src/main/java/com/android/tools/r8/shaking/MissingClassesDiagnostic.java
new file mode 100644
index 0000000..5965cc5
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/shaking/MissingClassesDiagnostic.java
@@ -0,0 +1,106 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.shaking;
+
+import com.android.tools.r8.Diagnostic;
+import com.android.tools.r8.Keep;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.position.Position;
+import com.android.tools.r8.references.ClassReference;
+import com.android.tools.r8.references.Reference;
+import com.google.common.collect.ImmutableSortedSet;
+import java.util.Collection;
+import java.util.Comparator;
+import java.util.Iterator;
+import java.util.Set;
+import java.util.SortedSet;
+
+@Keep
+public class MissingClassesDiagnostic implements Diagnostic {
+
+  private final boolean fatal;
+  private final SortedSet<ClassReference> missingClasses;
+
+  private MissingClassesDiagnostic(boolean fatal, SortedSet<ClassReference> missingClasses) {
+    assert !missingClasses.isEmpty();
+    this.fatal = fatal;
+    this.missingClasses = missingClasses;
+  }
+
+  public Set<ClassReference> getMissingClasses() {
+    return missingClasses;
+  }
+
+  /** A missing class(es) failure can generally not be attributed to a single origin. */
+  @Override
+  public Origin getOrigin() {
+    return Origin.unknown();
+  }
+
+  /** A missing class(es) failure can generally not be attributed to a single position. */
+  @Override
+  public Position getPosition() {
+    return Position.UNKNOWN;
+  }
+
+  // TODO(b/175755807): Extend diagnostic message with contextual information.
+  @Override
+  public String getDiagnosticMessage() {
+    return fatal ? getFatalDiagnosticMessage() : getNonFatalDiagnosticMessage();
+  }
+
+  private String getFatalDiagnosticMessage() {
+    if (missingClasses.size() == 1) {
+      return "Compilation can't be completed because the class "
+          + missingClasses.iterator().next().getTypeName()
+          + " is missing.";
+    }
+    StringBuilder builder =
+        new StringBuilder("Compilation can't be completed because the following ")
+            .append(missingClasses.size())
+            .append(" classes are missing:");
+    for (ClassReference missingClass : missingClasses) {
+      builder.append(System.lineSeparator()).append("- ").append(missingClass.getTypeName());
+    }
+    return builder.toString();
+  }
+
+  private String getNonFatalDiagnosticMessage() {
+    StringBuilder builder = new StringBuilder();
+    Iterator<ClassReference> missingClassesIterator = missingClasses.iterator();
+    while (missingClassesIterator.hasNext()) {
+      ClassReference missingClass = missingClassesIterator.next();
+      builder.append("Missing class ").append(missingClass.getTypeName());
+      if (missingClassesIterator.hasNext()) {
+        builder.append(System.lineSeparator());
+      }
+    }
+    return builder.toString();
+  }
+
+  public static class Builder {
+
+    private boolean fatal;
+    private ImmutableSortedSet.Builder<ClassReference> missingClassesBuilder =
+        ImmutableSortedSet.orderedBy(Comparator.comparing(ClassReference::getDescriptor));
+
+    public MissingClassesDiagnostic.Builder addMissingClasses(Collection<DexType> missingClasses) {
+      for (DexType missingClass : missingClasses) {
+        missingClassesBuilder.add(Reference.classFromDescriptor(missingClass.toDescriptorString()));
+      }
+      return this;
+    }
+
+    public MissingClassesDiagnostic.Builder setFatal(boolean fatal) {
+      this.fatal = fatal;
+      return this;
+    }
+
+    public MissingClassesDiagnostic build() {
+      return new MissingClassesDiagnostic(fatal, missingClassesBuilder.build());
+    }
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardClassFilter.java b/src/main/java/com/android/tools/r8/shaking/ProguardClassFilter.java
index c023594..d63374c 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardClassFilter.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardClassFilter.java
@@ -4,9 +4,13 @@
 
 package com.android.tools.r8.shaking;
 
+import static com.android.tools.r8.utils.PredicateUtils.not;
+
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.shaking.ProguardTypeMatcher.MatchSpecificType;
+import com.android.tools.r8.utils.TraversalContinuation;
 import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Sets;
 import java.util.Set;
 
 public class ProguardClassFilter {
@@ -55,6 +59,33 @@
     return false;
   }
 
+  public Set<DexType> getNonMatches(Set<DexType> types) {
+    Set<DexType> nonMatches = Sets.newIdentityHashSet();
+    for (DexType type : types) {
+      TraversalContinuation traversalContinuation = TraversalContinuation.CONTINUE;
+      for (ProguardClassNameList pattern : patterns) {
+        traversalContinuation =
+            pattern.traverseTypeMatchers(
+                matcher -> {
+                  if (matcher.matches(type)) {
+                    return TraversalContinuation.BREAK;
+                  }
+                  return TraversalContinuation.CONTINUE;
+                },
+                not(ProguardTypeMatcher::hasSpecificType));
+      }
+      if (traversalContinuation.shouldContinue()) {
+        nonMatches.add(type);
+      }
+    }
+    for (ProguardClassNameList pattern : patterns) {
+      pattern.forEachTypeMatcher(
+          matcher -> nonMatches.remove(matcher.getSpecificType()),
+          ProguardTypeMatcher::hasSpecificType);
+    }
+    return nonMatches;
+  }
+
   public void filterOutMatches(Set<DexType> types) {
     for (ProguardClassNameList pattern : patterns) {
       pattern.forEachTypeMatcher(matcher -> {
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardClassNameList.java b/src/main/java/com/android/tools/r8/shaking/ProguardClassNameList.java
index ae80f4c..8ffaa8e 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardClassNameList.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardClassNameList.java
@@ -5,6 +5,7 @@
 
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.utils.TraversalContinuation;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.Iterables;
 import it.unimi.dsi.fastutil.objects.Object2BooleanArrayMap;
@@ -15,6 +16,8 @@
 import java.util.List;
 import java.util.Objects;
 import java.util.function.Consumer;
+import java.util.function.Function;
+import java.util.function.Predicate;
 import java.util.stream.Collectors;
 import java.util.stream.StreamSupport;
 
@@ -101,6 +104,31 @@
 
   public abstract void forEachTypeMatcher(Consumer<ProguardTypeMatcher> consumer);
 
+  public final void forEachTypeMatcher(
+      Consumer<ProguardTypeMatcher> consumer, Predicate<ProguardTypeMatcher> predicate) {
+    forEachTypeMatcher(
+        matcher -> {
+          if (predicate.test(matcher)) {
+            consumer.accept(matcher);
+          }
+        });
+  }
+
+  public abstract TraversalContinuation traverseTypeMatchers(
+      Function<ProguardTypeMatcher, TraversalContinuation> fn);
+
+  public final TraversalContinuation traverseTypeMatchers(
+      Function<ProguardTypeMatcher, TraversalContinuation> fn,
+      Predicate<ProguardTypeMatcher> predicate) {
+    return traverseTypeMatchers(
+        matcher -> {
+          if (predicate.test(matcher)) {
+            return fn.apply(matcher);
+          }
+          return TraversalContinuation.CONTINUE;
+        });
+  }
+
   private static class EmptyClassNameList extends ProguardClassNameList {
 
     private EmptyClassNameList() {
@@ -138,6 +166,12 @@
     @Override
     public void forEachTypeMatcher(Consumer<ProguardTypeMatcher> consumer) {
     }
+
+    @Override
+    public TraversalContinuation traverseTypeMatchers(
+        Function<ProguardTypeMatcher, TraversalContinuation> fn) {
+      return TraversalContinuation.CONTINUE;
+    }
   }
 
   static class SingleClassNameList extends ProguardClassNameList {
@@ -200,6 +234,12 @@
     public void forEachTypeMatcher(Consumer<ProguardTypeMatcher> consumer) {
       consumer.accept(className);
     }
+
+    @Override
+    public TraversalContinuation traverseTypeMatchers(
+        Function<ProguardTypeMatcher, TraversalContinuation> fn) {
+      return fn.apply(className);
+    }
   }
 
   private static class PositiveClassNameList extends ProguardClassNameList {
@@ -278,6 +318,17 @@
     public void forEachTypeMatcher(Consumer<ProguardTypeMatcher> consumer) {
       classNames.forEach(consumer);
     }
+
+    @Override
+    public TraversalContinuation traverseTypeMatchers(
+        Function<ProguardTypeMatcher, TraversalContinuation> fn) {
+      for (ProguardTypeMatcher matcher : classNames) {
+        if (fn.apply(matcher).shouldBreak()) {
+          return TraversalContinuation.BREAK;
+        }
+      }
+      return TraversalContinuation.CONTINUE;
+    }
   }
 
   private static class MixedClassNameList extends ProguardClassNameList {
@@ -363,5 +414,16 @@
     public void forEachTypeMatcher(Consumer<ProguardTypeMatcher> consumer) {
       classNames.object2BooleanEntrySet().forEach(entry -> consumer.accept(entry.getKey()));
     }
+
+    @Override
+    public TraversalContinuation traverseTypeMatchers(
+        Function<ProguardTypeMatcher, TraversalContinuation> fn) {
+      for (ProguardTypeMatcher matcher : classNames.keySet()) {
+        if (fn.apply(matcher).shouldBreak()) {
+          return TraversalContinuation.BREAK;
+        }
+      }
+      return TraversalContinuation.CONTINUE;
+    }
   }
 }
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
index f71d027..8294be0 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
@@ -469,6 +469,11 @@
           configurationBuilder.addRule(rule);
           return true;
         }
+        if (acceptString("neversinglecallerinline")) {
+          InlineRule rule = parseInlineRule(InlineRule.Type.NEVER_SINGLE_CALLER, optionStart);
+          configurationBuilder.addRule(rule);
+          return true;
+        }
         if (acceptString(NoUnusedInterfaceRemovalRule.RULE_NAME)) {
           ProguardConfigurationRule rule = parseNoUnusedInterfaceRemovalRule(optionStart);
           configurationBuilder.addRule(rule);
diff --git a/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java b/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java
index be431d3..7f18fff 100644
--- a/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java
+++ b/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java
@@ -28,6 +28,7 @@
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.DirectMappedDexApplication;
 import com.android.tools.r8.graph.GraphLens;
+import com.android.tools.r8.graph.ProgramMember;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.graph.ResolutionResult.SingleResolutionResult;
 import com.android.tools.r8.graph.SubtypingInfo;
@@ -91,6 +92,7 @@
   private final Set<DexMethod> alwaysInline = Sets.newIdentityHashSet();
   private final Set<DexMethod> forceInline = Sets.newIdentityHashSet();
   private final Set<DexMethod> neverInline = Sets.newIdentityHashSet();
+  private final Set<DexMethod> neverInlineDueToSingleCaller = Sets.newIdentityHashSet();
   private final Set<DexMethod> bypassClinitforInlining = Sets.newIdentityHashSet();
   private final Set<DexMethod> whyAreYouNotInlining = Sets.newIdentityHashSet();
   private final Set<DexMethod> keepParametersWithConstantValue = Sets.newIdentityHashSet();
@@ -347,6 +349,7 @@
         alwaysInline,
         forceInline,
         neverInline,
+        neverInlineDueToSingleCaller,
         bypassClinitforInlining,
         whyAreYouNotInlining,
         keepParametersWithConstantValue,
@@ -431,6 +434,7 @@
   ConsequentRootSet buildConsequentRootSet() {
     return new ConsequentRootSet(
         neverInline,
+        neverInlineDueToSingleCaller,
         neverClassInline,
         noShrinking,
         softPinned,
@@ -1198,15 +1202,19 @@
       context.markAsUsed();
     } else if (context instanceof InlineRule) {
       if (item.isDexEncodedMethod()) {
+        DexMethod reference = item.asDexEncodedMethod().getReference();
         switch (((InlineRule) context).getType()) {
           case ALWAYS:
-            alwaysInline.add(item.asDexEncodedMethod().method);
+            alwaysInline.add(reference);
             break;
           case FORCE:
-            forceInline.add(item.asDexEncodedMethod().method);
+            forceInline.add(reference);
             break;
           case NEVER:
-            neverInline.add(item.asDexEncodedMethod().method);
+            neverInline.add(reference);
+            break;
+          case NEVER_SINGLE_CALLER:
+            neverInlineDueToSingleCaller.add(reference);
             break;
           default:
             throw new Unreachable();
@@ -1331,6 +1339,7 @@
   abstract static class RootSetBase {
 
     final Set<DexMethod> neverInline;
+    final Set<DexMethod> neverInlineDueToSingleCaller;
     final Set<DexType> neverClassInline;
     final MutableItemsWithRules noShrinking;
     final MutableItemsWithRules softPinned;
@@ -1342,6 +1351,7 @@
 
     RootSetBase(
         Set<DexMethod> neverInline,
+        Set<DexMethod> neverInlineDueToSingleCaller,
         Set<DexType> neverClassInline,
         MutableItemsWithRules noShrinking,
         MutableItemsWithRules softPinned,
@@ -1351,6 +1361,7 @@
         Map<DexType, Set<ProguardKeepRuleBase>> dependentKeepClassCompatRule,
         List<DelayedRootSetActionItem> delayedRootSetActionItems) {
       this.neverInline = neverInline;
+      this.neverInlineDueToSingleCaller = neverInlineDueToSingleCaller;
       this.neverClassInline = neverClassInline;
       this.noShrinking = noShrinking;
       this.softPinned = softPinned;
@@ -1414,14 +1425,14 @@
     public void forEachDependentMember(
         DexDefinition item,
         AppView<?> appView,
-        Consumer3<DexDefinition, DexEncodedMember<?, ?>, Set<ProguardKeepRuleBase>> fn) {
+        Consumer3<DexDefinition, ProgramMember<?, ?>, Set<ProguardKeepRuleBase>> fn) {
       getDependentItems(item)
           .forEachMember(
               (reference, reasons) -> {
                 DexProgramClass holder =
                     asProgramClassOrNull(appView.definitionForHolder(reference));
                 if (holder != null) {
-                  DexEncodedMember<?, ?> member = holder.lookupMember(reference);
+                  ProgramMember<?, ?> member = holder.lookupProgramMember(reference);
                   if (member != null) {
                     fn.accept(item, member, reasons);
                   }
@@ -1432,12 +1443,12 @@
     public void forEachDependentNonStaticMember(
         DexDefinition item,
         AppView<?> appView,
-        Consumer3<DexDefinition, DexEncodedMember<?, ?>, Set<ProguardKeepRuleBase>> fn) {
+        Consumer3<DexDefinition, ProgramMember<?, ?>, Set<ProguardKeepRuleBase>> fn) {
       forEachDependentMember(
           item,
           appView,
           (precondition, member, reasons) -> {
-            if (!member.isStatic()) {
+            if (!member.getDefinition().isStatic()) {
               fn.accept(precondition, member, reasons);
             }
           });
@@ -1446,12 +1457,12 @@
     public void forEachDependentStaticMember(
         DexDefinition item,
         AppView<?> appView,
-        Consumer3<DexDefinition, DexEncodedMember<?, ?>, Set<ProguardKeepRuleBase>> fn) {
+        Consumer3<DexDefinition, ProgramMember<?, ?>, Set<ProguardKeepRuleBase>> fn) {
       forEachDependentMember(
           item,
           appView,
           (precondition, member, reasons) -> {
-            if (member.isStatic()) {
+            if (member.getDefinition().isStatic()) {
               fn.accept(precondition, member, reasons);
             }
           });
@@ -1777,6 +1788,7 @@
         Set<DexMethod> alwaysInline,
         Set<DexMethod> forceInline,
         Set<DexMethod> neverInline,
+        Set<DexMethod> neverInlineDueToSingleCaller,
         Set<DexMethod> bypassClinitForInlining,
         Set<DexMethod> whyAreYouNotInlining,
         Set<DexMethod> keepConstantArguments,
@@ -1801,6 +1813,7 @@
         List<DelayedRootSetActionItem> delayedRootSetActionItems) {
       super(
           neverInline,
+          neverInlineDueToSingleCaller,
           neverClassInline,
           noShrinking,
           softPinned,
@@ -1850,6 +1863,7 @@
 
     void addConsequentRootSet(ConsequentRootSet consequentRootSet, boolean addNoShrinking) {
       neverInline.addAll(consequentRootSet.neverInline);
+      neverInlineDueToSingleCaller.addAll(consequentRootSet.neverInlineDueToSingleCaller);
       neverClassInline.addAll(consequentRootSet.neverClassInline);
       noObfuscation.addAll(consequentRootSet.noObfuscation);
       if (addNoShrinking) {
@@ -2107,6 +2121,7 @@
 
     ConsequentRootSet(
         Set<DexMethod> neverInline,
+        Set<DexMethod> neverInlineDueToSingleCaller,
         Set<DexType> neverClassInline,
         MutableItemsWithRules noShrinking,
         MutableItemsWithRules softPinned,
@@ -2117,6 +2132,7 @@
         List<DelayedRootSetActionItem> delayedRootSetActionItems) {
       super(
           neverInline,
+          neverInlineDueToSingleCaller,
           neverClassInline,
           noShrinking,
           softPinned,
diff --git a/src/main/java/com/android/tools/r8/shaking/SingleTargetLookupCache.java b/src/main/java/com/android/tools/r8/shaking/SingleTargetLookupCache.java
index a2f246e..df9803a 100644
--- a/src/main/java/com/android/tools/r8/shaking/SingleTargetLookupCache.java
+++ b/src/main/java/com/android/tools/r8/shaking/SingleTargetLookupCache.java
@@ -35,9 +35,9 @@
         instance ->
             appInfo.traverseSuperTypes(
                 instance,
-                (type, ignore) -> {
-                  if (seen.add(type)) {
-                    cache.remove(type);
+                (superType, subclass, ignore) -> {
+                  if (seen.add(superType)) {
+                    cache.remove(superType);
                     return TraversalContinuation.CONTINUE;
                   } else {
                     return TraversalContinuation.BREAK;
diff --git a/src/main/java/com/android/tools/r8/shaking/TreePruner.java b/src/main/java/com/android/tools/r8/shaking/TreePruner.java
index 9d0b174..9155165 100644
--- a/src/main/java/com/android/tools/r8/shaking/TreePruner.java
+++ b/src/main/java/com/android/tools/r8/shaking/TreePruner.java
@@ -36,7 +36,6 @@
   private final AppView<AppInfoWithLiveness> appView;
   private final TreePrunerConfiguration configuration;
   private final UnusedItemsPrinter unusedItemsPrinter;
-  private final Set<DexType> missingTypes;
   private final Set<DexType> prunedTypes = Sets.newIdentityHashSet();
   private final Set<DexMethod> methodsToKeepForConfigurationDebugging = Sets.newIdentityHashSet();
 
@@ -48,7 +47,6 @@
     InternalOptions options = appView.options();
     this.appView = appView;
     this.configuration = configuration;
-    this.missingTypes = appView.appInfo().getMissingTypes();
     this.unusedItemsPrinter =
         options.hasUsageInformationConsumer()
             ? new UnusedItemsPrinter(
@@ -207,7 +205,7 @@
   }
 
   private boolean isTypeMissing(DexType type) {
-    return missingTypes.contains(type);
+    return appView.appInfo().getMissingClasses().contains(type);
   }
 
   private boolean isTypeLive(DexType type) {
diff --git a/src/main/java/com/android/tools/r8/synthesis/CommittedItems.java b/src/main/java/com/android/tools/r8/synthesis/CommittedItems.java
index f6a6245..424fd8d 100644
--- a/src/main/java/com/android/tools/r8/synthesis/CommittedItems.java
+++ b/src/main/java/com/android/tools/r8/synthesis/CommittedItems.java
@@ -60,6 +60,11 @@
     return committedTypes;
   }
 
+  @Deprecated
+  public Collection<DexType> getLegacySyntheticTypes() {
+    return legacySyntheticTypes;
+  }
+
   @Override
   public DexClass definitionFor(DexType type, Function<DexType, DexClass> baseDefinitionFor) {
     // All synthetic types are committed to the application so lookup is just the base lookup.
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 fe3388b..985090a 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -256,6 +256,8 @@
   public boolean enableInliningOfInvokesWithClassInitializationSideEffects = true;
   public boolean enableInliningOfInvokesWithNullableReceivers = true;
   public boolean disableInliningOfLibraryMethodOverrides = true;
+  public boolean enableSimpleInliningConstraints = true;
+  public int simpleInliningConstraintThreshold = 0;
   public boolean enableClassInlining = true;
   public boolean enableClassStaticizer = true;
   public boolean enableInitializedClassesAnalysis = true;
@@ -1362,6 +1364,10 @@
 
   public static class TestingOptions {
 
+    public static void enableExperimentalMissingClassesReporting(InternalOptions options) {
+      options.testing.enableExperimentalMissingClassesReporting = true;
+    }
+
     public static int NO_LIMIT = -1;
 
     // Force writing the specified bytes as the DEX version content.
@@ -1431,6 +1437,7 @@
     public boolean alwaysUsePessimisticRegisterAllocation = false;
     public boolean enableCheckCastAndInstanceOfRemoval = true;
     public boolean enableDeadSwitchCaseElimination = true;
+    public boolean enableExperimentalMissingClassesReporting = false;
     public boolean enableInvokeSuperToInvokeVirtualRewriting = true;
     public boolean enableSwitchToIfRewriting = true;
     public boolean enableEnumUnboxingDebugLogs = false;
diff --git a/src/main/java/com/android/tools/r8/utils/IterableUtils.java b/src/main/java/com/android/tools/r8/utils/IterableUtils.java
index 871a7a5..ebbef95 100644
--- a/src/main/java/com/android/tools/r8/utils/IterableUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/IterableUtils.java
@@ -29,6 +29,15 @@
     return Collections.unmodifiableList(list);
   }
 
+  public static <T> T findOrDefault(Iterable<T> iterable, Predicate<T> predicate, T defaultValue) {
+    for (T element : iterable) {
+      if (predicate.test(element)) {
+        return element;
+      }
+    }
+    return defaultValue;
+  }
+
   public static <T> int firstIndexMatching(Iterable<T> iterable, Predicate<T> tester) {
     int i = 0;
     for (T element : iterable) {
diff --git a/src/main/java/com/android/tools/r8/utils/ListUtils.java b/src/main/java/com/android/tools/r8/utils/ListUtils.java
index d3c376f..40ae9ce 100644
--- a/src/main/java/com/android/tools/r8/utils/ListUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/ListUtils.java
@@ -48,6 +48,34 @@
     return result;
   }
 
+  /**
+   * Rewrites the input list based on the given function. Returns the mapped list if any elements
+   * were rewritten, otherwise returns the original list.
+   */
+  public static <T> List<T> mapOrElse(List<T> list, Function<T, T> fn, List<T> defaultValue) {
+    ArrayList<T> result = null;
+    for (int i = 0; i < list.size(); i++) {
+      T oldElement = list.get(i);
+      T newElement = fn.apply(oldElement);
+      if (newElement == oldElement) {
+        if (result != null) {
+          result.add(oldElement);
+        }
+      } else {
+        if (result == null) {
+          result = new ArrayList<>(list.size());
+          for (int j = 0; j < i; j++) {
+            result.add(list.get(j));
+          }
+        }
+        if (newElement != null) {
+          result.add(newElement);
+        }
+      }
+    }
+    return result != null ? result : defaultValue;
+  }
+
   public static <T> Optional<T> removeFirstMatch(List<T> list, Predicate<T> element) {
     int index = firstIndexMatching(list, element);
     if (index >= 0) {
diff --git a/src/main/java/com/android/tools/r8/utils/SetUtils.java b/src/main/java/com/android/tools/r8/utils/SetUtils.java
index 89880cc..c67225c 100644
--- a/src/main/java/com/android/tools/r8/utils/SetUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/SetUtils.java
@@ -18,6 +18,12 @@
     return result;
   }
 
+  public static <T> Set<T> newIdentityHashSet(T[] elements) {
+    Set<T> result = Sets.newIdentityHashSet();
+    Collections.addAll(result, elements);
+    return result;
+  }
+
   public static <T> Set<T> newIdentityHashSet(Iterable<T> c) {
     Set<T> result = Sets.newIdentityHashSet();
     c.forEach(result::add);
diff --git a/src/test/java/com/android/tools/r8/CompileWithJdkClassFileProviderTest.java b/src/test/java/com/android/tools/r8/CompileWithJdkClassFileProviderTest.java
index cbc5c01..d8e0196 100644
--- a/src/test/java/com/android/tools/r8/CompileWithJdkClassFileProviderTest.java
+++ b/src/test/java/com/android/tools/r8/CompileWithJdkClassFileProviderTest.java
@@ -89,15 +89,11 @@
       try {
         // java.util.concurrent.Flow$Subscriber is not present in JDK8 rt.jar.
         testBuilder.compileWithExpectedDiagnostics(
-            diagnotics -> {
-              diagnotics.assertErrorsCount(1);
-              diagnotics.assertWarningsCount(1);
-              diagnotics.assertInfosCount(0);
+            diagnostics -> {
+              diagnostics.assertOnlyErrors();
+              diagnostics.assertErrorsCount(1);
               assertThat(
-                  diagnotics.getErrors().get(0).getDiagnosticMessage(),
-                  StringContains.containsString("java.util.concurrent.Flow$Subscriber"));
-              assertThat(
-                  diagnotics.getWarnings().get(0).getDiagnosticMessage(),
+                  diagnostics.getErrors().get(0).getDiagnosticMessage(),
                   StringContains.containsString("java.util.concurrent.Flow$Subscriber"));
             });
       } catch (CompilationFailedException e) {
diff --git a/src/test/java/com/android/tools/r8/NeverInline.java b/src/test/java/com/android/tools/r8/NeverInline.java
index ca0c2a4..cfbb09f 100644
--- a/src/test/java/com/android/tools/r8/NeverInline.java
+++ b/src/test/java/com/android/tools/r8/NeverInline.java
@@ -4,7 +4,10 @@
 package com.android.tools.r8;
 
 import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 import java.lang.annotation.Target;
 
+@Retention(RetentionPolicy.CLASS)
 @Target({ElementType.CONSTRUCTOR, ElementType.METHOD})
 public @interface NeverInline {}
diff --git a/src/test/java/com/android/tools/r8/NeverSingleCallerInline.java b/src/test/java/com/android/tools/r8/NeverSingleCallerInline.java
new file mode 100644
index 0000000..8c710fb
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/NeverSingleCallerInline.java
@@ -0,0 +1,13 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+@Retention(RetentionPolicy.CLASS)
+@Target({ElementType.CONSTRUCTOR, ElementType.METHOD})
+public @interface NeverSingleCallerInline {}
diff --git a/src/test/java/com/android/tools/r8/R8TestBuilder.java b/src/test/java/com/android/tools/r8/R8TestBuilder.java
index 34d573a..e831d72 100644
--- a/src/test/java/com/android/tools/r8/R8TestBuilder.java
+++ b/src/test/java/com/android/tools/r8/R8TestBuilder.java
@@ -57,21 +57,6 @@
 
   private AllowedDiagnosticMessages allowedDiagnosticMessages = AllowedDiagnosticMessages.NONE;
   private boolean allowUnusedProguardConfigurationRules = false;
-  private boolean enableAssumeNoSideEffectsAnnotations = false;
-  private boolean enableConstantArgumentAnnotations = false;
-  private boolean enableInliningAnnotations = false;
-  private boolean enableMemberValuePropagationAnnotations = false;
-  private boolean enableNoUnusedInterfaceRemovalAnnotations = false;
-  private boolean enableNoVerticalClassMergingAnnotations = false;
-  private boolean enableNoHorizontalClassMergingAnnotations = false;
-  private boolean enableNoStaticClassMergingAnnotations = false;
-  private boolean enableNeverClassInliningAnnotations = false;
-  private boolean enableNeverReprocessClassInitializerAnnotations = false;
-  private boolean enableNeverReprocessMethodAnnotations = false;
-  private boolean enableReprocessClassInitializerAnnotations = false;
-  private boolean enableReprocessMethodAnnotations = false;
-  private boolean enableSideEffectAnnotations = false;
-  private boolean enableUnusedArgumentAnnotations = false;
   private CollectingGraphConsumer graphConsumer = null;
   private List<String> keepRules = new ArrayList<>();
   private List<Path> mainDexRulesFiles = new ArrayList<>();
@@ -82,22 +67,6 @@
   R8TestCompileResult internalCompile(
       Builder builder, Consumer<InternalOptions> optionsConsumer, Supplier<AndroidApp> app)
       throws CompilationFailedException {
-    if (enableConstantArgumentAnnotations
-        || enableInliningAnnotations
-        || enableMemberValuePropagationAnnotations
-        || enableNoUnusedInterfaceRemovalAnnotations
-        || enableNoVerticalClassMergingAnnotations
-        || enableNoHorizontalClassMergingAnnotations
-        || enableNoStaticClassMergingAnnotations
-        || enableNeverClassInliningAnnotations
-        || enableNeverReprocessClassInitializerAnnotations
-        || enableNeverReprocessMethodAnnotations
-        || enableReprocessClassInitializerAnnotations
-        || enableReprocessMethodAnnotations
-        || enableSideEffectAnnotations
-        || enableUnusedArgumentAnnotations) {
-      ToolHelper.allowTestProguardOptions(builder);
-    }
     if (!keepRules.isEmpty()) {
       builder.addProguardConfiguration(keepRules, Origin.unknown());
     }
@@ -357,75 +326,62 @@
   }
 
   public T enableAlwaysInliningAnnotations() {
-    return enableAlwaysInliningAnnotations(AlwaysInline.class.getPackage().getName());
+    return addAlwaysInliningAnnotations()
+        .enableAlwaysInliningAnnotations(AlwaysInline.class.getPackage().getName());
   }
 
   public T enableAlwaysInliningAnnotations(String annotationPackageName) {
-    if (!enableInliningAnnotations) {
-      enableInliningAnnotations = true;
-      addInternalKeepRules(
-          "-alwaysinline class * { @" + annotationPackageName + ".AlwaysInline *; }");
-    }
-    return self();
+    return addInternalKeepRules(
+        "-alwaysinline class * { @" + annotationPackageName + ".AlwaysInline *; }");
   }
 
   public T enableAssumeNoSideEffectsAnnotations() {
-    return enableAssumeNoSideEffectsAnnotations(AssumeNoSideEffects.class.getPackage().getName());
+    return addAssumeNoSideEffectsAnnotations()
+        .enableAssumeNoSideEffectsAnnotations(AssumeNoSideEffects.class.getPackage().getName());
   }
 
   public T enableAssumeNoSideEffectsAnnotations(String annotationPackageName) {
-    if (!enableAssumeNoSideEffectsAnnotations) {
-      enableAssumeNoSideEffectsAnnotations = true;
-      addInternalKeepRules(
-          "-assumenosideeffects class * { @"
-              + annotationPackageName
-              + ".AssumeNoSideEffects <methods>; }");
-    }
-    return self();
+    return addInternalKeepRules(
+        "-assumenosideeffects class * { @"
+            + annotationPackageName
+            + ".AssumeNoSideEffects <methods>; }");
   }
 
   public T enableInliningAnnotations() {
-    return enableInliningAnnotations(NeverInline.class.getPackage().getName());
+    return addInliningAnnotations()
+        .enableInliningAnnotations(NeverInline.class.getPackage().getName());
   }
 
   public T enableInliningAnnotations(String annotationPackageName) {
-    if (!enableInliningAnnotations) {
-      enableInliningAnnotations = true;
-      addInternalKeepRules(
-          "-neverinline class * { @" + annotationPackageName + ".NeverInline *; }");
-    }
-    return self();
+    return addInternalKeepRules(
+        "-neverinline class * { @" + annotationPackageName + ".NeverInline *; }");
   }
 
   public T enableForceInliningAnnotations() {
-    return enableForceInliningAnnotations(ForceInline.class.getPackage().getName());
+    return addForceInliningAnnotations()
+        .enableForceInliningAnnotations(ForceInline.class.getPackage().getName());
   }
 
   public T enableForceInliningAnnotations(String annotationPackageName) {
-    if (!enableInliningAnnotations) {
-      enableInliningAnnotations = true;
-      addInternalKeepRules(
-          "-forceinline class * { @" + annotationPackageName + ".ForceInline *; }");
-    }
-    return self();
+    return addInternalKeepRules(
+        "-forceinline class * { @" + annotationPackageName + ".ForceInline *; }");
+  }
+
+  public T enableNeverSingleCallerInlineAnnotations() {
+    return addNeverSingleCallerInlineAnnotations()
+        .addInternalKeepRules(
+            "-neversinglecallerinline class * {",
+            "  @com.android.tools.r8.NeverSingleCallerInline <methods>;",
+            "}");
   }
 
   public T enableNeverClassInliningAnnotations() {
-    if (!enableNeverClassInliningAnnotations) {
-      enableNeverClassInliningAnnotations = true;
-      addInternalKeepRules("-neverclassinline @com.android.tools.r8.NeverClassInline class *");
-    }
-    return self();
+    return addNeverClassInliningAnnotations()
+        .addInternalKeepRules("-neverclassinline @com.android.tools.r8.NeverClassInline class *");
   }
 
-  private void addInternalMatchInterfaceRule(String name, Class matchInterface) {
-    StringBuilder sb = new StringBuilder();
-    sb.append("-");
-    sb.append(name);
-    sb.append(" @");
-    sb.append(matchInterface.getTypeName());
-    sb.append(" class *");
-    addInternalKeepRules(sb.toString());
+  T addInternalMatchInterfaceRule(String name, Class<?> matchInterface) {
+    return addInternalKeepRules("-" + name + " @" + matchInterface.getTypeName() + " class *");
   }
 
   public T noClassInlining() {
@@ -467,42 +423,31 @@
   }
 
   public T enableNoUnusedInterfaceRemovalAnnotations() {
-    if (!enableNoUnusedInterfaceRemovalAnnotations) {
-      enableNoUnusedInterfaceRemovalAnnotations = true;
-      addInternalMatchInterfaceRule(
-          NoUnusedInterfaceRemovalRule.RULE_NAME, NoUnusedInterfaceRemoval.class);
-    }
-    return self();
+    return addNoUnusedInterfaceRemovalAnnotations()
+        .addInternalMatchInterfaceRule(
+            NoUnusedInterfaceRemovalRule.RULE_NAME, NoUnusedInterfaceRemoval.class);
   }
 
   public T enableNoVerticalClassMergingAnnotations() {
-    if (!enableNoVerticalClassMergingAnnotations) {
-      enableNoVerticalClassMergingAnnotations = true;
-      addInternalMatchInterfaceRule(
-          NoVerticalClassMergingRule.RULE_NAME, NoVerticalClassMerging.class);
-    }
-    return self();
-  }
-
-  public T addNoHorizontalClassMergingRule(String clazz) {
-    return addKeepRules("-nohorizontalclassmerging class " + clazz);
+    return addNoVerticalClassMergingAnnotations()
+        .addInternalMatchInterfaceRule(
+            NoVerticalClassMergingRule.RULE_NAME, NoVerticalClassMerging.class);
   }
 
   public T enableNoHorizontalClassMergingAnnotations() {
-    if (!enableNoHorizontalClassMergingAnnotations) {
-      enableNoHorizontalClassMergingAnnotations = true;
-      addInternalMatchInterfaceRule(
-          NoHorizontalClassMergingRule.RULE_NAME, NoHorizontalClassMerging.class);
-    }
-    return self();
+    return addProgramClasses(NoHorizontalClassMerging.class)
+        .addInternalMatchInterfaceRule(
+            NoHorizontalClassMergingRule.RULE_NAME, NoHorizontalClassMerging.class);
+  }
+
+  public T addNoHorizontalClassMergingRule(String clazz) {
+    return addInternalKeepRules("-nohorizontalclassmerging class " + clazz);
   }
 
   public T enableNoStaticClassMergingAnnotations() {
-    if (!enableNoStaticClassMergingAnnotations) {
-      enableNoStaticClassMergingAnnotations = true;
-      addInternalMatchInterfaceRule(NoStaticClassMergingRule.RULE_NAME, NoStaticClassMerging.class);
-    }
-    return self();
+    return addNoStaticClassMergingAnnotations()
+        .addInternalMatchInterfaceRule(
+            NoStaticClassMergingRule.RULE_NAME, NoStaticClassMerging.class);
   }
 
   public T enableMemberValuePropagationAnnotations() {
@@ -511,54 +456,40 @@
 
   public T enableMemberValuePropagationAnnotations(boolean enable) {
     if (enable) {
-      if (!enableMemberValuePropagationAnnotations) {
-        enableMemberValuePropagationAnnotations = true;
-        addInternalKeepRules(
-            "-neverpropagatevalue class * { @com.android.tools.r8.NeverPropagateValue *; }");
-      }
-    } else {
-      assert !enableMemberValuePropagationAnnotations;
+      return addMemberValuePropagationAnnotations()
+          .addInternalKeepRules(
+              "-neverpropagatevalue class * { @com.android.tools.r8.NeverPropagateValue *; }");
     }
     return self();
   }
 
   public T enableReprocessClassInitializerAnnotations() {
-    if (!enableReprocessClassInitializerAnnotations) {
-      enableReprocessClassInitializerAnnotations = true;
-      addInternalKeepRules(
-          "-reprocessclassinitializer @com.android.tools.r8.ReprocessClassInitializer class *");
-    }
-    return self();
+    return addReprocessClassInitializerAnnotations()
+        .addInternalKeepRules(
+            "-reprocessclassinitializer @com.android.tools.r8.ReprocessClassInitializer class *");
   }
 
   public T enableNeverReprocessClassInitializerAnnotations() {
-    if (!enableNeverReprocessClassInitializerAnnotations) {
-      enableNeverReprocessClassInitializerAnnotations = true;
-      addInternalKeepRules(
-          "-neverreprocessclassinitializer @com.android.tools.r8.NeverReprocessClassInitializer"
-              + " class *");
-    }
-    return self();
+    return addNeverReprocessClassInitializerAnnotations()
+        .addInternalKeepRules(
+            "-neverreprocessclassinitializer @com.android.tools.r8.NeverReprocessClassInitializer"
+                + " class *");
   }
 
   public T enableReprocessMethodAnnotations() {
-    if (!enableReprocessMethodAnnotations) {
-      enableReprocessMethodAnnotations = true;
-      addInternalKeepRules(
-          "-reprocessmethod class * {", "  @com.android.tools.r8.ReprocessMethod <methods>;", "}");
-    }
-    return self();
+    return addReprocessMethodAnnotations()
+        .addInternalKeepRules(
+            "-reprocessmethod class * {",
+            "  @com.android.tools.r8.ReprocessMethod <methods>;",
+            "}");
   }
 
   public T enableNeverReprocessMethodAnnotations() {
-    if (!enableNeverReprocessMethodAnnotations) {
-      enableNeverReprocessMethodAnnotations = true;
-      addInternalKeepRules(
-          "-neverreprocessmethod class * {",
-          "  @com.android.tools.r8.NeverReprocessMethod <methods>;",
-          "}");
-    }
-    return self();
+    return addNeverReprocessMethodAnnotations()
+        .addInternalKeepRules(
+            "-neverreprocessmethod class * {",
+            "  @com.android.tools.r8.NeverReprocessMethod <methods>;",
+            "}");
   }
 
   public T enableProtoShrinking() {
@@ -574,22 +505,15 @@
   }
 
   public T enableSideEffectAnnotations() {
-    if (!enableSideEffectAnnotations) {
-      enableSideEffectAnnotations = true;
-      addInternalKeepRules(
-          "-assumemayhavesideeffects class * {",
-          "  @com.android.tools.r8.AssumeMayHaveSideEffects <methods>;",
-          "}");
-    }
-    return self();
+    return addSideEffectAnnotations()
+        .addInternalKeepRules(
+            "-assumemayhavesideeffects class * {",
+            "  @com.android.tools.r8.AssumeMayHaveSideEffects <methods>;",
+            "}");
   }
 
   public T assumeAllMethodsMayHaveSideEffects() {
-    if (!enableSideEffectAnnotations) {
-      enableSideEffectAnnotations = true;
-      addInternalKeepRules("-assumemayhavesideeffects class * { <methods>; }");
-    }
-    return self();
+    return addInternalKeepRules("-assumemayhavesideeffects class * { <methods>; }");
   }
 
   public T enableConstantArgumentAnnotations() {
@@ -598,13 +522,9 @@
 
   public T enableConstantArgumentAnnotations(boolean value) {
     if (value) {
-      if (!enableConstantArgumentAnnotations) {
-        enableConstantArgumentAnnotations = true;
-        addInternalKeepRules(
-            "-keepconstantarguments class * { @com.android.tools.r8.KeepConstantArguments *; }");
-      }
-    } else {
-      assert !enableConstantArgumentAnnotations;
+      return addConstantArgumentAnnotations()
+          .addInternalKeepRules(
+              "-keepconstantarguments class * { @com.android.tools.r8.KeepConstantArguments *; }");
     }
     return self();
   }
@@ -615,13 +535,9 @@
 
   public T enableUnusedArgumentAnnotations(boolean value) {
     if (value) {
-      if (!enableUnusedArgumentAnnotations) {
-        enableUnusedArgumentAnnotations = true;
-        addInternalKeepRules(
-            "-keepunusedarguments class * { @com.android.tools.r8.KeepUnusedArguments *; }");
-      }
-    } else {
-      assert !enableUnusedArgumentAnnotations;
+      return addUnusedArgumentAnnotations()
+          .addInternalKeepRules(
+              "-keepunusedarguments class * { @com.android.tools.r8.KeepUnusedArguments *; }");
     }
     return self();
   }
@@ -659,9 +575,10 @@
     return self();
   }
 
-  private void addInternalKeepRules(String... rules) {
+  T addInternalKeepRules(String... rules) {
     // We don't add these to the keep-rule set for other test provided rules.
     builder.addProguardConfiguration(Arrays.asList(rules), Origin.unknown());
+    return enableProguardTestOptions();
   }
 
   @Override
diff --git a/src/test/java/com/android/tools/r8/TestShrinkerBuilder.java b/src/test/java/com/android/tools/r8/TestShrinkerBuilder.java
index 6f6c949..1bcbc55 100644
--- a/src/test/java/com/android/tools/r8/TestShrinkerBuilder.java
+++ b/src/test/java/com/android/tools/r8/TestShrinkerBuilder.java
@@ -12,11 +12,14 @@
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.FileUtils;
 import com.android.tools.r8.utils.StringUtils;
+import com.google.common.collect.Sets;
 import java.io.IOException;
+import java.lang.annotation.Annotation;
 import java.nio.file.Path;
 import java.util.Arrays;
 import java.util.Collection;
 import java.util.List;
+import java.util.Set;
 
 public abstract class TestShrinkerBuilder<
         C extends BaseCompilerCommand,
@@ -30,6 +33,9 @@
   protected boolean enableOptimization = true;
   protected boolean enableMinification = true;
 
+  private final Set<Class<? extends Annotation>> addedTestingAnnotations =
+      Sets.newIdentityHashSet();
+
   TestShrinkerBuilder(TestState state, B builder, Backend backend) {
     super(state, builder, backend);
   }
@@ -301,6 +307,82 @@
 
   public abstract T addApplyMapping(String proguardMap);
 
+  public final T addAlwaysInliningAnnotations() {
+    return addTestingAnnotation(AlwaysInline.class);
+  }
+
+  public final T addAssumeNoSideEffectsAnnotations() {
+    return addTestingAnnotation(AssumeNoSideEffects.class);
+  }
+
+  public final T addConstantArgumentAnnotations() {
+    return addTestingAnnotation(KeepConstantArguments.class);
+  }
+
+  public final T addForceInliningAnnotations() {
+    return addTestingAnnotation(ForceInline.class);
+  }
+
+  public final T addInliningAnnotations() {
+    return addTestingAnnotation(NeverInline.class);
+  }
+
+  public final T addMemberValuePropagationAnnotations() {
+    return addTestingAnnotation(NeverPropagateValue.class);
+  }
+
+  public final T addNeverClassInliningAnnotations() {
+    return addTestingAnnotation(NeverClassInline.class);
+  }
+
+  public final T addNeverReprocessClassInitializerAnnotations() {
+    return addTestingAnnotation(NeverReprocessClassInitializer.class);
+  }
+
+  public final T addNeverReprocessMethodAnnotations() {
+    return addTestingAnnotation(NeverReprocessMethod.class);
+  }
+
+  public final T addNeverSingleCallerInlineAnnotations() {
+    return addTestingAnnotation(NeverSingleCallerInline.class);
+  }
+
+  public final T addNoHorizontalClassMergingAnnotations() {
+    return addTestingAnnotation(NoHorizontalClassMerging.class);
+  }
+
+  public final T addNoStaticClassMergingAnnotations() {
+    return addTestingAnnotation(NoStaticClassMerging.class);
+  }
+
+  public final T addNoUnusedInterfaceRemovalAnnotations() {
+    return addTestingAnnotation(NoUnusedInterfaceRemoval.class);
+  }
+
+  public final T addNoVerticalClassMergingAnnotations() {
+    return addTestingAnnotation(NoVerticalClassMerging.class);
+  }
+
+  public final T addReprocessClassInitializerAnnotations() {
+    return addTestingAnnotation(ReprocessClassInitializer.class);
+  }
+
+  public final T addReprocessMethodAnnotations() {
+    return addTestingAnnotation(ReprocessMethod.class);
+  }
+
+  public final T addSideEffectAnnotations() {
+    return addTestingAnnotation(AssumeMayHaveSideEffects.class);
+  }
+
+  public final T addUnusedArgumentAnnotations() {
+    return addTestingAnnotation(KeepUnusedArguments.class);
+  }
+
+  private T addTestingAnnotation(Class<? extends Annotation> clazz) {
+    return addedTestingAnnotations.add(clazz) ? addProgramClasses(clazz) : self();
+  }
+
   private static String getMethodLine(MethodReference method) {
     // Should we encode modifiers in method references?
     StringBuilder builder = new StringBuilder();
diff --git a/src/test/java/com/android/tools/r8/accessrelaxation/NoRelaxationForSerializableTest.java b/src/test/java/com/android/tools/r8/accessrelaxation/NoRelaxationForSerializableTest.java
index 7521316..83a0155 100644
--- a/src/test/java/com/android/tools/r8/accessrelaxation/NoRelaxationForSerializableTest.java
+++ b/src/test/java/com/android/tools/r8/accessrelaxation/NoRelaxationForSerializableTest.java
@@ -125,9 +125,11 @@
     assumeTrue(parameters.isCfRuntime());
     testForProguard()
         .addProgramClasses(CLASSES)
-        .addTestingAnnotationsAsProgramClasses()
         .addKeepRuleFiles(configuration)
         .addKeepRules(KEEPMEMBER_RULES)
+        .addInliningAnnotations()
+        .addMemberValuePropagationAnnotations()
+        .addNoVerticalClassMergingAnnotations()
         .compile()
         .run(parameters.getRuntime(), MAIN)
         .assertSuccessWithOutput(EXPECTED_OUTPUT)
@@ -158,7 +160,9 @@
     assumeTrue(parameters.isCfRuntime());
     testForProguard()
         .addProgramClasses(CLASSES)
-        .addTestingAnnotationsAsProgramClasses()
+        .addInliningAnnotations()
+        .addMemberValuePropagationAnnotations()
+        .addNoVerticalClassMergingAnnotations()
         .addKeepRuleFiles(configuration)
         .compile()
         .run(parameters.getRuntime(), MAIN)
diff --git a/src/test/java/com/android/tools/r8/cfmethodgeneration/MethodGenerationBase.java b/src/test/java/com/android/tools/r8/cfmethodgeneration/MethodGenerationBase.java
index c43c289..689e080 100644
--- a/src/test/java/com/android/tools/r8/cfmethodgeneration/MethodGenerationBase.java
+++ b/src/test/java/com/android/tools/r8/cfmethodgeneration/MethodGenerationBase.java
@@ -25,7 +25,6 @@
 import java.nio.file.Files;
 import java.nio.file.Path;
 import java.nio.file.Paths;
-import java.util.Calendar;
 import java.util.List;
 import java.util.TreeSet;
 
@@ -46,11 +45,12 @@
 
   protected abstract List<Class<?>> getMethodTemplateClasses();
 
-  public static String getHeaderString(Class<?> generationClass, String generatedPackage) {
-    int year = Calendar.getInstance().get(Calendar.YEAR);
-    String simpleName = generationClass.getSimpleName();
+  protected abstract int getYear();
+
+  public String getHeaderString() {
+    String simpleName = getClass().getSimpleName();
     return StringUtils.lines(
-        "// Copyright (c) " + year + ", the R8 project authors. Please see the AUTHORS file",
+        "// Copyright (c) " + getYear() + ", 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.",
         "",
@@ -58,7 +58,7 @@
         "// GENERATED FILE. DO NOT EDIT! See " + simpleName + ".java.",
         "// ***********************************************************************************",
         "",
-        "package " + generatedPackage + ";");
+        "package " + getGeneratedClassPackageName() + ";");
   }
 
   protected Path getGeneratedFile() {
@@ -115,7 +115,7 @@
 
   private void generateRawOutput(CfCodePrinter codePrinter, Path tempFile) throws IOException {
     try (PrintStream printer = new PrintStream(Files.newOutputStream(tempFile))) {
-      printer.print(getHeaderString(this.getClass(), getGeneratedClassPackageName()));
+      printer.print(getHeaderString());
       printer.println("import com.android.tools.r8.graph.DexItemFactory;");
       codePrinter.getImports().forEach(i -> printer.println("import " + i + ";"));
       printer.println("public final class " + getGeneratedClassName() + " {\n");
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/ClassesWithDifferentFieldsTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/ClassesWithDifferentFieldsTest.java
new file mode 100644
index 0000000..d322729
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/ClassesWithDifferentFieldsTest.java
@@ -0,0 +1,82 @@
+/*
+ *  // Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+ *  // for details. All rights reserved. Use of this source code is governed by a
+ *  // BSD-style license that can be found in the LICENSE file.
+ */
+
+package com.android.tools.r8.classmerging.horizontal;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static com.android.tools.r8.utils.codeinspector.Matchers.notIf;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.TestParameters;
+import org.junit.Test;
+
+public class ClassesWithDifferentFieldsTest extends HorizontalClassMergingTestBase {
+  public ClassesWithDifferentFieldsTest(
+      TestParameters parameters, boolean enableHorizontalClassMerging) {
+    super(parameters, enableHorizontalClassMerging);
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(getClass())
+        .addKeepMainRule(Main.class)
+        .addOptionsModification(
+            options ->
+                options.horizontalClassMergerOptions().enableIf(enableHorizontalClassMerging))
+        .enableNeverClassInliningAnnotations()
+        .enableInliningAnnotations()
+        .setMinApi(parameters.getApiLevel())
+        .addHorizontallyMergedClassesInspectorIf(
+            enableHorizontalClassMerging, inspector -> inspector.assertMergedInto(B.class, A.class))
+        .compile()
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines("A. v: a", "B. i: 2")
+        .inspect(
+            codeInspector -> {
+              assertThat(codeInspector.clazz(A.class), isPresent());
+              assertThat(
+                  codeInspector.clazz(B.class), notIf(isPresent(), enableHorizontalClassMerging));
+            });
+  }
+
+  @NeverClassInline
+  public static class A {
+    public String v;
+
+    public A(String v) {
+      this.v = v;
+    }
+
+    @NeverInline
+    public void foo() {
+      System.out.println("A. v: " + v);
+    }
+  }
+
+  @NeverClassInline
+  public static class B {
+    public Integer i;
+
+    public B(Integer i) {
+      this.i = i;
+    }
+
+    @NeverInline
+    public void foo() {
+      System.out.println("B. i: " + i);
+    }
+  }
+
+  public static class Main {
+    public static void main(String[] args) {
+      new A("a").foo();
+      new B(2).foo();
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/FieldsWithDifferentAccessFlagsTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/FieldsWithDifferentAccessFlagsTest.java
new file mode 100644
index 0000000..58acdbf
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/FieldsWithDifferentAccessFlagsTest.java
@@ -0,0 +1,89 @@
+/*
+ *  // Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+ *  // for details. All rights reserved. Use of this source code is governed by a
+ *  // BSD-style license that can be found in the LICENSE file.
+ */
+
+package com.android.tools.r8.classmerging.horizontal;
+
+import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.utils.codeinspector.HorizontallyMergedClassesInspector;
+import org.junit.Test;
+
+public class FieldsWithDifferentAccessFlagsTest extends HorizontalClassMergingTestBase {
+
+  public FieldsWithDifferentAccessFlagsTest(
+      TestParameters parameters, boolean enableHorizontalClassMerging) {
+    super(parameters, enableHorizontalClassMerging);
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(getClass())
+        .addKeepMainRule(Main.class)
+        .addOptionsModification(
+            options ->
+                options.horizontalClassMergerOptions().enableIf(enableHorizontalClassMerging))
+        .addHorizontallyMergedClassesInspectorIf(
+            enableHorizontalClassMerging, HorizontallyMergedClassesInspector::assertNoClassesMerged)
+        .enableInliningAnnotations()
+        .enableNeverClassInliningAnnotations()
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines("A", "B", "C");
+  }
+
+  @NeverClassInline
+  public static class A {
+    public volatile String msg;
+
+    public A(String msg) {
+      this.msg = msg;
+    }
+
+    @NeverInline
+    public void foo() {
+      System.out.println(msg);
+    }
+  }
+
+  @NeverClassInline
+  public static class B {
+    public transient String msg;
+
+    public B(String msg) {
+      this.msg = msg;
+    }
+
+    @NeverInline
+    public void foo() {
+      System.out.println(msg);
+    }
+  }
+
+  @NeverClassInline
+  public static class C {
+    public String msg;
+
+    public C(String msg) {
+      this.msg = msg;
+    }
+
+    @NeverInline
+    public void foo() {
+      System.out.println(msg);
+    }
+  }
+
+  public static class Main {
+    public static void main(String[] args) {
+      new A(System.currentTimeMillis() > 0 ? "A" : null).foo();
+      new B(System.currentTimeMillis() > 0 ? "B" : null).foo();
+      new C(System.currentTimeMillis() > 0 ? "C" : null).foo();
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/MinimizeFieldCastsTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/MinimizeFieldCastsTest.java
new file mode 100644
index 0000000..90ae326
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/MinimizeFieldCastsTest.java
@@ -0,0 +1,121 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.classmerging.horizontal;
+
+import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NoHorizontalClassMerging;
+import com.android.tools.r8.TestParameters;
+import org.junit.Test;
+
+public class MinimizeFieldCastsTest extends HorizontalClassMergingTestBase {
+
+  public MinimizeFieldCastsTest(TestParameters parameters, boolean enableHorizontalClassMerging) {
+    super(parameters, enableHorizontalClassMerging);
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(getClass())
+        .addKeepMainRule(Main.class)
+        .addOptionsModification(
+            options ->
+                options.horizontalClassMergerOptions().enableIf(enableHorizontalClassMerging))
+        .enableInliningAnnotations()
+        .enableNeverClassInliningAnnotations()
+        .enableNoHorizontalClassMergingAnnotations()
+        .setMinApi(parameters.getApiLevel())
+        .addHorizontallyMergedClassesInspectorIf(
+            enableHorizontalClassMerging,
+            inspector ->
+                // Two merge groups are expected since we attempt to merge classes in a way that
+                // avoids merging fields with different types unless strictly required for merging.
+                inspector.assertMergedInto(B.class, A.class).assertMergedInto(D.class, C.class))
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines("Foo", "Foo", "Bar", "Bar");
+  }
+
+  @NeverClassInline
+  public static class A {
+    FooGreeter greeter;
+
+    A(FooGreeter greeter) {
+      this.greeter = greeter;
+    }
+
+    void greet() {
+      greeter.greet();
+    }
+  }
+
+  @NeverClassInline
+  public static class B {
+    FooGreeter greeter;
+
+    B(FooGreeter greeter) {
+      this.greeter = greeter;
+    }
+
+    void greet() {
+      greeter.greet();
+    }
+  }
+
+  @NeverClassInline
+  public static class C {
+    BarGreeter greeter;
+
+    C(BarGreeter greeter) {
+      this.greeter = greeter;
+    }
+
+    void greet() {
+      greeter.greet();
+    }
+  }
+
+  @NeverClassInline
+  public static class D {
+    BarGreeter greeter;
+
+    D(BarGreeter greeter) {
+      this.greeter = greeter;
+    }
+
+    void greet() {
+      greeter.greet();
+    }
+  }
+
+  @NeverClassInline
+  @NoHorizontalClassMerging
+  static class FooGreeter {
+
+    @NeverInline
+    void greet() {
+      System.out.println("Foo");
+    }
+  }
+
+  @NeverClassInline
+  @NoHorizontalClassMerging
+  static class BarGreeter {
+
+    @NeverInline
+    void greet() {
+      System.out.println("Bar");
+    }
+  }
+
+  static class Main {
+    public static void main(String[] args) {
+      new A(new FooGreeter()).greet();
+      new B(new FooGreeter()).greet();
+      new C(new BarGreeter()).greet();
+      new D(new BarGreeter()).greet();
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/NonReboundFieldAccessWithMergedTypeTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/NonReboundFieldAccessWithMergedTypeTest.java
index b6183a3..79aae15 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/NonReboundFieldAccessWithMergedTypeTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/NonReboundFieldAccessWithMergedTypeTest.java
@@ -6,7 +6,6 @@
 
 import com.android.tools.r8.NeverClassInline;
 import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.classmerging.horizontal.NonReboundFieldAccessOnMergedClassTest.C;
 import com.android.tools.r8.classmerging.horizontal.testclasses.NonReboundFieldAccessWithMergedTypeTestClasses;
 import org.junit.Test;
 import org.junit.runner.RunWith;
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/PreventMergeMainDexTracingTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/PreventMergeMainDexTracingTest.java
index 6a31a5e..13d8a62 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/PreventMergeMainDexTracingTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/PreventMergeMainDexTracingTest.java
@@ -13,7 +13,6 @@
 import com.android.tools.r8.OutputMode;
 import com.android.tools.r8.R8TestCompileResult;
 import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.classmerging.horizontal.PreventMergeMainDexListTest.Main;
 import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import java.nio.file.Path;
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/TreeFixerConstructorCollisionTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/TreeFixerConstructorCollisionTest.java
index c7a7d9c..5239543 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/TreeFixerConstructorCollisionTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/TreeFixerConstructorCollisionTest.java
@@ -12,10 +12,6 @@
 import com.android.tools.r8.NeverInline;
 import com.android.tools.r8.NoHorizontalClassMerging;
 import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.classmerging.horizontal.TreeFixerCollisionTest.A;
-import com.android.tools.r8.classmerging.horizontal.TreeFixerCollisionTest.B;
-import com.android.tools.r8.classmerging.horizontal.TreeFixerCollisionTest.C;
-import com.android.tools.r8.classmerging.horizontal.TreeFixerCollisionTest.Main;
 import org.junit.Test;
 
 public class TreeFixerConstructorCollisionTest extends HorizontalClassMergingTestBase {
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/TreeFixerInterfaceCollisionTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/TreeFixerInterfaceCollisionTest.java
index 946e113..6f3c8f8 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/TreeFixerInterfaceCollisionTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/TreeFixerInterfaceCollisionTest.java
@@ -13,8 +13,6 @@
 import com.android.tools.r8.NoHorizontalClassMerging;
 import com.android.tools.r8.NoVerticalClassMerging;
 import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.classmerging.horizontal.TreeFixerInterfaceFixedCollisionTest.B;
-import com.android.tools.r8.classmerging.horizontal.TreeFixerInterfaceImplementedByParentTest.I;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import org.junit.Test;
 
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/TreeFixerInterfaceFixedCollisionTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/TreeFixerInterfaceFixedCollisionTest.java
index 1d3290a..627838f 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/TreeFixerInterfaceFixedCollisionTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/TreeFixerInterfaceFixedCollisionTest.java
@@ -13,10 +13,6 @@
 import com.android.tools.r8.NoHorizontalClassMerging;
 import com.android.tools.r8.NoVerticalClassMerging;
 import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.classmerging.horizontal.TreeFixerInterfaceCollisionTest.C;
-import com.android.tools.r8.classmerging.horizontal.TreeFixerInterfaceImplementedByParentTest.B;
-import com.android.tools.r8.classmerging.horizontal.TreeFixerInterfaceImplementedByParentTest.E;
-import com.android.tools.r8.classmerging.horizontal.TreeFixerInterfaceImplementedByParentTest.I;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import org.junit.Test;
 
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/VerticallyMergedClassTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/VerticallyMergedClassTest.java
index a40b387..abd38f5 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/VerticallyMergedClassTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/VerticallyMergedClassTest.java
@@ -12,7 +12,6 @@
 import com.android.tools.r8.NeverInline;
 import com.android.tools.r8.NoHorizontalClassMerging;
 import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.classmerging.horizontal.ConstructorMergingOverlapTest.Main;
 import org.junit.Test;
 
 public class VerticallyMergedClassTest extends HorizontalClassMergingTestBase {
diff --git a/src/test/java/com/android/tools/r8/compatproguard/CompatKeepClassMemberNamesTestRunner.java b/src/test/java/com/android/tools/r8/compatproguard/CompatKeepClassMemberNamesTestRunner.java
index de0e481..034f226 100644
--- a/src/test/java/com/android/tools/r8/compatproguard/CompatKeepClassMemberNamesTestRunner.java
+++ b/src/test/java/com/android/tools/r8/compatproguard/CompatKeepClassMemberNamesTestRunner.java
@@ -11,7 +11,6 @@
 import static org.junit.Assert.assertTrue;
 
 import com.android.tools.r8.BaseCompilerCommand;
-import com.android.tools.r8.NeverInline;
 import com.android.tools.r8.ProguardVersion;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestCompileResult;
@@ -40,8 +39,7 @@
 
   private static Class<?> MAIN_CLASS = CompatKeepClassMemberNamesTest.class;
   private static Class<?> BAR_CLASS = CompatKeepClassMemberNamesTest.Bar.class;
-  private static Collection<Class<?>> CLASSES =
-      ImmutableList.of(MAIN_CLASS, BAR_CLASS, NeverInline.class);
+  private static Collection<Class<?>> CLASSES = ImmutableList.of(MAIN_CLASS, BAR_CLASS);
 
   private static String KEEP_RULE =
       "class "
@@ -112,7 +110,12 @@
           T extends TestShrinkerBuilder<C, B, CR, RR, T>>
       void testWithoutRules(TestShrinkerBuilder<C, B, CR, RR, T> builder) throws Exception {
     assertBarIsAbsent(
-        builder.addProgramClasses(CLASSES).addKeepMainRule(MAIN_CLASS).noMinification().compile());
+        builder
+            .addProgramClasses(CLASSES)
+            .addKeepMainRule(MAIN_CLASS)
+            .addInliningAnnotations()
+            .noMinification()
+            .compile());
   }
 
   @Test
@@ -207,6 +210,7 @@
         .addProgramClasses(CLASSES)
         .addKeepMainRule(MAIN_CLASS)
         .addKeepRules("-keepclassmembers " + KEEP_RULE_NON_STATIC)
+        .addInliningAnnotations()
         .noMinification();
   }
 
@@ -238,7 +242,8 @@
     return builder
         .addProgramClasses(CLASSES)
         .addKeepMainRule(MAIN_CLASS)
-        .addKeepRules("-keepclassmembers " + KEEP_RULE);
+        .addKeepRules("-keepclassmembers " + KEEP_RULE)
+        .addInliningAnnotations();
   }
 
   private <CR extends TestCompileResult<CR, RR>, RR extends TestRunResult<RR>>
@@ -309,6 +314,7 @@
         builder
             .addProgramClasses(CLASSES)
             .addKeepMainRule(MAIN_CLASS)
+            .addInliningAnnotations()
             .noMinification()
             .addKeepRules("-keepclassmembers class " + Bar.class.getTypeName())
             .compile());
@@ -394,7 +400,8 @@
     return builder
         .addProgramClasses(CLASSES)
         .addKeepMainRule(MAIN_CLASS)
-        .addKeepRules("-keepclassmembernames " + KEEP_RULE);
+        .addKeepRules("-keepclassmembernames " + KEEP_RULE)
+        .addInliningAnnotations();
   }
 
   private <CR extends TestCompileResult<CR, RR>, RR extends TestRunResult<RR>>
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/InstantTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/InstantTest.java
new file mode 100644
index 0000000..7e38a30
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/InstantTest.java
@@ -0,0 +1,75 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.desugar.desugaredlibrary;
+
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.utils.BooleanUtils;
+import java.time.Instant;
+import java.time.ZoneOffset;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class InstantTest extends DesugaredLibraryTestBase {
+
+  private final TestParameters parameters;
+  private final boolean shrinkDesugaredLibrary;
+
+  @Parameters(name = "{1}, shrinkDesugaredLibrary: {0}")
+  public static List<Object[]> data() {
+    return buildParameters(
+        BooleanUtils.values(), getTestParameters().withDexRuntimes().withAllApiLevels().build());
+  }
+
+  public InstantTest(boolean shrinkDesugaredLibrary, TestParameters parameters) {
+    this.shrinkDesugaredLibrary = shrinkDesugaredLibrary;
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testInstantD8() throws Exception {
+    KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters);
+    testForD8()
+        .addInnerClasses(InstantTest.class)
+        .setMinApi(parameters.getApiLevel())
+        .enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer)
+        .compile()
+        .addDesugaredCoreLibraryRunClassPath(
+            this::buildDesugaredLibrary,
+            parameters.getApiLevel(),
+            keepRuleConsumer.get(),
+            shrinkDesugaredLibrary)
+        .run(parameters.getRuntime(), Executor.class)
+        .assertSuccessWithOutputLines("1970-01-02T10:17:36.789Z");
+  }
+
+  @Test
+  public void testInstantR8() throws Exception {
+    KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters);
+    testForR8(parameters.getBackend())
+        .addInnerClasses(InstantTest.class)
+        .setMinApi(parameters.getApiLevel())
+        .addKeepClassAndMembersRules(Executor.class)
+        .enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer)
+        .compile()
+        .addDesugaredCoreLibraryRunClassPath(
+            this::buildDesugaredLibrary,
+            parameters.getApiLevel(),
+            keepRuleConsumer.get(),
+            shrinkDesugaredLibrary)
+        .run(parameters.getRuntime(), Executor.class)
+        .assertSuccessWithOutputLines("1970-01-02T10:17:36.789Z");
+  }
+
+  static class Executor {
+
+    public static void main(String[] args) {
+      System.out.println(Instant.ofEpochMilli(123456789L).atOffset(ZoneOffset.UTC));
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/Java11R8CompilationTest.java b/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/Java11R8CompilationTest.java
index 762916e..6ebe0db 100644
--- a/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/Java11R8CompilationTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/Java11R8CompilationTest.java
@@ -54,7 +54,7 @@
         .addOptionsModification(opt -> opt.ignoreMissingClasses = true)
         .allowDiagnosticWarningMessages()
         .compile()
-        .assertAllWarningMessagesMatch(containsString("Missing class:"))
+        .assertAllWarningMessagesMatch(containsString("Missing class "))
         .inspect(this::assertNotEmpty)
         .inspect(Java11R8CompilationTest::assertNoNests);
   }
diff --git a/src/test/java/com/android/tools/r8/dex/DebugByteCodeWriterTest.java b/src/test/java/com/android/tools/r8/dex/DebugByteCodeWriterTest.java
index 7ea9110..d2a5729 100644
--- a/src/test/java/com/android/tools/r8/dex/DebugByteCodeWriterTest.java
+++ b/src/test/java/com/android/tools/r8/dex/DebugByteCodeWriterTest.java
@@ -64,7 +64,9 @@
   @Test
   public void testEmptyDebugInfo() {
     DexDebugInfo debugInfo = new DexDebugInfo(1, DexString.EMPTY_ARRAY, new DexDebugEvent[]{});
-    DebugBytecodeWriter writer = new DebugBytecodeWriter(debugInfo, emptyObjectTObjectMapping());
+    DebugBytecodeWriter writer =
+        new DebugBytecodeWriter(
+            debugInfo, emptyObjectTObjectMapping(), GraphLens.getIdentityLens());
     Assert.assertEquals(3, writer.generate().length);
   }
 }
diff --git a/src/test/java/com/android/tools/r8/diagnostics/ModifyDiagnosticsLevelTest.java b/src/test/java/com/android/tools/r8/diagnostics/ModifyDiagnosticsLevelTest.java
index 8db3e12..3dabac7 100644
--- a/src/test/java/com/android/tools/r8/diagnostics/ModifyDiagnosticsLevelTest.java
+++ b/src/test/java/com/android/tools/r8/diagnostics/ModifyDiagnosticsLevelTest.java
@@ -12,7 +12,7 @@
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
-import com.android.tools.r8.utils.StringDiagnostic;
+import com.android.tools.r8.shaking.MissingClassesDiagnostic;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
@@ -20,7 +20,7 @@
 @RunWith(Parameterized.class)
 public class ModifyDiagnosticsLevelTest extends TestBase {
 
-  private static final String MISSING_CLASS_MESSAGE_PREFIX = "Missing class: ";
+  private static final String MISSING_CLASS_MESSAGE_PREFIX = "Missing class ";
 
   @Parameterized.Parameters(name = "{0}")
   public static TestParametersCollection data() {
@@ -40,7 +40,7 @@
         .setDiagnosticsLevelModifier(
             (level, diagnostic) -> {
               if (level == DiagnosticsLevel.WARNING
-                  && diagnostic instanceof StringDiagnostic
+                  && diagnostic instanceof MissingClassesDiagnostic
                   && diagnostic.getDiagnosticMessage().startsWith(MISSING_CLASS_MESSAGE_PREFIX)) {
                 return DiagnosticsLevel.INFO;
               }
@@ -57,7 +57,7 @@
   }
 
   @Test
-  public void testWarningToError() throws Exception {
+  public void testWarningToError() {
     try {
       testForR8(Backend.DEX)
           .addProgramClasses(TestClass.class)
@@ -66,7 +66,7 @@
           .setDiagnosticsLevelModifier(
               (level, diagnostic) -> {
                 if (level == DiagnosticsLevel.WARNING
-                    && diagnostic instanceof StringDiagnostic
+                    && diagnostic instanceof MissingClassesDiagnostic
                     && diagnostic.getDiagnosticMessage().startsWith(MISSING_CLASS_MESSAGE_PREFIX)) {
                   return DiagnosticsLevel.ERROR;
                 }
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/GenerateEnumUnboxingMethods.java b/src/test/java/com/android/tools/r8/enumunboxing/GenerateEnumUnboxingMethods.java
index 54a405c..74bf88f 100644
--- a/src/test/java/com/android/tools/r8/enumunboxing/GenerateEnumUnboxingMethods.java
+++ b/src/test/java/com/android/tools/r8/enumunboxing/GenerateEnumUnboxingMethods.java
@@ -50,6 +50,10 @@
     return METHOD_TEMPLATE_CLASSES;
   }
 
+  @Override
+  protected int getYear() {
+    return 2020;
+  }
 
   @Test
   public void testEnumUtilityMethodsGenerated() throws Exception {
diff --git a/src/test/java/com/android/tools/r8/graph/MissingClassThrowingTest.java b/src/test/java/com/android/tools/r8/graph/MissingClassThrowingTest.java
index c73b948..213db97 100644
--- a/src/test/java/com/android/tools/r8/graph/MissingClassThrowingTest.java
+++ b/src/test/java/com/android/tools/r8/graph/MissingClassThrowingTest.java
@@ -4,7 +4,8 @@
 
 package com.android.tools.r8.graph;
 
-import static org.hamcrest.core.StringContains.containsString;
+import static com.android.tools.r8.DiagnosticsMatcher.diagnosticType;
+import static org.junit.Assert.assertEquals;
 
 import com.android.tools.r8.NeverInline;
 import com.android.tools.r8.NoStaticClassMerging;
@@ -12,8 +13,10 @@
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.TestRuntime.CfVm;
+import com.android.tools.r8.shaking.MissingClassesDiagnostic;
+import com.android.tools.r8.utils.InternalOptions.TestingOptions;
+import com.android.tools.r8.utils.codeinspector.AssertUtils;
 import java.io.IOException;
-import org.junit.Ignore;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
@@ -61,21 +64,32 @@
     this.parameters = parameters;
   }
 
-  @Ignore("b/128885552")
   @Test
   public void testSuperTypeOfExceptions() throws Exception {
-    testForR8(parameters.getBackend())
-        .addProgramClasses(Program.class)
-        .noMinification()
-        .noTreeShaking()
-        .enableInliningAnnotations()
-        .enableNoStaticClassMergingAnnotations()
-        .debug()
-        .addKeepRules("-keep class ** { *; }", "-keepattributes *")
-        .compile()
-        .addRunClasspathClasses(MissingException.class)
-        .run(parameters.getRuntime(), Program.class)
-        .assertFailureWithErrorThatMatches(
-            containsString("Missing class: " + MissingException.class.getTypeName()));
+    AssertUtils.assertFailsCompilation(
+        () ->
+            testForR8(parameters.getBackend())
+                .addProgramClasses(Program.class)
+                .addKeepAllClassesRule()
+                .addKeepAllAttributes()
+                .addOptionsModification(TestingOptions::enableExperimentalMissingClassesReporting)
+                .noMinification()
+                .noTreeShaking()
+                .enableInliningAnnotations()
+                .enableNoStaticClassMergingAnnotations()
+                .debug()
+                .compileWithExpectedDiagnostics(
+                    diagnostics -> {
+                      diagnostics
+                          .assertOnlyErrors()
+                          .assertErrorsMatch(diagnosticType(MissingClassesDiagnostic.class));
+
+                      MissingClassesDiagnostic diagnostic =
+                          (MissingClassesDiagnostic) diagnostics.getErrors().get(0);
+                      assertEquals(1, diagnostic.getMissingClasses().size());
+                      assertEquals(
+                          MissingException.class.getTypeName(),
+                          diagnostic.getMissingClasses().iterator().next().getTypeName());
+                    }));
   }
 }
diff --git a/src/test/java/com/android/tools/r8/internal/Iosched2019Test.java b/src/test/java/com/android/tools/r8/internal/Iosched2019Test.java
index 34d0e01..0dbe71a 100644
--- a/src/test/java/com/android/tools/r8/internal/Iosched2019Test.java
+++ b/src/test/java/com/android/tools/r8/internal/Iosched2019Test.java
@@ -61,7 +61,7 @@
                 containsString("Proguard configuration rule does not match anything: ")))
         .assertAllWarningMessagesMatch(
             anyOf(
-                containsString("Missing class: "),
+                containsString("Missing class "),
                 containsString("required for default or static interface methods desugaring"),
                 equalTo("Resource 'META-INF/MANIFEST.MF' already exists.")))
         .assertNoErrorMessages();
diff --git a/src/test/java/com/android/tools/r8/ir/desugar/backports/GenerateBackportMethods.java b/src/test/java/com/android/tools/r8/ir/desugar/backports/GenerateBackportMethods.java
index 05c11c4..d78a1c7 100644
--- a/src/test/java/com/android/tools/r8/ir/desugar/backports/GenerateBackportMethods.java
+++ b/src/test/java/com/android/tools/r8/ir/desugar/backports/GenerateBackportMethods.java
@@ -68,6 +68,11 @@
     return METHOD_TEMPLATE_CLASSES;
   }
 
+  @Override
+  protected int getYear() {
+    return 2020;
+  }
+
   @Test
   public void testBackportsGenerated() throws Exception {
     ArrayList<Class<?>> sorted = new ArrayList<>(getMethodTemplateClasses());
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/NonNullParamTest.java b/src/test/java/com/android/tools/r8/ir/optimize/NonNullParamTest.java
index 964848a..9ba1f75 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/NonNullParamTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/NonNullParamTest.java
@@ -65,7 +65,6 @@
         .addProgramClasses(classes)
         .addKeepMainRule(mainClass)
         .addKeepRules(ImmutableList.of("-keepattributes InnerClasses,Signature,EnclosingMethod"))
-        .addTestingAnnotationsAsProgramClasses()
         // All tests are checking if invocations to certain null-check utils are gone.
         .noMinification()
         .addOptionsModification(
@@ -92,7 +91,7 @@
 
     MethodSubject selfCheck = mainSubject.uniqueMethodWithName("selfCheck");
     assertThat(selfCheck, isPresent());
-    assertEquals(1, countCallToParamNullCheck(selfCheck));
+    assertEquals(0, countCallToParamNullCheck(selfCheck));
     assertEquals(1, countPrintCall(selfCheck));
     assertEquals(0, countThrow(selfCheck));
 
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/EscapingBuilderTest.java b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/EscapingBuilderTest.java
index bf5e92a..50201f3 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/EscapingBuilderTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/EscapingBuilderTest.java
@@ -10,6 +10,7 @@
 import static org.hamcrest.MatcherAssert.assertThat;
 
 import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NoHorizontalClassMerging;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import org.junit.Test;
@@ -23,6 +24,7 @@
         .addInnerClasses(EscapingBuilderTest.class)
         .addKeepMainRule(TestClass.class)
         .enableInliningAnnotations()
+        .enableNoHorizontalClassMergingAnnotations()
         .compile()
         .inspect(this::inspect);
   }
@@ -101,6 +103,7 @@
   }
 
   // Builder that escapes via field `f` that is assigned in a virtual method.
+  @NoHorizontalClassMerging
   static class Builder2 {
 
     public Builder2 f;
@@ -118,6 +121,7 @@
   }
 
   // Builder that escapes via field `f` that is assigned in a virtual method.
+  @NoHorizontalClassMerging
   static class Builder3 {
 
     public Builder3 f;
@@ -135,6 +139,7 @@
   }
 
   // Builder that escapes via field `f` that is assigned in a virtual method.
+  @NoHorizontalClassMerging
   static class Builder4 {
 
     public Builder4 f;
@@ -152,6 +157,7 @@
   }
 
   // Builder that escapes via field `f` that is assigned in a static method.
+  @NoHorizontalClassMerging
   static class Builder5 {
 
     public Builder5 f;
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/inliner/SingleTargetAfterInliningTest.java b/src/test/java/com/android/tools/r8/ir/optimize/inliner/SingleTargetAfterInliningTest.java
index f5c7da8..2012c6f 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/inliner/SingleTargetAfterInliningTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/inliner/SingleTargetAfterInliningTest.java
@@ -54,7 +54,6 @@
         .enableNeverClassInliningAnnotations()
         .enableNoHorizontalClassMergingAnnotations()
         .enableSideEffectAnnotations()
-        .enableNoHorizontalClassMergingAnnotations()
         .setMinApi(parameters.getApiLevel())
         .compile()
         .inspect(this::inspect)
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/inliner/conditionalsimpleinlining/ConditionalSimpleInliningTestBase.java b/src/test/java/com/android/tools/r8/ir/optimize/inliner/conditionalsimpleinlining/ConditionalSimpleInliningTestBase.java
new file mode 100644
index 0000000..61c9edc
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/inliner/conditionalsimpleinlining/ConditionalSimpleInliningTestBase.java
@@ -0,0 +1,45 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.optimize.inliner.conditionalsimpleinlining;
+
+import com.android.tools.r8.R8FullTestBuilder;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.ir.optimize.Inliner;
+import com.android.tools.r8.utils.BooleanUtils;
+import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.SetUtils;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public abstract class ConditionalSimpleInliningTestBase extends TestBase {
+
+  protected final boolean enableSimpleInliningConstraints;
+  protected final TestParameters parameters;
+
+  @Parameters(name = "{1}, simple inlining constraints: {0}")
+  public static Iterable<?> data() {
+    return buildParameters(
+        BooleanUtils.values(), TestBase.getTestParameters().withAllRuntimesAndApiLevels().build());
+  }
+
+  public ConditionalSimpleInliningTestBase(
+      boolean enableSimpleInliningConstraints, TestParameters parameters) {
+    this.enableSimpleInliningConstraints = enableSimpleInliningConstraints;
+    this.parameters = parameters;
+  }
+
+  public void configure(R8FullTestBuilder testBuilder) {
+    testBuilder
+        .addOptionsModification(this::enableSimpleInliningConstraints)
+        .setMinApi(parameters.getApiLevel());
+  }
+
+  private void enableSimpleInliningConstraints(InternalOptions options) {
+    options.enableSimpleInliningConstraints = enableSimpleInliningConstraints;
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/inliner/conditionalsimpleinlining/ConditionalSimpleInliningWithEnumUnboxingTest.java b/src/test/java/com/android/tools/r8/ir/optimize/inliner/conditionalsimpleinlining/ConditionalSimpleInliningWithEnumUnboxingTest.java
new file mode 100644
index 0000000..3cebb62
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/inliner/conditionalsimpleinlining/ConditionalSimpleInliningWithEnumUnboxingTest.java
@@ -0,0 +1,71 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.optimize.inliner.conditionalsimpleinlining;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import org.junit.Test;
+import org.junit.runners.Parameterized.Parameters;
+
+public class ConditionalSimpleInliningWithEnumUnboxingTest
+    extends ConditionalSimpleInliningTestBase {
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return TestBase.getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public ConditionalSimpleInliningWithEnumUnboxingTest(TestParameters parameters) {
+    super(true, parameters);
+  }
+
+  @Test
+  public void test() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(getClass())
+        .addKeepMainRule(TestClass.class)
+        .apply(this::configure)
+        .addEnumUnboxingInspector(
+            inspector ->
+                inspector.assertUnboxedIf(parameters.isDexRuntime(), EnumUnboxingCandidate.class))
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutputLines("Hello world!");
+  }
+
+  static class TestClass {
+
+    public static void main(String[] args) {
+      EnumUnboxingCandidate value =
+          System.currentTimeMillis() > 0 ? EnumUnboxingCandidate.A : EnumUnboxingCandidate.B;
+      simpleIfNullTest(value);
+    }
+
+    static void simpleIfNullTest(EnumUnboxingCandidate arg) {
+      if (arg == null) {
+        return;
+      }
+      System.out.print("H");
+      System.out.print("e");
+      System.out.print("l");
+      System.out.print("l");
+      System.out.print("o");
+      System.out.print(" ");
+      System.out.print("w");
+      System.out.print("o");
+      System.out.print("r");
+      System.out.print("l");
+      System.out.print("d");
+      System.out.println("!");
+    }
+  }
+
+  enum EnumUnboxingCandidate {
+    A,
+    B;
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/inliner/conditionalsimpleinlining/SimpleIfNullOrNotNullInliningTest.java b/src/test/java/com/android/tools/r8/ir/optimize/inliner/conditionalsimpleinlining/SimpleIfNullOrNotNullInliningTest.java
new file mode 100644
index 0000000..c5073c5
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/inliner/conditionalsimpleinlining/SimpleIfNullOrNotNullInliningTest.java
@@ -0,0 +1,195 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.optimize.inliner.conditionalsimpleinlining;
+
+import static com.android.tools.r8.utils.codeinspector.CodeMatchers.invokesMethodWithName;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static com.android.tools.r8.utils.codeinspector.Matchers.notIf;
+import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.NeverSingleCallerInline;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.utils.BooleanUtils;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import com.google.common.collect.ImmutableList;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class SimpleIfNullOrNotNullInliningTest extends ConditionalSimpleInliningTestBase {
+
+  private final Class<?> mainClass;
+
+  @Parameters(name = "{2}, main: {1}, simple inlining constraints: {0}")
+  public static List<Object[]> data() {
+    return buildParameters(
+        BooleanUtils.values(),
+        ImmutableList.of(
+            TestClassEligibleForSimpleInlining.class, TestClassIneligibleForSimpleInlining.class),
+        TestBase.getTestParameters().withAllRuntimesAndApiLevels().build());
+  }
+
+  public SimpleIfNullOrNotNullInliningTest(
+      boolean enableSimpleInliningConstraints, Class<?> mainClass, TestParameters parameters) {
+    super(enableSimpleInliningConstraints, parameters);
+    this.mainClass = mainClass;
+  }
+
+  @Test
+  public void test() throws Exception {
+    testForR8(parameters.getBackend())
+        .addProgramClasses(mainClass, TestMethods.class)
+        .addKeepMainRule(mainClass)
+        .apply(this::configure)
+        .enableNeverSingleCallerInlineAnnotations()
+        .compile()
+        .inspect(this::inspect)
+        .run(parameters.getRuntime(), mainClass)
+        .assertSuccessWithOutput(getExpectedOutput());
+  }
+
+  private String getExpectedOutput() {
+    if (mainClass == TestClassEligibleForSimpleInlining.class) {
+      return "";
+    }
+    return StringUtils.times(StringUtils.lines("Hello world!"), 8);
+  }
+
+  private void inspect(CodeInspector inspector) {
+    MethodSubject mainMethodSubject = inspector.clazz(mainClass).mainMethod();
+    assertThat(mainMethodSubject, isPresent());
+    assertThat(mainMethodSubject, not(invokesMethodWithName("print")));
+
+    ClassSubject classSubject = inspector.clazz(TestMethods.class);
+    assertThat(classSubject, notIf(isPresent(), shouldBeEligibleForSimpleInlining()));
+
+    if (shouldBeEligibleForSimpleInlining()) {
+      return;
+    }
+
+    assertThat(classSubject, isPresent());
+    assertThat(classSubject.uniqueMethodWithName("simpleIfNullTest"), isPresent());
+    assertThat(classSubject.uniqueMethodWithName("simpleIfBothNullTest"), isPresent());
+    assertThat(classSubject.uniqueMethodWithName("simpleIfNotNullTest"), isPresent());
+    assertThat(classSubject.uniqueMethodWithName("simpleIfBothNotNullTest"), isPresent());
+  }
+
+  private boolean shouldBeEligibleForSimpleInlining() {
+    return mainClass == TestClassEligibleForSimpleInlining.class && enableSimpleInliningConstraints;
+  }
+
+  static class TestClassEligibleForSimpleInlining {
+
+    public static void main(String[] args) {
+      Object notNull = new Object();
+      TestMethods.simpleIfNullTest(null);
+      TestMethods.simpleIfBothNullTest(null, null);
+      TestMethods.simpleIfNotNullTest(notNull);
+      TestMethods.simpleIfBothNotNullTest(notNull, notNull);
+    }
+  }
+
+  static class TestClassIneligibleForSimpleInlining {
+
+    public static void main(String[] args) {
+      Object notNull = new Object();
+      TestMethods.simpleIfNullTest(notNull);
+      TestMethods.simpleIfBothNullTest(null, notNull);
+      TestMethods.simpleIfBothNullTest(notNull, null);
+      TestMethods.simpleIfBothNullTest(notNull, notNull);
+      TestMethods.simpleIfNotNullTest(null);
+      TestMethods.simpleIfBothNotNullTest(null, notNull);
+      TestMethods.simpleIfBothNotNullTest(notNull, null);
+      TestMethods.simpleIfBothNotNullTest(null, null);
+    }
+  }
+
+  static class TestMethods {
+
+    @NeverSingleCallerInline
+    static void simpleIfNullTest(Object o) {
+      if (o == null) {
+        return;
+      }
+      System.out.print("H");
+      System.out.print("e");
+      System.out.print("l");
+      System.out.print("l");
+      System.out.print("o");
+      System.out.print(" ");
+      System.out.print("w");
+      System.out.print("o");
+      System.out.print("r");
+      System.out.print("l");
+      System.out.print("d");
+      System.out.println("!");
+    }
+
+    @NeverSingleCallerInline
+    static void simpleIfBothNullTest(Object o1, Object o2) {
+      if (o1 == null && o2 == null) {
+        return;
+      }
+      System.out.print("H");
+      System.out.print("e");
+      System.out.print("l");
+      System.out.print("l");
+      System.out.print("o");
+      System.out.print(" ");
+      System.out.print("w");
+      System.out.print("o");
+      System.out.print("r");
+      System.out.print("l");
+      System.out.print("d");
+      System.out.println("!");
+    }
+
+    @NeverSingleCallerInline
+    static void simpleIfNotNullTest(Object o) {
+      if (o != null) {
+        return;
+      }
+      System.out.print("H");
+      System.out.print("e");
+      System.out.print("l");
+      System.out.print("l");
+      System.out.print("o");
+      System.out.print(" ");
+      System.out.print("w");
+      System.out.print("o");
+      System.out.print("r");
+      System.out.print("l");
+      System.out.print("d");
+      System.out.println("!");
+    }
+
+    @NeverSingleCallerInline
+    static void simpleIfBothNotNullTest(Object o1, Object o2) {
+      if (o1 != null && o2 != null) {
+        return;
+      }
+      System.out.print("H");
+      System.out.print("e");
+      System.out.print("l");
+      System.out.print("l");
+      System.out.print("o");
+      System.out.print(" ");
+      System.out.print("w");
+      System.out.print("o");
+      System.out.print("r");
+      System.out.print("l");
+      System.out.print("d");
+      System.out.println("!");
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/inliner/conditionalsimpleinlining/SimpleIfTrueOrFalseInliningTest.java b/src/test/java/com/android/tools/r8/ir/optimize/inliner/conditionalsimpleinlining/SimpleIfTrueOrFalseInliningTest.java
new file mode 100644
index 0000000..b7a7186
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/inliner/conditionalsimpleinlining/SimpleIfTrueOrFalseInliningTest.java
@@ -0,0 +1,193 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.optimize.inliner.conditionalsimpleinlining;
+
+import static com.android.tools.r8.utils.codeinspector.CodeMatchers.invokesMethodWithName;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static com.android.tools.r8.utils.codeinspector.Matchers.notIf;
+import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.NeverSingleCallerInline;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.utils.BooleanUtils;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import com.google.common.collect.ImmutableList;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class SimpleIfTrueOrFalseInliningTest extends ConditionalSimpleInliningTestBase {
+
+  private final Class<?> mainClass;
+
+  @Parameters(name = "{2}, main: {1}, simple inlining constraints: {0}")
+  public static List<Object[]> data() {
+    return buildParameters(
+        BooleanUtils.values(),
+        ImmutableList.of(
+            TestClassEligibleForSimpleInlining.class, TestClassIneligibleForSimpleInlining.class),
+        TestBase.getTestParameters().withAllRuntimesAndApiLevels().build());
+  }
+
+  public SimpleIfTrueOrFalseInliningTest(
+      boolean enableSimpleInliningConstraints, Class<?> mainClass, TestParameters parameters) {
+    super(enableSimpleInliningConstraints, parameters);
+    this.mainClass = mainClass;
+  }
+
+  @Test
+  public void test() throws Exception {
+    testForR8(parameters.getBackend())
+        .addProgramClasses(mainClass, TestMethods.class)
+        .addKeepMainRule(mainClass)
+        .apply(this::configure)
+        .enableNeverSingleCallerInlineAnnotations()
+        .compile()
+        .inspect(this::inspect)
+        .run(parameters.getRuntime(), mainClass)
+        .assertSuccessWithOutput(getExpectedOutput());
+  }
+
+  private String getExpectedOutput() {
+    if (mainClass == TestClassEligibleForSimpleInlining.class) {
+      return "";
+    }
+    return StringUtils.times(StringUtils.lines("Hello world!"), 8);
+  }
+
+  private void inspect(CodeInspector inspector) {
+    MethodSubject mainMethodSubject = inspector.clazz(mainClass).mainMethod();
+    assertThat(mainMethodSubject, isPresent());
+    assertThat(mainMethodSubject, not(invokesMethodWithName("print")));
+
+    ClassSubject classSubject = inspector.clazz(TestMethods.class);
+    assertThat(classSubject, notIf(isPresent(), shouldBeEligibleForSimpleInlining()));
+
+    if (shouldBeEligibleForSimpleInlining()) {
+      return;
+    }
+
+    assertThat(classSubject, isPresent());
+    assertThat(classSubject.uniqueMethodWithName("simpleIfTrueTest"), isPresent());
+    assertThat(classSubject.uniqueMethodWithName("simpleIfBothTrueTest"), isPresent());
+    assertThat(classSubject.uniqueMethodWithName("simpleIfFalseTest"), isPresent());
+    assertThat(classSubject.uniqueMethodWithName("simpleIfBothFalseTest"), isPresent());
+  }
+
+  private boolean shouldBeEligibleForSimpleInlining() {
+    return mainClass == TestClassEligibleForSimpleInlining.class && enableSimpleInliningConstraints;
+  }
+
+  static class TestClassEligibleForSimpleInlining {
+
+    public static void main(String[] args) {
+      TestMethods.simpleIfTrueTest(true);
+      TestMethods.simpleIfBothTrueTest(true, true);
+      TestMethods.simpleIfFalseTest(false);
+      TestMethods.simpleIfBothFalseTest(false, false);
+    }
+  }
+
+  static class TestClassIneligibleForSimpleInlining {
+
+    public static void main(String[] args) {
+      TestMethods.simpleIfTrueTest(false);
+      TestMethods.simpleIfBothTrueTest(true, false);
+      TestMethods.simpleIfBothTrueTest(false, true);
+      TestMethods.simpleIfBothTrueTest(false, false);
+      TestMethods.simpleIfFalseTest(true);
+      TestMethods.simpleIfBothFalseTest(true, false);
+      TestMethods.simpleIfBothFalseTest(false, true);
+      TestMethods.simpleIfBothFalseTest(true, true);
+    }
+  }
+
+  static class TestMethods {
+
+    @NeverSingleCallerInline
+    static void simpleIfTrueTest(boolean b) {
+      if (b) {
+        return;
+      }
+      System.out.print("H");
+      System.out.print("e");
+      System.out.print("l");
+      System.out.print("l");
+      System.out.print("o");
+      System.out.print(" ");
+      System.out.print("w");
+      System.out.print("o");
+      System.out.print("r");
+      System.out.print("l");
+      System.out.print("d");
+      System.out.println("!");
+    }
+
+    @NeverSingleCallerInline
+    static void simpleIfBothTrueTest(boolean b1, boolean b2) {
+      if (b1 && b2) {
+        return;
+      }
+      System.out.print("H");
+      System.out.print("e");
+      System.out.print("l");
+      System.out.print("l");
+      System.out.print("o");
+      System.out.print(" ");
+      System.out.print("w");
+      System.out.print("o");
+      System.out.print("r");
+      System.out.print("l");
+      System.out.print("d");
+      System.out.println("!");
+    }
+
+    @NeverSingleCallerInline
+    static void simpleIfFalseTest(boolean b) {
+      if (!b) {
+        return;
+      }
+      System.out.print("H");
+      System.out.print("e");
+      System.out.print("l");
+      System.out.print("l");
+      System.out.print("o");
+      System.out.print(" ");
+      System.out.print("w");
+      System.out.print("o");
+      System.out.print("r");
+      System.out.print("l");
+      System.out.print("d");
+      System.out.println("!");
+    }
+
+    @NeverSingleCallerInline
+    static void simpleIfBothFalseTest(boolean b1, boolean b2) {
+      if (!b1 && !b2) {
+        return;
+      }
+      System.out.print("H");
+      System.out.print("e");
+      System.out.print("l");
+      System.out.print("l");
+      System.out.print("o");
+      System.out.print(" ");
+      System.out.print("w");
+      System.out.print("o");
+      System.out.print("r");
+      System.out.print("l");
+      System.out.print("d");
+      System.out.println("!");
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/reflection/GetNameInClassInitializerTest.java b/src/test/java/com/android/tools/r8/ir/optimize/reflection/GetNameInClassInitializerTest.java
index d8d0c47..55afd12 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/reflection/GetNameInClassInitializerTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/reflection/GetNameInClassInitializerTest.java
@@ -45,7 +45,6 @@
     builder.addAll(ToolHelper.getClassFilesForTestDirectory(
         ToolHelper.getPackageDirectoryForTestPackage(MAIN.getPackage()),
         path -> path.getFileName().toString().startsWith("GetNameClinit")));
-    builder.add(ToolHelper.getClassFileForTestClass(NeverInline.class));
     classPaths = builder.build();
   }
 
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/reflection/GetSimpleNameTest.java b/src/test/java/com/android/tools/r8/ir/optimize/reflection/GetSimpleNameTest.java
index 14d7837..a9a5c6f 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/reflection/GetSimpleNameTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/reflection/GetSimpleNameTest.java
@@ -125,16 +125,6 @@
       "Inner",
       "Inner"
   );
-  private static final String OUTPUT_WITH_SHRUNK_ATTRIBUTES = StringUtils.lines(
-      "Local_t03",
-      "InnerLocal",
-      "$",
-      "$$",
-      "Local[][][]",
-      "[][][]",
-      "Outer$Inner",
-      "Outer$Inner"
-  );
   // JDK8 computes the simple name differently: some assumptions about non-member classes,
   // e.g., 1 or more digits (followed by the simple name if it's local).
   // Since JDK9, the simple name is computed by stripping off the package name.
@@ -171,8 +161,6 @@
     builder.add(ToolHelper.getClassFileForTestClass(Outer.class));
     builder.add(ToolHelper.getClassFileForTestClass(Outer.Inner.class));
     builder.add(ToolHelper.getClassFileForTestClass(Outer.TestHelper.class));
-    builder.add(ToolHelper.getClassFileForTestClass(ForceInline.class));
-    builder.add(ToolHelper.getClassFileForTestClass(NeverInline.class));
     classPaths = builder.build();
   }
 
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 9d4c185..9ee6ad7 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
@@ -13,7 +13,6 @@
 import static org.junit.Assert.assertTrue;
 
 import com.android.tools.r8.CompilationFailedException;
-import com.android.tools.r8.NeverInline;
 import com.android.tools.r8.R8TestRunResult;
 import com.android.tools.r8.SingleTestRunResult;
 import com.android.tools.r8.TestBase;
@@ -88,7 +87,6 @@
 
   private static final Class<?> main = TrivialTestClass.class;
   private static final Class<?>[] classes = {
-    NeverInline.class,
     TrivialTestClass.class,
     Simple.class,
     SimpleWithGetter.class,
@@ -238,7 +236,6 @@
   public void testMoveToHost_fieldOnly() throws Exception {
     Class<?> main = MoveToHostFieldOnlyTestClass.class;
     Class<?>[] classes = {
-        NeverInline.class,
         MoveToHostFieldOnlyTestClass.class,
         HostOkFieldOnly.class,
         CandidateOkFieldOnly.class
@@ -269,7 +266,6 @@
   public void testMoveToHost() throws Exception {
     Class<?> main = MoveToHostTestClass.class;
     Class<?>[] classes = {
-        NeverInline.class,
         MoveToHostTestClass.class,
         HostOk.class,
         CandidateOk.class,
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/templates/GenerateCfUtilityMethodsForCodeOptimizations.java b/src/test/java/com/android/tools/r8/ir/optimize/templates/GenerateCfUtilityMethodsForCodeOptimizations.java
index c35135e..732952d 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/templates/GenerateCfUtilityMethodsForCodeOptimizations.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/templates/GenerateCfUtilityMethodsForCodeOptimizations.java
@@ -43,6 +43,11 @@
     return ImmutableList.of(CfUtilityMethodsForCodeOptimizationsTemplates.class);
   }
 
+  @Override
+  protected int getYear() {
+    return 2020;
+  }
+
   @Test
   public void test() throws Exception {
     ArrayList<Class<?>> sorted = new ArrayList<>(getMethodTemplateClasses());
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/uninstantiatedtypes/ParameterRewritingTest.java b/src/test/java/com/android/tools/r8/ir/optimize/uninstantiatedtypes/ParameterRewritingTest.java
index 167c17b..8cc815b 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/uninstantiatedtypes/ParameterRewritingTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/uninstantiatedtypes/ParameterRewritingTest.java
@@ -54,8 +54,8 @@
             .addKeepMainRule(TestClass.class)
             .enableInliningAnnotations()
             .enableNoStaticClassMergingAnnotations()
-            .addKeepRules("-dontobfuscate")
             .addOptionsModification(options -> options.enableClassInlining = false)
+            .noMinification()
             .run(TestClass.class)
             .assertSuccessWithOutput(expected)
             .inspector();
diff --git a/src/test/java/com/android/tools/r8/ir/regalloc/RegisterMoveSchedulerTest.java b/src/test/java/com/android/tools/r8/ir/regalloc/RegisterMoveSchedulerTest.java
index dff06df..c253c45 100644
--- a/src/test/java/com/android/tools/r8/ir/regalloc/RegisterMoveSchedulerTest.java
+++ b/src/test/java/com/android/tools/r8/ir/regalloc/RegisterMoveSchedulerTest.java
@@ -145,6 +145,15 @@
     }
 
     @Override
+    public void addThrowingInstructionToPossiblyThrowingBlock(
+        IRCode code,
+        ListIterator<BasicBlock> blockIterator,
+        Instruction instruction,
+        InternalOptions options) {
+      throw new Unimplemented();
+    }
+
+    @Override
     public BasicBlock split(
         IRCode code, ListIterator<BasicBlock> blockIterator, boolean keepCatchHandlers) {
       throw new Unimplemented();
diff --git a/src/test/java/com/android/tools/r8/kotlin/KotlinClassInlinerTest.java b/src/test/java/com/android/tools/r8/kotlin/KotlinClassInlinerTest.java
index a949213..293caa8 100644
--- a/src/test/java/com/android/tools/r8/kotlin/KotlinClassInlinerTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/KotlinClassInlinerTest.java
@@ -205,21 +205,29 @@
               Predicate<DexType> lambdaCheck = createLambdaCheck(inspector);
               ClassSubject clazz = inspector.clazz(mainClassName);
 
+              // TODO(b/173337498): Should be empty, but horizontal class merging interferes with
+              //  class inlining.
               assertEquals(
-                  Sets.newHashSet(),
+                  Sets.newHashSet(
+                      "class_inliner_lambda_k_style.MainKt$testKotlinSequencesStateless$1"),
                   collectAccessedTypes(
                       lambdaCheck,
                       clazz,
                       "testKotlinSequencesStateless",
                       "kotlin.sequences.Sequence"));
 
+              // TODO(b/173337498): Should be absent, but horizontal class merging interferes with
+              //  class inlining.
               assertThat(
                   inspector.clazz(
                       "class_inliner_lambda_k_style.MainKt$testKotlinSequencesStateless$1"),
-                  not(isPresent()));
+                  isPresent());
 
+              // TODO(b/173337498): Should be empty, but horizontal class merging interferes with
+              //  class inlining.
               assertEquals(
-                  Sets.newHashSet(),
+                  Sets.newHashSet(
+                      "class_inliner_lambda_k_style.MainKt$testKotlinSequencesStateful$1"),
                   collectAccessedTypes(
                       lambdaCheck,
                       clazz,
@@ -228,10 +236,12 @@
                       "int",
                       "kotlin.sequences.Sequence"));
 
+              // TODO(b/173337498): Should be absent, but horizontal class merging interferes with
+              //  class inlining.
               assertThat(
                   inspector.clazz(
                       "class_inliner_lambda_k_style.MainKt$testKotlinSequencesStateful$1"),
-                  not(isPresent()));
+                  isPresent());
 
               assertEquals(
                   Sets.newHashSet(),
diff --git a/src/test/java/com/android/tools/r8/missingclasses/MissingClassReferencedFromNewInstanceTest.java b/src/test/java/com/android/tools/r8/missingclasses/MissingClassReferencedFromNewInstanceTest.java
new file mode 100644
index 0000000..8f43ab8
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/missingclasses/MissingClassReferencedFromNewInstanceTest.java
@@ -0,0 +1,62 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.missingclasses;
+
+import static com.android.tools.r8.DiagnosticsMatcher.diagnosticType;
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.TestDiagnosticMessages;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.references.TypeReference;
+import com.android.tools.r8.shaking.MissingClassesDiagnostic;
+import com.android.tools.r8.utils.codeinspector.AssertUtils;
+import org.junit.Test;
+
+public class MissingClassReferencedFromNewInstanceTest extends MissingClassesTestBase {
+
+  public MissingClassReferencedFromNewInstanceTest(
+      DontWarnConfiguration dontWarnConfiguration, TestParameters parameters) {
+    super(dontWarnConfiguration, parameters);
+  }
+
+  @Test
+  public void test() throws Exception {
+    AssertUtils.assertFailsCompilationIf(
+        // TODO(b/175542052): Should not fail compilation with -dontwarn Main.
+        !getDontWarnConfiguration().isDontWarnMissingClass(),
+        () ->
+            compileWithExpectedDiagnostics(
+                Main.class, MissingClass.class, this::inspectDiagnostics));
+  }
+
+  private void inspectDiagnostics(TestDiagnosticMessages diagnostics) {
+    // TODO(b/175542052): Should also not have any diagnostics with -dontwarn Main.
+    if (getDontWarnConfiguration().isDontWarnMissingClass()) {
+      diagnostics.assertNoMessages();
+      return;
+    }
+
+    diagnostics
+        .assertOnlyErrors()
+        .assertErrorsCount(1)
+        .assertAllErrorsMatch(diagnosticType(MissingClassesDiagnostic.class));
+
+    MissingClassesDiagnostic diagnostic = (MissingClassesDiagnostic) diagnostics.getErrors().get(0);
+    assertEquals(
+        !getDontWarnConfiguration().isDontWarnMissingClass(),
+        diagnostic.getMissingClasses().stream()
+            .map(TypeReference::getTypeName)
+            .anyMatch(MissingClass.class.getTypeName()::equals));
+  }
+
+  static class Main {
+
+    public static void main(String[] args) {
+      new MissingClass();
+    }
+  }
+
+  static class MissingClass {}
+}
diff --git a/src/test/java/com/android/tools/r8/missingclasses/MissingClassesTestBase.java b/src/test/java/com/android/tools/r8/missingclasses/MissingClassesTestBase.java
new file mode 100644
index 0000000..7c72273
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/missingclasses/MissingClassesTestBase.java
@@ -0,0 +1,70 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.missingclasses;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.R8TestCompileResult;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestCompilerBuilder.DiagnosticsConsumer;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.utils.InternalOptions.TestingOptions;
+import java.util.List;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public abstract class MissingClassesTestBase extends TestBase {
+
+  enum DontWarnConfiguration {
+    DONT_WARN_MAIN_CLASS,
+    DONT_WARN_MISSING_CLASS,
+    NONE;
+
+    public boolean isDontWarnMainClass() {
+      return this == DONT_WARN_MAIN_CLASS;
+    }
+
+    public boolean isDontWarnMissingClass() {
+      return this == DONT_WARN_MISSING_CLASS;
+    }
+  }
+
+  private final DontWarnConfiguration dontWarnConfiguration;
+  private final TestParameters parameters;
+
+  @Parameters(name = "{1}, {0}")
+  public static List<Object[]> data() {
+    return buildParameters(
+        DontWarnConfiguration.values(), getTestParameters().withAllRuntimesAndApiLevels().build());
+  }
+
+  public MissingClassesTestBase(
+      DontWarnConfiguration dontWarnConfiguration, TestParameters parameters) {
+    this.dontWarnConfiguration = dontWarnConfiguration;
+    this.parameters = parameters;
+  }
+
+  public R8TestCompileResult compileWithExpectedDiagnostics(
+      Class<?> mainClass, Class<?> missingClass, DiagnosticsConsumer diagnosticsConsumer)
+      throws CompilationFailedException {
+    return testForR8(parameters.getBackend())
+        .addProgramClasses(mainClass)
+        .addKeepMainRule(mainClass)
+        .applyIf(
+            dontWarnConfiguration.isDontWarnMainClass(),
+            testBuilder -> testBuilder.addDontWarn(mainClass))
+        .applyIf(
+            dontWarnConfiguration.isDontWarnMissingClass(),
+            testBuilder -> testBuilder.addDontWarn(missingClass))
+        .addOptionsModification(TestingOptions::enableExperimentalMissingClassesReporting)
+        .setMinApi(parameters.getApiLevel())
+        .compileWithExpectedDiagnostics(diagnosticsConsumer);
+  }
+
+  public DontWarnConfiguration getDontWarnConfiguration() {
+    return dontWarnConfiguration;
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/naming/RenameSourceFileRetraceTest.java b/src/test/java/com/android/tools/r8/naming/RenameSourceFileRetraceTest.java
index 5dfd98f..18e92d8 100644
--- a/src/test/java/com/android/tools/r8/naming/RenameSourceFileRetraceTest.java
+++ b/src/test/java/com/android/tools/r8/naming/RenameSourceFileRetraceTest.java
@@ -101,7 +101,7 @@
         .run(parameters.getRuntime(), Main.class)
         .assertFailureWithErrorThatMatches(containsString("ClassToBeMinified.foo()"))
         .inspectFailure(inspector -> inspectOutput(inspector, expectedName, expectedName))
-        .inspectStackTrace(stackTrace -> inspectStackTrace(stackTrace, expectedName));
+        .inspectStackTrace(stackTrace -> inspectStackTrace(stackTrace, FILENAME_MAIN));
   }
 
   private String getDefaultExpectedName() {
diff --git a/src/test/java/com/android/tools/r8/naming/applymapping/ApplyMappingAfterHorizontalMergingFieldTest.java b/src/test/java/com/android/tools/r8/naming/applymapping/ApplyMappingAfterHorizontalMergingFieldTest.java
index dc74758..5da69a2 100644
--- a/src/test/java/com/android/tools/r8/naming/applymapping/ApplyMappingAfterHorizontalMergingFieldTest.java
+++ b/src/test/java/com/android/tools/r8/naming/applymapping/ApplyMappingAfterHorizontalMergingFieldTest.java
@@ -82,7 +82,6 @@
     testForJvm()
         .addProgramClasses(LIBRARY_CLASSES)
         .addProgramClasses(PROGRAM_CLASSES)
-        .addTestingAnnotationsAsProgramClasses()
         .run(parameters.getRuntime(), ProgramClass.class)
         .assertSuccessWithOutput(EXPECTED_SUCCESS);
   }
@@ -92,7 +91,6 @@
     R8TestCompileResult libraryResult =
         testForR8(parameters.getBackend())
             .addProgramClasses(LIBRARY_CLASSES)
-            .addTestingAnnotationsAsProgramClasses()
             .addKeepMainRule(LibraryMain.class)
             .setMinApi(parameters.getApiLevel())
             .compile();
diff --git a/src/test/java/com/android/tools/r8/naming/applymapping/ApplyMappingAfterHorizontalMergingMethodTest.java b/src/test/java/com/android/tools/r8/naming/applymapping/ApplyMappingAfterHorizontalMergingMethodTest.java
index 7429e57..e101caa 100644
--- a/src/test/java/com/android/tools/r8/naming/applymapping/ApplyMappingAfterHorizontalMergingMethodTest.java
+++ b/src/test/java/com/android/tools/r8/naming/applymapping/ApplyMappingAfterHorizontalMergingMethodTest.java
@@ -63,7 +63,6 @@
   // Test runner code follows.
 
   private static final Class<?>[] LIBRARY_CLASSES = {
-      NeverInline.class,
       LibraryA.class,
       LibraryB.class,
       LibraryMain.class
diff --git a/src/test/java/com/android/tools/r8/naming/b130791310/B130791310.java b/src/test/java/com/android/tools/r8/naming/b130791310/B130791310.java
index 23271e2..ad2f639 100644
--- a/src/test/java/com/android/tools/r8/naming/b130791310/B130791310.java
+++ b/src/test/java/com/android/tools/r8/naming/b130791310/B130791310.java
@@ -122,7 +122,7 @@
         .addProgramClasses(CLASSES)
         .addKeepClassAndMembersRules(MAIN)
         .addKeepRules(RULES)
-        .addTestingAnnotationsAsProgramClasses()
+        .addNeverClassInliningAnnotations()
         .setMinApi(parameters.getApiLevel())
         .applyIf(
             !enableClassMerging, builder -> builder.addKeepRules("-optimizations !class/merging/*"))
diff --git a/src/test/java/com/android/tools/r8/repackage/RepackageDebugMinificationTest.java b/src/test/java/com/android/tools/r8/repackage/RepackageDebugMinificationTest.java
new file mode 100644
index 0000000..fe19640
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/repackage/RepackageDebugMinificationTest.java
@@ -0,0 +1,99 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.repackage;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresentAndNotRenamed;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresentAndRenamed;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.CompilationMode;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.LocalVariableTable;
+import com.android.tools.r8.utils.codeinspector.LocalVariableTable.LocalVariableTableEntry;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class RepackageDebugMinificationTest extends RepackageTestBase {
+
+  private static final String EXPECTED = "Hello World!";
+
+  public RepackageDebugMinificationTest(
+      String flattenPackageHierarchyOrRepackageClasses, TestParameters parameters) {
+    super(flattenPackageHierarchyOrRepackageClasses, parameters);
+  }
+
+  @Test
+  public void testRuntime() throws Exception {
+    testForRuntime(parameters)
+        .addProgramClasses(A.class, Main.class)
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines(EXPECTED);
+  }
+
+  @Test
+  public void testR8WithDebugDex() throws Exception {
+    assumeTrue(parameters.isDexRuntime());
+    testForR8(parameters.getBackend())
+        .addProgramClasses(A.class, Main.class)
+        .setMode(CompilationMode.DEBUG)
+        .setMinApi(parameters.getApiLevel())
+        .apply(this::configureRepackaging)
+        .addKeepMainRule(Main.class)
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines(EXPECTED);
+  }
+
+  @Test
+  public void testR8WithDebugCf() throws Exception {
+    assumeTrue(parameters.isCfRuntime());
+    testForR8(parameters.getBackend())
+        .addProgramClasses(A.class, Main.class)
+        .setMode(CompilationMode.DEBUG)
+        .setMinApi(parameters.getApiLevel())
+        .addKeepMainRule(Main.class)
+        .apply(this::configureRepackaging)
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines(EXPECTED)
+        .inspect(
+            inspector -> {
+              ClassSubject mainClass = inspector.clazz(Main.class);
+              assertThat(mainClass, isPresentAndNotRenamed());
+              MethodSubject main = mainClass.uniqueMethodWithName("main");
+              assertThat(main, isPresentAndNotRenamed());
+              ClassSubject aClass = inspector.clazz(A.class);
+              assertThat(aClass, isPresentAndRenamed());
+              LocalVariableTable localVariableTable = main.getLocalVariableTable();
+              // Take the second index which is localValue
+              assertEquals(2, localVariableTable.size());
+              LocalVariableTableEntry localVariableTableEntry = localVariableTable.get(1);
+              assertEquals("localValue", localVariableTableEntry.name);
+              assertTrue(localVariableTableEntry.type.is(aClass));
+            });
+  }
+
+  public static class A {
+
+    public void foo() {
+      System.out.println("Hello World!");
+    }
+  }
+
+  public static class Main {
+
+    public static void main(String[] args) {
+      A localValue = args.length == 0 ? new A() : null;
+      if (localValue != null) {
+        localValue.foo();
+      }
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/repackage/RepackageWithNestAndKeepOnPrivateTest.java b/src/test/java/com/android/tools/r8/repackage/RepackageWithNestAndKeepOnPrivateTest.java
new file mode 100644
index 0000000..e915d3c
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/repackage/RepackageWithNestAndKeepOnPrivateTest.java
@@ -0,0 +1,98 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.repackage;
+
+import static com.android.tools.r8.shaking.ProguardConfigurationParser.FLATTEN_PACKAGE_HIERARCHY;
+import static com.android.tools.r8.shaking.ProguardConfigurationParser.REPACKAGE_CLASSES;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresentAndNotRenamed;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestRuntime.CfVm;
+import com.google.common.collect.ImmutableList;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class RepackageWithNestAndKeepOnPrivateTest extends RepackageTestBase {
+
+  private final String EXPECTED = "Hello World!";
+
+  @Parameters(name = "{1}, kind: {0}")
+  public static List<Object[]> data() {
+    return buildParameters(
+        ImmutableList.of(FLATTEN_PACKAGE_HIERARCHY, REPACKAGE_CLASSES),
+        getTestParameters().withCfRuntimesStartingFromIncluding(CfVm.JDK11).build());
+  }
+
+  public RepackageWithNestAndKeepOnPrivateTest(
+      String flattenPackageHierarchyOrRepackageClasses, TestParameters parameters) {
+    super(flattenPackageHierarchyOrRepackageClasses, parameters);
+  }
+
+  @Test
+  public void testJvm() throws Exception {
+    testForRuntime(parameters)
+        .addProgramClassFileData(getProgramClassData())
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines(EXPECTED);
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    testForR8(parameters.getBackend())
+        .addProgramClassFileData(getProgramClassData())
+        .apply(this::configureRepackaging)
+        .addKeepClassAndMembersRules(A.class)
+        .enableInliningAnnotations()
+        .addKeepMainRule(Main.class)
+        .compile()
+        .inspect(
+            inspector -> {
+              assertThat(inspector.clazz(A.class), isPresentAndNotRenamed());
+              assertThat(B.class, isNotRepackaged(inspector));
+            })
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines(EXPECTED);
+  }
+
+  private List<byte[]> getProgramClassData() throws Exception {
+    return ImmutableList.of(
+        transformer(Main.class).removeInnerClasses().transform(),
+        transformer(A.class)
+            .setPrivate(A.class.getDeclaredMethod("aPrivate"))
+            .removeInnerClasses()
+            .setNest(A.class, B.class)
+            .transform(),
+        transformer(B.class).removeInnerClasses().setNest(A.class, B.class).transform());
+  }
+
+  public static class A {
+
+    @NeverInline
+    /* private */ static void aPrivate() {
+      System.out.println("Hello World!");
+    }
+  }
+
+  public static class B {
+
+    @NeverInline
+    public static void foo() {
+      A.aPrivate();
+    }
+  }
+
+  public static class Main {
+
+    public static void main(String[] args) {
+      B.foo();
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/repackage/RepackageWithNestAndKeepOnReferenceToPrivateTest.java b/src/test/java/com/android/tools/r8/repackage/RepackageWithNestAndKeepOnReferenceToPrivateTest.java
new file mode 100644
index 0000000..a976256
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/repackage/RepackageWithNestAndKeepOnReferenceToPrivateTest.java
@@ -0,0 +1,83 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.repackage;
+
+import static com.android.tools.r8.shaking.ProguardConfigurationParser.FLATTEN_PACKAGE_HIERARCHY;
+import static com.android.tools.r8.shaking.ProguardConfigurationParser.REPACKAGE_CLASSES;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestRuntime.CfVm;
+import com.google.common.collect.ImmutableList;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class RepackageWithNestAndKeepOnReferenceToPrivateTest extends RepackageTestBase {
+
+  private final String EXPECTED = "Hello World!";
+
+  @Parameters(name = "{1}, kind: {0}")
+  public static List<Object[]> data() {
+    return buildParameters(
+        ImmutableList.of(FLATTEN_PACKAGE_HIERARCHY, REPACKAGE_CLASSES),
+        getTestParameters().withCfRuntimesStartingFromIncluding(CfVm.JDK11).build());
+  }
+
+  public RepackageWithNestAndKeepOnReferenceToPrivateTest(
+      String flattenPackageHierarchyOrRepackageClasses, TestParameters parameters) {
+    super(flattenPackageHierarchyOrRepackageClasses, parameters);
+  }
+
+  @Test
+  public void testJvm() throws Exception {
+    testForRuntime(parameters)
+        .addProgramClassFileData(getProgramClassData())
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines(EXPECTED);
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    testForR8(parameters.getBackend())
+        .addProgramClassFileData(getProgramClassData())
+        .apply(this::configureRepackaging)
+        .enableInliningAnnotations()
+        .addKeepMainRule(Main.class)
+        .compile()
+        .inspect(inspector -> assertThat(A.class, isNotRepackaged(inspector)))
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines(EXPECTED);
+  }
+
+  private List<byte[]> getProgramClassData() throws Exception {
+    return ImmutableList.of(
+        transformer(A.class)
+            .setPrivate(A.class.getDeclaredMethod("aPrivate"))
+            .removeInnerClasses()
+            .setNest(A.class, Main.class)
+            .transform(),
+        transformer(Main.class).removeInnerClasses().setNest(A.class, Main.class).transform());
+  }
+
+  public static class A {
+
+    @NeverInline
+    /* private */ static void aPrivate() {
+      System.out.println("Hello World!");
+    }
+  }
+
+  public static class Main {
+
+    public static void main(String[] args) {
+      A.aPrivate();
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/retrace/KotlinInlineFunctionRetraceTest.java b/src/test/java/com/android/tools/r8/retrace/KotlinInlineFunctionRetraceTest.java
index 53d2a7c..d22a979 100644
--- a/src/test/java/com/android/tools/r8/retrace/KotlinInlineFunctionRetraceTest.java
+++ b/src/test/java/com/android/tools/r8/retrace/KotlinInlineFunctionRetraceTest.java
@@ -42,8 +42,7 @@
 public class KotlinInlineFunctionRetraceTest extends KotlinTestBase {
 
   private final TestParameters parameters;
-  // TODO(b/151132660): Fix filename
-  private static final String FILENAME_INLINE_STATIC = "InlineFunctionKt.kt";
+  private static final String FILENAME_INLINE_STATIC = "InlineFunction.kt";
   private static final String FILENAME_INLINE_INSTANCE = "InlineFunction.kt";
 
   @Parameters(name = "{0}")
diff --git a/src/test/java/com/android/tools/r8/retrace/RetraceRegularExpressionTests.java b/src/test/java/com/android/tools/r8/retrace/RetraceRegularExpressionTests.java
index 6c43c35..878a91d 100644
--- a/src/test/java/com/android/tools/r8/retrace/RetraceRegularExpressionTests.java
+++ b/src/test/java/com/android/tools/r8/retrace/RetraceRegularExpressionTests.java
@@ -358,7 +358,7 @@
 
           @Override
           public List<String> retracedStackTrace() {
-            return ImmutableList.of("a.b.d(d.java)");
+            return ImmutableList.of("a.b.d(SourceFile)");
           }
 
           @Override
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 72221e3..e8e09aa 100644
--- a/src/test/java/com/android/tools/r8/retrace/RetraceTests.java
+++ b/src/test/java/com/android/tools/r8/retrace/RetraceTests.java
@@ -39,6 +39,7 @@
 import com.android.tools.r8.retrace.stacktraces.ObfucatedExceptionClassStackTrace;
 import com.android.tools.r8.retrace.stacktraces.ObfuscatedRangeToSingleLineStackTrace;
 import com.android.tools.r8.retrace.stacktraces.RetraceAssertionErrorStackTrace;
+import com.android.tools.r8.retrace.stacktraces.SourceFileNameSynthesizeStackTrace;
 import com.android.tools.r8.retrace.stacktraces.SourceFileWithNumberAndEmptyStackTrace;
 import com.android.tools.r8.retrace.stacktraces.StackTraceForTest;
 import com.android.tools.r8.retrace.stacktraces.SuppressedStackTrace;
@@ -229,6 +230,11 @@
     runRetraceTest(new SourceFileWithNumberAndEmptyStackTrace());
   }
 
+  @Test
+  public void testSourceFileNameSynthesizeStackTrace() {
+    runRetraceTest(new SourceFileNameSynthesizeStackTrace());
+  }
+
   private void inspectRetraceTest(
       StackTraceForTest stackTraceForTest, Consumer<Retracer> inspection) {
     inspection.accept(
diff --git a/src/test/java/com/android/tools/r8/retrace/stacktraces/ActualIdentityStackTrace.java b/src/test/java/com/android/tools/r8/retrace/stacktraces/ActualIdentityStackTrace.java
index c86411e..9ac7068 100644
--- a/src/test/java/com/android/tools/r8/retrace/stacktraces/ActualIdentityStackTrace.java
+++ b/src/test/java/com/android/tools/r8/retrace/stacktraces/ActualIdentityStackTrace.java
@@ -37,10 +37,10 @@
             + " privateinterfacemethods.PrivateInterfaceMethods.main(PrivateInterfaceMethods.java:9)",
         "    \tat dalvik.system.NativeStart.main(Native Method)",
         "    Caused by: java.lang.NoSuchMethodError: privateinterfacemethods.I$-CC.$default$iFoo",
-        "    \tat privateinterfacemethods.I$-CC.sFoo(PrivateInterfaceMethods.java:40)",
-        "    \tat privateinterfacemethods.I$-CC.access$000(PrivateInterfaceMethods.java:28)",
-        "    \tat privateinterfacemethods.I$1.<init>(PrivateInterfaceMethods.java:31)",
-        "    \tat privateinterfacemethods.I.<clinit>(PrivateInterfaceMethods.java:30)",
+        "    \tat privateinterfacemethods.I$-CC.sFoo(I.java:40)",
+        "    \tat privateinterfacemethods.I$-CC.access$000(I.java:28)",
+        "    \tat privateinterfacemethods.I$1.<init>(I.java:31)",
+        "    \tat privateinterfacemethods.I.<clinit>(I.java:30)",
         "    \t... 2 more");
   }
 
diff --git a/src/test/java/com/android/tools/r8/retrace/stacktraces/AmbiguousMissingLineStackTrace.java b/src/test/java/com/android/tools/r8/retrace/stacktraces/AmbiguousMissingLineStackTrace.java
index b44b364..b92190e 100644
--- a/src/test/java/com/android/tools/r8/retrace/stacktraces/AmbiguousMissingLineStackTrace.java
+++ b/src/test/java/com/android/tools/r8/retrace/stacktraces/AmbiguousMissingLineStackTrace.java
@@ -30,7 +30,7 @@
         "    <OR> at com.android.tools.r8.R8.foo(R8.java:7)",
         "    at com.android.tools.r8.R8.bar(R8.java:8)",
         "    <OR> at com.android.tools.r8.R8.foo(R8.java:8)",
-        "    at com.android.tools.r8.R8.main(R8.java)",
+        "    at com.android.tools.r8.R8.main(Unknown Source)",
         "Caused by: com.android.tools.r8.CompilationException: foo[parens](Source:3)",
         "    at com.android.tools.r8.R8.bar(R8.java:9)",
         "    <OR> at com.android.tools.r8.R8.foo(R8.java:9)",
diff --git a/src/test/java/com/android/tools/r8/retrace/stacktraces/AmbiguousStackTrace.java b/src/test/java/com/android/tools/r8/retrace/stacktraces/AmbiguousStackTrace.java
index a3a8e58..429ee6b 100644
--- a/src/test/java/com/android/tools/r8/retrace/stacktraces/AmbiguousStackTrace.java
+++ b/src/test/java/com/android/tools/r8/retrace/stacktraces/AmbiguousStackTrace.java
@@ -57,7 +57,7 @@
         "    <OR> at com.android.tools.r8.R8.foo(R8.java)",
         "    at com.android.tools.r8.R8.bar(R8.java)",
         "    <OR> at com.android.tools.r8.R8.foo(R8.java)",
-        "    at com.android.tools.r8.R8.main(R8.java)",
+        "    at com.android.tools.r8.R8.main(Unknown Source)",
         "Caused by: com.android.tools.r8.CompilationException: foo[parens](Source:3)",
         "    at com.android.tools.r8.R8.bar(R8.java)",
         "    <OR> at com.android.tools.r8.R8.foo(R8.java)",
diff --git a/src/test/java/com/android/tools/r8/retrace/stacktraces/FileNameExtensionStackTrace.java b/src/test/java/com/android/tools/r8/retrace/stacktraces/FileNameExtensionStackTrace.java
index 71a3e64..ae9e0ff 100644
--- a/src/test/java/com/android/tools/r8/retrace/stacktraces/FileNameExtensionStackTrace.java
+++ b/src/test/java/com/android/tools/r8/retrace/stacktraces/FileNameExtensionStackTrace.java
@@ -4,6 +4,7 @@
 
 package com.android.tools.r8.retrace.stacktraces;
 
+import com.android.tools.r8.utils.StringUtils;
 import java.util.Arrays;
 import java.util.List;
 
@@ -29,24 +30,24 @@
 
   @Override
   public String mapping() {
-    return "foo.bar.baz -> a.b.c:";
+    return StringUtils.lines("foo.bar.baz -> a.b.c:", "R8 -> R8:");
   }
 
   @Override
   public List<String> retracedStackTrace() {
     return Arrays.asList(
         "foo.bar.baz: Problem when compiling program",
-        "    at R8.main(App:800)",
+        "    at R8.main(R8.java:800)",
         "    at R8.main(Native Method)",
-        "    at R8.main(Main.java:)",
-        "    at R8.main(Main.kt:1)",
-        "    at R8.main(Main.foo)",
+        "    at R8.main(R8.java:)",
+        "    at R8.main(R8.kt:1)",
+        "    at R8.main(R8.foo)",
         "    at R8.main(R8.java)",
         "    at R8.main(R8.java)",
         "    at R8.main(R8.java)",
         "    at R8.main(R8.java:1)",
         "Suppressed: foo.bar.baz: You have to write the program first",
-        "    at R8.retrace(App:184)",
+        "    at R8.retrace(R8.java:184)",
         "    ... 7 more");
   }
 
diff --git a/src/test/java/com/android/tools/r8/retrace/stacktraces/InlineWithLineNumbersStackTrace.java b/src/test/java/com/android/tools/r8/retrace/stacktraces/InlineWithLineNumbersStackTrace.java
index a7cd28d..59c9693 100644
--- a/src/test/java/com/android/tools/r8/retrace/stacktraces/InlineWithLineNumbersStackTrace.java
+++ b/src/test/java/com/android/tools/r8/retrace/stacktraces/InlineWithLineNumbersStackTrace.java
@@ -37,10 +37,10 @@
   public List<String> retracedStackTrace() {
     return Arrays.asList(
         "Exception in thread \"main\" java.lang.NullPointerException",
-        "\tat com.android.tools.r8.naming.retrace.Main.method3(InliningRetraceTest.java:81)",
-        "\tat com.android.tools.r8.naming.retrace.Main.method2(InliningRetraceTest.java:88)",
-        "\tat com.android.tools.r8.naming.retrace.Main.method1(InliningRetraceTest.java:96)",
-        "\tat com.android.tools.r8.naming.retrace.Main.main(InliningRetraceTest.java:102)");
+        "\tat com.android.tools.r8.naming.retrace.Main.method3(Main.java:81)",
+        "\tat com.android.tools.r8.naming.retrace.Main.method2(Main.java:88)",
+        "\tat com.android.tools.r8.naming.retrace.Main.method1(Main.java:96)",
+        "\tat com.android.tools.r8.naming.retrace.Main.main(Main.java:102)");
   }
 
   @Override
diff --git a/src/test/java/com/android/tools/r8/retrace/stacktraces/PGStackTrace.java b/src/test/java/com/android/tools/r8/retrace/stacktraces/PGStackTrace.java
index 2732b34..8698ac9 100644
--- a/src/test/java/com/android/tools/r8/retrace/stacktraces/PGStackTrace.java
+++ b/src/test/java/com/android/tools/r8/retrace/stacktraces/PGStackTrace.java
@@ -4,6 +4,7 @@
 
 package com.android.tools.r8.retrace.stacktraces;
 
+import com.android.tools.r8.utils.StringUtils;
 import java.util.Arrays;
 import java.util.List;
 
@@ -24,7 +25,10 @@
 
   @Override
   public String mapping() {
-    return "";
+    return StringUtils.lines(
+        "com.google.apps.sectionheader.SectionHeaderListController "
+            + "-> com.google.apps.sectionheader.SectionHeaderListController:",
+        "com.google.apps.Controller " + "-> com.google.apps.Controller:");
   }
 
   @Override
diff --git a/src/test/java/com/android/tools/r8/retrace/stacktraces/SourceFileNameSynthesizeStackTrace.java b/src/test/java/com/android/tools/r8/retrace/stacktraces/SourceFileNameSynthesizeStackTrace.java
new file mode 100644
index 0000000..0315fc0
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/retrace/stacktraces/SourceFileNameSynthesizeStackTrace.java
@@ -0,0 +1,42 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.retrace.stacktraces;
+
+import com.android.tools.r8.utils.StringUtils;
+import java.util.Arrays;
+import java.util.List;
+
+public class SourceFileNameSynthesizeStackTrace implements StackTraceForTest {
+
+  @Override
+  public List<String> obfuscatedStackTrace() {
+    return Arrays.asList(
+        "\tat mapping.a(AW779999992:21)",
+        "\tat noMappingKt.noMapping(AW779999992:21)",
+        "\tat mappingKotlin.b(AW779999992:21)");
+  }
+
+  @Override
+  public String mapping() {
+    return StringUtils.joinLines(
+        "android.support.v7.widget.ActionMenuView -> mapping:",
+        "  21:21:void invokeItem():624 -> a",
+        "android.support.v7.widget.ActionMenuViewKt -> mappingKotlin:",
+        "  21:21:void invokeItem():624 -> b");
+  }
+
+  @Override
+  public List<String> retracedStackTrace() {
+    return Arrays.asList(
+        "\tat android.support.v7.widget.ActionMenuView.invokeItem(ActionMenuView.java:624)",
+        "\tat noMappingKt.noMapping(AW779999992:21)",
+        "\tat android.support.v7.widget.ActionMenuViewKt.invokeItem(ActionMenuView.kt:624)");
+  }
+
+  @Override
+  public int expectedWarnings() {
+    return 0;
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/retrace/stacktraces/SourceFileWithNumberAndEmptyStackTrace.java b/src/test/java/com/android/tools/r8/retrace/stacktraces/SourceFileWithNumberAndEmptyStackTrace.java
index 8a94018..1522918 100644
--- a/src/test/java/com/android/tools/r8/retrace/stacktraces/SourceFileWithNumberAndEmptyStackTrace.java
+++ b/src/test/java/com/android/tools/r8/retrace/stacktraces/SourceFileWithNumberAndEmptyStackTrace.java
@@ -32,7 +32,7 @@
     return Arrays.asList(
         "  at com.android.tools.r8.utils.ExceptionUtils.withR8CompilationHandler("
             + "ExceptionUtils.java:59)",
-        "  at com.android.tools.r8.R8.runForTesting(R.java:261)",
+        "  at com.android.tools.r8.R8.runForTesting(R8.java:261)",
         "  at com.android.tools.r8.utils.ExceptionUtils.withR8CompilationHandler("
             + "ExceptionUtils.java:59)",
         "  at com.android.tools.r8.R8.runForTesting(R8.java:261)");
diff --git a/src/test/java/com/android/tools/r8/retrace/stacktraces/UnknownSourceStackTrace.java b/src/test/java/com/android/tools/r8/retrace/stacktraces/UnknownSourceStackTrace.java
index 4219979..76b89e4 100644
--- a/src/test/java/com/android/tools/r8/retrace/stacktraces/UnknownSourceStackTrace.java
+++ b/src/test/java/com/android/tools/r8/retrace/stacktraces/UnknownSourceStackTrace.java
@@ -30,7 +30,7 @@
         "    <OR> at com.android.tools.r8.R8.foo(R8.java)",
         "    at com.android.tools.r8.R8.bar(R8.java)",
         "    <OR> at com.android.tools.r8.R8.foo(R8.java)",
-        "    at com.android.tools.r8.R8.main(R8.java)",
+        "    at com.android.tools.r8.R8.main(Unknown Source)",
         "Caused by: com.android.tools.r8.CompilationException: foo[parens](Source:3)",
         "    at com.android.tools.r8.R8.bar(R8.java)",
         "    <OR> at com.android.tools.r8.R8.foo(R8.java)",
diff --git a/src/test/java/com/android/tools/r8/rewrite/JavaScriptScriptEngineTest.java b/src/test/java/com/android/tools/r8/rewrite/JavaScriptScriptEngineTest.java
index b299b76..0078c1b 100644
--- a/src/test/java/com/android/tools/r8/rewrite/JavaScriptScriptEngineTest.java
+++ b/src/test/java/com/android/tools/r8/rewrite/JavaScriptScriptEngineTest.java
@@ -68,7 +68,7 @@
         .compile()
         .assertAllWarningMessagesMatch(
             anyOf(
-                containsString("Missing class:"),
+                containsString("Missing class "),
                 containsString("required for default or static interface methods desugaring"),
                 equalTo("Resource 'META-INF/MANIFEST.MF' already exists.")))
         .run(parameters.getRuntime(), TestClass.class)
diff --git a/src/test/java/com/android/tools/r8/rewrite/ScriptEngineTest.java b/src/test/java/com/android/tools/r8/rewrite/ScriptEngineTest.java
index c7037ca..0077cef 100644
--- a/src/test/java/com/android/tools/r8/rewrite/ScriptEngineTest.java
+++ b/src/test/java/com/android/tools/r8/rewrite/ScriptEngineTest.java
@@ -81,7 +81,7 @@
         .compile()
         .assertAllWarningMessagesMatch(
             anyOf(
-                containsString("Missing class:"),
+                containsString("Missing class "),
                 containsString("it is required for default or static interface methods desugaring"),
                 equalTo("Resource 'META-INF/MANIFEST.MF' already exists.")))
         .writeToZip(path)
diff --git a/src/test/java/com/android/tools/r8/shaking/ProguardNameMatchingTest.java b/src/test/java/com/android/tools/r8/shaking/ProguardNameMatchingTest.java
index f0152a6..5163bbd 100644
--- a/src/test/java/com/android/tools/r8/shaking/ProguardNameMatchingTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/ProguardNameMatchingTest.java
@@ -96,7 +96,7 @@
     assertFalse(matchClassName("foobar", "!foobar", "*bar"));
 
     assertTrue(matchClassName("foo", "!boo"));
-    assertFalse(matchClassName("foo", "baz,!boo"));
+    assertTrue(matchClassName("foo", "baz", "!boo"));
 
     assertFalse(matchClassName("boo", "!boo", "**"));
     assertFalse(matchClassName("boo", "!b*<1>", "**"));
@@ -105,8 +105,7 @@
     assertTrue(matchClassName("boo",
         ImmutableList.of(ImmutableList.of("!boo"), ImmutableList.of("**"))));
 
-    // TODO(b/174824047): This parses as !(boo*,*foo,boofoo) and it should be !boo*,*foo,boofoo.
-    assertTrue(matchClassName("boofoo", "!boo*,*foo,boofoo"));
+    assertFalse(matchClassName("boofoo", "!boo*", "*foo", "boofoo"));
     assertTrue(matchClassName("boofoo",
         ImmutableList.of(ImmutableList.of("!boo*,*foo"), ImmutableList.of("boofoo"))));
   }
diff --git a/src/test/java/com/android/tools/r8/shaking/TreeShakingSpecificTest.java b/src/test/java/com/android/tools/r8/shaking/TreeShakingSpecificTest.java
index d246ba7..5ad87de 100644
--- a/src/test/java/com/android/tools/r8/shaking/TreeShakingSpecificTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/TreeShakingSpecificTest.java
@@ -93,8 +93,8 @@
           @Override
           public void error(Diagnostic error) {
             String expectedErrorMessage =
-                "Compilation can't be completed because the class `java.lang.Object` is missing";
-            if (error.getDiagnosticMessage().contains(expectedErrorMessage)) {
+                "Compilation can't be completed because the class java.lang.Object is missing.";
+            if (error.getDiagnosticMessage().equals(expectedErrorMessage)) {
               return;
             }
             throw new RuntimeException("Unexpected compilation error");
diff --git a/src/test/java/com/android/tools/r8/shaking/annotations/AnnotationsOnTargetedMethodTest.java b/src/test/java/com/android/tools/r8/shaking/annotations/AnnotationsOnTargetedMethodTest.java
index c4d9068..ace926f 100644
--- a/src/test/java/com/android/tools/r8/shaking/annotations/AnnotationsOnTargetedMethodTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/annotations/AnnotationsOnTargetedMethodTest.java
@@ -5,6 +5,7 @@
 package com.android.tools.r8.shaking.annotations;
 
 import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NoHorizontalClassMerging;
 import com.android.tools.r8.NoVerticalClassMerging;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
@@ -34,7 +35,7 @@
 
   @Parameters(name = "{0}")
   public static TestParametersCollection data() {
-    return getTestParameters().withAllRuntimes().withAllApiLevels().build();
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
   }
 
   public AnnotationsOnTargetedMethodTest(TestParameters parameters) {
@@ -52,9 +53,11 @@
     testForR8(parameters.getBackend())
         .addInnerClasses(AnnotationsOnTargetedMethodTest.class)
         .addKeepMainRule(TestClass.class)
-        .addKeepRules("-keepattributes *Annotation*", "-dontobfuscate")
+        .addKeepRuntimeVisibleAnnotations()
         .enableInliningAnnotations()
+        .enableNoHorizontalClassMergingAnnotations()
         .enableNoVerticalClassMergingAnnotations()
+        .noMinification()
         .setMinApi(parameters.getApiLevel())
         .run(parameters.getRuntime(), TestClass.class)
         .assertSuccessWithOutput(expectedOutput);
@@ -91,6 +94,7 @@
     void targetedMethod();
   }
 
+  @NoHorizontalClassMerging
   static class InterfaceImpl implements Interface {
 
     @NeverInline
@@ -100,6 +104,7 @@
     }
   }
 
+  @NoHorizontalClassMerging
   static class OtherInterfaceImpl implements Interface {
 
     @NeverInline
diff --git a/src/test/java/com/android/tools/r8/shaking/ifrule/IfOnAccessModifierTest.java b/src/test/java/com/android/tools/r8/shaking/ifrule/IfOnAccessModifierTest.java
index 445e24b..7c3a2d9 100644
--- a/src/test/java/com/android/tools/r8/shaking/ifrule/IfOnAccessModifierTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/ifrule/IfOnAccessModifierTest.java
@@ -54,13 +54,12 @@
     switch (shrinker) {
       case PROGUARD6:
         assertTrue(parameters.isCfRuntime());
-        return testForProguard().addTestingAnnotationsAsProgramClasses();
+        return testForProguard().addInliningAnnotations().addNeverClassInliningAnnotations();
       case R8:
         return testForR8(parameters.getBackend())
-            .addTestingAnnotationsAsProgramClasses()
             .allowUnusedProguardConfigurationRules(allowDiagnosticInfoMessages)
-            .enableNeverClassInliningAnnotations()
-            .enableInliningAnnotations();
+            .enableInliningAnnotations()
+            .enableNeverClassInliningAnnotations();
       default:
         throw new Unreachable();
     }
diff --git a/src/test/java/com/android/tools/r8/shaking/negatedrules/NegatedKeepRulesTest.java b/src/test/java/com/android/tools/r8/shaking/negatedrules/NegatedKeepRulesTest.java
index a0f56b6..b8de479 100644
--- a/src/test/java/com/android/tools/r8/shaking/negatedrules/NegatedKeepRulesTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/negatedrules/NegatedKeepRulesTest.java
@@ -272,6 +272,35 @@
             });
   }
 
+  @Test
+  public void testNegatedWithPositiveR8Compat() throws Exception {
+    testNegatedWithPositive(testForR8Compat(parameters.getBackend()));
+  }
+
+  @Test
+  public void testNegatedWithPositiveR8Full() throws Exception {
+    testNegatedWithPositive(testForR8(parameters.getBackend()));
+  }
+
+  @Test
+  public void testNegatedWithPositiveProguard() throws Exception {
+    testNegatedWithPositive(testForProguard(ProguardVersion.V7_0_0).addKeepRules("-dontwarn"));
+  }
+
+  public void testNegatedWithPositive(TestShrinkerBuilder<?, ?, ?, ?, ?> testBuilder)
+      throws Exception {
+    run(testBuilder.addKeepRules("-keep class !**$Foo*,**Bar { *; }"))
+        .inspect(
+            inspector -> {
+              assertThat(inspector.clazz(BarBar.class), isPresent());
+              assertThat(inspector.clazz(FooBar.class), not(isPresent()));
+              assertThat(inspector.clazz(A.class), not(isPresent()));
+              assertThat(inspector.clazz(B.class), not(isPresent()));
+              assertThat(inspector.clazz(C.class), not(isPresent()));
+              assertThat(inspector.clazz(D.class), not(isPresent()));
+            });
+  }
+
   private TestCompileResult<?, ?> run(TestShrinkerBuilder<?, ?, ?, ?, ?> testBuilder)
       throws Exception {
     return testBuilder
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/AssertUtils.java b/src/test/java/com/android/tools/r8/utils/codeinspector/AssertUtils.java
index c113b0c..87eb583 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/AssertUtils.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/AssertUtils.java
@@ -13,6 +13,16 @@
 
 public class AssertUtils {
 
+  public static <E extends Throwable> void assertFailsCompilation(ThrowingAction<E> action)
+      throws E {
+    assertFailsCompilationIf(true, action);
+  }
+
+  public static <E extends Throwable> void assertFailsCompilation(
+      ThrowingAction<E> action, Consumer<Throwable> consumer) throws E {
+    assertFailsCompilationIf(true, action, consumer);
+  }
+
   public static <E extends Throwable> void assertFailsCompilationIf(
       boolean condition, ThrowingAction<E> action) throws E {
     assertFailsCompilationIf(condition, action, null);
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/CodeMatchers.java b/src/test/java/com/android/tools/r8/utils/codeinspector/CodeMatchers.java
index 654f3a1..7059ae1 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/CodeMatchers.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/CodeMatchers.java
@@ -153,6 +153,10 @@
     return invokesMethod(null, null, name, null);
   }
 
+  public static Predicate<InstructionSubject> isInvokeWithTarget(MethodSubject target) {
+    return isInvokeWithTarget(target.getMethod().getReference());
+  }
+
   public static Predicate<InstructionSubject> isInvokeWithTarget(DexMethod target) {
     return instruction -> instruction.isInvoke() && instruction.getMethod() == target;
   }
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/EnumUnboxingInspector.java b/src/test/java/com/android/tools/r8/utils/codeinspector/EnumUnboxingInspector.java
index 2d41c1f..38f7e92 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/EnumUnboxingInspector.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/EnumUnboxingInspector.java
@@ -5,6 +5,7 @@
 package com.android.tools.r8.utils.codeinspector;
 
 import static com.android.tools.r8.TestBase.toDexType;
+import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 
 import com.android.tools.r8.graph.DexItemFactory;
@@ -25,6 +26,15 @@
     return this;
   }
 
+  public EnumUnboxingInspector assertUnboxedIf(boolean condition, Class<? extends Enum<?>> clazz) {
+    if (condition) {
+      assertUnboxed(clazz);
+    } else {
+      assertNotUnboxed(clazz);
+    }
+    return this;
+  }
+
   @SafeVarargs
   public final EnumUnboxingInspector assertUnboxed(Class<? extends Enum<?>>... classes) {
     for (Class<? extends Enum<?>> clazz : classes) {
@@ -32,4 +42,9 @@
     }
     return this;
   }
+
+  public EnumUnboxingInspector assertNotUnboxed(Class<? extends Enum<?>> clazz) {
+    assertFalse(unboxedEnums.isUnboxedEnum(toDexType(clazz, dexItemFactory)));
+    return this;
+  }
 }
diff --git a/third_party/opensource-apps/tivi.tar.gz.sha1 b/third_party/opensource-apps/tivi.tar.gz.sha1
index 6bd16e2..50f9741 100644
--- a/third_party/opensource-apps/tivi.tar.gz.sha1
+++ b/third_party/opensource-apps/tivi.tar.gz.sha1
@@ -1 +1 @@
-2f4adb11dcc8c56f377ee9945d47e88313bc5855
\ No newline at end of file
+cde92f3abe4e6a10a6c7ec865d6240c7b625a3a2
\ No newline at end of file
diff --git a/tools/compiledump.py b/tools/compiledump.py
index ebf3371..29fed2c 100755
--- a/tools/compiledump.py
+++ b/tools/compiledump.py
@@ -174,10 +174,10 @@
   dump_file = zipfile.ZipFile(os.path.abspath(args.dump), 'r')
   with utils.ChangedWorkingDirectory(temp):
     if args.override or not os.path.isfile(
-        os.path.join(temp, 'proguard.config')):
+        os.path.join(temp, 'r8-version')):
       print("Extracting into: %s" % temp)
       dump_file.extractall()
-      if not os.path.isfile(os.path.join(temp, 'proguard.config')):
+      if not os.path.isfile(os.path.join(temp, 'r8-version')):
         error("Did not extract into %s. Either the zip file is invalid or the "
               "dump is missing files" % temp)
     return Dump(temp)
diff --git a/tools/run_on_app.py b/tools/run_on_app.py
index 1bd62e3..983ce6d 100755
--- a/tools/run_on_app.py
+++ b/tools/run_on_app.py
@@ -11,12 +11,15 @@
 import sys
 import time
 
+import archive
+import as_utils
 import gmail_data
 import gmscore_data
 import golem
 import nest_data
 from sanitize_libraries import SanitizeLibraries, SanitizeLibrariesInPgconf
 import toolhelper
+import update_prebuilds_in_android
 import utils
 import youtube_data
 import chrome_data
@@ -49,6 +52,8 @@
                     help='Compiler build to use',
                     choices=COMPILER_BUILDS,
                     default='lib')
+  result.add_option('--hash',
+                    help='The version of D8/R8 to use')
   result.add_option('--app',
                     help='What app to run on',
                     choices=APPS)
@@ -186,8 +191,12 @@
                     help='Disable compiler logging',
                     default=False,
                     action='store_true')
-
-  return result.parse_args(argv)
+  (options, args) = result.parse_args(argv)
+  assert not options.hash or options.no_build, (
+      'Argument --no-build is required when using --hash')
+  assert not options.hash or options.compiler_build == 'full', (
+      'Compiler build lib not yet supported with --hash')
+  return (options, args)
 
 # Most apps have -printmapping, -printseeds, -printusage and
 # -printconfiguration in the Proguard configuration. However we don't
@@ -556,6 +565,16 @@
       and not os.path.exists(outdir):
     os.makedirs(outdir)
 
+  if options.hash:
+    # Download r8-<hash>.jar from
+    # https://storage.googleapis.com/r8-releases/raw/<hash>/.
+    download_path = archive.GetUploadDestination(options.hash, 'r8.jar', True)
+    assert utils.file_exists_on_cloud_storage(download_path), (
+        'Could not find r8.jar file from provided hash: %s' % options.hash)
+    destination = os.path.join(utils.LIBS, 'r8-' + options.hash + '.jar')
+    utils.download_file_from_cloud_storage(
+        download_path, destination, quiet=quiet)
+
   # Additional flags for the compiler from the configuration file.
   if 'flags' in values:
     args.extend(values['flags'].split(' '))
@@ -601,11 +620,16 @@
       build = not options.no_build and not options.golem
       stderr_path = os.path.join(temp, 'stderr')
       with open(stderr_path, 'w') as stderr:
+        jar = None
+        main = None
         if options.compiler_build == 'full':
           tool = options.compiler
         else:
           assert(options.compiler_build == 'lib')
           tool = 'r8lib-' + options.compiler
+        if options.hash:
+          jar = os.path.join(utils.LIBS, 'r8-' + options.hash + '.jar')
+          main = 'com.android.tools.r8.' + options.compiler.upper()
         exit_code = toolhelper.run(tool, args,
             build=build,
             debug=not options.no_debug,
@@ -617,7 +641,9 @@
             timeout=options.timeout,
             quiet=quiet,
             cmd_prefix=[
-                'taskset', '-c', options.cpu_list] if options.cpu_list else [])
+                'taskset', '-c', options.cpu_list] if options.cpu_list else [],
+            jar=jar,
+            main=main)
       if exit_code != 0:
         with open(stderr_path) as stderr:
           stderr_text = stderr.read()
diff --git a/tools/run_on_app_dump.py b/tools/run_on_app_dump.py
index 0d48887..1461181 100755
--- a/tools/run_on_app_dump.py
+++ b/tools/run_on_app_dump.py
@@ -317,7 +317,7 @@
     'dump_app': 'dump_app.zip',
     'apk_app': 'app-release.apk',
     'url': 'https://github.com/chrisbanes/tivi',
-    'revision': '8e2ddd8fe2d343264a66aa1ef8acbd4cc587e8ce',
+    'revision': '5c6d9ed338885c59b1fc64050d92d056417bb4de',
     'folder': 'tivi',
   }),
   App({
diff --git a/tools/toolhelper.py b/tools/toolhelper.py
index 1eee1f7..45d84e2 100644
--- a/tools/toolhelper.py
+++ b/tools/toolhelper.py
@@ -14,7 +14,7 @@
 def run(tool, args, build=None, debug=True,
         profile=False, track_memory_file=None, extra_args=None,
         stderr=None, stdout=None, return_stdout=False, timeout=0, quiet=False,
-        cmd_prefix=None):
+        cmd_prefix=None, jar=None, main=None):
   cmd = []
   if cmd_prefix:
     cmd.extend(cmd_prefix)
@@ -31,7 +31,9 @@
     cmd.append('-ea')
   if profile:
     cmd.append('-agentlib:hprof=cpu=samples,interval=1,depth=8')
-  if tool == 'r8lib-d8':
+  if jar:
+    cmd.extend(['-cp', jar, main])
+  elif tool == 'r8lib-d8':
     cmd.extend(['-cp', utils.R8LIB_JAR, 'com.android.tools.r8.D8'])
   elif tool == 'r8lib-r8':
     cmd.extend(['-cp', utils.R8LIB_JAR, 'com.android.tools.r8.R8'])
diff --git a/tools/update_prebuilds_in_android.py b/tools/update_prebuilds_in_android.py
index ee2b100..22c8863 100755
--- a/tools/update_prebuilds_in_android.py
+++ b/tools/update_prebuilds_in_android.py
@@ -70,20 +70,21 @@
 def copy_other_targets(root, target_root):
   copy_targets(root, target_root, OTHER_TARGETS, OTHER_TARGETS)
 
-def download_hash(root, commit_hash, target):
-  download_target(root, target, commit_hash, True)
+def download_hash(root, commit_hash, target, quiet=False):
+  download_target(root, target, commit_hash, True, quiet=quiet)
 
 def download_version(root, version, target):
   download_target(root, target, version, False)
 
-def download_target(root, target, hash_or_version, is_hash):
+def download_target(root, target, hash_or_version, is_hash, quiet=False):
   download_path = os.path.join(root, target)
   url = archive.GetUploadDestination(
     hash_or_version,
     target,
     is_hash)
-  print 'Downloading: ' + url + ' -> ' + download_path
-  utils.download_file_from_cloud_storage(url, download_path)
+  if not quiet:
+    print 'Downloading: ' + url + ' -> ' + download_path
+  utils.download_file_from_cloud_storage(url, download_path, quiet=quiet)
 
 def main_download(hash, maps, targets, target_root, version):
   jar_targets = JAR_TARGETS_MAP[targets]
diff --git a/tools/utils.py b/tools/utils.py
index 5396c0a..bed28d8 100644
--- a/tools/utils.py
+++ b/tools/utils.py
@@ -316,9 +316,9 @@
   PrintCmd(cmd)
   return subprocess.call(cmd) == 0
 
-def download_file_from_cloud_storage(source, destination):
+def download_file_from_cloud_storage(source, destination, quiet=False):
   cmd = ['gsutil.py', 'cp', source, destination]
-  PrintCmd(cmd)
+  PrintCmd(cmd, quiet=quiet)
   subprocess.check_call(cmd)
 
 def create_archive(name, sources=None):