Merge commit 'f38f0382' into dev-release
diff --git a/build.gradle b/build.gradle
index 3032180..3c96101 100644
--- a/build.gradle
+++ b/build.gradle
@@ -988,14 +988,19 @@
     return baseD8CommandLine(allArgs)
 }
 
-def r8LibCreateTask(name, pgConfs = [], r8Task, output, args = ["--release"], libs = []) {
+def r8LibCreateTask(name, pgConfs = [], r8Task, output, libs = []) {
     return tasks.create("r8Lib${name}", Exec) {
-        inputs.files ([pgConfs, r8WithRelocatedDeps.outputs, r8Task.outputs, libs])
+        inputs.files ([pgConfs, r8WithRelocatedDeps.outputs, r8Task.outputs])
         outputs.file output
         dependsOn downloadOpenJDKrt
         dependsOn r8WithRelocatedDeps
         dependsOn r8Task
-        commandLine r8CfCommandLine(r8Task.outputs.files[0], output, pgConfs, args, libs)
+        commandLine ([
+                "python", "tools/create_r8lib.py",
+                "--r8jar", r8Task.outputs.files[0],
+                "--output", output]
+                + (pgConfs.collectMany { ["--pg-conf", it] })
+                + (libs.collectMany { ["--lib", it] }))
         workingDir = projectDir
     }
 }
@@ -1077,11 +1082,6 @@
     workingDir = projectDir
 }
 
