Redundant field load elimination for final fields that are guaranteed to be initialized

Bug: 152196923
Change-Id: Ia22e105127d3ad73cdfff8198b73474ed9fde64c
diff --git a/src/main/java/com/android/tools/r8/graph/DexEncodedField.java b/src/main/java/com/android/tools/r8/graph/DexEncodedField.java
index 44526ed..0d5c50d 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedField.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedField.java
@@ -159,6 +159,10 @@
     return isStatic();
   }
 
+  public boolean isVolatile() {
+    return accessFlags.isVolatile();
+  }
+
   public boolean hasAnnotation() {
     return !annotations().isEmpty();
   }
diff --git a/src/main/java/com/android/tools/r8/graph/classmerging/VerticallyMergedClasses.java b/src/main/java/com/android/tools/r8/graph/classmerging/VerticallyMergedClasses.java
index 237f9ab..944d680 100644
--- a/src/main/java/com/android/tools/r8/graph/classmerging/VerticallyMergedClasses.java
+++ b/src/main/java/com/android/tools/r8/graph/classmerging/VerticallyMergedClasses.java
@@ -39,6 +39,10 @@
     return mergedClasses.containsKey(type);
   }
 
+  public boolean isTarget(DexType type) {
+    return !getSourcesFor(type).isEmpty();
+  }
+
   @Override
   public boolean verifyAllSourcesPruned(AppView<AppInfoWithLiveness> appView) {
     for (List<DexType> sourcesForTarget : sources.values()) {
diff --git a/src/main/java/com/android/tools/r8/ir/code/IRMetadata.java b/src/main/java/com/android/tools/r8/ir/code/IRMetadata.java
index 5c488c4..e46af2c 100644
--- a/src/main/java/com/android/tools/r8/ir/code/IRMetadata.java
+++ b/src/main/java/com/android/tools/r8/ir/code/IRMetadata.java
@@ -116,6 +116,10 @@
     return result;
   }
 
+  public boolean mayHaveInitClass() {
+    return get(Opcodes.INIT_CLASS);
+  }
+
   public boolean mayHaveInstanceGet() {
     return get(Opcodes.INSTANCE_GET);
   }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/RedundantFieldLoadElimination.java b/src/main/java/com/android/tools/r8/ir/optimize/RedundantFieldLoadElimination.java
index e94bb7b..a5501a6 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/RedundantFieldLoadElimination.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/RedundantFieldLoadElimination.java
@@ -10,6 +10,7 @@
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.classmerging.VerticallyMergedClasses;
 import com.android.tools.r8.ir.analysis.type.TypeAnalysis;
 import com.android.tools.r8.ir.analysis.value.SingleValue;
 import com.android.tools.r8.ir.code.BasicBlock;
@@ -31,10 +32,8 @@
 import com.android.tools.r8.ir.optimize.info.initializer.InstanceInitializerInfo;
 import com.android.tools.r8.utils.SetUtils;
 import com.google.common.collect.Sets;
-import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.IdentityHashMap;
-import java.util.List;
 import java.util.Map;
 import java.util.Set;
 
@@ -58,16 +57,12 @@
   // Maps keeping track of fields that have an already loaded value at basic block entry.
   private final Map<BasicBlock, Set<DexType>> activeInitializedClassesAtEntry =
       new IdentityHashMap<>();
-  private final Map<BasicBlock, Map<FieldAndObject, FieldValue>> activeInstanceFieldsAtEntry =
-      new IdentityHashMap<>();
-  private final Map<BasicBlock, Map<DexField, FieldValue>> activeStaticFieldsAtEntry =
-      new IdentityHashMap<>();
+  private final Map<BasicBlock, FieldValuesMap> activeFieldsAtEntry = new IdentityHashMap<>();
 
   // Maps keeping track of fields with already loaded values for the current block during
   // elimination.
   private Set<DexType> activeInitializedClasses;
-  private Map<FieldAndObject, FieldValue> activeInstanceFieldValues;
-  private Map<DexField, FieldValue> activeStaticFieldValues;
+  private FieldValuesMap activeFieldValues;
 
   public RedundantFieldLoadElimination(AppView<?> appView, IRCode code) {
     this.appView = appView;
@@ -78,7 +73,7 @@
 
   public static boolean shouldRun(AppView<?> appView, IRCode code) {
     return appView.options().enableRedundantFieldLoadElimination
-        && code.metadata().mayHaveFieldGet();
+        && (code.metadata().mayHaveFieldGet() || code.metadata().mayHaveInitClass());
   }
 
   private interface FieldValue {
@@ -145,17 +140,14 @@
     }
   }
 
-  private boolean couldBeVolatile(DexField field) {
-    DexEncodedField definition;
+  private DexEncodedField resolveField(DexField field) {
     if (appView.enableWholeProgramOptimizations()) {
-      definition = appView.appInfo().resolveField(field);
-    } else {
-      if (field.holder != method.holder()) {
-        return true;
-      }
-      definition = appView.definitionFor(field);
+      return appView.appInfo().resolveField(field);
     }
-    return definition == null || definition.accessFlags.isVolatile();
+    if (field.holder == method.holder()) {
+      return appView.definitionFor(field);
+    }
+    return null;
   }
 
   public void run() {
@@ -165,21 +157,18 @@
           activeInitializedClassesAtEntry.containsKey(block)
               ? activeInitializedClassesAtEntry.get(block)
               : Sets.newIdentityHashSet();
-      activeInstanceFieldValues =
-          activeInstanceFieldsAtEntry.containsKey(block)
-              ? activeInstanceFieldsAtEntry.get(block)
-              : new HashMap<>();
-      activeStaticFieldValues =
-          activeStaticFieldsAtEntry.containsKey(block)
-              ? activeStaticFieldsAtEntry.get(block)
-              : new IdentityHashMap<>();
+      activeFieldValues =
+          activeFieldsAtEntry.containsKey(block)
+              ? activeFieldsAtEntry.get(block)
+              : new FieldValuesMap();
       InstructionListIterator it = block.listIterator(code);
       while (it.hasNext()) {
         Instruction instruction = it.next();
         if (instruction.isFieldInstruction()) {
           DexField field = instruction.asFieldInstruction().getField();
-          if (couldBeVolatile(field)) {
-            killAllActiveFields();
+          DexEncodedField definition = resolveField(field);
+          if (definition == null || definition.isVolatile()) {
+            killAllNonFinalActiveFields();
             continue;
           }
 
@@ -190,41 +179,54 @@
             }
             Value object = instanceGet.object().getAliasedValue();
             FieldAndObject fieldAndObject = new FieldAndObject(field, object);
-            if (activeInstanceFieldValues.containsKey(fieldAndObject)) {
-              FieldValue replacement = activeInstanceFieldValues.get(fieldAndObject);
+            FieldValue replacement = activeFieldValues.getInstanceFieldValue(fieldAndObject);
+            if (replacement != null) {
               replacement.eliminateRedundantRead(it, instanceGet);
             } else {
-              activeInstanceFieldValues.put(fieldAndObject, new ExistingValue(instanceGet.value()));
+              activeFieldValues.putNonFinalInstanceField(
+                  fieldAndObject, new ExistingValue(instanceGet.value()));
             }
           } else if (instruction.isInstancePut()) {
             InstancePut instancePut = instruction.asInstancePut();
             // An instance-put instruction can potentially write the given field on all objects
             // because of aliases.
-            killActiveFields(instancePut);
+            killNonFinalActiveFields(instancePut);
             // ... but at least we know the field value for this particular object.
             Value object = instancePut.object().getAliasedValue();
             FieldAndObject fieldAndObject = new FieldAndObject(field, object);
-            activeInstanceFieldValues.put(fieldAndObject, new ExistingValue(instancePut.value()));
+            ExistingValue value = new ExistingValue(instancePut.value());
+            if (definition.isFinal()) {
+              assert method.isInstanceInitializer() || verifyWasInstanceInitializer();
+              activeFieldValues.putFinalInstanceField(fieldAndObject, value);
+            } else {
+              activeFieldValues.putNonFinalInstanceField(fieldAndObject, value);
+            }
           } else if (instruction.isStaticGet()) {
             StaticGet staticGet = instruction.asStaticGet();
             if (staticGet.outValue().hasLocalInfo()) {
               continue;
             }
-            if (activeStaticFieldValues.containsKey(field)) {
-              FieldValue replacement = activeStaticFieldValues.get(field);
+            FieldValue replacement = activeFieldValues.getStaticFieldValue(field);
+            if (replacement != null) {
               replacement.eliminateRedundantRead(it, staticGet);
             } else {
               // A field get on a different class can cause <clinit> to run and change static
               // field values.
-              killActiveFields(staticGet);
-              activeStaticFieldValues.put(field, new ExistingValue(staticGet.value()));
+              killNonFinalActiveFields(staticGet);
+              activeFieldValues.putNonFinalStaticField(field, new ExistingValue(staticGet.value()));
             }
           } else if (instruction.isStaticPut()) {
             StaticPut staticPut = instruction.asStaticPut();
             // A field put on a different class can cause <clinit> to run and change static
             // field values.
-            killActiveFields(staticPut);
-            activeStaticFieldValues.put(field, new ExistingValue(staticPut.value()));
+            killNonFinalActiveFields(staticPut);
+            ExistingValue value = new ExistingValue(staticPut.value());
+            if (definition.isFinal()) {
+              assert method.isClassInitializer();
+              activeFieldValues.putFinalStaticField(field, value);
+            } else {
+              activeFieldValues.putNonFinalStaticField(field, value);
+            }
           }
         } else if (instruction.isInitClass()) {
           InitClass initClass = instruction.asInitClass();
@@ -234,12 +236,12 @@
           }
         } else if (instruction.isMonitor()) {
           if (instruction.asMonitor().isEnter()) {
-            killAllActiveFields();
+            killAllNonFinalActiveFields();
           }
         } else if (instruction.isInvokeDirect()) {
           handleInvokeDirect(instruction.asInvokeDirect());
         } else if (instruction.isInvokeMethod() || instruction.isInvokeCustom()) {
-          killAllActiveFields();
+          killAllNonFinalActiveFields();
         } else if (instruction.isNewInstance()) {
           NewInstance newInstance = instruction.asNewInstance();
           if (newInstance.clazz.classInitializationMayHaveSideEffects(
@@ -247,7 +249,7 @@
               // Types that are a super type of `context` are guaranteed to be initialized already.
               type -> appView.isSubtype(context, type).isTrue(),
               Sets.newIdentityHashSet())) {
-            killAllActiveFields();
+            killAllNonFinalActiveFields();
           }
         } else {
           // If the current instruction could trigger a method invocation, it could also cause field
@@ -294,22 +296,33 @@
     assert code.isConsistentSSA();
   }
 
+  private boolean verifyWasInstanceInitializer() {
+    VerticallyMergedClasses verticallyMergedClasses = appView.verticallyMergedClasses();
+    assert verticallyMergedClasses != null;
+    assert verticallyMergedClasses.isTarget(method.holder());
+    assert appView
+        .dexItemFactory()
+        .isConstructor(appView.graphLense().getOriginalMethodSignature(method.method));
+    assert method.getOptimizationInfo().forceInline();
+    return true;
+  }
+
   private void handleInvokeDirect(InvokeDirect invoke) {
     if (!appView.enableWholeProgramOptimizations()) {
-      killAllActiveFields();
+      killAllNonFinalActiveFields();
       return;
     }
 
     DexEncodedMethod singleTarget = invoke.lookupSingleTarget(appView, method.holder());
     if (singleTarget == null || !singleTarget.isInstanceInitializer()) {
-      killAllActiveFields();
+      killAllNonFinalActiveFields();
       return;
     }
 
     InstanceInitializerInfo instanceInitializerInfo =
         singleTarget.getOptimizationInfo().getInstanceInitializerInfo();
     if (instanceInitializerInfo.mayHaveOtherSideEffectsThanInstanceFieldAssignments()) {
-      killAllActiveFields();
+      killAllNonFinalActiveFields();
     }
 
     InstanceFieldInitializationInfoCollection fieldInitializationInfos =
@@ -325,13 +338,14 @@
                 invoke.getArgument(info.asArgumentInitializationInfo().getArgumentIndex());
             Value object = invoke.getReceiver().getAliasedValue();
             FieldAndObject fieldAndObject = new FieldAndObject(field.field, object);
-            activeInstanceFieldValues.put(fieldAndObject, new ExistingValue(value));
+            activeFieldValues.putNonFinalInstanceField(fieldAndObject, new ExistingValue(value));
           } else if (info.isSingleValue()) {
             SingleValue value = info.asSingleValue();
             if (value.isMaterializableInContext(appView, method.holder())) {
               Value object = invoke.getReceiver().getAliasedValue();
               FieldAndObject fieldAndObject = new FieldAndObject(field.field, object);
-              activeInstanceFieldValues.put(fieldAndObject, new MaterializableValue(value));
+              activeFieldValues.putNonFinalInstanceField(
+                  fieldAndObject, new MaterializableValue(value));
             }
           } else {
             assert info.isTypeInitializationInfo();
@@ -357,44 +371,36 @@
         assert !activeInitializedClassesAtEntry.containsKey(successor);
         activeInitializedClassesAtEntry.put(
             successor, SetUtils.newIdentityHashSet(activeInitializedClasses));
-        assert !activeInstanceFieldsAtEntry.containsKey(successor);
-        activeInstanceFieldsAtEntry.put(successor, new HashMap<>(activeInstanceFieldValues));
-        assert !activeStaticFieldsAtEntry.containsKey(successor);
-        activeStaticFieldsAtEntry.put(successor, new IdentityHashMap<>(activeStaticFieldValues));
+        assert !activeFieldsAtEntry.containsKey(successor);
+        activeFieldsAtEntry.put(successor, new FieldValuesMap(activeFieldValues));
       }
     }
   }
 
-  private void killAllActiveFields() {
-    activeInstanceFieldValues.clear();
-    activeStaticFieldValues.clear();
+  private void killAllNonFinalActiveFields() {
+    activeFieldValues.clearNonFinalInstanceFields();
+    activeFieldValues.clearNonFinalStaticFields();
   }
 
-  private void killActiveFields(FieldInstruction instruction) {
+  private void killNonFinalActiveFields(FieldInstruction instruction) {
     DexField field = instruction.getField();
     if (instruction.isInstancePut()) {
       // Remove all the field/object pairs that refer to this field to make sure
       // that we are conservative.
-      List<FieldAndObject> keysToRemove = new ArrayList<>();
-      for (FieldAndObject key : activeInstanceFieldValues.keySet()) {
-        if (key.field == field) {
-          keysToRemove.add(key);
-        }
-      }
-      keysToRemove.forEach(activeInstanceFieldValues::remove);
+      activeFieldValues.removeNonFinalInstanceFields(field);
     } else if (instruction.isStaticPut()) {
       if (field.holder != code.method.holder()) {
         // Accessing a static field on a different object could cause <clinit> to run which
         // could modify any static field on any other object.
-        activeStaticFieldValues.clear();
+        activeFieldValues.clearNonFinalStaticFields();
       } else {
-        activeStaticFieldValues.remove(field);
+        activeFieldValues.removeNonFinalStaticField(field);
       }
     } else if (instruction.isStaticGet()) {
       if (field.holder != code.method.holder()) {
         // Accessing a static field on a different object could cause <clinit> to run which
         // could modify any static field on any other object.
-        activeStaticFieldValues.clear();
+        activeFieldValues.clearNonFinalStaticFields();
       }
     } else if (instruction.isInstanceGet()) {
       throw new Unreachable();
@@ -410,13 +416,99 @@
     if (instruction.isInstanceGet()) {
       Value object = instruction.asInstanceGet().object().getAliasedValue();
       FieldAndObject fieldAndObject = new FieldAndObject(field, object);
-      activeInstanceFieldValues.remove(fieldAndObject);
+      activeFieldValues.removeInstanceField(fieldAndObject);
     } else if (instruction.isStaticGet()) {
-      activeStaticFieldValues.remove(field);
+      activeFieldValues.removeStaticField(field);
     }
   }
 
   private void killActiveInitializedClassesForExceptionalExit(InitClass instruction) {
     activeInitializedClasses.remove(instruction.getClassValue());
   }
+
+  static class FieldValuesMap {
+
+    private final Map<FieldAndObject, FieldValue> finalInstanceFieldValues = new HashMap<>();
+
+    private final Map<DexField, FieldValue> finalStaticFieldValues = new IdentityHashMap<>();
+
+    private final Map<FieldAndObject, FieldValue> nonFinalInstanceFieldValues = new HashMap<>();
+
+    private final Map<DexField, FieldValue> nonFinalStaticFieldValues = new IdentityHashMap<>();
+
+    public FieldValuesMap() {}
+
+    public FieldValuesMap(FieldValuesMap map) {
+      finalInstanceFieldValues.putAll(map.finalInstanceFieldValues);
+      finalStaticFieldValues.putAll(map.finalStaticFieldValues);
+      nonFinalInstanceFieldValues.putAll(map.nonFinalInstanceFieldValues);
+      nonFinalStaticFieldValues.putAll(map.nonFinalStaticFieldValues);
+    }
+
+    public void clearNonFinalInstanceFields() {
+      nonFinalInstanceFieldValues.clear();
+    }
+
+    public void clearNonFinalStaticFields() {
+      nonFinalStaticFieldValues.clear();
+    }
+
+    public FieldValue getInstanceFieldValue(FieldAndObject field) {
+      FieldValue value = nonFinalInstanceFieldValues.get(field);
+      return value != null ? value : finalInstanceFieldValues.get(field);
+    }
+
+    public FieldValue getStaticFieldValue(DexField field) {
+      FieldValue value = nonFinalStaticFieldValues.get(field);
+      return value != null ? value : finalStaticFieldValues.get(field);
+    }
+
+    public void removeInstanceField(FieldAndObject field) {
+      removeFinalInstanceField(field);
+      removeNonFinalInstanceField(field);
+    }
+
+    public void removeFinalInstanceField(FieldAndObject field) {
+      finalInstanceFieldValues.remove(field);
+    }
+
+    public void removeNonFinalInstanceField(FieldAndObject field) {
+      nonFinalInstanceFieldValues.remove(field);
+    }
+
+    public void removeNonFinalInstanceFields(DexField field) {
+      nonFinalInstanceFieldValues.keySet().removeIf(key -> key.field == field);
+    }
+
+    public void removeStaticField(DexField field) {
+      removeFinalStaticField(field);
+      removeNonFinalStaticField(field);
+    }
+
+    public void removeFinalStaticField(DexField field) {
+      finalStaticFieldValues.remove(field);
+    }
+
+    public void removeNonFinalStaticField(DexField field) {
+      nonFinalStaticFieldValues.remove(field);
+    }
+
+    public void putFinalInstanceField(FieldAndObject field, FieldValue value) {
+      finalInstanceFieldValues.put(field, value);
+    }
+
+    public void putFinalStaticField(DexField field, FieldValue value) {
+      finalStaticFieldValues.put(field, value);
+    }
+
+    public void putNonFinalInstanceField(FieldAndObject field, FieldValue value) {
+      assert !finalInstanceFieldValues.containsKey(field);
+      nonFinalInstanceFieldValues.put(field, value);
+    }
+
+    public void putNonFinalStaticField(DexField field, FieldValue value) {
+      assert !nonFinalStaticFieldValues.containsKey(field);
+      nonFinalStaticFieldValues.put(field, value);
+    }
+  }
 }
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/redundantfieldloadelimination/RedundantFinalInstanceFieldLoadAfterStoreTest.java b/src/test/java/com/android/tools/r8/ir/optimize/redundantfieldloadelimination/RedundantFinalInstanceFieldLoadAfterStoreTest.java
index 4958163..329e574 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/redundantfieldloadelimination/RedundantFinalInstanceFieldLoadAfterStoreTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/redundantfieldloadelimination/RedundantFinalInstanceFieldLoadAfterStoreTest.java
@@ -61,9 +61,8 @@
 
     MethodSubject initMethodSubject = aClassSubject.init();
     assertThat(initMethodSubject, isPresent());
-    // TODO(b/152196923): Should be 0.
     assertEquals(
-        2,
+        0,
         countInstanceGetInstructions(
             initMethodSubject.asFoundMethodSubject(), fFieldSubject.asFoundFieldSubject()));
 
@@ -119,22 +118,26 @@
       System.out.println(f); // Not redundant, since `f` is not guaranteed to be initialized.
     }
 
+    @NeverInline
     void fork() {
       new Thread(this::m).start();
     }
 
+    @NeverInline
     void killNonFinalActiveFields() {
       if (System.currentTimeMillis() < 0) {
         System.out.println(this);
       }
     }
 
+    @NeverInline
     void waitUntilInitialized() {
       while (!initialized) {
         Thread.yield();
       }
     }
 
+    @NeverInline
     void waitUntilRead() {
       while (!read) {
         Thread.yield();
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/redundantfieldloadelimination/RedundantFinalStaticFieldLoadAfterStoreTest.java b/src/test/java/com/android/tools/r8/ir/optimize/redundantfieldloadelimination/RedundantFinalStaticFieldLoadAfterStoreTest.java
index 8f3ed33..fe3d79a 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/redundantfieldloadelimination/RedundantFinalStaticFieldLoadAfterStoreTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/redundantfieldloadelimination/RedundantFinalStaticFieldLoadAfterStoreTest.java
@@ -58,9 +58,8 @@
 
     MethodSubject initMethodSubject = aClassSubject.clinit();
     assertThat(initMethodSubject, isPresent());
-    // TODO(b/152196923): Should be 0.
     assertEquals(
-        2,
+        0,
         countStaticGetInstructions(
             initMethodSubject.asFoundMethodSubject(), fFieldSubject.asFoundFieldSubject()));
 
diff --git a/src/test/java/com/android/tools/r8/rewrite/enums/EnumOptimizationTest.java b/src/test/java/com/android/tools/r8/rewrite/enums/EnumOptimizationTest.java
index 91f0c4d..ae4947a 100644
--- a/src/test/java/com/android/tools/r8/rewrite/enums/EnumOptimizationTest.java
+++ b/src/test/java/com/android/tools/r8/rewrite/enums/EnumOptimizationTest.java
@@ -85,6 +85,7 @@
       assertOrdinalReplacedWithConst(clazz.uniqueMethodWithName("inSwitch"), 11);
       assertOrdinalReplacedWithConst(clazz.uniqueMethodWithName("differentTypeStaticField"), 1);
       assertOrdinalReplacedWithConst(clazz.uniqueMethodWithName("nonStaticGet"), 1);
+      assertOrdinalReplacedWithConst(clazz.uniqueMethodWithName("nonValueStaticField"), 1);
     } else {
       assertOrdinalWasNotReplaced(clazz.uniqueMethodWithName("simple"));
       assertOrdinalWasNotReplaced(clazz.uniqueMethodWithName("local"));
@@ -96,7 +97,6 @@
     }
 
     assertOrdinalWasNotReplaced(clazz.uniqueMethodWithName("libraryType"));
-    assertOrdinalWasNotReplaced(clazz.uniqueMethodWithName("nonValueStaticField"));
     assertOrdinalWasNotReplaced(clazz.uniqueMethodWithName("phi"));
   }
 
@@ -129,6 +129,7 @@
       assertNameReplacedWithConst(clazz.uniqueMethodWithName("inlined"), "TWO");
       assertNameReplacedWithConst(clazz.uniqueMethodWithName("differentTypeStaticField"), "DOWN");
       assertNameReplacedWithConst(clazz.uniqueMethodWithName("nonStaticGet"), "TWO");
+      assertNameReplacedWithConst(clazz.uniqueMethodWithName("nonValueStaticField"), "TWO");
     } else {
       assertNameWasNotReplaced(clazz.uniqueMethodWithName("simple"));
       assertNameWasNotReplaced(clazz.uniqueMethodWithName("local"));
@@ -141,7 +142,6 @@
     // TODO(jakew) this should be allowed!
     assertNameWasNotReplaced(clazz.uniqueMethodWithName("libraryType"));
 
-    assertNameWasNotReplaced(clazz.uniqueMethodWithName("nonValueStaticField"));
     assertNameWasNotReplaced(clazz.uniqueMethodWithName("phi"));
   }
 
diff --git a/src/test/java/com/android/tools/r8/rewrite/enums/Names.java b/src/test/java/com/android/tools/r8/rewrite/enums/Names.java
index 2ecc9eb..5dd5ec0 100644
--- a/src/test/java/com/android/tools/r8/rewrite/enums/Names.java
+++ b/src/test/java/com/android/tools/r8/rewrite/enums/Names.java
@@ -63,6 +63,7 @@
     return Number.DOWN.name();
   }
 
+  @AssumeMayHaveSideEffects
   @NeverInline
   private static String nonValueStaticField() {
     return Number.DEFAULT.name();
diff --git a/src/test/java/com/android/tools/r8/rewrite/enums/Ordinals.java b/src/test/java/com/android/tools/r8/rewrite/enums/Ordinals.java
index 534b16c..e1eee2c 100644
--- a/src/test/java/com/android/tools/r8/rewrite/enums/Ordinals.java
+++ b/src/test/java/com/android/tools/r8/rewrite/enums/Ordinals.java
@@ -73,6 +73,7 @@
     return Number.DOWN.ordinal();
   }
 
+  @AssumeMayHaveSideEffects
   @NeverInline
   private static long nonValueStaticField() {
     return Number.DEFAULT.ordinal();
diff --git a/src/test/java/com/android/tools/r8/shaking/keptgraph/WhyAreYouKeepingAllTest.java b/src/test/java/com/android/tools/r8/shaking/keptgraph/WhyAreYouKeepingAllTest.java
index 2387e7c..292cb21 100644
--- a/src/test/java/com/android/tools/r8/shaking/keptgraph/WhyAreYouKeepingAllTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/keptgraph/WhyAreYouKeepingAllTest.java
@@ -46,7 +46,6 @@
 
   @Test
   public void test() throws Throwable {
-    ByteArrayOutputStream baos = new ByteArrayOutputStream();
     testForR8(Backend.CF)
         .addProgramFiles(ToolHelper.R8_WITH_RELOCATED_DEPS_JAR)
         .addKeepRuleFiles(MAIN_KEEP)