Reland "Update the type-information after checkcast removal"
This reverts commit cfb96799497ad78ca35ae0287fc5f5c1d8eed8f4.
Bug: 134546895
Bug: 133739171
Change-Id: Ic19d74de581a42d6a35dc56d7acab66684b13d62
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/type/NullabilityVariants.java b/src/main/java/com/android/tools/r8/ir/analysis/type/NullabilityVariants.java
index e9baec4..9a0f95a 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/type/NullabilityVariants.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/type/NullabilityVariants.java
@@ -12,6 +12,7 @@
private T maybeNullVariant;
private T definitelyNullVariant;
private T definitelyNotNullVariant;
+ private T bottomVariant;
public static <T extends ReferenceTypeLatticeElement> T create(
Nullability nullability, Function<NullabilityVariants<T>, T> callback) {
@@ -26,9 +27,11 @@
maybeNullVariant = element;
} else if (nullability == Nullability.definitelyNull()) {
definitelyNullVariant = element;
- } else {
- assert nullability == Nullability.definitelyNotNull();
+ } else if (nullability == Nullability.definitelyNotNull()) {
definitelyNotNullVariant = element;
+ } else {
+ assert nullability == Nullability.bottom();
+ bottomVariant = element;
}
}
@@ -37,9 +40,11 @@
return maybeNullVariant;
} else if (nullability == Nullability.definitelyNull()) {
return definitelyNullVariant;
- } else {
- assert nullability == Nullability.definitelyNotNull();
+ } else if (nullability == Nullability.definitelyNotNull()) {
return definitelyNotNullVariant;
+ } else {
+ assert nullability == Nullability.bottom();
+ return bottomVariant;
}
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/IRCode.java b/src/main/java/com/android/tools/r8/ir/code/IRCode.java
index e6249ff..9e045d9 100644
--- a/src/main/java/com/android/tools/r8/ir/code/IRCode.java
+++ b/src/main/java/com/android/tools/r8/ir/code/IRCode.java
@@ -11,6 +11,7 @@
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.ir.analysis.TypeChecker;
import com.android.tools.r8.ir.analysis.type.ClassTypeLatticeElement;
+import com.android.tools.r8.ir.analysis.type.Nullability;
import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
import com.android.tools.r8.ir.conversion.IRBuilder;
import com.android.tools.r8.origin.Origin;
@@ -734,6 +735,26 @@
});
}
+ public boolean verifyNoNullabilityBottomTypes() {
+ Predicate<Value> verifyValue =
+ v -> {
+ assert v.getTypeLattice().isPrimitive()
+ || v.getTypeLattice().asReferenceTypeLatticeElement().nullability()
+ != Nullability.bottom();
+ return true;
+ };
+ return verifySSATypeLattice(
+ v -> {
+ // StackValues is an artificial type created to allow returning multiple values from an
+ // instruction.
+ if (v instanceof StackValues) {
+ return Stream.of(((StackValues) v).getStackValues()).allMatch(verifyValue);
+ } else {
+ return verifyValue(v);
+ }
+ });
+ }
+
private boolean verifySSATypeLattice(Predicate<Value> tester) {
for (BasicBlock block : blocks) {
for (Instruction instruction : block.getInstructions()) {
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 2aea633..ec6da10 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
@@ -41,10 +41,10 @@
import com.android.tools.r8.ir.code.InvokeStatic;
import com.android.tools.r8.ir.code.NumericType;
import com.android.tools.r8.ir.code.Value;
+import com.android.tools.r8.ir.desugar.BackportedMethodRewriter;
import com.android.tools.r8.ir.desugar.CovariantReturnTypeAnnotationTransformer;
import com.android.tools.r8.ir.desugar.D8NestBasedAccessDesugaring;
import com.android.tools.r8.ir.desugar.InterfaceMethodRewriter;
-import com.android.tools.r8.ir.desugar.BackportedMethodRewriter;
import com.android.tools.r8.ir.desugar.LambdaRewriter;
import com.android.tools.r8.ir.desugar.StringConcatRewriter;
import com.android.tools.r8.ir.desugar.TwrCloseResourceRewriter;
@@ -1038,6 +1038,8 @@
// dead code which is removed right before register allocation in performRegisterAllocation.
deadCodeRemover.run(code);
assert code.isConsistentSSA();
+ // Assert that we do not have unremoved dead code in the output.
+ assert code.verifyNoNullabilityBottomTypes();
if (options.enableDesugaring && enableTryWithResourcesDesugaring()) {
codeRewriter.rewriteThrowableAddAndGetSuppressed(code);
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java b/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
index af2aeba..6576097 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
@@ -1991,14 +1991,32 @@
if (!appView.enableWholeProgramOptimizations()) {
return;
}
-
+ // If we can remove a CheckCast it is due to us having at least as much information about the
+ // type as the CheckCast gives. We then need to propagate that information to the users of
+ // the CheckCast to ensure further optimizations and removals of CheckCast:
+ //
+ // : 1: NewArrayEmpty v2 <- v1(1) java.lang.String[] <-- v2 = String[]
+ // ...
+ // : 2: CheckCast v5 <- v2; java.lang.Object[] <-- v5 = Object[]
+ // ...
+ // : 3: ArrayGet v7 <- v5, v6(0) <-- v7 = Object
+ // : 4: CheckCast v8 <- v7; java.lang.String <-- v8 = String
+ // ...
+ //
+ // When looking at line 2 we can conclude that the CheckCast is trivial because v2 is String[]
+ // and remove it. However, v7 is still only known to be Object and we cannot remove the
+ // CheckCast at line 4 unless we update v7 with the most precise information by narrowing the
+ // affected values of v5. We therefore have to run the type analysis after each CheckCast
+ // removal.
+ TypeAnalysis typeAnalysis = new TypeAnalysis(appView, code.method);
+ Set<Value> affectedValues = Sets.newIdentityHashSet();
InstructionIterator it = code.instructionIterator();
boolean needToRemoveTrivialPhis = false;
while (it.hasNext()) {
Instruction current = it.next();
if (current.isCheckCast()) {
boolean hasPhiUsers = current.outValue().numberOfPhiUsers() != 0;
- if (removeCheckCastInstructionIfTrivial(current.asCheckCast(), it, code)) {
+ if (removeCheckCastInstructionIfTrivial(current.asCheckCast(), it, code, affectedValues)) {
needToRemoveTrivialPhis |= hasPhiUsers;
}
} else if (current.isInstanceOf()) {
@@ -2007,6 +2025,10 @@
needToRemoveTrivialPhis |= hasPhiUsers;
}
}
+ if (!affectedValues.isEmpty()) {
+ typeAnalysis.narrowing(affectedValues);
+ affectedValues.clear();
+ }
}
// ... v1
// ...
@@ -2022,7 +2044,7 @@
// Returns true if the given check-cast instruction was removed.
private boolean removeCheckCastInstructionIfTrivial(
- CheckCast checkCast, InstructionIterator it, IRCode code) {
+ CheckCast checkCast, InstructionIterator it, IRCode code, Set<Value> affectedValues) {
Value inValue = checkCast.object();
Value outValue = checkCast.outValue();
DexType castType = checkCast.getType();
@@ -2054,7 +2076,15 @@
&& outValue.isUsed()
&& outValue.numberOfPhiUsers() == 0
&& outValue.uniqueUsers().stream().allMatch(isCheckcastToSubtype)) {
+ // The removeOrReplaceByDebugLocalWrite will propagate the incoming value for the CheckCast
+ // to the users of the CheckCast's out value.
+ //
+ // v2 = CheckCast A v1 ~~> DebugLocalWrite $v0 <- v1
+ //
+ // The DebugLocalWrite is not a user of the outvalue, we therefore have to wait and take the
+ // CheckCast invalue users that includes the potential DebugLocalWrite.
removeOrReplaceByDebugLocalWrite(checkCast, it, inValue, outValue);
+ affectedValues.addAll(inValue.affectedValues());
return true;
}
@@ -2074,7 +2104,15 @@
// A a = ...
// B b = (B) a;
assert inTypeLattice.lessThanOrEqual(outTypeLattice, appView);
+ // The removeOrReplaceByDebugLocalWrite will propagate the incoming value for the CheckCast
+ // to the users of the CheckCast's out value.
+ //
+ // v2 = CheckCast A v1 ~~> DebugLocalWrite $v0 <- v1
+ //
+ // The DebugLocalWrite is not a user of the outvalue, we therefore have to wait and take the
+ // CheckCast invalue users that includes the potential DebugLocalWrite.
removeOrReplaceByDebugLocalWrite(checkCast, it, inValue, outValue);
+ affectedValues.addAll(inValue.affectedValues());
return true;
}
@@ -2165,13 +2203,14 @@
}
}
if (result != InstanceOfResult.UNKNOWN) {
- it.replaceCurrentInstruction(
+ ConstNumber newInstruction =
new ConstNumber(
new Value(
code.valueNumberGenerator.next(),
TypeLatticeElement.INT,
instanceOf.outValue().getLocalInfo()),
- result == InstanceOfResult.TRUE ? 1 : 0));
+ result == InstanceOfResult.TRUE ? 1 : 0);
+ it.replaceCurrentInstruction(newInstruction);
return true;
}
return false;
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/checkcast/CheckCastDebugTestRunner.java b/src/test/java/com/android/tools/r8/ir/optimize/checkcast/CheckCastDebugTestRunner.java
index e440876..52a1f72 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/checkcast/CheckCastDebugTestRunner.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/checkcast/CheckCastDebugTestRunner.java
@@ -81,7 +81,7 @@
assertThat(method, isPresent());
long count =
Streams.stream(method.iterateInstructions(InstructionSubject::isCheckCast)).count();
- assertEquals(1, count);
+ assertEquals(0, count);
DebugTestConfig config = backend == Backend.CF
? new CfDebugTestConfig()
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/checkcast/CheckCastRemovalTest.java b/src/test/java/com/android/tools/r8/ir/optimize/checkcast/CheckCastRemovalTest.java
index 83890de..aa58df7 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/checkcast/CheckCastRemovalTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/checkcast/CheckCastRemovalTest.java
@@ -218,7 +218,6 @@
}
@Test
- @Ignore("b/133739171")
public void bothUpAndDowncast() throws Exception {
JasminBuilder builder = new JasminBuilder();
ClassBuilder classBuilder = builder.addClass(CLASS_NAME);