-task R8LibApiOnly {
-    dependsOn r8LibCreateTask("Api", ["src/main/keep.txt"], r8NoManifest, r8LibPath)
-    outputs.file r8LibPath
-}
-
 task R8Lib {
     dependsOn r8LibCreateTask(
             "Main",
@@ -1095,11 +1095,10 @@
 
 task R8LibNoDeps {
     dependsOn r8LibCreateTask(
-            "NoDeps",
+            "MainNoDeps",
             ["src/main/keep.txt"],
             r8NoManifestWithoutDeps,
             r8LibExludeDepsPath,
-            "--release",
             repackageDeps.outputs.files
     ).dependsOn(repackageDeps)
     inputs.files ([r8NoManifestWithoutDeps.outputs, repackageDeps.outputs])
diff --git a/src/main/java/com/android/tools/r8/D8.java b/src/main/java/com/android/tools/r8/D8.java
index 7f17170..c867e02 100644
--- a/src/main/java/com/android/tools/r8/D8.java
+++ b/src/main/java/com/android/tools/r8/D8.java
@@ -261,7 +261,7 @@
 
       if (options.isGeneratingClassFiles()) {
         ProguardMapSupplier proguardMapSupplier =
-            finalizeApplication(inputApp, appView, namingLens);
+            finalizeApplication(inputApp, appView, executor, namingLens);
         new CfApplicationWriter(
                 appView, marker, GraphLens.getIdentityLens(), namingLens, proguardMapSupplier)
             .write(options.getClassFileConsumer());
@@ -306,7 +306,7 @@
           appView.setAppInfo(appView.appInfo().rebuildWithMainDexInfo(mainDexInfo));
         }
         ProguardMapSupplier proguardMapSupplier =
-            finalizeApplication(inputApp, appView, namingLens);
+            finalizeApplication(inputApp, appView, executor, namingLens);
 
         new ApplicationWriter(
                 appView,
@@ -330,8 +330,12 @@
   }
 
   private static ProguardMapSupplier finalizeApplication(
-      AndroidApp inputApp, AppView<AppInfo> appView, NamingLens namingLens) {
-    SyntheticFinalization.finalize(appView);
+      AndroidApp inputApp,
+      AppView<AppInfo> appView,
+      ExecutorService executorService,
+      NamingLens namingLens)
+      throws ExecutionException {
+    SyntheticFinalization.finalize(appView, executorService);
     if (appView.options().proguardMapConsumer == null) {
       return null;
     }
diff --git a/src/main/java/com/android/tools/r8/D8Command.java b/src/main/java/com/android/tools/r8/D8Command.java
index 3fbff40..2013fb9 100644
--- a/src/main/java/com/android/tools/r8/D8Command.java
+++ b/src/main/java/com/android/tools/r8/D8Command.java
@@ -480,7 +480,7 @@
     assert internal.neverMergePrefixes.contains("j$.");
 
     // Assert some of R8 optimizations are disabled.
-    assert !internal.enableInlining;
+    assert !internal.inlinerOptions().enableInlining;
     assert !internal.enableClassInlining;
     assert !internal.enableVerticalClassMerging;
     assert !internal.enableClassStaticizer;
diff --git a/src/main/java/com/android/tools/r8/DexFileMergerHelper.java b/src/main/java/com/android/tools/r8/DexFileMergerHelper.java
index d5f8787..f51d700 100644
--- a/src/main/java/com/android/tools/r8/DexFileMergerHelper.java
+++ b/src/main/java/com/android/tools/r8/DexFileMergerHelper.java
@@ -78,7 +78,7 @@
     options.enableMainDexListCheck = false;
     options.minimalMainDex = minimalMainDex;
     assert !options.isMinifying();
-    options.enableInlining = false;
+    options.inlinerOptions().enableInlining = false;
     options.outline.enabled = false;
 
     ExecutorService executor = ThreadUtils.getExecutorService(ThreadUtils.NOT_SPECIFIED);
diff --git a/src/main/java/com/android/tools/r8/DexSplitterHelper.java b/src/main/java/com/android/tools/r8/DexSplitterHelper.java
index d71cabc..875635f 100644
--- a/src/main/java/com/android/tools/r8/DexSplitterHelper.java
+++ b/src/main/java/com/android/tools/r8/DexSplitterHelper.java
@@ -67,7 +67,7 @@
     options.ignoreMainDexMissingClasses = true;
     options.minimalMainDex = false;
     assert !options.isMinifying();
-    options.enableInlining = false;
+    options.inlinerOptions().enableInlining = false;
     options.outline.enabled = false;
 
     try {
diff --git a/src/main/java/com/android/tools/r8/GenerateMainDexList.java b/src/main/java/com/android/tools/r8/GenerateMainDexList.java
index 2a794ab..1edb900 100644
--- a/src/main/java/com/android/tools/r8/GenerateMainDexList.java
+++ b/src/main/java/com/android/tools/r8/GenerateMainDexList.java
@@ -118,12 +118,9 @@
    */
   public static List<String> run(GenerateMainDexListCommand command)
       throws CompilationFailedException {
-    ExecutorService executorService = ThreadUtils.getExecutorService(command.getInternalOptions());
-    try {
-      return run(command, executorService);
-    } finally {
-      executorService.shutdown();
-    }
+    AndroidApp app = command.getInputApp();
+    InternalOptions options = command.getInternalOptions();
+    return runForTesting(app, options);
   }
 
   /**
@@ -145,28 +142,43 @@
     InternalOptions options = command.getInternalOptions();
     List<String> result = new ArrayList<>();
     ExceptionUtils.withMainDexListHandler(
-        command.getReporter(),
+        command.getReporter(), () -> run(app, executor, options, result));
+    return result;
+  }
+
+  static List<String> runForTesting(AndroidApp app, InternalOptions options)
+      throws CompilationFailedException {
+    ExecutorService executorService = ThreadUtils.getExecutorService(options);
+    List<String> result = new ArrayList<>();
+    ExceptionUtils.withMainDexListHandler(
+        options.reporter,
         () -> {
           try {
-            new GenerateMainDexList(options)
-                .run(
-                    app,
-                    executor,
-                    new SortingStringConsumer(
-                        new ForwardingConsumer(options.mainDexListConsumer) {
-                          @Override
-                          public void accept(String string, DiagnosticsHandler handler) {
-                            result.add(string);
-                            super.accept(string, handler);
-                          }
-                        }));
+            run(app, executorService, options, result);
           } finally {
-            executor.shutdown();
+            executorService.shutdown();
           }
         });
     return result;
   }
 
+  private static void run(
+      AndroidApp app, ExecutorService executor, InternalOptions options, List<String> result)
+      throws IOException {
+    new GenerateMainDexList(options)
+        .run(
+            app,
+            executor,
+            new SortingStringConsumer(
+                new ForwardingConsumer(options.mainDexListConsumer) {
+                  @Override
+                  public void accept(String string, DiagnosticsHandler handler) {
+                    result.add(string);
+                    super.accept(string, handler);
+                  }
+                }));
+  }
+
   public static void main(String[] args) throws CompilationFailedException {
     GenerateMainDexListCommand.Builder builder = GenerateMainDexListCommand.parse(args);
     GenerateMainDexListCommand command = builder.build();
diff --git a/src/main/java/com/android/tools/r8/GenerateMainDexListCommand.java b/src/main/java/com/android/tools/r8/GenerateMainDexListCommand.java
index 85bc305..2512782 100644
--- a/src/main/java/com/android/tools/r8/GenerateMainDexListCommand.java
+++ b/src/main/java/com/android/tools/r8/GenerateMainDexListCommand.java
@@ -223,7 +223,7 @@
     internal.mainDexKeptGraphConsumer = mainDexKeptGraphConsumer;
     internal.minimalMainDex = internal.debug;
     internal.enableEnumValueOptimization = false;
-    internal.enableInlining = false;
+    internal.inlinerOptions().enableInlining = false;
     return internal;
   }
 }
diff --git a/src/main/java/com/android/tools/r8/L8.java b/src/main/java/com/android/tools/r8/L8.java
index 8470275..9946047 100644
--- a/src/main/java/com/android/tools/r8/L8.java
+++ b/src/main/java/com/android/tools/r8/L8.java
@@ -141,7 +141,7 @@
 
       new IRConverter(appView, timing).convert(appView, executor);
 
-      SyntheticFinalization.finalize(appView);
+      SyntheticFinalization.finalize(appView, executor);
 
       NamingLens namingLens = PrefixRewritingNamingLens.createPrefixRewritingNamingLens(appView);
       new GenericSignatureRewriter(appView, namingLens).run(appView.appInfo().classes(), executor);
diff --git a/src/main/java/com/android/tools/r8/L8Command.java b/src/main/java/com/android/tools/r8/L8Command.java
index 3f2ce3d..133ca25 100644
--- a/src/main/java/com/android/tools/r8/L8Command.java
+++ b/src/main/java/com/android/tools/r8/L8Command.java
@@ -176,7 +176,7 @@
     assert !internal.passthroughDexCode;
 
     // Assert some of R8 optimizations are disabled.
-    assert !internal.enableInlining;
+    assert !internal.inlinerOptions().enableInlining;
     assert !internal.enableClassInlining;
     assert !internal.enableVerticalClassMerging;
     assert !internal.enableClassStaticizer;
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java
index 3eab5e6..ec0f361 100644
--- a/src/main/java/com/android/tools/r8/R8.java
+++ b/src/main/java/com/android/tools/r8/R8.java
@@ -419,7 +419,8 @@
                   .setPrunedApp(prunedApp)
                   .addRemovedClasses(removedClasses)
                   .addAdditionalPinnedItems(pruner.getMethodsToKeepForConfigurationDebugging())
-                  .build());
+                  .build(),
+              executorService);
           new AbstractMethodRemover(
                   appViewWithLiveness, appViewWithLiveness.appInfo().computeSubtypingInfo())
               .run();
@@ -618,7 +619,8 @@
                     .setPrunedApp(application)
                     .addRemovedClasses(CollectionUtils.mergeSets(prunedTypes, removedClasses))
                     .addAdditionalPinnedItems(pruner.getMethodsToKeepForConfigurationDebugging())
-                    .build());
+                    .build(),
+                executorService);
 
             new BridgeHoisting(appViewWithLiveness).run();
 
@@ -728,9 +730,9 @@
       }
 
       if (appView.appInfo().hasLiveness()) {
-        SyntheticFinalization.finalizeWithLiveness(appView.withLiveness());
+        SyntheticFinalization.finalizeWithLiveness(appView.withLiveness(), executorService);
       } else {
-        SyntheticFinalization.finalizeWithClassHierarchy(appView);
+        SyntheticFinalization.finalizeWithClassHierarchy(appView, executorService);
       }
 
       // Clear the reference type lattice element cache. This is required since class merging may
diff --git a/src/main/java/com/android/tools/r8/R8CommandParser.java b/src/main/java/com/android/tools/r8/R8CommandParser.java
index ab664c3..5e7cf59 100644
--- a/src/main/java/com/android/tools/r8/R8CommandParser.java
+++ b/src/main/java/com/android/tools/r8/R8CommandParser.java
@@ -6,6 +6,8 @@
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.FlagFile;
+import com.android.tools.r8.utils.MapIdTemplateProvider;
+import com.android.tools.r8.utils.SourceFileTemplateProvider;
 import com.android.tools.r8.utils.StringDiagnostic;
 import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Iterables;
@@ -34,6 +36,8 @@
           "--pg-map-output",
           "--desugared-lib",
           "--desugared-lib-pg-conf-output",
+          "--map-id-template",
+          "--source-file-template",
           THREAD_COUNT_FLAG);
 
   private static final Set<String> OPTIONS_WITH_TWO_PARAMETERS = ImmutableSet.of("--feature");
@@ -262,6 +266,11 @@
         builder.setDesugaredLibraryKeepRuleConsumer(consumer);
       } else if (arg.equals("--no-data-resources")) {
         state.includeDataResources = false;
+      } else if (arg.equals("--map-id-template")) {
+        builder.setMapIdProvider(MapIdTemplateProvider.create(nextArg, builder.getReporter()));
+      } else if (arg.equals("--source-file-template")) {
+        builder.setSourceFileProvider(
+            SourceFileTemplateProvider.create(nextArg, builder.getReporter()));
       } else if (arg.startsWith("--")) {
         if (tryParseAssertionArgument(builder, arg, argsOrigin)) {
           continue;
diff --git a/src/main/java/com/android/tools/r8/graph/AppInfo.java b/src/main/java/com/android/tools/r8/graph/AppInfo.java
index df45e6b..e5a744b 100644
--- a/src/main/java/com/android/tools/r8/graph/AppInfo.java
+++ b/src/main/java/com/android/tools/r8/graph/AppInfo.java
@@ -15,6 +15,8 @@
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.IterableUtils;
 import java.util.List;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
 import java.util.function.Consumer;
 
 public class AppInfo implements DexDefinitionSupplier {
@@ -63,7 +65,8 @@
     this.obsolete = obsolete;
   }
 
-  public AppInfo prunedCopyFrom(PrunedItems prunedItems) {
+  public AppInfo prunedCopyFrom(PrunedItems prunedItems, ExecutorService executorService)
+      throws ExecutionException {
     assert getClass() == AppInfo.class;
     assert checkIfObsolete();
     assert prunedItems.getPrunedApp() == app();
diff --git a/src/main/java/com/android/tools/r8/graph/AppInfoWithClassHierarchy.java b/src/main/java/com/android/tools/r8/graph/AppInfoWithClassHierarchy.java
index 12a5e8d..a13b905 100644
--- a/src/main/java/com/android/tools/r8/graph/AppInfoWithClassHierarchy.java
+++ b/src/main/java/com/android/tools/r8/graph/AppInfoWithClassHierarchy.java
@@ -40,6 +40,8 @@
 import java.util.List;
 import java.util.Map.Entry;
 import java.util.Set;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
 import java.util.function.Function;
 
 /* Specific subclass of AppInfo designed to support desugaring in D8. Desugaring requires a
@@ -123,7 +125,8 @@
   }
 
   @Override
-  public AppInfoWithClassHierarchy prunedCopyFrom(PrunedItems prunedItems) {
+  public AppInfoWithClassHierarchy prunedCopyFrom(
+      PrunedItems prunedItems, ExecutorService executorService) throws ExecutionException {
     assert getClass() == AppInfoWithClassHierarchy.class;
     assert checkIfObsolete();
     assert prunedItems.getPrunedApp() == app();
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 0e165f7..4d3d50a 100644
--- a/src/main/java/com/android/tools/r8/graph/AppView.java
+++ b/src/main/java/com/android/tools/r8/graph/AppView.java
@@ -51,6 +51,8 @@
 import java.util.IdentityHashMap;
 import java.util.Map;
 import java.util.Set;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
 import java.util.function.Function;
 import java.util.function.Predicate;
 import java.util.function.Supplier;
@@ -687,19 +689,20 @@
     return !cfByteCodePassThrough.isEmpty();
   }
 
-  public void pruneItems(PrunedItems prunedItems) {
+  public void pruneItems(PrunedItems prunedItems, ExecutorService executorService)
+      throws ExecutionException {
     if (prunedItems.isEmpty()) {
       assert appInfo().app() == prunedItems.getPrunedApp();
       return;
     }
     if (appInfo.hasLiveness()) {
       AppView<AppInfoWithLiveness> self = withLiveness();
-      self.setAppInfo(self.appInfo().prunedCopyFrom(prunedItems));
+      self.setAppInfo(self.appInfo().prunedCopyFrom(prunedItems, executorService));
     } else if (appInfo.hasClassHierarchy()) {
       AppView<AppInfoWithClassHierarchy> self = withClassHierarchy();
-      self.setAppInfo(self.appInfo().prunedCopyFrom(prunedItems));
+      self.setAppInfo(self.appInfo().prunedCopyFrom(prunedItems, executorService));
     } else {
-      pruneAppInfo(prunedItems, this);
+      pruneAppInfo(prunedItems, this, executorService);
     }
     if (appServices() != null) {
       setAppServices(appServices().prunedCopy(prunedItems));
@@ -714,8 +717,11 @@
   }
 
   @SuppressWarnings("unchecked")
-  private static void pruneAppInfo(PrunedItems prunedItems, AppView<?> appView) {
-    ((AppView<AppInfo>) appView).setAppInfo(appView.appInfo().prunedCopyFrom(prunedItems));
+  private static void pruneAppInfo(
+      PrunedItems prunedItems, AppView<?> appView, ExecutorService executorService)
+      throws ExecutionException {
+    ((AppView<AppInfo>) appView)
+        .setAppInfo(appView.appInfo().prunedCopyFrom(prunedItems, executorService));
   }
 
   public void rewriteWithLens(NonIdentityGraphLens lens) {
diff --git a/src/main/java/com/android/tools/r8/graph/DexCode.java b/src/main/java/com/android/tools/r8/graph/DexCode.java
index aa201c2..8409ee1 100644
--- a/src/main/java/com/android/tools/r8/graph/DexCode.java
+++ b/src/main/java/com/android/tools/r8/graph/DexCode.java
@@ -118,7 +118,7 @@
 
   @Override
   public int estimatedSizeForInlining() {
-    return instructions.length;
+    return codeSizeInBytes();
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/graph/GraphLens.java b/src/main/java/com/android/tools/r8/graph/GraphLens.java
index fdba79b..f0675ba 100644
--- a/src/main/java/com/android/tools/r8/graph/GraphLens.java
+++ b/src/main/java/com/android/tools/r8/graph/GraphLens.java
@@ -26,7 +26,6 @@
 import it.unimi.dsi.fastutil.objects.Object2BooleanArrayMap;
 import it.unimi.dsi.fastutil.objects.Object2BooleanMap;
 import java.util.ArrayList;
-import java.util.Collections;
 import java.util.IdentityHashMap;
 import java.util.List;
 import java.util.Map;
@@ -564,9 +563,9 @@
             this::lookupType, this::getRenamedFieldSignature, this::getRenamedMethodSignature);
   }
 
-  public Set<DexReference> rewriteReferences(Set<DexReference> references) {
-    Set<DexReference> result = SetUtils.newIdentityHashSet(references.size());
-    for (DexReference reference : references) {
+  public <T extends DexReference> Set<T> rewriteReferences(Set<T> references) {
+    Set<T> result = SetUtils.newIdentityHashSet(references.size());
+    for (T reference : references) {
       result.add(rewriteReference(reference));
     }
     return result;
@@ -602,30 +601,15 @@
     return result;
   }
 
-  public Object2BooleanMap<DexReference> rewriteReferenceKeys(Object2BooleanMap<DexReference> map) {
-    Object2BooleanMap<DexReference> result = new Object2BooleanArrayMap<>();
-    for (Object2BooleanMap.Entry<DexReference> entry : map.object2BooleanEntrySet()) {
+  public <T extends DexReference> Object2BooleanMap<T> rewriteReferenceKeys(
+      Object2BooleanMap<T> map) {
+    Object2BooleanMap<T> result = new Object2BooleanArrayMap<>();
+    for (Object2BooleanMap.Entry<T> entry : map.object2BooleanEntrySet()) {
       result.put(rewriteReference(entry.getKey()), entry.getBooleanValue());
     }
     return result;
   }
 
-  public ImmutableSet<DexMethod> rewriteMethods(Set<DexMethod> methods) {
-    ImmutableSet.Builder<DexMethod> builder = ImmutableSet.builder();
-    for (DexMethod method : methods) {
-      builder.add(getRenamedMethodSignature(method));
-    }
-    return builder.build();
-  }
-
-  public ImmutableSet<DexField> rewriteFields(Set<DexField> fields) {
-    ImmutableSet.Builder<DexField> builder = ImmutableSet.builder();
-    for (DexField field : fields) {
-      builder.add(getRenamedFieldSignature(field));
-    }
-    return builder.build();
-  }
-
   public <T> ImmutableMap<DexField, T> rewriteFieldKeys(Map<DexField, T> map) {
     ImmutableMap.Builder<DexField, T> builder = ImmutableMap.builder();
     map.forEach((field, value) -> builder.put(getRenamedFieldSignature(field), value));
@@ -649,7 +633,7 @@
           newMap.put(
               rewrittenType, previousValue != null ? merge.apply(value, previousValue) : value);
         });
-    return Collections.unmodifiableMap(newMap);
+    return newMap;
   }
 
   public boolean verifyMappingToOriginalProgram(
diff --git a/src/main/java/com/android/tools/r8/graph/PrunedItems.java b/src/main/java/com/android/tools/r8/graph/PrunedItems.java
index 1f8aa68..4489421 100644
--- a/src/main/java/com/android/tools/r8/graph/PrunedItems.java
+++ b/src/main/java/com/android/tools/r8/graph/PrunedItems.java
@@ -41,15 +41,18 @@
   }
 
   public boolean isEmpty() {
-    return removedClasses.isEmpty() && additionalPinnedItems.isEmpty();
+    return removedClasses.isEmpty()
+        && removedFields.isEmpty()
+        && removedMethods.isEmpty()
+        && additionalPinnedItems.isEmpty();
   }
 
   public boolean isRemoved(DexField field) {
-    return removedFields.contains(field);
+    return removedFields.contains(field) || removedClasses.contains(field.getHolderType());
   }
 
   public boolean isRemoved(DexMethod method) {
-    return removedMethods.contains(method);
+    return removedMethods.contains(method) || removedClasses.contains(method.getHolderType());
   }
 
   public boolean isRemoved(DexType type) {
@@ -72,10 +75,26 @@
     return !removedClasses.isEmpty();
   }
 
+  public boolean hasRemovedFields() {
+    return !removedFields.isEmpty();
+  }
+
+  public boolean hasRemovedMembers() {
+    return hasRemovedFields() || hasRemovedMethods();
+  }
+
+  public boolean hasRemovedMethods() {
+    return !removedMethods.isEmpty();
+  }
+
   public Set<DexType> getRemovedClasses() {
     return removedClasses;
   }
 
+  public Set<DexField> getRemovedFields() {
+    return removedFields;
+  }
+
   public Set<DexMethod> getRemovedMethods() {
     return removedMethods;
   }
@@ -86,7 +105,7 @@
 
     private final Set<DexReference> additionalPinnedItems = Sets.newIdentityHashSet();
     private final Set<DexType> noLongerSyntheticItems = Sets.newIdentityHashSet();
-    private final Set<DexType> removedClasses = Sets.newIdentityHashSet();
+    private Set<DexType> removedClasses = Sets.newIdentityHashSet();
     private final Set<DexField> removedFields = Sets.newIdentityHashSet();
     private final Set<DexMethod> removedMethods = Sets.newIdentityHashSet();
 
@@ -122,6 +141,11 @@
       return this;
     }
 
+    public Builder setRemovedClasses(Set<DexType> removedClasses) {
+      this.removedClasses = removedClasses;
+      return this;
+    }
+
     public PrunedItems build() {
       return new PrunedItems(
           prunedApp,
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMerger.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMerger.java
index 666bd64..2f9a4ee 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMerger.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMerger.java
@@ -123,7 +123,10 @@
 
     // Prune keep info.
     KeepInfoCollection keepInfo = appView.getKeepInfo();
-    keepInfo.mutate(mutator -> mutator.removeKeepInfoForPrunedItems(mergedClasses.getSources()));
+    keepInfo.mutate(
+        mutator ->
+            mutator.removeKeepInfoForPrunedItems(
+                PrunedItems.builder().setRemovedClasses(mergedClasses.getSources()).build()));
 
     // Must rewrite AppInfoWithLiveness before pruning the merged classes, to ensure that allocation
     // sites, fields accesses, etc. are correctly transferred to the target classes.
@@ -142,7 +145,8 @@
             .setPrunedApp(appView.appInfo().app())
             .addRemovedClasses(mergedClasses.getSources())
             .addNoLongerSyntheticItems(mergedClasses.getSources())
-            .build());
+            .build(),
+        executorService);
   }
 
   private FieldAccessInfoCollectionModifier createFieldAccessInfoCollectionModifier(
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeMethod.java b/src/main/java/com/android/tools/r8/ir/code/InvokeMethod.java
index 92998f3..efbcd4c 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokeMethod.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokeMethod.java
@@ -32,6 +32,7 @@
 import com.android.tools.r8.ir.optimize.inliner.WhyAreYouNotInliningReporter;
 import com.android.tools.r8.ir.regalloc.RegisterAllocator;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.collections.ProgramMethodSet;
 import com.google.common.collect.ImmutableList;
 import java.util.BitSet;
@@ -70,6 +71,10 @@
     }
   }
 
+  public int getFirstNonReceiverArgumentIndex() {
+    return BooleanUtils.intValue(isInvokeMethodWithReceiver());
+  }
+
   public abstract boolean getInterfaceBit();
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeVirtual.java b/src/main/java/com/android/tools/r8/ir/code/InvokeVirtual.java
index e063b8f..9179014 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokeVirtual.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokeVirtual.java
@@ -132,8 +132,7 @@
                   receiverLowerBoundType);
     } else {
       // In D8, allow lookupSingleTarget() to be used for finding final library methods. This is
-      // used
-      // for library modeling.
+      // used for library modeling.
       DexType holder = method.holder;
       if (holder.isClassType()) {
         DexClass clazz = appView.definitionFor(holder);
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/CallSiteInformation.java b/src/main/java/com/android/tools/r8/ir/conversion/CallSiteInformation.java
index 4899a50..5d93463 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/CallSiteInformation.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/CallSiteInformation.java
@@ -77,7 +77,7 @@
           continue;
         }
 
-        if (appView.options().disableInliningOfLibraryMethodOverrides
+        if (appView.options().inlinerOptions().disableInliningOfLibraryMethodOverrides
             && method.getDefinition().isLibraryMethodOverride().isTrue()) {
           continue;
         }
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 4fde21a..293635b 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
@@ -258,7 +258,9 @@
       AppView<AppInfoWithLiveness> appViewWithLiveness = appView.withLiveness();
       assumeInserter = new AssumeInserter(appViewWithLiveness);
       this.classInliner =
-          options.enableClassInlining && options.enableInlining ? new ClassInliner() : null;
+          options.enableClassInlining && options.inlinerOptions().enableInlining
+              ? new ClassInliner()
+              : null;
       this.classStaticizer =
           options.enableClassStaticizer ? new ClassStaticizer(appViewWithLiveness, this) : null;
       this.dynamicTypeOptimization = new DynamicTypeOptimization(appViewWithLiveness);
@@ -654,9 +656,6 @@
         classStaticizer,
         classStaticizer ->
             classStaticizer.prepareForPrimaryOptimizationPass(graphLensForPrimaryOptimizationPass));
-    ConsumerUtils.acceptIfNotNull(
-        inliner,
-        inliner -> inliner.initializeDoubleInlineCallers(graphLensForPrimaryOptimizationPass));
     outliner.prepareForPrimaryOptimizationPass(graphLensForPrimaryOptimizationPass);
 
     if (fieldAccessAnalysis != null) {
@@ -1257,7 +1256,7 @@
 
     previous = printMethod(code, "IR after generated message lite shrinking (SSA)", previous);
 
-    if (!isDebugMode && options.enableInlining && inliner != null) {
+    if (!isDebugMode && options.inlinerOptions().enableInlining && inliner != null) {
       timing.begin("Inlining");
       inliner.performInlining(code.context(), code, feedback, methodProcessor, timing);
       timing.end();
@@ -1408,7 +1407,7 @@
       timing.begin("Inline classes");
       // Class inliner should work before lambda merger, so if it inlines the
       // lambda, it does not get collected by merger.
-      assert options.enableInlining && inliner != null;
+      assert options.inlinerOptions().enableInlining && inliner != null;
       classInliner.processMethodCode(
           appView.withLiveness(),
           codeRewriter,
@@ -1425,7 +1424,6 @@
                   inliner.createDefaultOracle(
                       code.context(),
                       methodProcessor,
-                      options.classInliningInstructionLimit,
                       // Inlining instruction allowance is not needed for the class inliner since it
                       // always uses a force inlining oracle for inlining.
                       -1)));
@@ -1684,7 +1682,7 @@
   }
 
   private boolean shouldComputeInliningConstraint(ProgramMethod method) {
-    if (!options.enableInlining || inliner == null) {
+    if (!options.inlinerOptions().enableInlining || inliner == null) {
       return false;
     }
     DexEncodedMethod definition = method.getDefinition();
@@ -1957,12 +1955,30 @@
    * Called when a method is pruned as a result of optimizations during IR processing in R8, to
    * allow optimizations that track sets of methods to fixup their state.
    */
-  public void pruneMethod(ProgramMethod method) {
+  public void onMethodPruned(ProgramMethod method) {
     assert appView.enableWholeProgramOptimizations();
     assert method.getHolder().lookupMethod(method.getReference()) == null;
-    appView.withArgumentPropagator(argumentPropagator -> argumentPropagator.pruneMethod(method));
+    appView.withArgumentPropagator(argumentPropagator -> argumentPropagator.onMethodPruned(method));
+    enumUnboxer.onMethodPruned(method);
+    outliner.onMethodPruned(method);
     if (inliner != null) {
-      inliner.pruneMethod(method);
+      inliner.onMethodPruned(method);
+    }
+  }
+
+  /**
+   * Called when a method is transformed into an abstract or "throw null" method as a result of
+   * optimizations during IR processing in R8.
+   */
+  public void onMethodCodePruned(ProgramMethod method) {
+    assert appView.enableWholeProgramOptimizations();
+    assert method.getHolder().lookupMethod(method.getReference()) != null;
+    appView.withArgumentPropagator(
+        argumentPropagator -> argumentPropagator.onMethodCodePruned(method));
+    enumUnboxer.onMethodCodePruned(method);
+    outliner.onMethodCodePruned(method);
+    if (inliner != null) {
+      inliner.onMethodCodePruned(method);
     }
   }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java b/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java
index e7374c5..0b7b139 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java
@@ -7,6 +7,9 @@
 import static com.android.tools.r8.ir.optimize.inliner.InlinerUtils.collectAllMonitorEnterValues;
 import static com.android.tools.r8.utils.AndroidApiLevelUtils.isApiSafeForInlining;
 
+import com.android.tools.r8.code.MoveResult;
+import com.android.tools.r8.code.MoveResultObject;
+import com.android.tools.r8.code.MoveResultWide;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.features.ClassToFeatureSplitMap;
 import com.android.tools.r8.graph.AppView;
@@ -43,7 +46,7 @@
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.synthesis.SyntheticItems;
 import com.android.tools.r8.utils.BooleanUtils;
-import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.InternalOptions.InlinerOptions;
 import com.android.tools.r8.utils.Timing;
 import com.google.common.collect.Sets;
 import java.util.ArrayList;
@@ -55,10 +58,10 @@
 
   private final AppView<AppInfoWithLiveness> appView;
   private final Inliner inliner;
+  private final InlinerOptions inlinerOptions;
   private final ProgramMethod method;
   private final MethodProcessor methodProcessor;
   private final InliningReasonStrategy reasonStrategy;
-  private final int inliningInstructionLimit;
   private int instructionAllowance;
 
   DefaultInliningOracle(
@@ -67,14 +70,13 @@
       InliningReasonStrategy inliningReasonStrategy,
       ProgramMethod method,
       MethodProcessor methodProcessor,
-      int inliningInstructionLimit,
       int inliningInstructionAllowance) {
     this.appView = appView;
     this.inliner = inliner;
+    this.inlinerOptions = appView.options().inlinerOptions();
     this.reasonStrategy = inliningReasonStrategy;
     this.method = method;
     this.methodProcessor = methodProcessor;
-    this.inliningInstructionLimit = inliningInstructionLimit;
     this.instructionAllowance = inliningInstructionAllowance;
   }
 
@@ -168,7 +170,7 @@
       }
     }
 
-    Set<Reason> validInliningReasons = appView.options().testing.validInliningReasons;
+    Set<Reason> validInliningReasons = appView.testing().validInliningReasons;
     if (validInliningReasons != null && !validInliningReasons.contains(reason)) {
       whyAreYouNotInliningReporter.reportInvalidInliningReason(reason, validInliningReasons);
       return false;
@@ -180,26 +182,7 @@
       return false;
     }
 
-    if (reason == Reason.DUAL_CALLER) {
-      assert methodProcessor.isPrimaryMethodProcessor() || methodProcessor.isPostMethodProcessor();
-      if (satisfiesRequirementsForSimpleInlining(invoke, singleTarget)) {
-        // When we have a method with two call sites, we simply inline the method as we normally do
-        // when the method is small. We still need to ensure that the other call site is also
-        // inlined, though. Therefore, we record here that we have seen one of the two call sites
-        // as we normally do.
-        inliner.recordDoubleInliningCandidate(method, singleTarget, methodProcessor);
-      } else if (inliner.isDoubleInliningEnabled(methodProcessor)) {
-        if (!inliner.satisfiesRequirementsForDoubleInlining(
-            method, singleTarget, methodProcessor)) {
-          whyAreYouNotInliningReporter.reportInvalidDoubleInliningCandidate();
-          return false;
-        }
-      } else {
-        // TODO(b/142300882): Should in principle disallow inlining in this case.
-        inliner.recordDoubleInliningCandidate(method, singleTarget, methodProcessor);
-      }
-    } else if (reason == Reason.SIMPLE
-        && !satisfiesRequirementsForSimpleInlining(invoke, singleTarget)) {
+    if (reason == Reason.SIMPLE && !satisfiesRequirementsForSimpleInlining(invoke, singleTarget)) {
       whyAreYouNotInliningReporter.reportInlineeNotSimple();
       return false;
     }
@@ -222,7 +205,9 @@
       InvokeMethod invoke, ProgramMethod target) {
     // If we are looking for a simple method, only inline if actually simple.
     Code code = target.getDefinition().getCode();
-    int instructionLimit = computeInstructionLimit(invoke, target);
+    int instructionLimit =
+        inlinerOptions.getSimpleInliningInstructionLimit()
+            + getInliningInstructionLimitIncrement(invoke, target);
     if (code.estimatedSizeForInliningAtMost(instructionLimit)) {
       return true;
     }
@@ -233,15 +218,14 @@
     return simpleInliningConstraint.isSatisfied(invoke);
   }
 
-  private int computeInstructionLimit(InvokeMethod invoke, ProgramMethod candidate) {
-    int instructionLimit = inliningInstructionLimit;
+  private int getInliningInstructionLimitIncrement(InvokeMethod invoke, ProgramMethod candidate) {
+    int instructionLimit = 0;
     BitSet hints = candidate.getDefinition().getOptimizationInfo().getNonNullParamOrThrow();
     if (hints != null) {
-      List<Value> arguments = invoke.inValues();
-      if (invoke.isInvokeMethodWithReceiver()) {
-        arguments = arguments.subList(1, arguments.size());
-      }
-      for (int index = 0; index < arguments.size(); index++) {
+      List<Value> arguments = invoke.arguments();
+      for (int index = invoke.getFirstNonReceiverArgumentIndex();
+          index < arguments.size();
+          index++) {
         Value argument = arguments.get(index);
         if ((argument.isArgument()
                 || (argument.getType().isReferenceType() && argument.isNeverNull()))
@@ -251,6 +235,13 @@
         }
       }
     }
+    if (appView.options().isGeneratingDex()
+        && invoke.hasOutValue()
+        && invoke.outValue().hasNonDebugUsers()) {
+      assert MoveResult.SIZE == MoveResultObject.SIZE;
+      assert MoveResult.SIZE == MoveResultWide.SIZE;
+      instructionLimit += MoveResult.SIZE;
+    }
     return instructionLimit;
   }
 
@@ -336,8 +327,7 @@
           .getDefinition()
           .getOptimizationInfo()
           .checksNullReceiverBeforeAnySideEffect()) {
-        InternalOptions options = appView.options();
-        if (!options.enableInliningOfInvokesWithNullableReceivers) {
+        if (!inlinerOptions.enableInliningOfInvokesWithNullableReceivers) {
           whyAreYouNotInliningReporter.reportReceiverMaybeNull();
           return null;
         }
@@ -358,7 +348,7 @@
       return action;
     }
     if (appView.canUseInitClass()
-        && appView.options().enableInliningOfInvokesWithClassInitializationSideEffects) {
+        && inlinerOptions.enableInliningOfInvokesWithClassInitializationSideEffects) {
       action.setShouldSynthesizeInitClass();
       return action;
     }
@@ -438,7 +428,7 @@
       return true;
     }
 
-    int threshold = appView.options().applyInliningToInlineeMaxDepth;
+    int threshold = inlinerOptions.applyInliningToInlineeMaxDepth;
     if (inliningDepth <= threshold) {
       return true;
     }
@@ -644,7 +634,7 @@
 
     int numberOfMonitorEnterValuesAfterInlining =
         constantMonitorEnterValues.size() + nonConstantMonitorEnterValues.size();
-    int threshold = appView.options().inliningMonitorEnterValuesAllowance;
+    int threshold = inlinerOptions.inliningMonitorEnterValuesAllowance;
     if (numberOfMonitorEnterValuesAfterInlining > threshold) {
       whyAreYouNotInliningReporter.reportWillExceedMonitorEnterValuesBudget(
           numberOfMonitorEnterValuesAfterInlining, threshold);
@@ -693,7 +683,7 @@
         numberOfThrowingInstructionsInInlinee * block.numberOfCatchHandlers();
     // Abort if inlining could lead to an explosion in the number of control flow
     // resolution blocks that setup the register state before the actual catch handler.
-    int threshold = appView.options().inliningControlFlowResolutionBlocksThreshold;
+    int threshold = inlinerOptions.inliningControlFlowResolutionBlocksThreshold;
     if (estimatedNumberOfControlFlowResolutionBlocks >= threshold) {
       whyAreYouNotInliningReporter.reportPotentialExplosionInExceptionalControlFlowResolutionBlocks(
           estimatedNumberOfControlFlowResolutionBlocks, threshold);
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 ecf8f25..32d2228 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
@@ -63,6 +63,7 @@
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.shaking.MainDexInfo;
 import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.InternalOptions.InlinerOptions;
 import com.android.tools.r8.utils.IteratorUtils;
 import com.android.tools.r8.utils.ListUtils;
 import com.android.tools.r8.utils.SetUtils;
@@ -75,7 +76,6 @@
 import java.util.ArrayDeque;
 import java.util.ArrayList;
 import java.util.Deque;
-import java.util.IdentityHashMap;
 import java.util.List;
 import java.util.ListIterator;
 import java.util.Map;
@@ -94,13 +94,9 @@
   // due to not being processed at the time of inlining.
   private final LongLivedProgramMethodSetBuilder<ProgramMethodSet> singleInlineCallers;
 
-  // State for inlining methods which are known to be called twice.
-  private LongLivedProgramMethodSetBuilder<ProgramMethodSet> doubleInlineCallers;
-  private final ProgramMethodSet doubleInlineSelectedTargets = ProgramMethodSet.create();
-  private final Map<DexEncodedMethod, ProgramMethod> doubleInlineeCandidates =
-      new IdentityHashMap<>();
-
-  private final Map<DexProgramClass, ProgramMethodSet> singleCallerInlinedMethods =
+  // The set of methods that have been single caller inlined in the current wave. These need to be
+  // pruned when the wave ends.
+  private final Map<DexProgramClass, ProgramMethodSet> singleCallerInlinedMethodsInWave =
       new ConcurrentHashMap<>();
 
   private final AvailableApiExceptions availableApiExceptions;
@@ -219,47 +215,6 @@
     return false;
   }
 
-  public synchronized boolean isDoubleInlineSelectedTarget(ProgramMethod method) {
-    return doubleInlineSelectedTargets.contains(method);
-  }
-
-  synchronized boolean satisfiesRequirementsForDoubleInlining(
-      ProgramMethod method, ProgramMethod target, MethodProcessor methodProcessor) {
-    if (isDoubleInliningEnabled(methodProcessor)) {
-      // Don't perform the actual inlining if this was not selected.
-      return doubleInlineSelectedTargets.contains(target);
-    }
-
-    // Just preparing for double inlining.
-    recordDoubleInliningCandidate(method, target, methodProcessor);
-    return false;
-  }
-
-  synchronized void recordDoubleInliningCandidate(
-      ProgramMethod method, ProgramMethod target, MethodProcessor methodProcessor) {
-    if (isDoubleInliningEnabled(methodProcessor)) {
-      return;
-    }
-
-    if (doubleInlineeCandidates.containsKey(target.getDefinition())) {
-      // Both calls can be inlined.
-      GraphLens currentGraphLens = appView.graphLens();
-      ProgramMethod doubleInlineeCandidate = doubleInlineeCandidates.get(target.getDefinition());
-      doubleInlineCallers.add(doubleInlineeCandidate, currentGraphLens);
-      doubleInlineCallers.add(method, currentGraphLens);
-      doubleInlineSelectedTargets.add(target);
-    } else {
-      // First call can be inlined.
-      doubleInlineeCandidates.put(target.getDefinition(), method);
-    }
-  }
-
-  public void initializeDoubleInlineCallers(GraphLens graphLensForPrimaryOptimizationPass) {
-    assert appView.graphLens() == graphLensForPrimaryOptimizationPass;
-    doubleInlineCallers =
-        LongLivedProgramMethodSetBuilder.createForIdentitySet(graphLensForPrimaryOptimizationPass);
-  }
-
   public void enqueueMethodsForReprocessing(
       PostMethodProcessor.Builder postMethodProcessorBuilder) {
     // The double inline callers are always rewritten up until the graph lens of the primary
@@ -268,19 +223,7 @@
     postMethodProcessorBuilder
         .getMethodsToReprocessBuilder()
         .rewrittenWithLens(appView)
-        .merge(
-            doubleInlineCallers
-                .rewrittenWithLens(appView)
-                .removeIf(
-                    appView,
-                    method -> method.getOptimizationInfo().hasBeenInlinedIntoSingleCallSite()))
-        .merge(
-            singleInlineCallers
-                .rewrittenWithLens(appView)
-                .removeIf(
-                    appView,
-                    method -> method.getOptimizationInfo().hasBeenInlinedIntoSingleCallSite()));
-    doubleInlineCallers = null;
+        .merge(singleInlineCallers);
     singleInlineCallers.clear();
   }
 
@@ -953,12 +896,11 @@
       MethodProcessor methodProcessor,
       Timing timing,
       InliningReasonStrategy inliningReasonStrategy) {
-    InternalOptions options = appView.options();
+    InlinerOptions options = appView.options().inlinerOptions();
     DefaultInliningOracle oracle =
         createDefaultOracle(
             method,
             methodProcessor,
-            options.inliningInstructionLimit,
             options.inliningInstructionAllowance - numberOfInstructions(code),
             inliningReasonStrategy);
     InliningIRProvider inliningIRProvider =
@@ -970,7 +912,7 @@
   public InliningReasonStrategy createDefaultInliningReasonStrategy(
       MethodProcessor methodProcessor) {
     DefaultInliningReasonStrategy defaultInliningReasonStrategy =
-        new DefaultInliningReasonStrategy(appView, methodProcessor.getCallSiteInformation(), this);
+        new DefaultInliningReasonStrategy(appView, methodProcessor.getCallSiteInformation());
     return appView.withGeneratedMessageLiteShrinker(
         ignore -> new ProtoInliningReasonStrategy(appView, defaultInliningReasonStrategy),
         defaultInliningReasonStrategy);
@@ -979,12 +921,10 @@
   public DefaultInliningOracle createDefaultOracle(
       ProgramMethod method,
       MethodProcessor methodProcessor,
-      int inliningInstructionLimit,
       int inliningInstructionAllowance) {
     return createDefaultOracle(
         method,
         methodProcessor,
-        inliningInstructionLimit,
         inliningInstructionAllowance,
         createDefaultInliningReasonStrategy(methodProcessor));
   }
@@ -992,7 +932,6 @@
   public DefaultInliningOracle createDefaultOracle(
       ProgramMethod method,
       MethodProcessor methodProcessor,
-      int inliningInstructionLimit,
       int inliningInstructionAllowance,
       InliningReasonStrategy inliningReasonStrategy) {
     return new DefaultInliningOracle(
@@ -1001,7 +940,6 @@
         inliningReasonStrategy,
         method,
         methodProcessor,
-        inliningInstructionLimit,
         inliningInstructionAllowance);
   }
 
@@ -1019,7 +957,7 @@
     ClassInitializationAnalysis classInitializationAnalysis =
         new ClassInitializationAnalysis(appView, code);
     Deque<BasicBlock> inlineeStack = new ArrayDeque<>();
-    InternalOptions options = appView.options();
+    InlinerOptions options = appView.options().inlinerOptions();
     while (blockIterator.hasNext()) {
       BasicBlock block = blockIterator.next();
       if (!inlineeStack.isEmpty() && inlineeStack.peekFirst() == block) {
@@ -1141,10 +1079,10 @@
           if (inlinee.reason == Reason.SINGLE_CALLER) {
             assert converter.isInWave();
             feedback.markInlinedIntoSingleCallSite(singleTargetMethod);
-            if (singleCallerInlinedMethods.isEmpty()) {
+            if (singleCallerInlinedMethodsInWave.isEmpty()) {
               converter.addWaveDoneAction(this::onWaveDone);
             }
-            singleCallerInlinedMethods
+            singleCallerInlinedMethodsInWave
                 .computeIfAbsent(
                     singleTarget.getHolder(), ignoreKey(ProgramMethodSet::createConcurrent))
                 .add(singleTarget);
@@ -1317,18 +1255,23 @@
     singleInlineCallers.add(method, appView.graphLens());
   }
 
-  public void pruneMethod(ProgramMethod method) {
+  public void onMethodPruned(ProgramMethod method) {
+    onMethodCodePruned(method);
+  }
+
+  public void onMethodCodePruned(ProgramMethod method) {
     singleInlineCallers.remove(method.getReference(), appView.graphLens());
   }
 
   private void onWaveDone() {
-    singleCallerInlinedMethods.forEach(
+    singleCallerInlinedMethodsInWave.forEach(
         (clazz, singleCallerInlinedMethodsForClass) -> {
           // Convert and remove virtual single caller inlined methods to abstract or throw null.
           singleCallerInlinedMethodsForClass.removeIf(
               singleCallerInlinedMethod -> {
                 if (singleCallerInlinedMethod.getDefinition().belongsToVirtualPool() || true) {
                   singleCallerInlinedMethod.convertToAbstractOrThrowNullMethod(appView);
+                  converter.onMethodCodePruned(singleCallerInlinedMethod);
                   return true;
                 }
                 return false;
@@ -1341,10 +1284,10 @@
                 .removeMethods(
                     singleCallerInlinedMethodsForClass.toDefinitionSet(
                         SetUtils::newIdentityHashSet));
-            singleCallerInlinedMethodsForClass.forEach(converter::pruneMethod);
+            singleCallerInlinedMethodsForClass.forEach(converter::onMethodPruned);
           }
         });
-    singleCallerInlinedMethods.clear();
+    singleCallerInlinedMethodsInWave.clear();
   }
 
   public static boolean verifyAllSingleCallerMethodsHaveBeenPruned(
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/OutlinerImpl.java b/src/main/java/com/android/tools/r8/ir/optimize/OutlinerImpl.java
index c0da8fd..31801c6 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/OutlinerImpl.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/OutlinerImpl.java
@@ -1318,6 +1318,16 @@
   }
 
   @Override
+  public void onMethodPruned(ProgramMethod method) {
+    onMethodCodePruned(method);
+  }
+
+  @Override
+  public void onMethodCodePruned(ProgramMethod method) {
+    outlineCollection.remove(appView, method);
+  }
+
+  @Override
   public void prepareForPrimaryOptimizationPass(GraphLens graphLensForPrimaryOptimizationPass) {
     assert appView.graphLens() == graphLensForPrimaryOptimizationPass;
     assert outlineCollection == null;
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerCostAnalysis.java b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerCostAnalysis.java
index 262f8be..06e2b18 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerCostAnalysis.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerCostAnalysis.java
@@ -9,6 +9,9 @@
 import static com.android.tools.r8.ir.code.Opcodes.INSTANCE_PUT;
 import static com.android.tools.r8.ir.code.Opcodes.RETURN;
 
+import com.android.tools.r8.code.Iget;
+import com.android.tools.r8.code.Iput;
+import com.android.tools.r8.code.Return;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.ProgramMethod;
@@ -75,7 +78,7 @@
       IRCode inliningIR = inliningIRProvider.getAndCacheInliningIR(invoke, inlinee, false);
       int increment =
           inlinee.getDefinition().getCode().estimatedSizeForInlining()
-              - estimateNumberOfNonMaterializingInstructions(invoke, inliningIR);
+              - estimateSizeOfNonMaterializingInstructions(invoke, inliningIR);
       assert increment >= 0;
       if (exceedsInstructionBudgetAfterIncrement(increment)) {
         return true;
@@ -93,7 +96,8 @@
 
   private boolean exceedsInstructionBudgetAfterIncrement(int increment) {
     estimatedCost += increment;
-    return estimatedCost > appView.options().classInliningInstructionAllowance;
+    return estimatedCost
+        > appView.options().classInlinerOptions().getClassInliningInstructionAllowance();
   }
 
   // TODO(b/143176500): Do not include instructions that will be canonicalized after inlining.
@@ -101,7 +105,7 @@
   //  in the caller, which could then lead to a constant in the second inlinee being canonicalized.
   // TODO(b/143176500): Do not include instructions that will be dead code eliminated as a result of
   //  constant arguments.
-  private int estimateNumberOfNonMaterializingInstructions(InvokeMethod invoke, IRCode inlinee) {
+  private int estimateSizeOfNonMaterializingInstructions(InvokeMethod invoke, IRCode inlinee) {
     int result = 0;
     Set<Value> receiverAliasesInInlinee = null;
     for (Instruction instruction : inlinee.instructions()) {
@@ -123,13 +127,21 @@
             receiverAliasesInInlinee = getReceiverAliasesInInlinee(invoke, inlinee);
           }
           if (receiverAliasesInInlinee.contains(root)) {
-            result++;
+            if (appView.options().isGeneratingClassFiles()) {
+              result++;
+            } else {
+              result += instruction.isInstanceGet() ? Iget.SIZE : Iput.SIZE;
+            }
           }
           break;
 
         case RETURN:
           // Wil not materialize after class inlining.
-          result++;
+          if (appView.options().isGeneratingClassFiles()) {
+            result++;
+          } else {
+            result += Return.SIZE;
+          }
           break;
 
         default:
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/EmptyEnumUnboxer.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EmptyEnumUnboxer.java
index 9320e54..e7edb01 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/enums/EmptyEnumUnboxer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EmptyEnumUnboxer.java
@@ -7,6 +7,7 @@
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.GraphLens;
+import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.analysis.fieldvalueanalysis.StaticFieldValues;
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.Phi;
@@ -41,6 +42,16 @@
   }
 
   @Override
+  public void onMethodPruned(ProgramMethod method) {
+    // Intentionally empty.
+  }
+
+  @Override
+  public void onMethodCodePruned(ProgramMethod method) {
+    // Intentionally empty.
+  }
+
+  @Override
   public void recordEnumState(DexProgramClass clazz, StaticFieldValues staticFieldValues) {
     // Intentionally empty.
   }
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 2cdf22f..659e685 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,6 +7,7 @@
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.GraphLens;
+import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.analysis.fieldvalueanalysis.StaticFieldValues;
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.Phi;
@@ -35,6 +36,10 @@
 
   public abstract void analyzeEnums(IRCode code, MutableMethodConversionOptions conversionOptions);
 
+  public abstract void onMethodPruned(ProgramMethod method);
+
+  public abstract void onMethodCodePruned(ProgramMethod method);
+
   public abstract void recordEnumState(DexProgramClass clazz, StaticFieldValues staticFieldValues);
 
   public abstract Set<Phi> rewriteCode(IRCode code, MethodProcessor methodProcessor);
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxerImpl.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxerImpl.java
index 854a4e0..40e53fb 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxerImpl.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxerImpl.java
@@ -41,6 +41,7 @@
 import com.android.tools.r8.graph.FieldResolutionResult;
 import com.android.tools.r8.graph.GraphLens;
 import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.graph.PrunedItems;
 import com.android.tools.r8.ir.analysis.fieldvalueanalysis.StaticFieldValues;
 import com.android.tools.r8.ir.analysis.fieldvalueanalysis.StaticFieldValues.EnumStaticFieldValues;
 import com.android.tools.r8.ir.analysis.type.ArrayTypeElement;
@@ -642,17 +643,11 @@
         .merge(
             dependencies
                 .rewrittenWithLens(appView)
-                .removeAll(treeFixerResult.getPrunedItems().getRemovedMethods())
-                .removeIf(
-                    appView,
-                    method -> method.getOptimizationInfo().hasBeenInlinedIntoSingleCallSite()))
+                .removeAll(treeFixerResult.getPrunedItems().getRemovedMethods()))
         .merge(
             methodsDependingOnLibraryModelisation
                 .rewrittenWithLens(appView)
-                .removeAll(treeFixerResult.getPrunedItems().getRemovedMethods())
-                .removeIf(
-                    appView,
-                    method -> method.getOptimizationInfo().hasBeenInlinedIntoSingleCallSite()));
+                .removeAll(treeFixerResult.getPrunedItems().getRemovedMethods()));
     methodsDependingOnLibraryModelisation.clear();
 
     updateOptimizationInfos(executorService, feedback, treeFixerResult);
@@ -705,7 +700,10 @@
 
   private void updateKeepInfo(Set<DexType> enumsToUnbox) {
     KeepInfoCollection keepInfo = appView.appInfo().getKeepInfo();
-    keepInfo.mutate(mutator -> mutator.removeKeepInfoForPrunedItems(enumsToUnbox));
+    keepInfo.mutate(
+        mutator ->
+            mutator.removeKeepInfoForPrunedItems(
+                PrunedItems.builder().setRemovedClasses(enumsToUnbox).build()));
   }
 
   public EnumDataMap finishAnalysis() {
@@ -1412,6 +1410,17 @@
   }
 
   @Override
+  public void onMethodPruned(ProgramMethod method) {
+    onMethodCodePruned(method);
+  }
+
+  @Override
+  public void onMethodCodePruned(ProgramMethod method) {
+    enumUnboxingCandidatesInfo.addPrunedMethod(method);
+    methodsDependingOnLibraryModelisation.remove(method.getReference(), appView.graphLens());
+  }
+
+  @Override
   public Set<Phi> rewriteCode(IRCode code, MethodProcessor methodProcessor) {
     // This has no effect during primary processing since the enumUnboxerRewriter is set
     // in between primary and post processing.
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingCandidateInfoCollection.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingCandidateInfoCollection.java
index 386bcb0..39dd787 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingCandidateInfoCollection.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingCandidateInfoCollection.java
@@ -6,6 +6,7 @@
 
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.GraphLens;
@@ -25,6 +26,7 @@
 public class EnumUnboxingCandidateInfoCollection {
 
   private final Map<DexType, EnumUnboxingCandidateInfo> enumTypeToInfo = new ConcurrentHashMap<>();
+  private final Set<DexMethod> prunedMethods = Sets.newIdentityHashSet();
 
   public void addCandidate(
       AppView<AppInfoWithLiveness> appView,
@@ -36,6 +38,10 @@
         new EnumUnboxingCandidateInfo(appView, enumClass, graphLensForPrimaryOptimizationPass));
   }
 
+  public void addPrunedMethod(ProgramMethod method) {
+    prunedMethods.add(method.getReference());
+  }
+
   public void removeCandidate(DexProgramClass enumClass) {
     removeCandidate(enumClass.getType());
   }
@@ -80,6 +86,7 @@
     while (candidateInfoIterator.hasNext()) {
       allMethodDependencies.merge(candidateInfoIterator.next().methodDependencies);
     }
+    allMethodDependencies.removeAll(prunedMethods);
     return allMethodDependencies;
   }
 
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 4959de0..15819c7 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
@@ -8,6 +8,7 @@
 import static com.android.tools.r8.ir.code.Opcodes.ADD;
 import static com.android.tools.r8.ir.code.Opcodes.AND;
 import static com.android.tools.r8.ir.code.Opcodes.ARGUMENT;
+import static com.android.tools.r8.ir.code.Opcodes.ARRAY_GET;
 import static com.android.tools.r8.ir.code.Opcodes.ARRAY_LENGTH;
 import static com.android.tools.r8.ir.code.Opcodes.ASSUME;
 import static com.android.tools.r8.ir.code.Opcodes.CHECK_CAST;
@@ -138,7 +139,7 @@
     DexEncodedMethod definition = method.getDefinition();
     identifyBridgeInfo(definition, code, feedback, timing);
     analyzeReturns(code, feedback, methodProcessor, timing);
-    if (options.enableInlining) {
+    if (options.inlinerOptions().enableInlining) {
       identifyInvokeSemanticsForInlining(definition, code, feedback, timing);
     }
     if (options.enableClassInlining) {
@@ -471,6 +472,12 @@
             }
             break;
 
+          case ARRAY_GET:
+            {
+              builder.setMayHaveOtherSideEffectsThanInstanceFieldAssignments();
+              break;
+            }
+
           default:
             builder
                 .markAllFieldsAsRead()
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/inliner/DefaultInliningReasonStrategy.java b/src/main/java/com/android/tools/r8/ir/optimize/inliner/DefaultInliningReasonStrategy.java
index 43b302b..ef00071 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/inliner/DefaultInliningReasonStrategy.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/inliner/DefaultInliningReasonStrategy.java
@@ -11,23 +11,21 @@
 import com.android.tools.r8.ir.code.InvokeMethod;
 import com.android.tools.r8.ir.conversion.CallSiteInformation;
 import com.android.tools.r8.ir.conversion.MethodProcessor;
-import com.android.tools.r8.ir.optimize.Inliner;
 import com.android.tools.r8.ir.optimize.Inliner.Reason;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.utils.InternalOptions.InlinerOptions;
 
 public class DefaultInliningReasonStrategy implements InliningReasonStrategy {
 
   private final AppView<AppInfoWithLiveness> appView;
   private final CallSiteInformation callSiteInformation;
-  private final Inliner inliner;
+  private final InlinerOptions options;
 
   public DefaultInliningReasonStrategy(
-      AppView<AppInfoWithLiveness> appView,
-      CallSiteInformation callSiteInformation,
-      Inliner inliner) {
+      AppView<AppInfoWithLiveness> appView, CallSiteInformation callSiteInformation) {
     this.appView = appView;
     this.callSiteInformation = callSiteInformation;
-    this.inliner = inliner;
+    this.options = appView.options().inlinerOptions();
   }
 
   @Override
@@ -46,7 +44,7 @@
         && appView.withLiveness().appInfo().isAlwaysInlineMethod(targetReference)) {
       return Reason.ALWAYS;
     }
-    if (appView.options().disableInliningOfLibraryMethodOverrides
+    if (options.disableInliningOfLibraryMethodOverrides
         && targetMethod.isLibraryMethodOverride().isTrue()) {
       // This method will always have an implicit call site from the library, so we won't be able to
       // remove it after inlining even if we have single or dual call site information from the
@@ -56,7 +54,8 @@
     if (isSingleCallerInliningTarget(target)) {
       return Reason.SINGLE_CALLER;
     }
-    if (isDoubleInliningTarget(target, methodProcessor)) {
+    if (isDoubleInliningTarget(target)) {
+      assert methodProcessor.isPrimaryMethodProcessor();
       return Reason.DUAL_CALLER;
     }
     return Reason.SIMPLE;
@@ -69,21 +68,18 @@
     if (appView.appInfo().isNeverInlineDueToSingleCallerMethod(method)) {
       return false;
     }
-    if (appView.options().testing.validInliningReasons != null
-        && !appView.options().testing.validInliningReasons.contains(Reason.SINGLE_CALLER)) {
+    if (appView.testing().validInliningReasons != null
+        && !appView.testing().validInliningReasons.contains(Reason.SINGLE_CALLER)) {
       return false;
     }
     return true;
   }
 
-  private boolean isDoubleInliningTarget(ProgramMethod candidate, MethodProcessor methodProcessor) {
-    if (methodProcessor.isPrimaryMethodProcessor() || methodProcessor.isPostMethodProcessor()) {
-      if (callSiteInformation.hasDoubleCallSite(candidate)
-          || inliner.isDoubleInlineSelectedTarget(candidate)) {
-        // 10 is found from measuring.
-        return candidate.getDefinition().getCode().estimatedSizeForInliningAtMost(10);
-      }
-    }
-    return false;
+  private boolean isDoubleInliningTarget(ProgramMethod candidate) {
+    return callSiteInformation.hasDoubleCallSite(candidate)
+        && candidate
+            .getDefinition()
+            .getCode()
+            .estimatedSizeForInliningAtMost(options.getDoubleInliningInstructionLimit());
   }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/outliner/OutlineCollection.java b/src/main/java/com/android/tools/r8/ir/optimize/outliner/OutlineCollection.java
index 78dc248..713322d 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/outliner/OutlineCollection.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/outliner/OutlineCollection.java
@@ -34,6 +34,11 @@
     this.appliedGraphLens = graphLensForPrimaryOptimizationPass;
   }
 
+  public void remove(AppView<AppInfoWithLiveness> appView, ProgramMethod method) {
+    assert appView.graphLens() == appliedGraphLens;
+    outlines.remove(method.getReference());
+  }
+
   public void set(
       AppView<AppInfoWithLiveness> appView, ProgramMethod method, List<Outline> outlinesForMethod) {
     assert appView.graphLens() == appliedGraphLens;
@@ -101,9 +106,7 @@
             assert false;
             return;
           }
-          if (method.getOptimizationInfo().hasBeenInlinedIntoSingleCallSite()) {
-            return;
-          }
+          assert !method.getOptimizationInfo().hasBeenInlinedIntoSingleCallSite();
           for (Outline outline : outlinesForMethod) {
             methodsPerOutline.computeIfAbsent(outline, ignoreKey(ArrayList::new)).add(method);
           }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/outliner/Outliner.java b/src/main/java/com/android/tools/r8/ir/optimize/outliner/Outliner.java
index a2bd90f..b3ed5f5 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/outliner/Outliner.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/outliner/Outliner.java
@@ -6,6 +6,7 @@
 
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.GraphLens;
+import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.conversion.IRConverter;
 import com.android.tools.r8.ir.optimize.OutlinerImpl;
@@ -29,6 +30,16 @@
       }
 
       @Override
+      public void onMethodPruned(ProgramMethod method) {
+        // Intentionally empty.
+      }
+
+      @Override
+      public void onMethodCodePruned(ProgramMethod method) {
+        // Intentionally empty.
+      }
+
+      @Override
       public void prepareForPrimaryOptimizationPass(GraphLens graphLensForPrimaryOptimizationPass) {
         // Intentionally empty.
       }
@@ -52,6 +63,10 @@
 
   public abstract void collectOutlineSites(IRCode code, Timing timing);
 
+  public abstract void onMethodPruned(ProgramMethod method);
+
+  public abstract void onMethodCodePruned(ProgramMethod method);
+
   public abstract void prepareForPrimaryOptimizationPass(
       GraphLens graphLensForPrimaryOptimizationPass);
 
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/typechecks/CheckCastAndInstanceOfMethodSpecialization.java b/src/main/java/com/android/tools/r8/ir/optimize/typechecks/CheckCastAndInstanceOfMethodSpecialization.java
index 18a964f..13f384e 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/typechecks/CheckCastAndInstanceOfMethodSpecialization.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/typechecks/CheckCastAndInstanceOfMethodSpecialization.java
@@ -162,7 +162,7 @@
     // Remove the method and notify other optimizations that the override has been removed to allow
     // the optimizations to fixup their state.
     method.getHolder().removeMethod(method.getReference());
-    converter.pruneMethod(method);
+    converter.onMethodPruned(method);
   }
 
   private ProgramMethod resolveOnSuperClass(ProgramMethod method) {
diff --git a/src/main/java/com/android/tools/r8/naming/IdentifierNameStringMarker.java b/src/main/java/com/android/tools/r8/naming/IdentifierNameStringMarker.java
index e0de1d5..391f2c7 100644
--- a/src/main/java/com/android/tools/r8/naming/IdentifierNameStringMarker.java
+++ b/src/main/java/com/android/tools/r8/naming/IdentifierNameStringMarker.java
@@ -12,6 +12,7 @@
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexEncodedField;
 import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.graph.DexMember;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexReference;
 import com.android.tools.r8.graph.DexString;
@@ -52,7 +53,7 @@
 public class IdentifierNameStringMarker {
 
   private final AppView<AppInfoWithLiveness> appView;
-  private final Object2BooleanMap<DexReference> identifierNameStrings;
+  private final Object2BooleanMap<DexMember<?, ?>> identifierNameStrings;
 
   public IdentifierNameStringMarker(AppView<AppInfoWithLiveness> appView) {
     this.appView = appView;
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagator.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagator.java
index 922e198..158a8de 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagator.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagator.java
@@ -224,9 +224,13 @@
    *
    * <p>Therefore, we assert that we only find a method state for direct methods.
    */
-  public void pruneMethod(ProgramMethod method) {
+  public void onMethodPruned(ProgramMethod method) {
     assert codeScanner != null;
     MethodState methodState = codeScanner.getMethodStates().removeOrElse(method, null);
     assert methodState == null || method.getDefinition().belongsToDirectPool();
   }
+
+  public void onMethodCodePruned(ProgramMethod method) {
+    // Intentionally empty.
+  }
 }
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 45630fa..6f3e907 100644
--- a/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
+++ b/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
@@ -58,6 +58,7 @@
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.ListUtils;
 import com.android.tools.r8.utils.PredicateSet;
+import com.android.tools.r8.utils.ThreadUtils;
 import com.android.tools.r8.utils.TraversalContinuation;
 import com.android.tools.r8.utils.Visibility;
 import com.android.tools.r8.utils.WorkList;
@@ -68,12 +69,16 @@
 import it.unimi.dsi.fastutil.ints.Int2ReferenceMap;
 import it.unimi.dsi.fastutil.objects.Object2BooleanMap;
 import java.util.ArrayDeque;
+import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.Deque;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Future;
 import java.util.function.Consumer;
 import java.util.stream.Collectors;
 
@@ -179,13 +184,13 @@
    * All methods and fields whose value *must* never be propagated due to a configuration directive.
    * (testing only).
    */
-  private final Set<DexReference> neverPropagateValue;
+  private final Set<DexMember<?, ?>> neverPropagateValue;
   /**
    * All items with -identifiernamestring rule. Bound boolean value indicates the rule is explicitly
    * specified by users (<code>true</code>) or not, i.e., implicitly added by R8 (<code>false</code>
    * ).
    */
-  public final Object2BooleanMap<DexReference> identifierNameStrings;
+  public final Object2BooleanMap<DexMember<?, ?>> identifierNameStrings;
   /** A set of types that have been removed by the {@link TreePruner}. */
   final Set<DexType> prunedTypes;
   /** A map from switchmap class types to their corresponding switchmaps. */
@@ -230,8 +235,8 @@
       Set<DexType> noClassMerging,
       Set<DexType> noVerticalClassMerging,
       Set<DexType> noHorizontalClassMerging,
-      Set<DexReference> neverPropagateValue,
-      Object2BooleanMap<DexReference> identifierNameStrings,
+      Set<DexMember<?, ?>> neverPropagateValue,
+      Object2BooleanMap<DexMember<?, ?>> identifierNameStrings,
       Set<DexType> prunedTypes,
       Map<DexField, Int2ReferenceMap<DexField>> switchMaps,
       Set<DexType> lockCandidates,
@@ -283,7 +288,7 @@
         previous.getMainDexInfo(),
         previous.deadProtoTypes,
         previous.getMissingClasses(),
-        CollectionUtils.mergeSets(previous.liveTypes, committedItems.getCommittedProgramTypes()),
+        CollectionUtils.addAll(previous.liveTypes, committedItems.getCommittedProgramTypes()),
         previous.targetedMethods,
         previous.failedMethodResolutionTargets,
         previous.failedFieldResolutionTargets,
@@ -320,52 +325,208 @@
         previous.initClassReferences);
   }
 
-  private AppInfoWithLiveness(AppInfoWithLiveness previous, PrunedItems prunedItems) {
+  private AppInfoWithLiveness(
+      AppInfoWithLiveness previous,
+      PrunedItems prunedItems,
+      ExecutorService executorService,
+      List<Future<?>> futures) {
     this(
         previous.getSyntheticItems().commitPrunedItems(prunedItems),
         previous.getClassToFeatureSplitMap().withoutPrunedItems(prunedItems),
         previous.getMainDexInfo().withoutPrunedItems(prunedItems),
         previous.deadProtoTypes,
         previous.getMissingClasses(),
-        prunedItems.hasRemovedClasses()
-            ? Sets.difference(previous.liveTypes, prunedItems.getRemovedClasses())
-            : previous.liveTypes,
-        previous.targetedMethods,
-        previous.failedMethodResolutionTargets,
-        previous.failedFieldResolutionTargets,
-        previous.bootstrapMethods,
-        previous.methodsTargetedByInvokeDynamic,
-        previous.virtualMethodsTargetedByInvokeDirect,
-        previous.liveMethods,
+        pruneClasses(previous.liveTypes, prunedItems, executorService, futures),
+        pruneMethods(previous.targetedMethods, prunedItems, executorService, futures),
+        pruneMethods(previous.failedMethodResolutionTargets, prunedItems, executorService, futures),
+        pruneFields(previous.failedFieldResolutionTargets, prunedItems, executorService, futures),
+        pruneMethods(previous.bootstrapMethods, prunedItems, executorService, futures),
+        pruneMethods(
+            previous.methodsTargetedByInvokeDynamic, prunedItems, executorService, futures),
+        pruneMethods(
+            previous.virtualMethodsTargetedByInvokeDirect, prunedItems, executorService, futures),
+        pruneMethods(previous.liveMethods, prunedItems, executorService, futures),
         previous.fieldAccessInfoCollection,
         previous.methodAccessInfoCollection,
         previous.objectAllocationInfoCollection,
         previous.callSites,
         extendPinnedItems(previous, prunedItems.getAdditionalPinnedItems()),
         previous.mayHaveSideEffects,
-        previous.noSideEffects,
-        previous.assumedValues,
-        previous.alwaysInline,
-        previous.neverInline,
-        previous.neverInlineDueToSingleCaller,
-        previous.whyAreYouNotInlining,
-        previous.keepConstantArguments,
-        previous.keepUnusedArguments,
-        previous.reprocess,
-        previous.neverReprocess,
+        pruneMapFromMembers(previous.noSideEffects, prunedItems, executorService, futures),
+        pruneMapFromMembers(previous.assumedValues, prunedItems, executorService, futures),
+        pruneMethods(previous.alwaysInline, prunedItems, executorService, futures),
+        pruneMethods(previous.neverInline, prunedItems, executorService, futures),
+        pruneMethods(previous.neverInlineDueToSingleCaller, prunedItems, executorService, futures),
+        pruneMethods(previous.whyAreYouNotInlining, prunedItems, executorService, futures),
+        pruneMethods(previous.keepConstantArguments, prunedItems, executorService, futures),
+        pruneMethods(previous.keepUnusedArguments, prunedItems, executorService, futures),
+        pruneMethods(previous.reprocess, prunedItems, executorService, futures),
+        pruneMethods(previous.neverReprocess, prunedItems, executorService, futures),
         previous.alwaysClassInline,
-        previous.neverClassInline,
-        previous.noClassMerging,
-        previous.noVerticalClassMerging,
-        previous.noHorizontalClassMerging,
-        previous.neverPropagateValue,
-        previous.identifierNameStrings,
+        pruneClasses(previous.neverClassInline, prunedItems, executorService, futures),
+        pruneClasses(previous.noClassMerging, prunedItems, executorService, futures),
+        pruneClasses(previous.noVerticalClassMerging, prunedItems, executorService, futures),
+        pruneClasses(previous.noHorizontalClassMerging, prunedItems, executorService, futures),
+        pruneMembers(previous.neverPropagateValue, prunedItems, executorService, futures),
+        pruneMapFromMembers(previous.identifierNameStrings, prunedItems, executorService, futures),
         prunedItems.hasRemovedClasses()
             ? CollectionUtils.mergeSets(previous.prunedTypes, prunedItems.getRemovedClasses())
             : previous.prunedTypes,
         previous.switchMaps,
-        previous.lockCandidates,
-        previous.initClassReferences);
+        pruneClasses(previous.lockCandidates, prunedItems, executorService, futures),
+        pruneMapFromClasses(previous.initClassReferences, prunedItems, executorService, futures));
+  }
+
+  private static Set<DexType> pruneClasses(
+      Set<DexType> methods,
+      PrunedItems prunedItems,
+      ExecutorService executorService,
+      List<Future<?>> futures) {
+    return pruneItems(methods, prunedItems.getRemovedClasses(), executorService, futures);
+  }
+
+  private static Set<DexField> pruneFields(
+      Set<DexField> fields,
+      PrunedItems prunedItems,
+      ExecutorService executorService,
+      List<Future<?>> futures) {
+    return pruneItems(fields, prunedItems.getRemovedFields(), executorService, futures);
+  }
+
+  private static Set<DexMember<?, ?>> pruneMembers(
+      Set<DexMember<?, ?>> members,
+      PrunedItems prunedItems,
+      ExecutorService executorService,
+      List<Future<?>> futures) {
+    if (prunedItems.hasRemovedMembers()) {
+      futures.add(
+          ThreadUtils.processAsynchronously(
+              () -> {
+                Set<DexField> removedFields = prunedItems.getRemovedFields();
+                Set<DexMethod> removedMethods = prunedItems.getRemovedMethods();
+                if (members.size() <= removedFields.size() + removedMethods.size()) {
+                  members.removeIf(
+                      member ->
+                          member.isDexField()
+                              ? removedFields.contains(member.asDexField())
+                              : removedMethods.contains(member.asDexMethod()));
+                } else {
+                  removedFields.forEach(members::remove);
+                  removedMethods.forEach(members::remove);
+                }
+              },
+              executorService));
+    }
+    return members;
+  }
+
+  private static Set<DexMethod> pruneMethods(
+      Set<DexMethod> methods,
+      PrunedItems prunedItems,
+      ExecutorService executorService,
+      List<Future<?>> futures) {
+    return pruneItems(methods, prunedItems.getRemovedMethods(), executorService, futures);
+  }
+
+  private static <T> Set<T> pruneItems(
+      Set<T> items, Set<T> removedItems, ExecutorService executorService, List<Future<?>> futures) {
+    if (!removedItems.isEmpty()) {
+      futures.add(
+          ThreadUtils.processAsynchronously(
+              () -> {
+                if (items.size() <= removedItems.size()) {
+                  items.removeAll(removedItems);
+                } else {
+                  removedItems.forEach(items::remove);
+                }
+              },
+              executorService));
+    }
+    return items;
+  }
+
+  private static <V> Map<DexType, V> pruneMapFromClasses(
+      Map<DexType, V> map,
+      PrunedItems prunedItems,
+      ExecutorService executorService,
+      List<Future<?>> futures) {
+    return pruneMap(map, prunedItems.getRemovedClasses(), executorService, futures);
+  }
+
+  private static <V> Map<DexMember<?, ?>, V> pruneMapFromMembers(
+      Map<DexMember<?, ?>, V> map,
+      PrunedItems prunedItems,
+      ExecutorService executorService,
+      List<Future<?>> futures) {
+    if (prunedItems.hasRemovedMembers()) {
+      futures.add(
+          ThreadUtils.processAsynchronously(
+              () -> {
+                Set<DexField> removedFields = prunedItems.getRemovedFields();
+                Set<DexMethod> removedMethods = prunedItems.getRemovedMethods();
+                if (map.size() <= removedFields.size() + removedMethods.size()) {
+                  map.keySet()
+                      .removeIf(
+                          member ->
+                              member.isDexField()
+                                  ? removedFields.contains(member.asDexField())
+                                  : removedMethods.contains(member.asDexMethod()));
+                } else {
+                  removedFields.forEach(map::remove);
+                  removedMethods.forEach(map::remove);
+                }
+              },
+              executorService));
+    }
+    return map;
+  }
+
+  private static Object2BooleanMap<DexMember<?, ?>> pruneMapFromMembers(
+      Object2BooleanMap<DexMember<?, ?>> map,
+      PrunedItems prunedItems,
+      ExecutorService executorService,
+      List<Future<?>> futures) {
+    if (prunedItems.hasRemovedMembers()) {
+      futures.add(
+          ThreadUtils.processAsynchronously(
+              () -> {
+                Set<DexField> removedFields = prunedItems.getRemovedFields();
+                Set<DexMethod> removedMethods = prunedItems.getRemovedMethods();
+                if (map.size() <= removedFields.size() + removedMethods.size()) {
+                  map.keySet()
+                      .removeIf(
+                          member ->
+                              member.isDexField()
+                                  ? removedFields.contains(member.asDexField())
+                                  : removedMethods.contains(member.asDexMethod()));
+                } else {
+                  removedFields.forEach(map::remove);
+                  removedMethods.forEach(map::remove);
+                }
+              },
+              executorService));
+    }
+    return map;
+  }
+
+  private static <K, V> Map<K, V> pruneMap(
+      Map<K, V> map,
+      Set<K> removedItems,
+      ExecutorService executorService,
+      List<Future<?>> futures) {
+    if (!removedItems.isEmpty()) {
+      futures.add(
+          ThreadUtils.processAsynchronously(
+              () -> {
+                if (map.size() <= removedItems.size()) {
+                  map.keySet().removeAll(removedItems);
+                } else {
+                  removedItems.forEach(map::remove);
+                }
+              },
+              executorService));
+    }
+    return map;
   }
 
   private boolean verify() {
@@ -1038,7 +1199,8 @@
    * DexApplication object.
    */
   @Override
-  public AppInfoWithLiveness prunedCopyFrom(PrunedItems prunedItems) {
+  public AppInfoWithLiveness prunedCopyFrom(
+      PrunedItems prunedItems, ExecutorService executorService) throws ExecutionException {
     assert getClass() == AppInfoWithLiveness.class;
     assert checkIfObsolete();
     if (prunedItems.isEmpty()) {
@@ -1049,14 +1211,15 @@
       // Rebuild the hierarchy.
       objectAllocationInfoCollection.mutate(
           mutator -> mutator.removeAllocationsForPrunedItems(prunedItems), this);
-      keepInfo.mutate(
-          keepInfo -> keepInfo.removeKeepInfoForPrunedItems(prunedItems.getRemovedClasses()));
+      keepInfo.mutate(keepInfo -> keepInfo.removeKeepInfoForPrunedItems(prunedItems));
+    } else if (prunedItems.hasRemovedMembers()) {
+      keepInfo.mutate(keepInfo -> keepInfo.removeKeepInfoForPrunedItems(prunedItems));
     }
-    if (!prunedItems.getRemovedMethods().isEmpty()) {
-      keepInfo.mutate(
-          keepInfo -> keepInfo.removeKeepInfoForPrunedItems(prunedItems.getRemovedMethods()));
-    }
-    return new AppInfoWithLiveness(this, prunedItems);
+    List<Future<?>> futures = new ArrayList<>();
+    AppInfoWithLiveness appInfoWithLiveness =
+        new AppInfoWithLiveness(this, prunedItems, executorService, futures);
+    ThreadUtils.awaitFutures(futures);
+    return appInfoWithLiveness;
   }
 
   public AppInfoWithLiveness rebuildWithLiveness(CommittedItems committedItems) {
@@ -1084,14 +1247,14 @@
         getMainDexInfo().rewrittenWithLens(getSyntheticItems(), lens),
         deadProtoTypes,
         getMissingClasses().commitSyntheticItems(committedItems),
-        lens.rewriteTypes(liveTypes),
-        lens.rewriteMethods(targetedMethods),
-        lens.rewriteMethods(failedMethodResolutionTargets),
-        lens.rewriteFields(failedFieldResolutionTargets),
-        lens.rewriteMethods(bootstrapMethods),
-        lens.rewriteMethods(methodsTargetedByInvokeDynamic),
-        lens.rewriteMethods(virtualMethodsTargetedByInvokeDirect),
-        lens.rewriteMethods(liveMethods),
+        lens.rewriteReferences(liveTypes),
+        lens.rewriteReferences(targetedMethods),
+        lens.rewriteReferences(failedMethodResolutionTargets),
+        lens.rewriteReferences(failedFieldResolutionTargets),
+        lens.rewriteReferences(bootstrapMethods),
+        lens.rewriteReferences(methodsTargetedByInvokeDynamic),
+        lens.rewriteReferences(virtualMethodsTargetedByInvokeDirect),
+        lens.rewriteReferences(liveMethods),
         fieldAccessInfoCollection.rewrittenWithLens(definitionSupplier, lens),
         methodAccessInfoCollection.rewrittenWithLens(definitionSupplier, lens),
         objectAllocationInfoCollection.rewrittenWithLens(definitionSupplier, lens),
@@ -1102,25 +1265,25 @@
         // Drop assume rules in case of collisions.
         lens.rewriteReferenceKeys(noSideEffects, rules -> null),
         lens.rewriteReferenceKeys(assumedValues, rules -> null),
-        lens.rewriteMethods(alwaysInline),
-        lens.rewriteMethods(neverInline),
-        lens.rewriteMethods(neverInlineDueToSingleCaller),
-        lens.rewriteMethods(whyAreYouNotInlining),
-        lens.rewriteMethods(keepConstantArguments),
-        lens.rewriteMethods(keepUnusedArguments),
-        lens.rewriteMethods(reprocess),
-        lens.rewriteMethods(neverReprocess),
+        lens.rewriteReferences(alwaysInline),
+        lens.rewriteReferences(neverInline),
+        lens.rewriteReferences(neverInlineDueToSingleCaller),
+        lens.rewriteReferences(whyAreYouNotInlining),
+        lens.rewriteReferences(keepConstantArguments),
+        lens.rewriteReferences(keepUnusedArguments),
+        lens.rewriteReferences(reprocess),
+        lens.rewriteReferences(neverReprocess),
         alwaysClassInline.rewriteItems(lens::lookupType),
-        lens.rewriteTypes(neverClassInline),
-        lens.rewriteTypes(noClassMerging),
-        lens.rewriteTypes(noVerticalClassMerging),
-        lens.rewriteTypes(noHorizontalClassMerging),
+        lens.rewriteReferences(neverClassInline),
+        lens.rewriteReferences(noClassMerging),
+        lens.rewriteReferences(noVerticalClassMerging),
+        lens.rewriteReferences(noHorizontalClassMerging),
         lens.rewriteReferences(neverPropagateValue),
         lens.rewriteReferenceKeys(identifierNameStrings),
         // Don't rewrite pruned types - the removed types are identified by their original name.
         prunedTypes,
         lens.rewriteFieldKeys(switchMaps),
-        lens.rewriteTypes(lockCandidates),
+        lens.rewriteReferences(lockCandidates),
         rewriteInitClassReferences(lens));
   }
 
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 ec6c061..a7b0a76 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -261,7 +261,7 @@
   private final ObjectAllocationInfoCollectionImpl.Builder objectAllocationInfoCollection;
   private final Map<DexCallSite, ProgramMethodSet> callSites = new IdentityHashMap<>();
 
-  private final Set<DexReference> identifierNameStrings = Sets.newIdentityHashSet();
+  private final Set<DexMember<?, ?>> identifierNameStrings = Sets.newIdentityHashSet();
 
   private final AndroidApiLevelCompute apiLevelCompute;
 
@@ -3830,13 +3830,13 @@
     return builder.build();
   }
 
-  private static Object2BooleanMap<DexReference> joinIdentifierNameStrings(
-      Set<DexReference> explicit, Set<DexReference> implicit) {
-    Object2BooleanMap<DexReference> result = new Object2BooleanArrayMap<>();
-    for (DexReference e : explicit) {
+  private static Object2BooleanMap<DexMember<?, ?>> joinIdentifierNameStrings(
+      Set<DexMember<?, ?>> explicit, Set<DexMember<?, ?>> implicit) {
+    Object2BooleanMap<DexMember<?, ?>> result = new Object2BooleanArrayMap<>();
+    for (DexMember<?, ?> e : explicit) {
       result.putIfAbsent(e, true);
     }
-    for (DexReference i : implicit) {
+    for (DexMember<?, ?> i : implicit) {
       result.putIfAbsent(i, false);
     }
     return result;
diff --git a/src/main/java/com/android/tools/r8/shaking/KeepInfoCollection.java b/src/main/java/com/android/tools/r8/shaking/KeepInfoCollection.java
index c260f3e..36555ac 100644
--- a/src/main/java/com/android/tools/r8/shaking/KeepInfoCollection.java
+++ b/src/main/java/com/android/tools/r8/shaking/KeepInfoCollection.java
@@ -23,6 +23,7 @@
 import com.android.tools.r8.graph.ProgramField;
 import com.android.tools.r8.graph.ProgramMember;
 import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.graph.PrunedItems;
 import com.android.tools.r8.shaking.KeepFieldInfo.Joiner;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.MapUtils;
@@ -244,20 +245,16 @@
       this.methodRuleInstances = methodRuleInstances;
     }
 
-    public void removeKeepInfoForPrunedItems(Set<? extends DexReference> removedReferences) {
-      keepClassInfo.keySet().removeIf(removedReferences::contains);
-      keepFieldInfo
-          .keySet()
-          .removeIf(
-              field ->
-                  (removedReferences.contains(field)
-                      || removedReferences.contains(field.getHolderType())));
-      keepMethodInfo
-          .keySet()
-          .removeIf(
-              method ->
-                  (removedReferences.contains(method)
-                      || removedReferences.contains(method.getHolderType())));
+    public void removeKeepInfoForPrunedItems(PrunedItems prunedItems) {
+      if (prunedItems.hasRemovedClasses()) {
+        keepClassInfo.keySet().removeAll(prunedItems.getRemovedClasses());
+      }
+      if (prunedItems.hasRemovedClasses() || prunedItems.hasRemovedFields()) {
+        keepFieldInfo.keySet().removeIf(prunedItems::isRemoved);
+      }
+      if (prunedItems.hasRemovedClasses() || prunedItems.hasRemovedMembers()) {
+        keepMethodInfo.keySet().removeIf(prunedItems::isRemoved);
+      }
     }
 
     @Override
diff --git a/src/main/java/com/android/tools/r8/shaking/RootSetUtils.java b/src/main/java/com/android/tools/r8/shaking/RootSetUtils.java
index 3dc1bc5..b105a80 100644
--- a/src/main/java/com/android/tools/r8/shaking/RootSetUtils.java
+++ b/src/main/java/com/android/tools/r8/shaking/RootSetUtils.java
@@ -122,14 +122,14 @@
     private final Set<DexType> noUnusedInterfaceRemoval = Sets.newIdentityHashSet();
     private final Set<DexType> noVerticalClassMerging = Sets.newIdentityHashSet();
     private final Set<DexType> noHorizontalClassMerging = Sets.newIdentityHashSet();
-    private final Set<DexReference> neverPropagateValue = Sets.newIdentityHashSet();
+    private final Set<DexMember<?, ?>> neverPropagateValue = Sets.newIdentityHashSet();
     private final Map<DexType, Set<ProguardKeepRuleBase>> dependentKeepClassCompatRule =
         new IdentityHashMap<>();
     private final Map<DexReference, ProguardMemberRule> mayHaveSideEffects =
         new IdentityHashMap<>();
     private final Map<DexMember<?, ?>, ProguardMemberRule> noSideEffects = new IdentityHashMap<>();
     private final Map<DexMember<?, ?>, ProguardMemberRule> assumedValues = new IdentityHashMap<>();
-    private final Set<DexReference> identifierNameStrings = Sets.newIdentityHashSet();
+    private final Set<DexMember<?, ?>> identifierNameStrings = Sets.newIdentityHashSet();
     private final Queue<DelayedRootSetActionItem> delayedRootSetActionItems =
         new ConcurrentLinkedQueue<>();
     private final InternalOptions options;
@@ -1612,11 +1612,11 @@
     public final Set<DexType> noUnusedInterfaceRemoval;
     public final Set<DexType> noVerticalClassMerging;
     public final Set<DexType> noHorizontalClassMerging;
-    public final Set<DexReference> neverPropagateValue;
+    public final Set<DexMember<?, ?>> neverPropagateValue;
     public final Map<DexReference, ProguardMemberRule> mayHaveSideEffects;
     public final Map<DexMember<?, ?>, ProguardMemberRule> noSideEffects;
     public final Map<DexMember<?, ?>, ProguardMemberRule> assumedValues;
-    public final Set<DexReference> identifierNameStrings;
+    public final Set<DexMember<?, ?>> identifierNameStrings;
     public final Set<ProguardIfRule> ifRules;
 
     private RootSet(
@@ -1636,12 +1636,12 @@
         Set<DexType> noUnusedInterfaceRemoval,
         Set<DexType> noVerticalClassMerging,
         Set<DexType> noHorizontalClassMerging,
-        Set<DexReference> neverPropagateValue,
+        Set<DexMember<?, ?>> neverPropagateValue,
         Map<DexReference, ProguardMemberRule> mayHaveSideEffects,
         Map<DexMember<?, ?>, ProguardMemberRule> noSideEffects,
         Map<DexMember<?, ?>, ProguardMemberRule> assumedValues,
         Map<DexType, Set<ProguardKeepRuleBase>> dependentKeepClassCompatRule,
-        Set<DexReference> identifierNameStrings,
+        Set<DexMember<?, ?>> identifierNameStrings,
         Set<ProguardIfRule> ifRules,
         List<DelayedRootSetActionItem> delayedRootSetActionItems,
         ProgramMethodMap<ProgramMethod> pendingMethodMoveInverse) {
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 02c89bf..e157ff8 100644
--- a/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
+++ b/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
@@ -48,6 +48,7 @@
 import com.android.tools.r8.graph.MethodResolutionResult;
 import com.android.tools.r8.graph.ObjectAllocationInfoCollection;
 import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.graph.PrunedItems;
 import com.android.tools.r8.graph.RewrittenPrototypeDescription;
 import com.android.tools.r8.graph.SubtypingInfo;
 import com.android.tools.r8.graph.TopDownClassHierarchyTraversal;
@@ -717,7 +718,10 @@
                 appView, lensBuilder, verticallyMergedClasses, synthesizedBridges)
             .fixupTypeReferences();
     KeepInfoCollection keepInfo = appView.appInfo().getKeepInfo();
-    keepInfo.mutate(mutator -> mutator.removeKeepInfoForPrunedItems(mergedClasses.keySet()));
+    keepInfo.mutate(
+        mutator ->
+            mutator.removeKeepInfoForPrunedItems(
+                PrunedItems.builder().setRemovedClasses(mergedClasses.keySet()).build()));
     timing.end();
 
     assert lens != null;
@@ -1867,7 +1871,7 @@
   }
 
   private AbortReason disallowInlining(ProgramMethod method, DexProgramClass context) {
-    if (appView.options().enableInlining) {
+    if (appView.options().inlinerOptions().enableInlining) {
       Code code = method.getDefinition().getCode();
       if (code.isCfCode()) {
         CfCode cfCode = code.asCfCode();
diff --git a/src/main/java/com/android/tools/r8/synthesis/SyntheticFinalization.java b/src/main/java/com/android/tools/r8/synthesis/SyntheticFinalization.java
index 53214f3..0ce464d 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SyntheticFinalization.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticFinalization.java
@@ -54,6 +54,8 @@
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
 import java.util.function.BiConsumer;
 import java.util.function.Consumer;
 import java.util.function.Predicate;
@@ -152,7 +154,8 @@
     this.committed = committed;
   }
 
-  public static void finalize(AppView<AppInfo> appView) {
+  public static void finalize(AppView<AppInfo> appView, ExecutorService executorService)
+      throws ExecutionException {
     assert !appView.appInfo().hasClassHierarchy();
     assert !appView.appInfo().hasLiveness();
     Result result = appView.getSyntheticItems().computeFinalSynthetics(appView);
@@ -168,10 +171,12 @@
                       .rewrittenWithLens(appView.getSyntheticItems(), result.lens)));
       appView.setGraphLens(result.lens);
     }
-    appView.pruneItems(result.prunedItems);
+    appView.pruneItems(result.prunedItems, executorService);
   }
 
-  public static void finalizeWithClassHierarchy(AppView<AppInfoWithClassHierarchy> appView) {
+  public static void finalizeWithClassHierarchy(
+      AppView<AppInfoWithClassHierarchy> appView, ExecutorService executorService)
+      throws ExecutionException {
     assert !appView.appInfo().hasLiveness();
     Result result = appView.getSyntheticItems().computeFinalSynthetics(appView);
     appView.setAppInfo(appView.appInfo().rebuildWithClassHierarchy(result.commit));
@@ -187,10 +192,12 @@
                       .getMainDexInfo()
                       .rewrittenWithLens(appView.getSyntheticItems(), result.lens)));
     }
-    appView.pruneItems(result.prunedItems);
+    appView.pruneItems(result.prunedItems, executorService);
   }
 
-  public static void finalizeWithLiveness(AppView<AppInfoWithLiveness> appView) {
+  public static void finalizeWithLiveness(
+      AppView<AppInfoWithLiveness> appView, ExecutorService executorService)
+      throws ExecutionException {
     Result result = appView.getSyntheticItems().computeFinalSynthetics(appView);
     appView.setAppInfo(appView.appInfo().rebuildWithMainDexInfo(result.mainDexInfo));
     if (result.lens != null) {
@@ -199,7 +206,7 @@
       assert result.commit.getApplication() == appView.appInfo().app();
     }
     appView.setAppInfo(appView.appInfo().rebuildWithLiveness(result.commit));
-    appView.pruneItems(result.prunedItems);
+    appView.pruneItems(result.prunedItems, executorService);
   }
 
   Result computeFinalSynthetics(AppView<?> appView) {
diff --git a/src/main/java/com/android/tools/r8/utils/CollectionUtils.java b/src/main/java/com/android/tools/r8/utils/CollectionUtils.java
index 6af263f..c0e8750 100644
--- a/src/main/java/com/android/tools/r8/utils/CollectionUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/CollectionUtils.java
@@ -13,6 +13,11 @@
 
 public class CollectionUtils {
 
+  public static <S, T extends Collection<S>> T addAll(T collection, Collection<S> elementsToAdd) {
+    collection.addAll(elementsToAdd);
+    return collection;
+  }
+
   public static <T> Set<T> mergeSets(Collection<T> first, Collection<T> second) {
     ImmutableSet.Builder<T> builder = ImmutableSet.builder();
     builder.addAll(first);
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 24e9259..78aab7d 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -216,7 +216,7 @@
   }
 
   void enableProtoShrinking() {
-    applyInliningToInlinee = true;
+    inlinerOptions.applyInliningToInlinee = true;
     enableFieldBitAccessAnalysis = true;
     protoShrinking.enableGeneratedMessageLiteShrinking = true;
     protoShrinking.enableGeneratedMessageLiteBuilderShrinking = true;
@@ -231,7 +231,7 @@
   }
 
   public void disableGlobalOptimizations() {
-    enableInlining = false;
+    inlinerOptions.enableInlining = false;
     enableClassInlining = false;
     enableClassStaticizer = false;
     enableDevirtualization = false;
@@ -272,17 +272,7 @@
   public boolean enableVerticalClassMerging = true;
   public boolean enableUnusedInterfaceRemoval = true;
   public boolean enableDevirtualization = true;
-  public boolean enableInlining =
-      !Version.isDevelopmentVersion()
-          || System.getProperty("com.android.tools.r8.disableinlining") == null;
   public boolean enableEnumUnboxing = true;
-  // TODO(b/141451716): Evaluate the effect of allowing inlining in the inlinee.
-  public boolean applyInliningToInlinee =
-      System.getProperty("com.android.tools.r8.applyInliningToInlinee") != null;
-  public int applyInliningToInlineeMaxDepth = 0;
-  public boolean enableInliningOfInvokesWithClassInitializationSideEffects = true;
-  public boolean enableInliningOfInvokesWithNullableReceivers = true;
-  public boolean disableInliningOfLibraryMethodOverrides = true;
   public boolean enableSimpleInliningConstraints = true;
   public final int simpleInliningConstraintThreshold = 0;
   public boolean enableClassInlining = true;
@@ -318,25 +308,6 @@
     return 16383;
   }
 
-  // TODO(b/141719453): The inlining limit at least should be consistent with normal inlining.
-  public int classInliningInstructionLimit = 10;
-  public int classInliningInstructionAllowance = 50;
-  // This defines the limit of instructions in the inlinee
-  public int inliningInstructionLimit =
-      !Version.isDevelopmentVersion()
-          ? 3
-          : System.getProperty("com.android.tools.r8.inliningInstructionLimit") != null
-              ? Integer.parseInt(
-                  System.getProperty("com.android.tools.r8.inliningInstructionLimit"))
-              : 3;
-  // This defines how many instructions of inlinees we can inlinee overall.
-  public int inliningInstructionAllowance = 1500;
-  // Maximum number of distinct values in a method that may be used in a monitor-enter instruction.
-  public int inliningMonitorEnterValuesAllowance = 4;
-  // Maximum number of control flow resolution blocks that setup the register state before
-  // the actual catch handler allowed when inlining. Threshold found empirically by testing on
-  // GMS Core.
-  public int inliningControlFlowResolutionBlocksThreshold = 15;
   public boolean enableSwitchRewriting = true;
   public boolean enableStringSwitchConversion = true;
   public int minimumStringSwitchSize = 3;
@@ -721,6 +692,8 @@
 
   private final CallSiteOptimizationOptions callSiteOptimizationOptions =
       new CallSiteOptimizationOptions();
+  private final ClassInlinerOptions classInlinerOptions = new ClassInlinerOptions();
+  private final InlinerOptions inlinerOptions = new InlinerOptions();
   private final HorizontalClassMergerOptions horizontalClassMergerOptions =
       new HorizontalClassMergerOptions();
   private final ProtoShrinkingOptions protoShrinking = new ProtoShrinkingOptions();
@@ -747,6 +720,14 @@
     return callSiteOptimizationOptions;
   }
 
+  public ClassInlinerOptions classInlinerOptions() {
+    return classInlinerOptions;
+  }
+
+  public InlinerOptions inlinerOptions() {
+    return inlinerOptions;
+  }
+
   public HorizontalClassMergerOptions horizontalClassMergerOptions() {
     return horizontalClassMergerOptions;
   }
@@ -796,6 +777,24 @@
     return ImmutableSet.of();
   }
 
+  private static boolean isSystemPropertyForDevelopmentSet(String propertyName) {
+    if (Version.isDevelopmentVersion()) {
+      return System.getProperty(propertyName) != null;
+    }
+    return false;
+  }
+
+  private static int parseSystemPropertyForDevelopmentOrDefault(
+      String propertyName, int defaultValue) {
+    if (Version.isDevelopmentVersion()) {
+      String propertyValue = System.getProperty(propertyName);
+      if (propertyValue != null) {
+        return Integer.parseInt(propertyValue);
+      }
+    }
+    return defaultValue;
+  }
+
   public static class InvalidParameterAnnotationInfo {
 
     final DexMethod method;
@@ -1300,6 +1299,87 @@
     }
   }
 
+  public class ClassInlinerOptions {
+
+    public int classInliningInstructionAllowance = -1;
+
+    public int getClassInliningInstructionAllowance() {
+      if (classInliningInstructionAllowance >= 0) {
+        return classInliningInstructionAllowance;
+      }
+      if (isGeneratingClassFiles()) {
+        return 50;
+      }
+      assert isGeneratingDex();
+      return 65;
+    }
+  }
+
+  public class InlinerOptions {
+
+    public boolean enableInlining =
+        !isSystemPropertyForDevelopmentSet("com.android.tools.r8.disableinlining");
+
+    // This defines the limit of instructions in the inlinee
+    public int simpleInliningInstructionLimit =
+        parseSystemPropertyForDevelopmentOrDefault(
+            "com.android.tools.r8.inliningInstructionLimit", -1);
+
+    // This defines the limit of instructions in the inlinee
+    public int doubleInliningInstructionLimit =
+        parseSystemPropertyForDevelopmentOrDefault(
+            "com.android.tools.r8.doubleInliningInstructionLimit", -1);
+
+    // This defines how many instructions of inlinees we can inlinee overall.
+    public int inliningInstructionAllowance = 1500;
+
+    // Maximum number of distinct values in a method that may be used in a monitor-enter
+    // instruction.
+    public int inliningMonitorEnterValuesAllowance = 4;
+
+    // Maximum number of control flow resolution blocks that setup the register state before
+    // the actual catch handler allowed when inlining. Threshold found empirically by testing on
+    // GMS Core.
+    public int inliningControlFlowResolutionBlocksThreshold = 15;
+
+    // TODO(b/141451716): Evaluate the effect of allowing inlining in the inlinee.
+    public boolean applyInliningToInlinee =
+        System.getProperty("com.android.tools.r8.applyInliningToInlinee") != null;
+    public int applyInliningToInlineeMaxDepth = 0;
+
+    public boolean enableInliningOfInvokesWithClassInitializationSideEffects = true;
+    public boolean enableInliningOfInvokesWithNullableReceivers = true;
+    public boolean disableInliningOfLibraryMethodOverrides = true;
+
+    public int getSimpleInliningInstructionLimit() {
+      // If a custom simple inlining instruction limit is set, then use that.
+      if (simpleInliningInstructionLimit >= 0) {
+        return simpleInliningInstructionLimit;
+      }
+      // Allow 3 instructions when generating to class files.
+      if (isGeneratingClassFiles()) {
+        return 3;
+      }
+      // Allow the size of the dex code to be up to 5 bytes.
+      assert isGeneratingDex();
+      return 5;
+    }
+
+    public int getDoubleInliningInstructionLimit() {
+      // If a custom double inlining instruction limit is set, then use that.
+      if (doubleInliningInstructionLimit >= 0) {
+        return doubleInliningInstructionLimit;
+      }
+      // Allow 10 instructions when generating to class files.
+      if (isGeneratingClassFiles()) {
+        return 10;
+      }
+      // Allow the size of the dex code to be up to 20 bytes.
+      assert isGeneratingDex();
+      return 20;
+    }
+  }
+
   public class HorizontalClassMergerOptions {
 
     // TODO(b/138781768): Set enable to true when this bug is resolved.
@@ -1342,7 +1422,7 @@
         return false;
       }
       if (mode.isInitial()) {
-        return enableInlining && isShrinking();
+        return inlinerOptions.enableInlining && isShrinking();
       }
       assert mode.isFinal();
       return true;
diff --git a/src/main/java/com/android/tools/r8/utils/MapIdTemplateProvider.java b/src/main/java/com/android/tools/r8/utils/MapIdTemplateProvider.java
new file mode 100644
index 0000000..335ca09
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/utils/MapIdTemplateProvider.java
@@ -0,0 +1,72 @@
+// Copyright (c) 2021, 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.DiagnosticsHandler;
+import com.android.tools.r8.MapIdEnvironment;
+import com.android.tools.r8.MapIdProvider;
+import com.google.common.collect.ImmutableMap;
+import java.util.Map;
+
+public class MapIdTemplateProvider implements MapIdProvider {
+
+  private static final char VARIABLE_PREFIX = '%';
+
+  private static final Map<String, MapIdProvider> HANDLERS =
+      ImmutableMap.<String, MapIdProvider>builder()
+          .put(var("MAP_HASH"), MapIdEnvironment::getMapHash)
+          .build();
+
+  private static String var(String name) {
+    return VARIABLE_PREFIX + name;
+  }
+
+  private static int getMaxVariableLength() {
+    int max = 0;
+    for (String key : HANDLERS.keySet()) {
+      max = Math.max(max, key.length());
+    }
+    return max;
+  }
+
+  public static MapIdProvider create(String template, DiagnosticsHandler handler) {
+    String cleaned = template;
+    for (String variable : HANDLERS.keySet()) {
+      // Maintain the same size as template so indexing remains valid for error reporting.
+      cleaned = cleaned.replace(variable, ' ' + variable.substring(1));
+    }
+    assert template.length() == cleaned.length();
+    int unhandled = cleaned.indexOf(VARIABLE_PREFIX);
+    if (unhandled >= 0) {
+      while (unhandled >= 0) {
+        int endIndex = Math.min(unhandled + getMaxVariableLength(), template.length());
+        String variablePrefix = template.substring(unhandled, endIndex);
+        handler.error(
+            new StringDiagnostic("Invalid template variable starting with " + variablePrefix));
+        unhandled = cleaned.indexOf(VARIABLE_PREFIX, unhandled + 1);
+      }
+      return null;
+    }
+    return new MapIdTemplateProvider(template);
+  }
+
+  private final String template;
+  private String cachedValue = null;
+
+  private MapIdTemplateProvider(String template) {
+    this.template = template;
+  }
+
+  @Override
+  public String get(MapIdEnvironment environment) {
+    if (cachedValue == null) {
+      cachedValue = template;
+      HANDLERS.forEach(
+          (variable, getter) -> {
+            cachedValue = cachedValue.replace(variable, getter.get(environment));
+          });
+    }
+    return cachedValue;
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/utils/SourceFileTemplateProvider.java b/src/main/java/com/android/tools/r8/utils/SourceFileTemplateProvider.java
new file mode 100644
index 0000000..b1c3cda
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/utils/SourceFileTemplateProvider.java
@@ -0,0 +1,73 @@
+// Copyright (c) 2021, 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.DiagnosticsHandler;
+import com.android.tools.r8.SourceFileEnvironment;
+import com.android.tools.r8.SourceFileProvider;
+import com.google.common.collect.ImmutableMap;
+import java.util.Map;
+
+public class SourceFileTemplateProvider implements SourceFileProvider {
+
+  private static final char VARIABLE_PREFIX = '%';
+
+  private static final Map<String, SourceFileProvider> HANDLERS =
+      ImmutableMap.<String, SourceFileProvider>builder()
+          .put(var("MAP_ID"), SourceFileEnvironment::getMapId)
+          .put(var("MAP_HASH"), SourceFileEnvironment::getMapHash)
+          .build();
+
+  private static String var(String name) {
+    return VARIABLE_PREFIX + name;
+  }
+
+  private static int getMaxVariableLength() {
+    int max = 0;
+    for (String key : HANDLERS.keySet()) {
+      max = Math.max(max, key.length());
+    }
+    return max;
+  }
+
+  public static SourceFileProvider create(String template, DiagnosticsHandler handler) {
+    String cleaned = template;
+    for (String variable : HANDLERS.keySet()) {
+      // Maintain the same size as template so indexing remains valid for error reporting.
+      cleaned = cleaned.replace(variable, ' ' + variable.substring(1));
+    }
+    assert template.length() == cleaned.length();
+    int unhandled = cleaned.indexOf(VARIABLE_PREFIX);
+    if (unhandled >= 0) {
+      while (unhandled >= 0) {
+        int endIndex = Math.min(unhandled + getMaxVariableLength(), template.length());
+        String variablePrefix = template.substring(unhandled, endIndex);
+        handler.error(
+            new StringDiagnostic("Invalid template variable starting with " + variablePrefix));
+        unhandled = cleaned.indexOf(VARIABLE_PREFIX, unhandled + 1);
+      }
+      return null;
+    }
+    return new SourceFileTemplateProvider(template);
+  }
+
+  private final String template;
+  private String cachedValue = null;
+
+  private SourceFileTemplateProvider(String template) {
+    this.template = template;
+  }
+
+  @Override
+  public String get(SourceFileEnvironment environment) {
+    if (cachedValue == null) {
+      cachedValue = template;
+      HANDLERS.forEach(
+          (variable, getter) -> {
+            cachedValue = cachedValue.replace(variable, getter.get(environment));
+          });
+    }
+    return cachedValue;
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/utils/ThreadUtils.java b/src/main/java/com/android/tools/r8/utils/ThreadUtils.java
index a8fa794..3ae44fb 100644
--- a/src/main/java/com/android/tools/r8/utils/ThreadUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/ThreadUtils.java
@@ -20,6 +20,16 @@
   public static final int NOT_SPECIFIED = -1;
 
   public static <T> Future<T> processAsynchronously(
+      Action action, ExecutorService executorService) {
+    return processAsynchronously(
+        () -> {
+          action.execute();
+          return null;
+        },
+        executorService);
+  }
+
+  public static <T> Future<T> processAsynchronously(
       Callable<T> callable, ExecutorService executorService) {
     return executorService.submit(callable);
   }
diff --git a/src/main/java/com/android/tools/r8/utils/structural/HasherWrapper.java b/src/main/java/com/android/tools/r8/utils/structural/HasherWrapper.java
index 8427cfe..112aace 100644
--- a/src/main/java/com/android/tools/r8/utils/structural/HasherWrapper.java
+++ b/src/main/java/com/android/tools/r8/utils/structural/HasherWrapper.java
@@ -75,6 +75,7 @@
       hasher.putBytes(content);
     }
 
+    @SuppressWarnings("unchecked")
     @Override
     public <T> T hash() {
       return (T) hasher.hash();
diff --git a/src/test/java/com/android/tools/r8/AlwaysClassInline.java b/src/test/java/com/android/tools/r8/AlwaysClassInline.java
new file mode 100644
index 0000000..101bdf1
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/AlwaysClassInline.java
@@ -0,0 +1,10 @@
+// Copyright (c) 2021, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Target;
+
+@Target({ElementType.TYPE})
+public @interface AlwaysClassInline {}
diff --git a/src/test/java/com/android/tools/r8/GenerateMainDexListTestBuilder.java b/src/test/java/com/android/tools/r8/GenerateMainDexListTestBuilder.java
index 03ba4b8..c0665cf 100644
--- a/src/test/java/com/android/tools/r8/GenerateMainDexListTestBuilder.java
+++ b/src/test/java/com/android/tools/r8/GenerateMainDexListTestBuilder.java
@@ -7,12 +7,14 @@
 import com.android.tools.r8.debug.DebugTestConfig;
 import com.android.tools.r8.errors.Unimplemented;
 import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.utils.InternalOptions;
 import java.io.IOException;
 import java.nio.file.Path;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
 import java.util.List;
+import java.util.function.Consumer;
 
 public class GenerateMainDexListTestBuilder
     extends TestBaseBuilder<
@@ -22,6 +24,10 @@
         GenerateMainDexListRunResult,
         GenerateMainDexListTestBuilder> {
 
+  public static final Consumer<InternalOptions> DEFAULT_OPTIONS = options -> {};
+
+  private Consumer<InternalOptions> optionsConsumer = DEFAULT_OPTIONS;
+
   private GenerateMainDexListTestBuilder(TestState state, Builder builder) {
     super(state, builder);
   }
@@ -68,8 +74,12 @@
     throw new Unimplemented("No support for class path");
   }
 
-  public GenerateMainDexListRunResult run() throws CompilationFailedException {
-    return new GenerateMainDexListRunResult(GenerateMainDexList.run(builder.build()), getState());
+  public GenerateMainDexListRunResult run() throws CompilationFailedException, IOException {
+    GenerateMainDexListCommand command = builder.build();
+    InternalOptions internalOptions = command.getInternalOptions();
+    optionsConsumer.accept(internalOptions);
+    return new GenerateMainDexListRunResult(
+        GenerateMainDexList.runForTesting(command.getInputApp(), internalOptions), getState());
   }
 
   public GenerateMainDexListTestBuilder addMainDexRules(Collection<String> rules) {
@@ -94,4 +104,12 @@
     builder.setMainDexListOutputPath(output);
     return self();
   }
+
+  public GenerateMainDexListTestBuilder addOptionsModification(
+      Consumer<InternalOptions> optionsConsumer) {
+    if (optionsConsumer != null) {
+      this.optionsConsumer = this.optionsConsumer.andThen(optionsConsumer);
+    }
+    return self();
+  }
 }
diff --git a/src/test/java/com/android/tools/r8/R8RunArtTestsTest.java b/src/test/java/com/android/tools/r8/R8RunArtTestsTest.java
index 9a95fa7..f0c648b 100644
--- a/src/test/java/com/android/tools/r8/R8RunArtTestsTest.java
+++ b/src/test/java/com/android/tools/r8/R8RunArtTestsTest.java
@@ -736,6 +736,17 @@
               "550-new-instance-clinit",
               TestCondition.match(
                   TestCondition.D8_COMPILER, TestCondition.runtimes(DexVm.Version.V6_0_1)))
+          // Regression test for an issue that is not fixed on version 5.1.1. Throws an Exception
+          // instance instead of the expected NullPointerException. This bug is only tickled when
+          // running the R8 generated code when starting from jar or from dex code generated with
+          // dx. However, the code that R8 generates is valid and there is nothing we can do for
+          // this one.
+          .put(
+              "551-implicit-null-checks",
+              TestCondition.match(
+                  TestCondition.tools(DexTool.NONE, DexTool.DX),
+                  TestCondition.R8DEX_COMPILER,
+                  TestCondition.runtimes(DexVm.Version.V5_1_1)))
           // Contains a method (B.<init>) which pass too few arguments to invoke. Also, contains an
           // iput on a static field.
           .put(
@@ -1630,7 +1641,7 @@
     @Override
     public void accept(InternalOptions options) {
       if (disableInlining) {
-        options.enableInlining = false;
+        options.inlinerOptions().enableInlining = false;
       }
       if (disableClassInlining) {
         options.enableClassInlining = false;
@@ -1837,7 +1848,7 @@
               options -> {
                 compilationOptions.accept(options);
                 // Make sure we don't depend on this settings.
-                options.classInliningInstructionAllowance = 10000;
+                options.classInlinerOptions().classInliningInstructionAllowance = 10000;
                 options.lineNumberOptimization = LineNumberOptimization.OFF;
               });
           break;
diff --git a/src/test/java/com/android/tools/r8/R8RunExamplesAndroidOTest.java b/src/test/java/com/android/tools/r8/R8RunExamplesAndroidOTest.java
index 5d3c306..45d03e7 100644
--- a/src/test/java/com/android/tools/r8/R8RunExamplesAndroidOTest.java
+++ b/src/test/java/com/android/tools/r8/R8RunExamplesAndroidOTest.java
@@ -120,14 +120,14 @@
         .withOptionConsumer(opts -> opts.enableClassInlining = false)
         .withBuilderTransformation(
             b -> b.addProguardConfiguration(PROGUARD_OPTIONS, Origin.unknown()))
-        .withDexCheck(inspector -> checkLambdaCount(inspector, 9, "lambdadesugaring"))
+        .withDexCheck(inspector -> checkLambdaCount(inspector, 10, "lambdadesugaring"))
         .run();
 
     test("lambdadesugaring", "lambdadesugaring", "LambdaDesugaring")
         .withMinApiLevel(ToolHelper.getMinApiLevelForDexVmNoHigherThan(AndroidApiLevel.K))
         .withBuilderTransformation(
             b -> b.addProguardConfiguration(PROGUARD_OPTIONS, Origin.unknown()))
-        .withDexCheck(inspector -> checkLambdaCount(inspector, 2, "lambdadesugaring"))
+        .withDexCheck(inspector -> checkLambdaCount(inspector, 3, "lambdadesugaring"))
         .run();
   }
 
@@ -159,14 +159,14 @@
         .withOptionConsumer(opts -> opts.enableClassInlining = false)
         .withBuilderTransformation(
             b -> b.addProguardConfiguration(PROGUARD_OPTIONS, Origin.unknown()))
-        .withDexCheck(inspector -> checkLambdaCount(inspector, 9, "lambdadesugaring"))
+        .withDexCheck(inspector -> checkLambdaCount(inspector, 10, "lambdadesugaring"))
         .run();
 
     test("lambdadesugaring", "lambdadesugaring", "LambdaDesugaring")
         .withMinApiLevel(AndroidApiLevel.N)
         .withBuilderTransformation(
             b -> b.addProguardConfiguration(PROGUARD_OPTIONS, Origin.unknown()))
-        .withDexCheck(inspector -> checkLambdaCount(inspector, 2, "lambdadesugaring"))
+        .withDexCheck(inspector -> checkLambdaCount(inspector, 3, "lambdadesugaring"))
         .run();
   }
 
@@ -244,7 +244,7 @@
             b ->
                 b.addProguardConfiguration(
                     getProguardOptionsNPlus(enableProguardCompatibilityMode), Origin.unknown()))
-        .withDexCheck(inspector -> checkLambdaCount(inspector, 0, "lambdadesugaringnplus"))
+        .withDexCheck(inspector -> checkLambdaCount(inspector, 2, "lambdadesugaringnplus"))
         .run();
   }
 
diff --git a/src/test/java/com/android/tools/r8/R8TestBuilder.java b/src/test/java/com/android/tools/r8/R8TestBuilder.java
index 2233484..d30e214 100644
--- a/src/test/java/com/android/tools/r8/R8TestBuilder.java
+++ b/src/test/java/com/android/tools/r8/R8TestBuilder.java
@@ -345,6 +345,16 @@
     return self();
   }
 
+  public T enableAlwaysClassInlineAnnotations() {
+    return addAlwaysClassInlineAnnotation()
+        .enableAlwaysClassInlineAnnotations(AlwaysClassInline.class.getPackage().getName());
+  }
+
+  public T enableAlwaysClassInlineAnnotations(String annotationPackageName) {
+    return addInternalKeepRules(
+        "-alwaysclassinline @" + annotationPackageName + ".AlwaysClassInline class *");
+  }
+
   public T enableAlwaysInliningAnnotations() {
     return addAlwaysInliningAnnotations()
         .enableAlwaysInliningAnnotations(AlwaysInline.class.getPackage().getName());
diff --git a/src/test/java/com/android/tools/r8/TestShrinkerBuilder.java b/src/test/java/com/android/tools/r8/TestShrinkerBuilder.java
index be7b225..beb7da4 100644
--- a/src/test/java/com/android/tools/r8/TestShrinkerBuilder.java
+++ b/src/test/java/com/android/tools/r8/TestShrinkerBuilder.java
@@ -386,6 +386,10 @@
 
   public abstract T addApplyMapping(String proguardMap);
 
+  public final T addAlwaysClassInlineAnnotation() {
+    return addTestingAnnotation(AlwaysClassInline.class);
+  }
+
   public final T addAlwaysInliningAnnotations() {
     return addTestingAnnotation(AlwaysInline.class);
   }
diff --git a/src/test/java/com/android/tools/r8/accessrelaxation/ConstructorRelaxationTest.java b/src/test/java/com/android/tools/r8/accessrelaxation/ConstructorRelaxationTest.java
index 972d058..4f91077 100644
--- a/src/test/java/com/android/tools/r8/accessrelaxation/ConstructorRelaxationTest.java
+++ b/src/test/java/com/android/tools/r8/accessrelaxation/ConstructorRelaxationTest.java
@@ -186,7 +186,7 @@
             .addProgramClasses(CLASSES)
             .addOptionsModification(
                 o -> {
-                  o.enableInlining = false;
+                  o.inlinerOptions().enableInlining = false;
                   o.enableVerticalClassMerging = false;
                 })
             .noMinification()
diff --git a/src/test/java/com/android/tools/r8/accessrelaxation/InvokeTypeConversionTest.java b/src/test/java/com/android/tools/r8/accessrelaxation/InvokeTypeConversionTest.java
index 209aa18..8a91c84 100644
--- a/src/test/java/com/android/tools/r8/accessrelaxation/InvokeTypeConversionTest.java
+++ b/src/test/java/com/android/tools/r8/accessrelaxation/InvokeTypeConversionTest.java
@@ -79,7 +79,7 @@
             .addKeepRules(
                 // We're testing lens-based invocation type conversions.
                 "-dontoptimize", "-dontobfuscate", "-allowaccessmodification")
-            .addOptionsModification(o -> o.enableInlining = false)
+            .addOptionsModification(o -> o.inlinerOptions().enableInlining = false)
             .setMinApi(parameters.getApiLevel())
             .run(parameters.getRuntime(), CLASS_NAME);
     if (expectedException == null) {
diff --git a/src/test/java/com/android/tools/r8/apimodel/AndroidApiHashingDatabaseBuilderGeneratorTest.java b/src/test/java/com/android/tools/r8/apimodel/AndroidApiHashingDatabaseBuilderGeneratorTest.java
index 8a0c060..19e3299 100644
--- a/src/test/java/com/android/tools/r8/apimodel/AndroidApiHashingDatabaseBuilderGeneratorTest.java
+++ b/src/test/java/com/android/tools/r8/apimodel/AndroidApiHashingDatabaseBuilderGeneratorTest.java
@@ -121,16 +121,6 @@
   }
 
   @Test
-  public void initializeApiDatabaseTimeTest() {
-    DexItemFactory factory = new DexItemFactory();
-    long start = System.currentTimeMillis();
-    new AndroidApiLevelHashingDatabaseImpl(factory, ImmutableList.of());
-    long end = System.currentTimeMillis();
-    long timeSpan = end - start;
-    assertTrue("Time used was " + timeSpan, timeSpan < 100);
-  }
-
-  @Test
   public void testCanLookUpAllParsedApiClassesAndMembers() throws Exception {
     List<ParsedApiClass> parsedApiClasses =
         AndroidApiVersionsXmlParser.getParsedApiClasses(API_VERSIONS_XML.toFile(), API_LEVEL);
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 1790724..036dd46 100644
--- a/src/test/java/com/android/tools/r8/bridgeremoval/B77836766.java
+++ b/src/test/java/com/android/tools/r8/bridgeremoval/B77836766.java
@@ -505,6 +505,6 @@
     // bothers what the tests want to check, such as exact instructions in the body that include
     // invocation kinds, like virtual call to a bridge.
     // Disable inlining to avoid the (short) tested method from being inlined and then removed.
-    options.enableInlining = false;
+    options.inlinerOptions().enableInlining = false;
   }
 }
diff --git a/src/test/java/com/android/tools/r8/bridgeremoval/RemoveVisibilityBridgeMethodsTest.java b/src/test/java/com/android/tools/r8/bridgeremoval/RemoveVisibilityBridgeMethodsTest.java
index 32fa0b8..ed0081e 100644
--- a/src/test/java/com/android/tools/r8/bridgeremoval/RemoveVisibilityBridgeMethodsTest.java
+++ b/src/test/java/com/android/tools/r8/bridgeremoval/RemoveVisibilityBridgeMethodsTest.java
@@ -104,7 +104,7 @@
     testForR8(parameters.getBackend())
         .addProgramClassFileData(programClassFileData)
         .addKeepMainRule(mainClass.name)
-        .addOptionsModification(options -> options.enableInlining = false)
+        .addOptionsModification(options -> options.inlinerOptions().enableInlining = false)
         .allowAccessModification()
         .minification(minification)
         .setMinApi(parameters.getApiLevel())
diff --git a/src/test/java/com/android/tools/r8/cf/InlineCmpDoubleTest.java b/src/test/java/com/android/tools/r8/cf/InlineCmpDoubleTest.java
index 16dfaa7..1db3ee4 100644
--- a/src/test/java/com/android/tools/r8/cf/InlineCmpDoubleTest.java
+++ b/src/test/java/com/android/tools/r8/cf/InlineCmpDoubleTest.java
@@ -41,7 +41,7 @@
     testForR8(parameters.getBackend())
         .addProgramClasses(TestClass.class)
         .addKeepMainRule(TestClass.class)
-        .addOptionsModification(options -> options.enableInlining = enableInlining)
+        .addOptionsModification(options -> options.inlinerOptions().enableInlining = enableInlining)
         .setMinApi(parameters.getApiLevel())
         .compile()
         .inspect(this::inspect)
diff --git a/src/test/java/com/android/tools/r8/checkdiscarded/CheckDiscardModifyDiagnosticsLevelTest.java b/src/test/java/com/android/tools/r8/checkdiscarded/CheckDiscardModifyDiagnosticsLevelTest.java
index 94b6238..568c66f 100644
--- a/src/test/java/com/android/tools/r8/checkdiscarded/CheckDiscardModifyDiagnosticsLevelTest.java
+++ b/src/test/java/com/android/tools/r8/checkdiscarded/CheckDiscardModifyDiagnosticsLevelTest.java
@@ -48,7 +48,7 @@
   }
 
   private void noInlining(InternalOptions options) {
-    options.enableInlining = false;
+    options.inlinerOptions().enableInlining = false;
   }
 
   private Matcher<Diagnostic> discardCheckFailedMatcher() {
diff --git a/src/test/java/com/android/tools/r8/checkdiscarded/CheckDiscardedTest.java b/src/test/java/com/android/tools/r8/checkdiscarded/CheckDiscardedTest.java
index 389d347..ef048be 100644
--- a/src/test/java/com/android/tools/r8/checkdiscarded/CheckDiscardedTest.java
+++ b/src/test/java/com/android/tools/r8/checkdiscarded/CheckDiscardedTest.java
@@ -69,7 +69,7 @@
   }
 
   private void noInlining(InternalOptions options) {
-    options.enableInlining = false;
+    options.inlinerOptions().enableInlining = false;
   }
 
   private String checkDiscardRule(boolean member, Class<?> annotation) {
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/IllegalInliningOfMergedConstructorTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/IllegalInliningOfMergedConstructorTest.java
index aad40d0..c0ba9c8 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/IllegalInliningOfMergedConstructorTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/IllegalInliningOfMergedConstructorTest.java
@@ -36,7 +36,8 @@
         .addEnumUnboxingInspector(inspector -> inspector.assertUnboxed(Reprocess.class))
         .addHorizontallyMergedClassesInspector(
             inspector -> inspector.assertMergedInto(B.class, A.class))
-        .addOptionsModification(options -> options.inliningInstructionLimit = 4)
+        .addOptionsModification(
+            options -> options.inlinerOptions().simpleInliningInstructionLimit = 4)
         .enableInliningAnnotations()
         .enableNeverClassInliningAnnotations()
         .setMinApi(parameters.getApiLevel())
diff --git a/src/test/java/com/android/tools/r8/classmerging/vertical/MergeSynthesizingContextIntoSyntheticLambdaTest.java b/src/test/java/com/android/tools/r8/classmerging/vertical/MergeSynthesizingContextIntoSyntheticLambdaTest.java
index 01c8b8c..d2bf4cd 100644
--- a/src/test/java/com/android/tools/r8/classmerging/vertical/MergeSynthesizingContextIntoSyntheticLambdaTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/vertical/MergeSynthesizingContextIntoSyntheticLambdaTest.java
@@ -40,7 +40,7 @@
         .addKeepMainRule(Main.class)
         // Disable inlining to ensure that the synthetic lambdas remain in the residual
         // program.
-        .addOptionsModification(options -> options.enableInlining = false)
+        .addOptionsModification(options -> options.inlinerOptions().enableInlining = false)
         .addVerticallyMergedClassesInspector(
             inspector -> {
               if (parameters.isCfRuntime()) {
diff --git a/src/test/java/com/android/tools/r8/classmerging/vertical/VerticalClassMergerTest.java b/src/test/java/com/android/tools/r8/classmerging/vertical/VerticalClassMergerTest.java
index 8f4273f..46ecc50 100644
--- a/src/test/java/com/android/tools/r8/classmerging/vertical/VerticalClassMergerTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/vertical/VerticalClassMergerTest.java
@@ -347,7 +347,7 @@
         testForR8(parameters.getBackend())
             .addKeepRules(getProguardConfig(EXAMPLE_KEEP))
             .addOptionsModification(this::configure)
-            .addOptionsModification(options -> options.enableInlining = false)
+            .addOptionsModification(options -> options.inlinerOptions().enableInlining = false)
             .allowUnusedProguardConfigurationRules(),
         main,
         readProgramFiles(programFiles),
diff --git a/src/test/java/com/android/tools/r8/compatproguard/reflection/ReflectionTest.java b/src/test/java/com/android/tools/r8/compatproguard/reflection/ReflectionTest.java
index 352186b..fb1be42 100644
--- a/src/test/java/com/android/tools/r8/compatproguard/reflection/ReflectionTest.java
+++ b/src/test/java/com/android/tools/r8/compatproguard/reflection/ReflectionTest.java
@@ -517,7 +517,8 @@
         ImmutableList.of(keepMainProguardConfigurationWithInliningAnnotation(mainClass)),
         Origin.unknown());
     ToolHelper.allowTestProguardOptions(builder);
-    AndroidApp output = ToolHelper.runR8(builder.build(), o -> o.enableInlining = false);
+    AndroidApp output =
+        ToolHelper.runR8(builder.build(), o -> o.inlinerOptions().enableInlining = false);
 
     runOnVM(output, mainClass, backend);
   }
diff --git a/src/test/java/com/android/tools/r8/debug/DebugInfoWhenInliningTest.java b/src/test/java/com/android/tools/r8/debug/DebugInfoWhenInliningTest.java
index 965b610..05bbb73 100644
--- a/src/test/java/com/android/tools/r8/debug/DebugInfoWhenInliningTest.java
+++ b/src/test/java/com/android/tools/r8/debug/DebugInfoWhenInliningTest.java
@@ -43,7 +43,8 @@
                   options.testing.forceJumboStringProcessing = forceJumboStringProcessing;
                   // TODO(b/117848700): Can we make these tests neutral to inlining threshold?
                   // Also CF needs improvements here.
-                  options.inliningInstructionLimit = parameters.isCfRuntime() ? 5 : 4;
+                  options.inlinerOptions().simpleInliningInstructionLimit =
+                      parameters.isCfRuntime() ? 5 : 4;
                 })
             .compile();
     DebugTestConfig config = result.debugConfig();
diff --git a/src/test/java/com/android/tools/r8/debug/LineNumberOptimizationTest.java b/src/test/java/com/android/tools/r8/debug/LineNumberOptimizationTest.java
index 004a443..d227ea3 100644
--- a/src/test/java/com/android/tools/r8/debug/LineNumberOptimizationTest.java
+++ b/src/test/java/com/android/tools/r8/debug/LineNumberOptimizationTest.java
@@ -61,7 +61,7 @@
                   if (!dontOptimizeByEnablingDebug) {
                     options.lineNumberOptimization = lineNumberOptimization;
                   }
-                  options.enableInlining = false;
+                  options.inlinerOptions().enableInlining = false;
                 })
             .compile();
 
diff --git a/src/test/java/com/android/tools/r8/debuginfo/InliningWithoutPositionsTestRunner.java b/src/test/java/com/android/tools/r8/debuginfo/InliningWithoutPositionsTestRunner.java
index 4898ef3..e1b76d6 100644
--- a/src/test/java/com/android/tools/r8/debuginfo/InliningWithoutPositionsTestRunner.java
+++ b/src/test/java/com/android/tools/r8/debuginfo/InliningWithoutPositionsTestRunner.java
@@ -104,7 +104,8 @@
             .noMinification()
             .addKeepAttributeSourceFile()
             .addKeepAttributeLineNumberTable()
-            .addOptionsModification(options -> options.inliningInstructionLimit = 40)
+            .addOptionsModification(
+                options -> options.inlinerOptions().simpleInliningInstructionLimit = 40)
             .run(parameters.getRuntime(), MAIN_CLASS)
             .assertFailure();
 
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 f1f19ff..15d6a42 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
@@ -70,7 +70,7 @@
         .addOptionsModification(
             options -> {
               // Need to increase a little bit to inline System.out.println
-              options.inliningInstructionLimit = 4;
+              options.inlinerOptions().simpleInliningInstructionLimit = 4;
             })
         .apply(configuration)
         .setMinApi(parameters.getApiLevel())
@@ -119,7 +119,8 @@
         buildAndRun(
             mainClass,
             ImmutableList.of(IntrinsicsDeputy.class, NotPinnedClass.class, mainClass),
-            R8TestBuilder::enableInliningAnnotations);
+            testBuilder ->
+                testBuilder.enableAlwaysInliningAnnotations().enableInliningAnnotations());
 
     ClassSubject mainSubject = inspector.clazz(mainClass);
     assertThat(mainSubject, isPresent());
@@ -147,7 +148,8 @@
         buildAndRun(
             mainClass,
             ImmutableList.of(IntrinsicsDeputy.class, NotPinnedClass.class, mainClass),
-            R8TestBuilder::enableInliningAnnotations);
+            testBuilder ->
+                testBuilder.enableAlwaysInliningAnnotations().enableInliningAnnotations());
 
     ClassSubject mainSubject = inspector.clazz(mainClass);
     assertThat(mainSubject, isPresent());
@@ -179,7 +181,11 @@
                 NonNullParamAfterInvokeVirtual.class,
                 NotPinnedClass.class,
                 mainClass),
-            builder -> builder.enableNeverClassInliningAnnotations().enableInliningAnnotations());
+            builder ->
+                builder
+                    .enableAlwaysInliningAnnotations()
+                    .enableNeverClassInliningAnnotations()
+                    .enableInliningAnnotations());
 
     ClassSubject mainSubject = inspector.clazz(NonNullParamAfterInvokeVirtual.class);
     assertThat(mainSubject, isPresent());
@@ -232,6 +238,7 @@
                                     NonNullParamAfterInvokeInterface.class,
                                     NonNullParamInterfaceImpl.class))
                     .addOptionsModification(this::disableDevirtualization)
+                    .enableAlwaysInliningAnnotations()
                     .enableInliningAnnotations()
                     .enableNeverClassInliningAnnotations()
                     .enableNoVerticalClassMergingAnnotations());
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 b60f6c9..21f1be3 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
@@ -119,9 +119,9 @@
               // and
               // that the class is therefore made abstract.
               o.enableClassInlining = false;
-              o.enableInlining = inlining;
-              o.enableInliningOfInvokesWithNullableReceivers = false;
-              o.inliningInstructionLimit = 6;
+              o.inlinerOptions().enableInlining = inlining;
+              o.inlinerOptions().enableInliningOfInvokesWithNullableReceivers = false;
+              o.inlinerOptions().simpleInliningInstructionLimit = 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.
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerSimplePairBuilderTest.java b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerSimplePairBuilderTest.java
index b1e3cfc..d9b5cd1 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerSimplePairBuilderTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerSimplePairBuilderTest.java
@@ -9,6 +9,8 @@
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.junit.Assert.assertEquals;
 
+import com.android.tools.r8.AlwaysClassInline;
+import com.android.tools.r8.AlwaysInline;
 import com.android.tools.r8.NeverInline;
 import com.android.tools.r8.NoHorizontalClassMerging;
 import com.android.tools.r8.TestParameters;
@@ -55,8 +57,8 @@
     testForR8(parameters.getBackend())
         .addInnerClasses(ClassInlinerSimplePairBuilderTest.class)
         .addKeepMainRule(TestClass.class)
-        // TODO(b/143129517): This relies on PairBuilder::build being inlined, thus the limit of 6.
-        .addOptionsModification(options -> options.inliningInstructionLimit = 6)
+        .enableAlwaysClassInlineAnnotations()
+        .enableAlwaysInliningAnnotations()
         .enableInliningAnnotations()
         .enableNoHorizontalClassMergingAnnotations()
         .noMinification()
@@ -128,6 +130,7 @@
     }
   }
 
+  @AlwaysClassInline
   @NoHorizontalClassMerging
   static class PairBuilder<F, S> {
 
@@ -148,6 +151,7 @@
       return this;
     }
 
+    @AlwaysInline
     public Pair<F, S> build() {
       return new Pair<>(first, second);
     }
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerSimplePairBuilderWithMultipleBuildsTest.java b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerSimplePairBuilderWithMultipleBuildsTest.java
index 6e5dc39..d23951c 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerSimplePairBuilderWithMultipleBuildsTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerSimplePairBuilderWithMultipleBuildsTest.java
@@ -9,6 +9,7 @@
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.junit.Assert.assertEquals;
 
+import com.android.tools.r8.AlwaysInline;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.utils.StringUtils;
@@ -48,8 +49,7 @@
     testForR8(parameters.getBackend())
         .addInnerClasses(ClassInlinerSimplePairBuilderWithMultipleBuildsTest.class)
         .addKeepMainRule(TestClass.class)
-        // TODO(b/143129517): This relies on PairBuilder::build being inlined, thus the limit of 6.
-        .addOptionsModification(options -> options.inliningInstructionLimit = 6)
+        .enableAlwaysInliningAnnotations()
         .noMinification()
         .setMinApi(parameters.getApiLevel())
         .compile()
@@ -130,6 +130,7 @@
       System.out.println("[after] second = " + this.second);
     }
 
+    @AlwaysInline
     public Pair<F, S> build() {
       return new Pair<>(first, second);
     }
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerTest.java b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerTest.java
index cb494cb..52fcf43 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerTest.java
@@ -41,7 +41,8 @@
 import com.android.tools.r8.utils.codeinspector.FoundClassSubject;
 import com.google.common.collect.Sets;
 import java.util.Collections;
-import java.util.Set;
+import java.util.HashSet;
+import java.util.List;
 import java.util.stream.Collectors;
 import org.junit.Assume;
 import org.junit.Test;
@@ -248,8 +249,7 @@
             .addOptionsModification(
                 o -> {
                   // TODO(b/143129517, 141719453): The limit seems to only be needed for DEX...
-                  o.classInliningInstructionLimit = 100;
-                  o.classInliningInstructionAllowance = 1000;
+                  o.classInlinerOptions().classInliningInstructionAllowance = 1000;
                 })
             .allowAccessModification()
             .noMinification()
@@ -300,35 +300,32 @@
             .addProgramClasses(classes)
             .addKeepMainRule(main)
             .addKeepAttributes("LineNumberTable")
-            .addOptionsModification(
-                o -> {
-                  // TODO(b/141719453): Identify single instances instead of increasing the limit.
-                  o.classInliningInstructionLimit = 20;
-                })
             .allowAccessModification()
             .enableInliningAnnotations()
             .noMinification()
-            .run(main)
+            .setMinApi(parameters.getApiLevel())
+            .run(parameters.getRuntime(), main)
             .assertSuccessWithOutput(javaOutput);
 
     CodeInspector inspector = result.inspector();
     ClassSubject clazz = inspector.clazz(main);
 
-    assertEquals(
-        Sets.newHashSet("java.lang.StringBuilder"),
-        collectTypes(clazz.uniqueMethodWithName("testStatelessLambda")));
-
-    // TODO(b/120814598): Should only be "java.lang.StringBuilder". Lambdas are not class inlined
-    // because parameter usage is not available for each lambda constructor.
-    Set<String> expectedTypes = Sets.newHashSet("java.lang.StringBuilder");
-    expectedTypes.addAll(
+    List<String> synthesizedJavaLambdaClasses =
         inspector.allClasses().stream()
             .filter(FoundClassSubject::isSynthesizedJavaLambdaClass)
             .map(FoundClassSubject::getFinalName)
-            .collect(Collectors.toList()));
-    assertEquals(expectedTypes, collectTypes(clazz.uniqueMethodWithName("testStatefulLambda")));
+            .collect(Collectors.toList());
+
+    // TODO(b/120814598): Should only be "java.lang.StringBuilder".
+    assertEquals(
+        new HashSet<>(synthesizedJavaLambdaClasses),
+        collectTypes(clazz.uniqueMethodWithName("testStatelessLambda")));
     assertTrue(
-        inspector.allClasses().stream().noneMatch(ClassSubject::isSynthesizedJavaLambdaClass));
+        inspector.allClasses().stream().anyMatch(ClassSubject::isSynthesizedJavaLambdaClass));
+
+    assertEquals(
+        Sets.newHashSet("java.lang.StringBuilder"),
+        collectTypes(clazz.uniqueMethodWithName("testStatefulLambda")));
   }
 
   private String getProguardConfig(String main) {
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerTupleBuilderConstructorsTest.java b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerTupleBuilderConstructorsTest.java
index 1b5d955..187c5f3 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerTupleBuilderConstructorsTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerTupleBuilderConstructorsTest.java
@@ -10,6 +10,7 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assume.assumeTrue;
 
+import com.android.tools.r8.AlwaysClassInline;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.utils.StringUtils;
@@ -44,6 +45,7 @@
     testForR8(parameters.getBackend())
         .addInnerClasses(ClassInlinerTupleBuilderConstructorsTest.class)
         .addKeepMainRule(TestClass.class)
+        .enableAlwaysClassInlineAnnotations()
         .noMinification()
         .setMinApi(parameters.getApiLevel())
         .compile()
@@ -89,6 +91,7 @@
     }
   }
 
+  @AlwaysClassInline
   static class Tuple {
 
     boolean z;
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/LibraryOverrideClassInliningTest.java b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/LibraryOverrideClassInliningTest.java
index 24c1c8b..7b68b6d 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/LibraryOverrideClassInliningTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/LibraryOverrideClassInliningTest.java
@@ -6,7 +6,7 @@
 import static com.android.tools.r8.references.Reference.methodFromMethod;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
 import static org.hamcrest.MatcherAssert.assertThat;
-import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
 
 import com.android.tools.r8.NeverInline;
@@ -114,10 +114,12 @@
                             .holder
                             .toString()
                             .equals(SimpleLibraryOverride.class.getTypeName())));
-    // TODO(b/181942160): The non-simple run should ideally not inlined by the class inliner, since
-    //  there is an instance of NonSimpleLibraryOverride that is not eligible for class inlining.
-    assertFalse(
+    // TODO(b/181942160): The non-simple run should ideally not inlined independent of the class
+    //  inliner instruction allowance, since there is an instance of NonSimpleLibraryOverride that
+    //  is not eligible for class inlining.
+    assertEquals(
         "Expected NonSimple.run invoke in:\n" + main.getMethod().codeToString(),
+        parameters.isDexRuntime(),
         main.streamInstructions()
             .anyMatch(
                 i ->
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/cost/NonMaterializingFieldAccessesAfterClassInliningTest.java b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/cost/NonMaterializingFieldAccessesAfterClassInliningTest.java
index 9df6cb2..79d6062 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/cost/NonMaterializingFieldAccessesAfterClassInliningTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/cost/NonMaterializingFieldAccessesAfterClassInliningTest.java
@@ -39,7 +39,10 @@
         .addInnerClasses(NonMaterializingFieldAccessesAfterClassInliningTest.class)
         .addKeepMainRule(TestClass.class)
         // Should be able to class inline Builder even when the threshold is low.
-        .addOptionsModification(options -> options.classInliningInstructionAllowance = 3)
+        .addOptionsModification(
+            options ->
+                options.classInlinerOptions().classInliningInstructionAllowance =
+                    parameters.isCfRuntime() ? 3 : 6)
         .enableInliningAnnotations()
         .enableNoHorizontalClassMergingAnnotations()
         .setMinApi(parameters.getApiLevel())
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/devirtualize/InvokeInterfaceToInvokeVirtualTest.java b/src/test/java/com/android/tools/r8/ir/optimize/devirtualize/InvokeInterfaceToInvokeVirtualTest.java
index 32ea22a..2a88107 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/devirtualize/InvokeInterfaceToInvokeVirtualTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/devirtualize/InvokeInterfaceToInvokeVirtualTest.java
@@ -52,7 +52,8 @@
             .addProgramClasses(I.class, A.class, A0.class, A1.class, Main.class)
             .addKeepMainRule(Main.class)
             .addOptionsModification(
-                options -> options.enableInliningOfInvokesWithNullableReceivers = false)
+                options ->
+                    options.inlinerOptions().enableInliningOfInvokesWithNullableReceivers = false)
             .enableNoHorizontalClassMergingAnnotations()
             .setMinApi(parameters.getApiLevel())
             .run(parameters.getRuntime(), Main.class)
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/inliner/InliningAfterClassInitializationTest.java b/src/test/java/com/android/tools/r8/ir/optimize/inliner/InliningAfterClassInitializationTest.java
index 59e5118..b4c0a63 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/inliner/InliningAfterClassInitializationTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/inliner/InliningAfterClassInitializationTest.java
@@ -223,7 +223,9 @@
         .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.P))
         .addKeepMainRule(mainClass)
         .addOptionsModification(
-            options -> options.enableInliningOfInvokesWithClassInitializationSideEffects = false)
+            options ->
+                options.inlinerOptions().enableInliningOfInvokesWithClassInitializationSideEffects =
+                    false)
         .enableConstantArgumentAnnotations()
         .enableUnusedArgumentAnnotations()
         .enableInliningAnnotations()
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/inliner/SingleTargetAfterInliningTest.java b/src/test/java/com/android/tools/r8/ir/optimize/inliner/SingleTargetAfterInliningTest.java
index 2012c6f..1f51c2f 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/inliner/SingleTargetAfterInliningTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/inliner/SingleTargetAfterInliningTest.java
@@ -47,8 +47,8 @@
         .addKeepMainRule(TestClass.class)
         .addOptionsModification(
             options -> {
-              options.applyInliningToInlinee = true;
-              options.applyInliningToInlineeMaxDepth = maxInliningDepth;
+              options.inlinerOptions().applyInliningToInlinee = true;
+              options.inlinerOptions().applyInliningToInlineeMaxDepth = maxInliningDepth;
             })
         .enableAlwaysInliningAnnotations()
         .enableNeverClassInliningAnnotations()
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/inliner/sync/InlinerMonitorEnterValuesThresholdTest.java b/src/test/java/com/android/tools/r8/ir/optimize/inliner/sync/InlinerMonitorEnterValuesThresholdTest.java
index 18d1deb..6ac9e07 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/inliner/sync/InlinerMonitorEnterValuesThresholdTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/inliner/sync/InlinerMonitorEnterValuesThresholdTest.java
@@ -41,7 +41,8 @@
     testForR8(parameters.getBackend())
         .addProgramClasses(TestClass.class)
         .addKeepMainRule(TestClass.class)
