Remove redundant ConstClass instructions in redundant load elimination

Fixes: b/501056932
Bug: b/490364465
Change-Id: Ica28ba97cd3b907ca9cd3da7fce4eeeae6323215
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/passes/CodeRewriterPassCollection.java b/src/main/java/com/android/tools/r8/ir/conversion/passes/CodeRewriterPassCollection.java
index 2227864..70ae00e 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/passes/CodeRewriterPassCollection.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/passes/CodeRewriterPassCollection.java
@@ -12,7 +12,7 @@
 import com.android.tools.r8.ir.conversion.MethodProcessor;
 import com.android.tools.r8.ir.conversion.passes.result.CodeRewriterResult;
 import com.android.tools.r8.ir.optimize.ListIterationRewriter;
-import com.android.tools.r8.ir.optimize.RedundantFieldLoadAndStoreElimination;
+import com.android.tools.r8.ir.optimize.RedundantLoadAndStoreElimination;
 import com.android.tools.r8.ir.optimize.ServiceLoaderRewriter;
 import com.android.tools.r8.ir.optimize.ShareInstanceGetInstructions;
 import com.android.tools.r8.ir.optimize.enums.EnumValueOptimizer;
@@ -57,7 +57,7 @@
     passes.add(new SplitBranch(appView));
     passes.add(new RedundantConstNumberRemover(appView));
     if (appView.options().isRelease()) {
-      passes.add(new RedundantFieldLoadAndStoreElimination(appView));
+      passes.add(new RedundantLoadAndStoreElimination(appView));
     }
     passes.add(new BinopRewriter(appView));
     passes.add(new ServiceLoaderRewriter(appView));
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/RedundantFieldLoadAndStoreElimination.java b/src/main/java/com/android/tools/r8/ir/optimize/RedundantLoadAndStoreElimination.java
similarity index 92%
rename from src/main/java/com/android/tools/r8/ir/optimize/RedundantFieldLoadAndStoreElimination.java
rename to src/main/java/com/android/tools/r8/ir/optimize/RedundantLoadAndStoreElimination.java
index d3ec828..0fba875 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/RedundantFieldLoadAndStoreElimination.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/RedundantLoadAndStoreElimination.java
@@ -45,7 +45,7 @@
 import com.android.tools.r8.ir.conversion.MethodProcessor;
 import com.android.tools.r8.ir.conversion.passes.CodeRewriterPass;
 import com.android.tools.r8.ir.conversion.passes.result.CodeRewriterResult;
-import com.android.tools.r8.ir.optimize.RedundantFieldLoadAndStoreElimination.RedundantFieldLoadAndStoreEliminationOnCode.ExistingValue;
+import com.android.tools.r8.ir.optimize.RedundantLoadAndStoreElimination.RedundantFieldLoadAndStoreEliminationOnCode.ExistingValue;
 import com.android.tools.r8.ir.optimize.info.field.InstanceFieldInitializationInfoCollection;
 import com.android.tools.r8.ir.optimize.info.initializer.InstanceInitializerInfo;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
@@ -65,29 +65,30 @@
 import java.util.Set;
 
 /**
- * Eliminate redundant field loads.
+ * Eliminate redundant loads and stores.
  *
  * <p>Simple algorithm that goes through all blocks in one pass in topological order and propagates
- * active field sets across control-flow edges where the target has only one predecessor.
+ * active load/store sets across control-flow edges where the target has only one predecessor.
  */
