Merge commit '0aec7b3cb857285b21e47094b8cf5c56ff6e3b61' into dev-release
diff --git a/src/main/java/com/android/tools/r8/graph/AccessFlags.java b/src/main/java/com/android/tools/r8/graph/AccessFlags.java
index 68921ca..cdf0ad1 100644
--- a/src/main/java/com/android/tools/r8/graph/AccessFlags.java
+++ b/src/main/java/com/android/tools/r8/graph/AccessFlags.java
@@ -230,8 +230,9 @@
     promote(Constants.ACC_FINAL);
   }
 
-  public void demoteFromFinal() {
+  public T demoteFromFinal() {
     demote(Constants.ACC_FINAL);
+    return self();
   }
 
   public boolean isPromotedToPublic() {
diff --git a/src/main/java/com/android/tools/r8/graph/DexCode.java b/src/main/java/com/android/tools/r8/graph/DexCode.java
index 11d3e17..7689694 100644
--- a/src/main/java/com/android/tools/r8/graph/DexCode.java
+++ b/src/main/java/com/android/tools/r8/graph/DexCode.java
@@ -8,7 +8,6 @@
 import com.android.tools.r8.code.ReturnVoid;
 import com.android.tools.r8.code.SwitchPayload;
 import com.android.tools.r8.dex.CodeToKeep;
-import com.android.tools.r8.dex.Constants;
 import com.android.tools.r8.dex.IndexedItemCollection;
 import com.android.tools.r8.dex.JumboStringRewriter;
 import com.android.tools.r8.dex.MixedSectionCollection;
@@ -153,7 +152,7 @@
           || Arrays.stream(instructions).noneMatch(Instruction::isConstString);
       assert Arrays.stream(instructions).noneMatch(Instruction::isDexItemBasedConstString);
       if (highestSortingString != null
-          && mapping.getOffsetFor(highestSortingString) > Constants.MAX_NON_JUMBO_INDEX) {
+          && highestSortingString.isGreaterThanOrEqualTo(mapping.getFirstJumboString())) {
         firstJumboString = mapping.getFirstJumboString();
       }
     }
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 865a536..1f58402 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
@@ -1089,9 +1089,6 @@
 
   public DexEncodedMethod toTypeSubstitutedMethod(DexMethod method, Consumer<Builder> consumer) {
     checkIfObsolete();
-    if (this.getReference() == method) {
-      return this;
-    }
     Builder builder = builder(this);
     if (isNonPrivateVirtualMethod() && isLibraryMethodOverride() != OptionalBool.unknown()) {
       builder.setIsLibraryMethodOverride(isLibraryMethodOverride());
@@ -1184,7 +1181,7 @@
   }
 
   public static DexEncodedMethod createDesugaringForwardingMethod(
-      DexClassAndMethod target, DexClass clazz, DexMethod forwardMethod, DexItemFactory factory) {
+      DexEncodedMethod target, DexClass clazz, DexMethod forwardMethod, DexItemFactory factory) {
     DexMethod method = target.getReference();
     assert forwardMethod != null;
     // New method will have the same name, proto, and also all the flags of the
@@ -1206,8 +1203,8 @@
                 .setNonStaticSource(newMethod)
                 .setStaticTarget(forwardMethod, isInterfaceMethodReference)
                 .build())
-        .setApiLevelForDefinition(target.getDefinition().getApiLevelForDefinition())
-        .setApiLevelForCode(target.getDefinition().getApiLevelForCode())
+        .setApiLevelForDefinition(target.getApiLevelForDefinition())
+        .setApiLevelForCode(target.getApiLevelForCode())
         .build();
   }
 
diff --git a/src/main/java/com/android/tools/r8/graph/DexTypeList.java b/src/main/java/com/android/tools/r8/graph/DexTypeList.java
index f399895..f07c38e 100644
--- a/src/main/java/com/android/tools/r8/graph/DexTypeList.java
+++ b/src/main/java/com/android/tools/r8/graph/DexTypeList.java
@@ -64,7 +64,7 @@
   }
 
   public DexTypeList keepIf(Predicate<DexType> predicate) {
-    DexType[] filtered = ArrayUtils.filter(DexType[].class, values, predicate);
+    DexType[] filtered = ArrayUtils.filter(values, predicate, DexType.EMPTY_ARRAY);
     if (filtered != values) {
       return DexTypeList.create(filtered);
     }
diff --git a/src/main/java/com/android/tools/r8/graph/MethodArrayBacking.java b/src/main/java/com/android/tools/r8/graph/MethodArrayBacking.java
index 6930912..545645e 100644
--- a/src/main/java/com/android/tools/r8/graph/MethodArrayBacking.java
+++ b/src/main/java/com/android/tools/r8/graph/MethodArrayBacking.java
@@ -13,6 +13,7 @@
 import java.util.Arrays;
 import java.util.Collection;
 import java.util.List;
+import java.util.Objects;
 import java.util.Set;
 import java.util.function.Consumer;
 import java.util.function.Function;
@@ -337,33 +338,75 @@
 
   @Override
   public void replaceMethods(Function<DexEncodedMethod, DexEncodedMethod> replacement) {
-    replaceDirectMethods(replacement);
-    replaceVirtualMethods(replacement);
+    List<DexEncodedMethod> newVirtualMethods = internalReplaceDirectMethods(replacement);
+    List<DexEncodedMethod> newDirectMethods = internalReplaceVirtualMethods(replacement);
+    addDirectMethods(newDirectMethods);
+    addVirtualMethods(newVirtualMethods);
   }
 
   @Override
   public void replaceDirectMethods(Function<DexEncodedMethod, DexEncodedMethod> replacement) {
+    List<DexEncodedMethod> newVirtualMethods = internalReplaceDirectMethods(replacement);
+    addVirtualMethods(newVirtualMethods);
+  }
+
+  private List<DexEncodedMethod> internalReplaceDirectMethods(
+      Function<DexEncodedMethod, DexEncodedMethod> replacement) {
+    List<DexEncodedMethod> newVirtualMethods = new ArrayList<>();
     for (int i = 0; i < directMethods.length; i++) {
       DexEncodedMethod method = directMethods[i];
       DexEncodedMethod newMethod = replacement.apply(method);
       assert newMethod != null;
       if (method != newMethod) {
-        assert belongsToDirectPool(newMethod);
-        directMethods[i] = newMethod;
+        if (belongsToDirectPool(newMethod)) {
+          directMethods[i] = newMethod;
+        } else {
+          directMethods[i] = null;
+          newVirtualMethods.add(newMethod);
+        }
       }
     }
+    if (!newVirtualMethods.isEmpty()) {
+      directMethods =
+          ArrayUtils.filter(
+              directMethods,
+              Objects::nonNull,
+              DexEncodedMethod.EMPTY_ARRAY,
+              directMethods.length - newVirtualMethods.size());
+    }
+    return newVirtualMethods;
   }
 
   @Override
   public void replaceVirtualMethods(Function<DexEncodedMethod, DexEncodedMethod> replacement) {
+    List<DexEncodedMethod> newDirectMethods = internalReplaceVirtualMethods(replacement);
+    addDirectMethods(newDirectMethods);
+  }
+
+  private List<DexEncodedMethod> internalReplaceVirtualMethods(
+      Function<DexEncodedMethod, DexEncodedMethod> replacement) {
+    List<DexEncodedMethod> newDirectMethods = new ArrayList<>();
     for (int i = 0; i < virtualMethods.length; i++) {
       DexEncodedMethod method = virtualMethods[i];
       DexEncodedMethod newMethod = replacement.apply(method);
       if (method != newMethod) {
-        assert belongsToVirtualPool(newMethod);
-        virtualMethods[i] = newMethod;
+        if (belongsToVirtualPool(newMethod)) {
+          virtualMethods[i] = newMethod;
+        } else {
+          virtualMethods[i] = null;
+          newDirectMethods.add(newMethod);
+        }
       }
     }
+    if (!newDirectMethods.isEmpty()) {
+      virtualMethods =
+          ArrayUtils.filter(
+              virtualMethods,
+              Objects::nonNull,
+              DexEncodedMethod.EMPTY_ARRAY,
+              virtualMethods.length - newDirectMethods.size());
+    }
+    return newDirectMethods;
   }
 
   @Override
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 4f901fd..68c1dde 100644
--- a/src/main/java/com/android/tools/r8/graph/PrunedItems.java
+++ b/src/main/java/com/android/tools/r8/graph/PrunedItems.java
@@ -36,6 +36,10 @@
     return new Builder();
   }
 
+  public Builder toBuilder() {
+    return new Builder(this);
+  }
+
   public static PrunedItems empty(DexApplication application) {
     return new Builder().setPrunedApp(application).build();
   }
@@ -113,6 +117,17 @@
     private final Set<DexField> removedFields = Sets.newIdentityHashSet();
     private Set<DexMethod> removedMethods = Sets.newIdentityHashSet();
 
+    Builder() {}
+
+    Builder(PrunedItems prunedItems) {
+      additionalPinnedItems.addAll(prunedItems.getAdditionalPinnedItems());
+      noLongerSyntheticItems.addAll(prunedItems.getNoLongerSyntheticItems());
+      prunedApp = prunedItems.getPrunedApp();
+      removedClasses.addAll(prunedItems.getRemovedClasses());
+      removedFields.addAll(prunedItems.getRemovedFields());
+      removedMethods.addAll(prunedItems.getRemovedMethods());
+    }
+
     public Builder setPrunedApp(DexApplication prunedApp) {
       this.prunedApp = prunedApp;
       return this;
@@ -129,6 +144,12 @@
       return this;
     }
 
+    public Builder addRemovedClass(DexType removedClass) {
+      this.noLongerSyntheticItems.add(removedClass);
+      this.removedClasses.add(removedClass);
+      return this;
+    }
+
     public Builder addRemovedClasses(Set<DexType> removedClasses) {
       this.noLongerSyntheticItems.addAll(removedClasses);
       this.removedClasses.addAll(removedClasses);
diff --git a/src/main/java/com/android/tools/r8/graph/RewrittenPrototypeDescription.java b/src/main/java/com/android/tools/r8/graph/RewrittenPrototypeDescription.java
index dda7746..bf3849c 100644
--- a/src/main/java/com/android/tools/r8/graph/RewrittenPrototypeDescription.java
+++ b/src/main/java/com/android/tools/r8/graph/RewrittenPrototypeDescription.java
@@ -14,6 +14,7 @@
 import com.android.tools.r8.ir.conversion.ExtraUnusedNullParameter;
 import com.android.tools.r8.ir.optimize.info.MethodOptimizationInfoFixer;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.ConsumerUtils;
 import com.android.tools.r8.utils.IntObjConsumer;
 import com.android.tools.r8.utils.IteratorUtils;
@@ -450,6 +451,11 @@
       return removed;
     }
 
+    public int numberOfRemovedNonReceiverArguments(DexEncodedMethod method) {
+      return numberOfRemovedArguments()
+          - BooleanUtils.intValue(method.isInstance() && isArgumentRemoved(0));
+    }
+
     public boolean hasArgumentInfo(int argumentIndex) {
       return argumentInfos.containsKey(argumentIndex);
     }
@@ -542,14 +548,12 @@
     }
 
     public DexType[] rewriteParameters(DexEncodedMethod encodedMethod) {
-      // Currently not allowed to remove the receiver of an instance method. This would involve
-      // changing invoke-direct/invoke-virtual into invoke-static.
-      assert encodedMethod.isStatic() || !getArgumentInfo(0).isRemovedArgumentInfo();
-      DexType[] params = encodedMethod.getReference().proto.parameters.values;
+      DexType[] params = encodedMethod.getParameters().values;
       if (isEmpty()) {
         return params;
       }
-      DexType[] newParams = new DexType[params.length - numberOfRemovedArguments()];
+      DexType[] newParams =
+          new DexType[params.length - numberOfRemovedNonReceiverArguments(encodedMethod)];
       int offset = encodedMethod.getFirstNonReceiverArgumentIndex();
       int newParamIndex = 0;
       for (int oldParamIndex = 0; oldParamIndex < params.length; oldParamIndex++) {
diff --git a/src/main/java/com/android/tools/r8/graph/bytecodemetadata/BytecodeInstructionMetadata.java b/src/main/java/com/android/tools/r8/graph/bytecodemetadata/BytecodeInstructionMetadata.java
index 97839cf..5d40177 100644
--- a/src/main/java/com/android/tools/r8/graph/bytecodemetadata/BytecodeInstructionMetadata.java
+++ b/src/main/java/com/android/tools/r8/graph/bytecodemetadata/BytecodeInstructionMetadata.java
@@ -4,6 +4,11 @@
 
 package com.android.tools.r8.graph.bytecodemetadata;
 
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.ir.analysis.fieldaccess.TrivialFieldAccessReprocessor;
+import java.util.Collections;
+import java.util.Set;
+
 /**
  * A piece of information that can be attached to instructions in {@link
  * com.android.tools.r8.graph.CfCode} and {@link com.android.tools.r8.graph.DexCode}.
@@ -11,6 +16,16 @@
 public class BytecodeInstructionMetadata {
 
   /**
+   * Set for field-get instructions that are only used to invoke one or more instance methods.
+   *
+   * <p>This is used to skip field-get instructions that will be pruned as a result of method
+   * staticizing in the {@link TrivialFieldAccessReprocessor}. A common use case for this is Kotlin
+   * companion fields, which will generally not be read after the methods of a given companion class
+   * have been staticized.
+   */
+  private final Set<DexMethod> isReadForInvokeReceiver;
+
+  /**
    * Set for instance and static field read instructions which are only used to write the same
    * field.
    *
@@ -19,7 +34,9 @@
    */
   private final boolean isReadForWrite;
 
-  BytecodeInstructionMetadata(boolean isReadForWrite) {
+  private BytecodeInstructionMetadata(
+      Set<DexMethod> isReadForInvokeReceiver, boolean isReadForWrite) {
+    this.isReadForInvokeReceiver = isReadForInvokeReceiver;
     this.isReadForWrite = isReadForWrite;
   }
 
@@ -31,16 +48,32 @@
     return null;
   }
 
+  public boolean isReadForInvokeReceiver() {
+    return isReadForInvokeReceiver != null;
+  }
+
+  public Set<DexMethod> getReadForInvokeReceiver() {
+    return isReadForInvokeReceiver;
+  }
+
   public boolean isReadForWrite() {
     return isReadForWrite;
   }
 
   public static class Builder {
 
+    private Set<DexMethod> isReadForInvokeReceiver = Collections.emptySet();
     private boolean isReadForWrite;
 
     private boolean isEmpty() {
-      return !isReadForWrite;
+      return isReadForInvokeReceiver.isEmpty() && !isReadForWrite;
+    }
+
+    public Builder setIsReadForInvokeReceiver(Set<DexMethod> isReadForInvokeReceiver) {
+      assert isReadForInvokeReceiver != null;
+      assert !isReadForInvokeReceiver.isEmpty();
+      this.isReadForInvokeReceiver = isReadForInvokeReceiver;
+      return this;
     }
 
     public Builder setIsReadForWrite() {
@@ -50,7 +83,7 @@
 
     public BytecodeInstructionMetadata build() {
       assert !isEmpty();
-      return new BytecodeInstructionMetadata(isReadForWrite);
+      return new BytecodeInstructionMetadata(isReadForInvokeReceiver, isReadForWrite);
     }
   }
 }
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/ClassMerger.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/ClassMerger.java
index c76f43f..d7dc429 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/ClassMerger.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/ClassMerger.java
@@ -23,6 +23,7 @@
 import com.android.tools.r8.graph.MethodAccessFlags;
 import com.android.tools.r8.graph.ProgramMember;
 import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.graph.PrunedItems;
 import com.android.tools.r8.horizontalclassmerging.HorizontalClassMerger.Mode;
 import com.android.tools.r8.horizontalclassmerging.code.ClassInitializerMerger;
 import com.android.tools.r8.horizontalclassmerging.code.SyntheticInitializerConverter;
@@ -290,16 +291,21 @@
     group.getTarget().setInterfaces(DexTypeList.create(interfaces));
   }
 
-  void mergeFields() {
+  void mergeFields(PrunedItems.Builder prunedItemsBuilder) {
     if (group.hasClassIdField()) {
       appendClassIdField();
     }
-    mergeInstanceFields();
+    mergeInstanceFields(prunedItemsBuilder);
     mergeStaticFields();
   }
 
-  void mergeInstanceFields() {
-    group.forEachSource(DexClass::clearInstanceFields);
+  void mergeInstanceFields(PrunedItems.Builder prunedItemsBuilder) {
+    group.forEachSource(
+        clazz -> {
+          clazz.forEachInstanceField(
+              field -> prunedItemsBuilder.addRemovedField(field.getReference()));
+          clazz.clearInstanceFields();
+        });
     group.getTarget().setInstanceFields(classInstanceFieldsMerger.merge());
   }
 
@@ -310,16 +316,18 @@
   }
 
   public void mergeGroup(
+      PrunedItems.Builder prunedItemsBuilder,
       SyntheticArgumentClass syntheticArgumentClass,
       SyntheticInitializerConverter.Builder syntheticInitializerConverterBuilder) {
     fixAccessFlags();
     fixNestMemberAttributes();
     mergeAnnotations();
     mergeInterfaces();
-    mergeFields();
+    mergeFields(prunedItemsBuilder);
     mergeMethods(syntheticArgumentClass, syntheticInitializerConverterBuilder);
     group.getTarget().clearClassSignature();
     group.getTarget().forEachProgramMember(ProgramMember::clearGenericSignature);
+    group.forEachSource(clazz -> prunedItemsBuilder.addRemovedClass(clazz.getType()));
   }
 
   public static class Builder {
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMerger.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMerger.java
index b14f07c..ad8d9df 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMerger.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMerger.java
@@ -103,7 +103,9 @@
             : null;
     SyntheticInitializerConverter.Builder syntheticInitializerConverterBuilder =
         SyntheticInitializerConverter.builder(appView, codeProvider);
-    applyClassMergers(classMergers, syntheticArgumentClass, syntheticInitializerConverterBuilder);
+    PrunedItems prunedItems =
+        applyClassMergers(
+            classMergers, syntheticArgumentClass, syntheticInitializerConverterBuilder);
 
     SyntheticInitializerConverter syntheticInitializerConverter =
         syntheticInitializerConverterBuilder.build();
@@ -124,10 +126,7 @@
 
     // Prune keep info.
     KeepInfoCollection keepInfo = appView.getKeepInfo();
-    keepInfo.mutate(
-        mutator ->
-            mutator.removeKeepInfoForMergedClasses(
-                PrunedItems.builder().setRemovedClasses(mergedClasses.getSources()).build()));
+    keepInfo.mutate(mutator -> mutator.removeKeepInfoForMergedClasses(prunedItems));
 
     // Must rewrite AppInfoWithLiveness before pruning the merged classes, to ensure that allocation
     // sites, fields accesses, etc. are correctly transferred to the target classes.
@@ -142,12 +141,7 @@
     }
 
     appView.pruneItems(
-        PrunedItems.builder()
-            .setPrunedApp(appView.appInfo().app())
-            .addRemovedClasses(mergedClasses.getSources())
-            .addNoLongerSyntheticItems(mergedClasses.getSources())
-            .build(),
-        executorService);
+        prunedItems.toBuilder().setPrunedApp(appView.app()).build(), executorService);
   }
 
   private FieldAccessInfoCollectionModifier createFieldAccessInfoCollectionModifier(
@@ -220,13 +214,16 @@
   }
 
   /** Merges all class groups using {@link ClassMerger}. */
-  private void applyClassMergers(
+  private PrunedItems applyClassMergers(
       Collection<ClassMerger> classMergers,
       SyntheticArgumentClass syntheticArgumentClass,
       SyntheticInitializerConverter.Builder syntheticInitializerConverterBuilder) {
+    PrunedItems.Builder prunedItemsBuilder = PrunedItems.builder().setPrunedApp(appView.app());
     for (ClassMerger merger : classMergers) {
-      merger.mergeGroup(syntheticArgumentClass, syntheticInitializerConverterBuilder);
+      merger.mergeGroup(
+          prunedItemsBuilder, syntheticArgumentClass, syntheticInitializerConverterBuilder);
     }
+    return prunedItemsBuilder.build();
   }
 
   /**
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldAccessAnalysis.java b/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldAccessAnalysis.java
index e5a1aec..3ebd37a 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldAccessAnalysis.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldAccessAnalysis.java
@@ -28,6 +28,7 @@
   private final AppView<? extends AppInfoWithClassHierarchy> appView;
   private final FieldAssignmentTracker fieldAssignmentTracker;
   private final FieldBitAccessAnalysis fieldBitAccessAnalysis;
+  private final FieldReadForInvokeReceiverAnalysis fieldReadForInvokeReceiverAnalysis;
   private final FieldReadForWriteAnalysis fieldReadForWriteAnalysis;
 
   public FieldAccessAnalysis(AppView<AppInfoWithLiveness> appView) {
@@ -36,6 +37,7 @@
     this.fieldBitAccessAnalysis =
         options.enableFieldBitAccessAnalysis ? new FieldBitAccessAnalysis() : null;
     this.fieldAssignmentTracker = new FieldAssignmentTracker(appView);
+    this.fieldReadForInvokeReceiverAnalysis = new FieldReadForInvokeReceiverAnalysis(appView);
     this.fieldReadForWriteAnalysis = new FieldReadForWriteAnalysis(appView);
   }
 
@@ -44,10 +46,12 @@
       AppView<? extends AppInfoWithClassHierarchy> appView,
       FieldAssignmentTracker fieldAssignmentTracker,
       FieldBitAccessAnalysis fieldBitAccessAnalysis,
+      FieldReadForInvokeReceiverAnalysis fieldReadForInvokeReceiverAnalysis,
       FieldReadForWriteAnalysis fieldReadForWriteAnalysis) {
     this.appView = appView;
     this.fieldAssignmentTracker = fieldAssignmentTracker;
     this.fieldBitAccessAnalysis = fieldBitAccessAnalysis;
+    this.fieldReadForInvokeReceiverAnalysis = fieldReadForInvokeReceiverAnalysis;
     this.fieldReadForWriteAnalysis = fieldReadForWriteAnalysis;
   }
 
@@ -91,6 +95,10 @@
               fieldBitAccessAnalysis.recordFieldAccess(
                   fieldInstruction, field.getDefinition(), feedback);
             }
+            if (fieldReadForInvokeReceiverAnalysis != null) {
+              fieldReadForInvokeReceiverAnalysis.recordFieldAccess(
+                  fieldInstruction, field, bytecodeMetadataProviderBuilder, code.context());
+            }
             if (fieldReadForWriteAnalysis != null) {
               fieldReadForWriteAnalysis.recordFieldAccess(
                   fieldInstruction, field, bytecodeMetadataProviderBuilder);
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldReadForInvokeReceiverAnalysis.java b/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldReadForInvokeReceiverAnalysis.java
new file mode 100644
index 0000000..b2aded5
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldReadForInvokeReceiverAnalysis.java
@@ -0,0 +1,90 @@
+// Copyright (c) 2022, 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.fieldaccess;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.ProgramField;
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.graph.bytecodemetadata.BytecodeMetadataProvider;
+import com.android.tools.r8.ir.code.FieldInstruction;
+import com.android.tools.r8.ir.code.Instruction;
+import com.android.tools.r8.ir.code.InvokeMethodWithReceiver;
+import com.android.tools.r8.ir.code.StaticGet;
+import com.android.tools.r8.ir.code.Value;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.utils.WorkList;
+import com.google.common.collect.Sets;
+import java.util.Set;
+
+public class FieldReadForInvokeReceiverAnalysis {
+
+  private final AppView<AppInfoWithLiveness> appView;
+
+  FieldReadForInvokeReceiverAnalysis(AppView<AppInfoWithLiveness> appView) {
+    this.appView = appView;
+  }
+
+  public void recordFieldAccess(
+      FieldInstruction instruction,
+      ProgramField field,
+      BytecodeMetadataProvider.Builder bytecodeMetadataProviderBuilder,
+      ProgramMethod context) {
+    if (!instruction.isStaticGet()) {
+      return;
+    }
+
+    StaticGet staticGet = instruction.asStaticGet();
+    Set<DexMethod> methods = getMethods(staticGet.outValue(), context);
+    if (methods == null || methods.isEmpty()) {
+      return;
+    }
+
+    bytecodeMetadataProviderBuilder.addMetadata(
+        instruction, builder -> builder.setIsReadForInvokeReceiver(methods));
+  }
+
+  private Set<DexMethod> getMethods(Value value, ProgramMethod context) {
+    WorkList<Instruction> users = WorkList.newIdentityWorkList();
+    if (!enqueueUsersForAnalysis(value, users)) {
+      return null;
+    }
+    Set<DexMethod> methods = Sets.newIdentityHashSet();
+    while (users.hasNext()) {
+      Instruction user = users.next();
+      if (user.isAssume()) {
+        if (enqueueUsersForAnalysis(user.outValue(), users)) {
+          // OK.
+          continue;
+        }
+      } else if (user.isInvokeMethodWithReceiver()) {
+        InvokeMethodWithReceiver invoke = user.asInvokeMethodWithReceiver();
+        for (int argumentIndex = 1; argumentIndex < invoke.arguments().size(); argumentIndex++) {
+          if (invoke.getArgument(argumentIndex).getAliasedValue() == value) {
+            return null;
+          }
+        }
+        assert invoke.getReceiver().getAliasedValue() == value;
+
+        ProgramMethod singleTarget = invoke.lookupSingleProgramTarget(appView, context);
+        if (singleTarget == null) {
+          return null;
+        }
+
+        methods.add(singleTarget.getReference());
+      }
+      return null;
+    }
+    return methods;
+  }
+
+  private boolean enqueueUsersForAnalysis(Value value, WorkList<Instruction> users) {
+    if (value.hasDebugUsers() || value.hasPhiUsers()) {
+      return false;
+    }
+    users.addIfNotSeen(value.uniqueUsers());
+    return true;
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/TrivialFieldAccessReprocessor.java b/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/TrivialFieldAccessReprocessor.java
index f392dec..7f36c0c 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,7 +4,9 @@
 
 package com.android.tools.r8.ir.analysis.fieldaccess;
 
+import static com.android.tools.r8.graph.DexProgramClass.asProgramClassOrNull;
 import static com.android.tools.r8.ir.optimize.info.OptimizationFeedback.getSimpleFeedback;
+import static com.android.tools.r8.utils.MapUtils.ignoreKey;
 
 import com.android.tools.r8.code.CfOrDexInstanceFieldRead;
 import com.android.tools.r8.code.CfOrDexStaticFieldRead;
@@ -53,6 +55,8 @@
   private final AppView<AppInfoWithLiveness> appView;
   private final PostMethodProcessor.Builder postMethodProcessorBuilder;
 
+  private final Map<DexEncodedField, ProgramMethodSet> dependencies = new ConcurrentHashMap<>();
+
   /** Updated concurrently from {@link #processClass(DexProgramClass)}. */
   private final Map<DexEncodedField, AbstractAccessContexts> readFields = new ConcurrentHashMap<>();
 
@@ -233,6 +237,7 @@
           fieldAccessInfoCollection.get(field.getReference()).asMutable().clearReads();
           methodsToReprocess.addAll(
               contexts.asConcrete().getAccessesWithContexts().values().iterator().next());
+          methodsToReprocess.addAll(dependencies.getOrDefault(field, ProgramMethodSet.empty()));
         });
   }
 
@@ -250,6 +255,7 @@
           assert !writtenFields.containsKey(field);
           methodsToReprocess.addAll(
               contexts.asConcrete().getAccessesWithContexts().values().iterator().next());
+          methodsToReprocess.addAll(dependencies.getOrDefault(field, ProgramMethodSet.empty()));
         });
   }
 
@@ -326,11 +332,20 @@
         return;
       }
 
-      if (metadata != null && metadata.isReadForWrite()) {
-        // Ignore this read. If the field ends up only being written, then we will still reprocess
-        // the method with the read-for-write instruction, since the method contains a write that
-        // requires reprocessing.
-        return;
+      if (metadata != null) {
+        if (isUnusedReadAfterMethodStaticizing(metadata)) {
+          // Ignore this read.
+          dependencies
+              .computeIfAbsent(field.getDefinition(), ignoreKey(ProgramMethodSet::createConcurrent))
+              .add(getContext());
+          return;
+        }
+        if (metadata.isReadForWrite()) {
+          // Ignore this read. If the field ends up only being written, then we will still reprocess
+          // the method with the read-for-write instruction, since the method contains a write that
+          // requires reprocessing.
+          return;
+        }
       }
 
       // Record access.
@@ -352,6 +367,28 @@
       }
     }
 
+    private boolean isUnusedReadAfterMethodStaticizing(BytecodeInstructionMetadata metadata) {
+      if (!metadata.isReadForInvokeReceiver()) {
+        return false;
+      }
+      Set<DexMethod> readForInvokeReceiver = metadata.getReadForInvokeReceiver();
+      for (DexMethod methodReference : readForInvokeReceiver) {
+        DexMethod rewrittenMethodReference =
+            appView.graphLens().getRenamedMethodSignature(methodReference, appView.codeLens());
+        DexProgramClass holder =
+            asProgramClassOrNull(appView.definitionFor(rewrittenMethodReference.getHolderType()));
+        ProgramMethod method = rewrittenMethodReference.lookupOnProgramClass(holder);
+        if (method == null) {
+          assert false;
+          return false;
+        }
+        if (!method.getDefinition().isStatic()) {
+          return false;
+        }
+      }
+      return true;
+    }
+
     private void recordAccessThatCannotBeOptimized(
         DexClassAndField field, DexEncodedField definition) {
       constantFields.remove(definition);
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/proto/schema/ProtoEnqueuerExtension.java b/src/main/java/com/android/tools/r8/ir/analysis/proto/schema/ProtoEnqueuerExtension.java
index 66747a7..d0cfd52 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/proto/schema/ProtoEnqueuerExtension.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/proto/schema/ProtoEnqueuerExtension.java
@@ -295,9 +295,9 @@
       return;
     }
 
-    assert definition.isInvokeMethodWithReceiver()
+    assert definition.isInvokeMethod()
         && references.isFindLiteExtensionByNumberMethod(
-            definition.asInvokeMethodWithReceiver().getInvokedMethod());
+            definition.asInvokeMethod().getInvokedMethod());
   }
 
   /**
diff --git a/src/main/java/com/android/tools/r8/ir/code/BasicBlockInstructionListIterator.java b/src/main/java/com/android/tools/r8/ir/code/BasicBlockInstructionListIterator.java
index 62cbb04..2ad1f65 100644
--- a/src/main/java/com/android/tools/r8/ir/code/BasicBlockInstructionListIterator.java
+++ b/src/main/java/com/android/tools/r8/ir/code/BasicBlockInstructionListIterator.java
@@ -275,6 +275,40 @@
   }
 
   @Override
+  public InvokeMethod insertNullCheckInstruction(
+      AppView<?> appView,
+      IRCode code,
+      BasicBlockIterator blockIterator,
+      Value value,
+      Position position) {
+    InternalOptions options = appView.options();
+
+    InvokeMethod invoke;
+    if (appView.options().canUseJavaUtilObjectsRequireNonNull()) {
+      DexMethod requireNonNullMethod = appView.dexItemFactory().objectsMethods.requireNonNull;
+      invoke =
+          InvokeStatic.builder()
+              .setMethod(requireNonNullMethod)
+              .setSingleArgument(value)
+              .setPosition(position)
+              .build();
+    } else {
+      DexMethod getClassMethod = appView.dexItemFactory().objectMembers.getClass;
+      invoke =
+          InvokeVirtual.builder()
+              .setMethod(getClassMethod)
+              .setSingleArgument(value)
+              .setPosition(position)
+              .build();
+    }
+    add(invoke);
+    if (block.hasCatchHandlers()) {
+      splitCopyCatchHandlers(code, blockIterator, options);
+    }
+    return invoke;
+  }
+
+  @Override
   public boolean replaceCurrentInstructionByNullCheckIfPossible(
       AppView<?> appView, ProgramMethod context) {
     Instruction toBeReplaced = current;
diff --git a/src/main/java/com/android/tools/r8/ir/code/IRCodeInstructionListIterator.java b/src/main/java/com/android/tools/r8/ir/code/IRCodeInstructionListIterator.java
index f9bbcc8..a68da1a 100644
--- a/src/main/java/com/android/tools/r8/ir/code/IRCodeInstructionListIterator.java
+++ b/src/main/java/com/android/tools/r8/ir/code/IRCodeInstructionListIterator.java
@@ -45,6 +45,17 @@
   }
 
   @Override
+  public InvokeMethod insertNullCheckInstruction(
+      AppView<?> appView,
+      IRCode code,
+      BasicBlockIterator blockIterator,
+      Value value,
+      Position position) {
+    return instructionIterator.insertNullCheckInstruction(
+        appView, code, blockIterator, value, position);
+  }
+
+  @Override
   public boolean replaceCurrentInstructionByNullCheckIfPossible(
       AppView<?> appView, ProgramMethod context) {
     return instructionIterator.replaceCurrentInstructionByNullCheckIfPossible(appView, context);
diff --git a/src/main/java/com/android/tools/r8/ir/code/InstructionListIterator.java b/src/main/java/com/android/tools/r8/ir/code/InstructionListIterator.java
index d08413d..bc6bec6 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InstructionListIterator.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InstructionListIterator.java
@@ -100,6 +100,13 @@
 
   Value insertConstStringInstruction(AppView<?> appView, IRCode code, DexString value);
 
+  InvokeMethod insertNullCheckInstruction(
+      AppView<?> appView,
+      IRCode code,
+      BasicBlockIterator blockIterator,
+      Value value,
+      Position position);
+
   boolean replaceCurrentInstructionByNullCheckIfPossible(AppView<?> appView, ProgramMethod context);
 
   boolean replaceCurrentInstructionByInitClassIfPossible(
diff --git a/src/main/java/com/android/tools/r8/ir/code/LinearFlowInstructionListIterator.java b/src/main/java/com/android/tools/r8/ir/code/LinearFlowInstructionListIterator.java
index cb20fd4..37d45df 100644
--- a/src/main/java/com/android/tools/r8/ir/code/LinearFlowInstructionListIterator.java
+++ b/src/main/java/com/android/tools/r8/ir/code/LinearFlowInstructionListIterator.java
@@ -69,6 +69,17 @@
   }
 
   @Override
+  public InvokeMethod insertNullCheckInstruction(
+      AppView<?> appView,
+      IRCode code,
+      BasicBlockIterator blockIterator,
+      Value value,
+      Position position) {
+    return currentBlockIterator.insertNullCheckInstruction(
+        appView, code, blockIterator, value, position);
+  }
+
+  @Override
   public boolean replaceCurrentInstructionByNullCheckIfPossible(
       AppView<?> appView, ProgramMethod context) {
     return currentBlockIterator.replaceCurrentInstructionByNullCheckIfPossible(appView, context);
diff --git a/src/main/java/com/android/tools/r8/ir/code/Position.java b/src/main/java/com/android/tools/r8/ir/code/Position.java
index 47549e9..9fd0c74 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Position.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Position.java
@@ -13,6 +13,7 @@
 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.Predicate;
 
 public abstract class Position implements StructuralItem<Position> {
 
@@ -150,6 +151,26 @@
     return lastPosition;
   }
 
+  public Position getOutermostCallerMatchingOrElse(
+      Predicate<Position> predicate, Position defaultValue) {
+    return getOutermostCallerMatchingOrElse(predicate, defaultValue, false);
+  }
+
+  private Position getOutermostCallerMatchingOrElse(
+      Predicate<Position> predicate, Position defaultValue, boolean isCallerPosition) {
+    if (hasCallerPosition()) {
+      Position position =
+          getCallerPosition().getOutermostCallerMatchingOrElse(predicate, defaultValue, true);
+      if (position != null) {
+        return position;
+      }
+    }
+    if (isCallerPosition && predicate.test(this)) {
+      return this;
+    }
+    return defaultValue;
+  }
+
   public Position withOutermostCallerPosition(Position newOutermostCallerPosition) {
     return builderWithCopy()
         .setCallerPosition(
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 c4dba5e..45758d3 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
@@ -1505,6 +1505,9 @@
       CodeRewriter.removeAssumeInstructions(appView, code);
       timing.end();
       assert code.isConsistentSSA();
+
+      // TODO(b/214496607): Remove when dynamic types are safe w.r.t. interface assignment rules.
+      codeRewriter.rewriteMoveResult(code);
     }
 
     // Assert that we do not have unremoved non-sense code in the output, e.g., v <- non-null NULL.
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java b/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java
index f6c957c..e5602b2 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java
@@ -100,7 +100,6 @@
 import com.android.tools.r8.optimize.MemberRebindingAnalysis;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.verticalclassmerging.InterfaceTypeToClassTypeLensCodeRewriterHelper;
-import com.google.common.collect.Sets;
 import java.util.ArrayList;
 import java.util.HashSet;
 import java.util.IdentityHashMap;
@@ -151,10 +150,7 @@
 
   /** Replace type appearances, invoke targets and field accesses with actual definitions. */
   public void rewrite(IRCode code, ProgramMethod method, MethodProcessor methodProcessor) {
-    Set<Phi> affectedPhis =
-        enumUnboxer != null
-            ? enumUnboxer.rewriteCode(code, methodProcessor)
-            : Sets.newIdentityHashSet();
+    Set<Phi> affectedPhis = enumUnboxer.rewriteCode(code, methodProcessor);
     GraphLens graphLens = appView.graphLens();
     DexItemFactory factory = appView.dexItemFactory();
     // Rewriting types that affects phi can cause us to compute TOP for cyclic phi's. To solve this
@@ -256,6 +252,9 @@
               Invoke.Type actualInvokeType = lensLookup.getType();
 
               iterator =
+                  insertNullCheckForInvokeReceiverIfNeeded(
+                      code, blocks, iterator, invoke, lensLookup);
+              iterator =
                   insertCastsForInvokeArgumentsIfNeeded(code, blocks, iterator, invoke, lensLookup);
 
               RewrittenPrototypeDescription prototypeChanges = lensLookup.getPrototypeChanges();
@@ -772,6 +771,56 @@
     return iterator;
   }
 
