Merge commit '99cf2c2ea56f74a1626daf535043d14b43e1862a' into dev-release

Change-Id: Icfbf7264b44ba092c8c263b30e8907c308dcb8c2
diff --git a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
index 240933c..2bb729c 100644
--- a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
+++ b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
@@ -621,6 +621,7 @@
   public final DexType androidOsBuildVersionType =
       createStaticallyKnownType("Landroid/os/Build$VERSION;");
   public final DexType androidOsBundleType = createStaticallyKnownType("Landroid/os/Bundle;");
+  public final DexType androidOsHandlerType = createStaticallyKnownType("Landroid/os/Handler;");
   public final DexType androidOsParcelableCreatorType =
       createStaticallyKnownType("Landroid/os/Parcelable$Creator;");
   public final DexType androidSystemOsConstantsType =
@@ -1053,6 +1054,7 @@
       ImmutableSet.<DexType>builder()
           .add(
               androidAppActivity,
+              androidOsHandlerType,
               callableType,
               enumType,
               npeType,
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/inlining/ConstSimpleInliningConstraint.java b/src/main/java/com/android/tools/r8/ir/analysis/inlining/ConstSimpleInliningConstraint.java
new file mode 100644
index 0000000..4ca4fa3
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/analysis/inlining/ConstSimpleInliningConstraint.java
@@ -0,0 +1,61 @@
+// Copyright (c) 2024, 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.inlining;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.proto.ArgumentInfoCollection;
+import com.android.tools.r8.graph.proto.RemovedArgumentInfo;
+import com.android.tools.r8.ir.analysis.value.SingleValue;
+import com.android.tools.r8.ir.code.Instruction;
+import com.android.tools.r8.ir.code.InvokeMethod;
+import com.android.tools.r8.ir.code.Value;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+
+public class ConstSimpleInliningConstraint extends SimpleInliningArgumentConstraint {
+
+  private ConstSimpleInliningConstraint(int argumentIndex) {
+    super(argumentIndex);
+  }
+
+  static ConstSimpleInliningConstraint create(
+      int argumentIndex, SimpleInliningConstraintFactory witness) {
+    assert witness != null;
+    return new ConstSimpleInliningConstraint(argumentIndex);
+  }
+
+  @Override
+  public boolean isSatisfied(InvokeMethod invoke) {
+    Value argumentRoot = invoke.getArgument(getArgumentIndex()).getAliasedValue();
+    return argumentRoot.isDefinedByInstructionSatisfying(Instruction::isConstInstruction);
+  }
+
+  @Override
+  public SimpleInliningConstraint fixupAfterParametersChanged(
+      AppView<AppInfoWithLiveness> appView,
+      ArgumentInfoCollection changes,
+      SimpleInliningConstraintFactory factory) {
+    if (changes.isArgumentRemoved(getArgumentIndex())) {
+      RemovedArgumentInfo removedArgumentInfo =
+          changes.getArgumentInfo(getArgumentIndex()).asRemovedArgumentInfo();
+      if (!removedArgumentInfo.hasSingleValue()) {
+        // We should never have constraints for unused arguments.
+        assert false;
+        return NeverSimpleInliningConstraint.getInstance();
+      }
+      SingleValue singleValue = removedArgumentInfo.getSingleValue();
+      return singleValue.isSingleConstValue()
+          ? AlwaysSimpleInliningConstraint.getInstance()
+          : NeverSimpleInliningConstraint.getInstance();
+    } else {
+      assert !changes.hasArgumentInfo(getArgumentIndex());
+    }
+    return withArgumentIndex(changes.getNewArgumentIndex(getArgumentIndex()), factory);
+  }
+
+  @Override
+  SimpleInliningArgumentConstraint withArgumentIndex(
+      int argumentIndex, SimpleInliningConstraintFactory factory) {
+    return factory.createConstConstraint(argumentIndex);
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/inlining/SimpleInliningConstraint.java b/src/main/java/com/android/tools/r8/ir/analysis/inlining/SimpleInliningConstraint.java
index a207c8d..cbeb329 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/inlining/SimpleInliningConstraint.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/inlining/SimpleInliningConstraint.java
@@ -64,11 +64,12 @@
     return new SimpleInliningConstraintConjunction(ImmutableList.of(this, other));
   }
 
-  public final SimpleInliningConstraint lazyMeet(Supplier<SimpleInliningConstraint> supplier) {
+  public final SimpleInliningConstraintWithDepth lazyMeet(
+      Supplier<SimpleInliningConstraintWithDepth> supplier) {
     if (isNever()) {
-      return NeverSimpleInliningConstraint.getInstance();
+      return SimpleInliningConstraintWithDepth.getNever();
     }
-    return meet(supplier.get());
+    return supplier.get().meet(this);
   }
 
   public final SimpleInliningConstraint join(SimpleInliningConstraint other) {
@@ -96,4 +97,8 @@
       AppView<AppInfoWithLiveness> appView,
       ArgumentInfoCollection changes,
       SimpleInliningConstraintFactory factory);
+
+  public final SimpleInliningConstraintWithDepth withDepth(int instructionDepth) {
+    return new SimpleInliningConstraintWithDepth(this, instructionDepth);
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/inlining/SimpleInliningConstraintAnalysis.java b/src/main/java/com/android/tools/r8/ir/analysis/inlining/SimpleInliningConstraintAnalysis.java
index 49d5674..63a2886 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/inlining/SimpleInliningConstraintAnalysis.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/inlining/SimpleInliningConstraintAnalysis.java
@@ -7,17 +7,23 @@
 import static com.android.tools.r8.ir.code.Opcodes.GOTO;
 import static com.android.tools.r8.ir.code.Opcodes.IF;
 import static com.android.tools.r8.ir.code.Opcodes.RETURN;
+import static com.android.tools.r8.ir.code.Opcodes.STRING_SWITCH;
 import static com.android.tools.r8.ir.code.Opcodes.THROW;
 
 import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.ir.code.Argument;
 import com.android.tools.r8.ir.code.BasicBlock;
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.If;
 import com.android.tools.r8.ir.code.IfType;
 import com.android.tools.r8.ir.code.Instruction;
 import com.android.tools.r8.ir.code.InstructionIterator;
+import com.android.tools.r8.ir.code.InvokeVirtual;
+import com.android.tools.r8.ir.code.JumpInstruction;
+import com.android.tools.r8.ir.code.StringSwitch;
 import com.android.tools.r8.ir.code.Value;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.utils.InternalOptions;
@@ -40,7 +46,8 @@
  */
 public class SimpleInliningConstraintAnalysis {
 
-  private final SimpleInliningConstraintFactory factory;
+  private final SimpleInliningConstraintFactory constraintFactory;
+  private final DexItemFactory dexItemFactory;
   private final ProgramMethod method;
   private final InternalOptions options;
   private final int simpleInliningConstraintThreshold;
@@ -49,21 +56,22 @@
 
   public SimpleInliningConstraintAnalysis(
       AppView<AppInfoWithLiveness> appView, ProgramMethod method) {
-    this.factory = appView.simpleInliningConstraintFactory();
+    this.constraintFactory = appView.simpleInliningConstraintFactory();
+    this.dexItemFactory = appView.dexItemFactory();
     this.method = method;
     this.options = appView.options();
     this.simpleInliningConstraintThreshold = appView.options().simpleInliningConstraintThreshold;
   }
 
-  public SimpleInliningConstraint analyzeCode(IRCode code) {
+  public SimpleInliningConstraintWithDepth analyzeCode(IRCode code) {
     if (method.getReference().getArity() == 0) {
       // The method does not have any parameters, so there is no need to analyze the method.
-      return NeverSimpleInliningConstraint.getInstance();
+      return SimpleInliningConstraintWithDepth.getNever();
     }
 
     if (options.debug) {
       // Inlining is not enabled in debug mode.
-      return NeverSimpleInliningConstraint.getInstance();
+      return SimpleInliningConstraintWithDepth.getNever();
     }
 
     // Run a bounded depth-first traversal to collect the path constraints that lead to early
@@ -73,25 +81,33 @@
     return analyzeInstructionsInBlock(code.entryBlock(), 0, instructionIterator);
   }
 
-  private SimpleInliningConstraint analyzeInstructionsInBlock(BasicBlock block, int depth) {
+  private SimpleInliningConstraintWithDepth analyzeInstructionsInBlock(
+      BasicBlock block, int depth) {
     return analyzeInstructionsInBlock(block, depth, block.iterator());
   }
 
-  private SimpleInliningConstraint analyzeInstructionsInBlock(
+  private SimpleInliningConstraintWithDepth analyzeInstructionsInBlock(
       BasicBlock block, int instructionDepth, InstructionIterator instructionIterator) {
     // If we reach a block that has already been seen, give up.
     if (!seen.add(block)) {
-      return NeverSimpleInliningConstraint.getInstance();
+      return SimpleInliningConstraintWithDepth.getNever();
     }
 
     // Move the instruction iterator forward to the block's jump instruction, while incrementing the
     // instruction depth of the depth-first traversal.
     Instruction instruction = instructionIterator.next();
+    SimpleInliningConstraint blockConstraint = AlwaysSimpleInliningConstraint.getInstance();
     while (!instruction.isJumpInstruction()) {
       assert !instruction.isArgument();
       assert !instruction.isDebugInstruction();
-      if (!instruction.isAssume()) {
-        instructionDepth += 1;
+      SimpleInliningConstraint instructionConstraint =
+          computeConstraintForInstructionNotToMaterialize(instruction);
+      if (instructionConstraint.isAlways()) {
+        assert instruction.isAssume();
+      } else if (instructionConstraint.isNever()) {
+        instructionDepth++;
+      } else {
+        blockConstraint = blockConstraint.meet(instructionConstraint);
       }
       instruction = instructionIterator.next();
     }
@@ -99,11 +115,39 @@
     // If we have exceeded the threshold, then all paths from this instruction will not lead to any
     // early exits, so return 'never'.
     if (instructionDepth > simpleInliningConstraintThreshold) {
-      return NeverSimpleInliningConstraint.getInstance();
+      return SimpleInliningConstraintWithDepth.getNever();
     }
 
-    // Analyze the jump instruction.
-    // TODO(b/132600418): Extend to switch and throw instructions.
+    SimpleInliningConstraintWithDepth jumpConstraint =
+        computeConstraintForJumpInstruction(instruction.asJumpInstruction(), instructionDepth);
+    return jumpConstraint.meet(blockConstraint);
+  }
+
+  private SimpleInliningConstraint computeConstraintForInstructionNotToMaterialize(
+      Instruction instruction) {
+    if (instruction.isAssume()) {
+      return AlwaysSimpleInliningConstraint.getInstance();
+    }
+    if (instruction.isInvokeVirtual()) {
+      InvokeVirtual invoke = instruction.asInvokeVirtual();
+      if (invoke.getInvokedMethod().isIdenticalTo(dexItemFactory.objectMembers.getClass)
+          && invoke.hasUnusedOutValue()) {
+        Value receiver = invoke.getReceiver();
+        if (receiver.getType().isDefinitelyNotNull()) {
+          return AlwaysSimpleInliningConstraint.getInstance();
+        }
+        Value receiverRoot = receiver.getAliasedValue();
+        if (receiverRoot.isDefinedByInstructionSatisfying(Instruction::isArgument)) {
+          Argument argument = receiverRoot.getDefinition().asArgument();
+          return constraintFactory.createNotEqualToNullConstraint(argument.getIndex());
+        }
+      }
+    }
+    return NeverSimpleInliningConstraint.getInstance();
+  }
+
+  private SimpleInliningConstraintWithDepth computeConstraintForJumpInstruction(
+      JumpInstruction instruction, int instructionDepth) {
     switch (instruction.opcode()) {
       case IF:
         If ifInstruction = instruction.asIf();
@@ -125,7 +169,7 @@
 
         // Compute the constraint for which paths through the true target are guaranteed to exit
         // early.
-        SimpleInliningConstraint trueTargetConstraint =
+        SimpleInliningConstraintWithDepth trueTargetConstraint =
             computeConstraintFromIfTest(
                     argumentIndex, argumentType, otherOperand, ifInstruction.getType())
                 // Only recurse into the true target if the constraint from the if-instruction
@@ -135,7 +179,7 @@
 
         // Compute the constraint for which paths through the false target are guaranteed to
         // exit early.
-        SimpleInliningConstraint fallthroughTargetConstraint =
+        SimpleInliningConstraintWithDepth fallthroughTargetConstraint =
             computeConstraintFromIfTest(
                     argumentIndex, argumentType, otherOperand, ifInstruction.getType().inverted())
                 // Only recurse into the false target if the constraint from the if-instruction
@@ -152,19 +196,42 @@
         return analyzeInstructionsInBlock(instruction.asGoto().getTarget(), instructionDepth);
 
       case RETURN:
-        return AlwaysSimpleInliningConstraint.getInstance();
+        return AlwaysSimpleInliningConstraint.getInstance().withDepth(instructionDepth);
+
+      case STRING_SWITCH:
+        // Require that all cases including the default case are simple. In that case we can
+        // guarantee simpleness by requiring that the switch value is constant.
+        StringSwitch stringSwitch = instruction.asStringSwitch();
+        Value valueRoot = stringSwitch.value().getAliasedValue();
+        if (!valueRoot.isDefinedByInstructionSatisfying(Instruction::isArgument)) {
+          return SimpleInliningConstraintWithDepth.getNever();
+        }
+        int maxInstructionDepth = instructionDepth;
+        for (BasicBlock successor : stringSwitch.getBlock().getNormalSuccessors()) {
+          SimpleInliningConstraintWithDepth successorConstraintWithDepth =
+              analyzeInstructionsInBlock(successor, instructionDepth);
+          if (!successorConstraintWithDepth.getConstraint().isAlways()) {
+            return SimpleInliningConstraintWithDepth.getNever();
+          }
+          maxInstructionDepth =
+              Math.max(maxInstructionDepth, successorConstraintWithDepth.getInstructionDepth());
+        }
+        Argument argument = valueRoot.getDefinition().asArgument();
+        ConstSimpleInliningConstraint simpleConstraint =
+            constraintFactory.createConstConstraint(argument.getIndex());
+        return simpleConstraint.withDepth(maxInstructionDepth);
 
       case THROW:
-        return block.hasCatchHandlers()
-            ? NeverSimpleInliningConstraint.getInstance()
-            : AlwaysSimpleInliningConstraint.getInstance();
+        return instruction.getBlock().hasCatchHandlers()
+            ? SimpleInliningConstraintWithDepth.getNever()
+            : SimpleInliningConstraintWithDepth.getAlways(instructionDepth);
 
       default:
         break;
     }
 
     // Give up.
-    return NeverSimpleInliningConstraint.getInstance();
+    return SimpleInliningConstraintWithDepth.getNever();
   }
 
   private SimpleInliningConstraint computeConstraintFromIfTest(
@@ -174,15 +241,16 @@
       case EQ:
         if (isZeroTest) {
           if (argumentType.isReferenceType()) {
-            return factory.createEqualToNullConstraint(argumentIndex);
+            return constraintFactory.createEqualToNullConstraint(argumentIndex);
           }
           if (argumentType.isBooleanType()) {
-            return factory.createEqualToFalseConstraint(argumentIndex);
+            return constraintFactory.createEqualToFalseConstraint(argumentIndex);
           }
         } else if (argumentType.isPrimitiveType()) {
           OptionalLong rawValue = getRawNumberValue(otherOperand);
           if (rawValue.isPresent()) {
-            return factory.createEqualToNumberConstraint(argumentIndex, rawValue.getAsLong());
+            return constraintFactory.createEqualToNumberConstraint(
+                argumentIndex, rawValue.getAsLong());
           }
         }
         return NeverSimpleInliningConstraint.getInstance();
@@ -190,15 +258,16 @@
       case NE:
         if (isZeroTest) {
           if (argumentType.isReferenceType()) {
-            return factory.createNotEqualToNullConstraint(argumentIndex);
+            return constraintFactory.createNotEqualToNullConstraint(argumentIndex);
           }
           if (argumentType.isBooleanType()) {
-            return factory.createEqualToTrueConstraint(argumentIndex);
+            return constraintFactory.createEqualToTrueConstraint(argumentIndex);
           }
         } else if (argumentType.isPrimitiveType()) {
           OptionalLong rawValue = getRawNumberValue(otherOperand);
           if (rawValue.isPresent()) {
-            return factory.createNotEqualToNumberConstraint(argumentIndex, rawValue.getAsLong());
+            return constraintFactory.createNotEqualToNumberConstraint(
+                argumentIndex, rawValue.getAsLong());
           }
         }
         return NeverSimpleInliningConstraint.getInstance();
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/inlining/SimpleInliningConstraintFactory.java b/src/main/java/com/android/tools/r8/ir/analysis/inlining/SimpleInliningConstraintFactory.java
index 5b60ccd..39a8164 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/inlining/SimpleInliningConstraintFactory.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/inlining/SimpleInliningConstraintFactory.java
@@ -8,6 +8,7 @@
 import static com.android.tools.r8.ir.analysis.type.Nullability.definitelyNull;
 
 import com.android.tools.r8.ir.analysis.type.Nullability;
+import com.android.tools.r8.utils.ArrayUtils;
 import java.util.Map;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.function.Supplier;
@@ -15,16 +16,29 @@
 public class SimpleInliningConstraintFactory {
 
   // Immutable argument constraints for low argument indices to avoid overhead of ConcurrentHashMap.
+  private final ConstSimpleInliningConstraint[] lowConstConstraints =
+      ArrayUtils.initialize(
+          new ConstSimpleInliningConstraint[5], i -> ConstSimpleInliningConstraint.create(i, this));
   private final EqualToBooleanSimpleInliningConstraint[] lowEqualToFalseConstraints =
-      new EqualToBooleanSimpleInliningConstraint[5];
+      ArrayUtils.initialize(
+          new EqualToBooleanSimpleInliningConstraint[5],
+          i -> EqualToBooleanSimpleInliningConstraint.create(i, false, this));
   private final EqualToBooleanSimpleInliningConstraint[] lowEqualToTrueConstraints =
-      new EqualToBooleanSimpleInliningConstraint[5];
+      ArrayUtils.initialize(
+          new EqualToBooleanSimpleInliningConstraint[5],
+          i -> EqualToBooleanSimpleInliningConstraint.create(i, true, this));
   private final NullSimpleInliningConstraint[] lowNotEqualToNullConstraints =
-      new NullSimpleInliningConstraint[5];
+      ArrayUtils.initialize(
+          new NullSimpleInliningConstraint[5],
+          i -> NullSimpleInliningConstraint.create(i, definitelyNotNull(), this));
   private final NullSimpleInliningConstraint[] lowEqualToNullConstraints =
-      new NullSimpleInliningConstraint[5];
+      ArrayUtils.initialize(
+          new NullSimpleInliningConstraint[5],
+          i -> NullSimpleInliningConstraint.create(i, definitelyNull(), this));
 
   // Argument constraints for high argument indices.
+  private final Map<Integer, ConstSimpleInliningConstraint> highConstConstraints =
+      new ConcurrentHashMap<>();
   private final Map<Integer, EqualToBooleanSimpleInliningConstraint> highEqualToFalseConstraints =
       new ConcurrentHashMap<>();
   private final Map<Integer, EqualToBooleanSimpleInliningConstraint> highEqualToTrueConstraints =
@@ -34,20 +48,12 @@
   private final Map<Integer, NullSimpleInliningConstraint> highEqualToNullConstraints =
       new ConcurrentHashMap<>();
 
-  public SimpleInliningConstraintFactory() {
-    for (int i = 0; i < lowEqualToFalseConstraints.length; i++) {
-      lowEqualToFalseConstraints[i] = EqualToBooleanSimpleInliningConstraint.create(i, false, this);
-    }
-    for (int i = 0; i < lowEqualToTrueConstraints.length; i++) {
-      lowEqualToTrueConstraints[i] = EqualToBooleanSimpleInliningConstraint.create(i, true, this);
-    }
-    for (int i = 0; i < lowNotEqualToNullConstraints.length; i++) {
-      lowNotEqualToNullConstraints[i] =
-          NullSimpleInliningConstraint.create(i, definitelyNotNull(), this);
-    }
-    for (int i = 0; i < lowEqualToNullConstraints.length; i++) {
-      lowEqualToNullConstraints[i] = NullSimpleInliningConstraint.create(i, definitelyNull(), this);
-    }
+  public ConstSimpleInliningConstraint createConstConstraint(int argumentIndex) {
+    return createArgumentConstraint(
+        argumentIndex,
+        lowConstConstraints,
+        highConstConstraints,
+        () -> ConstSimpleInliningConstraint.create(argumentIndex, this));
   }
 
   public EqualToBooleanSimpleInliningConstraint createEqualToFalseConstraint(int argumentIndex) {
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/inlining/SimpleInliningConstraintWithDepth.java b/src/main/java/com/android/tools/r8/ir/analysis/inlining/SimpleInliningConstraintWithDepth.java
new file mode 100644
index 0000000..391f5fd
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/analysis/inlining/SimpleInliningConstraintWithDepth.java
@@ -0,0 +1,48 @@
+// Copyright (c) 2024, 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.inlining;
+
+public class SimpleInliningConstraintWithDepth {
+
+  private static final SimpleInliningConstraintWithDepth NEVER =
+      new SimpleInliningConstraintWithDepth(NeverSimpleInliningConstraint.getInstance(), 0);
+
+  private final SimpleInliningConstraint constraint;
+  private final int instructionDepth;
+
+  public SimpleInliningConstraintWithDepth(
+      SimpleInliningConstraint constraint, int instructionDepth) {
+    this.constraint = constraint;
+    this.instructionDepth = instructionDepth;
+  }
+
+  public static SimpleInliningConstraintWithDepth getAlways(int instructionDepth) {
+    return AlwaysSimpleInliningConstraint.getInstance().withDepth(instructionDepth);
+  }
+
+  public static SimpleInliningConstraintWithDepth getNever() {
+    return NEVER;
+  }
+
+  public SimpleInliningConstraint getConstraint() {
+    return constraint;
+  }
+
+  public SimpleInliningConstraint getNopConstraint() {
+    return instructionDepth == 0 ? constraint : NeverSimpleInliningConstraint.getInstance();
+  }
+
+  public int getInstructionDepth() {
+    return instructionDepth;
+  }
+
+  public SimpleInliningConstraintWithDepth join(SimpleInliningConstraintWithDepth other) {
+    SimpleInliningConstraint joinConstraint = constraint.join(other.constraint);
+    return joinConstraint.withDepth(Math.max(instructionDepth, other.instructionDepth));
+  }
+
+  public SimpleInliningConstraintWithDepth meet(SimpleInliningConstraint other) {
+    return new SimpleInliningConstraintWithDepth(constraint.meet(other), instructionDepth);
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java b/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java
index 994ef53..b95529b 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java
@@ -742,10 +742,6 @@
       new TypeAnalysis(appView, ir).narrowing();
     }
 
-    if (conversionOptions.isStringSwitchConversionEnabled()) {
-      StringSwitchConverter.convertToStringSwitchInstructions(ir, appView.dexItemFactory());
-    }
-
     ir.removeRedundantBlocks();
     assert ir.isConsistentSSABeforeTypesAreCorrect(appView);
 
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 9f18e74..9d6a8b6 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
@@ -35,6 +35,7 @@
 import com.android.tools.r8.ir.conversion.passes.FilledNewArrayRewriter;
 import com.android.tools.r8.ir.conversion.passes.MoveResultRewriter;
 import com.android.tools.r8.ir.conversion.passes.ParentConstructorHoistingCodeRewriter;
+import com.android.tools.r8.ir.conversion.passes.StringSwitchConverter;
 import com.android.tools.r8.ir.conversion.passes.StringSwitchRemover;
 import com.android.tools.r8.ir.conversion.passes.ThrowCatchOptimizer;
 import com.android.tools.r8.ir.conversion.passes.TrivialPhiSimplifier;
@@ -120,7 +121,6 @@
   protected final IdentifierNameStringMarker identifierNameStringMarker;
   private final Devirtualizer devirtualizer;
   protected final CovariantReturnTypeAnnotationTransformer covariantReturnTypeAnnotationTransformer;
-  private final StringSwitchRemover stringSwitchRemover;
   private final TypeChecker typeChecker;
   protected EnumUnboxer enumUnboxer;
   protected final NumberUnboxer numberUnboxer;
@@ -202,7 +202,6 @@
       this.identifierNameStringMarker = null;
       this.devirtualizer = null;
       this.typeChecker = null;
-      this.stringSwitchRemover = null;
       this.methodOptimizationInfoCollector = null;
       this.enumUnboxer = EnumUnboxer.empty();
       this.numberUnboxer = NumberUnboxer.empty();
@@ -275,10 +274,6 @@
       this.enumUnboxer = EnumUnboxer.empty();
       this.numberUnboxer = NumberUnboxer.empty();
     }
-    this.stringSwitchRemover =
-        options.isStringSwitchConversionEnabled()
-            ? new StringSwitchRemover(appView, identifierNameStringMarker)
-            : null;
   }
 
   public IRConverter(AppInfo appInfo) {
@@ -590,6 +585,13 @@
       return timing;
     }
 
+    // In R8, StringSwitch instructions are introduced when entering the LIR phase. In D8, we don't
+    // use LIR, so we explicitly introduce StringSwitch instructions here.
+    if (!options.getTestingOptions().isSupportedLirPhase()) {
+      new StringSwitchConverter(appView)
+          .run(code, methodProcessor, methodProcessingContext, timing);
+    }
+
     if (options.canHaveArtStringNewInitBug()) {
       timing.begin("Check for new-init issue");
       TrivialPhiSimplifier.ensureDirectStringNewToInit(appView, code);
@@ -773,11 +775,10 @@
           .run(code, methodProcessor, methodProcessingContext, timing);
     }
 
-    if (code.getConversionOptions().isStringSwitchConversionEnabled()) {
-      // Remove string switches prior to canonicalization to ensure that the constants that are
-      // being introduced will be canonicalized if possible.
-      stringSwitchRemover.run(code, methodProcessor, methodProcessingContext, timing);
-    }
+    // Remove string switches prior to canonicalization to ensure that the constants that are
+    // being introduced will be canonicalized if possible.
+    new StringSwitchRemover(appView, identifierNameStringMarker)
+        .run(code, methodProcessor, methodProcessingContext, timing);
 
     // TODO(mkroghj) Test if shorten live ranges is worth it.
     if (options.isGeneratingDex()) {
@@ -967,9 +968,7 @@
       IRCode code, OptimizationFeedback feedback, Timing timing) {
     if (!code.getConversionOptions().isGeneratingLir()) {
       new FilledNewArrayRewriter(appView).run(code, timing);
-    }
-    if (stringSwitchRemover != null) {
-      stringSwitchRemover.run(code, timing);
+      new StringSwitchRemover(appView, identifierNameStringMarker).run(code, timing);
     }
     code.removeRedundantBlocks();
     deadCodeRemover.run(code, timing);
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/LirConverter.java b/src/main/java/com/android/tools/r8/ir/conversion/LirConverter.java
index 6da063b..25df747 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/LirConverter.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/LirConverter.java
@@ -19,6 +19,7 @@
 import com.android.tools.r8.ir.conversion.passes.DexItemBasedConstStringRemover;
 import com.android.tools.r8.ir.conversion.passes.FilledNewArrayRewriter;
 import com.android.tools.r8.ir.conversion.passes.InitClassRemover;
+import com.android.tools.r8.ir.conversion.passes.StringSwitchConverter;
 import com.android.tools.r8.ir.conversion.passes.StringSwitchRemover;
 import com.android.tools.r8.ir.optimize.ConstantCanonicalizer;
 import com.android.tools.r8.ir.optimize.DeadCodeRemover;
@@ -42,8 +43,9 @@
     assert appView.testing().canUseLir(appView);
     assert appView.testing().isPreLirPhase();
     appView.testing().enterLirSupportedPhase();
-    ConstResourceNumberRewriter constResourceNumberRewriter =
-        new ConstResourceNumberRewriter(appView);
+    CodeRewriterPassCollection codeRewriterPassCollection =
+        new CodeRewriterPassCollection(
+            new ConstResourceNumberRewriter(appView), new StringSwitchConverter(appView));
     // Convert code objects to LIR.
     ThreadUtils.processItems(
         appView.appInfo().classes(),
@@ -53,7 +55,7 @@
               method -> {
                 assert !method.getDefinition().getCode().hasExplicitCodeLens();
                 IRCode code = method.buildIR(appView, MethodConversionOptions.forLirPhase(appView));
-                constResourceNumberRewriter.run(code, Timing.empty());
+                codeRewriterPassCollection.run(code, null, null, Timing.empty());
                 LirCode<Integer> lirCode =
                     IR2LirConverter.translate(
                         code,
@@ -143,11 +145,12 @@
         new CodeRewriterPassCollection(
             new AdaptClassStringsRewriter(appView),
             new ConstResourceNumberRemover(appView),
+            // Must run before DexItemBasedConstStringRemover.
+            new StringSwitchRemover(appView),
             new DexItemBasedConstStringRemover(appView),
             new InitClassRemover(appView),
             new RecordInvokeDynamicInvokeCustomRewriter(appView),
-            new FilledNewArrayRewriter(appView),
-            new StringSwitchRemover(appView));
+            new FilledNewArrayRewriter(appView));
     ThreadUtils.processItems(
         appView.appInfo().classes(),
         clazz ->
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/MethodConversionOptions.java b/src/main/java/com/android/tools/r8/ir/conversion/MethodConversionOptions.java
index 24f7613..fd3c580 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/MethodConversionOptions.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/MethodConversionOptions.java
@@ -7,7 +7,6 @@
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.ir.optimize.DeadCodeRemover;
-import com.android.tools.r8.utils.InternalOptions;
 
 public abstract class MethodConversionOptions {
 
@@ -16,7 +15,7 @@
       return forD8(appView);
     }
     assert appView.testing().isPreLirPhase();
-    return new MutableMethodConversionOptions(Target.CF, appView.options());
+    return new MutableMethodConversionOptions(Target.CF);
   }
 
   public static MutableMethodConversionOptions forPostLirPhase(AppView<?> appView) {
@@ -25,8 +24,7 @@
     }
     assert appView.testing().isPostLirPhase();
     Target target = appView.options().isGeneratingClassFiles() ? Target.CF : Target.DEX;
-    return new MutableMethodConversionOptions(target, appView.options())
-        .disableStringSwitchConversion();
+    return new MutableMethodConversionOptions(target);
   }
 
   public static MutableMethodConversionOptions forLirPhase(AppView<?> appView) {
@@ -34,12 +32,12 @@
       return forD8(appView);
     }
     assert appView.testing().isSupportedLirPhase();
-    return new MutableMethodConversionOptions(determineTarget(appView), appView.options());
+    return new MutableMethodConversionOptions(determineTarget(appView));
   }
 
   public static MutableMethodConversionOptions forD8(AppView<?> appView) {
     assert !appView.enableWholeProgramOptimizations();
-    return new MutableMethodConversionOptions(determineTarget(appView), appView.options());
+    return new MutableMethodConversionOptions(determineTarget(appView));
   }
 
   public static MutableMethodConversionOptions nonConverting() {
@@ -82,24 +80,16 @@
 
   public abstract boolean isPeepholeOptimizationsEnabled();
 
-  public abstract boolean isStringSwitchConversionEnabled();
-
   public abstract boolean shouldFinalizeAfterLensCodeRewriter();
 
   public static class MutableMethodConversionOptions extends MethodConversionOptions {
 
-    private Target target;
+    private final Target target;
     private boolean enablePeepholeOptimizations = true;
-    private boolean enableStringSwitchConversion;
     private boolean finalizeAfterLensCodeRewriter;
 
-    private MutableMethodConversionOptions(Target target, boolean enableStringSwitchConversion) {
+    private MutableMethodConversionOptions(Target target) {
       this.target = target;
-      this.enableStringSwitchConversion = enableStringSwitchConversion;
-    }
-
-    private MutableMethodConversionOptions(Target target, InternalOptions options) {
-      this(target, options.isStringSwitchConversionEnabled());
     }
 
     public void disablePeepholeOptimizations(MethodProcessor methodProcessor) {
@@ -107,11 +97,6 @@
       enablePeepholeOptimizations = false;
     }
 
-    public MutableMethodConversionOptions disableStringSwitchConversion() {
-      enableStringSwitchConversion = false;
-      return this;
-    }
-
     public MutableMethodConversionOptions setFinalizeAfterLensCodeRewriter() {
       finalizeAfterLensCodeRewriter = true;
       return this;
@@ -138,11 +123,6 @@
     }
 
     @Override
-    public boolean isStringSwitchConversionEnabled() {
-      return enableStringSwitchConversion;
-    }
-
-    @Override
     public boolean shouldFinalizeAfterLensCodeRewriter() {
       return finalizeAfterLensCodeRewriter;
     }
@@ -151,7 +131,7 @@
   public static class ThrowingMethodConversionOptions extends MutableMethodConversionOptions {
 
     private ThrowingMethodConversionOptions() {
-      super(null, true);
+      super(null);
     }
 
     @Override
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/MethodOptimizationFeedback.java b/src/main/java/com/android/tools/r8/ir/conversion/MethodOptimizationFeedback.java
index f22f61a..abe0324 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/MethodOptimizationFeedback.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/MethodOptimizationFeedback.java
@@ -72,6 +72,8 @@
 
   void setNonNullParamOnNormalExits(DexEncodedMethod method, BitSet facts);
 
+  void setNopInliningConstraint(ProgramMethod method, SimpleInliningConstraint constraint);
+
   void setSimpleInliningConstraint(ProgramMethod method, SimpleInliningConstraint constraint);
 
   void classInitializerMayBePostponed(DexEncodedMethod method);
@@ -117,6 +119,8 @@
 
   void unsetReturnValueOnlyDependsOnArguments(ProgramMethod method);
 
+  void unsetNopInliningConstraint(ProgramMethod method);
+
   void unsetSimpleInliningConstraint(ProgramMethod method);
 
   void unsetUnusedArguments(ProgramMethod method);
@@ -139,6 +143,7 @@
       unsetNonNullParamOrThrow(method);
       unsetReturnedArgument(method);
       unsetReturnValueOnlyDependsOnArguments(method);
+      unsetNopInliningConstraint(method);
       unsetSimpleInliningConstraint(method);
       unsetUnusedArguments(method);
     }
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/passes/CodeRewriterPass.java b/src/main/java/com/android/tools/r8/ir/conversion/passes/CodeRewriterPass.java
index 98a92e8..87fc847 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/passes/CodeRewriterPass.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/passes/CodeRewriterPass.java
@@ -59,7 +59,7 @@
     return noChange();
   }
 
-  private boolean verifyConsistentCode(IRCode code, boolean ssa, String preposition) {
+  protected boolean verifyConsistentCode(IRCode code, boolean ssa, String preposition) {
     boolean result;
     String message = "Invalid code " + preposition + " " + getRewriterId();
     try {
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/passes/ConstResourceNumberRewriter.java b/src/main/java/com/android/tools/r8/ir/conversion/passes/ConstResourceNumberRewriter.java
index e6806fc..7783c5c 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/passes/ConstResourceNumberRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/passes/ConstResourceNumberRewriter.java
@@ -62,4 +62,10 @@
     }
     return CodeRewriterResult.hasChanged(hasChanged);
   }
+
+  @Override
+  protected boolean verifyConsistentCode(IRCode code, boolean ssa, String preposition) {
+    // Skip verification since this runs prior to the removal of invalid code.
+    return true;
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/StringSwitchConverter.java b/src/main/java/com/android/tools/r8/ir/conversion/passes/StringSwitchConverter.java
similarity index 95%
rename from src/main/java/com/android/tools/r8/ir/conversion/StringSwitchConverter.java
rename to src/main/java/com/android/tools/r8/ir/conversion/passes/StringSwitchConverter.java
index 5d7faef..a49b93b 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/StringSwitchConverter.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/passes/StringSwitchConverter.java
@@ -1,12 +1,15 @@
-// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
+// Copyright (c) 2024, 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.conversion;
+package com.android.tools.r8.ir.conversion.passes;
 
 import static com.android.tools.r8.ir.code.ConstNumber.asConstNumberOrNull;
 
+import com.android.tools.r8.contexts.CompilationContext.MethodProcessingContext;
 import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.graph.AppInfo;
+import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexString;
 import com.android.tools.r8.ir.code.BasicBlock;
@@ -24,6 +27,8 @@
 import com.android.tools.r8.ir.code.Phi;
 import com.android.tools.r8.ir.code.StringSwitch;
 import com.android.tools.r8.ir.code.Value;
+import com.android.tools.r8.ir.conversion.MethodProcessor;
+import com.android.tools.r8.ir.conversion.passes.result.CodeRewriterResult;
 import com.google.common.collect.Sets;
 import it.unimi.dsi.fastutil.ints.Int2ReferenceMap;
 import it.unimi.dsi.fastutil.ints.Int2ReferenceOpenHashMap;
@@ -105,10 +110,28 @@
  *   }
  * </pre>
  */
-public class StringSwitchConverter {
+public class StringSwitchConverter extends CodeRewriterPass<AppInfo> {
 
-  public static boolean convertToStringSwitchInstructions(
-      IRCode code, DexItemFactory dexItemFactory) {
+  public StringSwitchConverter(AppView<?> appView) {
+    super(appView);
+  }
+
+  @Override
+  protected String getRewriterId() {
+    return "StringSwitchConverter";
+  }
+
+  @Override
+  protected boolean shouldRewriteCode(IRCode code, MethodProcessor methodProcessor) {
+    return options.shouldCompileMethodInReleaseMode(appView, code.context())
+        && options.enableStringSwitchConversion;
+  }
+
+  @Override
+  protected CodeRewriterResult rewriteCode(
+      IRCode code,
+      MethodProcessor methodProcessor,
+      MethodProcessingContext methodProcessingContext) {
     List<BasicBlock> rewritingCandidates = getRewritingCandidates(code, dexItemFactory);
     if (rewritingCandidates != null) {
       boolean changed = false;
@@ -121,9 +144,15 @@
         code.removeAllDeadAndTrivialPhis();
         code.removeUnreachableBlocks();
       }
-      return changed;
+      return CodeRewriterResult.hasChanged(changed);
     }
-    return false;
+    return CodeRewriterResult.NO_CHANGE;
+  }
+
+  @Override
+  protected boolean verifyConsistentCode(IRCode code, boolean ssa, String preposition) {
+    // Skip verification since this runs prior to the removal of invalid code.
+    return true;
   }
 
   private static List<BasicBlock> getRewritingCandidates(
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/passes/StringSwitchRemover.java b/src/main/java/com/android/tools/r8/ir/conversion/passes/StringSwitchRemover.java
index f35f820..74fa808 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/passes/StringSwitchRemover.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/passes/StringSwitchRemover.java
@@ -84,7 +84,7 @@
     while (blockIterator.hasNext()) {
       BasicBlock block = blockIterator.next();
       StringSwitch theSwitch = block.exit().asStringSwitch();
-      if (theSwitch != null) {
+      if (theSwitch != null && shouldBeRemoved(code, theSwitch)) {
         try {
           SingleStringSwitchRemover remover;
           if (theSwitch.numberOfKeys() < appView.options().minimumStringSwitchSize
@@ -132,7 +132,7 @@
       BasicBlock block = blockIterator.next();
       for (BasicBlock predecessor : block.getNormalPredecessors()) {
         StringSwitch exit = predecessor.exit().asStringSwitch();
-        if (exit != null) {
+        if (exit != null && shouldBeRemoved(code, exit)) {
           hasStringSwitch = true;
           if (block == exit.fallthroughBlock()) {
             // After the elimination of this string-switch instruction, there will be two
@@ -164,6 +164,15 @@
     return hasStringSwitch;
   }
 
+  private boolean shouldBeRemoved(IRCode code, StringSwitch theSwitch) {
+    // We only support retaining StringSwitch instructions in LIR. However, even when compiling to
+    // LIR, we (currently) need to remove StringSwitch instructions where the keys may be class
+    // names, so that these DexItemBasedConstStrings are correctly lens code rewritten.
+    // (Note this could be avoided by introducing a separate DexItemBasedStringSwitch instruction.)
+    return !code.getConversionOptions().isGeneratingLir()
+        || isClassNameValue(theSwitch.value(), dexItemFactory);
+  }
+
   @Override
   protected boolean shouldRewriteCode(IRCode code, MethodProcessor methodProcessor) {
     return code.metadata().mayHaveStringSwitch();
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java b/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java
index 92da768..a166867 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java
@@ -221,18 +221,21 @@
       int estimatedSizeForInlining =
           code.getEstimatedSizeForInliningIfLessThanOrEquals(
               instructionLimit + estimatedMaxIncrement);
-      if (estimatedSizeForInlining < 0) {
-        return false;
-      }
-      if (estimatedSizeForInlining <= instructionLimit) {
-        return true;
-      }
-      int actualIncrement =
-          getInliningInstructionLimitIncrement(invoke, target, inliningIRProvider);
-      if (estimatedSizeForInlining <= instructionLimit + actualIncrement) {
-        return true;
+      if (estimatedSizeForInlining >= 0) {
+        if (estimatedSizeForInlining <= instructionLimit) {
+          return true;
+        }
+        int actualIncrement =
+            getInliningInstructionLimitIncrement(invoke, target, inliningIRProvider);
+        if (estimatedSizeForInlining <= instructionLimit + actualIncrement) {
+          return true;
+        }
       }
     }
+    return satisfiesSimpleInliningConstraint(invoke, target);
+  }
+
+  private boolean satisfiesSimpleInliningConstraint(InvokeMethod invoke, ProgramMethod target) {
     // Even if the inlinee is big it may become simple after inlining. We therefore check if the
     // inlinee's simple inlining constraint is satisfied by the invoke.
     SimpleInliningConstraint simpleInliningConstraint =
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/DefaultMethodOptimizationInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/DefaultMethodOptimizationInfo.java
index 6c10cc6..8b2f63d 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/DefaultMethodOptimizationInfo.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/DefaultMethodOptimizationInfo.java
@@ -147,7 +147,7 @@
   }
 
   @Override
-  public SimpleInliningConstraint getNopInliningConstraint(InternalOptions options) {
+  public SimpleInliningConstraint getNopInliningConstraint() {
     return NeverSimpleInliningConstraint.getInstance();
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfo.java
index 9c7fb22..c23876d 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfo.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfo.java
@@ -87,7 +87,7 @@
 
   public abstract AbstractValue getAbstractReturnValue();
 
-  public abstract SimpleInliningConstraint getNopInliningConstraint(InternalOptions options);
+  public abstract SimpleInliningConstraint getNopInliningConstraint();
 
   public abstract SimpleInliningConstraint getSimpleInliningConstraint();
 
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfoCollector.java b/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfoCollector.java
index 382be09..76616ba 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfoCollector.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfoCollector.java
@@ -57,7 +57,9 @@
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.analysis.DeterminismAnalysis;
 import com.android.tools.r8.ir.analysis.InitializedClassesOnNormalExitAnalysis;
+import com.android.tools.r8.ir.analysis.inlining.SimpleInliningConstraint;
 import com.android.tools.r8.ir.analysis.inlining.SimpleInliningConstraintAnalysis;
+import com.android.tools.r8.ir.analysis.inlining.SimpleInliningConstraintWithDepth;
 import com.android.tools.r8.ir.analysis.sideeffect.ClassInitializerSideEffectAnalysis;
 import com.android.tools.r8.ir.analysis.sideeffect.ClassInitializerSideEffectAnalysis.ClassInitializerSideEffect;
 import com.android.tools.r8.ir.analysis.type.DynamicType;
@@ -827,8 +829,14 @@
 
   private void computeSimpleInliningConstraint(
       ProgramMethod method, IRCode code, OptimizationFeedback feedback) {
-    feedback.setSimpleInliningConstraint(
-        method, new SimpleInliningConstraintAnalysis(appView, method).analyzeCode(code));
+    SimpleInliningConstraintWithDepth simpleInliningConstraintWithDepth =
+        new SimpleInliningConstraintAnalysis(appView, method).analyzeCode(code);
+    SimpleInliningConstraint nopInliningConstraint =
+        simpleInliningConstraintWithDepth.getNopConstraint();
+    SimpleInliningConstraint simpleInliningConstraint =
+        simpleInliningConstraintWithDepth.getConstraint();
+    feedback.setNopInliningConstraint(method, nopInliningConstraint);
+    feedback.setSimpleInliningConstraint(method, simpleInliningConstraint);
   }
 
   private void computeDynamicReturnType(
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfoFixer.java b/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfoFixer.java
index 06ef441..808f2c9 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfoFixer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfoFixer.java
@@ -40,6 +40,13 @@
 
   public abstract int fixupReturnedArgumentIndex(int returnedArgumentIndex);
 
+  public final SimpleInliningConstraint fixupNopInliningConstraint(
+      AppView<AppInfoWithLiveness> appView,
+      SimpleInliningConstraint constraint,
+      SimpleInliningConstraintFactory factory) {
+    return fixupSimpleInliningConstraint(appView, constraint, factory);
+  }
+
   public abstract SimpleInliningConstraint fixupSimpleInliningConstraint(
       AppView<AppInfoWithLiveness> appView,
       SimpleInliningConstraint constraint,
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/MethodResolutionOptimizationInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/MethodResolutionOptimizationInfo.java
index 4831f7f..6c59889 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/MethodResolutionOptimizationInfo.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/MethodResolutionOptimizationInfo.java
@@ -201,7 +201,7 @@
   }
 
   @Override
-  public SimpleInliningConstraint getNopInliningConstraint(InternalOptions options) {
+  public SimpleInliningConstraint getNopInliningConstraint() {
     throw new Unreachable();
   }
 
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 3c46cf1..3c03079 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
@@ -78,6 +78,8 @@
   private BitSet nonNullParamOnNormalExits =
       DefaultMethodOptimizationInfo.NO_NULL_PARAMETER_ON_NORMAL_EXITS_FACTS;
 
+  private SimpleInliningConstraint nopInliningConstraint =
+      NeverSimpleInliningConstraint.getInstance();
   private SimpleInliningConstraint simpleInliningConstraint =
       NeverSimpleInliningConstraint.getInstance();
 
@@ -144,6 +146,7 @@
     abstractReturnValue = template.abstractReturnValue;
     setDynamicType(template.dynamicType);
     inlining = template.inlining;
+    nopInliningConstraint = template.nopInliningConstraint;
     simpleInliningConstraint = template.simpleInliningConstraint;
     bridgeInfo = template.bridgeInfo;
     instanceInitializerInfoCollection = template.instanceInitializerInfoCollection;
@@ -169,6 +172,7 @@
         .fixupNonNullParamOrThrow(fixer)
         .fixupReturnedArgumentIndex(fixer)
         .fixupParametersWithBitwiseOperations(fixer)
+        .fixupNopInliningConstraint(appView, fixer)
         .fixupSimpleInliningConstraint(appView, fixer)
         .fixupUnusedArguments(fixer);
   }
@@ -486,12 +490,8 @@
   }
 
   @Override
-  public SimpleInliningConstraint getNopInliningConstraint(InternalOptions options) {
-    // We currently require that having a simple inlining constraint implies that the method becomes
-    // empty after inlining. Therefore, an invoke is a nop if the simple inlining constraint is
-    // satisfied (if the invoke does not trigger other side effects, such as class initialization).
-    assert options.simpleInliningConstraintThreshold == 0;
-    return getSimpleInliningConstraint();
+  public SimpleInliningConstraint getNopInliningConstraint() {
+    return nopInliningConstraint;
   }
 
   @Override
@@ -595,7 +595,7 @@
     if (!mayHaveSideEffects()) {
       return false;
     }
-    if (getNopInliningConstraint(options).isSatisfied(invoke)) {
+    if (getNopInliningConstraint().isSatisfied(invoke)) {
       return false;
     }
     return true;
@@ -606,6 +606,22 @@
     return isFlagSet(RETURN_VALUE_ONLY_DEPENDS_ON_ARGUMENTS_FLAG);
   }
 
+  void setNopInliningConstraint(SimpleInliningConstraint constraint) {
+    this.nopInliningConstraint = constraint;
+  }
+
+  void unsetNopInliningConstraint() {
+    nopInliningConstraint = NeverSimpleInliningConstraint.getInstance();
+  }
+
+  public MutableMethodOptimizationInfo fixupNopInliningConstraint(
+      AppView<AppInfoWithLiveness> appView, MethodOptimizationInfoFixer fixer) {
+    nopInliningConstraint =
+        fixer.fixupNopInliningConstraint(
+            appView, nopInliningConstraint, appView.simpleInliningConstraintFactory());
+    return this;
+  }
+
   void setSimpleInliningConstraint(SimpleInliningConstraint constraint) {
     this.simpleInliningConstraint = constraint;
   }
@@ -811,6 +827,7 @@
         && instanceInitializerInfoCollection.isEmpty()
         && nonNullParamOrThrow == top.getNonNullParamOrThrow()
         && nonNullParamOnNormalExits == top.getNonNullParamOnNormalExits()
+        && nopInliningConstraint == top.getNopInliningConstraint()
         && simpleInliningConstraint == top.getSimpleInliningConstraint()
         && maxRemovedAndroidLogLevel == top.getMaxRemovedAndroidLogLevel()
         && parametersWithBitwiseOperations == top.getParametersWithBitwiseOperations()
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackDelayed.java b/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackDelayed.java
index 413d343..54a0fc0 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackDelayed.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackDelayed.java
@@ -271,6 +271,11 @@
   }
 
   @Override
+  public void setNopInliningConstraint(ProgramMethod method, SimpleInliningConstraint constraint) {
+    getMethodOptimizationInfoForUpdating(method).setNopInliningConstraint(constraint);
+  }
+
+  @Override
   public synchronized void setSimpleInliningConstraint(
       ProgramMethod method, SimpleInliningConstraint constraint) {
     getMethodOptimizationInfoForUpdating(method).setSimpleInliningConstraint(constraint);
@@ -381,6 +386,11 @@
   }
 
   @Override
+  public void unsetNopInliningConstraint(ProgramMethod method) {
+    getMethodOptimizationInfoForUpdating(method).unsetNopInliningConstraint();
+  }
+
+  @Override
   public synchronized void unsetSimpleInliningConstraint(ProgramMethod method) {
     getMethodOptimizationInfoForUpdating(method).unsetSimpleInliningConstraint();
   }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackIgnore.java b/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackIgnore.java
index 61fe21d..3c32b83 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackIgnore.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackIgnore.java
@@ -122,6 +122,9 @@
   public void setNonNullParamOnNormalExits(DexEncodedMethod method, BitSet facts) {}
 
   @Override
+  public void setNopInliningConstraint(ProgramMethod method, SimpleInliningConstraint constraint) {}
+
+  @Override
   public void setSimpleInliningConstraint(
       ProgramMethod method, SimpleInliningConstraint constraint) {}
 
@@ -189,6 +192,9 @@
   public void unsetReturnValueOnlyDependsOnArguments(ProgramMethod method) {}
 
   @Override
+  public void unsetNopInliningConstraint(ProgramMethod method) {}
+
+  @Override
   public void unsetSimpleInliningConstraint(ProgramMethod method) {}
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackSimple.java b/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackSimple.java
index 9edabc2..c015121 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackSimple.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackSimple.java
@@ -204,6 +204,11 @@
   }
 
   @Override
+  public void setNopInliningConstraint(ProgramMethod method, SimpleInliningConstraint constraint) {
+    method.getDefinition().getMutableOptimizationInfo().setNopInliningConstraint(constraint);
+  }
+
+  @Override
   public void setSimpleInliningConstraint(
       ProgramMethod method, SimpleInliningConstraint constraint) {
     method.getDefinition().getMutableOptimizationInfo().setSimpleInliningConstraint(constraint);
@@ -339,6 +344,12 @@
   }
 
   @Override
+  public void unsetNopInliningConstraint(ProgramMethod method) {
+    withMutableMethodOptimizationInfo(
+        method, MutableMethodOptimizationInfo::unsetNopInliningConstraint);
+  }
+
+  @Override
   public void unsetSimpleInliningConstraint(ProgramMethod method) {
     withMutableMethodOptimizationInfo(
         method, MutableMethodOptimizationInfo::unsetSimpleInliningConstraint);
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationInfoRemover.java b/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationInfoRemover.java
index 1ad49fd..db23102 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationInfoRemover.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationInfoRemover.java
@@ -59,6 +59,7 @@
     optimizationInfo.unsetDynamicType();
     optimizationInfo.unsetInitializedClassesOnNormalExit();
     optimizationInfo.unsetInstanceInitializerInfoCollection();
+    optimizationInfo.unsetNopInliningConstraint();
     optimizationInfo.unsetSimpleInliningConstraint();
     if (optimizationInfo.isEffectivelyDefault()) {
       method.unsetOptimizationInfo();
diff --git a/src/main/java/com/android/tools/r8/lightir/Lir2IRConverter.java b/src/main/java/com/android/tools/r8/lightir/Lir2IRConverter.java
index e8deff4..42c94e5 100644
--- a/src/main/java/com/android/tools/r8/lightir/Lir2IRConverter.java
+++ b/src/main/java/com/android/tools/r8/lightir/Lir2IRConverter.java
@@ -100,7 +100,6 @@
 import com.android.tools.r8.ir.code.Xor;
 import com.android.tools.r8.ir.conversion.ExtraParameter;
 import com.android.tools.r8.ir.conversion.MethodConversionOptions.MutableMethodConversionOptions;
-import com.android.tools.r8.ir.conversion.StringSwitchConverter;
 import com.android.tools.r8.lightir.LirBuilder.IntSwitchPayload;
 import com.android.tools.r8.lightir.LirBuilder.StringSwitchPayload;
 import com.android.tools.r8.lightir.LirCode.PositionEntry;
@@ -146,13 +145,6 @@
     IRCode irCode = parser.getIRCode(method, conversionOptions);
     // Some instructions have bottom types (e.g., phis). Compute their actual types by widening.
     new TypeAnalysis(appView, irCode).widening();
-
-    if (conversionOptions.isStringSwitchConversionEnabled()) {
-      if (StringSwitchConverter.convertToStringSwitchInstructions(
-          irCode, appView.dexItemFactory())) {
-        irCode.removeRedundantBlocks();
-      }
-    }
     return irCode;
   }
 
diff --git a/src/main/java/com/android/tools/r8/lightir/LirLensCodeRewriter.java b/src/main/java/com/android/tools/r8/lightir/LirLensCodeRewriter.java
index b2331cc..de48365 100644
--- a/src/main/java/com/android/tools/r8/lightir/LirLensCodeRewriter.java
+++ b/src/main/java/com/android/tools/r8/lightir/LirLensCodeRewriter.java
@@ -366,11 +366,7 @@
 
   @SuppressWarnings("unchecked")
   private LirCode<EV> removeUnreachableBlocks(LirCode<EV> rewritten) {
-    IRCode code =
-        rewritten.buildIR(
-            context,
-            appView,
-            MethodConversionOptions.forLirPhase(appView).disableStringSwitchConversion());
+    IRCode code = rewritten.buildIR(context, appView, MethodConversionOptions.forLirPhase(appView));
     AffectedValues affectedValues = code.removeUnreachableBlocks();
     affectedValues.narrowingWithAssumeRemoval(appView, code);
     new DeadCodeRemover(appView).run(code, Timing.empty());
@@ -386,7 +382,6 @@
         context.buildIR(
             appView,
             MethodConversionOptions.forLirPhase(appView)
-                .disableStringSwitchConversion()
                 .setFinalizeAfterLensCodeRewriter());
     // MethodProcessor argument is only used by unboxing lenses.
     MethodProcessor methodProcessor = null;
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorCodeScanner.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorCodeScanner.java
index 8df07a0..aa82269 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorCodeScanner.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorCodeScanner.java
@@ -538,12 +538,17 @@
       ProgramMethod context,
       ConcretePolymorphicMethodStateOrBottom existingMethodState) {
     DynamicTypeWithUpperBound dynamicReceiverType = invoke.getReceiver().getDynamicType(appView);
+    // TODO(b/331587404): Investigate if we can replace the receiver by null before entering this
+    //  pass, so that this special case is not needed.
     if (dynamicReceiverType.isNullType()) {
+      assert appView.testing().allowNullDynamicTypeInCodeScanner : "b/250634405";
       // This can happen if we were unable to determine that the receiver is a phi value where null
-      // information has not been propagated down. See if we can improve the test here or ensure
-      // that all phi's are normalized before computing the optimization info.
-      assert appView.checkForTesting(() -> false) : "b/250634405";
-      return MethodState.unknown();
+      // information has not been propagated down. Ideally this case would never happen as it should
+      // be possible to replace the receiver by the null constant in this case.
+      //
+      // Since the receiver is known to be null, no argument information should be propagated to the
+      // callees, so we return bottom here.
+      return MethodState.bottom();
     }
 
     ProgramMethod singleTarget = invoke.lookupSingleProgramTarget(appView, context);
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/MethodStateCollection.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/MethodStateCollection.java
index 856ff9d..758bd34 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/MethodStateCollection.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/MethodStateCollection.java
@@ -68,8 +68,7 @@
         (ignore, existingMethodState) -> {
           if (existingMethodState == null) {
             MethodState newMethodState = methodStateSupplier.apply(MethodState.bottom());
-            assert !newMethodState.isBottom();
-            return newMethodState;
+            return newMethodState.isBottom() ? null : newMethodState;
           }
           assert !existingMethodState.isBottom();
           timing.begin("Join temporary method state");
diff --git a/src/main/java/com/android/tools/r8/optimize/singlecaller/SingleCallerInliner.java b/src/main/java/com/android/tools/r8/optimize/singlecaller/SingleCallerInliner.java
index 126960b..10731fb 100644
--- a/src/main/java/com/android/tools/r8/optimize/singlecaller/SingleCallerInliner.java
+++ b/src/main/java/com/android/tools/r8/optimize/singlecaller/SingleCallerInliner.java
@@ -116,10 +116,7 @@
                   .setApiLevelForCode(
                       appView.apiLevelCompute().computeInitialMinApiLevel(appView.options()));
             }
-            IRCode code =
-                method.buildIR(
-                    appView,
-                    MethodConversionOptions.forLirPhase(appView).disableStringSwitchConversion());
+            IRCode code = method.buildIR(appView, MethodConversionOptions.forLirPhase(appView));
             inliner.performInlining(
                 method, code, getSimpleFeedback(), methodProcessor, Timing.empty());
             CodeRewriter.removeAssumeInstructions(appView, code);
diff --git a/src/main/java/com/android/tools/r8/profile/startup/instrumentation/StartupInstrumentation.java b/src/main/java/com/android/tools/r8/profile/startup/instrumentation/StartupInstrumentation.java
index ba3abda..192178b 100644
--- a/src/main/java/com/android/tools/r8/profile/startup/instrumentation/StartupInstrumentation.java
+++ b/src/main/java/com/android/tools/r8/profile/startup/instrumentation/StartupInstrumentation.java
@@ -176,8 +176,7 @@
   private void instrumentMethod(ProgramMethod method, boolean skipMethodLogging) {
     // Disable StringSwitch conversion to avoid having to run the StringSwitchRemover before
     // finalizing the code.
-    MutableMethodConversionOptions conversionOptions =
-        MethodConversionOptions.forD8(appView).disableStringSwitchConversion();
+    MutableMethodConversionOptions conversionOptions = MethodConversionOptions.forD8(appView);
     IRCode code = method.buildIR(appView, conversionOptions);
     InstructionListIterator instructionIterator = code.entryBlock().listIterator(code);
     instructionIterator.positionBeforeNextInstructionThatMatches(not(Instruction::isArgument));
diff --git a/src/main/java/com/android/tools/r8/shaking/EnqueuerDeferredTracingImpl.java b/src/main/java/com/android/tools/r8/shaking/EnqueuerDeferredTracingImpl.java
index 759ebd7..1eacba7 100644
--- a/src/main/java/com/android/tools/r8/shaking/EnqueuerDeferredTracingImpl.java
+++ b/src/main/java/com/android/tools/r8/shaking/EnqueuerDeferredTracingImpl.java
@@ -266,7 +266,6 @@
         mode.isInitialTreeShaking()
             ? MethodConversionOptions.forPreLirPhase(appView)
             : MethodConversionOptions.forLirPhase(appView);
-    conversionOptions.disableStringSwitchConversion();
 
     IRCode ir = method.buildIR(appView, conversionOptions);
 
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 28f377b..be887b7 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -408,7 +408,7 @@
   public boolean enableDevirtualization = true;
   public boolean enableEnumUnboxing = true;
   public boolean enableSimpleInliningConstraints = true;
-  public final int simpleInliningConstraintThreshold = 0;
+  public final int simpleInliningConstraintThreshold = 1;
   public boolean enableClassInlining = true;
   public boolean enableClassStaticizer = true;
   public boolean enableInitializedClassesAnalysis = true;
@@ -2333,6 +2333,7 @@
     public boolean allowClassInliningOfSynthetics = true;
     public boolean allowInjectedAnnotationMethods = false;
     public boolean allowInliningOfSynthetics = true;
+    public boolean allowNullDynamicTypeInCodeScanner = true;
     public boolean allowTypeErrors =
         !Version.isDevelopmentVersion()
             || System.getProperty("com.android.tools.r8.allowTypeErrors") != null;
diff --git a/src/test/java/com/android/tools/r8/ir/conversion/DexItemBasedStringSwitchTest.java b/src/test/java/com/android/tools/r8/ir/conversion/DexItemBasedStringSwitchTest.java
new file mode 100644
index 0000000..6b92f2a
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/conversion/DexItemBasedStringSwitchTest.java
@@ -0,0 +1,63 @@
+// Copyright (c) 2024, 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.conversion;
+
+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 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 DexItemBasedStringSwitchTest 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)
+        .addKeepRules("-repackageclasses")
+        .enableNoHorizontalClassMergingAnnotations()
+        .setMinApi(parameters)
+        .compile()
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines("A");
+  }
+
+  static class Main {
+
+    public static void main(String[] args) {
+      Object o = System.currentTimeMillis() > 0 ? new A() : new B();
+      switch (o.getClass().getName()) {
+        case "com.android.tools.r8.ir.conversion.DexItemBasedStringSwitchTest$A":
+          System.out.println("A");
+          return;
+        case "com.android.tools.r8.ir.conversion.DexItemBasedStringSwitchTest$B":
+          System.out.println("B");
+          break;
+        default:
+          System.out.println("Neither");
+          break;
+      }
+    }
+  }
+
+  @NoHorizontalClassMerging
+  static class A {}
+
+  @NoHorizontalClassMerging
+  static class B {}
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/inliner/conditionalsimpleinlining/SwitchWithSimpleCasesInliningTest.java b/src/test/java/com/android/tools/r8/ir/optimize/inliner/conditionalsimpleinlining/SwitchWithSimpleCasesInliningTest.java
index 44eb259..3d8006e 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/inliner/conditionalsimpleinlining/SwitchWithSimpleCasesInliningTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/inliner/conditionalsimpleinlining/SwitchWithSimpleCasesInliningTest.java
@@ -5,7 +5,7 @@
 
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
 import static org.hamcrest.MatcherAssert.assertThat;
-import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertEquals;
 
 import com.android.tools.r8.NeverInline;
 import com.android.tools.r8.TestBase;
@@ -46,8 +46,9 @@
 
               MethodSubject mainMethodSubject = mainClassSubject.mainMethod();
               assertThat(mainMethodSubject, isPresent());
-              // TODO(b/331337747): Should be true.
-              assertFalse(
+              // TODO(b/331337747): Account for constant canonicalization in constraint analysis.
+              assertEquals(
+                  parameters.isCfRuntime(),
                   mainMethodSubject
                       .streamInstructions()
                       .filter(InstructionSubject::isConstString)
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/switches/ConvertRemovedStringSwitchTest.java b/src/test/java/com/android/tools/r8/ir/optimize/switches/ConvertRemovedStringSwitchTest.java
index 1239133..fc490c64 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/switches/ConvertRemovedStringSwitchTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/switches/ConvertRemovedStringSwitchTest.java
@@ -13,8 +13,14 @@
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.Instruction;
+import com.android.tools.r8.ir.conversion.passes.StringSwitchConverter;
+import com.android.tools.r8.utils.AndroidApp;
+import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.Reporter;
+import com.android.tools.r8.utils.Timing;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.android.tools.r8.utils.codeinspector.InstructionSubject;
@@ -52,7 +58,7 @@
         .assertSuccessWithOutputLines("A", "B", "C", "D", "E!");
   }
 
-  private void inspect(CodeInspector inspector) {
+  private void inspect(CodeInspector inspector) throws Exception {
     ClassSubject classSubject = inspector.clazz(TestClass.class);
     assertThat(classSubject, isPresent());
 
@@ -69,7 +75,12 @@
     assertEquals(1, stringCounts.getInt("E!"));
 
     // Verify that we can rebuild the StringSwitch instruction.
-    IRCode code = mainMethodSubject.buildIR();
+    AppView<?> appView =
+        computeAppView(
+            AndroidApp.builder().build(),
+            new InternalOptions(inspector.getFactory(), new Reporter()));
+    IRCode code = mainMethodSubject.buildIR(appView);
+    new StringSwitchConverter(appView).run(code, Timing.empty());
     assertTrue(code.streamInstructions().anyMatch(Instruction::isStringSwitch));
   }
 
diff --git a/src/test/java/com/android/tools/r8/ir/regalloc/IdenticalAfterRegisterAllocationTest.java b/src/test/java/com/android/tools/r8/ir/regalloc/IdenticalAfterRegisterAllocationTest.java
index 867f9c9..181c0c5 100644
--- a/src/test/java/com/android/tools/r8/ir/regalloc/IdenticalAfterRegisterAllocationTest.java
+++ b/src/test/java/com/android/tools/r8/ir/regalloc/IdenticalAfterRegisterAllocationTest.java
@@ -151,11 +151,6 @@
               }
 
               @Override
-              public boolean isStringSwitchConversionEnabled() {
-                return false;
-              }
-
-              @Override
               public boolean shouldFinalizeAfterLensCodeRewriter() {
                 return false;
               }
diff --git a/src/test/java/com/android/tools/r8/kotlin/lambda/b159688129/LambdaSplitByCodeCorrectnessTest.java b/src/test/java/com/android/tools/r8/kotlin/lambda/b159688129/LambdaSplitByCodeCorrectnessTest.java
index 0556718..d4e6cfd 100644
--- a/src/test/java/com/android/tools/r8/kotlin/lambda/b159688129/LambdaSplitByCodeCorrectnessTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/lambda/b159688129/LambdaSplitByCodeCorrectnessTest.java
@@ -81,8 +81,7 @@
             inspector ->
                 inspector
                     .applyIf(
-                        kotlinParameters.isNewerThanOrEqualTo(KotlinCompilerVersion.KOTLINC_1_8_0)
-                            || splitGroup,
+                        splitGroup,
                         i -> {
                           if (kotlinParameters.getLambdaGeneration().isClass()) {
                             i.assertIsCompleteMergeGroup(
@@ -123,12 +122,7 @@
               ClassSubject mergeTarget =
                   codeInspector.clazz(
                       "com.android.tools.r8.kotlin.lambda.b159688129.SimpleKt$main$1");
-              assertThat(
-                  mergeTarget,
-                  isPresentIf(
-                      kotlinParameters.isNewerThanOrEqualTo(KotlinCompilerVersion.KOTLINC_1_8_0)
-                          || splitGroup));
-
+              assertThat(mergeTarget, isPresentIf(splitGroup));
               if (mergeTarget.isAbsent()) {
                 return;
               }
diff --git a/src/test/java/com/android/tools/r8/optimize/argumentpropagation/PolymorphicMethodWithNullReceiverBoundTest.java b/src/test/java/com/android/tools/r8/optimize/argumentpropagation/PolymorphicMethodWithNullReceiverBoundTest.java
index 776cf11..45f163e 100644
--- a/src/test/java/com/android/tools/r8/optimize/argumentpropagation/PolymorphicMethodWithNullReceiverBoundTest.java
+++ b/src/test/java/com/android/tools/r8/optimize/argumentpropagation/PolymorphicMethodWithNullReceiverBoundTest.java
@@ -23,11 +23,11 @@
  * This is an attempt on a regression test for b/250634405. What happens in the input program is
  * that we determine a phi value to be always null in Phi.getDynamicUpperBoundType. That information
  * has not been propagated to the receiver during optimizing of the IR. Therefor the check at {@link
- * ArgumentPropagatorCodeScanner.scan} for receiver always being null returns false.
+ * ArgumentPropagatorCodeScanner#scan} for receiver always being null returns false.
  *
  * <p>Getting the exact IR to match was difficult so this test short circuit this by disabling IR
  * processing of a simple method (by specifying pass-through) and disabling the check in {@link
- * ArgumentPropagatorCodeScanner.scan}.
+ * ArgumentPropagatorCodeScanner#scan}.
  */
 @RunWith(Parameterized.class)
 public class PolymorphicMethodWithNullReceiverBoundTest extends TestBase {
@@ -44,24 +44,23 @@
   public void testR8WithTestAssertionsEnabled() {
     assertThrows(
         CompilationFailedException.class,
-        () -> {
-          testForR8(parameters.getBackend())
-              .addInnerClasses(getClass())
-              .addKeepMainRule(Main.class)
-              .enableNoHorizontalClassMergingAnnotations()
-              .enableNoVerticalClassMergingAnnotations()
-              .setMinApi(parameters)
-              .addOptionsModification(
-                  options -> {
-                    options.testing.cfByteCodePassThrough =
-                        method -> method.getName().startsWith("main");
-                    options.testing.checkReceiverAlwaysNullInCallSiteOptimization = false;
-                  })
-              .compileWithExpectedDiagnostics(
-                  diagnostics -> {
-                    diagnostics.assertErrorMessageThatMatches(containsString("b/250634405"));
-                  });
-        });
+        () ->
+            testForR8(parameters.getBackend())
+                .addInnerClasses(getClass())
+                .addKeepMainRule(Main.class)
+                .enableNoHorizontalClassMergingAnnotations()
+                .enableNoVerticalClassMergingAnnotations()
+                .setMinApi(parameters)
+                .addOptionsModification(
+                    options -> {
+                      options.testing.allowNullDynamicTypeInCodeScanner = false;
+                      options.testing.cfByteCodePassThrough =
+                          method -> method.getName().startsWith("main");
+                      options.testing.checkReceiverAlwaysNullInCallSiteOptimization = false;
+                    })
+                .compileWithExpectedDiagnostics(
+                    diagnostics ->
+                        diagnostics.assertErrorMessageThatMatches(containsString("b/250634405"))));
   }
 
   @Test
diff --git a/src/test/testbase/java/com/android/tools/r8/TestBase.java b/src/test/testbase/java/com/android/tools/r8/TestBase.java
index a0dbfa4..5512419 100644
--- a/src/test/testbase/java/com/android/tools/r8/TestBase.java
+++ b/src/test/testbase/java/com/android/tools/r8/TestBase.java
@@ -793,9 +793,14 @@
   }
 
   protected static AppView<AppInfo> computeAppView(AndroidApp app) throws Exception {
+    return computeAppView(app, new InternalOptions());
+  }
+
+  protected static AppView<AppInfo> computeAppView(AndroidApp app, InternalOptions options)
+      throws Exception {
     AppInfo appInfo =
         AppInfo.createInitialAppInfo(
-            readApplicationForDexOutput(app, new InternalOptions()),
+            readApplicationForDexOutput(app, options),
             GlobalSyntheticsStrategy.forNonSynthesizing());
     return AppView.createForD8(appInfo);
   }
diff --git a/tools/run_benchmark.py b/tools/run_benchmark.py
index 7e0c692..6800813 100755
--- a/tools/run_benchmark.py
+++ b/tools/run_benchmark.py
@@ -15,6 +15,7 @@
 
 GOLEM_BUILD_TARGETS_TESTS = [
     utils.GRADLE_TASK_ALL_TESTS_WITH_APPLY_MAPPING_JAR,
+    utils.GRADLE_TASK_TESTBASE_WITH_APPLY_MAPPING_JAR,
     utils.GRADLE_TASK_TEST_DEPS_JAR
 ]
 GOLEM_BUILD_TARGETS = [utils.GRADLE_TASK_R8LIB] + GOLEM_BUILD_TARGETS_TESTS
@@ -95,14 +96,15 @@
         ]
         buildTargets = [utils.GRADLE_TASK_R8] + testBuildTargets
         r8jar = utils.R8_JAR
-        testjars = [utils.R8_TESTS_JAR, utils.R8_TESTS_DEPS_JAR]
+        testjars = [utils.R8_TESTS_JAR, utils.R8_TESTS_DEPS_JAR, utils.R8_TESTBASE_JAR]
     else:
         testBuildTargets = GOLEM_BUILD_TARGETS_TESTS
         buildTargets = GOLEM_BUILD_TARGETS
         r8jar = utils.R8LIB_JAR
         testjars = [
             os.path.join(utils.R8LIB_TESTS_JAR),
-            os.path.join(utils.R8LIB_TESTS_DEPS_JAR)
+            os.path.join(utils.R8LIB_TESTS_DEPS_JAR),
+            os.path.join(utils.R8LIB_TESTBASE_JAR)
         ]
 
     if options.version:
diff --git a/tools/utils.py b/tools/utils.py
index b250d53..6faecc8 100644
--- a/tools/utils.py
+++ b/tools/utils.py
@@ -53,6 +53,7 @@
 GRADLE_TASK_SWISS_ARMY_KNIFE = ':main:swissArmyKnife'
 GRADLE_TASK_TEST = ':test:test'
 GRADLE_TASK_ALL_TESTS_WITH_APPLY_MAPPING_JAR = ':test:rewriteTestsForR8LibWithRelocatedDeps'
+GRADLE_TASK_TESTBASE_WITH_APPLY_MAPPING_JAR = ':test:rewriteTestBaseForR8LibWithRelocatedDeps'
 GRADLE_TASK_TEST_DEPS_JAR = ':test:packageTestDeps'
 GRADLE_TASK_TEST_JAR = ':test:relocateTestsForR8LibWithRelocatedDeps'
 
@@ -69,6 +70,8 @@
 THREADING_MODULE_BLOCKING_JAR = os.path.join(LIBS, 'threading-module-blocking.jar')
 THREADING_MODULE_SINGLE_THREADED_JAR = os.path.join(LIBS, 'threading-module-single-threaded.jar')
 R8_TESTS_JAR = os.path.join(LIBS, 'r8tests.jar')
+R8_TESTBASE_JAR = os.path.join(LIBS, 'r8test_base.jar')
+R8LIB_TESTBASE_JAR = os.path.join(LIBS, 'r8libtestbase-cf.jar')
 R8LIB_TESTS_JAR = os.path.join(LIBS, 'r8libtestdeps-cf.jar')
 R8_TESTS_DEPS_JAR = os.path.join(LIBS, 'test_deps_all.jar')
 R8LIB_TESTS_DEPS_JAR = R8_TESTS_DEPS_JAR