Skip to content

Commit cdc02a4

Browse files
authored
Merge pull request #80924 from xedin/rdar-149107104-6.2
[6.2][TypeChecker] Allow closures to assume `nonisolated(nonsending)`
2 parents 3b21613 + f08c993 commit cdc02a4

File tree

5 files changed

+128
-92
lines changed

5 files changed

+128
-92
lines changed

lib/SILGen/SILGenConcurrency.cpp

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -206,10 +206,14 @@ void SILGenFunction::emitExpectedExecutorProlog() {
206206
switch (actorIsolation.getKind()) {
207207
case ActorIsolation::Unspecified:
208208
case ActorIsolation::Nonisolated:
209-
case ActorIsolation::CallerIsolationInheriting:
210209
case ActorIsolation::NonisolatedUnsafe:
211210
break;
212211

212+
case ActorIsolation::CallerIsolationInheriting:
213+
assert(F.isAsync());
214+
setExpectedExecutorForParameterIsolation(*this, actorIsolation);
215+
break;
216+
213217
case ActorIsolation::Erased:
214218
llvm_unreachable("closure cannot have erased isolation");
215219

lib/Sema/CSApply.cpp

Lines changed: 36 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6501,18 +6501,31 @@ ArgumentList *ExprRewriter::coerceCallArguments(
65016501
return ArgumentList::createTypeChecked(ctx, args, newArgs);
65026502
}
65036503

6504-
/// Whether the given expression is a closure that should inherit
6505-
/// the actor context from where it was formed.
6506-
static bool closureInheritsActorContext(Expr *expr) {
6504+
/// Looks through any non-semantic expressions and a capture list
6505+
/// to find out whether the given expression is an explicit closure.
6506+
static ClosureExpr *isExplicitClosureExpr(Expr *expr) {
65076507
if (auto IE = dyn_cast<IdentityExpr>(expr))
6508-
return closureInheritsActorContext(IE->getSubExpr());
6508+
return isExplicitClosureExpr(IE->getSubExpr());
65096509

65106510
if (auto CLE = dyn_cast<CaptureListExpr>(expr))
6511-
return closureInheritsActorContext(CLE->getClosureBody());
6511+
return isExplicitClosureExpr(CLE->getClosureBody());
6512+
6513+
return dyn_cast<ClosureExpr>(expr);
6514+
}
65126515

6513-
if (auto CE = dyn_cast<ClosureExpr>(expr))
6516+
/// Whether the given expression is a closure that should inherit
6517+
/// the actor context from where it was formed.
6518+
static bool closureInheritsActorContext(Expr *expr) {
6519+
if (auto *CE = isExplicitClosureExpr(expr))
65146520
return CE->inheritsActorContext();
6521+
return false;
6522+
}
65156523

6524+
/// Determine whether the given expression is a closure that
6525+
/// is explicitly marked as `@concurrent`.
6526+
static bool isClosureMarkedAsConcurrent(Expr *expr) {
6527+
if (auto *CE = isExplicitClosureExpr(expr))
6528+
return CE->getAttrs().hasAttribute<ConcurrentAttr>();
65166529
return false;
65176530
}
65186531

@@ -7754,6 +7767,23 @@ Expr *ExprRewriter::coerceToType(Expr *expr, Type toType,
77547767
}
77557768
}
77567769

7770+
// If we have a ClosureExpr, then we can safely propagate the
7771+
// 'nonisolated(nonsending)' isolation if it's not explicitly
7772+
// marked as `@concurrent`.
7773+
if (toEI.getIsolation().isNonIsolatedCaller() &&
7774+
(fromEI.getIsolation().isNonIsolated() &&
7775+
!isClosureMarkedAsConcurrent(expr))) {
7776+
auto newFromFuncType = fromFunc->withIsolation(
7777+
FunctionTypeIsolation::forNonIsolatedCaller());
7778+
if (applyTypeToClosureExpr(cs, expr, newFromFuncType)) {
7779+
fromFunc = newFromFuncType->castTo<FunctionType>();
7780+
// Propagating 'nonisolated(nonsending)' might have satisfied the entire
7781+
// conversion. If so, we're done, otherwise keep converting.
7782+
if (fromFunc->isEqual(toType))
7783+
return expr;
7784+
}
7785+
}
7786+
77577787
if (ctx.LangOpts.isDynamicActorIsolationCheckingEnabled()) {
77587788
// Passing a synchronous global actor-isolated function value and
77597789
// parameter that expects a synchronous non-isolated function type could

lib/Sema/TypeCheckConcurrency.cpp

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4701,6 +4701,13 @@ ActorIsolation ActorIsolationChecker::determineClosureIsolation(
47014701
}
47024702
}
47034703

4704+
// `nonisolated(nonsending)` inferred from the context makes
4705+
// the closure caller isolated.
4706+
if (auto *closureTy = getType(closure)->getAs<FunctionType>()) {
4707+
if (closureTy->getIsolation().isNonIsolatedCaller())
4708+
return ActorIsolation::forCallerIsolationInheriting();
4709+
}
4710+
47044711
// If a closure has an isolated parameter, it is isolated to that
47054712
// parameter.
47064713
for (auto param : *closure->getParameters()) {

test/Concurrency/Runtime/startSynchronously.swift

Lines changed: 49 additions & 85 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,23 @@ actor MyGlobalActor {
6464
static let shared: MyGlobalActor = MyGlobalActor()
6565
}
6666

67+
final class NaiveQueueExecutor: SerialExecutor {
68+
let queue: DispatchQueue
69+
70+
init(queue: DispatchQueue) {
71+
self.queue = queue
72+
}
73+
74+
public func enqueue(_ job: consuming ExecutorJob) {
75+
let unowned = UnownedJob(job)
76+
print("NaiveQueueExecutor(\(self.queue.label)) enqueue... [thread:\(getCurrentThreadID())]")
77+
queue.async {
78+
print("NaiveQueueExecutor(\(self.queue.label)) enqueue: run [thread:\(getCurrentThreadID())]")
79+
unowned.runSynchronously(on: self.asUnownedSerialExecutor())
80+
}
81+
}
82+
}
83+
6784
// Test on all platforms
6885
func syncOnMyGlobalActor() -> [Task<Void, Never>] {
6986
MyGlobalActor.shared.preconditionIsolated("Should be executing on the global actor here")
@@ -189,7 +206,7 @@ syncOnNonTaskThread(synchronousTask: behavior)
189206
// CHECK: after startSynchronously, outside; cancel (wakeup) the synchronous task! [thread:[[CALLING_THREAD3]]]
190207

191208
print("\n\n==== ------------------------------------------------------------------")
192-
print("callActorFromStartSynchronousTask()")
209+
print("callActorFromStartSynchronousTask() - not on specific queue")
193210
callActorFromStartSynchronousTask(recipient: .recipient(Recipient()))
194211

195212
// CHECK: callActorFromStartSynchronousTask()
@@ -203,11 +220,6 @@ callActorFromStartSynchronousTask(recipient: .recipient(Recipient()))
203220
// CHECK-NOT: ERROR!
204221
// CHECK: inside startSynchronously, call rec.sync() done
205222

206-
// CHECK-NOT: ERROR!
207-
// CHECK: inside startSynchronously, call rec.async()
208-
// CHECK-NOT: ERROR!
209-
// CHECK: inside startSynchronously, call rec.async() done
210-
211223
// CHECK-NOT: ERROR!
212224
// CHECK: inside startSynchronously, done
213225

@@ -219,35 +231,20 @@ enum TargetActorToCall {
219231
}
220232

221233
protocol RecipientProtocol where Self: Actor {
222-
func sync(syncTaskThreadID: ThreadID) async
223-
func async(syncTaskThreadID: ThreadID) async
234+
func callAndSuspend(syncTaskThreadID: ThreadID) async
224235
}
225236

226237
// default actor, must not declare an 'unownedExecutor'
227-
actor Recipient {
228-
func sync(syncTaskThreadID: ThreadID) {
229-
self.preconditionIsolated()
230-
231-
print("\(Recipient.self)/\(#function) Current actor thread id = \(getCurrentThreadID()) @ :\(#line)")
232-
if compareThreadIDs(syncTaskThreadID, .equal, getCurrentThreadID()) {
233-
print("NOTICE: Actor must not run on the synchronous task's thread :\(#line)")
234-
}
235-
}
236-
237-
func async(syncTaskThreadID: ThreadID) async {
238+
actor Recipient: RecipientProtocol {
239+
func callAndSuspend(syncTaskThreadID: ThreadID) async {
238240
self.preconditionIsolated()
239241

240-
// Dispatch may end up reusing the thread used to service the queue so we
241-
// cannot truly assert exact thread identity in such tests.
242-
// Usually this will be on a different thread by now though.
243242
print("\(Recipient.self)/\(#function) Current actor thread id = \(getCurrentThreadID()) @ :\(#line)")
244243
if compareThreadIDs(syncTaskThreadID, .equal, getCurrentThreadID()) {
245244
print("NOTICE: Actor must not run on the synchronous task's thread :\(#line)")
246245
}
247246

248-
await Task {
249-
self.preconditionIsolated()
250-
}.value
247+
try? await Task.sleep(for: .milliseconds(100))
251248
}
252249
}
253250

@@ -274,8 +271,8 @@ func callActorFromStartSynchronousTask(recipient rec: TargetActorToCall) {
274271

275272
print("inside startSynchronously, call rec.sync() [thread:\(getCurrentThreadID())] @ :\(#line)")
276273
switch rec {
277-
case .recipient(let recipient): await recipient.sync(syncTaskThreadID: innerTID)
278-
case .recipientOnQueue(let recipient): await recipient.sync(syncTaskThreadID: innerTID)
274+
case .recipient(let recipient): await recipient.callAndSuspend(syncTaskThreadID: innerTID)
275+
case .recipientOnQueue(let recipient): await recipient.callAndSuspend(syncTaskThreadID: innerTID)
279276
}
280277
print("inside startSynchronously, call rec.sync() done [thread:\(getCurrentThreadID())] @ :\(#line)")
281278

@@ -290,22 +287,6 @@ func callActorFromStartSynchronousTask(recipient rec: TargetActorToCall) {
290287
print("NOTICE: Task resumed on same thread as it entered the synchronous task!")
291288
}
292289

293-
print("inside startSynchronously, call rec.async() [thread:\(getCurrentThreadID())] @ :\(#line)")
294-
switch rec {
295-
case .recipient(let recipient): await recipient.async(syncTaskThreadID: innerTID)
296-
case .recipientOnQueue(let recipient): await recipient.async(syncTaskThreadID: innerTID)
297-
}
298-
print("inside startSynchronously, call rec.async() done [thread:\(getCurrentThreadID())] @ :\(#line)")
299-
300-
print("Inner thread id = \(innerTID)")
301-
print("Current thread id = \(getCurrentThreadID())")
302-
// Dispatch may end up reusing the thread used to service the queue so we
303-
// cannot truly assert exact thread identity in such tests.
304-
// Usually this will be on a different thread by now though.
305-
if compareThreadIDs(innerTID, .equal, getCurrentThreadID()) {
306-
print("NOTICE: Task resumed on same thread as it entered the synchronous task!")
307-
}
308-
309290
print("inside startSynchronously, done [thread:\(getCurrentThreadID())] @ :\(#line)")
310291
sem1.signal()
311292
}
@@ -323,6 +304,28 @@ print("callActorFromStartSynchronousTask() - actor in custom executor with its o
323304
let actorQueue = DispatchQueue(label: "recipient-actor-queue")
324305
callActorFromStartSynchronousTask(recipient: .recipientOnQueue(RecipientOnQueue(queue: actorQueue)))
325306

307+
308+
// 50: callActorFromStartSynchronousTask()
309+
// 51: before startSynchronously [thread:0x00007000054f5000] @ :366
310+
// 52: inside startSynchronously [thread:0x00007000054f5000] @ :372
311+
// 53: inside startSynchronously, call rec.sync() [thread:0x00007000054f5000] @ :380
312+
// 54: Recipient/sync(syncTaskThreadID:) Current actor thread id = 0x000070000567e000 @ :336
313+
// 55: inside startSynchronously, call rec.sync() done [thread:0x000070000567e000] @ :385
314+
// 56: Inner thread id = 0x00007000054f5000
315+
// 57: Current thread id = 0x000070000567e000
316+
// 60: after startSynchronously [thread:0x00007000054f5000] @ :418
317+
// 61: - async work on queue
318+
// 62: - async work on queue
319+
// 63: - async work on queue
320+
// 64: - async work on queue
321+
// 65: - async work on queue
322+
// 67: - async work on queue
323+
// 68: - async work on queue
324+
// 69: - async work on queue
325+
// 71: Inner thread id = 0x00007000054f5000
326+
// 72: Current thread id = 0x000070000567e000
327+
// 73: inside startSynchronously, done [thread:0x000070000567e000] @ :414
328+
326329
// CHECK-LABEL: callActorFromStartSynchronousTask() - actor in custom executor with its own queue
327330
// No interleaving allowed between "before" and "inside":
328331
// CHECK: before startSynchronously [thread:[[CALLING_THREAD4:.*]]]
@@ -333,38 +336,14 @@ callActorFromStartSynchronousTask(recipient: .recipientOnQueue(RecipientOnQueue(
333336
// allowing the 'after startSynchronously' to run.
334337
//
335338
// CHECK-NEXT: inside startSynchronously, call rec.sync() [thread:[[CALLING_THREAD4]]]
336-
// CHECK: NaiveQueueExecutor(recipient-actor-queue) enqueue
337339
// CHECK: after startSynchronously
338340
// CHECK-NOT: ERROR!
339341
// CHECK: inside startSynchronously, call rec.sync() done
340342

341-
// CHECK-NOT: ERROR!
342-
// CHECK: inside startSynchronously, call rec.async()
343-
// CHECK: NaiveQueueExecutor(recipient-actor-queue) enqueue
344-
// CHECK-NOT: ERROR!
345-
// CHECK: inside startSynchronously, call rec.async() done
346-
347343
// CHECK-NOT: ERROR!
348344
// CHECK: inside startSynchronously, done
349345

350-
final class NaiveQueueExecutor: SerialExecutor {
351-
let queue: DispatchQueue
352-
353-
init(queue: DispatchQueue) {
354-
self.queue = queue
355-
}
356-
357-
public func enqueue(_ job: consuming ExecutorJob) {
358-
let unowned = UnownedJob(job)
359-
print("NaiveQueueExecutor(\(self.queue.label)) enqueue... [thread:\(getCurrentThreadID())]")
360-
queue.async {
361-
print("NaiveQueueExecutor(\(self.queue.label)) enqueue: run [thread:\(getCurrentThreadID())]")
362-
unowned.runSynchronously(on: self.asUnownedSerialExecutor())
363-
}
364-
}
365-
}
366-
367-
actor RecipientOnQueue {
346+
actor RecipientOnQueue: RecipientProtocol {
368347
let executor: NaiveQueueExecutor
369348
nonisolated let unownedExecutor: UnownedSerialExecutor
370349

@@ -373,30 +352,15 @@ actor RecipientOnQueue {
373352
self.unownedExecutor = executor.asUnownedSerialExecutor()
374353
}
375354

376-
func sync(syncTaskThreadID: ThreadID) {
377-
self.preconditionIsolated()
378-
dispatchPrecondition(condition: .onQueue(self.executor.queue))
379-
380-
print("\(Recipient.self)/\(#function) Current actor thread id = \(getCurrentThreadID()) @ :\(#line)")
381-
if compareThreadIDs(syncTaskThreadID, .equal, getCurrentThreadID()) {
382-
print("NOTICE: Actor must not run on the synchronous task's thread :\(#line)")
383-
}
384-
}
385-
386-
func async(syncTaskThreadID: ThreadID) async {
355+
func callAndSuspend(syncTaskThreadID: ThreadID) async {
387356
self.preconditionIsolated()
388357
dispatchPrecondition(condition: .onQueue(self.executor.queue))
389358

390-
// Dispatch may end up reusing the thread used to service the queue so we
391-
// cannot truly assert exact thread identity in such tests.
392-
// Usually this will be on a different thread by now though.
393359
print("\(Recipient.self)/\(#function) Current actor thread id = \(getCurrentThreadID()) @ :\(#line)")
394360
if compareThreadIDs(syncTaskThreadID, .equal, getCurrentThreadID()) {
395361
print("NOTICE: Actor must not run on the synchronous task's thread :\(#line)")
396362
}
397363

398-
await Task {
399-
self.preconditionIsolated()
400-
}.value
364+
try? await Task.sleep(for: .milliseconds(100))
401365
}
402366
}

test/Concurrency/attr_execution/conversions_silgen.swift

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -425,3 +425,34 @@ func conversionsFromSyncToAsync(_ x: @escaping @Sendable (NonSendableKlass) -> V
425425
let _: @concurrent (SendableKlass) async -> Void = y
426426
let _: @concurrent (NonSendableKlass) async -> Void = z
427427
}
428+
429+
func testThatClosuresAssumeIsolation(fn: inout nonisolated(nonsending) (Int) async -> Void) {
430+
// CHECK-LABEL: sil private [ossa] @$s21attr_execution_silgen31testThatClosuresAssumeIsolation2fnyySiYaYCcz_tFyyYaYCcfU_ : $@convention(thin) @async (@sil_isolated @sil_implicit_leading_param @guaranteed Optional<any Actor>) -> ()
431+
// CHECK: bb0([[EXECUTOR:%.*]] : @guaranteed $Optional<any Actor>):
432+
// CHECK: hop_to_executor [[EXECUTOR]]
433+
let _: nonisolated(nonsending) () async -> Void = {
434+
42
435+
}
436+
437+
func testParam(_: nonisolated(nonsending) () async throws -> Void) {}
438+
439+
// CHECK-LABEL: sil private [ossa] @$s21attr_execution_silgen31testThatClosuresAssumeIsolation2fnyySiYaYCcz_tFyyYaYCXEfU0_ : $@convention(thin) @async (@sil_isolated @sil_implicit_leading_param @guaranteed Optional<any Actor>) -> @error any Error
440+
// CHECK: bb0([[EXECUTOR:%.*]] : @guaranteed $Optional<any Actor>):
441+
// CHECK: hop_to_executor [[EXECUTOR]]
442+
testParam { 42 }
443+
444+
// CHECK-LABEL: sil private [ossa] @$s21attr_execution_silgen31testThatClosuresAssumeIsolation2fnyySiYaYCcz_tFyyYaXEfU1_ : $@convention(thin) @async () -> ()
445+
// CHECK: [[GENERIC_EXECUTOR:%.*]] = enum $Optional<Builtin.Executor>, #Optional.none!enumelt
446+
// CHECK: hop_to_executor [[GENERIC_EXECUTOR]]
447+
testParam { @concurrent in 42 }
448+
449+
// CHECK-LABEL: sil private [ossa] @$s21attr_execution_silgen31testThatClosuresAssumeIsolation2fnyySiYaYCcz_tFySiYaYCcfU2_ : $@convention(thin) @async (@sil_isolated @sil_implicit_leading_param @guaranteed Optional<any Actor>, Int) -> ()
450+
// CHECK: bb0([[EXECUTOR:%.*]] : @guaranteed $Optional<any Actor>, %1 : $Int):
451+
// CHECK: hop_to_executor [[EXECUTOR]]
452+
fn = { _ in }
453+
454+
// CHECK-LABEL: sil private [ossa] @$s21attr_execution_silgen31testThatClosuresAssumeIsolation2fnyySiYaYCcz_tFySiYacfU3_ : $@convention(thin) @async (Int) -> ()
455+
// CHECK: [[GENERIC_EXECUTOR:%.*]] = enum $Optional<Builtin.Executor>, #Optional.none!enumelt
456+
// CHECK: hop_to_executor [[GENERIC_EXECUTOR]]
457+
fn = { @concurrent _ in }
458+
}

0 commit comments

Comments
 (0)