Merge commit '9efb73abbd817be724dd0ec141618787460a1778' into 1.7.8-dev
diff --git a/.gitignore b/.gitignore
index 4062928..b74632a 100644
--- a/.gitignore
+++ b/.gitignore
@@ -103,6 +103,8 @@
 third_party/rhino-1.7.10.tar.gz
 third_party/rhino-android-1.1.1
 third_party/rhino-android-1.1.1.tar.gz
+third_party/iosched_2019
+third_party/iosched_2019.tar.gz
 third_party/android_cts_baseline
 third_party/android_cts_baseline.tar.gz
 third_party/framework
diff --git a/build.gradle b/build.gradle
index 15d8deb..89e6798 100644
--- a/build.gradle
+++ b/build.gradle
@@ -304,6 +304,7 @@
                 "ddmlib",
                 "gradle/gradle",
                 "google-java-format",
+                "iosched_2019",
                 "jacoco",
                 "jasmin",
                 "jctf",
diff --git a/src/main/java/com/android/tools/r8/BaseCompilerCommand.java b/src/main/java/com/android/tools/r8/BaseCompilerCommand.java
index 9eccf41..b0d5055 100644
--- a/src/main/java/com/android/tools/r8/BaseCompilerCommand.java
+++ b/src/main/java/com/android/tools/r8/BaseCompilerCommand.java
@@ -13,7 +13,6 @@
 import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.FileUtils;
 import com.android.tools.r8.utils.Reporter;
-import com.android.tools.r8.utils.StringDiagnostic;
 import java.nio.file.Path;
 import java.util.ArrayList;
 import java.util.List;
@@ -509,10 +508,6 @@
         }
         reporter.error(builder.toString());
       }