-        .addOptionsModification(options -> options.inliningMonitorEnterValuesAllowance = threshold)
+        .addOptionsModification(
+            options -> options.inlinerOptions().inliningMonitorEnterValuesAllowance = threshold)
         .setMinApi(parameters.getApiLevel())
         .compile()
         .inspect(this::inspect)
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/nonnull/NotPinnedClass.java b/src/test/java/com/android/tools/r8/ir/optimize/nonnull/NotPinnedClass.java
index b9f368d..d629975 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/nonnull/NotPinnedClass.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/nonnull/NotPinnedClass.java
@@ -3,6 +3,8 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.ir.optimize.nonnull;
 
+import com.android.tools.r8.AlwaysInline;
+
 public class NotPinnedClass {
   final int field;
 
@@ -10,6 +12,7 @@
     this.field = field;
   }
 
+  @AlwaysInline
   void act() {
     System.out.println(field);
   }
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/outliner/b111893131/B111893131.java b/src/test/java/com/android/tools/r8/ir/optimize/outliner/b111893131/B111893131.java
index 5db4d92..f19dea9 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/outliner/b111893131/B111893131.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/outliner/b111893131/B111893131.java
@@ -14,7 +14,6 @@
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.ToolHelper.ProcessResult;
-import com.android.tools.r8.VmTestRunner;
 import com.android.tools.r8.code.Instruction;
 import com.android.tools.r8.code.InvokeVirtual;
 import com.android.tools.r8.graph.Code;
