blob: f80e1d7f4a0b5e22abc2f534d3ee996ff5551219 [file] [log] [blame]
// Copyright (c) 2019, 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.classinliner;
import static com.android.tools.r8.ir.code.Opcodes.ARGUMENT;
import static com.android.tools.r8.ir.code.Opcodes.INSTANCE_GET;
import static com.android.tools.r8.ir.code.Opcodes.INSTANCE_PUT;
import static com.android.tools.r8.ir.code.Opcodes.RETURN;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexProgramClass;
import com.android.tools.r8.graph.ProgramMethod;
import com.android.tools.r8.ir.analysis.type.TypeElement;
import com.android.tools.r8.ir.code.AssumeAndCheckCastAliasedValueConfiguration;
import com.android.tools.r8.ir.code.IRCode;
import com.android.tools.r8.ir.code.Instruction;
import com.android.tools.r8.ir.code.InstructionIterator;
import com.android.tools.r8.ir.code.InvokeMethod;
import com.android.tools.r8.ir.code.Value;
import com.android.tools.r8.ir.optimize.inliner.InliningIRProvider;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
import com.google.common.collect.Sets;
import java.util.List;
import java.util.Map;
import java.util.Set;
// TODO(b/160634549): Rename or refactor this to reflect its non-cost related analysis.
/** Analysis that estimates the cost of class inlining an object allocation. */
class ClassInlinerCostAnalysis {
private final AppView<AppInfoWithLiveness> appView;
private final InliningIRProvider inliningIRProvider;
private final Set<Value> definiteReceiverAliases;
private int estimatedCost = 0;
ClassInlinerCostAnalysis(
AppView<AppInfoWithLiveness> appView,
InliningIRProvider inliningIRProvider,
Set<Value> definiteReceiverAliases) {
this.appView = appView;
this.inliningIRProvider = inliningIRProvider;
this.definiteReceiverAliases = definiteReceiverAliases;
}
boolean willExceedInstructionBudget(
IRCode code,
DexProgramClass eligibleClass,
Map<InvokeMethod, ProgramMethod> directInlinees,
List<ProgramMethod> indirectInlinees) {
if (appView.appInfo().alwaysClassInline.contains(eligibleClass.type)) {
return false;
}
for (ProgramMethod inlinee : indirectInlinees) {
// We do not have the corresponding invoke instruction for the inlinees that are not called
// directly from `code` (these are called indirectly from one of the methods in
// `directInlinees`). Therefore, we currently choose not to build IR for estimating the number
// of non-materializing instructions, since we cannot cache the IR (it would have the wrong
// position).
int increment = inlinee.getDefinition().getCode().estimatedSizeForInlining();
if (exceedsInstructionBudgetAfterIncrement(increment)) {
return true;
}
}
// Visit the direct inlinees in a deterministic order to ensure that the state of the value
// number generated is deterministic for each inlinee.
int numberOfSeenDirectInlinees = 0;
int numberOfDirectInlinees = directInlinees.size();
for (InvokeMethod invoke : code.<InvokeMethod>instructions(Instruction::isInvokeMethod)) {
ProgramMethod inlinee = directInlinees.get(invoke);
if (inlinee == null) {
// Not a direct inlinee.
continue;
}
IRCode inliningIR = inliningIRProvider.getAndCacheInliningIR(invoke, inlinee);
// If the instance is part of a phi, then inlining will invalidate the inliner assumptions.
// TODO(b/160634549): This is not a budget miss but a hard requirement.
InstructionIterator iterator = inliningIR.entryBlock().iterator();
while (iterator.hasNext()) {
Instruction next = iterator.next();
if (!next.isArgument()) {
break;
}
Value argumentValue = next.outValue();
TypeElement argumentType = argumentValue.getType();
if (argumentType.isClassType()
&& argumentType.asClassType().getClassType() == eligibleClass.type) {
assert argumentValue.uniqueUsers().stream()
.noneMatch(
AssumeAndCheckCastAliasedValueConfiguration.getInstance()::isIntroducingAnAlias);
if (argumentValue.hasPhiUsers()) {
return true;
}
}
}
int increment =
inlinee.getDefinition().getCode().estimatedSizeForInlining()
- estimateNumberOfNonMaterializingInstructions(invoke, inliningIR);
assert increment >= 0;
if (exceedsInstructionBudgetAfterIncrement(increment)) {
return true;
}
if (++numberOfSeenDirectInlinees == numberOfDirectInlinees) {
break;
}
}
// Verify that all direct inlinees have been visited.
assert numberOfSeenDirectInlinees == numberOfDirectInlinees;
return false;
}
private boolean exceedsInstructionBudgetAfterIncrement(int increment) {
estimatedCost += increment;
return estimatedCost > appView.options().classInliningInstructionAllowance;
}
// TODO(b/143176500): Do not include instructions that will be canonicalized after inlining.
// Take care of that fact that after inlining the first method, this could introduce a constant
// in the caller, which could then lead to a constant in the second inlinee being canonicalized.
// TODO(b/143176500): Do not include instructions that will be dead code eliminated as a result of
// constant arguments.
private int estimateNumberOfNonMaterializingInstructions(InvokeMethod invoke, IRCode inlinee) {
int result = 0;
Set<Value> receiverAliasesInInlinee = null;
for (Instruction instruction : inlinee.instructions()) {
switch (instruction.opcode()) {
case ARGUMENT:
// Intentionally not counted as a non-materializing instruction, since there are no
// argument instructions in the CF/DEX code.
break;
case INSTANCE_GET:
case INSTANCE_PUT:
// Will not materialize after class inlining if the object is the "root" instance.
Value object =
instruction.isInstanceGet()
? instruction.asInstanceGet().object()
: instruction.asInstancePut().object();
Value root = object.getAliasedValue();
if (receiverAliasesInInlinee == null) {
receiverAliasesInInlinee = getReceiverAliasesInInlinee(invoke, inlinee);
}
if (receiverAliasesInInlinee.contains(root)) {
result++;
}
break;
case RETURN:
// Wil not materialize after class inlining.
result++;
break;
default:
// Will materialize.
break;
}
}
return result;
}
private Set<Value> getReceiverAliasesInInlinee(InvokeMethod invoke, IRCode inlinee) {
List<Value> arguments = inlinee.collectArguments();
Set<Value> receiverAliasesInInlinee = Sets.newIdentityHashSet();
for (int i = 0; i < invoke.inValues().size(); i++) {
Value inValue = invoke.inValues().get(i);
if (definiteReceiverAliases.contains(inValue)) {
receiverAliasesInInlinee.add(arguments.get(i));
} else {
assert !definiteReceiverAliases.contains(inValue.getAliasedValue());
}
}
return receiverAliasesInInlinee;
}
}