| // 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 com.android.tools.r8.graph.AccessFlags; |
| import com.android.tools.r8.graph.AppInfoWithSubtyping; |
| import com.android.tools.r8.graph.AppView; |
| import com.android.tools.r8.graph.ClassHierarchy; |
| import com.android.tools.r8.graph.DexClass; |
| import com.android.tools.r8.graph.DexEncodedField; |
| import com.android.tools.r8.graph.DexEncodedMethod; |
| import com.android.tools.r8.graph.DexField; |
| import com.android.tools.r8.graph.DexMethod; |
| import com.android.tools.r8.graph.DexType; |
| import com.android.tools.r8.graph.GraphLense; |
| import com.android.tools.r8.ir.analysis.ClassInitializationAnalysis; |
| import com.android.tools.r8.ir.code.BasicBlock; |
| import com.android.tools.r8.ir.code.CatchHandlers.CatchHandler; |
| import com.android.tools.r8.ir.code.IRCode; |
| import com.android.tools.r8.ir.code.If; |
| 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.Position; |
| import com.android.tools.r8.ir.code.Throw; |
| import com.android.tools.r8.ir.code.Value; |
| import com.android.tools.r8.ir.code.ValueNumberGenerator; |
| import com.android.tools.r8.ir.conversion.CallSiteInformation; |
| import com.android.tools.r8.ir.conversion.IRConverter; |
| import com.android.tools.r8.ir.conversion.LensCodeRewriter; |
| import com.android.tools.r8.ir.conversion.OptimizationFeedback; |
| import com.android.tools.r8.ir.desugar.TwrCloseResourceRewriter; |
| import com.android.tools.r8.origin.Origin; |
| import com.android.tools.r8.shaking.AppInfoWithLiveness; |
| import com.android.tools.r8.shaking.MainDexClasses; |
| import com.android.tools.r8.utils.InternalOptions; |
| import com.android.tools.r8.utils.ThreadUtils; |
| import com.google.common.collect.Sets; |
| import java.util.ArrayList; |
| import java.util.HashMap; |
| import java.util.List; |
| import java.util.ListIterator; |
| import java.util.Map; |
| import java.util.Set; |
| import java.util.concurrent.ExecutionException; |
| import java.util.concurrent.ExecutorService; |
| import java.util.concurrent.Future; |
| import java.util.function.Predicate; |
| |
| public class Inliner { |
| |
| protected final AppView<AppInfoWithLiveness> appView; |
| final MainDexClasses mainDexClasses; |
| |
| // State for inlining methods which are known to be called twice. |
| private boolean applyDoubleInlining = false; |
| private final Set<DexEncodedMethod> doubleInlineCallers = Sets.newIdentityHashSet(); |
| private final Set<DexEncodedMethod> doubleInlineSelectedTargets = Sets.newIdentityHashSet(); |
| private final Map<DexEncodedMethod, DexEncodedMethod> doubleInlineeCandidates = new HashMap<>(); |
| |
| private final Set<DexMethod> blackList = Sets.newIdentityHashSet(); |
| |
| public Inliner(AppView<AppInfoWithLiveness> appView, MainDexClasses mainDexClasses) { |
| this.appView = appView; |
| this.mainDexClasses = mainDexClasses; |
| fillInBlackList(); |
| } |
| |
| private void fillInBlackList() { |
| blackList.add(appView.dexItemFactory().kotlin.intrinsics.throwParameterIsNullException); |
| blackList.add(appView.dexItemFactory().kotlin.intrinsics.throwNpe); |
| } |
| |
| public boolean isBlackListed(DexMethod method) { |
| return blackList.contains(appView.graphLense().getOriginalMethodSignature(method)) |
| || appView.appInfo().neverInline.contains(method) |
| || TwrCloseResourceRewriter.isSynthesizedCloseResourceMethod(method, appView); |
| } |
| |
| private ConstraintWithTarget instructionAllowedForInlining( |
| Instruction instruction, InliningConstraints inliningConstraints, DexType invocationContext) { |
| ConstraintWithTarget result = |
| instruction.inliningConstraint(inliningConstraints, invocationContext); |
| if (result == ConstraintWithTarget.NEVER && instruction.isDebugInstruction()) { |
| return ConstraintWithTarget.ALWAYS; |
| } |
| return result; |
| } |
| |
| public ConstraintWithTarget computeInliningConstraint(IRCode code, DexEncodedMethod method) { |
| if (appView.options().canHaveDalvikCatchHandlerVerificationBug() |
| && useReflectiveOperationExceptionOrUnknownClassInCatch(code)) { |
| return ConstraintWithTarget.NEVER; |
| } |
| |
| ConstraintWithTarget result = ConstraintWithTarget.ALWAYS; |
| InliningConstraints inliningConstraints = |
| new InliningConstraints(appView, GraphLense.getIdentityLense()); |
| InstructionIterator it = code.instructionIterator(); |
| while (it.hasNext()) { |
| Instruction instruction = it.next(); |
| ConstraintWithTarget state = |
| instructionAllowedForInlining(instruction, inliningConstraints, method.method.holder); |
| if (state == ConstraintWithTarget.NEVER) { |
| result = state; |
| break; |
| } |
| // TODO(b/128967328): we may need to collect all meaningful constraints. |
| result = ConstraintWithTarget.meet(result, state, appView); |
| } |
| return result; |
| } |
| |
| boolean hasInliningAccess(DexEncodedMethod method, DexEncodedMethod target) { |
| if (!isVisibleWithFlags(target.method.holder, method.method.holder, target.accessFlags)) { |
| return false; |
| } |
| // The class needs also to be visible for us to have access. |
| DexClass targetClass = appView.definitionFor(target.method.holder); |
| return isVisibleWithFlags(target.method.holder, method.method.holder, targetClass.accessFlags); |
| } |
| |
| private boolean isVisibleWithFlags(DexType target, DexType context, AccessFlags flags) { |
| if (flags.isPublic()) { |
| return true; |
| } |
| if (flags.isPrivate()) { |
| return target == context; |
| } |
| if (flags.isProtected()) { |
| return appView.appInfo().isSubtype(context, target) || target.isSamePackage(context); |
| } |
| // package-private |
| return target.isSamePackage(context); |
| } |
| |
| synchronized boolean isDoubleInliningTarget( |
| CallSiteInformation callSiteInformation, DexEncodedMethod candidate) { |
| return callSiteInformation.hasDoubleCallSite(candidate.method) |
| || doubleInlineSelectedTargets.contains(candidate); |
| } |
| |
| synchronized DexEncodedMethod doubleInlining(DexEncodedMethod method, |
| DexEncodedMethod target) { |
| if (!applyDoubleInlining) { |
| if (doubleInlineeCandidates.containsKey(target)) { |
| // Both calls can be inlined. |
| doubleInlineCallers.add(doubleInlineeCandidates.get(target)); |
| doubleInlineCallers.add(method); |
| doubleInlineSelectedTargets.add(target); |
| } else { |
| // First call can be inlined. |
| doubleInlineeCandidates.put(target, method); |
| } |
| // Just preparing for double inlining. |
| return null; |
| } else { |
| // Don't perform the actual inlining if this was not selected. |
| if (!doubleInlineSelectedTargets.contains(target)) { |
| return null; |
| } |
| } |
| return target; |
| } |
| |
| public void processDoubleInlineCallers( |
| IRConverter converter, ExecutorService executorService, OptimizationFeedback feedback) |
| throws ExecutionException { |
| if (doubleInlineCallers.isEmpty()) { |
| return; |
| } |
| applyDoubleInlining = true; |
| List<Future<?>> futures = new ArrayList<>(); |
| for (DexEncodedMethod method : doubleInlineCallers) { |
| futures.add( |
| executorService.submit( |
| () -> { |
| converter.processMethod( |
| method, |
| feedback, |
| doubleInlineCallers::contains, |
| CallSiteInformation.empty(), |
| Outliner::noProcessing); |
| assert method.isProcessed(); |
| return null; |
| })); |
| } |
| ThreadUtils.awaitFutures(futures); |
| } |
| |
| /** |
| * 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. |
| NEVER(1), // Never inline this. |
| SAMECLASS(2), // Inlineable into methods with same holder. |
| PACKAGE(4), // Inlineable into methods with holders from the same package. |
| SUBCLASS(8), // Inlineable into methods with holders from a subclass in a different package. |
| ALWAYS(16); // No restrictions for inlining this. |
| |
| int value; |
| |
| Constraint(int value) { |
| this.value = value; |
| } |
| |
| static { |
| assert NEVER.ordinal() < SAMECLASS.ordinal(); |
| assert SAMECLASS.ordinal() < PACKAGE.ordinal(); |
| assert PACKAGE.ordinal() < SUBCLASS.ordinal(); |
| assert SUBCLASS.ordinal() < ALWAYS.ordinal(); |
| } |
| |
| 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 && constraint != Constraint.ALWAYS; |
| assert targetHolder != null; |
| this.constraint = constraint; |
| this.targetHolder = targetHolder; |
| } |
| |
| @Override |
| public int hashCode() { |
| if (targetHolder == null) { |
| return constraint.ordinal(); |
| } |
| return constraint.ordinal() * targetHolder.computeHashCode(); |
| } |
| |
| @Override |
| 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; |
| } |
| |
| public static ConstraintWithTarget deriveConstraint( |
| DexType contextHolder, DexType targetHolder, AccessFlags flags, AppView<?> appView) { |
| if (flags.isPublic()) { |
| return ALWAYS; |
| } else if (flags.isPrivate()) { |
| return targetHolder == contextHolder |
| ? new ConstraintWithTarget(Constraint.SAMECLASS, targetHolder) : NEVER; |
| } else if (flags.isProtected()) { |
| if (targetHolder.isSamePackage(contextHolder)) { |
| // Even though protected, this is visible via the same package from the context. |
| return new ConstraintWithTarget(Constraint.PACKAGE, targetHolder); |
| } else if (appView.isSubtype(contextHolder, targetHolder).isTrue()) { |
| return new ConstraintWithTarget(Constraint.SUBCLASS, targetHolder); |
| } |
| return NEVER; |
| } else { |
| /* package-private */ |
| return targetHolder.isSamePackage(contextHolder) |
| ? new ConstraintWithTarget(Constraint.PACKAGE, targetHolder) : NEVER; |
| } |
| } |
| |
| public static ConstraintWithTarget classIsVisible( |
| DexType 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); |
| } |
| |
| 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 == NEVER) { |
| 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, 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.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; |
| } |
| // 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; |
| } |
| // 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; |
| } |
| } |
| |
| /** |
| * 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 { |
| FORCE, // Inlinee is marked for forced inlining (bridge method or renamed constructor). |
| ALWAYS, // Inlinee is marked for inlining due to alwaysinline directive. |
| SINGLE_CALLER, // Inlinee has precisely one caller. |
| DUAL_CALLER, // Inlinee has precisely two callers. |
| SIMPLE; // Inlinee has simple code suitable for inlining. |
| |
| public boolean mustBeInlined() { |
| // TODO(118734615): Include SINGLE_CALLER and DUAL_CALLER here as well? |
| return this == FORCE || this == ALWAYS; |
| } |
| } |
| |
| static public class InlineAction { |
| |
| public final DexEncodedMethod target; |
| public final Invoke invoke; |
| final Reason reason; |
| |
| private boolean shouldReturnEmptyThrowingCode; |
| private boolean shouldSynthesizeNullCheckForReceiver; |
| |
| InlineAction(DexEncodedMethod target, Invoke invoke, Reason reason) { |
| this.target = target; |
| this.invoke = invoke; |
| this.reason = reason; |
| } |
| |
| void setShouldReturnEmptyThrowingCode() { |
| shouldReturnEmptyThrowingCode = true; |
| } |
| |
| void setShouldSynthesizeNullCheckForReceiver() { |
| shouldSynthesizeNullCheckForReceiver = true; |
| } |
| |
| public InlineeWithReason buildInliningIR( |
| DexEncodedMethod context, |
| ValueNumberGenerator generator, |
| AppView<? extends AppInfoWithSubtyping> appView, |
| Position callerPosition) { |
| Origin origin = appView.appInfo().originFor(target.method.holder); |
| |
| IRCode code; |
| if (shouldReturnEmptyThrowingCode) { |
| code = target.buildEmptyThrowingIRCode(appView, origin); |
| } else { |
| // Build the IR for a yet not processed method, and perform minimal IR processing. |
| code = target.buildInliningIR(context, appView, generator, callerPosition, origin); |
| |
| // Insert a null check if this is needed to preserve the implicit null check for the |
| // receiver. |
| if (shouldSynthesizeNullCheckForReceiver) { |
| List<Value> arguments = code.collectArguments(); |
| if (!arguments.isEmpty()) { |
| Value receiver = arguments.get(0); |
| assert receiver.isThis(); |
| |
| BasicBlock entryBlock = code.entryBlock(); |
| |
| // Insert a new block between the last argument instruction and the first actual |
| // instruction of the method. |
| BasicBlock throwBlock = entryBlock.listIterator(arguments.size()).split(code, 0, null); |
| assert !throwBlock.hasCatchHandlers(); |
| |
| // Link the entry block to the successor of the newly inserted block. |
| BasicBlock continuationBlock = throwBlock.unlinkSingleSuccessor(); |
| entryBlock.link(continuationBlock); |
| |
| // Replace the last instruction of the entry block, which is now a goto instruction, |
| // with an `if-eqz` instruction that jumps to the newly inserted block if the receiver |
| // is null. |
| If ifInstruction = new If(If.Type.EQ, receiver); |
| entryBlock.replaceLastInstruction(ifInstruction); |
| assert ifInstruction.getTrueTarget() == throwBlock; |
| assert ifInstruction.fallthroughBlock() == continuationBlock; |
| |
| // Replace the single goto instruction in the newly inserted block by `throw null`. |
| InstructionListIterator iterator = throwBlock.listIterator(); |
| Value nullValue = iterator.insertConstNullInstruction(code, appView.options()); |
| iterator.next(); |
| iterator.replaceCurrentInstruction(new Throw(nullValue)); |
| } else { |
| assert false : "Unable to synthesize a null check for the receiver"; |
| } |
| } |
| if (!target.isProcessed()) { |
| new LensCodeRewriter(appView).rewrite(code, target); |
| } |
| } |
| return new InlineeWithReason(code, reason); |
| } |
| } |
| |
| public static class InlineeWithReason { |
| |
| final Reason reason; |
| final IRCode code; |
| |
| InlineeWithReason(IRCode code, Reason reason) { |
| this.code = code; |
| this.reason = reason; |
| } |
| } |
| |
| 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; |
| } |
| |
| boolean legalConstructorInline( |
| DexEncodedMethod method, InvokeMethod invoke, IRCode code, ClassHierarchy hierarchy) { |
| |
| // In the Java VM Specification section "4.10.2.4. Instance Initialization Methods and |
| // Newly Created Objects" it says: |
| // |
| // Before that method invokes another instance initialization method of myClass or its direct |
| // superclass on this, the only operation the method can perform on this is assigning fields |
| // declared within myClass. |
| |
| // Allow inlining a constructor into a constructor of the same class, as the constructor code |
| // is expected to adhere to the VM specification. |
| DexType callerMethodHolder = method.method.holder; |
| boolean callerMethodIsConstructor = method.isInstanceInitializer(); |
| DexType calleeMethodHolder = invoke.asInvokeMethod().getInvokedMethod().holder; |
| // Calling a constructor on the same class from a constructor can always be inlined. |
| if (callerMethodIsConstructor && callerMethodHolder == calleeMethodHolder) { |
| return true; |
| } |
| |
| // We cannot invoke <init> on other values than |this| on Dalvik 4.4.4. Compute whether |
| // the receiver to the call was the this value at the call-site. |
| boolean receiverOfInnerCallIsThisOfOuter = invoke.asInvokeDirect().getReceiver().isThis(); |
| |
| // Don't allow inlining a constructor into a non-constructor if the first use of the |
| // un-initialized object is not an argument of an invoke of <init>. |
| // Also, we cannot inline a constructor if it initializes final fields, as such is only allowed |
| // from within a constructor of the corresponding class. |
| // Lastly, we can only inline a constructor, if its own <init> call is on the method's class. If |
| // we inline into a constructor, calls to super.<init> are also OK if the receiver of the |
| // super.<init> call is the this argument. |
| InstructionIterator iterator = code.instructionIterator(); |
| Instruction instruction = iterator.next(); |
| // A constructor always has the un-initialized object as the first argument. |
| assert instruction.isArgument(); |
| Value unInitializedObject = instruction.outValue(); |
| boolean seenSuperInvoke = false; |
| while (iterator.hasNext()) { |
| instruction = iterator.next(); |
| if (instruction.inValues().contains(unInitializedObject)) { |
| if (instruction.isInvokeDirect() && !seenSuperInvoke) { |
| DexMethod target = instruction.asInvokeDirect().getInvokedMethod(); |
| seenSuperInvoke = appView.dexItemFactory().isConstructor(target); |
| boolean callOnConstructorThatCallsConstructorSameClass = |
| calleeMethodHolder == target.holder; |
| boolean callOnSupertypeOfThisInConstructor = |
| hierarchy.isDirectSubtype(callerMethodHolder, target.holder) |
| && instruction.asInvokeDirect().getReceiver() == unInitializedObject |
| && receiverOfInnerCallIsThisOfOuter |
| && callerMethodIsConstructor; |
| if (seenSuperInvoke |
| // Calls to init on same class than the called constructor are OK. |
| && !callOnConstructorThatCallsConstructorSameClass |
| // If we are inlining into a constructor, calls to superclass init are only OK on the |
| // |this| value in the outer context. |
| && !callOnSupertypeOfThisInConstructor) { |
| return false; |
| } |
| } |
| if (!seenSuperInvoke) { |
| return false; |
| } |
| } |
| if (instruction.isInstancePut()) { |
| // Fields may not be initialized outside of a constructor. |
| if (!callerMethodIsConstructor) { |
| return false; |
| } |
| DexField field = instruction.asInstancePut().getField(); |
| DexEncodedField target = appView.appInfo().lookupInstanceTarget(field.holder, field); |
| if (target != null && target.accessFlags.isFinal()) { |
| return false; |
| } |
| } |
| } |
| return true; |
| } |
| |
| public static class InliningInfo { |
| public final DexEncodedMethod target; |
| public final DexType receiverType; // null, if unknown |
| |
| public InliningInfo(DexEncodedMethod target, DexType receiverType) { |
| this.target = target; |
| this.receiverType = receiverType; |
| } |
| } |
| |
| public void performForcedInlining( |
| DexEncodedMethod method, |
| IRCode code, |
| Map<InvokeMethod, InliningInfo> invokesToInline) { |
| |
| ForcedInliningOracle oracle = new ForcedInliningOracle(method, invokesToInline); |
| performInliningImpl(oracle, oracle, method, code); |
| } |
| |
| public void performInlining( |
| DexEncodedMethod method, |
| IRCode code, |
| Predicate<DexEncodedMethod> isProcessedConcurrently, |
| CallSiteInformation callSiteInformation) { |
| InternalOptions options = appView.options(); |
| DefaultInliningOracle oracle = |
| createDefaultOracle( |
| method, |
| code, |
| isProcessedConcurrently, |
| callSiteInformation, |
| options.inliningInstructionLimit, |
| options.inliningInstructionAllowance - numberOfInstructions(code)); |
| performInliningImpl(oracle, oracle, method, code); |
| } |
| |
| public DefaultInliningOracle createDefaultOracle( |
| DexEncodedMethod method, |
| IRCode code, |
| Predicate<DexEncodedMethod> isProcessedConcurrently, |
| CallSiteInformation callSiteInformation, |
| int inliningInstructionLimit, |
| int inliningInstructionAllowance) { |
| return new DefaultInliningOracle( |
| appView, |
| this, |
| method, |
| code, |
| callSiteInformation, |
| isProcessedConcurrently, |
| inliningInstructionLimit, |
| inliningInstructionAllowance); |
| } |
| |
| private void performInliningImpl( |
| InliningStrategy strategy, InliningOracle oracle, DexEncodedMethod context, IRCode code) { |
| List<BasicBlock> blocksToRemove = new ArrayList<>(); |
| ListIterator<BasicBlock> blockIterator = code.listIterator(); |
| ClassInitializationAnalysis classInitializationAnalysis = |
| new ClassInitializationAnalysis(appView, code); |
| while (blockIterator.hasNext()) { |
| BasicBlock block = blockIterator.next(); |
| if (blocksToRemove.contains(block)) { |
| continue; |
| } |
| InstructionListIterator iterator = block.listIterator(); |
| while (iterator.hasNext()) { |
| Instruction current = iterator.next(); |
| if (current.isInvokeMethod()) { |
| InvokeMethod invoke = current.asInvokeMethod(); |
| InlineAction result = |
| invoke.computeInlining(oracle, context.method.holder, classInitializationAnalysis); |
| if (result != null) { |
| if (!(strategy.stillHasBudget() || result.reason.mustBeInlined())) { |
| continue; |
| } |
| DexEncodedMethod target = result.target; |
| Position invokePosition = invoke.getPosition(); |
| if (invokePosition.method == null) { |
| assert invokePosition.isNone(); |
| invokePosition = Position.noneWithMethod(context.method, null); |
| } |
| assert invokePosition.callerPosition == null |
| || invokePosition.getOutermostCaller().method |
| == appView.graphLense().getOriginalMethodSignature(context.method); |
| |
| InlineeWithReason inlinee = |
| result.buildInliningIR(context, code.valueNumberGenerator, appView, invokePosition); |
| if (inlinee != null) { |
| if (strategy.willExceedBudget(inlinee, block)) { |
| continue; |
| } |
| |
| // If this code did not go through the full pipeline, apply inlining to make sure |
| // that force inline targets get processed. |
| strategy.ensureMethodProcessed(target, inlinee.code); |
| |
| // Make sure constructor inlining is legal. |
| assert !target.isClassInitializer(); |
| if (!strategy.isValidTarget(invoke, target, inlinee.code, appView.appInfo())) { |
| continue; |
| } |
| DexType downcast = getDowncastTypeIfNeeded(strategy, invoke, target); |
| // Inline the inlinee code in place of the invoke instruction |
| // Back up before the invoke instruction. |
| iterator.previous(); |
| strategy.markInlined(inlinee); |
| iterator.inlineInvoke( |
| appView, code, inlinee.code, blockIterator, blocksToRemove, downcast); |
| |
| classInitializationAnalysis.notifyCodeHasChanged(); |
| strategy.updateTypeInformationIfNeeded(inlinee.code, blockIterator, block); |
| |
| // If we inlined the invoke from a bridge method, it is no longer a bridge method. |
| if (context.accessFlags.isBridge()) { |
| context.accessFlags.unsetSynthetic(); |
| context.accessFlags.unsetBridge(); |
| } |
| |
| context.copyMetadata(target); |
| code.copyMetadataFromInlinee(inlinee.code); |
| } |
| } |
| } |
| } |
| } |
| classInitializationAnalysis.finish(); |
| oracle.finish(); |
| code.removeBlocks(blocksToRemove); |
| code.removeAllTrivialPhis(); |
| assert code.isConsistentSSA(); |
| } |
| |
| private boolean useReflectiveOperationExceptionOrUnknownClassInCatch(IRCode code) { |
| for (BasicBlock block : code.blocks) { |
| for (CatchHandler<BasicBlock> catchHandler : block.getCatchHandlers()) { |
| if (catchHandler.guard == appView.dexItemFactory().reflectiveOperationExceptionType) { |
| return true; |
| } |
| if (appView.definitionFor(catchHandler.guard) == null) { |
| return true; |
| } |
| } |
| } |
| return false; |
| } |
| |
| private static DexType getDowncastTypeIfNeeded( |
| InliningStrategy strategy, InvokeMethod invoke, DexEncodedMethod target) { |
| if (invoke.isInvokeMethodWithReceiver()) { |
| // If the invoke has a receiver but the actual type of the receiver is different |
| // from the computed target holder, inlining requires a downcast of the receiver. |
| DexType assumedReceiverType = strategy.getReceiverTypeIfKnown(invoke); |
| if (assumedReceiverType == null) { |
| // In case we don't know exact type of the receiver we use declared |
| // method holder as a fallback. |
| assumedReceiverType = invoke.getInvokedMethod().holder; |
| } |
| if (assumedReceiverType != target.method.holder) { |
| return target.method.holder; |
| } |
| } |
| return null; |
| } |
| } |