Reland "Initial support for member value propagation for instance fields"

This reverts commit f99a4cf73a37acfed319ec1e2fe9816775c8a4c8.

This guards member value propagation for instance fields behind a flag.

Bug: 125282093
Change-Id: I585e844962ab54b2a3f017cf23740596ac386ebb
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/FieldValueAnalysis.java b/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/FieldValueAnalysis.java
index 2c86c7b..17b91f3 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/FieldValueAnalysis.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/FieldValueAnalysis.java
@@ -25,12 +25,12 @@
 import com.android.tools.r8.ir.code.BasicBlock;
 import com.android.tools.r8.ir.code.DominatorTree;
 import com.android.tools.r8.ir.code.DominatorTree.Assumption;
+import com.android.tools.r8.ir.code.FieldInstruction;
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.Instruction;
 import com.android.tools.r8.ir.code.InstructionIterator;
 import com.android.tools.r8.ir.code.InvokeDirect;
 import com.android.tools.r8.ir.code.NewInstance;
-import com.android.tools.r8.ir.code.StaticPut;
 import com.android.tools.r8.ir.code.Value;
 import com.android.tools.r8.ir.optimize.info.OptimizationFeedback;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
@@ -56,9 +56,11 @@
       AppView<AppInfoWithLiveness> appView,
       IRCode code,
       OptimizationFeedback feedback,
