Skip to content

Commit

Permalink
Implement @_unsafeInheritExecutor.
Browse files Browse the repository at this point in the history
SE-0338 changed the execution of non-actor async functions
so that they always hop to the generic executor, but some
functions need a way to suppress this so that they inherit
the caller's executor.

The right way to implement this is to have the caller pass
down the target executor in some reliable way and then
switch to it in all the appropriate places in the caller.
We might reasonably be able to build this on top of isolated
parameters, using some sort of default argument, or we might
need a wholly novel mechanism.

But those things are all ABI-breaking absent some sort of
guarantee about switching that we probably don't want to make,
and unfortunately we have functions in the library which we
need to export that need to inherit executors.  So in the
short term, we need some unsafe way of getting back to the
previous behavior.
  • Loading branch information
rjmccall committed Feb 15, 2022
1 parent 9cc5d12 commit b3b6701
Show file tree
Hide file tree
Showing 13 changed files with 123 additions and 17 deletions.
5 changes: 5 additions & 0 deletions docs/ReferenceGuides/UnderscoredAttributes.md
Original file line number Diff line number Diff line change
Expand Up @@ -825,3 +825,8 @@ also locks.
Marks a synchronous API as being unavailable from asynchronous contexts. Direct
usage of annotated API from asynchronous contexts will result in a warning from
the compiler.

## `@_unsafeInheritExecutor`

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.

6 changes: 5 additions & 1 deletion include/swift/AST/Attr.def
Original file line number Diff line number Diff line change
Expand Up @@ -636,7 +636,11 @@ CONTEXTUAL_SIMPLE_DECL_ATTR(nonisolated, Nonisolated,
112)

// 113 was experimental _unsafeSendable and is now unused
// 114 was experimental _unsafeMainActor and is now unused

SIMPLE_DECL_ATTR(_unsafeInheritExecutor, UnsafeInheritExecutor,
OnFunc | UserInaccessible |
ABIStableToAdd | ABIStableToRemove | APIStableToAdd | APIBreakingToRemove,
114) // previously experimental _unsafeMainActor