@@ -27,7 +26,6 @@
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.google.common.collect.ImmutableList;
 import org.junit.Test;
-import org.junit.runner.RunWith;
 
 class TestClass {
   public interface Act {
@@ -76,11 +74,14 @@
     builder.setDisableMinification(true);
     String config = keepMainProguardConfiguration(TestClass.class);
     builder.addProguardConfiguration(ImmutableList.of(config), Origin.unknown());
-    AndroidApp app = ToolHelper.runR8(builder.build(), options -> {
-      // To trigger outliner, set # of expected outline candidate as threshold.
-      options.outline.threshold = 2;
-      options.enableInlining = false;
-    });
+    AndroidApp app =
+        ToolHelper.runR8(
+            builder.build(),
+            options -> {
+              // To trigger outliner, set # of expected outline candidate as threshold.
+              options.outline.threshold = 2;
+              options.inlinerOptions().enableInlining = false;
+            });
     ProcessResult result = runOnArtRaw(app, TestClass.class);
     assertEquals(result.toString(), 0, result.exitCode);
     assertEquals(javaResult, result.stdout);
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/outliner/b112247415/B112247415.java b/src/test/java/com/android/tools/r8/ir/optimize/outliner/b112247415/B112247415.java
index d5e8708..685d805 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/outliner/b112247415/B112247415.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/outliner/b112247415/B112247415.java
@@ -90,7 +90,7 @@
                 options -> {
                   // To trigger outliner, set # of expected outline candidate as threshold.
                   options.outline.threshold = 2;
-                  options.enableInlining = false;
+                  options.inlinerOptions().enableInlining = false;
                 })
             .noHorizontalClassMergingOfSynthetics()
             .run(parameters.getRuntime(), TestClass.class)
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/uninstantiatedtypes/NestedInterfaceMethodTest.java b/src/test/java/com/android/tools/r8/ir/optimize/uninstantiatedtypes/NestedInterfaceMethodTest.java
index 44d3e31..837a84e 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/uninstantiatedtypes/NestedInterfaceMethodTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/uninstantiatedtypes/NestedInterfaceMethodTest.java
@@ -61,7 +61,7 @@
             .addOptionsModification(
                 options -> {
                   options.enableDevirtualization = false;
-                  options.enableInliningOfInvokesWithNullableReceivers = false;
+                  options.inlinerOptions().enableInliningOfInvokesWithNullableReceivers = false;
                 })
             .setMinApi(AndroidApiLevel.B)
             .run(parameters.getRuntime(), TestClass.class)
diff --git a/src/test/java/com/android/tools/r8/kotlin/AbstractR8KotlinTestBase.java b/src/test/java/com/android/tools/r8/kotlin/AbstractR8KotlinTestBase.java
index 79880c8..5759deb 100644
--- a/src/test/java/com/android/tools/r8/kotlin/AbstractR8KotlinTestBase.java
+++ b/src/test/java/com/android/tools/r8/kotlin/AbstractR8KotlinTestBase.java
@@ -31,10 +31,10 @@
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.android.tools.r8.utils.codeinspector.FieldSubject;
+import com.android.tools.r8.utils.codeinspector.InstructionSubject;
 import com.android.tools.r8.utils.codeinspector.MethodSubject;
 import java.nio.file.Path;
 import java.util.ArrayList;
-import java.util.Arrays;
 import java.util.List;
 import java.util.Map;
 import org.junit.Assume;
@@ -66,32 +66,49 @@
   }
 
   protected static void checkMethodIsInvokedAtLeastOnce(
-      DexCode dexCode, MethodSignature... methodSignatures) {
+      MethodSubject subject, MethodSignature... methodSignatures) {
     for (MethodSignature methodSignature : methodSignatures) {
-      checkMethodIsInvokedAtLeastOnce(dexCode, methodSignature);
+      checkMethodIsInvokedAtLeastOnce(subject, methodSignature);
     }
   }
 
   private static void checkMethodIsInvokedAtLeastOnce(
-      DexCode dexCode, MethodSignature methodSignature) {
-    assertTrue("No invoke to '" + methodSignature.toString() + "'",
-        Arrays.stream(dexCode.instructions)
-            .filter((instr) -> instr.getMethod() != null)
-            .anyMatch((instr) -> instr.getMethod().name.toString().equals(methodSignature.name)));
+      MethodSubject subject, MethodSignature methodSignature) {
+    assertTrue(
+        "No invoke to '" + methodSignature.toString() + "'",
+        subject
+            .streamInstructions()
+            .filter(InstructionSubject::isInvoke)
+            .anyMatch(
+                instructionSubject ->
+                    instructionSubject
+                        .getMethod()
+                        .getName()
+                        .toString()
+                        .equals(methodSignature.name)));
   }
 
   protected static void checkMethodIsNeverInvoked(
-      DexCode dexCode, MethodSignature... methodSignatures) {
+      MethodSubject subject, MethodSignature... methodSignatures) {
     for (MethodSignature methodSignature : methodSignatures) {
-      checkMethodIsNeverInvoked(dexCode, methodSignature);
+      checkMethodIsNeverInvoked(subject, methodSignature);
     }
   }
 
