CheckCast removal after class inlining
Bug: 113129394
Change-Id: Ibc8747f9773f513d9f9c99b831d9d32afa21ba37
diff --git a/src/main/java/com/android/tools/r8/code/CheckCast.java b/src/main/java/com/android/tools/r8/code/CheckCast.java
index 9416a7b..57151fa 100644
--- a/src/main/java/com/android/tools/r8/code/CheckCast.java
+++ b/src/main/java/com/android/tools/r8/code/CheckCast.java
@@ -38,6 +38,11 @@
}
@Override
+ public boolean isCheckCast() {
+ return true;
+ }
+
+ @Override
public void registerUse(UseRegistry registry) {
registry.registerCheckCast(getType());
}
diff --git a/src/main/java/com/android/tools/r8/code/ConstString.java b/src/main/java/com/android/tools/r8/code/ConstString.java
index d165de5..ed7847a 100644
--- a/src/main/java/com/android/tools/r8/code/ConstString.java
+++ b/src/main/java/com/android/tools/r8/code/ConstString.java
@@ -45,6 +45,11 @@
}
@Override
+ public boolean isConstString() {
+ return true;
+ }
+
+ @Override
public String toString(ClassNameMapper naming) {
return formatString("v" + AA + ", \"" + BBBB.toString() + "\"");
}
diff --git a/src/main/java/com/android/tools/r8/code/Instruction.java b/src/main/java/com/android/tools/r8/code/Instruction.java
index c8b5154..fbb93ca 100644
--- a/src/main/java/com/android/tools/r8/code/Instruction.java
+++ b/src/main/java/com/android/tools/r8/code/Instruction.java
@@ -122,6 +122,14 @@
this.offset = offset;
}
+ public boolean isCheckCast() {
+ return false;
+ }
+
+ public boolean isConstString() {
+ return false;
+ }
+
public boolean isSimpleNop() {
return !isPayload() && this instanceof Nop;
}
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 17d407b..f8b85de 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
@@ -1672,8 +1672,7 @@
// A < B
// A a = ...
// B b = (B) a;
- assert inTypeLattice.isNull()
- || outTypeLattice.asNullable().equals(inTypeLattice.asNullable());
+ assert TypeLatticeElement.lessThanOrEqual(appInfo, inTypeLattice, outTypeLattice);
needToRemoveTrivialPhis = needToRemoveTrivialPhis || outValue.numberOfPhiUsers() != 0;
removeOrReplaceByDebugLocalWrite(checkCast, it, inValue, outValue);
} else {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInliner.java b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInliner.java
index 0ef4597..6999f9a 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInliner.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInliner.java
@@ -137,6 +137,7 @@
// of roots to avoid infinite inlining. Looping makes possible for some roots to
// become eligible after other roots are inlined.
+ boolean anyInlinedMethods = false;
boolean repeat;
do {
repeat = false;
@@ -169,18 +170,24 @@
}
// Inline the class instance.
- boolean anyInlinedMethods = processor.processInlining(code, inliner);
+ anyInlinedMethods |= processor.processInlining(code, inliner);
// Restore normality.
code.removeAllTrivialPhis();
assert code.isConsistentSSA();
- if (anyInlinedMethods) {
- codeRewriter.simplifyIf(code);
- }
rootsIterator.remove();
repeat = true;
}
} while (repeat);
+
+ if (anyInlinedMethods) {
+ // If a method was inlined we may be able to remove check-cast instructions because we may
+ // have more information about the types of the arguments at the call site. This is
+ // particularly important for bridge methods.
+ codeRewriter.removeCasts(code);
+ // If a method was inlined we may be able to prune additional branches.
+ codeRewriter.simplifyIf(code);
+ }
}
private boolean isClassEligible(AppInfo appInfo, DexClass clazz) {
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/checkcast/RemoveCheckCastAfterClassInlining.java b/src/test/java/com/android/tools/r8/ir/optimize/checkcast/RemoveCheckCastAfterClassInlining.java
new file mode 100644
index 0000000..d52725e
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/checkcast/RemoveCheckCastAfterClassInlining.java
@@ -0,0 +1,79 @@
+// 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.checkcast;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.code.Instruction;
+import com.android.tools.r8.graph.DexCode;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.utils.AndroidApp;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import com.google.common.collect.ImmutableList;
+import org.junit.Test;
+
+public class RemoveCheckCastAfterClassInlining extends TestBase {
+
+ @Test
+ public void test() throws Exception {
+ AndroidApp input = readClasses(Lambda.class, Lambda.Consumer.class);
+ AndroidApp output =
+ compileWithR8(
+ input,
+ keepMainProguardConfiguration(Lambda.class),
+ options -> options.enableMinification = false);
+
+ // Extract main method.
+ CodeInspector inspector = new CodeInspector(output);
+ ClassSubject classSubject = inspector.clazz(Lambda.class);
+ MethodSubject methodSubject = classSubject.mainMethod();
+ assertThat(methodSubject, isPresent());
+
+ DexEncodedMethod method = methodSubject.getMethod();
+ assertTrue(method.hasCode());
+
+ DexCode code = method.getCode().asDexCode();
+ int numberOfConstStringInstructions = 0;
+ for (Instruction instruction : code.instructions) {
+ // Make sure that we do not load a const-string and then subsequently use a check-cast
+ // instruction to check if it is actually a string.
+ assertFalse(instruction.isCheckCast());
+ if (instruction.isConstString()) {
+ numberOfConstStringInstructions++;
+ }
+ }
+
+ // Sanity check that load() was actually inlined.
+ assertThat(
+ classSubject.method("void", "load", ImmutableList.of(Lambda.Consumer.class.getName())),
+ not(isPresent()));
+ assertEquals(2, numberOfConstStringInstructions);
+ }
+}
+
+class Lambda {
+
+ interface Consumer<T> {
+ void accept(T value);
+ }
+
+ public static void main(String... args) {
+ load(s -> System.out.println(s));
+ // Other code…
+ load(s -> System.out.println(s));
+ }
+
+ public static void load(Consumer<String> c) {
+ c.accept("Hello!");
+ }
+}