Skip to content

Commit 2827804

Browse files
authored
Merge pull request #41376 from rjmccall/unsafe-inherits-executor
Inherit the caller's executor in `with*Continuation`
2 parents 70e7e84 + 84c7494 commit 2827804

File tree

15 files changed

+129
-17
lines changed

15 files changed

+129
-17
lines changed

docs/ReferenceGuides/UnderscoredAttributes.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -825,3 +825,8 @@ also locks.
825825
Marks a synchronous API as being unavailable from asynchronous contexts. Direct
826826
usage of annotated API from asynchronous contexts will result in a warning from
827827
the compiler.
828+
829+
## `@_unsafeInheritExecutor`
830+
831+
This `async` function uses the pre-SE-0338 semantics of unsafely inheriting the caller's executor. This is an underscored feature because the right way of inheriting an executor is to pass in the required executor and switch to it. Unfortunately, there are functions in the standard library which need to inherit their caller's executor but cannot change their ABI because they were not defined as `@_alwaysEmitIntoClient` in the initial release.
832+

include/swift/AST/Attr.def

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -636,7 +636,11 @@ CONTEXTUAL_SIMPLE_DECL_ATTR(nonisolated, Nonisolated,
636636
112)
637637

638638
// 113 was experimental _unsafeSendable and is now unused
639-
// 114 was experimental _unsafeMainActor and is now unused
639+
640+
SIMPLE_DECL_ATTR(_unsafeInheritExecutor, UnsafeInheritExecutor,
641+
OnFunc | UserInaccessible |
642+
ABIStableToAdd | ABIStableToRemove | APIStableToAdd | APIBreakingToRemove,
643+
114) // previously experimental _unsafeMainActor
640644

