| //===--- ClosureLifetimeFixup.cpp - Fixup the lifetime of closures --------===// |
| // |
| // 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 "closure-lifetime-fixup" |
| |
| #include "swift/SIL/DebugUtils.h" |
| #include "swift/SIL/InstructionUtils.h" |
| #include "swift/SIL/SILArgument.h" |
| #include "swift/SIL/SILBuilder.h" |
| #include "swift/SIL/SILInstruction.h" |
| #include "swift/SILOptimizer/PassManager/Passes.h" |
| #include "swift/SILOptimizer/PassManager/Transforms.h" |
| #include "swift/SILOptimizer/Utils/CFG.h" |
| #include "swift/SILOptimizer/Utils/Local.h" |
| |
| #include "llvm/Support/CommandLine.h" |
| |
| llvm::cl::opt<bool> DisableConvertEscapeToNoEscapeSwitchEnumPeephole( |
| "sil-disable-convert-escape-to-noescape-switch-peephole", |
| llvm::cl::init(false), |
| llvm::cl::desc( |
| "Disable the convert_escape_to_noescape switch enum peephole. "), |
| llvm::cl::Hidden); |
| |
| using namespace swift; |
| |
| static SILBasicBlock *getOptionalDiamondSuccessor(SwitchEnumInst *SEI) { |
| auto numSuccs = SEI->getNumSuccessors(); |
| if (numSuccs != 2) |
| return nullptr; |
| auto *SuccSome = SEI->getCase(0).second; |
| auto *SuccNone = SEI->getCase(1).second; |
| if (SuccSome->args_size() != 1) |
| std::swap(SuccSome, SuccNone); |
| |
| if (SuccSome->args_size() != 1 || SuccNone->args_size() != 0) |
| return nullptr; |
| |
| auto *Succ = SuccSome->getSingleSuccessorBlock(); |
| if (!Succ) |
| return nullptr; |
| |
| if (SuccNone == Succ) |
| return Succ; |
| |
| SuccNone = SuccNone->getSingleSuccessorBlock(); |
| if (SuccNone == Succ) |
| return Succ; |
| |
| if (SuccNone == nullptr) |
| return nullptr; |
| |
| SuccNone = SuccNone->getSingleSuccessorBlock(); |
| if (SuccNone == Succ) |
| return Succ; |
| |
| return nullptr; |
| } |
| |
| /// Find a safe insertion point for closure destruction. We might create a |
| /// closure that captures self in deinit of self. In this situation it is not |
| /// safe to destroy the closure after we called super deinit. We have to place |
| /// the closure destruction before that call. |
| /// |
| /// %deinit = objc_super_method %0 : $C, #A.deinit!deallocator.foreign |
| /// %super = upcast %0 : $C to $A |
| /// apply %deinit(%super) : $@convention(objc_method) (A) -> () |
| /// end_lifetime %super : $A |
| static SILInstruction *getDeinitSafeClosureDestructionPoint(TermInst *Term) { |
| for (auto It = Term->getParent()->rbegin(), E = Term->getParent()->rend(); |
| It != E; ++It) { |
| if (auto *EndLifetime = dyn_cast<EndLifetimeInst>(&*It)) { |
| auto *SuperInstance = EndLifetime->getOperand()->getDefiningInstruction(); |
| assert(SuperInstance && "Expected an instruction"); |
| return SuperInstance; |
| } |
| } |
| return Term; |
| } |
| |
| /// Extend the lifetime of the convert_escape_to_noescape's operand to the end |
| /// of the function. |
| static void extendLifetimeToEndOfFunction(SILFunction &Fn, |
| ConvertEscapeToNoEscapeInst *Cvt) { |
| auto EscapingClosure = Cvt->getOperand(); |
| auto EscapingClosureTy = EscapingClosure->getType(); |
| auto OptionalEscapingClosureTy = SILType::getOptionalType(EscapingClosureTy); |
| auto loc = RegularLocation::getAutoGeneratedLocation(); |
| |
| SILBuilderWithScope B(Cvt); |
| auto NewCvt = B.createConvertEscapeToNoEscape( |
| Cvt->getLoc(), Cvt->getOperand(), Cvt->getType(), false, true); |
| Cvt->replaceAllUsesWith(NewCvt); |
| Cvt->eraseFromParent(); |
| Cvt = NewCvt; |
| |
| // Create an alloc_stack Optional<() -> ()> at the beginning of the function. |
| AllocStackInst *Slot; |
| auto &Context = Cvt->getModule().getASTContext(); |
| { |
| SILBuilderWithScope B(Fn.getEntryBlock()->begin()); |
| Slot = B.createAllocStack(loc, OptionalEscapingClosureTy); |
| auto *NoneDecl = Context.getOptionalNoneDecl(); |
| // Store None to it. |
| B.createStore( |
| loc, B.createEnum(loc, SILValue(), NoneDecl, OptionalEscapingClosureTy), |
| Slot, StoreOwnershipQualifier::Init); |
| } |
| // Insert a copy before the convert_escape_to_noescape and store it to the |
| // alloc_stack location. |
| { |
| SILBuilderWithScope B(Cvt); |
| auto *SomeDecl = Context.getOptionalSomeDecl(); |
| B.createDestroyAddr(loc, Slot); |
| auto ClosureCopy = B.createCopyValue(loc, EscapingClosure); |
| B.createStore( |
| loc, |
| B.createEnum(loc, ClosureCopy, SomeDecl, OptionalEscapingClosureTy), |
| Slot, StoreOwnershipQualifier::Init); |
| } |
| // Insert destroys at the function exits. |
| SmallVector<SILBasicBlock *, 4> ExitingBlocks; |
| Fn.findExitingBlocks(ExitingBlocks); |
| for (auto *Exit : ExitingBlocks) { |
| auto *Term = Exit->getTerminator(); |
| auto *SafeClosureDestructionPt = getDeinitSafeClosureDestructionPoint(Term); |
| SILBuilderWithScope B(SafeClosureDestructionPt); |
| B.createDestroyAddr(loc, Slot); |
| SILBuilderWithScope B2(Term); |
| B2.createDeallocStack(loc, Slot); |
| } |
| } |
| |
| static SILInstruction *lookThroughRebastractionUsers( |
| SILInstruction *Inst, |
| llvm::DenseMap<SILInstruction *, SILInstruction *> &Memoized) { |
| |
| if (Inst == nullptr) |
| return nullptr; |
| |
| // Try a cached lookup. |
| auto Res = Memoized.find(Inst); |
| if (Res != Memoized.end()) |
| return Res->second; |
| |
| // Cache recursive results. |
| auto memoizeResult = [&] (SILInstruction *from, SILInstruction *toResult) { |
| Memoized[from] = toResult; |
| return toResult; |
| }; |
| |
| // If we have a convert_function, just look at its user. |
| if (auto *Cvt = dyn_cast<ConvertFunctionInst>(Inst)) |
| return memoizeResult(Inst, lookThroughRebastractionUsers( |
| getSingleNonDebugUser(Cvt), Memoized)); |
| if (auto *Cvt = dyn_cast<ConvertEscapeToNoEscapeInst>(Inst)) |
| return memoizeResult(Inst, lookThroughRebastractionUsers( |
| getSingleNonDebugUser(Cvt), Memoized)); |
| |
| // If we have a partial_apply user look at its single (non release) user. |
| auto *PA = dyn_cast<PartialApplyInst>(Inst); |
| if (!PA) return Inst; |
| |
| SILInstruction *SingleNonDebugNonRefCountUser = nullptr; |
| for (auto *Usr : getNonDebugUses(PA)) { |
| auto *I = Usr->getUser(); |
| if (onlyAffectsRefCount(I)) |
| continue; |
| if (SingleNonDebugNonRefCountUser) { |
| SingleNonDebugNonRefCountUser = nullptr; |
| break; |
| } |
| SingleNonDebugNonRefCountUser = I; |
| } |
| |
| return memoizeResult(Inst, lookThroughRebastractionUsers( |
| SingleNonDebugNonRefCountUser, Memoized)); |
| } |
| |
| static bool tryExtendLifetimeToLastUse( |
| ConvertEscapeToNoEscapeInst *Cvt, |
| llvm::DenseMap<SILInstruction *, SILInstruction *> &Memoized) { |
| // Don't optimize converts that might have been escaped by the function call |
| // (materializeForSet 'escapes' its arguments into the writeback buffer). |
| if (Cvt->isEscapedByUser()) |
| return false; |
| |
| // If there is a single user that is an apply this is simple: extend the |
| // lifetime of the operand until after the apply. |
| auto SingleUser = lookThroughRebastractionUsers(Cvt, Memoized); |
| if (!SingleUser) |
| return false; |
| |
| // Handle an apply. |
| if (auto SingleApplyUser = FullApplySite::isa(SingleUser)) { |
| // FIXME: Don't know how-to handle begin_apply/end_apply yet. |
| if (auto *Begin = |
| dyn_cast<BeginApplyInst>(SingleApplyUser.getInstruction())) { |
| return false; |
| } |
| |
| auto loc = RegularLocation::getAutoGeneratedLocation(); |
| |
| // Insert a copy at the convert_escape_to_noescape [not_guaranteed] and |
| // change the instruction to the guaranteed form. |
| auto EscapingClosure = Cvt->getOperand(); |
| { |
| SILBuilderWithScope B(Cvt); |
| auto NewCvt = B.createConvertEscapeToNoEscape( |
| Cvt->getLoc(), Cvt->getOperand(), Cvt->getType(), false, true); |
| Cvt->replaceAllUsesWith(NewCvt); |
| Cvt->eraseFromParent(); |
| Cvt = NewCvt; |
| } |
| |
| SILBuilderWithScope B2(Cvt); |
| auto ClosureCopy = B2.createCopyValue(loc, EscapingClosure); |
| |
| // Insert a destroy after the apply. |
| if (auto *Apply = dyn_cast<ApplyInst>(SingleApplyUser.getInstruction())) { |
| auto InsertPt = std::next(SILBasicBlock::iterator(Apply)); |
| SILBuilderWithScope B3(InsertPt); |
| B3.createDestroyValue(loc, ClosureCopy); |
| |
| } else if (auto *Try = |
| dyn_cast<TryApplyInst>(SingleApplyUser.getInstruction())) { |
| for (auto *SuccBB : Try->getSuccessorBlocks()) { |
| SILBuilderWithScope B3(SuccBB->begin()); |
| B3.createDestroyValue(loc, ClosureCopy); |
| } |
| } else { |
| llvm_unreachable("Unknown FullApplySite instruction kind"); |
| } |
| return true; |
| } |
| return false; |
| } |
| |
| /// Ensure the lifetime of the closure accross an |
| /// |
| /// optional<@escaping () -> ()> to |
| /// optional<@noescape @convention(block) () -> ()> |
| /// |
| /// conversion and its use. |
| /// |
| /// The pattern this is looking for |
| /// switch_enum %closure |
| /// / \ |
| /// convert_escape_to_noescape nil |
| /// switch_enum |
| /// / \ |
| /// convertToBlock nil |
| /// \ / |
| /// (%convertOptionalBlock :) |
| /// We will insert a copy_value of the original %closure before the two |
| /// diamonds. And a destroy of %closure at the last destroy of |
| /// %convertOptionalBlock. |
| static bool trySwitchEnumPeephole(ConvertEscapeToNoEscapeInst *Cvt) { |
| // Don't optimize converts that might have been escaped by the function call |
| // (materializeForSet 'escapes' its arguments into the writeback buffer). |
| if (Cvt->isEscapedByUser()) |
| return false; |
| |
| auto *blockArg = dyn_cast<SILArgument>(Cvt->getOperand()); |
| if (!blockArg) |
| return false; |
| auto *PredBB = Cvt->getParent()->getSinglePredecessorBlock(); |
| if (!PredBB) |
| return false; |
| auto *ConvertSuccessorBlock = Cvt->getParent()->getSingleSuccessorBlock(); |
| if (!ConvertSuccessorBlock) |
| return false; |
| auto *SwitchEnum1 = dyn_cast<SwitchEnumInst>(PredBB->getTerminator()); |
| if (!SwitchEnum1) |
| return false; |
| auto *DiamondSucc = getOptionalDiamondSuccessor(SwitchEnum1); |
| if (!DiamondSucc) |
| return false; |
| auto *SwitchEnum2 = dyn_cast<SwitchEnumInst>(DiamondSucc->getTerminator()); |
| if (!SwitchEnum2) |
| return false; |
| auto *DiamondSucc2 = getOptionalDiamondSuccessor(SwitchEnum2); |
| if (!DiamondSucc2) |
| return false; |
| if (DiamondSucc2->getNumArguments() != 1) |
| return false; |
| |
| // Look for the last and only destroy. |
| SILInstruction *onlyDestroy = [&]() -> SILInstruction * { |
| SILInstruction *lastDestroy = nullptr; |
| for (auto *Use : DiamondSucc2->getArgument(0)->getUses()) { |
| SILInstruction *Usr = Use->getUser(); |
| if (isa<ReleaseValueInst>(Usr) || isa<StrongReleaseInst>(Usr) || |
| isa<DestroyValueInst>(Usr)) { |
| if (lastDestroy) |
| return nullptr; |
| lastDestroy = Usr; |
| } |
| } |
| return lastDestroy; |
| }(); |
| if (!onlyDestroy) |
| return false; |
| |
| // Replace the convert_escape_to_noescape instruction. |
| { |
| SILBuilderWithScope B(Cvt); |
| auto NewCvt = B.createConvertEscapeToNoEscape( |
| Cvt->getLoc(), Cvt->getOperand(), Cvt->getType(), false, true); |
| Cvt->replaceAllUsesWith(NewCvt); |
| Cvt->eraseFromParent(); |
| } |
| |
| // Extend the lifetime. |
| SILBuilderWithScope B(SwitchEnum1); |
| auto loc = RegularLocation::getAutoGeneratedLocation(); |
| auto copy = |
| B.createCopyValue(loc, SwitchEnum1->getOperand()); |
| B.setInsertionPoint(onlyDestroy); |
| B.createDestroyValue(loc, copy); |
| return true; |
| } |
| |
| /// Look for a single destroy user and possibly unowned apply uses. |
| static SILInstruction *getOnlyDestroy(CopyBlockWithoutEscapingInst *CB) { |
| SILInstruction *onlyDestroy = nullptr; |
| |
| for (auto *Use : getNonDebugUses(CB)) { |
| SILInstruction *Inst = Use->getUser(); |
| |
| // If this an apply use, only handle unowned parameters. |
| if (auto Apply = FullApplySite::isa(Inst)) { |
| SILArgumentConvention Conv = Apply.getArgumentConvention(*Use); |
| if (Conv != SILArgumentConvention::Direct_Unowned) |
| return nullptr; |
| continue; |
| } |
| |
| // We have already seen one destroy. |
| if (onlyDestroy) |
| return nullptr; |
| |
| if (isa<DestroyValueInst>(Inst) || isa<ReleaseValueInst>(Inst) || |
| isa<StrongReleaseInst>(Inst)) { |
| onlyDestroy = Inst; |
| continue; |
| } |
| |
| // Some other instruction. |
| return nullptr; |
| } |
| |
| if (!onlyDestroy) |
| return nullptr; |
| |
| // Now look at whether the dealloc_stack or the destroy postdominates and |
| // return the post dominator. |
| auto *BlockInit = dyn_cast<InitBlockStorageHeaderInst>(CB->getBlock()); |
| if (!BlockInit) |
| return nullptr; |
| |
| auto *AS = dyn_cast<AllocStackInst>(BlockInit->getBlockStorage()); |
| if (!AS) |
| return nullptr; |
| auto *Dealloc = AS->getSingleDeallocStack(); |
| if (!Dealloc || Dealloc->getParent() != onlyDestroy->getParent()) |
| return nullptr; |
| |
| // Return the later instruction. |
| for (auto It = SILBasicBlock::iterator(onlyDestroy), |
| E = Dealloc->getParent()->end(); |
| It != E; ++It) { |
| if (&*It == Dealloc) |
| return Dealloc; |
| } |
| return onlyDestroy; |
| } |
| |
| /// Lower a copy_block_without_escaping instruction. |
| /// |
| /// This involves replacing: |
| /// |
| /// %copy = copy_block_without_escaping %block withoutEscaping %closure |
| /// |
| /// ... |
| /// destroy_value %copy |
| /// |
| /// by (roughly) the instruction sequence: |
| /// |
| /// %copy = copy_block %block |
| /// |
| /// ... |
| /// destroy_value %copy |
| /// %e = is_escaping %closure |
| /// cond_fail %e |
| /// destroy_value %closure |
| static bool fixupCopyBlockWithoutEscaping(CopyBlockWithoutEscapingInst *CB) { |
| SmallVector<SILInstruction *, 4> LifetimeEndPoints; |
| |
| // Find the end of the lifetime of the copy_block_without_escaping |
| // instruction. |
| auto &Fn = *CB->getFunction(); |
| auto *SingleDestroy = getOnlyDestroy(CB); |
| SmallVector<SILBasicBlock *, 4> ExitingBlocks; |
| Fn.findExitingBlocks(ExitingBlocks); |
| if (SingleDestroy) { |
| LifetimeEndPoints.push_back( |
| &*std::next(SILBasicBlock::iterator(SingleDestroy))); |
| } else { |
| // Otherwise, conservatively insert verification at the end of the function. |
| for (auto *Exit : ExitingBlocks) |
| LifetimeEndPoints.push_back(Exit->getTerminator()); |
| } |
| |
| auto SentinelClosure = CB->getClosure(); |
| auto Loc = CB->getLoc(); |
| |
| SILBuilderWithScope B(CB); |
| auto *NewCB = B.createCopyBlock(Loc, CB->getBlock()); |
| CB->replaceAllUsesWith(NewCB); |
| CB->eraseFromParent(); |
| |
| // Create an stack slot for the closure sentinel and store the sentinel (or |
| // none on other paths. |
| auto generatedLoc = RegularLocation::getAutoGeneratedLocation(); |
| AllocStackInst *Slot; |
| auto &Context = NewCB->getModule().getASTContext(); |
| auto OptionalEscapingClosureTy = |
| SILType::getOptionalType(SentinelClosure->getType()); |
| auto *NoneDecl = Context.getOptionalNoneDecl(); |
| { |
| SILBuilderWithScope B(Fn.getEntryBlock()->begin()); |
| Slot = B.createAllocStack(generatedLoc, OptionalEscapingClosureTy); |
| // Store None to it. |
| B.createStore(generatedLoc, |
| B.createEnum(generatedLoc, SILValue(), NoneDecl, |
| OptionalEscapingClosureTy), |
| Slot, StoreOwnershipQualifier::Init); |
| } |
| { |
| SILBuilderWithScope B(NewCB); |
| // Store the closure sentinel (the copy_block_without_escaping closure |
| // operand consumed at +1, so we don't need a copy) to it. |
| B.createDestroyAddr(generatedLoc, Slot); // We could be in a loop. |
| auto *SomeDecl = Context.getOptionalSomeDecl(); |
| B.createStore(generatedLoc, |
| B.createEnum(generatedLoc, SentinelClosure, SomeDecl, |
| OptionalEscapingClosureTy), |
| Slot, StoreOwnershipQualifier::Init); |
| } |
| |
| for (auto LifetimeEndPoint : LifetimeEndPoints) { |
| SILBuilderWithScope B(LifetimeEndPoint); |
| auto IsEscaping = |
| B.createIsEscapingClosure(Loc, B.createLoadBorrow(generatedLoc, Slot), |
| IsEscapingClosureInst::ObjCEscaping); |
| B.createCondFail(Loc, IsEscaping); |
| B.createDestroyAddr(generatedLoc, Slot); |
| // Store None to it. |
| B.createStore(generatedLoc, |
| B.createEnum(generatedLoc, SILValue(), NoneDecl, |
| OptionalEscapingClosureTy), |
| Slot, StoreOwnershipQualifier::Init); |
| } |
| |
| // Insert the dealloc_stack in all exiting blocks. |
| for (auto *ExitBlock: ExitingBlocks) { |
| auto *Terminator = ExitBlock->getTerminator(); |
| SILBuilderWithScope B(Terminator); |
| B.createDeallocStack(generatedLoc, Slot); |
| } |
| |
| return true; |
| } |
| |
| static bool fixupClosureLifetimes(SILFunction &Fn) { |
| bool Changed = false; |
| |
| // tryExtendLifetimeToLastUse uses a cache of recursive instruction use |
| // queries. |
| llvm::DenseMap<SILInstruction *, SILInstruction *> MemoizedQueries; |
| |
| for (auto &BB : Fn) { |
| auto I = BB.begin(); |
| while (I != BB.end()) { |
| SILInstruction *Inst = &*I; |
| ++I; |
| |
| // Handle, copy_block_without_escaping instructions. |
| if (auto *CB = dyn_cast<CopyBlockWithoutEscapingInst>(Inst)) { |
| Changed |= fixupCopyBlockWithoutEscaping(CB); |
| continue; |
| } |
| |
| // Otherwise, look at convert_escape_to_noescape [not_guaranteed] |
| // instructions. |
| auto *Cvt = dyn_cast<ConvertEscapeToNoEscapeInst>(Inst); |
| if (!Cvt || Cvt->isLifetimeGuaranteed()) |
| continue; |
| |
| // First try to peephole a known pattern. |
| if (!DisableConvertEscapeToNoEscapeSwitchEnumPeephole && |
| trySwitchEnumPeephole(Cvt)) { |
| Changed |= true; |
| continue; |
| } |
| |
| if (tryExtendLifetimeToLastUse(Cvt, MemoizedQueries)) { |
| Changed |= true; |
| continue; |
| } |
| |
| // Otherwise, extend the lifetime of the operand to the end of the |
| // function. |
| extendLifetimeToEndOfFunction(Fn, Cvt); |
| Changed |= true; |
| } |
| } |
| return Changed; |
| } |
| |
| /// Fix-up the lifetime of the escaping closure argument of |
| /// convert_escape_to_noescape [not_guaranteed] instructions. |
| /// |
| /// convert_escape_to_noescape [not_guaranteed] assume that someone guarantees |
| /// the lifetime of the operand for the duration of the trivial closure result. |
| /// SILGen does not guarantee this for '[not_guaranteed]' instructions so we |
| /// ensure it here. |
| namespace { |
| class ClosureLifetimeFixup : public SILFunctionTransform { |
| |
| /// The entry point to the transformation. |
| void run() override { |
| // Don't rerun diagnostics on deserialized functions. |
| if (getFunction()->wasDeserializedCanonical()) |
| return; |
| |
| // Fixup convert_escape_to_noescape [not_guaranteed] and |
| // copy_block_without_escaping instructions. |
| if (fixupClosureLifetimes(*getFunction())) |
| invalidateAnalysis(SILAnalysis::InvalidationKind::FunctionBody); |
| LLVM_DEBUG(getFunction()->verify()); |
| |
| } |
| |
| }; |
| } // end anonymous namespace |
| |
| SILTransform *swift::createClosureLifetimeFixup() { |
| return new ClosureLifetimeFixup(); |
| } |