From ca96a2b0609113fe5ba8fb02ca311cf667cead36 Mon Sep 17 00:00:00 2001 From: Pavel Yaskevich Date: Wed, 15 Jan 2025 17:18:56 -0800 Subject: [PATCH 1/7] [AST] Define `@execution(concurrent | caller)` attribute on function declarations --- include/swift/AST/Attr.h | 28 ++++++++++++++++++++++++++++ include/swift/AST/AttrKind.h | 9 +++++++++ include/swift/AST/DeclAttr.def | 7 ++++++- lib/AST/ASTDumper.cpp | 13 +++++++++++++ lib/AST/Attr.cpp | 22 ++++++++++++++++++++++ 5 files changed, 78 insertions(+), 1 deletion(-) diff --git a/include/swift/AST/Attr.h b/include/swift/AST/Attr.h index 50ab978ca3907..4780cb3f8b9ef 100644 --- a/include/swift/AST/Attr.h +++ b/include/swift/AST/Attr.h @@ -220,6 +220,10 @@ class DeclAttribute : public AttributeBase { NumFeatures : 31 ); + + SWIFT_INLINE_BITFIELD(ExecutionAttr, DeclAttribute, NumExecutionKindBits, + Behavior : NumExecutionKindBits + ); } Bits; // clang-format on @@ -2922,6 +2926,30 @@ class ABIAttr : public DeclAttribute { UNIMPLEMENTED_CLONE(ABIAttr) }; +class ExecutionAttr : public DeclAttribute { +public: + ExecutionAttr(SourceLoc AtLoc, SourceRange Range, + ExecutionKind behavior, + bool Implicit) + : DeclAttribute(DeclAttrKind::Execution, AtLoc, Range, Implicit) { + Bits.ExecutionAttr.Behavior = static_cast(behavior); + } + + ExecutionAttr(ExecutionKind behavior, bool Implicit) + : ExecutionAttr(/*AtLoc=*/SourceLoc(), /*Range=*/SourceRange(), behavior, + Implicit) {} + + ExecutionKind getBehavior() const { + return static_cast(Bits.ExecutionAttr.Behavior); + } + + static bool classof(const DeclAttribute *DA) { + return DA->getKind() == DeclAttrKind::Execution; + } + + UNIMPLEMENTED_CLONE(ExecutionAttr) +}; + /// Attributes that may be applied to declarations. class DeclAttributes { /// Linked list of declaration attributes. diff --git a/include/swift/AST/AttrKind.h b/include/swift/AST/AttrKind.h index 4cae581178d8b..1f4a784c70510 100644 --- a/include/swift/AST/AttrKind.h +++ b/include/swift/AST/AttrKind.h @@ -130,6 +130,15 @@ enum class ExternKind: uint8_t { enum : unsigned { NumExternKindBits = countBitsUsed(static_cast(ExternKind::Last_ExternKind)) }; +enum class ExecutionKind : uint8_t { + Concurrent = 0, + Caller, + Last_ExecutionKind = Caller +}; + +enum : unsigned { NumExecutionKindBits = + countBitsUsed(static_cast(ExecutionKind::Last_ExecutionKind)) }; + enum class DeclAttrKind : unsigned { #define DECL_ATTR(_, CLASS, ...) CLASS, #define LAST_DECL_ATTR(CLASS) Last_DeclAttr = CLASS, diff --git a/include/swift/AST/DeclAttr.def b/include/swift/AST/DeclAttr.def index d9231733e6825..3300a672912d8 100644 --- a/include/swift/AST/DeclAttr.def +++ b/include/swift/AST/DeclAttr.def @@ -538,7 +538,12 @@ DECL_ATTR(abi, ABI, 165) DECL_ATTR_FEATURE_REQUIREMENT(ABI, ABIAttribute) -LAST_DECL_ATTR(ABI) +DECL_ATTR(execution, Execution, + OnFunc | ABIBreakingToAdd | ABIBreakingToRemove | APIBreakingToAdd | APIBreakingToRemove, + 166) +DECL_ATTR_FEATURE_REQUIREMENT(Execution, NonIsolatedAsyncInheritsIsolationFromContext) + +LAST_DECL_ATTR(Execution) #undef DECL_ATTR_ALIAS #undef CONTEXTUAL_DECL_ATTR_ALIAS diff --git a/lib/AST/ASTDumper.cpp b/lib/AST/ASTDumper.cpp index 7c06b955f3bb1..bbda749ebd897 100644 --- a/lib/AST/ASTDumper.cpp +++ b/lib/AST/ASTDumper.cpp @@ -526,6 +526,14 @@ static StringRef getDumpString(NonSendableKind kind) { return "Specific"; } } +static StringRef getDumpString(ExecutionKind kind) { + switch (kind) { + case ExecutionKind::Concurrent: + return "concurrent"; + case ExecutionKind::Caller: + return "caller"; + } +} static StringRef getDumpString(StringRef s) { return s; } @@ -3871,6 +3879,11 @@ class PrintAttribute : public AttributeVisitor, #undef TRIVIAL_ATTR_PRINTER + void visitExecutionAttr(ExecutionAttr *Attr, StringRef label) { + printCommon(Attr, "execution_attr", label); + printField(Attr->getBehavior(), "behavior"); + printFoot(); + } void visitABIAttr(ABIAttr *Attr, StringRef label) { printCommon(Attr, "abi_attr", label); printRec(Attr->abiDecl, "decl"); diff --git a/lib/AST/Attr.cpp b/lib/AST/Attr.cpp index 47cc5ecee1bc6..5a6be3c274265 100644 --- a/lib/AST/Attr.cpp +++ b/lib/AST/Attr.cpp @@ -1650,6 +1650,19 @@ bool DeclAttribute::printImpl(ASTPrinter &Printer, const PrintOptions &Options, break; } + case DeclAttrKind::Execution: { + auto *attr = cast(this); + switch (attr->getBehavior()) { + case ExecutionKind::Concurrent: + Printer << "@execution(concurrent)"; + break; + case ExecutionKind::Caller: + Printer << "@execution(caller)"; + break; + } + break; + } + #define SIMPLE_DECL_ATTR(X, CLASS, ...) case DeclAttrKind::CLASS: #include "swift/AST/DeclAttr.def" llvm_unreachable("handled above"); @@ -1861,6 +1874,15 @@ StringRef DeclAttribute::getAttrName() const { } case DeclAttrKind::Lifetime: return "lifetime"; + case DeclAttrKind::Execution: { + switch (cast(this)->getBehavior()) { + case ExecutionKind::Concurrent: + return "execution(concurrent)"; + case ExecutionKind::Caller: + return "execution(caller)"; + } + llvm_unreachable("Invalid execution kind"); + } } llvm_unreachable("bad DeclAttrKind"); } From 5c8f7ec1bdb696075488c8d812a307324ad9bf4f Mon Sep 17 00:00:00 2001 From: Pavel Yaskevich Date: Wed, 15 Jan 2025 17:25:18 -0800 Subject: [PATCH 2/7] [Serialization] Add support for `@execution` attribute --- lib/Serialization/Deserialization.cpp | 8 ++++++++ lib/Serialization/ModuleFormat.h | 7 ++++++- lib/Serialization/Serialization.cpp | 8 ++++++++ 3 files changed, 22 insertions(+), 1 deletion(-) diff --git a/lib/Serialization/Deserialization.cpp b/lib/Serialization/Deserialization.cpp index 35f0fea712c9e..7ad0f93d0edec 100644 --- a/lib/Serialization/Deserialization.cpp +++ b/lib/Serialization/Deserialization.cpp @@ -5734,6 +5734,14 @@ llvm::Error DeclDeserializer::deserializeDeclCommon() { DeclAttribute *Attr = nullptr; bool skipAttr = false; switch (recordID) { + case decls_block::Execution_DECL_ATTR: { + unsigned behavior; + serialization::decls_block::ExecutionDeclAttrLayout::readRecord( + scratch, behavior); + Attr = new (ctx) ExecutionAttr(static_cast(behavior), + /*Implicit=*/false); + break; + } case decls_block::ABI_DECL_ATTR: { bool isImplicit; DeclID abiDeclID; diff --git a/lib/Serialization/ModuleFormat.h b/lib/Serialization/ModuleFormat.h index 34cc538891dfa..f4be640346c2f 100644 --- a/lib/Serialization/ModuleFormat.h +++ b/lib/Serialization/ModuleFormat.h @@ -58,7 +58,7 @@ const uint16_t SWIFTMODULE_VERSION_MAJOR = 0; /// describe what change you made. The content of this comment isn't important; /// it just ensures a conflict if two people change the module format. /// Don't worry about adhering to the 80-column limit for this line. -const uint16_t SWIFTMODULE_VERSION_MINOR = 913; // @safe removal +const uint16_t SWIFTMODULE_VERSION_MINOR = 914; // @execution attribute /// A standard hash seed used for all string hashes in a serialized module. /// @@ -2355,6 +2355,11 @@ namespace decls_block { BCFixed<2> // exclusivity mode >; + using ExecutionDeclAttrLayout = BCRecordLayout< + Execution_DECL_ATTR, + BCFixed<1> // execution behavior kind + >; + using ABIDeclAttrLayout = BCRecordLayout< ABI_DECL_ATTR, BCFixed<1>, // implicit flag diff --git a/lib/Serialization/Serialization.cpp b/lib/Serialization/Serialization.cpp index 5f553a2091eba..749cbd190e339 100644 --- a/lib/Serialization/Serialization.cpp +++ b/lib/Serialization/Serialization.cpp @@ -2891,6 +2891,14 @@ class Serializer::DeclSerializer : public DeclVisitor { } #include "swift/AST/DeclAttr.def" + case DeclAttrKind::Execution: { + auto *theAttr = cast(DA); + auto abbrCode = S.DeclTypeAbbrCodes[ExecutionDeclAttrLayout::Code]; + ExecutionDeclAttrLayout::emitRecord(S.Out, S.ScratchRecord, abbrCode, + (unsigned)theAttr->getKind()); + return; + } + case DeclAttrKind::ABI: { auto *theAttr = cast(DA); auto abbrCode = S.DeclTypeAbbrCodes[ABIDeclAttrLayout::Code]; From 0445e5af5ff44c6fc87466d80272d5907568ea45 Mon Sep 17 00:00:00 2001 From: Pavel Yaskevich Date: Thu, 16 Jan 2025 09:24:04 -0800 Subject: [PATCH 3/7] [Parse] Implement parsing of `@execution(...)` decl attribute --- include/swift/AST/KnownIdentifiers.def | 2 ++ lib/Parse/ParseDecl.cpp | 19 ++++++++++++- ...e_decl_attribute_feature_requirement.swift | 28 ++++++++++++++++++- test/attr/feature_requirement.swift | 11 +++++++- 4 files changed, 57 insertions(+), 3 deletions(-) diff --git a/include/swift/AST/KnownIdentifiers.def b/include/swift/AST/KnownIdentifiers.def index f4f1f203061f8..df2d96ee509cc 100644 --- a/include/swift/AST/KnownIdentifiers.def +++ b/include/swift/AST/KnownIdentifiers.def @@ -332,6 +332,8 @@ IDENTIFIER(size) IDENTIFIER(speed) IDENTIFIER(unchecked) IDENTIFIER(unsafe) +IDENTIFIER(concurrent) +IDENTIFIER(caller) // The singleton instance of TupleTypeDecl in the Builtin module IDENTIFIER(TheTupleType) diff --git a/lib/Parse/ParseDecl.cpp b/lib/Parse/ParseDecl.cpp index 1483424a1edb4..3e025a5502835 100644 --- a/lib/Parse/ParseDecl.cpp +++ b/lib/Parse/ParseDecl.cpp @@ -2706,6 +2706,8 @@ ParserResult Parser::parseLifetimeAttribute(SourceLoc atLoc, /* implicit */ false, lifetimeEntry.get())); } + + /// Parses a (possibly optional) argument for an attribute containing a single, arbitrary identifier. /// /// \param P The parser object. @@ -4022,6 +4024,21 @@ ParserStatus Parser::parseNewDeclAttribute(DeclAttributes &Attributes, Attributes.add(Attr.get()); break; } + + case DeclAttrKind::Execution: { + auto behavior = parseSingleAttrOption( + *this, Loc, AttrRange, AttrName, DK, + {{Context.Id_concurrent, ExecutionKind::Concurrent}, + {Context.Id_caller, ExecutionKind::Caller}}); + if (!behavior) + return makeParserSuccess(); + + if (!DiscardAttribute) + Attributes.add(new (Context) ExecutionAttr(AtLoc, AttrRange, *behavior, + /*Implicit*/ false)); + + break; + } } if (DuplicateAttribute) { @@ -9729,7 +9746,7 @@ Parser::parseDeclSubscript(SourceLoc StaticLoc, } } } - + ParserStatus Status; SourceLoc SubscriptLoc = consumeToken(tok::kw_subscript); diff --git a/test/IDE/complete_decl_attribute_feature_requirement.swift b/test/IDE/complete_decl_attribute_feature_requirement.swift index adeb35c9ed7af..827edc20ad419 100644 --- a/test/IDE/complete_decl_attribute_feature_requirement.swift +++ b/test/IDE/complete_decl_attribute_feature_requirement.swift @@ -6,7 +6,9 @@ // REQUIRES: asserts // RUN: %batch-code-completion -filecheck-additional-suffix _DISABLED -// RUN: %batch-code-completion -filecheck-additional-suffix _ENABLED -enable-experimental-feature ABIAttribute +// RUN: %batch-code-completion -filecheck-additional-suffix _ENABLED \ +// RUN: -enable-experimental-feature ABIAttribute \ +// RUN: -enable-experimental-feature NonIsolatedAsyncInheritsIsolationFromContext // NOTE: Please do not include the ", N items" after "Begin completions". The // item count creates needless merge conflicts given that an "End completions" @@ -16,14 +18,18 @@ // KEYWORD2: Begin completions // KEYWORD2_ENABLED-DAG: Keyword/None: abi[#Func Attribute#]; name=abi +// KEYWORD2_ENABLED-DAG: Keyword/None: execution[#Func Attribute#]; name=execution // KEYWORD2_DISABLED-NOT: Keyword/None: abi[#{{.*}} Attribute#]; name=abi +// KEYWORD2_DISABLED-NOT: Keyword/None: execution[#{{.*}} Attribute#]; name=execution // KEYWORD2: End completions @#^KEYWORD3^# class C {} // KEYWORD3: Begin completions // KEYWORD3_ENABLED-NOT: Keyword/None: abi[#{{.*}} Attribute#]; name=abi +// KEYWORD3_ENABLED-NOT: Keyword/None: execution[#{{.*}} Attribute#]; name=execution // KEYWORD3_DISABLED-NOT: Keyword/None: abi[#{{.*}} Attribute#]; name=abi +// KEYWORD3_DISABLED-NOT: Keyword/None: execution[#{{.*}} Attribute#]; name=execution // KEYWORD3: End completions @#^KEYWORD3_2?check=KEYWORD3^#IB class C2 {} @@ -32,46 +38,60 @@ @#^KEYWORD4^# enum E {} // KEYWORD4: Begin completions // KEYWORD4_ENABLED-NOT: Keyword/None: abi[#{{.*}} Attribute#]; name=abi +// KEYWORD4_ENABLED-NOT: Keyword/None: execution[#{{.*}} Attribute#]; name=execution // KEYWORD4_DISABLED-NOT: Keyword/None: abi[#{{.*}} Attribute#]; name=abi +// KEYWORD4_DISABLED-NOT: Keyword/None: execution[#{{.*}} Attribute#]; name=execution // KEYWORD4: End completions @#^KEYWORD5^# struct S{} // KEYWORD5: Begin completions // KEYWORD5_ENABLED-NOT: Keyword/None: abi[#{{.*}} Attribute#]; name=abi +// KEYWORD5_ENABLED-NOT: Keyword/None: execution[#{{.*}} Attribute#]; name=execution // KEYWORD5_DISABLED-NOT: Keyword/None: abi[#{{.*}} Attribute#]; name=abi +// KEYWORD5_DISABLED-NOT: Keyword/None: execution[#{{.*}} Attribute#]; name=execution // KEYWORD5: End completions @#^ON_GLOBALVAR^# var globalVar // ON_GLOBALVAR: Begin completions // ON_GLOBALVAR_ENABLED-DAG: Keyword/None: abi[#Var Attribute#]; name=abi +// ON_GLOBALVAR_ENABLED-NOT: Keyword/None: execution[#{{.*}} Attribute#]; name=execution // ON_GLOBALVAR_DISABLED-NOT: Keyword/None: abi[#{{.*}} Attribute#]; name=abi +// ON_GLOBALVAR_DISABLED-NOT: Keyword/None: execution[#{{.*}} Attribute#]; name=execution // ON_GLOBALVAR: End completions struct _S { @#^ON_INIT^# init() // ON_INIT: Begin completions // ON_INIT_ENABLED-DAG: Keyword/None: abi[#Constructor Attribute#]; name=abi +// ON_INIT_ENABLED-NOT: Keyword/None: execution[#{{.*}} Attribute#]; name=execution // ON_INIT_DISABLED-NOT: Keyword/None: abi[#{{.*}} Attribute#]; name=abi +// ON_INIT_DISABLED-NOT: Keyword/None: execution[#{{.*}} Attribute#]; name=execution // ON_INIT: End completions @#^ON_PROPERTY^# var foo // ON_PROPERTY: Begin completions // ON_PROPERTY_ENABLED-DAG: Keyword/None: abi[#Var Attribute#]; name=abi +// ON_PROPERTY_ENABLED-NOT: Keyword/None: execution[#{{.*}} Attribute#]; name=execution // ON_PROPERTY_DISABLED-NOT: Keyword/None: abi[#{{.*}} Attribute#]; name=abi +// ON_PROPERTY_DISABLED-NOT: Keyword/None: execution[#{{.*}} Attribute#]; name=execution // ON_PROPERTY: End completions @#^ON_METHOD^# private func foo() // ON_METHOD: Begin completions // ON_METHOD_ENABLED-DAG: Keyword/None: abi[#Func Attribute#]; name=abi +// ON_METHOD_ENABLED-DAG: Keyword/None: execution[#Func Attribute#]; name=execution // ON_METHOD_DISABLED-NOT: Keyword/None: abi[#{{.*}} Attribute#]; name=abi +// ON_METHOD_DISABLED-NOT: Keyword/None: execution[#{{.*}} Attribute#]; name=execution // ON_METHOD: End completions func bar(@#^ON_PARAM_1?check=ON_PARAM^#) // ON_PARAM: Begin completions // ON_PARAM_ENABLED-NOT: Keyword/None: abi[#{{.*}} Attribute#]; name=abi +// ON_PARAM_ENABLED-NOT: Keyword/None: execution[#{{.*}} Attribute#]; name=execution // ON_PARAM_DISABLED-NOT: Keyword/None: abi[#{{.*}} Attribute#]; name=abi +// ON_PARAM_DISABLED-NOT: Keyword/None: execution[#{{.*}} Attribute#]; name=execution // ON_PARAM: End completions func bar( @@ -94,7 +114,9 @@ struct _S { @#^ON_MEMBER_LAST^# // ON_MEMBER_LAST: Begin completions // ON_MEMBER_LAST_ENABLED-DAG: Keyword/None: abi[#Declaration Attribute#]; name=abi +// ON_MEMBER_LAST_ENABLED-DAG: Keyword/None: execution[#Declaration Attribute#]; name=execution // ON_MEMBER_LAST_DISABLED-NOT: Keyword/None: abi[#{{.*}} Attribute#]; name=abi +// ON_MEMBER_LAST_DISABLED-NOT: Keyword/None: execution[#{{.*}} Attribute#]; name=execution // ON_MEMBER_LAST: End completions } @@ -106,7 +128,9 @@ func takeClosure(_: () -> Void) { // IN_CLOSURE: Begin completions // FIXME: Not valid in this position (but CompletionLookup can't tell that) // IN_CLOSURE_ENABLED-DAG: Keyword/None: abi[#Declaration Attribute#]; name=abi +// IN_CLOSURE_ENABLED-DAG: Keyword/None: execution[#Declaration Attribute#]; name=execution // IN_CLOSURE_DISABLED-NOT: Keyword/None: abi[#{{.*}} Attribute#]; name=abi +// IN_CLOSURE_DISABLED-NOT: Keyword/None: execution[#{{.*}} Attribute#]; name=execution // IN_CLOSURE: End completions @#^KEYWORD_INDEPENDENT_1?check=KEYWORD_LAST^# @@ -122,5 +146,7 @@ func dummy2() {} // KEYWORD_LAST: Begin completions // KEYWORD_LAST_ENABLED-DAG: Keyword/None: abi[#Declaration Attribute#]; name=abi +// KEYWORD_LAST_ENABLED-DAG: Keyword/None: execution[#Declaration Attribute#]; name=execution // KEYWORD_LAST_DISABLED-NOT: Keyword/None: abi[#Declaration Attribute#]; name=abi +// KEYWORD_LAST_DISABLED-NOT: Keyword/None: execution[#Declaration Attribute#]; name=execution // KEYWORD_LAST: End completions diff --git a/test/attr/feature_requirement.swift b/test/attr/feature_requirement.swift index f568a707b91ad..f521ef2e25f19 100644 --- a/test/attr/feature_requirement.swift +++ b/test/attr/feature_requirement.swift @@ -1,5 +1,5 @@ // RUN: %target-typecheck-verify-swift -parse-as-library -disable-experimental-parser-round-trip -verify-additional-prefix disabled- -// RUN: %target-typecheck-verify-swift -parse-as-library -verify-additional-prefix enabled- -enable-experimental-feature ABIAttribute +// RUN: %target-typecheck-verify-swift -parse-as-library -verify-additional-prefix enabled- -enable-experimental-feature ABIAttribute -enable-experimental-feature NonIsolatedAsyncInheritsIsolationFromContext // REQUIRES: asserts @@ -14,3 +14,12 @@ func fn() {} // expected-disabled-error@-1 {{'abi' attribute is only valid when #else #error("doesn't have @abi") // expected-disabled-error {{doesn't have @abi}} #endif + +@execution(concurrent) func testExecutionAttr() async {} +// expected-disabled-error@-1 {{'execution(concurrent)' attribute is only valid when experimental feature NonIsolatedAsyncInheritsIsolationFromContext is enabled}} + +#if hasAttribute(execution) + #error("does have @execution") // expected-enabled-error {{does have @execution}} +#else + #error("doesn't have @execution") // expected-disabled-error {{doesn't have @execution}} +#endif From 667a976ec5b98233194f3304974953f7223b8220 Mon Sep 17 00:00:00 2001 From: Pavel Yaskevich Date: Thu, 16 Jan 2025 13:56:39 -0800 Subject: [PATCH 4/7] [ASTGen] Add support for `@execution(...)` attribute --- include/swift/AST/ASTBridging.h | 10 ++++++ lib/AST/Bridging/DeclAttributeBridging.cpp | 18 ++++++++++ lib/ASTGen/Sources/ASTGen/DeclAttrs.swift | 29 +++++++++++++++++ test/ASTGen/attrs.swift | 38 +++++++++++++++++++--- 4 files changed, 91 insertions(+), 4 deletions(-) diff --git a/include/swift/AST/ASTBridging.h b/include/swift/AST/ASTBridging.h index 561eeea5b26f5..42ebeec50b618 100644 --- a/include/swift/AST/ASTBridging.h +++ b/include/swift/AST/ASTBridging.h @@ -601,6 +601,16 @@ BridgedABIAttr BridgedABIAttr_createParsed( BridgedASTContext cContext, BridgedSourceLoc atLoc, BridgedSourceRange range, BridgedNullableDecl abiDecl); +enum ENUM_EXTENSIBILITY_ATTR(closed) BridgedExecutionKind { + BridgedExecutionKindConcurrent, + BridgedExecutionKindCaller, +}; + +SWIFT_NAME("BridgedExecutionAttr.createParsed(_:atLoc:range:behavior:)") +BridgedExecutionAttr BridgedExecutionAttr_createParsed( + BridgedASTContext cContext, BridgedSourceLoc atLoc, + BridgedSourceRange range, BridgedExecutionKind behavior); + enum ENUM_EXTENSIBILITY_ATTR(closed) BridgedAccessLevel { BridgedAccessLevelPrivate, BridgedAccessLevelFilePrivate, diff --git a/lib/AST/Bridging/DeclAttributeBridging.cpp b/lib/AST/Bridging/DeclAttributeBridging.cpp index 382f5d2835721..fbabc25f4c7e5 100644 --- a/lib/AST/Bridging/DeclAttributeBridging.cpp +++ b/lib/AST/Bridging/DeclAttributeBridging.cpp @@ -585,3 +585,21 @@ BridgedUnavailableFromAsyncAttr BridgedUnavailableFromAsyncAttr_createParsed( UnavailableFromAsyncAttr(cMessage.unbridged(), cAtLoc.unbridged(), cRange.unbridged(), /*implicit=*/false); } + +static ExecutionKind unbridged(BridgedExecutionKind kind) { + switch (kind) { + case BridgedExecutionKindConcurrent: + return ExecutionKind::Concurrent; + case BridgedExecutionKindCaller: + return ExecutionKind::Caller; + } + llvm_unreachable("unhandled enum value"); +} + +BridgedExecutionAttr BridgedExecutionAttr_createParsed( + BridgedASTContext cContext, BridgedSourceLoc atLoc, + BridgedSourceRange range, BridgedExecutionKind behavior) { + return new (cContext.unbridged()) + ExecutionAttr(atLoc.unbridged(), range.unbridged(), + unbridged(behavior), /*implicit=*/false); +} \ No newline at end of file diff --git a/lib/ASTGen/Sources/ASTGen/DeclAttrs.swift b/lib/ASTGen/Sources/ASTGen/DeclAttrs.swift index f9e39b4e8013e..ec891f5c77cc6 100644 --- a/lib/ASTGen/Sources/ASTGen/DeclAttrs.swift +++ b/lib/ASTGen/Sources/ASTGen/DeclAttrs.swift @@ -111,6 +111,8 @@ extension ASTGenVisitor { let attrName = identTy.name.rawText let attrKind = BridgedDeclAttrKind(from: attrName.bridged) switch attrKind { + case .execution: + return handle(self.generateExecutionAttr(attribute: node)?.asDeclAttribute) case .ABI: return handle(self.generateABIAttr(attribute: node)?.asDeclAttribute) case .alignment: @@ -329,6 +331,33 @@ extension ASTGenVisitor { return handle(self.generateCustomAttr(attribute: node)?.asDeclAttribute) } + /// E.g.: + /// ``` + /// @execution(concurrent) + /// @execution(caller) + /// ``` + func generateExecutionAttr(attribute node: AttributeSyntax) -> BridgedExecutionAttr? { + let behavior: BridgedExecutionKind? = self.generateSingleAttrOption( + attribute: node, + { + switch $0.rawText { + case "concurrent": return .concurrent + case "caller": return .caller + default: return nil + } + } + ) + guard let behavior else { + return nil + } + return .createParsed( + self.ctx, + atLoc: self.generateSourceLoc(node.atSign), + range: self.generateAttrSourceRange(node), + behavior: behavior + ) + } + /// E.g.: /// ``` /// @abi(func fn()) diff --git a/test/ASTGen/attrs.swift b/test/ASTGen/attrs.swift index cb7c6ed5a7182..9ae4ceca7b035 100644 --- a/test/ASTGen/attrs.swift +++ b/test/ASTGen/attrs.swift @@ -1,6 +1,18 @@ // RUN: %empty-directory(%t) -// RUN: %target-swift-frontend %s -dump-parse -disable-availability-checking -enable-experimental-feature SymbolLinkageMarkers -enable-experimental-feature ABIAttribute -enable-experimental-feature Extern -enable-experimental-move-only -enable-experimental-feature ParserASTGen > %t/astgen.ast.raw -// RUN: %target-swift-frontend %s -dump-parse -disable-availability-checking -enable-experimental-feature SymbolLinkageMarkers -enable-experimental-feature ABIAttribute -enable-experimental-feature Extern -enable-experimental-move-only > %t/cpp-parser.ast.raw +// RUN: %target-swift-frontend %s -dump-parse -disable-availability-checking \ +// RUN: -enable-experimental-feature SymbolLinkageMarkers \ +// RUN: -enable-experimental-feature ABIAttribute \ +// RUN: -enable-experimental-feature Extern \ +// RUN: -enable-experimental-feature NonIsolatedAsyncInheritsIsolationFromContext \ +// RUN: -enable-experimental-move-only \ +// RUN: -enable-experimental-feature ParserASTGen > %t/astgen.ast.raw + +// RUN: %target-swift-frontend %s -dump-parse -disable-availability-checking \ +// RUN: -enable-experimental-feature SymbolLinkageMarkers \ +// RUN: -enable-experimental-feature ABIAttribute \ +// RUN: -enable-experimental-feature Extern \ +// RUN: -enable-experimental-feature NonIsolatedAsyncInheritsIsolationFromContext \ +// RUN: -enable-experimental-move-only > %t/cpp-parser.ast.raw // Filter out any addresses in the dump, since they can differ. // RUN: sed -E 's#0x[0-9a-fA-F]+##g' %t/cpp-parser.ast.raw > %t/cpp-parser.ast @@ -8,7 +20,13 @@ // RUN: %diff -u %t/astgen.ast %t/cpp-parser.ast -// RUN: %target-typecheck-verify-swift -enable-experimental-feature SymbolLinkageMarkers -enable-experimental-feature ABIAttribute -enable-experimental-feature Extern -enable-experimental-move-only -enable-experimental-feature ParserASTGen +// RUN: %target-typecheck-verify-swift \ +// RUN: -enable-experimental-feature SymbolLinkageMarkers \ +// RUN: -enable-experimental-feature ABIAttribute \ +// RUN: -enable-experimental-feature Extern \ +// RUN: -enable-experimental-move-only \ +// RUN: -enable-experimental-feature ParserASTGen \ +// RUN: -enable-experimental-feature NonIsolatedAsyncInheritsIsolationFromContext // REQUIRES: executable_test // REQUIRES: swift_swift_parser @@ -16,6 +34,7 @@ // REQUIRES: swift_feature_Extern // REQUIRES: swift_feature_ParserASTGen // REQUIRES: swift_feature_ABIAttribute +// REQUIRES: swift_feature_NonIsolatedAsyncInheritsIsolationFromContext // rdar://116686158 // UNSUPPORTED: asan @@ -26,7 +45,7 @@ struct S1 { func testStatic() { // static. - S1.staticMethod() + S1.staticMethod() S1().staticMethod() // expected-error {{static member 'staticMethod' cannot be used on instance of type 'S1'}} } @@ -160,3 +179,14 @@ struct StorageRestrctionTest { @_unavailableFromAsync struct UnavailFromAsyncStruct { } // expected-error {{'@_unavailableFromAsync' attribute cannot be applied to this declaration}} @_unavailableFromAsync(message: "foo bar") func UnavailFromAsyncFn() {} + +@execution(concurrent) func testGlobal() async { // Ok +} + +do { + @execution(caller) func testLocal() async {} // Ok + + struct Test { + @execution(concurrent) func testMember() async {} // Ok + } +} From b1e30b22b94c14c27197ef04f3a7348bfb427091 Mon Sep 17 00:00:00 2001 From: Pavel Yaskevich Date: Thu, 16 Jan 2025 16:28:58 -0800 Subject: [PATCH 5/7] [Sema] Impliment initial validation of `@execution(concurrent)` attribute --- include/swift/AST/DiagnosticsSema.def | 27 ++++++++++ lib/Sema/TypeCheckAttr.cpp | 74 +++++++++++++++++++++++++++ lib/Sema/TypeCheckDeclOverride.cpp | 1 + test/attr/attr_execution.swift | 57 +++++++++++++++++++++ 4 files changed, 159 insertions(+) create mode 100644 test/attr/attr_execution.swift diff --git a/include/swift/AST/DiagnosticsSema.def b/include/swift/AST/DiagnosticsSema.def index 1ca9415157891..1bb3408c5ebd6 100644 --- a/include/swift/AST/DiagnosticsSema.def +++ b/include/swift/AST/DiagnosticsSema.def @@ -8211,5 +8211,32 @@ ERROR(attr_abi_incompatible_with_silgen_name,none, "the same purpose", (DescriptiveDeclKind)) +//===----------------------------------------------------------------------===// +// MARK: @execution Attribute +//===----------------------------------------------------------------------===// +ERROR(attr_execution_concurrent_only_on_async,none, + "cannot use '@execution(concurrent)' on non-async %kind0", + (ValueDecl *)) + +ERROR(attr_execution_concurrent_incompatible_with_global_actor,none, + "cannot use '@execution(concurrent)' on %kind0 isolated to global actor %1", + (ValueDecl *, ValueDecl *)) + +ERROR(attr_execution_concurrent_incompatible_isolated_parameter,none, + "cannot use '@execution(concurrent)' on %kind0 because it has " + "an isolated parameter: %1", + (ValueDecl *, ValueDecl *)) + +ERROR(attr_execution_concurrent_incompatible_dynamically_isolated_parameter,none, + "cannot use '@execution(concurrent)' on %kind0 because it has " + "a dynamically isolated parameter: %1", + (ValueDecl *, ValueDecl *)) + +ERROR(attr_execution_concurrent_incompatible_with_nonisolated,none, + "cannot use '@execution(concurrent)' and 'nonisolated' on the same %0 " + "because they serve the same purpose", + (ValueDecl *)) + + #define UNDEFINE_DIAGNOSTIC_MACROS #include "DefineDiagnosticMacros.h" diff --git a/lib/Sema/TypeCheckAttr.cpp b/lib/Sema/TypeCheckAttr.cpp index 2b92658dd81cb..f7a2afc8b5e1f 100644 --- a/lib/Sema/TypeCheckAttr.cpp +++ b/lib/Sema/TypeCheckAttr.cpp @@ -253,6 +253,80 @@ class AttributeChecker : public AttributeVisitor { } public: + void visitExecutionAttr(ExecutionAttr *attr) { + auto *F = dyn_cast(D); + if (!F) + return; + + if (!F->hasAsync()) { + diagnoseAndRemoveAttr(attr, diag::attr_execution_concurrent_only_on_async, + F); + return; + } + + // Checks based on explicit attributes, inferred ones would + // have to be handled during actor isolation inference. + + switch (attr->getBehavior()) { + case ExecutionKind::Concurrent: { + // 'concurrent' doesn't work with explicit `nonisolated` + if (F->hasExplicitIsolationAttribute()) { + if (F->getAttrs().hasAttribute()) { + diagnoseAndRemoveAttr( + attr, + diag::attr_execution_concurrent_incompatible_with_nonisolated, F); + return; + } + + if (auto globalActor = F->getGlobalActorAttr()) { + diagnoseAndRemoveAttr( + attr, + diag::attr_execution_concurrent_incompatible_with_global_actor, F, + globalActor->second); + return; + } + } + + auto parameters = F->getParameters(); + if (!parameters) + return; + + for (auto *P : *parameters) { + auto *repr = P->getTypeRepr(); + if (!repr) + continue; + + // isolated parameters affect isolation of the function itself + if (isa(repr)) { + diagnoseAndRemoveAttr( + attr, + diag::attr_execution_concurrent_incompatible_isolated_parameter, + F, P); + return; + } + + if (auto *attrType = dyn_cast(repr)) { + if (attrType->has(TypeAttrKind::Isolated)) { + diagnoseAndRemoveAttr( + attr, + diag:: + attr_execution_concurrent_incompatible_dynamically_isolated_parameter, + F, P); + return; + } + } + } + + break; + } + + case ExecutionKind::Caller: { + // no restrictions for now. + break; + } + } + } + void visitABIAttr(ABIAttr *attr) { Decl *AD = attr->abiDecl; if (isa(D) && isa(AD)) { diff --git a/lib/Sema/TypeCheckDeclOverride.cpp b/lib/Sema/TypeCheckDeclOverride.cpp index b9f5a73adec03..6c4c9ba027eae 100644 --- a/lib/Sema/TypeCheckDeclOverride.cpp +++ b/lib/Sema/TypeCheckDeclOverride.cpp @@ -1593,6 +1593,7 @@ namespace { UNINTERESTING_ATTR(DynamicCallable) UNINTERESTING_ATTR(DynamicMemberLookup) UNINTERESTING_ATTR(SILGenName) + UNINTERESTING_ATTR(Execution) UNINTERESTING_ATTR(Exported) UNINTERESTING_ATTR(ForbidSerializingReference) UNINTERESTING_ATTR(GKInspectable) diff --git a/test/attr/attr_execution.swift b/test/attr/attr_execution.swift new file mode 100644 index 0000000000000..75db4133619c5 --- /dev/null +++ b/test/attr/attr_execution.swift @@ -0,0 +1,57 @@ +// RUN: %target-typecheck-verify-swift -target %target-swift-5.1-abi-triple -enable-experimental-feature NonIsolatedAsyncInheritsIsolationFromContext + +// REQUIRES: concurrency +// REQUIRES: swift_feature_NonIsolatedAsyncInheritsIsolationFromContext + +@execution(something) func invalidAttr() async {} // expected-error {{unknown option 'something' for attribute 'execution'}} + +@execution(concurrent) @execution(caller) func mutipleAttrs() async {} +// expected-error@-1 {{duplicate attribute}} expected-note@-1 {{attribute already specified here}} + +@execution(concurrent) func nonAsync1() {} +// expected-error@-1 {{cannot use '@execution(concurrent)' on non-async global function 'nonAsync1()'}} + +@execution(caller) func nonAsync2() {} +// expected-error@-1 {{cannot use '@execution(concurrent)' on non-async global function 'nonAsync2()'}} + +@execution(concurrent) func testGlobal() async {} // Ok + +struct Test { + @execution(concurrent) init() {} + // expected-error@-1 {{@execution(concurrent) may only be used on 'func' declarations}} + + @execution(concurrent) func member() {} + // expected-error@-1 {{cannot use '@execution(concurrent)' on non-async instance method 'member()'}} + + @execution(concurrent) func member() async {} // Ok + + // expected-error@+1 {{@execution(caller) may only be used on 'func' declarations}} + @execution(caller) subscript(a: Int) -> Bool { + get { false } + @execution(concurrent) set { } + // expected-error@-1 {{@execution(concurrent) may only be used on 'func' declarations}} + } + + @execution(caller) var x: Int + // expected-error@-1 {{@execution(caller) may only be used on 'func' declarations}} +} + +do { + @execution(caller) func local() async {} // Ok +} + +struct TestAttributeCollisions { + @execution(concurrent) nonisolated func testNonIsolated() async {} + // expected-error@-1 {{cannot use '@execution(concurrent)' and 'nonisolated' on the same 'testNonIsolated()' because they serve the same purpose}} + + @execution(concurrent) func test(arg: isolated MainActor) async {} + // expected-error@-1 {{cannot use '@execution(concurrent)' on instance method 'test(arg:)' because it has an isolated parameter: 'arg'}} + + @execution(concurrent) func testIsolationAny(arg: @isolated(any) () -> Void) async {} + // expected-error@-1 {{cannot use '@execution(concurrent)' on instance method 'testIsolationAny(arg:)' because it has a dynamically isolated parameter: 'arg'}} + + @MainActor @execution(concurrent) func testGlobalActor() async {} + // expected-error@-1 {{cannot use '@execution(concurrent)' on instance method 'testGlobalActor()' isolated to global actor 'MainActor'}} + + @execution(concurrent) @Sendable func test(_: @Sendable () -> Void, _: sending Int) async {} // Ok +} From c89ba5f58c73188bd3e6dace34ff87b4af0ed329 Mon Sep 17 00:00:00 2001 From: Pavel Yaskevich Date: Thu, 16 Jan 2025 17:24:18 -0800 Subject: [PATCH 6/7] [Sema] Diagnose clash between inferred global actor isolation and @execution(concurrent) --- include/swift/AST/DiagnosticsSema.def | 2 +- lib/Sema/TypeCheckAttr.cpp | 23 ++++++++++++----------- test/attr/attr_execution.swift | 16 ++++++++++++++++ 3 files changed, 29 insertions(+), 12 deletions(-) diff --git a/include/swift/AST/DiagnosticsSema.def b/include/swift/AST/DiagnosticsSema.def index 1bb3408c5ebd6..b244e666382a1 100644 --- a/include/swift/AST/DiagnosticsSema.def +++ b/include/swift/AST/DiagnosticsSema.def @@ -8220,7 +8220,7 @@ ERROR(attr_execution_concurrent_only_on_async,none, ERROR(attr_execution_concurrent_incompatible_with_global_actor,none, "cannot use '@execution(concurrent)' on %kind0 isolated to global actor %1", - (ValueDecl *, ValueDecl *)) + (ValueDecl *, Type)) ERROR(attr_execution_concurrent_incompatible_isolated_parameter,none, "cannot use '@execution(concurrent)' on %kind0 because it has " diff --git a/lib/Sema/TypeCheckAttr.cpp b/lib/Sema/TypeCheckAttr.cpp index f7a2afc8b5e1f..34ac04a751dd7 100644 --- a/lib/Sema/TypeCheckAttr.cpp +++ b/lib/Sema/TypeCheckAttr.cpp @@ -264,9 +264,6 @@ class AttributeChecker : public AttributeVisitor { return; } - // Checks based on explicit attributes, inferred ones would - // have to be handled during actor isolation inference. - switch (attr->getBehavior()) { case ExecutionKind::Concurrent: { // 'concurrent' doesn't work with explicit `nonisolated` @@ -277,14 +274,6 @@ class AttributeChecker : public AttributeVisitor { diag::attr_execution_concurrent_incompatible_with_nonisolated, F); return; } - - if (auto globalActor = F->getGlobalActorAttr()) { - diagnoseAndRemoveAttr( - attr, - diag::attr_execution_concurrent_incompatible_with_global_actor, F, - globalActor->second); - return; - } } auto parameters = F->getParameters(); @@ -317,6 +306,18 @@ class AttributeChecker : public AttributeVisitor { } } + // We need isolation check here because global actor isolation + // could be inferred. + + auto isolation = getActorIsolation(F); + if (isolation.isGlobalActor()) { + diagnoseAndRemoveAttr( + attr, + diag::attr_execution_concurrent_incompatible_with_global_actor, F, + isolation.getGlobalActor()); + return; + } + break; } diff --git a/test/attr/attr_execution.swift b/test/attr/attr_execution.swift index 75db4133619c5..79f5a059da756 100644 --- a/test/attr/attr_execution.swift +++ b/test/attr/attr_execution.swift @@ -55,3 +55,19 @@ struct TestAttributeCollisions { @execution(concurrent) @Sendable func test(_: @Sendable () -> Void, _: sending Int) async {} // Ok } + +@MainActor +protocol P { + func test() async +} + +struct InfersMainActor : P { + @execution(concurrent) func test() async {} + // expected-error@-1 {{cannot use '@execution(concurrent)' on instance method 'test()' isolated to global actor 'MainActor'}} +} + +@MainActor +struct IsolatedType { + @execution(concurrent) func test() async {} + // expected-error@-1 {{cannot use '@execution(concurrent)' on instance method 'test()' isolated to global actor 'MainActor'}} +} From fc1e650b89a85581ffc2676d23e74b05a2d6af26 Mon Sep 17 00:00:00 2001 From: Pavel Yaskevich Date: Thu, 16 Jan 2025 17:53:51 -0800 Subject: [PATCH 7/7] [Tests] NFC: Add serialization test for `@execution(...)` attribute --- test/ModuleInterface/attrs.swift | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/test/ModuleInterface/attrs.swift b/test/ModuleInterface/attrs.swift index 2e33b59183b78..32be2a3c58120 100644 --- a/test/ModuleInterface/attrs.swift +++ b/test/ModuleInterface/attrs.swift @@ -1,8 +1,13 @@ -// RUN: %target-swift-emit-module-interface(%t.swiftinterface) %s -module-name attrs -enable-experimental-feature ABIAttribute +// RUN: %target-swift-emit-module-interface(%t.swiftinterface) %s -module-name attrs \ +// RUN: -enable-experimental-feature ABIAttribute \ +// RUN: -enable-experimental-feature NonIsolatedAsyncInheritsIsolationFromContext + // RUN: %target-swift-typecheck-module-from-interface(%t.swiftinterface) -module-name attrs + // RUN: %FileCheck %s --input-file %t.swiftinterface // REQUIRES: swift_feature_ABIAttribute +// REQUIRES: swift_feature_NonIsolatedAsyncInheritsIsolationFromContext // CHECK: @_transparent public func glass() -> Swift.Int { return 0 }{{$}} @_transparent public func glass() -> Int { return 0 } @@ -49,3 +54,11 @@ public var abiAttrOnVar: Int = 42 // CHECK: @available(*, unavailable, message: "this compiler cannot match the ABI specified by the @abi attribute") // CHECK: public var abiAttrOnVar: Swift.Int // CHECK: #endif + +@execution(concurrent) +public func testExecutionConcurrent() async {} +// CHECK: @execution(concurrent) public func testExecutionConcurrent() async + +@execution(caller) +public func testExecutionCaller() async {} +// CHECK: @execution(caller) public func testExecutionCaller() async