-  private static void checkMethodIsNeverInvoked(DexCode dexCode, MethodSignature methodSignature) {
-    assertTrue("At least one invoke to '" + methodSignature.toString() + "'",
-        Arrays.stream(dexCode.instructions)
-            .filter((instr) -> instr.getMethod() != null)
-            .noneMatch((instr) -> instr.getMethod().name.toString().equals(methodSignature.name)));
+  private static void checkMethodIsNeverInvoked(
+      MethodSubject subject, MethodSignature methodSignature) {
+    assertTrue(
+        "At least one invoke to '" + methodSignature.toString() + "'",
+        subject
+            .streamInstructions()
+            .filter(InstructionSubject::isInvoke)
+            .noneMatch(
+                instructionSubject ->
+                    instructionSubject
+                        .getMethod()
+                        .getName()
+                        .toString()
+                        .equals(methodSignature.name)));
   }
 
   protected static void checkMethodsPresence(
diff --git a/src/test/java/com/android/tools/r8/kotlin/KotlinClassInlinerTest.java b/src/test/java/com/android/tools/r8/kotlin/KotlinClassInlinerTest.java
index 105d097..c245f5a 100644
--- a/src/test/java/com/android/tools/r8/kotlin/KotlinClassInlinerTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/KotlinClassInlinerTest.java
@@ -14,13 +14,14 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
-import static org.junit.Assume.assumeTrue;
 
 import com.android.tools.r8.KotlinTestParameters;
 import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.cf.code.CfInstruction;
+import com.android.tools.r8.cf.code.CfNew;
+import com.android.tools.r8.code.Instruction;
 import com.android.tools.r8.code.NewInstance;
 import com.android.tools.r8.code.SgetObject;
-import com.android.tools.r8.graph.DexCode;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.naming.MemberNaming.MethodSignature;
 import com.android.tools.r8.utils.IntBox;
@@ -35,7 +36,6 @@
 import java.util.Set;
 import java.util.function.Predicate;
 import java.util.stream.Collectors;
-import java.util.stream.Stream;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
@@ -244,8 +244,6 @@
 
   @Test
   public void testDataClass() throws Exception {
-    // TODO(b/179866251): Update tests.
-    assumeTrue(kotlinc.is(KOTLINC_1_3_72) && testParameters.isDexRuntime());
     String mainClassName = "class_inliner_data_class.MainKt";
     runTest("class_inliner_data_class", mainClassName)
         .inspect(
@@ -260,7 +258,10 @@
                       String[].class.getCanonicalName()));
               assertEquals(
                   Lists.newArrayList(
-                      "void kotlin.jvm.internal.Intrinsics.throwParameterIsNullException(java.lang.String)"),
+                      kotlinc.is(KOTLINC_1_3_72)
+                          ? "void kotlin.jvm.internal.Intrinsics.throwParameterIsNullException(java.lang.String)"
+                          : "void kotlin.jvm.internal.Intrinsics.checkNotNullParameter(java.lang.Object,"
+                                + " java.lang.String)"),
                   collectStaticCalls(clazz, "main", String[].class.getCanonicalName()));
             });
   }
