Merge commit '514eacf7544322d7dffc141fd15a934f9784ca4b' into 1.7.6-dev
diff --git a/src/main/java/com/android/tools/r8/CompatProguardCommandBuilder.java b/src/main/java/com/android/tools/r8/CompatProguardCommandBuilder.java
index 5651dde..90544cc 100644
--- a/src/main/java/com/android/tools/r8/CompatProguardCommandBuilder.java
+++ b/src/main/java/com/android/tools/r8/CompatProguardCommandBuilder.java
@@ -4,8 +4,6 @@
 
 package com.android.tools.r8;
 
-import java.nio.file.Path;
-
 // This class is used by the Android Studio Gradle plugin and is thus part of the R8 API.
 @Keep
 public class CompatProguardCommandBuilder extends R8Command.Builder {
@@ -34,8 +32,4 @@
     setDisableVerticalClassMerging(disableVerticalClassMerging);
     setIgnoreDexInArchive(true);
   }
-
-  public void setProguardCompatibilityRulesOutput(Path path) {
-    proguardCompatibilityRulesOutput = path;
-  }
 }
diff --git a/src/main/java/com/android/tools/r8/GenerateLintFiles.java b/src/main/java/com/android/tools/r8/GenerateLintFiles.java
index 2c63a69..3bd76a6 100644
--- a/src/main/java/com/android/tools/r8/GenerateLintFiles.java
+++ b/src/main/java/com/android/tools/r8/GenerateLintFiles.java
@@ -47,6 +47,7 @@
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
+import java.util.Comparator;
 import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
@@ -81,6 +82,8 @@
             factory.createProto(streamType),
             factory.createString("parallelStream"));
     parallelMethods.add(parallelMethod);
+    DexType baseStreamType =
+        factory.createType(factory.createString("Ljava/util/stream/BaseStream;"));
     for (String typePrefix : new String[] {"Base", "Double", "Int", "Long"}) {
       streamType =
           factory.createType(factory.createString("Ljava/util/stream/" + typePrefix + "Stream;"));
@@ -88,6 +91,11 @@
           factory.createMethod(
               streamType, factory.createProto(streamType), factory.createString("parallel"));
       parallelMethods.add(parallelMethod);
+      // Also filter out the generated bridges for the covariant return type.
+      parallelMethod =
+          factory.createMethod(
+              streamType, factory.createProto(baseStreamType), factory.createString("parallel"));
+      parallelMethods.add(parallelMethod);
     }
   }
 
@@ -168,7 +176,19 @@
             false));
   }
 
-  private Map<DexClass, List<DexEncodedMethod>> collectSupportedMethods(
+  public static class SupportedMethods {
+    public final Set<DexClass> classesWithAllMethodsSupported;
+    public final Map<DexClass, List<DexEncodedMethod>> supportedMethods;
+
+    public SupportedMethods(
+        Set<DexClass> classesWithAllMethodsSupported,
+        Map<DexClass, List<DexEncodedMethod>> supportedMethods) {
+      this.classesWithAllMethodsSupported = classesWithAllMethodsSupported;
+      this.supportedMethods = supportedMethods;
+    }
+  }
+
+  private SupportedMethods collectSupportedMethods(
       AndroidApiLevel compilationApiLevel, Predicate<DexEncodedMethod> supported)
       throws IOException, ExecutionException {
 
@@ -178,18 +198,25 @@
     DirectMappedDexApplication dexApplication =
         new ApplicationReader(library, options, new Timing()).read().toDirect();
 
-    // collect all the methods that the library desugar configuration adds support for.
+    // Collect all the methods that the library desugar configuration adds support for.
+    Set<DexClass> classesWithAllMethodsSupported = Sets.newIdentityHashSet();
     Map<DexClass, List<DexEncodedMethod>> supportedMethods = new LinkedHashMap<>();
     for (DexLibraryClass clazz : dexApplication.libraryClasses()) {
       String className = clazz.toSourceString();
       // All the methods with the rewritten prefix are supported.
       for (String prefix : desugaredLibraryConfiguration.getRewritePrefix().keySet()) {
         if (clazz.accessFlags.isPublic() && className.startsWith(prefix)) {
+          boolean allMethodsAddad = true;
           for (DexEncodedMethod method : clazz.methods()) {
             if (supported.test(method)) {
               supportedMethods.computeIfAbsent(clazz, k -> new ArrayList<>()).add(method);
+            } else {
+              allMethodsAddad = false;
             }
           }
+          if (allMethodsAddad) {
+            classesWithAllMethodsSupported.add(clazz);
+          }
         }
       }
 
@@ -209,9 +236,14 @@
           }
         }
       }
-      // All emulated interfaces methods are supported.
+
+      // All emulated interfaces static and default methods are supported.
       if (desugaredLibraryConfiguration.getEmulateLibraryInterface().containsKey(clazz.type)) {
+        assert clazz.isInterface();
         for (DexEncodedMethod method : clazz.methods()) {
+          if (!method.isDefaultMethod() && !method.isStatic()) {
+            continue;
+          }
           if (supported.test(method)) {
             supportedMethods.computeIfAbsent(clazz, k -> new ArrayList<>()).add(method);
           }
@@ -219,7 +251,7 @@
       }
     }
 
