Skip to content

Commit e3f5de9

Browse files
committed
[Exclusivity] fix diagnostics for conditional noescape closures.
Add support to static diagnostics for tracking noescape closures through block arguments. Improve the noescape closure SIL verification logic to match. Cleanup noescape closure handling in both diagnostics and SIL verification to be more robust--they must perfectly match each other. Fixes <rdar://problem/42560459> [Exclusivity] Failure to statically diagnose a conflict when passing conditional noescape closures. Initially reported in [SR-8266] Compiler crash when checking exclusivity of inout alias. Example: struct S { var x: Int mutating func takeNoescapeClosure(_ f: ()->()) { f() } mutating func testNoescapePartialApplyPhiUse(z : Bool) { func f1() { x = 1 // expected-note {{conflicting access is here}} } func f2() { x = 1 // expected-note {{conflicting access is here}} } takeNoescapeClosure(z ? f1 : f2) // expected-error@-1 2 {{overlapping accesses to 'self', but modification requires exclusive access; consider copying to a local variable}} } }
1 parent a349133 commit e3f5de9

File tree

7 files changed

+398
-152
lines changed

7 files changed

+398
-152
lines changed

include/swift/SIL/InstructionUtils.h

Lines changed: 5 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -83,8 +83,8 @@ SILValue stripExpectIntrinsic(SILValue V);
8383
SILValue stripBorrow(SILValue V);
8484

8585
/// Return a non-null SingleValueInstruction if the given instruction merely
86-
/// copies a value, possibly changing its type or ownership state, but otherwise
87-
/// having no effect.
86+
/// copies the value of its first operand, possibly changing its type or
87+
/// ownership state, but otherwise having no effect.
8888
///
8989
/// This is useful for checking all users of a value to verify that the value is
9090
/// only used in recognizable patterns without otherwise "escaping". These are
@@ -123,17 +123,9 @@ SILValue stripConvertFunctions(SILValue V);
123123
/// argument of the partial apply if it is.
124124
SILValue isPartialApplyOfReabstractionThunk(PartialApplyInst *PAI);
125125

126-
struct LLVM_LIBRARY_VISIBILITY FindClosureResult {
127-
PartialApplyInst *PAI = nullptr;
128-
bool isReabstructionThunk = false;
129-
FindClosureResult(PartialApplyInst *PAI, bool isReabstructionThunk)
130-
: PAI(PAI), isReabstructionThunk(isReabstructionThunk) {}
131-
};
132-
133-
/// If V is a function closure, return the partial_apply and the
134-
/// IsReabstractionThunk flag set to true if the closure is indirectly captured
135-
/// by a reabstraction thunk.
136-
FindClosureResult findClosureForAppliedArg(SILValue V);
126+
/// If V is a function closure, return the reaching set of partial_apply's.
127+
void findClosuresForFunctionValue(SILValue V,
128+
TinyPtrVector<PartialApplyInst *> &results);
137129

138130
/// A utility class for evaluating whether a newly parsed or deserialized
139131
/// function has qualified or unqualified ownership.

lib/SIL/InstructionUtils.cpp

Lines changed: 93 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -247,6 +247,11 @@ SILValue swift::stripBorrow(SILValue V) {
247247
return V;
248248
}
249249

250+
// All instructions handled here must propagate their first operand into their
251+
// single result.
252+
//
253+
// This is guaranteed to handle all function-type converstions: ThinToThick,
254+
// ConvertFunction, and ConvertEscapeToNoEscapeInst.
250255
SingleValueInstruction *swift::getSingleValueCopyOrCast(SILInstruction *I) {
251256
if (auto *convert = dyn_cast<ConversionInst>(I))
252257
return convert;
@@ -259,6 +264,7 @@ SingleValueInstruction *swift::getSingleValueCopyOrCast(SILInstruction *I) {
259264
case SILInstructionKind::CopyBlockWithoutEscapingInst:
260265
case SILInstructionKind::BeginBorrowInst:
261266
case SILInstructionKind::BeginAccessInst:
267+
case SILInstructionKind::MarkDependenceInst:
262268
return cast<SingleValueInstruction>(I);
263269
}
264270
}
@@ -353,9 +359,12 @@ SILValue swift::isPartialApplyOfReabstractionThunk(PartialApplyInst *PAI) {
353359
return Arg;
354360
}
355361

