Skip to content

Commit a5fa349

Browse files
authored
Merge pull request #41281 from apple/rokhinip/88600541-task-priority-escalation-arm64_32
Task Escalation support on arm64_32 Apple platforms
2 parents f5bc40b + 6beb11e commit a5fa349

File tree

9 files changed

+100
-65
lines changed

9 files changed

+100
-65
lines changed

include/swift/ABI/Task.h

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -317,8 +317,9 @@ class AsyncTask : public Job {
317317
/// Flag that this task is now suspended.
318318
void flagAsSuspended();
319319

320-
/// Flag that the task is to be enqueued on the provided executor
321-
void flagAsEnqueuedOnExecutor(ExecutorRef newExecutor);
320+
/// Flag that the task is to be enqueued on the provided executor and actually
321+
/// enqueue it
322+
void flagAsAndEnqueueOnExecutor(ExecutorRef newExecutor);
322323

323324
/// Flag that this task is now completed. This normally does not do anything
324325
/// but can be used to locally insert logging.

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/Actor.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1616,7 +1616,7 @@ static void swift_task_switchImpl(SWIFT_ASYNC_CONTEXT AsyncContext *resumeContex
16161616
SWIFT_TASK_DEBUG_LOG("switch failed, task %p enqueued on executor %p", task,
16171617
newExecutor.getIdentity());
16181618

1619-
task->flagAsEnqueuedOnExecutor(newExecutor);
1619+
task->flagAsAndEnqueueOnExecutor(newExecutor);
16201620
_swift_task_clearCurrent();
16211621
}
16221622

stdlib/public/Concurrency/Task.cpp

Lines changed: 6 additions & 6 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();
@@ -234,7 +234,7 @@ void AsyncTask::completeFuture(AsyncContext *context) {
234234

235235
// Enqueue the waiter on the global executor.
236236
// TODO: allow waiters to fill in a suggested executor
237-
waitingTask->flagAsEnqueuedOnExecutor(ExecutorRef::generic());
237+
waitingTask->flagAsAndEnqueueOnExecutor(ExecutorRef::generic());
238238

239239
// Move to the next task.
240240
waitingTask = nextWaitingTask;
@@ -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

@@ -847,7 +847,7 @@ static AsyncTaskAndContext swift_task_create_commonImpl(
847847
// If we're supposed to enqueue the task, do so now.
848848
if (taskCreateFlags.enqueueJob()) {
849849
swift_retain(task);
850-
task->flagAsEnqueuedOnExecutor(executor);
850+
task->flagAsAndEnqueueOnExecutor(executor);
851851
}
852852

853853
return {task, initialContext};
@@ -1020,7 +1020,7 @@ SWIFT_CC(swift)
10201020
static void
10211021
swift_task_enqueueTaskOnExecutorImpl(AsyncTask *task, ExecutorRef executor)
10221022
{
1023-
task->flagAsEnqueuedOnExecutor(executor);
1023+
task->flagAsAndEnqueueOnExecutor(executor);
10241024
}
10251025

10261026
SWIFT_CC(swift)
@@ -1151,7 +1151,7 @@ static void resumeTaskAfterContinuation(AsyncTask *task,
11511151
// to make a stronger best-effort attempt to catch racing attempts to
11521152
// resume the continuation?
11531153

1154-
task->flagAsEnqueuedOnExecutor(context->ResumeToExecutor);
1154+
task->flagAsAndEnqueueOnExecutor(context->ResumeToExecutor);
11551155
}
11561156

11571157
SWIFT_CC(swift)

stdlib/public/Concurrency/TaskGroup.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -622,7 +622,7 @@ void TaskGroupImpl::offer(AsyncTask *completedTask, AsyncContext *context) {
622622
_swift_tsan_acquire(static_cast<Job *>(waitingTask));
623623

624624
// TODO: allow the caller to suggest an executor
625-
waitingTask->flagAsEnqueuedOnExecutor(ExecutorRef::generic());
625+
waitingTask->flagAsAndEnqueueOnExecutor(ExecutorRef::generic());
626626
return;
627627
} // else, try again
628628
}

stdlib/public/Concurrency/TaskPrivate.h

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

467488
#if SWIFT_CONCURRENCY_ENABLE_PRIORITY_ESCALATION && SWIFT_POINTER_IS_4_BYTES
468-
static_assert(sizeof(ActiveTaskStatus) == 4 * sizeof(uintptr_t),
469-
"ActiveTaskStatus is 4 words large");
489+
#define ACTIVE_TASK_STATUS_SIZE (4 * (sizeof(uintptr_t)))
470490
#else
471-
static_assert(sizeof(ActiveTaskStatus) == 2 * sizeof(uintptr_t),
472-
"ActiveTaskStatus is 2 words large");
491+
#define ACTIVE_TASK_STATUS_SIZE (2 * (sizeof(uintptr_t)))
473492
#endif
493+
static_assert(sizeof(ActiveTaskStatus) == ACTIVE_TASK_STATUS_SIZE,
494+
"ActiveTaskStatus is of incorrect size");
474495

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

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

490518
/// The allocator for the task stack.
491519
/// Currently 2 words + 8 bytes.
@@ -495,13 +523,6 @@ struct AsyncTask::PrivateStorage {
495523
/// Currently one word.
496524
TaskLocal::Storage Local;
497525

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

@@ -516,19 +537,22 @@ struct AsyncTask::PrivateStorage {
516537
// Always create an async task with max priority in ActiveTaskStatus = base
517538
// priority. It will be updated later if needed.
518539
PrivateStorage(JobPriority basePri)
519-
: Status(ActiveTaskStatus(basePri)), Local(TaskLocal::Storage()),
520-
BasePriority(basePri) {}
540+
: Local(TaskLocal::Storage()), BasePriority(basePri) {
541+
_status().store(ActiveTaskStatus(basePri), std::memory_order_relaxed);
542+
}
521543

522544
PrivateStorage(JobPriority basePri, void *slab, size_t slabCapacity)
523-
: Status(ActiveTaskStatus(basePri)), Allocator(slab, slabCapacity),
524-
Local(TaskLocal::Storage()), BasePriority(basePri) {}
545+
: Allocator(slab, slabCapacity), Local(TaskLocal::Storage()),
546+
BasePriority(basePri) {
547+
_status().store(ActiveTaskStatus(basePri), std::memory_order_relaxed);
548+
}
525549

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

545569
// This can fail since the task can still get concurrently cancelled or
546570
// escalated.
547-
if (task->_private().Status.compare_exchange_weak(oldStatus, newStatus,
571+
if (task->_private()._status().compare_exchange_weak(oldStatus, newStatus,
548572
/* success */ std::memory_order_relaxed,
549573
/* failure */ std::memory_order_relaxed)) {
550574
#if SWIFT_CONCURRENCY_ENABLE_PRIORITY_ESCALATION
@@ -563,12 +587,21 @@ struct AsyncTask::PrivateStorage {
563587

564588
this->~PrivateStorage();
565589
}
590+
591+
swift::atomic<ActiveTaskStatus> &_status() {
592+
return reinterpret_cast<swift::atomic<ActiveTaskStatus>&> (this->StatusStorage);
593+
}
594+
595+
const swift::atomic<ActiveTaskStatus> &_status() const {
596+
return reinterpret_cast<const swift::atomic<ActiveTaskStatus>&> (this->StatusStorage);
597+
}
566598
};
567599

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

574607
inline AsyncTask::PrivateStorage &
@@ -601,7 +634,7 @@ inline const AsyncTask::PrivateStorage &AsyncTask::_private() const {
601634
}
602635

603636
inline bool AsyncTask::isCancelled() const {
604-
return _private().Status.load(std::memory_order_relaxed)
637+
return _private()._status().load(std::memory_order_relaxed)
605638
.isCancelled();
606639
}
607640

@@ -614,7 +647,7 @@ inline void AsyncTask::flagAsRunning() {
614647
qos_class_t overrideFloor = threadOverrideInfo.override_qos_floor;
615648
retry:;
616649
#endif
617-
auto oldStatus = _private().Status.load(std::memory_order_relaxed);
650+
auto oldStatus = _private()._status().load(std::memory_order_relaxed);
618651
while (true) {
619652
// We can get here from being suspended or being enqueued
620653
assert(!oldStatus.isRunning());
@@ -638,7 +671,7 @@ retry:;
638671
newStatus = newStatus.withoutStoredPriorityEscalation();
639672
newStatus = newStatus.withoutEnqueued();
640673

641-
if (_private().Status.compare_exchange_weak(oldStatus, newStatus,
674+
if (_private()._status().compare_exchange_weak(oldStatus, newStatus,
642675
/* success */ std::memory_order_relaxed,
643676
/* failure */ std::memory_order_relaxed)) {
644677
newStatus.traceStatusChanged(this);
@@ -666,10 +699,10 @@ retry:;
666699
/// original enqueueing thread.
667700
///
668701
/// rdar://88366470 (Direct handoff behaviour when tasks switch executors)
669-
inline void AsyncTask::flagAsEnqueuedOnExecutor(ExecutorRef newExecutor) {
702+
inline void AsyncTask::flagAsAndEnqueueOnExecutor(ExecutorRef newExecutor) {
670703

671-
SWIFT_TASK_DEBUG_LOG("%p->flagAsEnqueuedOnExecutor()", this);
672-
auto oldStatus = _private().Status.load(std::memory_order_relaxed);
704+
SWIFT_TASK_DEBUG_LOG("%p->flagAsAndEnqueueOnExecutor()", this);
705+
auto oldStatus = _private()._status().load(std::memory_order_relaxed);
673706
auto newStatus = oldStatus;
674707

675708
while (true) {
@@ -684,7 +717,7 @@ inline void AsyncTask::flagAsEnqueuedOnExecutor(ExecutorRef newExecutor) {
684717
newStatus = newStatus.withoutStoredPriorityEscalation();
685718
newStatus = newStatus.withEnqueued();
686719

687-
if (_private().Status.compare_exchange_weak(oldStatus, newStatus,
720+
if (_private()._status().compare_exchange_weak(oldStatus, newStatus,
688721
/* success */std::memory_order_relaxed,
689722
/* failure */std::memory_order_relaxed)) {
690723
break;
@@ -715,7 +748,7 @@ inline void AsyncTask::flagAsEnqueuedOnExecutor(ExecutorRef newExecutor) {
715748

716749
inline void AsyncTask::flagAsSuspended() {
717750
SWIFT_TASK_DEBUG_LOG("%p->flagAsSuspended()", this);
718-
auto oldStatus = _private().Status.load(std::memory_order_relaxed);
751+
auto oldStatus = _private()._status().load(std::memory_order_relaxed);
719752
auto newStatus = oldStatus;
720753
while (true) {
721754
// We can only be suspended if we were previously running. See state
@@ -725,7 +758,7 @@ inline void AsyncTask::flagAsSuspended() {
725758
newStatus = oldStatus.withRunning(false);
726759
newStatus = newStatus.withoutStoredPriorityEscalation();
727760

728-
if (_private().Status.compare_exchange_weak(oldStatus, newStatus,
761+
if (_private()._status().compare_exchange_weak(oldStatus, newStatus,
729762
/* success */std::memory_order_relaxed,
730763
/* failure */std::memory_order_relaxed)) {
731764
break;

0 commit comments

Comments
 (0)