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 {