Merge "Rerun partial type analysis after NonNull removal"
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/type/TypeAnalysis.java b/src/main/java/com/android/tools/r8/ir/analysis/type/TypeAnalysis.java
index 6a0b9bc..ffbd3fd 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/type/TypeAnalysis.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/type/TypeAnalysis.java
@@ -51,6 +51,13 @@
analyze();
}
+ public void widening(Iterable<Value> values) {
+ mode = Mode.WIDENING;
+ assert worklist.isEmpty();
+ values.forEach(this::enqueue);
+ analyze();
+ }
+
public void narrowing(Iterable<Value> values) {
mode = Mode.NARROWING;
assert worklist.isEmpty();
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 a883959..a662e1e 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
@@ -161,7 +161,7 @@
this.enableWholeProgramOptimizations = appView != null;
if (enableWholeProgramOptimizations) {
assert appInfo.hasLiveness();
- this.nonNullTracker = new NonNullTracker();
+ this.nonNullTracker = new NonNullTracker(appInfo);
this.inliner = new Inliner(appView.withLiveness(), this, options);
this.outliner = new Outliner(appInfo.withLiveness(), options, this);
this.memberValuePropagation =
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java b/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java
index fc11b78..479c3e4 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java
@@ -3,6 +3,7 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.ir.optimize;
+import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.Code;
import com.android.tools.r8.graph.DexClass;
import com.android.tools.r8.graph.DexEncodedMethod;
@@ -19,6 +20,7 @@
import com.android.tools.r8.ir.optimize.Inliner.InlineAction;
import com.android.tools.r8.ir.optimize.Inliner.Reason;
import com.android.tools.r8.logging.Log;
+import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.IteratorUtils;
import java.util.BitSet;
@@ -29,6 +31,7 @@
final class DefaultInliningOracle implements InliningOracle, InliningStrategy {
+ private final AppView<? extends AppInfoWithLiveness> appView;
private final Inliner inliner;
private final DexEncodedMethod method;
private final IRCode code;
@@ -40,6 +43,7 @@
private int instructionAllowance;
DefaultInliningOracle(
+ AppView<? extends AppInfoWithLiveness> appView,
Inliner inliner,
DexEncodedMethod method,
IRCode code,
@@ -48,6 +52,7 @@
InternalOptions options,
int inliningInstructionLimit,
int inliningInstructionAllowance) {
+ this.appView = appView;
this.inliner = inliner;
this.method = method;
this.code = code;
@@ -379,8 +384,9 @@
assert IteratorUtils.peekNext(blockIterator) == block;
// Kick off the tracker to add non-null IRs only to the inlinee blocks.
- Set<Value> nonNullValues = new NonNullTracker()
- .addNonNullInPart(code, blockIterator, inlinee.blocks::contains);
+ Set<Value> nonNullValues =
+ new NonNullTracker(appView.appInfo())
+ .addNonNullInPart(code, blockIterator, inlinee.blocks::contains);
assert !blockIterator.hasNext();
// Restore the old state of the iterator.
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java b/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java
index 000eac2..2a74cad 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java
@@ -559,6 +559,7 @@
int inliningInstructionAllowance) {
return new DefaultInliningOracle(
+ appView,
this,
method,
code,
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/NonNullTracker.java b/src/main/java/com/android/tools/r8/ir/optimize/NonNullTracker.java
index e6997d4..536eac8 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/NonNullTracker.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/NonNullTracker.java
@@ -4,6 +4,8 @@
package com.android.tools.r8.ir.optimize;
import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.graph.AppInfo;
+import com.android.tools.r8.ir.analysis.type.TypeAnalysis;
import com.android.tools.r8.ir.code.BasicBlock;
import com.android.tools.r8.ir.code.DominatorTree;
import com.android.tools.r8.ir.code.IRCode;
@@ -30,7 +32,10 @@
public class NonNullTracker {
- public NonNullTracker() {
+ private final AppInfo appInfo;
+
+ public NonNullTracker(AppInfo appInfo) {
+ this.appInfo = appInfo;
}
@VisibleForTesting
@@ -260,6 +265,8 @@
}
public void cleanupNonNull(IRCode code) {
+ Set<Value> affectedValues = Sets.newIdentityHashSet();
+
InstructionIterator it = code.instructionIterator();
boolean needToCheckTrivialPhis = false;
while (it.hasNext()) {
@@ -275,6 +282,16 @@
NonNull nonNull = instruction.asNonNull();
Value src = nonNull.src();
Value dest = nonNull.dest();
+
+ // Add all values whose definition may depend on `dest` to `affectedValues`.
+ for (Instruction user : dest.uniqueUsers()) {
+ if (user.outValue() != null) {
+ affectedValues.add(user.outValue());
+ }
+ }
+ affectedValues.addAll(dest.uniquePhiUsers());
+
+ // Replace `dest` by `src`.
needToCheckTrivialPhis = needToCheckTrivialPhis || dest.uniquePhiUsers().size() != 0;
dest.replaceUsers(src);
it.remove();
@@ -290,6 +307,17 @@
if (needToCheckTrivialPhis) {
code.removeAllTrivialPhis();
}
+
+ // We need to update the types of all values whose definitions depend on a non-null value.
+ // This is needed to preserve soundness of the types after the NonNull instructions have been
+ // removed.
+ //
+ // As an example, consider a check-cast instruction on the form "z = (T) y". If y used to be
+ // defined by a NonNull instruction, then the type analysis could have used this information
+ // to mark z as non-null. However, cleanupNonNull() have now replaced y by a nullable value x.
+ // Since z is defined as "z = (T) x", and x is nullable, it is no longer sound to have that z
+ // is not nullable. This is fixed by rerunning the type analysis for the affected values.
+ new TypeAnalysis(appInfo, code.method).widening(affectedValues);
}
}
diff --git a/src/test/java/com/android/tools/r8/ir/analysis/type/NullabilityTest.java b/src/test/java/com/android/tools/r8/ir/analysis/type/NullabilityTest.java
index 0ea8731..c618dad 100644
--- a/src/test/java/com/android/tools/r8/ir/analysis/type/NullabilityTest.java
+++ b/src/test/java/com/android/tools/r8/ir/analysis/type/NullabilityTest.java
@@ -50,7 +50,7 @@
DexEncodedMethod foo = codeInspector.clazz(mainClass.getName()).method(signature).getMethod();
IRCode irCode =
foo.buildIR(appInfo, GraphLense.getIdentityLense(), TEST_OPTIONS, Origin.unknown());
- NonNullTracker nonNullTracker = new NonNullTracker();
+ NonNullTracker nonNullTracker = new NonNullTracker(appInfo);
nonNullTracker.addNonNull(irCode);
TypeAnalysis analysis = new TypeAnalysis(appInfo, foo);
analysis.widening(foo, irCode);
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/NonNullTrackerTest.java b/src/test/java/com/android/tools/r8/ir/optimize/NonNullTrackerTest.java
index 058f017..564a81c 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/NonNullTrackerTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/NonNullTrackerTest.java
@@ -41,7 +41,7 @@
foo.buildIR(appInfo, GraphLense.getIdentityLense(), TEST_OPTIONS, Origin.unknown());
checkCountOfNonNull(irCode, 0);
- NonNullTracker nonNullTracker = new NonNullTracker();
+ NonNullTracker nonNullTracker = new NonNullTracker(appInfo);
nonNullTracker.addNonNull(irCode);
assertTrue(irCode.isConsistentSSA());