|  | // Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file | 
|  | // for details. All rights reserved. Use of this source code is governed by a | 
|  | // BSD-style license that can be found in the LICENSE file. | 
|  | package com.android.tools.r8.ir.optimize; | 
|  |  | 
|  | import static com.android.tools.r8.ir.optimize.inliner.InlinerUtils.addMonitorEnterValue; | 
|  | import static com.android.tools.r8.ir.optimize.inliner.InlinerUtils.collectAllMonitorEnterValues; | 
|  |  | 
|  | import com.android.tools.r8.errors.Unreachable; | 
|  | import com.android.tools.r8.graph.AppView; | 
|  | import com.android.tools.r8.graph.Code; | 
|  | 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.ir.analysis.ClassInitializationAnalysis; | 
|  | import com.android.tools.r8.ir.analysis.proto.ProtoInliningReasonStrategy; | 
|  | import com.android.tools.r8.ir.code.BasicBlock; | 
|  | import com.android.tools.r8.ir.code.IRCode; | 
|  | import com.android.tools.r8.ir.code.InstancePut; | 
|  | import com.android.tools.r8.ir.code.Instruction; | 
|  | import com.android.tools.r8.ir.code.InvokeDirect; | 
|  | import com.android.tools.r8.ir.code.InvokeMethod; | 
|  | import com.android.tools.r8.ir.code.InvokeMethodWithReceiver; | 
|  | import com.android.tools.r8.ir.code.InvokeStatic; | 
|  | import com.android.tools.r8.ir.code.Monitor; | 
|  | import com.android.tools.r8.ir.code.Value; | 
|  | import com.android.tools.r8.ir.conversion.MethodProcessor; | 
|  | import com.android.tools.r8.ir.optimize.Inliner.InlineAction; | 
|  | import com.android.tools.r8.ir.optimize.Inliner.InlineeWithReason; | 
|  | import com.android.tools.r8.ir.optimize.Inliner.Reason; | 
|  | import com.android.tools.r8.ir.optimize.info.OptimizationFeedback; | 
|  | import com.android.tools.r8.ir.optimize.inliner.DefaultInliningReasonStrategy; | 
|  | import com.android.tools.r8.ir.optimize.inliner.InliningReasonStrategy; | 
|  | import com.android.tools.r8.ir.optimize.inliner.WhyAreYouNotInliningReporter; | 
|  | import com.android.tools.r8.logging.Log; | 
|  | import com.android.tools.r8.shaking.AppInfoWithLiveness; | 
|  | import com.android.tools.r8.shaking.MainDexDirectReferenceTracer; | 
|  | import com.android.tools.r8.utils.BooleanUtils; | 
|  | import com.android.tools.r8.utils.InternalOptions; | 
|  | import com.android.tools.r8.utils.IteratorUtils; | 
|  | import com.google.common.collect.Sets; | 
|  | import java.util.ArrayList; | 
|  | import java.util.BitSet; | 
|  | import java.util.List; | 
|  | import java.util.ListIterator; | 
|  | import java.util.Set; | 
|  | import java.util.function.Consumer; | 
|  | import java.util.function.Predicate; | 
|  |  | 
|  | public final class DefaultInliningOracle implements InliningOracle, InliningStrategy { | 
|  |  | 
|  | private final AppView<AppInfoWithLiveness> appView; | 
|  | private final Inliner inliner; | 
|  | private final DexEncodedMethod method; | 
|  | private final IRCode code; | 
|  | private final MethodProcessor methodProcessor; | 
|  | private final Predicate<DexEncodedMethod> isProcessedConcurrently; | 
|  | private final InliningReasonStrategy reasonStrategy; | 
|  | private final int inliningInstructionLimit; | 
|  | private int instructionAllowance; | 
|  |  | 
|  | DefaultInliningOracle( | 
|  | AppView<AppInfoWithLiveness> appView, | 
|  | Inliner inliner, | 
|  | DexEncodedMethod method, | 
|  | IRCode code, | 
|  | MethodProcessor methodProcessor, | 
|  | int inliningInstructionLimit, | 
|  | int inliningInstructionAllowance) { | 
|  | this.appView = appView; | 
|  | this.inliner = inliner; | 
|  | this.method = method; | 
|  | this.code = code; | 
|  | this.methodProcessor = methodProcessor; | 
|  | this.isProcessedConcurrently = methodProcessor::isProcessedConcurrently; | 
|  | this.inliningInstructionLimit = inliningInstructionLimit; | 
|  | this.instructionAllowance = inliningInstructionAllowance; | 
|  |  | 
|  | DefaultInliningReasonStrategy defaultInliningReasonStrategy = | 
|  | new DefaultInliningReasonStrategy( | 
|  | appView, methodProcessor.getCallSiteInformation(), inliner); | 
|  | this.reasonStrategy = | 
|  | appView.withGeneratedMessageLiteShrinker( | 
|  | ignore -> new ProtoInliningReasonStrategy(appView, defaultInliningReasonStrategy), | 
|  | defaultInliningReasonStrategy); | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public boolean isForcedInliningOracle() { | 
|  | return false; | 
|  | } | 
|  |  | 
|  | private boolean isSingleTargetInvalid( | 
|  | InvokeMethod invoke, | 
|  | DexEncodedMethod singleTarget, | 
|  | WhyAreYouNotInliningReporter whyAreYouNotInliningReporter) { | 
|  | if (singleTarget == null) { | 
|  | throw new Unreachable( | 
|  | "Unexpected attempt to inline invoke that does not have a single target"); | 
|  | } | 
|  |  | 
|  | if (singleTarget.isClassInitializer()) { | 
|  | throw new Unreachable( | 
|  | "Unexpected attempt to invoke a class initializer (`" | 
|  | + singleTarget.method.toSourceString() | 
|  | + "`)"); | 
|  | } | 
|  |  | 
|  | if (!singleTarget.hasCode()) { | 
|  | whyAreYouNotInliningReporter.reportInlineeDoesNotHaveCode(); | 
|  | return true; | 
|  | } | 
|  |  | 
|  | DexClass clazz = appView.definitionFor(singleTarget.method.holder); | 
|  | if (!clazz.isProgramClass()) { | 
|  | if (clazz.isClasspathClass()) { | 
|  | whyAreYouNotInliningReporter.reportClasspathMethod(); | 
|  | } else { | 
|  | assert clazz.isLibraryClass(); | 
|  | whyAreYouNotInliningReporter.reportLibraryMethod(); | 
|  | } | 
|  | return true; | 
|  | } | 
|  |  | 
|  | // Ignore the implicit receiver argument. | 
|  | int numberOfArguments = | 
|  | invoke.arguments().size() - BooleanUtils.intValue(invoke.isInvokeMethodWithReceiver()); | 
|  | int arity = singleTarget.method.getArity(); | 
|  | if (numberOfArguments != arity) { | 
|  | whyAreYouNotInliningReporter.reportIncorrectArity(numberOfArguments, arity); | 
|  | return true; | 
|  | } | 
|  |  | 
|  | return false; | 
|  | } | 
|  |  | 
|  | private boolean canInlineStaticInvoke( | 
|  | InvokeStatic invoke, | 
|  | DexEncodedMethod method, | 
|  | DexEncodedMethod target, | 
|  | ClassInitializationAnalysis classInitializationAnalysis, | 
|  | WhyAreYouNotInliningReporter whyAreYouNotInliningReporter) { | 
|  | // Only proceed with inlining a static invoke if: | 
|  | // - the holder for the target is a subtype of the holder for the method, | 
|  | // - the target method always triggers class initialization of its holder before any other side | 
|  | //   effect (hence preserving class initialization semantics), | 
|  | // - the current method has already triggered the holder for the target method to be | 
|  | //   initialized, or | 
|  | // - there is no non-trivial class initializer. | 
|  | DexType targetHolder = target.method.holder; | 
|  | if (appView.appInfo().isSubtype(method.method.holder, targetHolder)) { | 
|  | return true; | 
|  | } | 
|  | DexClass clazz = appView.definitionFor(targetHolder); | 
|  | assert clazz != null; | 
|  | if (target.getOptimizationInfo().triggersClassInitBeforeAnySideEffect()) { | 
|  | return true; | 
|  | } | 
|  | if (!method.isStatic()) { | 
|  | boolean targetIsGuaranteedToBeInitialized = | 
|  | appView.withInitializedClassesInInstanceMethods( | 
|  | analysis -> | 
|  | analysis.isClassDefinitelyLoadedInInstanceMethodsOn( | 
|  | target.method.holder, method.method.holder), | 
|  | false); | 
|  | if (targetIsGuaranteedToBeInitialized) { | 
|  | return true; | 
|  | } | 
|  | } | 
|  | if (classInitializationAnalysis.isClassDefinitelyLoadedBeforeInstruction( | 
|  | target.method.holder, invoke)) { | 
|  | return true; | 
|  | } | 
|  | // Check for class initializer side effects when loading this class, as inlining might remove | 
|  | // the load operation. | 
|  | // | 
|  | // See https://docs.oracle.com/javase/specs/jvms/se9/html/jvms-5.html#jvms-5.5. | 
|  | // | 
|  | // For simplicity, we are conservative and consider all interfaces, not only the ones with | 
|  | // default methods. | 
|  | if (!clazz.classInitializationMayHaveSideEffects(appView)) { | 
|  | return true; | 
|  | } | 
|  |  | 
|  | if (appView.rootSet().bypassClinitForInlining.contains(target.method)) { | 
|  | return true; | 
|  | } | 
|  |  | 
|  | whyAreYouNotInliningReporter.reportMustTriggerClassInitialization(); | 
|  | return false; | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public boolean passesInliningConstraints( | 
|  | InvokeMethod invoke, | 
|  | DexEncodedMethod singleTarget, | 
|  | Reason reason, | 
|  | WhyAreYouNotInliningReporter whyAreYouNotInliningReporter) { | 
|  | if (singleTarget.getOptimizationInfo().neverInline()) { | 
|  | whyAreYouNotInliningReporter.reportMarkedAsNeverInline(); | 
|  | return false; | 
|  | } | 
|  |  | 
|  | // We don't inline into constructors when producing class files since this can mess up | 
|  | // the stackmap, see b/136250031 | 
|  | if (method.isInstanceInitializer() | 
|  | && appView.options().isGeneratingClassFiles() | 
|  | && reason != Reason.FORCE) { | 
|  | whyAreYouNotInliningReporter.reportNoInliningIntoConstructorsWhenGeneratingClassFiles(); | 
|  | return false; | 
|  | } | 
|  |  | 
|  | if (method == singleTarget) { | 
|  | // Cannot handle recursive inlining at this point. | 
|  | // Force inlined method should never be recursive. | 
|  | assert !singleTarget.getOptimizationInfo().forceInline(); | 
|  | whyAreYouNotInliningReporter.reportRecursiveMethod(); | 
|  | return false; | 
|  | } | 
|  |  | 
|  | // We should never even try to inline something that is processed concurrently. It can lead | 
|  | // to non-deterministic behaviour as the inlining IR could be built from either original output | 
|  | // or optimized code. Right now this happens for the class class staticizer, as it just | 
|  | // processes all relevant methods in parallel with the full optimization pipeline enabled. | 
|  | // TODO(sgjesse): Add this assert "assert !isProcessedConcurrently.test(candidate);" | 
|  | if (reason != Reason.FORCE && isProcessedConcurrently.test(singleTarget)) { | 
|  | whyAreYouNotInliningReporter.reportProcessedConcurrently(); | 
|  | return false; | 
|  | } | 
|  |  | 
|  | InternalOptions options = appView.options(); | 
|  | if (options.featureSplitConfiguration != null | 
|  | && !options.featureSplitConfiguration.inSameFeatureOrBase( | 
|  | singleTarget.method, method.method)) { | 
|  | whyAreYouNotInliningReporter.reportInliningAcrossFeatureSplit(); | 
|  | return false; | 
|  | } | 
|  |  | 
|  | Set<Reason> validInliningReasons = options.testing.validInliningReasons; | 
|  | if (validInliningReasons != null && !validInliningReasons.contains(reason)) { | 
|  | whyAreYouNotInliningReporter.reportInvalidInliningReason(reason, validInliningReasons); | 
|  | return false; | 
|  | } | 
|  |  | 
|  | // Abort inlining attempt if method -> target access is not right. | 
|  | if (!inliner.hasInliningAccess(method, singleTarget)) { | 
|  | whyAreYouNotInliningReporter.reportInaccessible(); | 
|  | return false; | 
|  | } | 
|  |  | 
|  | if (reason == Reason.DUAL_CALLER) { | 
|  | if (satisfiesRequirementsForSimpleInlining(invoke, singleTarget)) { | 
|  | // When we have a method with two call sites, we simply inline the method as we normally do | 
|  | // when the method is small. We still need to ensure that the other call site is also | 
|  | // inlined, though. Therefore, we record here that we have seen one of the two call sites | 
|  | // as we normally do. | 
|  | inliner.recordDoubleInliningCandidate(method, singleTarget); | 
|  | } else if (inliner.isDoubleInliningEnabled()) { | 
|  | if (!inliner.satisfiesRequirementsForDoubleInlining(method, singleTarget)) { | 
|  | whyAreYouNotInliningReporter.reportInvalidDoubleInliningCandidate(); | 
|  | return false; | 
|  | } | 
|  | } else { | 
|  | // TODO(b/142300882): Should in principle disallow inlining in this case. | 
|  | inliner.recordDoubleInliningCandidate(method, singleTarget); | 
|  | } | 
|  | } else if (reason == Reason.SIMPLE | 
|  | && !satisfiesRequirementsForSimpleInlining(invoke, singleTarget)) { | 
|  | whyAreYouNotInliningReporter.reportInlineeNotSimple(); | 
|  | return false; | 
|  | } | 
|  |  | 
|  | // Don't inline code with references beyond root main dex classes into a root main dex class. | 
|  | // If we do this it can increase the size of the main dex dependent classes. | 
|  | if (reason != Reason.FORCE | 
|  | && inlineeRefersToClassesNotInMainDex(method.method.holder, singleTarget)) { | 
|  | whyAreYouNotInliningReporter.reportInlineeRefersToClassesNotInMainDex(); | 
|  | return false; | 
|  | } | 
|  | assert reason != Reason.FORCE | 
|  | || !inlineeRefersToClassesNotInMainDex(method.method.holder, singleTarget); | 
|  | return true; | 
|  | } | 
|  |  | 
|  | private boolean inlineeRefersToClassesNotInMainDex(DexType holder, DexEncodedMethod target) { | 
|  | if (inliner.mainDexClasses.isEmpty() || !inliner.mainDexClasses.getRoots().contains(holder)) { | 
|  | return false; | 
|  | } | 
|  | return MainDexDirectReferenceTracer.hasReferencesOutsideFromCode( | 
|  | appView.appInfo(), target, inliner.mainDexClasses.getRoots()); | 
|  | } | 
|  |  | 
|  | private boolean satisfiesRequirementsForSimpleInlining( | 
|  | InvokeMethod invoke, DexEncodedMethod target) { | 
|  | // If we are looking for a simple method, only inline if actually simple. | 
|  | Code code = target.getCode(); | 
|  | int instructionLimit = computeInstructionLimit(invoke, target); | 
|  | if (code.estimatedSizeForInliningAtMost(instructionLimit)) { | 
|  | return true; | 
|  | } | 
|  | return false; | 
|  | } | 
|  |  | 
|  | private int computeInstructionLimit(InvokeMethod invoke, DexEncodedMethod candidate) { | 
|  | int instructionLimit = inliningInstructionLimit; | 
|  | BitSet hints = candidate.getOptimizationInfo().getNonNullParamOrThrow(); | 
|  | if (hints != null) { | 
|  | List<Value> arguments = invoke.inValues(); | 
|  | if (invoke.isInvokeMethodWithReceiver()) { | 
|  | arguments = arguments.subList(1, arguments.size()); | 
|  | } | 
|  | for (int index = 0; index < arguments.size(); index++) { | 
|  | Value argument = arguments.get(index); | 
|  | if ((argument.isArgument() | 
|  | || (argument.getTypeLattice().isReference() && argument.isNeverNull())) | 
|  | && hints.get(index)) { | 
|  | // 5-4 instructions per parameter check are expected to be removed. | 
|  | instructionLimit += 4; | 
|  | } | 
|  | } | 
|  | } | 
|  | return instructionLimit; | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public DexEncodedMethod lookupSingleTarget(InvokeMethod invoke, DexType context) { | 
|  | return invoke.lookupSingleTarget(appView, context); | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public InlineAction computeInlining( | 
|  | InvokeMethod invoke, | 
|  | DexEncodedMethod singleTarget, | 
|  | ClassInitializationAnalysis classInitializationAnalysis, | 
|  | WhyAreYouNotInliningReporter whyAreYouNotInliningReporter) { | 
|  | if (isSingleTargetInvalid(invoke, singleTarget, whyAreYouNotInliningReporter)) { | 
|  | return null; | 
|  | } | 
|  |  | 
|  | if (inliner.isBlacklisted(singleTarget, whyAreYouNotInliningReporter)) { | 
|  | return null; | 
|  | } | 
|  |  | 
|  | Reason reason = reasonStrategy.computeInliningReason(invoke, singleTarget); | 
|  | if (reason == Reason.NEVER) { | 
|  | return null; | 
|  | } | 
|  |  | 
|  | if (!singleTarget.isInliningCandidate( | 
|  | method, reason, appView.appInfo(), whyAreYouNotInliningReporter)) { | 
|  | return null; | 
|  | } | 
|  |  | 
|  | if (!passesInliningConstraints(invoke, singleTarget, reason, whyAreYouNotInliningReporter)) { | 
|  | return null; | 
|  | } | 
|  |  | 
|  | return invoke.computeInlining( | 
|  | singleTarget, reason, this, classInitializationAnalysis, whyAreYouNotInliningReporter); | 
|  | } | 
|  |  | 
|  | public InlineAction computeForInvokeWithReceiver( | 
|  | InvokeMethodWithReceiver invoke, | 
|  | DexEncodedMethod singleTarget, | 
|  | Reason reason, | 
|  | WhyAreYouNotInliningReporter whyAreYouNotInliningReporter) { | 
|  | Value receiver = invoke.getReceiver(); | 
|  | if (receiver.getTypeLattice().isDefinitelyNull()) { | 
|  | // A definitely null receiver will throw an error on call site. | 
|  | whyAreYouNotInliningReporter.reportReceiverDefinitelyNull(); | 
|  | return null; | 
|  | } | 
|  |  | 
|  | InlineAction action = new InlineAction(singleTarget, invoke, reason); | 
|  | if (receiver.getTypeLattice().isNullable()) { | 
|  | assert !receiver.getTypeLattice().isDefinitelyNull(); | 
|  | // When inlining an instance method call, we need to preserve the null check for the | 
|  | // receiver. Therefore, if the receiver may be null and the candidate inlinee does not | 
|  | // throw if the receiver is null before any other side effect, then we must synthesize a | 
|  | // null check. | 
|  | if (!singleTarget.getOptimizationInfo().checksNullReceiverBeforeAnySideEffect()) { | 
|  | InternalOptions options = appView.options(); | 
|  | if (!options.enableInliningOfInvokesWithNullableReceivers) { | 
|  | whyAreYouNotInliningReporter.reportReceiverMaybeNull(); | 
|  | return null; | 
|  | } | 
|  | action.setShouldSynthesizeNullCheckForReceiver(); | 
|  | } | 
|  | } | 
|  | return action; | 
|  | } | 
|  |  | 
|  | public InlineAction computeForInvokeStatic( | 
|  | InvokeStatic invoke, | 
|  | DexEncodedMethod singleTarget, | 
|  | Reason reason, | 
|  | ClassInitializationAnalysis classInitializationAnalysis, | 
|  | WhyAreYouNotInliningReporter whyAreYouNotInliningReporter) { | 
|  | // Abort inlining attempt if we can not guarantee class for static target has been initialized. | 
|  | if (!canInlineStaticInvoke( | 
|  | invoke, method, singleTarget, classInitializationAnalysis, whyAreYouNotInliningReporter)) { | 
|  | return null; | 
|  | } | 
|  | return new InlineAction(singleTarget, invoke, reason); | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public void ensureMethodProcessed( | 
|  | DexEncodedMethod target, IRCode inlinee, OptimizationFeedback feedback) { | 
|  | if (!target.isProcessed()) { | 
|  | if (Log.ENABLED) { | 
|  | Log.verbose(getClass(), "Forcing extra inline on " + target.toSourceString()); | 
|  | } | 
|  | inliner.performInlining(target, inlinee, feedback, methodProcessor); | 
|  | } | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public boolean allowInliningOfInvokeInInlinee( | 
|  | InlineAction action, | 
|  | int inliningDepth, | 
|  | WhyAreYouNotInliningReporter whyAreYouNotInliningReporter) { | 
|  | assert inliningDepth > 0; | 
|  |  | 
|  | if (action.reason.mustBeInlined()) { | 
|  | return true; | 
|  | } | 
|  |  | 
|  | int threshold = appView.options().applyInliningToInlineeMaxDepth; | 
|  | if (inliningDepth <= threshold) { | 
|  | return true; | 
|  | } | 
|  |  | 
|  | whyAreYouNotInliningReporter.reportWillExceedMaxInliningDepth(inliningDepth, threshold); | 
|  | return false; | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public boolean canInlineInstanceInitializer( | 
|  | IRCode inlinee, WhyAreYouNotInliningReporter whyAreYouNotInliningReporter) { | 
|  | // In the Java VM Specification section "4.10.2.4. Instance Initialization Methods and | 
|  | // Newly Created Objects" it says: | 
|  | // | 
|  | // Before that method invokes another instance initialization method of myClass or its direct | 
|  | // superclass on this, the only operation the method can perform on this is assigning fields | 
|  | // declared within myClass. | 
|  |  | 
|  | // Allow inlining a constructor into a constructor of the same class, as the constructor code | 
|  | // is expected to adhere to the VM specification. | 
|  | DexType callerMethodHolder = method.method.holder; | 
|  | DexType calleeMethodHolder = inlinee.method.method.holder; | 
|  | // Calling a constructor on the same class from a constructor can always be inlined. | 
|  | if (method.isInstanceInitializer() && callerMethodHolder == calleeMethodHolder) { | 
|  | return true; | 
|  | } | 
|  |  | 
|  | // Only allow inlining a constructor into a non-constructor if: | 
|  | // (1) the first use of the uninitialized object is the receiver of an invoke of <init>(), | 
|  | // (2) the constructor does not initialize any final fields, as such is only allowed from within | 
|  | //     a constructor of the corresponding class, and | 
|  | // (3) the constructors own <init>() call is on the same class. | 
|  | // | 
|  | // Note that, due to (3), we do allow inlining of `A(int x)` into another class, but not the | 
|  | // default constructor `A()`, since the default constructor invokes Object.<init>(). | 
|  | // | 
|  | //   class A { | 
|  | //     A() { ... } | 
|  | //     A(int x) { | 
|  | //       this() | 
|  | //       ... | 
|  | //     } | 
|  | //   } | 
|  | Value thisValue = inlinee.entryBlock().entry().asArgument().outValue(); | 
|  |  | 
|  | List<InvokeDirect> initCallsOnThis = new ArrayList<>(); | 
|  | for (Instruction instruction : inlinee.instructions()) { | 
|  | if (instruction.isInvokeDirect()) { | 
|  | InvokeDirect initCall = instruction.asInvokeDirect(); | 
|  | DexMethod invokedMethod = initCall.getInvokedMethod(); | 
|  | if (appView.dexItemFactory().isConstructor(invokedMethod)) { | 
|  | Value receiver = initCall.getReceiver().getAliasedValue(); | 
|  | if (receiver == thisValue) { | 
|  | // The <init>() call of the constructor must be on the same class. | 
|  | if (calleeMethodHolder != invokedMethod.holder) { | 
|  | whyAreYouNotInliningReporter | 
|  | .reportUnsafeConstructorInliningDueToIndirectConstructorCall(initCall); | 
|  | return false; | 
|  | } | 
|  | initCallsOnThis.add(initCall); | 
|  | } | 
|  | } | 
|  | } else if (instruction.isInstancePut()) { | 
|  | // Final fields may not be initialized outside of a constructor in the enclosing class. | 
|  | InstancePut instancePut = instruction.asInstancePut(); | 
|  | DexField field = instancePut.getField(); | 
|  | DexEncodedField target = appView.appInfo().lookupInstanceTarget(field.holder, field); | 
|  | if (target == null || target.accessFlags.isFinal()) { | 
|  | whyAreYouNotInliningReporter.reportUnsafeConstructorInliningDueToFinalFieldAssignment( | 
|  | instancePut); | 
|  | return false; | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | // Check that there are no uses of the uninitialized object before it gets initialized. | 
|  | int markingColor = inlinee.reserveMarkingColor(); | 
|  | for (InvokeDirect initCallOnThis : initCallsOnThis) { | 
|  | BasicBlock block = initCallOnThis.getBlock(); | 
|  | for (Instruction instruction : block.instructionsBefore(initCallOnThis)) { | 
|  | for (Value inValue : instruction.inValues()) { | 
|  | Value root = inValue.getAliasedValue(); | 
|  | if (root == thisValue) { | 
|  | inlinee.returnMarkingColor(markingColor); | 
|  | whyAreYouNotInliningReporter.reportUnsafeConstructorInliningDueToUninitializedObjectUse( | 
|  | instruction); | 
|  | return false; | 
|  | } | 
|  | } | 
|  | } | 
|  | for (BasicBlock predecessor : block.getPredecessors()) { | 
|  | inlinee.markTransitivePredecessors(predecessor, markingColor); | 
|  | } | 
|  | } | 
|  |  | 
|  | for (BasicBlock block : inlinee.blocks) { | 
|  | if (block.isMarked(markingColor)) { | 
|  | for (Instruction instruction : block.getInstructions()) { | 
|  | for (Value inValue : instruction.inValues()) { | 
|  | Value root = inValue.getAliasedValue(); | 
|  | if (root == thisValue) { | 
|  | inlinee.returnMarkingColor(markingColor); | 
|  | whyAreYouNotInliningReporter | 
|  | .reportUnsafeConstructorInliningDueToUninitializedObjectUse(instruction); | 
|  | return false; | 
|  | } | 
|  | } | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | inlinee.returnMarkingColor(markingColor); | 
|  | return true; | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public boolean stillHasBudget( | 
|  | InlineAction action, WhyAreYouNotInliningReporter whyAreYouNotInliningReporter) { | 
|  | if (action.reason.mustBeInlined()) { | 
|  | return true; | 
|  | } | 
|  | boolean stillHasBudget = instructionAllowance > 0; | 
|  | if (!stillHasBudget) { | 
|  | whyAreYouNotInliningReporter.reportInstructionBudgetIsExceeded(); | 
|  | } | 
|  | return stillHasBudget; | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public boolean willExceedBudget( | 
|  | IRCode code, | 
|  | InvokeMethod invoke, | 
|  | InlineeWithReason inlinee, | 
|  | BasicBlock block, | 
|  | WhyAreYouNotInliningReporter whyAreYouNotInliningReporter) { | 
|  | if (inlinee.reason.mustBeInlined()) { | 
|  | return false; | 
|  | } | 
|  | return willExceedInstructionBudget(inlinee, whyAreYouNotInliningReporter) | 
|  | || willExceedMonitorEnterValuesBudget(code, invoke, inlinee, whyAreYouNotInliningReporter) | 
|  | || willExceedControlFlowResolutionBlocksBudget( | 
|  | inlinee, block, whyAreYouNotInliningReporter); | 
|  | } | 
|  |  | 
|  | private boolean willExceedInstructionBudget( | 
|  | InlineeWithReason inlinee, WhyAreYouNotInliningReporter whyAreYouNotInliningReporter) { | 
|  | int numberOfInstructions = Inliner.numberOfInstructions(inlinee.code); | 
|  | if (instructionAllowance < Inliner.numberOfInstructions(inlinee.code)) { | 
|  | whyAreYouNotInliningReporter.reportWillExceedInstructionBudget( | 
|  | numberOfInstructions, instructionAllowance); | 
|  | return true; | 
|  | } | 
|  | return false; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * If inlining would lead to additional lock values in the caller, then check that the number of | 
|  | * lock values after inlining would not exceed the threshold. | 
|  | * | 
|  | * <p>The motivation for limiting the number of locks in a given method is that the register | 
|  | * allocator will attempt to pin a register for each lock value. Thus, if a method has many locks, | 
|  | * many registers will be pinned, which will lead to high register pressure. | 
|  | */ | 
|  | private boolean willExceedMonitorEnterValuesBudget( | 
|  | IRCode code, | 
|  | InvokeMethod invoke, | 
|  | InlineeWithReason inlinee, | 
|  | WhyAreYouNotInliningReporter whyAreYouNotInliningReporter) { | 
|  | if (!code.metadata().mayHaveMonitorInstruction()) { | 
|  | return false; | 
|  | } | 
|  |  | 
|  | if (!inlinee.code.metadata().mayHaveMonitorInstruction()) { | 
|  | return false; | 
|  | } | 
|  |  | 
|  | Set<DexType> constantMonitorEnterValues = Sets.newIdentityHashSet(); | 
|  | Set<Value> nonConstantMonitorEnterValues = Sets.newIdentityHashSet(); | 
|  | collectAllMonitorEnterValues(code, constantMonitorEnterValues, nonConstantMonitorEnterValues); | 
|  | if (constantMonitorEnterValues.isEmpty() && nonConstantMonitorEnterValues.isEmpty()) { | 
|  | return false; | 
|  | } | 
|  |  | 
|  | for (Monitor monitor : inlinee.code.<Monitor>instructions(Instruction::isMonitorEnter)) { | 
|  | Value monitorEnterValue = monitor.object().getAliasedValue(); | 
|  | if (monitorEnterValue.isArgument()) { | 
|  | monitorEnterValue = | 
|  | invoke | 
|  | .arguments() | 
|  | .get(monitorEnterValue.computeArgumentPosition(inlinee.code)) | 
|  | .getAliasedValue(); | 
|  | } | 
|  | addMonitorEnterValue( | 
|  | monitorEnterValue, constantMonitorEnterValues, nonConstantMonitorEnterValues); | 
|  | } | 
|  |  | 
|  | int numberOfMonitorEnterValuesAfterInlining = | 
|  | constantMonitorEnterValues.size() + nonConstantMonitorEnterValues.size(); | 
|  | int threshold = appView.options().inliningMonitorEnterValuesAllowance; | 
|  | if (numberOfMonitorEnterValuesAfterInlining > threshold) { | 
|  | whyAreYouNotInliningReporter.reportWillExceedMonitorEnterValuesBudget( | 
|  | numberOfMonitorEnterValuesAfterInlining, threshold); | 
|  | return true; | 
|  | } | 
|  |  | 
|  | return false; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Inlining could lead to an explosion of move-exception and resolution moves. As an example, | 
|  | * consider the following piece of code. | 
|  | * | 
|  | * <pre> | 
|  | *   try { | 
|  | *     ... | 
|  | *     foo(); | 
|  | *     ... | 
|  | *   } catch (A e) { ... } | 
|  | *   } catch (B e) { ... } | 
|  | *   } catch (C e) { ... } | 
|  | * </pre> | 
|  | * | 
|  | * <p>The generated code for the above example will have a move-exception instruction for each of | 
|  | * the three catch handlers. Furthermore, the blocks with these move-exception instructions may | 
|  | * require a number of resolution moves to setup the register state for the catch handlers. When | 
|  | * inlining foo(), the generated code will have a move-exception instruction *for each of the | 
|  | * instructions in foo() that can throw*, along with the necessary resolution moves for each | 
|  | * exception-edge. We therefore abort inlining if the number of exception-edges explode. | 
|  | */ | 
|  | private boolean willExceedControlFlowResolutionBlocksBudget( | 
|  | InlineeWithReason inlinee, | 
|  | BasicBlock block, | 
|  | WhyAreYouNotInliningReporter whyAreYouNotInliningReporter) { | 
|  | if (!block.hasCatchHandlers()) { | 
|  | return false; | 
|  | } | 
|  | int numberOfThrowingInstructionsInInlinee = 0; | 
|  | for (BasicBlock inlineeBlock : inlinee.code.blocks) { | 
|  | numberOfThrowingInstructionsInInlinee += inlineeBlock.numberOfThrowingInstructions(); | 
|  | } | 
|  | // Estimate the number of "control flow resolution blocks", where we will insert a | 
|  | // move-exception instruction (if needed), along with all the resolution moves that | 
|  | // will be needed to setup the register state for the catch handler. | 
|  | int estimatedNumberOfControlFlowResolutionBlocks = | 
|  | numberOfThrowingInstructionsInInlinee * block.numberOfCatchHandlers(); | 
|  | // Abort if inlining could lead to an explosion in the number of control flow | 
|  | // resolution blocks that setup the register state before the actual catch handler. | 
|  | int threshold = appView.options().inliningControlFlowResolutionBlocksThreshold; | 
|  | if (estimatedNumberOfControlFlowResolutionBlocks >= threshold) { | 
|  | whyAreYouNotInliningReporter.reportPotentialExplosionInExceptionalControlFlowResolutionBlocks( | 
|  | estimatedNumberOfControlFlowResolutionBlocks, threshold); | 
|  | return true; | 
|  | } | 
|  | return false; | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public void markInlined(InlineeWithReason inlinee) { | 
|  | // TODO(118734615): All inlining use from the budget - should that only be SIMPLE? | 
|  | instructionAllowance -= Inliner.numberOfInstructions(inlinee.code); | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public void updateTypeInformationIfNeeded( | 
|  | IRCode inlinee, ListIterator<BasicBlock> blockIterator, BasicBlock block) { | 
|  | boolean assumersEnabled = | 
|  | appView.options().enableNonNullTracking | 
|  | || appView.options().enableDynamicTypeOptimization | 
|  | || appView.options().testing.forceAssumeNoneInsertion; | 
|  | if (assumersEnabled) { | 
|  | BasicBlock state = IteratorUtils.peekNext(blockIterator); | 
|  |  | 
|  | Set<BasicBlock> inlineeBlocks = Sets.newIdentityHashSet(); | 
|  | inlineeBlocks.addAll(inlinee.blocks); | 
|  |  | 
|  | // Introduce aliases only to the inlinee blocks. | 
|  | if (appView.options().testing.forceAssumeNoneInsertion) { | 
|  | insertAssumeInstructionsToInlinee( | 
|  | new AliasIntroducer(appView), code, block, blockIterator, inlineeBlocks); | 
|  | } | 
|  |  | 
|  | // Add non-null IRs only to the inlinee blocks. | 
|  | if (appView.options().enableNonNullTracking) { | 
|  | Consumer<BasicBlock> splitBlockConsumer = inlineeBlocks::add; | 
|  | Assumer nonNullTracker = new NonNullTracker(appView, splitBlockConsumer); | 
|  | insertAssumeInstructionsToInlinee( | 
|  | nonNullTracker, code, block, blockIterator, inlineeBlocks); | 
|  | } | 
|  |  | 
|  | // Add dynamic type assumptions only to the inlinee blocks. | 
|  | if (appView.options().enableDynamicTypeOptimization) { | 
|  | insertAssumeInstructionsToInlinee( | 
|  | new DynamicTypeOptimization(appView), code, block, blockIterator, inlineeBlocks); | 
|  | } | 
|  |  | 
|  | // Restore the old state of the iterator. | 
|  | while (blockIterator.hasPrevious() && blockIterator.previous() != state) { | 
|  | // Do nothing. | 
|  | } | 
|  | assert IteratorUtils.peekNext(blockIterator) == state; | 
|  | } | 
|  | // TODO(b/72693244): need a test where refined env in inlinee affects the caller. | 
|  | } | 
|  |  | 
|  | private void insertAssumeInstructionsToInlinee( | 
|  | Assumer assumer, | 
|  | IRCode code, | 
|  | BasicBlock block, | 
|  | ListIterator<BasicBlock> blockIterator, | 
|  | Set<BasicBlock> inlineeBlocks) { | 
|  | // Move the cursor back to where the first inlinee block was added. | 
|  | while (blockIterator.hasPrevious() && blockIterator.previous() != block) { | 
|  | // Do nothing. | 
|  | } | 
|  | assert IteratorUtils.peekNext(blockIterator) == block; | 
|  |  | 
|  | assumer.insertAssumeInstructionsInBlocks(code, blockIterator, inlineeBlocks::contains); | 
|  | assert !blockIterator.hasNext(); | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public DexType getReceiverTypeIfKnown(InvokeMethod invoke) { | 
|  | return null; // Maybe improve later. | 
|  | } | 
|  | } |