Skip to content

Commit 2696df4

Browse files
authored
Merge pull request #41067 from rjmccall/defer-isolation
Look through defer bodies in actor isolation checking
2 parents 19ea9e7 + 67ec699 commit 2696df4

File tree

4 files changed

+202
-5
lines changed

4 files changed

+202
-5
lines changed

CHANGELOG.md

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,31 @@ _**Note:** This is in reverse chronological order, so newer entries are added to
1919
Swift 5.6
2020
---------
2121

22+
* Actor isolation checking now understands that `defer` bodies share the isolation of their enclosing function.
23+
24+
```swift
25+
// Works on global actors
26+
@MainActor
27+
func runAnimation(controller: MyViewController) async {
28+
controller.hasActiveAnimation = true
29+
defer { controller.hasActiveAnimation = false }
30+
31+
// do the animation here...
32+
}
33+
34+
// Works on actor instances
35+
actor OperationCounter {
36+
var activeOperationCount = 0
37+
38+
func operate() async {
39+
activeOperationCount += 1
40+
defer { activeOperationCount -= 1 }
41+
42+
// do work here...
43+
}
44+
}
45+
```
46+
2247
* [SE-0335][]:
2348

2449
Swift now allows existential types to be explicitly written with the `any`

lib/SILGen/SILGenProlog.cpp

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -552,11 +552,23 @@ void SILGenFunction::emitProlog(CaptureInfo captureInfo,
552552
return false;
553553
};
554554

555-
// Initialize ExpectedExecutor if the function is an async
556-
// function or closure.
555+
// Initialize ExpectedExecutor if:
556+
// - this function is async or
557+
// - this function is sync and isolated to an actor, and we want to
558+
// dynamically check that we're on the right executor.
559+
//
560+
// Actor destructors are isolated in the sense that we now have a
561+
// unique reference to the actor, but we probably aren't running on
562+
// the actor's executor, so we cannot safely do this check.
563+
//
564+
// Defer bodies are always called synchronously within their enclosing
565+
// function, so the check is unnecessary; in addition, we cannot
566+
// necessarily perform the check because the defer may not have
567+
// captured the isolated parameter of the enclosing function.
557568
bool wantDataRaceChecks = getOptions().EnableActorDataRaceChecks &&
558569
!F.isAsync() &&
559-
!isInActorDestructor(FunctionDC);
570+
!isInActorDestructor(FunctionDC) &&
571+
!F.isDefer();
560572

561573
// Local function to load the expected executor from a local actor
562574
auto loadExpectedExecutorForLocalVar = [&](VarDecl *var) {

lib/Sema/TypeCheckConcurrency.cpp

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1733,6 +1733,12 @@ namespace {
17331733
}
17341734
}
17351735

