| // 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().getAliasedValue() |
| == (returnCast != null ? returnCast : invoke).outValue(); |
| } |
| |
| private static BridgeInfo failure() { |
| return null; |
| } |
| } |