@@ -272,15 +273,30 @@
       String... params) {
     assertNotNull(clazz);
     MethodSignature signature = new MethodSignature(methodName, "void", params);
-    // TODO(b/179866251): Allow for CF code here.
-    DexCode code = clazz.method(signature).getMethod().getCode().asDexCode();
-    return Stream.concat(
-        filterInstructionKind(code, NewInstance.class)
-            .map(insn -> ((NewInstance) insn).getType()),
-        filterInstructionKind(code, SgetObject.class)
-            .map(insn -> insn.getField().holder)
-    )
-        .filter(isTypeOfInterest)
+    return clazz
+        .method(signature)
+        .streamInstructions()
+        .filter(instruction -> instruction.isNewInstance() || instruction.isStaticGet())
+        .map(
+            instruction -> {
+              if (instruction.isCfInstruction()) {
+                CfInstruction baseInstruction = instruction.asCfInstruction().getInstruction();
+                if (baseInstruction instanceof CfNew) {
+                  return ((CfNew) baseInstruction).getType();
+                } else if (instruction.getField().getType().isReferenceType()) {
+                  return instruction.getField().getHolderType();
+                }
+              } else {
+                Instruction baseInstruction = instruction.asDexInstruction().getInstruction();
+                if (baseInstruction instanceof SgetObject) {
+                  return baseInstruction.getField().getHolderType();
+                } else if (baseInstruction instanceof NewInstance) {
+                  return ((NewInstance) baseInstruction).getType();
+                }
+              }
+              return null;
+            })
+        .filter(type -> type != null && isTypeOfInterest.test(type))
         .map(DexType::toSourceString)
         .collect(Collectors.toSet());
   }
diff --git a/src/test/java/com/android/tools/r8/kotlin/KotlinDuplicateAnnotationTest.java b/src/test/java/com/android/tools/r8/kotlin/KotlinDuplicateAnnotationTest.java
index 89e517a..af6986a 100644
--- a/src/test/java/com/android/tools/r8/kotlin/KotlinDuplicateAnnotationTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/KotlinDuplicateAnnotationTest.java
@@ -32,9 +32,9 @@
   );
 
   private Consumer<InternalOptions> optionsModifier =
-    o -> {
-      o.enableInlining = false;
-    };
+      o -> {
+        o.inlinerOptions().enableInlining = false;
+      };
 
   @Parameterized.Parameters(name = "{0}, {1}, allowAccessModification: {2}")
   public static Collection<Object[]> data() {
diff --git a/src/test/java/com/android/tools/r8/kotlin/KotlinIntrinsicsInlineTest.java b/src/test/java/com/android/tools/r8/kotlin/KotlinIntrinsicsInlineTest.java
index 6ec949a..c407a5b 100644
--- a/src/test/java/com/android/tools/r8/kotlin/KotlinIntrinsicsInlineTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/KotlinIntrinsicsInlineTest.java
@@ -3,13 +3,13 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.kotlin;
 
-import static com.android.tools.r8.KotlinCompilerTool.KotlinCompilerVersion.KOTLINC_1_3_72;
 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 static org.junit.Assume.assumeTrue;
 
+import com.android.tools.r8.KotlinCompilerTool.KotlinCompilerVersion;
 import com.android.tools.r8.KotlinTestBase;
 import com.android.tools.r8.KotlinTestParameters;
 import com.android.tools.r8.TestParameters;
@@ -53,8 +53,6 @@
 
   @Test
   public void b139432507() throws Exception {
-    // TODO(b/179866251): Update tests.
-    assumeTrue(kotlinc.is(KOTLINC_1_3_72) || allowAccessModification);
     testForR8(parameters.getBackend())
         .addProgramFiles(
             compiledJars.getForConfiguration(kotlinc, targetVersion),
@@ -78,7 +76,9 @@
               MethodSubject isSupported = main.uniqueMethodWithName("isSupported");
               assertThat(isSupported, isPresent());
               assertEquals(
-                  allowAccessModification ? 0 : 1,
+                  !kotlinc.is(KotlinCompilerVersion.KOTLINC_1_3_72) || allowAccessModification
+                      ? 0
+                      : 1,
                   countCall(isSupported, "checkParameterIsNotNull"));
 
               // In general cases, null check won't be invoked only once or twice, hence no subtle
@@ -91,21 +91,22 @@
 
   @Test
   public void b139432507_isSupported() throws Exception {
-    // TODO(b/179866251): Update tests.
-    assumeTrue(kotlinc.is(KOTLINC_1_3_72) || allowAccessModification);
     assumeTrue("Different inlining behavior on CF backend", parameters.isDexRuntime());
-    testSingle("isSupported");
+    testSingle(
+        "isSupported",
+        kotlinc.is(KotlinCompilerVersion.KOTLINC_1_3_72) && !allowAccessModification);
   }
 
   @Test
   public void b139432507_containsArray() throws Exception {
-    // TODO(b/179866251): Update tests.
-    assumeTrue(kotlinc.is(KOTLINC_1_3_72) || allowAccessModification);
     assumeTrue("Different inlining behavior on CF backend", parameters.isDexRuntime());
-    testSingle("containsArray");
+    // One for each of the method's own arguments, unless building with
+    // -allowaccessmodification.
+    testSingle("containsArray", allowAccessModification);
   }
 
-  private void testSingle(String methodName) throws Exception {
+  private void testSingle(String methodName, boolean checkParameterIsNotNullCountIsArity)
+      throws Exception {
     testForR8(parameters.getBackend())
         .addProgramFiles(
             compiledJars.getForConfiguration(kotlinc, targetVersion),
@@ -129,10 +130,8 @@
               MethodSubject method = main.uniqueMethodWithName(methodName);
               assertThat(method, isPresent());
               int arity = method.getMethod().getReference().getArity();
-              // One for each of the method's own arguments, unless building with
-              // -allowaccessmodification.
               assertEquals(
-                  allowAccessModification ? 0 : arity,
+                  checkParameterIsNotNullCountIsArity ? arity : 0,
                   countCall(method, "checkParameterIsNotNull"));
             });
   }
diff --git a/src/test/java/com/android/tools/r8/kotlin/KotlinUnusedSingletonTest.java b/src/test/java/com/android/tools/r8/kotlin/KotlinUnusedSingletonTest.java
index 9d66188..692f73b 100644
--- a/src/test/java/com/android/tools/r8/kotlin/KotlinUnusedSingletonTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/KotlinUnusedSingletonTest.java
@@ -3,13 +3,11 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.kotlin;
 
-import static com.android.tools.r8.KotlinCompilerTool.KotlinCompilerVersion.KOTLINC_1_3_72;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
 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 static org.junit.Assume.assumeTrue;
 
 import com.android.tools.r8.KotlinTestParameters;
 import com.android.tools.r8.TestParameters;
@@ -49,8 +47,6 @@
 
   @Test
   public void b110196118() throws Exception {
-    // TODO(b/179866251): Update tests.
-    assumeTrue(kotlinc.is(KOTLINC_1_3_72));
     final String mainClassName = "unused_singleton.MainKt";
     final String moduleName = "unused_singleton.TestModule";
     runTest("unused_singleton", mainClassName)
diff --git a/src/test/java/com/android/tools/r8/kotlin/R8KotlinAccessorTest.java b/src/test/java/com/android/tools/r8/kotlin/R8KotlinAccessorTest.java
index 282d8d7..8cc28de 100644
--- a/src/test/java/com/android/tools/r8/kotlin/R8KotlinAccessorTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/R8KotlinAccessorTest.java
@@ -8,7 +8,6 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
-import static org.junit.Assume.assumeTrue;
 
 import com.android.tools.r8.KotlinTestParameters;
 import com.android.tools.r8.R8TestBuilder;
@@ -388,8 +387,6 @@
 
   @Test
   public void testAccessorForInnerClassIsRemovedWhenNotUsed() throws Exception {
-    // TODO(b/185493636): Kotlinc 1.5 generated property accessors are not removed.
-    assumeTrue(kotlinc.isNot(KOTLINC_1_5_0));
     String mainClass =
         addMainToClasspath(
             "accessors.PropertyAccessorForInnerClassKt", "noUseOfPropertyAccessorFromInnerClass");
@@ -443,14 +440,7 @@
     String mainClass = addMainToClasspath(testedClass.className + "Kt",
         "usePrivateLateInitPropertyAccessorFromInnerClass");
     runTest("accessors", mainClass)
-        .inspect(
-            inspector -> {
-              if (kotlinc.getCompilerVersion() == KOTLINC_1_5_0 && testParameters.isDexRuntime()) {
-                checkClassIsKept(inspector, testedClass.getClassName());
-              } else {
-                checkClassIsRemoved(inspector, testedClass.getClassName());
-              }
-            });
+        .inspect(inspector -> checkClassIsRemoved(inspector, testedClass.getClassName()));
   }
 
   @Test
diff --git a/src/test/java/com/android/tools/r8/kotlin/R8KotlinDataClassTest.java b/src/test/java/com/android/tools/r8/kotlin/R8KotlinDataClassTest.java
index 2cb21d4..1da9c86 100644
--- a/src/test/java/com/android/tools/r8/kotlin/R8KotlinDataClassTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/R8KotlinDataClassTest.java
@@ -4,11 +4,8 @@
 
 package com.android.tools.r8.kotlin;
 
-import static org.junit.Assume.assumeTrue;
-
 import com.android.tools.r8.KotlinTestParameters;
 import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.graph.DexCode;
 import com.android.tools.r8.kotlin.TestKotlinClass.Visibility;
 import com.android.tools.r8.naming.MemberNaming.MethodSignature;
 import com.android.tools.r8.utils.BooleanUtils;
@@ -62,8 +59,6 @@
 
   @Test
   public void test_dataclass_gettersOnly() throws Exception {
-    // TODO(b/179866251): Allow for CF code.
-    assumeTrue(testParameters.isDexRuntime());
     String mainClassName = "dataclass.MainGettersOnlyKt";
     MethodSignature testMethodSignature =
         new MethodSignature("testDataClassGetters", "void", Collections.emptyList());
@@ -99,20 +94,17 @@
 
               ClassSubject classSubject = checkClassIsKept(inspector, mainClassName);
               MethodSubject testMethod = checkMethodIsKept(classSubject, testMethodSignature);
-              DexCode dexCode = getDexCode(testMethod);
               if (allowAccessModification) {
                 // Both getters should be inlined
-                checkMethodIsNeverInvoked(dexCode, NAME_GETTER_METHOD, AGE_GETTER_METHOD);
+                checkMethodIsNeverInvoked(testMethod, NAME_GETTER_METHOD, AGE_GETTER_METHOD);
               } else {
-                checkMethodIsInvokedAtLeastOnce(dexCode, NAME_GETTER_METHOD, AGE_GETTER_METHOD);
+                checkMethodIsInvokedAtLeastOnce(testMethod, NAME_GETTER_METHOD, AGE_GETTER_METHOD);
               }
             });
   }
 
   @Test
   public void test_dataclass_componentOnly() throws Exception {
-    // TODO(b/179866251): Allow for CF code.
-    assumeTrue(testParameters.isDexRuntime());
     String mainClassName = "dataclass.MainComponentOnlyKt";
     MethodSignature testMethodSignature =
         new MethodSignature("testAllDataClassComponentFunctions", "void", Collections.emptyList());
@@ -150,19 +142,16 @@
 
               ClassSubject classSubject = checkClassIsKept(inspector, mainClassName);
               MethodSubject testMethod = checkMethodIsKept(classSubject, testMethodSignature);
-              DexCode dexCode = getDexCode(testMethod);
               if (allowAccessModification) {
-                checkMethodIsNeverInvoked(dexCode, COMPONENT1_METHOD, COMPONENT2_METHOD);
+                checkMethodIsNeverInvoked(testMethod, COMPONENT1_METHOD, COMPONENT2_METHOD);
               } else {
-                checkMethodIsInvokedAtLeastOnce(dexCode, COMPONENT1_METHOD, COMPONENT2_METHOD);
+                checkMethodIsInvokedAtLeastOnce(testMethod, COMPONENT1_METHOD, COMPONENT2_METHOD);
               }
             });
   }
 
   @Test
   public void test_dataclass_componentPartial() throws Exception {
-    // TODO(b/179866251): Allow for CF code.
-    assumeTrue(testParameters.isDexRuntime());
     String mainClassName = "dataclass.MainComponentPartialKt";
     MethodSignature testMethodSignature =
         new MethodSignature("testSomeDataClassComponentFunctions", "void", Collections.emptyList());
@@ -198,11 +187,10 @@
 
               ClassSubject classSubject = checkClassIsKept(inspector, mainClassName);
               MethodSubject testMethod = checkMethodIsKept(classSubject, testMethodSignature);
-              DexCode dexCode = getDexCode(testMethod);
               if (allowAccessModification) {
-                checkMethodIsNeverInvoked(dexCode, COMPONENT2_METHOD);
+                checkMethodIsNeverInvoked(testMethod, COMPONENT2_METHOD);
               } else {
-                checkMethodIsInvokedAtLeastOnce(dexCode, COMPONENT2_METHOD);
+                checkMethodIsInvokedAtLeastOnce(testMethod, COMPONENT2_METHOD);
               }
             });
   }
diff --git a/src/test/java/com/android/tools/r8/kotlin/R8KotlinIntrinsicsTest.java b/src/test/java/com/android/tools/r8/kotlin/R8KotlinIntrinsicsTest.java
index 4457386..142e48d 100644
--- a/src/test/java/com/android/tools/r8/kotlin/R8KotlinIntrinsicsTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/R8KotlinIntrinsicsTest.java
@@ -4,9 +4,7 @@
 
 package com.android.tools.r8.kotlin;
 
-import static com.android.tools.r8.KotlinCompilerTool.KotlinCompilerVersion.KOTLINC_1_3_72;
-import static org.junit.Assume.assumeTrue;
-
+import com.android.tools.r8.KotlinCompilerTool.KotlinCompilerVersion;
 import com.android.tools.r8.KotlinTestParameters;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.naming.MemberNaming.MethodSignature;
@@ -44,12 +42,9 @@
 
   @Test
   public void testParameterNullCheckIsInlined() throws Exception {
-    // TODO(b/179866251): Update tests.
-    assumeTrue(kotlinc.is(KOTLINC_1_3_72));
     final String extraRules = keepClassMethod("intrinsics.IntrinsicsKt",
         new MethodSignature("expectsNonNullParameters",
             "java.lang.String", Lists.newArrayList("java.lang.String", "java.lang.String")));
-
     runTest(
             "intrinsics",
             "intrinsics.IntrinsicsKt",
@@ -67,13 +62,15 @@
                               "throwParameterIsNullException",
                               "void",
                               Collections.singletonList("java.lang.String")),
-                          true)
+                          // throwParameterIsNullException is not added for test starting from 1.4
+                          kotlinc.is(KotlinCompilerVersion.KOTLINC_1_3_72))
                       .put(
                           new MethodSignature(
                               "checkParameterIsNotNull",
                               "void",
                               Lists.newArrayList("java.lang.Object", "java.lang.String")),
-                          !allowAccessModification)
+                          kotlinc.is(KotlinCompilerVersion.KOTLINC_1_3_72)
+                              && !allowAccessModification)
                       .build());
             });
   }
diff --git a/src/test/java/com/android/tools/r8/kotlin/SimplifyIfNotNullKotlinTest.java b/src/test/java/com/android/tools/r8/kotlin/SimplifyIfNotNullKotlinTest.java
index 091a8b4..488271b 100644
--- a/src/test/java/com/android/tools/r8/kotlin/SimplifyIfNotNullKotlinTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/SimplifyIfNotNullKotlinTest.java
@@ -3,9 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.kotlin;
 
-import static com.android.tools.r8.KotlinCompilerTool.KotlinCompilerVersion.KOTLINC_1_3_72;
 import static org.junit.Assert.assertEquals;
-import static org.junit.Assume.assumeTrue;
 
 import com.android.tools.r8.KotlinTestParameters;
 import com.android.tools.r8.TestParameters;
@@ -69,20 +67,20 @@
               // Find forMakeAndModel(...) after parameter removal.
               MethodSubject testMethod = clazz.uniqueMethodWithName(testMethodSignature.name);
               long ifzCount =
-                  testMethod.streamInstructions().filter(i -> i.isIfEqz() || i.isIfNez()).count();
+                  testMethod
+                      .streamInstructions()
+                      .filter(i -> i.isIfEqz() || i.isIfNez() || i.isIfNonNull())
+                      .count();
               long paramNullCheckCount =
                   countCall(testMethod, "Intrinsics", "checkParameterIsNotNull");
               // One after Iterator#hasNext, and another in the filter predicate: sinceYear != null.
-              // TODO(b/179951729): Not the same amount of ifz on CF and DEX.
-              assertEquals(testParameters.isCfRuntime() ? 1 : 2, ifzCount);
+              assertEquals(2, ifzCount);
               assertEquals(0, paramNullCheckCount);
             });
   }
 
   @Test
   public void test_example2() throws Exception {
-    // TODO(b/179866251): Update tests.
-    assumeTrue(kotlinc.is(KOTLINC_1_3_72) || allowAccessModification);
     final TestKotlinClass ex2 = new TestKotlinClass("non_null.Example2Kt");
     final MethodSignature testMethodSignature =
         new MethodSignature("aOrDefault", STRING, ImmutableList.of(STRING, STRING));
@@ -93,15 +91,16 @@
         .inspect(
             inspector -> {
               ClassSubject clazz = checkClassIsKept(inspector, ex2.getClassName());
-
               MethodSubject testMethod = checkMethodIsKept(clazz, testMethodSignature);
               long ifzCount =
-                  testMethod.streamInstructions().filter(i -> i.isIfEqz() || i.isIfNez()).count();
+                  testMethod
+                      .streamInstructions()
+                      .filter(i -> i.isIfEqz() || i.isIfNez() || i.isIfNull() || i.isIfNonNull())
+                      .count();
               long paramNullCheckCount =
                   countCall(testMethod, "Intrinsics", "checkParameterIsNotNull");
               // ?: in aOrDefault
-              // TODO(b/179951729): Not the same amount of ifz on CF and DEX.
-              assertEquals(testParameters.isCfRuntime() ? 0 : 1, ifzCount);
+              assertEquals(1, ifzCount);
               assertEquals(0, paramNullCheckCount);
             });
   }
@@ -118,14 +117,15 @@
         .inspect(
             inspector -> {
               ClassSubject clazz = checkClassIsKept(inspector, ex3.getClassName());
-
               MethodSubject testMethod = checkMethodIsKept(clazz, testMethodSignature);
               long ifzCount =
-                  testMethod.streamInstructions().filter(i -> i.isIfEqz() || i.isIfNez()).count();
+                  testMethod
+                      .streamInstructions()
+                      .filter(i -> i.isIfEqz() || i.isIfNez() || i.isIfNull() || i.isIfNonNull())
+                      .count();
               // !! operator inside explicit null check should be gone.
               // One explicit null-check as well as 4 bar? accesses.
-              // TODO(b/179951729): Not the same amount of ifz on CF and DEX.
-              assertEquals(testParameters.isCfRuntime() ? 0 : 5, ifzCount);
+              assertEquals(5, ifzCount);
             });
   }
 }
diff --git a/src/test/java/com/android/tools/r8/kotlin/lambda/KotlinLambdaMergingWithSmallInliningBudgetTest.java b/src/test/java/com/android/tools/r8/kotlin/lambda/KotlinLambdaMergingWithSmallInliningBudgetTest.java
index 729869f..78613d4 100644
--- a/src/test/java/com/android/tools/r8/kotlin/lambda/KotlinLambdaMergingWithSmallInliningBudgetTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/lambda/KotlinLambdaMergingWithSmallInliningBudgetTest.java
@@ -37,7 +37,7 @@
         "lambdas_jstyle_runnable",
         mainClassName,
         testBuilder ->
-            testBuilder
-                .addOptionsModification(options -> options.inliningInstructionAllowance = 3));
+            testBuilder.addOptionsModification(
+                options -> options.inlinerOptions().inliningInstructionAllowance = 3));
   }
 }
diff --git a/src/test/java/com/android/tools/r8/kotlin/lambda/b159688129/LambdaSplitByCodeCorrectnessTest.java b/src/test/java/com/android/tools/r8/kotlin/lambda/b159688129/LambdaSplitByCodeCorrectnessTest.java
index 7bd2b8c..a4f497f 100644
--- a/src/test/java/com/android/tools/r8/kotlin/lambda/b159688129/LambdaSplitByCodeCorrectnessTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/lambda/b159688129/LambdaSplitByCodeCorrectnessTest.java
@@ -70,7 +70,7 @@
                     internalOptions ->
                         // Setting inliningInstructionAllowance = 1 will force each switch branch to
                         // contain an invoke instruction to a private method.
-                        internalOptions.inliningInstructionAllowance = 1))
+                        internalOptions.inlinerOptions().inliningInstructionAllowance = 1))
         .addHorizontallyMergedClassesInspector(
             inspector ->
                 inspector.assertIsCompleteMergeGroup(
diff --git a/src/test/java/com/android/tools/r8/maindexlist/MainDexTracingTest.java b/src/test/java/com/android/tools/r8/maindexlist/MainDexTracingTest.java
index 42cd647..013a15b 100644
--- a/src/test/java/com/android/tools/r8/maindexlist/MainDexTracingTest.java
+++ b/src/test/java/com/android/tools/r8/maindexlist/MainDexTracingTest.java
@@ -118,7 +118,7 @@
         builder ->
             builder.addOptionsModification(
                 options -> {
-                  options.enableInlining = false;
+                  options.inlinerOptions().enableInlining = false;
                   options.mainDexKeptGraphConsumer = graphConsumer;
                 }));
     {
@@ -211,7 +211,9 @@
         Paths.get(EXAMPLE_O_SRC_DIR, "multidex004", "ref-list-1.txt"),
         Paths.get(EXAMPLE_O_SRC_DIR, "multidex004", "ref-list-1.txt"),
         AndroidApiLevel.I,
-        builder -> builder.addOptionsModification(options -> options.enableInlining = false));
+        builder ->
+            builder.addOptionsModification(
+                options -> options.inlinerOptions().enableInlining = false));
   }
 
   @Test
@@ -311,7 +313,9 @@
         expectedR8MainDexList,
         expectedMainDexList,
         minSdk,
-        builder -> builder.addOptionsModification(options -> options.enableInlining = false));
+        builder ->
+            builder.addOptionsModification(
+                options -> options.inlinerOptions().enableInlining = false));
   }
 
   private void doTest(
diff --git a/src/test/java/com/android/tools/r8/memberrebinding/MemberRebindingTest.java b/src/test/java/com/android/tools/r8/memberrebinding/MemberRebindingTest.java
index 756cf01..48c9501 100644
--- a/src/test/java/com/android/tools/r8/memberrebinding/MemberRebindingTest.java
+++ b/src/test/java/com/android/tools/r8/memberrebinding/MemberRebindingTest.java
@@ -104,7 +104,7 @@
     ToolHelper.runR8(
         builder.build(),
         options -> {
-          options.enableInlining = false;
+          options.inlinerOptions().enableInlining = false;
           options.enableRedundantFieldLoadElimination = false;
         });
   }
diff --git a/src/test/java/com/android/tools/r8/naming/CovariantReturnTypeInSubInterfaceTest.java b/src/test/java/com/android/tools/r8/naming/CovariantReturnTypeInSubInterfaceTest.java
index 3eae470..97073ac 100644
--- a/src/test/java/com/android/tools/r8/naming/CovariantReturnTypeInSubInterfaceTest.java
+++ b/src/test/java/com/android/tools/r8/naming/CovariantReturnTypeInSubInterfaceTest.java
@@ -86,7 +86,8 @@
             "-keep,allowobfuscation class **.*$Super* { <methods>; }",
             "-keep,allowobfuscation class **.*$Sub* { <methods>; }",
             overloadAggressively ? "-overloadaggressively" : "# Not overload aggressively")
-        .addOptionsModification(internalOptions -> internalOptions.enableInlining = false)
+        .addOptionsModification(
+            internalOptions -> internalOptions.inlinerOptions().enableInlining = false)
         .run(parameters.getRuntime(), TestMain.class)
         .inspect(inspector -> inspect(inspector, overloadAggressively));
   }
diff --git a/src/test/java/com/android/tools/r8/naming/WarnReflectiveAccessTestRunner.java b/src/test/java/com/android/tools/r8/naming/WarnReflectiveAccessTestRunner.java
index 778b8ad..0e06680 100644
--- a/src/test/java/com/android/tools/r8/naming/WarnReflectiveAccessTestRunner.java
+++ b/src/test/java/com/android/tools/r8/naming/WarnReflectiveAccessTestRunner.java
@@ -97,7 +97,7 @@
     return ToolHelper.runR8(
         commandBuilder.build(),
         o -> {
-          o.enableInlining = false;
+          o.inlinerOptions().enableInlining = false;
           o.forceProguardCompatibility = forceProguardCompatibility;
         });
   }
diff --git a/src/test/java/com/android/tools/r8/naming/applymapping/ApplyMappingAfterDevirtualizationTest.java b/src/test/java/com/android/tools/r8/naming/applymapping/ApplyMappingAfterDevirtualizationTest.java
index 2587730..670b6ae 100644
--- a/src/test/java/com/android/tools/r8/naming/applymapping/ApplyMappingAfterDevirtualizationTest.java
+++ b/src/test/java/com/android/tools/r8/naming/applymapping/ApplyMappingAfterDevirtualizationTest.java
@@ -105,7 +105,7 @@
             .addProgramClasses(LIBRARY_CLASSES)
             .addKeepClassAndMembersRulesWithAllowObfuscation(LibClassA.class)
             .addKeepMainRule(LibClassB.class)
-            .addOptionsModification(options -> options.enableInlining = false)
+            .addOptionsModification(options -> options.inlinerOptions().enableInlining = false)
             .compile();
 
     CodeInspector inspector = libraryResult.inspector();
@@ -136,7 +136,7 @@
             .addProgramClasses(LIBRARY_CLASSES)
             .addKeepClassAndMembersRulesWithAllowObfuscation(LibClassA.class, LibInterfaceA.class)
             .addKeepMainRule(LibClassB.class)
-            .addOptionsModification(options -> options.enableInlining = false)
+            .addOptionsModification(options -> options.inlinerOptions().enableInlining = false)
             .compile();
 
     CodeInspector inspector = libraryResult.inspector();
diff --git a/src/test/java/com/android/tools/r8/naming/applymapping/ApplyMappingVirtualInvokeTest.java b/src/test/java/com/android/tools/r8/naming/applymapping/ApplyMappingVirtualInvokeTest.java
index e01926d..84a7f62 100644
--- a/src/test/java/com/android/tools/r8/naming/applymapping/ApplyMappingVirtualInvokeTest.java
+++ b/src/test/java/com/android/tools/r8/naming/applymapping/ApplyMappingVirtualInvokeTest.java
@@ -109,7 +109,7 @@
         .setMinApi(parameters.getRuntime())
         .addOptionsModification(
             options -> {
-              options.enableInlining = false;
+              options.inlinerOptions().enableInlining = false;
               options.enableVerticalClassMerging = false;
               options.enableClassInlining = false;
             })
diff --git a/src/test/java/com/android/tools/r8/naming/applymapping/sourcelibrary/ApplyMappingTest.java b/src/test/java/com/android/tools/r8/naming/applymapping/sourcelibrary/ApplyMappingTest.java
index c064105..554ea5c 100644
--- a/src/test/java/com/android/tools/r8/naming/applymapping/sourcelibrary/ApplyMappingTest.java
+++ b/src/test/java/com/android/tools/r8/naming/applymapping/sourcelibrary/ApplyMappingTest.java
@@ -219,7 +219,7 @@
         command,
         options -> {
           // Disable inlining to make this test not depend on inlining decisions.
-          options.enableInlining = false;
+          options.inlinerOptions().enableInlining = false;
         });
   }
 }
diff --git a/src/test/java/com/android/tools/r8/naming/applymapping/sourcelibrary/MemberResolutionAsmTest.java b/src/test/java/com/android/tools/r8/naming/applymapping/sourcelibrary/MemberResolutionAsmTest.java
index a689715..da28db3 100644
--- a/src/test/java/com/android/tools/r8/naming/applymapping/sourcelibrary/MemberResolutionAsmTest.java
+++ b/src/test/java/com/android/tools/r8/naming/applymapping/sourcelibrary/MemberResolutionAsmTest.java
@@ -105,7 +105,7 @@
             .addApplyMapping(pgMap)
             .addOptionsModification(
                 options -> {
-                  options.enableInlining = false;
+                  options.inlinerOptions().enableInlining = false;
                   options.enableVerticalClassMerging = false;
                 })
             .run(parameters.getRuntime(), noMappingMain)
@@ -197,7 +197,7 @@
             .addApplyMapping(pgMap)
             .addOptionsModification(
                 options -> {
-                  options.enableInlining = false;
+                  options.inlinerOptions().enableInlining = false;
                   options.enableVerticalClassMerging = false;
                 })
             .compile();
