Skip to content

Commit 8d204ad

Browse files
committed
ClosureLifetimeFixup : Fix bug in lifetime extension of non escaping closure
1 parent d6bb2c0 commit 8d204ad

File tree

3 files changed

+102
-79
lines changed

3 files changed

+102
-79
lines changed

lib/SILOptimizer/Mandatory/ClosureLifetimeFixup.cpp

Lines changed: 96 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -201,7 +201,12 @@ cleanupDeadTrivialPhiArgs(SILValue initialValue,
201201

202202
/// Extend the lifetime of the convert_escape_to_noescape's operand to the end
203203
/// of the function.
204-
///
204+
/// Create a copy of the escaping closure operand and end its lifetime at
205+
/// function exits. Since, the cvt may not be dominating function exits, we
206+
/// need to create an optional and use the SSAUpdater to extend the lifetime. In
207+
/// order to prevent the optional being optimized away, create a borrow scope
208+
/// and insert a mark_dependence of the non escaping closure on the borrow.
209+
205210
/// NOTE: Since we are lifetime extending a copy that we have introduced, we do
206211
/// not need to consider destroy_value emitted by SILGen unlike
207212
/// copy_block_without_escaping which consumes its sentinel parameter. Unlike
@@ -215,91 +220,108 @@ static void extendLifetimeToEndOfFunction(SILFunction &fn,
215220
auto optionalEscapingClosureTy = SILType::getOptionalType(escapingClosureTy);
216221
auto loc = RegularLocation::getAutoGeneratedLocation();
217222

218-
// If our Cvt is in the initial block, we do not need to use the SSA updater
219-
// since we know Cvt can not be in a loop and must dominate all exits
220-
// (*). Just insert a copy of the escaping closure at the Cvt and destroys at
223+
SmallVector<SILBasicBlock *, 4> exitingBlocks;
224+
fn.findExitingBlocks(exitingBlocks);
225+
226+
auto createLifetimeEnd = [](SILLocation loc, SILInstruction *insertPt,
227+
SILValue value) {
228+
SILBuilderWithScope builder(insertPt);
229+
if (value.getOwnershipKind() == OwnershipKind::Owned) {
230+
builder.emitDestroyOperation(loc, value);
231+
return;
232+
}
233+
builder.emitEndBorrowOperation(loc, value);
234+
};
235+
auto createLifetimeEndAtFunctionExits =
236+
[&](std::function<SILValue(SILBasicBlock *)> getValue) {
237+
for (auto *block : exitingBlocks) {
238+
auto *safeDestructionPoint =
239+
getDeinitSafeClosureDestructionPoint(block);
240+
createLifetimeEnd(loc, safeDestructionPoint, getValue(block));
241+
}
242+
};
243+
244+
// If our cvt is in the initial block, we do not need to use the SSA updater
245+
// since we know cvt cannot be in a loop and must dominate all exits
246+
// (*). Just insert a copy of the escaping closure at the cvt and destroys at
221247
// the exit blocks of the function.
222248
//
223249
// (*) In fact we can't use the SILSSAUpdater::GetValueInMiddleOfBlock.
224250
if (cvt->getParent() == cvt->getFunction()->getEntryBlock()) {
225-
auto *innerCVI =
226-
SILBuilderWithScope(cvt).createCopyValue(loc, escapingClosure);
251+
auto *copy = SILBuilderWithScope(cvt).createCopyValue(loc, escapingClosure);
227252
cvt->setLifetimeGuaranteed();
228-
cvt->setOperand(innerCVI);
229-
SmallVector<SILBasicBlock *, 4> exitingBlocks;
230-
fn.findExitingBlocks(exitingBlocks);
231-
232-
for (auto *block : exitingBlocks) {
233-
auto *safeDestructPoint = getDeinitSafeClosureDestructionPoint(block);
234-
SILBuilderWithScope(safeDestructPoint).createDestroyValue(loc, innerCVI);
235-
}
253+
cvt->setOperand(copy);
254+
createLifetimeEndAtFunctionExits([&copy](SILBasicBlock *) { return copy; });
236255
return;
237256
}
238257

239-
// Ok. At this point we know that Cvt is not in the entry block... so we can
240-
// use SILSSAUpdater::GetValueInMiddleOfBlock() to extend the object's
241-
// lifetime respecting loops.
242-
SmallVector<SILPhiArgument *, 8> insertedPhis;
243-
updater.setInsertedPhis(&insertedPhis);
244-
updater.initialize(optionalEscapingClosureTy, fn.hasOwnership()
245-
? OwnershipKind::Owned
246-
: OwnershipKind::None);
247-
248-
// Create an Optional<() -> ()>.none in the entry block of the function and
249-
// add it as an available value to the SSAUpdater.
250-
//
251-
// Since we know that Cvt is not in the entry block and this must be, we know
252-
// that it is safe to use the SSAUpdater's getValueInMiddleOfBlock with this
253-
// value.
254-
SILValue entryBlockOptionalNone = [&]() -> SILValue {
255-
SILBuilderWithScope b(fn.getEntryBlock()->begin());
256-
return b.createOptionalNone(loc, optionalEscapingClosureTy);
257-
}();
258-
updater.addAvailableValue(fn.getEntryBlock(), entryBlockOptionalNone);
259-
260-
// Create a copy of the convert_escape_to_no_escape and add it as an available
261-
// value to the SSA updater.
262-
//
258+
// Create a copy of the convert_escape_to_no_escape.
263259
// NOTE: The SSAUpdater does not support providing multiple values in the same
264-
// block without extra work. So the fact that Cvt is not in the entry block
260+
// block without extra work. So the fact that cvt is not in the entry block
265261
// means that we don't have to worry about overwriting the .none value.
266-
auto *cvi = [&]() -> CopyValueInst * {
267-
auto *innerCVI =
268-
SILBuilderWithScope(cvt).createCopyValue(loc, escapingClosure);
269-
cvt->setLifetimeGuaranteed();
270-
cvt->setOperand(innerCVI);
271-
SILBuilderWithScope b(std::next(cvt->getIterator()));
272-
updater.addAvailableValue(
273-
cvt->getParent(),
274-
b.createOptionalSome(loc, innerCVI, optionalEscapingClosureTy));
275-
return innerCVI;
276-
}();
277-
278-
// Then we use the SSA updater to insert a destroy_value before the cvt and at
279-
// the reachable exit blocks.
280-
SmallVector<SILBasicBlock *, 4> exitingBlocks;
281-
findReachableExitBlocks(cvt, exitingBlocks);
282-
283-
{
284-
// Before the copy value, insert an extra destroy_value to handle
285-
// loops. Since we used our enum value this is safe.
286-
SILValue v = updater.getValueInMiddleOfBlock(cvi->getParent());
287-
SILBuilderWithScope(cvi).createDestroyValue(loc, v);
262+
auto *copy = SILBuilderWithScope(cvt).createCopyValue(loc, escapingClosure);
263+
cvt->setLifetimeGuaranteed();
264+
cvt->setOperand(copy);
265+
266+
// Create an optional some to extend the lifetime of copy until function
267+
// exits.
268+
SILBuilderWithScope lifetimeExtendBuilder(std::next(cvt->getIterator()));
269+
auto *optionalSome = lifetimeExtendBuilder.createOptionalSome(
270+
loc, copy, optionalEscapingClosureTy);
271+
272+
// Create a borrow scope and a mark_dependence to prevent the enum being
273+
// optimized away.
274+
auto *borrow = lifetimeExtendBuilder.createBeginBorrow(loc, optionalSome);
275+
auto *mdi = lifetimeExtendBuilder.createMarkDependence(loc, cvt, borrow);
276+
277+
// Replace all uses of the non escaping closure with mark_dependence
278+
SmallVector<Operand *, 4> convertUses;
279+
for (auto *cvtUse : cvt->getUses()) {
280+
convertUses.push_back(cvtUse);
288281
}
289-
290-
for (auto *block : exitingBlocks) {
291-
auto *safeDestructionPt = getDeinitSafeClosureDestructionPoint(block);
292-
SILValue v = updater.getValueAtEndOfBlock(block);
293-
SILBuilderWithScope(safeDestructionPt).createDestroyValue(loc, v);
282+
for (auto *cvtUse : convertUses) {
283+
auto *cvtUser = cvtUse->getUser();
284+
if (cvtUser == mdi)
285+
continue;
286+
cvtUser->setOperand(cvtUse->getOperandNumber(), mdi);
294287
}
295288

296-
// Finally, we need to prune phis inserted by the SSA updater that only take
297-
// the .none from the entry block.
298-
//
299-
// TODO: Should we sort inserted phis before or after we initialize
300-
// the worklist or maybe backwards? We should investigate how the
301-
// SSA updater adds phi nodes to this list to resolve this question.
302-
cleanupDeadTrivialPhiArgs(entryBlockOptionalNone, insertedPhis);
289+
auto fixupSILForLifetimeExtension = [&](SILValue value) {
290+
// Use SSAUpdater to find insertion points for lifetime ends.
291+
updater.initialize(optionalEscapingClosureTy, value.getOwnershipKind());
292+
SmallVector<SILPhiArgument *, 8> insertedPhis;
293+
updater.setInsertedPhis(&insertedPhis);
294+
295+
// Create an optional none at the function entry.
296+
auto *optionalNone =
297+
SILBuilderWithScope(fn.getEntryBlock()->begin())
298+
.createOptionalNone(loc, optionalEscapingClosureTy);
299+
updater.addAvailableValue(fn.getEntryBlock(), optionalNone);
300+
updater.addAvailableValue(value->getParentBlock(), value);
301+
{
302+
// Since value maybe in a loop, insert an extra lifetime end. Since we
303+
// used our enum value, this is safe.
304+
SILValue midValue =
305+
updater.getValueInMiddleOfBlock(value->getParentBlock());
306+
createLifetimeEnd(loc, cvt, midValue);
307+
}
308+
309+
// Insert lifetime ends.
310+
createLifetimeEndAtFunctionExits([&updater](SILBasicBlock *block) {
311+
return updater.getValueAtEndOfBlock(block);
312+
});
313+
314+
// Prune the phis inserted by the SSA updater that only take
315+
// the .none from the entry block.
316+
// TODO: Should we sort inserted phis before or after we initialize
317+
// the worklist or maybe backwards? We should investigate how the
318+
// SSA updater adds phi nodes to this list to resolve this question.
319+
cleanupDeadTrivialPhiArgs(optionalNone, insertedPhis);
320+
};
321+
322+
// Use the SSAUpdater to create lifetime ends for the copy and the borrow.
323+
fixupSILForLifetimeExtension(borrow);
324+
fixupSILForLifetimeExtension(optionalSome);
303325
}
304326

305327
static SILInstruction *lookThroughRebastractionUsers(
@@ -979,8 +1001,8 @@ static bool fixupClosureLifetimes(SILFunction &fn, bool &checkStackNesting,
9791001

9801002
for (auto &block : fn) {
9811003
SILSSAUpdater updater;
982-
for (SILInstruction *inst : updater.getDeleter().updatingRange(&block)) {
9831004

1005+
for (SILInstruction *inst : updater.getDeleter().updatingRange(&block)) {
9841006
// Handle, copy_block_without_escaping instructions.
9851007
if (auto *cb = dyn_cast<CopyBlockWithoutEscapingInst>(inst)) {
9861008
if (fixupCopyBlockWithoutEscaping(cb, updater.getDeleter(), modifiedCFG)) {

lib/SILOptimizer/Utils/SILSSAUpdater.cpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -229,7 +229,7 @@ SILValue SILSSAUpdater::getValueInMiddleOfBlock(SILBasicBlock *block) {
229229
}
230230

231231
// Create a new phi node.
232-
SILPhiArgument *phiArg = block->createPhiArgument(type, OwnershipKind::Owned);
232+
SILPhiArgument *phiArg = block->createPhiArgument(type, ownershipKind);
233233
for (auto &pair : predVals) {
234234
addNewEdgeValueToBranch(pair.first->getTerminator(), block, pair.second,
235235
deleter);
@@ -341,6 +341,7 @@ class SSAUpdaterTraits<SILSSAUpdater> {
341341
auto *phiBlock = phi->getParent();
342342
size_t phiArgIndex = phi->getIndex();
343343
auto *ti = predBlock->getTerminator();
344+
344345
changeEdgeValue(ti, phiBlock, phiArgIndex, value);
345346
}
346347

test/SILOptimizer/definite-init-convert-to-escape.swift

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -93,13 +93,13 @@ public func returnOptionalEscape() -> (() ->())?
9393
// NOPEEPHOLE: switch_enum [[V1]] : $Optional<{{.*}}>, case #Optional.some!enumelt: [[SOME_BB:bb[0-9]+]], case #Optional.none!enumelt: [[NONE_BB:bb[0-9]+]]
9494
//
9595
// NOPEEPHOLE: [[SOME_BB]]([[V2:%.*]]: $@callee_guaranteed () -> ()):
96-
// NOPEEPHOLE-NEXT: release_value [[NONE_2]]
9796
// NOPEEPHOLE-NEXT: [[CVT:%.*]] = convert_escape_to_noescape [[V2]]
9897
// NOPEEPHOLE-NEXT: [[SOME:%.*]] = enum $Optional<{{.*}}>, #Optional.some!enumelt, [[V2]]
99-
// NOPEEPHOLE-NEXT: [[NOESCAPE_SOME:%.*]] = enum $Optional<{{.*}}>, #Optional.some!enumelt, [[CVT]]
100-
// NOPEEPHOLE-NEXT: br bb2([[NOESCAPE_SOME]] : $Optional<{{.*}}>, [[SOME]] :
98+
// NOPEEPHOLE-NEXT: [[MDI:%.*]] = mark_dependence [[CVT]] : $@noescape @callee_guaranteed () -> () on [[SOME]] : $Optional<@callee_guaranteed () -> ()>
99+
// NOPEEPHOLE-NEXT: [[NOESCAPE_SOME:%.*]] = enum $Optional<{{.*}}>, #Optional.some!enumelt, [[MDI]]
100+
// NOPEEPHOLE-NEXT: br bb2([[NOESCAPE_SOME]] : $Optional<{{.*}}>, [[SOME]] : $Optional<{{.*}}>, [[SOME]] : $Optional<{{.*}}>)
101101
//
102-
// NOPEEPHOLE: bb2([[NOESCAPE_SOME:%.*]] : $Optional<{{.*}}>, [[SOME:%.*]] :
102+
// NOPEEPHOLE: bb2([[NOESCAPE_SOME:%.*]] : $Optional<{{.*}}>, [[SOME1:%.*]] : $Optional<{{.*}}>, [[SOME:%.*]] : $Optional<{{.*}}>):
103103
// NOPEEPHOLE: switch_enum [[NOESCAPE_SOME]] : $Optional<{{.*}}>, case #Optional.some!enumelt: [[SOME_BB_2:bb[0-9]+]], case #Optional.none!enumelt: [[NONE_BB_2:bb[0-9]+]]
104104
//
105105
// NOPEEPHOLE: [[SOME_BB_2]](

0 commit comments

Comments
 (0)