void SideEffectAnalysis::getEffects(FunctionEffects &ApplyEffects, FullApplySite FAS) { assert(ApplyEffects.ParamEffects.size() == 0 && "Not using a new ApplyEffects?"); ApplyEffects.ParamEffects.resize(FAS.getNumArguments()); // Is this a call to a semantics function? ArraySemanticsCall ASC(FAS.getInstruction()); if (ASC && ASC.hasSelf()) { if (getSemanticEffects(ApplyEffects, ASC)) return; } if (SILFunction *SingleCallee = FAS.getReferencedFunction()) { // Does the function have any @effects? if (getDefinedEffects(ApplyEffects, SingleCallee)) return; } auto Callees = BCA->getCalleeList(FAS); if (!Callees.allCalleesVisible() || // @callee_owned function calls implicitly release the context, which // may call deinits of boxed values. // TODO: be less conservative about what destructors might be called. FAS.getOrigCalleeType()->isCalleeConsumed()) { ApplyEffects.setWorstEffects(); return; } // We can see all the callees. So we just merge the effects from all of // them. for (auto *Callee : Callees) { const FunctionEffects &CalleeFE = getEffects(Callee); ApplyEffects.mergeFrom(CalleeFE); } }
/// Checks if a generic callee and caller have compatible layout constraints. static bool isCallerAndCalleeLayoutConstraintsCompatible(FullApplySite AI) { SILFunction *Callee = AI.getReferencedFunction(); auto CalleeSig = Callee->getLoweredFunctionType()->getGenericSignature(); auto AISubs = AI.getSubstitutionMap(); SmallVector<GenericTypeParamType *, 4> SubstParams; CalleeSig->forEachParam([&](GenericTypeParamType *Param, bool Canonical) { if (Canonical) SubstParams.push_back(Param); }); for (auto Param : SubstParams) { // Map the parameter into context auto ContextTy = Callee->mapTypeIntoContext(Param->getCanonicalType()); auto Archetype = ContextTy->getAs<ArchetypeType>(); if (!Archetype) continue; auto Layout = Archetype->getLayoutConstraint(); if (!Layout) continue; // The generic parameter has a layout constraint. // Check that the substitution has the same constraint. auto AIReplacement = Type(Param).subst(AISubs); auto AIArchetype = AIReplacement->getAs<ArchetypeType>(); if (!AIArchetype) return false; auto AILayout = AIArchetype->getLayoutConstraint(); if (!AILayout) return false; if (AILayout != Layout) return false; } return true; }
/// Checks if a generic callee and caller have compatible layout constraints. static bool isCallerAndCalleeLayoutConstraintsCompatible(FullApplySite AI) { SILFunction *Callee = AI.getReferencedFunction(); auto CalleeSig = Callee->getLoweredFunctionType()->getGenericSignature(); auto SubstParams = CalleeSig->getSubstitutableParams(); auto AISubs = AI.getSubstitutionMap(); for (auto idx : indices(SubstParams)) { auto Param = SubstParams[idx]; // Map the parameter into context auto ContextTy = Callee->mapTypeIntoContext(Param->getCanonicalType()); auto Archetype = ContextTy->getAs<ArchetypeType>(); if (!Archetype) continue; auto Layout = Archetype->getLayoutConstraint(); if (!Layout) continue; // The generic parameter has a layout constraint. // Check that the substitution has the same constraint. auto AIReplacement = Type(Param).subst(AISubs); auto AIArchetype = AIReplacement->getAs<ArchetypeType>(); if (!AIArchetype) return false; auto AILayout = AIArchetype->getLayoutConstraint(); if (!AILayout) return false; if (AILayout != Layout) return false; } return true; }
/// Could this operand to an apply escape that function by being /// stored or returned? static bool applyArgumentEscapes(FullApplySite Apply, Operand *O) { SILFunction *F = Apply.getReferencedFunction(); // If we cannot examine the function body, assume the worst. if (!F || F->empty()) return true; // Check the uses of the operand, but do not recurse down into other // apply instructions. auto calleeArg = F->getArgument(Apply.getCalleeArgIndex(*O)); return partialApplyEscapes(calleeArg, /* examineApply = */ false); }
// Returns true if the callee contains a partial apply instruction, // whose substitutions list would contain opened existentials after // inlining. static bool calleeHasPartialApplyWithOpenedExistentials(FullApplySite AI) { if (!AI.hasSubstitutions()) return false; SILFunction *Callee = AI.getReferencedFunction(); auto Subs = AI.getSubstitutions(); // Bail if there are no open existentials in the list of substitutions. bool HasNoOpenedExistentials = true; for (auto Sub : Subs) { if (Sub.getReplacement()->hasOpenedExistential()) { HasNoOpenedExistentials = false; break; } } if (HasNoOpenedExistentials) return false; auto SubsMap = Callee->getLoweredFunctionType() ->getGenericSignature()->getSubstitutionMap(Subs); for (auto &BB : *Callee) { for (auto &I : BB) { if (auto PAI = dyn_cast<PartialApplyInst>(&I)) { auto PAISubs = PAI->getSubstitutions(); if (PAISubs.empty()) continue; // Check if any of substitutions would contain open existentials // after inlining. auto PAISubMap = PAI->getOrigCalleeType() ->getGenericSignature()->getSubstitutionMap(PAISubs); PAISubMap = PAISubMap.subst(SubsMap); if (PAISubMap.hasOpenedExistential()) return true; } } } return false; }
// Summarize the callee side effects of a call instruction using this // FunctionSideEffects object without analyzing the callee function bodies or // scheduling the callees for bottom-up propagation. // // Return true if this call-site's effects are summarized without visiting the // callee. bool FunctionSideEffects::summarizeCall(FullApplySite fullApply) { assert(ParamEffects.empty() && "Expect uninitialized effects."); ParamEffects.resize(fullApply.getNumArguments()); // Is this a call to a semantics function? if (auto apply = dyn_cast<ApplyInst>(fullApply.getInstruction())) { ArraySemanticsCall ASC(apply); if (ASC && ASC.hasSelf()) { if (setSemanticEffects(ASC)) return true; } } if (SILFunction *SingleCallee = fullApply.getReferencedFunction()) { // Does the function have any @effects? if (setDefinedEffects(SingleCallee)) return true; } return false; }
// Returns true if a given apply site should be skipped during the // early inlining pass. // // NOTE: Add here the checks for any specific @_semantics/@_effects // attributes causing a given callee to be excluded from the inlining // during the early inlining pass. static bool shouldSkipApplyDuringEarlyInlining(FullApplySite AI) { // Add here the checks for any specific @_semantics attributes that need // to be skipped during the early inlining pass. ArraySemanticsCall ASC(AI.getInstruction()); if (ASC && !ASC.canInlineEarly()) return true; SILFunction *Callee = AI.getReferencedFunction(); if (!Callee) return false; if (Callee->hasSemanticsAttr("self_no_escaping_closure") || Callee->hasSemanticsAttr("pair_no_escaping_closure")) return true; // Add here the checks for any specific @_effects attributes that need // to be skipped during the early inlining pass. if (Callee->hasEffectsKind()) return true; return false; }
/// Return true if inlining this call site is profitable. bool SILPerformanceInliner::isProfitableToInline(FullApplySite AI, unsigned loopDepthOfAI, DominanceAnalysis *DA, SILLoopAnalysis *LA, ConstantTracker &callerTracker, unsigned &NumCallerBlocks) { SILFunction *Callee = AI.getReferencedFunction(); if (Callee->getInlineStrategy() == AlwaysInline) return true; ConstantTracker constTracker(Callee, &callerTracker, AI); DominanceInfo *DT = DA->get(Callee); SILLoopInfo *LI = LA->get(Callee); DominanceOrder domOrder(&Callee->front(), DT, Callee->size()); // Calculate the inlining cost of the callee. unsigned CalleeCost = 0; unsigned Benefit = InlineCostThreshold > 0 ? InlineCostThreshold : RemovedCallBenefit; Benefit += loopDepthOfAI * LoopBenefitFactor; int testThreshold = TestThreshold; while (SILBasicBlock *block = domOrder.getNext()) { constTracker.beginBlock(); for (SILInstruction &I : *block) { constTracker.trackInst(&I); if (testThreshold >= 0) { // We are in test-mode: use a simplified cost model. CalleeCost += testCost(&I); } else { // Use the regular cost model. CalleeCost += unsigned(instructionInlineCost(I)); } if (ApplyInst *AI = dyn_cast<ApplyInst>(&I)) { // Check if the callee is passed as an argument. If so, increase the // threshold, because inlining will (probably) eliminate the closure. SILInstruction *def = constTracker.getDefInCaller(AI->getCallee()); if (def && (isa<FunctionRefInst>(def) || isa<PartialApplyInst>(def))) { unsigned loopDepth = LI->getLoopDepth(block); Benefit += ConstCalleeBenefit + loopDepth * LoopBenefitFactor; testThreshold *= 2; } } } // Don't count costs in blocks which are dead after inlining. SILBasicBlock *takenBlock = getTakenBlock(block->getTerminator(), constTracker); if (takenBlock) { Benefit += ConstTerminatorBenefit; domOrder.pushChildrenIf(block, [=] (SILBasicBlock *child) { return child->getSinglePredecessor() != block || child == takenBlock; }); } else { domOrder.pushChildren(block); } } unsigned Threshold = Benefit; // The default. if (testThreshold >= 0) { // We are in testing mode. Threshold = testThreshold; } else if (AI.getFunction()->isThunk()) { // Only inline trivial functions into thunks (which will not increase the // code size). Threshold = TrivialFunctionThreshold; } else { // The default case. // We reduce the benefit if the caller is too large. For this we use a // cubic function on the number of caller blocks. This starts to prevent // inlining at about 800 - 1000 caller blocks. unsigned blockMinus = (NumCallerBlocks * NumCallerBlocks) / BlockLimitDenominator * NumCallerBlocks / BlockLimitDenominator; if (Threshold > blockMinus + TrivialFunctionThreshold) Threshold -= blockMinus; else Threshold = TrivialFunctionThreshold; } if (CalleeCost > Threshold) { return false; } NumCallerBlocks += Callee->size(); DEBUG( dumpCaller(AI.getFunction()); llvm::dbgs() << " decision {" << CalleeCost << " < " << Threshold << ", ld=" << loopDepthOfAI << ", bb=" << NumCallerBlocks << "} " << Callee->getName() << '\n'; );
bool SILPerformanceInliner::isProfitableToInline(FullApplySite AI, Weight CallerWeight, ConstantTracker &callerTracker, int &NumCallerBlocks, bool IsGeneric) { SILFunction *Callee = AI.getReferencedFunction(); SILLoopInfo *LI = LA->get(Callee); ShortestPathAnalysis *SPA = getSPA(Callee, LI); assert(SPA->isValid()); ConstantTracker constTracker(Callee, &callerTracker, AI); DominanceInfo *DT = DA->get(Callee); SILBasicBlock *CalleeEntry = &Callee->front(); DominanceOrder domOrder(CalleeEntry, DT, Callee->size()); // Calculate the inlining cost of the callee. int CalleeCost = 0; int Benefit = 0; // Start with a base benefit. int BaseBenefit = RemovedCallBenefit; const SILOptions &Opts = Callee->getModule().getOptions(); // For some reason -Ounchecked can accept a higher base benefit without // increasing the code size too much. if (Opts.Optimization == SILOptions::SILOptMode::OptimizeUnchecked) BaseBenefit *= 2; CallerWeight.updateBenefit(Benefit, BaseBenefit); // Go through all blocks of the function, accumulate the cost and find // benefits. while (SILBasicBlock *block = domOrder.getNext()) { constTracker.beginBlock(); Weight BlockW = SPA->getWeight(block, CallerWeight); for (SILInstruction &I : *block) { constTracker.trackInst(&I); CalleeCost += (int)instructionInlineCost(I); if (FullApplySite AI = FullApplySite::isa(&I)) { // Check if the callee is passed as an argument. If so, increase the // threshold, because inlining will (probably) eliminate the closure. SILInstruction *def = constTracker.getDefInCaller(AI.getCallee()); if (def && (isa<FunctionRefInst>(def) || isa<PartialApplyInst>(def))) BlockW.updateBenefit(Benefit, RemovedClosureBenefit); } else if (auto *LI = dyn_cast<LoadInst>(&I)) { // Check if it's a load from a stack location in the caller. Such a load // might be optimized away if inlined. if (constTracker.isStackAddrInCaller(LI->getOperand())) BlockW.updateBenefit(Benefit, RemovedLoadBenefit); } else if (auto *SI = dyn_cast<StoreInst>(&I)) { // Check if it's a store to a stack location in the caller. Such a load // might be optimized away if inlined. if (constTracker.isStackAddrInCaller(SI->getDest())) BlockW.updateBenefit(Benefit, RemovedStoreBenefit); } else if (isa<StrongReleaseInst>(&I) || isa<ReleaseValueInst>(&I)) { SILValue Op = stripCasts(I.getOperand(0)); if (SILArgument *Arg = dyn_cast<SILArgument>(Op)) { if (Arg->isFunctionArg() && Arg->getArgumentConvention() == SILArgumentConvention::Direct_Guaranteed) { BlockW.updateBenefit(Benefit, RefCountBenefit); } } } else if (auto *BI = dyn_cast<BuiltinInst>(&I)) { if (BI->getBuiltinInfo().ID == BuiltinValueKind::OnFastPath) BlockW.updateBenefit(Benefit, FastPathBuiltinBenefit); } } // Don't count costs in blocks which are dead after inlining. SILBasicBlock *takenBlock = constTracker.getTakenBlock(block->getTerminator()); if (takenBlock) { BlockW.updateBenefit(Benefit, RemovedTerminatorBenefit); domOrder.pushChildrenIf(block, [=] (SILBasicBlock *child) { return child->getSinglePredecessor() != block || child == takenBlock; }); } else { domOrder.pushChildren(block); } } if (AI.getFunction()->isThunk()) { // Only inline trivial functions into thunks (which will not increase the // code size). if (CalleeCost > TrivialFunctionThreshold) return false; DEBUG( dumpCaller(AI.getFunction()); llvm::dbgs() << " decision {" << CalleeCost << " into thunk} " << Callee->getName() << '\n'; ); return true; }