Files
swift-mirror/lib/SILOptimizer/Mandatory/ClosureLifetimeFixup.cpp

1540 lines
58 KiB
C++

//===--- 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/Basic/Assertions.h"
#include "swift/Basic/Defer.h"
#include "swift/SIL/BasicBlockDatastructures.h"
#include "swift/SIL/DebugUtils.h"
#include "swift/SIL/InstructionUtils.h"
#include "swift/SIL/PrunedLiveness.h"
#include "swift/SIL/SILArgument.h"
#include "swift/SIL/SILBuilder.h"
#include "swift/SIL/SILInstruction.h"
#include "swift/SIL/SILValue.h"
#include "swift/SILOptimizer/Analysis/BasicCalleeAnalysis.h"
#include "swift/SILOptimizer/Analysis/DeadEndBlocksAnalysis.h"
#include "swift/SILOptimizer/PassManager/Passes.h"
#include "swift/SILOptimizer/PassManager/Transforms.h"
#include "swift/SILOptimizer/Utils/BasicBlockOptUtils.h"
#include "swift/SILOptimizer/Utils/CFGOptUtils.h"
#include "swift/SILOptimizer/Utils/InstOptUtils.h"
#include "swift/SILOptimizer/Utils/OwnershipOptUtils.h"
#include "swift/SILOptimizer/Utils/SILSSAUpdater.h"
#include "swift/SILOptimizer/Utils/StackNesting.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);
llvm::cl::opt<bool> DisableCopyEliminationOfCopyableCapture(
"sil-disable-copy-elimination-of-copyable-closure-capture",
llvm::cl::init(false),
llvm::cl::desc("Don't eliminate copy_addr of Copyable closure captures "
"inserted by SILGen"));
using namespace swift;
/// Given an optional diamond, return the bottom of the diamond.
///
/// That is given that sei is in bb0,
///
/// /---> bb1 ---\
/// / \
/// bb0 ---> bb3
/// \ /
/// \---> bb2 ---/
///
/// this routine will return bb3.
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(SILBasicBlock *bb) {
for (auto &i : llvm::reverse(*bb)) {
if (auto *endLifetime = dyn_cast<EndLifetimeInst>(&i)) {
auto *superInstance = endLifetime->getOperand()->getDefiningInstruction();
assert(superInstance && "Expected an instruction");
return superInstance;
}
}
return bb->getTerminator();
}
static void findReachableExitBlocks(SILInstruction *i,
SmallVectorImpl<SILBasicBlock *> &result) {
BasicBlockWorklist worklist(i->getParent());
while (SILBasicBlock *bb = worklist.pop()) {
if (bb->getTerminator()->isFunctionExiting()) {
result.push_back(bb);
continue;
}
for (SILBasicBlock *succ : bb->getSuccessors()) {
worklist.pushIfNotVisited(succ);
}
}
}
/// We use this to ensure that we properly handle recursive cases by revisiting
/// phi nodes that we are tracking. This just makes it easier to reproduce in a
/// test case.
static llvm::cl::opt<bool> ReverseInitialWorklist(
"sil-closure-lifetime-fixup-reverse-phi-order", llvm::cl::init(false),
llvm::cl::desc(
"Reverse the order in which we visit phis for testing purposes"),
llvm::cl::Hidden);
// Finally, we need to prune phis inserted by the SSA updater that
// only take the .none from the entry block. This means that they are
// not actually reachable from the .some() so we know that we do not
// need to lifetime extend there at all. As an additional benefit, we
// eliminate the need to balance these arguments to satisfy the
// ownership verifier. This occurs since arguments are a place in SIL
// where the trivialness of an enums case is erased.
static void
cleanupDeadTrivialPhiArgs(SILValue initialValue,
SmallVectorImpl<SILPhiArgument *> &insertedPhis) {
// Just for testing purposes.
if (ReverseInitialWorklist) {
std::reverse(insertedPhis.begin(), insertedPhis.end());
}
SmallVector<SILArgument *, 8> worklist(insertedPhis.begin(),
insertedPhis.end());
sortUnique(insertedPhis);
SmallVector<SILValue, 8> incomingValues;
while (!worklist.empty()) {
// Clear the incoming values array after each iteration.
SWIFT_DEFER { incomingValues.clear(); };
auto *phi = worklist.pop_back_val();
{
auto it = lower_bound(insertedPhis, phi);
if (it == insertedPhis.end() || *it != phi)
continue;
}
// TODO: When we split true phi arguments from transformational terminators,
// this will always succeed and the assert can go away.
bool foundPhiValues = phi->getIncomingPhiValues(incomingValues);
(void)foundPhiValues;
assert(foundPhiValues && "Should always have 'true' phi arguments since "
"these were inserted by the SSA updater.");
if (llvm::any_of(incomingValues,
[&](SILValue v) { return v != initialValue; }))
continue;
// Remove it from our insertedPhis list to prevent us from re-visiting this.
{
auto it = lower_bound(insertedPhis, phi);
assert((it != insertedPhis.end() && *it == phi) &&
"Should have found the phi");
insertedPhis.erase(it);
}
// See if any of our users are branch or cond_br. If so, we may have
// exposed additional unneeded phis. Add it back to the worklist in such a
// case.
for (auto *op : phi->getUses()) {
auto *user = op->getUser();
if (!isa<BranchInst>(user) && !isa<CondBranchInst>(user))
continue;
auto *termInst = cast<TermInst>(user);
for (auto succBlockArgList : termInst->getSuccessorBlockArgumentLists()) {
llvm::copy_if(succBlockArgList, std::back_inserter(worklist),
[&](SILArgument *succArg) -> bool {
auto it = lower_bound(insertedPhis, succArg);
return it != insertedPhis.end() && *it == succArg;
});
}
}
// Then RAUW the phi with the entryBlockOptionalNone and erase the
// argument.
phi->replaceAllUsesWith(initialValue);
erasePhiArgument(phi->getParent(), phi->getIndex(),
/*cleanupDeadPhiOp*/ false);
}
}
/// Extend the lifetime of the convert_escape_to_noescape's operand to the end
/// of the function.
/// Create a copy of the escaping closure operand and end its lifetime at
/// function exits. Since, the cvt may not be dominating function exits, we
/// need to create an optional and use the SSAUpdater to extend the lifetime. In
/// order to prevent the optional being optimized away, create a borrow scope
/// and insert a mark_dependence of the non escaping closure on the borrow.
/// NOTE: Since we are lifetime extending a copy that we have introduced, we do
/// not need to consider destroy_value emitted by SILGen unlike
/// copy_block_without_escaping which consumes its sentinel parameter. Unlike
/// that case where we have to consider that destroy_value, we have a simpler
/// time here.
static void extendLifetimeToEndOfFunction(SILFunction &fn,
ConvertEscapeToNoEscapeInst *cvt,
SILSSAUpdater &updater) {
auto escapingClosure = cvt->getOperand();
auto escapingClosureTy = escapingClosure->getType();
auto optionalEscapingClosureTy = SILType::getOptionalType(escapingClosureTy);
auto loc = RegularLocation::getAutoGeneratedLocation();
SmallVector<SILBasicBlock *, 4> exitingBlocks;
fn.findExitingBlocks(exitingBlocks);
auto createLifetimeEnd = [](SILLocation loc, SILInstruction *insertPt,
SILValue value) {
SILBuilderWithScope builder(insertPt);
if (value->getOwnershipKind() == OwnershipKind::Owned) {
builder.emitDestroyOperation(loc, value);
return;
}
builder.emitEndBorrowOperation(loc, value);
};
auto createLifetimeEndAtFunctionExits =
[&](std::function<SILValue(SILBasicBlock *)> getValue) {
for (auto *block : exitingBlocks) {
auto *safeDestructionPoint =
getDeinitSafeClosureDestructionPoint(block);
createLifetimeEnd(loc, safeDestructionPoint, getValue(block));
}
};
// If our cvt is in the initial block, we do not need to use the SSA updater
// since we know cvt cannot be in a loop and must dominate all exits
// (*). Just insert a copy of the escaping closure at the cvt and destroys at
// the exit blocks of the function.
//
// (*) In fact we can't use the SILSSAUpdater::GetValueInMiddleOfBlock.
if (cvt->getParent() == cvt->getFunction()->getEntryBlock()) {
auto *copy = SILBuilderWithScope(cvt).createCopyValue(loc, escapingClosure);
cvt->setLifetimeGuaranteed();
cvt->setOperand(copy);
createLifetimeEndAtFunctionExits([&copy](SILBasicBlock *) { return copy; });
return;
}
// Create a copy of the convert_escape_to_no_escape.
// NOTE: The SSAUpdater does not support providing multiple values in the same
// block without extra work. So the fact that cvt is not in the entry block
// means that we don't have to worry about overwriting the .none value.
auto *copy = SILBuilderWithScope(cvt).createCopyValue(loc, escapingClosure);
cvt->setLifetimeGuaranteed();
cvt->setOperand(copy);
// Create an optional some to extend the lifetime of copy until function
// exits.
SILBuilderWithScope lifetimeExtendBuilder(std::next(cvt->getIterator()));
auto *optionalSome = lifetimeExtendBuilder.createOptionalSome(
loc, copy, optionalEscapingClosureTy);
// Create a borrow scope and a mark_dependence to prevent the enum being
// optimized away.
auto *borrow = lifetimeExtendBuilder.createBeginBorrow(loc, optionalSome);
auto *mdi =
lifetimeExtendBuilder.createMarkDependence(loc, cvt, borrow,
MarkDependenceKind::Escaping);
// Replace all uses of the non escaping closure with mark_dependence
SmallVector<Operand *, 4> convertUses;
for (auto *cvtUse : cvt->getUses()) {
convertUses.push_back(cvtUse);
}
for (auto *cvtUse : convertUses) {
auto *cvtUser = cvtUse->getUser();
if (cvtUser == mdi)
continue;
cvtUser->setOperand(cvtUse->getOperandNumber(), mdi);
}
auto fixupSILForLifetimeExtension = [&](SILValue value, SILValue entryValue) {
// Use SSAUpdater to find insertion points for lifetime ends.
updater.initialize(value->getFunction(), optionalEscapingClosureTy,
value->getOwnershipKind());
SmallVector<SILPhiArgument *, 8> insertedPhis;
updater.setInsertedPhis(&insertedPhis);
updater.addAvailableValue(fn.getEntryBlock(), entryValue);
updater.addAvailableValue(value->getParentBlock(), value);
{
// Since value maybe in a loop, insert an extra lifetime end. Since we
// used our enum value, this is safe.
SILValue midValue =
updater.getValueInMiddleOfBlock(value->getParentBlock());
createLifetimeEnd(loc, cvt, midValue);
}
// Insert lifetime ends.
createLifetimeEndAtFunctionExits([&updater](SILBasicBlock *block) {
return updater.getValueAtEndOfBlock(block);
});
// Prune the phis inserted by the SSA updater that only take
// the .none from the entry block.
// TODO: Should we sort inserted phis before or after we initialize
// the worklist or maybe backwards? We should investigate how the
// SSA updater adds phi nodes to this list to resolve this question.
cleanupDeadTrivialPhiArgs(entryValue, insertedPhis);
};
// Create an optional none at the function entry.
auto *optionalNone = SILBuilderWithScope(fn.getEntryBlock()->begin())
.createOptionalNone(loc, optionalEscapingClosureTy);
auto *borrowNone = SILBuilderWithScope(optionalNone->getNextInstruction())
.createBeginBorrow(loc, optionalNone);
// Use the SSAUpdater to create lifetime ends for the copy and the borrow.
fixupSILForLifetimeExtension(borrow, borrowNone);
fixupSILForLifetimeExtension(optionalSome, optionalNone);
}
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;
};
auto getSingleNonDebugNonRefCountUser =
[](SILValue v) -> SILInstruction* {
SILInstruction *singleNonDebugNonRefCountUser = nullptr;
for (auto *use : getNonDebugUses(v)) {
auto *user = use->getUser();
if (onlyAffectsRefCount(user))
continue;
if (isa<EndBorrowInst>(user))
continue;
if (singleNonDebugNonRefCountUser) {
return nullptr;
}
singleNonDebugNonRefCountUser = user;
}
return singleNonDebugNonRefCountUser;
};
// If we have a convert_function, just look at its user.
if (auto *cvt = dyn_cast<ConvertFunctionInst>(inst))
return memoizeResult(inst, lookThroughRebastractionUsers(
getSingleNonDebugNonRefCountUser(cvt), memoized));
if (auto *cvt = dyn_cast<ConvertEscapeToNoEscapeInst>(inst))
return memoizeResult(inst, lookThroughRebastractionUsers(
getSingleNonDebugNonRefCountUser(cvt), memoized));
// If we have a partial_apply user look at its single (non release) user.
if (auto *pa = dyn_cast<PartialApplyInst>(inst))
return memoizeResult(inst, lookThroughRebastractionUsers(
getSingleNonDebugNonRefCountUser(pa), memoized));
// TODO: If the single user is a borrow, then generally the lifetime of that
// borrow ought to delineate the lifetime of the closure. But some codegen
// patterns in SILGen will try to notionally lifetime-extend the value by
// copying it and putting the lifetime on the copy. So look at the single
// user of the borrow, if any, to determine the lifetime this should have.
if (auto borrow = dyn_cast<BeginBorrowInst>(inst)) {
return memoizeResult(inst, lookThroughRebastractionUsers(
getSingleNonDebugNonRefCountUser(borrow), memoized));
}
return inst;
}
/// Insert a mark_dependence for any non-trivial argument of a partial_apply.
static SILValue insertMarkDependenceForCapturedArguments(PartialApplyInst *pai,
SILBuilder &b) {
SILValue curr(pai);
// Mark dependence on all non-trivial arguments that weren't borrowed.
for (auto &arg : pai->getArgumentOperands()) {
if (isa<BeginBorrowInst>(arg.get())
|| arg.get()->getType().isTrivial(*pai->getFunction()))
continue;
if (auto *m = dyn_cast<MoveOnlyWrapperToCopyableValueInst>(arg.get()))
if (m->hasGuaranteedInitialKind())
continue;
curr = b.createMarkDependence(pai->getLoc(), curr, arg.get(),
MarkDependenceKind::NonEscaping);
}
return curr;
}
/// Returns the (single) "endAsyncLetLifetime" builtin if \p startAsyncLet is a
/// "startAsyncLetWithLocalBuffer" builtin.
static BuiltinInst *getEndAsyncLet(BuiltinInst *startAsyncLet) {
if (startAsyncLet->getBuiltinKind() != BuiltinValueKind::StartAsyncLetWithLocalBuffer)
return nullptr;
BuiltinInst *endAsyncLet = nullptr;
for (Operand *op : startAsyncLet->getUses()) {
auto *endBI = dyn_cast<BuiltinInst>(op->getUser());
if (endBI && endBI->getBuiltinKind() == BuiltinValueKind::EndAsyncLetLifetime) {
// At this stage of the pipeline, it's always the case that a
// startAsyncLet has an endAsyncLet: that's how SILGen generates it.
// Just to be on the safe side, do this check.
if (endAsyncLet)
return nullptr;
endAsyncLet = endBI;
}
}
return endAsyncLet;
}
/// Call the \p insertFn with a builder at all insertion points after
/// a closure is used by \p closureUser.
static void insertAfterClosureUser(SILInstruction *closureUser,
function_ref<void(SILBuilder &)> insertFn) {
{
SILInstruction *userForBorrow = closureUser;
if (auto *m = dyn_cast<MoveOnlyWrapperToCopyableValueInst>(userForBorrow))
if (m->hasGuaranteedInitialKind())
if (auto *svi = dyn_cast<SingleValueInstruction>(m->getOperand()))
userForBorrow = svi;
if (auto *beginBorrow = dyn_cast<BeginBorrowInst>(userForBorrow)) {
// Insert everywhere after the borrow is ended.
SmallVector<EndBorrowInst *, 4> endBorrows;
for (auto eb : beginBorrow->getEndBorrows()) {
endBorrows.push_back(eb);
}
for (auto eb : endBorrows) {
SILBuilderWithScope builder(std::next(eb->getIterator()));
insertFn(builder);
}
return;
}
}
if (auto *startAsyncLet = dyn_cast<BuiltinInst>(closureUser)) {
BuiltinInst *endAsyncLet = getEndAsyncLet(startAsyncLet);
if (!endAsyncLet)
return;
SILBuilderWithScope builder(std::next(endAsyncLet->getIterator()));
insertFn(builder);
return;
}
FullApplySite fas = FullApplySite::isa(closureUser);
assert(fas);
fas.insertAfterApplication(insertFn);
}
static SILValue skipConvert(SILValue v) {
auto *cvt = dyn_cast<ConvertFunctionInst>(v);
if (!cvt)
return v;
auto *pa = dyn_cast<PartialApplyInst>(cvt->getOperand());
if (!pa || !pa->hasOneUse())
return v;
return pa;
}
static SILAnalysis::InvalidationKind
analysisInvalidationKind(const bool &modifiedCFG) {
return modifiedCFG ? SILAnalysis::InvalidationKind::FunctionBody
: SILAnalysis::InvalidationKind::CallsAndInstructions;
}
/// Find the stack closure's lifetime ends. This should be indicated either by
/// direct destruction of the closure after its application, or the destruction
/// of its consuming use, which should be either another function conversion
/// or a partial_apply into a closure that will also be imminently transformed
/// into a stack partial apply. The lifetime of the closure should not escape
/// the current function or we wouldn't be able to embark on this transform.
static void
collectStackClosureLifetimeEnds(SmallVectorImpl<SILInstruction *> &lifetimeEnds,
SILValue v) {
for (Operand *consume : v->getConsumingUses()) {
SILInstruction *consumer = consume->getUser();
if (isa<DestroyValueInst>(consumer)) {
lifetimeEnds.push_back(consumer);
continue;
}
if (auto pa = dyn_cast<PartialApplyInst>(consumer)) {
// The closure may be captured into another partial_apply (usually
// a reabstraction thunk, but possibly a nested closure-in-closure).
// This other partial_apply ought to be imminently changing into
// a nonescaping closure as well, so we want the end of the
// `convert_escape_to_noescape` operation's lifetime rather than the
// original escaping closure's.
//
// Any partial_apply already converted to a stack closure should have
// also been converted to borrowing its captures.
assert(!pa->isOnStack());
SILValue singlePAUser = pa;
do {
SILInstruction *nextUser = nullptr;
for (auto use : singlePAUser->getUses()) {
if (isa<DestroyValueInst>(use->getUser())) {
continue;
}
assert(!nextUser && "more than one non-destroying use?!");
nextUser = use->getUser();
}
assert(nextUser && nextUser->getNumResults() == 1
&& "partial_apply capturing a nonescaping closure that isn't"
"itself nonescaping?!");
singlePAUser = nextUser->getResult(0);
} while (!isa<ConvertEscapeToNoEscapeInst>(singlePAUser));
auto convert = cast<ConvertEscapeToNoEscapeInst>(singlePAUser);
collectStackClosureLifetimeEnds(lifetimeEnds, convert);
continue;
}
// There shouldn't be any other consuming uses of the value that aren't
// forwarding.
assert(consumer->hasResults());
for (auto result : consumer->getResults()) {
collectStackClosureLifetimeEnds(lifetimeEnds, result);
}
}
}
static bool lookThroughMarkDependenceChainForValue(MarkDependenceInst *mark,
PartialApplyInst *pai) {
if (mark->getValue() == pai) {
return true;
}
auto *markChain = dyn_cast<MarkDependenceInst>(mark->getValue());
if (!markChain) {
return false;
}
return lookThroughMarkDependenceChainForValue(markChain, pai);
}
/// Rewrite a partial_apply convert_escape_to_noescape sequence with a single
/// apply/try_apply user to a partial_apply [stack] terminated with a
/// dealloc_stack placed after the apply.
///
/// %p = partial_apply %f(%a, %b)
/// %ne = convert_escape_to_noescape %p
/// apply %f2(%p)
/// destroy_value %p
///
/// =>
///
/// %ab = begin_borrow %a
/// %bb = begin_borrow %b
/// %p = partial_apply [stack] %f(%aa, %bb)
/// apply %f2(%p)
/// destroy_value %p
/// end_borrow %bb
/// end_borrow %aa
static SILValue tryRewriteToPartialApplyStack(
ConvertEscapeToNoEscapeInst *cvt, SILInstruction *closureUser,
DominanceAnalysis *dominanceAnalysis, InstructionDeleter &deleter,
llvm::DenseMap<SILInstruction *, SILInstruction *> &memoized,
ReachableBlocks const &reachableBlocks, const bool &modifiedCFG) {
auto *origPA = dyn_cast<PartialApplyInst>(skipConvert(cvt->getOperand()));
if (!origPA)
return SILValue();
auto *convertOrPartialApply = cast<SingleValueInstruction>(origPA);
if (cvt->getOperand() != origPA)
convertOrPartialApply = cast<ConvertFunctionInst>(cvt->getOperand());
// Whenever we delete an instruction advance the iterator and remove the
// instruction from the memoized map.
auto saveDeleteInst = [&](SILInstruction *i) {
memoized.erase(i);
deleter.forceDelete(i);
};
// Look for a single non ref count user of the partial_apply.
SmallVector<SILInstruction *, 8> refCountInsts;
SILInstruction *singleNonDebugNonRefCountUser = nullptr;
for (auto *use : getNonDebugUses(convertOrPartialApply)) {
auto *user = use->getUser();
if (onlyAffectsRefCount(user)) {
refCountInsts.push_back(user);
continue;
}
if (singleNonDebugNonRefCountUser)
return SILValue();
singleNonDebugNonRefCountUser = user;
}
SILBuilderWithScope b(cvt);
// Remove the original destroy of the partial_apply, if any, since the
// nonescaping closure's lifetime becomes the lifetime of the new
// partial_apply.
if (auto destroy = convertOrPartialApply->getSingleUserOfType<DestroyValueInst>()) {
saveDeleteInst(destroy);
}
// Borrow the arguments that need borrowing.
SmallVector<MoveOnlyWrapperToCopyableValueInst *, 8>
noImplicitCopyWrapperToDelete;
SmallVector<SILValue, 8> args;
for (Operand &arg : origPA->getArgumentOperands()) {
auto argTy = arg.get()->getType();
if (!argTy.isAddress() && !argTy.isTrivial(*cvt->getFunction())) {
SILValue argValue = arg.get();
bool foundNoImplicitCopy = false;
if (auto *mmci = dyn_cast<MoveOnlyWrapperToCopyableValueInst>(argValue)) {
if (mmci->hasOwnedInitialKind() && mmci->hasOneUse()) {
foundNoImplicitCopy = true;
argValue = mmci->getOperand();
noImplicitCopyWrapperToDelete.push_back(mmci);
}
}
SILValue borrow = b.createBeginBorrow(origPA->getLoc(), argValue);
if (foundNoImplicitCopy)
borrow = b.createGuaranteedMoveOnlyWrapperToCopyableValue(
origPA->getLoc(), borrow);
args.push_back(borrow);
} else {
args.push_back(arg.get());
}
}
// The convert_escape_to_noescape is the only user of the partial_apply.
// Convert to a partial_apply [stack].
auto newPA = b.createPartialApply(
origPA->getLoc(), origPA->getCallee(), origPA->getSubstitutionMap(), args,
origPA->getCalleeConvention(), origPA->getResultIsolation(),
PartialApplyInst::OnStackKind::OnStack);
// Insert mark_dependence for any non-trivial address operands to the
// partial_apply.
auto closure = insertMarkDependenceForCapturedArguments(newPA, b);
SILValue closureOp = closure;
// Optionally, replace the convert_function instruction.
if (auto *convert = dyn_cast<ConvertFunctionInst>(convertOrPartialApply)) {
/* DEBUG
llvm::errs() << "=== replacing conversion\n";
convert->dumpInContext();
*/
auto origTy = convert->getType().castTo<SILFunctionType>();
auto origWithNoEscape = SILType::getPrimitiveObjectType(
origTy->getWithExtInfo(origTy->getExtInfo().withNoEscape()));
closureOp = b.createConvertFunction(convert->getLoc(), closure,
origWithNoEscape, false);
/* DEBUG
llvm::errs() << "--- with\n";
closureOp->dumpInContext();
*/
}
// Replace the convert_escape_to_noescape uses with the new
// partial_apply [stack].
cvt->replaceAllUsesWith(closureOp);
saveDeleteInst(cvt);
// Delete the ref count operations on the original partial_apply.
for (auto *refInst : refCountInsts)
saveDeleteInst(refInst);
convertOrPartialApply->replaceAllUsesWith(newPA);
if (convertOrPartialApply != origPA)
saveDeleteInst(convertOrPartialApply);
saveDeleteInst(origPA);
// Delete the mmci of the origPA.
while (!noImplicitCopyWrapperToDelete.empty())
saveDeleteInst(noImplicitCopyWrapperToDelete.pop_back_val());
ApplySite site(newPA);
SILFunctionConventions calleeConv(site.getSubstCalleeType(),
newPA->getModule());
// Since we create temporary allocation for in_guaranteed captures during SILGen,
// the dealloc_stack of it can occur before the apply due to conversion scopes.
// When we insert destroy_addr of the in_guaranteed capture after the apply,
// we may end up with a situation when the dealloc_stack occurs before the destroy_addr.
// The code below proactively removes the dealloc_stack of in_guaranteed capture,
// so that it can be reinserted at the correct place after the destroy_addr below.
for (auto &arg : newPA->getArgumentOperands()) {
unsigned calleeArgumentIndex = site.getCalleeArgIndex(arg);
assert(calleeArgumentIndex >= calleeConv.getSILArgIndexOfFirstParam());
auto paramInfo = calleeConv.getParamInfoForSILArg(calleeArgumentIndex);
if (paramInfo.getConvention() == ParameterConvention::Indirect_In_Guaranteed) {
SILValue argValue = arg.get();
if (auto *mmci = dyn_cast<MoveOnlyWrapperToCopyableAddrInst>(argValue))
argValue = mmci->getOperand();
// go over all the dealloc_stack, remove it
SmallVector<Operand *, 16> Uses(argValue->getUses());
for (auto use : Uses) {
if (auto *deallocInst = dyn_cast<DeallocStackInst>(use->getUser()))
deleter.forceDelete(deallocInst);
}
}
}
// End borrows and insert destroys of arguments after the stack closure's
// lifetime ends.
SmallVector<SILInstruction *, 4> lifetimeEnds;
collectStackClosureLifetimeEnds(lifetimeEnds, closureOp);
// For address-only captures, see if we can eliminate the copy
// that SILGen emitted to allow the original partial_apply to take ownership.
// We do this here because otherwise the move checker will see the copy as an
// attempt to consume the value, which we don't want.
SmallVector<SILBasicBlock *, 8> discoveredBlocks;
SSAPrunedLiveness closureLiveness(cvt->getFunction(), &discoveredBlocks);
closureLiveness.initializeDef(closureOp);
llvm::SmallSetVector<SILValue, 4> borrowedOriginals;
unsigned appliedArgStartIdx =
newPA->getOrigCalleeType()->getNumParameters() - newPA->getNumArguments();
for (unsigned i : indices(newPA->getArgumentOperands())) {
auto &arg = newPA->getArgumentOperands()[i];
SILValue copy = arg.get();
// The temporary should be a local stack allocation.
LLVM_DEBUG(llvm::dbgs() << "considering whether to eliminate copy of capture\n";
copy->printInContext(llvm::dbgs());
llvm::dbgs() << "\n");
auto stack = dyn_cast<AllocStackInst>(copy);
if (!stack) {
LLVM_DEBUG(llvm::dbgs() << "-- not an alloc_stack\n");
continue;
}
if (DisableCopyEliminationOfCopyableCapture) {
if (!copy->getType().isMoveOnly()) {
LLVM_DEBUG(llvm::dbgs() << "-- not move-only\n");
continue;
}
}
// Is the capture a borrow?
auto paramIndex = i + appliedArgStartIdx;
auto param = newPA->getOrigCalleeType()->getParameters()[paramIndex];
LLVM_DEBUG(param.print(llvm::dbgs());
llvm::dbgs() << '\n');
if (!param.isIndirectInGuaranteed()) {
LLVM_DEBUG(llvm::dbgs() << "-- not an in_guaranteed parameter\n";
newPA->getOrigCalleeType()->getParameters()[paramIndex]
.print(llvm::dbgs());
llvm::dbgs() << "\n");
continue;
}
// It needs to have been initialized by copying from somewhere else.
CopyAddrInst *initialization = nullptr;
MarkDependenceInst *markDep = nullptr;
for (auto *use : stack->getUses()) {
auto *user = use->getUser();
// Since we removed the `dealloc_stack`s from the capture arguments,
// the only uses of this stack slot should be the initialization, the
// partial application, and possibly a mark_dependence from the
// buffer to the partial application.
if (use->getUser() == newPA) {
continue;
}
if (auto mark = dyn_cast<MarkDependenceInst>(use->getUser())) {
// When we insert mark_dependence for non-trivial address operands, we
// emit a chain that looks like:
// %md = mark_dependence %pai on %0
// %md2 = mark_dependence %md on %1
// to tie all of those operands together on the same partial_apply.
// Check if we're marking dependence on this stack slot for the current
// partial_apply or it's chain of mark_dependences.
if (!lookThroughMarkDependenceChainForValue(mark, newPA) ||
mark->getBase() != stack) {
LLVM_DEBUG(llvm::dbgs() << "-- had unexpected mark_dependence use\n";
use->getUser()->print(llvm::dbgs()); llvm::dbgs() << "\n");
initialization = nullptr;
break;
}
markDep = mark;
continue;
}
// If we saw more than just the initialization, this isn't a pattern we
// recognize.
if (initialization) {
LLVM_DEBUG(llvm::dbgs()
<< "-- had non-initialization, non-partial-apply use\n";
use->getUser()->print(llvm::dbgs()); llvm::dbgs() << "\n");
initialization = nullptr;
break;
}
if (auto possibleInit = dyn_cast<CopyAddrInst>(use->getUser())) {
// Should copy the source and initialize the destination.
if (possibleInit->isTakeOfSrc() ||
!possibleInit->isInitializationOfDest()) {
LLVM_DEBUG(
llvm::dbgs()
<< "-- had non-initialization, non-partial-apply use\n";
use->getUser()->print(llvm::dbgs()); llvm::dbgs() << "\n");
break;
}
// This is the initialization if there are no other uses.
initialization = possibleInit;
continue;
}
if (isa<DebugValueInst>(user) || isa<DestroyAddrInst>(user) ||
isa<DeallocStackInst>(user)) {
continue;
}
LLVM_DEBUG(llvm::dbgs() << "-- unrecognized use\n");
// Reset initialization on an unrecognized use
initialization = nullptr;
break;
}
if (!initialization) {
LLVM_DEBUG(llvm::dbgs() << "-- failed to find single initializing use\n");
continue;
}
// The source should have no writes in the duration of the partial_apply's
// liveness.
auto orig = initialization->getSrc();
LLVM_DEBUG(llvm::dbgs() << "++ found original:\n";
orig->print(llvm::dbgs());
llvm::dbgs() << "\n");
bool origIsUnmodifiedDuringClosureLifetime = true;
class OrigUnmodifiedDuringClosureLifetimeWalker
: public TransitiveAddressWalker<
OrigUnmodifiedDuringClosureLifetimeWalker> {
SSAPrunedLiveness &closureLiveness;
bool &origIsUnmodifiedDuringClosureLifetime;
public:
OrigUnmodifiedDuringClosureLifetimeWalker(
SSAPrunedLiveness &closureLiveness,
bool &origIsUnmodifiedDuringClosureLifetime)
: closureLiveness(closureLiveness),
origIsUnmodifiedDuringClosureLifetime(
origIsUnmodifiedDuringClosureLifetime) {}
bool visitUse(Operand *origUse) {
LLVM_DEBUG(llvm::dbgs() << "looking at use\n";
origUse->getUser()->printInContext(llvm::dbgs());
llvm::dbgs() << "\n");
// If the user doesn't write to memory, then it's harmless.
if (!origUse->getUser()->mayWriteToMemory()) {
return true;
}
if (closureLiveness.isWithinBoundary(origUse->getUser(),
/*deadEndBlocks=*/nullptr)) {
origIsUnmodifiedDuringClosureLifetime = false;
LLVM_DEBUG(llvm::dbgs() << "-- original has other possibly writing "
"use during closure lifetime\n";
origUse->getUser()->print(llvm::dbgs());
llvm::dbgs() << "\n");
return false;
}
return true;
}
};
OrigUnmodifiedDuringClosureLifetimeWalker origUseWalker(
closureLiveness, origIsUnmodifiedDuringClosureLifetime);
switch (origUseWalker.walk(orig)) {
case AddressUseKind::NonEscaping:
case AddressUseKind::Dependent:
// Dependent uses are ignored because they cannot modify the original.
break;
case AddressUseKind::PointerEscape:
case AddressUseKind::Unknown:
continue;
}
if (!origIsUnmodifiedDuringClosureLifetime) {
continue;
}
// OK, we can use the original. Eliminate the copy and replace it with the
// original.
LLVM_DEBUG(llvm::dbgs() << "++ replacing with original!\n");
arg.set(orig);
if (markDep) {
markDep->setBase(orig);
}
initialization->eraseFromParent();
stack->eraseFromParent();
borrowedOriginals.insert(orig);
}
/* DEBUG
llvm::errs() << "=== found lifetime ends for\n";
closureOp->dump();
llvm::errs() << "--- at\n";
*/
for (auto destroy : lifetimeEnds) {
/* DEBUG
destroy->dump();
*/
SILBuilderWithScope builder(std::next(destroy->getIterator()));
// This getCapturedArg hack attempts to perfectly compensate for all the
// other hacks involved in gathering new arguments above.
// argValue may be 'undef'
auto getArgToDestroy = [&](SILValue argValue) -> SILValue {
// A MoveOnlyWrapperToCopyableValueInst may produce a trivial value. Be
// careful not to emit an extra destroy of the original.
if (argValue->getType().isTrivial(destroy->getFunction()))
return SILValue();
// We may have inserted a new begin_borrow->moveonlywrapper_to_copyvalue
// when creating the new arguments. Now we need to end that borrow.
if (auto *m = dyn_cast<MoveOnlyWrapperToCopyableValueInst>(argValue))
if (m->hasGuaranteedInitialKind())
argValue = m->getOperand();
auto *argBorrow = dyn_cast<BeginBorrowInst>(argValue);
if (argBorrow) {
argValue = argBorrow->getOperand();
builder.createEndBorrow(newPA->getLoc(), argBorrow);
}
// Don't need to destroy if we borrowed in place .
return borrowedOriginals.count(argValue) ? SILValue() : argValue;
};
insertDestroyOfCapturedArguments(newPA, builder, getArgToDestroy,
newPA->getLoc());
}
/* DEBUG
llvm::errs() << "=== function after conversion to stack partial_apply of\n";
newPA->dump();
llvm::errs() << "---\n";
newPA->getFunction()->dump();
*/
// The CFG may have been modified during this run. If it was, the dominance
// analysis would no longer be valid. Invalidate it now if necessary,
// according to the kinds of changes that may have been made. Note that if
// the CFG hasn't been modified, this is a noop thanks to
// DominanceAnalysis::shouldInvalidate's definition.
dominanceAnalysis->invalidate(closureUser->getFunction(),
analysisInvalidationKind(modifiedCFG));
// Insert dealloc_stacks of any in_guaranteed captures.
// Don't run insertDeallocOfCapturedArguments if newPA is in an unreachable
// block insertDeallocOfCapturedArguments will run code that computes the DF
// for newPA that will loop infinitely.
if (!reachableBlocks.isReachable(newPA->getParent()))
return closureOp;
auto getAddressToDealloc = [&](SILValue argAddress) -> SILValue {
if (auto moveWrapper =
dyn_cast<MoveOnlyWrapperToCopyableAddrInst>(argAddress)) {
argAddress = moveWrapper->getOperand();
}
// Don't need to destroy if we borrowed in place .
return borrowedOriginals.count(argAddress) ? SILValue() : argAddress;
};
insertDeallocOfCapturedArguments(
newPA, dominanceAnalysis->get(closureUser->getFunction()),
getAddressToDealloc);
return closureOp;
}
static bool tryExtendLifetimeToLastUse(
ConvertEscapeToNoEscapeInst *cvt, DominanceAnalysis *dominanceAnalysis,
DeadEndBlocksAnalysis *deadEndBlocksAnalysis,
llvm::DenseMap<SILInstruction *, SILInstruction *> &memoized,
ReachableBlocks const &reachableBlocks, InstructionDeleter &deleter,
const bool &modifiedCFG) {
// If there is a single user, this is simple: extend the
// lifetime of the operand until the use ends.
auto *singleUser = lookThroughRebastractionUsers(cvt, memoized);
if (!singleUser)
return false;
// Handle apply instructions and startAsyncLet.
BuiltinInst *endAsyncLet = nullptr;
if (FullApplySite::isa(singleUser)) {
// TODO: Enable begin_apply/end_apply. It should work, but is not tested yet.
if (isa<BeginApplyInst>(singleUser))
return false;
} else if (auto *bi = dyn_cast<BuiltinInst>(singleUser)) {
endAsyncLet = getEndAsyncLet(bi);
if (!endAsyncLet)
return false;
} else if (!isa<BeginBorrowInst>(singleUser)) {
return false;
}
if (SILValue closureOp = tryRewriteToPartialApplyStack(
cvt, singleUser, dominanceAnalysis, deleter, memoized,
reachableBlocks, /*const*/ modifiedCFG)) {
if (endAsyncLet) {
// Add the closure as a second operand to the endAsyncLet builtin.
// This ensures that the closure arguments are kept alive until the
// endAsyncLet builtin.
assert(endAsyncLet->getNumOperands() == 1);
SILBuilderWithScope builder(endAsyncLet);
builder.createBuiltin(endAsyncLet->getLoc(), endAsyncLet->getName(),
endAsyncLet->getType(), endAsyncLet->getSubstitutions(),
{endAsyncLet->getOperand(0), closureOp});
deleter.forceDelete(endAsyncLet);
}
return true;
}
// Insert a copy at the convert_escape_to_noescape [not_guaranteed] and
// change the instruction to the guaranteed form.
auto escapingClosure = cvt->getOperand();
auto *closureCopy =
SILBuilderWithScope(cvt).createCopyValue(cvt->getLoc(), escapingClosure);
cvt->setLifetimeGuaranteed();
cvt->setOperand(closureCopy);
auto *function = cvt->getFunction();
// The CFG may have been modified during this run, which would have made
// dead-end blocks analysis invalid. Mark it invalid it now if that
// happened. If the CFG hasn't been modified, this is a noop thanks to
// DeadEndBlocksAnalysis::shouldInvalidate.
deadEndBlocksAnalysis->invalidate(function,
analysisInvalidationKind(modifiedCFG));
auto *deadEndBlocks = deadEndBlocksAnalysis->get(function);
insertAfterClosureUser(
singleUser, [closureCopy, deadEndBlocks](SILBuilder &builder) {
auto loc = RegularLocation(builder.getInsertionPointLoc());
auto isDeadEnd = IsDeadEnd_t(
deadEndBlocks->isDeadEnd(builder.getInsertionPoint()->getParent()));
builder.createDestroyValue(loc, closureCopy, DontPoisonRefs, isDeadEnd);
});
// Closure User may not be post-dominating the previously created copy_value.
// Create destroy_value at leaking blocks.
endLifetimeAtLeakingBlocks(closureCopy, {singleUser->getParent()}, deadEndBlocks);
/*
llvm::errs() << "after lifetime extension of\n";
escapingClosure->dump();
escapingClosure->getFunction()->dump();
*/
return true;
}
/// Ensure the lifetime of the closure across a two step optional conversion
/// from:
///
/// optional<@escaping () -> ()>
///
/// to:
///
/// optional<@noescape () -> ()>
///
/// to:
///
/// optional<@noescape @convention(block) () -> ()>
///
/// and all uses of the block. The pattern that we are looking for is:
///
/// switch_enum %optional_closure (1)
/// / \
/// %trivial_closure = CVT %closure nil (2)
/// \ /
/// switch_enum %optional_trivial_closure (3)
/// / \
/// %b = convertToBlock %trivial_closure nil (4)
/// \ /
/// ... uses of %optional_block ...
/// destroy_value %optional_block
///
/// where CVT is convert_escape_to_no_escape [not_guaranteed]. We assume that
/// the %optional_block is going through a conversion sequence in SILGen meaning
/// that we should only have a single destroy of the optional block.
///
/// NOTE: There is a *lifetime gap* during the usage of the trivial_closure!
/// This means we must be careful when lifetime extending. We can only assume
/// that the underlying closure is alive immediately at the CVT. So to perform
/// our lifetime extend, we do the following:
///
/// 1. We copy and borrow optional_closure, right before the switch_enum in
/// (1).
///
/// 2. We rewrite the convert_escape_to_no_escape guaranteed to use the copy
/// instead.
///
/// 3. To make sure that even after ossa is complete, we do not move any
/// destroys above the convert_escape_to_no_escape by putting a mark_dependence
/// on %closure
///
/// 4. We insert an end_borrow, destroy for the copy at the destroy of the
/// optional block.
static bool trySwitchEnumPeephole(ConvertEscapeToNoEscapeInst *cvt) {
auto *blockArg = dyn_cast<SILArgument>(cvt->getOperand());
if (!blockArg)
return false;
auto *convertSuccessorBlock = cvt->getParent()->getSingleSuccessorBlock();
if (!convertSuccessorBlock)
return false;
auto *predBB = cvt->getParent()->getSinglePredecessorBlock();
if (!predBB)
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 of the diamond succ 2's argument. This
// is going to be the place where we destroy the lifetime extending copy.
SILInstruction *onlyDestroy = [&]() -> SILInstruction * {
SILInstruction *lastDestroy = nullptr;
for (auto *use : diamondSucc2->getArgument(0)->getUses()) {
SILInstruction *user = use->getUser();
if (isa<ReleaseValueInst>(user) || isa<StrongReleaseInst>(user) ||
isa<DestroyValueInst>(user)) {
if (lastDestroy)
return nullptr;
lastDestroy = user;
}
}
return lastDestroy;
}();
if (!onlyDestroy)
return false;
// Extend the lifetime.
auto loc = RegularLocation::getAutoGeneratedLocation();
SILValue copy, borrow;
std::tie(copy, borrow) = ([&]() -> std::pair<SILValue, SILValue> {
SILBuilderWithScope builder(switchEnum1);
auto copy = builder.emitCopyValueOperation(loc, switchEnum1->getOperand());
auto borrow = builder.emitBeginBorrowOperation(loc, copy);
return {copy, borrow};
})(); // end std::tie(copy, borrow).
{
SILBuilderWithScope builder(cvt);
auto value = builder.emitExtractOptionalPayloadOperation(loc, borrow);
cvt->setOperand(value);
cvt->setLifetimeGuaranteed();
}
{
SILBuilderWithScope builder(onlyDestroy);
builder.emitEndBorrowOperation(loc, borrow);
builder.emitDestroyValueOperation(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 *asi = dyn_cast<AllocStackInst>(blockInit->getBlockStorage());
if (!asi)
return nullptr;
auto *dealloc = asi->getSingleDeallocStack();
if (!dealloc || dealloc->getParent() != onlyDestroy->getParent())
return nullptr;
// Return the later instruction.
for (auto it = SILBasicBlock::iterator(onlyDestroy),
ie = dealloc->getParent()->end();
it != ie; ++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,
InstructionDeleter &deleter,
bool &modifiedCFG) {
// Find the end of the lifetime of the copy_block_without_escaping
// instruction.
auto &fn = *cb->getFunction();
// If we find a single destroy, this destroy is going to be a destroy that may
// be in the same block as CB. It is important that we make sure that the
// destroy is in a different block than CB or any terminating blocks to ensure
// that we can use the SSAUpdater if needed.
auto *singleDestroy = getOnlyDestroy(cb);
if (singleDestroy && singleDestroy->getParent() == cb->getParent()) {
modifiedCFG = true;
{
SILBuilderWithScope b(singleDestroy);
splitBasicBlockAndBranch(b, singleDestroy, nullptr, nullptr);
}
{
SILBuilderWithScope b(singleDestroy);
auto *term = singleDestroy->getParent()->getTerminator();
if (term->isFunctionExiting()) {
splitBasicBlockAndBranch(b, &*std::next(singleDestroy->getIterator()),
nullptr, nullptr);
}
}
}
auto sentinelClosure = cb->getClosure();
auto loc = cb->getLoc();
// At this point, we transform our copy_block_without_escaping into a
// copy_block. This has a few important implications:
//
// 1. copy_block_without_escaping takes the sentinel value at +1. We will need
// to balance that +1.
// 2. The destroy_value associated with the copy_block_without_escaping will
// be on the copy_block value.
SILBuilderWithScope b(cb);
auto *newCB = b.createCopyBlock(loc, cb->getBlock());
cb->replaceAllUsesWith(newCB);
deleter.forceDelete(cb);
auto autoGenLoc = RegularLocation::getAutoGeneratedLocation();
// If CB is in the entry block, we know that our definition of SentinelClosure
// must be as well. Thus we know that we do not need to worry about loops or
// dominance issues and can just insert destroy_values for the sentinel at the
// lifetime end points.
if (newCB->getParent() == newCB->getFunction()->getEntryBlock()) {
// Our single destroy must not be in the entry block since if so, we would
// have inserted an edge to appease the SSA updater.
if (singleDestroy) {
SILBuilderWithScope b(std::next(singleDestroy->getIterator()));
SILValue v = sentinelClosure;
SILValue isEscaping = b.createDestroyNotEscapedClosure(
loc, v, DestroyNotEscapedClosureInst::ObjCEscaping);
b.createCondFail(loc, isEscaping, "non-escaping closure has escaped");
return true;
}
// If we couldn't find a specific destroy_value, lifetime extend to the end
// of the function.
SmallVector<SILBasicBlock *, 4> ExitingBlocks;
fn.findExitingBlocks(ExitingBlocks);
for (auto *Block : ExitingBlocks) {
SILBuilderWithScope B(Block->getTerminator());
SILValue V = sentinelClosure;
SILValue isEscaping = B.createDestroyNotEscapedClosure(
loc, V, DestroyNotEscapedClosureInst::ObjCEscaping);
B.createCondFail(loc, isEscaping, "non-escaping closure has escaped");
}
return true;
}
// Otherwise, we need to be more careful since we can have loops and may not
// transitively dominate all uses of the closure. So we:
//
// 1. Create an Optional<T>.none at the entry.
// 2. Create a destroy_value(val), val = Optional<T>.some(sentinel) in the cvt
// block.
// 3. Create a destroy_value at all exits of the value.
//
// and then use the SSAUpdater to ensure that we handle loops correctly.
auto optionalEscapingClosureTy =
SILType::getOptionalType(sentinelClosure->getType());
SmallVector<SILPhiArgument *, 8> insertedPhis;
SILSSAUpdater updater(&insertedPhis);
updater.initialize(&fn, optionalEscapingClosureTy,
fn.hasOwnership() ? OwnershipKind::Owned
: OwnershipKind::None);
// Create the Optional.none as the beginning available value.
SILValue entryBlockOptionalNone;
{
SILBuilderWithScope b(fn.getEntryBlock()->begin());
entryBlockOptionalNone =
b.createOptionalNone(autoGenLoc, optionalEscapingClosureTy);
updater.addAvailableValue(fn.getEntryBlock(), entryBlockOptionalNone);
}
assert(entryBlockOptionalNone);
// Then create the Optional.some(closure sentinel).
//
// NOTE: We return the appropriate insertion point to insert the destroy_value
// before the value (to ensure we handle loops). We need to get all available
// values first though.
auto *initialValue = [&]() -> EnumInst * {
SILBuilderWithScope b(newCB);
// Create the closure sentinel (the copy_block_without_escaping closure
// operand consumed at +1, so we don't need a copy) to it.
auto *result = b.createOptionalSome(autoGenLoc, sentinelClosure,
optionalEscapingClosureTy);
updater.addAvailableValue(result->getParent(), result);
return result;
}();
// If we had a single destroy, creating a .none after it and add that as a
// value to the SSA updater.
if (singleDestroy) {
SILBuilderWithScope b(std::next(singleDestroy->getIterator()));
auto *result = b.createOptionalNone(autoGenLoc, optionalEscapingClosureTy);
updater.addAvailableValue(result->getParent(), result);
}
// Now that we have all of our available values, insert a destroy_value before
// the initial Optional.some value using the SSA updater to ensure that we
// handle loops correctly.
{
SILValue v = updater.getValueInMiddleOfBlock(initialValue->getParent());
SILBuilderWithScope(initialValue).createDestroyValue(autoGenLoc, v);
}
// And insert an destroy_not_escaped_closure, cond_fail at each of the
// lifetime end points. This ensures we do not expand our lifetime too much.
if (singleDestroy) {
SILBuilderWithScope b(std::next(singleDestroy->getIterator()));
SILValue v = updater.getValueInMiddleOfBlock(singleDestroy->getParent());
SILValue isEscaping =
b.createDestroyNotEscapedClosure(loc, v, DestroyNotEscapedClosureInst::ObjCEscaping);
b.createCondFail(loc, isEscaping, "non-escaping closure has escaped");
}
// Then to be careful with regards to loops, insert at each of the destroy
// blocks destroy_value to ensure that we obey ownership invariants.
{
SmallVector<SILBasicBlock *, 4> exitingBlocks;
findReachableExitBlocks(newCB, exitingBlocks);
for (auto *block : exitingBlocks) {
auto *safeDestructionPt = getDeinitSafeClosureDestructionPoint(block);
SILValue v = updater.getValueAtEndOfBlock(block);
SILBuilderWithScope(safeDestructionPt).createDestroyValue(autoGenLoc, v);
}
}
// Finally, we need to prune phis inserted by the SSA updater that only take
// the .none from the entry block.
//
// TODO: Should we sort inserted phis before or after we initialize
// the worklist or maybe backwards? We should investigate how the
// SSA updater adds phi nodes to this list to resolve this question.
cleanupDeadTrivialPhiArgs(entryBlockOptionalNone, insertedPhis);
return true;
}
static bool fixupClosureLifetimes(SILFunction &fn,
DominanceAnalysis *dominanceAnalysis,
DeadEndBlocksAnalysis *deadEndBlocksAnalysis,
bool &checkStackNesting, bool &modifiedCFG) {
bool changed = false;
// tryExtendLifetimeToLastUse uses a cache of recursive instruction use
// queries.
llvm::DenseMap<SILInstruction *, SILInstruction *> memoizedQueries;
ReachableBlocks reachableBlocks(&fn);
reachableBlocks.compute();
for (auto &block : fn) {
SILSSAUpdater updater;
for (SILInstruction &inst : block.deletableInstructions()) {
// Handle, copy_block_without_escaping instructions.
if (auto *cb = dyn_cast<CopyBlockWithoutEscapingInst>(&inst)) {
if (fixupCopyBlockWithoutEscaping(cb, updater.getDeleter(), modifiedCFG)) {
changed = true;
}
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) {
if (trySwitchEnumPeephole(cvt)) {
changed = true;
continue;
}
}
if (tryExtendLifetimeToLastUse(cvt, dominanceAnalysis,
deadEndBlocksAnalysis, memoizedQueries,
reachableBlocks, updater.getDeleter(),
/*const*/ modifiedCFG)) {
changed = true;
checkStackNesting = true;
continue;
}
// Otherwise, extend the lifetime of the operand to the end of the
// function.
extendLifetimeToEndOfFunction(fn, cvt, updater);
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.
bool checkStackNesting = false;
bool modifiedCFG = false;
auto *dominanceAnalysis = PM->getAnalysis<DominanceAnalysis>();
auto *deadEndBlocksAnalysis = getAnalysis<DeadEndBlocksAnalysis>();
if (fixupClosureLifetimes(*getFunction(), dominanceAnalysis,
deadEndBlocksAnalysis, checkStackNesting,
modifiedCFG)) {
updateAllGuaranteedPhis(getPassManager(), getFunction());
if (checkStackNesting){
modifiedCFG |=
StackNesting::fixNesting(getFunction()) == StackNesting::Changes::CFG;
}
invalidateAnalysis(analysisInvalidationKind(modifiedCFG));
}
LLVM_DEBUG(getFunction()->verify(getAnalysis<BasicCalleeAnalysis>()->getCalleeCache()));
}
};
} // end anonymous namespace
SILTransform *swift::createClosureLifetimeFixup() {
return new ClosureLifetimeFixup();
}