+      DexProgramClass clazz,
       DexEncodedMethod method) {
+    assert clazz.type == method.method.holder;
     this.appView = appView;
-    this.clazz = appView.definitionFor(method.method.holder).asProgramClass();
+    this.clazz = clazz;
     this.code = code;
     this.feedback = feedback;
     this.method = method;
@@ -67,11 +69,29 @@
 
   public static void run(
       AppView<?> appView, IRCode code, OptimizationFeedback feedback, DexEncodedMethod method) {
-    if (appView.enableWholeProgramOptimizations() && method.isClassInitializer()) {
-      assert appView.appInfo().hasLiveness();
-      new FieldValueAnalysis(appView.withLiveness(), code, feedback, method)
-          .computeFieldOptimizationInfo();
+    if (!appView.enableWholeProgramOptimizations()) {
+      return;
     }
+    assert appView.appInfo().hasLiveness();
+    if (!method.isInitializer()) {
+      return;
+    }
+    DexProgramClass clazz = appView.definitionFor(method.method.holder).asProgramClass();
+    if (method.isInstanceInitializer()) {
+      if (!appView.options().enableValuePropagationForInstanceFields) {
+        return;
+      }
+      DexEncodedMethod otherInstanceInitializer =
+          clazz.lookupDirectMethod(other -> other.isInstanceInitializer() && other != method);
+      if (otherInstanceInitializer != null) {
+        // Conservatively bail out.
+        // TODO(b/125282093): Handle multiple instance initializers on the same class.
+        return;
+      }
+    }
+
+    new FieldValueAnalysis(appView.withLiveness(), code, feedback, clazz, method)
+        .computeFieldOptimizationInfo();
   }
 
   private Map<BasicBlock, AbstractFieldSet> getOrCreateFieldsMaybeReadBeforeBlockInclusive() {
@@ -86,55 +106,51 @@
     AppInfoWithLiveness appInfo = appView.appInfo();
     DominatorTree dominatorTree = null;
 
-    if (method.isClassInitializer()) {
-      DexType context = method.method.holder;
+    DexType context = method.method.holder;
 
-      // Find all the static-put instructions that assign a field in the enclosing class which is
-      // guaranteed to be assigned only in the current initializer.
-      boolean isStraightLineCode = true;
-      Map<DexEncodedField, LinkedList<StaticPut>> staticPutsPerField = new IdentityHashMap<>();
-      for (Instruction instruction : code.instructions()) {
-        if (instruction.isStaticPut()) {
-          StaticPut staticPut = instruction.asStaticPut();
-          DexField field = staticPut.getField();
-          DexEncodedField encodedField = appInfo.resolveField(field);
-          if (encodedField != null
-              && encodedField.field.holder == context
-              && appInfo.isStaticFieldWrittenOnlyInEnclosingStaticInitializer(encodedField)) {
-            staticPutsPerField
-                .computeIfAbsent(encodedField, ignore -> new LinkedList<>())
-                .add(staticPut);
-          }
-        }
-        if (instruction.isJumpInstruction()) {
-          if (!instruction.isGoto() && !instruction.isReturn()) {
-            isStraightLineCode = false;
-          }
+    // Find all the static-put instructions that assign a field in the enclosing class which is
+    // guaranteed to be assigned only in the current initializer.
+    boolean isStraightLineCode = true;
+    Map<DexEncodedField, LinkedList<FieldInstruction>> putsPerField = new IdentityHashMap<>();
+    for (Instruction instruction : code.instructions()) {
+      if (instruction.isFieldPut()) {
+        FieldInstruction fieldPut = instruction.asFieldInstruction();
+        DexField field = fieldPut.getField();
+        DexEncodedField encodedField = appInfo.resolveField(field);
+        if (encodedField != null
+            && encodedField.field.holder == context
+            && appInfo.isFieldOnlyWrittenInMethod(encodedField, method)) {
+          putsPerField.computeIfAbsent(encodedField, ignore -> new LinkedList<>()).add(fieldPut);
         }
       }
-
-      List<BasicBlock> normalExitBlocks = code.computeNormalExitBlocks();
-      for (Entry<DexEncodedField, LinkedList<StaticPut>> entry : staticPutsPerField.entrySet()) {
-        DexEncodedField encodedField = entry.getKey();
-        LinkedList<StaticPut> staticPuts = entry.getValue();
-        if (staticPuts.size() > 1) {
-          continue;
+      if (instruction.isJumpInstruction()) {
+        if (!instruction.isGoto() && !instruction.isReturn()) {
+          isStraightLineCode = false;
         }
-        StaticPut staticPut = staticPuts.getFirst();
-        if (!isStraightLineCode) {
-          if (dominatorTree == null) {
-            dominatorTree = new DominatorTree(code, Assumption.NO_UNREACHABLE_BLOCKS);
-          }
-          if (!dominatorTree.dominatesAllOf(staticPut.getBlock(), normalExitBlocks)) {
-            continue;
-          }
-        }
-        if (fieldMaybeReadBeforeInstruction(encodedField, staticPut)) {
-          continue;
-        }
-        updateFieldOptimizationInfo(encodedField, staticPut.value());
       }
     }
+
+    List<BasicBlock> normalExitBlocks = code.computeNormalExitBlocks();
+    for (Entry<DexEncodedField, LinkedList<FieldInstruction>> entry : putsPerField.entrySet()) {
+      DexEncodedField encodedField = entry.getKey();
+      LinkedList<FieldInstruction> fieldPuts = entry.getValue();
+      if (fieldPuts.size() > 1) {
+        continue;
+      }
+      FieldInstruction fieldPut = fieldPuts.getFirst();
+      if (!isStraightLineCode) {
+        if (dominatorTree == null) {
+          dominatorTree = new DominatorTree(code, Assumption.NO_UNREACHABLE_BLOCKS);
+        }
+        if (!dominatorTree.dominatesAllOf(fieldPut.getBlock(), normalExitBlocks)) {
+          continue;
+        }
+      }
+      if (fieldMaybeReadBeforeInstruction(encodedField, fieldPut)) {
+        continue;
+      }
+      updateFieldOptimizationInfo(encodedField, fieldPut.value());
+    }
   }
 
   private boolean fieldMaybeReadBeforeInstruction(
diff --git a/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java b/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
index d1028f2..83d6bd1 100644
--- a/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
+++ b/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
@@ -761,22 +761,26 @@
     return false;
   }
 
-  public boolean isStaticFieldWrittenOnlyInEnclosingStaticInitializer(DexEncodedField field) {
+  public boolean isFieldOnlyWrittenInMethod(DexEncodedField field, DexEncodedMethod method) {
     assert checkIfObsolete();
     assert isFieldWritten(field) : "Expected field `" + field.toSourceString() + "` to be written";
     if (!isPinned(field.field)) {
-      DexEncodedMethod staticInitializer =
-          definitionFor(field.field.holder).asProgramClass().getClassInitializer();
-      if (staticInitializer != null) {
-        FieldAccessInfo fieldAccessInfo = fieldAccessInfoCollection.get(field.field);
-        return fieldAccessInfo != null
-            && fieldAccessInfo.isWritten()
-            && !fieldAccessInfo.isWrittenOutside(staticInitializer);
-      }
+      FieldAccessInfo fieldAccessInfo = fieldAccessInfoCollection.get(field.field);
+      return fieldAccessInfo != null
+          && fieldAccessInfo.isWritten()
+          && !fieldAccessInfo.isWrittenOutside(method);
     }
     return false;
   }
 
+  public boolean isStaticFieldWrittenOnlyInEnclosingStaticInitializer(DexEncodedField field) {
+    assert checkIfObsolete();
+    assert isFieldWritten(field) : "Expected field `" + field.toSourceString() + "` to be written";
+    DexEncodedMethod staticInitializer =
+        definitionFor(field.field.holder).asProgramClass().getClassInitializer();
+    return staticInitializer != null && isFieldOnlyWrittenInMethod(field, staticInitializer);
+  }
+
   public boolean mayPropagateValueFor(DexReference reference) {
     assert checkIfObsolete();
     return !isPinned(reference) && !neverPropagateValue.contains(reference);
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 4bc3518..77cc41e 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -246,6 +246,8 @@
   public boolean enableInitializedClassesInInstanceMethodsAnalysis = true;
   public boolean enableRedundantFieldLoadElimination = true;
   public boolean enableValuePropagation = true;
+  // TODO(b/125282093): Enable member value propagation for instance fields.
+  public boolean enableValuePropagationForInstanceFields = false;
   public boolean enableUninstantiatedTypeOptimization = true;
   // TODO(b/138917494): Disable until we have numbers on potential performance penalties.
   public boolean enableRedundantConstNumberOptimization = false;
diff --git a/src/test/examples/shaking18/Options.java b/src/test/examples/shaking18/Options.java
index edb9d77..7012340 100644
--- a/src/test/examples/shaking18/Options.java
+++ b/src/test/examples/shaking18/Options.java
@@ -4,9 +4,7 @@
 package shaking18;
 
 public class Options {
-  // TODO(b/138913138): member value propagation can behave same with and without initialization.
-  // public boolean alwaysFalse = false;
-  public boolean alwaysFalse;
+  public boolean alwaysFalse = false;
   public boolean dummy = false;
 
   public Options() {}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/InstanceFieldValuePropagationTest.java b/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/InstanceFieldValuePropagationTest.java
new file mode 100644
index 0000000..58ad103
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/InstanceFieldValuePropagationTest.java
@@ -0,0 +1,141 @@
+// Copyright (c) 2019, 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.membervaluepropagation;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.InstructionSubject;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class InstanceFieldValuePropagationTest extends TestBase {
+
+  private final TestParameters parameters;
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public InstanceFieldValuePropagationTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void test() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(InstanceFieldValuePropagationTest.class)
+        .addKeepMainRule(TestClass.class)
+        .addOptionsModification(options -> {
+          // TODO(b/125282093): Remove options modification once landed.
+          assert !options.enableValuePropagationForInstanceFields;
+          options.enableValuePropagationForInstanceFields = true;
+        })
+        .enableClassInliningAnnotations()
+        .enableInliningAnnotations()
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .inspect(this::inspect)
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutput(
+            StringUtils.times(StringUtils.lines("A", "42", "Hello world!"), 2));
+  }
+
+  private void inspect(CodeInspector inspector) {
+    ClassSubject testClassSubject = inspector.clazz(TestClass.class);
+    assertThat(testClassSubject, isPresent());
+
+    // Verify that all instance-get instructions in testDefinitelyNotNull() has been removed by
+    // member value propagation.
+    MethodSubject testDefinitelyNotNullMethodSubject =
+        testClassSubject.uniqueMethodWithName("testDefinitelyNotNull");
+    assertThat(testDefinitelyNotNullMethodSubject, isPresent());
+    assertTrue(
+        testDefinitelyNotNullMethodSubject
+            .streamInstructions()
+            .noneMatch(InstructionSubject::isInstanceGet));
+    // TODO(b/125282093): Should be able to remove the new-instance instruction since the instance
+    //  ends up being unused.
+    assertTrue(
+        testDefinitelyNotNullMethodSubject
+            .streamInstructions()
+            .anyMatch(InstructionSubject::isNewInstance));
+
+    // Verify that all instance-get instructions in testMaybeNull() has been removed by member value
+    // propagation.
+    MethodSubject testMaybeNullMethodSubject =
+        testClassSubject.uniqueMethodWithName("testMaybeNull");
+    assertThat(testMaybeNullMethodSubject, isPresent());
+    // TODO(b/125282093): Should synthesize a null-check and still propagate the field values even
+    //  when the receiver is nullable.
+    assertTrue(
+        testMaybeNullMethodSubject
+            .streamInstructions()
+            .anyMatch(InstructionSubject::isInstanceGet));
+    // TODO(b/125282093): Should be able to remove the new-instance instruction since the instance
+    //  ends up being unused.
+    assertTrue(
+        testMaybeNullMethodSubject
+            .streamInstructions()
+            .anyMatch(InstructionSubject::isNewInstance));
+
+    ClassSubject aClassSubject = inspector.clazz(A.class);
+    assertThat(aClassSubject, isPresent());
+    // TODO(b/125282093): Need to remove the instance-put instructions in A.<init>(). This can not
+    //  be done safely by the time we process A.<init>(), so some kind of post-processing is needed.
+    assertEquals(3, aClassSubject.allInstanceFields().size());
+  }
+
+  static class TestClass {
+
+    public static void main(String[] args) {
+      testDefinitelyNotNull();
+      testMaybeNull();
+    }
+
+    @NeverInline
+    static void testDefinitelyNotNull() {
+      A a = new A();
+      System.out.println(a.e);
+      System.out.println(a.i);
+      System.out.println(a.s);
+    }
+
+    @NeverInline
+    static void testMaybeNull() {
+      A a = System.currentTimeMillis() >= 0 ? new A() : null;
+      System.out.println(a.e);
+      System.out.println(a.i);
+      System.out.println(a.s);
+    }
+  }
+
+  @NeverClassInline
+  static class A {
+
+    MyEnum e = MyEnum.A;
+    int i = 42;
+    String s = "Hello world!";
+  }
+
+  enum MyEnum {
+    A,
+    B
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/InstanceFieldValuePropagationWithMultipleInstanceInitializersTest.java b/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/InstanceFieldValuePropagationWithMultipleInstanceInitializersTest.java
new file mode 100644
index 0000000..d2b739e
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/InstanceFieldValuePropagationWithMultipleInstanceInitializersTest.java
@@ -0,0 +1,87 @@
+// Copyright (c) 2019, 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.membervaluepropagation;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.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;
+
+@RunWith(Parameterized.class)
+public class InstanceFieldValuePropagationWithMultipleInstanceInitializersTest extends TestBase {
+
+  private final TestParameters parameters;
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public InstanceFieldValuePropagationWithMultipleInstanceInitializersTest(
+      TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void test() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(InstanceFieldValuePropagationWithMultipleInstanceInitializersTest.class)
+        .addKeepMainRule(TestClass.class)
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .inspect(this::inspect)
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutputLines("Hello world!");
+  }
+
+  private void inspect(CodeInspector inspector) {
+    ClassSubject testClassSubject = inspector.clazz(TestClass.class);
+    assertThat(testClassSubject, isPresent());
+
+    // Verify that the instance-get instruction in main() is still present in main(), since the
+    // value of `a.greeting` depends on the constructor being used.
+    MethodSubject mainMethodSubject = testClassSubject.mainMethod();
+    assertThat(mainMethodSubject, isPresent());
+    assertTrue(mainMethodSubject.streamInstructions().anyMatch(InstructionSubject::isInstanceGet));
+
+    // Verify that the `greeting` field is still present.
+    ClassSubject aClassSubject = inspector.clazz(A.class);
+    assertThat(aClassSubject, isPresent());
+    assertThat(aClassSubject.uniqueFieldWithName("greeting"), isPresent());
+  }
+
+  static class TestClass {
+
+    public static void main(String[] args) {
+      A a = System.currentTimeMillis() >= 0 ? new A() : new A(new Object());
+      System.out.println(a.greeting);
+    }
+  }
+
+  @NeverClassInline
+  static class A {
+
+    String greeting;
+
+    A() {
+      this.greeting = "Hello world!";
+    }
+
+    A(Object unused) {
+      this.greeting = ":-(";
+    }
+  }
+}
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 5d110e4..9da1f4a 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
@@ -82,6 +82,7 @@
       assertOrdinalReplacedWithConst(clazz.uniqueMethodWithName("inlined"), 1);
       assertOrdinalReplacedWithConst(clazz.uniqueMethodWithName("inSwitch"), 11);
       assertOrdinalReplacedWithConst(clazz.uniqueMethodWithName("differentTypeStaticField"), 1);
+      assertOrdinalReplacedWithConst(clazz.uniqueMethodWithName("nonStaticGet"), 1);
     } else {
       assertOrdinalWasNotReplaced(clazz.uniqueMethodWithName("simple"));
       assertOrdinalWasNotReplaced(clazz.uniqueMethodWithName("local"));
@@ -89,12 +90,12 @@
       assertOrdinalWasNotReplaced(clazz.uniqueMethodWithName("inlined"));
       assertOrdinalWasNotReplaced(clazz.uniqueMethodWithName("inSwitch"));
       assertOrdinalWasNotReplaced(clazz.uniqueMethodWithName("differentTypeStaticField"));
+      assertOrdinalWasNotReplaced(clazz.uniqueMethodWithName("nonStaticGet"));
     }
 
     assertOrdinalWasNotReplaced(clazz.uniqueMethodWithName("libraryType"));
     assertOrdinalWasNotReplaced(clazz.uniqueMethodWithName("nonValueStaticField"));
     assertOrdinalWasNotReplaced(clazz.uniqueMethodWithName("phi"));
-    assertOrdinalWasNotReplaced(clazz.uniqueMethodWithName("nonStaticGet"));
   }
 
   @Test
@@ -125,12 +126,14 @@
       assertNameReplacedWithConst(clazz.uniqueMethodWithName("multipleUsages"), expectedConst);
       assertNameReplacedWithConst(clazz.uniqueMethodWithName("inlined"), "TWO");
       assertNameReplacedWithConst(clazz.uniqueMethodWithName("differentTypeStaticField"), "DOWN");
+      assertNameReplacedWithConst(clazz.uniqueMethodWithName("nonStaticGet"), "TWO");
     } else {
       assertNameWasNotReplaced(clazz.uniqueMethodWithName("simple"));
       assertNameWasNotReplaced(clazz.uniqueMethodWithName("local"));
       assertNameWasNotReplaced(clazz.uniqueMethodWithName("multipleUsages"));
       assertNameWasNotReplaced(clazz.uniqueMethodWithName("inlined"));
       assertNameWasNotReplaced(clazz.uniqueMethodWithName("differentTypeStaticField"));
+      assertNameWasNotReplaced(clazz.uniqueMethodWithName("nonStaticGet"));
     }
 
     // TODO(jakew) this should be allowed!
@@ -138,7 +141,6 @@
 
     assertNameWasNotReplaced(clazz.uniqueMethodWithName("nonValueStaticField"));
     assertNameWasNotReplaced(clazz.uniqueMethodWithName("phi"));
-    assertNameWasNotReplaced(clazz.uniqueMethodWithName("nonStaticGet"));
   }
 
   @Test
@@ -178,6 +180,7 @@
       assertToStringReplacedWithConst(clazz.uniqueMethodWithName("nonValueStaticField"), "TWO");
       assertToStringReplacedWithConst(
           clazz.uniqueMethodWithName("differentTypeStaticField"), "DOWN");
+      assertToStringReplacedWithConst(clazz.uniqueMethodWithName("nonStaticGet"), "TWO");
     } else {
       assertToStringWasNotReplaced(clazz.uniqueMethodWithName("noToString"));
       assertToStringWasNotReplaced(clazz.uniqueMethodWithName("local"));
@@ -185,11 +188,11 @@
       assertToStringWasNotReplaced(clazz.uniqueMethodWithName("inlined"));
       assertToStringWasNotReplaced(clazz.uniqueMethodWithName("nonValueStaticField"));
       assertToStringWasNotReplaced(clazz.uniqueMethodWithName("differentTypeStaticField"));
+      assertToStringWasNotReplaced(clazz.uniqueMethodWithName("nonStaticGet"));
     }
 
     assertToStringWasNotReplaced(clazz.uniqueMethodWithName("libraryType"));
     assertToStringWasNotReplaced(clazz.uniqueMethodWithName("phi"));
-    assertToStringWasNotReplaced(clazz.uniqueMethodWithName("nonStaticGet"));
   }
 
   private static void assertOrdinalReplacedWithConst(MethodSubject method, int expectedConst) {
diff --git a/src/test/java/com/android/tools/r8/shaking/EffectivelyFinalInstanceFieldsTest.java b/src/test/java/com/android/tools/r8/shaking/EffectivelyFinalInstanceFieldsTest.java
index ea96ead..92d16ae 100644
--- a/src/test/java/com/android/tools/r8/shaking/EffectivelyFinalInstanceFieldsTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/EffectivelyFinalInstanceFieldsTest.java
@@ -56,43 +56,50 @@
         .enableMergeAnnotations()
         .setMinApi(parameters.getRuntime())
         .compile()
-        .inspect(codeInspector -> {
-          ClassSubject main = codeInspector.clazz(MAIN);
-          assertThat(main, isPresent());
+        .inspect(
+            codeInspector -> {
+              ClassSubject main = codeInspector.clazz(MAIN);
+              assertThat(main, isPresent());
 
-          MethodSubject mainMethod = main.mainMethod();
-          assertThat(mainMethod, isPresent());
+              MethodSubject mainMethod = main.mainMethod();
+              assertThat(mainMethod, isPresent());
 
-          assertTrue(
-              mainMethod.streamInstructions().noneMatch(
-                  i -> i.isConstString("Dead code: 1", JumboStringMode.ALLOW)));
-          // TODO(b/138913138): effectively final, and default value is set.
-          assertFalse(
-              mainMethod.streamInstructions().noneMatch(
-                  i -> i.isConstString("Dead code: 2", JumboStringMode.ALLOW)));
-          // TODO(b/138913138): not trivial; assigned only once in <init>
-          assertFalse(
-              mainMethod.streamInstructions().noneMatch(
-                  i -> i.isConstString("Dead code: 3", JumboStringMode.ALLOW)));
-          assertTrue(
-              mainMethod.streamInstructions().noneMatch(
-                  i -> i.isConstString("Dead code: 4", JumboStringMode.ALLOW)));
-          // TODO(b/138913138): effectively final, and default value is set.
-          assertFalse(
-              mainMethod.streamInstructions().noneMatch(
-                  i -> i.isConstString("Dead code: 5", JumboStringMode.ALLOW)));
-          // TODO(b/138913138): not trivial; assigned multiple times, but within a certain range.
-          assertFalse(
-              mainMethod.streamInstructions().noneMatch(
-                  i -> i.isConstString("Dead code: 6", JumboStringMode.ALLOW)));
-          assertTrue(
-              mainMethod.streamInstructions().noneMatch(
-                  i -> i.isConstString("Dead code: 7", JumboStringMode.ALLOW)));
-          // TODO(b/138913138): effectively final, and default value is set.
-          assertFalse(
-              mainMethod.streamInstructions().noneMatch(
-                  i -> i.isConstString("Dead code: 8", JumboStringMode.ALLOW)));
-        })
+              assertTrue(
+                  mainMethod
+                      .streamInstructions()
+                      .noneMatch(i -> i.isConstString("Dead code: 1", JumboStringMode.ALLOW)));
+              assertTrue(
+                  mainMethod
+                      .streamInstructions()
+                      .noneMatch(i -> i.isConstString("Dead code: 2", JumboStringMode.ALLOW)));
+              // TODO(b/138913138): not trivial; assigned only once in <init>
+              assertFalse(
+                  mainMethod
+                      .streamInstructions()
+                      .noneMatch(i -> i.isConstString("Dead code: 3", JumboStringMode.ALLOW)));
+              assertTrue(
+                  mainMethod
+                      .streamInstructions()
+                      .noneMatch(i -> i.isConstString("Dead code: 4", JumboStringMode.ALLOW)));
+              assertTrue(
+                  mainMethod
+                      .streamInstructions()
+                      .noneMatch(i -> i.isConstString("Dead code: 5", JumboStringMode.ALLOW)));
+              // TODO(b/138913138): not trivial; assigned multiple times, but within a certain
+              // range.
+              assertFalse(
+                  mainMethod
+                      .streamInstructions()
+                      .noneMatch(i -> i.isConstString("Dead code: 6", JumboStringMode.ALLOW)));
+              assertTrue(
+                  mainMethod
+                      .streamInstructions()
+                      .noneMatch(i -> i.isConstString("Dead code: 7", JumboStringMode.ALLOW)));
+              assertTrue(
+                  mainMethod
+                      .streamInstructions()
+                      .noneMatch(i -> i.isConstString("Dead code: 8", JumboStringMode.ALLOW)));
+            })
         .run(parameters.getRuntime(), MAIN)
         .assertSuccessWithOutputLines("The end");
   }