blob: 63a2886c654ec183a6707088624a81ac97bec811 [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.analysis.inlining;
import static com.android.tools.r8.ir.code.Opcodes.GOTO;
import static com.android.tools.r8.ir.code.Opcodes.IF;
import static com.android.tools.r8.ir.code.Opcodes.RETURN;
import static com.android.tools.r8.ir.code.Opcodes.STRING_SWITCH;
import static com.android.tools.r8.ir.code.Opcodes.THROW;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexItemFactory;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.ProgramMethod;
import com.android.tools.r8.ir.code.Argument;
import com.android.tools.r8.ir.code.BasicBlock;
import com.android.tools.r8.ir.code.IRCode;
import com.android.tools.r8.ir.code.If;
import com.android.tools.r8.ir.code.IfType;
import com.android.tools.r8.ir.code.Instruction;
import com.android.tools.r8.ir.code.InstructionIterator;
import com.android.tools.r8.ir.code.InvokeVirtual;
import com.android.tools.r8.ir.code.JumpInstruction;
import com.android.tools.r8.ir.code.StringSwitch;
import com.android.tools.r8.ir.code.Value;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
import com.android.tools.r8.utils.InternalOptions;
import com.google.common.collect.Sets;
import java.util.OptionalLong;
import java.util.Set;
/**
* Analysis that given a method computes a constraint which is satisfied by a concrete call site
* only if the method becomes simple after inlining into the concrete call site.
*
* <p>Examples of simple inlining constraints are:
*
* <ul>
* <li>Always simple,
* <li>Never simple,
* <li>Simple if argument i is {true, false, null, not-null},
* <li>Simple if argument i is true and argument j is false, or if argument i is false.
* </ul>
*/
public class SimpleInliningConstraintAnalysis {
private final SimpleInliningConstraintFactory constraintFactory;
private final DexItemFactory dexItemFactory;
private final ProgramMethod method;
private final InternalOptions options;
private final int simpleInliningConstraintThreshold;
private final Set<BasicBlock> seen = Sets.newIdentityHashSet();
public SimpleInliningConstraintAnalysis(
AppView<AppInfoWithLiveness> appView, ProgramMethod method) {
this.constraintFactory = appView.simpleInliningConstraintFactory();
this.dexItemFactory = appView.dexItemFactory();
this.method = method;
this.options = appView.options();
this.simpleInliningConstraintThreshold = appView.options().simpleInliningConstraintThreshold;
}
public SimpleInliningConstraintWithDepth analyzeCode(IRCode code) {
if (method.getReference().getArity() == 0) {
// The method does not have any parameters, so there is no need to analyze the method.
return SimpleInliningConstraintWithDepth.getNever();
}
if (options.debug) {
// Inlining is not enabled in debug mode.
return SimpleInliningConstraintWithDepth.getNever();
}
// Run a bounded depth-first traversal to collect the path constraints that lead to early
// returns.
InstructionIterator instructionIterator =
code.entryBlock().iterator(code.getNumberOfArguments());
return analyzeInstructionsInBlock(code.entryBlock(), 0, instructionIterator);
}
private SimpleInliningConstraintWithDepth analyzeInstructionsInBlock(
BasicBlock block, int depth) {
return analyzeInstructionsInBlock(block, depth, block.iterator());
}
private SimpleInliningConstraintWithDepth analyzeInstructionsInBlock(
BasicBlock block, int instructionDepth, InstructionIterator instructionIterator) {
// If we reach a block that has already been seen, give up.
if (!seen.add(block)) {
return SimpleInliningConstraintWithDepth.getNever();
}
// Move the instruction iterator forward to the block's jump instruction, while incrementing the
// instruction depth of the depth-first traversal.
Instruction instruction = instructionIterator.next();
SimpleInliningConstraint blockConstraint = AlwaysSimpleInliningConstraint.getInstance();
while (!instruction.isJumpInstruction()) {
assert !instruction.isArgument();
assert !instruction.isDebugInstruction();
SimpleInliningConstraint instructionConstraint =
computeConstraintForInstructionNotToMaterialize(instruction);
if (instructionConstraint.isAlways()) {
assert instruction.isAssume();
} else if (instructionConstraint.isNever()) {
instructionDepth++;
} else {
blockConstraint = blockConstraint.meet(instructionConstraint);
}
instruction = instructionIterator.next();
}
// If we have exceeded the threshold, then all paths from this instruction will not lead to any
// early exits, so return 'never'.
if (instructionDepth > simpleInliningConstraintThreshold) {
return SimpleInliningConstraintWithDepth.getNever();
}
SimpleInliningConstraintWithDepth jumpConstraint =
computeConstraintForJumpInstruction(instruction.asJumpInstruction(), instructionDepth);
return jumpConstraint.meet(blockConstraint);
}
private SimpleInliningConstraint computeConstraintForInstructionNotToMaterialize(
Instruction instruction) {
if (instruction.isAssume()) {
return AlwaysSimpleInliningConstraint.getInstance();
}
if (instruction.isInvokeVirtual()) {
InvokeVirtual invoke = instruction.asInvokeVirtual();
if (invoke.getInvokedMethod().isIdenticalTo(dexItemFactory.objectMembers.getClass)
&& invoke.hasUnusedOutValue()) {
Value receiver = invoke.getReceiver();
if (receiver.getType().isDefinitelyNotNull()) {
return AlwaysSimpleInliningConstraint.getInstance();
}
Value receiverRoot = receiver.getAliasedValue();
if (receiverRoot.isDefinedByInstructionSatisfying(Instruction::isArgument)) {
Argument argument = receiverRoot.getDefinition().asArgument();
return constraintFactory.createNotEqualToNullConstraint(argument.getIndex());
}
}
}
return NeverSimpleInliningConstraint.getInstance();
}
private SimpleInliningConstraintWithDepth computeConstraintForJumpInstruction(
JumpInstruction instruction, int instructionDepth) {
switch (instruction.opcode()) {
case IF:
If ifInstruction = instruction.asIf();
Value singleArgumentOperand = getSingleArgumentOperand(ifInstruction);
if (singleArgumentOperand == null || singleArgumentOperand.isThis()) {
break;
}
Value otherOperand =
ifInstruction.isZeroTest()
? null
: ifInstruction.getOperand(
1 - ifInstruction.inValues().indexOf(singleArgumentOperand));
int argumentIndex =
singleArgumentOperand.getAliasedValue().getDefinition().asArgument().getIndex();
DexType argumentType = method.getDefinition().getArgumentType(argumentIndex);
int currentDepth = instructionDepth;
// Compute the constraint for which paths through the true target are guaranteed to exit
// early.
SimpleInliningConstraintWithDepth trueTargetConstraint =
computeConstraintFromIfTest(
argumentIndex, argumentType, otherOperand, ifInstruction.getType())
// Only recurse into the true target if the constraint from the if-instruction
// is not 'never'.
.lazyMeet(
() -> analyzeInstructionsInBlock(ifInstruction.getTrueTarget(), currentDepth));
// Compute the constraint for which paths through the false target are guaranteed to
// exit early.
SimpleInliningConstraintWithDepth fallthroughTargetConstraint =
computeConstraintFromIfTest(
argumentIndex, argumentType, otherOperand, ifInstruction.getType().inverted())
// Only recurse into the false target if the constraint from the if-instruction
// is not 'never'.
.lazyMeet(
() ->
analyzeInstructionsInBlock(ifInstruction.fallthroughBlock(), currentDepth));
// Paths going through this basic block are guaranteed to exit early if the true target
// is guaranteed to exit early or the false target is.
return trueTargetConstraint.join(fallthroughTargetConstraint);
case GOTO:
return analyzeInstructionsInBlock(instruction.asGoto().getTarget(), instructionDepth);
case RETURN:
return AlwaysSimpleInliningConstraint.getInstance().withDepth(instructionDepth);
case STRING_SWITCH:
// Require that all cases including the default case are simple. In that case we can
// guarantee simpleness by requiring that the switch value is constant.
StringSwitch stringSwitch = instruction.asStringSwitch();
Value valueRoot = stringSwitch.value().getAliasedValue();
if (!valueRoot.isDefinedByInstructionSatisfying(Instruction::isArgument)) {
return SimpleInliningConstraintWithDepth.getNever();
}
int maxInstructionDepth = instructionDepth;
for (BasicBlock successor : stringSwitch.getBlock().getNormalSuccessors()) {
SimpleInliningConstraintWithDepth successorConstraintWithDepth =
analyzeInstructionsInBlock(successor, instructionDepth);
if (!successorConstraintWithDepth.getConstraint().isAlways()) {
return SimpleInliningConstraintWithDepth.getNever();
}
maxInstructionDepth =
Math.max(maxInstructionDepth, successorConstraintWithDepth.getInstructionDepth());
}
Argument argument = valueRoot.getDefinition().asArgument();
ConstSimpleInliningConstraint simpleConstraint =
constraintFactory.createConstConstraint(argument.getIndex());
return simpleConstraint.withDepth(maxInstructionDepth);
case THROW:
return instruction.getBlock().hasCatchHandlers()
? SimpleInliningConstraintWithDepth.getNever()
: SimpleInliningConstraintWithDepth.getAlways(instructionDepth);
default:
break;
}
// Give up.
return SimpleInliningConstraintWithDepth.getNever();
}
private SimpleInliningConstraint computeConstraintFromIfTest(
int argumentIndex, DexType argumentType, Value otherOperand, IfType type) {
boolean isZeroTest = otherOperand == null;
switch (type) {
case EQ:
if (isZeroTest) {
if (argumentType.isReferenceType()) {
return constraintFactory.createEqualToNullConstraint(argumentIndex);
}
if (argumentType.isBooleanType()) {
return constraintFactory.createEqualToFalseConstraint(argumentIndex);
}
} else if (argumentType.isPrimitiveType()) {
OptionalLong rawValue = getRawNumberValue(otherOperand);
if (rawValue.isPresent()) {
return constraintFactory.createEqualToNumberConstraint(
argumentIndex, rawValue.getAsLong());
}
}
return NeverSimpleInliningConstraint.getInstance();
case NE:
if (isZeroTest) {
if (argumentType.isReferenceType()) {
return constraintFactory.createNotEqualToNullConstraint(argumentIndex);
}
if (argumentType.isBooleanType()) {
return constraintFactory.createEqualToTrueConstraint(argumentIndex);
}
} else if (argumentType.isPrimitiveType()) {
OptionalLong rawValue = getRawNumberValue(otherOperand);
if (rawValue.isPresent()) {
return constraintFactory.createNotEqualToNumberConstraint(
argumentIndex, rawValue.getAsLong());
}
}
return NeverSimpleInliningConstraint.getInstance();
default:
return NeverSimpleInliningConstraint.getInstance();
}
}
private OptionalLong getRawNumberValue(Value value) {
Value root = value.getAliasedValue();
if (root.isDefinedByInstructionSatisfying(Instruction::isConstNumber)) {
return OptionalLong.of(root.getDefinition().asConstNumber().getRawValue());
}
return OptionalLong.empty();
}
private Value getSingleArgumentOperand(If ifInstruction) {
Value singleArgumentOperand = null;
Value lhs = ifInstruction.lhs();
if (lhs.getAliasedValue().isArgument()) {
singleArgumentOperand = lhs;
}
if (!ifInstruction.isZeroTest()) {
Value rhs = ifInstruction.rhs();
if (rhs.getAliasedValue().isArgument()) {
if (singleArgumentOperand != null) {
return null;
}
singleArgumentOperand = rhs;
}
}
return singleArgumentOperand;
}
}