Skip to content

Commit 98a8bdb

Browse files
committed
Task Escalation support on arm64_32 Apple platforms
Radar-Id: rdar://problem/88600541
1 parent aac6d97 commit 98a8bdb

File tree

6 files changed

+89
-55
lines changed

6 files changed

+89
-55
lines changed

include/swift/Reflection/RuntimeInternals.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -108,11 +108,12 @@ struct ActiveTaskStatusWithoutEscalation {
108108

109109
template <typename Runtime, typename ActiveTaskStatus>
110110
struct AsyncTaskPrivateStorage {
111+
typename Runtime::StoredPointer ExclusivityAccessSet[2];
111112
ActiveTaskStatus Status;
112113
StackAllocator<Runtime> Allocator;
113114
typename Runtime::StoredPointer Local;
114-
typename Runtime::StoredPointer ExclusivityAccessSet[2];
115115
uint32_t Id;
116+
uint32_t BasePriority;
116117
};
117118

118119
template <typename Runtime, typename ActiveTaskStatus>

include/swift/Runtime/Concurrency.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@
4545
#ifndef SWIFT_CONCURRENCY_ENABLE_PRIORITY_ESCALATION
4646
#if SWIFT_CONCURRENCY_ENABLE_DISPATCH && \
4747
__has_include(<dispatch/swift_concurrency_private.h>) && __APPLE__ && \
48-
SWIFT_POINTER_IS_8_BYTES
48+
(defined(__arm64__) || defined(__x86_64__))
4949
#define SWIFT_CONCURRENCY_ENABLE_PRIORITY_ESCALATION 1
5050
#else
5151
#define SWIFT_CONCURRENCY_ENABLE_PRIORITY_ESCALATION 0

stdlib/public/Concurrency/Task.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -148,7 +148,7 @@ FutureFragment::Status AsyncTask::waitFuture(AsyncTask *waitingTask,
148148

149149
// Escalate the priority of this task based on the priority
150150
// of the waiting task.
151-
auto status = waitingTask->_private().Status.load(std::memory_order_relaxed);
151+
auto status = waitingTask->_private()._status().load(std::memory_order_relaxed);
152152
swift_task_escalate(this, status.getStoredPriority());
153153

154154
_swift_task_clearCurrent();
@@ -491,7 +491,7 @@ const void *AsyncTask::getResumeFunctionForLogging() {
491491
JobPriority swift::swift_task_currentPriority(AsyncTask *task)
492492
{
493493
// This is racey but this is to be used in an API is inherently racey anyways.
494-
auto oldStatus = task->_private().Status.load(std::memory_order_relaxed);
494+
auto oldStatus = task->_private()._status().load(std::memory_order_relaxed);
495495
return oldStatus.getStoredPriority();
496496
}
497497

stdlib/public/Concurrency/TaskPrivate.h

Lines changed: 65 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -229,7 +229,28 @@ class TaskFutureWaitAsyncContext : public AsyncContext {
229229
/// only need to do double wide atomics if we need to reach for the
230230
/// StatusRecord pointers and therefore have to update the flags at the same
231231
/// time.
232-
class alignas(sizeof(void*) * 2) ActiveTaskStatus {
232+
///
233+
/// Size requirements:
234+
/// On 64 bit systems or if SWIFT_CONCURRENCY_ENABLE_PRIORITY_ESCALATION=1,
235+
/// the field is 16 bytes long.
236+
///
237+
/// Otherwise, it is 8 bytes long.
238+
///
239+
/// Alignment requirements:
240+
/// On 64 bit systems or if SWIFT_CONCURRENCY_ENABLE_PRIORITY_ESCALATION=1,
241+
/// this 16-byte field needs to be 16 byte aligned to be able to do aligned
242+
/// atomic stores field.
243+
///
244+
/// On all other systems, it needs to be 8 byte aligned for the atomic
245+
/// stores.
246+
///
247+
/// As a result of varying alignment needs, we've marked the class as
248+
/// needing 2-word alignment but on arm64_32 with
249+
/// SWIFT_CONCURRENCY_ENABLE_PRIORITY_ESCALATION=1, 16 byte alignment is
250+
/// achieved through careful arrangement of the storage for this in the
251+
/// AsyncTask::PrivateStorage. The additional alignment requirements are
252+
/// enforced by static asserts below.
253+
class alignas(2 * sizeof(void*)) ActiveTaskStatus {
233254
enum : uint32_t {
234255
/// The max priority of the task. This is always >= basePriority in the task
235256
PriorityMask = 0xFF,
@@ -463,12 +484,12 @@ class alignas(sizeof(void*) * 2) ActiveTaskStatus {
463484
};
464485

465486
#if SWIFT_CONCURRENCY_ENABLE_PRIORITY_ESCALATION && SWIFT_POINTER_IS_4_BYTES
466-
static_assert(sizeof(ActiveTaskStatus) == 4 * sizeof(uintptr_t),
467-
"ActiveTaskStatus is 4 words large");
487+
#define ACTIVE_TASK_STATUS_SIZE (4 * (sizeof(uintptr_t)))
468488
#else
469-
static_assert(sizeof(ActiveTaskStatus) == 2 * sizeof(uintptr_t),
470-
"ActiveTaskStatus is 2 words large");
489+
#define ACTIVE_TASK_STATUS_SIZE (2 * (sizeof(uintptr_t)))
471490
#endif
491+
static_assert(sizeof(ActiveTaskStatus) == ACTIVE_TASK_STATUS_SIZE,
492+
"ActiveTaskStatus is of incorrect size");
472493

473494
/// The size of an allocator slab. We want the full allocation to fit into a
474495
/// 1024-byte malloc quantum. We subtract off the slab header size, plus a
@@ -481,9 +502,16 @@ using TaskAllocator = StackAllocator<SlabCapacity, &TaskAllocatorSlabMetadata>;
481502

482503
/// Private storage in an AsyncTask object.
483504
struct AsyncTask::PrivateStorage {
484-
/// The currently-active information about cancellation.
485-
/// Currently two words.
486-
swift::atomic<ActiveTaskStatus> Status;
505+
/// State inside the AsyncTask whose state is only managed by the exclusivity
506+
/// runtime in stdlibCore. We zero initialize to provide a safe initial value,
507+
/// but actually initialize its bit state to a const global provided by
508+
/// libswiftCore so that libswiftCore can control the layout of our initial
509+
/// state.
510+
uintptr_t ExclusivityAccessSet[2] = {0, 0};
511+
512+
/// Storage for the ActiveTaskStatus. See doc for ActiveTaskStatus for size
513+
/// and alignment requirements.
514+
alignas(2 * sizeof(void *)) char StatusStorage[sizeof(ActiveTaskStatus)];
487515

488516
/// The allocator for the task stack.
489517
/// Currently 2 words + 8 bytes.
@@ -493,13 +521,6 @@ struct AsyncTask::PrivateStorage {
493521
/// Currently one word.
494522
TaskLocal::Storage Local;
495523

496-
/// State inside the AsyncTask whose state is only managed by the exclusivity
497-
/// runtime in stdlibCore. We zero initialize to provide a safe initial value,
498-
/// but actually initialize its bit state to a const global provided by
499-
/// libswiftCore so that libswiftCore can control the layout of our initial
500-
/// state.
501-
uintptr_t ExclusivityAccessSet[2] = {0, 0};
502-
503524
/// The top 32 bits of the task ID. The bottom 32 bits are in Job::Id.
504525
uint32_t Id;
505526

@@ -514,19 +535,22 @@ struct AsyncTask::PrivateStorage {
514535
// Always create an async task with max priority in ActiveTaskStatus = base
515536
// priority. It will be updated later if needed.
516537
PrivateStorage(JobPriority basePri)
517-
: Status(ActiveTaskStatus(basePri)), Local(TaskLocal::Storage()),
518-
BasePriority(basePri) {}
538+
: Local(TaskLocal::Storage()), BasePriority(basePri) {
539+
_status().store(ActiveTaskStatus(basePri), std::memory_order_relaxed);
540+
}
519541

520542
PrivateStorage(JobPriority basePri, void *slab, size_t slabCapacity)
521-
: Status(ActiveTaskStatus(basePri)), Allocator(slab, slabCapacity),
522-
Local(TaskLocal::Storage()), BasePriority(basePri) {}
543+
: Allocator(slab, slabCapacity), Local(TaskLocal::Storage()),
544+
BasePriority(basePri) {
545+
_status().store(ActiveTaskStatus(basePri), std::memory_order_relaxed);
546+
}
523547

524548
/// Called on the thread that was previously executing the task that we are
525549
/// now trying to complete.
526550
void complete(AsyncTask *task) {
527551
// Drain unlock the task and remove any overrides on thread as a
528552
// result of the task
529-
auto oldStatus = task->_private().Status.load(std::memory_order_relaxed);
553+
auto oldStatus = task->_private()._status().load(std::memory_order_relaxed);
530554
while (true) {
531555
// Task is completing, it shouldn't have any records and therefore
532556
// cannot be status record locked.
@@ -542,7 +566,7 @@ struct AsyncTask::PrivateStorage {
542566

543567
// This can fail since the task can still get concurrently cancelled or
544568
// escalated.
545-
if (task->_private().Status.compare_exchange_weak(oldStatus, newStatus,
569+
if (task->_private()._status().compare_exchange_weak(oldStatus, newStatus,
546570
/* success */ std::memory_order_relaxed,
547571
/* failure */ std::memory_order_relaxed)) {
548572
#if SWIFT_CONCURRENCY_ENABLE_PRIORITY_ESCALATION
@@ -561,12 +585,21 @@ struct AsyncTask::PrivateStorage {
561585

562586
this->~PrivateStorage();
563587
}
588+
589+
swift::atomic<ActiveTaskStatus> &_status() {
590+
return reinterpret_cast<swift::atomic<ActiveTaskStatus>&> (this->StatusStorage);
591+
}
592+
593+
const swift::atomic<ActiveTaskStatus> &_status() const {
594+
return reinterpret_cast<const swift::atomic<ActiveTaskStatus>&> (this->StatusStorage);
595+
}
564596
};
565597

566-
static_assert(sizeof(AsyncTask::PrivateStorage)
567-
<= sizeof(AsyncTask::OpaquePrivateStorage) &&
568-
alignof(AsyncTask::PrivateStorage)
569-
<= alignof(AsyncTask::OpaquePrivateStorage),
598+
// It will be aligned to 2 words on all platforms. On arm64_32, we have an
599+
// additional requirement where it is aligned to 4 words.
600+
static_assert(((offsetof(AsyncTask, Private) + offsetof(AsyncTask::PrivateStorage, StatusStorage)) % ACTIVE_TASK_STATUS_SIZE == 0),
601+
"StatusStorage is not aligned in the AsyncTask");
602+
static_assert(sizeof(AsyncTask::PrivateStorage) <= sizeof(AsyncTask::OpaquePrivateStorage),
570603
"Task-private storage doesn't fit in reserved space");
571604

572605
inline AsyncTask::PrivateStorage &
@@ -599,7 +632,7 @@ inline const AsyncTask::PrivateStorage &AsyncTask::_private() const {
599632
}
600633

601634
inline bool AsyncTask::isCancelled() const {
602-
return _private().Status.load(std::memory_order_relaxed)
635+
return _private()._status().load(std::memory_order_relaxed)
603636
.isCancelled();
604637
}
605638

@@ -612,7 +645,7 @@ inline void AsyncTask::flagAsRunning() {
612645
qos_class_t overrideFloor = threadOverrideInfo.override_qos_floor;
613646
retry:;
614647
#endif
615-
auto oldStatus = _private().Status.load(std::memory_order_relaxed);
648+
auto oldStatus = _private()._status().load(std::memory_order_relaxed);
616649
while (true) {
617650
// We can get here from being suspended or being enqueued
618651
assert(!oldStatus.isRunning());
@@ -636,7 +669,7 @@ retry:;
636669
newStatus = newStatus.withoutStoredPriorityEscalation();
637670
newStatus = newStatus.withoutEnqueued();
638671

639-
if (_private().Status.compare_exchange_weak(oldStatus, newStatus,
672+
if (_private()._status().compare_exchange_weak(oldStatus, newStatus,
640673
/* success */ std::memory_order_relaxed,
641674
/* failure */ std::memory_order_relaxed)) {
642675
newStatus.traceStatusChanged(this);
@@ -667,7 +700,7 @@ retry:;
667700
inline void AsyncTask::flagAsEnqueuedOnExecutor(ExecutorRef newExecutor) {
668701

669702
SWIFT_TASK_DEBUG_LOG("%p->flagAsEnqueuedOnExecutor()", this);
670-
auto oldStatus = _private().Status.load(std::memory_order_relaxed);
703+
auto oldStatus = _private()._status().load(std::memory_order_relaxed);
671704
auto newStatus = oldStatus;
672705

673706
while (true) {
@@ -682,7 +715,7 @@ inline void AsyncTask::flagAsEnqueuedOnExecutor(ExecutorRef newExecutor) {
682715
newStatus = newStatus.withoutStoredPriorityEscalation();
683716
newStatus = newStatus.withEnqueued();
684717

685-
if (_private().Status.compare_exchange_weak(oldStatus, newStatus,
718+
if (_private()._status().compare_exchange_weak(oldStatus, newStatus,
686719
/* success */std::memory_order_relaxed,
687720
/* failure */std::memory_order_relaxed)) {
688721
break;
@@ -713,7 +746,7 @@ inline void AsyncTask::flagAsEnqueuedOnExecutor(ExecutorRef newExecutor) {
713746

714747
inline void AsyncTask::flagAsSuspended() {
715748
SWIFT_TASK_DEBUG_LOG("%p->flagAsSuspended()", this);
716-
auto oldStatus = _private().Status.load(std::memory_order_relaxed);
749+
auto oldStatus = _private()._status().load(std::memory_order_relaxed);
717750
auto newStatus = oldStatus;
718751
while (true) {
719752
// We can only be suspended if we were previously running. See state
@@ -723,7 +756,7 @@ inline void AsyncTask::flagAsSuspended() {
723756
newStatus = oldStatus.withRunning(false);
724757
newStatus = newStatus.withoutStoredPriorityEscalation();
725758

726-
if (_private().Status.compare_exchange_weak(oldStatus, newStatus,
759+
if (_private()._status().compare_exchange_weak(oldStatus, newStatus,
727760
/* success */std::memory_order_relaxed,
728761
/* failure */std::memory_order_relaxed)) {
729762
break;

stdlib/public/Concurrency/TaskStatus.cpp

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@ static void waitForStatusRecordUnlock(AsyncTask *task,
9696

9797
bool waited = waiter.tryReloadAndWait([&]() -> StatusRecordLockRecord* {
9898
// Check that oldStatus is still correct.
99-
oldStatus = task->_private().Status.load(std::memory_order_acquire);
99+
oldStatus = task->_private()._status().load(std::memory_order_acquire);
100100
if (!oldStatus.isStatusRecordLocked())
101101
return nullptr;
102102

@@ -109,7 +109,7 @@ static void waitForStatusRecordUnlock(AsyncTask *task,
109109
return;
110110

111111
// Reload the status before trying to relock.
112-
oldStatus = task->_private().Status.load(std::memory_order_acquire);
112+
oldStatus = task->_private()._status().load(std::memory_order_acquire);
113113
if (!oldStatus.isStatusRecordLocked())
114114
return;
115115
}
@@ -162,7 +162,7 @@ static bool withStatusRecordLock(AsyncTask *task,
162162

163163
// Install the lock record as the top of the queue.
164164
ActiveTaskStatus newStatus = status.withLockingRecord(lockingRecord);
165-
if (task->_private().Status.compare_exchange_weak(status, newStatus,
165+
if (task->_private()._status().compare_exchange_weak(status, newStatus,
166166
/*success*/ std::memory_order_release,
167167
/*failure*/ loadOrdering)) {
168168
status = newStatus;
@@ -187,7 +187,7 @@ static bool withStatusRecordLock(AsyncTask *task,
187187
assert(status.isStatusRecordLocked());
188188
auto newStatus = status.withoutLockingRecord();
189189

190-
if (task->_private().Status.compare_exchange_weak(status, newStatus,
190+
if (task->_private()._status().compare_exchange_weak(status, newStatus,
191191
/*success*/ std::memory_order_relaxed,
192192
/*failure*/ std::memory_order_relaxed)) {
193193
status.traceStatusChanged(task);
@@ -208,7 +208,7 @@ static bool withStatusRecordLock(AsyncTask *task,
208208
LockContext lockContext,
209209
Fn &&fn) {
210210
ActiveTaskStatus status =
211-
task->_private().Status.load(getLoadOrdering(lockContext));
211+
task->_private()._status().load(getLoadOrdering(lockContext));
212212
return withStatusRecordLock(task, lockContext, status, [&] {
213213
fn(status);
214214
});
@@ -226,7 +226,7 @@ bool swift::addStatusRecord(
226226
auto task = swift_task_getCurrent();
227227
// Load the current state. We can use a relaxed load because we're
228228
// synchronous with the task.
229-
auto oldStatus = task->_private().Status.load(std::memory_order_relaxed);
229+
auto oldStatus = task->_private()._status().load(std::memory_order_relaxed);
230230

231231
while (true) {
232232
// Wait for any active lock to be released.
@@ -243,7 +243,7 @@ bool swift::addStatusRecord(
243243
// We have to use a release on success to make the initialization of
244244
// the new record visible to an asynchronous thread trying to modify the
245245
// status records
246-
if (task->_private().Status.compare_exchange_weak(oldStatus, newStatus,
246+
if (task->_private()._status().compare_exchange_weak(oldStatus, newStatus,
247247
/*success*/ std::memory_order_release,
248248
/*failure*/ std::memory_order_relaxed)) {
249249
return true;
@@ -263,7 +263,7 @@ bool swift::removeStatusRecord(TaskStatusRecord *record) {
263263
record, task);
264264

265265
// Load the current state.
266-
auto &status = task->_private().Status;
266+
auto &status = task->_private()._status();
267267
auto oldStatus = status.load(std::memory_order_relaxed);
268268

269269
while (true) {
@@ -358,7 +358,7 @@ void swift::updateNewChildWithParentAndGroupState(AsyncTask *child,
358358
// cannot be accessed by anyone else since it hasn't been linked in yet.
359359
// Avoids the extra logic in `swift_task_cancel` and `swift_task_escalate`
360360
auto oldChildTaskStatus =
361-
child->_private().Status.load(std::memory_order_relaxed);
361+
child->_private()._status().load(std::memory_order_relaxed);
362362
assert(oldChildTaskStatus.getInnermostRecord() == NULL);
363363

364364
auto newChildTaskStatus = oldChildTaskStatus;
@@ -372,7 +372,7 @@ void swift::updateNewChildWithParentAndGroupState(AsyncTask *child,
372372
newChildTaskStatus =
373373
newChildTaskStatus.withNewPriority(withUserInteractivePriorityDowngrade(pri));
374374

375-
child->_private().Status.store(newChildTaskStatus, std::memory_order_relaxed);
375+
child->_private()._status().store(newChildTaskStatus, std::memory_order_relaxed);
376376
}
377377

378378
SWIFT_CC(swift)
@@ -451,7 +451,7 @@ SWIFT_CC(swift)
451451
static void swift_task_cancelImpl(AsyncTask *task) {
452452
SWIFT_TASK_DEBUG_LOG("cancel task = %p", task);
453453

454-
auto oldStatus = task->_private().Status.load(std::memory_order_relaxed);
454+
auto oldStatus = task->_private()._status().load(std::memory_order_relaxed);
455455
auto newStatus = oldStatus;
456456
while (true) {
457457
if (oldStatus.isCancelled()) {
@@ -461,7 +461,7 @@ static void swift_task_cancelImpl(AsyncTask *task) {
461461
// Set cancelled bit even if oldStatus.isStatusRecordLocked()
462462
newStatus = oldStatus.withCancelled();
463463

464-
if (task->_private().Status.compare_exchange_weak(oldStatus, newStatus,
464+
if (task->_private()._status().compare_exchange_weak(oldStatus, newStatus,
465465
/*success*/ std::memory_order_relaxed,
466466
/*failure*/ std::memory_order_relaxed)) {
467467
break;
@@ -550,7 +550,7 @@ JobPriority
550550
static swift_task_escalateImpl(AsyncTask *task, JobPriority newPriority) {
551551

552552
SWIFT_TASK_DEBUG_LOG("Escalating %p to %#x priority", task, newPriority);
553-
auto oldStatus = task->_private().Status.load(std::memory_order_relaxed);
553+
auto oldStatus = task->_private()._status().load(std::memory_order_relaxed);
554554
auto newStatus = oldStatus;
555555

556556
while (true) {
@@ -569,7 +569,7 @@ static swift_task_escalateImpl(AsyncTask *task, JobPriority newPriority) {
569569
newStatus = oldStatus.withNewPriority(newPriority);
570570
}
571571

572-
if (task->_private().Status.compare_exchange_weak(oldStatus, newStatus,
572+
if (task->_private()._status().compare_exchange_weak(oldStatus, newStatus,
573573
/* success */ std::memory_order_relaxed,
574574
/* failure */ std::memory_order_relaxed)) {
575575
break;
@@ -582,7 +582,7 @@ static swift_task_escalateImpl(AsyncTask *task, JobPriority newPriority) {
582582
ActiveTaskStatus *taskStatus;
583583
dispatch_lock_t *executionLock;
584584

585-
taskStatus = (ActiveTaskStatus *) &task->_private().Status;
585+
taskStatus = (ActiveTaskStatus *) &task->_private()._status();
586586
executionLock = (dispatch_lock_t *) ((char*)taskStatus + ActiveTaskStatus::executionLockOffset());
587587

588588
SWIFT_TASK_DEBUG_LOG("[Override] Escalating %p which is running on %#x to %#x", task, newStatus.currentExecutionLockOwner(), newPriority);
@@ -631,7 +631,7 @@ static NearestTaskDeadline swift_task_getNearestDeadlineImpl(AsyncTask *task) {
631631
// ignoring the possibility of a concurrent cancelling task.
632632

633633
// Load the current state.
634-
auto &status = task->_private().Status;
634+
auto &status = task->_private()._status();
635635
auto oldStatus = status.load(std::memory_order_relaxed);
636636

637637
NearestTaskDeadline result;

0 commit comments

Comments
 (0)