blob: 0a428962ee28a61db30dec95d477a590dfcc5177 [file] [log] [blame]
// Copyright (c) 2018, 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;
import static com.android.tools.r8.ir.optimize.inliner.InlinerUtils.addMonitorEnterValue;
import static com.android.tools.r8.ir.optimize.inliner.InlinerUtils.collectAllMonitorEnterValues;
import static com.android.tools.r8.utils.AndroidApiLevelUtils.isApiSafeForInlining;
import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.features.ClassToFeatureSplitMap;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.Code;
import com.android.tools.r8.graph.DexEncodedField;
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexField;
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.MethodResolutionResult.SingleResolutionResult;
import com.android.tools.r8.graph.ProgramMethod;
import com.android.tools.r8.ir.analysis.ClassInitializationAnalysis;
import com.android.tools.r8.ir.analysis.inlining.SimpleInliningConstraint;
import com.android.tools.r8.ir.code.BasicBlock;
import com.android.tools.r8.ir.code.IRCode;
import com.android.tools.r8.ir.code.InstancePut;
import com.android.tools.r8.ir.code.Instruction;
import com.android.tools.r8.ir.code.InvokeDirect;
import com.android.tools.r8.ir.code.InvokeMethod;
import com.android.tools.r8.ir.code.InvokeMethodWithReceiver;
import com.android.tools.r8.ir.code.InvokeStatic;
import com.android.tools.r8.ir.code.Monitor;
import com.android.tools.r8.ir.code.Value;
import com.android.tools.r8.ir.conversion.MethodProcessor;
import com.android.tools.r8.ir.optimize.Inliner.InlineAction;
import com.android.tools.r8.ir.optimize.Inliner.InlineeWithReason;
import com.android.tools.r8.ir.optimize.Inliner.Reason;
import com.android.tools.r8.ir.optimize.info.MethodOptimizationInfo;
import com.android.tools.r8.ir.optimize.info.OptimizationFeedback;
import com.android.tools.r8.ir.optimize.inliner.InliningReasonStrategy;
import com.android.tools.r8.ir.optimize.inliner.WhyAreYouNotInliningReporter;
import com.android.tools.r8.logging.Log;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
import com.android.tools.r8.synthesis.SyntheticItems;
import com.android.tools.r8.utils.BooleanUtils;
import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.Timing;
import com.google.common.collect.Sets;
import java.util.ArrayList;
import java.util.BitSet;
import java.util.List;
import java.util.Set;
public final class DefaultInliningOracle implements InliningOracle, InliningStrategy {
private final AppView<AppInfoWithLiveness> appView;
private final Inliner inliner;
private final ProgramMethod method;
private final MethodProcessor methodProcessor;
private final InliningReasonStrategy reasonStrategy;
private final int inliningInstructionLimit;
private int instructionAllowance;
DefaultInliningOracle(
AppView<AppInfoWithLiveness> appView,
Inliner inliner,
InliningReasonStrategy inliningReasonStrategy,
ProgramMethod method,
MethodProcessor methodProcessor,
int inliningInstructionLimit,
int inliningInstructionAllowance) {
this.appView = appView;
this.inliner = inliner;
this.reasonStrategy = inliningReasonStrategy;
this.method = method;
this.methodProcessor = methodProcessor;
this.inliningInstructionLimit = inliningInstructionLimit;
this.instructionAllowance = inliningInstructionAllowance;
}
@Override
public boolean isForcedInliningOracle() {
return false;
}
private boolean isSingleTargetInvalid(
InvokeMethod invoke,
ProgramMethod singleTarget,
WhyAreYouNotInliningReporter whyAreYouNotInliningReporter) {
if (singleTarget == null) {
throw new Unreachable(
"Unexpected attempt to inline invoke that does not have a single target");
}
if (singleTarget.getDefinition().isClassInitializer()) {
throw new Unreachable(
"Unexpected attempt to invoke a class initializer (`"
+ singleTarget.toSourceString()
+ "`)");
}
if (!singleTarget.getDefinition().hasCode()) {
whyAreYouNotInliningReporter.reportInlineeDoesNotHaveCode();
return true;
}
// Ignore the implicit receiver argument.
int numberOfArguments =
invoke.arguments().size() - BooleanUtils.intValue(invoke.isInvokeMethodWithReceiver());
int arity = singleTarget.getReference().getArity();
if (numberOfArguments != arity) {
whyAreYouNotInliningReporter.reportIncorrectArity(numberOfArguments, arity);
return true;
}
return false;
}
@Override
public boolean passesInliningConstraints(
InvokeMethod invoke,
SingleResolutionResult resolutionResult,
ProgramMethod singleTarget,
Reason reason,
WhyAreYouNotInliningReporter whyAreYouNotInliningReporter) {
DexEncodedMethod singleTargetMethod = singleTarget.getDefinition();
MethodOptimizationInfo targetOptimizationInfo = singleTargetMethod.getOptimizationInfo();
if (targetOptimizationInfo.neverInline()) {
whyAreYouNotInliningReporter.reportMarkedAsNeverInline();
return false;
}
// Do not inline if the inlinee is greater than the api caller level.
// TODO(b/188498051): We should not force inline lower api method calls.
if (reason != Reason.FORCE
&& isApiSafeForInlining(method, singleTarget, appView.options()).isPossiblyFalse()) {
whyAreYouNotInliningReporter.reportInlineeHigherApiCall();
return false;
}
// We don't inline into constructors when producing class files since this can mess up
// the stackmap, see b/136250031
if (method.getDefinition().isInstanceInitializer()
&& appView.options().isGeneratingClassFiles()
&& reason != Reason.FORCE) {
whyAreYouNotInliningReporter.reportNoInliningIntoConstructorsWhenGeneratingClassFiles();
return false;
}
if (method.getDefinition() == singleTargetMethod) {
// Cannot handle recursive inlining at this point.
// Force inlined method should never be recursive.
assert !targetOptimizationInfo.forceInline();
whyAreYouNotInliningReporter.reportRecursiveMethod();
return false;
}
// We should never even try to inline something that is processed concurrently. It can lead
// to non-deterministic behaviour as the inlining IR could be built from either original output
// or optimized code. Right now this happens for the class class staticizer, as it just
// processes all relevant methods in parallel with the full optimization pipeline enabled.
// TODO(sgjesse): Add this assert "assert !isProcessedConcurrently.test(candidate);"
if (reason != Reason.FORCE && methodProcessor.isProcessedConcurrently(singleTarget)) {
whyAreYouNotInliningReporter.reportProcessedConcurrently();
return false;
}
SyntheticItems syntheticItems = appView.getSyntheticItems();
ClassToFeatureSplitMap classToFeatureSplitMap = appView.appInfo().getClassToFeatureSplitMap();
if (!classToFeatureSplitMap.isInSameFeatureOrBothInBase(singleTarget, method, syntheticItems)) {
// Still allow inlining if we inline from the base into a feature.
if (!classToFeatureSplitMap.isInBase(singleTarget.getHolder(), syntheticItems)) {
whyAreYouNotInliningReporter.reportInliningAcrossFeatureSplit();
return false;
}
}
Set<Reason> validInliningReasons = appView.options().testing.validInliningReasons;
if (validInliningReasons != null && !validInliningReasons.contains(reason)) {
whyAreYouNotInliningReporter.reportInvalidInliningReason(reason, validInliningReasons);
return false;
}
// Abort inlining attempt if method -> target access is not right.
if (resolutionResult.isAccessibleFrom(method, appView.appInfo()).isPossiblyFalse()) {
whyAreYouNotInliningReporter.reportInaccessible();
return false;
}
if (reason == Reason.DUAL_CALLER) {
if (satisfiesRequirementsForSimpleInlining(invoke, singleTarget)) {
// When we have a method with two call sites, we simply inline the method as we normally do
// when the method is small. We still need to ensure that the other call site is also
// inlined, though. Therefore, we record here that we have seen one of the two call sites
// as we normally do.
inliner.recordDoubleInliningCandidate(method, singleTarget);
} else if (inliner.isDoubleInliningEnabled()) {
if (!inliner.satisfiesRequirementsForDoubleInlining(method, singleTarget)) {
whyAreYouNotInliningReporter.reportInvalidDoubleInliningCandidate();
return false;
}
} else {
// TODO(b/142300882): Should in principle disallow inlining in this case.
inliner.recordDoubleInliningCandidate(method, singleTarget);
}
} else if (reason == Reason.SIMPLE
&& !satisfiesRequirementsForSimpleInlining(invoke, singleTarget)) {
whyAreYouNotInliningReporter.reportInlineeNotSimple();
return false;
}
// Don't inline code with references beyond root main dex classes into a root main dex class.
// If we do this it can increase the size of the main dex dependent classes.
if (reason != Reason.FORCE
&& inliner.mainDexInfo.disallowInliningIntoContext(
appView.appInfo(), method, singleTarget, appView.getSyntheticItems())) {
whyAreYouNotInliningReporter.reportInlineeRefersToClassesNotInMainDex();
return false;
}
assert reason != Reason.FORCE
|| !inliner.mainDexInfo.disallowInliningIntoContext(
appView.appInfo(), method, singleTarget, appView.getSyntheticItems());
return true;
}
private boolean satisfiesRequirementsForSimpleInlining(
InvokeMethod invoke, ProgramMethod target) {
// If we are looking for a simple method, only inline if actually simple.
Code code = target.getDefinition().getCode();
int instructionLimit = computeInstructionLimit(invoke, target);
if (code.estimatedSizeForInliningAtMost(instructionLimit)) {
return true;
}
// Even if the inlinee is big it may become simple after inlining. We therefore check if the
// inlinee's simple inlining constraint is satisfied by the invoke.
SimpleInliningConstraint simpleInliningConstraint =
target.getDefinition().getOptimizationInfo().getSimpleInliningConstraint();
return simpleInliningConstraint.isSatisfied(invoke);
}
private int computeInstructionLimit(InvokeMethod invoke, ProgramMethod candidate) {
int instructionLimit = inliningInstructionLimit;
BitSet hints = candidate.getDefinition().getOptimizationInfo().getNonNullParamOrThrow();
if (hints != null) {
List<Value> arguments = invoke.inValues();
if (invoke.isInvokeMethodWithReceiver()) {
arguments = arguments.subList(1, arguments.size());
}
for (int index = 0; index < arguments.size(); index++) {
Value argument = arguments.get(index);
if ((argument.isArgument()
|| (argument.getType().isReferenceType() && argument.isNeverNull()))
&& hints.get(index)) {
// 5-4 instructions per parameter check are expected to be removed.
instructionLimit += 4;
}
}
}
return instructionLimit;
}
@Override
public ProgramMethod lookupSingleTarget(InvokeMethod invoke, ProgramMethod context) {
return invoke.lookupSingleProgramTarget(appView, context);
}
@Override
public InlineAction computeInlining(
InvokeMethod invoke,
SingleResolutionResult resolutionResult,
ProgramMethod singleTarget,
ProgramMethod context,
ClassInitializationAnalysis classInitializationAnalysis,
WhyAreYouNotInliningReporter whyAreYouNotInliningReporter) {
if (isSingleTargetInvalid(invoke, singleTarget, whyAreYouNotInliningReporter)) {
return null;
}
if (inliner.neverInline(invoke, resolutionResult, singleTarget, whyAreYouNotInliningReporter)) {
if (singleTarget.getDefinition().getOptimizationInfo().forceInline()) {
throw new Unreachable(
"Unexpected attempt to force inline method `"
+ singleTarget.toSourceString()
+ "` in `"
+ context.toSourceString()
+ "`.");
}
return null;
}
Reason reason = reasonStrategy.computeInliningReason(invoke, singleTarget, context);
if (reason == Reason.NEVER) {
return null;
}
if (!singleTarget
.getDefinition()
.isInliningCandidate(method, reason, appView.appInfo(), whyAreYouNotInliningReporter)) {
return null;
}
if (!passesInliningConstraints(
invoke, resolutionResult, singleTarget, reason, whyAreYouNotInliningReporter)) {
return null;
}
return invoke.computeInlining(
singleTarget, reason, this, classInitializationAnalysis, whyAreYouNotInliningReporter);
}
public InlineAction computeForInvokeWithReceiver(
InvokeMethodWithReceiver invoke,
ProgramMethod singleTarget,
Reason reason,
WhyAreYouNotInliningReporter whyAreYouNotInliningReporter) {
Value receiver = invoke.getReceiver();
if (receiver.getType().isDefinitelyNull()) {
// A definitely null receiver will throw an error on call site.
whyAreYouNotInliningReporter.reportReceiverDefinitelyNull();
return null;
}
InlineAction action = new InlineAction(singleTarget, invoke, reason);
if (receiver.getType().isNullable()) {
assert !receiver.getType().isDefinitelyNull();
// When inlining an instance method call, we need to preserve the null check for the
// receiver. Therefore, if the receiver may be null and the candidate inlinee does not
// throw if the receiver is null before any other side effect, then we must synthesize a
// null check.
if (!singleTarget
.getDefinition()
.getOptimizationInfo()
.checksNullReceiverBeforeAnySideEffect()) {
InternalOptions options = appView.options();
if (!options.enableInliningOfInvokesWithNullableReceivers) {
whyAreYouNotInliningReporter.reportReceiverMaybeNull();
return null;
}
action.setShouldSynthesizeNullCheckForReceiver();
}
}
return action;
}
public InlineAction computeForInvokeStatic(
InvokeStatic invoke,
ProgramMethod singleTarget,
Reason reason,
ClassInitializationAnalysis classInitializationAnalysis,
WhyAreYouNotInliningReporter whyAreYouNotInliningReporter) {
InlineAction action = new InlineAction(singleTarget, invoke, reason);
if (isTargetClassInitialized(invoke, method, singleTarget, classInitializationAnalysis)) {
return action;
}
if (appView.canUseInitClass()
&& appView.options().enableInliningOfInvokesWithClassInitializationSideEffects) {
action.setShouldSynthesizeInitClass();
return action;
}
whyAreYouNotInliningReporter.reportMustTriggerClassInitialization();
return null;
}
private boolean isTargetClassInitialized(
InvokeStatic invoke,
ProgramMethod context,
ProgramMethod target,
ClassInitializationAnalysis classInitializationAnalysis) {
// Only proceed with inlining a static invoke if:
// - the holder for the target is a subtype of the holder for the method,
// - the target method always triggers class initialization of its holder before any other side
// effect (hence preserving class initialization semantics),
// - the current method has already triggered the holder for the target method to be
// initialized, or
// - there is no non-trivial class initializer.
if (appView.appInfo().isSubtype(context.getHolderType(), target.getHolderType())) {
return true;
}
if (target.getDefinition().getOptimizationInfo().triggersClassInitBeforeAnySideEffect()) {
return true;
}
if (!context.getDefinition().isStatic()) {
boolean targetIsGuaranteedToBeInitialized =
appView.withInitializedClassesInInstanceMethods(
analysis ->
analysis.isClassDefinitelyLoadedInInstanceMethod(target.getHolder(), context),
false);
if (targetIsGuaranteedToBeInitialized) {
return true;
}
}
if (classInitializationAnalysis.isClassDefinitelyLoadedBeforeInstruction(
target.getHolderType(), invoke)) {
return true;
}
// Check for class initializer side effects when loading this class, as inlining might remove
// the load operation.
//
// See https://docs.oracle.com/javase/specs/jvms/se9/html/jvms-5.html#jvms-5.5.
//
// For simplicity, we are conservative and consider all interfaces, not only the ones with
// default methods.
if (!target.getHolder().classInitializationMayHaveSideEffectsInContext(appView, context)) {
return true;
}
if (appView.rootSet().bypassClinitForInlining.contains(target.getReference())) {
return true;
}
return false;
}
@Override
public void ensureMethodProcessed(
ProgramMethod target, IRCode inlinee, OptimizationFeedback feedback) {
if (!target.getDefinition().isProcessed()) {
if (Log.ENABLED) {
Log.verbose(getClass(), "Forcing extra inline on " + target.toSourceString());
}
inliner.performInlining(target, inlinee, feedback, methodProcessor, Timing.empty());
}
}
@Override
public boolean allowInliningOfInvokeInInlinee(
InlineAction action,
int inliningDepth,
WhyAreYouNotInliningReporter whyAreYouNotInliningReporter) {
assert inliningDepth > 0;
if (action.reason.mustBeInlined()) {
return true;
}
int threshold = appView.options().applyInliningToInlineeMaxDepth;
if (inliningDepth <= threshold) {
return true;
}
whyAreYouNotInliningReporter.reportWillExceedMaxInliningDepth(inliningDepth, threshold);
return false;
}
@Override
public boolean canInlineInstanceInitializer(
IRCode code,
IRCode inlinee,
InvokeDirect invoke,
WhyAreYouNotInliningReporter whyAreYouNotInliningReporter) {
// In the Java VM Specification section "4.10.2.4. Instance Initialization Methods and
// Newly Created Objects" it says:
//
// Before that method invokes another instance initialization method of myClass or its direct
// superclass on this, the only operation the method can perform on this is assigning fields
// declared within myClass.
// Allow inlining a constructor into a constructor of the same class, as the constructor code
// is expected to adhere to the VM specification.
DexType callerMethodHolder = method.getHolderType();
DexType calleeMethodHolder = inlinee.method().getHolderType();
// Forwarding constructor calls that target a constructor in the same class can always be
// inlined.
if (method.getDefinition().isInstanceInitializer()
&& callerMethodHolder == calleeMethodHolder
&& invoke.getReceiver() == code.getThis()) {
return true;
}
// Only allow inlining a constructor into a non-constructor if:
// (1) the first use of the uninitialized object is the receiver of an invoke of <init>(),
// (2) the constructor does not initialize any final fields, as such is only allowed from within
// a constructor of the corresponding class, and
// (3) the constructors own <init>() call is on the same class.
//
// Note that, due to (3), we do allow inlining of `A(int x)` into another class, but not the
// default constructor `A()`, since the default constructor invokes Object.<init>().
//
// class A {
// A() { ... }
// A(int x) {
// this()
// ...
// }
// }
Value thisValue = inlinee.entryBlock().entry().asArgument().outValue();
List<InvokeDirect> initCallsOnThis = new ArrayList<>();
for (Instruction instruction : inlinee.instructions()) {
if (instruction.isInvokeDirect()) {
InvokeDirect initCall = instruction.asInvokeDirect();
DexMethod invokedMethod = initCall.getInvokedMethod();
if (appView.dexItemFactory().isConstructor(invokedMethod)) {
Value receiver = initCall.getReceiver().getAliasedValue();
if (receiver == thisValue) {
// The <init>() call of the constructor must be on the same class.
if (calleeMethodHolder != invokedMethod.holder) {
whyAreYouNotInliningReporter
.reportUnsafeConstructorInliningDueToIndirectConstructorCall(initCall);
return false;
}
initCallsOnThis.add(initCall);
}
}
} else if (instruction.isInstancePut()) {
// Final fields may not be initialized outside of a constructor in the enclosing class.
InstancePut instancePut = instruction.asInstancePut();
DexField field = instancePut.getField();
DexEncodedField target = appView.appInfo().lookupInstanceTarget(field);
if (target == null || target.accessFlags.isFinal()) {
whyAreYouNotInliningReporter.reportUnsafeConstructorInliningDueToFinalFieldAssignment(
instancePut);
return false;
}
}
}
// Check that there are no uses of the uninitialized object before it gets initialized.
int markingColor = inlinee.reserveMarkingColor();
for (InvokeDirect initCallOnThis : initCallsOnThis) {
BasicBlock block = initCallOnThis.getBlock();
for (Instruction instruction : block.instructionsBefore(initCallOnThis)) {
for (Value inValue : instruction.inValues()) {
Value root = inValue.getAliasedValue();
if (root == thisValue) {
inlinee.returnMarkingColor(markingColor);
whyAreYouNotInliningReporter.reportUnsafeConstructorInliningDueToUninitializedObjectUse(
instruction);
return false;
}
}
}
for (BasicBlock predecessor : block.getPredecessors()) {
inlinee.markTransitivePredecessors(predecessor, markingColor);
}
}
for (BasicBlock block : inlinee.blocks) {
if (block.isMarked(markingColor)) {
for (Instruction instruction : block.getInstructions()) {
for (Value inValue : instruction.inValues()) {
Value root = inValue.getAliasedValue();
if (root == thisValue) {
inlinee.returnMarkingColor(markingColor);
whyAreYouNotInliningReporter
.reportUnsafeConstructorInliningDueToUninitializedObjectUse(instruction);
return false;
}
}
}
}
}
inlinee.returnMarkingColor(markingColor);
return true;
}
@Override
public boolean stillHasBudget(
InlineAction action, WhyAreYouNotInliningReporter whyAreYouNotInliningReporter) {
if (action.reason.mustBeInlined()) {
return true;
}
boolean stillHasBudget = instructionAllowance > 0;
if (!stillHasBudget) {
whyAreYouNotInliningReporter.reportInstructionBudgetIsExceeded();
}
return stillHasBudget;
}
@Override
public boolean willExceedBudget(
IRCode code,
InvokeMethod invoke,
InlineeWithReason inlinee,
BasicBlock block,
WhyAreYouNotInliningReporter whyAreYouNotInliningReporter) {
if (inlinee.reason.mustBeInlined()) {
return false;
}
return willExceedInstructionBudget(inlinee, whyAreYouNotInliningReporter)
|| willExceedMonitorEnterValuesBudget(code, invoke, inlinee, whyAreYouNotInliningReporter)
|| willExceedControlFlowResolutionBlocksBudget(
inlinee, block, whyAreYouNotInliningReporter);
}
private boolean willExceedInstructionBudget(
InlineeWithReason inlinee, WhyAreYouNotInliningReporter whyAreYouNotInliningReporter) {
int numberOfInstructions = Inliner.numberOfInstructions(inlinee.code);
if (instructionAllowance < Inliner.numberOfInstructions(inlinee.code)) {
whyAreYouNotInliningReporter.reportWillExceedInstructionBudget(
numberOfInstructions, instructionAllowance);
return true;
}
return false;
}
/**
* If inlining would lead to additional lock values in the caller, then check that the number of
* lock values after inlining would not exceed the threshold.
*
* <p>The motivation for limiting the number of locks in a given method is that the register
* allocator will attempt to pin a register for each lock value. Thus, if a method has many locks,
* many registers will be pinned, which will lead to high register pressure.
*/
private boolean willExceedMonitorEnterValuesBudget(
IRCode code,
InvokeMethod invoke,
InlineeWithReason inlinee,
WhyAreYouNotInliningReporter whyAreYouNotInliningReporter) {
if (!code.metadata().mayHaveMonitorInstruction()) {
return false;
}
if (!inlinee.code.metadata().mayHaveMonitorInstruction()) {
return false;
}
Set<DexType> constantMonitorEnterValues = Sets.newIdentityHashSet();
Set<Value> nonConstantMonitorEnterValues = Sets.newIdentityHashSet();
collectAllMonitorEnterValues(code, constantMonitorEnterValues, nonConstantMonitorEnterValues);
if (constantMonitorEnterValues.isEmpty() && nonConstantMonitorEnterValues.isEmpty()) {
return false;
}
for (Monitor monitor : inlinee.code.<Monitor>instructions(Instruction::isMonitorEnter)) {
Value monitorEnterValue = monitor.object().getAliasedValue();
if (monitorEnterValue.isDefinedByInstructionSatisfying(Instruction::isArgument)) {
monitorEnterValue =
invoke
.arguments()
.get(monitorEnterValue.definition.asArgument().getIndex())
.getAliasedValue();
}
addMonitorEnterValue(
monitorEnterValue, constantMonitorEnterValues, nonConstantMonitorEnterValues);
}
int numberOfMonitorEnterValuesAfterInlining =
constantMonitorEnterValues.size() + nonConstantMonitorEnterValues.size();
int threshold = appView.options().inliningMonitorEnterValuesAllowance;
if (numberOfMonitorEnterValuesAfterInlining > threshold) {
whyAreYouNotInliningReporter.reportWillExceedMonitorEnterValuesBudget(
numberOfMonitorEnterValuesAfterInlining, threshold);
return true;
}
return false;
}
/**
* Inlining could lead to an explosion of move-exception and resolution moves. As an example,
* consider the following piece of code.
*
* <pre>
* try {
* ...
* foo();
* ...
* } catch (A e) { ... }
* } catch (B e) { ... }
* } catch (C e) { ... }
* </pre>
*
* <p>The generated code for the above example will have a move-exception instruction for each of
* the three catch handlers. Furthermore, the blocks with these move-exception instructions may
* require a number of resolution moves to setup the register state for the catch handlers. When
* inlining foo(), the generated code will have a move-exception instruction *for each of the
* instructions in foo() that can throw*, along with the necessary resolution moves for each
* exception-edge. We therefore abort inlining if the number of exception-edges explode.
*/
private boolean willExceedControlFlowResolutionBlocksBudget(
InlineeWithReason inlinee,
BasicBlock block,
WhyAreYouNotInliningReporter whyAreYouNotInliningReporter) {
if (!block.hasCatchHandlers()) {
return false;
}
int numberOfThrowingInstructionsInInlinee = 0;
for (BasicBlock inlineeBlock : inlinee.code.blocks) {
numberOfThrowingInstructionsInInlinee += inlineeBlock.numberOfThrowingInstructions();
}
// Estimate the number of "control flow resolution blocks", where we will insert a
// move-exception instruction (if needed), along with all the resolution moves that
// will be needed to setup the register state for the catch handler.
int estimatedNumberOfControlFlowResolutionBlocks =
numberOfThrowingInstructionsInInlinee * block.numberOfCatchHandlers();
// Abort if inlining could lead to an explosion in the number of control flow
// resolution blocks that setup the register state before the actual catch handler.
int threshold = appView.options().inliningControlFlowResolutionBlocksThreshold;
if (estimatedNumberOfControlFlowResolutionBlocks >= threshold) {
whyAreYouNotInliningReporter.reportPotentialExplosionInExceptionalControlFlowResolutionBlocks(
estimatedNumberOfControlFlowResolutionBlocks, threshold);
return true;
}
return false;
}
@Override
public void markInlined(InlineeWithReason inlinee) {
// TODO(118734615): All inlining use from the budget - should that only be SIMPLE?
instructionAllowance -= Inliner.numberOfInstructions(inlinee.code);
}
@Override
public DexType getReceiverTypeIfKnown(InvokeMethod invoke) {
return null; // Maybe improve later.
}
}