Skip to content

Commit de9c646

Browse files
committed
[Exclusivity] Use dynamic enforcement on boxes whose projections escape
In AccessEnforcementSelection, treat passing a projection from a box to a partial_apply as an escape to force using dynamic enforcement on the box. This will now correctly use dynamic enforcement on variables that are taken as inout and also captured by storage address in a closure: var x = ... x.mutatingMethod { ... use x ...} but does pessimize some of our existing enforcement into dynamic since access enforcement selection. Ideally we would distinguish between escaping via an nonescaping closures (which can only conflict with accesses that are in progress) and escaping via escaping closures (which can conflict for any reachable code after the escape)
1 parent 719c8ff commit de9c646

File tree

4 files changed

+164
-21
lines changed

4 files changed

+164
-21
lines changed

lib/SILOptimizer/Mandatory/AccessEnforcementSelection.cpp

Lines changed: 30 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,10 @@ class SelectEnforcement {
8585
void analyzeUsesOfBox(SILInstruction *source);
8686
void analyzeProjection(ProjectBoxInst *projection);
8787

88+
/// Note that the given instruction is a use of the box (or a use of
89+
/// a projection from it) in which the address escapes.
90+
void noteEscapingUse(SILInstruction *inst);
91+
8892
void propagateEscapes();
8993
void propagateEscapesFrom(SILBasicBlock *bb);
9094

@@ -137,20 +141,7 @@ void SelectEnforcement::analyzeUsesOfBox(SILInstruction *source) {
137141
continue;
138142

139143
// Treat everything else as an escape:
140-
141-
// Add it to the escapes set.
142-
Escapes.insert(user);
143-
144-
// Record this point as escaping.
145-
auto userBB = user->getParent();
146-
auto &state = StateMap[userBB];
147-
if (!state.IsInWorklist) {
148-
state.HasEscape = true;
149-
state.IsInWorklist = true;
150-
Worklist.push_back(userBB);
151-
}
152-
assert(state.HasEscape);
153-
assert(state.IsInWorklist);
144+
noteEscapingUse(user);
154145
}
155146

156147
assert(!Accesses.empty() && "didn't find original access!");
@@ -165,9 +156,34 @@ void SelectEnforcement::analyzeProjection(ProjectBoxInst *projection) {
165156
if (access->getEnforcement() == SILAccessEnforcement::Unknown)
166157
Accesses.push_back(access);
167158
}
159+
160+
// FIXME: We should distinguish between escapes via arguments to escaping
161+
// closures (which must propagate to to all reachable blocks because they
162+
// could access the address at any later point in the program) and
163+
// non-escaping closures (which only force dynamic enforcement for
164+
// accesses that are in progress at the time of the escape).
165+
if (auto partialApply = dyn_cast<PartialApplyInst>(user)) {
166+
noteEscapingUse(partialApply);
167+
}
168168
}
169169
}
170170