1736+
// Look through defers.
1737+
// FIXME: should this be covered automatically by the logic below?
1738+
if (auto func = dyn_cast<FuncDecl>(dc))
1739+
if (func->isDeferBody())
1740+
continue;
1741+
17361742
if (auto func = dyn_cast<AbstractFunctionDecl>(dc)) {
17371743
// @Sendable functions are nonisolated.
17381744
if (func->isSendable())
@@ -1784,12 +1790,26 @@ namespace {
17841790
return nullptr;
17851791
}
17861792

1793+
static FuncDecl *findAnnotatableFunction(DeclContext *dc) {
1794+
auto fn = dyn_cast<FuncDecl>(dc);
1795+
if (!fn) return nullptr;
1796+
if (fn->isDeferBody())
1797+
return findAnnotatableFunction(fn->getDeclContext());
1798+
return fn;
1799+
}
1800+
17871801
/// Note when the enclosing context could be put on a global actor.
17881802
void noteGlobalActorOnContext(DeclContext *dc, Type globalActor) {
17891803
// If we are in a synchronous function on the global actor,
17901804
// suggest annotating with the global actor itself.
1791-
if (auto fn = dyn_cast<FuncDecl>(dc)) {
1792-
if (!isa<AccessorDecl>(fn) && !fn->isAsyncContext()) {
1805+
if (auto fn = findAnnotatableFunction(dc)) {
1806+
// Suppress this for accesssors because you can't change the
1807+
// actor isolation of an individual accessor. Arguably we could
1808+
// add this to the entire storage declaration, though.
1809+
// Suppress this for async functions out of caution; but don't
1810+
// suppress it if we looked through a defer.
1811+
if (!isa<AccessorDecl>(fn) &&
1812+
(!fn->isAsyncContext() || fn != dc)) {
17931813
switch (getActorIsolation(fn)) {
17941814
case ActorIsolation::ActorInstance:
17951815
case ActorIsolation::DistributedActorInstance:
@@ -3468,6 +3488,14 @@ ActorIsolation ActorIsolationRequest::evaluate(
34683488
// If this is a local function, inherit the actor isolation from its
34693489
// context if it global or was captured.
34703490
if (auto func = dyn_cast<FuncDecl>(value)) {
3491+
// If this is a defer body, inherit unconditionally; we don't
3492+
// care if the enclosing function captures the isolated parameter.
3493+
if (func->isDeferBody()) {
3494+
auto enclosingIsolation =
3495+
getActorIsolationOfContext(func->getDeclContext());
3496+
return inferredIsolation(enclosingIsolation);
3497+
}
3498+
34713499
if (func->isLocalCapture() && !func->isSendable()) {
34723500
switch (auto enclosingIsolation =
34733501
getActorIsolationOfContext(func->getDeclContext())) {

test/Concurrency/actor_defer.swift

Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
// RUN: %target-swift-frontend -parse-as-library -emit-sil -DNEGATIVES -verify %s
2+
// RUN: %target-swift-frontend -parse-as-library -emit-sil -enable-actor-data-race-checks -o - %s | %FileCheck %s
3+
4+
// REQUIRES: concurrency
5+
6+
func doSomething() {}
7+
8+
// expected-note @+1 4 {{calls to global function 'requiresMainActor()' from outside of its actor context are implicitly asynchronous}}
9+
@MainActor func requiresMainActor() {}
10+
11+
@MainActor func testNonDefer_positive() {
12+
requiresMainActor()
13+
}
14+
15+
#if NEGATIVES
16+
// expected-note @+1 {{add '@MainActor' to make global function 'testNonDefer_negative()' part of global actor 'MainActor'}}
17+
func testNonDefer_negative() {
18+
// expected-error @+1 {{call to main actor-isolated global function 'requiresMainActor()' in a synchronous nonisolated context}}
19+
requiresMainActor()
20+
}
21+
#endif
22+
23+
@MainActor func testGlobalActor_positive() {
24+
defer {
25+
requiresMainActor()
26+
}
27+
doSomething()
28+
}
29+
// Don't include a data race check at the start of the defer
30+
// CHECK-LABEL: sil private @$s11actor_defer24testGlobalActor_positiveyyF6$deferL_yyF
31+
// CHECK-NEXT: bb0:
32+
// CHECK-NEXT: // function_ref
33+
// CHECK-NEXT: function_ref
34+
// CHECK-NEXT: apply
35+
36+
#if NEGATIVES
37+
// expected-note @+1 {{add '@MainActor' to make global function 'testGlobalActor_negative()' part of global actor 'MainActor'}}
38+
func testGlobalActor_negative() {
39+
defer {
40+
// expected-error @+1 {{call to main actor-isolated global function 'requiresMainActor()' in a synchronous nonisolated context}}
41+
requiresMainActor()
42+
}
43+
doSomething()
44+
}
45+
#endif
46+
47+
@available(SwiftStdlib 5.1, *)
48+
@MainActor func testGlobalActorAsync_positive() async {
49+
defer {
50+
requiresMainActor()
51+
}
52+
doSomething()
53+
}
54+
55+
#if NEGATIVES
56+
// expected-note @+2 {{add '@MainActor' to make global function 'testGlobalActorAsync_negative()' part of global actor 'MainActor'}}
57+
@available(SwiftStdlib 5.1, *)
58+
func testGlobalActorAsync_negative() async {
59+
defer {
60+
// expected-error @+1 {{call to main actor-isolated global function 'requiresMainActor()' in a synchronous nonisolated context}}
61+
requiresMainActor()
62+
}
63+
doSomething()
64+
}
65+
#endif
66+
67+
@available(SwiftStdlib 5.1, *)
68+
actor Actor {
69+
// expected-note @+1 3 {{mutation of this property is only permitted within the actor}}
70+
var actorProperty = 0
71+
72+
func testActor_positive() {
73+
defer {
74+
actorProperty += 1
75+
}
76+
doSomething()
77+
}
78+
79+
#if NEGATIVES
80+
nonisolated func testActor_negative() {
81+
defer {
82+
// expected-error @+1 {{actor-isolated property 'actorProperty' can not be mutated from a non-isolated context}}
83+
actorProperty += 1
84+
}
85+
doSomething()
86+
}
87+
@MainActor func testActor_negative_globalActor() {
88+
defer {
89+
// expected-error @+1 {{actor-isolated property 'actorProperty' can not be mutated from the main actor}}
90+
actorProperty += 1
91+
}
92+
doSomething()
93+
}
94+
#endif
95+
96+
@MainActor func testGlobalActor_positive() {
97+
defer {
98+
requiresMainActor()
99+
}
100+
doSomething()
101+
}
102+
103+
#if NEGATIVES
104+
func testGlobalActor_negative() {
105+
defer {
106+
// expected-error @+1 {{call to main actor-isolated global function 'requiresMainActor()' in a synchronous actor-isolated context}}
107+
requiresMainActor()
108+
}
109+
doSomething()
110+
}
111+
#endif
112+
}
113+
114+
@available(SwiftStdlib 5.1, *)
115+
func testIsolatedActor_positive(actor: isolated Actor) {
116+
actor.actorProperty += 1
117+
defer {
118+
actor.actorProperty += 1
119+
}
120+
doSomething()
121+
}
122+
123+
#if NEGATIVES
124+
@available(SwiftStdlib 5.1, *)
125+
func testIsolatedActor_negative(actor: Actor) {
126+
defer {
127+
// expected-error @+1 {{actor-isolated property 'actorProperty' can not be mutated from a non-isolated context}}
128+
actor.actorProperty += 1
129+
}
130+
doSomething()
131+
}
132+
#endif

0 commit comments

Comments
 (0)