Skip to content

Commit f0bdecd

Browse files
authored
Merge pull request #80872 from xedin/rdar-149107104
[TypeChecker] Allow closures to assume `nonisolated(nonsending)`
2 parents ac07594 + eee53aa commit f0bdecd

File tree

5 files changed

+111
-75
lines changed

5 files changed

+111
-75
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
@@ -6514,18 +6514,31 @@ ArgumentList *ExprRewriter::coerceCallArguments(
65146514
return ArgumentList::createTypeChecked(ctx, args, newArgs);
65156515
}
65166516

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

65236523
if (auto CLE = dyn_cast<CaptureListExpr>(expr))
6524-
return closureInheritsActorContext(CLE->getClosureBody());
6524+
return isExplicitClosureExpr(CLE->getClosureBody());
6525+
6526+
return dyn_cast<ClosureExpr>(expr);
6527+
}
65256528

6526-
if (auto CE = dyn_cast<ClosureExpr>(expr))
6529+
/// Whether the given expression is a closure that should inherit
6530+
/// the actor context from where it was formed.
6531+
static bool closureInheritsActorContext(Expr *expr) {
6532+
if (auto *CE = isExplicitClosureExpr(expr))
65276533
return CE->inheritsActorContext();
6534+
return false;
6535+
}
65286536

6537+
/// Determine whether the given expression is a closure that
6538+
/// is explicitly marked as `@concurrent`.
6539+
static bool isClosureMarkedAsConcurrent(Expr *expr) {
6540+
if (auto *CE = isExplicitClosureExpr(expr))
6541+
return CE->getAttrs().hasAttribute<ConcurrentAttr>();
65296542
return false;
65306543
}
65316544

@@ -7767,6 +7780,23 @@ Expr *ExprRewriter::coerceToType(Expr *expr, Type toType,
77677780
}
77687781
}
77697782

7783+
// If we have a ClosureExpr, then we can safely propagate the
7784+
// 'nonisolated(nonsending)' isolation if it's not explicitly
7785+
// marked as `@concurrent`.
7786+
if (toEI.getIsolation().isNonIsolatedCaller() &&
7787+
(fromEI.getIsolation().isNonIsolated() &&
7788+
!isClosureMarkedAsConcurrent(expr))) {
7789+
auto newFromFuncType = fromFunc->withIsolation(
7790+
FunctionTypeIsolation::forNonIsolatedCaller());
7791+
if (applyTypeToClosureExpr(cs, expr, newFromFuncType)) {
7792+
fromFunc = newFromFuncType->castTo<FunctionType>();
7793+
// Propagating 'nonisolated(nonsending)' might have satisfied the entire
7794+
// conversion. If so, we're done, otherwise keep converting.
7795+
if (fromFunc->isEqual(toType))
7796+
return expr;
7797+
}
7798+
}
7799+
77707800
if (ctx.LangOpts.isDynamicActorIsolationCheckingEnabled()) {
77717801
// Passing a synchronous global actor-isolated function value and
77727802
// 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
@@ -4696,6 +4696,13 @@ ActorIsolation ActorIsolationChecker::determineClosureIsolation(
46964696
}
46974697
}
46984698

