//===--- AllocBoxToStack.cpp - Promote alloc_box to alloc_stack -----------===// // // This source file is part of the Swift.org open source project // // Copyright (c) 2014 - 2017 Apple Inc. and the Swift project authors // Licensed under Apache License v2.0 with Runtime Library Exception // // See https://swift.org/LICENSE.txt for license information // See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors // //===----------------------------------------------------------------------===// #define DEBUG_TYPE "allocbox-to-stack" #include "swift/SILOptimizer/PassManager/Passes.h" #include "swift/SIL/Dominance.h" #include "swift/SILOptimizer/Utils/SpecializationMangler.h" #include "swift/SIL/SILArgument.h" #include "swift/SIL/SILBuilder.h" #include "swift/SIL/SILCloner.h" #include "swift/SILOptimizer/PassManager/Transforms.h" #include "swift/SILOptimizer/Utils/Local.h" #include "swift/SILOptimizer/Utils/StackNesting.h" #include "llvm/ADT/DenseMap.h" #include "llvm/ADT/SmallPtrSet.h" #include "llvm/ADT/SmallSet.h" #include "llvm/ADT/SmallVector.h" #include "llvm/ADT/Statistic.h" #include "llvm/Support/Debug.h" using namespace swift; STATISTIC(NumStackPromoted, "Number of alloc_box's promoted to the stack"); //===----------------------------------------------------------------------===// // SIL Utilities for alloc_box Promotion //===----------------------------------------------------------------------===// static SILValue stripOffCopyValue(SILValue V) { while (auto *CVI = dyn_cast(V)) { V = CVI->getOperand(); } return V; } /// \brief Returns True if the operand or one of its users is captured. static bool useCaptured(Operand *UI) { auto *User = UI->getUser(); // These instructions do not cause the address to escape. if (isa(User) || isa(User) || isa(User) || isa(User) || isa(User)) return false; if (auto *Store = dyn_cast(User)) { if (Store->getDest() == UI->get()) return false; } else if (auto *Assign = dyn_cast(User)) { if (Assign->getDest() == UI->get()) return false; } return true; } /// Given an apply or partial_apply, return the direct callee or /// nullptr if this is not a direct call. static FunctionRefInst *getDirectCallee(SILInstruction *Call) { if (auto *Apply = dyn_cast(Call)) return dyn_cast(Apply->getCallee()); else return dyn_cast(cast(Call)->getCallee()); } /// Given an operand of a direct apply or partial_apply of a function, /// return the argument index of the parameter used in the body of the function /// to represent this operand. static size_t getArgIndexForOperand(Operand *O) { assert(isa(O->getUser()) || isa(O->getUser()) && "Expected apply or partial_apply!"); auto OperandIndex = O->getOperandNumber(); assert(OperandIndex != 0 && "Operand cannot be the applied function!"); // The applied function is the first operand. auto ArgIndex = OperandIndex - ApplyInst::getArgumentOperandNumber(); if (auto *Apply = dyn_cast(O->getUser())) { assert(Apply->getSubstCalleeConv().getNumSILArguments() == Apply->getArguments().size() && "Expected all arguments to be supplied!"); (void)Apply; } else { auto *PartialApply = cast(O->getUser()); auto fnConv = PartialApply->getSubstCalleeConv(); auto ArgCount = PartialApply->getArguments().size(); assert(ArgCount <= fnConv.getNumParameters()); ArgIndex += (fnConv.getNumSILArguments() - ArgCount); } return ArgIndex; } /// Given an operand of a direct apply or partial_apply of a function, /// return the parameter used in the body of the function to represent /// this operand. static SILArgument *getParameterForOperand(SILFunction *F, Operand *O) { assert(F && !F->empty() && "Expected a function with a body!"); auto &Entry = F->front(); size_t ArgIndex = getArgIndexForOperand(O); assert(ArgIndex >= F->getConventions().getSILArgIndexOfFirstParam()); return Entry.getArgument(ArgIndex); } /// Return a pointer to the SILFunction called by Call if we can /// determine which function that is, and we have a body for that /// function. Otherwise return nullptr. static SILFunction *getFunctionBody(SILInstruction *Call) { if (auto *FRI = getDirectCallee(Call)) if (auto *F = FRI->getReferencedFunction()) if (!F->empty()) return F; return nullptr; } //===----------------------------------------------------------------------===// // Liveness for alloc_box Promotion //===----------------------------------------------------------------------===// // Is any successor of BB in the LiveIn set? static bool successorHasLiveIn(SILBasicBlock *BB, llvm::SmallPtrSetImpl &LiveIn) { for (auto &Succ : BB->getSuccessors()) if (LiveIn.count(Succ)) return true; return false; } // Propagate liveness backwards from an initial set of blocks in our // LiveIn set. static void propagateLiveness(llvm::SmallPtrSetImpl &LiveIn, SILBasicBlock *DefBB) { // First populate a worklist of predecessors. llvm::SmallVector Worklist; for (auto *BB : LiveIn) for (auto Pred : BB->getPredecessorBlocks()) Worklist.push_back(Pred); // Now propagate liveness backwards until we hit the alloc_box. while (!Worklist.empty()) { auto *BB = Worklist.pop_back_val(); // If it's already in the set, then we've already queued and/or // processed the predecessors. if (BB == DefBB || !LiveIn.insert(BB).second) continue; for (auto Pred : BB->getPredecessorBlocks()) Worklist.push_back(Pred); } } // Walk backwards in BB looking for strong_release, destroy_value, or // dealloc_box of the given value, and add it to releases. static bool addLastRelease(SILValue V, SILBasicBlock *BB, llvm::SmallVectorImpl &Releases) { for (auto I = BB->rbegin(); I != BB->rend(); ++I) { if (isa(*I) || isa(*I) || isa(*I)) { if (stripOffCopyValue(I->getOperand(0)) != V) continue; Releases.push_back(&*I); return true; } } return false; } // Find the final releases of the alloc_box along any given path. // These can include paths from a release back to the alloc_box in a // loop. static bool getFinalReleases(SILValue Box, llvm::SmallVectorImpl &Releases) { llvm::SmallPtrSet LiveIn; llvm::SmallPtrSet UseBlocks; auto *DefBB = Box->getParentBlock(); auto seenRelease = false; SILInstruction *OneRelease = nullptr; // We'll treat this like a liveness problem where the alloc_box is // the def. Each block that has a use of the owning pointer has the // value live-in unless it is the block with the alloc_box. llvm::SmallVector Worklist(Box->use_begin(), Box->use_end()); while (!Worklist.empty()) { auto *Op = Worklist.pop_back_val(); auto *User = Op->getUser(); auto *BB = User->getParent(); if (isa(User)) continue; if (BB != DefBB) LiveIn.insert(BB); // Also keep track of the blocks with uses. UseBlocks.insert(BB); // If we have a copy value or a mark_uninitialized, add its uses to the work // list and continue. if (isa(User) || isa(User)) { copy(cast(User)->getUses(), std::back_inserter(Worklist)); continue; } // Try to speed up the trivial case of single release/dealloc. if (isa(User) || isa(User) || isa(User)) { if (!seenRelease) OneRelease = User; else OneRelease = nullptr; seenRelease = true; } } // Only a single release/dealloc? We're done! if (OneRelease) { Releases.push_back(OneRelease); return true; } propagateLiveness(LiveIn, DefBB); // Now examine each block we saw a use in. If it has no successors // that are in LiveIn, then the last use in the block is the final // release/dealloc. for (auto *BB : UseBlocks) if (!successorHasLiveIn(BB, LiveIn)) if (!addLastRelease(Box, BB, Releases)) return false; return true; } //===----------------------------------------------------------------------===// // alloc_box Escape Analysis //===----------------------------------------------------------------------===// /// This is a list we use to store a set of indices. We create the set by /// sorting, uniquing at the appropriate time. The reason why it makes sense to /// just use a sorted vector with std::count is because generally functions do /// not have that many arguments and even fewer promoted arguments. using ArgIndexList = llvm::SmallVector; static bool partialApplyEscapes(SILValue V, bool examineApply); /// Could this operand to an apply escape that function by being /// stored or returned? static bool partialApplyArgumentEscapes(Operand *O) { SILFunction *F = getFunctionBody(O->getUser()); // If we cannot examine the function body, assume the worst. if (!F) return true; // Check the uses of the operand, but do not recurse down into other // apply instructions. auto Param = SILValue(getParameterForOperand(F, O)); return partialApplyEscapes(Param, /* examineApply = */ false); } static bool partialApplyEscapes(SILValue V, bool examineApply) { SILModuleConventions ModConv(*V->getModule()); llvm::SmallVector Worklist(V->use_begin(), V->use_end()); while (!Worklist.empty()) { auto *Op = Worklist.pop_back_val(); // These instructions do not cause the address to escape. if (!useCaptured(Op)) continue; auto *User = Op->getUser(); // If we have a copy_value, the copy value does not cause an escape, but its // uses might do so... so add the copy_value's uses to the worklist and // continue. if (auto CVI = dyn_cast(User)) { copy(CVI->getUses(), std::back_inserter(Worklist)); continue; } if (auto *Apply = dyn_cast(User)) { // Applying a function does not cause the function to escape. if (Op->getOperandNumber() == 0) continue; // apply instructions do not capture the pointer when it is passed // indirectly if (Apply->getArgumentConvention(Op->getOperandNumber() - 1) .isIndirectConvention()) continue; // Optionally drill down into an apply to see if the operand is // captured in or returned from the apply. if (examineApply && !partialApplyArgumentEscapes(Op)) continue; } // partial_apply instructions do not allow the pointer to escape // when it is passed indirectly, unless the partial_apply itself // escapes if (auto *PartialApply = dyn_cast(User)) { auto Args = PartialApply->getArguments(); auto Params = PartialApply->getSubstCalleeType()->getParameters(); Params = Params.slice(Params.size() - Args.size(), Args.size()); if (ModConv.isSILIndirect(Params[Op->getOperandNumber() - 1])) { if (partialApplyEscapes(PartialApply, /*examineApply = */ true)) return true; continue; } } return true; } return false; } static SILInstruction *findUnexpectedBoxUse(SILValue Box, bool examinePartialApply, bool inAppliedFunction, llvm::SmallVectorImpl &); /// checkPartialApplyBody - Check the body of a partial apply to see /// if the box pointer argument passed to it has uses that would /// disqualify it from being promoted to a stack location. Return /// true if this partial apply will not block our promoting the box. static bool checkPartialApplyBody(Operand *O) { SILFunction *F = getFunctionBody(O->getUser()); // If we cannot examine the function body, assume the worst. if (!F) return false; // We don't actually use these because we're not recursively // rewriting the partial applies we find. llvm::SmallVector PromotedOperands; auto Param = SILValue(getParameterForOperand(F, O)); return !findUnexpectedBoxUse(Param, /* examinePartialApply = */ false, /* inAppliedFunction = */ true, PromotedOperands); } /// Validate that the uses of a pointer to a box do not eliminate it from /// consideration for promotion to a stack element. Optionally examine the body /// of partial_apply to see if there is an unexpected use inside. Return the /// instruction with the unexpected use if we find one. static SILInstruction * findUnexpectedBoxUse(SILValue Box, bool examinePartialApply, bool inAppliedFunction, llvm::SmallVectorImpl &PromotedOperands) { assert((Box->getType().is() || Box->getType() == SILType::getNativeObjectType(Box->getType().getASTContext())) && "Expected an object pointer!"); llvm::SmallVector LocalPromotedOperands; // Scan all of the uses of the retain count value, collecting all // the releases and validating that we don't have an unexpected // user. llvm::SmallVector Worklist(Box->use_begin(), Box->use_end()); while (!Worklist.empty()) { auto *Op = Worklist.pop_back_val(); auto *User = Op->getUser(); // Retains and releases are fine. Deallocs are fine if we're not // examining a function that the alloc_box was passed into. // Projections are fine as well. if (isa(User) || isa(User) || isa(User) || isa(User) || (!inAppliedFunction && isa(User))) continue; // If our user instruction is a copy_value or a marked_uninitialized, visit // the users recursively. if (isa(User) || isa(User)) { copy(cast(User)->getUses(), std::back_inserter(Worklist)); continue; } // For partial_apply, if we've been asked to examine the body, the // uses of the argument are okay there, and the partial_apply // itself cannot escape, then everything is fine. if (auto *PAI = dyn_cast(User)) if (examinePartialApply && checkPartialApplyBody(Op) && !partialApplyEscapes(PAI, /* examineApply = */ true)) { LocalPromotedOperands.push_back(Op); continue; } return User; } PromotedOperands.append(LocalPromotedOperands.begin(), LocalPromotedOperands.end()); return nullptr; } /// canPromoteAllocBox - Can we promote this alloc_box to an alloc_stack? static bool canPromoteAllocBox(AllocBoxInst *ABI, llvm::SmallVectorImpl &PromotedOperands){ // Scan all of the uses of the address of the box to see if any // disqualifies the box from being promoted to the stack. if (auto *User = findUnexpectedBoxUse(ABI, /* examinePartialApply = */ true, /* inAppliedFunction = */ false, PromotedOperands)) { (void)User; // Otherwise, we have an unexpected use. DEBUG(llvm::dbgs() << "*** Failed to promote alloc_box in @" << ABI->getFunction()->getName() << ": " << *ABI << " Due to user: " << *User << "\n"); return false; } // Okay, it looks like this value doesn't escape. return true; } //===----------------------------------------------------------------------===// // alloc_box Promotion //===----------------------------------------------------------------------===// namespace { // Pass context and per-function analysis results. struct AllocBoxToStackState { SILFunctionTransform *T; bool CFGChanged = false; llvm::SmallVector Promotable; llvm::SmallVector PromotedOperands; AllocBoxToStackState(SILFunctionTransform *T) : T(T) {} }; } // anonymous namespace static void replaceProjectBoxUsers(SILValue HeapBox, SILValue StackBox) { llvm::SmallVector Worklist(HeapBox->use_begin(), HeapBox->use_end()); while (!Worklist.empty()) { auto *Op = Worklist.pop_back_val(); if (auto *PBI = dyn_cast(Op->getUser())) { // This may result in an alloc_stack being used by begin_access [dynamic]. PBI->replaceAllUsesWith(StackBox); continue; } auto *CVI = dyn_cast(Op->getUser()); if (!CVI) continue; copy(CVI->getUses(), std::back_inserter(Worklist)); } } /// rewriteAllocBoxAsAllocStack - Replace uses of the alloc_box with a /// new alloc_stack, but do not delete the alloc_box yet. static bool rewriteAllocBoxAsAllocStack(AllocBoxInst *ABI) { DEBUG(llvm::dbgs() << "*** Promoting alloc_box to stack: " << *ABI); SILValue HeapBox = ABI; Optional Kind; if (HeapBox->hasOneUse()) { auto *User = HeapBox->getSingleUse()->getUser(); if (auto *MUI = dyn_cast(User)) { HeapBox = MUI; Kind = MUI->getKind(); } } llvm::SmallVector FinalReleases; if (!getFinalReleases(HeapBox, FinalReleases)) return false; // Promote this alloc_box to an alloc_stack. Insert the alloc_stack // at the beginning of the function. SILBuilderWithScope Builder(ABI); assert(ABI->getBoxType()->getLayout()->getFields().size() == 1 && "rewriting multi-field box not implemented"); auto *ASI = Builder.createAllocStack( ABI->getLoc(), ABI->getBoxType()->getFieldType(ABI->getModule(), 0), ABI->getVarInfo()); // Transfer a mark_uninitialized if we have one. SILValue StackBox = ASI; if (Kind) { StackBox = Builder.createMarkUninitialized(ASI->getLoc(), ASI, Kind.getValue()); } // Replace all uses of the address of the box's contained value with // the address of the stack location. replaceProjectBoxUsers(HeapBox, StackBox); assert(ABI->getBoxType()->getLayout()->getFields().size() == 1 && "promoting multi-field box not implemented"); auto &Lowering = ABI->getModule() .getTypeLowering(ABI->getBoxType()->getFieldType(ABI->getModule(), 0)); auto Loc = CleanupLocation::get(ABI->getLoc()); for (auto LastRelease : FinalReleases) { SILBuilderWithScope Builder(LastRelease); if (!isa(LastRelease)&& !Lowering.isTrivial()) { // For non-trivial types, insert destroys for each final release-like // instruction we found that isn't an explicit dealloc_box. Builder.emitDestroyAddrAndFold(Loc, StackBox); } Builder.createDeallocStack(Loc, ASI); } // Remove any retain and release instructions. Since all uses of project_box // are gone, this only walks through uses of the box itself (the retain count // pointer). llvm::SmallVector Worklist; std::transform(ABI->use_begin(), ABI->use_end(), std::back_inserter(Worklist), [](Operand *Op) -> SILInstruction * { return Op->getUser(); }); while (!Worklist.empty()) { auto *User = Worklist.pop_back_val(); // Look through any mark_uninitialized, copy_values. if (isa(User) || isa(User)) { auto Inst = cast(User); transform(Inst->getUses(), std::back_inserter(Worklist), [](Operand *Op) -> SILInstruction * { return Op->getUser(); }); Inst->replaceAllUsesWithUndef(); Inst->eraseFromParent(); continue; } assert(isa(User) || isa(User) || isa(User) || isa(User) || isa(User)); User->eraseFromParent(); } return true; } namespace { /// \brief A SILCloner subclass which clones a closure function while /// promoting some of its box parameters to stack addresses. class PromotedParamCloner : public SILClonerWithScopes { public: friend class SILInstructionVisitor; friend class SILCloner; PromotedParamCloner(SILFunction *Orig, IsSerialized_t Serialized, ArgIndexList &PromotedArgIndices, llvm::StringRef ClonedName); void populateCloned(); SILFunction *getCloned() { return &getBuilder().getFunction(); } private: static SILFunction *initCloned(SILFunction *Orig, IsSerialized_t Serialized, ArgIndexList &PromotedArgIndices, llvm::StringRef ClonedName); void visitStrongReleaseInst(StrongReleaseInst *Inst); void visitDestroyValueInst(DestroyValueInst *Inst); void visitStrongRetainInst(StrongRetainInst *Inst); void visitCopyValueInst(CopyValueInst *Inst); void visitProjectBoxInst(ProjectBoxInst *Inst); SILFunction *Orig; ArgIndexList &PromotedArgIndices; // The values in the original function that are promoted to stack // references. llvm::SmallSet PromotedParameters; }; } // end anonymous namespace PromotedParamCloner::PromotedParamCloner(SILFunction *Orig, IsSerialized_t Serialized, ArgIndexList &PromotedArgIndices, llvm::StringRef ClonedName) : SILClonerWithScopes( *initCloned(Orig, Serialized, PromotedArgIndices, ClonedName)), Orig(Orig), PromotedArgIndices(PromotedArgIndices) { assert(Orig->getDebugScope()->getParentFunction() != getCloned()->getDebugScope()->getParentFunction()); } static std::string getClonedName(SILFunction *F, IsSerialized_t Serialized, ArgIndexList &PromotedArgIndices) { auto P = Demangle::SpecializationPass::AllocBoxToStack; Mangle::FunctionSignatureSpecializationMangler Mangler(P, Serialized, F); for (unsigned i : PromotedArgIndices) { Mangler.setArgumentBoxToStack(i); } return Mangler.mangle(); } /// \brief Create the function corresponding to the clone of the /// original closure with the signature modified to reflect promoted /// parameters (which are specified by PromotedArgIndices). SILFunction *PromotedParamCloner::initCloned(SILFunction *Orig, IsSerialized_t Serialized, ArgIndexList &PromotedArgIndices, llvm::StringRef ClonedName) { SILModule &M = Orig->getModule(); SmallVector ClonedInterfaceArgTys; // Generate a new parameter list with deleted parameters removed. SILFunctionType *OrigFTI = Orig->getLoweredFunctionType(); unsigned Index = Orig->getConventions().getSILArgIndexOfFirstParam(); for (auto ¶m : OrigFTI->getParameters()) { if (count(PromotedArgIndices, Index)) { auto boxTy = param.getSILStorageType().castTo(); assert(boxTy->getLayout()->getFields().size() == 1 && "promoting compound box not implemented"); SILType paramTy; { Lowering::GenericContextScope scope(Orig->getModule().Types, OrigFTI->getGenericSignature()); paramTy = boxTy->getFieldType(Orig->getModule(), 0); } auto promotedParam = SILParameterInfo(paramTy.getSwiftRValueType(), ParameterConvention::Indirect_InoutAliasable); ClonedInterfaceArgTys.push_back(promotedParam); } else { ClonedInterfaceArgTys.push_back(param); } ++Index; } // Create the new function type for the cloned function with some of // the parameters promoted. auto ClonedTy = SILFunctionType::get( OrigFTI->getGenericSignature(), OrigFTI->getExtInfo(), OrigFTI->getCoroutineKind(), OrigFTI->getCalleeConvention(), ClonedInterfaceArgTys, OrigFTI->getYields(), OrigFTI->getResults(), OrigFTI->getOptionalErrorResult(), M.getASTContext(), OrigFTI->getWitnessMethodConformanceOrNone()); assert((Orig->isTransparent() || Orig->isBare() || Orig->getLocation()) && "SILFunction missing location"); assert((Orig->isTransparent() || Orig->isBare() || Orig->getDebugScope()) && "SILFunction missing DebugScope"); assert(!Orig->isGlobalInit() && "Global initializer cannot be cloned"); auto *Fn = M.createFunction( SILLinkage::Shared, ClonedName, ClonedTy, Orig->getGenericEnvironment(), Orig->getLocation(), Orig->isBare(), IsNotTransparent, Serialized, Orig->getEntryCount(), Orig->isThunk(), Orig->getClassSubclassScope(), Orig->getInlineStrategy(), Orig->getEffectsKind(), Orig, Orig->getDebugScope()); for (auto &Attr : Orig->getSemanticsAttrs()) { Fn->addSemanticsAttr(Attr); } if (!Orig->hasQualifiedOwnership()) { Fn->setUnqualifiedOwnership(); } return Fn; } /// \brief Populate the body of the cloned closure, modifying instructions as /// necessary to take into consideration the removed parameters. void PromotedParamCloner::populateCloned() { SILFunction *Cloned = getCloned(); // Create arguments for the entry block SILBasicBlock *OrigEntryBB = &*Orig->begin(); SILBasicBlock *ClonedEntryBB = Cloned->createBasicBlock(); unsigned ArgNo = 0; auto I = OrigEntryBB->args_begin(), E = OrigEntryBB->args_end(); while (I != E) { if (count(PromotedArgIndices, ArgNo)) { // Create a new argument with the promoted type. auto boxTy = (*I)->getType().castTo(); assert(boxTy->getLayout()->getFields().size() == 1 && "promoting multi-field boxes not implemented yet"); auto promotedTy = boxTy->getFieldType(Cloned->getModule(), 0); auto *promotedArg = ClonedEntryBB->createFunctionArgument(promotedTy, (*I)->getDecl()); PromotedParameters.insert(*I); // Map any projections of the box to the promoted argument. for (auto use : (*I)->getUses()) { if (auto project = dyn_cast(use->getUser())) { ValueMap.insert(std::make_pair(project, promotedArg)); } } } else { // Create a new argument which copies the original argument. SILValue MappedValue = ClonedEntryBB->createFunctionArgument( (*I)->getType(), (*I)->getDecl()); ValueMap.insert(std::make_pair(*I, MappedValue)); } ++ArgNo; ++I; } getBuilder().setInsertionPoint(ClonedEntryBB); BBMap.insert(std::make_pair(OrigEntryBB, ClonedEntryBB)); // Recursively visit original BBs in depth-first preorder, starting with the // entry block, cloning all instructions other than terminators. visitSILBasicBlock(OrigEntryBB); // Now iterate over the BBs and fix up the terminators. for (auto BI = BBMap.begin(), BE = BBMap.end(); BI != BE; ++BI) { getBuilder().setInsertionPoint(BI->second); visit(BI->first->getTerminator()); } } /// \brief Handle a strong_release instruction during cloning of a closure; if /// it is a strong release of a promoted box argument, then it is replaced with /// a ReleaseValue of the new object type argument, otherwise it is handled /// normally. void PromotedParamCloner::visitStrongReleaseInst(StrongReleaseInst *Inst) { // If it's a release of a promoted parameter, just drop the instruction. if (PromotedParameters.count(Inst->getOperand())) return; SILCloner::visitStrongReleaseInst(Inst); } /// \brief Handle a strong_release instruction during cloning of a closure; if /// it is a strong release of a promoted box argument, then it is replaced with /// a ReleaseValue of the new object type argument, otherwise it is handled /// normally. void PromotedParamCloner::visitDestroyValueInst(DestroyValueInst *Inst) { // If we are a destroy of a promoted parameter, just drop the instruction. We // look through copy_value to preserve current behavior. SILInstruction *Tmp = Inst; while (auto *CopyOp = dyn_cast(Tmp->getOperand(0))) { Tmp = CopyOp; } if (PromotedParameters.count(Tmp->getOperand(0))) return; SILCloner::visitDestroyValueInst(Inst); } void PromotedParamCloner::visitStrongRetainInst(StrongRetainInst *Inst) { // If it's a retain of a promoted parameter, just drop the instruction. if (PromotedParameters.count(Inst->getOperand())) return; SILCloner::visitStrongRetainInst(Inst); } void PromotedParamCloner::visitCopyValueInst(CopyValueInst *CVI) { // If it's a copy of a promoted parameter, just drop the instruction. auto *Tmp = CVI; while (auto *CopyOp = dyn_cast(Tmp->getOperand())) { Tmp = CopyOp; } if (PromotedParameters.count(Tmp->getOperand())) return; SILCloner::visitCopyValueInst(CVI); } void PromotedParamCloner::visitProjectBoxInst(ProjectBoxInst *Inst) { // If it's a projection of a promoted parameter, drop the instruction. // Its uses will be replaced by the promoted address. // and replace its uses with if (PromotedParameters.count(Inst->getOperand())) return; SILCloner::visitProjectBoxInst(Inst); } /// Specialize a partial_apply by promoting the parameters indicated by /// indices. We expect these parameters to be replaced by stack address /// references. static PartialApplyInst * specializePartialApply(PartialApplyInst *PartialApply, ArgIndexList &PromotedArgIndices, AllocBoxToStackState &pass) { auto *FRI = cast(PartialApply->getCallee()); assert(FRI && "Expected a direct partial_apply!"); auto *F = FRI->getReferencedFunction(); assert(F && "Expected a referenced function!"); IsSerialized_t Serialized = IsNotSerialized; if (PartialApply->getFunction()->isSerialized()) Serialized = IsSerializable; std::string ClonedName = getClonedName(F, Serialized, PromotedArgIndices); auto &M = PartialApply->getModule(); SILFunction *ClonedFn; if (auto *PrevFn = M.lookUpFunction(ClonedName)) { assert(PrevFn->isSerialized() == Serialized); ClonedFn = PrevFn; } else { // Clone the function the existing partial_apply references. PromotedParamCloner Cloner(F, Serialized, PromotedArgIndices, ClonedName); Cloner.populateCloned(); ClonedFn = Cloner.getCloned(); pass.T->notifyAddFunction(ClonedFn, F); } // Now create the new partial_apply using the cloned function. llvm::SmallVector Args; ValueLifetimeAnalysis::Frontier PAFrontier; // Promote the arguments that need promotion. for (auto &O : PartialApply->getArgumentOperands()) { auto ArgIndex = getArgIndexForOperand(&O); if (!count(PromotedArgIndices, ArgIndex)) { Args.push_back(O.get()); continue; } // If this argument is promoted, it is a box that we're turning into an // address because we've proven we can keep this value on the stack. The // partial_apply had ownership of this box so we must now release it // explicitly when the partial_apply is released. auto *Box = cast(O.get()); assert((isa(Box) || isa(Box)) && "Expected either an alloc box or a copy of an alloc box"); SILBuilder B(Box); Args.push_back(B.createProjectBox(Box->getLoc(), Box, 0)); if (PAFrontier.empty()) { ValueLifetimeAnalysis VLA(PartialApply); pass.CFGChanged |= !VLA.computeFrontier( PAFrontier, ValueLifetimeAnalysis::AllowToModifyCFG); assert(!PAFrontier.empty() && "partial_apply must have at least one use " "to release the returned function"); } // Insert destroys of the box at each point where the partial_apply becomes // dead. for (SILInstruction *FrontierInst : PAFrontier) { SILBuilderWithScope Builder(FrontierInst); Builder.createDestroyValue(PartialApply->getLoc(), Box); } } SILBuilderWithScope Builder(PartialApply); // Build the function_ref and partial_apply. SILValue FunctionRef = Builder.createFunctionRef(PartialApply->getLoc(), ClonedFn); return Builder.createPartialApply( PartialApply->getLoc(), FunctionRef, PartialApply->getSubstitutions(), Args, PartialApply->getType().getAs()->getCalleeConvention()); } static void rewritePartialApplies(AllocBoxToStackState &pass) { llvm::DenseMap IndexMap; ArgIndexList Indices; // Build a map from partial_apply to the indices of the operands // that will be promoted in our rewritten version. for (auto *O : pass.PromotedOperands) { auto ArgIndexNumber = getArgIndexForOperand(O); Indices.clear(); Indices.push_back(ArgIndexNumber); auto *PartialApply = cast(O->getUser()); llvm::DenseMap::iterator It; bool Inserted; std::tie(It, Inserted) = IndexMap.insert(std::make_pair(PartialApply, Indices)); if (!Inserted) It->second.push_back(ArgIndexNumber); } // Clone the referenced function of each partial_apply, removing the // operands that we will not need, and remove the existing // partial_apply. for (auto &It : IndexMap) { auto *PartialApply = It.first; auto &Indices = It.second; // Sort the indices and unique them. std::sort(Indices.begin(), Indices.end()); Indices.erase(std::unique(Indices.begin(), Indices.end()), Indices.end()); PartialApplyInst *Replacement = specializePartialApply(PartialApply, Indices, pass); PartialApply->replaceAllUsesWith(Replacement); auto *FRI = cast(PartialApply->getCallee()); PartialApply->eraseFromParent(); // TODO: Erase from module if there are no more uses. if (FRI->use_empty()) FRI->eraseFromParent(); } } /// Clone closure bodies and rewrite partial applies. Returns the number of /// alloc_box allocations promoted. static unsigned rewritePromotedBoxes(AllocBoxToStackState &pass) { // First we'll rewrite any partial applies that we can to remove the // box container pointer from the operands. rewritePartialApplies(pass); unsigned Count = 0; auto rend = pass.Promotable.rend(); for (auto I = pass.Promotable.rbegin(); I != rend; ++I) { auto *ABI = *I; if (rewriteAllocBoxAsAllocStack(ABI)) { ++Count; ABI->eraseFromParent(); } } return Count; } namespace { class AllocBoxToStack : public SILFunctionTransform { /// The entry point to the transformation. void run() override { AllocBoxToStackState pass(this); for (auto &BB : *getFunction()) { for (auto &I : BB) if (auto *ABI = dyn_cast(&I)) if (canPromoteAllocBox(ABI, pass.PromotedOperands)) pass.Promotable.push_back(ABI); } if (!pass.Promotable.empty()) { auto Count = rewritePromotedBoxes(pass); NumStackPromoted += Count; if (Count) { StackNesting SN; if (SN.correctStackNesting(getFunction()) == StackNesting::Changes::CFG) pass.CFGChanged = true; } invalidateAnalysis( pass.CFGChanged ? SILAnalysis::InvalidationKind::FunctionBody : SILAnalysis::InvalidationKind::CallsAndInstructions); } } }; } // end anonymous namespace SILTransform *swift::createAllocBoxToStack() { return new AllocBoxToStack(); }