+  private InstructionListIterator insertNullCheckForInvokeReceiverIfNeeded(
+      IRCode code,
+      BasicBlockIterator blocks,
+      InstructionListIterator iterator,
+      InvokeMethod invoke,
+      MethodLookupResult lookup) {
+    // If the invoke has been staticized, then synthesize a null check for the receiver.
+    if (!invoke.isInvokeMethodWithReceiver() || !lookup.getType().isStatic()) {
+      return iterator;
+    }
+
+    TypeElement receiverType = invoke.asInvokeMethodWithReceiver().getReceiver().getType();
+    if (receiverType.isDefinitelyNotNull()) {
+      return iterator;
+    }
+
+    iterator.previous();
+
+    Position nullCheckPosition =
+        invoke
+            .getPosition()
+            .getOutermostCallerMatchingOrElse(
+                Position::isRemoveInnerFramesIfThrowingNpe, invoke.getPosition());
+    InvokeMethod nullCheck =
+        iterator.insertNullCheckInstruction(
+            appView, code, blocks, invoke.getFirstArgument(), nullCheckPosition);
+
+    // TODO(b/199864962): Lens code rewriting should follow the order of graph lenses, i.e., enum
+    //  unboxing rewriting should happen after method staticizing.
+    if (receiverType.isClassType()
+        && appView.unboxedEnums().isUnboxedEnum(receiverType.asClassType().getClassType())) {
+      iterator.previousUntil(instruction -> instruction == nullCheck);
+      iterator.next();
+      enumUnboxer.rewriteNullCheck(iterator, nullCheck);
+    }
+
+    // Reset the block iterator.
+    if (invoke.getBlock().hasCatchHandlers()) {
+      BasicBlock splitBlock = invoke.getBlock();
+      BasicBlock previousBlock = blocks.previousUntil(block -> block == splitBlock);
+      assert previousBlock == splitBlock;
+      blocks.next();
+      iterator = splitBlock.listIterator(code);
+    }
+
+    Instruction next = iterator.next();
+    assert next == invoke;
+    return iterator;
+  }
+
   private InstructionListIterator insertCastsForInvokeArgumentsIfNeeded(
       IRCode code,
       BasicBlockIterator blocks,
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/BackportedMethodRewriter.java b/src/main/java/com/android/tools/r8/ir/desugar/BackportedMethodRewriter.java
index a3fc0e8..ee7f8e1 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/BackportedMethodRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/BackportedMethodRewriter.java
@@ -1091,27 +1091,44 @@
     }
 
     private void initializeAndroidSv2MethodProviders(DexItemFactory factory) {
-      DexString name;
-      DexProto proto;
-      DexMethod method;
       // sun.misc.Unsafe
+      {
+        // compareAndSwapObject(Object receiver, long offset, Object expect, Object update)
+        DexType type = factory.unsafeType;
+        DexString name = factory.createString("compareAndSwapObject");
+        DexProto proto =
+            factory.createProto(
+                factory.booleanType,
+                factory.objectType,
+                factory.longType,
+                factory.objectType,
+                factory.objectType);
+        DexMethod method = factory.createMethod(type, proto, name);
+        addProvider(
+            new StatifyingMethodWithForwardingGenerator(
+                method,
+                BackportedMethods::UnsafeMethods_compareAndSwapObject,
+                "compareAndSwapObject",
+                type));
+      }
 
-      // compareAndSwapObject(Object receiver, long offset, Object expect, Object update)
-      name = factory.createString("compareAndSwapObject");
-      proto =
-          factory.createProto(
-              factory.booleanType,
-              factory.objectType,
-              factory.longType,
-              factory.objectType,
-              factory.objectType);
-      method = factory.createMethod(factory.unsafeType, proto, name);
-      addProvider(
-          new StatifyingMethodWithForwardingGenerator(
-              method,
-              BackportedMethods::UnsafeMethods_compareAndSwapObject,
-              "compareAndSwapObject",
-              factory.unsafeType));
+      // java.util.concurrent.atomic.AtomicReferenceFieldUpdater
+      {
+        // compareAndSet(Object object, Object expect, Object update)
+        DexType type =
+            factory.createType("Ljava/util/concurrent/atomic/AtomicReferenceFieldUpdater;");
+        DexString name = factory.createString("compareAndSet");
+        DexProto proto =
+            factory.createProto(
+                factory.booleanType, factory.objectType, factory.objectType, factory.objectType);
+        DexMethod method = factory.createMethod(type, proto, name);
+        addProvider(
+            new StatifyingMethodWithForwardingGenerator(
+                method,
+                BackportedMethods::AtomicReferenceFieldUpdaterMethods_compareAndSet,
+                "compareAndSet",
+                type));
+      }
     }
 
     private void initializeJava9MethodProviders(DexItemFactory factory) {
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/backports/BackportedMethods.java b/src/main/java/com/android/tools/r8/ir/desugar/backports/BackportedMethods.java
index b934c40..e6b793f 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/backports/BackportedMethods.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/backports/BackportedMethods.java
@@ -97,6 +97,7 @@
     factory.createSynthesizedType("Ljava/util/OptionalInt;");
     factory.createSynthesizedType("Ljava/util/OptionalLong;");
     factory.createSynthesizedType("Ljava/util/Set;");
+    factory.createSynthesizedType("Ljava/util/concurrent/atomic/AtomicReferenceFieldUpdater;");
     factory.createSynthesizedType("Ljava/util/function/Consumer;");
     factory.createSynthesizedType("Ljava/util/function/DoubleConsumer;");
     factory.createSynthesizedType("Ljava/util/function/IntConsumer;");
@@ -114,6 +115,85 @@
     factory.createSynthesizedType("[Ljava/util/Map$Entry;");
   }
 
