blob: 8f28fd58b709c7954ed7a93b8fc50f6c2d58578c [file] [log] [blame]
// 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;
}
}