Skip to content

Commit 29bd6c3

Browse files
authored
Merge pull request #10518 from atrick/swift-4.0-branch
[4.0] Static exclusivity diagnostics handling of non-escaping closures passed as blocks.
2 parents 0140bf7 + eab2830 commit 29bd6c3

File tree

7 files changed

+181
-46
lines changed

7 files changed

+181
-46
lines changed

include/swift/SILOptimizer/Analysis/ClosureScope.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@ class ClosureScopeAnalysis : public SILAnalysis {
9595
IndexLookupFunc(const std::vector<SILFunction *> &indexedScopes)
9696
: indexedScopes(indexedScopes) {}
9797

98-
Optional<SILFunction *> operator()(int &idx) const {
98+
Optional<SILFunction *> operator()(int idx) const {
9999
if (auto funcPtr = indexedScopes[idx]) {
100100
return funcPtr;
101101
}

lib/SILOptimizer/Analysis/AccessSummaryAnalysis.cpp

Lines changed: 39 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,8 @@ void AccessSummaryAnalysis::processArgument(FunctionInfo *info,
106106
case ValueKind::ExistentialMetatypeInst:
107107
case ValueKind::ValueMetatypeInst:
108108
case ValueKind::LoadInst:
109+
case ValueKind::LoadBorrowInst:
110+
case ValueKind::EndBorrowInst:
109111
case ValueKind::OpenExistentialAddrInst:
110112
case ValueKind::ProjectBlockStorageInst:
111113
// These likely represent scenarios in which we're not generating
@@ -129,24 +131,46 @@ void AccessSummaryAnalysis::processArgument(FunctionInfo *info,
129131
/// only ultimately used by an apply, a try_apply or as an argument (but not
130132
/// the called function) in a partial_apply.
131133
/// TODO: This really should be checked in the SILVerifier.
132-
static bool isExpectedUseOfNoEscapePartialApply(SILInstruction *user) {
133-
if (!user)
134-
return true;
134+
static bool hasExpectedUsesOfNoEscapePartialApply(Operand *partialApplyUse) {
135+
SILInstruction *user = partialApplyUse->getUser();
135136

136137
// It is fine to call the partial apply
137-
if (isa<ApplyInst>(user) || isa<TryApplyInst>(user)) {
138+
switch (user->getKind()) {
139+
case ValueKind::ApplyInst:
140+
case ValueKind::TryApplyInst:
138141
return true;
139-
}
140-
141-
if (isa<ConvertFunctionInst>(user)) {
142-
return isExpectedUseOfNoEscapePartialApply(user->getSingleUse()->getUser());
143-
}
144142

145-
if (auto *PAI = dyn_cast<PartialApplyInst>(user)) {
146-
return user != PAI->getCallee();
143+
case ValueKind::ConvertFunctionInst:
144+
return llvm::all_of(user->getUses(),
145+
hasExpectedUsesOfNoEscapePartialApply);
146+
147+
case ValueKind::PartialApplyInst:
148+
return partialApplyUse->get() != cast<PartialApplyInst>(user)->getCallee();
149+
150+
case ValueKind::StoreInst:
151+
case ValueKind::DestroyValueInst:
152+
// @block_storage is passed by storing it to the stack. We know this is
153+
// still nonescaping simply because our original argument convention is
154+
// @inout_aliasable. In this SIL, both store and destroy_value are users
155+
// of %closure:
156+
//
157+
// %closure = partial_apply %f1(%arg)
158+
// : $@convention(thin) (@inout_aliasable T) -> ()
159+
// %storage = alloc_stack $@block_storage @callee_owned () -> ()
160+
// %block_addr = project_block_storage %storage
161+
// : $*@block_storage @callee_owned () -> ()
162+
// store %closure to [init] %block_addr : $*@callee_owned () -> ()
163+
// %block = init_block_storage_header %storage
164+
// : $*@block_storage @callee_owned () -> (),
165+
// invoke %f2 : $@convention(c)
166+
// (@inout_aliasable @block_storage @callee_owned () -> ()) -> (),
167+
// type $@convention(block) () -> ()
168+
// %copy = copy_block %block : $@convention(block) () -> ()
169+
// destroy_value %storage : $@callee_owned () -> ()
170+
return true;
171+
default:
172+
return false;
147173
}
148-
149-
return false;
150174
}
151175
#endif
152176

@@ -164,15 +188,9 @@ void AccessSummaryAnalysis::processPartialApply(FunctionInfo *callerInfo,
164188
assert(isa<FunctionRefInst>(apply->getCallee()) &&
165189
"Noescape partial apply of non-functionref?");
166190

167-
SILInstruction *user = apply->getSingleUse()->getUser();
168-
assert(isExpectedUseOfNoEscapePartialApply(user) &&
191+
assert(llvm::all_of(apply->getUses(),
192+
hasExpectedUsesOfNoEscapePartialApply) &&
169193
"noescape partial_apply has unexpected use!");
170-
(void)user;
171-
172-
// The arguments to partial_apply are a suffix of the arguments to the
173-
// the actually-called function. Translate the index of the argument to
174-
// the partial_apply into to the corresponding index into the arguments of
175-
// the called function.
176194

177195
// The argument index in the called function.
178196
ApplySite site(apply);

lib/SILOptimizer/Mandatory/AccessEnforcementSelection.cpp

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,11 @@ static void setDynamicEnforcement(BeginAccessInst *access) {
5757
namespace {
5858
// Information about an address-type closure capture.
5959
// This is only valid for inout_aliasable parameters.
60+
//
61+
// TODO: Verify somewhere that we properly handle any non-inout_aliasable
62+
// partial apply captures or that they never happen. Eventually @inout_aliasable
63+
// should be simply replaced by @in or @out, once we don't have special aliasing
64+
// rules.
6065
struct AddressCapture {
6166
ApplySite site;
6267
unsigned calleeArgIdx;
@@ -466,7 +471,7 @@ void SelectEnforcement::updateCapture(AddressCapture capture) {
466471
namespace {
467472

468473
// Model the kind of access needed based on analyzing the access's source.
469-
// This is either determined to be static or dynamic, or requries further
474+
// This is either determined to be static or dynamic, or requires further
470475
// analysis of a boxed variable.
471476
struct SourceAccess {
472477
enum { StaticAccess, DynamicAccess, BoxAccess } kind;
@@ -534,8 +539,10 @@ void AccessEnforcementSelection::processFunction(SILFunction *F) {
534539

535540
if (auto access = dyn_cast<BeginAccessInst>(inst))
536541
handleAccess(access);
542+
537543
else if (auto access = dyn_cast<BeginUnpairedAccessInst>(inst))
538544
assert(access->getEnforcement() == SILAccessEnforcement::Dynamic);
545+
539546
else if(auto pa = dyn_cast<PartialApplyInst>(inst))
540547
handlePartialApply(pa);
541548
}

lib/SILOptimizer/Mandatory/DiagnoseStaticExclusivity.cpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -985,6 +985,11 @@ static PartialApplyInst *lookThroughForPartialApply(SILValue V) {
985985
/// if any of the @inout_aliasable captures passed to those closures have
986986
/// in-progress accesses that would conflict with any access the summary
987987
/// says the closure would perform.
988+
//
989+
/// TODO: We currently fail to statically diagnose non-escaping closures pased
990+
/// via @block_storage convention. To enforce this case, we should statically
991+
/// recognize when the apply takes a block argument that has been initialized to
992+
/// a non-escaping closure.
988993
static void checkForViolationsInNoEscapeClosures(
989994
const StorageMap &Accesses, FullApplySite FAS, AccessSummaryAnalysis *ASA,
990995
llvm::SmallVectorImpl<ConflictingAccess> &ConflictingAccesses) {

test/SILOptimizer/access_enforcement_noescape.swift

Lines changed: 75 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,9 @@
66
// (Some static/dynamic enforcement selection is done in SILGen, and some is
77
// deferred. That may change over time but we want the outcome to be the same).
88
//
9-
// Each FIXME line is a case that the current implementation misses.
10-
// The model is currently being refined, so this isn't set in stone.
11-
//
12-
// TODO: Ensure that each dynamic case is covered by
13-
// Interpreter/enforce_exclusive_access.swift.
9+
// These tests attempt to fully cover the possibilities of reads and
10+
// modifications to captures along with `inout` arguments on both the caller and
11+
// callee side.
1412

1513
// Helper
1614
func doOne(_ f: () -> ()) {
@@ -24,10 +22,8 @@ func doTwo(_: ()->(), _: ()->()) {}
2422
func doOneInout(_: ()->(), _: inout Int) {}
2523

2624
// Error: Cannot capture nonescaping closure.
27-
// Verification disabled because it suppresses all the other errors.
28-
// disabled-note@+1{{parameter 'fn' is implicitly non-escaping}}
25+
// This triggers an early diagnostics, so it's handled in inout_capture_disgnostics.swift.
2926
// func reentrantCapturedNoescape(fn: (() -> ()) -> ()) {
30-
// disabled-error@+1{{closure use of non-escaping parameter 'fn' may allow it to escape}}
3127
// let c = { fn {} }
3228
// fn(c)
3329
// }
@@ -308,8 +304,8 @@ func inoutReadWriteInout(x: inout Int) {
308304
// CHECK: end_access [[ACCESS]]
309305
// CHECK-LABEL: } // end sil function '_T027access_enforcement_noescape19inoutReadWriteInoutySiz1x_tFyycfU_'
310306

311-
// Trap on boxed read + write inout.
312-
// FIXME: Passing a captured var as inout needs dynamic enforcement.
307+
// Traps on boxed read + write inout.
308+
// Covered by Interpreter/enforce_exclusive_access.swift.
313309
func readBoxWriteInout() {
314310
var x = 3
315311
let c = { _ = x }
@@ -333,12 +329,11 @@ func readBoxWriteInout() {
333329
// CHECK-LABEL: } // end sil function '_T027access_enforcement_noescape17readBoxWriteInoutyyFyycfU_'
334330

335331
// Error: inout cannot be captured.
336-
// Verification disabled because is suppresses other errors.
337-
//func inoutReadBoxWriteInout(x: inout Int) {
338-
// disabled-error@+1{{escaping closures can only capture inout parameters explicitly by value}}
339-
// let c = { _ = x }
340-
// doOneInout(c, &x)
341-
//}
332+
// This triggers an early diagnostics, so it's handled in inout_capture_disgnostics.swift.
333+
// func inoutReadBoxWriteInout(x: inout Int) {
334+
// let c = { _ = x }
335+
// doOneInout(c, &x)
336+
// }
342337

343338
// Allow aliased noescape write + write.
344339
func writeWrite() {
@@ -395,9 +390,8 @@ func inoutWriteWrite(x: inout Int) {
395390
// CHECK: end_access [[ACCESS]]
396391
// CHECK-LABEL: } // end sil function '_T027access_enforcement_noescape010inoutWriteE0ySiz1x_tFyycfU0_'
397392

398-
// FIXME: Trap on aliased boxed write + noescape write.
399-
//
400-
// See the note above.
393+
// Traps on aliased boxed write + noescape write.
394+
// Covered by Interpreter/enforce_exclusive_access.swift.
401395
func writeWriteBox() {
402396
var x = 3
403397
let c = { x = 87 }
@@ -479,8 +473,8 @@ func inoutWriteWriteInout(x: inout Int) {
479473
// CHECK: end_access [[ACCESS]]
480474
// CHECK-LABEL: } // end sil function '_T027access_enforcement_noescape010inoutWriteE5InoutySiz1x_tFyycfU_'
481475

482-
// Trap on boxed write + write inout.
483-
// FIXME: Passing a captured var as inout needs dynamic enforcement.
476+
// Traps on boxed write + write inout.
477+
// Covered by Interpreter/enforce_exclusive_access.swift.
484478
func writeBoxWriteInout() {
485479
var x = 3
486480
let c = { x = 42 }
@@ -504,9 +498,67 @@ func writeBoxWriteInout() {
504498
// CHECK-LABEL: } // end sil function '_T027access_enforcement_noescape18writeBoxWriteInoutyyFyycfU_'
505499

506500
// Error: Cannot capture inout
507-
// Verification disabled because it suppresses other errors.
501+
// This triggers an early diagnostics, so it's handled in inout_capture_disgnostics.swift.
508502
// func inoutWriteBoxWriteInout(x: inout Int) {
509-
// disabled-error@+1{{escaping closures can only capture inout parameters explicitly by value}}
510503
// let c = { x = 42 }
511504
// doOneInout(c, &x)
512505
// }
506+
507+
// Helper
508+
func doBlockInout(_: @convention(block) ()->(), _: inout Int) {}
509+
510+
// FIXME: This case could be statically enforced, but requires quite a bit of SIL pattern matching.
511+
func readBlockWriteInout() {
512+
var x = 3
513+
// Around the call: [modify] [static]
514+
// Inside closure: [modify] [static]
515+
doBlockInout({ _ = x }, &x)
516+
}
517+
518+
// CHECK-LABEL: sil hidden @_T027access_enforcement_noescape19readBlockWriteInoutyyF : $@convention(thin) () -> () {
519+
// CHECK: [[F1:%.*]] = function_ref @_T027access_enforcement_noescape19readBlockWriteInoutyyFyycfU_ : $@convention(thin) (@inout_aliasable Int) -> ()
520+
// CHECK: [[PA:%.*]] = partial_apply [[F1]](%0) : $@convention(thin) (@inout_aliasable Int) -> ()
521+
// CHECK-NOT: begin_access
522+
// CHECK: [[WRITE:%.*]] = begin_access [modify] [static] %0 : $*Int
523+
// CHECK: apply
524+
// CHECK: end_access [[WRITE]] : $*Int
525+
// CHECK-LABEL: } // end sil function '_T027access_enforcement_noescape19readBlockWriteInoutyyF'
526+
527+
// CHECK-LABEL: sil private @_T027access_enforcement_noescape19readBlockWriteInoutyyFyycfU_ : $@convention(thin) (@inout_aliasable Int) -> () {
528+
// CHECK: begin_access [read] [static] %0 : $*Int
529+
// CHECK-LABEL: } // end sil function '_T027access_enforcement_noescape19readBlockWriteInoutyyFyycfU_'
530+
531+
// Test AccessSummaryAnalysis.
532+
//
533+
// The captured @inout_aliasable argument to `doOne` is re-partially applied,
534+
// then stored is a box before passing it to doBlockInout.
535+
func noEscapeBlock() {
536+
var x = 3
537+
doOne {
538+
doBlockInout({ _ = x }, &x)
539+
}
540+
}
541+
// CHECK-LABEL: sil hidden @_T027access_enforcement_noescape13noEscapeBlockyyF : $@convention(thin) () -> () {
542+
// CHECK: partial_apply
543+
// CHECK-NOT: begin_access
544+
// CHECK: apply
545+
// CHECK-LABEL: } // end sil function '_T027access_enforcement_noescape13noEscapeBlockyyF'
546+
547+
// CHECK-LABEL: sil private @_T027access_enforcement_noescape13noEscapeBlockyyFyycfU_ : $@convention(thin) (@inout_aliasable Int) -> () {
548+
// CHECK: [[F1:%.*]] = function_ref @_T027access_enforcement_noescape13noEscapeBlockyyFyycfU_yycfU_ : $@convention(thin) (@inout_aliasable Int) -> ()
549+
// CHECK: [[PA:%.*]] = partial_apply [[F1]](%0) : $@convention(thin) (@inout_aliasable Int) -> ()
550+
// CHECK: [[STORAGE:%.*]] = alloc_stack $@block_storage @callee_owned () -> ()
551+
// CHECK: [[ADDR:%.*]] = project_block_storage %5 : $*@block_storage @callee_owned () -> ()
552+
// CHECK: store [[PA]] to [[ADDR]] : $*@callee_owned () -> ()
553+
// CHECK: [[BLOCK:%.*]] = init_block_storage_header [[STORAGE]] : $*@block_storage @callee_owned () -> (), invoke %8 : $@convention(c) (@inout_aliasable @block_storage @callee_owned () -> ()) -> (), type $@convention(block) () -> ()
554+
// CHECK: [[ARG:%.*]] = copy_block [[BLOCK]] : $@convention(block) () -> ()
555+
// CHECK: [[WRITE:%.*]] = begin_access [modify] [static] %0 : $*Int
556+
// CHECK: apply %{{.*}}([[ARG]], [[WRITE]]) : $@convention(thin) (@owned @convention(block) () -> (), @inout Int) -> ()
557+
// CHECK: end_access [[WRITE]] : $*Int
558+
// CHECK: dealloc_stack [[STORAGE]] : $*@block_storage @callee_owned () -> ()
559+
// CHECK: strong_release [[PA]] : $@callee_owned () -> ()
560+
// CHECK-LABEL: } // end sil function '_T027access_enforcement_noescape13noEscapeBlockyyFyycfU_'
561+
562+
// CHECK-LABEL: sil private @_T027access_enforcement_noescape13noEscapeBlockyyFyycfU_yycfU_ : $@convention(thin) (@inout_aliasable Int) -> () {
563+
// CHECK: begin_access [read] [static] %0 : $*Int
564+
// CHECK-LABEL: } // end sil function '_T027access_enforcement_noescape13noEscapeBlockyyFyycfU_yycfU_'

test/SILOptimizer/access_summary_analysis.sil

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -228,6 +228,31 @@ bb0(%0 : $*Int):
228228
return %8 : $()
229229
}
230230

231+
// CHECK-LABEL: @reads
232+
// CHECK-NEXT: (read)
233+
sil private @reads : $@convention(thin) (@inout_aliasable Int) -> Int {
234+
bb0(%0 : $*Int):
235+
%3 = begin_access [read] [unknown] %0 : $*Int
236+
%4 = load %3 : $*Int
237+
end_access %3 : $*Int
238+
return %4 : $Int
239+
}
240+
241+
// CHECK-LABEL: @convertPartialApplyAndPassToPartialApply
242+
// CHECK-NEXT: (read)
243+
sil hidden @convertPartialApplyAndPassToPartialApply : $@convention(thin) (@inout_aliasable Int) -> () {
244+
bb0(%0 : $*Int):
245+
%2 = function_ref @takesAutoClosureReturningGeneric : $@convention(thin) <τ_0_0 where τ_0_0 : Equatable> (@owned @callee_owned () -> (@out τ_0_0, @error Error)) -> ()
246+
%3 = function_ref @reads : $@convention(thin) (@inout_aliasable Int) -> Int
247+
%4 = partial_apply %3(%0) : $@convention(thin) (@inout_aliasable Int) -> Int
248+
%5 = convert_function %4 : $@callee_owned () -> Int to $@callee_owned () -> (Int, @error Error)
249+
%6 = function_ref @thunkForAutoClosure : $@convention(thin) (@owned @callee_owned () -> (Int, @error Error)) -> (@out Int, @error Error)
250+
%7 = partial_apply %6(%5) : $@convention(thin) (@owned @callee_owned () -> (Int, @error Error)) -> (@out Int, @error Error)
251+
%8 = apply %2<Int>(%7) : $@convention(thin) <τ_0_0 where τ_0_0 : Equatable> (@owned @callee_owned () -> (@out τ_0_0, @error Error)) -> ()
252+
%9 = tuple ()
253+
return %9 : $()
254+
}
255+
231256
// CHECK-LABEL: @selfRecursion
232257
// CHECK-NEXT: (modify, none)
233258
sil private @selfRecursion : $@convention(thin) (@inout_aliasable Int, Int) -> () {
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
// RUN: %target-swift-frontend -enforce-exclusivity=checked -Onone -emit-sil -swift-version 4 -verify -parse-as-library %s
2+
//
3+
// This is an adjunct to access_enforcement_noescape.swift to cover early static diagnostics.
4+
5+
// Helper
6+
func doOneInout(_: ()->(), _: inout Int) {}
7+
8+
// Error: Cannot capture nonescaping closure.
9+
// expected-note@+1{{parameter 'fn' is implicitly non-escaping}}
10+
func reentrantCapturedNoescape(fn: (() -> ()) -> ()) {
11+
// expected-error@+1{{closure use of non-escaping parameter 'fn' may allow it to escape}}
12+
let c = { fn {} }
13+
fn(c)
14+
}
15+
16+
// Error: inout cannot be captured.
17+
func inoutReadBoxWriteInout(x: inout Int) {
18+
// expected-error@+1{{escaping closures can only capture inout parameters explicitly by value}}
19+
let c = { _ = x }
20+
doOneInout(c, &x)
21+
}
22+
23+
// Error: Cannot capture inout
24+
func inoutWriteBoxWriteInout(x: inout Int) {
25+
// expected-error@+1{{escaping closures can only capture inout parameters explicitly by value}}
26+
let c = { x = 42 }
27+
doOneInout(c, &x)
28+
}

0 commit comments

Comments
 (0)