diff --git a/src/test/java/com/android/tools/r8/naming/applymapping/sourcelibrary/MemberResolutionTest.java b/src/test/java/com/android/tools/r8/naming/applymapping/sourcelibrary/MemberResolutionTest.java
index cbd6e3b..7ed73c6 100644
--- a/src/test/java/com/android/tools/r8/naming/applymapping/sourcelibrary/MemberResolutionTest.java
+++ b/src/test/java/com/android/tools/r8/naming/applymapping/sourcelibrary/MemberResolutionTest.java
@@ -124,7 +124,7 @@
             .addKeepRules("-applymapping " + mapPath)
             .enableMemberValuePropagationAnnotations()
             .enableNoVerticalClassMergingAnnotations()
-            .addOptionsModification(options -> options.enableInlining = false)
+            .addOptionsModification(options -> options.inlinerOptions().enableInlining = false)
             .setMinApi(parameters.getApiLevel())
             .run(parameters.getRuntime(), MemberResolutionTestMain.class)
             .assertSuccessWithOutput(expectedOutput)
diff --git a/src/test/java/com/android/tools/r8/naming/b114554345/B114554345.java b/src/test/java/com/android/tools/r8/naming/b114554345/B114554345.java
index 6ceff62..4af7be5 100644
--- a/src/test/java/com/android/tools/r8/naming/b114554345/B114554345.java
+++ b/src/test/java/com/android/tools/r8/naming/b114554345/B114554345.java
@@ -41,7 +41,7 @@
         compileWithR8(
             input,
             keepMainProguardConfiguration(TestDriver.class),
-            options -> options.enableInlining = false,
+            options -> options.inlinerOptions().enableInlining = false,
             backend);
     String mainClass = TestDriver.class.getName();
     assertEquals(runOnJava(TestDriver.class), runOnVM(output, mainClass, backend));
diff --git a/src/test/java/com/android/tools/r8/naming/overloadaggressively/OverloadAggressivelyTest.java b/src/test/java/com/android/tools/r8/naming/overloadaggressively/OverloadAggressivelyTest.java
index e691d86..cbd22a0 100644
--- a/src/test/java/com/android/tools/r8/naming/overloadaggressively/OverloadAggressivelyTest.java
+++ b/src/test/java/com/android/tools/r8/naming/overloadaggressively/OverloadAggressivelyTest.java
@@ -59,10 +59,12 @@
             .setOutput(out, outputMode(backend))
             .addLibraryFiles(TestBase.runtimeJar(backend))
             .build();
-    return ToolHelper.runR8(command, o -> {
-      o.enableInlining = false;
-      o.forceProguardCompatibility = true;
-    });
+    return ToolHelper.runR8(
+        command,
+        o -> {
+          o.inlinerOptions().enableInlining = false;
+          o.forceProguardCompatibility = true;
+        });
   }
 
   private ProcessResult runRaw(AndroidApp app, String main) throws IOException {
@@ -183,7 +185,7 @@
     testForR8Compat(backend)
         .addProgramClasses(MethodResolution.class, B.class)
         .addKeepMainRule(MethodResolution.class)
-        .addOptionsModification(options -> options.enableInlining = false)
+        .addOptionsModification(options -> options.inlinerOptions().enableInlining = false)
         .applyIf(overloadaggressively, builder -> builder.addKeepRules("-overloadaggressively"))
         .enableMemberValuePropagationAnnotations()
         .compile()
diff --git a/src/test/java/com/android/tools/r8/naming/retrace/LineNumberRangeTest.java b/src/test/java/com/android/tools/r8/naming/retrace/LineNumberRangeTest.java
index 02ed76d..84f0389 100644
--- a/src/test/java/com/android/tools/r8/naming/retrace/LineNumberRangeTest.java
+++ b/src/test/java/com/android/tools/r8/naming/retrace/LineNumberRangeTest.java
@@ -86,7 +86,7 @@
             .setMinApi(parameters.getApiLevel())
             .addOptionsModification(
                 options -> {
-                  options.enableInlining = false;
+                  options.inlinerOptions().enableInlining = false;
                 })
             .run(parameters.getRuntime(), Main.class)
             .assertFailure();
diff --git a/src/test/java/com/android/tools/r8/naming/retrace/StackTrace.java b/src/test/java/com/android/tools/r8/naming/retrace/StackTrace.java
index c4d6dad..4472d60 100644
--- a/src/test/java/com/android/tools/r8/naming/retrace/StackTrace.java
+++ b/src/test/java/com/android/tools/r8/naming/retrace/StackTrace.java
@@ -132,6 +132,13 @@
         return this;
       }
 
+      public Builder applyIf(boolean condition, Consumer<Builder> fn) {
+        if (condition) {
+          fn.accept(this);
+        }
+        return this;
+      }
+
       public StackTraceLine build() {
         String lineNumberPart = lineNumber >= 0 ? ":" + lineNumber : "";
         String originalLine = className + '.' + methodName + '(' + fileName + lineNumberPart + ')';
@@ -490,6 +497,45 @@
     }
   }
 
+  // Equivalence comparing stack traces without taking the file name into account.
+  public static class EquivalenceWithoutLineNumbers extends StackTraceEquivalence {
+
+    private static class LineEquivalence extends Equivalence<StackTrace.StackTraceLine> {
+
+      private static final LineEquivalence INSTANCE = new LineEquivalence();
+
+      public static LineEquivalence get() {
+        return INSTANCE;
+      }
+
+      @Override
+      protected boolean doEquivalent(StackTrace.StackTraceLine a, StackTrace.StackTraceLine b) {
+        return a.className.equals(b.className)
+            && a.methodName.equals(b.methodName)
+            && Objects.equals(a.fileName, b.fileName);
+      }
+
+      @Override
+      protected int doHash(StackTrace.StackTraceLine stackTraceLine) {
+        return stackTraceLine.className.hashCode() * 13
+            + stackTraceLine.methodName.hashCode() * 7
+            + Objects.hashCode(stackTraceLine.fileName);
+      }
+    }
+
+    private static final EquivalenceWithoutLineNumbers INSTANCE =
+        new EquivalenceWithoutLineNumbers();
+
+    public static EquivalenceWithoutLineNumbers get() {
+      return INSTANCE;
+    }
+
+    @Override
+    public Equivalence<StackTrace.StackTraceLine> getLineEquivalence() {
+      return LineEquivalence.get();
+    }
+  }
+
   // Equivalence comparing stack traces without taking the file name and line number into account.
   public static class EquivalenceWithoutFileNameAndLineNumber extends StackTraceEquivalence {
 
@@ -637,10 +683,20 @@
     }
   }
 
+  public static class StackTraceIgnoreLineNumbersMatcher extends StackTraceMatcherBase {
+    private StackTraceIgnoreLineNumbersMatcher(StackTrace expected) {
+      super(expected, EquivalenceWithoutLineNumbers.get(), "(ignoring line numbers)");
+    }
+  }
+
   public static Matcher<StackTrace> isSameExceptForFileName(StackTrace stackTrace) {
     return new StackTraceIgnoreFileNameMatcher(stackTrace);
   }
 
