Skip to content

Commit 98b1cf0

Browse files
authored
Merge pull request swiftlang#38414 from rjmccall/track-task-is-running-5.5
[5.5] Track whether a task is actively running
2 parents bf57e39 + e189f7c commit 98b1cf0

File tree

18 files changed

+477
-199
lines changed

18 files changed

+477
-199
lines changed

include/swift/ABI/MetadataValues.h

Lines changed: 28 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ enum {
4848
NumWords_DefaultActor = 12,
4949

5050
/// The number of words in a task.
51-
NumWords_AsyncTask = 16,
51+
NumWords_AsyncTask = 24,
5252

5353
/// The number of words in a task group.
5454
NumWords_TaskGroup = 32,
@@ -2211,7 +2211,10 @@ class AsyncContextFlags : public FlagSet<uint32_t> {
22112211
Kind_width = 8,
22122212

22132213
CanThrow = 8,
2214-
ShouldNotDeallocate = 9
2214+
2215+
// Kind-specific flags should grow down from 31.
2216+
2217+
Continuation_IsExecutorSwitchForced = 31,
22152218
};
22162219

22172220
explicit AsyncContextFlags(uint32_t bits) : FlagSet(bits) {}
@@ -2227,17 +2230,10 @@ class AsyncContextFlags : public FlagSet<uint32_t> {
22272230
/// Whether this context is permitted to throw.
22282231
FLAGSET_DEFINE_FLAG_ACCESSORS(CanThrow, canThrow, setCanThrow)
22292232

2230-
/// Whether a function should avoid deallocating its context before
2231-
/// returning. It should still pass its caller's context to its
2232-
/// return continuation.
2233-
///
2234-
/// This flag can be set in the caller to optimize context allocation,
2235-
/// e.g. if the callee's context size is known statically and simply
2236-
/// allocated as part of the caller's context, or if the callee will
2237-
/// be called multiple times.
2238-
FLAGSET_DEFINE_FLAG_ACCESSORS(ShouldNotDeallocate,
2239-
shouldNotDeallocateInCallee,
2240-
setShouldNotDeallocateInCallee)
2233+
/// See AsyncContinuationFlags::isExecutorSwitchForced.
2234+
FLAGSET_DEFINE_FLAG_ACCESSORS(Continuation_IsExecutorSwitchForced,
2235+
continuation_isExecutorSwitchForced,
2236+
continuation_setIsExecutorSwitchForced)
22412237
};
22422238

22432239
/// Flags passed to swift_continuation_init.
@@ -2247,6 +2243,7 @@ class AsyncContinuationFlags : public FlagSet<size_t> {
22472243
CanThrow = 0,
22482244
HasExecutorOverride = 1,
22492245
IsPreawaited = 2,
2246+
IsExecutorSwitchForced = 3,
22502247
};
22512248

22522249
explicit AsyncContinuationFlags(size_t bits) : FlagSet(bits) {}
@@ -2262,10 +2259,27 @@ class AsyncContinuationFlags : public FlagSet<size_t> {
22622259
hasExecutorOverride,
22632260
setHasExecutorOverride)
22642261

2262+
/// Whether the switch to the target executor should be forced
2263+
/// by swift_continuation_await. If this is not set, and
2264+
/// swift_continuation_await finds that the continuation has
2265+
/// already been resumed, then execution will continue on the
2266+
/// current executor. This has no effect in combination with
2267+
/// pre-awaiting.
2268+
///
2269+
/// Setting this flag when you know statically that you're
2270+
/// already on the right executor is suboptimal. In particular,
2271+
/// there's no good reason to set this if you're not also using
2272+
/// an executor override.
2273+
FLAGSET_DEFINE_FLAG_ACCESSORS(IsExecutorSwitchForced,
2274+
isExecutorSwitchForced,
2275+
setIsExecutorSwitchForced)
2276+
22652277
/// Whether the continuation is "pre-awaited". If so, it should
22662278
/// be set up in the already-awaited state, and so resumptions
22672279
/// will immediately schedule the continuation to begin
2268-
/// asynchronously.
2280+
/// asynchronously. The continuation must not be subsequently
2281+
/// awaited if this is set. The task is immediately treated as
2282+
/// suspended.
22692283
FLAGSET_DEFINE_FLAG_ACCESSORS(IsPreawaited,
22702284
isPreawaited,
22712285
setIsPreawaited)

include/swift/ABI/Task.h

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -203,7 +203,7 @@ class AsyncTask : public Job {
203203

204204
/// Private storage for the use of the runtime.
205205
struct alignas(2 * alignof(void*)) OpaquePrivateStorage {
206-
void *Storage[6];
206+
void *Storage[14];
207207

208208
/// Initialize this storage during the creation of a task.
209209
void initialize(AsyncTask *task);
@@ -260,7 +260,25 @@ class AsyncTask : public Job {
260260
void runInFullyEstablishedContext() {
261261
return ResumeTask(ResumeContext); // 'return' forces tail call
262262
}
263-
263+
264+
/// Flag that this task is now running. This can update
265+
/// the priority stored in the job flags if the priority has been
266+
/// escalated.
267+
///
268+
/// Generally this should be done immediately after updating
269+
/// ActiveTask.
270+
void flagAsRunning();
271+
void flagAsRunning_slow();
272+
273+
/// Flag that this task is now suspended. This can update the
274+
/// priority stored in the job flags if the priority hsa been
275+
/// escalated. Generally this should be done immediately after
276+
/// clearing ActiveTask and immediately before enqueuing the task
277+
/// somewhere. TODO: record where the task is enqueued if
278+
/// possible.
279+
void flagAsSuspended();
280+
void flagAsSuspended_slow();
281+
264282
/// Check whether this task has been cancelled.
265283
/// Checking this is, of course, inherently race-prone on its own.
266284
bool isCancelled() const;
@@ -632,6 +650,10 @@ class ContinuationAsyncContext : public AsyncContext {
632650
ErrorResult = error;
633651
}
634652

653+
bool isExecutorSwitchForced() const {
654+
return Flags.continuation_isExecutorSwitchForced();
655+
}
656+
635657
static bool classof(const AsyncContext *context) {
636658
return context->Flags.getKind() == AsyncContextKind::Continuation;
637659
}

include/swift/Runtime/Atomic.h

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -53,10 +53,15 @@ class alignas(Size) atomic_impl {
5353
constexpr atomic_impl(Value value) : value(value) {}
5454

5555
/// Force clients to always pass an order.
56-
Value load(std::memory_order order) {
56+
Value load(std::memory_order order) const {
5757
return value.load(order);
5858
}
5959

60+
/// Force clients to always pass an order.
61+
void store(Value newValue, std::memory_order order) {
62+
return value.store(newValue, order);
63+
}
64+
6065
/// Force clients to always pass an order.
6166
bool compare_exchange_weak(Value &oldValue, Value newValue,
6267
std::memory_order successOrder,
@@ -75,14 +80,14 @@ class alignas(Size) atomic_impl {
7580
/// AMD processors that lack cmpxchg16b, so we just use the intrinsic.
7681
template <class Value>
7782
class alignas(2 * sizeof(void*)) atomic_impl<Value, 2 * sizeof(void*)> {
78-
volatile Value atomicValue;
83+
mutable volatile Value atomicValue;
7984
public:
8085
constexpr atomic_impl(Value initialValue) : atomicValue(initialValue) {}
8186

8287
atomic_impl(const atomic_impl &) = delete;
8388
atomic_impl &operator=(const atomic_impl &) = delete;
8489

85-
Value load(std::memory_order order) {
90+
Value load(std::memory_order order) const {
8691
assert(order == std::memory_order_relaxed ||
8792
order == std::memory_order_acquire ||
8893
order == std::memory_order_consume);
@@ -107,6 +112,17 @@ class alignas(2 * sizeof(void*)) atomic_impl<Value, 2 * sizeof(void*)> {
107112
return reinterpret_cast<Value &>(resultArray);
108113
}
109114

115+
void store(Value newValue, std::memory_order order) {
116+
assert(order == std::memory_order_relaxed ||
117+
order == std::memory_order_release);
118+
Value oldValue = load(std::memory_order_relaxed);
119+
while (!compare_exchange_weak(oldValue, newValue,
120+
/*success*/ order,
121+
/*failure*/ std::memory_order_relaxed)) {
122+
// try again
123+
}
124+
}
125+
110126
bool compare_exchange_weak(Value &oldValue, Value newValue,
111127
std::memory_order successOrder,
112128
std::memory_order failureOrder) {

include/swift/Runtime/Concurrency.h

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -580,20 +580,36 @@ void swift_defaultActor_deallocateResilient(HeapObject *actor);
580580
SWIFT_EXPORT_FROM(swift_Concurrency) SWIFT_CC(swift)
581581
void swift_defaultActor_enqueue(Job *job, DefaultActor *actor);
582582

583+
/// Do a primitive suspension of the current task, as if part of
584+
/// a continuation, although this does not provide any of the
585+
/// higher-level continuation semantics. The current task is returned;
586+
/// its ResumeFunction and ResumeContext will need to be initialized,
587+
/// and then it will need to be enqueued or run as a job later.
588+
SWIFT_EXPORT_FROM(swift_Concurrency) SWIFT_CC(swift)
589+
AsyncTask *swift_task_suspend();
590+
591+
/// Do a primitive suspension of the current task, as if part of
592+
/// a continuation, although this does not provide any of the
593+
/// higher-level continuation semantics. The current task is returned;
594+
/// its ResumeFunction and ResumeContext will need to be initialized,
595+
/// and then it will need to be enqueued or run as a job later.
596+
SWIFT_EXPORT_FROM(swift_Concurrency) SWIFT_CC(swift)
597+
AsyncTask *swift_task_suspend();
598+
583599
/// Prepare a continuation in the current task.
584600
///
585601
/// The caller should initialize the Parent, ResumeParent,
586602
/// and NormalResult fields. This function will initialize the other
587-
/// fields with appropriate defaaults; the caller may then overwrite
603+
/// fields with appropriate defaults; the caller may then overwrite
588604
/// them if desired.
589-
///
590-
/// This function is provided as a code-size and runtime-usage
591-
/// optimization; calling it is not required if code is willing to
592-
/// do all its work inline.
593605
SWIFT_EXPORT_FROM(swift_Concurrency) SWIFT_CC(swift)
594606
AsyncTask *swift_continuation_init(ContinuationAsyncContext *context,
595607
AsyncContinuationFlags flags);
596608

609+
/// Await an initialized continuation.
610+
SWIFT_EXPORT_FROM(swift_Concurrency) SWIFT_CC(swiftasync)
611+
void swift_continuation_await(ContinuationAsyncContext *continuationContext);
612+
597613
/// Resume a task from a non-throwing continuation, given a normal
598614
/// result which has already been stored into the continuation.
599615
SWIFT_EXPORT_FROM(swift_Concurrency) SWIFT_CC(swift)

include/swift/Runtime/RuntimeFunctions.def

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1582,6 +1582,14 @@ FUNCTION(ContinuationInit,
15821582
ARGS(ContinuationAsyncContextPtrTy, SizeTy),
15831583
ATTRS(NoUnwind))
15841584

1585+
// void swift_continuation_await(AsyncContext *continuationContext);
1586+
FUNCTION(ContinuationAwait,
1587+
swift_continuation_await, SwiftAsyncCC,
1588+
ConcurrencyAvailability,
1589+
RETURNS(VoidTy),
1590+
ARGS(ContinuationAsyncContextPtrTy),
1591+
ATTRS(NoUnwind))
1592+
15851593
// void swift_continuation_resume(AsyncTask *continuation);
15861594
FUNCTION(ContinuationResume,
15871595
swift_continuation_resume, SwiftCC,

lib/IRGen/GenConcurrency.cpp

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
#include "ExtraInhabitants.h"
2222
#include "GenProto.h"
2323
#include "GenType.h"
24+
#include "IRGenDebugInfo.h"
2425
#include "IRGenFunction.h"
2526
#include "IRGenModule.h"
2627
#include "LoadableTypeInfo.h"
@@ -241,3 +242,34 @@ void irgen::emitDestroyTaskGroup(IRGenFunction &IGF, llvm::Value *group) {
241242

242243
IGF.Builder.CreateLifetimeEnd(group);
243244
}
245+
246+
llvm::Function *IRGenModule::getAwaitAsyncContinuationFn() {
247+
StringRef name = "__swift_continuation_await_point";
248+
if (llvm::GlobalValue *F = Module.getNamedValue(name))
249+
return cast<llvm::Function>(F);
250+
251+
// The parameters here match the extra arguments passed to
252+
// @llvm.coro.suspend.async by emitAwaitAsyncContinuation.
253+
llvm::Type *argTys[] = { ContinuationAsyncContextPtrTy };
254+
auto *suspendFnTy =
255+
llvm::FunctionType::get(VoidTy, argTys, false /*vaargs*/);
256+
257+
llvm::Function *suspendFn =
258+
llvm::Function::Create(suspendFnTy, llvm::Function::InternalLinkage,
259+
name, &Module);
260+
suspendFn->setCallingConv(SwiftAsyncCC);
261+
suspendFn->setDoesNotThrow();
262+
IRGenFunction suspendIGF(*this, suspendFn);
263+
if (DebugInfo)
264+
DebugInfo->emitArtificialFunction(suspendIGF, suspendFn);
265+
auto &Builder = suspendIGF.Builder;
266+
267+
llvm::Value *context = suspendFn->getArg(0);
268+
auto *call = Builder.CreateCall(getContinuationAwaitFn(), { context });
269+
call->setDoesNotThrow();
270+
call->setCallingConv(SwiftAsyncCC);
271+
call->setTailCallKind(AsyncTailCallKind);
272+
273+
Builder.CreateRetVoid();
274+
return suspendFn;
275+
}

lib/IRGen/IRGenFunction.cpp

Lines changed: 4 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -665,50 +665,8 @@ void IRGenFunction::emitAwaitAsyncContinuation(
665665
assert(AsyncCoroutineCurrentContinuationContext && "no active continuation");
666666
auto pointerAlignment = IGM.getPointerAlignment();
667667

668-
// Check whether the continuation has already been resumed.
669-
// If so, we can just immediately continue with the control flow.
670-
// Otherwise, we need to suspend, and resuming the continuation will
671-
// trigger the function to resume.
672-
//
673-
// We do this by atomically trying to change the synchronization field
674-
// in the continuation context from 0 (the state it was initialized
675-
// with) to 1. If this fails, the continuation must already have been
676-
// resumed, so we can bypass the suspension point and immediately
677-
// start interpreting the result stored in the continuation.
678-
// Note that we use a strong compare-exchange (the default for the LLVM
679-
// cmpxchg instruction), so spurious failures are disallowed; we can
680-
// therefore trust that a failure means that the continuation has
681-
// already been resumed.
682-
683-
auto contAwaitSyncAddr =
684-
Builder.CreateStructGEP(AsyncCoroutineCurrentContinuationContext, 1);
685-
686-
auto pendingV = llvm::ConstantInt::get(
687-
contAwaitSyncAddr->getType()->getPointerElementType(),
688-
unsigned(ContinuationStatus::Pending));
689-
auto awaitedV = llvm::ConstantInt::get(
690-
contAwaitSyncAddr->getType()->getPointerElementType(),
691-
unsigned(ContinuationStatus::Awaited));
692-
auto results = Builder.CreateAtomicCmpXchg(
693-
contAwaitSyncAddr, pendingV, awaitedV,
694-
llvm::AtomicOrdering::Release /*success ordering*/,
695-
llvm::AtomicOrdering::Acquire /* failure ordering */,
696-
llvm::SyncScope::System);
697-
auto firstAtAwait = Builder.CreateExtractValue(results, 1);
698-
auto contBB = createBasicBlock("await.async.resume");
699-
auto abortBB = createBasicBlock("await.async.abort");
700-
Builder.CreateCondBr(firstAtAwait, abortBB, contBB);
701-
Builder.emitBlock(abortBB);
702-
{
703-
// We were the first to the sync point. "Abort" (return from the
704-
// coroutine partial function, without making a tail call to anything)
705-
// because the continuation result is not available yet. When the
706-
// continuation is later resumed, the task will get scheduled
707-
// starting from the suspension point.
708-
emitCoroutineOrAsyncExit();
709-
}
710-
711-
Builder.emitBlock(contBB);
668+
// Call swift_continuation_await to check whether the continuation
669+
// has already been resumed.
712670
{
713671
// Set up the suspend point.
714672
SmallVector<llvm::Value *, 8> arguments;
@@ -718,15 +676,10 @@ void IRGenFunction::emitAwaitAsyncContinuation(
718676
auto resumeProjFn = getOrCreateResumePrjFn();
719677
arguments.push_back(
720678
Builder.CreateBitOrPointerCast(resumeProjFn, IGM.Int8PtrTy));
721-
// The dispatch function just calls the resume point.
722-
auto resumeFnPtr =
723-
getFunctionPointerForResumeIntrinsic(AsyncCoroutineCurrentResume);
724679
arguments.push_back(Builder.CreateBitOrPointerCast(
725-
createAsyncDispatchFn(resumeFnPtr, {IGM.Int8PtrTy}),
680+
IGM.getAwaitAsyncContinuationFn(),
726681
IGM.Int8PtrTy));
727-
arguments.push_back(AsyncCoroutineCurrentResume);
728-
arguments.push_back(Builder.CreateBitOrPointerCast(
729-
AsyncCoroutineCurrentContinuationContext, IGM.Int8PtrTy));
682+
arguments.push_back(AsyncCoroutineCurrentContinuationContext);
730683
auto resultTy =
731684
llvm::StructType::get(IGM.getLLVMContext(), {IGM.Int8PtrTy}, false /*packed*/);
732685
emitSuspendAsyncCall(swiftAsyncContextIndex, resultTy, arguments);

lib/IRGen/IRGenModule.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1333,6 +1333,7 @@ private: \
13331333
llvm::Constant *getFixLifetimeFn();
13341334

13351335
llvm::Constant *getFixedClassInitializationFn();
1336+
llvm::Function *getAwaitAsyncContinuationFn();
13361337

13371338
/// The constructor used when generating code.
13381339
///

stdlib/public/CompatibilityOverride/CompatibilityOverrideConcurrency.def

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -78,10 +78,6 @@ OVERRIDE_ACTOR(job_run, void,
7878
swift::, (class Job *job, ExecutorRef executor),
7979
(job, executor))
8080

81-
OVERRIDE_ACTOR(task_getCurrent, AsyncTask *,
82-
SWIFT_EXPORT_FROM(swift_Concurrency), SWIFT_CC(swift),
83-
swift::, ,)
84-
8581
OVERRIDE_ACTOR(task_getCurrentExecutor, ExecutorRef,
8682
SWIFT_EXPORT_FROM(swift_Concurrency), SWIFT_CC(swift),
8783
swift::, ,)
@@ -159,6 +155,21 @@ OVERRIDE_TASK(task_asyncMainDrainQueue, void,
159155
SWIFT_EXPORT_FROM(swift_Concurrency), SWIFT_CC(swift), swift::,
160156
, )
161157

158+
OVERRIDE_TASK(task_suspend, AsyncTask *,
159+
SWIFT_EXPORT_FROM(swift_Concurrency), SWIFT_CC(swift),
160+
swift::, ,)
161+
162+
OVERRIDE_TASK(continuation_init, AsyncTask *,
163+
SWIFT_EXPORT_FROM(swift_Concurrency), SWIFT_CC(swift),
164+
swift::, (ContinuationAsyncContext *context,
165+
AsyncContinuationFlags flags),
166+
(context, flags))
167+
168+
OVERRIDE_TASK(continuation_await, void,
169+
SWIFT_EXPORT_FROM(swift_Concurrency), SWIFT_CC(swiftasync),
170+
swift::, (ContinuationAsyncContext *context),
171+
(context))
172+
162173
OVERRIDE_ASYNC_LET(asyncLet_wait, void, SWIFT_EXPORT_FROM(swift_Concurrency),
163174
SWIFT_CC(swiftasync), swift::,
164175
(OpaqueValue *result,

0 commit comments

Comments
 (0)