blob: 8068a8a2107210ad17eb9cc97123051499b51f67 [file] [log] [blame]
// Copyright (c) 2017, 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.analysis.type.Nullability.definitelyNotNull;
import static com.android.tools.r8.ir.optimize.SimpleDominatingEffectAnalysis.canInlineWithoutSynthesizingNullCheckForReceiver;
import static com.android.tools.r8.utils.MapUtils.ignoreKey;
import static com.google.common.base.Predicates.not;
import com.android.tools.r8.androidapi.AvailableApiExceptions;
import com.android.tools.r8.graph.AccessFlags;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexClass;
import com.android.tools.r8.graph.DexClassAndMember;
import com.android.tools.r8.graph.DexClassAndMethod;
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexItemFactory;
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.DexProgramClass;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.MethodResolutionResult.SingleResolutionResult;
import com.android.tools.r8.graph.NestMemberClassAttribute;
import com.android.tools.r8.graph.ProgramMethod;
import com.android.tools.r8.ir.analysis.ClassInitializationAnalysis;
import com.android.tools.r8.ir.analysis.proto.ProtoInliningReasonStrategy;
import com.android.tools.r8.ir.analysis.type.Nullability;
import com.android.tools.r8.ir.analysis.type.TypeElement;
import com.android.tools.r8.ir.code.BasicBlock;
import com.android.tools.r8.ir.code.BasicBlockIterator;
import com.android.tools.r8.ir.code.CatchHandlers.CatchHandler;
import com.android.tools.r8.ir.code.ConstClass;
import com.android.tools.r8.ir.code.IRCode;
import com.android.tools.r8.ir.code.InitClass;
import com.android.tools.r8.ir.code.Instruction;
import com.android.tools.r8.ir.code.InstructionIterator;
import com.android.tools.r8.ir.code.InstructionListIterator;
import com.android.tools.r8.ir.code.Invoke;
import com.android.tools.r8.ir.code.InvokeMethod;
import com.android.tools.r8.ir.code.InvokeVirtual;
import com.android.tools.r8.ir.code.Monitor;
import com.android.tools.r8.ir.code.MonitorType;
import com.android.tools.r8.ir.code.MoveException;
import com.android.tools.r8.ir.code.Phi;
import com.android.tools.r8.ir.code.Position;
import com.android.tools.r8.ir.code.Throw;
import com.android.tools.r8.ir.code.Value;
import com.android.tools.r8.ir.conversion.IRConverter;
import com.android.tools.r8.ir.conversion.LensCodeRewriter;
import com.android.tools.r8.ir.conversion.MethodProcessor;
import com.android.tools.r8.ir.conversion.OneTimeMethodProcessor;
import com.android.tools.r8.ir.conversion.PostMethodProcessor;
import com.android.tools.r8.ir.optimize.SimpleDominatingEffectAnalysis.SimpleEffectAnalysisResult;
import com.android.tools.r8.ir.optimize.info.OptimizationFeedback;
import com.android.tools.r8.ir.optimize.inliner.DefaultInliningReasonStrategy;
import com.android.tools.r8.ir.optimize.inliner.InliningIRProvider;
import com.android.tools.r8.ir.optimize.inliner.InliningReasonStrategy;
import com.android.tools.r8.ir.optimize.inliner.NopWhyAreYouNotInliningReporter;
import com.android.tools.r8.ir.optimize.inliner.WhyAreYouNotInliningReporter;
import com.android.tools.r8.ir.optimize.membervaluepropagation.R8MemberValuePropagation;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
import com.android.tools.r8.shaking.KeepMethodInfo;
import com.android.tools.r8.shaking.MainDexInfo;
import com.android.tools.r8.utils.ConsumerUtils;
import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.InternalOptions.InlinerOptions;
import com.android.tools.r8.utils.IteratorUtils;
import com.android.tools.r8.utils.ListUtils;
import com.android.tools.r8.utils.Timing;
import com.android.tools.r8.utils.collections.LongLivedProgramMethodSetBuilder;
import com.android.tools.r8.utils.collections.ProgramMethodMap;
import com.android.tools.r8.utils.collections.ProgramMethodSet;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Sets;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Deque;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.function.Consumer;
import java.util.stream.Collectors;
public class Inliner {
protected final AppView<AppInfoWithLiveness> appView;
private final IRConverter converter;
private final LensCodeRewriter lensCodeRewriter;
final MainDexInfo mainDexInfo;
// The set of callers of single caller methods where the single caller method could not be inlined
// due to not being processed at the time of inlining.
private final LongLivedProgramMethodSetBuilder<ProgramMethodSet> singleInlineCallers;
private final MultiCallerInliner multiCallerInliner;
// The set of methods that have been single caller inlined in the current wave. These need to be
// pruned when the wave ends.
private final Map<DexProgramClass, ProgramMethodMap<ProgramMethod>>
singleCallerInlinedMethodsInWave = new ConcurrentHashMap<>();
private final Set<DexMethod> singleCallerInlinedPrunedMethodsForTesting =
Sets.newIdentityHashSet();
private final AvailableApiExceptions availableApiExceptions;
public Inliner(AppView<AppInfoWithLiveness> appView) {
this(appView, null);
}
public Inliner(AppView<AppInfoWithLiveness> appView, IRConverter converter) {
this(appView, converter, null);
}
public Inliner(
AppView<AppInfoWithLiveness> appView,
IRConverter converter,
LensCodeRewriter lensCodeRewriter) {
this.appView = appView;
this.converter = converter;
this.lensCodeRewriter = lensCodeRewriter;
this.mainDexInfo = appView.appInfo().getMainDexInfo();
this.multiCallerInliner = new MultiCallerInliner(appView);
this.singleInlineCallers =
LongLivedProgramMethodSetBuilder.createConcurrentForIdentitySet(appView.graphLens());
availableApiExceptions =
appView.options().canHaveDalvikCatchHandlerVerificationBug()
? new AvailableApiExceptions(appView.options())
: null;
}
public WhyAreYouNotInliningReporter createWhyAreYouNotInliningReporter(
ProgramMethod singleTarget, ProgramMethod context) {
return WhyAreYouNotInliningReporter.createFor(singleTarget, appView, context);
}
public LensCodeRewriter getLensCodeRewriter() {
return lensCodeRewriter;
}
@SuppressWarnings("ReferenceEquality")
private ConstraintWithTarget instructionAllowedForInlining(
Instruction instruction, InliningConstraints inliningConstraints, ProgramMethod context) {
ConstraintWithTarget result = instruction.inliningConstraint(inliningConstraints, context);
if (result.isNever() && instruction.isDebugInstruction()) {
return ConstraintWithTarget.ALWAYS;
}
return result;
}
@SuppressWarnings("ReferenceEquality")
public ConstraintWithTarget computeInliningConstraint(IRCode code) {
InternalOptions options = appView.options();
if (!options.inlinerOptions().enableInlining) {
return ConstraintWithTarget.NEVER;
}
ProgramMethod method = code.context();
DexEncodedMethod definition = method.getDefinition();
if (definition.isClassInitializer() || method.getOrComputeReachabilitySensitive(appView)) {
return ConstraintWithTarget.NEVER;
}
KeepMethodInfo keepInfo = appView.getKeepInfo(method);
if (!keepInfo.isInliningAllowed(options) && !keepInfo.isClassInliningAllowed(options)) {
return ConstraintWithTarget.NEVER;
}
if (containsPotentialCatchHandlerVerificationError(code)) {
return ConstraintWithTarget.NEVER;
}
ProgramMethod context = code.context();
if (appView.options().canHaveDalvikIntUsedAsNonIntPrimitiveTypeBug()
&& returnsIntAsBoolean(code, context)) {
return ConstraintWithTarget.NEVER;
}
ConstraintWithTarget result = ConstraintWithTarget.ALWAYS;
InliningConstraints inliningConstraints = new InliningConstraints(appView);
for (Instruction instruction : code.instructions()) {
ConstraintWithTarget state =
instructionAllowedForInlining(instruction, inliningConstraints, context);
if (state.isNever()) {
result = state;
break;
}
// TODO(b/128967328): we may need to collect all meaningful constraints.
result = ConstraintWithTarget.meet(result, state, appView);
}
return result;
}
private boolean returnsIntAsBoolean(IRCode code, ProgramMethod method) {
DexType returnType = method.getDefinition().returnType();
for (BasicBlock basicBlock : code.blocks) {
InstructionIterator instructionIterator = basicBlock.iterator();
while (instructionIterator.hasNext()) {
Instruction instruction = instructionIterator.nextUntil(Instruction::isReturn);
if (instruction != null) {
if (returnType.isBooleanType() && !instruction.inValues().get(0).knownToBeBoolean()) {
return true;
}
}
}
}
return false;
}
public void recordCallEdgesForMultiCallerInlining(
ProgramMethod method, IRCode code, MethodProcessor methodProcessor, Timing timing) {
multiCallerInliner.recordCallEdgesForMultiCallerInlining(method, code, methodProcessor, timing);
}
/**
* Encodes the constraints for inlining a method's instructions into a different context.
* <p>
* This only takes the instructions into account and not whether a method should be inlined or
* what reason for inlining it might have. Also, it does not take the visibility of the method
* itself into account.
*/
public enum Constraint {
// The ordinal values are important so please do not reorder.
// Each constraint includes all constraints <= to it.
// For example, SAMENEST with class X means:
// - the target is in the same nest as X, or
// - the target has the same class as X (SAMECLASS <= SAMENEST).
// SUBCLASS with class X means:
// - the target is a subclass of X in different package, or
// - the target is in the same package (PACKAGE <= SUBCLASS), or
// ...
// - the target is the same class as X (SAMECLASS <= SUBCLASS).
NEVER(1), // Never inline this.
SAMECLASS(2), // Inlineable into methods in the same holder.
SAMENEST(4), // Inlineable into methods with same nest.
PACKAGE(8), // Inlineable into methods with holders from the same package.
SUBCLASS(16), // Inlineable into methods with holders from a subclass in a different package.
ALWAYS(32); // No restrictions for inlining this.
final int value;
Constraint(int value) {
this.value = value;
}
static {
assert NEVER.ordinal() < SAMECLASS.ordinal();
assert SAMECLASS.ordinal() < SAMENEST.ordinal();
assert SAMENEST.ordinal() < PACKAGE.ordinal();
assert PACKAGE.ordinal() < SUBCLASS.ordinal();
assert SUBCLASS.ordinal() < ALWAYS.ordinal();
}
public Constraint meet(Constraint otherConstraint) {
if (this.ordinal() < otherConstraint.ordinal()) {
return this;
}
return otherConstraint;
}
public boolean isAlways() {
return this == ALWAYS;
}
public boolean isNever() {
return this == NEVER;
}
boolean isSet(int value) {
return (this.value & value) != 0;
}
}
/**
* Encodes the constraints for inlining, along with the target holder.
* <p>
* Constraint itself cannot determine whether or not the method can be inlined if instructions in
* the method have different constraints with different targets. For example,
* SUBCLASS of x.A v.s. PACKAGE of y.B
* Without any target holder information, min of those two Constraints is PACKAGE, meaning that
* the current method can be inlined to any method whose holder is in package y. This could cause
* an illegal access error due to protect members in x.A. Because of different target holders,
* those constraints should not be combined.
* <p>
* Instead, a right constraint for inlining constraint for the example above is: a method whose
* holder is a subclass of x.A _and_ in the same package of y.B can inline this method.
*/
public static class ConstraintWithTarget {
public final Constraint constraint;
// Note that this is not context---where this constraint is encoded.
// It literally refers to the holder type of the target, which could be:
// invoked method in invocations, field in field instructions, type of check-cast, etc.
final DexType targetHolder;
public static final ConstraintWithTarget NEVER = new ConstraintWithTarget(Constraint.NEVER);
public static final ConstraintWithTarget ALWAYS = new ConstraintWithTarget(Constraint.ALWAYS);
private ConstraintWithTarget(Constraint constraint) {
assert constraint == Constraint.NEVER || constraint == Constraint.ALWAYS;
this.constraint = constraint;
this.targetHolder = null;
}
ConstraintWithTarget(Constraint constraint, DexType targetHolder) {
assert constraint != Constraint.NEVER;
assert constraint != Constraint.ALWAYS;
assert targetHolder != null;
this.constraint = constraint;
this.targetHolder = targetHolder;
}
public boolean isAlways() {
return constraint.isAlways();
}
public boolean isNever() {
return constraint.isNever();
}
@Override
public int hashCode() {
if (targetHolder == null) {
return constraint.ordinal();
}
return constraint.ordinal() * targetHolder.computeHashCode();
}
@Override
@SuppressWarnings("ReferenceEquality")
public boolean equals(Object other) {
if (!(other instanceof ConstraintWithTarget)) {
return false;
}
ConstraintWithTarget o = (ConstraintWithTarget) other;
return this.constraint.ordinal() == o.constraint.ordinal()
&& this.targetHolder == o.targetHolder;
}
@SuppressWarnings("ReferenceEquality")
public static ConstraintWithTarget deriveConstraint(
ProgramMethod context, DexType targetHolder, AccessFlags<?> flags, AppView<?> appView) {
if (flags.isPublic()) {
return ALWAYS;
} else if (flags.isPrivate()) {
if (context.getHolder().isInANest()) {
return NestUtils.sameNest(context.getHolderType(), targetHolder, appView)
? new ConstraintWithTarget(Constraint.SAMENEST, targetHolder)
: NEVER;
}
return targetHolder == context.getHolderType()
? new ConstraintWithTarget(Constraint.SAMECLASS, targetHolder)
: NEVER;
} else if (flags.isProtected()) {
if (targetHolder.isSamePackage(context.getHolderType())) {
// Even though protected, this is visible via the same package from the context.
return new ConstraintWithTarget(Constraint.PACKAGE, targetHolder);
} else if (appView.isSubtype(context.getHolderType(), targetHolder).isTrue()) {
return new ConstraintWithTarget(Constraint.SUBCLASS, targetHolder);
}
return NEVER;
} else {
/* package-private */
return targetHolder.isSamePackage(context.getHolderType())
? new ConstraintWithTarget(Constraint.PACKAGE, targetHolder)
: NEVER;
}
}
public static ConstraintWithTarget classIsVisible(
ProgramMethod context, DexType clazz, AppView<?> appView) {
if (clazz.isArrayType()) {
return classIsVisible(context, clazz.toArrayElementType(appView.dexItemFactory()), appView);
}
if (clazz.isPrimitiveType()) {
return ALWAYS;
}
DexClass definition = appView.definitionFor(clazz);
return definition == null
? NEVER
: deriveConstraint(context, clazz, definition.accessFlags, appView);
}
@SuppressWarnings("ReferenceEquality")
public static ConstraintWithTarget meet(
ConstraintWithTarget one, ConstraintWithTarget other, AppView<?> appView) {
if (one.equals(other)) {
return one;
}
if (other.constraint.ordinal() < one.constraint.ordinal()) {
return meet(other, one, appView);
}
// From now on, one.constraint.ordinal() <= other.constraint.ordinal()
if (one.isNever()) {
return NEVER;
}
if (other == ALWAYS) {
return one;
}
int constraint = one.constraint.value | other.constraint.value;
assert !Constraint.NEVER.isSet(constraint);
assert !Constraint.ALWAYS.isSet(constraint);
// SAMECLASS <= SAMECLASS, SAMENEST, PACKAGE, SUBCLASS
if (Constraint.SAMECLASS.isSet(constraint)) {
assert one.constraint == Constraint.SAMECLASS;
if (other.constraint == Constraint.SAMECLASS) {
assert one.targetHolder != other.targetHolder;
return NEVER;
}
if (other.constraint == Constraint.SAMENEST) {
if (NestUtils.sameNest(one.targetHolder, other.targetHolder, appView)) {
return one;
}
return NEVER;
}
if (other.constraint == Constraint.PACKAGE) {
if (one.targetHolder.isSamePackage(other.targetHolder)) {
return one;
}
return NEVER;
}
assert other.constraint == Constraint.SUBCLASS;
if (appView.isSubtype(one.targetHolder, other.targetHolder).isTrue()) {
return one;
}
return NEVER;
}
// SAMENEST <= SAMENEST, PACKAGE, SUBCLASS
if (Constraint.SAMENEST.isSet(constraint)) {
assert one.constraint == Constraint.SAMENEST;
if (other.constraint == Constraint.SAMENEST) {
if (NestUtils.sameNest(one.targetHolder, other.targetHolder, appView)) {
return one;
}
return NEVER;
}
assert verifyAllNestInSamePackage(one.targetHolder, appView);
if (other.constraint == Constraint.PACKAGE) {
if (one.targetHolder.isSamePackage(other.targetHolder)) {
return one;
}
return NEVER;
}
assert other.constraint == Constraint.SUBCLASS;
if (allNestMembersSubtypeOf(one.targetHolder, other.targetHolder, appView)) {
// Then, SAMENEST is a more restrictive constraint.
return one;
}
return NEVER;
}
// PACKAGE <= PACKAGE, SUBCLASS
if (Constraint.PACKAGE.isSet(constraint)) {
assert one.constraint == Constraint.PACKAGE;
if (other.constraint == Constraint.PACKAGE) {
assert one.targetHolder != other.targetHolder;
if (one.targetHolder.isSamePackage(other.targetHolder)) {
return one;
}
// PACKAGE of x and PACKAGE of y cannot be satisfied together.
return NEVER;
}
assert other.constraint == Constraint.SUBCLASS;
if (other.targetHolder.isSamePackage(one.targetHolder)) {
// Then, PACKAGE is more restrictive constraint.
return one;
}
if (appView.isSubtype(one.targetHolder, other.targetHolder).isTrue()) {
return new ConstraintWithTarget(Constraint.SAMECLASS, one.targetHolder);
}
// TODO(b/128967328): towards finer-grained constraints, we need both.
// The target method is still inlineable to methods with a holder from the same package of
// one's holder and a subtype of other's holder.
return NEVER;
}
// SUBCLASS <= SUBCLASS
assert Constraint.SUBCLASS.isSet(constraint);
assert one.constraint == other.constraint;
assert one.targetHolder != other.targetHolder;
if (appView.isSubtype(one.targetHolder, other.targetHolder).isTrue()) {
return one;
}
if (appView.isSubtype(other.targetHolder, one.targetHolder).isTrue()) {
return other;
}
// SUBCLASS of x and SUBCLASS of y while x and y are not a subtype of each other.
return NEVER;
}
private static boolean allNestMembersSubtypeOf(
DexType nestType, DexType superType, AppView<?> appView) {
DexClass dexClass = appView.definitionFor(nestType);
if (dexClass == null) {
assert false;
return false;
}
if (!dexClass.isInANest()) {
return appView.isSubtype(dexClass.type, superType).isTrue();
}
DexClass nestHost =
dexClass.isNestHost() ? dexClass : appView.definitionFor(dexClass.getNestHost());
if (nestHost == null) {
assert false;
return false;
}
for (NestMemberClassAttribute member : nestHost.getNestMembersClassAttributes()) {
if (!appView.isSubtype(member.getNestMember(), superType).isTrue()) {
return false;
}
}
return true;
}
private static boolean verifyAllNestInSamePackage(DexType type, AppView<?> appView) {
String descr = type.getPackageDescriptor();
DexClass dexClass = appView.definitionFor(type);
assert dexClass != null;
if (!dexClass.isInANest()) {
return true;
}
DexClass nestHost =
dexClass.isNestHost() ? dexClass : appView.definitionFor(dexClass.getNestHost());
assert nestHost != null;
for (NestMemberClassAttribute member : nestHost.getNestMembersClassAttributes()) {
assert member.getNestMember().getPackageDescriptor().equals(descr);
}
return true;
}
}
/**
* Encodes the reason why a method should be inlined.
* <p>
* This is independent of determining whether a method can be inlined, except for the FORCE state,
* that will inline a method irrespective of visibility and instruction checks.
*/
public enum Reason {
ALWAYS, // Inlinee is marked for inlining due to alwaysinline directive.
SINGLE_CALLER, // Inlinee has precisely one caller.
// Inlinee has multiple callers and should not be inlined. Only used during the primary
// optimization pass.
MULTI_CALLER_CANDIDATE,
SIMPLE, // Inlinee has simple code suitable for inlining.
NEVER; // Inlinee must not be inlined.
}
public abstract static class InlineResult {
InlineAction asInlineAction() {
return null;
}
boolean isRetryAction() {
return false;
}
}
public static class InlineAction extends InlineResult {
public final ProgramMethod target;
public final Invoke invoke;
final Reason reason;
private boolean shouldEnsureStaticInitialization;
private DexProgramClass downcastClass;
InlineAction(ProgramMethod target, Invoke invoke, Reason reason) {
this.target = target;
this.invoke = invoke;
this.reason = reason;
}
public static Builder builder() {
return new Builder();
}
@Override
InlineAction asInlineAction() {
return this;
}
DexProgramClass getDowncastClass() {
return downcastClass;
}
void setDowncastClass(DexProgramClass downcastClass) {
this.downcastClass = downcastClass;
}
void setShouldEnsureStaticInitialization() {
shouldEnsureStaticInitialization = true;
}
boolean mustBeInlined() {
return reason == Reason.ALWAYS;
}
IRCode buildInliningIR(
AppView<AppInfoWithLiveness> appView,
InvokeMethod invoke,
ProgramMethod context,
InliningIRProvider inliningIRProvider) {
DexItemFactory dexItemFactory = appView.dexItemFactory();
InternalOptions options = appView.options();
// Build the IR for a yet not processed method, and perform minimal IR processing.
IRCode code = inliningIRProvider.getInliningIR(invoke, target);
// Insert a init class instruction if this is needed to preserve class initialization
// semantics.
if (shouldEnsureStaticInitialization) {
handleSimpleEffectAnalysisResult(
SimpleDominatingEffectAnalysis.triggersClassInitializationBeforeAnyStaticRead(
appView, code, context),
code.entryBlock(),
ConsumerUtils.emptyConsumer(),
failingBlock -> synthesizeInitClass(code, failingBlock));
}
// Insert a null check if this is needed to preserve the implicit null check for the receiver.
// This is only needed if we do not also insert a monitor-enter instruction, since that will
// throw a NPE if the receiver is null.
//
// Note: When generating DEX, we synthesize monitor-enter/exit instructions during IR
// building, and therefore, we do not need to do anything here. Upon writing, we will use the
// flag "declared synchronized" instead of "synchronized".
boolean shouldSynthesizeMonitorEnterExit =
target.getDefinition().isSynchronized() && options.isGeneratingClassFiles();
boolean isSynthesizingNullCheckForReceiverUsingMonitorEnter =
shouldSynthesizeMonitorEnterExit && !target.getDefinition().isStatic();
if (invoke.isInvokeMethodWithReceiver()
&& invoke.asInvokeMethodWithReceiver().getReceiver().isMaybeNull()
&& !isSynthesizingNullCheckForReceiverUsingMonitorEnter) {
handleSimpleEffectAnalysisResult(
canInlineWithoutSynthesizingNullCheckForReceiver(appView, code),
code.entryBlock(),
this::setRemoveInnerFramePositionForReceiverUse,
failingBlock -> synthesizeNullCheckForReceiver(appView, code, invoke, failingBlock));
}
// Insert monitor-enter and monitor-exit instructions if the method is synchronized.
if (shouldSynthesizeMonitorEnterExit) {
TypeElement throwableType =
TypeElement.fromDexType(
dexItemFactory.throwableType, Nullability.definitelyNotNull(), appView);
code.prepareBlocksForCatchHandlers();
// Create a block for holding the monitor-exit instruction.
BasicBlock monitorExitBlock = new BasicBlock();
monitorExitBlock.setNumber(code.getNextBlockNumber());
// For each block in the code that may throw, add a catch-all handler targeting the
// monitor-exit block.
List<BasicBlock> moveExceptionBlocks = new ArrayList<>();
for (BasicBlock block : code.blocks) {
if (!block.canThrow()) {
continue;
}
if (block.hasCatchHandlers()
&& block.getCatchHandlersWithSuccessorIndexes().hasCatchAll(dexItemFactory)) {
continue;
}
BasicBlock moveExceptionBlock =
BasicBlock.createGotoBlock(
code.getNextBlockNumber(), Position.none(), code.metadata(), monitorExitBlock);
InstructionListIterator moveExceptionBlockIterator =
moveExceptionBlock.listIterator(code);
moveExceptionBlockIterator.setInsertionPosition(Position.syntheticNone());
moveExceptionBlockIterator.add(
new MoveException(
code.createValue(throwableType), dexItemFactory.throwableType, options));
block.appendCatchHandler(moveExceptionBlock, dexItemFactory.throwableType);
moveExceptionBlocks.add(moveExceptionBlock);
}
InstructionListIterator monitorExitBlockIterator = null;
if (!moveExceptionBlocks.isEmpty()) {
// Create a phi for the exception values such that we can rethrow the exception if needed.
Value exceptionValue;
if (moveExceptionBlocks.size() == 1) {
exceptionValue =
ListUtils.first(moveExceptionBlocks).getInstructions().getFirst().outValue();
} else {
Phi phi = code.createPhi(monitorExitBlock, throwableType);
List<Value> operands =
ListUtils.map(
moveExceptionBlocks, block -> block.getInstructions().getFirst().outValue());
phi.addOperands(operands);
exceptionValue = phi;
}
monitorExitBlockIterator = monitorExitBlock.listIterator(code);
monitorExitBlockIterator.setInsertionPosition(Position.syntheticNone());
monitorExitBlockIterator.add(new Throw(exceptionValue));
monitorExitBlock.getMutablePredecessors().addAll(moveExceptionBlocks);
// Insert the newly created blocks.
code.blocks.addAll(moveExceptionBlocks);
code.blocks.add(monitorExitBlock);
}
// Create a block for holding the monitor-enter instruction. Note that, since this block
// is created after we attach catch-all handlers to the code, this block will not have any
// catch handlers.
BasicBlock entryBlock = code.entryBlock();
InstructionListIterator entryBlockIterator = entryBlock.listIterator(code);
entryBlockIterator.nextUntil(not(Instruction::isArgument));
entryBlockIterator.previous();
BasicBlock monitorEnterBlock = entryBlockIterator.split(code, 0, null);
assert !monitorEnterBlock.hasCatchHandlers();
InstructionListIterator monitorEnterBlockIterator = monitorEnterBlock.listIterator(code);
// MonitorEnter will only throw an NPE if the lock is null and that can only happen if the
// receiver was null. To preserve NPE's at call-sites for synchronized methods we therefore
// put in the invoke-position.
monitorEnterBlockIterator.setInsertionPosition(invoke.getPosition());
// If this is a static method, then the class object will act as the lock, so we load it
// using a const-class instruction.
Value lockValue;
if (target.getDefinition().isStatic()) {
lockValue = code.createValue(TypeElement.classClassType(appView, definitelyNotNull()));
monitorEnterBlockIterator.add(new ConstClass(lockValue, target.getHolderType()));
} else {
lockValue = entryBlock.getInstructions().getFirst().asArgument().outValue();
}
// Insert the monitor-enter and monitor-exit instructions.
monitorEnterBlockIterator.add(new Monitor(MonitorType.ENTER, lockValue));
if (monitorExitBlockIterator != null) {
monitorExitBlockIterator.previous();
monitorExitBlockIterator.add(new Monitor(MonitorType.EXIT, lockValue));
monitorExitBlock.close(null);
}
for (BasicBlock block : code.blocks) {
if (block.exit().isReturn()) {
// Since return instructions are not allowed after a throwing instruction in a block
// with catch handlers, the call to prepareBlocksForCatchHandlers() has already taken
// care of ensuring that all return blocks have no throwing instructions.
assert !block.canThrow();
InstructionListIterator instructionIterator =
block.listIterator(code, block.getInstructions().size() - 1);
instructionIterator.setInsertionPosition(Position.syntheticNone());
instructionIterator.add(new Monitor(MonitorType.EXIT, lockValue));
}
}
}
if (options.testing.inlineeIrModifier != null) {
options.testing.inlineeIrModifier.accept(code);
}
code.removeRedundantBlocks();
assert code.isConsistentSSA(appView);
return code;
}
private void handleSimpleEffectAnalysisResult(
SimpleEffectAnalysisResult result,
BasicBlock entryBlock,
Consumer<Instruction> satisfyingInstructionConsumer,
Consumer<BasicBlock> failingPathConsumer) {
List<BasicBlock> topmostNotSatisfiedBlocks = result.getTopmostNotSatisfiedBlocks();
if (result.isNotSatisfied()
// We should only handle partial results if the number of failing paths are small (1) and
// if the failing blocks that root the failing paths do not have catch handlers.
|| (result.isPartial() && topmostNotSatisfiedBlocks.size() > 1)
|| (result.isPartial() && topmostNotSatisfiedBlocks.get(0).hasCatchHandlers())) {
failingPathConsumer.accept(entryBlock);
} else {
result.forEachSatisfyingInstruction(satisfyingInstructionConsumer);
topmostNotSatisfiedBlocks.forEach(failingPathConsumer);
}
}
private void synthesizeInitClass(IRCode code, BasicBlock block) {
// Insert a new block between the last argument instruction and the first actual instruction
// of the method, or the first instruction if not entry block.
assert !block.hasCatchHandlers();
BasicBlock initClassBlock =
block
.listIterator(code, block.isEntry() ? code.collectArguments().size() : 0)
.split(code, 0, null);
assert !initClassBlock.hasCatchHandlers();
InstructionListIterator iterator = initClassBlock.listIterator(code);
iterator.setInsertionPosition(invoke.getPosition());
iterator.add(new InitClass(code.createValue(TypeElement.getInt()), target.getHolderType()));
}
private void synthesizeNullCheckForReceiver(
AppView<?> appView, IRCode code, InvokeMethod invoke, BasicBlock block) {
List<Value> arguments = code.collectArguments();
if (!arguments.isEmpty()) {
Value receiver = arguments.get(0);
assert receiver.isThis();
// Insert a new block between the last argument instruction and the first actual
// instruction of the method.
BasicBlock throwBlock =
block.listIterator(code, block.isEntry() ? arguments.size() : 0).split(code, 0, null);
assert !throwBlock.hasCatchHandlers();
InstructionListIterator iterator = throwBlock.listIterator(code);
iterator.setInsertionPosition(invoke.getPosition());
DexMethod getClassMethod = appView.dexItemFactory().objectMembers.getClass;
iterator.add(new InvokeVirtual(getClassMethod, null, ImmutableList.of(receiver)));
} else {
assert false : "Unable to synthesize a null check for the receiver";
}
}
private void setRemoveInnerFramePositionForReceiverUse(Instruction instruction) {
Position position = instruction.getPosition();
if (position == null) {
assert false : "Expected position for inlinee call to receiver";
return;
}
Position outermostCaller = position.getOutermostCaller();
Position removeInnerFrame =
outermostCaller.builderWithCopy().setRemoveInnerFramesIfThrowingNpe(true).build();
instruction.forceOverwritePosition(
position.replacePosition(outermostCaller, removeInnerFrame));
}
public static class Builder {
private DexProgramClass downcastClass;
private InvokeMethod invoke;
private Reason reason;
private boolean shouldEnsureStaticInitialization;
private ProgramMethod target;
Builder setDowncastClass(DexProgramClass downcastClass) {
this.downcastClass = downcastClass;
return this;
}
Builder setInvoke(InvokeMethod invoke) {
this.invoke = invoke;
return this;
}
Builder setReason(Reason reason) {
this.reason = reason;
return this;
}
Builder setShouldEnsureStaticInitialization() {
this.shouldEnsureStaticInitialization = true;
return this;
}
Builder setTarget(ProgramMethod target) {
this.target = target;
return this;
}
InlineAction build() {
InlineAction action = new InlineAction(target, invoke, reason);
if (downcastClass != null) {
action.setDowncastClass(downcastClass);
}
if (shouldEnsureStaticInitialization) {
action.setShouldEnsureStaticInitialization();
}
return action;
}
}
}
public static class RetryAction extends InlineResult {
@Override
boolean isRetryAction() {
return true;
}
}
static int numberOfInstructions(IRCode code) {
int numberOfInstructions = 0;
for (BasicBlock block : code.blocks) {
for (Instruction instruction : block.getInstructions()) {
assert !instruction.isDebugInstruction();
// Do not include argument instructions since they do not materialize in the output.
if (instruction.isArgument()) {
continue;
}
// Do not include assume instructions in the calculation of the inlining budget, since they
// do not materialize in the output.
if (instruction.isAssume()) {
continue;
}
// Do not include goto instructions that target a basic block with exactly one predecessor,
// since these goto instructions will generally not materialize.
if (instruction.isGoto()) {
if (instruction.asGoto().getTarget().getPredecessors().size() == 1) {
continue;
}
}
// Do not include return instructions since they do not materialize once inlined.
if (instruction.isReturn()) {
continue;
}
++numberOfInstructions;
}
}
return numberOfInstructions;
}
public static class InliningInfo {
public final ProgramMethod target;
public final DexProgramClass receiverClass; // null, if unknown
public InliningInfo(ProgramMethod target) {
this(target, null);
}
public InliningInfo(ProgramMethod target, DexProgramClass receiverClass) {
this.target = target;
this.receiverClass = receiverClass;
}
}
public void performForcedInlining(
ProgramMethod method,
IRCode code,
Map<? extends InvokeMethod, InliningInfo> invokesToInline,
OptimizationFeedback feedback,
InliningIRProvider inliningIRProvider,
MethodProcessor methodProcessor,
Timing timing) {
ForcedInliningOracle oracle = new ForcedInliningOracle(appView, invokesToInline);
performInliningImpl(
oracle, method, code, feedback, inliningIRProvider, methodProcessor, timing);
}
public void performInlining(
ProgramMethod method,
IRCode code,
OptimizationFeedback feedback,
MethodProcessor methodProcessor,
Timing timing) {
performInlining(
method,
code,
feedback,
methodProcessor,
timing,
createDefaultInliningReasonStrategy(methodProcessor));
}
public void performInlining(
ProgramMethod method,
IRCode code,
OptimizationFeedback feedback,
MethodProcessor methodProcessor,
Timing timing,
InliningReasonStrategy inliningReasonStrategy) {
InlinerOptions options = appView.options().inlinerOptions();
DefaultInliningOracle oracle =
createDefaultOracle(
method,
methodProcessor,
options.inliningInstructionAllowance - numberOfInstructions(code),
inliningReasonStrategy);
InliningIRProvider inliningIRProvider =
new InliningIRProvider(appView, method, code, lensCodeRewriter, methodProcessor);
assert inliningIRProvider.verifyIRCacheIsEmpty();
performInliningImpl(
oracle, method, code, feedback, inliningIRProvider, methodProcessor, timing);
}
public InliningReasonStrategy createDefaultInliningReasonStrategy(
MethodProcessor methodProcessor) {
DefaultInliningReasonStrategy defaultInliningReasonStrategy =
new DefaultInliningReasonStrategy(appView, methodProcessor.getCallSiteInformation());
return appView.withGeneratedMessageLiteShrinker(
ignore -> new ProtoInliningReasonStrategy(appView, defaultInliningReasonStrategy),
defaultInliningReasonStrategy);
}
public DefaultInliningOracle createDefaultOracle(
ProgramMethod method,
MethodProcessor methodProcessor,
int inliningInstructionAllowance) {
return createDefaultOracle(
method,
methodProcessor,
inliningInstructionAllowance,
createDefaultInliningReasonStrategy(methodProcessor));
}
public DefaultInliningOracle createDefaultOracle(
ProgramMethod method,
MethodProcessor methodProcessor,
int inliningInstructionAllowance,
InliningReasonStrategy inliningReasonStrategy) {
return new DefaultInliningOracle(
appView,
inliningReasonStrategy,
method,
methodProcessor,
inliningInstructionAllowance);
}
private void performInliningImpl(
InliningOracle oracle,
ProgramMethod context,
IRCode code,
OptimizationFeedback feedback,
InliningIRProvider inliningIRProvider,
MethodProcessor methodProcessor,
Timing timing) {
AffectedValues affectedValues = new AffectedValues();
Set<BasicBlock> blocksToRemove = Sets.newIdentityHashSet();
BasicBlockIterator blockIterator = code.listIterator();
ClassInitializationAnalysis classInitializationAnalysis =
new ClassInitializationAnalysis(appView, code);
Deque<BasicBlock> inlineeStack = new ArrayDeque<>();
InlinerOptions options = appView.options().inlinerOptions();
while (blockIterator.hasNext()) {
BasicBlock block = blockIterator.next();
if (!inlineeStack.isEmpty() && inlineeStack.peekFirst() == block) {
inlineeStack.pop();
}
if (blocksToRemove.contains(block)) {
continue;
}
InstructionListIterator iterator = block.listIterator(code);
while (iterator.hasNext()) {
Instruction current = iterator.next();
if (current.isInvokeMethod()) {
InvokeMethod invoke = current.asInvokeMethod();
// TODO(b/142116551): This should be equivalent to invoke.lookupSingleTarget()!
DexMethod invokedMethod = invoke.getInvokedMethod();
SingleResolutionResult<?> resolutionResult =
appView
.appInfo()
.resolveMethodLegacy(invokedMethod, invoke.getInterfaceBit())
.asSingleResolution();
if (resolutionResult == null
|| resolutionResult.isAccessibleFrom(context, appView).isPossiblyFalse()) {
continue;
}
if (tryInlineMethodWithoutSideEffects(
code, iterator, invoke, resolutionResult.getResolutionPair())) {
continue;
}
// TODO(b/156853206): Should not duplicate resolution.
ProgramMethod singleTarget = oracle.lookupSingleTarget(invoke, context);
if (singleTarget == null) {
WhyAreYouNotInliningReporter.handleInvokeWithUnknownTarget(
this, invoke, appView, context);
continue;
}
InliningOracle singleTargetOracle = getSingleTargetOracle(invoke, singleTarget, oracle);
DexEncodedMethod singleTargetMethod = singleTarget.getDefinition();
WhyAreYouNotInliningReporter whyAreYouNotInliningReporter =
singleTargetOracle.isForcedInliningOracle()
? NopWhyAreYouNotInliningReporter.getInstance()
: createWhyAreYouNotInliningReporter(singleTarget, context);
InlineResult inlineResult =
singleTargetOracle.computeInlining(
code,
invoke,
resolutionResult,
singleTarget,
context,
classInitializationAnalysis,
inliningIRProvider,
whyAreYouNotInliningReporter);
if (inlineResult == null) {
assert whyAreYouNotInliningReporter.unsetReasonHasBeenReportedFlag();
continue;
}
if (inlineResult.isRetryAction()) {
enqueueMethodForReprocessing(context);
continue;
}
InlineAction action = inlineResult.asInlineAction();
if (action.reason == Reason.MULTI_CALLER_CANDIDATE) {
assert methodProcessor.isPrimaryMethodProcessor();
continue;
}
if (!singleTargetOracle.stillHasBudget(action, whyAreYouNotInliningReporter)) {
assert whyAreYouNotInliningReporter.unsetReasonHasBeenReportedFlag();
continue;
}
IRCode inlinee = action.buildInliningIR(appView, invoke, context, inliningIRProvider);
if (singleTargetOracle.willExceedBudget(
action, code, inlinee, invoke, block, whyAreYouNotInliningReporter)) {
assert whyAreYouNotInliningReporter.unsetReasonHasBeenReportedFlag();
continue;
}
// Verify this code went through the full pipeline.
assert singleTarget.getDefinition().isProcessed();
boolean inlineeMayHaveInvokeMethod = inlinee.metadata().mayHaveInvokeMethod();
// Inline the inlinee code in place of the invoke instruction
// Back up before the invoke instruction.
iterator.previous();
// Intentionally not using singleTargetOracle here, so that we decrease the inlining
// instruction allowance on the default inlining oracle when force inlining methods.
oracle.markInlined(inlinee);
iterator.inlineInvoke(
appView, code, inlinee, blockIterator, blocksToRemove, action.getDowncastClass());
if (methodProcessor.getCallSiteInformation().hasSingleCallSite(singleTarget, context)) {
feedback.markInlinedIntoSingleCallSite(singleTargetMethod);
if (!(methodProcessor instanceof OneTimeMethodProcessor)) {
assert converter.isInWave();
if (singleCallerInlinedMethodsInWave.isEmpty()) {
converter.addWaveDoneAction(this::onWaveDone);
}
singleCallerInlinedMethodsInWave
.computeIfAbsent(
singleTarget.getHolder(), ignoreKey(ProgramMethodMap::createConcurrent))
.put(singleTarget, context);
}
}
classInitializationAnalysis.notifyCodeHasChanged();
postProcessInlineeBlocks(
code, blockIterator, block, affectedValues, blocksToRemove, timing);
// The synthetic and bridge flags are maintained only if the inlinee has also these flags.
if (context.getAccessFlags().isBridge() && !singleTarget.getAccessFlags().isBridge()) {
context.getAccessFlags().demoteFromBridge();
}
if (context.getAccessFlags().isSynthetic()
&& !singleTarget.getAccessFlags().isSynthetic()) {
context.getAccessFlags().demoteFromSynthetic();
}
context.getDefinition().copyMetadata(appView, singleTargetMethod);
if (inlineeMayHaveInvokeMethod) {
int inliningDepth = inlineeStack.size() + 1;
if (options.shouldApplyInliningToInlinee(appView, singleTarget, inliningDepth)) {
// Record that we will be inside the inlinee until the next block.
BasicBlock inlineeEnd = IteratorUtils.peekNext(blockIterator);
inlineeStack.push(inlineeEnd);
// Move the cursor back to where the first inlinee block was added.
IteratorUtils.previousUntil(blockIterator, previous -> previous == block);
blockIterator.next();
}
}
}
}
}
assert inlineeStack.isEmpty();
code.removeBlocks(blocksToRemove);
classInitializationAnalysis.finish();
code.removeAllDeadAndTrivialPhis(affectedValues);
affectedValues.narrowingWithAssumeRemoval(appView, code);
code.removeRedundantBlocks();
assert code.isConsistentSSA(appView);
}
private InliningOracle getSingleTargetOracle(
InvokeMethod invoke, ProgramMethod singleTarget, InliningOracle oracle) {
return oracle.isForcedInliningOracle() || !singleTarget.getOptimizationInfo().forceInline()
? oracle
: new ForcedInliningOracle(
appView, ImmutableMap.of(invoke, new InliningInfo(singleTarget)));
}
private boolean tryInlineMethodWithoutSideEffects(
IRCode code,
InstructionListIterator iterator,
InvokeMethod invoke,
DexClassAndMethod resolvedMethod) {
if (invoke.isInvokeMethodWithReceiver()) {
if (!iterator.replaceCurrentInstructionByNullCheckIfPossible(appView, code.context())) {
return false;
}
} else if (invoke.isInvokeStatic()) {
if (!iterator.removeOrReplaceCurrentInstructionByInitClassIfPossible(
appView, code, resolvedMethod.getHolderType())) {
return false;
}
} else {
return false;
}
// Succeeded.
return true;
}
private boolean containsPotentialCatchHandlerVerificationError(IRCode code) {
if (availableApiExceptions == null) {
assert !appView.options().canHaveDalvikCatchHandlerVerificationBug();
return false;
}
for (BasicBlock block : code.blocks) {
for (CatchHandler<BasicBlock> catchHandler : block.getCatchHandlers()) {
DexClass clazz = appView.definitionFor(catchHandler.guard);
if ((clazz == null || clazz.isLibraryClass())
&& availableApiExceptions.canCauseVerificationError(catchHandler.guard)) {
return true;
}
}
}
return false;
}
/** Applies member rebinding to the inlinee and inserts assume instructions. */
private void postProcessInlineeBlocks(
IRCode code,
BasicBlockIterator blockIterator,
BasicBlock block,
AffectedValues affectedValues,
Set<BasicBlock> blocksToRemove,
Timing timing) {
BasicBlock state = IteratorUtils.peekNext(blockIterator);
Set<BasicBlock> inlineeBlocks = Sets.newIdentityHashSet();
// Run member value propagation on the inlinee blocks.
rewindBlockIterator(
blockIterator,
block,
inlineeBlock -> {
if (!blocksToRemove.contains(inlineeBlock)) {
inlineeBlocks.add(inlineeBlock);
}
});
applyMemberValuePropagationToInlinee(code, blockIterator, affectedValues, inlineeBlocks);
// Add non-null IRs only to the inlinee blocks.
rewindBlockIterator(blockIterator, block);
insertAssumeInstructions(code, blockIterator, inlineeBlocks, timing);
// Restore the old state of the iterator.
rewindBlockIterator(blockIterator, state);
}
private void insertAssumeInstructions(
IRCode code,
BasicBlockIterator blockIterator,
Set<BasicBlock> inlineeBlocks,
Timing timing) {
boolean keepRedundantBlocks = true; // since we have a live block iterator
new AssumeInserter(appView, keepRedundantBlocks)
.insertAssumeInstructionsInBlocks(code, blockIterator, inlineeBlocks::contains, timing);
assert !blockIterator.hasNext();
}
private void applyMemberValuePropagationToInlinee(
IRCode code,
BasicBlockIterator blockIterator,
AffectedValues affectedValues,
Set<BasicBlock> inlineeBlocks) {
new R8MemberValuePropagation(appView)
.run(code, blockIterator, affectedValues, inlineeBlocks::contains);
assert !blockIterator.hasNext();
}
private void rewindBlockIterator(ListIterator<BasicBlock> blockIterator, BasicBlock callerBlock) {
rewindBlockIterator(blockIterator, callerBlock, ConsumerUtils.emptyConsumer());
}
private void rewindBlockIterator(
ListIterator<BasicBlock> blockIterator,
BasicBlock callerBlock,
Consumer<BasicBlock> consumer) {
// Move the cursor back to where the first inlinee block was added.
while (blockIterator.hasPrevious()) {
BasicBlock previous = blockIterator.previous();
if (previous == callerBlock) {
break;
}
consumer.accept(previous);
}
assert IteratorUtils.peekNext(blockIterator) == callerBlock;
}
public void enqueueMethodForReprocessing(ProgramMethod method) {
singleInlineCallers.add(method, appView.graphLens());
}
public void onMethodPruned(ProgramMethod method) {
onMethodCodePruned(method);
multiCallerInliner.onMethodPruned(method);
}
public void onMethodCodePruned(ProgramMethod method) {
singleInlineCallers.remove(method.getReference(), appView.graphLens());
}
private void onWaveDone() {
singleCallerInlinedMethodsInWave.forEach(
(clazz, singleCallerInlinedMethodsForClass) -> {
// Convert and remove virtual single caller inlined methods to abstract or throw null.
singleCallerInlinedMethodsForClass.removeIf(
(callee, caller) -> {
// TODO(b/203188583): Enable pruning of methods with generic signatures. For this to
// work we need to pass in a seed to GenericSignatureContextBuilder.create in R8.
if (callee.getDefinition().belongsToVirtualPool()
|| callee.getDefinition().getGenericSignature().hasSignature()) {
callee.convertToAbstractOrThrowNullMethod(appView);
converter.onMethodCodePruned(callee);
return true;
}
return false;
});
// Remove direct single caller inlined methods from the application.
if (!singleCallerInlinedMethodsForClass.isEmpty()) {
Set<DexEncodedMethod> methodsToRemove =
singleCallerInlinedMethodsForClass
.streamKeys()
.map(DexClassAndMember::getDefinition)
.collect(Collectors.toSet());
clazz.getMethodCollection().removeMethods(methodsToRemove);
singleCallerInlinedMethodsForClass.forEach(
(callee, caller) -> {
converter.onMethodFullyInlined(callee, caller);
singleCallerInlinedPrunedMethodsForTesting.add(callee.getReference());
});
}
});
singleCallerInlinedMethodsInWave.clear();
}
public void onLastWaveDone(
PostMethodProcessor.Builder postMethodProcessorBuilder,
ExecutorService executorService,
Timing timing)
throws ExecutionException {
postMethodProcessorBuilder
.rewrittenWithLens(appView)
.merge(
singleInlineCallers
.rewrittenWithLens(appView)
.removeIf(
appView,
method -> method.getOptimizationInfo().hasBeenInlinedIntoSingleCallSite()));
singleInlineCallers.clear();
multiCallerInliner.onLastWaveDone(postMethodProcessorBuilder, executorService, timing);
}
public static boolean verifyAllSingleCallerMethodsHaveBeenPruned(
AppView<AppInfoWithLiveness> appView) {
for (DexProgramClass clazz : appView.appInfo().classesWithDeterministicOrder()) {
clazz.forEachProgramMethodMatching(
method -> method.getOptimizationInfo().hasBeenInlinedIntoSingleCallSite(),
method -> {
assert !method.getDefinition().hasCode()
|| !method.canBeConvertedToAbstractMethod(appView);
});
}
return true;
}
public boolean verifyIsPrunedDueToSingleCallerInlining(DexMethod method) {
assert singleCallerInlinedPrunedMethodsForTesting.contains(method);
return true;
}
public static boolean verifyAllMultiCallerInlinedMethodsHaveBeenPruned(AppView<?> appView) {
for (DexProgramClass clazz : appView.appInfo().classesWithDeterministicOrder()) {
for (DexEncodedMethod method : clazz.methods()) {
if (method.hasCode() && method.getOptimizationInfo().isMultiCallerMethod()) {
// TODO(b/142300882): Ensure soundness of multi caller inlining.
// assert false;
}
}
}
return true;
}
}