blob: f00f8ec72cc787ca7ac9fd69d532875a12e84d22 [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/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(), 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) {
// 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();
// 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(), 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) {
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(), 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);
SILValue isEscaping;
B.emitScopedBorrowOperation(generatedLoc, Slot, [&](SILValue value) {
isEscaping = B.createIsEscapingClosure(
Loc, value, 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();
}