4699+
// `nonisolated(nonsending)` inferred from the context makes
4700+
// the closure caller isolated.
4701+
if (auto *closureTy = getType(closure)->getAs<FunctionType>()) {
4702+
if (closureTy->getIsolation().isNonIsolatedCaller())
4703+
return ActorIsolation::forCallerIsolationInheriting();
4704+
}
4705+
46994706
// If a closure has an isolated parameter, it is isolated to that
47004707
// parameter.
47014708
for (auto param : *closure->getParameters()) {

test/Concurrency/Runtime/startSynchronously.swift

Lines changed: 32 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -294,7 +294,7 @@ syncOnNonTaskThread(synchronousTask: behavior)
294294
// CHECK: after startSynchronously, outside; cancel (wakeup) the synchronous task! [thread:[[CALLING_THREAD3]]]
295295

296296
print("\n\n==== ------------------------------------------------------------------")
297-
print("callActorFromStartSynchronousTask()")
297+
print("callActorFromStartSynchronousTask() - not on specific queue")
298298
callActorFromStartSynchronousTask(recipient: .recipient(Recipient()))
299299

300300
// CHECK: callActorFromStartSynchronousTask()
@@ -308,11 +308,6 @@ callActorFromStartSynchronousTask(recipient: .recipient(Recipient()))
308308
// CHECK-NOT: ERROR!
309309
// CHECK: inside startSynchronously, call rec.sync() done
310310

311-
// CHECK-NOT: ERROR!
312-
// CHECK: inside startSynchronously, call rec.async()
313-
// CHECK-NOT: ERROR!
314-
// CHECK: inside startSynchronously, call rec.async() done
315-
316311
// CHECK-NOT: ERROR!
317312
// CHECK: inside startSynchronously, done
318313

@@ -324,35 +319,20 @@ enum TargetActorToCall {
324319
}
325320

326321
protocol RecipientProtocol where Self: Actor {
327-
func sync(syncTaskThreadID: ThreadID) async
328-
func async(syncTaskThreadID: ThreadID) async
322+
func callAndSuspend(syncTaskThreadID: ThreadID) async
329323
}
330324

331325
// default actor, must not declare an 'unownedExecutor'
332-
actor Recipient {
333-
func sync(syncTaskThreadID: ThreadID) {
326+
actor Recipient: RecipientProtocol {
327+
func callAndSuspend(syncTaskThreadID: ThreadID) async {
334328
self.preconditionIsolated()
335329

336330
print("\(Recipient.self)/\(#function) Current actor thread id = \(getCurrentThreadID()) @ :\(#line)")
337331
if compareThreadIDs(syncTaskThreadID, .equal, getCurrentThreadID()) {
338332
print("NOTICE: Actor must not run on the synchronous task's thread :\(#line)")
339333
}
340-
}
341-
342-
func async(syncTaskThreadID: ThreadID) async {
343-
self.preconditionIsolated()
344334

345-
// Dispatch may end up reusing the thread used to service the queue so we
346-
// cannot truly assert exact thread identity in such tests.
347-
// Usually this will be on a different thread by now though.
348-
print("\(Recipient.self)/\(#function) Current actor thread id = \(getCurrentThreadID()) @ :\(#line)")
349-
if compareThreadIDs(syncTaskThreadID, .equal, getCurrentThreadID()) {
350-
print("NOTICE: Actor must not run on the synchronous task's thread :\(#line)")
351-
}
352-
353-
await Task {
354-
self.preconditionIsolated()
355-
}.value
335+
try? await Task.sleep(for: .milliseconds(100))
356336
}
357337
}
358338

@@ -379,8 +359,8 @@ func callActorFromStartSynchronousTask(recipient rec: TargetActorToCall) {
379359

380360
print("inside startSynchronously, call rec.sync() [thread:\(getCurrentThreadID())] @ :\(#line)")
381361
switch rec {
382-
case .recipient(let recipient): await recipient.sync(syncTaskThreadID: innerTID)
383-
case .recipientOnQueue(let recipient): await recipient.sync(syncTaskThreadID: innerTID)
362+
case .recipient(let recipient): await recipient.callAndSuspend(syncTaskThreadID: innerTID)
363+
case .recipientOnQueue(let recipient): await recipient.callAndSuspend(syncTaskThreadID: innerTID)
384364
}
385365
print("inside startSynchronously, call rec.sync() done [thread:\(getCurrentThreadID())] @ :\(#line)")
386366

@@ -395,22 +375,6 @@ func callActorFromStartSynchronousTask(recipient rec: TargetActorToCall) {
395375
print("NOTICE: Task resumed on same thread as it entered the synchronous task!")
396376
}
397377

398-
print("inside startSynchronously, call rec.async() [thread:\(getCurrentThreadID())] @ :\(#line)")
399-
switch rec {
400-
case .recipient(let recipient): await recipient.async(syncTaskThreadID: innerTID)
401-
case .recipientOnQueue(let recipient): await recipient.async(syncTaskThreadID: innerTID)
402-
}
403-
print("inside startSynchronously, call rec.async() done [thread:\(getCurrentThreadID())] @ :\(#line)")
404-
405-
print("Inner thread id = \(innerTID)")
406-
print("Current thread id = \(getCurrentThreadID())")
407-
// Dispatch may end up reusing the thread used to service the queue so we
408-
// cannot truly assert exact thread identity in such tests.
409-
// Usually this will be on a different thread by now though.
410-
if compareThreadIDs(innerTID, .equal, getCurrentThreadID()) {
411-
print("NOTICE: Task resumed on same thread as it entered the synchronous task!")
412-
}
413-
414378
print("inside startSynchronously, done [thread:\(getCurrentThreadID())] @ :\(#line)")
415379
sem1.signal()
416380
}
@@ -428,6 +392,28 @@ print("callActorFromStartSynchronousTask() - actor in custom executor with its o
428392
let actorQueue = DispatchQueue(label: "recipient-actor-queue")
429393
callActorFromStartSynchronousTask(recipient: .recipientOnQueue(RecipientOnQueue(queue: actorQueue)))
430394

395+
396+
// 50: callActorFromStartSynchronousTask()
397+
// 51: before startSynchronously [thread:0x00007000054f5000] @ :366
398+
// 52: inside startSynchronously [thread:0x00007000054f5000] @ :372
399+
// 53: inside startSynchronously, call rec.sync() [thread:0x00007000054f5000] @ :380
400+
// 54: Recipient/sync(syncTaskThreadID:) Current actor thread id = 0x000070000567e000 @ :336
401+
// 55: inside startSynchronously, call rec.sync() done [thread:0x000070000567e000] @ :385
402+
// 56: Inner thread id = 0x00007000054f5000
403+
// 57: Current thread id = 0x000070000567e000
404+
// 60: after startSynchronously [thread:0x00007000054f5000] @ :418
405+
// 61: - async work on queue
406+
// 62: - async work on queue
407+
// 63: - async work on queue
408+
// 64: - async work on queue
409+
// 65: - async work on queue
410+
// 67: - async work on queue
411+
// 68: - async work on queue
412+
// 69: - async work on queue
413+
// 71: Inner thread id = 0x00007000054f5000
414+
// 72: Current thread id = 0x000070000567e000
415+
// 73: inside startSynchronously, done [thread:0x000070000567e000] @ :414
416+
431417
// CHECK-LABEL: callActorFromStartSynchronousTask() - actor in custom executor with its own queue
432418
// No interleaving allowed between "before" and "inside":
433419
// CHECK: before startSynchronously [thread:[[CALLING_THREAD4:.*]]]
@@ -438,21 +424,14 @@ callActorFromStartSynchronousTask(recipient: .recipientOnQueue(RecipientOnQueue(
438424
// allowing the 'after startSynchronously' to run.
439425
//
440426
// CHECK-NEXT: inside startSynchronously, call rec.sync() [thread:[[CALLING_THREAD4]]]
441-
// CHECK: NaiveQueueExecutor(recipient-actor-queue) enqueue
442427
// CHECK: after startSynchronously
443428
// CHECK-NOT: ERROR!
444429
// CHECK: inside startSynchronously, call rec.sync() done
445430

446-
// CHECK-NOT: ERROR!
447-
// CHECK: inside startSynchronously, call rec.async()
448-
// CHECK: NaiveQueueExecutor(recipient-actor-queue) enqueue
449-
// CHECK-NOT: ERROR!
450-
// CHECK: inside startSynchronously, call rec.async() done
451-
452431
// CHECK-NOT: ERROR!
453432
// CHECK: inside startSynchronously, done
454433

455-
actor RecipientOnQueue {
434+
actor RecipientOnQueue: RecipientProtocol {
456435
let executor: NaiveQueueExecutor
457436
nonisolated let unownedExecutor: UnownedSerialExecutor
458437

@@ -461,30 +440,15 @@ actor RecipientOnQueue {
461440
self.unownedExecutor = executor.asUnownedSerialExecutor()
462441
}
463442

464-
func sync(syncTaskThreadID: ThreadID) {
465-
self.preconditionIsolated()
466-
dispatchPrecondition(condition: .onQueue(self.executor.queue))
467-
468-
print("\(Recipient.self)/\(#function) Current actor thread id = \(getCurrentThreadID()) @ :\(#line)")
469-
if compareThreadIDs(syncTaskThreadID, .equal, getCurrentThreadID()) {
470-
print("NOTICE: Actor must not run on the synchronous task's thread :\(#line)")
471-
}
472-
}
473-
474-
func async(syncTaskThreadID: ThreadID) async {
443+
func callAndSuspend(syncTaskThreadID: ThreadID) async {
475444
self.preconditionIsolated()
476445
dispatchPrecondition(condition: .onQueue(self.executor.queue))
477446

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

486-
await Task {
487-
self.preconditionIsolated()
488-
}.value
452+
try? await Task.sleep(for: .milliseconds(100))
489453
}
490454
}

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)