Skip to content

Commit 3c0f4f7

Browse files
committed
Provide a convenience for addStatusRecord for clients who have already
done the load or who need the oldStatus information after adding the status record. Change some of the memory barrier logic since we can take advantage of load-through HW address dependency. Radar-Id: rdar://problem/105634683
1 parent 89fec21 commit 3c0f4f7

File tree

7 files changed

+142
-149
lines changed

7 files changed

+142
-149
lines changed

include/swift/ABI/TaskStatus.h

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -32,16 +32,14 @@ namespace swift {
3232
/// context), partially initialized, and then atomically added to the task with
3333
/// `addStatusRecord`.
3434
///
35-
/// Status records currently are only added to a task by itself but they may be
36-
/// removed from the task by other threads, or accessed by other threads
37-
/// asynchronous to the task for other purposes.
38-
///
39-
/// As a result, while registered with the task, a status record should only be
40-
/// modified in ways that respect the possibility of asynchronous access by a
41-
/// different thread. In particular, the chain of status records must not be
42-
/// disturbed. When the task leaves the scope that requires the status record,
43-
/// the record can be unregistered from the task with `removeStatusRecord`, at
44-
/// which point the memory can be returned to the system.
35+
/// Status records can be added to or removed from a task by itself or by other
36+
/// threads. As a result, while registered with the task, a status record
37+
/// should only be modified in ways that respect the possibility of asynchronous
38+
/// access by a different thread. In particular, the chain of status records
39+
/// must not be disturbed. When the task leaves the scope that requires the
40+
/// status record, the record can be unregistered from the task with
41+
/// `removeStatusRecord`, at which point the memory can be returned to the
42+
/// system.
4543
class TaskStatusRecord {
4644
public:
4745
TaskStatusRecordFlags Flags;

include/swift/Runtime/Atomic.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
#include <intrin.h>
2626
#endif
2727

28+
2829
// FIXME: Workaround for rdar://problem/18889711. 'Consume' does not require
2930
// a barrier on ARM64, but LLVM doesn't know that. Although 'relaxed'
3031
// is formally UB by C++11 language rules, we should be OK because neither
@@ -36,6 +37,7 @@
3637
# define SWIFT_MEMORY_ORDER_CONSUME (std::memory_order_consume)
3738
#endif
3839

40+
3941
#if defined(_M_ARM) || defined(__arm__) || defined(__aarch64__)
4042
#define SWIFT_HAS_MSVC_ARM_ATOMICS 1
4143
#else

stdlib/public/Concurrency/AsyncLet.cpp

Lines changed: 26 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -48,16 +48,16 @@ class alignas(Alignment_AsyncLet) AsyncLetImpl: public ChildTaskStatusRecord {
4848
HasResult = 1 << 0,
4949
DidAllocateFromParentTask = 1 << 1,
5050
};
51-
51+
5252
/// The task that was kicked off to initialize this `async let`,
5353
/// and flags.
5454
llvm::PointerIntPair<AsyncTask *, 2, unsigned> taskAndFlags;
55-
55+
5656
/// Reserved space for a future_wait context frame, used during suspensions
5757
/// on the child task future.
5858
std::aligned_storage<sizeof(TaskFutureWaitAsyncContext),
5959
alignof(TaskFutureWaitAsyncContext)>::type futureWaitContextStorage;
60-
60+
6161
friend class ::swift::AsyncTask;
6262

6363
public:
@@ -77,43 +77,43 @@ class alignas(Alignment_AsyncLet) AsyncLetImpl: public ChildTaskStatusRecord {
7777
AsyncTask *getTask() const {
7878
return taskAndFlags.getPointer();
7979
}
80-
80+
8181
bool hasResultInBuffer() const {
8282
return taskAndFlags.getInt() & HasResult;
8383
}
84-
84+
8585
void setHasResultInBuffer(bool value = true) {
8686
if (value)
8787
taskAndFlags.setInt(taskAndFlags.getInt() | HasResult);
8888
else
8989
taskAndFlags.setInt(taskAndFlags.getInt() & ~HasResult);
9090
}
91-
91+
9292
bool didAllocateFromParentTask() const {
9393
return taskAndFlags.getInt() & DidAllocateFromParentTask;
9494
}
95-
95+
9696
void setDidAllocateFromParentTask(bool value = true) {
9797
if (value)
9898
taskAndFlags.setInt(taskAndFlags.getInt() | DidAllocateFromParentTask);
9999
else
100100
taskAndFlags.setInt(taskAndFlags.getInt() & ~DidAllocateFromParentTask);
101101
}
102-
102+
103103
// The compiler preallocates a large fixed space for the `async let`, with the
104104
// intent that most of it be used for the child task context. The next two
105105
// methods return the address and size of that space.
106-
106+
107107
/// Return a pointer to the unused space within the async let block.
108108
void *getPreallocatedSpace() {
109109
return (void*)(this + 1);
110110
}
111-
111+
112112
/// Return the size of the unused space within the async let block.
113113
static constexpr size_t getSizeOfPreallocatedSpace() {
114114
return sizeof(AsyncLet) - sizeof(AsyncLetImpl);
115115
}
116-
116+
117117
TaskFutureWaitAsyncContext *getFutureContext() {
118118
return reinterpret_cast<TaskFutureWaitAsyncContext*>(&futureWaitContextStorage);
119119
}
@@ -149,11 +149,11 @@ void swift::asyncLet_addImpl(AsyncTask *task, AsyncLet *asyncLet,
149149

150150
// ok, now that the async let task actually is initialized: attach it to the
151151
// current task
152-
bool addedRecord =
153-
addStatusRecord(record, [&](ActiveTaskStatus parentStatus, ActiveTaskStatus& newStatus) {
154-
updateNewChildWithParentAndGroupState(task, parentStatus, NULL);
155-
return true;
156-
});
152+
bool addedRecord = addStatusRecordToSelf(record,
153+
[&](ActiveTaskStatus parentStatus, ActiveTaskStatus& newStatus) {
154+
updateNewChildWithParentAndGroupState(task, parentStatus, NULL);
155+
return true;
156+
});
157157
(void)addedRecord;
158158
assert(addedRecord);
159159
}
@@ -279,13 +279,13 @@ static void _asyncLet_get_throwing_continuation(
279279
SWIFT_CONTEXT void *error) {
280280
auto continuationContext = static_cast<AsyncLetContinuationContext*>(callContext);
281281
auto alet = continuationContext->alet;
282-
282+
283283
// If the future completed successfully, its result is now in the async let
284284
// buffer.
285285
if (!error) {
286286
asImpl(alet)->setHasResultInBuffer();
287287
}
288-
288+
289289
// Continue the caller's execution.
290290
auto throwingResume
291291
= reinterpret_cast<ThrowingTaskFutureWaitContinuationFunction*>(callContext->ResumeParent);
@@ -303,14 +303,14 @@ static void swift_asyncLet_get_throwingImpl(
303303
if (asImpl(alet)->hasResultInBuffer()) {
304304
return resumeFunction(callerContext, nullptr);
305305
}
306-
306+
307307
auto aletContext = static_cast<AsyncLetContinuationContext*>(callContext);
308308
aletContext->ResumeParent
309309
= reinterpret_cast<TaskContinuationFunction*>(resumeFunction);
310310
aletContext->Parent = callerContext;
311311
aletContext->alet = alet;
312312
auto futureContext = asImpl(alet)->getFutureContext();
313-
313+
314314
// Unlike the non-throwing variant, whether we end up with a result depends
315315
// on the success of the task. If we raise an error, then the result buffer
316316
// will not be populated. Save the async let binding so we can fetch it
@@ -375,7 +375,7 @@ static void asyncLet_finish_after_task_completion(SWIFT_ASYNC_CONTEXT AsyncConte
375375
if (alet->didAllocateFromParentTask()) {
376376
swift_task_dealloc(task);
377377
}
378-
378+
379379
return reinterpret_cast<ThrowingTaskFutureWaitContinuationFunction*>(resumeFunction)
380380
(callerContext, error);
381381
}
@@ -390,7 +390,7 @@ static void _asyncLet_finish_continuation(
390390
= reinterpret_cast<AsyncLetContinuationContext*>(callContext);
391391
auto alet = continuationContext->alet;
392392
auto resultBuffer = continuationContext->resultBuffer;
393-
393+
394394
// Destroy the error, or the result that was stored to the buffer.
395395
if (error) {
396396
swift_errorRelease((SwiftError*)error);
@@ -436,7 +436,7 @@ static void swift_asyncLet_finishImpl(SWIFT_ASYNC_CONTEXT AsyncContext *callerCo
436436
aletContext->alet = alet;
437437
aletContext->resultBuffer = reinterpret_cast<OpaqueValue*>(resultBuffer);
438438
auto futureContext = asImpl(alet)->getFutureContext();
439-
439+
440440
// TODO: It would be nice if we could await the future without having to
441441
// provide a buffer to store the value to, since we're going to dispose of
442442
// it anyway.
@@ -504,7 +504,7 @@ static void _asyncLet_consume_throwing_continuation(
504504
// Get the async let pointer so we can destroy the task.
505505
auto continuationContext = static_cast<AsyncLetContinuationContext*>(callContext);
506506
auto alet = continuationContext->alet;
507-
507+
508508
return asyncLet_finish_after_task_completion(callContext->Parent,
509509
alet,
510510
callContext->ResumeParent,
@@ -528,14 +528,14 @@ static void swift_asyncLet_consume_throwingImpl(
528528
callContext,
529529
nullptr);
530530
}
531-
531+
532532
auto aletContext = static_cast<AsyncLetContinuationContext*>(callContext);
533533
aletContext->ResumeParent
534534
= reinterpret_cast<TaskContinuationFunction*>(resumeFunction);
535535
aletContext->Parent = callerContext;
536536
aletContext->alet = alet;
537537
auto futureContext = asImpl(alet)->getFutureContext();
538-
538+
539539
// Unlike the non-throwing variant, whether we end up with a result depends
540540
// on the success of the task. If we raise an error, then the result buffer
541541
// will not be populated. Save the async let binding so we can fetch it

stdlib/public/Concurrency/Task.cpp

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,6 @@ FutureFragment::Status AsyncTask::waitFuture(AsyncTask *waitingTask,
117117
// NOTE: this acquire synchronizes with `completeFuture`.
118118
auto queueHead = fragment->waitQueue.load(std::memory_order_acquire);
119119
bool contextInitialized = false;
120-
auto escalatedPriority = JobPriority::Unspecified;
121120
while (true) {
122121
switch (queueHead.getStatus()) {
123122
case Status::Error:
@@ -192,7 +191,6 @@ FutureFragment::Status AsyncTask::waitFuture(AsyncTask *waitingTask,
192191
void NullaryContinuationJob::process(Job *_job) {
193192
auto *job = cast<NullaryContinuationJob>(_job);
194193

195-
auto *task = job->Task;
196194
auto *continuation = job->Continuation;
197195

198196
delete job;
@@ -1496,7 +1494,7 @@ swift_task_addCancellationHandlerImpl(
14961494

14971495
bool fireHandlerNow = false;
14981496

1499-
addStatusRecord(record, [&](ActiveTaskStatus oldStatus, ActiveTaskStatus& newStatus) {
1497+
addStatusRecordToSelf(record, [&](ActiveTaskStatus oldStatus, ActiveTaskStatus& newStatus) {
15001498
if (oldStatus.isCancelled()) {
15011499
fireHandlerNow = true;
15021500
// We don't fire the cancellation handler here since this function needs

stdlib/public/Concurrency/TaskGroup.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -921,7 +921,7 @@ static void swift_taskGroup_initializeWithFlagsImpl(size_t rawGroupFlags,
921921
assert(record->getKind() == swift::TaskStatusRecordKind::TaskGroup);
922922

923923
// ok, now that the group actually is initialized: attach it to the task
924-
addStatusRecord(record, [&](ActiveTaskStatus oldStatus, ActiveTaskStatus& newStatus) {
924+
addStatusRecordToSelf(record, [&](ActiveTaskStatus oldStatus, ActiveTaskStatus& newStatus) {
925925
// If the task has already been cancelled, reflect that immediately in
926926
// the group's status.
927927
if (oldStatus.isCancelled()) {

stdlib/public/Concurrency/TaskPrivate.h

Lines changed: 36 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -147,8 +147,13 @@ void removeStatusRecord(AsyncTask *task, TaskStatusRecord *record,
147147
SWIFT_CC(swift)
148148
void removeStatusRecord(TaskStatusRecord *record);
149149

150-
/// Add a status record to the current task. This must be called synchronously
151-
/// with the task.
150+
/// Add a status record to the input task.
151+
///
152+
/// Clients can optionally pass in the status of the task if they have already
153+
/// done the load on it already or if they require the oldStatus on the task
154+
/// prior to the atomic ActiveTaskStatus update that addStatusRecord will
155+
/// perform. This status will be updated with the last status on the task prior
156+
/// to updating it with the new status if the input function_ref allows so.
152157
///
153158
/// This function also takes in a function_ref which is given the old
154159
/// ActiveTaskStatus on the task and a reference to the new ActiveTaskStatus
@@ -162,9 +167,27 @@ void removeStatusRecord(TaskStatusRecord *record);
162167
/// If the function_ref returns false, the status record is not added to the
163168
/// task. This function_ref may be called multiple times and must be idempotent.
164169
/// The new status passed to `fn` is freshly derived from the current status and
165-
/// does not include modifications made by previous runs through the loop
170+
/// does not include modifications made by previous runs through the loop.
171+
///
172+
/// A convenience overload is also provided for clients who have not done the
173+
/// load on the task status prior to adding the record
166174
SWIFT_CC(swift)
167-
bool addStatusRecord(TaskStatusRecord *record,
175+
bool addStatusRecord(AsyncTask *task, TaskStatusRecord *record,
176+
llvm::function_ref<bool(ActiveTaskStatus, ActiveTaskStatus&)> testAddRecord);
177+
178+
SWIFT_CC(swift)
179+
bool addStatusRecord(AsyncTask *task, TaskStatusRecord *record,
180+
ActiveTaskStatus& taskStatus,
181+
llvm::function_ref<bool(ActiveTaskStatus, ActiveTaskStatus&)> testAddRecord);
182+
183+
/// Convenience functions for clients who want to just add a record to the task
184+
/// on the current thread
185+
SWIFT_CC(swift)
186+
bool addStatusRecordToSelf(TaskStatusRecord *record,
187+
llvm::function_ref<bool(ActiveTaskStatus, ActiveTaskStatus&)> testAddRecord);
188+
189+
SWIFT_CC(swift)
190+
bool addStatusRecordToSelf(TaskStatusRecord *record, ActiveTaskStatus& taskStatus,
168191
llvm::function_ref<bool(ActiveTaskStatus, ActiveTaskStatus&)> testAddRecord);
169192

170193
/// A helper function for updating a new child task that is created with
@@ -958,17 +981,13 @@ void AsyncTask::flagAsSuspended(TaskDependencyStatusRecord *dependencyStatusReco
958981
dependencyStatusRecord);
959982
assert(dependencyStatusRecord != NULL);
960983

961-
bool taskWasEscalated = false;
962-
JobPriority taskStoredPriority = JobPriority::Unspecified;
984+
auto oldStatus = _private()._status().load(std::memory_order_relaxed);
985+
// We can only be suspended if we were previously running. See state
986+
// transitions listed out in Task.h
987+
assert(oldStatus.isRunning() && !oldStatus.isEnqueued());
963988

964-
addStatusRecord(dependencyStatusRecord, [&](ActiveTaskStatus oldStatus,
989+
addStatusRecord(this, dependencyStatusRecord, oldStatus, [&](ActiveTaskStatus unused,
965990
ActiveTaskStatus &newStatus) {
966-
// We can only be suspended if we were previously running. See state
967-
// transitions listed out in Task.h
968-
assert(oldStatus.isRunning() && !oldStatus.isEnqueued());
969-
taskWasEscalated = oldStatus.isStoredPriorityEscalated();
970-
taskStoredPriority = oldStatus.getStoredPriority();
971-
972991
newStatus = newStatus.withRunning(false);
973992
newStatus = newStatus.withoutStoredPriorityEscalation();
974993
newStatus = newStatus.withTaskDependency();
@@ -985,7 +1004,7 @@ void AsyncTask::flagAsSuspended(TaskDependencyStatusRecord *dependencyStatusReco
9851004
// published it in the ActiveTaskStatus.
9861005
SWIFT_TASK_DEBUG_LOG("[Dependency] Escalate the dependency %p of task %p",
9871006
dependencyStatusRecord, this);
988-
dependencyStatusRecord->performEscalationAction(taskStoredPriority);
1007+
dependencyStatusRecord->performEscalationAction(newStatus.getStoredPriority());
9891008

9901009
// Always add the dependency status record
9911010
return true;
@@ -994,10 +1013,10 @@ void AsyncTask::flagAsSuspended(TaskDependencyStatusRecord *dependencyStatusReco
9941013
#if SWIFT_CONCURRENCY_ENABLE_PRIORITY_ESCALATION
9951014
// Successfully dropped task drain lock, make sure to remove override on
9961015
// thread due to task
997-
if (taskWasEscalated) {
1016+
if (oldStatus.isStoredPriorityEscalated()) {
9981017
SWIFT_TASK_DEBUG_LOG("[Override] Reset override %#x on thread from task %p",
999-
taskStoredPriority, this);
1000-
swift_dispatch_lock_override_end((qos_class_t) taskStoredPriority);
1018+
oldStatus.getStoredPriority(), this);
1019+
swift_dispatch_lock_override_end((qos_class_t) oldStatus.getStoredPriority());
10011020
}
10021021
#endif
10031022

0 commit comments

Comments
 (0)