Compute bridge info for declared bridges using invoke-virtual
Bug: 153147967
Change-Id: I184fa31a3e0ccb06a0bfd70684657c34a2450df9
diff --git a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
index 20337fd..03f1415 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
@@ -257,6 +257,10 @@
assert parameterAnnotationsList != null;
}
+ public DexType returnType() {
+ return method.proto.returnType;
+ }
+
public ParameterAnnotationsList liveParameterAnnotations(AppView<AppInfoWithLiveness> appView) {
return parameterAnnotationsList.keepIf(
annotation -> AnnotationRemover.shouldKeepAnnotation(appView, this, annotation));
@@ -299,6 +303,10 @@
return accessFlags.isAbstract();
}
+ public boolean isBridge() {
+ return accessFlags.isBridge();
+ }
+
public boolean isFinal() {
return accessFlags.isFinal();
}
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 57c923c..ca2cdd4 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
@@ -440,6 +440,10 @@
return uniqueUsers = ImmutableSet.copyOf(users);
}
+ public boolean hasSingleUniqueUser() {
+ return uniqueUsers().size() == 1;
+ }
+
public Instruction singleUniqueUser() {
assert ImmutableSet.copyOf(users).size() == 1;
return users.getFirst();
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfoCollector.java b/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfoCollector.java
index 4889b2e..d1ddce9 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfoCollector.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfoCollector.java
@@ -85,6 +85,7 @@
import com.android.tools.r8.ir.optimize.classinliner.ClassInlinerReceiverAnalysis;
import com.android.tools.r8.ir.optimize.info.ParameterUsagesInfo.ParameterUsage;
import com.android.tools.r8.ir.optimize.info.ParameterUsagesInfo.ParameterUsageBuilder;
+import com.android.tools.r8.ir.optimize.info.bridge.BridgeAnalyzer;
import com.android.tools.r8.ir.optimize.info.field.InstanceFieldInitializationInfoCollection;
import com.android.tools.r8.ir.optimize.info.initializer.DefaultInstanceInitializerInfo;
import com.android.tools.r8.ir.optimize.info.initializer.InstanceInitializerInfo;
@@ -126,6 +127,7 @@
DynamicTypeOptimization dynamicTypeOptimization,
InstanceFieldInitializationInfoCollection instanceFieldInitializationInfos,
Timing timing) {
+ identifyBridgeInfo(method, code, feedback, timing);
identifyClassInlinerEligibility(method, code, feedback, timing);
identifyParameterUsages(method, code, feedback, timing);
identifyReturnsArgument(method, code, feedback, timing);
@@ -142,6 +144,13 @@
computeNonNullParamOnNormalExits(feedback, code, timing);
}
+ private void identifyBridgeInfo(
+ DexEncodedMethod method, IRCode code, OptimizationFeedback feedback, Timing timing) {
+ timing.begin("Identify bridge info");
+ feedback.setBridgeInfo(method, BridgeAnalyzer.analyzeMethod(method, code));
+ timing.end();
+ }
+
private void identifyClassInlinerEligibility(
DexEncodedMethod method, IRCode code, OptimizationFeedback feedback, Timing timing) {
timing.begin("Identify class inliner eligibility");
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/bridge/BridgeAnalyzer.java b/src/main/java/com/android/tools/r8/ir/optimize/info/bridge/BridgeAnalyzer.java
new file mode 100644
index 0000000..8f28fd5
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/bridge/BridgeAnalyzer.java
@@ -0,0 +1,175 @@
+// 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.info.bridge;
+
+import static com.android.tools.r8.ir.code.Opcodes.ARGUMENT;
+import static com.android.tools.r8.ir.code.Opcodes.ASSUME;
+import static com.android.tools.r8.ir.code.Opcodes.CHECK_CAST;
+import static com.android.tools.r8.ir.code.Opcodes.INVOKE_VIRTUAL;
+import static com.android.tools.r8.ir.code.Opcodes.RETURN;
+
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.ir.code.CheckCast;
+import com.android.tools.r8.ir.code.IRCode;
+import com.android.tools.r8.ir.code.Instruction;
+import com.android.tools.r8.ir.code.InvokeMethod;
+import com.android.tools.r8.ir.code.InvokeVirtual;
+import com.android.tools.r8.ir.code.Return;
+import com.android.tools.r8.ir.code.Value;
+import com.android.tools.r8.utils.BooleanUtils;
+
+public class BridgeAnalyzer {
+
+ /** Returns a {@link BridgeInfo} object describing this method if it is recognized as a bridge. */
+ public static BridgeInfo analyzeMethod(DexEncodedMethod method, IRCode code) {
+ // TODO(b/154263783): Consider computing BridgeInfo also for non-declared bridges.
+ if (!method.isBridge() || code.blocks.size() > 1) {
+ return failure();
+ }
+
+ // Scan through the instructions one-by-one. We expect a sequence of Argument instructions,
+ // followed by a (possibly empty) sequence of CheckCast instructions, followed by a single
+ // InvokeMethod instruction, followed by an optional CheckCast instruction, followed by a Return
+ // instruction.
+ InvokeVirtual uniqueInvoke = null;
+ CheckCast uniqueReturnCast = null;
+ for (Instruction instruction : code.entryBlock().getInstructions()) {
+ switch (instruction.opcode()) {
+ case ARGUMENT:
+ break;
+
+ case ASSUME:
+ break;
+
+ case CHECK_CAST:
+ {
+ CheckCast checkCast = instruction.asCheckCast();
+ if (!analyzeCheckCast(method, checkCast, uniqueInvoke)) {
+ return failure();
+ }
+ // If we have moved past the single invoke instruction, then record that this cast is
+ // the cast instruction for the result value.
+ if (uniqueInvoke != null) {
+ if (uniqueReturnCast != null) {
+ return failure();
+ }
+ uniqueReturnCast = checkCast;
+ }
+ break;
+ }
+
+ case INVOKE_VIRTUAL:
+ {
+ if (uniqueInvoke != null) {
+ return failure();
+ }
+ InvokeVirtual invoke = instruction.asInvokeVirtual();
+ if (!analyzeInvokeVirtual(invoke)) {
+ return failure();
+ }
+ // Record that we have seen the single invoke instruction.
+ uniqueInvoke = invoke;
+ break;
+ }
+
+ case RETURN:
+ if (!analyzeReturn(instruction.asReturn(), uniqueInvoke, uniqueReturnCast)) {
+ return failure();
+ }
+ break;
+
+ default:
+ return failure();
+ }
+ }
+
+ assert uniqueInvoke != null;
+ return new VirtualBridgeInfo(uniqueInvoke.getInvokedMethod());
+ }
+
+ private static boolean analyzeCheckCast(
+ DexEncodedMethod method, CheckCast checkCast, InvokeMethod invoke) {
+ return invoke == null
+ ? analyzeCheckCastBeforeInvoke(checkCast)
+ : analyzeCheckCastAfterInvoke(method, checkCast, invoke);
+ }
+
+ private static boolean analyzeCheckCastBeforeInvoke(CheckCast checkCast) {
+ Value object = checkCast.object().getAliasedValue();
+ // It must be casting one of the arguments.
+ if (!object.isArgument()) {
+ return false;
+ }
+ int argumentIndex = object.definition.asArgument().getIndex();
+ Value castValue = checkCast.outValue();
+ // The out value cannot have any phi users since there is only one block.
+ assert !castValue.hasPhiUsers();
+ // It is not allowed to have any other users than the invoke instruction.
+ if (castValue.hasDebugUsers() || !castValue.hasSingleUniqueUser()) {
+ return false;
+ }
+ InvokeMethod invoke = castValue.singleUniqueUser().asInvokeMethod();
+ if (invoke == null) {
+ return false;
+ }
+ if (invoke.arguments().size() <= argumentIndex) {
+ return false;
+ }
+ int parameterIndex = argumentIndex - BooleanUtils.intValue(invoke.isInvokeMethodWithReceiver());
+ // It is not allowed to cast the receiver.
+ if (parameterIndex == -1) {
+ return false;
+ }
+ // The type of the cast must match the type of the parameter.
+ if (checkCast.getType() != invoke.getInvokedMethod().proto.getParameter(parameterIndex)) {
+ return false;
+ }
+ // It must be forwarded at the same argument index.
+ Value argument = invoke.getArgument(argumentIndex);
+ if (argument != castValue) {
+ return false;
+ }
+ return true;
+ }
+
+ private static boolean analyzeCheckCastAfterInvoke(
+ DexEncodedMethod method, CheckCast checkCast, InvokeMethod invoke) {
+ Value returnValue = invoke.outValue();
+ Value uncastValue = checkCast.object().getAliasedValue();
+ Value castValue = checkCast.outValue();
+ // The out value cannot have any phi users since there is only one block.
+ assert !castValue.hasPhiUsers();
+ // It must cast the result to the return type of the enclosing method and return the cast value.
+ return uncastValue == returnValue
+ && checkCast.getType() == method.returnType()
+ && !castValue.hasDebugUsers()
+ && castValue.hasSingleUniqueUser()
+ && castValue.singleUniqueUser().isReturn();
+ }
+
+ private static boolean analyzeInvokeVirtual(InvokeVirtual invoke) {
+ // All of the forwarded arguments of the enclosing method must be in the same argument position.
+ for (int argumentIndex = 0; argumentIndex < invoke.arguments().size(); argumentIndex++) {
+ Value argument = invoke.getArgument(argumentIndex);
+ if (argument.isArgument() && argumentIndex != argument.definition.asArgument().getIndex()) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ private static boolean analyzeReturn(Return ret, InvokeMethod invoke, CheckCast returnCast) {
+ // If we haven't seen an invoke this is not a bridge.
+ if (invoke == null) {
+ return false;
+ }
+ // It must not return a value, or the return value must be the result value of the invoke.
+ return ret.isReturnVoid()
+ || ret.returnValue() == (returnCast != null ? returnCast : invoke).outValue();
+ }
+
+ private static BridgeInfo failure() {
+ return null;
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/bridge/BridgeInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/bridge/BridgeInfo.java
index a8a935e..2683d2f 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/bridge/BridgeInfo.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/bridge/BridgeInfo.java
@@ -7,4 +7,13 @@
* A piece of optimization info that is computed for bridge methods. The info stores details about
* the behavior of the bridge.
*/
-public abstract class BridgeInfo {}
+public abstract class BridgeInfo {
+
+ public boolean isVirtualBridgeInfo() {
+ return false;
+ }
+
+ public VirtualBridgeInfo asVirtualBridgeInfo() {
+ return null;
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/bridge/VirtualBridgeInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/bridge/VirtualBridgeInfo.java
new file mode 100644
index 0000000..a07abdc
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/bridge/VirtualBridgeInfo.java
@@ -0,0 +1,49 @@
+// 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.info.bridge;
+
+import com.android.tools.r8.graph.DexMethod;
+
+/**
+ * Optimization info computed for bridge methods that use an invoke-virtual instruction.
+ *
+ * <p>If the method is {@code String A.m(Object)} and {@link #invokedMethod} is {@code Object
+ * B.m(String)}, then the bridge is implemented as:
+ *
+ * <pre>
+ * java.lang.String A.m(java.lang.Object o) {
+ * v0 <- Argument
+ * v1 <- CheckCast v0, java.lang.String
+ * v2 <- InvokeVirtual { v0, v1 }, java.lang.Object B.m(java.lang.String)
+ * v3 <- CheckCast v2, java.lang.String
+ * Return v3
+ * }
+ * </pre>
+ *
+ * <p>This currently does not allow any permutation of the argument order, and it also does not
+ * allow constants to be passed as arguments.
+ */
+public class VirtualBridgeInfo extends BridgeInfo {
+
+ // The targeted method.
+ private final DexMethod invokedMethod;
+
+ public VirtualBridgeInfo(DexMethod invokedMethod) {
+ this.invokedMethod = invokedMethod;
+ }
+
+ public DexMethod getInvokedMethod() {
+ return invokedMethod;
+ }
+
+ @Override
+ public boolean isVirtualBridgeInfo() {
+ return true;
+ }
+
+ @Override
+ public VirtualBridgeInfo asVirtualBridgeInfo() {
+ return this;
+ }
+}