diff --git a/Runtimes/Core/Concurrency/CMakeLists.txt b/Runtimes/Core/Concurrency/CMakeLists.txt index 8bd8d12a5a2fb..8c0cba91e335d 100644 --- a/Runtimes/Core/Concurrency/CMakeLists.txt +++ b/Runtimes/Core/Concurrency/CMakeLists.txt @@ -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 @@ -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 diff --git a/docs/ReferenceGuides/UnderscoredAttributes.md b/docs/ReferenceGuides/UnderscoredAttributes.md index 07d66bc5013b9..a9db7c7ea998e 100644 --- a/docs/ReferenceGuides/UnderscoredAttributes.md +++ b/docs/ReferenceGuides/UnderscoredAttributes.md @@ -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. diff --git a/include/swift/ABI/Executor.h b/include/swift/ABI/Executor.h index 5523255c05076..ce219061bb353 100644 --- a/include/swift/ABI/Executor.h +++ b/include/swift/ABI/Executor.h @@ -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, diff --git a/include/swift/AST/Attr.h b/include/swift/AST/Attr.h index 0a49634fec4f4..5d09b1e3dbcc3 100644 --- a/include/swift/AST/Attr.h +++ b/include/swift/AST/Attr.h @@ -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" @@ -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" @@ -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, @@ -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(modifier); + assert((getModifier() == modifier) && "not enough bits for modifier"); + } + + InheritActorContextModifier getModifier() const { + return static_cast(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 diff --git a/include/swift/AST/AttrKind.h b/include/swift/AST/AttrKind.h index 7ac94fb07c0e2..d1f7facf1e953 100644 --- a/include/swift/AST/AttrKind.h +++ b/include/swift/AST/AttrKind.h @@ -142,6 +142,20 @@ enum : unsigned { static_cast(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(InheritActorContextModifier::Last_InheritActorContextKind)) }; + 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 5ab0c87977644..28560878799c4 100644 --- a/include/swift/AST/DeclAttr.def +++ b/include/swift/AST/DeclAttr.def @@ -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, diff --git a/include/swift/AST/Expr.h b/include/swift/AST/Expr.h index 2d052da7ce819..a0984699cb53a 100644 --- a/include/swift/AST/Expr.h +++ b/include/swift/AST/Expr.h @@ -267,7 +267,7 @@ class alignas(8) Expr : public ASTAllocated { 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, @@ -279,6 +279,7 @@ class alignas(8) Expr : public ASTAllocated { /// 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. @@ -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 diff --git a/include/swift/AST/KnownIdentifiers.def b/include/swift/AST/KnownIdentifiers.def index c4651036541ea..157cf4ff111d3 100644 --- a/include/swift/AST/KnownIdentifiers.def +++ b/include/swift/AST/KnownIdentifiers.def @@ -326,6 +326,7 @@ IDENTIFIER_WITH_NAME(builderSelf, "$builderSelf") // Attribute options IDENTIFIER_(_always) +IDENTIFIER(always) IDENTIFIER_(assumed) IDENTIFIER(checked) IDENTIFIER(never) diff --git a/include/swift/AST/Types.h b/include/swift/AST/Types.h index dc16ee631d578..90d409b0aa3cd 100644 --- a/include/swift/AST/Types.h +++ b/include/swift/AST/Types.h @@ -4049,6 +4049,7 @@ struct ParameterListInfo { SmallBitVector propertyWrappers; SmallBitVector implicitSelfCapture; SmallBitVector inheritActorContext; + SmallBitVector inheritActorContextAlways; SmallBitVector variadicGenerics; SmallBitVector sendingParameters; @@ -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; diff --git a/include/swift/Basic/Features.def b/include/swift/Basic/Features.def index 1b46be47bb81a..24994fe2ac422 100644 --- a/include/swift/Basic/Features.def +++ b/include/swift/Basic/Features.def @@ -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") diff --git a/include/swift/Parse/IDEInspectionCallbacks.h b/include/swift/Parse/IDEInspectionCallbacks.h index 938083795af51..3605662f6ac1c 100644 --- a/include/swift/Parse/IDEInspectionCallbacks.h +++ b/include/swift/Parse/IDEInspectionCallbacks.h @@ -39,7 +39,8 @@ enum class ParameterizedDeclAttributeKind { Available, FreestandingMacro, AttachedMacro, - StorageRestrictions + StorageRestrictions, + InheritActorContext }; /// A bit of a hack. When completing inside the '@storageRestrictions' diff --git a/lib/AST/ASTDumper.cpp b/lib/AST/ASTDumper.cpp index 805d444ac8047..5c7d0579db400 100644 --- a/lib/AST/ASTDumper.cpp +++ b/lib/AST/ASTDumper.cpp @@ -4110,8 +4110,14 @@ class PrintExpr : public ExprVisitor, 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"), diff --git a/lib/AST/Attr.cpp b/lib/AST/Attr.cpp index 37169d63a30b5..2cea0bd9152e9 100644 --- a/lib/AST/Attr.cpp +++ b/lib/AST/Attr.cpp @@ -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" @@ -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" @@ -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 @@ -1531,6 +1560,18 @@ bool DeclAttribute::printImpl(ASTPrinter &Printer, const PrintOptions &Options, break; } + case DeclAttrKind::InheritActorContext: { + Printer.printAttrName("@_inheritActorContext"); + switch (cast(this)->getModifier()) { + case InheritActorContextModifier::Default: + break; + case InheritActorContextModifier::Always: + Printer << "(always)"; + break; + } + break; + } + case DeclAttrKind::MacroRole: { auto Attr = cast(this); @@ -1915,6 +1956,13 @@ StringRef DeclAttribute::getAttrName() const { case NonIsolatedModifier::NonSending: return "nonisolated(nonsending)"; } + case DeclAttrKind::InheritActorContext: + switch (cast(this)->getModifier()) { + case InheritActorContextModifier::Default: + return "@_inheritActorContext"; + case InheritActorContextModifier::Always: + return "@_inheritActorContext(always)"; + } case DeclAttrKind::MacroRole: switch (cast(this)->getMacroSyntax()) { case MacroSyntax::Freestanding: diff --git a/lib/AST/Type.cpp b/lib/AST/Type.cpp index d78d66d68f649..7e93dc5e5c0f0 100644 --- a/lib/AST/Type.cpp +++ b/lib/AST/Type.cpp @@ -1366,6 +1366,7 @@ ParameterListInfo::ParameterListInfo( propertyWrappers.resize(params.size()); implicitSelfCapture.resize(params.size()); inheritActorContext.resize(params.size()); + inheritActorContextAlways.resize(params.size()); // FIXME: don't need that? variadicGenerics.resize(params.size()); sendingParameters.resize(params.size()); @@ -1422,8 +1423,11 @@ ParameterListInfo::ParameterListInfo( implicitSelfCapture.set(i); } - if (param->getAttrs().hasAttribute()) { + if (auto attr = param->getAttrs().getAttribute()) { inheritActorContext.set(i); + if (attr->isAlways()) { + inheritActorContextAlways.set(i); + } } if (param->getInterfaceType()->is()) { @@ -1463,6 +1467,12 @@ bool ParameterListInfo::inheritsActorContext(unsigned paramIdx) const { : false; } +bool ParameterListInfo::inheritsActorContextAlways(unsigned paramIdx) const { + return paramIdx < inheritActorContextAlways.size() + ? inheritActorContextAlways[paramIdx] + : false; +} + bool ParameterListInfo::isVariadicGenericParameter(unsigned paramIdx) const { return paramIdx < variadicGenerics.size() ? variadicGenerics[paramIdx] diff --git a/lib/IDE/CompletionLookup.cpp b/lib/IDE/CompletionLookup.cpp index 2dee28a4455bf..ab861f006e627 100644 --- a/lib/IDE/CompletionLookup.cpp +++ b/lib/IDE/CompletionLookup.cpp @@ -3141,6 +3141,9 @@ void CompletionLookup::getAttributeDeclParamCompletions( addDeclAttrParamKeyword("unsafe", /*Parameters=*/{}, "", false); addDeclAttrParamKeyword("nonsending", /*Parameters=*/{}, "", false); break; + case ParameterizedDeclAttributeKind::InheritActorContext: + addDeclAttrParamKeyword("always", /*Parameters=*/{}, "", false); + break; case ParameterizedDeclAttributeKind::AccessControl: addDeclAttrParamKeyword("set", /*Parameters=*/{}, "", false); break; diff --git a/lib/Parse/ParseDecl.cpp b/lib/Parse/ParseDecl.cpp index 2294a70f66fbf..439b4eb7e8983 100644 --- a/lib/Parse/ParseDecl.cpp +++ b/lib/Parse/ParseDecl.cpp @@ -3773,6 +3773,24 @@ ParserStatus Parser::parseNewDeclAttribute(DeclAttributes &Attributes, } break; } + case DeclAttrKind::InheritActorContext: { + AttrRange = Loc; + std::optional Modifier(InheritActorContextModifier::Default); + Modifier = parseSingleAttrOption( + *this, Loc, AttrRange, AttrName, DK, { + {Context.Id_always, InheritActorContextModifier::Always} + }, *Modifier, ParameterizedDeclAttributeKind::InheritActorContext); + if (!Modifier) { + makeParserSuccess(); + } + + if (!DiscardAttribute) { + Attributes.add(new (Context) InheritActorContextAttr( + AtLoc, AttrRange, *Modifier, /*implicit=*/false)); + } + + break; + } case DeclAttrKind::MacroRole: { auto syntax = (AttrName == "freestanding" ? MacroSyntax::Freestanding : MacroSyntax::Attached); diff --git a/lib/Sema/CSApply.cpp b/lib/Sema/CSApply.cpp index c2228bbd667d6..31e2acd1bb0db 100644 --- a/lib/Sema/CSApply.cpp +++ b/lib/Sema/CSApply.cpp @@ -6099,12 +6099,17 @@ static bool hasCurriedSelf(ConstraintSystem &cs, ConcreteDeclRef callee, /// Apply the contextually Sendable flag to the given expression, static void applyContextualClosureFlags(Expr *expr, bool implicitSelfCapture, bool inheritActorContext, + bool inheritActorContextAlways, bool isPassedToSendingParameter, bool requiresDynamicIsolationChecking, bool isMacroArg) { if (auto closure = dyn_cast(expr)) { closure->setAllowsImplicitSelfCapture(implicitSelfCapture); - closure->setInheritsActorContext(inheritActorContext); + if (inheritActorContext) { + closure->setInheritsActorContext(InheritActorContextModifier::Default); + } else if (inheritActorContextAlways) { + closure->setInheritsActorContext(InheritActorContextModifier::Always); + } closure->setIsPassedToSendingParameter(isPassedToSendingParameter); closure->setRequiresDynamicIsolationChecking( requiresDynamicIsolationChecking); @@ -6114,7 +6119,9 @@ static void applyContextualClosureFlags(Expr *expr, bool implicitSelfCapture, if (auto captureList = dyn_cast(expr)) { applyContextualClosureFlags(captureList->getClosureBody(), - implicitSelfCapture, inheritActorContext, + implicitSelfCapture, + inheritActorContext, + inheritActorContextAlways, isPassedToSendingParameter, requiresDynamicIsolationChecking, isMacroArg); @@ -6123,6 +6130,7 @@ static void applyContextualClosureFlags(Expr *expr, bool implicitSelfCapture, if (auto identity = dyn_cast(expr)) { applyContextualClosureFlags(identity->getSubExpr(), implicitSelfCapture, inheritActorContext, + inheritActorContextAlways, isPassedToSendingParameter, requiresDynamicIsolationChecking, isMacroArg); @@ -6258,11 +6266,13 @@ ArgumentList *ExprRewriter::coerceCallArguments( bool isImplicitSelfCapture = paramInfo.isImplicitSelfCapture(paramIdx); bool inheritsActorContext = paramInfo.inheritsActorContext(paramIdx); + bool inheritsActorContextAlways = paramInfo.inheritsActorContextAlways(paramIdx); bool isPassedToSendingParameter = paramInfo.isSendingParameter(paramIdx); bool isMacroArg = isExpr(locator.getAnchor()); applyContextualClosureFlags(argument, isImplicitSelfCapture, inheritsActorContext, + inheritsActorContextAlways, isPassedToSendingParameter, closuresRequireDynamicIsolationChecking, isMacroArg); @@ -7771,7 +7781,7 @@ Expr *ExprRewriter::coerceToType(Expr *expr, Type toType, // closure, do that here. fromEI = fromFunc->getExtInfo(); bool shouldPropagateAsync = - !isEffectPolymorphic(EffectKind::Async) || closureInheritsActorContext(expr); + !isEffectPolymorphic(EffectKind::Async) || closureInheritsActorContext(expr); // NOTE HERE if (toEI.isAsync() && !fromEI.isAsync() && shouldPropagateAsync) { auto newFromFuncType = fromFunc->withExtInfo(fromEI.withAsync()); if (applyTypeToClosureExpr(cs, expr, newFromFuncType)) { diff --git a/lib/Serialization/Deserialization.cpp b/lib/Serialization/Deserialization.cpp index e00c099d3f225..697320a74419e 100644 --- a/lib/Serialization/Deserialization.cpp +++ b/lib/Serialization/Deserialization.cpp @@ -6517,6 +6517,16 @@ llvm::Error DeclDeserializer::deserializeDeclCommon() { break; } + case decls_block::InheritActorContext_DECL_ATTR: { + unsigned modifier; + bool isImplicit{}; + serialization::decls_block::InheritActorContextDeclAttrLayout::readRecord( + scratch, modifier, isImplicit); + Attr = new (ctx) InheritActorContextAttr( + {}, {}, static_cast(modifier), isImplicit); + break; + } + case decls_block::MacroRole_DECL_ATTR: { bool isImplicit; uint8_t rawMacroSyntax; diff --git a/lib/Serialization/ModuleFormat.h b/lib/Serialization/ModuleFormat.h index 461c59d27ad36..fb2fd94911e77 100644 --- a/lib/Serialization/ModuleFormat.h +++ b/lib/Serialization/ModuleFormat.h @@ -2553,6 +2553,12 @@ namespace decls_block { BCFixed<1> // implicit flag >; + using InheritActorContextDeclAttrLayout = + BCRecordLayout, // the modifier (default (""), always) + BCFixed<1> // implicit flag + >; + using MacroRoleDeclAttrLayout = BCRecordLayout< MacroRole_DECL_ATTR, BCFixed<1>, // implicit flag diff --git a/lib/Serialization/Serialization.cpp b/lib/Serialization/Serialization.cpp index adc4742aeb497..88548e66eb650 100644 --- a/lib/Serialization/Serialization.cpp +++ b/lib/Serialization/Serialization.cpp @@ -3468,6 +3468,14 @@ class Serializer::DeclSerializer : public DeclVisitor { static_cast(theAttr->getModifier()), theAttr->isImplicit()); return; } + case DeclAttrKind::InheritActorContext: { + auto *theAttr = cast(DA); + auto abbrCode = S.DeclTypeAbbrCodes[InheritActorContextDeclAttrLayout::Code]; + InheritActorContextDeclAttrLayout::emitRecord( + S.Out, S.ScratchRecord, abbrCode, + static_cast(theAttr->getModifier()), theAttr->isImplicit()); + return; + } case DeclAttrKind::MacroRole: { auto *theAttr = cast(DA); diff --git a/stdlib/public/Concurrency/Actor.cpp b/stdlib/public/Concurrency/Actor.cpp index c59f05da93d58..c0c50340e04e9 100644 --- a/stdlib/public/Concurrency/Actor.cpp +++ b/stdlib/public/Concurrency/Actor.cpp @@ -2526,7 +2526,7 @@ swift_task_startSynchronouslyImpl(AsyncTask *task, _swift_task_setCurrent(originalTask); } else { assert(swift_task_isCurrentExecutor(targetExecutor) && - "startSynchronously must only be invoked when it is correctly in " + "immediate must only be invoked when it is correctly in " "the same isolation already, but wasn't!"); // We can run synchronously, we're on the expected executor so running in diff --git a/stdlib/public/Concurrency/CMakeLists.txt b/stdlib/public/Concurrency/CMakeLists.txt index 4f4404e42a100..443fb92a679d0 100644 --- a/stdlib/public/Concurrency/CMakeLists.txt +++ b/stdlib/public/Concurrency/CMakeLists.txt @@ -223,7 +223,7 @@ add_swift_target_library(swift_Concurrency ${SWIFT_STDLIB_LIBRARY_BUILD_TYPES} I GYB_SOURCES TaskGroup+addTask.swift.gyb - Task+startSynchronously.swift.gyb + Task+immediate.swift.gyb SWIFT_MODULE_DEPENDS ${SWIFT_CONCURRENCY_DEPENDENCIES} SWIFT_MODULE_DEPENDS_ANDROID Android diff --git a/stdlib/public/Concurrency/Task+startSynchronously.swift.gyb b/stdlib/public/Concurrency/Task+immediate.swift.gyb similarity index 96% rename from stdlib/public/Concurrency/Task+startSynchronously.swift.gyb rename to stdlib/public/Concurrency/Task+immediate.swift.gyb index c2331fdf6c8c7..6efa0d4c88c16 100644 --- a/stdlib/public/Concurrency/Task+startSynchronously.swift.gyb +++ b/stdlib/public/Concurrency/Task+immediate.swift.gyb @@ -13,7 +13,7 @@ import Swift @_implementationOnly import SwiftConcurrencyInternalShims -// ==== Task.startSynchronously ------------------------------------------------ +// ==== Task.immediate ------------------------------------------------ % METHOD_VARIANTS = [ % 'THROWING', @@ -56,12 +56,12 @@ extension Task where Failure == ${FAILURE_TYPE} { /// - Returns: A reference to the unstructured task which may be awaited on. @available(SwiftStdlib 6.2, *) @discardableResult - public static func startSynchronously( + public static func immediate( name: String? = nil, priority: TaskPriority? = nil, % # NOTE: This closure cannot be 'sending' because we'll trigger ' pattern that the region based isolation checker does not understand how to check' - % # In this case: `func syncOnMyGlobalActor() { Task.startSynchronously { @MyGlobalActor in } }` - @_implicitSelfCapture _ operation: __owned @isolated(any) @escaping () async throws -> Success + % # In this case: `func syncOnMyGlobalActor() { Task.immediate { @MyGlobalActor in } }` + @_inheritActorContext(always) @_implicitSelfCapture _ operation: __owned @Sendable @isolated(any) @escaping () async throws -> Success ) -> Task { let builtinSerialExecutor = diff --git a/test/ASTGen/attrs.swift b/test/ASTGen/attrs.swift index 9b9c3aba5e497..38cadbd5f0597 100644 --- a/test/ASTGen/attrs.swift +++ b/test/ASTGen/attrs.swift @@ -247,6 +247,9 @@ struct LayoutOuter { } @_rawLayout(like: LayoutOuter.Nested) struct TypeExprTest: ~Copyable {} +func take(@_inheritActorContext param: () -> async ()) { } +func take(@_inheritActorContext(always) param: () -> async ()) { } + @reasync protocol ReasyncProtocol {} @rethrows protocol RethrowingProtocol { func source() throws diff --git a/test/Concurrency/Runtime/startSynchronously.swift b/test/Concurrency/Runtime/task_immediate.swift similarity index 68% rename from test/Concurrency/Runtime/startSynchronously.swift rename to test/Concurrency/Runtime/task_immediate.swift index 6ea63196a5753..bce0fc75fc31b 100644 --- a/test/Concurrency/Runtime/startSynchronously.swift +++ b/test/Concurrency/Runtime/task_immediate.swift @@ -1,6 +1,3 @@ -// FIXME: Marking this disabled since we're reworking the semantics and the test is a bit racy until we do -// REQUIRES: rdar149506152 - // RUN: %empty-directory(%t) // RUN: %target-build-swift -Xfrontend -disable-availability-checking %s %import-libdispatch -swift-version 6 -o %t/a.out // RUN: %target-codesign %t/a.out @@ -109,24 +106,24 @@ func syncOnMyGlobalActor() -> [Task] { // This task must be guaranteed to happen AFTER 'tt' because we are already on this actor // so this enqueue must happen after we give up the actor. - print("schedule Task { @MyGlobalActor }, before startSynchronously [thread:\(getCurrentThreadID())] @ :\(#line)") + print("schedule Task { @MyGlobalActor }, before immediate [thread:\(getCurrentThreadID())] @ :\(#line)") let t1 = Task { @MyGlobalActor in print("inside Task { @MyGlobalActor }, after sleep [thread:\(getCurrentThreadID())] @ :\(#line)") } - print("before startSynchronously [thread:\(getCurrentThreadID())] @ :\(#line)") + print("before immediate [thread:\(getCurrentThreadID())] @ :\(#line)") let outerTID = getCurrentThreadID() - let tt = Task.startSynchronously { @MyGlobalActor in + let tt = Task.immediate { @MyGlobalActor in let innerTID = getCurrentThreadID() - print("inside startSynchronously, outer thread = \(outerTID)") - print("inside startSynchronously, inner thread = \(innerTID)") + print("inside immediate, outer thread = \(outerTID)") + print("inside immediate, inner thread = \(innerTID)") if (compareThreadIDs(outerTID, .notEqual, innerTID)) { print("ERROR! Outer Thread ID must be equal Thread ID inside runSynchronously synchronous part!") } - print("inside startSynchronously, sleep now [thread:\(getCurrentThreadID())] @ :\(#line)") + print("inside immediate, sleep now [thread:\(getCurrentThreadID())] @ :\(#line)") _ = try? await Task.sleep(for: .seconds(1)) - print("after sleep, inside startSynchronously [thread:\(getCurrentThreadID())] @ :\(#line)") + print("after sleep, inside immediate [thread:\(getCurrentThreadID())] @ :\(#line)") } return [t1, tt] @@ -138,18 +135,18 @@ func syncOnMyGlobalActorHopToDifferentActor() -> [Task] { // This task must be guaranteed to happen AFTER 'tt' because we are already on this actor // so this enqueue must happen after we give up the actor. - print("schedule Task { @DifferentGlobalActor }, before startSynchronously [thread:\(getCurrentThreadID())] @ :\(#line)") + print("schedule Task { @DifferentGlobalActor }, before immediate [thread:\(getCurrentThreadID())] @ :\(#line)") let t1 = Task { @DifferentGlobalActor in print("inside Task { @DifferentGlobalActor } [thread:\(getCurrentThreadID())] @ :\(#line)") DifferentGlobalActor.shared.preconditionIsolated("Expected Task{} to be on DifferentGlobalActor") } - print("before startSynchronously [thread:\(getCurrentThreadID())] @ :\(#line)") + print("before immediate [thread:\(getCurrentThreadID())] @ :\(#line)") let outerTID = getCurrentThreadID() - let tt = Task.startSynchronously { @DifferentGlobalActor in + let tt = Task.immediate { @DifferentGlobalActor in let innerTID = getCurrentThreadID() - print("inside startSynchronously, outer thread = \(outerTID)") - print("inside startSynchronously, inner thread = \(innerTID)") + print("inside immediate, outer thread = \(outerTID)") + print("inside immediate, inner thread = \(innerTID)") if (compareThreadIDs(outerTID, .equal, innerTID)) { // This case specifically is NOT synchronously run because we specified a different isolation for the closure // and FORCED a hop to the DifferentGlobalActor executor. @@ -158,14 +155,14 @@ func syncOnMyGlobalActorHopToDifferentActor() -> [Task] { // We crucially need to see this task be enqueued on the different global actor, // so it did not execute "synchronously" after all - it had to hop to the other actor. dispatchPrecondition(condition: .onQueue(DifferentGlobalActor.queue)) - DifferentGlobalActor.shared.preconditionIsolated("Expected Task.startSynchronously { @DifferentGlobalActor in } to be on DifferentGlobalActor") + DifferentGlobalActor.shared.preconditionIsolated("Expected Task.immediate { @DifferentGlobalActor in } to be on DifferentGlobalActor") - print("inside startSynchronously, sleep now [thread:\(getCurrentThreadID())] @ :\(#line)") + print("inside immediate, sleep now [thread:\(getCurrentThreadID())] @ :\(#line)") _ = try? await Task.sleep(for: .milliseconds(100)) - print("inside startSynchronously, after sleep [thread:\(getCurrentThreadID())] @ :\(#line)") + print("inside immediate, after sleep [thread:\(getCurrentThreadID())] @ :\(#line)") dispatchPrecondition(condition: .onQueue(DifferentGlobalActor.queue)) - DifferentGlobalActor.shared.preconditionIsolated("Expected Task.startSynchronously { @DifferentGlobalActor in } to be on DifferentGlobalActor") + DifferentGlobalActor.shared.preconditionIsolated("Expected Task.immediate { @DifferentGlobalActor in } to be on DifferentGlobalActor") // do something here await MyGlobalActor.test() @@ -182,33 +179,33 @@ func syncOnNonTaskThread(synchronousTask behavior: SynchronousTaskBehavior) { queue.async { // This is in order so we don't have a "current task" nor any "current executor" - print("before startSynchronously [thread:\(getCurrentThreadID())] @ :\(#line)") + print("before immediate [thread:\(getCurrentThreadID())] @ :\(#line)") let outerTID = getCurrentThreadID() - let tt = Task.startSynchronously { + let tt = Task.immediate { dispatchPrecondition(condition: .onQueue(queue)) let innerTID = getCurrentThreadID() if compareThreadIDs(outerTID, .notEqual, innerTID) { - print("inside startSynchronously, outer thread = \(outerTID)") - print("inside startSynchronously, inner thread = \(innerTID)") + print("inside immediate, outer thread = \(outerTID)") + print("inside immediate, inner thread = \(innerTID)") print("ERROR! Outer Thread ID must be equal Thread ID inside runSynchronously synchronous part!") } - print("inside startSynchronously [thread:\(getCurrentThreadID())] @ :\(#line)") + print("inside immediate [thread:\(getCurrentThreadID())] @ :\(#line)") switch behavior { case .suspend: // sleep until woken up by outer task; i.e. actually suspend - print("inside startSynchronously, before sleep [thread:\(getCurrentThreadID())] @ :\(#line)") + print("inside immediate, before sleep [thread:\(getCurrentThreadID())] @ :\(#line)") _ = try? await Task.sleep(for: .seconds(10)) - print("inside startSynchronously, after sleep [thread:\(getCurrentThreadID())] @ :\(#line)") + print("inside immediate, after sleep [thread:\(getCurrentThreadID())] @ :\(#line)") case .dontSuspend: - print("inside startSynchronously, done [thread:\(getCurrentThreadID())] @ :\(#line)") + print("inside immediate, done [thread:\(getCurrentThreadID())] @ :\(#line)") () } sem1.signal() } - print("after startSynchronously, outside; cancel (wakeup) the synchronous task! [thread:\(getCurrentThreadID())] @ :\(#line)") + print("after immediate, outside; cancel (wakeup) the synchronous task! [thread:\(getCurrentThreadID())] @ :\(#line)") tt.cancel() // wake up the sleep sem1.wait() @@ -235,13 +232,48 @@ await Task { @MyGlobalActor in // CHECK-LABEL: syncOnMyGlobalActor() // CHECK: Confirmed to be on @MyGlobalActor -// CHECK: schedule Task { @MyGlobalActor }, before startSynchronously [thread:[[CALLING_THREAD:.*]]] -// CHECK: before startSynchronously [thread:[[CALLING_THREAD]]] +// CHECK: schedule Task { @MyGlobalActor }, before immediate [thread:[[CALLING_THREAD:.*]]] +// CHECK: before immediate [thread:[[CALLING_THREAD]]] +// CHECK-NOT: ERROR! +// CHECK: inside immediate, sleep now +// CHECK: inside Task { @MyGlobalActor }, after sleep +// resume on some other thread +// CHECK: after sleep, inside immediate + +print("\n\n==== ------------------------------------------------------------------") +print("inherit actor context without closing over isolated reference") + +actor A { + static let queue = DispatchQueue(label: "DifferentGlobalActor-queue") + let executor: NaiveQueueExecutor + nonisolated let unownedExecutor: UnownedSerialExecutor + + init() { + self.executor = NaiveQueueExecutor(queue: DifferentGlobalActor.queue) + self.unownedExecutor = executor.asUnownedSerialExecutor() + } + + func foo() { + Task.immediate { + // doesn't capture self (!) + dispatchPrecondition(condition: .onQueue(DifferentGlobalActor.queue)) + } + } +} + +await Task { + await A().foo() +}.value + +// CHECK-LABEL: syncOnMyGlobalActor() +// CHECK: Confirmed to be on @MyGlobalActor +// CHECK: schedule Task { @MyGlobalActor }, before immediate [thread:[[CALLING_THREAD:.*]]] +// CHECK: before immediate [thread:[[CALLING_THREAD]]] // CHECK-NOT: ERROR! -// CHECK: inside startSynchronously, sleep now +// CHECK: inside immediate, sleep now // CHECK: inside Task { @MyGlobalActor }, after sleep // resume on some other thread -// CHECK: after sleep, inside startSynchronously +// CHECK: after sleep, inside immediate print("\n\n==== ------------------------------------------------------------------") print("syncOnMyGlobalActorHopToDifferentActor()") @@ -253,22 +285,22 @@ await Task { @MyGlobalActor in } }.value -// Assertion Notes: We expect the task to be on the specified queue as we force the Task.startSynchronously +// Assertion Notes: We expect the task to be on the specified queue as we force the Task.immediate // task to enqueue on the DifferentGlobalActor, however we CANNOT use threads to verify this behavior, // because dispatch may still pull tricks and reuse threads. We can only verify that we're on the right // queue, and that the `enqueue` calls on the target executor happen when we expect them to. // // CHECK: syncOnMyGlobalActorHopToDifferentActor() // CHECK: Confirmed to be on @MyGlobalActor -// CHECK: before startSynchronously +// CHECK: before immediate // This IS actually enqueueing on the target actor (not synchronous), as expected: // CHECK: NaiveQueueExecutor(DifferentGlobalActor-queue) enqueue -// CHECK: inside startSynchronously, sleep now +// CHECK: inside immediate, sleep now // After the sleep we get back onto the specified executor as expected // CHECK: NaiveQueueExecutor(DifferentGlobalActor-queue) enqueue -// CHECK: inside startSynchronously, after sleep +// CHECK: inside immediate, after sleep print("\n\n==== ------------------------------------------------------------------") var behavior: SynchronousTaskBehavior = .suspend @@ -277,12 +309,12 @@ syncOnNonTaskThread(synchronousTask: behavior) // CHECK-LABEL: syncOnNonTaskThread(synchronousTask: suspend) // No interleaving allowed between "before" and "inside": -// CHECK-NEXT: before startSynchronously [thread:[[CALLING_THREAD2:.*]]] +// CHECK-NEXT: before immediate [thread:[[CALLING_THREAD2:.*]]] // CHECK-NOT: ERROR! -// CHECK-NEXT: inside startSynchronously [thread:[[CALLING_THREAD2]]] -// CHECK-NEXT: inside startSynchronously, before sleep [thread:[[CALLING_THREAD2]]] -// CHECK-NEXT: after startSynchronously, outside; cancel (wakeup) the synchronous task! [thread:[[CALLING_THREAD2]]] -// CHECK-NEXT: inside startSynchronously, after sleep +// CHECK-NEXT: inside immediate [thread:[[CALLING_THREAD2]]] +// CHECK-NEXT: inside immediate, before sleep [thread:[[CALLING_THREAD2]]] +// CHECK-NEXT: after immediate, outside; cancel (wakeup) the synchronous task! [thread:[[CALLING_THREAD2]]] +// CHECK-NEXT: inside immediate, after sleep print("\n\n==== ------------------------------------------------------------------") behavior = .dontSuspend @@ -290,11 +322,11 @@ print("syncOnNonTaskThread(synchronousTask: \(behavior))") syncOnNonTaskThread(synchronousTask: behavior) // CHECK-LABEL: syncOnNonTaskThread(synchronousTask: dontSuspend) -// CHECK-NEXT: before startSynchronously [thread:[[CALLING_THREAD3:.*]]] +// CHECK-NEXT: before immediate [thread:[[CALLING_THREAD3:.*]]] // CHECK-NOT: ERROR! -// CHECK-NEXT: inside startSynchronously [thread:[[CALLING_THREAD3]]] -// CHECK: inside startSynchronously, done [thread:[[CALLING_THREAD3]]] -// CHECK: after startSynchronously, outside; cancel (wakeup) the synchronous task! [thread:[[CALLING_THREAD3]]] +// CHECK-NEXT: inside immediate [thread:[[CALLING_THREAD3]]] +// CHECK: inside immediate, done [thread:[[CALLING_THREAD3]]] +// CHECK: after immediate, outside; cancel (wakeup) the synchronous task! [thread:[[CALLING_THREAD3]]] print("\n\n==== ------------------------------------------------------------------") print("callActorFromStartSynchronousTask() - not on specific queue") @@ -302,17 +334,17 @@ callActorFromStartSynchronousTask(recipient: .recipient(Recipient())) // CHECK: callActorFromStartSynchronousTask() // No interleaving allowed between "before" and "inside": -// CHECK: before startSynchronously [thread:[[CALLING_THREAD4:.*]]] -// CHECK-NEXT: inside startSynchronously [thread:[[CALLING_THREAD4]]] +// CHECK: before immediate [thread:[[CALLING_THREAD4:.*]]] +// CHECK-NEXT: inside immediate [thread:[[CALLING_THREAD4]]] -// It is important that as we suspend on the actor call, the 'after' startSynchronously gets to run -// CHECK-NEXT: inside startSynchronously, call rec.sync() [thread:[[CALLING_THREAD4]]] -// CHECK: after startSynchronously +// It is important that as we suspend on the actor call, the 'after' immediate gets to run +// CHECK-NEXT: inside immediate, call rec.sync() [thread:[[CALLING_THREAD4]]] +// CHECK: after immediate // CHECK-NOT: ERROR! -// CHECK: inside startSynchronously, call rec.sync() done +// CHECK: inside immediate, call rec.sync() done // CHECK-NOT: ERROR! -// CHECK: inside startSynchronously, done +// CHECK: inside immediate, done /// Don't want to involve protocol calls to not confuse the test with additional details, /// so we use concrete types here. @@ -346,13 +378,13 @@ func callActorFromStartSynchronousTask(recipient rec: TargetActorToCall) { queue.async { let outerTID = getCurrentThreadID() - print("before startSynchronously [thread:\(outerTID)] @ :\(#line)") - let tt = Task.startSynchronously { + print("before immediate [thread:\(outerTID)] @ :\(#line)") + let tt = Task.immediate { dispatchPrecondition(condition: .onQueue(queue)) let innerTID = getCurrentThreadID() precondition(compareThreadIDs(outerTID, .equal, innerTID), "Outer Thread ID must be equal Thread ID inside runSynchronously synchronous part!") - print("inside startSynchronously [thread:\(getCurrentThreadID())] @ :\(#line)") + print("inside immediate [thread:\(getCurrentThreadID())] @ :\(#line)") for i in 1..<10 { queue.async { @@ -360,12 +392,12 @@ func callActorFromStartSynchronousTask(recipient rec: TargetActorToCall) { } } - print("inside startSynchronously, call rec.sync() [thread:\(getCurrentThreadID())] @ :\(#line)") + print("inside immediate, call rec.sync() [thread:\(getCurrentThreadID())] @ :\(#line)") switch rec { case .recipient(let recipient): await recipient.callAndSuspend(syncTaskThreadID: innerTID) case .recipientOnQueue(let recipient): await recipient.callAndSuspend(syncTaskThreadID: innerTID) } - print("inside startSynchronously, call rec.sync() done [thread:\(getCurrentThreadID())] @ :\(#line)") + print("inside immediate, call rec.sync() done [thread:\(getCurrentThreadID())] @ :\(#line)") // after suspension we are supposed to hop off to the global pool, // thus the thread IDs cannot be the same anymore @@ -378,11 +410,11 @@ func callActorFromStartSynchronousTask(recipient rec: TargetActorToCall) { print("NOTICE: Task resumed on same thread as it entered the synchronous task!") } - print("inside startSynchronously, done [thread:\(getCurrentThreadID())] @ :\(#line)") + print("inside immediate, done [thread:\(getCurrentThreadID())] @ :\(#line)") sem1.signal() } - print("after startSynchronously [thread:\(getCurrentThreadID())] @ :\(#line)") + print("after immediate [thread:\(getCurrentThreadID())] @ :\(#line)") sem2.signal() } @@ -395,44 +427,22 @@ print("callActorFromStartSynchronousTask() - actor in custom executor with its o let actorQueue = DispatchQueue(label: "recipient-actor-queue") callActorFromStartSynchronousTask(recipient: .recipientOnQueue(RecipientOnQueue(queue: actorQueue))) - -// 50: callActorFromStartSynchronousTask() -// 51: before startSynchronously [thread:0x00007000054f5000] @ :366 -// 52: inside startSynchronously [thread:0x00007000054f5000] @ :372 -// 53: inside startSynchronously, call rec.sync() [thread:0x00007000054f5000] @ :380 -// 54: Recipient/sync(syncTaskThreadID:) Current actor thread id = 0x000070000567e000 @ :336 -// 55: inside startSynchronously, call rec.sync() done [thread:0x000070000567e000] @ :385 -// 56: Inner thread id = 0x00007000054f5000 -// 57: Current thread id = 0x000070000567e000 -// 60: after startSynchronously [thread:0x00007000054f5000] @ :418 -// 61: - async work on queue -// 62: - async work on queue -// 63: - async work on queue -// 64: - async work on queue -// 65: - async work on queue -// 67: - async work on queue -// 68: - async work on queue -// 69: - async work on queue -// 71: Inner thread id = 0x00007000054f5000 -// 72: Current thread id = 0x000070000567e000 -// 73: inside startSynchronously, done [thread:0x000070000567e000] @ :414 - // CHECK-LABEL: callActorFromStartSynchronousTask() - actor in custom executor with its own queue // No interleaving allowed between "before" and "inside": -// CHECK: before startSynchronously [thread:[[CALLING_THREAD4:.*]]] -// CHECK-NEXT: inside startSynchronously [thread:[[CALLING_THREAD4]]] +// CHECK: before immediate [thread:[[CALLING_THREAD4:.*]]] +// CHECK-NEXT: inside immediate [thread:[[CALLING_THREAD4]]] // As we call into an actor, we must enqueue to its custom executor; // Make sure the enqueue happens as expected and only then do we give up the calling thread -// allowing the 'after startSynchronously' to run. +// allowing the 'after immediate' to run. // -// CHECK-NEXT: inside startSynchronously, call rec.sync() [thread:[[CALLING_THREAD4]]] -// CHECK: after startSynchronously +// CHECK-NEXT: inside immediate, call rec.sync() [thread:[[CALLING_THREAD4]]] +// CHECK: after immediate // CHECK-NOT: ERROR! -// CHECK: inside startSynchronously, call rec.sync() done +// CHECK: inside immediate, call rec.sync() done // CHECK-NOT: ERROR! -// CHECK: inside startSynchronously, done +// CHECK: inside immediate, done actor RecipientOnQueue: RecipientProtocol { let executor: NaiveQueueExecutor diff --git a/test/Concurrency/Runtime/task_immediate_SMALL.swift b/test/Concurrency/Runtime/task_immediate_SMALL.swift new file mode 100644 index 0000000000000..f38d7d638199c --- /dev/null +++ b/test/Concurrency/Runtime/task_immediate_SMALL.swift @@ -0,0 +1,121 @@ +// RUN: %empty-directory(%t) +// RUN: %target-build-swift -Xfrontend -disable-availability-checking %s %import-libdispatch -swift-version 6 -o %t/a.out +// RUN: %target-codesign %t/a.out +// RUN: %target-run %t/a.out | %FileCheck %s --dump-input=always + +// REQUIRES: executable_test +// REQUIRES: concurrency +// REQUIRES: concurrency_runtime +// REQUIRES: libdispatch + +// UNSUPPORTED: back_deployment_runtime +// UNSUPPORTED: back_deploy_concurrency +// UNSUPPORTED: use_os_stdlib +// UNSUPPORTED: freestanding + +import _Concurrency +import Dispatch + +enum CompareHow { + case equal + case notEqual + + func check(_ wasEqual: Bool) -> Bool { + switch self { + case .equal: wasEqual + case .notEqual: !wasEqual + } + } +} + +#if canImport(Darwin) +import Darwin +typealias ThreadID = pthread_t +func getCurrentThreadID() -> ThreadID { pthread_self() } +func compareThreadIDs(_ a: ThreadID, _ how: CompareHow, _ b: ThreadID) -> Bool { + how.check(pthread_equal(a, b) != 0) +} +#elseif canImport(Glibc) +import Glibc +typealias ThreadID = pthread_t +func getCurrentThreadID() -> ThreadID { pthread_self() } +func compareThreadIDs(_ a: ThreadID, _ how: CompareHow, _ b: ThreadID) -> Bool { + how.check(pthread_equal(a, b) != 0) +} +#elseif os(Windows) +import WinSDK +typealias ThreadID = UInt32 +func getCurrentThreadID() -> ThreadID { GetCurrentThreadId() } +func compareThreadIDs(_ a: ThreadID, _ how: CompareHow, _ b: ThreadID) -> Bool { + how.check(a == b) +} +#elseif os(WASI) +typealias ThreadID = UInt32 +func getCurrentThreadID() -> ThreadID { 0 } +func compareThreadIDs(_ a: ThreadID, _ how: CompareHow, _ b: ThreadID) -> Bool { + how.check(a == b) +} +#endif +extension ThreadID: @unchecked Sendable {} + +final class NaiveQueueExecutor: SerialExecutor { + let queue: DispatchQueue + + init(queue: DispatchQueue) { + self.queue = queue + } + + public func enqueue(_ job: consuming ExecutorJob) { + let unowned = UnownedJob(job) + print("NaiveQueueExecutor(\(self.queue.label)) enqueue [thread:\(getCurrentThreadID())]") + queue.async { + unowned.runSynchronously(on: self.asUnownedSerialExecutor()) + } + } +} + +@globalActor +actor DifferentGlobalActor { + static let queue = DispatchQueue(label: "DifferentGlobalActor-queue") + let executor: NaiveQueueExecutor + nonisolated let unownedExecutor: UnownedSerialExecutor + + init() { + self.executor = NaiveQueueExecutor(queue: DifferentGlobalActor.queue) + self.unownedExecutor = executor.asUnownedSerialExecutor() + } + + static let shared: DifferentGlobalActor = DifferentGlobalActor() + + @DifferentGlobalActor + static func test() {} +} + +print("\n\n==== ------------------------------------------------------------------") +print("inherit actor context without closing over isolated reference") + +actor A { + static let queue = DispatchQueue(label: "DifferentGlobalActor-queue") + let executor: NaiveQueueExecutor + nonisolated let unownedExecutor: UnownedSerialExecutor + + init() { + self.executor = NaiveQueueExecutor(queue: DifferentGlobalActor.queue) + self.unownedExecutor = executor.asUnownedSerialExecutor() + } + + func foo() async { + let task = Task.immediate { + // doesn't capture self (!) + dispatchPrecondition(condition: .onQueue(DifferentGlobalActor.queue)) + } + + await task.value + } +} + +await Task { + await A().foo() + print("OK") +}.value +// CHECK: OK \ No newline at end of file diff --git a/test/Concurrency/Runtime/startSynchronously_order.swift b/test/Concurrency/Runtime/task_immediate_ordering.swift similarity index 98% rename from test/Concurrency/Runtime/startSynchronously_order.swift rename to test/Concurrency/Runtime/task_immediate_ordering.swift index 03927dfaa7e5e..5214761016ea0 100644 --- a/test/Concurrency/Runtime/startSynchronously_order.swift +++ b/test/Concurrency/Runtime/task_immediate_ordering.swift @@ -17,7 +17,7 @@ import _Concurrency let max = 1000 func bar(x: Int, cc: CheckedContinuation) { - Task.startSynchronously { + Task.immediate { print("Task \(x) started") try! await Task.sleep(nanoseconds: 10000) if (x == max) { diff --git a/test/Concurrency/startSynchronouslyIsolation.swift b/test/Concurrency/task_immediate_isolation.swift similarity index 89% rename from test/Concurrency/startSynchronouslyIsolation.swift rename to test/Concurrency/task_immediate_isolation.swift index 8837fecdba3f1..45198e1085bee 100644 --- a/test/Concurrency/startSynchronouslyIsolation.swift +++ b/test/Concurrency/task_immediate_isolation.swift @@ -4,19 +4,19 @@ @available(SwiftStdlib 6.2, *) func sync() -> Task { - Task.startSynchronously { + Task.immediate { return "" } } @available(SwiftStdlib 6.2, *) func async() async throws { - let t1 = Task.startSynchronously { + let t1 = Task.immediate { return "" } let _: String = await t1.value - let t2: Task = Task.startSynchronously { + let t2: Task = Task.immediate { throw CancellationError() } let _: String = try await t2.value diff --git a/test/abi/macOS/arm64/concurrency.swift b/test/abi/macOS/arm64/concurrency.swift index 969a413ed5fb3..23958accf152a 100644 --- a/test/abi/macOS/arm64/concurrency.swift +++ b/test/abi/macOS/arm64/concurrency.swift @@ -389,7 +389,7 @@ Added: _$sScTss5NeverORszABRs_rlE4nameSSSgvgZ Added: _$sScTss5NeverORszABRs_rlE4nameSSSgvpZMV Added: _swift_task_getCurrentTaskName -// startSynchronously +// immediate Added: _swift_task_startSynchronously Added: _$ss27ThrowingDiscardingTaskGroupV05startC13Synchronously4name8priority9operationySSSg_ScPSgyyYaKYAcntF Added: _$ss27ThrowingDiscardingTaskGroupV05startC28SynchronouslyUnlessCancelled4name8priority9operationySSSg_ScPSgyyYaKYAcntF diff --git a/test/abi/macOS/x86_64/concurrency.swift b/test/abi/macOS/x86_64/concurrency.swift index aadf9ef61b2e6..9459dad04270e 100644 --- a/test/abi/macOS/x86_64/concurrency.swift +++ b/test/abi/macOS/x86_64/concurrency.swift @@ -389,7 +389,7 @@ Added: _$sScTss5NeverORszABRs_rlE4nameSSSgvgZ Added: _$sScTss5NeverORszABRs_rlE4nameSSSgvpZMV Added: _swift_task_getCurrentTaskName -// startSynchronously +// immediate Added: _swift_task_startSynchronously Added: _$ss27ThrowingDiscardingTaskGroupV05startC13Synchronously4name8priority9operationySSSg_ScPSgyyYaKYAcntF Added: _$ss27ThrowingDiscardingTaskGroupV05startC28SynchronouslyUnlessCancelled4name8priority9operationySSSg_ScPSgyyYaKYAcntF diff --git a/test/attr/attr_abi.swift b/test/attr/attr_abi.swift index 49116208b3740..e83fa2411c94e 100644 --- a/test/attr/attr_abi.swift +++ b/test/attr/attr_abi.swift @@ -1407,9 +1407,14 @@ func nonEphemeral2(_: UnsafeRawPointer) {} func nonEphemeral3(@_nonEphemeral _: UnsafeRawPointer) {} // @_inheritActorContext -- banned in @abi -@abi(func inheritActorContext1(@_inheritActorContext fn: @Sendable @escaping () async -> Void)) // expected-error {{unused '_inheritActorContext' attribute in '@abi'}} {{32-53=}} +@abi(func inheritActorContext1(@_inheritActorContext fn: @Sendable @escaping () async -> Void)) // expected-error {{unused '@_inheritActorContext' attribute in '@abi'}} {{32-53=}} func inheritActorContext1(@_inheritActorContext fn: @Sendable @escaping () async -> Void) {} +// FIXME: it must be allowed in ABI right? +// @_inheritActorContext(always) -- banned in @abi +@abi(func inheritActorContext1(@_inheritActorContext(always) fn: @Sendable @escaping () async -> Void)) // expected-error {{unused '@_inheritActorContext(always)' attribute in '@abi'}} {{32-53=}} +func inheritActorContext1(@_inheritActorContext(always) fn: @Sendable @escaping () async -> Void) {} + @abi(func inheritActorContext2(@_inheritActorContext fn: @Sendable @escaping () async -> Void)) // expected-error {{unused '_inheritActorContext' attribute in '@abi'}} {{32-53=}} func inheritActorContext2(fn: @Sendable @escaping () async -> Void) {} diff --git a/utils/swift_snapshot_tool/Package.resolved b/utils/swift_snapshot_tool/Package.resolved new file mode 100644 index 0000000000000..3dcc511149ffb --- /dev/null +++ b/utils/swift_snapshot_tool/Package.resolved @@ -0,0 +1,15 @@ +{ + "originHash" : "80ba966d30db4d84324c2bda4f2b419b9e6fe208fa1f70080f74c7aa3d432a49", + "pins" : [ + { + "identity" : "swift-argument-parser", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-argument-parser", + "state" : { + "revision" : "41982a3656a71c768319979febd796c6fd111d5c", + "version" : "1.5.0" + } + } + ], + "version" : 3 +}