171+
void SelectEnforcement::noteEscapingUse(SILInstruction *inst) {
172+
// Add it to the escapes set.
173+
Escapes.insert(inst);
174+
175+
// Record this point as escaping.
176+
auto userBB = inst->getParent();
177+
auto &state = StateMap[userBB];
178+
if (!state.IsInWorklist) {
179+
state.HasEscape = true;
180+
state.IsInWorklist = true;
181+
Worklist.push_back(userBB);
182+
}
183+
assert(state.HasEscape);
184+
assert(state.IsInWorklist);
185+
}
186+
171187
void SelectEnforcement::propagateEscapes() {
172188
while (!Worklist.empty()) {
173189
auto bb = Worklist.pop_back_val();

test/Interpreter/enforce_exclusive_access.swift

Lines changed: 44 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -101,19 +101,60 @@ ExclusiveAccessTestSuite.test("ModifyFollowedByModify") {
101101
globalX = X() // no-trap
102102
}
103103

104-
ExclusiveAccessTestSuite.test("ClosureCaptureModifyModify") {
104+
ExclusiveAccessTestSuite.test("ClosureCaptureModifyModify")
105+
.skip(.custom(
106+
{ _isFastAssertConfiguration() },
107+
reason: "this trap is not guaranteed to happen in -Ounchecked"))
108+
.crashOutputMatches("modify/modify access conflict detected on address")
109+
.code
110+
{
111+
var x = X()
112+
modifyAndPerform(&x) {
113+
expectCrashLater()
114+
x.i = 12
115+
}
116+
}
117+
118+
ExclusiveAccessTestSuite.test("ClosureCaptureReadModify")
119+
.skip(.custom(
120+
{ _isFastAssertConfiguration() },
121+
reason: "this trap is not guaranteed to happen in -Ounchecked"))
122+
.crashOutputMatches("read/modify access conflict detected on address")
123+
.code
124+
{
105125
var x = X()
106126
modifyAndPerform(&x) {
107-
// FIXME: This should be caught dynamically.
127+
expectCrashLater()
128+
_blackHole(x.i)
129+
}
130+
}
131+
132+
ExclusiveAccessTestSuite.test("ClosureCaptureModifyRead")
133+
.skip(.custom(
134+
{ _isFastAssertConfiguration() },
135+
reason: "this trap is not guaranteed to happen in -Ounchecked"))
136+
.crashOutputMatches("modify/read access conflict detected on address")
137+
.code
138+
{
139+
var x = X()
140+
readAndPerform(&x) {
141+
expectCrashLater()
108142
x.i = 12
109143
}
110144
}
111145

146+
ExclusiveAccessTestSuite.test("ClosureCaptureReadRead") {
147+
var x = X()
148+
readAndPerform(&x) {
149+
_blackHole(x.i) // no-trap
150+
}
151+
}
152+
112153
// Test for per-thread enforcement. Don't trap when two different threads
113154
// have overlapping accesses
114155
ExclusiveAccessTestSuite.test("PerThreadEnforcement") {
115156
modifyAndPerform(&globalX) {
116-
var (_, otherThread) = _stdlib_pthread_create_block(nil, { (_ : Void) -> () in
157+
let (_, otherThread) = _stdlib_pthread_create_block(nil, { (_ : Void) -> () in
117158
globalX.i = 12 // no-trap
118159
return ()
119160
}, ())

test/SILOptimizer/access_enforcement_selection.sil

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,41 @@ sil hidden @markFuncEscape : $@convention(thin) () -> () {
6464
%99 = return %98 : $()
6565
}
6666

67+
68+
sil @takesInoutAndClosure : $@convention(thin) (@inout Builtin.Int64, @owned @callee_owned () -> ()) -> ()
69+
sil @closureCapturingByStorageAddress : $@convention(thin) (@inout_aliasable Builtin.Int64) -> ()
70+
71+
// Test dynamic enforcement of box addresses that escape via closure
72+
// partial_applys.
73+
// application.
74+
// CHECK-LABEL: sil hidden @escapeAsArgumentToPartialApply : $@convention(thin) () -> () {
75+
// CHECK: bb0:
76+
// CHECK: [[BOX:%.*]] = alloc_box ${ var Builtin.Int64 }, var, name "x"
77+
// CHECK: [[ADR:%.*]] = project_box [[BOX]] : ${ var Builtin.Int64 }, 0
78+
// CHECK: [[FUNC:%.*]] = function_ref @takesInoutAndClosure : $@convention(thin) (@inout Builtin.Int64, @owned @callee_owned () -> ()) -> ()
79+
// CHECK: [[CLOSURE:%.*]] = function_ref @closureCapturingByStorageAddress : $@convention(thin) (@inout_aliasable Builtin.Int64) -> ()
80+
// CHECK: [[PA:%.*]] = partial_apply [[CLOSURE]]([[ADR]]) : $@convention(thin) (@inout_aliasable Builtin.Int64) -> ()
81+
// CHECK: [[MODIFY:%.*]] = begin_access [modify] [dynamic] [[ADR]] : $*Builtin.Int64
82+
// CHECK: %{{.*}} = apply [[FUNC]]([[MODIFY]], [[PA]]) : $@convention(thin) (@inout Builtin.Int64, @owned @callee_owned () -> ()) -> ()
83+
// CHECK: end_access [[MODIFY]] : $*Builtin.Int64
84+
// CHECK: destroy_value [[BOX]] : ${ var Builtin.Int64 }
85+
// CHECK: return %{{.*}} : $()
86+
// CHECK-LABEL:} // end sil function 'escapeAsArgumentToPartialApply'
87+
sil hidden @escapeAsArgumentToPartialApply : $@convention(thin) () -> () {
88+
%2 = alloc_box ${ var Builtin.Int64 }, var, name "x"
89+
%3 = project_box %2 : ${ var Builtin.Int64 }, 0
90+
%4 = function_ref @takesInoutAndClosure : $@convention(thin) (@inout Builtin.Int64, @owned @callee_owned () -> ()) -> ()
91+
%5 = function_ref @closureCapturingByStorageAddress : $@convention(thin) (@inout_aliasable Builtin.Int64) -> ()
92+
%6 = partial_apply %5(%3) : $@convention(thin) (@inout_aliasable Builtin.Int64) -> ()
93+
%7 = begin_access [modify] [unknown] %3 : $*Builtin.Int64
94+
%8 = apply %4(%7, %6) : $@convention(thin) (@inout Builtin.Int64, @owned @callee_owned () -> ()) -> ()
95+
end_access %7 : $*Builtin.Int64
96+
destroy_value %2 : ${ var Builtin.Int64 }
97+
%9 = tuple ()
98+
%10 = return %9 : $()
99+
}
100+
101+
67102
// Test static enforcement of copied boxes.
68103
// FIXME: Oops... We make this dynamic.
69104
//

test/SILOptimizer/access_enforcement_selection.swift

Lines changed: 55 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,11 @@ public func takesInout(_ i: inout Int) {
1212
// Helper taking a basic, no-escape closure.
1313
func takeClosure(_: ()->Int) {}
1414

15+
// Helper taking a basic, no-escape closure.
16+
func takeClosureAndInout(_: inout Int, _: ()->Int) {}
17+
18+
// Helper taking an escaping closure.
19+
func takeEscapingClosure(_: @escaping ()->Int) {}
1520

1621
// Generate an alloc_stack that escapes into a closure.
1722
public func captureStack() -> Int {
@@ -21,8 +26,10 @@ public func captureStack() -> Int {
2126
return x
2227
}
2328
// CHECK-LABEL: Access Enforcement Selection in _T028access_enforcement_selection12captureStackSiyF
24-
// Static access for `return x`.
25-
// CHECK: Static Access: %{{.*}} = begin_access [read] [static] %{{.*}} : $*Int
29+
// Dynamic access for `return x`. Since the closure is non-escaping, using
30+
// dynamic enforcement here is more conservative than it needs to be -- static
31+
// is sufficient here.
32+
// CHECK: Dynamic Access: %{{.*}} = begin_access [read] [dynamic] %{{.*}} : $*Int
2633

2734
// The access inside the closure is dynamic, until we have the logic necessary to
2835
// prove that no other closures are passed to `takeClosure` that may write to
@@ -31,6 +38,49 @@ public func captureStack() -> Int {
3138
// CHECK-LABEL: Access Enforcement Selection in _T028access_enforcement_selection12captureStackSiyFSiycfU_
3239
// CHECK: Dynamic Access: %{{.*}} = begin_access [read] [dynamic] %{{.*}} : $*Int
3340

41+
42+
// Generate an alloc_stack that does not escape into a closure.
43+
public func nocaptureStack() -> Int {
44+
var x = 3
45+
takeClosure { return 5 }
46+
return x
47+
}
48+
// CHECK-LABEL: Access Enforcement Selection in _T028access_enforcement_selection14nocaptureStackSiyF
49+
// Static access for `return x`.
50+
// CHECK: Static Access: %{{.*}} = begin_access [read] [static] %{{.*}} : $*Int
51+
//
52+
// CHECK-LABEL: Access Enforcement Selection in _T028access_enforcement_selection14nocaptureStackSiyFSiycfU_
53+
54+
// Generate an alloc_stack that escapes into a closure while an access is
55+
// in progress.
56+
public func captureStackWithInoutInProgress() -> Int {
57+
// Use a `var` so `x` isn't treated as a literal.
58+
var x = 3
59+
takeClosureAndInout(&x) { return x }
60+
return x
61+
}
62+
// CHECK-LABEL: Access Enforcement Selection in _T028access_enforcement_selection31captureStackWithInoutInProgressSiyF
63+
// Dynamic access for `&x`. This must be dynamic so that we catch the conflict
64+
// in the closure.
65+
// CHECK-DAG: Dynamic Access: %{{.*}} = begin_access [modify] [dynamic] %{{.*}} : $*Int
66+
// Dynamic access for `return x`. This is more conservative than it needs
67+
// to be; it can be static.
68+
// CHECK-DAG: Dynamic Access: %{{.*}} = begin_access [read] [dynamic] %{{.*}} : $*Int
69+
//
70+
// CHECK-LABEL: Access Enforcement Selection in _T028access_enforcement_selection31captureStackWithInoutInProgressSiyFSiycfU_
71+
// CHECK: Dynamic Access: %{{.*}} = begin_access [read] [dynamic] %{{.*}} : $*Int
72+
73+
// Generate an alloc_box that escapes into a closure.
74+
public func captureBox() -> Int {
75+
var x = 3
76+
takeEscapingClosure { x = 4; return x }
77+
return x
78+
}
79+
// CHECK-LABEL: Access Enforcement Selection in _T028access_enforcement_selection10captureBoxSiyF
80+
// Dynamic access for `return x`.
81+
// CHECK: Dynamic Access: %{{.*}} = begin_access [read] [dynamic] %{{.*}} : $*Int
82+
// CHECK-LABEL: _T028access_enforcement_selection10captureBoxSiyFSiycfU_
83+
3484
// Generate a closure in which the @inout_aliasing argument
3585
// escapes to an @inout function `bar`.
3686
public func recaptureStack() -> Int {
@@ -40,8 +90,9 @@ public func recaptureStack() -> Int {
4090
}
4191
// CHECK-LABEL: Access Enforcement Selection in _T028access_enforcement_selection14recaptureStackSiyF
4292
//
43-
// Static access for `return x`.
44-
// CHECK: Static Access: %{{.*}} = begin_access [read] [static] %{{.*}} : $*Int
93+
// Dynamic access for `return x`. This is more conservative than it needs
94+
// to be; static is sufficient.
95+
// CHECK: Dynamic Access: %{{.*}} = begin_access [read] [dynamic] %{{.*}} : $*Int
4596

4697
// CHECK-LABEL: Access Enforcement Selection in _T028access_enforcement_selection14recaptureStackSiyFSiycfU_
4798
//

0 commit comments

Comments
 (0)