Leverage field initialization info during field load elimination

Change-Id: I063eb1b7df6c5e0ca1e74b5859a4520a8638682f
diff --git a/src/main/java/com/android/tools/r8/ir/code/Invoke.java b/src/main/java/com/android/tools/r8/ir/code/Invoke.java
index ec77df1..15834d9 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Invoke.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Invoke.java
@@ -103,6 +103,10 @@
     return inValues;
   }
 
+  public Value getArgument(int index) {
+    return arguments().get(index);
+  }
+
   public int requiredArgumentRegisters() {
     int registers = 0;
     for (Value inValue : inValues) {
diff --git a/src/main/java/com/android/tools/r8/ir/code/Value.java b/src/main/java/com/android/tools/r8/ir/code/Value.java
index 5566552..bdaa741 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Value.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Value.java
@@ -507,6 +507,15 @@
     return !users.isEmpty();
   }
 
+  public boolean hasUserThatMatches(Predicate<Instruction> predicate) {
+    for (Instruction user : uniqueUsers()) {
+      if (predicate.test(user)) {
+        return true;
+      }
+    }
+    return false;
+  }
+
   public int numberOfUsers() {
     int size = users.size();
     if (size <= 1) {
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 98c1915..2b20b77 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
@@ -1498,6 +1498,7 @@
       codeRewriter.shortenLiveRanges(code);
       timing.end();
     }
+
     timing.begin("Canonicalize idempotent calls");
     idempotentFunctionCallCanonicalizer.canonicalize(code);
     timing.end();
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/ConstantCanonicalizer.java b/src/main/java/com/android/tools/r8/ir/optimize/ConstantCanonicalizer.java
index a63cb59..7cafb59 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/ConstantCanonicalizer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/ConstantCanonicalizer.java
@@ -206,6 +206,8 @@
     if (!iterator.hasNext()) {
       return;
     }
+
+    boolean shouldSimplifyControlFlow = false;
     do {
       Object2ObjectMap.Entry<Instruction, List<Value>> entry = iterator.next();
       Instruction canonicalizedConstant = entry.getKey();
@@ -252,9 +254,12 @@
       for (Value outValue : entry.getValue()) {
         outValue.replaceUsers(newConst.outValue());
       }
+      shouldSimplifyControlFlow |= newConst.outValue().hasUserThatMatches(Instruction::isIf);
     } while (iterator.hasNext());
 
-    if (code.removeAllTrivialPhis()) {
+    shouldSimplifyControlFlow |= code.removeAllTrivialPhis();
+
+    if (shouldSimplifyControlFlow) {
       codeRewriter.simplifyControlFlow(code);
     }
 
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 f362ef7..698818f 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
@@ -11,6 +11,7 @@
 import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.graph.DexType;
 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;
 import com.android.tools.r8.ir.code.DominatorTree;
 import com.android.tools.r8.ir.code.FieldInstruction;
@@ -19,11 +20,14 @@
 import com.android.tools.r8.ir.code.InstancePut;
 import com.android.tools.r8.ir.code.Instruction;
 import com.android.tools.r8.ir.code.InstructionListIterator;
+import com.android.tools.r8.ir.code.InvokeDirect;
 import com.android.tools.r8.ir.code.NewInstance;
 import com.android.tools.r8.ir.code.Phi;
 import com.android.tools.r8.ir.code.StaticGet;
 import com.android.tools.r8.ir.code.StaticPut;
 import com.android.tools.r8.ir.code.Value;
+import com.android.tools.r8.ir.optimize.info.field.InstanceFieldInitializationInfoCollection;
+import com.android.tools.r8.ir.optimize.info.initializer.InstanceInitializerInfo;
 import com.google.common.collect.Sets;
 import java.util.ArrayList;
 import java.util.HashMap;
@@ -50,15 +54,15 @@
   private final Set<Value> affectedValues = Sets.newIdentityHashSet();
 
   // Maps keeping track of fields that have an already loaded value at basic block entry.
-  private final Map<BasicBlock, Map<FieldAndObject, FieldInstruction>> activeInstanceFieldsAtEntry =
+  private final Map<BasicBlock, Map<FieldAndObject, FieldValue>> activeInstanceFieldsAtEntry =
       new IdentityHashMap<>();
-  private final Map<BasicBlock, Map<DexField, FieldInstruction>> activeStaticFieldsAtEntry =
+  private final Map<BasicBlock, Map<DexField, FieldValue>> activeStaticFieldsAtEntry =
       new IdentityHashMap<>();
 
   // Maps keeping track of fields with already loaded values for the current block during
   // elimination.
-  private Map<FieldAndObject, FieldInstruction> activeInstanceFields;
-  private Map<DexField, FieldInstruction> activeStaticFields;
+  private Map<FieldAndObject, FieldValue> activeInstanceFieldValues;
+  private Map<DexField, FieldValue> activeStaticFieldValues;
 
   public RedundantFieldLoadElimination(AppView<?> appView, IRCode code) {
     this.appView = appView;
@@ -72,6 +76,45 @@
         && code.metadata().mayHaveFieldGet();
   }
 
+  private interface FieldValue {
+
+    void eliminateRedundantRead(InstructionListIterator it, FieldInstruction redundant);
+  }
+
+  private class ExistingValue implements FieldValue {
+
+    private final Value value;
+
+    private ExistingValue(Value value) {
+      this.value = value;
+    }
+
+    @Override
+    public void eliminateRedundantRead(InstructionListIterator it, FieldInstruction redundant) {
+      affectedValues.addAll(redundant.value().affectedValues());
+      redundant.value().replaceUsers(value);
+      it.removeOrReplaceByDebugLocalRead();
+      value.uniquePhiUsers().forEach(Phi::removeTrivialPhi);
+    }
+  }
+
+  private class MaterializableValue implements FieldValue {
+
+    private final SingleValue value;
+
+    private MaterializableValue(SingleValue value) {
+      assert value.isMaterializableInContext(appView, method.holder());
+      this.value = value;
+    }
+
+    @Override
+    public void eliminateRedundantRead(InstructionListIterator it, FieldInstruction redundant) {
+      affectedValues.addAll(redundant.value().affectedValues());
+      it.replaceCurrentInstruction(
+          value.createMaterializingInstruction(appView.withSubtyping(), code, redundant));
+    }
+  }
+
   private static class FieldAndObject {
     private final DexField field;
     private final Value object;
@@ -98,25 +141,26 @@
   }
 
   private boolean couldBeVolatile(DexField field) {
-    if (!appView.enableWholeProgramOptimizations()) {
+    DexEncodedField definition;
+    if (appView.enableWholeProgramOptimizations()) {
+      definition = appView.appInfo().resolveField(field);
+    } else {
       if (field.holder != method.method.holder) {
         return true;
       }
-      DexEncodedField definition = appView.definitionFor(field);
-      return definition == null || definition.accessFlags.isVolatile();
+      definition = appView.definitionFor(field);
     }
-    DexEncodedField definition = appView.appInfo().resolveField(field);
     return definition == null || definition.accessFlags.isVolatile();
   }
 
   public void run() {
     DexType context = method.method.holder;
     for (BasicBlock block : dominatorTree.getSortedBlocks()) {
-      activeInstanceFields =
+      activeInstanceFieldValues =
           activeInstanceFieldsAtEntry.containsKey(block)
               ? activeInstanceFieldsAtEntry.get(block)
               : new HashMap<>();
-      activeStaticFields =
+      activeStaticFieldValues =
           activeStaticFieldsAtEntry.containsKey(block)
               ? activeStaticFieldsAtEntry.get(block)
               : new IdentityHashMap<>();
@@ -130,8 +174,6 @@
             continue;
           }
 
-          assert !couldBeVolatile(field);
-
           if (instruction.isInstanceGet()) {
             InstanceGet instanceGet = instruction.asInstanceGet();
             if (instanceGet.outValue().hasLocalInfo()) {
@@ -139,11 +181,11 @@
             }
             Value object = instanceGet.object().getAliasedValue();
             FieldAndObject fieldAndObject = new FieldAndObject(field, object);
-            if (activeInstanceFields.containsKey(fieldAndObject)) {
-              FieldInstruction active = activeInstanceFields.get(fieldAndObject);
-              eliminateRedundantRead(it, instanceGet, active);
+            if (activeInstanceFieldValues.containsKey(fieldAndObject)) {
+              FieldValue replacement = activeInstanceFieldValues.get(fieldAndObject);
+              replacement.eliminateRedundantRead(it, instanceGet);
             } else {
-              activeInstanceFields.put(fieldAndObject, instanceGet);
+              activeInstanceFieldValues.put(fieldAndObject, new ExistingValue(instanceGet.value()));
             }
           } else if (instruction.isInstancePut()) {
             InstancePut instancePut = instruction.asInstancePut();
@@ -153,32 +195,34 @@
             // ... but at least we know the field value for this particular object.
             Value object = instancePut.object().getAliasedValue();
             FieldAndObject fieldAndObject = new FieldAndObject(field, object);
-            activeInstanceFields.put(fieldAndObject, instancePut);
+            activeInstanceFieldValues.put(fieldAndObject, new ExistingValue(instancePut.value()));
           } else if (instruction.isStaticGet()) {
             StaticGet staticGet = instruction.asStaticGet();
             if (staticGet.outValue().hasLocalInfo()) {
               continue;
             }
-            if (activeStaticFields.containsKey(field)) {
-              FieldInstruction active = activeStaticFields.get(field);
-              eliminateRedundantRead(it, staticGet, active);
+            if (activeStaticFieldValues.containsKey(field)) {
+              FieldValue replacement = activeStaticFieldValues.get(field);
+              replacement.eliminateRedundantRead(it, staticGet);
             } else {
               // A field get on a different class can cause <clinit> to run and change static
               // field values.
               killActiveFields(staticGet);
-              activeStaticFields.put(field, staticGet);
+              activeStaticFieldValues.put(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);
-            activeStaticFields.put(field, staticPut);
+            activeStaticFieldValues.put(field, new ExistingValue(staticPut.value()));
           }
         } else if (instruction.isMonitor()) {
           if (instruction.asMonitor().isEnter()) {
             killAllActiveFields();
           }
+        } else if (instruction.isInvokeDirect()) {
+          handleInvokeDirect(instruction.asInvokeDirect());
         } else if (instruction.isInvokeMethod() || instruction.isInvokeCustom()) {
           killAllActiveFields();
         } else if (instruction.isNewInstance()) {
@@ -235,6 +279,51 @@
     assert code.isConsistentSSA();
   }
 
+  private void handleInvokeDirect(InvokeDirect invoke) {
+    if (!appView.enableWholeProgramOptimizations()) {
+      killAllActiveFields();
+      return;
+    }
+
+    DexEncodedMethod singleTarget = invoke.lookupSingleTarget(appView, method.holder());
+    if (singleTarget == null || !singleTarget.isInstanceInitializer()) {
+      killAllActiveFields();
+      return;
+    }
+
+    InstanceInitializerInfo instanceInitializerInfo =
+        singleTarget.getOptimizationInfo().getInstanceInitializerInfo();
+    if (instanceInitializerInfo.mayHaveOtherSideEffectsThanInstanceFieldAssignments()) {
+      killAllActiveFields();
+    }
+
+    InstanceFieldInitializationInfoCollection fieldInitializationInfos =
+        instanceInitializerInfo.fieldInitializationInfos();
+    fieldInitializationInfos.forEach(
+        appView,
+        (field, info) -> {
+          if (!appView.appInfo().withLiveness().mayPropagateValueFor(field.field)) {
+            return;
+          }
+          if (info.isArgumentInitializationInfo()) {
+            Value value =
+                invoke.getArgument(info.asArgumentInitializationInfo().getArgumentIndex());
+            Value object = invoke.getReceiver().getAliasedValue();
+            FieldAndObject fieldAndObject = new FieldAndObject(field.field, object);
+            activeInstanceFieldValues.put(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));
+            }
+          } else {
+            assert info.isTypeInitializationInfo();
+          }
+        });
+  }
+
   private void propagateActiveFieldsFrom(BasicBlock block) {
     for (BasicBlock successor : block.getSuccessors()) {
       // Allow propagation across exceptional edges, just be careful not to propagate if the
@@ -247,16 +336,16 @@
           }
         }
         assert !activeInstanceFieldsAtEntry.containsKey(successor);
-        activeInstanceFieldsAtEntry.put(successor, new HashMap<>(activeInstanceFields));
+        activeInstanceFieldsAtEntry.put(successor, new HashMap<>(activeInstanceFieldValues));
         assert !activeStaticFieldsAtEntry.containsKey(successor);
-        activeStaticFieldsAtEntry.put(successor, new IdentityHashMap<>(activeStaticFields));
+        activeStaticFieldsAtEntry.put(successor, new IdentityHashMap<>(activeStaticFieldValues));
       }
     }
   }
 
   private void killAllActiveFields() {
-    activeInstanceFields.clear();
-    activeStaticFields.clear();
+    activeInstanceFieldValues.clear();
+    activeStaticFieldValues.clear();
   }
 
   private void killActiveFields(FieldInstruction instruction) {
@@ -265,25 +354,25 @@
       // 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 : activeInstanceFields.keySet()) {
+      for (FieldAndObject key : activeInstanceFieldValues.keySet()) {
         if (key.field == field) {
           keysToRemove.add(key);
         }
       }
-      keysToRemove.forEach(activeInstanceFields::remove);
+      keysToRemove.forEach(activeInstanceFieldValues::remove);
     } else if (instruction.isStaticPut()) {
       if (field.holder != code.method.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.
-        activeStaticFields.clear();
+        activeStaticFieldValues.clear();
       } else {
-        activeStaticFields.remove(field);
+        activeStaticFieldValues.remove(field);
       }
     } else if (instruction.isStaticGet()) {
       if (field.holder != code.method.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.
-        activeStaticFields.clear();
+        activeStaticFieldValues.clear();
       }
     } else if (instruction.isInstanceGet()) {
       throw new Unreachable();
@@ -299,17 +388,9 @@
     if (instruction.isInstanceGet()) {
       Value object = instruction.asInstanceGet().object().getAliasedValue();
       FieldAndObject fieldAndObject = new FieldAndObject(field, object);
-      activeInstanceFields.remove(fieldAndObject);
+      activeInstanceFieldValues.remove(fieldAndObject);
     } else if (instruction.isStaticGet()) {
-      activeStaticFields.remove(field);
+      activeStaticFieldValues.remove(field);
     }
   }
-
-  private void eliminateRedundantRead(
-      InstructionListIterator it, FieldInstruction redundant, FieldInstruction active) {
-    affectedValues.addAll(redundant.value().affectedValues());
-    redundant.value().replaceUsers(active.value());
-    it.removeOrReplaceByDebugLocalRead();
-    active.value().uniquePhiUsers().forEach(Phi::removeTrivialPhi);
-  }
 }
diff --git a/src/test/java/com/android/tools/r8/R8RunExamplesAndroidOTest.java b/src/test/java/com/android/tools/r8/R8RunExamplesAndroidOTest.java
index db80105..a642002 100644
--- a/src/test/java/com/android/tools/r8/R8RunExamplesAndroidOTest.java
+++ b/src/test/java/com/android/tools/r8/R8RunExamplesAndroidOTest.java
@@ -102,7 +102,7 @@
         .withOptionConsumer(opts -> opts.enableClassInlining = false)
         .withBuilderTransformation(
             b -> b.addProguardConfiguration(PROGUARD_OPTIONS, Origin.unknown()))
-        .withDexCheck(inspector -> checkLambdaCount(inspector, 118, "lambdadesugaring"))
+        .withDexCheck(inspector -> checkLambdaCount(inspector, 115, "lambdadesugaring"))
         .run();
 
     test("lambdadesugaring", "lambdadesugaring", "LambdaDesugaring")
@@ -142,7 +142,7 @@
         .withOptionConsumer(opts -> opts.enableClassInlining = false)
         .withBuilderTransformation(
             b -> b.addProguardConfiguration(PROGUARD_OPTIONS, Origin.unknown()))
-        .withDexCheck(inspector -> checkLambdaCount(inspector, 118, "lambdadesugaring"))
+        .withDexCheck(inspector -> checkLambdaCount(inspector, 115, "lambdadesugaring"))
         .run();
 
     test("lambdadesugaring", "lambdadesugaring", "LambdaDesugaring")
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/fields/FieldInitializedByConstantInOneConstructorTest.java b/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/fields/FieldInitializedByConstantInOneConstructorTest.java
index 71ef038..8dcd7bc 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/fields/FieldInitializedByConstantInOneConstructorTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/fields/FieldInitializedByConstantInOneConstructorTest.java
@@ -5,6 +5,7 @@
 package com.android.tools.r8.ir.optimize.membervaluepropagation.fields;
 
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.CoreMatchers.not;
 import static org.hamcrest.MatcherAssert.assertThat;
 
 import com.android.tools.r8.NeverClassInline;
@@ -50,8 +51,11 @@
     ClassSubject testClassSubject = inspector.clazz(TestClass.class);
     assertThat(testClassSubject, isPresent());
     assertThat(testClassSubject.uniqueMethodWithName("live"), isPresent());
-    // TODO(b/147652121): Should be absent.
-    assertThat(testClassSubject.uniqueMethodWithName("dead"), isPresent());
+    if (parameters.isCfRuntime()) {
+      assertThat(testClassSubject.uniqueMethodWithName("dead"), isPresent());
+    } else {
+      assertThat(testClassSubject.uniqueMethodWithName("dead"), not(isPresent()));
+    }
   }
 
   static class TestClass {
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/fields/FieldInitializedByDifferentConstantsInMultipleConstructorsTest.java b/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/fields/FieldInitializedByDifferentConstantsInMultipleConstructorsTest.java
index cf7650f..9bedfb1 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/fields/FieldInitializedByDifferentConstantsInMultipleConstructorsTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/fields/FieldInitializedByDifferentConstantsInMultipleConstructorsTest.java
@@ -5,6 +5,7 @@
 package com.android.tools.r8.ir.optimize.membervaluepropagation.fields;
 
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.CoreMatchers.not;
 import static org.hamcrest.MatcherAssert.assertThat;
 
 import com.android.tools.r8.NeverClassInline;
@@ -50,8 +51,11 @@
     ClassSubject testClassSubject = inspector.clazz(TestClass.class);
     assertThat(testClassSubject, isPresent());
     assertThat(testClassSubject.uniqueMethodWithName("live"), isPresent());
-    // TODO(b/147652121): Should be absent.
-    assertThat(testClassSubject.uniqueMethodWithName("dead"), isPresent());
+    if (parameters.isCfRuntime()) {
+      assertThat(testClassSubject.uniqueMethodWithName("dead"), isPresent());
+    } else {
+      assertThat(testClassSubject.uniqueMethodWithName("dead"), not(isPresent()));
+    }
   }
 
   static class TestClass {
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/fields/FieldInitializedByNonConstantArgumentInForwardingConstructorTest.java b/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/fields/FieldInitializedByNonConstantArgumentInForwardingConstructorTest.java
new file mode 100644
index 0000000..c89cfa9
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/fields/FieldInitializedByNonConstantArgumentInForwardingConstructorTest.java
@@ -0,0 +1,98 @@
+// Copyright (c) 2020, 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.fields;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+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.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class FieldInitializedByNonConstantArgumentInForwardingConstructorTest extends TestBase {
+
+  private final TestParameters parameters;
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public FieldInitializedByNonConstantArgumentInForwardingConstructorTest(
+      TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void test() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(FieldInitializedByNonConstantArgumentInForwardingConstructorTest.class)
+        .addKeepMainRule(TestClass.class)
+        .enableInliningAnnotations()
+        .enableNeverClassInliningAnnotations()
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .inspect(this::inspect)
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutputLines("Live!");
+  }
+
+  private void inspect(CodeInspector inspector) {
+    ClassSubject testClassSubject = inspector.clazz(TestClass.class);
+    assertThat(testClassSubject, isPresent());
+    assertThat(testClassSubject.uniqueMethodWithName("live"), isPresent());
+    assertThat(testClassSubject.uniqueMethodWithName("dead"), not(isPresent()));
+  }
+
+  static class TestClass {
+
+    public static void main(String[] args) {
+      if (new A(42).x == 42) {
+        live();
+      } else {
+        dead();
+      }
+
+      if (System.currentTimeMillis() < 0) {
+        // So that we can't conclude a constant value for A.x.
+        System.out.println(new A(args.length));
+      }
+    }
+
+    @NeverInline
+    static void live() {
+      System.out.println("Live!");
+    }
+
+    @NeverInline
+    static void dead() {
+      System.out.println("Dead!");
+    }
+  }
+
+  @NeverClassInline
+  static class A {
+
+    int x;
+
+    A(int x) {
+      this(x, null);
+    }
+
+    @NeverInline
+    A(int x, Object unused) {
+      this.x = x;
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/fields/FieldInitializedByNonConstantArgumentInSuperConstructorTest.java b/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/fields/FieldInitializedByNonConstantArgumentInSuperConstructorTest.java
new file mode 100644
index 0000000..a59e9a9
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/fields/FieldInitializedByNonConstantArgumentInSuperConstructorTest.java
@@ -0,0 +1,102 @@
+// Copyright (c) 2020, 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.fields;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NeverMerge;
+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 org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class FieldInitializedByNonConstantArgumentInSuperConstructorTest extends TestBase {
+
+  private final TestParameters parameters;
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public FieldInitializedByNonConstantArgumentInSuperConstructorTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void test() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(FieldInitializedByNonConstantArgumentInSuperConstructorTest.class)
+        .addKeepMainRule(TestClass.class)
+        .enableInliningAnnotations()
+        .enableMergeAnnotations()
+        .enableNeverClassInliningAnnotations()
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .inspect(this::inspect)
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutputLines("Live!");
+  }
+
+  private void inspect(CodeInspector inspector) {
+    ClassSubject testClassSubject = inspector.clazz(TestClass.class);
+    assertThat(testClassSubject, isPresent());
+    assertThat(testClassSubject.uniqueMethodWithName("live"), isPresent());
+    assertThat(testClassSubject.uniqueMethodWithName("dead"), not(isPresent()));
+  }
+
+  static class TestClass {
+
+    public static void main(String[] args) {
+      if (new B(42).x == 42) {
+        live();
+      } else {
+        dead();
+      }
+
+      if (System.currentTimeMillis() < 0) {
+        // So that we can't conclude a constant value for A.x.
+        System.out.println(new B(args.length));
+      }
+    }
+
+    @NeverInline
+    static void live() {
+      System.out.println("Live!");
+    }
+
+    @NeverInline
+    static void dead() {
+      System.out.println("Dead!");
+    }
+  }
+
+  @NeverMerge
+  static class A {
+
+    int x;
+
+    A(int x) {
+      this.x = x;
+    }
+  }
+
+  @NeverClassInline
+  static class B extends A {
+
+    B(int x) {
+      super(x);
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/fields/FieldInitializedByNonConstantArgumentTest.java b/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/fields/FieldInitializedByNonConstantArgumentTest.java
index c0c12e2..c515e70 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/fields/FieldInitializedByNonConstantArgumentTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/fields/FieldInitializedByNonConstantArgumentTest.java
@@ -5,6 +5,7 @@
 package com.android.tools.r8.ir.optimize.membervaluepropagation.fields;
 
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.CoreMatchers.not;
 import static org.hamcrest.MatcherAssert.assertThat;
 
 import com.android.tools.r8.NeverClassInline;
@@ -50,8 +51,7 @@
     ClassSubject testClassSubject = inspector.clazz(TestClass.class);
     assertThat(testClassSubject, isPresent());
     assertThat(testClassSubject.uniqueMethodWithName("live"), isPresent());
-    // TODO(b/147652121): Should be absent.
-    assertThat(testClassSubject.uniqueMethodWithName("dead"), isPresent());
+    assertThat(testClassSubject.uniqueMethodWithName("dead"), not(isPresent()));
   }
 
   static class TestClass {