Handling roots removed after inlining of other roots.
Also adds check for methods w/o normal exit bloks which inliner
refuses to inline into blocks with catch handlers.
Bug: 110963213
Bug: 110736241
Change-Id: Ieececfb300713d3e0b6a6672ca938f7718198d72
diff --git a/src/main/java/com/android/tools/r8/ir/code/BasicBlock.java b/src/main/java/com/android/tools/r8/ir/code/BasicBlock.java
index 819c6f2..5bed061 100644
--- a/src/main/java/com/android/tools/r8/ir/code/BasicBlock.java
+++ b/src/main/java/com/android/tools/r8/ir/code/BasicBlock.java
@@ -659,6 +659,7 @@
for (Instruction instruction : getInstructions()) {
if (instruction.outValue != null) {
instruction.outValue.clearUsers();
+ instruction.setOutValue(null);
}
for (Value value : instruction.inValues) {
value.removeUser(instruction);
diff --git a/src/main/java/com/android/tools/r8/ir/code/BasicBlockInstructionIterator.java b/src/main/java/com/android/tools/r8/ir/code/BasicBlockInstructionIterator.java
index 58b61af..1adcd11 100644
--- a/src/main/java/com/android/tools/r8/ir/code/BasicBlockInstructionIterator.java
+++ b/src/main/java/com/android/tools/r8/ir/code/BasicBlockInstructionIterator.java
@@ -390,7 +390,7 @@
ImmutableList<BasicBlock> normalExits = inlinee.computeNormalExitBlocks();
if (normalExits.isEmpty()) {
assert inlineeCanThrow;
- // TODO(sgjesse): Remove this restriction.
+ // TODO(sgjesse): Remove this restriction (see b/64432527).
assert !invokeBlock.hasCatchHandlers();
blocksToRemove.addAll(
invokePredecessor.unlink(invokeBlock, new DominatorTree(code)));
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java
index 8556905..b3bde90 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java
@@ -444,6 +444,12 @@
return null;
}
+ // Don't inline code w/o normal returns into block with catch handlers (b/64432527).
+ if (initInvoke.getBlock().hasCatchHandlers() &&
+ definition.getOptimizationInfo().neverReturnsNormally()) {
+ return null;
+ }
+
// If the superclass of the initializer is NOT java.lang.Object, the super class
// initializer being called must be classified as TrivialInstanceInitializer.
//
@@ -476,16 +482,16 @@
if (invoke.inValues().lastIndexOf(eligibleInstance) > 0) {
return null; // Instance passed as an argument.
}
- return isEligibleMethodCall(invoke.getInvokedMethod(),
+ return isEligibleMethodCall(!invoke.getBlock().hasCatchHandlers(), invoke.getInvokedMethod(),
eligibility -> !eligibility.returnsReceiver ||
invoke.outValue() == null || invoke.outValue().numberOfAllUsers() == 0);
}
private InliningInfo isEligibleIndirectMethodCall(DexMethod callee) {
- return isEligibleMethodCall(callee, eligibility -> !eligibility.returnsReceiver);
+ return isEligibleMethodCall(false, callee, eligibility -> !eligibility.returnsReceiver);
}
- private InliningInfo isEligibleMethodCall(
+ private InliningInfo isEligibleMethodCall(boolean allowMethodsWithoutNormalReturns,
DexMethod callee, Predicate<ClassInlinerEligibility> eligibilityAcceptanceCheck) {
DexEncodedMethod singleTarget = findSingleTarget(callee);
@@ -496,8 +502,9 @@
return null; // Don't inline itself.
}
- ClassInlinerEligibility eligibility =
- singleTarget.getOptimizationInfo().getClassInlinerEligibility();
+ OptimizationInfo optimizationInfo = singleTarget.getOptimizationInfo();
+
+ ClassInlinerEligibility eligibility = optimizationInfo.getClassInlinerEligibility();
if (eligibility == null) {
return null;
}
@@ -508,6 +515,11 @@
return null;
}
+ // Don't inline code w/o normal returns into block with catch handlers (b/64432527).
+ if (!allowMethodsWithoutNormalReturns && optimizationInfo.neverReturnsNormally()) {
+ return null;
+ }
+
if (!singleTarget.isInliningCandidate(method, Reason.SIMPLE, appInfo)) {
// We won't be able to inline it here.
@@ -570,6 +582,11 @@
OptimizationInfo optimizationInfo = singleTarget.getOptimizationInfo();
+ // Don't inline code w/o normal returns into block with catch handlers (b/64432527).
+ if (invokeMethod.getBlock().hasCatchHandlers() && optimizationInfo.neverReturnsNormally()) {
+ return false;
+ }
+
// Go through all arguments, see if all usages of eligibleInstance are good.
for (int argIndex = 0; argIndex < arguments.size(); argIndex++) {
Value argument = arguments.get(argIndex);
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerTest.java b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerTest.java
index 51a5262..3239191 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerTest.java
@@ -9,6 +9,7 @@
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import com.android.tools.r8.OutputMode;
@@ -27,6 +28,7 @@
import com.android.tools.r8.ir.optimize.classinliner.builders.Tuple;
import com.android.tools.r8.ir.optimize.classinliner.code.C;
import com.android.tools.r8.ir.optimize.classinliner.code.CodeTestClass;
+import com.android.tools.r8.ir.optimize.classinliner.invalidroot.InvalidRootsTestClass;
import com.android.tools.r8.ir.optimize.classinliner.trivial.ClassWithFinal;
import com.android.tools.r8.ir.optimize.classinliner.trivial.CycleReferenceAB;
import com.android.tools.r8.ir.optimize.classinliner.trivial.CycleReferenceBA;
@@ -254,6 +256,51 @@
assertFalse(inspector.clazz(C.F.class).isPresent());
}
+ @Test
+ public void testInvalidatedRoot() throws Exception {
+ String prefix = "com.android.tools.r8.ir.optimize.classinliner.invalidroot.";
+
+ byte[][] classes = {
+ ToolHelper.getClassAsBytes(InvalidRootsTestClass.class),
+ ToolHelper.getClassAsBytes(InvalidRootsTestClass.A.class),
+ ToolHelper.getClassAsBytes(InvalidRootsTestClass.B.class),
+ ToolHelper.getClassAsBytes(InvalidRootsTestClass.NeverReturnsNormally.class),
+ ToolHelper.getClassAsBytes(InvalidRootsTestClass.InitNeverReturnsNormally.class)
+ };
+ AndroidApp app = runR8(buildAndroidApp(classes), InvalidRootsTestClass.class);
+
+ String javaOutput = runOnJava(InvalidRootsTestClass.class);
+ String artOutput = runOnArt(app, InvalidRootsTestClass.class);
+ assertEquals(javaOutput, artOutput);
+
+ DexInspector inspector = new DexInspector(app);
+ ClassSubject clazz = inspector.clazz(InvalidRootsTestClass.class);
+
+ assertEquals(
+ Sets.newHashSet(prefix + "InvalidRootsTestClass$NeverReturnsNormally"),
+ collectTypes(clazz, "testExtraNeverReturnsNormally", "void"));
+
+ assertEquals(
+ Sets.newHashSet(prefix + "InvalidRootsTestClass$NeverReturnsNormally"),
+ collectTypes(clazz, "testDirectNeverReturnsNormally", "void"));
+
+ assertEquals(
+ Sets.newHashSet(prefix + "InvalidRootsTestClass$InitNeverReturnsNormally"),
+ collectTypes(clazz, "testInitNeverReturnsNormally", "void"));
+
+ assertTrue(inspector.clazz(InvalidRootsTestClass.NeverReturnsNormally.class).isPresent());
+ assertTrue(inspector.clazz(InvalidRootsTestClass.InitNeverReturnsNormally.class).isPresent());
+
+ assertEquals(
+ Sets.newHashSet(
+ "java.lang.StringBuilder",
+ "java.lang.RuntimeException"),
+ collectTypes(clazz, "testRootInvalidatesAfterInlining", "void"));
+
+ assertFalse(inspector.clazz(InvalidRootsTestClass.A.class).isPresent());
+ assertFalse(inspector.clazz(InvalidRootsTestClass.B.class).isPresent());
+ }
+
private Set<String> collectTypes(
ClassSubject clazz, String methodName, String retValue, String... params) {
return Stream.concat(
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/invalidroot/InvalidRootsTestClass.java b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/invalidroot/InvalidRootsTestClass.java
new file mode 100644
index 0000000..26dc8cc
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/invalidroot/InvalidRootsTestClass.java
@@ -0,0 +1,129 @@
+// Copyright (c) 2018, 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.classinliner.invalidroot;
+
+public class InvalidRootsTestClass {
+ private static int ID = 0;
+
+ private static String next() {
+ return Integer.toString(ID++);
+ }
+
+ public static void main(String[] args) {
+ InvalidRootsTestClass test = new InvalidRootsTestClass();
+ test.testExtraNeverReturnsNormally();
+ test.testDirectNeverReturnsNormally();
+ test.testInitNeverReturnsNormally();
+ test.testRootInvalidatesAfterInlining();
+ }
+
+ private synchronized void testExtraNeverReturnsNormally() {
+ testExtraNeverReturnsNormallyA();
+ testExtraNeverReturnsNormallyB();
+
+ try {
+ NeverReturnsNormally a = new NeverReturnsNormally();
+ neverReturnsNormallyExtra(next(), a);
+ } catch (RuntimeException re) {
+ System.out.println(re.toString());
+ }
+ }
+
+ private synchronized void testExtraNeverReturnsNormallyA() {
+ try {
+ neverReturnsNormallyExtra(next(), null);
+ } catch (RuntimeException re) {
+ System.out.println(re.toString());
+ }
+ }
+
+ private synchronized void testExtraNeverReturnsNormallyB() {
+ try {
+ neverReturnsNormallyExtra(next(), null);
+ } catch (RuntimeException re) {
+ System.out.println(re.toString());
+ }
+ }
+
+ private synchronized void testDirectNeverReturnsNormally() {
+ try {
+ NeverReturnsNormally a = new NeverReturnsNormally();
+ System.out.println(a.foo());
+ } catch (RuntimeException re) {
+ System.out.println(re.toString());
+ }
+ }
+
+ private synchronized void testInitNeverReturnsNormally() {
+ try {
+ new InitNeverReturnsNormally();
+ } catch (RuntimeException re) {
+ System.out.println(re.toString());
+ }
+ }
+
+ private void neverReturnsNormallyExtra(String prefix, NeverReturnsNormally a) {
+ throw new RuntimeException("neverReturnsNormallyExtra(" +
+ prefix + ", " + (a == null ? "null" : a.foo()) + "): " + next());
+ }
+
+ public static class NeverReturnsNormally {
+ public String foo() {
+ throw new RuntimeException("NeverReturnsNormally::foo(): " + next());
+ }
+ }
+
+ public static class InitNeverReturnsNormally {
+ public InitNeverReturnsNormally() {
+ throw new RuntimeException("InitNeverReturnsNormally::init(): " + next());
+ }
+
+ public String foo() {
+ return "InitNeverReturnsNormally::foo(): " + next();
+ }
+ }
+
+ private synchronized void testRootInvalidatesAfterInlining() {
+ A a = new A();
+ try {
+ notInlinedExtraMethod(next(), a);
+ System.out.println(new B().foo() + " " + next());
+ testRootInvalidatesAfterInliningA(a);
+ testRootInvalidatesAfterInliningB(a);
+ } catch (RuntimeException re) {
+ System.out.println(re.toString());
+ }
+ }
+
+ private void notInlinedExtraMethod(String prefix, A a) {
+ System.out.println("notInlinedExtraMethod(" +
+ prefix + ", " + (a == null ? "null" : a.foo()) + "): " + next());
+ if (a != null) {
+ throw new RuntimeException(
+ "notInlinedExtraMethod(" + prefix + ", " + a.foo() + "): " + next());
+ }
+ System.out.println("notInlinedExtraMethod(" + prefix + ", null): " + next());
+ }
+
+ private void testRootInvalidatesAfterInliningA(A a) {
+ notInlinedExtraMethod(next(), a);
+ }
+
+ private void testRootInvalidatesAfterInliningB(A a) {
+ notInlinedExtraMethod(next(), a);
+ }
+
+ public static class A {
+ public String foo() {
+ return "B::foo(" + next() + ")";
+ }
+ }
+
+ public static class B {
+ public String foo() {
+ return "B::foo(" + next() + ")";
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/neverreturnsnormally/NeverReturnsNormallyTest.java b/src/test/java/com/android/tools/r8/neverreturnsnormally/NeverReturnsNormallyTest.java
index 3e46098..c929eaf 100644
--- a/src/test/java/com/android/tools/r8/neverreturnsnormally/NeverReturnsNormallyTest.java
+++ b/src/test/java/com/android/tools/r8/neverreturnsnormally/NeverReturnsNormallyTest.java
@@ -22,12 +22,12 @@
import com.google.common.collect.ImmutableList;
import java.util.Iterator;
import java.util.function.BiConsumer;
-import org.junit.Ignore;
import org.junit.Test;
public class NeverReturnsNormallyTest extends TestBase {
private void runTest(
- BiConsumer<DexInspector, CompilationMode> inspection, CompilationMode mode) throws Exception {
+ BiConsumer<DexInspector, CompilationMode> inspection,
+ boolean enableClassInliner, CompilationMode mode) throws Exception {
R8Command.Builder builder = R8Command.builder();
builder.addProgramFiles(ToolHelper.getClassFileForTestClass(TestClass.class));
builder.setProgramConsumer(DexIndexedConsumer.emptyConsumer());
@@ -49,7 +49,8 @@
"-allowaccessmodification"
),
Origin.unknown());
- AndroidApp app = ToolHelper.runR8(builder.build());
+ AndroidApp app = ToolHelper.runR8(builder.build(),
+ opts -> opts.enableClassInlining = enableClassInliner);
inspection.accept(new DexInspector(app), mode);
// Run on Art to check generated code against verifier.
@@ -128,10 +129,11 @@
return instructions.next();
}
- @Ignore("b/110736241")
@Test
public void test() throws Exception {
- runTest(this::validate, CompilationMode.DEBUG);
- runTest(this::validate, CompilationMode.RELEASE);
+ runTest(this::validate, true, CompilationMode.DEBUG);
+ runTest(this::validate, true, CompilationMode.RELEASE);
+ runTest(this::validate, false, CompilationMode.DEBUG);
+ runTest(this::validate, false, CompilationMode.RELEASE);
}
}