Skip to content

Commit 36fc503

Browse files
committed
[closure-lifetime-fixup] Use the SSAUpdater rather than memory to extend the lifetime to the end of the function.
This beyond being slightly cleaner ensures that we do not try to promote in PredictableMemOpts any loads from the alloc_stack. Doing this would force us to insert a copy in ossa which then would break is_escaping_closure even when we don't escape. rdar://problem/46188001
1 parent dc8239f commit 36fc503

File tree

4 files changed

+380
-122
lines changed

4 files changed

+380
-122
lines changed

lib/SILOptimizer/Mandatory/ClosureLifetimeFixup.cpp

Lines changed: 226 additions & 79 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
#include "swift/SILOptimizer/PassManager/Transforms.h"
2222
#include "swift/SILOptimizer/Utils/CFG.h"
2323
#include "swift/SILOptimizer/Utils/Local.h"
24+
#include "swift/SILOptimizer/Utils/SILSSAUpdater.h"
2425
#include "swift/SILOptimizer/Utils/StackNesting.h"
2526

2627
#include "llvm/Support/CommandLine.h"
@@ -88,8 +89,33 @@ static SILInstruction *getDeinitSafeClosureDestructionPoint(TermInst *Term) {
8889
return Term;
8990
}
9091

92+
static void findReachableExitBlocks(SILInstruction *i,
93+
SmallVectorImpl<SILBasicBlock *> &result) {
94+
SmallVector<SILBasicBlock *, 32> worklist;
95+
SmallPtrSet<SILBasicBlock *, 8> visitedBlocks;
96+
97+
visitedBlocks.insert(i->getParent());
98+
worklist.push_back(i->getParent());
99+
100+
while (!worklist.empty()) {
101+
auto *bb = worklist.pop_back_val();
102+
if (bb->getTerminator()->isFunctionExiting()) {
103+
result.push_back(bb);
104+
continue;
105+
}
106+
copy_if(bb->getSuccessorBlocks(), std::back_inserter(worklist),
107+
[&](SILBasicBlock *bb) { return visitedBlocks.insert(bb).second; });
108+
}
109+
}
110+
91111
/// Extend the lifetime of the convert_escape_to_noescape's operand to the end
92112
/// of the function.
113+
///
114+
/// NOTE: Since we are lifetime extending a copy that we have introduced, we do
115+
/// not need to consider destroy_value emitted by SILGen unlike
116+
/// copy_block_without_escaping which consumes its sentinel parameter. Unlike
117+
/// that case where we have to consider that destroy_value, we have a simpler
118+
/// time here.
93119
static void extendLifetimeToEndOfFunction(SILFunction &Fn,
94120
ConvertEscapeToNoEscapeInst *Cvt) {
95121
auto EscapingClosure = Cvt->getOperand();
@@ -104,40 +130,78 @@ static void extendLifetimeToEndOfFunction(SILFunction &Fn,
104130
Cvt->eraseFromParent();
105131
Cvt = NewCvt;
106132

107-
// Create an alloc_stack Optional<() -> ()> at the beginning of the function.
108-
AllocStackInst *Slot;
109-
auto &Context = Cvt->getModule().getASTContext();
110-
{
111-
SILBuilderWithScope B(Fn.getEntryBlock()->begin());
112-
Slot = B.createAllocStack(loc, OptionalEscapingClosureTy);
113-
auto *NoneDecl = Context.getOptionalNoneDecl();
114-
// Store None to it.
115-
B.createStore(
116-
loc, B.createEnum(loc, SILValue(), NoneDecl, OptionalEscapingClosureTy),
117-
Slot, StoreOwnershipQualifier::Init);
118-
}
119-
// Insert a copy before the convert_escape_to_noescape and store it to the
120-
// alloc_stack location.
121-
{
133+
// If our Cvt is in the initial block, we do not need to use the SSA updater
134+
// since we know Cvt can not be in a loop and must dominate all exits
135+
// (*). Just insert a copy of the escaping closure at the Cvt and destroys at
136+
// the exit blocks of the function.
137+
//
138+
// (*) In fact we can't use the SILSSAUpdater::GetValueInMiddleOfBlock.
139+
if (Cvt->getParent() == Cvt->getFunction()->getEntryBlock()) {
122140
SILBuilderWithScope B(Cvt);
123-
auto *SomeDecl = Context.getOptionalSomeDecl();
124-
B.createDestroyAddr(loc, Slot);
125-
auto ClosureCopy = B.createCopyValue(loc, EscapingClosure);
126-
B.createStore(
127-
loc,
128-
B.createEnum(loc, ClosureCopy, SomeDecl, OptionalEscapingClosureTy),
129-
Slot, StoreOwnershipQualifier::Init);
141+
CopyValueInst *InnerCVI = B.createCopyValue(loc, EscapingClosure);
142+
SmallVector<SILBasicBlock *, 4> ExitingBlocks;
143+
Fn.findExitingBlocks(ExitingBlocks);
144+
145+
for (auto *Exit : ExitingBlocks) {
146+
auto *Term = Exit->getTerminator();
147+
auto *SafeClosureDestructionPt =
148+
getDeinitSafeClosureDestructionPoint(Term);
149+
SILBuilderWithScope B(SafeClosureDestructionPt);
150+
B.createDestroyValue(loc, InnerCVI);
151+
}
152+
return;
130153
}
131-
// Insert destroys at the function exits.
154+
155+
// Ok. At this point we know that Cvt is not in the entry block... so we can
156+
// use SILSSAUpdater::GetValueInMiddleOfBlock() to extend the object's
157+
// lifetime respecting loops.
158+
SILSSAUpdater Updater(Cvt->getModule());
159+
Updater.Initialize(OptionalEscapingClosureTy);
160+
161+
// Create an Optional<() -> ()>.none in the entry block of the function and
162+
// add it as an available value to the SSAUpdater.
163+
//
164+
// Since we know that Cvt is not in the entry block and this must be, we know
165+
// that it is safe to use the SSAUpdater's getValueInMiddleOfBlock with this
166+
// value.
167+
Updater.AddAvailableValue(Fn.getEntryBlock(), [&]() -> SILValue {
168+
SILBuilderWithScope B(Fn.getEntryBlock()->begin());
169+
return B.createOptionalNone(loc, OptionalEscapingClosureTy);
170+
}());
171+
172+
// Create a copy of the convert_escape_to_no_escape and add it as an available
173+
// value to the SSA updater.
174+
//
175+
// NOTE: The SSAUpdater does not support providing multiple values in the same
176+
// block without extra work. So the fact that Cvt is not in the entry block
177+
// means that we don't have to worry about overwriting the .none value.
178+
auto *CVI = [&]() -> CopyValueInst * {
179+
SILBuilderWithScope B(Cvt);
180+
CopyValueInst *InnerCVI = B.createCopyValue(loc, EscapingClosure);
181+
Updater.AddAvailableValue(
182+
Cvt->getParent(),
183+
B.createOptionalSome(loc, InnerCVI, OptionalEscapingClosureTy));
184+
return InnerCVI;
185+
}();
186+
187+
// Then we use the SSA updater to insert a destroy_value before the cvt and at
188+
// the reachable exit blocks.
132189
SmallVector<SILBasicBlock *, 4> ExitingBlocks;
133-
Fn.findExitingBlocks(ExitingBlocks);
190+
findReachableExitBlocks(Cvt, ExitingBlocks);
191+
192+
{
193+
// Before the copy value, insert an extra destroy_value to handle
194+
// loops. Since we used our enum value this is safe.
195+
SILBuilderWithScope B(CVI);
196+
B.createDestroyValue(loc,
197+
Updater.GetValueInMiddleOfBlock(CVI->getParent()));
198+
}
199+
134200
for (auto *Exit : ExitingBlocks) {
135201
auto *Term = Exit->getTerminator();
136202
auto *SafeClosureDestructionPt = getDeinitSafeClosureDestructionPoint(Term);
137203
SILBuilderWithScope B(SafeClosureDestructionPt);
138-
B.createDestroyAddr(loc, Slot);
139-
SILBuilderWithScope B2(Term);
140-
B2.createDeallocStack(loc, Slot);
204+
B.createDestroyValue(loc, Updater.GetValueAtEndOfBlock(Exit));
141205
}
142206
}
143207

@@ -538,88 +602,171 @@ static SILInstruction *getOnlyDestroy(CopyBlockWithoutEscapingInst *CB) {
538602
/// %e = is_escaping %closure
539603
/// cond_fail %e
540604
/// destroy_value %closure
541-
static bool fixupCopyBlockWithoutEscaping(CopyBlockWithoutEscapingInst *CB) {
542-
SmallVector<SILInstruction *, 4> LifetimeEndPoints;
543-
605+
static bool fixupCopyBlockWithoutEscaping(CopyBlockWithoutEscapingInst *CB,
606+
bool &modifiedCFG) {
544607
// Find the end of the lifetime of the copy_block_without_escaping
545608
// instruction.
546609
auto &Fn = *CB->getFunction();
610+
611+
// If we find a single destroy, this destroy is going to be a destroy that may
612+
// be in the same block as CB. It is important that we make sure that the
613+
// destroy is in a different block than CB or any terminating blocks to ensure
614+
// that we can use the SSAUpdater if needed.
547615
auto *SingleDestroy = getOnlyDestroy(CB);
548-
SmallVector<SILBasicBlock *, 4> ExitingBlocks;
549-
Fn.findExitingBlocks(ExitingBlocks);
550-
if (SingleDestroy) {
551-
LifetimeEndPoints.push_back(
552-
&*std::next(SILBasicBlock::iterator(SingleDestroy)));
553-
} else {
554-
// Otherwise, conservatively insert verification at the end of the function.
555-
for (auto *Exit : ExitingBlocks)
556-
LifetimeEndPoints.push_back(Exit->getTerminator());
616+
if (SingleDestroy && SingleDestroy->getParent() == CB->getParent()) {
617+
modifiedCFG = true;
618+
{
619+
SILBuilderWithScope B(SingleDestroy);
620+
splitBasicBlockAndBranch(B, SingleDestroy, nullptr, nullptr);
621+
}
622+
623+
{
624+
SILBuilderWithScope B(SingleDestroy);
625+
auto *Term = SingleDestroy->getParent()->getTerminator();
626+
if (Term->isFunctionExiting()) {
627+
splitBasicBlockAndBranch(B, &*std::next(SingleDestroy->getIterator()),
628+
nullptr, nullptr);
629+
}
630+
}
557631
}
558632

559633
auto SentinelClosure = CB->getClosure();
560634
auto Loc = CB->getLoc();
561635

636+
// At this point, we transform our copy_block_without_escaping into a
637+
// copy_block. This has a few important implications:
638+
//
639+
// 1. copy_block_without_escaping takes the sentinel value at +1. We will need
640+
// to balance that +1.
641+
// 2. The destroy_value associated with the copy_block_without_escaping will
642+
// be on the copy_block value.
562643
SILBuilderWithScope B(CB);
563644
auto *NewCB = B.createCopyBlock(Loc, CB->getBlock());
564645
CB->replaceAllUsesWith(NewCB);
565646
CB->eraseFromParent();
566647

567-
// Create an stack slot for the closure sentinel and store the sentinel (or
568-
// none on other paths.
569648
auto generatedLoc = RegularLocation::getAutoGeneratedLocation();
570-
AllocStackInst *Slot;
571-
auto &Context = NewCB->getModule().getASTContext();
649+
650+
// If CB is in the entry block, we know that our definition of SentinelClosure
651+
// must be as well. Thus we know that we do not need to worry about loops or
652+
// dominance issues and can just insert destroy_values for the sentinel at the
653+
// lifetime end points.
654+
if (NewCB->getParent() == NewCB->getFunction()->getEntryBlock()) {
655+
// Our single destroy must not be in the entry block since if so, we would
656+
// have inserted an edge to appease the SSA updater.
657+
if (SingleDestroy) {
658+
SILBuilderWithScope B(std::next(SingleDestroy->getIterator()));
659+
SILValue V = SentinelClosure;
660+
SILValue isEscaping = B.createIsEscapingClosure(
661+
Loc, V, IsEscapingClosureInst::ObjCEscaping);
662+
B.createCondFail(Loc, isEscaping);
663+
B.createDestroyValue(Loc, V);
664+
return true;
665+
}
666+
667+
// If we couldn't find a specific destroy_value, lifetime extend to the end
668+
// of the function.
669+
SmallVector<SILBasicBlock *, 4> ExitingBlocks;
670+
Fn.findExitingBlocks(ExitingBlocks);
671+
for (auto *Block : ExitingBlocks) {
672+
SILBuilderWithScope B(Block->getTerminator());
673+
SILValue V = SentinelClosure;
674+
SILValue isEscaping = B.createIsEscapingClosure(
675+
Loc, V, IsEscapingClosureInst::ObjCEscaping);
676+
B.createCondFail(Loc, isEscaping);
677+
B.createDestroyValue(Loc, V);
678+
}
679+
680+
return true;
681+
}
682+
683+
// Otherwise, we need to be more careful since we can have loops and may not
684+
// transitively dominate all uses of the closure. So we:
685+
//
686+
// 1. Create an Optional<T>.none at the entry.
687+
// 2. Create a destroy_value(val), val = Optional<T>.some(sentinel) in the cvt
688+
// block.
689+
// 3. Create a destroy_value at all exits of the value.
690+
//
691+
// and then use the SSAUpdater to ensure that we handle loops correctly.
572692
auto OptionalEscapingClosureTy =
573693
SILType::getOptionalType(SentinelClosure->getType());
574-
auto *NoneDecl = Context.getOptionalNoneDecl();
694+
695+
SILSSAUpdater Updater(Fn.getModule());
696+
Updater.Initialize(OptionalEscapingClosureTy);
697+
698+
// Create the Optional.none as the beginning available value.
575699
{
576700
SILBuilderWithScope B(Fn.getEntryBlock()->begin());
577-
Slot = B.createAllocStack(generatedLoc, OptionalEscapingClosureTy);
578-
// Store None to it.
579-
B.createStore(generatedLoc,
580-
B.createEnum(generatedLoc, SILValue(), NoneDecl,
581-
OptionalEscapingClosureTy),
582-
Slot, StoreOwnershipQualifier::Init);
701+
Updater.AddAvailableValue(
702+
Fn.getEntryBlock(),
703+
B.createOptionalNone(generatedLoc, OptionalEscapingClosureTy));
583704
}
584-
{
705+
706+
// Then create the Optional.some(closure sentinel).
707+
//
708+
// NOTE: We return the appropriate insertion point to insert the destroy_value
709+
// before the value (to ensure we handle loops). We need to get all available
710+
// values first though.
711+
auto *InitialValue = [&]() -> EnumInst * {
585712
SILBuilderWithScope B(NewCB);
586-
// Store the closure sentinel (the copy_block_without_escaping closure
713+
// Create the closure sentinel (the copy_block_without_escaping closure
587714
// operand consumed at +1, so we don't need a copy) to it.
588-
B.createDestroyAddr(generatedLoc, Slot); // We could be in a loop.
589-
auto *SomeDecl = Context.getOptionalSomeDecl();
590-
B.createStore(generatedLoc,
591-
B.createEnum(generatedLoc, SentinelClosure, SomeDecl,
592-
OptionalEscapingClosureTy),
593-
Slot, StoreOwnershipQualifier::Init);
715+
auto *Result = B.createOptionalSome(generatedLoc, SentinelClosure,
716+
OptionalEscapingClosureTy);
717+
Updater.AddAvailableValue(Result->getParent(), Result);
718+
return Result;
719+
}();
720+
721+
// If we had a single destroy, creating a .none after it and add that as a
722+
// value to the SSA updater.
723+
if (SingleDestroy) {
724+
SILBuilderWithScope B(std::next(SingleDestroy->getIterator()));
725+
auto *Result =
726+
B.createOptionalNone(generatedLoc, OptionalEscapingClosureTy);
727+
Updater.AddAvailableValue(Result->getParent(), Result);
594728
}
595729

596-
for (auto LifetimeEndPoint : LifetimeEndPoints) {
597-
SILBuilderWithScope B(LifetimeEndPoint);
598-
SILValue isEscaping;
599-
B.emitScopedBorrowOperation(generatedLoc, Slot, [&](SILValue value) {
600-
isEscaping = B.createIsEscapingClosure(
601-
Loc, value, IsEscapingClosureInst::ObjCEscaping);
602-
});
730+
// Now that we have all of our available values, insert a destroy_value before
731+
// the initial Optional.some value using the SSA updater to ensure that we
732+
// handle loops correctly.
733+
{
734+
SILBuilderWithScope B(InitialValue);
735+
B.createDestroyValue(generatedLoc, Updater.GetValueInMiddleOfBlock(
736+
InitialValue->getParent()));
737+
}
738+
739+
// And insert an is_escaping_closure, cond_fail, destroy_value at each of the
740+
// lifetime end points. This ensures we do not expand our lifetime too much.
741+
if (SingleDestroy) {
742+
SILBuilderWithScope B(std::next(SingleDestroy->getIterator()));
743+
SILValue V = Updater.GetValueInMiddleOfBlock(SingleDestroy->getParent());
744+
SILValue isEscaping =
745+
B.createIsEscapingClosure(Loc, V, IsEscapingClosureInst::ObjCEscaping);
603746
B.createCondFail(Loc, isEscaping);
604-
B.createDestroyAddr(generatedLoc, Slot);
605-
// Store None to it.
606-
B.createStore(generatedLoc,
607-
B.createEnum(generatedLoc, SILValue(), NoneDecl,
608-
OptionalEscapingClosureTy),
609-
Slot, StoreOwnershipQualifier::Init);
747+
B.createDestroyValue(Loc, V);
610748
}
611749

612-
// Insert the dealloc_stack in all exiting blocks.
613-
for (auto *ExitBlock: ExitingBlocks) {
614-
auto *Terminator = ExitBlock->getTerminator();
615-
SILBuilderWithScope B(Terminator);
616-
B.createDeallocStack(generatedLoc, Slot);
750+
// Then to be careful with regards to loops, insert at each of the destroy
751+
// blocks destroy_value to ensure that we obey ownership invariants.
752+
{
753+
SmallVector<SILBasicBlock *, 4> ExitingBlocks;
754+
findReachableExitBlocks(NewCB, ExitingBlocks);
755+
756+
for (auto *Exit : ExitingBlocks) {
757+
auto *Term = Exit->getTerminator();
758+
auto *SafeClosureDestructionPt =
759+
getDeinitSafeClosureDestructionPoint(Term);
760+
SILBuilderWithScope B(SafeClosureDestructionPt);
761+
B.createDestroyValue(generatedLoc, Updater.GetValueInMiddleOfBlock(Exit));
762+
}
617763
}
618764

619765
return true;
620766
}
621767

622-
static bool fixupClosureLifetimes(SILFunction &Fn, bool &checkStackNesting) {
768+
static bool fixupClosureLifetimes(SILFunction &Fn, bool &checkStackNesting,
769+
bool &modifiedCFG) {
623770
bool Changed = false;
624771

625772
// tryExtendLifetimeToLastUse uses a cache of recursive instruction use
@@ -634,7 +781,7 @@ static bool fixupClosureLifetimes(SILFunction &Fn, bool &checkStackNesting) {
634781

635782
// Handle, copy_block_without_escaping instructions.
636783
if (auto *CB = dyn_cast<CopyBlockWithoutEscapingInst>(Inst)) {
637-
Changed |= fixupCopyBlockWithoutEscaping(CB);
784+
Changed |= fixupCopyBlockWithoutEscaping(CB, modifiedCFG);
638785
continue;
639786
}
640787

@@ -688,7 +835,7 @@ class ClosureLifetimeFixup : public SILFunctionTransform {
688835
bool checkStackNesting = false;
689836
bool modifiedCFG = false;
690837

691-
if (fixupClosureLifetimes(*getFunction(), checkStackNesting)) {
838+
if (fixupClosureLifetimes(*getFunction(), checkStackNesting, modifiedCFG)) {
692839
if (checkStackNesting){
693840
StackNesting SN;
694841
modifiedCFG =

0 commit comments

Comments
 (0)