SIMPLE_DECL_ATTR(_implicitSelfCapture, ImplicitSelfCapture,
OnParam | UserInaccessible |
Expand Down
3 changes: 3 additions & 0 deletions include/swift/AST/DiagnosticsSema.def
Original file line number Diff line number Diff line change
Expand Up @@ -3164,6 +3164,9 @@ ERROR(override_reasync_with_non_reasync,none,
ERROR(reasync_without_async_parameter,none,
"'reasync' function must take an 'async' function argument", ())

ERROR(inherits_executor_without_async,none,
"non-async functions cannot inherit an executor", ())

ERROR(autoclosure_function_type,none,
"@autoclosure attribute only applies to function types",
())
Expand Down
1 change: 1 addition & 0 deletions include/swift/Basic/Features.def
Original file line number Diff line number Diff line change
Expand Up @@ -59,5 +59,6 @@ LANGUAGE_FEATURE(BuiltinCopy, 0, "Builtin.copy()", true)
LANGUAGE_FEATURE(BuiltinStackAlloc, 0, "Builtin.stackAlloc", true)
LANGUAGE_FEATURE(SpecializeAttributeWithAvailability, 0, "@_specialize attribute with availability", true)
LANGUAGE_FEATURE(BuiltinAssumeAlignment, 0, "Builtin.assumeAlignment", true)
LANGUAGE_FEATURE(UnsafeInheritExecutor, 0, "@_unsafeInheritExecutor", true)

#undef LANGUAGE_FEATURE
4 changes: 4 additions & 0 deletions lib/AST/ASTPrinter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2963,6 +2963,10 @@ static bool usesFeatureBuiltinAssumeAlignment(Decl *decl) {
return false;
}

static bool usesFeatureUnsafeInheritExecutor(Decl *decl) {
return decl->getAttrs().hasAttribute<UnsafeInheritExecutorAttr>();
}

/// Determine the set of "new" features used on a given declaration.
///
/// Note: right now, all features we check for are "new". At some point, we'll
Expand Down
12 changes: 6 additions & 6 deletions lib/SILGen/ExecutorBreadcrumb.h
Original file line number Diff line number Diff line change
Expand Up @@ -23,16 +23,16 @@ class SILGenFunction;
/// Represents the information necessary to return to a caller's own
/// active executor after making a hop to an actor for actor-isolated calls.
class ExecutorBreadcrumb {
SILValue Executor;
bool mustReturnToExecutor;

public:
// An empty breadcrumb, indicating no hop back is necessary.
ExecutorBreadcrumb() : Executor() {}
ExecutorBreadcrumb() : mustReturnToExecutor(false) {}

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

// Emits the hop back sequence, if any, necessary to get back to
// the executor represented by this breadcrumb.
Expand Down
2 changes: 1 addition & 1 deletion lib/SILGen/SILGenApply.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4552,7 +4552,7 @@ RValue SILGenFunction::emitApply(
// any sort of async function, we'll want to make sure to hop back to our
// own executor afterward, since the callee could have made arbitrary hops
// out of our isolation domain.
breadcrumb = ExecutorBreadcrumb(ExpectedExecutor);
breadcrumb = ExecutorBreadcrumb(true);
}

SILValue rawDirectResult;
Expand Down
4 changes: 4 additions & 0 deletions lib/SILGen/SILGenFunction.h
Original file line number Diff line number Diff line change
Expand Up @@ -864,6 +864,10 @@ class LLVM_LIBRARY_VISIBILITY SILGenFunction
/// inside an async actor-independent function. No hop-back is expected.
void emitHopToActorValue(SILLocation loc, ManagedValue actor);

/// Return true if the function being emitted is an async function
/// that unsafely inherits its executor.
bool unsafelyInheritsExecutor();

/// A version of `emitHopToTargetActor` that is specialized to the needs
/// of various types of ConstructorDecls, like class or value initializers,
/// because their prolog emission is not the same as for regular functions.
Expand Down
24 changes: 15 additions & 9 deletions lib/SILGen/SILGenProlog.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -653,7 +653,7 @@ void SILGenFunction::emitProlog(CaptureInfo captureInfo,

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

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

bool SILGenFunction::unsafelyInheritsExecutor() {
if (auto fn = dyn_cast<AbstractFunctionDecl>(FunctionDC))
return fn->getAttrs().hasAttribute<UnsafeInheritExecutorAttr>();
return false;
}

void ExecutorBreadcrumb::emit(SILGenFunction &SGF, SILLocation loc) {
if (Executor)
SGF.B.createHopToExecutor(loc.asAutoGenerated(), Executor, /*mandatory*/ false);
if (mustReturnToExecutor) {
assert(SGF.ExpectedExecutor || SGF.unsafelyInheritsExecutor());
if (auto executor = SGF.ExpectedExecutor)
SGF.B.createHopToExecutor(loc.asAutoGenerated(), executor,
/*mandatory*/ false);
}
}

SILValue SILGenFunction::emitGetCurrentExecutor(SILLocation loc) {
Expand Down
10 changes: 10 additions & 0 deletions lib/Sema/TypeCheckAttr.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -275,6 +275,8 @@ class AttributeChecker : public AttributeVisitor<AttributeChecker> {

void visitUnavailableFromAsyncAttr(UnavailableFromAsyncAttr *attr);

void visitUnsafeInheritExecutorAttr(UnsafeInheritExecutorAttr *attr);

void visitPrimaryAssociatedTypeAttr(PrimaryAssociatedTypeAttr *attr);

void checkBackDeployAttrs(Decl *D, ArrayRef<BackDeployAttr *> Attrs);
Expand Down Expand Up @@ -5816,6 +5818,14 @@ void AttributeChecker::visitUnavailableFromAsyncAttr(
}
}

void AttributeChecker::visitUnsafeInheritExecutorAttr(
UnsafeInheritExecutorAttr *attr) {
auto fn = cast<FuncDecl>(D);
if (!fn->isAsyncContext()) {
diagnose(attr->getLocation(), diag::inherits_executor_without_async);
}
}

void AttributeChecker::visitPrimaryAssociatedTypeAttr(
PrimaryAssociatedTypeAttr *attr) {
}
Expand Down
2 changes: 2 additions & 0 deletions lib/Sema/TypeCheckDeclOverride.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1569,6 +1569,8 @@ namespace {

UNINTERESTING_ATTR(BackDeploy)

UNINTERESTING_ATTR(UnsafeInheritExecutor)

#undef UNINTERESTING_ATTR

void visitAvailableAttr(AvailableAttr *attr) {
Expand Down
37 changes: 37 additions & 0 deletions test/Concurrency/unsafe_inherit_executor.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
// RUN: %target-swift-frontend -typecheck -verify -disable-availability-checking %s

// expected-error @+1 {{non-async functions cannot inherit an executor}}
@_unsafeInheritExecutor
func testNonAsync() {}

@_unsafeInheritExecutor
func testAsync() async {}

struct A {
// expected-error @+1 {{@_unsafeInheritExecutor may only be used on 'func' declarations}}
@_unsafeInheritExecutor
init() async {}

// expected-error @+1 {{non-async functions cannot inherit an executor}}
@_unsafeInheritExecutor
func testNonAsync() {}

@_unsafeInheritExecutor
func testAsync() async {}
}


class NonSendableObject {
var property = 0
}

@_unsafeInheritExecutor
func useNonSendable(object: NonSendableObject) async {}

actor MyActor {
var object = NonSendableObject()
func foo() async {
// This should not be diagnosed when we implement SE-0338 checking.
await useNonSendable(object: self.object)
}
}
30 changes: 30 additions & 0 deletions test/SILGen/hop_to_executor.swift
Original file line number Diff line number Diff line change
Expand Up @@ -438,3 +438,33 @@ func asyncWithIsolatedParam(i: Int, actor: isolated MyActor, d: Double) async {
// CHECK-NEXT: hop_to_executor [[BORROWED]] : $MyActor
// CHECK-NEXT: end_borrow [[BORROWED]] : $MyActor
}

// CHECK-LABEL: sil hidden [ossa] @$s4test26asyncWithUnsafeInheritanceyyYaF
@_unsafeInheritExecutor
func asyncWithUnsafeInheritance() async {
// CHECK-NEXT: bb0:
// CHECK-NEXT: // function_ref
// CHECK-NEXT: [[FN:%.*]] = function_ref
// CHECK-NEXT: apply [[FN]]()
// CHECK-NEXT: tuple ()
// CHECK-NEXT: return
await unspecifiedAsync()
}

// CHECK-LABEL: sil hidden [ossa] @$s4test34asyncWithUnsafeInheritance_hopbackyyYaF
@_unsafeInheritExecutor
func asyncWithUnsafeInheritance_hopback() async {
// CHECK: [[FN:%.*]] = function_ref @$s4test5redFnyySiF
// CHECK-NEXT: [[METATYPE:%.*]] = metatype $@thin RedActor.Type
// CHECK-NEXT: // function_ref
// CHECK-NEXT: [[ACTOR_GETTER:%.*]] = function_ref @$s4test8RedActorV6sharedAA0bC4ImplCvgZ
// CHECK-NEXT: [[ACTOR:%.*]] = apply [[ACTOR_GETTER]]([[METATYPE]])
// CHECK-NEXT: [[BORROWED_ACTOR:%.*]] = begin_borrow [[ACTOR]]
// CHECK-NEXT: hop_to_executor [[BORROWED_ACTOR]]
// CHECK-NEXT: apply [[FN]]({{%.*}})
// CHECK-NEXT: end_borrow [[BORROWED_ACTOR]]
// CHECK-NEXT: destroy_value [[ACTOR]]
// CHECK-NEXT: tuple
// CHECK-NEXT: return
await redFn(0)
}

0 comments on commit b3b6701

Please sign in to comment.