| // 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; |
| } |
| } |