Prune methods from AppView in preparation for single caller inlined method pruning

Change-Id: Ie9d7581a8d873c5816fd9747a7259eb7abc83ca5
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/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/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/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/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/optimize/enums/EnumUnboxerImpl.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxerImpl.java
index 854a4e0..ea6b5e5 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;
@@ -705,7 +706,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() {
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/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 583efa7..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;
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/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);
   }