+  public static Matcher<StackTrace> isSameExceptForLineNumbers(StackTrace stackTrace) {
+    return new StackTraceIgnoreLineNumbersMatcher(stackTrace);
+  }
+
   public static class StackTraceIgnoreFileNameAndLineNumberMatcher extends StackTraceMatcherBase {
     private StackTraceIgnoreFileNameAndLineNumberMatcher(StackTrace expected) {
       super(
diff --git a/src/test/java/com/android/tools/r8/naming/retraceproguard/VerticalClassMergingRetraceTest.java b/src/test/java/com/android/tools/r8/naming/retraceproguard/VerticalClassMergingRetraceTest.java
new file mode 100644
index 0000000..3ad54fb
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/naming/retraceproguard/VerticalClassMergingRetraceTest.java
@@ -0,0 +1,163 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.naming.retraceproguard;
+
+import static com.android.tools.r8.naming.retraceproguard.StackTrace.isSameExceptForFileName;
+import static com.android.tools.r8.naming.retraceproguard.StackTrace.isSameExceptForFileNameAndLineNumber;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.CompilationMode;
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.R8TestBuilder;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.ToolHelper.DexVm.Version;
+import com.android.tools.r8.naming.retraceproguard.StackTrace.StackTraceLine;
+import com.android.tools.r8.utils.BooleanUtils;
+import com.google.common.collect.ImmutableList;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.Set;
+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 VerticalClassMergingRetraceTest extends RetraceTestBase {
+  private Set<StackTraceLine> haveSeenLines = new HashSet<>();
+
+  @Parameters(name = "{0}, mode: {1}, compat: {2}")
+  public static Collection<Object[]> data() {
+    return buildParameters(
+        getTestParameters()
+            .withCfRuntimes()
+            // Runtimes prior to 8 will emit stack trace lines as:
+            // Exception in thread "main" java.lang.NullPointerException: throw with null exception
+            // 	at com.android.tools.r8.naming.retraceproguard.a.b(SourceFile)
+            // PG do not support retracing if no line number is specified.
+            .withDexRuntimesStartingFromIncluding(Version.V8_1_0)
+            .withAllApiLevels()
+            .build(),
+        CompilationMode.values(),
+        BooleanUtils.values());
+  }
+
+  public VerticalClassMergingRetraceTest(
+      TestParameters parameters, CompilationMode mode, boolean compat) {
+    super(parameters, mode, compat);
+  }
+
+  @Override
+  public void configure(R8TestBuilder builder) {
+    builder.enableInliningAnnotations();
+  }
+
+  @Override
+  public Collection<Class<?>> getClasses() {
+    return ImmutableList.of(getMainClass(), ResourceWrapper.class, TintResources.class);
+  }
+
+  @Override
+  public Class<?> getMainClass() {
+    return MainApp.class;
+  }
+
+  private int expectedActualStackTraceHeight() {
+    // In RELEASE mode, a synthetic bridge will be added by vertical class merger.
+    int height = mode == CompilationMode.RELEASE ? 3 : 2;
+    if (parameters.isDexRuntime() && parameters.getDexRuntimeVersion().isDalvik()) {
+      // Dalvik places a stack trace line in the bottom.
+      height += 1;
+    }
+    return height;
+  }
+
+  private boolean filterSynthesizedMethodWhenLineNumberAvailable(
+      StackTraceLine retracedStackTraceLine) {
+    return retracedStackTraceLine.lineNumber > 0;
+  }
+
+  private boolean filterSynthesizedMethod(StackTraceLine retracedStackTraceLine) {
+    return haveSeenLines.add(retracedStackTraceLine)
+        && (retracedStackTraceLine.className.contains("ResourceWrapper")
+            || retracedStackTraceLine.className.contains("MainApp"));
+  }
+
+  @Test
+  public void testSourceFileAndLineNumberTable() throws Exception {
+    runTest(
+        ImmutableList.of("-keepattributes SourceFile,LineNumberTable"),
+        (StackTrace actualStackTrace, StackTrace retracedStackTrace) -> {
+          // Even when SourceFile is present retrace replaces the file name in the stack trace.
+          StackTrace reprocessedStackTrace =
+              mode == CompilationMode.DEBUG
+                  ? retracedStackTrace
+                  : retracedStackTrace.filter(this::filterSynthesizedMethodWhenLineNumberAvailable);
+          assertThat(
+              reprocessedStackTrace.filter(this::isNotDalvikNativeStartMethod),
+              isSameExceptForFileName(
+                  expectedStackTrace.filter(this::isNotDalvikNativeStartMethod)));
+          assertEquals(expectedActualStackTraceHeight(), actualStackTrace.size());
+        });
+  }
+
+  @Test
+  public void testLineNumberTableOnly() throws Exception {
+    assumeTrue(compat);
+    assumeTrue(parameters.isDexRuntime());
+    runTest(
+        ImmutableList.of("-keepattributes LineNumberTable"),
+        (StackTrace actualStackTrace, StackTrace retracedStackTrace) -> {
+          StackTrace reprocessedStackTrace =
+              mode == CompilationMode.DEBUG
+                  ? retracedStackTrace
+                  : retracedStackTrace.filter(this::filterSynthesizedMethodWhenLineNumberAvailable);
+          assertThat(
+              reprocessedStackTrace.filter(this::isNotDalvikNativeStartMethod),
+              isSameExceptForFileName(
+                  expectedStackTrace.filter(this::isNotDalvikNativeStartMethod)));
+          assertEquals(expectedActualStackTraceHeight(), actualStackTrace.size());
+        });
+  }
+
+  @Test
+  public void testNoLineNumberTable() throws Exception {
+    assumeTrue(compat);
+    assumeTrue(parameters.isDexRuntime());
+    haveSeenLines.clear();
+    runTest(
+        ImmutableList.of(),
+        (StackTrace actualStackTrace, StackTrace retracedStackTrace) -> {
+          StackTrace reprocessedStackTrace =
+              mode == CompilationMode.DEBUG
+                  ? retracedStackTrace
+                  : retracedStackTrace.filter(this::filterSynthesizedMethod);
+          assertThat(
+              reprocessedStackTrace.filter(this::isNotDalvikNativeStartMethod),
+              isSameExceptForFileNameAndLineNumber(
+                  expectedStackTrace.filter(this::isNotDalvikNativeStartMethod)));
+          assertEquals(expectedActualStackTraceHeight(), actualStackTrace.size());
+        });
+  }
+}
+
+class ResourceWrapper {
+  // Will be merged down, and represented as:
+  //     java.lang.String ...ResourceWrapper.foo() -> a
+  @NeverInline
+  String foo() {
+    throw null;
+  }
+}
+
+class TintResources extends ResourceWrapper {}
+
+class MainApp {
+  public static void main(String[] args) {
+    TintResources t = new TintResources();
+    System.out.println(t.foo());
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/naming/sourcefile/MapIdTemplateTest.java b/src/test/java/com/android/tools/r8/naming/sourcefile/MapIdTemplateTest.java
new file mode 100644
index 0000000..7da7b54
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/naming/sourcefile/MapIdTemplateTest.java
@@ -0,0 +1,142 @@
+// Copyright (c) 2021, 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.naming.sourcefile;
+
+import static com.android.tools.r8.DiagnosticsMatcher.diagnosticMessage;
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.CoreMatchers.startsWith;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.ClassFileConsumer.ArchiveConsumer;
+import com.android.tools.r8.DexIndexedConsumer;
+import com.android.tools.r8.DiagnosticsHandler;
+import com.android.tools.r8.R8;
+import com.android.tools.r8.R8Command.Builder;
+import com.android.tools.r8.R8CommandParser;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestDiagnosticMessagesImpl;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.origin.Origin;
+import java.nio.file.Path;
+import java.util.Arrays;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class MapIdTemplateTest extends TestBase {
+
+  @Parameterized.Parameters(name = "{0}, {1}")
+  public static List<Object[]> data() {
+    return buildParameters(getTestParameters().withNoneRuntime().build(), Backend.values());
+  }
+
+  private final Backend backend;
+
+  public MapIdTemplateTest(TestParameters parameters, Backend backend) {
+    parameters.assertNoneRuntime();
+    this.backend = backend;
+  }
+
+  @Test
+  public void testNoVariables() throws Exception {
+    String template = "my-build-id";
+    assertEquals(template, compileWithMapIdTemplate(template));
+  }
+
+  @Test
+  public void testInvalidVariable() {
+    TestDiagnosticMessagesImpl messages = new TestDiagnosticMessagesImpl();
+    parseMapIdTemplate("my-%build-id", messages);
+    messages
+        .assertOnlyErrors()
+        .assertErrorsMatch(
+            diagnosticMessage(containsString("Invalid template variable starting with %bu")));
+  }
+
+  @Test
+  public void testInvalidVariablesMix() {
+    TestDiagnosticMessagesImpl messages = new TestDiagnosticMessagesImpl();
+    parseMapIdTemplate("my%%MAP_HASHJUNK", messages);
+    messages
+        .assertOnlyErrors()
+        .assertErrorsMatch(
+            diagnosticMessage(containsString("Invalid template variable starting with %%MAP_")));
+  }
+
+  @Test
+  public void testNoEscape() {
+    TestDiagnosticMessagesImpl messages = new TestDiagnosticMessagesImpl();
+    parseMapIdTemplate("my%%buildid", messages);
+    messages
+        .assertOnlyErrors()
+        .assertErrorsMatch(
+            Arrays.asList(
+                diagnosticMessage(containsString("Invalid template variable starting with %%b")),
+                diagnosticMessage(containsString("Invalid template variable starting with %b"))));
+  }
+
+  @Test
+  public void testMapHash() throws Exception {
+    String template = "mybuildid %MAP_HASH";
+    String actual = compileWithMapIdTemplate(template);
+    assertThat(actual, startsWith("mybuildid "));
+    assertThat(actual, not(containsString("%")));
+    assertEquals("mybuildid ".length() + 64, actual.length());
+  }
+
+  @Test
+  public void testMultiple() throws Exception {
+    String template = "hash %MAP_HASH hash %MAP_HASH";
+    String actual = compileWithMapIdTemplate(template);
+    assertEquals("hash  hash ".length() + 2 * 64, actual.length());
+  }
+
+  private String compileWithMapIdTemplate(String template) throws Exception {
+    Path out = temp.newFolder().toPath().resolve("out.jar");
+    TestDiagnosticMessagesImpl messages = new TestDiagnosticMessagesImpl();
+    StringBuilder mapping = new StringBuilder();
+    R8.run(
+        parseMapIdTemplate(template, messages)
+            .addProguardConfiguration(
+                Arrays.asList("-keep class * { *; }", "-dontwarn " + typeName(TestClass.class)),
+                Origin.unknown())
+            .setProgramConsumer(
+                backend.isCf()
+                    ? new ArchiveConsumer(out)
+                    : new DexIndexedConsumer.ArchiveConsumer(out))
+            .addProgramFiles(ToolHelper.getClassFileForTestClass(TestClass.class))
+            // TODO(b/201269335): What should be the expected result when no map is created?
+            .setProguardMapConsumer((content, handler) -> mapping.append(content))
+            .build());
+    messages.assertNoMessages();
+    return getMapId(mapping.toString());
+  }
+
+  private Builder parseMapIdTemplate(String template, DiagnosticsHandler handler) {
+    return R8CommandParser.parse(
+        new String[] {"--map-id-template", template}, Origin.unknown(), handler);
+  }
+
+  private String getMapId(String mapping) {
+    String lineHeader = "# pg_map_id: ";
+    int i = mapping.indexOf(lineHeader);
+    assertTrue(i >= 0);
+    int start = i + lineHeader.length();
+    int end = mapping.indexOf('\n', start);
+    return mapping.substring(start, end);
+  }
+
+  static class TestClass {
+
+    public static void main(String[] args) {
+      System.out.println("Hello world");
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/naming/sourcefile/SourceFileTemplateTest.java b/src/test/java/com/android/tools/r8/naming/sourcefile/SourceFileTemplateTest.java
new file mode 100644
index 0000000..cdff91c
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/naming/sourcefile/SourceFileTemplateTest.java
@@ -0,0 +1,168 @@
+// Copyright (c) 2021, 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.naming.sourcefile;
+
+import static com.android.tools.r8.DiagnosticsMatcher.diagnosticMessage;
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.CoreMatchers.startsWith;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.ClassFileConsumer.ArchiveConsumer;
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.DexIndexedConsumer;
+import com.android.tools.r8.DiagnosticsHandler;
+import com.android.tools.r8.R8;
+import com.android.tools.r8.R8Command.Builder;
+import com.android.tools.r8.R8CommandParser;
+import com.android.tools.r8.StringConsumer;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestDiagnosticMessagesImpl;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import java.io.IOException;
+import java.nio.file.Path;
+import java.util.Arrays;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class SourceFileTemplateTest extends TestBase {
+
+  @Parameterized.Parameters(name = "{0}, {1}")
+  public static List<Object[]> data() {
+    return buildParameters(getTestParameters().withNoneRuntime().build(), Backend.values());
+  }
+
+  private final Backend backend;
+
+  public SourceFileTemplateTest(TestParameters parameters, Backend backend) {
+    parameters.assertNoneRuntime();
+    this.backend = backend;
+  }
+
+  @Test
+  public void testNoVariables() throws Exception {
+    String template = "MySourceFile";
+    assertEquals(
+        template,
+        new CodeInspector(compileWithSourceFileTemplate(template))
+            .clazz(TestClass.class)
+            .getDexProgramClass()
+            .getSourceFile()
+            .toString());
+  }
+
+  @Test
+  public void testInvalidVariables() {
+    TestDiagnosticMessagesImpl messages = new TestDiagnosticMessagesImpl();
+    parseSourceFileTemplate("My%Source%File", messages);
+    messages
+        .assertOnlyErrors()
+        .assertErrorsMatch(
+            Arrays.asList(
+                diagnosticMessage(containsString("Invalid template variable starting with %So")),
+                diagnosticMessage(containsString("Invalid template variable starting with %Fi"))));
+  }
+
+  @Test
+  public void testInvalidVariablesMix() {
+    TestDiagnosticMessagesImpl messages = new TestDiagnosticMessagesImpl();
+    parseSourceFileTemplate("My%%MAP_IDJUNK", messages);
+    messages
+        .assertOnlyErrors()
+        .assertErrorsMatch(
+            diagnosticMessage(containsString("Invalid template variable starting with %%MAP_")));
+  }
+
+  @Test
+  public void testNoEscape() {
+    TestDiagnosticMessagesImpl messages = new TestDiagnosticMessagesImpl();
+    parseSourceFileTemplate("My%%SourceFile", messages);
+    messages
+        .assertOnlyErrors()
+        .assertErrorsMatch(
+            Arrays.asList(
+                diagnosticMessage(containsString("Invalid template variable starting with %%S")),
+                diagnosticMessage(containsString("Invalid template variable starting with %So"))));
+  }
+
+  @Test
+  public void testMapId() throws Exception {
+    String template = "MySourceFile %MAP_ID";
+    String actual =
+        new CodeInspector(compileWithSourceFileTemplate(template))
+            .clazz(TestClass.class)
+            .getDexProgramClass()
+            .getSourceFile()
+            .toString();
+    assertThat(actual, startsWith("MySourceFile "));
+    assertThat(actual, not(containsString("%")));
+    assertEquals("MySourceFile ".length() + 7, actual.length());
+  }
+
+  @Test
+  public void testMapHash() throws Exception {
+    String template = "MySourceFile %MAP_HASH";
+    String actual =
+        new CodeInspector(compileWithSourceFileTemplate(template))
+            .clazz(TestClass.class)
+            .getDexProgramClass()
+            .getSourceFile()
+            .toString();
+    assertThat(actual, startsWith("MySourceFile "));
+    assertThat(actual, not(containsString("%")));
+    assertEquals("MySourceFile ".length() + 64, actual.length());
+  }
+
+  @Test
+  public void testMultiple() throws Exception {
+    String template = "id %MAP_ID hash %MAP_HASH id %MAP_ID hash %MAP_HASH";
+    String actual =
+        new CodeInspector(compileWithSourceFileTemplate(template))
+            .clazz(TestClass.class)
+            .getDexProgramClass()
+            .getSourceFile()
+            .toString();
+    assertEquals("id  hash  id  hash ".length() + 2 * 7 + 2 * 64, actual.length());
+  }
+
+  private Path compileWithSourceFileTemplate(String template)
+      throws IOException, CompilationFailedException {
+    Path out = temp.newFolder().toPath().resolve("out.jar");
+    TestDiagnosticMessagesImpl messages = new TestDiagnosticMessagesImpl();
+    R8.run(
+        parseSourceFileTemplate(template, messages)
+            .addProguardConfiguration(
+                Arrays.asList("-keep class * { *; }", "-dontwarn " + typeName(TestClass.class)),
+                Origin.unknown())
+            .setProgramConsumer(
+                backend.isCf()
+                    ? new ArchiveConsumer(out)
+                    : new DexIndexedConsumer.ArchiveConsumer(out))
+            .addProgramFiles(ToolHelper.getClassFileForTestClass(TestClass.class))
+            // TODO(b/201269335): What should be the expected result when no map is created?
+            .setProguardMapConsumer(StringConsumer.emptyConsumer())
+            .build());
+    messages.assertNoMessages();
+    return out;
+  }
+
+  private Builder parseSourceFileTemplate(String template, DiagnosticsHandler handler) {
+    return R8CommandParser.parse(
+        new String[] {"--source-file-template", template}, Origin.unknown(), handler);
+  }
+
+  static class TestClass {
+
+    public static void main(String[] args) {
+      System.out.println("Hello world");
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/regress/Regress160394262Test.java b/src/test/java/com/android/tools/r8/regress/Regress160394262Test.java
index 383a7ef..3e295a8 100644
--- a/src/test/java/com/android/tools/r8/regress/Regress160394262Test.java
+++ b/src/test/java/com/android/tools/r8/regress/Regress160394262Test.java
@@ -3,8 +3,8 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.regress;
 
+import static com.android.tools.r8.utils.codeinspector.Matchers.isAbsent;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
-import static org.hamcrest.CoreMatchers.not;
 import static org.hamcrest.MatcherAssert.assertThat;
 
 import com.android.tools.r8.TestBase;
@@ -57,13 +57,9 @@
   }
 
   private void checkJoinerIsClassInlined(CodeInspector inspector) {
-    assertThat(inspector.clazz(Joiner.class.getTypeName() + "$1"), not(isPresent()));
-    // TODO(b/160640028): When compiling to DEX the outer Joiner class is not inlined.
-    if (parameters.isDexRuntime()) {
-      assertThat(inspector.clazz(Joiner.class), isPresent());
-    } else {
-      assertThat(inspector.clazz(Joiner.class), not(isPresent()));
-    }
+    assertThat(inspector.clazz(Joiner.class.getTypeName() + "$1"), isAbsent());
+    // TODO(b/160640028): Joiner should be class inlined.
+    assertThat(inspector.clazz(Joiner.class), isPresent());
   }
 
   static class TestClass {
diff --git a/src/test/java/com/android/tools/r8/regress/b69825683/Regress69825683Test.java b/src/test/java/com/android/tools/r8/regress/b69825683/Regress69825683Test.java
index d251e28..7138094 100644
--- a/src/test/java/com/android/tools/r8/regress/b69825683/Regress69825683Test.java
+++ b/src/test/java/com/android/tools/r8/regress/b69825683/Regress69825683Test.java
@@ -64,10 +64,11 @@
 
     // Check that the synthetic class is still present.
     assertEquals(3, classes.size());
-    assertEquals(1,
+    assertEquals(
+        1,
         classes.stream()
             .map(FoundClassSubject::getOriginalName)
-            .filter(name  -> name.endsWith("$1"))
+            .filter(name -> name.endsWith("$1"))
             .count());
   }
 
@@ -95,10 +96,11 @@
 
     // Check that the synthetic class is still present.
     assertEquals(3, classes.size());
-    assertEquals(1,
+    assertEquals(
+        1,
         classes.stream()
             .map(FoundClassSubject::getOriginalName)
-            .filter(name  -> name.endsWith("$1"))
+            .filter(name -> name.endsWith("$1"))
             .count());
   }
 }
diff --git a/src/test/java/com/android/tools/r8/resolution/B77944861.java b/src/test/java/com/android/tools/r8/resolution/B77944861.java
index 3572263..09a3054 100644
--- a/src/test/java/com/android/tools/r8/resolution/B77944861.java
+++ b/src/test/java/com/android/tools/r8/resolution/B77944861.java
@@ -61,9 +61,11 @@
             .setDisableTreeShaking(true)
             .setDisableMinification(true)
             .build();
-    return ToolHelper.runR8(command, o -> {
-      o.enableInlining = false;
-    });
+    return ToolHelper.runR8(
+        command,
+        o -> {
+          o.inlinerOptions().enableInlining = false;
+        });
   }
 
   @Test
diff --git a/src/test/java/com/android/tools/r8/retrace/StackTraceWithPcAndNoLineTableTest.java b/src/test/java/com/android/tools/r8/retrace/StackTraceWithPcAndNoLineTableTest.java
new file mode 100644
index 0000000..b8c86fa
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/retrace/StackTraceWithPcAndNoLineTableTest.java
@@ -0,0 +1,21 @@
+// Copyright (c) 2021, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.retrace;
+
+public class StackTraceWithPcAndNoLineTableTest {
+
+  public static void foo() {
+    if (System.nanoTime() > 0) {
+      throw new RuntimeException();
+    }
+  }
+
+  public static void bar() {
+    foo();
+  }
+
+  public static void main(String[] args) {
+    bar();
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/retrace/StackTraceWithPcAndNoLineTableTestRunner.java b/src/test/java/com/android/tools/r8/retrace/StackTraceWithPcAndNoLineTableTestRunner.java
new file mode 100644
index 0000000..6783fa7
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/retrace/StackTraceWithPcAndNoLineTableTestRunner.java
@@ -0,0 +1,116 @@
+// Copyright (c) 2021, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.retrace;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.ToolHelper.DexVm.Version;
+import com.android.tools.r8.naming.retrace.StackTrace;
+import com.android.tools.r8.naming.retrace.StackTrace.StackTraceLine;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class StackTraceWithPcAndNoLineTableTestRunner extends TestBase {
+
+  private final TestParameters parameters;
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public StackTraceWithPcAndNoLineTableTestRunner(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  private Class<?> getTestClass() {
+    return StackTraceWithPcAndNoLineTableTest.class;
+  }
+
+  @Test
+  public void testReference() throws Exception {
+    testForRuntime(parameters)
+        .addProgramClasses(getTestClass())
+        .run(parameters.getRuntime(), getTestClass())
+        .assertFailureWithErrorThatThrows(RuntimeException.class)
+        .inspectStackTrace(
+            stacktrace -> {
+              assertThat(stacktrace, StackTrace.isSame(getExpectedStackTrace(true)));
+            });
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    testForR8(parameters.getBackend())
+        .addProgramClasses(getTestClass())
+        .addKeepMainRule(getTestClass())
+        .addKeepRules("-keep,allowshrinking,allowobfuscation class * { *; }")
+        .setMinApi(parameters.getApiLevel())
+        .run(parameters.getRuntime(), getTestClass())
+        .assertFailureWithErrorThatThrows(RuntimeException.class)
+        .inspectStackTrace(
+            (stacktrace, inspector) -> {
+              // The source file should be set to the default SourceFile when not kept.
+              assertEquals(
+                  "SourceFile",
+                  inspector.clazz(getTestClass()).getDexProgramClass().sourceFile.toString());
+              // The stack-trace should not have line numbers unless on a VM with DEX PC support.
+              if (parameters.isDexRuntime()
+                  && parameters.getDexRuntimeVersion().isNewerThanOrEqual(Version.V8_1_0)) {
+                if (parameters
+                    .getApiLevel()
+                    .isGreaterThanOrEqualTo(apiLevelWithPcAsLineNumberSupport())) {
+                  // TODO(b/202919530): With PC support it should retrace.
+                  assertThat(
+                      stacktrace,
+                      StackTrace.isSameExceptForLineNumbers(getExpectedStackTrace(true)));
+                } else {
+                  // Compiling for an API level before PC support it may be unfeasible to include
+                  // the mapping information due to mapping size increase. That is up for
+                  // discussion.
+                  assertThat(
+                      stacktrace,
+                      StackTrace.isSameExceptForLineNumbers(getExpectedStackTrace(true)));
+                }
+              } else {
+                // Having stripped the line-number table, the raw stack will not have line info.
+                assertThat(stacktrace, StackTrace.isSame(getExpectedStackTrace(false)));
+              }
+            });
+  }
+
+  private StackTrace getExpectedStackTrace(boolean withLines) {
+    String className = getTestClass().getName();
+    String sourceFile = getTestClass().getSimpleName() + ".java";
+    return StackTrace.builder()
+        .add(
+            StackTraceLine.builder()
+                .setClassName(className)
+                .setFileName(sourceFile)
+                .setMethodName("foo")
+                .applyIf(withLines, b -> b.setLineNumber(10))
+                .build())
+        .add(
+            StackTraceLine.builder()
+                .setClassName(className)
+                .setFileName(sourceFile)
+                .setMethodName("bar")
+                .applyIf(withLines, b -> b.setLineNumber(15))
+                .build())
+        .add(
+            StackTraceLine.builder()
+                .setClassName(className)
+                .setFileName(sourceFile)
+                .setMethodName("main")
+                .applyIf(withLines, b -> b.setLineNumber(19))
+                .build())
+        .build();
+  }
+}
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 239697e..7452a5a 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
@@ -187,7 +187,7 @@
     return testForR8(staticTemp, backend)
         .addProgramClasses(ClassWithAssertions.class)
         .addKeepMainRule(ClassWithAssertions.class)
-        .addOptionsModification(o -> o.enableInlining = false)
+        .addOptionsModification(o -> o.inlinerOptions().enableInlining = false)
         .allowAccessModification()
         .noMinification()
         .compile();
diff --git a/src/test/java/com/android/tools/r8/shaking/FieldTypeTest.java b/src/test/java/com/android/tools/r8/shaking/FieldTypeTest.java
index df19a94..ce08a6a 100644
--- a/src/test/java/com/android/tools/r8/shaking/FieldTypeTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/FieldTypeTest.java
@@ -13,7 +13,6 @@
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.ToolHelper.ProcessResult;
-import com.android.tools.r8.VmTestRunner;
 import com.android.tools.r8.jasmin.JasminBuilder;
 import com.android.tools.r8.jasmin.JasminBuilder.ClassBuilder;
 import com.android.tools.r8.naming.MemberNaming.FieldSignature;
@@ -25,7 +24,6 @@
 import com.google.common.collect.ImmutableList;
 import java.nio.file.Path;
 import org.junit.Test;
-import org.junit.runner.RunWith;
 
 public class FieldTypeTest extends TestBase {
 
@@ -126,7 +124,7 @@
             // Disable inlining to avoid the (short) tested method from being inlined and then
             // removed.
             options -> {
-              options.enableInlining = false;
+              options.inlinerOptions().enableInlining = false;
               options.testing.allowTypeErrors = true;
             });
 
@@ -201,15 +199,16 @@
     assertEquals(0, javaResult.exitCode);
     assertThat(javaResult.stdout, containsString(impl2.name));
 
-    AndroidApp processedApp = compileWithR8(
-        jasminBuilder.build(),
-        proguardConfig,
-        internalOptions -> {
-          // Disable inlining to avoid the (short) tested method from being inlined and then
-          // removed.
-          internalOptions.enableInlining = false;
-          internalOptions.testing.allowTypeErrors = true;
-        });
+    AndroidApp processedApp =
+        compileWithR8(
+            jasminBuilder.build(),
+            proguardConfig,
+            internalOptions -> {
+              // Disable inlining to avoid the (short) tested method from being inlined and then
+              // removed.
+              internalOptions.inlinerOptions().enableInlining = false;
+              internalOptions.testing.allowTypeErrors = true;
+            });
 
     // Run processed (output) program on ART
     ProcessResult artResult = runOnArtRaw(processedApp, mainClassName);
diff --git a/src/test/java/com/android/tools/r8/shaking/MissingInterfaceTest.java b/src/test/java/com/android/tools/r8/shaking/MissingInterfaceTest.java
index c5c0859..0222e3d 100644
--- a/src/test/java/com/android/tools/r8/shaking/MissingInterfaceTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/MissingInterfaceTest.java
@@ -38,7 +38,7 @@
         .addDontWarn(GoingToBeMissed.class)
         .setMinApi(parameters.getApiLevel())
         .addKeepMainRule(TestClassForB112849320.class)
-        .addOptionsModification(options -> options.enableInlining = false)
+        .addOptionsModification(options -> options.inlinerOptions().enableInlining = false)
         .addKeepPackageNamesRule(GoingToBeMissed.class.getPackage())
         .compile()
         .addRunClasspathFiles(
@@ -64,7 +64,7 @@
         .addDefaultRuntimeLibrary(parameters)
         .setMinApi(parameters.getApiLevel())
         .addKeepMainRule(TestClassForB112849320.class)
-        .addOptionsModification(options -> options.enableInlining = false)
+        .addOptionsModification(options -> options.inlinerOptions().enableInlining = false)
         .addRunClasspathFiles(buildOnDexRuntime(parameters, lib))
         .run(parameters.getRuntime(), TestClassForB112849320.class)
         .assertSuccessWithOutputLines(EXPECTED);
diff --git a/src/test/java/com/android/tools/r8/shaking/ParameterTypeTest.java b/src/test/java/com/android/tools/r8/shaking/ParameterTypeTest.java
index b99494e..873b1e4 100644
--- a/src/test/java/com/android/tools/r8/shaking/ParameterTypeTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/ParameterTypeTest.java
@@ -128,7 +128,7 @@
             .addKeepMainRule(B112452064TestMain.class)
             .addOptionsModification(
                 options -> {
-                  options.enableInlining = false;
+                  options.inlinerOptions().enableInlining = false;
                   options.enableUnusedInterfaceRemoval = enableUnusedInterfaceRemoval;
                   options.enableVerticalClassMerging = enableVerticalClassMerging;
                 })
@@ -227,7 +227,7 @@
             proguardConfig,
             options -> {
               // Disable inlining to avoid the (short) tested method from being inlined and removed.
-              options.enableInlining = false;
+              options.inlinerOptions().enableInlining = false;
               options.callSiteOptimizationOptions().setEnabled(enableArgumentPropagation);
             });
 
@@ -306,7 +306,7 @@
         .addOptionsModification(
             options -> {
               // Disable inlining to avoid the (short) tested method from being inlined and removed.
-              options.enableInlining = false;
+              options.inlinerOptions().enableInlining = false;
               options.callSiteOptimizationOptions().setEnabled(enableArgumentPropagation);
             })
         .noMinification()
@@ -409,7 +409,7 @@
             proguardConfig,
             options -> {
               // Disable inlining to avoid the (short) tested method from being inlined and removed.
-              options.enableInlining = false;
+              options.inlinerOptions().enableInlining = false;
               options.callSiteOptimizationOptions().setEnabled(enableArgumentPropagation);
             });
 
diff --git a/src/test/java/com/android/tools/r8/shaking/PrintUsageTest.java b/src/test/java/com/android/tools/r8/shaking/PrintUsageTest.java
index 3d2e137..d241adb 100644
--- a/src/test/java/com/android/tools/r8/shaking/PrintUsageTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/PrintUsageTest.java
@@ -71,7 +71,7 @@
                 && parameters.getApiLevel().isLessThan(AndroidApiLevel.K),
             builder -> builder.addDontWarn(ReflectiveOperationException.class))
         // Disable inlining to make this test not depend on inlining decisions.
-        .addOptionsModification(o -> o.enableInlining = false)
+        .addOptionsModification(o -> o.inlinerOptions().enableInlining = false)
         .enableProguardTestOptions()
         .setMinApi(parameters.getApiLevel())
         .compile();
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 82e9d69..354c5ae 100644
--- a/src/test/java/com/android/tools/r8/shaking/ReturnTypeTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/ReturnTypeTest.java
@@ -86,7 +86,7 @@
               // 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().disableDynamicTypePropagationForTesting();
-              o.enableInlining = false;
+              o.inlinerOptions().enableInlining = false;
             })
         .run(parameters.getRuntime(), MAIN)
         .assertSuccessWithOutput(JAVA_OUTPUT)
diff --git a/src/test/java/com/android/tools/r8/shaking/ServiceLoaderTest.java b/src/test/java/com/android/tools/r8/shaking/ServiceLoaderTest.java
index b21fc5f..86ccd24 100644
--- a/src/test/java/com/android/tools/r8/shaking/ServiceLoaderTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/ServiceLoaderTest.java
@@ -88,7 +88,7 @@
                   dataResourceConsumer =
                       new DataResourceConsumerForTesting(options.dataResourceConsumer);
                   options.dataResourceConsumer = dataResourceConsumer;
-                  options.enableInliningOfInvokesWithNullableReceivers = false;
+                  options.inlinerOptions().enableInliningOfInvokesWithNullableReceivers = false;
                 })
             .enableGraphInspector()
             .enableMemberValuePropagationAnnotations()
diff --git a/src/test/java/com/android/tools/r8/shaking/TreeShakingSpecificTest.java b/src/test/java/com/android/tools/r8/shaking/TreeShakingSpecificTest.java
index 2fac539..5e17250 100644
--- a/src/test/java/com/android/tools/r8/shaking/TreeShakingSpecificTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/TreeShakingSpecificTest.java
@@ -130,7 +130,7 @@
             builder -> builder.addProgramFiles(getProgramFiles(test)),
             builder -> builder.addProgramDexFileData(getProgramDexFileData(test)))
         .addKeepRuleFiles(Paths.get(EXAMPLES_DIR, test, "keep-rules.txt"))
-        .addOptionsModification(options -> options.enableInlining = false)
+        .addOptionsModification(options -> options.inlinerOptions().enableInlining = false)
         .compile()
         .inspectProguardMap(
             proguardMap -> {
diff --git a/src/test/java/com/android/tools/r8/shaking/TreeShakingTest.java b/src/test/java/com/android/tools/r8/shaking/TreeShakingTest.java
index 869deb0..e480696 100644
--- a/src/test/java/com/android/tools/r8/shaking/TreeShakingTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/TreeShakingTest.java
@@ -193,7 +193,7 @@
             .addDefaultRuntimeLibrary(parameters)
             .addOptionsModification(
                 options -> {
-                  options.enableInlining = programFile.contains("inlining");
+                  options.inlinerOptions().enableInlining = programFile.contains("inlining");
                   if (optionsConsumer != null) {
                     optionsConsumer.accept(options);
                   }
diff --git a/src/test/java/com/android/tools/r8/shaking/b113138046/NativeMethodTest.java b/src/test/java/com/android/tools/r8/shaking/b113138046/NativeMethodTest.java
index 686c35b..9d35176 100644
--- a/src/test/java/com/android/tools/r8/shaking/b113138046/NativeMethodTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/b113138046/NativeMethodTest.java
@@ -48,7 +48,7 @@
             .setMinApi(parameters.getApiLevel())
             .addProgramClassesAndInnerClasses(Keep.class, Data.class, Handler.class, Outer.class)
             .addKeepRules(config)
-            .addOptionsModification(options -> options.enableInlining = false)
+            .addOptionsModification(options -> options.inlinerOptions().enableInlining = false)
             .compile();
     compileResult.runDex2Oat(parameters.getRuntime()).assertNoVerificationErrors();
     CodeInspector inspector = compileResult.inspector();
diff --git a/src/test/java/com/android/tools/r8/shaking/interfacebridge/LambdaAbstractMethodErrorTest.java b/src/test/java/com/android/tools/r8/shaking/interfacebridge/LambdaAbstractMethodErrorTest.java
index 48576f5..d388cec 100644
--- a/src/test/java/com/android/tools/r8/shaking/interfacebridge/LambdaAbstractMethodErrorTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/interfacebridge/LambdaAbstractMethodErrorTest.java
@@ -37,7 +37,7 @@
         .addKeepMainRule(Main.class)
         .addOptionsModification(
             internalOptions -> {
-              internalOptions.enableInlining = false;
+              internalOptions.inlinerOptions().enableInlining = false;
               internalOptions.enableClassInlining = false;
               internalOptions.enableVerticalClassMerging = false;
             })
diff --git a/src/test/java/com/android/tools/r8/shaking/proxy/ProxiesTest.java b/src/test/java/com/android/tools/r8/shaking/proxy/ProxiesTest.java
index b2313a7..2851643 100644
--- a/src/test/java/com/android/tools/r8/shaking/proxy/ProxiesTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/proxy/ProxiesTest.java
@@ -65,11 +65,9 @@
         .addOptionsModification(
             o -> {
               o.enableDevirtualization = false;
-              o.enableInliningOfInvokesWithNullableReceivers = false;
-              // Tests indirectly check if a certain method is inlined or not, where the target
-              // method has at least 4 instructions.
-              o.inliningInstructionLimit = 4;
+              o.inlinerOptions().enableInliningOfInvokesWithNullableReceivers = false;
             })
+        .enableAlwaysInliningAnnotations()
         .noMinification()
         .setMinApi(parameters.getApiLevel())
         .compile()
diff --git a/src/test/java/com/android/tools/r8/shaking/proxy/testclasses/TestClass.java b/src/test/java/com/android/tools/r8/shaking/proxy/testclasses/TestClass.java
index 97fdb2b..8a5d3fd 100644
--- a/src/test/java/com/android/tools/r8/shaking/proxy/testclasses/TestClass.java
+++ b/src/test/java/com/android/tools/r8/shaking/proxy/testclasses/TestClass.java
@@ -4,6 +4,8 @@
 
 package com.android.tools.r8.shaking.proxy.testclasses;
 
+import com.android.tools.r8.AlwaysInline;
+
 public class TestClass extends BaseClass implements SubInterface, Interface2 {
   public final String name;  // Must be public to allow inlining.
 
@@ -11,6 +13,7 @@
     this.name = name;
   }
 
+  @AlwaysInline
   @Override
   public void method() {
     System.out.println(name);
diff --git a/src/test/java/com/android/tools/r8/smali/OutlineTest.java b/src/test/java/com/android/tools/r8/smali/OutlineTest.java
index c9d30fd..242d5d2 100644
--- a/src/test/java/com/android/tools/r8/smali/OutlineTest.java
+++ b/src/test/java/com/android/tools/r8/smali/OutlineTest.java
@@ -72,7 +72,7 @@
   private Consumer<InternalOptions> configureOptions(Consumer<InternalOptions> optionsConsumer) {
     return options -> {
       // Disable inlining to make sure that code looks as expected.
-      options.enableInlining = false;
+      options.inlinerOptions().enableInlining = false;
       // Disable string concatenation optimization to not bother outlining of StringBuilder usage.
       options.enableStringConcatenationOptimization = false;
       // Also apply outline options.
@@ -84,7 +84,7 @@
       Consumer<OutlineOptions> optionsConsumer) {
     return options -> {
       // Disable inlining to make sure that code looks as expected.
-      options.enableInlining = false;
+      options.inlinerOptions().enableInlining = false;
       // Disable the devirtulizer to not remove the super calls
       options.enableDevirtualization = false;
       // Disable string concatenation optimization to not bother outlining of StringBuilder usage.
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/CfInstructionSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/CfInstructionSubject.java
index 64b3914..4465113 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/CfInstructionSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/CfInstructionSubject.java
@@ -312,10 +312,16 @@
         && ((CfStackInstruction) instruction).getOpcode() == opcode;
   }
 
+  @Override
   public boolean isIfNull() {
     return instruction instanceof CfIf && ((CfIf) instruction).getOpcode() == Opcodes.IFNULL;
   }
 
+  @Override
+  public boolean isIfNonNull() {
+    return instruction instanceof CfIf && ((CfIf) instruction).getOpcode() == Opcodes.IFNONNULL;
+  }
+
   public boolean isLoad() {
     return instruction instanceof CfLoad;
   }
@@ -407,4 +413,8 @@
   public String toString() {
     return instruction.toString();
   }
+
+  public CfInstruction getInstruction() {
+    return instruction;
+  }
 }
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/DexInstructionSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/DexInstructionSubject.java
index 2bfadc8..91f0ea9 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/DexInstructionSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/DexInstructionSubject.java
@@ -336,6 +336,18 @@
   }
 
   @Override
+  public boolean isIfNull() {
+    // Not in DEX.
+    return false;
+  }
+
+  @Override
+  public boolean isIfNonNull() {
+    // Not in DEX.
+    return false;
+  }
+
+  @Override
   public boolean isReturn() {
     return instruction instanceof Return;
   }
@@ -496,4 +508,8 @@
   public String toString() {
     return instruction.toString();
   }
+
+  public Instruction getInstruction() {
+    return instruction;
+  }
 }
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/InstructionSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/InstructionSubject.java
index cf9ce0a..9f55b56 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/InstructionSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/InstructionSubject.java
@@ -98,6 +98,10 @@
 
   boolean isIfEqz();
 
+  boolean isIfNull();
+
+  boolean isIfNonNull();
+
   boolean isReturn();
 
   boolean isReturnVoid();
diff --git a/src/test/kotlinR8TestResources/class_inliner_data_class/main.kt b/src/test/kotlinR8TestResources/class_inliner_data_class/main.kt
index c8ef79a..f4e2c7a 100644
--- a/src/test/kotlinR8TestResources/class_inliner_data_class/main.kt
+++ b/src/test/kotlinR8TestResources/class_inliner_data_class/main.kt
@@ -9,7 +9,9 @@
     alpha.right = "l"
     alpha.left = "r"
     alpha.rotate()
-    println("result: ${alpha.toString()}")
+    // For Kotlin 1.5 we need to have the toString call outside the concat.
+    val alphaString = alpha.toString()
+    println("result: ${alphaString} is good")
 }
 
 data class Alpha(var left: String, val middle: String, var right: String) {
diff --git a/tools/compiledump.py b/tools/compiledump.py
index fb3b901..dbb4a18 100755
--- a/tools/compiledump.py
+++ b/tools/compiledump.py
@@ -52,6 +52,9 @@
     help='Path to an R8 jar.',
     default=None)
   parser.add_argument(
+    '--r8-flags', '--r8_flags',
+    help='Additional option(s) for the compiler.')
+  parser.add_argument(
     '-override',
     help='Do not override any extracted dump in temp-dir',
     default=False,
@@ -341,6 +344,8 @@
       cmd.append('-Dcom.android.tools.r8.enableTestAssertions=1')
     if args.print_times:
       cmd.append('-Dcom.android.tools.r8.printtimes=1')
+    if args.r8_flags:
+      cmd.extend(args.r8_flags.split(' '))
     if hasattr(args, 'properties'):
       cmd.extend(args.properties);
     cmd.extend(determine_properties(build_properties))
diff --git a/tools/create_r8lib.py b/tools/create_r8lib.py
new file mode 100755
index 0000000..329ae8e
--- /dev/null
+++ b/tools/create_r8lib.py
@@ -0,0 +1,93 @@
+#!/usr/bin/env python3
+# Copyright (c) 2021, the R8 project authors. Please see the AUTHORS file
+# for details. All rights reserved. Use of this source code is governed by a
+# BSD-style license that can be found in the LICENSE file.
+
+import argparse
+import os
+import subprocess
+import sys
+
+import jdk
+import utils
+
+VERSION_EXTRACTOR = """
+import com.android.tools.r8.Version;
+public class VersionExtractor {
+  public static void main(String[] args) {
+    System.out.println(Version.LABEL);
+  }
+}
+"""
+
+def parse_options():
+  parser = argparse.ArgumentParser(description='Tag R8 Versions')
+  parser.add_argument(
+      '--r8jar',
+      required=True,
+      help='The R8 jar to compile')
+  parser.add_argument(
+      '--output',
+      required=True,
+      help='The output path for the r8lib')
+  parser.add_argument(
+      '--pg-conf',
+      action='append',
+      help='Keep configuration')
+  parser.add_argument(
+      '--lib',
+      action='append',
+      help='Additional libraries (JDK 1.8 rt.jar already included)')
+  return parser.parse_args()
+
+def get_r8_version(r8jar):
+  with utils.TempDir() as temp:
+    name = os.path.join(temp, "VersionExtractor.java")
+    fd = open(name, 'w')
+    fd.write(VERSION_EXTRACTOR)
+    fd.close()
+    cmd = [jdk.GetJavacExecutable(), '-cp', r8jar, name]
+    print(' '.join(cmd))
+    cp_separator = ';' if utils.IsWindows() else ':'
+    subprocess.check_call(cmd)
+    output = subprocess.check_output([
+      jdk.GetJavaExecutable(),
+      '-cp',
+      cp_separator.join([r8jar, os.path.dirname(name)]),
+      'VersionExtractor'
+    ]).decode('UTF-8').strip()
+    if output == 'main':
+      return subprocess.check_output(
+        ['git', 'rev-parse', 'HEAD']).decode('UTF-8').strip()
+    else:
+      return output
+
+def main():
+  args = parse_options()
+  if not os.path.exists(args.r8jar):
+    print("Could not find jar: " + args.r8jar)
+    return 1
+  version = get_r8_version(args.r8jar)
+  map_id_template = version
+  source_file_template = 'R8_%MAP_ID_%MAP_HASH'
+  # TODO(b/139725780): See if we can remove or lower the heap size (-Xmx8g).
+  cmd = [jdk.GetJavaExecutable(), '-Xmx8g', '-ea']
+  cmd.extend(['-cp', 'build/libs/r8_with_deps.jar', 'com.android.tools.r8.R8'])
+  cmd.append(args.r8jar)
+  cmd.append('--classfile')
+  cmd.extend(['--map-id-template', map_id_template])
+  cmd.extend(['--source-file-template', source_file_template])
+  cmd.extend(['--output', args.output])
+  cmd.extend(['--pg-map-output', args.output + '.map'])
+  cmd.extend(['--lib', 'third_party/openjdk/openjdk-rt-1.8/rt.jar'])
+  if args.pg_conf:
+    for pgconf in args.pg_conf:
+      cmd.extend(['--pg-conf', pgconf])
+  if args.lib:
+    for lib in args.lib:
+      cmd.extend(['--lib', lib])
+  print(' '.join(cmd))
+  subprocess.check_call(cmd)
+
+if __name__ == '__main__':
+  sys.exit(main())
diff --git a/tools/golem_build.py b/tools/golem_build.py
index cbebc4c..24a1906 100755
--- a/tools/golem_build.py
+++ b/tools/golem_build.py
@@ -8,8 +8,8 @@
 import gradle
 import sys
 
-GRADLE_ARGS = ['--no-daemon']
-BUILD_TARGETS = ['R8', 'D8', 'R8LibApiOnly', 'buildExampleJars',
+GRADLE_ARGS = ['--no-daemon', '-Pno_internal']
+BUILD_TARGETS = ['R8', 'D8', 'R8Lib', 'buildExampleJars',
                  'downloadAndroidCts', 'downloadDx']
 
 def Main():
diff --git a/tools/retrace.py b/tools/retrace.py
index 94ec1b3..0aa5af2 100755
--- a/tools/retrace.py
+++ b/tools/retrace.py
@@ -1,13 +1,14 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
 # 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.
 
 import argparse
-import jdk
+import os
 import subprocess
 import sys
 
+import jdk
 import utils
 
 
@@ -30,7 +31,7 @@
   parser.add_argument(
       '--map',
       help='Path to r8lib map.',
-      default=utils.R8LIB_JAR + '.map')
+      default=None)
   parser.add_argument(
       '--no-r8lib',
       default=False,
@@ -38,7 +39,7 @@
       help='Use r8.jar and not r8lib.jar.')
   parser.add_argument(
       '--stacktrace',
-      help='Path to stacktrace file.',
+      help='Path to stacktrace file (read from stdin if not passed).',
       default=None)
   parser.add_argument(
       '--quiet',
@@ -63,11 +64,75 @@
   return parser.parse_args()
 
 
+def get_map_file(args, temp):
+  # default to using the specified map file.
+  if args.map:
+    return args.map
+
+  # next try to extract it from the tag/version options.
+  map_path = utils.find_cloud_storage_file_from_options('r8lib.jar.map', args)
+  if map_path:
+    return map_path
+
+  # next try to extract it from the stack-trace source-file content.
+  if not args.stacktrace:
+    if not args.quiet:
+      print('Waiting for stack-trace input...')
+    args.stacktrace = os.path.join(temp, 'stacktrace.txt')
+    open(args.stacktrace, 'w').writelines(sys.stdin.readlines())
+
+  r8_source_file = None
+  for line in open(args.stacktrace, 'r'):
+    start = line.rfind("(R8_")
+    if start > 0:
+      end = line.find(":", start)
+      content = line[start + 1: end]
+      if r8_source_file:
+        if content != r8_source_file:
+          print('WARNING: there are multiple distinct R8 source files:')
+          print(' ' + r8_source_file)
+          print(' ' + content)
+      else:
+        r8_source_file = content
+
+  if r8_source_file:
+    (header, r8_version_or_hash, maphash) = r8_source_file.split('_')
+    if len(r8_version_or_hash) < 40:
+      args.version = r8_version_or_hash
+    else:
+      args.commit_hash = r8_version_or_hash
+    map_path = None
+    try:
+      map_path = utils.find_cloud_storage_file_from_options('r8lib.jar.map', args)
+    except Exception as e:
+      print(e)
+      print('WARNING: Falling back to using local mapping file.')
+
+    if map_path:
+      check_maphash(map_path, maphash)
+      return map_path
+
+  # If no other map file was found, use the local mapping file.
+  return utils.R8LIB_JAR + '.map'
+
+
+def check_maphash(mapping_path, maphash):
+  map_hash_header = "# pg_map_hash: SHA-256 "
+  for line in open(mapping_path, 'r'):
+    if line.startswith(map_hash_header):
+      infile_maphash = line[len(map_hash_header):].strip()
+      if infile_maphash != maphash:
+        print('ERROR: The mapping file hash does not match the R8 line')
+        print('  In mapping file: ' + infile_maphash)
+        print('  In source file:  ' + maphash)
+        sys.exit(1)
+
+
 def main():
   args = parse_arguments()
-  map_path = utils.find_cloud_storage_file_from_options(
-      'r8lib.jar.map', args, orElse=args.map)
-  return run(
+  with utils.TempDir() as temp:
+    map_path = get_map_file(args, temp)
+    return run(
       map_path,
       args.stacktrace,
       args.no_r8lib,
@@ -76,6 +141,7 @@
       regex=args.regex,
       verbose=args.verbose)
 
+
 def run(map_path, stacktrace, no_r8lib, quiet=False, debug=False, regex=None, verbose=False):
   retrace_args = [jdk.GetJavaExecutable()]
 
diff --git a/tools/run_on_app_dump.py b/tools/run_on_app_dump.py
index 30205f9..88c758d 100755
--- a/tools/run_on_app_dump.py
+++ b/tools/run_on_app_dump.py
@@ -655,6 +655,7 @@
   args = AttrDict({
     'dump': dump_for_app(app_dir, app),
     'r8_jar': get_r8_jar(options, temp_dir, shrinker),
+    'r8_flags': options.r8_flags,
     'ea': False if options.disable_assertions else True,
     'version': options.version,
     'compiler': 'r8full' if is_full_r8(shrinker) else 'r8',
@@ -931,6 +932,8 @@
                       help='Number of times R8 should be run on each app',
                       default=2,
                       type=int)
+  result.add_argument('--r8-flags', '--r8_flags',
+                      help='Additional option(s) for the compiler.')
   result.add_argument('--run-tests', '--run_tests',
                       help='Whether to run instrumentation tests',
                       default=False,
diff --git a/tools/test.py b/tools/test.py
index 1f6d450..5da5b71 100755
--- a/tools/test.py
+++ b/tools/test.py
@@ -452,7 +452,7 @@
 
 def archive_and_return(return_code, options):
   if return_code != 0:
-    if options.archive_failures and os.name != 'nt':
+    if options.archive_failures:
       archive_failures(options)
   return return_code
 
diff --git a/tools/utils.py b/tools/utils.py
index 53aac35..40c5c7a 100644
--- a/tools/utils.py
+++ b/tools/utils.py
@@ -316,9 +316,12 @@
     if not os.path.isdir(path):
         raise
 
+def get_gsutil():
+  return 'gsutil.py' if os.name != 'nt' else 'gsutil.py.bat'
+
 def upload_dir_to_cloud_storage(directory, destination, is_html=False, public_read=True):
   # Upload and make the content encoding right for viewing directly
-  cmd = ['gsutil.py', '-m', 'cp']
+  cmd = [get_gsutil(), '-m', 'cp']
   if is_html:
     cmd += ['-z', 'html']
   if public_read:
@@ -328,7 +331,7 @@
   subprocess.check_call(cmd)
 
 def upload_file_to_cloud_storage(source, destination, public_read=True):
-  cmd = ['gsutil.py', 'cp']
+  cmd = [get_gsutil(), 'cp']
   if public_read:
     cmd += ['-a', 'public-read']
   cmd += [source, destination]
@@ -336,17 +339,17 @@
   subprocess.check_call(cmd)
 
 def delete_file_from_cloud_storage(destination):
-  cmd = ['gsutil.py', 'rm', destination]
+  cmd = [get_gsutil(), 'rm', destination]
   PrintCmd(cmd)
   subprocess.check_call(cmd)
 
 def ls_files_on_cloud_storage(destination):
-  cmd = ['gsutil.py', 'ls', destination]
+  cmd = [get_gsutil(), 'ls', destination]
   PrintCmd(cmd)
   return subprocess.check_output(cmd)
 
 def cat_file_on_cloud_storage(destination, ignore_errors=False):
-  cmd = ['gsutil.py', 'cat', destination]
+  cmd = [get_gsutil(), 'cat', destination]
   PrintCmd(cmd)
   try:
     return subprocess.check_output(cmd)
@@ -357,12 +360,12 @@
       raise e
 
 def file_exists_on_cloud_storage(destination):
-  cmd = ['gsutil.py', 'ls', destination]
+  cmd = [get_gsutil(), 'ls', destination]
   PrintCmd(cmd)
   return subprocess.call(cmd) == 0
 
 def download_file_from_cloud_storage(source, destination, quiet=False):
-  cmd = ['gsutil.py', 'cp', source, destination]
+  cmd = [get_gsutil(), 'cp', source, destination]
   PrintCmd(cmd, quiet=quiet)
   subprocess.check_call(cmd)
 
@@ -394,7 +397,7 @@
 # This is not a problem in our case, but don't ever use this method
 # for synchronization.
 def cloud_storage_exists(destination):
-  cmd = ['gsutil.py', 'ls', destination]
+  cmd = [get_gsutil(), 'ls', destination]
   PrintCmd(cmd)
   exit_code = subprocess.call(cmd)
   return exit_code == 0