From b9f1e7f6265adeab24b15a1b5830a4b5298a0703 Mon Sep 17 00:00:00 2001 From: Doug Gregor Date: Wed, 21 Apr 2021 15:13:38 -0700 Subject: [PATCH 1/5] [Concurrency] Add `@_inheritActorContext` hidden parameter attribute. This new attribute can be used on parameters of `@Sendable async` type to indicate that the closures arguments passed to such parameters should inherit the actor context where they are formed, which is not the normal behavior for `@Sendable` closures. Another part of rdar://76927008. --- include/swift/AST/Attr.def | 5 ++++ include/swift/AST/Expr.h | 19 ++++++++++-- include/swift/AST/Types.h | 5 ++++ lib/AST/ASTDumper.cpp | 2 ++ lib/AST/Type.cpp | 13 +++++++- lib/Sema/CSApply.cpp | 13 ++++---- lib/Sema/TypeCheckAttr.cpp | 1 + lib/Sema/TypeCheckConcurrency.cpp | 30 ++++++++++++------- lib/Sema/TypeCheckDeclOverride.cpp | 1 + test/Concurrency/actor_isolation.swift | 29 +++++++++++++++++- test/Concurrency/global_actor_inference.swift | 26 +++++++++++++++- 11 files changed, 123 insertions(+), 21 deletions(-) diff --git a/include/swift/AST/Attr.def b/include/swift/AST/Attr.def index a11372c077801..1d3b4566b9f95 100644 --- a/include/swift/AST/Attr.def +++ b/include/swift/AST/Attr.def @@ -652,6 +652,11 @@ SIMPLE_DECL_ATTR(_implicitSelfCapture, ImplicitSelfCapture, ABIStableToAdd | ABIStableToRemove | APIStableToAdd | APIBreakingToRemove, 115) +SIMPLE_DECL_ATTR(_inheritActorContext, InheritActorContext, + OnParam | UserInaccessible | + ABIStableToAdd | ABIStableToRemove | APIBreakingToAdd | APIBreakingToRemove, + 116) + #undef TYPE_ATTR #undef DECL_ATTR_ALIAS #undef CONTEXTUAL_DECL_ATTR_ALIAS diff --git a/include/swift/AST/Expr.h b/include/swift/AST/Expr.h index fd0160a816f44..a03037a5b3173 100644 --- a/include/swift/AST/Expr.h +++ b/include/swift/AST/Expr.h @@ -292,14 +292,18 @@ class alignas(8) Expr { Kind : 2 ); - SWIFT_INLINE_BITFIELD(ClosureExpr, AbstractClosureExpr, 1+1, + SWIFT_INLINE_BITFIELD(ClosureExpr, AbstractClosureExpr, 1+1+1, /// True if closure parameters were synthesized from anonymous closure /// variables. HasAnonymousClosureVars : 1, /// True if "self" can be captured implicitly without requiring "self." /// on each member reference. - ImplicitSelfCapture : 1 + ImplicitSelfCapture : 1, + + /// True if this @Sendable async closure parameter should implicitly + /// inherit the actor context from where it was formed. + InheritActorContext : 1 ); SWIFT_INLINE_BITFIELD_FULL(BindOptionalExpr, Expr, 16, @@ -3876,6 +3880,7 @@ class ClosureExpr : public AbstractClosureExpr { setParameterList(params); Bits.ClosureExpr.HasAnonymousClosureVars = false; Bits.ClosureExpr.ImplicitSelfCapture = false; + Bits.ClosureExpr.InheritActorContext = false; } SourceRange getSourceRange() const; @@ -3914,6 +3919,16 @@ class ClosureExpr : public AbstractClosureExpr { Bits.ClosureExpr.ImplicitSelfCapture = value; } + /// Whether this closure should implicitly inherit the actor context from + /// where it was formed. This only affects @Sendable async closures. + bool inheritsActorContext() const { + return Bits.ClosureExpr.InheritActorContext; + } + + void setInheritsActorContext(bool value = true) { + Bits.ClosureExpr.InheritActorContext = value; + } + /// Determine whether this closure expression has an /// explicitly-specified result type. bool hasExplicitResultType() const { return ArrowLoc.isValid(); } diff --git a/include/swift/AST/Types.h b/include/swift/AST/Types.h index 4c7b8668f57de..105aee97bfeb0 100644 --- a/include/swift/AST/Types.h +++ b/include/swift/AST/Types.h @@ -3382,6 +3382,7 @@ struct ParameterListInfo { SmallBitVector unsafeSendable; SmallBitVector unsafeMainActor; SmallBitVector implicitSelfCapture; + SmallBitVector inheritActorContext; public: ParameterListInfo() { } @@ -3414,6 +3415,10 @@ struct ParameterListInfo { /// 'self' to be implicit, without requiring "self.". bool isImplicitSelfCapture(unsigned paramIdx) const; + /// 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; + /// Whether there is any contextual information set on this parameter list. bool anyContextualInfo() const; diff --git a/lib/AST/ASTDumper.cpp b/lib/AST/ASTDumper.cpp index 3f274aae22c82..c1e3868bd2bd4 100644 --- a/lib/AST/ASTDumper.cpp +++ b/lib/AST/ASTDumper.cpp @@ -2548,6 +2548,8 @@ class PrintExpr : public ExprVisitor { PrintWithColorRAII(OS, ClosureModifierColor) << " single-expression"; if (E->allowsImplicitSelfCapture()) PrintWithColorRAII(OS, ClosureModifierColor) << " implicit-self"; + if (E->inheritsActorContext()) + PrintWithColorRAII(OS, ClosureModifierColor) << " inherits-actor-context"; if (E->getParameters()) { OS << '\n'; diff --git a/lib/AST/Type.cpp b/lib/AST/Type.cpp index 1ba28734e5b54..da32af4b45498 100644 --- a/lib/AST/Type.cpp +++ b/lib/AST/Type.cpp @@ -962,6 +962,7 @@ ParameterListInfo::ParameterListInfo( unsafeSendable.resize(params.size()); unsafeMainActor.resize(params.size()); implicitSelfCapture.resize(params.size()); + inheritActorContext.resize(params.size()); // No parameter owner means no parameter list means no default arguments // - hand back the zeroed bitvector. @@ -1025,6 +1026,10 @@ ParameterListInfo::ParameterListInfo( if (param->getAttrs().hasAttribute()) { implicitSelfCapture.set(i); } + + if (param->getAttrs().hasAttribute()) { + inheritActorContext.set(i); + } } } @@ -1061,9 +1066,15 @@ bool ParameterListInfo::isImplicitSelfCapture(unsigned paramIdx) const { : false; } +bool ParameterListInfo::inheritsActorContext(unsigned paramIdx) const { + return paramIdx < inheritActorContext.size() + ? inheritActorContext[paramIdx] + : false; +} + bool ParameterListInfo::anyContextualInfo() const { return unsafeSendable.any() || unsafeMainActor.any() || - implicitSelfCapture.any(); + implicitSelfCapture.any() || inheritActorContext.any(); } /// Turn a param list into a symbolic and printable representation that does not diff --git a/lib/Sema/CSApply.cpp b/lib/Sema/CSApply.cpp index 69e2da2281b76..4e34ce274e467 100644 --- a/lib/Sema/CSApply.cpp +++ b/lib/Sema/CSApply.cpp @@ -5710,17 +5710,19 @@ static bool hasCurriedSelf(ConstraintSystem &cs, ConcreteDeclRef callee, /// Apply the contextually Sendable flag to the given expression, static void applyContextualClosureFlags( - Expr *expr, bool sendable, bool forMainActor, bool implicitSelfCapture) { + Expr *expr, bool sendable, bool forMainActor, bool implicitSelfCapture, + bool inheritActorContext) { if (auto closure = dyn_cast(expr)) { closure->setUnsafeConcurrent(sendable, forMainActor); closure->setAllowsImplicitSelfCapture(implicitSelfCapture); + closure->setInheritsActorContext(inheritActorContext); return; } if (auto captureList = dyn_cast(expr)) { applyContextualClosureFlags( captureList->getClosureBody(), sendable, forMainActor, - implicitSelfCapture); + implicitSelfCapture, inheritActorContext); } } @@ -5958,9 +5960,10 @@ Expr *ExprRewriter::coerceCallArguments( bool isMainActor = paramInfo.isUnsafeMainActor(paramIdx) || (isUnsafeSendable && apply && isMainDispatchQueue(apply->getFn())); bool isImplicitSelfCapture = paramInfo.isImplicitSelfCapture(paramIdx); + bool inheritsActorContext = paramInfo.inheritsActorContext(paramIdx); applyContextualClosureFlags( arg, isUnsafeSendable && contextUsesConcurrencyFeatures(dc), - isMainActor, isImplicitSelfCapture); + isMainActor, isImplicitSelfCapture, inheritsActorContext); // If the types exactly match, this is easy. auto paramType = param.getOldType(); @@ -6976,8 +6979,8 @@ Expr *ExprRewriter::coerceToType(Expr *expr, Type toType, } } - // If we have a ClosureExpr, then we can safely propagate the 'concurrent' - // bit to the closure without invalidating prior analysis. + // If we have a ClosureExpr, then we can safely propagate @Sendable + // to the closure without invalidating prior analysis. auto fromEI = fromFunc->getExtInfo(); if (toEI.isSendable() && !fromEI.isSendable()) { auto newFromFuncType = fromFunc->withExtInfo(fromEI.withConcurrent()); diff --git a/lib/Sema/TypeCheckAttr.cpp b/lib/Sema/TypeCheckAttr.cpp index 990550e3dd10b..24ec3054983b5 100644 --- a/lib/Sema/TypeCheckAttr.cpp +++ b/lib/Sema/TypeCheckAttr.cpp @@ -138,6 +138,7 @@ class AttributeChecker : public AttributeVisitor { IGNORED_ATTR(UnsafeSendable) IGNORED_ATTR(UnsafeMainActor) IGNORED_ATTR(ImplicitSelfCapture) + IGNORED_ATTR(InheritActorContext) #undef IGNORED_ATTR void visitAlignmentAttr(AlignmentAttr *attr) { diff --git a/lib/Sema/TypeCheckConcurrency.cpp b/lib/Sema/TypeCheckConcurrency.cpp index 4eadc7fb6bada..51d812058dafd 100644 --- a/lib/Sema/TypeCheckConcurrency.cpp +++ b/lib/Sema/TypeCheckConcurrency.cpp @@ -773,19 +773,26 @@ static bool isAsyncCall(const ApplyExpr *call) { /// features. static bool shouldDiagnoseExistingDataRaces(const DeclContext *dc); -/// Determine whether this closure is escaping. -static bool isSendableClosure(const AbstractClosureExpr *closure) { +/// Determine whether this closure should be treated as Sendable. +/// +/// \param forActorIsolation Whether this check is for the purposes of +/// determining whether the closure must be non-isolated. +static bool isSendableClosure( + const AbstractClosureExpr *closure, bool forActorIsolation) { + if (auto explicitClosure = dyn_cast(closure)) { + if (forActorIsolation && explicitClosure->inheritsActorContext()) + return false; + + if (explicitClosure->isUnsafeSendable()) + return true; + } + if (auto type = closure->getType()) { if (auto fnType = type->getAs()) if (fnType->isSendable()) return true; } - if (auto explicitClosure = dyn_cast(closure)) { - if (explicitClosure->isUnsafeSendable()) - return true; - } - return false; } @@ -2113,7 +2120,7 @@ namespace { } if (auto closure = dyn_cast(dc)) { - if (isSendableClosure(closure)) { + if (isSendableClosure(closure, /*forActorIsolation=*/true)) { return diag::actor_isolated_from_concurrent_closure; } @@ -2310,8 +2317,9 @@ namespace { } } - // Sendable closures are always actor-independent. - if (isSendableClosure(closure)) + // Sendable closures are actor-independent unless the closure has + // specifically opted into inheriting actor isolation. + if (isSendableClosure(closure, /*forActorIsolation=*/true)) return ClosureActorIsolation::forIndependent(); // A non-escaping closure gets its isolation from its context. @@ -2362,7 +2370,7 @@ bool ActorIsolationChecker::mayExecuteConcurrentlyWith( while (useContext != defContext) { // If we find a concurrent closure... it can be run concurrently. if (auto closure = dyn_cast(useContext)) { - if (isSendableClosure(closure)) + if (isSendableClosure(closure, /*forActorIsolation=*/false)) return true; } diff --git a/lib/Sema/TypeCheckDeclOverride.cpp b/lib/Sema/TypeCheckDeclOverride.cpp index 4efc99a0e9e39..1d42f617a4650 100644 --- a/lib/Sema/TypeCheckDeclOverride.cpp +++ b/lib/Sema/TypeCheckDeclOverride.cpp @@ -1547,6 +1547,7 @@ namespace { UNINTERESTING_ATTR(UnsafeSendable) UNINTERESTING_ATTR(UnsafeMainActor) UNINTERESTING_ATTR(ImplicitSelfCapture) + UNINTERESTING_ATTR(InheritActorContext) #undef UNINTERESTING_ATTR void visitAvailableAttr(AvailableAttr *attr) { diff --git a/test/Concurrency/actor_isolation.swift b/test/Concurrency/actor_isolation.swift index 8dcb330845b8c..9474eadf39eb6 100644 --- a/test/Concurrency/actor_isolation.swift +++ b/test/Concurrency/actor_isolation.swift @@ -70,7 +70,7 @@ actor MyActor: MySuperActor { class func synchronousClass() { } static func synchronousStatic() { } - func synchronous() -> String { text.first ?? "nothing" } // expected-note 19{{calls to instance method 'synchronous()' from outside of its actor context are implicitly asynchronous}} + func synchronous() -> String { text.first ?? "nothing" } // expected-note 20{{calls to instance method 'synchronous()' from outside of its actor context are implicitly asynchronous}} func asynchronous() async -> String { super.superState += 4 return synchronous() @@ -754,3 +754,30 @@ func testCrossActorProtocol(t: T) async { t.f() // expected-error{{call is 'async' but is not marked with 'await'}} t.g() // expected-error{{call is 'async' but is not marked with 'await'}} } + +// ---------------------------------------------------------------------- +// @_ +// ---------------------------------------------------------------------- +func acceptAsyncSendableClosure(_: @Sendable () async -> T) { } +func acceptAsyncSendableClosureInheriting(@_inheritActorContext _: @Sendable () async -> T) { } + +@available(macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, *) +extension MyActor { + func testSendableAndInheriting() { + acceptAsyncSendableClosure { + synchronous() // expected-error{{actor-isolated instance method 'synchronous()' cannot be referenced from a concurrent closure}} + } + + acceptAsyncSendableClosure { + await synchronous() // ok + } + + acceptAsyncSendableClosureInheriting { + synchronous() // okay + } + + acceptAsyncSendableClosureInheriting { + await synchronous() // expected-warning{{no 'async' operations occur within 'await' expression}} + } + } +} diff --git a/test/Concurrency/global_actor_inference.swift b/test/Concurrency/global_actor_inference.swift index 3fb44f7d18e0a..d619effdd2491 100644 --- a/test/Concurrency/global_actor_inference.swift +++ b/test/Concurrency/global_actor_inference.swift @@ -506,10 +506,34 @@ func acceptClosure(_: () -> T) { } // ---------------------------------------------------------------------- func takesUnsafeMainActor(@_unsafeMainActor fn: () -> Void) { } -@MainActor func onlyOnMainActor() { } +@MainActor func onlyOnMainActor() { } // expected-note{{calls to global function 'onlyOnMainActor()' from outside of its actor context are implicitly asynchronous}} func useUnsafeMainActor() { takesUnsafeMainActor { onlyOnMainActor() // okay due to parameter attribute } } + +// ---------------------------------------------------------------------- +// @_inheritActorContext +// ---------------------------------------------------------------------- +func acceptAsyncSendableClosure(_: @Sendable () async -> T) { } +func acceptAsyncSendableClosureInheriting(@_inheritActorContext _: @Sendable () async -> T) { } + +@MainActor func testCallFromMainActor() { + acceptAsyncSendableClosure { + onlyOnMainActor() // expected-error{{call to main actor-isolated global function 'onlyOnMainActor()' in a synchronous nonisolated context}} + } + + acceptAsyncSendableClosure { + await onlyOnMainActor() // okay + } + + acceptAsyncSendableClosureInheriting { + onlyOnMainActor() // okay + } + + acceptAsyncSendableClosureInheriting { + await onlyOnMainActor() // expected-warning{{no 'async' operations occur within 'await' expression}} + } +} From 6928c3f1c9b0aac148a016c10a76b59b10777fb6 Mon Sep 17 00:00:00 2001 From: Doug Gregor Date: Wed, 21 Apr 2021 17:35:17 -0700 Subject: [PATCH 2/5] Make sure that we perform the actor hop for @_inheritActorContext --- lib/Sema/CSApply.cpp | 36 ++++++++++++++++++++++++++ test/Concurrency/actor_isolation.swift | 2 +- test/SILGen/hop_to_executor.swift | 17 ++++++++++++ 3 files changed, 54 insertions(+), 1 deletion(-) diff --git a/lib/Sema/CSApply.cpp b/lib/Sema/CSApply.cpp index 4e34ce274e467..91de16cc4f540 100644 --- a/lib/Sema/CSApply.cpp +++ b/lib/Sema/CSApply.cpp @@ -5724,6 +5724,12 @@ static void applyContextualClosureFlags( captureList->getClosureBody(), sendable, forMainActor, implicitSelfCapture, inheritActorContext); } + + if (auto identity = dyn_cast(expr)) { + applyContextualClosureFlags( + identity->getSubExpr(), sendable, forMainActor, + implicitSelfCapture, inheritActorContext); + } } /// Whether this is a reference to a method on the main dispatch queue. @@ -6116,6 +6122,20 @@ static bool isClosureLiteralExpr(Expr *expr) { return (isa(expr) || isa(expr)); } +/// Whether we should propagate async down to a closure. +static bool shouldPropagateAsyncToClosure(Expr *expr) { + if (auto IE = dyn_cast(expr)) + return shouldPropagateAsyncToClosure(IE->getSubExpr()); + + if (auto CLE = dyn_cast(expr)) + return shouldPropagateAsyncToClosure(CLE->getClosureBody()); + + if (auto CE = dyn_cast(expr)) + return CE->inheritsActorContext(); + + return false; +} + /// If the expression is an explicit closure expression (potentially wrapped in /// IdentityExprs), change the type of the closure and identities to the /// specified type and return true. Otherwise, return false with no effect. @@ -6994,6 +7014,22 @@ Expr *ExprRewriter::coerceToType(Expr *expr, Type toType, } } + // If we have a ClosureExpr, then we can safely propagate the 'async' + // bit to the closure without invalidating prior analysis. + fromEI = fromFunc->getExtInfo(); + if (toEI.isAsync() && !fromEI.isAsync() && + shouldPropagateAsyncToClosure(expr)) { + auto newFromFuncType = fromFunc->withExtInfo(fromEI.withAsync()); + if (applyTypeToClosureExpr(cs, expr, newFromFuncType)) { + fromFunc = newFromFuncType->castTo(); + + // Propagating the 'concurrent' bit might have satisfied the entire + // conversion. If so, we're done, otherwise keep converting. + if (fromFunc->isEqual(toType)) + return expr; + } + } + // If we have a ClosureExpr, then we can safely propagate a global actor // to the closure without invalidating prior analysis. fromEI = fromFunc->getExtInfo(); diff --git a/test/Concurrency/actor_isolation.swift b/test/Concurrency/actor_isolation.swift index 9474eadf39eb6..3f0ed607d4eee 100644 --- a/test/Concurrency/actor_isolation.swift +++ b/test/Concurrency/actor_isolation.swift @@ -756,7 +756,7 @@ func testCrossActorProtocol(t: T) async { } // ---------------------------------------------------------------------- -// @_ +// @_inheritActorContext // ---------------------------------------------------------------------- func acceptAsyncSendableClosure(_: @Sendable () async -> T) { } func acceptAsyncSendableClosureInheriting(@_inheritActorContext _: @Sendable () async -> T) { } diff --git a/test/SILGen/hop_to_executor.swift b/test/SILGen/hop_to_executor.swift index 09562294346cb..f6837c646bd69 100644 --- a/test/SILGen/hop_to_executor.swift +++ b/test/SILGen/hop_to_executor.swift @@ -245,3 +245,20 @@ func anotherUnspecifiedAsyncFunc(_ red : RedActorImpl) async { func testGlobalActorFuncValue(_ fn: @RedActor () -> Void) async { await fn() } + +func acceptAsyncSendableClosureInheriting(@_inheritActorContext _: @Sendable () async -> T) { } + +extension MyActor { + func synchronous() { } + + // CHECK-LABEL: sil private [ossa] @$s4test7MyActorC0A10InheritingyyFyyYaYbXEfU_ + // CHECK: debug_value [[SELF:%[0-9]+]] : $MyActor + // CHECK-NEXT: [[COPY:%[0-9]+]] = copy_value [[SELF]] : $MyActor + // CHECK-NEXT: [[BORROW:%[0-9]+]] = begin_borrow [[COPY]] : $MyActor + // CHECK-NEXT: hop_to_executor [[BORROW]] : $MyActor + func testInheriting() { + acceptAsyncSendableClosureInheriting { + synchronous() + } + } +} From 3c3f216be395b27474b88745eafee10239050f35 Mon Sep 17 00:00:00 2001 From: Doug Gregor Date: Wed, 21 Apr 2021 22:21:24 -0700 Subject: [PATCH 3/5] [Concurrency] Add "async" operation for continuing work asynchronously. The `async` operation is a global function that initiates asynchronous work on behalf of the synchronous code that calls it. Unlike `detach`, `async` inherits priority, actor context, and other aspects of the synchronous code that initiates it, making it a better "default" operation for creating asynchronous work than `detach`. The `detach` operation is still important for creating truly detached tasks that can later be `await`'d or cancelled if needed. Implements the main entry point for rdar://76927008. --- include/swift/Runtime/Concurrency.h | 4 ++ stdlib/public/Concurrency/Actor.cpp | 8 ++++ stdlib/public/Concurrency/Task.swift | 53 ++++++++++++++++++++++ test/Concurrency/Runtime/async.swift | 67 ++++++++++++++++++++++++++++ 4 files changed, 132 insertions(+) create mode 100644 test/Concurrency/Runtime/async.swift diff --git a/include/swift/Runtime/Concurrency.h b/include/swift/Runtime/Concurrency.h index a6184171ea287..19fe8650c2fa7 100644 --- a/include/swift/Runtime/Concurrency.h +++ b/include/swift/Runtime/Concurrency.h @@ -642,6 +642,10 @@ SWIFT_EXPORT_FROM(swift_Concurrency) SWIFT_CC(swift) void swift_task_reportUnexpectedExecutor( const unsigned char *file, uintptr_t fileLength, bool fileIsASCII, uintptr_t line, ExecutorRef executor); + +SWIFT_EXPORT_FROM(swift_Concurrency) SWIFT_CC(swift) +JobPriority swift_task_getCurrentThreadPriority(void); + } #pragma clang diagnostic pop diff --git a/stdlib/public/Concurrency/Actor.cpp b/stdlib/public/Concurrency/Actor.cpp index 97a2b8764ee6d..116b5092c8bc0 100644 --- a/stdlib/public/Concurrency/Actor.cpp +++ b/stdlib/public/Concurrency/Actor.cpp @@ -27,6 +27,7 @@ #include "swift/ABI/Actor.h" #include "llvm/ADT/PointerIntPair.h" #include "TaskPrivate.h" +#include #if defined(__APPLE__) #include @@ -259,6 +260,13 @@ static bool isExecutingOnMainThread() { #endif } +JobPriority swift::swift_task_getCurrentThreadPriority() { + if (isExecutingOnMainThread()) + return JobPriority::UserInitiated; + + return static_cast(qos_class_self()); +} + SWIFT_CC(swift) static bool swift_task_isCurrentExecutorImpl(ExecutorRef executor) { if (auto currentTracking = ExecutorTrackingInfo::current()) { diff --git a/stdlib/public/Concurrency/Task.swift b/stdlib/public/Concurrency/Task.swift index 39b8a58393574..5f08fba3d2d56 100644 --- a/stdlib/public/Concurrency/Task.swift +++ b/stdlib/public/Concurrency/Task.swift @@ -481,6 +481,55 @@ public func detach( return Task.Handle(task) } +/// Run given `operation` as asynchronously in its own top-level task. +/// +/// The `async` function should be used when creating asynchronous work +/// that operations on behalf of the synchronous function that calls it. +/// Like `detach`, the async function creates a separate, top-level task. +/// Unlike `detach`, the task creating by `async` inherits the priority and +/// actor context of the caller, so the `operation` is treated more like an +/// asynchronous extension to the synchronous operation. Additionally, `async` +/// does not return a handle to refer to the task. +/// +/// - Parameters: +/// - priority: priority of the task. If unspecified, the priority will +/// be inherited from the task that is currently executing +/// or, if there is none, from the platform's understanding of +/// which thread is executing. +/// - operation: the operation to execute +@available(macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, *) +public func async( + priority: Task.Priority = .unspecified, + @_inheritActorContext @_implicitSelfCapture operation: __owned @Sendable @escaping () async -> Void +) { + // Determine the priority at which we should create this task + let actualPriority: Task.Priority + if priority == .unspecified { + actualPriority = withUnsafeCurrentTask { task in + // If we are running on behalf of a task, + if let task = task { + return task.priority + } + + return Task.Priority(rawValue: _getCurrentThreadPriority()) ?? .unspecified + } + } else { + actualPriority = priority + } + + // Set up the job flags for a new task. + var flags = Task.JobFlags() + flags.kind = .task + flags.priority = actualPriority + flags.isFuture = true + + // Create the asynchronous task future. + let (task, _) = Builtin.createAsyncTaskFuture(flags.bits, operation) + + // Enqueue the resulting job. + _enqueueJobGlobal(Builtin.convertTaskToJob(task)) +} + // ==== Async Handler ---------------------------------------------------------- // TODO: remove this? @@ -746,6 +795,10 @@ func _reportUnexpectedExecutor(_ _filenameStart: Builtin.RawPointer, _ _line: Builtin.Word, _ _executor: Builtin.Executor) +@available(macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, *) +@_silgen_name("swift_task_getCurrentThreadPriority") +func _getCurrentThreadPriority() -> Int + #if _runtime(_ObjC) /// Intrinsic used by SILGen to launch a task for bridging a Swift async method diff --git a/test/Concurrency/Runtime/async.swift b/test/Concurrency/Runtime/async.swift new file mode 100644 index 0000000000000..46476057d310d --- /dev/null +++ b/test/Concurrency/Runtime/async.swift @@ -0,0 +1,67 @@ +// RUN: %target-run-simple-swift(-Xfrontend -enable-experimental-concurrency %import-libdispatch) + +// REQUIRES: executable_test +// REQUIRES: concurrency +// REQUIRES: libdispatch + +// rdar://76038845 +// UNSUPPORTED: use_os_stdlib + +import Dispatch +import StdlibUnittest + +// for sleep +#if canImport(Darwin) + import Darwin +#elseif canImport(Glibc) + import Glibc +#endif + +var asyncTests = TestSuite("Async") + +@available(macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, *) +actor MyActor { + func synchronous() { } + + func doSomething(expectedPriority: Task.Priority) { + async { + synchronous() // okay to be synchronous + assert(Task.currentPriority == expectedPriority) + } + } +} + +if #available(macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, *) { + let actor = MyActor() + + asyncTests.test("Detach") { + detach(priority: .background) { + async { + assert(Task.currentPriority == .background) + await actor.doSomething(expectedPriority: .background) + } + } + sleep(1) + } + + asyncTests.test("MainQueue") { + DispatchQueue.main.async { + async { + assert(Task.currentPriority == .userInitiated) + } + } + sleep(1) + } + + asyncTests.test("GlobalDispatchQueue") { + DispatchQueue.global(qos: .utility).async { + async { + assert(Task.currentPriority == .utility) + } + } + sleep(1) + } +} + +runAllTests() + From fbc62373e0ab971879d2cbe42acef5f22c3bb343 Mon Sep 17 00:00:00 2001 From: Doug Gregor Date: Thu, 22 Apr 2021 09:11:00 -0700 Subject: [PATCH 4/5] Work around lack of qos_class_self in non-Darwin libdispatch --- stdlib/public/Concurrency/Actor.cpp | 4 ++++ test/Concurrency/Runtime/async.swift | 3 +++ 2 files changed, 7 insertions(+) diff --git a/stdlib/public/Concurrency/Actor.cpp b/stdlib/public/Concurrency/Actor.cpp index 116b5092c8bc0..74359795bbf22 100644 --- a/stdlib/public/Concurrency/Actor.cpp +++ b/stdlib/public/Concurrency/Actor.cpp @@ -264,7 +264,11 @@ JobPriority swift::swift_task_getCurrentThreadPriority() { if (isExecutingOnMainThread()) return JobPriority::UserInitiated; +#if defined(__APPLE__) return static_cast(qos_class_self()); +#else + return JobPriority::Unspecified; +#endif } SWIFT_CC(swift) diff --git a/test/Concurrency/Runtime/async.swift b/test/Concurrency/Runtime/async.swift index 46476057d310d..d33aeb84f81d0 100644 --- a/test/Concurrency/Runtime/async.swift +++ b/test/Concurrency/Runtime/async.swift @@ -56,7 +56,10 @@ if #available(macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, *) { asyncTests.test("GlobalDispatchQueue") { DispatchQueue.global(qos: .utility).async { async { +#if (os(macOS) || os(iOS) || os(tvOS) || os(watchOS)) + // Non-Darwin platforms currently lack qos_class_self(). assert(Task.currentPriority == .utility) +#endif } } sleep(1) From 181ffaf73391524f51a050026f06fec7ea7d0d78 Mon Sep 17 00:00:00 2001 From: Doug Gregor Date: Thu, 22 Apr 2021 09:12:37 -0700 Subject: [PATCH 5/5] Fix a typo in async comment --- stdlib/public/Concurrency/Task.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/stdlib/public/Concurrency/Task.swift b/stdlib/public/Concurrency/Task.swift index 5f08fba3d2d56..13bf356b68301 100644 --- a/stdlib/public/Concurrency/Task.swift +++ b/stdlib/public/Concurrency/Task.swift @@ -484,7 +484,7 @@ public func detach( /// Run given `operation` as asynchronously in its own top-level task. /// /// The `async` function should be used when creating asynchronous work -/// that operations on behalf of the synchronous function that calls it. +/// that operates on behalf of the synchronous function that calls it. /// Like `detach`, the async function creates a separate, top-level task. /// Unlike `detach`, the task creating by `async` inherits the priority and /// actor context of the caller, so the `operation` is treated more like an