Skip to content

[WIP] @_inheritActorContext(always) #81438

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all 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
4 changes: 2 additions & 2 deletions Runtimes/Core/Concurrency/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
add_subdirectory(InternalShims)

gyb_expand(TaskGroup+addTask.swift.gyb TaskGroup+addTask.swift)
gyb_expand(Task+startSynchronously.swift.gyb Task+startSynchronously.swift)
gyb_expand(Task+immediate.swift.gyb Task+immediate.swift)

add_library(swift_Concurrency
Actor.cpp
Expand Down Expand Up @@ -97,7 +97,7 @@ add_library(swift_Concurrency
TaskSleep.swift
TaskSleepDuration.swift
"${CMAKE_CURRENT_BINARY_DIR}/TaskGroup+addTask.swift"
"${CMAKE_CURRENT_BINARY_DIR}/Task+startSynchronously.swift")
"${CMAKE_CURRENT_BINARY_DIR}/Task+immediate.swift")

include(${SwiftCore_CONCURRENCY_GLOBAL_EXECUTOR}.cmake)
target_compile_definitions(swift_Concurrency PRIVATE
Expand Down
37 changes: 37 additions & 0 deletions docs/ReferenceGuides/UnderscoredAttributes.md
Original file line number Diff line number Diff line change
Expand Up @@ -600,6 +600,43 @@ inherit the actor context (i.e. what actor it should be run on) based on the
declaration site of the closure rather than be non-Sendable. This does not do
anything if the closure is synchronous.

This works with global actors as expected:

```swift
@MainActor
func test() {
Task { /* main actor isolated */ }
}
```

However, for the inference to work with instance actors (i.e. `isolated` parameters),
the closure must capture the isolated parameter explicitly:

```swift
func test(actor: isolated (any Actor)) {
Task { /* non isolated */ } // !!!
}

func test(actor: isolated (any Actor)) {
Task { // @_inheritActorContext
_ = actor // 'actor'-isolated
}
}
```

The attribute takes an optional modifier '`always`', which changes this behavior
and *always* captures the enclosing isolated context, rather than forcing developers
to perform the explicit capture themselfes:

```swift
func test(actor: isolated (any Actor)) {
Task.immediate { // @_inheritActorContext(always)
// 'actor'-isolated!
// (without having to capture 'actor explicitly')
}
}
```

DISCUSSION: The reason why this does nothing when the closure is synchronous is
since it does not have the ability to hop to the appropriate executor before it
is run, so we may create concurrency errors.
Expand Down
2 changes: 1 addition & 1 deletion include/swift/ABI/Executor.h
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ class SerialExecutorRef {
/// Executor that may need to participate in complex "same context" checks,
/// by invoking `isSameExclusiveExecutionContext` when comparing execution contexts.
ComplexEquality = 0b01,
/// Mark this executor as the one used by `Task.startSynchronously`,
/// Mark this executor as the one used by `Task.immediate`,
/// It cannot participate in switching.
// TODO: Perhaps make this a generic "cannot switch" rather than start synchronously specific.
StartSynchronously = 0b10,
Expand Down
52 changes: 51 additions & 1 deletion include/swift/AST/Attr.h
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
#ifndef SWIFT_ATTR_H
#define SWIFT_ATTR_H

#include "Attr.h"
#include "swift/AST/ASTAllocated.h"
#include "swift/AST/AttrKind.h"
#include "swift/AST/AutoDiff.h"
Expand All @@ -42,10 +43,10 @@
#include "swift/Basic/SourceLoc.h"
#include "swift/Basic/UUID.h"
#include "swift/Basic/Version.h"
#include "llvm/ADT/bit.h"
#include "llvm/ADT/DenseMapInfo.h"
#include "llvm/ADT/SmallVector.h"
#include "llvm/ADT/StringRef.h"
#include "llvm/ADT/bit.h"
#include "llvm/ADT/iterator_range.h"
#include "llvm/Support/ErrorHandling.h"
#include "llvm/Support/TrailingObjects.h"
Expand Down Expand Up @@ -230,6 +231,10 @@ class DeclAttribute : public AttributeBase {
Modifier : NumNonIsolatedModifierBits
);

SWIFT_INLINE_BITFIELD(InheritActorContextAttr, DeclAttribute, NumInheritActorContextKindBits,
Modifier : NumInheritActorContextKindBits
);

SWIFT_INLINE_BITFIELD_FULL(AllowFeatureSuppressionAttr, DeclAttribute, 1+31,
: NumPadBits,
Inverted : 1,
Expand Down Expand Up @@ -3011,6 +3016,51 @@ class NonisolatedAttr final : public DeclAttribute {
}
};

/// Represents @_inheritActorContext modifier.
class InheritActorContextAttr final : public DeclAttribute {
public:
InheritActorContextAttr(SourceLoc atLoc, SourceRange range,
InheritActorContextModifier modifier, bool implicit)
: DeclAttribute(DeclAttrKind::InheritActorContext, atLoc, range, implicit) {
Bits.InheritActorContextAttr.Modifier = static_cast<unsigned>(modifier);
assert((getModifier() == modifier) && "not enough bits for modifier");
}

InheritActorContextModifier getModifier() const {
return static_cast<InheritActorContextModifier>(Bits.InheritActorContextAttr.Modifier);
}

bool isAlways() const { return getModifier() == InheritActorContextModifier::Always; }

static InheritActorContextAttr *
createImplicit(ASTContext &ctx,
InheritActorContextModifier modifier = InheritActorContextModifier::Default) {
return new (ctx) InheritActorContextAttr(/*atLoc*/ {}, /*range*/ {}, modifier,
/*implicit=*/true);
}

static bool classof(const DeclAttribute *DA) {
return DA->getKind() == DeclAttrKind::InheritActorContext;
}

/// Create a copy of this attribute.
InheritActorContextAttr *clone(ASTContext &ctx) const {
return new (ctx) InheritActorContextAttr(AtLoc, Range, getModifier(), isImplicit());
}

bool isEquivalent(const InheritActorContextAttr *other, Decl *attachedTo) const {
return getModifier() == other->getModifier();
}

const char *getModifierName() const {
return getModifierName(getModifier());
}
static const char *getModifierName(InheritActorContextModifier modifier);

void printImpl(ASTPrinter &printer, const PrintOptions &options) const;

};

/// A macro role attribute, spelled with either @attached or @freestanding,
/// which declares one of the roles that a given macro can inhabit.
class MacroRoleAttr final
Expand Down
14 changes: 14 additions & 0 deletions include/swift/AST/AttrKind.h
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,20 @@ enum : unsigned {
static_cast<unsigned>(NonIsolatedModifier::Last_NonIsolatedModifier))
};

enum class InheritActorContextModifier: uint8_t {
/// Only inherit the actor execution context if the isolated parameter was
/// captured by the closure.
Default,

/// Always inherit the actor context, even when the isolated parameter
/// is not closed over explicitly.
Always,
Last_InheritActorContextKind = Always
};

enum : unsigned { NumInheritActorContextKindBits =
countBitsUsed(static_cast<unsigned>(InheritActorContextModifier::Last_InheritActorContextKind)) };

enum class DeclAttrKind : unsigned {
#define DECL_ATTR(_, CLASS, ...) CLASS,
#define LAST_DECL_ATTR(CLASS) Last_DeclAttr = CLASS,
Expand Down
5 changes: 3 additions & 2 deletions include/swift/AST/DeclAttr.def
Original file line number Diff line number Diff line change
Expand Up @@ -620,9 +620,10 @@ SIMPLE_DECL_ATTR(_implicitSelfCapture, ImplicitSelfCapture,
UserInaccessible | ABIStableToAdd | ABIStableToRemove | APIStableToAdd | APIBreakingToRemove | ForbiddenInABIAttr,
115)

SIMPLE_DECL_ATTR(_inheritActorContext, InheritActorContext,
DECL_ATTR(_inheritActorContext, InheritActorContext,
OnParam,
UserInaccessible | ABIStableToAdd | ABIStableToRemove | APIBreakingToAdd | APIBreakingToRemove | ForbiddenInABIAttr,
// since the _inheritActorContext(always) forces an actor capture, it changes ABI of the closure this applies to
UserInaccessible | ABIBreakingToAdd | ABIBreakingToRemove | APIBreakingToAdd | APIBreakingToRemove | UnconstrainedInABIAttr,
116)

SIMPLE_DECL_ATTR(_eagerMove, EagerMove,
Expand Down
22 changes: 19 additions & 3 deletions include/swift/AST/Expr.h
Original file line number Diff line number Diff line change
Expand Up @@ -267,7 +267,7 @@ class alignas(8) Expr : public ASTAllocated<Expr> {
Kind : 2
);

SWIFT_INLINE_BITFIELD(ClosureExpr, AbstractClosureExpr, 1+1+1+1+1+1+1+1,
SWIFT_INLINE_BITFIELD(ClosureExpr, AbstractClosureExpr, 1+1+1+1+1+1+1+1+1,
/// True if closure parameters were synthesized from anonymous closure
/// variables.
HasAnonymousClosureVars : 1,
Expand All @@ -279,6 +279,7 @@ class alignas(8) Expr : public ASTAllocated<Expr> {
/// True if this @Sendable async closure parameter should implicitly
/// inherit the actor context from where it was formed.
InheritActorContext : 1,
InheritActorContextAlways : 1,

/// True if this closure's actor isolation behavior was determined by an
/// \c \@preconcurrency declaration.
Expand Down Expand Up @@ -4366,8 +4367,23 @@ class ClosureExpr : public AbstractClosureExpr {
return Bits.ClosureExpr.InheritActorContext;
}

void setInheritsActorContext(bool value = true) {
Bits.ClosureExpr.InheritActorContext = value;
void setInheritsActorContext(InheritActorContextModifier modifier = InheritActorContextModifier::Default) { // TODO: change into enum
switch (modifier) {
case InheritActorContextModifier::Default: {
Bits.ClosureExpr.InheritActorContext = true;
Bits.ClosureExpr.InheritActorContextAlways = true;
break;
}
case InheritActorContextModifier::Always: {
Bits.ClosureExpr.InheritActorContext = true;
Bits.ClosureExpr.InheritActorContextAlways = true;
break;
}
}
}

bool inheritsActorContextAlways() const {
return Bits.ClosureExpr.InheritActorContextAlways;
}

/// Whether the closure's concurrency behavior was determined by an
Expand Down
1 change: 1 addition & 0 deletions include/swift/AST/KnownIdentifiers.def
Original file line number Diff line number Diff line change
Expand Up @@ -326,6 +326,7 @@ IDENTIFIER_WITH_NAME(builderSelf, "$builderSelf")

// Attribute options
IDENTIFIER_(_always)
IDENTIFIER(always)
IDENTIFIER_(assumed)
IDENTIFIER(checked)
IDENTIFIER(never)
Expand Down
2 changes: 2 additions & 0 deletions include/swift/AST/Types.h
Original file line number Diff line number Diff line change
Expand Up @@ -4049,6 +4049,7 @@ struct ParameterListInfo {
SmallBitVector propertyWrappers;
SmallBitVector implicitSelfCapture;
SmallBitVector inheritActorContext;
SmallBitVector inheritActorContextAlways;
SmallBitVector variadicGenerics;
SmallBitVector sendingParameters;

Expand Down Expand Up @@ -4076,6 +4077,7 @@ struct ParameterListInfo {
/// Whether the given parameter is a closure that should inherit the
/// actor context from the context in which it was created.
bool inheritsActorContext(unsigned paramIdx) const;
bool inheritsActorContextAlways(unsigned paramIdx) const;

bool isVariadicGenericParameter(unsigned paramIdx) const;

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 @@ -191,6 +191,7 @@ BASELINE_LANGUAGE_FEATURE(BuiltinContinuation, 0, "Continuation builtins")
BASELINE_LANGUAGE_FEATURE(BuiltinHopToActor, 0, "Builtin.HopToActor")
BASELINE_LANGUAGE_FEATURE(BuiltinTaskGroupWithArgument, 0, "TaskGroup builtins")
BASELINE_LANGUAGE_FEATURE(InheritActorContext, 0, "@_inheritActorContext attribute")
BASELINE_LANGUAGE_FEATURE(InheritActorContextAlways, 0, "@_inheritActorContext(always) attribute")
BASELINE_LANGUAGE_FEATURE(ImplicitSelfCapture, 0, "@_implicitSelfCapture attribute")
BASELINE_LANGUAGE_FEATURE(BuiltinBuildTaskExecutorRef, 0, "TaskExecutor-building builtins")
BASELINE_LANGUAGE_FEATURE(BuiltinBuildExecutor, 0, "Executor-building builtins")
Expand Down
3 changes: 2 additions & 1 deletion include/swift/Parse/IDEInspectionCallbacks.h
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,8 @@ enum class ParameterizedDeclAttributeKind {
Available,
FreestandingMacro,
AttachedMacro,
StorageRestrictions
StorageRestrictions,
InheritActorContext
};

/// A bit of a hack. When completing inside the '@storageRestrictions'
Expand Down
8 changes: 7 additions & 1 deletion lib/AST/ASTDumper.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4110,8 +4110,14 @@ class PrintExpr : public ExprVisitor<PrintExpr, void, Label>,
ClosureModifierColor);
printFlag(E->allowsImplicitSelfCapture(), "implicit_self",
ClosureModifierColor);
printFlag(E->inheritsActorContext(), "inherits_actor_context",

if (E->inheritsActorContextAlways()) {
printFlag(E->inheritsActorContextAlways(), "inherits_actor_context(always)",
ClosureModifierColor);
} else {
printFlag(E->inheritsActorContext(), "inherits_actor_context",
ClosureModifierColor);
}

if (E->getParameters()) {
printRec(E->getParameters(), Label::optional("params"),
Expand Down
48 changes: 48 additions & 0 deletions lib/AST/Attr.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
//===----------------------------------------------------------------------===//

#include "swift/AST/Attr.h"
#include "swift/AST/ASTBridging.h"
#include "swift/AST/ASTContext.h"
#include "swift/AST/ASTPrinter.h"
#include "swift/AST/AvailabilityDomain.h"
Expand All @@ -34,6 +35,7 @@
#include "swift/Basic/Defer.h"
#include "swift/Basic/QuotedString.h"
#include "swift/Strings.h"

#include "llvm/ADT/SmallSet.h"
#include "llvm/ADT/SmallString.h"
#include "llvm/ADT/StringSwitch.h"
Expand Down Expand Up @@ -304,6 +306,33 @@ void IsolatedTypeAttr::printImpl(ASTPrinter &printer,
printer.printStructurePost(PrintStructureKind::BuiltinAttribute);
}

const char *InheritActorContextAttr::getModifierName(
InheritActorContextModifier modifier) {
switch (modifier) {
case InheritActorContextModifier::Default: return "";
case InheritActorContextModifier::Always: return "always";
}
llvm_unreachable("bad kind");
}


void InheritActorContextAttr::printImpl(ASTPrinter &printer,
const PrintOptions &options) const {
printer.callPrintStructurePre(PrintStructureKind::BuiltinAttribute);
printer.printAttrName("@_inheritActorContext");

switch (getModifier()) {
case InheritActorContextModifier::Default:
break;
case InheritActorContextModifier::Always: {
printer << "(" << getModifierName() << ")";
break;
}
}

printer.printStructurePost(PrintStructureKind::BuiltinAttribute);
}

/// Given a name like "inline", return the decl attribute ID that corresponds
/// to it. Note that this is a many-to-one mapping, and that the identifier
/// passed in may only be the first portion of the attribute (e.g. in the case
Expand Down Expand Up @@ -1531,6 +1560,18 @@ bool DeclAttribute::printImpl(ASTPrinter &Printer, const PrintOptions &Options,
break;
}

case DeclAttrKind::InheritActorContext: {
Printer.printAttrName("@_inheritActorContext");
switch (cast<InheritActorContextAttr>(this)->getModifier()) {
case InheritActorContextModifier::Default:
break;
case InheritActorContextModifier::Always:
Printer << "(always)";
break;
}
break;
}

case DeclAttrKind::MacroRole: {
auto Attr = cast<MacroRoleAttr>(this);

Expand Down Expand Up @@ -1915,6 +1956,13 @@ StringRef DeclAttribute::getAttrName() const {
case NonIsolatedModifier::NonSending:
return "nonisolated(nonsending)";
}
case DeclAttrKind::InheritActorContext:
switch (cast<InheritActorContextAttr>(this)->getModifier()) {
case InheritActorContextModifier::Default:
return "@_inheritActorContext";
case InheritActorContextModifier::Always:
return "@_inheritActorContext(always)";
}
case DeclAttrKind::MacroRole:
switch (cast<MacroRoleAttr>(this)->getMacroSyntax()) {
case MacroSyntax::Freestanding:
Expand Down
Loading