-    return supportedMethods;
+    return new SupportedMethods(classesWithAllMethodsSupported, supportedMethods);
   }
 
   private String lintBaseFileName(
@@ -243,22 +275,26 @@
   private void writeLintFiles(
       AndroidApiLevel compilationApiLevel,
       AndroidApiLevel minApiLevel,
-      Map<DexClass, List<DexEncodedMethod>> supportedMethods)
+      SupportedMethods supportedMethods)
       throws Exception {
     // Build a plain text file with the desugared APIs.
     List<String> desugaredApisSignatures = new ArrayList<>();
 
     DexApplication.Builder builder = DexApplication.builder(options, new Timing());
-    supportedMethods.forEach(
+    supportedMethods.supportedMethods.forEach(
         (clazz, methods) -> {
           String classBinaryName =
               DescriptorUtils.getClassBinaryNameFromDescriptor(clazz.type.descriptor.toString());
-          for (DexEncodedMethod method : methods) {
-            desugaredApisSignatures.add(
-                classBinaryName
-                    + '/'
-                    + method.method.name
-                    + method.method.proto.toDescriptorString());
+          if (!supportedMethods.classesWithAllMethodsSupported.contains(clazz)) {
+            for (DexEncodedMethod method : methods) {
+              desugaredApisSignatures.add(
+                  classBinaryName
+                      + '#'
+                      + method.method.name
+                      + method.method.proto.toDescriptorString());
+            }
+          } else {
+            desugaredApisSignatures.add(classBinaryName);
           }
 
           addMethodsToHeaderJar(builder, clazz, methods);
@@ -266,6 +302,7 @@
     DexApplication app = builder.build();
 
     // Write a plain text file with the desugared APIs.
+    desugaredApisSignatures.sort(Comparator.naturalOrder());
     FileUtils.writeTextFile(
         lintFile(compilationApiLevel, minApiLevel, ".txt"), desugaredApisSignatures);
 
@@ -293,16 +330,20 @@
       Predicate<AndroidApiLevel> generateForThisMinApiLevel,
       BiPredicate<AndroidApiLevel, DexEncodedMethod> supportedForMinApiLevel)
       throws Exception {
-    for (AndroidApiLevel value : AndroidApiLevel.values()) {
-      if (!generateForThisMinApiLevel.test(value)) {
+    System.out.print("  - generating for min API:");
+    for (AndroidApiLevel minApiLevel : AndroidApiLevel.values()) {
+      if (!generateForThisMinApiLevel.test(minApiLevel)) {
         continue;
       }
 
-      Map<DexClass, List<DexEncodedMethod>> supportedMethods =
+      System.out.print(" " + minApiLevel);
+
+      SupportedMethods supportedMethods =
           collectSupportedMethods(
-              compilationApiLevel, (method -> supportedForMinApiLevel.test(value, method)));
-      writeLintFiles(compilationApiLevel, value, supportedMethods);
+              compilationApiLevel, (method -> supportedForMinApiLevel.test(minApiLevel, method)));
+      writeLintFiles(compilationApiLevel, minApiLevel, supportedMethods);
     }
+    System.out.println();
   }
 
   private void run() throws Exception {
diff --git a/src/main/java/com/android/tools/r8/L8Command.java b/src/main/java/com/android/tools/r8/L8Command.java
index d1a8e6a..f3b7e05 100644
--- a/src/main/java/com/android/tools/r8/L8Command.java
+++ b/src/main/java/com/android/tools/r8/L8Command.java
@@ -13,6 +13,7 @@
 import com.android.tools.r8.utils.Pair;
 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.Collection;
 import java.util.Collections;
@@ -150,7 +151,8 @@
   @Keep
   public static class Builder extends BaseCompilerCommand.Builder<L8Command, Builder> {
 
-    private final List<Pair<List<String>, Origin>> proguardConfigs = new ArrayList<>();
+    private final List<Pair<List<String>, Origin>> proguardConfigStrings = new ArrayList<>();
+    private final List<Path> proguardConfigFiles = new ArrayList<>();
 
     private Builder() {
       this(new DefaultL8DiagnosticsHandler());
@@ -161,8 +163,15 @@
     }
 
     public boolean isShrinking() {
+      // TODO(b/139273544): Re-enable shrinking once fixed.
+      getReporter()
+          .warning(
+              new StringDiagnostic(
+                  "Shrinking of desugared library has been temporarily disabled due to known bugs"
+                      + " being fixed."));
+      return false;
       // Answers true if keep rules, even empty, are provided.
-      return !proguardConfigs.isEmpty();
+      // return !proguardConfigStrings.isEmpty() || !proguardConfigFiles.isEmpty();
     }
 
     @Override
@@ -175,16 +184,28 @@
       return CompilationMode.DEBUG;
     }
 
+    /** Add proguard configuration-file resources. */
+    public Builder addProguardConfigurationFiles(Path... paths) {
+      Collections.addAll(proguardConfigFiles, paths);
+      return self();
+    }
+
+    /** Add proguard configuration-file resources. */
+    public Builder addProguardConfigurationFiles(List<Path> paths) {
+      proguardConfigFiles.addAll(paths);
+      return self();
+    }
+
     /** Add proguard configuration. */
     public Builder addProguardConfiguration(List<String> lines, Origin origin) {
-      proguardConfigs.add(new Pair<>(lines, origin));
-      return this;
+      proguardConfigStrings.add(new Pair<>(lines, origin));
+      return self();
     }
 
     @Override
     void validate() {
       Reporter reporter = getReporter();
-      if (!hasDesugaredLibraryConfiguration()){
+      if (!hasDesugaredLibraryConfiguration()) {
         reporter.error("L8 requires a desugared library configuration");
       }
       if (getProgramConsumer() instanceof ClassFileConsumer) {
@@ -238,9 +259,10 @@
             inputs.getLibraryResourceProviders()) {
           r8Builder.addLibraryResourceProvider(libraryResourceProvider);
         }
-        for (Pair<List<String>, Origin> proguardConfig : proguardConfigs) {
+        for (Pair<List<String>, Origin> proguardConfig : proguardConfigStrings) {
           r8Builder.addProguardConfiguration(proguardConfig.getFirst(), proguardConfig.getSecond());
         }
+        r8Builder.addProguardConfigurationFiles(proguardConfigFiles);
         r8Command = r8Builder.makeCommand();
       } else {
         D8Command.Builder d8Builder =
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java
index 4cd2f7b..91b6ab7 100644
--- a/src/main/java/com/android/tools/r8/R8.java
+++ b/src/main/java/com/android/tools/r8/R8.java
@@ -62,7 +62,6 @@
 import com.android.tools.r8.shaking.MainDexClasses;
 import com.android.tools.r8.shaking.MainDexListBuilder;
 import com.android.tools.r8.shaking.ProguardClassFilter;
-import com.android.tools.r8.shaking.ProguardConfiguration;
 import com.android.tools.r8.shaking.ProguardConfigurationRule;
 import com.android.tools.r8.shaking.ProguardConfigurationUtils;
 import com.android.tools.r8.shaking.RootSetBuilder;
@@ -76,7 +75,6 @@
 import com.android.tools.r8.utils.CfgPrinter;
 import com.android.tools.r8.utils.CollectionUtils;
 import com.android.tools.r8.utils.ExceptionUtils;
-import com.android.tools.r8.utils.FileUtils;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.InternalOptions.AssertionProcessing;
 import com.android.tools.r8.utils.LineNumberOptimizer;
@@ -88,15 +86,12 @@
 import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Iterables;
 import com.google.common.io.ByteStreams;
-import com.google.common.io.Closer;
 import java.io.ByteArrayOutputStream;
 import java.io.FileOutputStream;
 import java.io.IOException;
-import java.io.OutputStream;
 import java.io.OutputStreamWriter;
 import java.io.PrintStream;
 import java.nio.charset.StandardCharsets;
-import java.nio.file.StandardOpenOption;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.HashSet;
@@ -297,9 +292,6 @@
         // kotlin metadata annotation is removed.
         computeKotlinInfoForProgramClasses(application, appView);
 
-        ProguardConfiguration.Builder compatibility =
-            ProguardConfiguration.builder(application.dexItemFactory, options.reporter);
-
         // Add synthesized -assumenosideeffects from min api if relevant.
         if (options.isGeneratingDex()) {
           if (!ProguardConfigurationUtils.hasExplicitAssumeValuesOrAssumeNoSideEffectsRuleForMinSdk(
@@ -318,7 +310,7 @@
                         options.getProguardConfiguration().getRules(), synthesizedProguardRules))
                 .run(executorService));
 
-        Enqueuer enqueuer = EnqueuerFactory.createForInitialTreeShaking(appView, compatibility);
+        Enqueuer enqueuer = EnqueuerFactory.createForInitialTreeShaking(appView);
 
         if (appView.options().enableInitializedClassesInInstanceMethodsAnalysis) {
           enqueuer.registerAnalysis(new InitializedClassesInInstanceMethodsAnalysis(appView));
@@ -376,26 +368,9 @@
         classesToRetainInnerClassAttributeFor =
             AnnotationRemover.computeClassesToRetainInnerClassAttributeFor(appView.withLiveness());
         new AnnotationRemover(appView.withLiveness(), classesToRetainInnerClassAttributeFor)
-            .ensureValid(compatibility)
+            .ensureValid()
             .run();
 
-        // TODO(69445518): This is still work in progress, and this file writing is currently used
-        // for testing.
-        if (options.forceProguardCompatibility
-            && options.proguardCompatibilityRulesOutput != null) {
-          try (Closer closer = Closer.create()) {
-            OutputStream outputStream =
-                FileUtils.openPath(
-                    closer,
-                    options.proguardCompatibilityRulesOutput,
-                    StandardOpenOption.CREATE,
-                    StandardOpenOption.TRUNCATE_EXISTING,
-                    StandardOpenOption.WRITE);
-            try (PrintStream ps = new PrintStream(outputStream)) {
-              ps.println(compatibility.buildRaw().toString());
-            }
-          }
-        }
       } finally {
         timing.end();
       }
diff --git a/src/main/java/com/android/tools/r8/R8Command.java b/src/main/java/com/android/tools/r8/R8Command.java
index 5c90ae8..ae68adb 100644
--- a/src/main/java/com/android/tools/r8/R8Command.java
+++ b/src/main/java/com/android/tools/r8/R8Command.java
@@ -100,9 +100,6 @@
     private BiFunction<String, Long, Boolean> dexClassChecksumFilter = (name, checksum) -> true;
     private final List<FeatureSplit> featureSplits = new ArrayList<>();
 
-    // Internal compatibility mode for use from CompatProguard tool.
-    Path proguardCompatibilityRulesOutput = null;
-
     private boolean allowPartiallyImplementedProguardOptions = false;
     private boolean allowTestProguardOptions = false;
 
@@ -541,7 +538,6 @@
               proguardUsageConsumer,
               proguardSeedsConsumer,
               proguardConfigurationConsumer,
-              proguardCompatibilityRulesOutput,
               keptGraphConsumer,
               mainDexKeptGraphConsumer,
               syntheticProguardRulesConsumer,
@@ -626,7 +622,6 @@
   private final StringConsumer proguardUsageConsumer;
   private final StringConsumer proguardSeedsConsumer;
   private final StringConsumer proguardConfigurationConsumer;
-  private final Path proguardCompatibilityRulesOutput;
   private final GraphConsumer keptGraphConsumer;
   private final GraphConsumer mainDexKeptGraphConsumer;
   private final Consumer<List<ProguardConfigurationRule>> syntheticProguardRulesConsumer;
@@ -699,7 +694,6 @@
       StringConsumer proguardUsageConsumer,
       StringConsumer proguardSeedsConsumer,
       StringConsumer proguardConfigurationConsumer,
-      Path proguardCompatibilityRulesOutput,
       GraphConsumer keptGraphConsumer,
       GraphConsumer mainDexKeptGraphConsumer,
       Consumer<List<ProguardConfigurationRule>> syntheticProguardRulesConsumer,
@@ -732,7 +726,6 @@
     this.proguardUsageConsumer = proguardUsageConsumer;
     this.proguardSeedsConsumer = proguardSeedsConsumer;
     this.proguardConfigurationConsumer = proguardConfigurationConsumer;
-    this.proguardCompatibilityRulesOutput = proguardCompatibilityRulesOutput;
     this.keptGraphConsumer = keptGraphConsumer;
     this.mainDexKeptGraphConsumer = mainDexKeptGraphConsumer;
     this.syntheticProguardRulesConsumer = syntheticProguardRulesConsumer;
@@ -753,7 +746,6 @@
     proguardUsageConsumer = null;
     proguardSeedsConsumer = null;
     proguardConfigurationConsumer = null;
-    proguardCompatibilityRulesOutput = null;
     keptGraphConsumer = null;
     mainDexKeptGraphConsumer = null;
     syntheticProguardRulesConsumer = null;
@@ -853,7 +845,6 @@
     internal.keptGraphConsumer = keptGraphConsumer;
     internal.mainDexKeptGraphConsumer = mainDexKeptGraphConsumer;
 
-    internal.proguardCompatibilityRulesOutput = proguardCompatibilityRulesOutput;
     internal.dataResourceConsumer = internal.programConsumer.getDataResourceConsumer();
 
     internal.featureSplitConfiguration = featureSplitConfiguration;
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 bc4f6f0..90de8ca 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
@@ -273,7 +273,7 @@
 
   public boolean isDefaultMethod() {
     // Assumes holder is an interface
-    return !isAbstract() && !isPrivateMethod() && !isInstanceInitializer();
+    return !isStatic() && !isAbstract() && !isPrivateMethod() && !isInstanceInitializer();
   }
 
   /**
@@ -600,11 +600,6 @@
     return this;
   }
 
-  public IRCode buildEmptyThrowingIRCode(AppView<?> appView, Origin origin) {
-    DexCode emptyThrowingDexCode = buildEmptyThrowingDexCode();
-    return emptyThrowingDexCode.buildIR(this, appView, origin);
-  }
-
   /**
    * Generates a {@link DexCode} object for the given instructions.
    */
@@ -887,7 +882,7 @@
       List<Pair<DexType, DexMethod>> extraDispatchCases,
       AppView<?> appView) {
     // TODO(134732760): Deal with overrides for correct dispatch to implementations of Interfaces
-    assert isDefaultMethod();
+    assert isDefaultMethod() || isStatic();
     DexEncodedMethod.Builder builder = DexEncodedMethod.builder(this);
     builder.setMethod(newMethod);
     builder.accessFlags.setSynthetic();
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/type/Nullability.java b/src/main/java/com/android/tools/r8/ir/analysis/type/Nullability.java
index 11c1c8c..26b9030 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/type/Nullability.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/type/Nullability.java
@@ -58,6 +58,19 @@
     return MAYBE_NULL;
   }
 
+  public Nullability meet(Nullability other) {
+    if (this == MAYBE_NULL) {
+      return other;
+    }
+    if (other == MAYBE_NULL) {
+      return this;
+    }
+    if (this == other) {
+      return this;
+    }
+    return BOTTOM;
+  }
+
   public boolean lessThanOrEqual(Nullability other) {
     return join(other) == other;
   }
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/type/ReferenceTypeLatticeElement.java b/src/main/java/com/android/tools/r8/ir/analysis/type/ReferenceTypeLatticeElement.java
index 8e42de0..f6af1f7 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/type/ReferenceTypeLatticeElement.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/type/ReferenceTypeLatticeElement.java
@@ -75,8 +75,8 @@
 
   public abstract ReferenceTypeLatticeElement getOrCreateVariant(Nullability nullability);
 
-  public TypeLatticeElement asNotNull() {
-    return getOrCreateVariant(Nullability.definitelyNotNull());
+  public TypeLatticeElement asMeetWithNotNull() {
+    return getOrCreateVariant(nullability.meet(Nullability.definitelyNotNull()));
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/Assume.java b/src/main/java/com/android/tools/r8/ir/code/Assume.java
index c4dfbc4..9ba29fd 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Assume.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Assume.java
@@ -153,6 +153,10 @@
     return self;
   }
 
+  public boolean mayAffectStaticType() {
+    return isAssumeNonNull();
+  }
+
   @Override
   public boolean couldIntroduceAnAlias(AppView<?> appView, Value root) {
     assert root != null && root.getTypeLattice().isReference();
@@ -237,7 +241,7 @@
     }
     if (assumption.isAssumeNonNull()) {
       assert src().getTypeLattice().isReference();
-      return src().getTypeLattice().asReferenceTypeLatticeElement().asNotNull();
+      return src().getTypeLattice().asReferenceTypeLatticeElement().asMeetWithNotNull();
     }
     throw new Unimplemented();
   }
@@ -276,7 +280,7 @@
       assert isAssumeNonNull() : this;
       assert inType.isReference() : inType;
       assert inType.isNullType()
-          || outType.equals(inType.asReferenceTypeLatticeElement().asNotNull())
+          || outType.equals(inType.asReferenceTypeLatticeElement().asMeetWithNotNull())
               : "At " + this + System.lineSeparator() + outType + " != " + inType;
     }
     return true;
@@ -284,16 +288,20 @@
 
   @Override
   public String toString() {
-    String originString = "(origin: `" + origin.toString() + "`)";
-    if (isAssumeNone()) {
-      return super.toString() + "; nothing " + originString;
+    // During branch simplification, the origin `if` could be simplified.
+    // It means the assumption became "truth."
+    assert origin.hasBlock() || isAssumeNonNull();
+    String originString =
+        origin.hasBlock() ? " (origin: `" + origin.toString() + "`)" : " (origin simplified)";
+    if (isAssumeNone() || isAssumeNonNull()) {
+      return super.toString() + originString;
     }
     if (isAssumeDynamicType()) {
+      DynamicTypeAssumption assumption = asAssumeDynamicType().getAssumption();
       return super.toString()
-          + "; type: " + asAssumeDynamicType().getAssumption().type + originString;
-    }
-    if (isAssumeNonNull()) {
-      return super.toString() + "; not null " + originString;
+          + "; upper bound: " + assumption.type
+          + (assumption.lowerBoundType != null ? "; lower bound: " + assumption.lowerBoundType : "")
+          + originString;
     }
     return super.toString();
   }
diff --git a/src/main/java/com/android/tools/r8/ir/code/IRCode.java b/src/main/java/com/android/tools/r8/ir/code/IRCode.java
index fa3074e..cb32015 100644
--- a/src/main/java/com/android/tools/r8/ir/code/IRCode.java
+++ b/src/main/java/com/android/tools/r8/ir/code/IRCode.java
@@ -1008,14 +1008,22 @@
   }
 
   public void removeAllTrivialPhis() {
-    removeAllTrivialPhis(null);
+    removeAllTrivialPhis(null, null);
   }
 
   public void removeAllTrivialPhis(IRBuilder builder) {
+    removeAllTrivialPhis(builder, null);
+  }
+
+  public void removeAllTrivialPhis(Set<Value> affectedValues) {
+    removeAllTrivialPhis(null, affectedValues);
+  }
+
+  public void removeAllTrivialPhis(IRBuilder builder, Set<Value> affectedValues) {
     for (BasicBlock block : blocks) {
       List<Phi> phis = new ArrayList<>(block.getPhis());
       for (Phi phi : phis) {
-        phi.removeTrivialPhi(builder);
+        phi.removeTrivialPhi(builder, affectedValues);
       }
     }
   }
diff --git a/src/main/java/com/android/tools/r8/ir/code/Phi.java b/src/main/java/com/android/tools/r8/ir/code/Phi.java
index e2f9cf3..95dac01 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Phi.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Phi.java
@@ -123,7 +123,7 @@
       builder.constrainType(operand, readConstraint);
       appendOperand(operand);
     }
-    removeTrivialPhi(builder);
+    removeTrivialPhi(builder, null);
   }
 
   public void addOperands(List<Value> operands) {
@@ -224,10 +224,10 @@
   }
 
   public void removeTrivialPhi() {
-    removeTrivialPhi(null);
+    removeTrivialPhi(null, null);
   }
 
-  public void removeTrivialPhi(IRBuilder builder) {
+  public void removeTrivialPhi(IRBuilder builder, Set<Value> affectedValues) {
     Value same = null;
     for (Value op : operands) {
       if (op == same || op == this) {
@@ -252,6 +252,9 @@
     if (builder != null && typeLattice.isPreciseType() && !typeLattice.isBottom()) {
       builder.constrainType(same, ValueTypeConstraint.fromTypeLattice(typeLattice));
     }
+    if (affectedValues != null) {
+      affectedValues.addAll(this.affectedValues());
+    }
     // Removing this phi, so get rid of it as a phi user from all of the operands to avoid
     // recursively getting back here with the same phi. If the phi has itself as an operand
     // that also removes the self-reference.
@@ -277,7 +280,7 @@
       replaceUsers(same);
       // Try to simplify phi users that might now have become trivial.
       for (Phi user : phiUsersToSimplify) {
-        user.removeTrivialPhi(builder);
+        user.removeTrivialPhi(builder, affectedValues);
       }
     }
     // Get rid of the phi itself.
diff --git a/src/main/java/com/android/tools/r8/ir/code/Position.java b/src/main/java/com/android/tools/r8/ir/code/Position.java
index ec923ee..51196ea 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Position.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Position.java
@@ -108,6 +108,7 @@
       return line == o.line
           && file == o.file
           && method == o.method
+          && synthetic == o.synthetic
           && Objects.equals(callerPosition, o.callerPosition);
     }
     return false;
diff --git a/src/main/java/com/android/tools/r8/ir/code/Value.java b/src/main/java/com/android/tools/r8/ir/code/Value.java
index 2881de7..8e57306 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Value.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Value.java
@@ -19,6 +19,7 @@
 import com.android.tools.r8.position.MethodPosition;
 import com.android.tools.r8.utils.LongInterval;
 import com.android.tools.r8.utils.Reporter;
+import com.android.tools.r8.utils.SetUtils;
 import com.android.tools.r8.utils.StringDiagnostic;
 import com.google.common.base.Predicates;
 import com.google.common.collect.ImmutableSet;
@@ -424,6 +425,22 @@
     return users.getFirst();
   }
 
+  public Set<Instruction> aliasedUsers() {
+    Set<Instruction> users = SetUtils.newIdentityHashSet(uniqueUsers());
+    collectAliasedUsersViaAssume(uniqueUsers(), users);
+    return users;
+  }
+
+  private static void collectAliasedUsersViaAssume(
+      Set<Instruction> usersToTest, Set<Instruction> collectedUsers) {
+    for (Instruction user : usersToTest) {
+      if (user.isAssume()) {
+        collectedUsers.addAll(user.outValue().uniqueUsers());
+        collectAliasedUsersViaAssume(user.outValue().uniqueUsers(), collectedUsers);
+      }
+    }
+  }
+
   public Phi firstPhiUser() {
     assert !phiUsers.isEmpty();
     return phiUsers.getFirst();
@@ -1142,9 +1159,9 @@
     //   i.e., towards something wider.
     assert this.typeLattice.lessThanOrEqual(newType, appView)
         : "During WIDENING, "
-            + typeLattice
-            + " < "
             + newType
+            + " < "
+            + typeLattice
             + " at "
             + (isPhi() ? asPhi().printPhi() : definition.toString());
     setTypeLattice(newType);
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
index dc2516c..1b31d62 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
@@ -1249,26 +1249,6 @@
 
     assert code.verifyTypes(appView);
 
-    if (nonNullTracker != null) {
-      // TODO(b/139246447): Once we extend this optimization to, e.g., constants of primitive args,
-      //   this may not be the right place to collect call site optimization info.
-      // Collecting call-site optimization info depends on the existence of non-null IRs.
-      // Arguments can be changed during the debug mode.
-      if (!isDebugMode && appView.callSiteOptimizationInfoPropagator() != null) {
-        appView.callSiteOptimizationInfoPropagator().collectCallSiteOptimizationInfo(code);
-      }
-      // Computation of non-null parameters on normal exits rely on the existence of non-null IRs.
-      nonNullTracker.computeNonNullParamOnNormalExits(feedback, code);
-    }
-    if (aliasIntroducer != null || nonNullTracker != null || dynamicTypeOptimization != null) {
-      codeRewriter.removeAssumeInstructions(code);
-      assert code.isConsistentSSA();
-    }
-    // Assert that we do not have unremoved non-sense code in the output, e.g., v <- non-null NULL.
-    assert code.verifyNoNullabilityBottomTypes();
-
-    assert code.verifyTypes(appView);
-
     previous = printMethod(code, "IR before class inlining (SSA)", previous);
 
     if (classInliner != null) {
@@ -1293,6 +1273,7 @@
                       Integer.MAX_VALUE / 2,
                       Integer.MAX_VALUE / 2)));
       assert code.isConsistentSSA();
+      assert code.verifyTypes(appView);
     }
 
     previous = printMethod(code, "IR after class inlining (SSA)", previous);
@@ -1323,13 +1304,6 @@
       twrCloseResourceRewriter.rewriteMethodCode(code);
     }
 
-    previous = printMethod(code, "IR after twr close resource rewriter (SSA)", previous);
-
-    if (lambdaMerger != null) {
-      lambdaMerger.processMethodCode(method, code);
-      assert code.isConsistentSSA();
-    }
-
     if (nonNullTracker != null) {
       // TODO(b/139246447): Once we extend this optimization to, e.g., constants of primitive args,
       //   this may not be the right place to collect call site optimization info.
@@ -1350,6 +1324,13 @@
 
     assert code.verifyTypes(appView);
 
+    previous = printMethod(code, "IR after twr close resource rewriter (SSA)", previous);
+
+    if (lambdaMerger != null) {
+      lambdaMerger.processMethodCode(method, code);
+      assert code.isConsistentSSA();
+    }
+
     previous = printMethod(code, "IR after lambda merger (SSA)", previous);
 
     if (options.outline.enabled) {
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/MethodOptimizationFeedback.java b/src/main/java/com/android/tools/r8/ir/conversion/MethodOptimizationFeedback.java
index 376e186..08aefe9 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/MethodOptimizationFeedback.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/MethodOptimizationFeedback.java
@@ -19,6 +19,8 @@
 
 public interface MethodOptimizationFeedback {
 
+  void markForceInline(DexEncodedMethod method);
+
   void markInlinedIntoSingleCallSite(DexEncodedMethod method);
 
   void markMethodCannotBeKept(DexEncodedMethod method);
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..1f049e5 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
@@ -139,6 +139,10 @@
     initializeEmulatedInterfaceVariables();
   }
 
+  private boolean isDefaultOrStatic(DexEncodedMethod method) {
+    return method.isDefaultMethod() || method.isStatic();
+  }
+
   private void initializeEmulatedInterfaceVariables() {
     Map<DexType, DexType> emulateLibraryInterface =
         options.desugaredLibraryConfiguration.getEmulateLibraryInterface();
@@ -148,7 +152,7 @@
       DexClass emulatedInterfaceClass = appView.definitionFor(interfaceType);
       if (emulatedInterfaceClass != null) {
         for (DexEncodedMethod encodedMethod :
-            emulatedInterfaceClass.methods(DexEncodedMethod::isDefaultMethod)) {
+            emulatedInterfaceClass.methods(this::isDefaultOrStatic)) {
           emulatedMethods.add(encodedMethod.method.name);
         }
       }
@@ -635,7 +639,7 @@
       DexProgramClass theInterface, Map<DexType, List<DexType>> emulatedInterfacesHierarchy) {
     List<DexEncodedMethod> emulationMethods = new ArrayList<>();
     for (DexEncodedMethod method : theInterface.methods()) {
-      if (method.isDefaultMethod()) {
+      if (isDefaultOrStatic(method)) {
         DexMethod libraryMethod =
             factory.createMethod(
                 emulatedInterfaces.get(theInterface.type), method.method.proto, method.method.name);
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/CallSiteOptimizationInfoPropagator.java b/src/main/java/com/android/tools/r8/ir/optimize/CallSiteOptimizationInfoPropagator.java
index 45f8c45..8d3f18f 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/CallSiteOptimizationInfoPropagator.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/CallSiteOptimizationInfoPropagator.java
@@ -197,12 +197,12 @@
       } else {
         specializedArg = originalArg;
       }
+      assert specializedArg != null && specializedArg.getTypeLattice().isReference();
       if (dynamicType.isDefinitelyNotNull()) {
         // If we already knew `arg` is never null, e.g., receiver, skip adding non-null.
         if (!specializedArg.getTypeLattice().isDefinitelyNotNull()) {
-          Value nonNullArg =
-              code.createValue(
-                  specializedArg.getTypeLattice().asReferenceTypeLatticeElement().asNotNull());
+          Value nonNullArg = code.createValue(
+              specializedArg.getTypeLattice().asReferenceTypeLatticeElement().asMeetWithNotNull());
           affectedValues.addAll(specializedArg.affectedValues());
           specializedArg.replaceUsers(nonNullArg);
           Assume<NonNullAssumption> assumeNotNull =
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java b/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
index fba5ae7..1f36d25 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
@@ -218,7 +218,7 @@
     // Therefore, Assume elimination may result in a trivial phi:
     //   z <- phi(x, x)
     if (needToCheckTrivialPhis) {
-      code.removeAllTrivialPhis();
+      code.removeAllTrivialPhis(valuesThatRequireWidening);
     }
 
     if (!valuesThatRequireWidening.isEmpty()) {
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 9d2c5ad..545b624 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
@@ -357,46 +357,37 @@
       info.include(invoke.getType(), candidate);
     }
 
+    Value receiver = invoke.getReceiver();
+    if (receiver.getTypeLattice().isDefinitelyNull()) {
+      // A definitely null receiver will throw an error on call site.
+      return null;
+    }
     InlineAction action = new InlineAction(candidate, invoke, reason);
 
-    Value receiver = invoke.getReceiver();
     if (receiver.getTypeLattice().isNullable()) {
-      InternalOptions options = appView.options();
-      if (receiver.getTypeLattice().isDefinitelyNull()) {
-        if (!options.enableInliningOfInvokesWithDefinitelyNullReceivers) {
+      assert !receiver.getTypeLattice().isDefinitelyNull();
+      // When inlining an instance method call, we need to preserve the null check for the
+      // receiver. Therefore, if the receiver may be null and the candidate inlinee does not
+      // throw if the receiver is null before any other side effect, then we must synthesize a
+      // null check.
+      if (!candidate.getOptimizationInfo().checksNullReceiverBeforeAnySideEffect()) {
+        InternalOptions options = appView.options();
+        if (!options.enableInliningOfInvokesWithNullableReceivers) {
+          return null;
+        }
+        if (!options.nullableReceiverInliningFilter.isEmpty()
+            && !options.nullableReceiverInliningFilter.contains(
+                invoke.getInvokedMethod().toSourceString())) {
           return null;
         }
         if (Log.ENABLED && Log.isLoggingEnabledFor(Inliner.class)) {
           Log.debug(
               Inliner.class,
-              "Inlining method `%s` with definitely null receiver into `%s`",
+              "Inlining method `%s` with nullable receiver into `%s`",
               invoke.getInvokedMethod().toSourceString(),
               invocationContext.toSourceString());
         }
-        action.setShouldReturnEmptyThrowingCode();
-      } else {
-        // When inlining an instance method call, we need to preserve the null check for the
-        // receiver. Therefore, if the receiver may be null and the candidate inlinee does not
-        // throw if the receiver is null before any other side effect, then we must synthesize a
-        // null check.
-        if (!candidate.getOptimizationInfo().checksNullReceiverBeforeAnySideEffect()) {
-          if (!options.enableInliningOfInvokesWithNullableReceivers) {
-            return null;
-          }
-          if (!options.nullableReceiverInliningFilter.isEmpty()
-              && !options.nullableReceiverInliningFilter.contains(
-                  invoke.getInvokedMethod().toSourceString())) {
-            return null;
-          }
-          if (Log.ENABLED && Log.isLoggingEnabledFor(Inliner.class)) {
-            Log.debug(
-                Inliner.class,
-                "Inlining method `%s` with nullable receiver into `%s`",
-                invoke.getInvokedMethod().toSourceString(),
-                invocationContext.toSourceString());
-          }
-          action.setShouldSynthesizeNullCheckForReceiver();
-        }
+        action.setShouldSynthesizeNullCheckForReceiver();
       }
     }
 
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 c35cd11..b9c16e7 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
@@ -536,7 +536,6 @@
     public final Invoke invoke;
     final Reason reason;
 
-    private boolean shouldReturnEmptyThrowingCode;
     private boolean shouldSynthesizeNullCheckForReceiver;
 
     InlineAction(DexEncodedMethod target, Invoke invoke, Reason reason) {
@@ -545,10 +544,6 @@
       this.reason = reason;
     }
 
-    void setShouldReturnEmptyThrowingCode() {
-      shouldReturnEmptyThrowingCode = true;
-    }
-
     void setShouldSynthesizeNullCheckForReceiver() {
       shouldSynthesizeNullCheckForReceiver = true;
     }
@@ -561,53 +556,48 @@
         LensCodeRewriter lensCodeRewriter) {
       Origin origin = appView.appInfo().originFor(target.method.holder);
 
-      IRCode code;
-      if (shouldReturnEmptyThrowingCode) {
-        code = target.buildEmptyThrowingIRCode(appView, origin);
-      } else {
-        // Build the IR for a yet not processed method, and perform minimal IR processing.
-        code = target.buildInliningIR(context, appView, generator, callerPosition, origin);
+      // Build the IR for a yet not processed method, and perform minimal IR processing.
+      IRCode code = target.buildInliningIR(context, appView, generator, callerPosition, origin);
 
-        // Insert a null check if this is needed to preserve the implicit null check for the
-        // receiver.
-        if (shouldSynthesizeNullCheckForReceiver) {
-          List<Value> arguments = code.collectArguments();
-          if (!arguments.isEmpty()) {
-            Value receiver = arguments.get(0);
-            assert receiver.isThis();
+      // Insert a null check if this is needed to preserve the implicit null check for the
+      // receiver.
+      if (shouldSynthesizeNullCheckForReceiver) {
+        List<Value> arguments = code.collectArguments();
+        if (!arguments.isEmpty()) {
+          Value receiver = arguments.get(0);
+          assert receiver.isThis();
 
-            BasicBlock entryBlock = code.entryBlock();
+          BasicBlock entryBlock = code.entryBlock();
 
-            // Insert a new block between the last argument instruction and the first actual
-            // instruction of the method.
-            BasicBlock throwBlock =
-                entryBlock.listIterator(code, arguments.size()).split(code, 0, null);
-            assert !throwBlock.hasCatchHandlers();
+          // Insert a new block between the last argument instruction and the first actual
+          // instruction of the method.
+          BasicBlock throwBlock =
+              entryBlock.listIterator(code, arguments.size()).split(code, 0, null);
+          assert !throwBlock.hasCatchHandlers();
 
-            // Link the entry block to the successor of the newly inserted block.
-            BasicBlock continuationBlock = throwBlock.unlinkSingleSuccessor();
-            entryBlock.link(continuationBlock);
+          // Link the entry block to the successor of the newly inserted block.
+          BasicBlock continuationBlock = throwBlock.unlinkSingleSuccessor();
+          entryBlock.link(continuationBlock);
 
-            // Replace the last instruction of the entry block, which is now a goto instruction,
-            // with an `if-eqz` instruction that jumps to the newly inserted block if the receiver
-            // is null.
-            If ifInstruction = new If(If.Type.EQ, receiver);
-            entryBlock.replaceLastInstruction(ifInstruction, code);
-            assert ifInstruction.getTrueTarget() == throwBlock;
-            assert ifInstruction.fallthroughBlock() == continuationBlock;
+          // Replace the last instruction of the entry block, which is now a goto instruction,
+          // with an `if-eqz` instruction that jumps to the newly inserted block if the receiver
+          // is null.
+          If ifInstruction = new If(If.Type.EQ, receiver);
+          entryBlock.replaceLastInstruction(ifInstruction, code);
+          assert ifInstruction.getTrueTarget() == throwBlock;
+          assert ifInstruction.fallthroughBlock() == continuationBlock;
 
-            // Replace the single goto instruction in the newly inserted block by `throw null`.
-            InstructionListIterator iterator = throwBlock.listIterator(code);
-            Value nullValue = iterator.insertConstNullInstruction(code, appView.options());
-            iterator.next();
-            iterator.replaceCurrentInstruction(new Throw(nullValue));
-          } else {
-            assert false : "Unable to synthesize a null check for the receiver";
-          }
+          // Replace the single goto instruction in the newly inserted block by `throw null`.
+          InstructionListIterator iterator = throwBlock.listIterator(code);
+          Value nullValue = iterator.insertConstNullInstruction(code, appView.options());
+          iterator.next();
+          iterator.replaceCurrentInstruction(new Throw(nullValue));
+        } else {
+          assert false : "Unable to synthesize a null check for the receiver";
         }
-        if (!target.isProcessed()) {
-          lensCodeRewriter.rewrite(code, target);
-        }
+      }
+      if (!target.isProcessed()) {
+        lensCodeRewriter.rewrite(code, target);
       }
       return new InlineeWithReason(code, reason);
     }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/NonNullTracker.java b/src/main/java/com/android/tools/r8/ir/optimize/NonNullTracker.java
index 02cfe3a..0adf3f7 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/NonNullTracker.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/NonNullTracker.java
@@ -227,7 +227,7 @@
                 TypeLatticeElement typeLattice = knownToBeNonNullValue.getTypeLattice();
                 Value nonNullValue =
                     code.createValue(
-                        typeLattice.asReferenceTypeLatticeElement().asNotNull(),
+                        typeLattice.asReferenceTypeLatticeElement().asMeetWithNotNull(),
                         knownToBeNonNullValue.getLocalInfo());
                 affectedValues.addAll(knownToBeNonNullValue.affectedValues());
                 Assume<NonNullAssumption> nonNull =
@@ -359,7 +359,7 @@
         assert typeLattice.isReference();
         Value nonNullValue =
             code.createValue(
-                typeLattice.asReferenceTypeLatticeElement().asNotNull(),
+                typeLattice.asReferenceTypeLatticeElement().asMeetWithNotNull(),
                 knownToBeNonNullValue.getLocalInfo());
         affectedValues.addAll(knownToBeNonNullValue.affectedValues());
         Assume<NonNullAssumption> nonNull =
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInliner.java b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInliner.java
index b656678..f550309 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInliner.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInliner.java
@@ -8,9 +8,11 @@
 import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.ir.analysis.type.TypeAnalysis;
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.Instruction;
 import com.android.tools.r8.ir.code.InstructionOrPhi;
+import com.android.tools.r8.ir.code.Value;
 import com.android.tools.r8.ir.desugar.LambdaRewriter;
 import com.android.tools.r8.ir.optimize.CodeRewriter;
 import com.android.tools.r8.ir.optimize.Inliner;
@@ -18,9 +20,11 @@
 import com.android.tools.r8.ir.optimize.string.StringOptimizer;
 import com.android.tools.r8.logging.Log;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.google.common.collect.Sets;
 import com.google.common.collect.Streams;
 import java.util.Iterator;
 import java.util.List;
+import java.util.Set;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.function.Predicate;
 import java.util.function.Supplier;
@@ -34,8 +38,15 @@
     NON_CLASS_TYPE,
     UNKNOWN_TYPE,
 
+    // Used by isClassEligible
+    NON_PROGRAM_CLASS,
+    ABSTRACT_OR_INTERFACE,
+    NEVER_CLASS_INLINE,
+    IS_PINNED_TYPE,
+    HAS_FINALIZER,
+    TRIGGER_CLINIT,
+
     // Used by InlineCandidateProcessor#isClassAndUsageEligible
-    INELIGIBLE_CLASS,
     HAS_CLINIT,
     HAS_INSTANCE_FIELDS,
     NON_FINAL_TYPE,
@@ -46,7 +57,8 @@
   }
 
   private final LambdaRewriter lambdaRewriter;
-  private final ConcurrentHashMap<DexClass, Boolean> knownClasses = new ConcurrentHashMap<>();
+  private final ConcurrentHashMap<DexClass, EligibilityStatus> knownClasses =
+      new ConcurrentHashMap<>();
 
   public ClassInliner(LambdaRewriter lambdaRewriter) {
     this.lambdaRewriter = lambdaRewriter;
@@ -56,7 +68,15 @@
       DexEncodedMethod context, Instruction root, EligibilityStatus status) {
     if (Log.ENABLED && Log.isLoggingEnabledFor(ClassInliner.class)) {
       Log.info(getClass(), "At %s,", context.toSourceString());
-      Log.info(getClass(), "ClassInlining eligibility of %s: %s,", root, status);
+      Log.info(getClass(), "ClassInlining eligibility of `%s`: %s.", root, status);
+    }
+  }
+
+  private void logIneligibleUser(
+      DexEncodedMethod context, Instruction root, InstructionOrPhi ineligibleUser) {
+    if (Log.ENABLED && Log.isLoggingEnabledFor(ClassInliner.class)) {
+      Log.info(getClass(), "At %s,", context.toSourceString());
+      Log.info(getClass(), "Ineligible user of `%s`: `%s`.", root, ineligibleUser);
     }
   }
 
@@ -133,7 +153,7 @@
   //       return 1;
   //     }
   //     static int method3() {
-  //       return "F::getX";
+  //       return 123;
   //     }
   //   }
   //
@@ -195,6 +215,7 @@
         InstructionOrPhi ineligibleUser = processor.areInstanceUsersEligible(defaultOracle);
         if (ineligibleUser != null) {
           // This root may succeed if users change in future.
+          logIneligibleUser(code.method, root, ineligibleUser);
           continue;
         }
 
@@ -208,7 +229,11 @@
         anyInlinedMethods |= processor.processInlining(code, defaultOracle);
 
         // Restore normality.
-        code.removeAllTrivialPhis();
+        Set<Value> affectedValues = Sets.newIdentityHashSet();
+        code.removeAllTrivialPhis(affectedValues);
+        if (!affectedValues.isEmpty()) {
+          new TypeAnalysis(appView).narrowing(affectedValues);
+        }
         assert code.isConsistentSSA();
         rootsIterator.remove();
         repeat = true;
@@ -233,11 +258,11 @@
     }
   }
 
-  private boolean isClassEligible(AppView<AppInfoWithLiveness> appView, DexClass clazz) {
-    Boolean eligible = knownClasses.get(clazz);
+  private EligibilityStatus isClassEligible(AppView<AppInfoWithLiveness> appView, DexClass clazz) {
+    EligibilityStatus eligible = knownClasses.get(clazz);
     if (eligible == null) {
-      boolean computed = computeClassEligible(appView, clazz);
-      Boolean existing = knownClasses.putIfAbsent(clazz, computed);
+      EligibilityStatus computed = computeClassEligible(appView, clazz);
+      EligibilityStatus existing = knownClasses.putIfAbsent(clazz, computed);
       assert existing == null || existing == computed;
       eligible = existing == null ? computed : existing;
     }
@@ -248,13 +273,22 @@
   //   - is not an abstract class or interface
   //   - does not declare finalizer
   //   - does not trigger any static initializers except for its own
-  private boolean computeClassEligible(AppView<AppInfoWithLiveness> appView, DexClass clazz) {
-    if (clazz == null
-        || clazz.isNotProgramClass()
-        || clazz.accessFlags.isAbstract()
-        || clazz.accessFlags.isInterface()
-        || appView.appInfo().neverClassInline.contains(clazz.type)) {
-      return false;
+  private EligibilityStatus computeClassEligible(
+      AppView<AppInfoWithLiveness> appView, DexClass clazz) {
+    if (clazz == null) {
+      return EligibilityStatus.UNKNOWN_TYPE;
+    }
+    if (clazz.isNotProgramClass()) {
+      return EligibilityStatus.NON_PROGRAM_CLASS;
+    }
+    if (clazz.isAbstract() || clazz.isInterface()) {
+      return EligibilityStatus.ABSTRACT_OR_INTERFACE;
+    }
+    if (appView.appInfo().neverClassInline.contains(clazz.type)) {
+      return EligibilityStatus.NEVER_CLASS_INLINE;
+    }
+    if (appView.appInfo().isPinned(clazz.type)) {
+      return EligibilityStatus.IS_PINNED_TYPE;
     }
 
     // Class must not define finalizer.
@@ -262,11 +296,14 @@
     for (DexEncodedMethod method : clazz.virtualMethods()) {
       if (method.method.name == dexItemFactory.finalizeMethodName
           && method.method.proto == dexItemFactory.objectMethods.finalize.proto) {
-        return false;
+        return EligibilityStatus.HAS_FINALIZER;
       }
     }
 
     // Check for static initializers in this class or any of interfaces it implements.
-    return !clazz.initializationOfParentTypesMayHaveSideEffects(appView);
+    if (clazz.initializationOfParentTypesMayHaveSideEffects(appView)) {
+      return EligibilityStatus.TRIGGER_CLINIT;
+    }
+    return EligibilityStatus.ELIGIBLE;
   }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/FieldValueHelper.java b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/FieldValueHelper.java
index e207e50..d06b3f9 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/FieldValueHelper.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/FieldValueHelper.java
@@ -41,6 +41,9 @@
     this.code = code;
     this.root = root;
     this.appView = appView;
+    // Verify that `root` is not aliased.
+    assert root.hasOutValue();
+    assert root.outValue() == root.outValue().getAliasedValue();
   }
 
   void replaceValue(Value oldValue, Value newValue) {
@@ -124,10 +127,10 @@
       Instruction instruction = iterator.previous();
       assert instruction != null;
 
-      if (instruction == root ||
-          (instruction.isInstancePut() &&
-              instruction.asInstancePut().getField() == field &&
-              instruction.asInstancePut().object() == root.outValue())) {
+      if (instruction == root
+          || (instruction.isInstancePut()
+              && instruction.asInstancePut().getField() == field
+              && instruction.asInstancePut().object().getAliasedValue() == root.outValue())) {
         valueProducingInsn = instruction;
         break;
       }
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 a2e9358..6fdba3d 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
@@ -17,6 +17,7 @@
 import com.android.tools.r8.graph.ResolutionResult;
 import com.android.tools.r8.ir.analysis.ClassInitializationAnalysis;
 import com.android.tools.r8.ir.analysis.type.TypeAnalysis;
+import com.android.tools.r8.ir.code.Assume;
 import com.android.tools.r8.ir.code.BasicBlock;
 import com.android.tools.r8.ir.code.ConstNumber;
 import com.android.tools.r8.ir.code.IRCode;
@@ -40,16 +41,19 @@
 import com.android.tools.r8.ir.optimize.info.ParameterUsagesInfo.ParameterUsage;
 import com.android.tools.r8.kotlin.KotlinInfo;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.utils.ListUtils;
 import com.android.tools.r8.utils.Pair;
+import com.android.tools.r8.utils.StringUtils;
 import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Iterables;
 import com.google.common.collect.Lists;
 import com.google.common.collect.Sets;
 import java.util.ArrayList;
-import java.util.HashSet;
 import java.util.IdentityHashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
+import java.util.function.Function;
 import java.util.function.Predicate;
 import java.util.function.Supplier;
 
@@ -60,7 +64,7 @@
   private final AppView<AppInfoWithLiveness> appView;
   private final LambdaRewriter lambdaRewriter;
   private final Inliner inliner;
-  private final Predicate<DexClass> isClassEligible;
+  private final Function<DexClass, EligibilityStatus> isClassEligible;
   private final Predicate<DexEncodedMethod> isProcessedConcurrently;
   private final DexEncodedMethod method;
   private final Instruction root;
@@ -83,7 +87,7 @@
       AppView<AppInfoWithLiveness> appView,
       LambdaRewriter lambdaRewriter,
       Inliner inliner,
-      Predicate<DexClass> isClassEligible,
+      Function<DexClass, EligibilityStatus> isClassEligible,
       Predicate<DexEncodedMethod> isProcessedConcurrently,
       DexEncodedMethod method,
       Instruction root) {
@@ -140,8 +144,9 @@
   //      * class has class initializer marked as TrivialClassInitializer, and
   //        class initializer initializes the field we are reading here.
   EligibilityStatus isClassAndUsageEligible() {
-    if (!isClassEligible.test(eligibleClassDefinition)) {
-      return EligibilityStatus.INELIGIBLE_CLASS;
+    EligibilityStatus status = isClassEligible.apply(eligibleClassDefinition);
+    if (status != EligibilityStatus.ELIGIBLE) {
+      return status;
     }
 
     if (root.isNewInstance()) {
@@ -251,7 +256,7 @@
    *
    * @return null if all users are eligible, or the first ineligible user.
    */
-  protected InstructionOrPhi areInstanceUsersEligible(Supplier<InliningOracle> defaultOracle) {
+  InstructionOrPhi areInstanceUsersEligible(Supplier<InliningOracle> defaultOracle) {
     // No Phi users.
     if (eligibleInstance.numberOfPhiUsers() > 0) {
       return eligibleInstance.firstPhiUser(); // Not eligible.
@@ -259,8 +264,15 @@
 
     Set<Instruction> currentUsers = eligibleInstance.uniqueUsers();
     while (!currentUsers.isEmpty()) {
-      Set<Instruction> indirectUsers = new HashSet<>();
+      Set<Instruction> indirectUsers = Sets.newIdentityHashSet();
       for (Instruction user : currentUsers) {
+        if (user.isAssume()) {
+          if (user.outValue().numberOfPhiUsers() > 0) {
+            return user.outValue().firstPhiUser(); // Not eligible.
+          }
+          indirectUsers.addAll(user.outValue().uniqueUsers());
+          continue;
+        }
         // Field read/write.
         if (user.isInstanceGet()
             || (user.isInstancePut() && user.asInstancePut().value() != eligibleInstance)) {
@@ -288,7 +300,7 @@
               boolean isCorrespondingConstructorCall =
                   root.isNewInstance()
                       && !invoke.inValues().isEmpty()
-                      && root.outValue() == invoke.inValues().get(0);
+                      && root.outValue() == invoke.getReceiver();
               if (isCorrespondingConstructorCall) {
                 InliningInfo inliningInfo =
                     isEligibleConstructorCall(user.asInvokeDirect(), singleTarget, defaultOracle);
@@ -343,6 +355,7 @@
 
   // Process inlining, includes the following steps:
   //
+  //  * remove linked assume instructions if any so that users of the eligible field are up-to-date.
   //  * replace unused instance usages as arguments which are never used
   //  * inline extra methods if any, collect new direct method calls
   //  * inline direct methods if any
@@ -352,6 +365,8 @@
   //
   // Returns `true` if at least one method was inlined.
   boolean processInlining(IRCode code, Supplier<InliningOracle> defaultOracle) {
+    // Verify that `eligibleInstance` is not aliased.
+    assert eligibleInstance == eligibleInstance.getAliasedValue();
     replaceUsagesAsUnusedArgument(code);
 
     boolean anyInlinedMethods = forceInlineExtraMethodInvocations(code);
@@ -374,11 +389,14 @@
         // methods that need to be inlined anyway.
         return true;
       }
-      assert extraMethodCalls.isEmpty();
-      assert unusedArguments.isEmpty();
+      assert extraMethodCalls.isEmpty()
+          : "Remaining extra method calls: " + StringUtils.join(extraMethodCalls.entrySet(), ", ");
+      assert unusedArguments.isEmpty()
+          : "Remaining unused arg: " + StringUtils.join(unusedArguments, ", ");
     }
 
     anyInlinedMethods |= forceInlineDirectMethodInvocations(code);
+    removeAssumeInstructionsLinkedToEligibleInstance();
     removeMiscUsages(code);
     removeFieldReads(code);
     removeFieldWrites();
@@ -419,6 +437,22 @@
     return true;
   }
 
+  private void removeAssumeInstructionsLinkedToEligibleInstance() {
+    for (Instruction user : eligibleInstance.aliasedUsers()) {
+      if (!user.isAssume()) {
+        continue;
+      }
+      Assume<?> assumeInstruction = user.asAssume();
+      Value src = assumeInstruction.src();
+      Value dest = assumeInstruction.outValue();
+      assert dest.numberOfPhiUsers() == 0;
+      dest.replaceUsers(src);
+      removeInstruction(user);
+    }
+    // Verify that no more assume instructions are left as users.
+    assert eligibleInstance.aliasedUsers().stream().noneMatch(Instruction::isAssume);
+  }
+
   // Remove miscellaneous users before handling field reads.
   private void removeMiscUsages(IRCode code) {
     boolean needToRemoveUnreachableBlocks = false;
@@ -495,8 +529,8 @@
     }
   }
 
-  private void replaceFieldRead(IRCode code,
-      InstanceGet fieldRead, Map<DexField, FieldValueHelper> fieldHelpers) {
+  private void replaceFieldRead(
+      IRCode code, InstanceGet fieldRead, Map<DexField, FieldValueHelper> fieldHelpers) {
     Value value = fieldRead.outValue();
     if (value != null) {
       FieldValueHelper helper =
@@ -508,7 +542,11 @@
         fieldValueHelper.replaceValue(value, newValue);
       }
       assert value.numberOfAllUsers() == 0;
-      new TypeAnalysis(appView).narrowing(newValue.affectedValues());
+      // `newValue` could be a phi introduced by FieldValueHelper. Its initial type is set as the
+      // type of read field, but it could be more precise than that due to (multiple) inlining.
+      // In addition to values affected by `newValue`, it's necessary to revisit `newValue` itself.
+      new TypeAnalysis(appView).narrowing(
+          Iterables.concat(ImmutableSet.of(newValue), newValue.affectedValues()));
     }
     removeInstruction(fieldRead);
   }
@@ -539,7 +577,8 @@
     assert isEligibleSingleTarget(singleTarget);
 
     // Must be a constructor called on the receiver.
-    if (invoke.inValues().lastIndexOf(eligibleInstance) != 0) {
+    if (ListUtils.lastIndexMatching(
+        invoke.inValues(), v -> v.getAliasedValue() == eligibleInstance) != 0) {
       return null;
     }
 
@@ -594,7 +633,7 @@
         : null;
   }
 
-  // An invoke is eligible for inlinining in the following cases:
+  // An invoke is eligible for inlining in the following cases:
   //
   // - if it does not return the receiver
   // - if there are no uses of the out value
@@ -645,7 +684,8 @@
       DexEncodedMethod singleTarget,
       Set<Instruction> indirectUsers) {
     assert isEligibleSingleTarget(singleTarget);
-    if (invoke.inValues().lastIndexOf(eligibleInstance) > 0) {
+    if (ListUtils.lastIndexMatching(
+        invoke.inValues(), v -> v.getAliasedValue() == eligibleInstance) > 0) {
       return null; // Instance passed as an argument.
     }
     return isEligibleVirtualMethodCall(
@@ -714,7 +754,8 @@
       return false;
     }
     if (invoke.isInvokeMethodWithReceiver()
-        && invoke.asInvokeMethodWithReceiver().getReceiver() == eligibleInstance) {
+        && invoke.asInvokeMethodWithReceiver().getReceiver().getAliasedValue()
+            == eligibleInstance) {
       return false;
     }
     if (invoke.isInvokeSuper()) {
@@ -755,7 +796,7 @@
 
     // If we got here with invocation on receiver the user is ineligible.
     if (invoke.isInvokeMethodWithReceiver()) {
-      if (arguments.get(0) == eligibleInstance) {
+      if (arguments.get(0).getAliasedValue() == eligibleInstance) {
         return false;
       }
 
@@ -796,7 +837,7 @@
       Supplier<InliningOracle> defaultOracle) {
     // Go through all arguments, see if all usages of eligibleInstance are good.
     for (int argIndex = 0; argIndex < arguments.size(); argIndex++) {
-      Value argument = arguments.get(argIndex);
+      Value argument = arguments.get(argIndex).getAliasedValue();
       if (argument != eligibleInstance) {
         continue; // Nothing to worry about.
       }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackDelayed.java b/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackDelayed.java
index 162642c..d185a37 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackDelayed.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackDelayed.java
@@ -107,6 +107,11 @@
   // METHOD OPTIMIZATION INFO:
 
   @Override
+  public void markForceInline(DexEncodedMethod method) {
+    getMethodOptimizationInfoForUpdating(method).markForceInline();
+  }
+
+  @Override
   public synchronized void markInlinedIntoSingleCallSite(DexEncodedMethod method) {
     getMethodOptimizationInfoForUpdating(method).markInlinedIntoSingleCallSite();
   }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackIgnore.java b/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackIgnore.java
index ea5afa0..79b2e7f 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackIgnore.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackIgnore.java
@@ -44,6 +44,9 @@
   // METHOD OPTIMIZATION INFO:
 
   @Override
+  public void markForceInline(DexEncodedMethod method) {}
+
+  @Override
   public void markInlinedIntoSingleCallSite(DexEncodedMethod method) {}
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackSimple.java b/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackSimple.java
index 537cbd1..9119b4b 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackSimple.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackSimple.java
@@ -52,6 +52,11 @@
   // METHOD OPTIMIZATION INFO.
 
   @Override
+  public void markForceInline(DexEncodedMethod method) {
+    // Ignored.
+  }
+
+  @Override
   public synchronized void markInlinedIntoSingleCallSite(DexEncodedMethod method) {
     method.getMutableOptimizationInfo().markInlinedIntoSingleCallSite();
   }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/lambda/LambdaGroup.java b/src/main/java/com/android/tools/r8/ir/optimize/lambda/LambdaGroup.java
index a90efc7..4ce3033 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/lambda/LambdaGroup.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/lambda/LambdaGroup.java
@@ -79,6 +79,10 @@
     }
   }
 
+  public final boolean allLambdas(Predicate<LambdaInfo> predicate) {
+    return !anyLambda(lambda -> !predicate.test(lambda));
+  }
+
   public final boolean anyLambda(Predicate<LambdaInfo> predicate) {
     assert verifyLambdaIds(false);
     for (LambdaInfo info : lambdas.values()) {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/lambda/LambdaMerger.java b/src/main/java/com/android/tools/r8/ir/optimize/lambda/LambdaMerger.java
index f399ad6..89451c9 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/lambda/LambdaMerger.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/lambda/LambdaMerger.java
@@ -228,6 +228,11 @@
     // sequential lambda ids, create group lambda classes.
     Map<LambdaGroup, DexProgramClass> lambdaGroupsClasses = finalizeLambdaGroups();
 
+    // Mark all the implementation methods for force inlining.
+    for (LambdaGroup group : lambdaGroupsClasses.keySet()) {
+      group.forEachLambda(info -> info.clazz.virtualMethods().forEach(feedback::markForceInline));
+    }
+
     // Fixup optimization info to ensure that the optimization info does not refer to any merged
     // lambdas.
     LambdaMergerOptimizationInfoFixer optimizationInfoFixer =
@@ -238,7 +243,6 @@
     this.strategyFactory = (method, code) -> new ApplyStrategy(method, code, optimizationInfoFixer);
 
     // Add synthesized lambda group classes to the builder.
-
     for (Entry<LambdaGroup, DexProgramClass> entry : lambdaGroupsClasses.entrySet()) {
       DexProgramClass synthesizedClass = entry.getValue();
       appView.appInfo().addSynthesizedClass(synthesizedClass);
@@ -254,6 +258,18 @@
       synthesizedClass.forEachMethod(
           encodedMethod -> encodedMethod.markProcessed(ConstraintWithTarget.NEVER));
     }
+
+    // Verify that all implementation methods are marked for force inlining (i.e., check that the
+    // delayed optimization feedback has been flushed).
+    assert lambdaGroupsClasses.keySet().stream()
+        .allMatch(
+            group ->
+                group.allLambdas(
+                    lambda ->
+                        lambda.clazz.virtualMethods().stream()
+                            .map(DexEncodedMethod::getOptimizationInfo)
+                            .allMatch(MethodOptimizationInfo::forceInline)));
+
     converter.optimizeSynthesizedClasses(lambdaGroupsClasses.values(), executorService);
 
     // Rewrite lambda class references into lambda group class
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KotlinLambdaGroupClassBuilder.java b/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KotlinLambdaGroupClassBuilder.java
index 4226daa..15d4160 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KotlinLambdaGroupClassBuilder.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KotlinLambdaGroupClassBuilder.java
@@ -105,14 +105,6 @@
         // anyways and our new method is a product of inlining.
         MethodAccessFlags accessFlags = MAIN_METHOD_FLAGS.copy();
 
-        // Mark all the impl methods for force inlining
-        // LambdaGroupVirtualMethodSourceCode relies on.
-        for (DexEncodedMethod implMethod : implMethods) {
-          if (implMethod != null) {
-            implMethod.getMutableOptimizationInfo().markForceInline();
-          }
-        }
-
         DexMethod method = factory.createMethod(group.getGroupClassType(), methodProto, methodName);
         result.add(
             new DexEncodedMethod(
diff --git a/src/main/java/com/android/tools/r8/shaking/AnnotationRemover.java b/src/main/java/com/android/tools/r8/shaking/AnnotationRemover.java
index cadd59c..693058b 100644
--- a/src/main/java/com/android/tools/r8/shaking/AnnotationRemover.java
+++ b/src/main/java/com/android/tools/r8/shaking/AnnotationRemover.java
@@ -139,8 +139,8 @@
     return isAnnotationTypeLive(annotation);
   }
 
-  public AnnotationRemover ensureValid(ProguardConfiguration.Builder compatibility) {
-    keep.ensureValid(appView.options().forceProguardCompatibility, compatibility);
+  public AnnotationRemover ensureValid() {
+    keep.ensureValid(appView.options().forceProguardCompatibility);
     return this;
   }
 
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 2e7ed67..65b708c 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -276,9 +276,6 @@
   /** A queue of items that need processing. Different items trigger different actions. */
   private final EnqueuerWorklist workList;
 
-  /** A queue of items that have been added to try to keep Proguard compatibility. */
-  private final EnqueuerWorklist proguardCompatibilityWorkList;
-
   /**
    * A set of methods that need code inspection for Java reflection in use.
    */
@@ -305,11 +302,6 @@
    */
   private final Map<DexType, Set<DexAnnotation>> deferredAnnotations = new IdentityHashMap<>();
 
-  /**
-   * Set of keep rules generated for Proguard compatibility in Proguard compatibility mode.
-   */
-  private final ProguardConfiguration.Builder compatibility;
-
   /** Map of active if rules to speed up aapt2 generated keep rules. */
   private Map<Wrapper<ProguardIfRule>, Set<ProguardIfRule>> activeIfRules;
 
@@ -328,21 +320,17 @@
   Enqueuer(
       AppView<? extends AppInfoWithSubtyping> appView,
       GraphConsumer keptGraphConsumer,
-      ProguardConfiguration.Builder compatibility,
       Mode mode) {
     assert appView.appServices() != null;
     InternalOptions options = appView.options();
     this.appInfo = appView.appInfo();
     this.appView = appView;
-    this.compatibility = compatibility;
     this.forceProguardCompatibility = options.forceProguardCompatibility;
     this.graphReporter = new GraphReporter();
     this.keptGraphConsumer = recordKeptGraph(options, keptGraphConsumer);
     this.mode = mode;
     this.options = options;
     this.workList = EnqueuerWorklist.createWorklist(appView);
-    this.proguardCompatibilityWorkList =
-        EnqueuerWorklist.createProguardCompatibilityWorklist(appView);
 
     if (options.enableGeneratedMessageLiteShrinking && mode.isInitialOrFinalTreeShaking()) {
       registerAnalysis(new ProtoEnqueuerExtension(appView));
@@ -458,15 +446,14 @@
       } else {
         workList.enqueueMarkInstantiatedAction(clazz, witness);
         if (clazz.hasDefaultInitializer()) {
+          DexEncodedMethod defaultInitializer = clazz.getDefaultInitializer();
           if (forceProguardCompatibility) {
-            ProguardKeepRule compatRule =
-                ProguardConfigurationUtils.buildDefaultInitializerKeepRule(clazz);
-            proguardCompatibilityWorkList.enqueueMarkMethodKeptAction(
-                clazz.getDefaultInitializer(),
-                KeepReason.dueToProguardCompatibilityKeepRule(compatRule));
+            workList.enqueueMarkMethodKeptAction(
+                defaultInitializer,
+                graphReporter.reportCompatKeepDefaultInitializer(clazz, defaultInitializer));
           }
           if (clazz.isExternalizable(appView)) {
-            enqueueMarkMethodLiveAction(clazz, clazz.getDefaultInitializer(), witness);
+            enqueueMarkMethodLiveAction(clazz, defaultInitializer, witness);
           }
         }
       }
@@ -1055,10 +1042,8 @@
         DexProgramClass baseClass = getProgramClassOrNull(baseType);
         if (baseClass != null) {
           // Don't require any constructor, see b/112386012.
-          markClassAsInstantiatedWithCompatRule(baseClass);
-        } else {
-          // This also handles reporting of missing classes.
-          markTypeAsLive(baseType, this::reportClassReferenced);
+          markClassAsInstantiatedWithCompatRule(
+              baseClass, graphReporter.reportCompatInstantiated(baseClass, currentMethod));
         }
         return true;
       }
@@ -1463,7 +1448,6 @@
 
     populateInstantiatedTypesCache(clazz);
 
-    collectProguardCompatibilityRule(reason);
     if (Log.ENABLED) {
       Log.verbose(getClass(), "Class `%s` is instantiated, processing...", clazz);
     }
@@ -1700,7 +1684,6 @@
     }
     processAnnotations(encodedField, encodedField.annotations.annotations);
     liveFields.add(encodedField, reason);
-    collectProguardCompatibilityRule(reason);
 
     // Add all dependent members to the workqueue.
     enqueueRootItems(rootSet.getDependentItems(encodedField));
@@ -1719,7 +1702,6 @@
     }
     processAnnotations(field, field.annotations.annotations);
     liveFields.add(field, reason);
-    collectProguardCompatibilityRule(reason);
 
     // Add all dependent members to the workqueue.
     enqueueRootItems(rootSet.getDependentItems(field));
@@ -2342,9 +2324,6 @@
           pendingReflectiveUses.forEach(this::handleReflectiveBehavior);
           pendingReflectiveUses.clear();
         }
-        if (!proguardCompatibilityWorkList.isEmpty()) {
-          proguardCompatibilityWorkList.transferTo(workList, liveMethods::add);
-        }
         if (!workList.isEmpty()) {
           continue;
         }
@@ -2493,8 +2472,6 @@
       return;
     }
 
-    collectProguardCompatibilityRule(reason);
-
     Set<DexEncodedMethod> superCallTargets = superInvokeDependencies.get(method);
     if (superCallTargets != null) {
       for (DexEncodedMethod superCallTarget : superCallTargets) {
@@ -2536,12 +2513,6 @@
         clazz -> graphReporter.reportClassReferencedFrom(clazz, method));
   }
 
-  private void collectProguardCompatibilityRule(KeepReason reason) {
-    if (reason.isDueToProguardCompatibility() && compatibility != null) {
-      compatibility.addRule(reason.getProguardKeepRule());
-    }
-  }
-
   private void markClassAsInstantiatedWithReason(DexProgramClass clazz, KeepReason reason) {
     workList.enqueueMarkInstantiatedAction(clazz, reason);
     if (clazz.hasDefaultInitializer()) {
@@ -2549,26 +2520,22 @@
     }
   }
 
-  private void markClassAsInstantiatedWithCompatRule(DexProgramClass clazz) {
-    ProguardKeepRule rule = ProguardConfigurationUtils.buildDefaultInitializerKeepRule(clazz);
-    KeepReason reason = KeepReason.dueToProguardCompatibilityKeepRule(rule);
+  private void markClassAsInstantiatedWithCompatRule(DexProgramClass clazz, KeepReason reason) {
     if (clazz.isInterface() && !clazz.accessFlags.isAnnotation()) {
       markInterfaceAsInstantiated(clazz, reason);
       return;
     }
-    proguardCompatibilityWorkList.enqueueMarkInstantiatedAction(clazz, reason);
+    workList.enqueueMarkInstantiatedAction(clazz, reason);
     if (clazz.hasDefaultInitializer()) {
-      proguardCompatibilityWorkList.enqueueMarkReachableDirectAction(
-          clazz.getDefaultInitializer().method, reason);
+      DexEncodedMethod defaultInitializer = clazz.getDefaultInitializer();
+      workList.enqueueMarkReachableDirectAction(
+          defaultInitializer.method,
+          graphReporter.reportCompatKeepDefaultInitializer(clazz, defaultInitializer));
     }
   }
 
   private void markMethodAsLiveWithCompatRule(DexProgramClass clazz, DexEncodedMethod method) {
-    proguardCompatibilityWorkList.enqueueMarkMethodLiveAction(
-        clazz,
-        method,
-        KeepReason.dueToProguardCompatibilityKeepRule(
-            ProguardConfigurationUtils.buildMethodKeepRule(clazz, method)));
+    enqueueMarkMethodLiveAction(clazz, method, graphReporter.reportCompatKeepMethod(clazz, method));
   }
 
   private void handleReflectiveBehavior(DexEncodedMethod method) {
@@ -3173,6 +3140,40 @@
       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) {
@@ -3233,6 +3234,13 @@
   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() {
@@ -3247,7 +3255,7 @@
 
   private boolean skipReporting(KeepReason reason) {
     assert reason != null;
-    if (reason == KeepReasonWitness.INSTANCE) {
+    if (reason == KeepReasonWitness.INSTANCE || reason == KeepReasonWitness.COMPAT_INSTANCE) {
       return true;
     }
     assert getSourceNode(reason) != null;
diff --git a/src/main/java/com/android/tools/r8/shaking/EnqueuerFactory.java b/src/main/java/com/android/tools/r8/shaking/EnqueuerFactory.java
index 340b469..e716083 100644
--- a/src/main/java/com/android/tools/r8/shaking/EnqueuerFactory.java
+++ b/src/main/java/com/android/tools/r8/shaking/EnqueuerFactory.java
@@ -13,18 +13,12 @@
 
   public static Enqueuer createForInitialTreeShaking(
       AppView<? extends AppInfoWithSubtyping> appView) {
-    return createForInitialTreeShaking(appView, null);
-  }
-
-  public static Enqueuer createForInitialTreeShaking(
-      AppView<? extends AppInfoWithSubtyping> appView,
-      ProguardConfiguration.Builder compatibility) {
-    return new Enqueuer(appView, null, compatibility, Mode.INITIAL_TREE_SHAKING);
+    return new Enqueuer(appView, null, Mode.INITIAL_TREE_SHAKING);
   }
 
   public static Enqueuer createForFinalTreeShaking(
       AppView<? extends AppInfoWithSubtyping> appView, GraphConsumer keptGraphConsumer) {
-    return new Enqueuer(appView, keptGraphConsumer, null, Mode.FINAL_TREE_SHAKING);
+    return new Enqueuer(appView, keptGraphConsumer, Mode.FINAL_TREE_SHAKING);
   }
 
   public static Enqueuer createForMainDexTracing(AppView<? extends AppInfoWithSubtyping> appView) {
@@ -33,11 +27,11 @@
 
   public static Enqueuer createForMainDexTracing(
       AppView<? extends AppInfoWithSubtyping> appView, GraphConsumer keptGraphConsumer) {
-    return new Enqueuer(appView, keptGraphConsumer, null, Mode.MAIN_DEX_TRACING);
+    return new Enqueuer(appView, keptGraphConsumer, Mode.MAIN_DEX_TRACING);
   }
 
   public static Enqueuer createForWhyAreYouKeeping(
       AppView<? extends AppInfoWithSubtyping> appView, GraphConsumer keptGraphConsumer) {
-    return new Enqueuer(appView, keptGraphConsumer, null, Mode.WHY_ARE_YOU_KEEPING);
+    return new Enqueuer(appView, keptGraphConsumer, Mode.WHY_ARE_YOU_KEEPING);
   }
 }
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 86058bd..bde6b8d 100644
--- a/src/main/java/com/android/tools/r8/shaking/EnqueuerWorklist.java
+++ b/src/main/java/com/android/tools/r8/shaking/EnqueuerWorklist.java
@@ -12,7 +12,6 @@
 import com.android.tools.r8.graph.DexProgramClass;
 import java.util.ArrayDeque;
 import java.util.Queue;
-import java.util.function.BiPredicate;
 
 public class EnqueuerWorklist {
 
@@ -46,19 +45,12 @@
   private final AppView<?> appView;
   private final Queue<Action> queue = new ArrayDeque<>();
 
-  private final boolean restrictToProguardCompatibilityRules;
-
-  private EnqueuerWorklist(AppView<?> appView, boolean restrictToProguardCompatibilityRules) {
+  private EnqueuerWorklist(AppView<?> appView) {
     this.appView = appView;
-    this.restrictToProguardCompatibilityRules = restrictToProguardCompatibilityRules;
   }
 
   public static EnqueuerWorklist createWorklist(AppView<?> appView) {
-    return new EnqueuerWorklist(appView, false);
-  }
-
-  public static EnqueuerWorklist createProguardCompatibilityWorklist(AppView<?> appView) {
-    return new EnqueuerWorklist(appView, true);
+    return new EnqueuerWorklist(appView);
   }
 
   public boolean isEmpty() {
@@ -69,32 +61,15 @@
     return queue.poll();
   }
 
-  public void transferTo(
-      EnqueuerWorklist worklist, BiPredicate<DexEncodedMethod, KeepReason> filter) {
-    while (!queue.isEmpty()) {
-      Action action = queue.poll();
-      if (action.kind == Action.Kind.MARK_METHOD_LIVE) {
-        DexEncodedMethod method = (DexEncodedMethod) action.target;
-        if (!filter.test(method, action.reason)) {
-          continue;
-        }
-      }
-      worklist.queue.add(action);
-    }
-  }
-
   void enqueueMarkReachableDirectAction(DexMethod method, KeepReason reason) {
-    assert !restrictToProguardCompatibilityRules || reason.isDueToProguardCompatibility();
     queue.add(new Action(Action.Kind.MARK_REACHABLE_DIRECT, method, null, reason));
   }
 
   void enqueueMarkReachableVirtualAction(DexMethod method, KeepReason reason) {
-    assert !restrictToProguardCompatibilityRules || reason.isDueToProguardCompatibility();
     queue.add(new Action(Action.Kind.MARK_REACHABLE_VIRTUAL, method, null, reason));
   }
 
   void enqueueMarkReachableInterfaceAction(DexMethod method, KeepReason reason) {
-    assert !restrictToProguardCompatibilityRules || reason.isDueToProguardCompatibility();
     queue.add(new Action(Action.Kind.MARK_REACHABLE_INTERFACE, method, null, reason));
   }
 
@@ -104,32 +79,27 @@
 
   public void enqueueMarkReachableFieldAction(
       DexProgramClass clazz, DexEncodedField field, KeepReason reason) {
-    assert !restrictToProguardCompatibilityRules || reason.isDueToProguardCompatibility();
     assert field.field.holder == clazz.type;
     queue.add(new Action(Action.Kind.MARK_REACHABLE_FIELD, field, null, reason));
   }
 
   void enqueueMarkInstantiatedAction(DexProgramClass clazz, KeepReason reason) {
     assert !clazz.isInterface() || clazz.accessFlags.isAnnotation();
-    assert !restrictToProguardCompatibilityRules || reason.isDueToProguardCompatibility();
     queue.add(new Action(Action.Kind.MARK_INSTANTIATED, clazz, null, reason));
   }
 
   void enqueueMarkMethodLiveAction(
       DexProgramClass clazz, DexEncodedMethod method, KeepReason reason) {
-    assert !restrictToProguardCompatibilityRules || reason.isDueToProguardCompatibility();
     assert method.method.holder == clazz.type;
     queue.add(new Action(Action.Kind.MARK_METHOD_LIVE, method, null, reason));
   }
 
   void enqueueMarkMethodKeptAction(DexEncodedMethod method, KeepReason reason) {
-    assert !restrictToProguardCompatibilityRules || reason.isDueToProguardCompatibility();
     assert method.isProgramMethod(appView);
     queue.add(new Action(Action.Kind.MARK_METHOD_KEPT, method, null, reason));
   }
 
   void enqueueMarkFieldKeptAction(DexEncodedField field, KeepReason reason) {
-    assert !restrictToProguardCompatibilityRules || reason.isDueToProguardCompatibility();
     assert field.isProgramField(appView);
     queue.add(new Action(Action.Kind.MARK_FIELD_KEPT, field, null, reason));
   }
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 eae4a9b..95769e7 100644
--- a/src/main/java/com/android/tools/r8/shaking/KeepReason.java
+++ b/src/main/java/com/android/tools/r8/shaking/KeepReason.java
@@ -3,7 +3,6 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.shaking;
 
-import com.android.tools.r8.errors.Unreachable;
 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;
@@ -12,9 +11,7 @@
 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 java.util.Collection;
 
 // TODO(herhut): Canonicalize reason objects.
 public abstract class KeepReason {
@@ -27,10 +24,6 @@
     return new AnnotatedOn(definition);
   }
 
-  static KeepReason dueToProguardCompatibilityKeepRule(ProguardKeepRule rule) {
-    return new DueToProguardCompatibilityKeepRule(rule);
-  }
-
   static KeepReason instantiatedIn(DexEncodedMethod method) {
     return new InstatiatedIn(method);
   }
@@ -83,10 +76,6 @@
     return null;
   }
 
-  public ProguardKeepRuleBase getProguardKeepRule() {
-    return null;
-  }
-
   public static KeepReason targetedBySuperFrom(DexEncodedMethod from) {
     return new TargetedBySuper(from);
   }
@@ -103,55 +92,6 @@
     return new OverridesMethod(method);
   }
 
-  public Collection<DexReference> getPreconditions() {
-    throw new Unreachable();
-  }
-
-  private static class DueToKeepRule extends KeepReason {
-
-    final ProguardKeepRuleBase keepRule;
-
-    private DueToKeepRule(ProguardKeepRuleBase keepRule) {
-      this.keepRule = keepRule;
-    }
-
-    @Override
-    public EdgeKind edgeKind() {
-      return EdgeKind.KeepRule;
-    }
-
-    @Override
-    public boolean isDueToKeepRule() {
-      return true;
-    }
-
-    @Override
-    public ProguardKeepRuleBase getProguardKeepRule() {
-      return keepRule;
-    }
-
-    @Override
-    public GraphNode getSourceNode(Enqueuer enqueuer) {
-      return enqueuer.getKeepRuleGraphNode(null, keepRule);
-    }
-  }
-
-  private static class DueToProguardCompatibilityKeepRule extends DueToKeepRule {
-    private DueToProguardCompatibilityKeepRule(ProguardKeepRule keepRule) {
-      super(keepRule);
-    }
-
-    @Override
-    public EdgeKind edgeKind() {
-      return EdgeKind.CompatibilityRule;
-    }
-
-    @Override
-    public boolean isDueToProguardCompatibility() {
-      return true;
-    }
-  }
-
   private abstract static class BasedOnOtherMethod extends KeepReason {
 
     private final DexEncodedMethod method;
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardKeepAttributes.java b/src/main/java/com/android/tools/r8/shaking/ProguardKeepAttributes.java
index b5305d5..f1e06d1 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardKeepAttributes.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardKeepAttributes.java
@@ -140,16 +140,11 @@
     stackMapTable = update(stackMapTable, STACK_MAP_TABLE, patterns);
   }
 
-  public void ensureValid(
-      boolean forceProguardCompatibility, ProguardConfiguration.Builder compatibility) {
+  public void ensureValid(boolean forceProguardCompatibility) {
     if (forceProguardCompatibility && innerClasses != enclosingMethod) {
       // If only one is true set both to true in Proguard compatibility mode.
       enclosingMethod = true;
       innerClasses = true;
-      compatibility.addKeepAttributePatterns(
-          ImmutableList.of(
-              ProguardKeepAttributes.INNER_CLASSES,
-              ProguardKeepAttributes.ENCLOSING_METHOD));
     }
     if (innerClasses && !enclosingMethod) {
       throw new CompilationError("Attribute InnerClasses requires EnclosingMethod attribute. "
@@ -164,8 +159,6 @@
     if (forceProguardCompatibility && localVariableTable && !lineNumberTable) {
       // If locals are kept, assume line numbers should be kept too.
       lineNumberTable = true;
-      compatibility.addKeepAttributePatterns(
-          ImmutableList.of(ProguardKeepAttributes.LINE_NUMBER_TABLE));
     }
     if (localVariableTable && !lineNumberTable) {
       throw new CompilationError(
diff --git a/src/main/java/com/android/tools/r8/utils/InternalOptions.java b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
index d78cb45..9f2a36b 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -197,9 +197,6 @@
   public boolean enableNonNullTracking = true;
   public boolean enableInlining =
       !Version.isDev() || System.getProperty("com.android.tools.r8.disableinlining") == null;
-  public boolean enableInliningOfInvokesWithDefinitelyNullReceivers =
-      System.getProperty("com.android.tools.r8.disableInliningOfInvokesWithDefinitelyNullReceivers")
-          == null;
   public boolean enableInliningOfInvokesWithNullableReceivers = true;
   public boolean disableInliningOfLibraryMethodOverrides = true;
   public boolean enableClassInlining = true;
@@ -616,7 +613,6 @@
   // code objects needed for correct desugaring needs to be provided to the consumer.
   public DesugarGraphConsumer desugarGraphConsumer = null;
 
-  public Path proguardCompatibilityRulesOutput = null;
   public Consumer<List<ProguardConfigurationRule>> syntheticProguardRulesConsumer = null;
 
   public static boolean assertionsEnabled() {
diff --git a/src/main/java/com/android/tools/r8/utils/ListUtils.java b/src/main/java/com/android/tools/r8/utils/ListUtils.java
index a9c3f98..370d5e2 100644
--- a/src/main/java/com/android/tools/r8/utils/ListUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/ListUtils.java
@@ -8,9 +8,19 @@
 import java.util.Collection;
 import java.util.List;
 import java.util.function.Function;
+import java.util.function.Predicate;
 
 public class ListUtils {
 
+  public static <T> int lastIndexMatching(List<T> list, Predicate<T> tester) {
+    for (int i = list.size() - 1; i >= 0; i--) {
+      if (tester.test(list.get(i))) {
+        return i;
+      }
+    }
+    return -1;
+  }
+
   public static <S, T> List<T> map(Collection<S> list, Function<S, T> fn) {
     List<T> result = new ArrayList<>(list.size());
     for (S element : list) {
diff --git a/src/main/java/com/android/tools/r8/utils/StringUtils.java b/src/main/java/com/android/tools/r8/utils/StringUtils.java
index cd47dc5..44a26be 100644
--- a/src/main/java/com/android/tools/r8/utils/StringUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/StringUtils.java
@@ -165,6 +165,10 @@
     return join(LINE_SEPARATOR, lines);
   }
 
+  public static <T> String joinLines(Collection<T> collection) {
+    return join(collection, LINE_SEPARATOR, BraceType.NONE);
+  }
+
   public static List<String> splitLines(String content) {
     int length = content.length();
     List<String> lines = new ArrayList<>();
diff --git a/src/main/java/com/android/tools/r8/utils/Timing.java b/src/main/java/com/android/tools/r8/utils/Timing.java
index df575a6..7d68954 100644
--- a/src/main/java/com/android/tools/r8/utils/Timing.java
+++ b/src/main/java/com/android/tools/r8/utils/Timing.java
@@ -13,13 +13,9 @@
 // Finally a report is printed by:
 //     t.report();
 
-import java.lang.management.ManagementFactory;
-import java.lang.management.MemoryMXBean;
-import java.lang.management.MemoryPoolMXBean;
-import java.util.ArrayList;
 import java.util.LinkedHashMap;
-import java.util.List;
 import java.util.Map;
+import java.util.Map.Entry;
 import java.util.Stack;
 
 public class Timing {
@@ -41,29 +37,45 @@
     stack.push(new Node("Recorded timings for " + title));
   }
 
+  private static class MemInfo {
+    final long used;
+
+    MemInfo(long used) {
+      this.used = used;
+    }
+
+    public static MemInfo fromTotalAndFree(long total, long free) {
+      return new MemInfo(total - free);
+    }
+
+    long usedDelta(MemInfo previous) {
+      return used - previous.used;
+    }
+  }
+
   class Node {
     final String title;
 
     final Map<String, Node> children = new LinkedHashMap<>();
     long duration = 0;
     long start_time;
-    List<String> startMemory;
-    List<String> endMemory;
+    Map<String, MemInfo> startMemory;
+    Map<String, MemInfo> endMemory;
 
     Node(String title) {
       this.title = title;
-      this.start_time = System.nanoTime();
       if (trackMemory) {
         startMemory = computeMemoryInformation();
       }
+      this.start_time = System.nanoTime();
     }
 
     void restart() {
       assert start_time == -1;
-      start_time = System.nanoTime();
       if (trackMemory) {
         startMemory = computeMemoryInformation();
       }
+      start_time = System.nanoTime();
     }
 
     void end() {
@@ -71,7 +83,6 @@
       start_time = -1;
       assert duration() >= 0;
       if (trackMemory) {
-        System.gc();
         endMemory = computeMemoryInformation();
       }
     }
@@ -82,13 +93,12 @@
 
     @Override
     public String toString() {
-      return title + ": " + (duration() / 1000000) + "ms.";
+      return title + ": " + prettyTime(duration());
     }
 
     public String toString(Node top) {
       if (this == top) return toString();
-      long percentage = duration() * 100 / top.duration();
-      return toString() + " (" + percentage + "%)";
+      return toString() + " (" + prettyPercentage(duration(), top.duration()) + ")";
     }
 
     public void report(int depth, Node top) {
@@ -100,44 +110,64 @@
         System.out.print("- ");
       }
       System.out.println(toString(top));
-      System.out.println();
       if (trackMemory) {
-        printMemoryStart(depth);
-        System.out.println();
+        printMemory(depth);
       }
       children.values().forEach(p -> p.report(depth + 1, top));
-      if (trackMemory) {
-        printMemoryEnd(depth);
-        System.out.println();
-      }
     }
 
-    private void printMemoryStart(int depth) {
-      if (startMemory != null) {
-        printMemory(depth, title + "(Memory) Start: ", startMemory);
-      }
-    }
-
-    private void printMemoryEnd(int depth) {
-      if (endMemory != null) {
-        printMemory(depth, title + "(Memory) End: ", endMemory);
-      }
-    }
-
-    private void printMemory(int depth, String header, List<String> strings) {
-      for (int i = 0; i <= depth; i++) {
-        System.out.print("  ");
-      }
-      System.out.println(header);
-      for (String memoryInfo : strings) {
-        for (int i = 0; i <= depth; i++) {
-          System.out.print("  ");
+    private void printMemory(int depth) {
+      for (Entry<String, MemInfo> start : startMemory.entrySet()) {
+        if (start.getKey().equals("Memory")) {
+          for (int i = 0; i <= depth; i++) {
+            System.out.print("  ");
+          }
+          MemInfo endValue = endMemory.get(start.getKey());
+          MemInfo startValue = start.getValue();
+          System.out.println(
+              start.getKey()
+                  + " start: "
+                  + prettySize(startValue.used)
+                  + ", end: "
+                  + prettySize(endValue.used)
+                  + ", delta: "
+                  + prettySize(endValue.usedDelta(startValue)));
         }
-        System.out.println(memoryInfo);
       }
     }
   }
 
+  private static String prettyPercentage(long part, long total) {
+    return (part * 100 / total) + "%";
+  }
+
+  private static String prettyTime(long value) {
+    return (value / 1000000) + "ms";
+  }
+
+  private static String prettySize(long value) {
+    return prettyNumber(value / 1024) + "k";
+  }
+
+  private static String prettyNumber(long value) {
+    String printed = "" + Math.abs(value);
+    if (printed.length() < 4) {
+      return "" + value;
+    }
+    StringBuilder builder = new StringBuilder();
+    if (value < 0) {
+      builder.append('-');
+    }
+    int prefix = printed.length() % 3;
+    builder.append(printed, 0, prefix);
+    for (int i = prefix; i < printed.length(); i += 3) {
+      if (i > 0) {
+        builder.append('.');
+      }
+      builder.append(printed, i, i + 3);
+    }
+    return builder.toString();
+  }
 
   public void begin(String title) {
     Node parent = stack.peek();
@@ -177,22 +207,13 @@
     void apply();
   }
 
-  private List<String> computeMemoryInformation() {
-    List<String> strings = new ArrayList<>();
-    strings.add(
-        "Free memory: "
-            + Runtime.getRuntime().freeMemory()
-            + "\tTotal memory: "
-            + Runtime.getRuntime().totalMemory()
-            + "\tMax memory: "
-            + Runtime.getRuntime().maxMemory());
-    MemoryMXBean memoryMXBean = ManagementFactory.getMemoryMXBean();
-    strings.add("Heap summary: " + memoryMXBean.getHeapMemoryUsage().toString());
-    strings.add("Non-heap summary: " + memoryMXBean.getNonHeapMemoryUsage().toString());
-    // Print out the memory information for all managed memory pools.
-    for (MemoryPoolMXBean memoryPoolMXBean : ManagementFactory.getMemoryPoolMXBeans()) {
-      strings.add(memoryPoolMXBean.getName() + ": " + memoryPoolMXBean.getUsage().toString());
-    }
-    return strings;
+  private Map<String, MemInfo> computeMemoryInformation() {
+    System.gc();
+    Map<String, MemInfo> info = new LinkedHashMap<>();
+    info.put(
+        "Memory",
+        MemInfo.fromTotalAndFree(
+            Runtime.getRuntime().totalMemory(), Runtime.getRuntime().freeMemory()));
+    return info;
   }
 }
diff --git a/src/test/java/com/android/tools/r8/L8CommandTest.java b/src/test/java/com/android/tools/r8/L8CommandTest.java
index 525a9ec..97569ab 100644
--- a/src/test/java/com/android/tools/r8/L8CommandTest.java
+++ b/src/test/java/com/android/tools/r8/L8CommandTest.java
@@ -8,9 +8,16 @@
 import static org.junit.Assert.assertTrue;
 
 import com.android.tools.r8.dex.Marker;
+import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.utils.AndroidApiLevel;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
 import java.nio.file.Path;
+import java.util.ArrayList;
 import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import org.junit.Ignore;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.rules.TemporaryFolder;
@@ -117,4 +124,49 @@
                     StringResource.fromFile(ToolHelper.DESUGAR_LIB_JSON_FOR_TESTING))
                 .build());
   }
+
+  // TODO(b/139273544): Re-enable shrinking once fixed and re-enable tests using shrinking.
+  @Test
+  @Ignore
+  public void addProguardConfigurationString() throws Throwable {
+    String keepRule = "-keep class java.time.*";
+    List<String> keepRules = new ArrayList<>();
+    keepRules.add(keepRule);
+    L8Command.Builder builder =
+        prepareBuilder(new TestDiagnosticMessagesImpl())
+            .setProgramConsumer(DexIndexedConsumer.emptyConsumer())
+            .addDesugaredLibraryConfiguration(
+                StringResource.fromFile(ToolHelper.DESUGAR_LIB_JSON_FOR_TESTING))
+            .addProguardConfiguration(keepRules, Origin.unknown());
+    assertTrue(builder.isShrinking());
+    assertNotNull(builder.build().getR8Command());
+  }
+
+  @Test
+  @Ignore
+  public void addProguardConfigurationFile() throws Throwable {
+    String keepRule = "-keep class java.time.*";
+    Path keepRuleFile = temp.newFile("keepRuleFile.txt").toPath();
+    Files.write(keepRuleFile, Collections.singletonList(keepRule), StandardCharsets.UTF_8);
+
+    L8Command.Builder builder1 =
+        prepareBuilder(new TestDiagnosticMessagesImpl())
+            .setProgramConsumer(DexIndexedConsumer.emptyConsumer())
+            .addDesugaredLibraryConfiguration(
+                StringResource.fromFile(ToolHelper.DESUGAR_LIB_JSON_FOR_TESTING))
+            .addProguardConfigurationFiles(keepRuleFile);
+    assertTrue(builder1.isShrinking());
+    assertNotNull(builder1.build().getR8Command());
+
+    List<Path> keepRuleFiles = new ArrayList<>();
+    keepRuleFiles.add(keepRuleFile);
+    L8Command.Builder builder2 =
+        prepareBuilder(new TestDiagnosticMessagesImpl())
+            .setProgramConsumer(DexIndexedConsumer.emptyConsumer())
+            .addDesugaredLibraryConfiguration(
+                StringResource.fromFile(ToolHelper.DESUGAR_LIB_JSON_FOR_TESTING))
+            .addProguardConfigurationFiles(keepRuleFiles);
+    assertTrue(builder2.isShrinking());
+    assertNotNull(builder2.build().getR8Command());
+  }
 }
diff --git a/src/test/java/com/android/tools/r8/R8CompatTestBuilder.java b/src/test/java/com/android/tools/r8/R8CompatTestBuilder.java
index 43f1ffa..568aa74 100644
--- a/src/test/java/com/android/tools/r8/R8CompatTestBuilder.java
+++ b/src/test/java/com/android/tools/r8/R8CompatTestBuilder.java
@@ -20,13 +20,6 @@
     return new R8CompatTestBuilder(state, builder, backend);
   }
 
-  public R8CompatTestBuilder setProguardCompatibilityRulesOutput(
-      Path proguardCompatibilityRulesOutput) {
-    assert builder.proguardCompatibilityRulesOutput == null;
-    builder.proguardCompatibilityRulesOutput = proguardCompatibilityRulesOutput;
-    return self();
-  }
-
   @Override
   R8CompatTestBuilder self() {
     return this;
diff --git a/src/test/java/com/android/tools/r8/R8TestRunResult.java b/src/test/java/com/android/tools/r8/R8TestRunResult.java
index ecf247c..835c959 100644
--- a/src/test/java/com/android/tools/r8/R8TestRunResult.java
+++ b/src/test/java/com/android/tools/r8/R8TestRunResult.java
@@ -7,10 +7,14 @@
 import static org.junit.Assert.assertNotNull;
 
 import com.android.tools.r8.ToolHelper.ProcessResult;
+import com.android.tools.r8.retrace.Retrace;
+import com.android.tools.r8.retrace.RetraceCommand;
 import com.android.tools.r8.utils.AndroidApp;
+import com.android.tools.r8.utils.StringUtils;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.android.tools.r8.utils.graphinspector.GraphInspector;
 import java.io.IOException;
+import java.util.List;
 import java.util.concurrent.ExecutionException;
 import java.util.function.Consumer;
 
@@ -60,4 +64,18 @@
   public String proguardMap() {
     return proguardMap;
   }
+
+  public List<String> retrace() {
+    class Box {
+      List<String> result;
+    }
+    Box box = new Box();
+    Retrace.run(
+        RetraceCommand.builder()
+            .setProguardMapProducer(() -> proguardMap)
+            .setStackTrace(StringUtils.splitLines(getStdErr()))
+            .setRetracedStackTraceConsumer(retraced -> box.result = retraced)
+            .build());
+    return box.result;
+  }
 }
diff --git a/src/test/java/com/android/tools/r8/desugar/corelib/LintFilesTest.java b/src/test/java/com/android/tools/r8/desugar/corelib/LintFilesTest.java
index 87a6140..146707f 100644
--- a/src/test/java/com/android/tools/r8/desugar/corelib/LintFilesTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/corelib/LintFilesTest.java
@@ -21,6 +21,8 @@
 import com.android.tools.r8.utils.Reporter;
 import java.nio.file.Files;
 import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.Comparator;
 import java.util.List;
 import org.junit.Assume;
 import org.junit.Test;
@@ -30,19 +32,49 @@
   private void checkFileContent(AndroidApiLevel minApiLevel, Path lintFile) throws Exception {
     // Just do some light probing in the generated lint files.
     List<String> methods = FileUtils.readAllLines(lintFile);
-    assertTrue(methods.contains("java/util/List/spliterator()Ljava/util/Spliterator;"));
-    assertTrue(methods.contains("java/util/Optional/empty()Ljava/util/Optional;"));
-    assertTrue(methods.contains("java/util/OptionalInt/empty()Ljava/util/OptionalInt;"));
+
+    // All methods supported on Optional*.
+    assertTrue(methods.contains("java/util/Optional"));
+    assertTrue(methods.contains("java/util/OptionalInt"));
+
+    // No parallel* methods pre L, and all stream methods supported from L.
     assertEquals(
         minApiLevel == AndroidApiLevel.L,
-        methods.contains("java/util/Collection/parallelStream()Ljava/util/stream/Stream;"));
+        methods.contains("java/util/Collection#parallelStream()Ljava/util/stream/Stream;"));
     assertEquals(
-        minApiLevel == AndroidApiLevel.L,
+        minApiLevel == AndroidApiLevel.L, methods.contains("java/util/stream/DoubleStream"));
+    assertFalse(
         methods.contains(
-            "java/util/stream/DoubleStream/parallel()Ljava/util/stream/DoubleStream;"));
+            "java/util/stream/DoubleStream#parallel()Ljava/util/stream/DoubleStream;"));
+    assertFalse(
+        methods.contains("java/util/stream/DoubleStream#parallel()Ljava/util/stream/BaseStream;"));
     assertEquals(
-        minApiLevel == AndroidApiLevel.L,
-        methods.contains("java/util/stream/IntStream/parallel()Ljava/util/stream/IntStream;"));
+        minApiLevel == AndroidApiLevel.B,
+        methods.contains(
+            "java/util/stream/DoubleStream#allMatch(Ljava/util/function/DoublePredicate;)Z"));
+    assertEquals(minApiLevel == AndroidApiLevel.L, methods.contains("java/util/stream/IntStream"));
+    assertFalse(
+        methods.contains("java/util/stream/IntStream#parallel()Ljava/util/stream/IntStream;"));
+    assertFalse(
+        methods.contains("java/util/stream/IntStream#parallel()Ljava/util/stream/BaseStream;"));
+    assertEquals(
+        minApiLevel == AndroidApiLevel.B,
+        methods.contains(
+            "java/util/stream/IntStream#allMatch(Ljava/util/function/IntPredicate;)Z"));
+
+    // Emulated interface default method.
+    assertTrue(methods.contains("java/util/List#spliterator()Ljava/util/Spliterator;"));
+
+    // Emulated interface static method.
+    assertTrue(methods.contains("java/util/Map$Entry#comparingByValue()Ljava/util/Comparator;"));
+
+    // No no-default method from emulated interface.
+    assertFalse(methods.contains("java/util/List#size()I"));
+
+    // File should be sorted.
+    List<String> sorted = new ArrayList<>(methods);
+    sorted.sort(Comparator.naturalOrder());
+    assertEquals(methods, sorted);
   }
 
   @Test
diff --git a/src/test/java/com/android/tools/r8/ir/analysis/type/TypeLatticeTest.java b/src/test/java/com/android/tools/r8/ir/analysis/type/TypeLatticeTest.java
index 25988b8..42908d2 100644
--- a/src/test/java/com/android/tools/r8/ir/analysis/type/TypeLatticeTest.java
+++ b/src/test/java/com/android/tools/r8/ir/analysis/type/TypeLatticeTest.java
@@ -554,7 +554,9 @@
 
   @Test
   public void testNotNullOfNullGivesBottom() {
-    assertEquals(Nullability.bottom(), ReferenceTypeLatticeElement.NULL.asNotNull().nullability());
+    assertEquals(
+        Nullability.bottom(),
+        ReferenceTypeLatticeElement.NULL.asMeetWithNotNull().nullability());
   }
 
   @Test
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/KeptClassInliningTest.java b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/KeptClassInliningTest.java
new file mode 100644
index 0000000..c8d287b
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/KeptClassInliningTest.java
@@ -0,0 +1,73 @@
+// 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.classinliner;
+
+import static com.android.tools.r8.references.Reference.methodFromMethod;
+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.NeverInline;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class KeptClassInliningTest extends TestBase {
+
+  public static class KeptClass {
+
+    // Annotate with never-inline to avoid the method inliner from eliminating the call, which in
+    // turn allows removing the instantiation.
+    @NeverInline
+    public void used() {
+      System.out.println("used()");
+    }
+  }
+
+  public static class Main {
+
+    public static void main(String[] args) {
+      new KeptClass().used();
+    }
+  }
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public final TestParameters parameters;
+
+  public KeptClassInliningTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void test() throws Exception {
+    CodeInspector inspector =
+        testForR8(parameters.getBackend())
+            .enableInliningAnnotations()
+            .addProgramClasses(KeptClass.class, Main.class)
+            .addKeepMainRule(Main.class)
+            .addKeepClassRules(KeptClass.class)
+            .setMinApi(parameters.getApiLevel())
+            .run(parameters.getRuntime(), Main.class)
+            .assertSuccessWithOutputLines("used()")
+            .inspector();
+    assertThat(inspector.clazz(KeptClass.class), isPresent());
+    MethodSubject main =
+        inspector.method(methodFromMethod(Main.class.getMethod("main", String[].class)));
+    // Check the instantiation of the class remains in 'main'.
+    assertTrue(
+        main.streamInstructions()
+            .anyMatch(i -> i.isInvoke() && i.getMethod().name.toString().equals("<init>")));
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/inliner/InlineSynchronizedTest.java b/src/test/java/com/android/tools/r8/ir/optimize/inliner/InlineSynchronizedTest.java
index e3beb92..dcfe291 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/inliner/InlineSynchronizedTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/inliner/InlineSynchronizedTest.java
@@ -9,16 +9,14 @@
 import static org.junit.Assert.assertEquals;
 
 import com.android.tools.r8.TestBase;
-import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.graph.DexString;
-import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.android.tools.r8.utils.codeinspector.InstructionSubject;
 import com.android.tools.r8.utils.codeinspector.InvokeInstructionSubject;
 import com.android.tools.r8.utils.codeinspector.MethodSubject;
 import com.google.common.collect.ImmutableList;
-import java.util.Collection;
 import java.util.Iterator;
 import java.util.List;
 import org.junit.Test;
@@ -36,17 +34,17 @@
           "normalInlinedControl",
           "classInlinedControl");
 
-  @Parameterized.Parameters(name = "Backend: {0}, ClassInlining: {1}")
-  public static Collection data() {
-    return buildParameters(ToolHelper.getBackends(), BooleanUtils.values());
+  @Parameterized.Parameters(name = "{1}")
+  public static List<Object[]> data() {
+    return buildParameters(getTestParameters().withNoneRuntime().build(), Backend.values());
   }
 
   private final Backend backend;
-  private final boolean classInlining;
+  private final TestParameters parameters;
 
-  public InlineSynchronizedTest(Backend backend, boolean classInlining) {
+  public InlineSynchronizedTest(TestParameters parameters, Backend backend) {
+    this.parameters = parameters;
     this.backend = backend;
-    this.classInlining = classInlining;
   }
 
   @Test
@@ -55,7 +53,6 @@
         testForR8(backend)
             .addProgramClasses(InlineSynchronizedTestClass.class)
             .addKeepMainRule(InlineSynchronizedTestClass.class)
-            .addOptionsModification(o -> o.enableClassInlining = classInlining)
             .enableInliningAnnotations()
             .noMinification()
             .compile()
@@ -80,9 +77,9 @@
     // Synchronized methods can never be inlined.
     assertCount(counts, "normalInlinedSynchronized", 1);
     assertCount(counts, "classInlinedSynchronized", 1);
-    // Control methods must be inlined, only the normal one or both, depending on classInlining.
+    // Only the never-merge method is inlined, classInlining should run on the kept class.
     assertCount(counts, "normalInlinedControl", 0);
-    assertCount(counts, "classInlinedControl", classInlining ? 0 : 1);
+    assertCount(counts, "classInlinedControl", 1);
     // Double check the total.
     int total = 0;
     for (int i = 0; i < counts.length; ++i) {
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/inliner/InlinerShouldNotInlineDefinitelyNullTest.java b/src/test/java/com/android/tools/r8/ir/optimize/inliner/InlinerShouldNotInlineDefinitelyNullTest.java
new file mode 100644
index 0000000..654e0f2
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/inliner/InlinerShouldNotInlineDefinitelyNullTest.java
@@ -0,0 +1,90 @@
+// 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.inliner;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.core.StringContains.containsString;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.R8TestRunResult;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.codeinspector.InstructionSubject;
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.concurrent.ExecutionException;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+/** This is a small reproduction of b/140851070 */
+@RunWith(Parameterized.class)
+public class InlinerShouldNotInlineDefinitelyNullTest extends TestBase {
+
+  public static String EXPECTED_STACK_TRACE = StringUtils.joinLines(
+      "java.lang.NullPointerException", "  at " + Main.class.getTypeName() + ".main(");
+
+  public static class A {
+
+    void foo() {
+      System.out.println("This will never be called");
+    }
+  }
+
+  public static class Main {
+
+    public static void main(String[] args) {
+      A a = null;
+      a.foo(); // <-- will insert empty throwing code because a is definitely null.
+    }
+  }
+
+  private final TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public InlinerShouldNotInlineDefinitelyNullTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void ensureThrowNullInliningsHaveInlinePositions()
+      throws CompilationFailedException, IOException, ExecutionException {
+    R8TestRunResult result =
+        testForR8(parameters.getBackend())
+            .addProgramClasses(Main.class, A.class)
+            .addKeepMainRule(Main.class)
+            .addKeepAllAttributes()
+            .addKeepClassRules(A.class)
+            .setMinApi(parameters.getApiLevel())
+            .compile()
+            .inspect(
+                inspector -> {
+                  // Do a manual check that the invoke to foo is replaced by throw.
+                  assertTrue(
+                      inspector
+                          .clazz(Main.class)
+                          .uniqueMethodWithName("main")
+                          .streamInstructions()
+                          .anyMatch(InstructionSubject::isThrow));
+                })
+            .run(parameters.getRuntime(), Main.class)
+            .assertFailure();
+    String[] split = result.proguardMap().split("\n");
+    assertTrue(Arrays.stream(split).noneMatch(l -> l.contains(A.class.getTypeName() + ".foo")));
+    assertThat(
+        StringUtils.joinLines(result.retrace())
+            .replace("\tat", "  at")
+            .replace(": throw with null exception", ""),
+        containsString(EXPECTED_STACK_TRACE));
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/nonnull/B141654799.java b/src/test/java/com/android/tools/r8/ir/optimize/nonnull/B141654799.java
new file mode 100644
index 0000000..de6f2e7
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/nonnull/B141654799.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.nonnull;
+
+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.NeverInline;
+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.MethodSubject;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class B141654799 extends TestBase {
+  private final TestParameters parameters;
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimes().build();
+  }
+
+  public B141654799(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void test() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(B141654799.class)
+        .enableInliningAnnotations()
+        .addKeepMainRule(TestClass.class)
+        .setMinApi(parameters.getRuntime())
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutputLines("The end")
+        .inspect(inspector -> {
+          ClassSubject main = inspector.clazz(TestClass.class);
+          assertThat(main, isPresent());
+          MethodSubject mainMethod = main.mainMethod();
+          assertThat(mainMethod, isPresent());
+          assertTrue(mainMethod.streamInstructions().noneMatch(i -> i.isIfEqz() || i.isIfNez()));
+        });
+  }
+
+  static class TestClass {
+    public static void main(String... args) {
+      TestClass x = null;
+      if (System.currentTimeMillis() > 0) {
+        TestClass y = (TestClass) returnsNull();
+        if (y != null) {
+          x = y;
+        }
+      }
+      if (x != null) {
+        System.out.println("Dead code: " + x.toString());
+      }
+      System.out.println("The end");
+    }
+
+    @NeverInline
+    static Object returnsNull() {
+      return null;
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/rewrite/ServiceLoaderRewritingTest.java b/src/test/java/com/android/tools/r8/rewrite/ServiceLoaderRewritingTest.java
index 91411e5..14af67a 100644
--- a/src/test/java/com/android/tools/r8/rewrite/ServiceLoaderRewritingTest.java
+++ b/src/test/java/com/android/tools/r8/rewrite/ServiceLoaderRewritingTest.java
@@ -22,12 +22,12 @@
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.android.tools.r8.utils.codeinspector.InstructionSubject;
-import com.android.tools.r8.utils.codeinspector.MethodSubject;
 import java.io.IOException;
 import java.nio.file.Path;
 import java.util.ServiceLoader;
 import java.util.concurrent.ExecutionException;
 import java.util.zip.ZipFile;
+import org.junit.Ignore;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
@@ -52,6 +52,14 @@
     }
   }
 
+  public static class ServiceImpl2 implements Service {
+
+    @Override
+    public void print() {
+      System.out.println("Hello World 2!");
+    }
+  }
+
   public static class MainRunner {
 
     public static void main(String[] args) {
@@ -67,6 +75,18 @@
     }
   }
 
+  public static class MainWithTryCatchRunner {
+
+    public static void main(String[] args) {
+      try {
+        ServiceLoader.load(Service.class, Service.class.getClassLoader()).iterator().next().print();
+      } catch (Throwable e) {
+        System.out.println(e);
+        throw e;
+      }
+    }
+  }
+
   public static class OtherRunner {
 
     public static void main(String[] args) {
@@ -144,13 +164,66 @@
         .inspect(
             inspector -> {
               // Check that we have actually rewritten the calls to ServiceLoader.load.
-              ClassSubject clazz = inspector.clazz(MainRunner.class);
-              assertTrue(clazz.isPresent());
-              MethodSubject main = clazz.uniqueMethodWithName("main");
-              assertTrue(main.isPresent());
-              assertTrue(
-                  main.streamInstructions()
-                      .noneMatch(ServiceLoaderRewritingTest::isServiceLoaderLoad));
+              assertEquals(0, getServiceLoaderLoads(inspector, MainRunner.class));
+            });
+
+    // Check that we have removed the service configuration from META-INF/services.
+    ZipFile zip = new ZipFile(path.toFile());
+    assertNull(zip.getEntry("META-INF/services"));
+  }
+
+  @Test
+  public void testRewritingWithMultiple()
+      throws IOException, CompilationFailedException, ExecutionException {
+    Path path = temp.newFile("out.zip").toPath();
+    testForR8(parameters.getBackend())
+        .addInnerClasses(ServiceLoaderRewritingTest.class)
+        .addKeepMainRule(MainRunner.class)
+        .setMinApi(parameters.getRuntime())
+        .addDataEntryResources(
+            DataEntryResource.fromBytes(
+                StringUtils.lines(ServiceImpl.class.getTypeName(), ServiceImpl2.class.getTypeName())
+                    .getBytes(),
+                "META-INF/services/" + Service.class.getTypeName(),
+                Origin.unknown()))
+        .compile()
+        .writeToZip(path)
+        .run(parameters.getRuntime(), MainRunner.class)
+        .assertSuccessWithOutput(EXPECTED_OUTPUT + StringUtils.lines("Hello World 2!"))
+        .inspect(
+            inspector -> {
+              // Check that we have actually rewritten the calls to ServiceLoader.load.
+              assertEquals(0, getServiceLoaderLoads(inspector, MainRunner.class));
+            });
+
+    // Check that we have removed the service configuration from META-INF/services.
+    ZipFile zip = new ZipFile(path.toFile());
+    assertNull(zip.getEntry("META-INF/services"));
+  }
+
+  @Test
+  @Ignore("b/141290856")
+  public void testRewritingsWithCatchHandlers()
+      throws IOException, CompilationFailedException, ExecutionException {
+    Path path = temp.newFile("out.zip").toPath();
+    testForR8(parameters.getBackend())
+        .addInnerClasses(ServiceLoaderRewritingTest.class)
+        .addKeepMainRule(MainWithTryCatchRunner.class)
+        .setMinApi(parameters.getRuntime())
+        .addDataEntryResources(
+            DataEntryResource.fromBytes(
+                StringUtils.lines(ServiceImpl.class.getTypeName(), ServiceImpl2.class.getTypeName())
+                    .getBytes(),
+                "META-INF/services/" + Service.class.getTypeName(),
+                Origin.unknown()))
+        .compile()
+        .writeToZip(path)
+        .run(parameters.getRuntime(), MainRunner.class)
+        .assertSuccessWithOutput(EXPECTED_OUTPUT + StringUtils.lines("Hello World 2!"))
+        .inspect(
+            inspector -> {
+              // Check that we have actually rewritten the calls to ServiceLoader.load.
+              assertEquals(0, getServiceLoaderLoads(inspector, MainRunner.class));
             });
 
     // Check that we have removed the service configuration from META-INF/services.
@@ -178,13 +251,7 @@
             .inspector();
 
     // Check that we have not rewritten the calls to ServiceLoader.load.
-    ClassSubject clazz = inspector.clazz(OtherRunner.class);
-    assertTrue(clazz.isPresent());
-    MethodSubject main = clazz.uniqueMethodWithName("main");
-    assertTrue(main.isPresent());
-    assertEquals(
-        3,
-        main.streamInstructions().filter(ServiceLoaderRewritingTest::isServiceLoaderLoad).count());
+    assertEquals(3, getServiceLoaderLoads(inspector, OtherRunner.class));
 
     // Check that we have not removed the service configuration from META-INF/services.
     ZipFile zip = new ZipFile(path.toFile());
@@ -216,19 +283,8 @@
             .inspector();
 
     // Check that we have not rewritten the calls to ServiceLoader.load.
-    ClassSubject clazz = inspector.clazz(EscapingRunner.class);
-    assertTrue(clazz.isPresent());
-    int allServiceLoaders =
-        clazz.allMethods().stream()
-            .mapToInt(
-                method ->
-                    (int)
-                        method
-                            .streamInstructions()
-                            .filter(ServiceLoaderRewritingTest::isServiceLoaderLoad)
-                            .count())
-            .sum();
-    assertEquals(3, allServiceLoaders);
+    assertEquals(3, getServiceLoaderLoads(inspector, EscapingRunner.class));
+
     // Check that we have not removed the service configuration from META-INF/services.
     ZipFile zip = new ZipFile(path.toFile());
     ClassSubject serviceImpl = inspector.clazz(ServiceImpl.class);
@@ -263,13 +319,7 @@
             .inspector();
 
     // Check that we have not rewritten the calls to ServiceLoader.load.
-    ClassSubject clazz = inspector.clazz(MainRunner.class);
-    assertTrue(clazz.isPresent());
-    MethodSubject main = clazz.uniqueMethodWithName("main");
-    assertTrue(main.isPresent());
-    assertEquals(
-        3,
-        main.streamInstructions().filter(ServiceLoaderRewritingTest::isServiceLoaderLoad).count());
+    assertEquals(3, getServiceLoaderLoads(inspector, MainRunner.class));
 
     // Check that we have not removed the service configuration from META-INF/services.
     ZipFile zip = new ZipFile(path.toFile());
@@ -278,6 +328,19 @@
     assertNotNull(zip.getEntry("META-INF/services/" + service.getFinalName()));
   }
 
+  private static long getServiceLoaderLoads(CodeInspector inspector, Class<?> clazz) {
+    ClassSubject classSubject = inspector.clazz(clazz);
+    assertTrue(classSubject.isPresent());
+    return classSubject.allMethods().stream()
+        .mapToLong(
+            method ->
+                method
+                    .streamInstructions()
+                    .filter(ServiceLoaderRewritingTest::isServiceLoaderLoad)
+                    .count())
+        .sum();
+  }
+
   private static boolean isServiceLoaderLoad(InstructionSubject instruction) {
     return instruction.isInvokeStatic()
         && instruction.getMethod().qualifiedName().contains("ServiceLoader.load");
diff --git a/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking18Test.java b/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking18Test.java
index fa76296..f28f242 100644
--- a/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking18Test.java
+++ b/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking18Test.java
@@ -3,13 +3,14 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.shaking.examples;
 
+import static org.junit.Assert.assertFalse;
+
 import com.android.tools.r8.shaking.TreeShakingTest;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.google.common.collect.ImmutableList;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.List;
-import org.junit.Assert;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
@@ -43,8 +44,7 @@
   }
 
   private static void unusedRemoved(CodeInspector inspector) {
-    // TODO(b/80455722): Change to assertFalse when tree-shaking detects this case.
-    Assert.assertTrue(
+    assertFalse(
         "DerivedUnused should be removed", inspector.clazz("shaking18.DerivedUnused").isPresent());
   }
 }
diff --git a/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/ForceProguardCompatibilityTest.java b/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/ForceProguardCompatibilityTest.java
index 6773501..2ac7880 100644
--- a/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/ForceProguardCompatibilityTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/ForceProguardCompatibilityTest.java
@@ -4,6 +4,8 @@
 
 package com.android.tools.r8.shaking.forceproguardcompatibility;
 
+import static com.android.tools.r8.references.Reference.classFromClass;
+import static com.android.tools.r8.references.Reference.methodFromMethod;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.junit.Assert.assertEquals;
@@ -15,18 +17,11 @@
 import com.android.tools.r8.R8Command;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.ToolHelper;
-import com.android.tools.r8.graph.DexItemFactory;
-import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.invokesuper.Consumer;
 import com.android.tools.r8.naming.MemberNaming.MethodSignature;
 import com.android.tools.r8.origin.Origin;
-import com.android.tools.r8.shaking.ProguardClassNameList;
-import com.android.tools.r8.shaking.ProguardConfiguration;
-import com.android.tools.r8.shaking.ProguardConfigurationParser;
-import com.android.tools.r8.shaking.ProguardConfigurationRule;
+import com.android.tools.r8.shaking.CollectingGraphConsumer;
 import com.android.tools.r8.shaking.ProguardKeepAttributes;
-import com.android.tools.r8.shaking.ProguardMemberRule;
-import com.android.tools.r8.shaking.ProguardMemberType;
 import com.android.tools.r8.shaking.forceproguardcompatibility.defaultmethods.ClassImplementingInterface;
 import com.android.tools.r8.shaking.forceproguardcompatibility.defaultmethods.InterfaceWithDefaultMethods;
 import com.android.tools.r8.shaking.forceproguardcompatibility.defaultmethods.TestClass;
@@ -34,11 +29,12 @@
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.FileUtils;
-import com.android.tools.r8.utils.Reporter;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.android.tools.r8.utils.codeinspector.FieldSubject;
 import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import com.android.tools.r8.utils.graphinspector.GraphInspector;
+import com.android.tools.r8.utils.graphinspector.GraphInspector.QueryNode;
 import com.google.common.collect.ImmutableList;
 import java.io.File;
 import java.nio.file.Path;
@@ -145,31 +141,22 @@
         "  public void method();",
         "}");
     builder.addProguardConfiguration(proguardConfig, Origin.unknown());
-    Path proguardCompatibilityRules = temp.newFile().toPath();
-    builder.setProguardCompatibilityRulesOutput(proguardCompatibilityRules);
 
     builder.setProgramConsumer(emptyConsumer(backend)).addLibraryFiles(runtimeJar(backend));
-    CodeInspector inspector = new CodeInspector(ToolHelper.runR8(builder.build()));
-    ClassSubject clazz = inspector.clazz(getJavacGeneratedClassName(testClass));
-    assertTrue(clazz.isPresent());
-    assertEquals(forceProguardCompatibility && hasDefaultConstructor, clazz.init().isPresent());
 
-    // Check the Proguard compatibility rules generated.
-    ProguardConfigurationParser parser =
-        new ProguardConfigurationParser(new DexItemFactory(), new Reporter());
-    parser.parse(proguardCompatibilityRules);
-    ProguardConfiguration configuration = parser.getConfigRawForTesting();
-    if (forceProguardCompatibility && hasDefaultConstructor) {
-      assertEquals(1, configuration.getRules().size());
-      ProguardClassNameList classNames = configuration.getRules().get(0).getClassNames();
-      assertEquals(1, classNames.size());
-      assertEquals(testClass.getCanonicalName(),
-          classNames.asSpecificDexTypes().get(0).toSourceString());
-      List<ProguardMemberRule> memberRules = configuration.getRules().get(0).getMemberRules();
-      assertEquals(1, memberRules.size());
-      assertEquals(ProguardMemberType.INIT, memberRules.iterator().next().getRuleType());
-    } else {
-      assertEquals(0, configuration.getRules().size());
+    CollectingGraphConsumer graphConsumer = new CollectingGraphConsumer(null);
+    builder.setKeptGraphConsumer(graphConsumer);
+
+    GraphInspector inspector =
+        new GraphInspector(graphConsumer, new CodeInspector(ToolHelper.runR8(builder.build())));
+    QueryNode clazzNode = inspector.clazz(classFromClass(testClass)).assertPresent();
+    if (hasDefaultConstructor) {
+      QueryNode initNode = inspector.method(methodFromMethod(testClass.getConstructor()));
+      if (forceProguardCompatibility) {
+        initNode.assertPureCompatKeptBy(clazzNode);
+      } else {
+        initNode.assertAbsent();
+      }
     }
 
     if (isRunProguard()) {
@@ -259,8 +246,6 @@
     }
     List<String> proguardConfig = proguardConfigurationBuilder.build();
     builder.addProguardConfiguration(proguardConfig, Origin.unknown());
-    Path proguardCompatibilityRules = temp.newFile().toPath();
-    builder.setProguardCompatibilityRulesOutput(proguardCompatibilityRules);
     if (allowObfuscation) {
       builder.setProguardMapOutputPath(temp.newFile().toPath());
     }
@@ -319,8 +304,6 @@
     }
     List<String> proguardConfig = proguardConfigurationBuilder.build();
     builder.addProguardConfiguration(proguardConfig, Origin.unknown());
-    Path proguardCompatibilityRules = temp.newFile().toPath();
-    builder.setProguardCompatibilityRulesOutput(proguardCompatibilityRules);
     if (allowObfuscation) {
       builder.setProguardMapOutputPath(temp.newFile().toPath());
     }
@@ -390,8 +373,6 @@
     }
     List<String> proguardConfig = proguardConfigurationBuilder.build();
     builder.addProguardConfiguration(proguardConfig, Origin.unknown());
-    Path proguardCompatibilityRules = temp.newFile().toPath();
-    builder.setProguardCompatibilityRulesOutput(proguardCompatibilityRules);
     if (allowObfuscation) {
       builder.setProguardMapOutputPath(temp.newFile().toPath());
     }
@@ -461,7 +442,6 @@
       }
       keepRules = "-keepattributes " + String.join(",", attributes);
     }
-    Path proguardCompatibilityRules = temp.newFile().toPath();
     CodeInspector inspector;
 
     try {
@@ -477,7 +457,6 @@
                   keepRules)
               .addOptionsModification(options -> options.enableClassInlining = false)
               .enableSideEffectAnnotations()
-              .setProguardCompatibilityRulesOutput(proguardCompatibilityRules)
               .compile()
               .run(TestKeepAttributes.class)
               .assertSuccessWithOutput(innerClasses || enclosingMethod ? "1" : "0")
@@ -487,20 +466,12 @@
       return;
     }
 
-    assertThat(inspector.clazz(getJavacGeneratedClassName(TestKeepAttributes.class)), isPresent());
-
-    // Check the Proguard compatibility configuration generated.
-    ProguardConfigurationParser parser =
-        new ProguardConfigurationParser(new DexItemFactory(), new Reporter());
-    parser.parse(proguardCompatibilityRules);
-    ProguardConfiguration configuration = parser.getConfigRawForTesting();
-    assertTrue(configuration.getRules().isEmpty());
-    if (innerClasses ^ enclosingMethod) {
-      assertTrue(configuration.getKeepAttributes().innerClasses);
-      assertTrue(configuration.getKeepAttributes().enclosingMethod);
+    ClassSubject clazz = inspector.clazz(TestKeepAttributes.class);
+    assertThat(clazz, isPresent());
+    if (innerClasses || enclosingMethod) {
+      assertFalse(clazz.getDexClass().getInnerClasses().isEmpty());
     } else {
-      assertFalse(configuration.getKeepAttributes().innerClasses);
-      assertFalse(configuration.getKeepAttributes().enclosingMethod);
+      assertTrue(clazz.getDexClass().getInnerClasses().isEmpty());
     }
   }
 
@@ -519,7 +490,8 @@
   private void runKeepDefaultMethodsTest(
       List<String> additionalKeepRules,
       Consumer<CodeInspector> inspection,
-      Consumer<ProguardConfiguration> compatInspection) throws Exception {
+      Consumer<GraphInspector> compatInspection)
+      throws Exception {
     Class mainClass = TestClass.class;
     CompatProguardCommandBuilder builder = new CompatProguardCommandBuilder();
     builder.addProgramFiles(ToolHelper.getClassFilesForTestPackage(mainClass.getPackage()));
@@ -532,11 +504,11 @@
         Origin.unknown());
     builder.addProguardConfiguration(additionalKeepRules, Origin.unknown());
     builder.setProgramConsumer(emptyConsumer(backend)).addLibraryFiles(runtimeJar(backend));
+    CollectingGraphConsumer graphConsumer = new CollectingGraphConsumer(null);
+    builder.setKeptGraphConsumer(graphConsumer);
     if (backend == Backend.DEX) {
       builder.setMinApiLevel(AndroidApiLevel.O.getLevel());
     }
-    Path proguardCompatibilityRules = temp.newFile().toPath();
-    builder.setProguardCompatibilityRulesOutput(proguardCompatibilityRules);
     AndroidApp app =
         ToolHelper.runR8(
             builder.build(),
@@ -547,17 +519,15 @@
               // ClassImplementingInterface.
               o.enableVerticalClassMerging = false;
             });
-    inspection.accept(new CodeInspector(app));
-    // Check the Proguard compatibility configuration generated.
-    ProguardConfigurationParser parser =
-        new ProguardConfigurationParser(new DexItemFactory(), new Reporter());
-    parser.parse(proguardCompatibilityRules);
-    ProguardConfiguration configuration = parser.getConfigRawForTesting();
-    compatInspection.accept(configuration);
+
+    CodeInspector inspector = new CodeInspector(app);
+    GraphInspector graphInspector = new GraphInspector(graphConsumer, inspector);
+    inspection.accept(inspector);
+    compatInspection.accept(graphInspector);
   }
 
-  private void noCompatibilityRules(ProguardConfiguration configuration) {
-    assertEquals(0, configuration.getRules().size());
+  private void noCompatibilityRules(GraphInspector inspector) {
+    inspector.assertNoPureCompatibilityEdges();
   }
 
   private void defaultMethodKept(CodeInspector inspector) {
@@ -568,20 +538,8 @@
     assertFalse(method.isAbstract());
   }
 
-  private void defaultMethodCompatibilityRules(ProguardConfiguration configuration) {
-    assertEquals(1, configuration.getRules().size());
-    ProguardConfigurationRule rule = configuration.getRules().get(0);
-    List<ProguardMemberRule> memberRules = rule.getMemberRules();
-    ProguardClassNameList classNames = rule.getClassNames();
-    assertEquals(1, classNames.size());
-    DexType type = classNames.asSpecificDexTypes().get(0);
-    assertEquals(type.toSourceString(), InterfaceWithDefaultMethods.class.getCanonicalName());
-    assertEquals(1, memberRules.size());
-    ProguardMemberRule memberRule = memberRules.iterator().next();
-    assertEquals(ProguardMemberType.METHOD, memberRule.getRuleType());
-    assertTrue(memberRule.getName().matches("method"));
-    assertTrue(memberRule.getType().matches(configuration.getDexItemFactory().intType));
-    assertEquals(0, memberRule.getArguments().size());
+  private void defaultMethodCompatibilityRules(GraphInspector inspector) {
+    // The enqueuer does not add an edge for the referenced => kept edges so we cant check compat.
   }
 
   private void defaultMethod2Kept(CodeInspector inspector) {
@@ -593,23 +551,8 @@
     assertFalse(method.isAbstract());
   }
 
-  private void defaultMethod2CompatibilityRules(ProguardConfiguration configuration) {
-    assertEquals(1, configuration.getRules().size());
-    ProguardConfigurationRule rule = configuration.getRules().get(0);
-    List<ProguardMemberRule> memberRules = rule.getMemberRules();
-    ProguardClassNameList classNames = rule.getClassNames();
-    assertEquals(1, classNames.size());
-    DexType type = classNames.asSpecificDexTypes().get(0);
-    assertEquals(type.toSourceString(), InterfaceWithDefaultMethods.class.getCanonicalName());
-    assertEquals(1, memberRules.size());
-    ProguardMemberRule memberRule = memberRules.iterator().next();
-    assertEquals(ProguardMemberType.METHOD, memberRule.getRuleType());
-    assertTrue(memberRule.getName().matches("method2"));
-    assertTrue(memberRule.getType().matches(configuration.getDexItemFactory().voidType));
-    assertEquals(2, memberRule.getArguments().size());
-    assertTrue(
-        memberRule.getArguments().get(0).matches(configuration.getDexItemFactory().stringType));
-    assertTrue(memberRule.getArguments().get(1).matches(configuration.getDexItemFactory().intType));
+  private void defaultMethod2CompatibilityRules(GraphInspector inspector) {
+    // The enqueuer does not add an edge for the referenced => kept edges so we cant check compat.
   }
 
   @Test
diff --git a/src/test/java/com/android/tools/r8/utils/ListUtilsTest.java b/src/test/java/com/android/tools/r8/utils/ListUtilsTest.java
new file mode 100644
index 0000000..3a63555
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/utils/ListUtilsTest.java
@@ -0,0 +1,50 @@
+// 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.utils;
+
+import static org.junit.Assert.assertEquals;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.function.Predicate;
+import org.junit.Test;
+
+public class ListUtilsTest {
+
+  private List<Integer> createInputData(int size) {
+    List<Integer> input = new ArrayList<>(size);
+    for (int i = 0; i < size; i++) {
+      input.add(i);
+    }
+    return input;
+  }
+
+  @Test
+  public void lastIndexOf_outOfRange() {
+    List<Integer> input = createInputData(3);
+    Predicate<Integer> tester = x -> x * x == -1;
+    assertEquals(-1, ListUtils.lastIndexMatching(input, tester));
+  }
+
+  @Test
+  public void lastIndexOf_first() {
+    List<Integer> input = createInputData(3);
+    Predicate<Integer> tester = x -> x * x == 0;
+    assertEquals(0, ListUtils.lastIndexMatching(input, tester));
+  }
+
+  @Test
+  public void lastIndexOf_middle() {
+    List<Integer> input = createInputData(4);
+    Predicate<Integer> tester = x -> x * x == 4;
+    assertEquals(2, ListUtils.lastIndexMatching(input, tester));
+  }
+
+  @Test
+  public void lastIndexOf_last() {
+    List<Integer> input = createInputData(2);
+    Predicate<Integer> tester = x -> x * x == 1;
+    assertEquals(1, ListUtils.lastIndexMatching(input, tester));
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/utils/graphinspector/GraphInspector.java b/src/test/java/com/android/tools/r8/utils/graphinspector/GraphInspector.java
index cbd91f1..59376e6 100644
--- a/src/test/java/com/android/tools/r8/utils/graphinspector/GraphInspector.java
+++ b/src/test/java/com/android/tools/r8/utils/graphinspector/GraphInspector.java
@@ -54,6 +54,8 @@
         new EdgeKindPredicate(EdgeKind.IsLibraryMethod);
     public static final EdgeKindPredicate overriding =
         new EdgeKindPredicate(EdgeKind.OverridingMethod);
+    public static final EdgeKindPredicate compatibilityRule =
+        new EdgeKindPredicate(EdgeKind.CompatibilityRule);
 
     private final EdgeKind edgeKind;
 
@@ -141,6 +143,10 @@
 
     public abstract boolean isKeptBy(QueryNode node);
 
+    public abstract boolean isCompatKeptBy(QueryNode node);
+
+    public abstract boolean isPureCompatKeptBy(QueryNode node);
+
     public abstract boolean isKeptByLibraryMethod(QueryNode node);
 
     public abstract boolean isSatisfiedBy(QueryNode... nodes);
@@ -234,6 +240,35 @@
       return this;
     }
 
+    public QueryNode assertCompatKeptBy(QueryNode node) {
+      assertTrue(
+          "Invalid call to assertCompatKeptBy with: " + node.getNodeDescription(),
+          node.isPresent());
+      assertTrue(
+          errorMessage("compat kept by " + node.getNodeDescription(), "was not kept by it"),
+          isCompatKeptBy(node));
+      return this;
+    }
+
+    public QueryNode assertNotCompatKeptBy(QueryNode node) {
+      assertTrue(
+          "Invalid call to assertNotKeptBy with: " + node.getNodeDescription(), node.isPresent());
+      assertFalse(
+          errorMessage("not kept by " + node.getNodeDescription(), "was kept by it"),
+          isCompatKeptBy(node));
+      return this;
+    }
+
+    public QueryNode assertPureCompatKeptBy(QueryNode node) {
+      assertTrue(
+          "Invalid call to assertPureCompatKeptBy with: " + node.getNodeDescription(),
+          node.isPresent());
+      assertTrue(
+          errorMessage("compat kept by " + node.getNodeDescription(), "was not kept by it"),
+          isPureCompatKeptBy(node));
+      return this;
+    }
+
     public QueryNode assertSatisfiedBy(QueryNode... nodes) {
       if (isSatisfiedBy(nodes)) {
         return this;
@@ -328,6 +363,18 @@
     }
 
     @Override
+    public boolean isCompatKeptBy(QueryNode node) {
+      fail("Invalid call to isCompatKeptBy on " + getNodeDescription());
+      throw new Unreachable();
+    }
+
+    @Override
+    public boolean isPureCompatKeptBy(QueryNode node) {
+      fail("Invalid call to isPureCompatKeptBy on " + getNodeDescription());
+      throw new Unreachable();
+    }
+
+    @Override
     public boolean isKeptByLibraryMethod(QueryNode node) {
       fail("Invalid call to isKeptByLibrary on " + getNodeDescription());
       throw new Unreachable();
@@ -441,6 +488,28 @@
     }
 
     @Override
+    public boolean isCompatKeptBy(QueryNode node) {
+      if (!(node instanceof QueryNodeImpl)) {
+        return false;
+      }
+      QueryNodeImpl impl = (QueryNodeImpl) node;
+      return filterSources(
+              (source, infos) ->
+                  impl.graphNode == source && EdgeKindPredicate.compatibilityRule.test(infos))
+          .findFirst()
+          .isPresent();
+    }
+
+    @Override
+    public boolean isPureCompatKeptBy(QueryNode node) {
+      if (!isCompatKeptBy(node)) {
+        return false;
+      }
+      QueryNodeImpl impl = (QueryNodeImpl) node;
+      return filterSources((source, infos) -> impl.graphNode != source).count() == 0;
+    }
+
+    @Override
     public boolean isKeptByLibraryMethod(QueryNode node) {
       assert graphNode instanceof MethodGraphNode;
       if (!(node instanceof QueryNodeImpl)) {
@@ -664,4 +733,25 @@
   private QueryNode getQueryNode(GraphNode node, String absentString) {
     return node == null ? new AbsentQueryNode(absentString) : new QueryNodeImpl(this, node);
   }
+
+  private boolean isPureCompatTarget(GraphNode target) {
+    Map<GraphNode, Set<GraphEdgeInfo>> sources = consumer.getSourcesTargeting(target);
+    if (sources == null || sources.isEmpty()) {
+      return false;
+    }
+    for (Entry<GraphNode, Set<GraphEdgeInfo>> edge : sources.entrySet()) {
+      for (GraphEdgeInfo edgeInfo : edge.getValue()) {
+        if (edgeInfo.edgeKind() != EdgeKind.CompatibilityRule) {
+          return false;
+        }
+      }
+    }
+    return true;
+  }
+
+  public void assertNoPureCompatibilityEdges() {
+    for (GraphNode target : consumer.getTargets()) {
+      assertFalse(isPureCompatTarget(target));
+    }
+  }
 }