641645
SIMPLE_DECL_ATTR(_implicitSelfCapture, ImplicitSelfCapture,
642646
OnParam | UserInaccessible |

include/swift/AST/DiagnosticsSema.def

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3164,6 +3164,9 @@ ERROR(override_reasync_with_non_reasync,none,
31643164
ERROR(reasync_without_async_parameter,none,
31653165
"'reasync' function must take an 'async' function argument", ())
31663166

3167+
ERROR(inherits_executor_without_async,none,
3168+
"non-async functions cannot inherit an executor", ())
3169+
31673170
ERROR(autoclosure_function_type,none,
31683171
"@autoclosure attribute only applies to function types",
31693172
())

include/swift/Basic/Features.def

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,5 +59,6 @@ LANGUAGE_FEATURE(BuiltinCopy, 0, "Builtin.copy()", true)
5959
LANGUAGE_FEATURE(BuiltinStackAlloc, 0, "Builtin.stackAlloc", true)
6060
LANGUAGE_FEATURE(SpecializeAttributeWithAvailability, 0, "@_specialize attribute with availability", true)
6161
LANGUAGE_FEATURE(BuiltinAssumeAlignment, 0, "Builtin.assumeAlignment", true)
62+
LANGUAGE_FEATURE(UnsafeInheritExecutor, 0, "@_unsafeInheritExecutor", true)
6263

6364
#undef LANGUAGE_FEATURE

lib/AST/ASTPrinter.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2963,6 +2963,10 @@ static bool usesFeatureBuiltinAssumeAlignment(Decl *decl) {
29632963
return false;
29642964
}
29652965

2966+
static bool usesFeatureUnsafeInheritExecutor(Decl *decl) {
2967+
return decl->getAttrs().hasAttribute<UnsafeInheritExecutorAttr>();
2968+
}
2969+
29662970
/// Determine the set of "new" features used on a given declaration.
29672971
///
29682972
/// Note: right now, all features we check for are "new". At some point, we'll

lib/SILGen/ExecutorBreadcrumb.h

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -23,16 +23,16 @@ class SILGenFunction;
2323
/// Represents the information necessary to return to a caller's own
2424
/// active executor after making a hop to an actor for actor-isolated calls.
2525
class ExecutorBreadcrumb {
26-
SILValue Executor;
26+
bool mustReturnToExecutor;
2727

2828
public:
2929
// An empty breadcrumb, indicating no hop back is necessary.
30-
ExecutorBreadcrumb() : Executor() {}
30+
ExecutorBreadcrumb() : mustReturnToExecutor(false) {}
3131

32-
// A breadcrumb representing the need to hop back to the executor
33-
// represented by the given value.
34-
explicit ExecutorBreadcrumb(SILValue executor)
35-
: Executor(executor) {}
32+
// A breadcrumb representing the need to hop back to the expected
33+
// executor of the current function.
34+
explicit ExecutorBreadcrumb(bool mustReturnToExecutor)
35+
: mustReturnToExecutor(mustReturnToExecutor) {}
3636

3737
// Emits the hop back sequence, if any, necessary to get back to
3838
// the executor represented by this breadcrumb.

lib/SILGen/SILGenApply.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4552,7 +4552,7 @@ RValue SILGenFunction::emitApply(
45524552
// any sort of async function, we'll want to make sure to hop back to our
45534553
// own executor afterward, since the callee could have made arbitrary hops
45544554
// out of our isolation domain.
4555-
breadcrumb = ExecutorBreadcrumb(ExpectedExecutor);
4555+
breadcrumb = ExecutorBreadcrumb(true);
45564556
}
45574557

45584558
SILValue rawDirectResult;

lib/SILGen/SILGenFunction.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -864,6 +864,10 @@ class LLVM_LIBRARY_VISIBILITY SILGenFunction
864864
/// inside an async actor-independent function. No hop-back is expected.
865865
void emitHopToActorValue(SILLocation loc, ManagedValue actor);
866866

867+
/// Return true if the function being emitted is an async function
868+
/// that unsafely inherits its executor.
869+
bool unsafelyInheritsExecutor();
870+
867871
/// A version of `emitHopToTargetActor` that is specialized to the needs
868872
/// of various types of ConstructorDecls, like class or value initializers,
869873
/// because their prolog emission is not the same as for regular functions.

lib/SILGen/SILGenProlog.cpp

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -653,7 +653,7 @@ void SILGenFunction::emitProlog(CaptureInfo captureInfo,
653653

654654
// In async functions, the generic executor is our expected executor
655655
// if we don't have any sort of isolation.
656-
if (!ExpectedExecutor && F.isAsync()) {
656+
if (!ExpectedExecutor && F.isAsync() && !unsafelyInheritsExecutor()) {
657657
ExpectedExecutor = emitGenericExecutor(
658658
RegularLocation::getAutoGeneratedLocation(F.getLocation()));
659659
}
@@ -780,12 +780,8 @@ ExecutorBreadcrumb SILGenFunction::emitHopToTargetActor(SILLocation loc,
780780

781781
ExecutorBreadcrumb SILGenFunction::emitHopToTargetExecutor(
782782
SILLocation loc, SILValue executor) {
783-
// Record the previous executor to hop back to when we no longer need to
784-
// be isolated to the target actor.
785-
//
786-
// If we're calling from an actor method ourselves, then we'll want to hop
787-
// back to our own actor.
788-
auto breadcrumb = ExecutorBreadcrumb(emitGetCurrentExecutor(loc));
783+
// Record that we need to hop back to the current executor.
784+
auto breadcrumb = ExecutorBreadcrumb(true);
789785
B.createHopToExecutor(loc.asAutoGenerated(), executor, /*mandatory*/ false);
790786
return breadcrumb;
791787
}
@@ -856,9 +852,19 @@ void SILGenFunction::emitPreconditionCheckExpectedExecutor(
856852
SGFContext());
857853
}
858854

855+
bool SILGenFunction::unsafelyInheritsExecutor() {
856+
if (auto fn = dyn_cast<AbstractFunctionDecl>(FunctionDC))
857+
return fn->getAttrs().hasAttribute<UnsafeInheritExecutorAttr>();
858+
return false;
859+
}
860+
859861
void ExecutorBreadcrumb::emit(SILGenFunction &SGF, SILLocation loc) {
860-
if (Executor)
861-
SGF.B.createHopToExecutor(loc.asAutoGenerated(), Executor, /*mandatory*/ false);
862+
if (mustReturnToExecutor) {
863+
assert(SGF.ExpectedExecutor || SGF.unsafelyInheritsExecutor());
864+
if (auto executor = SGF.ExpectedExecutor)
865+
SGF.B.createHopToExecutor(loc.asAutoGenerated(), executor,
866+
/*mandatory*/ false);
867+
}
862868
}
863869

864870
SILValue SILGenFunction::emitGetCurrentExecutor(SILLocation loc) {

lib/Sema/TypeCheckAttr.cpp

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -275,6 +275,8 @@ class AttributeChecker : public AttributeVisitor<AttributeChecker> {
275275

276276
void visitUnavailableFromAsyncAttr(UnavailableFromAsyncAttr *attr);
277277

278+
void visitUnsafeInheritExecutorAttr(UnsafeInheritExecutorAttr *attr);
279+
278280
void visitPrimaryAssociatedTypeAttr(PrimaryAssociatedTypeAttr *attr);
279281

280282
void checkBackDeployAttrs(Decl *D, ArrayRef<BackDeployAttr *> Attrs);
@@ -5816,6 +5818,14 @@ void AttributeChecker::visitUnavailableFromAsyncAttr(
58165818
}
58175819
}
58185820

5821+
void AttributeChecker::visitUnsafeInheritExecutorAttr(
5822+
UnsafeInheritExecutorAttr *attr) {
5823+
auto fn = cast<FuncDecl>(D);
5824+
if (!fn->isAsyncContext()) {
5825+
diagnose(attr->getLocation(), diag::inherits_executor_without_async);
5826+
}
5827+
}
5828+
58195829
void AttributeChecker::visitPrimaryAssociatedTypeAttr(
58205830
PrimaryAssociatedTypeAttr *attr) {
58215831
}

lib/Sema/TypeCheckDeclOverride.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1569,6 +1569,8 @@ namespace {
15691569

15701570
UNINTERESTING_ATTR(BackDeploy)
15711571

1572+
UNINTERESTING_ATTR(UnsafeInheritExecutor)
1573+
15721574
#undef UNINTERESTING_ATTR
15731575

15741576
void visitAvailableAttr(AvailableAttr *attr) {

stdlib/public/Concurrency/CheckedContinuation.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -265,6 +265,8 @@ extension CheckedContinuation {
265265
/// - body: A closure that takes a `CheckedContinuation` parameter.
266266
/// You must resume the continuation exactly once.
267267
@available(SwiftStdlib 5.1, *)
268+
@_unsafeInheritExecutor // ABI compatibility with Swift 5.1
269+
@inlinable
268270
public func withCheckedContinuation<T>(
269271
function: String = #function,
270272
_ body: (CheckedContinuation<T, Never>) -> Void
@@ -287,6 +289,8 @@ public func withCheckedContinuation<T>(
287289
/// If `resume(throwing:)` is called on the continuation,
288290
/// this function throws that error.
289291
@available(SwiftStdlib 5.1, *)
292+
@_unsafeInheritExecutor // ABI compatibility with Swift 5.1
293+
@inlinable
290294
public func withCheckedThrowingContinuation<T>(
291295
function: String = #function,
292296
_ body: (CheckedContinuation<T, Error>) -> Void

stdlib/public/Concurrency/PartialAsyncTask.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -257,6 +257,7 @@ internal func _resumeUnsafeThrowingContinuationWithError<T>(
257257
///
258258
/// - Returns: The value passed to the continuation by the closure.
259259
@available(SwiftStdlib 5.1, *)
260+
@_unsafeInheritExecutor
260261
@_alwaysEmitIntoClient
261262
public func withUnsafeContinuation<T>(
262263
_ fn: (UnsafeContinuation<T, Never>) -> Void
@@ -277,6 +278,7 @@ public func withUnsafeContinuation<T>(
277278
/// If `resume(throwing:)` is called on the continuation,
278279
/// this function throws that error.
279280
@available(SwiftStdlib 5.1, *)
281+
@_unsafeInheritExecutor
280282
@_alwaysEmitIntoClient
281283
public func withUnsafeThrowingContinuation<T>(
282284
_ fn: (UnsafeContinuation<T, Error>) -> Void
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
// RUN: %target-swift-frontend -typecheck -verify -disable-availability-checking %s
2+
3+
// expected-error @+1 {{non-async functions cannot inherit an executor}}
4+
@_unsafeInheritExecutor
5+
func testNonAsync() {}
6+
7+
@_unsafeInheritExecutor
8+
func testAsync() async {}
9+
10+
struct A {
11+
// expected-error @+1 {{@_unsafeInheritExecutor may only be used on 'func' declarations}}
12+
@_unsafeInheritExecutor
13+
init() async {}
14+
15+
// expected-error @+1 {{non-async functions cannot inherit an executor}}
16+
@_unsafeInheritExecutor
17+
func testNonAsync() {}
18+
19+
@_unsafeInheritExecutor
20+
func testAsync() async {}
21+
}
22+
23+
24+
class NonSendableObject {
25+
var property = 0
26+
}
27+
28+
@_unsafeInheritExecutor
29+
func useNonSendable(object: NonSendableObject) async {}
30+
31+
actor MyActor {
32+
var object = NonSendableObject()
33+
func foo() async {
34+
// This should not be diagnosed when we implement SE-0338 checking.
35+
await useNonSendable(object: self.object)
36+
}
37+
}

test/SILGen/hop_to_executor.swift

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -438,3 +438,33 @@ func asyncWithIsolatedParam(i: Int, actor: isolated MyActor, d: Double) async {
438438
// CHECK-NEXT: hop_to_executor [[BORROWED]] : $MyActor
439439
// CHECK-NEXT: end_borrow [[BORROWED]] : $MyActor
440440
}
441+
442+
// CHECK-LABEL: sil hidden [ossa] @$s4test26asyncWithUnsafeInheritanceyyYaF
443+
@_unsafeInheritExecutor
444+
func asyncWithUnsafeInheritance() async {
445+
// CHECK-NEXT: bb0:
446+
// CHECK-NEXT: // function_ref
447+
// CHECK-NEXT: [[FN:%.*]] = function_ref
448+
// CHECK-NEXT: apply [[FN]]()
449+
// CHECK-NEXT: tuple ()
450+
// CHECK-NEXT: return
451+
await unspecifiedAsync()
452+
}
453+
454+
// CHECK-LABEL: sil hidden [ossa] @$s4test34asyncWithUnsafeInheritance_hopbackyyYaF
455+
@_unsafeInheritExecutor
456+
func asyncWithUnsafeInheritance_hopback() async {
457+
// CHECK: [[FN:%.*]] = function_ref @$s4test5redFnyySiF
458+
// CHECK-NEXT: [[METATYPE:%.*]] = metatype $@thin RedActor.Type
459+
// CHECK-NEXT: // function_ref
460+
// CHECK-NEXT: [[ACTOR_GETTER:%.*]] = function_ref @$s4test8RedActorV6sharedAA0bC4ImplCvgZ
461+
// CHECK-NEXT: [[ACTOR:%.*]] = apply [[ACTOR_GETTER]]([[METATYPE]])
462+
// CHECK-NEXT: [[BORROWED_ACTOR:%.*]] = begin_borrow [[ACTOR]]
463+
// CHECK-NEXT: hop_to_executor [[BORROWED_ACTOR]]
464+
// CHECK-NEXT: apply [[FN]]({{%.*}})
465+
// CHECK-NEXT: end_borrow [[BORROWED_ACTOR]]
466+
// CHECK-NEXT: destroy_value [[ACTOR]]
467+
// CHECK-NEXT: tuple
468+
// CHECK-NEXT: return
469+
await redFn(0)
470+
}

0 commit comments

Comments
 (0)