blob: ff14e4a0f05ead56bc09691d7086599438ecb837 [file] [log] [blame]
//===--- 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/CFGOptUtils.h"
#include "swift/SILOptimizer/Utils/InstOptUtils.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);
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) {
SmallVector<SILBasicBlock *, 32> worklist;
SmallPtrSet<SILBasicBlock *, 8> visitedBlocks;
visitedBlocks.insert(i->getParent());
worklist.push_back(i->getParent());
while (!worklist.empty()) {
auto *bb = worklist.pop_back_val();
if (bb->getTerminator()->isFunctionExiting()) {
result.push_back(bb);
continue;
}
llvm::copy_if(bb->getSuccessorBlocks(), std::back_inserter(worklist),
[&](SILBasicBlock *bb) {
return visitedBlocks.insert(bb).second;
});
}
}
/// 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());
}
}
/// Extend the lifetime of the convert_escape_to_noescape's operand to the end
/// of the function.
///
/// 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) {
auto escapingClosure = cvt->getOperand();
auto escapingClosureTy = escapingClosure->getType();
auto optionalEscapingClosureTy = SILType::getOptionalType(escapingClosureTy);
auto loc = RegularLocation::getAutoGeneratedLocation();
// If our Cvt is in the initial block, we do not need to use the SSA updater
// since we know Cvt can not 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 *innerCVI =
SILBuilderWithScope(cvt).createCopyValue(loc, escapingClosure);
cvt->setLifetimeGuaranteed();
cvt->setOperand(innerCVI);
SmallVector<SILBasicBlock *, 4> exitingBlocks;
fn.findExitingBlocks(exitingBlocks);
for (auto *block : exitingBlocks) {
auto *safeDestructPoint = getDeinitSafeClosureDestructionPoint(block);
SILBuilderWithScope(safeDestructPoint).createDestroyValue(loc, innerCVI);
}
return;
}
// Ok. At this point we know that Cvt is not in the entry block... so we can
// use SILSSAUpdater::GetValueInMiddleOfBlock() to extend the object's
// lifetime respecting loops.
SmallVector<SILPhiArgument *, 8> insertedPhis;
SILSSAUpdater updater(&insertedPhis);
updater.initialize(optionalEscapingClosureTy);
// Create an Optional<() -> ()>.none in the entry block of the function and
// add it as an available value to the SSAUpdater.
//
// Since we know that Cvt is not in the entry block and this must be, we know
// that it is safe to use the SSAUpdater's getValueInMiddleOfBlock with this
// value.
SILValue entryBlockOptionalNone = [&]() -> SILValue {
SILBuilderWithScope b(fn.getEntryBlock()->begin());
return b.createOptionalNone(loc, optionalEscapingClosureTy);
}();
updater.addAvailableValue(fn.getEntryBlock(), entryBlockOptionalNone);
// Create a copy of the convert_escape_to_no_escape and add it as an available
// value to the SSA updater.
//
// 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 *cvi = [&]() -> CopyValueInst * {
auto *innerCVI =
SILBuilderWithScope(cvt).createCopyValue(loc, escapingClosure);
cvt->setLifetimeGuaranteed();
cvt->setOperand(innerCVI);
SILBuilderWithScope b(std::next(cvt->getIterator()));
updater.addAvailableValue(
cvt->getParent(),
b.createOptionalSome(loc, innerCVI, optionalEscapingClosureTy));
return innerCVI;
}();
// Then we use the SSA updater to insert a destroy_value before the cvt and at
// the reachable exit blocks.
SmallVector<SILBasicBlock *, 4> exitingBlocks;
findReachableExitBlocks(cvt, exitingBlocks);
{
// Before the copy value, insert an extra destroy_value to handle
// loops. Since we used our enum value this is safe.
SILValue v = updater.getValueInMiddleOfBlock(cvi->getParent());
SILBuilderWithScope(cvi).createDestroyValue(loc, v);
}
for (auto *block : exitingBlocks) {
auto *safeDestructionPt = getDeinitSafeClosureDestructionPoint(block);
SILValue v = updater.getValueAtEndOfBlock(block);
SILBuilderWithScope(safeDestructionPt).createDestroyValue(loc, 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);
}
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 (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));
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.
for (auto &arg : pai->getArgumentOperands()) {
if (arg.get()->getType().isTrivial(*pai->getFunction()))
continue;
curr = b.createMarkDependence(pai->getLoc(), curr, arg.get());
}
return curr;
}
/// 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
///
/// =>
///
/// %p = partial_apply [stack] %f(%a, %b)
/// %md = mark_dependence %p on %a
/// %md2 = mark_dependence %md on %b
/// apply %f2(%md2)
/// dealloc_stack %p
/// destroy_value %a
/// destroy_value %b
///
/// Note: If the rewrite succeeded we have inserted a dealloc_stack. This
/// dealloc_stack still needs to be balanced with other dealloc_stacks i.e the
/// caller needs to use the StackNesting utility to update the dealloc_stack
/// nesting.
static bool tryRewriteToPartialApplyStack(
SILLocation &loc, PartialApplyInst *origPA,
ConvertEscapeToNoEscapeInst *cvt, SILInstruction *singleApplyUser,
SILBasicBlock::iterator &advanceIfDelete,
llvm::DenseMap<SILInstruction *, SILInstruction *> &memoized) {
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) {
if (&*advanceIfDelete == i)
++advanceIfDelete;
memoized.erase(i);
i->eraseFromParent();
};
// 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 false;
singleNonDebugNonRefCountUser = user;
}
SILBuilderWithScope b(cvt);
// The convert_escape_to_noescape is the only user of the partial_apply.
// Convert to a partial_apply [stack].
SmallVector<SILValue, 8> args;
for (auto &arg : origPA->getArgumentOperands())
args.push_back(arg.get());
auto newPA = b.createPartialApply(
origPA->getLoc(), origPA->getCallee(), origPA->getSubstitutionMap(), args,
origPA->getType().getAs<SILFunctionType>()->getCalleeConvention(),
PartialApplyInst::OnStackKind::OnStack);
// Insert mark_dependence for any non-trivial operand to the partial_apply.
auto closure = insertMarkDependenceForCapturedArguments(newPA, b);
// Optionally, replace the convert_function instruction.
if (auto *convert = dyn_cast<ConvertFunctionInst>(convertOrPartialApply)) {
auto origTy = convert->getType().castTo<SILFunctionType>();
auto origWithNoEscape = SILType::getPrimitiveObjectType(
origTy->getWithExtInfo(origTy->getExtInfo().withNoEscape()));
closure = b.createConvertFunction(convert->getLoc(), closure,
origWithNoEscape, false);
convert->replaceAllUsesWith(closure);
}
// Replace the convert_escape_to_noescape uses with the new
// partial_apply [stack].
cvt->replaceAllUsesWith(closure);
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);
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) {
// go over all the dealloc_stack, remove it
SmallVector<Operand*, 16> Uses(arg.get()->getUses());
for (auto use : Uses)
if (auto *deallocInst = dyn_cast<DeallocStackInst>(use->getUser()))
deallocInst->eraseFromParent();
}
}
// Insert destroys of arguments after the apply and the dealloc_stack.
if (auto *apply = dyn_cast<ApplyInst>(singleApplyUser)) {
auto insertPt = std::next(SILBasicBlock::iterator(apply));
// Don't insert dealloc_stacks at unreachable.
if (isa<UnreachableInst>(*insertPt))
return true;
SILBuilderWithScope b3(insertPt);
b3.createDeallocStack(loc, newPA);
insertDestroyOfCapturedArguments(newPA, b3);
// dealloc_stack of the in_guaranteed capture is inserted
insertDeallocOfCapturedArguments(newPA, b3);
} else if (auto *tai = dyn_cast<TryApplyInst>(singleApplyUser)) {
for (auto *succBB : tai->getSuccessorBlocks()) {
SILBuilderWithScope b3(succBB->begin());
b3.createDeallocStack(loc, newPA);
insertDestroyOfCapturedArguments(newPA, b3);
// dealloc_stack of the in_guaranteed capture is inserted
insertDeallocOfCapturedArguments(newPA, b3);
}
} else {
llvm_unreachable("Unknown FullApplySite instruction kind");
}
return true;
}
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 bool tryExtendLifetimeToLastUse(
ConvertEscapeToNoEscapeInst *cvt,
llvm::DenseMap<SILInstruction *, SILInstruction *> &memoized,
SILBasicBlock::iterator &advanceIfDelete) {
// 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 (isa<BeginApplyInst>(singleApplyUser.getInstruction())) {
return false;
}
auto loc = RegularLocation::getAutoGeneratedLocation();
auto origPA = dyn_cast<PartialApplyInst>(skipConvert(cvt->getOperand()));
if (origPA && tryRewriteToPartialApplyStack(
loc, origPA, cvt, singleApplyUser.getInstruction(),
advanceIfDelete, memoized))
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(loc, escapingClosure);
cvt->setLifetimeGuaranteed();
cvt->setOperand(closureCopy);
// Insert a destroy after the apply.
if (auto *apply = dyn_cast<ApplyInst>(singleApplyUser.getInstruction())) {
auto insertPt = std::next(SILBasicBlock::iterator(apply));
SILBuilderWithScope(insertPt).createDestroyValue(loc, closureCopy);
} else if (auto *tai =
dyn_cast<TryApplyInst>(singleApplyUser.getInstruction())) {
for (auto *succBB : tai->getSuccessorBlocks()) {
SILBuilderWithScope(succBB->begin())
.createDestroyValue(loc, closureCopy);
}
} else {
llvm_unreachable("Unknown FullApplySite instruction kind");
}
return true;
}
return false;
}
/// 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,
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);
cb->eraseFromParent();
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.createIsEscapingClosure(
loc, v, IsEscapingClosureInst::ObjCEscaping);
b.createCondFail(loc, isEscaping, "non-escaping closure has escaped");
b.createDestroyValue(loc, v);
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.createIsEscapingClosure(
loc, V, IsEscapingClosureInst::ObjCEscaping);
B.createCondFail(loc, isEscaping, "non-escaping closure has escaped");
B.createDestroyValue(loc, V);
}
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(optionalEscapingClosureTy);
// 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 is_escaping_closure, cond_fail, destroy_value 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.createIsEscapingClosure(loc, v, IsEscapingClosureInst::ObjCEscaping);
b.createCondFail(loc, isEscaping, "non-escaping closure has escaped");
b.createDestroyValue(loc, v);
}
// 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, bool &checkStackNesting,
bool &modifiedCFG) {
bool changed = false;
// tryExtendLifetimeToLastUse uses a cache of recursive instruction use
// queries.
llvm::DenseMap<SILInstruction *, SILInstruction *> memoizedQueries;
for (auto &block : fn) {
auto i = block.begin();
while (i != block.end()) {
SILInstruction *inst = &*i;
++i;
// Handle, copy_block_without_escaping instructions.
if (auto *cb = dyn_cast<CopyBlockWithoutEscapingInst>(inst)) {
if (fixupCopyBlockWithoutEscaping(cb, 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, memoizedQueries, i)) {
changed = true;
checkStackNesting = 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.
bool checkStackNesting = false;
bool modifiedCFG = false;
if (fixupClosureLifetimes(*getFunction(), checkStackNesting, modifiedCFG)) {
if (checkStackNesting){
StackNesting sn;
modifiedCFG |=
sn.correctStackNesting(getFunction()) == StackNesting::Changes::CFG;
}
if (modifiedCFG)
invalidateAnalysis(SILAnalysis::InvalidationKind::FunctionBody);
else
invalidateAnalysis(SILAnalysis::InvalidationKind::CallsAndInstructions);
}
LLVM_DEBUG(getFunction()->verify());
}
};
} // end anonymous namespace
SILTransform *swift::createClosureLifetimeFixup() {
return new ClosureLifetimeFixup();
}