Merge commit '99d0625e6e21db571fe2b9482e0b89d21724c8f5' into dev-release
diff --git a/.gitignore b/.gitignore
index 147cf9a..26634e8 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,4 +1,5 @@
 
+!third_party/chrome/*.sha1
 !third_party/gmail/*.sha1
 !third_party/gmscore/*.sha1
 !third_party/internal/*.sha1
@@ -30,6 +31,8 @@
 tests/2017-10-04/art.tar.gz
 third_party/android_cts_baseline
 third_party/android_cts_baseline.tar.gz
+third_party/clank/clank_google3_prebuilt
+third_party/clank/clank_google3.tar.gz
 third_party/android_jar/lib
 third_party/android_jar/lib-v[0-9][0-9]
 third_party/android_jar/lib-v[0-9][0-9].tar.gz
diff --git a/build.gradle b/build.gradle
index d7c83e7..121279f 100644
--- a/build.gradle
+++ b/build.gradle
@@ -420,6 +420,7 @@
         "chrome/chrome_180917_ffbaa8",
         "chrome/chrome_200430",
         "chrome/monochrome_public_minimal_apks/chrome_200520",
+        "chrome/clank_google3_prebuilt",
         "classlib",
         "cf_segments",
         "desugar/desugar_20180308",
diff --git a/src/main/java/com/android/tools/r8/D8.java b/src/main/java/com/android/tools/r8/D8.java
index b762557..7310ecf 100644
--- a/src/main/java/com/android/tools/r8/D8.java
+++ b/src/main/java/com/android/tools/r8/D8.java
@@ -266,15 +266,7 @@
       InspectorImpl.runInspections(options.outputInspections, appView.appInfo().classes());
       if (options.isGeneratingClassFiles()) {
         // TODO(b/158159959): Move this out so it is shared for both CF and DEX pipelines.
-        SyntheticFinalization.Result result =
-            appView.getSyntheticItems().computeFinalSynthetics(appView);
-        if (result != null) {
-          appView.setAppInfo(new AppInfo(result.commit, appView.appInfo().getMainDexClasses()));
-          appView.pruneItems(result.prunedItems);
-          if (result.lens != null) {
-            appView.setGraphLens(result.lens);
-          }
-        }
+        SyntheticFinalization.finalize(appView);
         new CfApplicationWriter(
                 appView,
                 marker,
@@ -313,15 +305,7 @@
         }
 
         // TODO(b/158159959): Move this out so it is shared for both CF and DEX pipelines.
-        SyntheticFinalization.Result result =
-            appView.getSyntheticItems().computeFinalSynthetics(appView);
-        if (result != null) {
-          appView.setAppInfo(new AppInfo(result.commit, appView.appInfo().getMainDexClasses()));
-          appView.pruneItems(result.prunedItems);
-          if (result.lens != null) {
-            appView.setGraphLens(result.lens);
-          }
-        }
+        SyntheticFinalization.finalize(appView);
 
         new ApplicationWriter(
                 appView,
diff --git a/src/main/java/com/android/tools/r8/D8Command.java b/src/main/java/com/android/tools/r8/D8Command.java
index b12a940..da20df4 100644
--- a/src/main/java/com/android/tools/r8/D8Command.java
+++ b/src/main/java/com/android/tools/r8/D8Command.java
@@ -471,7 +471,6 @@
     assert !internal.enableInlining;
     assert !internal.enableClassInlining;
     assert internal.horizontalClassMergerOptions().isDisabled();
-    assert !internal.enableStaticClassMerging;
     assert !internal.enableVerticalClassMerging;
     assert !internal.enableClassStaticizer;
     assert !internal.enableEnumValueOptimization;
diff --git a/src/main/java/com/android/tools/r8/GenerateMainDexList.java b/src/main/java/com/android/tools/r8/GenerateMainDexList.java
index bf85148..f91a003 100644
--- a/src/main/java/com/android/tools/r8/GenerateMainDexList.java
+++ b/src/main/java/com/android/tools/r8/GenerateMainDexList.java
@@ -18,8 +18,7 @@
 import com.android.tools.r8.shaking.EnqueuerFactory;
 import com.android.tools.r8.shaking.MainDexListBuilder;
 import com.android.tools.r8.shaking.MainDexTracingResult;
-import com.android.tools.r8.shaking.RootSetBuilder;
-import com.android.tools.r8.shaking.RootSetBuilder.RootSet;
+import com.android.tools.r8.shaking.RootSetUtils.RootSet;
 import com.android.tools.r8.shaking.WhyAreYouKeepingConsumer;
 import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.Box;
@@ -86,7 +85,7 @@
     SubtypingInfo subtypingInfo = new SubtypingInfo(appView);
 
     RootSet mainDexRootSet =
-        new RootSetBuilder(appView, subtypingInfo, options.mainDexKeepRules).run(executor);
+        RootSet.builder(appView, subtypingInfo, options.mainDexKeepRules).build(executor);
 
     GraphConsumer graphConsumer = options.mainDexKeptGraphConsumer;
     WhyAreYouKeepingConsumer whyAreYouKeepingConsumer = null;
diff --git a/src/main/java/com/android/tools/r8/L8.java b/src/main/java/com/android/tools/r8/L8.java
index 64fc53e..f12d33c 100644
--- a/src/main/java/com/android/tools/r8/L8.java
+++ b/src/main/java/com/android/tools/r8/L8.java
@@ -138,15 +138,7 @@
 
       new IRConverter(appView, timing).convert(appView, executor);
 
-      SyntheticFinalization.Result result =
-          appView.getSyntheticItems().computeFinalSynthetics(appView);
-      if (result != null) {
-        appView.setAppInfo(new AppInfo(result.commit, appView.appInfo().getMainDexClasses()));
-        appView.pruneItems(result.prunedItems);
-        if (result.lens != null) {
-          appView.setGraphLens(result.lens);
-        }
-      }
+      SyntheticFinalization.finalize(appView);
 
       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 fff3341..8bf7cc6 100644
--- a/src/main/java/com/android/tools/r8/L8Command.java
+++ b/src/main/java/com/android/tools/r8/L8Command.java
@@ -169,7 +169,6 @@
     assert !internal.enableInlining;
     assert !internal.enableClassInlining;
     assert internal.horizontalClassMergerOptions().isDisabled();
-    assert !internal.enableStaticClassMerging;
     assert !internal.enableVerticalClassMerging;
     assert !internal.enableClassStaticizer;
     assert !internal.enableEnumValueOptimization;
diff --git a/src/main/java/com/android/tools/r8/PrintSeeds.java b/src/main/java/com/android/tools/r8/PrintSeeds.java
index 4be995a..99fc6db 100644
--- a/src/main/java/com/android/tools/r8/PrintSeeds.java
+++ b/src/main/java/com/android/tools/r8/PrintSeeds.java
@@ -14,8 +14,8 @@
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.shaking.Enqueuer;
 import com.android.tools.r8.shaking.EnqueuerFactory;
-import com.android.tools.r8.shaking.RootSetBuilder;
-import com.android.tools.r8.shaking.RootSetBuilder.RootSet;
+import com.android.tools.r8.shaking.RootSetUtils.RootSet;
+import com.android.tools.r8.shaking.RootSetUtils.RootSetBuilder;
 import com.android.tools.r8.utils.ExceptionUtils;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.StringUtils;
@@ -90,8 +90,8 @@
       appView.setAppServices(AppServices.builder(appView).build());
       SubtypingInfo subtypingInfo = new SubtypingInfo(appView);
       RootSet rootSet =
-          new RootSetBuilder(appView, subtypingInfo, options.getProguardConfiguration().getRules())
-              .run(executor);
+          RootSet.builder(appView, subtypingInfo, options.getProguardConfiguration().getRules())
+              .build(executor);
       Enqueuer enqueuer =
           EnqueuerFactory.createForInitialTreeShaking(appView, executor, subtypingInfo);
       AppInfoWithLiveness appInfo = enqueuer.traceApplication(rootSet, executor, timing);
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java
index 5e31835..9c93e0e 100644
--- a/src/main/java/com/android/tools/r8/R8.java
+++ b/src/main/java/com/android/tools/r8/R8.java
@@ -38,7 +38,6 @@
 import com.android.tools.r8.graph.SubtypingInfo;
 import com.android.tools.r8.graph.analysis.ClassInitializerAssertionEnablingAnalysis;
 import com.android.tools.r8.graph.analysis.InitializedClassesInInstanceMethodsAnalysis;
-import com.android.tools.r8.graph.classmerging.StaticallyMergedClasses;
 import com.android.tools.r8.graph.classmerging.VerticallyMergedClasses;
 import com.android.tools.r8.horizontalclassmerging.HorizontalClassMerger;
 import com.android.tools.r8.horizontalclassmerging.HorizontalClassMergerResult;
@@ -95,10 +94,9 @@
 import com.android.tools.r8.shaking.MissingClasses;
 import com.android.tools.r8.shaking.ProguardConfigurationRule;
 import com.android.tools.r8.shaking.ProguardConfigurationUtils;
-import com.android.tools.r8.shaking.RootSetBuilder;
-import com.android.tools.r8.shaking.RootSetBuilder.RootSet;
+import com.android.tools.r8.shaking.RootSetUtils.RootSet;
+import com.android.tools.r8.shaking.RootSetUtils.RootSetBuilder;
 import com.android.tools.r8.shaking.RuntimeTypeCheckInfo;
-import com.android.tools.r8.shaking.StaticClassMerger;
 import com.android.tools.r8.shaking.TreePruner;
 import com.android.tools.r8.shaking.TreePrunerConfiguration;
 import com.android.tools.r8.shaking.VerticalClassMerger;
@@ -343,12 +341,12 @@
         }
         SubtypingInfo subtypingInfo = new SubtypingInfo(appView);
         appView.setRootSet(
-            new RootSetBuilder(
+            RootSet.builder(
                     appView,
                     subtypingInfo,
                     Iterables.concat(
                         options.getProguardConfiguration().getRules(), synthesizedProguardRules))
-                .run(executorService));
+                .build(executorService));
 
         AnnotationRemover.Builder annotationRemoverBuilder =
             options.isShrinking() ? AnnotationRemover.builder() : null;
@@ -430,8 +428,8 @@
         // Find classes which may have code executed before secondary dex files installation.
         SubtypingInfo subtypingInfo = new SubtypingInfo(appView);
         mainDexRootSet =
-            new RootSetBuilder(appView, subtypingInfo, options.mainDexKeepRules)
-                .run(executorService);
+            RootSet.builder(appView, subtypingInfo, options.mainDexKeepRules)
+                .build(executorService);
         // Live types is the tracing result.
         Set<DexProgramClass> mainDexBaseClasses =
             EnqueuerFactory.createForInitialMainDexTracing(appView, executorService, subtypingInfo)
@@ -477,18 +475,6 @@
       RuntimeTypeCheckInfo runtimeTypeCheckInfo = classMergingEnqueuerExtensionBuilder.build();
       if (!isKotlinLibraryCompilationWithInlinePassThrough
           && options.getProguardConfiguration().isOptimizing()) {
-        if (options.enableStaticClassMerging) {
-          timing.begin("HorizontalStaticClassMerger");
-          StaticClassMerger staticClassMerger =
-              new StaticClassMerger(appViewWithLiveness, options, mainDexTracingResult);
-          NestedGraphLens lens = staticClassMerger.run();
-          appView.rewriteWithLens(lens);
-          timing.end();
-        } else {
-          appView.setStaticallyMergedClasses(StaticallyMergedClasses.empty());
-        }
-        assert appView.staticallyMergedClasses() != null;
-
         if (options.enableVerticalClassMerging) {
           timing.begin("VerticalClassMerger");
           VerticalClassMerger verticalClassMerger =
@@ -822,25 +808,10 @@
         appView.appInfo().getMainDexClasses().addAll(mainDexTracingResult);
       }
 
-      SyntheticFinalization.Result result =
-          appView.getSyntheticItems().computeFinalSynthetics(appView);
-      if (result != null) {
-        if (appView.appInfo().hasLiveness()) {
-          if (result.lens == null) {
-            appViewWithLiveness.setAppInfo(
-                appViewWithLiveness.appInfo().rebuildWithLiveness(result.commit));
-          } else {
-            appViewWithLiveness.rewriteWithLensAndApplication(
-                result.lens, result.commit.getApplication().asDirect());
-          }
-          appViewWithLiveness.pruneItems(result.prunedItems);
-        } else {
-          appView.setAppInfo(appView.appInfo().rebuildWithClassHierarchy(result.commit));
-          appView.pruneItems(result.prunedItems);
-          if (result.lens != null) {
-            appView.setGraphLens(result.lens);
-          }
-        }
+      if (appView.appInfo().hasLiveness()) {
+        SyntheticFinalization.finalizeWithLiveness(appView.withLiveness());
+      } else {
+        SyntheticFinalization.finalizeWithClassHierarchy(appView);
       }
 
       // Perform minification.
diff --git a/src/main/java/com/android/tools/r8/R8Command.java b/src/main/java/com/android/tools/r8/R8Command.java
index d6ab3a6..0fa55a7 100644
--- a/src/main/java/com/android/tools/r8/R8Command.java
+++ b/src/main/java/com/android/tools/r8/R8Command.java
@@ -858,7 +858,6 @@
 
     assert proguardConfiguration.isOptimizing()
         || internal.horizontalClassMergerOptions().isDisabled();
-    assert internal.enableStaticClassMerging || !proguardConfiguration.isOptimizing();
     assert !internal.enableTreeShakingOfLibraryMethodOverrides;
     assert internal.enableVerticalClassMerging || !proguardConfiguration.isOptimizing();
     if (internal.debug) {
@@ -868,7 +867,6 @@
       internal.enableInlining = false;
       internal.enableClassInlining = false;
       internal.horizontalClassMergerOptions().disable();
-      internal.enableStaticClassMerging = false;
       internal.enableVerticalClassMerging = false;
       internal.enableClassStaticizer = false;
       internal.outline.enabled = false;
@@ -881,7 +879,6 @@
       internal.enableEnumUnboxing = false;
       internal.horizontalClassMergerOptions().disable();
       internal.enableLambdaMerging = false;
-      internal.enableStaticClassMerging = false;
       internal.enableVerticalClassMerging = false;
     }
 
diff --git a/src/main/java/com/android/tools/r8/cf/CfPrinter.java b/src/main/java/com/android/tools/r8/cf/CfPrinter.java
index 656c8f8..92930b0 100644
--- a/src/main/java/com/android/tools/r8/cf/CfPrinter.java
+++ b/src/main/java/com/android/tools/r8/cf/CfPrinter.java
@@ -563,7 +563,7 @@
     indent();
     Kind kind = cfSwitch.getKind();
     builder.append(kind == Kind.LOOKUP ? "lookup" : "table").append("switch");
-    IntList keys = cfSwitch.getKeys();
+    IntList keys = (IntList) cfSwitch.getKeys();
     List<CfLabel> targets = cfSwitch.getSwitchTargets();
     for (int i = 0; i < targets.size(); i++) {
       indent();
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfSwitch.java b/src/main/java/com/android/tools/r8/cf/code/CfSwitch.java
index cb915ff..f07011a 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfSwitch.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfSwitch.java
@@ -21,7 +21,6 @@
 import com.android.tools.r8.naming.NamingLens;
 import com.android.tools.r8.utils.structural.CompareToVisitor;
 import it.unimi.dsi.fastutil.ints.IntArrayList;
-import it.unimi.dsi.fastutil.ints.IntList;
 import java.util.List;
 import org.objectweb.asm.Label;
 import org.objectweb.asm.MethodVisitor;
@@ -71,7 +70,7 @@
     return defaultTarget;
   }
 
-  public IntList getKeys() {
+  public List<Integer> getKeys() {
     return new IntArrayList(keys);
   }
 
diff --git a/src/main/java/com/android/tools/r8/dex/VirtualFile.java b/src/main/java/com/android/tools/r8/dex/VirtualFile.java
index 7da0bc5..414031e 100644
--- a/src/main/java/com/android/tools/r8/dex/VirtualFile.java
+++ b/src/main/java/com/android/tools/r8/dex/VirtualFile.java
@@ -36,6 +36,7 @@
 import com.android.tools.r8.utils.SetUtils;
 import com.android.tools.r8.utils.Timing;
 import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Iterables;
 import com.google.common.collect.Iterators;
 import com.google.common.collect.Sets;
 import java.io.IOException;
@@ -471,6 +472,20 @@
           classes.removeAll(featureClasses);
         }
       }
+      List<DexProgramClass> toRemove = new ArrayList<>();
+      for (DexProgramClass dexProgramClass : classes) {
+        Collection<DexProgramClass> synthesizedFrom = dexProgramClass.getSynthesizedFrom();
+        if (!synthesizedFrom.isEmpty()) {
+          DexProgramClass from = Iterables.getFirst(synthesizedFrom, null);
+          FeatureSplit featureSplit = classToFeatureSplitMap.getFeatureSplit(from);
+          if (!featureSplit.isBase()) {
+            Set<DexProgramClass> dexProgramClasses = featureSplitClasses.get(featureSplit);
+            dexProgramClasses.add(dexProgramClass);
+            toRemove.add(dexProgramClass);
+          }
+        }
+      }
+      classes.removeAll(toRemove);
       return featureSplitClasses;
     }
 
diff --git a/src/main/java/com/android/tools/r8/features/ClassToFeatureSplitMap.java b/src/main/java/com/android/tools/r8/features/ClassToFeatureSplitMap.java
index ec95ade..1ea1595 100644
--- a/src/main/java/com/android/tools/r8/features/ClassToFeatureSplitMap.java
+++ b/src/main/java/com/android/tools/r8/features/ClassToFeatureSplitMap.java
@@ -20,6 +20,7 @@
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.Reporter;
 import com.google.common.collect.Sets;
+import java.util.HashMap;
 import java.util.IdentityHashMap;
 import java.util.Map;
 import java.util.Set;
@@ -27,11 +28,18 @@
 public class ClassToFeatureSplitMap {
 
   private final Map<DexType, FeatureSplit> classToFeatureSplitMap = new IdentityHashMap<>();
+  private final Map<FeatureSplit, String> representativeStringsForFeatureSplit;
 
-  private ClassToFeatureSplitMap() {}
+  private ClassToFeatureSplitMap() {
+    this(new HashMap<>());
+  }
+
+  private ClassToFeatureSplitMap(Map<FeatureSplit, String> representativeStringsForFeatureSplit) {
+    this.representativeStringsForFeatureSplit = representativeStringsForFeatureSplit;
+  }
 
   public static ClassToFeatureSplitMap createEmptyClassToFeatureSplitMap() {
-    return new ClassToFeatureSplitMap();
+    return new ClassToFeatureSplitMap(null);
   }
 
   public static ClassToFeatureSplitMap createInitialClassToFeatureSplitMap(
@@ -56,6 +64,7 @@
     }
 
     for (FeatureSplit featureSplit : featureSplitConfiguration.getFeatureSplits()) {
+      String representativeType = null;
       for (ProgramResourceProvider programResourceProvider :
           featureSplit.getProgramResourceProviders()) {
         try {
@@ -63,16 +72,42 @@
             for (String classDescriptor : programResource.getClassDescriptors()) {
               DexType type = dexItemFactory.createType(classDescriptor);
               result.classToFeatureSplitMap.put(type, featureSplit);
+              if (representativeType == null || classDescriptor.compareTo(representativeType) > 0) {
+                representativeType = classDescriptor;
+              }
             }
           }
         } catch (ResourceException e) {
           throw reporter.fatalError(e.getMessage());
         }
       }
+      if (representativeType != null) {
+        result.representativeStringsForFeatureSplit.put(featureSplit, representativeType);
+      }
     }
     return result;
   }
 
+  public int compareFeatureSplitsForDexTypes(DexType a, DexType b) {
+    FeatureSplit featureSplitA = getFeatureSplit(a);
+    FeatureSplit featureSplitB = getFeatureSplit(b);
+    assert featureSplitA != null;
+    assert featureSplitB != null;
+    if (featureSplitA == featureSplitB) {
+      return 0;
+    }
+    // Base bigger than any other feature
+    if (featureSplitA.isBase()) {
+      return 1;
+    }
+    if (featureSplitB.isBase()) {
+      return -1;
+    }
+    return representativeStringsForFeatureSplit
+        .get(featureSplitA)
+        .compareTo(representativeStringsForFeatureSplit.get(featureSplitB));
+  }
+
   public Map<FeatureSplit, Set<DexProgramClass>> getFeatureSplitClasses(
       Set<DexProgramClass> classes) {
     Map<FeatureSplit, Set<DexProgramClass>> result = new IdentityHashMap<>();
@@ -118,8 +153,13 @@
     return getFeatureSplit(a) == getFeatureSplit(b);
   }
 
+  public boolean isInSameFeatureOrBothInBase(DexType a, DexType b) {
+    return getFeatureSplit(a) == getFeatureSplit(b);
+  }
+
   public ClassToFeatureSplitMap rewrittenWithLens(GraphLens lens) {
-    ClassToFeatureSplitMap rewrittenClassToFeatureSplitMap = new ClassToFeatureSplitMap();
+    ClassToFeatureSplitMap rewrittenClassToFeatureSplitMap =
+        new ClassToFeatureSplitMap(representativeStringsForFeatureSplit);
     classToFeatureSplitMap.forEach(
         (type, featureSplit) -> {
           DexType rewrittenType = lens.lookupType(type);
@@ -137,7 +177,8 @@
   }
 
   public ClassToFeatureSplitMap withoutPrunedItems(PrunedItems prunedItems) {
-    ClassToFeatureSplitMap classToFeatureSplitMapAfterPruning = new ClassToFeatureSplitMap();
+    ClassToFeatureSplitMap classToFeatureSplitMapAfterPruning =
+        new ClassToFeatureSplitMap(representativeStringsForFeatureSplit);
     classToFeatureSplitMap.forEach(
         (type, featureSplit) -> {
           if (!prunedItems.getRemovedClasses().contains(type)) {
diff --git a/src/main/java/com/android/tools/r8/graph/AbstractAccessContexts.java b/src/main/java/com/android/tools/r8/graph/AbstractAccessContexts.java
index 8767555..f8c4f3b 100644
--- a/src/main/java/com/android/tools/r8/graph/AbstractAccessContexts.java
+++ b/src/main/java/com/android/tools/r8/graph/AbstractAccessContexts.java
@@ -293,7 +293,7 @@
       return accessesWithContexts.isEmpty();
     }
 
-    boolean recordAccess(DexField access, ProgramMethod context) {
+    public boolean recordAccess(DexField access, ProgramMethod context) {
       return accessesWithContexts
           .computeIfAbsent(access, ignore -> ProgramMethodSet.create())
           .add(context);
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 2d91d71..7d574dd 100644
--- a/src/main/java/com/android/tools/r8/graph/AppView.java
+++ b/src/main/java/com/android/tools/r8/graph/AppView.java
@@ -11,7 +11,6 @@
 import com.android.tools.r8.graph.analysis.InitializedClassesInInstanceMethodsAnalysis.InitializedClassesInInstanceMethods;
 import com.android.tools.r8.graph.classmerging.HorizontallyMergedLambdaClasses;
 import com.android.tools.r8.graph.classmerging.MergedClassesCollection;
-import com.android.tools.r8.graph.classmerging.StaticallyMergedClasses;
 import com.android.tools.r8.graph.classmerging.VerticallyMergedClasses;
 import com.android.tools.r8.horizontalclassmerging.HorizontallyMergedClasses;
 import com.android.tools.r8.ir.analysis.inlining.SimpleInliningConstraintFactory;
@@ -34,7 +33,8 @@
 import com.android.tools.r8.shaking.KeepInfoCollection;
 import com.android.tools.r8.shaking.LibraryModeledPredicate;
 import com.android.tools.r8.shaking.MainDexClasses;
-import com.android.tools.r8.shaking.RootSetBuilder.RootSet;
+import com.android.tools.r8.shaking.ProguardCompatibilityActions;
+import com.android.tools.r8.shaking.RootSetUtils.RootSet;
 import com.android.tools.r8.synthesis.SyntheticItems;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.InternalOptions.TestingOptions;
@@ -62,10 +62,12 @@
   private final WholeProgramOptimizations wholeProgramOptimizations;
   private GraphLens graphLens;
   private InitClassLens initClassLens;
+  private ProguardCompatibilityActions proguardCompatibilityActions;
   private RootSet rootSet;
   // This should perferably always be obtained via AppInfoWithLiveness.
   // Currently however the liveness may be downgraded thus loosing the computed keep info.
   private KeepInfoCollection keepInfo = null;
+
   private final AbstractValueFactory abstractValueFactory = new AbstractValueFactory();
   private final InstanceFieldInitializationInfoFactory instanceFieldInitializationInfoFactory =
       new InstanceFieldInitializationInfoFactory();
@@ -91,7 +93,6 @@
   private InitializedClassesInInstanceMethods initializedClassesInInstanceMethods;
   private HorizontallyMergedLambdaClasses horizontallyMergedLambdaClasses;
   private HorizontallyMergedClasses horizontallyMergedClasses;
-  private StaticallyMergedClasses staticallyMergedClasses;
   private VerticallyMergedClasses verticallyMergedClasses;
   private EnumDataMap unboxedEnums = EnumDataMap.empty();
   // TODO(b/169115389): Remove
@@ -201,6 +202,10 @@
     return appInfo;
   }
 
+  public AppInfoWithLiveness appInfoWithLiveness() {
+    return appInfo.hasLiveness() ? appInfo.withLiveness() : null;
+  }
+
   public AppInfoWithClassHierarchy appInfoForDesugaring() {
     if (enableWholeProgramOptimizations()) {
       assert appInfo.hasClassHierarchy();
@@ -459,6 +464,20 @@
     return keepInfo;
   }
 
+  public boolean hasProguardCompatibilityActions() {
+    return proguardCompatibilityActions != null;
+  }
+
+  public ProguardCompatibilityActions getProguardCompatibilityActions() {
+    return proguardCompatibilityActions;
+  }
+
+  public void setProguardCompatibilityActions(
+      ProguardCompatibilityActions proguardCompatibilityActions) {
+    assert options().forceProguardCompatibility;
+    this.proguardCompatibilityActions = proguardCompatibilityActions;
+  }
+
   public MergedClassesCollection allMergedClasses() {
     MergedClassesCollection collection = new MergedClassesCollection();
     if (horizontallyMergedClasses != null) {
@@ -505,19 +524,6 @@
   }
 
   /**
-   * Get the result of static class merging. Returns null if static class merging has not been run.
-   */
-  public StaticallyMergedClasses staticallyMergedClasses() {
-    return staticallyMergedClasses;
-  }
-
-  public void setStaticallyMergedClasses(StaticallyMergedClasses staticallyMergedClasses) {
-    assert this.staticallyMergedClasses == null;
-    this.staticallyMergedClasses = staticallyMergedClasses;
-    testing().staticallyMergedClassesConsumer.accept(dexItemFactory(), staticallyMergedClasses);
-  }
-
-  /**
    * Get the result of vertical class merging. Returns null if vertical class merging has not been
    * run.
    */
@@ -558,6 +564,10 @@
         : null;
   }
 
+  public boolean hasLiveness() {
+    return appInfo().hasLiveness();
+  }
+
   public AppView<AppInfoWithLiveness> withLiveness() {
     @SuppressWarnings("unchecked")
     AppView<AppInfoWithLiveness> appViewWithLiveness = (AppView<AppInfoWithLiveness>) this;
@@ -605,6 +615,10 @@
     if (appServices() != null) {
       setAppServices(appServices().prunedCopy(prunedItems));
     }
+    if (hasProguardCompatibilityActions()) {
+      setProguardCompatibilityActions(
+          getProguardCompatibilityActions().withoutPrunedItems(prunedItems));
+    }
   }
 
   @SuppressWarnings("unchecked")
@@ -670,6 +684,10 @@
           if (appView.hasInitClassLens()) {
             appView.setInitClassLens(appView.initClassLens().rewrittenWithLens(lens));
           }
+          if (appView.hasProguardCompatibilityActions()) {
+            appView.setProguardCompatibilityActions(
+                appView.getProguardCompatibilityActions().rewrittenWithLens(lens));
+          }
         });
   }
 
diff --git a/src/main/java/com/android/tools/r8/graph/DexApplication.java b/src/main/java/com/android/tools/r8/graph/DexApplication.java
index 37b8d28..d5ea16e 100644
--- a/src/main/java/com/android/tools/r8/graph/DexApplication.java
+++ b/src/main/java/com/android/tools/r8/graph/DexApplication.java
@@ -98,8 +98,6 @@
 
   abstract List<DexProgramClass> programClasses();
 
-  abstract List<DexClasspathClass> classpathClasses();
-
   public List<DexProgramClass> classes() {
     ReorderBox<DexProgramClass> box = new ReorderBox<>(programClasses());
     assert box.reorderClasses();
@@ -134,7 +132,6 @@
   public abstract static class Builder<T extends Builder<T>> {
 
     private final List<DexProgramClass> programClasses = new ArrayList<>();
-    private final List<DexClasspathClass> classpathClasses = new ArrayList<>();
 
     final List<DataResourceProvider> dataResourceProviders = new ArrayList<>();
 
@@ -157,7 +154,6 @@
 
     public Builder(DexApplication application) {
       programClasses.addAll(application.programClasses());
-      classpathClasses.addAll(application.classpathClasses());
       dataResourceProviders.addAll(application.dataResourceProviders);
       proguardMap = application.getProguardMap();
       timing = application.timing;
@@ -167,6 +163,14 @@
       synthesizedClasses = new ArrayList<>();
     }
 
+    public boolean isDirect() {
+      return false;
+    }
+
+    public DirectMappedDexApplication.Builder asDirect() {
+      return null;
+    }
+
     public synchronized T setProguardMap(ClassNameMapper proguardMap) {
       assert this.proguardMap == null;
       this.proguardMap = proguardMap;
@@ -180,14 +184,6 @@
       return self();
     }
 
-    public synchronized T replaceClasspathClasses(
-        Collection<DexClasspathClass> newClasspathClasses) {
-      assert newClasspathClasses != null;
-      classpathClasses.clear();
-      classpathClasses.addAll(newClasspathClasses);
-      return self();
-    }
-
     public synchronized T addDataResourceProvider(DataResourceProvider provider) {
       dataResourceProviders.add(provider);
       return self();
@@ -208,16 +204,6 @@
       return self();
     }
 
-    public synchronized T addClasspathClass(DexClasspathClass clazz) {
-      classpathClasses.add(clazz);
-      return self();
-    }
-
-    public synchronized T addClasspathClasses(Collection<DexClasspathClass> classes) {
-      classpathClasses.addAll(classes);
-      return self();
-    }
-
     public synchronized T addSynthesizedClass(DexProgramClass synthesizedClass) {
       assert synthesizedClass.isProgramClass() : "All synthesized classes must be program classes";
       addProgramClass(synthesizedClass);
@@ -229,10 +215,6 @@
       return programClasses;
     }
 
-    public List<DexClasspathClass> getClasspathClasses() {
-      return classpathClasses;
-    }
-
     public Collection<DexProgramClass> getSynthesizedClasses() {
       return synthesizedClasses;
     }
diff --git a/src/main/java/com/android/tools/r8/graph/DexEncodedField.java b/src/main/java/com/android/tools/r8/graph/DexEncodedField.java
index 30d5cfd..f933754 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedField.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedField.java
@@ -20,9 +20,11 @@
 import com.android.tools.r8.ir.optimize.info.MutableFieldOptimizationInfo;
 import com.android.tools.r8.kotlin.KotlinFieldLevelInfo;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.utils.ConsumerUtils;
 import com.android.tools.r8.utils.structural.StructuralItem;
 import com.android.tools.r8.utils.structural.StructuralMapping;
 import com.android.tools.r8.utils.structural.StructuralSpecification;
+import java.util.function.Consumer;
 
 public class DexEncodedField extends DexEncodedMember<DexEncodedField, DexField>
     implements StructuralItem<DexEncodedField> {
@@ -65,6 +67,10 @@
     assert GenericSignatureUtils.verifyNoDuplicateGenericDefinitions(genericSignature, annotations);
   }
 
+  public DexEncodedField(DexField field, FieldAccessFlags accessFlags) {
+    this(field, accessFlags, FieldTypeSignature.noSignature(), DexAnnotationSet.empty(), null);
+  }
+
   public DexEncodedField(
       DexField field,
       FieldAccessFlags accessFlags,
@@ -162,6 +168,10 @@
     return getReference().getType();
   }
 
+  public TypeElement getTypeElement(AppView<?> appView) {
+    return getReference().getTypeElement(appView);
+  }
+
   @Override
   public boolean isDexEncodedField() {
     return true;
@@ -295,17 +305,14 @@
   }
 
   public DexEncodedField toTypeSubstitutedField(DexField field) {
+    return toTypeSubstitutedField(field, ConsumerUtils.emptyConsumer());
+  }
+
+  public DexEncodedField toTypeSubstitutedField(DexField field, Consumer<Builder> consumer) {
     if (this.field == field) {
       return this;
     }
-    // TODO(b/169923358): Consider removing the fieldSignature here.
-    DexEncodedField result =
-        new DexEncodedField(field, accessFlags, genericSignature, annotations(), staticValue);
-    result.optimizationInfo =
-        optimizationInfo.isMutableFieldOptimizationInfo()
-            ? optimizationInfo.asMutableFieldOptimizationInfo().mutableCopy()
-            : DefaultFieldOptimizationInfo.getInstance();
-    return result;
+    return builder(this).setField(field).apply(consumer).build();
   }
 
   public boolean validateDexValue(DexItemFactory factory) {
@@ -335,4 +342,58 @@
   public void clearGenericSignature() {
     this.genericSignature = FieldTypeSignature.noSignature();
   }
+
+  private static Builder builder(DexEncodedField from) {
+    return new Builder(from);
+  }
+
+  public static class Builder {
+
+    private DexField field;
+    private DexAnnotationSet annotations;
+    private FieldAccessFlags accessFlags;
+    private FieldTypeSignature genericSignature;
+    private DexValue staticValue;
+    private FieldOptimizationInfo optimizationInfo;
+
+    Builder(DexEncodedField from) {
+      // Copy all the mutable state of a DexEncodedField here.
+      field = from.field;
+      accessFlags = from.accessFlags.copy();
+      // TODO(b/169923358): Consider removing the fieldSignature here.
+      genericSignature = from.getGenericSignature();
+      annotations = from.annotations();
+      staticValue = from.staticValue;
+      optimizationInfo =
+          from.optimizationInfo.isDefaultFieldOptimizationInfo()
+              ? DefaultFieldOptimizationInfo.getInstance()
+              : from.optimizationInfo.mutableCopy();
+    }
+
+    public Builder fixupOptimizationInfo(Consumer<MutableFieldOptimizationInfo> consumer) {
+      if (optimizationInfo.isMutableFieldOptimizationInfo()) {
+        consumer.accept(optimizationInfo.asMutableFieldOptimizationInfo());
+      }
+      return this;
+    }
+
+    public Builder apply(Consumer<Builder> consumer) {
+      consumer.accept(this);
+      return this;
+    }
+
+    public Builder setField(DexField field) {
+      this.field = field;
+      return this;
+    }
+
+    DexEncodedField build() {
+      DexEncodedField dexEncodedField =
+          new DexEncodedField(field, accessFlags, genericSignature, annotations, staticValue);
+      if (optimizationInfo.isMutableFieldOptimizationInfo()) {
+        dexEncodedField.setOptimizationInfo(optimizationInfo.asMutableFieldOptimizationInfo());
+      }
+      return dexEncodedField;
+    }
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
index 8060cbe..c736831 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
@@ -1293,15 +1293,13 @@
                           }
                         })
                     .build())
-            .modifyAccessFlags(
-                accessFlags -> {
-                  accessFlags.setSynthetic();
-                  accessFlags.setStatic();
-                  accessFlags.unsetPrivate();
-                  if (holder.isInterface()) {
-                    accessFlags.setPublic();
-                  }
-                })
+            .setAccessFlags(
+                MethodAccessFlags.builder()
+                    .setBridge()
+                    .setPublic(holder.isInterface())
+                    .setStatic()
+                    .setSynthetic()
+                    .build())
             .build());
   }
 
@@ -1581,8 +1579,9 @@
       return this;
     }
 
-    public void setAccessFlags(MethodAccessFlags accessFlags) {
-      this.accessFlags = accessFlags.copy();
+    public Builder setAccessFlags(MethodAccessFlags accessFlags) {
+      this.accessFlags = accessFlags;
+      return this;
     }
 
     public Builder setMethod(DexMethod method) {
diff --git a/src/main/java/com/android/tools/r8/graph/DexField.java b/src/main/java/com/android/tools/r8/graph/DexField.java
index a925fcc..5ebf3ae 100644
--- a/src/main/java/com/android/tools/r8/graph/DexField.java
+++ b/src/main/java/com/android/tools/r8/graph/DexField.java
@@ -3,8 +3,11 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.graph;
 
+import static com.android.tools.r8.ir.analysis.type.Nullability.maybeNull;
+
 import com.android.tools.r8.dex.IndexedItemCollection;
 import com.android.tools.r8.errors.CompilationError;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.references.FieldReference;
 import com.android.tools.r8.references.Reference;
 import com.android.tools.r8.utils.structural.CompareToVisitor;
@@ -46,6 +49,10 @@
     return type;
   }
 
+  public TypeElement getTypeElement(AppView<?> appView) {
+    return TypeElement.fromDexType(getType(), maybeNull(), appView);
+  }
+
   @Override
   public DexEncodedField lookupOnClass(DexClass clazz) {
     return clazz != null ? clazz.lookupField(this) : null;
diff --git a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
index e775ac6..30ac84b 100644
--- a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
+++ b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
@@ -293,6 +293,11 @@
       createString(Constants.TEMPORARY_INSTANCE_INITIALIZER_PREFIX);
 
   public final DexString thisName = createString("this");
+
+  // As much as possible, R8 should rely on the content of the static enum field, using
+  // enumMembers.isValuesFieldCandidate or checking the object state in the optimization info.
+  // The field name is unrealiable since the filed can be minified prior to this compilation.
+  // We keep enumValuesFieldName as a heuristic only.
   public final DexString enumValuesFieldName = createString("$VALUES");
 
   public final DexString enabledFieldName = createString("ENABLED");
@@ -1398,21 +1403,17 @@
       return field == nameField || field == ordinalField;
     }
 
-    public boolean isValuesMethod(DexMethod method, DexClass enumClass) {
-      assert enumClass.isEnum();
-      return method.holder == enumClass.type
-          && method.proto.returnType == enumClass.type.toArrayType(1, DexItemFactory.this)
-          && method.proto.parameters.size() == 0
-          && method.name == valuesMethodName;
+    public boolean isEnumField(DexEncodedField staticField, DexType enumType) {
+      assert staticField.isStatic();
+      return staticField.getType() == enumType && staticField.isEnum() && staticField.isFinal();
     }
 
-    public boolean isValueOfMethod(DexMethod method, DexClass enumClass) {
-      assert enumClass.isEnum();
-      return method.holder == enumClass.type
-          && method.proto.returnType == enumClass.type
-          && method.proto.parameters.size() == 1
-          && method.proto.parameters.values[0] == stringType
-          && method.name == valueOfMethodName;
+    public boolean isValuesFieldCandidate(DexEncodedField staticField, DexType enumType) {
+      assert staticField.isStatic();
+      return staticField.getType().isArrayType()
+          && staticField.getType().toArrayElementType(DexItemFactory.this) == enumType
+          && staticField.isSynthetic()
+          && staticField.isFinal();
     }
   }
 
diff --git a/src/main/java/com/android/tools/r8/graph/DexType.java b/src/main/java/com/android/tools/r8/graph/DexType.java
index c488caf..76d2ea4 100644
--- a/src/main/java/com/android/tools/r8/graph/DexType.java
+++ b/src/main/java/com/android/tools/r8/graph/DexType.java
@@ -4,20 +4,12 @@
 package com.android.tools.r8.graph;
 
 import static com.android.tools.r8.graph.DexProgramClass.asProgramClassOrNull;
-import static com.android.tools.r8.ir.desugar.DesugaredLibraryWrapperSynthesizer.TYPE_WRAPPER_SUFFIX;
-import static com.android.tools.r8.ir.desugar.DesugaredLibraryWrapperSynthesizer.VIVIFIED_TYPE_WRAPPER_SUFFIX;
-import static com.android.tools.r8.ir.desugar.InterfaceMethodRewriter.COMPANION_CLASS_NAME_SUFFIX;
-import static com.android.tools.r8.ir.desugar.InterfaceMethodRewriter.EMULATE_LIBRARY_CLASS_NAME_SUFFIX;
 import static com.android.tools.r8.ir.desugar.LambdaRewriter.LAMBDA_GROUP_CLASS_NAME_PREFIX;
-import static com.android.tools.r8.ir.optimize.enums.UnboxedEnumMemberRelocator.ENUM_UNBOXING_UTILITY_CLASS_SUFFIX;
 
 import com.android.tools.r8.dex.IndexedItemCollection;
 import com.android.tools.r8.errors.Unreachable;
-import com.android.tools.r8.horizontalclassmerging.SyntheticArgumentClass;
-import com.android.tools.r8.ir.desugar.DesugaredLibraryRetargeter;
 import com.android.tools.r8.ir.desugar.nest.NestBasedAccessDesugaring;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
-import com.android.tools.r8.synthesis.SyntheticNaming;
 import com.android.tools.r8.utils.DescriptorUtils;
 import com.android.tools.r8.utils.structural.CompareToVisitor;
 import com.android.tools.r8.utils.structural.HashingVisitor;
@@ -308,27 +300,6 @@
     return isDoubleType() || isLongType();
   }
 
-  // TODO(b/158159959): Remove usage of name-based identification.
-  public boolean isD8R8SynthesizedClassType() {
-    String name = toSourceString();
-    // The synthesized classes listed here must always be unique to a program context and thus
-    // never duplicated for distinct inputs.
-    return false
-        // Hygienic suffix.
-        || name.contains(COMPANION_CLASS_NAME_SUFFIX)
-        || name.contains(ENUM_UNBOXING_UTILITY_CLASS_SUFFIX)
-        || name.contains(SyntheticArgumentClass.SYNTHETIC_CLASS_SUFFIX)
-        // New and hygienic synthesis infrastructure.
-        || SyntheticNaming.isSyntheticName(name)
-        // Only generated in core lib.
-        || name.contains(EMULATE_LIBRARY_CLASS_NAME_SUFFIX)
-        || name.contains(TYPE_WRAPPER_SUFFIX)
-        || name.contains(VIVIFIED_TYPE_WRAPPER_SUFFIX)
-        || name.contains(DesugaredLibraryRetargeter.DESUGAR_LIB_RETARGET_CLASS_NAME_PREFIX)
-        // Non-hygienic types.
-        || isSynthesizedTypeThatCouldBeDuplicated(name);
-  }
-
   public boolean isLegacySynthesizedTypeAllowedDuplication() {
     String name = toSourceString();
     return isSynthesizedTypeThatCouldBeDuplicated(name) || oldSynthesizedName(name);
diff --git a/src/main/java/com/android/tools/r8/graph/DexValue.java b/src/main/java/com/android/tools/r8/graph/DexValue.java
index f355030..3c17c60 100644
--- a/src/main/java/com/android/tools/r8/graph/DexValue.java
+++ b/src/main/java/com/android/tools/r8/graph/DexValue.java
@@ -1358,9 +1358,7 @@
 
     @Override
     public AbstractValue toAbstractValue(AbstractValueFactory factory) {
-      // TODO(b/150835624): Update once there is an abstract value to represent dex item based
-      //  strings.
-      return UnknownValue.getInstance();
+      return factory.createSingleDexItemBasedStringValue(value, nameComputationInfo);
     }
 
     @Override
diff --git a/src/main/java/com/android/tools/r8/graph/DirectMappedDexApplication.java b/src/main/java/com/android/tools/r8/graph/DirectMappedDexApplication.java
index 4f38fb2..86377b3 100644
--- a/src/main/java/com/android/tools/r8/graph/DirectMappedDexApplication.java
+++ b/src/main/java/com/android/tools/r8/graph/DirectMappedDexApplication.java
@@ -13,6 +13,7 @@
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.Timing;
 import com.google.common.collect.ImmutableList;
+import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.IdentityHashMap;
@@ -58,6 +59,10 @@
     return allClasses.values();
   }
 
+  public List<DexClasspathClass> classpathClasses() {
+    return classpathClasses;
+  }
+
   @Override
   List<DexProgramClass> programClasses() {
     return programClasses;
@@ -68,11 +73,6 @@
   }
 
   @Override
-  public List<DexClasspathClass> classpathClasses() {
-    return classpathClasses;
-  }
-
-  @Override
   public DexClass definitionFor(DexType type) {
     assert type.isClassType() : "Cannot lookup definition for type: " + type;
     return allClasses.get(type);
@@ -181,12 +181,16 @@
 
   public static class Builder extends DexApplication.Builder<Builder> {
 
+    private ImmutableList<DexClasspathClass> classpathClasses;
     private ImmutableList<DexLibraryClass> libraryClasses;
 
+    private final List<DexClasspathClass> pendingClasspathClasses = new ArrayList<>();
+
     Builder(LazyLoadedDexApplication application) {
       super(application);
       // As a side-effect, this will force-load all classes.
       AllClasses allClasses = application.loadAllClasses();
+      classpathClasses = allClasses.getClasspathClasses();
       libraryClasses = allClasses.getLibraryClasses();
       replaceProgramClasses(allClasses.getProgramClasses());
       replaceClasspathClasses(allClasses.getClasspathClasses());
@@ -194,14 +198,58 @@
 
     private Builder(DirectMappedDexApplication application) {
       super(application);
+      classpathClasses = application.classpathClasses;
       libraryClasses = application.libraryClasses;
     }
 
     @Override
+    public boolean isDirect() {
+      return true;
+    }
+
+    @Override
+    public Builder asDirect() {
+      return this;
+    }
+
+    @Override
     Builder self() {
       return this;
     }
 
+    public Builder addClasspathClass(DexClasspathClass clazz) {
+      pendingClasspathClasses.add(clazz);
+      return self();
+    }
+
+    public Builder addClasspathClasses(Collection<DexClasspathClass> classes) {
+      pendingClasspathClasses.addAll(classes);
+      return self();
+    }
+
+    private void commitPendingClasspathClasses() {
+      if (!pendingClasspathClasses.isEmpty()) {
+        classpathClasses =
+            ImmutableList.<DexClasspathClass>builder()
+                .addAll(classpathClasses)
+                .addAll(pendingClasspathClasses)
+                .build();
+        pendingClasspathClasses.clear();
+      }
+    }
+
+    public List<DexClasspathClass> getClasspathClasses() {
+      commitPendingClasspathClasses();
+      return classpathClasses;
+    }
+
+    public Builder replaceClasspathClasses(Collection<DexClasspathClass> newClasspathClasses) {
+      assert newClasspathClasses != null;
+      classpathClasses = ImmutableList.copyOf(newClasspathClasses);
+      pendingClasspathClasses.clear();
+      return self();
+    }
+
     public Builder replaceLibraryClasses(Collection<DexLibraryClass> libraryClasses) {
       this.libraryClasses = ImmutableList.copyOf(libraryClasses);
       return self();
diff --git a/src/main/java/com/android/tools/r8/graph/FieldAccessFlags.java b/src/main/java/com/android/tools/r8/graph/FieldAccessFlags.java
index 554df05..4f07887 100644
--- a/src/main/java/com/android/tools/r8/graph/FieldAccessFlags.java
+++ b/src/main/java/com/android/tools/r8/graph/FieldAccessFlags.java
@@ -58,6 +58,11 @@
     return this;
   }
 
+  public static FieldAccessFlags createPublicStaticSynthetic() {
+    return fromSharedAccessFlags(
+        Constants.ACC_PUBLIC | Constants.ACC_STATIC | Constants.ACC_SYNTHETIC);
+  }
+
   public static FieldAccessFlags fromSharedAccessFlags(int access) {
     assert (access & FLAGS) == access;
     return new FieldAccessFlags(access & FLAGS);
diff --git a/src/main/java/com/android/tools/r8/graph/FieldAccessInfoImpl.java b/src/main/java/com/android/tools/r8/graph/FieldAccessInfoImpl.java
index c5f516b..871ed69 100644
--- a/src/main/java/com/android/tools/r8/graph/FieldAccessInfoImpl.java
+++ b/src/main/java/com/android/tools/r8/graph/FieldAccessInfoImpl.java
@@ -79,6 +79,10 @@
     this.readsWithContexts = readsWithContexts;
   }
 
+  public void setWritesWithContexts(AbstractAccessContexts writesWithContexts) {
+    this.writesWithContexts = writesWithContexts;
+  }
+
   @Override
   public int getNumberOfReadContexts() {
     return readsWithContexts.getNumberOfAccessContexts();
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 ca9a57a..2f6c224 100644
--- a/src/main/java/com/android/tools/r8/graph/GraphLens.java
+++ b/src/main/java/com/android/tools/r8/graph/GraphLens.java
@@ -627,7 +627,7 @@
         // that they can be mapped back to the original program.
         DexField originalField = getOriginalFieldSignature(field.getReference());
         assert originalFields.contains(originalField)
-                || isD8R8SynthesizedField(originalField, appView)
+                || isD8R8SynthesizedField(field.getReference(), appView)
             : "Unable to map field `"
                 + field.getReference().toSourceString()
                 + "` back to original program";
@@ -654,7 +654,7 @@
     if (field.getName().toSourceString().equals(CLASS_ID_FIELD_NAME)) {
       return true;
     }
-    if (appView.getSyntheticItems().isSyntheticClass(field.getHolderType())
+    if (appView.getSyntheticItems().isNonLegacySynthetic(field.getHolderType())
         && field.getName().toSourceString().equals(LAMBDA_INSTANCE_FIELD_NAME)) {
       return true;
     }
diff --git a/src/main/java/com/android/tools/r8/graph/LazyLoadedDexApplication.java b/src/main/java/com/android/tools/r8/graph/LazyLoadedDexApplication.java
index ba240f6..7005e06 100644
--- a/src/main/java/com/android/tools/r8/graph/LazyLoadedDexApplication.java
+++ b/src/main/java/com/android/tools/r8/graph/LazyLoadedDexApplication.java
@@ -54,12 +54,6 @@
   }
 
   @Override
-  List<DexClasspathClass> classpathClasses() {
-    classpathClasses.forceLoad(t -> true);
-    return classpathClasses.getAllClasses();
-  }
-
-  @Override
   public DexClass definitionFor(DexType type) {
     assert type.isClassType() : "Cannot lookup definition for type: " + type;
     DexClass clazz = null;
diff --git a/src/main/java/com/android/tools/r8/graph/MethodAccessFlags.java b/src/main/java/com/android/tools/r8/graph/MethodAccessFlags.java
index 7d6eac0..4a5c689 100644
--- a/src/main/java/com/android/tools/r8/graph/MethodAccessFlags.java
+++ b/src/main/java/com/android/tools/r8/graph/MethodAccessFlags.java
@@ -163,6 +163,10 @@
     set(Constants.ACC_NATIVE);
   }
 
+  public void unsetNative() {
+    unset(Constants.ACC_NATIVE);
+  }
+
   public boolean isAbstract() {
     return isSet(Constants.ACC_ABSTRACT);
   }
@@ -225,6 +229,11 @@
       super(MethodAccessFlags.fromSharedAccessFlags(0, false));
     }
 
+    public Builder setBridge() {
+      flags.setBridge();
+      return this;
+    }
+
     public Builder setConstructor() {
       flags.setConstructor();
       return this;
diff --git a/src/main/java/com/android/tools/r8/graph/ProgramPackageCollection.java b/src/main/java/com/android/tools/r8/graph/ProgramPackageCollection.java
index 5d53ac0..b58a7b8 100644
--- a/src/main/java/com/android/tools/r8/graph/ProgramPackageCollection.java
+++ b/src/main/java/com/android/tools/r8/graph/ProgramPackageCollection.java
@@ -35,6 +35,11 @@
         .add(clazz);
   }
 
+  public boolean contains(DexProgramClass clazz) {
+    ProgramPackage pkg = packages.get(clazz.getType().getPackageDescriptor());
+    return pkg != null && pkg.contains(clazz);
+  }
+
   public boolean isEmpty() {
     return packages.isEmpty();
   }
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 ed42622..7441412 100644
--- a/src/main/java/com/android/tools/r8/graph/PrunedItems.java
+++ b/src/main/java/com/android/tools/r8/graph/PrunedItems.java
@@ -38,6 +38,10 @@
     return removedClasses.isEmpty() && additionalPinnedItems.isEmpty();
   }
 
+  public boolean isRemoved(DexType type) {
+    return removedClasses.contains(type);
+  }
+
   public DexApplication getPrunedApp() {
     return prunedApp;
   }
diff --git a/src/main/java/com/android/tools/r8/graph/classmerging/StaticallyMergedClasses.java b/src/main/java/com/android/tools/r8/graph/classmerging/StaticallyMergedClasses.java
deleted file mode 100644
index 9aabf18..0000000
--- a/src/main/java/com/android/tools/r8/graph/classmerging/StaticallyMergedClasses.java
+++ /dev/null
@@ -1,74 +0,0 @@
-// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-
-package com.android.tools.r8.graph.classmerging;
-
-import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.DexProgramClass;
-import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.shaking.AppInfoWithLiveness;
-import com.android.tools.r8.utils.collections.BidirectionalManyToOneHashMap;
-import com.android.tools.r8.utils.collections.BidirectionalManyToOneMap;
-import com.android.tools.r8.utils.collections.EmptyBidirectionalOneToOneMap;
-import com.android.tools.r8.utils.collections.MutableBidirectionalManyToOneMap;
-import java.util.Set;
-import java.util.function.BiConsumer;
-
-public class StaticallyMergedClasses implements MergedClasses {
-
-  private final BidirectionalManyToOneMap<DexType, DexType> mergedClasses;
-
-  public StaticallyMergedClasses(BidirectionalManyToOneMap<DexType, DexType> mergedClasses) {
-    this.mergedClasses = mergedClasses;
-  }
-
-  public static StaticallyMergedClasses empty() {
-    return new StaticallyMergedClasses(new EmptyBidirectionalOneToOneMap<>());
-  }
-
-  public static Builder builder() {
-    return new Builder();
-  }
-
-  @Override
-  public void forEachMergeGroup(BiConsumer<Set<DexType>, DexType> consumer) {
-    mergedClasses.forEachManyToOneMapping(consumer);
-  }
-
-  @Override
-  public boolean hasBeenMergedIntoDifferentType(DexType type) {
-    return false;
-  }
-
-  @Override
-  public boolean isMergeTarget(DexType type) {
-    // Intentionally returns false since static class merging technically doesn't merge any classes,
-    // it only moves static members.
-    return false;
-  }
-
-  @Override
-  public boolean verifyAllSourcesPruned(AppView<AppInfoWithLiveness> appView) {
-    return true;
-  }
-
-  public static class Builder {
-
-    private final MutableBidirectionalManyToOneMap<DexType, DexType> mergedClasses =
-        new BidirectionalManyToOneHashMap<>();
-
-    private Builder() {}
-
-    public void recordMerge(DexProgramClass source, DexProgramClass target) {
-      for (DexType previousSource : mergedClasses.removeValue(source.getType())) {
-        mergedClasses.put(previousSource, target.getType());
-      }
-      mergedClasses.put(source.getType(), target.getType());
-    }
-
-    public StaticallyMergedClasses build() {
-      return new StaticallyMergedClasses(mergedClasses);
-    }
-  }
-}
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/PreserveMethodCharacteristics.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/PreserveMethodCharacteristics.java
index 3adb35c..fcafba0 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/PreserveMethodCharacteristics.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/PreserveMethodCharacteristics.java
@@ -89,7 +89,7 @@
 
     public boolean tryAdd(AppView<AppInfoWithLiveness> appView, DexProgramClass clazz) {
       Map<DexMethodSignature, MethodCharacteristics> newMethods = new HashMap<>();
-      for (DexEncodedMethod method : clazz.methods()) {
+      for (DexEncodedMethod method : clazz.methods(this::isSubjectToMethodMerging)) {
         DexMethodSignature signature = method.getSignature();
         MethodCharacteristics existingCharacteristics = methodMap.get(signature);
         MethodCharacteristics methodCharacteristics = MethodCharacteristics.create(appView, method);
@@ -105,6 +105,14 @@
       group.add(clazz);
       return true;
     }
+
+    private boolean isSubjectToMethodMerging(DexEncodedMethod method) {
+      if (method.isStatic() || (method.isPrivate() && !method.isInstanceInitializer())) {
+        // Static and private instance methods can easily be renamed and do not require merging.
+        return false;
+      }
+      return true;
+    }
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/PreventMethodImplementation.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/PreventMethodImplementation.java
index dcbd291..c7c57c8 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/PreventMethodImplementation.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/PreventMethodImplementation.java
@@ -79,7 +79,7 @@
 
     DexMethodSignatureSet getOrComputeSignatures(DexType type) {
       DexClass clazz = appView.definitionFor(type);
-      return clazz != null ? getOrComputeSignatures(clazz) : null;
+      return clazz != null ? getOrComputeSignatures(clazz) : DexMethodSignatureSet.create();
     }
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/TrivialFieldAccessReprocessor.java b/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/TrivialFieldAccessReprocessor.java
index 1e93d1b..899fa2b 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/TrivialFieldAccessReprocessor.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/TrivialFieldAccessReprocessor.java
@@ -4,8 +4,10 @@
 
 package com.android.tools.r8.ir.analysis.fieldaccess;
 
-
+import com.android.tools.r8.graph.AbstractAccessContexts;
+import com.android.tools.r8.graph.AbstractAccessContexts.ConcreteAccessContexts;
 import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexClassAndField;
 import com.android.tools.r8.graph.DexEncodedField;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexField;
@@ -14,8 +16,11 @@
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.FieldAccessInfo;
 import com.android.tools.r8.graph.FieldAccessInfoCollection;
+import com.android.tools.r8.graph.FieldResolutionResult.SuccessfulFieldResolutionResult;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.graph.UseRegistry;
+import com.android.tools.r8.ir.analysis.type.ClassTypeElement;
+import com.android.tools.r8.ir.analysis.type.ReferenceTypeElement;
 import com.android.tools.r8.ir.analysis.value.AbstractValue;
 import com.android.tools.r8.ir.analysis.value.SingleFieldValue;
 import com.android.tools.r8.ir.analysis.value.SingleValue;
@@ -27,7 +32,9 @@
 import com.android.tools.r8.utils.Timing;
 import com.android.tools.r8.utils.collections.ProgramMethodSet;
 import com.google.common.collect.Sets;
+import java.util.Map;
 import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.ExecutorService;
 
@@ -37,7 +44,14 @@
   private final PostMethodProcessor.Builder postMethodProcessorBuilder;
 
   /** Updated concurrently from {@link #processClass(DexProgramClass)}. */
-  private final Set<DexEncodedField> fieldsOfInterest = Sets.newConcurrentHashSet();
+  private final Map<DexEncodedField, AbstractAccessContexts> readFields = new ConcurrentHashMap<>();
+
+  /** Updated concurrently from {@link #processClass(DexProgramClass)}. */
+  private final Map<DexEncodedField, AbstractAccessContexts> writtenFields =
+      new ConcurrentHashMap<>();
+
+  /** Updated concurrently from {@link #processClass(DexProgramClass)}. */
+  private final Set<DexEncodedField> constantFields = Sets.newConcurrentHashSet();
 
   /** Updated concurrently from {@link #processClass(DexProgramClass)}. */
   private final ProgramMethodSet methodsToReprocess = ProgramMethodSet.createConcurrent();
@@ -58,38 +72,34 @@
     assert feedback.noUpdatesLeft();
 
     timing.begin("Compute fields of interest");
-    computeFieldsOfInterest();
+    computeConstantFields();
     timing.end(); // Compute fields of interest
 
-    if (fieldsOfInterest.isEmpty()) {
-      timing.end(); // Trivial field accesses analysis
-      return;
-    }
-
     timing.begin("Enqueue methods for reprocessing");
     enqueueMethodsForReprocessing(appInfo, executorService);
     timing.end(); // Enqueue methods for reprocessing
 
-    timing.begin("Clear reads from fields of interest");
-    clearReadsFromFieldsOfInterest(appInfo);
+    timing.begin("Clear reads and writes from fields of interest");
+    clearReadsAndWritesFromFieldsOfInterest(appInfo);
     timing.end(); // Clear reads from fields of interest
-
     timing.end(); // Trivial field accesses analysis
 
-    fieldsOfInterest.forEach(OptimizationFeedbackSimple.getInstance()::markFieldAsDead);
+    constantFields.forEach(OptimizationFeedbackSimple.getInstance()::markFieldAsDead);
+    readFields.keySet().forEach(OptimizationFeedbackSimple.getInstance()::markFieldAsDead);
+    writtenFields.keySet().forEach(OptimizationFeedbackSimple.getInstance()::markFieldAsDead);
   }
 
-  private void computeFieldsOfInterest() {
+  private void computeConstantFields() {
     for (DexProgramClass clazz : appView.appInfo().classes()) {
       for (DexEncodedField field : clazz.instanceFields()) {
         if (canOptimizeField(field, appView)) {
-          fieldsOfInterest.add(field);
+          constantFields.add(field);
         }
       }
       if (appView.canUseInitClass() || !clazz.classInitializationMayHaveSideEffects(appView)) {
         for (DexEncodedField field : clazz.staticFields()) {
           if (canOptimizeField(field, appView)) {
-            fieldsOfInterest.add(field);
+            constantFields.add(field);
           }
         }
       }
@@ -97,11 +107,17 @@
     assert verifyNoConstantFieldsOnSynthesizedClasses(appView);
   }
 
-  private void clearReadsFromFieldsOfInterest(AppInfoWithLiveness appInfo) {
+  private void clearReadsAndWritesFromFieldsOfInterest(AppInfoWithLiveness appInfo) {
     FieldAccessInfoCollection<?> fieldAccessInfoCollection = appInfo.getFieldAccessInfoCollection();
-    for (DexEncodedField field : fieldsOfInterest) {
+    for (DexEncodedField field : constantFields) {
       fieldAccessInfoCollection.get(field.field).asMutable().clearReads();
     }
+    for (DexEncodedField field : readFields.keySet()) {
+      fieldAccessInfoCollection.get(field.getReference()).asMutable().clearWrites();
+    }
+    for (DexEncodedField field : writtenFields.keySet()) {
+      fieldAccessInfoCollection.get(field.getReference()).asMutable().clearReads();
+    }
   }
 
   private void enqueueMethodsForReprocessing(
@@ -111,6 +127,8 @@
         appInfo.getSyntheticItems().getPendingSyntheticClasses(),
         this::processClass,
         executorService);
+    processFieldsNeverRead(appInfo);
+    processFieldsNeverWritten(appInfo);
     postMethodProcessorBuilder.put(methodsToReprocess);
   }
 
@@ -146,6 +164,74 @@
     return false;
   }
 
+  private void processFieldsNeverRead(AppInfoWithLiveness appInfo) {
+    FieldAccessInfoCollection<?> fieldAccessInfoCollection = appInfo.getFieldAccessInfoCollection();
+    writtenFields
+        .entrySet()
+        .removeIf(
+            entry ->
+                !entry.getValue().isConcrete()
+                    || !canOptimizeOnlyReadOrWrittenField(
+                        entry.getKey(), true, fieldAccessInfoCollection));
+    writtenFields.forEach(
+        (field, contexts) -> {
+          assert !readFields.containsKey(field);
+          fieldAccessInfoCollection.get(field.getReference()).asMutable().clearReads();
+          methodsToReprocess.addAll(
+              contexts.asConcrete().getAccessesWithContexts().values().iterator().next());
+        });
+  }
+
+  private void processFieldsNeverWritten(AppInfoWithLiveness appInfo) {
+    FieldAccessInfoCollection<?> fieldAccessInfoCollection = appInfo.getFieldAccessInfoCollection();
+    readFields
+        .entrySet()
+        .removeIf(
+            entry ->
+                !entry.getValue().isConcrete()
+                    || !canOptimizeOnlyReadOrWrittenField(
+                        entry.getKey(), false, fieldAccessInfoCollection));
+    readFields.forEach(
+        (field, contexts) -> {
+          assert !writtenFields.containsKey(field);
+          methodsToReprocess.addAll(
+              contexts.asConcrete().getAccessesWithContexts().values().iterator().next());
+        });
+  }
+
+  private boolean canOptimizeOnlyReadOrWrittenField(
+      DexEncodedField field,
+      boolean isWrite,
+      FieldAccessInfoCollection<?> fieldAccessInfoCollection) {
+    assert !appView.appInfo().isPinned(field);
+    FieldAccessInfo fieldAccessInfo = fieldAccessInfoCollection.get(field.getReference());
+    if (fieldAccessInfo == null) {
+      assert false
+          : "Expected program field with concrete accesses to be present in field access "
+              + "collection";
+      return false;
+    }
+
+    if (fieldAccessInfo.hasReflectiveAccess()
+        || fieldAccessInfo.isAccessedFromMethodHandle()
+        || fieldAccessInfo.isReadFromAnnotation()) {
+      return false;
+    }
+
+    if (isWrite && field.getType().isReferenceType()) {
+      ReferenceTypeElement fieldType = field.getTypeElement(appView).asReferenceType();
+      ClassTypeElement classType =
+          (fieldType.isArrayType() ? fieldType.asArrayType().getBaseType() : fieldType)
+              .asClassType();
+      if (classType != null
+          && appView.appInfo().mayHaveFinalizeMethodDirectlyOrIndirectly(classType)) {
+        return false;
+      }
+    }
+
+    return true;
+  }
+
   private static boolean verifyNoConstantFieldsOnSynthesizedClasses(
       AppView<AppInfoWithLiveness> appView) {
     for (DexProgramClass clazz :
@@ -166,44 +252,95 @@
       this.method = method;
     }
 
-    private boolean registerFieldAccess(DexField field, boolean isStatic) {
-      DexEncodedField encodedField = appView.appInfo().resolveField(field).getResolvedField();
-      if (encodedField != null) {
-        // We cannot remove references from pass through functions.
-        if (appView.isCfByteCodePassThrough(method.getDefinition())) {
-          fieldsOfInterest.remove(encodedField);
-          return true;
-        }
-        if (encodedField.isStatic() == isStatic) {
-          if (fieldsOfInterest.contains(encodedField)) {
-            methodsToReprocess.add(method);
+    private void registerFieldAccess(DexField reference, boolean isStatic, boolean isWrite) {
+      SuccessfulFieldResolutionResult resolutionResult =
+          appView.appInfo().resolveField(reference).asSuccessfulResolution();
+      if (resolutionResult == null) {
+        return;
+      }
+
+      DexClassAndField field = resolutionResult.getResolutionPair();
+      DexEncodedField definition = field.getDefinition();
+
+      // Record access.
+      if (field.isProgramField() && appView.appInfo().mayPropagateValueFor(field)) {
+        if (field.getAccessFlags().isStatic() == isStatic) {
+          if (isWrite) {
+            recordFieldAccessContext(definition, writtenFields, readFields);
+          } else {
+            recordFieldAccessContext(definition, readFields, writtenFields);
           }
         } else {
-          // Should generally not happen.
-          fieldsOfInterest.remove(encodedField);
+          destroyFieldAccessContexts(definition);
         }
       }
-      return true;
+
+      // We cannot remove references from pass through functions.
+      if (appView.isCfByteCodePassThrough(method.getDefinition())) {
+        constantFields.remove(definition);
+        return;
+      }
+
+      if (definition.isStatic() == isStatic) {
+        if (constantFields.contains(definition)) {
+          methodsToReprocess.add(method);
+        }
+      } else {
+        // Should generally not happen.
+        constantFields.remove(definition);
+      }
     }
 
-    @Override
-    public void registerInstanceFieldWrite(DexField field) {
-      registerFieldAccess(field, false);
+    private void recordFieldAccessContext(
+        DexEncodedField field,
+        Map<DexEncodedField, AbstractAccessContexts> fieldAccesses,
+        Map<DexEncodedField, AbstractAccessContexts> otherFieldAccesses) {
+      synchronized (field) {
+        AbstractAccessContexts otherAccessContexts =
+            otherFieldAccesses.getOrDefault(field, AbstractAccessContexts.empty());
+        if (otherAccessContexts.isBottom()) {
+          // Only read or written.
+          AbstractAccessContexts accessContexts =
+              fieldAccesses.computeIfAbsent(field, ignore -> new ConcreteAccessContexts());
+          assert accessContexts.isConcrete();
+          accessContexts.asConcrete().recordAccess(field.getReference(), method);
+        } else if (!otherAccessContexts.isTop()) {
+          // Now both read and written.
+          fieldAccesses.put(field, AbstractAccessContexts.unknown());
+          otherFieldAccesses.put(field, AbstractAccessContexts.unknown());
+        } else {
+          // Already read and written.
+          assert fieldAccesses.getOrDefault(field, AbstractAccessContexts.empty()).isTop();
+          assert otherFieldAccesses.getOrDefault(field, AbstractAccessContexts.empty()).isTop();
+        }
+      }
+    }
+
+    private void destroyFieldAccessContexts(DexEncodedField field) {
+      synchronized (field) {
+        readFields.put(field, AbstractAccessContexts.unknown());
+        writtenFields.put(field, AbstractAccessContexts.unknown());
+      }
     }
 
     @Override
     public void registerInstanceFieldRead(DexField field) {
-      registerFieldAccess(field, false);
+      registerFieldAccess(field, false, false);
+    }
+
+    @Override
+    public void registerInstanceFieldWrite(DexField field) {
+      registerFieldAccess(field, false, true);
     }
 
     @Override
     public void registerStaticFieldRead(DexField field) {
-      registerFieldAccess(field, true);
+      registerFieldAccess(field, true, false);
     }
 
     @Override
     public void registerStaticFieldWrite(DexField field) {
-      registerFieldAccess(field, true);
+      registerFieldAccess(field, true, true);
     }
 
     @Override
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/StaticFieldValueAnalysis.java b/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/StaticFieldValueAnalysis.java
index 281215f..fc01602 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/StaticFieldValueAnalysis.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/StaticFieldValueAnalysis.java
@@ -12,9 +12,6 @@
 import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexClassAndMethod;
 import com.android.tools.r8.graph.DexEncodedField;
-import com.android.tools.r8.graph.DexField;
-import com.android.tools.r8.graph.DexItemFactory;
-import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.DexValue;
 import com.android.tools.r8.graph.DexValue.DexValueNull;
 import com.android.tools.r8.ir.analysis.type.ClassTypeElement;
@@ -26,6 +23,7 @@
 import com.android.tools.r8.ir.analysis.value.NullOrAbstractValue;
 import com.android.tools.r8.ir.analysis.value.ObjectState;
 import com.android.tools.r8.ir.analysis.value.SingleFieldValue;
+import com.android.tools.r8.ir.analysis.value.UnknownValue;
 import com.android.tools.r8.ir.code.ArrayPut;
 import com.android.tools.r8.ir.code.FieldInstruction;
 import com.android.tools.r8.ir.code.IRCode;
@@ -40,10 +38,13 @@
 import com.android.tools.r8.ir.optimize.info.field.InstanceFieldInitializationInfoCollection;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.utils.Timing;
+import java.util.IdentityHashMap;
+import java.util.Map;
 
 public class StaticFieldValueAnalysis extends FieldValueAnalysis {
 
   private final StaticFieldValues.Builder builder;
+  private final Map<Value, AbstractValue> computedValues = new IdentityHashMap<>();
 
   private StaticFieldValueAnalysis(
       AppView<AppInfoWithLiveness> appView, IRCode code, OptimizationFeedback feedback) {
@@ -63,7 +64,7 @@
     timing.begin("Analyze class initializer");
     StaticFieldValues result =
         new StaticFieldValueAnalysis(appView.withLiveness(), code, feedback)
-            .analyze(classInitializerDefaultsResult, code.context().getHolderType());
+            .analyze(classInitializerDefaultsResult);
     timing.end();
     return result;
   }
@@ -78,8 +79,7 @@
     return this;
   }
 
-  StaticFieldValues analyze(
-      ClassInitializerDefaultsResult classInitializerDefaultsResult, DexType holderType) {
+  StaticFieldValues analyze(ClassInitializerDefaultsResult classInitializerDefaultsResult) {
     computeFieldOptimizationInfo(classInitializerDefaultsResult);
     return builder.build();
   }
@@ -218,16 +218,41 @@
       return null;
     }
     assert !value.hasAliasedValue();
-    if (isEnumValuesArray(value)) {
+    if (value.isPhi()) {
+      return null;
+    }
+    if (value.definition.isNewArrayEmpty()) {
       return computeSingleEnumFieldValueForValuesArray(value);
     }
-    return computeSingleEnumFieldValueForInstance(value);
+    if (value.definition.isNewInstance()) {
+      return computeSingleEnumFieldValueForInstance(value);
+    }
+    return null;
   }
 
   private SingleFieldValue computeSingleEnumFieldValueForValuesArray(Value value) {
-    if (!value.isDefinedByInstructionSatisfying(Instruction::isNewArrayEmpty)) {
+    if (!value.definition.isNewArrayEmpty()) {
       return null;
     }
+    AbstractValue valuesValue = computedValues.get(value);
+    if (valuesValue != null) {
+      // This implicitely answers null if the value could not get computed.
+      if (valuesValue.isSingleFieldValue()) {
+        SingleFieldValue fieldValue = valuesValue.asSingleFieldValue();
+        if (fieldValue.getState().isEnumValuesObjectState()) {
+          return fieldValue;
+        }
+      }
+      return null;
+    }
+    SingleFieldValue singleFieldValue = internalComputeSingleEnumFieldValueForValuesArray(value);
+    computedValues.put(
+        value, singleFieldValue == null ? UnknownValue.getInstance() : singleFieldValue);
+    return singleFieldValue;
+  }
+
+  private SingleFieldValue internalComputeSingleEnumFieldValueForValuesArray(Value value) {
+    assert value.isDefinedByInstructionSatisfying(Instruction::isNewArrayEmpty);
 
     NewArrayEmpty newArrayEmpty = value.definition.asNewArrayEmpty();
     if (newArrayEmpty.type.toBaseType(appView.dexItemFactory()) != context.getHolder().type) {
@@ -267,7 +292,9 @@
             // We need the state of all fields for the analysis to be valuable.
             return null;
           }
-          assert verifyValuesArrayIndexMatchesOrdinal(index, objectState);
+          if (!valuesArrayIndexMatchesOrdinal(index, objectState)) {
+            return null;
+          }
           if (valuesState[index] != null) {
             return null;
           }
@@ -326,24 +353,25 @@
     return ObjectState.empty();
   }
 
-  private boolean verifyValuesArrayIndexMatchesOrdinal(int ordinal, ObjectState objectState) {
+  private boolean valuesArrayIndexMatchesOrdinal(int ordinal, ObjectState objectState) {
     DexEncodedField ordinalField =
         appView
             .appInfo()
             .resolveField(appView.dexItemFactory().enumMembers.ordinalField, context)
             .getResolvedField();
-    assert ordinalField != null;
+    if (ordinalField == null) {
+      return false;
+    }
     AbstractValue ordinalState = objectState.getAbstractFieldValue(ordinalField);
-    assert ordinalState != null;
-    assert ordinalState.isSingleNumberValue();
-    assert ordinalState.asSingleNumberValue().getIntValue() == ordinal;
-    return true;
+    if (ordinalState == null || !ordinalState.isSingleNumberValue()) {
+      return false;
+    }
+    int intValue = ordinalState.asSingleNumberValue().getIntValue();
+    return intValue == ordinal;
   }
 
   private SingleFieldValue computeSingleEnumFieldValueForInstance(Value value) {
-    if (!value.isDefinedByInstructionSatisfying(Instruction::isNewInstance)) {
-      return null;
-    }
+    assert value.isDefinedByInstructionSatisfying(Instruction::isNewInstance);
 
     NewInstance newInstance = value.definition.asNewInstance();
     // Some enums have direct subclasses, and the subclass is instantiated here.
@@ -459,30 +487,7 @@
   }
 
   private boolean isEnumValuesArray(Value value) {
-    assert context.getHolder().isEnum();
-    DexItemFactory dexItemFactory = appView.dexItemFactory();
-    DexField valuesField =
-        dexItemFactory.createField(
-            context.getHolderType(),
-            context.getHolderType().toArrayType(1, dexItemFactory),
-            dexItemFactory.enumValuesFieldName);
-
-    Value root = value.getAliasedValue();
-    if (root.isPhi()) {
-      return false;
-    }
-
-    Instruction definition = root.definition;
-    if (definition.isNewArrayEmpty()) {
-      for (Instruction user : root.aliasedUsers()) {
-        if (user.isStaticPut() && user.asStaticPut().getField() == valuesField) {
-          return true;
-        }
-      }
-    } else if (definition.isStaticGet()) {
-      return definition.asStaticGet().getField() == valuesField;
-    }
-
-    return false;
+    SingleFieldValue singleFieldValue = computeSingleEnumFieldValueForValuesArray(value);
+    return singleFieldValue != null && singleFieldValue.getState().isEnumValuesObjectState();
   }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/StaticFieldValues.java b/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/StaticFieldValues.java
index b2dcdcd..65e1ea3 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/StaticFieldValues.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/StaticFieldValues.java
@@ -37,17 +37,10 @@
   // All the abstract values stored here may match a pinned field, using them requires therefore
   // to check the field is not pinned or prove it is no longer pinned.
   public static class EnumStaticFieldValues extends StaticFieldValues {
-    private final ImmutableMap<DexField, AbstractValue> enumAbstractValues;
-    private final DexField valuesField;
-    private final AbstractValue valuesAbstractValue;
+    private final ImmutableMap<DexField, ObjectState> enumAbstractValues;
 
-    public EnumStaticFieldValues(
-        ImmutableMap<DexField, AbstractValue> enumAbstractValues,
-        DexField valuesField,
-        AbstractValue valuesAbstractValue) {
+    public EnumStaticFieldValues(ImmutableMap<DexField, ObjectState> enumAbstractValues) {
       this.enumAbstractValues = enumAbstractValues;
-      this.valuesField = valuesField;
-      this.valuesAbstractValue = valuesAbstractValue;
     }
 
     static StaticFieldValues.Builder builder() {
@@ -55,33 +48,38 @@
     }
 
     public static class Builder extends StaticFieldValues.Builder {
-      private final ImmutableMap.Builder<DexField, AbstractValue> enumAbstractValuesBuilder =
+      private final ImmutableMap.Builder<DexField, ObjectState> enumObjectStateBuilder =
           ImmutableMap.builder();
-      private DexField valuesFields;
-      private AbstractValue valuesAbstractValue;
+      private AbstractValue valuesCandidateAbstractValue;
 
       Builder() {}
 
       @Override
       public void recordStaticField(
           DexEncodedField staticField, AbstractValue value, DexItemFactory factory) {
-        // TODO(b/166532388): Stop relying on the values name.
-        if (staticField.getName() == factory.enumValuesFieldName) {
-          valuesFields = staticField.field;
-          valuesAbstractValue = value;
-        } else if (staticField.isEnum()) {
-          enumAbstractValuesBuilder.put(staticField.field, value);
+        if (factory.enumMembers.isValuesFieldCandidate(staticField, staticField.getHolderType())) {
+          if (value.isSingleFieldValue()
+              && value.asSingleFieldValue().getState().isEnumValuesObjectState()) {
+            assert valuesCandidateAbstractValue == null
+                || valuesCandidateAbstractValue.equals(value);
+            valuesCandidateAbstractValue = value;
+            enumObjectStateBuilder.put(staticField.field, value.asSingleFieldValue().getState());
+          }
+        } else if (factory.enumMembers.isEnumField(staticField, staticField.getHolderType())) {
+          if (value.isSingleFieldValue() && !value.asSingleFieldValue().getState().isEmpty()) {
+            enumObjectStateBuilder.put(staticField.field, value.asSingleFieldValue().getState());
+          }
         }
       }
 
       @Override
       public StaticFieldValues build() {
-        ImmutableMap<DexField, AbstractValue> enumAbstractValues =
-            enumAbstractValuesBuilder.build();
-        if (valuesAbstractValue == null && enumAbstractValues.isEmpty()) {
+        ImmutableMap<DexField, ObjectState> enumAbstractValues = enumObjectStateBuilder.build();
+        if (enumAbstractValues.isEmpty()) {
           return EmptyStaticValues.getInstance();
         }
-        return new EnumStaticFieldValues(enumAbstractValues, valuesFields, valuesAbstractValue);
+        assert enumAbstractValues.values().stream().noneMatch(ObjectState::isEmpty);
+        return new EnumStaticFieldValues(enumAbstractValues);
       }
     }
 
@@ -96,20 +94,7 @@
     }
 
     public ObjectState getObjectStateForPossiblyPinnedField(DexField field) {
-      AbstractValue fieldValue = enumAbstractValues.get(field);
-      if (fieldValue == null || fieldValue.isZero()) {
-        return null;
-      }
-      if (fieldValue.isSingleFieldValue()) {
-        return fieldValue.asSingleFieldValue().getState();
-      }
-      assert fieldValue.isUnknown();
-      return ObjectState.empty();
-    }
-
-    public AbstractValue getValuesAbstractValueForPossiblyPinnedField(DexField field) {
-      assert valuesField == field || valuesAbstractValue == null;
-      return valuesAbstractValue;
+      return enumAbstractValues.get(field);
     }
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedMessageLiteBuilderShrinker.java b/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedMessageLiteBuilderShrinker.java
index c374875..c5f72a3 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedMessageLiteBuilderShrinker.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedMessageLiteBuilderShrinker.java
@@ -266,7 +266,6 @@
       PredicateSet<DexType> alwaysClassInline,
       Set<DexType> neverMergeClassVertically,
       Set<DexType> neverMergeClassHorizontally,
-      Set<DexType> neverMergeStaticClassHorizontally,
       Set<DexMethod> alwaysInline,
       Set<DexMethod> bypassClinitforInlining) {
     new RootSetExtension(
@@ -274,7 +273,6 @@
             alwaysClassInline,
             neverMergeClassVertically,
             neverMergeClassHorizontally,
-            neverMergeStaticClassHorizontally,
             alwaysInline,
             bypassClinitforInlining)
         .extend(subtypingInfo);
@@ -393,7 +391,6 @@
     private final PredicateSet<DexType> alwaysClassInline;
     private final Set<DexType> neverMergeClassVertically;
     private final Set<DexType> neverMergeClassHorizontally;
-    private final Set<DexType> neverMergeStaticClassHorizontally;
 
     private final Set<DexMethod> alwaysInline;
     private final Set<DexMethod> bypassClinitforInlining;
@@ -403,7 +400,6 @@
         PredicateSet<DexType> alwaysClassInline,
         Set<DexType> neverMergeClassVertically,
         Set<DexType> neverMergeClassHorizontally,
-        Set<DexType> neverMergeStaticClassHorizontally,
         Set<DexMethod> alwaysInline,
         Set<DexMethod> bypassClinitforInlining) {
       this.appView = appView;
@@ -411,7 +407,6 @@
       this.alwaysClassInline = alwaysClassInline;
       this.neverMergeClassVertically = neverMergeClassVertically;
       this.neverMergeClassHorizontally = neverMergeClassHorizontally;
-      this.neverMergeStaticClassHorizontally = neverMergeStaticClassHorizontally;
       this.alwaysInline = alwaysInline;
       this.bypassClinitforInlining = bypassClinitforInlining;
     }
@@ -478,7 +473,6 @@
     private void neverMergeClass(DexType type) {
       neverMergeClassVertically.add(type);
       neverMergeClassHorizontally.add(type);
-      neverMergeStaticClassHorizontally.add(type);
     }
   }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/value/AbstractValue.java b/src/main/java/com/android/tools/r8/ir/analysis/value/AbstractValue.java
index e53f27b..d45bf07 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/value/AbstractValue.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/value/AbstractValue.java
@@ -84,6 +84,14 @@
     return null;
   }
 
+  public boolean isSingleDexItemBasedStringValue() {
+    return false;
+  }
+
+  public SingleDexItemBasedStringValue asSingleDexItemBasedStringValue() {
+    return null;
+  }
+
   public boolean isUnknown() {
     return false;
   }
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/value/AbstractValueFactory.java b/src/main/java/com/android/tools/r8/ir/analysis/value/AbstractValueFactory.java
index d242202..58a44a3 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/value/AbstractValueFactory.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/value/AbstractValueFactory.java
@@ -5,8 +5,10 @@
 package com.android.tools.r8.ir.analysis.value;
 
 import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.graph.DexReference;
 import com.android.tools.r8.graph.DexString;
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.naming.dexitembasedstring.NameComputationInfo;
 import java.util.concurrent.ConcurrentHashMap;
 
 public class AbstractValueFactory {
@@ -38,4 +40,9 @@
   public SingleStringValue createSingleStringValue(DexString string) {
     return singleStringValues.computeIfAbsent(string, SingleStringValue::new);
   }
+
+  public SingleDexItemBasedStringValue createSingleDexItemBasedStringValue(
+      DexReference reference, NameComputationInfo<?> nameComputationInfo) {
+    return new SingleDexItemBasedStringValue(reference, nameComputationInfo);
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/value/EmptyObjectState.java b/src/main/java/com/android/tools/r8/ir/analysis/value/EmptyObjectState.java
index db6be42..f8056dc 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/value/EmptyObjectState.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/value/EmptyObjectState.java
@@ -6,8 +6,10 @@
 
 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.GraphLens;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import java.util.function.BiConsumer;
 
 public class EmptyObjectState extends ObjectState {
 
@@ -20,6 +22,11 @@
   }
 
   @Override
+  public void forEachAbstractFieldValue(BiConsumer<DexField, AbstractValue> consumer) {
+    // Intentionally empty.
+  }
+
+  @Override
   public AbstractValue getAbstractFieldValue(DexEncodedField field) {
     return UnknownValue.getInstance();
   }
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/value/EnumValuesObjectState.java b/src/main/java/com/android/tools/r8/ir/analysis/value/EnumValuesObjectState.java
index 2d8ddc2..13686eb 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/value/EnumValuesObjectState.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/value/EnumValuesObjectState.java
@@ -6,10 +6,12 @@
 
 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.GraphLens;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import java.util.Arrays;
 import java.util.Objects;
+import java.util.function.BiConsumer;
 
 public class EnumValuesObjectState extends ObjectState {
 
@@ -22,6 +24,9 @@
   }
 
   @Override
+  public void forEachAbstractFieldValue(BiConsumer<DexField, AbstractValue> consumer) {}
+
+  @Override
   public AbstractValue getAbstractFieldValue(DexEncodedField field) {
     return UnknownValue.getInstance();
   }
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/value/NonEmptyObjectState.java b/src/main/java/com/android/tools/r8/ir/analysis/value/NonEmptyObjectState.java
index fad7303..6aacc3d 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/value/NonEmptyObjectState.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/value/NonEmptyObjectState.java
@@ -11,6 +11,7 @@
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import java.util.IdentityHashMap;
 import java.util.Map;
+import java.util.function.BiConsumer;
 
 public class NonEmptyObjectState extends ObjectState {
 
@@ -24,6 +25,11 @@
   }
 
   @Override
+  public void forEachAbstractFieldValue(BiConsumer<DexField, AbstractValue> consumer) {
+    state.forEach(consumer);
+  }
+
+  @Override
   public AbstractValue getAbstractFieldValue(DexEncodedField field) {
     return state.getOrDefault(field.field, UnknownValue.getInstance());
   }
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/value/ObjectState.java b/src/main/java/com/android/tools/r8/ir/analysis/value/ObjectState.java
index 4e6d970..f0af9b4 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/value/ObjectState.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/value/ObjectState.java
@@ -11,6 +11,7 @@
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import java.util.IdentityHashMap;
 import java.util.Map;
+import java.util.function.BiConsumer;
 
 public abstract class ObjectState {
 
@@ -22,6 +23,8 @@
     return EmptyObjectState.getInstance();
   }
 
+  public abstract void forEachAbstractFieldValue(BiConsumer<DexField, AbstractValue> consumer);
+
   public abstract AbstractValue getAbstractFieldValue(DexEncodedField field);
 
   public abstract boolean isEmpty();
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/value/SingleDexItemBasedStringValue.java b/src/main/java/com/android/tools/r8/ir/analysis/value/SingleDexItemBasedStringValue.java
new file mode 100644
index 0000000..88ea973
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/analysis/value/SingleDexItemBasedStringValue.java
@@ -0,0 +1,107 @@
+// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.analysis.value;
+
+import static com.android.tools.r8.ir.analysis.type.Nullability.definitelyNotNull;
+import static com.android.tools.r8.ir.analysis.type.TypeElement.stringClassType;
+
+import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DebugLocalInfo;
+import com.android.tools.r8.graph.DexReference;
+import com.android.tools.r8.graph.GraphLens;
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
+import com.android.tools.r8.ir.code.BasicBlock.ThrowingInfo;
+import com.android.tools.r8.ir.code.DexItemBasedConstString;
+import com.android.tools.r8.ir.code.IRCode;
+import com.android.tools.r8.ir.code.Instruction;
+import com.android.tools.r8.ir.code.TypeAndLocalInfoSupplier;
+import com.android.tools.r8.ir.code.Value;
+import com.android.tools.r8.naming.dexitembasedstring.NameComputationInfo;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import java.util.Objects;
+
+public class SingleDexItemBasedStringValue extends SingleConstValue {
+
+  private final DexReference item;
+  private final NameComputationInfo<?> nameComputationInfo;
+
+  /** Intentionally package private, use {@link AbstractValueFactory} instead. */
+  SingleDexItemBasedStringValue(DexReference item, NameComputationInfo<?> nameComputationInfo) {
+    this.item = item;
+    this.nameComputationInfo = nameComputationInfo;
+  }
+
+  @Override
+  public boolean isSingleDexItemBasedStringValue() {
+    return true;
+  }
+
+  @Override
+  public SingleDexItemBasedStringValue asSingleDexItemBasedStringValue() {
+    return this;
+  }
+
+  @Override
+  public boolean equals(Object o) {
+    if (o == null || getClass() != o.getClass()) {
+      return false;
+    }
+    SingleDexItemBasedStringValue value = (SingleDexItemBasedStringValue) o;
+    return item == value.item && nameComputationInfo == value.nameComputationInfo;
+  }
+
+  @Override
+  public int hashCode() {
+    return Objects.hash(item, nameComputationInfo);
+  }
+
+  @Override
+  public String toString() {
+    return "DexItemBasedConstString(" + item.toSourceString() + ")";
+  }
+
+  @Override
+  public Instruction createMaterializingInstruction(
+      AppView<? extends AppInfoWithClassHierarchy> appView,
+      IRCode code,
+      TypeAndLocalInfoSupplier info) {
+    TypeElement typeLattice = info.getOutType();
+    DebugLocalInfo debugLocalInfo = info.getLocalInfo();
+    assert typeLattice.isClassType();
+    assert appView
+        .isSubtype(appView.dexItemFactory().stringType, typeLattice.asClassType().getClassType())
+        .isTrue();
+    Value returnedValue =
+        code.createValue(stringClassType(appView, definitelyNotNull()), debugLocalInfo);
+    DexItemBasedConstString instruction =
+        new DexItemBasedConstString(
+            returnedValue,
+            item,
+            nameComputationInfo,
+            ThrowingInfo.defaultForConstString(appView.options()));
+    assert !instruction.instructionInstanceCanThrow();
+    return instruction;
+  }
+
+  @Override
+  public boolean isMaterializableInContext(
+      AppView<AppInfoWithLiveness> appView, ProgramMethod context) {
+    return true;
+  }
+
+  @Override
+  public boolean isMaterializableInAllContexts(AppView<AppInfoWithLiveness> appView) {
+    return true;
+  }
+
+  @Override
+  public SingleValue rewrittenWithLens(AppView<AppInfoWithLiveness> appView, GraphLens lens) {
+    return appView
+        .abstractValueFactory()
+        .createSingleDexItemBasedStringValue(lens.rewriteReference(item), nameComputationInfo);
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/code/DexItemBasedConstString.java b/src/main/java/com/android/tools/r8/ir/code/DexItemBasedConstString.java
index 2c7313e..9c209c9 100644
--- a/src/main/java/com/android/tools/r8/ir/code/DexItemBasedConstString.java
+++ b/src/main/java/com/android/tools/r8/ir/code/DexItemBasedConstString.java
@@ -13,6 +13,7 @@
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.analysis.type.Nullability;
 import com.android.tools.r8.ir.analysis.type.TypeElement;
+import com.android.tools.r8.ir.analysis.value.AbstractValue;
 import com.android.tools.r8.ir.code.BasicBlock.ThrowingInfo;
 import com.android.tools.r8.ir.conversion.CfBuilder;
 import com.android.tools.r8.ir.conversion.DexBuilder;
@@ -20,6 +21,7 @@
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
 import com.android.tools.r8.ir.optimize.InliningConstraints;
 import com.android.tools.r8.naming.dexitembasedstring.NameComputationInfo;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
 
 public class DexItemBasedConstString extends ConstInstruction {
 
@@ -167,4 +169,12 @@
   public boolean instructionMayTriggerMethodInvocation(AppView<?> appView, ProgramMethod context) {
     return false;
   }
+
+  @Override
+  public AbstractValue getAbstractValue(
+      AppView<AppInfoWithLiveness> appView, ProgramMethod context) {
+    return appView
+        .abstractValueFactory()
+        .createSingleDexItemBasedStringValue(item, nameComputationInfo);
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/code/IRCode.java b/src/main/java/com/android/tools/r8/ir/code/IRCode.java
index efc52d1..36eeadf 100644
--- a/src/main/java/com/android/tools/r8/ir/code/IRCode.java
+++ b/src/main/java/com/android/tools/r8/ir/code/IRCode.java
@@ -685,6 +685,7 @@
           values.add(value);
           assert value.uniquePhiUsers().contains(phi);
           assert !phi.hasLocalInfo() || phi.getLocalInfo() == value.getLocalInfo();
+          assert value.isPhi() || value.definition.hasBlock();
         }
       }
       for (Instruction instruction : block.getInstructions()) {
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/ClassConverter.java b/src/main/java/com/android/tools/r8/ir/conversion/ClassConverter.java
index e747891..7d32802 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/ClassConverter.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/ClassConverter.java
@@ -54,9 +54,9 @@
         }
       }
       ThreadUtils.processItems(wave, this::convertClass, executorService);
+      methodProcessor.awaitMethodProcessing();
       classes = deferred;
     }
-    methodProcessor.awaitMethodProcessing();
   }
 
   abstract void convertClass(DexProgramClass clazz);
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 11a9d00..4131111 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
@@ -748,17 +748,15 @@
     if (inliner != null) {
       postMethodProcessorBuilder.put(inliner);
     }
+    if (!options.debug) {
+      new TrivialFieldAccessReprocessor(appView.withLiveness(), postMethodProcessorBuilder)
+          .run(executorService, feedback, timing);
+    }
     if (enumUnboxer != null) {
       enumUnboxer.unboxEnums(postMethodProcessorBuilder, executorService, feedback);
     } else {
       appView.setUnboxedEnums(EnumDataMap.empty());
     }
-
-    if (!options.debug) {
-      new TrivialFieldAccessReprocessor(appView.withLiveness(), postMethodProcessorBuilder)
-          .run(executorService, feedback, timing);
-    }
-
     timing.begin("IR conversion phase 2");
     graphLensForIR = appView.graphLens();
     PostMethodProcessor postMethodProcessor =
@@ -1759,6 +1757,8 @@
     DexEncodedMethod method = code.method();
     // Workaround massive dex2oat memory use for self-recursive methods.
     CodeRewriter.disableDex2OatInliningForSelfRecursiveMethods(appView, code);
+    // Workaround MAX_INT switch issue.
+    codeRewriter.rewriteSwitchForMaxInt(code);
     // Perform register allocation.
     RegisterAllocator registerAllocator = performRegisterAllocation(code, method, timing);
     timing.begin("Build DEX code");
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/NeedsIRDesugarUseRegistry.java b/src/main/java/com/android/tools/r8/ir/conversion/NeedsIRDesugarUseRegistry.java
index c44ce41..d91b447 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/NeedsIRDesugarUseRegistry.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/NeedsIRDesugarUseRegistry.java
@@ -4,12 +4,19 @@
 
 package com.android.tools.r8.ir.conversion;
 
+import static com.android.tools.r8.ir.code.Invoke.Type.DIRECT;
+import static com.android.tools.r8.ir.code.Invoke.Type.INTERFACE;
+import static com.android.tools.r8.ir.code.Invoke.Type.STATIC;
+import static com.android.tools.r8.ir.code.Invoke.Type.SUPER;
+import static com.android.tools.r8.ir.code.Invoke.Type.VIRTUAL;
+
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexCallSite;
 import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.UseRegistry;
+import com.android.tools.r8.ir.code.Invoke.Type;
 import com.android.tools.r8.ir.desugar.BackportedMethodRewriter;
 import com.android.tools.r8.ir.desugar.DesugaredLibraryAPIConverter;
 import com.android.tools.r8.ir.desugar.DesugaredLibraryRetargeter;
@@ -56,14 +63,14 @@
   public void registerInvokeVirtual(DexMethod method) {
     registerBackportedMethodRewriting(method);
     registerLibraryRetargeting(method, false);
-    registerInterfaceMethodRewriting(method, false);
+    registerInterfaceMethodRewriting(method, VIRTUAL);
     registerDesugaredLibraryAPIConverter(method);
   }
 
   @Override
   public void registerInvokeDirect(DexMethod method) {
     registerLibraryRetargeting(method, false);
-    registerInterfaceMethodRewriting(method, false);
+    registerInterfaceMethodRewriting(method, DIRECT);
     registerDesugaredLibraryAPIConverter(method);
   }
 
@@ -80,11 +87,11 @@
     }
   }
 
-  private void registerInterfaceMethodRewriting(DexMethod method, boolean isInvokeSuper) {
+  private void registerInterfaceMethodRewriting(DexMethod method, Type invokeType) {
     if (!needsDesugaring) {
       needsDesugaring =
           interfaceMethodRewriter != null
-              && interfaceMethodRewriter.needsRewriting(method, isInvokeSuper, appView);
+              && interfaceMethodRewriter.needsRewriting(method, invokeType, appView);
     }
   }
 
@@ -109,14 +116,14 @@
     registerTwrCloseResourceRewriting(method);
     registerBackportedMethodRewriting(method);
     registerLibraryRetargeting(method, false);
-    registerInterfaceMethodRewriting(method, false);
+    registerInterfaceMethodRewriting(method, STATIC);
     registerDesugaredLibraryAPIConverter(method);
   }
 
   @Override
   public void registerInvokeInterface(DexMethod method) {
     registerLibraryRetargeting(method, true);
-    registerInterfaceMethodRewriting(method, false);
+    registerInterfaceMethodRewriting(method, INTERFACE);
     registerDesugaredLibraryAPIConverter(method);
   }
 
@@ -137,7 +144,7 @@
   @Override
   public void registerInvokeSuper(DexMethod method) {
     registerLibraryRetargeting(method, false);
-    registerInterfaceMethodRewriting(method, true);
+    registerInterfaceMethodRewriting(method, SUPER);
     registerDesugaredLibraryAPIConverter(method);
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/InterfaceMethodRewriter.java b/src/main/java/com/android/tools/r8/ir/desugar/InterfaceMethodRewriter.java
index d2225e6..1b17172b5 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/InterfaceMethodRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/InterfaceMethodRewriter.java
@@ -4,12 +4,18 @@
 
 package com.android.tools.r8.ir.desugar;
 
+import static com.android.tools.r8.ir.code.Invoke.Type.DIRECT;
+import static com.android.tools.r8.ir.code.Invoke.Type.STATIC;
+import static com.android.tools.r8.ir.code.Invoke.Type.SUPER;
+import static com.android.tools.r8.ir.code.Invoke.Type.VIRTUAL;
 import static com.android.tools.r8.ir.code.Opcodes.INVOKE_CUSTOM;
 import static com.android.tools.r8.ir.code.Opcodes.INVOKE_DIRECT;
 import static com.android.tools.r8.ir.code.Opcodes.INVOKE_INTERFACE;
 import static com.android.tools.r8.ir.code.Opcodes.INVOKE_STATIC;
 import static com.android.tools.r8.ir.code.Opcodes.INVOKE_SUPER;
 import static com.android.tools.r8.ir.code.Opcodes.INVOKE_VIRTUAL;
+import static com.android.tools.r8.ir.desugar.DesugaredLibraryWrapperSynthesizer.TYPE_WRAPPER_SUFFIX;
+import static com.android.tools.r8.ir.desugar.DesugaredLibraryWrapperSynthesizer.VIVIFIED_TYPE_WRAPPER_SUFFIX;
 
 import com.android.tools.r8.DesugarGraphConsumer;
 import com.android.tools.r8.cf.CfVersion;
@@ -47,6 +53,7 @@
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.Instruction;
 import com.android.tools.r8.ir.code.InstructionListIterator;
+import com.android.tools.r8.ir.code.Invoke.Type;
 import com.android.tools.r8.ir.code.InvokeCustom;
 import com.android.tools.r8.ir.code.InvokeDirect;
 import com.android.tools.r8.ir.code.InvokeMethod;
@@ -226,13 +233,10 @@
     return emulatedInterfaces.containsKey(itf);
   }
 
-  public boolean needsRewriting(DexMethod method, boolean isInvokeSuper, AppView<?> appView) {
-    if (isInvokeSuper) {
+  public boolean needsRewriting(DexMethod method, Type invokeType, AppView<?> appView) {
+    if (invokeType == SUPER || invokeType == STATIC || invokeType == DIRECT) {
       DexClass clazz = appView.appInfo().definitionFor(method.getHolderType());
-      if (clazz != null
-          && clazz.isLibraryClass()
-          && clazz.isInterface()
-          && appView.rewritePrefix.hasRewrittenType(clazz.type, appView)) {
+      if (clazz != null && clazz.isInterface()) {
         return true;
       }
     }
@@ -364,6 +368,7 @@
       // This can be a private instance method call. Note that the referenced
       // method is expected to be in the current class since it is private, but desugaring
       // may move some methods or their code into other classes.
+      assert needsRewriting(method, DIRECT, appView);
       instructions.replaceCurrentInstruction(
           new InvokeStatic(
               directTarget.getDefinition().isPrivateMethod()
@@ -377,6 +382,7 @@
           appView.appInfoForDesugaring().lookupMaximallySpecificMethod(clazz, method);
       if (virtualTarget != null) {
         // This is a invoke-direct call to a virtual method.
+        assert needsRewriting(method, DIRECT, appView);
         instructions.replaceCurrentInstruction(
             new InvokeStatic(
                 defaultAsMethodOfCompanionClass(virtualTarget),
@@ -459,6 +465,7 @@
                                         .setStaticTarget(invokedMethod, true)
                                         .setStaticSource(m)
                                         .build()));
+        assert needsRewriting(invokedMethod, STATIC, appView);
         instructions.replaceCurrentInstruction(
             new InvokeStatic(
                 newProgramMethod.getReference(), invoke.outValue(), invoke.arguments()));
@@ -481,6 +488,7 @@
             .resolveMethodOnInterface(clazz, invokedMethod)
             .asSingleResolution();
     if (resolutionResult != null && resolutionResult.getResolvedMethod().isStatic()) {
+      assert needsRewriting(invokedMethod, STATIC, appView);
       instructions.replaceCurrentInstruction(
           new InvokeStatic(
               staticAsMethodOfCompanionClass(resolutionResult.getResolutionPair()),
@@ -507,6 +515,7 @@
     instructions.previous();
     instructions.add(throwInvoke);
     instructions.next();
+    assert needsRewriting(invokedMethod, STATIC, appView);
     instructions.replaceCurrentInstructionWithThrow(
         appView, code, blockIterator, throwInvoke.outValue(), blocksToRemove, affectedValues);
   }
@@ -533,6 +542,7 @@
       //
       // WARNING: This may result in incorrect code on older platforms!
       // Retarget call to an appropriate method of companion class.
+      assert needsRewriting(invokedMethod, SUPER, appView);
       DexMethod amendedMethod = amendDefaultMethod(context.getHolder(), invokedMethod);
       instructions.replaceCurrentInstruction(
           new InvokeStatic(
@@ -548,6 +558,7 @@
           if (target != null && target.getDefinition().isDefaultMethod()) {
             DexClass holder = target.getHolder();
             if (holder.isLibraryClass() && holder.isInterface()) {
+              assert needsRewriting(invokedMethod, SUPER, appView);
               instructions.replaceCurrentInstruction(
                   new InvokeStatic(
                       defaultAsMethodOfCompanionClass(target),
@@ -577,9 +588,11 @@
                     factory.protoWithDifferentFirstParameter(
                         originalCompanionMethod.proto, emulatedItf),
                     originalCompanionMethod.name);
+            assert needsRewriting(invokedMethod, SUPER, appView);
             instructions.replaceCurrentInstruction(
                 new InvokeStatic(companionMethod, invoke.outValue(), invoke.arguments()));
           } else {
+            assert needsRewriting(invokedMethod, SUPER, appView);
             instructions.replaceCurrentInstruction(
                 new InvokeStatic(retargetMethod, invoke.outValue(), invoke.arguments()));
           }
@@ -606,6 +619,7 @@
     if (resolution != null
         && (resolution.getResolvedHolder().isLibraryClass()
             || appView.options().isDesugaredLibraryCompilation())) {
+      assert needsRewriting(invokedMethod, VIRTUAL, appView);
       rewriteCurrentInstructionToEmulatedInterfaceCall(
           emulatedItf, invokedMethod, invoke, instructions);
     }
@@ -940,6 +954,11 @@
     return type.descriptor.toString().endsWith(EMULATE_LIBRARY_CLASS_NAME_SUFFIX + ";");
   }
 
+  public static boolean isTypeWrapper(DexType type) {
+    String name = type.toBinaryName();
+    return name.endsWith(TYPE_WRAPPER_SUFFIX) || name.endsWith(VIVIFIED_TYPE_WRAPPER_SUFFIX);
+  }
+
   // Gets the interface class for a companion class `type`.
   private DexType getInterfaceClassType(DexType type) {
     return getInterfaceClassType(type, factory);
@@ -1274,7 +1293,7 @@
 
   private boolean shouldIgnoreFromReports(DexType missing) {
     return appView.rewritePrefix.hasRewrittenType(missing, appView)
-        || missing.isD8R8SynthesizedClassType()
+        || isTypeWrapper(missing)
         || isCompanionClassType(missing)
         || emulatedInterfaces.containsValue(missing)
         || options.desugaredLibraryConfiguration.getCustomConversions().containsValue(missing)
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/AssumeRemover.java b/src/main/java/com/android/tools/r8/ir/optimize/AssumeRemover.java
index 41f72cb..85ac5b8 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/AssumeRemover.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/AssumeRemover.java
@@ -13,6 +13,7 @@
 import com.android.tools.r8.ir.code.InstructionListIterator;
 import com.android.tools.r8.ir.code.Value;
 import com.google.common.collect.Sets;
+import java.util.Collection;
 import java.util.Set;
 
 /**
@@ -62,6 +63,14 @@
     }
   }
 
+  public void markUnusedAssumeValuesForRemoval(Collection<Value> values) {
+    for (Value value : values) {
+      if (value.isDefinedByInstructionSatisfying(Instruction::isAssume) && !value.hasAnyUsers()) {
+        markForRemoval(value.getDefinition().asAssume());
+      }
+    }
+  }
+
   private void markForRemoval(Assume assumeInstruction) {
     assumeInstructionsToRemove.add(assumeInstruction);
   }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java b/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
index 5112116..3fa8be0 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
@@ -961,7 +961,61 @@
     if (!code.metadata().mayHaveSwitch()) {
       return false;
     }
+    return rewriteSwitchFull(code, switchCaseAnalyzer);
+  }
 
+  public void rewriteSwitchForMaxInt(IRCode code) {
+    if (options.canHaveSwitchMaxIntBug() && code.metadata().mayHaveSwitch()) {
+      // Always rewrite for workaround switch bug.
+      rewriteSwitchForMaxIntOnly(code);
+    }
+  }
+
+  private void rewriteSwitchForMaxIntOnly(IRCode code) {
+    boolean needToSplitCriticalEdges = false;
+    ListIterator<BasicBlock> blocksIterator = code.listIterator();
+    while (blocksIterator.hasNext()) {
+      BasicBlock block = blocksIterator.next();
+      InstructionListIterator iterator = block.listIterator(code);
+      while (iterator.hasNext()) {
+        Instruction instruction = iterator.next();
+        assert !instruction.isStringSwitch();
+        if (instruction.isIntSwitch()) {
+          IntSwitch intSwitch = instruction.asIntSwitch();
+          if (intSwitch.getKey(intSwitch.numberOfKeys() - 1) == Integer.MAX_VALUE) {
+            if (intSwitch.numberOfKeys() == 1) {
+              rewriteSingleKeySwitchToIf(code, block, iterator, intSwitch);
+            } else {
+              IntList newSwitchSequences = new IntArrayList(intSwitch.numberOfKeys() - 1);
+              for (int i = 0; i < intSwitch.numberOfKeys() - 1; i++) {
+                newSwitchSequences.add(intSwitch.getKey(i));
+              }
+              IntList outliers = new IntArrayList(1);
+              outliers.add(Integer.MAX_VALUE);
+              convertSwitchToSwitchAndIfs(
+                  code,
+                  blocksIterator,
+                  block,
+                  iterator,
+                  intSwitch,
+                  ImmutableList.of(newSwitchSequences),
+                  outliers);
+            }
+            needToSplitCriticalEdges = true;
+          }
+        }
+      }
+    }
+
+    // Rewriting of switches introduces new branching structure. It relies on critical edges
+    // being split on the way in but does not maintain this property. We therefore split
+    // critical edges at exit.
+    if (needToSplitCriticalEdges) {
+      code.splitCriticalEdges();
+    }
+  }
+
+  private boolean rewriteSwitchFull(IRCode code, SwitchCaseAnalyzer switchCaseAnalyzer) {
     boolean needToRemoveUnreachableBlocks = false;
     ListIterator<BasicBlock> blocksIterator = code.listIterator();
     while (blocksIterator.hasNext()) {
@@ -1010,6 +1064,29 @@
     return !affectedValues.isEmpty();
   }
 
+  private void rewriteSingleKeySwitchToIf(
+      IRCode code, BasicBlock block, InstructionListIterator iterator, IntSwitch theSwitch) {
+    // Rewrite the switch to an if.
+    int fallthroughBlockIndex = theSwitch.getFallthroughBlockIndex();
+    int caseBlockIndex = theSwitch.targetBlockIndices()[0];
+    if (fallthroughBlockIndex < caseBlockIndex) {
+      block.swapSuccessorsByIndex(fallthroughBlockIndex, caseBlockIndex);
+    }
+    If replacement;
+    if (theSwitch.isIntSwitch() && theSwitch.asIntSwitch().getFirstKey() == 0) {
+      replacement = new If(Type.EQ, theSwitch.value());
+    } else {
+      Instruction labelConst = theSwitch.materializeFirstKey(appView, code);
+      labelConst.setPosition(theSwitch.getPosition());
+      iterator.previous();
+      iterator.add(labelConst);
+      Instruction dummy = iterator.next();
+      assert dummy == theSwitch;
+      replacement = new If(Type.EQ, ImmutableList.of(theSwitch.value(), labelConst.outValue()));
+    }
+    iterator.replaceCurrentInstruction(replacement);
+  }
+
   private void rewriteIntSwitch(
       IRCode code,
       ListIterator<BasicBlock> blockIterator,
@@ -1017,25 +1094,7 @@
       InstructionListIterator iterator,
       IntSwitch theSwitch) {
     if (theSwitch.numberOfKeys() == 1) {
-      // Rewrite the switch to an if.
-      int fallthroughBlockIndex = theSwitch.getFallthroughBlockIndex();
-      int caseBlockIndex = theSwitch.targetBlockIndices()[0];
-      if (fallthroughBlockIndex < caseBlockIndex) {
-        block.swapSuccessorsByIndex(fallthroughBlockIndex, caseBlockIndex);
-      }
-      If replacement;
-      if (theSwitch.isIntSwitch() && theSwitch.asIntSwitch().getFirstKey() == 0) {
-        replacement = new If(Type.EQ, theSwitch.value());
-      } else {
-        Instruction labelConst = theSwitch.materializeFirstKey(appView, code);
-        labelConst.setPosition(theSwitch.getPosition());
-        iterator.previous();
-        iterator.add(labelConst);
-        Instruction dummy = iterator.next();
-        assert dummy == theSwitch;
-        replacement = new If(Type.EQ, ImmutableList.of(theSwitch.value(), labelConst.outValue()));
-      }
-      iterator.replaceCurrentInstruction(replacement);
+      rewriteSingleKeySwitchToIf(code, block, iterator, theSwitch);
       return;
     }
 
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 38c4e6f..7eca624 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
@@ -12,6 +12,7 @@
 import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexClassAndMethod;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexMethod;
@@ -745,6 +746,9 @@
       if (lambdaMerger != null) {
         lambdaMerger.rewriteCodeForInlining(target, code, context, inliningIRProvider);
       }
+      if (options.testing.inlineeIrModifier != null) {
+        options.testing.inlineeIrModifier.accept(code);
+      }
       assert code.isConsistentSSA();
       return new InlineeWithReason(code, reason);
     }
@@ -982,13 +986,8 @@
             continue;
           }
 
-          if (invoke.isInvokeMethodWithReceiver()) {
-            if (iterator.replaceCurrentInstructionByNullCheckIfPossible(appView, context)) {
-              continue;
-            }
-          } else if (invoke.isInvokeStatic()
-              && iterator.replaceCurrentInstructionByInitClassIfPossible(
-                  appView, code, resolutionResult.getResolvedHolder().getType())) {
+          if (tryInlineMethodWithoutSideEffects(
+              code, iterator, invoke, resolutionResult.getResolutionPair(), assumeRemover)) {
             continue;
           }
 
@@ -1121,6 +1120,30 @@
     assert code.isConsistentSSA();
   }
 
+  private boolean tryInlineMethodWithoutSideEffects(
+      IRCode code,
+      InstructionListIterator iterator,
+      InvokeMethod invoke,
+      DexClassAndMethod resolvedMethod,
+      AssumeRemover assumeRemover) {
+    if (invoke.isInvokeMethodWithReceiver()) {
+      if (!iterator.replaceCurrentInstructionByNullCheckIfPossible(appView, code.context())) {
+        return false;
+      }
+    } else if (invoke.isInvokeStatic()) {
+      if (!iterator.replaceCurrentInstructionByInitClassIfPossible(
+          appView, code, resolvedMethod.getHolderType())) {
+        return false;
+      }
+    } else {
+      return false;
+    }
+
+    // Succeeded.
+    assumeRemover.markUnusedAssumeValuesForRemoval(invoke.arguments());
+    return true;
+  }
+
   private boolean containsPotentialCatchHandlerVerificationError(IRCode code) {
     if (availableApiExceptions == null) {
       assert !appView.options().canHaveDalvikCatchHandlerVerificationBug();
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/MemberValuePropagation.java b/src/main/java/com/android/tools/r8/ir/optimize/MemberValuePropagation.java
index d92d375..2d36f99 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/MemberValuePropagation.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/MemberValuePropagation.java
@@ -170,7 +170,9 @@
         assert replacement.outValue() != null;
         current.outValue().replaceUsers(replacement.outValue());
       }
-      if (current.isStaticGet()) {
+      if (current.isInstanceGet()) {
+        iterator.replaceCurrentInstructionByNullCheckIfPossible(appView, code.context());
+      } else if (current.isStaticGet()) {
         StaticGet staticGet = current.asStaticGet();
         iterator.replaceCurrentInstructionByInitClassIfPossible(
             appView, code, staticGet.getField().holder);
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/RedundantFieldLoadElimination.java b/src/main/java/com/android/tools/r8/ir/optimize/RedundantFieldLoadElimination.java
index 10b8b25..f802efe 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/RedundantFieldLoadElimination.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/RedundantFieldLoadElimination.java
@@ -17,6 +17,8 @@
 import com.android.tools.r8.graph.classmerging.VerticallyMergedClasses;
 import com.android.tools.r8.horizontalclassmerging.HorizontallyMergedClasses;
 import com.android.tools.r8.ir.analysis.type.TypeAnalysis;
+import com.android.tools.r8.ir.analysis.value.ObjectState;
+import com.android.tools.r8.ir.analysis.value.SingleFieldValue;
 import com.android.tools.r8.ir.analysis.value.SingleValue;
 import com.android.tools.r8.ir.code.BasicBlock;
 import com.android.tools.r8.ir.code.FieldInstruction;
@@ -235,24 +237,7 @@
                 activeState.putNonFinalInstanceField(fieldAndObject, value);
               }
             } else if (instruction.isStaticGet()) {
-              StaticGet staticGet = instruction.asStaticGet();
-              if (staticGet.outValue().hasLocalInfo()) {
-                continue;
-              }
-              FieldValue replacement = activeState.getStaticFieldValue(reference);
-              if (replacement != null) {
-                replacement.eliminateRedundantRead(it, staticGet);
-              } else {
-                // A field get on a different class can cause <clinit> to run and change static
-                // field values.
-                killNonFinalActiveFields(staticGet);
-                FieldValue value = new ExistingValue(staticGet.value());
-                if (isFinal(field)) {
-                  activeState.putFinalStaticField(reference, value);
-                } else {
-                  activeState.putNonFinalStaticField(reference, value);
-                }
-              }
+              handleStaticGet(it, instruction.asStaticGet(), field);
             } else if (instruction.isStaticPut()) {
               StaticPut staticPut = instruction.asStaticPut();
               // A field put on a different class can cause <clinit> to run and change static
@@ -409,6 +394,52 @@
         });
   }
 
+  private void handleStaticGet(
+      InstructionListIterator instructionIterator, StaticGet staticGet, DexClassAndField field) {
+    if (staticGet.outValue().hasLocalInfo()) {
+      return;
+    }
+    FieldValue replacement = activeState.getStaticFieldValue(field.getReference());
+    if (replacement != null) {
+      replacement.eliminateRedundantRead(instructionIterator, staticGet);
+      return;
+    }
+
+    // A field get on a different class can cause <clinit> to run and change static field values.
+    if (staticGet.instructionMayHaveSideEffects(appView, method)) {
+      killNonFinalActiveFields(staticGet);
+    }
+
+    FieldValue value = new ExistingValue(staticGet.value());
+    if (isFinal(field)) {
+      activeState.putFinalStaticField(field.getReference(), value);
+    } else {
+      activeState.putNonFinalStaticField(field.getReference(), value);
+    }
+
+    if (appView.hasLiveness()) {
+      SingleFieldValue singleFieldValue =
+          field.getDefinition().getOptimizationInfo().getAbstractValue().asSingleFieldValue();
+      if (singleFieldValue != null) {
+        applyObjectState(staticGet.outValue(), singleFieldValue.getState());
+      }
+    }
+  }
+
+  private void applyObjectState(Value value, ObjectState objectState) {
+    objectState.forEachAbstractFieldValue(
+        (field, fieldValue) -> {
+          if (appView.appInfoWithLiveness().mayPropagateValueFor(field)
+              && fieldValue.isSingleValue()) {
+            SingleValue singleFieldValue = fieldValue.asSingleValue();
+            if (singleFieldValue.isMaterializableInContext(appView.withLiveness(), method)) {
+              activeState.putFinalInstanceField(
+                  new FieldAndObject(field, value), new MaterializableValue(singleFieldValue));
+            }
+          }
+        });
+  }
+
   private void killAllNonFinalActiveFields() {
     activeState.clearNonFinalInstanceFields();
     activeState.clearNonFinalStaticFields();
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java
index 0ce08a5..19a9da1 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java
@@ -62,6 +62,7 @@
 import com.android.tools.r8.kotlin.KotlinClassLevelInfo;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.utils.Pair;
+import com.android.tools.r8.utils.SetUtils;
 import com.android.tools.r8.utils.StringUtils;
 import com.android.tools.r8.utils.Timing;
 import com.android.tools.r8.utils.WorkList;
@@ -429,7 +430,8 @@
 
     anyInlinedMethods |= forceInlineDirectMethodInvocations(code, inliningIRProvider);
     anyInlinedMethods |= forceInlineIndirectMethodInvocations(code, inliningIRProvider);
-    removeAliasIntroducingInstructionsLinkedToEligibleInstance();
+
+    rebindIndirectEligibleInstanceUsersFromPhis();
     removeMiscUsages(code, affectedValues);
     removeFieldReads(code);
     removeFieldWrites();
@@ -594,47 +596,65 @@
     return true;
   }
 
-  private void removeAliasIntroducingInstructionsLinkedToEligibleInstance() {
-    Set<Instruction> currentUsers = eligibleInstance.uniqueUsers();
-    while (!currentUsers.isEmpty()) {
-      Set<Instruction> indirectOutValueUsers = Sets.newIdentityHashSet();
-      for (Instruction instruction : currentUsers) {
+  private void rebindIndirectEligibleInstanceUsersFromPhis() {
+    // Building the inlinee can cause some of the eligibleInstance users to be phi's. These phi's
+    // should be trivial.
+    // block X:
+    // vX <- NewInstance ...
+    // block Y:
+    // vZ : phi(vX, vY)
+    // block Z
+    // vY : phi(vX, vZ)
+    // These are not pruned by the trivial phi removal. We have to ensure that we rewrite all users
+    // also the indirect users directly using phi's, potentially through assumes and checkcast.
+    Set<Value> aliases = SetUtils.newIdentityHashSet(eligibleInstance);
+    Set<Phi> expectedDeadOrTrivialPhis = Sets.newIdentityHashSet();
+    WorkList<InstructionOrPhi> worklist = WorkList.newIdentityWorkList();
+    eligibleInstance.uniqueUsers().forEach(worklist::addIfNotSeen);
+    eligibleInstance.uniquePhiUsers().forEach(worklist::addIfNotSeen);
+    while (worklist.hasNext()) {
+      InstructionOrPhi instructionOrPhi = worklist.next();
+      if (instructionOrPhi.isPhi()) {
+        Phi phi = instructionOrPhi.asPhi();
+        expectedDeadOrTrivialPhis.add(phi);
+        phi.uniqueUsers().forEach(worklist::addIfNotSeen);
+        phi.uniquePhiUsers().forEach(worklist::addIfNotSeen);
+      } else {
+        Instruction instruction = instructionOrPhi.asInstruction();
         if (aliasesThroughAssumeAndCheckCasts.isIntroducingAnAlias(instruction)) {
-          Value src = aliasesThroughAssumeAndCheckCasts.getAliasForOutValue(instruction);
-          Value dest = instruction.outValue();
-          if (dest.hasPhiUsers()) {
-            // It is possible that a trivial phi is constructed upon IR building for the eligible
-            // value. It must actually be trivial so verify that it is indeed trivial and replace
-            // all of the phis involved with the value.
-            WorkList<Phi> worklist = WorkList.newIdentityWorkList(dest.uniquePhiUsers());
-            while (worklist.hasNext()) {
-              Phi phi = worklist.next();
-              for (Value operand : phi.getOperands()) {
-                operand = operand.getAliasedValue(aliasesThroughAssumeAndCheckCasts);
-                if (operand.isPhi()) {
-                  worklist.addIfNotSeen(operand.asPhi());
-                } else if (src != operand) {
-                  throw new InternalCompilerError(
-                      "Unexpected non-trivial phi in method eligible for class inlining");
-                }
-              }
-            }
-            // The only value flowing into any of the phis is src, so replace all phis by src.
-            for (Phi phi : worklist.getSeenSet()) {
-              indirectOutValueUsers.addAll(phi.uniqueUsers());
-              phi.replaceUsers(src);
-              phi.removeDeadPhi();
-            }
-          }
-          assert !dest.hasPhiUsers();
-          indirectOutValueUsers.addAll(dest.uniqueUsers());
-          dest.replaceUsers(src);
-          removeInstruction(instruction);
+          aliases.add(instruction.outValue());
+          instruction.outValue().uniqueUsers().forEach(worklist::addIfNotSeen);
+          instruction.outValue().uniquePhiUsers().forEach(worklist::addIfNotSeen);
         }
       }
-      currentUsers = indirectOutValueUsers;
     }
-
+    // Check that all phis are dead or trivial.
+    for (Phi deadTrivialPhi : expectedDeadOrTrivialPhis) {
+      for (Value operand : deadTrivialPhi.getOperands()) {
+        operand = operand.getAliasedValue(aliasesThroughAssumeAndCheckCasts);
+        // If the operand is a phi we should have found it in the search above.
+        if (operand.isPhi() && !expectedDeadOrTrivialPhis.contains(operand.asPhi())) {
+          throw new InternalCompilerError(
+              "Unexpected non-trivial phi in method eligible for class inlining");
+        }
+        // If the operand is not a phi, it should be an alias (or the eligibleInstance).
+        if (!operand.isPhi() && !aliases.contains(operand)) {
+          throw new InternalCompilerError(
+              "Unexpected non-trivial phi in method eligible for class inlining");
+        }
+      }
+      deadTrivialPhi.replaceUsers(eligibleInstance);
+      deadTrivialPhi.removeDeadPhi();
+    }
+    // We can now prune the aliases
+    for (Value alias : aliases) {
+      if (alias == eligibleInstance) {
+        continue;
+      }
+      assert alias.definition.isAssume() || alias.definition.isCheckCast();
+      alias.replaceUsers(eligibleInstance);
+      removeInstruction(alias.definition);
+    }
     // Verify that no more assume or check-cast instructions are left as users.
     assert eligibleInstance.aliasedUsers().stream().noneMatch(Instruction::isAssume);
     assert eligibleInstance.aliasedUsers().stream().noneMatch(Instruction::isCheckCast);
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 166022e..27deb32 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
@@ -63,6 +63,7 @@
 import com.android.tools.r8.ir.optimize.info.OptimizationFeedbackDelayed;
 import com.android.tools.r8.ir.optimize.info.UpdatableMethodOptimizationInfo;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.shaking.FieldAccessInfoCollectionModifier;
 import com.android.tools.r8.shaking.KeepInfoCollection;
 import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.Reporter;
@@ -388,11 +389,17 @@
     // Update keep info on any of the enum methods of the removed classes.
     updateKeepInfo(enumsToUnbox);
     DirectMappedDexApplication.Builder appBuilder = appView.appInfo().app().asDirect().builder();
+    FieldAccessInfoCollectionModifier.Builder fieldAccessInfoCollectionModifierBuilder =
+        FieldAccessInfoCollectionModifier.builder();
     UnboxedEnumMemberRelocator relocator =
         UnboxedEnumMemberRelocator.builder(appView)
             .synthesizeEnumUnboxingUtilityClasses(
-                enumClassesToUnbox, enumsToUnboxWithPackageRequirement, appBuilder)
+                enumClassesToUnbox,
+                enumsToUnboxWithPackageRequirement,
+                appBuilder,
+                fieldAccessInfoCollectionModifierBuilder)
             .build();
+    fieldAccessInfoCollectionModifierBuilder.build().modify(appView);
     enumUnboxerRewriter = new EnumUnboxingRewriter(appView, enumDataMap, relocator);
     EnumUnboxingLens enumUnboxingLens =
         new EnumUnboxingTreeFixer(appView, enumsToUnbox, relocator, enumUnboxerRewriter)
@@ -491,34 +498,38 @@
     // Step 1: We iterate over the field to find direct enum instance information and the values
     // fields.
     for (DexEncodedField staticField : enumClass.staticFields()) {
-      if (EnumUnboxingCandidateAnalysis.isEnumField(staticField, enumClass.type)) {
+      if (factory.enumMembers.isEnumField(staticField, enumClass.type)) {
         ObjectState enumState =
             enumStaticFieldValues.getObjectStateForPossiblyPinnedField(staticField.field);
-        if (enumState != null) {
-          OptionalInt optionalOrdinal = getOrdinal(enumState);
-          if (!optionalOrdinal.isPresent()) {
-            return null;
+        if (enumState == null) {
+          if (staticField.getOptimizationInfo().isDead()) {
+            // We don't care about unused field data.
+            continue;
           }
-          int ordinal = optionalOrdinal.getAsInt();
-          unboxedValues.put(staticField.field, ordinalToUnboxedInt(ordinal));
-          ordinalToObjectState.put(ordinal, enumState);
-        }
-      } else if (EnumUnboxingCandidateAnalysis.matchesValuesField(
-          staticField, enumClass.type, factory)) {
-        AbstractValue valuesValue =
-            enumStaticFieldValues.getValuesAbstractValueForPossiblyPinnedField(staticField.field);
-        if (valuesValue == null || valuesValue.isZero()) {
-          // Unused field
-          continue;
-        }
-        if (valuesValue.isUnknown()) {
+          // We could not track the content of that field. We bail out.
           return null;
         }
-        assert valuesValue.isSingleFieldValue();
-        ObjectState valuesState = valuesValue.asSingleFieldValue().getState();
-        if (!valuesState.isEnumValuesObjectState()) {
+        OptionalInt optionalOrdinal = getOrdinal(enumState);
+        if (!optionalOrdinal.isPresent()) {
           return null;
         }
+        int ordinal = optionalOrdinal.getAsInt();
+        unboxedValues.put(staticField.field, ordinalToUnboxedInt(ordinal));
+        ordinalToObjectState.put(ordinal, enumState);
+      } else if (factory.enumMembers.isValuesFieldCandidate(staticField, enumClass.type)) {
+        ObjectState valuesState =
+            enumStaticFieldValues.getObjectStateForPossiblyPinnedField(staticField.field);
+        if (valuesState == null) {
+          if (staticField.getOptimizationInfo().isDead()) {
+            // We don't care about unused field data.
+            continue;
+          }
+          // We could not track the content of that field. We bail out.
+          // We could not track the content of that field, and the field could be a values field.
+          // We conservatively bail out.
+          return null;
+        }
+        assert valuesState.isEnumValuesObjectState();
         assert valuesContents == null
             || valuesContents.equals(valuesState.asEnumValuesObjectState());
         valuesContents = valuesState.asEnumValuesObjectState();
@@ -564,6 +575,13 @@
         valuesContents == null ? EnumData.INVALID_VALUES_SIZE : valuesContents.getEnumValuesSize());
   }
 
+  private boolean isFinalFieldInitialized(DexEncodedField staticField, DexProgramClass enumClass) {
+    assert staticField.isFinal();
+    return appView
+        .appInfo()
+        .isFieldOnlyWrittenInMethodIgnoringPinning(staticField, enumClass.getClassInitializer());
+  }
+
   private EnumInstanceFieldData computeEnumFieldData(
       DexField instanceField,
       DexProgramClass enumClass,
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingRewriter.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingRewriter.java
index 6c20c4f..1602032 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingRewriter.java
@@ -12,7 +12,6 @@
 import com.android.tools.r8.graph.CfCode;
 import com.android.tools.r8.graph.DexAnnotationSet;
 import com.android.tools.r8.graph.DexClassAndMethod;
-import com.android.tools.r8.graph.DexEncodedField;
 import com.android.tools.r8.graph.DexEncodedMember;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexField;
@@ -21,8 +20,6 @@
 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.FieldAccessFlags;
-import com.android.tools.r8.graph.GenericSignature.FieldTypeSignature;
 import com.android.tools.r8.graph.GenericSignature.MethodTypeSignature;
 import com.android.tools.r8.graph.GraphLens.NestedGraphLens;
 import com.android.tools.r8.graph.MethodAccessFlags;
@@ -78,7 +75,6 @@
   private NestedGraphLens enumUnboxingLens;
 
   private final Map<DexMethod, DexEncodedMethod> utilityMethods = new ConcurrentHashMap<>();
-  private final Map<DexField, DexEncodedField> utilityFields = new ConcurrentHashMap<>();
 
   private final DexMethod ordinalUtilityMethod;
   private final DexMethod equalsUtilityMethod;
@@ -279,7 +275,6 @@
               utilityMethods.computeIfAbsent(
                   valuesUtilityMethod, m -> synthesizeValuesUtilityMethod());
               DexField fieldValues = createValuesField(holder);
-              utilityFields.computeIfAbsent(fieldValues, this::computeValuesEncodedField);
               DexMethod methodValues = createValuesMethod(holder);
               utilityMethods.computeIfAbsent(
                   methodValues,
@@ -426,32 +421,27 @@
     return unboxedEnumsData.isUnboxedEnum(enumType) ? enumType : null;
   }
 
-  public String compatibleName(DexType type) {
+  public static String compatibleName(DexType type) {
     return type.toSourceString().replace('.', '$');
   }
 
   private DexField createValuesField(DexType enumType) {
-    return factory.createField(
-        relocator.getNewMemberLocationFor(enumType),
-        factory.intArrayType,
-        factory.enumValuesFieldName + "$field$" + compatibleName(enumType));
+    return createValuesField(enumType, relocator.getNewMemberLocationFor(enumType), factory);
   }
 
-  private DexEncodedField computeValuesEncodedField(DexField field) {
-    return new DexEncodedField(
-        field,
-        FieldAccessFlags.fromSharedAccessFlags(
-            Constants.ACC_SYNTHETIC | Constants.ACC_STATIC | Constants.ACC_PUBLIC),
-        FieldTypeSignature.noSignature(),
-        DexAnnotationSet.empty(),
-        null);
+  static DexField createValuesField(
+      DexType enumType, DexType enumUtilityClass, DexItemFactory dexItemFactory) {
+    return dexItemFactory.createField(
+        enumUtilityClass,
+        dexItemFactory.intArrayType,
+        "$$values$$field$" + compatibleName(enumType));
   }
 
   private DexMethod createValuesMethod(DexType enumType) {
     return factory.createMethod(
         relocator.getNewMemberLocationFor(enumType),
         factory.createProto(factory.intArrayType),
-        factory.enumValuesFieldName + "$method$" + compatibleName(enumType));
+        "$$values$$method$" + compatibleName(enumType));
   }
 
   private DexEncodedMethod computeValuesEncodedMethod(
@@ -533,7 +523,6 @@
     // fields required.
     Map<DexType, List<DexEncodedMethod>> methodMap = triageEncodedMembers(utilityMethods.values());
     if (methodMap.isEmpty()) {
-      assert utilityFields.isEmpty();
       return;
     }
     SortedProgramMethodSet wave = SortedProgramMethodSet.create();
@@ -546,13 +535,6 @@
             wave.add(new ProgramMethod(utilityClass, dexEncodedMethod));
           }
         });
-    Map<DexType, List<DexEncodedField>> fieldMap = triageEncodedMembers(utilityFields.values());
-    fieldMap.forEach(
-        (type, fieldsSorted) -> {
-          DexProgramClass utilityClass = appView.definitionFor(type).asProgramClass();
-          assert utilityClass != null;
-          utilityClass.appendStaticFields(fieldsSorted);
-        });
     converter.processMethodsConcurrently(wave, executorService);
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingTreeFixer.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingTreeFixer.java
index 10a50ca..bcd8458 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingTreeFixer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingTreeFixer.java
@@ -193,7 +193,15 @@
       if (newType != field.type) {
         DexField newField = factory.createField(field.holder, newType, field.name);
         lensBuilder.move(field, newField);
-        DexEncodedField newEncodedField = encodedField.toTypeSubstitutedField(newField);
+        DexEncodedField newEncodedField =
+            encodedField.toTypeSubstitutedField(
+                newField,
+                builder ->
+                    builder.fixupOptimizationInfo(
+                        mutableFieldOptimizationInfo -> {
+                          mutableFieldOptimizationInfo.setAbstractValue(
+                              encodedField.getOptimizationInfo().getAbstractValue());
+                        }));
         setter.setField(i, newEncodedField);
         if (encodedField.isStatic() && encodedField.hasExplicitStaticValue()) {
           assert encodedField.getStaticValue() == DexValue.DexValueNull.NULL;
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/UnboxedEnumMemberRelocator.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/UnboxedEnumMemberRelocator.java
index a0ea10c..5a8ae19 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/enums/UnboxedEnumMemberRelocator.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/UnboxedEnumMemberRelocator.java
@@ -4,23 +4,32 @@
 
 package com.android.tools.r8.ir.optimize.enums;
 
+import static com.android.tools.r8.ir.optimize.enums.EnumUnboxingRewriter.createValuesField;
+
 import com.android.tools.r8.dex.Constants;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.ClassAccessFlags;
 import com.android.tools.r8.graph.DexAnnotationSet;
 import com.android.tools.r8.graph.DexEncodedField;
 import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.DexTypeList;
 import com.android.tools.r8.graph.DirectMappedDexApplication;
+import com.android.tools.r8.graph.FieldAccessFlags;
 import com.android.tools.r8.graph.GenericSignature.ClassSignature;
 import com.android.tools.r8.graph.ProgramPackage;
 import com.android.tools.r8.graph.ProgramPackageCollection;
 import com.android.tools.r8.origin.SynthesizedOrigin;
+import com.android.tools.r8.shaking.FieldAccessInfoCollectionModifier;
+import com.android.tools.r8.utils.SetUtils;
 import com.google.common.collect.ImmutableMap;
+import java.util.ArrayList;
 import java.util.Collections;
+import java.util.Comparator;
 import java.util.IdentityHashMap;
+import java.util.List;
 import java.util.Map;
 import java.util.Set;
 
@@ -56,7 +65,7 @@
   }
 
   public static class Builder {
-    private DexType defaultEnumUnboxingUtility;
+    private DexProgramClass defaultEnumUnboxingUtility;
     private Map<DexType, DexType> relocationMap = new IdentityHashMap<>();
     private final AppView<?> appView;
 
@@ -67,44 +76,84 @@
     public Builder synthesizeEnumUnboxingUtilityClasses(
         Set<DexProgramClass> enumsToUnbox,
         ProgramPackageCollection enumsToUnboxWithPackageRequirement,
-        DirectMappedDexApplication.Builder appBuilder) {
-      defaultEnumUnboxingUtility = synthesizeUtilityClass(enumsToUnbox, appBuilder);
+        DirectMappedDexApplication.Builder appBuilder,
+        FieldAccessInfoCollectionModifier.Builder fieldAccessInfoCollectionModifierBuilder) {
+      Set<DexProgramClass> enumsToUnboxWithoutPackageRequirement =
+          SetUtils.newIdentityHashSet(enumsToUnbox);
+      enumsToUnboxWithoutPackageRequirement.removeIf(enumsToUnboxWithPackageRequirement::contains);
+      defaultEnumUnboxingUtility =
+          synthesizeUtilityClass(
+              enumsToUnbox,
+              enumsToUnboxWithoutPackageRequirement,
+              appBuilder,
+              fieldAccessInfoCollectionModifierBuilder);
       if (!enumsToUnboxWithPackageRequirement.isEmpty()) {
-        synthesizeRelocationMap(enumsToUnboxWithPackageRequirement, appBuilder);
+        synthesizeRelocationMap(
+            enumsToUnbox,
+            enumsToUnboxWithPackageRequirement,
+            appBuilder,
+            fieldAccessInfoCollectionModifierBuilder);
       }
       return this;
     }
 
     public UnboxedEnumMemberRelocator build() {
       return new UnboxedEnumMemberRelocator(
-          defaultEnumUnboxingUtility, ImmutableMap.copyOf(relocationMap));
+          defaultEnumUnboxingUtility.getType(), ImmutableMap.copyOf(relocationMap));
     }
 
     private void synthesizeRelocationMap(
+        Set<DexProgramClass> contexts,
         ProgramPackageCollection enumsToUnboxWithPackageRequirement,
-        DirectMappedDexApplication.Builder appBuilder) {
+        DirectMappedDexApplication.Builder appBuilder,
+        FieldAccessInfoCollectionModifier.Builder fieldAccessInfoCollectionModifierBuilder) {
       for (ProgramPackage programPackage : enumsToUnboxWithPackageRequirement) {
         Set<DexProgramClass> enumsToUnboxInPackage = programPackage.classesInPackage();
-        DexType utilityType = synthesizeUtilityClass(enumsToUnboxInPackage, appBuilder);
-        for (DexProgramClass enumToUnbox : enumsToUnboxInPackage) {
-          assert !relocationMap.containsKey(enumToUnbox.type);
-          relocationMap.put(enumToUnbox.type, utilityType);
+        DexProgramClass enumUtilityClass =
+            synthesizeUtilityClass(
+                contexts,
+                enumsToUnboxInPackage,
+                appBuilder,
+                fieldAccessInfoCollectionModifierBuilder);
+        if (enumUtilityClass != defaultEnumUnboxingUtility) {
+          for (DexProgramClass enumToUnbox : enumsToUnboxInPackage) {
+            assert !relocationMap.containsKey(enumToUnbox.type);
+            relocationMap.put(enumToUnbox.type, enumUtilityClass.getType());
+          }
         }
       }
     }
 
-    private DexType synthesizeUtilityClass(
-        Set<DexProgramClass> contexts, DirectMappedDexApplication.Builder appBuilder) {
+    private DexProgramClass synthesizeUtilityClass(
+        Set<DexProgramClass> contexts,
+        Set<DexProgramClass> relocatedEnums,
+        DirectMappedDexApplication.Builder appBuilder,
+        FieldAccessInfoCollectionModifier.Builder fieldAccessInfoCollectionModifierBuilder) {
       DexType deterministicContextType = findDeterministicContextType(contexts);
       assert deterministicContextType.isClassType();
       String descriptorString = deterministicContextType.toDescriptorString();
       String descriptorPrefix = descriptorString.substring(0, descriptorString.length() - 1);
       String syntheticClassDescriptor = descriptorPrefix + ENUM_UNBOXING_UTILITY_CLASS_SUFFIX + ";";
       DexType type = appView.dexItemFactory().createType(syntheticClassDescriptor);
+
+      // Required fields.
+      List<DexEncodedField> staticFields = new ArrayList<>(relocatedEnums.size());
+      for (DexProgramClass relocatedEnum : relocatedEnums) {
+        DexField reference =
+            createValuesField(relocatedEnum.getType(), type, appView.dexItemFactory());
+        staticFields.add(
+            new DexEncodedField(reference, FieldAccessFlags.createPublicStaticSynthetic()));
+        fieldAccessInfoCollectionModifierBuilder
+            .recordFieldReadInUnknownContext(reference)
+            .recordFieldWriteInUnknownContext(reference);
+      }
+      staticFields.sort(Comparator.comparing(DexEncodedField::getReference));
+
       // The defaultEnumUnboxingUtility depends on all unboxable enums, and other synthetic types
       // depend on a subset of the unboxable enums, the deterministicContextType can therefore
       // be found twice, and in that case the same utility class can be used for both.
-      if (type == defaultEnumUnboxingUtility) {
+      if (defaultEnumUnboxingUtility != null && type == defaultEnumUnboxingUtility.getType()) {
+        defaultEnumUnboxingUtility.appendStaticFields(staticFields);
         return defaultEnumUnboxingUtility;
       }
       assert appView.appInfo().definitionForWithoutExistenceAssert(type) == null;
@@ -124,7 +173,7 @@
               Collections.emptyList(),
               ClassSignature.noSignature(),
               DexAnnotationSet.empty(),
-              DexEncodedField.EMPTY_ARRAY,
+              staticFields.toArray(DexEncodedField.EMPTY_ARRAY),
               DexEncodedField.EMPTY_ARRAY,
               DexEncodedMethod.EMPTY_ARRAY,
               DexEncodedMethod.EMPTY_ARRAY,
@@ -135,7 +184,7 @@
           .appInfo()
           .addSynthesizedClass(
               syntheticClass, appView.appInfo().getMainDexClasses().containsAnyOf(contexts));
-      return syntheticClass.type;
+      return syntheticClass;
     }
 
     private DexType findDeterministicContextType(Set<DexProgramClass> contexts) {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/MutableFieldOptimizationInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/MutableFieldOptimizationInfo.java
index 41ee658..3bf8d88 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/MutableFieldOptimizationInfo.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/MutableFieldOptimizationInfo.java
@@ -65,7 +65,7 @@
     return abstractValue;
   }
 
-  void setAbstractValue(AbstractValue abstractValue) {
+  public void setAbstractValue(AbstractValue abstractValue) {
     this.abstractValue = abstractValue;
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/lambda/LambdaMerger.java b/src/main/java/com/android/tools/r8/ir/optimize/lambda/LambdaMerger.java
index e9ff9e8..5bccf4d4 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/lambda/LambdaMerger.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/lambda/LambdaMerger.java
@@ -50,6 +50,7 @@
 import com.android.tools.r8.ir.optimize.lambda.kotlin.KotlinLambdaGroupIdFactory;
 import com.android.tools.r8.kotlin.Kotlin;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.shaking.FieldAccessInfoCollectionModifier;
 import com.android.tools.r8.shaking.ProguardConfiguration;
 import com.android.tools.r8.utils.SetUtils;
 import com.android.tools.r8.utils.StringDiagnostic;
@@ -364,6 +365,9 @@
     // Switch to APPLY strategy.
     this.mode = new ApplyMode(lambdaGroupsClasses.inverse(), optimizationInfoFixer);
 
+    FieldAccessInfoCollectionModifier.Builder fieldAccessInfoCollectionModifierBuilder =
+        FieldAccessInfoCollectionModifier.builder();
+
     // Add synthesized lambda group classes to the builder.
     for (Entry<LambdaGroup, DexProgramClass> entry : lambdaGroupsClasses.entrySet()) {
       DexProgramClass synthesizedClass = entry.getValue();
@@ -371,6 +375,13 @@
           .appInfo()
           .addSynthesizedClass(synthesizedClass, entry.getKey().shouldAddToMainDex(appView));
       builder.addSynthesizedClass(synthesizedClass);
+
+      synthesizedClass.forEachField(
+          field ->
+              fieldAccessInfoCollectionModifierBuilder
+                  .recordFieldReadInUnknownContext(field.getReference())
+                  .recordFieldWriteInUnknownContext(field.getReference()));
+
       // Eventually, we need to process synthesized methods in the lambda group.
       // Otherwise, abstract SynthesizedCode will be flown to Enqueuer.
       // But that process should not see the holder. Otherwise, lambda calls in the main dispatch
@@ -383,6 +394,9 @@
           encodedMethod -> encodedMethod.markProcessed(ConstraintWithTarget.NEVER));
     }
 
+    // Record field accesses for synthesized fields.
+    fieldAccessInfoCollectionModifierBuilder.build().modify(appView);
+
     converter.optimizeSynthesizedClasses(lambdaGroupsClasses.values(), executorService);
 
     // Rewrite lambda class references into lambda group class
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/staticizer/StaticizingProcessor.java b/src/main/java/com/android/tools/r8/ir/optimize/staticizer/StaticizingProcessor.java
index 9d84c1f..bf9f986 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/staticizer/StaticizingProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/staticizer/StaticizingProcessor.java
@@ -454,7 +454,8 @@
       }
     }
 
-    assert false : "Must always be able to find and remove the instantiation";
+    assert candidateInfo.singletonField.getOptimizationInfo().isDead()
+        : "Must always be able to find and remove the instantiation";
   }
 
   private void removeReferencesToThis(IRCode code, MethodProcessor methodProcessor) {
diff --git a/src/main/java/com/android/tools/r8/naming/identifiernamestring/ClassForNameIdentifierNameStringLookupResult.java b/src/main/java/com/android/tools/r8/naming/identifiernamestring/ClassForNameIdentifierNameStringLookupResult.java
index 2498026..5785d0a 100644
--- a/src/main/java/com/android/tools/r8/naming/identifiernamestring/ClassForNameIdentifierNameStringLookupResult.java
+++ b/src/main/java/com/android/tools/r8/naming/identifiernamestring/ClassForNameIdentifierNameStringLookupResult.java
@@ -20,7 +20,7 @@
   }
 
   @Override
-  public boolean isTypeInstantiatedFromUse(InternalOptions options) {
+  public boolean isTypeCompatInstantiatedFromUse(InternalOptions options) {
     return options.isForceProguardCompatibilityEnabled();
   }
 }
diff --git a/src/main/java/com/android/tools/r8/naming/identifiernamestring/ClassNameComparisonIdentifierNameStringLookupResult.java b/src/main/java/com/android/tools/r8/naming/identifiernamestring/ClassNameComparisonIdentifierNameStringLookupResult.java
index 0305995..431e27d 100644
--- a/src/main/java/com/android/tools/r8/naming/identifiernamestring/ClassNameComparisonIdentifierNameStringLookupResult.java
+++ b/src/main/java/com/android/tools/r8/naming/identifiernamestring/ClassNameComparisonIdentifierNameStringLookupResult.java
@@ -20,7 +20,7 @@
   }
 
   @Override
-  public boolean isTypeInstantiatedFromUse(InternalOptions options) {
+  public boolean isTypeCompatInstantiatedFromUse(InternalOptions options) {
     return false;
   }
 }
diff --git a/src/main/java/com/android/tools/r8/naming/identifiernamestring/DexTypeBasedConstStringIdentifierNameStringLookupResult.java b/src/main/java/com/android/tools/r8/naming/identifiernamestring/DexTypeBasedConstStringIdentifierNameStringLookupResult.java
index f2c1ec1..2cdc3c4 100644
--- a/src/main/java/com/android/tools/r8/naming/identifiernamestring/DexTypeBasedConstStringIdentifierNameStringLookupResult.java
+++ b/src/main/java/com/android/tools/r8/naming/identifiernamestring/DexTypeBasedConstStringIdentifierNameStringLookupResult.java
@@ -20,7 +20,7 @@
   }
 
   @Override
-  public boolean isTypeInstantiatedFromUse(InternalOptions options) {
+  public boolean isTypeCompatInstantiatedFromUse(InternalOptions options) {
     return false;
   }
 }
diff --git a/src/main/java/com/android/tools/r8/naming/identifiernamestring/IdentifierNameStringTypeLookupResult.java b/src/main/java/com/android/tools/r8/naming/identifiernamestring/IdentifierNameStringTypeLookupResult.java
index 6707406..7f4d69d 100644
--- a/src/main/java/com/android/tools/r8/naming/identifiernamestring/IdentifierNameStringTypeLookupResult.java
+++ b/src/main/java/com/android/tools/r8/naming/identifiernamestring/IdentifierNameStringTypeLookupResult.java
@@ -16,7 +16,7 @@
 
   public abstract boolean isTypeInitializedFromUse();
 
-  public abstract boolean isTypeInstantiatedFromUse(InternalOptions options);
+  public abstract boolean isTypeCompatInstantiatedFromUse(InternalOptions options);
 
   @Override
   public boolean isTypeResult() {
diff --git a/src/main/java/com/android/tools/r8/retrace/ProguardMapProducer.java b/src/main/java/com/android/tools/r8/retrace/ProguardMapProducer.java
new file mode 100644
index 0000000..7e66ed3
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/retrace/ProguardMapProducer.java
@@ -0,0 +1,30 @@
+// 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 com.android.tools.r8.Keep;
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.Reader;
+
+/** Interface for producing a string format of a mapping file. */
+@Keep
+public interface ProguardMapProducer {
+
+  String get() throws IOException;
+
+  static ProguardMapProducer fromReader(Reader reader) {
+    return () -> {
+      try (BufferedReader br = new BufferedReader(reader)) {
+        StringBuilder sb = new StringBuilder();
+        String line;
+        while ((line = br.readLine()) != null) {
+          sb.append(line).append('\n');
+        }
+        return sb.toString();
+      }
+    };
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/retrace/Retrace.java b/src/main/java/com/android/tools/r8/retrace/Retrace.java
index 0d66a64..76fad7d 100644
--- a/src/main/java/com/android/tools/r8/retrace/Retrace.java
+++ b/src/main/java/com/android/tools/r8/retrace/Retrace.java
@@ -12,7 +12,6 @@
 import com.android.tools.r8.Keep;
 import com.android.tools.r8.Version;
 import com.android.tools.r8.retrace.RetraceCommand.Builder;
-import com.android.tools.r8.retrace.RetraceCommand.ProguardMapProducer;
 import com.android.tools.r8.retrace.internal.PlainStackTraceVisitor;
 import com.android.tools.r8.retrace.internal.RetraceAbortException;
 import com.android.tools.r8.retrace.internal.RetraceRegularExpression;
diff --git a/src/main/java/com/android/tools/r8/retrace/RetraceCommand.java b/src/main/java/com/android/tools/r8/retrace/RetraceCommand.java
index d9f67ef..e346e3c 100644
--- a/src/main/java/com/android/tools/r8/retrace/RetraceCommand.java
+++ b/src/main/java/com/android/tools/r8/retrace/RetraceCommand.java
@@ -145,7 +145,4 @@
     }
   }
 
-  public interface ProguardMapProducer {
-    String get();
-  }
 }
diff --git a/src/main/java/com/android/tools/r8/retrace/Retracer.java b/src/main/java/com/android/tools/r8/retrace/Retracer.java
index ed1d338..015dac8 100644
--- a/src/main/java/com/android/tools/r8/retrace/Retracer.java
+++ b/src/main/java/com/android/tools/r8/retrace/Retracer.java
@@ -10,7 +10,6 @@
 import com.android.tools.r8.references.FieldReference;
 import com.android.tools.r8.references.MethodReference;
 import com.android.tools.r8.references.TypeReference;
-import com.android.tools.r8.retrace.RetraceCommand.ProguardMapProducer;
 import com.android.tools.r8.retrace.internal.RetracerImpl;
 
 /** This is the main api interface for retrace. */
diff --git a/src/main/java/com/android/tools/r8/retrace/internal/DirectClassNameMapperProguardMapProducer.java b/src/main/java/com/android/tools/r8/retrace/internal/DirectClassNameMapperProguardMapProducer.java
index 07bf237..db73155 100644
--- a/src/main/java/com/android/tools/r8/retrace/internal/DirectClassNameMapperProguardMapProducer.java
+++ b/src/main/java/com/android/tools/r8/retrace/internal/DirectClassNameMapperProguardMapProducer.java
@@ -5,7 +5,7 @@
 package com.android.tools.r8.retrace.internal;
 
 import com.android.tools.r8.naming.ClassNameMapper;
-import com.android.tools.r8.retrace.RetraceCommand.ProguardMapProducer;
+import com.android.tools.r8.retrace.ProguardMapProducer;
 
 public interface DirectClassNameMapperProguardMapProducer extends ProguardMapProducer {
 
diff --git a/src/main/java/com/android/tools/r8/retrace/internal/RetracerImpl.java b/src/main/java/com/android/tools/r8/retrace/internal/RetracerImpl.java
index c2a6749..d25afab 100644
--- a/src/main/java/com/android/tools/r8/retrace/internal/RetracerImpl.java
+++ b/src/main/java/com/android/tools/r8/retrace/internal/RetracerImpl.java
@@ -11,7 +11,7 @@
 import com.android.tools.r8.references.MethodReference;
 import com.android.tools.r8.references.TypeReference;
 import com.android.tools.r8.retrace.InvalidMappingFileException;
-import com.android.tools.r8.retrace.RetraceCommand.ProguardMapProducer;
+import com.android.tools.r8.retrace.ProguardMapProducer;
 import com.android.tools.r8.retrace.Retracer;
 
 /** A default implementation for the retrace api using the ClassNameMapper defined in R8. */
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 363cb30..baa1189 100644
--- a/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
+++ b/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
@@ -13,6 +13,7 @@
 import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
 import com.android.tools.r8.graph.DexCallSite;
 import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexClassAndMember;
 import com.android.tools.r8.graph.DexClassAndMethod;
 import com.android.tools.r8.graph.DexClasspathClass;
 import com.android.tools.r8.graph.DexDefinition;
@@ -161,7 +162,6 @@
   private final Set<DexType> noClassMerging;
   private final Set<DexType> noHorizontalClassMerging;
   private final Set<DexType> noVerticalClassMerging;
-  private final Set<DexType> noStaticClassMerging;
 
   /**
    * Set of lock candidates (i.e., types whose class reference may flow to a monitor instruction).
@@ -228,7 +228,6 @@
       Set<DexType> noClassMerging,
       Set<DexType> noVerticalClassMerging,
       Set<DexType> noHorizontalClassMerging,
-      Set<DexType> noStaticClassMerging,
       Set<DexReference> neverPropagateValue,
       Object2BooleanMap<DexReference> identifierNameStrings,
       Set<DexType> prunedTypes,
@@ -267,7 +266,6 @@
     this.noClassMerging = noClassMerging;
     this.noVerticalClassMerging = noVerticalClassMerging;
     this.noHorizontalClassMerging = noHorizontalClassMerging;
-    this.noStaticClassMerging = noStaticClassMerging;
     this.neverPropagateValue = neverPropagateValue;
     this.identifierNameStrings = identifierNameStrings;
     this.prunedTypes = prunedTypes;
@@ -314,7 +312,6 @@
         previous.noClassMerging,
         previous.noVerticalClassMerging,
         previous.noHorizontalClassMerging,
-        previous.noStaticClassMerging,
         previous.neverPropagateValue,
         previous.identifierNameStrings,
         previous.prunedTypes,
@@ -362,7 +359,6 @@
         previous.noClassMerging,
         previous.noVerticalClassMerging,
         previous.noHorizontalClassMerging,
-        previous.noStaticClassMerging,
         previous.neverPropagateValue,
         previous.identifierNameStrings,
         prunedItems.hasRemovedClasses()
@@ -455,7 +451,6 @@
     this.noClassMerging = previous.noClassMerging;
     this.noVerticalClassMerging = previous.noVerticalClassMerging;
     this.noHorizontalClassMerging = previous.noHorizontalClassMerging;
-    this.noStaticClassMerging = previous.noStaticClassMerging;
     this.neverPropagateValue = previous.neverPropagateValue;
     this.identifierNameStrings = previous.identifierNameStrings;
     this.prunedTypes = previous.prunedTypes;
@@ -592,6 +587,10 @@
     return noSideEffects.containsKey(method);
   }
 
+  public boolean isAssumeNoSideEffectsMethod(DexClassAndMethod method) {
+    return isAssumeNoSideEffectsMethod(method.getReference());
+  }
+
   public boolean isWhyAreYouNotInliningMethod(DexMethod method) {
     return whyAreYouNotInlining.contains(method);
   }
@@ -743,7 +742,7 @@
     DexType type = clazz.type;
     return
     // TODO(b/165224388): Synthetic classes should be represented in the allocation info.
-    getSyntheticItems().isSyntheticClass(clazz)
+    getSyntheticItems().isLegacySyntheticClass(clazz)
         || (!clazz.isInterface() && objectAllocationInfoCollection.isInstantiatedDirectly(clazz))
         // TODO(b/145344105): Model annotations in the object allocation info.
         || (clazz.isAnnotation() && liveTypes.contains(type));
@@ -769,11 +768,6 @@
     if (keepInfo.isPinned(field, this)) {
       return true;
     }
-    // Fields in the class that is synthesized by D8/R8 would be used soon.
-    // TODO(b/165229577): Do we need this special handling of synthetics?
-    if (getSyntheticItems().isSyntheticClass(field.holder)) {
-      return true;
-    }
     // For library classes we don't know whether a field is read.
     return isLibraryOrClasspathField(encodedField);
   }
@@ -791,10 +785,6 @@
       // The field is written directly by the program itself.
       return true;
     }
-    // TODO(b/165229577): Do we need this special handling of synthetics?
-    if (getSyntheticItems().isSyntheticClass(field.holder)) {
-      return true;
-    }
     // For library classes we don't know whether a field is rewritten.
     return isLibraryOrClasspathField(encodedField);
   }
@@ -853,9 +843,9 @@
         && !keepInfo.getMethodInfo(method).isPinned();
   }
 
-  public boolean mayPropagateValueFor(DexMember<?, ?> reference) {
+  public boolean mayPropagateValueFor(DexClassAndMember<?, ?> member) {
     assert checkIfObsolete();
-    return reference.apply(this::mayPropagateValueFor, this::mayPropagateValueFor);
+    return member.getReference().apply(this::mayPropagateValueFor, this::mayPropagateValueFor);
   }
 
   public boolean mayPropagateValueFor(DexField field) {
@@ -944,6 +934,11 @@
     return isPinned(definition.getReference());
   }
 
+  public boolean isPinned(DexClassAndMember<?, ?> member) {
+    assert member != null;
+    return isPinned(member.getReference());
+  }
+
   public boolean hasPinnedInstanceInitializer(DexType type) {
     assert type.isClassType();
     DexProgramClass clazz = asProgramClassOrNull(definitionFor(type));
@@ -1037,7 +1032,6 @@
         lens.rewriteTypes(noClassMerging),
         lens.rewriteTypes(noVerticalClassMerging),
         lens.rewriteTypes(noHorizontalClassMerging),
-        lens.rewriteTypes(noStaticClassMerging),
         lens.rewriteReferences(neverPropagateValue),
         lens.rewriteReferenceKeys(identifierNameStrings),
         // Don't rewrite pruned types - the removed types are identified by their original name.
@@ -1402,12 +1396,4 @@
   public boolean isNoVerticalClassMergingOfType(DexType type) {
     return noClassMerging.contains(type) || noVerticalClassMerging.contains(type);
   }
-
-  /**
-   * All types that *must* never be merged by the static class merger due to a configuration
-   * directive (testing only).
-   */
-  public Set<DexType> getNoStaticClassMergingSet() {
-    return noStaticClassMerging;
-  }
 }
diff --git a/src/main/java/com/android/tools/r8/shaking/ConsequentRootSetBuilder.java b/src/main/java/com/android/tools/r8/shaking/ConsequentRootSetBuilder.java
deleted file mode 100644
index 540db63..0000000
--- a/src/main/java/com/android/tools/r8/shaking/ConsequentRootSetBuilder.java
+++ /dev/null
@@ -1,30 +0,0 @@
-// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-package com.android.tools.r8.shaking;
-
-import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
-import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.SubtypingInfo;
-
-class ConsequentRootSetBuilder extends RootSetBuilder {
-
-  private final Enqueuer enqueuer;
-
-  ConsequentRootSetBuilder(
-      AppView<? extends AppInfoWithClassHierarchy> appView,
-      SubtypingInfo subtypingInfo,
-      Enqueuer enqueuer) {
-    super(appView, subtypingInfo, null);
-    this.enqueuer = enqueuer;
-  }
-
-  @Override
-  void handleMatchedAnnotation(AnnotationMatchResult annotationMatchResult) {
-    if (enqueuer.getMode().isInitialTreeShaking()
-        && annotationMatchResult.isConcreteAnnotationMatchResult()) {
-      enqueuer.retainAnnotationForFinalTreeShaking(
-          annotationMatchResult.asConcreteAnnotationMatchResult().getMatchedAnnotations());
-    }
-  }
-}
diff --git a/src/main/java/com/android/tools/r8/shaking/DelayedRootSetActionItem.java b/src/main/java/com/android/tools/r8/shaking/DelayedRootSetActionItem.java
index 1c99cc6..b7a3810 100644
--- a/src/main/java/com/android/tools/r8/shaking/DelayedRootSetActionItem.java
+++ b/src/main/java/com/android/tools/r8/shaking/DelayedRootSetActionItem.java
@@ -5,6 +5,7 @@
 package com.android.tools.r8.shaking;
 
 import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.shaking.RootSetUtils.RootSetBuilder;
 import java.util.function.Consumer;
 
 public abstract class DelayedRootSetActionItem {
diff --git a/src/main/java/com/android/tools/r8/shaking/DiscardedChecker.java b/src/main/java/com/android/tools/r8/shaking/DiscardedChecker.java
index bbcaf32..b04748c 100644
--- a/src/main/java/com/android/tools/r8/shaking/DiscardedChecker.java
+++ b/src/main/java/com/android/tools/r8/shaking/DiscardedChecker.java
@@ -6,7 +6,7 @@
 import com.android.tools.r8.graph.DexDefinition;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexReference;
-import com.android.tools.r8.shaking.RootSetBuilder.RootSet;
+import com.android.tools.r8.shaking.RootSetUtils.RootSet;
 import java.util.ArrayList;
 import java.util.HashSet;
 import java.util.List;
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 bc50542..ca98aff 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -101,10 +101,12 @@
 import com.android.tools.r8.shaking.GraphReporter.KeepReasonWitness;
 import com.android.tools.r8.shaking.KeepInfo.Joiner;
 import com.android.tools.r8.shaking.KeepInfoCollection.MutableKeepInfoCollection;
-import com.android.tools.r8.shaking.RootSetBuilder.ConsequentRootSet;
-import com.android.tools.r8.shaking.RootSetBuilder.ItemsWithRules;
-import com.android.tools.r8.shaking.RootSetBuilder.MutableItemsWithRules;
-import com.android.tools.r8.shaking.RootSetBuilder.RootSet;
+import com.android.tools.r8.shaking.RootSetUtils.ConsequentRootSet;
+import com.android.tools.r8.shaking.RootSetUtils.ConsequentRootSetBuilder;
+import com.android.tools.r8.shaking.RootSetUtils.ItemsWithRules;
+import com.android.tools.r8.shaking.RootSetUtils.MutableItemsWithRules;
+import com.android.tools.r8.shaking.RootSetUtils.RootSet;
+import com.android.tools.r8.shaking.RootSetUtils.RootSetBuilder;
 import com.android.tools.r8.shaking.ScopedDexMethodSet.AddMethodIfMoreVisibleResult;
 import com.android.tools.r8.utils.Action;
 import com.android.tools.r8.utils.InternalOptions;
@@ -156,6 +158,7 @@
 import java.util.function.Consumer;
 import java.util.function.Function;
 import java.util.function.Predicate;
+import java.util.function.Supplier;
 
 /**
  * Approximates the runtime dependencies for the given set of roots.
@@ -333,6 +336,8 @@
   /** A queue of items that need processing. Different items trigger different actions. */
   private final EnqueuerWorklist workList;
 
+  private final ProguardCompatibilityActions.Builder proguardCompatibilityActionsBuilder;
+
   /** A set of methods that need code inspection for Java reflection in use. */
   private final ProgramMethodSet pendingReflectiveUses = ProgramMethodSet.createLinked();
 
@@ -415,6 +420,10 @@
     this.options = options;
     this.useRegistryFactory = createUseRegistryFactory();
     this.workList = EnqueuerWorklist.createWorklist();
+    this.proguardCompatibilityActionsBuilder =
+        mode.isInitialTreeShaking() && options.forceProguardCompatibility
+            ? ProguardCompatibilityActions.builder()
+            : null;
     this.previousMainDexTracingResult = previousMainDexTracingResult;
 
     if (mode.isInitialOrFinalTreeShaking()) {
@@ -1091,7 +1100,7 @@
       if (baseClass != null) {
         // Don't require any constructor, see b/112386012.
         markClassAsInstantiatedWithCompatRule(
-            baseClass, graphReporter.reportCompatInstantiated(baseClass, currentMethod));
+            baseClass, () -> graphReporter.reportCompatInstantiated(baseClass, currentMethod));
       }
     }
   }
@@ -1448,6 +1457,12 @@
       return;
     }
 
+    assert !mode.isFinalTreeShaking() || !field.getDefinition().getOptimizationInfo().isDead()
+        : "Unexpected reference in `"
+            + currentMethod.toSourceString()
+            + "` to field marked dead: "
+            + field.getReference().toSourceString();
+
     if (fromMethodHandle) {
       fieldAccessInfoCollection.get(field.getReference()).setReadFromMethodHandle();
     }
@@ -1498,6 +1513,12 @@
       return;
     }
 
+    assert !mode.isFinalTreeShaking() || !field.getDefinition().getOptimizationInfo().isDead()
+        : "Unexpected reference in `"
+            + currentMethod.toSourceString()
+            + "` to field marked dead: "
+            + field.getReference().toSourceString();
+
     if (fromMethodHandle) {
       fieldAccessInfoCollection.get(field.getReference()).setWrittenFromMethodHandle();
     }
@@ -1551,6 +1572,12 @@
       return;
     }
 
+    assert !mode.isFinalTreeShaking() || !field.getDefinition().getOptimizationInfo().isDead()
+        : "Unexpected reference in `"
+            + currentMethod.toSourceString()
+            + "` to field marked dead: "
+            + field.getReference().toSourceString();
+
     if (fromMethodHandle) {
       fieldAccessInfoCollection.get(field.getReference()).setReadFromMethodHandle();
     }
@@ -1608,6 +1635,12 @@
       return;
     }
 
+    assert !mode.isFinalTreeShaking() || !field.getDefinition().getOptimizationInfo().isDead()
+        : "Unexpected reference in `"
+            + currentMethod.toSourceString()
+            + "` to field marked dead: "
+            + field.getReference().toSourceString();
+
     if (fromMethodHandle) {
       fieldAccessInfoCollection.get(field.getReference()).setWrittenFromMethodHandle();
     }
@@ -2189,7 +2222,7 @@
 
   private void keepClassAndAllMembers(DexProgramClass clazz, KeepReason keepReason) {
     KeepReasonWitness keepReasonWitness = graphReporter.registerClass(clazz, keepReason);
-    markClassAsInstantiatedWithCompatRule(clazz.asProgramClass(), keepReasonWitness);
+    markClassAsInstantiatedWithCompatRule(clazz.asProgramClass(), () -> keepReasonWitness);
     keepInfo.keepClass(clazz);
     shouldNotBeMinified(clazz.getReference());
     clazz.forEachProgramField(
@@ -2390,7 +2423,7 @@
       } else {
         markLibraryAndClasspathMethodOverridesAsLive(instantiation, clazz);
       }
-      worklist.addIfNotSeen(Arrays.asList(clazz.interfaces.values));
+      worklist.addIfNotSeen(clazz.interfaces);
       clazz = clazz.superType != null ? appInfo().definitionFor(clazz.superType) : null;
     }
     // The targets for methods on the type and its supertype that are reachable are now marked.
@@ -3053,6 +3086,11 @@
     finalizeLibraryMethodOverrideInformation();
     analyses.forEach(analyses -> analyses.done(this));
     assert verifyKeptGraph();
+    if (mode.isInitialTreeShaking() && forceProguardCompatibility) {
+      appView.setProguardCompatibilityActions(proguardCompatibilityActionsBuilder.build());
+    } else {
+      assert proguardCompatibilityActionsBuilder == null;
+    }
     if (mode.isWhyAreYouKeeping()) {
       // For why are you keeping the information is reported through the kept graph callbacks and
       // no AppInfo is returned.
@@ -3499,7 +3537,6 @@
             noClassMerging,
             rootSet.noVerticalClassMerging,
             rootSet.noHorizontalClassMerging,
-            rootSet.noStaticClassMerging,
             rootSet.neverPropagateValue,
             joinIdentifierNameStrings(rootSet.identifierNameStrings, identifierNameStrings),
             Collections.emptySet(),
@@ -3674,7 +3711,7 @@
             }
           }
           ConsequentRootSetBuilder consequentSetBuilder =
-              new ConsequentRootSetBuilder(appView, subtypingInfo, this);
+              ConsequentRootSet.builder(appView, subtypingInfo, this);
           IfRuleEvaluator ifRuleEvaluator =
               new IfRuleEvaluator(
                   appView,
@@ -3818,7 +3855,7 @@
   }
 
   private ConsequentRootSet computeDelayedInterfaceMethodSyntheticBridges() {
-    RootSetBuilder builder = new RootSetBuilder(appView, subtypingInfo);
+    RootSetBuilder builder = RootSet.builder(appView, subtypingInfo);
     for (DelayedRootSetActionItem delayedRootSetActionItem : rootSet.delayedRootSetActionItems) {
       if (delayedRootSetActionItem.isInterfaceMethodSyntheticBridgeAction()) {
         handleInterfaceMethodSyntheticBridgeAction(
@@ -4067,7 +4104,19 @@
   }
 
   private void markClassAsInstantiatedWithCompatRule(
-      DexProgramClass clazz, KeepReasonWitness witness) {
+      DexProgramClass clazz, Supplier<KeepReason> reasonSupplier) {
+    assert forceProguardCompatibility;
+
+    if (appView.hasProguardCompatibilityActions()
+        && !appView.getProguardCompatibilityActions().isCompatInstantiated(clazz)) {
+      return;
+    }
+
+    if (mode.isInitialTreeShaking()) {
+      proguardCompatibilityActionsBuilder.addCompatInstantiatedType(clazz);
+    }
+
+    KeepReasonWitness witness = graphReporter.registerClass(clazz, reasonSupplier.get());
     if (clazz.isAnnotation()) {
       markTypeAsLive(clazz, witness);
     } else if (clazz.isInterface()) {
@@ -4144,15 +4193,8 @@
       }
       markTypeAsLive(clazz, KeepReason.reflectiveUseIn(method));
       if (clazz.canBeInstantiatedByNewInstance()
-          && identifierTypeLookupResult.isTypeInstantiatedFromUse(options)) {
-        workList.enqueueMarkInstantiatedAction(
-            clazz, null, InstantiationReason.REFLECTION, KeepReason.reflectiveUseIn(method));
-        if (clazz.hasDefaultInitializer()) {
-          ProgramMethod initializer = clazz.getProgramDefaultInitializer();
-          KeepReason reason = KeepReason.reflectiveUseIn(method);
-          markMethodAsTargeted(initializer, reason);
-          markDirectStaticOrConstructorMethodAsLive(initializer, reason);
-        }
+          && identifierTypeLookupResult.isTypeCompatInstantiatedFromUse(options)) {
+        markClassAsInstantiatedWithCompatRule(clazz, () -> KeepReason.reflectiveUseIn(method));
       } else if (identifierTypeLookupResult.isTypeInitializedFromUse()) {
         markDirectAndIndirectClassInitializersAsLive(clazz);
       }
diff --git a/src/main/java/com/android/tools/r8/shaking/FieldAccessInfoCollectionModifier.java b/src/main/java/com/android/tools/r8/shaking/FieldAccessInfoCollectionModifier.java
index f656a56..a05a33b 100644
--- a/src/main/java/com/android/tools/r8/shaking/FieldAccessInfoCollectionModifier.java
+++ b/src/main/java/com/android/tools/r8/shaking/FieldAccessInfoCollectionModifier.java
@@ -4,59 +4,108 @@
 
 package com.android.tools.r8.shaking;
 
+import com.android.tools.r8.graph.AbstractAccessContexts;
+import com.android.tools.r8.graph.AbstractAccessContexts.ConcreteAccessContexts;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.graph.FieldAccessInfoCollectionImpl;
 import com.android.tools.r8.graph.FieldAccessInfoImpl;
 import com.android.tools.r8.graph.ProgramMethod;
-import com.android.tools.r8.utils.collections.ProgramMethodSet;
 import java.util.IdentityHashMap;
 import java.util.Map;
 
 public class FieldAccessInfoCollectionModifier {
 
-  static class FieldReferences {
-    private final ProgramMethodSet writeContexts = ProgramMethodSet.create();
-    private final ProgramMethodSet readContexts = ProgramMethodSet.create();
+  private static class FieldAccessContexts {
+
+    private AbstractAccessContexts readsWithContexts = AbstractAccessContexts.empty();
+    private AbstractAccessContexts writesWithContexts = AbstractAccessContexts.empty();
+
+    void addReadContext(DexField field, ProgramMethod context) {
+      if (readsWithContexts.isBottom()) {
+        ConcreteAccessContexts concreteReadContexts = new ConcreteAccessContexts();
+        concreteReadContexts.recordAccess(field, context);
+        readsWithContexts = concreteReadContexts;
+      } else if (readsWithContexts.isConcrete()) {
+        readsWithContexts.asConcrete().recordAccess(field, context);
+      } else {
+        assert readsWithContexts.isTop();
+      }
+    }
+
+    void recordReadInUnknownContext() {
+      readsWithContexts = AbstractAccessContexts.unknown();
+    }
+
+    void addWriteContext(DexField field, ProgramMethod context) {
+      if (writesWithContexts.isBottom()) {
+        ConcreteAccessContexts concreteWriteContexts = new ConcreteAccessContexts();
+        concreteWriteContexts.recordAccess(field, context);
+        writesWithContexts = concreteWriteContexts;
+      } else if (writesWithContexts.isConcrete()) {
+        writesWithContexts.asConcrete().recordAccess(field, context);
+      } else {
+        assert writesWithContexts.isTop();
+      }
+    }
+
+    void recordWriteInUnknownContext() {
+      writesWithContexts = AbstractAccessContexts.unknown();
+    }
   }
 
-  private final Map<DexField, FieldReferences> newFieldAccesses;
+  private final Map<DexField, FieldAccessContexts> newFieldAccessContexts;
 
-  FieldAccessInfoCollectionModifier(Map<DexField, FieldReferences> newFieldAccesses) {
-    this.newFieldAccesses = newFieldAccesses;
+  private FieldAccessInfoCollectionModifier(
+      Map<DexField, FieldAccessContexts> newFieldAccessContexts) {
+    this.newFieldAccessContexts = newFieldAccessContexts;
+  }
+
+  public static Builder builder() {
+    return new Builder();
   }
 
   public void modify(AppView<AppInfoWithLiveness> appView) {
     FieldAccessInfoCollectionImpl impl = appView.appInfo().getMutableFieldAccessInfoCollection();
-    newFieldAccesses.forEach(
-        (field, info) -> {
+    newFieldAccessContexts.forEach(
+        (field, accessContexts) -> {
           FieldAccessInfoImpl fieldAccessInfo = new FieldAccessInfoImpl(field);
-          info.readContexts.forEach(context -> fieldAccessInfo.recordRead(field, context));
-          info.writeContexts.forEach(context -> fieldAccessInfo.recordWrite(field, context));
+          fieldAccessInfo.setReadsWithContexts(accessContexts.readsWithContexts);
+          fieldAccessInfo.setWritesWithContexts(accessContexts.writesWithContexts);
           impl.extend(field, fieldAccessInfo);
         });
   }
 
   public static class Builder {
 
-    private final Map<DexField, FieldReferences> newFieldAccesses = new IdentityHashMap<>();
+    private final Map<DexField, FieldAccessContexts> newFieldAccessContexts =
+        new IdentityHashMap<>();
 
     public Builder() {}
 
-    private FieldReferences getFieldReferences(DexField field) {
-      return newFieldAccesses.computeIfAbsent(field, ignore -> new FieldReferences());
+    private FieldAccessContexts getFieldAccessContexts(DexField field) {
+      return newFieldAccessContexts.computeIfAbsent(field, ignore -> new FieldAccessContexts());
     }
 
     public void recordFieldReadInContext(DexField field, ProgramMethod context) {
-      getFieldReferences(field).readContexts.add(context);
+      getFieldAccessContexts(field).addReadContext(field, context);
+    }
+
+    public Builder recordFieldReadInUnknownContext(DexField field) {
+      getFieldAccessContexts(field).recordReadInUnknownContext();
+      return this;
     }
 
     public void recordFieldWrittenInContext(DexField field, ProgramMethod context) {
-      getFieldReferences(field).writeContexts.add(context);
+      getFieldAccessContexts(field).addWriteContext(field, context);
+    }
+
+    public void recordFieldWriteInUnknownContext(DexField field) {
+      getFieldAccessContexts(field).recordWriteInUnknownContext();
     }
 
     public FieldAccessInfoCollectionModifier build() {
-      return new FieldAccessInfoCollectionModifier(newFieldAccesses);
+      return new FieldAccessInfoCollectionModifier(newFieldAccessContexts);
     }
   }
 }
diff --git a/src/main/java/com/android/tools/r8/shaking/IfRuleEvaluator.java b/src/main/java/com/android/tools/r8/shaking/IfRuleEvaluator.java
index 8f62c4d..e843d0d 100644
--- a/src/main/java/com/android/tools/r8/shaking/IfRuleEvaluator.java
+++ b/src/main/java/com/android/tools/r8/shaking/IfRuleEvaluator.java
@@ -16,7 +16,9 @@
 import com.android.tools.r8.graph.DexReference;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.SubtypingInfo;
-import com.android.tools.r8.shaking.RootSetBuilder.ConsequentRootSet;
+import com.android.tools.r8.shaking.RootSetUtils.ConsequentRootSet;
+import com.android.tools.r8.shaking.RootSetUtils.ConsequentRootSetBuilder;
+import com.android.tools.r8.shaking.RootSetUtils.RootSetBuilder;
 import com.android.tools.r8.utils.InternalOptions.TestingOptions.ProguardIfRuleEvaluationData;
 import com.android.tools.r8.utils.ThreadUtils;
 import com.google.common.base.Equivalence.Wrapper;
diff --git a/src/main/java/com/android/tools/r8/shaking/NoStaticClassMergingRule.java b/src/main/java/com/android/tools/r8/shaking/NoStaticClassMergingRule.java
deleted file mode 100644
index dc19834..0000000
--- a/src/main/java/com/android/tools/r8/shaking/NoStaticClassMergingRule.java
+++ /dev/null
@@ -1,83 +0,0 @@
-// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-
-package com.android.tools.r8.shaking;
-
-import com.android.tools.r8.origin.Origin;
-import com.android.tools.r8.position.Position;
-import java.util.List;
-
-public class NoStaticClassMergingRule extends ProguardConfigurationRule {
-  public static final String RULE_NAME = "nostaticclassmerging";
-
-  public static class Builder
-      extends ProguardConfigurationRule.Builder<NoStaticClassMergingRule, Builder> {
-
-    private Builder() {
-      super();
-    }
-
-    @Override
-    public NoStaticClassMergingRule.Builder self() {
-      return this;
-    }
-
-    @Override
-    public NoStaticClassMergingRule build() {
-      return new NoStaticClassMergingRule(
-          origin,
-          getPosition(),
-          source,
-          buildClassAnnotations(),
-          classAccessFlags,
-          negatedClassAccessFlags,
-          classTypeNegated,
-          classType,
-          classNames,
-          buildInheritanceAnnotations(),
-          inheritanceClassName,
-          inheritanceIsExtends,
-          memberRules);
-    }
-  }
-
-  private NoStaticClassMergingRule(
-      Origin origin,
-      Position position,
-      String source,
-      List<ProguardTypeMatcher> classAnnotations,
-      ProguardAccessFlags classAccessFlags,
-      ProguardAccessFlags negatedClassAccessFlags,
-      boolean classTypeNegated,
-      ProguardClassType classType,
-      ProguardClassNameList classNames,
-      List<ProguardTypeMatcher> inheritanceAnnotations,
-      ProguardTypeMatcher inheritanceClassName,
-      boolean inheritanceIsExtends,
-      List<ProguardMemberRule> memberRules) {
-    super(
-        origin,
-        position,
-        source,
-        classAnnotations,
-        classAccessFlags,
-        negatedClassAccessFlags,
-        classTypeNegated,
-        classType,
-        classNames,
-        inheritanceAnnotations,
-        inheritanceClassName,
-        inheritanceIsExtends,
-        memberRules);
-  }
-
-  public static Builder builder() {
-    return new Builder();
-  }
-
-  @Override
-  String typeString() {
-    return RULE_NAME;
-  }
-}
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardCompatibilityActions.java b/src/main/java/com/android/tools/r8/shaking/ProguardCompatibilityActions.java
new file mode 100644
index 0000000..eed4fec
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardCompatibilityActions.java
@@ -0,0 +1,64 @@
+// 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.shaking;
+
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.GraphLens;
+import com.android.tools.r8.graph.PrunedItems;
+import com.google.common.collect.Sets;
+import java.util.Set;
+
+public class ProguardCompatibilityActions {
+
+  private final Set<DexType> compatInstantiatedTypes;
+
+  private ProguardCompatibilityActions(Set<DexType> compatInstantiatedTypes) {
+    this.compatInstantiatedTypes = compatInstantiatedTypes;
+  }
+
+  public static Builder builder() {
+    return new Builder();
+  }
+
+  public boolean isCompatInstantiated(DexProgramClass clazz) {
+    return compatInstantiatedTypes.contains(clazz.getType());
+  }
+
+  public ProguardCompatibilityActions withoutPrunedItems(PrunedItems prunedItems) {
+    Builder builder = builder();
+    for (DexType compatInstantiatedType : compatInstantiatedTypes) {
+      if (!prunedItems.isRemoved(compatInstantiatedType)) {
+        builder.addCompatInstantiatedType(compatInstantiatedType);
+      }
+    }
+    return builder.build();
+  }
+
+  public ProguardCompatibilityActions rewrittenWithLens(GraphLens lens) {
+    Builder builder = builder();
+    for (DexType compatInstantiatedType : compatInstantiatedTypes) {
+      builder.addCompatInstantiatedType(lens.lookupType(compatInstantiatedType));
+    }
+    return builder.build();
+  }
+
+  public static class Builder {
+
+    private final Set<DexType> compatInstantiatedTypes = Sets.newIdentityHashSet();
+
+    public void addCompatInstantiatedType(DexProgramClass clazz) {
+      addCompatInstantiatedType(clazz.getType());
+    }
+
+    private void addCompatInstantiatedType(DexType type) {
+      compatInstantiatedTypes.add(type);
+    }
+
+    public ProguardCompatibilityActions build() {
+      return new ProguardCompatibilityActions(compatInstantiatedTypes);
+    }
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
index 49e9f1e..2871b74 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
@@ -500,11 +500,6 @@
           configurationBuilder.addRule(rule);
           return true;
         }
-        if (acceptString(NoStaticClassMergingRule.RULE_NAME)) {
-          ProguardConfigurationRule rule = parseNoStaticClassMergingRule(optionStart);
-          configurationBuilder.addRule(rule);
-          return true;
-        }
         if (acceptString("neverpropagatevalue")) {
           MemberValuePropagationRule rule =
               parseMemberValuePropagationRule(MemberValuePropagationRule.Type.NEVER, optionStart);
@@ -805,17 +800,6 @@
       return keepRuleBuilder.build();
     }
 
-    private NoStaticClassMergingRule parseNoStaticClassMergingRule(Position start)
-        throws ProguardRuleParserException {
-      NoStaticClassMergingRule.Builder keepRuleBuilder =
-          NoStaticClassMergingRule.builder().setOrigin(origin).setStart(start);
-      parseClassSpec(keepRuleBuilder, false);
-      Position end = getPosition();
-      keepRuleBuilder.setSource(getSourceSnippet(contents, start, end));
-      keepRuleBuilder.setEnd(end);
-      return keepRuleBuilder.build();
-    }
-
     private MemberValuePropagationRule parseMemberValuePropagationRule(
         MemberValuePropagationRule.Type type, Position start)
         throws ProguardRuleParserException {
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardMemberRule.java b/src/main/java/com/android/tools/r8/shaking/ProguardMemberRule.java
index 5163a69..1b80a1e 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardMemberRule.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardMemberRule.java
@@ -12,6 +12,7 @@
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.shaking.ProguardConfigurationParser.IdentifierPatternWithWildcards;
+import com.android.tools.r8.shaking.RootSetUtils.RootSetBuilder;
 import com.android.tools.r8.utils.StringUtils;
 import com.google.common.collect.Iterables;
 import java.util.Collections;
diff --git a/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java b/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java
deleted file mode 100644
index 33bfc85..0000000
--- a/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java
+++ /dev/null
@@ -1,2142 +0,0 @@
-// Copyright (c) 2016, the R8 project authors. Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-package com.android.tools.r8.shaking;
-
-import static com.android.tools.r8.graph.DexProgramClass.asProgramClassOrNull;
-
-import com.android.tools.r8.dex.Constants;
-import com.android.tools.r8.errors.AssumeNoSideEffectsRuleForObjectMembersDiagnostic;
-import com.android.tools.r8.errors.Unreachable;
-import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
-import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.BottomUpClassHierarchyTraversal;
-import com.android.tools.r8.graph.DexAnnotation;
-import com.android.tools.r8.graph.DexAnnotationSet;
-import com.android.tools.r8.graph.DexClass;
-import com.android.tools.r8.graph.DexDefinition;
-import com.android.tools.r8.graph.DexDefinitionSupplier;
-import com.android.tools.r8.graph.DexEncodedField;
-import com.android.tools.r8.graph.DexEncodedMember;
-import com.android.tools.r8.graph.DexEncodedMethod;
-import com.android.tools.r8.graph.DexField;
-import com.android.tools.r8.graph.DexLibraryClass;
-import com.android.tools.r8.graph.DexMember;
-import com.android.tools.r8.graph.DexMethod;
-import com.android.tools.r8.graph.DexProgramClass;
-import com.android.tools.r8.graph.DexReference;
-import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.graph.DirectMappedDexApplication;
-import com.android.tools.r8.graph.GraphLens;
-import com.android.tools.r8.graph.ProgramMember;
-import com.android.tools.r8.graph.ProgramMethod;
-import com.android.tools.r8.graph.ResolutionResult.SingleResolutionResult;
-import com.android.tools.r8.graph.SubtypingInfo;
-import com.android.tools.r8.ir.analysis.proto.GeneratedMessageLiteBuilderShrinker;
-import com.android.tools.r8.logging.Log;
-import com.android.tools.r8.shaking.AnnotationMatchResult.AnnotationsIgnoredMatchResult;
-import com.android.tools.r8.shaking.AnnotationMatchResult.ConcreteAnnotationMatchResult;
-import com.android.tools.r8.shaking.DelayedRootSetActionItem.InterfaceMethodSyntheticBridgeAction;
-import com.android.tools.r8.utils.ArrayUtils;
-import com.android.tools.r8.utils.Consumer3;
-import com.android.tools.r8.utils.InternalOptions;
-import com.android.tools.r8.utils.MethodSignatureEquivalence;
-import com.android.tools.r8.utils.OriginWithPosition;
-import com.android.tools.r8.utils.PredicateSet;
-import com.android.tools.r8.utils.StringDiagnostic;
-import com.android.tools.r8.utils.ThreadUtils;
-import com.google.common.base.Equivalence.Wrapper;
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.ImmutableMap;
-import com.google.common.collect.ImmutableSet;
-import com.google.common.collect.Lists;
-import com.google.common.collect.Sets;
-import com.google.common.collect.Streams;
-import java.io.PrintStream;
-import java.util.ArrayDeque;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.Deque;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.IdentityHashMap;
-import java.util.LinkedHashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Map.Entry;
-import java.util.Queue;
-import java.util.Set;
-import java.util.Stack;
-import java.util.TreeSet;
-import java.util.concurrent.ConcurrentLinkedQueue;
-import java.util.concurrent.ExecutionException;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Future;
-import java.util.function.BiConsumer;
-import java.util.function.Consumer;
-import java.util.function.Predicate;
-import java.util.stream.Collectors;
-
-public class RootSetBuilder {
-
-  private final AppView<? extends AppInfoWithClassHierarchy> appView;
-  private final SubtypingInfo subtypingInfo;
-  private final DirectMappedDexApplication application;
-  private final Iterable<? extends ProguardConfigurationRule> rules;
-  private final MutableItemsWithRules noShrinking = new MutableItemsWithRules();
-  private final MutableItemsWithRules softPinned = new MutableItemsWithRules();
-  private final Set<DexReference> noObfuscation = Sets.newIdentityHashSet();
-  private final LinkedHashMap<DexReference, DexReference> reasonAsked = new LinkedHashMap<>();
-  private final LinkedHashMap<DexReference, DexReference> checkDiscarded = new LinkedHashMap<>();
-  private final Set<DexMethod> alwaysInline = Sets.newIdentityHashSet();
-  private final Set<DexMethod> forceInline = Sets.newIdentityHashSet();
-  private final Set<DexMethod> neverInline = Sets.newIdentityHashSet();
-  private final Set<DexMethod> neverInlineDueToSingleCaller = Sets.newIdentityHashSet();
-  private final Set<DexMethod> bypassClinitforInlining = Sets.newIdentityHashSet();
-  private final Set<DexMethod> whyAreYouNotInlining = Sets.newIdentityHashSet();
-  private final Set<DexMethod> keepParametersWithConstantValue = Sets.newIdentityHashSet();
-  private final Set<DexMethod> keepUnusedArguments = Sets.newIdentityHashSet();
-  private final Set<DexMethod> reprocess = Sets.newIdentityHashSet();
-  private final Set<DexMethod> neverReprocess = Sets.newIdentityHashSet();
-  private final PredicateSet<DexType> alwaysClassInline = new PredicateSet<>();
-  private final Set<DexType> neverClassInline = Sets.newIdentityHashSet();
-  private final Set<DexType> noUnusedInterfaceRemoval = Sets.newIdentityHashSet();
-  private final Set<DexType> noVerticalClassMerging = Sets.newIdentityHashSet();
-  private final Set<DexType> noHorizontalClassMerging = Sets.newIdentityHashSet();
-  private final Set<DexType> noStaticClassMerging = Sets.newIdentityHashSet();
-  private final Set<DexReference> neverPropagateValue = Sets.newIdentityHashSet();
-  private final Map<DexReference, MutableItemsWithRules> dependentNoShrinking =
-      new IdentityHashMap<>();
-  private final Map<DexReference, MutableItemsWithRules> dependentSoftPinned =
-      new IdentityHashMap<>();
-  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 Queue<DelayedRootSetActionItem> delayedRootSetActionItems =
-      new ConcurrentLinkedQueue<>();
-  private final InternalOptions options;
-
-  private final DexStringCache dexStringCache = new DexStringCache();
-  private final Set<ProguardIfRule> ifRules = Sets.newIdentityHashSet();
-
-  private final Map<OriginWithPosition, Set<DexMethod>> assumeNoSideEffectsWarnings =
-      new LinkedHashMap<>();
-
-  public RootSetBuilder(
-      AppView<? extends AppInfoWithClassHierarchy> appView,
-      SubtypingInfo subtypingInfo,
-      Iterable<? extends ProguardConfigurationRule> rules) {
-    this.appView = appView;
-    this.subtypingInfo = subtypingInfo;
-    this.application = appView.appInfo().app().asDirect();
-    this.rules = rules;
-    this.options = appView.options();
-  }
-
-  public RootSetBuilder(
-      AppView<? extends AppInfoWithClassHierarchy> appView, SubtypingInfo subtypingInfo) {
-    this(appView, subtypingInfo, null);
-  }
-
-  void handleMatchedAnnotation(AnnotationMatchResult annotation) {
-    // Intentionally empty.
-  }
-
-  // Process a class with the keep rule.
-  private void process(
-      DexClass clazz,
-      ProguardConfigurationRule rule,
-      ProguardIfRule ifRule) {
-    if (!satisfyClassType(rule, clazz)) {
-      return;
-    }
-    if (!satisfyAccessFlag(rule, clazz)) {
-      return;
-    }
-    AnnotationMatchResult annotationMatchResult = satisfyAnnotation(rule, clazz);
-    if (annotationMatchResult == null) {
-      return;
-    }
-    handleMatchedAnnotation(annotationMatchResult);
-    // In principle it should make a difference whether the user specified in a class
-    // spec that a class either extends or implements another type. However, proguard
-    // seems not to care, so users have started to use this inconsistently. We are thus
-    // inconsistent, as well, but tell them.
-    // TODO(herhut): One day make this do what it says.
-    if (rule.hasInheritanceClassName() && !satisfyInheritanceRule(clazz, rule)) {
-      return;
-    }
-
-    if (!rule.getClassNames().matches(clazz.type)) {
-      return;
-    }
-
-    Collection<ProguardMemberRule> memberKeepRules = rule.getMemberRules();
-    Map<Predicate<DexDefinition>, DexDefinition> preconditionSupplier;
-    if (rule instanceof ProguardKeepRule) {
-      if (clazz.isNotProgramClass()) {
-        return;
-      }
-      switch (((ProguardKeepRule) rule).getType()) {
-        case KEEP_CLASS_MEMBERS:
-          // Members mentioned at -keepclassmembers always depend on their holder.
-          preconditionSupplier = ImmutableMap.of(definition -> true, clazz);
-          markMatchingVisibleMethods(
-              clazz, memberKeepRules, rule, preconditionSupplier, false, ifRule);
-          markMatchingVisibleFields(
-              clazz, memberKeepRules, rule, preconditionSupplier, false, ifRule);
-          break;
-        case KEEP_CLASSES_WITH_MEMBERS:
-          if (!allRulesSatisfied(memberKeepRules, clazz)) {
-            break;
-          }
-          // fall through;
-        case KEEP:
-          markClass(clazz, rule, ifRule);
-          preconditionSupplier = new HashMap<>();
-          if (ifRule != null) {
-            // Static members in -keep are pinned no matter what.
-            preconditionSupplier.put(DexDefinition::isStaticMember, null);
-            // Instance members may need to be kept even though the holder is not instantiated.
-            preconditionSupplier.put(definition -> !definition.isStaticMember(), clazz);
-          } else {
-            // Members mentioned at -keep should always be pinned as long as that -keep rule is
-            // not triggered conditionally.
-            preconditionSupplier.put((definition -> true), null);
-          }
-          markMatchingVisibleMethods(
-              clazz, memberKeepRules, rule, preconditionSupplier, false, ifRule);
-          markMatchingVisibleFields(
-              clazz, memberKeepRules, rule, preconditionSupplier, false, ifRule);
-          break;
-        case CONDITIONAL:
-          throw new Unreachable("-if rule will be evaluated separately, not here.");
-      }
-      return;
-    }
-    // Only the ordinary keep rules are supported in a conditional rule.
-    assert ifRule == null;
-    if (rule instanceof ProguardIfRule) {
-      throw new Unreachable("-if rule will be evaluated separately, not here.");
-    } else if (rule instanceof ProguardCheckDiscardRule) {
-      if (memberKeepRules.isEmpty()) {
-        markClass(clazz, rule, ifRule);
-      } else {
-        preconditionSupplier = ImmutableMap.of((definition -> true), clazz);
-        markMatchingVisibleMethods(
-            clazz, memberKeepRules, rule, preconditionSupplier, true, ifRule);
-        markMatchingVisibleFields(clazz, memberKeepRules, rule, preconditionSupplier, true, ifRule);
-      }
-    } else if (rule instanceof ProguardWhyAreYouKeepingRule) {
-      markClass(clazz, rule, ifRule);
-      markMatchingVisibleMethods(clazz, memberKeepRules, rule, null, true, ifRule);
-      markMatchingVisibleFields(clazz, memberKeepRules, rule, null, true, ifRule);
-    } else if (rule instanceof ProguardAssumeMayHaveSideEffectsRule
-        || rule instanceof ProguardAssumeNoSideEffectRule
-        || rule instanceof ProguardAssumeValuesRule) {
-      markMatchingVisibleMethods(clazz, memberKeepRules, rule, null, true, ifRule);
-      markMatchingOverriddenMethods(
-          appView.appInfo(), clazz, memberKeepRules, rule, null, true, ifRule);
-      markMatchingVisibleFields(clazz, memberKeepRules, rule, null, true, ifRule);
-    } else if (rule instanceof InlineRule
-        || rule instanceof ConstantArgumentRule
-        || rule instanceof UnusedArgumentRule
-        || rule instanceof ReprocessMethodRule
-        || rule instanceof WhyAreYouNotInliningRule) {
-      markMatchingMethods(clazz, memberKeepRules, rule, null, ifRule);
-    } else if (rule instanceof ClassInlineRule
-        || rule instanceof NoUnusedInterfaceRemovalRule
-        || rule instanceof NoVerticalClassMergingRule
-        || rule instanceof NoHorizontalClassMergingRule
-        || rule instanceof NoStaticClassMergingRule
-        || rule instanceof ReprocessClassInitializerRule) {
-      if (allRulesSatisfied(memberKeepRules, clazz)) {
-        markClass(clazz, rule, ifRule);
-      }
-    } else if (rule instanceof MemberValuePropagationRule) {
-      markMatchingVisibleMethods(clazz, memberKeepRules, rule, null, true, ifRule);
-      markMatchingVisibleFields(clazz, memberKeepRules, rule, null, true, ifRule);
-    } else {
-      assert rule instanceof ProguardIdentifierNameStringRule;
-      markMatchingFields(clazz, memberKeepRules, rule, null, ifRule);
-      markMatchingMethods(clazz, memberKeepRules, rule, null, ifRule);
-    }
-  }
-
-  void runPerRule(
-      ExecutorService executorService,
-      List<Future<?>> futures,
-      ProguardConfigurationRule rule,
-      ProguardIfRule ifRule) {
-    List<DexType> specifics = rule.getClassNames().asSpecificDexTypes();
-    if (specifics != null) {
-      // This keep rule only lists specific type matches.
-      // This means there is no need to iterate over all classes.
-      for (DexType type : specifics) {
-        DexClass clazz = application.definitionFor(type);
-        // Ignore keep rule iff it does not reference a class in the app.
-        if (clazz != null) {
-          process(clazz, rule, ifRule);
-        }
-      }
-      return;
-    }
-
-    futures.add(
-        executorService.submit(
-            () -> {
-              for (DexProgramClass clazz :
-                  rule.relevantCandidatesForRule(appView, subtypingInfo, application.classes())) {
-                process(clazz, rule, ifRule);
-              }
-              if (rule.applyToNonProgramClasses()) {
-                for (DexLibraryClass clazz : application.libraryClasses()) {
-                  process(clazz, rule, ifRule);
-                }
-              }
-            }));
-  }
-
-  public RootSet run(ExecutorService executorService) throws ExecutionException {
-    application.timing.begin("Build root set...");
-    try {
-      List<Future<?>> futures = new ArrayList<>();
-      // Mark all the things explicitly listed in keep rules.
-      if (rules != null) {
-        for (ProguardConfigurationRule rule : rules) {
-          if (rule instanceof ProguardIfRule) {
-            ProguardIfRule ifRule = (ProguardIfRule) rule;
-            ifRules.add(ifRule);
-          } else {
-            runPerRule(executorService, futures, rule, null);
-          }
-        }
-        ThreadUtils.awaitFutures(futures);
-      }
-    } finally {
-      application.timing.end();
-    }
-    generateAssumeNoSideEffectsWarnings();
-    if (!noSideEffects.isEmpty() || !assumedValues.isEmpty()) {
-      BottomUpClassHierarchyTraversal.forAllClasses(appView, subtypingInfo)
-          .visit(appView.appInfo().classes(), this::propagateAssumeRules);
-    }
-    if (appView.options().protoShrinking().enableGeneratedMessageLiteBuilderShrinking) {
-      GeneratedMessageLiteBuilderShrinker.addInliningHeuristicsForBuilderInlining(
-          appView,
-          subtypingInfo,
-          alwaysClassInline,
-          noVerticalClassMerging,
-          noHorizontalClassMerging,
-          noStaticClassMerging,
-          alwaysInline,
-          bypassClinitforInlining);
-    }
-    assert Sets.intersection(neverInline, alwaysInline).isEmpty()
-            && Sets.intersection(neverInline, forceInline).isEmpty()
-        : "A method cannot be marked as both -neverinline and -forceinline/-alwaysinline.";
-    assert appView.options().isMinificationEnabled() || noObfuscation.isEmpty();
-    return new RootSet(
-        noShrinking,
-        softPinned,
-        noObfuscation,
-        ImmutableList.copyOf(reasonAsked.values()),
-        ImmutableList.copyOf(checkDiscarded.values()),
-        alwaysInline,
-        forceInline,
-        neverInline,
-        neverInlineDueToSingleCaller,
-        bypassClinitforInlining,
-        whyAreYouNotInlining,
-        keepParametersWithConstantValue,
-        keepUnusedArguments,
-        reprocess,
-        neverReprocess,
-        alwaysClassInline,
-        neverClassInline,
-        noUnusedInterfaceRemoval,
-        noVerticalClassMerging,
-        noHorizontalClassMerging,
-        noStaticClassMerging,
-        neverPropagateValue,
-        mayHaveSideEffects,
-        noSideEffects,
-        assumedValues,
-        dependentNoShrinking,
-        dependentSoftPinned,
-        dependentKeepClassCompatRule,
-        identifierNameStrings,
-        ifRules,
-        Lists.newArrayList(delayedRootSetActionItems));
-  }
-
-  private void propagateAssumeRules(DexClass clazz) {
-    Set<DexType> subTypes = subtypingInfo.allImmediateSubtypes(clazz.type);
-    if (subTypes.isEmpty()) {
-      return;
-    }
-    for (DexEncodedMethod encodedMethod : clazz.virtualMethods()) {
-      // If the method has a body, it may have side effects. Don't do bottom-up propagation.
-      if (encodedMethod.hasCode()) {
-        assert !encodedMethod.shouldNotHaveCode();
-        continue;
-      }
-      propagateAssumeRules(clazz.type, encodedMethod.method, subTypes, noSideEffects);
-      propagateAssumeRules(clazz.type, encodedMethod.method, subTypes, assumedValues);
-    }
-  }
-
-  private void propagateAssumeRules(
-      DexType type,
-      DexMethod reference,
-      Set<DexType> subTypes,
-      Map<DexMember<?, ?>, ProguardMemberRule> assumeRulePool) {
-    ProguardMemberRule ruleToBePropagated = null;
-    for (DexType subType : subTypes) {
-      DexMethod referenceInSubType =
-          appView.dexItemFactory().createMethod(subType, reference.proto, reference.name);
-      // Those rules are bound to definitions, not references. If the current subtype does not
-      // override the method, and when the retrieval of bound rule fails, it is unclear whether it
-      // is due to the lack of the definition or it indeed means no matching rules. Similar to how
-      // we apply those assume rules, here we use a resolved target.
-      DexEncodedMethod target =
-          appView.appInfo().unsafeResolveMethodDueToDexFormat(referenceInSubType).getSingleTarget();
-      // But, the resolution should not be landed on the current type we are visiting.
-      if (target == null || target.getHolderType() == type) {
-        continue;
-      }
-      ProguardMemberRule ruleInSubType = assumeRulePool.get(target.method);
-      // We are looking for the greatest lower bound of assume rules from all sub types.
-      // If any subtype doesn't have a matching assume rule, the lower bound is literally nothing.
-      if (ruleInSubType == null) {
-        ruleToBePropagated = null;
-        break;
-      }
-      if (ruleToBePropagated == null) {
-        ruleToBePropagated = ruleInSubType;
-      } else {
-        // TODO(b/133208961): Introduce comparison/meet of assume rules.
-        if (!ruleToBePropagated.equals(ruleInSubType)) {
-          ruleToBePropagated = null;
-          break;
-        }
-      }
-    }
-    if (ruleToBePropagated != null) {
-      assumeRulePool.put(reference, ruleToBePropagated);
-    }
-  }
-
-  ConsequentRootSet buildConsequentRootSet() {
-    return new ConsequentRootSet(
-        neverInline,
-        neverInlineDueToSingleCaller,
-        neverClassInline,
-        noShrinking,
-        softPinned,
-        noObfuscation,
-        dependentNoShrinking,
-        dependentSoftPinned,
-        dependentKeepClassCompatRule,
-        Lists.newArrayList(delayedRootSetActionItems));
-  }
-
-  private static DexDefinition testAndGetPrecondition(
-      DexDefinition definition, Map<Predicate<DexDefinition>, DexDefinition> preconditionSupplier) {
-    if (preconditionSupplier == null) {
-      return null;
-    }
-    DexDefinition precondition = null;
-    boolean conditionEverMatched = false;
-    for (Entry<Predicate<DexDefinition>, DexDefinition> entry : preconditionSupplier.entrySet()) {
-      if (entry.getKey().test(definition)) {
-        precondition = entry.getValue();
-        conditionEverMatched = true;
-        break;
-      }
-    }
-    // If precondition-supplier is given, there should be at least one predicate that holds.
-    // Actually, there should be only one predicate as we break the loop when it is found.
-    assert conditionEverMatched;
-    return precondition;
-  }
-
-  private void markMatchingVisibleMethods(
-      DexClass clazz,
-      Collection<ProguardMemberRule> memberKeepRules,
-      ProguardConfigurationRule rule,
-      Map<Predicate<DexDefinition>, DexDefinition> preconditionSupplier,
-      boolean includeLibraryClasses,
-      ProguardIfRule ifRule) {
-    Set<Wrapper<DexMethod>> methodsMarked =
-        options.forceProguardCompatibility ? null : new HashSet<>();
-    Stack<DexClass> worklist = new Stack<>();
-    worklist.add(clazz);
-    while (!worklist.isEmpty()) {
-      DexClass currentClass = worklist.pop();
-      if (!includeLibraryClasses && currentClass.isNotProgramClass()) {
-        break;
-      }
-      // In compat mode traverse all direct methods in the hierarchy.
-      if (currentClass == clazz || options.forceProguardCompatibility) {
-        currentClass
-            .directMethods()
-            .forEach(
-                method -> {
-                  DexDefinition precondition = testAndGetPrecondition(method, preconditionSupplier);
-                  markMethod(method, memberKeepRules, methodsMarked, rule, precondition, ifRule);
-                });
-      }
-      currentClass
-          .virtualMethods()
-          .forEach(
-              method -> {
-                DexDefinition precondition = testAndGetPrecondition(method, preconditionSupplier);
-                markMethod(method, memberKeepRules, methodsMarked, rule, precondition, ifRule);
-              });
-      if (currentClass.superType != null) {
-        DexClass dexClass = application.definitionFor(currentClass.superType);
-        if (dexClass != null) {
-          worklist.add(dexClass);
-        }
-      }
-    }
-    // TODO(b/143643942): Generalize the below approach to also work for subtyping hierarchies in
-    //  fullmode.
-    if (clazz.isProgramClass()
-        && rule.isProguardKeepRule()
-        && !rule.asProguardKeepRule().getModifiers().allowsShrinking) {
-      new SynthesizeMissingInterfaceMethodsForMemberRules(
-              clazz.asProgramClass(), memberKeepRules, rule, preconditionSupplier, ifRule)
-          .run();
-    }
-  }
-
-  /**
-   * Utility class for visiting all super interfaces to ensure we keep method definitions specified
-   * by proguard rules. If possible, we generate a forwarding bridge to the resolved target. If not,
-   * we specifically synthesize a keep rule for the interface method.
-   */
-  private class SynthesizeMissingInterfaceMethodsForMemberRules {
-    private final DexProgramClass originalClazz;
-    private final Collection<ProguardMemberRule> memberKeepRules;
-    private final ProguardConfigurationRule context;
-    private final Map<Predicate<DexDefinition>, DexDefinition> preconditionSupplier;
-    private final ProguardIfRule ifRule;
-    private final Set<Wrapper<DexMethod>> seenMethods = Sets.newHashSet();
-    private final Set<DexType> seenTypes = Sets.newIdentityHashSet();
-
-    private SynthesizeMissingInterfaceMethodsForMemberRules(
-        DexProgramClass originalClazz,
-        Collection<ProguardMemberRule> memberKeepRules,
-        ProguardConfigurationRule context,
-        Map<Predicate<DexDefinition>, DexDefinition> preconditionSupplier,
-        ProguardIfRule ifRule) {
-      assert context.isProguardKeepRule();
-      assert !context.asProguardKeepRule().getModifiers().allowsShrinking;
-      this.originalClazz = originalClazz;
-      this.memberKeepRules = memberKeepRules;
-      this.context = context;
-      this.preconditionSupplier = preconditionSupplier;
-      this.ifRule = ifRule;
-    }
-
-    void handleMatchedAnnotation(AnnotationMatchResult annotationMatchResult) {
-      // Intentionally empty.
-    }
-
-    void run() {
-      visitAllSuperInterfaces(originalClazz.type);
-    }
-
-    private void visitAllSuperInterfaces(DexType type) {
-      DexClass clazz = appView.definitionFor(type);
-      if (clazz == null || clazz.isNotProgramClass() || !seenTypes.add(type)) {
-        return;
-      }
-      for (DexType iface : clazz.interfaces.values) {
-        visitAllSuperInterfaces(iface);
-      }
-      if (!clazz.isInterface()) {
-        visitAllSuperInterfaces(clazz.superType);
-        return;
-      }
-      if (originalClazz == clazz) {
-        return;
-      }
-      for (DexEncodedMethod method : clazz.virtualMethods()) {
-        // Check if we already added this.
-        Wrapper<DexMethod> wrapped = MethodSignatureEquivalence.get().wrap(method.method);
-        if (!seenMethods.add(wrapped)) {
-          continue;
-        }
-        for (ProguardMemberRule rule : memberKeepRules) {
-          if (rule.matches(method, appView, this::handleMatchedAnnotation, dexStringCache)) {
-            tryAndKeepMethodOnClass(method, rule);
-          }
-        }
-      }
-    }
-
-    private void tryAndKeepMethodOnClass(DexEncodedMethod method, ProguardMemberRule rule) {
-      SingleResolutionResult resolutionResult =
-          appView.appInfo().resolveMethodOn(originalClazz, method.method).asSingleResolution();
-      if (resolutionResult == null || !resolutionResult.isVirtualTarget()) {
-        return;
-      }
-      if (resolutionResult.getResolvedHolder() == originalClazz
-          || resolutionResult.getResolvedHolder().isNotProgramClass()) {
-        return;
-      }
-      if (!resolutionResult.getResolvedHolder().isInterface()) {
-        // TODO(b/143643942): For fullmode, this check should probably be removed.
-        return;
-      }
-      ProgramMethod resolutionMethod =
-          new ProgramMethod(
-              resolutionResult.getResolvedHolder().asProgramClass(),
-              resolutionResult.getResolvedMethod());
-      ProgramMethod methodToKeep =
-          canInsertForwardingMethod(originalClazz, resolutionMethod.getDefinition())
-              ? new ProgramMethod(
-                  originalClazz,
-                  resolutionMethod.getDefinition().toForwardingMethod(originalClazz, appView))
-              : resolutionMethod;
-
-      delayedRootSetActionItems.add(
-          new InterfaceMethodSyntheticBridgeAction(
-              methodToKeep,
-              resolutionMethod,
-              (rootSetBuilder) -> {
-                if (Log.ENABLED) {
-                  Log.verbose(
-                      getClass(),
-                      "Marking method `%s` due to `%s { %s }`.",
-                      methodToKeep,
-                      context,
-                      rule);
-                }
-                DexDefinition precondition =
-                    testAndGetPrecondition(methodToKeep.getDefinition(), preconditionSupplier);
-                rootSetBuilder.addItemToSets(
-                    methodToKeep.getDefinition(), context, rule, precondition, ifRule);
-              }));
-    }
-  }
-
-  private boolean canInsertForwardingMethod(DexClass holder, DexEncodedMethod target) {
-    return appView.options().isGeneratingDex()
-        || ArrayUtils.contains(holder.interfaces.values, target.getHolderType());
-  }
-
-  private void markMatchingOverriddenMethods(
-      AppInfoWithClassHierarchy appInfoWithSubtyping,
-      DexClass clazz,
-      Collection<ProguardMemberRule> memberKeepRules,
-      ProguardConfigurationRule rule,
-      Map<Predicate<DexDefinition>, DexDefinition> preconditionSupplier,
-      boolean onlyIncludeProgramClasses,
-      ProguardIfRule ifRule) {
-    Set<DexType> visited = new HashSet<>();
-    Deque<DexType> worklist = new ArrayDeque<>();
-    // Intentionally skip the current `clazz`, assuming it's covered by markMatchingVisibleMethods.
-    worklist.addAll(subtypingInfo.allImmediateSubtypes(clazz.type));
-
-    while (!worklist.isEmpty()) {
-      DexType currentType = worklist.poll();
-      if (!visited.add(currentType)) {
-        continue;
-      }
-      DexClass currentClazz = appView.definitionFor(currentType);
-      if (currentClazz == null) {
-        continue;
-      }
-      if (!onlyIncludeProgramClasses && currentClazz.isNotProgramClass()) {
-        continue;
-      }
-      currentClazz
-          .virtualMethods()
-          .forEach(
-              method -> {
-                DexDefinition precondition = testAndGetPrecondition(method, preconditionSupplier);
-                markMethod(method, memberKeepRules, null, rule, precondition, ifRule);
-              });
-      worklist.addAll(subtypingInfo.allImmediateSubtypes(currentClazz.type));
-    }
-  }
-
-  private void markMatchingMethods(
-      DexClass clazz,
-      Collection<ProguardMemberRule> memberKeepRules,
-      ProguardConfigurationRule rule,
-      Map<Predicate<DexDefinition>, DexDefinition> preconditionSupplier,
-      ProguardIfRule ifRule) {
-    clazz.forEachMethod(
-        method -> {
-          DexDefinition precondition = testAndGetPrecondition(method, preconditionSupplier);
-          markMethod(method, memberKeepRules, null, rule, precondition, ifRule);
-        });
-  }
-
-  private void markMatchingVisibleFields(
-      DexClass clazz,
-      Collection<ProguardMemberRule> memberKeepRules,
-      ProguardConfigurationRule rule,
-      Map<Predicate<DexDefinition>, DexDefinition> preconditionSupplier,
-      boolean includeLibraryClasses,
-      ProguardIfRule ifRule) {
-    while (clazz != null) {
-      if (!includeLibraryClasses && clazz.isNotProgramClass()) {
-        return;
-      }
-      clazz.forEachField(
-          field -> {
-            DexDefinition precondition = testAndGetPrecondition(field, preconditionSupplier);
-            markField(field, memberKeepRules, rule, precondition, ifRule);
-          });
-      clazz = clazz.superType == null ? null : application.definitionFor(clazz.superType);
-    }
-  }
-
-  private void markMatchingFields(
-      DexClass clazz,
-      Collection<ProguardMemberRule> memberKeepRules,
-      ProguardConfigurationRule rule,
-      Map<Predicate<DexDefinition>, DexDefinition> preconditionSupplier,
-      ProguardIfRule ifRule) {
-    clazz.forEachField(
-        field -> {
-          DexDefinition precondition = testAndGetPrecondition(field, preconditionSupplier);
-          markField(field, memberKeepRules, rule, precondition, ifRule);
-        });
-  }
-
-  // TODO(b/67934426): Test this code.
-  public static void writeSeeds(
-      AppInfoWithLiveness appInfo, PrintStream out, Predicate<DexType> include) {
-    appInfo
-        .getKeepInfo()
-        .forEachPinnedType(
-            type -> {
-              if (include.test(type)) {
-                out.println(type.toSourceString());
-              }
-            });
-    appInfo
-        .getKeepInfo()
-        .forEachPinnedField(
-            field -> {
-              if (include.test(field.holder)) {
-                out.println(
-                    field.holder.toSourceString()
-                        + ": "
-                        + field.type.toSourceString()
-                        + " "
-                        + field.name.toSourceString());
-              }
-            });
-    appInfo
-        .getKeepInfo()
-        .forEachPinnedMethod(
-            method -> {
-              if (!include.test(method.holder)) {
-                return;
-              }
-              DexProgramClass holder = asProgramClassOrNull(appInfo.definitionForHolder(method));
-              DexEncodedMethod definition = method.lookupOnClass(holder);
-              if (definition == null) {
-                assert method.match(appInfo.dexItemFactory().deserializeLambdaMethod);
-                return;
-              }
-              out.print(method.holder.toSourceString() + ": ");
-              if (definition.isClassInitializer()) {
-                out.print(Constants.CLASS_INITIALIZER_NAME);
-              } else if (definition.isInstanceInitializer()) {
-                String holderName = method.holder.toSourceString();
-                String constrName = holderName.substring(holderName.lastIndexOf('.') + 1);
-                out.print(constrName);
-              } else {
-                out.print(
-                    method.proto.returnType.toSourceString() + " " + method.name.toSourceString());
-              }
-              boolean first = true;
-              out.print("(");
-              for (DexType param : method.proto.parameters.values) {
-                if (!first) {
-                  out.print(",");
-                }
-                first = false;
-                out.print(param.toSourceString());
-              }
-              out.println(")");
-            });
-    out.close();
-  }
-
-  static boolean satisfyClassType(ProguardConfigurationRule rule, DexClass clazz) {
-    return rule.getClassType().matches(clazz) != rule.getClassTypeNegated();
-  }
-
-  static boolean satisfyAccessFlag(ProguardConfigurationRule rule, DexClass clazz) {
-    return rule.getClassAccessFlags().containsAll(clazz.accessFlags)
-        && rule.getNegatedClassAccessFlags().containsNone(clazz.accessFlags);
-  }
-
-  static AnnotationMatchResult satisfyAnnotation(ProguardConfigurationRule rule, DexClass clazz) {
-    return containsAllAnnotations(rule.getClassAnnotations(), clazz);
-  }
-
-  boolean satisfyInheritanceRule(DexClass clazz, ProguardConfigurationRule rule) {
-    if (satisfyExtendsRule(clazz, rule)) {
-      return true;
-    }
-
-    return satisfyImplementsRule(clazz, rule);
-  }
-
-  boolean satisfyExtendsRule(DexClass clazz, ProguardConfigurationRule rule) {
-    if (anySuperTypeMatchesExtendsRule(clazz.superType, rule)) {
-      return true;
-    }
-    // It is possible that this class used to inherit from another class X, but no longer does it,
-    // because X has been merged into `clazz`.
-    return anySourceMatchesInheritanceRuleDirectly(clazz, rule, false);
-  }
-
-  boolean anySuperTypeMatchesExtendsRule(DexType type, ProguardConfigurationRule rule) {
-    while (type != null) {
-      DexClass clazz = application.definitionFor(type);
-      if (clazz == null) {
-        // TODO(herhut): Warn about broken supertype chain?
-        return false;
-      }
-      // TODO(b/110141157): Should the vertical class merger move annotations from the source to
-      // the target class? If so, it is sufficient only to apply the annotation-matcher to the
-      // annotations of `class`.
-      if (rule.getInheritanceClassName().matches(clazz.type, appView)) {
-        AnnotationMatchResult annotationMatchResult =
-            containsAllAnnotations(rule.getInheritanceAnnotations(), clazz);
-        if (annotationMatchResult != null) {
-          handleMatchedAnnotation(annotationMatchResult);
-          return true;
-        }
-      }
-      type = clazz.superType;
-    }
-    return false;
-  }
-
-  boolean satisfyImplementsRule(DexClass clazz, ProguardConfigurationRule rule) {
-    if (anyImplementedInterfaceMatchesImplementsRule(clazz, rule)) {
-      return true;
-    }
-    // It is possible that this class used to implement an interface I, but no longer does it,
-    // because I has been merged into `clazz`.
-    return anySourceMatchesInheritanceRuleDirectly(clazz, rule, true);
-  }
-
-  private boolean anyImplementedInterfaceMatchesImplementsRule(
-      DexClass clazz, ProguardConfigurationRule rule) {
-    // TODO(herhut): Maybe it would be better to do this breadth first.
-    if (clazz == null) {
-      return false;
-    }
-    for (DexType iface : clazz.interfaces.values) {
-      DexClass ifaceClass = application.definitionFor(iface);
-      if (ifaceClass == null) {
-        // TODO(herhut): Warn about broken supertype chain?
-        return false;
-      }
-      // TODO(b/110141157): Should the vertical class merger move annotations from the source to
-      // the target class? If so, it is sufficient only to apply the annotation-matcher to the
-      // annotations of `ifaceClass`.
-      if (rule.getInheritanceClassName().matches(iface, appView)) {
-        AnnotationMatchResult annotationMatchResult =
-            containsAllAnnotations(rule.getInheritanceAnnotations(), ifaceClass);
-        if (annotationMatchResult != null) {
-          handleMatchedAnnotation(annotationMatchResult);
-          return true;
-        }
-      }
-      if (anyImplementedInterfaceMatchesImplementsRule(ifaceClass, rule)) {
-        return true;
-      }
-    }
-    if (clazz.superType == null) {
-      return false;
-    }
-    DexClass superClass = application.definitionFor(clazz.superType);
-    if (superClass == null) {
-      // TODO(herhut): Warn about broken supertype chain?
-      return false;
-    }
-    return anyImplementedInterfaceMatchesImplementsRule(superClass, rule);
-  }
-
-  private boolean anySourceMatchesInheritanceRuleDirectly(
-      DexClass clazz, ProguardConfigurationRule rule, boolean isInterface) {
-    // TODO(b/110141157): Figure out what to do with annotations. Should the annotations of
-    // the DexClass corresponding to `sourceType` satisfy the `annotation`-matcher?
-    return appView.verticallyMergedClasses() != null
-        && appView.verticallyMergedClasses().getSourcesFor(clazz.type).stream()
-            .filter(
-                sourceType ->
-                    appView.definitionFor(sourceType).accessFlags.isInterface() == isInterface)
-            .anyMatch(rule.getInheritanceClassName()::matches);
-  }
-
-  private boolean allRulesSatisfied(Collection<ProguardMemberRule> memberKeepRules,
-      DexClass clazz) {
-    for (ProguardMemberRule rule : memberKeepRules) {
-      if (!ruleSatisfied(rule, clazz)) {
-        return false;
-      }
-    }
-    return true;
-  }
-
-  /**
-   * Checks whether the given rule is satisfied by this clazz, not taking superclasses into
-   * account.
-   */
-  private boolean ruleSatisfied(ProguardMemberRule rule, DexClass clazz) {
-    return ruleSatisfiedByMethods(rule, clazz.directMethods())
-        || ruleSatisfiedByMethods(rule, clazz.virtualMethods())
-        || ruleSatisfiedByFields(rule, clazz.staticFields())
-        || ruleSatisfiedByFields(rule, clazz.instanceFields());
-  }
-
-  boolean ruleSatisfiedByMethods(ProguardMemberRule rule, Iterable<DexEncodedMethod> methods) {
-    if (rule.getRuleType().includesMethods()) {
-      for (DexEncodedMethod method : methods) {
-        if (rule.matches(method, appView, this::handleMatchedAnnotation, dexStringCache)) {
-          return true;
-        }
-      }
-    }
-    return false;
-  }
-
-  boolean ruleSatisfiedByFields(ProguardMemberRule rule, Iterable<DexEncodedField> fields) {
-    if (rule.getRuleType().includesFields()) {
-      for (DexEncodedField field : fields) {
-        if (rule.matches(field, appView, this::handleMatchedAnnotation, dexStringCache)) {
-          return true;
-        }
-      }
-    }
-    return false;
-  }
-
-  static AnnotationMatchResult containsAllAnnotations(
-      List<ProguardTypeMatcher> annotationMatchers, DexClass clazz) {
-    return containsAllAnnotations(annotationMatchers, clazz.annotations());
-  }
-
-  static <D extends DexEncodedMember<D, R>, R extends DexMember<D, R>>
-      boolean containsAllAnnotations(
-          List<ProguardTypeMatcher> annotationMatchers,
-          DexEncodedMember<D, R> member,
-          Consumer<AnnotationMatchResult> matchedAnnotationsConsumer) {
-    AnnotationMatchResult annotationMatchResult =
-        containsAllAnnotations(annotationMatchers, member.annotations());
-    if (annotationMatchResult != null) {
-      matchedAnnotationsConsumer.accept(annotationMatchResult);
-      return true;
-    }
-    if (member.isDexEncodedMethod()) {
-      DexEncodedMethod method = member.asDexEncodedMethod();
-      for (int i = 0; i < method.parameterAnnotationsList.size(); i++) {
-        annotationMatchResult =
-            containsAllAnnotations(annotationMatchers, method.parameterAnnotationsList.get(i));
-        if (annotationMatchResult != null) {
-          matchedAnnotationsConsumer.accept(annotationMatchResult);
-          return true;
-        }
-      }
-    }
-    return false;
-  }
-
-  private static AnnotationMatchResult containsAllAnnotations(
-      List<ProguardTypeMatcher> annotationMatchers, DexAnnotationSet annotations) {
-    if (annotationMatchers.isEmpty()) {
-      return AnnotationsIgnoredMatchResult.getInstance();
-    }
-    List<DexAnnotation> matchedAnnotations = new ArrayList<>();
-    for (ProguardTypeMatcher annotationMatcher : annotationMatchers) {
-      DexAnnotation matchedAnnotation =
-          getFirstAnnotationThatMatches(annotationMatcher, annotations);
-      if (matchedAnnotation == null) {
-        return null;
-      }
-      matchedAnnotations.add(matchedAnnotation);
-    }
-    return new ConcreteAnnotationMatchResult(matchedAnnotations);
-  }
-
-  private static DexAnnotation getFirstAnnotationThatMatches(
-      ProguardTypeMatcher annotationMatcher, DexAnnotationSet annotations) {
-    for (DexAnnotation annotation : annotations.annotations) {
-      if (annotationMatcher.matches(annotation.getAnnotationType())) {
-        return annotation;
-      }
-    }
-    return null;
-  }
-
-  private void markMethod(
-      DexEncodedMethod method,
-      Collection<ProguardMemberRule> rules,
-      Set<Wrapper<DexMethod>> methodsMarked,
-      ProguardConfigurationRule context,
-      DexDefinition precondition,
-      ProguardIfRule ifRule) {
-    if (methodsMarked != null
-        && methodsMarked.contains(MethodSignatureEquivalence.get().wrap(method.method))) {
-      // Ignore, method is overridden in sub class.
-      return;
-    }
-    for (ProguardMemberRule rule : rules) {
-      if (rule.matches(method, appView, this::handleMatchedAnnotation, dexStringCache)) {
-        if (Log.ENABLED) {
-          Log.verbose(getClass(), "Marking method `%s` due to `%s { %s }`.", method, context,
-              rule);
-        }
-        if (methodsMarked != null) {
-          methodsMarked.add(MethodSignatureEquivalence.get().wrap(method.method));
-        }
-        addItemToSets(method, context, rule, precondition, ifRule);
-      }
-    }
-  }
-
-  private void markField(
-      DexEncodedField field,
-      Collection<ProguardMemberRule> rules,
-      ProguardConfigurationRule context,
-      DexDefinition precondition,
-      ProguardIfRule ifRule) {
-    for (ProguardMemberRule rule : rules) {
-      if (rule.matches(field, appView, this::handleMatchedAnnotation, dexStringCache)) {
-        if (Log.ENABLED) {
-          Log.verbose(getClass(), "Marking field `%s` due to `%s { %s }`.", field, context,
-              rule);
-        }
-        addItemToSets(field, context, rule, precondition, ifRule);
-      }
-    }
-  }
-
-  private void markClass(DexClass clazz, ProguardConfigurationRule rule, ProguardIfRule ifRule) {
-    if (Log.ENABLED) {
-      Log.verbose(getClass(), "Marking class `%s` due to `%s`.", clazz.type, rule);
-    }
-    addItemToSets(clazz, rule, null, null, ifRule);
-  }
-
-  private void includeDescriptor(DexDefinition item, DexType type, ProguardKeepRuleBase context) {
-    if (type.isVoidType()) {
-      return;
-    }
-    if (type.isArrayType()) {
-      type = type.toBaseType(appView.dexItemFactory());
-    }
-    if (type.isPrimitiveType()) {
-      return;
-    }
-    DexClass definition = appView.definitionFor(type);
-    if (definition == null || definition.isNotProgramClass()) {
-      return;
-    }
-    // Keep the type if the item is also kept.
-    dependentNoShrinking
-        .computeIfAbsent(item.getReference(), x -> new MutableItemsWithRules())
-        .addClassWithRule(type, context);
-    // Unconditionally add to no-obfuscation, as that is only checked for surviving items.
-    if (appView.options().isMinificationEnabled()) {
-      noObfuscation.add(type);
-    }
-  }
-
-  private void includeDescriptorClasses(DexDefinition item, ProguardKeepRuleBase context) {
-    if (item.isDexEncodedMethod()) {
-      DexMethod method = item.asDexEncodedMethod().method;
-      includeDescriptor(item, method.proto.returnType, context);
-      for (DexType value : method.proto.parameters.values) {
-        includeDescriptor(item, value, context);
-      }
-    } else if (item.isDexEncodedField()) {
-      DexField field = item.asDexEncodedField().field;
-      includeDescriptor(item, field.type, context);
-    } else {
-      assert item.isDexClass();
-    }
-  }
-
-  private synchronized void addItemToSets(
-      DexDefinition item,
-      ProguardConfigurationRule context,
-      ProguardMemberRule rule,
-      DexDefinition precondition,
-      ProguardIfRule ifRule) {
-    if (context instanceof ProguardKeepRule) {
-      if (item.isDexEncodedField()) {
-        DexEncodedField encodedField = item.asDexEncodedField();
-        if (encodedField.getOptimizationInfo().cannotBeKept()) {
-          // We should only ever get here with if rules.
-          assert ifRule != null;
-          return;
-        }
-      } else if (item.isDexEncodedMethod()) {
-        DexEncodedMethod encodedMethod = item.asDexEncodedMethod();
-        if (encodedMethod.isClassInitializer() && !options.debug) {
-          // Don't keep class initializers.
-          return;
-        }
-        if (encodedMethod.getOptimizationInfo().cannotBeKept()) {
-          // We should only ever get here with if rules.
-          assert ifRule != null;
-          return;
-        }
-        if (options.isGeneratingDex()
-            && encodedMethod.method.isLambdaDeserializeMethod(appView.dexItemFactory())) {
-          // Don't keep lambda deserialization methods.
-          return;
-        }
-        // If desugaring is enabled, private and static interface methods will be moved to a
-        // companion class. So we don't need to add them to the root set in the beginning.
-        if (options.isInterfaceMethodDesugaringEnabled()
-            && encodedMethod.hasCode()
-            && (encodedMethod.isPrivateMethod() || encodedMethod.isStaticMember())) {
-          DexClass holder = appView.definitionFor(encodedMethod.getHolderType());
-          if (holder != null && holder.isInterface()) {
-            if (rule.isSpecific()) {
-              options.reporter.warning(
-                  new StringDiagnostic(
-                      "The rule `" + rule + "` is ignored because the targeting interface method `"
-                          + encodedMethod.method.toSourceString() + "` will be desugared."));
-            }
-            return;
-          }
-        }
-      }
-
-      // The reason for keeping should link to the conditional rule as a whole, if present.
-      ProguardKeepRuleBase keepRule = ifRule != null ? ifRule : (ProguardKeepRuleBase) context;
-      // The modifiers are specified on the actual keep rule (ie, the consequent/context).
-      ProguardKeepRuleModifiers modifiers = ((ProguardKeepRule) context).getModifiers();
-      // In compatibility mode, for a match on instance members a referenced class becomes live.
-      if (options.forceProguardCompatibility
-          && !modifiers.allowsShrinking
-          && precondition != null
-          && precondition.isDexClass()) {
-        if (!item.isDexClass() && !item.isStaticMember()) {
-          dependentKeepClassCompatRule
-              .computeIfAbsent(precondition.asDexClass().getType(), i -> new HashSet<>())
-              .add(keepRule);
-          context.markAsUsed();
-        }
-      }
-      if (!modifiers.allowsShrinking) {
-        if (precondition != null) {
-          dependentNoShrinking
-              .computeIfAbsent(precondition.getReference(), x -> new MutableItemsWithRules())
-              .addReferenceWithRule(item.getReference(), keepRule);
-        } else {
-          noShrinking.addReferenceWithRule(item.getReference(), keepRule);
-        }
-        context.markAsUsed();
-      } else if (!modifiers.allowsOptimization) {
-        if (precondition != null) {
-          dependentSoftPinned
-              .computeIfAbsent(precondition.getReference(), x -> new MutableItemsWithRules())
-              .addReferenceWithRule(item.getReference(), keepRule);
-        } else {
-          softPinned.addReferenceWithRule(item.getReference(), keepRule);
-        }
-      }
-      if (!modifiers.allowsOptimization) {
-        // The -dontoptimize flag has only effect through the keep all rule, but we still
-        // need to mark the rule as used.
-        context.markAsUsed();
-      }
-
-      if (appView.options().isMinificationEnabled() && !modifiers.allowsObfuscation) {
-        noObfuscation.add(item.getReference());
-        context.markAsUsed();
-      }
-      if (modifiers.includeDescriptorClasses) {
-        includeDescriptorClasses(item, keepRule);
-        context.markAsUsed();
-      }
-    } else if (context instanceof ProguardAssumeMayHaveSideEffectsRule) {
-      mayHaveSideEffects.put(item.getReference(), rule);
-      context.markAsUsed();
-    } else if (context instanceof ProguardAssumeNoSideEffectRule) {
-      if (item.isDexEncodedMember()) {
-        DexEncodedMember<?, ?> member = item.asDexEncodedMember();
-        if (member.getHolderType() == appView.dexItemFactory().objectType) {
-          assert member.isDexEncodedMethod();
-          reportAssumeNoSideEffectsWarningForJavaLangClassMethod(
-              member.asDexEncodedMethod(), (ProguardAssumeNoSideEffectRule) context);
-        } else {
-          noSideEffects.put(member.getReference(), rule);
-        }
-        context.markAsUsed();
-      }
-    } else if (context instanceof ProguardWhyAreYouKeepingRule) {
-      reasonAsked.computeIfAbsent(item.getReference(), i -> i);
-      context.markAsUsed();
-    } else if (context instanceof ProguardAssumeValuesRule) {
-      if (item.isDexEncodedMember()) {
-        assumedValues.put(item.asDexEncodedMember().getReference(), rule);
-        context.markAsUsed();
-      }
-    } else if (context instanceof ProguardCheckDiscardRule) {
-      checkDiscarded.computeIfAbsent(item.getReference(), i -> i);
-      context.markAsUsed();
-    } else if (context instanceof InlineRule) {
-      if (item.isDexEncodedMethod()) {
-        DexMethod reference = item.asDexEncodedMethod().getReference();
-        switch (((InlineRule) context).getType()) {
-          case ALWAYS:
-            alwaysInline.add(reference);
-            break;
-          case FORCE:
-            forceInline.add(reference);
-            break;
-          case NEVER:
-            neverInline.add(reference);
-            break;
-          case NEVER_SINGLE_CALLER:
-            neverInlineDueToSingleCaller.add(reference);
-            break;
-          default:
-            throw new Unreachable();
-        }
-        context.markAsUsed();
-      }
-    } else if (context instanceof WhyAreYouNotInliningRule) {
-      if (!item.isDexEncodedMethod()) {
-        throw new Unreachable();
-      }
-      whyAreYouNotInlining.add(item.asDexEncodedMethod().method);
-      context.markAsUsed();
-    } else if (context.isClassInlineRule()) {
-      ClassInlineRule classInlineRule = context.asClassInlineRule();
-      DexClass clazz = item.asDexClass();
-      if (clazz == null) {
-        throw new IllegalStateException(
-            "Unexpected -"
-                + classInlineRule.typeString()
-                + " rule for a non-class type: `"
-                + item.getReference().toSourceString()
-                + "`");
-      }
-      switch (classInlineRule.getType()) {
-        case ALWAYS:
-          alwaysClassInline.addElement(item.asDexClass().type);
-          break;
-        case NEVER:
-          neverClassInline.add(item.asDexClass().type);
-          break;
-        default:
-          throw new Unreachable();
-      }
-      context.markAsUsed();
-    } else if (context instanceof NoUnusedInterfaceRemovalRule) {
-      noUnusedInterfaceRemoval.add(item.asDexClass().type);
-      context.markAsUsed();
-    } else if (context instanceof NoVerticalClassMergingRule) {
-      noVerticalClassMerging.add(item.asDexClass().type);
-      context.markAsUsed();
-    } else if (context instanceof NoHorizontalClassMergingRule) {
-      noHorizontalClassMerging.add(item.asDexClass().type);
-      context.markAsUsed();
-    } else if (context instanceof NoStaticClassMergingRule) {
-      noStaticClassMerging.add(item.asDexClass().type);
-      context.markAsUsed();
-    } else if (context instanceof MemberValuePropagationRule) {
-      switch (((MemberValuePropagationRule) context).getType()) {
-        case NEVER:
-          // Only add members from propgram classes to `neverPropagateValue` since class member
-          // values from library types are not propagated by default.
-          if (item.isDexEncodedField()) {
-            DexEncodedField field = item.asDexEncodedField();
-            if (field.isProgramField(appView)) {
-              neverPropagateValue.add(item.asDexEncodedField().field);
-              context.markAsUsed();
-            }
-          } else if (item.isDexEncodedMethod()) {
-            DexEncodedMethod method = item.asDexEncodedMethod();
-            if (method.isProgramMethod(appView)) {
-              neverPropagateValue.add(item.asDexEncodedMethod().method);
-              context.markAsUsed();
-            }
-          }
-          break;
-        default:
-          throw new Unreachable();
-      }
-    } else if (context instanceof ProguardIdentifierNameStringRule) {
-      if (item.isDexEncodedField()) {
-        identifierNameStrings.add(item.asDexEncodedField().field);
-        context.markAsUsed();
-      } else if (item.isDexEncodedMethod()) {
-        identifierNameStrings.add(item.asDexEncodedMethod().method);
-        context.markAsUsed();
-      }
-    } else if (context instanceof ConstantArgumentRule) {
-      if (item.isDexEncodedMethod()) {
-        keepParametersWithConstantValue.add(item.asDexEncodedMethod().method);
-        context.markAsUsed();
-      }
-    } else if (context instanceof ReprocessClassInitializerRule) {
-      DexProgramClass clazz = item.asProgramClass();
-      if (clazz != null && clazz.hasClassInitializer()) {
-        switch (context.asReprocessClassInitializerRule().getType()) {
-          case ALWAYS:
-            reprocess.add(clazz.getClassInitializer().method);
-            break;
-          case NEVER:
-            neverReprocess.add(clazz.getClassInitializer().method);
-            break;
-          default:
-            throw new Unreachable();
-        }
-        context.markAsUsed();
-      }
-    } else if (context.isReprocessMethodRule()) {
-      if (item.isDexEncodedMethod()) {
-        DexEncodedMethod method = item.asDexEncodedMethod();
-        switch (context.asReprocessMethodRule().getType()) {
-          case ALWAYS:
-            reprocess.add(method.method);
-            break;
-          case NEVER:
-            neverReprocess.add(method.method);
-            break;
-          default:
-            throw new Unreachable();
-        }
-        context.markAsUsed();
-      }
-    } else if (context instanceof UnusedArgumentRule) {
-      if (item.isDexEncodedMethod()) {
-        keepUnusedArguments.add(item.asDexEncodedMethod().method);
-        context.markAsUsed();
-      }
-    } else {
-      throw new Unreachable();
-    }
-  }
-
-  abstract static class RootSetBase {
-
-    final Set<DexMethod> neverInline;
-    final Set<DexMethod> neverInlineDueToSingleCaller;
-    final Set<DexType> neverClassInline;
-    final MutableItemsWithRules noShrinking;
-    final MutableItemsWithRules softPinned;
-    final Set<DexReference> noObfuscation;
-    final Map<DexReference, MutableItemsWithRules> dependentNoShrinking;
-    final Map<DexReference, MutableItemsWithRules> dependentSoftPinned;
-    final Map<DexType, Set<ProguardKeepRuleBase>> dependentKeepClassCompatRule;
-    final List<DelayedRootSetActionItem> delayedRootSetActionItems;
-
-    RootSetBase(
-        Set<DexMethod> neverInline,
-        Set<DexMethod> neverInlineDueToSingleCaller,
-        Set<DexType> neverClassInline,
-        MutableItemsWithRules noShrinking,
-        MutableItemsWithRules softPinned,
-        Set<DexReference> noObfuscation,
-        Map<DexReference, MutableItemsWithRules> dependentNoShrinking,
-        Map<DexReference, MutableItemsWithRules> dependentSoftPinned,
-        Map<DexType, Set<ProguardKeepRuleBase>> dependentKeepClassCompatRule,
-        List<DelayedRootSetActionItem> delayedRootSetActionItems) {
-      this.neverInline = neverInline;
-      this.neverInlineDueToSingleCaller = neverInlineDueToSingleCaller;
-      this.neverClassInline = neverClassInline;
-      this.noShrinking = noShrinking;
-      this.softPinned = softPinned;
-      this.noObfuscation = noObfuscation;
-      this.dependentNoShrinking = dependentNoShrinking;
-      this.dependentSoftPinned = dependentSoftPinned;
-      this.dependentKeepClassCompatRule = dependentKeepClassCompatRule;
-      this.delayedRootSetActionItems = delayedRootSetActionItems;
-    }
-
-    public void forEachClassWithDependentItems(
-        DexDefinitionSupplier definitions, Consumer<DexProgramClass> consumer) {
-      for (DexReference reference : dependentNoShrinking.keySet()) {
-        if (reference.isDexType()) {
-          DexType type = reference.asDexType();
-          DexProgramClass clazz = asProgramClassOrNull(definitions.definitionFor(type));
-          if (clazz != null) {
-            consumer.accept(clazz);
-          }
-        }
-      }
-    }
-
-    public void forEachMemberWithDependentItems(
-        DexDefinitionSupplier definitions,
-        BiConsumer<DexEncodedMember<?, ?>, ItemsWithRules> consumer) {
-      dependentNoShrinking.forEach(
-          (reference, dependentItems) -> {
-            if (reference.isDexMember()) {
-              DexMember<?, ?> member = reference.asDexMember();
-              DexProgramClass holder =
-                  asProgramClassOrNull(definitions.definitionForHolder(member));
-              if (holder != null) {
-                DexEncodedMember<?, ?> definition = holder.lookupMember(member);
-                if (definition != null) {
-                  consumer.accept(definition, dependentItems);
-                }
-              }
-            }
-          });
-    }
-
-    public void forEachDependentInstanceConstructor(
-        DexProgramClass clazz,
-        AppView<?> appView,
-        BiConsumer<ProgramMethod, Set<ProguardKeepRuleBase>> fn) {
-      getDependentItems(clazz)
-          .forEachMethod(
-              (reference, reasons) -> {
-                DexProgramClass holder =
-                    asProgramClassOrNull(appView.definitionForHolder(reference));
-                if (holder != null) {
-                  ProgramMethod method = holder.lookupProgramMethod(reference);
-                  if (method != null && method.getDefinition().isInstanceInitializer()) {
-                    fn.accept(method, reasons);
-                  }
-                }
-              });
-    }
-
-    public void forEachDependentMember(
-        DexDefinition item,
-        AppView<?> appView,
-        Consumer3<DexDefinition, ProgramMember<?, ?>, Set<ProguardKeepRuleBase>> fn) {
-      getDependentItems(item)
-          .forEachMember(
-              (reference, reasons) -> {
-                DexProgramClass holder =
-                    asProgramClassOrNull(appView.definitionForHolder(reference));
-                if (holder != null) {
-                  ProgramMember<?, ?> member = holder.lookupProgramMember(reference);
-                  if (member != null) {
-                    fn.accept(item, member, reasons);
-                  }
-                }
-              });
-    }
-
-    public void forEachDependentNonStaticMember(
-        DexDefinition item,
-        AppView<?> appView,
-        Consumer3<DexDefinition, ProgramMember<?, ?>, Set<ProguardKeepRuleBase>> fn) {
-      forEachDependentMember(
-          item,
-          appView,
-          (precondition, member, reasons) -> {
-            if (!member.getDefinition().isStatic()) {
-              fn.accept(precondition, member, reasons);
-            }
-          });
-    }
-
-    public void forEachDependentStaticMember(
-        DexDefinition item,
-        AppView<?> appView,
-        Consumer3<DexDefinition, ProgramMember<?, ?>, Set<ProguardKeepRuleBase>> fn) {
-      forEachDependentMember(
-          item,
-          appView,
-          (precondition, member, reasons) -> {
-            if (member.getDefinition().isStatic()) {
-              fn.accept(precondition, member, reasons);
-            }
-          });
-    }
-
-    ItemsWithRules getDependentItems(DexDefinition item) {
-      ItemsWithRules found = dependentNoShrinking.get(item.getReference());
-      return found != null ? found : ItemsWithRules.empty();
-    }
-
-    Set<ProguardKeepRuleBase> getDependentKeepClassCompatRule(DexType type) {
-      return dependentKeepClassCompatRule.get(type);
-    }
-  }
-
-  abstract static class ItemsWithRules {
-
-    public static ItemsWithRules empty() {
-      return MutableItemsWithRules.EMPTY;
-    }
-
-    public abstract boolean containsClass(DexType type);
-
-    public abstract boolean containsField(DexField field);
-
-    public abstract boolean containsMethod(DexMethod method);
-
-    public final boolean containsReference(DexReference reference) {
-      return reference.apply(this::containsClass, this::containsField, this::containsMethod);
-    }
-
-    public abstract void forEachClass(Consumer<? super DexType> consumer);
-
-    public abstract void forEachClass(
-        BiConsumer<? super DexType, Set<ProguardKeepRuleBase>> consumer);
-
-    public abstract void forEachField(Consumer<? super DexField> consumer);
-
-    public abstract void forEachField(
-        BiConsumer<? super DexField, Set<ProguardKeepRuleBase>> consumer);
-
-    public abstract void forEachMember(Consumer<? super DexMember<?, ?>> consumer);
-
-    public abstract void forEachMember(
-        BiConsumer<? super DexMember<?, ?>, Set<ProguardKeepRuleBase>> consumer);
-
-    public abstract void forEachMethod(Consumer<? super DexMethod> consumer);
-
-    public abstract void forEachMethod(
-        BiConsumer<? super DexMethod, Set<ProguardKeepRuleBase>> consumer);
-
-    public abstract Set<ProguardKeepRuleBase> getRulesForClass(DexType type);
-
-    public abstract Set<ProguardKeepRuleBase> getRulesForField(DexField field);
-
-    public abstract Set<ProguardKeepRuleBase> getRulesForMethod(DexMethod method);
-
-    public final Set<ProguardKeepRuleBase> getRulesForReference(DexReference reference) {
-      return reference.apply(
-          this::getRulesForClass, this::getRulesForField, this::getRulesForMethod);
-    }
-  }
-
-  static class MutableItemsWithRules extends ItemsWithRules {
-
-    private static final ItemsWithRules EMPTY =
-        new MutableItemsWithRules(
-            Collections.emptyMap(), Collections.emptyMap(), Collections.emptyMap());
-
-    final Map<DexType, Set<ProguardKeepRuleBase>> classesWithRules;
-    final Map<DexField, Set<ProguardKeepRuleBase>> fieldsWithRules;
-    final Map<DexMethod, Set<ProguardKeepRuleBase>> methodsWithRules;
-
-    MutableItemsWithRules() {
-      this(new IdentityHashMap<>(), new IdentityHashMap<>(), new IdentityHashMap<>());
-    }
-
-    private MutableItemsWithRules(
-        Map<DexType, Set<ProguardKeepRuleBase>> classesWithRules,
-        Map<DexField, Set<ProguardKeepRuleBase>> fieldsWithRules,
-        Map<DexMethod, Set<ProguardKeepRuleBase>> methodsWithRules) {
-      this.classesWithRules = classesWithRules;
-      this.fieldsWithRules = fieldsWithRules;
-      this.methodsWithRules = methodsWithRules;
-    }
-
-    public void addAll(ItemsWithRules items) {
-      items.forEachClass(this::addClassWithRules);
-      items.forEachField(this::addFieldWithRules);
-      items.forEachMethod(this::addMethodWithRules);
-    }
-
-    public void addClassWithRule(DexType type, ProguardKeepRuleBase rule) {
-      classesWithRules.computeIfAbsent(type, ignore -> new HashSet<>()).add(rule);
-    }
-
-    public void addClassWithRules(DexType type, Set<ProguardKeepRuleBase> rules) {
-      classesWithRules.computeIfAbsent(type, ignore -> new HashSet<>()).addAll(rules);
-    }
-
-    public void addFieldWithRule(DexField field, ProguardKeepRuleBase rule) {
-      fieldsWithRules.computeIfAbsent(field, ignore -> new HashSet<>()).add(rule);
-    }
-
-    public void addFieldWithRules(DexField field, Set<ProguardKeepRuleBase> rules) {
-      fieldsWithRules.computeIfAbsent(field, ignore -> new HashSet<>()).addAll(rules);
-    }
-
-    public void addMethodWithRule(DexMethod method, ProguardKeepRuleBase rule) {
-      methodsWithRules.computeIfAbsent(method, ignore -> new HashSet<>()).add(rule);
-    }
-
-    public void addMethodWithRules(DexMethod method, Set<ProguardKeepRuleBase> rules) {
-      methodsWithRules.computeIfAbsent(method, ignore -> new HashSet<>()).addAll(rules);
-    }
-
-    public void addReferenceWithRule(DexReference reference, ProguardKeepRuleBase rule) {
-      reference.accept(
-          this::addClassWithRule, this::addFieldWithRule, this::addMethodWithRule, rule);
-    }
-
-    public void addReferenceWithRules(DexReference reference, Set<ProguardKeepRuleBase> rules) {
-      reference.accept(
-          this::addClassWithRules, this::addFieldWithRules, this::addMethodWithRules, rules);
-    }
-
-    @Override
-    public boolean containsClass(DexType type) {
-      return classesWithRules.containsKey(type);
-    }
-
-    @Override
-    public boolean containsField(DexField field) {
-      return fieldsWithRules.containsKey(field);
-    }
-
-    @Override
-    public boolean containsMethod(DexMethod method) {
-      return methodsWithRules.containsKey(method);
-    }
-
-    public void forEachReference(Consumer<DexReference> consumer) {
-      forEachClass(consumer);
-      forEachMember(consumer);
-    }
-
-    @Override
-    public void forEachClass(Consumer<? super DexType> consumer) {
-      classesWithRules.keySet().forEach(consumer);
-    }
-
-    @Override
-    public void forEachClass(BiConsumer<? super DexType, Set<ProguardKeepRuleBase>> consumer) {
-      classesWithRules.forEach(consumer);
-    }
-
-    @Override
-    public void forEachField(Consumer<? super DexField> consumer) {
-      fieldsWithRules.keySet().forEach(consumer);
-    }
-
-    @Override
-    public void forEachField(BiConsumer<? super DexField, Set<ProguardKeepRuleBase>> consumer) {
-      fieldsWithRules.forEach(consumer);
-    }
-
-    @Override
-    public void forEachMember(Consumer<? super DexMember<?, ?>> consumer) {
-      forEachField(consumer);
-      forEachMethod(consumer);
-    }
-
-    @Override
-    public void forEachMember(
-        BiConsumer<? super DexMember<?, ?>, Set<ProguardKeepRuleBase>> consumer) {
-      forEachField(consumer);
-      forEachMethod(consumer);
-    }
-
-    @Override
-    public void forEachMethod(Consumer<? super DexMethod> consumer) {
-      methodsWithRules.keySet().forEach(consumer);
-    }
-
-    @Override
-    public void forEachMethod(BiConsumer<? super DexMethod, Set<ProguardKeepRuleBase>> consumer) {
-      methodsWithRules.forEach(consumer);
-    }
-
-    @Override
-    public Set<ProguardKeepRuleBase> getRulesForClass(DexType type) {
-      return classesWithRules.get(type);
-    }
-
-    @Override
-    public Set<ProguardKeepRuleBase> getRulesForField(DexField field) {
-      return fieldsWithRules.get(field);
-    }
-
-    @Override
-    public Set<ProguardKeepRuleBase> getRulesForMethod(DexMethod method) {
-      return methodsWithRules.get(method);
-    }
-
-    public void removeClass(DexType type) {
-      classesWithRules.remove(type);
-    }
-
-    public void removeField(DexField field) {
-      fieldsWithRules.remove(field);
-    }
-
-    public void removeMethod(DexMethod method) {
-      methodsWithRules.remove(method);
-    }
-
-    public void removeReference(DexReference reference) {
-      reference.accept(this::removeClass, this::removeField, this::removeMethod);
-    }
-
-    public void putAll(ItemsWithRules items) {
-      items.forEachClass(this::putClassWithRules);
-      items.forEachField(this::putFieldWithRules);
-      items.forEachMethod(this::putMethodWithRules);
-    }
-
-    public void putClassWithRules(DexType type, Set<ProguardKeepRuleBase> rules) {
-      classesWithRules.put(type, rules);
-    }
-
-    public void putFieldWithRules(DexField field, Set<ProguardKeepRuleBase> rules) {
-      fieldsWithRules.put(field, rules);
-    }
-
-    public void putMethodWithRules(DexMethod method, Set<ProguardKeepRuleBase> rules) {
-      methodsWithRules.put(method, rules);
-    }
-
-    public void putReferenceWithRules(DexReference reference, Set<ProguardKeepRuleBase> rules) {
-      reference.accept(
-          this::putClassWithRules, this::putFieldWithRules, this::putMethodWithRules, rules);
-    }
-
-    public int size() {
-      return classesWithRules.size() + fieldsWithRules.size() + methodsWithRules.size();
-    }
-  }
-
-  private void reportAssumeNoSideEffectsWarningForJavaLangClassMethod(
-      DexEncodedMethod method, ProguardAssumeNoSideEffectRule context) {
-    assert method.getHolderType() == options.dexItemFactory().objectType;
-    OriginWithPosition key = new OriginWithPosition(context.getOrigin(), context.getPosition());
-    assumeNoSideEffectsWarnings
-        .computeIfAbsent(key, ignore -> new TreeSet<>(DexMethod::compareTo))
-        .add(method.getReference());
-  }
-
-  private boolean isWaitOrNotifyMethod(DexMethod method) {
-    return method.name == options.itemFactory.waitMethodName
-        || method.name == options.itemFactory.notifyMethodName
-        || method.name == options.itemFactory.notifyAllMethodName;
-  }
-
-  private void generateAssumeNoSideEffectsWarnings() {
-    if (appView.getDontWarnConfiguration().matches(options.itemFactory.objectType)) {
-      // Don't report any warnings since we don't apply -assumenosideeffects rules to notify() or
-      // wait() anyway.
-      return;
-    }
-
-    assumeNoSideEffectsWarnings.forEach(
-        (originWithPosition, methods) -> {
-          boolean matchesWaitOrNotifyMethods =
-              methods.stream().anyMatch(this::isWaitOrNotifyMethod);
-          if (!matchesWaitOrNotifyMethods) {
-            // We model the remaining methods on java.lang.Object, and thus there should be no need
-            // to warn in this case.
-            return;
-          }
-          options.reporter.warning(
-              new AssumeNoSideEffectsRuleForObjectMembersDiagnostic.Builder()
-                  .addMatchedMethods(methods)
-                  .setOrigin(originWithPosition.getOrigin())
-                  .setPosition(originWithPosition.getPosition())
-                  .build());
-        });
-  }
-
-  public static class RootSet extends RootSetBase {
-
-    public final ImmutableList<DexReference> reasonAsked;
-    public final ImmutableList<DexReference> checkDiscarded;
-    public final Set<DexMethod> alwaysInline;
-    public final Set<DexMethod> forceInline;
-    public final Set<DexMethod> bypassClinitForInlining;
-    public final Set<DexMethod> whyAreYouNotInlining;
-    public final Set<DexMethod> keepConstantArguments;
-    public final Set<DexMethod> keepUnusedArguments;
-    public final Set<DexMethod> reprocess;
-    public final Set<DexMethod> neverReprocess;
-    public final PredicateSet<DexType> alwaysClassInline;
-    public final Set<DexType> noUnusedInterfaceRemoval;
-    public final Set<DexType> noVerticalClassMerging;
-    public final Set<DexType> noHorizontalClassMerging;
-    public final Set<DexType> noStaticClassMerging;
-    public final Set<DexReference> 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<ProguardIfRule> ifRules;
-
-    private RootSet(
-        MutableItemsWithRules noShrinking,
-        MutableItemsWithRules softPinned,
-        Set<DexReference> noObfuscation,
-        ImmutableList<DexReference> reasonAsked,
-        ImmutableList<DexReference> checkDiscarded,
-        Set<DexMethod> alwaysInline,
-        Set<DexMethod> forceInline,
-        Set<DexMethod> neverInline,
-        Set<DexMethod> neverInlineDueToSingleCaller,
-        Set<DexMethod> bypassClinitForInlining,
-        Set<DexMethod> whyAreYouNotInlining,
-        Set<DexMethod> keepConstantArguments,
-        Set<DexMethod> keepUnusedArguments,
-        Set<DexMethod> reprocess,
-        Set<DexMethod> neverReprocess,
-        PredicateSet<DexType> alwaysClassInline,
-        Set<DexType> neverClassInline,
-        Set<DexType> noUnusedInterfaceRemoval,
-        Set<DexType> noVerticalClassMerging,
-        Set<DexType> noHorizontalClassMerging,
-        Set<DexType> noStaticClassMerging,
-        Set<DexReference> neverPropagateValue,
-        Map<DexReference, ProguardMemberRule> mayHaveSideEffects,
-        Map<DexMember<?, ?>, ProguardMemberRule> noSideEffects,
-        Map<DexMember<?, ?>, ProguardMemberRule> assumedValues,
-        Map<DexReference, MutableItemsWithRules> dependentNoShrinking,
-        Map<DexReference, MutableItemsWithRules> dependentSoftPinned,
-        Map<DexType, Set<ProguardKeepRuleBase>> dependentKeepClassCompatRule,
-        Set<DexReference> identifierNameStrings,
-        Set<ProguardIfRule> ifRules,
-        List<DelayedRootSetActionItem> delayedRootSetActionItems) {
-      super(
-          neverInline,
-          neverInlineDueToSingleCaller,
-          neverClassInline,
-          noShrinking,
-          softPinned,
-          noObfuscation,
-          dependentNoShrinking,
-          dependentSoftPinned,
-          dependentKeepClassCompatRule,
-          delayedRootSetActionItems);
-      this.reasonAsked = reasonAsked;
-      this.checkDiscarded = checkDiscarded;
-      this.alwaysInline = alwaysInline;
-      this.forceInline = forceInline;
-      this.bypassClinitForInlining = bypassClinitForInlining;
-      this.whyAreYouNotInlining = whyAreYouNotInlining;
-      this.keepConstantArguments = keepConstantArguments;
-      this.keepUnusedArguments = keepUnusedArguments;
-      this.reprocess = reprocess;
-      this.neverReprocess = neverReprocess;
-      this.alwaysClassInline = alwaysClassInline;
-      this.noUnusedInterfaceRemoval = noUnusedInterfaceRemoval;
-      this.noVerticalClassMerging = noVerticalClassMerging;
-      this.noHorizontalClassMerging = noHorizontalClassMerging;
-      this.noStaticClassMerging = noStaticClassMerging;
-      this.neverPropagateValue = neverPropagateValue;
-      this.mayHaveSideEffects = mayHaveSideEffects;
-      this.noSideEffects = noSideEffects;
-      this.assumedValues = assumedValues;
-      this.identifierNameStrings = Collections.unmodifiableSet(identifierNameStrings);
-      this.ifRules = Collections.unmodifiableSet(ifRules);
-    }
-
-    public void checkAllRulesAreUsed(InternalOptions options) {
-      List<ProguardConfigurationRule> rules = options.getProguardConfiguration().getRules();
-      if (rules != null) {
-        for (ProguardConfigurationRule rule : rules) {
-          if (!rule.isUsed()) {
-            String message =
-                "Proguard configuration rule does not match anything: `" + rule.toString() + "`";
-            StringDiagnostic diagnostic = new StringDiagnostic(message, rule.getOrigin());
-            if (options.testing.reportUnusedProguardConfigurationRules) {
-              options.reporter.info(diagnostic);
-            }
-          }
-        }
-      }
-    }
-
-    void addConsequentRootSet(ConsequentRootSet consequentRootSet, boolean addNoShrinking) {
-      neverInline.addAll(consequentRootSet.neverInline);
-      neverInlineDueToSingleCaller.addAll(consequentRootSet.neverInlineDueToSingleCaller);
-      neverClassInline.addAll(consequentRootSet.neverClassInline);
-      noObfuscation.addAll(consequentRootSet.noObfuscation);
-      if (addNoShrinking) {
-        noShrinking.addAll(consequentRootSet.noShrinking);
-      }
-      addDependentItems(consequentRootSet.dependentNoShrinking, dependentNoShrinking);
-      addDependentItems(consequentRootSet.dependentSoftPinned, dependentSoftPinned);
-      consequentRootSet.dependentKeepClassCompatRule.forEach(
-          (type, rules) ->
-              dependentKeepClassCompatRule.computeIfAbsent(
-                  type, k -> new HashSet<>()).addAll(rules));
-      delayedRootSetActionItems.addAll(consequentRootSet.delayedRootSetActionItems);
-    }
-
-    // Add dependent items that depend on -if rules.
-    private static void addDependentItems(
-        Map<DexReference, ? extends ItemsWithRules> dependentItemsToAdd,
-        Map<DexReference, MutableItemsWithRules> dependentItemsToAddTo) {
-      dependentItemsToAdd.forEach(
-          (reference, dependence) ->
-              dependentItemsToAddTo
-                  .computeIfAbsent(reference, x -> new MutableItemsWithRules())
-                  .putAll(dependence));
-    }
-
-    public void copy(DexReference original, DexReference rewritten) {
-      if (noShrinking.containsReference(original)) {
-        noShrinking.putReferenceWithRules(rewritten, noShrinking.getRulesForReference(original));
-      }
-      if (noObfuscation.contains(original)) {
-        noObfuscation.add(rewritten);
-      }
-      if (original.isDexMember()) {
-        assert rewritten.isDexMember();
-        DexMember<?, ?> originalMember = original.asDexMember();
-        if (noSideEffects.containsKey(originalMember)) {
-          noSideEffects.put(rewritten.asDexMember(), noSideEffects.get(originalMember));
-        }
-        if (assumedValues.containsKey(originalMember)) {
-          assumedValues.put(rewritten.asDexMember(), assumedValues.get(originalMember));
-        }
-      }
-    }
-
-    public void prune(DexReference reference) {
-      noShrinking.removeReference(reference);
-      noObfuscation.remove(reference);
-      noSideEffects.remove(reference);
-      assumedValues.remove(reference);
-    }
-
-    public void pruneDeadItems(DexDefinitionSupplier definitions, Enqueuer enqueuer) {
-      pruneDeadReferences(noUnusedInterfaceRemoval, definitions, enqueuer);
-      pruneDeadReferences(noVerticalClassMerging, definitions, enqueuer);
-      pruneDeadReferences(noHorizontalClassMerging, definitions, enqueuer);
-      pruneDeadReferences(noStaticClassMerging, definitions, enqueuer);
-      pruneDeadReferences(alwaysInline, definitions, enqueuer);
-      pruneDeadReferences(noSideEffects.keySet(), definitions, enqueuer);
-    }
-
-    private static void pruneDeadReferences(
-        Set<? extends DexReference> references,
-        DexDefinitionSupplier definitions,
-        Enqueuer enqueuer) {
-      references.removeIf(
-          reference -> {
-            if (reference.isDexType()) {
-              DexClass definition = definitions.definitionFor(reference.asDexType());
-              return definition == null || !enqueuer.isTypeLive(definition);
-            }
-
-            assert reference.isDexMember();
-
-            DexMember<?, ?> member = reference.asDexMember();
-            DexClass holder = definitions.definitionForHolder(member);
-            DexEncodedMember<?, ?> definition = member.lookupOnClass(holder);
-            if (definition == null) {
-              return true;
-            }
-            if (holder.isProgramClass()) {
-              if (definition.isDexEncodedField()) {
-                DexEncodedField field = definition.asDexEncodedField();
-                return !enqueuer.isFieldReferenced(field);
-              }
-              assert definition.isDexEncodedMethod();
-              DexEncodedMethod method = definition.asDexEncodedMethod();
-              return !enqueuer.isMethodLive(method) && !enqueuer.isMethodTargeted(method);
-            }
-            return !enqueuer.isNonProgramTypeLive(holder);
-          });
-    }
-
-    public void move(DexReference original, DexReference rewritten) {
-      copy(original, rewritten);
-      prune(original);
-    }
-
-    void shouldNotBeMinified(DexReference reference) {
-      noObfuscation.add(reference);
-    }
-
-    public boolean verifyKeptFieldsAreAccessedAndLive(AppInfoWithLiveness appInfo) {
-      noShrinking.forEachField(
-          reference -> {
-            DexClass holder = appInfo.definitionForHolder(reference);
-            DexEncodedField field = reference.lookupOnClass(holder);
-            if (field != null
-                && (field.isStatic()
-                    || isKeptDirectlyOrIndirectly(field.getHolderType(), appInfo))) {
-              assert appInfo.isFieldRead(field)
-                  : "Expected kept field `" + field.toSourceString() + "` to be read";
-              assert appInfo.isFieldWritten(field)
-                  : "Expected kept field `" + field.toSourceString() + "` to be written";
-            }
-          });
-      return true;
-    }
-
-    public boolean verifyKeptMethodsAreTargetedAndLive(AppInfoWithLiveness appInfo) {
-      noShrinking.forEachMethod(
-          reference -> {
-            assert appInfo.isTargetedMethod(reference)
-                : "Expected kept method `" + reference.toSourceString() + "` to be targeted";
-            DexEncodedMethod method =
-                appInfo.definitionForHolder(reference).lookupMethod(reference);
-            if (!method.isAbstract()
-                && isKeptDirectlyOrIndirectly(method.getHolderType(), appInfo)) {
-              assert appInfo.isLiveMethod(reference)
-                  : "Expected non-abstract kept method `"
-                      + reference.toSourceString()
-                      + "` to be live";
-            }
-          });
-      return true;
-    }
-
-    public boolean verifyKeptTypesAreLive(AppInfoWithLiveness appInfo) {
-      noShrinking.forEachClass(
-          type -> {
-            assert appInfo.isLiveProgramType(type)
-                : "Expected kept type `" + type.toSourceString() + "` to be live";
-          });
-      return true;
-    }
-
-    private boolean isKeptDirectlyOrIndirectly(DexType type, AppInfoWithLiveness appInfo) {
-      if (noShrinking.containsClass(type)) {
-        return true;
-      }
-      DexClass clazz = appInfo.definitionFor(type);
-      if (clazz == null) {
-        return false;
-      }
-      if (clazz.superType != null) {
-        return isKeptDirectlyOrIndirectly(clazz.superType, appInfo);
-      }
-      return false;
-    }
-
-    public boolean verifyKeptItemsAreKept(AppView<? extends AppInfoWithClassHierarchy> appView) {
-      AppInfoWithClassHierarchy appInfo = appView.appInfo();
-      GraphLens lens = appView.graphLens();
-      // Create a mapping from each required type to the set of required members on that type.
-      Map<DexType, Set<DexMember<?, ?>>> requiredMembersPerType = new IdentityHashMap<>();
-      noShrinking.forEachClass(
-          type -> {
-            DexType rewrittenType = lens.lookupType(type);
-            assert !appInfo.hasLiveness() || appInfo.withLiveness().isPinned(rewrittenType)
-                : "Expected reference `" + rewrittenType.toSourceString() + "` to be pinned";
-            requiredMembersPerType.computeIfAbsent(rewrittenType, key -> Sets.newIdentityHashSet());
-          });
-      noShrinking.forEachMember(
-          member -> {
-            DexMember<?, ?> rewrittenMember = lens.getRenamedMemberSignature(member);
-            assert !appInfo.hasLiveness() || appInfo.withLiveness().isPinned(rewrittenMember)
-                : "Expected reference `" + rewrittenMember.toSourceString() + "` to be pinned";
-            requiredMembersPerType
-                .computeIfAbsent(rewrittenMember.holder, key -> Sets.newIdentityHashSet())
-                .add(rewrittenMember);
-          });
-
-      // Run through each class in the program and check that it has members it must have.
-      for (DexProgramClass clazz : appView.appInfo().classes()) {
-        Set<DexMember<?, ?>> requiredMembers =
-            requiredMembersPerType.getOrDefault(clazz.type, ImmutableSet.of());
-
-        Set<DexField> fields = null;
-        Set<DexMethod> methods = null;
-
-        for (DexMember<?, ?> requiredMember : requiredMembers) {
-          if (requiredMember.isDexField()) {
-            DexField requiredField = requiredMember.asDexField();
-            if (fields == null) {
-              // Create a Set of the fields to avoid quadratic behavior.
-              fields =
-                  Streams.stream(clazz.fields())
-                      .map(DexEncodedField::getReference)
-                      .collect(Collectors.toSet());
-            }
-            assert fields.contains(requiredField)
-                : "Expected field `"
-                    + requiredField.toSourceString()
-                    + "` from the root set to be present";
-          } else {
-            DexMethod requiredMethod = requiredMember.asDexMethod();
-            if (methods == null) {
-              // Create a Set of the methods to avoid quadratic behavior.
-              methods =
-                  Streams.stream(clazz.methods())
-                      .map(DexEncodedMethod::getReference)
-                      .collect(Collectors.toSet());
-            }
-            assert methods.contains(requiredMethod)
-                : "Expected method `"
-                    + requiredMethod.toSourceString()
-                    + "` from the root set to be present";
-          }
-        }
-        requiredMembersPerType.remove(clazz.type);
-      }
-
-      // If the map is non-empty, then a type in the root set was not in the application.
-      if (!requiredMembersPerType.isEmpty()) {
-        DexType type = requiredMembersPerType.keySet().iterator().next();
-        DexClass clazz = appView.definitionFor(type);
-        assert clazz == null || clazz.isProgramClass()
-            : "Unexpected library type in root set: `" + type + "`";
-        assert requiredMembersPerType.isEmpty()
-            : "Expected type `" + type.toSourceString() + "` to be present";
-      }
-
-      return true;
-    }
-
-    @Override
-    public String toString() {
-      StringBuilder builder = new StringBuilder();
-      builder.append("RootSet");
-      builder.append("\nnoShrinking: " + noShrinking.size());
-      builder.append("\nnoObfuscation: " + noObfuscation.size());
-      builder.append("\nreasonAsked: " + reasonAsked.size());
-      builder.append("\ncheckDiscarded: " + checkDiscarded.size());
-      builder.append("\nnoSideEffects: " + noSideEffects.size());
-      builder.append("\nassumedValues: " + assumedValues.size());
-      builder.append("\ndependentNoShrinking: " + dependentNoShrinking.size());
-      builder.append("\nidentifierNameStrings: " + identifierNameStrings.size());
-      builder.append("\nifRules: " + ifRules.size());
-      return builder.toString();
-    }
-  }
-
-  // A partial RootSet that becomes live due to the enabled -if rule or the addition of interface
-  // keep rules.
-  public static class ConsequentRootSet extends RootSetBase {
-
-    ConsequentRootSet(
-        Set<DexMethod> neverInline,
-        Set<DexMethod> neverInlineDueToSingleCaller,
-        Set<DexType> neverClassInline,
-        MutableItemsWithRules noShrinking,
-        MutableItemsWithRules softPinned,
-        Set<DexReference> noObfuscation,
-        Map<DexReference, MutableItemsWithRules> dependentNoShrinking,
-        Map<DexReference, MutableItemsWithRules> dependentSoftPinned,
-        Map<DexType, Set<ProguardKeepRuleBase>> dependentKeepClassCompatRule,
-        List<DelayedRootSetActionItem> delayedRootSetActionItems) {
-      super(
-          neverInline,
-          neverInlineDueToSingleCaller,
-          neverClassInline,
-          noShrinking,
-          softPinned,
-          noObfuscation,
-          dependentNoShrinking,
-          dependentSoftPinned,
-          dependentKeepClassCompatRule,
-          delayedRootSetActionItems);
-    }
-  }
-}
diff --git a/src/main/java/com/android/tools/r8/shaking/RootSetUtils.java b/src/main/java/com/android/tools/r8/shaking/RootSetUtils.java
new file mode 100644
index 0000000..f4567e9
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/shaking/RootSetUtils.java
@@ -0,0 +1,2196 @@
+// Copyright (c) 2016, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.shaking;
+
+import static com.android.tools.r8.graph.DexProgramClass.asProgramClassOrNull;
+
+import com.android.tools.r8.dex.Constants;
+import com.android.tools.r8.errors.AssumeNoSideEffectsRuleForObjectMembersDiagnostic;
+import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.BottomUpClassHierarchyTraversal;
+import com.android.tools.r8.graph.DexAnnotation;
+import com.android.tools.r8.graph.DexAnnotationSet;
+import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexDefinition;
+import com.android.tools.r8.graph.DexDefinitionSupplier;
+import com.android.tools.r8.graph.DexEncodedField;
+import com.android.tools.r8.graph.DexEncodedMember;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.graph.DexLibraryClass;
+import com.android.tools.r8.graph.DexMember;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.DexReference;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.DirectMappedDexApplication;
+import com.android.tools.r8.graph.GraphLens;
+import com.android.tools.r8.graph.ProgramMember;
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.graph.ResolutionResult.SingleResolutionResult;
+import com.android.tools.r8.graph.SubtypingInfo;
+import com.android.tools.r8.ir.analysis.proto.GeneratedMessageLiteBuilderShrinker;
+import com.android.tools.r8.ir.optimize.info.OptimizationFeedbackSimple;
+import com.android.tools.r8.logging.Log;
+import com.android.tools.r8.shaking.AnnotationMatchResult.AnnotationsIgnoredMatchResult;
+import com.android.tools.r8.shaking.AnnotationMatchResult.ConcreteAnnotationMatchResult;
+import com.android.tools.r8.shaking.DelayedRootSetActionItem.InterfaceMethodSyntheticBridgeAction;
+import com.android.tools.r8.utils.ArrayUtils;
+import com.android.tools.r8.utils.Consumer3;
+import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.MethodSignatureEquivalence;
+import com.android.tools.r8.utils.OriginWithPosition;
+import com.android.tools.r8.utils.PredicateSet;
+import com.android.tools.r8.utils.StringDiagnostic;
+import com.android.tools.r8.utils.ThreadUtils;
+import com.google.common.base.Equivalence.Wrapper;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Sets;
+import com.google.common.collect.Streams;
+import java.io.PrintStream;
+import java.util.ArrayDeque;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Deque;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.IdentityHashMap;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Queue;
+import java.util.Set;
+import java.util.Stack;
+import java.util.TreeSet;
+import java.util.concurrent.ConcurrentLinkedQueue;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Future;
+import java.util.function.BiConsumer;
+import java.util.function.Consumer;
+import java.util.function.Predicate;
+import java.util.stream.Collectors;
+
+public class RootSetUtils {
+
+  public static class RootSetBuilder {
+
+    private final AppView<? extends AppInfoWithClassHierarchy> appView;
+    private final SubtypingInfo subtypingInfo;
+    private final DirectMappedDexApplication application;
+    private final Iterable<? extends ProguardConfigurationRule> rules;
+    private final MutableItemsWithRules noShrinking = new MutableItemsWithRules();
+    private final MutableItemsWithRules softPinned = new MutableItemsWithRules();
+    private final Set<DexReference> noObfuscation = Sets.newIdentityHashSet();
+    private final LinkedHashMap<DexReference, DexReference> reasonAsked = new LinkedHashMap<>();
+    private final LinkedHashMap<DexReference, DexReference> checkDiscarded = new LinkedHashMap<>();
+    private final Set<DexMethod> alwaysInline = Sets.newIdentityHashSet();
+    private final Set<DexMethod> forceInline = Sets.newIdentityHashSet();
+    private final Set<DexMethod> neverInline = Sets.newIdentityHashSet();
+    private final Set<DexMethod> neverInlineDueToSingleCaller = Sets.newIdentityHashSet();
+    private final Set<DexMethod> bypassClinitforInlining = Sets.newIdentityHashSet();
+    private final Set<DexMethod> whyAreYouNotInlining = Sets.newIdentityHashSet();
+    private final Set<DexMethod> keepParametersWithConstantValue = Sets.newIdentityHashSet();
+    private final Set<DexMethod> keepUnusedArguments = Sets.newIdentityHashSet();
+    private final Set<DexMethod> reprocess = Sets.newIdentityHashSet();
+    private final Set<DexMethod> neverReprocess = Sets.newIdentityHashSet();
+    private final PredicateSet<DexType> alwaysClassInline = new PredicateSet<>();
+    private final Set<DexType> neverClassInline = Sets.newIdentityHashSet();
+    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 Map<DexReference, MutableItemsWithRules> dependentNoShrinking =
+        new IdentityHashMap<>();
+    private final Map<DexReference, MutableItemsWithRules> dependentSoftPinned =
+        new IdentityHashMap<>();
+    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 Queue<DelayedRootSetActionItem> delayedRootSetActionItems =
+        new ConcurrentLinkedQueue<>();
+    private final InternalOptions options;
+
+    private final DexStringCache dexStringCache = new DexStringCache();
+    private final Set<ProguardIfRule> ifRules = Sets.newIdentityHashSet();
+
+    private final Map<OriginWithPosition, Set<DexMethod>> assumeNoSideEffectsWarnings =
+        new LinkedHashMap<>();
+
+    private final OptimizationFeedbackSimple feedback = OptimizationFeedbackSimple.getInstance();
+
+    private RootSetBuilder(
+        AppView<? extends AppInfoWithClassHierarchy> appView,
+        SubtypingInfo subtypingInfo,
+        Iterable<? extends ProguardConfigurationRule> rules) {
+      this.appView = appView;
+      this.subtypingInfo = subtypingInfo;
+      this.application = appView.appInfo().app().asDirect();
+      this.rules = rules;
+      this.options = appView.options();
+    }
+
+    private RootSetBuilder(
+        AppView<? extends AppInfoWithClassHierarchy> appView, SubtypingInfo subtypingInfo) {
+      this(appView, subtypingInfo, null);
+    }
+
+    void handleMatchedAnnotation(AnnotationMatchResult annotation) {
+      // Intentionally empty.
+    }
+
+    // Process a class with the keep rule.
+    private void process(DexClass clazz, ProguardConfigurationRule rule, ProguardIfRule ifRule) {
+      if (!satisfyClassType(rule, clazz)) {
+        return;
+      }
+      if (!satisfyAccessFlag(rule, clazz)) {
+        return;
+      }
+      AnnotationMatchResult annotationMatchResult = satisfyAnnotation(rule, clazz);
+      if (annotationMatchResult == null) {
+        return;
+      }
+      handleMatchedAnnotation(annotationMatchResult);
+      // In principle it should make a difference whether the user specified in a class
+      // spec that a class either extends or implements another type. However, proguard
+      // seems not to care, so users have started to use this inconsistently. We are thus
+      // inconsistent, as well, but tell them.
+      // TODO(herhut): One day make this do what it says.
+      if (rule.hasInheritanceClassName() && !satisfyInheritanceRule(clazz, rule)) {
+        return;
+      }
+
+      if (!rule.getClassNames().matches(clazz.type)) {
+        return;
+      }
+
+      Collection<ProguardMemberRule> memberKeepRules = rule.getMemberRules();
+      Map<Predicate<DexDefinition>, DexDefinition> preconditionSupplier;
+      if (rule instanceof ProguardKeepRule) {
+        if (clazz.isNotProgramClass()) {
+          return;
+        }
+        switch (((ProguardKeepRule) rule).getType()) {
+          case KEEP_CLASS_MEMBERS:
+            // Members mentioned at -keepclassmembers always depend on their holder.
+            preconditionSupplier = ImmutableMap.of(definition -> true, clazz);
+            markMatchingVisibleMethods(
+                clazz, memberKeepRules, rule, preconditionSupplier, false, ifRule);
+            markMatchingVisibleFields(
+                clazz, memberKeepRules, rule, preconditionSupplier, false, ifRule);
+            break;
+          case KEEP_CLASSES_WITH_MEMBERS:
+            if (!allRulesSatisfied(memberKeepRules, clazz)) {
+              break;
+            }
+            // fall through;
+          case KEEP:
+            markClass(clazz, rule, ifRule);
+            preconditionSupplier = new HashMap<>();
+            if (ifRule != null) {
+              // Static members in -keep are pinned no matter what.
+              preconditionSupplier.put(DexDefinition::isStaticMember, null);
+              // Instance members may need to be kept even though the holder is not instantiated.
+              preconditionSupplier.put(definition -> !definition.isStaticMember(), clazz);
+            } else {
+              // Members mentioned at -keep should always be pinned as long as that -keep rule is
+              // not triggered conditionally.
+              preconditionSupplier.put((definition -> true), null);
+            }
+            markMatchingVisibleMethods(
+                clazz, memberKeepRules, rule, preconditionSupplier, false, ifRule);
+            markMatchingVisibleFields(
+                clazz, memberKeepRules, rule, preconditionSupplier, false, ifRule);
+            break;
+          case CONDITIONAL:
+            throw new Unreachable("-if rule will be evaluated separately, not here.");
+        }
+        return;
+      }
+      // Only the ordinary keep rules are supported in a conditional rule.
+      assert ifRule == null;
+      if (rule instanceof ProguardIfRule) {
+        throw new Unreachable("-if rule will be evaluated separately, not here.");
+      } else if (rule instanceof ProguardCheckDiscardRule) {
+        if (memberKeepRules.isEmpty()) {
+          markClass(clazz, rule, ifRule);
+        } else {
+          preconditionSupplier = ImmutableMap.of((definition -> true), clazz);
+          markMatchingVisibleMethods(
+              clazz, memberKeepRules, rule, preconditionSupplier, true, ifRule);
+          markMatchingVisibleFields(
+              clazz, memberKeepRules, rule, preconditionSupplier, true, ifRule);
+        }
+      } else if (rule instanceof ProguardWhyAreYouKeepingRule) {
+        markClass(clazz, rule, ifRule);
+        markMatchingVisibleMethods(clazz, memberKeepRules, rule, null, true, ifRule);
+        markMatchingVisibleFields(clazz, memberKeepRules, rule, null, true, ifRule);
+      } else if (rule instanceof ProguardAssumeMayHaveSideEffectsRule
+          || rule instanceof ProguardAssumeNoSideEffectRule
+          || rule instanceof ProguardAssumeValuesRule) {
+        markMatchingVisibleMethods(clazz, memberKeepRules, rule, null, true, ifRule);
+        markMatchingOverriddenMethods(
+            appView.appInfo(), clazz, memberKeepRules, rule, null, true, ifRule);
+        markMatchingVisibleFields(clazz, memberKeepRules, rule, null, true, ifRule);
+      } else if (rule instanceof InlineRule
+          || rule instanceof ConstantArgumentRule
+          || rule instanceof UnusedArgumentRule
+          || rule instanceof ReprocessMethodRule
+          || rule instanceof WhyAreYouNotInliningRule) {
+        markMatchingMethods(clazz, memberKeepRules, rule, null, ifRule);
+      } else if (rule instanceof ClassInlineRule
+          || rule instanceof NoUnusedInterfaceRemovalRule
+          || rule instanceof NoVerticalClassMergingRule
+          || rule instanceof NoHorizontalClassMergingRule
+          || rule instanceof ReprocessClassInitializerRule) {
+        if (allRulesSatisfied(memberKeepRules, clazz)) {
+          markClass(clazz, rule, ifRule);
+        }
+      } else if (rule instanceof MemberValuePropagationRule) {
+        markMatchingVisibleMethods(clazz, memberKeepRules, rule, null, true, ifRule);
+        markMatchingVisibleFields(clazz, memberKeepRules, rule, null, true, ifRule);
+      } else {
+        assert rule instanceof ProguardIdentifierNameStringRule;
+        markMatchingFields(clazz, memberKeepRules, rule, null, ifRule);
+        markMatchingMethods(clazz, memberKeepRules, rule, null, ifRule);
+      }
+    }
+
+    void runPerRule(
+        ExecutorService executorService,
+        List<Future<?>> futures,
+        ProguardConfigurationRule rule,
+        ProguardIfRule ifRule) {
+      List<DexType> specifics = rule.getClassNames().asSpecificDexTypes();
+      if (specifics != null) {
+        // This keep rule only lists specific type matches.
+        // This means there is no need to iterate over all classes.
+        for (DexType type : specifics) {
+          DexClass clazz = application.definitionFor(type);
+          // Ignore keep rule iff it does not reference a class in the app.
+          if (clazz != null) {
+            process(clazz, rule, ifRule);
+          }
+        }
+        return;
+      }
+
+      futures.add(
+          executorService.submit(
+              () -> {
+                for (DexProgramClass clazz :
+                    rule.relevantCandidatesForRule(appView, subtypingInfo, application.classes())) {
+                  process(clazz, rule, ifRule);
+                }
+                if (rule.applyToNonProgramClasses()) {
+                  for (DexLibraryClass clazz : application.libraryClasses()) {
+                    process(clazz, rule, ifRule);
+                  }
+                }
+              }));
+    }
+
+    public RootSet build(ExecutorService executorService) throws ExecutionException {
+      application.timing.begin("Build root set...");
+      try {
+        List<Future<?>> futures = new ArrayList<>();
+        // Mark all the things explicitly listed in keep rules.
+        if (rules != null) {
+          for (ProguardConfigurationRule rule : rules) {
+            if (rule instanceof ProguardIfRule) {
+              ProguardIfRule ifRule = (ProguardIfRule) rule;
+              ifRules.add(ifRule);
+            } else {
+              runPerRule(executorService, futures, rule, null);
+            }
+          }
+          ThreadUtils.awaitFutures(futures);
+        }
+      } finally {
+        application.timing.end();
+      }
+      generateAssumeNoSideEffectsWarnings();
+      if (!noSideEffects.isEmpty() || !assumedValues.isEmpty()) {
+        BottomUpClassHierarchyTraversal.forAllClasses(appView, subtypingInfo)
+            .visit(appView.appInfo().classes(), this::propagateAssumeRules);
+      }
+      if (appView.options().protoShrinking().enableGeneratedMessageLiteBuilderShrinking) {
+        GeneratedMessageLiteBuilderShrinker.addInliningHeuristicsForBuilderInlining(
+            appView,
+            subtypingInfo,
+            alwaysClassInline,
+            noVerticalClassMerging,
+            noHorizontalClassMerging,
+            alwaysInline,
+            bypassClinitforInlining);
+      }
+      assert Sets.intersection(neverInline, alwaysInline).isEmpty()
+              && Sets.intersection(neverInline, forceInline).isEmpty()
+          : "A method cannot be marked as both -neverinline and -forceinline/-alwaysinline.";
+      assert appView.options().isMinificationEnabled() || noObfuscation.isEmpty();
+      return new RootSet(
+          noShrinking,
+          softPinned,
+          noObfuscation,
+          ImmutableList.copyOf(reasonAsked.values()),
+          ImmutableList.copyOf(checkDiscarded.values()),
+          alwaysInline,
+          forceInline,
+          neverInline,
+          neverInlineDueToSingleCaller,
+          bypassClinitforInlining,
+          whyAreYouNotInlining,
+          keepParametersWithConstantValue,
+          keepUnusedArguments,
+          reprocess,
+          neverReprocess,
+          alwaysClassInline,
+          neverClassInline,
+          noUnusedInterfaceRemoval,
+          noVerticalClassMerging,
+          noHorizontalClassMerging,
+          neverPropagateValue,
+          mayHaveSideEffects,
+          noSideEffects,
+          assumedValues,
+          dependentNoShrinking,
+          dependentSoftPinned,
+          dependentKeepClassCompatRule,
+          identifierNameStrings,
+          ifRules,
+          Lists.newArrayList(delayedRootSetActionItems));
+    }
+
+    private void propagateAssumeRules(DexClass clazz) {
+      Set<DexType> subTypes = subtypingInfo.allImmediateSubtypes(clazz.type);
+      if (subTypes.isEmpty()) {
+        return;
+      }
+      for (DexEncodedMethod encodedMethod : clazz.virtualMethods()) {
+        // If the method has a body, it may have side effects. Don't do bottom-up propagation.
+        if (encodedMethod.hasCode()) {
+          assert !encodedMethod.shouldNotHaveCode();
+          continue;
+        }
+        propagateAssumeRules(clazz.type, encodedMethod.method, subTypes, noSideEffects);
+        propagateAssumeRules(clazz.type, encodedMethod.method, subTypes, assumedValues);
+      }
+    }
+
+    private void propagateAssumeRules(
+        DexType type,
+        DexMethod reference,
+        Set<DexType> subTypes,
+        Map<DexMember<?, ?>, ProguardMemberRule> assumeRulePool) {
+      ProguardMemberRule ruleToBePropagated = null;
+      for (DexType subType : subTypes) {
+        DexMethod referenceInSubType =
+            appView.dexItemFactory().createMethod(subType, reference.proto, reference.name);
+        // Those rules are bound to definitions, not references. If the current subtype does not
+        // override the method, and when the retrieval of bound rule fails, it is unclear whether it
+        // is due to the lack of the definition or it indeed means no matching rules. Similar to how
+        // we apply those assume rules, here we use a resolved target.
+        DexEncodedMethod target =
+            appView
+                .appInfo()
+                .unsafeResolveMethodDueToDexFormat(referenceInSubType)
+                .getSingleTarget();
+        // But, the resolution should not be landed on the current type we are visiting.
+        if (target == null || target.getHolderType() == type) {
+          continue;
+        }
+        ProguardMemberRule ruleInSubType = assumeRulePool.get(target.method);
+        // We are looking for the greatest lower bound of assume rules from all sub types.
+        // If any subtype doesn't have a matching assume rule, the lower bound is literally nothing.
+        if (ruleInSubType == null) {
+          ruleToBePropagated = null;
+          break;
+        }
+        if (ruleToBePropagated == null) {
+          ruleToBePropagated = ruleInSubType;
+        } else {
+          // TODO(b/133208961): Introduce comparison/meet of assume rules.
+          if (!ruleToBePropagated.equals(ruleInSubType)) {
+            ruleToBePropagated = null;
+            break;
+          }
+        }
+      }
+      if (ruleToBePropagated != null) {
+        assumeRulePool.put(reference, ruleToBePropagated);
+      }
+    }
+
+    ConsequentRootSet buildConsequentRootSet() {
+      return new ConsequentRootSet(
+          neverInline,
+          neverInlineDueToSingleCaller,
+          neverClassInline,
+          noShrinking,
+          softPinned,
+          noObfuscation,
+          dependentNoShrinking,
+          dependentSoftPinned,
+          dependentKeepClassCompatRule,
+          Lists.newArrayList(delayedRootSetActionItems));
+    }
+
+    private static DexDefinition testAndGetPrecondition(
+        DexDefinition definition,
+        Map<Predicate<DexDefinition>, DexDefinition> preconditionSupplier) {
+      if (preconditionSupplier == null) {
+        return null;
+      }
+      DexDefinition precondition = null;
+      boolean conditionEverMatched = false;
+      for (Entry<Predicate<DexDefinition>, DexDefinition> entry : preconditionSupplier.entrySet()) {
+        if (entry.getKey().test(definition)) {
+          precondition = entry.getValue();
+          conditionEverMatched = true;
+          break;
+        }
+      }
+      // If precondition-supplier is given, there should be at least one predicate that holds.
+      // Actually, there should be only one predicate as we break the loop when it is found.
+      assert conditionEverMatched;
+      return precondition;
+    }
+
+    private void markMatchingVisibleMethods(
+        DexClass clazz,
+        Collection<ProguardMemberRule> memberKeepRules,
+        ProguardConfigurationRule rule,
+        Map<Predicate<DexDefinition>, DexDefinition> preconditionSupplier,
+        boolean includeLibraryClasses,
+        ProguardIfRule ifRule) {
+      Set<Wrapper<DexMethod>> methodsMarked =
+          options.forceProguardCompatibility ? null : new HashSet<>();
+      Stack<DexClass> worklist = new Stack<>();
+      worklist.add(clazz);
+      while (!worklist.isEmpty()) {
+        DexClass currentClass = worklist.pop();
+        if (!includeLibraryClasses && currentClass.isNotProgramClass()) {
+          break;
+        }
+        // In compat mode traverse all direct methods in the hierarchy.
+        if (currentClass == clazz || options.forceProguardCompatibility) {
+          currentClass
+              .directMethods()
+              .forEach(
+                  method -> {
+                    DexDefinition precondition =
+                        testAndGetPrecondition(method, preconditionSupplier);
+                    markMethod(method, memberKeepRules, methodsMarked, rule, precondition, ifRule);
+                  });
+        }
+        currentClass
+            .virtualMethods()
+            .forEach(
+                method -> {
+                  DexDefinition precondition = testAndGetPrecondition(method, preconditionSupplier);
+                  markMethod(method, memberKeepRules, methodsMarked, rule, precondition, ifRule);
+                });
+        if (currentClass.superType != null) {
+          DexClass dexClass = application.definitionFor(currentClass.superType);
+          if (dexClass != null) {
+            worklist.add(dexClass);
+          }
+        }
+      }
+      // TODO(b/143643942): Generalize the below approach to also work for subtyping hierarchies in
+      //  fullmode.
+      if (clazz.isProgramClass()
+          && rule.isProguardKeepRule()
+          && !rule.asProguardKeepRule().getModifiers().allowsShrinking) {
+        new SynthesizeMissingInterfaceMethodsForMemberRules(
+                clazz.asProgramClass(), memberKeepRules, rule, preconditionSupplier, ifRule)
+            .run();
+      }
+    }
+
+    /**
+     * Utility class for visiting all super interfaces to ensure we keep method definitions
+     * specified by proguard rules. If possible, we generate a forwarding bridge to the resolved
+     * target. If not, we specifically synthesize a keep rule for the interface method.
+     */
+    private class SynthesizeMissingInterfaceMethodsForMemberRules {
+
+      private final DexProgramClass originalClazz;
+      private final Collection<ProguardMemberRule> memberKeepRules;
+      private final ProguardConfigurationRule context;
+      private final Map<Predicate<DexDefinition>, DexDefinition> preconditionSupplier;
+      private final ProguardIfRule ifRule;
+      private final Set<Wrapper<DexMethod>> seenMethods = Sets.newHashSet();
+      private final Set<DexType> seenTypes = Sets.newIdentityHashSet();
+
+      private SynthesizeMissingInterfaceMethodsForMemberRules(
+          DexProgramClass originalClazz,
+          Collection<ProguardMemberRule> memberKeepRules,
+          ProguardConfigurationRule context,
+          Map<Predicate<DexDefinition>, DexDefinition> preconditionSupplier,
+          ProguardIfRule ifRule) {
+        assert context.isProguardKeepRule();
+        assert !context.asProguardKeepRule().getModifiers().allowsShrinking;
+        this.originalClazz = originalClazz;
+        this.memberKeepRules = memberKeepRules;
+        this.context = context;
+        this.preconditionSupplier = preconditionSupplier;
+        this.ifRule = ifRule;
+      }
+
+      void handleMatchedAnnotation(AnnotationMatchResult annotationMatchResult) {
+        // Intentionally empty.
+      }
+
+      void run() {
+        visitAllSuperInterfaces(originalClazz.type);
+      }
+
+      private void visitAllSuperInterfaces(DexType type) {
+        DexClass clazz = appView.definitionFor(type);
+        if (clazz == null || clazz.isNotProgramClass() || !seenTypes.add(type)) {
+          return;
+        }
+        for (DexType iface : clazz.interfaces.values) {
+          visitAllSuperInterfaces(iface);
+        }
+        if (!clazz.isInterface()) {
+          visitAllSuperInterfaces(clazz.superType);
+          return;
+        }
+        if (originalClazz == clazz) {
+          return;
+        }
+        for (DexEncodedMethod method : clazz.virtualMethods()) {
+          // Check if we already added this.
+          Wrapper<DexMethod> wrapped = MethodSignatureEquivalence.get().wrap(method.method);
+          if (!seenMethods.add(wrapped)) {
+            continue;
+          }
+          for (ProguardMemberRule rule : memberKeepRules) {
+            if (rule.matches(method, appView, this::handleMatchedAnnotation, dexStringCache)) {
+              tryAndKeepMethodOnClass(method, rule);
+            }
+          }
+        }
+      }
+
+      private void tryAndKeepMethodOnClass(DexEncodedMethod method, ProguardMemberRule rule) {
+        SingleResolutionResult resolutionResult =
+            appView.appInfo().resolveMethodOn(originalClazz, method.method).asSingleResolution();
+        if (resolutionResult == null || !resolutionResult.isVirtualTarget()) {
+          return;
+        }
+        if (resolutionResult.getResolvedHolder() == originalClazz
+            || resolutionResult.getResolvedHolder().isNotProgramClass()) {
+          return;
+        }
+        if (!resolutionResult.getResolvedHolder().isInterface()) {
+          // TODO(b/143643942): For fullmode, this check should probably be removed.
+          return;
+        }
+        ProgramMethod resolutionMethod =
+            new ProgramMethod(
+                resolutionResult.getResolvedHolder().asProgramClass(),
+                resolutionResult.getResolvedMethod());
+        ProgramMethod methodToKeep =
+            canInsertForwardingMethod(originalClazz, resolutionMethod.getDefinition())
+                ? new ProgramMethod(
+                    originalClazz,
+                    resolutionMethod.getDefinition().toForwardingMethod(originalClazz, appView))
+                : resolutionMethod;
+
+        delayedRootSetActionItems.add(
+            new InterfaceMethodSyntheticBridgeAction(
+                methodToKeep,
+                resolutionMethod,
+                (rootSetBuilder) -> {
+                  if (Log.ENABLED) {
+                    Log.verbose(
+                        getClass(),
+                        "Marking method `%s` due to `%s { %s }`.",
+                        methodToKeep,
+                        context,
+                        rule);
+                  }
+                  DexDefinition precondition =
+                      testAndGetPrecondition(methodToKeep.getDefinition(), preconditionSupplier);
+                  rootSetBuilder.addItemToSets(
+                      methodToKeep.getDefinition(), context, rule, precondition, ifRule);
+                }));
+      }
+    }
+
+    private boolean canInsertForwardingMethod(DexClass holder, DexEncodedMethod target) {
+      return appView.options().isGeneratingDex()
+          || ArrayUtils.contains(holder.interfaces.values, target.getHolderType());
+    }
+
+    private void markMatchingOverriddenMethods(
+        AppInfoWithClassHierarchy appInfoWithSubtyping,
+        DexClass clazz,
+        Collection<ProguardMemberRule> memberKeepRules,
+        ProguardConfigurationRule rule,
+        Map<Predicate<DexDefinition>, DexDefinition> preconditionSupplier,
+        boolean onlyIncludeProgramClasses,
+        ProguardIfRule ifRule) {
+      Set<DexType> visited = new HashSet<>();
+      Deque<DexType> worklist = new ArrayDeque<>();
+      // Intentionally skip the current `clazz`, assuming it's covered by
+      // markMatchingVisibleMethods.
+      worklist.addAll(subtypingInfo.allImmediateSubtypes(clazz.type));
+
+      while (!worklist.isEmpty()) {
+        DexType currentType = worklist.poll();
+        if (!visited.add(currentType)) {
+          continue;
+        }
+        DexClass currentClazz = appView.definitionFor(currentType);
+        if (currentClazz == null) {
+          continue;
+        }
+        if (!onlyIncludeProgramClasses && currentClazz.isNotProgramClass()) {
+          continue;
+        }
+        currentClazz
+            .virtualMethods()
+            .forEach(
+                method -> {
+                  DexDefinition precondition = testAndGetPrecondition(method, preconditionSupplier);
+                  markMethod(method, memberKeepRules, null, rule, precondition, ifRule);
+                });
+        worklist.addAll(subtypingInfo.allImmediateSubtypes(currentClazz.type));
+      }
+    }
+
+    private void markMatchingMethods(
+        DexClass clazz,
+        Collection<ProguardMemberRule> memberKeepRules,
+        ProguardConfigurationRule rule,
+        Map<Predicate<DexDefinition>, DexDefinition> preconditionSupplier,
+        ProguardIfRule ifRule) {
+      clazz.forEachMethod(
+          method -> {
+            DexDefinition precondition = testAndGetPrecondition(method, preconditionSupplier);
+            markMethod(method, memberKeepRules, null, rule, precondition, ifRule);
+          });
+    }
+
+    private void markMatchingVisibleFields(
+        DexClass clazz,
+        Collection<ProguardMemberRule> memberKeepRules,
+        ProguardConfigurationRule rule,
+        Map<Predicate<DexDefinition>, DexDefinition> preconditionSupplier,
+        boolean includeLibraryClasses,
+        ProguardIfRule ifRule) {
+      while (clazz != null) {
+        if (!includeLibraryClasses && clazz.isNotProgramClass()) {
+          return;
+        }
+        clazz.forEachField(
+            field -> {
+              DexDefinition precondition = testAndGetPrecondition(field, preconditionSupplier);
+              markField(field, memberKeepRules, rule, precondition, ifRule);
+            });
+        clazz = clazz.superType == null ? null : application.definitionFor(clazz.superType);
+      }
+    }
+
+    private void markMatchingFields(
+        DexClass clazz,
+        Collection<ProguardMemberRule> memberKeepRules,
+        ProguardConfigurationRule rule,
+        Map<Predicate<DexDefinition>, DexDefinition> preconditionSupplier,
+        ProguardIfRule ifRule) {
+      clazz.forEachField(
+          field -> {
+            DexDefinition precondition = testAndGetPrecondition(field, preconditionSupplier);
+            markField(field, memberKeepRules, rule, precondition, ifRule);
+          });
+    }
+
+    // TODO(b/67934426): Test this code.
+    public static void writeSeeds(
+        AppInfoWithLiveness appInfo, PrintStream out, Predicate<DexType> include) {
+      appInfo
+          .getKeepInfo()
+          .forEachPinnedType(
+              type -> {
+                if (include.test(type)) {
+                  out.println(type.toSourceString());
+                }
+              });
+      appInfo
+          .getKeepInfo()
+          .forEachPinnedField(
+              field -> {
+                if (include.test(field.holder)) {
+                  out.println(
+                      field.holder.toSourceString()
+                          + ": "
+                          + field.type.toSourceString()
+                          + " "
+                          + field.name.toSourceString());
+                }
+              });
+      appInfo
+          .getKeepInfo()
+          .forEachPinnedMethod(
+              method -> {
+                if (!include.test(method.holder)) {
+                  return;
+                }
+                DexProgramClass holder = asProgramClassOrNull(appInfo.definitionForHolder(method));
+                DexEncodedMethod definition = method.lookupOnClass(holder);
+                if (definition == null) {
+                  assert method.match(appInfo.dexItemFactory().deserializeLambdaMethod);
+                  return;
+                }
+                out.print(method.holder.toSourceString() + ": ");
+                if (definition.isClassInitializer()) {
+                  out.print(Constants.CLASS_INITIALIZER_NAME);
+                } else if (definition.isInstanceInitializer()) {
+                  String holderName = method.holder.toSourceString();
+                  String constrName = holderName.substring(holderName.lastIndexOf('.') + 1);
+                  out.print(constrName);
+                } else {
+                  out.print(
+                      method.proto.returnType.toSourceString()
+                          + " "
+                          + method.name.toSourceString());
+                }
+                boolean first = true;
+                out.print("(");
+                for (DexType param : method.proto.parameters.values) {
+                  if (!first) {
+                    out.print(",");
+                  }
+                  first = false;
+                  out.print(param.toSourceString());
+                }
+                out.println(")");
+              });
+      out.close();
+    }
+
+    static boolean satisfyClassType(ProguardConfigurationRule rule, DexClass clazz) {
+      return rule.getClassType().matches(clazz) != rule.getClassTypeNegated();
+    }
+
+    static boolean satisfyAccessFlag(ProguardConfigurationRule rule, DexClass clazz) {
+      return rule.getClassAccessFlags().containsAll(clazz.accessFlags)
+          && rule.getNegatedClassAccessFlags().containsNone(clazz.accessFlags);
+    }
+
+    static AnnotationMatchResult satisfyAnnotation(ProguardConfigurationRule rule, DexClass clazz) {
+      return containsAllAnnotations(rule.getClassAnnotations(), clazz);
+    }
+
+    boolean satisfyInheritanceRule(DexClass clazz, ProguardConfigurationRule rule) {
+      if (satisfyExtendsRule(clazz, rule)) {
+        return true;
+      }
+
+      return satisfyImplementsRule(clazz, rule);
+    }
+
+    boolean satisfyExtendsRule(DexClass clazz, ProguardConfigurationRule rule) {
+      if (anySuperTypeMatchesExtendsRule(clazz.superType, rule)) {
+        return true;
+      }
+      // It is possible that this class used to inherit from another class X, but no longer does it,
+      // because X has been merged into `clazz`.
+      return anySourceMatchesInheritanceRuleDirectly(clazz, rule, false);
+    }
+
+    boolean anySuperTypeMatchesExtendsRule(DexType type, ProguardConfigurationRule rule) {
+      while (type != null) {
+        DexClass clazz = application.definitionFor(type);
+        if (clazz == null) {
+          // TODO(herhut): Warn about broken supertype chain?
+          return false;
+        }
+        // TODO(b/110141157): Should the vertical class merger move annotations from the source to
+        // the target class? If so, it is sufficient only to apply the annotation-matcher to the
+        // annotations of `class`.
+        if (rule.getInheritanceClassName().matches(clazz.type, appView)) {
+          AnnotationMatchResult annotationMatchResult =
+              containsAllAnnotations(rule.getInheritanceAnnotations(), clazz);
+          if (annotationMatchResult != null) {
+            handleMatchedAnnotation(annotationMatchResult);
+            return true;
+          }
+        }
+        type = clazz.superType;
+      }
+      return false;
+    }
+
+    boolean satisfyImplementsRule(DexClass clazz, ProguardConfigurationRule rule) {
+      if (anyImplementedInterfaceMatchesImplementsRule(clazz, rule)) {
+        return true;
+      }
+      // It is possible that this class used to implement an interface I, but no longer does it,
+      // because I has been merged into `clazz`.
+      return anySourceMatchesInheritanceRuleDirectly(clazz, rule, true);
+    }
+
+    private boolean anyImplementedInterfaceMatchesImplementsRule(
+        DexClass clazz, ProguardConfigurationRule rule) {
+      // TODO(herhut): Maybe it would be better to do this breadth first.
+      if (clazz == null) {
+        return false;
+      }
+      for (DexType iface : clazz.interfaces.values) {
+        DexClass ifaceClass = application.definitionFor(iface);
+        if (ifaceClass == null) {
+          // TODO(herhut): Warn about broken supertype chain?
+          return false;
+        }
+        // TODO(b/110141157): Should the vertical class merger move annotations from the source to
+        // the target class? If so, it is sufficient only to apply the annotation-matcher to the
+        // annotations of `ifaceClass`.
+        if (rule.getInheritanceClassName().matches(iface, appView)) {
+          AnnotationMatchResult annotationMatchResult =
+              containsAllAnnotations(rule.getInheritanceAnnotations(), ifaceClass);
+          if (annotationMatchResult != null) {
+            handleMatchedAnnotation(annotationMatchResult);
+            return true;
+          }
+        }
+        if (anyImplementedInterfaceMatchesImplementsRule(ifaceClass, rule)) {
+          return true;
+        }
+      }
+      if (clazz.superType == null) {
+        return false;
+      }
+      DexClass superClass = application.definitionFor(clazz.superType);
+      if (superClass == null) {
+        // TODO(herhut): Warn about broken supertype chain?
+        return false;
+      }
+      return anyImplementedInterfaceMatchesImplementsRule(superClass, rule);
+    }
+
+    private boolean anySourceMatchesInheritanceRuleDirectly(
+        DexClass clazz, ProguardConfigurationRule rule, boolean isInterface) {
+      // TODO(b/110141157): Figure out what to do with annotations. Should the annotations of
+      // the DexClass corresponding to `sourceType` satisfy the `annotation`-matcher?
+      return appView.verticallyMergedClasses() != null
+          && appView.verticallyMergedClasses().getSourcesFor(clazz.type).stream()
+              .filter(
+                  sourceType ->
+                      appView.definitionFor(sourceType).accessFlags.isInterface() == isInterface)
+              .anyMatch(rule.getInheritanceClassName()::matches);
+    }
+
+    private boolean allRulesSatisfied(
+        Collection<ProguardMemberRule> memberKeepRules, DexClass clazz) {
+      for (ProguardMemberRule rule : memberKeepRules) {
+        if (!ruleSatisfied(rule, clazz)) {
+          return false;
+        }
+      }
+      return true;
+    }
+
+    /**
+     * Checks whether the given rule is satisfied by this clazz, not taking superclasses into
+     * account.
+     */
+    private boolean ruleSatisfied(ProguardMemberRule rule, DexClass clazz) {
+      return ruleSatisfiedByMethods(rule, clazz.directMethods())
+          || ruleSatisfiedByMethods(rule, clazz.virtualMethods())
+          || ruleSatisfiedByFields(rule, clazz.staticFields())
+          || ruleSatisfiedByFields(rule, clazz.instanceFields());
+    }
+
+    boolean ruleSatisfiedByMethods(ProguardMemberRule rule, Iterable<DexEncodedMethod> methods) {
+      if (rule.getRuleType().includesMethods()) {
+        for (DexEncodedMethod method : methods) {
+          if (rule.matches(method, appView, this::handleMatchedAnnotation, dexStringCache)) {
+            return true;
+          }
+        }
+      }
+      return false;
+    }
+
+    boolean ruleSatisfiedByFields(ProguardMemberRule rule, Iterable<DexEncodedField> fields) {
+      if (rule.getRuleType().includesFields()) {
+        for (DexEncodedField field : fields) {
+          if (rule.matches(field, appView, this::handleMatchedAnnotation, dexStringCache)) {
+            return true;
+          }
+        }
+      }
+      return false;
+    }
+
+    static AnnotationMatchResult containsAllAnnotations(
+        List<ProguardTypeMatcher> annotationMatchers, DexClass clazz) {
+      return containsAllAnnotations(annotationMatchers, clazz.annotations());
+    }
+
+    static <D extends DexEncodedMember<D, R>, R extends DexMember<D, R>>
+        boolean containsAllAnnotations(
+            List<ProguardTypeMatcher> annotationMatchers,
+            DexEncodedMember<D, R> member,
+            Consumer<AnnotationMatchResult> matchedAnnotationsConsumer) {
+      AnnotationMatchResult annotationMatchResult =
+          containsAllAnnotations(annotationMatchers, member.annotations());
+      if (annotationMatchResult != null) {
+        matchedAnnotationsConsumer.accept(annotationMatchResult);
+        return true;
+      }
+      if (member.isDexEncodedMethod()) {
+        DexEncodedMethod method = member.asDexEncodedMethod();
+        for (int i = 0; i < method.parameterAnnotationsList.size(); i++) {
+          annotationMatchResult =
+              containsAllAnnotations(annotationMatchers, method.parameterAnnotationsList.get(i));
+          if (annotationMatchResult != null) {
+            matchedAnnotationsConsumer.accept(annotationMatchResult);
+            return true;
+          }
+        }
+      }
+      return false;
+    }
+
+    private static AnnotationMatchResult containsAllAnnotations(
+        List<ProguardTypeMatcher> annotationMatchers, DexAnnotationSet annotations) {
+      if (annotationMatchers.isEmpty()) {
+        return AnnotationsIgnoredMatchResult.getInstance();
+      }
+      List<DexAnnotation> matchedAnnotations = new ArrayList<>();
+      for (ProguardTypeMatcher annotationMatcher : annotationMatchers) {
+        DexAnnotation matchedAnnotation =
+            getFirstAnnotationThatMatches(annotationMatcher, annotations);
+        if (matchedAnnotation == null) {
+          return null;
+        }
+        matchedAnnotations.add(matchedAnnotation);
+      }
+      return new ConcreteAnnotationMatchResult(matchedAnnotations);
+    }
+
+    private static DexAnnotation getFirstAnnotationThatMatches(
+        ProguardTypeMatcher annotationMatcher, DexAnnotationSet annotations) {
+      for (DexAnnotation annotation : annotations.annotations) {
+        if (annotationMatcher.matches(annotation.getAnnotationType())) {
+          return annotation;
+        }
+      }
+      return null;
+    }
+
+    private void markMethod(
+        DexEncodedMethod method,
+        Collection<ProguardMemberRule> rules,
+        Set<Wrapper<DexMethod>> methodsMarked,
+        ProguardConfigurationRule context,
+        DexDefinition precondition,
+        ProguardIfRule ifRule) {
+      if (methodsMarked != null
+          && methodsMarked.contains(MethodSignatureEquivalence.get().wrap(method.method))) {
+        // Ignore, method is overridden in sub class.
+        return;
+      }
+      for (ProguardMemberRule rule : rules) {
+        if (rule.matches(method, appView, this::handleMatchedAnnotation, dexStringCache)) {
+          if (Log.ENABLED) {
+            Log.verbose(
+                getClass(), "Marking method `%s` due to `%s { %s }`.", method, context, rule);
+          }
+          if (methodsMarked != null) {
+            methodsMarked.add(MethodSignatureEquivalence.get().wrap(method.method));
+          }
+          addItemToSets(method, context, rule, precondition, ifRule);
+        }
+      }
+    }
+
+    private void markField(
+        DexEncodedField field,
+        Collection<ProguardMemberRule> rules,
+        ProguardConfigurationRule context,
+        DexDefinition precondition,
+        ProguardIfRule ifRule) {
+      for (ProguardMemberRule rule : rules) {
+        if (rule.matches(field, appView, this::handleMatchedAnnotation, dexStringCache)) {
+          if (Log.ENABLED) {
+            Log.verbose(getClass(), "Marking field `%s` due to `%s { %s }`.", field, context, rule);
+          }
+          addItemToSets(field, context, rule, precondition, ifRule);
+        }
+      }
+    }
+
+    private void markClass(DexClass clazz, ProguardConfigurationRule rule, ProguardIfRule ifRule) {
+      if (Log.ENABLED) {
+        Log.verbose(getClass(), "Marking class `%s` due to `%s`.", clazz.type, rule);
+      }
+      addItemToSets(clazz, rule, null, null, ifRule);
+    }
+
+    private void includeDescriptor(DexDefinition item, DexType type, ProguardKeepRuleBase context) {
+      if (type.isVoidType()) {
+        return;
+      }
+      if (type.isArrayType()) {
+        type = type.toBaseType(appView.dexItemFactory());
+      }
+      if (type.isPrimitiveType()) {
+        return;
+      }
+      DexClass definition = appView.definitionFor(type);
+      if (definition == null || definition.isNotProgramClass()) {
+        return;
+      }
+      // Keep the type if the item is also kept.
+      dependentNoShrinking
+          .computeIfAbsent(item.getReference(), x -> new MutableItemsWithRules())
+          .addClassWithRule(type, context);
+      // Unconditionally add to no-obfuscation, as that is only checked for surviving items.
+      if (appView.options().isMinificationEnabled()) {
+        noObfuscation.add(type);
+      }
+    }
+
+    private void includeDescriptorClasses(DexDefinition item, ProguardKeepRuleBase context) {
+      if (item.isDexEncodedMethod()) {
+        DexMethod method = item.asDexEncodedMethod().method;
+        includeDescriptor(item, method.proto.returnType, context);
+        for (DexType value : method.proto.parameters.values) {
+          includeDescriptor(item, value, context);
+        }
+      } else if (item.isDexEncodedField()) {
+        DexField field = item.asDexEncodedField().field;
+        includeDescriptor(item, field.type, context);
+      } else {
+        assert item.isDexClass();
+      }
+    }
+
+    private synchronized void addItemToSets(
+        DexDefinition item,
+        ProguardConfigurationRule context,
+        ProguardMemberRule rule,
+        DexDefinition precondition,
+        ProguardIfRule ifRule) {
+      if (context instanceof ProguardKeepRule) {
+        if (item.isDexEncodedField()) {
+          DexEncodedField encodedField = item.asDexEncodedField();
+          if (encodedField.getOptimizationInfo().cannotBeKept()) {
+            // We should only ever get here with if rules.
+            assert ifRule != null;
+            return;
+          }
+        } else if (item.isDexEncodedMethod()) {
+          DexEncodedMethod encodedMethod = item.asDexEncodedMethod();
+          if (encodedMethod.isClassInitializer() && !options.debug) {
+            // Don't keep class initializers.
+            return;
+          }
+          if (encodedMethod.getOptimizationInfo().cannotBeKept()) {
+            // We should only ever get here with if rules.
+            assert ifRule != null;
+            return;
+          }
+          if (options.isGeneratingDex()
+              && encodedMethod.method.isLambdaDeserializeMethod(appView.dexItemFactory())) {
+            // Don't keep lambda deserialization methods.
+            return;
+          }
+          // If desugaring is enabled, private and static interface methods will be moved to a
+          // companion class. So we don't need to add them to the root set in the beginning.
+          if (options.isInterfaceMethodDesugaringEnabled()
+              && encodedMethod.hasCode()
+              && (encodedMethod.isPrivateMethod() || encodedMethod.isStaticMember())) {
+            DexClass holder = appView.definitionFor(encodedMethod.getHolderType());
+            if (holder != null && holder.isInterface()) {
+              if (rule.isSpecific()) {
+                options.reporter.warning(
+                    new StringDiagnostic(
+                        "The rule `"
+                            + rule
+                            + "` is ignored because the targeting interface method `"
+                            + encodedMethod.method.toSourceString()
+                            + "` will be desugared."));
+              }
+              return;
+            }
+          }
+        }
+
+        // The reason for keeping should link to the conditional rule as a whole, if present.
+        ProguardKeepRuleBase keepRule = ifRule != null ? ifRule : (ProguardKeepRuleBase) context;
+        // The modifiers are specified on the actual keep rule (ie, the consequent/context).
+        ProguardKeepRuleModifiers modifiers = ((ProguardKeepRule) context).getModifiers();
+        // In compatibility mode, for a match on instance members a referenced class becomes live.
+        if (options.forceProguardCompatibility
+            && !modifiers.allowsShrinking
+            && precondition != null
+            && precondition.isDexClass()) {
+          if (!item.isDexClass() && !item.isStaticMember()) {
+            dependentKeepClassCompatRule
+                .computeIfAbsent(precondition.asDexClass().getType(), i -> new HashSet<>())
+                .add(keepRule);
+            context.markAsUsed();
+          }
+        }
+        if (!modifiers.allowsShrinking) {
+          if (precondition != null) {
+            dependentNoShrinking
+                .computeIfAbsent(precondition.getReference(), x -> new MutableItemsWithRules())
+                .addReferenceWithRule(item.getReference(), keepRule);
+          } else {
+            noShrinking.addReferenceWithRule(item.getReference(), keepRule);
+          }
+          context.markAsUsed();
+        } else if (!modifiers.allowsOptimization) {
+          if (precondition != null) {
+            dependentSoftPinned
+                .computeIfAbsent(precondition.getReference(), x -> new MutableItemsWithRules())
+                .addReferenceWithRule(item.getReference(), keepRule);
+          } else {
+            softPinned.addReferenceWithRule(item.getReference(), keepRule);
+          }
+        }
+        if (!modifiers.allowsOptimization) {
+          // The -dontoptimize flag has only effect through the keep all rule, but we still
+          // need to mark the rule as used.
+          context.markAsUsed();
+        }
+
+        if (appView.options().isMinificationEnabled() && !modifiers.allowsObfuscation) {
+          noObfuscation.add(item.getReference());
+          context.markAsUsed();
+        }
+        if (modifiers.includeDescriptorClasses) {
+          includeDescriptorClasses(item, keepRule);
+          context.markAsUsed();
+        }
+      } else if (context instanceof ProguardAssumeMayHaveSideEffectsRule) {
+        mayHaveSideEffects.put(item.getReference(), rule);
+        context.markAsUsed();
+      } else if (context instanceof ProguardAssumeNoSideEffectRule) {
+        if (item.isDexEncodedMember()) {
+          DexEncodedMember<?, ?> member = item.asDexEncodedMember();
+          if (member.getHolderType() == appView.dexItemFactory().objectType) {
+            assert member.isDexEncodedMethod();
+            reportAssumeNoSideEffectsWarningForJavaLangClassMethod(
+                member.asDexEncodedMethod(), (ProguardAssumeNoSideEffectRule) context);
+          } else {
+            noSideEffects.put(member.getReference(), rule);
+            if (member.isDexEncodedMethod()) {
+              DexEncodedMethod method = member.asDexEncodedMethod();
+              if (method.isClassInitializer()) {
+                feedback.classInitializerMayBePostponed(method);
+              }
+            }
+          }
+          context.markAsUsed();
+        }
+      } else if (context instanceof ProguardWhyAreYouKeepingRule) {
+        reasonAsked.computeIfAbsent(item.getReference(), i -> i);
+        context.markAsUsed();
+      } else if (context instanceof ProguardAssumeValuesRule) {
+        if (item.isDexEncodedMember()) {
+          assumedValues.put(item.asDexEncodedMember().getReference(), rule);
+          context.markAsUsed();
+        }
+      } else if (context instanceof ProguardCheckDiscardRule) {
+        checkDiscarded.computeIfAbsent(item.getReference(), i -> i);
+        context.markAsUsed();
+      } else if (context instanceof InlineRule) {
+        if (item.isDexEncodedMethod()) {
+          DexMethod reference = item.asDexEncodedMethod().getReference();
+          switch (((InlineRule) context).getType()) {
+            case ALWAYS:
+              alwaysInline.add(reference);
+              break;
+            case FORCE:
+              forceInline.add(reference);
+              break;
+            case NEVER:
+              neverInline.add(reference);
+              break;
+            case NEVER_SINGLE_CALLER:
+              neverInlineDueToSingleCaller.add(reference);
+              break;
+            default:
+              throw new Unreachable();
+          }
+          context.markAsUsed();
+        }
+      } else if (context instanceof WhyAreYouNotInliningRule) {
+        if (!item.isDexEncodedMethod()) {
+          throw new Unreachable();
+        }
+        whyAreYouNotInlining.add(item.asDexEncodedMethod().method);
+        context.markAsUsed();
+      } else if (context.isClassInlineRule()) {
+        ClassInlineRule classInlineRule = context.asClassInlineRule();
+        DexClass clazz = item.asDexClass();
+        if (clazz == null) {
+          throw new IllegalStateException(
+              "Unexpected -"
+                  + classInlineRule.typeString()
+                  + " rule for a non-class type: `"
+                  + item.getReference().toSourceString()
+                  + "`");
+        }
+        switch (classInlineRule.getType()) {
+          case ALWAYS:
+            alwaysClassInline.addElement(item.asDexClass().type);
+            break;
+          case NEVER:
+            neverClassInline.add(item.asDexClass().type);
+            break;
+          default:
+            throw new Unreachable();
+        }
+        context.markAsUsed();
+      } else if (context instanceof NoUnusedInterfaceRemovalRule) {
+        noUnusedInterfaceRemoval.add(item.asDexClass().type);
+        context.markAsUsed();
+      } else if (context instanceof NoVerticalClassMergingRule) {
+        noVerticalClassMerging.add(item.asDexClass().type);
+        context.markAsUsed();
+      } else if (context instanceof NoHorizontalClassMergingRule) {
+        noHorizontalClassMerging.add(item.asDexClass().type);
+        context.markAsUsed();
+      } else if (context instanceof MemberValuePropagationRule) {
+        switch (((MemberValuePropagationRule) context).getType()) {
+          case NEVER:
+            // Only add members from propgram classes to `neverPropagateValue` since class member
+            // values from library types are not propagated by default.
+            if (item.isDexEncodedField()) {
+              DexEncodedField field = item.asDexEncodedField();
+              if (field.isProgramField(appView)) {
+                neverPropagateValue.add(item.asDexEncodedField().field);
+                context.markAsUsed();
+              }
+            } else if (item.isDexEncodedMethod()) {
+              DexEncodedMethod method = item.asDexEncodedMethod();
+              if (method.isProgramMethod(appView)) {
+                neverPropagateValue.add(item.asDexEncodedMethod().method);
+                context.markAsUsed();
+              }
+            }
+            break;
+          default:
+            throw new Unreachable();
+        }
+      } else if (context instanceof ProguardIdentifierNameStringRule) {
+        if (item.isDexEncodedField()) {
+          identifierNameStrings.add(item.asDexEncodedField().field);
+          context.markAsUsed();
+        } else if (item.isDexEncodedMethod()) {
+          identifierNameStrings.add(item.asDexEncodedMethod().method);
+          context.markAsUsed();
+        }
+      } else if (context instanceof ConstantArgumentRule) {
+        if (item.isDexEncodedMethod()) {
+          keepParametersWithConstantValue.add(item.asDexEncodedMethod().method);
+          context.markAsUsed();
+        }
+      } else if (context instanceof ReprocessClassInitializerRule) {
+        DexProgramClass clazz = item.asProgramClass();
+        if (clazz != null && clazz.hasClassInitializer()) {
+          switch (context.asReprocessClassInitializerRule().getType()) {
+            case ALWAYS:
+              reprocess.add(clazz.getClassInitializer().method);
+              break;
+            case NEVER:
+              neverReprocess.add(clazz.getClassInitializer().method);
+              break;
+            default:
+              throw new Unreachable();
+          }
+          context.markAsUsed();
+        }
+      } else if (context.isReprocessMethodRule()) {
+        if (item.isDexEncodedMethod()) {
+          DexEncodedMethod method = item.asDexEncodedMethod();
+          switch (context.asReprocessMethodRule().getType()) {
+            case ALWAYS:
+              reprocess.add(method.method);
+              break;
+            case NEVER:
+              neverReprocess.add(method.method);
+              break;
+            default:
+              throw new Unreachable();
+          }
+          context.markAsUsed();
+        }
+      } else if (context instanceof UnusedArgumentRule) {
+        if (item.isDexEncodedMethod()) {
+          keepUnusedArguments.add(item.asDexEncodedMethod().method);
+          context.markAsUsed();
+        }
+      } else {
+        throw new Unreachable();
+      }
+    }
+
+    private void reportAssumeNoSideEffectsWarningForJavaLangClassMethod(
+        DexEncodedMethod method, ProguardAssumeNoSideEffectRule context) {
+      assert method.getHolderType() == options.dexItemFactory().objectType;
+      OriginWithPosition key = new OriginWithPosition(context.getOrigin(), context.getPosition());
+      assumeNoSideEffectsWarnings
+          .computeIfAbsent(key, ignore -> new TreeSet<>(DexMethod::compareTo))
+          .add(method.getReference());
+    }
+
+    private boolean isWaitOrNotifyMethod(DexMethod method) {
+      return method.name == options.itemFactory.waitMethodName
+          || method.name == options.itemFactory.notifyMethodName
+          || method.name == options.itemFactory.notifyAllMethodName;
+    }
+
+    private void generateAssumeNoSideEffectsWarnings() {
+      if (appView.getDontWarnConfiguration().matches(options.itemFactory.objectType)) {
+        // Don't report any warnings since we don't apply -assumenosideeffects rules to notify() or
+        // wait() anyway.
+        return;
+      }
+
+      assumeNoSideEffectsWarnings.forEach(
+          (originWithPosition, methods) -> {
+            boolean matchesWaitOrNotifyMethods =
+                methods.stream().anyMatch(this::isWaitOrNotifyMethod);
+            if (!matchesWaitOrNotifyMethods) {
+              // We model the remaining methods on java.lang.Object, and thus there should be no
+              // need
+              // to warn in this case.
+              return;
+            }
+            options.reporter.warning(
+                new AssumeNoSideEffectsRuleForObjectMembersDiagnostic.Builder()
+                    .addMatchedMethods(methods)
+                    .setOrigin(originWithPosition.getOrigin())
+                    .setPosition(originWithPosition.getPosition())
+                    .build());
+          });
+    }
+  }
+
+  abstract static class RootSetBase {
+
+    final Set<DexMethod> neverInline;
+    final Set<DexMethod> neverInlineDueToSingleCaller;
+    final Set<DexType> neverClassInline;
+    final MutableItemsWithRules noShrinking;
+    final MutableItemsWithRules softPinned;
+    final Set<DexReference> noObfuscation;
+    final Map<DexReference, MutableItemsWithRules> dependentNoShrinking;
+    final Map<DexReference, MutableItemsWithRules> dependentSoftPinned;
+    final Map<DexType, Set<ProguardKeepRuleBase>> dependentKeepClassCompatRule;
+    final List<DelayedRootSetActionItem> delayedRootSetActionItems;
+
+    RootSetBase(
+        Set<DexMethod> neverInline,
+        Set<DexMethod> neverInlineDueToSingleCaller,
+        Set<DexType> neverClassInline,
+        MutableItemsWithRules noShrinking,
+        MutableItemsWithRules softPinned,
+        Set<DexReference> noObfuscation,
+        Map<DexReference, MutableItemsWithRules> dependentNoShrinking,
+        Map<DexReference, MutableItemsWithRules> dependentSoftPinned,
+        Map<DexType, Set<ProguardKeepRuleBase>> dependentKeepClassCompatRule,
+        List<DelayedRootSetActionItem> delayedRootSetActionItems) {
+      this.neverInline = neverInline;
+      this.neverInlineDueToSingleCaller = neverInlineDueToSingleCaller;
+      this.neverClassInline = neverClassInline;
+      this.noShrinking = noShrinking;
+      this.softPinned = softPinned;
+      this.noObfuscation = noObfuscation;
+      this.dependentNoShrinking = dependentNoShrinking;
+      this.dependentSoftPinned = dependentSoftPinned;
+      this.dependentKeepClassCompatRule = dependentKeepClassCompatRule;
+      this.delayedRootSetActionItems = delayedRootSetActionItems;
+    }
+
+    public void forEachClassWithDependentItems(
+        DexDefinitionSupplier definitions, Consumer<DexProgramClass> consumer) {
+      for (DexReference reference : dependentNoShrinking.keySet()) {
+        if (reference.isDexType()) {
+          DexType type = reference.asDexType();
+          DexProgramClass clazz = asProgramClassOrNull(definitions.definitionFor(type));
+          if (clazz != null) {
+            consumer.accept(clazz);
+          }
+        }
+      }
+    }
+
+    public void forEachMemberWithDependentItems(
+        DexDefinitionSupplier definitions,
+        BiConsumer<DexEncodedMember<?, ?>, ItemsWithRules> consumer) {
+      dependentNoShrinking.forEach(
+          (reference, dependentItems) -> {
+            if (reference.isDexMember()) {
+              DexMember<?, ?> member = reference.asDexMember();
+              DexProgramClass holder =
+                  asProgramClassOrNull(definitions.definitionForHolder(member));
+              if (holder != null) {
+                DexEncodedMember<?, ?> definition = holder.lookupMember(member);
+                if (definition != null) {
+                  consumer.accept(definition, dependentItems);
+                }
+              }
+            }
+          });
+    }
+
+    public void forEachDependentInstanceConstructor(
+        DexProgramClass clazz,
+        AppView<?> appView,
+        BiConsumer<ProgramMethod, Set<ProguardKeepRuleBase>> fn) {
+      getDependentItems(clazz)
+          .forEachMethod(
+              (reference, reasons) -> {
+                DexProgramClass holder =
+                    asProgramClassOrNull(appView.definitionForHolder(reference));
+                if (holder != null) {
+                  ProgramMethod method = holder.lookupProgramMethod(reference);
+                  if (method != null && method.getDefinition().isInstanceInitializer()) {
+                    fn.accept(method, reasons);
+                  }
+                }
+              });
+    }
+
+    public void forEachDependentMember(
+        DexDefinition item,
+        AppView<?> appView,
+        Consumer3<DexDefinition, ProgramMember<?, ?>, Set<ProguardKeepRuleBase>> fn) {
+      getDependentItems(item)
+          .forEachMember(
+              (reference, reasons) -> {
+                DexProgramClass holder =
+                    asProgramClassOrNull(appView.definitionForHolder(reference));
+                if (holder != null) {
+                  ProgramMember<?, ?> member = holder.lookupProgramMember(reference);
+                  if (member != null) {
+                    fn.accept(item, member, reasons);
+                  }
+                }
+              });
+    }
+
+    public void forEachDependentNonStaticMember(
+        DexDefinition item,
+        AppView<?> appView,
+        Consumer3<DexDefinition, ProgramMember<?, ?>, Set<ProguardKeepRuleBase>> fn) {
+      forEachDependentMember(
+          item,
+          appView,
+          (precondition, member, reasons) -> {
+            if (!member.getDefinition().isStatic()) {
+              fn.accept(precondition, member, reasons);
+            }
+          });
+    }
+
+    public void forEachDependentStaticMember(
+        DexDefinition item,
+        AppView<?> appView,
+        Consumer3<DexDefinition, ProgramMember<?, ?>, Set<ProguardKeepRuleBase>> fn) {
+      forEachDependentMember(
+          item,
+          appView,
+          (precondition, member, reasons) -> {
+            if (member.getDefinition().isStatic()) {
+              fn.accept(precondition, member, reasons);
+            }
+          });
+    }
+
+    ItemsWithRules getDependentItems(DexDefinition item) {
+      ItemsWithRules found = dependentNoShrinking.get(item.getReference());
+      return found != null ? found : ItemsWithRules.empty();
+    }
+
+    Set<ProguardKeepRuleBase> getDependentKeepClassCompatRule(DexType type) {
+      return dependentKeepClassCompatRule.get(type);
+    }
+  }
+
+  abstract static class ItemsWithRules {
+
+    public static ItemsWithRules empty() {
+      return MutableItemsWithRules.EMPTY;
+    }
+
+    public abstract boolean containsClass(DexType type);
+
+    public abstract boolean containsField(DexField field);
+
+    public abstract boolean containsMethod(DexMethod method);
+
+    public final boolean containsReference(DexReference reference) {
+      return reference.apply(this::containsClass, this::containsField, this::containsMethod);
+    }
+
+    public abstract void forEachClass(Consumer<? super DexType> consumer);
+
+    public abstract void forEachClass(
+        BiConsumer<? super DexType, Set<ProguardKeepRuleBase>> consumer);
+
+    public abstract void forEachField(Consumer<? super DexField> consumer);
+
+    public abstract void forEachField(
+        BiConsumer<? super DexField, Set<ProguardKeepRuleBase>> consumer);
+
+    public abstract void forEachMember(Consumer<? super DexMember<?, ?>> consumer);
+
+    public abstract void forEachMember(
+        BiConsumer<? super DexMember<?, ?>, Set<ProguardKeepRuleBase>> consumer);
+
+    public abstract void forEachMethod(Consumer<? super DexMethod> consumer);
+
+    public abstract void forEachMethod(
+        BiConsumer<? super DexMethod, Set<ProguardKeepRuleBase>> consumer);
+
+    public abstract Set<ProguardKeepRuleBase> getRulesForClass(DexType type);
+
+    public abstract Set<ProguardKeepRuleBase> getRulesForField(DexField field);
+
+    public abstract Set<ProguardKeepRuleBase> getRulesForMethod(DexMethod method);
+
+    public final Set<ProguardKeepRuleBase> getRulesForReference(DexReference reference) {
+      return reference.apply(
+          this::getRulesForClass, this::getRulesForField, this::getRulesForMethod);
+    }
+  }
+
+  static class MutableItemsWithRules extends ItemsWithRules {
+
+    private static final ItemsWithRules EMPTY =
+        new MutableItemsWithRules(
+            Collections.emptyMap(), Collections.emptyMap(), Collections.emptyMap());
+
+    final Map<DexType, Set<ProguardKeepRuleBase>> classesWithRules;
+    final Map<DexField, Set<ProguardKeepRuleBase>> fieldsWithRules;
+    final Map<DexMethod, Set<ProguardKeepRuleBase>> methodsWithRules;
+
+    MutableItemsWithRules() {
+      this(new IdentityHashMap<>(), new IdentityHashMap<>(), new IdentityHashMap<>());
+    }
+
+    private MutableItemsWithRules(
+        Map<DexType, Set<ProguardKeepRuleBase>> classesWithRules,
+        Map<DexField, Set<ProguardKeepRuleBase>> fieldsWithRules,
+        Map<DexMethod, Set<ProguardKeepRuleBase>> methodsWithRules) {
+      this.classesWithRules = classesWithRules;
+      this.fieldsWithRules = fieldsWithRules;
+      this.methodsWithRules = methodsWithRules;
+    }
+
+    public void addAll(ItemsWithRules items) {
+      items.forEachClass(this::addClassWithRules);
+      items.forEachField(this::addFieldWithRules);
+      items.forEachMethod(this::addMethodWithRules);
+    }
+
+    public void addClassWithRule(DexType type, ProguardKeepRuleBase rule) {
+      classesWithRules.computeIfAbsent(type, ignore -> new HashSet<>()).add(rule);
+    }
+
+    public void addClassWithRules(DexType type, Set<ProguardKeepRuleBase> rules) {
+      classesWithRules.computeIfAbsent(type, ignore -> new HashSet<>()).addAll(rules);
+    }
+
+    public void addFieldWithRule(DexField field, ProguardKeepRuleBase rule) {
+      fieldsWithRules.computeIfAbsent(field, ignore -> new HashSet<>()).add(rule);
+    }
+
+    public void addFieldWithRules(DexField field, Set<ProguardKeepRuleBase> rules) {
+      fieldsWithRules.computeIfAbsent(field, ignore -> new HashSet<>()).addAll(rules);
+    }
+
+    public void addMethodWithRule(DexMethod method, ProguardKeepRuleBase rule) {
+      methodsWithRules.computeIfAbsent(method, ignore -> new HashSet<>()).add(rule);
+    }
+
+    public void addMethodWithRules(DexMethod method, Set<ProguardKeepRuleBase> rules) {
+      methodsWithRules.computeIfAbsent(method, ignore -> new HashSet<>()).addAll(rules);
+    }
+
+    public void addReferenceWithRule(DexReference reference, ProguardKeepRuleBase rule) {
+      reference.accept(
+          this::addClassWithRule, this::addFieldWithRule, this::addMethodWithRule, rule);
+    }
+
+    public void addReferenceWithRules(DexReference reference, Set<ProguardKeepRuleBase> rules) {
+      reference.accept(
+          this::addClassWithRules, this::addFieldWithRules, this::addMethodWithRules, rules);
+    }
+
+    @Override
+    public boolean containsClass(DexType type) {
+      return classesWithRules.containsKey(type);
+    }
+
+    @Override
+    public boolean containsField(DexField field) {
+      return fieldsWithRules.containsKey(field);
+    }
+
+    @Override
+    public boolean containsMethod(DexMethod method) {
+      return methodsWithRules.containsKey(method);
+    }
+
+    public void forEachReference(Consumer<DexReference> consumer) {
+      forEachClass(consumer);
+      forEachMember(consumer);
+    }
+
+    @Override
+    public void forEachClass(Consumer<? super DexType> consumer) {
+      classesWithRules.keySet().forEach(consumer);
+    }
+
+    @Override
+    public void forEachClass(BiConsumer<? super DexType, Set<ProguardKeepRuleBase>> consumer) {
+      classesWithRules.forEach(consumer);
+    }
+
+    @Override
+    public void forEachField(Consumer<? super DexField> consumer) {
+      fieldsWithRules.keySet().forEach(consumer);
+    }
+
+    @Override
+    public void forEachField(BiConsumer<? super DexField, Set<ProguardKeepRuleBase>> consumer) {
+      fieldsWithRules.forEach(consumer);
+    }
+
+    @Override
+    public void forEachMember(Consumer<? super DexMember<?, ?>> consumer) {
+      forEachField(consumer);
+      forEachMethod(consumer);
+    }
+
+    @Override
+    public void forEachMember(
+        BiConsumer<? super DexMember<?, ?>, Set<ProguardKeepRuleBase>> consumer) {
+      forEachField(consumer);
+      forEachMethod(consumer);
+    }
+
+    @Override
+    public void forEachMethod(Consumer<? super DexMethod> consumer) {
+      methodsWithRules.keySet().forEach(consumer);
+    }
+
+    @Override
+    public void forEachMethod(BiConsumer<? super DexMethod, Set<ProguardKeepRuleBase>> consumer) {
+      methodsWithRules.forEach(consumer);
+    }
+
+    @Override
+    public Set<ProguardKeepRuleBase> getRulesForClass(DexType type) {
+      return classesWithRules.get(type);
+    }
+
+    @Override
+    public Set<ProguardKeepRuleBase> getRulesForField(DexField field) {
+      return fieldsWithRules.get(field);
+    }
+
+    @Override
+    public Set<ProguardKeepRuleBase> getRulesForMethod(DexMethod method) {
+      return methodsWithRules.get(method);
+    }
+
+    public void removeClass(DexType type) {
+      classesWithRules.remove(type);
+    }
+
+    public void removeField(DexField field) {
+      fieldsWithRules.remove(field);
+    }
+
+    public void removeMethod(DexMethod method) {
+      methodsWithRules.remove(method);
+    }
+
+    public void removeReference(DexReference reference) {
+      reference.accept(this::removeClass, this::removeField, this::removeMethod);
+    }
+
+    public void putAll(ItemsWithRules items) {
+      items.forEachClass(this::putClassWithRules);
+      items.forEachField(this::putFieldWithRules);
+      items.forEachMethod(this::putMethodWithRules);
+    }
+
+    public void putClassWithRules(DexType type, Set<ProguardKeepRuleBase> rules) {
+      classesWithRules.put(type, rules);
+    }
+
+    public void putFieldWithRules(DexField field, Set<ProguardKeepRuleBase> rules) {
+      fieldsWithRules.put(field, rules);
+    }
+
+    public void putMethodWithRules(DexMethod method, Set<ProguardKeepRuleBase> rules) {
+      methodsWithRules.put(method, rules);
+    }
+
+    public void putReferenceWithRules(DexReference reference, Set<ProguardKeepRuleBase> rules) {
+      reference.accept(
+          this::putClassWithRules, this::putFieldWithRules, this::putMethodWithRules, rules);
+    }
+
+    public int size() {
+      return classesWithRules.size() + fieldsWithRules.size() + methodsWithRules.size();
+    }
+  }
+
+  public static class RootSet extends RootSetBase {
+
+    public final ImmutableList<DexReference> reasonAsked;
+    public final ImmutableList<DexReference> checkDiscarded;
+    public final Set<DexMethod> alwaysInline;
+    public final Set<DexMethod> forceInline;
+    public final Set<DexMethod> bypassClinitForInlining;
+    public final Set<DexMethod> whyAreYouNotInlining;
+    public final Set<DexMethod> keepConstantArguments;
+    public final Set<DexMethod> keepUnusedArguments;
+    public final Set<DexMethod> reprocess;
+    public final Set<DexMethod> neverReprocess;
+    public final PredicateSet<DexType> alwaysClassInline;
+    public final Set<DexType> noUnusedInterfaceRemoval;
+    public final Set<DexType> noVerticalClassMerging;
+    public final Set<DexType> noHorizontalClassMerging;
+    public final Set<DexReference> 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<ProguardIfRule> ifRules;
+
+    private RootSet(
+        MutableItemsWithRules noShrinking,
+        MutableItemsWithRules softPinned,
+        Set<DexReference> noObfuscation,
+        ImmutableList<DexReference> reasonAsked,
+        ImmutableList<DexReference> checkDiscarded,
+        Set<DexMethod> alwaysInline,
+        Set<DexMethod> forceInline,
+        Set<DexMethod> neverInline,
+        Set<DexMethod> neverInlineDueToSingleCaller,
+        Set<DexMethod> bypassClinitForInlining,
+        Set<DexMethod> whyAreYouNotInlining,
+        Set<DexMethod> keepConstantArguments,
+        Set<DexMethod> keepUnusedArguments,
+        Set<DexMethod> reprocess,
+        Set<DexMethod> neverReprocess,
+        PredicateSet<DexType> alwaysClassInline,
+        Set<DexType> neverClassInline,
+        Set<DexType> noUnusedInterfaceRemoval,
+        Set<DexType> noVerticalClassMerging,
+        Set<DexType> noHorizontalClassMerging,
+        Set<DexReference> neverPropagateValue,
+        Map<DexReference, ProguardMemberRule> mayHaveSideEffects,
+        Map<DexMember<?, ?>, ProguardMemberRule> noSideEffects,
+        Map<DexMember<?, ?>, ProguardMemberRule> assumedValues,
+        Map<DexReference, MutableItemsWithRules> dependentNoShrinking,
+        Map<DexReference, MutableItemsWithRules> dependentSoftPinned,
+        Map<DexType, Set<ProguardKeepRuleBase>> dependentKeepClassCompatRule,
+        Set<DexReference> identifierNameStrings,
+        Set<ProguardIfRule> ifRules,
+        List<DelayedRootSetActionItem> delayedRootSetActionItems) {
+      super(
+          neverInline,
+          neverInlineDueToSingleCaller,
+          neverClassInline,
+          noShrinking,
+          softPinned,
+          noObfuscation,
+          dependentNoShrinking,
+          dependentSoftPinned,
+          dependentKeepClassCompatRule,
+          delayedRootSetActionItems);
+      this.reasonAsked = reasonAsked;
+      this.checkDiscarded = checkDiscarded;
+      this.alwaysInline = alwaysInline;
+      this.forceInline = forceInline;
+      this.bypassClinitForInlining = bypassClinitForInlining;
+      this.whyAreYouNotInlining = whyAreYouNotInlining;
+      this.keepConstantArguments = keepConstantArguments;
+      this.keepUnusedArguments = keepUnusedArguments;
+      this.reprocess = reprocess;
+      this.neverReprocess = neverReprocess;
+      this.alwaysClassInline = alwaysClassInline;
+      this.noUnusedInterfaceRemoval = noUnusedInterfaceRemoval;
+      this.noVerticalClassMerging = noVerticalClassMerging;
+      this.noHorizontalClassMerging = noHorizontalClassMerging;
+      this.neverPropagateValue = neverPropagateValue;
+      this.mayHaveSideEffects = mayHaveSideEffects;
+      this.noSideEffects = noSideEffects;
+      this.assumedValues = assumedValues;
+      this.identifierNameStrings = Collections.unmodifiableSet(identifierNameStrings);
+      this.ifRules = Collections.unmodifiableSet(ifRules);
+    }
+
+    public void checkAllRulesAreUsed(InternalOptions options) {
+      List<ProguardConfigurationRule> rules = options.getProguardConfiguration().getRules();
+      if (rules != null) {
+        for (ProguardConfigurationRule rule : rules) {
+          if (!rule.isUsed()) {
+            String message =
+                "Proguard configuration rule does not match anything: `" + rule.toString() + "`";
+            StringDiagnostic diagnostic = new StringDiagnostic(message, rule.getOrigin());
+            if (options.testing.reportUnusedProguardConfigurationRules) {
+              options.reporter.info(diagnostic);
+            }
+          }
+        }
+      }
+    }
+
+    void addConsequentRootSet(ConsequentRootSet consequentRootSet, boolean addNoShrinking) {
+      neverInline.addAll(consequentRootSet.neverInline);
+      neverInlineDueToSingleCaller.addAll(consequentRootSet.neverInlineDueToSingleCaller);
+      neverClassInline.addAll(consequentRootSet.neverClassInline);
+      noObfuscation.addAll(consequentRootSet.noObfuscation);
+      if (addNoShrinking) {
+        noShrinking.addAll(consequentRootSet.noShrinking);
+      }
+      addDependentItems(consequentRootSet.dependentNoShrinking, dependentNoShrinking);
+      addDependentItems(consequentRootSet.dependentSoftPinned, dependentSoftPinned);
+      consequentRootSet.dependentKeepClassCompatRule.forEach(
+          (type, rules) ->
+              dependentKeepClassCompatRule
+                  .computeIfAbsent(type, k -> new HashSet<>())
+                  .addAll(rules));
+      delayedRootSetActionItems.addAll(consequentRootSet.delayedRootSetActionItems);
+    }
+
+    // Add dependent items that depend on -if rules.
+    private static void addDependentItems(
+        Map<DexReference, ? extends ItemsWithRules> dependentItemsToAdd,
+        Map<DexReference, MutableItemsWithRules> dependentItemsToAddTo) {
+      dependentItemsToAdd.forEach(
+          (reference, dependence) ->
+              dependentItemsToAddTo
+                  .computeIfAbsent(reference, x -> new MutableItemsWithRules())
+                  .putAll(dependence));
+    }
+
+    public void copy(DexReference original, DexReference rewritten) {
+      if (noShrinking.containsReference(original)) {
+        noShrinking.putReferenceWithRules(rewritten, noShrinking.getRulesForReference(original));
+      }
+      if (noObfuscation.contains(original)) {
+        noObfuscation.add(rewritten);
+      }
+      if (original.isDexMember()) {
+        assert rewritten.isDexMember();
+        DexMember<?, ?> originalMember = original.asDexMember();
+        if (noSideEffects.containsKey(originalMember)) {
+          noSideEffects.put(rewritten.asDexMember(), noSideEffects.get(originalMember));
+        }
+        if (assumedValues.containsKey(originalMember)) {
+          assumedValues.put(rewritten.asDexMember(), assumedValues.get(originalMember));
+        }
+      }
+    }
+
+    public void prune(DexReference reference) {
+      noShrinking.removeReference(reference);
+      noObfuscation.remove(reference);
+      noSideEffects.remove(reference);
+      assumedValues.remove(reference);
+    }
+
+    public void pruneDeadItems(DexDefinitionSupplier definitions, Enqueuer enqueuer) {
+      pruneDeadReferences(noUnusedInterfaceRemoval, definitions, enqueuer);
+      pruneDeadReferences(noVerticalClassMerging, definitions, enqueuer);
+      pruneDeadReferences(noHorizontalClassMerging, definitions, enqueuer);
+      pruneDeadReferences(alwaysInline, definitions, enqueuer);
+      pruneDeadReferences(noSideEffects.keySet(), definitions, enqueuer);
+    }
+
+    private static void pruneDeadReferences(
+        Set<? extends DexReference> references,
+        DexDefinitionSupplier definitions,
+        Enqueuer enqueuer) {
+      references.removeIf(
+          reference -> {
+            if (reference.isDexType()) {
+              DexClass definition = definitions.definitionFor(reference.asDexType());
+              return definition == null || !enqueuer.isTypeLive(definition);
+            }
+
+            assert reference.isDexMember();
+
+            DexMember<?, ?> member = reference.asDexMember();
+            DexClass holder = definitions.definitionForHolder(member);
+            DexEncodedMember<?, ?> definition = member.lookupOnClass(holder);
+            if (definition == null) {
+              return true;
+            }
+            if (holder.isProgramClass()) {
+              if (definition.isDexEncodedField()) {
+                DexEncodedField field = definition.asDexEncodedField();
+                return !enqueuer.isFieldReferenced(field);
+              }
+              assert definition.isDexEncodedMethod();
+              DexEncodedMethod method = definition.asDexEncodedMethod();
+              return !enqueuer.isMethodLive(method) && !enqueuer.isMethodTargeted(method);
+            }
+            return !enqueuer.isNonProgramTypeLive(holder);
+          });
+    }
+
+    public void move(DexReference original, DexReference rewritten) {
+      copy(original, rewritten);
+      prune(original);
+    }
+
+    void shouldNotBeMinified(DexReference reference) {
+      noObfuscation.add(reference);
+    }
+
+    public boolean verifyKeptFieldsAreAccessedAndLive(AppInfoWithLiveness appInfo) {
+      noShrinking.forEachField(
+          reference -> {
+            DexClass holder = appInfo.definitionForHolder(reference);
+            DexEncodedField field = reference.lookupOnClass(holder);
+            if (field != null
+                && (field.isStatic()
+                    || isKeptDirectlyOrIndirectly(field.getHolderType(), appInfo))) {
+              assert appInfo.isFieldRead(field)
+                  : "Expected kept field `" + field.toSourceString() + "` to be read";
+              assert appInfo.isFieldWritten(field)
+                  : "Expected kept field `" + field.toSourceString() + "` to be written";
+            }
+          });
+      return true;
+    }
+
+    public boolean verifyKeptMethodsAreTargetedAndLive(AppInfoWithLiveness appInfo) {
+      noShrinking.forEachMethod(
+          reference -> {
+            assert appInfo.isTargetedMethod(reference)
+                : "Expected kept method `" + reference.toSourceString() + "` to be targeted";
+            DexEncodedMethod method =
+                appInfo.definitionForHolder(reference).lookupMethod(reference);
+            if (!method.isAbstract()
+                && isKeptDirectlyOrIndirectly(method.getHolderType(), appInfo)) {
+              assert appInfo.isLiveMethod(reference)
+                  : "Expected non-abstract kept method `"
+                      + reference.toSourceString()
+                      + "` to be live";
+            }
+          });
+      return true;
+    }
+
+    public boolean verifyKeptTypesAreLive(AppInfoWithLiveness appInfo) {
+      noShrinking.forEachClass(
+          type -> {
+            assert appInfo.isLiveProgramType(type)
+                : "Expected kept type `" + type.toSourceString() + "` to be live";
+          });
+      return true;
+    }
+
+    private boolean isKeptDirectlyOrIndirectly(DexType type, AppInfoWithLiveness appInfo) {
+      if (noShrinking.containsClass(type)) {
+        return true;
+      }
+      DexClass clazz = appInfo.definitionFor(type);
+      if (clazz == null) {
+        return false;
+      }
+      if (clazz.superType != null) {
+        return isKeptDirectlyOrIndirectly(clazz.superType, appInfo);
+      }
+      return false;
+    }
+
+    public boolean verifyKeptItemsAreKept(AppView<? extends AppInfoWithClassHierarchy> appView) {
+      AppInfoWithClassHierarchy appInfo = appView.appInfo();
+      GraphLens lens = appView.graphLens();
+      // Create a mapping from each required type to the set of required members on that type.
+      Map<DexType, Set<DexMember<?, ?>>> requiredMembersPerType = new IdentityHashMap<>();
+      noShrinking.forEachClass(
+          type -> {
+            DexType rewrittenType = lens.lookupType(type);
+            assert !appInfo.hasLiveness() || appInfo.withLiveness().isPinned(rewrittenType)
+                : "Expected reference `" + rewrittenType.toSourceString() + "` to be pinned";
+            requiredMembersPerType.computeIfAbsent(rewrittenType, key -> Sets.newIdentityHashSet());
+          });
+      noShrinking.forEachMember(
+          member -> {
+            DexMember<?, ?> rewrittenMember = lens.getRenamedMemberSignature(member);
+            assert !appInfo.hasLiveness() || appInfo.withLiveness().isPinned(rewrittenMember)
+                : "Expected reference `" + rewrittenMember.toSourceString() + "` to be pinned";
+            requiredMembersPerType
+                .computeIfAbsent(rewrittenMember.holder, key -> Sets.newIdentityHashSet())
+                .add(rewrittenMember);
+          });
+
+      // Run through each class in the program and check that it has members it must have.
+      for (DexProgramClass clazz : appView.appInfo().classes()) {
+        Set<DexMember<?, ?>> requiredMembers =
+            requiredMembersPerType.getOrDefault(clazz.type, ImmutableSet.of());
+
+        Set<DexField> fields = null;
+        Set<DexMethod> methods = null;
+
+        for (DexMember<?, ?> requiredMember : requiredMembers) {
+          if (requiredMember.isDexField()) {
+            DexField requiredField = requiredMember.asDexField();
+            if (fields == null) {
+              // Create a Set of the fields to avoid quadratic behavior.
+              fields =
+                  Streams.stream(clazz.fields())
+                      .map(DexEncodedField::getReference)
+                      .collect(Collectors.toSet());
+            }
+            assert fields.contains(requiredField)
+                : "Expected field `"
+                    + requiredField.toSourceString()
+                    + "` from the root set to be present";
+          } else {
+            DexMethod requiredMethod = requiredMember.asDexMethod();
+            if (methods == null) {
+              // Create a Set of the methods to avoid quadratic behavior.
+              methods =
+                  Streams.stream(clazz.methods())
+                      .map(DexEncodedMethod::getReference)
+                      .collect(Collectors.toSet());
+            }
+            assert methods.contains(requiredMethod)
+                : "Expected method `"
+                    + requiredMethod.toSourceString()
+                    + "` from the root set to be present";
+          }
+        }
+        requiredMembersPerType.remove(clazz.type);
+      }
+
+      // If the map is non-empty, then a type in the root set was not in the application.
+      if (!requiredMembersPerType.isEmpty()) {
+        DexType type = requiredMembersPerType.keySet().iterator().next();
+        DexClass clazz = appView.definitionFor(type);
+        assert clazz == null || clazz.isProgramClass()
+            : "Unexpected library type in root set: `" + type + "`";
+        assert requiredMembersPerType.isEmpty()
+            : "Expected type `" + type.toSourceString() + "` to be present";
+      }
+
+      return true;
+    }
+
+    @Override
+    public String toString() {
+      StringBuilder builder = new StringBuilder();
+      builder.append("RootSet");
+      builder.append("\nnoShrinking: " + noShrinking.size());
+      builder.append("\nnoObfuscation: " + noObfuscation.size());
+      builder.append("\nreasonAsked: " + reasonAsked.size());
+      builder.append("\ncheckDiscarded: " + checkDiscarded.size());
+      builder.append("\nnoSideEffects: " + noSideEffects.size());
+      builder.append("\nassumedValues: " + assumedValues.size());
+      builder.append("\ndependentNoShrinking: " + dependentNoShrinking.size());
+      builder.append("\nidentifierNameStrings: " + identifierNameStrings.size());
+      builder.append("\nifRules: " + ifRules.size());
+      return builder.toString();
+    }
+
+    public static RootSetBuilder builder(
+        AppView<? extends AppInfoWithClassHierarchy> appView, SubtypingInfo subtypingInfo) {
+      return new RootSetBuilder(appView, subtypingInfo);
+    }
+
+    public static RootSetBuilder builder(
+        AppView<? extends AppInfoWithClassHierarchy> appView,
+        SubtypingInfo subtypingInfo,
+        Iterable<? extends ProguardConfigurationRule> rules) {
+      return new RootSetBuilder(appView, subtypingInfo, rules);
+    }
+  }
+
+  static class ConsequentRootSetBuilder extends RootSetBuilder {
+
+    private final Enqueuer enqueuer;
+
+    private ConsequentRootSetBuilder(
+        AppView<? extends AppInfoWithClassHierarchy> appView,
+        SubtypingInfo subtypingInfo,
+        Enqueuer enqueuer) {
+      super(appView, subtypingInfo, null);
+      this.enqueuer = enqueuer;
+    }
+
+    @Override
+    void handleMatchedAnnotation(AnnotationMatchResult annotationMatchResult) {
+      if (enqueuer.getMode().isInitialTreeShaking()
+          && annotationMatchResult.isConcreteAnnotationMatchResult()) {
+        enqueuer.retainAnnotationForFinalTreeShaking(
+            annotationMatchResult.asConcreteAnnotationMatchResult().getMatchedAnnotations());
+      }
+    }
+  }
+
+  // A partial RootSet that becomes live due to the enabled -if rule or the addition of interface
+  // keep rules.
+  public static class ConsequentRootSet extends RootSetBase {
+
+    ConsequentRootSet(
+        Set<DexMethod> neverInline,
+        Set<DexMethod> neverInlineDueToSingleCaller,
+        Set<DexType> neverClassInline,
+        MutableItemsWithRules noShrinking,
+        MutableItemsWithRules softPinned,
+        Set<DexReference> noObfuscation,
+        Map<DexReference, MutableItemsWithRules> dependentNoShrinking,
+        Map<DexReference, MutableItemsWithRules> dependentSoftPinned,
+        Map<DexType, Set<ProguardKeepRuleBase>> dependentKeepClassCompatRule,
+        List<DelayedRootSetActionItem> delayedRootSetActionItems) {
+      super(
+          neverInline,
+          neverInlineDueToSingleCaller,
+          neverClassInline,
+          noShrinking,
+          softPinned,
+          noObfuscation,
+          dependentNoShrinking,
+          dependentSoftPinned,
+          dependentKeepClassCompatRule,
+          delayedRootSetActionItems);
+    }
+
+    static ConsequentRootSetBuilder builder(
+        AppView<? extends AppInfoWithClassHierarchy> appView,
+        SubtypingInfo subtypingInfo,
+        Enqueuer enqueuer) {
+      return new ConsequentRootSetBuilder(appView, subtypingInfo, enqueuer);
+    }
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/shaking/StaticClassMerger.java b/src/main/java/com/android/tools/r8/shaking/StaticClassMerger.java
deleted file mode 100644
index 33e64f0..0000000
--- a/src/main/java/com/android/tools/r8/shaking/StaticClassMerger.java
+++ /dev/null
@@ -1,579 +0,0 @@
-// 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.shaking;
-
-import com.android.tools.r8.FeatureSplit;
-import com.android.tools.r8.features.ClassToFeatureSplitMap;
-import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.DexEncodedField;
-import com.android.tools.r8.graph.DexEncodedMethod;
-import com.android.tools.r8.graph.DexField;
-import com.android.tools.r8.graph.DexMethod;
-import com.android.tools.r8.graph.DexProgramClass;
-import com.android.tools.r8.graph.DexString;
-import com.android.tools.r8.graph.GraphLens.NestedGraphLens;
-import com.android.tools.r8.graph.classmerging.StaticallyMergedClasses;
-import com.android.tools.r8.logging.Log;
-import com.android.tools.r8.shaking.VerticalClassMerger.IllegalAccessDetector;
-import com.android.tools.r8.utils.FieldSignatureEquivalence;
-import com.android.tools.r8.utils.InternalOptions;
-import com.android.tools.r8.utils.MethodJavaSignatureEquivalence;
-import com.android.tools.r8.utils.MethodSignatureEquivalence;
-import com.android.tools.r8.utils.SingletonEquivalence;
-import com.android.tools.r8.utils.TraversalContinuation;
-import com.android.tools.r8.utils.collections.BidirectionalOneToOneHashMap;
-import com.android.tools.r8.utils.collections.BidirectionalOneToOneMap;
-import com.android.tools.r8.utils.collections.MutableBidirectionalOneToOneMap;
-import com.google.common.base.Equivalence;
-import com.google.common.base.Equivalence.Wrapper;
-import com.google.common.collect.HashMultiset;
-import com.google.common.collect.ImmutableMap;
-import com.google.common.collect.Multiset.Entry;
-import com.google.common.collect.Streams;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Objects;
-import java.util.Set;
-import java.util.function.Predicate;
-import java.util.stream.Collectors;
-
-/**
- * This optimization merges all classes that only have static members and private virtual methods.
- *
- * <p>If a merge candidate does not access any package-private or protected members, then it is
- * merged into a global representative. Otherwise it is merged into a representative for its
- * package. If no such representatives exist, then the merge candidate is promoted to be the
- * representative.
- *
- * <p>Note that, when merging a merge candidate X into Y, this optimization merely moves the members
- * of X into Y -- it does not change all occurrences of X in the program into Y. This makes the
- * optimization more applicable, because it would otherwise not be possible to merge two classes if
- * they inherited from, say, X' and Y' (since multiple inheritance is not allowed).
- *
- * <p>When there is a main dex specification, merging will only happen within the three groups of
- * classes found from main dex tracing ("main dex roots", "main dex dependencies" and
- * "non main dex classes"), and not between them. This ensures that the size of the main dex
- * will not grow out of proportions due to non main dex classes getting merged into main dex
- * classes.
- */
-public class StaticClassMerger {
-
-  private enum MergeGroup {
-    MAIN_DEX_ROOTS,
-    MAIN_DEX_DEPENDENCIES,
-    NOT_MAIN_DEX,
-    DONT_MERGE;
-
-    @Override
-    public String toString() {
-      switch (this) {
-        case NOT_MAIN_DEX:
-          return "outside main dex";
-        case MAIN_DEX_ROOTS:
-          return "main dex roots";
-        case MAIN_DEX_DEPENDENCIES:
-          return "main dex dependencies";
-        default:
-          assert this == DONT_MERGE;
-          return "don't merge";
-      }
-    }
-  }
-
-  private static class MergeKey {
-
-    private static final String GLOBAL = "<global>";
-
-    private final FeatureSplit featureSplit;
-    private final MergeGroup mergeGroup;
-    private final String packageOrGlobal;
-
-    public MergeKey(FeatureSplit featureSplit, MergeGroup mergeGroup, String packageOrGlobal) {
-      this.featureSplit = featureSplit;
-      this.mergeGroup = mergeGroup;
-      this.packageOrGlobal = packageOrGlobal;
-    }
-
-    public MergeGroup getMergeGroup() {
-      return mergeGroup;
-    }
-
-    public MergeKey toGlobal() {
-      return new MergeKey(featureSplit, mergeGroup, GLOBAL);
-    }
-
-    public String getPackageOrGlobal() {
-      return packageOrGlobal;
-    }
-
-    public boolean isGlobal() {
-      return packageOrGlobal.equals(GLOBAL);
-    }
-
-    @Override
-    public int hashCode() {
-      return Objects.hash(featureSplit, mergeGroup, packageOrGlobal);
-    }
-
-    @Override
-    public boolean equals(Object other) {
-      if (other == this) {
-        return true;
-      }
-      if (other == null || this.getClass() != other.getClass()) {
-        return false;
-      }
-      MergeKey o = (MergeKey) other;
-      return o.featureSplit == featureSplit
-          && o.mergeGroup == mergeGroup
-          && o.packageOrGlobal.equals(packageOrGlobal);
-    }
-  }
-
-  // There are 52 characters in [a-zA-Z], so with a capacity just below 52 the minifier should be
-  // able to find single-character names for all members, but around 30 appears to work better in
-  // practice.
-  private static final int HEURISTIC_FOR_CAPACITY_OF_REPRESENTATIVES = 30;
-
-  private class Representative {
-
-    private final DexProgramClass clazz;
-
-    // Put all members of this class into buckets, where each bucket represent members that have a
-    // conflicting signature, and therefore must be given distinct names.
-    private final HashMultiset<Wrapper<DexField>> fieldBuckets = HashMultiset.create();
-    private final HashMultiset<Wrapper<DexMethod>> methodBuckets = HashMultiset.create();
-
-    private boolean hasSynchronizedMethods = false;
-
-    public Representative(DexProgramClass clazz) {
-      this.clazz = clazz;
-      include(clazz);
-    }
-
-    // Includes the members of `clazz` in `fieldBuckets` and `methodBuckets`.
-    public void include(DexProgramClass clazz) {
-      for (DexEncodedField field : clazz.fields()) {
-        Wrapper<DexField> wrapper = fieldEquivalence.wrap(field.field);
-        fieldBuckets.add(wrapper);
-      }
-      boolean classHasSynchronizedMethods = false;
-      for (DexEncodedMethod method : clazz.methods()) {
-        assert !hasSynchronizedMethods || !method.isSynchronized();
-        classHasSynchronizedMethods |= method.isSynchronized();
-        Wrapper<DexMethod> wrapper = methodEquivalence.wrap(method.method);
-        methodBuckets.add(wrapper);
-      }
-      hasSynchronizedMethods |= classHasSynchronizedMethods;
-    }
-
-    // Returns true if this representative should no longer be used. The current heuristic is to
-    // stop using a representative when the number of members with the same signature (ignoring the
-    // name) exceeds a given threshold. This way it is unlikely that we will not be able to find a
-    // single-character name for all members.
-    public boolean isFull() {
-      int numberOfNamesNeeded = 1;
-      for (Entry<Wrapper<DexField>> entry : fieldBuckets.entrySet()) {
-        numberOfNamesNeeded = Math.max(entry.getCount(), numberOfNamesNeeded);
-      }
-      for (Entry<Wrapper<DexMethod>> entry : methodBuckets.entrySet()) {
-        numberOfNamesNeeded = Math.max(entry.getCount(), numberOfNamesNeeded);
-      }
-      return numberOfNamesNeeded > HEURISTIC_FOR_CAPACITY_OF_REPRESENTATIVES;
-    }
-  }
-
-  private final AppView<AppInfoWithLiveness> appView;
-  private final MainDexTracingResult mainDexClasses;
-  private final StaticallyMergedClasses.Builder mergedClassesBuilder =
-      StaticallyMergedClasses.builder();
-
-  /** The equivalence that should be used for the member buckets in {@link Representative}. */
-  private final Equivalence<DexField> fieldEquivalence;
-  private final Equivalence<DexMethod> methodEquivalence;
-
-  private final Map<MergeKey, Representative> representatives = new HashMap<>();
-
-  private final MutableBidirectionalOneToOneMap<DexField, DexField> newFieldSignatures =
-      new BidirectionalOneToOneHashMap<>();
-  private final MutableBidirectionalOneToOneMap<DexMethod, DexMethod> methodMapping =
-      new BidirectionalOneToOneHashMap<>();
-
-  private int numberOfMergedClasses = 0;
-
-  public StaticClassMerger(
-      AppView<AppInfoWithLiveness> appView,
-      InternalOptions options,
-      MainDexTracingResult mainDexClasses) {
-    this.appView = appView;
-    if (options.getProguardConfiguration().isOverloadAggressively()) {
-      fieldEquivalence = FieldSignatureEquivalence.getEquivalenceIgnoreName();
-      methodEquivalence = MethodSignatureEquivalence.getEquivalenceIgnoreName();
-    } else {
-      fieldEquivalence = new SingletonEquivalence<>();
-      methodEquivalence = MethodJavaSignatureEquivalence.getEquivalenceIgnoreName();
-    }
-    this.mainDexClasses = mainDexClasses;
-  }
-
-  public NestedGraphLens run() {
-    appView.appInfo().classesWithDeterministicOrder().forEach(this::merge);
-    appView.setStaticallyMergedClasses(mergedClassesBuilder.build());
-    return buildGraphLens();
-  }
-
-  private NestedGraphLens buildGraphLens() {
-    if (!newFieldSignatures.isEmpty() || !methodMapping.isEmpty()) {
-      BidirectionalOneToOneMap<DexMethod, DexMethod> originalMethodSignatures =
-          methodMapping.getInverseOneToOneMap();
-      return new NestedGraphLens(
-          ImmutableMap.of(),
-          methodMapping.getForwardMap(),
-          newFieldSignatures,
-          originalMethodSignatures,
-          appView.graphLens(),
-          appView.dexItemFactory());
-    }
-    return null;
-  }
-
-  private ClassToFeatureSplitMap getClassToFeatureSplitMap() {
-    return appView.appInfo().getClassToFeatureSplitMap();
-  }
-
-  private MergeGroup getMergeGroup(DexProgramClass clazz) {
-    if (appView.getSyntheticItems().isSyntheticClass(clazz)) {
-      // In principle it would be valid to merge synthetic classes into program classes.
-      // Doing so may however increase size as static methods will not be de-duplicated
-      // at synthetic finalization.
-      return MergeGroup.DONT_MERGE;
-    }
-    if (appView.appInfo().getNoStaticClassMergingSet().contains(clazz.type)) {
-      return MergeGroup.DONT_MERGE;
-    }
-    if (clazz.staticFields().size() + clazz.getMethodCollection().size() == 0) {
-      return MergeGroup.DONT_MERGE;
-    }
-    if (clazz.instanceFields().size() > 0) {
-      return MergeGroup.DONT_MERGE;
-    }
-    if (clazz.staticFields().stream()
-        .anyMatch(field -> appView.appInfo().isPinned(field.field))) {
-      return MergeGroup.DONT_MERGE;
-    }
-    if (clazz.getMethodCollection().hasDirectMethods(DexEncodedMethod::isInitializer)) {
-      return MergeGroup.DONT_MERGE;
-    }
-    if (clazz.getMethodCollection().hasVirtualMethods(method -> !method.isPrivateMethod())) {
-      return MergeGroup.DONT_MERGE;
-    }
-    if (clazz.isInANest()) {
-      // Breaks nest access control, abort merging.
-      return MergeGroup.DONT_MERGE;
-    }
-    if (Streams.stream(clazz.methods())
-        .anyMatch(
-            method ->
-                method.isNative()
-                    || appView.appInfo().isPinned(method.method)
-                    // TODO(christofferqa): Remove the invariant that the graph lens should not
-                    // modify any methods from the sets alwaysInline and noSideEffects.
-                    || appView.appInfo().isAlwaysInlineMethod(method.method)
-                    || appView.appInfo().noSideEffects.keySet().contains(method.method))) {
-      return MergeGroup.DONT_MERGE;
-    }
-    if (clazz.classInitializationMayHaveSideEffects(appView)) {
-      // This could have a negative impact on inlining.
-      //
-      // See {@link com.android.tools.r8.ir.optimize.DefaultInliningOracle#canInlineStaticInvoke}
-      // for further details.
-      //
-      // Note that this will be true for all classes that inherit from or implement a library class.
-      return MergeGroup.DONT_MERGE;
-    }
-    if (!mainDexClasses.isEmpty()) {
-      if (mainDexClasses.getRoots().contains(clazz.type)) {
-        return MergeGroup.MAIN_DEX_ROOTS;
-      }
-      if (mainDexClasses.getDependencies().contains(clazz.type)) {
-        return MergeGroup.MAIN_DEX_DEPENDENCIES;
-      }
-    }
-    return MergeGroup.NOT_MAIN_DEX;
-  }
-
-  private MergeKey getMergeKey(DexProgramClass clazz, MergeGroup mergeGroup) {
-    return new MergeKey(
-        getClassToFeatureSplitMap().getFeatureSplit(clazz),
-        mergeGroup,
-        mayMergeAcrossPackageBoundaries(clazz)
-            ? MergeKey.GLOBAL
-            : clazz.type.getPackageDescriptor());
-  }
-
-  private boolean isValidRepresentative(DexProgramClass clazz) {
-    // Disallow interfaces from being representatives, since interface methods require desugaring.
-    return !clazz.isInterface();
-  }
-
-  private void merge(DexProgramClass clazz) {
-    MergeGroup mergeGroup = getMergeGroup(clazz);
-    if (mergeGroup != MergeGroup.DONT_MERGE) {
-      merge(clazz, mergeGroup);
-    }
-  }
-
-  private void merge(DexProgramClass clazz, MergeGroup mergeGroup) {
-    MergeKey key = getMergeKey(clazz, mergeGroup);
-    Representative representative = representatives.get(key);
-    if (representative != null) {
-      if (representative.hasSynchronizedMethods && clazz.hasStaticSynchronizedMethods()) {
-        // We are not allowed to merge synchronized classes with synchronized methods.
-        return;
-      }
-      if (appView.appInfo().isLockCandidate(clazz.type)) {
-        // Since the type is const-class referenced (and the static merger does not create a lens
-        // to map the merged type) the class will likely remain and there is no gain from merging.
-        return;
-      }
-      // Check if current candidate is a better choice depending on visibility. For package private
-      // or protected, the key is parameterized by the package name already, so we just have to
-      // check accessibility-flags. For global this is no-op.
-      if (isValidRepresentative(clazz)
-          && !representative.clazz.accessFlags.isAtLeastAsVisibleAs(clazz.accessFlags)) {
-        assert clazz.type.getPackageDescriptor().equals(key.packageOrGlobal);
-        assert representative.clazz.type.getPackageDescriptor().equals(key.packageOrGlobal);
-        Representative newRepresentative = getOrCreateRepresentative(key, clazz);
-        newRepresentative.include(representative.clazz);
-        if (!newRepresentative.isFull()) {
-          setRepresentative(key, newRepresentative);
-          moveMembersFromSourceToTarget(representative.clazz, clazz);
-          return;
-        }
-      } else {
-        representative.include(clazz);
-        if (!representative.isFull()) {
-          moveMembersFromSourceToTarget(clazz, representative.clazz);
-          return;
-        }
-      }
-    }
-    if (isValidRepresentative(clazz)) {
-      setRepresentative(key, getOrCreateRepresentative(key, clazz));
-    }
-  }
-
-  private Representative getOrCreateRepresentative(MergeKey key, DexProgramClass clazz) {
-    Representative globalRepresentative = representatives.get(key.toGlobal());
-    if (globalRepresentative != null && globalRepresentative.clazz == clazz) {
-      return globalRepresentative;
-    }
-    Representative packageRepresentative = representatives.get(key);
-    if (packageRepresentative != null && packageRepresentative.clazz == clazz) {
-      return packageRepresentative;
-    }
-    return new Representative(clazz);
-  }
-
-  private void setRepresentative(MergeKey key, Representative representative) {
-    assert isValidRepresentative(representative.clazz);
-    if (Log.ENABLED) {
-      if (key.isGlobal()) {
-        Log.info(
-            getClass(),
-            "Making %s the global representative in group %s",
-            representative.clazz.type.toSourceString(),
-            key.getMergeGroup().toString());
-      } else {
-        Log.info(
-            getClass(),
-            "Making %s the representative for package %s in group %s",
-            representative.clazz.type.toSourceString(),
-            key.getPackageOrGlobal(),
-            key.getMergeGroup().toString());
-      }
-    }
-    representatives.put(key, representative);
-  }
-
-  private boolean mayMergeAcrossPackageBoundaries(DexProgramClass clazz) {
-    // Check that the class is public. Otherwise, accesses to `clazz` from within its current
-    // package may become illegal.
-    if (!clazz.isPublic()) {
-      return false;
-    }
-    // Check that all of the members are private or public.
-    if (clazz
-        .getMethodCollection()
-        .hasDirectMethods(method -> !method.isPrivate() && !method.isPublic())) {
-      return false;
-    }
-    if (!clazz.staticFields().stream().allMatch(field -> field.isPrivate() || field.isPublic())) {
-      return false;
-    }
-
-    // Note that a class is only considered a candidate if it has no instance fields and all of its
-    // virtual methods are private. Therefore, we don't need to consider check if there are any
-    // package-private or protected instance fields or virtual methods here.
-    assert clazz.instanceFields().size() == 0;
-    assert !clazz.getMethodCollection().hasVirtualMethods(method -> !method.isPrivate());
-
-    // Check that no methods access package-private or protected members.
-    IllegalAccessDetector registry = new IllegalAccessDetector(appView, clazz);
-    TraversalContinuation result =
-        clazz.traverseProgramMethods(
-            method -> {
-              registry.setContext(method);
-              method.registerCodeReferences(registry);
-              if (registry.foundIllegalAccess()) {
-                return TraversalContinuation.BREAK;
-              }
-              return TraversalContinuation.CONTINUE;
-            });
-    return result.shouldContinue();
-  }
-
-  private void moveMembersFromSourceToTarget(
-      DexProgramClass sourceClass, DexProgramClass targetClass) {
-    if (Log.ENABLED) {
-      Log.info(
-          getClass(),
-          "Merging %s into %s",
-          sourceClass.type.toSourceString(),
-          targetClass.type.toSourceString());
-    }
-
-    // TODO(b/136457753) This check is a bit weird for protected, since it is moving access.
-    assert targetClass.accessFlags.isAtLeastAsVisibleAs(sourceClass.accessFlags);
-    assert sourceClass.instanceFields().isEmpty();
-    assert targetClass.instanceFields().isEmpty();
-    assert getClassToFeatureSplitMap().isInSameFeatureOrBothInBase(sourceClass, targetClass);
-
-    numberOfMergedClasses++;
-    mergedClassesBuilder.recordMerge(sourceClass, targetClass);
-
-    // Move members from source to target.
-    targetClass.addDirectMethods(
-        mergeMethods(sourceClass.directMethods(), targetClass.directMethods(), targetClass));
-    targetClass.addVirtualMethods(
-        mergeMethods(sourceClass.virtualMethods(), targetClass.virtualMethods(), targetClass));
-    targetClass.setStaticFields(
-        mergeFields(sourceClass.staticFields(), targetClass.staticFields(), targetClass));
-
-    // Cleanup source.
-    sourceClass.setDirectMethods(DexEncodedMethod.EMPTY_ARRAY);
-    sourceClass.setVirtualMethods(DexEncodedMethod.EMPTY_ARRAY);
-    sourceClass.setStaticFields(DexEncodedField.EMPTY_ARRAY);
-  }
-
-  private List<DexEncodedMethod> mergeMethods(
-      Iterable<DexEncodedMethod> sourceMethods,
-      Iterable<DexEncodedMethod> targetMethods,
-      DexProgramClass targetClass) {
-    // Move source methods to result one by one, renaming them if needed.
-    MethodSignatureEquivalence equivalence = MethodSignatureEquivalence.get();
-    Set<Wrapper<DexMethod>> existingMethods = new HashSet<>();
-    for (DexEncodedMethod targetMethod : targetMethods) {
-      existingMethods.add(equivalence.wrap(targetMethod.method));
-    }
-
-    Predicate<DexMethod> availableMethodSignatures =
-        method -> !existingMethods.contains(equivalence.wrap(method));
-
-    List<DexEncodedMethod> newMethods = new ArrayList<>();
-    for (DexEncodedMethod sourceMethod : sourceMethods) {
-      DexEncodedMethod sourceMethodAfterMove =
-          renameMethodIfNeeded(sourceMethod, targetClass, availableMethodSignatures);
-      newMethods.add(sourceMethodAfterMove);
-
-      DexMethod originalMethod =
-          methodMapping.getRepresentativeKeyOrDefault(sourceMethod.method, sourceMethod.method);
-      methodMapping.put(originalMethod, sourceMethodAfterMove.method);
-
-      existingMethods.add(equivalence.wrap(sourceMethodAfterMove.method));
-    }
-    return newMethods;
-  }
-
-  private DexEncodedField[] mergeFields(
-      List<DexEncodedField> sourceFields,
-      List<DexEncodedField> targetFields,
-      DexProgramClass targetClass) {
-    DexEncodedField[] result = new DexEncodedField[sourceFields.size() + targetFields.size()];
-
-    // Move all target fields to result.
-    int index = 0;
-    for (DexEncodedField targetField : targetFields) {
-      result[index++] = targetField;
-    }
-
-    // Move source fields to result one by one, renaming them if needed.
-    FieldSignatureEquivalence equivalence = FieldSignatureEquivalence.get();
-    Set<Wrapper<DexField>> existingFields =
-        targetFields.stream()
-            .map(targetField -> equivalence.wrap(targetField.field))
-            .collect(Collectors.toSet());
-
-    Predicate<DexField> availableFieldSignatures =
-        field -> !existingFields.contains(equivalence.wrap(field));
-
-    for (DexEncodedField sourceField : sourceFields) {
-      DexEncodedField sourceFieldAfterMove =
-          renameFieldIfNeeded(sourceField, targetClass, availableFieldSignatures);
-      result[index++] = sourceFieldAfterMove;
-
-      DexField originalField =
-          newFieldSignatures.getRepresentativeKeyOrDefault(sourceField.field, sourceField.field);
-      newFieldSignatures.put(originalField, sourceFieldAfterMove.field);
-
-      existingFields.add(equivalence.wrap(sourceFieldAfterMove.field));
-    }
-
-    assert index == result.length;
-    return result;
-  }
-
-  private DexEncodedMethod renameMethodIfNeeded(
-      DexEncodedMethod method,
-      DexProgramClass targetClass,
-      Predicate<DexMethod> availableMethodSignatures) {
-    assert !method.accessFlags.isConstructor();
-    DexString oldName = method.method.name;
-    DexMethod newSignature =
-        appView.dexItemFactory().createMethod(targetClass.type, method.method.proto, oldName);
-    if (!availableMethodSignatures.test(newSignature)) {
-      int count = 1;
-      do {
-        DexString newName = appView.dexItemFactory().createString(oldName.toSourceString() + count);
-        newSignature =
-            appView.dexItemFactory().createMethod(targetClass.type, method.method.proto, newName);
-        count++;
-      } while (!availableMethodSignatures.test(newSignature));
-    }
-    return method.toTypeSubstitutedMethod(newSignature);
-  }
-
-  private DexEncodedField renameFieldIfNeeded(
-      DexEncodedField field,
-      DexProgramClass targetClass,
-      Predicate<DexField> availableFieldSignatures) {
-    DexString oldName = field.field.name;
-    DexField newSignature =
-        appView.dexItemFactory().createField(targetClass.type, field.field.type, oldName);
-    if (!availableFieldSignatures.test(newSignature)) {
-      int count = 1;
-      do {
-        DexString newName = appView.dexItemFactory().createString(oldName.toSourceString() + count);
-        newSignature =
-            appView.dexItemFactory().createField(targetClass.type, field.field.type, newName);
-        count++;
-      } while (!availableFieldSignatures.test(newSignature));
-    }
-    return field.toTypeSubstitutedField(newSignature);
-  }
-}
diff --git a/src/main/java/com/android/tools/r8/synthesis/SyntheticDefinition.java b/src/main/java/com/android/tools/r8/synthesis/SyntheticDefinition.java
index 071e7b6..eefe1b9 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SyntheticDefinition.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticDefinition.java
@@ -3,6 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.synthesis;
 
+import com.android.tools.r8.features.ClassToFeatureSplitMap;
 import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.GraphLens;
@@ -60,30 +61,58 @@
 
   public abstract C getHolder();
 
-  final HashCode computeHash(RepresentativeMap map, boolean intermediate) {
+  final HashCode computeHash(
+      RepresentativeMap map, boolean intermediate, ClassToFeatureSplitMap classToFeatureSplitMap) {
     Hasher hasher = Hashing.murmur3_128().newHasher();
     if (intermediate) {
       // If in intermediate mode, include the context type as sharing is restricted to within a
       // single context.
       getContext().getSynthesizingContextType().hashWithTypeEquivalence(hasher, map);
     }
+    if (!classToFeatureSplitMap.isEmpty()) {
+      hasher.putInt(
+          classToFeatureSplitMap
+              .getFeatureSplit(getContext().getSynthesizingContextType())
+              .hashCode());
+    }
+
     internalComputeHash(hasher, map);
     return hasher.hash();
   }
 
   abstract void internalComputeHash(Hasher hasher, RepresentativeMap map);
 
-  final boolean isEquivalentTo(D other, boolean includeContext, GraphLens graphLens) {
-    return compareTo(other, includeContext, graphLens) == 0;
+  final boolean isEquivalentTo(
+      D other,
+      boolean includeContext,
+      GraphLens graphLens,
+      ClassToFeatureSplitMap classToFeatureSplitMap) {
+    return compareTo(other, includeContext, graphLens, classToFeatureSplitMap) == 0;
   }
 
-  int compareTo(D other, boolean includeContext, GraphLens graphLens) {
+  int compareTo(
+      D other,
+      boolean includeContext,
+      GraphLens graphLens,
+      ClassToFeatureSplitMap classToFeatureSplitMap) {
     if (includeContext) {
       int order = getContext().compareTo(other.getContext());
       if (order != 0) {
         return order;
       }
     }
+    if (!classToFeatureSplitMap.isEmpty()) {
+      DexType synthesizingContextType = this.getContext().getSynthesizingContextType();
+      DexType otherSynthesizingContextType = other.getContext().getSynthesizingContextType();
+      if (!classToFeatureSplitMap.isInSameFeatureOrBothInBase(
+          synthesizingContextType, otherSynthesizingContextType)) {
+        int order =
+            classToFeatureSplitMap.compareFeatureSplitsForDexTypes(
+                synthesizingContextType, otherSynthesizingContextType);
+        assert order != 0;
+        return order;
+      }
+    }
     DexType thisType = getHolder().getType();
     DexType otherType = other.getHolder().getType();
     RepresentativeMap map = 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 bf9106a..e2e0b55 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SyntheticFinalization.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticFinalization.java
@@ -3,6 +3,9 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.synthesis;
 
+import com.android.tools.r8.features.ClassToFeatureSplitMap;
+import com.android.tools.r8.graph.AppInfo;
+import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexAnnotation;
 import com.android.tools.r8.graph.DexApplication;
@@ -19,6 +22,7 @@
 import com.android.tools.r8.graph.PrunedItems;
 import com.android.tools.r8.graph.TreeFixerBase;
 import com.android.tools.r8.ir.code.NumberGenerator;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.shaking.MainDexClasses;
 import com.android.tools.r8.synthesis.SyntheticNaming.SyntheticKind;
 import com.android.tools.r8.utils.InternalOptions;
@@ -191,8 +195,12 @@
       return members;
     }
 
-    public int compareToIncludingContext(EquivalenceGroup<T> other, GraphLens graphLens) {
-      return getRepresentative().compareTo(other.getRepresentative(), true, graphLens);
+    public int compareToIncludingContext(
+        EquivalenceGroup<T> other,
+        GraphLens graphLens,
+        ClassToFeatureSplitMap classToFeatureSplitMap) {
+      return getRepresentative()
+          .compareTo(other.getRepresentative(), true, graphLens, classToFeatureSplitMap);
     }
 
     @Override
@@ -213,7 +221,35 @@
     this.synthetics = synthetics;
   }
 
-  public Result computeFinalSynthetics(AppView<?> appView) {
+  public static void finalize(AppView<AppInfo> appView) {
+    assert !appView.appInfo().hasClassHierarchy();
+    assert !appView.appInfo().hasLiveness();
+    Result result = appView.getSyntheticItems().computeFinalSynthetics(appView);
+    appView.setAppInfo(new AppInfo(result.commit, appView.appInfo().getMainDexClasses()));
+    appView.pruneItems(result.prunedItems);
+    if (result.lens != null) {
+      appView.setGraphLens(result.lens);
+    }
+  }
+
+  public static void finalizeWithClassHierarchy(AppView<AppInfoWithClassHierarchy> appView) {
+    assert !appView.appInfo().hasLiveness();
+    Result result = appView.getSyntheticItems().computeFinalSynthetics(appView);
+    appView.setAppInfo(appView.appInfo().rebuildWithClassHierarchy(result.commit));
+    appView.pruneItems(result.prunedItems);
+    if (result.lens != null) {
+      appView.setGraphLens(result.lens);
+    }
+  }
+
+  public static void finalizeWithLiveness(AppView<AppInfoWithLiveness> appView) {
+    Result result = appView.getSyntheticItems().computeFinalSynthetics(appView);
+    appView.setAppInfo(appView.appInfo().rebuildWithLiveness(result.commit));
+    appView.rewriteWithLens(result.lens);
+    appView.pruneItems(result.prunedItems);
+  }
+
+  Result computeFinalSynthetics(AppView<?> appView) {
     assert verifyNoNestedSynthetics();
     DexApplication application;
     MainDexClasses mainDexClasses = appView.appInfo().getMainDexClasses();
@@ -284,10 +320,19 @@
           Map<DexType, NumberGenerator> generators) {
     boolean intermediate = appView.options().intermediate;
     Map<DexType, D> definitions = lookupDefinitions(appView, references);
+    ClassToFeatureSplitMap classToFeatureSplitMap =
+        appView.appInfo().hasClassHierarchy()
+            ? appView.appInfo().withClassHierarchy().getClassToFeatureSplitMap()
+            : ClassToFeatureSplitMap.createEmptyClassToFeatureSplitMap();
     Collection<List<D>> potentialEquivalences =
         computePotentialEquivalences(
-            definitions, intermediate, appView.dexItemFactory(), appView.graphLens());
-    return computeActualEquivalences(potentialEquivalences, generators, appView, intermediate);
+            definitions,
+            intermediate,
+            appView.dexItemFactory(),
+            appView.graphLens(),
+            classToFeatureSplitMap);
+    return computeActualEquivalences(
+        potentialEquivalences, generators, appView, intermediate, classToFeatureSplitMap);
   }
 
   private boolean isNotSyntheticType(DexType type) {
@@ -510,8 +555,13 @@
     syntheticClassGroups.forEach(
         (syntheticType, syntheticGroup) -> {
           DexProgramClass externalSyntheticClass = appForLookup.programDefinitionFor(syntheticType);
+          SyntheticProgramClassDefinition representative = syntheticGroup.getRepresentative();
           addFinalSyntheticClass.accept(
-              externalSyntheticClass, syntheticGroup.getRepresentative().toReference());
+              externalSyntheticClass,
+              new SyntheticProgramClassReference(
+                  representative.getKind(),
+                  representative.getContext(),
+                  externalSyntheticClass.type));
           for (SyntheticProgramClassDefinition member : syntheticGroup.getMembers()) {
             addMainDexAndSynthesizedFromForMember(
                 member,
@@ -524,8 +574,16 @@
     syntheticMethodGroups.forEach(
         (syntheticType, syntheticGroup) -> {
           DexProgramClass externalSyntheticClass = appForLookup.programDefinitionFor(syntheticType);
+          SyntheticMethodDefinition representative = syntheticGroup.getRepresentative();
           addFinalSyntheticMethod.accept(
-              externalSyntheticClass, syntheticGroup.getRepresentative().toReference());
+              externalSyntheticClass,
+              new SyntheticMethodReference(
+                  representative.getKind(),
+                  representative.getContext(),
+                  representative
+                      .getMethod()
+                      .getReference()
+                      .withHolder(externalSyntheticClass.type, factory)));
           for (SyntheticMethodDefinition member : syntheticGroup.getMembers()) {
             addMainDexAndSynthesizedFromForMember(
                 member,
@@ -607,13 +665,16 @@
           Collection<List<T>> potentialEquivalences,
           Map<DexType, NumberGenerator> generators,
           AppView<?> appView,
-          boolean intermediate) {
+          boolean intermediate,
+          ClassToFeatureSplitMap classToFeatureSplitMap) {
     Map<DexType, List<EquivalenceGroup<T>>> groupsPerContext = new IdentityHashMap<>();
     potentialEquivalences.forEach(
         members -> {
-          List<List<T>> groups = groupEquivalent(members, intermediate, appView.graphLens());
+          List<List<T>> groups =
+              groupEquivalent(members, intermediate, appView.graphLens(), classToFeatureSplitMap);
           for (List<T> group : groups) {
-            T representative = findDeterministicRepresentative(group, appView.graphLens());
+            T representative =
+                findDeterministicRepresentative(group, appView.graphLens(), classToFeatureSplitMap);
             // The representative is required to be the first element of the group.
             group.remove(representative);
             group.add(0, representative);
@@ -630,13 +691,17 @@
         (context, groups) -> {
           // Sort the equivalence groups that go into 'context' including the context type of the
           // representative which is equal to 'context' here (see assert below).
-          groups.sort((a, b) -> a.compareToIncludingContext(b, appView.graphLens()));
+          groups.sort(
+              (a, b) ->
+                  a.compareToIncludingContext(b, appView.graphLens(), classToFeatureSplitMap));
           for (int i = 0; i < groups.size(); i++) {
             EquivalenceGroup<T> group = groups.get(i);
             assert group.getRepresentative().getContext().getSynthesizingContextType() == context;
             // Two equivalence groups in same context type must be distinct otherwise the assignment
             // of the synthetic name will be non-deterministic between the two.
-            assert i == 0 || checkGroupsAreDistinct(groups.get(i - 1), group, appView.graphLens());
+            assert i == 0
+                || checkGroupsAreDistinct(
+                    groups.get(i - 1), group, appView.graphLens(), classToFeatureSplitMap);
             SyntheticKind kind = group.members.get(0).getKind();
             DexType representativeType = createExternalType(kind, context, generators, appView);
             equivalences.put(representativeType, group);
@@ -646,13 +711,17 @@
   }
 
   private static <T extends SyntheticDefinition<?, T, ?>> List<List<T>> groupEquivalent(
-      List<T> potentialEquivalence, boolean intermediate, GraphLens graphLens) {
+      List<T> potentialEquivalence,
+      boolean intermediate,
+      GraphLens graphLens,
+      ClassToFeatureSplitMap classToFeatureSplitMap) {
     List<List<T>> groups = new ArrayList<>();
     // Each other member is in a shared group if it is actually equivalent to the first member.
     for (T synthetic : potentialEquivalence) {
       boolean requireNewGroup = true;
       for (List<T> group : groups) {
-        if (synthetic.isEquivalentTo(group.get(0), intermediate, graphLens)) {
+        if (synthetic.isEquivalentTo(
+            group.get(0), intermediate, graphLens, classToFeatureSplitMap)) {
           requireNewGroup = false;
           group.add(synthetic);
           break;
@@ -668,20 +737,23 @@
   }
 
   private static <T extends SyntheticDefinition<?, T, ?>> boolean checkGroupsAreDistinct(
-      EquivalenceGroup<T> g1, EquivalenceGroup<T> g2, GraphLens graphLens) {
-    int order = g1.compareToIncludingContext(g2, graphLens);
+      EquivalenceGroup<T> g1,
+      EquivalenceGroup<T> g2,
+      GraphLens graphLens,
+      ClassToFeatureSplitMap classToFeatureSplitMap) {
+    int order = g1.compareToIncludingContext(g2, graphLens, classToFeatureSplitMap);
     assert order != 0;
-    assert order != g2.compareToIncludingContext(g1, graphLens);
+    assert order != g2.compareToIncludingContext(g1, graphLens, classToFeatureSplitMap);
     return true;
   }
 
   private static <T extends SyntheticDefinition<?, T, ?>> T findDeterministicRepresentative(
-      List<T> members, GraphLens graphLens) {
+      List<T> members, GraphLens graphLens, ClassToFeatureSplitMap classToFeatureSplitMap) {
     // Pick a deterministic member as representative.
     T smallest = members.get(0);
     for (int i = 1; i < members.size(); i++) {
       T next = members.get(i);
-      if (next.compareTo(smallest, true, graphLens) < 0) {
+      if (next.compareTo(smallest, true, graphLens, classToFeatureSplitMap) < 0) {
         smallest = next;
       }
     }
@@ -718,7 +790,8 @@
           Map<DexType, T> definitions,
           boolean intermediate,
           DexItemFactory factory,
-          GraphLens graphLens) {
+          GraphLens graphLens,
+          ClassToFeatureSplitMap classToFeatureSplitMap) {
     if (definitions.isEmpty()) {
       return Collections.emptyList();
     }
@@ -741,7 +814,7 @@
     RepresentativeMap map = t -> syntheticTypes.contains(t) ? factory.voidType : t;
     Map<HashCode, List<T>> equivalences = new HashMap<>(definitions.size());
     for (T definition : definitions.values()) {
-      HashCode hash = definition.computeHash(map, intermediate);
+      HashCode hash = definition.computeHash(map, intermediate, classToFeatureSplitMap);
       equivalences.computeIfAbsent(hash, k -> new ArrayList<>()).add(definition);
     }
     return equivalences.values();
diff --git a/src/main/java/com/android/tools/r8/synthesis/SyntheticItems.java b/src/main/java/com/android/tools/r8/synthesis/SyntheticItems.java
index d526570..c198197 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SyntheticItems.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticItems.java
@@ -197,19 +197,28 @@
     return pending.containsType(type);
   }
 
-  public boolean isLegacyPendingSynthetic(DexType type) {
+  private boolean isLegacyPendingSynthetic(DexType type) {
     return pending.legacyClasses.containsKey(type);
   }
 
+  public boolean isLegacySyntheticClass(DexType type) {
+    return isLegacyCommittedSynthetic(type) || isLegacyPendingSynthetic(type);
+  }
+
+  public boolean isLegacySyntheticClass(DexProgramClass clazz) {
+    return isLegacySyntheticClass(clazz.getType());
+  }
+
   public boolean isNonLegacySynthetic(DexProgramClass clazz) {
-    return isCommittedSynthetic(clazz.type) || isPendingSynthetic(clazz.type);
+    return isNonLegacySynthetic(clazz.type);
+  }
+
+  public boolean isNonLegacySynthetic(DexType type) {
+    return isCommittedSynthetic(type) || isPendingSynthetic(type);
   }
 
   public boolean isSyntheticClass(DexType type) {
-    return isCommittedSynthetic(type)
-        || isPendingSynthetic(type)
-        // TODO(b/158159959): Remove usage of name-based identification.
-        || type.isD8R8SynthesizedClassType();
+    return isLegacySyntheticClass(type) || isNonLegacySynthetic(type);
   }
 
   public boolean isSyntheticClass(DexProgramClass clazz) {
@@ -237,14 +246,6 @@
     return true;
   }
 
-  public boolean isLegacySyntheticClass(DexType type) {
-    return isLegacyCommittedSynthetic(type) || isLegacyPendingSynthetic(type);
-  }
-
-  public boolean isLegacySyntheticClass(DexProgramClass clazz) {
-    return isLegacySyntheticClass(clazz.getType());
-  }
-
   public Collection<DexProgramClass> getLegacyPendingClasses() {
     return Collections.unmodifiableCollection(pending.legacyClasses.values());
   }
@@ -265,9 +266,8 @@
 
   // Addition and creation of synthetic items.
 
-  // TODO(b/158159959): Remove the usage of this direct class addition (and name-based id).
+  // TODO(b/158159959): Remove the usage of this direct class addition.
   public void addLegacySyntheticClass(DexProgramClass clazz) {
-    assert clazz.type.isD8R8SynthesizedClassType();
     assert !isCommittedSynthetic(clazz.type);
     assert !pending.nonLegacyDefinitions.containsKey(clazz.type);
     DexProgramClass previous = pending.legacyClasses.put(clazz.type, clazz);
@@ -401,9 +401,9 @@
           if (definition.isProgramDefinition()) {
             committedProgramTypesBuilder.add(definition.getHolder().getType());
             appBuilder.addProgramClass(definition.asProgramDefinition().getHolder());
-          } else {
+          } else if (appBuilder.isDirect()) {
             assert definition.isClasspathDefinition();
-            appBuilder.addClasspathClass(definition.asClasspathDefinition().getHolder());
+            appBuilder.asDirect().addClasspathClass(definition.asClasspathDefinition().getHolder());
           }
           builder.addItem(definition);
         }
@@ -428,7 +428,7 @@
 
   // Finalization of synthetic items.
 
-  public Result computeFinalSynthetics(AppView<?> appView) {
+  Result computeFinalSynthetics(AppView<?> appView) {
     assert !hasPendingSyntheticClasses();
     return new SyntheticFinalization(appView.options(), committed).computeFinalSynthetics(appView);
   }
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 a9f55ee..0d4a768 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -41,7 +41,6 @@
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.graph.classmerging.HorizontallyMergedLambdaClasses;
-import com.android.tools.r8.graph.classmerging.StaticallyMergedClasses;
 import com.android.tools.r8.graph.classmerging.VerticallyMergedClasses;
 import com.android.tools.r8.horizontalclassmerging.HorizontallyMergedClasses;
 import com.android.tools.r8.inspector.internal.InspectorImpl;
@@ -204,7 +203,6 @@
     enableDevirtualization = false;
     enableLambdaMerging = false;
     horizontalClassMergerOptions.disable();
-    enableStaticClassMerging = false;
     enableVerticalClassMerging = false;
     enableEnumUnboxing = false;
     enableUninstantiatedTypeOptimization = false;
@@ -238,7 +236,6 @@
   public boolean enableFieldAssignmentTracker = true;
   public boolean enableFieldBitAccessAnalysis =
       System.getProperty("com.android.tools.r8.fieldBitAccessAnalysis") != null;
-  public boolean enableStaticClassMerging = true;
   public boolean enableVerticalClassMerging = true;
   public boolean enableArgumentRemoval = true;
   public boolean enableUnusedInterfaceRemoval = true;
@@ -1263,9 +1260,6 @@
     public BiConsumer<DexItemFactory, HorizontallyMergedLambdaClasses>
         horizontallyMergedLambdaClassesConsumer = ConsumerUtils.emptyBiConsumer();
 
-    public BiConsumer<DexItemFactory, StaticallyMergedClasses> staticallyMergedClassesConsumer =
-        ConsumerUtils.emptyBiConsumer();
-
     public BiConsumer<DexItemFactory, EnumDataMap> unboxedEnumsConsumer =
         ConsumerUtils.emptyBiConsumer();
 
@@ -1318,6 +1312,7 @@
     public boolean forceNameReflectionOptimization = false;
     public boolean enableNarrowAndWideningingChecksInD8 = false;
     public Consumer<IRCode> irModifier = null;
+    public Consumer<IRCode> inlineeIrModifier = null;
     public int basicBlockMuncherIterationLimit = NO_LIMIT;
     public boolean dontReportFailingCheckDiscarded = false;
     public PrintStream whyAreYouNotInliningConsumer = System.out;
@@ -1895,4 +1890,12 @@
   public boolean canHaveZipFileWithMissingCloseableBug() {
     return isGeneratingClassFiles() || minApiLevel < AndroidApiLevel.K.getLevel();
   }
+
+  // Some versions of Dalvik had a bug where a switch with a MAX_INT key would still go to
+  // the default case when switching on the value MAX_INT.
+  //
+  // See b/177790310.
+  public boolean canHaveSwitchMaxIntBug() {
+    return isGeneratingDex() && minApiLevel < AndroidApiLevel.K.getLevel();
+  }
 }
diff --git a/src/test/java/com/android/tools/r8/NoStaticClassMerging.java b/src/test/java/com/android/tools/r8/AssumeNoClassInitializationSideEffects.java
similarity index 85%
rename from src/test/java/com/android/tools/r8/NoStaticClassMerging.java
rename to src/test/java/com/android/tools/r8/AssumeNoClassInitializationSideEffects.java
index b97416f..bfe15ee 100644
--- a/src/test/java/com/android/tools/r8/NoStaticClassMerging.java
+++ b/src/test/java/com/android/tools/r8/AssumeNoClassInitializationSideEffects.java
@@ -1,11 +1,10 @@
 // Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
-
 package com.android.tools.r8;
 
 import java.lang.annotation.ElementType;
 import java.lang.annotation.Target;
 
 @Target({ElementType.TYPE})
-public @interface NoStaticClassMerging {}
+public @interface AssumeNoClassInitializationSideEffects {}
diff --git a/src/test/java/com/android/tools/r8/NoStaticClassMerging.java b/src/test/java/com/android/tools/r8/AssumeNotNull.java
similarity index 79%
copy from src/test/java/com/android/tools/r8/NoStaticClassMerging.java
copy to src/test/java/com/android/tools/r8/AssumeNotNull.java
index b97416f..2c086a4 100644
--- a/src/test/java/com/android/tools/r8/NoStaticClassMerging.java
+++ b/src/test/java/com/android/tools/r8/AssumeNotNull.java
@@ -1,11 +1,10 @@
 // Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
-
 package com.android.tools.r8;
 
 import java.lang.annotation.ElementType;
 import java.lang.annotation.Target;
 
-@Target({ElementType.TYPE})
-public @interface NoStaticClassMerging {}
+@Target({ElementType.FIELD, ElementType.METHOD})
+public @interface AssumeNotNull {}
diff --git a/src/test/java/com/android/tools/r8/R8RunArtTestsTest.java b/src/test/java/com/android/tools/r8/R8RunArtTestsTest.java
index f22a173..f3cbda6 100644
--- a/src/test/java/com/android/tools/r8/R8RunArtTestsTest.java
+++ b/src/test/java/com/android/tools/r8/R8RunArtTestsTest.java
@@ -737,19 +737,8 @@
               TestCondition.match(TestCondition.runtimes(DexVm.Version.V4_0_4)))
           // Illegal class flags in Dalvik 4.0.4.
           .put("121-modifiers", TestCondition.match(TestCondition.runtimes(DexVm.Version.V4_0_4)))
-          // Switch regression still present in Dalvik 4.0.4.
-          .put(
-              "095-switch-MAX_INT",
-              TestCondition.match(
-                  TestCondition.tools(DexTool.NONE),
-                  TestCondition.compilers(CompilerUnderTest.D8_AFTER_R8CF),
-                  TestCondition.runtimes(DexVm.Version.V4_0_4)))
-          .put(
-              "095-switch-MAX_INT",
-              TestCondition.match(
-                  TestCondition.tools(DexTool.NONE),
-                  TestCondition.compilers(CompilerUnderTest.D8),
-                  TestCondition.runtimes(DexVm.Version.V4_0_4)))
+          // Switch regression still present in the DEX code produced by DX on Dalvik 4.0.4.
+          // The fix for b/177790310 is not applied for DEX input (when merging).
           .put(
               "095-switch-MAX_INT",
               TestCondition.match(
diff --git a/src/test/java/com/android/tools/r8/R8TestBuilder.java b/src/test/java/com/android/tools/r8/R8TestBuilder.java
index 909d45e..83313a3 100644
--- a/src/test/java/com/android/tools/r8/R8TestBuilder.java
+++ b/src/test/java/com/android/tools/r8/R8TestBuilder.java
@@ -17,7 +17,6 @@
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.shaking.CollectingGraphConsumer;
 import com.android.tools.r8.shaking.NoHorizontalClassMergingRule;
-import com.android.tools.r8.shaking.NoStaticClassMergingRule;
 import com.android.tools.r8.shaking.NoUnusedInterfaceRemovalRule;
 import com.android.tools.r8.shaking.NoVerticalClassMergingRule;
 import com.android.tools.r8.shaking.ProguardConfiguration;
@@ -335,6 +334,34 @@
         "-alwaysinline class * { @" + annotationPackageName + ".AlwaysInline *; }");
   }
 
+  public T enableAssumeNotNullAnnotations() {
+    return addAssumeNotNullAnnotation()
+        .enableAssumeNotNullAnnotations(AssumeNotNull.class.getPackage().getName());
+  }
+
+  public T enableAssumeNotNullAnnotations(String annotationPackageName) {
+    return addInternalKeepRules(
+        "-assumevalues class * {",
+        "  @" + annotationPackageName + ".AssumeNotNull *** * return 1;",
+        "  @" + annotationPackageName + ".AssumeNotNull *** *(...) return 1;",
+        "}");
+  }
+
+  public T enableAssumeNoClassInitializationSideEffectsAnnotations() {
+    return addAssumeNoClassInitializationSideEffectsAnnotation()
+        .enableAssumeNoClassInitializationSideEffectsAnnotations(
+            AssumeNoClassInitializationSideEffects.class.getPackage().getName());
+  }
+
+  public T enableAssumeNoClassInitializationSideEffectsAnnotations(String annotationPackageName) {
+    return addInternalKeepRules(
+        "-assumenosideeffects @"
+            + annotationPackageName
+            + ".AssumeNoClassInitializationSideEffects class * {",
+        "  void <clinit>();",
+        "}");
+  }
+
   public T enableAssumeNoSideEffectsAnnotations() {
     return addAssumeNoSideEffectsAnnotations()
         .enableAssumeNoSideEffectsAnnotations(AssumeNoSideEffects.class.getPackage().getName());
@@ -342,9 +369,9 @@
 
   public T enableAssumeNoSideEffectsAnnotations(String annotationPackageName) {
     return addInternalKeepRules(
-        "-assumenosideeffects class * { @"
-            + annotationPackageName
-            + ".AssumeNoSideEffects <methods>; }");
+        "-assumenosideeffects class * {",
+        "  @" + annotationPackageName + ".AssumeNoSideEffects <methods>;",
+        "}");
   }
 
   public T enableInliningAnnotations() {
@@ -435,7 +462,7 @@
   }
 
   public T enableNoHorizontalClassMergingAnnotations() {
-    return addProgramClasses(NoHorizontalClassMerging.class)
+    return addNoHorizontalClassMergingAnnotations()
         .addInternalMatchInterfaceRule(
             NoHorizontalClassMergingRule.RULE_NAME, NoHorizontalClassMerging.class);
   }
@@ -444,12 +471,6 @@
     return addInternalKeepRules("-nohorizontalclassmerging class " + clazz);
   }
 
-  public T enableNoStaticClassMergingAnnotations() {
-    return addNoStaticClassMergingAnnotations()
-        .addInternalMatchInterfaceRule(
-            NoStaticClassMergingRule.RULE_NAME, NoStaticClassMerging.class);
-  }
-
   public T enableMemberValuePropagationAnnotations() {
     return enableMemberValuePropagationAnnotations(true);
   }
diff --git a/src/test/java/com/android/tools/r8/TestBase.java b/src/test/java/com/android/tools/r8/TestBase.java
index f6846b4..b2945dc 100644
--- a/src/test/java/com/android/tools/r8/TestBase.java
+++ b/src/test/java/com/android/tools/r8/TestBase.java
@@ -47,7 +47,7 @@
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.shaking.EnqueuerFactory;
 import com.android.tools.r8.shaking.MainDexClasses;
-import com.android.tools.r8.shaking.NoStaticClassMergingRule;
+import com.android.tools.r8.shaking.NoHorizontalClassMergingRule;
 import com.android.tools.r8.shaking.NoVerticalClassMergingRule;
 import com.android.tools.r8.shaking.ProguardClassNameList;
 import com.android.tools.r8.shaking.ProguardConfiguration;
@@ -58,8 +58,7 @@
 import com.android.tools.r8.shaking.ProguardMemberRule;
 import com.android.tools.r8.shaking.ProguardMemberType;
 import com.android.tools.r8.shaking.ProguardTypeMatcher;
-import com.android.tools.r8.shaking.RootSetBuilder;
-import com.android.tools.r8.shaking.RootSetBuilder.RootSet;
+import com.android.tools.r8.shaking.RootSetUtils.RootSet;
 import com.android.tools.r8.shaking.serviceloader.ServiceLoaderMultipleTest.Greeter;
 import com.android.tools.r8.transformers.ClassFileTransformer;
 import com.android.tools.r8.utils.AndroidApiLevel;
@@ -763,9 +762,9 @@
     ExecutorService executor = Executors.newSingleThreadExecutor();
     SubtypingInfo subtypingInfo = new SubtypingInfo(appView);
     RootSet rootSet =
-        new RootSetBuilder(
+        RootSet.builder(
                 appView, subtypingInfo, appView.options().getProguardConfiguration().getRules())
-            .run(executor);
+            .build(executor);
     appView.setRootSet(rootSet);
     AppInfoWithLiveness appInfoWithLiveness =
         EnqueuerFactory.createForInitialTreeShaking(appView, executor, subtypingInfo)
@@ -1128,8 +1127,9 @@
   }
 
   @Deprecated
-  public static String noStaticClassMergingRule() {
-    return matchInterfaceRule(NoStaticClassMergingRule.RULE_NAME, NoStaticClassMerging.class);
+  public static String noHorizontalClassMerging() {
+    return matchInterfaceRule(
+        NoHorizontalClassMergingRule.RULE_NAME, NoHorizontalClassMerging.class);
   }
 
   /**
diff --git a/src/test/java/com/android/tools/r8/TestBuilder.java b/src/test/java/com/android/tools/r8/TestBuilder.java
index 8534d9f..3d33673 100644
--- a/src/test/java/com/android/tools/r8/TestBuilder.java
+++ b/src/test/java/com/android/tools/r8/TestBuilder.java
@@ -176,7 +176,6 @@
         NeverInline.class,
         NoVerticalClassMerging.class,
         NoHorizontalClassMerging.class,
-        NoStaticClassMerging.class,
         NeverPropagateValue.class);
   }
 
diff --git a/src/test/java/com/android/tools/r8/TestCompilerBuilder.java b/src/test/java/com/android/tools/r8/TestCompilerBuilder.java
index 79f00e2..b7ca275 100644
--- a/src/test/java/com/android/tools/r8/TestCompilerBuilder.java
+++ b/src/test/java/com/android/tools/r8/TestCompilerBuilder.java
@@ -21,7 +21,6 @@
 import com.android.tools.r8.utils.codeinspector.EnumUnboxingInspector;
 import com.android.tools.r8.utils.codeinspector.HorizontallyMergedClassesInspector;
 import com.android.tools.r8.utils.codeinspector.HorizontallyMergedLambdaClassesInspector;
-import com.android.tools.r8.utils.codeinspector.StaticallyMergedClassesInspector;
 import com.android.tools.r8.utils.codeinspector.VerticallyMergedClassesInspector;
 import com.google.common.base.Suppliers;
 import java.io.ByteArrayOutputStream;
@@ -154,17 +153,6 @@
                             dexItemFactory, horizontallyMergedLambdaClasses))));
   }
 
-  public T addStaticallyMergedClassesInspector(
-      Consumer<StaticallyMergedClassesInspector> inspector) {
-    return addOptionsModification(
-        options ->
-            options.testing.staticallyMergedClassesConsumer =
-                ((dexItemFactory, staticallyMergedClasses) ->
-                    inspector.accept(
-                        new StaticallyMergedClassesInspector(
-                            dexItemFactory, staticallyMergedClasses))));
-  }
-
   public T addVerticallyMergedClassesInspector(
       Consumer<VerticallyMergedClassesInspector> inspector) {
     return addOptionsModification(
diff --git a/src/test/java/com/android/tools/r8/TestShrinkerBuilder.java b/src/test/java/com/android/tools/r8/TestShrinkerBuilder.java
index b28a51f..362941b 100644
--- a/src/test/java/com/android/tools/r8/TestShrinkerBuilder.java
+++ b/src/test/java/com/android/tools/r8/TestShrinkerBuilder.java
@@ -460,6 +460,14 @@
     return addTestingAnnotation(AlwaysInline.class);
   }
 
+  public final T addAssumeNotNullAnnotation() {
+    return addTestingAnnotation(AssumeNotNull.class);
+  }
+
+  public final T addAssumeNoClassInitializationSideEffectsAnnotation() {
+    return addTestingAnnotation(AssumeNoClassInitializationSideEffects.class);
+  }
+
   public final T addAssumeNoSideEffectsAnnotations() {
     return addTestingAnnotation(AssumeNoSideEffects.class);
   }
@@ -504,10 +512,6 @@
     return addTestingAnnotation(NoHorizontalClassMerging.class);
   }
 
-  public final T addNoStaticClassMergingAnnotations() {
-    return addTestingAnnotation(NoStaticClassMerging.class);
-  }
-
   public final T addNoUnusedInterfaceRemovalAnnotations() {
     return addTestingAnnotation(NoUnusedInterfaceRemoval.class);
   }
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontalstatic/HorizontalClassMergerSynchronizedMethodTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontalstatic/HorizontalClassMergerSynchronizedMethodTest.java
index 5f4945e..533cb86 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontalstatic/HorizontalClassMergerSynchronizedMethodTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontalstatic/HorizontalClassMergerSynchronizedMethodTest.java
@@ -8,7 +8,6 @@
 import static org.hamcrest.MatcherAssert.assertThat;
 
 import com.android.tools.r8.CompilationFailedException;
-import com.android.tools.r8.NoStaticClassMerging;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
@@ -53,7 +52,11 @@
     testForR8(parameters.getBackend())
         .addInnerClasses(HorizontalClassMergerSynchronizedMethodTest.class)
         .addKeepMainRule(Main.class)
-        .enableNoStaticClassMergingAnnotations()
+        .addHorizontallyMergedClassesInspector(
+            inspector ->
+                inspector
+                    .assertClassesNotMerged(LockOne.class, LockTwo.class, LockThree.class)
+                    .assertMergedInto(AcquireThree.class, AcquireOne.class))
         .setMinApi(parameters.getApiLevel())
         .compile()
         .run(parameters.getRuntime(), Main.class)
@@ -66,7 +69,7 @@
     void action();
   }
 
-  // Will be merged with LockTwo.
+  // Must not be merged with LockTwo or LockThree.
   static class LockOne {
 
     static synchronized void acquire(I c) {
@@ -74,6 +77,7 @@
     }
   }
 
+  // Must not be merged with LockOne or LockThree.
   public static class LockTwo {
 
     static synchronized void acquire(I c) {
@@ -83,7 +87,7 @@
     }
   }
 
-  @NoStaticClassMerging
+  // Must not be merged with LockOne or LockTwo.
   public static class LockThree {
 
     static synchronized void acquire(I c) {
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontalstatic/StaticClassMergerInterfaceTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontalstatic/StaticClassMergerInterfaceTest.java
index 3b0b12f..53f5ae2 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontalstatic/StaticClassMergerInterfaceTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontalstatic/StaticClassMergerInterfaceTest.java
@@ -4,17 +4,17 @@
 
 package com.android.tools.r8.classmerging.horizontalstatic;
 
+import static com.android.tools.r8.synthesis.SyntheticItemsTestUtils.syntheticCompanionClass;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
 import static org.hamcrest.MatcherAssert.assertThat;
-import static org.hamcrest.core.IsNot.not;
 
-import com.android.tools.r8.NeverClassInline;
 import com.android.tools.r8.NeverInline;
 import com.android.tools.r8.TestBase;
-import com.android.tools.r8.ToolHelper;
-import com.android.tools.r8.ToolHelper.DexVm.Version;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.utils.StringUtils;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.HorizontallyMergedClassesInspector;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
@@ -23,82 +23,78 @@
 @RunWith(Parameterized.class)
 public class StaticClassMergerInterfaceTest extends TestBase {
 
-  private final Backend backend;
+  private final TestParameters parameters;
 
-  @Parameters(name = "Backend: {0}")
-  public static Backend[] data() {
-    return ToolHelper.getBackends();
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
   }
 
-  public StaticClassMergerInterfaceTest(Backend backend) {
-    this.backend = backend;
+  public StaticClassMergerInterfaceTest(TestParameters parameters) {
+    this.parameters = parameters;
   }
 
   @Test
   public void test() throws Exception {
-    String expectedOutput = StringUtils.lines("In A.a()", "In B.b()", "In C.c()");
+    String expectedOutput = StringUtils.lines("In I.a()", "In J.b()", "In A.c()");
 
     CodeInspector inspector =
-        testForR8(backend)
-            .addInnerClasses(StaticClassMergerInterfaceTest.class)
+        testForR8(parameters.getBackend())
+            .addInnerClasses(getClass())
             .addKeepMainRule(TestClass.class)
-            .addKeepRules("-dontobfuscate")
+            // TODO(b/173990042): Extend horizontal class merging to interfaces.
+            .addHorizontallyMergedClassesInspector(
+                HorizontallyMergedClassesInspector::assertNoClassesMerged)
             .enableInliningAnnotations()
-            .enableNeverClassInliningAnnotations()
-            .run(TestClass.class)
+            .setMinApi(parameters.getApiLevel())
+            .run(parameters.getRuntime(), TestClass.class)
             .assertSuccessWithOutput(expectedOutput)
             .inspector();
 
-    // Check that A has not been merged into B. The static class merger visits classes in alpha-
-    // betical order. By the time A is processed, there is no merge representative and A is not
-    // a valid merge representative itself, because it is an interface.
-    if (ToolHelper.getDexVm().getVersion().isNewerThan(Version.V6_0_1) || backend == Backend.CF) {
-      assertThat(inspector.clazz(A.class), isPresent());
+    // We do not allow horizontal class merging of interfaces and classes. Therefore, A should
+    // remain in the output.
+    assertThat(inspector.clazz(A.class), isPresent());
+
+    // TODO(b/173990042): I and J should be merged.
+    if (parameters.canUseDefaultAndStaticInterfaceMethods()) {
+      assertThat(inspector.clazz(I.class), isPresent());
+      assertThat(inspector.clazz(J.class), isPresent());
     } else {
-      assertThat(inspector.clazz(A.class.getTypeName() + "$-CC"), isPresent());
+      assertThat(inspector.clazz(syntheticCompanionClass(I.class)), isPresent());
+      assertThat(inspector.clazz(syntheticCompanionClass(I.class)), isPresent());
     }
-
-    // By the time B is processed, there is no merge representative, so it should be present.
-    assertThat(inspector.clazz(B.class), isPresent());
-
-    // By the time C is processed, B should be merge candidate. Therefore, we should allow C.c() to
-    // be moved to B *although C is an interface*.
-    assertThat(inspector.clazz(C.class), not(isPresent()));
   }
 
   static class TestClass {
 
     public static void main(String[] args) {
-      A.a();
-      B.b();
-      C.c();
+      I.a();
+      J.b();
+      A.c();
     }
   }
 
-  @NeverClassInline
-  interface A {
+  interface I {
 
     @NeverInline
     static void a() {
-      System.out.println("In A.a()");
+      System.out.println("In I.a()");
     }
   }
 
-  @NeverClassInline
-  static class B {
+  interface J {
 
     @NeverInline
     static void b() {
-      System.out.println("In B.b()");
+      System.out.println("In J.b()");
     }
   }
 
-  @NeverClassInline
-  interface C {
+  static class A {
 
     @NeverInline
     static void c() {
-      System.out.println("In C.c()");
+      System.out.println("In A.c()");
     }
   }
 }
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontalstatic/StaticClassMergerTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontalstatic/StaticClassMergerTest.java
index 7d6aa4a..12f1b94 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontalstatic/StaticClassMergerTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontalstatic/StaticClassMergerTest.java
@@ -6,14 +6,12 @@
 
 import static org.junit.Assert.assertEquals;
 
-import com.android.tools.r8.AssumeMayHaveSideEffects;
 import com.android.tools.r8.CompilationFailedException;
 import com.android.tools.r8.NeverInline;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.utils.StringUtils;
-import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.android.tools.r8.utils.codeinspector.FoundClassSubject;
 import java.io.IOException;
 import java.util.List;
@@ -29,32 +27,30 @@
 
   static class StaticMergeCandidateA {
 
-    @AssumeMayHaveSideEffects
     @NeverInline
-    public static String m() {
-      return "StaticMergeCandidateA.m()";
+    public static void m() {
+      System.out.println("StaticMergeCandidateA.m()");
     }
   }
 
   static class StaticMergeCandidateB {
 
-    @AssumeMayHaveSideEffects
     @NeverInline
-    public static String m() {
-      return "StaticMergeCandidateB.m()";
+    public static void m() {
+      System.out.println("StaticMergeCandidateB.m()");
     }
   }
 
   static class TestClass {
 
     public static void main(String[] args) {
-      System.out.println(StaticMergeCandidateA.m());
-      System.out.print(StaticMergeCandidateB.m());
+      StaticMergeCandidateA.m();
+      StaticMergeCandidateB.m();
     }
   }
 
   private final String EXPECTED =
-      StringUtils.joinLines("StaticMergeCandidateA.m()", "StaticMergeCandidateB.m()");
+      StringUtils.lines("StaticMergeCandidateA.m()", "StaticMergeCandidateB.m()");
 
   private final TestParameters parameters;
 
@@ -77,27 +73,29 @@
 
   @Test
   public void testStaticClassIsRemoved() throws Exception {
-    CodeInspector inspector =
-        testForR8(parameters.getBackend())
-            .addInnerClasses(StaticClassMergerTest.class)
-            .addKeepMainRule(TestClass.class)
-            .noMinification()
-            .enableInliningAnnotations()
-            .enableSideEffectAnnotations()
-            .setMinApi(parameters.getApiLevel())
-            .run(parameters.getRuntime(), TestClass.class)
-            .assertSuccessWithOutput(EXPECTED)
-            .inspector();
+    testForR8(parameters.getBackend())
+        .addInnerClasses(getClass())
+        .addKeepMainRule(TestClass.class)
+        .addHorizontallyMergedClassesInspector(
+            inspector ->
+                inspector.assertMergedInto(
+                    StaticMergeCandidateB.class, StaticMergeCandidateA.class))
+        .enableInliningAnnotations()
+        .setMinApi(parameters.getApiLevel())
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutput(EXPECTED)
+        .inspect(
+            inspector -> {
+              // Check that one of the two static merge candidates has been removed
+              List<FoundClassSubject> classes =
+                  inspector.allClasses().stream()
+                      .filter(clazz -> clazz.getOriginalName().contains("StaticMergeCandidate"))
+                      .collect(Collectors.toList());
+              assertEquals(1, classes.size());
 
-    // Check that one of the two static merge candidates has been removed
-    List<FoundClassSubject> classes =
-        inspector.allClasses().stream()
-            .filter(clazz -> clazz.getOriginalName().contains("StaticMergeCandidate"))
-            .collect(Collectors.toList());
-    assertEquals(1, classes.size());
-
-    // Check that the remaining static merge candidate has two methods.
-    FoundClassSubject remaining = classes.get(0);
-    assertEquals(2, remaining.allMethods().size());
+              // Check that the remaining static merge candidate has two methods.
+              FoundClassSubject remaining = classes.get(0);
+              assertEquals(2, remaining.allMethods().size());
+            });
   }
 }
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontalstatic/StaticClassMergerVisibilityTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontalstatic/StaticClassMergerVisibilityTest.java
index 9d545e5..189ac21 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontalstatic/StaticClassMergerVisibilityTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontalstatic/StaticClassMergerVisibilityTest.java
@@ -5,17 +5,15 @@
 package com.android.tools.r8.classmerging.horizontalstatic;
 
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPublic;
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
 
 import com.android.tools.r8.NeverInline;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
-import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
@@ -37,38 +35,32 @@
 
   @Test
   public void testStaticClassIsRemoved() throws Exception {
-    CodeInspector inspector =
-        testForR8(parameters.getBackend())
-            .addInnerClasses(StaticClassMergerVisibilityTest.class)
-            .addKeepMainRule(Main.class)
-            .enableInliningAnnotations()
-            .setMinApi(parameters.getApiLevel())
-            .run(parameters.getRuntime(), Main.class)
-            .assertSuccessWithOutputLines("A.print()", "B.print()", "C.print()", "D.print()")
-            .inspector();
-
-    // The global group will have one representative, which is C.
-    ClassSubject clazzC = inspector.clazz(C.class);
-    assertThat(clazzC, isPresent());
-    assertEquals(1, clazzC.allMethods().size());
-
-    // The package group will be merged into one of A, B or C.
-    assertExactlyOneIsPresent(
-        inspector.clazz(A.class), inspector.clazz(B.class), inspector.clazz(D.class));
+    testForR8(parameters.getBackend())
+        .addInnerClasses(StaticClassMergerVisibilityTest.class)
+        .addKeepMainRule(Main.class)
+        .addHorizontallyMergedClassesInspector(
+            inspector ->
+                inspector
+                    .assertMergedInto(A.class, D.class)
+                    .assertMergedInto(B.class, D.class)
+                    .assertMergedInto(C.class, D.class))
+        .enableInliningAnnotations()
+        .setMinApi(parameters.getApiLevel())
+        .noMinification()
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines("A.print()", "B.print()", "C.print()", "D.print()")
+        .inspect(
+            inspector -> {
+              // All classes are merged into D.
+              ClassSubject clazzD = inspector.clazz(D.class);
+              assertThat(clazzD, isPresent());
+              assertThat(clazzD, isPublic());
+              // D now has 5 methods (there is a synthetic access bridge for the private A.print()).
+              assertEquals(5, clazzD.allMethods().size());
+            });
   }
 
-  private void assertExactlyOneIsPresent(ClassSubject... subjects) {
-    boolean seenPresent = false;
-    for (ClassSubject subject : subjects) {
-      if (subject.isPresent()) {
-        assertFalse(seenPresent);
-        seenPresent = true;
-      }
-    }
-    assertTrue(seenPresent);
-  }
-
-  // Will be merged into the package group.
+  // Will be merged into D.
   private static class A {
     @NeverInline
     private static void print() {
@@ -76,7 +68,7 @@
     }
   }
 
-  // Will be merged into the package group.
+  // Will be merged into D.
   static class B {
     @NeverInline
     static void print() {
@@ -84,7 +76,7 @@
     }
   }
 
-  // Will be merged into global group
+  // Will be merged into D
   public static class C {
     @NeverInline
     public static void print() {
@@ -92,7 +84,6 @@
     }
   }
 
-  // Will be merged into the package group.
   protected static class D {
     @NeverInline
     protected static void print() {
diff --git a/src/test/java/com/android/tools/r8/desugar/nest/NestBasedAccessToNativeMethodTest.java b/src/test/java/com/android/tools/r8/desugar/nest/NestBasedAccessToNativeMethodTest.java
new file mode 100644
index 0000000..c8f709b
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/nest/NestBasedAccessToNativeMethodTest.java
@@ -0,0 +1,85 @@
+// 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.desugar.nest;
+
+import static com.android.tools.r8.TestRuntime.CfVm.JDK11;
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.desugar.nest.NestBasedAccessToNativeMethodTest.Main.Inner;
+import com.google.common.collect.ImmutableList;
+import java.io.IOException;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class NestBasedAccessToNativeMethodTest extends TestBase {
+
+  private final TestParameters parameters;
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters()
+        .withCfRuntimesStartingFromIncluding(JDK11)
+        .withDexRuntimes()
+        .withAllApiLevels()
+        .build();
+  }
+
+  public NestBasedAccessToNativeMethodTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testD8() throws Exception {
+    assumeTrue(parameters.isDexRuntime());
+    testForD8()
+        .addProgramClassFileData(getProgramClassFileData())
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines("Hello from native");
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    testForR8(parameters.getBackend())
+        .addProgramClassFileData(getProgramClassFileData())
+        .addKeepMainRule(Main.class)
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines("Hello from native");
+  }
+
+  private List<byte[]> getProgramClassFileData() throws IOException, NoSuchMethodException {
+    return ImmutableList.of(
+        transformer(Main.class).setNest(Main.class, Inner.class).transform(),
+        transformer(Inner.class)
+            .setPrivate(Inner.class.getDeclaredMethod("goingToBePrivate"))
+            .setNest(Main.class, Inner.class)
+            .transform());
+  }
+
+  static class Main {
+
+    public static void main(String[] args) {
+      try {
+        Inner.goingToBePrivate();
+      } catch (UnsatisfiedLinkError e) {
+        System.out.println("Hello from native");
+      }
+    }
+
+    static class Inner {
+
+      /*private*/ static native void goingToBePrivate();
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/dexsplitter/SplitterTestBase.java b/src/test/java/com/android/tools/r8/dexsplitter/SplitterTestBase.java
index dce9fbc..dbb1e76 100644
--- a/src/test/java/com/android/tools/r8/dexsplitter/SplitterTestBase.java
+++ b/src/test/java/com/android/tools/r8/dexsplitter/SplitterTestBase.java
@@ -127,8 +127,6 @@
       ThrowingConsumer<R8TestCompileResult, E> compileResultConsumer,
       Consumer<R8FullTestBuilder> r8TestConfigurator)
       throws IOException, CompilationFailedException, E {
-    Path featureOutput = temp.newFile("feature.zip").toPath();
-
     R8FullTestBuilder r8FullTestBuilder = testForR8(parameters.getBackend());
     if (parameters.isCfRuntime()) {
       // Compiling to jar we need to support the same way of loading code at runtime as
@@ -141,8 +139,7 @@
     r8FullTestBuilder
         .addProgramClasses(SplitRunner.class, RunInterface.class)
         .addProgramClasses(baseClasses)
-        .addFeatureSplit(
-            builder -> simpleSplitProvider(builder, featureOutput, temp, featureClasses))
+        .addFeatureSplit(featureClasses.toArray(new Class[0]))
         .addInliningAnnotations()
         .setMinApi(parameters.getApiLevel())
         .addKeepMainRule(SplitRunner.class)
@@ -153,8 +150,8 @@
     R8TestCompileResult r8TestCompileResult = r8FullTestBuilder.compile();
     compileResultConsumer.accept(r8TestCompileResult);
     Path baseOutput = r8TestCompileResult.writeToZip();
-
-    return runFeatureOnArt(toRun, baseOutput, featureOutput, parameters.getRuntime());
+    return runFeatureOnArt(
+        toRun, baseOutput, r8TestCompileResult.getFeature(0), parameters.getRuntime());
   }
 
   // Compile the passed in classes plus RunInterface and SplitRunner using R8, then split
diff --git a/src/test/java/com/android/tools/r8/dexsplitter/SyntheticDistributionTest.java b/src/test/java/com/android/tools/r8/dexsplitter/SyntheticDistributionTest.java
new file mode 100644
index 0000000..af73519
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/dexsplitter/SyntheticDistributionTest.java
@@ -0,0 +1,165 @@
+// 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.dexsplitter;
+
+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.NeverInline;
+import com.android.tools.r8.R8FullTestBuilder;
+import com.android.tools.r8.R8TestCompileResult;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.ToolHelper.ProcessResult;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.ThrowingConsumer;
+import com.android.tools.r8.utils.codeinspector.FoundClassSubject;
+import com.google.common.collect.ImmutableSet;
+import java.util.function.Consumer;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+/**
+ * We need to ensure that we distribute the synthetic items in the features where they where
+ * generated.
+ */
+@RunWith(Parameterized.class)
+public class SyntheticDistributionTest extends SplitterTestBase {
+
+  public static final String EXPECTED = StringUtils.lines("42");
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection params() {
+    return getTestParameters().withDexRuntimes().withAllApiLevels().build();
+  }
+
+  private final TestParameters parameters;
+
+  public SyntheticDistributionTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testDistribution() throws Exception {
+    assumeTrue(parameters.isDexRuntime());
+    Consumer<R8FullTestBuilder> configurator =
+        r8FullTestBuilder ->
+            r8FullTestBuilder
+                .noMinification()
+                .enableInliningAnnotations()
+                .addInliningAnnotations()
+                .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.O));
+    ThrowingConsumer<R8TestCompileResult, Exception> ensureLambdaNotInBase =
+        r8TestCompileResult -> {
+          r8TestCompileResult.inspect(
+              base ->
+                  assertFalse(base.allClasses().stream().anyMatch(FoundClassSubject::isSynthetic)),
+              feature ->
+                  assertTrue(
+                      feature.allClasses().stream().anyMatch(FoundClassSubject::isSynthetic)));
+        };
+    ProcessResult processResult =
+        testR8Splitter(
+            parameters,
+            ImmutableSet.of(BaseSuperClass.class),
+            ImmutableSet.of(FeatureClass.class, MyFunction.class),
+            FeatureClass.class,
+            ensureLambdaNotInBase,
+            configurator);
+    assertEquals(processResult.exitCode, 0);
+    assertEquals(processResult.stdout, StringUtils.lines("42foobar"));
+  }
+
+  @Test
+  public void testNoMergingAcrossBoundaries() throws Exception {
+    R8TestCompileResult compileResult =
+        testForR8(parameters.getBackend())
+            .addProgramClasses(BaseSuperClass.class, MyFunction.class)
+            .addFeatureSplitRuntime()
+            .addFeatureSplit(FeatureClass.class)
+            .addFeatureSplit(Feature2Class.class)
+            .addKeepFeatureMainRules(BaseSuperClass.class, FeatureClass.class, Feature2Class.class)
+            .noMinification()
+            .enableInliningAnnotations()
+            .setMinApi(parameters.getApiLevel())
+            .compile();
+
+    compileResult
+        .runFeature(parameters.getRuntime(), FeatureClass.class, compileResult.getFeature(0))
+        .assertSuccessWithOutputLines("42foobar");
+
+    compileResult
+        .runFeature(parameters.getRuntime(), Feature2Class.class, compileResult.getFeature(1))
+        .assertSuccessWithOutputLines("43barfoo");
+  }
+
+  public abstract static class BaseSuperClass implements RunInterface {
+    @Override
+    public void run() {
+      System.out.println(getFromFeature());
+    }
+
+    public abstract String getFromFeature();
+  }
+
+  public interface MyFunction {
+    String apply(String s);
+  }
+
+  public static class FeatureClass extends BaseSuperClass {
+    @NeverInline
+    public static String getAString() {
+      return System.currentTimeMillis() < 2 ? "Not happening" : "foobar";
+    }
+
+    @Override
+    public void run() {
+      super.run();
+    }
+
+    @Override
+    public String getFromFeature() {
+      String s = getAString();
+      return useTheLambda(a -> a.concat(s));
+    }
+
+    @NeverInline
+    private String useTheLambda(MyFunction f) {
+      return f.apply("42");
+    }
+  }
+
+  public static class Feature2Class extends BaseSuperClass {
+    @NeverInline
+    public static String getAString() {
+      return System.currentTimeMillis() < 2 ? "Not happening" : "barfoo";
+    }
+
+    @Override
+    public void run() {
+      super.run();
+    }
+
+    @Override
+    public String getFromFeature() {
+      // The lambda is the same as in FeatureClass, but we should not share it since there is
+      // no way for Feature2Class to access code in Feature1Class (assuming either that
+      // Feature1Class is not installed of isolated splits are used).
+      String s = getAString();
+      return useTheLambda(a -> a.concat(s));
+    }
+
+    @NeverInline
+    private String useTheLambda(MyFunction f) {
+      return f.apply("43");
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/ClInitSideEffectEnumUnboxingTest.java b/src/test/java/com/android/tools/r8/enumunboxing/ClInitSideEffectEnumUnboxingTest.java
index 74ee98c..67dfbc0 100644
--- a/src/test/java/com/android/tools/r8/enumunboxing/ClInitSideEffectEnumUnboxingTest.java
+++ b/src/test/java/com/android/tools/r8/enumunboxing/ClInitSideEffectEnumUnboxingTest.java
@@ -6,7 +6,7 @@
 
 import com.android.tools.r8.NeverClassInline;
 import com.android.tools.r8.NeverInline;
-import com.android.tools.r8.NoStaticClassMerging;
+import com.android.tools.r8.NoHorizontalClassMerging;
 import com.android.tools.r8.R8TestRunResult;
 import com.android.tools.r8.TestParameters;
 import java.util.List;
@@ -43,7 +43,7 @@
             .addKeepMainRule(classToTest)
             .addKeepRules(enumKeepRules.getKeepRules())
             .enableInliningAnnotations()
-            .enableNoStaticClassMergingAnnotations()
+            .enableNoHorizontalClassMergingAnnotations()
             .enableNeverClassInliningAnnotations()
             .addOptionsModification(opt -> enableEnumOptions(opt, enumValueOptimization))
             .allowDiagnosticInfoMessages()
@@ -69,7 +69,7 @@
   }
 
   @NeverClassInline
-  @NoStaticClassMerging
+  @NoHorizontalClassMerging
   static class OtherClass {
     static {
       Switch.otherClassInit = true;
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/EnumToStringLibTest.java b/src/test/java/com/android/tools/r8/enumunboxing/EnumToStringLibTest.java
index 89395ef..b21cbd0 100644
--- a/src/test/java/com/android/tools/r8/enumunboxing/EnumToStringLibTest.java
+++ b/src/test/java/com/android/tools/r8/enumunboxing/EnumToStringLibTest.java
@@ -97,13 +97,6 @@
     return testForR8(Backend.CF)
         .addProgramClasses(ToStringLib.class, ToStringLib.LibEnum.class)
         .addKeepRules(enumKeepRules.getKeepRules())
-        // TODO(b/160535629): Work-around on some optimizations relying on $VALUES name.
-        .addKeepRules(
-            "-keep enum "
-                + ToStringLib.LibEnum.class.getName()
-                + " { static "
-                + ToStringLib.LibEnum.class.getName()
-                + "[] $VALUES; }")
         .addOptionsModification(opt -> enableEnumOptions(opt, enumValueOptimization))
         .addKeepMethodRules(
             Reference.methodFromMethod(
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/FailingMethodEnumUnboxingTest.java b/src/test/java/com/android/tools/r8/enumunboxing/FailingMethodEnumUnboxingTest.java
index 374895c..702ce3e 100644
--- a/src/test/java/com/android/tools/r8/enumunboxing/FailingMethodEnumUnboxingTest.java
+++ b/src/test/java/com/android/tools/r8/enumunboxing/FailingMethodEnumUnboxingTest.java
@@ -5,6 +5,7 @@
 
 import static junit.framework.TestCase.assertTrue;
 import static org.junit.Assert.assertEquals;
+
 import com.android.tools.r8.NeverClassInline;
 import com.android.tools.r8.NeverInline;
 import com.android.tools.r8.R8TestCompileResult;
@@ -100,11 +101,16 @@
       InstanceFieldPutObject fieldPut = new InstanceFieldPutObject();
       fieldPut.setA();
       Object obj = new Object();
-      fieldPut.e = obj;
+      put(fieldPut, obj);
       System.out.println(fieldPut.e);
       System.out.println(obj);
     }
 
+    @NeverInline
+    static void put(InstanceFieldPutObject object, Object value) {
+      object.e = value;
+    }
+
     void setA() {
       e = MyEnum.A;
     }
diff --git a/src/test/java/com/android/tools/r8/graph/MissingClassThrowingTest.java b/src/test/java/com/android/tools/r8/graph/MissingClassThrowingTest.java
index 7277139..ae244b5 100644
--- a/src/test/java/com/android/tools/r8/graph/MissingClassThrowingTest.java
+++ b/src/test/java/com/android/tools/r8/graph/MissingClassThrowingTest.java
@@ -8,7 +8,7 @@
 import static org.junit.Assert.assertEquals;
 
 import com.android.tools.r8.NeverInline;
-import com.android.tools.r8.NoStaticClassMerging;
+import com.android.tools.r8.NoHorizontalClassMerging;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
@@ -24,10 +24,10 @@
 @RunWith(Parameterized.class)
 public class MissingClassThrowingTest extends TestBase {
 
-  @NoStaticClassMerging
+  @NoHorizontalClassMerging
   public static class MissingException extends Exception {}
 
-  @NoStaticClassMerging
+  @NoHorizontalClassMerging
   public static class Program {
 
     public static final String EXPECTED_OUTPUT = "Hello world!";
@@ -76,7 +76,7 @@
                 .noMinification()
                 .noTreeShaking()
                 .enableInliningAnnotations()
-                .enableNoStaticClassMergingAnnotations()
+                .enableNoHorizontalClassMergingAnnotations()
                 .debug()
                 .compileWithExpectedDiagnostics(
                     diagnostics -> {
diff --git a/src/test/java/com/android/tools/r8/graph/invokespecial/InvokeSpecialToSubclassTest.java b/src/test/java/com/android/tools/r8/graph/invokespecial/InvokeSpecialToSubclassTest.java
index 9c44e2a..b8dd4fc 100644
--- a/src/test/java/com/android/tools/r8/graph/invokespecial/InvokeSpecialToSubclassTest.java
+++ b/src/test/java/com/android/tools/r8/graph/invokespecial/InvokeSpecialToSubclassTest.java
@@ -6,7 +6,7 @@
 
 import static com.android.tools.r8.utils.DescriptorUtils.getBinaryNameFromJavaType;
 import static org.junit.Assert.assertEquals;
-import static org.objectweb.asm.Opcodes.INVOKESPECIAL;
+import static org.objectweb.asm.Opcodes.INVOKEVIRTUAL;
 
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
@@ -45,7 +45,25 @@
         .addProgramClasses(EmptySubC.class, C.class, D.class, Main.class)
         .addProgramClassFileData(getClassBWithTransformedInvoked(holder))
         .run(parameters.getRuntime(), Main.class)
-        .assertSuccessWithOutputLines("Should not be called.");
+        .assertSuccessWithOutputLinesIf(isExpectedToSucceedWithD8(), "D")
+        .assertFailureWithErrorThatThrowsIf(!isExpectedToSucceedWithD8(), VerifyError.class);
+  }
+
+  private boolean isExpectedToSucceedWithD8() {
+    if (holder == C.class) {
+      // Should always succeed.
+      return true;
+    }
+    // TODO(b/144410139): Consider making this a compilation failure instead.
+    if (holder == EmptySubC.class && parameters.isDexRuntime()) {
+      // Fails with verification error due to "Bad type on operand stack" on the JVM.
+      //
+      // D8 replaces the invoke in B.callPrint() with an invoke-virtual to EmptySubC.print().
+      // Strictly speaking this doesn't type check, since '(C) this' in B is not a subtype of
+      // EmptySubC, but the code still runs on Art and produces 'D'.
+      return true;
+    }
+    return false;
   }
 
   @Test
@@ -56,7 +74,7 @@
         .addKeepMainRule(Main.class)
         .setMinApi(parameters.getApiLevel())
         .run(parameters.getRuntime(), Main.class)
-        .assertSuccessWithOutputLines("Should not be called.");
+        .assertSuccessWithOutputLines("D");
   }
 
   private byte[] getClassBWithTransformedInvoked(Class<?> holder) throws IOException {
@@ -65,8 +83,8 @@
             "callPrint",
             (opcode, owner, name, descriptor, isInterface, continuation) -> {
               if (name.equals("replace")) {
-                assertEquals(INVOKESPECIAL, opcode);
-                assertEquals(getBinaryNameFromJavaType(B.class.getTypeName()), owner);
+                assertEquals(INVOKEVIRTUAL, opcode);
+                assertEquals(getBinaryNameFromJavaType(C.class.getTypeName()), owner);
                 continuation.visitMethodInsn(
                     opcode,
                     getBinaryNameFromJavaType(holder.getTypeName()),
@@ -93,7 +111,7 @@
 
   public static class C extends B {
 
-    private void replace() {
+    void replace() {
       System.out.println("Should not be called.");
     }
 
@@ -108,7 +126,7 @@
   public static class D extends EmptySubC {
     @Override
     void print() {
-      System.out.println("C");
+      System.out.println("D");
     }
   }
 
diff --git a/src/test/java/com/android/tools/r8/internal/ClankDepsTest.java b/src/test/java/com/android/tools/r8/internal/ClankDepsTest.java
new file mode 100644
index 0000000..cebfab3
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/internal/ClankDepsTest.java
@@ -0,0 +1,49 @@
+// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.internal;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestDiagnosticMessages;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+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 ClankDepsTest extends TestBase {
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withNoneRuntime().build();
+  }
+
+  public ClankDepsTest(TestParameters parameters) {
+    parameters.assertNoneRuntime();
+  }
+
+  private static final Path DIR = Paths.get("third_party", "chrome", "clank_google3_prebuilt");
+  private static final Path CLASSES = DIR.resolve("classes.jar");
+  private static final Path CONFIG = DIR.resolve("proguard.txt");
+
+  @Test
+  public void test() throws Exception {
+    testForR8(Backend.DEX)
+        .addProgramFiles(CLASSES)
+        .addKeepRuleFiles(CONFIG)
+        .addDontWarn("androidx.**")
+        .addDontWarnJavax()
+        .addDontWarn("dalvik.system.VMStack")
+        .addDontWarn("zzz.com.facebook.litho.R$id")
+        .addDontWarn("com.google.android.libraries.elements.R$id")
+        .allowUnusedProguardConfigurationRules()
+        .allowUnusedDontWarnPatterns()
+        .setMinApi(AndroidApiLevel.N)
+        .compileWithExpectedDiagnostics(TestDiagnosticMessages::assertOnlyInfos);
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/InlineTest.java b/src/test/java/com/android/tools/r8/ir/InlineTest.java
index 18f15fd..73f1561 100644
--- a/src/test/java/com/android/tools/r8/ir/InlineTest.java
+++ b/src/test/java/com/android/tools/r8/ir/InlineTest.java
@@ -23,7 +23,7 @@
 import com.android.tools.r8.shaking.EnqueuerFactory;
 import com.android.tools.r8.shaking.ProguardConfiguration;
 import com.android.tools.r8.shaking.ProguardKeepRule;
-import com.android.tools.r8.shaking.RootSetBuilder;
+import com.android.tools.r8.shaking.RootSetUtils.RootSet;
 import com.android.tools.r8.smali.SmaliBuilder;
 import com.android.tools.r8.smali.SmaliBuilder.MethodSignature;
 import com.android.tools.r8.utils.InternalOptions;
@@ -68,11 +68,11 @@
     ExecutorService executorService = ThreadUtils.getExecutorService(options);
     SubtypingInfo subtypingInfo = new SubtypingInfo(appView);
     appView.setRootSet(
-        new RootSetBuilder(
+        RootSet.builder(
                 appView,
                 subtypingInfo,
                 ImmutableList.of(ProguardKeepRule.defaultKeepAllRule(unused -> {})))
-            .run(executorService));
+            .build(executorService));
     Timing timing = Timing.empty();
     Enqueuer enqueuer =
         EnqueuerFactory.createForInitialTreeShaking(appView, executorService, subtypingInfo);
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlineInstanceInitializerWithInaccessibleStaticGetTest.java b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlineInstanceInitializerWithInaccessibleStaticGetTest.java
index 26e44d5..4aaeda5 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlineInstanceInitializerWithInaccessibleStaticGetTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlineInstanceInitializerWithInaccessibleStaticGetTest.java
@@ -41,7 +41,7 @@
             ClassInlineInstanceInitializerWithInaccessibleStaticGetTestClasses.class)
         .addKeepMainRule(TestClass.class)
         .enableInliningAnnotations()
-        .enableNoStaticClassMergingAnnotations()
+        .enableNoHorizontalClassMergingAnnotations()
         .setMinApi(parameters.getApiLevel())
         .compile()
         .inspect(this::inspect)
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerDirectWithUnknownReturnTest.java b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerDirectWithUnknownReturnTest.java
new file mode 100644
index 0000000..99a169d
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerDirectWithUnknownReturnTest.java
@@ -0,0 +1,77 @@
+// 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.ir.optimize.classinliner;
+
+import static com.android.tools.r8.DiagnosticsMatcher.diagnosticMessage;
+import static org.hamcrest.CoreMatchers.containsString;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.NeverSingleCallerInline;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class ClassInlinerDirectWithUnknownReturnTest extends TestBase {
+
+  private final TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public ClassInlinerDirectWithUnknownReturnTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test(expected = CompilationFailedException.class)
+  public void testR8() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(getClass())
+        .addKeepMainRule(Main.class)
+        .enableNeverSingleCallerInlineAnnotations()
+        .setMinApi(parameters.getApiLevel())
+        .compileWithExpectedDiagnostics(
+            diagnostics -> {
+              diagnostics.assertErrorsMatch(
+                  diagnosticMessage(
+                      containsString(
+                          "Unexpected non-trivial phi in method eligible for class inlining")));
+            });
+  }
+
+  public static class A {
+
+    public int number;
+
+    @NeverSingleCallerInline
+    public A abs() {
+      if (number > 0) {
+        return this;
+      }
+      return new A();
+    }
+
+    @Override
+    @NeverSingleCallerInline
+    public String toString() {
+      return "Hello World " + number;
+    }
+  }
+
+  public static class Main {
+
+    public static void main(String[] args) {
+      A a = new A();
+      a.number = args.length;
+      System.out.println(a.abs().toString());
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerPhiDirectUserAfterInlineTest.java b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerPhiDirectUserAfterInlineTest.java
new file mode 100644
index 0000000..1740c8a
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerPhiDirectUserAfterInlineTest.java
@@ -0,0 +1,241 @@
+// 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.ir.optimize.classinliner;
+
+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.assertNotNull;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.ir.code.Argument;
+import com.android.tools.r8.ir.code.BasicBlock;
+import com.android.tools.r8.ir.code.IRCode;
+import com.android.tools.r8.ir.code.InstanceGet;
+import com.android.tools.r8.ir.code.Phi;
+import com.android.tools.r8.ir.code.Phi.RegisterReadType;
+import com.android.tools.r8.ir.code.Value;
+import com.android.tools.r8.ir.optimize.Inliner.Reason;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+// This is a reproduction of b/178608910.
+@RunWith(Parameterized.class)
+public class ClassInlinerPhiDirectUserAfterInlineTest extends TestBase {
+
+  private final TestParameters parameters;
+  private final List<String> EXPECTED = ImmutableList.of("0", "A::baz");
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withDexRuntimes().withAllApiLevels().build();
+  }
+
+  public ClassInlinerPhiDirectUserAfterInlineTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testNoClassInlining() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(getClass())
+        .setMinApi(parameters.getApiLevel())
+        .addKeepMainRule(Main.class)
+        .addOptionsModification(
+            options -> {
+              options.testing.validInliningReasons = ImmutableSet.of(Reason.FORCE);
+              // Here we modify the IR when it is processed normally.
+              options.testing.irModifier = this::modifyIr;
+              options.enableClassInlining = false;
+            })
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines(EXPECTED);
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(getClass())
+        .setMinApi(parameters.getApiLevel())
+        .addKeepMainRule(Main.class)
+        .addOptionsModification(
+            options -> {
+              options.testing.validInliningReasons = ImmutableSet.of(Reason.FORCE);
+              options.testing.inlineeIrModifier = this::modifyIr;
+            })
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines(EXPECTED)
+        .inspect(
+            inspector -> {
+              // Assert that the A has been class-inlined into the caller.
+              ClassSubject aSubject = inspector.clazz(A.class);
+              assertThat(aSubject, not(isPresent()));
+            });
+  }
+
+  private void modifyIr(IRCode irCode) {
+    if (irCode.context().getReference().qualifiedName().equals(A.class.getTypeName() + ".foo")) {
+      assertEquals(7, irCode.blocks.size());
+      // This is the code that we expect
+      // <pre>
+      // blocks:
+      // block 0, pred-counts: 0, succ-count: 1, filled: true, sealed: true
+      // predecessors: -
+      // successors: 1  (no try/catch successors)
+      // no phis
+      //  #0:foo;0:main: -1: Argument             v3 <-
+      //               : -1: ConstNumber          v4(0) <-  0 (INT)
+      //               : -1: Goto                 block 1
+      //
+      // block 1, pred-counts: 2, succ-count: 2, filled: true, sealed: true
+      // predecessors: 0 5
+      // successors: 2 3  (no try/catch successors)
+      // v7 <- phi(v4(0), v13) : INT
+      //  #0:foo;0:main: -1: InstanceGet          v6 <- v3; field: int
+      // com.android.tools.r8.ir.optimize.classinliner
+      //    .ClassInlinerPhiDirectUserAfterInlineTest$A.number
+      //               : -1: If                   v6 EQZ block 2 (fallthrough 3)
+      //
+      // block 2, pred-counts: 1, succ-count: 0, filled: true, sealed: true
+      // predecessors: 1
+      // successors: -
+      // no phis
+      //  #0:foo;0:main: -1: Invoke-Virtual       v3, v7; method: void
+      // com.android.tools.r8.ir.optimize.classinliner
+      //  .ClassInlinerPhiDirectUserAfterInlineTest$A.bar(int)
+      //               : -1: Return
+      //
+      // block 3, pred-counts: 1, succ-count: 1, filled: true, sealed: true
+      // predecessors: 1
+      // successors: 4  (no try/catch successors)
+      // no phis
+      //  #0:foo;0:main: -1: ConstNumber          v8(2) <-  2 (INT)
+      //               : -1: Add                  v9 <- v7, v8(2)
+      //               : -1: Goto                 block 4
+      //
+      // block 4, pred-counts: 2, succ-count: 2, filled: true, sealed: true
+      // predecessors: 3 6
+      // successors: 5 6  (no try/catch successors)
+      // v13 <- phi(v9, v15) : INT
+      //  #0:foo;0:main: -1: InstanceGet          v11 <- v3; field: int
+      // com.android.tools.r8.ir.optimize.classinliner
+      //  .ClassInlinerPhiDirectUserAfterInlineTest$A.number
+      //               : -1: ConstNumber          v12(10) <-  10 (INT)
+      //               : -1: If                   v11, v12(10) LE  block 5
+      // (fallthrough 6)
+      //
+      // block 5, pred-counts: 1, succ-count: 1, filled: true, sealed: true
+      // predecessors: 4
+      // successors: 1  (no try/catch successors)
+      // no phis
+      //  #0:foo;0:main: -1: Goto                 block 1
+      //
+      // block 6, pred-counts: 1, succ-count: 1, filled: true, sealed: true
+      // predecessors: 4
+      // successors: 4  (no try/catch successors)
+      // no phis
+      //  #0:foo;0:main: -1: ConstNumber          v14(3) <-  3 (INT)
+      //               : -1: Add                  v15 <- v13, v14(3)
+      //               : -1: ConstNumber          v16(-1) <-  -1 (INT)
+      //               : -1: Add                  v17 <- v11, v16(-1)
+      //               : -1: InstancePut          v3, v17; field: int
+      // com.android.tools.r8.ir.optimize.classinliner
+      //  .ClassInlinerPhiDirectUserAfterInlineTest$A.number
+      //               : -1: Goto                 block 4
+      // </pre>
+      // We modify block 1 to have:
+      // vX : phi(v3, vY)
+      // .. InstanceGet       vX ...
+      //
+      // and block 4 to have:
+      // vY : phi(v3, vX)
+      BasicBlock basicBlock = irCode.blocks.get(0);
+      Argument argument = basicBlock.getInstructions().get(0).asArgument();
+      assertNotNull(argument);
+      Value argumentValue = argument.outValue();
+
+      BasicBlock block1 = irCode.blocks.get(1);
+      assertTrue(block1.exit().isIf());
+
+      BasicBlock block3 = irCode.blocks.get(3);
+      assertTrue(block1.getSuccessors().contains(block3));
+      assertTrue(block3.exit().isGoto());
+
+      BasicBlock block4 = irCode.blocks.get(4);
+      assertSame(block3.getUniqueNormalSuccessor(), block4);
+
+      Phi firstPhi =
+          new Phi(
+              irCode.valueNumberGenerator.next(),
+              block1,
+              argumentValue.getType(),
+              null,
+              RegisterReadType.NORMAL);
+
+      Phi secondPhi =
+          new Phi(
+              irCode.valueNumberGenerator.next(),
+              block4,
+              argumentValue.getType(),
+              null,
+              RegisterReadType.NORMAL);
+
+      firstPhi.addOperands(ImmutableList.of(argumentValue, secondPhi));
+      secondPhi.addOperands(ImmutableList.of(argumentValue, firstPhi));
+
+      // Replace the invoke to use the phi
+      InstanceGet instanceGet = block1.getInstructions().get(0).asInstanceGet();
+      assertNotNull(instanceGet);
+      assertEquals(A.class.getTypeName(), instanceGet.getField().holder.toSourceString());
+      instanceGet.replaceValue(0, firstPhi);
+    }
+  }
+
+  public static class A {
+
+    int number = 0;
+
+    public void foo() {
+      int otherNumber = 0;
+      while (number != 0) {
+        otherNumber += 2;
+        while (number > 10) {
+          otherNumber += 3;
+          number--;
+        }
+      }
+      bar(otherNumber);
+    }
+
+    public void bar(int number) {
+      System.out.println(number + "");
+    }
+
+    public void baz() {
+      System.out.println("A::baz");
+    }
+  }
+
+  public static class Main {
+
+    public static void main(String[] args) {
+      A a = new A();
+      a.number = args.length;
+      a.foo();
+      a.baz();
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/testclasses/ClassInlineInstanceInitializerWithInaccessibleStaticGetTestClasses.java b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/testclasses/ClassInlineInstanceInitializerWithInaccessibleStaticGetTestClasses.java
index cc0734b..4943a47 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/testclasses/ClassInlineInstanceInitializerWithInaccessibleStaticGetTestClasses.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/testclasses/ClassInlineInstanceInitializerWithInaccessibleStaticGetTestClasses.java
@@ -4,11 +4,11 @@
 
 package com.android.tools.r8.ir.optimize.classinliner.testclasses;
 
-import com.android.tools.r8.NoStaticClassMerging;
+import com.android.tools.r8.NoHorizontalClassMerging;
 
 public class ClassInlineInstanceInitializerWithInaccessibleStaticGetTestClasses {
 
-  @NoStaticClassMerging
+  @NoHorizontalClassMerging
   public static class CandidateBase {
 
     public final String f;
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/logging/CustomLoggingFrameworkRemovalTest.java b/src/test/java/com/android/tools/r8/ir/optimize/logging/CustomLoggingFrameworkRemovalTest.java
new file mode 100644
index 0000000..b40d47e
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/logging/CustomLoggingFrameworkRemovalTest.java
@@ -0,0 +1,119 @@
+// 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.ir.optimize.logging;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isAbsent;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.AssumeNoClassInitializationSideEffects;
+import com.android.tools.r8.AssumeNoSideEffects;
+import com.android.tools.r8.AssumeNotNull;
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.InstructionSubject;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Objects;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class CustomLoggingFrameworkRemovalTest extends TestBase {
+
+  private final TestParameters parameters;
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters()
+        .withAllRuntimes()
+        .withApiLevelsStartingAtIncluding(AndroidApiLevel.N)
+        .build();
+  }
+
+  public CustomLoggingFrameworkRemovalTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void test() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(getClass())
+        .addKeepMainRule(Main.class)
+        .addKeepRules("-assumenosideeffects class java.lang.StringBuilder { void <init>(int); }")
+        .enableAssumeNotNullAnnotations()
+        .enableAssumeNoClassInitializationSideEffectsAnnotations()
+        .enableAssumeNoSideEffectsAnnotations()
+        .enableInliningAnnotations()
+        .setMinApi(parameters.getApiLevel())
+        .noMinification()
+        .compile()
+        .inspect(
+            inspector -> {
+              ClassSubject mainClassSubject = inspector.clazz(Main.class);
+              assertThat(mainClassSubject, isPresent());
+
+              MethodSubject mainMethodSubject = mainClassSubject.mainMethod();
+              assertThat(mainMethodSubject, isPresent());
+              assertTrue(
+                  mainMethodSubject
+                      .streamInstructions()
+                      .allMatch(InstructionSubject::isReturnVoid));
+
+              MethodSubject clinitMethodSubject = mainClassSubject.clinit();
+              assertThat(clinitMethodSubject, isAbsent());
+
+              assertThat(inspector.clazz(Logger.class), isAbsent());
+              assertThat(inspector.clazz(LoggerFactory.class), isAbsent());
+            })
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithEmptyOutput();
+  }
+
+  public static class Main {
+
+    @AssumeNotNull private static final Logger logger = LoggerFactory.getLogger("MainActivity");
+
+    public static void main(String[] args) {
+      logger.print(new StringBuilder(getInt(5)).append("Hello").toString());
+      logger.print(Objects.toString(new StringBuilder(getInt(6)).append(" world")));
+      logger.print(String.valueOf(new StringBuilder(getInt(1)).append("!")));
+    }
+
+    @AssumeNoSideEffects
+    @NeverInline
+    private static int getInt(int value) {
+      return System.currentTimeMillis() > 0 ? value : -1;
+    }
+  }
+
+  public static class Logger {
+
+    private Logger() {}
+
+    @AssumeNoSideEffects
+    public void print(String message) {
+      System.out.print(message);
+    }
+  }
+
+  @AssumeNoClassInitializationSideEffects
+  public static class LoggerFactory {
+
+    private static final Map<String, Logger> loggers = new HashMap<>();
+
+    @AssumeNoSideEffects
+    static synchronized Logger getLogger(String key) {
+      return loggers.computeIfAbsent(key, ignore -> new Logger());
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/AssumeInstanceFieldValueTest.java b/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/AssumeInstanceFieldValueTest.java
index e1e6b4b..f975e36 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/AssumeInstanceFieldValueTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/AssumeInstanceFieldValueTest.java
@@ -4,10 +4,11 @@
 
 package com.android.tools.r8.ir.optimize.membervaluepropagation;
 
+import static com.android.tools.r8.utils.codeinspector.CodeMatchers.invokesMethodWithName;
+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 static org.junit.Assert.assertEquals;
 
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
@@ -15,7 +16,6 @@
 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 org.junit.Test;
 import org.junit.runner.RunWith;
@@ -59,7 +59,7 @@
     assertThat(configClassSubject, isPresent());
 
     FieldSubject alwaysTrueFieldSubject = configClassSubject.uniqueFieldWithName("alwaysTrue");
-    assertThat(alwaysTrueFieldSubject, isPresent());
+    assertThat(alwaysTrueFieldSubject, isAbsent());
 
     FieldSubject alwaysTrueNoSideEffectsFieldSubject =
         configClassSubject.uniqueFieldWithName("alwaysTrueNoSideEffects");
@@ -70,14 +70,11 @@
 
     MethodSubject mainMethodSubject = testClassSubject.mainMethod();
     assertThat(mainMethodSubject, isPresent());
-    assertEquals(
-        1,
-        mainMethodSubject
-            .streamInstructions()
-            .filter(InstructionSubject::isInstanceGet)
-            .map(InstructionSubject::getField)
-            .filter(alwaysTrueFieldSubject.getField().field::equals)
-            .count());
+    if (canUseRequireNonNull(parameters)) {
+      assertThat(mainMethodSubject, invokesMethodWithName("requireNonNull"));
+    } else {
+      assertThat(mainMethodSubject, invokesMethodWithName("getClass"));
+    }
   }
 
   static class TestClass {
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/redundantfieldloadelimination/RedundantInstanceFieldLoadAfterStoreTest.java b/src/test/java/com/android/tools/r8/ir/optimize/redundantfieldloadelimination/RedundantInstanceFieldLoadAfterStoreTest.java
index 7c39564..73e21ba 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/redundantfieldloadelimination/RedundantInstanceFieldLoadAfterStoreTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/redundantfieldloadelimination/RedundantInstanceFieldLoadAfterStoreTest.java
@@ -4,9 +4,9 @@
 
 package com.android.tools.r8.ir.optimize.redundantfieldloadelimination;
 
+import static com.android.tools.r8.utils.codeinspector.Matchers.isAbsent;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
 import static org.hamcrest.MatcherAssert.assertThat;
-import static org.junit.Assert.assertTrue;
 
 import com.android.tools.r8.NeverClassInline;
 import com.android.tools.r8.TestBase;
@@ -14,8 +14,6 @@
 import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 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 org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
@@ -27,7 +25,7 @@
 
   @Parameterized.Parameters(name = "{0}")
   public static TestParametersCollection data() {
-    return getTestParameters().withAllRuntimes().build();
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
   }
 
   public RedundantInstanceFieldLoadAfterStoreTest(TestParameters parameters) {
@@ -40,23 +38,15 @@
         .addInnerClasses(RedundantInstanceFieldLoadAfterStoreTest.class)
         .addKeepMainRule(TestClass.class)
         .enableNeverClassInliningAnnotations()
-        .setMinApi(parameters.getRuntime())
+        .setMinApi(parameters.getApiLevel())
         .compile()
         .inspect(
             inspector -> {
               ClassSubject classSubject = inspector.clazz(TestClass.class);
               assertThat(classSubject, isPresent());
 
-              // Removing the actual field definition would require two optimization passes, because
-              // we would need another round of tree shaking to learn that the `greeting` field is
-              // no longer read after the field load in main() has been eliminated.
               FieldSubject fieldSubject = classSubject.uniqueFieldWithName("greeting");
-              assertThat(fieldSubject, isPresent());
-
-              MethodSubject methodSubject = classSubject.mainMethod();
-              assertThat(methodSubject, isPresent());
-              assertTrue(
-                  methodSubject.streamInstructions().noneMatch(InstructionSubject::isInstanceGet));
+              assertThat(fieldSubject, isAbsent());
             })
         .run(parameters.getRuntime(), TestClass.class)
         .assertSuccessWithOutputLines("Hello world!");
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/redundantfieldloadelimination/RedundantStaticFieldLoadAfterStoreTest.java b/src/test/java/com/android/tools/r8/ir/optimize/redundantfieldloadelimination/RedundantStaticFieldLoadAfterStoreTest.java
index d29688e..936b777 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/redundantfieldloadelimination/RedundantStaticFieldLoadAfterStoreTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/redundantfieldloadelimination/RedundantStaticFieldLoadAfterStoreTest.java
@@ -4,6 +4,7 @@
 
 package com.android.tools.r8.ir.optimize.redundantfieldloadelimination;
 
+import static com.android.tools.r8.utils.codeinspector.Matchers.isAbsent;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.junit.Assert.assertTrue;
@@ -26,7 +27,7 @@
 
   @Parameterized.Parameters(name = "{0}")
   public static TestParametersCollection data() {
-    return getTestParameters().withAllRuntimes().build();
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
   }
 
   public RedundantStaticFieldLoadAfterStoreTest(TestParameters parameters) {
@@ -38,18 +39,15 @@
     testForR8(parameters.getBackend())
         .addInnerClasses(RedundantStaticFieldLoadAfterStoreTest.class)
         .addKeepMainRule(TestClass.class)
-        .setMinApi(parameters.getRuntime())
+        .setMinApi(parameters.getApiLevel())
         .compile()
         .inspect(
             inspector -> {
               ClassSubject classSubject = inspector.clazz(TestClass.class);
               assertThat(classSubject, isPresent());
 
-              // Removing the actual field definition would require two optimization passes, because
-              // we would need another round of tree shaking to learn that the `greeting` field is
-              // no longer read after the field load in main() has been eliminated.
               FieldSubject fieldSubject = classSubject.uniqueFieldWithName("greeting");
-              assertThat(fieldSubject, isPresent());
+              assertThat(fieldSubject, isAbsent());
 
               MethodSubject methodSubject = classSubject.mainMethod();
               assertThat(methodSubject, isPresent());
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/uninstantiatedtypes/ParameterRewritingTest.java b/src/test/java/com/android/tools/r8/ir/optimize/uninstantiatedtypes/ParameterRewritingTest.java
index 8cc815b..b254b43 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/uninstantiatedtypes/ParameterRewritingTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/uninstantiatedtypes/ParameterRewritingTest.java
@@ -9,7 +9,7 @@
 import static org.junit.Assert.assertEquals;
 
 import com.android.tools.r8.NeverInline;
-import com.android.tools.r8.NoStaticClassMerging;
+import com.android.tools.r8.NoHorizontalClassMerging;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.graph.DexMethod;
@@ -53,7 +53,7 @@
             .addInnerClasses(ParameterRewritingTest.class)
             .addKeepMainRule(TestClass.class)
             .enableInliningAnnotations()
-            .enableNoStaticClassMergingAnnotations()
+            .enableNoHorizontalClassMergingAnnotations()
             .addOptionsModification(options -> options.enableClassInlining = false)
             .noMinification()
             .run(TestClass.class)
@@ -114,10 +114,10 @@
     }
   }
 
-  @NoStaticClassMerging
+  @NoHorizontalClassMerging
   static class Uninstantiated {}
 
-  @NoStaticClassMerging
+  @NoHorizontalClassMerging
   static class Factory {
 
     @NeverInline
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/uninstantiatedtypes/VoidReturnTypeRewritingTest.java b/src/test/java/com/android/tools/r8/ir/optimize/uninstantiatedtypes/VoidReturnTypeRewritingTest.java
index 7d3eb83..0ac4efc 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/uninstantiatedtypes/VoidReturnTypeRewritingTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/uninstantiatedtypes/VoidReturnTypeRewritingTest.java
@@ -8,7 +8,7 @@
 import static org.junit.Assert.assertTrue;
 
 import com.android.tools.r8.NeverInline;
-import com.android.tools.r8.NoStaticClassMerging;
+import com.android.tools.r8.NoHorizontalClassMerging;
 import com.android.tools.r8.NoVerticalClassMerging;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.ToolHelper;
@@ -52,7 +52,7 @@
             .addKeepMainRule(TestClass.class)
             .enableInliningAnnotations()
             .enableNoVerticalClassMergingAnnotations()
-            .enableNoStaticClassMergingAnnotations()
+            .enableNoHorizontalClassMergingAnnotations()
             .addKeepRules("-dontobfuscate")
             .addOptionsModification(options -> options.enableClassInlining = false)
             .run(TestClass.class)
@@ -104,7 +104,7 @@
     }
   }
 
-  @NoStaticClassMerging
+  @NoHorizontalClassMerging
   @NoVerticalClassMerging
   static class Uninstantiated {}
 
diff --git a/src/test/java/com/android/tools/r8/kotlin/R8KotlinPropertiesTest.java b/src/test/java/com/android/tools/r8/kotlin/R8KotlinPropertiesTest.java
index d6d2477..b9fefbaf4 100644
--- a/src/test/java/com/android/tools/r8/kotlin/R8KotlinPropertiesTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/R8KotlinPropertiesTest.java
@@ -464,8 +464,7 @@
             inspector -> {
               ClassSubject outerClass =
                   checkClassIsKept(inspector, "properties.CompanionProperties");
-              ClassSubject companionClass =
-                  checkClassIsKept(inspector, COMPANION_PROPERTY_CLASS.getClassName());
+              checkClassIsRemoved(inspector, COMPANION_PROPERTY_CLASS.getClassName());
               String propertyName = "primitiveProp";
               FieldSubject fieldSubject = checkFieldIsKept(outerClass, "int", propertyName);
               assertTrue(fieldSubject.getField().accessFlags.isStatic());
@@ -474,14 +473,6 @@
               } else {
                 assertTrue(fieldSubject.getField().accessFlags.isPrivate());
               }
-
-              MemberNaming.MethodSignature getter =
-                  COMPANION_PROPERTY_CLASS.getGetterForProperty(propertyName);
-              checkMethodIsRemoved(companionClass, getter);
-
-              MemberNaming.MethodSignature setter =
-                  COMPANION_PROPERTY_CLASS.getSetterForProperty(propertyName);
-              checkMethodIsRemoved(companionClass, setter);
             });
   }
 
@@ -500,8 +491,7 @@
             inspector -> {
               ClassSubject outerClass =
                   checkClassIsKept(inspector, "properties.CompanionProperties");
-              ClassSubject companionClass =
-                  checkClassIsKept(inspector, COMPANION_PROPERTY_CLASS.getClassName());
+              checkClassIsRemoved(inspector, COMPANION_PROPERTY_CLASS.getClassName());
               String propertyName = "privateProp";
               FieldSubject fieldSubject =
                   checkFieldIsKept(outerClass, JAVA_LANG_STRING, propertyName);
@@ -517,8 +507,6 @@
               // known to be non-null, thus the getter/setter can be inlined if their code is small
               // enough. Because the backing field is private, they will call into an accessor
               // (static) method. If access relaxation is enabled, this accessor can be removed.
-              checkMethodIsAbsent(companionClass, getter);
-              checkMethodIsAbsent(companionClass, setter);
               if (allowAccessModification) {
                 assertTrue(fieldSubject.getField().accessFlags.isPublic());
               } else {
@@ -542,8 +530,7 @@
             inspector -> {
               ClassSubject outerClass =
                   checkClassIsKept(inspector, "properties.CompanionProperties");
-              ClassSubject companionClass =
-                  checkClassIsKept(inspector, COMPANION_PROPERTY_CLASS.getClassName());
+              checkClassIsRemoved(inspector, COMPANION_PROPERTY_CLASS.getClassName());
               String propertyName = "internalProp";
               FieldSubject fieldSubject =
                   checkFieldIsKept(outerClass, JAVA_LANG_STRING, propertyName);
@@ -553,13 +540,6 @@
               } else {
                 assertTrue(fieldSubject.getField().accessFlags.isPrivate());
               }
-
-              MemberNaming.MethodSignature getter =
-                  COMPANION_PROPERTY_CLASS.getGetterForProperty(propertyName);
-              checkMethodIsRemoved(companionClass, getter);
-              MemberNaming.MethodSignature setter =
-                  COMPANION_PROPERTY_CLASS.getSetterForProperty(propertyName);
-              checkMethodIsRemoved(companionClass, setter);
             });
   }
 
@@ -578,8 +558,7 @@
             inspector -> {
               ClassSubject outerClass =
                   checkClassIsKept(inspector, "properties.CompanionProperties");
-              ClassSubject companionClass =
-                  checkClassIsKept(inspector, COMPANION_PROPERTY_CLASS.getClassName());
+              checkClassIsRemoved(inspector, COMPANION_PROPERTY_CLASS.getClassName());
               String propertyName = "publicProp";
               FieldSubject fieldSubject =
                   checkFieldIsKept(outerClass, JAVA_LANG_STRING, propertyName);
@@ -589,14 +568,6 @@
               } else {
                 assertTrue(fieldSubject.getField().accessFlags.isPrivate());
               }
-
-              MemberNaming.MethodSignature getter =
-                  COMPANION_PROPERTY_CLASS.getGetterForProperty(propertyName);
-              checkMethodIsRemoved(companionClass, getter);
-
-              MemberNaming.MethodSignature setter =
-                  COMPANION_PROPERTY_CLASS.getSetterForProperty(propertyName);
-              checkMethodIsRemoved(companionClass, setter);
             });
   }
 
@@ -616,26 +587,21 @@
             inspector -> {
               ClassSubject outerClass =
                   checkClassIsKept(inspector, testedClass.getOuterClassName());
-              ClassSubject companionClass = checkClassIsKept(inspector, testedClass.getClassName());
+              checkClassIsRemoved(inspector, testedClass.getClassName());
               String propertyName = "privateLateInitProp";
               FieldSubject fieldSubject =
                   checkFieldIsKept(outerClass, JAVA_LANG_STRING, propertyName);
               assertTrue(fieldSubject.getField().accessFlags.isStatic());
 
-              MemberNaming.MethodSignature getter = testedClass.getGetterForProperty(propertyName);
-              MemberNaming.MethodSignature setter = testedClass.getSetterForProperty(propertyName);
-
               // Because the getter/setter are private, they can only be called from another method
               // in the class. If this is an instance method, they will be called on 'this' which is
               // known to be non-null, thus the getter/setter can be inlined if their code is small
               // enough. Because the backing field is private, they will call into an accessor
               // (static) method. If access relaxation is enabled, this accessor can be removed.
-              checkMethodIsAbsent(companionClass, getter);
-              checkMethodIsAbsent(companionClass, setter);
               if (allowAccessModification) {
-                assertTrue(fieldSubject.getField().accessFlags.isPublic());
+                assertTrue(fieldSubject.getField().isPublic());
               } else {
-                assertTrue(fieldSubject.getField().accessFlags.isPrivate());
+                assertTrue(fieldSubject.getField().isPrivate());
               }
             });
   }
@@ -656,18 +622,12 @@
             inspector -> {
               ClassSubject outerClass =
                   checkClassIsKept(inspector, testedClass.getOuterClassName());
-              ClassSubject companionClass = checkClassIsKept(inspector, testedClass.getClassName());
+              checkClassIsRemoved(inspector, testedClass.getClassName());
               String propertyName = "internalLateInitProp";
               FieldSubject fieldSubject =
                   checkFieldIsKept(outerClass, JAVA_LANG_STRING, propertyName);
               assertTrue(fieldSubject.getField().accessFlags.isStatic());
               assertTrue(fieldSubject.getField().accessFlags.isPublic());
-
-              MemberNaming.MethodSignature getter = testedClass.getGetterForProperty(propertyName);
-              checkMethodIsRemoved(companionClass, getter);
-
-              MemberNaming.MethodSignature setter = testedClass.getSetterForProperty(propertyName);
-              checkMethodIsRemoved(companionClass, setter);
             });
   }
 
@@ -687,18 +647,12 @@
             inspector -> {
               ClassSubject outerClass =
                   checkClassIsKept(inspector, testedClass.getOuterClassName());
-              ClassSubject companionClass = checkClassIsKept(inspector, testedClass.getClassName());
+              checkClassIsRemoved(inspector, testedClass.getClassName());
               String propertyName = "publicLateInitProp";
               FieldSubject fieldSubject =
                   checkFieldIsKept(outerClass, JAVA_LANG_STRING, propertyName);
               assertTrue(fieldSubject.getField().accessFlags.isStatic());
               assertTrue(fieldSubject.getField().accessFlags.isPublic());
-
-              MemberNaming.MethodSignature getter = testedClass.getGetterForProperty(propertyName);
-              checkMethodIsRemoved(companionClass, getter);
-
-              MemberNaming.MethodSignature setter = testedClass.getSetterForProperty(propertyName);
-              checkMethodIsRemoved(companionClass, setter);
             });
   }
 
diff --git a/src/test/java/com/android/tools/r8/kotlin/lambda/KotlinLambdaMergingTest.java b/src/test/java/com/android/tools/r8/kotlin/lambda/KotlinLambdaMergingTest.java
index 5a23bd3..4997fe3 100644
--- a/src/test/java/com/android/tools/r8/kotlin/lambda/KotlinLambdaMergingTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/lambda/KotlinLambdaMergingTest.java
@@ -113,10 +113,13 @@
   }
 
   private static Group kstyleImpl(String pkg, String capture, int arity, int singletons) {
-    assertEquals(capture.isEmpty(), singletons != 0);
     return new Group(pkg, capture, arity, KOTLIN_FUNCTION_IFACE_STR + arity, singletons);
   }
 
+  static Group kstyle(String pkg, int arity) {
+    return kstyleImpl(pkg, "", arity, 0);
+  }
+
   static Group kstyle(String pkg, int arity, int singletons) {
     assertTrue(singletons != 0);
     return kstyleImpl(pkg, "", arity, singletons);
@@ -568,7 +571,7 @@
               String grp = allowAccessModification ? "" : pkg;
 
               verifier.assertLambdaGroups(
-                  kstyle(grp, 1, 1 /* 1 out of 5 lambdas in the group */),
+                  kstyle(grp, 1 /* 1 out of 5 lambdas in the group */),
                   jstyle(grp, 2, "java.util.Comparator", 0 /* 0 out of 2 lambdas in the group */));
 
               verifier.assertLambdas(/* None */ );
diff --git a/src/test/java/com/android/tools/r8/maindexlist/MainDexListFromGenerateMainDexInliningTest.java b/src/test/java/com/android/tools/r8/maindexlist/MainDexListFromGenerateMainDexInliningTest.java
index 8f88d08..a76d8cc 100644
--- a/src/test/java/com/android/tools/r8/maindexlist/MainDexListFromGenerateMainDexInliningTest.java
+++ b/src/test/java/com/android/tools/r8/maindexlist/MainDexListFromGenerateMainDexInliningTest.java
@@ -12,7 +12,6 @@
 
 import com.android.tools.r8.NeverInline;
 import com.android.tools.r8.NoHorizontalClassMerging;
-import com.android.tools.r8.NoStaticClassMerging;
 import com.android.tools.r8.R8TestCompileResult;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
@@ -79,7 +78,7 @@
             .collectMainDexClasses()
             .enableInliningAnnotations()
             .enableNoHorizontalClassMergingAnnotations()
-            .enableNoStaticClassMergingAnnotations()
+            .enableNoHorizontalClassMergingAnnotations()
             .setMinApi(parameters.getApiLevel())
             .compile();
 
@@ -130,7 +129,6 @@
   }
 
   @NoHorizontalClassMerging
-  @NoStaticClassMerging
   static class A {
 
     static void bar() {
@@ -139,7 +137,6 @@
   }
 
   @NoHorizontalClassMerging
-  @NoStaticClassMerging
   static class B {
 
     @NeverInline
diff --git a/src/test/java/com/android/tools/r8/maindexlist/MainDexPrunedReferenceTest.java b/src/test/java/com/android/tools/r8/maindexlist/MainDexPrunedReferenceTest.java
new file mode 100644
index 0000000..65bf904
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/maindexlist/MainDexPrunedReferenceTest.java
@@ -0,0 +1,129 @@
+// 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.maindexlist;
+
+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.assertTrue;
+import static org.junit.Assume.assumeFalse;
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NoHorizontalClassMerging;
+import com.android.tools.r8.R8FullTestBuilder;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestCompilerBuilder;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.ThrowableConsumer;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import java.util.Set;
+import java.util.function.Consumer;
+import org.junit.Assert;
+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 MainDexPrunedReferenceTest extends TestBase {
+
+  private final TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withDexRuntimes().withAllApiLevels().build();
+  }
+
+  public MainDexPrunedReferenceTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    assumeFalse(parameters.getDexRuntimeVersion().isDalvik());
+    testMainDex(builder -> {}, Assert::assertNull);
+  }
+
+  @Test
+  public void testMainDexClassesList() throws Exception {
+    assumeTrue(parameters.getDexRuntimeVersion().isDalvik());
+    testMainDex(
+        builder -> builder.addMainDexListClasses(Main.class),
+        mainDexClasses -> {
+          assertTrue(mainDexClasses.contains(Main.class.getTypeName()));
+          assertFalse(mainDexClasses.contains(Outside.class.getTypeName()));
+        });
+  }
+
+  @Test
+  public void testMainDexTracing() throws Exception {
+    assumeTrue(parameters.getDexRuntimeVersion().isDalvik());
+    testMainDex(
+        builder ->
+            builder.addMainDexRules(
+                "-keep class " + Main.class.getTypeName() + " { public static void notMain(); }"),
+        mainDexClasses -> {
+          assertTrue(mainDexClasses.contains(Main.class.getTypeName()));
+          // TODO(b/178362682): This should be false.
+          assertTrue(mainDexClasses.contains(Outside.class.getTypeName()));
+        });
+  }
+
+  private void testMainDex(
+      ThrowableConsumer<R8FullTestBuilder> configureMainDex,
+      Consumer<Set<String>> mainDexListConsumer)
+      throws Exception {
+    testForR8(parameters.getBackend())
+        .addProgramClasses(Main.class, Outside.class)
+        .enableInliningAnnotations()
+        .enableNoHorizontalClassMergingAnnotations()
+        .addKeepMainRule(Main.class)
+        .addKeepClassRules(Outside.class)
+        .setMinApi(parameters.getApiLevel())
+        .apply(configureMainDex)
+        .applyIf(
+            parameters.getDexRuntimeVersion().isDalvik(),
+            TestCompilerBuilder::collectMainDexClasses)
+        .compile()
+        .apply(compileResult -> compileResult.inspectMainDexClasses(mainDexListConsumer))
+        .run(parameters.getRuntime(), Main.class)
+        .inspect(
+            inspector -> {
+              ClassSubject mainClassSubject = inspector.clazz(Main.class);
+              assertThat(mainClassSubject, isPresent());
+              ClassSubject outsideClassSubject = inspector.clazz(Outside.class);
+              assertThat(outsideClassSubject, isPresent());
+            })
+        .assertSuccessWithOutputLines("Hello World");
+  }
+
+  @NoHorizontalClassMerging
+  public static class Outside {
+
+    @NeverInline
+    public static void foo() {
+      System.out.println("Outside::foo");
+    }
+  }
+
+  @NoHorizontalClassMerging
+  public static class Main {
+
+    public static void main(String[] args) {
+      int val = 0;
+      if (val != 0) {
+        notMain();
+      }
+      System.out.println("Hello World");
+    }
+
+    // If we trace before second round of enqueueing, we do not observe notMain being pruned.
+    public static void notMain() {
+      Outside.foo();
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/naming/EnumMinification.java b/src/test/java/com/android/tools/r8/naming/EnumMinification.java
index 6a5ff3e..aeb142f 100644
--- a/src/test/java/com/android/tools/r8/naming/EnumMinification.java
+++ b/src/test/java/com/android/tools/r8/naming/EnumMinification.java
@@ -4,6 +4,7 @@
 
 package com.android.tools.r8.naming;
 
+import static com.android.tools.r8.utils.codeinspector.Matchers.isAbsent;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresentAndNotRenamed;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresentAndRenamed;
@@ -50,7 +51,7 @@
         .addKeepMainRule(mainClass)
         .addKeepRules("-neverinline enum * extends java.lang.Enum { valueOf(...); }")
         .enableProguardTestOptions()
-        .setMinApi(parameters.getRuntime())
+        .setMinApi(parameters.getApiLevel())
         .compile();
   }
 
@@ -64,11 +65,11 @@
 
     CodeInspector inspector = result.inspector();
     ClassSubject clazz = inspector.clazz(enumTypeName);
-    // The class and fields - including field $VALUES and method valueOf - can be renamed. Only
-    // the values() method needs to be
+    // The class and fields - including the method valueOf - can be renamed. Only the values()
+    // method needs to be.
     assertThat(clazz, isPresentAndRenamed());
-    assertThat(clazz.uniqueFieldWithName("VALUE1"), isPresentAndRenamed());
-    assertThat(clazz.uniqueFieldWithName("VALUE2"), isPresentAndRenamed());
+    assertThat(clazz.uniqueFieldWithName("VALUE1"), isAbsent());
+    assertThat(clazz.uniqueFieldWithName("VALUE2"), isAbsent());
     assertThat(clazz.uniqueFieldWithName("$VALUES"), isPresentAndRenamed());
     assertThat(
         clazz.uniqueMethodWithName("valueOf"),
diff --git a/src/test/java/com/android/tools/r8/naming/EnumMinificationKotlinTest.java b/src/test/java/com/android/tools/r8/naming/EnumMinificationKotlinTest.java
index 8dd68fc..b96ff51 100644
--- a/src/test/java/com/android/tools/r8/naming/EnumMinificationKotlinTest.java
+++ b/src/test/java/com/android/tools/r8/naming/EnumMinificationKotlinTest.java
@@ -4,6 +4,7 @@
 package com.android.tools.r8.naming;
 
 import static com.android.tools.r8.ToolHelper.getKotlinCompilers;
+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.equalTo;
 import static org.hamcrest.MatcherAssert.assertThat;
@@ -16,9 +17,6 @@
 import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
-import com.android.tools.r8.utils.codeinspector.InstructionSubject;
-import com.android.tools.r8.utils.codeinspector.MethodSubject;
-import com.google.common.collect.Streams;
 import java.util.Collection;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -76,9 +74,6 @@
     ClassSubject enumClass = inspector.clazz(ENUM_CLASS_NAME);
     assertThat(enumClass, isPresent());
     assertEquals(minify, enumClass.isRenamed());
-    MethodSubject clinit = enumClass.clinit();
-    assertThat(clinit, isPresent());
-    assertEquals(
-        0, Streams.stream(clinit.iterateInstructions(InstructionSubject::isThrow)).count());
+    assertThat(enumClass.clinit(), isAbsent());
   }
 }
diff --git a/src/test/java/com/android/tools/r8/naming/KotlinIntrinsicsIdentifierTest.java b/src/test/java/com/android/tools/r8/naming/KotlinIntrinsicsIdentifierTest.java
index 3d143a1..7a1b6e4 100644
--- a/src/test/java/com/android/tools/r8/naming/KotlinIntrinsicsIdentifierTest.java
+++ b/src/test/java/com/android/tools/r8/naming/KotlinIntrinsicsIdentifierTest.java
@@ -18,7 +18,6 @@
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.kotlin.TestKotlinClass;
 import com.android.tools.r8.shaking.NoHorizontalClassMergingRule;
-import com.android.tools.r8.shaking.NoStaticClassMergingRule;
 import com.android.tools.r8.shaking.NoVerticalClassMergingRule;
 import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.StringUtils;
@@ -148,7 +147,6 @@
                     "-neverclassinline class **." + targetClassName,
                     "-" + NoVerticalClassMergingRule.RULE_NAME + " class **." + targetClassName,
                     "-" + NoHorizontalClassMergingRule.RULE_NAME + " class **." + targetClassName,
-                    "-" + NoStaticClassMergingRule.RULE_NAME + " class **." + targetClassName,
                     "-neverinline class **." + targetClassName + " { <methods>; }"))
             .addDontWarnJetBrainsNotNullAnnotation()
             .allowDiagnosticWarningMessages()
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 36bf030..e01926d 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
@@ -111,7 +111,6 @@
             options -> {
               options.enableInlining = false;
               options.enableVerticalClassMerging = false;
-              options.enableStaticClassMerging = false;
               options.enableClassInlining = false;
             })
         .compile();
diff --git a/src/test/java/com/android/tools/r8/naming/b116840216/ReserveOuterClassNameTest.java b/src/test/java/com/android/tools/r8/naming/b116840216/ReserveOuterClassNameTest.java
index 66b2a18..a5a165b 100644
--- a/src/test/java/com/android/tools/r8/naming/b116840216/ReserveOuterClassNameTest.java
+++ b/src/test/java/com/android/tools/r8/naming/b116840216/ReserveOuterClassNameTest.java
@@ -9,7 +9,7 @@
 
 import com.android.tools.r8.CompatProguardCommandBuilder;
 import com.android.tools.r8.NeverInline;
-import com.android.tools.r8.NoStaticClassMerging;
+import com.android.tools.r8.NoHorizontalClassMerging;
 import com.android.tools.r8.R8Command;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.ToolHelper;
@@ -24,10 +24,10 @@
 import org.junit.runners.Parameterized;
 import org.junit.runners.Parameterized.Parameters;
 
-@NoStaticClassMerging
+@NoHorizontalClassMerging
 class Outer {
 
-  @NoStaticClassMerging
+  @NoHorizontalClassMerging
   static class Inner {
     @NeverInline
     static void foo() {
@@ -80,7 +80,7 @@
             // the visiting of classes during class minification to be Outer$Inner before Outer.
             "-keepnames class " + Outer.class.getCanonicalName() + "$Inner",
             keepOuterName ? "-keepnames class " + Outer.class.getCanonicalName() : "",
-            noStaticClassMergingRule()),
+            noHorizontalClassMerging()),
         Origin.unknown());
 
     ToolHelper.allowTestProguardOptions(builder);
diff --git a/src/test/java/com/android/tools/r8/repackage/RepackageInnerAndOuterClassTest.java b/src/test/java/com/android/tools/r8/repackage/RepackageInnerAndOuterClassTest.java
index 81de378..6a8a384 100644
--- a/src/test/java/com/android/tools/r8/repackage/RepackageInnerAndOuterClassTest.java
+++ b/src/test/java/com/android/tools/r8/repackage/RepackageInnerAndOuterClassTest.java
@@ -8,7 +8,6 @@
 
 import com.android.tools.r8.NeverInline;
 import com.android.tools.r8.NoHorizontalClassMerging;
-import com.android.tools.r8.NoStaticClassMerging;
 import com.android.tools.r8.R8TestBuilder;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.repackage.RepackageInnerAndOuterClassTest.Outer.Inner;
@@ -48,7 +47,7 @@
         .apply(this::configureRepackaging)
         .enableInliningAnnotations()
         .enableNoHorizontalClassMergingAnnotations()
-        .enableNoStaticClassMergingAnnotations()
+        .enableNoHorizontalClassMergingAnnotations()
         .setMinApi(parameters.getApiLevel())
         .compile()
         .inspect(inspector -> inspect(inspector, eligibleForRepackaging))
@@ -70,7 +69,6 @@
   }
 
   @NoHorizontalClassMerging
-  @NoStaticClassMerging
   public static class Outer {
 
     @NeverInline
@@ -78,7 +76,7 @@
       System.out.print("Hello");
     }
 
-    @NoStaticClassMerging
+    @NoHorizontalClassMerging
     public static class Inner {
 
       @NeverInline
diff --git a/src/test/java/com/android/tools/r8/repackage/RepackageTest.java b/src/test/java/com/android/tools/r8/repackage/RepackageTest.java
index da09e88..179845d 100644
--- a/src/test/java/com/android/tools/r8/repackage/RepackageTest.java
+++ b/src/test/java/com/android/tools/r8/repackage/RepackageTest.java
@@ -113,7 +113,7 @@
         .apply(this::configureRepackaging)
         .enableInliningAnnotations()
         .enableNoHorizontalClassMergingAnnotations()
-        .enableNoStaticClassMergingAnnotations()
+        .enableNoHorizontalClassMergingAnnotations()
         .setMinApi(parameters.getApiLevel())
         .compile()
         .inspect(this::inspect)
diff --git a/src/test/java/com/android/tools/r8/repackage/testclasses/repackagetest/AccessPackagePrivateKeptMethodAllowRenamingOnReachableClassDirect.java b/src/test/java/com/android/tools/r8/repackage/testclasses/repackagetest/AccessPackagePrivateKeptMethodAllowRenamingOnReachableClassDirect.java
index 6ee9df6..e9f96c9 100644
--- a/src/test/java/com/android/tools/r8/repackage/testclasses/repackagetest/AccessPackagePrivateKeptMethodAllowRenamingOnReachableClassDirect.java
+++ b/src/test/java/com/android/tools/r8/repackage/testclasses/repackagetest/AccessPackagePrivateKeptMethodAllowRenamingOnReachableClassDirect.java
@@ -6,10 +6,8 @@
 
 import com.android.tools.r8.NeverInline;
 import com.android.tools.r8.NoHorizontalClassMerging;
-import com.android.tools.r8.NoStaticClassMerging;
 
 @NoHorizontalClassMerging
-@NoStaticClassMerging
 public class AccessPackagePrivateKeptMethodAllowRenamingOnReachableClassDirect {
 
   @NeverInline
diff --git a/src/test/java/com/android/tools/r8/repackage/testclasses/repackagetest/AccessPackagePrivateKeptMethodAllowRenamingOnReachableClassIndirect.java b/src/test/java/com/android/tools/r8/repackage/testclasses/repackagetest/AccessPackagePrivateKeptMethodAllowRenamingOnReachableClassIndirect.java
index 7a80bb3..535d72e 100644
--- a/src/test/java/com/android/tools/r8/repackage/testclasses/repackagetest/AccessPackagePrivateKeptMethodAllowRenamingOnReachableClassIndirect.java
+++ b/src/test/java/com/android/tools/r8/repackage/testclasses/repackagetest/AccessPackagePrivateKeptMethodAllowRenamingOnReachableClassIndirect.java
@@ -6,10 +6,8 @@
 
 import com.android.tools.r8.NeverInline;
 import com.android.tools.r8.NoHorizontalClassMerging;
-import com.android.tools.r8.NoStaticClassMerging;
 
 @NoHorizontalClassMerging
-@NoStaticClassMerging
 public class AccessPackagePrivateKeptMethodAllowRenamingOnReachableClassIndirect {
 
   @NeverInline
@@ -18,7 +16,6 @@
   }
 
   @NoHorizontalClassMerging
-  @NoStaticClassMerging
   public static class Helper {
 
     @NeverInline
diff --git a/src/test/java/com/android/tools/r8/repackage/testclasses/repackagetest/AccessPackagePrivateKeptMethodOnReachableClassDirect.java b/src/test/java/com/android/tools/r8/repackage/testclasses/repackagetest/AccessPackagePrivateKeptMethodOnReachableClassDirect.java
index f8af76d..c19f477 100644
--- a/src/test/java/com/android/tools/r8/repackage/testclasses/repackagetest/AccessPackagePrivateKeptMethodOnReachableClassDirect.java
+++ b/src/test/java/com/android/tools/r8/repackage/testclasses/repackagetest/AccessPackagePrivateKeptMethodOnReachableClassDirect.java
@@ -6,10 +6,8 @@
 
 import com.android.tools.r8.NeverInline;
 import com.android.tools.r8.NoHorizontalClassMerging;
-import com.android.tools.r8.NoStaticClassMerging;
 
 @NoHorizontalClassMerging
-@NoStaticClassMerging
 public class AccessPackagePrivateKeptMethodOnReachableClassDirect {
 
   @NeverInline
diff --git a/src/test/java/com/android/tools/r8/repackage/testclasses/repackagetest/AccessPackagePrivateKeptMethodOnReachableClassIndirect.java b/src/test/java/com/android/tools/r8/repackage/testclasses/repackagetest/AccessPackagePrivateKeptMethodOnReachableClassIndirect.java
index 279d85b..5b3b011 100644
--- a/src/test/java/com/android/tools/r8/repackage/testclasses/repackagetest/AccessPackagePrivateKeptMethodOnReachableClassIndirect.java
+++ b/src/test/java/com/android/tools/r8/repackage/testclasses/repackagetest/AccessPackagePrivateKeptMethodOnReachableClassIndirect.java
@@ -6,10 +6,8 @@
 
 import com.android.tools.r8.NeverInline;
 import com.android.tools.r8.NoHorizontalClassMerging;
-import com.android.tools.r8.NoStaticClassMerging;
 
 @NoHorizontalClassMerging
-@NoStaticClassMerging
 public class AccessPackagePrivateKeptMethodOnReachableClassIndirect {
 
   @NeverInline
@@ -18,7 +16,6 @@
   }
 
   @NoHorizontalClassMerging
-  @NoStaticClassMerging
   public static class Helper {
 
     @NeverInline
diff --git a/src/test/java/com/android/tools/r8/repackage/testclasses/repackagetest/AccessPackagePrivateMethodOnKeptClassAllowRenamingDirect.java b/src/test/java/com/android/tools/r8/repackage/testclasses/repackagetest/AccessPackagePrivateMethodOnKeptClassAllowRenamingDirect.java
index a333df7..29ce708 100644
--- a/src/test/java/com/android/tools/r8/repackage/testclasses/repackagetest/AccessPackagePrivateMethodOnKeptClassAllowRenamingDirect.java
+++ b/src/test/java/com/android/tools/r8/repackage/testclasses/repackagetest/AccessPackagePrivateMethodOnKeptClassAllowRenamingDirect.java
@@ -6,10 +6,8 @@
 
 import com.android.tools.r8.NeverInline;
 import com.android.tools.r8.NoHorizontalClassMerging;
-import com.android.tools.r8.NoStaticClassMerging;
 
 @NoHorizontalClassMerging
-@NoStaticClassMerging
 public class AccessPackagePrivateMethodOnKeptClassAllowRenamingDirect {
 
   @NeverInline
diff --git a/src/test/java/com/android/tools/r8/repackage/testclasses/repackagetest/AccessPackagePrivateMethodOnKeptClassAllowRenamingIndirect.java b/src/test/java/com/android/tools/r8/repackage/testclasses/repackagetest/AccessPackagePrivateMethodOnKeptClassAllowRenamingIndirect.java
index 040dd72..efdeff6 100644
--- a/src/test/java/com/android/tools/r8/repackage/testclasses/repackagetest/AccessPackagePrivateMethodOnKeptClassAllowRenamingIndirect.java
+++ b/src/test/java/com/android/tools/r8/repackage/testclasses/repackagetest/AccessPackagePrivateMethodOnKeptClassAllowRenamingIndirect.java
@@ -6,10 +6,8 @@
 
 import com.android.tools.r8.NeverInline;
 import com.android.tools.r8.NoHorizontalClassMerging;
-import com.android.tools.r8.NoStaticClassMerging;
 
 @NoHorizontalClassMerging
-@NoStaticClassMerging
 public class AccessPackagePrivateMethodOnKeptClassAllowRenamingIndirect {
 
   @NeverInline
@@ -18,7 +16,6 @@
   }
 
   @NoHorizontalClassMerging
-  @NoStaticClassMerging
   public static class Helper {
 
     @NeverInline
diff --git a/src/test/java/com/android/tools/r8/repackage/testclasses/repackagetest/AccessPackagePrivateMethodOnKeptClassDirect.java b/src/test/java/com/android/tools/r8/repackage/testclasses/repackagetest/AccessPackagePrivateMethodOnKeptClassDirect.java
index a7bb32f..ef4fc49 100644
--- a/src/test/java/com/android/tools/r8/repackage/testclasses/repackagetest/AccessPackagePrivateMethodOnKeptClassDirect.java
+++ b/src/test/java/com/android/tools/r8/repackage/testclasses/repackagetest/AccessPackagePrivateMethodOnKeptClassDirect.java
@@ -6,10 +6,8 @@
 
 import com.android.tools.r8.NeverInline;
 import com.android.tools.r8.NoHorizontalClassMerging;
-import com.android.tools.r8.NoStaticClassMerging;
 
 @NoHorizontalClassMerging
-@NoStaticClassMerging
 public class AccessPackagePrivateMethodOnKeptClassDirect {
 
   @NeverInline
diff --git a/src/test/java/com/android/tools/r8/repackage/testclasses/repackagetest/AccessPackagePrivateMethodOnKeptClassIndirect.java b/src/test/java/com/android/tools/r8/repackage/testclasses/repackagetest/AccessPackagePrivateMethodOnKeptClassIndirect.java
index 7190c3f..355420d 100644
--- a/src/test/java/com/android/tools/r8/repackage/testclasses/repackagetest/AccessPackagePrivateMethodOnKeptClassIndirect.java
+++ b/src/test/java/com/android/tools/r8/repackage/testclasses/repackagetest/AccessPackagePrivateMethodOnKeptClassIndirect.java
@@ -6,10 +6,8 @@
 
 import com.android.tools.r8.NeverInline;
 import com.android.tools.r8.NoHorizontalClassMerging;
-import com.android.tools.r8.NoStaticClassMerging;
 
 @NoHorizontalClassMerging
-@NoStaticClassMerging
 public class AccessPackagePrivateMethodOnKeptClassIndirect {
 
   @NeverInline
@@ -18,7 +16,6 @@
   }
 
   @NoHorizontalClassMerging
-  @NoStaticClassMerging
   public static class Helper {
 
     @NeverInline
diff --git a/src/test/java/com/android/tools/r8/repackage/testclasses/repackagetest/AccessPackagePrivateMethodOnReachableClassDirect.java b/src/test/java/com/android/tools/r8/repackage/testclasses/repackagetest/AccessPackagePrivateMethodOnReachableClassDirect.java
index 07872c6b..f77847d 100644
--- a/src/test/java/com/android/tools/r8/repackage/testclasses/repackagetest/AccessPackagePrivateMethodOnReachableClassDirect.java
+++ b/src/test/java/com/android/tools/r8/repackage/testclasses/repackagetest/AccessPackagePrivateMethodOnReachableClassDirect.java
@@ -6,10 +6,8 @@
 
 import com.android.tools.r8.NeverInline;
 import com.android.tools.r8.NoHorizontalClassMerging;
-import com.android.tools.r8.NoStaticClassMerging;
 
 @NoHorizontalClassMerging
-@NoStaticClassMerging
 public class AccessPackagePrivateMethodOnReachableClassDirect {
 
   @NeverInline
diff --git a/src/test/java/com/android/tools/r8/repackage/testclasses/repackagetest/AccessPackagePrivateMethodOnReachableClassIndirect.java b/src/test/java/com/android/tools/r8/repackage/testclasses/repackagetest/AccessPackagePrivateMethodOnReachableClassIndirect.java
index d528244..f68331a 100644
--- a/src/test/java/com/android/tools/r8/repackage/testclasses/repackagetest/AccessPackagePrivateMethodOnReachableClassIndirect.java
+++ b/src/test/java/com/android/tools/r8/repackage/testclasses/repackagetest/AccessPackagePrivateMethodOnReachableClassIndirect.java
@@ -6,10 +6,8 @@
 
 import com.android.tools.r8.NeverInline;
 import com.android.tools.r8.NoHorizontalClassMerging;
-import com.android.tools.r8.NoStaticClassMerging;
 
 @NoHorizontalClassMerging
-@NoStaticClassMerging
 public class AccessPackagePrivateMethodOnReachableClassIndirect {
 
   @NeverInline
@@ -18,7 +16,6 @@
   }
 
   @NoHorizontalClassMerging
-  @NoStaticClassMerging
   public static class Helper {
 
     @NeverInline
diff --git a/src/test/java/com/android/tools/r8/repackage/testclasses/repackagetest/AccessPublicKeptMethodAllowRenamingOnReachableClass.java b/src/test/java/com/android/tools/r8/repackage/testclasses/repackagetest/AccessPublicKeptMethodAllowRenamingOnReachableClass.java
index d99a9b5..1ad561e 100644
--- a/src/test/java/com/android/tools/r8/repackage/testclasses/repackagetest/AccessPublicKeptMethodAllowRenamingOnReachableClass.java
+++ b/src/test/java/com/android/tools/r8/repackage/testclasses/repackagetest/AccessPublicKeptMethodAllowRenamingOnReachableClass.java
@@ -6,10 +6,8 @@
 
 import com.android.tools.r8.NeverInline;
 import com.android.tools.r8.NoHorizontalClassMerging;
-import com.android.tools.r8.NoStaticClassMerging;
 
 @NoHorizontalClassMerging
-@NoStaticClassMerging
 public class AccessPublicKeptMethodAllowRenamingOnReachableClass {
 
   @NeverInline
diff --git a/src/test/java/com/android/tools/r8/repackage/testclasses/repackagetest/AccessPublicKeptMethodOnReachableClass.java b/src/test/java/com/android/tools/r8/repackage/testclasses/repackagetest/AccessPublicKeptMethodOnReachableClass.java
index 8880ca5..c21813f 100644
--- a/src/test/java/com/android/tools/r8/repackage/testclasses/repackagetest/AccessPublicKeptMethodOnReachableClass.java
+++ b/src/test/java/com/android/tools/r8/repackage/testclasses/repackagetest/AccessPublicKeptMethodOnReachableClass.java
@@ -6,10 +6,8 @@
 
 import com.android.tools.r8.NeverInline;
 import com.android.tools.r8.NoHorizontalClassMerging;
-import com.android.tools.r8.NoStaticClassMerging;
 
 @NoHorizontalClassMerging
-@NoStaticClassMerging
 public class AccessPublicKeptMethodOnReachableClass {
 
   @NeverInline
diff --git a/src/test/java/com/android/tools/r8/repackage/testclasses/repackagetest/AccessPublicMethodOnKeptClass.java b/src/test/java/com/android/tools/r8/repackage/testclasses/repackagetest/AccessPublicMethodOnKeptClass.java
index d3fa44b..f492f35 100644
--- a/src/test/java/com/android/tools/r8/repackage/testclasses/repackagetest/AccessPublicMethodOnKeptClass.java
+++ b/src/test/java/com/android/tools/r8/repackage/testclasses/repackagetest/AccessPublicMethodOnKeptClass.java
@@ -6,10 +6,8 @@
 
 import com.android.tools.r8.NeverInline;
 import com.android.tools.r8.NoHorizontalClassMerging;
-import com.android.tools.r8.NoStaticClassMerging;
 
 @NoHorizontalClassMerging
-@NoStaticClassMerging
 public class AccessPublicMethodOnKeptClass {
 
   @NeverInline
diff --git a/src/test/java/com/android/tools/r8/repackage/testclasses/repackagetest/AccessPublicMethodOnKeptClassAllowRenaming.java b/src/test/java/com/android/tools/r8/repackage/testclasses/repackagetest/AccessPublicMethodOnKeptClassAllowRenaming.java
index 9578cc7..3571398 100644
--- a/src/test/java/com/android/tools/r8/repackage/testclasses/repackagetest/AccessPublicMethodOnKeptClassAllowRenaming.java
+++ b/src/test/java/com/android/tools/r8/repackage/testclasses/repackagetest/AccessPublicMethodOnKeptClassAllowRenaming.java
@@ -6,10 +6,8 @@
 
 import com.android.tools.r8.NeverInline;
 import com.android.tools.r8.NoHorizontalClassMerging;
-import com.android.tools.r8.NoStaticClassMerging;
 
 @NoHorizontalClassMerging
-@NoStaticClassMerging
 public class AccessPublicMethodOnKeptClassAllowRenaming {
 
   @NeverInline
diff --git a/src/test/java/com/android/tools/r8/repackage/testclasses/repackagetest/AccessPublicMethodOnReachableClass.java b/src/test/java/com/android/tools/r8/repackage/testclasses/repackagetest/AccessPublicMethodOnReachableClass.java
index ec21ae7..4fc936f 100644
--- a/src/test/java/com/android/tools/r8/repackage/testclasses/repackagetest/AccessPublicMethodOnReachableClass.java
+++ b/src/test/java/com/android/tools/r8/repackage/testclasses/repackagetest/AccessPublicMethodOnReachableClass.java
@@ -6,10 +6,8 @@
 
 import com.android.tools.r8.NeverInline;
 import com.android.tools.r8.NoHorizontalClassMerging;
-import com.android.tools.r8.NoStaticClassMerging;
 
 @NoHorizontalClassMerging
-@NoStaticClassMerging
 public class AccessPublicMethodOnReachableClass {
 
   @NeverInline
diff --git a/src/test/java/com/android/tools/r8/repackage/testclasses/repackagetest/ReachableClass.java b/src/test/java/com/android/tools/r8/repackage/testclasses/repackagetest/ReachableClass.java
index 22c5aa0..dcd099d 100644
--- a/src/test/java/com/android/tools/r8/repackage/testclasses/repackagetest/ReachableClass.java
+++ b/src/test/java/com/android/tools/r8/repackage/testclasses/repackagetest/ReachableClass.java
@@ -6,10 +6,8 @@
 
 import com.android.tools.r8.NeverInline;
 import com.android.tools.r8.NoHorizontalClassMerging;
-import com.android.tools.r8.NoStaticClassMerging;
 
 @NoHorizontalClassMerging
-@NoStaticClassMerging
 public class ReachableClass {
 
   @NeverInline
diff --git a/src/test/java/com/android/tools/r8/rewrite/switches/MaxIntSwitchTest.java b/src/test/java/com/android/tools/r8/rewrite/switches/MaxIntSwitchTest.java
index 0f3f2cc..b32e476 100644
--- a/src/test/java/com/android/tools/r8/rewrite/switches/MaxIntSwitchTest.java
+++ b/src/test/java/com/android/tools/r8/rewrite/switches/MaxIntSwitchTest.java
@@ -3,14 +3,22 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.rewrite.switches;
 
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
 import com.android.tools.r8.CompilationMode;
 import com.android.tools.r8.NeverInline;
 import com.android.tools.r8.NeverPropagateValue;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.TestRunResult;
-import com.android.tools.r8.ToolHelper.DexVm.Version;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.BooleanUtils;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.InstructionSubject;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
 import java.util.List;
+import java.util.stream.Collectors;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
@@ -33,12 +41,51 @@
     this.mode = mode;
   }
 
-  private void checkResult(TestRunResult<?> result) {
-    if (parameters.getDexRuntimeVersion().isOlderThanOrEqual(Version.V4_0_4)) {
-      result.assertFailureWithErrorThatThrows(AssertionError.class);
-    } else {
-      result.assertSuccessWithOutputLines("good");
-    }
+  private void checkSwitch(InstructionSubject instruction, boolean hasMaxIntKey) {
+    assertEquals(hasMaxIntKey, instruction.asSwitch().getKeys().contains(0x7fffffff));
+  }
+
+  private void checkSwitch(MethodSubject method, boolean hasMaxIntKey) {
+    assertTrue(method.streamInstructions().anyMatch(InstructionSubject::isSwitch));
+    method
+        .streamInstructions()
+        .filter(InstructionSubject::isSwitch)
+        .forEach(instruction -> checkSwitch(instruction, hasMaxIntKey));
+  }
+
+  private void checkStringSwitch(MethodSubject method, boolean hasMaxIntKey) {
+    // String switch will have two switches.
+    List<InstructionSubject> switchInstructions =
+        method
+            .streamInstructions()
+            .filter(InstructionSubject::isSwitch)
+            .collect(Collectors.toList());
+    assertEquals(2, switchInstructions.size());
+    checkSwitch(switchInstructions.get(0), hasMaxIntKey);
+  }
+
+  public void checkSwitchKeys(CodeInspector inspector) {
+    checkSwitch(
+        inspector.clazz(TestClass.class).uniqueMethodWithName("f"),
+        parameters.getApiLevel().isGreaterThanOrEqualTo(AndroidApiLevel.K));
+    checkSwitch(
+        inspector.clazz(TestClass.class).uniqueMethodWithName("g"),
+        parameters.getApiLevel().isGreaterThanOrEqualTo(AndroidApiLevel.K));
+    // Debug mode will not rewrite switch statements except when the MAX_INT key is present.
+    assertEquals(
+        inspector
+            .clazz(TestClass.class)
+            .uniqueMethodWithName("h")
+            .streamInstructions()
+            .filter(InstructionSubject::isSwitch)
+            .count(),
+        BooleanUtils.intValue(
+            mode == CompilationMode.DEBUG
+                && parameters.getApiLevel().isGreaterThanOrEqualTo(AndroidApiLevel.K)));
+
+    checkStringSwitch(
+        inspector.clazz(TestClass.class).uniqueMethodWithName("s"),
+        parameters.getApiLevel().isGreaterThanOrEqualTo(AndroidApiLevel.K));
   }
 
   @Test
@@ -48,7 +95,8 @@
         .setMinApi(parameters.getApiLevel())
         .setMode(mode)
         .run(parameters.getRuntime(), TestClass.class)
-        .apply(this::checkResult);
+        .inspect(this::checkSwitchKeys)
+        .assertSuccessWithOutput(EXPECTED_OUTPUT);
   }
 
   @Test
@@ -61,9 +109,21 @@
         .setMinApi(parameters.getApiLevel())
         .setMode(mode)
         .run(parameters.getRuntime(), TestClass.class)
-        .apply(this::checkResult);
+        .inspect(this::checkSwitchKeys)
+        .assertSuccessWithOutput(EXPECTED_OUTPUT);
   }
 
+  static final String EXPECTED_OUTPUT =
+      StringUtils.lines(
+          Integer.toString(0x7ffffffc),
+          Integer.toString(0x7ffffffd),
+          Integer.toString(0x7ffffffe),
+          Integer.toString(0x7fffffff),
+          "good",
+          "show",
+          "!",
+          "?");
+
   static class TestClass {
     @NeverInline
     @NeverPropagateValue
@@ -83,8 +143,67 @@
       }
     }
 
+    @NeverInline
+    @NeverPropagateValue
+    public static void g(int i) {
+      switch (i) {
+        case 0x7ffffffc:
+          System.out.println("a");
+          break;
+        case 0x7ffffffd:
+          System.out.println("b");
+          break;
+        case 0x7ffffffe:
+        case 0x7fffffff:
+          System.out.println("show");
+          break;
+        default:
+          throw new AssertionError();
+      }
+    }
+
+    @NeverInline
+    @NeverPropagateValue
+    public static void h(int i) {
+      switch (i) {
+        case 0x7fffffff:
+          System.out.println("!");
+          break;
+        default:
+          throw new AssertionError();
+      }
+    }
+
+    @NeverInline
+    @NeverPropagateValue
+    public static void s(String s) {
+      switch (s) {
+        case "DAEFIDU":
+          System.out.println("a");
+          break;
+        case "DAEFIDV":
+          System.out.println("b");
+          break;
+        case "DAEFIDW":
+          System.out.println("c");
+          break;
+        case "DAEFIDX":
+          System.out.println("?");
+          break;
+        default:
+          throw new AssertionError();
+      }
+    }
+
     public static void main(String[] args) {
+      System.out.println("DAEFIDU".hashCode());
+      System.out.println("DAEFIDV".hashCode());
+      System.out.println("DAEFIDW".hashCode());
+      System.out.println("DAEFIDX".hashCode());
       f(0x7fffffff);
+      g(0x7fffffff);
+      h(0x7fffffff);
+      s("DAEFIDX");
     }
   }
 }
diff --git a/src/test/java/com/android/tools/r8/rewrite/switches/SwitchRewritingJarTest.java b/src/test/java/com/android/tools/r8/rewrite/switches/SwitchRewritingJarTest.java
index 7987f96..4f53fe2 100644
--- a/src/test/java/com/android/tools/r8/rewrite/switches/SwitchRewritingJarTest.java
+++ b/src/test/java/com/android/tools/r8/rewrite/switches/SwitchRewritingJarTest.java
@@ -210,6 +210,7 @@
     Statistics stat =
         countInstructions(getMethodSubject(app, "Test", signature).iterateInstructions());
 
+    int expectedIfCount = 0;
     int expectedPackedSwitchCount, expectedSparseSwitchCount;
     if (keyStep <= 2) {
       expectedPackedSwitchCount = 1;
@@ -218,8 +219,15 @@
       expectedPackedSwitchCount = 0;
       expectedSparseSwitchCount = 1;
     }
+    if (backend == Backend.DEX
+        && additionalLastKey != null
+        && additionalLastKey == Integer.MAX_VALUE) {
+      expectedIfCount = 1;
+    }
 
-    assertEquals(new Statistics(0, expectedPackedSwitchCount, expectedSparseSwitchCount), stat);
+    assertEquals(
+        new Statistics(expectedIfCount, expectedPackedSwitchCount, expectedSparseSwitchCount),
+        stat);
   }
 
   @Test
diff --git a/src/test/java/com/android/tools/r8/rewrite/switches/SwitchRewritingTest.java b/src/test/java/com/android/tools/r8/rewrite/switches/SwitchRewritingTest.java
index 37c3bc3..bcd4063 100644
--- a/src/test/java/com/android/tools/r8/rewrite/switches/SwitchRewritingTest.java
+++ b/src/test/java/com/android/tools/r8/rewrite/switches/SwitchRewritingTest.java
@@ -152,8 +152,6 @@
     Consumer<InternalOptions> optionsConsumer =
         options -> {
           options.testing.enableDeadSwitchCaseElimination = false;
-          options.verbose = true;
-          options.printTimes = true;
         };
     AndroidApp originalApplication = buildApplication(builder);
     AndroidApp processedApplication = processApplication(originalApplication, optionsConsumer);
@@ -162,7 +160,12 @@
     if (keyStep <= 2) {
       assertTrue(code.instructions[0] instanceof PackedSwitch);
     } else {
-      assertTrue(code.instructions[0] instanceof SparseSwitch);
+      if (additionalLastKey != null && additionalLastKey == Integer.MAX_VALUE) {
+        assertTrue(code.instructions[0] instanceof Const);
+        assertTrue(code.instructions[1] instanceof IfEq);
+      } else {
+        assertTrue(code.instructions[0] instanceof SparseSwitch);
+      }
     }
   }
 
diff --git a/src/test/java/com/android/tools/r8/shaking/FieldReadsJasminTest.java b/src/test/java/com/android/tools/r8/shaking/FieldReadsJasminTest.java
index 3c15d88..3f27bc6 100644
--- a/src/test/java/com/android/tools/r8/shaking/FieldReadsJasminTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/FieldReadsJasminTest.java
@@ -3,6 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.shaking;
 
+import static com.android.tools.r8.utils.codeinspector.Matchers.isAbsent;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresentAndRenamed;
 import static org.hamcrest.MatcherAssert.assertThat;
@@ -149,40 +150,18 @@
         "p:",
         "  putstatic Main/sField I",
         "  return");
-    MethodSignature mainMethod = main.addMainMethod(
-        ".limit stack 2",
-        ".limit locals 1",
-        "  getstatic Main/sField I",
-        "  return");
 
-    ensureFieldExistsButNoRead(builder, main, mainMethod, main, "sField");
-  }
+    main.addMainMethod(
+        ".limit stack 2", ".limit locals 1", "  getstatic Main/sField I", "  return");
 
-  private void ensureFieldExistsButNoRead(
-      JasminBuilder app,
-      ClassBuilder clazz,
-      MethodSignature method,
-      ClassBuilder fieldHolder,
-      String fieldName)
-      throws Exception {
     testForR8(parameters.getBackend())
-        .addProgramClassFileData(app.buildClasses())
+        .addProgramClassFileData(builder.buildClasses())
         .addKeepRules("-keep class * { <methods>; }")
         .setMinApi(parameters.getApiLevel())
         .compile()
         .inspect(
-            inspector -> {
-              FieldSubject fld = inspector.clazz(fieldHolder.name).uniqueFieldWithName(fieldName);
-              assertThat(fld, isPresentAndRenamed());
-
-              ClassSubject classSubject = inspector.clazz(clazz.name);
-              assertThat(classSubject, isPresent());
-              MethodSubject methodSubject = classSubject.uniqueMethodWithName(method.name);
-              assertThat(methodSubject, isPresent());
-              Iterator<InstructionSubject> it =
-                  methodSubject.iterateInstructions(InstructionSubject::isFieldAccess);
-              assertFalse(it.hasNext());
-            });
+            inspector ->
+                assertThat(inspector.clazz(main.name).uniqueFieldWithName("sField"), isAbsent()));
   }
 
   @Test
diff --git a/src/test/java/com/android/tools/r8/shaking/KeepAnnotatedMemberTest.java b/src/test/java/com/android/tools/r8/shaking/KeepAnnotatedMemberTest.java
index 993a658..550356e 100644
--- a/src/test/java/com/android/tools/r8/shaking/KeepAnnotatedMemberTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/KeepAnnotatedMemberTest.java
@@ -224,7 +224,7 @@
             .addDontWarnJavaxNullableAnnotation()
             .compile()
             .graphInspector();
-    assertRetainedClassesEqual(referenceInspector, ifThenKeepClassMembersInspector, false, false);
+    assertRetainedClassesEqual(referenceInspector, ifThenKeepClassMembersInspector);
 
     GraphInspector ifThenKeepClassesWithMembersInspector =
         testForR8(Backend.CF)
@@ -243,8 +243,7 @@
             .addDontWarnJavaxNullableAnnotation()
             .compile()
             .graphInspector();
-    assertRetainedClassesEqual(
-        referenceInspector, ifThenKeepClassesWithMembersInspector, false, false);
+    assertRetainedClassesEqual(referenceInspector, ifThenKeepClassesWithMembersInspector);
 
     GraphInspector ifHasMemberThenKeepClassInspector =
         testForR8(Backend.CF)
@@ -265,8 +264,12 @@
             .addDontWarnJavaxNullableAnnotation()
             .compile()
             .graphInspector();
-    // TODO(b/159418523): Should the reference be equal to the result with the conditional rule?
-    assertRetainedClassesEqual(referenceInspector, ifHasMemberThenKeepClassInspector, true, true);
+    assertRetainedClassesEqual(referenceInspector, ifHasMemberThenKeepClassInspector);
+  }
+
+  private void assertRetainedClassesEqual(
+      GraphInspector referenceResult, GraphInspector conditionalResult) {
+    assertRetainedClassesEqual(referenceResult, conditionalResult, false, false);
   }
 
   private void assertRetainedClassesEqual(
diff --git a/src/test/java/com/android/tools/r8/shaking/testrules/A.java b/src/test/java/com/android/tools/r8/shaking/testrules/A.java
index 1387558..23aa04e 100644
--- a/src/test/java/com/android/tools/r8/shaking/testrules/A.java
+++ b/src/test/java/com/android/tools/r8/shaking/testrules/A.java
@@ -5,10 +5,8 @@
 package com.android.tools.r8.shaking.testrules;
 
 import com.android.tools.r8.NoHorizontalClassMerging;
-import com.android.tools.r8.NoStaticClassMerging;
 
 @NoHorizontalClassMerging
-@NoStaticClassMerging
 public class A {
 
   public static int m(int a, int b) {
diff --git a/src/test/java/com/android/tools/r8/shaking/testrules/ForceInlineTest.java b/src/test/java/com/android/tools/r8/shaking/testrules/ForceInlineTest.java
index b669c95..aaafe0c 100644
--- a/src/test/java/com/android/tools/r8/shaking/testrules/ForceInlineTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/testrules/ForceInlineTest.java
@@ -44,7 +44,7 @@
         .addKeepRules(proguardConfiguration)
         .enableInliningAnnotations()
         .enableNoHorizontalClassMergingAnnotations()
-        .enableNoStaticClassMergingAnnotations()
+        .enableNoHorizontalClassMergingAnnotations()
         .enableProguardTestOptions()
         .compile()
         .inspector();
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentClassSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentClassSubject.java
index a42fa4b..cb09a17 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentClassSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentClassSubject.java
@@ -5,6 +5,7 @@
 package com.android.tools.r8.utils.codeinspector;
 
 import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.graph.AccessFlags;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.naming.ClassNamingForNameMapper;
@@ -21,11 +22,6 @@
   }
 
   @Override
-  public boolean isFinal() {
-    throw new Unreachable("Cannot determine if an absent class is final");
-  }
-
-  @Override
   public boolean isPresent() {
     return false;
   }
@@ -91,11 +87,6 @@
   }
 
   @Override
-  public boolean isPublic() {
-    throw new Unreachable("Cannot determine if an absent class is public");
-  }
-
-  @Override
   public boolean isImplementing(ClassSubject subject) {
     throw new Unreachable("Cannot determine if an absent class is implementing a given interface");
   }
@@ -121,6 +112,11 @@
   }
 
   @Override
+  public AccessFlags<?> getAccessFlags() {
+    throw new Unreachable("Absent class has no access flags");
+  }
+
+  @Override
   public String getOriginalName() {
     return reference.getTypeName();
   }
@@ -181,11 +177,6 @@
   }
 
   @Override
-  public boolean isSynthetic() {
-    throw new Unreachable("Cannot determine if an absent class is synthetic");
-  }
-
-  @Override
   public boolean isSynthesizedJavaLambdaClass() {
     throw new Unreachable("Cannot determine if an absent class is a synthesized lambda class");
   }
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentFieldSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentFieldSubject.java
index f41135f..ceafd09 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentFieldSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentFieldSubject.java
@@ -5,6 +5,7 @@
 package com.android.tools.r8.utils.codeinspector;
 
 import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.graph.AccessFlags;
 import com.android.tools.r8.graph.DexEncodedField;
 import com.android.tools.r8.graph.DexValue;
 import com.android.tools.r8.naming.MemberNaming.Signature;
@@ -12,36 +13,6 @@
 public class AbsentFieldSubject extends FieldSubject {
 
   @Override
-  public boolean isPublic() {
-    throw new Unreachable("Cannot determine if an absent field is public");
-  }
-
-  @Override
-  public boolean isProtected() {
-    throw new Unreachable("Cannot determine if an absent field is protected");
-  }
-
-  @Override
-  public boolean isPrivate() {
-    throw new Unreachable("Cannot determine if an absent field is private");
-  }
-
-  @Override
-  public boolean isPackagePrivate() {
-    throw new Unreachable("Cannot determine if an absent field is package-private");
-  }
-
-  @Override
-  public boolean isStatic() {
-    throw new Unreachable("Cannot determine if an absent field is static");
-  }
-
-  @Override
-  public boolean isFinal() {
-    throw new Unreachable("Cannot determine if an absent field is final");
-  }
-
-  @Override
   public boolean isPresent() {
     return false;
   }
@@ -52,11 +23,6 @@
   }
 
   @Override
-  public boolean isSynthetic() {
-    throw new Unreachable("Cannot determine if an absent field is synthetic");
-  }
-
-  @Override
   public Signature getOriginalSignature() {
     return null;
   }
@@ -100,4 +66,9 @@
   public String getJvmFieldSignatureAsString() {
     return null;
   }
+
+  @Override
+  public AccessFlags<?> getAccessFlags() {
+    throw new Unreachable("Absent field has no access flags");
+  }
 }
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentMethodSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentMethodSubject.java
index 37f26db..ec133d7 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentMethodSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentMethodSubject.java
@@ -30,41 +30,6 @@
   }
 
   @Override
-  public boolean isPublic() {
-    throw new Unreachable("Cannot determine if an absent method is public");
-  }
-
-  @Override
-  public boolean isProtected() {
-    throw new Unreachable("Cannot determine if an absent method is protected");
-  }
-
-  @Override
-  public boolean isPrivate() {
-    throw new Unreachable("Cannot determine if an absent method is private");
-  }
-
-  @Override
-  public boolean isPackagePrivate() {
-    throw new Unreachable("Cannot determine if an absent method is package-private");
-  }
-
-  @Override
-  public boolean isStatic() {
-    throw new Unreachable("Cannot determine if an absent method is static");
-  }
-
-  @Override
-  public boolean isSynthetic() {
-    throw new Unreachable("Cannot determine if an absent method is synthetic");
-  }
-
-  @Override
-  public boolean isFinal() {
-    throw new Unreachable("Cannot determine if an absent method is final");
-  }
-
-  @Override
   public boolean isAbstract() {
     throw new Unreachable("Cannot determine if an absent method is abstract");
   }
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/ClassOrMemberSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/ClassOrMemberSubject.java
index 14666f1..d86cc6c 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/ClassOrMemberSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/ClassOrMemberSubject.java
@@ -4,7 +4,39 @@
 
 package com.android.tools.r8.utils.codeinspector;
 
+import com.android.tools.r8.graph.AccessFlags;
+
 public abstract class ClassOrMemberSubject extends Subject {
 
-  public abstract boolean isFinal();
+  public abstract AccessFlags<?> getAccessFlags();
+
+  public abstract String getOriginalName();
+
+  public final boolean isFinal() {
+    return getAccessFlags().isFinal();
+  }
+
+  public final boolean isPackagePrivate() {
+    return getAccessFlags().isPackagePrivate();
+  }
+
+  public final boolean isPrivate() {
+    return getAccessFlags().isPrivate();
+  }
+
+  public final boolean isProtected() {
+    return getAccessFlags().isProtected();
+  }
+
+  public final boolean isPublic() {
+    return getAccessFlags().isPublic();
+  }
+
+  public final boolean isStatic() {
+    return getAccessFlags().isStatic();
+  }
+
+  public final boolean isSynthetic() {
+    return getAccessFlags().isSynthetic();
+  }
 }
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/ClassSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/ClassSubject.java
index 20d7466..cec4e2b 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/ClassSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/ClassSubject.java
@@ -165,8 +165,6 @@
 
   public abstract boolean isAnnotation();
 
-  public abstract boolean isPublic();
-
   public abstract boolean isImplementing(ClassSubject subject);
 
   public abstract boolean isImplementing(Class<?> clazz);
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/CodeInspector.java b/src/test/java/com/android/tools/r8/utils/codeinspector/CodeInspector.java
index d70775f..5f67ef0 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/CodeInspector.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/CodeInspector.java
@@ -23,6 +23,7 @@
 import com.android.tools.r8.graph.DexCode.TryHandler;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.ir.conversion.SwitchPayloadResolver;
 import com.android.tools.r8.ir.desugar.InterfaceMethodRewriter;
 import com.android.tools.r8.naming.ClassNameMapper;
 import com.android.tools.r8.naming.ClassNamingForNameMapper;
@@ -330,7 +331,8 @@
     return originalTypeName != null ? originalTypeName : minifiedTypeName;
   }
 
-  InstructionSubject createInstructionSubject(Instruction instruction, MethodSubject method) {
+  InstructionSubject createInstructionSubject(
+      Instruction instruction, MethodSubject method, SwitchPayloadResolver switchPayloadResolver) {
     DexInstructionSubject dexInst = new DexInstructionSubject(instruction, method);
     if (dexInst.isInvoke()) {
       return new InvokeDexInstructionSubject(this, instruction, method);
@@ -342,6 +344,8 @@
       return new ConstStringDexInstructionSubject(instruction, method);
     } else if (dexInst.isCheckCast()) {
       return new CheckCastDexInstructionSubject(instruction, method);
+    } else if (dexInst.isSwitch()) {
+      return new SwitchDexInstructionSubject(instruction, method, switchPayloadResolver);
     } else {
       return dexInst;
     }
@@ -359,6 +363,8 @@
       return new ConstStringCfInstructionSubject(instruction, method);
     } else if (cfInst.isCheckCast()) {
       return new CheckCastCfInstructionSubject(instruction, method);
+    } else if (cfInst.isSwitch()) {
+      return new SwitchCfInstructionSubject(instruction, method);
     } else {
       return cfInst;
     }
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/DexInstructionIterator.java b/src/test/java/com/android/tools/r8/utils/codeinspector/DexInstructionIterator.java
index 0d98b29..1ea464c 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/DexInstructionIterator.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/DexInstructionIterator.java
@@ -4,8 +4,11 @@
 
 package com.android.tools.r8.utils.codeinspector;
 
+import com.android.tools.r8.code.Instruction;
+import com.android.tools.r8.code.SwitchPayload;
 import com.android.tools.r8.graph.Code;
 import com.android.tools.r8.graph.DexCode;
+import com.android.tools.r8.ir.conversion.SwitchPayloadResolver;
 import java.util.NoSuchElementException;
 
 class DexInstructionIterator implements InstructionIterator {
@@ -13,6 +16,7 @@
   private final CodeInspector codeInspector;
   private final DexCode code;
   private final MethodSubject methodSubject;
+  private SwitchPayloadResolver switchPayloadResolver;
   private int index;
 
   DexInstructionIterator(CodeInspector codeInspector, MethodSubject method) {
@@ -35,6 +39,24 @@
     if (index == code.instructions.length) {
       throw new NoSuchElementException();
     }
-    return codeInspector.createInstructionSubject(code.instructions[index++], methodSubject);
+    if (code.instructions[index].isIntSwitch()) {
+      ensureSwitchPayloadResolver();
+    }
+    return codeInspector.createInstructionSubject(
+        code.instructions[index++], methodSubject, switchPayloadResolver);
+  }
+
+  private void ensureSwitchPayloadResolver() {
+    if (switchPayloadResolver == null) {
+      switchPayloadResolver = new SwitchPayloadResolver();
+      for (Instruction instruction : code.instructions) {
+        if (instruction.isIntSwitch()) {
+          switchPayloadResolver.addPayloadUser(instruction);
+        }
+        if (instruction.isSwitchPayload()) {
+          switchPayloadResolver.resolve((SwitchPayload) instruction);
+        }
+      }
+    }
   }
 }
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundClassSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundClassSubject.java
index 4220b50..0bfe560 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundClassSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundClassSubject.java
@@ -10,6 +10,7 @@
 import com.android.tools.r8.TestRuntime.CfRuntime;
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.ToolHelper.ProcessResult;
+import com.android.tools.r8.graph.AccessFlags;
 import com.android.tools.r8.graph.DexAnnotation;
 import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexEncodedField;
@@ -64,21 +65,11 @@
   }
 
   @Override
-  public boolean isFinal() {
-    return dexClass.isFinal();
-  }
-
-  @Override
   public boolean isPresent() {
     return true;
   }
 
   @Override
-  public boolean isSynthetic() {
-    return dexClass.accessFlags.isSynthetic();
-  }
-
-  @Override
   public void forAllMethods(Consumer<FoundMethodSubject> inspection) {
     CodeInspector.forAll(
         dexClass.directMethods(),
@@ -296,11 +287,6 @@
   }
 
   @Override
-  public boolean isPublic() {
-    return dexClass.accessFlags.isPublic();
-  }
-
-  @Override
   public boolean isImplementing(ClassSubject subject) {
     assertTrue(subject.isPresent());
     for (DexType itf : getDexProgramClass().interfaces) {
@@ -363,6 +349,11 @@
   }
 
   @Override
+  public AccessFlags<?> getAccessFlags() {
+    return getDexProgramClass().getAccessFlags();
+  }
+
+  @Override
   public String getOriginalName() {
     if (naming != null) {
       return naming.originalName;
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundFieldSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundFieldSubject.java
index 4504213..b071916 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundFieldSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundFieldSubject.java
@@ -4,6 +4,7 @@
 
 package com.android.tools.r8.utils.codeinspector;
 
+import com.android.tools.r8.graph.AccessFlags;
 import com.android.tools.r8.graph.DexAnnotation;
 import com.android.tools.r8.graph.DexEncodedField;
 import com.android.tools.r8.graph.DexField;
@@ -32,36 +33,6 @@
   }
 
   @Override
-  public boolean isPublic() {
-    return dexField.accessFlags.isPublic();
-  }
-
-  @Override
-  public boolean isProtected() {
-    return dexField.accessFlags.isProtected();
-  }
-
-  @Override
-  public boolean isPrivate() {
-    return dexField.accessFlags.isPrivate();
-  }
-
-  @Override
-  public boolean isPackagePrivate() {
-    return !isPublic() && !isProtected() && !isPrivate();
-  }
-
-  @Override
-  public boolean isStatic() {
-    return dexField.accessFlags.isStatic();
-  }
-
-  @Override
-  public boolean isFinal() {
-    return dexField.accessFlags.isFinal();
-  }
-
-  @Override
   public boolean isPresent() {
     return true;
   }
@@ -71,11 +42,6 @@
     return clazz.naming != null && !getFinalSignature().name.equals(getOriginalSignature().name);
   }
 
-  @Override
-  public boolean isSynthetic() {
-    return dexField.accessFlags.isSynthetic();
-  }
-
   public TypeSubject type() {
     return new TypeSubject(codeInspector, dexField.field.type);
   }
@@ -159,4 +125,9 @@
   public String getJvmFieldSignatureAsString() {
     return dexField.field.name.toString() + ":" + dexField.field.type.toDescriptorString();
   }
+
+  @Override
+  public AccessFlags<?> getAccessFlags() {
+    return getField().getAccessFlags();
+  }
 }
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundMethodSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundMethodSubject.java
index c2ae3ab..17fffc3 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundMethodSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundMethodSubject.java
@@ -78,41 +78,6 @@
   }
 
   @Override
-  public boolean isPublic() {
-    return dexMethod.accessFlags.isPublic();
-  }
-
-  @Override
-  public boolean isProtected() {
-    return dexMethod.accessFlags.isProtected();
-  }
-
-  @Override
-  public boolean isPrivate() {
-    return dexMethod.accessFlags.isPrivate();
-  }
-
-  @Override
-  public boolean isPackagePrivate() {
-    return !isPublic() && !isProtected() && !isPrivate();
-  }
-
-  @Override
-  public boolean isStatic() {
-    return dexMethod.accessFlags.isStatic();
-  }
-
-  @Override
-  public boolean isSynthetic() {
-    return dexMethod.accessFlags.isSynthetic();
-  }
-
-  @Override
-  public boolean isFinal() {
-    return dexMethod.accessFlags.isFinal();
-  }
-
-  @Override
   public boolean isAbstract() {
     return dexMethod.accessFlags.isAbstract();
   }
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/HorizontallyMergedClassesInspector.java b/src/test/java/com/android/tools/r8/utils/codeinspector/HorizontallyMergedClassesInspector.java
index 7cfc3fe..e90df17 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/HorizontallyMergedClassesInspector.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/HorizontallyMergedClassesInspector.java
@@ -14,6 +14,8 @@
 import com.android.tools.r8.horizontalclassmerging.HorizontallyMergedClasses;
 import com.android.tools.r8.utils.SetUtils;
 import com.google.common.collect.Sets;
+import java.util.Arrays;
+import java.util.Collection;
 import java.util.Set;
 import java.util.function.BiConsumer;
 
@@ -73,6 +75,17 @@
     return this;
   }
 
+  public HorizontallyMergedClassesInspector assertClassesNotMerged(Collection<Class<?>> classes) {
+    for (Class<?> clazz : classes) {
+      assertClassNotMerged(clazz);
+    }
+    return this;
+  }
+
+  public HorizontallyMergedClassesInspector assertClassesNotMerged(Class<?>... classes) {
+    return assertClassesNotMerged(Arrays.asList(classes));
+  }
+
   public HorizontallyMergedClassesInspector assertClassNotMergedIntoDifferentType(Class<?> clazz) {
     assertFalse(
         horizontallyMergedClasses.hasBeenMergedIntoDifferentType(toDexType(clazz, dexItemFactory)));
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 8b34260..959a2c9 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
@@ -109,6 +109,10 @@
 
   boolean isSwitch();
 
+  default SwitchInstructionSubject asSwitch() {
+    return null;
+  }
+
   boolean isPackedSwitch();
 
   boolean isSparseSwitch();
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/Matchers.java b/src/test/java/com/android/tools/r8/utils/codeinspector/Matchers.java
index c2a8cc8..28f0b9c 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/Matchers.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/Matchers.java
@@ -304,11 +304,11 @@
     return hasVisibility(Visibility.PACKAGE_PRIVATE);
   }
 
-  public static <T extends MemberSubject> Matcher<T> isPublic() {
+  public static <T extends ClassOrMemberSubject> Matcher<T> isPublic() {
     return hasVisibility(Visibility.PUBLIC);
   }
 
-  private static <T extends MemberSubject> Matcher<T> hasVisibility(Visibility visibility) {
+  private static <T extends ClassOrMemberSubject> Matcher<T> hasVisibility(Visibility visibility) {
     return new TypeSafeMatcher<T>() {
       @Override
       public boolean matchesSafely(final T subject) {
@@ -340,15 +340,9 @@
 
       @Override
       public void describeMismatchSafely(final T subject, Description description) {
-        description
-            .appendText("method ")
-            .appendValue(subject.getOriginalName())
-            .appendText(" was ");
+        description.appendText("item ").appendValue(subject.getOriginalName()).appendText(" was ");
         if (subject.isPresent()) {
-          AccessFlags<?> accessFlags =
-              subject.isMethodSubject()
-                  ? subject.asMethodSubject().getMethod().accessFlags
-                  : subject.asFieldSubject().getField().accessFlags;
+          AccessFlags<?> accessFlags = subject.getAccessFlags();
           if (accessFlags.isPublic()) {
             description.appendText("public");
           } else if (accessFlags.isProtected()) {
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/MemberSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/MemberSubject.java
index 847675f..d8fd4b9 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/MemberSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/MemberSubject.java
@@ -8,16 +8,6 @@
 
 public abstract class MemberSubject extends ClassOrMemberSubject {
 
-  public abstract boolean isPublic();
-
-  public abstract boolean isProtected();
-
-  public abstract boolean isPrivate();
-
-  public abstract boolean isPackagePrivate();
-
-  public abstract boolean isStatic();
-
   public abstract Signature getOriginalSignature();
 
   public abstract Signature getFinalSignature();
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/MethodSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/MethodSubject.java
index 4826876..536fa58 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/MethodSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/MethodSubject.java
@@ -38,6 +38,7 @@
     return null;
   }
 
+  @Override
   public abstract MethodAccessFlags getAccessFlags();
 
   @Override
@@ -45,9 +46,6 @@
 
   public abstract String getOriginalSignatureAttribute();
 
-  @Override
-  public abstract String getFinalSignatureAttribute();
-
   public abstract DexEncodedMethod getMethod();
 
   public abstract ProgramMethod getProgramMethod();
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/StaticallyMergedClassesInspector.java b/src/test/java/com/android/tools/r8/utils/codeinspector/StaticallyMergedClassesInspector.java
deleted file mode 100644
index 0b45f27..0000000
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/StaticallyMergedClassesInspector.java
+++ /dev/null
@@ -1,27 +0,0 @@
-// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-
-package com.android.tools.r8.utils.codeinspector;
-
-import com.android.tools.r8.graph.DexItemFactory;
-import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.graph.classmerging.StaticallyMergedClasses;
-import java.util.Set;
-import java.util.function.BiConsumer;
-
-public class StaticallyMergedClassesInspector {
-
-  private final DexItemFactory dexItemFactory;
-  private final StaticallyMergedClasses staticallyMergedClasses;
-
-  public StaticallyMergedClassesInspector(
-      DexItemFactory dexItemFactory, StaticallyMergedClasses staticallyMergedClasses) {
-    this.dexItemFactory = dexItemFactory;
-    this.staticallyMergedClasses = staticallyMergedClasses;
-  }
-
-  public void forEachMergeGroup(BiConsumer<Set<DexType>, DexType> consumer) {
-    staticallyMergedClasses.forEachMergeGroup(consumer);
-  }
-}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/SwitchCfInstructionSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/SwitchCfInstructionSubject.java
new file mode 100644
index 0000000..4b5cbc6
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/SwitchCfInstructionSubject.java
@@ -0,0 +1,27 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.utils.codeinspector;
+
+import com.android.tools.r8.cf.code.CfInstruction;
+import com.android.tools.r8.cf.code.CfSwitch;
+import java.util.List;
+
+public class SwitchCfInstructionSubject extends CfInstructionSubject
+    implements SwitchInstructionSubject {
+  public SwitchCfInstructionSubject(CfInstruction instruction, MethodSubject method) {
+    super(instruction, method);
+    assert isSwitch();
+  }
+
+  @Override
+  public List<Integer> getKeys() {
+    return ((CfSwitch) instruction).getKeys();
+  }
+
+  @Override
+  public SwitchInstructionSubject asSwitch() {
+    return this;
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/SwitchDexInstructionSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/SwitchDexInstructionSubject.java
new file mode 100644
index 0000000..a49553f
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/SwitchDexInstructionSubject.java
@@ -0,0 +1,57 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.utils.codeinspector;
+
+import com.android.tools.r8.code.Instruction;
+import com.android.tools.r8.code.PackedSwitch;
+import com.android.tools.r8.code.SparseSwitch;
+import com.android.tools.r8.ir.conversion.SwitchPayloadResolver;
+import it.unimi.dsi.fastutil.ints.IntArrayList;
+import java.util.List;
+
+public class SwitchDexInstructionSubject extends DexInstructionSubject
+    implements SwitchInstructionSubject {
+
+  private final SwitchPayloadResolver switchPayloadResolver;
+
+  public SwitchDexInstructionSubject(
+      Instruction instruction, MethodSubject method, SwitchPayloadResolver switchPayloadResolver) {
+    super(instruction, method);
+    assert isSwitch();
+    assert instruction.isIntSwitch();
+    assert switchPayloadResolver != null;
+    this.switchPayloadResolver = switchPayloadResolver;
+  }
+
+  @Override
+  public List<Integer> getKeys() {
+    if (instruction instanceof PackedSwitch) {
+      assert switchPayloadResolver.getKeys(instruction.getOffset() + instruction.getPayloadOffset())
+              .length
+          == 1;
+      int firstKey =
+          switchPayloadResolver
+              .getKeys(instruction.getOffset() + instruction.getPayloadOffset())[0];
+      int numberOfKeys =
+          switchPayloadResolver.absoluteTargets(
+                  instruction.getOffset() + instruction.getPayloadOffset())
+              .length;
+      List<Integer> keys = new IntArrayList(numberOfKeys);
+      for (int i = 0; i < numberOfKeys; i++) {
+        keys.add(firstKey + i);
+      }
+      return keys;
+    } else {
+      assert instruction instanceof SparseSwitch;
+      return new IntArrayList(
+          switchPayloadResolver.getKeys(instruction.getOffset() + instruction.getPayloadOffset()));
+    }
+  }
+
+  @Override
+  public SwitchInstructionSubject asSwitch() {
+    return this;
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/SwitchInstructionSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/SwitchInstructionSubject.java
new file mode 100644
index 0000000..c04b4f8
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/SwitchInstructionSubject.java
@@ -0,0 +1,11 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.utils.codeinspector;
+
+import java.util.List;
+
+public interface SwitchInstructionSubject extends InstructionSubject {
+  List<Integer> getKeys();
+}
diff --git a/third_party/chrome/clank_google3_prebuilt.tar.gz.sha1 b/third_party/chrome/clank_google3_prebuilt.tar.gz.sha1
new file mode 100644
index 0000000..862139a
--- /dev/null
+++ b/third_party/chrome/clank_google3_prebuilt.tar.gz.sha1
@@ -0,0 +1 @@
+4af48dbfdd1333bb41c716a62a136dfd1fa2354d
\ No newline at end of file
diff --git a/tools/gradle.py b/tools/gradle.py
index b6f5187..63c92b5 100755
--- a/tools/gradle.py
+++ b/tools/gradle.py
@@ -26,6 +26,9 @@
 
 def ParseOptions():
   parser = argparse.ArgumentParser(description = 'Call gradle.')
+  parser.add_argument('--exclude-deps', '--exclude_deps',
+      help='Build without internalized dependencies.',
+      default=False, action='store_true')
   parser.add_argument('--no-internal', '--no_internal',
       help='Do not build with support for Google internal tests.',
       default=False, action='store_true')
@@ -107,6 +110,8 @@
     args.append('-Dorg.gradle.java.home=' + options.java_home)
   if options.no_internal:
     args.append('-Pno_internal')
+  if options.exclude_deps:
+    args.append('-Pexclude_deps')
   if options.worktree:
     args.append('-g=' + os.path.join(utils.REPO_ROOT, ".gradle_user_home"))
   return RunGradle(args)