-public class RedundantFieldLoadAndStoreElimination extends CodeRewriterPass<AppInfo> {
+public class RedundantLoadAndStoreElimination extends CodeRewriterPass<AppInfo> {
 
   private static final int MAX_CAPACITY = 10000;
   private static final int MIN_CAPACITY_PER_BLOCK = 50;
 
-  public RedundantFieldLoadAndStoreElimination(AppView<?> appView) {
+  public RedundantLoadAndStoreElimination(AppView<?> appView) {
     super(appView);
   }
 
   @Override
   protected String getRewriterId() {
-    return "RedundantFieldLoadAndStoreElimination";
+    return "RedundantLoadAndStoreElimination";
   }
 
   @Override
   protected boolean shouldRewriteCode(IRCode code, MethodProcessor methodProcessor) {
-    return appView.options().enableRedundantFieldLoadElimination
+    return appView.options().enableRedundantLoadAndStoreElimination
         && (code.metadata().mayHaveArrayGet()
+            || code.metadata().mayHaveConstClass()
             || code.metadata().mayHaveFieldInstruction()
             || code.metadata().mayHaveInitClass());
   }
@@ -97,7 +98,7 @@
     return new RedundantFieldLoadAndStoreEliminationOnCode(code).run();
   }
 
-  private interface FieldValue {
+  private interface ExistingOrMaterializableValue {
 
     default ExistingValue asExistingValue() {
       return null;
@@ -259,7 +260,7 @@
       assert !appView.options().debug;
     }
 
-    class ExistingValue implements FieldValue {
+    class ExistingValue implements ExistingOrMaterializableValue {
 
       private final Value value;
 
@@ -294,7 +295,7 @@
       }
     }
 
-    private class MaterializableValue implements FieldValue {
+    private class MaterializableValue implements ExistingOrMaterializableValue {
 
       private final SingleValue value;
 
@@ -318,7 +319,7 @@
         DexItemFactory dexItemFactory = appView.dexItemFactory();
         if (value.isSingleStringValue() || value.isSingleDexItemBasedStringValue()) {
           return dexItemFactory.stringType.toTypeElement(
-              RedundantFieldLoadAndStoreElimination.this.appView, Nullability.definitelyNotNull());
+              RedundantLoadAndStoreElimination.this.appView, Nullability.definitelyNotNull());
         }
         if (value.isSingleFieldValue()) {
           return value.asSingleFieldValue().getField().getTypeElement(appView);
@@ -394,6 +395,8 @@
               }
             } else if (instruction.isInitClass()) {
               handleInitClass(it, instruction.asInitClass());
+            } else if (instruction.isConstClass()) {
+              handleConstClass(it, instruction.asConstClass());
             } else if (instruction.isMonitor()) {
               if (instruction.isMonitorEnter()) {
                 killAllNonFinalActiveFields();
@@ -428,7 +431,6 @@
                       || instruction.isAssume()
                       || instruction.isBinop()
                       || instruction.isCheckCast()
-                      || instruction.isConstClass()
                       || instruction.isConstMethodHandle()
                       || instruction.isConstMethodType()
                       || instruction.isConstNumber()
@@ -604,6 +606,23 @@
       }
     }
 
+    private void handleConstClass(
+        InstructionListIterator instructionIterator,
+        com.android.tools.r8.ir.code.ConstClass constClass) {
+      if (constClass.outValue().hasLocalInfo()) {
+        return;
+      }
+
+      DexType type = constClass.getType();
+      ExistingOrMaterializableValue replacement = activeState.getConstClassValue(type);
+      if (replacement != null) {
+        replacement.eliminateRedundantRead(instructionIterator, constClass);
+        return;
+      }
+
+      activeState.putConstClassValue(type, new ExistingValue(constClass.outValue()));
+    }
+
     private boolean markClassAsInitialized(DexType type) {
       return activeState.markClassAsInitialized(type);
     }
@@ -637,7 +656,7 @@
       Value array = arrayGet.array().getAliasedValue();
       Value index = arrayGet.index().getAliasedValue();
       ArraySlot arraySlot = ArraySlot.create(array, index, arrayGet.getMemberType());
-      FieldValue replacement = activeState.getArraySlotValue(arraySlot);
+      ExistingOrMaterializableValue replacement = activeState.getArraySlotValue(arraySlot);
       if (replacement != null) {
         TypeElement outType = arrayGet.outValue().getType();
         if (replacement.getType(appView, outType).lessThanOrEqual(outType, appView)) {
@@ -684,7 +703,7 @@
 
       Value object = instanceGet.object().getAliasedValue();
       FieldAndObject fieldAndObject = new FieldAndObject(field.getReference(), object);
-      FieldValue replacement = activeState.getInstanceFieldValue(fieldAndObject);
+      ExistingOrMaterializableValue replacement = activeState.getInstanceFieldValue(fieldAndObject);
       if (replacement != null) {
         if (isRedundantFieldLoadEliminationAllowed(field)) {
           replacement.eliminateRedundantRead(it, instanceGet);
@@ -775,7 +794,8 @@
         return;
       }
 
-      FieldValue replacement = activeState.getStaticFieldValue(field.getReference());
+      ExistingOrMaterializableValue replacement =
+          activeState.getStaticFieldValue(field.getReference());
       if (replacement != null) {
         replacement.eliminateRedundantRead(instructionIterator, staticGet);
         return;
@@ -785,7 +805,7 @@
       killNonFinalActiveFields(staticGet);
       clearMostRecentStaticFieldWrite(staticGet, field);
 
-      FieldValue value = new ExistingValue(staticGet.value());
+      ExistingOrMaterializableValue value = new ExistingValue(staticGet.value());
       if (field.isFinalOrEffectivelyFinal(appView)) {
         activeState.putFinalStaticField(field.getReference(), value);
       } else {
@@ -1032,17 +1052,20 @@
 
   static class BlockState {
 
-    private LinkedHashMap<ArraySlot, FieldValue> arraySlotValues;
+    private LinkedHashMap<ArraySlot, ExistingOrMaterializableValue> arraySlotValues;
 
-    private LinkedHashMap<FieldAndObject, FieldValue> finalInstanceFieldValues;
+    private LinkedHashMap<DexType, ExistingOrMaterializableValue> constClassValues;
 
-    private LinkedHashMap<DexField, FieldValue> finalStaticFieldValues;
+    private LinkedHashMap<FieldAndObject, ExistingOrMaterializableValue> finalInstanceFieldValues;
+
+    private LinkedHashMap<DexField, ExistingOrMaterializableValue> finalStaticFieldValues;
 
     private LinkedHashSet<DexType> initializedClasses;
 
-    private LinkedHashMap<FieldAndObject, FieldValue> nonFinalInstanceFieldValues;
+    private LinkedHashMap<FieldAndObject, ExistingOrMaterializableValue>
+        nonFinalInstanceFieldValues;
 
-    private LinkedHashMap<DexField, FieldValue> nonFinalStaticFieldValues;
+    private LinkedHashMap<DexField, ExistingOrMaterializableValue> nonFinalStaticFieldValues;
 
     private InitClass mostRecentInitClass;
 
@@ -1063,6 +1086,10 @@
           arraySlotValues = new LinkedHashMap<>();
           arraySlotValues.putAll(state.arraySlotValues);
         }
+        if (state.constClassValues != null && !state.constClassValues.isEmpty()) {
+          constClassValues = new LinkedHashMap<>();
+          constClassValues.putAll(state.constClassValues);
+        }
         if (state.finalInstanceFieldValues != null && !state.finalInstanceFieldValues.isEmpty()) {
           finalInstanceFieldValues = new LinkedHashMap<>();
           finalInstanceFieldValues.putAll(state.finalInstanceFieldValues);
@@ -1143,12 +1170,16 @@
       }
     }
 
-    public FieldValue getArraySlotValue(ArraySlot arraySlot) {
+    public ExistingOrMaterializableValue getArraySlotValue(ArraySlot arraySlot) {
       return arraySlotValues != null ? arraySlotValues.get(arraySlot) : null;
     }
 
-    public FieldValue getInstanceFieldValue(FieldAndObject field) {
-      FieldValue value =
+    public ExistingOrMaterializableValue getConstClassValue(DexType type) {
+      return constClassValues != null ? constClassValues.get(type) : null;
+    }
+
+    public ExistingOrMaterializableValue getInstanceFieldValue(FieldAndObject field) {
+      ExistingOrMaterializableValue value =
           nonFinalInstanceFieldValues != null ? nonFinalInstanceFieldValues.get(field) : null;
       if (value != null) {
         return value;
@@ -1156,8 +1187,8 @@
       return finalInstanceFieldValues != null ? finalInstanceFieldValues.get(field) : null;
     }
 
-    public FieldValue getStaticFieldValue(DexField field) {
-      FieldValue value =
+    public ExistingOrMaterializableValue getStaticFieldValue(DexField field) {
+      ExistingOrMaterializableValue value =
           nonFinalStaticFieldValues != null ? nonFinalStaticFieldValues.get(field) : null;
       if (value != null) {
         return value;
@@ -1171,6 +1202,11 @@
       } else {
         arraySlotValues = null;
       }
+      if (constClassValues != null && state.constClassValues != null) {
+        intersectFieldValues(constClassValues, state.constClassValues);
+      } else {
+        constClassValues = null;
+      }
       if (finalInstanceFieldValues != null && state.finalInstanceFieldValues != null) {
         intersectFieldValues(finalInstanceFieldValues, state.finalInstanceFieldValues);
       } else {
@@ -1202,7 +1238,8 @@
     }
 
     private static <K> void intersectFieldValues(
-        Map<K, FieldValue> fieldValues, Map<K, FieldValue> other) {
+        Map<K, ExistingOrMaterializableValue> fieldValues,
+        Map<K, ExistingOrMaterializableValue> other) {
       fieldValues.entrySet().removeIf(entry -> other.get(entry.getKey()) != entry.getValue());
     }
 
@@ -1213,6 +1250,7 @@
 
     public boolean isEmpty() {
       return isEmpty(arraySlotValues)
+          && isEmpty(constClassValues)
           && isEmpty(initializedClasses)
           && isEmpty(finalInstanceFieldValues)
           && isEmpty(finalStaticFieldValues)
@@ -1261,6 +1299,7 @@
       assert numberOfItemsToRemove > 0;
       assert numberOfItemsToRemove < size();
       numberOfItemsToRemove = reduceSize(numberOfItemsToRemove, arraySlotValues);
+      numberOfItemsToRemove = reduceSize(numberOfItemsToRemove, constClassValues);
       numberOfItemsToRemove = reduceSize(numberOfItemsToRemove, initializedClasses);
       numberOfItemsToRemove = reduceSize(numberOfItemsToRemove, nonFinalInstanceFieldValues);
       numberOfItemsToRemove = reduceSize(numberOfItemsToRemove, nonFinalStaticFieldValues);
@@ -1358,7 +1397,7 @@
       }
     }
 
-    public void putArraySlotValue(ArraySlot arraySlot, FieldValue value) {
+    public void putArraySlotValue(ArraySlot arraySlot, ExistingOrMaterializableValue value) {
       ensureCapacityForNewElement();
       if (arraySlotValues == null) {
         arraySlotValues = new LinkedHashMap<>();
@@ -1366,7 +1405,16 @@
       arraySlotValues.put(arraySlot, value);
     }
 
-    public void putFinalOrEffectivelyFinalInstanceField(FieldAndObject field, FieldValue value) {
+    public void putConstClassValue(DexType type, ExistingOrMaterializableValue value) {
+      ensureCapacityForNewElement();
+      if (constClassValues == null) {
+        constClassValues = new LinkedHashMap<>();
+      }
+      constClassValues.put(type, value);
+    }
+
+    public void putFinalOrEffectivelyFinalInstanceField(
+        FieldAndObject field, ExistingOrMaterializableValue value) {
       ensureCapacityForNewElement();
       if (finalInstanceFieldValues == null) {
         finalInstanceFieldValues = new LinkedHashMap<>();
@@ -1374,7 +1422,7 @@
       finalInstanceFieldValues.put(field, value);
     }
 
-    public void putFinalStaticField(DexField field, FieldValue value) {
+    public void putFinalStaticField(DexField field, ExistingOrMaterializableValue value) {
       ensureCapacityForNewElement();
       if (finalStaticFieldValues == null) {
         finalStaticFieldValues = new LinkedHashMap<>();
@@ -1399,7 +1447,8 @@
       return mostRecentStaticFieldWrites.put(field, staticPut);
     }
 
-    public void putNonFinalInstanceField(FieldAndObject field, FieldValue value) {
+    public void putNonFinalInstanceField(
+        FieldAndObject field, ExistingOrMaterializableValue value) {
       ensureCapacityForNewElement();
       assert finalInstanceFieldValues == null || !finalInstanceFieldValues.containsKey(field);
       if (nonFinalInstanceFieldValues == null) {
@@ -1408,7 +1457,7 @@
       nonFinalInstanceFieldValues.put(field, value);
     }
 
-    public void putNonFinalStaticField(DexField field, FieldValue value) {
+    public void putNonFinalStaticField(DexField field, ExistingOrMaterializableValue value) {
       ensureCapacityForNewElement();
       assert nonFinalStaticFieldValues == null || !nonFinalStaticFieldValues.containsKey(field);
       if (nonFinalStaticFieldValues == null) {
@@ -1433,6 +1482,7 @@
 
     public int size() {
       return size(arraySlotValues)
+          + size(constClassValues)
           + size(finalInstanceFieldValues)
           + size(finalStaticFieldValues)
           + size(initializedClasses)
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 63dd31a..b115033 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -502,7 +502,7 @@
   public boolean enableEnumSwitchMapRemoval = true;
   public final OutlineOptions outline = new OutlineOptions();
   public boolean enableInitializedClassesInInstanceMethodsAnalysis = true;
-  public boolean enableRedundantFieldLoadElimination = true;
+  public boolean enableRedundantLoadAndStoreElimination = true;
   // TODO(b/138917494): Disable until we have numbers on potential performance penalties.
   public boolean enableRedundantConstNumberOptimization = false;
   public boolean enableLoopUnrolling = true;
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/canonicalization/ConstClassCanonicalizationTest.java b/src/test/java/com/android/tools/r8/ir/optimize/canonicalization/ConstClassCanonicalizationTest.java
index 54dc249..5dd01c8 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/canonicalization/ConstClassCanonicalizationTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/canonicalization/ConstClassCanonicalizationTest.java
@@ -97,6 +97,7 @@
   private static final int CANONICALIZED_MAIN_COUNT = 1;
   private static final int CANONICALIZED_OUTER_COUNT = 1;
   private static final int CANONICALIZED_INNER_COUNT = 1;
+  private static final int PARTIALLY_OPTIMIZED_INNER_COUNT = 2;
 
   @Parameterized.Parameters(name = "{0}, isCompat: {1}")
   public static List<Object[]> data() {
@@ -148,7 +149,7 @@
               MethodSubject mainMethod = main.mainMethod();
               assertThat(mainMethod, isPresent());
               assertEquals(
-                  3,
+                  1,
                   mainMethod.streamInstructions().filter(InstructionSubject::isConstClass).count());
             });
   }
@@ -203,8 +204,8 @@
             .run(parameters.getRuntime(), MAIN)
             .assertSuccessWithOutput(JAVA_OUTPUT),
         CANONICALIZED_MAIN_COUNT,
-        ORIGINAL_OUTER_COUNT,
-        ORIGINAL_INNER_COUNT);
+        CANONICALIZED_OUTER_COUNT,
+        PARTIALLY_OPTIMIZED_INNER_COUNT);
   }
 
   @Test
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/canonicalization/IllegalStaticGetCanonicalizationTest.java b/src/test/java/com/android/tools/r8/ir/optimize/canonicalization/IllegalStaticGetCanonicalizationTest.java
index 5f7f094..cb0c098 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/canonicalization/IllegalStaticGetCanonicalizationTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/canonicalization/IllegalStaticGetCanonicalizationTest.java
@@ -33,7 +33,7 @@
     testForR8(parameters.getBackend())
         .addInnerClasses(IllegalStaticGetCanonicalizationTest.class)
         .addKeepMainRule(TestClass.class)
-        .addOptionsModification(options -> options.enableRedundantFieldLoadElimination = false)
+        .addOptionsModification(options -> options.enableRedundantLoadAndStoreElimination = false)
         .enableInliningAnnotations()
         .enableReprocessClassInitializerAnnotations()
         .setMinApi(parameters)
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/redundantconstclasselimination/RedundantConstClassEliminationTest.java b/src/test/java/com/android/tools/r8/ir/optimize/redundantconstclasselimination/RedundantConstClassEliminationTest.java
new file mode 100644
index 0000000..46a3a0c
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/redundantconstclasselimination/RedundantConstClassEliminationTest.java
@@ -0,0 +1,67 @@
+// Copyright (c) 2026, 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.redundantconstclasselimination;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertEquals;
+
+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.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;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class RedundantConstClassEliminationTest extends TestBase {
+
+  @Parameter(0)
+  public TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withDexRuntimesAndAllApiLevels().build();
+  }
+
+  @Test
+  public void test() throws Exception {
+    testForD8(parameters)
+        .addInnerClasses(RedundantConstClassEliminationTest.class)
+        .release()
+        .setMinApi(parameters)
+        .compile()
+        .inspect(this::inspect);
+  }
+
+  private void inspect(CodeInspector inspector) {
+    MethodSubject mainMethodSubject = inspector.clazz(TestClass.class).mainMethod();
+    assertThat(mainMethodSubject, isPresent());
+    assertEquals(
+        2, mainMethodSubject.streamInstructions().filter(InstructionSubject::isConstClass).count());
+  }
+
+  static class A {}
+
+  static class B {}
+
+  static class TestClass {
+
+    public static void main(String[] args) {
+      consume(A.class);
+      consume(A.class);
+      consume(B.class);
+      consume(B.class);
+    }
+
+    static void consume(Class<?> clazz) {
+      System.out.println(clazz.getName());
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/reflection/GetClassTest.java b/src/test/java/com/android/tools/r8/ir/optimize/reflection/GetClassTest.java
index b23e434..bf63725 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/reflection/GetClassTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/reflection/GetClassTest.java
@@ -209,7 +209,7 @@
     boolean isRelease = mode == CompilationMode.RELEASE;
     boolean expectCallPresent = !isRelease;
     int expectedGetClassCount = isRelease ? 0 : 5;
-    int expectedConstClassCount = isRelease ? (parameters.isCfRuntime() ? 9 : 6) : 1;
+    int expectedConstClassCount = isRelease ? (parameters.isCfRuntime() ? 7 : 6) : 1;
     testForR8(parameters)
         .setMode(mode)
         .addInnerClasses(GetClassTest.class)
diff --git a/src/test/java/com/android/tools/r8/memberrebinding/MemberRebindingTest.java b/src/test/java/com/android/tools/r8/memberrebinding/MemberRebindingTest.java
index 15d91c2..902421d 100644
--- a/src/test/java/com/android/tools/r8/memberrebinding/MemberRebindingTest.java
+++ b/src/test/java/com/android/tools/r8/memberrebinding/MemberRebindingTest.java
@@ -254,7 +254,7 @@
         .addKeepRules("-neverpropagatevalue class * { *; }")
         .addOptionsModification(
             options -> {
-              options.enableRedundantFieldLoadElimination = false;
+              options.enableRedundantLoadAndStoreElimination = false;
               options.inlinerOptions().enableInlining = false;
             })
         .enableProguardTestOptions()
diff --git a/src/test/java/com/android/tools/r8/naming/applymapping/ApplyMappingAfterHorizontalMergingFieldTest.java b/src/test/java/com/android/tools/r8/naming/applymapping/ApplyMappingAfterHorizontalMergingFieldTest.java
index 1e11234..04f428d 100644
--- a/src/test/java/com/android/tools/r8/naming/applymapping/ApplyMappingAfterHorizontalMergingFieldTest.java
+++ b/src/test/java/com/android/tools/r8/naming/applymapping/ApplyMappingAfterHorizontalMergingFieldTest.java
@@ -90,7 +90,8 @@
     R8TestCompileResult libraryResult =
         testForR8(parameters.getBackend())
             .addProgramClasses(LIBRARY_CLASSES)
-            .addOptionsModification(options -> options.enableRedundantFieldLoadElimination = false)
+            .addOptionsModification(
+                options -> options.enableRedundantLoadAndStoreElimination = false)
             .addKeepMainRule(LibraryMain.class)
             .setMinApi(parameters)
             .compile();
diff --git a/src/test/java/com/android/tools/r8/repackage/RepackageWithInitClassTest.java b/src/test/java/com/android/tools/r8/repackage/RepackageWithInitClassTest.java
index d6cce99..1c2bbab 100644
--- a/src/test/java/com/android/tools/r8/repackage/RepackageWithInitClassTest.java
+++ b/src/test/java/com/android/tools/r8/repackage/RepackageWithInitClassTest.java
@@ -54,7 +54,7 @@
         .apply(this::configureRepackaging)
         .enableMemberValuePropagationAnnotations(enableMemberValuePropagationAnnotations)
         .enableNoAccessModificationAnnotationsForMembers()
-        .addOptionsModification(options -> options.enableRedundantFieldLoadElimination = false)
+        .addOptionsModification(options -> options.enableRedundantLoadAndStoreElimination = false)
         .setMinApi(parameters)
         .compile()
         .inspect(this::inspect)
diff --git a/src/test/java/com/android/tools/r8/rewrite/arrays/ConstClassArrayWithNonUniqueValuesTest.java b/src/test/java/com/android/tools/r8/rewrite/arrays/ConstClassArrayWithNonUniqueValuesTest.java
index 7d8bba7..9459997 100644
--- a/src/test/java/com/android/tools/r8/rewrite/arrays/ConstClassArrayWithNonUniqueValuesTest.java
+++ b/src/test/java/com/android/tools/r8/rewrite/arrays/ConstClassArrayWithNonUniqueValuesTest.java
@@ -5,6 +5,7 @@
 package com.android.tools.r8.rewrite.arrays;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assume.assumeTrue;
 
 import com.android.tools.r8.CompilationMode;
 import com.android.tools.r8.NeverInline;
@@ -54,8 +55,11 @@
     assertEquals(
         expectingFilledNewArray ? 1 : 0,
         method.streamInstructions().filter(InstructionSubject::isFilledNewArray).count());
+    // When using filled-new-array, we generate 1 const-class instruction + 99 moves.
     assertEquals(
-        expectingFilledNewArray || parameters.isCfRuntime() ? puts : constClasses,
+        (compilationMode.isDebug() && expectingFilledNewArray) || parameters.isCfRuntime()
+            ? puts
+            : constClasses,
         method.streamInstructions().filter(InstructionSubject::isConstClass).count());
   }
 
@@ -69,7 +73,9 @@
     inspect(inspector.clazz(TestClass.class).uniqueMethodWithOriginalName("m1"), 1, 100);
     inspect(
         inspector.clazz(TestClass.class).uniqueMethodWithOriginalName("m2"),
-        maxMaterializingConstants == 2 ? 98 : 26,
+        canUseFilledNewArrayOfNonStringObjects(parameters) || maxMaterializingConstants != 2
+            ? 26
+            : 98,
         104);
   }
 
@@ -79,6 +85,7 @@
     testForD8(parameters.getBackend())
         .addInnerClasses(getClass())
         .setMinApi(parameters)
+        .setMode(compilationMode)
         .apply(this::configure)
         .run(parameters.getRuntime(), TestClass.class)
         .inspect(this::inspectD8)
@@ -89,12 +96,17 @@
     inspect(inspector.clazz(TestClass.class).uniqueMethodWithOriginalName("m1"), 1, 100);
     inspect(
         inspector.clazz(TestClass.class).uniqueMethodWithOriginalName("m2"),
-        maxMaterializingConstants == 2 ? 32 : 26,
+        // The lowering from FilledNewArray to a sequence of ArrayPut instructions in the compiler
+        // uses rematerialization when possible, rather than moves.
+        canUseFilledNewArrayOfNonStringObjects(parameters) || maxMaterializingConstants != 2
+            ? 26
+            : 32,
         104);
   }
 
   @Test
   public void testR8() throws Exception {
+    assumeTrue(compilationMode.isRelease());
     testForR8(parameters.getBackend())
         .addInnerClasses(getClass())
         .addKeepMainRule(TestClass.class)