Skip to content

Commit 23f79f7

Browse files
authored
Merge pull request swiftlang#22901 from gottesmm/pr-3ccb97d3295cc1c1a850d2e03daab424fcbdfcef
[closure-lifetime-fixup] Use the SSAUpdater rather than memory to ext…
2 parents 3a21da9 + 36fc503 commit 23f79f7

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)