Merge commit '4732fa63e34129422d08843061e8597b19c691cd' into dev-release
diff --git a/foo.txt b/foo.txt
new file mode 100644
index 0000000..d896812
--- /dev/null
+++ b/foo.txt
@@ -0,0 +1,25 @@
+Caused by: java.lang.RuntimeException: java.util.concurrent.ExecutionException: java.lang.AssertionError
+	at com.android.tools.r8.dex.a.a(SourceFile:298)
+	at com.android.tools.r8.dex.a.a(SourceFile:227)
+	at com.android.tools.r8.dex.a.a(SourceFile:217)
+	at com.android.tools.r8.dex.a.a(SourceFile:214)
+	at com.android.tools.r8.ResourceShrinker.run(ResourceShrinker.java:5)
+	at com.android.builder.dexing.R8ResourceShrinker.runResourceShrinkerAnalysis(r8ResourceShrinker.kt:33)
+	at com.android.build.gradle.tasks.ResourceUsageAnalyzer.recordClassUsages(ResourceUsageAnalyzer.java:1493)
+	at com.android.build.gradle.tasks.ResourceUsageAnalyzer.recordClassUsages(ResourceUsageAnalyzer.java:1412)
+	at com.android.build.gradle.tasks.ResourceUsageAnalyzer.recordClassUsages(ResourceUsageAnalyzer.java:1406)
+	at com.android.build.gradle.tasks.ResourceUsageAnalyzer.analyze(ResourceUsageAnalyzer.java:288)
+	at com.android.build.gradle.internal.transforms.ShrinkResourcesTransform.splitAction(ShrinkResourcesTransform.java:317)
+	at com.android.build.gradle.internal.transforms.ShrinkResourcesTransform.lambda$transform$0(ShrinkResourcesTransform.java:248)
+	at com.android.build.gradle.internal.scope.BuildElements$ExecutorBasedScheduler$transform$$inlined$forEach$lambda$1.call(BuildElements.kt:121)
+	at com.android.build.gradle.internal.scope.BuildElements$ExecutorBasedScheduler$transform$$inlined$forEach$lambda$1.call(BuildElements.kt:110)
+Caused by: java.util.concurrent.ExecutionException: java.lang.AssertionError
+	at com.android.tools.r8.utils.Y0.a(SourceFile:50)
+	at com.android.tools.r8.dex.a.a(SourceFile:278)
+	... 13 more
+Caused by: java.lang.AssertionError
+	at com.android.tools.r8.graph.W$b.a(SourceFile:3)
+	at com.android.tools.r8.graph.W.o0(SourceFile:6)
+	at com.android.tools.r8.dex.n.a(SourceFile:707)
+	at com.android.tools.r8.dex.n.a(SourceFile:989)
+	at com.android.tools.r8.dex.a$a.a(SourceFile:90)
diff --git a/src/main/java/com/android/tools/r8/CompilationFailedException.java b/src/main/java/com/android/tools/r8/CompilationFailedException.java
index dc9fa64..0078c06 100644
--- a/src/main/java/com/android/tools/r8/CompilationFailedException.java
+++ b/src/main/java/com/android/tools/r8/CompilationFailedException.java
@@ -15,7 +15,11 @@
   }
 
   public CompilationFailedException(Throwable cause) {
-    super("Compilation failed to complete", cause);
+    this("Compilation failed to complete", cause);
+  }
+
+  public CompilationFailedException(String message, Throwable cause) {
+    super(message, cause);
   }
 
   public CompilationFailedException(String message) {
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java
index 382dd08..d3a291f 100644
--- a/src/main/java/com/android/tools/r8/R8.java
+++ b/src/main/java/com/android/tools/r8/R8.java
@@ -47,9 +47,11 @@
 import com.android.tools.r8.ir.optimize.UnusedArgumentsCollector;
 import com.android.tools.r8.ir.optimize.UnusedArgumentsCollector.UnusedArgumentsGraphLense;
 import com.android.tools.r8.ir.optimize.enums.EnumUnboxingCfMethods;
+import com.android.tools.r8.ir.optimize.enums.EnumUnboxingRewriter;
 import com.android.tools.r8.ir.optimize.enums.EnumValueInfoMapCollector;
 import com.android.tools.r8.ir.optimize.info.OptimizationFeedbackSimple;
 import com.android.tools.r8.jar.CfApplicationWriter;
+import com.android.tools.r8.kotlin.KotlinMetadataUtils;
 import com.android.tools.r8.naming.ClassNameMapper;
 import com.android.tools.r8.naming.Minifier;
 import com.android.tools.r8.naming.NamingLens;
@@ -274,6 +276,10 @@
           AppView.createForR8(new AppInfoWithClassHierarchy(application), options);
       appView.setAppServices(AppServices.builder(appView).build());
 
+      // Check for potentially having pass-through of Cf-code for kotlin libraries.
+      options.enableCfByteCodePassThrough =
+          options.isGeneratingClassFiles() && KotlinMetadataUtils.mayProcessKotlinMetadata(appView);
+
       // Up-front check for valid library setup.
       if (!options.mainDexKeepRules.isEmpty()) {
         MainDexListBuilder.checkForAssumedLibraryTypes(appView.appInfo());
@@ -284,7 +290,13 @@
       InterfaceMethodRewriter.checkForAssumedLibraryTypes(appView.appInfo(), options);
       BackportedMethodRewriter.registerAssumedLibraryTypes(options);
       if (options.enableEnumUnboxing) {
-        EnumUnboxingCfMethods.registerSynthesizedCodeReferences(options.itemFactory);
+        if (application.definitionFor(options.itemFactory.enumUnboxingUtilityType) != null) {
+          // The enum unboxing utility class can be created only during cf to dex compilation.
+          // If this is true, we are recompiling the dex application with R8 (compilation-steps).
+          options.enableEnumUnboxing = false;
+        } else {
+          EnumUnboxingCfMethods.registerSynthesizedCodeReferences(options.itemFactory);
+        }
       }
 
       List<ProguardConfigurationRule> synthesizedProguardRules = new ArrayList<>();
@@ -369,6 +381,15 @@
           TreePruner pruner = new TreePruner(appViewWithLiveness);
           application = pruner.run(application);
 
+          if (options.enableEnumUnboxing) {
+            DexProgramClass utilityClass =
+                EnumUnboxingRewriter.synthesizeEmptyEnumUnboxingUtilityClass(appView);
+            // We cannot know at this point if the class will be on the main dex list,
+            // updated later. Since this is inserted in the app at this point, we do not need
+            // to use any synthesized class hack and add the class as a program class.
+            application = application.builder().addProgramClass(utilityClass).build();
+          }
+
           // Recompute the subtyping information.
           Set<DexType> removedClasses = pruner.getRemovedClasses();
           appView.setAppInfo(
@@ -465,7 +486,11 @@
         timing.end();
       }
 
-      if (options.getProguardConfiguration().isOptimizing()) {
+      boolean isKotlinLibraryCompilationWithInlinePassThrough =
+          options.enableCfByteCodePassThrough && appView.hasCfByteCodePassThroughMethods();
+
+      if (!isKotlinLibraryCompilationWithInlinePassThrough
+          && options.getProguardConfiguration().isOptimizing()) {
         if (options.enableHorizontalClassMerging) {
           timing.begin("HorizontalStaticClassMerger");
           StaticClassMerger staticClassMerger =
diff --git a/src/main/java/com/android/tools/r8/R8Command.java b/src/main/java/com/android/tools/r8/R8Command.java
index 895c680..656f3aa 100644
--- a/src/main/java/com/android/tools/r8/R8Command.java
+++ b/src/main/java/com/android/tools/r8/R8Command.java
@@ -858,6 +858,11 @@
       internal.outline.enabled = false;
       internal.enableEnumUnboxing = false;
     }
+    if (!internal.isShrinking()) {
+      // If R8 is not shrinking, there is no point in unboxing enums since the unboxed enums
+      // will still remain in the program (The application size would actually increase).
+      internal.enableEnumUnboxing = false;
+    }
 
     // Amend the proguard-map consumer with options from the proguard configuration.
     internal.proguardMapConsumer =
diff --git a/src/main/java/com/android/tools/r8/ResourceShrinker.java b/src/main/java/com/android/tools/r8/ResourceShrinker.java
index 0affffd..86859f7 100644
--- a/src/main/java/com/android/tools/r8/ResourceShrinker.java
+++ b/src/main/java/com/android/tools/r8/ResourceShrinker.java
@@ -102,7 +102,9 @@
 
     @Override
     InternalOptions getInternalOptions() {
-      return new InternalOptions();
+      InternalOptions internalOptions = new InternalOptions();
+      internalOptions.isRunningDeprecatedResourceShrinker = true;
+      return internalOptions;
     }
   }
 
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfFrame.java b/src/main/java/com/android/tools/r8/cf/code/CfFrame.java
index 72f5be9..f1058d6 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfFrame.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfFrame.java
@@ -76,6 +76,16 @@
     private FrameType() {}
   }
 
+  @Override
+  public boolean isFrame() {
+    return true;
+  }
+
+  @Override
+  public CfFrame asFrame() {
+    return this;
+  }
+
   private static class InitializedType extends FrameType {
 
     private final DexType type;
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfInstruction.java b/src/main/java/com/android/tools/r8/cf/code/CfInstruction.java
index 05f1181..041409b 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfInstruction.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfInstruction.java
@@ -87,6 +87,14 @@
     return false;
   }
 
+  public CfFrame asFrame() {
+    return null;
+  }
+
+  public boolean isFrame() {
+    return false;
+  }
+
   public CfPosition asPosition() {
     return null;
   }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfInvoke.java b/src/main/java/com/android/tools/r8/cf/code/CfInvoke.java
index 1146ff2..b47570c 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfInvoke.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfInvoke.java
@@ -115,6 +115,10 @@
         !method.name.toString().equals(Constants.INSTANCE_INITIALIZER_NAME);
   }
 
+  public boolean isInvokeVirtual() {
+    return opcode == Opcodes.INVOKEVIRTUAL;
+  }
+
   @Override
   public boolean canThrow() {
     return true;
diff --git a/src/main/java/com/android/tools/r8/code/Instruction.java b/src/main/java/com/android/tools/r8/code/Instruction.java
index ea056cb..b6b1f9c 100644
--- a/src/main/java/com/android/tools/r8/code/Instruction.java
+++ b/src/main/java/com/android/tools/r8/code/Instruction.java
@@ -163,6 +163,14 @@
     return false;
   }
 
+  public boolean isInvokeVirtual() {
+    return false;
+  }
+
+  public InvokeVirtual asInvokeVirtual() {
+    return null;
+  }
+
   public boolean isSimpleNop() {
     return !isPayload() && this instanceof Nop;
   }
diff --git a/src/main/java/com/android/tools/r8/code/InvokeVirtual.java b/src/main/java/com/android/tools/r8/code/InvokeVirtual.java
index 003debf..b35882b 100644
--- a/src/main/java/com/android/tools/r8/code/InvokeVirtual.java
+++ b/src/main/java/com/android/tools/r8/code/InvokeVirtual.java
@@ -39,6 +39,16 @@
   }
 
   @Override
+  public boolean isInvokeVirtual() {
+    return true;
+  }
+
+  @Override
+  public InvokeVirtual asInvokeVirtual() {
+    return this;
+  }
+
+  @Override
   public void registerUse(UseRegistry registry) {
     registry.registerInvokeVirtual(getMethod());
   }
diff --git a/src/main/java/com/android/tools/r8/dex/ApplicationReader.java b/src/main/java/com/android/tools/r8/dex/ApplicationReader.java
index 8890a4d..ede2193 100644
--- a/src/main/java/com/android/tools/r8/dex/ApplicationReader.java
+++ b/src/main/java/com/android/tools/r8/dex/ApplicationReader.java
@@ -74,11 +74,11 @@
     this.inputApp = inputApp;
   }
 
-  public DexApplication read() throws IOException, ExecutionException {
+  public DexApplication read() throws IOException {
     return read((StringResource) null);
   }
 
-  public DexApplication read(StringResource proguardMap) throws IOException, ExecutionException {
+  public DexApplication read(StringResource proguardMap) throws IOException {
     ExecutorService executor = Executors.newSingleThreadExecutor();
     try {
       return read(proguardMap, executor);
@@ -87,14 +87,13 @@
     }
   }
 
-  public final DexApplication read(ExecutorService executorService)
-      throws IOException, ExecutionException {
+  public final DexApplication read(ExecutorService executorService) throws IOException {
     return read(
         null, executorService, ProgramClassCollection.defaultConflictResolver(options.reporter));
   }
 
   public final DexApplication read(StringResource proguardMap, ExecutorService executorService)
-      throws IOException, ExecutionException {
+      throws IOException {
     return read(
         proguardMap,
         executorService,
@@ -105,7 +104,7 @@
       StringResource proguardMap,
       ExecutorService executorService,
       ProgramClassConflictResolver resolver)
-      throws IOException, ExecutionException {
+      throws IOException {
     assert verifyMainDexOptionsCompatible(inputApp, options);
     Path dumpOutput = null;
     boolean cleanDump = false;
@@ -167,7 +166,7 @@
   }
 
   private static void dumpInputToFile(AndroidApp app, Path output, InternalOptions options) {
-    app.dump(output, options.getProguardConfiguration(), options.reporter);
+    app.dump(output, options);
   }
 
   private static boolean verifyMainDexOptionsCompatible(
diff --git a/src/main/java/com/android/tools/r8/dex/DexParser.java b/src/main/java/com/android/tools/r8/dex/DexParser.java
index 2e37abb..4dba5c6 100644
--- a/src/main/java/com/android/tools/r8/dex/DexParser.java
+++ b/src/main/java/com/android/tools/r8/dex/DexParser.java
@@ -668,7 +668,9 @@
               annotationIterator.getNextFor(method),
               parameterAnnotationsIterator.getNextFor(method),
               code);
-      if (accessFlags.isAbstract() && ensureNonAbstract) {
+      if (accessFlags.isAbstract()
+          && ensureNonAbstract
+          && !options.isRunningDeprecatedResourceShrinker) {
         accessFlags.unsetAbstract();
         encodedMethod =
             options.isGeneratingClassFiles()
diff --git a/src/main/java/com/android/tools/r8/dexsplitter/DexSplitter.java b/src/main/java/com/android/tools/r8/dexsplitter/DexSplitter.java
index bdb6155..d9a5609 100644
--- a/src/main/java/com/android/tools/r8/dexsplitter/DexSplitter.java
+++ b/src/main/java/com/android/tools/r8/dexsplitter/DexSplitter.java
@@ -8,6 +8,7 @@
 import com.android.tools.r8.D8Command;
 import com.android.tools.r8.DexIndexedConsumer;
 import com.android.tools.r8.DexSplitterHelper;
+import com.android.tools.r8.Diagnostic;
 import com.android.tools.r8.DiagnosticsHandler;
 import com.android.tools.r8.Keep;
 import com.android.tools.r8.origin.PathOrigin;
@@ -186,8 +187,10 @@
     }
 
     // Shorthand error messages.
-    public void error(String msg) {
-      diagnosticsHandler.error(new StringDiagnostic(msg));
+    public Diagnostic error(String msg) {
+      StringDiagnostic error = new StringDiagnostic(msg);
+      diagnosticsHandler.error(error);
+      return error;
     }
   }
 
@@ -283,21 +286,18 @@
 
   public static void run(Options options)
       throws FeatureMappingException, CompilationFailedException {
-    boolean errors = false;
+    Diagnostic error = null;
     if (options.getInputArchives().isEmpty()) {
-      errors = true;
-      options.error("Need at least one --input");
+      error = options.error("Need at least one --input");
     }
     if (options.getFeatureSplitMapping() == null && options.getFeatureJars().isEmpty()) {
-      errors = true;
-      options.error("You must supply a feature split mapping or feature jars");
+      error = options.error("You must supply a feature split mapping or feature jars");
     }
     if (options.getFeatureSplitMapping() != null && !options.getFeatureJars().isEmpty()) {
-      errors = true;
-      options.error("You can't supply both a feature split mapping and feature jars");
+      error = options.error("You can't supply both a feature split mapping and feature jars");
     }
-    if (errors) {
-      throw new AbortException();
+    if (error != null) {
+      throw new AbortException(error);
     }
 
     D8Command.Builder builder = D8Command.builder(options.diagnosticsHandler);
@@ -345,9 +345,9 @@
           }
         }
       } catch (IOException e) {
-        options.getDiagnosticsHandler().error(
-            new ExceptionDiagnostic(e, new ZipFileOrigin(Paths.get(s))));
-        throw new AbortException();
+        ExceptionDiagnostic error = new ExceptionDiagnostic(e, new ZipFileOrigin(Paths.get(s)));
+        options.getDiagnosticsHandler().error(error);
+        throw new AbortException(error);
       }
     }
   }
diff --git a/src/main/java/com/android/tools/r8/errors/CompilationError.java b/src/main/java/com/android/tools/r8/errors/CompilationError.java
index ccefc7b..cb89d0b 100644
--- a/src/main/java/com/android/tools/r8/errors/CompilationError.java
+++ b/src/main/java/com/android/tools/r8/errors/CompilationError.java
@@ -30,6 +30,10 @@
     this(message, null, origin);
   }
 
+  public CompilationError(String message, Origin origin, Position position) {
+    this(message, null, origin, position);
+  }
+
   public CompilationError(String message, Throwable cause, Origin origin) {
     this(message, cause, origin, Position.UNKNOWN);
   }
@@ -48,18 +52,6 @@
     return position;
   }
 
-  public CompilationError withAdditionalOriginAndPositionInfo(Origin origin, Position position) {
-    if (this.origin == Origin.unknown() || this.position == Position.UNKNOWN) {
-      return new CompilationError(
-          getMessage(),
-          this,
-          this.origin != Origin.unknown() ? this.origin : origin,
-          this.position != Position.UNKNOWN ? this.position : position);
-    } else {
-      return this;
-    }
-  }
-
   public Diagnostic toStringDiagnostic() {
     return new StringDiagnostic(getMessage(), origin, position);
   }
diff --git a/src/main/java/com/android/tools/r8/errors/DesugarDiagnostic.java b/src/main/java/com/android/tools/r8/errors/DesugarDiagnostic.java
index 59711e9..27b5a39 100644
--- a/src/main/java/com/android/tools/r8/errors/DesugarDiagnostic.java
+++ b/src/main/java/com/android/tools/r8/errors/DesugarDiagnostic.java
@@ -4,8 +4,8 @@
 package com.android.tools.r8.errors;
 
 import com.android.tools.r8.Diagnostic;
-import com.android.tools.r8.KeepForSubclassing;
+import com.android.tools.r8.Keep;
 
 /** Common interface type for all diagnostics related to desugaring. */
-@KeepForSubclassing
+@Keep
 public interface DesugarDiagnostic extends Diagnostic {}
diff --git a/src/main/java/com/android/tools/r8/errors/InterfaceDesugarDiagnostic.java b/src/main/java/com/android/tools/r8/errors/InterfaceDesugarDiagnostic.java
index 7bd18fd..afc2115 100644
--- a/src/main/java/com/android/tools/r8/errors/InterfaceDesugarDiagnostic.java
+++ b/src/main/java/com/android/tools/r8/errors/InterfaceDesugarDiagnostic.java
@@ -3,10 +3,8 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.errors;
 
-import com.android.tools.r8.KeepForSubclassing;
+import com.android.tools.r8.Keep;
 
 /** Common interface type for all diagnostics related to interface-method desugaring. */
-@KeepForSubclassing
-public interface InterfaceDesugarDiagnostic extends DesugarDiagnostic {
-
-}
+@Keep
+public interface InterfaceDesugarDiagnostic extends DesugarDiagnostic {}
diff --git a/src/main/java/com/android/tools/r8/errors/NestDesugarDiagnostic.java b/src/main/java/com/android/tools/r8/errors/NestDesugarDiagnostic.java
index 78f2f96..8662eee 100644
--- a/src/main/java/com/android/tools/r8/errors/NestDesugarDiagnostic.java
+++ b/src/main/java/com/android/tools/r8/errors/NestDesugarDiagnostic.java
@@ -4,11 +4,11 @@
 
 package com.android.tools.r8.errors;
 
-import com.android.tools.r8.KeepForSubclassing;
+import com.android.tools.r8.Keep;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.position.Position;
 
-@KeepForSubclassing
+@Keep
 public class NestDesugarDiagnostic implements DesugarDiagnostic {
 
   private final Origin origin;
diff --git a/src/main/java/com/android/tools/r8/features/FeatureSplitConfiguration.java b/src/main/java/com/android/tools/r8/features/FeatureSplitConfiguration.java
index 4a4db11..610104a 100644
--- a/src/main/java/com/android/tools/r8/features/FeatureSplitConfiguration.java
+++ b/src/main/java/com/android/tools/r8/features/FeatureSplitConfiguration.java
@@ -108,14 +108,12 @@
   }
 
   public boolean inBaseOrSameFeatureAs(DexProgramClass clazz, DexProgramClass context) {
-    FeatureSplit split = javaTypeToFeatureSplitMapping.get(clazz.type.toSourceString());
-    return split == null
-        || split == javaTypeToFeatureSplitMapping.get(context.type.toSourceString());
+    FeatureSplit split = getFeatureSplit(clazz.type);
+    return split == null || split == getFeatureSplit(context.type);
   }
 
   public boolean isInFeature(DexProgramClass clazz) {
-    return javaTypeToFeatureSplitMapping.containsKey(
-        DescriptorUtils.descriptorToJavaType(clazz.type.toDescriptorString()));
+    return getFeatureSplit(clazz.type) != null;
   }
 
   public boolean isInBase(DexProgramClass clazz) {
@@ -132,13 +130,19 @@
       return true;
     }
     // TODO(141451259): Consider doing the mapping from DexType to Feature (with support in mapper)
-    return javaTypeToFeatureSplitMapping.get(
-            DescriptorUtils.descriptorToJavaType(a.toDescriptorString()))
-        == javaTypeToFeatureSplitMapping.get(
-            DescriptorUtils.descriptorToJavaType(b.toDescriptorString()));
+    return getFeatureSplit(a) == getFeatureSplit(b);
   }
 
   public List<FeatureSplit> getFeatureSplits() {
     return featureSplits;
   }
+
+  public FeatureSplit getFeatureSplitFromClassDescriptor(String classDescriptor) {
+    return javaTypeToFeatureSplitMapping.get(DescriptorUtils.descriptorToJavaType(classDescriptor));
+  }
+
+  private FeatureSplit getFeatureSplit(DexType type) {
+    assert type.isClassType();
+    return javaTypeToFeatureSplitMapping.get(type.toSourceString());
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/graph/AccessControl.java b/src/main/java/com/android/tools/r8/graph/AccessControl.java
index 2c05df2..854c5e7 100644
--- a/src/main/java/com/android/tools/r8/graph/AccessControl.java
+++ b/src/main/java/com/android/tools/r8/graph/AccessControl.java
@@ -15,6 +15,12 @@
 public class AccessControl {
 
   public static OptionalBool isClassAccessible(
+      DexClass clazz, ProgramMethod context, AppView<?> appView) {
+    return isClassAccessible(
+        clazz, context.getHolder(), appView.options().featureSplitConfiguration);
+  }
+
+  public static OptionalBool isClassAccessible(
       DexClass clazz,
       DexProgramClass context,
       FeatureSplitConfiguration featureSplitConfiguration) {
@@ -40,6 +46,14 @@
   public static OptionalBool isFieldAccessible(
       DexEncodedField field,
       DexClass holder,
+      ProgramMethod context,
+      AppView<? extends AppInfoWithClassHierarchy> appView) {
+    return isFieldAccessible(field, holder, context.getHolder(), appView.appInfo());
+  }
+
+  public static OptionalBool isFieldAccessible(
+      DexEncodedField field,
+      DexClass holder,
       DexProgramClass context,
       AppInfoWithClassHierarchy appInfo) {
     return isMemberAccessible(field.accessFlags, holder, context, appInfo);
diff --git a/src/main/java/com/android/tools/r8/graph/AppView.java b/src/main/java/com/android/tools/r8/graph/AppView.java
index 0b6547e..8077269 100644
--- a/src/main/java/com/android/tools/r8/graph/AppView.java
+++ b/src/main/java/com/android/tools/r8/graph/AppView.java
@@ -26,8 +26,10 @@
 import com.android.tools.r8.utils.OptionalBool;
 import com.android.tools.r8.utils.ThrowingConsumer;
 import com.google.common.base.Predicates;
+import com.google.common.collect.ImmutableSet;
 import java.util.IdentityHashMap;
 import java.util.Map;
+import java.util.Set;
 import java.util.function.Function;
 import java.util.function.Predicate;
 
@@ -67,6 +69,7 @@
   private HorizontallyMergedLambdaClasses horizontallyMergedLambdaClasses;
   private VerticallyMergedClasses verticallyMergedClasses;
   private EnumValueInfoMapCollection unboxedEnums = EnumValueInfoMapCollection.empty();
+  private Set<DexMethod> cfByteCodePassThrough = ImmutableSet.of();
 
   private Map<DexClass, DexValueString> sourceDebugExtensions = new IdentityHashMap<>();
 
@@ -96,7 +99,7 @@
     this.options = options;
     this.rewritePrefix = mapper;
 
-    if (enableWholeProgramOptimizations() && options.isCallSiteOptimizationEnabled()) {
+    if (enableWholeProgramOptimizations() && options.callSiteOptimizationOptions().isEnabled()) {
       this.callSiteOptimizationInfoPropagator =
           new CallSiteOptimizationInfoPropagator(withLiveness());
     } else {
@@ -372,6 +375,10 @@
     this.initializedClassesInInstanceMethods = initializedClassesInInstanceMethods;
   }
 
+  public void setCfByteCodePassThrough(Set<DexMethod> cfByteCodePassThrough) {
+    this.cfByteCodePassThrough = cfByteCodePassThrough;
+  }
+
   public <U> U withInitializedClassesInInstanceMethods(
       Function<InitializedClassesInInstanceMethods, U> fn, U defaultValue) {
     if (initializedClassesInInstanceMethods != null) {
@@ -463,7 +470,17 @@
   }
 
   public boolean isCfByteCodePassThrough(DexEncodedMethod method) {
+    if (!options.isGeneratingClassFiles()) {
+      return false;
+    }
+    if (cfByteCodePassThrough.contains(method.method)) {
+      return true;
+    }
     return options.testing.cfByteCodePassThrough != null
-        && options.testing.cfByteCodePassThrough.test(method);
+        && options.testing.cfByteCodePassThrough.test(method.method);
+  }
+
+  public boolean hasCfByteCodePassThroughMethods() {
+    return !cfByteCodePassThrough.isEmpty();
   }
 }
diff --git a/src/main/java/com/android/tools/r8/graph/AppliedGraphLens.java b/src/main/java/com/android/tools/r8/graph/AppliedGraphLens.java
index 158bf1f..7e46aad 100644
--- a/src/main/java/com/android/tools/r8/graph/AppliedGraphLens.java
+++ b/src/main/java/com/android/tools/r8/graph/AppliedGraphLens.java
@@ -101,8 +101,10 @@
   }
 
   @Override
-  public DexMethod getRenamedMethodSignature(DexMethod originalMethod) {
-    return originalMethodSignatures.inverse().getOrDefault(originalMethod, originalMethod);
+  public DexMethod getRenamedMethodSignature(DexMethod originalMethod, GraphLense applied) {
+    return this != applied
+        ? originalMethodSignatures.inverse().getOrDefault(originalMethod, originalMethod)
+        : originalMethod;
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/graph/AssemblyWriter.java b/src/main/java/com/android/tools/r8/graph/AssemblyWriter.java
index 9893684..1315f4a 100644
--- a/src/main/java/com/android/tools/r8/graph/AssemblyWriter.java
+++ b/src/main/java/com/android/tools/r8/graph/AssemblyWriter.java
@@ -160,7 +160,7 @@
         ps.println("# Annotations:");
         String prefix = "#  ";
         for (DexAnnotation annotation : annotations.annotations) {
-          if (annotation.annotation.type == kotlin.metadata.kotlinMetadataType) {
+          if (annotation.annotation.type == kotlin.factory.kotlinMetadataType) {
             assert clazz != null : "Kotlin metadata is a class annotation";
             KotlinMetadataWriter.writeKotlinMetadataAnnotation(prefix, annotation, ps, kotlin);
           } else {
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 c2390fb..6e99fbe 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
@@ -131,7 +131,7 @@
   //   we need to maintain a set of states with (potentially different) contexts.
   private CompilationState compilationState = CompilationState.NOT_PROCESSED;
   private MethodOptimizationInfo optimizationInfo = DefaultMethodOptimizationInfo.DEFAULT_INSTANCE;
-  private CallSiteOptimizationInfo callSiteOptimizationInfo = CallSiteOptimizationInfo.BOTTOM;
+  private CallSiteOptimizationInfo callSiteOptimizationInfo = CallSiteOptimizationInfo.bottom();
   private int classFileVersion;
   private KotlinMethodLevelInfo kotlinMemberInfo = NO_KOTLIN_INFO;
 
@@ -783,6 +783,9 @@
     assert !shouldNotHaveCode();
     Builder builder = builder(this);
     builder.setCode(buildEmptyThrowingDexCode());
+    if (isNonPrivateVirtualMethod()) {
+      builder.setIsLibraryMethodOverride(isLibraryMethodOverride());
+    }
     // Note that we are not marking this instance obsolete, since this util is only used by
     // TreePruner while keeping non-live yet targeted, empty method. Such method can be retrieved
     // again only during the 2nd round of tree sharking, and seeing an obsolete empty body v.s.
@@ -807,6 +810,9 @@
     assert !shouldNotHaveCode();
     Builder builder = builder(this);
     builder.setCode(buildEmptyThrowingCfCode());
+    if (isNonPrivateVirtualMethod()) {
+      builder.setIsLibraryMethodOverride(isLibraryMethodOverride());
+    }
     // Note that we are not marking this instance obsolete:
     // refer to Dex-backend version of this method above.
     return builder.build();
@@ -1232,6 +1238,11 @@
     optimizationInfo = info;
   }
 
+  public synchronized void abandonCallSiteOptimizationInfo() {
+    checkIfObsolete();
+    callSiteOptimizationInfo = CallSiteOptimizationInfo.abandoned();
+  }
+
   public synchronized CallSiteOptimizationInfo getCallSiteOptimizationInfo() {
     checkIfObsolete();
     return callSiteOptimizationInfo;
@@ -1263,6 +1274,7 @@
     private DexMethod method;
     private final MethodAccessFlags accessFlags;
     private final DexAnnotationSet annotations;
+    private OptionalBool isLibraryMethodOverride = OptionalBool.UNKNOWN;
     private ParameterAnnotationsList parameterAnnotations;
     private Code code;
     private CompilationState compilationState;
@@ -1301,6 +1313,12 @@
       this.method = method;
     }
 
+    public Builder setIsLibraryMethodOverride(OptionalBool isLibraryMethodOverride) {
+      assert !isLibraryMethodOverride.isUnknown();
+      this.isLibraryMethodOverride = isLibraryMethodOverride;
+      return this;
+    }
+
     public Builder setParameterAnnotations(ParameterAnnotationsList parameterAnnotations) {
       this.parameterAnnotations = parameterAnnotations;
       return this;
@@ -1381,6 +1399,9 @@
       result.setKotlinMemberInfo(kotlinMemberInfo);
       result.compilationState = compilationState;
       result.optimizationInfo = optimizationInfo;
+      if (!isLibraryMethodOverride.isUnknown()) {
+        result.setLibraryMethodOverride(isLibraryMethodOverride);
+      }
       return result;
     }
   }
diff --git a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
index 7275598..3b635e6 100644
--- a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
+++ b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
@@ -269,6 +269,7 @@
   public final DexString npeDescriptor = createString("Ljava/lang/NullPointerException;");
   public final DexString reflectiveOperationExceptionDescriptor =
       createString("Ljava/lang/ReflectiveOperationException;");
+  public final DexString kotlinMetadataDescriptor = createString("Lkotlin/Metadata;");
 
   public final DexString intFieldUpdaterDescriptor =
       createString("Ljava/util/concurrent/atomic/AtomicIntegerFieldUpdater;");
@@ -300,13 +301,13 @@
   public final DexType voidType = createStaticallyKnownType(voidDescriptor);
 
   public final DexType booleanArrayType = createStaticallyKnownType(booleanArrayDescriptor);
-   public final DexType byteArrayType = createStaticallyKnownType(byteArrayDescriptor);
-   public final DexType charArrayType = createStaticallyKnownType(charArrayDescriptor);
-   public final DexType doubleArrayType = createStaticallyKnownType(doubleArrayDescriptor);
-   public final DexType floatArrayType = createStaticallyKnownType(floatArrayDescriptor);
-   public final DexType intArrayType = createStaticallyKnownType(intArrayDescriptor);
-   public final DexType longArrayType = createStaticallyKnownType(longArrayDescriptor);
-   public final DexType shortArrayType = createStaticallyKnownType(shortArrayDescriptor);
+  public final DexType byteArrayType = createStaticallyKnownType(byteArrayDescriptor);
+  public final DexType charArrayType = createStaticallyKnownType(charArrayDescriptor);
+  public final DexType doubleArrayType = createStaticallyKnownType(doubleArrayDescriptor);
+  public final DexType floatArrayType = createStaticallyKnownType(floatArrayDescriptor);
+  public final DexType intArrayType = createStaticallyKnownType(intArrayDescriptor);
+  public final DexType longArrayType = createStaticallyKnownType(longArrayDescriptor);
+  public final DexType shortArrayType = createStaticallyKnownType(shortArrayDescriptor);
 
   public final DexType boxedBooleanType = createStaticallyKnownType(boxedBooleanDescriptor);
   public final DexType boxedByteType = createStaticallyKnownType(boxedByteDescriptor);
@@ -399,6 +400,7 @@
   public final DexType npeType = createStaticallyKnownType(npeDescriptor);
   public final DexType reflectiveOperationExceptionType =
       createStaticallyKnownType(reflectiveOperationExceptionDescriptor);
+  public final DexType kotlinMetadataType = createStaticallyKnownType(kotlinMetadataDescriptor);
 
   public final DexType javaIoFileType = createStaticallyKnownType("Ljava/io/File;");
   public final DexType javaMathBigIntegerType = createStaticallyKnownType("Ljava/math/BigInteger;");
diff --git a/src/main/java/com/android/tools/r8/graph/DirectMappedDexApplication.java b/src/main/java/com/android/tools/r8/graph/DirectMappedDexApplication.java
index 03c329c..5f0a349 100644
--- a/src/main/java/com/android/tools/r8/graph/DirectMappedDexApplication.java
+++ b/src/main/java/com/android/tools/r8/graph/DirectMappedDexApplication.java
@@ -282,7 +282,7 @@
       Map<DexType, DexClass> allClasses, Iterable<T> toAdd) {
     for (DexClass clazz : toAdd) {
       DexClass old = allClasses.put(clazz.type, clazz);
-      assert old == null;
+      assert old == null : "Class " + old.type.toString() + " was already present.";
     }
   }
 }
diff --git a/src/main/java/com/android/tools/r8/graph/FieldResolutionResult.java b/src/main/java/com/android/tools/r8/graph/FieldResolutionResult.java
index e5d7239..0d2390b 100644
--- a/src/main/java/com/android/tools/r8/graph/FieldResolutionResult.java
+++ b/src/main/java/com/android/tools/r8/graph/FieldResolutionResult.java
@@ -49,6 +49,10 @@
       this.resolvedField = resolvedField;
     }
 
+    public DexClass getInitialResolutionHolder() {
+      return initialResolutionHolder;
+    }
+
     public DexClass getResolvedHolder() {
       return resolvedHolder;
     }
diff --git a/src/main/java/com/android/tools/r8/graph/GraphLense.java b/src/main/java/com/android/tools/r8/graph/GraphLense.java
index 2a5f317..ea55ec4 100644
--- a/src/main/java/com/android/tools/r8/graph/GraphLense.java
+++ b/src/main/java/com/android/tools/r8/graph/GraphLense.java
@@ -140,12 +140,23 @@
 
   public abstract DexField getRenamedFieldSignature(DexField originalField);
 
-  public abstract DexMethod getRenamedMethodSignature(DexMethod originalMethod);
+  public final DexMethod getRenamedMethodSignature(DexMethod originalMethod) {
+    return getRenamedMethodSignature(originalMethod, null);
+  }
+
+  public abstract DexMethod getRenamedMethodSignature(DexMethod originalMethod, GraphLense applied);
 
   public DexEncodedMethod mapDexEncodedMethod(
       DexEncodedMethod originalEncodedMethod, DexDefinitionSupplier definitions) {
+    return mapDexEncodedMethod(originalEncodedMethod, definitions, null);
+  }
+
+  public DexEncodedMethod mapDexEncodedMethod(
+      DexEncodedMethod originalEncodedMethod,
+      DexDefinitionSupplier definitions,
+      GraphLense applied) {
     assert originalEncodedMethod != DexEncodedMethod.SENTINEL;
-    DexMethod newMethod = getRenamedMethodSignature(originalEncodedMethod.method);
+    DexMethod newMethod = getRenamedMethodSignature(originalEncodedMethod.method, applied);
     // Note that:
     // * Even if `newMethod` is the same as `originalEncodedMethod.method`, we still need to look it
     //   up, since `originalEncodedMethod` may be obsolete.
@@ -445,7 +456,7 @@
     }
 
     @Override
-    public DexMethod getRenamedMethodSignature(DexMethod originalMethod) {
+    public DexMethod getRenamedMethodSignature(DexMethod originalMethod, GraphLense applied) {
       return originalMethod;
     }
 
@@ -511,8 +522,10 @@
     }
 
     @Override
-    public DexMethod getRenamedMethodSignature(DexMethod originalMethod) {
-      return previous.getRenamedMethodSignature(originalMethod);
+    public DexMethod getRenamedMethodSignature(DexMethod originalMethod, GraphLense applied) {
+      return this != applied
+          ? previous.getRenamedMethodSignature(originalMethod, applied)
+          : originalMethod;
     }
 
     @Override
@@ -611,8 +624,11 @@
     }
 
     @Override
-    public DexMethod getRenamedMethodSignature(DexMethod originalMethod) {
-      DexMethod renamedMethod = previousLense.getRenamedMethodSignature(originalMethod);
+    public DexMethod getRenamedMethodSignature(DexMethod originalMethod, GraphLense applied) {
+      if (this == applied) {
+        return originalMethod;
+      }
+      DexMethod renamedMethod = previousLense.getRenamedMethodSignature(originalMethod, applied);
       return originalMethodSignatures != null
           ? originalMethodSignatures.inverse().getOrDefault(renamedMethod, renamedMethod)
           : renamedMethod;
diff --git a/src/main/java/com/android/tools/r8/graph/JarClassFileReader.java b/src/main/java/com/android/tools/r8/graph/JarClassFileReader.java
index 0b5795d..1f04cca 100644
--- a/src/main/java/com/android/tools/r8/graph/JarClassFileReader.java
+++ b/src/main/java/com/android/tools/r8/graph/JarClassFileReader.java
@@ -36,6 +36,7 @@
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.shaking.ProguardKeepAttributes;
 import com.android.tools.r8.utils.DescriptorUtils;
+import com.android.tools.r8.utils.ExceptionUtils;
 import com.android.tools.r8.utils.FieldSignatureEquivalence;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.MethodSignatureEquivalence;
@@ -86,6 +87,11 @@
   }
 
   public void read(Origin origin, ClassKind classKind, byte[] bytes) {
+    ExceptionUtils.withOriginAttachmentHandler(
+        origin, () -> internalRead(origin, classKind, bytes));
+  }
+
+  public void internalRead(Origin origin, ClassKind classKind, byte[] bytes) {
     if (bytes.length < CLASSFILE_HEADER.length) {
       throw new CompilationError("Invalid empty classfile", origin);
     }
diff --git a/src/main/java/com/android/tools/r8/graph/LazyCfCode.java b/src/main/java/com/android/tools/r8/graph/LazyCfCode.java
index d17a4d2..e7a799d 100644
--- a/src/main/java/com/android/tools/r8/graph/LazyCfCode.java
+++ b/src/main/java/com/android/tools/r8/graph/LazyCfCode.java
@@ -372,8 +372,10 @@
         // This code visitor is used only if the method is neither abstract nor native, hence it
         // should have exactly one Code attribute:
         // https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html#jvms-4.7.3
-        throw new CompilationError("Absent Code attribute in method that is not native or abstract")
-            .withAdditionalOriginAndPositionInfo(origin, new MethodPosition(method));
+        throw new CompilationError(
+            "Absent Code attribute in method that is not native or abstract",
+            origin,
+            new MethodPosition(method));
       }
       code.setCode(
           new CfCode(
@@ -988,7 +990,8 @@
   private static DebugParsingOptions getParsingOptions(
       JarApplicationReader application, boolean reachabilitySensitive) {
     int parsingOptions =
-        application.options.testing.readInputStackMaps
+        (application.options.enableCfByteCodePassThrough
+                || application.options.testing.readInputStackMaps)
             ? ClassReader.EXPAND_FRAMES
             : ClassReader.SKIP_FRAMES;
 
diff --git a/src/main/java/com/android/tools/r8/graph/MethodArrayBacking.java b/src/main/java/com/android/tools/r8/graph/MethodArrayBacking.java
index 5a5db62..c7edad2 100644
--- a/src/main/java/com/android/tools/r8/graph/MethodArrayBacking.java
+++ b/src/main/java/com/android/tools/r8/graph/MethodArrayBacking.java
@@ -8,6 +8,7 @@
 import com.google.common.base.MoreObjects;
 import com.google.common.collect.Iterables;
 import com.google.common.collect.Sets;
+import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
 import java.util.List;
@@ -121,6 +122,23 @@
     return null;
   }
 
+  @Override
+  void removeMethods(Set<DexEncodedMethod> methods) {
+    directMethods = removeMethodsHelper(methods, directMethods);
+    virtualMethods = removeMethodsHelper(methods, virtualMethods);
+  }
+
+  private static DexEncodedMethod[] removeMethodsHelper(
+      Set<DexEncodedMethod> methodsToRemove, DexEncodedMethod[] existingMethods) {
+    List<DexEncodedMethod> newMethods = new ArrayList<>(existingMethods.length);
+    for (DexEncodedMethod method : existingMethods) {
+      if (!methodsToRemove.contains(method)) {
+        newMethods.add(method);
+      }
+    }
+    return newMethods.toArray(DexEncodedMethod.EMPTY_ARRAY);
+  }
+
   private DexEncodedMethod removeMethodWithIndex(
       int index, DexEncodedMethod[] methods, Consumer<DexEncodedMethod[]> newMethodsConsumer) {
     DexEncodedMethod removed = methods[index];
diff --git a/src/main/java/com/android/tools/r8/graph/MethodCollection.java b/src/main/java/com/android/tools/r8/graph/MethodCollection.java
index 14655d5..c93d366 100644
--- a/src/main/java/com/android/tools/r8/graph/MethodCollection.java
+++ b/src/main/java/com/android/tools/r8/graph/MethodCollection.java
@@ -259,6 +259,12 @@
     return removed;
   }
 
+  public void removeMethods(Set<DexEncodedMethod> methods) {
+    backing.removeMethods(methods);
+    resetDirectMethodCaches();
+    resetVirtualMethodCaches();
+  }
+
   public void setDirectMethods(DexEncodedMethod[] methods) {
     assert verifyCorrectnessOfMethodHolders(methods);
     resetDirectMethodCaches();
diff --git a/src/main/java/com/android/tools/r8/graph/MethodCollectionBacking.java b/src/main/java/com/android/tools/r8/graph/MethodCollectionBacking.java
index 2ce0cd8..0b3f435 100644
--- a/src/main/java/com/android/tools/r8/graph/MethodCollectionBacking.java
+++ b/src/main/java/com/android/tools/r8/graph/MethodCollectionBacking.java
@@ -96,6 +96,8 @@
 
   abstract DexEncodedMethod removeMethod(DexMethod method);
 
+  abstract void removeMethods(Set<DexEncodedMethod> method);
+
   // Replacement/mutation methods.
 
   abstract void setDirectMethods(DexEncodedMethod[] methods);
diff --git a/src/main/java/com/android/tools/r8/graph/MethodMapBacking.java b/src/main/java/com/android/tools/r8/graph/MethodMapBacking.java
index 18257c1..19c3cec 100644
--- a/src/main/java/com/android/tools/r8/graph/MethodMapBacking.java
+++ b/src/main/java/com/android/tools/r8/graph/MethodMapBacking.java
@@ -207,6 +207,11 @@
   }
 
   @Override
+  void removeMethods(Set<DexEncodedMethod> methods) {
+    methods.forEach(method -> methodMap.remove(wrap(method.getReference())));
+  }
+
+  @Override
   void setDirectMethods(DexEncodedMethod[] methods) {
     if ((methods == null || methods.length == 0) && methodMap.isEmpty()) {
       return;
diff --git a/src/main/java/com/android/tools/r8/graph/SmaliWriter.java b/src/main/java/com/android/tools/r8/graph/SmaliWriter.java
index 0f63ed1..e676697 100644
--- a/src/main/java/com/android/tools/r8/graph/SmaliWriter.java
+++ b/src/main/java/com/android/tools/r8/graph/SmaliWriter.java
@@ -13,7 +13,6 @@
 import java.io.IOException;
 import java.io.PrintStream;
 import java.nio.charset.StandardCharsets;
-import java.util.concurrent.ExecutionException;
 
 public class SmaliWriter extends DexByteCodeWriter {
 
@@ -30,7 +29,7 @@
           new ApplicationReader(application, options, Timing.empty()).read();
       SmaliWriter writer = new SmaliWriter(dexApplication, options);
       writer.write(ps);
-    } catch (IOException | ExecutionException e) {
+    } catch (IOException e) {
       throw new CompilationError("Failed to generate smali sting", e);
     }
     return new String(os.toByteArray(), StandardCharsets.UTF_8);
diff --git a/src/main/java/com/android/tools/r8/graph/analysis/ClassInitializerAssertionEnablingAnalysis.java b/src/main/java/com/android/tools/r8/graph/analysis/ClassInitializerAssertionEnablingAnalysis.java
index 6b03d33..c57a3da 100644
--- a/src/main/java/com/android/tools/r8/graph/analysis/ClassInitializerAssertionEnablingAnalysis.java
+++ b/src/main/java/com/android/tools/r8/graph/analysis/ClassInitializerAssertionEnablingAnalysis.java
@@ -167,8 +167,8 @@
         i < code.instructions.size() && nextExpectedInstructionIndex < sequence.size();
         i++) {
       instruction = code.instructions.get(i);
-      if (instruction.isLabel()) {
-        // Just ignore labels.
+      if (instruction.isLabel() || instruction.isFrame()) {
+        // Just ignore labels and frames.
         continue;
       }
       if (instruction.getClass() != sequence.get(nextExpectedInstructionIndex)) {
diff --git a/src/main/java/com/android/tools/r8/graph/analysis/EnqueuerAnalysis.java b/src/main/java/com/android/tools/r8/graph/analysis/EnqueuerAnalysis.java
index 049e211..1a00007 100644
--- a/src/main/java/com/android/tools/r8/graph/analysis/EnqueuerAnalysis.java
+++ b/src/main/java/com/android/tools/r8/graph/analysis/EnqueuerAnalysis.java
@@ -4,7 +4,6 @@
 
 package com.android.tools.r8.graph.analysis;
 
-import com.android.tools.r8.graph.DexDefinitionSupplier;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.ProgramField;
 import com.android.tools.r8.graph.ProgramMethod;
@@ -18,8 +17,7 @@
   public void processNewlyInstantiatedClass(DexProgramClass clazz, ProgramMethod context) {}
 
   /** Called when a class is found to be live. */
-  public void processNewlyLiveClass(
-      DexProgramClass clazz, EnqueuerWorklist worklist, DexDefinitionSupplier definitionSupplier) {}
+  public void processNewlyLiveClass(DexProgramClass clazz, EnqueuerWorklist worklist) {}
 
   /** Called when a field is found to be live. */
   public void processNewlyLiveField(ProgramField field) {}
@@ -37,5 +35,5 @@
    * Called when the Enqueuer has reached the final fixpoint. Each analysis may use this callback to
    * perform some post-processing.
    */
-  public void done() {}
+  public void done(Enqueuer enqueuer) {}
 }
diff --git a/src/main/java/com/android/tools/r8/graph/analysis/InitializedClassesInInstanceMethodsAnalysis.java b/src/main/java/com/android/tools/r8/graph/analysis/InitializedClassesInInstanceMethodsAnalysis.java
index 59c37b8..cc74d21 100644
--- a/src/main/java/com/android/tools/r8/graph/analysis/InitializedClassesInInstanceMethodsAnalysis.java
+++ b/src/main/java/com/android/tools/r8/graph/analysis/InitializedClassesInInstanceMethodsAnalysis.java
@@ -10,6 +10,7 @@
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.analysis.type.ClassTypeElement;
+import com.android.tools.r8.shaking.Enqueuer;
 import java.util.IdentityHashMap;
 import java.util.Map;
 
@@ -84,7 +85,7 @@
   }
 
   @Override
-  public void done() {
+  public void done(Enqueuer enqueuer) {
     appView.setInitializedClassesInInstanceMethods(
         new InitializedClassesInInstanceMethods(appView, mapping));
   }
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/AbstractError.java b/src/main/java/com/android/tools/r8/ir/analysis/AbstractError.java
deleted file mode 100644
index 937fe2a..0000000
--- a/src/main/java/com/android/tools/r8/ir/analysis/AbstractError.java
+++ /dev/null
@@ -1,60 +0,0 @@
-// 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.analysis;
-
-import com.android.tools.r8.graph.DexItemFactory;
-import com.android.tools.r8.graph.DexType;
-
-// Note that this is not what D8/R8 can throw.
-//
-// Finer-grained abstraction of Throwable that an instruction can throw if any.
-//
-//       Top        // throwing, but not quite sure what it would be.
-//    /   |    \
-//  NPE  ICCE  ...  // specific exceptions.
-//    \   |    /
-//      Bottom      // not throwing.
-public class AbstractError {
-
-  private static final AbstractError TOP = new AbstractError();
-  private static final AbstractError BOTTOM = new AbstractError();
-
-  private DexType simulatedError;
-
-  private AbstractError() {}
-
-  private AbstractError(DexType throwable) {
-    simulatedError = throwable;
-  }
-
-  public static AbstractError top() {
-    return TOP;
-  }
-
-  public static AbstractError bottom() {
-    return BOTTOM;
-  }
-
-  public static AbstractError specific(DexType throwable) {
-    return new AbstractError(throwable);
-  }
-
-  public boolean cannotThrow() {
-    return this == BOTTOM;
-  }
-
-  public boolean isThrowing() {
-    return this != BOTTOM;
-  }
-
-  public DexType getSpecificError(DexItemFactory factory) {
-    assert isThrowing();
-    if (simulatedError != null) {
-      return simulatedError;
-    }
-    assert this == TOP;
-    return factory.throwableType;
-  }
-
-}
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/ValueMayDependOnEnvironmentAnalysis.java b/src/main/java/com/android/tools/r8/ir/analysis/ValueMayDependOnEnvironmentAnalysis.java
index 82e53b7..fbb42a9 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/ValueMayDependOnEnvironmentAnalysis.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/ValueMayDependOnEnvironmentAnalysis.java
@@ -388,7 +388,7 @@
           if (instruction.isStaticPut()) {
             StaticPut otherStaticPut = instruction.asStaticPut();
             if (otherStaticPut.getField().holder == staticPut.getField().holder
-                && instruction.instructionInstanceCanThrow(appView, context).cannotThrow()) {
+                && !instruction.instructionInstanceCanThrow(appView, context)) {
               continue;
             }
             return true;
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/TrivialFieldAccessReprocessor.java b/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/TrivialFieldAccessReprocessor.java
index b5bf1c4..dd450d6 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/TrivialFieldAccessReprocessor.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/TrivialFieldAccessReprocessor.java
@@ -66,13 +66,14 @@
       return;
     }
 
+    timing.begin("Enqueue methods for reprocessing");
+    enqueueMethodsForReprocessing(appInfo, executorService);
+    timing.end(); // Enqueue methods for reprocessing
+
     timing.begin("Clear reads from fields of interest");
     clearReadsFromFieldsOfInterest(appInfo);
     timing.end(); // Clear reads from fields of interest
 
-    timing.begin("Enqueue methods for reprocessing");
-    enqueueMethodsForReprocessing(appInfo, executorService);
-    timing.end(); // Enqueue methods for reprocessing
     timing.end(); // Trivial field accesses analysis
 
     fieldsOfInterest.forEach(OptimizationFeedbackSimple.getInstance()::markFieldAsDead);
@@ -164,6 +165,11 @@
     private boolean registerFieldAccess(DexField field, boolean isStatic) {
       DexEncodedField encodedField = appView.appInfo().resolveField(field).getResolvedField();
       if (encodedField != null) {
+        // We cannot remove references from pass through functions.
+        if (appView.isCfByteCodePassThrough(method.getDefinition())) {
+          fieldsOfInterest.remove(encodedField);
+          return true;
+        }
         if (encodedField.isStatic() == isStatic) {
           if (fieldsOfInterest.contains(encodedField)) {
             methodsToReprocess.add(method);
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/proto/schema/ProtoEnqueuerExtension.java b/src/main/java/com/android/tools/r8/ir/analysis/proto/schema/ProtoEnqueuerExtension.java
index d1bf3d1..c44747f 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/proto/schema/ProtoEnqueuerExtension.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/proto/schema/ProtoEnqueuerExtension.java
@@ -8,7 +8,6 @@
 
 import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
 import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.DexDefinitionSupplier;
 import com.android.tools.r8.graph.DexEncodedField;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexField;
@@ -97,8 +96,7 @@
   }
 
   @Override
-  public void processNewlyLiveClass(
-      DexProgramClass clazz, EnqueuerWorklist worklist, DexDefinitionSupplier definitionSupplier) {
+  public void processNewlyLiveClass(DexProgramClass clazz, EnqueuerWorklist worklist) {
     assert appView.appInfo().hasClassHierarchy();
     AppInfoWithClassHierarchy appInfo = appView.appInfo().withClassHierarchy();
     if (appInfo.isStrictSubtypeOf(clazz.type, references.generatedMessageLiteType)) {
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/sideeffect/ClassInitializerSideEffectAnalysis.java b/src/main/java/com/android/tools/r8/ir/analysis/sideeffect/ClassInitializerSideEffectAnalysis.java
index f92edd0..136c209 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/sideeffect/ClassInitializerSideEffectAnalysis.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/sideeffect/ClassInitializerSideEffectAnalysis.java
@@ -56,7 +56,7 @@
             || !array.definition.isCreatingArray()
             || environmentAnalysis.valueMayDependOnEnvironment(arrayPut.index())
             || environmentAnalysis.valueMayDependOnEnvironment(arrayPut.value())
-            || arrayPut.instructionInstanceCanThrow(appView, context).isThrowing()) {
+            || arrayPut.instructionInstanceCanThrow(appView, context)) {
           return ClassInitializerSideEffect.SIDE_EFFECTS_THAT_CANNOT_BE_POSTPONED;
         }
         if (controlFlowMayDependOnEnvironment.isUnknown()) {
@@ -75,7 +75,7 @@
         Value array = newArrayFilledData.src();
         if (array.isPhi()
             || !array.definition.isCreatingArray()
-            || newArrayFilledData.instructionInstanceCanThrow(appView, context).isThrowing()) {
+            || newArrayFilledData.instructionInstanceCanThrow(appView, context)) {
           return ClassInitializerSideEffect.SIDE_EFFECTS_THAT_CANNOT_BE_POSTPONED;
         }
         if (controlFlowMayDependOnEnvironment.isUnknown()) {
@@ -92,7 +92,7 @@
       // on the environment.
       if (instruction.isInvokeNewArray()) {
         InvokeNewArray invokeNewArray = instruction.asInvokeNewArray();
-        if (invokeNewArray.instructionInstanceCanThrow(appView, context).isThrowing()) {
+        if (invokeNewArray.instructionInstanceCanThrow(appView, context)) {
           return ClassInitializerSideEffect.SIDE_EFFECTS_THAT_CANNOT_BE_POSTPONED;
         }
         for (Value argument : invokeNewArray.arguments()) {
@@ -104,7 +104,7 @@
       }
 
       if (instruction.isNewArrayEmpty()) {
-        if (instruction.instructionInstanceCanThrow(appView, context).isThrowing()) {
+        if (instruction.instructionInstanceCanThrow(appView, context)) {
           return ClassInitializerSideEffect.SIDE_EFFECTS_THAT_CANNOT_BE_POSTPONED;
         }
         continue;
@@ -117,7 +117,7 @@
         if (field == null
             || field.holder() != context.getHolderType()
             || environmentAnalysis.valueMayDependOnEnvironment(staticPut.value())
-            || instruction.instructionInstanceCanThrow(appView, context).isThrowing()) {
+            || instruction.instructionInstanceCanThrow(appView, context)) {
           return ClassInitializerSideEffect.SIDE_EFFECTS_THAT_CANNOT_BE_POSTPONED;
         }
         mayHaveSideEffects = true;
diff --git a/src/main/java/com/android/tools/r8/ir/code/ArrayLength.java b/src/main/java/com/android/tools/r8/ir/code/ArrayLength.java
index fcea78c..e9f074b 100644
--- a/src/main/java/com/android/tools/r8/ir/code/ArrayLength.java
+++ b/src/main/java/com/android/tools/r8/ir/code/ArrayLength.java
@@ -9,7 +9,6 @@
 import com.android.tools.r8.dex.Constants;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.ProgramMethod;
-import com.android.tools.r8.ir.analysis.AbstractError;
 import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.conversion.CfBuilder;
 import com.android.tools.r8.ir.conversion.DexBuilder;
@@ -74,18 +73,14 @@
   }
 
   @Override
-  public AbstractError instructionInstanceCanThrow(AppView<?> appView, ProgramMethod context) {
-    if (array().type.isNullable()) {
-      return AbstractError.specific(appView.dexItemFactory().npeType);
-    }
-
-    return AbstractError.bottom();
+  public boolean instructionInstanceCanThrow(AppView<?> appView, ProgramMethod context) {
+    return array().type.isNullable();
   }
 
   @Override
   public boolean instructionMayHaveSideEffects(
       AppView<?> appView, ProgramMethod context, SideEffectAssumption assumption) {
-    return instructionInstanceCanThrow(appView, context).isThrowing();
+    return instructionInstanceCanThrow(appView, context);
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/BasicBlockIterator.java b/src/main/java/com/android/tools/r8/ir/code/BasicBlockIterator.java
index d9fc837..523ca73 100644
--- a/src/main/java/com/android/tools/r8/ir/code/BasicBlockIterator.java
+++ b/src/main/java/com/android/tools/r8/ir/code/BasicBlockIterator.java
@@ -4,6 +4,7 @@
 
 package com.android.tools.r8.ir.code;
 
+import com.android.tools.r8.utils.IteratorUtils;
 import java.util.ListIterator;
 
 public class BasicBlockIterator implements ListIterator<BasicBlock> {
@@ -22,6 +23,14 @@
     this.listIterator = code.blocks.listIterator(index);
   }
 
+  public BasicBlock peekPrevious() {
+    return IteratorUtils.peekPrevious(this);
+  }
+
+  public BasicBlock peekNext() {
+    return IteratorUtils.peekNext(this);
+  }
+
   @Override
   public boolean hasNext() {
     return listIterator.hasNext();
diff --git a/src/main/java/com/android/tools/r8/ir/code/CheckCast.java b/src/main/java/com/android/tools/r8/ir/code/CheckCast.java
index cdbf6f0..35767cc 100644
--- a/src/main/java/com/android/tools/r8/ir/code/CheckCast.java
+++ b/src/main/java/com/android/tools/r8/ir/code/CheckCast.java
@@ -5,7 +5,6 @@
 package com.android.tools.r8.ir.code;
 
 import static com.android.tools.r8.ir.analysis.type.Nullability.definitelyNotNull;
-import static com.android.tools.r8.optimize.MemberRebindingAnalysis.isClassTypeVisibleFromContext;
 
 import com.android.tools.r8.cf.LoadStoreHelper;
 import com.android.tools.r8.cf.TypeVerificationHelper;
@@ -13,11 +12,11 @@
 import com.android.tools.r8.code.MoveObject;
 import com.android.tools.r8.code.MoveObjectFrom16;
 import com.android.tools.r8.dex.Constants;
+import com.android.tools.r8.graph.AccessControl;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.ProgramMethod;
-import com.android.tools.r8.ir.analysis.AbstractError;
 import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.conversion.CfBuilder;
 import com.android.tools.r8.ir.conversion.DexBuilder;
@@ -98,27 +97,27 @@
   @Override
   public boolean instructionMayHaveSideEffects(
       AppView<?> appView, ProgramMethod context, SideEffectAssumption assumption) {
-    return instructionInstanceCanThrow(appView, context).isThrowing();
+    return instructionInstanceCanThrow(appView, context);
   }
 
   @Override
-  public AbstractError instructionInstanceCanThrow(AppView<?> appView, ProgramMethod context) {
+  public boolean instructionInstanceCanThrow(AppView<?> appView, ProgramMethod context) {
     if (appView.options().debug || !appView.appInfo().hasLiveness()) {
-      return AbstractError.top();
+      return true;
     }
     if (type.isPrimitiveType()) {
-      return AbstractError.top();
+      return true;
     }
     DexType baseType = type.toBaseType(appView.dexItemFactory());
     if (baseType.isClassType()) {
-      DexClass dexClass = appView.definitionFor(baseType);
-      // * NoClassDefFoundError (resolution failure).
-      if (dexClass == null || !dexClass.isResolvable(appView)) {
-        return AbstractError.specific(appView.dexItemFactory().noClassDefFoundErrorType);
+      DexClass definition = appView.definitionFor(baseType);
+      // Check that the class and its super types are present.
+      if (definition == null || !definition.isResolvable(appView)) {
+        return true;
       }
-      // * IllegalAccessError (not visible from the access context).
-      if (!isClassTypeVisibleFromContext(appView, context, dexClass)) {
-        return AbstractError.specific(appView.dexItemFactory().illegalAccessErrorType);
+      // Check that the class is accessible.
+      if (AccessControl.isClassAccessible(definition, context, appView).isPossiblyFalse()) {
+        return true;
       }
     }
     AppView<? extends AppInfoWithLiveness> appViewWithLiveness = appView.withLiveness();
@@ -128,9 +127,9 @@
         .lessThanOrEqualUpToNullability(castType, appView)) {
       // This is a check-cast that has to be there for bytecode correctness, but R8 has proven
       // that this cast will never throw.
-      return AbstractError.bottom();
+      return false;
     }
-    return AbstractError.top();
+    return true;
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/ConstClass.java b/src/main/java/com/android/tools/r8/ir/code/ConstClass.java
index 759e764..4594db9 100644
--- a/src/main/java/com/android/tools/r8/ir/code/ConstClass.java
+++ b/src/main/java/com/android/tools/r8/ir/code/ConstClass.java
@@ -4,17 +4,15 @@
 
 package com.android.tools.r8.ir.code;
 
-import static com.android.tools.r8.optimize.MemberRebindingAnalysis.isClassTypeVisibleFromContext;
-
 import com.android.tools.r8.cf.LoadStoreHelper;
 import com.android.tools.r8.cf.TypeVerificationHelper;
 import com.android.tools.r8.cf.code.CfConstClass;
 import com.android.tools.r8.dex.Constants;
+import com.android.tools.r8.graph.AccessControl;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.ProgramMethod;
-import com.android.tools.r8.ir.analysis.AbstractError;
 import com.android.tools.r8.ir.analysis.type.Nullability;
 import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.analysis.value.AbstractValue;
@@ -101,42 +99,37 @@
   }
 
   @Override
-  public AbstractError instructionInstanceCanThrow(AppView<?> appView, ProgramMethod context) {
+  public boolean instructionInstanceCanThrow(AppView<?> appView, ProgramMethod context) {
     DexType baseType = getValue().toBaseType(appView.dexItemFactory());
     if (baseType.isPrimitiveType()) {
-      return AbstractError.bottom();
+      return false;
     }
 
     // Not applicable for D8.
     if (!appView.enableWholeProgramOptimizations()) {
       // Unless the type of interest is same as the context.
       if (baseType == context.getHolderType()) {
-        return AbstractError.bottom();
+        return false;
       }
-      return AbstractError.top();
+      return true;
     }
 
     DexClass clazz = appView.definitionFor(baseType);
-
-    if (clazz == null) {
-      return AbstractError.specific(appView.dexItemFactory().noClassDefFoundErrorType);
+    // * Check that the class and its super types are present.
+    if (clazz == null || !clazz.isResolvable(appView)) {
+      return true;
     }
-    // * NoClassDefFoundError (resolution failure).
-    if (!clazz.isResolvable(appView)) {
-      return AbstractError.specific(appView.dexItemFactory().noClassDefFoundErrorType);
+    // * Check that the class is accessible.
+    if (AccessControl.isClassAccessible(clazz, context, appView).isPossiblyFalse()) {
+      return true;
     }
-    // * IllegalAccessError (not visible from the access context).
-    if (!isClassTypeVisibleFromContext(appView, context, clazz)) {
-      return AbstractError.specific(appView.dexItemFactory().illegalAccessErrorType);
-    }
-
-    return AbstractError.bottom();
+    return false;
   }
 
   @Override
   public boolean instructionMayHaveSideEffects(
       AppView<?> appView, ProgramMethod context, SideEffectAssumption assumption) {
-    return instructionInstanceCanThrow(appView, context).isThrowing();
+    return instructionInstanceCanThrow(appView, context);
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/DominatorTree.java b/src/main/java/com/android/tools/r8/ir/code/DominatorTree.java
index 120a2d4..20eadc9 100644
--- a/src/main/java/com/android/tools/r8/ir/code/DominatorTree.java
+++ b/src/main/java/com/android/tools/r8/ir/code/DominatorTree.java
@@ -168,8 +168,12 @@
 
   /** Returns the blocks dominated by dominator, including dominator itself. */
   public List<BasicBlock> dominatedBlocks(BasicBlock dominator) {
+    return dominatedBlocks(dominator, new ArrayList<>());
+  }
+
+  public <T extends Collection<BasicBlock>> T dominatedBlocks(
+      BasicBlock dominator, T dominatedBlocks) {
     assert !obsolete;
-    List<BasicBlock> dominatedBlocks = new ArrayList<>();
     for (int i = dominator.getNumber(); i < unreachableStartIndex; ++i) {
       BasicBlock block = sorted[i];
       if (dominatedBy(block, dominator)) {
diff --git a/src/main/java/com/android/tools/r8/ir/code/FieldInstruction.java b/src/main/java/com/android/tools/r8/ir/code/FieldInstruction.java
index 436de09..fde9019 100644
--- a/src/main/java/com/android/tools/r8/ir/code/FieldInstruction.java
+++ b/src/main/java/com/android/tools/r8/ir/code/FieldInstruction.java
@@ -11,7 +11,6 @@
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.FieldResolutionResult.SuccessfulFieldResolutionResult;
 import com.android.tools.r8.graph.ProgramMethod;
-import com.android.tools.r8.ir.analysis.AbstractError;
 import com.android.tools.r8.ir.analysis.fieldvalueanalysis.AbstractFieldSet;
 import com.android.tools.r8.ir.analysis.fieldvalueanalysis.ConcreteMutableFieldSet;
 import com.android.tools.r8.ir.analysis.fieldvalueanalysis.EmptyFieldSet;
@@ -60,28 +59,28 @@
   }
 
   @Override
-  public AbstractError instructionInstanceCanThrow(AppView<?> appView, ProgramMethod context) {
+  public boolean instructionInstanceCanThrow(AppView<?> appView, ProgramMethod context) {
     return instructionInstanceCanThrow(appView, context, SideEffectAssumption.NONE);
   }
 
-  public AbstractError instructionInstanceCanThrow(
+  public boolean instructionInstanceCanThrow(
       AppView<?> appView, ProgramMethod context, SideEffectAssumption assumption) {
     SuccessfulFieldResolutionResult resolutionResult =
         appView.appInfo().resolveField(field, context).asSuccessfulResolution();
     if (resolutionResult == null) {
-      return AbstractError.top();
+      return true;
     }
     DexEncodedField resolvedField = resolutionResult.getResolvedField();
     // Check if the instruction may fail with an IncompatibleClassChangeError.
     if (resolvedField.isStatic() != isStaticFieldInstruction()) {
-      return AbstractError.top();
+      return true;
     }
     // Check if the resolution target is accessible.
     if (resolutionResult.getResolvedHolder() != context.getHolder()) {
       if (resolutionResult
           .isAccessibleFrom(context, appView.appInfo().withClassHierarchy())
           .isPossiblyFalse()) {
-        return AbstractError.top();
+        return true;
       }
     }
     // TODO(b/137168535): Without non-null tracking, only locally created receiver is allowed in D8.
@@ -90,14 +89,14 @@
       if (!assumption.canAssumeReceiverIsNotNull()) {
         Value receiver = inValues.get(0);
         if (receiver.isAlwaysNull(appView) || receiver.type.isNullable()) {
-          return AbstractError.specific(appView.dexItemFactory().npeType);
+          return true;
         }
       }
     }
     // For D8, reaching here means the field is in the same context, hence the class is guaranteed
     // to be initialized already.
     if (!appView.enableWholeProgramOptimizations()) {
-      return AbstractError.bottom();
+      return false;
     }
     boolean mayTriggerClassInitialization =
         isStaticFieldInstruction() && !assumption.canAssumeClassIsAlreadyInitialized();
@@ -106,7 +105,7 @@
       if (appView.appInfo().hasLiveness()) {
         AppInfoWithLiveness appInfoWithLiveness = appView.appInfo().withLiveness();
         if (appInfoWithLiveness.noSideEffects.containsKey(resolvedField.field)) {
-          return AbstractError.bottom();
+          return false;
         }
       }
       // May trigger <clinit> that may have side effects.
@@ -115,10 +114,10 @@
           // Types that are a super type of `context` are guaranteed to be initialized already.
           type -> appView.isSubtype(context.getHolderType(), type).isTrue(),
           Sets.newIdentityHashSet())) {
-        return AbstractError.top();
+        return true;
       }
     }
-    return AbstractError.bottom();
+    return false;
   }
 
   @Override
@@ -172,7 +171,7 @@
 
     AbstractValue abstractValue = field.getOptimizationInfo().getAbstractValue();
     if (abstractValue.isSingleValue()) {
-      if (abstractValue.isZero()) {
+      if (abstractValue.isSingleConstValue()) {
         return false;
       }
       if (abstractValue.isSingleFieldValue()) {
diff --git a/src/main/java/com/android/tools/r8/ir/code/If.java b/src/main/java/com/android/tools/r8/ir/code/If.java
index 42068de..706d470 100644
--- a/src/main/java/com/android/tools/r8/ir/code/If.java
+++ b/src/main/java/com/android/tools/r8/ir/code/If.java
@@ -89,6 +89,14 @@
     return visitor.visit(this);
   }
 
+  public boolean isNullTest() {
+    return isZeroTest() && lhs().getType().isReferenceType();
+  }
+
+  public boolean isNonTrivialNullTest() {
+    return isNullTest() && lhs().getType().isNullable();
+  }
+
   public boolean isZeroTest() {
     return inValues.size() == 1;
   }
diff --git a/src/main/java/com/android/tools/r8/ir/code/InitClass.java b/src/main/java/com/android/tools/r8/ir/code/InitClass.java
index 3ff5c53..d24dce8 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InitClass.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InitClass.java
@@ -4,16 +4,15 @@
 
 package com.android.tools.r8.ir.code;
 
-import static com.android.tools.r8.optimize.MemberRebindingAnalysis.isTypeVisibleFromContext;
-
 import com.android.tools.r8.cf.LoadStoreHelper;
 import com.android.tools.r8.cf.code.CfInitClass;
 import com.android.tools.r8.code.DexInitClass;
 import com.android.tools.r8.dex.Constants;
+import com.android.tools.r8.graph.AccessControl;
 import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.ProgramMethod;
-import com.android.tools.r8.ir.analysis.AbstractError;
 import com.android.tools.r8.ir.analysis.ClassInitializationAnalysis;
 import com.android.tools.r8.ir.analysis.ClassInitializationAnalysis.AnalysisAssumption;
 import com.android.tools.r8.ir.analysis.ClassInitializationAnalysis.Query;
@@ -94,24 +93,30 @@
   }
 
   @Override
-  public AbstractError instructionInstanceCanThrow(AppView<?> appView, ProgramMethod context) {
-    if (!isTypeVisibleFromContext(appView, context, clazz)) {
-      return AbstractError.top();
+  public boolean instructionInstanceCanThrow(AppView<?> appView, ProgramMethod context) {
+    DexClass definition = appView.definitionFor(clazz);
+    // * Check that the class is present.
+    if (definition == null) {
+      return true;
+    }
+    // * Check that the class is accessible.
+    if (AccessControl.isClassAccessible(definition, context, appView).isPossiblyFalse()) {
+      return true;
     }
     if (clazz.classInitializationMayHaveSideEffects(
         appView,
         // Types that are a super type of `context` are guaranteed to be initialized already.
         type -> appView.isSubtype(context.getHolderType(), type).isTrue(),
         Sets.newIdentityHashSet())) {
-      return AbstractError.top();
+      return true;
     }
-    return AbstractError.bottom();
+    return false;
   }
 
   @Override
   public boolean instructionMayHaveSideEffects(
       AppView<?> appView, ProgramMethod context, SideEffectAssumption assumption) {
-    return instructionInstanceCanThrow(appView, context).isThrowing();
+    return instructionInstanceCanThrow(appView, context);
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/InstanceGet.java b/src/main/java/com/android/tools/r8/ir/code/InstanceGet.java
index 38c1813..924d256 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InstanceGet.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InstanceGet.java
@@ -119,7 +119,7 @@
   @Override
   public boolean instructionMayHaveSideEffects(
       AppView<?> appView, ProgramMethod context, SideEffectAssumption assumption) {
-    return instructionInstanceCanThrow(appView, context, assumption).isThrowing();
+    return instructionInstanceCanThrow(appView, context, assumption);
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/InstancePut.java b/src/main/java/com/android/tools/r8/ir/code/InstancePut.java
index d5aad2d..115afec 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InstancePut.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InstancePut.java
@@ -121,7 +121,7 @@
       AppView<AppInfoWithLiveness> appViewWithLiveness = appView.withLiveness();
       AppInfoWithLiveness appInfoWithLiveness = appViewWithLiveness.appInfo();
 
-      if (instructionInstanceCanThrow(appView, context, assumption).isThrowing()) {
+      if (instructionInstanceCanThrow(appView, context, assumption)) {
         return true;
       }
 
diff --git a/src/main/java/com/android/tools/r8/ir/code/Instruction.java b/src/main/java/com/android/tools/r8/ir/code/Instruction.java
index 5fa8d11..6d298df 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Instruction.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Instruction.java
@@ -11,7 +11,6 @@
 import com.android.tools.r8.graph.DebugLocalInfo;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.ProgramMethod;
-import com.android.tools.r8.ir.analysis.AbstractError;
 import com.android.tools.r8.ir.analysis.ClassInitializationAnalysis.AnalysisAssumption;
 import com.android.tools.r8.ir.analysis.ClassInitializationAnalysis.Query;
 import com.android.tools.r8.ir.analysis.constant.Bottom;
@@ -590,8 +589,8 @@
   public abstract boolean instructionMayTriggerMethodInvocation(
       AppView<?> appView, ProgramMethod context);
 
-  public AbstractError instructionInstanceCanThrow(AppView<?> appView, ProgramMethod context) {
-    return instructionInstanceCanThrow() ? AbstractError.top() : AbstractError.bottom();
+  public boolean instructionInstanceCanThrow(AppView<?> appView, ProgramMethod context) {
+    return instructionInstanceCanThrow();
   }
 
   /** Returns true is this instruction can be treated as dead code if its outputs are not used. */
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeMethodWithReceiver.java b/src/main/java/com/android/tools/r8/ir/code/InvokeMethodWithReceiver.java
index 950d1bc..496f869 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokeMethodWithReceiver.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokeMethodWithReceiver.java
@@ -33,6 +33,18 @@
     super(target, result, arguments);
   }
 
+  public boolean hasRefinedReceiverLowerBoundType(AppView<AppInfoWithLiveness> appView) {
+    assert isInvokeMethodWithDynamicDispatch();
+    return getReceiver().getDynamicLowerBoundType(appView) != null;
+  }
+
+  public boolean hasRefinedReceiverUpperBoundType(AppView<AppInfoWithLiveness> appView) {
+    assert isInvokeMethodWithDynamicDispatch();
+    DexType staticReceiverType = getInvokedMethod().holder;
+    DexType refinedReceiverType = TypeAnalysis.getRefinedReceiverType(appView, this);
+    return refinedReceiverType != staticReceiverType;
+  }
+
   @Override
   public boolean isInvokeMethodWithReceiver() {
     return true;
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeMultiNewArray.java b/src/main/java/com/android/tools/r8/ir/code/InvokeMultiNewArray.java
index 1e41503..cc83815 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokeMultiNewArray.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokeMultiNewArray.java
@@ -3,17 +3,15 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.ir.code;
 
-import static com.android.tools.r8.optimize.MemberRebindingAnalysis.isClassTypeVisibleFromContext;
-
 import com.android.tools.r8.cf.LoadStoreHelper;
 import com.android.tools.r8.cf.TypeVerificationHelper;
 import com.android.tools.r8.cf.code.CfMultiANewArray;
 import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.graph.AccessControl;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.ProgramMethod;
-import com.android.tools.r8.ir.analysis.AbstractError;
 import com.android.tools.r8.ir.analysis.type.Nullability;
 import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.conversion.CfBuilder;
@@ -114,7 +112,7 @@
   }
 
   @Override
-  public AbstractError instructionInstanceCanThrow(AppView<?> appView, ProgramMethod context) {
+  public boolean instructionInstanceCanThrow(AppView<?> appView, ProgramMethod context) {
     DexType baseType = type.isArrayType() ? type.toBaseType(appView.dexItemFactory()) : type;
     if (baseType.isPrimitiveType()) {
       // Primitives types are known to be present and accessible.
@@ -132,30 +130,30 @@
     if (!appView.enableWholeProgramOptimizations()) {
       // Conservatively bail-out in D8, because we require whole program knowledge to determine if
       // the type is present and accessible.
-      return AbstractError.top();
+      return true;
     }
 
     // Check if the type is guaranteed to be present.
     DexClass clazz = appView.definitionFor(baseType);
     if (clazz == null) {
-      return AbstractError.top();
+      return true;
     }
 
     if (clazz.isLibraryClass()
         && !appView.dexItemFactory().libraryTypesAssumedToBePresent.contains(baseType)) {
-      return AbstractError.top();
+      return true;
     }
 
     // Check if the type is guaranteed to be accessible.
-    if (!isClassTypeVisibleFromContext(appView, context, clazz)) {
-      return AbstractError.top();
+    if (AccessControl.isClassAccessible(clazz, context, appView).isPossiblyFalse()) {
+      return true;
     }
 
     // The type is known to be present and accessible.
     return instructionInstanceCanThrowNegativeArraySizeException();
   }
 
-  private AbstractError instructionInstanceCanThrowNegativeArraySizeException() {
+  private boolean instructionInstanceCanThrowNegativeArraySizeException() {
     boolean mayHaveNegativeArraySize = false;
     for (Value value : arguments()) {
       if (!value.hasValueRange()) {
@@ -168,7 +166,7 @@
         break;
       }
     }
-    return mayHaveNegativeArraySize ? AbstractError.top() : AbstractError.bottom();
+    return mayHaveNegativeArraySize;
   }
 
   @Override
@@ -180,7 +178,7 @@
       return true;
     }
 
-    return instructionInstanceCanThrow(appView, context).isThrowing();
+    return instructionInstanceCanThrow(appView, context);
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeNewArray.java b/src/main/java/com/android/tools/r8/ir/code/InvokeNewArray.java
index 1d6547e..fc6028f 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokeNewArray.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokeNewArray.java
@@ -3,18 +3,16 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.ir.code;
 
-import static com.android.tools.r8.optimize.MemberRebindingAnalysis.isClassTypeVisibleFromContext;
-
 import com.android.tools.r8.cf.LoadStoreHelper;
 import com.android.tools.r8.cf.TypeVerificationHelper;
 import com.android.tools.r8.code.FilledNewArray;
 import com.android.tools.r8.code.FilledNewArrayRange;
 import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.graph.AccessControl;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.ProgramMethod;
-import com.android.tools.r8.ir.analysis.AbstractError;
 import com.android.tools.r8.ir.analysis.type.Nullability;
 import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.conversion.CfBuilder;
@@ -141,47 +139,47 @@
   }
 
   @Override
-  public AbstractError instructionInstanceCanThrow(AppView<?> appView, ProgramMethod context) {
+  public boolean instructionInstanceCanThrow(AppView<?> appView, ProgramMethod context) {
     DexType baseType = type.isArrayType() ? type.toBaseType(appView.dexItemFactory()) : type;
     if (baseType.isPrimitiveType()) {
       // Primitives types are known to be present and accessible.
       assert !type.isWideType() : "The array's contents must be single-word";
-      return AbstractError.bottom();
+      return false;
     }
 
     assert baseType.isReferenceType();
 
     if (baseType == context.getHolderType()) {
       // The enclosing type is known to be present and accessible.
-      return AbstractError.bottom();
+      return false;
     }
 
     if (!appView.enableWholeProgramOptimizations()) {
       // Conservatively bail-out in D8, because we require whole program knowledge to determine if
       // the type is present and accessible.
-      return AbstractError.top();
+      return true;
     }
 
     // Check if the type is guaranteed to be present.
     DexClass clazz = appView.definitionFor(baseType);
     if (clazz == null) {
-      return AbstractError.top();
+      return true;
     }
 
     if (clazz.isLibraryClass()) {
       if (!appView.dexItemFactory().libraryTypesAssumedToBePresent.contains(baseType)) {
-        return AbstractError.top();
+        return true;
       }
     }
 
     // Check if the type is guaranteed to be accessible.
-    if (!isClassTypeVisibleFromContext(appView, context, clazz)) {
-      return AbstractError.top();
+    if (AccessControl.isClassAccessible(clazz, context, appView).isPossiblyFalse()) {
+      return true;
     }
 
     // Note: Implicitly assuming that all the arguments are of the right type, because the input
     // code must be valid.
-    return AbstractError.bottom();
+    return false;
   }
 
   @Override
@@ -193,7 +191,7 @@
       return true;
     }
 
-    return instructionInstanceCanThrow(appView, context).isThrowing();
+    return instructionInstanceCanThrow(appView, context);
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeStatic.java b/src/main/java/com/android/tools/r8/ir/code/InvokeStatic.java
index 2c5faaf..7cd9b1e 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokeStatic.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokeStatic.java
@@ -3,8 +3,6 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.ir.code;
 
-import static com.android.tools.r8.optimize.MemberRebindingAnalysis.isMemberVisibleFromOriginalContext;
-
 import com.android.tools.r8.cf.code.CfInvoke;
 import com.android.tools.r8.code.InvokeStaticRange;
 import com.android.tools.r8.graph.AppView;
@@ -13,6 +11,7 @@
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.graph.ResolutionResult.SingleResolutionResult;
 import com.android.tools.r8.ir.analysis.ClassInitializationAnalysis;
 import com.android.tools.r8.ir.analysis.ClassInitializationAnalysis.AnalysisAssumption;
 import com.android.tools.r8.ir.analysis.ClassInitializationAnalysis.Query;
@@ -31,16 +30,16 @@
 
 public class InvokeStatic extends InvokeMethod {
 
-  private final boolean itf;
+  private final boolean isInterface;
 
   public InvokeStatic(DexMethod target, Value result, List<Value> arguments) {
     this(target, result, arguments, false);
     assert target.proto.parameters.size() == arguments.size();
   }
 
-  public InvokeStatic(DexMethod target, Value result, List<Value> arguments, boolean itf) {
+  public InvokeStatic(DexMethod target, Value result, List<Value> arguments, boolean isInterface) {
     super(target, result, arguments);
-    this.itf = itf;
+    this.isInterface = isInterface;
   }
 
   @Override
@@ -142,7 +141,8 @@
 
   @Override
   public void buildCf(CfBuilder builder) {
-    builder.add(new CfInvoke(org.objectweb.asm.Opcodes.INVOKESTATIC, getInvokedMethod(), itf));
+    builder.add(
+        new CfInvoke(org.objectweb.asm.Opcodes.INVOKESTATIC, getInvokedMethod(), isInterface));
   }
 
   @Override
@@ -175,43 +175,54 @@
     }
 
     // Find the target and check if the invoke may have side effects.
-    if (appView.appInfo().hasLiveness()) {
-      AppView<AppInfoWithLiveness> appViewWithLiveness = appView.withLiveness();
-      DexEncodedMethod target = lookupSingleTarget(appViewWithLiveness, context);
-      if (target == null) {
-        return true;
-      }
-
-      // Verify that the target method is accessible in the current context.
-      if (!isMemberVisibleFromOriginalContext(
-          appView, context, target.holder(), target.accessFlags)) {
-        return true;
-      }
-
-      // Verify that the target method does not have side-effects.
-      if (appViewWithLiveness.appInfo().noSideEffects.containsKey(target.method)) {
-        return false;
-      }
-
-      if (target.getOptimizationInfo().mayHaveSideEffects()) {
-        return true;
-      }
-
-      if (assumption.canAssumeClassIsAlreadyInitialized()) {
-        return false;
-      }
-
-      return target
-          .holder()
-          .classInitializationMayHaveSideEffects(
-              appView,
-              // Types that are a super type of `context` are guaranteed to be initialized
-              // already.
-              type -> appView.isSubtype(context.getHolderType(), type).isTrue(),
-              Sets.newIdentityHashSet());
+    if (!appView.appInfo().hasLiveness()) {
+      return true;
     }
 
-    return true;
+    AppView<AppInfoWithLiveness> appViewWithLiveness = appView.withLiveness();
+    AppInfoWithLiveness appInfoWithLiveness = appViewWithLiveness.appInfo();
+
+    SingleResolutionResult resolutionResult =
+        appViewWithLiveness
+            .appInfo()
+            .resolveMethod(getInvokedMethod(), isInterface)
+            .asSingleResolution();
+
+    // Verify that the target method is present.
+    if (resolutionResult == null) {
+      return true;
+    }
+
+    DexEncodedMethod singleTarget = resolutionResult.getSingleTarget();
+    assert singleTarget != null;
+
+    // Verify that the target method is static and accessible.
+    if (!singleTarget.isStatic()
+        || resolutionResult.isAccessibleFrom(context, appInfoWithLiveness).isPossiblyFalse()) {
+      return true;
+    }
+
+    // Verify that the target method does not have side-effects.
+    if (appViewWithLiveness.appInfo().noSideEffects.containsKey(singleTarget.getReference())) {
+      return false;
+    }
+
+    if (singleTarget.getOptimizationInfo().mayHaveSideEffects()) {
+      return true;
+    }
+
+    if (assumption.canAssumeClassIsAlreadyInitialized()) {
+      return false;
+    }
+
+    return singleTarget
+        .holder()
+        .classInitializationMayHaveSideEffects(
+            appView,
+            // Types that are a super type of `context` are guaranteed to be initialized
+            // already.
+            type -> appInfoWithLiveness.isSubtype(context.getHolderType(), type),
+            Sets.newIdentityHashSet());
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/LazyDominatorTree.java b/src/main/java/com/android/tools/r8/ir/code/LazyDominatorTree.java
new file mode 100644
index 0000000..bcb0fff
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/code/LazyDominatorTree.java
@@ -0,0 +1,21 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.ir.code;
+
+import com.android.tools.r8.ir.code.DominatorTree.Assumption;
+import com.android.tools.r8.utils.Box;
+
+public class LazyDominatorTree extends Box<DominatorTree> {
+
+  private final IRCode code;
+
+  public LazyDominatorTree(IRCode code) {
+    this.code = code;
+  }
+
+  @Override
+  public DominatorTree get() {
+    return computeIfAbsent(() -> new DominatorTree(code, Assumption.MAY_HAVE_UNREACHABLE_BLOCKS));
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/code/NewArrayFilledData.java b/src/main/java/com/android/tools/r8/ir/code/NewArrayFilledData.java
index aaa27dd..54d1ad4 100644
--- a/src/main/java/com/android/tools/r8/ir/code/NewArrayFilledData.java
+++ b/src/main/java/com/android/tools/r8/ir/code/NewArrayFilledData.java
@@ -10,7 +10,6 @@
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.ProgramMethod;
-import com.android.tools.r8.ir.analysis.AbstractError;
 import com.android.tools.r8.ir.conversion.CfBuilder;
 import com.android.tools.r8.ir.conversion.DexBuilder;
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
@@ -116,24 +115,15 @@
   }
 
   @Override
-  public AbstractError instructionInstanceCanThrow(AppView<?> appView, ProgramMethod context) {
-    if (appView.options().debug) {
-      return AbstractError.top();
-    }
-
-    if (src().getType().isNullable()) {
-      return AbstractError.top();
-    }
-
-    return AbstractError.bottom();
+  public boolean instructionInstanceCanThrow(AppView<?> appView, ProgramMethod context) {
+    return appView.options().debug || src().getType().isNullable();
   }
 
   @Override
   public boolean instructionMayHaveSideEffects(
       AppView<?> appView, ProgramMethod context, SideEffectAssumption assumption) {
     // Treat the instruction as possibly having side-effects if it may throw or the array is used.
-    if (instructionInstanceCanThrow(appView, context).isThrowing()
-        || src().numberOfAllUsers() > 1) {
+    if (instructionInstanceCanThrow(appView, context) || src().numberOfAllUsers() > 1) {
       return true;
     }
 
diff --git a/src/main/java/com/android/tools/r8/ir/code/NewInstance.java b/src/main/java/com/android/tools/r8/ir/code/NewInstance.java
index 3e6c121..c1b2bd7 100644
--- a/src/main/java/com/android/tools/r8/ir/code/NewInstance.java
+++ b/src/main/java/com/android/tools/r8/ir/code/NewInstance.java
@@ -3,12 +3,11 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.ir.code;
 
-import static com.android.tools.r8.optimize.MemberRebindingAnalysis.isMemberVisibleFromOriginalContext;
-
 import com.android.tools.r8.cf.LoadStoreHelper;
 import com.android.tools.r8.cf.TypeVerificationHelper;
 import com.android.tools.r8.cf.code.CfNew;
 import com.android.tools.r8.dex.Constants;
+import com.android.tools.r8.graph.AccessControl;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexItemFactory;
@@ -170,8 +169,7 @@
     }
 
     // Verify that the instruction does not lead to an IllegalAccessError.
-    if (!isMemberVisibleFromOriginalContext(
-        appView, context, definition.type, definition.accessFlags)) {
+    if (AccessControl.isClassAccessible(definition, context, appView).isPossiblyFalse()) {
       return true;
     }
 
diff --git a/src/main/java/com/android/tools/r8/ir/code/StaticGet.java b/src/main/java/com/android/tools/r8/ir/code/StaticGet.java
index e0e8b63..4dc7c12 100644
--- a/src/main/java/com/android/tools/r8/ir/code/StaticGet.java
+++ b/src/main/java/com/android/tools/r8/ir/code/StaticGet.java
@@ -145,7 +145,7 @@
   @Override
   public boolean instructionMayHaveSideEffects(
       AppView<?> appView, ProgramMethod context, SideEffectAssumption assumption) {
-    return instructionInstanceCanThrow(appView, context, assumption).isThrowing();
+    return instructionInstanceCanThrow(appView, context, assumption);
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/StaticPut.java b/src/main/java/com/android/tools/r8/ir/code/StaticPut.java
index 11732a9..678a569 100644
--- a/src/main/java/com/android/tools/r8/ir/code/StaticPut.java
+++ b/src/main/java/com/android/tools/r8/ir/code/StaticPut.java
@@ -110,7 +110,7 @@
         return false;
       }
 
-      if (instructionInstanceCanThrow(appView, context, assumption).isThrowing()) {
+      if (instructionInstanceCanThrow(appView, context, assumption)) {
         return true;
       }
 
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 02ab2d9..f1a6b89 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
@@ -592,13 +592,17 @@
   public void clearUsers() {
     users.clear();
     uniqueUsers = null;
-    phiUsers.clear();
-    uniquePhiUsers = null;
+    clearPhiUsers();
     if (debugData != null) {
       debugData.users.clear();
     }
   }
 
+  public void clearPhiUsers() {
+    phiUsers.clear();
+    uniquePhiUsers = null;
+  }
+
   public void addPhiUser(Phi user) {
     phiUsers.add(user);
     uniquePhiUsers = null;
@@ -688,6 +692,28 @@
     clearUsers();
   }
 
+  public void replacePhiUsers(Value newValue) {
+    if (this == newValue) {
+      return;
+    }
+    for (Phi user : uniquePhiUsers()) {
+      user.replaceOperand(this, newValue);
+    }
+    clearPhiUsers();
+  }
+
+  public void replaceSelectiveInstructionUsers(Value newValue, Predicate<Instruction> predicate) {
+    if (this == newValue) {
+      return;
+    }
+    for (Instruction user : uniqueUsers()) {
+      if (predicate.test(user)) {
+        fullyRemoveUser(user);
+        user.replaceValue(this, newValue);
+      }
+    }
+  }
+
   public void replaceSelectiveUsers(
       Value newValue,
       Set<Instruction> selectedInstructions,
@@ -1129,6 +1155,10 @@
     setType(newType);
   }
 
+  public BasicBlock getBlock() {
+    return definition.getBlock();
+  }
+
   public TypeElement getType() {
     return type;
   }
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 9822c48..ecd971f 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
@@ -51,7 +51,6 @@
 import com.android.tools.r8.ir.desugar.LambdaRewriter;
 import com.android.tools.r8.ir.desugar.StringConcatRewriter;
 import com.android.tools.r8.ir.desugar.TwrCloseResourceRewriter;
-import com.android.tools.r8.ir.optimize.AliasIntroducer;
 import com.android.tools.r8.ir.optimize.AssertionsRewriter;
 import com.android.tools.r8.ir.optimize.Assumer;
 import com.android.tools.r8.ir.optimize.ClassInitializerDefaultsOptimization;
@@ -288,9 +287,6 @@
         options.processCovariantReturnTypeAnnotations
             ? new CovariantReturnTypeAnnotationTransformer(this, appView.dexItemFactory())
             : null;
-    if (options.testing.forceAssumeNoneInsertion) {
-      assumers.add(new AliasIntroducer(appView));
-    }
     if (appView.enableWholeProgramOptimizations()) {
       assert appView.appInfo().hasLiveness();
       assert appView.rootSet() != null;
@@ -477,10 +473,10 @@
     }
   }
 
-  private void synthesizeEnumUnboxingUtilityClass(
+  private void synthesizeEnumUnboxingUtilityMethods(
       Builder<?> builder, ExecutorService executorService) throws ExecutionException {
     if (enumUnboxer != null) {
-      enumUnboxer.synthesizeUtilityClass(builder, this, executorService);
+      enumUnboxer.synthesizeUtilityMethods(builder, this, executorService);
     }
   }
 
@@ -730,8 +726,7 @@
     }
     if (enumUnboxer != null) {
       enumUnboxer.finishAnalysis();
-      enumUnboxer.unboxEnums(
-          postMethodProcessorBuilder, executorService, feedback, classStaticizer);
+      enumUnboxer.unboxEnums(postMethodProcessorBuilder, executorService, feedback);
     }
     new TrivialFieldAccessReprocessor(appView.withLiveness(), postMethodProcessorBuilder)
         .run(executorService, feedback, timing);
@@ -779,7 +774,7 @@
     synthesizeJava8UtilityClass(builder, executorService);
     synthesizeRetargetClass(builder, executorService);
     handleSynthesizedClassMapping(builder);
-    synthesizeEnumUnboxingUtilityClass(builder, executorService);
+    synthesizeEnumUnboxingUtilityMethods(builder, executorService);
 
     printPhase("Lambda merging finalization");
     // TODO(b/127694949): Adapt to PostOptimization.
@@ -1102,7 +1097,7 @@
       OptimizationFeedback feedback,
       MethodProcessor methodProcessor,
       MethodProcessingId methodProcessingId) {
-    return ExceptionUtils.withOriginAttachmentHandler(
+    return ExceptionUtils.withOriginAndPositionAttachmentHandler(
         method.getOrigin(),
         new MethodPosition(method.getReference()),
         () -> rewriteCodeInternal(method, feedback, methodProcessor, methodProcessingId));
@@ -1124,6 +1119,9 @@
           method.toSourceString(),
           logCode(options, method.getDefinition()));
     }
+    if (options.testing.hookInIrConversion != null) {
+      options.testing.hookInIrConversion.run();
+    }
     if (options.skipIR) {
       feedback.markProcessed(method.getDefinition(), ConstraintWithTarget.NEVER);
       return Timing.empty();
@@ -1218,6 +1216,16 @@
     assert code.verifyTypes(appView);
     assert code.isConsistentSSA();
 
+    if (appView.isCfByteCodePassThrough(method)) {
+      // If the code is pass trough, do not finalize by overwriting the existing code.
+      assert appView.enableWholeProgramOptimizations();
+      timing.begin("Collect optimization info");
+      collectOptimizationInfo(
+          method, code, ClassInitializerDefaultsResult.empty(), feedback, methodProcessor, timing);
+      timing.end();
+      return timing;
+    }
+
     assertionsRewriter.run(method, code, timing);
 
     if (serviceLoaderRewriter != null) {
@@ -1608,11 +1616,6 @@
       timing.end();
     }
 
-    if (appView.isCfByteCodePassThrough(method)) {
-      // If the code is pass trough, do not finalize by overwriting the existing code.
-      return timing;
-    }
-
     printMethod(code, "Optimized IR (SSA)", previous);
     timing.begin("Finalize IR");
     finalizeIR(code, feedback, timing);
@@ -1648,7 +1651,7 @@
     boolean isDebugMode = options.debug || method.getOptimizationInfo().isReachabilitySensitive();
     if (!isDebugMode && appView.callSiteOptimizationInfoPropagator() != null) {
       timing.begin("Collect call-site info");
-      appView.callSiteOptimizationInfoPropagator().collectCallSiteOptimizationInfo(code);
+      appView.callSiteOptimizationInfoPropagator().collectCallSiteOptimizationInfo(code, timing);
       timing.end();
     }
 
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/PostMethodProcessor.java b/src/main/java/com/android/tools/r8/ir/conversion/PostMethodProcessor.java
index 0b2476b..fbeff68 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/PostMethodProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/PostMethodProcessor.java
@@ -7,6 +7,7 @@
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.GraphLense;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.conversion.MethodProcessingId.Factory.ReservedMethodProcessingIds;
@@ -60,8 +61,8 @@
   public static class Builder {
 
     private final Collection<CodeOptimization> defaultCodeOptimizations;
-    private final LongLivedProgramMethodSetBuilder methodsMap =
-        new LongLivedProgramMethodSetBuilder();
+    private final LongLivedProgramMethodSetBuilder<?> methodsToReprocess =
+        LongLivedProgramMethodSetBuilder.create();
     private final Map<DexEncodedMethod, Collection<CodeOptimization>> optimizationsMap =
         new IdentityHashMap<>();
 
@@ -76,7 +77,7 @@
         return;
       }
       for (ProgramMethod method : methodsToRevisit) {
-        methodsMap.add(method);
+        methodsToReprocess.add(method);
         optimizationsMap
             .computeIfAbsent(
                 method.getDefinition(),
@@ -102,13 +103,15 @@
     // Some optimizations may change methods, creating new instances of the encoded methods with a
     // new signature. The compiler needs to update the set of methods that must be reprocessed
     // according to the graph lens.
-    public void mapDexEncodedMethods(AppView<?> appView) {
+    public void rewrittenWithLens(AppView<AppInfoWithLiveness> appView, GraphLense applied) {
+      methodsToReprocess.rewrittenWithLens(appView, applied);
       Map<DexEncodedMethod, Collection<CodeOptimization>> newOptimizationsMap =
           new IdentityHashMap<>();
       optimizationsMap.forEach(
           (method, optimizations) ->
               newOptimizationsMap.put(
-                  appView.graphLense().mapDexEncodedMethod(method, appView), optimizations));
+                  appView.graphLense().mapDexEncodedMethod(method, appView, applied),
+                  optimizations));
       optimizationsMap.clear();
       optimizationsMap.putAll(newOptimizationsMap);
     }
@@ -132,12 +135,13 @@
                 });
         put(set);
       }
-      if (methodsMap.isEmpty()) {
+      if (methodsToReprocess.isEmpty()) {
         // Nothing to revisit.
         return null;
       }
       CallGraph callGraph =
-          new PartialCallGraphBuilder(appView, methodsMap.build(appView))
+          new PartialCallGraphBuilder(
+                  appView, methodsToReprocess.build(appView, appView.graphLense()))
               .build(executorService, timing);
       return new PostMethodProcessor(appView, optimizationsMap, callGraph);
     }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/BackportedMethodRewriter.java b/src/main/java/com/android/tools/r8/ir/desugar/BackportedMethodRewriter.java
index e059db8..c0cdc54 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/BackportedMethodRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/BackportedMethodRewriter.java
@@ -4,7 +4,6 @@
 
 package com.android.tools.r8.ir.desugar;
 
-import static com.android.tools.r8.utils.ExceptionUtils.unwrapExecutionException;
 
 import com.android.tools.r8.dex.ApplicationReader;
 import com.android.tools.r8.dex.Constants;
@@ -99,29 +98,25 @@
 
   public static List<DexMethod> generateListOfBackportedMethods(
       AndroidApp androidApp, InternalOptions options, ExecutorService executor) throws IOException {
-    try {
-      List<DexMethod> methods = new ArrayList<>();
-      PrefixRewritingMapper rewritePrefix =
-          options.desugaredLibraryConfiguration.createPrefixRewritingMapper(options);
-      AppInfo appInfo = null;
-      if (androidApp != null) {
-        DexApplication app =
-            new ApplicationReader(androidApp, options, Timing.empty()).read(executor);
-        appInfo = new AppInfo(app);
-      }
-      AppView<?> appView = AppView.createForD8(appInfo, options, rewritePrefix);
-      BackportedMethodRewriter.RewritableMethods rewritableMethods =
-          new BackportedMethodRewriter.RewritableMethods(options, appView);
-      rewritableMethods.visit(methods::add);
-      if (appInfo != null) {
-        DesugaredLibraryRetargeter desugaredLibraryRetargeter =
-            new DesugaredLibraryRetargeter(appView);
-        desugaredLibraryRetargeter.visit(methods::add);
-      }
-      return methods;
-    } catch (ExecutionException e) {
-      throw unwrapExecutionException(e);
+    List<DexMethod> methods = new ArrayList<>();
+    PrefixRewritingMapper rewritePrefix =
+        options.desugaredLibraryConfiguration.createPrefixRewritingMapper(options);
+    AppInfo appInfo = null;
+    if (androidApp != null) {
+      DexApplication app =
+          new ApplicationReader(androidApp, options, Timing.empty()).read(executor);
+      appInfo = new AppInfo(app);
     }
+    AppView<?> appView = AppView.createForD8(appInfo, options, rewritePrefix);
+    BackportedMethodRewriter.RewritableMethods rewritableMethods =
+        new BackportedMethodRewriter.RewritableMethods(options, appView);
+    rewritableMethods.visit(methods::add);
+    if (appInfo != null) {
+      DesugaredLibraryRetargeter desugaredLibraryRetargeter =
+          new DesugaredLibraryRetargeter(appView);
+      desugaredLibraryRetargeter.visit(methods::add);
+    }
+    return methods;
   }
 
   public static void registerAssumedLibraryTypes(InternalOptions options) {
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/LambdaClass.java b/src/main/java/com/android/tools/r8/ir/desugar/LambdaClass.java
index 7ed2773..f41bdce 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/LambdaClass.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/LambdaClass.java
@@ -34,6 +34,7 @@
 import com.android.tools.r8.ir.synthetic.SynthesizedCode;
 import com.android.tools.r8.origin.SynthesizedOrigin;
 import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.OptionalBool;
 import com.google.common.base.Suppliers;
 import com.google.common.primitives.Longs;
 import java.nio.ByteBuffer;
@@ -723,6 +724,7 @@
                   forwardSourceCodeBuilder::build,
                   registry -> registry.registerInvokeDirect(implMethod)),
               true);
+      accessorEncodedMethod.setLibraryMethodOverride(OptionalBool.FALSE);
 
       implMethodHolder.addVirtualMethod(accessorEncodedMethod);
       return new ProgramMethod(implMethodHolder, accessorEncodedMethod);
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/LambdaDescriptor.java b/src/main/java/com/android/tools/r8/ir/desugar/LambdaDescriptor.java
index deb4579..45a8824 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/LambdaDescriptor.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/LambdaDescriptor.java
@@ -7,6 +7,7 @@
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
 import com.android.tools.r8.graph.DexCallSite;
+import com.android.tools.r8.graph.DexDefinitionSupplier;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexMethod;
@@ -259,11 +260,12 @@
     return descriptor == MATCH_FAILED ? null : descriptor;
   }
 
-  public static boolean isLambdaMetafactoryMethod(DexCallSite callSite, DexItemFactory factory) {
-    if (!callSite.bootstrapMethod.type.isInvokeStatic()) {
-      return false;
-    }
-    return factory.isLambdaMetafactoryMethod(callSite.bootstrapMethod.asMethod());
+  public static boolean isLambdaMetafactoryMethod(
+      DexCallSite callSite, DexDefinitionSupplier definitions) {
+    return callSite.bootstrapMethod.type.isInvokeStatic()
+        && definitions
+            .dexItemFactory()
+            .isLambdaMetafactoryMethod(callSite.bootstrapMethod.asMethod());
   }
 
   /**
@@ -272,7 +274,7 @@
    */
   static LambdaDescriptor infer(
       DexCallSite callSite, AppInfoWithClassHierarchy appInfo, ProgramMethod context) {
-    if (!isLambdaMetafactoryMethod(callSite, appInfo.dexItemFactory())) {
+    if (!isLambdaMetafactoryMethod(callSite, appInfo)) {
       return LambdaDescriptor.MATCH_FAILED;
     }
 
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/AliasIntroducer.java b/src/main/java/com/android/tools/r8/ir/optimize/AliasIntroducer.java
deleted file mode 100644
index cdb1699..0000000
--- a/src/main/java/com/android/tools/r8/ir/optimize/AliasIntroducer.java
+++ /dev/null
@@ -1,99 +0,0 @@
-// 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;
-
-import static com.google.common.base.Predicates.alwaysTrue;
-
-import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.ir.code.Assume;
-import com.android.tools.r8.ir.code.Assume.NoAssumption;
-import com.android.tools.r8.ir.code.BasicBlock;
-import com.android.tools.r8.ir.code.BasicBlockIterator;
-import com.android.tools.r8.ir.code.IRCode;
-import com.android.tools.r8.ir.code.Instruction;
-import com.android.tools.r8.ir.code.InstructionListIterator;
-import com.android.tools.r8.ir.code.Position;
-import com.android.tools.r8.ir.code.Value;
-import com.android.tools.r8.utils.Timing;
-import com.google.common.collect.Sets;
-import java.util.ListIterator;
-import java.util.Set;
-import java.util.function.Predicate;
-
-public class AliasIntroducer implements Assumer {
-  private final AppView<?> appView;
-
-  public AliasIntroducer(AppView<?> appView) {
-    this.appView = appView;
-  }
-
-  @Override
-  public void insertAssumeInstructions(IRCode code, Timing timing) {
-    insertAssumeInstructionsInBlocks(code, code.listIterator(), alwaysTrue(), timing);
-  }
-
-  @Override
-  public void insertAssumeInstructionsInBlocks(
-      IRCode code,
-      BasicBlockIterator blockIterator,
-      Predicate<BasicBlock> blockTester,
-      Timing timing) {
-    while (blockIterator.hasNext()) {
-      BasicBlock block = blockIterator.next();
-      if (blockTester.test(block)) {
-        insertAssumeNoneInstructionsInBlock(code, blockIterator, block);
-      }
-    }
-  }
-
-  private void insertAssumeNoneInstructionsInBlock(
-      IRCode code, ListIterator<BasicBlock> blockIterator, BasicBlock block) {
-    Set<Assume<NoAssumption>> deferredInstructions = Sets.newIdentityHashSet();
-    InstructionListIterator instructionIterator = block.listIterator(code);
-    while (instructionIterator.hasNext()) {
-      Instruction current = instructionIterator.next();
-      if (!current.hasOutValue() || !current.outValue().isUsed()) {
-        continue;
-      }
-      Value outValue = current.outValue();
-      // TODO(b/129859039): We may need similar concept when adding/testing assume-range
-      if (outValue.getType().isPrimitiveType() || outValue.getType().isNullType()) {
-        continue;
-      }
-      // Split block if needed
-      BasicBlock insertionBlock =
-          block.hasCatchHandlers() ? instructionIterator.split(code, blockIterator) : block;
-      // Replace usages of out-value by the out-value of the AssumeNone instruction.
-      Value aliasedValue = code.createValue(outValue.getType(), outValue.getLocalInfo());
-      outValue.replaceUsers(aliasedValue);
-      // Insert AssumeNone instruction.
-      Assume<NoAssumption> assumeNone =
-          Assume.createAssumeNoneInstruction(aliasedValue, outValue, current, appView);
-      // {@link BasicBlock} needs Argument instructions to be packed.
-      if (current.isArgument()) {
-        deferredInstructions.add(assumeNone);
-        continue;
-      }
-      assumeNone.setPosition(
-          appView.options().debug ? current.getPosition() : Position.none());
-      if (insertionBlock == block) {
-        instructionIterator.add(assumeNone);
-      } else {
-        insertionBlock.listIterator(code).add(assumeNone);
-      }
-    }
-    // For deferred instructions due to packed arguments,
-    //   restart the iterator; move up to the last Argument instruction; and then add aliases.
-    if (!deferredInstructions.isEmpty()) {
-      final Position firstNonNonePosition = code.findFirstNonNonePosition();
-      final InstructionListIterator it = block.listIterator(code);
-      it.nextUntil(i -> !i.isArgument());
-      it.previous();
-      deferredInstructions.forEach(i -> {
-        i.setPosition(firstNonNonePosition);
-        it.add(i);
-      });
-    }
-  }
-}
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 8912aeb..d05d64e 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
@@ -3,12 +3,16 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.ir.optimize;
 
+import static com.android.tools.r8.ir.optimize.info.CallSiteOptimizationInfo.abandoned;
+import static com.android.tools.r8.ir.optimize.info.CallSiteOptimizationInfo.top;
+import static com.android.tools.r8.utils.ConsumerUtils.emptyConsumer;
+
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexMethodHandle;
 import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.LookupResult;
 import com.android.tools.r8.graph.ProgramMethod;
-import com.android.tools.r8.graph.ResolutionResult;
 import com.android.tools.r8.graph.ResolutionResult.SingleResolutionResult;
 import com.android.tools.r8.ir.analysis.type.TypeAnalysis;
 import com.android.tools.r8.ir.analysis.type.TypeElement;
@@ -23,14 +27,19 @@
 import com.android.tools.r8.ir.code.InstructionListIterator;
 import com.android.tools.r8.ir.code.InvokeCustom;
 import com.android.tools.r8.ir.code.InvokeMethod;
+import com.android.tools.r8.ir.code.InvokeMethodWithReceiver;
 import com.android.tools.r8.ir.code.Value;
 import com.android.tools.r8.ir.conversion.CodeOptimization;
 import com.android.tools.r8.ir.conversion.PostOptimization;
-import com.android.tools.r8.ir.desugar.LambdaDescriptor;
 import com.android.tools.r8.ir.optimize.info.CallSiteOptimizationInfo;
 import com.android.tools.r8.ir.optimize.info.ConcreteCallSiteOptimizationInfo;
 import com.android.tools.r8.logging.Log;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.utils.BooleanUtils;
+import com.android.tools.r8.utils.ForEachable;
+import com.android.tools.r8.utils.InternalOptions.CallSiteOptimizationOptions;
+import com.android.tools.r8.utils.LazyBox;
+import com.android.tools.r8.utils.Timing;
 import com.android.tools.r8.utils.collections.ProgramMethodSet;
 import com.google.common.collect.Sets;
 import java.util.Collection;
@@ -53,12 +62,14 @@
   }
 
   private final AppView<AppInfoWithLiveness> appView;
+  private final CallSiteOptimizationOptions options;
   private ProgramMethodSet revisitedMethods = null;
   private Mode mode = Mode.COLLECT;
 
   public CallSiteOptimizationInfoPropagator(AppView<AppInfoWithLiveness> appView) {
     assert appView.enableWholeProgramOptimizations();
     this.appView = appView;
+    this.options = appView.options().callSiteOptimizationOptions();
     if (Log.isLoggingEnabledFor(CallSiteOptimizationInfoPropagator.class)) {
       revisitedMethods = ProgramMethodSet.create();
     }
@@ -78,7 +89,7 @@
     }
   }
 
-  public void collectCallSiteOptimizationInfo(IRCode code) {
+  public void collectCallSiteOptimizationInfo(IRCode code, Timing timing) {
     // TODO(b/139246447): we could collect call site optimization during REVISIT mode as well,
     //   but that may require a separate copy of CallSiteOptimizationInfo.
     if (mode != Mode.COLLECT) {
@@ -86,125 +97,204 @@
     }
     ProgramMethod context = code.context();
     for (Instruction instruction : code.instructions()) {
-      if (!instruction.isInvokeMethod() && !instruction.isInvokeCustom()) {
-        continue;
-      }
       if (instruction.isInvokeMethod()) {
-        InvokeMethod invoke = instruction.asInvokeMethod();
-        if (invoke.isInvokeMethodWithDynamicDispatch()) {
-          DexMethod invokedMethod = invoke.getInvokedMethod();
-          ResolutionResult resolutionResult =
-              appView.appInfo().resolveMethod(invokedMethod, invoke.isInvokeInterface());
-          // For virtual and interface calls, proceed on valid results only (since it's enforced).
-          if (!resolutionResult.isSingleResolution() || !resolutionResult.isVirtualTarget()) {
-            continue;
-          }
-          // If the resolution ended up with a single target, check if it is a library override.
-          // And if so, bail out early (to avoid expensive target lookup).
-          ProgramMethod resolutionTarget =
-              resolutionResult.asSingleResolution().getResolutionPair().asProgramMethod();
-          if (resolutionTarget == null
-              || isLibraryMethodOrLibraryMethodOverride(resolutionTarget)) {
-            continue;
-          }
-        }
-        ProgramMethodSet targets = invoke.lookupProgramDispatchTargets(appView, context);
-        assert invoke.isInvokeMethodWithDynamicDispatch()
-            // For other invocation types, the size of targets should be at most one.
-            || targets == null || targets.size() <= 1;
-        if (targets == null || targets.isEmpty() || hasLibraryOverrides(targets)) {
-          continue;
-        }
-        for (ProgramMethod target : targets) {
-          recordArgumentsIfNecessary(target, invoke.inValues());
-        }
-      }
-      if (instruction.isInvokeCustom()) {
-        InvokeCustom invokeCustom = instruction.asInvokeCustom();
-        // The bootstrap method for lambda allocation is always runtime internal.
-        if (LambdaDescriptor.isLambdaMetafactoryMethod(
-            invokeCustom.getCallSite(), appView.dexItemFactory())) {
-          continue;
-        }
-        // In other cases, if the bootstrap method is program declared it will be called. The call
-        // is with runtime provided arguments so ensure that the call-site info is TOP.
-        DexMethodHandle bootstrapMethod = invokeCustom.getCallSite().bootstrapMethod;
-        SingleResolutionResult resolution =
-            appView
-                .appInfo()
-                .resolveMethod(
-                    bootstrapMethod.asMethod(),
-                    bootstrapMethod.isInterface)
-                .asSingleResolution();
-        if (resolution != null && resolution.getResolvedHolder().isProgramClass()) {
-          resolution
-              .getResolvedMethod()
-              .joinCallSiteOptimizationInfo(CallSiteOptimizationInfo.TOP, appView);
-        }
+        collectCallSiteOptimizationInfoForInvokeMethod(
+            instruction.asInvokeMethod(), context, timing);
+      } else if (instruction.isInvokeCustom()) {
+        collectCallSiteOptimizationInfoForInvokeCustom(instruction.asInvokeCustom());
       }
     }
   }
 
-  // TODO(b/140204899): Instead of reprocessing here, pass stopping criteria to lookup?
-  // If any of target method is a library method override, bail out entirely/early.
-  private boolean hasLibraryOverrides(ProgramMethodSet targets) {
-    for (ProgramMethod target : targets) {
-      if (isLibraryMethodOrLibraryMethodOverride(target)) {
-        return true;
-      }
+  private void collectCallSiteOptimizationInfoForInvokeMethod(
+      InvokeMethod invoke, ProgramMethod context, Timing timing) {
+    DexMethod invokedMethod = invoke.getInvokedMethod();
+    SingleResolutionResult resolutionResult =
+        appView
+            .appInfo()
+            .resolveMethod(invokedMethod, invoke.isInvokeInterface())
+            .asSingleResolution();
+    if (resolutionResult == null) {
+      return;
     }
-    return false;
+    // For virtual and interface calls, proceed on valid results only (since it's enforced).
+    if (invoke.isInvokeMethodWithDynamicDispatch() && !resolutionResult.isVirtualTarget()) {
+      return;
+    }
+    ProgramMethod resolutionTarget =
+        resolutionResult.asSingleResolution().getResolutionPair().asProgramMethod();
+    if (resolutionTarget == null || isMaybeClasspathOrLibraryMethodOverride(resolutionTarget)) {
+      return;
+    }
+    propagateArgumentsToDispatchTargets(invoke, resolutionResult, context, timing);
   }
 
-  private boolean isLibraryMethodOrLibraryMethodOverride(ProgramMethod target) {
+  private void collectCallSiteOptimizationInfoForInvokeCustom(InvokeCustom invoke) {
+    // If the bootstrap method is program declared it will be called. The call is with runtime
+    // provided arguments so ensure that the call-site info is TOP.
+    DexMethodHandle bootstrapMethod = invoke.getCallSite().bootstrapMethod;
+    SingleResolutionResult resolution =
+        appView
+            .appInfo()
+            .resolveMethod(bootstrapMethod.asMethod(), bootstrapMethod.isInterface)
+            .asSingleResolution();
+    if (resolution != null && resolution.getResolvedHolder().isProgramClass()) {
+      resolution.getResolvedMethod().joinCallSiteOptimizationInfo(top(), appView);
+    }
+  }
+
+  private boolean isMaybeClasspathOrLibraryMethodOverride(ProgramMethod target) {
     // If the method overrides a library method, it is unsure how the method would be invoked by
     // that library.
-    return target.getDefinition().isLibraryMethodOverride().isTrue();
+    return target.getDefinition().isLibraryMethodOverride().isPossiblyTrue();
   }
 
-  // Record arguments for the given method if necessary.
-  // At the same time, if it decides to bail out, make the corresponding info immutable so that we
-  // can avoid recording arguments for the same method accidentally.
-  private void recordArgumentsIfNecessary(ProgramMethod target, List<Value> inValues) {
-    assert !target.getDefinition().isObsolete();
-    if (appView.appInfo().neverReprocess.contains(target.getReference())) {
+  // Propagate information about the arguments to all possible dispatch targets of the invoke.
+  private void propagateArgumentsToDispatchTargets(
+      InvokeMethod invoke,
+      SingleResolutionResult resolutionResult,
+      ProgramMethod context,
+      Timing timing) {
+    if (invoke.arguments().isEmpty()) {
+      // Nothing to propagate.
       return;
     }
-    if (target.getDefinition().getCallSiteOptimizationInfo().isTop()) {
+
+    if (invoke.arguments().size()
+        != invoke.getInvokedMethod().getArity()
+            + BooleanUtils.intValue(invoke.isInvokeMethodWithReceiver())) {
+      // Verification error.
+      assert false;
       return;
     }
+
+    if (resolutionResult.getResolvedMethod().getCallSiteOptimizationInfo().isAbandoned()) {
+      // We stopped tracking the arguments to all possible dispatch targets.
+      assert verifyAllProgramDispatchTargetsHaveBeenAbandoned(invoke, context);
+      return;
+    }
+
+    timing.begin("Lookup possible dispatch targets");
+    ProgramMethodSet targets = invoke.lookupProgramDispatchTargets(appView, context);
+    timing.end();
+
+    assert invoke.isInvokeMethodWithDynamicDispatch()
+        // For other invocation types, the size of targets should be at most one.
+        || targets == null
+        || targets.size() <= 1;
+
+    if (targets == null || targets.isEmpty()) {
+      return;
+    }
+
+    if (targets.size() > options.getMaxNumberOfDispatchTargetsBeforeAbandoning()) {
+      // If the number of targets exceed the threshold, abandon call site optimization for all
+      // targets.
+      abandonCallSitePropagation(invoke, resolutionResult, targets, context);
+      return;
+    }
+
+    timing.begin("Record arguments");
+    // Lazily computed piece of information that needs to be propagated to all dispatch targets.
+    LazyBox<CallSiteOptimizationInfo> callSiteOptimizationInfo =
+        new LazyBox<>(() -> computeCallSiteOptimizationInfoFromArguments(invoke, context, timing));
+    for (ProgramMethod target : targets) {
+      CallSiteOptimizationInfo newCallSiteOptimizationInfo =
+          propagateArgumentsToDispatchTarget(target, callSiteOptimizationInfo, timing);
+
+      // If one of the targets is abandoned or ends up being abandoned, then abandon call site
+      // optimization for all targets.
+      if (newCallSiteOptimizationInfo.isAbandoned()) {
+        abandonCallSitePropagation(invoke, resolutionResult, targets, context);
+        break;
+      }
+    }
+    timing.end();
+  }
+
+  private CallSiteOptimizationInfo propagateArgumentsToDispatchTarget(
+      ProgramMethod target,
+      LazyBox<CallSiteOptimizationInfo> lazyCallSiteOptimizationInfo,
+      Timing timing) {
+    CallSiteOptimizationInfo existingCallSiteOptimizationInfo =
+        target.getDefinition().getCallSiteOptimizationInfo();
+    if (existingCallSiteOptimizationInfo.isAbandoned()
+        || existingCallSiteOptimizationInfo.isTop()) {
+      return existingCallSiteOptimizationInfo;
+    }
+    if (!appView.appInfo().mayPropagateArgumentsTo(target)) {
+      return top();
+    }
+    timing.begin("Join argument info");
+    CallSiteOptimizationInfo callSiteOptimizationInfo =
+        lazyCallSiteOptimizationInfo.computeIfAbsent();
     target
         .getDefinition()
         .joinCallSiteOptimizationInfo(
-            computeCallSiteOptimizationInfoFromArguments(target, inValues), appView);
+            callSiteOptimizationInfo.hasUsefulOptimizationInfo(appView, target)
+                ? callSiteOptimizationInfo
+                : top(),
+            appView);
+    timing.end();
+    return target.getDefinition().getCallSiteOptimizationInfo();
+  }
+
+  private void abandonCallSitePropagation(
+      InvokeMethod invoke,
+      SingleResolutionResult resolutionResult,
+      ProgramMethodSet targets,
+      ProgramMethod context) {
+    if (invoke.isInvokeMethodWithDynamicDispatch()) {
+      // When there is a dynamic dispatch, we may have used dynamic type information to reduce the
+      // set of possible dispatch targets. However, it is an invariant that a method is marked as
+      // abandoned if-and-only-if that method and all of its overrides have been marked as
+      // abandoned. Therefore, we need to find all the overrides of the targeted method and mark
+      // them as abandoned, which we accomplish by performing a lookup without any dynamic type
+      // information.
+      InvokeMethodWithReceiver invokeMethodWithReceiver = invoke.asInvokeMethodWithReceiver();
+      if (invokeMethodWithReceiver.hasRefinedReceiverUpperBoundType(appView)
+          || invokeMethodWithReceiver.hasRefinedReceiverLowerBoundType(appView)) {
+        LookupResult lookupResult =
+            resolutionResult.lookupVirtualDispatchTargets(context.getHolder(), appView.appInfo());
+        // This should always succeed since we already looked up `targets` successfully.
+        assert lookupResult.isLookupResultSuccess();
+        abandonCallSitePropagation(
+            consumer ->
+                lookupResult.forEach(
+                    methodTarget -> {
+                      if (methodTarget.isProgramMethod()) {
+                        consumer.accept(methodTarget.asProgramMethod());
+                      } else {
+                        // This may happen if an interface method in the program is implemented
+                        // by a method in the classpath or library.
+                        assert invoke.isInvokeInterface();
+                      }
+                    },
+                    emptyConsumer()));
+        return;
+      }
+    }
+    abandonCallSitePropagation(targets::forEach);
+  }
+
+  private void abandonCallSitePropagation(ForEachable<ProgramMethod> methods) {
+    methods.forEach(method -> method.getDefinition().abandonCallSiteOptimizationInfo());
   }
 
   private CallSiteOptimizationInfo computeCallSiteOptimizationInfoFromArguments(
-      ProgramMethod target, List<Value> inValues) {
-    // No method body or no argument at all.
-    if (target.getDefinition().shouldNotHaveCode() || inValues.size() == 0) {
-      return CallSiteOptimizationInfo.TOP;
+      InvokeMethod invoke, ProgramMethod context, Timing timing) {
+    timing.begin("Compute argument info");
+    CallSiteOptimizationInfo callSiteOptimizationInfo =
+        ConcreteCallSiteOptimizationInfo.fromArguments(
+            appView, invoke.getInvokedMethod(), invoke.arguments(), context);
+    if (callSiteOptimizationInfo.isTop()) {
+      // If we are propagating unknown information to all call sites, then mark them as abandoned
+      // such that we bail out before looking up the possible dispatch targets if we see any future
+      // invokes to these methods.
+      callSiteOptimizationInfo = abandoned();
     }
-    // If pinned, that method could be invoked via reflection.
-    if (appView.appInfo().isPinned(target.getReference())) {
-      return CallSiteOptimizationInfo.TOP;
-    }
-    // If the method overrides a library method, it is unsure how the method would be invoked by
-    // that library.
-    if (target.getDefinition().isLibraryMethodOverride().isTrue()) {
-      // But, should not be reachable, since we already bail out.
-      assert false
-          : "Trying to compute call site optimization info for " + target.toSourceString();
-      return CallSiteOptimizationInfo.TOP;
-    }
-    // If the program already has illegal accesses, method resolution results will reflect that too.
-    // We should avoid recording arguments in that case. E.g., b/139823850: static methods can be a
-    // result of virtual call targets, if that's the only method that matches name and signature.
-    int argumentOffset = target.getDefinition().isStatic() ? 0 : 1;
-    if (inValues.size() != argumentOffset + target.getReference().getArity()) {
-      return CallSiteOptimizationInfo.BOTTOM;
-    }
-    return ConcreteCallSiteOptimizationInfo.fromArguments(appView, target, inValues);
+    timing.end();
+    return callSiteOptimizationInfo;
   }
 
   // If collected call site optimization info has something useful, e.g., non-null argument,
@@ -236,7 +326,7 @@
       int argIndex = argumentsSeen - 1;
       AbstractValue abstractValue = callSiteOptimizationInfo.getAbstractArgumentValue(argIndex);
       if (abstractValue.isSingleValue()) {
-        assert appView.options().enablePropagationOfConstantsAtCallSites;
+        assert options.isConstantPropagationEnabled();
         SingleValue singleValue = abstractValue.asSingleValue();
         if (singleValue.isMaterializableInContext(appView, code.context())) {
           Instruction replacement =
@@ -346,4 +436,15 @@
     // Run IRConverter#optimize.
     return null;
   }
+
+  private boolean verifyAllProgramDispatchTargetsHaveBeenAbandoned(
+      InvokeMethod invoke, ProgramMethod context) {
+    ProgramMethodSet targets = invoke.lookupProgramDispatchTargets(appView, context);
+    if (targets != null) {
+      for (ProgramMethod target : targets) {
+        assert target.getDefinition().getCallSiteOptimizationInfo().isAbandoned();
+      }
+    }
+    return true;
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/ClassInitializerDefaultsOptimization.java b/src/main/java/com/android/tools/r8/ir/optimize/ClassInitializerDefaultsOptimization.java
index a7a24cc..e769dc9 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/ClassInitializerDefaultsOptimization.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/ClassInitializerDefaultsOptimization.java
@@ -376,7 +376,7 @@
             // Array stores do not impact our ability to move constants into the class definition,
             // as long as the instructions do not throw.
             ArrayPut arrayPut = instruction.asArrayPut();
-            if (arrayPut.instructionInstanceCanThrow(appView, context).isThrowing()) {
+            if (arrayPut.instructionInstanceCanThrow(appView, context)) {
               return validateFinalFieldPuts(finalFieldPuts, isWrittenBefore);
             }
           } else if (instruction.isStaticGet()) {
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 22a211b..52844be 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
@@ -7,12 +7,12 @@
 import static com.android.tools.r8.graph.DexProgramClass.asProgramClassOrNull;
 import static com.android.tools.r8.ir.analysis.type.Nullability.definitelyNotNull;
 import static com.android.tools.r8.ir.analysis.type.Nullability.maybeNull;
-import static com.android.tools.r8.optimize.MemberRebindingAnalysis.isTypeVisibleFromContext;
 import static com.google.common.base.Predicates.alwaysTrue;
 
 import com.android.tools.r8.dex.Constants;
 import com.android.tools.r8.errors.CompilationError;
 import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.graph.AccessControl;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DebugLocalInfo;
 import com.android.tools.r8.graph.DexClass;
@@ -1387,12 +1387,18 @@
     Value inValue = checkCast.object();
     Value outValue = checkCast.outValue();
     DexType castType = checkCast.getType();
+    DexType baseCastType = castType.toBaseType(dexItemFactory);
 
     // If the cast type is not accessible in the current context, we should not remove the cast
-    // in order to preserve IllegalAccessError. Note that JVM and ART behave differently: see
+    // in order to preserve runtime errors. Note that JVM and ART behave differently: see
     // {@link com.android.tools.r8.ir.optimize.checkcast.IllegalAccessErrorTest}.
-    if (!isTypeVisibleFromContext(appView, code.context(), castType)) {
-      return RemoveCheckCastInstructionIfTrivialResult.NO_REMOVALS;
+    if (baseCastType.isClassType()) {
+      DexClass baseCastClass = appView.definitionFor(baseCastType);
+      if (baseCastClass == null
+          || AccessControl.isClassAccessible(baseCastClass, code.context(), appView)
+              .isPossiblyFalse()) {
+        return RemoveCheckCastInstructionIfTrivialResult.NO_REMOVALS;
+      }
     }
 
     // If the in-value is `null` and the cast-type is a float-array type, then trivial check-cast
@@ -1446,10 +1452,17 @@
   // Returns true if the given instance-of instruction was removed.
   private boolean removeInstanceOfInstructionIfTrivial(
       InstanceOf instanceOf, InstructionListIterator it, IRCode code) {
+    ProgramMethod context = code.context();
+
     // If the instance-of type is not accessible in the current context, we should not remove the
     // instance-of instruction in order to preserve IllegalAccessError.
-    if (!isTypeVisibleFromContext(appView, code.context(), instanceOf.type())) {
-      return false;
+    DexType instanceOfBaseType = instanceOf.type().toBaseType(dexItemFactory);
+    if (instanceOfBaseType.isClassType()) {
+      DexClass instanceOfClass = appView.definitionFor(instanceOfBaseType);
+      if (instanceOfClass == null
+          || AccessControl.isClassAccessible(instanceOfClass, context, appView).isPossiblyFalse()) {
+        return false;
+      }
     }
 
     Value inValue = instanceOf.value();
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/IdempotentFunctionCallCanonicalizer.java b/src/main/java/com/android/tools/r8/ir/optimize/IdempotentFunctionCallCanonicalizer.java
index b1af390..abf12f7 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/IdempotentFunctionCallCanonicalizer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/IdempotentFunctionCallCanonicalizer.java
@@ -3,13 +3,12 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.ir.optimize;
 
-import static com.android.tools.r8.optimize.MemberRebindingAnalysis.isMemberVisibleFromOriginalContext;
-
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.graph.ResolutionResult.SingleResolutionResult;
 import com.android.tools.r8.ir.code.BasicBlock;
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.Instruction;
@@ -19,6 +18,7 @@
 import com.android.tools.r8.ir.code.Position;
 import com.android.tools.r8.ir.code.Value;
 import com.android.tools.r8.logging.Log;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.utils.StringUtils;
 import com.google.common.collect.Maps;
 import it.unimi.dsi.fastutil.Hash.Strategy;
@@ -132,20 +132,35 @@
             // Give up in D8
             continue;
           }
+
           assert appView.appInfo().hasLiveness();
+          AppView<AppInfoWithLiveness> appViewWithLiveness = appView.withLiveness();
+          AppInfoWithLiveness appInfoWithLiveness = appViewWithLiveness.appInfo();
+
+          SingleResolutionResult resolutionResult =
+              appInfoWithLiveness
+                  .resolveMethod(invoke.getInvokedMethod(), invoke.isInvokeInterface())
+                  .asSingleResolution();
+          if (resolutionResult == null
+              || resolutionResult
+                  .isAccessibleFrom(context, appInfoWithLiveness)
+                  .isPossiblyFalse()) {
+            continue;
+          }
+
           // Check if the call has a single target; that target is side effect free; and
           // that target's output depends only on arguments.
-          DexEncodedMethod target = invoke.lookupSingleTarget(appView.withLiveness(), context);
+          // TODO(b/156853206): This should either (i) use the resolution result from above, (ii)
+          //  return the resolution result such that the call site can perform the accessibility
+          //  check, or (iii) always perform the accessibility check such that it can be skipped
+          //  at the call site.
+          DexEncodedMethod target = invoke.lookupSingleTarget(appViewWithLiveness, context);
           if (target == null
               || target.getOptimizationInfo().mayHaveSideEffects()
               || !target.getOptimizationInfo().returnValueOnlyDependsOnArguments()) {
             continue;
           }
-          // Verify that the target method is accessible in the current context.
-          if (!isMemberVisibleFromOriginalContext(
-              appView, context, target.holder(), target.accessFlags)) {
-            continue;
-          }
+
           // Check if the call could throw a NPE as a result of the receiver being null.
           if (current.isInvokeMethodWithReceiver()) {
             Value receiver = current.asInvokeMethodWithReceiver().getReceiver().getAliasedValue();
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 c17efb4..fe6cbb4 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
@@ -1143,8 +1143,7 @@
     boolean skip =
         !(options.enableDynamicTypeOptimization
             || options.enableNonNullTracking
-            || options.enableValuePropagation
-            || options.testing.forceAssumeNoneInsertion);
+            || options.enableValuePropagation);
     if (skip) {
       return;
     }
@@ -1159,12 +1158,6 @@
       applyMemberValuePropagationToInlinee(code, blockIterator, block, inlineeBlocks);
     }
 
-    // Introduce aliases only to the inlinee blocks.
-    if (options.testing.forceAssumeNoneInsertion) {
-      applyAssumerToInlinee(
-          new AliasIntroducer(appView), code, blockIterator, block, inlineeBlocks, timing);
-    }
-
     // Add non-null IRs only to the inlinee blocks.
     if (options.enableNonNullTracking) {
       Assumer nonNullTracker = new NonNullTracker(appView);
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 59f5772..e08aeae 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
@@ -3,7 +3,6 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.ir.optimize;
 
-import static com.android.tools.r8.ir.code.DominatorTree.Assumption.MAY_HAVE_UNREACHABLE_BLOCKS;
 import static com.google.common.base.Predicates.alwaysTrue;
 
 import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
@@ -11,7 +10,6 @@
 import com.android.tools.r8.graph.DexEncodedField;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexField;
-import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.ir.analysis.type.TypeAnalysis;
 import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.code.Assume;
@@ -26,38 +24,36 @@
 import com.android.tools.r8.ir.code.InstructionIterator;
 import com.android.tools.r8.ir.code.InstructionListIterator;
 import com.android.tools.r8.ir.code.InvokeMethod;
+import com.android.tools.r8.ir.code.LazyDominatorTree;
 import com.android.tools.r8.ir.code.Phi;
 import com.android.tools.r8.ir.code.Value;
 import com.android.tools.r8.ir.optimize.info.FieldOptimizationInfo;
 import com.android.tools.r8.ir.optimize.info.MethodOptimizationInfo;
 import com.android.tools.r8.utils.Timing;
+import com.android.tools.r8.utils.TriConsumer;
 import com.google.common.collect.Sets;
 import it.unimi.dsi.fastutil.ints.IntArrayList;
 import it.unimi.dsi.fastutil.ints.IntList;
+import it.unimi.dsi.fastutil.ints.IntListIterator;
+import java.util.ArrayList;
 import java.util.BitSet;
 import java.util.IdentityHashMap;
 import java.util.Iterator;
+import java.util.LinkedHashMap;
 import java.util.List;
-import java.util.ListIterator;
 import java.util.Map;
+import java.util.Map.Entry;
 import java.util.Set;
-import java.util.function.Consumer;
+import java.util.function.BiFunction;
+import java.util.function.BiPredicate;
 import java.util.function.Predicate;
 
 public class NonNullTracker implements Assumer {
 
   private final AppView<? extends AppInfoWithClassHierarchy> appView;
-  private final Consumer<BasicBlock> splitBlockConsumer;
 
   public NonNullTracker(AppView<? extends AppInfoWithClassHierarchy> appView) {
-    this(appView, null);
-  }
-
-  public NonNullTracker(
-      AppView<? extends AppInfoWithClassHierarchy> appView,
-      Consumer<BasicBlock> splitBlockConsumer) {
     this.appView = appView;
-    this.splitBlockConsumer = splitBlockConsumer;
   }
 
   @Override
@@ -81,277 +77,357 @@
       BasicBlockIterator blockIterator,
       Predicate<BasicBlock> blockTester,
       Timing timing) {
-    Set<Value> affectedValues = Sets.newIdentityHashSet();
-    Set<Value> knownToBeNonNullValues = Sets.newIdentityHashSet();
+    timing.begin("Part 1: Compute non null values");
+    NonNullValues nonNullValues = computeNonNullValues(code, blockIterator, blockTester);
+    timing.end();
+    if (nonNullValues.isEmpty()) {
+      return;
+    }
+
+    timing.begin("Part 2: Remove redundant assume instructions");
+    removeRedundantAssumeInstructions(nonNullValues);
+    timing.end();
+
+    timing.begin("Part 3: Compute dominated users");
+    Map<Instruction, Set<Value>> redundantKeys =
+        computeDominanceForNonNullValues(code, nonNullValues);
+    timing.end();
+    if (nonNullValues.isEmpty()) {
+      return;
+    }
+
+    timing.begin("Part 4: Remove redundant dominated assume instructions");
+    removeRedundantDominatedAssumeInstructions(nonNullValues, redundantKeys);
+    timing.end();
+    if (nonNullValues.isEmpty()) {
+      return;
+    }
+
+    timing.begin("Part 5: Materialize assume instructions");
+    materializeAssumeInstructions(code, nonNullValues);
+    timing.end();
+  }
+
+  private NonNullValues computeNonNullValues(
+      IRCode code, BasicBlockIterator blockIterator, Predicate<BasicBlock> blockTester) {
+    NonNullValues.Builder nonNullValuesBuilder = new NonNullValues.Builder();
     while (blockIterator.hasNext()) {
       BasicBlock block = blockIterator.next();
-      if (!blockTester.test(block)) {
-        continue;
+      if (blockTester.test(block)) {
+        computeNonNullValuesInBlock(code, blockIterator, block, nonNullValuesBuilder);
       }
-      // Add non-null after
-      // 1) instructions that implicitly indicate receiver/array is not null.
-      // 2) invocations that are guaranteed to return a non-null value.
-      // 3) parameters that are not null after the invocation.
-      // 4) field-get instructions that are guaranteed to read a non-null value.
-      InstructionListIterator iterator = block.listIterator(code);
-      while (iterator.hasNext()) {
-        Instruction current = iterator.next();
-        Value outValue = current.outValue();
+    }
+    return nonNullValuesBuilder.build();
+  }
 
-        // Case (1), instructions that implicitly indicate receiver/array is not null.
-        if (current.throwsOnNullInput()) {
-          Value couldBeNonNull = current.getNonNullInput();
-          if (isNullableReferenceTypeWithUsers(couldBeNonNull)) {
-            knownToBeNonNullValues.add(couldBeNonNull);
-          }
-        }
+  private void computeNonNullValuesInBlock(
+      IRCode code,
+      BasicBlockIterator blockIterator,
+      BasicBlock block,
+      NonNullValues.Builder nonNullValuesBuilder) {
+    // Add non-null after
+    // 1) instructions that implicitly indicate receiver/array is not null.
+    // 2) invocations that are guaranteed to return a non-null value.
+    // 3) parameters that are not null after the invocation.
+    // 4) field-get instructions that are guaranteed to read a non-null value.
+    InstructionListIterator instructionIterator = block.listIterator(code);
+    while (instructionIterator.hasNext()) {
+      Instruction current = instructionIterator.next();
+      boolean needsAssumeInstruction = false;
 
-        if (current.isInvokeMethod()) {
-          InvokeMethod invoke = current.asInvokeMethod();
-          DexMethod invokedMethod = invoke.getInvokedMethod();
-
-          DexEncodedMethod singleTarget = invoke.lookupSingleTarget(appView, code.context());
-          if (singleTarget != null) {
-            MethodOptimizationInfo optimizationInfo = singleTarget.getOptimizationInfo();
-
-            // Case (2), invocations that are guaranteed to return a non-null value.
-            if (optimizationInfo.neverReturnsNull()) {
-              if (invoke.hasOutValue() && isNullableReferenceTypeWithUsers(outValue)) {
-                knownToBeNonNullValues.add(outValue);
-              }
-            }
-
-            // Case (3), parameters that are not null after the invocation.
-            BitSet nonNullParamOnNormalExits = optimizationInfo.getNonNullParamOnNormalExits();
-            if (nonNullParamOnNormalExits != null) {
-              for (int i = 0; i < current.inValues().size(); i++) {
-                if (nonNullParamOnNormalExits.get(i)) {
-                  Value knownToBeNonNullValue = current.inValues().get(i);
-                  if (isNullableReferenceTypeWithUsers(knownToBeNonNullValue)) {
-                    knownToBeNonNullValues.add(knownToBeNonNullValue);
-                  }
-                }
-              }
-            }
-          }
-        } else if (current.isFieldGet()) {
-          // Case (4), field-get instructions that are guaranteed to read a non-null value.
-          FieldInstruction fieldInstruction = current.asFieldInstruction();
-          DexField field = fieldInstruction.getField();
-          if (field.type.isReferenceType() && isNullableReferenceTypeWithUsers(outValue)) {
-            DexEncodedField encodedField = appView.appInfo().resolveField(field).getResolvedField();
-            if (encodedField != null) {
-              FieldOptimizationInfo optimizationInfo = encodedField.getOptimizationInfo();
-              if (optimizationInfo.getDynamicUpperBoundType() != null
-                  && optimizationInfo.getDynamicUpperBoundType().isDefinitelyNotNull()) {
-                knownToBeNonNullValues.add(outValue);
-              }
-            }
-          }
-        }
-
-        // This is to ensure that we do not add redundant non-null instructions.
-        // Otherwise, we will have something like:
-        //   y <- assume-not-null(x)
-        //   ...
-        //   z <- assume-not-null(y)
-        assert knownToBeNonNullValues.stream()
-            .allMatch(NonNullTracker::isNullableReferenceTypeWithUsers);
-
-        if (!knownToBeNonNullValues.isEmpty()) {
-          addNonNullForValues(
-              code,
-              blockIterator,
-              block,
-              iterator,
-              current,
-              knownToBeNonNullValues,
-              affectedValues);
-          knownToBeNonNullValues.clear();
+      // Case (1), instructions that implicitly indicate receiver/array is not null.
+      if (current.throwsOnNullInput()) {
+        Value inValue = current.getNonNullInput();
+        if (nonNullValuesBuilder.isMaybeNull(inValue)
+            && isNullableReferenceTypeWithOtherNonDebugUsers(inValue, current)) {
+          nonNullValuesBuilder.addNonNullValueWithUnknownDominance(current, inValue);
+          needsAssumeInstruction = true;
         }
       }
 
-      // Add non-null on top of the successor block if the current block ends with a null check.
-      if (block.exit().isIf() && block.exit().asIf().isZeroTest()) {
-        // if v EQ blockX
-        // ... (fallthrough)
-        // blockX: ...
-        //
-        //   ~>
-        //
-        // if v EQ blockX
-        // non_null_value <- non-null(v)
-        // ...
-        // blockX: ...
-        //
-        // or
-        //
-        // if v NE blockY
-        // ...
-        // blockY: ...
-        //
-        //   ~>
-        //
-        // blockY: non_null_value <- non-null(v)
-        // ...
-        If theIf = block.exit().asIf();
-        Value knownToBeNonNullValue = theIf.inValues().get(0);
-        // Avoid adding redundant non-null instruction.
-        if (isNullableReferenceTypeWithUsers(knownToBeNonNullValue)) {
-          BasicBlock target = theIf.targetFromNonNullObject();
-          // Ignore uncommon empty blocks.
-          if (!target.isEmpty()) {
-            DominatorTree dominatorTree = new DominatorTree(code, MAY_HAVE_UNREACHABLE_BLOCKS);
-            // Make sure there are no paths to the target block without passing the current block.
-            if (dominatorTree.dominatedBy(target, block)) {
-              // Collect users of the original value that are dominated by the target block.
-              Set<Instruction> dominatedUsers = Sets.newIdentityHashSet();
-              Map<Phi, IntList> dominatedPhiUsersWithPositions = new IdentityHashMap<>();
-              Set<BasicBlock> dominatedBlocks =
-                  Sets.newHashSet(dominatorTree.dominatedBlocks(target));
-              for (Instruction user : knownToBeNonNullValue.uniqueUsers()) {
-                if (dominatedBlocks.contains(user.getBlock())) {
-                  dominatedUsers.add(user);
-                }
-              }
-              for (Phi user : knownToBeNonNullValue.uniquePhiUsers()) {
-                IntList dominatedPredecessorIndexes = findDominatedPredecessorIndexesInPhi(
-                    user, knownToBeNonNullValue, dominatedBlocks);
-                if (!dominatedPredecessorIndexes.isEmpty()) {
-                  dominatedPhiUsersWithPositions.put(user, dominatedPredecessorIndexes);
-                }
-              }
-              // Avoid adding a non-null for the value without meaningful users.
-              if (knownToBeNonNullValue.isArgument()
-                  || !dominatedUsers.isEmpty()
-                  || !dominatedPhiUsersWithPositions.isEmpty()) {
-                TypeElement typeLattice = knownToBeNonNullValue.getType();
-                Value nonNullValue =
-                    code.createValue(
-                        typeLattice.asReferenceType().asMeetWithNotNull(),
-                        knownToBeNonNullValue.getLocalInfo());
-                affectedValues.addAll(knownToBeNonNullValue.affectedValues());
-                Assume<NonNullAssumption> nonNull =
-                    Assume.createAssumeNonNullInstruction(
-                        nonNullValue, knownToBeNonNullValue, theIf, appView);
-                InstructionListIterator targetIterator = target.listIterator(code);
-                nonNull.setPosition(targetIterator.next().getPosition());
-                targetIterator.previous();
-                targetIterator.add(nonNull);
-                knownToBeNonNullValue.replaceSelectiveUsers(
-                    nonNullValue, dominatedUsers, dominatedPhiUsersWithPositions);
-              }
+      Value outValue = current.outValue();
+      if (current.isInvokeMethod()) {
+        InvokeMethod invoke = current.asInvokeMethod();
+        if (invoke.hasOutValue() || !invoke.getInvokedMethod().proto.parameters.isEmpty()) {
+          // Case (2) and (3).
+          needsAssumeInstruction |=
+              computeNonNullValuesFromSingleTarget(code, invoke, nonNullValuesBuilder);
+        }
+      } else if (current.isFieldGet()) {
+        // Case (4), field-get instructions that are guaranteed to read a non-null value.
+        FieldInstruction fieldInstruction = current.asFieldInstruction();
+        DexField field = fieldInstruction.getField();
+        if (isNullableReferenceTypeWithNonDebugUsers(outValue)) {
+          DexEncodedField encodedField = appView.appInfo().resolveField(field).getResolvedField();
+          if (encodedField != null) {
+            FieldOptimizationInfo optimizationInfo = encodedField.getOptimizationInfo();
+            if (optimizationInfo.getDynamicUpperBoundType() != null
+                && optimizationInfo.getDynamicUpperBoundType().isDefinitelyNotNull()) {
+              nonNullValuesBuilder.addNonNullValueKnownToDominateAllUsers(current, outValue);
+              needsAssumeInstruction = true;
             }
           }
         }
       }
+
+      // If we need to insert an assume instruction into a block with catch handlers, we split the
+      // block such that the IR is ready for the insertion of the assume instruction.
+      //
+      // This splitting could in principle be deferred until we materialize the assume instructions,
+      // but then we would need to rewind the basic block iterator to the beginning, and scan over
+      // the instructions another time, splitting the blocks as needed.
+      if (block.hasCatchHandlers()) {
+        if (needsAssumeInstruction) {
+          BasicBlock insertionBlock = instructionIterator.split(code, blockIterator);
+          assert !instructionIterator.hasNext();
+          assert instructionIterator.peekPrevious().isGoto();
+          assert blockIterator.peekPrevious() == insertionBlock;
+          computeNonNullValuesInBlock(code, blockIterator, insertionBlock, nonNullValuesBuilder);
+          return;
+        }
+        if (current.instructionTypeCanThrow()) {
+          break;
+        }
+      }
+    }
+
+    If ifInstruction = block.exit().asIf();
+    if (ifInstruction != null && ifInstruction.isNonTrivialNullTest()) {
+      Value lhs = ifInstruction.lhs();
+      if (nonNullValuesBuilder.isMaybeNull(lhs)
+          && isNullableReferenceTypeWithOtherNonDebugUsers(lhs, ifInstruction)
+          && ifInstruction.targetFromNonNullObject().getPredecessors().size() == 1) {
+        nonNullValuesBuilder.addNonNullValueWithUnknownDominance(ifInstruction, lhs);
+      }
+    }
+  }
+
+  private boolean computeNonNullValuesFromSingleTarget(
+      IRCode code, InvokeMethod invoke, NonNullValues.Builder nonNullValuesBuilder) {
+    DexEncodedMethod singleTarget = invoke.lookupSingleTarget(appView, code.context());
+    if (singleTarget == null) {
+      return false;
+    }
+
+    boolean needsAssumeInstruction = false;
+    MethodOptimizationInfo optimizationInfo = singleTarget.getOptimizationInfo();
+
+    // Case (2), invocations that are guaranteed to return a non-null value.
+    Value outValue = invoke.outValue();
+    if (outValue != null
+        && optimizationInfo.neverReturnsNull()
+        && isNullableReferenceTypeWithNonDebugUsers(outValue)) {
+      nonNullValuesBuilder.addNonNullValueKnownToDominateAllUsers(invoke, outValue);
+      needsAssumeInstruction = true;
+    }
+
+    // Case (3), parameters that are not null after the invocation.
+    BitSet nonNullParamOnNormalExits = optimizationInfo.getNonNullParamOnNormalExits();
+    if (nonNullParamOnNormalExits != null) {
+      int start = invoke.isInvokeMethodWithReceiver() ? 1 : 0;
+      for (int i = start; i < invoke.arguments().size(); i++) {
+        if (nonNullParamOnNormalExits.get(i)) {
+          Value argument = invoke.getArgument(i);
+          if (nonNullValuesBuilder.isMaybeNull(argument)
+              && isNullableReferenceTypeWithOtherNonDebugUsers(argument, invoke)) {
+            nonNullValuesBuilder.addNonNullValueWithUnknownDominance(invoke, argument);
+            needsAssumeInstruction = true;
+          }
+        }
+      }
     }
+    return needsAssumeInstruction;
+  }
+
+  private void removeRedundantAssumeInstructions(NonNullValues nonNullValues) {
+    nonNullValues.removeIf(
+        (instruction, nonNullValue) -> {
+          if (nonNullValue.isPhi()) {
+            return false;
+          }
+          Instruction definition = nonNullValue.definition;
+          return definition != instruction && nonNullValues.contains(definition, nonNullValue);
+        });
+  }
+
+  private Map<Instruction, Set<Value>> computeDominanceForNonNullValues(
+      IRCode code, NonNullValues nonNullValues) {
+    Map<Instruction, Set<Value>> redundantKeys = new IdentityHashMap<>();
+    LazyDominatorTree lazyDominatorTree = new LazyDominatorTree(code);
+    Map<BasicBlock, Set<BasicBlock>> dominatedBlocksCache = new IdentityHashMap<>();
+    nonNullValues.computeDominance(
+        (instruction, nonNullValue) -> {
+          Set<Value> alreadyNonNullValues = redundantKeys.get(instruction);
+          if (alreadyNonNullValues != null && alreadyNonNullValues.contains(nonNullValue)) {
+            // Returning redundant() will cause the entry (instruction, nonNullValue) to be removed.
+            return NonNullDominance.redundant();
+          }
+
+          // If this value is non-null since its definition, then it is known to dominate all users.
+          if (nonNullValue == instruction.outValue()) {
+            return NonNullDominance.everything();
+          }
+
+          // If we learn that this value is known to be non-null in the same block as it is defined,
+          // and it is not used between its definition and the instruction that performs the null
+          // check, then the non-null-value is known to dominate all other users than the null check
+          // itself.
+          BasicBlock block = instruction.getBlock();
+          if (nonNullValue.getBlock() == block
+              && block.exit().isGoto()
+              && !instruction.getBlock().hasCatchHandlers()) {
+            InstructionIterator iterator = instruction.getBlock().iterator();
+            if (!nonNullValue.isPhi()) {
+              iterator.nextUntil(x -> x != nonNullValue.definition);
+              iterator.previous();
+            }
+            boolean isUsedBeforeInstruction = false;
+            while (iterator.hasNext()) {
+              Instruction current = iterator.next();
+              if (current == instruction) {
+                break;
+              }
+              if (current.inValues().contains(nonNullValue)
+                  || current.getDebugValues().contains(nonNullValue)) {
+                isUsedBeforeInstruction = true;
+                break;
+              }
+            }
+            if (!isUsedBeforeInstruction) {
+              return NonNullDominance.everythingElse();
+            }
+          }
+
+          // Otherwise, we need a dominator tree to determine which users are dominated.
+          BasicBlock insertionBlock = getInsertionBlock(instruction);
+
+          assert nonNullValue.hasPhiUsers()
+              || nonNullValue.uniqueUsers().stream().anyMatch(user -> user != instruction)
+              || nonNullValue.isArgument();
+
+          // Find all users of the original value that are dominated by either the current block
+          // or the new split-off block. Since NPE can be explicitly caught, nullness should be
+          // propagated through dominance.
+          DominatorTree dominatorTree = lazyDominatorTree.get();
+          Set<BasicBlock> dominatedBlocks =
+              dominatedBlocksCache.computeIfAbsent(
+                  insertionBlock, x -> dominatorTree.dominatedBlocks(x, Sets.newIdentityHashSet()));
+
+          NonNullDominance.Builder dominance = NonNullDominance.builder(nonNullValue);
+          for (Instruction user : nonNullValue.uniqueUsers()) {
+            if (user != instruction && dominatedBlocks.contains(user.getBlock())) {
+              if (user.getBlock() == insertionBlock && insertionBlock == block) {
+                Instruction first = block.iterator().nextUntil(x -> x == instruction || x == user);
+                assert first != null;
+                if (first == user) {
+                  continue;
+                }
+              }
+              dominance.addDominatedUser(user);
+
+              // Record that there is no need to insert an assume instruction for the non-null-value
+              // after the given user in case the user is also a null check for the non-null-value.
+              redundantKeys
+                  .computeIfAbsent(user, ignore -> Sets.newIdentityHashSet())
+                  .add(nonNullValue);
+            }
+          }
+          for (Phi user : nonNullValue.uniquePhiUsers()) {
+            IntList dominatedPredecessorIndices =
+                findDominatedPredecessorIndexesInPhi(user, nonNullValue, dominatedBlocks);
+            if (!dominatedPredecessorIndices.isEmpty()) {
+              dominance.addDominatedPhiUser(user, dominatedPredecessorIndices);
+            }
+          }
+          return dominance.build();
+        });
+    return redundantKeys;
+  }
+
+  private void removeRedundantDominatedAssumeInstructions(
+      NonNullValues nonNullValues, Map<Instruction, Set<Value>> redundantKeys) {
+    nonNullValues.removeAll(redundantKeys);
+  }
+
+  private void materializeAssumeInstructions(IRCode code, NonNullValues nonNullValues) {
+    Set<Value> affectedValues = Sets.newIdentityHashSet();
+    Map<BasicBlock, Map<Instruction, List<Instruction>>> pendingInsertions =
+        new IdentityHashMap<>();
+    nonNullValues.forEach(
+        (instruction, nonNullValue, dominance) -> {
+          BasicBlock block = instruction.getBlock();
+          BasicBlock insertionBlock = getInsertionBlock(instruction);
+
+          Value newValue =
+              code.createValue(
+                  nonNullValue.getType().asReferenceType().asMeetWithNotNull(),
+                  nonNullValue.getLocalInfo());
+          if (dominance.isEverything()) {
+            nonNullValue.replaceUsers(newValue);
+          } else if (dominance.isEverythingElse()) {
+            nonNullValue.replaceSelectiveInstructionUsers(newValue, user -> user != instruction);
+            nonNullValue.replacePhiUsers(newValue);
+          } else if (dominance.isSomething()) {
+            SomethingNonNullDominance somethingDominance = dominance.asSomething();
+            somethingDominance
+                .getDominatedPhiUsers()
+                .forEach(
+                    (user, indices) -> {
+                      IntListIterator iterator = indices.iterator();
+                      while (iterator.hasNext()) {
+                        Value operand = user.getOperand(iterator.nextInt());
+                        if (operand != nonNullValue) {
+                          assert operand.isDefinedByInstructionSatisfying(
+                              Instruction::isAssumeNonNull);
+                          iterator.remove();
+                        }
+                      }
+                    });
+            nonNullValue.replaceSelectiveUsers(
+                newValue,
+                somethingDominance.getDominatedUsers(),
+                somethingDominance.getDominatedPhiUsers());
+          }
+          affectedValues.addAll(newValue.affectedValues());
+
+          Assume<NonNullAssumption> assumeInstruction =
+              Assume.createAssumeNonNullInstruction(newValue, nonNullValue, instruction, appView);
+          assumeInstruction.setPosition(instruction.getPosition());
+          if (insertionBlock != block) {
+            insertionBlock.listIterator(code).add(assumeInstruction);
+          } else {
+            pendingInsertions
+                .computeIfAbsent(block, ignore -> new IdentityHashMap<>())
+                .computeIfAbsent(instruction, ignore -> new ArrayList<>())
+                .add(assumeInstruction);
+          }
+        });
+    pendingInsertions.forEach(
+        (block, pendingInsertionsPerInstruction) -> {
+          InstructionListIterator instructionIterator = block.listIterator(code);
+          while (instructionIterator.hasNext() && !pendingInsertionsPerInstruction.isEmpty()) {
+            Instruction instruction = instructionIterator.next();
+            List<Instruction> pendingAssumeInstructions =
+                pendingInsertionsPerInstruction.remove(instruction);
+            if (pendingAssumeInstructions != null) {
+              pendingAssumeInstructions.forEach(instructionIterator::add);
+            }
+          }
+        });
     if (!affectedValues.isEmpty()) {
       new TypeAnalysis(appView).narrowing(affectedValues);
     }
   }
 
-  private void addNonNullForValues(
-      IRCode code,
-      ListIterator<BasicBlock> blockIterator,
-      BasicBlock block,
-      InstructionListIterator iterator,
-      Instruction current,
-      Set<Value> knownToBeNonNullValues,
-      Set<Value> affectedValues) {
-    // First, if the current block has catch handler, split into two blocks, e.g.,
-    //
-    // ...x
-    // invoke(rcv, ...)
-    // ...y
-    //
-    //   ~>
-    //
-    // ...x
-    // invoke(rcv, ...)
-    // goto A
-    //
-    // A: ...y // blockWithNonNullInstruction
-    boolean split = block.hasCatchHandlers();
-    BasicBlock blockWithNonNullInstruction;
-    if (split) {
-      blockWithNonNullInstruction = iterator.split(code, blockIterator);
-      if (splitBlockConsumer != null) {
-        splitBlockConsumer.accept(blockWithNonNullInstruction);
-      }
-    } else {
-      blockWithNonNullInstruction = block;
+  private BasicBlock getInsertionBlock(Instruction instruction) {
+    if (instruction.isIf()) {
+      return instruction.asIf().targetFromNonNullObject();
     }
-
-    DominatorTree dominatorTree = new DominatorTree(code, MAY_HAVE_UNREACHABLE_BLOCKS);
-    for (Value knownToBeNonNullValue : knownToBeNonNullValues) {
-      // Find all users of the original value that are dominated by either the current block
-      // or the new split-off block. Since NPE can be explicitly caught, nullness should be
-      // propagated through dominance.
-      Set<Instruction> users = knownToBeNonNullValue.uniqueUsers();
-      Set<Instruction> dominatedUsers = Sets.newIdentityHashSet();
-      Map<Phi, IntList> dominatedPhiUsersWithPositions = new IdentityHashMap<>();
-      Set<BasicBlock> dominatedBlocks = Sets.newIdentityHashSet();
-      for (BasicBlock dominatee : dominatorTree.dominatedBlocks(blockWithNonNullInstruction)) {
-        dominatedBlocks.add(dominatee);
-        InstructionIterator dominateeIterator = dominatee.iterator();
-        if (dominatee == blockWithNonNullInstruction && !split) {
-          // In the block where the non null instruction will be inserted, skip instructions up to
-          // and including the insertion point.
-          dominateeIterator.nextUntil(instruction -> instruction == current);
-        }
-        while (dominateeIterator.hasNext()) {
-          Instruction potentialUser = dominateeIterator.next();
-          if (users.contains(potentialUser)) {
-            dominatedUsers.add(potentialUser);
-          }
-        }
-      }
-      for (Phi user : knownToBeNonNullValue.uniquePhiUsers()) {
-        IntList dominatedPredecessorIndexes =
-            findDominatedPredecessorIndexesInPhi(user, knownToBeNonNullValue, dominatedBlocks);
-        if (!dominatedPredecessorIndexes.isEmpty()) {
-          dominatedPhiUsersWithPositions.put(user, dominatedPredecessorIndexes);
-        }
-      }
-
-      // Only insert non-null instruction if it is ever used.
-      // Exception: if it is an argument, non-null IR can be used to compute non-null parameter.
-      if (knownToBeNonNullValue.isArgument()
-          || !dominatedUsers.isEmpty()
-          || !dominatedPhiUsersWithPositions.isEmpty()) {
-        // Add non-null fake IR, e.g.,
-        // ...x
-        // invoke(rcv, ...)
-        // goto A
-        // ...
-        // A: non_null_rcv <- non-null(rcv)
-        // ...y
-        TypeElement typeLattice = knownToBeNonNullValue.getType();
-        assert typeLattice.isReferenceType();
-        Value nonNullValue =
-            code.createValue(
-                typeLattice.asReferenceType().asMeetWithNotNull(),
-                knownToBeNonNullValue.getLocalInfo());
-        affectedValues.addAll(knownToBeNonNullValue.affectedValues());
-        Assume<NonNullAssumption> nonNull =
-            Assume.createAssumeNonNullInstruction(
-                nonNullValue, knownToBeNonNullValue, current, appView);
-        nonNull.setPosition(current.getPosition());
-        if (blockWithNonNullInstruction != block) {
-          // If we split, add non-null IR on top of the new split block.
-          blockWithNonNullInstruction.listIterator(code).add(nonNull);
-        } else {
-          // Otherwise, just add it to the current block at the position of the iterator.
-          iterator.add(nonNull);
-        }
-
-        // Replace all users of the original value that are dominated by either the current block
-        // or the new split-off block.
-        knownToBeNonNullValue.replaceSelectiveUsers(
-            nonNullValue, dominatedUsers, dominatedPhiUsersWithPositions);
-      }
+    BasicBlock block = instruction.getBlock();
+    if (block.hasCatchHandlers()) {
+      return block.exit().asGoto().getTarget();
     }
+    return block;
   }
 
   private IntList findDominatedPredecessorIndexesInPhi(
@@ -379,10 +455,346 @@
     return predecessorIndexes;
   }
 
-  private static boolean isNullableReferenceTypeWithUsers(Value value) {
+  private static boolean isNullableReferenceType(Value value) {
     TypeElement type = value.getType();
-    return type.isReferenceType()
-        && type.asReferenceType().isNullable()
-        && value.numberOfAllUsers() > 0;
+    return type.isReferenceType() && type.asReferenceType().isNullable();
+  }
+
+  private static boolean isNullableReferenceTypeWithNonDebugUsers(Value value) {
+    return isNullableReferenceType(value) && value.numberOfAllNonDebugUsers() > 0;
+  }
+
+  private static boolean isNullableReferenceTypeWithOtherNonDebugUsers(
+      Value value, Instruction ignore) {
+    if (isNullableReferenceType(value)) {
+      if (value.hasPhiUsers()) {
+        return true;
+      }
+      for (Instruction user : value.uniqueUsers()) {
+        if (user != ignore) {
+          return true;
+        }
+      }
+    }
+    return false;
+  }
+
+  static class NonNullValues {
+
+    /**
+     * A mapping from each instruction to the (in and out) values that are guaranteed to be non-null
+     * by the instruction. Each non-null value is subsequently mapped to the set of users that it
+     * dominates.
+     */
+    Map<Instruction, Map<Value, NonNullDominance>> nonNullValues;
+
+    public NonNullValues(Map<Instruction, Map<Value, NonNullDominance>> nonNullValues) {
+      this.nonNullValues = nonNullValues;
+    }
+
+    public static Builder builder() {
+      return new Builder();
+    }
+
+    void computeDominance(BiFunction<Instruction, Value, NonNullDominance> function) {
+      Iterator<Entry<Instruction, Map<Value, NonNullDominance>>> outerIterator =
+          nonNullValues.entrySet().iterator();
+      while (outerIterator.hasNext()) {
+        Entry<Instruction, Map<Value, NonNullDominance>> outerEntry = outerIterator.next();
+        Instruction instruction = outerEntry.getKey();
+        Map<Value, NonNullDominance> dominancePerValue = outerEntry.getValue();
+        Iterator<Entry<Value, NonNullDominance>> innerIterator =
+            dominancePerValue.entrySet().iterator();
+        while (innerIterator.hasNext()) {
+          Entry<Value, NonNullDominance> innerEntry = innerIterator.next();
+          Value nonNullValue = innerEntry.getKey();
+          NonNullDominance dominance = innerEntry.getValue();
+          if (dominance.isEverything()) {
+            assert nonNullValue.isDefinedByInstructionSatisfying(
+                definition -> definition.outValue() == nonNullValue);
+            continue;
+          }
+          assert dominance.isUnknown();
+          dominance = function.apply(instruction, nonNullValue);
+          if ((dominance.isNothing() && !nonNullValue.isArgument()) || dominance.isUnknown()) {
+            innerIterator.remove();
+          } else {
+            innerEntry.setValue(dominance);
+          }
+        }
+        if (dominancePerValue.isEmpty()) {
+          outerIterator.remove();
+        }
+      }
+    }
+
+    boolean contains(Instruction instruction, Value nonNullValue) {
+      Map<Value, NonNullDominance> dominancePerValue = nonNullValues.get(instruction);
+      return dominancePerValue != null && dominancePerValue.containsKey(nonNullValue);
+    }
+
+    boolean isEmpty() {
+      return nonNullValues.isEmpty();
+    }
+
+    void forEach(TriConsumer<Instruction, Value, NonNullDominance> consumer) {
+      nonNullValues.forEach(
+          (instruction, dominancePerValue) ->
+              dominancePerValue.forEach(
+                  (nonNullValue, dominance) ->
+                      consumer.accept(instruction, nonNullValue, dominance)));
+    }
+
+    void removeAll(Map<Instruction, Set<Value>> keys) {
+      keys.forEach(
+          (instruction, values) -> {
+            Map<Value, NonNullDominance> dominancePerValue = nonNullValues.get(instruction);
+            if (dominancePerValue != null) {
+              values.forEach(dominancePerValue::remove);
+              if (dominancePerValue.isEmpty()) {
+                nonNullValues.remove(instruction);
+              }
+            }
+          });
+    }
+
+    void removeIf(BiPredicate<Instruction, Value> predicate) {
+      Iterator<Entry<Instruction, Map<Value, NonNullDominance>>> outerIterator =
+          nonNullValues.entrySet().iterator();
+      while (outerIterator.hasNext()) {
+        Entry<Instruction, Map<Value, NonNullDominance>> outerEntry = outerIterator.next();
+        Instruction instruction = outerEntry.getKey();
+        Map<Value, NonNullDominance> dominancePerValue = outerEntry.getValue();
+        Iterator<Entry<Value, NonNullDominance>> innerIterator =
+            dominancePerValue.entrySet().iterator();
+        while (innerIterator.hasNext()) {
+          Value nonNullValue = innerIterator.next().getKey();
+          if (predicate.test(instruction, nonNullValue)) {
+            innerIterator.remove();
+          }
+        }
+        if (dominancePerValue.isEmpty()) {
+          outerIterator.remove();
+        }
+      }
+    }
+
+    static class Builder {
+
+      private final Map<Instruction, Map<Value, NonNullDominance>> nonNullValues =
+          new LinkedHashMap<>();
+
+      // Used to avoid unnecessary block splitting during phase 1.
+      private final Set<Value> nonNullValuesKnownToDominateAllUsers = Sets.newIdentityHashSet();
+
+      private void add(Instruction instruction, Value nonNullValue, NonNullDominance dominance) {
+        nonNullValues
+            .computeIfAbsent(instruction, ignore -> new LinkedHashMap<>())
+            .put(nonNullValue, dominance);
+        if (dominance.isEverything()) {
+          nonNullValuesKnownToDominateAllUsers.add(nonNullValue);
+        }
+      }
+
+      void addNonNullValueKnownToDominateAllUsers(Instruction instruction, Value nonNullValue) {
+        add(instruction, nonNullValue, NonNullDominance.everything());
+      }
+
+      void addNonNullValueWithUnknownDominance(Instruction instruction, Value nonNullValue) {
+        add(instruction, nonNullValue, NonNullDominance.unknown());
+      }
+
+      public boolean isMaybeNull(Value value) {
+        return !nonNullValuesKnownToDominateAllUsers.contains(value);
+      }
+
+      public NonNullValues build() {
+        return new NonNullValues(nonNullValues);
+      }
+    }
+  }
+
+  abstract static class NonNullDominance {
+
+    boolean isEverything() {
+      return false;
+    }
+
+    boolean isEverythingElse() {
+      return false;
+    }
+
+    boolean isNothing() {
+      return false;
+    }
+
+    boolean isSomething() {
+      return false;
+    }
+
+    SomethingNonNullDominance asSomething() {
+      return null;
+    }
+
+    boolean isUnknown() {
+      return false;
+    }
+
+    public static Builder builder(Value nonNullValue) {
+      return new Builder(nonNullValue);
+    }
+
+    public static EverythingNonNullDominance everything() {
+      return EverythingNonNullDominance.getInstance();
+    }
+
+    public static EverythingElseNonNullDominance everythingElse() {
+      return EverythingElseNonNullDominance.getInstance();
+    }
+
+    public static NothingNonNullDominance nothing() {
+      return NothingNonNullDominance.getInstance();
+    }
+
+    public static UnknownNonNullDominance redundant() {
+      return unknown();
+    }
+
+    public static SomethingNonNullDominance something(
+        Set<Instruction> dominatedUsers, Map<Phi, IntList> dominatedPhiUsers) {
+      return new SomethingNonNullDominance(dominatedUsers, dominatedPhiUsers);
+    }
+
+    public static UnknownNonNullDominance unknown() {
+      return UnknownNonNullDominance.getInstance();
+    }
+
+    static class Builder {
+
+      private final Value nonNullValue;
+
+      private final Set<Instruction> dominatedUsers = Sets.newIdentityHashSet();
+      private final Map<Phi, IntList> dominatedPhiUsers = new IdentityHashMap<>();
+
+      private Builder(Value nonNullValue) {
+        this.nonNullValue = nonNullValue;
+      }
+
+      void addDominatedUser(Instruction user) {
+        assert nonNullValue.uniqueUsers().contains(user);
+        assert !dominatedUsers.contains(user);
+        dominatedUsers.add(user);
+      }
+
+      void addDominatedPhiUser(Phi user, IntList dominatedPredecessorIndices) {
+        assert nonNullValue.uniquePhiUsers().contains(user);
+        assert !dominatedPhiUsers.containsKey(user);
+        dominatedPhiUsers.put(user, dominatedPredecessorIndices);
+      }
+
+      NonNullDominance build() {
+        if (dominatedUsers.isEmpty() && dominatedPhiUsers.isEmpty()) {
+          return nothing();
+        }
+        assert dominatedUsers.size() < nonNullValue.uniqueUsers().size()
+            || dominatedPhiUsers.size() < nonNullValue.uniquePhiUsers().size();
+        return something(dominatedUsers, dominatedPhiUsers);
+      }
+    }
+  }
+
+  static class EverythingNonNullDominance extends NonNullDominance {
+
+    private static final EverythingNonNullDominance INSTANCE = new EverythingNonNullDominance();
+
+    private EverythingNonNullDominance() {}
+
+    public static EverythingNonNullDominance getInstance() {
+      return INSTANCE;
+    }
+
+    @Override
+    boolean isEverything() {
+      return true;
+    }
+  }
+
+  static class EverythingElseNonNullDominance extends NonNullDominance {
+
+    private static final EverythingElseNonNullDominance INSTANCE =
+        new EverythingElseNonNullDominance();
+
+    private EverythingElseNonNullDominance() {}
+
+    public static EverythingElseNonNullDominance getInstance() {
+      return INSTANCE;
+    }
+
+    @Override
+    boolean isEverythingElse() {
+      return true;
+    }
+  }
+
+  static class NothingNonNullDominance extends NonNullDominance {
+
+    private static final NothingNonNullDominance INSTANCE = new NothingNonNullDominance();
+
+    private NothingNonNullDominance() {}
+
+    public static NothingNonNullDominance getInstance() {
+      return INSTANCE;
+    }
+
+    @Override
+    boolean isNothing() {
+      return true;
+    }
+  }
+
+  static class SomethingNonNullDominance extends NonNullDominance {
+
+    private final Set<Instruction> dominatedUsers;
+    private final Map<Phi, IntList> dominatedPhiUsers;
+
+    SomethingNonNullDominance(
+        Set<Instruction> dominatedUsers, Map<Phi, IntList> dominatedPhiUsers) {
+      this.dominatedUsers = dominatedUsers;
+      this.dominatedPhiUsers = dominatedPhiUsers;
+    }
+
+    public Set<Instruction> getDominatedUsers() {
+      return dominatedUsers;
+    }
+
+    public Map<Phi, IntList> getDominatedPhiUsers() {
+      return dominatedPhiUsers;
+    }
+
+    @Override
+    boolean isSomething() {
+      return true;
+    }
+
+    @Override
+    SomethingNonNullDominance asSomething() {
+      return this;
+    }
+  }
+
+  static class UnknownNonNullDominance extends NonNullDominance {
+
+    private static final UnknownNonNullDominance INSTANCE = new UnknownNonNullDominance();
+
+    private UnknownNonNullDominance() {}
+
+    public static UnknownNonNullDominance getInstance() {
+      return INSTANCE;
+    }
+
+    @Override
+    boolean isUnknown() {
+      return true;
+    }
   }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/Outliner.java b/src/main/java/com/android/tools/r8/ir/optimize/Outliner.java
index 5c4197c..0ac7f7b 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/Outliner.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/Outliner.java
@@ -114,8 +114,8 @@
   /** Result of first step (see {@link Outliner#createOutlineMethodIdentifierGenerator()}. */
   private final List<Multiset<Wrapper<ProgramMethod>>> candidateMethodLists = new ArrayList<>();
   /** Result of second step (see {@link Outliner#selectMethodsForOutlining()}. */
-  private final LongLivedProgramMethodSetBuilder methodsSelectedForOutlining =
-      new LongLivedProgramMethodSetBuilder();
+  private final LongLivedProgramMethodSetBuilder<?> methodsSelectedForOutlining =
+      LongLivedProgramMethodSetBuilder.create();
   /** Result of second step (see {@link Outliner#selectMethodsForOutlining()}. */
   private final Map<Outline, List<ProgramMethod>> outlineSites = new HashMap<>();
   /** Result of third step (see {@link Outliner#buildOutlinerClass(DexType)}. */
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/UninstantiatedTypeOptimization.java b/src/main/java/com/android/tools/r8/ir/optimize/UninstantiatedTypeOptimization.java
index 32bea32..6077882 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/UninstantiatedTypeOptimization.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/UninstantiatedTypeOptimization.java
@@ -23,13 +23,13 @@
 import com.android.tools.r8.graph.RewrittenPrototypeDescription.ArgumentInfoCollection;
 import com.android.tools.r8.graph.RewrittenPrototypeDescription.RemovedArgumentInfo;
 import com.android.tools.r8.graph.TopDownClassHierarchyTraversal;
-import com.android.tools.r8.ir.analysis.AbstractError;
 import com.android.tools.r8.ir.analysis.TypeChecker;
 import com.android.tools.r8.ir.analysis.type.TypeAnalysis;
 import com.android.tools.r8.ir.code.BasicBlock;
 import com.android.tools.r8.ir.code.FieldInstruction;
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.Instruction;
+import com.android.tools.r8.ir.code.Instruction.SideEffectAssumption;
 import com.android.tools.r8.ir.code.InstructionListIterator;
 import com.android.tools.r8.ir.code.InvokeMethod;
 import com.android.tools.r8.ir.code.Value;
@@ -374,7 +374,6 @@
         if (instruction.isFieldInstruction()) {
           rewriteFieldInstruction(
               instruction.asFieldInstruction(),
-              blockIterator,
               instructionIterator,
               code,
               assumeDynamicTypeRemover,
@@ -411,13 +410,11 @@
     }
     if (current.isFieldInstruction()) {
       // Other resolution-related errors come first.
-      AbstractError abstractError =
-          current.asFieldInstruction().instructionInstanceCanThrow(appView, context);
-      if (abstractError.isThrowing()
-          && abstractError.getSpecificError(appView.dexItemFactory())
-              != appView.dexItemFactory().npeType) {
-        // We can't replace the current instruction with `throw null` if it may throw another
-        // Error/Exception than NullPointerException.
+      FieldInstruction fieldInstruction = current.asFieldInstruction();
+      // We can't replace the current instruction with `throw null` if it may throw another
+      // exception than NullPointerException.
+      if (fieldInstruction.instructionInstanceCanThrow(
+          appView, context, SideEffectAssumption.RECEIVER_NOT_NULL)) {
         return false;
       }
     }
@@ -446,7 +443,6 @@
   // At this point, field-instruction whose target field type is uninstantiated will be handled.
   private void rewriteFieldInstruction(
       FieldInstruction instruction,
-      ListIterator<BasicBlock> blockIterator,
       InstructionListIterator instructionIterator,
       IRCode code,
       AssumeDynamicTypeRemover assumeDynamicTypeRemover,
@@ -461,8 +457,7 @@
         return;
       }
 
-      boolean instructionCanBeRemoved =
-          instruction.instructionInstanceCanThrow(appView, context).isThrowing();
+      boolean instructionCanBeRemoved = !instruction.instructionInstanceCanThrow(appView, context);
 
       BasicBlock block = instruction.getBlock();
       if (instruction.isFieldPut()) {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxer.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxer.java
index b601000..c179446 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxer.java
@@ -7,7 +7,7 @@
 import static com.android.tools.r8.ir.analysis.type.Nullability.definitelyNotNull;
 
 import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.DexApplication;
+import com.android.tools.r8.graph.DexApplication.Builder;
 import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexClass.FieldSetter;
 import com.android.tools.r8.graph.DexEncodedField;
@@ -17,9 +17,11 @@
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexProto;
+import com.android.tools.r8.graph.DexString;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.DexValue.DexValueInt;
 import com.android.tools.r8.graph.DexValue.DexValueNull;
+import com.android.tools.r8.graph.FieldResolutionResult;
 import com.android.tools.r8.graph.GraphLense;
 import com.android.tools.r8.graph.GraphLense.NestedGraphLense;
 import com.android.tools.r8.graph.RewrittenPrototypeDescription;
@@ -37,7 +39,9 @@
 import com.android.tools.r8.ir.code.If;
 import com.android.tools.r8.ir.code.Instruction;
 import com.android.tools.r8.ir.code.InvokeMethod;
+import com.android.tools.r8.ir.code.InvokeStatic;
 import com.android.tools.r8.ir.code.MemberType;
+import com.android.tools.r8.ir.code.Opcodes;
 import com.android.tools.r8.ir.code.Phi;
 import com.android.tools.r8.ir.code.Value;
 import com.android.tools.r8.ir.conversion.CodeOptimization;
@@ -48,7 +52,6 @@
 import com.android.tools.r8.ir.optimize.info.MethodOptimizationInfo;
 import com.android.tools.r8.ir.optimize.info.OptimizationFeedback.OptimizationInfoFixer;
 import com.android.tools.r8.ir.optimize.info.OptimizationFeedbackDelayed;
-import com.android.tools.r8.ir.optimize.staticizer.ClassStaticizer;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.Reporter;
@@ -58,6 +61,7 @@
 import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Sets;
+import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
 import java.util.IdentityHashMap;
@@ -148,26 +152,23 @@
             }
           }
         }
-        if (instruction.isConstClass()) {
-          analyzeConstClass(instruction.asConstClass(), eligibleEnums);
-        } else if (instruction.isCheckCast()) {
-          analyzeCheckCast(instruction.asCheckCast(), eligibleEnums);
-        } else if (instruction.isInvokeStatic()) {
-          // TODO(b/150370354): Since we temporary allow enum unboxing on enums with values and
-          // valueOf static methods only if such methods are unused, such methods cannot be
-          // called. the long term solution is to simply move called methods to a companion class,
-          // as any static helper method, and remove these checks.
-          DexMethod invokedMethod = instruction.asInvokeStatic().getInvokedMethod();
-          DexProgramClass enumClass = getEnumUnboxingCandidateOrNull(invokedMethod.holder);
-          if (enumClass != null) {
-            if (factory.enumMethods.isValueOfMethod(invokedMethod, enumClass)) {
-              markEnumAsUnboxable(Reason.VALUE_OF_INVOKE, enumClass);
-            } else if (factory.enumMethods.isValuesMethod(invokedMethod, enumClass)) {
-              markEnumAsUnboxable(Reason.VALUES_INVOKE, enumClass);
-            } else {
-              assert false; // We do not allow any other static call in unboxing candidates.
-            }
-          }
+        switch (instruction.opcode()) {
+          case Opcodes.CONST_CLASS:
+            analyzeConstClass(instruction.asConstClass(), eligibleEnums);
+            break;
+          case Opcodes.CHECK_CAST:
+            analyzeCheckCast(instruction.asCheckCast(), eligibleEnums);
+            break;
+          case Opcodes.INVOKE_STATIC:
+            analyzeInvokeStatic(instruction.asInvokeStatic(), eligibleEnums);
+            break;
+          case Opcodes.STATIC_GET:
+          case Opcodes.INSTANCE_GET:
+          case Opcodes.STATIC_PUT:
+          case Opcodes.INSTANCE_PUT:
+            analyzeFieldInstruction(instruction.asFieldInstruction(), code);
+            break;
+          default: // Nothing to do for other instructions.
         }
       }
       for (Phi phi : block.getPhis()) {
@@ -195,6 +196,26 @@
     }
   }
 
+  private void analyzeFieldInstruction(FieldInstruction fieldInstruction, IRCode code) {
+    DexField field = fieldInstruction.getField();
+    DexProgramClass enumClass = getEnumUnboxingCandidateOrNull(field.holder);
+    if (enumClass != null) {
+      FieldResolutionResult resolutionResult =
+          appView.appInfo().resolveField(field, code.context());
+      if (resolutionResult.isFailedOrUnknownResolution()) {
+        markEnumAsUnboxable(Reason.UNRESOLVABLE_FIELD, enumClass);
+      }
+    }
+  }
+
+  private void analyzeInvokeStatic(InvokeStatic invokeStatic, Set<DexType> eligibleEnums) {
+    DexMethod invokedMethod = invokeStatic.getInvokedMethod();
+    DexProgramClass enumClass = getEnumUnboxingCandidateOrNull(invokedMethod.holder);
+    if (enumClass != null) {
+      eligibleEnums.add(enumClass.type);
+    }
+  }
+
   private void analyzeCheckCast(CheckCast checkCast, Set<DexType> eligibleEnums) {
     // We are doing a type check, which typically means the in-value is of an upper
     // type and cannot be dealt with.
@@ -297,58 +318,54 @@
   public void unboxEnums(
       PostMethodProcessor.Builder postBuilder,
       ExecutorService executorService,
-      OptimizationFeedbackDelayed feedback,
-      ClassStaticizer classStaticizer)
+      OptimizationFeedbackDelayed feedback)
       throws ExecutionException {
     // At this point the enumsToUnbox are no longer candidates, they will all be unboxed.
     if (enumsUnboxingCandidates.isEmpty()) {
       return;
     }
     ImmutableSet<DexType> enumsToUnbox = ImmutableSet.copyOf(this.enumsUnboxingCandidates.keySet());
-    NestedGraphLense enumUnboxingLens = new TreeFixer(enumsToUnbox).fixupTypeReferences();
     enumUnboxerRewriter = new EnumUnboxingRewriter(appView, enumsToUnbox);
+    NestedGraphLense enumUnboxingLens = new TreeFixer(enumsToUnbox).fixupTypeReferences();
     appView.setUnboxedEnums(enumUnboxerRewriter.getEnumsToUnbox());
-    if (enumUnboxingLens != null) {
-      appView.setGraphLense(enumUnboxingLens);
-      appView.setAppInfo(
-          appView
-              .appInfo()
-              .rewrittenWithLens(appView.appInfo().app().asDirect(), enumUnboxingLens));
-      // Update optimization info.
-      feedback.fixupOptimizationInfos(
-          appView,
-          executorService,
-          new OptimizationInfoFixer() {
-            @Override
-            public void fixup(DexEncodedField field) {
-              FieldOptimizationInfo optimizationInfo = field.getOptimizationInfo();
-              if (optimizationInfo.isMutableFieldOptimizationInfo()) {
-                optimizationInfo
-                    .asMutableFieldOptimizationInfo()
-                    .fixupClassTypeReferences(appView.graphLense()::lookupType, appView)
-                    .fixupAbstractValue(appView, appView.graphLense());
-              } else {
-                assert optimizationInfo.isDefaultFieldOptimizationInfo();
-              }
+    GraphLense previousLens = appView.graphLense();
+    appView.setGraphLense(enumUnboxingLens);
+    appView.setAppInfo(
+        appView.appInfo().rewrittenWithLens(appView.appInfo().app().asDirect(), enumUnboxingLens));
+    // Update optimization info.
+    feedback.fixupOptimizationInfos(
+        appView,
+        executorService,
+        new OptimizationInfoFixer() {
+          @Override
+          public void fixup(DexEncodedField field) {
+            FieldOptimizationInfo optimizationInfo = field.getOptimizationInfo();
+            if (optimizationInfo.isMutableFieldOptimizationInfo()) {
+              optimizationInfo
+                  .asMutableFieldOptimizationInfo()
+                  .fixupClassTypeReferences(appView.graphLense()::lookupType, appView)
+                  .fixupAbstractValue(appView, appView.graphLense());
+            } else {
+              assert optimizationInfo.isDefaultFieldOptimizationInfo();
             }
+          }
 
-            @Override
-            public void fixup(DexEncodedMethod method) {
-              MethodOptimizationInfo optimizationInfo = method.getOptimizationInfo();
-              if (optimizationInfo.isUpdatableMethodOptimizationInfo()) {
-                optimizationInfo
-                    .asUpdatableMethodOptimizationInfo()
-                    .fixupClassTypeReferences(appView.graphLense()::lookupType, appView)
-                    .fixupAbstractReturnValue(appView, appView.graphLense())
-                    .fixupInstanceInitializerInfo(appView, appView.graphLense());
-              } else {
-                assert optimizationInfo.isDefaultMethodOptimizationInfo();
-              }
+          @Override
+          public void fixup(DexEncodedMethod method) {
+            MethodOptimizationInfo optimizationInfo = method.getOptimizationInfo();
+            if (optimizationInfo.isUpdatableMethodOptimizationInfo()) {
+              optimizationInfo
+                  .asUpdatableMethodOptimizationInfo()
+                  .fixupClassTypeReferences(appView.graphLense()::lookupType, appView)
+                  .fixupAbstractReturnValue(appView, appView.graphLense())
+                  .fixupInstanceInitializerInfo(appView, appView.graphLense());
+            } else {
+              assert optimizationInfo.isDefaultMethodOptimizationInfo();
             }
-          });
-    }
+          }
+        });
     postBuilder.put(this);
-    postBuilder.mapDexEncodedMethods(appView);
+    postBuilder.rewrittenWithLens(appView, previousLens);
   }
 
   public void finishAnalysis() {
@@ -600,12 +617,11 @@
     return Sets.newIdentityHashSet();
   }
 
-  public void synthesizeUtilityClass(
-      DexApplication.Builder<?> appBuilder, IRConverter converter, ExecutorService executorService)
+  public void synthesizeUtilityMethods(
+      Builder<?> builder, IRConverter converter, ExecutorService executorService)
       throws ExecutionException {
     if (enumUnboxerRewriter != null) {
-      enumUnboxerRewriter.synthesizeEnumUnboxingUtilityClass(
-          appBuilder, converter, executorService);
+      enumUnboxerRewriter.synthesizeEnumUnboxingUtilityMethods(builder, converter, executorService);
     }
   }
 
@@ -634,6 +650,7 @@
     INSTANCE_FIELD,
     GENERIC_INVOKE,
     UNEXPECTED_STATIC_FIELD,
+    UNRESOLVABLE_FIELD,
     VIRTUAL_METHOD,
     UNEXPECTED_DIRECT_METHOD,
     CONST_CLASS,
@@ -663,6 +680,7 @@
 
   private class TreeFixer {
 
+    private final List<DexEncodedMethod> unboxedEnumsMethods = new ArrayList<>();
     private final EnumUnboxingLens.Builder lensBuilder = EnumUnboxingLens.builder();
     private final Set<DexType> enumsToUnbox;
 
@@ -671,27 +689,29 @@
     }
 
     private NestedGraphLense fixupTypeReferences() {
+      assert enumUnboxerRewriter != null;
       // Fix all methods and fields using enums to unbox.
       for (DexProgramClass clazz : appView.appInfo().classes()) {
         if (enumsToUnbox.contains(clazz.type)) {
           assert clazz.instanceFields().size() == 0;
-          // TODO(b/150370354): Remove when static methods are supported.
-          if (appView.options().testing.enumUnboxingRewriteJavaCGeneratedMethod) {
-            // Clear only the initializers.
-            clazz
-                .methods()
-                .forEach(
-                    m -> {
-                      if (m.isInitializer()) {
-                        clearEnumToUnboxMethod(m);
-                      }
-                    });
-            clazz.getMethodCollection().replaceMethods(this::fixupMethod);
-          } else {
-            clazz.methods().forEach(this::clearEnumToUnboxMethod);
-          }
+          // Clear the initializers and move the static methods to the utility class.
+          Set<DexEncodedMethod> methodsToRemove = Sets.newIdentityHashSet();
+          clazz
+              .methods()
+              .forEach(
+                  m -> {
+                    if (m.isInitializer()) {
+                      clearEnumToUnboxMethod(m);
+                    } else {
+                      assert m.isStatic();
+                      unboxedEnumsMethods.add(
+                          fixupEncodedMethodToUtility(m, factory.enumUnboxingUtilityType));
+                      methodsToRemove.add(m);
+                    }
+                  });
+          clazz.getMethodCollection().removeMethods(methodsToRemove);
         } else {
-          clazz.getMethodCollection().replaceMethods(this::fixupMethod);
+          clazz.getMethodCollection().replaceMethods(this::fixupEncodedMethod);
           fixupFields(clazz.staticFields(), clazz::setStaticField);
           fixupFields(clazz.instanceFields(), clazz::setInstanceField);
         }
@@ -699,6 +719,10 @@
       for (DexType toUnbox : enumsToUnbox) {
         lensBuilder.map(toUnbox, factory.intType);
       }
+      DexProgramClass utilityClass =
+          appView.definitionForProgramType(factory.enumUnboxingUtilityType);
+      assert utilityClass != null : "Should have been synthesized upfront";
+      utilityClass.addDirectMethods(unboxedEnumsMethods);
       return lensBuilder.build(factory, appView.graphLense());
     }
 
@@ -715,7 +739,19 @@
           appView);
     }
 
-    private DexEncodedMethod fixupMethod(DexEncodedMethod encodedMethod) {
+    private DexEncodedMethod fixupEncodedMethodToUtility(
+        DexEncodedMethod encodedMethod, DexType newHolder) {
+      DexMethod method = encodedMethod.method;
+      DexString newMethodName =
+          factory.createString(
+              enumUnboxerRewriter.compatibleName(method.holder) + "$$" + method.name.toString());
+      DexMethod newMethod = fixupMethod(method, newHolder, newMethodName);
+      lensBuilder.move(method, newMethod, encodedMethod.isStatic());
+      encodedMethod.accessFlags.promoteToPublic();
+      return encodedMethod.toTypeSubstitutedMethod(newMethod);
+    }
+
+    private DexEncodedMethod fixupEncodedMethod(DexEncodedMethod encodedMethod) {
       DexMethod newMethod = fixupMethod(encodedMethod.method);
       if (newMethod != encodedMethod.method) {
         lensBuilder.move(encodedMethod.method, newMethod, encodedMethod.isStatic());
@@ -747,7 +783,11 @@
     }
 
     private DexMethod fixupMethod(DexMethod method) {
-      return factory.createMethod(method.holder, fixupProto(method.proto), method.name);
+      return fixupMethod(method, method.holder, method.name);
+    }
+
+    private DexMethod fixupMethod(DexMethod method, DexType newHolder, DexString newMethodName) {
+      return factory.createMethod(newHolder, fixupProto(method.proto), newMethodName);
     }
 
     private DexProto fixupProto(DexProto proto) {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingCandidateAnalysis.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingCandidateAnalysis.java
index 7ae1ffa..b5530c2 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingCandidateAnalysis.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingCandidateAnalysis.java
@@ -81,23 +81,11 @@
       enumUnboxer.reportFailure(clazz.type, Reason.MISSING_INFO_MAP);
       return false;
     }
-    // Methods values, valueOf, init, clinit are present on each enum.
-    // Methods init and clinit are required if the enum is used.
-    // Methods valueOf and values are normally kept by the commonly used/recommended enum keep rule
-    // -keepclassmembers,allowoptimization enum * {
-    //     public static **[] values();
-    //     public static ** valueOf(java.lang.String);
-    // }
-    // In general there will be 4 methods, unless the enum keep rule is not present.
-    if (clazz.getMethodCollection().numberOfDirectMethods() > 4) {
-      enumUnboxer.reportFailure(clazz.type, Reason.UNEXPECTED_DIRECT_METHOD);
-      return false;
-    }
+
+    // TODO(b/155036467): Fail lazily when an unsupported method is not only present but also used.
+    // Only Enums with default initializers and static methods can be unboxed at the moment.
     for (DexEncodedMethod directMethod : clazz.directMethods()) {
-      if (!(factory.enumMethods.isValuesMethod(directMethod.method, clazz)
-          || factory.enumMethods.isValueOfMethod(directMethod.method, clazz)
-          || isStandardEnumInitializer(directMethod)
-          || directMethod.isClassInitializer())) {
+      if (!(directMethod.isStatic() || isStandardEnumInitializer(directMethod))) {
         enumUnboxer.reportFailure(clazz.type, Reason.UNEXPECTED_DIRECT_METHOD);
         return false;
       }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingRewriter.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingRewriter.java
index 383b03b..6f6fba1 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingRewriter.java
@@ -11,7 +11,7 @@
 import com.android.tools.r8.graph.CfCode;
 import com.android.tools.r8.graph.ClassAccessFlags;
 import com.android.tools.r8.graph.DexAnnotationSet;
-import com.android.tools.r8.graph.DexApplication;
+import com.android.tools.r8.graph.DexApplication.Builder;
 import com.android.tools.r8.graph.DexEncodedField;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexField;
@@ -45,7 +45,6 @@
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.Sets;
 import java.util.ArrayList;
-import java.util.Arrays;
 import java.util.Collections;
 import java.util.List;
 import java.util.Map;
@@ -67,7 +66,6 @@
   private final Map<DexMethod, DexEncodedMethod> extraUtilityMethods = new ConcurrentHashMap<>();
   private final Map<DexField, DexEncodedField> extraUtilityFields = new ConcurrentHashMap<>();
 
-  private final DexType utilityClassType;
   private final DexMethod ordinalUtilityMethod;
   private final DexMethod valuesUtilityMethod;
 
@@ -83,15 +81,14 @@
     }
     this.enumsToUnbox = builder.build();
 
-    this.utilityClassType = factory.enumUnboxingUtilityType;
     this.ordinalUtilityMethod =
         factory.createMethod(
-            utilityClassType,
+            factory.enumUnboxingUtilityType,
             factory.createProto(factory.intType, factory.intType),
             ENUM_UNBOXING_UTILITY_ORDINAL);
     this.valuesUtilityMethod =
         factory.createMethod(
-            utilityClassType,
+            factory.enumUnboxingUtilityType,
             factory.createProto(factory.intArrayType, factory.intType),
             ENUM_UNBOXING_UTILITY_VALUES);
   }
@@ -222,13 +219,13 @@
     return enumsToUnbox.containsEnum(type.asClassType().getClassType());
   }
 
-  private String compatibleName(DexType type) {
+  public String compatibleName(DexType type) {
     return type.toSourceString().replace('.', '$');
   }
 
   private DexField createValuesField(DexType type) {
     return factory.createField(
-        utilityClassType,
+        factory.enumUnboxingUtilityType,
         factory.intArrayType,
         factory.enumValuesFieldName + "$field$" + compatibleName(type));
   }
@@ -244,7 +241,7 @@
 
   private DexMethod createValuesMethod(DexType type) {
     return factory.createMethod(
-        utilityClassType,
+        factory.enumUnboxingUtilityType,
         factory.createProto(factory.intArrayType),
         factory.enumValuesFieldName + "$method$" + compatibleName(type));
   }
@@ -253,7 +250,11 @@
       DexMethod method, DexField fieldValues, int numEnumInstances) {
     CfCode cfCode =
         new EnumUnboxingCfCodeProvider.EnumUnboxingValuesCfCodeProvider(
-                appView, utilityClassType, fieldValues, numEnumInstances, valuesUtilityMethod)
+                appView,
+                factory.enumUnboxingUtilityType,
+                fieldValues,
+                numEnumInstances,
+                valuesUtilityMethod)
             .generateCfCode();
     return synthesizeUtilityMethod(cfCode, method, true);
   }
@@ -262,7 +263,7 @@
     assert enumsToUnbox.containsEnum(type);
     DexMethod valueOf =
         factory.createMethod(
-            utilityClassType,
+            factory.enumUnboxingUtilityType,
             factory.createProto(factory.intType, factory.stringType),
             "valueOf" + compatibleName(type));
     extraUtilityMethods.computeIfAbsent(valueOf, m -> synthesizeValueOfUtilityMethod(m, type));
@@ -283,9 +284,8 @@
         && enumsToUnbox.containsEnum(baseType.asClassType().getClassType());
   }
 
-  // TODO(b/150172351): Synthesize the utility class upfront in the enqueuer.
-  void synthesizeEnumUnboxingUtilityClass(
-      DexApplication.Builder<?> appBuilder, IRConverter converter, ExecutorService executorService)
+  void synthesizeEnumUnboxingUtilityMethods(
+      Builder<?> builder, IRConverter converter, ExecutorService executorService)
       throws ExecutionException {
     // Synthesize a class which holds various utility methods that may be called from the IR
     // rewriting. If any of these methods are not used, they will be removed by the Enqueuer.
@@ -302,38 +302,51 @@
     if (requiredMethods.isEmpty()) {
       return;
     }
-    DexEncodedField[] fields = extraUtilityFields.values().toArray(DexEncodedField.EMPTY_ARRAY);
-    Arrays.sort(fields, (f1, f2) -> f1.field.name.slowCompareTo(f2.field.name));
+    List<DexEncodedField> fields = new ArrayList<>(extraUtilityFields.values());
+    fields.sort((f1, f2) -> f1.field.name.slowCompareTo(f2.field.name));
     DexProgramClass utilityClass =
-        new DexProgramClass(
-            utilityClassType,
-            null,
-            new SynthesizedOrigin("EnumUnboxing ", getClass()),
-            ClassAccessFlags.fromSharedAccessFlags(Constants.ACC_PUBLIC | Constants.ACC_SYNTHETIC),
-            factory.objectType,
-            DexTypeList.empty(),
-            factory.createString("enumunboxing"),
-            null,
-            Collections.emptyList(),
-            null,
-            Collections.emptyList(),
-            DexAnnotationSet.empty(),
-            fields,
-            DexEncodedField.EMPTY_ARRAY,
-            // All synthesized methods are static in this case.
-            requiredMethods.toArray(DexEncodedMethod.EMPTY_ARRAY),
-            DexEncodedMethod.EMPTY_ARRAY,
-            factory.getSkipNameValidationForTesting(),
-            DexProgramClass::checksumFromType);
-    appBuilder.addSynthesizedClass(utilityClass, utilityClassInMainDexList());
-    appView.appInfo().addSynthesizedClass(utilityClass);
+        appView.definitionForProgramType(factory.enumUnboxingUtilityType);
+    assert utilityClass != null : "Should have been synthesized upfront.";
+    utilityClass.appendStaticFields(fields);
+    utilityClass.addDirectMethods(requiredMethods);
+    assert requiredMethods.stream().allMatch(DexEncodedMethod::isPublic);
+    if (utilityClassInMainDexList()) {
+      builder.addToMainDexList(Collections.singletonList(utilityClass.type));
+    }
+    // TODO(b/147860220): Use processMethodsConcurrently on requiredMethods instead.
     converter.optimizeSynthesizedClass(utilityClass, executorService);
   }
 
+  public static DexProgramClass synthesizeEmptyEnumUnboxingUtilityClass(AppView<?> appView) {
+    DexItemFactory factory = appView.dexItemFactory();
+    return new DexProgramClass(
+        factory.enumUnboxingUtilityType,
+        null,
+        new SynthesizedOrigin("EnumUnboxing ", EnumUnboxingRewriter.class),
+        ClassAccessFlags.fromSharedAccessFlags(Constants.ACC_PUBLIC | Constants.ACC_SYNTHETIC),
+        factory.objectType,
+        DexTypeList.empty(),
+        factory.createString("enumunboxing"),
+        null,
+        Collections.emptyList(),
+        null,
+        Collections.emptyList(),
+        DexAnnotationSet.empty(),
+        DexEncodedField.EMPTY_ARRAY,
+        DexEncodedField.EMPTY_ARRAY,
+        DexEncodedMethod.EMPTY_ARRAY,
+        DexEncodedMethod.EMPTY_ARRAY,
+        factory.getSkipNameValidationForTesting(),
+        DexProgramClass::checksumFromType);
+  }
+
   private DexEncodedMethod synthesizeValueOfUtilityMethod(DexMethod method, DexType enumType) {
     CfCode cfCode =
         new EnumUnboxingCfCodeProvider.EnumUnboxingValueOfCfCodeProvider(
-                appView, utilityClassType, enumType, enumsToUnbox.getEnumValueInfoMap(enumType))
+                appView,
+                factory.enumUnboxingUtilityType,
+                enumType,
+                enumsToUnbox.getEnumValueInfoMap(enumType))
             .generateCfCode();
     return new DexEncodedMethod(
         method,
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/AbandonedCallSiteOptimizationInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/AbandonedCallSiteOptimizationInfo.java
new file mode 100644
index 0000000..a1dece6
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/AbandonedCallSiteOptimizationInfo.java
@@ -0,0 +1,25 @@
+// 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.info;
+
+/**
+ * Used to represent that nothing is known about the argument values of a given method and all of
+ * its overrides.
+ */
+public class AbandonedCallSiteOptimizationInfo extends CallSiteOptimizationInfo {
+
+  private static final AbandonedCallSiteOptimizationInfo INSTANCE =
+      new AbandonedCallSiteOptimizationInfo();
+
+  private AbandonedCallSiteOptimizationInfo() {}
+
+  static AbandonedCallSiteOptimizationInfo getInstance() {
+    return INSTANCE;
+  }
+
+  @Override
+  public boolean isAbandoned() {
+    return true;
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/BottomCallSiteOptimizationInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/BottomCallSiteOptimizationInfo.java
index 20ee5df..14d9dc0 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/BottomCallSiteOptimizationInfo.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/BottomCallSiteOptimizationInfo.java
@@ -5,10 +5,16 @@
 
 // Nothing is known about arguments at call sites.
 public class BottomCallSiteOptimizationInfo extends CallSiteOptimizationInfo {
-  static BottomCallSiteOptimizationInfo INSTANCE = new BottomCallSiteOptimizationInfo();
+
+  private static final BottomCallSiteOptimizationInfo INSTANCE =
+      new BottomCallSiteOptimizationInfo();
 
   private BottomCallSiteOptimizationInfo() {}
 
+  static BottomCallSiteOptimizationInfo getInstance() {
+    return INSTANCE;
+  }
+
   @Override
   public boolean isBottom() {
     return true;
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/CallSiteOptimizationInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/CallSiteOptimizationInfo.java
index e85cf9e..ad274e9 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/CallSiteOptimizationInfo.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/CallSiteOptimizationInfo.java
@@ -5,6 +5,7 @@
 
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.analysis.value.AbstractValue;
 import com.android.tools.r8.ir.analysis.value.UnknownValue;
@@ -13,8 +14,22 @@
 // A flat lattice structure:
 //   BOTTOM, TOP, and a lattice element that holds accumulated argument info.
 public abstract class CallSiteOptimizationInfo {
-  public static BottomCallSiteOptimizationInfo BOTTOM = BottomCallSiteOptimizationInfo.INSTANCE;
-  public static TopCallSiteOptimizationInfo TOP = TopCallSiteOptimizationInfo.INSTANCE;
+
+  public static AbandonedCallSiteOptimizationInfo abandoned() {
+    return AbandonedCallSiteOptimizationInfo.getInstance();
+  }
+
+  public static BottomCallSiteOptimizationInfo bottom() {
+    return BottomCallSiteOptimizationInfo.getInstance();
+  }
+
+  public static TopCallSiteOptimizationInfo top() {
+    return TopCallSiteOptimizationInfo.getInstance();
+  }
+
+  public boolean isAbandoned() {
+    return false;
+  }
 
   public boolean isBottom() {
     return false;
@@ -33,19 +48,19 @@
   }
 
   public CallSiteOptimizationInfo join(
-      CallSiteOptimizationInfo other, AppView<?> appView, DexEncodedMethod encodedMethod) {
-    if (isBottom()) {
+      CallSiteOptimizationInfo other, AppView<?> appView, DexEncodedMethod method) {
+    if (isAbandoned() || other.isAbandoned()) {
+      return abandoned();
+    }
+    if (isBottom() || other.isTop()) {
       return other;
     }
-    if (other.isBottom()) {
+    if (isTop() || other.isBottom()) {
       return this;
     }
-    if (isTop() || other.isTop()) {
-      return TOP;
-    }
     assert isConcreteCallSiteOptimizationInfo() && other.isConcreteCallSiteOptimizationInfo();
     return asConcreteCallSiteOptimizationInfo()
-        .join(other.asConcreteCallSiteOptimizationInfo(), appView, encodedMethod);
+        .join(other.asConcreteCallSiteOptimizationInfo(), appView, method);
   }
 
   /**
@@ -54,10 +69,14 @@
    * if a certain argument is guaranteed to be definitely not null for all call sites, null-check on
    * that argument can be simplified during the reprocessing of the method.
    */
-  public boolean hasUsefulOptimizationInfo(AppView<?> appView, DexEncodedMethod encodedMethod) {
+  public boolean hasUsefulOptimizationInfo(AppView<?> appView, DexEncodedMethod method) {
     return false;
   }
 
+  public final boolean hasUsefulOptimizationInfo(AppView<?> appView, ProgramMethod method) {
+    return hasUsefulOptimizationInfo(appView, method.getDefinition());
+  }
+
   // The index exactly matches with in values of invocation, i.e., even including receiver.
   public TypeElement getDynamicUpperBoundType(int argIndex) {
     return null;
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/ConcreteCallSiteOptimizationInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/ConcreteCallSiteOptimizationInfo.java
index 86d107a..3d20843 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/ConcreteCallSiteOptimizationInfo.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/ConcreteCallSiteOptimizationInfo.java
@@ -8,6 +8,7 @@
 
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.analysis.type.Nullability;
 import com.android.tools.r8.ir.analysis.type.TypeElement;
@@ -30,12 +31,6 @@
   private final Int2ReferenceMap<TypeElement> dynamicUpperBoundTypes;
   private final Int2ReferenceMap<AbstractValue> constants;
 
-  private ConcreteCallSiteOptimizationInfo(
-      DexEncodedMethod encodedMethod, boolean allowConstantPropagation) {
-    this(encodedMethod.method.getArity() + (encodedMethod.isStatic() ? 0 : 1),
-        allowConstantPropagation);
-  }
-
   private ConcreteCallSiteOptimizationInfo(int size, boolean allowConstantPropagation) {
     assert size > 0;
     this.size = size;
@@ -44,12 +39,12 @@
   }
 
   CallSiteOptimizationInfo join(
-      ConcreteCallSiteOptimizationInfo other, AppView<?> appView, DexEncodedMethod encodedMethod) {
+      ConcreteCallSiteOptimizationInfo other, AppView<?> appView, DexEncodedMethod method) {
     assert this.size == other.size;
-    boolean allowConstantPropagation = appView.options().enablePropagationOfConstantsAtCallSites;
+    boolean allowConstantPropagation =
+        appView.options().callSiteOptimizationOptions().isConstantPropagationEnabled();
     ConcreteCallSiteOptimizationInfo result =
         new ConcreteCallSiteOptimizationInfo(this.size, allowConstantPropagation);
-    assert result.dynamicUpperBoundTypes != null;
     for (int i = 0; i < result.size; i++) {
       if (allowConstantPropagation) {
         assert result.constants != null;
@@ -72,42 +67,40 @@
       result.dynamicUpperBoundTypes.put(
           i, thisUpperBoundType.join(otherUpperBoundType, appView));
     }
-    if (result.hasUsefulOptimizationInfo(appView, encodedMethod)) {
+    if (result.hasUsefulOptimizationInfo(appView, method)) {
       return result;
     }
     // As soon as we know the argument collection so far does not have any useful optimization info,
     // move to TOP so that further collection can be simply skipped.
-    return TOP;
+    return top();
   }
 
-  private TypeElement[] getStaticTypes(AppView<?> appView, DexEncodedMethod encodedMethod) {
-    int argOffset = encodedMethod.isStatic() ? 0 : 1;
-    int size = encodedMethod.method.getArity() + argOffset;
+  private TypeElement[] getStaticTypes(AppView<?> appView, DexEncodedMethod method) {
+    int argOffset = method.isStatic() ? 0 : 1;
+    int size = method.method.getArity() + argOffset;
     TypeElement[] staticTypes = new TypeElement[size];
-    if (!encodedMethod.isStatic()) {
-      staticTypes[0] =
-          TypeElement.fromDexType(encodedMethod.holder(), definitelyNotNull(), appView);
+    if (!method.isStatic()) {
+      staticTypes[0] = TypeElement.fromDexType(method.holder(), definitelyNotNull(), appView);
     }
-    for (int i = 0; i < encodedMethod.method.getArity(); i++) {
+    for (int i = 0; i < method.method.getArity(); i++) {
       staticTypes[i + argOffset] =
-          TypeElement.fromDexType(
-              encodedMethod.method.proto.parameters.values[i], maybeNull(), appView);
+          TypeElement.fromDexType(method.parameters().values[i], maybeNull(), appView);
     }
     return staticTypes;
   }
 
   @Override
-  public boolean hasUsefulOptimizationInfo(AppView<?> appView, DexEncodedMethod encodedMethod) {
-    TypeElement[] staticTypes = getStaticTypes(appView, encodedMethod);
+  public boolean hasUsefulOptimizationInfo(AppView<?> appView, DexEncodedMethod method) {
+    TypeElement[] staticTypes = getStaticTypes(appView, method);
     for (int i = 0; i < size; i++) {
-      ParameterUsage parameterUsage = encodedMethod.getOptimizationInfo().getParameterUsages(i);
+      ParameterUsage parameterUsage = method.getOptimizationInfo().getParameterUsages(i);
       // If the parameter is not used, passing accurate argument info doesn't matter.
       if (parameterUsage != null && parameterUsage.notUsed()) {
         continue;
       }
       AbstractValue abstractValue = getAbstractArgumentValue(i);
       if (abstractValue.isNonTrivial()) {
-        assert appView.options().enablePropagationOfConstantsAtCallSites;
+        assert appView.options().callSiteOptimizationOptions().isConstantPropagationEnabled();
         return true;
       }
 
@@ -118,7 +111,7 @@
       if (dynamicUpperBoundType == null) {
         continue;
       }
-      assert appView.options().enablePropagationOfDynamicTypesAtCallSites;
+      assert appView.options().callSiteOptimizationOptions().isTypePropagationEnabled();
       // To avoid the full join of type lattices below, separately check if the nullability of
       // arguments is improved, and if so, we can eagerly conclude that we've collected useful
       // call site information for this method.
@@ -154,37 +147,50 @@
   }
 
   public static CallSiteOptimizationInfo fromArguments(
-      AppView<AppInfoWithLiveness> appView, ProgramMethod target, List<Value> inValues) {
-    boolean allowConstantPropagation = appView.options().enablePropagationOfConstantsAtCallSites;
+      AppView<AppInfoWithLiveness> appView,
+      DexMethod invokedMethod,
+      List<Value> arguments,
+      ProgramMethod context) {
+    boolean allowConstantPropagation =
+        appView.options().callSiteOptimizationOptions().isConstantPropagationEnabled();
     ConcreteCallSiteOptimizationInfo newCallSiteInfo =
-        new ConcreteCallSiteOptimizationInfo(target.getDefinition(), allowConstantPropagation);
-    assert newCallSiteInfo.size == inValues.size();
+        new ConcreteCallSiteOptimizationInfo(arguments.size(), allowConstantPropagation);
+    boolean hasReceiver = arguments.size() > invokedMethod.getArity();
+    boolean isTop = true;
     assert newCallSiteInfo.dynamicUpperBoundTypes != null;
     for (int i = 0; i < newCallSiteInfo.size; i++) {
-      Value arg = inValues.get(i);
+      Value arg = arguments.get(i);
+
+      // Constant propagation.
       if (allowConstantPropagation) {
         assert newCallSiteInfo.constants != null;
         Value aliasedValue = arg.getAliasedValue();
         if (!aliasedValue.isPhi()) {
-          AbstractValue abstractValue = aliasedValue.definition.getAbstractValue(appView, target);
+          AbstractValue abstractValue = aliasedValue.definition.getAbstractValue(appView, context);
           if (abstractValue.isNonTrivial()) {
             newCallSiteInfo.constants.put(i, abstractValue);
+            isTop = false;
           }
         }
       }
 
-      if (arg.getType().isPrimitiveType()) {
-        continue;
+      // Type propagation.
+      if (arg.getType().isReferenceType()) {
+        TypeElement staticType =
+            TypeElement.fromDexType(
+                hasReceiver ? invokedMethod.holder : invokedMethod.proto.getParameter(i),
+                maybeNull(),
+                appView);
+        TypeElement dynamicUpperBoundType = arg.getDynamicUpperBoundType(appView);
+        if (dynamicUpperBoundType != staticType) {
+          newCallSiteInfo.dynamicUpperBoundTypes.put(i, dynamicUpperBoundType);
+          isTop = false;
+        } else {
+          newCallSiteInfo.dynamicUpperBoundTypes.put(i, staticType);
+        }
       }
-      assert arg.getType().isReferenceType();
-      newCallSiteInfo.dynamicUpperBoundTypes.put(i, arg.getDynamicUpperBoundType(appView));
     }
-    if (newCallSiteInfo.hasUsefulOptimizationInfo(appView, target.getDefinition())) {
-      return newCallSiteInfo;
-    }
-    // As soon as we know the current call site does not have any useful optimization info,
-    // return TOP so that further collection can be simply skipped.
-    return TOP;
+    return isTop ? CallSiteOptimizationInfo.top() : newCallSiteInfo;
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfoCollector.java b/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfoCollector.java
index 8bb40e0..a1a6638 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfoCollector.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfoCollector.java
@@ -534,8 +534,7 @@
               }
               Value object =
                   instancePut.object().getAliasedValue(aliasesThroughAssumeAndCheckCasts);
-              if (object != receiver
-                  || instancePut.instructionInstanceCanThrow(appView, context).isThrowing()) {
+              if (object != receiver || instancePut.instructionInstanceCanThrow(appView, context)) {
                 builder.setMayHaveOtherSideEffectsThanInstanceFieldAssignments();
               }
 
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 8942ea1..9696162 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
@@ -156,7 +156,7 @@
 
   @Override
   public void setBridgeInfo(DexEncodedMethod method, BridgeInfo bridgeInfo) {
-    // Ignored.
+    method.getMutableOptimizationInfo().setBridgeInfo(bridgeInfo);
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/TopCallSiteOptimizationInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/TopCallSiteOptimizationInfo.java
index 5e1fdd8..241b934 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/TopCallSiteOptimizationInfo.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/TopCallSiteOptimizationInfo.java
@@ -5,10 +5,15 @@
 
 // Nothing should not be assumed about arguments at call sites.
 class TopCallSiteOptimizationInfo extends CallSiteOptimizationInfo {
-  static TopCallSiteOptimizationInfo INSTANCE = new TopCallSiteOptimizationInfo();
+
+  private static final TopCallSiteOptimizationInfo INSTANCE = new TopCallSiteOptimizationInfo();
 
   private TopCallSiteOptimizationInfo() {}
 
+  static TopCallSiteOptimizationInfo getInstance() {
+    return INSTANCE;
+  }
+
   @Override
   public boolean isTop() {
     return true;
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/bridge/BridgeAnalyzer.java b/src/main/java/com/android/tools/r8/ir/optimize/info/bridge/BridgeAnalyzer.java
index 8f28fd5..9eeb579 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/bridge/BridgeAnalyzer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/bridge/BridgeAnalyzer.java
@@ -166,7 +166,8 @@
     }
     // It must not return a value, or the return value must be the result value of the invoke.
     return ret.isReturnVoid()
-        || ret.returnValue() == (returnCast != null ? returnCast : invoke).outValue();
+        || ret.returnValue().getAliasedValue()
+            == (returnCast != null ? returnCast : invoke).outValue();
   }
 
   private static BridgeInfo failure() {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/bridge/BridgeInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/bridge/BridgeInfo.java
index c718391..2683d2f 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/bridge/BridgeInfo.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/bridge/BridgeInfo.java
@@ -9,8 +9,6 @@
  */
 public abstract class BridgeInfo {
 
-  public abstract boolean hasSameTarget(BridgeInfo bridgeInfo);
-
   public boolean isVirtualBridgeInfo() {
     return false;
   }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/bridge/VirtualBridgeInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/bridge/VirtualBridgeInfo.java
index 585699f..a07abdc 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/bridge/VirtualBridgeInfo.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/bridge/VirtualBridgeInfo.java
@@ -38,13 +38,6 @@
   }
 
   @Override
-  public boolean hasSameTarget(BridgeInfo bridgeInfo) {
-    assert bridgeInfo.isVirtualBridgeInfo();
-    VirtualBridgeInfo virtualBridgeInfo = bridgeInfo.asVirtualBridgeInfo();
-    return invokedMethod.match(virtualBridgeInfo.invokedMethod);
-  }
-
-  @Override
   public boolean isVirtualBridgeInfo() {
     return true;
   }
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 35add7f..6e0805e 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
@@ -195,8 +195,8 @@
   // we mark a method for further processing, and then invalidate the only lambda referenced
   // from it. In this case we will reprocess method that does not need patching, but it
   // should not be happening very frequently and we ignore possible overhead.
-  private final LongLivedProgramMethodSetBuilder methodsToReprocess =
-      new LongLivedProgramMethodSetBuilder();
+  private final LongLivedProgramMethodSetBuilder<SortedProgramMethodSet> methodsToReprocess =
+      LongLivedProgramMethodSetBuilder.createSorted();
 
   private final AppView<AppInfoWithLiveness> appView;
   private final Kotlin kotlin;
@@ -455,8 +455,7 @@
     if (methodsToReprocess.isEmpty()) {
       return;
     }
-    SortedProgramMethodSet methods =
-        methodsToReprocess.build(appView, ignore -> SortedProgramMethodSet.create());
+    SortedProgramMethodSet methods = methodsToReprocess.build(appView);
     converter.processMethodsConcurrently(methods, executorService);
     assert methods.stream()
         .map(DexClassAndMethod::getDefinition)
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KotlinLambdaGroupIdFactory.java b/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KotlinLambdaGroupIdFactory.java
index c8e08c1..049748f 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KotlinLambdaGroupIdFactory.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KotlinLambdaGroupIdFactory.java
@@ -94,7 +94,7 @@
       if (DexAnnotation.isSignatureAnnotation(annotation, kotlin.factory)) {
         continue;
       }
-      if (annotation.annotation.type == kotlin.metadata.kotlinMetadataType) {
+      if (annotation.annotation.type == kotlin.factory.kotlinMetadataType) {
         continue;
       }
       return false;
@@ -111,7 +111,7 @@
         continue;
       }
 
-      if (annotation.annotation.type == kotlin.metadata.kotlinMetadataType) {
+      if (annotation.annotation.type == appView.dexItemFactory().kotlinMetadataType) {
         // Ignore kotlin metadata on lambda classes. Metadata on synthetic
         // classes exists but is not used in the current Kotlin version (1.2.21)
         // and newly generated lambda _group_ class is not exactly a kotlin class.
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/staticizer/ClassStaticizer.java b/src/main/java/com/android/tools/r8/ir/optimize/staticizer/ClassStaticizer.java
index acba57f..499c975 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/staticizer/ClassStaticizer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/staticizer/ClassStaticizer.java
@@ -102,7 +102,7 @@
     }
   }
 
-  final Map<CandidateInfo, LongLivedProgramMethodSetBuilder> referencedFrom =
+  final Map<CandidateInfo, LongLivedProgramMethodSetBuilder<?>> referencedFrom =
       new IdentityHashMap<>();
 
   // The map storing all the potential candidates for staticizing.
@@ -291,7 +291,7 @@
             // Ignore just read instruction.
           }
           referencedFrom
-              .computeIfAbsent(candidateInfo, ignore -> new LongLivedProgramMethodSetBuilder())
+              .computeIfAbsent(candidateInfo, ignore -> LongLivedProgramMethodSetBuilder.create())
               .add(context);
         }
         continue;
@@ -313,7 +313,7 @@
         CandidateInfo info = processStaticFieldRead(instruction.asStaticGet());
         if (info != null) {
           referencedFrom
-              .computeIfAbsent(info, ignore -> new LongLivedProgramMethodSetBuilder())
+              .computeIfAbsent(info, ignore -> LongLivedProgramMethodSetBuilder.create())
               .add(context);
           // If the candidate is still valid, ignore all usages in further analysis.
           Value value = instruction.outValue();
@@ -329,7 +329,7 @@
         CandidateInfo info = processInvokeStatic(instruction.asInvokeStatic());
         if (info != null) {
           referencedFrom
-              .computeIfAbsent(info, ignore -> new LongLivedProgramMethodSetBuilder())
+              .computeIfAbsent(info, ignore -> LongLivedProgramMethodSetBuilder.create())
               .add(context);
           // If the candidate is still valid, ignore all usages in further analysis.
           Value value = instruction.outValue();
diff --git a/src/main/java/com/android/tools/r8/kotlin/KmVisitorProviders.java b/src/main/java/com/android/tools/r8/kotlin/KmVisitorProviders.java
index 1ac680a..84d6e82 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KmVisitorProviders.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KmVisitorProviders.java
@@ -95,4 +95,10 @@
 
     KmValueParameterVisitor get(int flags, String name);
   }
+
+  @FunctionalInterface
+  public interface KmFlexibleUpperBoundVisitorProvider {
+
+    KmTypeVisitor get(int flags, String typeFlexibilityId);
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/kotlin/Kotlin.java b/src/main/java/com/android/tools/r8/kotlin/Kotlin.java
index b225440..602165c 100644
--- a/src/main/java/com/android/tools/r8/kotlin/Kotlin.java
+++ b/src/main/java/com/android/tools/r8/kotlin/Kotlin.java
@@ -161,7 +161,6 @@
   }
 
   public final class Metadata {
-    public final DexType kotlinMetadataType = factory.createType(addKotlinPrefix("Metadata;"));
     public final DexString kind = factory.createString("k");
     public final DexString metadataVersion = factory.createString("mv");
     public final DexString bytecodeVersion = factory.createString("bv");
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinClassInfo.java b/src/main/java/com/android/tools/r8/kotlin/KotlinClassInfo.java
index 2cb8fe4..b507093 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinClassInfo.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinClassInfo.java
@@ -23,6 +23,7 @@
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.function.Consumer;
 import kotlinx.metadata.KmClass;
 import kotlinx.metadata.KmConstructor;
 import kotlinx.metadata.KmType;
@@ -45,6 +46,7 @@
   // TODO(b/154347404): Understand enum entries.
   private final List<String> enumEntries;
   private final DexType anonymousObjectOrigin;
+  private final String packageName;
 
   public KotlinClassInfo(
       int flags,
@@ -57,7 +59,8 @@
       List<DexType> sealedSubClasses,
       List<DexType> nestedClasses,
       List<String> enumEntries,
-      DexType anonymousObjectOrigin) {
+      DexType anonymousObjectOrigin,
+      String packageName) {
     this.flags = flags;
     this.name = name;
     this.moduleName = moduleName;
@@ -69,13 +72,16 @@
     this.nestedClasses = nestedClasses;
     this.enumEntries = enumEntries;
     this.anonymousObjectOrigin = anonymousObjectOrigin;
+    this.packageName = packageName;
   }
 
   public static KotlinClassInfo create(
       KmClass kmClass,
+      String packageName,
       DexClass hostClass,
       DexDefinitionSupplier definitionSupplier,
-      Reporter reporter) {
+      Reporter reporter,
+      Consumer<DexEncodedMethod> keepByteCode) {
     Map<String, DexEncodedField> fieldMap = new HashMap<>();
     for (DexEncodedField field : hostClass.fields()) {
       fieldMap.put(toJvmFieldSignature(field.field).asString(), field);
@@ -101,7 +107,7 @@
     }
     KotlinDeclarationContainerInfo container =
         KotlinDeclarationContainerInfo.create(
-            kmClass, methodMap, fieldMap, definitionSupplier, reporter);
+            kmClass, methodMap, fieldMap, definitionSupplier, reporter, keepByteCode);
     setCompanionObject(kmClass, hostClass, reporter);
     return new KotlinClassInfo(
         kmClass.getFlags(),
@@ -114,7 +120,8 @@
         getSealedSubClasses(hostClass, kmClass.getSealedSubclasses(), definitionSupplier),
         getNestedClasses(hostClass, kmClass.getNestedClasses(), definitionSupplier),
         kmClass.getEnumEntries(),
-        getAnonymousObjectOrigin(kmClass, definitionSupplier));
+        getAnonymousObjectOrigin(kmClass, definitionSupplier),
+        packageName);
   }
 
   private static DexType getAnonymousObjectOrigin(
@@ -267,4 +274,9 @@
     kmClass.accept(writer);
     return writer.write().getHeader();
   }
+
+  @Override
+  public String getPackageName() {
+    return packageName;
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinClassLevelInfo.java b/src/main/java/com/android/tools/r8/kotlin/KotlinClassLevelInfo.java
index 76bfb00..f9662ef 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinClassLevelInfo.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinClassLevelInfo.java
@@ -54,4 +54,6 @@
 
   KotlinClassHeader rewrite(
       DexClass clazz, AppView<AppInfoWithLiveness> appView, NamingLens namingLens);
+
+  String getPackageName();
 }
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinClassMetadataReader.java b/src/main/java/com/android/tools/r8/kotlin/KotlinClassMetadataReader.java
index a1ad70f..5fe02b2 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinClassMetadataReader.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinClassMetadataReader.java
@@ -11,6 +11,7 @@
 import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexDefinitionSupplier;
 import com.android.tools.r8.graph.DexEncodedAnnotation;
+import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexString;
 import com.android.tools.r8.graph.DexValue;
 import com.android.tools.r8.graph.DexValue.DexValueArray;
@@ -18,18 +19,34 @@
 import com.android.tools.r8.utils.StringDiagnostic;
 import java.util.IdentityHashMap;
 import java.util.Map;
+import java.util.function.Consumer;
 import kotlinx.metadata.InconsistentKotlinMetadataException;
 import kotlinx.metadata.jvm.KotlinClassHeader;
 import kotlinx.metadata.jvm.KotlinClassMetadata;
 
 public final class KotlinClassMetadataReader {
 
+  private static int KOTLIN_METADATA_KIND_LAMBDA = 3;
+
   public static KotlinClassLevelInfo getKotlinInfo(
-      Kotlin kotlin, DexClass clazz, DexDefinitionSupplier definitionSupplier, Reporter reporter) {
-    DexAnnotation meta = clazz.annotations().getFirstMatching(kotlin.metadata.kotlinMetadataType);
+      Kotlin kotlin,
+      DexClass clazz,
+      DexDefinitionSupplier definitionSupplier,
+      Reporter reporter,
+      boolean onlyProcessLambda,
+      Consumer<DexEncodedMethod> keepByteCode) {
+    DexAnnotation meta =
+        clazz
+            .annotations()
+            .getFirstMatching(definitionSupplier.dexItemFactory().kotlinMetadataType);
     if (meta != null) {
       try {
-        return createKotlinInfo(kotlin, clazz, meta.annotation, definitionSupplier, reporter);
+        KotlinClassMetadata kMetadata = toKotlinClassMetadata(kotlin, meta.annotation);
+        if (onlyProcessLambda && kMetadata.getHeader().getKind() != KOTLIN_METADATA_KIND_LAMBDA) {
+          return NO_KOTLIN_INFO;
+        }
+        return createKotlinInfo(
+            kotlin, clazz, kMetadata, definitionSupplier, reporter, keepByteCode);
       } catch (ClassCastException | InconsistentKotlinMetadataException | MetadataError e) {
         reporter.info(
             new StringDiagnostic(
@@ -85,29 +102,45 @@
   public static KotlinClassLevelInfo createKotlinInfo(
       Kotlin kotlin,
       DexClass clazz,
-      DexEncodedAnnotation metadataAnnotation,
+      KotlinClassMetadata kMetadata,
       DexDefinitionSupplier definitionSupplier,
-      Reporter reporter) {
-    KotlinClassMetadata kMetadata = toKotlinClassMetadata(kotlin, metadataAnnotation);
-
+      Reporter reporter,
+      Consumer<DexEncodedMethod> keepByteCode) {
+    String packageName = kMetadata.getHeader().getPackageName();
     if (kMetadata instanceof KotlinClassMetadata.Class) {
       return KotlinClassInfo.create(
-          ((KotlinClassMetadata.Class) kMetadata).toKmClass(), clazz, definitionSupplier, reporter);
+          ((KotlinClassMetadata.Class) kMetadata).toKmClass(),
+          packageName,
+          clazz,
+          definitionSupplier,
+          reporter,
+          keepByteCode);
     } else if (kMetadata instanceof KotlinClassMetadata.FileFacade) {
       // e.g., B.kt becomes class `BKt`
       return KotlinFileFacadeInfo.create(
-          (KotlinClassMetadata.FileFacade) kMetadata, clazz, definitionSupplier, reporter);
+          (KotlinClassMetadata.FileFacade) kMetadata,
+          packageName,
+          clazz,
+          definitionSupplier,
+          reporter,
+          keepByteCode);
     } else if (kMetadata instanceof KotlinClassMetadata.MultiFileClassFacade) {
       // multi-file class with the same @JvmName.
       return KotlinMultiFileClassFacadeInfo.create(
-          (KotlinClassMetadata.MultiFileClassFacade) kMetadata, definitionSupplier);
+          (KotlinClassMetadata.MultiFileClassFacade) kMetadata, packageName, definitionSupplier);
     } else if (kMetadata instanceof KotlinClassMetadata.MultiFileClassPart) {
       // A single file, which is part of multi-file class.
       return KotlinMultiFileClassPartInfo.create(
-          (KotlinClassMetadata.MultiFileClassPart) kMetadata, clazz, definitionSupplier, reporter);
+          (KotlinClassMetadata.MultiFileClassPart) kMetadata,
+          packageName,
+          clazz,
+          definitionSupplier,
+          reporter,
+          keepByteCode);
     } else if (kMetadata instanceof KotlinClassMetadata.SyntheticClass) {
       return KotlinSyntheticClassInfo.create(
           (KotlinClassMetadata.SyntheticClass) kMetadata,
+          packageName,
           clazz,
           kotlin,
           definitionSupplier,
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinClassifierInfo.java b/src/main/java/com/android/tools/r8/kotlin/KotlinClassifierInfo.java
index 60851e2..d6ff5b4 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinClassifierInfo.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinClassifierInfo.java
@@ -25,19 +25,19 @@
   public static KotlinClassifierInfo create(
       KmClassifier classifier, DexDefinitionSupplier definitionSupplier, Reporter reporter) {
     if (classifier instanceof KmClassifier.Class) {
-      String typeName = ((KmClassifier.Class) classifier).getName();
+      String originalTypeName = ((KmClassifier.Class) classifier).getName();
       // If this name starts with '.', it represents a local class or an anonymous object. This is
       // used by the Kotlin compiler to prevent lookup of this name in the resolution:
       // .kotlin/random/FallbackThreadLocalRandom$implStorage$1
-      if (typeName.startsWith(".")) {
-        return new KotlinUnknownClassClassifierInfo(typeName);
-      }
-      String descriptor = DescriptorUtils.getDescriptorFromKotlinClassifier(typeName);
+      boolean isLocalOrAnonymous = originalTypeName.startsWith(".");
+      String descriptor =
+          DescriptorUtils.getDescriptorFromKotlinClassifier(
+              isLocalOrAnonymous ? originalTypeName.substring(1) : originalTypeName);
       if (DescriptorUtils.isClassDescriptor(descriptor)) {
         return new KotlinClassClassifierInfo(
-            referenceTypeFromDescriptor(descriptor, definitionSupplier));
+            referenceTypeFromDescriptor(descriptor, definitionSupplier), isLocalOrAnonymous);
       } else {
-        return new KotlinUnknownClassClassifierInfo(typeName);
+        return new KotlinUnknownClassClassifierInfo(originalTypeName);
       }
     } else if (classifier instanceof KmClassifier.TypeAlias) {
       return new KotlinTypeAliasClassifierInfo(((TypeAlias) classifier).getName());
@@ -55,22 +55,29 @@
   public static class KotlinClassClassifierInfo extends KotlinClassifierInfo {
 
     private final DexType type;
+    private final boolean isLocalOrAnonymous;
 
-    private KotlinClassClassifierInfo(DexType type) {
+    private KotlinClassClassifierInfo(DexType type, boolean isLocalOrAnonymous) {
       this.type = type;
+      this.isLocalOrAnonymous = isLocalOrAnonymous;
     }
 
     @Override
     void rewrite(
         KmTypeVisitor visitor, AppView<AppInfoWithLiveness> appView, NamingLens namingLens) {
-      String classifier;
       if (appView.appInfo().wasPruned(type)) {
-        classifier = ClassClassifiers.anyName;
-      } else {
-        DexString descriptor = namingLens.lookupDescriptor(type);
-        classifier = DescriptorUtils.descriptorToKotlinClassifier(descriptor.toString());
+        visitor.visitClass(ClassClassifiers.anyName);
+        return;
       }
-      visitor.visitClass(classifier);
+      DexString descriptor = namingLens.lookupDescriptor(type);
+      // For local or anonymous classes, the classifier is prefixed with '.' and inner classes are
+      // separated with '$'.
+      if (isLocalOrAnonymous) {
+        visitor.visitClass(
+            "." + DescriptorUtils.getBinaryNameFromDescriptor(descriptor.toString()));
+      } else {
+        visitor.visitClass(DescriptorUtils.descriptorToKotlinClassifier(descriptor.toString()));
+      }
     }
   }
 
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinDeclarationContainerInfo.java b/src/main/java/com/android/tools/r8/kotlin/KotlinDeclarationContainerInfo.java
index 6dc4d4a..cd349ab 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinDeclarationContainerInfo.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinDeclarationContainerInfo.java
@@ -19,10 +19,12 @@
 import java.util.IdentityHashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.function.Consumer;
 import kotlinx.metadata.KmDeclarationContainer;
 import kotlinx.metadata.KmFunction;
 import kotlinx.metadata.KmProperty;
 import kotlinx.metadata.KmTypeAlias;
+import kotlinx.metadata.internal.metadata.deserialization.Flags;
 import kotlinx.metadata.jvm.JvmExtensionsKt;
 import kotlinx.metadata.jvm.JvmMethodSignature;
 
@@ -50,7 +52,8 @@
       Map<String, DexEncodedMethod> methodSignatureMap,
       Map<String, DexEncodedField> fieldSignatureMap,
       DexDefinitionSupplier definitionSupplier,
-      Reporter reporter) {
+      Reporter reporter,
+      Consumer<DexEncodedMethod> keepByteCode) {
     ImmutableList.Builder<KotlinFunctionInfo> notBackedFunctions = ImmutableList.builder();
     for (KmFunction kmFunction : container.getFunctions()) {
       JvmMethodSignature signature = JvmExtensionsKt.getSignature(kmFunction);
@@ -75,6 +78,7 @@
         }
         continue;
       }
+      keepIfInline(kmFunction.getFlags(), method, keepByteCode);
       method.setKotlinMemberInfo(kotlinFunctionInfo);
     }
 
@@ -97,6 +101,7 @@
             methodSignatureMap.get(propertyProcessor.getterSignature().asString());
         if (method != null) {
           hasBacking = true;
+          keepIfAccessorInline(kmProperty.getGetterFlags(), method, keepByteCode);
           method.setKotlinMemberInfo(kotlinPropertyInfo);
         }
       }
@@ -105,6 +110,7 @@
             methodSignatureMap.get(propertyProcessor.setterSignature().asString());
         if (method != null) {
           hasBacking = true;
+          keepIfAccessorInline(kmProperty.getGetterFlags(), method, keepByteCode);
           method.setKotlinMemberInfo(kotlinPropertyInfo);
         }
       }
@@ -118,6 +124,20 @@
         notBackedProperties.build());
   }
 
+  private static void keepIfInline(
+      int flags, DexEncodedMethod method, Consumer<DexEncodedMethod> keepByteCode) {
+    if (Flags.IS_INLINE.get(flags)) {
+      keepByteCode.accept(method);
+    }
+  }
+
+  private static void keepIfAccessorInline(
+      int flags, DexEncodedMethod method, Consumer<DexEncodedMethod> keepByteCode) {
+    if (Flags.IS_INLINE_ACCESSOR.get(flags)) {
+      keepByteCode.accept(method);
+    }
+  }
+
   private static List<KotlinTypeAliasInfo> getTypeAliases(
       List<KmTypeAlias> aliases, DexDefinitionSupplier definitionSupplier, Reporter reporter) {
     ImmutableList.Builder<KotlinTypeAliasInfo> builder = ImmutableList.builder();
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinFileFacadeInfo.java b/src/main/java/com/android/tools/r8/kotlin/KotlinFileFacadeInfo.java
index ccd957a..c31df8c 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinFileFacadeInfo.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinFileFacadeInfo.java
@@ -7,9 +7,11 @@
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexDefinitionSupplier;
+import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.naming.NamingLens;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.utils.Reporter;
+import java.util.function.Consumer;
 import kotlinx.metadata.KmPackage;
 import kotlinx.metadata.jvm.KotlinClassHeader;
 import kotlinx.metadata.jvm.KotlinClassMetadata;
@@ -19,18 +21,24 @@
 public class KotlinFileFacadeInfo implements KotlinClassLevelInfo {
 
   private final KotlinPackageInfo packageInfo;
+  private final String packageName;
 
-  private KotlinFileFacadeInfo(KotlinPackageInfo packageInfo) {
+  private KotlinFileFacadeInfo(KotlinPackageInfo packageInfo, String packageName) {
     this.packageInfo = packageInfo;
+    this.packageName = packageName;
   }
 
   public static KotlinFileFacadeInfo create(
       FileFacade kmFileFacade,
+      String packageName,
       DexClass clazz,
       DexDefinitionSupplier definitionSupplier,
-      Reporter reporter) {
+      Reporter reporter,
+      Consumer<DexEncodedMethod> keepByteCode) {
     return new KotlinFileFacadeInfo(
-        KotlinPackageInfo.create(kmFileFacade.toKmPackage(), clazz, definitionSupplier, reporter));
+        KotlinPackageInfo.create(
+            kmFileFacade.toKmPackage(), clazz, definitionSupplier, reporter, keepByteCode),
+        packageName);
   }
 
   @Override
@@ -52,4 +60,9 @@
     kmPackage.accept(writer);
     return writer.write().getHeader();
   }
+
+  @Override
+  public String getPackageName() {
+    return packageName;
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinFlexibleTypeUpperBoundInfo.java b/src/main/java/com/android/tools/r8/kotlin/KotlinFlexibleTypeUpperBoundInfo.java
new file mode 100644
index 0000000..c8c8836
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinFlexibleTypeUpperBoundInfo.java
@@ -0,0 +1,77 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.kotlin;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexDefinitionSupplier;
+import com.android.tools.r8.naming.NamingLens;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.utils.Reporter;
+import java.util.List;
+import kotlinx.metadata.KmFlexibleTypeUpperBound;
+import kotlinx.metadata.KmType;
+import kotlinx.metadata.jvm.JvmExtensionsKt;
+
+public class KotlinFlexibleTypeUpperBoundInfo extends KotlinTypeInfo {
+
+  private static final String KOTLIN_JVM_PLATFORMTYPE = "kotlin.jvm.PlatformType";
+  private static final KotlinFlexibleTypeUpperBoundInfo NO_FLEXIBLE_UPPER_BOUND =
+      new KotlinFlexibleTypeUpperBoundInfo(
+          0, null, null, null, null, null, null, KOTLIN_JVM_PLATFORMTYPE);
+
+  private final String typeFlexibilityId;
+
+  private KotlinFlexibleTypeUpperBoundInfo(
+      int flags,
+      KotlinClassifierInfo classifier,
+      KotlinTypeInfo abbreviatedType,
+      KotlinTypeInfo outerType,
+      List<KotlinTypeProjectionInfo> arguments,
+      List<KotlinAnnotationInfo> annotations,
+      KotlinFlexibleTypeUpperBoundInfo flexibleTypeUpperBoundInfo,
+      String typeFlexibilityId) {
+    super(
+        flags,
+        classifier,
+        abbreviatedType,
+        outerType,
+        arguments,
+        annotations,
+        flexibleTypeUpperBoundInfo);
+    this.typeFlexibilityId = typeFlexibilityId;
+    assert KOTLIN_JVM_PLATFORMTYPE.equals(typeFlexibilityId);
+  }
+
+  static KotlinFlexibleTypeUpperBoundInfo create(
+      KmFlexibleTypeUpperBound flexibleTypeUpperBound,
+      DexDefinitionSupplier definitionSupplier,
+      Reporter reporter) {
+    if (flexibleTypeUpperBound == null) {
+      return NO_FLEXIBLE_UPPER_BOUND;
+    }
+    KmType kmType = flexibleTypeUpperBound.getType();
+    return new KotlinFlexibleTypeUpperBoundInfo(
+        kmType.getFlags(),
+        KotlinClassifierInfo.create(kmType.classifier, definitionSupplier, reporter),
+        KotlinTypeInfo.create(kmType.getAbbreviatedType(), definitionSupplier, reporter),
+        KotlinTypeInfo.create(kmType.getOuterType(), definitionSupplier, reporter),
+        getArguments(kmType.getArguments(), definitionSupplier, reporter),
+        KotlinAnnotationInfo.create(JvmExtensionsKt.getAnnotations(kmType), definitionSupplier),
+        KotlinFlexibleTypeUpperBoundInfo.create(
+            kmType.getFlexibleTypeUpperBound(), definitionSupplier, reporter),
+        flexibleTypeUpperBound.getTypeFlexibilityId());
+  }
+
+  public void rewrite(
+      KmVisitorProviders.KmFlexibleUpperBoundVisitorProvider visitorProvider,
+      AppView<AppInfoWithLiveness> appView,
+      NamingLens namingLens) {
+    if (this == NO_FLEXIBLE_UPPER_BOUND) {
+      // Nothing to do.
+      return;
+    }
+    super.rewrite(flags -> visitorProvider.get(flags, typeFlexibilityId), appView, namingLens);
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataEnqueuerExtension.java b/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataEnqueuerExtension.java
index d280d13..28928ef 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataEnqueuerExtension.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataEnqueuerExtension.java
@@ -5,25 +5,49 @@
 package com.android.tools.r8.kotlin;
 
 import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexDefinitionSupplier;
-import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.analysis.EnqueuerAnalysis;
-import com.android.tools.r8.shaking.EnqueuerWorklist;
+import com.android.tools.r8.shaking.Enqueuer;
+import com.google.common.collect.Sets;
+import java.util.Set;
 
 public class KotlinMetadataEnqueuerExtension extends EnqueuerAnalysis {
 
   private final AppView<?> appView;
+  private final DexDefinitionSupplier definitionSupplier;
+  private final Set<DexMethod> keepByteCodeFunctions = Sets.newIdentityHashSet();
 
-  public KotlinMetadataEnqueuerExtension(AppView<?> appView) {
+  public KotlinMetadataEnqueuerExtension(
+      AppView<?> appView, DexDefinitionSupplier definitionSupplier) {
     this.appView = appView;
+    this.definitionSupplier = definitionSupplier;
   }
 
   @Override
-  public void processNewlyLiveClass(
-      DexProgramClass clazz, EnqueuerWorklist worklist, DexDefinitionSupplier definitionSupplier) {
-    Kotlin kotlin = appView.dexItemFactory().kotlin;
-    clazz.setKotlinInfo(
-        KotlinClassMetadataReader.getKotlinInfo(
-            kotlin, clazz, definitionSupplier, appView.options().reporter));
+  public void done(Enqueuer enqueuer) {
+    DexType kotlinMetadataType = appView.dexItemFactory().kotlinMetadataType;
+    DexClass kotlinMetadataClass =
+        appView.appInfo().definitionForWithoutExistenceAssert(kotlinMetadataType);
+    // We will process kotlin.Metadata even if the type is not present in the program, as long as
+    // the annotation will be in the output
+    boolean keepMetadata =
+        enqueuer.isPinned(kotlinMetadataType)
+            || enqueuer.isMissing(kotlinMetadataType)
+            || (kotlinMetadataClass != null && kotlinMetadataClass.isNotProgramClass());
+    enqueuer.forAllLiveClasses(
+        clazz -> {
+          clazz.setKotlinInfo(
+              KotlinClassMetadataReader.getKotlinInfo(
+                  appView.dexItemFactory().kotlin,
+                  clazz,
+                  definitionSupplier,
+                  appView.options().reporter,
+                  !keepMetadata || !enqueuer.isPinned(clazz.type),
+                  method -> keepByteCodeFunctions.add(method.method)));
+        });
+    appView.setCfByteCodePassThrough(keepByteCodeFunctions);
   }
 }
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataRewriter.java b/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataRewriter.java
index 31818e8..ce0ba80 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataRewriter.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataRewriter.java
@@ -9,11 +9,8 @@
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexAnnotation;
 import com.android.tools.r8.graph.DexAnnotationElement;
-import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexEncodedAnnotation;
 import com.android.tools.r8.graph.DexItemFactory;
-import com.android.tools.r8.graph.DexProgramClass;
-import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.DexValue;
 import com.android.tools.r8.graph.DexValue.DexValueArray;
 import com.android.tools.r8.graph.DexValue.DexValueInt;
@@ -41,30 +38,8 @@
     this.kotlin = factory.kotlin;
   }
 
-  public static void removeKotlinMetadataFromRenamedClass(AppView<?> appView, DexType type) {
-    // TODO(b/154294232): Seems like this should never be called. Either we explicitly keep the
-    //  class and allow obfuscation - in which we should not remove it, or we should never have
-    //  metadata in the first place.
-    DexProgramClass clazz = appView.definitionForProgramType(type);
-    if (clazz == null) {
-      return;
-    }
-    removeKotlinMetadataFromRenamedClass(appView, clazz);
-  }
-
-  public static void removeKotlinMetadataFromRenamedClass(AppView<?> appView, DexClass clazz) {
-    // Remove @Metadata in DexAnnotation form if a class is renamed.
-    clazz.setAnnotations(clazz.annotations().keepIf(anno -> isNotKotlinMetadata(appView, anno)));
-    // Clear associated {@link KotlinInfo} to avoid accidentally deserialize it back to
-    // DexAnnotation we've just removed above.
-    if (clazz.isProgramClass()) {
-      clazz.asProgramClass().setKotlinInfo(NO_KOTLIN_INFO);
-    }
-  }
-
   private static boolean isNotKotlinMetadata(AppView<?> appView, DexAnnotation annotation) {
-    return annotation.annotation.type
-        != appView.dexItemFactory().kotlin.metadata.kotlinMetadataType;
+    return annotation.annotation.type != appView.dexItemFactory().kotlinMetadataType;
   }
 
   public void run(ExecutorService executorService) throws ExecutionException {
@@ -72,34 +47,40 @@
         appView.appInfo().classes(),
         clazz -> {
           KotlinClassLevelInfo kotlinInfo = clazz.getKotlinInfo();
-          DexAnnotation oldMeta =
-              clazz.annotations().getFirstMatching(kotlin.metadata.kotlinMetadataType);
+          DexAnnotation oldMeta = clazz.annotations().getFirstMatching(factory.kotlinMetadataType);
           if (kotlinInfo == INVALID_KOTLIN_INFO) {
             // Maintain invalid kotlin info for classes.
             return;
           }
-          if (kotlinInfo == NO_KOTLIN_INFO) {
-            assert oldMeta == null;
+          if (oldMeta == null
+              || kotlinInfo == NO_KOTLIN_INFO
+              || !appView.appInfo().isPinned(clazz.type)) {
+            // Remove @Metadata in DexAnnotation when there is no kotlin info and the type is not
+            // missing.
+            if (oldMeta != null) {
+              clazz.setAnnotations(
+                  clazz.annotations().keepIf(anno -> isNotKotlinMetadata(appView, anno)));
+            }
             return;
           }
-          if (oldMeta != null) {
-            try {
-              KotlinClassHeader kotlinClassHeader = kotlinInfo.rewrite(clazz, appView, lens);
-              DexAnnotation newMeta = createKotlinMetadataAnnotation(kotlinClassHeader);
-              clazz.setAnnotations(
-                  clazz.annotations().rewrite(anno -> anno == oldMeta ? newMeta : anno));
-            } catch (Throwable t) {
-              appView
-                  .options()
-                  .reporter
-                  .warning(KotlinMetadataDiagnostic.unexpectedErrorWhenRewriting(clazz.type, t));
-            }
+          try {
+            KotlinClassHeader kotlinClassHeader = kotlinInfo.rewrite(clazz, appView, lens);
+            DexAnnotation newMeta =
+                createKotlinMetadataAnnotation(kotlinClassHeader, kotlinInfo.getPackageName());
+            clazz.setAnnotations(
+                clazz.annotations().rewrite(anno -> anno == oldMeta ? newMeta : anno));
+          } catch (Throwable t) {
+            appView
+                .options()
+                .reporter
+                .warning(KotlinMetadataDiagnostic.unexpectedErrorWhenRewriting(clazz.type, t));
           }
         },
         executorService);
   }
 
-  private DexAnnotation createKotlinMetadataAnnotation(KotlinClassHeader header) {
+  private DexAnnotation createKotlinMetadataAnnotation(
+      KotlinClassHeader header, String packageName) {
     List<DexAnnotationElement> elements = new ArrayList<>();
     elements.add(
         new DexAnnotationElement(kotlin.metadata.kind, DexValueInt.create(header.getKind())));
@@ -119,14 +100,13 @@
             new DexValueString(factory.createString(header.getExtraString()))));
     elements.add(
         new DexAnnotationElement(
-            kotlin.metadata.packageName,
-            new DexValueString(factory.createString(header.getPackageName()))));
+            kotlin.metadata.packageName, new DexValueString(factory.createString(packageName))));
     elements.add(
         new DexAnnotationElement(
             kotlin.metadata.extraInt, DexValueInt.create(header.getExtraInt())));
     DexEncodedAnnotation encodedAnnotation =
         new DexEncodedAnnotation(
-            kotlin.metadata.kotlinMetadataType, elements.toArray(DexAnnotationElement.EMPTY_ARRAY));
+            factory.kotlinMetadataType, elements.toArray(DexAnnotationElement.EMPTY_ARRAY));
     return new DexAnnotation(DexAnnotation.VISIBILITY_RUNTIME, encodedAnnotation);
   }
 
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataUtils.java b/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataUtils.java
index e846c14..1f21451 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataUtils.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataUtils.java
@@ -10,11 +10,16 @@
 import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexDefinitionSupplier;
 import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexString;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.naming.NamingLens;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.shaking.ProguardConfiguration;
+import com.android.tools.r8.shaking.ProguardConfigurationRule;
+import com.android.tools.r8.shaking.ProguardKeepRule;
+import com.android.tools.r8.shaking.ProguardKeepRuleType;
 import com.android.tools.r8.utils.DescriptorUtils;
 import kotlinx.metadata.KmExtensionType;
 import kotlinx.metadata.KmProperty;
@@ -49,6 +54,11 @@
         DexClass clazz, AppView<AppInfoWithLiveness> appView, NamingLens namingLens) {
       throw new Unreachable("Should never be called");
     }
+
+    @Override
+    public String getPackageName() {
+      throw new Unreachable("Should never be called");
+    }
   }
 
   static JvmFieldSignature toJvmFieldSignature(DexField field) {
@@ -161,4 +171,47 @@
     }
     return type;
   }
+
+  public static boolean mayProcessKotlinMetadata(AppView<?> appView) {
+    // This can run before we have determined the pinned items, because we may need to load the
+    // stack-map table on input. This is therefore a conservative guess on kotlin.Metadata is kept.
+    DexClass kotlinMetadata =
+        appView
+            .appInfo()
+            .definitionForWithoutExistenceAssert(appView.dexItemFactory().kotlinMetadataType);
+    if (kotlinMetadata == null || kotlinMetadata.isNotProgramClass()) {
+      return true;
+    }
+    ProguardConfiguration proguardConfiguration = appView.options().getProguardConfiguration();
+    if (proguardConfiguration != null && proguardConfiguration.getRules() != null) {
+      for (ProguardConfigurationRule rule : proguardConfiguration.getRules()) {
+        if (KotlinMetadataUtils.canBeKotlinMetadataKeepRule(rule, appView.options().itemFactory)) {
+          return true;
+        }
+      }
+    }
+    return false;
+  }
+
+  private static boolean canBeKotlinMetadataKeepRule(
+      ProguardConfigurationRule rule, DexItemFactory factory) {
+    if (rule.isProguardIfRule()) {
+      // For if rules, we simply assume that the precondition can become true.
+      return canBeKotlinMetadataKeepRule(rule.asProguardIfRule().getSubsequentRule(), factory);
+    }
+    if (!rule.isProguardKeepRule()) {
+      return false;
+    }
+    ProguardKeepRule proguardKeepRule = rule.asProguardKeepRule();
+    // -keepclassmembers will not in itself keep a class alive.
+    if (proguardKeepRule.getType() == ProguardKeepRuleType.KEEP_CLASS_MEMBERS) {
+      return false;
+    }
+    // If the rule allows shrinking, it will not require us to keep the class.
+    if (proguardKeepRule.getModifiers().allowsShrinking) {
+      return false;
+    }
+    // Check if the type is matched
+    return proguardKeepRule.getClassNames().matches(factory.kotlinMetadataType);
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataWriter.java b/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataWriter.java
index 33a0aa6..fa21f81 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataWriter.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataWriter.java
@@ -23,6 +23,7 @@
 import kotlinx.metadata.KmClass;
 import kotlinx.metadata.KmConstructor;
 import kotlinx.metadata.KmDeclarationContainer;
+import kotlinx.metadata.KmFlexibleTypeUpperBound;
 import kotlinx.metadata.KmFunction;
 import kotlinx.metadata.KmLambda;
 import kotlinx.metadata.KmPackage;
@@ -43,7 +44,7 @@
 
   public static void writeKotlinMetadataAnnotation(
       String prefix, DexAnnotation annotation, PrintStream ps, Kotlin kotlin) {
-    assert annotation.annotation.type == kotlin.metadata.kotlinMetadataType;
+    assert annotation.annotation.type == kotlin.factory.kotlinMetadataType;
     try {
       KotlinClassMetadata kMetadata =
           KotlinClassMetadataReader.toKotlinClassMetadata(kotlin, annotation.annotation);
@@ -525,6 +526,33 @@
               "outerType",
               sb,
               nextIndent -> appendKmType(newIndent, sb, kmType.getOuterType()));
+          KmFlexibleTypeUpperBound flexibleTypeUpperBound = kmType.getFlexibleTypeUpperBound();
+          if (flexibleTypeUpperBound != null) {
+            appendKeyValue(
+                newIndent,
+                "flexibleTypeUpperBound",
+                sb,
+                nextIndent -> {
+                  appendKmSection(
+                      newIndent,
+                      "FlexibleTypeUpperBound",
+                      sb,
+                      nextNextIndent -> {
+                        appendKeyValue(
+                            nextNextIndent,
+                            "typeFlexibilityId",
+                            sb,
+                            flexibleTypeUpperBound.getTypeFlexibilityId());
+                        appendKeyValue(
+                            nextNextIndent,
+                            "type",
+                            sb,
+                            nextNextNextIndent ->
+                                appendKmType(
+                                    nextNextNextIndent, sb, flexibleTypeUpperBound.getType()));
+                      });
+                });
+          }
           appendKeyValue(newIndent, "raw", sb, JvmExtensionsKt.isRaw(kmType) + "");
           appendKeyValue(
               newIndent,
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinMultiFileClassFacadeInfo.java b/src/main/java/com/android/tools/r8/kotlin/KotlinMultiFileClassFacadeInfo.java
index 7eaa6b8..3baea9c 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinMultiFileClassFacadeInfo.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinMultiFileClassFacadeInfo.java
@@ -23,20 +23,24 @@
 public class KotlinMultiFileClassFacadeInfo implements KotlinClassLevelInfo {
 
   private final List<DexType> partClassNames;
+  private final String packageName;
 
-  private KotlinMultiFileClassFacadeInfo(List<DexType> partClassNames) {
+  private KotlinMultiFileClassFacadeInfo(List<DexType> partClassNames, String packageName) {
     this.partClassNames = partClassNames;
+    this.packageName = packageName;
   }
 
   static KotlinMultiFileClassFacadeInfo create(
-      MultiFileClassFacade kmMultiFileClassFacade, DexDefinitionSupplier appView) {
+      MultiFileClassFacade kmMultiFileClassFacade,
+      String packageName,
+      DexDefinitionSupplier definitionSupplier) {
     ImmutableList.Builder<DexType> builder = ImmutableList.builder();
     for (String partClassName : kmMultiFileClassFacade.getPartClassNames()) {
       String descriptor = DescriptorUtils.getDescriptorFromClassBinaryName(partClassName);
-      DexType type = appView.dexItemFactory().createType(descriptor);
+      DexType type = definitionSupplier.dexItemFactory().createType(descriptor);
       builder.add(type);
     }
-    return new KotlinMultiFileClassFacadeInfo(builder.build());
+    return new KotlinMultiFileClassFacadeInfo(builder.build(), packageName);
   }
 
   @Override
@@ -64,4 +68,9 @@
     }
     return writer.write(partClassNameStrings).getHeader();
   }
+
+  @Override
+  public String getPackageName() {
+    return packageName;
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinMultiFileClassPartInfo.java b/src/main/java/com/android/tools/r8/kotlin/KotlinMultiFileClassPartInfo.java
index 6a06b7d..d4ec800 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinMultiFileClassPartInfo.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinMultiFileClassPartInfo.java
@@ -7,9 +7,11 @@
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexDefinitionSupplier;
+import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.naming.NamingLens;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.utils.Reporter;
+import java.util.function.Consumer;
 import kotlinx.metadata.KmPackage;
 import kotlinx.metadata.jvm.KotlinClassHeader;
 import kotlinx.metadata.jvm.KotlinClassMetadata;
@@ -20,20 +22,27 @@
 
   private final String facadeClassName;
   private final KotlinPackageInfo packageInfo;
+  private final String packageName;
 
-  private KotlinMultiFileClassPartInfo(String facadeClassName, KotlinPackageInfo packageInfo) {
+  private KotlinMultiFileClassPartInfo(
+      String facadeClassName, KotlinPackageInfo packageInfo, String packageName) {
     this.facadeClassName = facadeClassName;
     this.packageInfo = packageInfo;
+    this.packageName = packageName;
   }
 
   static KotlinMultiFileClassPartInfo create(
       MultiFileClassPart classPart,
+      String packageName,
       DexClass clazz,
       DexDefinitionSupplier definitionSupplier,
-      Reporter reporter) {
+      Reporter reporter,
+      Consumer<DexEncodedMethod> keepByteCode) {
     return new KotlinMultiFileClassPartInfo(
         classPart.getFacadeClassName(),
-        KotlinPackageInfo.create(classPart.toKmPackage(), clazz, definitionSupplier, reporter));
+        KotlinPackageInfo.create(
+            classPart.toKmPackage(), clazz, definitionSupplier, reporter, keepByteCode),
+        packageName);
   }
 
   @Override
@@ -56,4 +65,9 @@
     kmPackage.accept(writer);
     return writer.write(facadeClassName).getHeader();
   }
+
+  @Override
+  public String getPackageName() {
+    return packageName;
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinPackageInfo.java b/src/main/java/com/android/tools/r8/kotlin/KotlinPackageInfo.java
index 7a667e6..d98e109 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinPackageInfo.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinPackageInfo.java
@@ -17,6 +17,7 @@
 import com.android.tools.r8.utils.Reporter;
 import java.util.HashMap;
 import java.util.Map;
+import java.util.function.Consumer;
 import kotlinx.metadata.KmPackage;
 import kotlinx.metadata.jvm.JvmExtensionsKt;
 
@@ -35,7 +36,8 @@
       KmPackage kmPackage,
       DexClass clazz,
       DexDefinitionSupplier definitionSupplier,
-      Reporter reporter) {
+      Reporter reporter,
+      Consumer<DexEncodedMethod> keepByteCode) {
     Map<String, DexEncodedField> fieldMap = new HashMap<>();
     for (DexEncodedField field : clazz.fields()) {
       fieldMap.put(toJvmFieldSignature(field.field).asString(), field);
@@ -47,7 +49,7 @@
     return new KotlinPackageInfo(
         JvmExtensionsKt.getModuleName(kmPackage),
         KotlinDeclarationContainerInfo.create(
-            kmPackage, methodMap, fieldMap, definitionSupplier, reporter));
+            kmPackage, methodMap, fieldMap, definitionSupplier, reporter, keepByteCode));
   }
 
   public void rewrite(
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinSyntheticClassInfo.java b/src/main/java/com/android/tools/r8/kotlin/KotlinSyntheticClassInfo.java
index af02060..38933b5 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinSyntheticClassInfo.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinSyntheticClassInfo.java
@@ -20,6 +20,7 @@
 public class KotlinSyntheticClassInfo implements KotlinClassLevelInfo {
 
   private final KotlinLambdaInfo lambda;
+  private final String packageName;
 
   public enum Flavour {
     KotlinStyleLambda,
@@ -29,13 +30,15 @@
 
   private final Flavour flavour;
 
-  private KotlinSyntheticClassInfo(KotlinLambdaInfo lambda, Flavour flavour) {
+  private KotlinSyntheticClassInfo(KotlinLambdaInfo lambda, Flavour flavour, String packageName) {
     this.lambda = lambda;
     this.flavour = flavour;
+    this.packageName = packageName;
   }
 
   static KotlinSyntheticClassInfo create(
       SyntheticClass syntheticClass,
+      String packageName,
       DexClass clazz,
       Kotlin kotlin,
       DexDefinitionSupplier definitionSupplier,
@@ -49,7 +52,8 @@
         lambda != null
             ? KotlinLambdaInfo.create(clazz, lambda, definitionSupplier, reporter)
             : null,
-        getFlavour(syntheticClass, clazz, kotlin));
+        getFlavour(syntheticClass, clazz, kotlin),
+        packageName);
   }
 
   public boolean isLambda() {
@@ -87,6 +91,11 @@
     return writer.write().getHeader();
   }
 
+  @Override
+  public String getPackageName() {
+    return packageName;
+  }
+
   private static Flavour getFlavour(
       KotlinClassMetadata.SyntheticClass metadata, DexClass clazz, Kotlin kotlin) {
     // Returns KotlinStyleLambda if the given clazz is a Kotlin-style lambda:
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinTypeInfo.java b/src/main/java/com/android/tools/r8/kotlin/KotlinTypeInfo.java
index 204bcbb..8601667 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinTypeInfo.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinTypeInfo.java
@@ -28,25 +28,23 @@
   private final KotlinTypeInfo outerType;
   private final List<KotlinTypeProjectionInfo> arguments;
   private final List<KotlinAnnotationInfo> annotations;
-  // TODO(b/154351125): Extend with flexible upper bounds
+  private final KotlinFlexibleTypeUpperBoundInfo flexibleTypeUpperBoundInfo;
 
-  private KotlinTypeInfo(
+  KotlinTypeInfo(
       int flags,
       KotlinClassifierInfo classifier,
       KotlinTypeInfo abbreviatedType,
       KotlinTypeInfo outerType,
       List<KotlinTypeProjectionInfo> arguments,
-      List<KotlinAnnotationInfo> annotations) {
+      List<KotlinAnnotationInfo> annotations,
+      KotlinFlexibleTypeUpperBoundInfo flexibleTypeUpperBoundInfo) {
     this.flags = flags;
     this.classifier = classifier;
     this.abbreviatedType = abbreviatedType;
     this.outerType = outerType;
     this.arguments = arguments;
     this.annotations = annotations;
-  }
-
-  public List<KotlinTypeProjectionInfo> getArguments() {
-    return arguments;
+    this.flexibleTypeUpperBoundInfo = flexibleTypeUpperBoundInfo;
   }
 
   static KotlinTypeInfo create(
@@ -60,10 +58,12 @@
         KotlinTypeInfo.create(kmType.getAbbreviatedType(), definitionSupplier, reporter),
         KotlinTypeInfo.create(kmType.getOuterType(), definitionSupplier, reporter),
         getArguments(kmType.getArguments(), definitionSupplier, reporter),
-        KotlinAnnotationInfo.create(JvmExtensionsKt.getAnnotations(kmType), definitionSupplier));
+        KotlinAnnotationInfo.create(JvmExtensionsKt.getAnnotations(kmType), definitionSupplier),
+        KotlinFlexibleTypeUpperBoundInfo.create(
+            kmType.getFlexibleTypeUpperBound(), definitionSupplier, reporter));
   }
 
-  private static List<KotlinTypeProjectionInfo> getArguments(
+  static List<KotlinTypeProjectionInfo> getArguments(
       List<KmTypeProjection> projections,
       DexDefinitionSupplier definitionSupplier,
       Reporter reporter) {
@@ -94,6 +94,8 @@
       argument.rewrite(
           kmTypeVisitor::visitArgument, kmTypeVisitor::visitStarProjection, appView, namingLens);
     }
+    flexibleTypeUpperBoundInfo.rewrite(
+        kmTypeVisitor::visitFlexibleTypeUpperBound, appView, namingLens);
     if (annotations.isEmpty()) {
       return;
     }
diff --git a/src/main/java/com/android/tools/r8/naming/ClassNameMinifier.java b/src/main/java/com/android/tools/r8/naming/ClassNameMinifier.java
index d7797a4..0c223b1 100644
--- a/src/main/java/com/android/tools/r8/naming/ClassNameMinifier.java
+++ b/src/main/java/com/android/tools/r8/naming/ClassNameMinifier.java
@@ -17,7 +17,6 @@
 import com.android.tools.r8.graph.DexString;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.InnerClassAttribute;
-import com.android.tools.r8.kotlin.KotlinMetadataRewriter;
 import com.android.tools.r8.naming.signature.GenericSignatureRewriter;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.shaking.ProguardPackageNameList;
@@ -129,9 +128,6 @@
       if (!renaming.containsKey(clazz.type)) {
         DexString renamed = computeName(clazz.type);
         renaming.put(clazz.type, renamed);
-        if (!appView.options().enableKotlinMetadataRewritingForRenamedClasses) {
-          KotlinMetadataRewriter.removeKotlinMetadataFromRenamedClass(appView, clazz);
-        }
         // If the class is a member class and it has used $ separator, its renamed name should have
         // the same separator (as long as inner-class attribute is honored).
         assert !keepInnerClassStructure
@@ -350,7 +346,6 @@
         // and then use that renamed name as a base prefix for the current inner class.
         renamed = computeName(outer);
         renaming.put(outer, renamed);
-        KotlinMetadataRewriter.removeKotlinMetadataFromRenamedClass(appView, outer);
       }
       String binaryName = getClassBinaryNameFromDescriptor(renamed.toString());
       state = new Namespace(binaryName, innerClassSeparator);
diff --git a/src/main/java/com/android/tools/r8/naming/ProguardMapMinifier.java b/src/main/java/com/android/tools/r8/naming/ProguardMapMinifier.java
index ab735ca..3ce8e7f 100644
--- a/src/main/java/com/android/tools/r8/naming/ProguardMapMinifier.java
+++ b/src/main/java/com/android/tools/r8/naming/ProguardMapMinifier.java
@@ -201,9 +201,6 @@
       // TODO(b/133091438) assert that !dexClass.isLibraryClass();
       DexString mappedName = factory.createString(classNaming.renamedName);
       checkAndAddMappedNames(type, mappedName, classNaming.position);
-      if (dexClass != null) {
-        KotlinMetadataRewriter.removeKotlinMetadataFromRenamedClass(appView, dexClass);
-      }
       classNaming.forAllMemberNaming(
           memberNaming -> addMemberNamings(type, memberNaming, nonPrivateMembers, false));
     } else {
diff --git a/src/main/java/com/android/tools/r8/optimize/BridgeHoisting.java b/src/main/java/com/android/tools/r8/optimize/BridgeHoisting.java
index 1cdeec3..31e239b 100644
--- a/src/main/java/com/android/tools/r8/optimize/BridgeHoisting.java
+++ b/src/main/java/com/android/tools/r8/optimize/BridgeHoisting.java
@@ -5,15 +5,25 @@
 
 import static com.android.tools.r8.graph.DexProgramClass.asProgramClassOrNull;
 
+import com.android.tools.r8.cf.code.CfInstruction;
+import com.android.tools.r8.cf.code.CfInvoke;
+import com.android.tools.r8.code.Instruction;
+import com.android.tools.r8.code.InvokeVirtual;
+import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.BottomUpClassHierarchyTraversal;
+import com.android.tools.r8.graph.CfCode;
+import com.android.tools.r8.graph.Code;
+import com.android.tools.r8.graph.DexCode;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.GraphLense.NestedGraphLense;
 import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.graph.ResolutionResult;
 import com.android.tools.r8.graph.SubtypingInfo;
+import com.android.tools.r8.ir.optimize.info.OptimizationFeedbackSimple;
 import com.android.tools.r8.ir.optimize.info.bridge.BridgeInfo;
 import com.android.tools.r8.ir.optimize.info.bridge.VirtualBridgeInfo;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
@@ -23,7 +33,12 @@
 import com.google.common.collect.BiMap;
 import com.google.common.collect.HashBiMap;
 import com.google.common.collect.ImmutableMap;
+import java.util.ArrayList;
+import java.util.HashMap;
 import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
 import java.util.Set;
 import java.util.TreeSet;
 
@@ -52,6 +67,9 @@
  */
 public class BridgeHoisting {
 
+  private static final OptimizationFeedbackSimple feedback =
+      OptimizationFeedbackSimple.getInstance();
+
   private final AppView<AppInfoWithLiveness> appView;
 
   // A lens that keeps track of the changes for construction of the Proguard map.
@@ -96,10 +114,7 @@
     for (DexProgramClass subclass : subclasses) {
       for (DexEncodedMethod method : subclass.virtualMethods()) {
         BridgeInfo bridgeInfo = method.getOptimizationInfo().getBridgeInfo();
-        // TODO(b/153147967): Even if the bridge is not targeting a method in the superclass, it may
-        //  be possible to rewrite the bridge to target a method in the superclass, such that we can
-        //  hoist it. Add a test.
-        if (bridgeInfo != null && bridgeIsTargetingMethodInSuperclass(subclass, bridgeInfo)) {
+        if (bridgeInfo != null) {
           candidates.add(equivalence.wrap(method.method));
         }
       }
@@ -150,9 +165,10 @@
       return;
     }
 
-    // Go through each of the subclasses and bail-out if each subclass does not declare the same
-    // bridge.
-    BridgeInfo firstBridgeInfo = null;
+    // Go through each of the subclasses and find the bridges that can be hoisted. The bridge holder
+    // classes are stored in buckets grouped by the behavior of the body of the bridge (which is
+    // implicitly defined by the signature of the invoke-virtual instruction).
+    Map<Wrapper<DexMethod>, List<DexProgramClass>> eligibleVirtualInvokeBridges = new HashMap<>();
     for (DexProgramClass subclass : subclasses) {
       DexEncodedMethod definition = subclass.lookupVirtualMethod(method);
       if (definition == null) {
@@ -172,44 +188,88 @@
           // should never be the case in practice.
           continue;
         }
+
+        // Hoisting would change the program behavior.
         return;
       }
 
       BridgeInfo currentBridgeInfo = definition.getOptimizationInfo().getBridgeInfo();
       if (currentBridgeInfo == null) {
-        return;
+        // This is not a bridge, so the method needs to remain on the subclass.
+        continue;
       }
 
-      if (firstBridgeInfo == null) {
-        firstBridgeInfo = currentBridgeInfo;
-      } else if (!currentBridgeInfo.hasSameTarget(firstBridgeInfo)) {
-        return;
-      }
+      assert currentBridgeInfo.isVirtualBridgeInfo();
+
+      VirtualBridgeInfo currentVirtualBridgeInfo = currentBridgeInfo.asVirtualBridgeInfo();
+      DexMethod invokedMethod = currentVirtualBridgeInfo.getInvokedMethod();
+      Wrapper<DexMethod> wrapper = MethodSignatureEquivalence.get().wrap(invokedMethod);
+      eligibleVirtualInvokeBridges
+          .computeIfAbsent(wrapper, ignore -> new ArrayList<>())
+          .add(subclass);
     }
 
-    // If we reached this point, it is because all of the subclasses define the same bridge.
-    assert firstBridgeInfo != null;
+    // There should be at least one method that is eligible for hoisting.
+    assert !eligibleVirtualInvokeBridges.isEmpty();
+
+    Entry<Wrapper<DexMethod>, List<DexProgramClass>> mostFrequentBridge =
+        findMostFrequentBridge(eligibleVirtualInvokeBridges);
+    assert mostFrequentBridge != null;
+    DexMethod invokedMethod = mostFrequentBridge.getKey().get();
+    List<DexProgramClass> eligibleSubclasses = mostFrequentBridge.getValue();
 
     // Choose one of the bridge definitions as the one that we will be moving to the superclass.
-    ProgramMethod representative = findRepresentative(subclasses, method);
-    assert representative != null;
+    ProgramMethod representative = findRepresentative(eligibleSubclasses, method);
 
     // Guard against accessibility issues.
     if (mayBecomeInaccessibleAfterHoisting(clazz, representative)) {
       return;
     }
 
-    // Move the bridge method to the super class, and record this in the graph lens.
-    DexMethod newMethod =
-        appView.dexItemFactory().createMethod(clazz.type, method.proto, method.name);
-    clazz.addVirtualMethod(representative.getDefinition().toTypeSubstitutedMethod(newMethod));
-    lensBuilder.move(representative.getDefinition().method, newMethod);
+    // Rewrite the invoke-virtual instruction to target the virtual method on the new holder class.
+    // Otherwise the code might not type check.
+    DexMethod methodToInvoke =
+        appView.dexItemFactory().createMethod(clazz.type, invokedMethod.proto, invokedMethod.name);
 
-    // Remove all of the bridges in the subclasses.
-    for (DexProgramClass subclass : subclasses) {
-      DexEncodedMethod removed = subclass.removeMethod(method);
-      assert removed == null || !appView.appInfo().isPinned(removed.method);
+    // The targeted method must be present on the new holder class for this to be feasible.
+    ResolutionResult resolutionResult =
+        appView.appInfo().resolveMethodOnClass(methodToInvoke, clazz);
+    if (!resolutionResult.isSingleResolution()) {
+      return;
     }
+
+    // Now update the code of the bridge method chosen as representative.
+    representative
+        .getDefinition()
+        .setCode(createCodeForVirtualBridge(representative, methodToInvoke), appView);
+    feedback.setBridgeInfo(representative.getDefinition(), new VirtualBridgeInfo(methodToInvoke));
+
+    // Move the bridge method to the super class, and record this in the graph lens.
+    DexMethod newMethodReference =
+        appView.dexItemFactory().createMethod(clazz.type, method.proto, method.name);
+    DexEncodedMethod newMethod =
+        representative.getDefinition().toTypeSubstitutedMethod(newMethodReference);
+    clazz.addVirtualMethod(newMethod);
+    lensBuilder.move(representative.getReference(), newMethodReference);
+
+    // Remove all of the bridges in the eligible subclasses.
+    for (DexProgramClass subclass : eligibleSubclasses) {
+      DexEncodedMethod removed = subclass.removeMethod(method);
+      assert removed != null && !appView.appInfo().isPinned(removed.method);
+    }
+  }
+
+  private static Entry<Wrapper<DexMethod>, List<DexProgramClass>> findMostFrequentBridge(
+      Map<Wrapper<DexMethod>, List<DexProgramClass>> eligibleVirtualInvokeBridges) {
+    Entry<Wrapper<DexMethod>, List<DexProgramClass>> result = null;
+    for (Entry<Wrapper<DexMethod>, List<DexProgramClass>> candidate :
+        eligibleVirtualInvokeBridges.entrySet()) {
+      List<DexProgramClass> eligibleSubclassesCandidate = candidate.getValue();
+      if (result == null || eligibleSubclassesCandidate.size() > result.getValue().size()) {
+        result = candidate;
+      }
+    }
+    return result;
   }
 
   private ProgramMethod findRepresentative(Iterable<DexProgramClass> subclasses, DexMethod method) {
@@ -219,7 +279,7 @@
         return new ProgramMethod(subclass, definition);
       }
     }
-    return null;
+    throw new Unreachable();
   }
 
   private boolean mayBecomeInaccessibleAfterHoisting(
@@ -230,6 +290,64 @@
     return !representative.getDefinition().isPublic();
   }
 
+  private Code createCodeForVirtualBridge(ProgramMethod representative, DexMethod methodToInvoke) {
+    Code code = representative.getDefinition().getCode();
+    if (code.isCfCode()) {
+      return createCfCodeForVirtualBridge(code.asCfCode(), methodToInvoke);
+    }
+    if (code.isDexCode()) {
+      return createDexCodeForVirtualBridge(code.asDexCode(), methodToInvoke);
+    }
+    throw new Unreachable("Unexpected code object of type " + code.getClass().getTypeName());
+  }
+
+  private CfCode createCfCodeForVirtualBridge(CfCode code, DexMethod methodToInvoke) {
+    List<CfInstruction> newInstructions = new ArrayList<>();
+    for (CfInstruction instruction : code.getInstructions()) {
+      if (instruction.isInvoke()) {
+        CfInvoke invoke = instruction.asInvoke();
+        assert invoke.isInvokeVirtual();
+        assert !invoke.isInterface();
+        assert invoke.getMethod().match(methodToInvoke);
+        newInstructions.add(new CfInvoke(invoke.getOpcode(), methodToInvoke, false));
+      } else {
+        newInstructions.add(instruction);
+      }
+    }
+    return new CfCode(
+        methodToInvoke.holder,
+        code.getMaxStack(),
+        code.getMaxLocals(),
+        newInstructions,
+        code.getTryCatchRanges(),
+        code.getLocalVariables());
+  }
+
+  private DexCode createDexCodeForVirtualBridge(DexCode code, DexMethod methodToInvoke) {
+    Instruction[] newInstructions = new Instruction[code.instructions.length];
+    for (int i = 0; i < code.instructions.length; i++) {
+      Instruction instruction = code.instructions[i];
+      if (instruction.isInvokeVirtual()) {
+        InvokeVirtual invoke = instruction.asInvokeVirtual();
+        InvokeVirtual newInvoke =
+            new InvokeVirtual(
+                invoke.A, methodToInvoke, invoke.C, invoke.D, invoke.E, invoke.F, invoke.G);
+        newInvoke.setOffset(invoke.getOffset());
+        newInstructions[i] = newInvoke;
+      } else {
+        newInstructions[i] = instruction;
+      }
+    }
+    return new DexCode(
+        code.registerSize,
+        code.incomingRegisterSize,
+        code.outgoingRegisterSize,
+        newInstructions,
+        code.tries,
+        code.handlers,
+        code.getDebugInfo());
+  }
+
   static class BridgeHoistingLens extends NestedGraphLense {
 
     public BridgeHoistingLens(
diff --git a/src/main/java/com/android/tools/r8/optimize/ClassAndMemberPublicizer.java b/src/main/java/com/android/tools/r8/optimize/ClassAndMemberPublicizer.java
index 5da8e5f..44efffc 100644
--- a/src/main/java/com/android/tools/r8/optimize/ClassAndMemberPublicizer.java
+++ b/src/main/java/com/android/tools/r8/optimize/ClassAndMemberPublicizer.java
@@ -100,7 +100,7 @@
           if (field.isPublic()) {
             return;
           }
-          if (appView.appInfo().isPinned(field.field)) {
+          if (!appView.appInfo().isAccessModificationAllowed(field.field)) {
             // TODO(b/131130038): Also do not publicize package-private and protected fields that
             //  are kept.
             if (field.isPrivate()) {
@@ -138,7 +138,7 @@
       return false;
     }
     // If this method is mentioned in keep rules, do not transform (rule applications changed).
-    if (appView.appInfo().isPinned(method.method)) {
+    if (!appView.appInfo().isAccessModificationAllowed(method.method)) {
       // TODO(b/131130038): Also do not publicize package-private and protected methods that are
       //  kept.
       if (method.isPrivate()) {
diff --git a/src/main/java/com/android/tools/r8/optimize/MemberRebindingAnalysis.java b/src/main/java/com/android/tools/r8/optimize/MemberRebindingAnalysis.java
index a3e4666..b992f8d 100644
--- a/src/main/java/com/android/tools/r8/optimize/MemberRebindingAnalysis.java
+++ b/src/main/java/com/android/tools/r8/optimize/MemberRebindingAnalysis.java
@@ -3,7 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.optimize;
 
-import com.android.tools.r8.graph.AccessFlags;
+import com.android.tools.r8.graph.AccessControl;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexEncodedField;
@@ -14,6 +14,7 @@
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.FieldAccessInfo;
 import com.android.tools.r8.graph.FieldAccessInfoCollection;
+import com.android.tools.r8.graph.FieldResolutionResult.SuccessfulFieldResolutionResult;
 import com.android.tools.r8.graph.GraphLense;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.code.Invoke.Type;
@@ -301,64 +302,39 @@
 
   private void computeFieldRebindingForIndirectAccessWithContexts(
       DexField field, ProgramMethodSet contexts) {
-    DexEncodedField target = appView.appInfo().resolveField(field).getResolvedField();
-    if (target == null) {
-      assert false;
+    SuccessfulFieldResolutionResult resolutionResult =
+        appView.appInfo().resolveField(field).asSuccessfulResolution();
+    if (resolutionResult == null) {
       return;
     }
 
-    if (target.field == field) {
+    DexEncodedField resolvedField = resolutionResult.getResolvedField();
+    if (resolvedField.field == field) {
       assert false;
       return;
     }
 
     // Rebind to the lowest library class or program class. Do not rebind accesses to fields that
     // are not visible from the access context.
-    if (contexts.stream()
-        .allMatch(
-            context ->
-                isMemberVisibleFromOriginalContext(
-                    appView, context, target.holder(), target.accessFlags))) {
+    boolean accessibleInAllContexts = true;
+    for (ProgramMethod context : contexts) {
+      boolean inaccessibleInContext =
+          AccessControl.isFieldAccessible(
+                  resolvedField, resolutionResult.getResolvedHolder(), context, appView)
+              .isPossiblyFalse();
+      if (inaccessibleInContext) {
+        accessibleInAllContexts = false;
+        break;
+      }
+    }
+
+    if (accessibleInAllContexts) {
       builder.map(
-          field, lense.lookupField(validTargetFor(target.field, field, DexClass::lookupField)));
+          field,
+          lense.lookupField(validTargetFor(resolvedField.field, field, DexClass::lookupField)));
     }
   }
 
-  public static boolean isTypeVisibleFromContext(
-      AppView<?> appView, ProgramMethod context, DexType type) {
-    DexType baseType = type.toBaseType(appView.dexItemFactory());
-    if (baseType.isPrimitiveType()) {
-      return true;
-    }
-    return isClassTypeVisibleFromContext(appView, context, baseType);
-  }
-
-  public static boolean isClassTypeVisibleFromContext(
-      AppView<?> appView, ProgramMethod context, DexType type) {
-    assert type.isClassType();
-    DexClass clazz = appView.definitionFor(type);
-    return clazz != null && isClassTypeVisibleFromContext(appView, context, clazz);
-  }
-
-  public static boolean isClassTypeVisibleFromContext(
-      AppView<?> appView, ProgramMethod context, DexClass clazz) {
-    ConstraintWithTarget classVisibility =
-        ConstraintWithTarget.deriveConstraint(
-            context.getHolder(), clazz.type, clazz.accessFlags, appView);
-    return classVisibility != ConstraintWithTarget.NEVER;
-  }
-
-  public static boolean isMemberVisibleFromOriginalContext(
-      AppView<?> appView, ProgramMethod context, DexType holder, AccessFlags<?> memberAccessFlags) {
-    if (!isClassTypeVisibleFromContext(appView, context, holder)) {
-      return false;
-    }
-    ConstraintWithTarget memberVisibility =
-        ConstraintWithTarget.deriveConstraint(
-            context.getHolder(), holder, memberAccessFlags, appView);
-    return memberVisibility != ConstraintWithTarget.NEVER;
-  }
-
   public GraphLense run() {
     AppInfoWithLiveness appInfo = appView.appInfo();
     // Virtual invokes are on classes, so use class resolution.
diff --git a/src/main/java/com/android/tools/r8/retrace/RetraceInvalidStackTraceLineDiagnostics.java b/src/main/java/com/android/tools/r8/retrace/RetraceInvalidStackTraceLineDiagnostics.java
index b595f87..959976a 100644
--- a/src/main/java/com/android/tools/r8/retrace/RetraceInvalidStackTraceLineDiagnostics.java
+++ b/src/main/java/com/android/tools/r8/retrace/RetraceInvalidStackTraceLineDiagnostics.java
@@ -40,11 +40,11 @@
     return message;
   }
 
-  public static RetraceInvalidStackTraceLineDiagnostics createNull(int lineNumber) {
+  static RetraceInvalidStackTraceLineDiagnostics createNull(int lineNumber) {
     return new RetraceInvalidStackTraceLineDiagnostics(lineNumber, NULL_STACK_TRACE_LINE_MESSAGE);
   }
 
-  public static RetraceInvalidStackTraceLineDiagnostics createParse(int lineNumber, String line) {
+  static RetraceInvalidStackTraceLineDiagnostics createParse(int lineNumber, String line) {
     return new RetraceInvalidStackTraceLineDiagnostics(
         lineNumber, String.format(PARSE_STACK_TRACE_LINE_MESSAGE, line));
   }
diff --git a/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java b/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
index 308cf69..275b882 100644
--- a/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
+++ b/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
@@ -138,6 +138,8 @@
   public final Set<DexCallSite> callSites;
   /** Set of all items that have to be kept independent of whether they are used. */
   final Set<DexReference> pinnedItems;
+  /** Set of kept items that are allowed to be publicized. */
+  final Set<DexReference> allowAccessModification;
   /** All items with assumemayhavesideeffects rule. */
   public final Map<DexReference, ProguardMemberRule> mayHaveSideEffects;
   /** All items with assumenosideeffects rule. */
@@ -216,6 +218,7 @@
       SortedMap<DexMethod, ProgramMethodSet> staticInvokes,
       Set<DexCallSite> callSites,
       Set<DexReference> pinnedItems,
+      Set<DexReference> allowAccessModification,
       Map<DexReference, ProguardMemberRule> mayHaveSideEffects,
       Map<DexReference, ProguardMemberRule> noSideEffects,
       Map<DexReference, ProguardMemberRule> assumedValues,
@@ -251,6 +254,7 @@
     this.fieldAccessInfoCollection = fieldAccessInfoCollection;
     this.objectAllocationInfoCollection = objectAllocationInfoCollection;
     this.pinnedItems = pinnedItems;
+    this.allowAccessModification = allowAccessModification;
     this.mayHaveSideEffects = mayHaveSideEffects;
     this.noSideEffects = noSideEffects;
     this.assumedValues = assumedValues;
@@ -301,6 +305,7 @@
       SortedMap<DexMethod, ProgramMethodSet> staticInvokes,
       Set<DexCallSite> callSites,
       Set<DexReference> pinnedItems,
+      Set<DexReference> allowAccessModification,
       Map<DexReference, ProguardMemberRule> mayHaveSideEffects,
       Map<DexReference, ProguardMemberRule> noSideEffects,
       Map<DexReference, ProguardMemberRule> assumedValues,
@@ -336,6 +341,7 @@
     this.fieldAccessInfoCollection = fieldAccessInfoCollection;
     this.objectAllocationInfoCollection = objectAllocationInfoCollection;
     this.pinnedItems = pinnedItems;
+    this.allowAccessModification = allowAccessModification;
     this.mayHaveSideEffects = mayHaveSideEffects;
     this.noSideEffects = noSideEffects;
     this.assumedValues = assumedValues;
@@ -387,6 +393,7 @@
         previous.staticInvokes,
         previous.callSites,
         previous.pinnedItems,
+        previous.allowAccessModification,
         previous.mayHaveSideEffects,
         previous.noSideEffects,
         previous.assumedValues,
@@ -439,6 +446,7 @@
         additionalPinnedItems == null
             ? previous.pinnedItems
             : CollectionUtils.mergeSets(previous.pinnedItems, additionalPinnedItems),
+        previous.allowAccessModification,
         previous.mayHaveSideEffects,
         previous.noSideEffects,
         previous.assumedValues,
@@ -484,6 +492,7 @@
     this.fieldAccessInfoCollection = previous.fieldAccessInfoCollection;
     this.objectAllocationInfoCollection = previous.objectAllocationInfoCollection;
     this.pinnedItems = previous.pinnedItems;
+    this.allowAccessModification = previous.allowAccessModification;
     this.mayHaveSideEffects = previous.mayHaveSideEffects;
     this.noSideEffects = previous.noSideEffects;
     this.assumedValues = previous.assumedValues;
@@ -518,21 +527,10 @@
     return new AppInfoWithLivenessModifier();
   }
 
-  private boolean assertDefinitionFor = true;
-
-  public void disableDefinitionForAssert() {
-    assertDefinitionFor = false;
-  }
-
-  public void enableDefinitionForAssert() {
-    assertDefinitionFor = true;
-  }
-
   @Override
   public DexClass definitionFor(DexType type) {
     DexClass definition = super.definitionFor(type);
-    assert !assertDefinitionFor
-            || definition != null
+    assert definition != null
             || deadProtoTypes.contains(type)
             || missingTypes.contains(type)
             // TODO(b/150693139): Remove these exceptions once fixed.
@@ -866,6 +864,14 @@
     return staticInitializer != null && isFieldOnlyWrittenInMethod(field, staticInitializer);
   }
 
+  public boolean mayPropagateArgumentsTo(ProgramMethod method) {
+    DexMethod reference = method.getReference();
+    return method.getDefinition().hasCode()
+        && !method.getDefinition().isLibraryMethodOverride().isPossiblyTrue()
+        && !neverReprocess.contains(reference)
+        && !pinnedItems.contains(reference);
+  }
+
   public boolean mayPropagateValueFor(DexReference reference) {
     assert checkIfObsolete();
     return options().enableValuePropagation
@@ -919,6 +925,11 @@
     return this;
   }
 
+  public boolean isAccessModificationAllowed(DexReference reference) {
+    assert options().getProguardConfiguration().isAccessModificationAllowed();
+    return allowAccessModification.contains(reference) || !isPinned(reference);
+  }
+
   public boolean isPinned(DexReference reference) {
     assert checkIfObsolete();
     return pinnedItems.contains(reference);
@@ -1014,6 +1025,7 @@
         //   after second tree shaking.
         callSites,
         lens.rewriteReferencesConservatively(pinnedItems),
+        lens.rewriteReferencesConservatively(allowAccessModification),
         rewriteReferenceKeys(mayHaveSideEffects, lens::lookupReference),
         rewriteReferenceKeys(noSideEffects, lens::lookupReference),
         rewriteReferenceKeys(assumedValues, lens::lookupReference),
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 f2dddae..6e8f93b 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -135,6 +135,7 @@
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.ExecutorService;
 import java.util.function.BiConsumer;
+import java.util.function.Consumer;
 import java.util.function.Function;
 import java.util.function.Predicate;
 import org.objectweb.asm.Opcodes;
@@ -189,7 +190,7 @@
   private ProguardClassFilter dontWarnPatterns;
   private final EnqueuerUseRegistryFactory useRegistryFactory;
   private AnnotationRemover.Builder annotationRemoverBuilder;
-  private final DexDefinitionSupplier enqueuerDefinitionSupplier;
+  private final EnqueuerDefinitionSupplier enqueuerDefinitionSupplier;
 
   private final Map<DexMethod, ProgramMethodSet> virtualInvokes = new IdentityHashMap<>();
   private final Map<DexMethod, ProgramMethodSet> interfaceInvokes = new IdentityHashMap<>();
@@ -308,6 +309,13 @@
   private final Set<DexReference> pinnedItems = Sets.newIdentityHashSet();
 
   /**
+   * A set of references that we are keeping due to keep rules, which we are allowed to publicize.
+   */
+  // TODO(b/156715504): This should be maintained in a structure that describes what we are allowed
+  //  and not allowed to do with program items that are referenced from keep rules.
+  private final Map<DexReference, OptionalBool> allowAccessModification = new IdentityHashMap<>();
+
+  /**
    * A set of seen const-class references that both serve as an initial lock-candidate set and will
    * prevent statically merging the classes referenced.
    */
@@ -662,7 +670,7 @@
     } else {
       throw new IllegalArgumentException(item.toString());
     }
-    pinnedItems.add(item.toReference());
+    addPinnedItem(item.toReference(), rules);
   }
 
   private void enqueueFirstNonSerializableClassInitializer(
@@ -1536,8 +1544,7 @@
     compatEnqueueHolderIfDependentNonStaticMember(
         holder, rootSet.getDependentKeepClassCompatRule(holder.getType()));
 
-    analyses.forEach(
-        analysis -> analysis.processNewlyLiveClass(holder, workList, enqueuerDefinitionSupplier));
+    analyses.forEach(analysis -> analysis.processNewlyLiveClass(holder, workList));
   }
 
   private void ensureMethodsContinueToWidenAccess(DexClass clazz) {
@@ -1794,11 +1801,11 @@
             (dexType, ignored) -> {
               if (holder.isProgramClass()) {
                 DexReference holderReference = holder.toReference();
-                pinnedItems.add(holderReference);
+                addPinnedItem(holderReference);
                 rootSet.shouldNotBeMinified(holderReference);
                 for (DexEncodedMember<?, ?> member : holder.members()) {
                   DexMember<?, ?> memberReference = member.toReference();
-                  pinnedItems.add(memberReference);
+                  addPinnedItem(memberReference);
                   rootSet.shouldNotBeMinified(memberReference);
                 }
               }
@@ -2339,6 +2346,10 @@
     return liveNonProgramTypes.contains(clazz);
   }
 
+  public void forAllLiveClasses(Consumer<DexProgramClass> consumer) {
+    liveTypes.items.forEach(consumer);
+  }
+
   // Package protected due to entry point from worklist.
   void markInstanceFieldAsReachable(ProgramField field, KeepReason reason) {
     if (Log.ENABLED) {
@@ -2509,7 +2520,7 @@
       // TODO(sgjesse): Does this have to be enqueued as a root item? Right now it is done as the
       // marking for not renaming it is in the root set.
       workList.enqueueMarkMethodKeptAction(new ProgramMethod(clazz, valuesMethod), reason);
-      pinnedItems.add(valuesMethod.toReference());
+      addPinnedItem(valuesMethod.toReference());
       rootSet.shouldNotBeMinified(valuesMethod.toReference());
     }
   }
@@ -2578,11 +2589,12 @@
       throws ExecutionException {
     this.rootSet = rootSet;
     this.dontWarnPatterns = dontWarnPatterns;
-    if (!options.kotlinOptimizationOptions().disableKotlinSpecificOptimizations
-        && mode.isInitialTreeShaking()) {
-      registerAnalysis(new KotlinMetadataEnqueuerExtension(appView));
-    }
     // Translate the result of root-set computation into enqueuer actions.
+    if (appView.options().getProguardConfiguration() != null
+        && !options.kotlinOptimizationOptions().disableKotlinSpecificOptimizations
+        && mode.isInitialTreeShaking()) {
+      registerAnalysis(new KotlinMetadataEnqueuerExtension(appView, enqueuerDefinitionSupplier));
+    }
     if (appView.options().isShrinking() || appView.options().getProguardConfiguration() == null) {
       enqueueRootItems(rootSet.noShrinking);
     } else {
@@ -2603,7 +2615,7 @@
     trace(executorService, timing);
     options.reporter.failIfPendingErrors();
     finalizeLibraryMethodOverrideInformation();
-    analyses.forEach(EnqueuerAnalysis::done);
+    analyses.forEach(analyses -> analyses.done(this));
     assert verifyKeptGraph();
     AppInfoWithLiveness appInfoWithLiveness = createAppInfo(appInfo);
     if (options.testing.enqueuerInspector != null) {
@@ -2612,6 +2624,39 @@
     return appInfoWithLiveness;
   }
 
+  public boolean isPinned(DexReference reference) {
+    return pinnedItems.contains(reference);
+  }
+
+  private boolean addPinnedItem(DexReference reference) {
+    allowAccessModification.put(reference, OptionalBool.unknown());
+    return pinnedItems.add(reference);
+  }
+
+  private boolean addPinnedItem(DexReference reference, Set<ProguardKeepRuleBase> rules) {
+    assert rules != null;
+    assert !rules.isEmpty();
+    OptionalBool allowAccessModificationOfReference =
+        allowAccessModification.getOrDefault(reference, OptionalBool.TRUE);
+    if (allowAccessModificationOfReference.isTrue()) {
+      for (ProguardKeepRuleBase rule : rules) {
+        ProguardKeepRuleModifiers modifiers =
+            (rule.isProguardIfRule() ? rule.asProguardIfRule().getSubsequentRule() : rule)
+                .getModifiers();
+        if (!modifiers.allowsAccessModification) {
+          allowAccessModificationOfReference = OptionalBool.FALSE;
+          break;
+        }
+      }
+      allowAccessModification.put(reference, allowAccessModificationOfReference);
+    }
+    return pinnedItems.add(reference);
+  }
+
+  public boolean isMissing(DexType type) {
+    return missingTypes.contains(type);
+  }
+
   private static class SyntheticAdditions {
 
     Map<DexType, Pair<DexProgramClass, ProgramMethod>> syntheticInstantiations =
@@ -2674,7 +2719,7 @@
       // All synthetic additions are initial tree shaking only. No need to track keep reasons.
       KeepReasonWitness fakeReason = enqueuer.graphReporter.fakeReportShouldNotBeUsed();
 
-      enqueuer.pinnedItems.addAll(pinnedMethods);
+      pinnedMethods.forEach(enqueuer::addPinnedItem);
       for (Pair<DexProgramClass, ProgramMethod> clazzAndContext :
           syntheticInstantiations.values()) {
         enqueuer.workList.enqueueMarkInstantiatedAction(
@@ -2789,6 +2834,9 @@
     // to a static method will invalidate the reachable method sets for tracing methods.
     ensureLambdaAccessibility();
 
+    // Remove the items from `allowAccessModification` that we are not allowed to publicize.
+    allowAccessModification.entrySet().removeIf(entry -> !entry.getValue().isTrue());
+
     // Compute the set of dead proto types.
     deadProtoTypeCandidates.removeIf(this::isTypeLive);
 
@@ -2860,6 +2908,7 @@
             toImmutableSortedMap(staticInvokes, PresortedComparable::slowCompare),
             callSites,
             pinnedItems,
+            allowAccessModification.keySet(),
             rootSet.mayHaveSideEffects,
             rootSet.noSideEffects,
             rootSet.assumedValues,
@@ -3538,7 +3587,7 @@
         workList.enqueueMarkInstantiatedAction(
             clazz, null, InstantiationReason.REFLECTION, KeepReason.reflectiveUseIn(method));
       }
-      if (pinnedItems.add(encodedField.field)) {
+      if (addPinnedItem(encodedField.field)) {
         markFieldAsKept(new ProgramField(clazz, encodedField), KeepReason.reflectiveUseIn(method));
       }
     } else {
@@ -3725,14 +3774,14 @@
         // Add this interface to the set of pinned items to ensure that we do not merge the
         // interface into its unique subtype, if any.
         // TODO(b/145344105): This should be superseded by the unknown interface hierarchy.
-        pinnedItems.add(clazz.type);
+        addPinnedItem(clazz.type);
         KeepReason reason = KeepReason.reflectiveUseIn(method);
         markInterfaceAsInstantiated(clazz, graphReporter.registerClass(clazz, reason));
 
         // Also pin all of its virtual methods to ensure that the devirtualizer does not perform
         // illegal rewritings of invoke-interface instructions into invoke-virtual instructions.
         for (DexEncodedMethod virtualMethod : clazz.virtualMethods()) {
-          pinnedItems.add(virtualMethod.method);
+          addPinnedItem(virtualMethod.method);
           markVirtualMethodAsReachable(virtualMethod.method, true, null, reason);
         }
       }
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardClassNameList.java b/src/main/java/com/android/tools/r8/shaking/ProguardClassNameList.java
index 0b02b70..d00bc9a 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardClassNameList.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardClassNameList.java
@@ -87,6 +87,10 @@
     return Collections::emptyIterator;
   }
 
+  public boolean hasWildcards() {
+    return getWildcards().iterator().hasNext();
+  }
+
   static Iterable<ProguardWildcard> getWildcardsOrEmpty(ProguardClassNameList nameList) {
     return nameList == null ? Collections::emptyIterator : nameList.getWildcards();
   }
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
index 5b1f672..aee4a83 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
@@ -27,7 +27,6 @@
 import com.android.tools.r8.utils.Reporter;
 import com.android.tools.r8.utils.StringDiagnostic;
 import com.android.tools.r8.utils.StringUtils;
-import com.google.common.base.MoreObjects;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.Iterables;
 import java.io.File;
@@ -179,7 +178,7 @@
         reporter.error(new StringDiagnostic("Failed to read file: " + e.getMessage(),
             source.getOrigin()));
       } catch (ProguardRuleParserException e) {
-        reporter.error(e, MoreObjects.firstNonNull(e.getCause(), e));
+        reporter.error(e);
       }
     }
     reporter.failIfPendingErrors();
@@ -237,6 +236,12 @@
           || parseTestingOption(optionStart)
           || parseUnsupportedOptionAndErr(optionStart)) {
         // Intentionally left empty.
+      } else if (acceptString("adaptkotlinmetadata")) {
+        reporter.info(
+            new StringDiagnostic(
+                "Ignoring -adaptkotlinmetadata because R8 always process kotlin.Metadata",
+                origin,
+                getPosition(optionStart)));
       } else if (acceptString("renamesourcefileattribute")) {
         skipWhitespace();
         if (isOptionalArgumentGiven()) {
@@ -634,11 +639,10 @@
           parseClassSpec(keepRuleBuilder, true);
           return true;
         } catch (ProguardRuleParserException e) {
-          throw reporter.fatalError(e, MoreObjects.firstNonNull(e.getCause(), e));
+          throw reporter.fatalError(e);
         }
       }
       return false;
-
     }
 
     private boolean parseOptimizationOption(TextPosition optionStart)
@@ -949,6 +953,8 @@
             builder.getModifiersBuilder().setAllowsOptimization(true);
           } else if (acceptString("obfuscation")) {
             builder.getModifiersBuilder().setAllowsObfuscation(true);
+          } else if (acceptString("accessmodification")) {
+            builder.getModifiersBuilder().setAllowsAccessModification(true);
           }
         } else if (acceptString("includedescriptorclasses")) {
           builder.getModifiersBuilder().setIncludeDescriptorClasses(true);
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationRule.java b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationRule.java
index 52c3b4c..0e21e66 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationRule.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationRule.java
@@ -58,6 +58,14 @@
     return null;
   }
 
+  public boolean isProguardIfRule() {
+    return false;
+  }
+
+  public ProguardIfRule asProguardIfRule() {
+    return null;
+  }
+
   public boolean isClassInlineRule() {
     return false;
   }
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardIfRule.java b/src/main/java/com/android/tools/r8/shaking/ProguardIfRule.java
index d4e2b1b..ba9ec8a 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardIfRule.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardIfRule.java
@@ -30,6 +30,10 @@
     return preconditions;
   }
 
+  public ProguardKeepRule getSubsequentRule() {
+    return subsequentRule;
+  }
+
   public static class Builder extends ProguardKeepRuleBase.Builder<ProguardIfRule, Builder> {
 
     ProguardKeepRule subsequentRule = null;
@@ -102,6 +106,16 @@
     return Iterables.concat(super.getWildcards(), subsequentRule.getWildcards());
   }
 
+  @Override
+  public boolean isProguardIfRule() {
+    return true;
+  }
+
+  @Override
+  public ProguardIfRule asProguardIfRule() {
+    return this;
+  }
+
   protected ProguardIfRule materialize(
       DexItemFactory dexItemFactory, Set<DexReference> preconditions) {
     return new ProguardIfRule(
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardKeepRuleModifiers.java b/src/main/java/com/android/tools/r8/shaking/ProguardKeepRuleModifiers.java
index eb2acee..a02117d 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardKeepRuleModifiers.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardKeepRuleModifiers.java
@@ -6,6 +6,7 @@
 public class ProguardKeepRuleModifiers {
   public static class Builder {
 
+    private boolean allowsAccessModification = false;
     private boolean allowsShrinking = false;
     private boolean allowsOptimization = false;
     private boolean allowsObfuscation = false;
@@ -13,6 +14,11 @@
 
     private Builder() {}
 
+    public Builder setAllowsAccessModification(boolean allowsAccessModification) {
+      this.allowsAccessModification = allowsAccessModification;
+      return this;
+    }
+
     public void setAllowsShrinking(boolean allowsShrinking) {
       this.allowsShrinking = allowsShrinking;
     }
@@ -31,26 +37,34 @@
     }
 
     ProguardKeepRuleModifiers build() {
-      return new ProguardKeepRuleModifiers(allowsShrinking, allowsOptimization, allowsObfuscation,
+      return new ProguardKeepRuleModifiers(
+          allowsAccessModification,
+          allowsShrinking,
+          allowsOptimization,
+          allowsObfuscation,
           includeDescriptorClasses);
     }
   }
 
+  public final boolean allowsAccessModification;
   public final boolean allowsShrinking;
   public final boolean allowsOptimization;
   public final boolean allowsObfuscation;
   public final boolean includeDescriptorClasses;
 
   private ProguardKeepRuleModifiers(
+      boolean allowsAccessModification,
       boolean allowsShrinking,
       boolean allowsOptimization,
       boolean allowsObfuscation,
       boolean includeDescriptorClasses) {
+    this.allowsAccessModification = allowsAccessModification;
     this.allowsShrinking = allowsShrinking;
     this.allowsOptimization = allowsOptimization;
     this.allowsObfuscation = allowsObfuscation;
     this.includeDescriptorClasses = includeDescriptorClasses;
   }
+
   /**
    * Create a new empty builder.
    */
@@ -64,8 +78,8 @@
       return false;
     }
     ProguardKeepRuleModifiers that = (ProguardKeepRuleModifiers) o;
-
-    return allowsShrinking == that.allowsShrinking
+    return allowsAccessModification == that.allowsAccessModification
+        && allowsShrinking == that.allowsShrinking
         && allowsOptimization == that.allowsOptimization
         && allowsObfuscation == that.allowsObfuscation
         && includeDescriptorClasses == that.includeDescriptorClasses;
@@ -73,15 +87,17 @@
 
   @Override
   public int hashCode() {
-    return (allowsShrinking ? 1 : 0)
-        | (allowsOptimization ? 2 : 0)
-        | (allowsObfuscation ? 4 : 0)
-        | (includeDescriptorClasses ? 8 : 0);
+    return (allowsAccessModification ? 1 : 0)
+        | (allowsShrinking ? 2 : 0)
+        | (allowsOptimization ? 4 : 0)
+        | (allowsObfuscation ? 8 : 0)
+        | (includeDescriptorClasses ? 16 : 0);
   }
 
   @Override
   public String toString() {
     StringBuilder builder = new StringBuilder();
+    appendWithComma(builder, allowsAccessModification, "allowaccessmodification");
     appendWithComma(builder, allowsObfuscation, "allowobfuscation");
     appendWithComma(builder, allowsShrinking, "allowshrinking");
     appendWithComma(builder, allowsOptimization, "allowoptimization");
@@ -89,8 +105,7 @@
     return builder.toString();
   }
 
-  private void appendWithComma(StringBuilder builder, boolean predicate,
-      String text) {
+  private void appendWithComma(StringBuilder builder, boolean predicate, String text) {
     if (!predicate) {
       return;
     }
diff --git a/src/main/java/com/android/tools/r8/shaking/TreePruner.java b/src/main/java/com/android/tools/r8/shaking/TreePruner.java
index 6560ebc..346caa4 100644
--- a/src/main/java/com/android/tools/r8/shaking/TreePruner.java
+++ b/src/main/java/com/android/tools/r8/shaking/TreePruner.java
@@ -109,7 +109,12 @@
           Log.debug(getClass(), "Removing class: " + clazz);
         }
         prunedTypes.add(clazz.type);
-        usagePrinter.printUnusedClass(clazz);
+        // TODO(b/150118654): It would be nice to add something such as
+        //  clazz.type.isD8R8SynthesizedType, but such test is currently expensive since it is
+        //  based on strings, so we check only against the enum unboxing utility class.
+        if (clazz.type != appView.dexItemFactory().enumUnboxingUtilityType) {
+          usagePrinter.printUnusedClass(clazz);
+        }
       }
     }
     return newClasses;
diff --git a/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java b/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
index a540236..da9038d 100644
--- a/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
+++ b/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
@@ -52,6 +52,7 @@
 import com.android.tools.r8.utils.Box;
 import com.android.tools.r8.utils.FieldSignatureEquivalence;
 import com.android.tools.r8.utils.MethodSignatureEquivalence;
+import com.android.tools.r8.utils.OptionalBool;
 import com.android.tools.r8.utils.Timing;
 import com.android.tools.r8.utils.TraversalContinuation;
 import com.google.common.base.Equivalence;
@@ -946,7 +947,7 @@
       for (DexEncodedMethod virtualMethod : source.virtualMethods()) {
         DexEncodedMethod shadowedBy = findMethodInTarget(virtualMethod);
         if (shadowedBy != null) {
-          if (virtualMethod.accessFlags.isAbstract()) {
+          if (virtualMethod.isAbstract()) {
             // Remove abstract/interface methods that are shadowed.
             deferredRenamings.map(virtualMethod.method, shadowedBy.method);
 
@@ -968,7 +969,7 @@
           // The method is not shadowed. If it is abstract, we can simply move it to the subclass.
           // Non-abstract methods are handled below (they cannot simply be moved to the subclass as
           // a virtual method, because they might be the target of an invoke-super instruction).
-          if (virtualMethod.accessFlags.isAbstract()) {
+          if (virtualMethod.isAbstract()) {
             // Abort if target is non-abstract and does not override the abstract method.
             if (!target.isAbstract()) {
               assert appView.options().testing.allowNonAbstractClassesWithAbstractMethods;
@@ -978,6 +979,8 @@
             // Update the holder of [virtualMethod] using renameMethod().
             DexEncodedMethod resultingVirtualMethod =
                 renameMethod(virtualMethod, availableMethodSignatures, Rename.NEVER);
+            resultingVirtualMethod.setLibraryMethodOverride(
+                virtualMethod.isLibraryMethodOverride());
             deferredRenamings.map(virtualMethod.method, resultingVirtualMethod.method);
             deferredRenamings.recordMove(virtualMethod.method, resultingVirtualMethod.method);
             add(virtualMethods, resultingVirtualMethod, MethodSignatureEquivalence.get());
@@ -1251,6 +1254,7 @@
               code,
               method.hasClassFileVersion() ? method.getClassFileVersion() : -1,
               true);
+      bridge.setLibraryMethodOverride(method.isLibraryMethodOverride());
       if (method.accessFlags.isPromotedToPublic()) {
         // The bridge is now the public method serving the role of the original method, and should
         // reflect that this method was publicized.
@@ -1472,16 +1476,25 @@
       return lens;
     }
 
-    private DexEncodedMethod fixupMethod(DexEncodedMethod encodedMethod) {
-      DexMethod method = encodedMethod.method;
-      DexMethod newMethod = fixupMethod(method);
-      if (newMethod != method) {
-        if (!lensBuilder.hasOriginalSignatureMappingFor(newMethod)) {
-          lensBuilder.map(method, newMethod).recordMove(method, newMethod);
+    private DexEncodedMethod fixupMethod(DexEncodedMethod method) {
+      DexMethod methodReference = method.method;
+      DexMethod newMethodReference = fixupMethod(methodReference);
+      if (newMethodReference != methodReference) {
+        if (!lensBuilder.hasOriginalSignatureMappingFor(newMethodReference)) {
+          lensBuilder
+              .map(methodReference, newMethodReference)
+              .recordMove(methodReference, newMethodReference);
         }
-        return encodedMethod.toTypeSubstitutedMethod(newMethod);
+        DexEncodedMethod newMethod = method.toTypeSubstitutedMethod(newMethodReference);
+        if (newMethod.isNonPrivateVirtualMethod()) {
+          // Since we changed the return type or one of the parameters, this method cannot be a
+          // classpath or library method override, since we only class merge program classes.
+          assert !method.isLibraryMethodOverride().isTrue();
+          newMethod.setLibraryMethodOverride(OptionalBool.FALSE);
+        }
+        return newMethod;
       }
-      return encodedMethod;
+      return method;
     }
 
     private void fixupFields(List<DexEncodedField> fields, FieldSetter setter) {
@@ -1714,7 +1727,7 @@
     }
 
     @Override
-    public DexMethod getRenamedMethodSignature(DexMethod originalMethod) {
+    public DexMethod getRenamedMethodSignature(DexMethod originalMethod, GraphLense applied) {
       throw new Unreachable();
     }
 
diff --git a/src/main/java/com/android/tools/r8/utils/AbortException.java b/src/main/java/com/android/tools/r8/utils/AbortException.java
index 0253dfa..c1e4cd2 100644
--- a/src/main/java/com/android/tools/r8/utils/AbortException.java
+++ b/src/main/java/com/android/tools/r8/utils/AbortException.java
@@ -3,17 +3,43 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.utils;
 
+import com.android.tools.r8.Diagnostic;
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.position.Position;
+
 /**
- * Exception thrown to interrupt processing after a fatal error. The exception doesn't carry
- * directly information about the fatal error: the problem was already reported to the
- * {@link com.android.tools.r8.DiagnosticsHandler}.
+ * Exception thrown to interrupt processing after a fatal error or fail-if-error barrier.
+ *
+ * <p>The abort always contains the diagnostic causing the fatal error, or in the case of multiple
+ * pending errors, one of the errors. In all cases, the abort exception signifies that the error has
+ * been reported to the {@link com.android.tools.r8.DiagnosticsHandler}.
  */
 public class AbortException extends RuntimeException {
-  public AbortException() {
+  private final Diagnostic diagnostic;
 
+  public AbortException(Diagnostic diagnostic) {
+    assert diagnostic != null;
+    this.diagnostic = diagnostic;
   }
 
-  public AbortException(String message) {
-    super(message);
+  @Override
+  public synchronized Throwable getCause() {
+    // In the case of exception diagnostics, treat that as the parent cause.
+    return diagnostic instanceof ExceptionDiagnostic
+        ? ((ExceptionDiagnostic) diagnostic).getCause()
+        : null;
+  }
+
+  @Override
+  public String getMessage() {
+    return diagnostic.getDiagnosticMessage();
+  }
+
+  public Origin getOrigin() {
+    return diagnostic != null ? diagnostic.getOrigin() : Origin.unknown();
+  }
+
+  public Position getPosition() {
+    return diagnostic != null ? diagnostic.getPosition() : Position.UNKNOWN;
   }
 }
diff --git a/src/main/java/com/android/tools/r8/utils/AndroidApp.java b/src/main/java/com/android/tools/r8/utils/AndroidApp.java
index 8dd1b92..00853e0 100644
--- a/src/main/java/com/android/tools/r8/utils/AndroidApp.java
+++ b/src/main/java/com/android/tools/r8/utils/AndroidApp.java
@@ -20,6 +20,7 @@
 import com.android.tools.r8.DexFilePerClassFileConsumer;
 import com.android.tools.r8.DexIndexedConsumer;
 import com.android.tools.r8.DirectoryClassFileProvider;
+import com.android.tools.r8.FeatureSplit;
 import com.android.tools.r8.OutputMode;
 import com.android.tools.r8.ProgramResource;
 import com.android.tools.r8.ProgramResource.Kind;
@@ -31,11 +32,11 @@
 import com.android.tools.r8.errors.CompilationError;
 import com.android.tools.r8.errors.InternalCompilerError;
 import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.features.FeatureSplitConfiguration;
 import com.android.tools.r8.origin.ArchiveEntryOrigin;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.origin.PathOrigin;
 import com.android.tools.r8.shaking.FilteredClassPath;
-import com.android.tools.r8.shaking.ProguardConfiguration;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
 import com.google.common.io.ByteStreams;
@@ -45,6 +46,7 @@
 import java.io.ByteArrayOutputStream;
 import java.io.IOException;
 import java.io.InputStream;
+import java.io.OutputStream;
 import java.nio.charset.StandardCharsets;
 import java.nio.file.Files;
 import java.nio.file.NoSuchFileException;
@@ -57,11 +59,13 @@
 import java.util.Collections;
 import java.util.Comparator;
 import java.util.HashMap;
+import java.util.IdentityHashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
 import java.util.TreeSet;
 import java.util.function.Consumer;
+import java.util.function.Function;
 import java.util.zip.ZipEntry;
 import java.util.zip.ZipInputStream;
 import java.util.zip.ZipOutputStream;
@@ -75,11 +79,25 @@
 public class AndroidApp {
 
   private static final String dumpVersionFileName = "r8-version";
+  private static final String dumpBuildPropertiesFileName = "build.properties";
   private static final String dumpProgramFileName = "program.jar";
   private static final String dumpClasspathFileName = "classpath.jar";
   private static final String dumpLibraryFileName = "library.jar";
   private static final String dumpConfigFileName = "proguard.config";
 
+  private static Map<FeatureSplit, String> dumpFeatureSplitFileNames(
+      FeatureSplitConfiguration featureSplitConfiguration) {
+    Map<FeatureSplit, String> featureSplitFileNames = new IdentityHashMap<>();
+    if (featureSplitConfiguration != null) {
+      int i = 1;
+      for (FeatureSplit featureSplit : featureSplitConfiguration.getFeatureSplits()) {
+        featureSplitFileNames.put(featureSplit, "feature-" + i + ".jar");
+        i++;
+      }
+    }
+    return featureSplitFileNames;
+  }
+
   private final ImmutableList<ProgramResourceProvider> programResourceProviders;
   private final ImmutableMap<Resource, String> programResourcesMainDescriptor;
   private final ImmutableList<ClassFileResourceProvider> classpathResourceProviders;
@@ -422,25 +440,40 @@
     return programResourcesMainDescriptor.get(resource);
   }
 
-  public void dump(Path output, ProguardConfiguration configuration, Reporter reporter) {
+  public void dump(Path output, InternalOptions options) {
     int nextDexIndex = 0;
     OpenOption[] openOptions =
         new OpenOption[] {StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING};
     try (ZipOutputStream out = new ZipOutputStream(Files.newOutputStream(output, openOptions))) {
       writeToZipStream(
           out, dumpVersionFileName, Version.getVersionString().getBytes(), ZipEntry.DEFLATED);
-      if (configuration != null) {
-        String proguardConfig = configuration.getParsedConfiguration();
+      writeToZipStream(
+          out,
+          dumpBuildPropertiesFileName,
+          getBuildPropertiesContents(options).getBytes(),
+          ZipEntry.DEFLATED);
+      if (options.getProguardConfiguration() != null) {
+        String proguardConfig = options.getProguardConfiguration().getParsedConfiguration();
         writeToZipStream(out, dumpConfigFileName, proguardConfig.getBytes(), ZipEntry.DEFLATED);
       }
-      nextDexIndex = dumpProgramResources(dumpProgramFileName, nextDexIndex, out);
+      nextDexIndex =
+          dumpProgramResources(
+              dumpProgramFileName,
+              dumpFeatureSplitFileNames(options.featureSplitConfiguration),
+              nextDexIndex,
+              out,
+              options.featureSplitConfiguration);
       nextDexIndex = dumpClasspathResources(nextDexIndex, out);
       nextDexIndex = dumpLibraryResources(nextDexIndex, out);
     } catch (IOException | ResourceException e) {
-      reporter.fatalError(new StringDiagnostic("Failed to dump inputs"), e);
+      throw options.reporter.fatalError(new ExceptionDiagnostic(e));
     }
   }
 
+  private String getBuildPropertiesContents(InternalOptions options) {
+    return "min-api=" + options.minApiLevel;
+  }
+
   private int dumpLibraryResources(int nextDexIndex, ZipOutputStream out)
       throws IOException, ResourceException {
     nextDexIndex =
@@ -480,31 +513,96 @@
     };
   }
 
-  private int dumpProgramResources(String archiveName, int nextDexIndex, ZipOutputStream out)
+  private int dumpProgramResources(
+      String archiveName,
+      Map<FeatureSplit, String> featureSplitArchiveNames,
+      int nextDexIndex,
+      ZipOutputStream out,
+      FeatureSplitConfiguration featureSplitConfiguration)
       throws IOException, ResourceException {
-    try (ByteArrayOutputStream archiveByteStream = new ByteArrayOutputStream()) {
-      try (ZipOutputStream archiveOutputStream = new ZipOutputStream(archiveByteStream)) {
-        Object2IntMap<String> seen = new Object2IntOpenHashMap<>();
-        Set<DataEntryResource> dataEntries = getDataEntryResourcesForTesting();
-        for (DataEntryResource dataResource : dataEntries) {
-          String entryName = dataResource.getName();
-          try (InputStream dataStream = dataResource.getByteStream()) {
-            byte[] bytes = ByteStreams.toByteArray(dataStream);
-            writeToZipStream(archiveOutputStream, entryName, bytes, ZipEntry.DEFLATED);
+    Map<FeatureSplit, ByteArrayOutputStream> featureSplitArchiveByteStreams =
+        new IdentityHashMap<>();
+    Map<FeatureSplit, ZipOutputStream> featureSplitArchiveOutputStreams = new IdentityHashMap<>();
+    try {
+      if (featureSplitConfiguration != null) {
+        for (FeatureSplit featureSplit : featureSplitConfiguration.getFeatureSplits()) {
+          ByteArrayOutputStream archiveByteStream = new ByteArrayOutputStream();
+          featureSplitArchiveByteStreams.put(featureSplit, archiveByteStream);
+          featureSplitArchiveOutputStreams.put(
+              featureSplit, new ZipOutputStream(archiveByteStream));
+        }
+      }
+      try (ByteArrayOutputStream archiveByteStream = new ByteArrayOutputStream()) {
+        try (ZipOutputStream archiveOutputStream = new ZipOutputStream(archiveByteStream)) {
+          Object2IntMap<String> seen = new Object2IntOpenHashMap<>();
+          Set<DataEntryResource> dataEntries = getDataEntryResourcesForTesting();
+          for (DataEntryResource dataResource : dataEntries) {
+            String entryName = dataResource.getName();
+            try (InputStream dataStream = dataResource.getByteStream()) {
+              byte[] bytes = ByteStreams.toByteArray(dataStream);
+              writeToZipStream(archiveOutputStream, entryName, bytes, ZipEntry.DEFLATED);
+            }
+          }
+          for (ProgramResourceProvider provider : programResourceProviders) {
+            for (ProgramResource programResource : provider.getProgramResources()) {
+              nextDexIndex =
+                  dumpProgramResource(
+                      seen,
+                      nextDexIndex,
+                      classDescriptor -> {
+                        if (featureSplitConfiguration != null) {
+                          FeatureSplit featureSplit =
+                              featureSplitConfiguration.getFeatureSplitFromClassDescriptor(
+                                  classDescriptor);
+                          if (featureSplit != null) {
+                            return featureSplitArchiveOutputStreams.get(featureSplit);
+                          }
+                        }
+                        return archiveOutputStream;
+                      },
+                      archiveOutputStream,
+                      programResource);
+            }
           }
         }
-        for (ProgramResourceProvider provider : programResourceProviders) {
-          for (ProgramResource programResource : provider.getProgramResources()) {
-            nextDexIndex =
-                dumpProgramResource(seen, nextDexIndex, archiveOutputStream, programResource);
+        writeToZipStream(out, archiveName, archiveByteStream.toByteArray(), ZipEntry.DEFLATED);
+        if (featureSplitConfiguration != null) {
+          for (FeatureSplit featureSplit : featureSplitConfiguration.getFeatureSplits()) {
+            featureSplitArchiveOutputStreams.remove(featureSplit).close();
+            writeToZipStream(
+                out,
+                featureSplitArchiveNames.get(featureSplit),
+                featureSplitArchiveByteStreams.get(featureSplit).toByteArray(),
+                ZipEntry.DEFLATED);
           }
         }
       }
-      writeToZipStream(out, archiveName, archiveByteStream.toByteArray(), ZipEntry.DEFLATED);
+    } finally {
+      closeOutputStreams(featureSplitArchiveOutputStreams.values());
     }
     return nextDexIndex;
   }
 
+  private void closeOutputStreams(Collection<ZipOutputStream> outputStreams) throws IOException {
+    IOException exception = null;
+    RuntimeException runtimeException = null;
+    for (OutputStream outputStream : outputStreams) {
+      try {
+        outputStream.close();
+      } catch (IOException e) {
+        exception = e;
+      } catch (RuntimeException e) {
+        runtimeException = e;
+      }
+    }
+    if (exception != null) {
+      throw exception;
+    }
+    if (runtimeException != null) {
+      throw runtimeException;
+    }
+  }
+
   private static int dumpClassFileResources(
       String archiveName,
       int nextDexIndex,
@@ -519,7 +617,12 @@
             ProgramResource programResource = provider.getProgramResource(descriptor);
             int oldDexIndex = nextDexIndex;
             nextDexIndex =
-                dumpProgramResource(seen, nextDexIndex, archiveOutputStream, programResource);
+                dumpProgramResource(
+                    seen,
+                    nextDexIndex,
+                    ignore -> archiveOutputStream,
+                    archiveOutputStream,
+                    programResource);
             assert nextDexIndex == oldDexIndex;
           }
         }
@@ -532,11 +635,11 @@
   private static int dumpProgramResource(
       Object2IntMap<String> seen,
       int nextDexIndex,
-      ZipOutputStream archiveOutputStream,
+      Function<String, ZipOutputStream> cfArchiveOutputStream,
+      ZipOutputStream dexArchiveOutputStream,
       ProgramResource programResource)
       throws ResourceException, IOException {
     byte[] bytes = ByteStreams.toByteArray(programResource.getByteStream());
-    String entryName;
     if (programResource.getKind() == Kind.CF) {
       Set<String> classDescriptors = programResource.getClassDescriptors();
       String classDescriptor =
@@ -546,12 +649,14 @@
       String classFileName = DescriptorUtils.getClassFileName(classDescriptor);
       int dupCount = seen.getOrDefault(classDescriptor, 0);
       seen.put(classDescriptor, dupCount + 1);
-      entryName = dupCount == 0 ? classFileName : (classFileName + "." + dupCount + ".dup");
+      String entryName = dupCount == 0 ? classFileName : (classFileName + "." + dupCount + ".dup");
+      writeToZipStream(
+          cfArchiveOutputStream.apply(classDescriptor), entryName, bytes, ZipEntry.DEFLATED);
     } else {
       assert programResource.getKind() == Kind.DEX;
-      entryName = "classes" + nextDexIndex++ + ".dex";
+      String entryName = "classes" + nextDexIndex++ + ".dex";
+      writeToZipStream(dexArchiveOutputStream, entryName, bytes, ZipEntry.DEFLATED);
     }
-    writeToZipStream(archiveOutputStream, entryName, bytes, ZipEntry.DEFLATED);
     return nextDexIndex;
   }
 
diff --git a/src/main/java/com/android/tools/r8/utils/ArchiveBuilder.java b/src/main/java/com/android/tools/r8/utils/ArchiveBuilder.java
index a47684f..dfb625b 100644
--- a/src/main/java/com/android/tools/r8/utils/ArchiveBuilder.java
+++ b/src/main/java/com/android/tools/r8/utils/ArchiveBuilder.java
@@ -92,12 +92,13 @@
   }
 
   private void handleIOException(IOException e, DiagnosticsHandler handler) {
+    ExceptionDiagnostic diagnostic = new ExceptionDiagnostic(e, origin);
     if (e instanceof ZipException && e.getMessage().startsWith("duplicate entry")) {
       // For now we stick to the Proguard behaviour, see section "Warning: can't write resource ...
       // Duplicate zip entry" on https://www.guardsquare.com/en/proguard/manual/troubleshooting.
-      handler.warning(new ExceptionDiagnostic(e, origin));
+      handler.warning(diagnostic);
     } else {
-      handler.error(new ExceptionDiagnostic(e, origin));
+      handler.error(diagnostic);
     }
   }
 
diff --git a/src/main/java/com/android/tools/r8/utils/Box.java b/src/main/java/com/android/tools/r8/utils/Box.java
index 8d3ec1e..8938bc8 100644
--- a/src/main/java/com/android/tools/r8/utils/Box.java
+++ b/src/main/java/com/android/tools/r8/utils/Box.java
@@ -4,6 +4,8 @@
 
 package com.android.tools.r8.utils;
 
+import java.util.function.Supplier;
+
 public class Box<T> {
 
   private T value;
@@ -14,6 +16,13 @@
     set(initialValue);
   }
 
+  public T computeIfAbsent(Supplier<T> supplier) {
+    if (value == null) {
+      value = supplier.get();
+    }
+    return value;
+  }
+
   public T get() {
     return value;
   }
diff --git a/src/main/java/com/android/tools/r8/utils/CompileDumpCompatR8.java b/src/main/java/com/android/tools/r8/utils/CompileDumpCompatR8.java
index 21a5123..38cd73e 100644
--- a/src/main/java/com/android/tools/r8/utils/CompileDumpCompatR8.java
+++ b/src/main/java/com/android/tools/r8/utils/CompileDumpCompatR8.java
@@ -3,9 +3,12 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.utils;
 
+import static com.android.tools.r8.utils.FileUtils.isArchive;
+
 import com.android.tools.r8.CompatProguardCommandBuilder;
 import com.android.tools.r8.CompilationFailedException;
 import com.android.tools.r8.CompilationMode;
+import com.android.tools.r8.DexIndexedConsumer.ArchiveConsumer;
 import com.android.tools.r8.OutputMode;
 import com.android.tools.r8.R8;
 import com.android.tools.r8.R8Command.Builder;
@@ -13,7 +16,9 @@
 import java.nio.file.Paths;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.LinkedHashMap;
 import java.util.List;
+import java.util.Map;
 
 /**
  * Wrapper to make it easy to call R8 in compat mode when compiling a dump file.
@@ -30,7 +35,7 @@
   private static final List<String> VALID_OPTIONS =
       Arrays.asList("--classfile", "--compat", "--debug", "--release");
 
-  private static final List<String> VALID_OPTIONS_WITH_OPERAND =
+  private static final List<String> VALID_OPTIONS_WITH_SINGLE_OPERAND =
       Arrays.asList(
           "--output",
           "--lib",
@@ -43,6 +48,9 @@
           "--pg-map-output",
           "--desugared-lib");
 
+  private static final List<String> VALID_OPTIONS_WITH_TWO_OPERANDS =
+      Arrays.asList("--feature-jar");
+
   public static void main(String[] args) throws CompilationFailedException {
     boolean isCompatMode = false;
     OutputMode outputMode = OutputMode.DexIndexed;
@@ -50,6 +58,7 @@
     Path pgMapOutput = null;
     CompilationMode compilationMode = CompilationMode.RELEASE;
     List<Path> program = new ArrayList<>();
+    Map<Path, Path> features = new LinkedHashMap<>();
     List<Path> library = new ArrayList<>();
     List<Path> classpath = new ArrayList<>();
     List<Path> config = new ArrayList<>();
@@ -81,7 +90,7 @@
           default:
             throw new IllegalArgumentException("Unimplemented option: " + option);
         }
-      } else if (VALID_OPTIONS_WITH_OPERAND.contains(option)) {
+      } else if (VALID_OPTIONS_WITH_SINGLE_OPERAND.contains(option)) {
         String operand = args[++i];
         switch (option) {
           case "--output":
@@ -117,11 +126,29 @@
           default:
             throw new IllegalArgumentException("Unimplemented option: " + option);
         }
+      } else if (VALID_OPTIONS_WITH_TWO_OPERANDS.contains(option)) {
+        String firstOperand = args[++i];
+        String secondOperand = args[++i];
+        switch (option) {
+          case "--feature-jar":
+            {
+              Path featureIn = Paths.get(firstOperand);
+              Path featureOut = Paths.get(secondOperand);
+              if (!isArchive(featureIn)) {
+                throw new IllegalArgumentException(
+                    "Expected an archive, got `" + featureIn.toString() + "`.");
+              }
+              features.put(featureIn, featureOut);
+              break;
+            }
+          default:
+            throw new IllegalArgumentException("Unimplemented option: " + option);
+        }
       } else {
         program.add(Paths.get(option));
       }
     }
-    Builder builder =
+    Builder commandBuilder =
         new CompatProguardCommandBuilder(isCompatMode)
             .addProgramFiles(program)
             .addLibraryFiles(library)
@@ -130,9 +157,17 @@
             .setOutput(outputPath, outputMode)
             .setMode(compilationMode)
             .setMinApiLevel(minApi);
+    features.forEach(
+        (in, out) ->
+            commandBuilder.addFeatureSplit(
+                featureBuilder ->
+                    featureBuilder
+                        .addProgramResourceProvider(ArchiveResourceProvider.fromArchive(in, true))
+                        .setProgramConsumer(new ArchiveConsumer(out))
+                        .build()));
     if (pgMapOutput != null) {
-      builder.setProguardMapOutputPath(pgMapOutput);
+      commandBuilder.setProguardMapOutputPath(pgMapOutput);
     }
-    R8.run(builder.build());
+    R8.run(commandBuilder.build());
   }
 }
diff --git a/src/main/java/com/android/tools/r8/utils/ConsumerUtils.java b/src/main/java/com/android/tools/r8/utils/ConsumerUtils.java
index 0979b27..2079af2 100644
--- a/src/main/java/com/android/tools/r8/utils/ConsumerUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/ConsumerUtils.java
@@ -4,8 +4,14 @@
 
 package com.android.tools.r8.utils;
 
+import java.util.function.Consumer;
+
 public class ConsumerUtils {
 
+  public static <T> Consumer<T> emptyConsumer() {
+    return ignore -> {};
+  }
+
   public static <T> ThrowingConsumer<T, RuntimeException> emptyThrowingConsumer() {
     return ignore -> {};
   }
diff --git a/src/main/java/com/android/tools/r8/utils/DiagnosticWithThrowable.java b/src/main/java/com/android/tools/r8/utils/DiagnosticWithThrowable.java
deleted file mode 100644
index b169297..0000000
--- a/src/main/java/com/android/tools/r8/utils/DiagnosticWithThrowable.java
+++ /dev/null
@@ -1,21 +0,0 @@
-// Copyright (c) 2017, the R8 project authors. Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-
-package com.android.tools.r8.utils;
-
-import com.android.tools.r8.Diagnostic;
-
-public abstract class DiagnosticWithThrowable implements Diagnostic {
-
-  private final Throwable throwable;
-
-  protected DiagnosticWithThrowable(Throwable throwable) {
-    assert throwable != null;
-    this.throwable = throwable;
-  }
-
-  public Throwable getThrowable() {
-    return throwable;
-  }
-}
diff --git a/src/main/java/com/android/tools/r8/utils/ExceptionDiagnostic.java b/src/main/java/com/android/tools/r8/utils/ExceptionDiagnostic.java
index 4dc6d48..a73aeb5 100644
--- a/src/main/java/com/android/tools/r8/utils/ExceptionDiagnostic.java
+++ b/src/main/java/com/android/tools/r8/utils/ExceptionDiagnostic.java
@@ -4,28 +4,40 @@
 
 package com.android.tools.r8.utils;
 
+import com.android.tools.r8.Diagnostic;
 import com.android.tools.r8.Keep;
 import com.android.tools.r8.ResourceException;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.position.Position;
-import java.io.FileNotFoundException;
-import java.io.PrintWriter;
-import java.io.StringWriter;
-import java.nio.file.FileAlreadyExistsException;
-import java.nio.file.NoSuchFileException;
 
+/**
+ * Diagnostic for any unhandled exception arising during compilation.
+ *
+ * <p>The inner-most exception giving rise to the exception can be obtained as the "cause". If the
+ * the inner-most exception is not the same as the exception at the point of the interception and
+ * conversion to a diagnostic, the full exception stack can be obtained in the suppressed exceptions
+ * on the inner-most cause.
+ */
 @Keep
-public class ExceptionDiagnostic extends DiagnosticWithThrowable {
+public class ExceptionDiagnostic implements Diagnostic {
 
+  private final Throwable cause;
   private final Origin origin;
   private final Position position;
 
-  public ExceptionDiagnostic(Throwable e, Origin origin, Position position) {
-    super(e);
+  public ExceptionDiagnostic(Throwable cause, Origin origin, Position position) {
+    assert cause != null;
+    assert origin != null;
+    assert position != null;
+    this.cause = cause;
     this.origin = origin;
     this.position = position;
   }
 
+  public ExceptionDiagnostic(Throwable cause) {
+    this(cause, Origin.unknown(), Position.UNKNOWN);
+  }
+
   public ExceptionDiagnostic(Throwable e, Origin origin) {
     this(e, origin, Position.UNKNOWN);
   }
@@ -44,20 +56,12 @@
     return position;
   }
 
+  public Throwable getCause() {
+    return cause;
+  }
+
   @Override
   public String getDiagnosticMessage() {
-    Throwable e = getThrowable();
-    if (e instanceof NoSuchFileException || e instanceof FileNotFoundException) {
-      return "File not found: " + e.getMessage();
-    }
-    if (e instanceof FileAlreadyExistsException) {
-      return "File already exists: " + e.getMessage();
-    }
-    StringWriter stack = new StringWriter();
-    e.printStackTrace(new PrintWriter(stack));
-    String message = e.getMessage();
-    return message != null
-        ? StringUtils.joinLines(message, "Stack trace:", stack.toString())
-        : StringUtils.joinLines(stack.toString());
+    return cause.toString();
   }
 }
diff --git a/src/main/java/com/android/tools/r8/utils/ExceptionUtils.java b/src/main/java/com/android/tools/r8/utils/ExceptionUtils.java
index 79e3818..e5ba3f1 100644
--- a/src/main/java/com/android/tools/r8/utils/ExceptionUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/ExceptionUtils.java
@@ -12,10 +12,11 @@
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.origin.PathOrigin;
 import com.android.tools.r8.position.Position;
-import com.google.common.collect.ObjectArrays;
 import java.io.IOException;
 import java.nio.file.FileSystemException;
 import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.List;
 import java.util.concurrent.ExecutionException;
 import java.util.function.Consumer;
 import java.util.function.Supplier;
@@ -65,30 +66,95 @@
   public static void withCompilationHandler(Reporter reporter, CompileAction action)
       throws CompilationFailedException {
     try {
-      try {
-        action.run();
-      } catch (IOException e) {
-        throw reporter.fatalError(new ExceptionDiagnostic(e, extractIOExceptionOrigin(e)));
-      } catch (CompilationError e) {
-        throw reporter.fatalError(new ExceptionDiagnostic(e, e.getOrigin(), e.getPosition()));
-      } catch (ResourceException e) {
-        throw reporter.fatalError(new ExceptionDiagnostic(e, e.getOrigin()));
-      } catch (AssertionError e) {
-        throw reporter.fatalError(new ExceptionDiagnostic(e, Origin.unknown()));
-      } catch (Exception e) {
-        String filename = "Version_" + Version.LABEL + ".java";
-        StackTraceElement versionElement = new StackTraceElement(
-            Version.class.getSimpleName(), "fakeStackEntry", filename, 0);
-        StackTraceElement[] withVersion = ObjectArrays.concat(versionElement, e.getStackTrace());
-        e.setStackTrace(withVersion);
-        throw e;
-      }
+      action.run();
       reporter.failIfPendingErrors();
-    } catch (AbortException e) {
-      throw new CompilationFailedException(e);
+    } catch (Throwable e) {
+      throw failCompilation(reporter, e);
     }
   }
 
+  private static CompilationFailedException failCompilation(
+      Reporter reporter, Throwable topMostException) {
+    // Find inner-most cause of the failure and compute origin, position and reported for the path.
+    boolean hasBeenReported = false;
+    Origin origin = Origin.unknown();
+    Position position = Position.UNKNOWN;
+    List<Throwable> suppressed = new ArrayList<>();
+    Throwable innerMostCause = topMostException;
+    while (true) {
+      hasBeenReported |= innerMostCause instanceof AbortException;
+      Origin nextOrigin = getOrigin(innerMostCause);
+      if (nextOrigin != Origin.unknown()) {
+        origin = nextOrigin;
+      }
+      Position nextPosition = getPosition(innerMostCause);
+      if (nextPosition != Position.UNKNOWN) {
+        position = nextPosition;
+      }
+      if (innerMostCause.getCause() == null || suppressed.contains(innerMostCause)) {
+        break;
+      }
+      suppressed.add(innerMostCause);
+      innerMostCause = innerMostCause.getCause();
+    }
+
+    // If no abort is seen, the exception is not reported, so report it now.
+    if (!hasBeenReported) {
+      reporter.error(new ExceptionDiagnostic(innerMostCause, origin, position));
+    }
+
+    // Build the top-level compiler exception and version stack.
+    StringBuilder message = new StringBuilder("Compilation failed to complete");
+    if (position != Position.UNKNOWN) {
+      message.append(", position: ").append(position);
+    }
+    if (origin != Origin.unknown()) {
+      message.append(", origin: ").append(origin);
+    }
+    // Create the final exception object.
+    CompilationFailedException rethrow =
+        new CompilationFailedException(message.toString(), innerMostCause);
+    // Replace its stack by the cause stack and insert version info at the top.
+    String filename = "Version_" + Version.LABEL + ".java";
+    rethrow.setStackTrace(
+        new StackTraceElement[] {
+          new StackTraceElement(Version.class.getSimpleName(), "fakeStackEntry", filename, 0)
+        });
+    return rethrow;
+  }
+
+  private static Origin getOrigin(Throwable e) {
+    if (e instanceof IOException) {
+      return extractIOExceptionOrigin((IOException) e);
+    }
+    if (e instanceof CompilationError) {
+      return ((CompilationError) e).getOrigin();
+    }
+    if (e instanceof ResourceException) {
+      return ((ResourceException) e).getOrigin();
+    }
+    if (e instanceof OriginAttachmentException) {
+      return ((OriginAttachmentException) e).origin;
+    }
+    if (e instanceof AbortException) {
+      return ((AbortException) e).getOrigin();
+    }
+    return Origin.unknown();
+  }
+
+  private static Position getPosition(Throwable e) {
+    if (e instanceof CompilationError) {
+      return ((CompilationError) e).getPosition();
+    }
+    if (e instanceof OriginAttachmentException) {
+      return ((OriginAttachmentException) e).position;
+    }
+    if (e instanceof AbortException) {
+      return ((AbortException) e).getPosition();
+    }
+    return Position.UNKNOWN;
+  }
+
   public interface MainAction {
     void run() throws CompilationFailedException;
   }
@@ -111,7 +177,7 @@
   // We should try to avoid the use of this extraction as it signifies a point where we don't have
   // enough context to associate a specific origin with an IOException. Concretely, we should move
   // towards always catching IOException and rethrowing CompilationError with proper origins.
-  public static Origin extractIOExceptionOrigin(IOException e) {
+  private static Origin extractIOExceptionOrigin(IOException e) {
     if (e instanceof FileSystemException) {
       FileSystemException fse = (FileSystemException) e;
       if (fse.getFile() != null && !fse.getFile().isEmpty()) {
@@ -122,27 +188,60 @@
   }
 
   public static RuntimeException unwrapExecutionException(ExecutionException executionException) {
-    Throwable cause = executionException.getCause();
-    if (cause instanceof Error) {
-      // add original exception as suppressed exception to provide the original stack trace
-      cause.addSuppressed(executionException);
-      throw (Error) cause;
-    } else if (cause instanceof RuntimeException) {
-      cause.addSuppressed(executionException);
-      throw (RuntimeException) cause;
-    } else {
-      throw new RuntimeException(executionException.getMessage(), cause);
-    }
+    return new RuntimeException(executionException);
   }
 
-  public static <T> T withOriginAttachmentHandler(
+  public static void withOriginAttachmentHandler(Origin origin, Runnable action) {
+    withOriginAndPositionAttachmentHandler(origin, Position.UNKNOWN, action);
+  }
+
+  public static <T> T withOriginAttachmentHandler(Origin origin, Supplier<T> action) {
+    return withOriginAndPositionAttachmentHandler(origin, Position.UNKNOWN, action);
+  }
+
+  public static void withOriginAndPositionAttachmentHandler(
+      Origin origin, Position position, Runnable action) {
+    withOriginAndPositionAttachmentHandler(
+        origin,
+        position,
+        () -> {
+          action.run();
+          return null;
+        });
+  }
+
+  public static <T> T withOriginAndPositionAttachmentHandler(
       Origin origin, Position position, Supplier<T> action) {
     try {
       return action.get();
-    } catch (CompilationError e) {
-      throw e.withAdditionalOriginAndPositionInfo(origin, position);
     } catch (RuntimeException e) {
-      throw new CompilationError(e.getMessage(), e, origin, position);
+      throw OriginAttachmentException.wrap(e, origin, position);
+    }
+  }
+
+  private static class OriginAttachmentException extends RuntimeException {
+    final Origin origin;
+    final Position position;
+
+    public static RuntimeException wrap(RuntimeException e, Origin origin, Position position) {
+      return needsAttachment(e, origin, position)
+          ? new OriginAttachmentException(e, origin, position)
+          : e;
+    }
+
+    private OriginAttachmentException(RuntimeException e, Origin origin, Position position) {
+      super(e);
+      this.origin = origin;
+      this.position = position;
+    }
+
+    private static boolean needsAttachment(RuntimeException e, Origin origin, Position position) {
+      if (origin == Origin.unknown() && position == Position.UNKNOWN) {
+        return false;
+      }
+      Origin existingOrigin = getOrigin(e);
+      Position existingPosition = getPosition(e);
+      return origin != existingOrigin || position != existingPosition;
     }
   }
 }
diff --git a/src/main/java/com/android/tools/r8/utils/FeatureClassMapping.java b/src/main/java/com/android/tools/r8/utils/FeatureClassMapping.java
index 0230ec1..00412d9 100644
--- a/src/main/java/com/android/tools/r8/utils/FeatureClassMapping.java
+++ b/src/main/java/com/android/tools/r8/utils/FeatureClassMapping.java
@@ -101,8 +101,9 @@
     try {
       lines = FileUtils.readAllLines(file);
     } catch (IOException e) {
-      reporter.error(new ExceptionDiagnostic(e, new SpecificationOrigin(file)));
-      throw new AbortException();
+      ExceptionDiagnostic error = new ExceptionDiagnostic(e, new SpecificationOrigin(file));
+      reporter.error(error);
+      throw new AbortException(error);
     }
     for (int i = 0; i < lines.size(); i++) {
       String line = lines.get(i);
@@ -120,8 +121,9 @@
             .map(DescriptorUtils::descriptorToJavaType)
             .collect(Collectors.toList());
       } catch (IOException e) {
-        reporter.error(new ExceptionDiagnostic(e, new JarFileOrigin(jarPath)));
-        throw new AbortException();
+        ExceptionDiagnostic error = new ExceptionDiagnostic(e, new JarFileOrigin(jarPath));
+        reporter.error(error);
+        throw new AbortException(error);
       }
     }
 
@@ -132,8 +134,9 @@
               .map(ZipEntry::getName)
               .collect(Collectors.toList());
         } catch (IOException e) {
-          reporter.error(new ExceptionDiagnostic(e, new JarFileOrigin(Paths.get(jar))));
-          throw new AbortException();
+        ExceptionDiagnostic error = new ExceptionDiagnostic(e, new JarFileOrigin(Paths.get(jar)));
+        reporter.error(error);
+        throw new AbortException(error);
         }
     }
 
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 6b1fb12..997d4e9 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -195,8 +195,7 @@
     enableValuePropagation = false;
     enableSideEffectAnalysis = false;
     enableTreeShakingOfLibraryMethodOverrides = false;
-    enablePropagationOfDynamicTypesAtCallSites = false;
-    enablePropagationOfConstantsAtCallSites = false;
+    callSiteOptimizationOptions.disableOptimization();
   }
 
   public boolean printTimes = System.getProperty("com.android.tools.r8.printtimes") != null;
@@ -247,14 +246,6 @@
   public boolean enableNameReflectionOptimization = true;
   public boolean enableStringConcatenationOptimization = true;
   public boolean enableTreeShakingOfLibraryMethodOverrides = false;
-  public boolean enablePropagationOfDynamicTypesAtCallSites = true;
-  // TODO(b/69963623): enable if everything is ready, including signature rewriting at call sites.
-  public boolean enablePropagationOfConstantsAtCallSites = false;
-  // At 2.0, part of @Metadata up to this flag is rewritten, which is super-type hierarchy.
-  public boolean enableKotlinMetadataRewritingForMembers = true;
-  // Up to 2.0, Kotlin @Metadata is removed if the associated class is renamed.
-  // Under this flag, Kotlin @Metadata is generally kept and modified for all program classes.
-  public boolean enableKotlinMetadataRewritingForRenamedClasses = true;
   public boolean encodeChecksums = false;
   public BiPredicate<String, Long> dexClassChecksumFilter = (name, checksum) -> true;
   public boolean cfToCfDesugar = false;
@@ -308,6 +299,9 @@
   // fused together by play store when shipped for pre-L devices.
   public boolean ignoreMainDexMissingClasses = false;
 
+  // Boolean value indicating that byte code pass through may be enabled.
+  public boolean enableCfByteCodePassThrough = false;
+
   // Hidden marker for classes.dex
   private boolean hasMarker = false;
   private Marker marker;
@@ -488,6 +482,9 @@
   // to disable the check that the build makes sense for multi-dexing.
   public boolean enableMainDexListCheck = true;
 
+  // TODO(b/156934674): Remove when resource shrinker is removed.
+  public boolean isRunningDeprecatedResourceShrinker = false;
+
   private final boolean enableTreeShaking;
   private final boolean enableMinification;
 
@@ -517,6 +514,8 @@
 
   public boolean debug = false;
 
+  private final CallSiteOptimizationOptions callSiteOptimizationOptions =
+      new CallSiteOptimizationOptions();
   private final ProtoShrinkingOptions protoShrinking = new ProtoShrinkingOptions();
   private final KotlinOptimizationOptions kotlinOptimizationOptions =
       new KotlinOptimizationOptions();
@@ -535,6 +534,10 @@
 
   public LineNumberOptimization lineNumberOptimization = LineNumberOptimization.ON;
 
+  public CallSiteOptimizationOptions callSiteOptimizationOptions() {
+    return callSiteOptimizationOptions;
+  }
+
   public ProtoShrinkingOptions protoShrinking() {
     return protoShrinking;
   }
@@ -1065,6 +1068,51 @@
         System.getProperty("com.android.tools.r8.disableKotlinSpecificOptimizations") != null;
   }
 
+  public static class CallSiteOptimizationOptions {
+
+    // Each time we see an invoke with more dispatch targets than the threshold, we stop call site
+    // propagation for all these dispatch targets. The motivation for this is that it is expensive
+    // and that we are somewhat unlikely to have precise knowledge about the value of arguments when
+    // there are many (possibly spurious) call graph edges.
+    private final int maxNumberOfDispatchTargetsBeforeAbandoning = 10;
+
+    // TODO(b/69963623): enable if everything is ready, including signature rewriting at call sites.
+    private boolean enableConstantPropagation = false;
+    private boolean enableTypePropagation = true;
+
+    private void disableOptimization() {
+      enableConstantPropagation = false;
+      enableTypePropagation = false;
+    }
+
+    public void disableTypePropagationForTesting() {
+      enableTypePropagation = false;
+    }
+
+    // TODO(b/69963623): Remove this once enabled.
+    @VisibleForTesting
+    public static void enableConstantPropagationForTesting(InternalOptions options) {
+      assert !options.callSiteOptimizationOptions().isConstantPropagationEnabled();
+      options.callSiteOptimizationOptions().enableConstantPropagation = true;
+    }
+
+    public int getMaxNumberOfDispatchTargetsBeforeAbandoning() {
+      return maxNumberOfDispatchTargetsBeforeAbandoning;
+    }
+
+    public boolean isEnabled() {
+      return enableConstantPropagation || enableTypePropagation;
+    }
+
+    public boolean isConstantPropagationEnabled() {
+      return enableConstantPropagation;
+    }
+
+    public boolean isTypePropagationEnabled() {
+      return enableTypePropagation;
+    }
+  }
+
   public static class ProtoShrinkingOptions {
 
     public boolean enableGeneratedExtensionRegistryShrinking = false;
@@ -1128,7 +1176,6 @@
     public boolean enableSwitchToIfRewriting = true;
     public boolean enableEnumUnboxingDebugLogs = false;
     public boolean forceRedundantConstNumberRemoval = false;
-    public boolean forceAssumeNoneInsertion = false;
     public boolean invertConditionals = false;
     public boolean placeExceptionalBlocksLast = false;
     public boolean dontCreateMarkerInD8 = false;
@@ -1166,6 +1213,9 @@
 
     public MinifierTestingOptions minifier = new MinifierTestingOptions();
 
+    // Testing hooks to trigger effects in various compiler places.
+    public Runnable hookInIrConversion = null;
+
     public static class MinifierTestingOptions {
 
       public Comparator<DexMethod> interfaceMethodOrdering = null;
@@ -1191,7 +1241,7 @@
 
     public Consumer<ProgramMethod> callSiteOptimizationInfoInspector = null;
 
-    public Predicate<DexEncodedMethod> cfByteCodePassThrough = null;
+    public Predicate<DexMethod> cfByteCodePassThrough = null;
   }
 
   @VisibleForTesting
@@ -1201,22 +1251,6 @@
     enableNameReflectionOptimization = false;
   }
 
-  // TODO(b/69963623): Remove this once enabled.
-  @VisibleForTesting
-  public void enablePropagationOfConstantsAtCallSites() {
-    assert !enablePropagationOfConstantsAtCallSites;
-    enablePropagationOfConstantsAtCallSites = true;
-  }
-
-  public boolean isCallSiteOptimizationEnabled() {
-    return enablePropagationOfConstantsAtCallSites || enablePropagationOfDynamicTypesAtCallSites;
-  }
-
-  public void disableCallSiteOptimization() {
-    enablePropagationOfConstantsAtCallSites = false;
-    enablePropagationOfDynamicTypesAtCallSites = false;
-  }
-
   private boolean hasMinApi(AndroidApiLevel level) {
     assert isGeneratingDex();
     return minApiLevel >= level.getLevel();
diff --git a/src/main/java/com/android/tools/r8/utils/LazyBox.java b/src/main/java/com/android/tools/r8/utils/LazyBox.java
new file mode 100644
index 0000000..38d1f2a
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/utils/LazyBox.java
@@ -0,0 +1,20 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.utils;
+
+import java.util.function.Supplier;
+
+public class LazyBox<T> extends Box<T> {
+
+  private final Supplier<T> supplier;
+
+  public LazyBox(Supplier<T> supplier) {
+    this.supplier = supplier;
+  }
+
+  public T computeIfAbsent() {
+    return super.computeIfAbsent(supplier);
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/utils/Reporter.java b/src/main/java/com/android/tools/r8/utils/Reporter.java
index ef17f80..f7f9d1f 100644
--- a/src/main/java/com/android/tools/r8/utils/Reporter.java
+++ b/src/main/java/com/android/tools/r8/utils/Reporter.java
@@ -3,22 +3,14 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.utils;
 
-import com.android.tools.r8.CompilationFailedException;
 import com.android.tools.r8.Diagnostic;
 import com.android.tools.r8.DiagnosticsHandler;
-import com.android.tools.r8.errors.CompilationError;
 import com.android.tools.r8.errors.Unreachable;
-import com.android.tools.r8.origin.Origin;
-import com.android.tools.r8.position.Position;
-import java.util.ArrayList;
-import java.util.Collection;
 
 public class Reporter implements DiagnosticsHandler {
 
   private final DiagnosticsHandler clientHandler;
-  private int errorCount = 0;
-  private Diagnostic lastError;
-  private final Collection<Throwable> suppressedExceptions = new ArrayList<>();
+  private AbortException abort = null;
 
   public Reporter() {
     this(new DiagnosticsHandler() {});
@@ -44,28 +36,19 @@
 
   @Override
   public synchronized void error(Diagnostic error) {
+    abort = new AbortException(error);
     clientHandler.error(error);
-    lastError = error;
-    errorCount++;
   }
 
   public void error(String message) {
     error(new StringDiagnostic(message));
   }
 
-  public synchronized void error(Diagnostic error, Throwable suppressedException) {
-    clientHandler.error(error);
-    lastError = error;
-    errorCount++;
-    suppressedExceptions.add(suppressedException);
-  }
-
   /**
    * @throws AbortException always.
    */
   public RuntimeException fatalError(String message) {
-    fatalError(new StringDiagnostic(message));
-    throw new Unreachable();
+    throw fatalError(new StringDiagnostic(message));
   }
 
   /**
@@ -77,53 +60,10 @@
     throw new Unreachable();
   }
 
-  /**
-   * @throws AbortException always.
-   */
-  public RuntimeException fatalError(Diagnostic error, Throwable suppressedException) {
-    error(error, suppressedException);
-    failIfPendingErrors();
-    throw new Unreachable();
-  }
-
-  /**
-   * @throws AbortException if any error was reported.
-   */
-  public void failIfPendingErrors() {
-    synchronized (this) {
-      if (errorCount != 0) {
-        AbortException abort;
-        if (lastError != null && lastError.getDiagnosticMessage() != null) {
-          StringBuilder builder = new StringBuilder("Error: ");
-          if (lastError.getOrigin() != Origin.unknown()) {
-            builder.append(lastError.getOrigin()).append(", ");
-          }
-          if (lastError.getPosition() != Position.UNKNOWN) {
-            builder.append(lastError.getPosition()).append(", ");
-          }
-          builder.append(lastError.getDiagnosticMessage());
-          abort = new AbortException(builder.toString());
-        } else {
-          abort = new AbortException();
-        }
-        throw addSuppressedExceptions(abort);
-      }
-    }
-  }
-
-  private <T extends Throwable> T addSuppressedExceptions(T t) {
-    suppressedExceptions.forEach(t::addSuppressed);
-    return t;
-  }
-
-  public void guard(Runnable action) throws CompilationFailedException {
-    try {
-      action.run();
-    } catch (CompilationError e) {
-      error(e.toStringDiagnostic());
-      throw addSuppressedExceptions(new CompilationFailedException());
-    } catch (AbortException e) {
-      throw new CompilationFailedException(e);
+  /** @throws AbortException if any error was reported. */
+  public synchronized void failIfPendingErrors() {
+    if (abort != null) {
+      throw abort;
     }
   }
 }
diff --git a/src/main/java/com/android/tools/r8/utils/StringDiagnostic.java b/src/main/java/com/android/tools/r8/utils/StringDiagnostic.java
index 7b482a8..6476a5a 100644
--- a/src/main/java/com/android/tools/r8/utils/StringDiagnostic.java
+++ b/src/main/java/com/android/tools/r8/utils/StringDiagnostic.java
@@ -33,6 +33,9 @@
   }
 
   public StringDiagnostic(String message, Origin origin, Position position) {
+    assert message != null;
+    assert origin != null;
+    assert position != null;
     this.origin = origin;
     this.position = position;
     this.message = message;
diff --git a/src/main/java/com/android/tools/r8/utils/TriConsumer.java b/src/main/java/com/android/tools/r8/utils/TriConsumer.java
new file mode 100644
index 0000000..e42aa62
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/utils/TriConsumer.java
@@ -0,0 +1,9 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.utils;
+
+public interface TriConsumer<S, T, U> {
+
+  void accept(S s, T t, U u);
+}
diff --git a/src/main/java/com/android/tools/r8/utils/collections/LongLivedProgramMethodSetBuilder.java b/src/main/java/com/android/tools/r8/utils/collections/LongLivedProgramMethodSetBuilder.java
index 954eae1..1c6d63a 100644
--- a/src/main/java/com/android/tools/r8/utils/collections/LongLivedProgramMethodSetBuilder.java
+++ b/src/main/java/com/android/tools/r8/utils/collections/LongLivedProgramMethodSetBuilder.java
@@ -7,17 +7,29 @@
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.GraphLense;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.google.common.collect.Sets;
 import java.util.Set;
 import java.util.function.IntFunction;
 
-public class LongLivedProgramMethodSetBuilder {
+public class LongLivedProgramMethodSetBuilder<T extends ProgramMethodSet> {
 
-  private Set<DexMethod> methods = Sets.newIdentityHashSet();
+  private final IntFunction<T> factory;
+  private final Set<DexMethod> methods = Sets.newIdentityHashSet();
 
-  public LongLivedProgramMethodSetBuilder() {}
+  private LongLivedProgramMethodSetBuilder(IntFunction<T> factory) {
+    this.factory = factory;
+  }
+
+  public static LongLivedProgramMethodSetBuilder<?> create() {
+    return new LongLivedProgramMethodSetBuilder<>(ProgramMethodSet::create);
+  }
+
+  public static LongLivedProgramMethodSetBuilder<SortedProgramMethodSet> createSorted() {
+    return new LongLivedProgramMethodSetBuilder<>(ignore -> SortedProgramMethodSet.create());
+  }
 
   public void add(ProgramMethod method) {
     methods.add(method.getReference());
@@ -27,15 +39,23 @@
     methods.forEach(this::add);
   }
 
-  public ProgramMethodSet build(AppView<AppInfoWithLiveness> appView) {
-    return build(appView, ProgramMethodSet::create);
+  public void rewrittenWithLens(AppView<AppInfoWithLiveness> appView, GraphLense applied) {
+    Set<DexMethod> newMethods = Sets.newIdentityHashSet();
+    for (DexMethod method : methods) {
+      newMethods.add(appView.graphLense().getRenamedMethodSignature(method, applied));
+    }
+    methods.clear();
+    methods.addAll(newMethods);
   }
 
-  public <T extends ProgramMethodSet> T build(
-      AppView<AppInfoWithLiveness> appView, IntFunction<T> factory) {
+  public T build(AppView<AppInfoWithLiveness> appView) {
+    return build(appView, null);
+  }
+
+  public T build(AppView<AppInfoWithLiveness> appView, GraphLense applied) {
     T result = factory.apply(methods.size());
     for (DexMethod oldMethod : methods) {
-      DexMethod method = appView.graphLense().getRenamedMethodSignature(oldMethod);
+      DexMethod method = appView.graphLense().getRenamedMethodSignature(oldMethod, applied);
       DexProgramClass holder = appView.definitionForHolder(method).asProgramClass();
       result.createAndAdd(holder, holder.lookupMethod(method));
     }
diff --git a/src/test/java/com/android/tools/r8/AndroidAppDumpsTest.java b/src/test/java/com/android/tools/r8/AndroidAppDumpsTest.java
index 04c4bf2..441be7c 100644
--- a/src/test/java/com/android/tools/r8/AndroidAppDumpsTest.java
+++ b/src/test/java/com/android/tools/r8/AndroidAppDumpsTest.java
@@ -13,7 +13,7 @@
 import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.Box;
 import com.android.tools.r8.utils.DescriptorUtils;
-import com.android.tools.r8.utils.Reporter;
+import com.android.tools.r8.utils.InternalOptions;
 import java.io.ByteArrayInputStream;
 import java.io.IOException;
 import java.io.InputStream;
@@ -41,7 +41,7 @@
 
   @Test
   public void test() throws Exception {
-    Reporter reporter = new Reporter();
+    InternalOptions options = new InternalOptions();
 
     String dataResourceName = "my-resource.bin";
     byte[] dataResourceData = new byte[] {1, 2, 3};
@@ -50,7 +50,7 @@
         testForD8().addProgramClasses(B.class).setMinApi(AndroidApiLevel.B).compile().writeToZip();
 
     AndroidApp appIn =
-        AndroidApp.builder(reporter)
+        AndroidApp.builder(options.reporter)
             .addClassProgramData(ToolHelper.getClassAsBytes(A.class), origin("A"))
             .addClassProgramData(ToolHelper.getClassAsBytes(A.class), origin("A"))
             .addClassProgramData(ToolHelper.getClassAsBytes(A.class), origin("A"))
@@ -67,9 +67,9 @@
             .build();
 
     Path dumpFile = temp.newFolder().toPath().resolve("dump.zip");
-    appIn.dump(dumpFile, null, reporter);
+    appIn.dump(dumpFile, options);
 
-    AndroidApp appOut = AndroidApp.builder(reporter).addDump(dumpFile).build();
+    AndroidApp appOut = AndroidApp.builder(options.reporter).addDump(dumpFile).build();
     assertEquals(1, appOut.getClassProgramResourcesForTesting().size());
     assertEquals(
         DescriptorUtils.javaTypeToDescriptor(A.class.getTypeName()),
diff --git a/src/test/java/com/android/tools/r8/CompileWithJdkClassFileProviderTest.java b/src/test/java/com/android/tools/r8/CompileWithJdkClassFileProviderTest.java
index ce6cbcc..cbc5c01 100644
--- a/src/test/java/com/android/tools/r8/CompileWithJdkClassFileProviderTest.java
+++ b/src/test/java/com/android/tools/r8/CompileWithJdkClassFileProviderTest.java
@@ -119,7 +119,9 @@
                     // Art.
                     containsString(
                         "java.lang.NoClassDefFoundError: "
-                            + "Failed resolution of: Ljava/util/concurrent/Flow$Subscriber;")));
+                            + "Failed resolution of: Ljava/util/concurrent/Flow$Subscriber;"),
+                    // Art 10+.
+                    containsString("java.lang.ClassNotFoundException: MySubscriber")));
       } else {
         if (parameters.getRuntime().asCf().getVm() == CfVm.JDK8) {
           // java.util.concurrent.Flow$Subscriber not present in JDK8.
diff --git a/src/test/java/com/android/tools/r8/D8CommandTest.java b/src/test/java/com/android/tools/r8/D8CommandTest.java
index 8d43d07..ce051a5 100644
--- a/src/test/java/com/android/tools/r8/D8CommandTest.java
+++ b/src/test/java/com/android/tools/r8/D8CommandTest.java
@@ -131,12 +131,13 @@
     Path working = temp.getRoot().toPath();
     Path flags = working.resolve("flags.txt").toAbsolutePath();
     assertNotEquals(0, ToolHelper.forkR8(working, "@flags.txt").exitCode);
-    DiagnosticsChecker.checkErrorsContains("File not found", handler ->
-        D8.run(
-            D8Command.parse(
-                new String[] { "@" + flags.toString() },
-                EmbeddedOrigin.INSTANCE,
-                handler).build()));
+    DiagnosticsChecker.checkErrorsContains(
+        "NoSuchFileException",
+        handler ->
+            D8.run(
+                D8Command.parse(
+                        new String[] {"@" + flags.toString()}, EmbeddedOrigin.INSTANCE, handler)
+                    .build()));
   }
 
   @Test(expected = CompilationFailedException.class)
diff --git a/src/test/java/com/android/tools/r8/DiagnosticsMatcher.java b/src/test/java/com/android/tools/r8/DiagnosticsMatcher.java
new file mode 100644
index 0000000..5f6b792
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/DiagnosticsMatcher.java
@@ -0,0 +1,85 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8;
+
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.utils.ExceptionDiagnostic;
+import org.hamcrest.Description;
+import org.hamcrest.Matcher;
+import org.hamcrest.TypeSafeMatcher;
+
+public abstract class DiagnosticsMatcher extends TypeSafeMatcher<Diagnostic> {
+
+  public static Matcher<Diagnostic> diagnosticMessage(Matcher<String> messageMatcher) {
+    return new DiagnosticsMatcher() {
+      @Override
+      protected boolean eval(Diagnostic diagnostic) {
+        return messageMatcher.matches(diagnostic.getDiagnosticMessage());
+      }
+
+      @Override
+      protected void explain(Description description) {
+        description.appendText("message with ");
+        messageMatcher.describeTo(description);
+      }
+    };
+  }
+
+  public static Matcher<Diagnostic> diagnosticType(Class<? extends Diagnostic> type) {
+    return new DiagnosticsMatcher() {
+      @Override
+      protected boolean eval(Diagnostic diagnostic) {
+        return type.isInstance(diagnostic);
+      }
+
+      @Override
+      protected void explain(Description description) {
+        description.appendText("type ").appendText(type.getSimpleName());
+      }
+    };
+  }
+
+  public static Matcher<Diagnostic> diagnosticException(Class<? extends Throwable> exception) {
+    return new DiagnosticsMatcher() {
+      @Override
+      protected boolean eval(Diagnostic diagnostic) {
+        return diagnostic instanceof ExceptionDiagnostic
+            && exception.isInstance(((ExceptionDiagnostic) diagnostic).getCause());
+      }
+
+      @Override
+      protected void explain(Description description) {
+        description.appendText("exception type ").appendText(exception.getSimpleName());
+      }
+    };
+  }
+
+  public static Matcher<Diagnostic> diagnosticOrigin(Origin origin) {
+    return new DiagnosticsMatcher() {
+      @Override
+      protected boolean eval(Diagnostic diagnostic) {
+        return diagnostic.getOrigin().equals(origin);
+      }
+
+      @Override
+      protected void explain(Description description) {
+        description.appendText("orgin ").appendText(origin.toString());
+      }
+    };
+  }
+
+  @Override
+  protected boolean matchesSafely(Diagnostic diagnostic) {
+    return eval(diagnostic);
+  }
+
+  @Override
+  public void describeTo(Description description) {
+    explain(description.appendText("a diagnostic "));
+  }
+
+  protected abstract boolean eval(Diagnostic diagnostic);
+
+  protected abstract void explain(Description description);
+}
diff --git a/src/test/java/com/android/tools/r8/R8CommandTest.java b/src/test/java/com/android/tools/r8/R8CommandTest.java
index 29a9e22..4f8806c 100644
--- a/src/test/java/com/android/tools/r8/R8CommandTest.java
+++ b/src/test/java/com/android/tools/r8/R8CommandTest.java
@@ -150,12 +150,13 @@
     Path working = temp.getRoot().toPath();
     Path flags = working.resolve("flags.txt").toAbsolutePath();
     assertNotEquals(0, ToolHelper.forkR8(working, "@flags.txt").exitCode);
-    DiagnosticsChecker.checkErrorsContains("File not found", handler ->
-        R8.run(
-            R8Command.parse(
-                new String[] { "@" + flags.toString() },
-                EmbeddedOrigin.INSTANCE,
-                handler).build()));
+    DiagnosticsChecker.checkErrorsContains(
+        "NoSuchFileException",
+        handler ->
+            R8.run(
+                R8Command.parse(
+                        new String[] {"@" + flags.toString()}, EmbeddedOrigin.INSTANCE, handler)
+                    .build()));
   }
 
   @Test(expected = CompilationFailedException.class)
diff --git a/src/test/java/com/android/tools/r8/R8RunArtTestsTest.java b/src/test/java/com/android/tools/r8/R8RunArtTestsTest.java
index 8ffe6af..6326718 100644
--- a/src/test/java/com/android/tools/r8/R8RunArtTestsTest.java
+++ b/src/test/java/com/android/tools/r8/R8RunArtTestsTest.java
@@ -169,7 +169,8 @@
   private static final Multimap<String, TestCondition> timeoutOrSkipRunWithArt =
       new ImmutableListMultimap.Builder<String, TestCondition>()
           // Loops on art - timeout.
-          .put("109-suspend-check",
+          .put(
+              "109-suspend-check",
               TestCondition.match(TestCondition.runtimes(DexVm.Version.V5_1_1)))
           // Flaky loops on art.
           .put("129-ThreadGetId", TestCondition.match(TestCondition.runtimes(DexVm.Version.V5_1_1)))
@@ -177,8 +178,13 @@
           // tests on 5.1.1 makes our buildbot cycles time too long.
           .put("800-smali", TestCondition.match(TestCondition.runtimes(DexVm.Version.V5_1_1)))
           // Hangs on dalvik.
-          .put("802-deoptimization",
+          .put(
+              "802-deoptimization",
               TestCondition.match(TestCondition.runtimesUpTo(DexVm.Version.V4_4_4)))
+          // TODO(b/144975341): Triage
+          .put(
+              "134-reg-promotion",
+              TestCondition.match(TestCondition.runtimes(DexVm.Version.V10_0_0)))
           .build();
 
   // Tests that are flaky with the Art version we currently use.
diff --git a/src/test/java/com/android/tools/r8/R8RunExamplesAndroidOTest.java b/src/test/java/com/android/tools/r8/R8RunExamplesAndroidOTest.java
index 9252555..8b19ae3 100644
--- a/src/test/java/com/android/tools/r8/R8RunExamplesAndroidOTest.java
+++ b/src/test/java/com/android/tools/r8/R8RunExamplesAndroidOTest.java
@@ -71,6 +71,10 @@
               Version.V9_0_0,
               // TODO(120402963) Triage.
               ImmutableList.of("invokecustom-with-shrinking", "invokecustom2-with-shrinking"))
+          .put(
+              Version.V10_0_0,
+              // TODO(120402963) Triage.
+              ImmutableList.of("invokecustom-with-shrinking", "invokecustom2-with-shrinking"))
           .put(Version.DEFAULT, ImmutableList.of())
           .build();
 
diff --git a/src/test/java/com/android/tools/r8/R8RunExamplesCommon.java b/src/test/java/com/android/tools/r8/R8RunExamplesCommon.java
index 174c55d..5a90d52 100644
--- a/src/test/java/com/android/tools/r8/R8RunExamplesCommon.java
+++ b/src/test/java/com/android/tools/r8/R8RunExamplesCommon.java
@@ -190,8 +190,8 @@
     }
     Assume.assumeTrue(ToolHelper.artSupported() || ToolHelper.compareAgaintsGoldenFiles());
 
-
     DexVm vm = ToolHelper.getDexVm();
+    Assume.assumeFalse("Triage (b/144966342)", vm.isNewerThan(DexVm.ART_9_0_0_HOST));
 
     if (shouldSkipVm(vm.getVersion())) {
       return;
diff --git a/src/test/java/com/android/tools/r8/R8TestBuilder.java b/src/test/java/com/android/tools/r8/R8TestBuilder.java
index e28b4eb..a109e20 100644
--- a/src/test/java/com/android/tools/r8/R8TestBuilder.java
+++ b/src/test/java/com/android/tools/r8/R8TestBuilder.java
@@ -138,7 +138,7 @@
             graphConsumer);
     switch (allowedDiagnosticMessages) {
       case ALL:
-        compileResult.assertDiagnosticMessageThatMatches(new IsAnything<>());
+        compileResult.assertDiagnosticThatMatches(new IsAnything<>());
         break;
       case ERROR:
         compileResult.assertOnlyErrors();
diff --git a/src/test/java/com/android/tools/r8/RunExamplesAndroidOTest.java b/src/test/java/com/android/tools/r8/RunExamplesAndroidOTest.java
index daab81d..6568713 100644
--- a/src/test/java/com/android/tools/r8/RunExamplesAndroidOTest.java
+++ b/src/test/java/com/android/tools/r8/RunExamplesAndroidOTest.java
@@ -202,7 +202,8 @@
     ImmutableMap.Builder<DexVm.Version, List<String>> builder = ImmutableMap.builder();
     builder
         .put(
-            DexVm.Version.V4_0_4, ImmutableList.of(
+            DexVm.Version.V4_0_4,
+            ImmutableList.of(
                 // API not supported
                 "paramnames",
                 "repeat_annotations_new_api",
@@ -214,10 +215,10 @@
                 "StaticMethodInAndroidJar25",
                 "testMissingInterfaceDesugared2AndroidO",
                 "testCallToMissingSuperInterfaceDesugaredAndroidO",
-                "testMissingSuperDesugaredAndroidO"
-            ))
+                "testMissingSuperDesugaredAndroidO"))
         .put(
-            DexVm.Version.V4_4_4, ImmutableList.of(
+            DexVm.Version.V4_4_4,
+            ImmutableList.of(
                 // API not supported
                 "paramnames",
                 "repeat_annotations_new_api",
@@ -229,10 +230,10 @@
                 "StaticMethodInAndroidJar25",
                 "testMissingInterfaceDesugared2AndroidO",
                 "testCallToMissingSuperInterfaceDesugaredAndroidO",
-                "testMissingSuperDesugaredAndroidO"
-            ))
+                "testMissingSuperDesugaredAndroidO"))
         .put(
-            DexVm.Version.V5_1_1, ImmutableList.of(
+            DexVm.Version.V5_1_1,
+            ImmutableList.of(
                 // API not supported
                 "paramnames",
                 "repeat_annotations_new_api",
@@ -244,10 +245,10 @@
                 "StaticMethodInAndroidJar25",
                 "testMissingInterfaceDesugared2AndroidO",
                 "testCallToMissingSuperInterfaceDesugaredAndroidO",
-                "testMissingSuperDesugaredAndroidO"
-            ))
+                "testMissingSuperDesugaredAndroidO"))
         .put(
-            DexVm.Version.V6_0_1, ImmutableList.of(
+            DexVm.Version.V6_0_1,
+            ImmutableList.of(
                 // API not supported
                 "paramnames",
                 "repeat_annotations_new_api",
@@ -259,10 +260,10 @@
                 "StaticMethodInAndroidJar25",
                 "testMissingInterfaceDesugared2AndroidO",
                 "testCallToMissingSuperInterfaceDesugaredAndroidO",
-                "testMissingSuperDesugaredAndroidO"
-            ))
+                "testMissingSuperDesugaredAndroidO"))
         .put(
-            DexVm.Version.V7_0_0, ImmutableList.of(
+            DexVm.Version.V7_0_0,
+            ImmutableList.of(
                 // API not supported
                 "paramnames",
                 // Dex version not supported
@@ -271,17 +272,18 @@
                 "invokecustom2",
                 "testMissingInterfaceDesugared2AndroidO",
                 "testCallToMissingSuperInterfaceDesugaredAndroidO",
-                "testMissingSuperDesugaredAndroidO"
-            ))
+                "testMissingSuperDesugaredAndroidO"))
         .put(
-            DexVm.Version.V9_0_0, ImmutableList.of(
+            DexVm.Version.V9_0_0,
+            ImmutableList.of(
                 // TODO(120402963): Triage.
-                "invokecustom",
-                "invokecustom2"
-            ))
+                "invokecustom", "invokecustom2"))
         .put(
-            DexVm.Version.DEFAULT, ImmutableList.of()
-        );
+            DexVm.Version.V10_0_0,
+            ImmutableList.of(
+                // TODO(120402963): Triage.
+                "invokecustom", "invokecustom2"))
+        .put(DexVm.Version.DEFAULT, ImmutableList.of());
     failsOn = builder.build();
   }
 
diff --git a/src/test/java/com/android/tools/r8/TestBase.java b/src/test/java/com/android/tools/r8/TestBase.java
index 000ffbc..aeaf585 100644
--- a/src/test/java/com/android/tools/r8/TestBase.java
+++ b/src/test/java/com/android/tools/r8/TestBase.java
@@ -96,7 +96,6 @@
 import java.util.List;
 import java.util.Objects;
 import java.util.Set;
-import java.util.concurrent.ExecutionException;
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Executors;
 import java.util.function.BiFunction;
@@ -580,7 +579,7 @@
       DexApplication dexApplication =
           new ApplicationReader(application, new InternalOptions(), Timing.empty()).read();
       return new AppInfo(dexApplication);
-    } catch (IOException | ExecutionException e) {
+    } catch (IOException e) {
       throw new RuntimeException(e);
     }
   }
@@ -591,7 +590,7 @@
       DexApplication dexApplication =
           new ApplicationReader(application, new InternalOptions(), Timing.empty()).read();
       return new AppInfoWithClassHierarchy(dexApplication);
-    } catch (IOException | ExecutionException e) {
+    } catch (IOException e) {
       throw new RuntimeException(e);
     }
   }
@@ -1384,6 +1383,7 @@
     }
   }
 
+  @Deprecated
   public static Path runtimeJar(Backend backend) {
     if (backend == Backend.DEX) {
       return ToolHelper.getDefaultAndroidJar();
diff --git a/src/test/java/com/android/tools/r8/TestBuilder.java b/src/test/java/com/android/tools/r8/TestBuilder.java
index e9ac10d..21ab2b1 100644
--- a/src/test/java/com/android/tools/r8/TestBuilder.java
+++ b/src/test/java/com/android/tools/r8/TestBuilder.java
@@ -12,7 +12,6 @@
 import java.util.Collection;
 import java.util.List;
 import java.util.concurrent.ExecutionException;
-import java.util.function.Consumer;
 
 public abstract class TestBuilder<RR extends TestRunResult, T extends TestBuilder<RR, T>> {
 
@@ -39,10 +38,21 @@
     return self();
   }
 
-  public T ifTrue(boolean value, Consumer<T> consumer) {
+  public T applyIf(boolean value, ThrowableConsumer<T> consumer) {
     T self = self();
     if (value) {
-      consumer.accept(self);
+      consumer.acceptWithRuntimeException(self);
+    }
+    return self;
+  }
+
+  public T applyIf(
+      boolean value, ThrowableConsumer<T> trueConsumer, ThrowableConsumer<T> falseConsumer) {
+    T self = self();
+    if (value) {
+      trueConsumer.acceptWithRuntimeException(self);
+    } else {
+      falseConsumer.acceptWithRuntimeException(self);
     }
     return self;
   }
diff --git a/src/test/java/com/android/tools/r8/TestCompileResult.java b/src/test/java/com/android/tools/r8/TestCompileResult.java
index 7f777ed..39356e1 100644
--- a/src/test/java/com/android/tools/r8/TestCompileResult.java
+++ b/src/test/java/com/android/tools/r8/TestCompileResult.java
@@ -3,6 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8;
 
+import static com.android.tools.r8.DiagnosticsMatcher.diagnosticMessage;
 import static com.android.tools.r8.TestBase.Backend.DEX;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
 import static org.hamcrest.CoreMatchers.not;
@@ -288,13 +289,13 @@
     return self();
   }
 
-  public CR assertDiagnosticMessageThatMatches(Matcher<String> matcher) {
-    getDiagnosticMessages().assertDiagnosticMessageThatMatches(matcher);
+  public CR assertDiagnosticThatMatches(Matcher<Diagnostic> matcher) {
+    getDiagnosticMessages().assertDiagnosticThatMatches(matcher);
     return self();
   }
 
   public CR assertInfoMessageThatMatches(Matcher<String> matcher) {
-    getDiagnosticMessages().assertInfoMessageThatMatches(matcher);
+    getDiagnosticMessages().assertInfoThatMatches(diagnosticMessage(matcher));
     return self();
   }
 
@@ -303,7 +304,7 @@
   }
 
   public CR assertNoInfoMessageThatMatches(Matcher<String> matcher) {
-    getDiagnosticMessages().assertNoInfoMessageThatMatches(matcher);
+    getDiagnosticMessages().assertNoInfosMatch(diagnosticMessage(matcher));
     return self();
   }
 
@@ -328,7 +329,7 @@
   }
 
   public CR assertAllWarningMessagesMatch(Matcher<String> matcher) {
-    getDiagnosticMessages().assertNoWarningMessageThatMatches(not(matcher));
+    getDiagnosticMessages().assertAllWarningsMatch(diagnosticMessage(matcher));
     return self();
   }
 
@@ -338,7 +339,7 @@
   }
 
   public CR assertNoWarningMessageThatMatches(Matcher<String> matcher) {
-    getDiagnosticMessages().assertNoWarningMessageThatMatches(matcher);
+    getDiagnosticMessages().assertNoWarningsMatch(diagnosticMessage(matcher));
     return self();
   }
 
@@ -352,11 +353,6 @@
     return self();
   }
 
-  public CR assertNoErrorMessageThatMatches(Matcher<String> matcher) {
-    getDiagnosticMessages().assertNoErrorMessageThatMatches(matcher);
-    return self();
-  }
-
   public CR assertNoStdout() {
     assertEquals("", getStdout());
     return self();
diff --git a/src/test/java/com/android/tools/r8/TestCompilerBuilder.java b/src/test/java/com/android/tools/r8/TestCompilerBuilder.java
index 78dee67..be6ffbb 100644
--- a/src/test/java/com/android/tools/r8/TestCompilerBuilder.java
+++ b/src/test/java/com/android/tools/r8/TestCompilerBuilder.java
@@ -146,8 +146,13 @@
     }
   }
 
-  public CR compileWithExpectedDiagnostics(
-      Consumer<TestDiagnosticMessages> diagnosticsConsumer) throws CompilationFailedException {
+  @FunctionalInterface
+  public interface DiagnosticsConsumer {
+    void accept(TestDiagnosticMessages diagnostics);
+  }
+
+  public CR compileWithExpectedDiagnostics(DiagnosticsConsumer diagnosticsConsumer)
+      throws CompilationFailedException {
     TestDiagnosticMessages diagnosticsHandler = getState().getDiagnosticsMessages();
     try {
       CR result = compile();
diff --git a/src/test/java/com/android/tools/r8/TestDiagnosticMessages.java b/src/test/java/com/android/tools/r8/TestDiagnosticMessages.java
index 0dd7e67..0cab77c 100644
--- a/src/test/java/com/android/tools/r8/TestDiagnosticMessages.java
+++ b/src/test/java/com/android/tools/r8/TestDiagnosticMessages.java
@@ -4,6 +4,9 @@
 
 package com.android.tools.r8;
 
+import static com.android.tools.r8.DiagnosticsMatcher.diagnosticMessage;
+import static org.hamcrest.CoreMatchers.not;
+
 import java.util.List;
 import org.hamcrest.Matcher;
 
@@ -29,21 +32,61 @@
 
   TestDiagnosticMessages assertErrorsCount(int count);
 
-  TestDiagnosticMessages assertDiagnosticMessageThatMatches(Matcher<String> matcher);
+  // Match exact.
 
-  TestDiagnosticMessages assertInfoMessageThatMatches(Matcher<String> matcher);
+  TestDiagnosticMessages assertDiagnosticsMatch(Matcher<Diagnostic>... matchers);
 
-  TestDiagnosticMessages assertAllInfoMessagesMatch(Matcher<String> matcher);
+  TestDiagnosticMessages assertInfosMatch(Matcher<Diagnostic>... matchers);
 
-  TestDiagnosticMessages assertNoInfoMessageThatMatches(Matcher<String> matcher);
+  TestDiagnosticMessages assertWarningsMatch(Matcher<Diagnostic>... matchers);
 
-  TestDiagnosticMessages assertWarningMessageThatMatches(Matcher<String> matcher);
+  TestDiagnosticMessages assertErrorsMatch(Matcher<Diagnostic>... matchers);
 
-  TestDiagnosticMessages assertAllWarningMessagesMatch(Matcher<String> matcher);
+  // Match one.
 
-  TestDiagnosticMessages assertNoWarningMessageThatMatches(Matcher<String> matcher);
+  TestDiagnosticMessages assertDiagnosticThatMatches(Matcher<Diagnostic> matcher);
 
-  TestDiagnosticMessages assertErrorMessageThatMatches(Matcher<String> matcher);
+  TestDiagnosticMessages assertInfoThatMatches(Matcher<Diagnostic> matcher);
 
-  TestDiagnosticMessages assertNoErrorMessageThatMatches(Matcher<String> matcher);
+  TestDiagnosticMessages assertWarningThatMatches(Matcher<Diagnostic> matcher);
+
+  TestDiagnosticMessages assertErrorThatMatches(Matcher<Diagnostic> matcher);
+
+  // Consider removing this helper.
+  default TestDiagnosticMessages assertWarningMessageThatMatches(Matcher<String> matcher) {
+    return assertWarningThatMatches(diagnosticMessage(matcher));
+  }
+
+  // Consider removing this helper.
+  default TestDiagnosticMessages assertErrorMessageThatMatches(Matcher<String> matcher) {
+    return assertErrorThatMatches(diagnosticMessage(matcher));
+  }
+
+  // Match all.
+
+  TestDiagnosticMessages assertAllDiagnosticsMatch(Matcher<Diagnostic> matcher);
+
+  TestDiagnosticMessages assertAllInfosMatch(Matcher<Diagnostic> matcher);
+
+  TestDiagnosticMessages assertAllWarningsMatch(Matcher<Diagnostic> matcher);
+
+  TestDiagnosticMessages assertAllErrorsMatch(Matcher<Diagnostic> matcher);
+
+  // Match none.
+
+  default TestDiagnosticMessages assertNoDiagnosticsMatch(Matcher<Diagnostic> matcher) {
+    return assertAllDiagnosticsMatch(not(matcher));
+  }
+
+  default TestDiagnosticMessages assertNoInfosMatch(Matcher<Diagnostic> matcher) {
+    return assertAllInfosMatch(not(matcher));
+  }
+
+  default TestDiagnosticMessages assertNoWarningsMatch(Matcher<Diagnostic> matcher) {
+    return assertAllWarningsMatch(not(matcher));
+  }
+
+  default TestDiagnosticMessages assertNoErrorsMatch(Matcher<Diagnostic> matcher) {
+    return assertAllErrorsMatch(not(matcher));
+  }
 }
diff --git a/src/test/java/com/android/tools/r8/TestDiagnosticMessagesImpl.java b/src/test/java/com/android/tools/r8/TestDiagnosticMessagesImpl.java
index eba971d..2635732 100644
--- a/src/test/java/com/android/tools/r8/TestDiagnosticMessagesImpl.java
+++ b/src/test/java/com/android/tools/r8/TestDiagnosticMessagesImpl.java
@@ -4,7 +4,7 @@
 
 package com.android.tools.r8;
 
-import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.MatcherAssert.assertThat;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.fail;
@@ -12,7 +12,10 @@
 import com.android.tools.r8.utils.ListUtils;
 import com.google.common.collect.Iterables;
 import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashSet;
 import java.util.List;
+import java.util.Set;
 import org.hamcrest.Matcher;
 
 public class TestDiagnosticMessagesImpl implements DiagnosticsHandler, TestDiagnosticMessages {
@@ -137,19 +140,26 @@
     return this;
   }
 
-  private TestDiagnosticMessages assertMessageThatMatches(
-      Iterable<Diagnostic> diagnostics, String tag, Matcher<String> matcher) {
+  private TestDiagnosticMessages assertAllDiagnosticsMatches(
+      Iterable<Diagnostic> diagnostics, String tag, Matcher<Diagnostic> matcher) {
+    for (Diagnostic diagnostic : diagnostics) {
+      assertThat(diagnostic, matcher);
+    }
+    return this;
+  }
+
+  private TestDiagnosticMessages assertDiagnosticThatMatches(
+      Iterable<Diagnostic> diagnostics, String tag, Matcher<Diagnostic> matcher) {
     int numberOfDiagnostics = 0;
     for (Diagnostic diagnostic : diagnostics) {
-      if (matcher.matches(diagnostic.getDiagnosticMessage())) {
+      if (matcher.matches(diagnostic)) {
         return this;
       }
       numberOfDiagnostics++;
     }
-    assertNotEquals(0, numberOfDiagnostics);
     StringBuilder builder = new StringBuilder("No " + tag + " matches " + matcher.toString());
     builder.append(System.lineSeparator());
-    if (getWarnings().size() == 0) {
+    if (numberOfDiagnostics == 0) {
       builder.append("There were no " + tag + "s.");
     } else {
       builder.append("There were " + numberOfDiagnostics + " " + tag + "s:");
@@ -163,60 +173,132 @@
     return this;
   }
 
-  private TestDiagnosticMessages assertNoMessageThatMatches(
-      List<Diagnostic> diagnostics, String tag, Matcher<String> matcher) {
-    for (int i = 0; i < diagnostics.size(); i++) {
-      String message = diagnostics.get(i).getDiagnosticMessage();
-      if (matcher.matches(message)) {
-        fail("The " + tag + ": \"" + message + "\" + matches " + matcher + ".");
+  private static void assertDiagnosticsMatch(
+      Iterable<Diagnostic> diagnostics, String tag, List<Matcher<Diagnostic>> matchers) {
+    // Match is unordered, but we make no attempts to find the maximum match.
+    int diagnosticsCount = 0;
+    Set<Diagnostic> matchedDiagnostics = new HashSet<>();
+    Set<Matcher<Diagnostic>> matchedMatchers = new HashSet<>();
+    for (Diagnostic diagnostic : diagnostics) {
+      diagnosticsCount++;
+      for (Matcher<Diagnostic> matcher : matchers) {
+        if (matchedMatchers.contains(matcher)) {
+          continue;
+        }
+        if (matcher.matches(diagnostic)) {
+          matchedDiagnostics.add(diagnostic);
+          matchedMatchers.add(matcher);
+          break;
+        }
       }
     }
+    StringBuilder builder = new StringBuilder();
+    boolean failedMatching = false;
+    if (matchedDiagnostics.size() < diagnosticsCount) {
+      failedMatching = true;
+      builder.append("\nUnmatched diagnostics:");
+      for (Diagnostic diagnostic : diagnostics) {
+        if (!matchedDiagnostics.contains(diagnostic)) {
+          builder
+              .append("\n  - ")
+              .append(diagnostics.getClass().getName())
+              .append(diagnostic.getDiagnosticMessage());
+        }
+      }
+    }
+    if (matchedMatchers.size() < matchers.size()) {
+      failedMatching = true;
+      builder.append("\nUnmatched matchers:");
+      for (Matcher<Diagnostic> matcher : matchers) {
+        if (!matchedMatchers.contains(matcher)) {
+          builder.append("\n  - ").append(matcher);
+        }
+      }
+    }
+    if (failedMatching) {
+      builder.append("\nAll diagnostics:");
+      for (Diagnostic diagnostic : diagnostics) {
+        builder
+            .append("\n  - ")
+            .append(diagnostics.getClass().getName())
+            .append(diagnostic.getDiagnosticMessage());
+      }
+      builder.append("\nAll matchers:");
+      for (Matcher<Diagnostic> matcher : matchers) {
+        builder.append("\n  - ").append(matcher);
+      }
+      fail(builder.toString());
+    }
+    // Double check consistency.
+    assertEquals(matchers.size(), diagnosticsCount);
+    assertEquals(diagnosticsCount, matchedDiagnostics.size());
+    assertEquals(diagnosticsCount, matchedMatchers.size());
+  }
+
+  @Override
+  public TestDiagnosticMessages assertDiagnosticsMatch(Matcher<Diagnostic>... matchers) {
+    assertDiagnosticsMatch(getAllDiagnostics(), "diagnostics", Arrays.asList(matchers));
     return this;
   }
 
   @Override
-  public TestDiagnosticMessages assertDiagnosticMessageThatMatches(Matcher<String> matcher) {
-    return assertMessageThatMatches(
-        Iterables.concat(getInfos(), getWarnings(), getErrors()), "diagnostic message", matcher);
+  public TestDiagnosticMessages assertInfosMatch(Matcher<Diagnostic>... matchers) {
+    assertDiagnosticsMatch(getInfos(), "infos", Arrays.asList(matchers));
+    return this;
   }
 
   @Override
-  public TestDiagnosticMessages assertInfoMessageThatMatches(Matcher<String> matcher) {
-    return assertMessageThatMatches(getInfos(), "info", matcher);
+  public TestDiagnosticMessages assertWarningsMatch(Matcher<Diagnostic>... matchers) {
+    assertDiagnosticsMatch(getWarnings(), "warnings", Arrays.asList(matchers));
+    return this;
   }
 
   @Override
-  public TestDiagnosticMessages assertAllInfoMessagesMatch(Matcher<String> matcher) {
-    return assertNoInfoMessageThatMatches(not(matcher));
+  public TestDiagnosticMessages assertErrorsMatch(Matcher<Diagnostic>... matchers) {
+    assertDiagnosticsMatch(getErrors(), "errors", Arrays.asList(matchers));
+    return this;
   }
 
   @Override
-  public TestDiagnosticMessages assertNoInfoMessageThatMatches(Matcher<String> matcher) {
-    return assertNoMessageThatMatches(getInfos(), "info", matcher);
+  public TestDiagnosticMessages assertDiagnosticThatMatches(Matcher<Diagnostic> matcher) {
+    return assertDiagnosticThatMatches(getAllDiagnostics(), "diagnostic message", matcher);
+  }
+
+  private Iterable<Diagnostic> getAllDiagnostics() {
+    return Iterables.concat(getInfos(), getWarnings(), getErrors());
   }
 
   @Override
-  public TestDiagnosticMessages assertWarningMessageThatMatches(Matcher<String> matcher) {
-    return assertMessageThatMatches(getWarnings(), "warning", matcher);
+  public TestDiagnosticMessages assertInfoThatMatches(Matcher<Diagnostic> matcher) {
+    return assertDiagnosticThatMatches(getInfos(), "info", matcher);
+  }
+
+  public TestDiagnosticMessages assertWarningThatMatches(Matcher<Diagnostic> matcher) {
+    return assertDiagnosticThatMatches(getWarnings(), "warning", matcher);
   }
 
   @Override
-  public TestDiagnosticMessages assertAllWarningMessagesMatch(Matcher<String> matcher) {
-    return assertNoWarningMessageThatMatches(not(matcher));
+  public TestDiagnosticMessages assertErrorThatMatches(Matcher<Diagnostic> matcher) {
+    return assertDiagnosticThatMatches(getErrors(), "error", matcher);
   }
 
   @Override
-  public TestDiagnosticMessages assertNoWarningMessageThatMatches(Matcher<String> matcher) {
-    return assertNoMessageThatMatches(getWarnings(), "warning", matcher);
+  public TestDiagnosticMessages assertAllDiagnosticsMatch(Matcher<Diagnostic> matcher) {
+    return assertAllDiagnosticsMatches(getAllDiagnostics(), "diagnostic message", matcher);
   }
 
   @Override
-  public TestDiagnosticMessages assertErrorMessageThatMatches(Matcher<String> matcher) {
-    return assertMessageThatMatches(getErrors(), "error", matcher);
+  public TestDiagnosticMessages assertAllInfosMatch(Matcher<Diagnostic> matcher) {
+    return assertAllDiagnosticsMatches(getInfos(), "info", matcher);
   }
 
   @Override
-  public TestDiagnosticMessages assertNoErrorMessageThatMatches(Matcher<String> matcher) {
-    return assertNoMessageThatMatches(getErrors(), "error", matcher);
+  public TestDiagnosticMessages assertAllWarningsMatch(Matcher<Diagnostic> matcher) {
+    return assertAllDiagnosticsMatches(getWarnings(), "warning", matcher);
+  }
+
+  @Override
+  public TestDiagnosticMessages assertAllErrorsMatch(Matcher<Diagnostic> matcher) {
+    return assertAllDiagnosticsMatches(getErrors(), "error", matcher);
   }
 }
diff --git a/src/test/java/com/android/tools/r8/TestShrinkerBuilder.java b/src/test/java/com/android/tools/r8/TestShrinkerBuilder.java
index fab7d11..b262e4e 100644
--- a/src/test/java/com/android/tools/r8/TestShrinkerBuilder.java
+++ b/src/test/java/com/android/tools/r8/TestShrinkerBuilder.java
@@ -70,6 +70,10 @@
     return addKeepRules(Arrays.asList(rules));
   }
 
+  public T addKeepKotlinMetadata() {
+    return addKeepRules("-keep class kotlin.Metadata { *; }");
+  }
+
   public T addKeepAllClassesRule() {
     return addKeepRules("-keep class ** { *; }");
   }
diff --git a/src/test/java/com/android/tools/r8/accessrelaxation/PrivateKeptMembersPublicizerTest.java b/src/test/java/com/android/tools/r8/accessrelaxation/PrivateKeptMembersPublicizerTest.java
index 940479a..61c1d77 100644
--- a/src/test/java/com/android/tools/r8/accessrelaxation/PrivateKeptMembersPublicizerTest.java
+++ b/src/test/java/com/android/tools/r8/accessrelaxation/PrivateKeptMembersPublicizerTest.java
@@ -5,14 +5,16 @@
 package com.android.tools.r8.accessrelaxation;
 
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPrivate;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPublic;
 import static org.hamcrest.MatcherAssert.assertThat;
-import static org.junit.Assert.assertTrue;
 
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import java.util.List;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
@@ -22,21 +24,41 @@
 public class PrivateKeptMembersPublicizerTest extends TestBase {
 
   private final TestParameters parameters;
+  private final boolean withKeepAllowAccessModification;
+  private final boolean withPrecondition;
 
-  @Parameters(name = "{0}")
-  public static TestParametersCollection data() {
-    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  @Parameters(name = "{0}, with keep allow access modification: {1}, with precondition: {2}")
+  public static List<Object[]> data() {
+    return buildParameters(
+        getTestParameters().withAllRuntimesAndApiLevels().build(),
+        BooleanUtils.values(),
+        BooleanUtils.values());
   }
 
-  public PrivateKeptMembersPublicizerTest(TestParameters parameters) {
+  public PrivateKeptMembersPublicizerTest(
+      TestParameters parameters,
+      boolean withKeepAllowAccessModification,
+      boolean withPrecondition) {
     this.parameters = parameters;
+    this.withKeepAllowAccessModification = withKeepAllowAccessModification;
+    this.withPrecondition = withPrecondition;
   }
 
   @Test
   public void test() throws Exception {
     testForR8(parameters.getBackend())
         .addInnerClasses(PrivateKeptMembersPublicizerTest.class)
-        .addKeepClassAndMembersRules(TestClass.class)
+        .addKeepMainRule(TestClass.class)
+        .addKeepRules(
+            withPrecondition ? "-if class *" : "",
+            "-keep"
+                + (withKeepAllowAccessModification ? ",allowaccessmodification" : "")
+                + " class "
+                + typeName(TestClass.class)
+                + " {",
+            "  private static java.lang.String greeting;",
+            "  private static void greet(java.lang.String);",
+            "}")
         .allowAccessModification()
         .setMinApi(parameters.getApiLevel())
         .compile()
@@ -48,8 +70,13 @@
   private void inspect(CodeInspector inspector) {
     ClassSubject classSubject = inspector.clazz(TestClass.class);
     assertThat(classSubject, isPresent());
-    assertTrue(classSubject.uniqueFieldWithName("greeting").isPrivate());
-    assertTrue(classSubject.uniqueMethodWithName("greet").isPrivate());
+    if (withKeepAllowAccessModification) {
+      assertThat(classSubject.uniqueFieldWithName("greeting"), isPublic());
+      assertThat(classSubject.uniqueMethodWithName("greet"), isPublic());
+    } else {
+      assertThat(classSubject.uniqueFieldWithName("greeting"), isPrivate());
+      assertThat(classSubject.uniqueMethodWithName("greet"), isPrivate());
+    }
   }
 
   static class TestClass {
diff --git a/src/test/java/com/android/tools/r8/annotations/SourceDebugExtensionTest.java b/src/test/java/com/android/tools/r8/annotations/SourceDebugExtensionTest.java
index 56cde8f..524cbfa 100644
--- a/src/test/java/com/android/tools/r8/annotations/SourceDebugExtensionTest.java
+++ b/src/test/java/com/android/tools/r8/annotations/SourceDebugExtensionTest.java
@@ -7,7 +7,6 @@
 import static com.android.tools.r8.KotlinCompilerTool.KOTLINC;
 import static com.android.tools.r8.ToolHelper.getFilesInTestFolderRelativeToClass;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
-import static org.hamcrest.CoreMatchers.containsString;
 import static org.hamcrest.MatcherAssert.assertThat;
 
 import com.android.tools.r8.CompilationFailedException;
@@ -17,10 +16,10 @@
 import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.TestRuntime;
 import com.android.tools.r8.TestRuntime.CfRuntime;
+import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.ToolHelper.KotlinTargetVersion;
 import com.android.tools.r8.retrace.KotlinInlineFunctionRetraceTest;
 import com.android.tools.r8.shaking.ProguardKeepAttributes;
-import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.codeinspector.AnnotationSubject;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
@@ -59,19 +58,13 @@
     CodeInspector kotlinInspector = new CodeInspector(kotlinSources);
     inspectSourceDebugExtension(kotlinInspector);
     testForR8(parameters.getBackend())
+        .addClasspathFiles(ToolHelper.getKotlinStdlibJar())
         .addProgramFiles(kotlinSources)
         .addKeepAttributes(ProguardKeepAttributes.SOURCE_DEBUG_EXTENSION)
         .addKeepAllClassesRule()
         .setMode(CompilationMode.RELEASE)
         .setMinApi(parameters.getApiLevel())
-        .allowDiagnosticWarningMessages(
-            parameters.isDexRuntime()
-                && parameters.getApiLevel().isLessThanOrEqualTo(AndroidApiLevel.M))
         .compile()
-        .assertAllWarningMessagesMatch(
-            containsString(
-                "Type `kotlin.jvm.internal.Intrinsics` was not found, it is required for default"
-                    + " or static interface methods"))
         .inspect(this::inspectSourceDebugExtension);
   }
 
diff --git a/src/test/java/com/android/tools/r8/bridgeremoval/B77836766.java b/src/test/java/com/android/tools/r8/bridgeremoval/B77836766.java
index 6a0cf4d..76ebd58 100644
--- a/src/test/java/com/android/tools/r8/bridgeremoval/B77836766.java
+++ b/src/test/java/com/android/tools/r8/bridgeremoval/B77836766.java
@@ -453,7 +453,7 @@
     // Callees are invoked with a simple constant, e.g., "Bar". Propagating it into the callees
     // bothers what the tests want to check, such as exact instructions in the body that include
     // invocation kinds, like virtual call to a bridge.
-    options.enablePropagationOfConstantsAtCallSites = false;
+    assert !options.callSiteOptimizationOptions().isConstantPropagationEnabled();
     // Disable inlining to avoid the (short) tested method from being inlined and then removed.
     options.enableInlining = false;
   }
diff --git a/src/test/java/com/android/tools/r8/bridgeremoval/hoisting/BridgesAndNonBridgesHoistingTest.java b/src/test/java/com/android/tools/r8/bridgeremoval/hoisting/BridgesAndNonBridgesHoistingTest.java
new file mode 100644
index 0000000..c6a1c88
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/bridgeremoval/hoisting/BridgesAndNonBridgesHoistingTest.java
@@ -0,0 +1,35 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.bridgeremoval.hoisting;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class BridgesAndNonBridgesHoistingTest extends TestBase {
+
+  private final TestParameters parameters;
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public BridgesAndNonBridgesHoistingTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void test() throws Exception {}
+
+  static class TestClass {
+
+    public static void main(String[] args) {}
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/bridgeremoval/hoisting/NonReboundBridgeHoistingTest.java b/src/test/java/com/android/tools/r8/bridgeremoval/hoisting/NonReboundBridgeHoistingTest.java
index d6eb6cd..d93514a 100644
--- a/src/test/java/com/android/tools/r8/bridgeremoval/hoisting/NonReboundBridgeHoistingTest.java
+++ b/src/test/java/com/android/tools/r8/bridgeremoval/hoisting/NonReboundBridgeHoistingTest.java
@@ -56,11 +56,11 @@
     ClassSubject aClassSubject = inspector.clazz(NonReboundBridgeHoistingTestClasses.getClassA());
     assertThat(aClassSubject, isPresent());
     assertThat(aClassSubject.uniqueMethodWithName("m"), isPresent());
-    assertThat(aClassSubject.uniqueMethodWithName("bridge"), not(isPresent()));
+    assertThat(aClassSubject.uniqueMethodWithName("bridge"), isPresent());
 
     ClassSubject bClassSubject = inspector.clazz(NonReboundBridgeHoistingTestClasses.B.class);
     assertThat(bClassSubject, isPresent());
-    assertThat(bClassSubject.uniqueMethodWithName("bridge"), isPresent());
+    assertThat(bClassSubject.uniqueMethodWithName("bridge"), not(isPresent()));
 
     ClassSubject cClassSubject = inspector.clazz(C.class);
     assertThat(cClassSubject, isPresent());
@@ -79,8 +79,8 @@
 
     // The invoke instruction in this bridge cannot be rewritten to target A.m(), since A is not
     // accessible in this context. It therefore points to B.m(), where there is no definition of the
-    // method. As a result of this, we cannot move this bridge to A without also rewriting the
-    // signature referenced from the invoke instruction.
+    // method. When the bridge is hoisted to B.m(), the invoke-virtual instruction can be rewritten
+    // to target A.m(). This allows hoisting the bridge further from B.m() to A.m().
     @NeverInline
     public /*bridge*/ void bridge() {
       m();
diff --git a/src/test/java/com/android/tools/r8/bridgeremoval/hoisting/PositiveBridgeHoistingTest.java b/src/test/java/com/android/tools/r8/bridgeremoval/hoisting/PositiveBridgeHoistingTest.java
index 89437f1..d764895 100644
--- a/src/test/java/com/android/tools/r8/bridgeremoval/hoisting/PositiveBridgeHoistingTest.java
+++ b/src/test/java/com/android/tools/r8/bridgeremoval/hoisting/PositiveBridgeHoistingTest.java
@@ -35,7 +35,7 @@
   @Test
   public void test() throws Exception {
     testForR8(parameters.getBackend())
-        .addProgramClasses(TestClass.class, A.class, B3.class)
+        .addProgramClasses(TestClass.class, A.class, B3.class, B4.class)
         .addProgramClassFileData(
             transformer(B1.class)
                 .setBridge(B1.class.getDeclaredMethod("superBridge", Object.class))
@@ -44,6 +44,10 @@
             transformer(B2.class)
                 .setBridge(B2.class.getDeclaredMethod("superBridge", Object.class))
                 .setBridge(B2.class.getDeclaredMethod("virtualBridge", Object.class))
+                .transform(),
+            transformer(B5.class)
+                .setBridge(B5.class.getDeclaredMethod("superBridge", Object.class))
+                .setBridge(B5.class.getDeclaredMethod("virtualBridge", Object.class))
                 .transform())
         .addKeepMainRule(TestClass.class)
         .enableInliningAnnotations()
@@ -71,16 +75,30 @@
     assertThat(b2ClassSubject, isPresent());
     assertThat(b2ClassSubject.uniqueMethodWithName("superBridge"), not(isPresent()));
     assertThat(b2ClassSubject.uniqueMethodWithName("virtualBridge"), not(isPresent()));
+
+    ClassSubject b4ClassSubject = inspector.clazz(B4.class);
+    assertThat(b4ClassSubject, isPresent());
+    assertThat(b4ClassSubject.uniqueMethodWithName("superBridge"), isPresent());
+    assertThat(b4ClassSubject.uniqueMethodWithName("virtualBridge"), isPresent());
+
+    ClassSubject b5ClassSubject = inspector.clazz(B5.class);
+    assertThat(b5ClassSubject, isPresent());
+    assertThat(b5ClassSubject.uniqueMethodWithName("superBridge"), isPresent());
+    assertThat(b5ClassSubject.uniqueMethodWithName("virtualBridge"), isPresent());
   }
 
   static class TestClass {
 
     public static void main(String[] args) {
-      System.out.print(new B1().superBridge("Hello"));
-      System.out.print(new B1().virtualBridge(" "));
-      System.out.print(new B2().superBridge("world"));
-      System.out.print(new B2().virtualBridge("!"));
-      System.out.println(new B3().m(""));
+      System.out.print(new B1().superBridge("Hel"));
+      System.out.print(new B1().virtualBridge("lo"));
+      System.out.print(new B2().superBridge(" "));
+      System.out.print(new B2().virtualBridge("w"));
+      System.out.print(new B3().m("o"));
+      System.out.print(new B4().superBridge("r"));
+      System.out.print(new B4().virtualBridge("l"));
+      System.out.print(new B5().superBridge("d"));
+      System.out.println(new B5().virtualBridge("!"));
     }
   }
 
@@ -90,6 +108,11 @@
     public Object m(String arg) {
       return System.currentTimeMillis() >= 0 ? arg : null;
     }
+
+    @NeverInline
+    public Object m2(String arg) {
+      return System.currentTimeMillis() >= 0 ? arg : null;
+    }
   }
 
   @NeverClassInline
@@ -137,4 +160,36 @@
   // but this should never be the case in practice.
   @NeverClassInline
   static class B3 extends A {}
+
+  // The fact that this class declares superBridge() and virtualBridge() should not prevent
+  // us from hoisting other bridges to A.
+  @NeverClassInline
+  static class B4 extends A {
+
+    @NeverInline
+    public String superBridge(Object o) {
+      return System.currentTimeMillis() >= 0 ? ((String) o) : null;
+    }
+
+    @NeverInline
+    public String virtualBridge(Object o) {
+      return System.currentTimeMillis() >= 0 ? ((String) o) : null;
+    }
+  }
+
+  // This class declares the same bridges, but with different (bridge) behavior. They are candidates
+  // for hoisting, but will not be hoisted because it is better to hoist the bridges declared on B1.
+  @NeverClassInline
+  static class B5 extends A {
+
+    @NeverInline
+    public /*bridge*/ String superBridge(Object o) {
+      return (String) super.m2((String) o);
+    }
+
+    @NeverInline
+    public /*bridge*/ String virtualBridge(Object o) {
+      return (String) m2((String) o);
+    }
+  }
 }
diff --git a/src/test/java/com/android/tools/r8/code/PassThroughTest.java b/src/test/java/com/android/tools/r8/code/PassThroughTest.java
index b1a9f2f..673f3a2 100644
--- a/src/test/java/com/android/tools/r8/code/PassThroughTest.java
+++ b/src/test/java/com/android/tools/r8/code/PassThroughTest.java
@@ -10,6 +10,7 @@
 import com.android.tools.r8.CfFrontendExamplesTest;
 import com.android.tools.r8.ClassFileResourceProvider;
 import com.android.tools.r8.DirectoryClassFileProvider;
+import com.android.tools.r8.NeverInline;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestShrinkerBuilder;
@@ -21,6 +22,7 @@
 import java.nio.file.Path;
 import java.util.Arrays;
 import java.util.List;
+import java.util.function.Predicate;
 import java.util.stream.Collectors;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -30,7 +32,7 @@
 @RunWith(Parameterized.class)
 public class PassThroughTest extends TestBase {
 
-  private final String EXPECTED = StringUtils.lines("0", "foo", "0");
+  private final String EXPECTED = StringUtils.lines("0", "foo", "0", "foo", "foo");
 
   private final TestParameters parameters;
   private final boolean keepDebug;
@@ -56,7 +58,8 @@
     // Check that reading the same input is actual matches.
     ClassFileResourceProvider original =
         DirectoryClassFileProvider.fromDirectory(ToolHelper.getClassPathForTests());
-    verifyInstructionsForMainMatchingExpectation(original, true, true);
+    verifyInstructionsForMethodMatchingExpectation(original, "main", true, true);
+    verifyInstructionsForMethodMatchingExpectation(original, "exceptionTest", true, true);
   }
 
   @Test
@@ -64,14 +67,16 @@
     Path outputJar = temp.newFile("output.jar").toPath();
     testForR8(parameters.getBackend())
         .addProgramClasses(Main.class)
-        .addKeepMainRule(Main.class)
-        .ifTrue(keepDebug, TestShrinkerBuilder::addKeepAllAttributes)
+        .addKeepAllClassesRule()
+        .enableInliningAnnotations()
+        .applyIf(keepDebug, TestShrinkerBuilder::addKeepAllAttributes)
         .compile()
         .writeToZip(outputJar)
         .run(parameters.getRuntime(), Main.class)
         .assertSuccessWithOutput(EXPECTED);
-    verifyInstructionsForMainMatchingExpectation(
-        new ArchiveClassFileProvider(outputJar), keepDebug, false);
+    ArchiveClassFileProvider actual = new ArchiveClassFileProvider(outputJar);
+    verifyInstructionsForMethodMatchingExpectation(actual, "main", keepDebug, false);
+    verifyInstructionsForMethodMatchingExpectation(actual, "exceptionTest", keepDebug, false);
   }
 
   @Test
@@ -79,22 +84,27 @@
     Path outputJar = temp.newFile("output.jar").toPath();
     testForR8(parameters.getBackend())
         .addProgramClasses(Main.class)
-        .addKeepMainRule(Main.class)
-        .ifTrue(keepDebug, TestShrinkerBuilder::addKeepAllAttributes)
+        .addKeepAllClassesRule()
+        .enableInliningAnnotations()
+        .applyIf(keepDebug, TestShrinkerBuilder::addKeepAllAttributes)
         .addOptionsModification(
-            internalOptions ->
-                internalOptions.testing.cfByteCodePassThrough =
-                    method -> method.method.name.toString().equals("main"))
+            internalOptions -> {
+              internalOptions.testing.cfByteCodePassThrough =
+                  method -> !method.name.toString().equals("<init>");
+              internalOptions.testing.readInputStackMaps = true;
+            })
         .compile()
         .writeToZip(outputJar)
         .run(parameters.getRuntime(), Main.class)
         .assertSuccessWithOutput(EXPECTED);
-    verifyInstructionsForMainMatchingExpectation(
-        new ArchiveClassFileProvider(outputJar), keepDebug, true);
+    ArchiveClassFileProvider actual = new ArchiveClassFileProvider(outputJar);
+    verifyInstructionsForMethodMatchingExpectation(actual, "main", keepDebug, true);
+    verifyInstructionsForMethodMatchingExpectation(actual, "exceptionTest", keepDebug, true);
   }
 
-  private void verifyInstructionsForMainMatchingExpectation(
-      ClassFileResourceProvider actual, boolean checkDebug, boolean expectation) throws Exception {
+  private void verifyInstructionsForMethodMatchingExpectation(
+      ClassFileResourceProvider actual, String methodName, boolean checkDebug, boolean expectation)
+      throws Exception {
     ClassFileResourceProvider original =
         DirectoryClassFileProvider.fromDirectory(ToolHelper.getClassPathForTests());
     String descriptor = DescriptorUtils.javaTypeToDescriptor(Main.class.getTypeName());
@@ -103,43 +113,60 @@
     if (!Arrays.equals(expectedBytes, actualBytes)) {
       String expectedString = CfFrontendExamplesTest.asmToString(expectedBytes);
       String actualString = CfFrontendExamplesTest.asmToString(actualBytes);
-      verifyInstructionsForMainMatchingExpectation(
-          getMethodInstructions(expectedString),
-          getMethodInstructions(actualString),
+      verifyInstructionsForMethodMatchingExpectation(
+          getMethodInstructions(expectedString, methodName),
+          getMethodInstructions(actualString, methodName),
           checkDebug,
           expectation);
     }
   }
 
-  private String getMethodInstructions(String asm) {
+  private String getMethodInstructions(String asm, String methodName) {
     int methodIndexStart =
         asm.indexOf(
-            "methodVisitor = classWriter.visitMethod(ACC_PUBLIC | ACC_STATIC, \"main\","
-                + " \"([Ljava/lang/String;)V\", null, null);");
-    int methodIndexEnd = asm.indexOf("}", methodIndexStart);
+            "methodVisitor = classWriter.visitMethod(ACC_PUBLIC | ACC_STATIC, \""
+                + methodName
+                + "\",");
+    methodIndexStart = asm.indexOf("methodVisitor.visitCode();", methodIndexStart);
+    int methodIndexEnd = asm.indexOf("methodVisitor.visitEnd();", methodIndexStart);
     return asm.substring(methodIndexStart, methodIndexEnd);
   }
 
-  private void verifyInstructionsForMainMatchingExpectation(
+  private void verifyInstructionsForMethodMatchingExpectation(
       String originalInstructions,
       String actualInstructions,
       boolean checkDebug,
       boolean expectation) {
-    if (!checkDebug) {
-      originalInstructions =
-          StringUtils.splitLines(originalInstructions).stream()
-              .filter(this::isNotDebugInstruction)
-              .map(instr -> instr + "\n")
-              .collect(Collectors.joining());
+    if (checkDebug) {
+      // We may rewrite jump instructions, so filter those out.
+      originalInstructions = filter(originalInstructions, this::isNotLabelOrJumpInstruction);
+      actualInstructions = filter(actualInstructions, this::isNotLabelOrJumpInstruction);
+    } else {
+      originalInstructions = filter(originalInstructions, this::isNotDebugInstructionOrJump);
+      actualInstructions = filter(actualInstructions, this::isNotLabelOrJumpInstruction);
     }
     assertSame(expectation, actualInstructions.equals(originalInstructions));
   }
 
-  private boolean isNotDebugInstruction(String instruction) {
+  private String filter(String instructions, Predicate<String> predicate) {
+    return StringUtils.splitLines(instructions).stream()
+        .filter(predicate)
+        .map(instr -> instr + "\n")
+        .collect(Collectors.joining());
+  }
+
+  private boolean isNotDebugInstructionOrJump(String instruction) {
     return !(instruction.startsWith("methodVisitor.visitLocalVariable")
         || instruction.startsWith("methodVisitor.visitLabel")
         || instruction.startsWith("Label")
-        || instruction.startsWith("methodVisitor.visitLineNumber"));
+        || instruction.startsWith("methodVisitor.visitLineNumber")
+        || instruction.startsWith("methodVisitor.visitJumpInsn"));
+  }
+
+  private boolean isNotLabelOrJumpInstruction(String instruction) {
+    return !(instruction.startsWith("Label")
+        || instruction.startsWith("methodVisitor.visitJumpInsn")
+        || instruction.startsWith("methodVisitor.visitLabel"));
   }
 
   public static class Main {
@@ -155,6 +182,28 @@
       }
       System.out.println(foo);
       System.out.println(j);
+      System.out.println(phiTest(args.length > 0 ? args[0] : null));
+      System.out.println(exceptionTest(args.length > 0 ? args[0] : null));
+    }
+
+    @NeverInline
+    public static String phiTest(String arg) {
+      String result;
+      if (arg == null) {
+        result = "foo";
+      } else {
+        result = "bar";
+      }
+      return result;
+    }
+
+    @NeverInline
+    public static String exceptionTest(String arg) {
+      try {
+        return arg.toLowerCase();
+      } catch (NullPointerException ignored) {
+        return "foo";
+      }
     }
   }
 }
diff --git a/src/test/java/com/android/tools/r8/desugar/DesugarToClassFileB156591935.java b/src/test/java/com/android/tools/r8/desugar/DesugarToClassFileB156591935.java
new file mode 100644
index 0000000..63f1aa6
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/DesugarToClassFileB156591935.java
@@ -0,0 +1,254 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.desugar;
+
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.InstructionSubject;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.objectweb.asm.ClassWriter;
+import org.objectweb.asm.FieldVisitor;
+import org.objectweb.asm.Label;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+
+@RunWith(Parameterized.class)
+public class DesugarToClassFileB156591935 extends TestBase implements Opcodes {
+
+  @Parameterized.Parameters(name = "{0}")
+  public static AndroidApiLevel[] data() {
+    return AndroidApiLevel.values();
+  }
+
+  private final AndroidApiLevel apiLevel;
+
+  public DesugarToClassFileB156591935(AndroidApiLevel apiLevel) {
+    this.apiLevel = apiLevel;
+  }
+
+  private void expectNops(CodeInspector inspector, int numberOfNops) {
+    ClassSubject a = inspector.clazz("A");
+    assertEquals(
+        numberOfNops, a.clinit().streamInstructions().filter(InstructionSubject::isNop).count());
+  }
+
+  @Test
+  public void test() throws Exception {
+    // No nops in the input - see dump below.
+    // TODO(b/156591935): The three nops should be avoided.
+    testForD8(Backend.CF)
+        .addProgramClassFileData(dump())
+        .setMinApi(apiLevel)
+        .compile()
+        .inspect(inspector -> expectNops(inspector, 3));
+  }
+
+  /*
+    Dump of the compiled code for ths class below. The dump has not been modified, but the
+    code needs to have the specific line breaks for javac to insert the line numbers in the
+    way that this test is about. Used a dump to avoid source formatting invalidating the test.
+
+    static class A {
+      // The line break before createA is needed for the expected line info.
+      public static final A A_1 =
+          createA(1, "FIRST");
+      public static final A A_2 =
+          createA(1, "SECOND");
+      public static final A A_3 =
+          createA(1, "THIRD");
+
+      private final int value;
+      private final String name;
+
+      private static A createA(int value, String name) {
+        return new A(value, name);
+      }
+
+      private A(int value, String name) {
+        this.value = value;
+        this.name = name;
+      }
+
+      int getValue() {
+        return value;
+      }
+
+      String getName() {
+        return name;
+      }
+    }
+  */
+  public static byte[] dump() {
+
+    ClassWriter classWriter = new ClassWriter(0);
+    FieldVisitor fieldVisitor;
+    MethodVisitor methodVisitor;
+
+    classWriter.visit(V1_8, ACC_SUPER, "A", null, "java/lang/Object", null);
+
+    classWriter.visitSource("A.java", null);
+
+    {
+      fieldVisitor =
+          classWriter.visitField(ACC_PUBLIC | ACC_FINAL | ACC_STATIC, "A_1", "LA;", null, null);
+      fieldVisitor.visitEnd();
+    }
+    {
+      fieldVisitor =
+          classWriter.visitField(ACC_PUBLIC | ACC_FINAL | ACC_STATIC, "A_2", "LA;", null, null);
+      fieldVisitor.visitEnd();
+    }
+    {
+      fieldVisitor =
+          classWriter.visitField(ACC_PUBLIC | ACC_FINAL | ACC_STATIC, "A_3", "LA;", null, null);
+      fieldVisitor.visitEnd();
+    }
+    {
+      fieldVisitor = classWriter.visitField(ACC_PRIVATE | ACC_FINAL, "value", "I", null, null);
+      fieldVisitor.visitEnd();
+    }
+    {
+      fieldVisitor =
+          classWriter.visitField(ACC_PRIVATE | ACC_FINAL, "name", "Ljava/lang/String;", null, null);
+      fieldVisitor.visitEnd();
+    }
+    {
+      methodVisitor =
+          classWriter.visitMethod(
+              ACC_PRIVATE | ACC_STATIC, "createA", "(ILjava/lang/String;)LA;", null, null);
+      methodVisitor.visitCode();
+      Label label0 = new Label();
+      methodVisitor.visitLabel(label0);
+      methodVisitor.visitLineNumber(69, label0);
+      methodVisitor.visitTypeInsn(NEW, "A");
+      methodVisitor.visitInsn(DUP);
+      methodVisitor.visitVarInsn(ILOAD, 0);
+      methodVisitor.visitVarInsn(ALOAD, 1);
+      methodVisitor.visitMethodInsn(INVOKESPECIAL, "A", "<init>", "(ILjava/lang/String;)V", false);
+      methodVisitor.visitInsn(ARETURN);
+      Label label1 = new Label();
+      methodVisitor.visitLabel(label1);
+      methodVisitor.visitLocalVariable("value", "I", null, label0, label1, 0);
+      methodVisitor.visitLocalVariable("name", "Ljava/lang/String;", null, label0, label1, 1);
+      methodVisitor.visitMaxs(4, 2);
+      methodVisitor.visitEnd();
+    }
+    {
+      methodVisitor =
+          classWriter.visitMethod(ACC_PRIVATE, "<init>", "(ILjava/lang/String;)V", null, null);
+      methodVisitor.visitCode();
+      Label label0 = new Label();
+      methodVisitor.visitLabel(label0);
+      methodVisitor.visitLineNumber(72, label0);
+      methodVisitor.visitVarInsn(ALOAD, 0);
+      methodVisitor.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
+      Label label1 = new Label();
+      methodVisitor.visitLabel(label1);
+      methodVisitor.visitLineNumber(73, label1);
+      methodVisitor.visitVarInsn(ALOAD, 0);
+      methodVisitor.visitVarInsn(ILOAD, 1);
+      methodVisitor.visitFieldInsn(PUTFIELD, "A", "value", "I");
+      Label label2 = new Label();
+      methodVisitor.visitLabel(label2);
+      methodVisitor.visitLineNumber(74, label2);
+      methodVisitor.visitVarInsn(ALOAD, 0);
+      methodVisitor.visitVarInsn(ALOAD, 2);
+      methodVisitor.visitFieldInsn(PUTFIELD, "A", "name", "Ljava/lang/String;");
+      Label label3 = new Label();
+      methodVisitor.visitLabel(label3);
+      methodVisitor.visitLineNumber(75, label3);
+      methodVisitor.visitInsn(RETURN);
+      Label label4 = new Label();
+      methodVisitor.visitLabel(label4);
+      methodVisitor.visitLocalVariable("this", "LA;", null, label0, label4, 0);
+      methodVisitor.visitLocalVariable("value", "I", null, label0, label4, 1);
+      methodVisitor.visitLocalVariable("name", "Ljava/lang/String;", null, label0, label4, 2);
+      methodVisitor.visitMaxs(2, 3);
+      methodVisitor.visitEnd();
+    }
+    {
+      methodVisitor = classWriter.visitMethod(0, "getValue", "()I", null, null);
+      methodVisitor.visitCode();
+      Label label0 = new Label();
+      methodVisitor.visitLabel(label0);
+      methodVisitor.visitLineNumber(78, label0);
+      methodVisitor.visitVarInsn(ALOAD, 0);
+      methodVisitor.visitFieldInsn(GETFIELD, "A", "value", "I");
+      methodVisitor.visitInsn(IRETURN);
+      Label label1 = new Label();
+      methodVisitor.visitLabel(label1);
+      methodVisitor.visitLocalVariable("this", "LA;", null, label0, label1, 0);
+      methodVisitor.visitMaxs(1, 1);
+      methodVisitor.visitEnd();
+    }
+    {
+      methodVisitor = classWriter.visitMethod(0, "getName", "()Ljava/lang/String;", null, null);
+      methodVisitor.visitCode();
+      Label label0 = new Label();
+      methodVisitor.visitLabel(label0);
+      methodVisitor.visitLineNumber(82, label0);
+      methodVisitor.visitVarInsn(ALOAD, 0);
+      methodVisitor.visitFieldInsn(GETFIELD, "A", "name", "Ljava/lang/String;");
+      methodVisitor.visitInsn(ARETURN);
+      Label label1 = new Label();
+      methodVisitor.visitLabel(label1);
+      methodVisitor.visitLocalVariable("this", "LA;", null, label0, label1, 0);
+      methodVisitor.visitMaxs(1, 1);
+      methodVisitor.visitEnd();
+    }
+    {
+      methodVisitor = classWriter.visitMethod(ACC_STATIC, "<clinit>", "()V", null, null);
+      methodVisitor.visitCode();
+      Label label0 = new Label();
+      methodVisitor.visitLabel(label0);
+      methodVisitor.visitLineNumber(67, label0);
+      methodVisitor.visitInsn(ICONST_1);
+      methodVisitor.visitLdcInsn("FIRST");
+      Label label1 = new Label();
+      methodVisitor.visitLabel(label1);
+      methodVisitor.visitLineNumber(68, label1);
+      methodVisitor.visitMethodInsn(
+          INVOKESTATIC, "A", "createA", "(ILjava/lang/String;)LA;", false);
+      methodVisitor.visitFieldInsn(PUTSTATIC, "A", "A_1", "LA;");
+      Label label2 = new Label();
+      methodVisitor.visitLabel(label2);
+      methodVisitor.visitLineNumber(69, label2);
+      methodVisitor.visitInsn(ICONST_1);
+      methodVisitor.visitLdcInsn("SECOND");
+      Label label3 = new Label();
+      methodVisitor.visitLabel(label3);
+      methodVisitor.visitLineNumber(70, label3);
+      methodVisitor.visitMethodInsn(
+          INVOKESTATIC, "A", "createA", "(ILjava/lang/String;)LA;", false);
+      methodVisitor.visitFieldInsn(PUTSTATIC, "A", "A_2", "LA;");
+      Label label4 = new Label();
+      methodVisitor.visitLabel(label4);
+      methodVisitor.visitLineNumber(71, label4);
+      methodVisitor.visitInsn(ICONST_1);
+      methodVisitor.visitLdcInsn("THIRD");
+      Label label5 = new Label();
+      methodVisitor.visitLabel(label5);
+      methodVisitor.visitLineNumber(72, label5);
+      methodVisitor.visitMethodInsn(
+          INVOKESTATIC, "A", "createA", "(ILjava/lang/String;)LA;", false);
+      methodVisitor.visitFieldInsn(PUTSTATIC, "A", "A_3", "LA;");
+      Label label6 = new Label();
+      methodVisitor.visitLabel(label6);
+      methodVisitor.visitLineNumber(71, label6);
+      methodVisitor.visitInsn(RETURN);
+      methodVisitor.visitMaxs(2, 0);
+      methodVisitor.visitEnd();
+    }
+    classWriter.visitEnd();
+
+    return classWriter.toByteArray();
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/InconsistentPrefixTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/InconsistentPrefixTest.java
index 401f798..6e8f3e8 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/InconsistentPrefixTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/InconsistentPrefixTest.java
@@ -4,21 +4,33 @@
 
 package com.android.tools.r8.desugar.desugaredlibrary;
 
-import static junit.framework.TestCase.assertTrue;
-import static junit.framework.TestCase.fail;
+import static org.hamcrest.CoreMatchers.containsString;
 
 import com.android.tools.r8.CompilationFailedException;
 import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.ir.desugar.DesugaredLibraryConfiguration;
 import com.android.tools.r8.jasmin.JasminBuilder;
 import java.nio.file.Path;
 import java.util.HashMap;
 import java.util.Map;
 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 InconsistentPrefixTest extends TestBase {
 
-  @Test
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withNoneRuntime().build();
+  }
+
+  public InconsistentPrefixTest(TestParameters parameters) {}
+
+  @Test(expected = CompilationFailedException.class)
   public void testNoInconsistentPrefixes() throws Exception {
     Map<String, String> x = new HashMap<>();
     x.put("pkg.sub", "p$.bus");
@@ -30,18 +42,16 @@
     Path inputJar = temp.getRoot().toPath().resolve("input.jar");
     jasminBuilder.writeJar(inputJar);
 
-    try {
-      testForD8()
-          .addProgramFiles(inputJar)
-          .addOptionsModification(
-              options ->
-                  options.desugaredLibraryConfiguration =
-                      DesugaredLibraryConfiguration.withOnlyRewritePrefixForTesting(x))
-          .compile();
-      fail("Should have raised the compilation error.");
-    } catch (CompilationFailedException e) {
-      assertTrue(
-          e.getCause().getMessage().startsWith("Error: Inconsistent prefix in desugared library:"));
-    }
+    testForD8()
+        .addProgramFiles(inputJar)
+        .addOptionsModification(
+            options ->
+                options.desugaredLibraryConfiguration =
+                    DesugaredLibraryConfiguration.withOnlyRewritePrefixForTesting(x))
+        .compileWithExpectedDiagnostics(
+            diagnostics -> {
+              diagnostics.assertErrorMessageThatMatches(
+                  containsString("Inconsistent prefix in desugared library"));
+            });
   }
 }
diff --git a/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/NestCompilationExceptionTest.java b/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/NestCompilationExceptionTest.java
index 867b61c..7f2fede 100644
--- a/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/NestCompilationExceptionTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/NestCompilationExceptionTest.java
@@ -13,8 +13,10 @@
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 
+import com.android.tools.r8.CompilationFailedException;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestCompileResult;
+import com.android.tools.r8.TestCompilerBuilder;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.TestRuntime.CfVm;
@@ -68,12 +70,11 @@
     testIncompleteNestError();
   }
 
-  private TestCompileResult<?, ?> compileOnlyClassesMatching(
+  private TestCompilerBuilder<?, ?, ?, ?, ?> compileOnlyClassesMatching(
       Matcher<String> matcher,
       boolean d8,
       boolean allowDiagnosticWarningMessages,
-      boolean ignoreMissingClasses)
-      throws Exception {
+      boolean ignoreMissingClasses) {
     List<Path> matchingClasses =
         CLASS_NAMES.stream()
             .filter(matcher::matches)
@@ -83,8 +84,7 @@
       return testForD8()
           .setMinApi(parameters.getApiLevel())
           .addProgramFiles(matchingClasses)
-          .addOptionsModification(options -> options.enableNestBasedAccessDesugaring = true)
-          .compile();
+          .addOptionsModification(options -> options.enableNestBasedAccessDesugaring = true);
     } else {
       return testForR8(parameters.getBackend())
           .noTreeShaking()
@@ -97,8 +97,7 @@
                 options.enableNestBasedAccessDesugaring = true;
                 options.ignoreMissingClasses = ignoreMissingClasses;
               })
-          .allowDiagnosticWarningMessages(allowDiagnosticWarningMessages)
-          .compile();
+          .allowDiagnosticWarningMessages(allowDiagnosticWarningMessages);
     }
   }
 
@@ -106,27 +105,39 @@
     try {
       Matcher<String> innerClassMatcher =
           containsString("BasicNestHostWithInnerClassMethods$BasicNestedClass");
-      compileOnlyClassesMatching(innerClassMatcher, false, false, false);
-      fail("Should have raised an exception for missing nest host");
-    } catch (Exception e) {
-      assertTrue(e.getCause().getCause().getMessage().contains("requires its nest host"));
+      compileOnlyClassesMatching(innerClassMatcher, false, false, false)
+          .compileWithExpectedDiagnostics(
+              diagnostics -> {
+                diagnostics.assertErrorMessageThatMatches(containsString("requires its nest host"));
+              });
+    } catch (CompilationFailedException e) {
+      // Expected failure.
+      return;
     }
+    fail("Should have raised an exception for missing nest host");
   }
 
   private void testIncompleteNestError() {
     try {
       Matcher<String> innerClassMatcher = endsWith("BasicNestHostWithInnerClassMethods");
-      compileOnlyClassesMatching(innerClassMatcher, false, false, false);
-      fail("Should have raised an exception for incomplete nest");
+      compileOnlyClassesMatching(innerClassMatcher, false, false, false)
+          .compileWithExpectedDiagnostics(
+              diagnostics -> {
+                diagnostics.assertErrorMessageThatMatches(
+                    containsString("requires its nest mates"));
+              });
     } catch (Exception e) {
-      assertTrue(e.getCause().getCause().getMessage().contains("requires its nest mates"));
+      // Expected failure.
+      return;
     }
+    fail("Should have raised an exception for incomplete nest");
   }
 
   private void testMissingNestHostWarning(boolean d8, boolean desugarWarning) throws Exception {
     Matcher<String> innerClassMatcher =
         containsString("BasicNestHostWithInnerClassMethods$BasicNestedClass");
-    TestCompileResult compileResult = compileOnlyClassesMatching(innerClassMatcher, d8, !d8, true);
+    TestCompileResult<?, ?> compileResult =
+        compileOnlyClassesMatching(innerClassMatcher, d8, !d8, true).compile();
     assertTrue(compileResult.getDiagnosticMessages().getWarnings().size() >= 1);
     if (desugarWarning) {
       assertTrue(
@@ -143,7 +154,7 @@
   private void testIncompleteNestWarning(boolean d8, boolean desugarWarning) throws Exception {
     Matcher<String> innerClassMatcher = endsWith("BasicNestHostWithInnerClassMethods");
     TestCompileResult<?, ?> compileResult =
-        compileOnlyClassesMatching(innerClassMatcher, d8, !d8, true);
+        compileOnlyClassesMatching(innerClassMatcher, d8, !d8, true).compile();
     assertTrue(compileResult.getDiagnosticMessages().getWarnings().size() >= 1);
     if (desugarWarning) {
       assertTrue(
diff --git a/src/test/java/com/android/tools/r8/diagnostics/ErrorDuringIrConversionTest.java b/src/test/java/com/android/tools/r8/diagnostics/ErrorDuringIrConversionTest.java
new file mode 100644
index 0000000..1f19d89
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/diagnostics/ErrorDuringIrConversionTest.java
@@ -0,0 +1,193 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.diagnostics;
+
+import static com.android.tools.r8.DiagnosticsMatcher.diagnosticException;
+import static com.android.tools.r8.DiagnosticsMatcher.diagnosticMessage;
+import static com.android.tools.r8.DiagnosticsMatcher.diagnosticOrigin;
+import static com.android.tools.r8.DiagnosticsMatcher.diagnosticType;
+import static org.hamcrest.CoreMatchers.allOf;
+import static org.hamcrest.CoreMatchers.anyOf;
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.fail;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.D8TestBuilder;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.ThrowableConsumer;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.utils.Reporter;
+import com.android.tools.r8.utils.StringDiagnostic;
+import com.android.tools.r8.utils.StringUtils;
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.util.concurrent.atomic.AtomicBoolean;
+import org.hamcrest.Matcher;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class ErrorDuringIrConversionTest extends TestBase {
+
+  static final String EXPECTED = StringUtils.lines("Hello, world");
+
+  static final Origin ORIGIN =
+      new Origin(Origin.root()) {
+        @Override
+        public String part() {
+          return "<test-origin>";
+        }
+      };
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withNoneRuntime().build();
+  }
+
+  public ErrorDuringIrConversionTest(TestParameters parameters) {}
+
+  private ThrowableConsumer<D8TestBuilder> addTestClassWithOrigin() {
+    return b ->
+        b.getBuilder().addClassProgramData(ToolHelper.getClassAsBytes(TestClass.class), ORIGIN);
+  }
+
+  private void checkCompilationFailedException(
+      CompilationFailedException e, Matcher<String> messageMatcher, Matcher<String> stackMatcher) {
+    // Check that the failure exception exiting the compiler contains origin info in the message.
+    assertThat(e.getMessage(), messageMatcher);
+    // Check that the stack trace has the version marker.
+    StringWriter writer = new StringWriter();
+    e.printStackTrace(new PrintWriter(writer));
+    assertThat(writer.toString(), stackMatcher);
+  }
+
+  private static void throwNPE() {
+    throw new NullPointerException("A test NPE");
+  }
+
+  @Test
+  public void testNPE() throws Exception {
+    try {
+      testForD8()
+          .apply(addTestClassWithOrigin())
+          .addOptionsModification(options -> options.testing.hookInIrConversion = () -> throwNPE())
+          .compileWithExpectedDiagnostics(
+              diagnostics -> {
+                // Check that the error is reported as an error to the diagnostics handler.
+                diagnostics
+                    .assertOnlyErrors()
+                    .assertErrorsMatch(
+                        allOf(
+                            diagnosticOrigin(ORIGIN),
+                            diagnosticException(NullPointerException.class),
+                            diagnosticMessage(containsString("A test NPE"))));
+              });
+    } catch (CompilationFailedException e) {
+      checkCompilationFailedException(
+          e,
+          containsString(ORIGIN.toString()),
+          allOf(containsString("fakeStackEntry"), containsString("throwNPE")));
+      return;
+    }
+    fail("Expected compilation to fail");
+  }
+
+  @Test
+  public void testFatalError() throws Exception {
+    try {
+      testForD8()
+          .apply(addTestClassWithOrigin())
+          .addOptionsModification(
+              options ->
+                  options.testing.hookInIrConversion =
+                      () -> options.reporter.fatalError("My Fatal Error!"))
+          .compileWithExpectedDiagnostics(
+              diagnostics -> {
+                // Check that the error is reported as an error to the diagnostics handler.
+                diagnostics.assertOnlyErrors();
+                diagnostics.assertErrorsMatch(
+                    allOf(
+                        diagnosticType(StringDiagnostic.class),
+                        diagnosticMessage(containsString("My Fatal Error")),
+                        // The fatal error is not given an origin, so it can't provide it.
+                        // Note: This could be fixed by delaying reporting and associate the info
+                        //  at the top-level handler. It would require mangling of the diagnostic,
+                        //  so maybe not that elegant.
+                        diagnosticOrigin(Origin.unknown())));
+              });
+    } catch (CompilationFailedException e) {
+      checkCompilationFailedException(
+          e, containsString(ORIGIN.toString()), containsString("fakeStackEntry"));
+      return;
+    }
+    fail("Expected compilation to fail");
+  }
+
+  private static void reportErrors(Reporter reporter) {
+    reporter.error("FOO!");
+    reporter.error("BAR!");
+    reporter.error("BAZ!");
+  }
+
+  @Test
+  public void testThreeErrors() throws Exception {
+    AtomicBoolean doError = new AtomicBoolean(true);
+    try {
+      testForD8()
+          .apply(addTestClassWithOrigin())
+          .addOptionsModification(
+              options ->
+                  options.testing.hookInIrConversion =
+                      () -> {
+                        // Ensure that the errors are reported just once as IR conversion is
+                        // threaded.
+                        if (doError.getAndSet(false)) {
+                          reportErrors(options.reporter);
+                        }
+                      })
+          .compileWithExpectedDiagnostics(
+              diagnostics -> {
+                // Check that the error is reported as an error to the diagnostics handler.
+                diagnostics
+                    .assertOnlyErrors()
+                    .assertErrorsCount(3)
+                    .assertAllErrorsMatch(
+                        allOf(
+                            diagnosticOrigin(Origin.unknown()),
+                            diagnosticType(StringDiagnostic.class),
+                            diagnosticMessage(
+                                anyOf(
+                                    containsString("FOO!"),
+                                    containsString("BAR!"),
+                                    containsString("BAZ!")))));
+              });
+    } catch (CompilationFailedException e) {
+      checkCompilationFailedException(
+          e,
+          // There may be no fail-if-error barrier inside any origin association, thus only the
+          // top level message can be expected here.
+          containsString("Compilation failed to complete"),
+          // The stack trace must contain both the version, the frame for the hook above, and one
+          // of the error messages.
+          allOf(
+              containsString("fakeStackEntry"),
+              containsString("reportErrors"),
+              anyOf(containsString("FOO!"), containsString("BAR!"), containsString("BAZ!"))));
+      return;
+    }
+    fail("Expected compilation to fail");
+  }
+
+  static class TestClass {
+
+    public static void main(String[] args) {
+      System.out.println("Hello, world");
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/StaticMethodsEnumUnboxingTest.java b/src/test/java/com/android/tools/r8/enumunboxing/StaticMethodsEnumUnboxingTest.java
new file mode 100644
index 0000000..0d01ffe
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/enumunboxing/StaticMethodsEnumUnboxingTest.java
@@ -0,0 +1,158 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.enumunboxing;
+
+import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.R8TestRunResult;
+import com.android.tools.r8.TestParameters;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class StaticMethodsEnumUnboxingTest extends EnumUnboxingTestBase {
+
+  private final TestParameters parameters;
+  private final boolean enumValueOptimization;
+  private final KeepRule enumKeepRules;
+
+  @Parameters(name = "{0} valueOpt: {1} keep: {2}")
+  public static List<Object[]> data() {
+    return enumUnboxingTestParameters();
+  }
+
+  public StaticMethodsEnumUnboxingTest(
+      TestParameters parameters, boolean enumValueOptimization, KeepRule enumKeepRules) {
+    this.parameters = parameters;
+    this.enumValueOptimization = enumValueOptimization;
+    this.enumKeepRules = enumKeepRules;
+  }
+
+  @Test
+  public void testEnumUnboxing() throws Exception {
+    Class<?> classToTest = StaticMethods.class;
+    R8TestRunResult run =
+        testForR8(parameters.getBackend())
+            .addInnerClasses(StaticMethodsEnumUnboxingTest.class)
+            .addKeepMainRule(classToTest)
+            .addKeepRules(enumKeepRules.getKeepRule())
+            .enableNeverClassInliningAnnotations()
+            .enableInliningAnnotations()
+            .addOptionsModification(opt -> enableEnumOptions(opt, enumValueOptimization))
+            .allowDiagnosticInfoMessages()
+            .setMinApi(parameters.getApiLevel())
+            .compile()
+            .inspectDiagnosticMessages(
+                m -> {
+                  assertEnumIsUnboxed(MyEnum.class, classToTest.getSimpleName(), m);
+                  assertEnumIsUnboxed(MyEnum2.class, classToTest.getSimpleName(), m);
+                })
+            .run(parameters.getRuntime(), classToTest)
+            .assertSuccess();
+    assertLines2By2Correct(run.getStdOut());
+  }
+
+  @SuppressWarnings("SameParameterValue")
+  @NeverClassInline
+  enum MyEnum {
+    A,
+    B,
+    C;
+
+    @NeverInline
+    public static void print(Object o) {
+      System.out.println(o);
+    }
+
+    @NeverInline
+    public static void printEnum(MyEnum e) {
+      System.out.println(e.ordinal());
+    }
+
+    @NeverInline
+    public static MyEnum returnEnum(boolean bool) {
+      return bool ? MyEnum.A : MyEnum.B;
+    }
+
+    @NeverInline
+    protected static void printProtected() {
+      System.out.println("protected");
+    }
+
+    @NeverInline
+    static void printPackagePrivate() {
+      System.out.println("package-private");
+    }
+
+    @NeverInline
+    private static void printPrivate() {
+      System.out.println("private");
+    }
+
+    @NeverInline
+    public static void callPrivate() {
+      System.out.print("call: ");
+      printPrivate();
+    }
+  }
+
+  // Use two enums to test collision between values and valueOf.
+  enum MyEnum2 {
+    A,
+    B,
+    C;
+  }
+
+  static class StaticMethods {
+
+    public static void main(String[] args) {
+      testCustomMethods();
+      testNonPublicMethods();
+      testGeneratedMethods();
+      testGeneratedMethods2();
+    }
+
+    @NeverInline
+    private static void testNonPublicMethods() {
+      MyEnum.printPrivate();
+      System.out.println("private");
+      MyEnum.printPackagePrivate();
+      System.out.println("package-private");
+      MyEnum.printProtected();
+      System.out.println("protected");
+      MyEnum.callPrivate();
+      System.out.println("call: private");
+    }
+
+    @NeverInline
+    private static void testCustomMethods() {
+      MyEnum.print("print");
+      System.out.println("print");
+      MyEnum.printEnum(MyEnum.A);
+      System.out.println(0);
+      System.out.println((MyEnum.returnEnum(true).ordinal()));
+      System.out.println(0);
+    }
+
+    @NeverInline
+    private static void testGeneratedMethods() {
+      System.out.println(MyEnum.valueOf("C").ordinal());
+      System.out.println(2);
+      System.out.println(MyEnum.values()[0].ordinal());
+      System.out.println(0);
+    }
+
+    @NeverInline
+    private static void testGeneratedMethods2() {
+      System.out.println(MyEnum2.valueOf("C").ordinal());
+      System.out.println(2);
+      System.out.println(MyEnum2.values()[0].ordinal());
+      System.out.println(0);
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/SwitchEnumUnboxingTest.java b/src/test/java/com/android/tools/r8/enumunboxing/SwitchEnumUnboxingTest.java
index 3ccdd37..dc2e217 100644
--- a/src/test/java/com/android/tools/r8/enumunboxing/SwitchEnumUnboxingTest.java
+++ b/src/test/java/com/android/tools/r8/enumunboxing/SwitchEnumUnboxingTest.java
@@ -50,15 +50,7 @@
             .setMinApi(parameters.getApiLevel())
             .compile()
             .inspectDiagnosticMessages(
-                m -> {
-                  // TODO(b/150370354): We need to allow static helper method to re-enable unboxing
-                  // with switches.
-                  if (enumValueOptimization && enumKeepRules.getKeepRule().equals("")) {
-                    assertEnumIsUnboxed(ENUM_CLASS, classToTest.getSimpleName(), m);
-                  } else {
-                    assertEnumIsBoxed(ENUM_CLASS, classToTest.getSimpleName(), m);
-                  }
-                })
+                m -> assertEnumIsUnboxed(ENUM_CLASS, classToTest.getSimpleName(), m))
             .run(parameters.getRuntime(), classToTest)
             .assertSuccess();
     assertLines2By2Correct(run.getStdOut());
diff --git a/src/test/java/com/android/tools/r8/ir/IrInjectionTestBase.java b/src/test/java/com/android/tools/r8/ir/IrInjectionTestBase.java
index 3b3473a..e9d9abd 100644
--- a/src/test/java/com/android/tools/r8/ir/IrInjectionTestBase.java
+++ b/src/test/java/com/android/tools/r8/ir/IrInjectionTestBase.java
@@ -45,7 +45,7 @@
   protected DexApplication buildApplication(AndroidApp input, InternalOptions options) {
     try {
       return new ApplicationReader(input, options, Timing.empty()).read();
-    } catch (IOException | ExecutionException e) {
+    } catch (IOException e) {
       throw new RuntimeException(e);
     }
   }
diff --git a/src/test/java/com/android/tools/r8/ir/analysis/type/MissingClassesJoinTest.java b/src/test/java/com/android/tools/r8/ir/analysis/type/MissingClassesJoinTest.java
index 41ce8a8..f46650f 100644
--- a/src/test/java/com/android/tools/r8/ir/analysis/type/MissingClassesJoinTest.java
+++ b/src/test/java/com/android/tools/r8/ir/analysis/type/MissingClassesJoinTest.java
@@ -4,9 +4,9 @@
 
 package com.android.tools.r8.ir.analysis.type;
 
+import static com.android.tools.r8.DiagnosticsMatcher.diagnosticException;
 import static org.hamcrest.CoreMatchers.containsString;
 import static org.hamcrest.CoreMatchers.equalTo;
-import static org.hamcrest.MatcherAssert.assertThat;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 
@@ -72,7 +72,12 @@
               .allowDiagnosticWarningMessages()
               .enableMergeAnnotations()
               .setMinApi(parameters.getApiLevel())
-              .compile()
+              .compileWithExpectedDiagnostics(
+                  diagnostics -> {
+                    if (!allowTypeErrors) {
+                      diagnostics.assertErrorThatMatches(diagnosticException(AssertionError.class));
+                    }
+                  })
               .assertAllWarningMessagesMatch(
                   equalTo(
                       "The method `void "
@@ -104,6 +109,7 @@
           //         locals: { 'java/lang/Object' }
           //         stack: { 'java/lang/Object' }
           .assertFailureWithErrorThatMatches(containsString("NullPointerException"));
+
     } catch (CompilationFailedException e) {
       // Compilation should only fail when type errors are not allowed.
       assertFalse(
@@ -111,9 +117,6 @@
               "Test should only throw when type errors are not allowed",
               Throwables.getStackTraceAsString(e)),
           allowTypeErrors);
-
-      // Verify that we fail with an assertion error.
-      assertThat(e.getCause().getMessage(), containsString("AssertionError"));
     }
   }
 
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/B156470722.java b/src/test/java/com/android/tools/r8/ir/optimize/B156470722.java
new file mode 100644
index 0000000..57a7fb2
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/B156470722.java
@@ -0,0 +1,62 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.optimize;
+
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.ReprocessMethod;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class B156470722 extends TestBase {
+
+  private final TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public B156470722(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void test() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(B156470722.class)
+        .addKeepMainRule(TestClass.class)
+        .enableInliningAnnotations()
+        .enableReprocessMethodAnnotations()
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutputLines("Hello world!");
+  }
+
+  static class TestClass {
+
+    public static void main(String[] args) {
+      method("Unused");
+      method(" world!", "Unused");
+    }
+
+    @NeverInline
+    static void method(String unused) {
+      System.out.print("Hello");
+    }
+
+    @NeverInline
+    @ReprocessMethod
+    static void method(String used, String unused) {
+      System.out.println(used);
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/NonNullParamTest.java b/src/test/java/com/android/tools/r8/ir/optimize/NonNullParamTest.java
index e01ede5..626f771 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/NonNullParamTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/NonNullParamTest.java
@@ -156,7 +156,7 @@
     MethodSubject checkViaCall = mainSubject.uniqueMethodWithName("checkViaCall");
     assertThat(checkViaCall, isPresent());
     assertEquals(0, countActCall(checkViaCall));
-    assertEquals(2, countPrintCall(checkViaCall));
+    assertEquals(canSharePrintCallInSuccessorBlock() ? 1 : 2, countPrintCall(checkViaCall));
 
     MethodSubject checkViaIntrinsic = mainSubject.uniqueMethodWithName("checkViaIntrinsic");
     assertThat(checkViaIntrinsic, isPresent());
@@ -188,12 +188,7 @@
     MethodSubject checkViaCall = mainSubject.uniqueMethodWithName("checkViaCall");
     assertThat(checkViaCall, isPresent());
     assertEquals(0, countActCall(checkViaCall));
-    // With API level >= Q we get a register assignment that allows us to share the print call in a
-    // successor block. See also InternalOptions.canHaveThisJitCodeDebuggingBug().
-    boolean canSharePrintCallInSuccessorBlock =
-        parameters.isDexRuntime()
-            && parameters.getApiLevel().getLevel() >= AndroidApiLevel.Q.getLevel();
-    assertEquals(canSharePrintCallInSuccessorBlock ? 1 : 2, countPrintCall(checkViaCall));
+    assertEquals(canSharePrintCallInSuccessorBlock() ? 1 : 2, countPrintCall(checkViaCall));
 
     MethodSubject checkViaIntrinsic = mainSubject.uniqueMethodWithName("checkViaIntrinsic");
     assertThat(checkViaIntrinsic, isPresent());
@@ -206,6 +201,13 @@
     assertEquals(0, countThrow(checkAtOneLevelHigher));
   }
 
+  private boolean canSharePrintCallInSuccessorBlock() {
+    // With API level >= Q we get a register assignment that allows us to share the print call in a
+    // successor block. See also InternalOptions.canHaveThisJitCodeDebuggingBug().
+    return parameters.isDexRuntime()
+        && parameters.getApiLevel().getLevel() >= AndroidApiLevel.Q.getLevel();
+  }
+
   @Test
   public void testNonNullParamAfterInvokeInterface() throws Exception {
     Class<?> mainClass = NonNullParamAfterInvokeInterfaceMain.class;
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/R8InliningTest.java b/src/test/java/com/android/tools/r8/ir/optimize/R8InliningTest.java
index d474dad..8ff71b8 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/R8InliningTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/R8InliningTest.java
@@ -124,7 +124,7 @@
           o.inliningInstructionLimit = 6;
           // Tests depend on nullability of receiver and argument in general. Learning very accurate
           // nullability from actual usage in tests bothers what we want to test.
-          o.enablePropagationOfDynamicTypesAtCallSites = false;
+          o.callSiteOptimizationOptions().disableTypePropagationForTesting();
         });
   }
 
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/callsites/constants/InvokeDirectPositiveTest.java b/src/test/java/com/android/tools/r8/ir/optimize/callsites/constants/InvokeDirectPositiveTest.java
index bdc3694..55daa59 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/callsites/constants/InvokeDirectPositiveTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/callsites/constants/InvokeDirectPositiveTest.java
@@ -15,7 +15,7 @@
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.analysis.value.AbstractValue;
 import com.android.tools.r8.ir.optimize.info.CallSiteOptimizationInfo;
-import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.InternalOptions.CallSiteOptimizationOptions;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.android.tools.r8.utils.codeinspector.InstructionSubject;
@@ -52,7 +52,7 @@
               o.testing.callSiteOptimizationInfoInspector = this::callSiteOptimizationInfoInspect;
             })
         .setMinApi(parameters.getApiLevel())
-        .addOptionsModification(InternalOptions::enablePropagationOfConstantsAtCallSites)
+        .addOptionsModification(CallSiteOptimizationOptions::enableConstantPropagationForTesting)
         .run(parameters.getRuntime(), MAIN)
         .assertSuccessWithOutputLines("non-null")
         .inspect(this::inspect);
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/callsites/constants/InvokeInterfacePositiveTest.java b/src/test/java/com/android/tools/r8/ir/optimize/callsites/constants/InvokeInterfacePositiveTest.java
index e837610..cb8dfec 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/callsites/constants/InvokeInterfacePositiveTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/callsites/constants/InvokeInterfacePositiveTest.java
@@ -15,7 +15,7 @@
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.analysis.value.AbstractValue;
 import com.android.tools.r8.ir.optimize.info.CallSiteOptimizationInfo;
-import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.InternalOptions.CallSiteOptimizationOptions;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.android.tools.r8.utils.codeinspector.InstructionSubject;
@@ -47,7 +47,7 @@
         .addKeepMainRule(MAIN)
         .enableNeverClassInliningAnnotations()
         .enableInliningAnnotations()
-        .addOptionsModification(InternalOptions::enablePropagationOfConstantsAtCallSites)
+        .addOptionsModification(CallSiteOptimizationOptions::enableConstantPropagationForTesting)
         .addOptionsModification(
             o -> {
               // To prevent invoke-interface from being rewritten to invoke-virtual w/ a single
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/callsites/constants/InvokeStaticPositiveTest.java b/src/test/java/com/android/tools/r8/ir/optimize/callsites/constants/InvokeStaticPositiveTest.java
index a1e936b..3a00d96 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/callsites/constants/InvokeStaticPositiveTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/callsites/constants/InvokeStaticPositiveTest.java
@@ -14,7 +14,7 @@
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.analysis.value.AbstractValue;
 import com.android.tools.r8.ir.optimize.info.CallSiteOptimizationInfo;
-import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.InternalOptions.CallSiteOptimizationOptions;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.android.tools.r8.utils.codeinspector.InstructionSubject;
@@ -45,11 +45,12 @@
         .addInnerClasses(InvokeStaticPositiveTest.class)
         .addKeepMainRule(MAIN)
         .enableInliningAnnotations()
-        .addOptionsModification(o -> {
-          o.testing.callSiteOptimizationInfoInspector = this::callSiteOptimizationInfoInspect;
-        })
+        .addOptionsModification(
+            o -> {
+              o.testing.callSiteOptimizationInfoInspector = this::callSiteOptimizationInfoInspect;
+            })
         .setMinApi(parameters.getApiLevel())
-        .addOptionsModification(InternalOptions::enablePropagationOfConstantsAtCallSites)
+        .addOptionsModification(CallSiteOptimizationOptions::enableConstantPropagationForTesting)
         .run(parameters.getRuntime(), MAIN)
         .assertSuccessWithOutputLines("non-null")
         .inspect(this::inspect);
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/callsites/constants/InvokeVirtualPositiveTest.java b/src/test/java/com/android/tools/r8/ir/optimize/callsites/constants/InvokeVirtualPositiveTest.java
index 30764845..91d181e 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/callsites/constants/InvokeVirtualPositiveTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/callsites/constants/InvokeVirtualPositiveTest.java
@@ -16,7 +16,7 @@
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.analysis.value.AbstractValue;
 import com.android.tools.r8.ir.optimize.info.CallSiteOptimizationInfo;
-import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.InternalOptions.CallSiteOptimizationOptions;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.android.tools.r8.utils.codeinspector.InstructionSubject;
@@ -53,7 +53,7 @@
               o.testing.callSiteOptimizationInfoInspector = this::callSiteOptimizationInfoInspect;
             })
         .setMinApi(parameters.getApiLevel())
-        .addOptionsModification(InternalOptions::enablePropagationOfConstantsAtCallSites)
+        .addOptionsModification(CallSiteOptimizationOptions::enableConstantPropagationForTesting)
         .run(parameters.getRuntime(), MAIN)
         .assertSuccessWithOutputLines("non-null", "null")
         .inspect(this::inspect);
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/reflection/GetClassTest.java b/src/test/java/com/android/tools/r8/ir/optimize/reflection/GetClassTest.java
index 7807a9f..0d81ff2 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/reflection/GetClassTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/reflection/GetClassTest.java
@@ -150,7 +150,7 @@
     // In `getMainClass`, a call with `null`, which will throw NPE, is replaced with null throwing
     // code. Then, remaining call with non-null argument made getClass() replaceable.
     // Disable the propagation of call site information to separate the tests.
-    options.enablePropagationOfDynamicTypesAtCallSites = false;
+    options.callSiteOptimizationOptions().disableTypePropagationForTesting();
   }
 
   @Test
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/string/StringIsEmptyTest.java b/src/test/java/com/android/tools/r8/ir/optimize/string/StringIsEmptyTest.java
index 0ac155c..0fe6e92 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/string/StringIsEmptyTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/string/StringIsEmptyTest.java
@@ -58,7 +58,7 @@
     // This test wants to check if compile-time computation is not applied to non-null,
     // non-constant value. In a simple test setting, call-site optimization knows the argument is
     // always a non-null, specific constant, but that is beyond the scope of this test.
-    options.enablePropagationOfConstantsAtCallSites = false;
+    assert !options.callSiteOptimizationOptions().isConstantPropagationEnabled();
   }
 
   private void test(TestRunResult result, int expectedStringIsEmptyCount) throws Exception {
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/string/StringValueOfTest.java b/src/test/java/com/android/tools/r8/ir/optimize/string/StringValueOfTest.java
index c4dbbc4..0333284 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/string/StringValueOfTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/string/StringValueOfTest.java
@@ -58,7 +58,7 @@
     // Disable the propagation of call site information to test String#valueOf optimization with
     // nullable argument. Otherwise, e.g., we know that only `null` is used for `hideNPE`, and then
     // simplify everything in that method.
-    options.enablePropagationOfDynamicTypesAtCallSites = false;
+    options.callSiteOptimizationOptions().disableTypePropagationForTesting();
     options.testing.forceNameReflectionOptimization = true;
   }
 
diff --git a/src/test/java/com/android/tools/r8/jdwp/RunJdwpTests.java b/src/test/java/com/android/tools/r8/jdwp/RunJdwpTests.java
index 3028525..d21bd3a 100644
--- a/src/test/java/com/android/tools/r8/jdwp/RunJdwpTests.java
+++ b/src/test/java/com/android/tools/r8/jdwp/RunJdwpTests.java
@@ -311,6 +311,7 @@
   }
 
   private void skipIfNeeded(String test, Tool tool) {
+    Assume.assumeFalse("Triage (b/144966342)", getDexVm().isNewerThan(DexVm.ART_9_0_0_HOST));
     // Is it part of smoke tests ?
     if (!RUN_ALL_TESTS) {
       Assume.assumeTrue("Skipping non-smoke test " + test, SMOKE_TESTS.contains(test));
diff --git a/src/test/java/com/android/tools/r8/kotlin/lambda/KotlinLambdaMergerValidationTest.java b/src/test/java/com/android/tools/r8/kotlin/lambda/KotlinLambdaMergerValidationTest.java
index 4062cb0..bf17ac0 100644
--- a/src/test/java/com/android/tools/r8/kotlin/lambda/KotlinLambdaMergerValidationTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/lambda/KotlinLambdaMergerValidationTest.java
@@ -4,7 +4,6 @@
 package com.android.tools.r8.kotlin.lambda;
 
 import static com.android.tools.r8.KotlinCompilerTool.KOTLINC;
-import static org.hamcrest.CoreMatchers.allOf;
 import static org.hamcrest.CoreMatchers.containsString;
 import static org.hamcrest.CoreMatchers.equalTo;
 import static org.junit.Assume.assumeTrue;
@@ -60,10 +59,7 @@
         .setMinApi(parameters.getApiLevel())
         .compile()
         // TODO(b/143165163): better not output info like this.
-        .assertAllInfoMessagesMatch(
-            allOf(
-                containsString("Unrecognized Kotlin lambda"),
-                containsString("unexpected static method")))
+        .assertAllInfoMessagesMatch(containsString("unexpected static method"))
         .addRunClasspathFiles(ToolHelper.getKotlinStdlibJar())
         .run(parameters.getRuntime(), pkg + ".B143165163Kt")
         .assertSuccessWithOutputLines("outer foo bar", "outer foo default");
@@ -89,10 +85,7 @@
         .setMinApi(parameters.getApiLevel())
         .compile()
         // TODO(b/143165163): better not output info like this.
-        .assertAllInfoMessagesMatch(
-            allOf(
-                containsString("Unrecognized Kotlin lambda"),
-                containsString("does not implement any interfaces")))
+        .assertAllInfoMessagesMatch(containsString("does not implement any interfaces"))
         .assertAllWarningMessagesMatch(equalTo("Resource 'META-INF/MANIFEST.MF' already exists."))
         .run(parameters.getRuntime(), pkg + ".B143165163Kt")
         .assertSuccessWithOutputLines("outer foo bar", "outer foo default");
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteAnonymousTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteAnonymousTest.java
new file mode 100644
index 0000000..f4ee57a
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteAnonymousTest.java
@@ -0,0 +1,113 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.kotlin.metadata;
+
+import static com.android.tools.r8.KotlinCompilerTool.KOTLINC;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isRenamed;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.ToolHelper.KotlinTargetVersion;
+import com.android.tools.r8.kotlin.KotlinMetadataWriter;
+import com.android.tools.r8.shaking.ProguardKeepAttributes;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import java.nio.file.Path;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class MetadataRewriteAnonymousTest extends KotlinMetadataTestBase {
+
+  private final String EXPECTED = "foo";
+
+  @Parameterized.Parameters(name = "{0} target: {1}")
+  public static Collection<Object[]> data() {
+    return buildParameters(
+        getTestParameters().withCfRuntimes().build(), KotlinTargetVersion.values());
+  }
+
+  public MetadataRewriteAnonymousTest(
+      TestParameters parameters, KotlinTargetVersion targetVersion) {
+    super(targetVersion);
+    this.parameters = parameters;
+  }
+
+  private static Map<KotlinTargetVersion, Path> libJars = new HashMap<>();
+  private final TestParameters parameters;
+
+  @BeforeClass
+  public static void createLibJar() throws Exception {
+    String baseLibFolder = PKG_PREFIX + "/anonymous_lib";
+    for (KotlinTargetVersion targetVersion : KotlinTargetVersion.values()) {
+      Path baseLibJar =
+          kotlinc(KOTLINC, targetVersion)
+              .addSourceFiles(getKotlinFileInTest(baseLibFolder, "lib"))
+              .compile();
+      libJars.put(targetVersion, baseLibJar);
+    }
+  }
+
+  @Test
+  public void smokeTest() throws Exception {
+    Path libJar = libJars.get(targetVersion);
+
+    Path output =
+        kotlinc(parameters.getRuntime().asCf(), KOTLINC, targetVersion)
+            .addClasspathFiles(libJar)
+            .addSourceFiles(getKotlinFileInTest(PKG_PREFIX + "/anonymous_app", "main"))
+            .setOutputPath(temp.newFolder().toPath())
+            .compile();
+    testForJvm()
+        .addRunClasspathFiles(ToolHelper.getKotlinStdlibJar(), libJar)
+        .addClasspath(output)
+        .run(parameters.getRuntime(), PKG + ".anonymous_app.MainKt")
+        .assertSuccessWithOutputLines(EXPECTED);
+  }
+
+  @Test
+  public void testMetadataForLib() throws Exception {
+    Path libJar =
+        testForR8(parameters.getBackend())
+            .addClasspathFiles(ToolHelper.getKotlinStdlibJar())
+            .addProgramFiles(libJars.get(targetVersion))
+            .addKeepAllClassesRuleWithAllowObfuscation()
+            .addKeepRules("-keep class " + PKG + ".anonymous_lib.Test$A { *; }")
+            .addKeepRules("-keep class " + PKG + ".anonymous_lib.Test { *; }")
+            .addKeepAttributes(
+                ProguardKeepAttributes.RUNTIME_VISIBLE_ANNOTATIONS,
+                ProguardKeepAttributes.SIGNATURE,
+                ProguardKeepAttributes.INNER_CLASSES,
+                ProguardKeepAttributes.ENCLOSING_METHOD)
+            .compile()
+            .inspect(this::inspect)
+            .writeToZip();
+    Path main =
+        kotlinc(parameters.getRuntime().asCf(), KOTLINC, targetVersion)
+            .addClasspathFiles(libJar)
+            .addSourceFiles(getKotlinFileInTest(PKG_PREFIX + "/anonymous_app", "main"))
+            .setOutputPath(temp.newFolder().toPath())
+            .compile();
+    testForJvm()
+        .addRunClasspathFiles(ToolHelper.getKotlinStdlibJar(), libJar)
+        .addClasspath(main)
+        .run(parameters.getRuntime(), PKG + ".anonymous_app.MainKt")
+        .assertSuccessWithOutputLines(EXPECTED);
+  }
+
+  private void inspect(CodeInspector inspector) {
+    ClassSubject clazz = inspector.clazz(PKG + ".anonymous_lib.Test");
+    System.out.println(
+        KotlinMetadataWriter.kotlinMetadataToString("", clazz.getKotlinClassMetadata()));
+    ClassSubject anonymousClass = inspector.clazz(PKG + ".anonymous_lib.Test$internalProp$1");
+    assertThat(anonymousClass, isRenamed());
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteBoxedTypesTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteBoxedTypesTest.java
index bc0f2a4..06d2636 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteBoxedTypesTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteBoxedTypesTest.java
@@ -104,6 +104,7 @@
   public void testMetadataForLib() throws Exception {
     Path libJar =
         testForR8(parameters.getBackend())
+            .addClasspathFiles(ToolHelper.getKotlinStdlibJar())
             .addProgramFiles(libJars.get(targetVersion))
             .addKeepAllClassesRule()
             .addKeepAttributes(
@@ -158,6 +159,7 @@
   public void testMetadataForReflect() throws Exception {
     Path libJar =
         testForR8(parameters.getBackend())
+            .addClasspathFiles(ToolHelper.getKotlinStdlibJar())
             .addProgramFiles(libJars.get(targetVersion))
             .addKeepAllClassesRule()
             .addKeepAttributes(
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteDependentKeep.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteDependentKeep.java
new file mode 100644
index 0000000..a8fcf61
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteDependentKeep.java
@@ -0,0 +1,65 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.kotlin.metadata;
+
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.ToolHelper.KotlinTargetVersion;
+import com.android.tools.r8.shaking.ProguardKeepAttributes;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.FoundClassSubject;
+import java.io.IOException;
+import java.util.Collection;
+import java.util.concurrent.ExecutionException;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class MetadataRewriteDependentKeep extends KotlinMetadataTestBase {
+
+  @Parameterized.Parameters(name = "{0} target: {1}")
+  public static Collection<Object[]> data() {
+    return buildParameters(
+        getTestParameters().withCfRuntimes().build(), KotlinTargetVersion.values());
+  }
+
+  private final TestParameters parameters;
+
+  public MetadataRewriteDependentKeep(
+      TestParameters parameters, KotlinTargetVersion targetVersion) {
+    super(targetVersion);
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testR8() throws CompilationFailedException, IOException, ExecutionException {
+    testForR8(parameters.getBackend())
+        .addProgramFiles(ToolHelper.getKotlinStdlibJar())
+        .setMinApi(parameters.getApiLevel())
+        .addKeepKotlinMetadata()
+        .addKeepRules(StringUtils.joinLines("-if class *.Metadata", "-keep class <1>.io.** { *; }"))
+        .addKeepAttributes(ProguardKeepAttributes.RUNTIME_VISIBLE_ANNOTATIONS)
+        .compile()
+        .inspect(this::inspect);
+  }
+
+  private void inspect(CodeInspector inspector) {
+    // All kept classes should have their kotlin metadata.
+    for (FoundClassSubject clazz : inspector.allClasses()) {
+      if (clazz.getFinalName().startsWith("kotlin.io")
+          || clazz.getFinalName().equals("kotlin.Metadata")) {
+        assertNotNull(clazz.getKotlinClassMetadata());
+      } else {
+        assertNull(clazz.getKotlinClassMetadata());
+      }
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteFlexibleUpperBoundTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteFlexibleUpperBoundTest.java
new file mode 100644
index 0000000..90b74f9
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteFlexibleUpperBoundTest.java
@@ -0,0 +1,142 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.kotlin.metadata;
+
+import static com.android.tools.r8.KotlinCompilerTool.KOTLINC;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isRenamed;
+import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.ToolHelper.KotlinTargetVersion;
+import com.android.tools.r8.shaking.ProguardKeepAttributes;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.KmPropertySubject;
+import com.android.tools.r8.utils.codeinspector.KmTypeProjectionSubject;
+import com.android.tools.r8.utils.codeinspector.KmTypeSubject;
+import java.io.IOException;
+import java.nio.file.Path;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ExecutionException;
+import kotlinx.metadata.KmFlexibleTypeUpperBound;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class MetadataRewriteFlexibleUpperBoundTest extends KotlinMetadataTestBase {
+
+  private final String EXPECTED = StringUtils.lines("B.foo(): 42");
+  private final String PKG_LIB = PKG + ".flexible_upper_bound_lib";
+
+  @Parameterized.Parameters(name = "{0} target: {1}")
+  public static Collection<Object[]> data() {
+    return buildParameters(
+        getTestParameters().withCfRuntimes().build(), KotlinTargetVersion.values());
+  }
+
+  public MetadataRewriteFlexibleUpperBoundTest(
+      TestParameters parameters, KotlinTargetVersion targetVersion) {
+    super(targetVersion);
+    this.parameters = parameters;
+  }
+
+  private static Map<KotlinTargetVersion, Path> libJars = new HashMap<>();
+  private final TestParameters parameters;
+
+  @BeforeClass
+  public static void createLibJar() throws Exception {
+    String baseLibFolder = PKG_PREFIX + "/flexible_upper_bound_lib";
+    for (KotlinTargetVersion targetVersion : KotlinTargetVersion.values()) {
+      Path baseLibJar =
+          kotlinc(KOTLINC, targetVersion)
+              .addSourceFiles(getKotlinFileInTest(baseLibFolder, "lib"))
+              .compile();
+      libJars.put(targetVersion, baseLibJar);
+    }
+  }
+
+  @Test
+  public void smokeTest() throws Exception {
+    Path libJar = libJars.get(targetVersion);
+    Path output =
+        kotlinc(parameters.getRuntime().asCf(), KOTLINC, targetVersion)
+            .addClasspathFiles(libJar)
+            .addSourceFiles(getKotlinFileInTest(PKG_PREFIX + "/flexible_upper_bound_app", "main"))
+            .setOutputPath(temp.newFolder().toPath())
+            .compile();
+    testForJvm()
+        .addRunClasspathFiles(ToolHelper.getKotlinStdlibJar(), libJar)
+        .addClasspath(output)
+        .run(parameters.getRuntime(), PKG + ".flexible_upper_bound_app.MainKt")
+        .assertSuccessWithOutput(EXPECTED);
+  }
+
+  @Test
+  public void testMetadataForLib() throws Exception {
+    Path libJar =
+        testForR8(parameters.getBackend())
+            .addClasspathFiles(ToolHelper.getKotlinStdlibJar())
+            .addProgramFiles(libJars.get(targetVersion))
+            // Allow renaming A to ensure that we rename in the flexible upper bound type.
+            .addKeepRules("-keep,allowobfuscation class " + PKG_LIB + ".A { *; }")
+            .addKeepRules("-keep class " + PKG_LIB + ".B { *; }")
+            .addKeepRules("-keep class " + PKG_LIB + ".FlexibleUpperBound { *; }")
+            .addKeepAttributes(
+                ProguardKeepAttributes.RUNTIME_VISIBLE_ANNOTATIONS,
+                ProguardKeepAttributes.SIGNATURE,
+                ProguardKeepAttributes.INNER_CLASSES,
+                ProguardKeepAttributes.ENCLOSING_METHOD)
+            .compile()
+            .inspect(this::inspect)
+            .writeToZip();
+    Path main =
+        kotlinc(parameters.getRuntime().asCf(), KOTLINC, targetVersion)
+            .addClasspathFiles(libJar)
+            .addSourceFiles(getKotlinFileInTest(PKG_PREFIX + "/flexible_upper_bound_app", "main"))
+            .setOutputPath(temp.newFolder().toPath())
+            .compile();
+    testForJvm()
+        .addRunClasspathFiles(
+            ToolHelper.getKotlinStdlibJar(), ToolHelper.getKotlinReflectJar(), libJar)
+        .addClasspath(main)
+        .run(parameters.getRuntime(), PKG + ".flexible_upper_bound_app.MainKt")
+        .assertSuccessWithOutput(EXPECTED);
+  }
+
+  private void inspect(CodeInspector inspector) throws IOException, ExecutionException {
+    // We are checking that A is renamed, and that the flexible upper bound information is
+    // reflecting that.
+    ClassSubject a = inspector.clazz(PKG_LIB + ".A");
+    assertThat(a, isRenamed());
+
+    ClassSubject flexibleUpperBound = inspector.clazz(PKG_LIB + ".FlexibleUpperBound");
+    assertThat(flexibleUpperBound, isPresent());
+    assertThat(flexibleUpperBound, not(isRenamed()));
+
+    List<KmPropertySubject> properties = flexibleUpperBound.getKmClass().getProperties();
+    assertEquals(1, properties.size());
+    KmPropertySubject kmPropertySubject = properties.get(0);
+    KmTypeSubject returnTypeSubject = kmPropertySubject.returnType();
+    assertThat(returnTypeSubject, isPresent());
+    assertEquals(1, returnTypeSubject.typeArguments().size());
+    KmTypeProjectionSubject argumentSubject = returnTypeSubject.typeArguments().get(0);
+    KmFlexibleTypeUpperBound flexUpperBound = argumentSubject.type().getFlexibleUpperBound();
+    assertNotNull(flexUpperBound);
+    assertEquals(
+        "Class(name=" + a.getFinalBinaryName() + ")",
+        flexUpperBound.getType().classifier.toString());
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInClasspathTypeTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInClasspathTypeTest.java
index 4e5ae0e..4aa0a2c 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInClasspathTypeTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInClasspathTypeTest.java
@@ -9,7 +9,6 @@
 import static com.android.tools.r8.utils.codeinspector.Matchers.isRenamed;
 import static org.hamcrest.CoreMatchers.not;
 import static org.hamcrest.MatcherAssert.assertThat;
-import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
 
 import com.android.tools.r8.TestParameters;
@@ -92,74 +91,11 @@
   }
 
   @Test
-  public void testMetadataInClasspathType_merged() throws Exception {
-    Path baseLibJar = baseLibJarMap.get(targetVersion);
-    Path libJar =
-        testForR8(parameters.getBackend())
-            .addClasspathFiles(baseLibJar)
-            .addProgramFiles(extLibJarMap.get(targetVersion))
-            // Keep the Extra class and its interface (which has the method).
-            .addKeepRules("-keep class **.Extra")
-            // Keep the ImplKt extension method which requires metadata
-            // to be called with Kotlin syntax from other kotlin code.
-            .addKeepRules("-keep class **.ImplKt { <methods>; }")
-            .addKeepAttributes(ProguardKeepAttributes.RUNTIME_VISIBLE_ANNOTATIONS)
-            .compile()
-            .inspect(this::inspectMerged)
-            .writeToZip();
-
-    Path output =
-        kotlinc(parameters.getRuntime().asCf(), KOTLINC, targetVersion)
-            .addClasspathFiles(baseLibJar, libJar)
-            .addSourceFiles(getKotlinFileInTest(PKG_PREFIX + "/classpath_app", "main"))
-            .setOutputPath(temp.newFolder().toPath())
-            .compile();
-
-    testForJvm()
-        .addRunClasspathFiles(ToolHelper.getKotlinStdlibJar(), baseLibJar, libJar)
-        .addClasspath(output)
-        .run(parameters.getRuntime(), PKG + ".classpath_app.MainKt")
-        .assertSuccessWithOutput(EXPECTED);
-  }
-
-  private void inspectMerged(CodeInspector inspector) {
-    String implClassName = PKG + ".classpath_lib_ext.Impl";
-    String implKtClassName = PKG + ".classpath_lib_ext.ImplKt";
-    String extraClassName = PKG + ".classpath_lib_ext.Extra";
-
-    assertThat(inspector.clazz(implClassName), not(isPresent()));
-
-    ClassSubject implKt = inspector.clazz(implKtClassName);
-    assertThat(implKt, isPresent());
-    assertThat(implKt, not(isRenamed()));
-    // API entry is kept, hence the presence of Metadata.
-    KmPackageSubject kmPackage = implKt.getKmPackage();
-    assertThat(kmPackage, isPresent());
-
-    KmFunctionSubject kmFunction = kmPackage.kmFunctionExtensionWithUniqueName("fooExt");
-    assertThat(kmFunction, isPresent());
-
-    ClassSubject extra = inspector.clazz(extraClassName);
-    assertThat(extra, isPresent());
-    assertThat(extra, not(isRenamed()));
-    // API entry is kept, hence the presence of Metadata.
-    KmClassSubject kmClass = extra.getKmClass();
-    assertThat(kmClass, isPresent());
-    List<ClassSubject> superTypes = kmClass.getSuperTypes();
-    assertTrue(superTypes.stream().noneMatch(
-        supertype -> supertype.getFinalDescriptor().contains("Impl")));
-    // The super types are changed and we should not keep any information about it in the metadata.
-    List<String> superTypeDescriptors = kmClass.getSuperTypeDescriptors();
-    assertEquals(1, superTypeDescriptors.size());
-    assertEquals(KT_ANY, superTypeDescriptors.get(0));
-  }
-
-  @Test
   public void testMetadataInClasspathType_renamed() throws Exception {
     Path baseLibJar = baseLibJarMap.get(targetVersion);
     Path libJar =
         testForR8(parameters.getBackend())
-            .addClasspathFiles(baseLibJar)
+            .addClasspathFiles(baseLibJar, ToolHelper.getKotlinStdlibJar())
             .addProgramFiles(extLibJarMap.get(targetVersion))
             // Keep the Extra class and its interface (which has the method).
             .addKeepRules("-keep class **.Extra")
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInCompanionTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInCompanionTest.java
index 9d75aaa..38a6815 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInCompanionTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInCompanionTest.java
@@ -93,6 +93,7 @@
   public void testMetadataInCompanion_kept() throws Exception {
     Path libJar =
         testForR8(parameters.getBackend())
+            .addClasspathFiles(ToolHelper.getKotlinStdlibJar())
             .addProgramFiles(companionLibJarMap.get(targetVersion))
             // Keep everything
             .addKeepRules("-keep class **.companion_lib.** { *; }")
@@ -124,6 +125,7 @@
   public void testMetadataInCompanion_renamed() throws Exception {
     Path libJar =
         testForR8(parameters.getBackend())
+            .addClasspathFiles(ToolHelper.getKotlinStdlibJar())
             .addProgramFiles(companionLibJarMap.get(targetVersion))
             // Keep the B class and its interface (which has the doStuff method).
             .addKeepRules("-keep class **.B")
@@ -134,8 +136,8 @@
             .addKeepRules("-keepclassmembers class **.B$* { *** get*(...); *** set*(...); }")
             // Keep the companion instance in the B class
             .addKeepRules("-keepclassmembers class **.B { *** Companion; }")
-            // Keep the name of companion class
-            .addKeepRules("-keepnames class **.*$Companion")
+            // Keep the class of the companion class.
+            .addKeepRules("-keep class **.*$Companion")
             // No rule for Super, but will be kept and renamed.
             .addKeepAttributes(ProguardKeepAttributes.RUNTIME_VISIBLE_ANNOTATIONS)
             // To keep @JvmField annotation
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInExtensionFunctionTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInExtensionFunctionTest.java
index aaf2674..41d1f09 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInExtensionFunctionTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInExtensionFunctionTest.java
@@ -143,6 +143,7 @@
   public void testMetadataInExtensionFunction_renamed() throws Exception {
     Path libJar =
         testForR8(parameters.getBackend())
+            .addClasspathFiles(ToolHelper.getKotlinStdlibJar())
             .addProgramFiles(extLibJarMap.get(targetVersion))
             // Keep the B class and its interface (which has the doStuff method).
             .addKeepRules("-keep class **.B")
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInExtensionPropertyTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInExtensionPropertyTest.java
index 1606cbe..c627171 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInExtensionPropertyTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInExtensionPropertyTest.java
@@ -152,6 +152,7 @@
   public void testMetadataInExtensionProperty_renamed() throws Exception {
     Path libJar =
         testForR8(parameters.getBackend())
+            .addClasspathFiles(ToolHelper.getKotlinStdlibJar())
             .addProgramFiles(extLibJarMap.get(targetVersion))
             // Keep the B class and its interface (which has the doStuff method).
             .addKeepRules("-keep class **.B")
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInFunctionTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInFunctionTest.java
index 60cad3b..7cfa4a7 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInFunctionTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInFunctionTest.java
@@ -149,6 +149,7 @@
   public void testMetadataInFunction_renamed() throws Exception {
     Path libJar =
         testForR8(parameters.getBackend())
+            .addClasspathFiles(ToolHelper.getKotlinStdlibJar())
             .addProgramFiles(funLibJarMap.get(targetVersion))
             // Keep the B class and its interface (which has the doStuff method).
             .addKeepRules("-keep class **.B")
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInFunctionWithVarargTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInFunctionWithVarargTest.java
index fea7b3e..d82b5c6 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInFunctionWithVarargTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInFunctionWithVarargTest.java
@@ -90,6 +90,7 @@
   public void testMetadataInFunctionWithVararg() throws Exception {
     Path libJar =
         testForR8(parameters.getBackend())
+            .addClasspathFiles(ToolHelper.getKotlinStdlibJar())
             .addProgramFiles(varargLibJarMap.get(targetVersion))
             // keep SomeClass#foo, since there is a method reference in the app.
             .addKeepRules("-keep class **.SomeClass { *** foo(...); }")
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInLibraryTypeTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInLibraryTypeTest.java
index 71dd5df..12b361b 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInLibraryTypeTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInLibraryTypeTest.java
@@ -92,7 +92,8 @@
     String main = PKG + ".libtype_app.MainKt";
     Path out =
         testForR8(parameters.getBackend())
-            // Intentionally not providing basLibJar as lib file nor classpath file.
+            // Intentionally not providing baseLibJar as lib file nor classpath file.
+            .addClasspathFiles()
             .addProgramFiles(extLibJarMap.get(targetVersion), appJarMap.get(targetVersion))
             // Keep Ext extension method which requires metadata to be called with Kotlin syntax
             // from other kotlin code.
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInMultifileClassTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInMultifileClassTest.java
index 477c5e6..09d62d7 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInMultifileClassTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInMultifileClassTest.java
@@ -95,6 +95,7 @@
   public void testMetadataInMultifileClass_merged() throws Exception {
     Path libJar =
         testForR8(parameters.getBackend())
+            .addClasspathFiles(ToolHelper.getKotlinStdlibJar())
             .addProgramFiles(multifileLibJarMap.get(targetVersion))
             // Keep UtilKt#comma*Join*(). Let R8 optimize (inline) others, such as joinOf*(String).
             .addKeepRules("-keep class **.UtilKt")
@@ -129,21 +130,22 @@
     assertThat(joinOfInt, not(isPresent()));
 
     inspectMetadataForFacade(inspector, util);
-
-    inspectSignedKt(inspector);
+    // TODO(b/156290332): Seems like this test is incorrect and should never work.
+    // inspectSignedKt(inspector);
   }
 
   @Test
   public void testMetadataInMultifileClass_renamed() throws Exception {
     Path libJar =
         testForR8(parameters.getBackend())
+            .addClasspathFiles(ToolHelper.getKotlinStdlibJar())
             .addProgramFiles(multifileLibJarMap.get(targetVersion))
             // Keep UtilKt#comma*Join*().
             .addKeepRules("-keep class **.UtilKt")
+            .addKeepRules("-keep,allowobfuscation class **.UtilKt__SignedKt")
             .addKeepRules("-keepclassmembers class * { ** comma*Join*(...); }")
             // Keep yet rename joinOf*(String).
-            .addKeepRules(
-                "-keepclassmembers,allowobfuscation class * { ** joinOf*(...); }")
+            .addKeepRules("-keepclassmembers,allowobfuscation class * { ** joinOf*(...); }")
             .addKeepAttributes(ProguardKeepAttributes.RUNTIME_VISIBLE_ANNOTATIONS)
             .compile()
             .inspect(this::inspectRenamed)
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInNestedClassTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInNestedClassTest.java
index f4530b3..3c218fe 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInNestedClassTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInNestedClassTest.java
@@ -84,6 +84,7 @@
   public void testMetadataInNestedClass() throws Exception {
     Path libJar =
         testForR8(parameters.getBackend())
+            .addClasspathFiles(ToolHelper.getKotlinStdlibJar())
             .addProgramFiles(nestedLibJarMap.get(targetVersion))
             // Keep the Outer class and delegations.
             .addKeepRules("-keep class **.Outer { <init>(...); *** delegate*(...); }")
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInParameterTypeTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInParameterTypeTest.java
index 54cd15c..53c8e4f 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInParameterTypeTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInParameterTypeTest.java
@@ -82,6 +82,7 @@
   public void testMetadataInParameterType_renamed() throws Exception {
     Path libJar =
         testForR8(parameters.getBackend())
+            .addClasspathFiles(ToolHelper.getKotlinStdlibJar())
             .addProgramFiles(parameterTypeLibJarMap.get(targetVersion))
             // Keep non-private members of Impl
             .addKeepRules("-keep public class **.Impl { !private *; }")
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInPropertyTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInPropertyTest.java
index 3847134..f97fd1c 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInPropertyTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInPropertyTest.java
@@ -88,6 +88,7 @@
   public void testMetadataInProperty_getterOnly() throws Exception {
     Path libJar =
         testForR8(parameters.getBackend())
+            .addClasspathFiles(ToolHelper.getKotlinStdlibJar())
             .addProgramFiles(propertyTypeLibJarMap.get(targetVersion))
             // Keep property getters
             .addKeepRules("-keep class **.Person { <init>(...); }")
@@ -180,6 +181,7 @@
   public void testMetadataInProperty_setterOnly() throws Exception {
     Path libJar =
         testForR8(parameters.getBackend())
+            .addClasspathFiles(ToolHelper.getKotlinStdlibJar())
             .addProgramFiles(propertyTypeLibJarMap.get(targetVersion))
             // Keep property setters (and users)
             .addKeepRules("-keep class **.Person { <init>(...); }")
@@ -243,9 +245,9 @@
     // #        fieldSignature: name:Ljava/lang/String;,
     // #        getterSignature: getName()Ljava/lang/String;,
     // #        setterSignature: setName(Ljava/lang/String;)V,
-    assertEquals(name.fieldSignature().asString(), "name:Ljava/lang/String;");
-    assertEquals(name.getterSignature().asString(), "getName()Ljava/lang/String;");
-    assertEquals(name.setterSignature().asString(), "setName(Ljava/lang/String;)V");
+    assertEquals("name:Ljava/lang/String;", name.fieldSignature().asString());
+    assertEquals("getName()Ljava/lang/String;", name.getterSignature().asString());
+    assertEquals("setName(Ljava/lang/String;)V", name.setterSignature().asString());
 
     KmPropertySubject familyName = kmClass.kmPropertyWithUniqueName("familyName");
     assertThat(familyName, not(isPresent()));
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInPropertyTypeTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInPropertyTypeTest.java
index ceced0d..dea1c4a 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInPropertyTypeTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInPropertyTypeTest.java
@@ -82,6 +82,7 @@
   public void testMetadataInProperty_renamed() throws Exception {
     Path libJar =
         testForR8(parameters.getBackend())
+            .addClasspathFiles(ToolHelper.getKotlinStdlibJar())
             .addProgramFiles(propertyTypeLibJarMap.get(targetVersion))
             // Keep non-private members of Impl
             .addKeepRules("-keep public class **.Impl { !private *; }")
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInReturnTypeTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInReturnTypeTest.java
index 09d4a5a..63fc38d 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInReturnTypeTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInReturnTypeTest.java
@@ -82,6 +82,7 @@
   public void testMetadataInReturnType_renamed() throws Exception {
     Path libJar =
         testForR8(parameters.getBackend())
+            .addClasspathFiles(ToolHelper.getKotlinStdlibJar())
             .addProgramFiles(returnTypeLibJarMap.get(targetVersion))
             // Keep non-private members of Impl
             .addKeepRules("-keep public class **.Impl { !private *; }")
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInSealedClassTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInSealedClassTest.java
index b09ef7e..318a765 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInSealedClassTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInSealedClassTest.java
@@ -14,7 +14,6 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotEquals;
-import static org.junit.Assert.assertTrue;
 
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.ToolHelper;
@@ -90,6 +89,7 @@
   public void testMetadataInSealedClass_valid() throws Exception {
     Path libJar =
         testForR8(parameters.getBackend())
+            .addClasspathFiles(ToolHelper.getKotlinStdlibJar())
             .addProgramFiles(sealedLibJarMap.get(targetVersion))
             // Keep the Expr class
             .addKeepRules("-keep class **.Expr")
@@ -155,6 +155,7 @@
   public void testMetadataInSealedClass_invalid() throws Exception {
     Path libJar =
         testForR8(parameters.getBackend())
+            .addClasspathFiles(ToolHelper.getKotlinStdlibJar())
             .addProgramFiles(sealedLibJarMap.get(targetVersion))
             // Keep the Expr class
             .addKeepRules("-keep class **.Expr")
@@ -180,14 +181,8 @@
 
   private void inspectInvalid(CodeInspector inspector) {
     String exprClassName = PKG + ".sealed_lib.Expr";
-    String numClassName = PKG + ".sealed_lib.Num";
     String libClassName = PKG + ".sealed_lib.LibKt";
 
-    // Without any specific keep rule and no instantiation point, it's not necessary to keep
-    // sub classes of Expr.
-    ClassSubject num = inspector.clazz(numClassName);
-    assertThat(num, not(isPresent()));
-
     ClassSubject expr = inspector.clazz(exprClassName);
     assertThat(expr, isPresent());
     assertThat(expr, not(isRenamed()));
@@ -195,8 +190,6 @@
     KmClassSubject kmClass = expr.getKmClass();
     assertThat(kmClass, isPresent());
 
-    assertTrue(kmClass.getSealedSubclassDescriptors().isEmpty());
-
     ClassSubject libKt = inspector.clazz(libClassName);
     assertThat(expr, isPresent());
     assertThat(expr, not(isRenamed()));
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInSuperTypeTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInSuperTypeTest.java
index 3c02dac..8eb76bb 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInSuperTypeTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInSuperTypeTest.java
@@ -84,6 +84,7 @@
   public void testMetadataInSupertype_merged() throws Exception {
     Path libJar =
         testForR8(parameters.getBackend())
+            .addClasspathFiles(ToolHelper.getKotlinStdlibJar())
             .addProgramFiles(superTypeLibJarMap.get(targetVersion))
             // Keep non-private members except for ones in `internal` definitions.
             .addKeepRules("-keep public class !**.internal.**, * { !private *; }")
@@ -129,6 +130,7 @@
   public void testMetadataInSupertype_renamed() throws Exception {
     Path libJar =
         testForR8(parameters.getBackend())
+            .addClasspathFiles(ToolHelper.getKotlinStdlibJar())
             .addProgramFiles(superTypeLibJarMap.get(targetVersion))
             // Keep non-private members except for ones in `internal` definitions.
             .addKeepRules("-keep public class !**.internal.**, * { !private *; }")
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInTypeAliasTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInTypeAliasTest.java
index 26ea8de..b2e0b8a 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInTypeAliasTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInTypeAliasTest.java
@@ -112,6 +112,7 @@
   public void testMetadataInTypeAlias_renamed() throws Exception {
     Path libJar =
         testForR8(parameters.getBackend())
+            .addClasspathFiles(ToolHelper.getKotlinStdlibJar())
             .addProgramFiles(typeAliasLibJarMap.get(targetVersion))
             // Keep non-private members of Impl
             .addKeepRules("-keep class **.Impl { !private *; }")
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInTypeArgumentsTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInTypeArgumentsTest.java
index bed423f..df35c97 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInTypeArgumentsTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInTypeArgumentsTest.java
@@ -8,7 +8,6 @@
 import static com.android.tools.r8.utils.codeinspector.Matchers.isExtensionFunction;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isRenamed;
-import static org.hamcrest.CoreMatchers.containsString;
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
@@ -16,7 +15,6 @@
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.ToolHelper.KotlinTargetVersion;
-import com.android.tools.r8.ToolHelper.ProcessResult;
 import com.android.tools.r8.shaking.ProguardKeepAttributes;
 import com.android.tools.r8.utils.StringUtils;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
@@ -129,6 +127,7 @@
   public void testMetadataInTypeAliasWithR8() throws Exception {
     Path libJar =
         testForR8(parameters.getBackend())
+            .addClasspathFiles(ToolHelper.getKotlinStdlibJar())
             .addProgramFiles(jarMap.get(targetVersion))
             // Keep ClassThatWillBeObfuscated, but allow minification.
             .addKeepRules("-keep,allowobfuscation class **ClassThatWillBeObfuscated")
@@ -148,20 +147,16 @@
             .compile()
             .inspect(this::inspect)
             .writeToZip();
-
-    // TODO(b/152306391): Reified type-parameters are not flagged correctly.
-    ProcessResult mainResult =
+    Path mainJar =
         kotlinc(parameters.getRuntime().asCf(), KOTLINC, targetVersion)
             .addClasspathFiles(libJar)
             .addSourceFiles(getKotlinFileInTest(PKG_PREFIX + "/typeargument_app", "main"))
-            .setOutputPath(temp.newFolder().toPath())
-            .compileRaw();
-    assertEquals(1, mainResult.exitCode);
-    assertThat(
-        mainResult.stderr,
-        containsString(
-            "org.jetbrains.kotlin.codegen.CompilationException: "
-                + "Back-end (JVM) Internal error: wrong bytecode generated"));
+            .compile();
+    testForJvm()
+        .addRunClasspathFiles(ToolHelper.getKotlinStdlibJar(), libJar)
+        .addClasspath(mainJar)
+        .run(parameters.getRuntime(), PKG + ".typeargument_app.MainKt")
+        .assertSuccessWithOutput(EXPECTED);
   }
 
   private void inspect(CodeInspector inspector) {
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInlinePropertyTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInlinePropertyTest.java
new file mode 100644
index 0000000..ffe5a51
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInlinePropertyTest.java
@@ -0,0 +1,137 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.kotlin.metadata;
+
+import static com.android.tools.r8.KotlinCompilerTool.KOTLINC;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertNull;
+
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.ToolHelper.KotlinTargetVersion;
+import com.android.tools.r8.kotlin.KotlinMetadataWriter;
+import com.android.tools.r8.shaking.ProguardKeepAttributes;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.FoundClassSubject;
+import java.io.IOException;
+import java.nio.file.Path;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.ExecutionException;
+import junit.framework.TestCase;
+import kotlinx.metadata.jvm.KotlinClassHeader;
+import kotlinx.metadata.jvm.KotlinClassMetadata;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class MetadataRewriteInlinePropertyTest extends KotlinMetadataTestBase {
+
+  private final String EXPECTED = StringUtils.lines("true", "false", "false", "true");
+  private final String PKG_LIB = PKG + ".inline_property_lib";
+
+  @Parameterized.Parameters(name = "{0} target: {1}")
+  public static Collection<Object[]> data() {
+    return buildParameters(
+        getTestParameters().withCfRuntimes().build(), KotlinTargetVersion.values());
+  }
+
+  public MetadataRewriteInlinePropertyTest(
+      TestParameters parameters, KotlinTargetVersion targetVersion) {
+    super(targetVersion);
+    this.parameters = parameters;
+  }
+
+  private static Map<KotlinTargetVersion, Path> libJars = new HashMap<>();
+  private final TestParameters parameters;
+
+  @BeforeClass
+  public static void createLibJar() throws Exception {
+    String baseLibFolder = PKG_PREFIX + "/inline_property_lib";
+    for (KotlinTargetVersion targetVersion : KotlinTargetVersion.values()) {
+      Path baseLibJar =
+          kotlinc(KOTLINC, targetVersion)
+              .addSourceFiles(getKotlinFileInTest(baseLibFolder, "lib"))
+              .compile();
+      libJars.put(targetVersion, baseLibJar);
+    }
+  }
+
+  @Test
+  public void smokeTest() throws Exception {
+    Path libJar = libJars.get(targetVersion);
+    Path output =
+        kotlinc(parameters.getRuntime().asCf(), KOTLINC, targetVersion)
+            .addClasspathFiles(libJar)
+            .addSourceFiles(getKotlinFileInTest(PKG_PREFIX + "/inline_property_app", "main"))
+            .setOutputPath(temp.newFolder().toPath())
+            .compile();
+    testForJvm()
+        .addRunClasspathFiles(ToolHelper.getKotlinStdlibJar(), libJar)
+        .addClasspath(output)
+        .run(parameters.getRuntime(), PKG + ".inline_property_app.MainKt")
+        .assertSuccessWithOutput(EXPECTED);
+  }
+
+  @Test
+  public void testMetadataForLib() throws Exception {
+    Path libJar =
+        testForR8(parameters.getBackend())
+            .addClasspathFiles(ToolHelper.getKotlinStdlibJar())
+            .addProgramFiles(libJars.get(targetVersion))
+            // Allow renaming A to ensure that we rename in the flexible upper bound type.
+            .addKeepAllClassesRule()
+            .addKeepAttributes(
+                ProguardKeepAttributes.RUNTIME_VISIBLE_ANNOTATIONS,
+                ProguardKeepAttributes.SIGNATURE,
+                ProguardKeepAttributes.INNER_CLASSES,
+                ProguardKeepAttributes.ENCLOSING_METHOD)
+            .compile()
+            .inspect(this::inspect)
+            .writeToZip();
+    Path main =
+        kotlinc(parameters.getRuntime().asCf(), KOTLINC, targetVersion)
+            .addClasspathFiles(libJar)
+            .addSourceFiles(getKotlinFileInTest(PKG_PREFIX + "/inline_property_app", "main"))
+            .setOutputPath(temp.newFolder().toPath())
+            .compile();
+    testForJvm()
+        .addRunClasspathFiles(
+            ToolHelper.getKotlinStdlibJar(), ToolHelper.getKotlinReflectJar(), libJar)
+        .addClasspath(main)
+        .run(parameters.getRuntime(), PKG + ".inline_property_app.MainKt")
+        .assertSuccessWithOutput(EXPECTED);
+  }
+
+  private void inspect(CodeInspector inspector) throws IOException, ExecutionException {
+    CodeInspector stdLibInspector = new CodeInspector(libJars.get(targetVersion));
+    for (FoundClassSubject clazzSubject : stdLibInspector.allClasses()) {
+      ClassSubject r8Clazz = inspector.clazz(clazzSubject.getOriginalName());
+      assertThat(r8Clazz, isPresent());
+      KotlinClassMetadata originalMetadata = clazzSubject.getKotlinClassMetadata();
+      KotlinClassMetadata rewrittenMetadata = r8Clazz.getKotlinClassMetadata();
+      if (originalMetadata == null) {
+        assertNull(rewrittenMetadata);
+        continue;
+      }
+      TestCase.assertNotNull(rewrittenMetadata);
+      KotlinClassHeader originalHeader = originalMetadata.getHeader();
+      KotlinClassHeader rewrittenHeader = rewrittenMetadata.getHeader();
+      TestCase.assertEquals(originalHeader.getKind(), rewrittenHeader.getKind());
+      TestCase.assertEquals(originalHeader.getPackageName(), rewrittenHeader.getPackageName());
+      // We cannot assert equality of the data since it may be ordered differently. Instead we use
+      // the KotlinMetadataWriter.
+      String expected = KotlinMetadataWriter.kotlinMetadataToString("", originalMetadata);
+      String actual = KotlinMetadataWriter.kotlinMetadataToString("", rewrittenMetadata);
+      TestCase.assertEquals(expected, actual);
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteKeepPathTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteKeepPathTest.java
new file mode 100644
index 0000000..bb61374
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteKeepPathTest.java
@@ -0,0 +1,116 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.kotlin.metadata;
+
+import static com.android.tools.r8.KotlinCompilerTool.KOTLINC;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.CoreMatchers.equalTo;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestShrinkerBuilder;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.ToolHelper.KotlinTargetVersion;
+import com.android.tools.r8.utils.BooleanUtils;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import java.nio.file.Path;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class MetadataRewriteKeepPathTest extends KotlinMetadataTestBase {
+
+  @Parameterized.Parameters(name = "{0} target: {1}, keep: {2}")
+  public static Collection<Object[]> data() {
+    return buildParameters(
+        getTestParameters().withCfRuntimes().build(),
+        KotlinTargetVersion.values(),
+        BooleanUtils.values());
+  }
+
+  private static Map<KotlinTargetVersion, Path> libJars = new HashMap<>();
+  private static final String LIB_CLASS_NAME = PKG + ".box_primitives_lib.Test";
+  private final TestParameters parameters;
+  private final boolean keepMetadata;
+
+  public MetadataRewriteKeepPathTest(
+      TestParameters parameters, KotlinTargetVersion targetVersion, boolean keepMetadata) {
+    super(targetVersion);
+    this.parameters = parameters;
+    this.keepMetadata = keepMetadata;
+  }
+
+  @BeforeClass
+  public static void createLibJar() throws Exception {
+    String baseLibFolder = PKG_PREFIX + "/box_primitives_lib";
+    for (KotlinTargetVersion targetVersion : KotlinTargetVersion.values()) {
+      Path baseLibJar =
+          kotlinc(KOTLINC, targetVersion)
+              .addSourceFiles(getKotlinFileInTest(baseLibFolder, "lib"))
+              .compile();
+      libJars.put(targetVersion, baseLibJar);
+    }
+  }
+
+  @Test
+  public void testProgramPath() throws Exception {
+    testForR8(parameters.getBackend())
+        .addProgramFiles(libJars.get(targetVersion))
+        .addProgramFiles(ToolHelper.getKotlinStdlibJar())
+        .addKeepRules("-keep class " + LIB_CLASS_NAME)
+        .applyIf(keepMetadata, TestShrinkerBuilder::addKeepKotlinMetadata)
+        .addKeepRuntimeVisibleAnnotations()
+        .allowDiagnosticWarningMessages()
+        .compile()
+        .assertAllWarningMessagesMatch(equalTo("Resource 'META-INF/MANIFEST.MF' already exists."))
+        .inspect(inspector -> inspect(inspector, keepMetadata));
+  }
+
+  @Test
+  public void testClassPathPath() throws Exception {
+    testForR8(parameters.getBackend())
+        .addProgramFiles(libJars.get(targetVersion))
+        .addClasspathFiles(ToolHelper.getKotlinStdlibJar())
+        .addKeepRules("-keep class " + LIB_CLASS_NAME)
+        .addKeepRuntimeVisibleAnnotations()
+        .compile()
+        .inspect(inspector -> inspect(inspector, true));
+  }
+
+  @Test
+  public void testLibraryPath() throws Exception {
+    testForR8(parameters.getBackend())
+        .addProgramFiles(libJars.get(targetVersion))
+        .addLibraryFiles(ToolHelper.getKotlinStdlibJar())
+        .addLibraryFiles(ToolHelper.getJava8RuntimeJar())
+        .addKeepRules("-keep class " + LIB_CLASS_NAME)
+        .addKeepRuntimeVisibleAnnotations()
+        .compile()
+        .inspect(inspector -> inspect(inspector, true));
+  }
+
+  @Test
+  public void testMissing() throws Exception {
+    testForR8(parameters.getBackend())
+        .addProgramFiles(libJars.get(targetVersion))
+        .addKeepRules("-keep class " + LIB_CLASS_NAME)
+        .addKeepRuntimeVisibleAnnotations()
+        .compile()
+        .inspect(inspector -> inspect(inspector, true));
+  }
+
+  private void inspect(CodeInspector inspector, boolean expectMetadata) {
+    ClassSubject clazz = inspector.clazz(LIB_CLASS_NAME);
+    assertThat(clazz, isPresent());
+    assertEquals(expectMetadata, clazz.getKotlinClassMetadata() != null);
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteKeepTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteKeepTest.java
new file mode 100644
index 0000000..295f26a
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteKeepTest.java
@@ -0,0 +1,117 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.kotlin.metadata;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.ToolHelper.KotlinTargetVersion;
+import com.android.tools.r8.shaking.ProguardKeepAttributes;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.FoundClassSubject;
+import java.util.Collection;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class MetadataRewriteKeepTest extends KotlinMetadataTestBase {
+
+  @Parameterized.Parameters(name = "{0} target: {1}")
+  public static Collection<Object[]> data() {
+    return buildParameters(
+        getTestParameters().withCfRuntimes().build(), KotlinTargetVersion.values());
+  }
+
+  private final TestParameters parameters;
+
+  public MetadataRewriteKeepTest(TestParameters parameters, KotlinTargetVersion targetVersion) {
+    super(targetVersion);
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    testForR8(parameters.getBackend())
+        .addProgramFiles(ToolHelper.getKotlinStdlibJar())
+        .setMinApi(parameters.getApiLevel())
+        .addKeepKotlinMetadata()
+        .addKeepRules("-keep class kotlin.io.** { *; }")
+        .addKeepAttributes(ProguardKeepAttributes.RUNTIME_VISIBLE_ANNOTATIONS)
+        .compile()
+        .inspect(this::inspect);
+  }
+
+  @Test
+  public void testR8KeepPartial() throws Exception {
+    // This test is a bit weird, since it shows that we can remove params from the kotlin.Metadata
+    // class, but still be able to fully read the kotlin.Metadata.
+    testForR8(parameters.getBackend())
+        .addProgramFiles(ToolHelper.getKotlinStdlibJar())
+        .setMinApi(parameters.getApiLevel())
+        .addKeepRules("-keep class kotlin.Metadata { *** d1(); }")
+        .addKeepAttributes(ProguardKeepAttributes.RUNTIME_VISIBLE_ANNOTATIONS)
+        .compile()
+        .inspect(
+            inspector -> {
+              inspect(inspector);
+              ClassSubject kotlinMetadataClass = inspector.clazz("kotlin.Metadata");
+              assertThat(kotlinMetadataClass, isPresent());
+              assertEquals(1, kotlinMetadataClass.allMethods().size());
+              assertNotNull(kotlinMetadataClass.getKmClass().getName());
+            });
+  }
+
+  @Test
+  public void testR8KeepPartialCooking() throws Exception {
+    // This test is a bit weird, since it shows that we can remove params from the kotlin.Metadata
+    // class, but still be able to fully read the kotlin.Metadata externally.
+    testForR8(parameters.getBackend())
+        .addProgramFiles(ToolHelper.getKotlinStdlibJar())
+        .setMinApi(parameters.getApiLevel())
+        .addKeepRules("-keep class kotlin.Metadata { *** d1(); }")
+        .addKeepAttributes(ProguardKeepAttributes.RUNTIME_VISIBLE_ANNOTATIONS)
+        .compile()
+        .inspect(
+            inspector -> {
+              inspect(inspector);
+              ClassSubject kotlinMetadataClass = inspector.clazz("kotlin.Metadata");
+              assertThat(kotlinMetadataClass, isPresent());
+              assertEquals(1, kotlinMetadataClass.allMethods().size());
+              assertNotNull(kotlinMetadataClass.getKmClass().getName());
+            });
+  }
+
+  @Test
+  public void testR8KeepIf() throws Exception {
+    testForR8(parameters.getBackend())
+        .addProgramFiles(ToolHelper.getKotlinStdlibJar())
+        .setMinApi(parameters.getApiLevel())
+        .addKeepRules("-keep class kotlin.io.** { *; }")
+        .addKeepRules("-if class * { *** $VALUES; }", "-keep class kotlin.Metadata { *; }")
+        .addKeepAttributes(ProguardKeepAttributes.RUNTIME_VISIBLE_ANNOTATIONS)
+        .compile()
+        .inspect(this::inspect);
+  }
+
+  private void inspect(CodeInspector inspector) {
+    // All kept classes should have their kotlin metadata.
+    for (FoundClassSubject clazz : inspector.allClasses()) {
+      if (clazz.getFinalName().startsWith("kotlin.io")
+          || clazz.getFinalName().equals("kotlin.Metadata")) {
+        assertNotNull(clazz.getKotlinClassMetadata());
+        assertNotNull(clazz.getKotlinClassMetadata().getHeader().getData2());
+      } else {
+        assertNull(clazz.getKotlinClassMetadata());
+      }
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewritePassThroughTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewritePassThroughTest.java
index 4826e1a..a17f7dc 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewritePassThroughTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewritePassThroughTest.java
@@ -50,6 +50,7 @@
         .addProgramFiles(ToolHelper.getKotlinStdlibJar())
         .setMinApi(parameters.getApiLevel())
         .addKeepAllClassesRule()
+        .addKeepKotlinMetadata()
         .addKeepAttributes(ProguardKeepAttributes.RUNTIME_VISIBLE_ANNOTATIONS)
         .compile()
         .inspect(this::inspect);
@@ -71,16 +72,12 @@
       KotlinClassHeader rewrittenHeader = rewrittenMetadata.getHeader();
       assertEquals(originalHeader.getKind(), rewrittenHeader.getKind());
       // TODO(b/154199572): Should we check for meta-data version?
-      // TODO(b/156290606): Check if we should assert the package names are equal.
-      // assertEquals(originalHeader.getPackageName(), rewrittenHeader.getPackageName());
+      assertEquals(originalHeader.getPackageName(), rewrittenHeader.getPackageName());
       // We cannot assert equality of the data since it may be ordered differently. Instead we use
       // the KotlinMetadataWriter.
       String expected = KotlinMetadataWriter.kotlinMetadataToString("", originalMetadata);
       String actual = KotlinMetadataWriter.kotlinMetadataToString("", rewrittenMetadata);
-      // TODO(b/155534905): For invalid synthetic class lambdas, we emit null after rewriting.
-      if (clazzSubject.getKotlinClassMetadata().getHeader().getKind() != 3) {
-        assertEquals(expected, actual);
-      }
+      assertEquals(expected, actual);
     }
   }
 }
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataStripTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataStripTest.java
index aa0fcf8..043a48a 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataStripTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataStripTest.java
@@ -49,10 +49,8 @@
             .addProgramFiles(getJavaJarFile(folder))
             .addProgramFiles(ToolHelper.getKotlinReflectJar())
             .addKeepMainRule(mainClassName)
+            .addKeepKotlinMetadata()
             .addKeepAttributes(ProguardKeepAttributes.RUNTIME_VISIBLE_ANNOTATIONS)
-            .addKeepRules("-keep class kotlin.Metadata")
-            // TODO(b/151194540): if this option is settled down, this test is meaningless.
-            .addOptionsModification(o -> o.enableKotlinMetadataRewritingForRenamedClasses = false)
             .allowDiagnosticWarningMessages()
             .setMinApi(parameters.getApiLevel())
             .compile()
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/anonymous_app/main.kt b/src/test/java/com/android/tools/r8/kotlin/metadata/anonymous_app/main.kt
new file mode 100644
index 0000000..14cd0b1
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/anonymous_app/main.kt
@@ -0,0 +1,11 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.kotlin.metadata.anonymous_app
+
+import com.android.tools.r8.kotlin.metadata.anonymous_lib.Test
+
+fun main() {
+  println(Test().prop.foo())
+}
\ No newline at end of file
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/anonymous_lib/lib.kt b/src/test/java/com/android/tools/r8/kotlin/metadata/anonymous_lib/lib.kt
new file mode 100644
index 0000000..70d6668
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/anonymous_lib/lib.kt
@@ -0,0 +1,20 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.kotlin.metadata.anonymous_lib
+
+class Test {
+
+  abstract class A {
+    abstract fun foo() : String;
+  }
+
+  private val internalProp = object : A() {
+    override fun foo(): String {
+      return "foo";
+    }
+  }
+
+  val prop = internalProp
+}
\ No newline at end of file
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/flexible_upper_bound_app/main.kt b/src/test/java/com/android/tools/r8/kotlin/metadata/flexible_upper_bound_app/main.kt
new file mode 100644
index 0000000..9c56878
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/flexible_upper_bound_app/main.kt
@@ -0,0 +1,14 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.kotlin.metadata.flexible_upper_bound_app
+
+import com.android.tools.r8.kotlin.metadata.flexible_upper_bound_lib.B
+import com.android.tools.r8.kotlin.metadata.flexible_upper_bound_lib.FlexibleUpperBound
+
+fun main() {
+  val flexible = FlexibleUpperBound(B())
+  flexible.ref.get().foo(42)
+  flexible.ref.set(null)
+}
\ No newline at end of file
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/flexible_upper_bound_lib/lib.kt b/src/test/java/com/android/tools/r8/kotlin/metadata/flexible_upper_bound_lib/lib.kt
new file mode 100644
index 0000000..9dbb382
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/flexible_upper_bound_lib/lib.kt
@@ -0,0 +1,23 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.kotlin.metadata.flexible_upper_bound_lib
+
+open class A<T> {
+
+  open fun foo(t : T) {
+    println("A.foo(): " + t.toString())
+  }
+}
+
+class B : A<Int>() {
+
+  override fun foo(t : Int) {
+    println("B.foo(): " + t)
+  }
+}
+
+class FlexibleUpperBound<T> constructor(element: A<T>) {
+  var ref = java.util.concurrent.atomic.AtomicReference(element)
+}
\ No newline at end of file
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/inline_property_app/main.kt b/src/test/java/com/android/tools/r8/kotlin/metadata/inline_property_app/main.kt
new file mode 100644
index 0000000..3f7acc6
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/inline_property_app/main.kt
@@ -0,0 +1,15 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.kotlin.metadata.inline_property_app
+
+import com.android.tools.r8.kotlin.metadata.inline_property_lib.Lib
+import com.android.tools.r8.kotlin.metadata.inline_property_lib.is7
+
+fun main() {
+  println(Lib(42).is42)
+  println(Lib(42).is7)
+  println(Lib(7).is42)
+  println(Lib(7).is7)
+}
\ No newline at end of file
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/inline_property_lib/lib.kt b/src/test/java/com/android/tools/r8/kotlin/metadata/inline_property_lib/lib.kt
new file mode 100644
index 0000000..0636795
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/inline_property_lib/lib.kt
@@ -0,0 +1,15 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.kotlin.metadata.inline_property_lib
+
+class Lib(val number : Int) {
+
+  val is42
+    inline get() = number == 42;
+}
+
+class LibExt
+  val Lib.is7
+    inline get() = number == 7;
\ No newline at end of file
diff --git a/src/test/java/com/android/tools/r8/kotlin/sealed/SealedClassTest.java b/src/test/java/com/android/tools/r8/kotlin/sealed/SealedClassTest.java
index 18badf8..5731086 100644
--- a/src/test/java/com/android/tools/r8/kotlin/sealed/SealedClassTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/sealed/SealedClassTest.java
@@ -4,6 +4,7 @@
 
 package com.android.tools.r8.kotlin.sealed;
 
+import static com.android.tools.r8.DiagnosticsMatcher.diagnosticMessage;
 import static com.android.tools.r8.KotlinCompilerTool.KOTLINC;
 import static com.android.tools.r8.ToolHelper.getFilesInTestFolderRelativeToClass;
 import static org.hamcrest.CoreMatchers.containsString;
@@ -74,10 +75,10 @@
         .allowDiagnosticWarningMessages(parameters.isCfRuntime())
         .addKeepMainRule(MAIN)
         .compileWithExpectedDiagnostics(
-            diagnosticMessages -> {
-              diagnosticMessages.assertAllWarningMessagesMatch(
-                  containsString("Resource 'META-INF/MANIFEST.MF' already exists."));
-            })
+            diagnostics ->
+                diagnostics.assertAllWarningsMatch(
+                    diagnosticMessage(
+                        containsString("Resource 'META-INF/MANIFEST.MF' already exists."))))
         .run(parameters.getRuntime(), MAIN)
         .assertSuccessWithOutputLines(EXPECTED);
   }
diff --git a/src/test/java/com/android/tools/r8/naming/ClassNameMinifierOriginalClassNameTest.java b/src/test/java/com/android/tools/r8/naming/ClassNameMinifierOriginalClassNameTest.java
index 9d4c4fb..553fe0c 100644
--- a/src/test/java/com/android/tools/r8/naming/ClassNameMinifierOriginalClassNameTest.java
+++ b/src/test/java/com/android/tools/r8/naming/ClassNameMinifierOriginalClassNameTest.java
@@ -4,6 +4,7 @@
 
 package com.android.tools.r8.naming;
 
+import static com.android.tools.r8.DiagnosticsMatcher.diagnosticMessage;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isRenamed;
 import static org.hamcrest.CoreMatchers.containsString;
 import static org.hamcrest.MatcherAssert.assertThat;
@@ -98,14 +99,15 @@
                 .addApplyMapping(libraryCompileResult.getProguardMap())
                 .allowDiagnosticWarningMessages()
                 .compileWithExpectedDiagnostics(
-                    diagnosticMessages ->
-                        diagnosticMessages.assertAllWarningMessagesMatch(
-                            containsString(
-                                "'"
-                                    + B.class.getTypeName()
-                                    + "' cannot be mapped to '"
-                                    + A.class.getTypeName()
-                                    + "' because it is in conflict"))));
+                    diagnostics ->
+                        diagnostics.assertAllWarningsMatch(
+                            diagnosticMessage(
+                                containsString(
+                                    "'"
+                                        + B.class.getTypeName()
+                                        + "' cannot be mapped to '"
+                                        + A.class.getTypeName()
+                                        + "' because it is in conflict")))));
   }
 
   public static class Main {
diff --git a/src/test/java/com/android/tools/r8/naming/DontUseMixedCaseClassNamesExistingClassTest.java b/src/test/java/com/android/tools/r8/naming/DontUseMixedCaseClassNamesExistingClassTest.java
index b16347d..6804b54 100644
--- a/src/test/java/com/android/tools/r8/naming/DontUseMixedCaseClassNamesExistingClassTest.java
+++ b/src/test/java/com/android/tools/r8/naming/DontUseMixedCaseClassNamesExistingClassTest.java
@@ -51,7 +51,7 @@
         .addKeepClassRulesWithAllowObfuscation(A.class)
         .addKeepMainRule(Main.class)
         .addKeepRules("-classobfuscationdictionary " + dictionary.toString())
-        .ifTrue(dontUseMixedCase, b -> b.addKeepRules("-dontusemixedcaseclassnames"))
+        .applyIf(dontUseMixedCase, b -> b.addKeepRules("-dontusemixedcaseclassnames"))
         .run(parameters.getRuntime(), Main.class)
         .assertSuccessWithOutputLines(EXPECTED)
         .inspect(
diff --git a/src/test/java/com/android/tools/r8/naming/EnumMinificationKotlinTest.java b/src/test/java/com/android/tools/r8/naming/EnumMinificationKotlinTest.java
index 39cf142..73d9085 100644
--- a/src/test/java/com/android/tools/r8/naming/EnumMinificationKotlinTest.java
+++ b/src/test/java/com/android/tools/r8/naming/EnumMinificationKotlinTest.java
@@ -5,8 +5,8 @@
 
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
 import static org.hamcrest.CoreMatchers.equalTo;
-import static org.hamcrest.CoreMatchers.not;
 import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertEquals;
 
 import com.android.tools.r8.KotlinTestBase;
 import com.android.tools.r8.TestParameters;
@@ -14,6 +14,9 @@
 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.MethodSubject;
+import com.google.common.collect.Streams;
 import java.util.Collection;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -50,6 +53,7 @@
             .addProgramFiles(getKotlinJarFile(FOLDER))
             .addProgramFiles(getJavaJarFile(FOLDER))
             .addKeepMainRule(MAIN_CLASS_NAME)
+            .addKeepClassRulesWithAllowObfuscation(ENUM_CLASS_NAME)
             .allowDiagnosticWarningMessages()
             .minification(minify)
             .setMinApi(parameters.getApiLevel())
@@ -59,7 +63,11 @@
             .run(parameters.getRuntime(), MAIN_CLASS_NAME)
             .inspector();
     ClassSubject enumClass = inspector.clazz(ENUM_CLASS_NAME);
-    // TODO(b/156340144): Check why the ENUM_CLASS_NAME is not present.
-    assertThat(enumClass, not(isPresent()));
+    assertThat(enumClass, isPresent());
+    assertEquals(minify, enumClass.isRenamed());
+    MethodSubject clinit = enumClass.clinit();
+    assertThat(clinit, isPresent());
+    assertEquals(
+        0, Streams.stream(clinit.iterateInstructions(InstructionSubject::isThrow)).count());
   }
 }
diff --git a/src/test/java/com/android/tools/r8/naming/b155249069/DontUseMixedCaseClassNamesExistingClassPackageTest.java b/src/test/java/com/android/tools/r8/naming/b155249069/DontUseMixedCaseClassNamesExistingClassPackageTest.java
index a55ef4b..f662aa6 100644
--- a/src/test/java/com/android/tools/r8/naming/b155249069/DontUseMixedCaseClassNamesExistingClassPackageTest.java
+++ b/src/test/java/com/android/tools/r8/naming/b155249069/DontUseMixedCaseClassNamesExistingClassPackageTest.java
@@ -54,7 +54,7 @@
         .addKeepClassRulesWithAllowObfuscation(A.class)
         .addKeepMainRule(Main.class)
         .addKeepRules("-packageobfuscationdictionary " + packageDictionary.toString())
-        .ifTrue(dontUseMixedCase, b -> b.addKeepRules("-dontusemixedcaseclassnames"))
+        .applyIf(dontUseMixedCase, b -> b.addKeepRules("-dontusemixedcaseclassnames"))
         .run(parameters.getRuntime(), Main.class)
         .assertSuccessWithOutputLines("A.A.foo()", "package_b.B.foo()")
         .inspect(
diff --git a/src/test/java/com/android/tools/r8/naming/b72391662/B72391662.java b/src/test/java/com/android/tools/r8/naming/b72391662/B72391662.java
index 7509d3d..a29c664 100644
--- a/src/test/java/com/android/tools/r8/naming/b72391662/B72391662.java
+++ b/src/test/java/com/android/tools/r8/naming/b72391662/B72391662.java
@@ -161,7 +161,7 @@
   @Test
   public void test_keepNonPublic() throws Exception {
     Assume.assumeFalse(shrinker.generatesDex() && vmVersionIgnored());
-    Class mainClass = TestMain.class;
+    Class<?> mainClass = TestMain.class;
     String keep = !minify ? "-keep" : "-keep,allowobfuscation";
     List<String> config = ImmutableList.of(
         "-printmapping",
diff --git a/src/test/java/com/android/tools/r8/proguard/rules/NegatedClassMemberTest.java b/src/test/java/com/android/tools/r8/proguard/rules/NegatedClassMemberTest.java
index 520e1d8..8e0213a 100644
--- a/src/test/java/com/android/tools/r8/proguard/rules/NegatedClassMemberTest.java
+++ b/src/test/java/com/android/tools/r8/proguard/rules/NegatedClassMemberTest.java
@@ -4,6 +4,7 @@
 
 package com.android.tools.r8.proguard.rules;
 
+import static com.android.tools.r8.DiagnosticsMatcher.diagnosticMessage;
 import static org.hamcrest.CoreMatchers.allOf;
 import static org.hamcrest.CoreMatchers.containsString;
 import static org.hamcrest.MatcherAssert.assertThat;
@@ -11,6 +12,7 @@
 
 import com.android.tools.r8.CompilationFailedException;
 import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestDiagnosticMessages;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.utils.StringUtils;
@@ -21,15 +23,13 @@
 @RunWith(Parameterized.class)
 public class NegatedClassMemberTest extends TestBase {
 
-  private final TestParameters parameters;
-
   @Parameterized.Parameters(name = "{0}")
   public static TestParametersCollection data() {
     return getTestParameters().withNoneRuntime().build();
   }
 
   public NegatedClassMemberTest(TestParameters parameters) {
-    this.parameters = parameters;
+
   }
 
   @Test
@@ -41,7 +41,6 @@
               NegatedClassMemberTestClassB.class,
               NegatedClassMemberTestClassC.class)
           .addKeepRules(getKeepRule())
-          .setMinApi(parameters.getRuntime())
           .compile();
 
       // For some reason, Proguard fails with "The output jar is empty". One likely explanation is
@@ -62,27 +61,30 @@
               NegatedClassMemberTestClassB.class,
               NegatedClassMemberTestClassC.class)
           .addKeepRules(getKeepRule())
-          .compile();
-      fail("Expected R8 to fail during parsing of the Proguard configuration file");
+          .compileWithExpectedDiagnostics(this::checkDiagnostics);
     } catch (CompilationFailedException e) {
-      int expectedOffset = getKeepRule().indexOf("!");
-      int expectedColumn = expectedOffset + 1;
-      assertThat(
-          e.getCause().getMessage(),
-          allOf(
-              containsString(
-                  "Error: offset: "
-                      + expectedOffset
-                      + ", line: 1, column: "
-                      + expectedColumn
-                      + ", Unexpected character '!': "
-                      + "The negation character can only be used to negate access flags"),
-              containsString(
-                  StringUtils.join(
-                      "\n",
-                      "-keepclasseswithmembers class ** { long x; !long y; }",
-                      "                                           ^"))));
+      return;
     }
+    fail("Expected R8 to fail during parsing of the Proguard configuration file");
+  }
+
+  private void checkDiagnostics(TestDiagnosticMessages diagnostics) {
+    int expectedOffset = getKeepRule().indexOf("!");
+    int expectedColumn = expectedOffset + 1;
+    diagnostics
+        .assertOnlyErrors()
+        .assertErrorsMatch(
+            diagnosticMessage(
+                allOf(
+                    containsString(":1:" + expectedColumn),
+                    containsString(
+                        "Unexpected character '!': "
+                            + "The negation character can only be used to negate access flags"),
+                    containsString(
+                        StringUtils.join(
+                            "\n",
+                            "-keepclasseswithmembers class ** { long x; !long y; }",
+                            "                                           ^")))));
   }
 
   private String getKeepRule() {
diff --git a/src/test/java/com/android/tools/r8/reachabilitysensitive/ReachabilitySensitiveTest.java b/src/test/java/com/android/tools/r8/reachabilitysensitive/ReachabilitySensitiveTest.java
index 4c2fedc..5d06942 100644
--- a/src/test/java/com/android/tools/r8/reachabilitysensitive/ReachabilitySensitiveTest.java
+++ b/src/test/java/com/android/tools/r8/reachabilitysensitive/ReachabilitySensitiveTest.java
@@ -11,6 +11,7 @@
 import com.android.tools.r8.CompilationFailedException;
 import com.android.tools.r8.CompilationMode;
 import com.android.tools.r8.TestBase;
+import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.code.AddIntLit8;
 import com.android.tools.r8.code.Const4;
 import com.android.tools.r8.code.Instruction;
@@ -25,6 +26,7 @@
 import java.util.List;
 import java.util.concurrent.ExecutionException;
 import java.util.stream.Collectors;
+import org.junit.Assume;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
@@ -87,6 +89,9 @@
         inspector.method(TestClass.class.getMethod("method")).getMethod().getCode().asDexCode();
     // Computation of k is constant folded and the value takes up one register. System.out takes
     // up another register and the receiver is the last.
+    Assume.assumeTrue(
+        "TODO(b/144966342): Why 2 on Q?",
+        ToolHelper.getDexVm().isOlderThanOrEqual(ToolHelper.DexVm.ART_9_0_0_HOST));
     assertEquals(3, code.registerSize);
     checkNoLocals(code);
   }
diff --git a/src/test/java/com/android/tools/r8/regress/b120164595/B120164595.java b/src/test/java/com/android/tools/r8/regress/b120164595/B120164595.java
index 9d633f2..7e56259 100644
--- a/src/test/java/com/android/tools/r8/regress/b120164595/B120164595.java
+++ b/src/test/java/com/android/tools/r8/regress/b120164595/B120164595.java
@@ -62,14 +62,14 @@
   }
 
   private void checkArt(TestCompileResult result) throws IOException {
-    ProcessResult artResult = runOnArtRaw(
-        result.app,
-        TestClass.class.getCanonicalName(),
-        builder -> {
-          builder.appendArtOption("-Xusejit:true");
-        },
-        DexVm.ART_9_0_0_HOST
-    );
+    ProcessResult artResult =
+        runOnArtRaw(
+            result.app,
+            TestClass.class.getCanonicalName(),
+            builder -> {
+              builder.appendArtOption("-Xusejit:true");
+            },
+            DexVm.ART_10_0_0_HOST);
     assertEquals(0, artResult.exitCode);
     assertFalse(artResult.stderr.contains("Expected NullPointerException"));
   }
diff --git a/src/test/java/com/android/tools/r8/regress/b152973695/CompileToInvalidFileTest.java b/src/test/java/com/android/tools/r8/regress/b152973695/CompileToInvalidFileTest.java
index 4f617ac..c96cbda 100644
--- a/src/test/java/com/android/tools/r8/regress/b152973695/CompileToInvalidFileTest.java
+++ b/src/test/java/com/android/tools/r8/regress/b152973695/CompileToInvalidFileTest.java
@@ -4,7 +4,9 @@
 
 package com.android.tools.r8.regress.b152973695;
 
-import static org.junit.Assert.assertTrue;
+import static com.android.tools.r8.DiagnosticsMatcher.diagnosticMessage;
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.hamcrest.CoreMatchers.equalTo;
 import static org.junit.Assert.fail;
 
 import com.android.tools.r8.ClassFileConsumer;
@@ -12,6 +14,7 @@
 import com.android.tools.r8.DexIndexedConsumer.ArchiveConsumer;
 import com.android.tools.r8.ProgramConsumer;
 import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestDiagnosticMessages;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.utils.BooleanUtils;
 import java.io.IOException;
@@ -48,12 +51,13 @@
             ? new ClassFileConsumer.ArchiveConsumer(INVALID_FILE)
             : new ArchiveConsumer(INVALID_FILE);
     try {
-      testForD8().addProgramClasses(Main.class).setProgramConsumer(programConsumer).compile();
+      testForD8()
+          .addProgramClasses(Main.class)
+          .setProgramConsumer(programConsumer)
+          .compileWithExpectedDiagnostics(diagnostics -> checkDiagnostics(diagnostics, true));
       fail("Expected a CompilationFailedException but the code succeeded");
     } catch (CompilationFailedException ex) {
-      assertInvalidFileNotFound(ex);
-    } catch (Throwable t) {
-      fail("Expected a CompilationFailedException but got instead " + t);
+      // Expected.
     }
   }
 
@@ -69,18 +73,22 @@
           .addProgramClasses(Main.class)
           .addKeepMainRule(Main.class)
           .setProgramConsumer(programConsumer)
-          .compile();
+          .compileWithExpectedDiagnostics(diagnostics -> checkDiagnostics(diagnostics, false));
       fail("Expected a CompilationFailedException but the code succeeded");
     } catch (CompilationFailedException ex) {
-      assertInvalidFileNotFound(ex);
-    } catch (Throwable t) {
-      fail("Expected a CompilationFailedException but got instead " + t);
+      // Expected.
     }
   }
 
-  private void assertInvalidFileNotFound(CompilationFailedException ex) {
-    assertTrue(ex.getCause().getMessage().contains("File not found"));
-    assertTrue(ex.getCause().getMessage().contains(INVALID_FILE.toString()));
+  private void checkDiagnostics(TestDiagnosticMessages diagnostics, boolean isD8) {
+    if (classFileConsumer && isD8) {
+      diagnostics.assertWarningsMatch(
+          diagnosticMessage(
+              equalTo("Compiling to Java class files with D8 is not officially supported")));
+    } else {
+      diagnostics.assertOnlyErrors();
+    }
+    diagnostics.assertAllErrorsMatch(diagnosticMessage(containsString(INVALID_FILE.toString())));
   }
 
   private void ensureInvalidFileIsInvalid() {
diff --git a/src/test/java/com/android/tools/r8/relocator/RelocatorTest.java b/src/test/java/com/android/tools/r8/relocator/RelocatorTest.java
index 62d8695..e891067 100644
--- a/src/test/java/com/android/tools/r8/relocator/RelocatorTest.java
+++ b/src/test/java/com/android/tools/r8/relocator/RelocatorTest.java
@@ -17,7 +17,6 @@
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.ToolHelper.ProcessResult;
-import com.android.tools.r8.errors.CompilationError;
 import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.references.Reference;
 import com.android.tools.r8.utils.BooleanUtils;
@@ -78,9 +77,9 @@
     runRelocator(ToolHelper.R8_WITH_DEPS_JAR, mapping, output);
     // TODO(b/155618698): Extend relocator with a richer language such that java.lang.Object is not
     //   relocated.
-    CompilationError compilationError =
+    RuntimeException compilationError =
         assertThrows(
-            CompilationError.class,
+            RuntimeException.class,
             () ->
                 inspectAllClassesRelocated(
                     ToolHelper.R8_WITH_DEPS_JAR, output, originalPrefix, newPrefix + "."));
diff --git a/src/test/java/com/android/tools/r8/rewrite/assertions/AssertionConfigurationKotlinTest.java b/src/test/java/com/android/tools/r8/rewrite/assertions/AssertionConfigurationKotlinTest.java
index 873788c..a0049f4 100644
--- a/src/test/java/com/android/tools/r8/rewrite/assertions/AssertionConfigurationKotlinTest.java
+++ b/src/test/java/com/android/tools/r8/rewrite/assertions/AssertionConfigurationKotlinTest.java
@@ -183,38 +183,27 @@
       boolean enableJvmAssertions)
       throws Exception {
 
-    if (kotlinStdlibAsLibrary) {
-      testForR8(parameters.getBackend())
-          .addClasspathFiles(ToolHelper.getKotlinStdlibJar())
-          .addProgramFiles(kotlinClasses.get(kotlinCompilationKey))
-          .addKeepMainRule(testClassKt)
-          .addKeepClassAndMembersRules(class1, class2)
-          .setMinApi(parameters.getApiLevel())
-          .apply(builderConsumer)
-          .noMinification()
-          .addRunClasspathFiles(kotlinStdlibLibraryForRuntime())
-          .compile()
-          .enableRuntimeAssertions(enableJvmAssertions)
-          .run(parameters.getRuntime(), testClassKt)
-          .inspect(inspector)
-          .assertSuccessWithOutputLines(outputLines);
-    } else {
-      testForR8(parameters.getBackend())
-          .addProgramFiles(ToolHelper.getKotlinStdlibJar())
-          .addProgramFiles(kotlinClasses.get(kotlinCompilationKey))
-          .addKeepMainRule(testClassKt)
-          .addKeepClassAndMembersRules(class1, class2)
-          .setMinApi(parameters.getApiLevel())
-          .apply(builderConsumer)
-          .noMinification()
-          .allowDiagnosticWarningMessages()
-          .compile()
-          .assertAllWarningMessagesMatch(equalTo("Resource 'META-INF/MANIFEST.MF' already exists."))
-          .enableRuntimeAssertions(enableJvmAssertions)
-          .run(parameters.getRuntime(), testClassKt)
-          .inspect(inspector)
-          .assertSuccessWithOutputLines(outputLines);
-    }
+    testForR8(parameters.getBackend())
+        .applyIf(
+            kotlinStdlibAsLibrary,
+            b -> {
+              b.addClasspathFiles(ToolHelper.getKotlinStdlibJar());
+              b.addRunClasspathFiles(kotlinStdlibLibraryForRuntime());
+            },
+            b -> b.addProgramFiles(ToolHelper.getKotlinStdlibJar()))
+        .addProgramFiles(kotlinClasses.get(kotlinCompilationKey))
+        .addKeepMainRule(testClassKt)
+        .addKeepClassAndMembersRules(class1, class2)
+        .setMinApi(parameters.getApiLevel())
+        .apply(builderConsumer)
+        .allowDiagnosticWarningMessages(!kotlinStdlibAsLibrary)
+        .addRunClasspathFiles(kotlinStdlibLibraryForRuntime())
+        .compile()
+        .assertAllWarningMessagesMatch(equalTo("Resource 'META-INF/MANIFEST.MF' already exists."))
+        .enableRuntimeAssertions(enableJvmAssertions)
+        .run(parameters.getRuntime(), testClassKt)
+        .inspect(inspector)
+        .assertSuccessWithOutputLines(outputLines);
   }
 
   private List<String> allAssertionsExpectedLines() {
@@ -484,7 +473,30 @@
   }
 
   @Test
-  public void testAssertionsForCf() throws Exception {
+  public void testAssertionsForCfEnableWithStackMap() throws Exception {
+    Assume.assumeTrue(parameters.isCfRuntime());
+    Assume.assumeTrue(useJvmAssertions);
+    Assume.assumeTrue(kotlinCompilationKey.targetVersion == KotlinTargetVersion.JAVA_8);
+    // Compile time enabling or disabling assertions means the -ea flag has no effect.
+    runR8Test(
+        builder -> {
+          builder.addAssertionsConfiguration(AssertionsConfiguration.Builder::enableAllAssertions);
+          builder.addOptionsModification(options -> options.testing.readInputStackMaps = true);
+        },
+        inspector -> checkAssertionCodeEnabled(inspector, true),
+        allAssertionsExpectedLines());
+    runR8Test(
+        builder -> {
+          builder.addAssertionsConfiguration(AssertionsConfiguration.Builder::enableAllAssertions);
+          builder.addOptionsModification(options -> options.testing.readInputStackMaps = true);
+        },
+        inspector -> checkAssertionCodeEnabled(inspector, true),
+        allAssertionsExpectedLines(),
+        true);
+  }
+
+  @Test
+  public void testAssertionsForCfPassThrough() throws Exception {
     Assume.assumeTrue(parameters.isCfRuntime());
     // Leaving assertion code means assertions are controlled by the -ea flag.
     runR8Test(
@@ -500,6 +512,11 @@
         inspector -> checkAssertionCodeLeft(inspector, true),
         allAssertionsExpectedLines(),
         true);
+  }
+
+  @Test
+  public void testAssertionsForCfEnable() throws Exception {
+    Assume.assumeTrue(parameters.isCfRuntime());
     // Compile time enabling or disabling assertions means the -ea flag has no effect.
     runR8Test(
         builder ->
@@ -514,6 +531,11 @@
         inspector -> checkAssertionCodeEnabled(inspector, true),
         allAssertionsExpectedLines(),
         true);
+  }
+
+  @Test
+  public void testAssertionsForCfDisable() throws Exception {
+    Assume.assumeTrue(parameters.isCfRuntime());
     runR8Test(
         builder ->
             builder.addAssertionsConfiguration(
diff --git a/src/test/java/com/android/tools/r8/rewrite/assertions/RemoveAssertionsTest.java b/src/test/java/com/android/tools/r8/rewrite/assertions/RemoveAssertionsTest.java
index bbcc8e2..239697e 100644
--- a/src/test/java/com/android/tools/r8/rewrite/assertions/RemoveAssertionsTest.java
+++ b/src/test/java/com/android/tools/r8/rewrite/assertions/RemoveAssertionsTest.java
@@ -4,6 +4,7 @@
 
 package com.android.tools.r8.rewrite.assertions;
 
+import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assume.assumeTrue;
 
@@ -249,10 +250,9 @@
     String main = ClassWithAssertions.class.getCanonicalName();
     // When running on the JVM enable assertions. For Art this is not possible, and assertions
     // can only be activated at compile time.
-    assert parameters.getRuntime().isCf();
+    assertTrue(parameters.isCfRuntime());
     result.enableRuntimeAssertions();
     result
-        .disassemble()
         .run(parameters.getRuntime(), main, "0")
         .assertFailureWithOutput(StringUtils.lines("1"));
     // Assertion is not hit.
@@ -304,10 +304,10 @@
     assertTrue(clazz.isPresent());
     MethodSubject conditionMethod =
         clazz.method(new MethodSignature("condition", "boolean", new String[]{}));
-    assertTrue(!conditionMethod.isPresent());
+    assertFalse(conditionMethod.isPresent());
     MethodSubject clinit =
         clazz.method(new MethodSignature(Constants.CLASS_INITIALIZER_NAME, "void", new String[]{}));
-    assertTrue(!clinit.isPresent());
+    assertFalse(clinit.isPresent());
   }
 
   @Test
diff --git a/src/test/java/com/android/tools/r8/shaking/InvalidTypesTest.java b/src/test/java/com/android/tools/r8/shaking/InvalidTypesTest.java
index 5b119ce..2e123d7 100644
--- a/src/test/java/com/android/tools/r8/shaking/InvalidTypesTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/InvalidTypesTest.java
@@ -22,12 +22,14 @@
 import com.android.tools.r8.jasmin.JasminBuilder;
 import com.android.tools.r8.jasmin.JasminBuilder.ClassBuilder;
 import com.android.tools.r8.jasmin.JasminTestBase;
+import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.StringUtils;
 import com.google.common.collect.ImmutableList;
 import java.nio.file.Path;
 import java.util.Collection;
 import org.hamcrest.Matcher;
+import org.junit.Assume;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
@@ -252,6 +254,14 @@
   }
 
   private void checkTestRunResult(TestRunResult<?> result, Compiler compiler) {
+    Assume.assumeFalse(
+        "Triage (b/144966342)",
+        parameters.getRuntime().isDex()
+            && parameters
+                .getRuntime()
+                .asDex()
+                .getMinApiLevel()
+                .isGreaterThanOrEqualTo(AndroidApiLevel.Q));
     switch (mode) {
       case NO_INVOKE:
         result.assertSuccessWithOutput(getExpectedOutput(compiler));
diff --git a/src/test/java/com/android/tools/r8/shaking/ProguardConfigurationParserTest.java b/src/test/java/com/android/tools/r8/shaking/ProguardConfigurationParserTest.java
index a06dea1..3b39b815 100644
--- a/src/test/java/com/android/tools/r8/shaking/ProguardConfigurationParserTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/ProguardConfigurationParserTest.java
@@ -78,6 +78,8 @@
       VALID_PROGUARD_DIR + "assume-no-side-effects-with-return-value.flags";
   private static final String ASSUME_VALUES_WITH_RETURN_VALUE =
       VALID_PROGUARD_DIR + "assume-values-with-return-value.flags";
+  private static final String ADAPT_KOTLIN_METADATA =
+      VALID_PROGUARD_DIR + "adapt-kotlin-metadata.flags";
   private static final String INCLUDING =
       VALID_PROGUARD_DIR + "including.flags";
   private static final String INVALID_INCLUDING_1 =
@@ -770,6 +772,15 @@
   }
 
   @Test
+  public void parseAdaptKotlinMetadata() {
+    ProguardConfigurationParser parser =
+        new ProguardConfigurationParser(new DexItemFactory(), reporter);
+    Path path = Paths.get(ADAPT_KOTLIN_METADATA);
+    parser.parse(path);
+    checkDiagnostics(handler.infos, path, 1, 1, "Ignoring", "-adaptkotlinmetadata");
+  }
+
+  @Test
   public void parseIncluding() throws Exception {
     ProguardConfigurationParser parser =
         new ProguardConfigurationParser(new DexItemFactory(), reporter);
@@ -831,14 +842,30 @@
     for (String before : whiteSpace) {
       for (String after : whiteSpace) {
         reset();
-        parseAndVerifyParserEndsCleanly(ImmutableList.of(
-            "-keep"
-                + before + "," + after + "includedescriptorclasses"
-                + before + "," + after + "allowshrinking"
-                + before + "," + after + "allowobfuscation"
-                + before + "," + after + "allowoptimization "
-                + "class A { *; }"
-        ));
+        parseAndVerifyParserEndsCleanly(
+            ImmutableList.of(
+                "-keep"
+                    + before
+                    + ","
+                    + after
+                    + "includedescriptorclasses"
+                    + before
+                    + ","
+                    + after
+                    + "allowaccessmodification"
+                    + before
+                    + ","
+                    + after
+                    + "allowshrinking"
+                    + before
+                    + ","
+                    + after
+                    + "allowobfuscation"
+                    + before
+                    + ","
+                    + after
+                    + "allowoptimization "
+                    + "class A { *; }"));
       }
     }
   }
diff --git a/src/test/java/com/android/tools/r8/shaking/ReturnTypeTest.java b/src/test/java/com/android/tools/r8/shaking/ReturnTypeTest.java
index cc02426..4c25b86 100644
--- a/src/test/java/com/android/tools/r8/shaking/ReturnTypeTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/ReturnTypeTest.java
@@ -53,7 +53,7 @@
 
   @Parameterized.Parameters(name = "{0}")
   public static TestParametersCollection data() {
-    return getTestParameters().withAllRuntimes().build();
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
   }
 
   private final TestParameters parameters;
@@ -77,20 +77,23 @@
         .addProgramClasses(
             B112517039ReturnType.class, B112517039I.class, B112517039Caller.class, MAIN)
         .addKeepMainRule(MAIN)
-        .setMinApi(parameters.getRuntime())
-        .addOptionsModification(o -> {
-          // No actual implementation of B112517039I, rather invoked with `null`.
-          // Call site optimization propagation will conclude that the input of B...Caller#call is
-          // always null, and replace the last call with null-throwing instruction.
-          // However, we want to test return type and parameter type are kept in this scenario.
-          o.enablePropagationOfDynamicTypesAtCallSites = false;
-          o.enableInlining = false;
-        })
+        .setMinApi(parameters.getApiLevel())
+        .addOptionsModification(
+            o -> {
+              // No actual implementation of B112517039I, rather invoked with `null`.
+              // Call site optimization propagation will conclude that the input of B...Caller#call
+              // is
+              // always null, and replace the last call with null-throwing instruction.
+              // However, we want to test return type and parameter type are kept in this scenario.
+              o.callSiteOptimizationOptions().disableTypePropagationForTesting();
+              o.enableInlining = false;
+            })
         .run(parameters.getRuntime(), MAIN)
         .assertSuccessWithOutput(JAVA_OUTPUT)
-        .inspect(inspector -> {
-          ClassSubject returnType = inspector.clazz(B112517039ReturnType.class);
-          assertThat(returnType, isRenamed());
-        });
+        .inspect(
+            inspector -> {
+              ClassSubject returnType = inspector.clazz(B112517039ReturnType.class);
+              assertThat(returnType, isRenamed());
+            });
   }
 }
diff --git a/src/test/java/com/android/tools/r8/shaking/b134858535/EventPublisherTest.java b/src/test/java/com/android/tools/r8/shaking/b134858535/EventPublisherTest.java
index 6d0d767..d4801ea 100644
--- a/src/test/java/com/android/tools/r8/shaking/b134858535/EventPublisherTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/b134858535/EventPublisherTest.java
@@ -20,6 +20,7 @@
 
   @Test
   public void testPrivateMethodsInLambdaClass() throws CompilationFailedException {
+    // This test only tests if the dump can be compiled without errors.
     testForR8(Backend.DEX)
         .addProgramClasses(Main.class, Interface.class)
         .addProgramClassFileData(EventPublisher$bDump.dump())
diff --git a/src/test/java/com/android/tools/r8/smali/SmaliTestBase.java b/src/test/java/com/android/tools/r8/smali/SmaliTestBase.java
index 8ef02dc..efcc42f 100644
--- a/src/test/java/com/android/tools/r8/smali/SmaliTestBase.java
+++ b/src/test/java/com/android/tools/r8/smali/SmaliTestBase.java
@@ -64,7 +64,7 @@
   protected DexApplication buildApplication(AndroidApp input, InternalOptions options) {
     try {
       return new ApplicationReader(input, options, Timing.empty()).read();
-    } catch (IOException | ExecutionException e) {
+    } catch (IOException e) {
       throw new RuntimeException(e);
     }
   }
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentClassSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentClassSubject.java
index 92e0e10..47a6182 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentClassSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentClassSubject.java
@@ -103,6 +103,11 @@
   }
 
   @Override
+  public String getFinalBinaryName() {
+    return null;
+  }
+
+  @Override
   public boolean isRenamed() {
     throw new Unreachable("Cannot determine if an absent class has been renamed");
   }
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/ClassSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/ClassSubject.java
index a234c36..8fed691 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/ClassSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/ClassSubject.java
@@ -168,6 +168,8 @@
 
   public abstract String getFinalDescriptor();
 
+  public abstract String getFinalBinaryName();
+
   public abstract boolean isMemberClass();
 
   public abstract boolean isLocalClass();
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundClassSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundClassSubject.java
index 7c0683f..6923021 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundClassSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundClassSubject.java
@@ -278,6 +278,11 @@
   }
 
   @Override
+  public String getFinalBinaryName() {
+    return DescriptorUtils.getBinaryNameFromDescriptor(getFinalDescriptor());
+  }
+
+  @Override
   public boolean isRenamed() {
     return naming != null && !getFinalDescriptor().equals(getOriginalDescriptor());
   }
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/KmTypeSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/KmTypeSubject.java
index c9975a3..5f50367 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/KmTypeSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/KmTypeSubject.java
@@ -9,8 +9,10 @@
 import com.android.tools.r8.references.Reference;
 import com.android.tools.r8.utils.Box;
 import java.util.List;
+import java.util.Objects;
 import java.util.stream.Collectors;
 import kotlinx.metadata.KmAnnotation;
+import kotlinx.metadata.KmFlexibleTypeUpperBound;
 import kotlinx.metadata.KmType;
 import kotlinx.metadata.KmTypeVisitor;
 import kotlinx.metadata.jvm.JvmExtensionsKt;
@@ -63,6 +65,10 @@
     return new KmClassifierSubject(kmType.classifier);
   }
 
+  public KmFlexibleTypeUpperBound getFlexibleUpperBound() {
+    return kmType.getFlexibleTypeUpperBound();
+  }
+
   @Override
   public boolean isPresent() {
     return true;
@@ -94,7 +100,7 @@
     return areEqual(this.kmType, other.kmType, false);
   }
 
-  public static boolean areEqual(KmType one, KmType other, boolean checkAbbreviatedType) {
+  public static boolean areEqual(KmType one, KmType other, boolean checkInnerTypeReferences) {
     if (one == null && other == null) {
       return true;
     }
@@ -116,14 +122,31 @@
         return false;
       }
     }
-    if (checkAbbreviatedType
-        && !areEqual(one.getAbbreviatedType(), other.getAbbreviatedType(), checkAbbreviatedType)) {
+    if (checkInnerTypeReferences
+        && !areEqual(
+            one.getAbbreviatedType(), other.getAbbreviatedType(), checkInnerTypeReferences)) {
       return false;
     }
-    if (!areEqual(one.getOuterType(), other.getOuterType(), checkAbbreviatedType)) {
+    if (!areEqual(one.getOuterType(), other.getOuterType(), checkInnerTypeReferences)) {
       return false;
     }
-    // TODO(b/152745540): Add equality for flexibleUpperBoundType.
+    if ((one.getFlexibleTypeUpperBound() == null) != (other.getFlexibleTypeUpperBound() == null)
+        && checkInnerTypeReferences) {
+      return false;
+    }
+    if (one.getFlexibleTypeUpperBound() != null && checkInnerTypeReferences) {
+      if (!Objects.equals(
+          one.getFlexibleTypeUpperBound().getTypeFlexibilityId(),
+          other.getFlexibleTypeUpperBound().getTypeFlexibilityId())) {
+        return false;
+      }
+      if (!areEqual(
+          one.getFlexibleTypeUpperBound().getType(),
+          other.getFlexibleTypeUpperBound().getType(),
+          checkInnerTypeReferences)) {
+        return false;
+      }
+    }
     if (JvmExtensionsKt.isRaw(one) != JvmExtensionsKt.isRaw(other)) {
       return false;
     }
diff --git a/src/test/proguard/valid/adapt-kotlin-metadata.flags b/src/test/proguard/valid/adapt-kotlin-metadata.flags
new file mode 100644
index 0000000..c6c2667
--- /dev/null
+++ b/src/test/proguard/valid/adapt-kotlin-metadata.flags
@@ -0,0 +1 @@
+-adaptkotlinmetadata
\ No newline at end of file
diff --git a/tools/compiledump.py b/tools/compiledump.py
index 5aa5f97..511ec8d 100755
--- a/tools/compiledump.py
+++ b/tools/compiledump.py
@@ -20,7 +20,7 @@
   parser.add_argument(
     '-d',
     '--dump',
-    help='Dump file to compile',
+    help='Dump file or directory to compile',
     default=None)
   parser.add_argument(
     '--temp',
@@ -85,12 +85,26 @@
   def program_jar(self):
     return self.if_exists('program.jar')
 
+  def feature_jars(self):
+    feature_jars = []
+    i = 1
+    while True:
+      feature_jar = self.if_exists('feature-%s.jar' % i)
+      if feature_jar:
+        feature_jars.append(feature_jar)
+        i = i + 1
+      else:
+        return feature_jars
+
   def library_jar(self):
     return self.if_exists('library.jar')
 
   def classpath_jar(self):
     return self.if_exists('classpath.jar')
 
+  def build_properties_file(self):
+    return self.if_exists('build.properties')
+
   def config_file(self):
     return self.if_exists('proguard.config')
 
@@ -105,12 +119,27 @@
 
 def read_dump(args, temp):
   if args.dump is None:
-    error("A dump file must be specified")
-  dump_file = zipfile.ZipFile(args.dump, 'r')
+    error("A dump file or directory must be specified")
+  if os.path.isdir(args.dump):
+    return Dump(args.dump)
+  dump_file = zipfile.ZipFile(os.path.abspath(args.dump), 'r')
   with utils.ChangedWorkingDirectory(temp):
     dump_file.extractall()
     return Dump(temp)
 
+def determine_build_properties(args, dump):
+  build_properties = {}
+  build_properties_file = dump.build_properties_file()
+  if build_properties_file:
+    with open(build_properties_file) as f:
+      build_properties_contents = f.readlines()
+      for line in build_properties_contents:
+        stripped = line.strip()
+        if stripped:
+          pair = stripped.split('=')
+          build_properties[pair[0]] = pair[1]
+  return build_properties
+
 def determine_version(args, dump):
   if args.version is None:
     return dump.version()
@@ -126,6 +155,9 @@
 def determine_output(args, temp):
   return os.path.join(temp, 'out.jar')
 
+def determine_feature_output(feature_jar, temp):
+  return os.path.join(temp, os.path.basename(feature_jar)[:-4] + ".out.jar")
+
 def download_distribution(args, version, temp):
   if version == 'master':
     return utils.R8_JAR if args.nolib else utils.R8LIB_JAR
@@ -157,6 +189,7 @@
       if not os.path.exists(temp):
         os.makedirs(temp)
     dump = read_dump(args, temp)
+    build_properties = determine_build_properties(args, dump)
     version = determine_version(args, dump)
     compiler = determine_compiler(args, dump)
     out = determine_output(args, temp)
@@ -181,6 +214,9 @@
       cmd.append('--compat')
     cmd.append(dump.program_jar())
     cmd.extend(['--output', out])
+    for feature_jar in dump.feature_jars():
+      cmd.extend(['--feature-jar', feature_jar,
+                 determine_feature_output(feature_jar, temp)])
     if dump.library_jar():
       cmd.extend(['--lib', dump.library_jar()])
     if dump.classpath_jar():
@@ -189,6 +225,8 @@
       cmd.extend(['--pg-conf', dump.config_file()])
     if compiler != 'd8':
       cmd.extend(['--pg-map-output', '%s.map' % out])
+    if 'min-api' in build_properties:
+      cmd.extend(['--min-api', build_properties.get('min-api')])
     cmd.extend(otherargs)
     utils.PrintCmd(cmd)
     try:
diff --git a/tools/run-jdwp-tests.py b/tools/run-jdwp-tests.py
index 2ef479d..f40f2ce 100755
--- a/tools/run-jdwp-tests.py
+++ b/tools/run-jdwp-tests.py
@@ -15,6 +15,7 @@
 
 VERSIONS = [
   'default',
+  '10.0.0',
   '9.0.0',
   '8.1.0',
   '7.0.0',
@@ -102,7 +103,7 @@
     flags.extend(['-Ximage:%s' % IMAGE])
     if version != '5.1.1':
       flags.extend(['-Xcompiler-option', '--debuggable'])
-  if version == '9.0.0':
+  if version == '9.0.0' or version == '10.0.0':
     flags.extend(['-XjdwpProvider:internal'])
   return flags
 
diff --git a/tools/test.py b/tools/test.py
index d6e4983..95766ca 100755
--- a/tools/test.py
+++ b/tools/test.py
@@ -156,10 +156,6 @@
 
 def Main():
   (options, args) = ParseOptions()
-  # See b/144966342
-  if options.dex_vm == '10.0.0':
-    print 'Running on 10.0.0 is temporarily disabled, see b/144966342'
-    return 0
 
   if utils.is_bot():
     gradle.RunGradle(['--no-daemon', 'clean'])