Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion lib/SILGen/SILGenConcurrency.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -206,10 +206,14 @@ void SILGenFunction::emitExpectedExecutorProlog() {
switch (actorIsolation.getKind()) {
case ActorIsolation::Unspecified:
case ActorIsolation::Nonisolated:
case ActorIsolation::CallerIsolationInheriting:
case ActorIsolation::NonisolatedUnsafe:
break;

case ActorIsolation::CallerIsolationInheriting:
assert(F.isAsync());
setExpectedExecutorForParameterIsolation(*this, actorIsolation);
break;

case ActorIsolation::Erased:
llvm_unreachable("closure cannot have erased isolation");

Expand Down
42 changes: 36 additions & 6 deletions lib/Sema/CSApply.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6514,18 +6514,31 @@ ArgumentList *ExprRewriter::coerceCallArguments(
return ArgumentList::createTypeChecked(ctx, args, newArgs);
}

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

if (auto CLE = dyn_cast<CaptureListExpr>(expr))
return closureInheritsActorContext(CLE->getClosureBody());
return isExplicitClosureExpr(CLE->getClosureBody());

return dyn_cast<ClosureExpr>(expr);
}

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

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

Expand Down Expand Up @@ -7767,6 +7780,23 @@ Expr *ExprRewriter::coerceToType(Expr *expr, Type toType,
}
}

// If we have a ClosureExpr, then we can safely propagate the
// 'nonisolated(nonsending)' isolation if it's not explicitly
// marked as `@concurrent`.
if (toEI.getIsolation().isNonIsolatedCaller() &&
(fromEI.getIsolation().isNonIsolated() &&
!isClosureMarkedAsConcurrent(expr))) {
auto newFromFuncType = fromFunc->withIsolation(
FunctionTypeIsolation::forNonIsolatedCaller());
if (applyTypeToClosureExpr(cs, expr, newFromFuncType)) {
fromFunc = newFromFuncType->castTo<FunctionType>();
// Propagating 'nonisolated(nonsending)' might have satisfied the entire
// conversion. If so, we're done, otherwise keep converting.
if (fromFunc->isEqual(toType))
return expr;
}
}

if (ctx.LangOpts.isDynamicActorIsolationCheckingEnabled()) {
// Passing a synchronous global actor-isolated function value and
// parameter that expects a synchronous non-isolated function type could
Expand Down
7 changes: 7 additions & 0 deletions lib/Sema/TypeCheckConcurrency.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4696,6 +4696,13 @@ ActorIsolation ActorIsolationChecker::determineClosureIsolation(
}
}

// `nonisolated(nonsending)` inferred from the context makes
// the closure caller isolated.
if (auto *closureTy = getType(closure)->getAs<FunctionType>()) {
if (closureTy->getIsolation().isNonIsolatedCaller())
return ActorIsolation::forCallerIsolationInheriting();
}

// If a closure has an isolated parameter, it is isolated to that
// parameter.
for (auto param : *closure->getParameters()) {
Expand Down
31 changes: 31 additions & 0 deletions test/Concurrency/attr_execution/conversions_silgen.swift
Original file line number Diff line number Diff line change
Expand Up @@ -425,3 +425,34 @@ func conversionsFromSyncToAsync(_ x: @escaping @Sendable (NonSendableKlass) -> V
let _: @concurrent (SendableKlass) async -> Void = y
let _: @concurrent (NonSendableKlass) async -> Void = z
}

func testThatClosuresAssumeIsolation(fn: inout nonisolated(nonsending) (Int) async -> Void) {
// CHECK-LABEL: sil private [ossa] @$s21attr_execution_silgen31testThatClosuresAssumeIsolation2fnyySiYaYCcz_tFyyYaYCcfU_ : $@convention(thin) @async (@sil_isolated @sil_implicit_leading_param @guaranteed Optional<any Actor>) -> ()
// CHECK: bb0([[EXECUTOR:%.*]] : @guaranteed $Optional<any Actor>):
// CHECK: hop_to_executor [[EXECUTOR]]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, thank you!

let _: nonisolated(nonsending) () async -> Void = {
42
}

func testParam(_: nonisolated(nonsending) () async throws -> Void) {}

// 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
// CHECK: bb0([[EXECUTOR:%.*]] : @guaranteed $Optional<any Actor>):
// CHECK: hop_to_executor [[EXECUTOR]]
testParam { 42 }

// CHECK-LABEL: sil private [ossa] @$s21attr_execution_silgen31testThatClosuresAssumeIsolation2fnyySiYaYCcz_tFyyYaXEfU1_ : $@convention(thin) @async () -> ()
// CHECK: [[GENERIC_EXECUTOR:%.*]] = enum $Optional<Builtin.Executor>, #Optional.none!enumelt
// CHECK: hop_to_executor [[GENERIC_EXECUTOR]]
testParam { @concurrent in 42 }

// CHECK-LABEL: sil private [ossa] @$s21attr_execution_silgen31testThatClosuresAssumeIsolation2fnyySiYaYCcz_tFySiYaYCcfU2_ : $@convention(thin) @async (@sil_isolated @sil_implicit_leading_param @guaranteed Optional<any Actor>, Int) -> ()
// CHECK: bb0([[EXECUTOR:%.*]] : @guaranteed $Optional<any Actor>, %1 : $Int):
// CHECK: hop_to_executor [[EXECUTOR]]
fn = { _ in }

// CHECK-LABEL: sil private [ossa] @$s21attr_execution_silgen31testThatClosuresAssumeIsolation2fnyySiYaYCcz_tFySiYacfU3_ : $@convention(thin) @async (Int) -> ()
// CHECK: [[GENERIC_EXECUTOR:%.*]] = enum $Optional<Builtin.Executor>, #Optional.none!enumelt
// CHECK: hop_to_executor [[GENERIC_EXECUTOR]]
fn = { @concurrent _ in }
}