356-
/// Given a block used as a noescape function argument, attempt to find
357-
/// the Swift closure that invoking the block will call.
362+
/// Given a block used as a noescape function argument, attempt to find all
363+
/// Swift closures that invoking the block will call. The StoredClosures may not
364+
/// actually be partial_apply instructions. They may be copied, block arguments,
365+
/// or conversions. The caller must continue searching up the use-def chain.
358366
static SILValue findClosureStoredIntoBlock(SILValue V) {
367+
359368
auto FnType = V->getType().castTo<SILFunctionType>();
360369
assert(FnType->getRepresentation() == SILFunctionTypeRepresentation::Block);
361370
(void)FnType;
@@ -374,24 +383,7 @@ static SILValue findClosureStoredIntoBlock(SILValue V) {
374383
// %block = init_block_storage_header %storage invoke %thunk
375384
// %arg = copy_block %block
376385

377-
InitBlockStorageHeaderInst *IBSHI = nullptr;
378-
379-
// Look through block copies to find the initialization of block storage.
380-
while (true) {
381-
if (auto *CBI = dyn_cast<CopyBlockInst>(V)) {
382-
V = CBI->getOperand();
383-
continue;
384-
}
385-
386-
if (auto *CBI = dyn_cast<CopyBlockWithoutEscapingInst>(V)) {
387-
V = CBI->getBlock();
388-
continue;
389-
}
390-
391-
IBSHI = dyn_cast<InitBlockStorageHeaderInst>(V);
392-
break;
393-
}
394-
386+
InitBlockStorageHeaderInst *IBSHI = dyn_cast<InitBlockStorageHeaderInst>(V);
395387
if (!IBSHI)
396388
return nullptr;
397389

@@ -414,45 +406,93 @@ static SILValue findClosureStoredIntoBlock(SILValue V) {
414406
auto NoEscapeClosure = isPartialApplyOfReabstractionThunk(Sentinel);
415407
if (WrappedNoEscape->getBase() != NoEscapeClosure)
416408
return nullptr;
409+
410+
// This is the value of the closure to be invoked. To find the partial_apply
411+
// itself, the caller must search the use-def chain.
417412
return NoEscapeClosure;
418413
}
419414

420-
/// Look through a value passed as a function argument to determine whether
421-
/// it is a closure.
415+
/// Find all closures that may be propagated into the given function-type value.
422416
///
423-
/// Return the partial_apply and a flag set to true if the closure is
424-
/// indirectly captured by a reabstraction thunk.
425-
FindClosureResult swift::findClosureForAppliedArg(SILValue V) {
426-
// Look through borrows.
427-
if (auto *bbi = dyn_cast<BeginBorrowInst>(V))
428-
V = bbi->getOperand();
429-
430-
if (V->getType().getOptionalObjectType()) {
431-
auto *EI = dyn_cast<EnumInst>(V);
432-
if (!EI || !EI->hasOperand())
433-
return FindClosureResult(nullptr, false);
434-
435-
V = EI->getOperand();
436-
}
417+
/// Searches the use-def chain from the given value upward until a partial_apply
418+
/// is reached. Populates `results` with the set of partial_apply instructions.
419+
///
420+
/// `funcVal` may be either a function type or an Optional function type. This
421+
/// might be called on a directly applied value or on a call argument, which may
422+
/// in turn be applied within the callee.
423+
void swift::findClosuresForFunctionValue(
424+
SILValue funcVal, TinyPtrVector<PartialApplyInst *> &results) {
425+
426+
SILType funcTy = funcVal->getType();
427+
// Handle `Optional<@convention(block) @noescape (_)->(_)>`
428+
if (auto optionalObjTy = funcTy.getOptionalObjectType())
429+
funcTy = optionalObjTy;
430+
assert(funcTy.is<SILFunctionType>());
431+
432+
SmallVector<SILValue, 4> worklist;
433+
// Avoid exponential path exploration and prevent duplicate results.
434+
llvm::SmallDenseSet<SILValue, 8> visited;
435+
auto worklistInsert = [&](SILValue V) {
436+
if (visited.insert(V).second)
437+
worklist.push_back(V);
438+
};
439+
worklistInsert(funcVal);
440+
441+
while (!worklist.empty()) {
442+
SILValue V = worklist.pop_back_val();
443+
444+
if (auto *I = V->getDefiningInstruction()) {
445+
// Look through copies, borrows, and conversions.
446+
//
447+
// Handle copy_block and copy_block_without_actually_escaping before
448+
// calling findClosureStoredIntoBlock.
449+
if (SingleValueInstruction *SVI = getSingleValueCopyOrCast(I)) {
450+
worklistInsert(SVI->getOperand(0));
451+
continue;
452+
}
453+
}
454+
// Look through Optionals.
455+
if (V->getType().getOptionalObjectType()) {
456+
auto *EI = dyn_cast<EnumInst>(V);
457+
if (EI && EI->hasOperand()) {
458+
worklistInsert(EI->getOperand());
459+
}
460+
// Ignore the .None case.
461+
continue;
462+
}
463+
// Look through Phis.
464+
//
465+
// This should be done before calling findClosureStoredIntoBlock.
466+
if (auto *arg = dyn_cast<SILPHIArgument>(V)) {
467+
SmallVector<std::pair<SILBasicBlock *, SILValue>, 2> blockArgs;
468+
arg->getIncomingValues(blockArgs);
469+
for (auto &blockAndArg : blockArgs)
470+
worklistInsert(blockAndArg.second);
437471

438-
auto fnType = V->getType().getAs<SILFunctionType>();
439-
if (fnType->getRepresentation() == SILFunctionTypeRepresentation::Block) {
440-
V = findClosureStoredIntoBlock(V);
441-
if (!V)
442-
return FindClosureResult(nullptr, false);
443-
}
444-
auto *PAI = dyn_cast<PartialApplyInst>(stripConvertFunctions(V));
445-
if (!PAI)
446-
return FindClosureResult(nullptr, false);
447-
448-
SILValue thunkArg = isPartialApplyOfReabstractionThunk(PAI);
449-
if (thunkArg) {
450-
// Handle reabstraction thunks recursively. This may reabstract over
451-
// @convention(block).
452-
auto result = findClosureForAppliedArg(thunkArg);
453-
return FindClosureResult(result.PAI, true);
472+
continue;
473+
}
474+
// Look through ObjC closures.
475+
auto fnType = V->getType().getAs<SILFunctionType>();
476+
if (fnType
477+
&& fnType->getRepresentation() == SILFunctionTypeRepresentation::Block) {
478+
if (SILValue storedClosure = findClosureStoredIntoBlock(V))
479+
worklistInsert(storedClosure);
480+
481+
continue;
482+
}
483+
if (auto *PAI = dyn_cast<PartialApplyInst>(V)) {
484+
SILValue thunkArg = isPartialApplyOfReabstractionThunk(PAI);
485+
if (thunkArg) {
486+
// Handle reabstraction thunks recursively. This may reabstract over
487+
// @convention(block).
488+
worklistInsert(thunkArg);
489+
continue;
490+
}
491+
results.push_back(PAI);
492+
continue;
493+
}
494+
// Ignore other unrecognized values that feed this applied argument.
454495
}
455-
return FindClosureResult(PAI, false);
456496
}
457497

458498
namespace {

lib/SIL/MemAccessUtils.cpp

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -512,12 +512,11 @@ static void visitApplyAccesses(ApplySite apply,
512512

513513
// When @noescape function closures are passed as arguments, their
514514
// arguments are considered accessed at the call site.
515-
FindClosureResult result = findClosureForAppliedArg(oper.get());
516-
if (!result.PAI)
517-
continue;
518-
515+
TinyPtrVector<PartialApplyInst *> partialApplies;
516+
findClosuresForFunctionValue(oper.get(), partialApplies);
519517
// Recursively visit @noescape function closure arguments.
520-
visitApplyAccesses(result.PAI, visitor);
518+
for (auto *PAI : partialApplies)
519+
visitApplyAccesses(PAI, visitor);
521520
}
522521
}
523522

0 commit comments

Comments
 (0)