-      if (hasDesugaredLibraryConfiguration()) {
-        reporter.warning(
-            new StringDiagnostic("Desugared library configuration is still work in progress"));
-      }
       super.validate();
     }
   }
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java
index a7d57b1..c56ea8a 100644
--- a/src/main/java/com/android/tools/r8/R8.java
+++ b/src/main/java/com/android/tools/r8/R8.java
@@ -821,7 +821,7 @@
     if (whyAreYouKeepingConsumer != null) {
       for (DexReference reference : rootSet.reasonAsked) {
         whyAreYouKeepingConsumer.printWhyAreYouKeeping(
-            enqueuer.getGraphNode(reference), System.out);
+            enqueuer.getGraphReporter().getGraphNode(reference), System.out);
       }
     }
     if (rootSet.checkDiscarded.isEmpty()
@@ -851,7 +851,8 @@
       if (!failed.isEmpty()) {
         ByteArrayOutputStream baos = new ByteArrayOutputStream();
         whyAreYouKeepingConsumer.printWhyAreYouKeeping(
-            enqueuer.getGraphNode(definition.toReference()), new PrintStream(baos));
+            enqueuer.getGraphReporter().getGraphNode(definition.toReference()),
+            new PrintStream(baos));
         options.reporter.info(
             new StringDiagnostic(
                 "Item " + definition.toSourceString() + " was not discarded.\n" + baos.toString()));
diff --git a/src/main/java/com/android/tools/r8/R8Command.java b/src/main/java/com/android/tools/r8/R8Command.java
index ae68adb..f4b3763 100644
--- a/src/main/java/com/android/tools/r8/R8Command.java
+++ b/src/main/java/com/android/tools/r8/R8Command.java
@@ -101,7 +101,8 @@
     private final List<FeatureSplit> featureSplits = new ArrayList<>();
 
     private boolean allowPartiallyImplementedProguardOptions = false;
-    private boolean allowTestProguardOptions = false;
+    private boolean allowTestProguardOptions =
+        System.getProperty("com.android.tools.r8.allowTestProguardOptions") != null;
 
     // TODO(zerny): Consider refactoring CompatProguardCommandBuilder to avoid subclassing.
     Builder() {
diff --git a/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java b/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java
index d7f3861..ad763d8 100644
--- a/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java
+++ b/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java
@@ -37,6 +37,7 @@
 import com.android.tools.r8.graph.InnerClassAttribute;
 import com.android.tools.r8.graph.ObjectToOffsetMapping;
 import com.android.tools.r8.graph.ParameterAnnotationsList;
+import com.android.tools.r8.ir.desugar.DesugaredLibraryWrapperSynthesizer;
 import com.android.tools.r8.naming.ClassNameMapper;
 import com.android.tools.r8.naming.NamingLens;
 import com.android.tools.r8.naming.ProguardMapSupplier;
@@ -218,8 +219,18 @@
         if (inputChecksums.containsKey(clazz.getType().descriptor.toASCIIString())) {
           continue;
         } else {
-          throw new CompilationError(clazz + " from " + clazz.origin +
-              " has no checksum information while checksum encoding is requested");
+          String name = clazz.toSourceString();
+          if (name.contains(DesugaredLibraryWrapperSynthesizer.TYPE_WRAPPER_SUFFIX)
+              || name.contains(DesugaredLibraryWrapperSynthesizer.VIVIFIED_TYPE_WRAPPER_SUFFIX)) {
+            synthesizedChecksums.put(
+                clazz.getType().descriptor.toASCIIString(), (long) name.hashCode());
+          } else {
+            throw new CompilationError(
+                clazz
+                    + " from "
+                    + clazz.origin
+                    + " has no checksum information while checksum encoding is requested");
+          }
         }
       }
 
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 dfe6a37..f34e2bc 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
@@ -363,11 +363,6 @@
       AppInfoWithSubtyping appInfo,
       WhyAreYouNotInliningReporter whyAreYouNotInliningReporter) {
     checkIfObsolete();
-    if (isClassInitializer()) {
-      // This will probably never happen but never inline a class initializer.
-      whyAreYouNotInliningReporter.reportUnknownReason();
-      return false;
-    }
 
     if (inliningReason == Reason.FORCE) {
       // Make sure we would be able to inline this normally.
@@ -389,33 +384,36 @@
         if (appInfo.isSubtype(containerType, method.holder)) {
           return true;
         }
-        whyAreYouNotInliningReporter.reportUnknownReason();
+        whyAreYouNotInliningReporter.reportCallerNotSubtype();
         return false;
 
       case PROCESSED_INLINING_CANDIDATE_SAME_PACKAGE:
         if (containerType.isSamePackage(method.holder)) {
           return true;
         }
-        whyAreYouNotInliningReporter.reportUnknownReason();
+        whyAreYouNotInliningReporter.reportCallerNotSamePackage();
         return false;
 
       case PROCESSED_INLINING_CANDIDATE_SAME_NEST:
         if (NestUtils.sameNest(containerType, method.holder, appInfo)) {
           return true;
         }
-        whyAreYouNotInliningReporter.reportUnknownReason();
+        whyAreYouNotInliningReporter.reportCallerNotSameNest();
         return false;
 
       case PROCESSED_INLINING_CANDIDATE_SAME_CLASS:
         if (containerType == method.holder) {
           return true;
         }
-        whyAreYouNotInliningReporter.reportUnknownReason();
+        whyAreYouNotInliningReporter.reportCallerNotSameClass();
         return false;
 
       case PROCESSED_NOT_INLINING_CANDIDATE:
+        whyAreYouNotInliningReporter.reportInlineeNotInliningCandidate();
+        return false;
+
       case NOT_PROCESSED:
-        whyAreYouNotInliningReporter.reportUnknownReason();
+        whyAreYouNotInliningReporter.reportInlineeNotProcessed();
         return false;
 
       default:
diff --git a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
index 3bdaf06..0cc264a 100644
--- a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
+++ b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
@@ -156,6 +156,7 @@
   public final DexString compareToIgnoreCaseMethodName = createString("compareToIgnoreCase");
   public final DexString cloneMethodName = createString("clone");
   public final DexString substringName = createString("substring");
+  public final DexString trimName = createString("trim");
 
   public final DexString valueOfMethodName = createString("valueOf");
   public final DexString toStringMethodName = createString("toString");
@@ -882,6 +883,8 @@
     public final DexMethod toString;
     public final DexMethod intern;
 
+    public final DexMethod trim = createMethod(stringType, createProto(stringType), trimName);
+
     private StringMethods() {
       isEmpty = createMethod(
           stringDescriptor, isEmptyMethodName, booleanDescriptor, DexString.EMPTY_ARRAY);
diff --git a/src/main/java/com/android/tools/r8/graph/JarClassFileReader.java b/src/main/java/com/android/tools/r8/graph/JarClassFileReader.java
index 3ba63c8..0fcb5b6 100644
--- a/src/main/java/com/android/tools/r8/graph/JarClassFileReader.java
+++ b/src/main/java/com/android/tools/r8/graph/JarClassFileReader.java
@@ -456,7 +456,7 @@
         clazz.asProgramClass().setInitialClassFileVersion(version);
         if (application.options.encodeChecksums) {
           CRC32 crc = new CRC32();
-          crc.update(this.context.classCache);
+          crc.update(this.context.classCache, 0, this.context.classCache.length);
           checksums.addChecksum(type.descriptor.toASCIIString(), crc.getValue());
         }
       }
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 7ad4c5b..2b55be8 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
@@ -9,12 +9,11 @@
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.shaking.Enqueuer;
 import com.android.tools.r8.shaking.EnqueuerWorklist;
-import com.android.tools.r8.shaking.KeepReason;
 
 public abstract class EnqueuerAnalysis {
 
   /** Called when a class is found to be instantiated. */
-  public void processNewlyInstantiatedClass(DexProgramClass clazz, KeepReason reason) {}
+  public void processNewlyInstantiatedClass(DexProgramClass clazz, DexEncodedMethod context) {}
 
   /** Called when a field is found to be live. */
   public void processNewlyLiveField(DexEncodedField field) {}
diff --git a/src/main/java/com/android/tools/r8/graph/analysis/InitializedClassesInInstanceMethodsAnalysis.java b/src/main/java/com/android/tools/r8/graph/analysis/InitializedClassesInInstanceMethodsAnalysis.java
index 54b03a6..e7c6418 100644
--- a/src/main/java/com/android/tools/r8/graph/analysis/InitializedClassesInInstanceMethodsAnalysis.java
+++ b/src/main/java/com/android/tools/r8/graph/analysis/InitializedClassesInInstanceMethodsAnalysis.java
@@ -7,9 +7,9 @@
 import com.android.tools.r8.graph.AppInfoWithSubtyping;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.shaking.KeepReason;
 import java.util.IdentityHashMap;
 import java.util.Map;
 
@@ -61,10 +61,10 @@
   }
 
   @Override
-  public void processNewlyInstantiatedClass(DexProgramClass clazz, KeepReason reason) {
+  public void processNewlyInstantiatedClass(DexProgramClass clazz, DexEncodedMethod context) {
     DexType key = clazz.type;
     DexType objectType = appView.dexItemFactory().objectType;
-    if (!reason.isInstantiatedIn()) {
+    if (context == null) {
       // Record that we don't know anything about the set of classes that are guaranteed to be
       // initialized in the instance methods of `clazz`.
       mapping.put(key, objectType);
@@ -73,7 +73,7 @@
 
     // Record that the enclosing class is guaranteed to be initialized at the allocation site.
     AppInfoWithSubtyping appInfo = appView.appInfo();
-    DexType guaranteedToBeInitialized = reason.asInstantiatedIn().getMethod().holder;
+    DexType guaranteedToBeInitialized = context.method.holder;
     DexType existingGuaranteedToBeInitialized =
         mapping.getOrDefault(key, guaranteedToBeInitialized);
     mapping.put(
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeMethod.java b/src/main/java/com/android/tools/r8/ir/code/InvokeMethod.java
index e5e86fe..b790829 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokeMethod.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokeMethod.java
@@ -13,8 +13,9 @@
 import com.android.tools.r8.ir.analysis.ClassInitializationAnalysis;
 import com.android.tools.r8.ir.analysis.fieldvalueanalysis.AbstractFieldSet;
 import com.android.tools.r8.ir.analysis.modeling.LibraryMethodReadSetModeling;
+import com.android.tools.r8.ir.optimize.DefaultInliningOracle;
 import com.android.tools.r8.ir.optimize.Inliner.InlineAction;
-import com.android.tools.r8.ir.optimize.InliningOracle;
+import com.android.tools.r8.ir.optimize.Inliner.Reason;
 import com.android.tools.r8.ir.optimize.inliner.WhyAreYouNotInliningReporter;
 import com.android.tools.r8.ir.regalloc.RegisterAllocator;
 import java.util.Collection;
@@ -68,8 +69,8 @@
 
   public abstract InlineAction computeInlining(
       DexEncodedMethod singleTarget,
-      InliningOracle decider,
-      DexMethod invocationContext,
+      Reason reason,
+      DefaultInliningOracle decider,
       ClassInitializationAnalysis classInitializationAnalysis,
       WhyAreYouNotInliningReporter whyAreYouNotInliningReporter);
 
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeMethodWithReceiver.java b/src/main/java/com/android/tools/r8/ir/code/InvokeMethodWithReceiver.java
index 7276e77..aea5a70 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokeMethodWithReceiver.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokeMethodWithReceiver.java
@@ -13,8 +13,9 @@
 import com.android.tools.r8.ir.analysis.type.ClassTypeLatticeElement;
 import com.android.tools.r8.ir.analysis.type.TypeAnalysis;
 import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.optimize.DefaultInliningOracle;
 import com.android.tools.r8.ir.optimize.Inliner.InlineAction;
-import com.android.tools.r8.ir.optimize.InliningOracle;
+import com.android.tools.r8.ir.optimize.Inliner.Reason;
 import com.android.tools.r8.ir.optimize.inliner.WhyAreYouNotInliningReporter;
 import java.util.List;
 
@@ -42,12 +43,12 @@
   @Override
   public final InlineAction computeInlining(
       DexEncodedMethod singleTarget,
-      InliningOracle decider,
-      DexMethod invocationContext,
+      Reason reason,
+      DefaultInliningOracle decider,
       ClassInitializationAnalysis classInitializationAnalysis,
       WhyAreYouNotInliningReporter whyAreYouNotInliningReporter) {
     return decider.computeForInvokeWithReceiver(
-        this, singleTarget, invocationContext, whyAreYouNotInliningReporter);
+        this, singleTarget, reason, whyAreYouNotInliningReporter);
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokePolymorphic.java b/src/main/java/com/android/tools/r8/ir/code/InvokePolymorphic.java
index 3379b61..f7b8688 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokePolymorphic.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokePolymorphic.java
@@ -16,10 +16,11 @@
 import com.android.tools.r8.ir.analysis.ClassInitializationAnalysis;
 import com.android.tools.r8.ir.conversion.CfBuilder;
 import com.android.tools.r8.ir.conversion.DexBuilder;
+import com.android.tools.r8.ir.optimize.DefaultInliningOracle;
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
 import com.android.tools.r8.ir.optimize.Inliner.InlineAction;
+import com.android.tools.r8.ir.optimize.Inliner.Reason;
 import com.android.tools.r8.ir.optimize.InliningConstraints;
-import com.android.tools.r8.ir.optimize.InliningOracle;
 import com.android.tools.r8.ir.optimize.inliner.WhyAreYouNotInliningReporter;
 import java.util.Collection;
 import java.util.List;
@@ -139,8 +140,8 @@
   @Override
   public InlineAction computeInlining(
       DexEncodedMethod singleTarget,
-      InliningOracle decider,
-      DexMethod invocationContext,
+      Reason reason,
+      DefaultInliningOracle decider,
       ClassInitializationAnalysis classInitializationAnalysis,
       WhyAreYouNotInliningReporter whyAreYouNotInliningReporter) {
     // We never determine a single target for invoke-polymorphic.
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeStatic.java b/src/main/java/com/android/tools/r8/ir/code/InvokeStatic.java
index 078b220..acd864e 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokeStatic.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokeStatic.java
@@ -18,10 +18,11 @@
 import com.android.tools.r8.ir.analysis.ClassInitializationAnalysis.Query;
 import com.android.tools.r8.ir.conversion.CfBuilder;
 import com.android.tools.r8.ir.conversion.DexBuilder;
+import com.android.tools.r8.ir.optimize.DefaultInliningOracle;
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
 import com.android.tools.r8.ir.optimize.Inliner.InlineAction;
+import com.android.tools.r8.ir.optimize.Inliner.Reason;
 import com.android.tools.r8.ir.optimize.InliningConstraints;
-import com.android.tools.r8.ir.optimize.InliningOracle;
 import com.android.tools.r8.ir.optimize.inliner.WhyAreYouNotInliningReporter;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import java.util.Collection;
@@ -141,16 +142,12 @@
   @Override
   public InlineAction computeInlining(
       DexEncodedMethod singleTarget,
-      InliningOracle decider,
-      DexMethod invocationContext,
+      Reason reason,
+      DefaultInliningOracle decider,
       ClassInitializationAnalysis classInitializationAnalysis,
       WhyAreYouNotInliningReporter whyAreYouNotInliningReporter) {
     return decider.computeForInvokeStatic(
-        this,
-        singleTarget,
-        invocationContext,
-        classInitializationAnalysis,
-        whyAreYouNotInliningReporter);
+        this, singleTarget, reason, classInitializationAnalysis, whyAreYouNotInliningReporter);
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/BackportedMethodRewriter.java b/src/main/java/com/android/tools/r8/ir/desugar/BackportedMethodRewriter.java
index f6f34e6..be6ffb2 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/BackportedMethodRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/BackportedMethodRewriter.java
@@ -1304,15 +1304,15 @@
       DexItemFactory factory = appView.dexItemFactory();
       String unqualifiedName = method.holder.getName();
       // Avoid duplicate class names between core lib dex file and program dex files.
-      String coreLibUtilitySuffix =
-          appView.options().isDesugaredLibraryCompilation() ? "$corelib" : "";
+      String desugaredLibUtilitySuffix =
+          appView.options().isDesugaredLibraryCompilation() ? "$desugaredLib" : "";
       String descriptor =
           UTILITY_CLASS_DESCRIPTOR_PREFIX
               + '$'
               + unqualifiedName
               + '$'
               + method.proto.parameters.size()
-              + coreLibUtilitySuffix
+              + desugaredLibUtilitySuffix
               + '$'
               + methodName
               + ';';
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/D8NestBasedAccessDesugaring.java b/src/main/java/com/android/tools/r8/ir/desugar/D8NestBasedAccessDesugaring.java
index 95a70df..1c2e894 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/D8NestBasedAccessDesugaring.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/D8NestBasedAccessDesugaring.java
@@ -144,7 +144,7 @@
       throws ExecutionException {
     processNestsConcurrently(executorService);
     addDeferredBridges();
-    synthetizeNestConstructor(builder);
+    synthesizeNestConstructor(builder);
     optimizeDeferredBridgesConcurrently(executorService, converter);
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryAPIConverter.java b/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryAPIConverter.java
index b9a758a..f0eda16 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryAPIConverter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryAPIConverter.java
@@ -15,6 +15,7 @@
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.ir.analysis.type.Nullability;
 import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.code.BasicBlock;
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.Instruction;
 import com.android.tools.r8.ir.code.InstructionListIterator;
@@ -31,9 +32,12 @@
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.HashMap;
+import java.util.HashSet;
 import java.util.LinkedList;
 import java.util.List;
+import java.util.ListIterator;
 import java.util.Map;
+import java.util.Set;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.ExecutorService;
 
@@ -59,7 +63,7 @@
   private final AppView<?> appView;
   private final DexItemFactory factory;
   private final DesugaredLibraryWrapperSynthesizer wrapperSynthesizor;
-  private final Map<DexClass, List<DexEncodedMethod>> callBackMethods = new HashMap<>();
+  private final Map<DexClass, Set<DexEncodedMethod>> callBackMethods = new HashMap<>();
 
   public DesugaredLibraryAPIConverter(AppView<?> appView) {
     this.appView = appView;
@@ -75,27 +79,31 @@
 
     generateCallBackIfNeeded(code);
 
-    InstructionListIterator iterator = code.instructionListIterator();
-    while (iterator.hasNext()) {
-      Instruction instruction = iterator.next();
-      if (!instruction.isInvokeMethod()) {
-        continue;
-      }
-      InvokeMethod invokeMethod = instruction.asInvokeMethod();
-      DexMethod invokedMethod = invokeMethod.getInvokedMethod();
-      // Rewritting is required only on calls to library methods which are not desugared.
-      if (appView.rewritePrefix.hasRewrittenType(invokedMethod.holder)
-          || invokedMethod.holder.isArrayType()) {
-        continue;
-      }
-      DexClass dexClass = appView.definitionFor(invokedMethod.holder);
-      if (dexClass == null || !dexClass.isLibraryClass()) {
-        continue;
-      }
-      // Library methods do not understand desugared types, hence desugared types have to be
-      // converted around non desugared library calls for the invoke to resolve.
-      if (appView.rewritePrefix.hasRewrittenTypeInSignature(invokedMethod.proto)) {
-        rewriteLibraryInvoke(code, invokeMethod, iterator);
+    ListIterator<BasicBlock> blockIterator = code.listIterator();
+    while (blockIterator.hasNext()) {
+      BasicBlock block = blockIterator.next();
+      InstructionListIterator iterator = block.listIterator(code);
+      while (iterator.hasNext()) {
+        Instruction instruction = iterator.next();
+        if (!instruction.isInvokeMethod()) {
+          continue;
+        }
+        InvokeMethod invokeMethod = instruction.asInvokeMethod();
+        DexMethod invokedMethod = invokeMethod.getInvokedMethod();
+        // Rewriting is required only on calls to library methods which are not desugared.
+        if (appView.rewritePrefix.hasRewrittenType(invokedMethod.holder)
+            || invokedMethod.holder.isArrayType()) {
+          continue;
+        }
+        DexClass dexClass = appView.definitionFor(invokedMethod.holder);
+        if (dexClass == null || !dexClass.isLibraryClass()) {
+          continue;
+        }
+        // Library methods do not understand desugared types, hence desugared types have to be
+        // converted around non desugared library calls for the invoke to resolve.
+        if (appView.rewritePrefix.hasRewrittenTypeInSignature(invokedMethod.proto)) {
+          rewriteLibraryInvoke(code, invokeMethod, iterator, blockIterator);
+        }
       }
     }
   }
@@ -158,7 +166,7 @@
 
   private synchronized void generateCallBack(DexClass dexClass, DexEncodedMethod originalMethod) {
     DexMethod methodToInstall =
-        methodWithVivifiedTypeInSignature(originalMethod.method, dexClass.type);
+        methodWithVivifiedTypeInSignature(originalMethod.method, dexClass.type, appView);
     CfCode cfCode =
         new APIConverterWrapperCfCodeProvider(
                 appView, originalMethod.method, null, this, dexClass.isInterface())
@@ -170,27 +178,27 @@
   }
 
   private synchronized void addCallBackSignature(DexClass dexClass, DexEncodedMethod method) {
-    callBackMethods.putIfAbsent(dexClass, new ArrayList<>());
-    List<DexEncodedMethod> dexEncodedMethods = callBackMethods.get(dexClass);
-    dexEncodedMethods.add(method);
+    callBackMethods.putIfAbsent(dexClass, new HashSet<>());
+    callBackMethods.get(dexClass).add(method);
   }
 
-  DexMethod methodWithVivifiedTypeInSignature(DexMethod originalMethod, DexType holder) {
+  public static DexMethod methodWithVivifiedTypeInSignature(
+      DexMethod originalMethod, DexType holder, AppView<?> appView) {
     DexType[] newParameters = originalMethod.proto.parameters.values.clone();
     int index = 0;
     for (DexType param : originalMethod.proto.parameters.values) {
       if (appView.rewritePrefix.hasRewrittenType(param)) {
-        newParameters[index] = this.vivifiedTypeFor(param);
+        newParameters[index] = vivifiedTypeFor(param, appView);
       }
       index++;
     }
     DexType returnType = originalMethod.proto.returnType;
     DexType newReturnType =
         appView.rewritePrefix.hasRewrittenType(returnType)
-            ? this.vivifiedTypeFor(returnType)
+            ? vivifiedTypeFor(returnType, appView)
             : returnType;
-    DexProto newProto = factory.createProto(newReturnType, newParameters);
-    return factory.createMethod(holder, newProto, originalMethod.name);
+    DexProto newProto = appView.dexItemFactory().createProto(newReturnType, newParameters);
+    return appView.dexItemFactory().createMethod(holder, newProto, originalMethod.name);
   }
 
   public void generateWrappers(
@@ -198,8 +206,7 @@
       throws ExecutionException {
     wrapperSynthesizor.finalizeWrappers(builder, irConverter, executorService);
     for (DexClass dexClass : callBackMethods.keySet()) {
-      // TODO(b/134732760): add the methods in the root set.
-      List<DexEncodedMethod> dexEncodedMethods = callBackMethods.get(dexClass);
+      Set<DexEncodedMethod> dexEncodedMethods = callBackMethods.get(dexClass);
       dexClass.appendVirtualMethods(dexEncodedMethods);
       irConverter.optimizeSynthesizedMethodsConcurrently(dexEncodedMethods, executorService);
     }
@@ -223,15 +230,20 @@
                     + " is a desugared type)."));
   }
 
-  public DexType vivifiedTypeFor(DexType type) {
+  public static DexType vivifiedTypeFor(DexType type, AppView<?> appView) {
     DexType vivifiedType =
-        factory.createType(DescriptorUtils.javaTypeToDescriptor(VIVIFIED_PREFIX + type.toString()));
+        appView
+            .dexItemFactory()
+            .createType(DescriptorUtils.javaTypeToDescriptor(VIVIFIED_PREFIX + type.toString()));
     appView.rewritePrefix.rewriteType(vivifiedType, type);
     return vivifiedType;
   }
 
   private void rewriteLibraryInvoke(
-      IRCode code, InvokeMethod invokeMethod, InstructionListIterator iterator) {
+      IRCode code,
+      InvokeMethod invokeMethod,
+      InstructionListIterator iterator,
+      ListIterator<BasicBlock> blockIterator) {
     DexMethod invokedMethod = invokeMethod.getInvokedMethod();
 
     // Create return conversion if required.
@@ -240,7 +252,7 @@
     DexType returnType = invokedMethod.proto.returnType;
     if (appView.rewritePrefix.hasRewrittenType(returnType)) {
       if (canConvert(returnType)) {
-        newReturnType = vivifiedTypeFor(returnType);
+        newReturnType = vivifiedTypeFor(returnType, appView);
         // Return conversion added only if return value is used.
         if (invokeMethod.outValue() != null
             && invokeMethod.outValue().numberOfUsers() + invokeMethod.outValue().numberOfPhiUsers()
@@ -270,7 +282,7 @@
       DexType argType = parameters[i];
       if (appView.rewritePrefix.hasRewrittenType(argType)) {
         if (canConvert(argType)) {
-          DexType argVivifiedType = vivifiedTypeFor(argType);
+          DexType argVivifiedType = vivifiedTypeFor(argType, appView);
           Value inValue = invokeMethod.inValues().get(i + receiverShift);
           newParameters[i] = argVivifiedType;
           parameterConversions.add(
@@ -296,6 +308,8 @@
             newDexMethod.proto,
             invokeMethod.outValue(),
             newInValues);
+    assert newDexMethod
+        == methodWithVivifiedTypeInSignature(invokedMethod, invokedMethod.holder, appView);
 
     // Insert and reschedule all instructions.
     iterator.previous();
@@ -310,6 +324,41 @@
       returnConversion.setPosition(invokeMethod.getPosition());
       iterator.add(returnConversion);
     }
+
+    // If the invoke is in a try-catch, since all conversions can throw, the basic block needs
+    // to be split in between each invoke...
+    if (newInvokeMethod.getBlock().hasCatchHandlers()) {
+      splitIfCatchHandlers(code, newInvokeMethod.getBlock(), blockIterator);
+    }
+  }
+
+  private void splitIfCatchHandlers(
+      IRCode code,
+      BasicBlock blockWithIncorrectThrowingInstructions,
+      ListIterator<BasicBlock> blockIterator) {
+    InstructionListIterator instructionsIterator =
+        blockWithIncorrectThrowingInstructions.listIterator(code);
+    BasicBlock currentBlock = blockWithIncorrectThrowingInstructions;
+    while (currentBlock != null && instructionsIterator.hasNext()) {
+      Instruction throwingInstruction =
+          instructionsIterator.nextUntil(Instruction::instructionTypeCanThrow);
+      BasicBlock nextBlock;
+      if (throwingInstruction != null) {
+        nextBlock = instructionsIterator.split(code, blockIterator);
+        // Back up to before the split before inserting catch handlers.
+        blockIterator.previous();
+        nextBlock.copyCatchHandlers(code, blockIterator, currentBlock, appView.options());
+        BasicBlock b = blockIterator.next();
+        assert b == nextBlock;
+        // Switch iteration to the split block.
+        instructionsIterator = nextBlock.listIterator(code);
+        currentBlock = nextBlock;
+      } else {
+        assert !instructionsIterator.hasNext();
+        instructionsIterator = null;
+        currentBlock = null;
+      }
+    }
   }
 
   private Instruction createParameterConversion(
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryWrapperSynthesizer.java b/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryWrapperSynthesizer.java
index a9c70ca..95db2cf 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryWrapperSynthesizer.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryWrapperSynthesizer.java
@@ -94,6 +94,7 @@
 //   }
 public class DesugaredLibraryWrapperSynthesizer {
 
+  public static final String WRAPPER_PREFIX = "$r8$wrapper$";
   public static final String TYPE_WRAPPER_SUFFIX = "$-WRP";
   public static final String VIVIFIED_TYPE_WRAPPER_SUFFIX = "$-V-WRP";
 
@@ -111,14 +112,13 @@
   private final DexItemFactory factory;
   private final DesugaredLibraryAPIConverter converter;
 
-  public DesugaredLibraryWrapperSynthesizer(
-      AppView<?> appView, DesugaredLibraryAPIConverter converter) {
+  DesugaredLibraryWrapperSynthesizer(AppView<?> appView, DesugaredLibraryAPIConverter converter) {
     this.appView = appView;
     this.factory = appView.dexItemFactory();
     this.converter = converter;
   }
 
-  public boolean hasSynthesized(DexType type) {
+  boolean hasSynthesized(DexType type) {
     return generatedWrappers.contains(type);
   }
 
@@ -126,7 +126,7 @@
   // 1. Generate wrappers without conversion methods.
   // 2. Compute wrapper types.
 
-  public boolean canGenerateWrapper(DexType type) {
+  boolean canGenerateWrapper(DexType type) {
     DexClass dexClass = appView.definitionFor(type);
     if (dexClass == null) {
       return false;
@@ -134,11 +134,11 @@
     return dexClass.isLibraryClass();
   }
 
-  public DexType getTypeWrapper(DexType type) {
+  DexType getTypeWrapper(DexType type) {
     return getWrapper(type, TYPE_WRAPPER_SUFFIX, typeWrappers, this::generateTypeWrapper);
   }
 
-  public DexType getVivifiedTypeWrapper(DexType type) {
+  DexType getVivifiedTypeWrapper(DexType type) {
     return getWrapper(
         type,
         VIVIFIED_TYPE_WRAPPER_SUFFIX,
@@ -146,6 +146,12 @@
         this::generateVivifiedTypeWrapper);
   }
 
+  private DexType createWrapperType(DexType type, String suffix) {
+    return factory.createType(
+        DescriptorUtils.javaTypeToDescriptor(
+            WRAPPER_PREFIX + type.toString().replace('.', '$') + suffix));
+  }
+
   private DexType getWrapper(
       DexType type,
       String suffix,
@@ -163,9 +169,7 @@
             type,
             t -> {
               toGenerate.set(true);
-              DexType wrapperType =
-                  factory.createType(
-                      DescriptorUtils.javaTypeToDescriptor(type.toString() + suffix));
+              DexType wrapperType = createWrapperType(type, suffix);
               generatedWrappers.add(wrapperType);
               return new Pair<>(wrapperType, null);
             });
@@ -189,21 +193,25 @@
     return pair.getFirst();
   }
 
-  public DexProgramClass generateTypeWrapper(DexClass dexClass, DexType typeWrapperType) {
+  private DexType vivifiedTypeFor(DexType type) {
+    return DesugaredLibraryAPIConverter.vivifiedTypeFor(type, appView);
+  }
+
+  private DexProgramClass generateTypeWrapper(DexClass dexClass, DexType typeWrapperType) {
     DexType type = dexClass.type;
     DexEncodedField wrapperField = synthesizeWrappedValueField(typeWrapperType, type);
     return synthesizeWrapper(
-        converter.vivifiedTypeFor(type),
+        vivifiedTypeFor(type),
         dexClass,
         synthesizeVirtualMethodsForTypeWrapper(dexClass.asLibraryClass(), wrapperField),
         wrapperField);
   }
 
-  public DexProgramClass generateVivifiedTypeWrapper(
+  private DexProgramClass generateVivifiedTypeWrapper(
       DexClass dexClass, DexType vivifiedTypeWrapperType) {
     DexType type = dexClass.type;
     DexEncodedField wrapperField =
-        synthesizeWrappedValueField(vivifiedTypeWrapperType, converter.vivifiedTypeFor(type));
+        synthesizeWrappedValueField(vivifiedTypeWrapperType, vivifiedTypeFor(type));
     return synthesizeWrapper(
         type,
         dexClass,
@@ -307,8 +315,8 @@
       DexClass holderClass = appView.definitionFor(dexEncodedMethod.method.holder);
       assert holderClass != null;
       DexMethod methodToInstall =
-          converter.methodWithVivifiedTypeInSignature(
-              dexEncodedMethod.method, wrapperField.field.holder);
+          DesugaredLibraryAPIConverter.methodWithVivifiedTypeInSignature(
+              dexEncodedMethod.method, wrapperField.field.holder, appView);
       CfCode cfCode;
       if (dexEncodedMethod.isFinal()) {
         invalidWrappers.add(wrapperField.field.holder);
@@ -347,7 +355,7 @@
     appView
         .options()
         .reporter
-        .warning(
+        .info(
             new StringDiagnostic(
                 "Desugared library API conversion: cannot wrap final methods "
                     + Arrays.toString(methodArray)
@@ -382,8 +390,7 @@
           // This looks quadratic but given the size of the collections met in practice for
           // desugared libraries (Max ~15) it does not matter.
           for (DexEncodedMethod alreadyImplementedMethod : implementedMethods) {
-            if (alreadyImplementedMethod.method.proto == virtualMethod.method.proto
-                && alreadyImplementedMethod.method.name == virtualMethod.method.name) {
+            if (alreadyImplementedMethod.method.match(virtualMethod.method)) {
               alreadyAdded = true;
               continue;
             }
@@ -409,7 +416,7 @@
       appView
           .options()
           .reporter
-          .warning(
+          .info(
               new StringDiagnostic(
                   "Desugared library API conversion: Generating a large wrapper for "
                       + libraryClass.type
@@ -455,7 +462,7 @@
   // 2. Add the synthesized classes.
   // 3. Process all methods.
 
-  public void finalizeWrappers(
+  void finalizeWrappers(
       DexApplication.Builder<?> builder, IRConverter irConverter, ExecutorService executorService)
       throws ExecutionException {
     finalizeWrappers(
@@ -509,7 +516,7 @@
             synthesizedClass.type,
             type,
             type,
-            converter.vivifiedTypeFor(type),
+            vivifiedTypeFor(type),
             reverse == null ? null : reverse.getSecond()));
   }
 
@@ -519,7 +526,7 @@
         synthesizeConversionMethod(
             synthesizedClass.type,
             type,
-            converter.vivifiedTypeFor(type),
+            vivifiedTypeFor(type),
             type,
             reverse == null ? null : reverse.getSecond()));
   }
@@ -541,7 +548,7 @@
                   factory.createString(
                       "Unsupported conversion for "
                           + type
-                          + ". See compilation time warnings for more infos."),
+                          + ". See compilation time infos for more details."),
                   holder)
               .generateCfCode();
     } else {
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/InterfaceMethodRewriter.java b/src/main/java/com/android/tools/r8/ir/desugar/InterfaceMethodRewriter.java
index db8f582..b79a316 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/InterfaceMethodRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/InterfaceMethodRewriter.java
@@ -560,7 +560,7 @@
         warnMissingEmulatedInterface(interfaceType);
       } else if (theInterface.isProgramClass()) {
         DexProgramClass synthesizedClass =
-            synthetizeEmulateInterfaceLibraryClass(
+            synthesizeEmulateInterfaceLibraryClass(
                 theInterface.asProgramClass(), emulatedInterfacesHierarchy);
         if (synthesizedClass != null) {
           builder.addSynthesizedClass(synthesizedClass, isInMainDexList(interfaceType));
@@ -631,7 +631,7 @@
         factory.createString(method.name.toString()));
   }
 
-  private DexProgramClass synthetizeEmulateInterfaceLibraryClass(
+  private DexProgramClass synthesizeEmulateInterfaceLibraryClass(
       DexProgramClass theInterface, Map<DexType, List<DexType>> emulatedInterfacesHierarchy) {
     List<DexEncodedMethod> emulationMethods = new ArrayList<>();
     for (DexEncodedMethod method : theInterface.methods()) {
@@ -731,7 +731,7 @@
         DexAnnotationSet.empty(),
         DexEncodedField.EMPTY_ARRAY,
         DexEncodedField.EMPTY_ARRAY,
-        // All synthetized methods are static in this case.
+        // All synthesized methods are static in this case.
         emulationMethods.toArray(DexEncodedMethod.EMPTY_ARRAY),
         DexEncodedMethod.EMPTY_ARRAY,
         factory.getSkipNameValidationForTesting(),
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/NestBasedAccessDesugaring.java b/src/main/java/com/android/tools/r8/ir/desugar/NestBasedAccessDesugaring.java
index 449f404..dc9c9a0 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/NestBasedAccessDesugaring.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/NestBasedAccessDesugaring.java
@@ -167,7 +167,7 @@
         appView.dexItemFactory().getSkipNameValidationForTesting());
   }
 
-  void synthetizeNestConstructor(DexApplication.Builder<?> builder) {
+  void synthesizeNestConstructor(DexApplication.Builder<?> builder) {
     if (nestConstructorUsed) {
       appView.appInfo().addSynthesizedClass(nestConstructor);
       builder.addSynthesizedClass(nestConstructor, true);
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/R8NestBasedAccessDesugaring.java b/src/main/java/com/android/tools/r8/ir/desugar/R8NestBasedAccessDesugaring.java
index 9ec39ed..a4642cd 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/R8NestBasedAccessDesugaring.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/R8NestBasedAccessDesugaring.java
@@ -49,7 +49,7 @@
     if (nothingToMap()) {
       return appView.graphLense();
     }
-    synthetizeNestConstructor(appBuilder);
+    synthesizeNestConstructor(appBuilder);
     return new NestedPrivateMethodLense(
         appView,
         getNestConstructorType(),
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 2784e8d..87350e7 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
@@ -83,24 +83,39 @@
       DexEncodedMethod singleTarget,
       WhyAreYouNotInliningReporter whyAreYouNotInliningReporter) {
     if (singleTarget == null) {
-      throw new Unreachable();
+      throw new Unreachable(
+          "Unexpected attempt to inline invoke that does not have a single target");
+    }
+
+    if (singleTarget.isClassInitializer()) {
+      throw new Unreachable(
+          "Unexpected attempt to invoke a class initializer (`"
+              + singleTarget.method.toSourceString()
+              + "`)");
     }
 
     if (!singleTarget.hasCode()) {
-      whyAreYouNotInliningReporter.reportUnknownReason();
+      whyAreYouNotInliningReporter.reportInlineeDoesNotHaveCode();
       return true;
     }
 
-    if (appView.definitionFor(singleTarget.method.holder).isNotProgramClass()) {
-      whyAreYouNotInliningReporter.reportUnknownReason();
+    DexClass clazz = appView.definitionFor(singleTarget.method.holder);
+    if (!clazz.isProgramClass()) {
+      if (clazz.isClasspathClass()) {
+        whyAreYouNotInliningReporter.reportClasspathMethod();
+      } else {
+        assert clazz.isLibraryClass();
+        whyAreYouNotInliningReporter.reportLibraryMethod();
+      }
       return true;
     }
 
     // Ignore the implicit receiver argument.
     int numberOfArguments =
         invoke.arguments().size() - BooleanUtils.intValue(invoke.isInvokeMethodWithReceiver());
-    if (numberOfArguments != singleTarget.method.getArity()) {
-      whyAreYouNotInliningReporter.reportUnknownReason();
+    int arity = singleTarget.method.getArity();
+    if (numberOfArguments != arity) {
+      whyAreYouNotInliningReporter.reportIncorrectArity(numberOfArguments, arity);
       return true;
     }
 
@@ -182,7 +197,7 @@
       return true;
     }
 
-    whyAreYouNotInliningReporter.reportUnknownReason();
+    whyAreYouNotInliningReporter.reportMustTriggerClassInitialization();
     return false;
   }
 
@@ -194,11 +209,11 @@
 
   private boolean passesInliningConstraints(
       InvokeMethod invoke,
-      DexEncodedMethod candidate,
+      DexEncodedMethod singleTarget,
       Reason reason,
       WhyAreYouNotInliningReporter whyAreYouNotInliningReporter) {
-    if (candidate.getOptimizationInfo().neverInline()) {
-      whyAreYouNotInliningReporter.reportUnknownReason();
+    if (singleTarget.getOptimizationInfo().neverInline()) {
+      whyAreYouNotInliningReporter.reportMarkedAsNeverInline();
       return false;
     }
 
@@ -207,15 +222,15 @@
     if (method.isInstanceInitializer()
         && appView.options().isGeneratingClassFiles()
         && reason != Reason.FORCE) {
-      whyAreYouNotInliningReporter.reportUnknownReason();
+      whyAreYouNotInliningReporter.reportNoInliningIntoConstructorsWhenGeneratingClassFiles();
       return false;
     }
 
-    if (method == candidate) {
+    if (method == singleTarget) {
       // Cannot handle recursive inlining at this point.
       // Force inlined method should never be recursive.
-      assert !candidate.getOptimizationInfo().forceInline();
-      whyAreYouNotInliningReporter.reportUnknownReason();
+      assert !singleTarget.getOptimizationInfo().forceInline();
+      whyAreYouNotInliningReporter.reportRecursiveMethod();
       return false;
     }
 
@@ -224,67 +239,64 @@
     // or optimized code. Right now this happens for the class class staticizer, as it just
     // processes all relevant methods in parallel with the full optimization pipeline enabled.
     // TODO(sgjesse): Add this assert "assert !isProcessedConcurrently.test(candidate);"
-    if (reason != Reason.FORCE && isProcessedConcurrently.test(candidate)) {
-      whyAreYouNotInliningReporter.reportUnknownReason();
+    if (reason != Reason.FORCE && isProcessedConcurrently.test(singleTarget)) {
+      whyAreYouNotInliningReporter.reportProcessedConcurrently();
       return false;
     }
 
     InternalOptions options = appView.options();
     if (options.featureSplitConfiguration != null
         && !options.featureSplitConfiguration.inSameFeatureOrBase(
-            candidate.method, method.method)) {
-      whyAreYouNotInliningReporter.reportUnknownReason();
+            singleTarget.method, method.method)) {
+      whyAreYouNotInliningReporter.reportInliningAcrossFeatureSplit();
       return false;
     }
 
-    if (options.testing.validInliningReasons != null
-        && !options.testing.validInliningReasons.contains(reason)) {
-      whyAreYouNotInliningReporter.reportUnknownReason();
+    Set<Reason> validInliningReasons = options.testing.validInliningReasons;
+    if (validInliningReasons != null && !validInliningReasons.contains(reason)) {
+      whyAreYouNotInliningReporter.reportInvalidInliningReason(reason, validInliningReasons);
       return false;
     }
 
     // Abort inlining attempt if method -> target access is not right.
-    if (!inliner.hasInliningAccess(method, candidate)) {
-      whyAreYouNotInliningReporter.reportUnknownReason();
+    if (!inliner.hasInliningAccess(method, singleTarget)) {
+      whyAreYouNotInliningReporter.reportInaccessible();
       return false;
     }
 
-    DexClass holder = appView.definitionFor(candidate.method.holder);
-
+    DexClass holder = appView.definitionFor(singleTarget.method.holder);
     if (holder.isInterface()) {
       // Art978_virtual_interfaceTest correctly expects an IncompatibleClassChangeError exception at
       // runtime.
-      whyAreYouNotInliningReporter.reportUnknownReason();
-      return false;
-    }
-
-    if (holder.isNotProgramClass()) {
-      whyAreYouNotInliningReporter.reportUnknownReason();
+      whyAreYouNotInliningReporter.reportIncompatibleClassChangeError();
       return false;
     }
 
     // Don't inline if target is synchronized.
-    if (candidate.accessFlags.isSynchronized()) {
-      whyAreYouNotInliningReporter.reportUnknownReason();
+    if (singleTarget.accessFlags.isSynchronized()) {
+      whyAreYouNotInliningReporter.reportSynchronizedMethod();
       return false;
     }
 
     if (reason == Reason.DUAL_CALLER) {
-      if (satisfiesRequirementsForSimpleInlining(invoke, candidate)) {
+      if (satisfiesRequirementsForSimpleInlining(invoke, singleTarget)) {
         // When we have a method with two call sites, we simply inline the method as we normally do
         // when the method is small. We still need to ensure that the other call site is also
         // inlined, though. Therefore, we record here that we have seen one of the two call sites
         // as we normally do.
-        inliner.recordDoubleInliningCandidate(method, candidate);
-      } else {
-        if (inliner.satisfiesRequirementsForDoubleInlining(method, candidate)) {
-          whyAreYouNotInliningReporter.reportUnknownReason();
+        inliner.recordDoubleInliningCandidate(method, singleTarget);
+      } else if (inliner.isDoubleInliningEnabled()) {
+        if (!inliner.satisfiesRequirementsForDoubleInlining(method, singleTarget)) {
+          whyAreYouNotInliningReporter.reportInvalidDoubleInliningCandidate();
           return false;
         }
+      } else {
+        // TODO(b/142300882): Should in principle disallow inlining in this case.
+        inliner.recordDoubleInliningCandidate(method, singleTarget);
       }
     } else if (reason == Reason.SIMPLE
-        && !satisfiesRequirementsForSimpleInlining(invoke, candidate)) {
-      whyAreYouNotInliningReporter.reportUnknownReason();
+        && !satisfiesRequirementsForSimpleInlining(invoke, singleTarget)) {
+      whyAreYouNotInliningReporter.reportInlineeNotSimple();
       return false;
     }
 
@@ -293,8 +305,8 @@
       // If we do this it can increase the size of the main dex dependent classes.
       if (inliner.mainDexClasses.getRoots().contains(method.method.holder)
           && MainDexDirectReferenceTracer.hasReferencesOutsideFromCode(
-          appView.appInfo(), candidate, inliner.mainDexClasses.getRoots())) {
-        whyAreYouNotInliningReporter.reportUnknownReason();
+              appView.appInfo(), singleTarget, inliner.mainDexClasses.getRoots())) {
+        whyAreYouNotInliningReporter.reportInlineeRefersToClassesNotInMainDex();
         return false;
       }
       // Allow inlining into the classes in the main dex dependent set without restrictions.
@@ -341,16 +353,16 @@
   }
 
   @Override
-  public InlineAction computeForInvokeWithReceiver(
-      InvokeMethodWithReceiver invoke,
+  public InlineAction computeInlining(
+      InvokeMethod invoke,
       DexEncodedMethod singleTarget,
-      DexMethod invocationContext,
+      ClassInitializationAnalysis classInitializationAnalysis,
       WhyAreYouNotInliningReporter whyAreYouNotInliningReporter) {
     if (isSingleTargetInvalid(invoke, singleTarget, whyAreYouNotInliningReporter)) {
       return null;
     }
 
-    if (inliner.isBlackListed(singleTarget, whyAreYouNotInliningReporter)) {
+    if (inliner.isBlacklisted(singleTarget, whyAreYouNotInliningReporter)) {
       return null;
     }
 
@@ -364,10 +376,19 @@
       return null;
     }
 
+    return invoke.computeInlining(
+        singleTarget, reason, this, classInitializationAnalysis, whyAreYouNotInliningReporter);
+  }
+
+  public InlineAction computeForInvokeWithReceiver(
+      InvokeMethodWithReceiver invoke,
+      DexEncodedMethod singleTarget,
+      Reason reason,
+      WhyAreYouNotInliningReporter whyAreYouNotInliningReporter) {
     Value receiver = invoke.getReceiver();
     if (receiver.getTypeLattice().isDefinitelyNull()) {
       // A definitely null receiver will throw an error on call site.
-      whyAreYouNotInliningReporter.reportUnknownReason();
+      whyAreYouNotInliningReporter.reportReceiverDefinitelyNull();
       return null;
     }
 
@@ -381,7 +402,7 @@
       if (!singleTarget.getOptimizationInfo().checksNullReceiverBeforeAnySideEffect()) {
         InternalOptions options = appView.options();
         if (!options.enableInliningOfInvokesWithNullableReceivers) {
-          whyAreYouNotInliningReporter.reportUnknownReason();
+          whyAreYouNotInliningReporter.reportReceiverMaybeNull();
           return null;
         }
         action.setShouldSynthesizeNullCheckForReceiver();
@@ -390,38 +411,17 @@
     return action;
   }
 
-  @Override
   public InlineAction computeForInvokeStatic(
       InvokeStatic invoke,
       DexEncodedMethod singleTarget,
-      DexMethod invocationContext,
+      Reason reason,
       ClassInitializationAnalysis classInitializationAnalysis,
       WhyAreYouNotInliningReporter whyAreYouNotInliningReporter) {
-    if (isSingleTargetInvalid(invoke, singleTarget, whyAreYouNotInliningReporter)) {
-      return null;
-    }
-
-    if (inliner.isBlackListed(singleTarget, whyAreYouNotInliningReporter)) {
-      return null;
-    }
-
-    Reason reason = computeInliningReason(singleTarget);
-    // Determine if this should be inlined no matter how big it is.
-    if (!singleTarget.isInliningCandidate(
-        method, reason, appView.appInfo(), whyAreYouNotInliningReporter)) {
-      return null;
-    }
-
     // Abort inlining attempt if we can not guarantee class for static target has been initialized.
     if (!canInlineStaticInvoke(
         invoke, method, singleTarget, classInitializationAnalysis, whyAreYouNotInliningReporter)) {
       return null;
     }
-
-    if (!passesInliningConstraints(invoke, singleTarget, reason, whyAreYouNotInliningReporter)) {
-      return null;
-    }
-
     return new InlineAction(singleTarget, invoke, reason);
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/ForcedInliningOracle.java b/src/main/java/com/android/tools/r8/ir/optimize/ForcedInliningOracle.java
index e29070d..cbaa36f 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/ForcedInliningOracle.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/ForcedInliningOracle.java
@@ -6,14 +6,11 @@
 
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexEncodedMethod;
-import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.ir.analysis.ClassInitializationAnalysis;
 import com.android.tools.r8.ir.code.BasicBlock;
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.InvokeMethod;
-import com.android.tools.r8.ir.code.InvokeMethodWithReceiver;
-import com.android.tools.r8.ir.code.InvokeStatic;
 import com.android.tools.r8.ir.optimize.Inliner.InlineAction;
 import com.android.tools.r8.ir.optimize.Inliner.InlineeWithReason;
 import com.android.tools.r8.ir.optimize.Inliner.Reason;
@@ -53,19 +50,9 @@
   }
 
   @Override
-  public InlineAction computeForInvokeWithReceiver(
-      InvokeMethodWithReceiver invoke,
+  public InlineAction computeInlining(
+      InvokeMethod invoke,
       DexEncodedMethod singleTarget,
-      DexMethod invocationContext,
-      WhyAreYouNotInliningReporter whyAreYouNotInliningReporter) {
-    return computeForInvoke(invoke);
-  }
-
-  @Override
-  public InlineAction computeForInvokeStatic(
-      InvokeStatic invoke,
-      DexEncodedMethod singleTarget,
-      DexMethod invocationContext,
       ClassInitializationAnalysis classInitializationAnalysis,
       WhyAreYouNotInliningReporter whyAreYouNotInliningReporter) {
     return computeForInvoke(invoke);
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java b/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java
index f3bdff2..44c72c5 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java
@@ -58,7 +58,7 @@
 public class Inliner {
 
   protected final AppView<AppInfoWithLiveness> appView;
-  private final Set<DexMethod> blackList;
+  private final Set<DexMethod> blacklist;
   private final LensCodeRewriter lensCodeRewriter;
   final MainDexClasses mainDexClasses;
 
@@ -74,12 +74,12 @@
       LensCodeRewriter lensCodeRewriter) {
     Kotlin.Intrinsics intrinsics = appView.dexItemFactory().kotlin.intrinsics;
     this.appView = appView;
-    this.blackList = ImmutableSet.of(intrinsics.throwNpe, intrinsics.throwParameterIsNullException);
+    this.blacklist = ImmutableSet.of(intrinsics.throwNpe, intrinsics.throwParameterIsNullException);
     this.lensCodeRewriter = lensCodeRewriter;
     this.mainDexClasses = mainDexClasses;
   }
 
-  boolean isBlackListed(
+  boolean isBlacklisted(
       DexEncodedMethod encodedMethod, WhyAreYouNotInliningReporter whyAreYouNotInliningReporter) {
     DexMethod method = encodedMethod.method;
     if (encodedMethod.getOptimizationInfo().forceInline()
@@ -88,28 +88,28 @@
     }
 
     if (appView.appInfo().isPinned(method)) {
-      whyAreYouNotInliningReporter.reportUnknownReason();
+      whyAreYouNotInliningReporter.reportPinned();
       return true;
     }
 
-    if (blackList.contains(appView.graphLense().getOriginalMethodSignature(method))) {
-      whyAreYouNotInliningReporter.reportUnknownReason();
+    if (blacklist.contains(appView.graphLense().getOriginalMethodSignature(method))
+        || TwrCloseResourceRewriter.isSynthesizedCloseResourceMethod(method, appView)) {
+      whyAreYouNotInliningReporter.reportBlacklisted();
       return true;
     }
 
     if (appView.appInfo().neverInline.contains(method)) {
-      whyAreYouNotInliningReporter.reportUnknownReason();
-      return true;
-    }
-
-    if (TwrCloseResourceRewriter.isSynthesizedCloseResourceMethod(method, appView)) {
-      whyAreYouNotInliningReporter.reportUnknownReason();
+      whyAreYouNotInliningReporter.reportMarkedAsNeverInline();
       return true;
     }
 
     return false;
   }
 
+  boolean isDoubleInliningEnabled() {
+    return applyDoubleInlining;
+  }
+
   private ConstraintWithTarget instructionAllowedForInlining(
       Instruction instruction, InliningConstraints inliningConstraints, DexType invocationContext) {
     ConstraintWithTarget result =
@@ -759,12 +759,8 @@
                   ? NopWhyAreYouNotInliningReporter.getInstance()
                   : WhyAreYouNotInliningReporter.createFor(singleTarget, appView, context);
           InlineAction action =
-              invoke.computeInlining(
-                  singleTarget,
-                  oracle,
-                  context.method,
-                  classInitializationAnalysis,
-                  whyAreYouNotInliningReporter);
+              oracle.computeInlining(
+                  invoke, singleTarget, classInitializationAnalysis, whyAreYouNotInliningReporter);
           if (action == null) {
             assert whyAreYouNotInliningReporter.verifyReasonHasBeenReported();
             continue;
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/InliningOracle.java b/src/main/java/com/android/tools/r8/ir/optimize/InliningOracle.java
index f75f3ff..676187e 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/InliningOracle.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/InliningOracle.java
@@ -5,12 +5,9 @@
 package com.android.tools.r8.ir.optimize;
 
 import com.android.tools.r8.graph.DexEncodedMethod;
-import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.ir.analysis.ClassInitializationAnalysis;
 import com.android.tools.r8.ir.code.InvokeMethod;
-import com.android.tools.r8.ir.code.InvokeMethodWithReceiver;
-import com.android.tools.r8.ir.code.InvokeStatic;
 import com.android.tools.r8.ir.optimize.Inliner.InlineAction;
 import com.android.tools.r8.ir.optimize.inliner.WhyAreYouNotInliningReporter;
 
@@ -24,16 +21,9 @@
   // TODO(b/142116551): This should be equivalent to invoke.lookupSingleTarget(appView, context)!
   DexEncodedMethod lookupSingleTarget(InvokeMethod invoke, DexType context);
 
-  InlineAction computeForInvokeWithReceiver(
-      InvokeMethodWithReceiver invoke,
+  InlineAction computeInlining(
+      InvokeMethod invoke,
       DexEncodedMethod singleTarget,
-      DexMethod invocationContext,
-      WhyAreYouNotInliningReporter whyAreYouNotInliningReporter);
-
-  InlineAction computeForInvokeStatic(
-      InvokeStatic invoke,
-      DexEncodedMethod singleTarget,
-      DexMethod invocationContext,
       ClassInitializationAnalysis classInitializationAnalysis,
       WhyAreYouNotInliningReporter whyAreYouNotInliningReporter);
 }
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 8930a2a..f836195 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
@@ -905,11 +905,11 @@
         return false;
       }
 
+      InliningOracle oracle = defaultOracle.get();
       InlineAction inlineAction =
-          invoke.computeInlining(
+          oracle.computeInlining(
+              invoke,
               singleTarget,
-              defaultOracle.get(),
-              method.method,
               ClassInitializationAnalysis.trivial(),
               NopWhyAreYouNotInliningReporter.getInstance());
       if (inlineAction == null) {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/inliner/NopWhyAreYouNotInliningReporter.java b/src/main/java/com/android/tools/r8/ir/optimize/inliner/NopWhyAreYouNotInliningReporter.java
index 0858b45..818b8c2 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/inliner/NopWhyAreYouNotInliningReporter.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/inliner/NopWhyAreYouNotInliningReporter.java
@@ -7,6 +7,8 @@
 import com.android.tools.r8.ir.code.InstancePut;
 import com.android.tools.r8.ir.code.Instruction;
 import com.android.tools.r8.ir.code.InvokeDirect;
+import com.android.tools.r8.ir.optimize.Inliner.Reason;
+import java.util.Set;
 
 public class NopWhyAreYouNotInliningReporter extends WhyAreYouNotInliningReporter {
 
@@ -20,14 +22,92 @@
   }
 
   @Override
+  public void reportBlacklisted() {}
+
+  @Override
+  public void reportCallerNotSameClass() {}
+
+  @Override
+  public void reportCallerNotSameNest() {}
+
+  @Override
+  public void reportCallerNotSamePackage() {}
+
+  @Override
+  public void reportCallerNotSubtype() {}
+
+  @Override
+  public void reportClasspathMethod() {}
+
+  @Override
+  public void reportInaccessible() {}
+
+  @Override
+  public void reportIncompatibleClassChangeError() {}
+
+  @Override
+  public void reportIncorrectArity(int numberOfArguments, int arity) {}
+
+  @Override
+  public void reportInlineeDoesNotHaveCode() {}
+
+  @Override
+  public void reportInlineeNotInliningCandidate() {}
+
+  @Override
+  public void reportInlineeNotProcessed() {}
+
+  @Override
+  public void reportInlineeNotSimple() {}
+
+  @Override
+  public void reportInlineeRefersToClassesNotInMainDex() {}
+
+  @Override
+  public void reportInliningAcrossFeatureSplit() {}
+
+  @Override
   public void reportInstructionBudgetIsExceeded() {}
 
   @Override
+  public void reportInvalidDoubleInliningCandidate() {}
+
+  @Override
+  public void reportInvalidInliningReason(Reason reason, Set<Reason> validInliningReasons) {}
+
+  @Override
+  public void reportLibraryMethod() {}
+
+  @Override
+  public void reportMarkedAsNeverInline() {}
+
+  @Override
+  public void reportMustTriggerClassInitialization() {}
+
+  @Override
+  public void reportNoInliningIntoConstructorsWhenGeneratingClassFiles() {}
+
+  @Override
+  public void reportPinned() {}
+
+  @Override
   public void reportPotentialExplosionInExceptionalControlFlowResolutionBlocks(
       int estimatedNumberOfControlFlowResolutionBlocks, int threshold) {}
 
   @Override
-  public void reportUnknownReason() {}
+  public void reportProcessedConcurrently() {}
+
+  @Override
+  public void reportReceiverDefinitelyNull() {}
+
+  @Override
+  public void reportReceiverMaybeNull() {}
+
+  @Override
+  public void reportRecursiveMethod() {}
+
+  @Override
+  public void reportSynchronizedMethod() {}
 
   @Override
   public void reportUnknownTarget() {}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/inliner/WhyAreYouNotInliningReporter.java b/src/main/java/com/android/tools/r8/ir/optimize/inliner/WhyAreYouNotInliningReporter.java
index 8834ddf..c560944 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/inliner/WhyAreYouNotInliningReporter.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/inliner/WhyAreYouNotInliningReporter.java
@@ -10,8 +10,10 @@
 import com.android.tools.r8.ir.code.Instruction;
 import com.android.tools.r8.ir.code.InvokeDirect;
 import com.android.tools.r8.ir.code.InvokeMethod;
+import com.android.tools.r8.ir.optimize.Inliner.Reason;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import java.util.Collection;
+import java.util.Set;
 
 public abstract class WhyAreYouNotInliningReporter {
 
@@ -44,12 +46,64 @@
     }
   }
 
+  public abstract void reportBlacklisted();
+
+  public abstract void reportCallerNotSameClass();
+
+  public abstract void reportCallerNotSameNest();
+
+  public abstract void reportCallerNotSamePackage();
+
+  public abstract void reportCallerNotSubtype();
+
+  public abstract void reportClasspathMethod();
+
+  public abstract void reportInaccessible();
+
+  public abstract void reportIncompatibleClassChangeError();
+
+  public abstract void reportIncorrectArity(int numberOfArguments, int arity);
+
+  public abstract void reportInlineeDoesNotHaveCode();
+
+  public abstract void reportInlineeNotInliningCandidate();
+
+  public abstract void reportInlineeNotProcessed();
+
+  public abstract void reportInlineeNotSimple();
+
+  public abstract void reportInlineeRefersToClassesNotInMainDex();
+
+  public abstract void reportInliningAcrossFeatureSplit();
+
   public abstract void reportInstructionBudgetIsExceeded();
 
+  public abstract void reportInvalidDoubleInliningCandidate();
+
+  public abstract void reportInvalidInliningReason(Reason reason, Set<Reason> validInliningReasons);
+
+  public abstract void reportLibraryMethod();
+
+  public abstract void reportMarkedAsNeverInline();
+
+  public abstract void reportMustTriggerClassInitialization();
+
+  public abstract void reportNoInliningIntoConstructorsWhenGeneratingClassFiles();
+
+  public abstract void reportPinned();
+
   public abstract void reportPotentialExplosionInExceptionalControlFlowResolutionBlocks(
       int estimatedNumberOfControlFlowResolutionBlocks, int threshold);
 
-  public abstract void reportUnknownReason();
+  public abstract void reportProcessedConcurrently();
+
+  public abstract void reportReceiverDefinitelyNull();
+
+  public abstract void reportReceiverMaybeNull();
+
+  public abstract void reportRecursiveMethod();
+
+  public abstract void reportSynchronizedMethod();
 
   abstract void reportUnknownTarget();
 
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/inliner/WhyAreYouNotInliningReporterImpl.java b/src/main/java/com/android/tools/r8/ir/optimize/inliner/WhyAreYouNotInliningReporterImpl.java
index 6340db4..6bc09fd 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/inliner/WhyAreYouNotInliningReporterImpl.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/inliner/WhyAreYouNotInliningReporterImpl.java
@@ -8,7 +8,10 @@
 import com.android.tools.r8.ir.code.InstancePut;
 import com.android.tools.r8.ir.code.Instruction;
 import com.android.tools.r8.ir.code.InvokeDirect;
+import com.android.tools.r8.ir.optimize.Inliner.Reason;
+import com.android.tools.r8.utils.StringUtils;
 import java.io.PrintStream;
+import java.util.Set;
 
 class WhyAreYouNotInliningReporterImpl extends WhyAreYouNotInliningReporter {
 
@@ -45,11 +48,142 @@
   }
 
   @Override
+  public void reportBlacklisted() {
+    print("method is blacklisted from inlining.");
+  }
+
+  @Override
+  public void reportCallerNotSameClass() {
+    print("inlinee can only be inlined into methods in the same class.");
+  }
+
+  @Override
+  public void reportCallerNotSameNest() {
+    print("inlinee can only be inlined into methods in the same class (and its nest members).");
+  }
+
+  @Override
+  public void reportCallerNotSamePackage() {
+    print(
+        "inlinee can only be inlined into methods in the same package "
+            + "(declared package private or accesses package private type or member).");
+  }
+
+  @Override
+  public void reportCallerNotSubtype() {
+    print(
+        "inlinee can only be inlined into methods in the same package and methods in subtypes of "
+            + "the inlinee's enclosing class"
+            + "(declared protected or accesses protected type or member).");
+  }
+
+  @Override
+  public void reportClasspathMethod() {
+    print("inlinee is on the classpath.");
+  }
+
+  @Override
+  public void reportInaccessible() {
+    print("inlinee is not accessible from the caller context.");
+  }
+
+  @Override
+  public void reportIncompatibleClassChangeError() {
+    print("invoke may fail with an IncompatibleClassChangeError.");
+  }
+
+  @Override
+  public void reportIncorrectArity(int numberOfArguments, int arity) {
+    print(
+        "number of arguments ("
+            + numberOfArguments
+            + ") does not match arity of method ("
+            + arity
+            + ").");
+  }
+
+  @Override
+  public void reportInlineeDoesNotHaveCode() {
+    print("inlinee does not have code.");
+  }
+
+  @Override
+  public void reportInlineeNotInliningCandidate() {
+    print("unsupported instruction in inlinee.");
+  }
+
+  @Override
+  public void reportInlineeNotProcessed() {
+    print("inlinee not processed yet.");
+  }
+
+  @Override
+  public void reportInlineeNotSimple() {
+    print(
+        "not inlining due to code size heuristic "
+            + "(inlinee may have multiple callers and is not considered trivial).");
+  }
+
+  @Override
+  public void reportInlineeRefersToClassesNotInMainDex() {
+    print(
+        "inlining could increase the main dex size "
+            + "(caller is in main dex and inlinee refers to classes not in main dex).");
+  }
+
+  @Override
+  public void reportInliningAcrossFeatureSplit() {
+    print("cannot inline across feature splits.");
+  }
+
+  @Override
   public void reportInstructionBudgetIsExceeded() {
     print("caller's instruction budget is exceeded.");
   }
 
   @Override
+  public void reportInvalidDoubleInliningCandidate() {
+    print("inlinee is invoked more than once and could not be inlined into all call sites.");
+  }
+
+  @Override
+  public void reportInvalidInliningReason(Reason reason, Set<Reason> validInliningReasons) {
+    print(
+        "not a valid inlining reason (was: "
+            + reason
+            + ", allowed: one of "
+            + StringUtils.join(validInliningReasons, ", ")
+            + ").");
+  }
+
+  @Override
+  public void reportLibraryMethod() {
+    print("inlinee is a library method.");
+  }
+
+  @Override
+  public void reportMarkedAsNeverInline() {
+    print("method is marked by a -neverinline rule.");
+  }
+
+  @Override
+  public void reportMustTriggerClassInitialization() {
+    print(
+        "cannot guarantee that the enclosing class of the inlinee is guaranteed to be class "
+            + "initialized before the first side-effecting instruction in the inlinee.");
+  }
+
+  @Override
+  public void reportNoInliningIntoConstructorsWhenGeneratingClassFiles() {
+    print("inlining into constructors not supported when generating class files.");
+  }
+
+  @Override
+  public void reportPinned() {
+    print("method is kept by a Proguard configuration rule.");
+  }
+
+  @Override
   public void reportPotentialExplosionInExceptionalControlFlowResolutionBlocks(
       int estimatedNumberOfControlFlowResolutionBlocks, int threshold) {
     printWithExceededThreshold(
@@ -59,10 +193,30 @@
         threshold);
   }
 
-  // TODO(b/142108662): Always report a meaningful reason.
   @Override
-  public void reportUnknownReason() {
-    print(null);
+  public void reportProcessedConcurrently() {
+    print(
+        "could lead to nondeterministic output since the inlinee is being optimized concurrently.");
+  }
+
+  @Override
+  public void reportReceiverDefinitelyNull() {
+    print("the receiver is always null at the call site.");
+  }
+
+  @Override
+  public void reportReceiverMaybeNull() {
+    print("the receiver may be null at the call site.");
+  }
+
+  @Override
+  public void reportRecursiveMethod() {
+    print("recursive calls are not inlined.");
+  }
+
+  @Override
+  public void reportSynchronizedMethod() {
+    print("synchronized methods are not inlined.");
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/string/StringOptimizer.java b/src/main/java/com/android/tools/r8/ir/optimize/string/StringOptimizer.java
index 15eda31..7728c1b 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/string/StringOptimizer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/string/StringOptimizer.java
@@ -111,8 +111,6 @@
     }
   }
 
-  // int String#hashCode()
-  // int String#length()
   // boolean String#isEmpty()
   // boolean String#startsWith(String)
   // boolean String#endsWith(String)
@@ -120,6 +118,8 @@
   // boolean String#equals(String)
   // boolean String#equalsIgnoreCase(String)
   // boolean String#contentEquals(String)
+  // int String#hashCode()
+  // int String#length()
   // int String#indexOf(String)
   // int String#indexOf(int)
   // int String#lastIndexOf(String)
@@ -128,6 +128,7 @@
   // int String#compareToIgnoreCase(String)
   // String String#substring(int)
   // String String#substring(int, int)
+  // String String#trim()
   public void computeTrivialOperationsOnConstString(IRCode code) {
     if (!code.metadata().mayHaveConstString()) {
       return;
@@ -191,6 +192,23 @@
         continue;
       }
 
+      if (invokedMethod == factory.stringMethods.trim) {
+        Value receiver = invoke.getReceiver().getAliasedValue();
+        if (receiver.hasLocalInfo() || receiver.isPhi() || !receiver.definition.isConstString()) {
+          continue;
+        }
+        DexString resultString =
+            factory.createString(receiver.definition.asConstString().getValue().toString().trim());
+        Value newOutValue =
+            code.createValue(
+                TypeLatticeElement.stringClassType(appView, definitelyNotNull()),
+                invoke.getLocalInfo());
+        affectedValues.addAll(invoke.outValue().affectedValues());
+        it.replaceCurrentInstruction(new ConstString(newOutValue, resultString, throwingInfo));
+        numberOfSimplifiedConversions++;
+        continue;
+      }
+
       Function<DexString, Integer> operatorWithNoArg = null;
       BiFunction<DexString, DexString, Integer> operatorWithString = null;
       BiFunction<DexString, Integer, Integer> operatorWithInt = null;
diff --git a/src/main/java/com/android/tools/r8/ir/synthetic/DesugaredLibraryAPIConversionCfCodeProvider.java b/src/main/java/com/android/tools/r8/ir/synthetic/DesugaredLibraryAPIConversionCfCodeProvider.java
index 60ca8ca..5597bb5 100644
--- a/src/main/java/com/android/tools/r8/ir/synthetic/DesugaredLibraryAPIConversionCfCodeProvider.java
+++ b/src/main/java/com/android/tools/r8/ir/synthetic/DesugaredLibraryAPIConversionCfCodeProvider.java
@@ -35,13 +35,14 @@
 import java.util.List;
 import org.objectweb.asm.Opcodes;
 
-public class DesugaredLibraryAPIConversionCfCodeProvider {
+public abstract class DesugaredLibraryAPIConversionCfCodeProvider extends SyntheticCfCodeProvider {
 
-  private static boolean shouldConvert(
-      DexType type,
-      DesugaredLibraryAPIConverter converter,
-      AppView<?> appView,
-      DexString methodName) {
+  DesugaredLibraryAPIConversionCfCodeProvider(AppView<?> appView, DexType holder) {
+    super(appView, holder);
+  }
+
+  boolean shouldConvert(
+      DexType type, DesugaredLibraryAPIConverter converter, DexString methodName) {
     if (!appView.rewritePrefix.hasRewrittenType(type)) {
       return false;
     }
@@ -61,7 +62,12 @@
     return false;
   }
 
-  public static class APIConverterVivifiedWrapperCfCodeProvider extends SyntheticCfCodeProvider {
+  DexType vivifiedTypeFor(DexType type) {
+    return DesugaredLibraryAPIConverter.vivifiedTypeFor(type, appView);
+  }
+
+  public static class APIConverterVivifiedWrapperCfCodeProvider
+      extends DesugaredLibraryAPIConversionCfCodeProvider {
 
     DexField wrapperField;
     DexMethod forwardMethod;
@@ -95,13 +101,13 @@
       DexType[] newParameters = forwardMethod.proto.parameters.values.clone();
       for (DexType param : forwardMethod.proto.parameters.values) {
         instructions.add(new CfLoad(ValueType.fromDexType(param), stackIndex));
-        if (shouldConvert(param, converter, appView, forwardMethod.name)) {
+        if (shouldConvert(param, converter, forwardMethod.name)) {
           instructions.add(
               new CfInvoke(
                   Opcodes.INVOKESTATIC,
-                  converter.createConversionMethod(param, param, converter.vivifiedTypeFor(param)),
+                  converter.createConversionMethod(param, param, vivifiedTypeFor(param)),
                   false));
-          newParameters[index - 1] = converter.vivifiedTypeFor(param);
+          newParameters[index - 1] = vivifiedTypeFor(param);
         }
         if (param == factory.longType || param == factory.doubleType) {
           stackIndex++;
@@ -113,7 +119,7 @@
       DexType returnType = forwardMethod.proto.returnType;
       DexType forwardMethodReturnType =
           appView.rewritePrefix.hasRewrittenType(returnType)
-              ? converter.vivifiedTypeFor(returnType)
+              ? vivifiedTypeFor(returnType)
               : returnType;
 
       DexProto newProto = factory.createProto(forwardMethodReturnType, newParameters);
@@ -126,12 +132,12 @@
         instructions.add(new CfInvoke(Opcodes.INVOKEVIRTUAL, newForwardMethod, false));
       }
 
-      if (shouldConvert(returnType, converter, appView, forwardMethod.name)) {
+      if (shouldConvert(returnType, converter, forwardMethod.name)) {
         instructions.add(
             new CfInvoke(
                 Opcodes.INVOKESTATIC,
                 converter.createConversionMethod(
-                    returnType, converter.vivifiedTypeFor(returnType), returnType),
+                    returnType, vivifiedTypeFor(returnType), returnType),
                 false));
       }
       if (returnType == factory.voidType) {
@@ -143,7 +149,8 @@
     }
   }
 
-  public static class APIConverterWrapperCfCodeProvider extends SyntheticCfCodeProvider {
+  public static class APIConverterWrapperCfCodeProvider
+      extends DesugaredLibraryAPIConversionCfCodeProvider {
 
     DexField wrapperField;
     DexMethod forwardMethod;
@@ -181,11 +188,11 @@
       int stackIndex = 1;
       for (DexType param : forwardMethod.proto.parameters.values) {
         instructions.add(new CfLoad(ValueType.fromDexType(param), stackIndex));
-        if (shouldConvert(param, converter, appView, forwardMethod.name)) {
+        if (shouldConvert(param, converter, forwardMethod.name)) {
           instructions.add(
               new CfInvoke(
                   Opcodes.INVOKESTATIC,
-                  converter.createConversionMethod(param, converter.vivifiedTypeFor(param), param),
+                  converter.createConversionMethod(param, vivifiedTypeFor(param), param),
                   false));
         }
         if (param == factory.longType || param == factory.doubleType) {
@@ -201,14 +208,14 @@
       }
 
       DexType returnType = forwardMethod.proto.returnType;
-      if (shouldConvert(returnType, converter, appView, forwardMethod.name)) {
+      if (shouldConvert(returnType, converter, forwardMethod.name)) {
         instructions.add(
             new CfInvoke(
                 Opcodes.INVOKESTATIC,
                 converter.createConversionMethod(
-                    returnType, returnType, converter.vivifiedTypeFor(returnType)),
+                    returnType, returnType, vivifiedTypeFor(returnType)),
                 false));
-        returnType = converter.vivifiedTypeFor(returnType);
+        returnType = vivifiedTypeFor(returnType);
       }
       if (returnType == factory.voidType) {
         instructions.add(new CfReturnVoid());
diff --git a/src/main/java/com/android/tools/r8/naming/MethodNameMinifier.java b/src/main/java/com/android/tools/r8/naming/MethodNameMinifier.java
index d8fc1c6..7af0752 100644
--- a/src/main/java/com/android/tools/r8/naming/MethodNameMinifier.java
+++ b/src/main/java/com/android/tools/r8/naming/MethodNameMinifier.java
@@ -10,6 +10,7 @@
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexString;
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.ir.desugar.DesugaredLibraryAPIConverter;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.Timing;
@@ -286,6 +287,14 @@
         DexString reservedName = strategy.getReservedName(method, holder);
         if (reservedName != null) {
           state.reserveName(reservedName, method.method);
+          // This is reserving names which after prefix rewriting will actually override a library
+          // method.
+          if (appView.rewritePrefix.hasRewrittenTypeInSignature(method.method.proto)) {
+            state.reserveName(
+                reservedName,
+                DesugaredLibraryAPIConverter.methodWithVivifiedTypeInSignature(
+                    method.method, method.method.holder, appView));
+          }
         }
       }
     }
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 57ed2d3..1d209ea 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -12,17 +12,8 @@
 
 import com.android.tools.r8.Diagnostic;
 import com.android.tools.r8.dex.IndexedItemCollection;
-import com.android.tools.r8.errors.Unimplemented;
 import com.android.tools.r8.errors.Unreachable;
-import com.android.tools.r8.experimental.graphinfo.AnnotationGraphNode;
-import com.android.tools.r8.experimental.graphinfo.ClassGraphNode;
-import com.android.tools.r8.experimental.graphinfo.FieldGraphNode;
 import com.android.tools.r8.experimental.graphinfo.GraphConsumer;
-import com.android.tools.r8.experimental.graphinfo.GraphEdgeInfo;
-import com.android.tools.r8.experimental.graphinfo.GraphEdgeInfo.EdgeKind;
-import com.android.tools.r8.experimental.graphinfo.GraphNode;
-import com.android.tools.r8.experimental.graphinfo.KeepRuleGraphNode;
-import com.android.tools.r8.experimental.graphinfo.MethodGraphNode;
 import com.android.tools.r8.graph.AppInfoWithSubtyping;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.Descriptor;
@@ -58,24 +49,20 @@
 import com.android.tools.r8.ir.code.InvokeMethod;
 import com.android.tools.r8.ir.code.InvokeVirtual;
 import com.android.tools.r8.ir.code.Value;
-import com.android.tools.r8.ir.desugar.InterfaceMethodRewriter;
+import com.android.tools.r8.ir.desugar.DesugaredLibraryAPIConverter;
 import com.android.tools.r8.ir.desugar.LambdaDescriptor;
 import com.android.tools.r8.logging.Log;
 import com.android.tools.r8.origin.Origin;
-import com.android.tools.r8.references.Reference;
-import com.android.tools.r8.references.TypeReference;
 import com.android.tools.r8.shaking.EnqueuerWorklist.Action;
+import com.android.tools.r8.shaking.GraphReporter.KeepReasonWitness;
 import com.android.tools.r8.shaking.RootSetBuilder.ConsequentRootSet;
 import com.android.tools.r8.shaking.RootSetBuilder.RootSet;
 import com.android.tools.r8.shaking.ScopedDexMethodSet.AddMethodIfMoreVisibleResult;
-import com.android.tools.r8.utils.DequeUtils;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.SetUtils;
 import com.android.tools.r8.utils.StringDiagnostic;
 import com.android.tools.r8.utils.Timing;
 import com.google.common.base.Equivalence.Wrapper;
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.ImmutableList.Builder;
 import com.google.common.collect.ImmutableSortedSet;
 import com.google.common.collect.Maps;
 import com.google.common.collect.Sets;
@@ -84,7 +71,6 @@
 import it.unimi.dsi.fastutil.objects.Object2BooleanMap;
 import java.lang.reflect.InvocationHandler;
 import java.util.ArrayDeque;
-import java.util.Collection;
 import java.util.Collections;
 import java.util.Deque;
 import java.util.HashMap;
@@ -164,14 +150,6 @@
 
   private final Set<DexReference> identifierNameStrings = Sets.newIdentityHashSet();
 
-  // Canonicalization of external graph-nodes and edge info.
-  private final Map<DexItem, AnnotationGraphNode> annotationNodes = new IdentityHashMap<>();
-  private final Map<DexType, ClassGraphNode> classNodes = new IdentityHashMap<>();
-  private final Map<DexMethod, MethodGraphNode> methodNodes = new IdentityHashMap<>();
-  private final Map<DexField, FieldGraphNode> fieldNodes = new IdentityHashMap<>();
-  private final Map<ProguardKeepRuleBase, KeepRuleGraphNode> ruleNodes = new IdentityHashMap<>();
-  private final Map<EdgeKind, GraphEdgeInfo> reasonInfo = new IdentityHashMap<>();
-
   /**
    * Set of method signatures used in invoke-super instructions that either cannot be resolved or
    * resolve to a private method (leading to an IllegalAccessError).
@@ -199,7 +177,7 @@
    * Set of types that are mentioned in the program. We at least need an empty abstract class item
    * for these.
    */
-  private final SetWithReason<DexProgramClass> liveTypes = new SetWithReason<>(this::registerClass);
+  private final SetWithReportedReason<DexProgramClass> liveTypes;
 
   /** Set of live types defined in the library and classpath. Used to avoid duplicate tracing. */
   private final Set<DexClass> liveNonProgramTypes = Sets.newIdentityHashSet();
@@ -213,12 +191,10 @@
   private final Set<DexType> skippedProtoExtensionTypes = Sets.newIdentityHashSet();
 
   /** Set of annotation types that are instantiated. */
-  private final SetWithReason<DexAnnotation> liveAnnotations =
-      new SetWithReason<>(this::registerAnnotation);
+  private final SetWithReason<DexAnnotation> liveAnnotations;
 
   /** Set of types that are actually instantiated. These cannot be abstract. */
-  private final SetWithReason<DexProgramClass> instantiatedTypes =
-      new SetWithReason<>(this::registerClass);
+  private final SetWithReason<DexProgramClass> instantiatedTypes;
 
   /** Set of all types that are instantiated, directly or indirectly, thus may be abstract. */
   private final Set<DexProgramClass> directAndIndirectlyInstantiatedTypes =
@@ -229,8 +205,7 @@
    * are required so that invokes can find the method. If a method is only a target but not live,
    * its implementation may be removed and it may be marked abstract.
    */
-  private final SetWithReason<DexEncodedMethod> targetedMethods =
-      new SetWithReason<>(this::registerMethod);
+  private final SetWithReason<DexEncodedMethod> targetedMethods;
   /**
    * Set of program methods that are used as the bootstrap method for an invoke-dynamic instruction.
    */
@@ -245,20 +220,18 @@
   private final Set<DexMethod> lambdaMethodsTargetedByInvokeDynamic = Sets.newIdentityHashSet();
   /**
    * Set of virtual methods that are the immediate target of an invoke-direct.
-   * */
+   */
   private final Set<DexMethod> virtualMethodsTargetedByInvokeDirect = Sets.newIdentityHashSet();
   /**
    * Set of methods that belong to live classes and can be reached by invokes. These need to be
    * kept.
    */
-  private final SetWithReason<DexEncodedMethod> liveMethods =
-      new SetWithReason<>(this::registerMethod);
+  private final SetWithReason<DexEncodedMethod> liveMethods;
 
   /**
    * Set of fields that belong to live classes and can be reached by invokes. These need to be kept.
    */
-  private final SetWithReason<DexEncodedField> liveFields =
-      new SetWithReason<>(this::registerField);
+  private final SetWithReason<DexEncodedField> liveFields;
 
   /**
    * Set of service types (from META-INF/services/) that may have been instantiated reflectively via
@@ -270,8 +243,7 @@
    * Set of interface types for which there may be instantiations, such as lambda expressions or
    * explicit keep rules.
    */
-  private final SetWithReason<DexProgramClass> instantiatedInterfaceTypes =
-      new SetWithReason<>(this::registerInterface);
+  private final SetWithReason<DexProgramClass> instantiatedInterfaceTypes;
 
   /** A queue of items that need processing. Different items trigger different actions. */
   private final EnqueuerWorklist workList;
@@ -314,8 +286,6 @@
       new IdentityHashMap<>();
 
   private final GraphReporter graphReporter;
-  private final GraphConsumer keptGraphConsumer;
-  private CollectingGraphConsumer verificationGraphConsumer = null;
 
   Enqueuer(
       AppView<? extends AppInfoWithSubtyping> appView,
@@ -326,8 +296,7 @@
     this.appInfo = appView.appInfo();
     this.appView = appView;
     this.forceProguardCompatibility = options.forceProguardCompatibility;
-    this.graphReporter = new GraphReporter();
-    this.keptGraphConsumer = recordKeptGraph(options, keptGraphConsumer);
+    this.graphReporter = new GraphReporter(appView, keptGraphConsumer);
     this.mode = mode;
     this.options = options;
     this.workList = EnqueuerWorklist.createWorklist(appView);
@@ -335,12 +304,24 @@
     if (options.enableGeneratedMessageLiteShrinking && mode.isInitialOrFinalTreeShaking()) {
       registerAnalysis(new ProtoEnqueuerExtension(appView));
     }
+
+    liveTypes = new SetWithReportedReason<>();
+    liveAnnotations = new SetWithReason<>(graphReporter::registerAnnotation);
+    instantiatedTypes = new SetWithReason<>(graphReporter::registerClass);
+    targetedMethods = new SetWithReason<>(graphReporter::registerMethod);
+    liveMethods = new SetWithReason<>(graphReporter::registerMethod);
+    liveFields = new SetWithReason<>(graphReporter::registerField);
+    instantiatedInterfaceTypes = new SetWithReason<>(graphReporter::registerInterface);
   }
 
   public Mode getMode() {
     return mode;
   }
 
+  public GraphReporter getGraphReporter() {
+    return graphReporter;
+  }
+
   public Enqueuer registerAnalysis(EnqueuerAnalysis analysis) {
     this.analyses.add(analysis);
     return this;
@@ -444,7 +425,7 @@
       if (clazz.isInterface() && !clazz.accessFlags.isAnnotation()) {
         markInterfaceAsInstantiated(clazz, witness);
       } else {
-        workList.enqueueMarkInstantiatedAction(clazz, witness);
+        workList.enqueueMarkInstantiatedAction(clazz, null, witness);
         if (clazz.hasDefaultInitializer()) {
           DexEncodedMethod defaultInitializer = clazz.getDefaultInitializer();
           if (forceProguardCompatibility) {
@@ -471,14 +452,14 @@
     pinnedItems.add(item.toReference());
   }
 
-  private void markInterfaceAsInstantiated(DexProgramClass clazz, KeepReason reason) {
+  private void markInterfaceAsInstantiated(DexProgramClass clazz, KeepReasonWitness witness) {
     assert clazz.isInterface() && !clazz.accessFlags.isAnnotation();
 
-    if (!instantiatedInterfaceTypes.add(clazz, reason)) {
+    if (!instantiatedInterfaceTypes.add(clazz, witness)) {
       return;
     }
     populateInstantiatedTypesCache(clazz);
-    markTypeAsLive(clazz, reason);
+    markTypeAsLive(clazz, witness);
   }
 
   private void enqueueFirstNonSerializableClassInitializer(
@@ -787,16 +768,17 @@
 
     @Override
     public boolean registerNewInstance(DexType type) {
-      return registerNewInstance(type, KeepReason.instantiatedIn(currentMethod));
+      return registerNewInstance(type, currentMethod, KeepReason.instantiatedIn(currentMethod));
     }
 
-    public boolean registerNewInstance(DexType type, KeepReason keepReason) {
+    public boolean registerNewInstance(
+        DexType type, DexEncodedMethod context, KeepReason keepReason) {
       DexProgramClass clazz = getProgramClassOrNull(type);
       if (clazz != null) {
         if (clazz.isInterface()) {
-          markTypeAsLive(clazz, keepReason);
+          markTypeAsLive(clazz, graphReporter.registerClass(clazz, keepReason));
         } else {
-          markInstantiated(clazz, keepReason);
+          markInstantiated(clazz, context, keepReason);
         }
       }
       return true;
@@ -931,9 +913,9 @@
         if (clazz != null) {
           KeepReason reason = KeepReason.methodHandleReferencedIn(currentMethod);
           if (clazz.isInterface() && !clazz.accessFlags.isAnnotation()) {
-            markInterfaceAsInstantiated(clazz, reason);
+            markInterfaceAsInstantiated(clazz, graphReporter.registerClass(clazz, reason));
           } else {
-            markInstantiated(clazz, reason);
+            markInstantiated(clazz, null, reason);
           }
         }
       }
@@ -1001,7 +983,8 @@
           registerInvokeDirect(method, KeepReason.invokedFromLambdaCreatedIn(currentMethod));
           break;
         case INVOKE_CONSTRUCTOR:
-          registerNewInstance(method.holder, KeepReason.invokedFromLambdaCreatedIn(currentMethod));
+          registerNewInstance(
+              method.holder, null, KeepReason.invokedFromLambdaCreatedIn(currentMethod));
           break;
         default:
           throw new Unreachable();
@@ -1103,10 +1086,10 @@
     markTypeAsLive(
         holder,
         scopedMethodsForLiveTypes.computeIfAbsent(type, ignore -> new ScopedDexMethodSet()),
-        reason);
+        graphReporter.registerClass(holder, reason));
   }
 
-  private void markTypeAsLive(DexType type, Function<DexProgramClass, KeepReason> reason) {
+  private void markTypeAsLive(DexType type, Function<DexProgramClass, KeepReasonWitness> reason) {
     if (type.isArrayType()) {
       markTypeAsLive(type.toBaseType(appView.dexItemFactory()), reason);
       return;
@@ -1125,16 +1108,16 @@
         reason.apply(holder));
   }
 
-  private void markTypeAsLive(DexProgramClass clazz, KeepReason reason) {
+  private void markTypeAsLive(DexProgramClass clazz, KeepReasonWitness witness) {
     markTypeAsLive(
         clazz,
         scopedMethodsForLiveTypes.computeIfAbsent(clazz.type, ignore -> new ScopedDexMethodSet()),
-        reason);
+        witness);
   }
 
   private void markTypeAsLive(
-      DexProgramClass holder, ScopedDexMethodSet seen, KeepReason reasonForType) {
-    if (!liveTypes.add(holder, reasonForType)) {
+      DexProgramClass holder, ScopedDexMethodSet seen, KeepReasonWitness witness) {
+    if (!liveTypes.add(holder, witness)) {
       return;
     }
 
@@ -1156,7 +1139,6 @@
       markTypeAsLive(holder.superType, reason);
     }
 
-
     // We cannot remove virtual methods defined earlier in the type hierarchy if it is widening
     // access and is defined in an interface:
     //
@@ -1197,7 +1179,7 @@
       assert holder.accessFlags.isAnnotation();
       assert annotations.stream().allMatch(a -> a.annotation.type == holder.type);
       annotations.forEach(annotation -> handleAnnotation(holder, annotation));
-      }
+    }
 
     rootSet.forEachDependentStaticMember(holder, appView, this::enqueueDependentItem);
     compatEnqueueHolderIfDependentNonStaticMember(
@@ -1315,11 +1297,12 @@
 
   private void registerClassInitializer(DexProgramClass definition, KeepReason reason) {
     if (definition.hasClassInitializer()) {
-      registerMethod(definition.getClassInitializer(), reason);
+      graphReporter.registerMethod(definition.getClassInitializer(), reason);
     }
   }
 
-  private void markNonStaticDirectMethodAsReachable(DexMethod method, KeepReason reason) {
+  // Package protected due to entry point from worklist.
+  void markNonStaticDirectMethodAsReachable(DexMethod method, KeepReason reason) {
     handleInvokeOfDirectTarget(method, reason);
   }
 
@@ -1434,13 +1417,15 @@
    * Adds the class to the set of instantiated classes and marks its fields and methods live
    * depending on the currently seen invokes and field reads.
    */
-  private void processNewlyInstantiatedClass(DexProgramClass clazz, KeepReason reason) {
+  // Package protected due to entry point from worklist.
+  void processNewlyInstantiatedClass(
+      DexProgramClass clazz, DexEncodedMethod context, KeepReason reason) {
     assert !clazz.isInterface() || clazz.accessFlags.isAnnotation();
     // Notify analyses. This is done even if `clazz` has already been marked as instantiated,
     // because each analysis may depend on seeing all the (clazz, reason) pairs. Thus, not doing so
     // could lead to nondeterminism.
     analyses.forEach(
-        analysis -> analysis.processNewlyInstantiatedClass(clazz.asProgramClass(), reason));
+        analysis -> analysis.processNewlyInstantiatedClass(clazz.asProgramClass(), context));
 
     if (!instantiatedTypes.add(clazz, reason)) {
       return;
@@ -1452,7 +1437,7 @@
       Log.verbose(getClass(), "Class `%s` is instantiated, processing...", clazz);
     }
     // This class becomes live, so it and all its supertypes become live types.
-    markTypeAsLive(clazz, reason);
+    markTypeAsLive(clazz, graphReporter.registerClass(clazz, reason));
     // For all methods of the class, if we have seen a call, mark the method live.
     // We only do this for virtual calls, as the other ones will be done directly.
     transitionMethodsForInstantiatedClass(clazz);
@@ -1566,26 +1551,45 @@
     for (DexEncodedMethod method : libraryClass.virtualMethods()) {
       // Note: it may be worthwhile to add a resolution cache here. If so, it must till ensure
       // that all library override edges are reported to the kept-graph consumer.
-      ResolutionResult resolution =
+      ResolutionResult firstResolution =
           appView.appInfo().resolveMethod(instantiatedClass, method.method);
-      if (resolution.isValidVirtualTarget(options)) {
-        resolution.forEachTarget(
-            target -> {
-              if (!target.isAbstract()) {
-                DexClass targetHolder = appView.definitionFor(target.method.holder);
-                if (targetHolder != null && targetHolder.isProgramClass()) {
-                  DexProgramClass programClass = targetHolder.asProgramClass();
-                  if (shouldMarkLibraryMethodOverrideAsReachable(programClass, target)) {
-                    target.setLibraryMethodOverride();
-                    markVirtualMethodAsLive(
-                        programClass,
-                        target,
-                        KeepReason.isLibraryMethod(programClass, libraryClass.type));
-                  }
+      markResolutionAsLive(libraryClass, firstResolution);
+
+      // Due to API conversion, some overrides can be hidden since they will be rewritten. See
+      // class comment of DesugaredLibraryAPIConverter and vivifiedType logic.
+      // In the first enqueuer phase, the signature has not been desugared, so firstResolution
+      // maintains the library override. In the second enqueuer phase, the signature has been
+      // desugared, and the second resolution maintains the the library override.
+      if (appView.rewritePrefix.hasRewrittenTypeInSignature(method.method.proto)) {
+        DexMethod methodToResolve =
+            DesugaredLibraryAPIConverter.methodWithVivifiedTypeInSignature(
+                method.method, method.method.holder, appView);
+        assert methodToResolve != method.method;
+        ResolutionResult secondResolution =
+            appView.appInfo().resolveMethod(instantiatedClass, methodToResolve);
+        markResolutionAsLive(libraryClass, secondResolution);
+      }
+    }
+  }
+
+  private void markResolutionAsLive(DexClass libraryClass, ResolutionResult resolution) {
+    if (resolution.isValidVirtualTarget(options)) {
+      resolution.forEachTarget(
+          target -> {
+            if (!target.isAbstract()) {
+              DexClass targetHolder = appView.definitionFor(target.method.holder);
+              if (targetHolder != null && targetHolder.isProgramClass()) {
+                DexProgramClass programClass = targetHolder.asProgramClass();
+                if (shouldMarkLibraryMethodOverrideAsReachable(programClass, target)) {
+                  target.setLibraryMethodOverride();
+                  markVirtualMethodAsLive(
+                      programClass,
+                      target,
+                      KeepReason.isLibraryMethod(programClass, libraryClass.type));
                 }
               }
-            });
-      }
+            }
+          });
     }
   }
 
@@ -1709,11 +1713,12 @@
     analyses.forEach(analysis -> analysis.processNewlyLiveField(field));
   }
 
-  private void markInstantiated(DexProgramClass clazz, KeepReason reason) {
+  private void markInstantiated(
+      DexProgramClass clazz, DexEncodedMethod context, KeepReason reason) {
     if (Log.ENABLED) {
       Log.verbose(getClass(), "Register new instantiation of `%s`.", clazz);
     }
-    workList.enqueueMarkInstantiatedAction(clazz, reason);
+    workList.enqueueMarkInstantiatedAction(clazz, context, reason);
   }
 
   private void markLambdaInstantiated(DexType itf, DexEncodedMethod method) {
@@ -1785,7 +1790,8 @@
     return directAndIndirectlyInstantiatedTypes.contains(clazz);
   }
 
-  private void markInstanceFieldAsReachable(DexEncodedField encodedField, KeepReason reason) {
+  // Package protected due to entry point from worklist.
+  void markInstanceFieldAsReachable(DexEncodedField encodedField, KeepReason reason) {
     DexField field = encodedField.field;
     if (Log.ENABLED) {
       Log.verbose(getClass(), "Marking instance field `%s` as reachable.", field);
@@ -1819,8 +1825,8 @@
     }
   }
 
-  private void markVirtualMethodAsReachable(
-      DexMethod method, boolean interfaceInvoke, KeepReason reason) {
+  // Package protected due to entry point from worklist.
+  void markVirtualMethodAsReachable(DexMethod method, boolean interfaceInvoke, KeepReason reason) {
     markVirtualMethodAsReachable(method, interfaceInvoke, reason, (x, y) -> true);
   }
 
@@ -1850,7 +1856,7 @@
     MarkedResolutionTarget resolution = virtualTargetsMarkedAsReachable.get(method);
     if (resolution != null) {
       if (!resolution.isUnresolved()) {
-        registerMethod(resolution.method, reason);
+        graphReporter.registerMethod(resolution.method, reason);
       }
       return;
     }
@@ -2044,7 +2050,8 @@
     }
   }
 
-  private void markSuperMethodAsReachable(DexMethod method, DexEncodedMethod from) {
+  // Package protected due to entry point from worklist.
+  void markSuperMethodAsReachable(DexMethod method, DexEncodedMethod from) {
     // We have to mark the immediate target of the descriptor as targeted, as otherwise
     // the invoke super will fail in the resolution step with a NSM error.
     // See <a
@@ -2126,47 +2133,15 @@
     return createAppInfo(appInfo);
   }
 
-  private GraphConsumer recordKeptGraph(InternalOptions options, GraphConsumer consumer) {
-    if (options.testing.verifyKeptGraphInfo) {
-      verificationGraphConsumer = new CollectingGraphConsumer(consumer);
-      return verificationGraphConsumer;
-    }
-    return consumer;
-  }
-
-  private boolean verifyKeptGraph() {
-    if (options.testing.verifyKeptGraphInfo) {
-      assert verificationGraphConsumer != null;
-      for (DexProgramClass liveType : liveTypes.items) {
-        assert verifyRootedPath(liveType, verificationGraphConsumer);
+  public boolean verifyKeptGraph() {
+    if (appView.options().testing.verifyKeptGraphInfo) {
+      for (DexProgramClass liveType : liveTypes.getItems()) {
+        assert graphReporter.verifyRootedPath(liveType);
       }
     }
     return true;
   }
 
-  private boolean verifyRootedPath(DexProgramClass liveType, CollectingGraphConsumer graph) {
-    ClassGraphNode node = getClassGraphNode(liveType.type);
-    Set<GraphNode> seen = Sets.newIdentityHashSet();
-    Deque<GraphNode> targets = DequeUtils.newArrayDeque(node);
-    while (!targets.isEmpty()) {
-      GraphNode item = targets.pop();
-      if (item instanceof KeepRuleGraphNode) {
-        KeepRuleGraphNode rule = (KeepRuleGraphNode) item;
-        if (rule.getPreconditions().isEmpty()) {
-          return true;
-        }
-      }
-      if (seen.add(item)) {
-        Map<GraphNode, Set<GraphEdgeInfo>> sources = graph.getSourcesTargeting(item);
-        assert sources != null : "No sources set for " + item;
-        assert !sources.isEmpty() : "Empty sources set for " + item;
-        targets.addAll(sources.keySet());
-      }
-    }
-    assert false : "No rooted path to " + liveType.type;
-    return true;
-  }
-
   private AppInfoWithLiveness createAppInfo(AppInfoWithSubtyping appInfo) {
     ImmutableSortedSet.Builder<DexType> builder =
         ImmutableSortedSet.orderedBy(PresortedComparable::slowCompareTo);
@@ -2258,38 +2233,7 @@
         numOfLiveItems += (long) liveFields.items.size();
         while (!workList.isEmpty()) {
           Action action = workList.poll();
-          switch (action.kind) {
-            case MARK_INSTANTIATED:
-              processNewlyInstantiatedClass((DexProgramClass) action.target, action.reason);
-              break;
-            case MARK_REACHABLE_FIELD:
-              markInstanceFieldAsReachable((DexEncodedField) action.target, action.reason);
-              break;
-            case MARK_REACHABLE_DIRECT:
-              markNonStaticDirectMethodAsReachable((DexMethod) action.target, action.reason);
-              break;
-            case MARK_REACHABLE_VIRTUAL:
-              markVirtualMethodAsReachable((DexMethod) action.target, false, action.reason);
-              break;
-            case MARK_REACHABLE_INTERFACE:
-              markVirtualMethodAsReachable((DexMethod) action.target, true, action.reason);
-              break;
-            case MARK_REACHABLE_SUPER:
-              markSuperMethodAsReachable((DexMethod) action.target,
-                  (DexEncodedMethod) action.context);
-              break;
-            case MARK_METHOD_KEPT:
-              markMethodAsKept((DexEncodedMethod) action.target, action.reason);
-              break;
-            case MARK_FIELD_KEPT:
-              markFieldAsKept((DexEncodedField) action.target, action.reason);
-              break;
-            case MARK_METHOD_LIVE:
-              markMethodAsLive(((DexEncodedMethod) action.target), action.reason);
-              break;
-            default:
-              throw new IllegalArgumentException("" + action.kind);
-          }
+          action.run(this);
         }
 
         // Continue fix-point processing if -if rules are enabled by items that newly became live.
@@ -2393,7 +2337,8 @@
     lambdaMethodsTargetedByInvokeDynamic.clear();
   }
 
-  private void markMethodAsKept(DexEncodedMethod target, KeepReason reason) {
+  // Package protected due to entry point from worklist.
+  void markMethodAsKept(DexEncodedMethod target, KeepReason reason) {
     DexMethod method = target.method;
     DexProgramClass holder = getProgramClassOrNull(method.holder);
     if (holder == null) {
@@ -2432,7 +2377,8 @@
     }
   }
 
-  private void markFieldAsKept(DexEncodedField target, KeepReason reason) {
+  // Package protected due to entry point from worklist.
+  void markFieldAsKept(DexEncodedField target, KeepReason reason) {
     DexProgramClass clazz = getProgramClassOrNull(target.field.holder);
     if (clazz == null) {
       return;
@@ -2485,7 +2431,8 @@
     return false;
   }
 
-  private void markMethodAsLive(DexEncodedMethod method, KeepReason reason) {
+  // Package protected due to entry point from worklist.
+  void markMethodAsLive(DexEncodedMethod method, KeepReason reason) {
     assert liveMethods.contains(method);
 
     DexProgramClass clazz = getProgramClassOrNull(method.method.holder);
@@ -2535,18 +2482,19 @@
   }
 
   private void markClassAsInstantiatedWithReason(DexProgramClass clazz, KeepReason reason) {
-    workList.enqueueMarkInstantiatedAction(clazz, reason);
+    workList.enqueueMarkInstantiatedAction(clazz, null, reason);
     if (clazz.hasDefaultInitializer()) {
       workList.enqueueMarkReachableDirectAction(clazz.getDefaultInitializer().method, reason);
     }
   }
 
-  private void markClassAsInstantiatedWithCompatRule(DexProgramClass clazz, KeepReason reason) {
+  private void markClassAsInstantiatedWithCompatRule(
+      DexProgramClass clazz, KeepReasonWitness witness) {
     if (clazz.isInterface() && !clazz.accessFlags.isAnnotation()) {
-      markInterfaceAsInstantiated(clazz, reason);
+      markInterfaceAsInstantiated(clazz, witness);
       return;
     }
-    workList.enqueueMarkInstantiatedAction(clazz, reason);
+    workList.enqueueMarkInstantiatedAction(clazz, null, witness);
     if (clazz.hasDefaultInitializer()) {
       DexEncodedMethod defaultInitializer = clazz.getDefaultInitializer();
       workList.enqueueMarkReachableDirectAction(
@@ -2610,7 +2558,7 @@
         return;
       }
       if (!clazz.isInterface()) {
-        markInstantiated(clazz, KeepReason.reflectiveUseIn(method));
+        markInstantiated(clazz, null, KeepReason.reflectiveUseIn(method));
         if (clazz.hasDefaultInitializer()) {
           DexEncodedMethod initializer = clazz.getDefaultInitializer();
           KeepReason reason = KeepReason.reflectiveUseIn(method);
@@ -2637,7 +2585,7 @@
           !encodedField.accessFlags.isStatic()
               && dexItemFactory.atomicFieldUpdaterMethods.isFieldUpdater(invokedMethod);
       if (keepClass) {
-        markInstantiated(clazz, KeepReason.reflectiveUseIn(method));
+        markInstantiated(clazz, null, KeepReason.reflectiveUseIn(method));
       }
       markFieldAsKept(encodedField, KeepReason.reflectiveUseIn(method));
       // Fields accessed by reflection is marked as both read and written.
@@ -2897,6 +2845,24 @@
     }
   }
 
+  private static class SetWithReportedReason<T> {
+
+    private final Set<T> items = Sets.newIdentityHashSet();
+
+    boolean add(T item, KeepReasonWitness witness) {
+      assert witness != null;
+      return items.add(item);
+    }
+
+    boolean contains(T item) {
+      return items.contains(item);
+    }
+
+    Set<T> getItems() {
+      return Collections.unmodifiableSet(items);
+    }
+  }
+
   private static class SetWithReason<T> {
 
     private final Set<T> items = Sets.newIdentityHashSet();
@@ -2921,7 +2887,7 @@
     }
   }
 
-  private static class MarkedResolutionTarget {
+  public static class MarkedResolutionTarget {
 
     private static final MarkedResolutionTarget UNRESOLVED = new MarkedResolutionTarget(null, null);
 
@@ -2957,6 +2923,7 @@
   }
 
   private static class ReachableVirtualMethodsSet {
+
     private final Map<DexEncodedMethod, Set<MarkedResolutionTarget>> methods =
         Maps.newIdentityHashMap();
 
@@ -3120,402 +3087,4 @@
     }
   }
 
-  class GraphReporter {
-
-    private EdgeKind reportPrecondition(KeepRuleGraphNode keepRuleGraphNode) {
-      if (keepRuleGraphNode.getPreconditions().isEmpty()) {
-        return EdgeKind.KeepRule;
-      }
-      for (GraphNode precondition : keepRuleGraphNode.getPreconditions()) {
-        reportEdge(precondition, keepRuleGraphNode, EdgeKind.KeepRulePrecondition);
-      }
-      return EdgeKind.ConditionalKeepRule;
-    }
-
-    KeepReasonWitness reportKeepClass(
-        DexDefinition precondition, ProguardKeepRuleBase rule, DexProgramClass clazz) {
-      if (keptGraphConsumer == null) {
-        return KeepReasonWitness.INSTANCE;
-      }
-      KeepRuleGraphNode ruleNode = getKeepRuleGraphNode(precondition, rule);
-      EdgeKind edgeKind = reportPrecondition(ruleNode);
-      return reportEdge(ruleNode, getClassGraphNode(clazz.type), edgeKind);
-    }
-
-    KeepReasonWitness reportKeepClass(
-        DexDefinition precondition, Collection<ProguardKeepRuleBase> rules, DexProgramClass clazz) {
-      assert !rules.isEmpty();
-      if (keptGraphConsumer != null) {
-        for (ProguardKeepRuleBase rule : rules) {
-          reportKeepClass(precondition, rule, clazz);
-        }
-      }
-      return KeepReasonWitness.INSTANCE;
-    }
-
-    KeepReasonWitness reportKeepMethod(
-        DexDefinition precondition, ProguardKeepRuleBase rule, DexEncodedMethod method) {
-      if (keptGraphConsumer == null) {
-        return KeepReasonWitness.INSTANCE;
-      }
-      KeepRuleGraphNode ruleNode = getKeepRuleGraphNode(precondition, rule);
-      EdgeKind edgeKind = reportPrecondition(ruleNode);
-      return reportEdge(ruleNode, getMethodGraphNode(method.method), edgeKind);
-    }
-
-    KeepReasonWitness reportKeepMethod(
-        DexDefinition precondition,
-        Collection<ProguardKeepRuleBase> rules,
-        DexEncodedMethod method) {
-      assert !rules.isEmpty();
-      if (keptGraphConsumer != null) {
-        for (ProguardKeepRuleBase rule : rules) {
-          reportKeepMethod(precondition, rule, method);
-        }
-      }
-      return KeepReasonWitness.INSTANCE;
-    }
-
-    KeepReasonWitness reportKeepField(
-        DexDefinition precondition, ProguardKeepRuleBase rule, DexEncodedField field) {
-      if (keptGraphConsumer == null) {
-        return KeepReasonWitness.INSTANCE;
-      }
-      KeepRuleGraphNode ruleNode = getKeepRuleGraphNode(precondition, rule);
-      EdgeKind edgeKind = reportPrecondition(ruleNode);
-      return reportEdge(ruleNode, getFieldGraphNode(field.field), edgeKind);
-    }
-
-    KeepReasonWitness reportKeepField(
-        DexDefinition precondition, Collection<ProguardKeepRuleBase> rules, DexEncodedField field) {
-      assert !rules.isEmpty();
-      if (keptGraphConsumer != null) {
-        for (ProguardKeepRuleBase rule : rules) {
-          reportKeepField(precondition, rule, field);
-        }
-      }
-      return KeepReasonWitness.INSTANCE;
-    }
-
-    public KeepReasonWitness reportCompatKeepDefaultInitializer(
-        DexProgramClass holder, DexEncodedMethod defaultInitializer) {
-      assert holder.type == defaultInitializer.method.holder;
-      assert holder.getDefaultInitializer() == defaultInitializer;
-      if (keptGraphConsumer != null) {
-        reportEdge(
-            getClassGraphNode(holder.type),
-            getMethodGraphNode(defaultInitializer.method),
-            EdgeKind.CompatibilityRule);
-      }
-      return KeepReasonWitness.COMPAT_INSTANCE;
-    }
-
-    public KeepReasonWitness reportCompatKeepMethod(
-        DexProgramClass holder, DexEncodedMethod method) {
-      assert holder.type == method.method.holder;
-      // TODO(b/141729349): This compat rule is from the method to itself and has not edge. Fix it.
-      // The rule is stating that if the method is targeted it is live. Since such an edge does
-      // not contribute to additional information in the kept graph as it stands (no distinction
-      // of targeted vs live edges), there is little point in emitting it.
-      return KeepReasonWitness.COMPAT_INSTANCE;
-    }
-
-    public KeepReason reportCompatInstantiated(
-        DexProgramClass instantiated, DexEncodedMethod method) {
-      if (keptGraphConsumer != null) {
-        reportEdge(
-            getMethodGraphNode(method.method),
-            getClassGraphNode(instantiated.type),
-            EdgeKind.CompatibilityRule);
-      }
-      return KeepReasonWitness.COMPAT_INSTANCE;
-    }
-
-    public KeepReasonWitness reportClassReferencedFrom(
-        DexProgramClass clazz, DexEncodedMethod method) {
-      if (keptGraphConsumer != null) {
-        MethodGraphNode source = getMethodGraphNode(method.method);
-        ClassGraphNode target = getClassGraphNode(clazz.type);
-        return reportEdge(source, target, EdgeKind.ReferencedFrom);
-      }
-      return KeepReasonWitness.INSTANCE;
-    }
-
-    public KeepReasonWitness reportClassReferencedFrom(
-        DexProgramClass clazz, DexEncodedField field) {
-      if (keptGraphConsumer != null) {
-        FieldGraphNode source = getFieldGraphNode(field.field);
-        ClassGraphNode target = getClassGraphNode(clazz.type);
-        return reportEdge(source, target, EdgeKind.ReferencedFrom);
-      }
-      return KeepReasonWitness.INSTANCE;
-    }
-
-    public KeepReasonWitness reportReachableMethodAsLive(
-        DexEncodedMethod encodedMethod, MarkedResolutionTarget reason) {
-      if (keptGraphConsumer != null) {
-        return reportEdge(
-            getMethodGraphNode(reason.method.method),
-            getMethodGraphNode(encodedMethod.method),
-            EdgeKind.OverridingMethod);
-      }
-      return KeepReasonWitness.INSTANCE;
-    }
-
-    public KeepReasonWitness reportReachableMethodAsLive(
-        DexEncodedMethod encodedMethod, Set<MarkedResolutionTarget> reasons) {
-      assert !reasons.isEmpty();
-      if (keptGraphConsumer != null) {
-        MethodGraphNode target = getMethodGraphNode(encodedMethod.method);
-        for (MarkedResolutionTarget reason : reasons) {
-          reportEdge(getMethodGraphNode(reason.method.method), target, EdgeKind.OverridingMethod);
-        }
-      }
-      return KeepReasonWitness.INSTANCE;
-    }
-
-    private KeepReason reportCompanionClass(DexProgramClass iface, DexProgramClass companion) {
-      assert iface.isInterface();
-      assert InterfaceMethodRewriter.isCompanionClassType(companion.type);
-      if (keptGraphConsumer == null) {
-        return KeepReasonWitness.INSTANCE;
-      }
-      return reportEdge(
-          getClassGraphNode(iface.type),
-          getClassGraphNode(companion.type),
-          EdgeKind.CompanionClass);
-    }
-
-    private KeepReason reportCompanionMethod(
-        DexEncodedMethod definition, DexEncodedMethod implementation) {
-      assert InterfaceMethodRewriter.isCompanionClassType(implementation.method.holder);
-      if (keptGraphConsumer == null) {
-        return KeepReasonWitness.INSTANCE;
-      }
-      return reportEdge(
-          getMethodGraphNode(definition.method),
-          getMethodGraphNode(implementation.method),
-          EdgeKind.CompanionMethod);
-    }
-
-    private KeepReasonWitness reportEdge(
-        GraphNode source, GraphNode target, GraphEdgeInfo.EdgeKind kind) {
-      assert keptGraphConsumer != null;
-      keptGraphConsumer.acceptEdge(source, target, getEdgeInfo(kind));
-      return KeepReasonWitness.INSTANCE;
-    }
-
-  }
-
-  /**
-   * Sentinel value indicating that a keep reason has been reported.
-   *
-   * <p>Should only ever be returned by the register* function below.
-   */
-  private static class KeepReasonWitness extends KeepReason {
-
-    private static KeepReasonWitness INSTANCE = new KeepReasonWitness();
-    private static KeepReasonWitness COMPAT_INSTANCE =
-        new KeepReasonWitness() {
-          @Override
-          public boolean isDueToProguardCompatibility() {
-            return true;
-          }
-        };
-
-    @Override
-    public EdgeKind edgeKind() {
-      throw new Unreachable();
-    }
-
-    @Override
-    public GraphNode getSourceNode(Enqueuer enqueuer) {
-      throw new Unreachable();
-    }
-  }
-
-  private boolean skipReporting(KeepReason reason) {
-    assert reason != null;
-    if (reason == KeepReasonWitness.INSTANCE || reason == KeepReasonWitness.COMPAT_INSTANCE) {
-      return true;
-    }
-    assert getSourceNode(reason) != null;
-    return keptGraphConsumer == null;
-  }
-
-  private KeepReasonWitness registerType(DexType type, KeepReason reason) {
-    if (skipReporting(reason)) {
-      return KeepReasonWitness.INSTANCE;
-    }
-    return registerEdge(getClassGraphNode(type), reason);
-  }
-
-  private KeepReasonWitness registerInterface(DexProgramClass iface, KeepReason reason) {
-    assert iface.isInterface();
-    if (skipReporting(reason)) {
-      return KeepReasonWitness.INSTANCE;
-    }
-    return registerEdge(getClassGraphNode(iface.type), reason);
-  }
-
-  private KeepReasonWitness registerClass(DexProgramClass clazz, KeepReason reason) {
-    if (skipReporting(reason)) {
-      return KeepReasonWitness.INSTANCE;
-    }
-    return registerEdge(getClassGraphNode(clazz.type), reason);
-  }
-
-  private KeepReasonWitness registerAnnotation(DexAnnotation annotation, KeepReason reason) {
-    if (skipReporting(reason)) {
-      return KeepReasonWitness.INSTANCE;
-    }
-    return registerEdge(getAnnotationGraphNode(annotation.annotation.type), reason);
-  }
-
-  private KeepReasonWitness registerMethod(DexEncodedMethod method, KeepReason reason) {
-    if (skipReporting(reason)) {
-      return KeepReasonWitness.INSTANCE;
-    }
-    if (reason.edgeKind() == EdgeKind.IsLibraryMethod && isNonProgramClass(method.method.holder)) {
-      // Don't report edges to actual library methods.
-      // TODO(b/120959039): This should be dead code once no library classes are ever enqueued.
-      return KeepReasonWitness.INSTANCE;
-    }
-    return registerEdge(getMethodGraphNode(method.method), reason);
-  }
-
-  private KeepReasonWitness registerField(DexEncodedField field, KeepReason reason) {
-    if (skipReporting(reason)) {
-      return KeepReasonWitness.INSTANCE;
-    }
-    return registerEdge(getFieldGraphNode(field.field), reason);
-  }
-
-  private KeepReasonWitness registerEdge(GraphNode target, KeepReason reason) {
-    assert !skipReporting(reason);
-    GraphNode sourceNode = getSourceNode(reason);
-    // TODO(b/120959039): Make sure we do have edges to nodes deriving library nodes!
-    if (!sourceNode.isLibraryNode()) {
-      GraphEdgeInfo edgeInfo = getEdgeInfo(reason);
-      keptGraphConsumer.acceptEdge(sourceNode, target, edgeInfo);
-    }
-    return KeepReasonWitness.INSTANCE;
-  }
-
-  private boolean isNonProgramClass(DexType type) {
-    DexClass clazz = appView.definitionFor(type);
-    return clazz == null || clazz.isNotProgramClass();
-  }
-
-  private GraphNode getSourceNode(KeepReason reason) {
-    return reason.getSourceNode(this);
-  }
-
-  public GraphNode getGraphNode(DexReference reference) {
-    if (reference.isDexType()) {
-      return getClassGraphNode(reference.asDexType());
-    }
-    if (reference.isDexMethod()) {
-      return getMethodGraphNode(reference.asDexMethod());
-    }
-    if (reference.isDexField()) {
-      return getFieldGraphNode(reference.asDexField());
-    }
-    throw new Unreachable();
-  }
-
-  GraphEdgeInfo getEdgeInfo(KeepReason reason) {
-    return getEdgeInfo(reason.edgeKind());
-  }
-
-  GraphEdgeInfo getEdgeInfo(GraphEdgeInfo.EdgeKind kind) {
-    return reasonInfo.computeIfAbsent(kind, k -> new GraphEdgeInfo(k));
-  }
-
-  AnnotationGraphNode getAnnotationGraphNode(DexItem type) {
-    return annotationNodes.computeIfAbsent(type, t -> {
-      if (t instanceof DexType) {
-        return new AnnotationGraphNode(getClassGraphNode(((DexType) t)));
-      }
-      throw new Unimplemented("Incomplete support for annotation node on item: " + type.getClass());
-    });
-  }
-
-  ClassGraphNode getClassGraphNode(DexType type) {
-    return classNodes.computeIfAbsent(
-        type,
-        t -> {
-          DexClass definition = appView.definitionFor(t);
-          return new ClassGraphNode(
-              definition != null && definition.isNotProgramClass(),
-              Reference.classFromDescriptor(t.toDescriptorString()));
-        });
-  }
-
-  MethodGraphNode getMethodGraphNode(DexMethod context) {
-    return methodNodes.computeIfAbsent(
-        context,
-        m -> {
-          DexClass holderDefinition = appView.definitionFor(context.holder);
-          Builder<TypeReference> builder = ImmutableList.builder();
-          for (DexType param : m.proto.parameters.values) {
-            builder.add(Reference.typeFromDescriptor(param.toDescriptorString()));
-          }
-          return new MethodGraphNode(
-              holderDefinition != null && holderDefinition.isNotProgramClass(),
-              Reference.method(
-                  Reference.classFromDescriptor(m.holder.toDescriptorString()),
-                  m.name.toString(),
-                  builder.build(),
-                  m.proto.returnType.isVoidType()
-                      ? null
-                      : Reference.typeFromDescriptor(m.proto.returnType.toDescriptorString())));
-        });
-  }
-
-  FieldGraphNode getFieldGraphNode(DexField context) {
-    return fieldNodes.computeIfAbsent(
-        context,
-        f -> {
-          DexClass holderDefinition = appView.definitionFor(context.holder);
-          return new FieldGraphNode(
-              holderDefinition != null && holderDefinition.isNotProgramClass(),
-              Reference.field(
-                  Reference.classFromDescriptor(f.holder.toDescriptorString()),
-                  f.name.toString(),
-                  Reference.typeFromDescriptor(f.type.toDescriptorString())));
-        });
-  }
-
-  // Due to the combined encoding of dependent rules, ala keepclassmembers and conditional keep
-  // rules the conversion to a keep-rule graph node can be one of three forms:
-  // 1. A non-dependent keep rule. In this case precondtion == null and the rule is not an if-rule.
-  // 2. A dependent keep rule. In this case precondtion != null and rule is not an if-rule.
-  // 3. A conditional keep rule. In this case rule is an if-rule, but precondition may or may not be
-  //    null. In the non-null case, the precondition is the type the consequent may depend on,
-  //    say T for the consequent "-keep T { f; }". It is *not* the precondition of the conditional
-  //    rule.
-  KeepRuleGraphNode getKeepRuleGraphNode(DexDefinition precondition, ProguardKeepRuleBase rule) {
-    if (rule instanceof ProguardKeepRule) {
-      Set<GraphNode> preconditions =
-          precondition != null
-              ? Collections.singleton(getGraphNode(precondition.toReference()))
-              : Collections.emptySet();
-      return ruleNodes.computeIfAbsent(rule, key -> new KeepRuleGraphNode(rule, preconditions));
-    }
-    if (rule instanceof ProguardIfRule) {
-      ProguardIfRule ifRule = (ProguardIfRule) rule;
-      assert !ifRule.getPreconditions().isEmpty();
-      return ruleNodes.computeIfAbsent(
-          ifRule,
-          key -> {
-            Set<GraphNode> preconditions = new HashSet<>(ifRule.getPreconditions().size());
-            for (DexReference condition : ifRule.getPreconditions()) {
-              preconditions.add(getGraphNode(condition));
-            }
-            return new KeepRuleGraphNode(ifRule, preconditions);
-          });
-    }
-    throw new Unreachable("Unexpected type of keep rule: " + rule);
-  }
 }
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 bde6b8d..275e5a7 100644
--- a/src/main/java/com/android/tools/r8/shaking/EnqueuerWorklist.java
+++ b/src/main/java/com/android/tools/r8/shaking/EnqueuerWorklist.java
@@ -7,7 +7,6 @@
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexEncodedField;
 import com.android.tools.r8.graph.DexEncodedMethod;
-import com.android.tools.r8.graph.DexItem;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexProgramClass;
 import java.util.ArrayDeque;
@@ -15,31 +14,146 @@
 
 public class EnqueuerWorklist {
 
-  public static class Action {
+  public abstract static class Action {
+    public abstract void run(Enqueuer enqueuer);
+  }
 
-    public enum Kind {
-      MARK_REACHABLE_DIRECT,
-      MARK_REACHABLE_VIRTUAL,
-      MARK_REACHABLE_INTERFACE,
-      MARK_REACHABLE_SUPER,
-      MARK_REACHABLE_FIELD,
-      MARK_INSTANTIATED,
-      MARK_METHOD_LIVE,
-      MARK_METHOD_KEPT,
-      MARK_FIELD_KEPT
-    }
-
-    final Kind kind;
-    final DexItem target;
-    final DexItem context;
+  static class MarkReachableDirectAction extends Action {
+    final DexMethod target;
     final KeepReason reason;
 
-    private Action(Kind kind, DexItem target, DexItem context, KeepReason reason) {
-      this.kind = kind;
+    MarkReachableDirectAction(DexMethod target, KeepReason reason) {
+      this.target = target;
+      this.reason = reason;
+    }
+
+    @Override
+    public void run(Enqueuer enqueuer) {
+      enqueuer.markNonStaticDirectMethodAsReachable(target, reason);
+    }
+  }
+
+  static class MarkReachableVirtualAction extends Action {
+    final DexMethod target;
+    final KeepReason reason;
+
+    MarkReachableVirtualAction(DexMethod target, KeepReason reason) {
+      this.target = target;
+      this.reason = reason;
+    }
+
+    @Override
+    public void run(Enqueuer enqueuer) {
+      enqueuer.markVirtualMethodAsReachable(target, false, reason);
+    }
+  }
+
+  static class MarkReachableInterfaceAction extends Action {
+    final DexMethod target;
+    final KeepReason reason;
+
+    public MarkReachableInterfaceAction(DexMethod target, KeepReason reason) {
+      this.target = target;
+      this.reason = reason;
+    }
+
+    @Override
+    public void run(Enqueuer enqueuer) {
+      enqueuer.markVirtualMethodAsReachable(target, true, reason);
+    }
+  }
+
+  static class MarkReachableSuperAction extends Action {
+    final DexMethod target;
+    final DexEncodedMethod context;
+
+    public MarkReachableSuperAction(DexMethod target, DexEncodedMethod context) {
+      this.target = target;
+      this.context = context;
+    }
+
+    @Override
+    public void run(Enqueuer enqueuer) {
+      enqueuer.markSuperMethodAsReachable(target, context);
+    }
+  }
+
+  static class MarkReachableFieldAction extends Action {
+    final DexEncodedField target;
+    final KeepReason reason;
+
+    public MarkReachableFieldAction(DexEncodedField target, KeepReason reason) {
+      this.target = target;
+      this.reason = reason;
+    }
+
+    @Override
+    public void run(Enqueuer enqueuer) {
+      enqueuer.markInstanceFieldAsReachable(target, reason);
+    }
+  }
+
+  static class MarkInstantiatedAction extends Action {
+    final DexProgramClass target;
+    final DexEncodedMethod context;
+    final KeepReason reason;
+
+    public MarkInstantiatedAction(
+        DexProgramClass target, DexEncodedMethod context, KeepReason reason) {
       this.target = target;
       this.context = context;
       this.reason = reason;
     }
+
+    @Override
+    public void run(Enqueuer enqueuer) {
+      enqueuer.processNewlyInstantiatedClass(target, context, reason);
+    }
+  }
+
+  static class MarkMethodLiveAction extends Action {
+    final DexEncodedMethod target;
+    final KeepReason reason;
+
+    public MarkMethodLiveAction(DexEncodedMethod target, KeepReason reason) {
+      this.target = target;
+      this.reason = reason;
+    }
+
+    @Override
+    public void run(Enqueuer enqueuer) {
+      enqueuer.markMethodAsLive(target, reason);
+    }
+  }
+
+  static class MarkMethodKeptAction extends Action {
+    final DexEncodedMethod target;
+    final KeepReason reason;
+
+    public MarkMethodKeptAction(DexEncodedMethod target, KeepReason reason) {
+      this.target = target;
+      this.reason = reason;
+    }
+
+    @Override
+    public void run(Enqueuer enqueuer) {
+      enqueuer.markMethodAsKept(target, reason);
+    }
+  }
+
+  static class MarkFieldKeptAction extends Action {
+    final DexEncodedField target;
+    final KeepReason reason;
+
+    public MarkFieldKeptAction(DexEncodedField target, KeepReason reason) {
+      this.target = target;
+      this.reason = reason;
+    }
+
+    @Override
+    public void run(Enqueuer enqueuer) {
+      enqueuer.markFieldAsKept(target, reason);
+    }
   }
 
   private final AppView<?> appView;
@@ -62,45 +176,48 @@
   }
 
   void enqueueMarkReachableDirectAction(DexMethod method, KeepReason reason) {
-    queue.add(new Action(Action.Kind.MARK_REACHABLE_DIRECT, method, null, reason));
+    queue.add(new MarkReachableDirectAction(method, reason));
   }
 
   void enqueueMarkReachableVirtualAction(DexMethod method, KeepReason reason) {
-    queue.add(new Action(Action.Kind.MARK_REACHABLE_VIRTUAL, method, null, reason));
+    queue.add(new MarkReachableVirtualAction(method, reason));
   }
 
   void enqueueMarkReachableInterfaceAction(DexMethod method, KeepReason reason) {
-    queue.add(new Action(Action.Kind.MARK_REACHABLE_INTERFACE, method, null, reason));
+    queue.add(new MarkReachableInterfaceAction(method, reason));
   }
 
   void enqueueMarkReachableSuperAction(DexMethod method, DexEncodedMethod from) {
-    queue.add(new Action(Action.Kind.MARK_REACHABLE_SUPER, method, from, null));
+    queue.add(new MarkReachableSuperAction(method, from));
   }
 
   public void enqueueMarkReachableFieldAction(
       DexProgramClass clazz, DexEncodedField field, KeepReason reason) {
     assert field.field.holder == clazz.type;
-    queue.add(new Action(Action.Kind.MARK_REACHABLE_FIELD, field, null, reason));
+    queue.add(new MarkReachableFieldAction(field, reason));
   }
 
-  void enqueueMarkInstantiatedAction(DexProgramClass clazz, KeepReason reason) {
+  // TODO(b/142378367): Context is the containing method that is cause of the instantiation.
+  // Consider updating call sites with the context information to increase precision where possible.
+  void enqueueMarkInstantiatedAction(
+      DexProgramClass clazz, DexEncodedMethod context, KeepReason reason) {
     assert !clazz.isInterface() || clazz.accessFlags.isAnnotation();
-    queue.add(new Action(Action.Kind.MARK_INSTANTIATED, clazz, null, reason));
+    queue.add(new MarkInstantiatedAction(clazz, context, reason));
   }
 
   void enqueueMarkMethodLiveAction(
       DexProgramClass clazz, DexEncodedMethod method, KeepReason reason) {
     assert method.method.holder == clazz.type;
-    queue.add(new Action(Action.Kind.MARK_METHOD_LIVE, method, null, reason));
+    queue.add(new MarkMethodLiveAction(method, reason));
   }
 
   void enqueueMarkMethodKeptAction(DexEncodedMethod method, KeepReason reason) {
     assert method.isProgramMethod(appView);
-    queue.add(new Action(Action.Kind.MARK_METHOD_KEPT, method, null, reason));
+    queue.add(new MarkMethodKeptAction(method, reason));
   }
 
   void enqueueMarkFieldKeptAction(DexEncodedField field, KeepReason reason) {
     assert field.isProgramField(appView);
-    queue.add(new Action(Action.Kind.MARK_FIELD_KEPT, field, null, reason));
+    queue.add(new MarkFieldKeptAction(field, reason));
   }
 }
diff --git a/src/main/java/com/android/tools/r8/shaking/GraphReporter.java b/src/main/java/com/android/tools/r8/shaking/GraphReporter.java
new file mode 100644
index 0000000..5dc405e
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/shaking/GraphReporter.java
@@ -0,0 +1,475 @@
+// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.shaking;
+
+import com.android.tools.r8.errors.Unimplemented;
+import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.experimental.graphinfo.AnnotationGraphNode;
+import com.android.tools.r8.experimental.graphinfo.ClassGraphNode;
+import com.android.tools.r8.experimental.graphinfo.FieldGraphNode;
+import com.android.tools.r8.experimental.graphinfo.GraphConsumer;
+import com.android.tools.r8.experimental.graphinfo.GraphEdgeInfo;
+import com.android.tools.r8.experimental.graphinfo.GraphEdgeInfo.EdgeKind;
+import com.android.tools.r8.experimental.graphinfo.GraphNode;
+import com.android.tools.r8.experimental.graphinfo.KeepRuleGraphNode;
+import com.android.tools.r8.experimental.graphinfo.MethodGraphNode;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexAnnotation;
+import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexDefinition;
+import com.android.tools.r8.graph.DexEncodedField;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.graph.DexItem;
+import com.android.tools.r8.graph.DexMethod;
+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.ir.desugar.InterfaceMethodRewriter;
+import com.android.tools.r8.references.Reference;
+import com.android.tools.r8.references.TypeReference;
+import com.android.tools.r8.shaking.Enqueuer.MarkedResolutionTarget;
+import com.android.tools.r8.utils.DequeUtils;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableList.Builder;
+import com.google.common.collect.Sets;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Deque;
+import java.util.HashSet;
+import java.util.IdentityHashMap;
+import java.util.Map;
+import java.util.Set;
+
+public class GraphReporter {
+
+  private final AppView<?> appView;
+  private final GraphConsumer keptGraphConsumer;
+  private final CollectingGraphConsumer verificationGraphConsumer;
+
+  // Canonicalization of external graph-nodes and edge info.
+  private final Map<DexItem, AnnotationGraphNode> annotationNodes = new IdentityHashMap<>();
+  private final Map<DexType, ClassGraphNode> classNodes = new IdentityHashMap<>();
+  private final Map<DexMethod, MethodGraphNode> methodNodes = new IdentityHashMap<>();
+  private final Map<DexField, FieldGraphNode> fieldNodes = new IdentityHashMap<>();
+  private final Map<ProguardKeepRuleBase, KeepRuleGraphNode> ruleNodes = new IdentityHashMap<>();
+  private final Map<EdgeKind, GraphEdgeInfo> reasonInfo = new IdentityHashMap<>();
+
+  GraphReporter(AppView<?> appView, GraphConsumer keptGraphConsumer) {
+    this.appView = appView;
+    if (appView.options().testing.verifyKeptGraphInfo) {
+      this.verificationGraphConsumer = new CollectingGraphConsumer(keptGraphConsumer);
+      this.keptGraphConsumer = verificationGraphConsumer;
+    } else {
+      this.verificationGraphConsumer = null;
+      this.keptGraphConsumer = keptGraphConsumer;
+    }
+  }
+
+  public boolean verifyRootedPath(DexProgramClass liveType) {
+    assert verificationGraphConsumer != null;
+    ClassGraphNode node = getClassGraphNode(liveType.type);
+    Set<GraphNode> seen = Sets.newIdentityHashSet();
+    Deque<GraphNode> targets = DequeUtils.newArrayDeque(node);
+    while (!targets.isEmpty()) {
+      GraphNode item = targets.pop();
+      if (item instanceof KeepRuleGraphNode) {
+        KeepRuleGraphNode rule = (KeepRuleGraphNode) item;
+        if (rule.getPreconditions().isEmpty()) {
+          return true;
+        }
+      }
+      if (seen.add(item)) {
+        Map<GraphNode, Set<GraphEdgeInfo>> sources =
+            verificationGraphConsumer.getSourcesTargeting(item);
+        assert sources != null : "No sources set for " + item;
+        assert !sources.isEmpty() : "Empty sources set for " + item;
+        targets.addAll(sources.keySet());
+      }
+    }
+    assert false : "No rooted path to " + liveType.type;
+    return false;
+  }
+
+  private EdgeKind reportPrecondition(KeepRuleGraphNode keepRuleGraphNode) {
+    if (keepRuleGraphNode.getPreconditions().isEmpty()) {
+      return EdgeKind.KeepRule;
+    }
+    for (GraphNode precondition : keepRuleGraphNode.getPreconditions()) {
+      reportEdge(precondition, keepRuleGraphNode, EdgeKind.KeepRulePrecondition);
+    }
+    return EdgeKind.ConditionalKeepRule;
+  }
+
+  KeepReasonWitness reportKeepClass(
+      DexDefinition precondition, ProguardKeepRuleBase rule, DexProgramClass clazz) {
+    if (keptGraphConsumer == null) {
+      return KeepReasonWitness.INSTANCE;
+    }
+    KeepRuleGraphNode ruleNode = getKeepRuleGraphNode(precondition, rule);
+    EdgeKind edgeKind = reportPrecondition(ruleNode);
+    return reportEdge(ruleNode, getClassGraphNode(clazz.type), edgeKind);
+  }
+
+  KeepReasonWitness reportKeepClass(
+      DexDefinition precondition, Collection<ProguardKeepRuleBase> rules, DexProgramClass clazz) {
+    assert !rules.isEmpty();
+    if (keptGraphConsumer != null) {
+      for (ProguardKeepRuleBase rule : rules) {
+        reportKeepClass(precondition, rule, clazz);
+      }
+    }
+    return KeepReasonWitness.INSTANCE;
+  }
+
+  KeepReasonWitness reportKeepMethod(
+      DexDefinition precondition, ProguardKeepRuleBase rule, DexEncodedMethod method) {
+    if (keptGraphConsumer == null) {
+      return KeepReasonWitness.INSTANCE;
+    }
+    KeepRuleGraphNode ruleNode = getKeepRuleGraphNode(precondition, rule);
+    EdgeKind edgeKind = reportPrecondition(ruleNode);
+    return reportEdge(ruleNode, getMethodGraphNode(method.method), edgeKind);
+  }
+
+  KeepReasonWitness reportKeepMethod(
+      DexDefinition precondition, Collection<ProguardKeepRuleBase> rules, DexEncodedMethod method) {
+    assert !rules.isEmpty();
+    if (keptGraphConsumer != null) {
+      for (ProguardKeepRuleBase rule : rules) {
+        reportKeepMethod(precondition, rule, method);
+      }
+    }
+    return KeepReasonWitness.INSTANCE;
+  }
+
+  KeepReasonWitness reportKeepField(
+      DexDefinition precondition, ProguardKeepRuleBase rule, DexEncodedField field) {
+    if (keptGraphConsumer == null) {
+      return KeepReasonWitness.INSTANCE;
+    }
+    KeepRuleGraphNode ruleNode = getKeepRuleGraphNode(precondition, rule);
+    EdgeKind edgeKind = reportPrecondition(ruleNode);
+    return reportEdge(ruleNode, getFieldGraphNode(field.field), edgeKind);
+  }
+
+  KeepReasonWitness reportKeepField(
+      DexDefinition precondition, Collection<ProguardKeepRuleBase> rules, DexEncodedField field) {
+    assert !rules.isEmpty();
+    if (keptGraphConsumer != null) {
+      for (ProguardKeepRuleBase rule : rules) {
+        reportKeepField(precondition, rule, field);
+      }
+    }
+    return KeepReasonWitness.INSTANCE;
+  }
+
+  public KeepReasonWitness reportCompatKeepDefaultInitializer(
+      DexProgramClass holder, DexEncodedMethod defaultInitializer) {
+    assert holder.type == defaultInitializer.method.holder;
+    assert holder.getDefaultInitializer() == defaultInitializer;
+    if (keptGraphConsumer != null) {
+      reportEdge(
+          getClassGraphNode(holder.type),
+          getMethodGraphNode(defaultInitializer.method),
+          EdgeKind.CompatibilityRule);
+    }
+    return KeepReasonWitness.INSTANCE;
+  }
+
+  public KeepReasonWitness reportCompatKeepMethod(DexProgramClass holder, DexEncodedMethod method) {
+    assert holder.type == method.method.holder;
+    // TODO(b/141729349): This compat rule is from the method to itself and has not edge. Fix it.
+    // The rule is stating that if the method is targeted it is live. Since such an edge does
+    // not contribute to additional information in the kept graph as it stands (no distinction
+    // of targeted vs live edges), there is little point in emitting it.
+    return KeepReasonWitness.INSTANCE;
+  }
+
+  public KeepReasonWitness reportCompatInstantiated(
+      DexProgramClass instantiated, DexEncodedMethod method) {
+    if (keptGraphConsumer != null) {
+      reportEdge(
+          getMethodGraphNode(method.method),
+          getClassGraphNode(instantiated.type),
+          EdgeKind.CompatibilityRule);
+    }
+    return KeepReasonWitness.INSTANCE;
+  }
+
+  public KeepReasonWitness reportClassReferencedFrom(
+      DexProgramClass clazz, DexEncodedMethod method) {
+    if (keptGraphConsumer != null) {
+      MethodGraphNode source = getMethodGraphNode(method.method);
+      ClassGraphNode target = getClassGraphNode(clazz.type);
+      return reportEdge(source, target, EdgeKind.ReferencedFrom);
+    }
+    return KeepReasonWitness.INSTANCE;
+  }
+
+  public KeepReasonWitness reportClassReferencedFrom(DexProgramClass clazz, DexEncodedField field) {
+    if (keptGraphConsumer != null) {
+      FieldGraphNode source = getFieldGraphNode(field.field);
+      ClassGraphNode target = getClassGraphNode(clazz.type);
+      return reportEdge(source, target, EdgeKind.ReferencedFrom);
+    }
+    return KeepReasonWitness.INSTANCE;
+  }
+
+  public KeepReasonWitness reportReachableMethodAsLive(
+      DexEncodedMethod encodedMethod, MarkedResolutionTarget reason) {
+    if (keptGraphConsumer != null) {
+      return reportEdge(
+          getMethodGraphNode(reason.method.method),
+          getMethodGraphNode(encodedMethod.method),
+          EdgeKind.OverridingMethod);
+    }
+    return KeepReasonWitness.INSTANCE;
+  }
+
+  public KeepReasonWitness reportReachableMethodAsLive(
+      DexEncodedMethod encodedMethod, Set<MarkedResolutionTarget> reasons) {
+    assert !reasons.isEmpty();
+    if (keptGraphConsumer != null) {
+      MethodGraphNode target = getMethodGraphNode(encodedMethod.method);
+      for (MarkedResolutionTarget reason : reasons) {
+        reportEdge(getMethodGraphNode(reason.method.method), target, EdgeKind.OverridingMethod);
+      }
+    }
+    return KeepReasonWitness.INSTANCE;
+  }
+
+  public KeepReasonWitness reportCompanionClass(DexProgramClass iface, DexProgramClass companion) {
+    assert iface.isInterface();
+    assert InterfaceMethodRewriter.isCompanionClassType(companion.type);
+    if (keptGraphConsumer == null) {
+      return KeepReasonWitness.INSTANCE;
+    }
+    return reportEdge(
+        getClassGraphNode(iface.type), getClassGraphNode(companion.type), EdgeKind.CompanionClass);
+  }
+
+  public KeepReasonWitness reportCompanionMethod(
+      DexEncodedMethod definition, DexEncodedMethod implementation) {
+    assert InterfaceMethodRewriter.isCompanionClassType(implementation.method.holder);
+    if (keptGraphConsumer == null) {
+      return KeepReasonWitness.INSTANCE;
+    }
+    return reportEdge(
+        getMethodGraphNode(definition.method),
+        getMethodGraphNode(implementation.method),
+        EdgeKind.CompanionMethod);
+  }
+
+  private KeepReasonWitness reportEdge(GraphNode source, GraphNode target, EdgeKind kind) {
+    assert keptGraphConsumer != null;
+    keptGraphConsumer.acceptEdge(source, target, getEdgeInfo(kind));
+    return KeepReasonWitness.INSTANCE;
+  }
+
+  /**
+   * Sentinel value indicating that a keep reason has been reported.
+   *
+   * <p>Should only ever be returned by the graph reporter functions.
+   */
+  public static class KeepReasonWitness extends KeepReason {
+
+    private static KeepReasonWitness INSTANCE = new KeepReasonWitness();
+
+    private KeepReasonWitness() {
+      // Only the reporter may create instances.
+    }
+
+    @Override
+    public EdgeKind edgeKind() {
+      throw new Unreachable();
+    }
+
+    @Override
+    public GraphNode getSourceNode(GraphReporter graphReporter) {
+      throw new Unreachable();
+    }
+  }
+
+  private boolean skipReporting(KeepReason reason) {
+    assert reason != null;
+    if (reason == KeepReasonWitness.INSTANCE) {
+      return true;
+    }
+    assert getSourceNode(reason) != null;
+    return keptGraphConsumer == null;
+  }
+
+  public KeepReasonWitness registerInterface(DexProgramClass iface, KeepReason reason) {
+    assert iface.isInterface();
+    if (skipReporting(reason)) {
+      return KeepReasonWitness.INSTANCE;
+    }
+    return registerEdge(getClassGraphNode(iface.type), reason);
+  }
+
+  public KeepReasonWitness registerClass(DexProgramClass clazz, KeepReason reason) {
+    if (skipReporting(reason)) {
+      return KeepReasonWitness.INSTANCE;
+    }
+    return registerEdge(getClassGraphNode(clazz.type), reason);
+  }
+
+  public KeepReasonWitness registerAnnotation(DexAnnotation annotation, KeepReason reason) {
+    if (skipReporting(reason)) {
+      return KeepReasonWitness.INSTANCE;
+    }
+    return registerEdge(getAnnotationGraphNode(annotation.annotation.type), reason);
+  }
+
+  public KeepReasonWitness registerMethod(DexEncodedMethod method, KeepReason reason) {
+    if (skipReporting(reason)) {
+      return KeepReasonWitness.INSTANCE;
+    }
+    if (reason.edgeKind() == EdgeKind.IsLibraryMethod && isNonProgramClass(method.method.holder)) {
+      // Don't report edges to actual library methods.
+      // TODO(b/120959039): This should be dead code once no library classes are ever enqueued.
+      return KeepReasonWitness.INSTANCE;
+    }
+    return registerEdge(getMethodGraphNode(method.method), reason);
+  }
+
+  public KeepReasonWitness registerField(DexEncodedField field, KeepReason reason) {
+    if (skipReporting(reason)) {
+      return KeepReasonWitness.INSTANCE;
+    }
+    return registerEdge(getFieldGraphNode(field.field), reason);
+  }
+
+  private KeepReasonWitness registerEdge(GraphNode target, KeepReason reason) {
+    assert !skipReporting(reason);
+    GraphNode sourceNode = getSourceNode(reason);
+    // TODO(b/120959039): Make sure we do have edges to nodes deriving library nodes!
+    if (!sourceNode.isLibraryNode()) {
+      GraphEdgeInfo edgeInfo = getEdgeInfo(reason);
+      keptGraphConsumer.acceptEdge(sourceNode, target, edgeInfo);
+    }
+    return KeepReasonWitness.INSTANCE;
+  }
+
+  private boolean isNonProgramClass(DexType type) {
+    DexClass clazz = appView.definitionFor(type);
+    return clazz == null || clazz.isNotProgramClass();
+  }
+
+  private GraphNode getSourceNode(KeepReason reason) {
+    return reason.getSourceNode(this);
+  }
+
+  public GraphNode getGraphNode(DexReference reference) {
+    if (reference.isDexType()) {
+      return getClassGraphNode(reference.asDexType());
+    }
+    if (reference.isDexMethod()) {
+      return getMethodGraphNode(reference.asDexMethod());
+    }
+    if (reference.isDexField()) {
+      return getFieldGraphNode(reference.asDexField());
+    }
+    throw new Unreachable();
+  }
+
+  GraphEdgeInfo getEdgeInfo(KeepReason reason) {
+    return getEdgeInfo(reason.edgeKind());
+  }
+
+  GraphEdgeInfo getEdgeInfo(EdgeKind kind) {
+    return reasonInfo.computeIfAbsent(kind, k -> new GraphEdgeInfo(k));
+  }
+
+  AnnotationGraphNode getAnnotationGraphNode(DexItem type) {
+    return annotationNodes.computeIfAbsent(
+        type,
+        t -> {
+          if (t instanceof DexType) {
+            return new AnnotationGraphNode(getClassGraphNode(((DexType) t)));
+          }
+          throw new Unimplemented(
+              "Incomplete support for annotation node on item: " + type.getClass());
+        });
+  }
+
+  ClassGraphNode getClassGraphNode(DexType type) {
+    return classNodes.computeIfAbsent(
+        type,
+        t -> {
+          DexClass definition = appView.definitionFor(t);
+          return new ClassGraphNode(
+              definition != null && definition.isNotProgramClass(),
+              Reference.classFromDescriptor(t.toDescriptorString()));
+        });
+  }
+
+  MethodGraphNode getMethodGraphNode(DexMethod context) {
+    return methodNodes.computeIfAbsent(
+        context,
+        m -> {
+          DexClass holderDefinition = appView.definitionFor(context.holder);
+          Builder<TypeReference> builder = ImmutableList.builder();
+          for (DexType param : m.proto.parameters.values) {
+            builder.add(Reference.typeFromDescriptor(param.toDescriptorString()));
+          }
+          return new MethodGraphNode(
+              holderDefinition != null && holderDefinition.isNotProgramClass(),
+              Reference.method(
+                  Reference.classFromDescriptor(m.holder.toDescriptorString()),
+                  m.name.toString(),
+                  builder.build(),
+                  m.proto.returnType.isVoidType()
+                      ? null
+                      : Reference.typeFromDescriptor(m.proto.returnType.toDescriptorString())));
+        });
+  }
+
+  FieldGraphNode getFieldGraphNode(DexField context) {
+    return fieldNodes.computeIfAbsent(
+        context,
+        f -> {
+          DexClass holderDefinition = appView.definitionFor(context.holder);
+          return new FieldGraphNode(
+              holderDefinition != null && holderDefinition.isNotProgramClass(),
+              Reference.field(
+                  Reference.classFromDescriptor(f.holder.toDescriptorString()),
+                  f.name.toString(),
+                  Reference.typeFromDescriptor(f.type.toDescriptorString())));
+        });
+  }
+
+  // Due to the combined encoding of dependent rules, ala keepclassmembers and conditional keep
+  // rules the conversion to a keep-rule graph node can be one of three forms:
+  // 1. A non-dependent keep rule. In this case precondtion == null and the rule is not an if-rule.
+  // 2. A dependent keep rule. In this case precondtion != null and rule is not an if-rule.
+  // 3. A conditional keep rule. In this case rule is an if-rule, but precondition may or may not be
+  //    null. In the non-null case, the precondition is the type the consequent may depend on,
+  //    say T for the consequent "-keep T { f; }". It is *not* the precondition of the conditional
+  //    rule.
+  KeepRuleGraphNode getKeepRuleGraphNode(DexDefinition precondition, ProguardKeepRuleBase rule) {
+    if (rule instanceof ProguardKeepRule) {
+      Set<GraphNode> preconditions =
+          precondition != null
+              ? Collections.singleton(getGraphNode(precondition.toReference()))
+              : Collections.emptySet();
+      return ruleNodes.computeIfAbsent(rule, key -> new KeepRuleGraphNode(rule, preconditions));
+    }
+    if (rule instanceof ProguardIfRule) {
+      ProguardIfRule ifRule = (ProguardIfRule) rule;
+      assert !ifRule.getPreconditions().isEmpty();
+      return ruleNodes.computeIfAbsent(
+          ifRule,
+          key -> {
+            Set<GraphNode> preconditions = new HashSet<>(ifRule.getPreconditions().size());
+            for (DexReference condition : ifRule.getPreconditions()) {
+              preconditions.add(getGraphNode(condition));
+            }
+            return new KeepRuleGraphNode(ifRule, preconditions);
+          });
+    }
+    throw new Unreachable("Unexpected type of keep rule: " + rule);
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/shaking/KeepReason.java b/src/main/java/com/android/tools/r8/shaking/KeepReason.java
index 43b875a..680c0b5 100644
--- a/src/main/java/com/android/tools/r8/shaking/KeepReason.java
+++ b/src/main/java/com/android/tools/r8/shaking/KeepReason.java
@@ -18,7 +18,7 @@
 
   public abstract GraphEdgeInfo.EdgeKind edgeKind();
 
-  public abstract GraphNode getSourceNode(Enqueuer enqueuer);
+  public abstract GraphNode getSourceNode(GraphReporter graphReporter);
 
   static KeepReason annotatedOn(DexDefinition definition) {
     return new AnnotatedOn(definition);
@@ -64,18 +64,6 @@
     return false;
   }
 
-  public boolean isDueToProguardCompatibility() {
-    return false;
-  }
-
-  public boolean isInstantiatedIn() {
-    return false;
-  }
-
-  public InstatiatedIn asInstantiatedIn() {
-    return null;
-  }
-
   public static KeepReason targetedBySuperFrom(DexEncodedMethod from) {
     return new TargetedBySuper(from);
   }
@@ -103,8 +91,8 @@
     }
 
     @Override
-    public GraphNode getSourceNode(Enqueuer enqueuer) {
-      return enqueuer.getMethodGraphNode(method.method);
+    public GraphNode getSourceNode(GraphReporter graphReporter) {
+      return graphReporter.getMethodGraphNode(method.method);
     }
   }
 
@@ -115,16 +103,6 @@
     }
 
     @Override
-    public boolean isInstantiatedIn() {
-      return true;
-    }
-
-    @Override
-    public InstatiatedIn asInstantiatedIn() {
-      return this;
-    }
-
-    @Override
     public EdgeKind edgeKind() {
       return EdgeKind.InstantiatedIn;
     }
@@ -235,8 +213,8 @@
     }
 
     @Override
-    public GraphNode getSourceNode(Enqueuer enqueuer) {
-      return enqueuer.getClassGraphNode(type);
+    public GraphNode getSourceNode(GraphReporter graphReporter) {
+      return graphReporter.getClassGraphNode(type);
     }
   }
 
@@ -256,8 +234,8 @@
     }
 
     @Override
-    public GraphNode getSourceNode(Enqueuer enqueuer) {
-      return enqueuer.getClassGraphNode(implementer);
+    public GraphNode getSourceNode(GraphReporter graphReporter) {
+      return graphReporter.getClassGraphNode(implementer);
     }
   }
 
@@ -275,8 +253,8 @@
     }
 
     @Override
-    public GraphNode getSourceNode(Enqueuer enqueuer) {
-      return enqueuer.getAnnotationGraphNode(holder);
+    public GraphNode getSourceNode(GraphReporter graphReporter) {
+      return graphReporter.getAnnotationGraphNode(holder);
     }
   }
 
@@ -294,14 +272,14 @@
     }
 
     @Override
-    public GraphNode getSourceNode(Enqueuer enqueuer) {
+    public GraphNode getSourceNode(GraphReporter graphReporter) {
       if (holder.isDexClass()) {
-        return enqueuer.getClassGraphNode(holder.asDexClass().type);
+        return graphReporter.getClassGraphNode(holder.asDexClass().type);
       } else if (holder.isDexEncodedField()) {
-        return enqueuer.getFieldGraphNode(holder.asDexEncodedField().field);
+        return graphReporter.getFieldGraphNode(holder.asDexEncodedField().field);
       } else {
         assert holder.isDexEncodedMethod();
-        return enqueuer.getMethodGraphNode(holder.asDexEncodedMethod().method);
+        return graphReporter.getMethodGraphNode(holder.asDexEncodedMethod().method);
       }
     }
   }
diff --git a/src/test/java/com/android/tools/r8/D8CommandTest.java b/src/test/java/com/android/tools/r8/D8CommandTest.java
index 1347279..ae63272 100644
--- a/src/test/java/com/android/tools/r8/D8CommandTest.java
+++ b/src/test/java/com/android/tools/r8/D8CommandTest.java
@@ -525,19 +525,6 @@
         "Missing parameter", handler -> parse(handler, "--output"));
   }
 
-  @Test
-  public void warnForSpecialLibraryConfiguration() throws Throwable {
-    Path emptyZip = temp.getRoot().toPath().resolve("empty.zip");
-    DiagnosticsChecker.checkWarningsContains(
-        "Desugared library configuration is still work in progress",
-        handler ->
-            D8Command.builder(handler)
-                .addDesugaredLibraryConfiguration(
-                    StringResource.fromFile(ToolHelper.DESUGAR_LIB_JSON_FOR_TESTING))
-                .setOutput(emptyZip, OutputMode.DexIndexed)
-                .build());
-  }
-
   private D8Command parse(String... args) throws CompilationFailedException {
     return D8Command.parse(args, EmbeddedOrigin.INSTANCE).build();
   }
diff --git a/src/test/java/com/android/tools/r8/L8CommandTest.java b/src/test/java/com/android/tools/r8/L8CommandTest.java
index 2370b74..7a72d1b 100644
--- a/src/test/java/com/android/tools/r8/L8CommandTest.java
+++ b/src/test/java/com/android/tools/r8/L8CommandTest.java
@@ -114,18 +114,6 @@
   }
 
   @Test
-  public void warnForSpecialLibraryConfiguration() throws Throwable {
-    DiagnosticsChecker.checkWarningsContains(
-        "Desugared library configuration is still work in progress",
-        handler ->
-            prepareBuilder(handler)
-                .setProgramConsumer(DexIndexedConsumer.emptyConsumer())
-                .addDesugaredLibraryConfiguration(
-                    StringResource.fromFile(ToolHelper.DESUGAR_LIB_JSON_FOR_TESTING))
-                .build());
-  }
-
-  @Test
   public void addProguardConfigurationString() throws Throwable {
     String keepRule = "-keep class java.time.*";
     List<String> keepRules = new ArrayList<>();
diff --git a/src/test/java/com/android/tools/r8/TestParametersBuilder.java b/src/test/java/com/android/tools/r8/TestParametersBuilder.java
index 9a6752f..e64a95f 100644
--- a/src/test/java/com/android/tools/r8/TestParametersBuilder.java
+++ b/src/test/java/com/android/tools/r8/TestParametersBuilder.java
@@ -199,12 +199,12 @@
       return Stream.of();
     }
     AndroidApiLevel vmLevel = runtime.asDex().getMinApiLevel();
-    AndroidApiLevel lowestApplicable = sortedApiLevels.get(sortedApiLevels.size() - 1);
+    AndroidApiLevel lowestApplicable = sortedApiLevels.get(0);
     if (vmLevel.getLevel() < lowestApplicable.getLevel()) {
       return Stream.of();
     }
     if (sortedApiLevels.size() > 1) {
-      for (int i = 0; i < sortedApiLevels.size(); i++) {
+      for (int i = sortedApiLevels.size() - 1; i >= 0; i--) {
         AndroidApiLevel highestApplicable = sortedApiLevels.get(i);
         if (highestApplicable.getLevel() <= vmLevel.getLevel()
             && lowestApplicable != highestApplicable) {
diff --git a/src/test/java/com/android/tools/r8/ToolHelper.java b/src/test/java/com/android/tools/r8/ToolHelper.java
index ef32ed5..0464ccb 100644
--- a/src/test/java/com/android/tools/r8/ToolHelper.java
+++ b/src/test/java/com/android/tools/r8/ToolHelper.java
@@ -632,7 +632,7 @@
       Assert.fail(ioe.toString());
     }
     CRC32 crc = new CRC32();
-    crc.update(bytes);
+    crc.update(bytes, 0, bytes.length);
     return crc.getValue();
   }
 
diff --git a/src/test/java/com/android/tools/r8/classlookup/LibraryClassExtendsProgramClassTest.java b/src/test/java/com/android/tools/r8/classlookup/LibraryClassExtendsProgramClassTest.java
index 6c64801..231f2c1 100644
--- a/src/test/java/com/android/tools/r8/classlookup/LibraryClassExtendsProgramClassTest.java
+++ b/src/test/java/com/android/tools/r8/classlookup/LibraryClassExtendsProgramClassTest.java
@@ -9,6 +9,7 @@
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.fail;
+import static org.junit.Assume.assumeFalse;
 import static org.junit.Assume.assumeTrue;
 
 import com.android.tools.r8.CompilationFailedException;
@@ -77,6 +78,7 @@
 
   @Test
   public void testFullMode() throws Exception {
+    assumeFalse(parameters.getApiLevel().getLevel() == 28);
     testForR8(parameters.getBackend())
         .setMinApi(parameters.getApiLevel())
         .addProgramClasses(TestClass.class)
@@ -91,6 +93,7 @@
 
   @Test
   public void testCompatibilityMode() throws Exception {
+    assumeFalse(parameters.getApiLevel().getLevel() == 28);
     testForR8Compat(parameters.getBackend())
         .setMinApi(parameters.getApiLevel())
         .addProgramClasses(TestClass.class)
@@ -117,6 +120,7 @@
 
   @Test
   public void testFullModeError() {
+    assumeFalse(parameters.getApiLevel().getLevel() == 28);
     assumeTrue("Only run for Dex backend", parameters.isDexRuntime());
     try {
       testForR8(parameters.getBackend())
@@ -134,6 +138,7 @@
 
   @Test
   public void testCompatibilityModeWarning() throws Exception {
+    assumeFalse(parameters.getApiLevel().getLevel() == 28);
     assumeTrue("Only run for Dex backend", parameters.isDexRuntime());
     R8TestCompileResult result =
         testForR8Compat(parameters.getBackend())
diff --git a/src/test/java/com/android/tools/r8/desugar/corelib/CustomCollectionTest.java b/src/test/java/com/android/tools/r8/desugar/corelib/CustomCollectionTest.java
index c8a05f8..9a8bb6b 100644
--- a/src/test/java/com/android/tools/r8/desugar/corelib/CustomCollectionTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/corelib/CustomCollectionTest.java
@@ -10,6 +10,7 @@
 import com.android.tools.r8.D8TestRunResult;
 import com.android.tools.r8.R8TestRunResult;
 import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.ToolHelper.DexVm;
 import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.android.tools.r8.utils.codeinspector.InstructionSubject;
@@ -26,6 +27,7 @@
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
 import org.junit.Assert;
+import org.junit.Assume;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
@@ -53,6 +55,10 @@
 
   @Test
   public void testCustomCollectionD8() throws Exception {
+    // TODO(b/142377475).
+    Assume.assumeTrue(!shrinkDesugaredLibrary);
+    // TODO(b/142377161).
+    Assume.assumeTrue(parameters.getRuntime().asDex().getVm().isNewerThan(DexVm.ART_4_4_4_HOST));
     KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters);
     D8TestRunResult d8TestRunResult =
         testForD8()
@@ -83,6 +89,8 @@
 
   @Test
   public void testCustomCollectionR8() throws Exception {
+    // TODO(b/142377161).
+    Assume.assumeTrue(parameters.getRuntime().asDex().getVm().isNewerThan(DexVm.ART_4_4_4_HOST));
     KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters);
     R8TestRunResult r8TestRunResult =
         testForR8(Backend.DEX)
diff --git a/src/test/java/com/android/tools/r8/desugar/corelib/DisableDesugarTest.java b/src/test/java/com/android/tools/r8/desugar/corelib/DisableDesugarTest.java
index ca2ff2b..cb928cc 100644
--- a/src/test/java/com/android/tools/r8/desugar/corelib/DisableDesugarTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/corelib/DisableDesugarTest.java
@@ -34,10 +34,7 @@
 
   private void checkExpectedDiagnostics(TestDiagnosticMessages messages) {
     messages.assertInfosCount(0);
-    messages.assertWarningsCount(1);
-    assertThat(
-        messages.getWarnings().get(0).getDiagnosticMessage(),
-        containsString("Desugared library configuration is still work in progress"));
+    messages.assertWarningsCount(0);
     messages.assertErrorsCount(1);
     assertThat(
         messages.getErrors().get(0).getDiagnosticMessage(),
diff --git a/src/test/java/com/android/tools/r8/desugar/corelib/JavaUtilFunctionTest.java b/src/test/java/com/android/tools/r8/desugar/corelib/JavaUtilFunctionTest.java
index 30fd9d3..2ddacd7 100644
--- a/src/test/java/com/android/tools/r8/desugar/corelib/JavaUtilFunctionTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/corelib/JavaUtilFunctionTest.java
@@ -10,6 +10,8 @@
 
 import com.android.tools.r8.NeverInline;
 import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.ir.desugar.DesugaredLibraryWrapperSynthesizer;
+import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.StringUtils;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
@@ -65,6 +67,7 @@
         .addInnerClasses(JavaUtilFunctionTest.class)
         .setMinApi(parameters.getApiLevel())
         .enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer)
+        .setIncludeClassesChecksum(true)
         .compile()
         .inspect(this::checkRewrittenArguments)
         .addDesugaredCoreLibraryRunClassPath(
@@ -100,6 +103,40 @@
         .assertSuccessWithOutput(expectedOutput);
   }
 
+  @Test
+  public void testWrapperWithChecksum() throws Exception {
+    KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters);
+    testForD8()
+        .addInnerClasses(JavaUtilFunctionTest.class)
+        .setMinApi(parameters.getApiLevel())
+        .enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer)
+        .setIncludeClassesChecksum(true) // Compilation fails if some classes are missing checksum.
+        .compile()
+        .inspect(
+            inspector -> {
+              assertEquals(
+                  parameters.getApiLevel().getLevel() >= AndroidApiLevel.N.getLevel() ? 0 : 1,
+                  inspector.allClasses().stream()
+                      .filter(
+                          clazz ->
+                              clazz
+                                  .getFinalName()
+                                  .contains(DesugaredLibraryWrapperSynthesizer.TYPE_WRAPPER_SUFFIX))
+                      .count());
+              assertEquals(
+                  parameters.getApiLevel().getLevel() >= AndroidApiLevel.N.getLevel() ? 0 : 1,
+                  inspector.allClasses().stream()
+                      .filter(
+                          clazz ->
+                              clazz
+                                  .getFinalName()
+                                  .contains(
+                                      DesugaredLibraryWrapperSynthesizer
+                                          .VIVIFIED_TYPE_WRAPPER_SUFFIX))
+                      .count());
+            });
+  }
+
   static class TestClass {
 
     @NeverInline
diff --git a/src/test/java/com/android/tools/r8/desugar/corelib/conversionTests/APIConversionFinalWarningTest.java b/src/test/java/com/android/tools/r8/desugar/corelib/conversionTests/APIConversionFinalWarningTest.java
index 10b6d75..48d836d 100644
--- a/src/test/java/com/android/tools/r8/desugar/corelib/conversionTests/APIConversionFinalWarningTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/corelib/conversionTests/APIConversionFinalWarningTest.java
@@ -25,7 +25,7 @@
         .addLibraryClasses(CustomLibClass.class)
         .enableCoreLibraryDesugaring(AndroidApiLevel.B)
         .compile()
-        .assertWarningMessageThatMatches(
+        .assertInfoMessageThatMatches(
             startsWith(
                 "Desugared library API conversion: cannot wrap final methods"
                     + " [java.util.LongSummaryStatistics"))
@@ -36,7 +36,7 @@
         .assertSuccessWithOutput(
             StringUtils.lines(
                 "Unsupported conversion for java.util.LongSummaryStatistics. See compilation time"
-                    + " warnings for more infos."));
+                    + " infos for more details."));
   }
 
   static class Executor {
diff --git a/src/test/java/com/android/tools/r8/desugar/corelib/conversionTests/APIConversionLargeWarningTest.java b/src/test/java/com/android/tools/r8/desugar/corelib/conversionTests/APIConversionLargeWarningTest.java
index efbe9b6..1818802 100644
--- a/src/test/java/com/android/tools/r8/desugar/corelib/conversionTests/APIConversionLargeWarningTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/corelib/conversionTests/APIConversionLargeWarningTest.java
@@ -24,14 +24,14 @@
         .addLibraryClasses(CustomLibClass.class)
         .enableCoreLibraryDesugaring(AndroidApiLevel.B)
         .compile()
-        .assertWarningMessageThatMatches(
+        .assertInfoMessageThatMatches(
             startsWith(
                 "Desugared library API conversion: Generating a large wrapper for"
                     + " java.util.stream.Stream"))
-        .assertNoWarningMessageThatMatches(
+        .assertNoInfoMessageThatMatches(
             startsWith(
                 "Desugared library API conversion: Generating a large wrapper for java.time.Clock"))
-        .assertNoWarningMessageThatMatches(
+        .assertNoInfoMessageThatMatches(
             startsWith(
                 "Desugared library API conversion: Generating a large wrapper for"
                     + " java.util.function.Function"));
diff --git a/src/test/java/com/android/tools/r8/desugar/corelib/conversionTests/APIConversionTest.java b/src/test/java/com/android/tools/r8/desugar/corelib/conversionTests/APIConversionTest.java
index e3e8c77..6bbf8a1 100644
--- a/src/test/java/com/android/tools/r8/desugar/corelib/conversionTests/APIConversionTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/corelib/conversionTests/APIConversionTest.java
@@ -65,9 +65,9 @@
         .assertSuccessWithOutput(
             StringUtils.lines(
                 "[5, 6, 7]",
-                "j$.util.stream.IntStream$-V-WRP",
+                "$r8$wrapper$java$util$stream$IntStream$-V-WRP",
                 "Unsupported conversion for java.util.IntSummaryStatistics. See compilation time"
-                    + " warnings for more infos."));
+                    + " infos for more details."));
   }
 
   static class Executor {
diff --git a/src/test/java/com/android/tools/r8/desugar/corelib/conversionTests/CallBackConversionTest.java b/src/test/java/com/android/tools/r8/desugar/corelib/conversionTests/CallBackConversionTest.java
index f42a03d..49ee9af 100644
--- a/src/test/java/com/android/tools/r8/desugar/corelib/conversionTests/CallBackConversionTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/corelib/conversionTests/CallBackConversionTest.java
@@ -11,6 +11,7 @@
 import com.android.tools.r8.ToolHelper.DexVm;
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.android.tools.r8.utils.codeinspector.FoundMethodSubject;
 import java.nio.file.Path;
 import java.util.List;
@@ -63,6 +64,61 @@
         .assertSuccessWithOutput(StringUtils.lines("0", "1", "0", "1"));
   }
 
+  @Test
+  public void testCallBackR8() throws Exception {
+    Path customLib = testForD8().addProgramClasses(CustomLibClass.class).compile().writeToZip();
+    testForR8(Backend.DEX)
+        .addKeepMainRule(Impl.class)
+        .noMinification()
+        .setMinApi(AndroidApiLevel.B)
+        .addProgramClasses(Impl.class)
+        .addLibraryClasses(CustomLibClass.class)
+        .enableCoreLibraryDesugaring(AndroidApiLevel.B)
+        .compile()
+        .inspect(this::assertLibraryOverridesThere)
+        .addDesugaredCoreLibraryRunClassPath(
+            this::buildDesugaredLibraryWithConversionExtension, AndroidApiLevel.B)
+        .addRunClasspathFiles(customLib)
+        .run(new DexRuntime(DexVm.ART_9_0_0_HOST), Impl.class)
+        .assertSuccessWithOutput(StringUtils.lines("0", "1", "0", "1"));
+  }
+
+  @Test
+  public void testCallBackR8Minifying() throws Exception {
+    Path customLib = testForD8().addProgramClasses(CustomLibClass.class).compile().writeToZip();
+    testForR8(Backend.DEX)
+        .addKeepMainRule(Impl.class)
+        .setMinApi(AndroidApiLevel.B)
+        .addProgramClasses(Impl.class)
+        .addLibraryClasses(CustomLibClass.class)
+        .enableCoreLibraryDesugaring(AndroidApiLevel.B)
+        .compile()
+        .inspect(this::assertLibraryOverridesThere)
+        .addDesugaredCoreLibraryRunClassPath(
+            this::buildDesugaredLibraryWithConversionExtension, AndroidApiLevel.B)
+        .addRunClasspathFiles(customLib)
+        .run(new DexRuntime(DexVm.ART_9_0_0_HOST), Impl.class)
+        .assertSuccessWithOutput(StringUtils.lines("0", "1", "0", "1"));
+  }
+
+  private void assertLibraryOverridesThere(CodeInspector i) {
+    // The j$ method can be optimized, but the java method should be present to be called
+    // through the library.
+    List<FoundMethodSubject> virtualMethods = i.clazz(Impl.class).virtualMethods();
+    assertTrue(
+        virtualMethods.stream()
+            .anyMatch(
+                m ->
+                    m.getMethod().method.name.toString().equals("foo")
+                        && m.getMethod()
+                            .method
+                            .proto
+                            .parameters
+                            .values[0]
+                            .toString()
+                            .equals("java.util.function.Consumer")));
+  }
+
   static class Impl extends CustomLibClass {
 
     public int foo(Consumer<Object> o) {
diff --git a/src/test/java/com/android/tools/r8/desugar/corelib/conversionTests/MoreFunctionConversionTest.java b/src/test/java/com/android/tools/r8/desugar/corelib/conversionTests/MoreFunctionConversionTest.java
new file mode 100644
index 0000000..80a61e7
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/corelib/conversionTests/MoreFunctionConversionTest.java
@@ -0,0 +1,142 @@
+// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.desugar.corelib.conversionTests;
+
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.D8TestCompileResult;
+import com.android.tools.r8.TestRuntime.DexRuntime;
+import com.android.tools.r8.ToolHelper.DexVm;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.FoundClassSubject;
+import com.google.common.collect.Sets;
+import java.nio.file.Path;
+import java.util.Set;
+import java.util.function.Function;
+import org.junit.Test;
+
+public class MoreFunctionConversionTest extends APIConversionTestBase {
+
+  @Test
+  public void testFunction() throws Exception {
+    Path customLib = testForD8().addProgramClasses(CustomLibClass.class).compile().writeToZip();
+    D8TestCompileResult compileResult =
+        testForD8()
+            .setMinApi(AndroidApiLevel.B)
+            .addProgramClasses(Executor.class)
+            .addLibraryClasses(CustomLibClass.class)
+            .enableCoreLibraryDesugaring(AndroidApiLevel.B)
+            .compile();
+    Path program = compileResult.writeToZip();
+    assertNoDuplicateLambdas(program, customLib);
+    compileResult
+        .addDesugaredCoreLibraryRunClassPath(
+            this::buildDesugaredLibraryWithConversionExtension, AndroidApiLevel.B)
+        .addRunClasspathFiles(customLib)
+        .run(new DexRuntime(DexVm.ART_9_0_0_HOST), Executor.class)
+        // TODO(clement): Make output reliable and put back expected results.
+        .assertSuccess();
+  }
+
+  // If we have the exact same lambda in both, but one implements j$..Function and the other
+  // java..Function, ART is obviously very confused.
+  private void assertNoDuplicateLambdas(Path program, Path customLib) throws Exception {
+    CodeInspector programInspector = new CodeInspector(program);
+    CodeInspector customLibInspector = new CodeInspector(customLib);
+    int programSize = programInspector.allClasses().size();
+    int customLibSize = customLibInspector.allClasses().size();
+    Set<String> foundClassSubjects = Sets.newHashSet();
+    for (FoundClassSubject aClass : programInspector.allClasses()) {
+      foundClassSubjects.add(aClass.getOriginalName());
+    }
+    for (FoundClassSubject aClass : customLibInspector.allClasses()) {
+      foundClassSubjects.add(aClass.getOriginalName());
+    }
+    assertEquals(foundClassSubjects.size(), programSize + customLibSize);
+  }
+
+  @SuppressWarnings("WeakerAccess")
+  static class Executor {
+
+    public static void main(String[] args) {
+      returnOnly();
+      oneParameter();
+      twoParameters();
+      oneParameterReturn();
+      twoParametersReturn();
+    }
+
+    public static void returnOnly() {
+      Function<Object, Integer> returnOnly = CustomLibClass.returnOnly();
+      System.out.println(returnOnly.apply(new Object()));
+    }
+
+    public static void oneParameterReturn() {
+      Function<Object, String> toString = Object::toString;
+      Function<Object, Integer> oneParam = CustomLibClass.oneParameterReturn(toString);
+      System.out.println(oneParam.apply(new Object()));
+    }
+
+    public static void twoParametersReturn() {
+      Function<Object, String> toString = Object::toString;
+      Function<String, Integer> length = String::length;
+      Function<Object, Integer> twoParam = CustomLibClass.twoParametersReturn(toString, length);
+      System.out.println(twoParam.apply(new Object()));
+    }
+
+    public static void oneParameter() {
+      Function<Object, String> toString = Object::toString;
+      int res = CustomLibClass.oneParameter(toString);
+      System.out.println(res);
+    }
+
+    public static void twoParameters() {
+      Function<Object, String> toString = Object::toString;
+      Function<String, Integer> length = String::length;
+      int res = CustomLibClass.twoParameters(toString, length);
+      System.out.println(res);
+    }
+  }
+
+  // This class will be put at compilation time as library and on the runtime class path.
+  // This class is convenient for easy testing. Each method plays the role of methods in the
+  // platform APIs for which argument/return values need conversion.
+  @SuppressWarnings("WeakerAccess")
+  static class CustomLibClass {
+
+    public static Function<Object, Integer> returnOnly() {
+      Function<Object, String> toString = getObjectStringConv();
+      return toString.andThen(getStringIntConv());
+    }
+
+    public static Function<Object, Integer> oneParameterReturn(Function<Object, String> f1) {
+      return f1.andThen(getStringIntConv());
+    }
+
+    public static Function<Object, Integer> twoParametersReturn(
+        Function<Object, String> f1, Function<String, Integer> f2) {
+      return f1.andThen(f2);
+    }
+
+    public static int oneParameter(Function<Object, String> f1) {
+      return f1.andThen(getStringIntConv()).apply(new Object());
+    }
+
+    public static int twoParameters(Function<Object, String> f1, Function<String, Integer> f2) {
+      return f1.andThen(f2).apply(new Object());
+    }
+
+    // Following functions are defined to avoid name collision with the program.
+    public static Function<String, Integer> getStringIntConv() {
+      return (String s) -> 1 + s.length() - 1;
+    }
+
+    public static Function<Object, String> getObjectStringConv() {
+      return (Object o) -> "" + o.toString() + "";
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/corelib/conversionTests/TryCatchTimeConversionTest.java b/src/test/java/com/android/tools/r8/desugar/corelib/conversionTests/TryCatchTimeConversionTest.java
new file mode 100644
index 0000000..7fdae41
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/corelib/conversionTests/TryCatchTimeConversionTest.java
@@ -0,0 +1,211 @@
+// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.desugar.corelib.conversionTests;
+
+import com.android.tools.r8.TestRuntime.DexRuntime;
+import com.android.tools.r8.ToolHelper.DexVm;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.StringUtils;
+import java.nio.file.Path;
+import java.time.ZoneId;
+import org.junit.Test;
+
+public class TryCatchTimeConversionTest extends APIConversionTestBase {
+
+  @Test
+  public void testBaseline() throws Exception {
+    Path customLib = testForD8().addProgramClasses(CustomLibClass.class).compile().writeToZip();
+    testForD8()
+        .setMinApi(AndroidApiLevel.B)
+        .addProgramClasses(BaselineExecutor.class)
+        .addLibraryClasses(CustomLibClass.class)
+        .enableCoreLibraryDesugaring(AndroidApiLevel.B)
+        .compile()
+        .addDesugaredCoreLibraryRunClassPath(
+            this::buildDesugaredLibraryWithConversionExtension, AndroidApiLevel.B)
+        .addRunClasspathFiles(customLib)
+        .run(new DexRuntime(DexVm.ART_9_0_0_HOST), BaselineExecutor.class)
+        .assertSuccessWithOutput(StringUtils.lines("GMT", "GMT", "GMT", "GMT", "GMT"));
+  }
+
+  @Test
+  public void testTryCatch() throws Exception {
+    Path customLib = testForD8().addProgramClasses(CustomLibClass.class).compile().writeToZip();
+    testForD8()
+        .setMinApi(AndroidApiLevel.B)
+        .addProgramClasses(TryCatchExecutor.class)
+        .addLibraryClasses(CustomLibClass.class)
+        .enableCoreLibraryDesugaring(AndroidApiLevel.B)
+        .compile()
+        .addDesugaredCoreLibraryRunClassPath(
+            this::buildDesugaredLibraryWithConversionExtension, AndroidApiLevel.B)
+        .addRunClasspathFiles(customLib)
+        .run(new DexRuntime(DexVm.ART_9_0_0_HOST), TryCatchExecutor.class)
+        .assertSuccessWithOutput(
+            StringUtils.lines("GMT", "GMT", "GMT", "GMT", "GMT", "Exception caught"));
+  }
+
+  @SuppressWarnings("WeakerAccess")
+  static class BaselineExecutor {
+
+    private static final String ZONE_ID = "GMT";
+
+    public static void main(String[] args) {
+      returnOnly();
+      oneParameter();
+      twoParameters();
+      oneParameterReturn();
+      twoParametersReturn();
+    }
+
+    public static void returnOnly() {
+      ZoneId returnOnly = CustomLibClass.returnOnly();
+      System.out.println(returnOnly.getId());
+    }
+
+    public static void oneParameterReturn() {
+      ZoneId z1 = ZoneId.of(ZONE_ID);
+      ZoneId oneParam = CustomLibClass.oneParameterReturn(z1);
+      System.out.println(oneParam.getId());
+    }
+
+    public static void twoParametersReturn() {
+      ZoneId z1 = ZoneId.of(ZONE_ID);
+      ZoneId z2 = ZoneId.of(ZONE_ID);
+      ZoneId twoParam = CustomLibClass.twoParametersReturn(z1, z2);
+      System.out.println(twoParam.getId());
+    }
+
+    public static void oneParameter() {
+      ZoneId z1 = ZoneId.of(ZONE_ID);
+      String res = CustomLibClass.oneParameter(z1);
+      System.out.println(res);
+    }
+
+    public static void twoParameters() {
+      ZoneId z1 = ZoneId.of(ZONE_ID);
+      ZoneId z2 = ZoneId.of(ZONE_ID);
+      String res = CustomLibClass.twoParameters(z1, z2);
+      System.out.println(res);
+    }
+  }
+
+  @SuppressWarnings("WeakerAccess")
+  static class TryCatchExecutor {
+
+    private static final String ZONE_ID = "GMT";
+
+    public static void main(String[] args) {
+      returnOnly();
+      oneParameter();
+      twoParameters();
+      oneParameterReturn();
+      twoParametersReturn();
+      twoParametersThrow();
+    }
+
+    public static void returnOnly() {
+      ZoneId returnOnly;
+      try {
+        returnOnly = CustomLibClass.returnOnly();
+      } catch (Exception e) {
+        throw new RuntimeException("Test failed.");
+      }
+      System.out.println(returnOnly.getId());
+    }
+
+    public static void oneParameterReturn() {
+      ZoneId z1 = ZoneId.of(ZONE_ID);
+      ZoneId oneParam;
+      try {
+        oneParam = CustomLibClass.oneParameterReturn(z1);
+      } catch (Exception e) {
+        throw new RuntimeException("Test failed.");
+      }
+      System.out.println(oneParam.getId());
+    }
+
+    public static void twoParametersReturn() {
+      ZoneId z1 = ZoneId.of(ZONE_ID);
+      ZoneId z2 = ZoneId.of(ZONE_ID);
+      ZoneId twoParam;
+      try {
+        twoParam = CustomLibClass.twoParametersReturn(z1, z2);
+      } catch (Exception e) {
+        throw new RuntimeException("Test failed.");
+      }
+      System.out.println(twoParam.getId());
+    }
+
+    public static void oneParameter() {
+      ZoneId z1 = ZoneId.of(ZONE_ID);
+      String res;
+      try {
+        res = CustomLibClass.oneParameter(z1);
+      } catch (Exception e) {
+        throw new RuntimeException("Test failed.");
+      }
+      System.out.println(res);
+    }
+
+    public static void twoParameters() {
+      ZoneId z1 = ZoneId.of(ZONE_ID);
+      ZoneId z2 = ZoneId.of(ZONE_ID);
+      String res;
+      try {
+        res = CustomLibClass.twoParameters(z1, z2);
+      } catch (Exception e) {
+        throw new RuntimeException("Test failed.");
+      }
+      System.out.println(res);
+    }
+
+    @SuppressWarnings("ResultOfMethodCallIgnored")
+    public static void twoParametersThrow() {
+      ZoneId z1 = ZoneId.of(ZONE_ID);
+      ZoneId z2 = ZoneId.of(ZONE_ID);
+      try {
+        CustomLibClass.twoParametersThrow(z1, z2);
+        throw new RuntimeException("Test failed.");
+      } catch (ArithmeticException e) {
+        System.out.println("Exception caught");
+      }
+    }
+  }
+
+  // This class will be put at compilation time as library and on the runtime class path.
+  // This class is convenient for easy testing. Each method plays the role of methods in the
+  // platform APIs for which argument/return values need conversion.
+  @SuppressWarnings("WeakerAccess")
+  static class CustomLibClass {
+
+    private static final String ZONE_ID = "GMT";
+
+    public static ZoneId returnOnly() {
+      return ZoneId.of(ZONE_ID);
+    }
+
+    public static ZoneId oneParameterReturn(ZoneId z1) {
+      return z1;
+    }
+
+    public static ZoneId twoParametersReturn(ZoneId z1, ZoneId z2) {
+      return z1;
+    }
+
+    public static String oneParameter(ZoneId z1) {
+      return z1.getId();
+    }
+
+    public static String twoParameters(ZoneId z1, ZoneId z2) {
+      return z1.getId();
+    }
+
+    @SuppressWarnings({"divzero", "NumericOverflow", "UnusedReturnValue"})
+    public static String twoParametersThrow(ZoneId z1, ZoneId z2) {
+      return "" + (1 / 0);
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/corelib/corelibjdktests/Jdk11AtomicTests.java b/src/test/java/com/android/tools/r8/desugar/corelib/corelibjdktests/Jdk11AtomicTests.java
index 150fbf6..0010c65 100644
--- a/src/test/java/com/android/tools/r8/desugar/corelib/corelibjdktests/Jdk11AtomicTests.java
+++ b/src/test/java/com/android/tools/r8/desugar/corelib/corelibjdktests/Jdk11AtomicTests.java
@@ -19,6 +19,7 @@
 import java.nio.file.Paths;
 import java.util.Collections;
 import java.util.List;
+import org.junit.Assume;
 import org.junit.BeforeClass;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -71,6 +72,8 @@
 
   @Test
   public void testD8AtomicReference() throws Exception {
+    // TODO(b/142377475).
+    Assume.assumeTrue(!shrinkDesugaredLibrary);
     KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters);
     String verbosity = "2";
     testForD8()
@@ -94,6 +97,8 @@
 
   @Test
   public void testD8AtomicUpdaters() throws Exception {
+    // TODO(b/142377475).
+    Assume.assumeTrue(!shrinkDesugaredLibrary);
     KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters);
     String verbosity = "2";
     testForD8()
diff --git a/src/test/java/com/android/tools/r8/desugar/corelib/corelibjdktests/Jdk11ConcurrentMapTests.java b/src/test/java/com/android/tools/r8/desugar/corelib/corelibjdktests/Jdk11ConcurrentMapTests.java
index d340245..f80607a 100644
--- a/src/test/java/com/android/tools/r8/desugar/corelib/corelibjdktests/Jdk11ConcurrentMapTests.java
+++ b/src/test/java/com/android/tools/r8/desugar/corelib/corelibjdktests/Jdk11ConcurrentMapTests.java
@@ -99,6 +99,8 @@
   public void testD8Concurrent() throws Exception {
     // TODO(b/134732760): Support Java 9+ libraries.
     // We skip the ConcurrentRemoveIf test because of the  non desugared class CompletableFuture.
+    // TODO(b/142377475).
+    Assume.assumeTrue(!shrinkDesugaredLibrary);
     KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters);
     String verbosity = "2";
     testForD8()
diff --git a/src/test/java/com/android/tools/r8/desugar/corelib/corelibjdktests/Jdk11TimeTests.java b/src/test/java/com/android/tools/r8/desugar/corelib/corelibjdktests/Jdk11TimeTests.java
index d5debbb..51ce216 100644
--- a/src/test/java/com/android/tools/r8/desugar/corelib/corelibjdktests/Jdk11TimeTests.java
+++ b/src/test/java/com/android/tools/r8/desugar/corelib/corelibjdktests/Jdk11TimeTests.java
@@ -132,7 +132,9 @@
   @Test
   public void testTime() throws Exception {
     // TODO(b/137876068): Temporarily ignored to move forward with Desugared API conversion.
-    Assume.assumeFalse(parameters.getApiLevel().getLevel() <= AndroidApiLevel.M.getLevel());
+    // Extra conversions are required due to extra classes injected in the desugared library
+    // in this test. Disabled for now.
+    Assume.assumeFalse(parameters.getApiLevel().getLevel() <= AndroidApiLevel.N.getLevel());
     String verbosity = "2";
     D8TestCompileResult compileResult =
         testForD8()
diff --git a/src/test/java/com/android/tools/r8/desugar/corelib/desugar_jdk_libs.json b/src/test/java/com/android/tools/r8/desugar/corelib/desugar_jdk_libs.json
index 11179f5..fa917a3 100644
--- a/src/test/java/com/android/tools/r8/desugar/corelib/desugar_jdk_libs.json
+++ b/src/test/java/com/android/tools/r8/desugar/corelib/desugar_jdk_libs.json
@@ -1,6 +1,6 @@
 {
   "configuration_format_version": 1,
-  "version": "0.3.0",
+  "version": "0.5.0",
   "required_compilation_api_level": 26,
   "library_flags": [
     {
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/string/StringTrimTest.java b/src/test/java/com/android/tools/r8/ir/optimize/string/StringTrimTest.java
new file mode 100644
index 0000000..cd7618d
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/string/StringTrimTest.java
@@ -0,0 +1,71 @@
+// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.optimize.string;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.InstructionSubject.JumboStringMode;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+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 StringTrimTest extends TestBase {
+
+  private final TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public StringTrimTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void test() throws Exception {
+    testForR8(parameters.getBackend())
+        .addProgramClasses(TestClass.class)
+        .addKeepMainRule(TestClass.class)
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .inspect(this::inspect)
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutputLines("Hello world!");
+  }
+
+  private void inspect(CodeInspector inspector) {
+    ClassSubject classSubject = inspector.clazz(TestClass.class);
+    assertThat(classSubject, isPresent());
+
+    MethodSubject mainMethodSubject = classSubject.mainMethod();
+    assertThat(mainMethodSubject, isPresent());
+    assertTrue(
+        mainMethodSubject
+            .streamInstructions()
+            .anyMatch(x -> x.isConstString("Hello world!", JumboStringMode.ALLOW)));
+    assertTrue(
+        mainMethodSubject
+            .streamInstructions()
+            .noneMatch(x -> x.isConstString(" Hello world! ", JumboStringMode.ALLOW)));
+  }
+
+  static class TestClass {
+
+    public static void main(String[] args) {
+      System.out.println(" Hello world! ".trim());
+    }
+  }
+}
diff --git a/third_party/iosched_2019.tar.gz.sha1 b/third_party/iosched_2019.tar.gz.sha1
new file mode 100644
index 0000000..257df06
--- /dev/null
+++ b/third_party/iosched_2019.tar.gz.sha1
@@ -0,0 +1 @@
+e1df1acda9f21519c802adbe3eef03c3e60bf8b8
\ No newline at end of file
diff --git a/tools/archive_desugar_jdk_libs.py b/tools/archive_desugar_jdk_libs.py
index 3ca25a6..903f3b8 100755
--- a/tools/archive_desugar_jdk_libs.py
+++ b/tools/archive_desugar_jdk_libs.py
@@ -19,6 +19,7 @@
 # repository to fetch the artifact com.android.tools:desugar_jdk_libs:1.0.0
 
 import archive
+import defines
 import git_utils
 import optparse
 import os
@@ -27,6 +28,19 @@
 import subprocess
 import sys
 import utils
+import zipfile
+
+if defines.IsLinux():
+  JDK8_JAVAC = os.path.join(
+      defines.THIRD_PARTY, 'openjdk', 'jdk8', 'linux-x86', 'bin', 'javac')
+elif defines.IsOsX():
+  JDK8_JAVAC = os.path.join(
+      defines.THIRD_PARTY, 'openjdk', 'jdk8', 'darwin-x86', 'bin', 'javac')
+elif defines.IsWindows():
+  raise Exception('Cannot compile using JDK8 on Windows hence cannot archive.')
+
+CONVERSION_FOLDER = os.path.join(
+    defines.REPO_ROOT, 'src', 'test', 'desugaredLibraryConversions')
 
 VERSION_FILE = 'VERSION.txt'
 LIBRARY_NAME = 'desugar_jdk_libs'
@@ -55,6 +69,10 @@
       raise Exception('Version file '
           + VERSION_FILE + ' is expected to have exactly one line')
     version = lines[0].strip()
+    if (version == '1.0.1'):
+      raise Exception('Version file ' + VERSION_FILE + 'cannot have version 1.0.1')
+    if (version == '1.0.0'):
+      version = '1.0.1'
     utils.check_basic_semver_version(
         version, 'in version file ' + VERSION_FILE)
     return version
@@ -76,6 +94,13 @@
     print('File available at: %s' %
         destination.replace('gs://', 'http://storage.googleapis.com/', 1))
 
+def GetFilesInFolder(folder):
+  resulting_files = []
+  for root, dirs, files in os.walk(folder):
+    for name in files:
+      resulting_files.append(os.path.join(root, name))
+  assert len(resulting_files) > 0
+  return resulting_files
 
 def Main(argv):
   (options, args) = ParseOptions(argv)
@@ -95,6 +120,7 @@
 
   # Make sure bazel is extracted in third_party.
   utils.DownloadFromGoogleCloudStorage(utils.BAZEL_SHA_FILE)
+  utils.DownloadFromGoogleCloudStorage(utils.JAVA8_SHA_FILE)
 
   # Only handling versioned desugar_jdk_libs.
   is_master = False
@@ -113,19 +139,53 @@
             'Target archive directory %s already exists' % destination)
 
       bazel = os.path.join(utils.BAZEL_TOOL, 'lib', 'bazel', 'bin', 'bazel')
-      cmd = [bazel, 'build', 'maven_release']
+      cmd = [bazel, 'build', '--host_force_python=PY2', 'maven_release']
       utils.PrintCmd(cmd)
       subprocess.check_call(cmd)
       cmd = [bazel, 'shutdown']
       utils.PrintCmd(cmd)
       subprocess.check_call(cmd)
 
+      # Compile the stubs for conversion files compilation.
+      stub_compiled_folder = os.path.join(checkout_dir, 'stubs')
+      os.mkdir(stub_compiled_folder)
+      all_stubs = GetFilesInFolder(os.path.join(CONVERSION_FOLDER, 'stubs'))
+      cmd = [JDK8_JAVAC, '-d', stub_compiled_folder] + all_stubs
+      utils.PrintCmd(cmd)
+      subprocess.check_call(cmd)
+
+      # Compile the conversion files.
+      conversions_compiled_folder = os.path.join(checkout_dir, 'conversions')
+      os.mkdir(conversions_compiled_folder)
+      all_conversions = GetFilesInFolder(
+          os.path.join(CONVERSION_FOLDER, 'conversions'))
+      cmd = [JDK8_JAVAC, '-cp', stub_compiled_folder, '-d',
+             conversions_compiled_folder] + all_conversions
+      utils.PrintCmd(cmd)
+      subprocess.check_call(cmd)
+
       # Locate the library jar and the maven zip with the jar from the
       # bazel build.
       library_jar = os.path.join(
           'bazel-bin', 'src', 'share', 'classes', 'java', 'libjava.jar')
       maven_zip = os.path.join('bazel-bin', LIBRARY_NAME +'.zip')
 
+      # Make a writable copy of the jar.
+      jar_folder = os.path.join(checkout_dir, 'jar')
+      os.mkdir(jar_folder)
+      shutil.copy(library_jar, jar_folder)
+      library_jar = os.path.join(jar_folder,'libjava.jar')
+      os.chmod(library_jar, 0o777)
+
+      # Add conversion classes into the jar.
+      all_compiled_conversions = GetFilesInFolder(conversions_compiled_folder)
+      with zipfile.ZipFile(library_jar, mode='a', allowZip64=True) as jar:
+        for clazz in all_compiled_conversions:
+          jar.write(clazz,arcname=os.path.relpath(
+              clazz,
+              conversions_compiled_folder),
+              compress_type = zipfile.ZIP_DEFLATED)
+
       storage_path = LIBRARY_NAME + '/' + version
       # Upload the jar file with the library.
       destination = archive.GetUploadDestination(
diff --git a/tools/internal_test.py b/tools/internal_test.py
index a0cc52b..6c4b7ff 100755
--- a/tools/internal_test.py
+++ b/tools/internal_test.py
@@ -39,7 +39,6 @@
 
 # How often the bot/tester should check state
 PULL_DELAY = 30
-BUCKET = 'r8-test-results'
 TEST_RESULT_DIR = 'internal'
 
 # Magic files
@@ -55,13 +54,94 @@
 EXITCODE = 'exitcode'
 TIMED_OUT = 'timed_out'
 
+BENCHMARK_APPS = [
+    {
+        'app': 'r8',
+        'version': 'cf',
+        'find-xmx-min': 128,
+        'find-xmx-max': 400,
+        'find-xmx-range': 16,
+        'oom-threshold': 247,
+    },
+    {
+        'app': 'chrome',
+        'version': '180917',
+        'find-xmx-min': 256,
+        'find-xmx-max': 450,
+        'find-xmx-range': 16,
+        'oom-threshold': 392,
+    },
+    {
+        'app': 'youtube',
+        'version': '12.22',
+        'find-xmx-min': 1200,
+        'find-xmx-max': 800,
+        'find-xmx-range': 32,
+        'oom-threshold': 1000,
+    },
+    # TODO(b/142375244): Narrow when run a few times.
+    {
+        'app': 'iosched',
+        'version': '2019',
+        'find-xmx-min': 128,
+        'find-xmx-max': 1024,
+        'find-xmx-range': 16,
+        'oom-threshold': 666, # will be changed after b/142375244 has run
+    },
+]
+
+def find_min_xmx_command(record):
+  return [
+      'tools/run_on_app.py',
+      '--compiler=r8',
+      '--compiler-build=lib',
+      '--app=%s' % record['app'],
+      '--version=%s' % record['version'],
+      '--no-debug',
+      '--no-build',
+      '--find-min-xmx',
+      '--find-min-xmx-min-memory=%s' % record['find-xmx-min'],
+      '--find-min-xmx-max-memory=%s' % record['find-xmx-max'],
+      '--find-min-xmx-range-size=%s' % record['find-xmx-range'],
+      '--find-min-xmx-archive']
+
+def compile_with_memory_max_command(record):
+  return [
+      'tools/run_on_app.py',
+      '--compiler=r8',
+      '--compiler-build=lib',
+      '--app=%s' % record['app'],
+      '--version=%s' % record['version'],
+      '--no-debug',
+      '--no-build',
+      '--max-memory=%s' % int(record['oom-threshold'] * 1.1)
+  ]
+
+def compile_with_memory_min_command(record):
+  return [
+      'tools/run_on_app.py',
+      '--compiler=r8',
+      '--compiler-build=lib',
+      '--app=%s' % record['app'],
+      '--version=%s' % record['version'],
+      '--no-debug',
+      '--no-build',
+      '--expect-oom',
+      '--max-memory=%s' % int(record['oom-threshold'] * 0.9)
+  ]
+
 TEST_COMMANDS = [
     # Run test.py internal testing.
     ['tools/test.py', '--only_internal', '--slow_tests',
      '--java_max_memory_size=8G'],
     # Ensure that all internal apps compile.
-    ['tools/run_on_app.py', '--ignore-java-version','--run-all', '--out=out']
-]
+    ['tools/run_on_app.py', '--run-all', '--out=out'],
+    # Find min xmx for selected benchmark apps
+    ['tools/gradle.py', 'r8lib'],
+] + (map(compile_with_memory_max_command, BENCHMARK_APPS)
+     + map(compile_with_memory_min_command, BENCHMARK_APPS)
+     + map(find_min_xmx_command, BENCHMARK_APPS))
+
 
 # Command timeout, in seconds.
 RUN_TIMEOUT = 3600 * 6
@@ -123,30 +203,19 @@
   return utils.get_HEAD_sha1()
 
 def get_test_result_dir():
-  return os.path.join(BUCKET, TEST_RESULT_DIR)
+  return os.path.join(utils.R8_TEST_RESULTS_BUCKET, TEST_RESULT_DIR)
 
 def get_sha_destination(sha):
   return os.path.join(get_test_result_dir(), sha)
 
 def archive_status(failed):
   gs_destination = 'gs://%s' % get_sha_destination(utils.get_HEAD_sha1())
-  archive_value('status', gs_destination, failed)
+  utils.archive_value('status', gs_destination, failed)
 
 def get_status(sha):
   gs_destination = 'gs://%s/status' % get_sha_destination(sha)
   return utils.cat_file_on_cloud_storage(gs_destination)
 
-def archive_file(name, gs_dir, src_file):
-  gs_file = '%s/%s' % (gs_dir, name)
-  utils.upload_file_to_cloud_storage(src_file, gs_file, public_read=False)
-
-def archive_value(name, gs_dir, value):
-  with utils.TempDir() as temp:
-    tempfile = os.path.join(temp, name);
-    with open(tempfile, 'w') as f:
-      f.write(str(value))
-    archive_file(name, gs_dir, tempfile)
-
 def archive_log(stdout, stderr, exitcode, timed_out, cmd):
   sha = utils.get_HEAD_sha1()
   cmd_dir = cmd.replace(' ', '_').replace('/', '_')
@@ -154,10 +223,10 @@
   gs_destination = 'gs://%s' % destination
   url = 'https://storage.cloud.google.com/%s' % destination
   log('Archiving logs to: %s' % gs_destination)
-  archive_value(EXITCODE, gs_destination, exitcode)
-  archive_value(TIMED_OUT, gs_destination, timed_out)
-  archive_file(STDOUT, gs_destination, stdout)
-  archive_file(STDERR, gs_destination, stderr)
+  utils.archive_value(EXITCODE, gs_destination, exitcode)
+  utils.archive_value(TIMED_OUT, gs_destination, timed_out)
+  utils.archive_file(STDOUT, gs_destination, stdout)
+  utils.archive_file(STDERR, gs_destination, stderr)
   log('Logs available at: %s' % url)
 
 def get_magic_file_base_path():
@@ -173,7 +242,7 @@
   utils.delete_file_from_cloud_storage(get_magic_file_gs_path(name))
 
 def put_magic_file(name, sha):
-  archive_value(name, get_magic_file_base_path(), sha)
+  utils.archive_value(name, get_magic_file_base_path(), sha)
 
 def get_magic_file_content(name, ignore_errors=False):
   return utils.cat_file_on_cloud_storage(get_magic_file_gs_path(name),
diff --git a/tools/iosched_data.py b/tools/iosched_data.py
new file mode 100644
index 0000000..10edd2b
--- /dev/null
+++ b/tools/iosched_data.py
@@ -0,0 +1,176 @@
+# 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.
+
+import os
+import utils
+
+BASE = os.path.join(utils.THIRD_PARTY, 'iosched_2019')
+
+INPUT_JARS = [
+    '1258593f0b2b739993b682bbaf9fbb78.jar',
+    '89e9dbba1fbe6b0fb368f1e95589a721.jar',
+    '1fc8941eba5f85af1a8b7da23c729ec1.jar',
+    'ea69d715e74c5a1a5796f035243f3e97.jar',
+    '811d2312b489a587d1581f4e37d637f7.jar',
+    'core-common-2.1.0-beta01.jar',
+    'annotations-4.6.1.jar',
+    'okhttp-3.12.1.jar',
+    'd55e0fc606c88c93df0d2c3ad9fbc66e.jar',
+    'a88afc3d193dd0fa82c07cb40636e3ca.jar',
+    '9a226aecc46543548aa3b8d516848199.jar',
+    '57aa054ef470b0eef00e8fd0b0d634bc.jar',
+    '04db891d0460d81f9bf27f14c75ab1c1.jar',
+    'okhttp-2.7.5.jar',
+    '66e02cfd6fc641b6806c903ab1385c8f.jar',
+    '429150960927f98f31cfb8f93acce495.jar',
+    'gson-2.8.1.jar',
+    '9f174a718fccec6dd22216b50e2024dd.jar',
+    'fccc983abc03ed47fb7f92e27c01f754.jar',
+    'f682c9b637850b361f6987e36818df61.jar',
+    'a2ff39bc73ca8ad09f4da1873bb0c86b.jar',
+    '3e62317d1e8cb1c33d978124952bbdde.jar',
+    '6d7cf1b5cfc5deefb39819e6927763d2.jar',
+    '684e8bb7e37e94adea3de977d6b8d785.jar',
+    '6da5a19a73b7295a9a14ebc739c15513.jar',
+    'e8d3c32813b7956c025e0b94ef0f2d3f.jar',
+    'annotations-4.1.1.4.jar',
+    '19c2c18938ee2a99115dd700b18692c2.jar',
+    '87ae4245c0a8fe0ba2dcd40275a203cf.jar',
+    'databinding-common-3.4.1.jar',
+    '27556a10d0fdbd9730a712a196a1ea33.jar',
+    'kotlinx-coroutines-core-1.1.1.jar',
+    'bdb2bd187f4ec8a364ebc58e0682bf5c.jar',
+    '72054a54b7a1555355136f0f12550439.jar',
+    'fd24a8a68ac1192744d79cb3f8a50bff.jar',
+    'disklrucache-4.6.1.jar',
+    'de1154a48df042b18af6f861738b1f4f.jar',
+    '49d0c995b51f741d1cd5095ff50aa610.jar',
+    '5a906c2026b29fcf8e38dfc091dedf5f.jar',
+    'error_prone_annotations-2.3.2.jar',
+    'fc3371678bf5b92c8ad09bff3cca7d34.jar',
+    'b1328978c6137d4afd05e8b449089790.jar',
+    '5d9bc74a14654749fec1bd3f57868314.jar',
+    '4ca93957d039e88756ce172a3f23f92c.jar',
+    'b3ae138529e3059714654ee61047c60c.jar',
+    '04c45dab1f83131034d1e8ff7a7e4062.jar',
+    '6d09c79fcadbf28f15bd5d302f1da434.jar',
+    '3c5f06144063c91423d28cbd925d9a93.jar',
+    'room-common-2.1.0.jar',
+    'cc16ff10efeb6d0fb62ab09980fa6368.jar',
+    '095c50fdef7bb1f16479501c204965c3.jar',
+    '0f8cbd6ea38249489aa4b6e08f637a9c.jar',
+    'dfba629a83faeb465bc5281f59b33410.jar',
+    'kotlin-android-extensions-runtime-1.3.41.jar',
+    '36e56f8acb47c47dc4aba993954571df.jar',
+    'animal-sniffer-annotations-1.17.jar',
+    '50262b47ca79a3e0887f4363cee3269a.jar',
+    '653fd9ff51b91c8cb7e0b8b4933292e7.jar',
+    'bd5cfb8566d0f18b7349220f77b99038.jar',
+    'annotations-16.0.1.jar',
+    'checker-compat-qual-2.5.2.jar',
+    'fad1e20626a9c0638b081c19bc5b8a39.jar',
+    '82e00b90bc477d17e9da6a4c8ff9ecea.jar',
+    '67302edfd6d295aff1eb26c436372785.jar',
+    'ce77a2a47382a2f825868572b889d2c7.jar',
+    'javax.inject-1.jar',
+    '96f7d3ab17962219c936737346db0f85.jar',
+    'f3393d6b2b44cbf7b1344884834e89b0.jar',
+    '54ffaad03867b0dadad722ce3fb9960b.jar',
+    'grpc-api-1.21.0.jar',
+    '91ed4f2ea5824596c9b228ad297f23b3.jar',
+    '67a09a53395866b35f9cd93bd668c84e.jar',
+    'e3d353a45188abc61dd104d69fa1dd82.jar',
+    'jetified-kotlinx-coroutines-android-1.1.1.jar',
+    '5ecec3c73e22ced41f9a40a26c0e5281.jar',
+    '2771336e639c434ce717c9ac58875dc2.jar',
+    'e9bfc218eb8cdc6f1110f0d048c4862d.jar',
+    '38ace74613d265c1f1cccbb1fa10f627.jar',
+    '562cc9d1cf74343dfb5fd7ced76ba2c4.jar',
+    'opencensus-api-0.21.0.jar',
+    'd337a2d970073e4d08c0afa5ad287a45.jar',
+    '50ed6ac2559a57159d48c24be1ec847a.jar',
+    'a3e463112f8c2d5d0e99f08c274e4df7.jar',
+    'ae7980f49ccbaa525389f6b7e73240fe.jar',
+    '8e0a68dd4e7fa4c84c2471cc6b46ad98.jar',
+    '7072528e00796ef6011ab617dfdbed57.jar',
+    '45c6fe8ab0e41a359c77e925bfe8a467.jar',
+    'room-common-java8-2.1.0.jar',
+    'fc5e4f8937eeeac973e266d99abdcbc9.jar',
+    'lifecycle-common-2.1.0-beta01.jar',
+    'guava-26.0-android.jar',
+    'grpc-stub-1.21.0.jar',
+    'jsr305-3.0.2.jar',
+    '02941d2c97d179cc4a4187fbf269010b.jar',
+    'grpc-okhttp-1.21.0.jar',
+    '79b02b1828fc13f97dfd7c60656f58a3.jar',
+    '97123a1bfa3e489812597dd9ca6a476f.jar',
+    'c87d55c5aff3cfb40573462b40c16e93.jar',
+    'd5d50e3f4be084f67aea3d7274996dac.jar',
+    '22798eb6762e1b6e1ed68e0a26a11416.jar',
+    'opencensus-contrib-grpc-metrics-0.21.0.jar',
+    'e7d37c099e227a9a8063906d2d2fac39.jar',
+    '58fa238dea0a0425db38a9ef7614544a.jar',
+    '656d56b89a5bdfdb34c6df1a03cdb44a.jar',
+    'cd4453bc1349dc354726f443d14e42a1.jar',
+    'model.jar',
+    '9b1a33316cb5646c0c7f2409b59a6e3e.jar',
+    'ab21f6ad1b9d44aef2e4f748aadbd6e0.jar',
+    'd84ac18db47f5c63a7c906981a8c74c2.jar',
+    'auto-value-annotations-1.6.3.jar',
+    'e14a0047c43e74969599da9eecd2c326.jar',
+    'f12c26a7262a280545381f23a1e99fae.jar',
+    'd69110fa9aef88987146fb1855fc797b.jar',
+    '147569cf828c2f744bbbc06c0f207c31.jar',
+    'grpc-protobuf-lite-1.21.0.jar',
+    'da0774b870df803349a111b937795a71.jar',
+    '2624862b1ed2676a15e6c4ecacdb2faa.jar',
+    'grpc-context-1.21.0.jar',
+    'logging-interceptor-3.10.0.jar',
+    'threetenbp-1.3.6-no-tzdb.jar',
+    '5b200d64a4850d02b6965e2bce869691.jar',
+    '55ad080882c073b8ccc51a9c516ec407.jar',
+    '1e61604b06dc10dcdd1cb9edab5f3411.jar',
+    '6d74ce3d9ef226c95dd9e0589df9d8c0.jar',
+    '1fdcde2b38692788ec8efcab3958b42c.jar',
+    '50bef79f89b168cefcfbcad72f8cabfa.jar',
+    'annotation-1.1.0.jar',
+    '956e5331c88f28df168ce3ea2402dffc.jar',
+    '471bc8aa5928f4fbb3c9f00cc6f907ca.jar',
+    'dagger-2.24.jar',
+    '234bdadd74c099896158187676ea0650.jar',
+    'kotlin-stdlib-1.3.41.jar',
+    'j2objc-annotations-1.1.jar',
+    'fe2d5d6bd2084907c7dd971f366042e6.jar',
+    '66fb157c1fdab977f2e15c1dcc222649.jar',
+    '18ded358602e992e34f5131e231c9fdd.jar',
+    'cb7e7d424e289578c115667747b262cd.jar',
+    '0ed7d992c263060a704a951b53ac0bbb.jar',
+    '6a495a5466b08b8e825e78918680b946.jar',
+    '7c3656acf184f5103c65f49acad46981.jar',
+    'classes_0.jar',
+    '47bbfe33b04bd140773a33c708d5df34.jar',
+    'grpc-core-1.21.0.jar',
+    'collection-1.1.0.jar',
+    'programclasses.jar',
+    '592c92d42c935f46f0e50e988310a94b.jar',
+    'constraintlayout-solver-1.1.3.jar',
+    'protobuf-lite-3.0.1.jar',
+    '309d47704a456783a746357a53f76ed9.jar',
+    'kotlin-stdlib-jdk7-1.3.41.jar',
+    'e47b3993e1f3cb81b6a13a44a60edc7d.jar',
+    'classes.jar',
+    'e9385c983e864be1243e1865c529f2ba.jar',
+    'okio-1.15.0.jar',
+    '492c0eca59ede4f2e0dd149bb44e0c5a.jar',
+]
+
+VERSIONS = {
+  '2019': {
+    'deploy' : {
+        'inputs': [os.path.join(BASE, path) for path in INPUT_JARS],
+        'pgconf': [os.path.join(BASE, 'proguard-rules.pro')],
+        'libraries': [utils.get_android_jar(28)],
+    },
+  },
+}
diff --git a/tools/r8_data.py b/tools/r8_data.py
index 71b1f12..c4c509f 100644
--- a/tools/r8_data.py
+++ b/tools/r8_data.py
@@ -5,6 +5,8 @@
 import utils
 import os
 
+ANDROID_L_API = '21'
+
 VERSIONS = {
     'cf': {
       'deploy': {
@@ -12,6 +14,11 @@
           'pgconf': [os.path.join(utils.REPO_ROOT, 'src', 'main', 'keep.txt')],
           'libraries' : [utils.RT_JAR],
           'flags': '--classfile',
+      },
+      'proguarded': {
+          'inputs': [utils.PINNED_R8_JAR],
+          'libraries' : [utils.RT_JAR],
+          'min-api' : ANDROID_L_API,
       }
     }
 }
diff --git a/tools/run_on_app.py b/tools/run_on_app.py
index f2592c9..b32581b 100755
--- a/tools/run_on_app.py
+++ b/tools/run_on_app.py
@@ -21,9 +21,10 @@
 import youtube_data
 import chrome_data
 import r8_data
+import iosched_data
 
 TYPES = ['dex', 'deploy', 'proguarded']
-APPS = ['gmscore', 'nest', 'youtube', 'gmail', 'chrome', 'r8']
+APPS = ['gmscore', 'nest', 'youtube', 'gmail', 'chrome', 'r8', 'iosched']
 COMPILERS = ['d8', 'r8']
 COMPILER_BUILDS = ['full', 'lib']
 
@@ -33,6 +34,10 @@
 # A negative value -N indicates that the child was terminated by signal N.
 TIMEOUT_KILL_CODE = -9
 
+# Log file names
+FIND_MIN_XMX_FILE = 'find_min_xmx_results'
+FIND_MIN_XMX_DIR = 'find_min_xmx'
+
 def ParseOptions(argv):
   result = optparse.OptionParser()
   result.add_option('--compiler',
@@ -49,6 +54,10 @@
                     help='Compile all possible combinations',
                     default=False,
                     action='store_true')
+  result.add_option('--expect-oom',
+                    help='Expect that compilation will fail with an OOM',
+                    default=False,
+                    action='store_true')
   result.add_option('--type',
                     help='Default for R8: deploy, for D8: proguarded',
                     choices=TYPES)
@@ -59,6 +68,9 @@
                     help='Run without building first',
                     default=False,
                     action='store_true')
+  result.add_option('--max-memory',
+                    help='The maximum memory in MB to run with',
+                    type='int')
   result.add_option('--find-min-xmx',
                     help='Find the minimum amount of memory we can run in',
                     default=False,
@@ -73,8 +85,12 @@
                     help='Setting the size of the acceptable memory range',
                     type='int',
                     default=32)
+  result.add_option('--find-min-xmx-archive',
+                    help='Archive find-min-xmx results on GCS',
+                    default=False,
+                    action='store_true')
   result.add_option('--timeout',
-                    type=int,
+                    type='int',
                     default=0,
                     help='Set timeout instead of waiting for OOM.')
   result.add_option('--golem',
@@ -132,6 +148,7 @@
                     metavar='BENCHMARKNAME',
                     help='Print the sizes of individual dex segments as ' +
                         '\'<BENCHMARKNAME>-<segment>(CodeSize): <bytes>\'')
+
   return result.parse_args(argv)
 
 # Most apps have -printmapping, -printseeds, -printusage and
@@ -162,7 +179,8 @@
       'youtube': youtube_data,
       'chrome': chrome_data,
       'gmail': gmail_data,
-      'r8': r8_data
+      'r8': r8_data,
+      'iosched': iosched_data,
   }
   # Check to ensure that we add all variants here.
   assert len(APPS) == len(data_providers)
@@ -234,16 +252,73 @@
       not_working = next_candidate
 
   assert working - not_working <= range
-  print('Found range: %s - %s' % (not_working, working))
+  found_range = 'Found range: %s - %s' % (not_working, working)
+  print(found_range)
+
+  if options.find_min_xmx_archive:
+    sha = utils.get_HEAD_sha1()
+    (version, _) = get_version_and_data(options)
+    destination = os.path.join(
+        utils.R8_TEST_RESULTS_BUCKET,
+        FIND_MIN_XMX_DIR,
+        sha,
+        options.compiler,
+        options.compiler_build,
+        options.app,
+        version,
+        get_type(options))
+    gs_destination = 'gs://%s' % destination
+    utils.archive_value(FIND_MIN_XMX_FILE, gs_destination, found_range + '\n')
+
   return 0
 
 def main(argv):
   (options, args) = ParseOptions(argv)
+  if options.expect_oom and not options.max_memory:
+    raise Exception(
+        'You should only use --expect-oom if also specifying --max-memory')
+  if options.expect_oom and options.timeout:
+    raise Exception(
+        'You should not use --timeout when also specifying --expect-oom')
   if options.run_all:
     return run_all(options, args)
   if options.find_min_xmx:
     return find_min_xmx(options, args)
-  return run_with_options(options, args)
+  exit_code = run_with_options(options, args)
+  if options.expect_oom:
+    exit_code = 0 if exit_code == OOM_EXIT_CODE else 1
+  return exit_code
+
+def get_version_and_data(options):
+  if options.app == 'gmscore':
+    version = options.version or 'v9'
+    data = gmscore_data
+  elif options.app == 'nest':
+    version = options.version or '20180926'
+    data = nest_data
+  elif options.app == 'youtube':
+    version = options.version or '12.22'
+    data = youtube_data
+  elif options.app == 'chrome':
+    version = options.version or '180917'
+    data = chrome_data
+  elif options.app == 'gmail':
+    version = options.version or '170604.16'
+    data = gmail_data
+  elif options.app == 'r8':
+    version = options.version or 'cf'
+    data = r8_data
+  elif options.app == 'iosched':
+    version = options.version or '2019'
+    data = iosched_data
+  else:
+    raise Exception("You need to specify '--app={}'".format('|'.join(APPS)))
+  return version, data
+
+def get_type(options):
+  if not options.type:
+    return 'deploy' if options.compiler == 'r8' else 'proguarded'
+  return options.type
 
 def run_with_options(options, args, extra_args=None):
   if extra_args is None:
@@ -251,7 +326,10 @@
   app_provided_pg_conf = False;
   # todo(121018500): remove when memory is under control
   if not any('-Xmx' in arg for arg in extra_args):
-    extra_args.append('-Xmx8G')
+    if options.max_memory:
+      extra_args.append('-Xmx%sM' % options.max_memory)
+    else:
+      extra_args.append('-Xmx8G')
   if options.golem:
     golem.link_third_party()
     options.out = os.getcwd()
@@ -259,27 +337,7 @@
     utils.check_java_version()
 
   outdir = options.out
-  data = None
-  if options.app == 'gmscore':
-    options.version = options.version or 'v9'
-    data = gmscore_data
-  elif options.app == 'nest':
-    options.version = options.version or '20180926'
-    data = nest_data
-  elif options.app == 'youtube':
-    options.version = options.version or '12.22'
-    data = youtube_data
-  elif options.app == 'chrome':
-    options.version = options.version or '180917'
-    data = chrome_data
-  elif options.app == 'gmail':
-    options.version = options.version or '170604.16'
-    data = gmail_data
-  elif options.app == 'r8':
-    options.version = options.version or 'cf'
-    data = r8_data
-  else:
-    raise Exception("You need to specify '--app={}'".format('|'.join(APPS)))
+  (version_id, data) = get_version_and_data(options)
 
   if options.compiler not in COMPILERS:
     raise Exception("You need to specify '--compiler={}'"
@@ -289,32 +347,31 @@
     raise Exception("You need to specify '--compiler-build={}'"
         .format('|'.join(COMPILER_BUILDS)))
 
-  if not options.version in data.VERSIONS.keys():
+  if not version_id in data.VERSIONS.keys():
     print('No version {} for application {}'
-        .format(options.version, options.app))
+        .format(version_id, options.app))
     print('Valid versions are {}'.format(data.VERSIONS.keys()))
     return 1
 
-  version = data.VERSIONS[options.version]
+  version = data.VERSIONS[version_id]
 
-  if not options.type:
-    options.type = 'deploy' if options.compiler == 'r8' \
-        else 'proguarded'
+  type = get_type(options)
 
-  if options.type not in version:
-    print('No type {} for version {}'.format(options.type, options.version))
+  if type not in version:
+    print('No type {} for version {}'.format(type, version))
     print('Valid types are {}'.format(version.keys()))
     return 1
-  values = version[options.type]
+  values = version[type]
   inputs = None
   # For R8 'deploy' the JAR is located using the Proguard configuration
   # -injars option. For chrome and nest we don't have the injars in the
   # proguard files.
   if 'inputs' in values and (options.compiler != 'r8'
-                             or options.type != 'deploy'
+                             or type != 'deploy'
                              or options.app == 'chrome'
                              or options.app == 'nest'
-                             or options.app == 'r8'):
+                             or options.app == 'r8'
+                             or options.app == 'iosched'):
     inputs = values['inputs']
 
   args.extend(['--output', outdir])
diff --git a/tools/utils.py b/tools/utils.py
index 152c1e7..fb937e6 100644
--- a/tools/utils.py
+++ b/tools/utils.py
@@ -94,11 +94,25 @@
 OPENSOURCE_APPS_FOLDER = os.path.join(THIRD_PARTY, 'opensource_apps')
 BAZEL_SHA_FILE = os.path.join(THIRD_PARTY, 'bazel.tar.gz.sha1')
 BAZEL_TOOL = os.path.join(THIRD_PARTY, 'bazel')
+JAVA8_SHA_FILE = os.path.join(THIRD_PARTY, 'openjdk', 'jdk8', 'linux-x86.tar.gz.sha1')
 
 ANDROID_HOME_ENVIROMENT_NAME = "ANDROID_HOME"
 ANDROID_TOOLS_VERSION_ENVIRONMENT_NAME = "ANDROID_TOOLS_VERSION"
 USER_HOME = os.path.expanduser('~')
 
+R8_TEST_RESULTS_BUCKET = 'r8-test-results'
+
+def archive_file(name, gs_dir, src_file):
+  gs_file = '%s/%s' % (gs_dir, name)
+  upload_file_to_cloud_storage(src_file, gs_file, public_read=False)
+
+def archive_value(name, gs_dir, value):
+  with TempDir() as temp:
+    tempfile = os.path.join(temp, name);
+    with open(tempfile, 'w') as f:
+      f.write(str(value))
+    archive_file(name, gs_dir, tempfile)
+
 def getAndroidHome():
   return os.environ.get(
       ANDROID_HOME_ENVIROMENT_NAME, os.path.join(USER_HOME, 'Android', 'Sdk'))