[Concurrency] Allow transferring nonisolated(nonsending) to isolation boundary closures

Isolation boundary closures don't assume parent isolation and should
be able to get `nonisolated(nonsending)` transferred to them jus
like regular nonisolated closures do.

Resolves: rdar://163792371
This commit is contained in:
Pavel Yaskevich
2025-11-19 13:19:49 -08:00
parent 5cc8264d8d
commit 2335293f0d
3 changed files with 40 additions and 11 deletions

View File

@@ -5013,6 +5013,10 @@ ActorIsolation ActorIsolationChecker::determineClosureIsolation(
auto normalIsolation = computeClosureIsolationFromParent(
closure, parentIsolation, checkIsolatedCapture);
bool isIsolationBoundary =
isIsolationInferenceBoundaryClosure(closure,
/*canInheritActorContext=*/true);
// The solver has to be conservative and produce a conversion to
// `nonisolated(nonsending)` because at solution application time
// we don't yet know whether there are any captures which would
@@ -5023,7 +5027,7 @@ ActorIsolation ActorIsolationChecker::determineClosureIsolation(
// isolated parameters. If our closure is nonisolated and we have a
// conversion to nonisolated(nonsending), then we should respect that.
if (auto *explicitClosure = dyn_cast<ClosureExpr>(closure);
!normalIsolation.isGlobalActor()) {
isIsolationBoundary || !normalIsolation.isGlobalActor()) {
if (auto *fce =
dyn_cast_or_null<FunctionConversionExpr>(Parent.getAsExpr())) {
auto expectedIsolation =
@@ -5042,8 +5046,7 @@ ActorIsolation ActorIsolationChecker::determineClosureIsolation(
// NOTE: Since we already checked for global actor isolated things, we
// know that all Sendable closures must be nonisolated. That is why it is
// safe to rely on this path to handle Sendable closures.
if (isIsolationInferenceBoundaryClosure(closure,
/*canInheritActorContext=*/true))
if (isIsolationBoundary)
return ActorIsolation::forNonisolated(/*unsafe=*/false);
return normalIsolation;

View File

@@ -659,3 +659,29 @@ func testSendableToSendableConversionWithNonisilatedNonsending() {
nonisolated(nonsending) @escaping (String) async throws -> String
) async throws -> Void = test
}
func testNonisolatedNonsendingClosureInGlobalActorContext() {
class NonSendable {
var state = ""
}
struct S {
static func compute(closure: nonisolated(nonsending) @Sendable @escaping (sending NonSendable) async -> Void) async {}
}
// CHECK-LABEL: sil private [ossa] @$s21attr_execution_silgen52testNonisolatedNonsendingClosureInGlobalActorContextyyF0D0L_yyYaF : $@convention(thin) @async () -> ()
// CHECK: [[CLOSURE:%.*]] = function_ref @$s21attr_execution_silgen52testNonisolatedNonsendingClosureInGlobalActorContextyyF0D0L_yyYaFyAaByyF11NonSendableL_CYuYaYbYCcfU_ : $@convention(thin) @Sendable @async (@sil_isolated @sil_implicit_leading_param @guaranteed Builtin.ImplicitActor, @sil_sending @guaranteed NonSendable) -> ()
// CHECK: [[THICK_CLOSURE:%.*]] = thin_to_thick_function [[CLOSURE]] to $@Sendable @async @callee_guaranteed (@sil_isolated @sil_implicit_leading_param @guaranteed Builtin.ImplicitActor, @sil_sending @guaranteed NonSendable) -> ()
// CHECK: [[CLOSURE_THUNK:%.*]] = function_ref @$sBA21attr_execution_silgen52testNonisolatedNonsendingClosureInGlobalActorContextyyF11NonSendableL_CIeghHgILgT_BAADIeghHgILxT_TR : $@convention(thin) @Sendable @async (@sil_isolated @sil_implicit_leading_param @guaranteed Builtin.ImplicitActor, @sil_sending @owned NonSendable, @guaranteed @Sendable @async @callee_guaranteed (@sil_isolated @sil_implicit_leading_param @guaranteed Builtin.ImplicitActor, @sil_sending @guaranteed NonSendable) -> ()) -> ()
// CHECK: [[THUNKED_CLOSURE:%.*]] = partial_apply [callee_guaranteed] [[CLOSURE_THUNK]]([[THICK_CLOSURE]])
// CHECK: [[COMPUTE:%.*]] = function_ref @$s21attr_execution_silgen52testNonisolatedNonsendingClosureInGlobalActorContextyyF1SL_V7compute7closureyyAaByyF11NonSendableL_CnYuYaYbYCc_tYaFZ : $@convention(method) @async (@guaranteed @Sendable @async @callee_guaranteed (@sil_isolated @sil_implicit_leading_param @guaranteed Builtin.ImplicitActor, @sil_sending @owned NonSendable) -> (), @thin S.Type) -> ()
// CHECK: apply [[COMPUTE]]([[THUNKED_CLOSURE]], {{.*}}) : $@convention(method) @async (@guaranteed @Sendable @async @callee_guaranteed (@sil_isolated @sil_implicit_leading_param @guaranteed Builtin.ImplicitActor, @sil_sending @owned NonSendable) -> (), @thin S.Type) -> ()
// CHECK: } // end sil function '$s21attr_execution_silgen52testNonisolatedNonsendingClosureInGlobalActorContextyyF0D0L_yyYaF'
@MainActor
func test() async {
// CHECK: // closure #1 in test #1 () in testNonisolatedNonsendingClosureInGlobalActorContext()
// CHECK: // Isolation: caller_isolation_inheriting
await S.compute { _ in
}
}
}

View File

@@ -143,11 +143,11 @@ func test_CallerSyncNormal_CalleeAsyncNonIsolated() async {
normalAcceptsAsyncClosure { }
// CHECK-LABEL: closure #2 in test_CallerSyncNormal_CalleeAsyncNonIsolated()
// CHECK-NEXT: Isolation: nonisolated
// CHECK-NEXT: Isolation: {{nonisolated|caller_isolation_inheriting}}
normalAcceptsSendingAsyncClosure { }
// CHECK-LABEL: // closure #3 in test_CallerSyncNormal_CalleeAsyncNonIsolated()
// CHECK-NEXT: // Isolation: nonisolated
// CHECK-NEXT: // Isolation: {{nonisolated|caller_isolation_inheriting}}
normalAcceptsSendableAsyncClosure { }
}
@@ -177,11 +177,11 @@ func test_CallerSyncNormal_CalleeAsyncMainActorIsolated() async {
// expected-ni-ns-note @-4 {{sending global actor 'CustomActor'-isolated value of non-Sendable type '@concurrent () async -> ()' to main actor-isolated global function 'normalGlobalActorAcceptsAsyncClosure' risks causing races in between global actor 'CustomActor'-isolated and main actor-isolated uses}}
// CHECK-LABEL: // closure #2 in test_CallerSyncNormal_CalleeAsyncMainActorIsolated()
// CHECK-NEXT: // Isolation: nonisolated
// CHECK-NEXT: Isolation: {{nonisolated|caller_isolation_inheriting}}
await normalGlobalActorAcceptsSendingAsyncClosure { }
// CHECK-LABEL: // closure #3 in test_CallerSyncNormal_CalleeAsyncMainActorIsolated()
// CHECK-NEXT: // Isolation: nonisolated
// CHECK-NEXT: // Isolation: {{nonisolated|caller_isolation_inheriting}}
await normalGlobalActorAcceptsSendableAsyncClosure { }
}
@@ -254,11 +254,11 @@ func test_CallerAsyncNormal_CalleeAsyncNonIsolated() async {
// expected-ni-note @-1 {{sending global actor 'CustomActor'-isolated value of non-Sendable type '() async -> ()' to nonisolated global function 'asyncNormalAcceptsAsyncClosure' risks causing races in between global actor 'CustomActor'-isolated and nonisolated uses}}
// CHECK-LABEL: closure #2 in test_CallerAsyncNormal_CalleeAsyncNonIsolated()
// CHECK-NEXT: Isolation: nonisolated
// CHECK-NEXT: Isolation: {{nonisolated|caller_isolation_inheriting}}
await asyncNormalAcceptsSendingAsyncClosure { }
// CHECK-LABEL: // closure #3 in test_CallerAsyncNormal_CalleeAsyncNonIsolated()
// CHECK-NEXT: // Isolation: nonisolated
// CHECK-NEXT: // Isolation: {{nonisolated|caller_isolation_inheriting}}
await asyncNormalAcceptsSendableAsyncClosure { }
}
@@ -296,11 +296,11 @@ func test_CallerAsyncNormal_CalleeAsyncMainActorIsolated() async {
// expected-ni-ns-note @-4 {{sending global actor 'CustomActor'-isolated value of non-Sendable type '@concurrent () async -> ()' to main actor-isolated global function 'asyncNormalGlobalActorAcceptsAsyncClosure' risks causing races in between global actor 'CustomActor'-isolated and main actor-isolated uses}}
// CHECK-LABEL: // closure #2 in test_CallerAsyncNormal_CalleeAsyncMainActorIsolated()
// CHECK-NEXT: // Isolation: nonisolated
// CHECK-NEXT: Isolation: {{nonisolated|caller_isolation_inheriting}}
await asyncNormalGlobalActorAcceptsSendingAsyncClosure { }
// CHECK-LABEL: // closure #3 in test_CallerAsyncNormal_CalleeAsyncMainActorIsolated()
// CHECK-NEXT: // Isolation: nonisolated
// CHECK-NEXT: // Isolation: {{nonisolated|caller_isolation_inheriting}}
await asyncNormalGlobalActorAcceptsSendableAsyncClosure { }
}