+  public static CfCode AtomicReferenceFieldUpdaterMethods_compareAndSet(
+      InternalOptions options, DexMethod method) {
+    CfLabel label0 = new CfLabel();
+    CfLabel label1 = new CfLabel();
+    CfLabel label2 = new CfLabel();
+    CfLabel label3 = new CfLabel();
+    CfLabel label4 = new CfLabel();
+    return new CfCode(
+        method.holder,
+        4,
+        4,
+        ImmutableList.of(
+            label0,
+            new CfFrame(
+                new Int2ReferenceAVLTreeMap<>(
+                    new int[] {0, 1, 2, 3},
+                    new FrameType[] {
+                      FrameType.initialized(
+                          options.itemFactory.createType(
+                              "Ljava/util/concurrent/atomic/AtomicReferenceFieldUpdater;")),
+                      FrameType.initialized(options.itemFactory.objectType),
+                      FrameType.initialized(options.itemFactory.objectType),
+                      FrameType.initialized(options.itemFactory.objectType)
+                    }),
+                new ArrayDeque<>(Arrays.asList())),
+            new CfLoad(ValueType.OBJECT, 0),
+            new CfLoad(ValueType.OBJECT, 1),
+            new CfLoad(ValueType.OBJECT, 2),
+            new CfLoad(ValueType.OBJECT, 3),
+            new CfInvoke(
+                182,
+                options.itemFactory.createMethod(
+                    options.itemFactory.createType(
+                        "Ljava/util/concurrent/atomic/AtomicReferenceFieldUpdater;"),
+                    options.itemFactory.createProto(
+                        options.itemFactory.booleanType,
+                        options.itemFactory.objectType,
+                        options.itemFactory.objectType,
+                        options.itemFactory.objectType),
+                    options.itemFactory.createString("compareAndSet")),
+                false),
+            new CfIf(If.Type.EQ, ValueType.INT, label2),
+            label1,
+            new CfConstNumber(1, ValueType.INT),
+            new CfReturn(ValueType.INT),
+            label2,
+            new CfFrame(
+                new Int2ReferenceAVLTreeMap<>(
+                    new int[] {0, 1, 2, 3},
+                    new FrameType[] {
+                      FrameType.initialized(
+                          options.itemFactory.createType(
+                              "Ljava/util/concurrent/atomic/AtomicReferenceFieldUpdater;")),
+                      FrameType.initialized(options.itemFactory.objectType),
+                      FrameType.initialized(options.itemFactory.objectType),
+                      FrameType.initialized(options.itemFactory.objectType)
+                    }),
+                new ArrayDeque<>(Arrays.asList())),
+            new CfLoad(ValueType.OBJECT, 0),
+            new CfLoad(ValueType.OBJECT, 1),
+            new CfInvoke(
+                182,
+                options.itemFactory.createMethod(
+                    options.itemFactory.createType(
+                        "Ljava/util/concurrent/atomic/AtomicReferenceFieldUpdater;"),
+                    options.itemFactory.createProto(
+                        options.itemFactory.objectType, options.itemFactory.objectType),
+                    options.itemFactory.createString("get")),
+                false),
+            new CfLoad(ValueType.OBJECT, 2),
+            new CfIfCmp(If.Type.EQ, ValueType.OBJECT, label0),
+            label3,
+            new CfConstNumber(0, ValueType.INT),
+            new CfReturn(ValueType.INT),
+            label4),
+        ImmutableList.of(),
+        ImmutableList.of());
+  }
+
   public static CfCode BooleanMethods_compare(InternalOptions options, DexMethod method) {
     CfLabel label0 = new CfLabel();
     CfLabel label1 = new CfLabel();
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/machinespecification/DerivedMethod.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/machinespecification/DerivedMethod.java
new file mode 100644
index 0000000..c383dcd
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/machinespecification/DerivedMethod.java
@@ -0,0 +1,52 @@
+// Copyright (c) 2022, 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.desugar.desugaredlibrary.machinespecification;
+
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexProto;
+import com.android.tools.r8.graph.DexString;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.synthesis.SyntheticNaming.SyntheticKind;
+
+/**
+ * A derived method is: - if the holderKind is null, a normal dexMethod; - if the holderKind is
+ * non-null, a method derived from the dexMethod In that case the method holder is used as the
+ * context to generate the holder type. The method may however differ (for example the method name
+ * may be different).
+ */
+public class DerivedMethod {
+
+  private final DexMethod method;
+  private final SyntheticKind holderKind;
+
+  public DerivedMethod(DexMethod method) {
+    this(method, null);
+  }
+
+  public DerivedMethod(DexMethod method, SyntheticKind holderKind) {
+    this.holderKind = holderKind;
+    this.method = method;
+  }
+
+  public SyntheticKind getHolderKind() {
+    return holderKind;
+  }
+
+  public DexType getHolderContext() {
+    return method.getHolderType();
+  }
+
+  public DexMethod getMethod() {
+    return method;
+  }
+
+  public DexString getName() {
+    return method.getName();
+  }
+
+  public DexProto getProto() {
+    return method.getProto();
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/machinespecification/EmulatedDispatchMethodDescriptor.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/machinespecification/EmulatedDispatchMethodDescriptor.java
new file mode 100644
index 0000000..c5154cd
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/machinespecification/EmulatedDispatchMethodDescriptor.java
@@ -0,0 +1,71 @@
+// Copyright (c) 2022, 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.desugar.desugaredlibrary.machinespecification;
+
+import com.android.tools.r8.graph.DexType;
+import java.util.LinkedHashMap;
+
+public class EmulatedDispatchMethodDescriptor {
+
+  /**
+   * When resolving into the descriptor, if the resolution is used for a super-invoke or to generate
+   * a forwarding method, then the forwarding method should be used. If the resolution is used to
+   * rewrite an invoke, then it should be rewritten to an invoke-static to the emulated dispatch
+   * method.
+   *
+   * <p>Emulated dispatch method are generated as follows: <code>emulatedDispatchMethod {
+   *   if (rcvr instanceof itf) {
+   *     invoke-interface itfMethod
+   *   }
+   *   if (rcvr instanceof dispatchCases0) {
+   *     invoke-static dispatchCases0
+   *   }
+   *   ...
+   *   if (rcvr instanceof dispatchCasesN) {
+   *     invoke-static dispatchCasesN
+   *   }
+   *   invoke-static forwardingMethod }</code>
+   *
+   * <p>For emulatedVirtualRetarget instances, the itfMethod holder and emulatedDispatchMethod
+   * holder are the itf and emulated dispatch holder types to synthesize. The forwardingMethod is
+   * the method to retarget to.
+   *
+   * <p>For emulated interface, itfMethod holder is the rewritten emulated interface type and the
+   * emulated dispatch method is on the $-EI class holding the dispatch methods. The forwarding
+   * method is the method on the companion class.
+   */
+  private final DerivedMethod interfaceMethod;
+
+  private final DerivedMethod emulatedDispatchMethod;
+  private final DerivedMethod forwardingMethod;
+  private final LinkedHashMap<DexType, DerivedMethod> dispatchCases;
+
+  public EmulatedDispatchMethodDescriptor(
+      DerivedMethod interfaceMethod,
+      DerivedMethod emulatedDispatchMethod,
+      DerivedMethod forwardingMethod,
+      LinkedHashMap<DexType, DerivedMethod> dispatchCases) {
+    this.interfaceMethod = interfaceMethod;
+    this.emulatedDispatchMethod = emulatedDispatchMethod;
+    this.forwardingMethod = forwardingMethod;
+    this.dispatchCases = dispatchCases;
+  }
+
+  public DerivedMethod getInterfaceMethod() {
+    return interfaceMethod;
+  }
+
+  public DerivedMethod getEmulatedDispatchMethod() {
+    return emulatedDispatchMethod;
+  }
+
+  public DerivedMethod getForwardingMethod() {
+    return forwardingMethod;
+  }
+
+  public LinkedHashMap<DexType, DerivedMethod> getDispatchCases() {
+    return dispatchCases;
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/machinespecification/MachineDesugaredLibrarySpecification.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/machinespecification/MachineDesugaredLibrarySpecification.java
new file mode 100644
index 0000000..2a88f9a
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/machinespecification/MachineDesugaredLibrarySpecification.java
@@ -0,0 +1,26 @@
+// Copyright (c) 2022, 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.desugar.desugaredlibrary.machinespecification;
+
+/** TODO(b/184026720): Move the rest of the flags. */
+public class MachineDesugaredLibrarySpecification {
+
+  private final boolean libraryCompilation;
+  private final MachineRewritingFlags rewritingFlags;
+
+  public MachineDesugaredLibrarySpecification(
+      boolean libraryCompilation, MachineRewritingFlags rewritingFlags) {
+    this.libraryCompilation = libraryCompilation;
+    this.rewritingFlags = rewritingFlags;
+  }
+
+  public boolean isLibraryCompilation() {
+    return libraryCompilation;
+  }
+
+  public MachineRewritingFlags getRewritingFlags() {
+    return rewritingFlags;
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/machinespecification/MachineRewritingFlags.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/machinespecification/MachineRewritingFlags.java
new file mode 100644
index 0000000..f73e1c9
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/machinespecification/MachineRewritingFlags.java
@@ -0,0 +1,82 @@
+// Copyright (c) 2022, 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.desugar.desugaredlibrary.machinespecification;
+
+import com.android.tools.r8.graph.DexMethod;
+import com.google.common.collect.ImmutableMap;
+import java.util.Map;
+
+public class MachineRewritingFlags {
+
+  public static Builder builder() {
+    return new Builder();
+  }
+
+  MachineRewritingFlags(
+      Map<DexMethod, DexMethod> staticRetarget,
+      Map<DexMethod, DexMethod> nonEmulatedVirtualRetarget,
+      Map<DexMethod, EmulatedDispatchMethodDescriptor> emulatedVirtualRetarget) {
+    this.staticRetarget = staticRetarget;
+    this.nonEmulatedVirtualRetarget = nonEmulatedVirtualRetarget;
+    this.emulatedVirtualRetarget = emulatedVirtualRetarget;
+  }
+
+  // Static methods to retarget, duplicated to library boundaries.
+  private final Map<DexMethod, DexMethod> staticRetarget;
+
+  // Virtual methods to retarget, which are guaranteed not to require emulated dispatch.
+  // A method does not require emulated dispatch if two conditions are met:
+  // (1) the method does not override any other library method;
+  // (2) the method is final or installed in a final class.
+  // Any invoke resolving into the method will be rewritten into an invoke-static to the desugared
+  // code.
+  private final Map<DexMethod, DexMethod> nonEmulatedVirtualRetarget;
+
+  // Virtual methods to retarget through emulated dispatch.
+  private final Map<DexMethod, EmulatedDispatchMethodDescriptor> emulatedVirtualRetarget;
+
+  public Map<DexMethod, DexMethod> getStaticRetarget() {
+    return staticRetarget;
+  }
+
+  public Map<DexMethod, DexMethod> getNonEmulatedVirtualRetarget() {
+    return nonEmulatedVirtualRetarget;
+  }
+
+  public Map<DexMethod, EmulatedDispatchMethodDescriptor> getEmulatedVirtualRetarget() {
+    return emulatedVirtualRetarget;
+  }
+
+  public static class Builder {
+
+    Builder() {}
+
+    private final ImmutableMap.Builder<DexMethod, DexMethod> staticRetarget =
+        ImmutableMap.builder();
+    private final ImmutableMap.Builder<DexMethod, DexMethod> nonEmulatedVirtualRetarget =
+        ImmutableMap.builder();
+    private final ImmutableMap.Builder<DexMethod, EmulatedDispatchMethodDescriptor>
+        emulatedVirtualRetarget = ImmutableMap.builder();
+
+    public void putStaticRetarget(DexMethod src, DexMethod dest) {
+      staticRetarget.put(src, dest);
+    }
+
+    public void putNonEmulatedVirtualRetarget(DexMethod src, DexMethod dest) {
+      nonEmulatedVirtualRetarget.put(src, dest);
+    }
+
+    public void putEmulatedVirtualRetarget(DexMethod src, EmulatedDispatchMethodDescriptor dest) {
+      emulatedVirtualRetarget.put(src, dest);
+    }
+
+    public MachineRewritingFlags build() {
+      return new MachineRewritingFlags(
+          staticRetarget.build(),
+          nonEmulatedVirtualRetarget.build(),
+          emulatedVirtualRetarget.build());
+    }
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/retargeter/DesugaredLibraryRetargeter.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/retargeter/DesugaredLibraryRetargeter.java
index c4a01f6..7c5d1c3 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/retargeter/DesugaredLibraryRetargeter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/retargeter/DesugaredLibraryRetargeter.java
@@ -9,14 +9,12 @@
 import com.android.tools.r8.cf.code.CfInstruction;
 import com.android.tools.r8.cf.code.CfInvoke;
 import com.android.tools.r8.contexts.CompilationContext.MethodProcessingContext;
+import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
 import com.android.tools.r8.graph.AppView;
 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;
-import com.android.tools.r8.graph.DexProto;
-import com.android.tools.r8.graph.DexString;
-import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.MethodResolutionResult;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.desugar.CfInstructionDesugaring;
@@ -24,11 +22,10 @@
 import com.android.tools.r8.ir.desugar.CfInstructionDesugaringEventConsumer;
 import com.android.tools.r8.ir.desugar.FreshLocalProvider;
 import com.android.tools.r8.ir.desugar.LocalStackAllocator;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.machinespecification.EmulatedDispatchMethodDescriptor;
 import com.android.tools.r8.ir.desugar.desugaredlibrary.retargeter.DesugaredLibraryRetargeterSynthesizerEventConsumer.DesugaredLibraryRetargeterInstructionEventConsumer;
-import com.android.tools.r8.utils.collections.DexClassAndMethodSet;
 import java.util.Collection;
 import java.util.Collections;
-import java.util.List;
 import java.util.Map;
 import java.util.function.Consumer;
 import java.util.function.Function;
@@ -40,22 +37,24 @@
   private final DesugaredLibraryRetargeterSyntheticHelper syntheticHelper;
 
   private final RetargetingInfo retargetingInfo;
-  private final Map<DexMethod, DexMethod> retargetLibraryMember;
-  private final Map<DexString, List<DexMethod>> nonFinalHolderRewrites;
-  private final DexClassAndMethodSet emulatedDispatchMethods;
+  private final Map<DexMethod, DexMethod> staticRetarget;
+  private final Map<DexMethod, DexMethod> nonEmulatedVirtualRetarget;
+  private final Map<DexMethod, EmulatedDispatchMethodDescriptor> emulatedVirtualRetarget;
 
   public DesugaredLibraryRetargeter(AppView<?> appView) {
     this.appView = appView;
     this.syntheticHelper = new DesugaredLibraryRetargeterSyntheticHelper(appView);
     retargetingInfo = RetargetingInfo.get(appView);
-    retargetLibraryMember = retargetingInfo.getRetargetLibraryMember();
-    nonFinalHolderRewrites = retargetingInfo.getNonFinalHolderRewrites();
-    emulatedDispatchMethods = retargetingInfo.getEmulatedDispatchMethods();
+    staticRetarget = retargetingInfo.getStaticRetarget();
+    nonEmulatedVirtualRetarget = retargetingInfo.getNonEmulatedVirtualRetarget();
+    emulatedVirtualRetarget = retargetingInfo.getEmulatedVirtualRetarget();
   }
 
   // Used by the ListOfBackportedMethods utility.
   public void visit(Consumer<DexMethod> consumer) {
-    retargetLibraryMember.keySet().forEach(consumer);
+    staticRetarget.keySet().forEach(consumer);
+    nonEmulatedVirtualRetarget.keySet().forEach(consumer);
+    emulatedVirtualRetarget.keySet().forEach(consumer);
   }
 
   public RetargetingInfo getRetargetingInfo() {
@@ -125,7 +124,7 @@
 
   private InvokeRetargetingResult computeNewInvokeTarget(
       CfInstruction instruction, ProgramMethod context) {
-    if (retargetLibraryMember.isEmpty() || !instruction.isInvoke()) {
+    if (!instruction.isInvoke()) {
       return NO_REWRITING;
     }
     if (appView
@@ -137,77 +136,56 @@
     }
     CfInvoke cfInvoke = instruction.asInvoke();
     DexMethod invokedMethod = cfInvoke.getMethod();
-    InvokeRetargetingResult retarget =
-        computeRetargetedMethod(invokedMethod, cfInvoke.isInterface());
+    AppInfoWithClassHierarchy appInfo = appView.appInfoForDesugaring();
+    MethodResolutionResult resolutionResult =
+        appInfo.resolveMethod(invokedMethod, cfInvoke.isInterface());
+    if (!resolutionResult.isSingleResolution()) {
+      return NO_REWRITING;
+    }
+    DexEncodedMethod singleTarget = resolutionResult.getSingleTarget();
+    assert singleTarget != null;
+    if (cfInvoke.isInvokeStatic()) {
+      DexMethod retarget = staticRetarget.get(singleTarget.getReference());
+      return retarget == null
+          ? NO_REWRITING
+          : InvokeRetargetingResult.createInvokeRetargetingResult(retarget);
+    }
+    InvokeRetargetingResult retarget = computeNonStaticRetarget(singleTarget);
     if (!retarget.hasNewInvokeTarget()) {
       return NO_REWRITING;
     }
-    if (cfInvoke.isInvokeSuper(context.getHolderType())
-        && matchesNonFinalHolderRewrite(invokedMethod)) {
-      DexClassAndMethod superTarget =
-          appView.appInfoForDesugaring().lookupSuperTarget(invokedMethod, context);
-      // Final methods can be rewritten as a normal invoke.
-      if (superTarget != null && !superTarget.getAccessFlags().isFinal()) {
-        return InvokeRetargetingResult.createInvokeRetargetingResult(
-            appView.options().desugaredLibrarySpecification.retargetMethod(superTarget, appView));
+    if (cfInvoke.isInvokeSuper(context.getHolderType())) {
+      DexClassAndMethod superTarget = appInfo.lookupSuperTarget(invokedMethod, context);
+      if (superTarget != null) {
+        return computeSuperRetarget(superTarget.getDefinition());
       }
     }
     return retarget;
   }
 
-  private InvokeRetargetingResult computeRetargetedMethod(
-      DexMethod invokedMethod, boolean isInterface) {
-    InvokeRetargetingResult invokeRetargetingResult = computeRetargetLibraryMember(invokedMethod);
-    if (!invokeRetargetingResult.hasNewInvokeTarget()) {
-      if (!matchesNonFinalHolderRewrite(invokedMethod)) {
-        return NO_REWRITING;
-      }
-      // We need to force resolution, even on d8, to know if the invoke has to be rewritten.
-      MethodResolutionResult resolutionResult =
-          appView.appInfoForDesugaring().resolveMethod(invokedMethod, isInterface);
-      if (resolutionResult.isFailedResolution()) {
-        return NO_REWRITING;
-      }
-      DexEncodedMethod singleTarget = resolutionResult.getSingleTarget();
-      assert singleTarget != null;
-      invokeRetargetingResult = computeRetargetLibraryMember(singleTarget.getReference());
-    }
-    return invokeRetargetingResult;
-  }
-
-  private InvokeRetargetingResult computeRetargetLibraryMember(DexMethod method) {
-    DexClassAndMethod emulatedMethod = emulatedDispatchMethods.get(method);
-    if (emulatedMethod != null) {
-      assert !emulatedMethod.getAccessFlags().isStatic();
+  private InvokeRetargetingResult computeNonStaticRetarget(DexEncodedMethod singleTarget) {
+    assert !singleTarget.isStatic();
+    DexMethod reference = singleTarget.getReference();
+    EmulatedDispatchMethodDescriptor descriptor = emulatedVirtualRetarget.get(reference);
+    if (descriptor != null) {
       return new InvokeRetargetingResult(
           true,
-          eventConsumer -> {
-            DexType newHolder =
-                syntheticHelper.ensureEmulatedHolderDispatchMethod(emulatedMethod, eventConsumer)
-                    .type;
-            return computeRetargetMethod(
-                method, emulatedMethod.getAccessFlags().isStatic(), newHolder);
-          });
+          eventConsumer ->
+              syntheticHelper.ensureEmulatedHolderDispatchMethod(descriptor, eventConsumer));
     }
-    return InvokeRetargetingResult.createInvokeRetargetingResult(retargetLibraryMember.get(method));
+    return InvokeRetargetingResult.createInvokeRetargetingResult(
+        nonEmulatedVirtualRetarget.get(reference));
   }
 
-  private boolean matchesNonFinalHolderRewrite(DexMethod method) {
-    List<DexMethod> dexMethods = nonFinalHolderRewrites.get(method.name);
-    if (dexMethods == null) {
-      return false;
+  private InvokeRetargetingResult computeSuperRetarget(DexEncodedMethod singleTarget) {
+    assert !singleTarget.isStatic();
+    DexMethod reference = singleTarget.getReference();
+    EmulatedDispatchMethodDescriptor descriptor = emulatedVirtualRetarget.get(reference);
+    if (descriptor != null) {
+      return InvokeRetargetingResult.createInvokeRetargetingResult(
+          syntheticHelper.ensureForwardingMethod(descriptor));
     }
-    for (DexMethod dexMethod : dexMethods) {
-      if (method.match(dexMethod)) {
-        return true;
-      }
-    }
-    return false;
-  }
-
-  DexMethod computeRetargetMethod(DexMethod method, boolean isStatic, DexType newHolder) {
-    DexItemFactory factory = appView.dexItemFactory();
-    DexProto newProto = isStatic ? method.getProto() : factory.prependHolderToProto(method);
-    return factory.createMethod(newHolder, newProto, method.getName());
+    return InvokeRetargetingResult.createInvokeRetargetingResult(
+        nonEmulatedVirtualRetarget.get(reference));
   }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/retargeter/DesugaredLibraryRetargeterL8Synthesizer.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/retargeter/DesugaredLibraryRetargeterL8Synthesizer.java
index d757c94..11fcfb7 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/retargeter/DesugaredLibraryRetargeterL8Synthesizer.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/retargeter/DesugaredLibraryRetargeterL8Synthesizer.java
@@ -4,21 +4,22 @@
 package com.android.tools.r8.ir.desugar.desugaredlibrary.retargeter;
 
 import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.DexClassAndMethod;
+import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.ir.desugar.CfClassSynthesizerDesugaring;
 import com.android.tools.r8.ir.desugar.CfClassSynthesizerDesugaringEventConsumer;
-import com.android.tools.r8.utils.collections.DexClassAndMethodSet;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.machinespecification.EmulatedDispatchMethodDescriptor;
+import java.util.Map;
 
 public class DesugaredLibraryRetargeterL8Synthesizer implements CfClassSynthesizerDesugaring {
 
   private final AppView<?> appView;
   private final DesugaredLibraryRetargeterSyntheticHelper syntheticHelper;
-  private final DexClassAndMethodSet emulatedDispatchMethods;
+  private final Map<DexMethod, EmulatedDispatchMethodDescriptor> emulatedDispatchMethods;
 
   public static DesugaredLibraryRetargeterL8Synthesizer create(
       AppView<?> appView, RetargetingInfo retargetingInfo) {
     assert appView.options().isDesugaredLibraryCompilation();
-    if (retargetingInfo == null || retargetingInfo.getEmulatedDispatchMethods().isEmpty()) {
+    if (retargetingInfo == null || retargetingInfo.getEmulatedVirtualRetarget().isEmpty()) {
       assert appView.options().desugaredLibrarySpecification.getRetargetCoreLibMember().isEmpty();
       return null;
     }
@@ -29,13 +30,14 @@
       AppView<?> appView, RetargetingInfo retargetingInfo) {
     this.appView = appView;
     this.syntheticHelper = new DesugaredLibraryRetargeterSyntheticHelper(appView);
-    emulatedDispatchMethods = retargetingInfo.getEmulatedDispatchMethods();
+    emulatedDispatchMethods = retargetingInfo.getEmulatedVirtualRetarget();
   }
 
   @Override
   public void synthesizeClasses(CfClassSynthesizerDesugaringEventConsumer eventConsumer) {
     assert !emulatedDispatchMethods.isEmpty();
-    for (DexClassAndMethod emulatedDispatchMethod : emulatedDispatchMethods) {
+    for (EmulatedDispatchMethodDescriptor emulatedDispatchMethod :
+        emulatedDispatchMethods.values()) {
       syntheticHelper.ensureProgramEmulatedHolderDispatchMethod(
           emulatedDispatchMethod, eventConsumer);
     }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/retargeter/DesugaredLibraryRetargeterPostProcessor.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/retargeter/DesugaredLibraryRetargeterPostProcessor.java
index aae08a7..414c32d 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/retargeter/DesugaredLibraryRetargeterPostProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/retargeter/DesugaredLibraryRetargeterPostProcessor.java
@@ -5,7 +5,6 @@
 
 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.DexLibraryClass;
 import com.android.tools.r8.graph.DexMethod;
@@ -15,15 +14,16 @@
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.desugar.CfPostProcessingDesugaring;
 import com.android.tools.r8.ir.desugar.CfPostProcessingDesugaringEventConsumer;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.machinespecification.EmulatedDispatchMethodDescriptor;
 import com.android.tools.r8.ir.desugar.desugaredlibrary.retargeter.DesugaredLibraryRetargeterSynthesizerEventConsumer.DesugaredLibraryRetargeterPostProcessingEventConsumer;
 import com.android.tools.r8.utils.OptionalBool;
-import com.android.tools.r8.utils.collections.DexClassAndMethodSet;
 import com.google.common.collect.Maps;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.List;
 import java.util.Map;
+import java.util.Set;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.ExecutorService;
 
@@ -34,13 +34,13 @@
 
   private final AppView<?> appView;
   private final DesugaredLibraryRetargeterSyntheticHelper syntheticHelper;
-  private final DexClassAndMethodSet emulatedDispatchMethods;
+  private final Map<DexMethod, EmulatedDispatchMethodDescriptor> emulatedDispatchMethods;
 
   public DesugaredLibraryRetargeterPostProcessor(
       AppView<?> appView, RetargetingInfo retargetingInfo) {
     this.appView = appView;
     this.syntheticHelper = new DesugaredLibraryRetargeterSyntheticHelper(appView);
-    emulatedDispatchMethods = retargetingInfo.getEmulatedDispatchMethods();
+    emulatedDispatchMethods = retargetingInfo.getEmulatedVirtualRetarget();
   }
 
   @Override
@@ -57,11 +57,12 @@
       Collection<DexProgramClass> programClasses,
       DesugaredLibraryRetargeterPostProcessingEventConsumer eventConsumer) {
     assert !appView.options().isDesugaredLibraryCompilation();
-    Map<DexType, List<DexClassAndMethod>> map = Maps.newIdentityHashMap();
-    for (DexClassAndMethod emulatedDispatchMethod : emulatedDispatchMethods) {
-      map.putIfAbsent(emulatedDispatchMethod.getHolderType(), new ArrayList<>(1));
-      map.get(emulatedDispatchMethod.getHolderType()).add(emulatedDispatchMethod);
-    }
+    Map<DexType, List<DexMethod>> map = Maps.newIdentityHashMap();
+    emulatedDispatchMethods.forEach(
+        (method, descriptor) -> {
+          map.putIfAbsent(method.getHolderType(), new ArrayList<>(1));
+          map.get(method.getHolderType()).add(method);
+        });
     for (DexProgramClass clazz : programClasses) {
       if (clazz.superType == null) {
         assert clazz.type == appView.dexItemFactory().objectType : clazz.type.toSourceString();
@@ -84,7 +85,9 @@
   }
 
   private boolean inherit(
-      DexLibraryClass clazz, DexType typeToInherit, DexClassAndMethodSet retarget) {
+      DexLibraryClass clazz,
+      DexType typeToInherit,
+      Map<DexMethod, EmulatedDispatchMethodDescriptor> retarget) {
     DexLibraryClass current = clazz;
     while (current.type != appView.dexItemFactory().objectType) {
       if (current.type == typeToInherit) {
@@ -92,7 +95,7 @@
       }
       DexClass dexClass = appView.definitionFor(current.superType);
       if (dexClass == null || dexClass.isClasspathClass()) {
-        reportInvalidLibrarySupertype(current, retarget);
+        reportInvalidLibrarySupertype(current, retarget.keySet());
         return false;
       } else if (dexClass.isProgramClass()) {
         // If dexClass is a program class, then it is already correctly desugared.
@@ -106,7 +109,7 @@
   private void ensureInterfacesAndForwardingMethodsSynthesized(
       DesugaredLibraryRetargeterPostProcessingEventConsumer eventConsumer,
       DexProgramClass clazz,
-      List<DexClassAndMethod> methods) {
+      List<DexMethod> methods) {
     // DesugaredLibraryRetargeter emulate dispatch: insertion of a marker interface & forwarding
     // methods.
     // We cannot use the ClassProcessor since this applies up to 26, while the ClassProcessor
@@ -114,9 +117,10 @@
     if (appView.isAlreadyLibraryDesugared(clazz)) {
       return;
     }
-    for (DexClassAndMethod method : methods) {
+    for (DexMethod method : methods) {
+      EmulatedDispatchMethodDescriptor descriptor = emulatedDispatchMethods.get(method);
       DexClass newInterface =
-          syntheticHelper.ensureEmulatedInterfaceDispatchMethod(method, eventConsumer);
+          syntheticHelper.ensureEmulatedInterfaceDispatchMethod(descriptor, eventConsumer);
       if (clazz.interfaces.contains(newInterface.type)) {
         // The class has already been desugared.
         continue;
@@ -131,30 +135,35 @@
       clazz.addExtraInterfaces(
           Collections.singletonList(new ClassTypeSignature(newInterface.type)));
       eventConsumer.acceptInterfaceInjection(clazz, newInterface);
-      if (clazz.lookupVirtualMethod(method.getReference()) == null) {
-        DexEncodedMethod newMethod = createForwardingMethod(method, clazz);
+      DexMethod itfMethod =
+          syntheticHelper.getEmulatedInterfaceDispatchMethod(newInterface, descriptor);
+      if (clazz.lookupVirtualMethod(method) == null) {
+        DexEncodedMethod newMethod = createForwardingMethod(itfMethod, descriptor, clazz);
         clazz.addVirtualMethod(newMethod);
         eventConsumer.acceptForwardingMethod(new ProgramMethod(clazz, newMethod));
       }
     }
   }
 
-  private DexEncodedMethod createForwardingMethod(DexClassAndMethod target, DexClass clazz) {
+  private DexEncodedMethod createForwardingMethod(
+      DexMethod target, EmulatedDispatchMethodDescriptor descriptor, DexClass clazz) {
     // NOTE: Never add a forwarding method to methods of classes unknown or coming from android.jar
     // even if this results in invalid code, these classes are never desugared.
     // In desugared library, emulated interface methods can be overridden by retarget lib members.
-    DexMethod forwardMethod =
-        appView.options().desugaredLibrarySpecification.retargetMethod(target, appView);
-    assert forwardMethod != null && forwardMethod != target.getReference();
+    DexMethod forwardMethod = syntheticHelper.ensureForwardingMethod(descriptor);
+    assert forwardMethod != null && forwardMethod != target;
+    DexEncodedMethod resolvedMethod =
+        appView.appInfoForDesugaring().resolveMethod(target, true).getResolvedMethod();
+    assert resolvedMethod != null;
     DexEncodedMethod desugaringForwardingMethod =
         DexEncodedMethod.createDesugaringForwardingMethod(
-            target, clazz, forwardMethod, appView.dexItemFactory());
+            resolvedMethod, clazz, forwardMethod, appView.dexItemFactory());
     desugaringForwardingMethod.setLibraryMethodOverride(OptionalBool.TRUE);
     return desugaringForwardingMethod;
   }
 
   private void reportInvalidLibrarySupertype(
-      DexLibraryClass libraryClass, DexClassAndMethodSet retarget) {
+      DexLibraryClass libraryClass, Set<DexMethod> retarget) {
     DexClass dexClass = appView.definitionFor(libraryClass.superType);
     String message;
     if (dexClass == null) {
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/retargeter/DesugaredLibraryRetargeterSyntheticHelper.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/retargeter/DesugaredLibraryRetargeterSyntheticHelper.java
index 1a46480..5a5768f 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/retargeter/DesugaredLibraryRetargeterSyntheticHelper.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/retargeter/DesugaredLibraryRetargeterSyntheticHelper.java
@@ -5,19 +5,21 @@
 
 import com.android.tools.r8.dex.Constants;
 import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.CfCode;
 import com.android.tools.r8.graph.ClasspathOrLibraryClass;
 import com.android.tools.r8.graph.DexClass;
-import com.android.tools.r8.graph.DexClassAndMethod;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.MethodAccessFlags;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.machinespecification.DerivedMethod;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.machinespecification.EmulatedDispatchMethodDescriptor;
 import com.android.tools.r8.ir.desugar.desugaredlibrary.retargeter.DesugaredLibraryRetargeterSynthesizerEventConsumer.DesugaredLibraryRetargeterInstructionEventConsumer;
 import com.android.tools.r8.ir.desugar.desugaredlibrary.retargeter.DesugaredLibraryRetargeterSynthesizerEventConsumer.DesugaredLibraryRetargeterL8SynthesizerEventConsumer;
 import com.android.tools.r8.ir.synthetic.EmulateDispatchSyntheticCfCodeProvider;
 import com.android.tools.r8.synthesis.SyntheticClassBuilder;
 import com.android.tools.r8.synthesis.SyntheticNaming.SyntheticKind;
 import com.android.tools.r8.utils.DescriptorUtils;
-import java.util.Collections;
+import java.util.LinkedHashMap;
 
 public class DesugaredLibraryRetargeterSyntheticHelper {
 
@@ -27,54 +29,87 @@
     this.appView = appView;
   }
 
-  public DexClass ensureEmulatedHolderDispatchMethod(
-      DexClassAndMethod emulatedDispatchMethod,
+  public DexMethod ensureForwardingMethod(EmulatedDispatchMethodDescriptor descriptor) {
+    // TODO(b/184026720): We may synthesize a stub on the classpath if absent.
+    assert descriptor.getForwardingMethod().getHolderKind() == null;
+    return descriptor.getForwardingMethod().getMethod();
+  }
+
+  private DexMethod emulatedHolderDispatchMethod(DexType holder, DerivedMethod method) {
+    assert method.getHolderKind() == SyntheticKind.RETARGET_CLASS;
+    return appView.dexItemFactory().createMethod(holder, method.getProto(), method.getName());
+  }
+
+  DexMethod emulatedInterfaceDispatchMethod(DexType holder, DerivedMethod method) {
+    assert method.getHolderKind() == SyntheticKind.RETARGET_INTERFACE;
+    return appView.dexItemFactory().createMethod(holder, method.getProto(), method.getName());
+  }
+
+  public DexMethod getEmulatedInterfaceDispatchMethod(
+      DexClass newInterface, EmulatedDispatchMethodDescriptor descriptor) {
+    DexMethod method =
+        emulatedInterfaceDispatchMethod(newInterface.type, descriptor.getInterfaceMethod());
+    assert newInterface.lookupMethod(method) != null;
+    return method;
+  }
+
+  public DexMethod ensureEmulatedHolderDispatchMethod(
+      EmulatedDispatchMethodDescriptor descriptor,
       DesugaredLibraryRetargeterInstructionEventConsumer eventConsumer) {
     assert eventConsumer != null;
+    DerivedMethod emulatedDispatchMethod = descriptor.getEmulatedDispatchMethod();
+    DexClass holderContext =
+        appView.contextIndependentDefinitionFor(emulatedDispatchMethod.getHolderContext());
+    DexClass syntheticClass;
     if (appView.options().isDesugaredLibraryCompilation()) {
-      return appView
-          .getSyntheticItems()
-          .getExistingFixedClass(
-              SyntheticKind.RETARGET_CLASS, emulatedDispatchMethod.getHolder(), appView);
+      syntheticClass =
+          appView
+              .getSyntheticItems()
+              .getExistingFixedClass(
+                  emulatedDispatchMethod.getHolderKind(), holderContext, appView);
+      DexMethod dispatchMethod =
+          emulatedHolderDispatchMethod(syntheticClass.type, emulatedDispatchMethod);
+      assert syntheticClass.lookupMethod(dispatchMethod) != null;
+      return dispatchMethod;
+    } else {
+      DexClass itfClass = ensureEmulatedInterfaceDispatchMethod(descriptor, eventConsumer);
+      ClasspathOrLibraryClass context = holderContext.asClasspathOrLibraryClass();
+      assert context != null;
+      syntheticClass =
+          appView
+              .getSyntheticItems()
+              .ensureFixedClasspathClass(
+                  SyntheticKind.RETARGET_CLASS,
+                  context,
+                  appView,
+                  classBuilder -> buildHolderDispatchMethod(classBuilder, itfClass, descriptor),
+                  clazz -> {
+                    eventConsumer.acceptDesugaredLibraryRetargeterDispatchClasspathClass(clazz);
+                    rewriteType(clazz.type);
+                  });
     }
-    DexClass interfaceClass =
-        ensureEmulatedInterfaceDispatchMethod(emulatedDispatchMethod, eventConsumer);
-    DexMethod itfMethod =
-        interfaceClass.lookupMethod(emulatedDispatchMethod.getReference()).getReference();
-    ClasspathOrLibraryClass context =
-        emulatedDispatchMethod.getHolder().asClasspathOrLibraryClass();
-    assert context != null;
-    return appView
-        .getSyntheticItems()
-        .ensureFixedClasspathClass(
-            SyntheticKind.RETARGET_CLASS,
-            context,
-            appView,
-            classBuilder ->
-                buildHolderDispatchMethod(classBuilder, emulatedDispatchMethod, itfMethod),
-            clazz -> {
-              eventConsumer.acceptDesugaredLibraryRetargeterDispatchClasspathClass(clazz);
-              rewriteType(clazz.type);
-            });
+    DexMethod dispatchMethod =
+        emulatedHolderDispatchMethod(syntheticClass.type, emulatedDispatchMethod);
+    assert syntheticClass.lookupMethod(dispatchMethod) != null;
+    return dispatchMethod;
   }
 
   public void ensureProgramEmulatedHolderDispatchMethod(
-      DexClassAndMethod emulatedDispatchMethod,
+      EmulatedDispatchMethodDescriptor descriptor,
       DesugaredLibraryRetargeterL8SynthesizerEventConsumer eventConsumer) {
     assert eventConsumer != null;
     assert appView.options().isDesugaredLibraryCompilation();
-    DexClass interfaceClass =
-        ensureEmulatedInterfaceDispatchMethod(emulatedDispatchMethod, eventConsumer);
-    DexMethod itfMethod =
-        interfaceClass.lookupMethod(emulatedDispatchMethod.getReference()).getReference();
+    DerivedMethod emulatedDispatchMethod = descriptor.getEmulatedDispatchMethod();
+    DexClass holderContext =
+        appView.contextIndependentDefinitionFor(emulatedDispatchMethod.getHolderContext());
+    DexClass itfClass = ensureEmulatedInterfaceDispatchMethod(descriptor, eventConsumer);
     appView
         .getSyntheticItems()
         .ensureFixedClass(
-            SyntheticKind.RETARGET_CLASS,
-            emulatedDispatchMethod.getHolder(),
+            emulatedDispatchMethod.getHolderKind(),
+            holderContext,
             appView,
-            classBuilder ->
-                buildHolderDispatchMethod(classBuilder, emulatedDispatchMethod, itfMethod),
+            classBuilder -> buildHolderDispatchMethod(classBuilder, itfClass, descriptor),
             clazz -> {
               eventConsumer.acceptDesugaredLibraryRetargeterDispatchProgramClass(clazz);
               rewriteType(clazz.type);
@@ -82,17 +117,17 @@
   }
 
   public DexClass ensureEmulatedInterfaceDispatchMethod(
-      DexClassAndMethod emulatedDispatchMethod,
+      EmulatedDispatchMethodDescriptor descriptor,
       DesugaredLibraryRetargeterInstructionEventConsumer eventConsumer) {
     assert eventConsumer != null;
+    DerivedMethod itfMethod = descriptor.getInterfaceMethod();
+    DexClass itfContext = appView.contextIndependentDefinitionFor(itfMethod.getHolderContext());
     if (appView.options().isDesugaredLibraryCompilation()) {
       return appView
           .getSyntheticItems()
-          .getExistingFixedClass(
-              SyntheticKind.RETARGET_INTERFACE, emulatedDispatchMethod.getHolder(), appView);
+          .getExistingFixedClass(itfMethod.getHolderKind(), itfContext, appView);
     }
-    ClasspathOrLibraryClass context =
-        emulatedDispatchMethod.getHolder().asClasspathOrLibraryClass();
+    ClasspathOrLibraryClass context = itfContext.asClasspathOrLibraryClass();
     assert context != null;
     return appView
         .getSyntheticItems()
@@ -100,7 +135,7 @@
             SyntheticKind.RETARGET_INTERFACE,
             context,
             appView,
-            classBuilder -> buildInterfaceDispatchMethod(classBuilder, emulatedDispatchMethod),
+            classBuilder -> buildInterfaceDispatchMethod(classBuilder, descriptor),
             clazz -> {
               eventConsumer.acceptDesugaredLibraryRetargeterDispatchClasspathClass(clazz);
               rewriteType(clazz.type);
@@ -108,17 +143,19 @@
   }
 
   public DexClass ensureEmulatedInterfaceDispatchMethod(
-      DexClassAndMethod emulatedDispatchMethod,
+      EmulatedDispatchMethodDescriptor descriptor,
       DesugaredLibraryRetargeterL8SynthesizerEventConsumer eventConsumer) {
     assert appView.options().isDesugaredLibraryCompilation();
     assert eventConsumer != null;
+    DerivedMethod itfMethod = descriptor.getInterfaceMethod();
+    DexClass itfContext = appView.contextIndependentDefinitionFor(itfMethod.getHolderContext());
     return appView
         .getSyntheticItems()
         .ensureFixedClass(
-            SyntheticKind.RETARGET_INTERFACE,
-            emulatedDispatchMethod.getHolder(),
+            itfMethod.getHolderKind(),
+            itfContext,
             appView,
-            classBuilder -> buildInterfaceDispatchMethod(classBuilder, emulatedDispatchMethod),
+            classBuilder -> buildInterfaceDispatchMethod(classBuilder, descriptor),
             clazz -> {
               eventConsumer.acceptDesugaredLibraryRetargeterDispatchProgramClass(clazz);
               rewriteType(clazz.type);
@@ -126,18 +163,21 @@
   }
 
   private void buildInterfaceDispatchMethod(
-      SyntheticClassBuilder<?, ?> classBuilder, DexClassAndMethod emulatedDispatchMethod) {
+      SyntheticClassBuilder<?, ?> classBuilder, EmulatedDispatchMethodDescriptor descriptor) {
     classBuilder
         .setInterface()
         .addMethod(
             methodBuilder -> {
+              DexMethod itfMethod =
+                  emulatedInterfaceDispatchMethod(
+                      classBuilder.getType(), descriptor.getInterfaceMethod());
               MethodAccessFlags flags =
                   MethodAccessFlags.fromSharedAccessFlags(
                       Constants.ACC_PUBLIC | Constants.ACC_ABSTRACT | Constants.ACC_SYNTHETIC,
                       false);
               methodBuilder
-                  .setName(emulatedDispatchMethod.getName())
-                  .setProto(emulatedDispatchMethod.getProto())
+                  .setName(itfMethod.getName())
+                  .setProto(itfMethod.getProto())
                   // Will be traced by the enqueuer.
                   .disableAndroidApiLevelCheck()
                   .setAccessFlags(flags);
@@ -145,36 +185,36 @@
   }
 
   private <SCB extends SyntheticClassBuilder<?, ?>> void buildHolderDispatchMethod(
-      SCB classBuilder, DexClassAndMethod emulatedDispatchMethod, DexMethod itfMethod) {
+      SCB classBuilder, DexClass itfClass, EmulatedDispatchMethodDescriptor descriptor) {
     classBuilder.addMethod(
         methodBuilder -> {
-          DexMethod desugarMethod =
-              appView
-                  .options()
-                  .desugaredLibrarySpecification
-                  .retargetMethod(emulatedDispatchMethod, appView);
-          assert desugarMethod
-              != null; // This method is reached only for retarget core lib members.
+          DexMethod dispatchMethod =
+              emulatedHolderDispatchMethod(
+                  classBuilder.getType(), descriptor.getEmulatedDispatchMethod());
           methodBuilder
-              .setName(emulatedDispatchMethod.getName())
-              .setProto(desugarMethod.proto)
+              .setName(dispatchMethod.getName())
+              .setProto(dispatchMethod.getProto())
               .setAccessFlags(MethodAccessFlags.createPublicStaticSynthetic())
               // Will be traced by the enqueuer.
               .disableAndroidApiLevelCheck()
               .setCode(
                   methodSig ->
                       appView.options().isDesugaredLibraryCompilation()
-                          ? new EmulateDispatchSyntheticCfCodeProvider(
-                                  methodSig.getHolderType(),
-                                  desugarMethod,
-                                  itfMethod,
-                                  Collections.emptyList(),
-                                  appView)
-                              .generateCfCode()
+                          ? generateEmulatedDispatchCfCode(descriptor, itfClass, methodSig)
                           : null);
         });
   }
 
+  private CfCode generateEmulatedDispatchCfCode(
+      EmulatedDispatchMethodDescriptor descriptor, DexClass itfClass, DexMethod methodSig) {
+    DexMethod forwardingMethod = ensureForwardingMethod(descriptor);
+    DexMethod itfMethod = getEmulatedInterfaceDispatchMethod(itfClass, descriptor);
+    assert descriptor.getDispatchCases().isEmpty();
+    return new EmulateDispatchSyntheticCfCodeProvider(
+            methodSig.getHolderType(), forwardingMethod, itfMethod, new LinkedHashMap<>(), appView)
+        .generateCfCode();
+  }
+
   private void rewriteType(DexType type) {
     String newName =
         appView.options().desugaredLibrarySpecification.convertJavaNameToDesugaredLibrary(type);
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/retargeter/RetargetingInfo.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/retargeter/RetargetingInfo.java
index d9656fe..eb38e9e 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/retargeter/RetargetingInfo.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/retargeter/RetargetingInfo.java
@@ -12,55 +12,64 @@
 import com.android.tools.r8.graph.DexString;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.ir.desugar.desugaredlibrary.legacyspecification.LegacyDesugaredLibrarySpecification;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.machinespecification.DerivedMethod;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.machinespecification.EmulatedDispatchMethodDescriptor;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.machinespecification.MachineRewritingFlags;
+import com.android.tools.r8.synthesis.SyntheticNaming.SyntheticKind;
 import com.android.tools.r8.utils.WorkList;
-import com.android.tools.r8.utils.collections.DexClassAndMethodSet;
 import com.google.common.collect.ImmutableMap;
 import java.util.ArrayList;
 import java.util.IdentityHashMap;
+import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
 
 public class RetargetingInfo {
 
-  private final Map<DexMethod, DexMethod> retargetLibraryMember;
-  // Map nonFinalRewrite hold a methodName -> method mapping for methods which are rewritten while
-  // the holder is non final. In this case d8 needs to force resolution of given methods to see if
-  // the invoke needs to be rewritten.
-  private final Map<DexString, List<DexMethod>> nonFinalHolderRewrites;
-  // Non final virtual library methods requiring generation of emulated dispatch.
-  private final DexClassAndMethodSet emulatedDispatchMethods;
+  private final Map<DexMethod, DexMethod> staticRetarget;
+  private final Map<DexMethod, DexMethod> nonEmulatedVirtualRetarget;
+  private final Map<DexMethod, EmulatedDispatchMethodDescriptor> emulatedVirtualRetarget;
 
   RetargetingInfo(
-      Map<DexMethod, DexMethod> retargetLibraryMember,
-      Map<DexString, List<DexMethod>> nonFinalHolderRewrites,
-      DexClassAndMethodSet emulatedDispatchMethods) {
-    this.retargetLibraryMember = retargetLibraryMember;
-    this.nonFinalHolderRewrites = nonFinalHolderRewrites;
-    this.emulatedDispatchMethods = emulatedDispatchMethods;
+      Map<DexMethod, DexMethod> staticRetarget,
+      Map<DexMethod, DexMethod> nonEmulatedVirtualRetarget,
+      Map<DexMethod, EmulatedDispatchMethodDescriptor> emulatedVirtualRetarget) {
+    this.staticRetarget = staticRetarget;
+    this.nonEmulatedVirtualRetarget = nonEmulatedVirtualRetarget;
+    this.emulatedVirtualRetarget = emulatedVirtualRetarget;
   }
 
-  public static synchronized RetargetingInfo get(AppView<?> appView) {
+  public static RetargetingInfo get(AppView<?> appView) {
+    if (appView.options().testing.machineDesugaredLibrarySpecification != null) {
+      MachineRewritingFlags rewritingFlags =
+          appView.options().testing.machineDesugaredLibrarySpecification.getRewritingFlags();
+      return new RetargetingInfo(
+          rewritingFlags.getStaticRetarget(),
+          rewritingFlags.getNonEmulatedVirtualRetarget(),
+          rewritingFlags.getEmulatedVirtualRetarget());
+    }
     return new RetargetingInfoBuilder(appView).computeRetargetingInfo();
   }
 
-  public Map<DexMethod, DexMethod> getRetargetLibraryMember() {
-    return retargetLibraryMember;
+  public Map<DexMethod, DexMethod> getStaticRetarget() {
+    return staticRetarget;
   }
 
-  public Map<DexString, List<DexMethod>> getNonFinalHolderRewrites() {
-    return nonFinalHolderRewrites;
+  public Map<DexMethod, DexMethod> getNonEmulatedVirtualRetarget() {
+    return nonEmulatedVirtualRetarget;
   }
 
-  public DexClassAndMethodSet getEmulatedDispatchMethods() {
-    return emulatedDispatchMethods;
+  public Map<DexMethod, EmulatedDispatchMethodDescriptor> getEmulatedVirtualRetarget() {
+    return emulatedVirtualRetarget;
   }
 
   private static class RetargetingInfoBuilder {
 
     private final AppView<?> appView;
-    private final Map<DexMethod, DexMethod> retargetLibraryMember = new IdentityHashMap<>();
-    private final Map<DexString, List<DexMethod>> nonFinalHolderRewrites = new IdentityHashMap<>();
-    private final DexClassAndMethodSet emulatedDispatchMethods = DexClassAndMethodSet.create();
+    private final Map<DexMethod, DexMethod> staticRetarget = new IdentityHashMap<>();
+    private final Map<DexMethod, DexMethod> nonEmulatedVirtualRetarget = new IdentityHashMap<>();
+    private final Map<DexMethod, EmulatedDispatchMethodDescriptor> emulatedVirtualRetarget =
+        new IdentityHashMap<>();
 
     public RetargetingInfoBuilder(AppView<?> appView) {
       this.appView = appView;
@@ -72,8 +81,7 @@
       Map<DexString, Map<DexType, DexType>> retargetCoreLibMember =
           desugaredLibrarySpecification.getRetargetCoreLibMember();
       if (retargetCoreLibMember.isEmpty()) {
-        return new RetargetingInfo(
-            ImmutableMap.of(), ImmutableMap.of(), DexClassAndMethodSet.empty());
+        return new RetargetingInfo(ImmutableMap.of(), ImmutableMap.of(), ImmutableMap.of());
       }
       for (DexString methodName : retargetCoreLibMember.keySet()) {
         for (DexType inType : retargetCoreLibMember.get(methodName).keySet()) {
@@ -82,28 +90,41 @@
             DexType newHolder = retargetCoreLibMember.get(methodName).get(inType);
             List<DexClassAndMethod> found = findMethodsWithName(methodName, typeClass);
             for (DexClassAndMethod method : found) {
-              boolean emulatedDispatch = false;
               DexMethod methodReference = method.getReference();
-              if (!typeClass.isFinal()) {
-                nonFinalHolderRewrites.putIfAbsent(method.getName(), new ArrayList<>());
-                nonFinalHolderRewrites.get(method.getName()).add(methodReference);
-                if (!method.getAccessFlags().isStatic()) {
-                  if (isEmulatedInterfaceDispatch(method)) {
-                    // In this case interface method rewriter takes care of it.
-                    continue;
-                  } else if (!method.getAccessFlags().isFinal()) {
-                    // Virtual rewrites require emulated dispatch for inheritance.
-                    // The call is rewritten to the dispatch holder class instead.
-                    emulatedDispatchMethods.add(method);
-                    emulatedDispatch = true;
-                  }
-                }
-              }
-              if (!emulatedDispatch) {
-                retargetLibraryMember.put(
+              if (method.getAccessFlags().isStatic()) {
+                staticRetarget.put(
                     methodReference,
                     computeRetargetMethod(
                         methodReference, method.getAccessFlags().isStatic(), newHolder));
+                continue;
+              }
+              if (isEmulatedInterfaceDispatch(method)) {
+                continue;
+              }
+              if (typeClass.isFinal() || method.getAccessFlags().isFinal()) {
+                nonEmulatedVirtualRetarget.put(
+                    methodReference,
+                    computeRetargetMethod(
+                        methodReference, method.getAccessFlags().isStatic(), newHolder));
+              } else {
+                // Virtual rewrites require emulated dispatch for inheritance.
+                // The call is rewritten to the dispatch holder class instead.
+                DexProto newProto = appView.dexItemFactory().prependHolderToProto(methodReference);
+                DexMethod forwardingDexMethod =
+                    appView.dexItemFactory().createMethod(newHolder, newProto, methodName);
+                DerivedMethod forwardingMethod = new DerivedMethod(forwardingDexMethod);
+                DerivedMethod interfaceMethod =
+                    new DerivedMethod(methodReference, SyntheticKind.RETARGET_INTERFACE);
+                DexMethod dispatchDexMethod =
+                    appView
+                        .dexItemFactory()
+                        .createMethod(methodReference.getHolderType(), newProto, methodName);
+                DerivedMethod dispatchMethod =
+                    new DerivedMethod(dispatchDexMethod, SyntheticKind.RETARGET_CLASS);
+                emulatedVirtualRetarget.put(
+                    methodReference,
+                    new EmulatedDispatchMethodDescriptor(
+                        interfaceMethod, dispatchMethod, forwardingMethod, new LinkedHashMap<>()));
               }
             }
           }
@@ -123,7 +144,7 @@
         DexMethod target =
             computeRetargetMethod(
                 source, true, itemFactory.createType("Ljava/util/DesugarArrays;"));
-        retargetLibraryMember.put(source, target);
+        staticRetarget.put(source, target);
         // TODO(b/181629049): This is only a workaround rewriting invokes of
         //  j.u.TimeZone.getTimeZone taking a java.time.ZoneId.
         name = itemFactory.createString("getTimeZone");
@@ -136,12 +157,12 @@
         target =
             computeRetargetMethod(
                 source, true, itemFactory.createType("Ljava/util/DesugarTimeZone;"));
-        retargetLibraryMember.put(source, target);
+        staticRetarget.put(source, target);
       }
       return new RetargetingInfo(
-          ImmutableMap.copyOf(retargetLibraryMember),
-          ImmutableMap.copyOf(nonFinalHolderRewrites),
-          emulatedDispatchMethods);
+          ImmutableMap.copyOf(staticRetarget),
+          ImmutableMap.copyOf(nonEmulatedVirtualRetarget),
+          emulatedVirtualRetarget);
     }
 
     DexMethod computeRetargetMethod(DexMethod method, boolean isStatic, DexType newHolder) {
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/specificationconversion/HumanToMachineSpecificationConverter.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/specificationconversion/HumanToMachineSpecificationConverter.java
new file mode 100644
index 0000000..d2227cb
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/specificationconversion/HumanToMachineSpecificationConverter.java
@@ -0,0 +1,211 @@
+// Copyright (c) 2022, 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.desugar.desugaredlibrary.specificationconversion;
+
+import com.android.tools.r8.dex.ApplicationReader;
+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.DexApplication;
+import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexProto;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.MethodResolutionResult;
+import com.android.tools.r8.graph.SubtypingInfo;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.humanspecification.HumanDesugaredLibrarySpecification;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.humanspecification.HumanRewritingFlags;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.machinespecification.DerivedMethod;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.machinespecification.EmulatedDispatchMethodDescriptor;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.machinespecification.MachineDesugaredLibrarySpecification;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.machinespecification.MachineRewritingFlags;
+import com.android.tools.r8.synthesis.SyntheticNaming.SyntheticKind;
+import com.android.tools.r8.utils.AndroidApp;
+import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.ThreadUtils;
+import com.android.tools.r8.utils.Timing;
+import com.android.tools.r8.utils.TraversalContinuation;
+import java.io.IOException;
+import java.nio.file.Path;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.concurrent.ExecutorService;
+import java.util.function.BiConsumer;
+
+public class HumanToMachineSpecificationConverter {
+
+  public MachineDesugaredLibrarySpecification convert(
+      HumanDesugaredLibrarySpecification humanSpec, Path androidLib, InternalOptions options)
+      throws IOException {
+    DexApplication app = readApp(androidLib, options);
+    AppView<?> appView = AppView.createForD8(AppInfo.createInitialAppInfo(app));
+    MachineRewritingFlags machineRewritingFlags =
+        convertRewritingFlags(humanSpec.getRewritingFlags(), appView.appInfoForDesugaring());
+    return new MachineDesugaredLibrarySpecification(
+        humanSpec.isLibraryCompilation(), machineRewritingFlags);
+  }
+
+  private MachineRewritingFlags convertRewritingFlags(
+      HumanRewritingFlags rewritingFlags, AppInfoWithClassHierarchy appInfo) {
+    MachineRewritingFlags.Builder builder = MachineRewritingFlags.builder();
+    SubtypingInfo subtypingInfo = new SubtypingInfo(appInfo);
+    rewritingFlags
+        .getRetargetCoreLibMember()
+        .forEach(
+            (method, type) ->
+                convertRetargetCoreLibMemberFlag(
+                    builder, rewritingFlags, method, type, appInfo, subtypingInfo));
+    return builder.build();
+  }
+
+  private void convertRetargetCoreLibMemberFlag(
+      MachineRewritingFlags.Builder builder,
+      HumanRewritingFlags rewritingFlags,
+      DexMethod method,
+      DexType type,
+      AppInfoWithClassHierarchy appInfo,
+      SubtypingInfo subtypingInfo) {
+    DexClass holder = appInfo.definitionFor(method.holder);
+    DexEncodedMethod foundMethod = holder.lookupMethod(method);
+    assert foundMethod != null;
+    if (foundMethod.isStatic()) {
+      convertStaticRetarget(builder, foundMethod, type, appInfo, subtypingInfo);
+      return;
+    }
+    if (holder.isFinal() || foundMethod.isFinal()) {
+      convertNonEmulatedVirtualRetarget(builder, foundMethod, type, appInfo, subtypingInfo);
+      return;
+    }
+    convertEmulatedVirtualRetarget(
+        builder, rewritingFlags, foundMethod, type, appInfo, subtypingInfo);
+  }
+
+  private void convertEmulatedVirtualRetarget(
+      MachineRewritingFlags.Builder builder,
+      HumanRewritingFlags rewritingFlags,
+      DexEncodedMethod src,
+      DexType type,
+      AppInfoWithClassHierarchy appInfo,
+      SubtypingInfo subtypingInfo) {
+    if (isEmulatedInterfaceDispatch(src, appInfo, rewritingFlags)) {
+      // Handled by emulated interface dispatch.
+      return;
+    }
+    // TODO(b/184026720): Implement library boundaries.
+    DexProto newProto = appInfo.dexItemFactory().prependHolderToProto(src.getReference());
+    DexMethod forwardingDexMethod =
+        appInfo.dexItemFactory().createMethod(type, newProto, src.getName());
+    DerivedMethod forwardingMethod = new DerivedMethod(forwardingDexMethod);
+    DerivedMethod interfaceMethod =
+        new DerivedMethod(src.getReference(), SyntheticKind.RETARGET_INTERFACE);
+    DexMethod dispatchDexMethod =
+        appInfo.dexItemFactory().createMethod(src.getHolderType(), newProto, src.getName());
+    DerivedMethod dispatchMethod =
+        new DerivedMethod(dispatchDexMethod, SyntheticKind.RETARGET_CLASS);
+    LinkedHashMap<DexType, DerivedMethod> dispatchCases = new LinkedHashMap<>();
+    assert validateNoOverride(src, appInfo, subtypingInfo);
+    builder.putEmulatedVirtualRetarget(
+        src.getReference(),
+        new EmulatedDispatchMethodDescriptor(
+            interfaceMethod, dispatchMethod, forwardingMethod, dispatchCases));
+  }
+
+  private boolean validateNoOverride(
+      DexEncodedMethod src, AppInfoWithClassHierarchy appInfo, SubtypingInfo subtypingInfo) {
+    for (DexType subtype : subtypingInfo.subtypes(src.getHolderType())) {
+      DexClass subclass = appInfo.definitionFor(subtype);
+      MethodResolutionResult resolutionResult =
+          appInfo.resolveMethodOn(subclass, src.getReference());
+      if (resolutionResult.isSuccessfulMemberResolutionResult()
+          && resolutionResult.getResolvedMethod().getReference() != src.getReference()) {
+        assert false; // Unsupported.
+      }
+    }
+    return true;
+  }
+
+  private boolean isEmulatedInterfaceDispatch(
+      DexEncodedMethod method,
+      AppInfoWithClassHierarchy appInfo,
+      HumanRewritingFlags humanRewritingFlags) {
+    // Answers true if this method is already managed through emulated interface dispatch.
+    Map<DexType, DexType> emulateLibraryInterface =
+        humanRewritingFlags.getEmulateLibraryInterface();
+    if (emulateLibraryInterface.isEmpty()) {
+      return false;
+    }
+    DexMethod methodToFind = method.getReference();
+    // Look-up all superclass and interfaces, if an emulated interface is found,
+    // and it implements the method, answers true.
+    DexClass dexClass = appInfo.definitionFor(method.getHolderType());
+    // Cannot retarget a method on a virtual method on an emulated interface.
+    assert !emulateLibraryInterface.containsKey(dexClass.getType());
+    return appInfo
+        .traverseSuperTypes(
+            dexClass,
+            (supertype, subclass, isSupertypeAnInterface) ->
+                TraversalContinuation.breakIf(
+                    subclass.isInterface()
+                        && emulateLibraryInterface.containsKey(subclass.getType())
+                        && subclass.lookupMethod(methodToFind) != null))
+        .shouldBreak();
+  }
+
+  private void convertNonEmulatedRetarget(
+      DexEncodedMethod foundMethod,
+      DexType type,
+      AppInfoWithClassHierarchy appInfo,
+      SubtypingInfo subtypingInfo,
+      BiConsumer<DexMethod, DexMethod> consumer) {
+    DexMethod src = foundMethod.getReference();
+    DexMethod dest = src.withHolder(type, appInfo.dexItemFactory());
+    consumer.accept(src, dest);
+    for (DexType subtype : subtypingInfo.subtypes(foundMethod.getHolderType())) {
+      DexClass subclass = appInfo.definitionFor(subtype);
+      MethodResolutionResult resolutionResult = appInfo.resolveMethodOn(subclass, src);
+      if (resolutionResult.isSuccessfulMemberResolutionResult()
+          && resolutionResult.getResolvedMethod().getReference() == src) {
+        consumer.accept(src.withHolder(subtype, appInfo.dexItemFactory()), dest);
+      }
+    }
+  }
+
+  private void convertNonEmulatedVirtualRetarget(
+      MachineRewritingFlags.Builder builder,
+      DexEncodedMethod foundMethod,
+      DexType type,
+      AppInfoWithClassHierarchy appInfo,
+      SubtypingInfo subtypingInfo) {
+    convertNonEmulatedRetarget(
+        foundMethod,
+        type,
+        appInfo,
+        subtypingInfo,
+        (src, dest) ->
+            builder.putNonEmulatedVirtualRetarget(
+                src,
+                dest.withExtraArgumentPrepended(
+                    foundMethod.getHolderType(), appInfo.dexItemFactory())));
+  }
+
+  private void convertStaticRetarget(
+      MachineRewritingFlags.Builder builder,
+      DexEncodedMethod foundMethod,
+      DexType type,
+      AppInfoWithClassHierarchy appInfo,
+      SubtypingInfo subtypingInfo) {
+    convertNonEmulatedRetarget(
+        foundMethod, type, appInfo, subtypingInfo, builder::putStaticRetarget);
+  }
+
+  private DexApplication readApp(Path androidLib, InternalOptions options) throws IOException {
+    AndroidApp androidApp = AndroidApp.builder().addProgramFile(androidLib).build();
+    ApplicationReader applicationReader =
+        new ApplicationReader(androidApp, options, Timing.empty());
+    ExecutorService executorService = ThreadUtils.getExecutorService(options);
+    return applicationReader.read(executorService).toDirect();
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/specificationconversion/LegacyToHumanSpecificationConverter.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/specificationconversion/LegacyToHumanSpecificationConverter.java
index 14898fc..34bf5fa 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/specificationconversion/LegacyToHumanSpecificationConverter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/specificationconversion/LegacyToHumanSpecificationConverter.java
@@ -15,7 +15,6 @@
 import com.android.tools.r8.graph.DexProto;
 import com.android.tools.r8.graph.DexString;
 import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.graph.DirectMappedDexApplication;
 import com.android.tools.r8.ir.desugar.desugaredlibrary.humanspecification.HumanDesugaredLibrarySpecification;
 import com.android.tools.r8.ir.desugar.desugaredlibrary.humanspecification.HumanRewritingFlags;
 import com.android.tools.r8.ir.desugar.desugaredlibrary.humanspecification.HumanTopLevelFlags;
@@ -43,9 +42,8 @@
 import java.util.Map;
 import java.util.concurrent.ExecutorService;
 
-public class LegacyToHumanSpecificationConverter implements SpecificationConverter {
+public class LegacyToHumanSpecificationConverter {
 
-  @Override
   public void convertAllAPILevels(
       StringResource inputSpecification, Path androidLib, StringConsumer output)
       throws IOException {
@@ -136,8 +134,7 @@
     libraryFlags.put(level, builder.build());
   }
 
-  private DirectMappedDexApplication readApp(Path androidLib, InternalOptions options)
-      throws IOException {
+  private DexApplication readApp(Path androidLib, InternalOptions options) throws IOException {
     AndroidApp androidApp = AndroidApp.builder().addLibraryFile(androidLib).build();
     ApplicationReader applicationReader =
         new ApplicationReader(androidApp, options, Timing.empty());
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/specificationconversion/SpecificationConverter.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/specificationconversion/SpecificationConverter.java
deleted file mode 100644
index 85a8c8b..0000000
--- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/specificationconversion/SpecificationConverter.java
+++ /dev/null
@@ -1,16 +0,0 @@
-// 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.desugar.desugaredlibrary.specificationconversion;
-
-import com.android.tools.r8.StringConsumer;
-import com.android.tools.r8.StringResource;
-import java.io.IOException;
-import java.nio.file.Path;
-
-public interface SpecificationConverter {
-
-  void convertAllAPILevels(
-      StringResource inputSpecification, Path androidLib, StringConsumer output) throws IOException;
-}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/itf/ClassProcessor.java b/src/main/java/com/android/tools/r8/ir/desugar/itf/ClassProcessor.java
index 0d79ac1..3a91fef 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/itf/ClassProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/itf/ClassProcessor.java
@@ -853,7 +853,7 @@
             : appView.options().desugaredLibrarySpecification.retargetMethod(target, appView);
     DexEncodedMethod desugaringForwardingMethod =
         DexEncodedMethod.createDesugaringForwardingMethod(
-            target, clazz, forwardMethod, dexItemFactory);
+            target.getDefinition(), clazz, forwardMethod, dexItemFactory);
     if (!target.isProgramDefinition()
         || target.getDefinition().isLibraryMethodOverride().isTrue()) {
       desugaringForwardingMethod.setLibraryMethodOverride(OptionalBool.TRUE);
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/itf/ProgramEmulatedInterfaceSynthesizer.java b/src/main/java/com/android/tools/r8/ir/desugar/itf/ProgramEmulatedInterfaceSynthesizer.java
index 3d91f5c..55cf273 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/itf/ProgramEmulatedInterfaceSynthesizer.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/itf/ProgramEmulatedInterfaceSynthesizer.java
@@ -20,7 +20,6 @@
 import com.android.tools.r8.synthesis.SyntheticMethodBuilder;
 import com.android.tools.r8.synthesis.SyntheticNaming;
 import com.android.tools.r8.synthesis.SyntheticProgramClassBuilder;
-import com.android.tools.r8.utils.Pair;
 import com.android.tools.r8.utils.StringDiagnostic;
 import com.google.common.collect.Iterables;
 import com.google.common.collect.Sets;
@@ -28,6 +27,7 @@
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.IdentityHashMap;
+import java.util.LinkedHashMap;
 import java.util.LinkedList;
 import java.util.List;
 import java.util.Map;
@@ -143,7 +143,7 @@
             .withHolder(helper.getEmulatedInterface(theInterface.type), appView.dexItemFactory());
     DexMethod companionMethod =
         helper.ensureDefaultAsMethodOfProgramCompanionClassStub(method).getReference();
-    List<Pair<DexType, DexMethod>> extraDispatchCases =
+    LinkedHashMap<DexType, DexMethod> extraDispatchCases =
         getDispatchCases(method, theInterface, companionMethod);
     return new EmulateDispatchSyntheticCfCodeProvider(
             emulatedInterfaceMethod.getHolderType(),
@@ -154,14 +154,14 @@
         .generateCfCode();
   }
 
-  private List<Pair<DexType, DexMethod>> getDispatchCases(
+  private LinkedHashMap<DexType, DexMethod> getDispatchCases(
       ProgramMethod method, DexProgramClass theInterface, DexMethod companionMethod) {
     // To properly emulate the library interface call, we need to compute the interfaces
     // inheriting from the interface and manually implement the dispatch with instance of.
     // The list guarantees that an interface is always after interfaces it extends,
     // hence reverse iteration.
     List<DexType> subInterfaces = emulatedInterfacesHierarchy.get(theInterface.type);
-    List<Pair<DexType, DexMethod>> extraDispatchCases = new ArrayList<>();
+    LinkedHashMap<DexType, DexMethod> extraDispatchCases = new LinkedHashMap<>();
     // In practice, there is usually a single case (except for tests),
     // so we do not bother to make the following loop more clever.
     Map<DexString, Map<DexType, DexType>> retargetCoreLibMember =
@@ -171,17 +171,16 @@
         for (DexType inType : retargetCoreLibMember.get(methodName).keySet()) {
           DexClass inClass = appView.definitionFor(inType);
           if (inClass != null && implementsInterface(inClass, theInterface.type)) {
-            extraDispatchCases.add(
-                new Pair<>(
-                    inType,
-                    appView
-                        .dexItemFactory()
-                        .createMethod(
-                            retargetCoreLibMember.get(methodName).get(inType),
-                            appView
-                                .dexItemFactory()
-                                .protoWithDifferentFirstParameter(companionMethod.proto, inType),
-                            method.getName())));
+            extraDispatchCases.put(
+                inType,
+                appView
+                    .dexItemFactory()
+                    .createMethod(
+                        retargetCoreLibMember.get(methodName).get(inType),
+                        appView
+                            .dexItemFactory()
+                            .protoWithDifferentFirstParameter(companionMethod.proto, inType),
+                        method.getName()));
           }
         }
       }
@@ -196,11 +195,10 @@
         DexEncodedMethod result = subInterfaceClass.lookupVirtualMethod(method.getReference());
         if (result != null && !result.isAbstract()) {
           assert result.isDefaultMethod();
-          extraDispatchCases.add(
-              new Pair<>(
-                  subInterfaceClass.type,
-                  InterfaceDesugaringSyntheticHelper.defaultAsMethodOfCompanionClass(
-                      result.getReference(), appView.dexItemFactory())));
+          extraDispatchCases.put(
+              subInterfaceClass.type,
+              InterfaceDesugaringSyntheticHelper.defaultAsMethodOfCompanionClass(
+                  result.getReference(), appView.dexItemFactory()));
         }
       }
     } else {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/EmptyEnumUnboxer.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EmptyEnumUnboxer.java
index e7edb01..1b9d97b 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/enums/EmptyEnumUnboxer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EmptyEnumUnboxer.java
@@ -10,6 +10,8 @@
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.analysis.fieldvalueanalysis.StaticFieldValues;
 import com.android.tools.r8.ir.code.IRCode;
+import com.android.tools.r8.ir.code.InstructionListIterator;
+import com.android.tools.r8.ir.code.InvokeMethod;
 import com.android.tools.r8.ir.code.Phi;
 import com.android.tools.r8.ir.conversion.IRConverter;
 import com.android.tools.r8.ir.conversion.MethodConversionOptions.MutableMethodConversionOptions;
@@ -62,6 +64,11 @@
   }
 
   @Override
+  public void rewriteNullCheck(InstructionListIterator iterator, InvokeMethod invoke) {
+    // Intentionally empty.
+  }
+
+  @Override
   public void unboxEnums(
       AppView<AppInfoWithLiveness> appView,
       IRConverter converter,
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 659e685..4179fed 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
@@ -10,6 +10,8 @@
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.analysis.fieldvalueanalysis.StaticFieldValues;
 import com.android.tools.r8.ir.code.IRCode;
+import com.android.tools.r8.ir.code.InstructionListIterator;
+import com.android.tools.r8.ir.code.InvokeMethod;
 import com.android.tools.r8.ir.code.Phi;
 import com.android.tools.r8.ir.conversion.IRConverter;
 import com.android.tools.r8.ir.conversion.MethodConversionOptions.MutableMethodConversionOptions;
@@ -44,6 +46,8 @@
 
   public abstract Set<Phi> rewriteCode(IRCode code, MethodProcessor methodProcessor);
 
+  public abstract void rewriteNullCheck(InstructionListIterator iterator, InvokeMethod invoke);
+
   public abstract void unboxEnums(
       AppView<AppInfoWithLiveness> appView,
       IRConverter converter,
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxerImpl.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxerImpl.java
index d35c7b7..acde184 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxerImpl.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxerImpl.java
@@ -62,6 +62,7 @@
 import com.android.tools.r8.ir.code.If;
 import com.android.tools.r8.ir.code.InstanceGet;
 import com.android.tools.r8.ir.code.Instruction;
+import com.android.tools.r8.ir.code.InstructionListIterator;
 import com.android.tools.r8.ir.code.InvokeCustom;
 import com.android.tools.r8.ir.code.InvokeMethod;
 import com.android.tools.r8.ir.code.InvokeStatic;
@@ -1457,6 +1458,13 @@
   }
 
   @Override
+  public void rewriteNullCheck(InstructionListIterator iterator, InvokeMethod invoke) {
+    if (enumUnboxerRewriter != null) {
+      enumUnboxerRewriter.rewriteNullCheck(iterator, invoke);
+    }
+  }
+
+  @Override
   public void unsetRewriter() {
     enumUnboxerRewriter = null;
   }
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 5fc743e..787b4da 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
@@ -148,9 +148,7 @@
                 continue;
               }
             } else if (invokedMethod == factory.objectMembers.getClass) {
-              assert !invoke.hasOutValue() || !invoke.outValue().hasAnyUsers();
-              replaceEnumInvoke(
-                  iterator, invoke, getSharedUtilityClass().ensureCheckNotZeroMethod(appView));
+              rewriteNullCheck(iterator, invoke);
               continue;
             }
           } else if (invokedMethod == factory.stringBuilderMethods.appendObject
@@ -346,10 +344,7 @@
         Value argument = invoke.getFirstArgument();
         DexType enumType = getEnumTypeOrNull(argument, convertedEnums);
         if (enumType != null) {
-          replaceEnumInvoke(
-              instructionIterator,
-              invoke,
-              getSharedUtilityClass().ensureCheckNotZeroMethod(appView));
+          rewriteNullCheck(instructionIterator, invoke);
         }
       } else if (invokedMethod == factory.objectsMethods.requireNonNullWithMessage) {
         assert invoke.arguments().size() == 2;
@@ -434,6 +429,11 @@
     }
   }
 
+  public void rewriteNullCheck(InstructionListIterator iterator, InvokeMethod invoke) {
+    assert !invoke.hasOutValue() || !invoke.outValue().hasAnyUsers();
+    replaceEnumInvoke(iterator, invoke, getSharedUtilityClass().ensureCheckNotZeroMethod(appView));
+  }
+
   private void removeRedundantValuesArrayCloning(
       InvokeStatic invoke, Set<Instruction> instructionsToRemove, Set<BasicBlock> seenBlocks) {
     for (Instruction user : invoke.outValue().aliasedUsers()) {
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 9997cc0..862840b 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
@@ -43,6 +43,7 @@
 import com.android.tools.r8.ir.optimize.enums.EnumDataMap.EnumData;
 import com.android.tools.r8.ir.optimize.enums.classification.CheckNotNullEnumUnboxerMethodClassification;
 import com.android.tools.r8.ir.optimize.enums.code.CheckNotZeroCode;
+import com.android.tools.r8.ir.optimize.info.DefaultMethodOptimizationInfo;
 import com.android.tools.r8.ir.optimize.info.OptimizationFeedback;
 import com.android.tools.r8.ir.optimize.info.OptimizationFeedbackIgnore;
 import com.android.tools.r8.ir.optimize.info.field.InstanceFieldInitializationInfo;
@@ -189,10 +190,10 @@
                               .setApiLevelForCode(appView.computedMinApiLevel())
                               .setCode(method -> new CheckNotZeroCode(checkNotNullMethod))
                               .setOptimizationInfo(
-                                  checkNotNullMethod
-                                      .getOptimizationInfo()
-                                      .asMutableMethodOptimizationInfo()
-                                      .mutableCopy())
+                                  DefaultMethodOptimizationInfo.getInstance()
+                                      .toMutableOptimizationInfo()
+                                      .setEnumUnboxerMethodClassification(
+                                          checkNotNullClassification))
                               .setProto(newProto));
           checkNotNullToCheckNotZeroMapping.put(
               checkNotNullMethod.getReference(), checkNotZeroMethod.getReference());
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/CallSiteOptimizationInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/CallSiteOptimizationInfo.java
index 6356bbf..b651b1f 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/CallSiteOptimizationInfo.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/CallSiteOptimizationInfo.java
@@ -24,7 +24,7 @@
 
   // The index exactly matches with in values of invocation, i.e., even including receiver.
   public DynamicType getDynamicType(int argIndex) {
-    return null;
+    return DynamicType.unknown();
   }
 
   // The index exactly matches with in values of invocation, i.e., even including receiver.
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/MutableMethodOptimizationInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/MutableMethodOptimizationInfo.java
index 2cb900b..e07fee5 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/MutableMethodOptimizationInfo.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/MutableMethodOptimizationInfo.java
@@ -282,12 +282,13 @@
     return enumUnboxerMethodClassification;
   }
 
-  void setEnumUnboxerMethodClassification(
+  public MutableMethodOptimizationInfo setEnumUnboxerMethodClassification(
       EnumUnboxerMethodClassification enumUnboxerMethodClassification) {
     // Check monotonicity.
     assert !this.enumUnboxerMethodClassification.isCheckNotNullClassification()
         || enumUnboxerMethodClassification.isCheckNotNullClassification();
     this.enumUnboxerMethodClassification = enumUnboxerMethodClassification;
+    return this;
   }
 
   public void unsetEnumUnboxerMethodClassification() {
diff --git a/src/main/java/com/android/tools/r8/ir/synthetic/EmulateDispatchSyntheticCfCodeProvider.java b/src/main/java/com/android/tools/r8/ir/synthetic/EmulateDispatchSyntheticCfCodeProvider.java
index 4847e78..e356796 100644
--- a/src/main/java/com/android/tools/r8/ir/synthetic/EmulateDispatchSyntheticCfCodeProvider.java
+++ b/src/main/java/com/android/tools/r8/ir/synthetic/EmulateDispatchSyntheticCfCodeProvider.java
@@ -21,29 +21,30 @@
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.ir.code.If;
 import com.android.tools.r8.ir.code.ValueType;
-import com.android.tools.r8.utils.Pair;
 import com.android.tools.r8.utils.collections.ImmutableDeque;
 import com.android.tools.r8.utils.collections.ImmutableInt2ReferenceSortedMap;
 import java.util.ArrayList;
+import java.util.LinkedHashMap;
 import java.util.List;
+import java.util.Map;
 import org.objectweb.asm.Opcodes;
 
 public class EmulateDispatchSyntheticCfCodeProvider extends SyntheticCfCodeProvider {
 
-  private final DexType receiverType;
   private final DexMethod forwardingMethod;
+  private final DexType receiverType;
   private final DexMethod interfaceMethod;
-  private final List<Pair<DexType, DexMethod>> extraDispatchCases;
+  private final LinkedHashMap<DexType, DexMethod> extraDispatchCases;
 
   public EmulateDispatchSyntheticCfCodeProvider(
       DexType holder,
       DexMethod forwardingMethod,
       DexMethod interfaceMethod,
-      List<Pair<DexType, DexMethod>> extraDispatchCases,
+      LinkedHashMap<DexType, DexMethod> extraDispatchCases,
       AppView<?> appView) {
     super(appView, holder);
-    this.receiverType = forwardingMethod.getParameter(0);
     this.forwardingMethod = forwardingMethod;
+    this.receiverType = forwardingMethod.getParameter(0);
     this.interfaceMethod = interfaceMethod;
     this.extraDispatchCases = extraDispatchCases;
   }
@@ -78,19 +79,19 @@
     addReturn(instructions);
 
     // SubInterface dispatch (subInterfaces are ordered).
-    for (Pair<DexType, DexMethod> dispatch : extraDispatchCases) {
+    for (Map.Entry<DexType, DexMethod> dispatch : extraDispatchCases.entrySet()) {
       // Type check basic block.
       instructions.add(labels[nextLabel++]);
       instructions.add(new CfFrame(locals, ImmutableDeque.of()));
       instructions.add(new CfLoad(ValueType.fromDexType(receiverType), 0));
-      instructions.add(new CfInstanceOf(dispatch.getFirst()));
+      instructions.add(new CfInstanceOf(dispatch.getKey()));
       instructions.add(new CfIf(If.Type.EQ, ValueType.INT, labels[nextLabel]));
 
       // Call basic block.
       instructions.add(new CfLoad(ValueType.fromDexType(receiverType), 0));
-      instructions.add(new CfCheckCast(dispatch.getFirst()));
+      instructions.add(new CfCheckCast(dispatch.getKey()));
       loadExtraParameters(instructions);
-      instructions.add(new CfInvoke(Opcodes.INVOKESTATIC, dispatch.getSecond(), false));
+      instructions.add(new CfInvoke(Opcodes.INVOKESTATIC, dispatch.getValue(), false));
       addReturn(instructions);
     }
 
diff --git a/src/main/java/com/android/tools/r8/naming/IdentifierNameStringMarker.java b/src/main/java/com/android/tools/r8/naming/IdentifierNameStringMarker.java
index ea756b5..dce1aee 100644
--- a/src/main/java/com/android/tools/r8/naming/IdentifierNameStringMarker.java
+++ b/src/main/java/com/android/tools/r8/naming/IdentifierNameStringMarker.java
@@ -143,6 +143,9 @@
       return iterator;
     }
     Value in = instruction.value();
+    if (in.isDexItemBasedConstString()) {
+      return iterator;
+    }
     if (!in.isConstString()) {
       warnUndeterminedIdentifierIfNecessary(field, code.context(), instruction, null);
       return iterator;
diff --git a/src/main/java/com/android/tools/r8/naming/MethodNameMinifier.java b/src/main/java/com/android/tools/r8/naming/MethodNameMinifier.java
index cd6c271..8a8d804 100644
--- a/src/main/java/com/android/tools/r8/naming/MethodNameMinifier.java
+++ b/src/main/java/com/android/tools/r8/naming/MethodNameMinifier.java
@@ -11,7 +11,7 @@
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.MethodAccessInfoCollection;
 import com.android.tools.r8.graph.MethodResolutionResult;
-import com.android.tools.r8.graph.SubtypingInfo;
+import com.android.tools.r8.graph.TopDownClassHierarchyTraversal;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.ThreadUtils;
@@ -123,7 +123,6 @@
   }
 
   private final AppView<AppInfoWithLiveness> appView;
-  private final SubtypingInfo subtypingInfo;
   private final MemberNamingStrategy strategy;
 
   private final Map<DexMethod, DexString> renaming = new IdentityHashMap<>();
@@ -141,12 +140,11 @@
 
   MethodNameMinifier(
       AppView<AppInfoWithLiveness> appView,
-      SubtypingInfo subtypingInfo,
       MemberNamingStrategy strategy) {
     this.appView = appView;
-    this.subtypingInfo = subtypingInfo;
     this.strategy = strategy;
     rootReservationState = MethodReservationState.createRoot(getReservationKeyTransform());
+    reservationStates.put(null, rootReservationState);
     rootNamingState =
         MethodNamingState.createRoot(getNamingKeyTransform(), strategy, rootReservationState);
     namingStates.put(null, rootNamingState);
@@ -202,7 +200,8 @@
     timing.end();
     // Phase 4: Assign names top-down by traversing the subtype hierarchy.
     timing.begin("Phase 4");
-    assignNamesToClassesMethods(appView.dexItemFactory().objectType, rootNamingState);
+    assignNamesToClassesMethods();
+    renameMethodsInUnrelatedClasspathClasses();
     timing.end();
     timing.begin("Phase 5: non-rebound references");
     renameNonReboundReferences(executorService);
@@ -211,21 +210,46 @@
     return new MethodRenaming(renaming);
   }
 
-  private void assignNamesToClassesMethods(DexType type, MethodNamingState<?> parentNamingState) {
-    MethodReservationState<?> reservationState =
-        reservationStates.get(frontiers.getOrDefault(type, type));
-    assert reservationState != null : "Could not find reservation state for " + type.toString();
-    MethodNamingState<?> namingState =
-        namingStates.computeIfAbsent(
-            type, ignore -> parentNamingState.createChild(reservationState));
-    DexClass holder = appView.definitionFor(type);
-    if (holder != null && strategy.allowMemberRenaming(holder)) {
-      for (DexEncodedMethod method : holder.allMethodsSorted()) {
-        assignNameToMethod(holder, method, namingState);
-      }
-    }
-    for (DexType subType : subtypingInfo.allImmediateExtendsSubtypes(type)) {
-      assignNamesToClassesMethods(subType, namingState);
+  private void assignNamesToClassesMethods() {
+    TopDownClassHierarchyTraversal.forAllClasses(appView)
+        .excludeInterfaces()
+        .visit(
+            appView.appInfo().classes(),
+            clazz -> {
+              DexType type = clazz.type;
+              MethodReservationState<?> reservationState =
+                  reservationStates.get(frontiers.getOrDefault(type, type));
+              assert reservationState != null
+                  : "Could not find reservation state for " + type.toString();
+              MethodNamingState<?> namingState =
+                  namingStates.computeIfAbsent(
+                      type,
+                      ignore ->
+                          namingStates
+                              .getOrDefault(clazz.superType, rootNamingState)
+                              .createChild(reservationState));
+              DexClass holder = appView.definitionFor(type);
+              if (holder != null && strategy.allowMemberRenaming(holder)) {
+                for (DexEncodedMethod method : holder.allMethodsSorted()) {
+                  assignNameToMethod(holder, method, namingState);
+                }
+              }
+            });
+  }
+
+  private void renameMethodsInUnrelatedClasspathClasses() {
+    if (appView.options().getProguardConfiguration().hasApplyMappingFile()) {
+      appView
+          .appInfo()
+          .forEachReferencedClasspathClass(
+              clazz -> {
+                for (DexEncodedMethod method : clazz.methods()) {
+                  DexString reservedName = strategy.getReservedName(method, clazz);
+                  if (reservedName != null && reservedName != method.getReference().name) {
+                    renaming.put(method.getReference(), reservedName);
+                  }
+                }
+              });
     }
   }
 
@@ -248,42 +272,30 @@
   }
 
   private void reserveNamesInClasses() {
-    reserveNamesInClasses(
-        appView.dexItemFactory().objectType,
-        appView.dexItemFactory().objectType,
-        rootReservationState);
+    TopDownClassHierarchyTraversal.forAllClasses(appView)
+        .visit(
+            appView.appInfo().classes(),
+            clazz -> {
+              DexType type = clazz.type;
+              DexType frontier = frontiers.getOrDefault(clazz.superType, type);
+              if (frontier != type || clazz.isProgramClass()) {
+                DexType existingValue = frontiers.put(clazz.type, frontier);
+                assert existingValue == null;
+              }
+              // If this is not a program class (or effectively a library class as it is missing)
+              // move the frontier forward. This will ensure all reservations are put on the library
+              // or classpath frontier for the program path.
+              allocateReservationStateAndReserve(
+                  type,
+                  frontier,
+                  reservationStates.getOrDefault(clazz.superType, rootReservationState));
+            });
   }
 
-  private void reserveNamesInClasses(
-      DexType type, DexType libraryFrontier, MethodReservationState<?> parentReservationState) {
-    assert !appView.isInterface(type).isTrue();
-
-    MethodReservationState<?> reservationState =
-        allocateReservationStateAndReserve(type, libraryFrontier, parentReservationState);
-
-    // If this is not a program class (or effectively a library class as it is missing) move the
-    // frontier forward. This will ensure all reservations are put on the library or classpath
-    // frontier for the program path.
-    DexClass holder = appView.definitionFor(type);
-    for (DexType subtype : subtypingInfo.allImmediateExtendsSubtypes(type)) {
-      reserveNamesInClasses(
-          subtype,
-          holder == null || holder.isNotProgramClass() ? subtype : libraryFrontier,
-          reservationState);
-    }
-  }
-
-  private MethodReservationState<?> allocateReservationStateAndReserve(
+  private void allocateReservationStateAndReserve(
       DexType type, DexType frontier, MethodReservationState<?> parent) {
-    assert parent != null;
-
-    if (frontier != type) {
-      frontiers.put(type, frontier);
-    }
-
     MethodReservationState<?> state =
         reservationStates.computeIfAbsent(frontier, ignore -> parent.createChild());
-
     DexClass holder = appView.definitionFor(type);
     if (holder != null) {
       for (DexEncodedMethod method : shuffleMethods(holder.methods(), appView.options())) {
@@ -293,8 +305,6 @@
         }
       }
     }
-
-    return state;
   }
 
   private MethodNamingState<?> getOrAllocateMethodNamingStates(DexType type) {
diff --git a/src/main/java/com/android/tools/r8/naming/Minifier.java b/src/main/java/com/android/tools/r8/naming/Minifier.java
index e7106a6..738be08 100644
--- a/src/main/java/com/android/tools/r8/naming/Minifier.java
+++ b/src/main/java/com/android/tools/r8/naming/Minifier.java
@@ -66,7 +66,7 @@
     MemberNamingStrategy minifyMembers = new MinifierMemberNamingStrategy(appView);
     timing.begin("MinifyMethods");
     MethodRenaming methodRenaming =
-        new MethodNameMinifier(appView, subtypingInfo, minifyMembers)
+        new MethodNameMinifier(appView, minifyMembers)
             .computeRenaming(interfaces, executorService, timing);
     timing.end();
 
diff --git a/src/main/java/com/android/tools/r8/naming/ProguardMapMinifier.java b/src/main/java/com/android/tools/r8/naming/ProguardMapMinifier.java
index 5c07e4a..7ee0681 100644
--- a/src/main/java/com/android/tools/r8/naming/ProguardMapMinifier.java
+++ b/src/main/java/com/android/tools/r8/naming/ProguardMapMinifier.java
@@ -147,7 +147,7 @@
         new ApplyMappingMemberNamingStrategy(appView, memberNames);
     timing.begin("MinifyMethods");
     MethodRenaming methodRenaming =
-        new MethodNameMinifier(appView, subtypingInfo, nameStrategy)
+        new MethodNameMinifier(appView, nameStrategy)
             .computeRenaming(interfaces, executorService, timing);
     // Amend the method renamings with the default interface methods.
     methodRenaming.renaming.putAll(defaultInterfaceMethodImplementationNames);
diff --git a/src/main/java/com/android/tools/r8/optimize/MemberRebindingAnalysis.java b/src/main/java/com/android/tools/r8/optimize/MemberRebindingAnalysis.java
index de68c75..32bc033 100644
--- a/src/main/java/com/android/tools/r8/optimize/MemberRebindingAnalysis.java
+++ b/src/main/java/com/android/tools/r8/optimize/MemberRebindingAnalysis.java
@@ -127,6 +127,7 @@
     return resolvedMethod.isLibraryMethod()
         && isAccessibleInAllContexts(resolvedMethod, resolutionResult, contexts)
         && !isInvokeSuperToInterfaceMethod(resolvedMethod, invokeType)
+        && !isInvokeSuperToAbstractMethod(resolvedMethod, invokeType)
         && isApiSafeForMemberRebinding(
             resolvedMethod.asLibraryMethod(), androidApiLevelCompute, options);
   }
@@ -147,6 +148,10 @@
     return method.getHolder().isInterface() && invokeType.isSuper();
   }
 
+  private boolean isInvokeSuperToAbstractMethod(DexClassAndMethod method, Type invokeType) {
+    return method.getAccessFlags().isAbstract() && invokeType.isSuper();
+  }
+
   public static DexField validMemberRebindingTargetFor(
       DexDefinitionSupplier definitions, DexClassAndField field, DexField original) {
     if (field.isProgramField()) {
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorApplicationFixer.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorApplicationFixer.java
index 7c196b1..d8757d1 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorApplicationFixer.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorApplicationFixer.java
@@ -86,7 +86,8 @@
           DexMethod methodReferenceBeforeParameterRemoval = method.getReference();
           DexMethod methodReferenceAfterParameterRemoval =
               graphLens.internalGetNextMethodSignature(methodReferenceBeforeParameterRemoval);
-          if (methodReferenceAfterParameterRemoval == methodReferenceBeforeParameterRemoval) {
+          if (methodReferenceAfterParameterRemoval == methodReferenceBeforeParameterRemoval
+              && !graphLens.hasPrototypeChanges(methodReferenceAfterParameterRemoval)) {
             return method;
           }
 
@@ -97,6 +98,13 @@
                   RewrittenPrototypeDescription prototypeChanges =
                       graphLens.getPrototypeChanges(methodReferenceAfterParameterRemoval);
                   builder.apply(prototypeChanges.createParameterAnnotationsRemover(method));
+
+                  if (method.isInstance()
+                      && prototypeChanges.getArgumentInfoCollection().isArgumentRemoved(0)) {
+                    builder
+                        .modifyAccessFlags(flags -> flags.demoteFromFinal().promoteToStatic())
+                        .unsetIsLibraryMethodOverride();
+                  }
                 }
               });
         });
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorGraphLens.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorGraphLens.java
index 17865e4..0955d0a 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorGraphLens.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorGraphLens.java
@@ -9,6 +9,7 @@
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.NestedGraphLens;
 import com.android.tools.r8.graph.RewrittenPrototypeDescription;
+import com.android.tools.r8.ir.code.Invoke.Type;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.utils.collections.BidirectionalOneToOneHashMap;
 import com.android.tools.r8.utils.collections.BidirectionalOneToOneMap;
@@ -48,6 +49,11 @@
   }
 
   @Override
+  protected boolean isLegitimateToHaveEmptyMappings() {
+    return true;
+  }
+
+  @Override
   protected FieldLookupResult internalDescribeLookupField(FieldLookupResult previous) {
     FieldLookupResult lookupResult = super.internalDescribeLookupField(previous);
     if (lookupResult.getReference().getType() != previous.getReference().getType()) {
@@ -91,6 +97,17 @@
     return super.internalGetNextMethodSignature(method);
   }
 
+  @Override
+  protected Type mapInvocationType(DexMethod newMethod, DexMethod originalMethod, Type type) {
+    if (!type.isStatic() && hasPrototypeChanges(newMethod)) {
+      RewrittenPrototypeDescription prototypeChanges = getPrototypeChanges(newMethod);
+      if (prototypeChanges.getArgumentInfoCollection().isArgumentRemoved(0)) {
+        return Type.STATIC;
+      }
+    }
+    return super.mapInvocationType(newMethod, originalMethod, type);
+  }
+
   public static class Builder {
 
     private final AppView<AppInfoWithLiveness> appView;
@@ -106,7 +123,9 @@
     }
 
     public boolean isEmpty() {
-      return newFieldSignatures.isEmpty() && newMethodSignatures.isEmpty();
+      return newFieldSignatures.isEmpty()
+          && newMethodSignatures.isEmpty()
+          && prototypeChanges.isEmpty();
     }
 
     public ArgumentPropagatorGraphLens.Builder mergeDisjoint(
@@ -136,6 +155,12 @@
       return this;
     }
 
+    public Builder recordStaticized(
+        DexMethod method, RewrittenPrototypeDescription prototypeChangesForMethod) {
+      prototypeChanges.put(method, prototypeChangesForMethod);
+      return this;
+    }
+
     public ArgumentPropagatorGraphLens build() {
       if (isEmpty()) {
         return null;
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorMethodReprocessingEnqueuer.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorMethodReprocessingEnqueuer.java
index d79d23e..6479ad3 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorMethodReprocessingEnqueuer.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorMethodReprocessingEnqueuer.java
@@ -201,7 +201,8 @@
       ProgramMethod resolvedMethod = resolutionResult.getResolvedProgramMethod();
       DexMethod rewrittenMethodReference =
           graphLens.internalGetNextMethodSignature(resolvedMethod.getReference());
-      if (rewrittenMethodReference != resolvedMethod.getReference()) {
+      if (rewrittenMethodReference != resolvedMethod.getReference()
+          || graphLens.hasPrototypeChanges(rewrittenMethodReference)) {
         markAffected();
       }
     }
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorProgramOptimizer.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorProgramOptimizer.java
index 4890dd9..0e68d70 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorProgramOptimizer.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorProgramOptimizer.java
@@ -32,6 +32,7 @@
 import com.android.tools.r8.ir.optimize.info.CallSiteOptimizationInfo;
 import com.android.tools.r8.ir.optimize.info.ConcreteCallSiteOptimizationInfo;
 import com.android.tools.r8.optimize.argumentpropagation.ArgumentPropagatorGraphLens.Builder;
+import com.android.tools.r8.optimize.argumentpropagation.utils.ParameterRemovalUtils;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.shaking.KeepFieldInfo;
 import com.android.tools.r8.utils.AccessUtils;
@@ -345,7 +346,7 @@
             // all possible to strengthen to the same stronger type, in all methods.
             Int2ReferenceMap<DexType> newParameterTypes = new Int2ReferenceOpenHashMap<>();
             IntSet removableVirtualMethodParametersInAllMethods = new IntArraySet();
-            for (int parameterIndex = 1;
+            for (int parameterIndex = 0;
                 parameterIndex < signature.getProto().getArity() + 1;
                 parameterIndex++) {
               if (canRemoveParameterFromVirtualMethods(methods, parameterIndex)) {
@@ -455,6 +456,17 @@
 
     private boolean canRemoveParameterFromVirtualMethods(
         ProgramMethodSet methods, int parameterIndex) {
+      if (parameterIndex == 0) {
+        if (methods.size() > 1) {
+          // Method staticizing would break dynamic dispatch.
+          return false;
+        }
+        ProgramMethod method = methods.getFirst();
+        return method.getOptimizationInfo().hasUnusedArguments()
+            && method.getOptimizationInfo().getUnusedArguments().get(0)
+            && ParameterRemovalUtils.canRemoveUnusedParametersFrom(appView, method)
+            && ParameterRemovalUtils.canRemoveUnusedParameter(appView, method, parameterIndex);
+      }
       for (ProgramMethod method : methods) {
         if (method.getDefinition().isAbstract()) {
           // OK, this parameter can be removed.
@@ -501,6 +513,9 @@
 
     private DexType getNewParameterTypeForVirtualMethods(
         ProgramMethodSet methods, int parameterIndex) {
+      if (parameterIndex == 0) {
+        return null;
+      }
       DexType newParameterType = null;
       for (ProgramMethod method : methods) {
         if (method.getAccessFlags().isAbstract()) {
@@ -548,6 +563,9 @@
               partialGraphLensBuilder.recordMove(
                   method.getReference(), newMethodSignature, prototypeChanges);
               affected.set();
+            } else if (!prototypeChanges.isEmpty()) {
+              partialGraphLensBuilder.recordStaticized(method.getReference(), prototypeChanges);
+              affected.set();
             }
           });
       return affected.get();
@@ -907,42 +925,50 @@
         ProgramMethod method,
         IntFunction<DexType> newParameterTypes,
         IntPredicate removableParameterIndices) {
-      ConcreteCallSiteOptimizationInfo optimizationInfo =
-          method.getOptimizationInfo().getArgumentInfos().asConcreteCallSiteOptimizationInfo();
-      if (optimizationInfo == null) {
-        return ArgumentInfoCollection.empty();
+      ArgumentInfoCollection.Builder parameterChangesBuilder = ArgumentInfoCollection.builder();
+      if (method.getDefinition().isInstance()
+          && removableParameterIndices.test(0)
+          && method.getOptimizationInfo().hasUnusedArguments()
+          && method.getOptimizationInfo().getUnusedArguments().get(0)
+          && ParameterRemovalUtils.canRemoveUnusedParametersFrom(appView, method)
+          && ParameterRemovalUtils.canRemoveUnusedParameter(appView, method, 0)) {
+        parameterChangesBuilder.addArgumentInfo(
+            0, RemovedArgumentInfo.builder().setType(method.getHolderType()).build());
       }
 
-      ArgumentInfoCollection.Builder parameterChangesBuilder = ArgumentInfoCollection.builder();
-      for (int argumentIndex = method.getDefinition().getFirstNonReceiverArgumentIndex();
-          argumentIndex < method.getDefinition().getNumberOfArguments();
-          argumentIndex++) {
-        if (removableParameterIndices.test(argumentIndex)) {
-          AbstractValue abstractValue = optimizationInfo.getAbstractArgumentValue(argumentIndex);
-          if (abstractValue.isSingleValue()
-              && abstractValue.asSingleValue().isMaterializableInContext(appView, method)) {
+      ConcreteCallSiteOptimizationInfo optimizationInfo =
+          method.getOptimizationInfo().getArgumentInfos().asConcreteCallSiteOptimizationInfo();
+      if (optimizationInfo != null) {
+        for (int argumentIndex = method.getDefinition().getFirstNonReceiverArgumentIndex();
+            argumentIndex < method.getDefinition().getNumberOfArguments();
+            argumentIndex++) {
+          if (removableParameterIndices.test(argumentIndex)) {
+            AbstractValue abstractValue = optimizationInfo.getAbstractArgumentValue(argumentIndex);
+            if (abstractValue.isSingleValue()
+                && abstractValue.asSingleValue().isMaterializableInContext(appView, method)) {
+              parameterChangesBuilder.addArgumentInfo(
+                  argumentIndex,
+                  RemovedArgumentInfo.builder()
+                      .setSingleValue(abstractValue.asSingleValue())
+                      .setType(method.getArgumentType(argumentIndex))
+                      .build());
+              continue;
+            }
+          }
+
+          DexType dynamicType = newParameterTypes.apply(argumentIndex);
+          if (dynamicType != null) {
+            DexType staticType = method.getArgumentType(argumentIndex);
+            assert dynamicType != staticType;
             parameterChangesBuilder.addArgumentInfo(
                 argumentIndex,
-                RemovedArgumentInfo.builder()
-                    .setSingleValue(abstractValue.asSingleValue())
-                    .setType(method.getArgumentType(argumentIndex))
+                RewrittenTypeInfo.builder()
+                    .setCastType(dynamicType)
+                    .setOldType(staticType)
+                    .setNewType(dynamicType)
                     .build());
-            continue;
           }
         }
-
-        DexType dynamicType = newParameterTypes.apply(argumentIndex);
-        if (dynamicType != null) {
-          DexType staticType = method.getArgumentType(argumentIndex);
-          assert dynamicType != staticType;
-          parameterChangesBuilder.addArgumentInfo(
-              argumentIndex,
-              RewrittenTypeInfo.builder()
-                  .setCastType(dynamicType)
-                  .setOldType(staticType)
-                  .setNewType(dynamicType)
-                  .build());
-        }
       }
       return parameterChangesBuilder.build();
     }
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/utils/ParameterRemovalUtils.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/utils/ParameterRemovalUtils.java
new file mode 100644
index 0000000..0d16b37
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/utils/ParameterRemovalUtils.java
@@ -0,0 +1,45 @@
+// Copyright (c) 2022, 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.optimize.argumentpropagation.utils;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.shaking.KeepMethodInfo;
+import com.android.tools.r8.utils.InternalOptions;
+
+public class ParameterRemovalUtils {
+
+  public static boolean canRemoveUnusedParametersFrom(
+      AppView<AppInfoWithLiveness> appView, ProgramMethod method) {
+    KeepMethodInfo keepInfo = appView.getKeepInfo(method);
+    InternalOptions options = appView.options();
+    if (!keepInfo.isParameterRemovalAllowed(options)) {
+      return false;
+    }
+    return !appView.appInfoWithLiveness().isMethodTargetedByInvokeDynamic(method);
+  }
+
+  public static boolean canRemoveUnusedParameter(
+      AppView<AppInfoWithLiveness> appView, ProgramMethod method, int argumentIndex) {
+    assert canRemoveUnusedParametersFrom(appView, method);
+    if (argumentIndex == 0) {
+      if (method.getDefinition().isInstanceInitializer()) {
+        return false;
+      }
+      if (method.getDefinition().isInstance()) {
+        if (method.getAccessFlags().isSynchronized()) {
+          return false;
+        }
+        KeepMethodInfo keepInfo = appView.getKeepInfo(method);
+        InternalOptions options = appView.options();
+        if (!keepInfo.isMethodStaticizingAllowed(options)) {
+          return false;
+        }
+      }
+    }
+    return true;
+  }
+}
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 2a5405b..68dba3e 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -53,6 +53,7 @@
 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.graph.DexValue;
 import com.android.tools.r8.graph.DirectMappedDexApplication;
 import com.android.tools.r8.graph.DirectMappedDexApplication.Builder;
 import com.android.tools.r8.graph.EnclosingMethodAttribute;
@@ -1009,6 +1010,14 @@
 
     LambdaDescriptor descriptor = LambdaDescriptor.tryInfer(callSite, appInfo(), context);
     if (descriptor == null) {
+      for (DexValue bootstrapArgument : callSite.getBootstrapArgs()) {
+        if (bootstrapArgument.isDexValueMethodHandle()) {
+          DexMethodHandle method = bootstrapArgument.asDexValueMethodHandle().getValue();
+          if (method.isMethodHandle()) {
+            methodsTargetedByInvokeDynamic.add(method.asMethod());
+          }
+        }
+      }
       return;
     }
 
diff --git a/src/main/java/com/android/tools/r8/shaking/KeepInfoCollection.java b/src/main/java/com/android/tools/r8/shaking/KeepInfoCollection.java
index 570ed16..62529ed 100644
--- a/src/main/java/com/android/tools/r8/shaking/KeepInfoCollection.java
+++ b/src/main/java/com/android/tools/r8/shaking/KeepInfoCollection.java
@@ -249,6 +249,12 @@
       if (prunedItems.hasRemovedClasses()) {
         keepClassInfo.keySet().removeAll(prunedItems.getRemovedClasses());
       }
+      if (prunedItems.hasRemovedFields()) {
+        keepFieldInfo.keySet().removeAll(prunedItems.getRemovedFields());
+      }
+      if (prunedItems.hasRemovedMembers()) {
+        keepMethodInfo.keySet().removeAll(prunedItems.getRemovedMethods());
+      }
     }
 
     public void removeKeepInfoForPrunedItems(PrunedItems prunedItems) {
diff --git a/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java b/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
index c882cf4..a7362e5 100644
--- a/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
+++ b/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
@@ -1335,7 +1335,8 @@
       }
       List<FormalTypeParameter> formals = source.getClassSignature().getFormalTypeParameters();
       if (genericArgumentsToSuperType.size() != formals.size()) {
-        assert false : "Invalid argument count to formals";
+        // TODO(b/214509535): Correctly rewrite signature when type arguments is empty.
+        assert genericArgumentsToSuperType.isEmpty() : "Invalid argument count to formals";
         return null;
       }
       Map<String, FieldTypeSignature> substitutionMap = new HashMap<>();
diff --git a/src/main/java/com/android/tools/r8/utils/ArrayUtils.java b/src/main/java/com/android/tools/r8/utils/ArrayUtils.java
index 06fdc4c..13f630e 100644
--- a/src/main/java/com/android/tools/r8/utils/ArrayUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/ArrayUtils.java
@@ -47,39 +47,6 @@
     return results;
   }
 
-  /**
-   * Filters the input array based on the given predicate.
-   *
-   * @param clazz target type's Class to cast
-   * @param original an array of original elements
-   * @param filter a predicate that tells us what to keep
-   * @param <T> target type
-   * @return a partial copy of the original array
-   */
-  public static <T> T[] filter(Class<T[]> clazz, T[] original, Predicate<T> filter) {
-    ArrayList<T> filtered = null;
-    for (int i = 0; i < original.length; i++) {
-      T elt = original[i];
-      if (filter.test(elt)) {
-        if (filtered != null) {
-          filtered.add(elt);
-        }
-      } else {
-        if (filtered == null) {
-          filtered = new ArrayList<>(original.length);
-          for (int j = 0; j < i; j++) {
-            filtered.add(original[j]);
-          }
-        }
-      }
-    }
-    if (filtered == null) {
-      return original;
-    }
-    return filtered.toArray(
-        clazz.cast(Array.newInstance(clazz.getComponentType(), filtered.size())));
-  }
-
   public static <T> boolean isEmpty(T[] array) {
     return array.length == 0;
   }
@@ -131,8 +98,23 @@
     return results != null ? results.toArray(emptyArray) : original;
   }
 
-  public static <T> T[] filter(T[] original, Predicate<T> test, T[] emptyArray) {
-    return map(original, e -> test.test(e) ? e : null, emptyArray);
+  public static <T> T[] filter(T[] original, Predicate<T> predicate, T[] emptyArray) {
+    return map(original, e -> predicate.test(e) ? e : null, emptyArray);
+  }
+
+  @SuppressWarnings("unchecked")
+  public static <T> T[] filter(T[] original, Predicate<T> predicate, T[] emptyArray, int newSize) {
+    T[] result = (T[]) Array.newInstance(emptyArray.getClass().getComponentType(), newSize);
+    int newIndex = 0;
+    for (int originalIndex = 0; originalIndex < original.length; originalIndex++) {
+      T element = original[originalIndex];
+      if (predicate.test(element)) {
+        result[newIndex] = element;
+        newIndex++;
+      }
+    }
+    assert newIndex == newSize;
+    return result;
   }
 
   public static int[] createIdentityArray(int size) {
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 f3dcf92..c1dc2c9 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -54,6 +54,7 @@
 import com.android.tools.r8.inspector.internal.InspectorImpl;
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.desugar.desugaredlibrary.legacyspecification.LegacyDesugaredLibrarySpecification;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.machinespecification.MachineDesugaredLibrarySpecification;
 import com.android.tools.r8.ir.desugar.nest.Nest;
 import com.android.tools.r8.ir.optimize.Inliner;
 import com.android.tools.r8.ir.optimize.enums.EnumDataMap;
@@ -74,7 +75,6 @@
 import com.android.tools.r8.shaking.ProguardConfigurationRule;
 import com.android.tools.r8.utils.IROrdering.IdentityIROrdering;
 import com.android.tools.r8.utils.IROrdering.NondeterministicIROrdering;
-import com.android.tools.r8.utils.collections.DexClassAndMethodSet;
 import com.android.tools.r8.utils.collections.ProgramMethodSet;
 import com.android.tools.r8.utils.structural.Ordered;
 import com.google.common.annotations.VisibleForTesting;
@@ -1037,7 +1037,7 @@
       DexType libraryType,
       DexType invalidSuperType,
       String message,
-      DexClassAndMethodSet retarget) {
+      Set<DexMethod> retarget) {
     if (invalidLibraryClasses.add(invalidSuperType)) {
       reporter.warning(
           new InvalidLibrarySuperclassDiagnostic(
@@ -1046,8 +1046,7 @@
               Reference.classFromDescriptor(invalidSuperType.toDescriptorString()),
               message,
               Lists.newArrayList(
-                  Iterables.transform(
-                      retarget, method -> method.getReference().asMethodReference()))));
+                  Iterables.transform(retarget, method -> method.asMethodReference()))));
     }
   }
 
@@ -1615,6 +1614,9 @@
 
     public Consumer<Deque<ProgramMethodSet>> waveModifier = waves -> {};
 
+    // Meant to replace desugaredLibrarySpecification, set only from tests at the moment.
+    public MachineDesugaredLibrarySpecification machineDesugaredLibrarySpecification = null;
+
     /**
      * If this flag is enabled, we will also compute the set of possible targets for invoke-
      * interface and invoke-virtual instructions that target a library method, and add the
diff --git a/src/test/examples/inlining/NoHorizontalClassMerging.java b/src/test/examples/inlining/NoHorizontalClassMerging.java
new file mode 100644
index 0000000..fcd446d
--- /dev/null
+++ b/src/test/examples/inlining/NoHorizontalClassMerging.java
@@ -0,0 +1,10 @@
+// Copyright (c) 2022, 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 inlining;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Target;
+
+@Target({ElementType.TYPE})
+public @interface NoHorizontalClassMerging {}
diff --git a/src/test/examples/inlining/NoMethodStaticizing.java b/src/test/examples/inlining/NoMethodStaticizing.java
new file mode 100644
index 0000000..1ffa6f0
--- /dev/null
+++ b/src/test/examples/inlining/NoMethodStaticizing.java
@@ -0,0 +1,10 @@
+// Copyright (c) 2022, 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 inlining;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Target;
+
+@Target({ElementType.METHOD})
+public @interface NoMethodStaticizing {}
diff --git a/src/test/examples/inlining/Nullability.java b/src/test/examples/inlining/Nullability.java
index 4c8dcb0..472e06a 100644
--- a/src/test/examples/inlining/Nullability.java
+++ b/src/test/examples/inlining/Nullability.java
@@ -3,6 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 package inlining;
 
+@NoHorizontalClassMerging
 class Nullability {
   private final int f;
   public final int publicField;
@@ -48,6 +49,7 @@
     return a != null ? a.b() : -1;
   }
 
+  @NoMethodStaticizing
   int notInlinableOnThrow(Throwable t) throws Throwable {
     // NPE is not preserved if t is not a NullPointerException.
     throw t;
@@ -61,6 +63,7 @@
     }
   }
 
+  @NoMethodStaticizing
   public int notInlinableDueToMissingNpeBeforeThrow(Throwable t) throws Throwable {
     try {
       throw t;
diff --git a/src/test/examples/inlining/keep-rules.txt b/src/test/examples/inlining/keep-rules.txt
index 2c0f288..bf9bc45 100644
--- a/src/test/examples/inlining/keep-rules.txt
+++ b/src/test/examples/inlining/keep-rules.txt
@@ -17,3 +17,7 @@
 -keepconstantarguments class * {
   @inlining.KeepConstantArguments <methods>;
 }
+-nohorizontalclassmerging @inlining.NoHorizontalClassMerging class *
+-nomethodstaticizing class * {
+  @inlining.NoMethodStaticizing <methods>;
+}
diff --git a/src/test/java/com/android/tools/r8/apimodel/ApiModelNoInliningOfDefaultInterfaceMethodsTest.java b/src/test/java/com/android/tools/r8/apimodel/ApiModelNoInliningOfDefaultInterfaceMethodsTest.java
index 9f88bbb..420bf8d 100644
--- a/src/test/java/com/android/tools/r8/apimodel/ApiModelNoInliningOfDefaultInterfaceMethodsTest.java
+++ b/src/test/java/com/android/tools/r8/apimodel/ApiModelNoInliningOfDefaultInterfaceMethodsTest.java
@@ -11,6 +11,7 @@
 import static junit.framework.TestCase.assertEquals;
 import static org.hamcrest.MatcherAssert.assertThat;
 
+import com.android.tools.r8.NoMethodStaticizing;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
@@ -50,6 +51,7 @@
         .apply(ApiModelingTestHelper::enableApiCallerIdentification)
         // We are testing that we do not inline/merge higher api-levels
         .apply(ApiModelingTestHelper::disableOutliningAndStubbing)
+        .enableNoMethodStaticizingAnnotations()
         .noMinification()
         .compile()
         .inspect(
@@ -94,6 +96,8 @@
   }
 
   public interface ApiCaller {
+
+    @NoMethodStaticizing
     default void callApiLevel() {
       System.out.println("ApiCaller::callApiLevel");
       Api.apiLevel22();
diff --git a/src/test/java/com/android/tools/r8/apimodel/ApiModelNoVerticalMergingSubReferenceApiTest.java b/src/test/java/com/android/tools/r8/apimodel/ApiModelNoVerticalMergingSubReferenceApiTest.java
index da967e8..d105c17 100644
--- a/src/test/java/com/android/tools/r8/apimodel/ApiModelNoVerticalMergingSubReferenceApiTest.java
+++ b/src/test/java/com/android/tools/r8/apimodel/ApiModelNoVerticalMergingSubReferenceApiTest.java
@@ -13,6 +13,7 @@
 
 import com.android.tools.r8.NeverClassInline;
 import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NoMethodStaticizing;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
@@ -54,6 +55,7 @@
         .apply(ApiModelingTestHelper::disableOutliningAndStubbing)
         .enableInliningAnnotations()
         .enableNeverClassInliningAnnotations()
+        .enableNoMethodStaticizingAnnotations()
         .addVerticallyMergedClassesInspector(
             inspector -> {
               if (parameters.isDexRuntime()
@@ -105,6 +107,7 @@
   public static class Sub extends Base {
 
     @NeverInline
+    @NoMethodStaticizing
     public void callCallApi() {
       System.out.println("Sub::callCallApi");
       callApi();
diff --git a/src/test/java/com/android/tools/r8/apimodel/ApiModelNoVerticalMergingTest.java b/src/test/java/com/android/tools/r8/apimodel/ApiModelNoVerticalMergingTest.java
index 7276937..352cd3a 100644
--- a/src/test/java/com/android/tools/r8/apimodel/ApiModelNoVerticalMergingTest.java
+++ b/src/test/java/com/android/tools/r8/apimodel/ApiModelNoVerticalMergingTest.java
@@ -13,6 +13,7 @@
 
 import com.android.tools.r8.NeverClassInline;
 import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NoMethodStaticizing;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
@@ -54,6 +55,7 @@
         .apply(ApiModelingTestHelper::disableOutliningAndStubbing)
         .enableInliningAnnotations()
         .enableNeverClassInliningAnnotations()
+        .enableNoMethodStaticizingAnnotations()
         .addVerticallyMergedClassesInspector(
             inspector -> {
               if (parameters.isDexRuntime()
@@ -105,6 +107,7 @@
   public static class Sub extends Base {
 
     @NeverInline
+    @NoMethodStaticizing
     public void callCallApi() {
       System.out.println("Sub::callCallApi");
       callApi();
diff --git a/src/test/java/com/android/tools/r8/apimodel/ApiModelingTestHelper.java b/src/test/java/com/android/tools/r8/apimodel/ApiModelingTestHelper.java
index b9cf537..581fca7 100644
--- a/src/test/java/com/android/tools/r8/apimodel/ApiModelingTestHelper.java
+++ b/src/test/java/com/android/tools/r8/apimodel/ApiModelingTestHelper.java
@@ -91,7 +91,7 @@
     };
   }
 
-  static <T extends TestCompilerBuilder<?, ?, ?, ?, ?>>
+  public static <T extends TestCompilerBuilder<?, ?, ?, ?, ?>>
       ThrowableConsumer<T> setMockApiLevelForClass(Class<?> clazz, AndroidApiLevel apiLevel) {
     return compilerBuilder -> {
       compilerBuilder.addOptionsModification(
@@ -104,7 +104,8 @@
     };
   }
 
-  static void enableApiCallerIdentification(TestCompilerBuilder<?, ?, ?, ?, ?> compilerBuilder) {
+  public static void enableApiCallerIdentification(
+      TestCompilerBuilder<?, ?, ?, ?, ?> compilerBuilder) {
     compilerBuilder.addOptionsModification(
         options -> {
           options.apiModelingOptions().enableApiCallerIdentification = true;
diff --git a/src/test/java/com/android/tools/r8/bridgeremoval/B77836766.java b/src/test/java/com/android/tools/r8/bridgeremoval/B77836766.java
index 036dd46..fb4f27e 100644
--- a/src/test/java/com/android/tools/r8/bridgeremoval/B77836766.java
+++ b/src/test/java/com/android/tools/r8/bridgeremoval/B77836766.java
@@ -501,6 +501,7 @@
   }
 
   private void configure(InternalOptions options) {
+    options.callSiteOptimizationOptions().setEnableMethodStaticizing(false);
     // Callees are invoked with a simple constant, e.g., "Bar". Propagating it into the callees
     // bothers what the tests want to check, such as exact instructions in the body that include
     // invocation kinds, like virtual call to a bridge.
diff --git a/src/test/java/com/android/tools/r8/bridgeremoval/hoisting/BridgeHoistingAccessibilityTest.java b/src/test/java/com/android/tools/r8/bridgeremoval/hoisting/BridgeHoistingAccessibilityTest.java
index bf166ba..1eb5247 100644
--- a/src/test/java/com/android/tools/r8/bridgeremoval/hoisting/BridgeHoistingAccessibilityTest.java
+++ b/src/test/java/com/android/tools/r8/bridgeremoval/hoisting/BridgeHoistingAccessibilityTest.java
@@ -9,6 +9,7 @@
 import com.android.tools.r8.KeepConstantArguments;
 import com.android.tools.r8.NeverClassInline;
 import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NoMethodStaticizing;
 import com.android.tools.r8.NoVerticalClassMerging;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
@@ -75,6 +76,7 @@
         .enableConstantArgumentAnnotations()
         .enableInliningAnnotations()
         .enableNoHorizontalClassMergingAnnotations()
+        .enableNoMethodStaticizingAnnotations()
         .enableNoVerticalClassMergingAnnotations()
         .enableNeverClassInliningAnnotations()
         // TODO(b/173398086): uniqueMethodWithName() does not work with argument changes.
@@ -125,6 +127,7 @@
     // in TestClass.main().
     @KeepConstantArguments
     @NeverInline
+    @NoMethodStaticizing
     /*bridge*/ String bridgeB(Object o) {
       return (String) m((String) o);
     }
@@ -138,6 +141,7 @@
     // access bridgeC() via class C. From B, the bridge can be hoisted again to A.
     @KeepConstantArguments
     @NeverInline
+    @NoMethodStaticizing
     public /*bridge*/ String bridgeC(Object o) {
       return (String) m((String) o);
     }
@@ -150,6 +154,7 @@
     // in TestClass.main().
     @KeepConstantArguments
     @NeverInline
+    @NoMethodStaticizing
     /*bridge*/ String bridgeB(Object o, int a, int b, int c, int d, int e) {
       return (String) m((String) o, a, b, c, d, e);
     }
@@ -163,6 +168,7 @@
     // access bridgeC() via class C. From B, the bridge can be hoisted again to A.
     @KeepConstantArguments
     @NeverInline
+    @NoMethodStaticizing
     public /*bridge*/ String bridgeC(Object o, int a, int b, int c, int d, int e) {
       return (String) m((String) o, a, b, c, d, e);
     }
diff --git a/src/test/java/com/android/tools/r8/bridgeremoval/hoisting/PositiveBridgeHoistingTest.java b/src/test/java/com/android/tools/r8/bridgeremoval/hoisting/PositiveBridgeHoistingTest.java
index bdd55fc..120d7b4 100644
--- a/src/test/java/com/android/tools/r8/bridgeremoval/hoisting/PositiveBridgeHoistingTest.java
+++ b/src/test/java/com/android/tools/r8/bridgeremoval/hoisting/PositiveBridgeHoistingTest.java
@@ -11,6 +11,7 @@
 import com.android.tools.r8.NeverClassInline;
 import com.android.tools.r8.NeverInline;
 import com.android.tools.r8.NoHorizontalClassMerging;
+import com.android.tools.r8.NoMethodStaticizing;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
@@ -55,6 +56,7 @@
         .enableConstantArgumentAnnotations()
         .enableInliningAnnotations()
         .enableNeverClassInliningAnnotations()
+        .enableNoMethodStaticizingAnnotations()
         .enableNoHorizontalClassMergingAnnotations()
         // TODO(b/173398086): uniqueMethodWithName() does not work with argument changes.
         .noMinification()
@@ -112,11 +114,13 @@
 
     @KeepConstantArguments
     @NeverInline
+    @NoMethodStaticizing
     public Object m(String arg) {
       return System.currentTimeMillis() >= 0 ? arg : null;
     }
 
     @NeverInline
+    @NoMethodStaticizing
     public Object m2(String arg) {
       return System.currentTimeMillis() >= 0 ? arg : null;
     }
@@ -129,6 +133,7 @@
     // invoke-virtual instruction.
     @KeepConstantArguments
     @NeverInline
+    @NoMethodStaticizing
     public /*bridge*/ String superBridge(Object o) {
       return (String) super.m((String) o);
     }
@@ -136,6 +141,7 @@
     // This bridge can be hoisted to A.
     @KeepConstantArguments
     @NeverInline
+    @NoMethodStaticizing
     public /*bridge*/ String virtualBridge(Object o) {
       return (String) m((String) o);
     }
@@ -148,6 +154,7 @@
     // By hoisting B1.superBridge() to A this method bridge redundant.
     @KeepConstantArguments
     @NeverInline
+    @NoMethodStaticizing
     public /*bridge*/ String superBridge(Object o) {
       return (String) super.m((String) o);
     }
@@ -155,6 +162,7 @@
     // By hoisting B1.virtualBridge() to A this method bridge redundant.
     @KeepConstantArguments
     @NeverInline
+    @NoMethodStaticizing
     public /*bridge*/ String virtualBridge(Object o) {
       return (String) m((String) o);
     }
@@ -182,12 +190,14 @@
 
     @KeepConstantArguments
     @NeverInline
+    @NoMethodStaticizing
     public String superBridge(Object o) {
       return System.currentTimeMillis() >= 0 ? ((String) o) : null;
     }
 
     @KeepConstantArguments
     @NeverInline
+    @NoMethodStaticizing
     public String virtualBridge(Object o) {
       return System.currentTimeMillis() >= 0 ? ((String) o) : null;
     }
@@ -201,12 +211,14 @@
 
     @KeepConstantArguments
     @NeverInline
+    @NoMethodStaticizing
     public /*bridge*/ String superBridge(Object o) {
       return (String) super.m2((String) o);
     }
 
     @KeepConstantArguments
     @NeverInline
+    @NoMethodStaticizing
     public /*bridge*/ String virtualBridge(Object o) {
       return (String) m2((String) o);
     }
diff --git a/src/test/java/com/android/tools/r8/bridgeremoval/hoisting/testclasses/BridgeHoistingAccessibilityTestClasses.java b/src/test/java/com/android/tools/r8/bridgeremoval/hoisting/testclasses/BridgeHoistingAccessibilityTestClasses.java
index 36cc8be..c26b1c6 100644
--- a/src/test/java/com/android/tools/r8/bridgeremoval/hoisting/testclasses/BridgeHoistingAccessibilityTestClasses.java
+++ b/src/test/java/com/android/tools/r8/bridgeremoval/hoisting/testclasses/BridgeHoistingAccessibilityTestClasses.java
@@ -7,6 +7,7 @@
 import com.android.tools.r8.KeepConstantArguments;
 import com.android.tools.r8.NeverInline;
 import com.android.tools.r8.NoHorizontalClassMerging;
+import com.android.tools.r8.NoMethodStaticizing;
 import com.android.tools.r8.NoVerticalClassMerging;
 import com.android.tools.r8.bridgeremoval.hoisting.BridgeHoistingAccessibilityTest;
 import com.android.tools.r8.bridgeremoval.hoisting.BridgeHoistingAccessibilityTest.CWithRangedInvoke;
@@ -18,6 +19,7 @@
   public static class A {
 
     @NeverInline
+    @NoMethodStaticizing
     public Object m(String arg) {
       return System.currentTimeMillis() > 0 ? arg : null;
     }
@@ -37,6 +39,7 @@
 
     @KeepConstantArguments
     @NeverInline
+    @NoMethodStaticizing
     public Object m(String arg, int a, int b, int c, int d, int e) {
       return System.currentTimeMillis() > 0 ? arg + a + b + c + d + e : null;
     }
diff --git a/src/test/java/com/android/tools/r8/cf/MissingClassJoinsToObjectTest.java b/src/test/java/com/android/tools/r8/cf/MissingClassJoinsToObjectTest.java
index 43d02cb..792f793 100644
--- a/src/test/java/com/android/tools/r8/cf/MissingClassJoinsToObjectTest.java
+++ b/src/test/java/com/android/tools/r8/cf/MissingClassJoinsToObjectTest.java
@@ -4,6 +4,7 @@
 package com.android.tools.r8.cf;
 
 import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NoMethodStaticizing;
 import com.android.tools.r8.R8TestRunResult;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
@@ -52,6 +53,7 @@
             .addProgramClasses(TestClass.class, A.class)
             .addKeepMainRule(TestClass.class)
             .addDontWarn(B.class)
+            .enableNoMethodStaticizingAnnotations()
             .setMinApi(parameters.getApiLevel())
             .compile()
             .addRunClasspathFiles(getRuntimeClasspath())
@@ -66,6 +68,7 @@
 
   static class A {
     @NeverInline
+    @NoMethodStaticizing
     public void foo() {
       System.out.println("A::foo");
     }
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/EquivalentConstructorsWithPrimitiveAndReferencesParametersTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/EquivalentConstructorsWithPrimitiveAndReferencesParametersTest.java
index 48e6535..d276d0c 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/EquivalentConstructorsWithPrimitiveAndReferencesParametersTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/EquivalentConstructorsWithPrimitiveAndReferencesParametersTest.java
@@ -44,6 +44,8 @@
         .addHorizontallyMergedClassesInspector(
             inspector ->
                 inspector.assertIsCompleteMergeGroup(A.class, B.class).assertNoOtherClassesMerged())
+        .addOptionsModification(
+            options -> options.callSiteOptimizationOptions().setEnableMethodStaticizing(false))
         .enableInliningAnnotations()
         .enableNeverClassInliningAnnotations()
         .enableUnusedArgumentAnnotations()
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/InterfacesVisibilityTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/InterfacesVisibilityTest.java
index 508b10b..ef4dacd 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/InterfacesVisibilityTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/InterfacesVisibilityTest.java
@@ -11,6 +11,7 @@
 
 import com.android.tools.r8.NeverClassInline;
 import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NoMethodStaticizing;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.classmerging.horizontal.testclasses.InterfacesVisibilityTestClasses;
 import com.android.tools.r8.classmerging.horizontal.testclasses.InterfacesVisibilityTestClasses.ImplementingPackagePrivateInterface;
@@ -32,6 +33,7 @@
         .addKeepMainRule(Main.class)
         .enableInliningAnnotations()
         .enableNeverClassInliningAnnotations()
+        .enableNoMethodStaticizingAnnotations()
         .enableNoParameterTypeStrengtheningAnnotations()
         .enableNoVerticalClassMergingAnnotations()
         .enableNoHorizontalClassMergingAnnotations()
@@ -59,6 +61,7 @@
   @NeverClassInline
   public static class A {
     @NeverInline
+    @NoMethodStaticizing
     public void bar() {
       System.out.println("bar");
     }
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/NoAbstractClassesWithNonAbstractClassesTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/NoAbstractClassesWithNonAbstractClassesTest.java
index 4ebbd51..621a805 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/NoAbstractClassesWithNonAbstractClassesTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/NoAbstractClassesWithNonAbstractClassesTest.java
@@ -10,6 +10,7 @@
 
 import com.android.tools.r8.NeverClassInline;
 import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NoMethodStaticizing;
 import com.android.tools.r8.NoVerticalClassMerging;
 import com.android.tools.r8.TestParameters;
 import org.junit.Test;
@@ -26,6 +27,7 @@
         .addKeepMainRule(Main.class)
         .enableInliningAnnotations()
         .enableNeverClassInliningAnnotations()
+        .enableNoMethodStaticizingAnnotations()
         .enableNoVerticalClassMergingAnnotations()
         .setMinApi(parameters.getApiLevel())
         .run(parameters.getRuntime(), Main.class)
@@ -47,6 +49,7 @@
   @NeverClassInline
   public static class B {
     @NeverInline
+    @NoMethodStaticizing
     public void bar() {
       System.out.println("bar");
     }
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/NonPublicOverrideOfPublicMethodAfterAbstractClassMergingTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/NonPublicOverrideOfPublicMethodAfterAbstractClassMergingTest.java
index f540d9d..d96c16c 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/NonPublicOverrideOfPublicMethodAfterAbstractClassMergingTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/NonPublicOverrideOfPublicMethodAfterAbstractClassMergingTest.java
@@ -15,6 +15,7 @@
 import com.android.tools.r8.NeverClassInline;
 import com.android.tools.r8.NeverInline;
 import com.android.tools.r8.NoHorizontalClassMerging;
+import com.android.tools.r8.NoMethodStaticizing;
 import com.android.tools.r8.NoVerticalClassMerging;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
@@ -48,6 +49,7 @@
         .enableInliningAnnotations()
         .enableNeverClassInliningAnnotations()
         .enableNoHorizontalClassMergingAnnotations()
+        .enableNoMethodStaticizingAnnotations()
         .enableNoVerticalClassMergingAnnotations()
         .setMinApi(parameters.getApiLevel())
         .compile()
@@ -84,6 +86,7 @@
   abstract static class A {
 
     @NeverInline
+    @NoMethodStaticizing
     void m() {
       System.out.println("A.m()");
     }
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/CollisionWithDefaultMethodOutsideMergeGroupAfterSubclassMergingTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/CollisionWithDefaultMethodOutsideMergeGroupAfterSubclassMergingTest.java
index 6529d20..440ccde 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/CollisionWithDefaultMethodOutsideMergeGroupAfterSubclassMergingTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/CollisionWithDefaultMethodOutsideMergeGroupAfterSubclassMergingTest.java
@@ -12,6 +12,7 @@
 import com.android.tools.r8.NeverClassInline;
 import com.android.tools.r8.NeverInline;
 import com.android.tools.r8.NoHorizontalClassMerging;
+import com.android.tools.r8.NoMethodStaticizing;
 import com.android.tools.r8.NoUnusedInterfaceRemoval;
 import com.android.tools.r8.NoVerticalClassMerging;
 import com.android.tools.r8.TestBase;
@@ -70,6 +71,7 @@
         .enableInliningAnnotations()
         .enableNeverClassInliningAnnotations()
         .enableNoHorizontalClassMergingAnnotations()
+        .enableNoMethodStaticizingAnnotations()
         .enableNoUnusedInterfaceRemovalAnnotations()
         .enableNoVerticalClassMergingAnnotations()
         .setMinApi(parameters.getApiLevel())
@@ -110,6 +112,7 @@
   @NoVerticalClassMerging
   interface J {
     @NeverInline
+    @NoMethodStaticizing
     default void m() {
       System.out.println("J");
     }
@@ -120,6 +123,7 @@
   @NoVerticalClassMerging
   interface K {
     @NeverInline
+    @NoMethodStaticizing
     default void m() {
       System.out.println("K");
     }
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/CollisionWithDefaultMethodOutsideMergeGroupClassTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/CollisionWithDefaultMethodOutsideMergeGroupClassTest.java
index 5ee0365..dc48b45 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/CollisionWithDefaultMethodOutsideMergeGroupClassTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/CollisionWithDefaultMethodOutsideMergeGroupClassTest.java
@@ -12,6 +12,7 @@
 import com.android.tools.r8.NeverClassInline;
 import com.android.tools.r8.NeverInline;
 import com.android.tools.r8.NoHorizontalClassMerging;
+import com.android.tools.r8.NoMethodStaticizing;
 import com.android.tools.r8.NoUnusedInterfaceRemoval;
 import com.android.tools.r8.NoVerticalClassMerging;
 import com.android.tools.r8.TestBase;
@@ -60,6 +61,7 @@
         .enableInliningAnnotations()
         .enableNeverClassInliningAnnotations()
         .enableNoHorizontalClassMergingAnnotations()
+        .enableNoMethodStaticizingAnnotations()
         .enableNoUnusedInterfaceRemovalAnnotations()
         .enableNoVerticalClassMergingAnnotations()
         .setMinApi(parameters.getApiLevel())
@@ -100,6 +102,7 @@
   @NoVerticalClassMerging
   interface J {
     @NeverInline
+    @NoMethodStaticizing
     default void m() {
       System.out.println("J");
     }
@@ -110,6 +113,7 @@
   @NoVerticalClassMerging
   interface K {
     @NeverInline
+    @NoMethodStaticizing
     default void m() {
       System.out.println("K");
     }
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/CollisionWithDefaultMethodOutsideMergeGroupLambdaTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/CollisionWithDefaultMethodOutsideMergeGroupLambdaTest.java
index a1dbbaf..9fa9260 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/CollisionWithDefaultMethodOutsideMergeGroupLambdaTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/CollisionWithDefaultMethodOutsideMergeGroupLambdaTest.java
@@ -12,6 +12,7 @@
 import com.android.tools.r8.NeverClassInline;
 import com.android.tools.r8.NeverInline;
 import com.android.tools.r8.NoHorizontalClassMerging;
+import com.android.tools.r8.NoMethodStaticizing;
 import com.android.tools.r8.NoUnusedInterfaceRemoval;
 import com.android.tools.r8.NoVerticalClassMerging;
 import com.android.tools.r8.TestBase;
@@ -55,6 +56,7 @@
         .enableInliningAnnotations()
         .enableNeverClassInliningAnnotations()
         .enableNoHorizontalClassMergingAnnotations()
+        .enableNoMethodStaticizingAnnotations()
         .enableNoUnusedInterfaceRemovalAnnotations()
         .enableNoVerticalClassMergingAnnotations()
         .setMinApi(parameters.getApiLevel())
@@ -107,6 +109,7 @@
   @NoVerticalClassMerging
   interface J {
     @NeverInline
+    @NoMethodStaticizing
     default void m() {
       System.out.println("J");
     }
@@ -117,6 +120,7 @@
   @NoVerticalClassMerging
   interface K {
     @NeverInline
+    @NoMethodStaticizing
     default void m() {
       System.out.println("K");
     }
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/DisjointInterfacesWithDefaultMethodsMergingTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/DisjointInterfacesWithDefaultMethodsMergingTest.java
index 2c40a58..a544c7e 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/DisjointInterfacesWithDefaultMethodsMergingTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/DisjointInterfacesWithDefaultMethodsMergingTest.java
@@ -11,6 +11,7 @@
 
 import com.android.tools.r8.NeverClassInline;
 import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NoMethodStaticizing;
 import com.android.tools.r8.NoUnusedInterfaceRemoval;
 import com.android.tools.r8.NoVerticalClassMerging;
 import com.android.tools.r8.TestBase;
@@ -50,6 +51,7 @@
             })
         .enableInliningAnnotations()
         .enableNeverClassInliningAnnotations()
+        .enableNoMethodStaticizingAnnotations()
         .enableNoUnusedInterfaceRemovalAnnotations()
         .enableNoVerticalClassMergingAnnotations()
         .setMinApi(parameters.getApiLevel())
@@ -80,6 +82,7 @@
   @NoVerticalClassMerging
   interface I {
     @NeverInline
+    @NoMethodStaticizing
     default void f() {
       System.out.println("I");
     }
@@ -89,6 +92,7 @@
   @NoVerticalClassMerging
   interface J {
     @NeverInline
+    @NoMethodStaticizing
     default void g() {
       System.out.println("J");
     }
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/IllegalOverrideAfterInterfaceMergingTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/IllegalOverrideAfterInterfaceMergingTest.java
index e9cd1c1..f58252e 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/IllegalOverrideAfterInterfaceMergingTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/IllegalOverrideAfterInterfaceMergingTest.java
@@ -14,6 +14,7 @@
 import com.android.tools.r8.NeverClassInline;
 import com.android.tools.r8.NeverInline;
 import com.android.tools.r8.NoHorizontalClassMerging;
+import com.android.tools.r8.NoMethodStaticizing;
 import com.android.tools.r8.NoUnusedInterfaceRemoval;
 import com.android.tools.r8.NoVerticalClassMerging;
 import com.android.tools.r8.TestBase;
@@ -48,6 +49,7 @@
         .enableInliningAnnotations()
         .enableNeverClassInliningAnnotations()
         .enableNoHorizontalClassMergingAnnotations()
+        .enableNoMethodStaticizingAnnotations()
         .enableNoUnusedInterfaceRemovalAnnotations()
         .enableNoVerticalClassMergingAnnotations()
         .setMinApi(parameters.getApiLevel())
@@ -96,6 +98,7 @@
     // Intentionally package private. If J is merged into I then this is an illegal override of
     // I.m().
     @NeverInline
+    @NoMethodStaticizing
     void m() {
       System.out.println("A.m()");
     }
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/IllegalSiblingAfterInterfaceMergingTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/IllegalSiblingAfterInterfaceMergingTest.java
index 00a1394..420ccaa 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/IllegalSiblingAfterInterfaceMergingTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/IllegalSiblingAfterInterfaceMergingTest.java
@@ -15,6 +15,7 @@
 import com.android.tools.r8.NeverClassInline;
 import com.android.tools.r8.NeverInline;
 import com.android.tools.r8.NoHorizontalClassMerging;
+import com.android.tools.r8.NoMethodStaticizing;
 import com.android.tools.r8.NoUnusedInterfaceRemoval;
 import com.android.tools.r8.NoVerticalClassMerging;
 import com.android.tools.r8.TestBase;
@@ -49,6 +50,7 @@
         .enableInliningAnnotations()
         .enableNeverClassInliningAnnotations()
         .enableNoHorizontalClassMergingAnnotations()
+        .enableNoMethodStaticizingAnnotations()
         .enableNoUnusedInterfaceRemovalAnnotations()
         .enableNoVerticalClassMergingAnnotations()
         .setMinApi(parameters.getApiLevel())
@@ -101,6 +103,7 @@
     // Intentionally package private. If J is merged into I then this is an illegal override of
     // I.m().
     @NeverInline
+    @NoMethodStaticizing
     void m() {
       System.out.println("A.m()");
     }
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/NoDefaultMethodMergingTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/NoDefaultMethodMergingTest.java
index f68dd90..9d18660 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/NoDefaultMethodMergingTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/NoDefaultMethodMergingTest.java
@@ -12,6 +12,7 @@
 import com.android.tools.r8.NeverClassInline;
 import com.android.tools.r8.NeverInline;
 import com.android.tools.r8.NoHorizontalClassMerging;
+import com.android.tools.r8.NoMethodStaticizing;
 import com.android.tools.r8.NoUnusedInterfaceRemoval;
 import com.android.tools.r8.NoVerticalClassMerging;
 import com.android.tools.r8.TestBase;
@@ -48,6 +49,7 @@
         .enableInliningAnnotations()
         .enableNeverClassInliningAnnotations()
         .enableNoHorizontalClassMergingAnnotations()
+        .enableNoMethodStaticizingAnnotations()
         .enableNoUnusedInterfaceRemovalAnnotations()
         .enableNoVerticalClassMergingAnnotations()
         .setMinApi(parameters.getApiLevel())
@@ -91,6 +93,7 @@
   @NoVerticalClassMerging
   interface I {
     @NeverInline
+    @NoMethodStaticizing
     default void m() {
       System.out.println("I");
     }
@@ -100,6 +103,7 @@
   @NoVerticalClassMerging
   interface J {
     @NeverInline
+    @NoMethodStaticizing
     default void m() {
       System.out.println("J");
     }
diff --git a/src/test/java/com/android/tools/r8/classmerging/vertical/VerticalClassMergingWithMissingTypeArgsTest.java b/src/test/java/com/android/tools/r8/classmerging/vertical/VerticalClassMergingWithMissingTypeArgsTest.java
new file mode 100644
index 0000000..00d8557
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/classmerging/vertical/VerticalClassMergingWithMissingTypeArgsTest.java
@@ -0,0 +1,60 @@
+// Copyright (c) 2022, 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.classmerging.vertical;
+
+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 org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class VerticalClassMergingWithMissingTypeArgsTest extends TestBase {
+
+  @Parameter(0)
+  public TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  @Test
+  public void test() throws Exception {
+    testForR8Compat(parameters.getBackend())
+        .addInnerClasses(getClass())
+        .addKeepMainRule(Main.class)
+        .addKeepAttributes("Signature, InnerClasses, EnclosingMethod, *Annotation*")
+        .addVerticallyMergedClassesInspector(
+            inspector -> inspector.assertMergedIntoSubtype(A.class))
+        .enableInliningAnnotations()
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines("B.m()");
+  }
+
+  static class Main {
+
+    public static void main(String[] args) {
+      new B().m();
+    }
+  }
+
+  static class A<T> {}
+
+  interface I<T> {}
+
+  static class B extends A implements I<B> {
+    @NeverInline
+    void m() {
+      System.out.println("B.m()");
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/backports/AtomicReferenceFieldUpdaterTest.java b/src/test/java/com/android/tools/r8/desugar/backports/AtomicReferenceFieldUpdaterTest.java
new file mode 100644
index 0000000..1e66d40
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/backports/AtomicReferenceFieldUpdaterTest.java
@@ -0,0 +1,47 @@
+// Copyright (c) 2022, 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.backports;
+
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.ToolHelper.DexVm.Version;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class AtomicReferenceFieldUpdaterTest extends AbstractBackportTest {
+
+  @Parameters(name = "{0}")
+  public static Iterable<?> data() {
+    return getTestParameters()
+        .withDexRuntimesStartingFromExcluding(Version.V4_0_4)
+        .withAllApiLevels()
+        .build();
+  }
+
+  public AtomicReferenceFieldUpdaterTest(TestParameters parameters) {
+    super(parameters, AtomicReferenceFieldUpdater.class, Main.class);
+
+    ignoreInvokes("newUpdater");
+
+    // java.util.concurrent.atomic.AtomicReferenceFieldUpdater issue is on API 31, see b/211646483.
+    registerTarget(AndroidApiLevel.Sv2, 3);
+  }
+
+  public static class Main extends MiniAssert {
+    public volatile String field;
+
+    public static void main(String[] args) throws Exception {
+      AtomicReferenceFieldUpdater<Main, String> updater =
+          AtomicReferenceFieldUpdater.newUpdater(Main.class, String.class, "field");
+      Main x = new Main();
+      assertTrue(updater.compareAndSet(x, null, "A"));
+      assertTrue(updater.compareAndSet(x, "A", "B"));
+      assertFalse(updater.compareAndSet(x, "A", "B"));
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/backports/TestBackportedNotPresentInAndroidJar.java b/src/test/java/com/android/tools/r8/desugar/backports/TestBackportedNotPresentInAndroidJar.java
index bd338b6..8db2a9c 100644
--- a/src/test/java/com/android/tools/r8/desugar/backports/TestBackportedNotPresentInAndroidJar.java
+++ b/src/test/java/com/android/tools/r8/desugar/backports/TestBackportedNotPresentInAndroidJar.java
@@ -5,14 +5,19 @@
 package com.android.tools.r8.desugar.backports;
 
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
-import static org.hamcrest.CoreMatchers.not;
+import static com.android.tools.r8.utils.codeinspector.Matchers.notIf;
 import static org.hamcrest.MatcherAssert.assertThat;
 
 import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.ir.desugar.BackportedMethodRewriter;
+import com.android.tools.r8.references.MethodReference;
+import com.android.tools.r8.references.Reference;
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.InternalOptions;
@@ -20,13 +25,38 @@
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import com.google.common.collect.ImmutableSet;
 import java.util.Arrays;
 import java.util.List;
+import java.util.Set;
+import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
 import java.util.stream.Collectors;
 import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
 
+@RunWith(Parameterized.class)
 public class TestBackportedNotPresentInAndroidJar extends TestBase {
 
+  @Parameter(0)
+  public TestParameters parameters;
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withNoneRuntime().build();
+  }
+
+  private Set<DexMethod> expectedToAlwaysBePresentInAndroidJar(DexItemFactory factory)
+      throws Exception {
+    MethodReference compareAndSet =
+        Reference.methodFromMethod(
+            AtomicReferenceFieldUpdater.class.getDeclaredMethod(
+                "compareAndSet", Object.class, Object.class, Object.class));
+    assert compareAndSet.getReturnType().getTypeName().equals("boolean");
+    return ImmutableSet.of(factory.createMethod(compareAndSet));
+  }
+
   @Test
   public void testBackportedMethodsPerAPILevel() throws Exception {
     for (AndroidApiLevel apiLevel : AndroidApiLevel.values()) {
@@ -43,6 +73,7 @@
       List<DexMethod> backportedMethods =
           BackportedMethodRewriter.generateListOfBackportedMethods(
               AndroidApp.builder().build(), options, ThreadUtils.getExecutorService(options));
+      Set<DexMethod> alwaysPresent = expectedToAlwaysBePresentInAndroidJar(options.itemFactory);
       for (DexMethod method : backportedMethods) {
         // Two different DexItemFactories are in play, but as toSourceString is used for lookup
         // that is not an issue.
@@ -55,7 +86,9 @@
                     .map(DexType::toSourceString)
                     .collect(Collectors.toList()));
         assertThat(
-            foundInAndroidJar + " present in " + apiLevel, foundInAndroidJar, not(isPresent()));
+            foundInAndroidJar + " present in " + apiLevel,
+            foundInAndroidJar,
+            notIf(isPresent(), !alwaysPresent.contains(method)));
       }
     }
   }
diff --git a/src/test/java/com/android/tools/r8/desugar/backports/UnsafeBackportTest.java b/src/test/java/com/android/tools/r8/desugar/backports/UnsafeBackportTest.java
index dbe597d..00134b5 100644
--- a/src/test/java/com/android/tools/r8/desugar/backports/UnsafeBackportTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/backports/UnsafeBackportTest.java
@@ -39,7 +39,7 @@
 
     ignoreInvokes("objectFieldOffset");
 
-    // sun.misc.Unsafe issue is on API 31.
+    // sun.misc.Unsafe issue is on API 31, see b/211646483..
     registerTarget(AndroidApiLevel.Sv2, 3);
   }
 
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/RetargetOverrideTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/RetargetOverrideTest.java
index 61a1bf8..6bf49a0 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/RetargetOverrideTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/RetargetOverrideTest.java
@@ -5,7 +5,13 @@
 package com.android.tools.r8.desugar.desugaredlibrary;
 
 import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.humanspecification.HumanDesugaredLibrarySpecification;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.machinespecification.MachineDesugaredLibrarySpecification;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.specificationconversion.HumanToMachineSpecificationConverter;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.specificationconversion.LegacyToHumanSpecificationConverter;
 import com.android.tools.r8.utils.BooleanUtils;
+import com.android.tools.r8.utils.InternalOptions;
+import java.io.IOException;
 import java.nio.file.Path;
 import java.time.Instant;
 import java.time.ZonedDateTime;
@@ -25,15 +31,19 @@
 
   private final TestParameters parameters;
   private final boolean shrinkDesugaredLibrary;
+  private final boolean machineSpec;
 
-  @Parameters(name = "{1}, shrinkDesugaredLibrary: {0}")
+  @Parameters(name = "machine: {0}, {2}, shrink: {1}")
   public static List<Object[]> data() {
     return buildParameters(
         BooleanUtils.values(),
+        BooleanUtils.values(),
         getTestParameters().withAllRuntimes().withAllApiLevelsAlsoForCf().build());
   }
 
-  public RetargetOverrideTest(boolean shrinkDesugaredLibrary, TestParameters parameters) {
+  public RetargetOverrideTest(
+      boolean machineSpec, boolean shrinkDesugaredLibrary, TestParameters parameters) {
+    this.machineSpec = machineSpec;
     this.shrinkDesugaredLibrary = shrinkDesugaredLibrary;
     this.parameters = parameters;
   }
@@ -44,6 +54,7 @@
     Path desugaredTwice =
         testForD8(Backend.CF)
             .addLibraryFiles(getLibraryFile())
+            .addOptionsModification(this::setMachineSpec)
             .addProgramFiles(
                 testForD8(Backend.CF)
                     .addLibraryFiles(getLibraryFile())
@@ -65,6 +76,7 @@
       stdout =
           testForD8(Backend.DEX)
               .addProgramFiles(desugaredTwice)
+              .addOptionsModification(this::setMachineSpec)
               .setMinApi(parameters.getApiLevel())
               .disableDesugaring()
               .compile()
@@ -95,6 +107,22 @@
     assertLines2By2Correct(stdout);
   }
 
+  private void setMachineSpec(InternalOptions opt) {
+    if (!machineSpec) {
+      return;
+    }
+    try {
+      HumanDesugaredLibrarySpecification human =
+          new LegacyToHumanSpecificationConverter()
+              .convert(opt.desugaredLibrarySpecification, getLibraryFile(), opt);
+      MachineDesugaredLibrarySpecification machine =
+          new HumanToMachineSpecificationConverter().convert(human, getLibraryFile(), opt);
+      opt.testing.machineDesugaredLibrarySpecification = machine;
+    } catch (IOException e) {
+      throw new RuntimeException(e);
+    }
+  }
+
   @Test
   public void testRetargetOverrideD8() throws Exception {
     Assume.assumeTrue(parameters.getRuntime().isDex());
@@ -102,6 +130,7 @@
     String stdout =
         testForD8()
             .addLibraryFiles(getLibraryFile())
+            .addOptionsModification(this::setMachineSpec)
             .addInnerClasses(RetargetOverrideTest.class)
             .enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer)
             .setMinApi(parameters.getApiLevel())
@@ -127,6 +156,7 @@
     String stdout =
         testForR8(Backend.DEX)
             .addLibraryFiles(getLibraryFile())
+            .addOptionsModification(this::setMachineSpec)
             .addKeepMainRule(Executor.class)
             .addInnerClasses(RetargetOverrideTest.class)
             .enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer)
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/EnumUnboxingClassStaticizerTest.java b/src/test/java/com/android/tools/r8/enumunboxing/EnumUnboxingClassStaticizerTest.java
index c531710..d510c33 100644
--- a/src/test/java/com/android/tools/r8/enumunboxing/EnumUnboxingClassStaticizerTest.java
+++ b/src/test/java/com/android/tools/r8/enumunboxing/EnumUnboxingClassStaticizerTest.java
@@ -44,6 +44,11 @@
         .addKeepMainRule(TestClass.class)
         .addKeepRules(enumKeepRules.getKeepRules())
         .addEnumUnboxingInspector(inspector -> inspector.assertUnboxed(UnboxableEnum.class))
+        .addHorizontallyMergedClassesInspector(
+            inspector ->
+                inspector
+                    .assertMergedInto(CompanionHost.class, Companion.class)
+                    .assertNoOtherClassesMerged())
         .enableNeverClassInliningAnnotations()
         .enableInliningAnnotations()
         .noMinification() // For assertions.
@@ -65,10 +70,9 @@
           isPresent());
       return;
     }
-    MethodSubject method =
-        codeInspector.clazz(CompanionHost.class).uniqueMethodWithName(renamedMethodName);
+    MethodSubject method = codeInspector.clazz(Companion.class).uniqueMethodWithName("method");
     assertThat(method, isPresent());
-    assertEquals("int", method.getMethod().getReference().proto.parameters.toString());
+    assertEquals("int", method.getMethod().getParameters().toString());
   }
 
   static class TestClass {
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/VirtualMethodOverrideEnumUnboxingTest.java b/src/test/java/com/android/tools/r8/enumunboxing/VirtualMethodOverrideEnumUnboxingTest.java
index 093f902..9dcd9cb 100644
--- a/src/test/java/com/android/tools/r8/enumunboxing/VirtualMethodOverrideEnumUnboxingTest.java
+++ b/src/test/java/com/android/tools/r8/enumunboxing/VirtualMethodOverrideEnumUnboxingTest.java
@@ -11,6 +11,7 @@
 import com.android.tools.r8.KeepConstantArguments;
 import com.android.tools.r8.NeverClassInline;
 import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NoMethodStaticizing;
 import com.android.tools.r8.NoVerticalClassMerging;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
@@ -49,6 +50,7 @@
         .enableConstantArgumentAnnotations()
         .enableInliningAnnotations()
         .enableNeverClassInliningAnnotations()
+        .enableNoMethodStaticizingAnnotations()
         .enableNoVerticalClassMergingAnnotations()
         .addOptionsModification(options -> enableEnumOptions(options, enumValueOptimization))
         .addEnumUnboxingInspector(inspector -> inspector.assertUnboxed(MyEnum.class))
@@ -93,6 +95,7 @@
 
     @KeepConstantArguments
     @NeverInline
+    @NoMethodStaticizing
     void m(int x, MyEnum y) {
       System.out.println("B.m(" + x + " : int, " + y.toString() + " : MyEnum)");
     }
diff --git a/src/test/java/com/android/tools/r8/graph/InvokeSuperTest.java b/src/test/java/com/android/tools/r8/graph/InvokeSuperTest.java
index 644fc9c..cb43c01 100644
--- a/src/test/java/com/android/tools/r8/graph/InvokeSuperTest.java
+++ b/src/test/java/com/android/tools/r8/graph/InvokeSuperTest.java
@@ -6,6 +6,7 @@
 import static org.hamcrest.CoreMatchers.containsString;
 import static org.junit.Assert.assertTrue;
 
+import com.android.tools.r8.NoMethodStaticizing;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
@@ -146,6 +147,7 @@
         .setMinApi(parameters.getApiLevel())
         .addKeepMainRule(MainClassFailing.class)
         .addOptionsModification(o -> o.testing.allowTypeErrors = true)
+        .enableNoMethodStaticizingAnnotations()
         .run(parameters.getRuntime(), MainClassFailing.class)
         .apply(r -> checkNonVerifyingResult(r, true));
   }
@@ -190,6 +192,7 @@
 
   static class SubClassOfInvokerClass extends InvokerClass {
 
+    @NoMethodStaticizing
     public void subLevel2Method() {
       System.out.println("subLevel2Method in SubClassOfInvokerClass");
     }
diff --git a/src/test/java/com/android/tools/r8/graph/invokespecial/InvokeSpecialOnOtherInterfaceTest.java b/src/test/java/com/android/tools/r8/graph/invokespecial/InvokeSpecialOnOtherInterfaceTest.java
index 99ab7b3..d05122a 100644
--- a/src/test/java/com/android/tools/r8/graph/invokespecial/InvokeSpecialOnOtherInterfaceTest.java
+++ b/src/test/java/com/android/tools/r8/graph/invokespecial/InvokeSpecialOnOtherInterfaceTest.java
@@ -8,6 +8,7 @@
 import static org.junit.Assert.assertEquals;
 import static org.objectweb.asm.Opcodes.INVOKESPECIAL;
 
+import com.android.tools.r8.NoMethodStaticizing;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
@@ -50,6 +51,7 @@
         .addProgramClasses(I.class)
         .addProgramClassFileData(getClassWithTransformedInvoked())
         .addKeepMainRule(Main.class)
+        .enableNoMethodStaticizingAnnotations()
         .setMinApi(parameters.getApiLevel())
         .run(parameters.getRuntime(), Main.class)
         .assertFailureWithErrorThatThrowsIf(parameters.isCfRuntime(), VerifyError.class)
@@ -75,6 +77,7 @@
 
   public interface I {
 
+    @NoMethodStaticizing
     default void foo() {
       System.out.println("Hello World!");
     }
diff --git a/src/test/java/com/android/tools/r8/internal/opensourceapps/TiviTest.java b/src/test/java/com/android/tools/r8/internal/opensourceapps/TiviTest.java
index 6b593fd..128f780 100644
--- a/src/test/java/com/android/tools/r8/internal/opensourceapps/TiviTest.java
+++ b/src/test/java/com/android/tools/r8/internal/opensourceapps/TiviTest.java
@@ -58,6 +58,7 @@
         testForR8(Backend.CF)
             .addProgramFiles(outDirectory.resolve("program.jar"))
             .apply(this::configure)
+            .apply(this::configureCf)
             .compile();
     testForR8(Backend.DEX)
         .addProgramFiles(compileResult.writeToZip())
@@ -79,6 +80,7 @@
         testForR8Compat(Backend.CF)
             .addProgramFiles(outDirectory.resolve("program.jar"))
             .apply(this::configure)
+            .apply(this::configureCf)
             .compile();
     testForR8Compat(Backend.DEX)
         .addProgramFiles(compileResult.writeToZip())
@@ -93,7 +95,12 @@
         .addKeepRuleFiles(outDirectory.resolve("proguard.config"))
         .setMinApi(AndroidApiLevel.M)
         .allowDiagnosticMessages()
+        .allowUnnecessaryDontWarnWildcards()
         .allowUnusedDontWarnPatterns()
         .allowUnusedProguardConfigurationRules();
   }
+
+  private void configureCf(R8TestBuilder<?> testBuilder) {
+    testBuilder.addOptionsModification(options -> options.horizontalClassMergerOptions().disable());
+  }
 }
diff --git a/src/test/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldBitAccessInfoTest.java b/src/test/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldBitAccessInfoTest.java
index 82bc605..2e3512d 100644
--- a/src/test/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldBitAccessInfoTest.java
+++ b/src/test/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldBitAccessInfoTest.java
@@ -86,7 +86,7 @@
     OptimizationFeedbackMock feedback = new OptimizationFeedbackMock();
     FieldBitAccessAnalysis fieldBitAccessAnalysis = new FieldBitAccessAnalysis();
     FieldAccessAnalysis fieldAccessAnalysis =
-        new FieldAccessAnalysis(appView, null, fieldBitAccessAnalysis, null);
+        new FieldAccessAnalysis(appView, null, fieldBitAccessAnalysis, null, null);
 
     DexProgramClass clazz = appView.appInfo().classes().iterator().next();
     assertEquals(TestClass.class.getTypeName(), clazz.type.toSourceString());
diff --git a/src/test/java/com/android/tools/r8/ir/desugar/backports/AtomicReferenceFieldUpdaterMethods.java b/src/test/java/com/android/tools/r8/ir/desugar/backports/AtomicReferenceFieldUpdaterMethods.java
new file mode 100644
index 0000000..b3f658c
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/desugar/backports/AtomicReferenceFieldUpdaterMethods.java
@@ -0,0 +1,23 @@
+// Copyright (c) 2022, 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.desugar.backports;
+
+import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
+
+public final class AtomicReferenceFieldUpdaterMethods {
+  // Workaround Android S issue with AtomicReferenceFieldUpdater.compareAndSet (b/211646483).
+  public static boolean compareAndSet(
+      AtomicReferenceFieldUpdater<Object, Object> updater,
+      Object object,
+      Object expect,
+      Object update) {
+    do {
+      if (updater.compareAndSet(object, expect, update)) {
+        return true;
+      }
+    } while (updater.get(object) == expect);
+    return false;
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/desugar/backports/GenerateBackportMethods.java b/src/test/java/com/android/tools/r8/ir/desugar/backports/GenerateBackportMethods.java
index f955ca3..c6e3eea 100644
--- a/src/test/java/com/android/tools/r8/ir/desugar/backports/GenerateBackportMethods.java
+++ b/src/test/java/com/android/tools/r8/ir/desugar/backports/GenerateBackportMethods.java
@@ -34,6 +34,7 @@
       factory.createType("Lcom/android/tools/r8/ir/desugar/backports/BackportedMethods;");
   private final List<Class<?>> METHOD_TEMPLATE_CLASSES =
       ImmutableList.of(
+          AtomicReferenceFieldUpdaterMethods.class,
           BooleanMethods.class,
           ByteMethods.class,
           CharSequenceMethods.class,
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/R8InliningTest.java b/src/test/java/com/android/tools/r8/ir/optimize/R8InliningTest.java
index 62a44a1..bded17f 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/R8InliningTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/R8InliningTest.java
@@ -363,7 +363,7 @@
     assertThat(clazz.uniqueMethodWithName("conditionalOperator"), isAbsent());
 
     // The enum parameter is unboxed.
-    MethodSubject m = clazz.uniqueMethodWithName("moreControlFlows$enumunboxing$");
+    MethodSubject m = clazz.uniqueMethodWithName("moreControlFlows");
     assertTrue(m.isPresent());
 
     // Verify that a.b() is resolved to an inline instance-get.
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/callsites/LibraryMethodOverridesTest.java b/src/test/java/com/android/tools/r8/ir/optimize/callsites/LibraryMethodOverridesTest.java
index 5fd1825..e4b5197 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/callsites/LibraryMethodOverridesTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/callsites/LibraryMethodOverridesTest.java
@@ -8,12 +8,14 @@
 import static org.junit.Assert.assertTrue;
 
 import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NoMethodStaticizing;
 import com.android.tools.r8.R8TestCompileResult;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.ToolHelper.DexVm.Version;
 import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.android.tools.r8.utils.codeinspector.InstructionSubject;
@@ -34,6 +36,7 @@
         .withCfRuntimes()
         // java.util.function.Predicate is not available prior to API level 24 (V7.0).
         .withDexRuntimesStartingFromIncluding(Version.V7_0_0)
+        .withApiLevelsStartingAtIncluding(AndroidApiLevel.N)
         .build();
   }
 
@@ -44,10 +47,10 @@
   public void testR8() throws Exception {
     R8TestCompileResult libraryCompileResult =
         testForR8(parameters.getBackend())
-        .addProgramClasses(LibClass.class)
-        .addKeepClassAndMembersRules(LibClass.class)
-        .setMinApi(parameters.getRuntime())
-        .compile();
+            .addProgramClasses(LibClass.class)
+            .addKeepClassAndMembersRules(LibClass.class)
+            .setMinApi(parameters.getApiLevel())
+            .compile();
     testForR8(parameters.getBackend())
         .addProgramClasses(TestClass.class, CustomPredicate.class)
         .addClasspathClasses(LibClass.class)
@@ -56,7 +59,8 @@
             o ->
                 o.testing.callSiteOptimizationInfoInspector = this::callSiteOptimizationInfoInspect)
         .enableInliningAnnotations()
-        .setMinApi(parameters.getRuntime())
+        .enableNoMethodStaticizingAnnotations()
+        .setMinApi(parameters.getApiLevel())
         .compile()
         .addRunClasspathFiles(libraryCompileResult.writeToZip())
         .run(parameters.getRuntime(), MAIN)
@@ -107,11 +111,13 @@
     }
 
     @NeverInline
+    @NoMethodStaticizing
     private void live() {
       System.out.println("Live");
     }
 
     @NeverInline
+    @NoMethodStaticizing
     private void alsoLive() {
       System.out.println("Also live");
     }
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/callsites/WithStaticizerTest.java b/src/test/java/com/android/tools/r8/ir/optimize/callsites/WithStaticizerTest.java
index 07df10b..3dc868d 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/callsites/WithStaticizerTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/callsites/WithStaticizerTest.java
@@ -4,8 +4,7 @@
 package com.android.tools.r8.ir.optimize.callsites;
 
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
-import static junit.framework.TestCase.assertTrue;
-import static org.hamcrest.CoreMatchers.not;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isStatic;
 import static org.hamcrest.MatcherAssert.assertThat;
 
 import com.android.tools.r8.NeverClassInline;
@@ -16,7 +15,6 @@
 import com.android.tools.r8.TestParametersCollection;
 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 org.junit.Test;
 import org.junit.runner.RunWith;
@@ -43,6 +41,11 @@
     testForR8(parameters.getBackend())
         .addInnerClasses(WithStaticizerTest.class)
         .addKeepMainRule(MAIN)
+        .addHorizontallyMergedClassesInspector(
+            inspector ->
+                inspector
+                    .assertMergedInto(Host.class, Host.Companion.class)
+                    .assertNoOtherClassesMerged())
         .enableInliningAnnotations()
         .enableNeverClassInliningAnnotations()
         .enableNoHorizontalClassMergingAnnotations()
@@ -55,14 +58,10 @@
   private void inspect(CodeInspector inspector) {
     // Check if the candidate is indeed staticized.
     ClassSubject companion = inspector.clazz(Host.Companion.class);
-    assertThat(companion, not(isPresent()));
-
-    // Null check in Companion#foo is migrated to Host#foo.
-    ClassSubject host = inspector.clazz(Host.class);
-    assertThat(host, isPresent());
-    MethodSubject foo = host.uniqueMethodWithName("foo");
+    assertThat(companion, isPresent());
+    MethodSubject foo = companion.uniqueMethodWithName("foo");
     assertThat(foo, isPresent());
-    assertTrue(foo.streamInstructions().noneMatch(InstructionSubject::isIf));
+    assertThat(foo, isStatic());
   }
 
   @NeverClassInline
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamicupperboundtype/InvokeDirectPositiveTest.java b/src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamicupperboundtype/InvokeDirectPositiveTest.java
index 0cd06dc..ef373cd 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamicupperboundtype/InvokeDirectPositiveTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamicupperboundtype/InvokeDirectPositiveTest.java
@@ -9,6 +9,7 @@
 
 import com.android.tools.r8.NeverClassInline;
 import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NoMethodStaticizing;
 import com.android.tools.r8.NoParameterTypeStrengthening;
 import com.android.tools.r8.NoVerticalClassMerging;
 import com.android.tools.r8.TestBase;
@@ -44,6 +45,7 @@
     testForR8(parameters.getBackend())
         .addInnerClasses(InvokeDirectPositiveTest.class)
         .addKeepMainRule(MAIN)
+        .enableNoMethodStaticizingAnnotations()
         .enableNoParameterTypeStrengtheningAnnotations()
         .enableNoVerticalClassMergingAnnotations()
         .enableNeverClassInliningAnnotations()
@@ -109,6 +111,7 @@
     }
 
     @NeverInline
+    @NoMethodStaticizing
     @NoParameterTypeStrengthening
     private void test(Base arg) {
       if (arg instanceof Sub1) {
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/callsites/nullability/InvokeDirectNegativeTest.java b/src/test/java/com/android/tools/r8/ir/optimize/callsites/nullability/InvokeDirectNegativeTest.java
index 0372989..3a3a62d 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/callsites/nullability/InvokeDirectNegativeTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/callsites/nullability/InvokeDirectNegativeTest.java
@@ -9,6 +9,7 @@
 
 import com.android.tools.r8.NeverClassInline;
 import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NoMethodStaticizing;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
@@ -42,6 +43,7 @@
         .addKeepMainRule(MAIN)
         .enableNeverClassInliningAnnotations()
         .enableInliningAnnotations()
+        .enableNoMethodStaticizingAnnotations()
         .addOptionsModification(
             o ->
                 o.testing.callSiteOptimizationInfoInspector = this::callSiteOptimizationInfoInspect)
@@ -74,6 +76,7 @@
     }
 
     @NeverInline
+    @NoMethodStaticizing
     private void test(Object arg) {
       if (arg != null) {
         System.out.println("non-null");
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerTest.java b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerTest.java
index 6ea5d98..1884e5e 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerTest.java
@@ -87,6 +87,7 @@
             .addProgramClasses(classes)
             .enableInliningAnnotations()
             .enableNoHorizontalClassMergingAnnotations()
+            .enableNoMethodStaticizingAnnotations()
             .enableSideEffectAnnotations()
             .addKeepMainRule(main)
             .addKeepAttributes("LineNumberTable")
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/trivial/CycleReferenceAB.java b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/trivial/CycleReferenceAB.java
index 0390712..f56779e 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/trivial/CycleReferenceAB.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/trivial/CycleReferenceAB.java
@@ -5,6 +5,7 @@
 package com.android.tools.r8.ir.optimize.classinliner.trivial;
 
 import com.android.tools.r8.NoHorizontalClassMerging;
+import com.android.tools.r8.NoMethodStaticizing;
 
 @NoHorizontalClassMerging
 public class CycleReferenceAB {
@@ -14,6 +15,7 @@
     this.a = a;
   }
 
+  @NoMethodStaticizing
   public void foo(int depth) {
     CycleReferenceBA ba = new CycleReferenceBA("depth=" + depth);
     System.out.println("CycleReferenceAB::foo(" + depth + ")");
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/trivial/CycleReferenceBA.java b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/trivial/CycleReferenceBA.java
index 1c5d147..6d22b16 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/trivial/CycleReferenceBA.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/trivial/CycleReferenceBA.java
@@ -4,6 +4,8 @@
 
 package com.android.tools.r8.ir.optimize.classinliner.trivial;
 
+import com.android.tools.r8.NoMethodStaticizing;
+
 public class CycleReferenceBA {
   private String a;
 
@@ -11,6 +13,7 @@
     this.a = a;
   }
 
+  @NoMethodStaticizing
   public void foo(int depth) {
     CycleReferenceAB ab = new CycleReferenceAB("depth=" + depth);
     System.out.println("CycleReferenceBA::foo(" + depth + ")");
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/trivial/TrivialTestClass.java b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/trivial/TrivialTestClass.java
index 86cf25e..820a5de 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/trivial/TrivialTestClass.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/trivial/TrivialTestClass.java
@@ -6,6 +6,7 @@
 
 import com.android.tools.r8.AssumeMayHaveSideEffects;
 import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NoMethodStaticizing;
 
 public class TrivialTestClass {
   private static int ID = 0;
@@ -88,6 +89,7 @@
   }
 
   @NeverInline
+  @NoMethodStaticizing
   private void testCycles() {
     new CycleReferenceAB("first").foo(3);
     new CycleReferenceBA("second").foo(4);
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/devirtualize/InvokeSuperToInvokeVirtualTest.java b/src/test/java/com/android/tools/r8/ir/optimize/devirtualize/InvokeSuperToInvokeVirtualTest.java
index 3d48fb9..b21bcab 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/devirtualize/InvokeSuperToInvokeVirtualTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/devirtualize/InvokeSuperToInvokeVirtualTest.java
@@ -10,6 +10,7 @@
 
 import com.android.tools.r8.NeverClassInline;
 import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NoMethodStaticizing;
 import com.android.tools.r8.NoVerticalClassMerging;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
@@ -42,6 +43,7 @@
         .addInnerClasses(InvokeSuperToInvokeVirtualTest.class)
         .addKeepMainRule(TestClass.class)
         .enableInliningAnnotations()
+        .enableNoMethodStaticizingAnnotations()
         .enableNoVerticalClassMergingAnnotations()
         .enableNeverClassInliningAnnotations()
         .setMinApi(parameters.getApiLevel())
@@ -98,6 +100,7 @@
     }
 
     @NeverInline
+    @NoMethodStaticizing
     void world() {
       System.out.println(" world!");
     }
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/dynamictype/DynamicTypeOptimizationTest.java b/src/test/java/com/android/tools/r8/ir/optimize/dynamictype/DynamicTypeOptimizationTest.java
index f3db3a7..ca2d49a 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/dynamictype/DynamicTypeOptimizationTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/dynamictype/DynamicTypeOptimizationTest.java
@@ -10,6 +10,7 @@
 import static org.junit.Assert.assertTrue;
 
 import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NoMethodStaticizing;
 import com.android.tools.r8.NoReturnTypeStrengthening;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
@@ -44,6 +45,7 @@
         // Keep B to ensure that we will treat it as being instantiated.
         .addKeepClassRulesWithAllowObfuscation(B.class)
         .enableInliningAnnotations()
+        .enableNoMethodStaticizingAnnotations()
         .enableNoReturnTypeStrengtheningAnnotations()
         .setMinApi(parameters.getApiLevel())
         .compile()
@@ -155,6 +157,7 @@
   static class A implements I {
 
     @NeverInline
+    @NoMethodStaticizing
     @Override
     public void hello() {
       System.out.print("Hello");
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/outliner/classtypes/B134462736.java b/src/test/java/com/android/tools/r8/ir/optimize/outliner/classtypes/B134462736.java
index 3a0b304..883c62a 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/outliner/classtypes/B134462736.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/outliner/classtypes/B134462736.java
@@ -9,6 +9,7 @@
 
 import com.android.tools.r8.KeepConstantArguments;
 import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NoMethodStaticizing;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
@@ -66,6 +67,7 @@
         .addInnerClasses(B134462736.class)
         .addKeepMainRule(TestClass.class)
         .enableConstantArgumentAnnotations()
+        .enableNoMethodStaticizingAnnotations()
         .setMinApi(parameters.getApiLevel())
         .noMinification()
         .addOptionsModification(
@@ -83,6 +85,7 @@
 
     @KeepConstantArguments
     @NeverInline
+    @NoMethodStaticizing
     public void consumer(String arg1, String arg2) {
       System.out.println(arg1 + " " + arg2);
     }
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/staticizer/ClassStaticizerTest.java b/src/test/java/com/android/tools/r8/ir/optimize/staticizer/ClassStaticizerTest.java
index f3a61c4..5987d3c 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/staticizer/ClassStaticizerTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/staticizer/ClassStaticizerTest.java
@@ -4,6 +4,7 @@
 
 package com.android.tools.r8.ir.optimize.staticizer;
 
+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;
@@ -168,9 +169,9 @@
     //  instantiation of SimpleWithParams, it is marked as ineligible for staticizing.
     assertEquals(
         Lists.newArrayList(
+            "STATIC: String SimpleWithParams.bar(String)",
             "STATIC: String TrivialTestClass.next()",
             "SimpleWithParams SimpleWithParams.INSTANCE",
-            "VIRTUAL: String SimpleWithParams.bar(String)",
             "VIRTUAL: String SimpleWithParams.foo()"),
         references(clazz, "testSimpleWithParams", "void"));
 
@@ -204,9 +205,9 @@
 
     assertEquals(
         Lists.newArrayList(
+            "STATIC: String SimpleWithThrowingGetter.bar(String)",
             "STATIC: String TrivialTestClass.next()",
             "SimpleWithThrowingGetter SimpleWithThrowingGetter.INSTANCE",
-            "VIRTUAL: String SimpleWithThrowingGetter.bar(String)",
             "VIRTUAL: String SimpleWithThrowingGetter.foo()"),
         references(clazz, "testSimpleWithThrowingGetter", "void"));
 
@@ -219,6 +220,7 @@
         Lists.newArrayList(
             "DIRECT: void SimpleWithLazyInit.<init>()",
             "DIRECT: void SimpleWithLazyInit.<init>()",
+            "STATIC: String SimpleWithLazyInit.bar(String)",
             "STATIC: String TrivialTestClass.next()",
             "SimpleWithLazyInit SimpleWithLazyInit.INSTANCE",
             "SimpleWithLazyInit SimpleWithLazyInit.INSTANCE",
@@ -226,7 +228,6 @@
             "SimpleWithLazyInit SimpleWithLazyInit.INSTANCE",
             "SimpleWithLazyInit SimpleWithLazyInit.INSTANCE",
             "SimpleWithLazyInit SimpleWithLazyInit.INSTANCE",
-            "VIRTUAL: String SimpleWithLazyInit.bar(String)",
             "VIRTUAL: String SimpleWithLazyInit.foo()"),
         references(clazz, "testSimpleWithLazyInit", "void"));
 
@@ -300,35 +301,37 @@
 
     assertEquals(
         Lists.newArrayList(
-            "STATIC: String movetohost.HostOk.bar(String)",
-            "STATIC: String movetohost.HostOk.foo()",
+            "STATIC: String movetohost.CandidateOk.bar(String)",
+            "STATIC: String movetohost.CandidateOk.foo()",
             "STATIC: String movetohost.MoveToHostTestClass.next()",
             "STATIC: String movetohost.MoveToHostTestClass.next()",
-            "STATIC: void movetohost.HostOk.blah(String)"),
+            "STATIC: void movetohost.CandidateOk.blah(String)"),
         references(clazz, "testOk", "void"));
 
-    assertThat(inspector.clazz(CandidateOk.class), not(isPresent()));
+    assertThat(inspector.clazz(HostOk.class), isAbsent());
+    assertThat(inspector.clazz(CandidateOk.class), isPresent());
 
     assertEquals(
         Lists.newArrayList(
-            "STATIC: String movetohost.HostOkSideEffects.bar(String)",
-            "STATIC: String movetohost.HostOkSideEffects.foo()",
+            "STATIC: String movetohost.CandidateOkSideEffects.bar(String)",
+            "STATIC: String movetohost.CandidateOkSideEffects.foo()",
             "STATIC: String movetohost.MoveToHostTestClass.next()",
-            "movetohost.HostOkSideEffects movetohost.HostOkSideEffects.INSTANCE"),
+            "movetohost.CandidateOkSideEffects movetohost.HostOkSideEffects.INSTANCE"),
         references(clazz, "testOkSideEffects", "void"));
 
-    assertThat(inspector.clazz(CandidateOkSideEffects.class), not(isPresent()));
+    assertThat(inspector.clazz(HostOkSideEffects.class), isPresent());
+    assertThat(inspector.clazz(CandidateOkSideEffects.class), isPresent());
 
     assertEquals(
         Lists.newArrayList(
-            "DIRECT: void movetohost.HostConflictMethod.<init>()",
             "STATIC: String movetohost.CandidateConflictMethod.bar(String)",
             "STATIC: String movetohost.CandidateConflictMethod.foo()",
+            "STATIC: String movetohost.HostConflictMethod.bar(String)",
             "STATIC: String movetohost.MoveToHostTestClass.next()",
-            "STATIC: String movetohost.MoveToHostTestClass.next()",
-            "VIRTUAL: String movetohost.HostConflictMethod.bar(String)"),
+            "STATIC: String movetohost.MoveToHostTestClass.next()"),
         references(clazz, "testConflictMethod", "void"));
 
+    assertThat(inspector.clazz(HostConflictMethod.class), isPresent());
     assertThat(inspector.clazz(CandidateConflictMethod.class), isPresent());
 
     assertEquals(
@@ -427,12 +430,14 @@
     assertThat(clazz.uniqueMethodWithName("calledTwice"), not(isPresent()));
 
     // Check that the two inlines of "calledTwice" is correctly rewritten.
-    assertThat(clazz.uniqueMethodWithName("foo"), isPresent());
-    assertThat(clazz.uniqueMethodWithName("bar"), isPresent());
+    ClassSubject candidateClassSubject = inspector.clazz(Candidate.class);
+    assertThat(candidateClassSubject, isPresent());
+    assertThat(candidateClassSubject.uniqueMethodWithName("foo"), isPresent());
+    assertThat(candidateClassSubject.uniqueMethodWithName("bar"), isPresent());
     assertEquals(
         Lists.newArrayList(
-            "STATIC: String dualcallinline.DualCallTest.foo()",
-            "STATIC: String dualcallinline.DualCallTest.foo()"),
+            "STATIC: String dualcallinline.Candidate.foo()",
+            "STATIC: String dualcallinline.Candidate.foo()"),
         references(clazz, "main", "void", "java.lang.String[]"));
   }
 }
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/staticizer/CompanionClassWithNewInstanceUserTest.java b/src/test/java/com/android/tools/r8/ir/optimize/staticizer/CompanionClassWithNewInstanceUserTest.java
index 74dc3f4..76bee19 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/staticizer/CompanionClassWithNewInstanceUserTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/staticizer/CompanionClassWithNewInstanceUserTest.java
@@ -1,7 +1,7 @@
 package com.android.tools.r8.ir.optimize.staticizer;
 
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
-import static org.hamcrest.CoreMatchers.not;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isStatic;
 import static org.hamcrest.MatcherAssert.assertThat;
 
 import com.android.tools.r8.NeverInline;
@@ -10,6 +10,7 @@
 import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
@@ -43,18 +44,16 @@
   }
 
   private void inspect(CodeInspector inspector) {
-    if (parameters.isCfRuntime()) {
-      // Class staticizer is disabled when generating class files.
-      assertThat(inspector.clazz(Companion.class), isPresent());
-    } else {
-      // The companion class has been removed.
-      assertThat(inspector.clazz(Companion.class), not(isPresent()));
+    ClassSubject hostClassSubject = inspector.clazz(CompanionHost.class);
+    assertThat(hostClassSubject, isPresent());
 
-      // The companion method has been moved to the companion host class.
-      ClassSubject hostClassSubject = inspector.clazz(CompanionHost.class);
-      assertThat(hostClassSubject, isPresent());
-      assertThat(hostClassSubject.uniqueMethodWithName("method"), isPresent());
-    }
+    // The companion class has been removed.
+    ClassSubject companionClassSubject = inspector.clazz(Companion.class);
+    assertThat(companionClassSubject, isPresent());
+
+    MethodSubject companionMethodSubject = companionClassSubject.uniqueMethodWithName("method");
+    assertThat(companionMethodSubject, isPresent());
+    assertThat(companionMethodSubject, isStatic());
   }
 
   static class TestClass {
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/uninstantiatedtypes/PrivateInstanceMethodCollisionTest.java b/src/test/java/com/android/tools/r8/ir/optimize/uninstantiatedtypes/PrivateInstanceMethodCollisionTest.java
index 58dc4e5..2c8e8ed 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/uninstantiatedtypes/PrivateInstanceMethodCollisionTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/uninstantiatedtypes/PrivateInstanceMethodCollisionTest.java
@@ -11,6 +11,7 @@
 import com.android.tools.r8.NeverClassInline;
 import com.android.tools.r8.NeverInline;
 import com.android.tools.r8.NoHorizontalClassMerging;
+import com.android.tools.r8.NoMethodStaticizing;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.utils.BooleanUtils;
@@ -64,6 +65,7 @@
         .enableInliningAnnotations()
         .enableNeverClassInliningAnnotations()
         .enableNoHorizontalClassMergingAnnotations()
+        .enableNoMethodStaticizingAnnotations()
         .minification(minification)
         .allowAccessModification(allowAccessModification)
         .setMinApi(parameters.getApiLevel())
@@ -113,11 +115,13 @@
   @NeverClassInline
   static class A {
     @NeverInline
+    @NoMethodStaticizing
     private void foo(B instantiated) {
       System.out.println("A#foo(" + instantiated + ")");
     }
 
     @NeverInline
+    @NoMethodStaticizing
     void foo(B instantiated, C uninstantiated) {
       System.out.println("A#foo(" + instantiated + ", Object)");
     }
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 472ec24..f8dd8d9 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
@@ -10,9 +10,11 @@
 
 import com.android.tools.r8.NeverInline;
 import com.android.tools.r8.NoHorizontalClassMerging;
+import com.android.tools.r8.NoMethodStaticizing;
 import com.android.tools.r8.NoVerticalClassMerging;
 import com.android.tools.r8.TestBase;
-import com.android.tools.r8.ToolHelper;
+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.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
@@ -20,20 +22,18 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
 import org.junit.runners.Parameterized.Parameters;
 
 @RunWith(Parameterized.class)
 public class VoidReturnTypeRewritingTest extends TestBase {
 
-  private final Backend backend;
+  @Parameter(0)
+  public TestParameters parameters;
 
-  @Parameters(name = "Backend: {0}")
-  public static Backend[] data() {
-    return ToolHelper.getBackends();
-  }
-
-  public VoidReturnTypeRewritingTest(Backend backend) {
-    this.backend = backend;
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
   }
 
   @Test
@@ -48,15 +48,17 @@
     testForJvm().addTestClasspath().run(TestClass.class).assertSuccessWithOutput(expected);
 
     CodeInspector inspector =
-        testForR8(backend)
+        testForR8(parameters.getBackend())
             .addInnerClasses(VoidReturnTypeRewritingTest.class)
             .addKeepMainRule(TestClass.class)
             .enableInliningAnnotations()
+            .enableNoMethodStaticizingAnnotations()
             .enableNoVerticalClassMergingAnnotations()
             .enableNoHorizontalClassMergingAnnotations()
             .addKeepRules("-dontobfuscate")
             .addOptionsModification(options -> options.enableClassInlining = false)
-            .run(TestClass.class)
+            .setMinApi(parameters.getApiLevel())
+            .run(parameters.getRuntime(), TestClass.class)
             .assertSuccessWithOutput(expected)
             .inspector();
 
@@ -116,6 +118,7 @@
     }
 
     @NeverInline
+    @NoMethodStaticizing
     public Uninstantiated createVirtual() {
       System.out.print("Factory.createVirtual()");
       return null;
@@ -127,6 +130,7 @@
 
     @Override
     @NeverInline
+    @NoMethodStaticizing
     public Uninstantiated createVirtual() {
       System.out.print("SubFactory.createVirtual()");
       return null;
@@ -138,6 +142,7 @@
 
     @Override
     @NeverInline
+    @NoMethodStaticizing
     public SubUninstantiated createVirtual() {
       System.out.print("SubSubFactory.createVirtual()");
       return null;
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/unusedarguments/CollisionWithLibraryMethodsTest.java b/src/test/java/com/android/tools/r8/ir/optimize/unusedarguments/CollisionWithLibraryMethodsTest.java
index e990830..9cdc693 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/unusedarguments/CollisionWithLibraryMethodsTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/unusedarguments/CollisionWithLibraryMethodsTest.java
@@ -11,6 +11,7 @@
 import com.android.tools.r8.NeverClassInline;
 import com.android.tools.r8.NeverInline;
 import com.android.tools.r8.NeverPropagateValue;
+import com.android.tools.r8.NoMethodStaticizing;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.utils.BooleanUtils;
@@ -50,6 +51,7 @@
         .addKeepMainRule(TestClass.class)
         .enableMemberValuePropagationAnnotations()
         .enableNeverClassInliningAnnotations()
+        .enableNoMethodStaticizingAnnotations()
         .enableInliningAnnotations()
         .minification(minification)
         .setMinApi(parameters.getApiLevel())
@@ -87,6 +89,7 @@
 
     @NeverInline
     @NeverPropagateValue
+    @NoMethodStaticizing
     public String toString(Object unused) {
       System.out.print("Hello ");
       return "world!";
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/unusedarguments/PrivateInstanceMethodCollisionTest.java b/src/test/java/com/android/tools/r8/ir/optimize/unusedarguments/PrivateInstanceMethodCollisionTest.java
index ed2d01d..abc1bd6 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/unusedarguments/PrivateInstanceMethodCollisionTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/unusedarguments/PrivateInstanceMethodCollisionTest.java
@@ -11,6 +11,7 @@
 import com.android.tools.r8.KeepConstantArguments;
 import com.android.tools.r8.NeverClassInline;
 import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NoMethodStaticizing;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.utils.BooleanUtils;
@@ -35,7 +36,7 @@
   @Parameters(name = "{0}, minification: {1}, allowaccessmodification: {2}")
   public static List<Object[]> data() {
     return buildParameters(
-        getTestParameters().withAllRuntimes().build(),
+        getTestParameters().withAllRuntimesAndApiLevels().build(),
         BooleanUtils.values(),
         BooleanUtils.values());
   }
@@ -64,9 +65,10 @@
         .enableConstantArgumentAnnotations()
         .enableInliningAnnotations()
         .enableNeverClassInliningAnnotations()
+        .enableNoMethodStaticizingAnnotations()
         .minification(minification)
         .allowAccessModification(allowAccessModification)
-        .setMinApi(parameters.getRuntime())
+        .setMinApi(parameters.getApiLevel())
         .compile()
         .inspect(this::verifyUnusedArgumentsRemovedAndNoCollisions)
         .run(parameters.getRuntime(), TestClass.class)
@@ -113,12 +115,14 @@
   static class A {
     @KeepConstantArguments
     @NeverInline
+    @NoMethodStaticizing
     private void foo(String used) {
       System.out.println("A#foo(" + used + ")");
     }
 
     @KeepConstantArguments
     @NeverInline
+    @NoMethodStaticizing
     void foo(String used, Object unused) {
       System.out.println("A#foo(" + used + ", Object)");
     }
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/unusedarguments/UnusedArgumentsCollisionTest.java b/src/test/java/com/android/tools/r8/ir/optimize/unusedarguments/UnusedArgumentsCollisionTest.java
index c59eb85..7527598 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/unusedarguments/UnusedArgumentsCollisionTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/unusedarguments/UnusedArgumentsCollisionTest.java
@@ -11,6 +11,7 @@
 
 import com.android.tools.r8.NeverClassInline;
 import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NoMethodStaticizing;
 import com.android.tools.r8.NoVerticalClassMerging;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
@@ -59,6 +60,7 @@
         .addKeepMainRule(TestClass.class)
         .enableInliningAnnotations()
         .enableNeverClassInliningAnnotations()
+        .enableNoMethodStaticizingAnnotations()
         .enableNoVerticalClassMergingAnnotations()
         .minification(minification)
         .setMinApi(parameters.getApiLevel())
@@ -119,6 +121,7 @@
   static class A {
 
     @NeverInline
+    @NoMethodStaticizing
     public void method1() {
       System.out.print("Hello");
     }
@@ -138,6 +141,7 @@
     }
 
     @NeverInline
+    @NoMethodStaticizing
     public void method2(Object unused) {
       System.out.println(" world");
     }
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/unusedarguments/UnusedArgumentsObjectTest.java b/src/test/java/com/android/tools/r8/ir/optimize/unusedarguments/UnusedArgumentsObjectTest.java
index c06823f..5497d76 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/unusedarguments/UnusedArgumentsObjectTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/unusedarguments/UnusedArgumentsObjectTest.java
@@ -8,6 +8,7 @@
 
 import com.android.tools.r8.NeverClassInline;
 import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NoMethodStaticizing;
 import com.android.tools.r8.R8FullTestBuilder;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
@@ -58,31 +59,37 @@
     }
 
     @NeverInline
+    @NoMethodStaticizing
     private Object privateMethod(Object a) {
       return a;
     }
 
     @NeverInline
+    @NoMethodStaticizing
     private Object privateMethod(Object a, Object b) {
       return a;
     }
 
     @NeverInline
+    @NoMethodStaticizing
     private Object privateMethod(Object a, Object b, Object c) {
       return a;
     }
 
     @NeverInline
+    @NoMethodStaticizing
     public Object publicMethod(Object a) {
       return a;
     }
 
     @NeverInline
+    @NoMethodStaticizing
     public Object publicMethod(Object a, Object b) {
       return a;
     }
 
     @NeverInline
+    @NoMethodStaticizing
     public Object publicMethod(Object a, Object b, Object c) {
       return a;
     }
@@ -106,7 +113,10 @@
 
   @Override
   public void configure(R8FullTestBuilder builder) {
-    builder.enableNeverClassInliningAnnotations().enableInliningAnnotations();
+    builder
+        .enableNeverClassInliningAnnotations()
+        .enableInliningAnnotations()
+        .enableNoMethodStaticizingAnnotations();
   }
 
   @Override
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/unusedarguments/UnusedReceiverInUnboxedEnumTest.java b/src/test/java/com/android/tools/r8/ir/optimize/unusedarguments/UnusedReceiverInUnboxedEnumTest.java
new file mode 100644
index 0000000..0218614
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/unusedarguments/UnusedReceiverInUnboxedEnumTest.java
@@ -0,0 +1,57 @@
+// Copyright (c) 2022, 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.unusedarguments;
+
+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 org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class UnusedReceiverInUnboxedEnumTest extends TestBase {
+
+  @Parameter(0)
+  public TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  @Test
+  public void test() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(getClass())
+        .addKeepMainRule(Main.class)
+        .addEnumUnboxingInspector(inspector -> inspector.assertUnboxed(MyEnum.class))
+        .enableInliningAnnotations()
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .run(parameters.getRuntime(), Main.class)
+        .assertFailureWithErrorThatThrows(NullPointerException.class);
+  }
+
+  static class Main {
+
+    public static void main(String[] args) {
+      MyEnum alwaysNull = System.currentTimeMillis() > 0 ? null : MyEnum.A;
+      alwaysNull.foo();
+    }
+  }
+
+  enum MyEnum {
+    A;
+
+    @NeverInline
+    void foo() {
+      System.out.println("Hello!");
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/unusedarguments/UnusedReceiverTest.java b/src/test/java/com/android/tools/r8/ir/optimize/unusedarguments/UnusedReceiverTest.java
new file mode 100644
index 0000000..175b2cb
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/unusedarguments/UnusedReceiverTest.java
@@ -0,0 +1,65 @@
+// Copyright (c) 2022, 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.unusedarguments;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isStatic;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class UnusedReceiverTest extends TestBase {
+
+  @Parameter(0)
+  public TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  @Test
+  public void test() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(getClass())
+        .addKeepMainRule(Main.class)
+        .enableInliningAnnotations()
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .inspect(
+            inspector -> {
+              ClassSubject mainClassSubject = inspector.clazz(Main.class);
+              assertThat(mainClassSubject, isPresent());
+
+              MethodSubject testMethodSubject = mainClassSubject.uniqueMethodWithName("test");
+              assertThat(testMethodSubject, isStatic());
+            })
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines("Hello world!");
+  }
+
+  static class Main {
+
+    public static void main(String[] args) {
+      new Main().test();
+    }
+
+    @NeverInline
+    void test() {
+      System.out.println("Hello world!");
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/regalloc/RegisterMoveSchedulerTest.java b/src/test/java/com/android/tools/r8/ir/regalloc/RegisterMoveSchedulerTest.java
index f3db67c..b05928d 100644
--- a/src/test/java/com/android/tools/r8/ir/regalloc/RegisterMoveSchedulerTest.java
+++ b/src/test/java/com/android/tools/r8/ir/regalloc/RegisterMoveSchedulerTest.java
@@ -19,10 +19,13 @@
 import com.android.tools.r8.ir.analysis.type.Nullability;
 import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.code.BasicBlock;
+import com.android.tools.r8.ir.code.BasicBlockIterator;
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.Instruction;
 import com.android.tools.r8.ir.code.InstructionListIterator;
+import com.android.tools.r8.ir.code.InvokeMethod;
 import com.android.tools.r8.ir.code.Move;
+import com.android.tools.r8.ir.code.Position;
 import com.android.tools.r8.ir.code.Value;
 import com.android.tools.r8.ir.code.ValueType;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
@@ -65,6 +68,16 @@
     }
 
     @Override
+    public InvokeMethod insertNullCheckInstruction(
+        AppView<?> appView,
+        IRCode code,
+        BasicBlockIterator blockIterator,
+        Value value,
+        Position position) {
+      throw new Unimplemented();
+    }
+
+    @Override
     public boolean replaceCurrentInstructionByNullCheckIfPossible(
         AppView<?> appView, ProgramMethod context) {
       throw new Unimplemented();
diff --git a/src/test/java/com/android/tools/r8/maindexlist/MainDexUnusedArgumentRewriteWithLensTest.java b/src/test/java/com/android/tools/r8/maindexlist/MainDexUnusedArgumentRewriteWithLensTest.java
index fea8bfa..ee3a60f 100644
--- a/src/test/java/com/android/tools/r8/maindexlist/MainDexUnusedArgumentRewriteWithLensTest.java
+++ b/src/test/java/com/android/tools/r8/maindexlist/MainDexUnusedArgumentRewriteWithLensTest.java
@@ -12,6 +12,7 @@
 
 import com.android.tools.r8.NeverClassInline;
 import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NoMethodStaticizing;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
@@ -49,6 +50,7 @@
         .addInnerClasses(getClass())
         .enableInliningAnnotations()
         .enableNeverClassInliningAnnotations()
+        .enableNoMethodStaticizingAnnotations()
         .setMinApi(parameters.getApiLevel())
         .addKeepClassRules(Dependency.class)
         .addMainDexRules(
@@ -101,6 +103,7 @@
 
     // Will be rewritten because it has an unused argument
     @NeverInline
+    @NoMethodStaticizing
     public void foo(Object obj, int argumentUnused) {
       B.foo(obj);
     }
diff --git a/src/test/java/com/android/tools/r8/memberrebinding/MemberRebindingInvokeSuperAbstractTest.java b/src/test/java/com/android/tools/r8/memberrebinding/MemberRebindingInvokeSuperAbstractTest.java
new file mode 100644
index 0000000..24c31c2
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/memberrebinding/MemberRebindingInvokeSuperAbstractTest.java
@@ -0,0 +1,108 @@
+// Copyright (c) 2022, 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.memberrebinding;
+
+import static com.android.tools.r8.apimodel.ApiModelingTestHelper.setMockApiLevelForClass;
+import static com.android.tools.r8.apimodel.ApiModelingTestHelper.setMockApiLevelForMethod;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+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.apimodel.ApiModelingTestHelper;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.codeinspector.CodeMatchers;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import com.google.common.collect.ImmutableList;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+// This is a reproduction of b/213581039.
+@RunWith(Parameterized.class)
+public class MemberRebindingInvokeSuperAbstractTest extends TestBase {
+
+  @Parameter() public TestParameters parameters;
+
+  private final List<Class<?>> libraryClasses =
+      ImmutableList.of(LibraryBase.class, LibrarySub.class, LibrarySubSub.class);
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    testForR8(parameters.getBackend())
+        .addLibraryClasses(libraryClasses)
+        .addDefaultRuntimeLibrary(parameters)
+        .addProgramClasses(Main.class)
+        .setMinApi(parameters.getApiLevel())
+        .addKeepMainRule(Main.class)
+        .enableInliningAnnotations()
+        .apply(ApiModelingTestHelper::enableApiCallerIdentification)
+        .apply(ApiModelingTestHelper::disableOutliningAndStubbing)
+        .apply(
+            builder ->
+                libraryClasses.forEach(clazz -> setMockApiLevelForClass(clazz, AndroidApiLevel.B)))
+        .apply(
+            setMockApiLevelForMethod(
+                LibraryBase.class.getDeclaredMethod("getSystemService"), AndroidApiLevel.B))
+        .apply(
+            setMockApiLevelForMethod(
+                LibrarySub.class.getDeclaredMethod("getSystemService"), AndroidApiLevel.B))
+        .compile()
+        .addRunClasspathClasses(libraryClasses)
+        .inspect(
+            inspector -> {
+              MethodSubject getSystemService =
+                  inspector.clazz(Main.class).uniqueMethodWithName("getSystemService");
+              assertThat(getSystemService, isPresent());
+              // We should never rebind this call to LibraryBase::getSystemService since this can
+              // cause errors when verifying the code on a device where the image has a definition
+              // but it is abstract. For more information, see b/213581039.
+              assertThat(
+                  getSystemService,
+                  CodeMatchers.invokesMethodWithHolderAndName(
+                      typeName(LibrarySub.class), "getSystemService"));
+            })
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines("LibrarySub::getSystemService");
+  }
+
+  public abstract static class LibraryBase {
+
+    public abstract void getSystemService();
+  }
+
+  public static class LibrarySub extends LibraryBase {
+
+    @Override
+    public void getSystemService() {
+      System.out.println("LibrarySub::getSystemService");
+    }
+  }
+
+  public static class LibrarySubSub extends LibrarySub {}
+
+  public static class Main extends LibrarySubSub {
+
+    public static void main(String[] args) {
+      new Main().getSystemService();
+    }
+
+    @Override
+    @NeverInline
+    public void getSystemService() {
+      super.getSystemService();
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/naming/IdentifierNameStringReprocessingTest.java b/src/test/java/com/android/tools/r8/naming/IdentifierNameStringReprocessingTest.java
new file mode 100644
index 0000000..4041e32
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/naming/IdentifierNameStringReprocessingTest.java
@@ -0,0 +1,72 @@
+// Copyright (c) 2022, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.naming;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresentAndRenamed;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.NeverPropagateValue;
+import com.android.tools.r8.ReprocessClassInitializer;
+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 org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class IdentifierNameStringReprocessingTest extends TestBase {
+
+  @Parameter(0)
+  public TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  @Test
+  public void test() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(getClass())
+        .addKeepMainRule(Main.class)
+        .addKeepClassRulesWithAllowObfuscation(A.class)
+        .addKeepRules(
+            "-identifiernamestring class " + Main.class.getTypeName() + " {",
+            "  static java.lang.String f;",
+            "}")
+        .enableMemberValuePropagationAnnotations()
+        .enableReprocessClassInitializerAnnotations()
+        .setMinApi(parameters.getApiLevel())
+        .run(parameters.getRuntime(), Main.class)
+        .apply(
+            runResult -> {
+              ClassSubject aClassSubject = runResult.inspector().clazz(A.class);
+              assertThat(aClassSubject, isPresentAndRenamed());
+              runResult.assertSuccessWithOutputLines(aClassSubject.getFinalName());
+            });
+  }
+
+  @ReprocessClassInitializer
+  static class Main {
+
+    @NeverPropagateValue static String f;
+
+    static {
+      // Prevent class initializer defaults optimization.
+      System.out.print("");
+      f = "com.android.tools.r8.naming.IdentifierNameStringReprocessingTest$A";
+    }
+
+    public static void main(String[] args) {
+      System.out.println(f);
+    }
+  }
+
+  static class A {}
+}
diff --git a/src/test/java/com/android/tools/r8/naming/applymapping/ApplyMappingAfterDevirtualizationTest.java b/src/test/java/com/android/tools/r8/naming/applymapping/ApplyMappingAfterDevirtualizationTest.java
index 670b6ae..99554bf 100644
--- a/src/test/java/com/android/tools/r8/naming/applymapping/ApplyMappingAfterDevirtualizationTest.java
+++ b/src/test/java/com/android/tools/r8/naming/applymapping/ApplyMappingAfterDevirtualizationTest.java
@@ -4,18 +4,24 @@
 package com.android.tools.r8.naming.applymapping;
 
 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;
 import static org.hamcrest.CoreMatchers.not;
 import static org.hamcrest.MatcherAssert.assertThat;
 
+import com.android.tools.r8.NoMethodStaticizing;
 import com.android.tools.r8.R8TestCompileResult;
 import com.android.tools.r8.TestBase;
-import com.android.tools.r8.ToolHelper;
+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 org.junit.Assume;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
 
 @RunWith(Parameterized.class)
 public class ApplyMappingAfterDevirtualizationTest extends TestBase {
@@ -35,19 +41,22 @@
   public static class LibClassA implements LibInterfaceA {
 
     @Override
+    @NoMethodStaticizing
     public void foo() {
       System.out.println("LibClassA::foo");
     }
   }
 
-  // LibClassB should be devirtualized into LibInterfaceB
+  // LibInterfaceB should be devirtualized into LibClassB
   public static class LibClassB implements LibInterfaceB {
 
     @Override
+    @NoMethodStaticizing
     public void foo() {
       System.out.println("LibClassB::foo");
     }
 
+    @NoMethodStaticizing
     public void bar() {
       System.out.println("LibClassB::bar");
     }
@@ -71,92 +80,94 @@
     }
   }
 
-  private static final Class<?>[] LIBRARY_CLASSES = {
+  private static final Class<?>[] CLASSPATH_CLASSES = {
     LibInterfaceA.class, LibInterfaceB.class, LibClassA.class, LibClassB.class
   };
 
   private static final Class<?>[] PROGRAM_CLASSES = {ProgramClass.class};
 
-  private Backend backend;
+  @Parameter(0)
+  public TestParameters parameters;
 
-  @Parameterized.Parameters(name = "{0}")
-  public static Backend[] data() {
-    return ToolHelper.getBackends();
-  }
-
-  public ApplyMappingAfterDevirtualizationTest(Backend backend) {
-    this.backend = backend;
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
   }
 
   @Test
   public void runOnJvm() throws Throwable {
-    Assume.assumeTrue(backend == Backend.CF);
+    Assume.assumeTrue(parameters.isCfRuntime());
     testForJvm()
-        .addProgramClasses(LIBRARY_CLASSES)
+        .addProgramClasses(CLASSPATH_CLASSES)
         .addProgramClasses(PROGRAM_CLASSES)
-        .run(ProgramClass.class)
+        .run(parameters.getRuntime(), ProgramClass.class)
         .assertSuccessWithOutput(EXPECTED_OUTPUT);
   }
 
   @Test
   public void devirtualizingNoRenamingOfOverriddenNotKeptInterfaceMethods() throws Exception {
     R8TestCompileResult libraryResult =
-        testForR8(backend)
-            .addProgramClasses(LIBRARY_CLASSES)
+        testForR8(parameters.getBackend())
+            .addProgramClasses(CLASSPATH_CLASSES)
             .addKeepClassAndMembersRulesWithAllowObfuscation(LibClassA.class)
             .addKeepMainRule(LibClassB.class)
+            .addKeepClassAndDefaultConstructor(LibClassB.class)
             .addOptionsModification(options -> options.inlinerOptions().enableInlining = false)
+            .enableNoMethodStaticizingAnnotations()
+            .setMinApi(parameters.getApiLevel())
             .compile();
 
     CodeInspector inspector = libraryResult.inspector();
-    assertThat(inspector.clazz(LibClassA.class), isPresent());
-    assertThat(inspector.clazz(LibClassB.class), isPresent());
+    assertThat(inspector.clazz(LibClassA.class), isPresentAndRenamed());
+    assertThat(inspector.clazz(LibClassB.class), isPresentAndNotRenamed());
 
     // LibInterfaceX should have been moved into LibClassX.
     assertThat(inspector.clazz(LibInterfaceA.class), not(isPresent()));
     assertThat(inspector.clazz(LibInterfaceB.class), not(isPresent()));
 
-    testForR8(backend)
+    testForR8(parameters.getBackend())
         .noTreeShaking()
         .noMinification()
         .addProgramClasses(PROGRAM_CLASSES)
         .addApplyMapping(libraryResult.getProguardMap())
-        .addLibraryClasses(LIBRARY_CLASSES)
-        .addLibraryFiles(runtimeJar(backend))
+        .addClasspathClasses(CLASSPATH_CLASSES)
+        .setMinApi(parameters.getApiLevel())
         .compile()
         .addRunClasspathFiles(libraryResult.writeToZip())
-        .run(ProgramClass.class)
+        .run(parameters.getRuntime(), ProgramClass.class)
         .assertSuccessWithOutput(EXPECTED_OUTPUT);
   }
 
   @Test
   public void devirtualizingNoRenamingOfOverriddenKeptInterfaceMethods() throws Exception {
     R8TestCompileResult libraryResult =
-        testForR8(backend)
-            .addProgramClasses(LIBRARY_CLASSES)
-            .addKeepClassAndMembersRulesWithAllowObfuscation(LibClassA.class, LibInterfaceA.class)
-            .addKeepMainRule(LibClassB.class)
+        testForR8(parameters.getBackend())
+            .addProgramClasses(CLASSPATH_CLASSES)
+            .addKeepClassAndMembersRulesWithAllowObfuscation(
+                LibClassA.class, LibClassB.class, LibInterfaceA.class)
             .addOptionsModification(options -> options.inlinerOptions().enableInlining = false)
+            .enableNoMethodStaticizingAnnotations()
+            .setMinApi(parameters.getApiLevel())
             .compile();
 
     CodeInspector inspector = libraryResult.inspector();
-    assertThat(inspector.clazz(LibClassA.class), isPresent());
-    assertThat(inspector.clazz(LibClassB.class), isPresent());
+    assertThat(inspector.clazz(LibClassA.class), isPresentAndRenamed());
+    assertThat(inspector.clazz(LibClassB.class), isPresentAndRenamed());
 
     // LibInterfaceA is now kept.
-    assertThat(inspector.clazz(LibInterfaceA.class), isPresent());
+    assertThat(inspector.clazz(LibInterfaceA.class), isPresentAndRenamed());
     assertThat(inspector.clazz(LibInterfaceB.class), not(isPresent()));
 
-    testForR8(backend)
+    testForR8(parameters.getBackend())
         .noTreeShaking()
         .noMinification()
         .addProgramClasses(PROGRAM_CLASSES)
         .addApplyMapping(libraryResult.getProguardMap())
-        .addLibraryClasses(LIBRARY_CLASSES)
-        .addLibraryFiles(runtimeJar(backend))
+        .addClasspathClasses(CLASSPATH_CLASSES)
+        .setMinApi(parameters.getApiLevel())
         .compile()
         .addRunClasspathFiles(libraryResult.writeToZip())
-        .run(ProgramClass.class)
+        .run(parameters.getRuntime(), ProgramClass.class)
         .assertSuccessWithOutput(EXPECTED_OUTPUT);
   }
 }
diff --git a/src/test/java/com/android/tools/r8/naming/applymapping/ApplyMappingInterfaceInvokeTest.java b/src/test/java/com/android/tools/r8/naming/applymapping/ApplyMappingInterfaceInvokeTest.java
index a1ef87f..b4b6807 100644
--- a/src/test/java/com/android/tools/r8/naming/applymapping/ApplyMappingInterfaceInvokeTest.java
+++ b/src/test/java/com/android/tools/r8/naming/applymapping/ApplyMappingInterfaceInvokeTest.java
@@ -4,13 +4,10 @@
 
 package com.android.tools.r8.naming.applymapping;
 
-import com.android.tools.r8.CompilationFailedException;
 import com.android.tools.r8.R8TestCompileResult;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
-import java.io.IOException;
-import java.util.concurrent.ExecutionException;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
@@ -57,7 +54,7 @@
 
   @Parameters(name = "{0}")
   public static TestParametersCollection data() {
-    return getTestParameters().withAllRuntimes().build();
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
   }
 
   public ApplyMappingInterfaceInvokeTest(TestParameters parameters) {
@@ -65,14 +62,13 @@
   }
 
   @Test
-  public void testInvokeVirtual()
-      throws IOException, CompilationFailedException, ExecutionException {
+  public void testInvokeVirtual() throws Exception {
     Class<?>[] classPathClasses = {I.class, A.class, B.class, C.class};
     R8TestCompileResult libraryResult =
         testForR8(parameters.getBackend())
             .addProgramClasses(classPathClasses)
             .addKeepAllClassesRuleWithAllowObfuscation()
-            .setMinApi(parameters.getRuntime())
+            .setMinApi(parameters.getApiLevel())
             .compile();
     testForR8(parameters.getBackend())
         .addClasspathClasses(classPathClasses)
@@ -80,7 +76,7 @@
         .noMinification()
         .noTreeShaking()
         .addApplyMapping(libraryResult.getProguardMap())
-        .setMinApi(parameters.getRuntime())
+        .setMinApi(parameters.getApiLevel())
         .compile()
         .addRunClasspathFiles(libraryResult.writeToZip())
         .run(parameters.getRuntime(), TestApp.class)
diff --git a/src/test/java/com/android/tools/r8/naming/retrace/RetraceInlineeWithNullCheckFollowingImplicitReceiverNullCheckTest.java b/src/test/java/com/android/tools/r8/naming/retrace/RetraceInlineeWithNullCheckFollowingImplicitReceiverNullCheckTest.java
new file mode 100644
index 0000000..b5cd101
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/naming/retrace/RetraceInlineeWithNullCheckFollowingImplicitReceiverNullCheckTest.java
@@ -0,0 +1,95 @@
+// Copyright (c) 2022, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.naming.retrace;
+
+import static com.android.tools.r8.naming.retrace.StackTrace.isSame;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NoMethodStaticizing;
+import com.android.tools.r8.SingleTestRunResult;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class RetraceInlineeWithNullCheckFollowingImplicitReceiverNullCheckTest extends TestBase {
+
+  @Parameter(0)
+  public TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimes().withAllApiLevelsAlsoForCf().build();
+  }
+
+  public StackTrace expectedStackTrace;
+  public StackTrace unexpectedStackTrace;
+
+  @Before
+  public void setup() throws Exception {
+    expectedStackTrace = getStackTrace();
+    unexpectedStackTrace = getStackTrace("foo");
+  }
+
+  private StackTrace getStackTrace(String... args) throws Exception {
+    SingleTestRunResult<?> runResult =
+        testForRuntime(parameters)
+            .addProgramClasses(Caller.class, Foo.class)
+            .run(parameters.getRuntime(), Caller.class, args);
+    return parameters.isCfRuntime()
+        ? runResult.map(StackTrace::extractFromJvm)
+        : StackTrace.extractFromArt(runResult.getStdErr(), parameters.asDexRuntime().getVm());
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(getClass())
+        .addKeepMainRule(Caller.class)
+        .addKeepAttributeLineNumberTable()
+        .addKeepAttributeSourceFile()
+        .enableExperimentalMapFileVersion()
+        .enableInliningAnnotations()
+        .enableNoMethodStaticizingAnnotations()
+        .setMinApi(parameters.getApiLevel())
+        .run(parameters.getRuntime(), Caller.class)
+        .assertFailureWithErrorThatThrows(NullPointerException.class)
+        // TODO(b/214377135): Should retrace to expectedStackTrace.
+        .inspectStackTrace(
+            (stackTrace, codeInspector) -> assertThat(stackTrace, isSame(unexpectedStackTrace)));
+  }
+
+  static class Foo {
+
+    @NeverInline
+    @NoMethodStaticizing
+    void checkNull() {
+      System.out.println("Hello, world!");
+    }
+
+    void inlinable(Foo foo) {
+      checkNull();
+      foo.checkNull();
+    }
+  }
+
+  static class Caller {
+
+    @NeverInline
+    static void caller(Foo f) {
+      f.inlinable(null);
+    }
+
+    public static void main(String[] args) {
+      caller(args.length == 0 ? new Foo() : null);
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/naming/retrace/RetraceInlineeWithNullCheckSequenceTest.java b/src/test/java/com/android/tools/r8/naming/retrace/RetraceInlineeWithNullCheckSequenceTest.java
index 7c0f956..bf8856b 100644
--- a/src/test/java/com/android/tools/r8/naming/retrace/RetraceInlineeWithNullCheckSequenceTest.java
+++ b/src/test/java/com/android/tools/r8/naming/retrace/RetraceInlineeWithNullCheckSequenceTest.java
@@ -4,13 +4,16 @@
 package com.android.tools.r8.naming.retrace;
 
 import static com.android.tools.r8.naming.retrace.StackTrace.isSame;
+import static com.android.tools.r8.naming.retrace.StackTrace.isSameExceptForLineNumbers;
 import static org.hamcrest.MatcherAssert.assertThat;
 
 import com.android.tools.r8.NeverInline;
 import com.android.tools.r8.SingleTestRunResult;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.naming.retrace.StackTrace.StackTraceLine;
 import java.util.List;
+import java.util.Objects;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -61,7 +64,27 @@
         .assertFailureWithErrorThatThrows(NullPointerException.class)
         .inspectStackTrace(
             (stackTrace, codeInspector) -> {
-              assertThat(stackTrace, isSame(expectedStackTrace));
+              if (canUseJavaUtilObjectsRequireNonNull(parameters)) {
+                StackTrace requireNonNullFrame =
+                    StackTrace.builder().add(stackTrace.get(0)).build();
+                assertThat(
+                    requireNonNullFrame,
+                    isSameExceptForLineNumbers(
+                        StackTrace.builder()
+                            .add(
+                                StackTraceLine.builder()
+                                    .setClassName(Objects.class.getTypeName())
+                                    .setMethodName("requireNonNull")
+                                    .setFileName("Objects.java")
+                                    .build())
+                            .build()));
+
+                StackTrace stackTraceWithoutRequireNonNull =
+                    StackTrace.builder().add(stackTrace).remove(0).build();
+                assertThat(stackTraceWithoutRequireNonNull, isSame(expectedStackTrace));
+              } else {
+                assertThat(stackTrace, isSame(expectedStackTrace));
+              }
             });
   }
 
diff --git a/src/test/java/com/android/tools/r8/naming/retrace/RetraceInlineeWithNullCheckTest.java b/src/test/java/com/android/tools/r8/naming/retrace/RetraceInlineeWithNullCheckTest.java
index 81c2200..13e3e7f 100644
--- a/src/test/java/com/android/tools/r8/naming/retrace/RetraceInlineeWithNullCheckTest.java
+++ b/src/test/java/com/android/tools/r8/naming/retrace/RetraceInlineeWithNullCheckTest.java
@@ -4,6 +4,7 @@
 package com.android.tools.r8.naming.retrace;
 
 import static com.android.tools.r8.naming.retrace.StackTrace.isSame;
+import static com.android.tools.r8.naming.retrace.StackTrace.isSameExceptForLineNumbers;
 import static org.hamcrest.MatcherAssert.assertThat;
 
 import com.android.tools.r8.NeverInline;
@@ -13,8 +14,10 @@
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestBuilder;
 import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.naming.retrace.StackTrace.StackTraceLine;
 import com.android.tools.r8.utils.BooleanUtils;
 import java.util.List;
+import java.util.Objects;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -80,7 +83,27 @@
         .assertFailureWithErrorThatThrows(NullPointerException.class)
         .inspectStackTrace(
             (stackTrace, codeInspector) -> {
-              assertThat(stackTrace, isSame(expectedStackTrace));
+              if (throwReceiverNpe && canUseJavaUtilObjectsRequireNonNull(parameters)) {
+                StackTrace requireNonNullFrame =
+                    StackTrace.builder().add(stackTrace.get(0)).build();
+                assertThat(
+                    requireNonNullFrame,
+                    isSameExceptForLineNumbers(
+                        StackTrace.builder()
+                            .add(
+                                StackTraceLine.builder()
+                                    .setClassName(Objects.class.getTypeName())
+                                    .setMethodName("requireNonNull")
+                                    .setFileName("Objects.java")
+                                    .build())
+                            .build()));
+
+                StackTrace stackTraceWithoutRequireNonNull =
+                    StackTrace.builder().add(stackTrace).remove(0).build();
+                assertThat(stackTraceWithoutRequireNonNull, isSame(expectedStackTrace));
+              } else {
+                assertThat(stackTrace, isSame(expectedStackTrace));
+              }
             });
   }
 
diff --git a/src/test/java/com/android/tools/r8/naming/retrace/StackTrace.java b/src/test/java/com/android/tools/r8/naming/retrace/StackTrace.java
index aea2a25..f4158a1 100644
--- a/src/test/java/com/android/tools/r8/naming/retrace/StackTrace.java
+++ b/src/test/java/com/android/tools/r8/naming/retrace/StackTrace.java
@@ -76,6 +76,11 @@
       return this;
     }
 
+    public Builder remove(int i) {
+      stackTraceLines.remove(i);
+      return this;
+    }
+
     public Builder applyIf(boolean condition, Consumer<Builder> fn) {
       if (condition) {
         fn.accept(this);
diff --git a/src/test/java/com/android/tools/r8/optimize/argumentpropagation/CollisionWithLibraryMethodAfterConstantParameterRemovalTest.java b/src/test/java/com/android/tools/r8/optimize/argumentpropagation/CollisionWithLibraryMethodAfterConstantParameterRemovalTest.java
index a0c58ac..a8f51d2 100644
--- a/src/test/java/com/android/tools/r8/optimize/argumentpropagation/CollisionWithLibraryMethodAfterConstantParameterRemovalTest.java
+++ b/src/test/java/com/android/tools/r8/optimize/argumentpropagation/CollisionWithLibraryMethodAfterConstantParameterRemovalTest.java
@@ -11,6 +11,7 @@
 
 import com.android.tools.r8.NeverClassInline;
 import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NoMethodStaticizing;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
@@ -41,6 +42,7 @@
         .addKeepMainRule(Main.class)
         .enableInliningAnnotations()
         .enableNeverClassInliningAnnotations()
+        .enableNoMethodStaticizingAnnotations()
         .setMinApi(parameters.getApiLevel())
         .compile()
         .inspect(
@@ -77,6 +79,7 @@
   static class A {
 
     @NeverInline
+    @NoMethodStaticizing
     public String toString(String whichOne) {
       return System.currentTimeMillis() > 0 ? whichOne : null;
     }
diff --git a/src/test/java/com/android/tools/r8/optimize/argumentpropagation/CompanionConstructorShakingTest.java b/src/test/java/com/android/tools/r8/optimize/argumentpropagation/CompanionConstructorShakingTest.java
new file mode 100644
index 0000000..f5fb4f5
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/optimize/argumentpropagation/CompanionConstructorShakingTest.java
@@ -0,0 +1,87 @@
+// Copyright (c) 2022, 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.optimize.argumentpropagation;
+
+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.isStatic;
+import static junit.framework.TestCase.assertEquals;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NoHorizontalClassMerging;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class CompanionConstructorShakingTest extends TestBase {
+
+  @Parameter(0)
+  public TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  @Test
+  public void test() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(getClass())
+        .addKeepMainRule(Main.class)
+        .addOptionsModification(options -> options.enableClassStaticizer = false)
+        .enableInliningAnnotations()
+        .enableNeverClassInliningAnnotations()
+        .enableNoHorizontalClassMergingAnnotations()
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .inspect(
+            inspector -> {
+              ClassSubject hostClassSubject = inspector.clazz(Host.class);
+              assertThat(hostClassSubject, isAbsent());
+
+              ClassSubject companionClassSubject = inspector.clazz(Host.Companion.class);
+              assertThat(companionClassSubject, isPresent());
+              assertEquals(1, companionClassSubject.allMethods().size());
+
+              MethodSubject greetMethodSubject =
+                  companionClassSubject.uniqueMethodWithName("greet");
+              assertThat(greetMethodSubject, isStatic());
+            })
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines("Hello world!");
+  }
+
+  static class Main {
+
+    public static void main(String[] args) {
+      Host.companion.greet();
+    }
+  }
+
+  static class Host {
+
+    static final Companion companion = new Companion();
+
+    @NeverClassInline
+    @NoHorizontalClassMerging
+    static class Companion {
+
+      @NeverInline
+      void greet() {
+        System.out.println("Hello world!");
+      }
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/optimize/finalize/FinalizeVirtualMethodTest.java b/src/test/java/com/android/tools/r8/optimize/finalize/FinalizeVirtualMethodTest.java
index b8f5492..115b180 100644
--- a/src/test/java/com/android/tools/r8/optimize/finalize/FinalizeVirtualMethodTest.java
+++ b/src/test/java/com/android/tools/r8/optimize/finalize/FinalizeVirtualMethodTest.java
@@ -11,6 +11,7 @@
 
 import com.android.tools.r8.NeverClassInline;
 import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NoMethodStaticizing;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
@@ -39,6 +40,7 @@
         .addKeepClassAndMembersRules(Main.class)
         .enableInliningAnnotations()
         .enableNeverClassInliningAnnotations()
+        .enableNoMethodStaticizingAnnotations()
         .setMinApi(parameters.getApiLevel())
         .compile()
         .inspect(
@@ -63,6 +65,7 @@
 
     // Should be made final.
     @NeverInline
+    @NoMethodStaticizing
     public void m() {
       System.out.println("A.m()");
     }
diff --git a/src/test/java/com/android/tools/r8/regress/Regress214340258.java b/src/test/java/com/android/tools/r8/regress/Regress214340258.java
new file mode 100644
index 0000000..bce5a5b
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/regress/Regress214340258.java
@@ -0,0 +1,105 @@
+// Copyright (c) 2022, 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.regress;
+
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.R8TestRunResult;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.TestRuntime.CfRuntime;
+import com.android.tools.r8.dex.Constants;
+import com.android.tools.r8.utils.codeinspector.FoundClassSubject;
+import com.android.tools.r8.utils.codeinspector.FoundMethodSubject;
+import com.android.tools.r8.utils.codeinspector.InstructionSubject;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.ExecutionException;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class Regress214340258 extends TestBase {
+  // Generate this many classes to not overflow instruction limit.
+  static final int NUMBER_OF_FILES = 50;
+
+  private final TestParameters parameters;
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withDexRuntimes().withAllApiLevels().build();
+  }
+
+  public Regress214340258(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    Path compiledJumbo = getZipWithJumboString();
+    R8TestRunResult r8TestRunResult =
+        testForR8(parameters.getBackend())
+            .addDontOptimize()
+            .addKeepAllClassesRule()
+            .addProgramFiles(compiledJumbo)
+            .setMinApi(parameters.getApiLevel())
+            .run(parameters.getRuntime(), "TestClass0");
+    r8TestRunResult.assertSuccessWithOutputLines("foobar");
+    assertTrue(hasJumboString(r8TestRunResult));
+  }
+
+  private boolean hasJumboString(R8TestRunResult r8TestRunResult)
+      throws IOException, ExecutionException {
+    for (FoundClassSubject classSubject : r8TestRunResult.inspector().allClasses()) {
+      for (FoundMethodSubject foundMethodSubject : classSubject.allMethods()) {
+        for (InstructionSubject instruction : foundMethodSubject.instructions()) {
+          if (instruction.isJumboString()) {
+            return true;
+          }
+        }
+      }
+    }
+    return false;
+  }
+
+  public Path getZipWithJumboString() throws IOException {
+    List<Path> javaFiles = new ArrayList<>();
+    for (int i = 0; i < NUMBER_OF_FILES; i++) {
+      String name = "TestClass" + i;
+      Path file = temp.newFile(name + ".java").toPath();
+      Files.write(file, getClassWithManyStrings(name, i).getBytes(StandardCharsets.UTF_8));
+      javaFiles.add(file);
+    }
+    Path compiledJumbo = javac(CfRuntime.getCheckedInJdk9()).addSourceFiles(javaFiles).compile();
+    return compiledJumbo;
+  }
+
+  private String getClassWithManyStrings(String className, int index) {
+    String file =
+        ""
+            + "public class "
+            + className
+            + " {\n"
+            + "  public static void use(String s) { }\n"
+            + "\n"
+            + "  public static void main(String[] args) {\n"
+            + "    String s = \"foobar\";\n";
+
+    StringBuilder builder = new StringBuilder();
+    for (int i = 0; i < Constants.MAX_NON_JUMBO_INDEX / NUMBER_OF_FILES; i++) {
+      builder.append("    s = \"foobar" + i + "_" + index + "\";\n");
+      builder.append("    System.getenv(s);\n");
+    }
+    file += builder.toString();
+
+    file += "" + "    s = \"foobar\";\n" + "    System.out.println(s);\n" + "  }\n" + "}";
+    return file;
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/regress/b69825683/Regress69825683Test.java b/src/test/java/com/android/tools/r8/regress/b69825683/Regress69825683Test.java
index 01d1c30..9a81a6a 100644
--- a/src/test/java/com/android/tools/r8/regress/b69825683/Regress69825683Test.java
+++ b/src/test/java/com/android/tools/r8/regress/b69825683/Regress69825683Test.java
@@ -6,6 +6,7 @@
 
 import static org.hamcrest.CoreMatchers.startsWith;
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
 
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
@@ -92,13 +93,10 @@
             .inspector();
 
     List<FoundClassSubject> classes = inspector.allClasses();
-
-    // The synthetic class is still present when generating class files.
-    assertEquals(parameters.isCfRuntime() ? 3 : 2, classes.size());
-    assertEquals(
-        parameters.isCfRuntime(),
+    assertEquals(2, classes.size());
+    assertTrue(
         classes.stream()
             .map(FoundClassSubject::getOriginalName)
-            .anyMatch(name -> name.endsWith("$1")));
+            .noneMatch(name -> name.endsWith("$1")));
   }
 }
diff --git a/src/test/java/com/android/tools/r8/resolution/access/NestStaticMethodAccessTest.java b/src/test/java/com/android/tools/r8/resolution/access/NestStaticMethodAccessTest.java
index 760215a..cbbfa70 100644
--- a/src/test/java/com/android/tools/r8/resolution/access/NestStaticMethodAccessTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/access/NestStaticMethodAccessTest.java
@@ -7,6 +7,7 @@
 import static org.hamcrest.core.StringContains.containsString;
 import static org.junit.Assert.assertEquals;
 
+import com.android.tools.r8.NoHorizontalClassMerging;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestRunResult;
@@ -64,7 +65,7 @@
   public void testResolutionAccess() throws Exception {
     AppView<AppInfoWithLiveness> appView =
         computeAppViewWithLiveness(
-            buildClasses(getClasses())
+            buildClassesWithTestingAnnotations(getClasses())
                 .addClassProgramData(getTransformedClasses())
                 .addLibraryFile(parameters.getDefaultRuntimeLibrary())
                 .build(),
@@ -91,6 +92,7 @@
     testForR8(parameters.getBackend())
         .addProgramClasses(getClasses())
         .addProgramClassFileData(getTransformedClasses())
+        .enableNoHorizontalClassMergingAnnotations()
         .setMinApi(parameters.getApiLevel())
         .addKeepMainRule(Main.class)
         .run(parameters.getRuntime(), Main.class)
@@ -114,12 +116,14 @@
     return transformer(clazz).setNest(clazz);
   }
 
+  @NoHorizontalClassMerging
   static class A {
     /* will be private */ static void bar() {
       System.out.println("A::bar");
     }
   }
 
+  @NoHorizontalClassMerging
   static class B {
     public void foo() {
       // Static invoke to private method.
diff --git a/src/test/java/com/android/tools/r8/shaking/EventuallyNonTargetedMethodTest.java b/src/test/java/com/android/tools/r8/shaking/EventuallyNonTargetedMethodTest.java
index 24d4436..cb750b5 100644
--- a/src/test/java/com/android/tools/r8/shaking/EventuallyNonTargetedMethodTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/EventuallyNonTargetedMethodTest.java
@@ -9,6 +9,7 @@
 import com.android.tools.r8.NeverClassInline;
 import com.android.tools.r8.NeverInline;
 import com.android.tools.r8.NoHorizontalClassMerging;
+import com.android.tools.r8.NoMethodStaticizing;
 import com.android.tools.r8.NoVerticalClassMerging;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
@@ -19,27 +20,26 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
 
 @RunWith(Parameterized.class)
 public class EventuallyNonTargetedMethodTest extends TestBase {
 
   static final String EXPECTED = StringUtils.lines("A::foo", "C::bar");
 
-  private final TestParameters parameters;
+  @Parameter(0)
+  public TestParameters parameters;
 
   @Parameterized.Parameters(name = "{0}")
   public static TestParametersCollection data() {
     return getTestParameters().withAllRuntimesAndApiLevels().build();
   }
 
-  public EventuallyNonTargetedMethodTest(TestParameters parameters) {
-    this.parameters = parameters;
-  }
-
   @Test
   public void test() throws Exception {
     testForR8(parameters.getBackend())
         .enableInliningAnnotations()
+        .enableNoMethodStaticizingAnnotations()
         .enableNoVerticalClassMergingAnnotations()
         .enableNoHorizontalClassMergingAnnotations()
         .enableNeverClassInliningAnnotations()
@@ -78,11 +78,13 @@
 
     // Non-targeted override.
     @Override
+    @NoMethodStaticizing
     public void foo() {
       System.out.println("C::foo");
     }
 
     @NeverInline
+    @NoMethodStaticizing
     public void bar() {
       System.out.println("C::bar");
     }
diff --git a/src/test/java/com/android/tools/r8/shaking/NonVirtualOverrideTest.java b/src/test/java/com/android/tools/r8/shaking/NonVirtualOverrideTest.java
index 7c9c834..163a63f 100644
--- a/src/test/java/com/android/tools/r8/shaking/NonVirtualOverrideTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/NonVirtualOverrideTest.java
@@ -8,16 +8,15 @@
 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;
-import static org.junit.Assert.assertEquals;
 
 import com.android.tools.r8.ByteDataView;
 import com.android.tools.r8.ClassFileConsumer.ArchiveConsumer;
 import com.android.tools.r8.R8TestCompileResult;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestRuntime.CfRuntime;
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.ToolHelper.DexVm.Version;
-import com.android.tools.r8.ToolHelper.ProcessResult;
 import com.android.tools.r8.ir.optimize.Inliner.Reason;
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.BooleanUtils;
@@ -93,50 +92,31 @@
       memoizeFunction(NonVirtualOverrideTest::compile);
 
   public static String getExpectedResult(boolean isOldVm) throws Exception {
-    if (isOldVm) {
-      return String.join(
-          System.lineSeparator(),
-          "In A.m1()",
-          "In A.m2()",
-          "In A.m3()",
-          "In A.m4()",
-          "In C.m1()",
-          "In A.m2()",
-          "In C.m3()",
-          "In A.m4()",
-          "In A.m1()", // With Java: Caught IllegalAccessError when calling B.m1()
-          "Caught IncompatibleClassChangeError when calling B.m3()",
-          "In C.m1()", // With Java: Caught IllegalAccessError when calling B.m1()
-          "Caught IncompatibleClassChangeError when calling B.m3()",
-          "In C.m1()",
-          "In C.m3()",
-          "");
-    } else {
-      Path referenceJar = staticTemp.getRoot().toPath().resolve("input.jar");
-      ArchiveConsumer inputConsumer = new ArchiveConsumer(referenceJar);
-      inputConsumer.accept(
-          ByteDataView.of(NonVirtualOverrideTestClassDump.dump()),
-          DescriptorUtils.javaTypeToDescriptor(NonVirtualOverrideTestClass.class.getName()),
-          null);
-      inputConsumer.accept(
-          ByteDataView.of(ADump.dump()),
-          DescriptorUtils.javaTypeToDescriptor(A.class.getName()),
-          null);
-      inputConsumer.accept(
-          ByteDataView.of(BDump.dump()),
-          DescriptorUtils.javaTypeToDescriptor(B.class.getName()),
-          null);
-      inputConsumer.accept(
-          ByteDataView.of(CDump.dump()),
-          DescriptorUtils.javaTypeToDescriptor(C.class.getName()),
-          null);
-      inputConsumer.finished(null);
+    Path referenceJar = staticTemp.getRoot().toPath().resolve("input.jar");
+    ArchiveConsumer inputConsumer = new ArchiveConsumer(referenceJar);
+    inputConsumer.accept(
+        ByteDataView.of(NonVirtualOverrideTestClassDump.dump()),
+        DescriptorUtils.javaTypeToDescriptor(NonVirtualOverrideTestClass.class.getName()),
+        null);
+    inputConsumer.accept(
+        ByteDataView.of(ADump.dump()),
+        DescriptorUtils.javaTypeToDescriptor(A.class.getName()),
+        null);
+    inputConsumer.accept(
+        ByteDataView.of(BDump.dump()),
+        DescriptorUtils.javaTypeToDescriptor(B.class.getName()),
+        null);
+    inputConsumer.accept(
+        ByteDataView.of(CDump.dump()),
+        DescriptorUtils.javaTypeToDescriptor(C.class.getName()),
+        null);
+    inputConsumer.finished(null);
 
-      ProcessResult javaResult =
-          ToolHelper.runJava(referenceJar, NonVirtualOverrideTestClass.class.getName());
-      assertEquals(javaResult.exitCode, 0);
-      return javaResult.stdout;
-    }
+    return testForJvm(getStaticTemp())
+        .addProgramFiles(referenceJar)
+        .run(CfRuntime.getDefaultCfRuntime(), NonVirtualOverrideTestClass.class)
+        .assertSuccess()
+        .getStdOut();
   }
 
   public static boolean isDexVmBetween5_1_1and7_0_0(TestParameters parameters) {
diff --git a/src/test/java/com/android/tools/r8/shaking/ServiceLoaderTest.java b/src/test/java/com/android/tools/r8/shaking/ServiceLoaderTest.java
index 51bd0cf..dda29d7 100644
--- a/src/test/java/com/android/tools/r8/shaking/ServiceLoaderTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/ServiceLoaderTest.java
@@ -15,6 +15,7 @@
 
 import com.android.tools.r8.DataEntryResource;
 import com.android.tools.r8.NeverPropagateValue;
+import com.android.tools.r8.NoMethodStaticizing;
 import com.android.tools.r8.R8TestRunResult;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
@@ -92,6 +93,7 @@
                 })
             .enableGraphInspector()
             .enableMemberValuePropagationAnnotations()
+            .enableNoMethodStaticizingAnnotations()
             .setMinApi(parameters.getApiLevel())
             .run(parameters.getRuntime(), TestClass.class)
             .assertSuccessWithOutput(expectedOutput);
@@ -189,6 +191,7 @@
                       new DataResourceConsumerForTesting(options.dataResourceConsumer);
                   options.dataResourceConsumer = dataResourceConsumer;
                 })
+            .enableNoMethodStaticizingAnnotations()
             .setMinApi(parameters.getApiLevel())
             .run(parameters.getRuntime(), OtherTestClass.class)
             .assertSuccessWithOutput(expectedOutput)
@@ -230,6 +233,7 @@
   public static class HelloGreeter implements Greeter {
 
     @NeverPropagateValue
+    @NoMethodStaticizing
     @Override
     public String greeting() {
       return "Hello";
@@ -238,6 +242,7 @@
 
   public static class WorldGreeter implements Greeter {
 
+    @NoMethodStaticizing
     @Override
     public String greeting() {
       return " world!";
diff --git a/src/test/java/com/android/tools/r8/shaking/clinit/InterfaceInitializedByImplementationTest.java b/src/test/java/com/android/tools/r8/shaking/clinit/InterfaceInitializedByImplementationTest.java
index 3f68961..dcb962b 100644
--- a/src/test/java/com/android/tools/r8/shaking/clinit/InterfaceInitializedByImplementationTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/clinit/InterfaceInitializedByImplementationTest.java
@@ -8,6 +8,7 @@
 import static org.hamcrest.MatcherAssert.assertThat;
 
 import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NoMethodStaticizing;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
@@ -43,6 +44,7 @@
         .addInnerClasses(InterfaceInitializedByImplementationTest.class)
         .addKeepMainRule(TestClass.class)
         .enableInliningAnnotations()
+        .enableNoMethodStaticizingAnnotations()
         .setMinApi(parameters.getApiLevel())
         .noMinification()
         .compile()
@@ -81,6 +83,7 @@
     // TODO(b/144266257): If tree shaking removes this method, then I.<clinit>() won't be run when
     //  A is being class initialized.
     @NeverInline
+    @NoMethodStaticizing
     default void m() {
       System.out.println(" world!");
     }
diff --git a/src/test/java/com/android/tools/r8/shaking/ifrule/accessrelaxation/IfRuleWithAccessRelaxationTest.java b/src/test/java/com/android/tools/r8/shaking/ifrule/accessrelaxation/IfRuleWithAccessRelaxationTest.java
index 6518cfe..882076c 100644
--- a/src/test/java/com/android/tools/r8/shaking/ifrule/accessrelaxation/IfRuleWithAccessRelaxationTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/ifrule/accessrelaxation/IfRuleWithAccessRelaxationTest.java
@@ -12,32 +12,32 @@
 import static org.junit.Assert.assertTrue;
 
 import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NoMethodStaticizing;
 import com.android.tools.r8.TestBase;
-import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
 import org.junit.runners.Parameterized.Parameters;
 
 @RunWith(Parameterized.class)
 public class IfRuleWithAccessRelaxationTest extends TestBase {
 
-  private final Backend backend;
+  @Parameter(0)
+  public TestParameters parameters;
 
-  public IfRuleWithAccessRelaxationTest(Backend backend) {
-    this.backend = backend;
-  }
-
-  @Parameters(name = "Backend: {0}")
-  public static Backend[] data() {
-    return ToolHelper.getBackends();
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
   }
 
   @Test
   public void r8Test() throws Exception {
     CodeInspector inspector =
-        testForR8(backend)
+        testForR8(parameters.getBackend())
             .addInnerClasses(IfRuleWithAccessRelaxationTest.class)
             .addKeepMainRule(TestClass.class)
             .addKeepRules(
@@ -54,6 +54,8 @@
                 "-keep class " + Unused3.class.getTypeName())
             .allowAccessModification()
             .enableInliningAnnotations()
+            .enableNoMethodStaticizingAnnotations()
+            .setMinApi(parameters.getApiLevel())
             .compile()
             .inspector();
 
@@ -80,6 +82,7 @@
     protected int field = 42;
 
     @NeverInline
+    @NoMethodStaticizing
     private void privateMethod() {
       System.out.println("In privateMethod()");
     }
diff --git a/src/test/java/com/android/tools/r8/shaking/ifrule/classstaticizer/IfRuleWithClassStaticizerTest.java b/src/test/java/com/android/tools/r8/shaking/ifrule/classstaticizer/IfRuleWithClassStaticizerTest.java
index b4e4fce..8c67d3e 100644
--- a/src/test/java/com/android/tools/r8/shaking/ifrule/classstaticizer/IfRuleWithClassStaticizerTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/ifrule/classstaticizer/IfRuleWithClassStaticizerTest.java
@@ -4,15 +4,17 @@
 
 package com.android.tools.r8.shaking.ifrule.classstaticizer;
 
+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.NeverClassInline;
 import com.android.tools.r8.NeverInline;
 import com.android.tools.r8.TestBase;
-import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.shaking.ifrule.classstaticizer.IfRuleWithClassStaticizerTest.StaticizerCandidate.Companion;
 import com.android.tools.r8.utils.StringUtils;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
@@ -22,69 +24,67 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
 import org.junit.runners.Parameterized.Parameters;
 
 @RunWith(Parameterized.class)
 public class IfRuleWithClassStaticizerTest extends TestBase {
 
-  private final Backend backend;
+  @Parameter(0)
+  public TestParameters parameters;
 
-  @Parameters(name = "Backend: {0}")
-  public static Backend[] data() {
-    return ToolHelper.getBackends();
-  }
-
-  public IfRuleWithClassStaticizerTest(Backend backend) {
-    this.backend = backend;
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
   }
 
   @Test
   public void test() throws Exception {
     String expectedOutput = StringUtils.lines("In method()");
 
-    if (backend == Backend.CF) {
-      testForJvm().addTestClasspath().run(TestClass.class).assertSuccessWithOutput(expectedOutput);
+    if (parameters.isCfRuntime()) {
+      testForJvm()
+          .addTestClasspath()
+          .run(parameters.getRuntime(), TestClass.class)
+          .assertSuccessWithOutput(expectedOutput);
     }
 
     CodeInspector inspector =
-        testForR8(backend)
+        testForR8(parameters.getBackend())
             .addInnerClasses(IfRuleWithClassStaticizerTest.class)
             .addKeepMainRule(TestClass.class)
             .addKeepRules(
-                "-if class " + StaticizerCandidate.Companion.class.getTypeName() + " {",
+                "-if class " + Companion.class.getTypeName() + " {",
                 "  public !static void method();",
                 "}",
                 "-keep class " + Unused.class.getTypeName())
             .enableInliningAnnotations()
             .enableNeverClassInliningAnnotations()
-            .run(TestClass.class)
+            .setMinApi(parameters.getApiLevel())
+            .run(parameters.getRuntime(), TestClass.class)
             .assertSuccessWithOutput(expectedOutput)
             .inspector();
 
     ClassSubject classSubject = inspector.clazz(StaticizerCandidate.class);
-    assertThat(classSubject, isPresent());
+    assertThat(classSubject, isAbsent());
 
-    if (backend == Backend.CF) {
+    if (parameters.isCfRuntime()) {
       // The class staticizer is not enabled for CF.
       assertThat(inspector.clazz(Unused.class), isPresent());
     } else {
-      assert backend == Backend.DEX;
+      assert parameters.isDexRuntime();
 
-      // There should be a static method on StaticizerCandidate after staticizing.
+      // There should be a static method after staticizing.
+      ClassSubject companionClassSubject = inspector.clazz(StaticizerCandidate.Companion.class);
+      assertThat(companionClassSubject, isPresent());
       List<FoundMethodSubject> staticMethods =
-          classSubject.allMethods().stream()
+          companionClassSubject.allMethods().stream()
               .filter(method -> method.isStatic() && !method.isClassInitializer())
               .collect(Collectors.toList());
       assertEquals(1, staticMethods.size());
-      assertEquals(
-          "void " + StaticizerCandidate.Companion.class.getTypeName() + ".method()",
-          staticMethods.get(0).getOriginalSignature().toString());
+      assertEquals("void method()", staticMethods.get(0).getOriginalSignature().toString());
 
-      // The Companion class should not be present after staticizing.
-      assertThat(inspector.clazz(StaticizerCandidate.Companion.class), not(isPresent()));
-
-      // TODO(b/122867080): The Unused class should be present due to the -if rule.
-      assertThat(inspector.clazz(Unused.class), not(isPresent()));
+      assertThat(inspector.clazz(Unused.class), isPresent());
     }
   }
 
diff --git a/src/test/java/com/android/tools/r8/utils/ArrayUtilsTest.java b/src/test/java/com/android/tools/r8/utils/ArrayUtilsTest.java
index b97fd41..93cc98f 100644
--- a/src/test/java/com/android/tools/r8/utils/ArrayUtilsTest.java
+++ b/src/test/java/com/android/tools/r8/utils/ArrayUtilsTest.java
@@ -113,7 +113,7 @@
   public void testFilter_identity() {
     int size = 3;
     Integer[] input = createInputData(size);
-    Integer[] output = ArrayUtils.filter(Integer[].class, input, x -> true);
+    Integer[] output = ArrayUtils.filter(input, x -> true, new Integer[0]);
     assertEquals(input, output);
   }
 
@@ -121,7 +121,7 @@
   public void testFilter_dropOdd() {
     int size = 3;
     Integer[] input = createInputData(size);
-    Integer[] output = ArrayUtils.filter(Integer[].class, input, x -> x % 2 == 0);
+    Integer[] output = ArrayUtils.filter(input, x -> x % 2 == 0, new Integer[0]);
     assertNotEquals(input, output);
     assertEquals(2, output.length);
     assertEquals(0, (int) output[0]);
@@ -132,7 +132,7 @@
   public void testFilter_dropAll() {
     int size = 3;
     Integer[] input = createInputData(size);
-    Integer[] output = ArrayUtils.filter(Integer[].class, input, x -> false);
+    Integer[] output = ArrayUtils.filter(input, x -> false, new Integer[0]);
     assertNotEquals(input, output);
     assertEquals(0, output.length);
   }