Staticize methods that don't use receiver

Change-Id: I935446199fa61385bf801e8dd0bd9bb77a44f7d9
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 1977132..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());
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/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/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/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/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/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/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/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/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/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/naming/applymapping/ApplyMappingAfterDevirtualizationTest.java b/src/test/java/com/android/tools/r8/naming/applymapping/ApplyMappingAfterDevirtualizationTest.java
index 670b6ae..ccdaae7 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
@@ -9,13 +9,16 @@
 
 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 {
@@ -40,7 +43,7 @@
     }
   }
 
-  // LibClassB should be devirtualized into LibInterfaceB
+  // LibInterfaceB should be devirtualized into LibClassB
   public static class LibClassB implements LibInterfaceB {
 
     @Override
@@ -77,35 +80,32 @@
 
   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(PROGRAM_CLASSES)
-        .run(ProgramClass.class)
+        .run(parameters.getRuntime(), ProgramClass.class)
         .assertSuccessWithOutput(EXPECTED_OUTPUT);
   }
 
   @Test
   public void devirtualizingNoRenamingOfOverriddenNotKeptInterfaceMethods() throws Exception {
     R8TestCompileResult libraryResult =
-        testForR8(backend)
+        testForR8(parameters.getBackend())
             .addProgramClasses(LIBRARY_CLASSES)
-            .addKeepClassAndMembersRulesWithAllowObfuscation(LibClassA.class)
-            .addKeepMainRule(LibClassB.class)
+            .addKeepClassAndMembersRulesWithAllowObfuscation(LibClassA.class, LibClassB.class)
             .addOptionsModification(options -> options.inlinerOptions().enableInlining = false)
+            .setMinApi(parameters.getApiLevel())
             .compile();
 
     CodeInspector inspector = libraryResult.inspector();
@@ -116,27 +116,29 @@
     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))
+        .addDefaultRuntimeLibrary(parameters)
+        .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)
+        testForR8(parameters.getBackend())
             .addProgramClasses(LIBRARY_CLASSES)
-            .addKeepClassAndMembersRulesWithAllowObfuscation(LibClassA.class, LibInterfaceA.class)
-            .addKeepMainRule(LibClassB.class)
+            .addKeepClassAndMembersRulesWithAllowObfuscation(
+                LibClassA.class, LibClassB.class, LibInterfaceA.class)
             .addOptionsModification(options -> options.inlinerOptions().enableInlining = false)
+            .setMinApi(parameters.getApiLevel())
             .compile();
 
     CodeInspector inspector = libraryResult.inspector();
@@ -147,16 +149,17 @@
     assertThat(inspector.clazz(LibInterfaceA.class), 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))
+        .addDefaultRuntimeLibrary(parameters)
+        .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/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/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/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());
     }
   }