From 33c2dacb1e07c11a587f320232649ec525474048 Mon Sep 17 00:00:00 2001 From: Jonathan Grynspan Date: Fri, 31 Oct 2025 14:16:31 -0400 Subject: [PATCH 01/17] [WIP] Track how much time a task spends scheduled. --- include/swift/ABI/Task.h | 42 +++++++++++++++++-- include/swift/Runtime/Concurrency.h | 14 +++++++ .../public/Concurrency/ConcurrencyHooks.cpp | 11 +++++ stdlib/public/Concurrency/Task.cpp | 25 +++++++++++ stdlib/public/Concurrency/TaskPrivate.h | 7 ++++ 5 files changed, 96 insertions(+), 3 deletions(-) diff --git a/include/swift/ABI/Task.h b/include/swift/ABI/Task.h index e80fd3b889559..a09d0b488470d 100644 --- a/include/swift/ABI/Task.h +++ b/include/swift/ABI/Task.h @@ -320,10 +320,11 @@ class AsyncTask : public Job { #endif // Private storage is currently 6 pointers, 16 bytes of non-pointer data, - // 8 bytes of padding, the ActiveTaskStatus, and a RecursiveMutex. + // 8 bytes of padding, the ActiveTaskStatus, a RecursiveMutex, and an atomic + // uint64_t. static constexpr size_t PrivateStorageSize = 6 * sizeof(void *) + 16 + 8 + ActiveTaskStatusSize - + sizeof(RecursiveMutex); + + sizeof(RecursiveMutex) + sizeof(std::atomic); char Storage[PrivateStorageSize]; @@ -391,13 +392,37 @@ class AsyncTask : public Job { /// resume context may not be valid and just return the wrapper. const void *getResumeFunctionForLogging(bool isStarting); + /// Whether or not the concurrency library is tracking the time spent running + /// tasks. + static inline bool isTimeSpentRunningTracked(void) { + return _isTimeSpentRunningTracked.load(std::memory_order_relaxed); + } + + /// Set whether or not the concurrency library is tracking the time spent + /// running tasks. Returns the old value. + static inline bool setTimeSpentRunningTracked(bool isTracked) { + return _isTimeSpentRunningTracked.exchange(isTracked, + std::memory_order_relaxed); + } + + /// Get the number of nanoseconds spent running this task so far, or `0` if + /// task duration tracking isn't enabled. + __attribute__((cold)) uint64_t getTimeSpentRunning(void); + /// Given that we've already fully established the job context /// in the current thread, start running this task. To establish /// the job context correctly, call swift_job_run or /// runInExecutorContext. SWIFT_CC(swiftasync) void runInFullyEstablishedContext() { - return ResumeTask(ResumeContext); // 'return' forces tail call + if (SWIFT_UNLIKELY(isTimeSpentRunningTracked())) { + auto begin = getNanosecondsOnSuspendingClock(); + ResumeTask(ResumeContext); + auto end = getNanosecondsOnSuspendingClock(); + Private.ranForNanoseconds(end - start); + } else { + return ResumeTask(ResumeContext); // 'return' forces tail call + } } /// A task can have the following states: @@ -779,6 +804,17 @@ class AsyncTask : public Job { return reinterpret_cast( SchedulerPrivate[NextWaitingTaskIndex]); } + + /// Whether or not the concurrency library is tracking the time spent running + /// tasks. + static constinit std::atomic _isTimeSpentRunningTracked; + + /// Record that the task spent an additional `ns` nanoseconds running. + void ranForNanoseconds(uint64_t ns); + + /// Get the current instant on the system's suspending clock to use when + /// tracking the time spent running tasks. + static inline uint64_t getNanosecondsOnSuspendingClock(void); }; // The compiler will eventually assume these. diff --git a/include/swift/Runtime/Concurrency.h b/include/swift/Runtime/Concurrency.h index 69631393d0c02..1ef3139045391 100644 --- a/include/swift/Runtime/Concurrency.h +++ b/include/swift/Runtime/Concurrency.h @@ -1072,6 +1072,20 @@ SWIFT_EXPORT_FROM(swift_Concurrency) SWIFT_CC(swift) void swift_task_donateThreadToGlobalExecutorUntil(bool (*condition)(void*), void *context); +/// Set whether or not the concurrency library is tracking the time spent +/// running tasks. Returns the old value. +SWIFT_EXPORT_FROM(swift_Concurrency) +__attribute__((__cold__)) bool +_swift_task_setTimeSpentRunningTracked(bool isTracked); + + /// Get the duration spent running the given task (so far) in nanoseconds. +/// +/// If `AsyncTask::isTimeSpentRunningTracked()` is `false` (the common case), +/// task duration isn't tracked and this function returns `false`. +SWIFT_EXPORT_FROM(swift_Concurrency) +__attribute__((__cold__)) bool +_swift_task_getTimeSpentRunning(AsyncTask *task, uint64_t *outNanoseconds); + enum swift_clock_id : int { swift_clock_id_continuous = 1, swift_clock_id_suspending = 2, diff --git a/stdlib/public/Concurrency/ConcurrencyHooks.cpp b/stdlib/public/Concurrency/ConcurrencyHooks.cpp index 642f887e133c0..3a807e04c813d 100644 --- a/stdlib/public/Concurrency/ConcurrencyHooks.cpp +++ b/stdlib/public/Concurrency/ConcurrencyHooks.cpp @@ -225,3 +225,14 @@ swift_task_donateThreadToGlobalExecutorUntil(bool (*condition)(void *), else return swift_task_donateThreadToGlobalExecutorUntilOrig(condition, context); } + +__attribute__((__cold__)) bool +_swift_task_setTimeSpentRunningTracked(bool isTracked) { + return AsyncTask::setTimeSpentRunningTracked(isTracked); +} + +__attribute__((__cold__)) bool +_swift_task_getTimeSpentRunning(AsyncTask *task, uint64_t *outNanoseconds) { + *outNanoseconds = task->getTimeSpentRunning(); + return AsyncTask::timeSpentRunningTracked(); +} diff --git a/stdlib/public/Concurrency/Task.cpp b/stdlib/public/Concurrency/Task.cpp index 3f77983c4301b..36cd4b0d31fca 100644 --- a/stdlib/public/Concurrency/Task.cpp +++ b/stdlib/public/Concurrency/Task.cpp @@ -646,6 +646,31 @@ const void *AsyncTask::getResumeFunctionForLogging(bool isStarting) { return __ptrauth_swift_runtime_function_entry_strip(result); } +constinit std::atomic AsyncTask::_isTimeSpentRunningTracked { false }; + +__attribute__((cold)) uint64_t AsyncTask::getTimeSpentRunning(void) { + return _private().TimeSpentRunning.load(); +} + +void AsyncTask::ranForNanoseconds(uint64_t ns) { + _private().TimeSpentRunning.fetch_add(end - start); + if (hasChildFragment()) { + if (auto parent = childFragment()->getParent()) { + parent->ranForNanoseconds(ns); + } + } +} + +static inline uint64_t AsyncTask::getNanosecondsOnSuspendingClock(void) { + long long seconds = 0; + long long nanoseconds = 0; + swift_get_time(&seconds, &nanoseconds, swift_clock_id_suspending); + + uint64_t result = static_cast(seconds) * UINT64_C(1'000'000'000); + result += static_cast(nanoseconds); + return result; +} + JobPriority swift::swift_task_currentPriority(AsyncTask *task) { // This is racey but this is to be used in an API is inherently racey anyways. auto oldStatus = task->_private()._status().load(std::memory_order_relaxed); diff --git a/stdlib/public/Concurrency/TaskPrivate.h b/stdlib/public/Concurrency/TaskPrivate.h index 3587c68f70bea..691ccfb1f850a 100644 --- a/stdlib/public/Concurrency/TaskPrivate.h +++ b/stdlib/public/Concurrency/TaskPrivate.h @@ -796,6 +796,13 @@ struct AsyncTask::PrivateStorage { // The lock used to protect more complicated operations on the task status. RecursiveMutex statusLock; + /// If explicitly enabled at runtime, the total duration for which this task + /// has been scheduled and running since it was created (in nanoseconds). + /// + /// Time tracking is not free, so by default we don't do it. It can be enabled + /// by setting `AsyncTask::isTimeSpentRunningTracked` to `true`. + std::atomic TimeSpentRunning = { 0 }; + // Always create an async task with max priority in ActiveTaskStatus = base // priority. It will be updated later if needed. PrivateStorage(JobPriority basePri) From 690e7d21baf7cfc7c370ffc982ecf0044c833ad3 Mon Sep 17 00:00:00 2001 From: Jonathan Grynspan Date: Fri, 31 Oct 2025 15:11:56 -0400 Subject: [PATCH 02/17] Use a status record --- include/swift/ABI/MetadataValues.h | 3 ++ include/swift/ABI/Task.h | 46 +++++++++++++----------- include/swift/ABI/TaskStatus.h | 12 +++++++ stdlib/public/Concurrency/Task.cpp | 29 +++++++++++++-- stdlib/public/Concurrency/TaskPrivate.h | 12 +++---- stdlib/public/Concurrency/TaskStatus.cpp | 22 ++++++++++++ 6 files changed, 93 insertions(+), 31 deletions(-) diff --git a/include/swift/ABI/MetadataValues.h b/include/swift/ABI/MetadataValues.h index aaa6e9674d6df..584822104b1ba 100644 --- a/include/swift/ABI/MetadataValues.h +++ b/include/swift/ABI/MetadataValues.h @@ -2897,6 +2897,9 @@ enum class TaskStatusRecordKind : uint8_t { /// A human-readable task name. TaskName = 6, + // The total time the task has spent running. + TimeSpentRunning = 7, + // Kinds >= 192 are private to the implementation. First_Reserved = 192, Private_RecordLock = 192 diff --git a/include/swift/ABI/Task.h b/include/swift/ABI/Task.h index a09d0b488470d..30945ca2f2bb2 100644 --- a/include/swift/ABI/Task.h +++ b/include/swift/ABI/Task.h @@ -320,11 +320,10 @@ class AsyncTask : public Job { #endif // Private storage is currently 6 pointers, 16 bytes of non-pointer data, - // 8 bytes of padding, the ActiveTaskStatus, a RecursiveMutex, and an atomic - // uint64_t. + // 8 bytes of padding, the ActiveTaskStatus, and a RecursiveMutex. static constexpr size_t PrivateStorageSize = 6 * sizeof(void *) + 16 + 8 + ActiveTaskStatusSize - + sizeof(RecursiveMutex) + sizeof(std::atomic); + + sizeof(RecursiveMutex); char Storage[PrivateStorageSize]; @@ -392,23 +391,6 @@ class AsyncTask : public Job { /// resume context may not be valid and just return the wrapper. const void *getResumeFunctionForLogging(bool isStarting); - /// Whether or not the concurrency library is tracking the time spent running - /// tasks. - static inline bool isTimeSpentRunningTracked(void) { - return _isTimeSpentRunningTracked.load(std::memory_order_relaxed); - } - - /// Set whether or not the concurrency library is tracking the time spent - /// running tasks. Returns the old value. - static inline bool setTimeSpentRunningTracked(bool isTracked) { - return _isTimeSpentRunningTracked.exchange(isTracked, - std::memory_order_relaxed); - } - - /// Get the number of nanoseconds spent running this task so far, or `0` if - /// task duration tracking isn't enabled. - __attribute__((cold)) uint64_t getTimeSpentRunning(void); - /// Given that we've already fully established the job context /// in the current thread, start running this task. To establish /// the job context correctly, call swift_job_run or @@ -523,6 +505,28 @@ class AsyncTask : public Job { /// `swift_task_popTaskExecutorPreference(record)` method pair. void dropInitialTaskExecutorPreferenceRecord(); + // ==== Tracking Time Spent Running ------------------------------------------ + + /// Whether or not the concurrency library is tracking the time spent running + /// tasks. + static inline bool isTimeSpentRunningTracked(void) { + return _isTimeSpentRunningTracked.load(std::memory_order_relaxed); + } + + /// Set whether or not the concurrency library is tracking the time spent + /// running tasks. Returns the old value. + static inline bool setTimeSpentRunningTracked(bool isTracked) { + return _isTimeSpentRunningTracked.exchange(isTracked, + std::memory_order_relaxed); + } + + /// Get the number of nanoseconds spent running this task so far, or `0` if + /// task duration tracking isn't enabled. + __attribute__((cold)) uint64_t getTimeSpentRunning(void); + + void pushTimeSpentRunningRecord(void); + void popTimeSpentRunningRecord(void); + // ==== Task Local Values ---------------------------------------------------- void localValuePush(const HeapObject *key, @@ -814,7 +818,7 @@ class AsyncTask : public Job { /// Get the current instant on the system's suspending clock to use when /// tracking the time spent running tasks. - static inline uint64_t getNanosecondsOnSuspendingClock(void); + static uint64_t getNanosecondsOnSuspendingClock(void); }; // The compiler will eventually assume these. diff --git a/include/swift/ABI/TaskStatus.h b/include/swift/ABI/TaskStatus.h index 56718978e8245..3cf21503d0308 100644 --- a/include/swift/ABI/TaskStatus.h +++ b/include/swift/ABI/TaskStatus.h @@ -454,6 +454,18 @@ class TaskDependencyStatusRecord : public TaskStatusRecord { JobPriority oldPriority, JobPriority newPriority); }; +class TimeSpentRunningStatusRecord : public TaskStatusRecord { +public: + TimeSpentTaskOptionRecord() + : TaskOptionRecord(TaskStatusRecordKind::TimeSpentRunning) {} + + uint64_t TimeSpentRunning = 0; + + static bool classof(const TaskStatusRecord *record) { + return record->getKind() == TaskStatusRecordKind::TimeSpentRunning; + } +}; + } // end namespace swift #endif diff --git a/stdlib/public/Concurrency/Task.cpp b/stdlib/public/Concurrency/Task.cpp index 36cd4b0d31fca..bde9de5753e7e 100644 --- a/stdlib/public/Concurrency/Task.cpp +++ b/stdlib/public/Concurrency/Task.cpp @@ -649,11 +649,30 @@ const void *AsyncTask::getResumeFunctionForLogging(bool isStarting) { constinit std::atomic AsyncTask::_isTimeSpentRunningTracked { false }; __attribute__((cold)) uint64_t AsyncTask::getTimeSpentRunning(void) { - return _private().TimeSpentRunning.load(); + uint64_t result = 0; + + withStatusRecordLock(this, [&](ActiveTaskStatus status) { + for (auto record : status.records()) { + if (auto timeRecord = dyn_cast(record)) { + result = timeRecord->TimeSpentRunning; + break; + } + } + }); + + return result; } void AsyncTask::ranForNanoseconds(uint64_t ns) { - _private().TimeSpentRunning.fetch_add(end - start); + withStatusRecordLock(this, [&](ActiveTaskStatus status) { + for (auto record : status.records()) { + if (auto timeRecord = dyn_cast(record)) { + timeRecord->TimeSpentRunning += ns; + break; + } + } + }); + if (hasChildFragment()) { if (auto parent = childFragment()->getParent()) { parent->ranForNanoseconds(ns); @@ -661,7 +680,7 @@ void AsyncTask::ranForNanoseconds(uint64_t ns) { } } -static inline uint64_t AsyncTask::getNanosecondsOnSuspendingClock(void) { +static uint64_t AsyncTask::getNanosecondsOnSuspendingClock(void) { long long seconds = 0; long long nanoseconds = 0; swift_get_time(&seconds, &nanoseconds, swift_clock_id_suspending); @@ -1216,6 +1235,10 @@ swift_task_create_commonImpl(size_t rawTaskCreateFlags, if (jobFlags.task_hasInitialTaskName()) { task->pushInitialTaskName(taskName); } + + if (SWIFT_UNLIKELY(AsyncTask::isTimeSpentRunningTracked())) { + task->pushTimeSpentRunningRecord(); + } } // If we're supposed to enqueue the task, do so now. diff --git a/stdlib/public/Concurrency/TaskPrivate.h b/stdlib/public/Concurrency/TaskPrivate.h index 691ccfb1f850a..7dc948a46ab16 100644 --- a/stdlib/public/Concurrency/TaskPrivate.h +++ b/stdlib/public/Concurrency/TaskPrivate.h @@ -796,13 +796,6 @@ struct AsyncTask::PrivateStorage { // The lock used to protect more complicated operations on the task status. RecursiveMutex statusLock; - /// If explicitly enabled at runtime, the total duration for which this task - /// has been scheduled and running since it was created (in nanoseconds). - /// - /// Time tracking is not free, so by default we don't do it. It can be enabled - /// by setting `AsyncTask::isTimeSpentRunningTracked` to `true`. - std::atomic TimeSpentRunning = { 0 }; - // Always create an async task with max priority in ActiveTaskStatus = base // priority. It will be updated later if needed. PrivateStorage(JobPriority basePri) @@ -833,6 +826,11 @@ struct AsyncTask::PrivateStorage { } } + // If we were tracking time spent running, clear that now too. + if (SWIFT_UNLIKELY(isTimeSpentRunningTracked())) { + task->popTimeSpentRunningRecord(); + } + // Drain unlock the task and remove any overrides on thread as a // result of the task auto oldStatus = task->_private()._status().load(std::memory_order_relaxed); diff --git a/stdlib/public/Concurrency/TaskStatus.cpp b/stdlib/public/Concurrency/TaskStatus.cpp index c4c5d849161c9..1f5e568841a9f 100644 --- a/stdlib/public/Concurrency/TaskStatus.cpp +++ b/stdlib/public/Concurrency/TaskStatus.cpp @@ -1100,5 +1100,27 @@ void TaskDependencyStatusRecord::performEscalationAction( } } +/**************************************************************************/ +/*************************** TIME SPENT RUNNING ***************************/ +/**************************************************************************/ + +void AsyncTask::pushTimeSpentRunningRecord(void) { + void *allocation = _swift_task_alloc_specific( + this, sizeof(class TimeSpentRunningStatusRecord)); + auto record = ::new (allocation) TimeSpentRunningStatusRecord(); + + addStatusRecord(this, record, + [&](ActiveTaskStatus oldStatus, ActiveTaskStatus &newStatus) { + return true; // always add the record + }); +} + +void AsyncTask::popTimeSpentRunningRecord(void) { + if (auto record = popStatusRecordOfType(this)) { + record->~TimeSpentRunningStatusRecord(); + _swift_task_dealloc_specific(this, record); + } +} + #define OVERRIDE_TASK_STATUS COMPATIBILITY_OVERRIDE #include "../CompatibilityOverride/CompatibilityOverrideIncludePath.h" From f665660a9c71faf2b797d2be9b0d5da6ee45b418 Mon Sep 17 00:00:00 2001 From: Jonathan Grynspan Date: Fri, 31 Oct 2025 15:14:22 -0400 Subject: [PATCH 03/17] Typo --- include/swift/ABI/Task.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/swift/ABI/Task.h b/include/swift/ABI/Task.h index 30945ca2f2bb2..98613c00eafbf 100644 --- a/include/swift/ABI/Task.h +++ b/include/swift/ABI/Task.h @@ -401,7 +401,7 @@ class AsyncTask : public Job { auto begin = getNanosecondsOnSuspendingClock(); ResumeTask(ResumeContext); auto end = getNanosecondsOnSuspendingClock(); - Private.ranForNanoseconds(end - start); + ranForNanoseconds(end - start); } else { return ResumeTask(ResumeContext); // 'return' forces tail call } From bc619bc6450b4493c8cc468fcee1495517bbf711 Mon Sep 17 00:00:00 2001 From: Jonathan Grynspan Date: Fri, 31 Oct 2025 16:57:10 -0400 Subject: [PATCH 04/17] Remove constinit --- include/swift/ABI/Task.h | 2 +- stdlib/public/Concurrency/Task.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/include/swift/ABI/Task.h b/include/swift/ABI/Task.h index 98613c00eafbf..23b0db091f791 100644 --- a/include/swift/ABI/Task.h +++ b/include/swift/ABI/Task.h @@ -811,7 +811,7 @@ class AsyncTask : public Job { /// Whether or not the concurrency library is tracking the time spent running /// tasks. - static constinit std::atomic _isTimeSpentRunningTracked; + static std::atomic _isTimeSpentRunningTracked; /// Record that the task spent an additional `ns` nanoseconds running. void ranForNanoseconds(uint64_t ns); diff --git a/stdlib/public/Concurrency/Task.cpp b/stdlib/public/Concurrency/Task.cpp index bde9de5753e7e..6a3ee7c540711 100644 --- a/stdlib/public/Concurrency/Task.cpp +++ b/stdlib/public/Concurrency/Task.cpp @@ -646,7 +646,7 @@ const void *AsyncTask::getResumeFunctionForLogging(bool isStarting) { return __ptrauth_swift_runtime_function_entry_strip(result); } -constinit std::atomic AsyncTask::_isTimeSpentRunningTracked { false }; +std::atomic AsyncTask::_isTimeSpentRunningTracked { false }; __attribute__((cold)) uint64_t AsyncTask::getTimeSpentRunning(void) { uint64_t result = 0; From 2a90b8c3028cad1b96bfdb03d896ddfc479bee23 Mon Sep 17 00:00:00 2001 From: Jonathan Grynspan Date: Fri, 31 Oct 2025 18:03:25 -0400 Subject: [PATCH 05/17] Fix more typos --- include/swift/ABI/Task.h | 2 +- include/swift/ABI/TaskStatus.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/include/swift/ABI/Task.h b/include/swift/ABI/Task.h index 23b0db091f791..347535bd66df0 100644 --- a/include/swift/ABI/Task.h +++ b/include/swift/ABI/Task.h @@ -401,7 +401,7 @@ class AsyncTask : public Job { auto begin = getNanosecondsOnSuspendingClock(); ResumeTask(ResumeContext); auto end = getNanosecondsOnSuspendingClock(); - ranForNanoseconds(end - start); + ranForNanoseconds(end - begin); } else { return ResumeTask(ResumeContext); // 'return' forces tail call } diff --git a/include/swift/ABI/TaskStatus.h b/include/swift/ABI/TaskStatus.h index 3cf21503d0308..7552e278967ba 100644 --- a/include/swift/ABI/TaskStatus.h +++ b/include/swift/ABI/TaskStatus.h @@ -456,7 +456,7 @@ class TaskDependencyStatusRecord : public TaskStatusRecord { class TimeSpentRunningStatusRecord : public TaskStatusRecord { public: - TimeSpentTaskOptionRecord() + TimeSpentRunningStatusRecord() : TaskOptionRecord(TaskStatusRecordKind::TimeSpentRunning) {} uint64_t TimeSpentRunning = 0; From 50cb7b4f929bc8a335326f5e9e5bc53d419b79ed Mon Sep 17 00:00:00 2001 From: Jonathan Grynspan Date: Sat, 1 Nov 2025 10:13:01 -0400 Subject: [PATCH 06/17] Oh so many typos --- include/swift/ABI/TaskStatus.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/swift/ABI/TaskStatus.h b/include/swift/ABI/TaskStatus.h index 7552e278967ba..d1ef79956f8e8 100644 --- a/include/swift/ABI/TaskStatus.h +++ b/include/swift/ABI/TaskStatus.h @@ -457,7 +457,7 @@ class TaskDependencyStatusRecord : public TaskStatusRecord { class TimeSpentRunningStatusRecord : public TaskStatusRecord { public: TimeSpentRunningStatusRecord() - : TaskOptionRecord(TaskStatusRecordKind::TimeSpentRunning) {} + : TaskStatusRecord(TaskStatusRecordKind::TimeSpentRunning) {} uint64_t TimeSpentRunning = 0; From f4c6deea7f63451af27c35b6389e7f09b4d0f2f6 Mon Sep 17 00:00:00 2001 From: Jonathan Grynspan Date: Sun, 2 Nov 2025 10:09:39 -0500 Subject: [PATCH 07/17] Move code around --- .../public/Concurrency/ConcurrencyHooks.cpp | 2 +- stdlib/public/Concurrency/Task.cpp | 32 ------------------- stdlib/public/Concurrency/TaskStatus.cpp | 32 +++++++++++++++++++ 3 files changed, 33 insertions(+), 33 deletions(-) diff --git a/stdlib/public/Concurrency/ConcurrencyHooks.cpp b/stdlib/public/Concurrency/ConcurrencyHooks.cpp index 3a807e04c813d..26ff8dd0c6251 100644 --- a/stdlib/public/Concurrency/ConcurrencyHooks.cpp +++ b/stdlib/public/Concurrency/ConcurrencyHooks.cpp @@ -234,5 +234,5 @@ _swift_task_setTimeSpentRunningTracked(bool isTracked) { __attribute__((__cold__)) bool _swift_task_getTimeSpentRunning(AsyncTask *task, uint64_t *outNanoseconds) { *outNanoseconds = task->getTimeSpentRunning(); - return AsyncTask::timeSpentRunningTracked(); + return AsyncTask::isTimeSpentRunningTracked(); } diff --git a/stdlib/public/Concurrency/Task.cpp b/stdlib/public/Concurrency/Task.cpp index 6a3ee7c540711..cfad8cf6ab3e8 100644 --- a/stdlib/public/Concurrency/Task.cpp +++ b/stdlib/public/Concurrency/Task.cpp @@ -648,38 +648,6 @@ const void *AsyncTask::getResumeFunctionForLogging(bool isStarting) { std::atomic AsyncTask::_isTimeSpentRunningTracked { false }; -__attribute__((cold)) uint64_t AsyncTask::getTimeSpentRunning(void) { - uint64_t result = 0; - - withStatusRecordLock(this, [&](ActiveTaskStatus status) { - for (auto record : status.records()) { - if (auto timeRecord = dyn_cast(record)) { - result = timeRecord->TimeSpentRunning; - break; - } - } - }); - - return result; -} - -void AsyncTask::ranForNanoseconds(uint64_t ns) { - withStatusRecordLock(this, [&](ActiveTaskStatus status) { - for (auto record : status.records()) { - if (auto timeRecord = dyn_cast(record)) { - timeRecord->TimeSpentRunning += ns; - break; - } - } - }); - - if (hasChildFragment()) { - if (auto parent = childFragment()->getParent()) { - parent->ranForNanoseconds(ns); - } - } -} - static uint64_t AsyncTask::getNanosecondsOnSuspendingClock(void) { long long seconds = 0; long long nanoseconds = 0; diff --git a/stdlib/public/Concurrency/TaskStatus.cpp b/stdlib/public/Concurrency/TaskStatus.cpp index 1f5e568841a9f..4a70edd15102a 100644 --- a/stdlib/public/Concurrency/TaskStatus.cpp +++ b/stdlib/public/Concurrency/TaskStatus.cpp @@ -1122,5 +1122,37 @@ void AsyncTask::popTimeSpentRunningRecord(void) { } } +__attribute__((cold)) uint64_t AsyncTask::getTimeSpentRunning(void) { + uint64_t result = 0; + + withStatusRecordLock(this, [&](ActiveTaskStatus status) { + for (auto record : status.records()) { + if (auto timeRecord = dyn_cast(record)) { + result = timeRecord->TimeSpentRunning; + break; + } + } + }); + + return result; +} + +void AsyncTask::ranForNanoseconds(uint64_t ns) { + withStatusRecordLock(this, [&](ActiveTaskStatus status) { + for (auto record : status.records()) { + if (auto timeRecord = dyn_cast(record)) { + timeRecord->TimeSpentRunning += ns; + break; + } + } + }); + + if (hasChildFragment()) { + if (auto parent = childFragment()->getParent()) { + parent->ranForNanoseconds(ns); + } + } +} + #define OVERRIDE_TASK_STATUS COMPATIBILITY_OVERRIDE #include "../CompatibilityOverride/CompatibilityOverrideIncludePath.h" From c00e0e28f09ffcb2ff0e21c0b850d486436f2755 Mon Sep 17 00:00:00 2001 From: Jonathan Grynspan Date: Sun, 2 Nov 2025 10:11:50 -0500 Subject: [PATCH 08/17] Fix switches --- stdlib/public/Concurrency/TaskStatus.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/stdlib/public/Concurrency/TaskStatus.cpp b/stdlib/public/Concurrency/TaskStatus.cpp index 4a70edd15102a..cebeb2a39183d 100644 --- a/stdlib/public/Concurrency/TaskStatus.cpp +++ b/stdlib/public/Concurrency/TaskStatus.cpp @@ -873,6 +873,10 @@ static void performCancellationAction(TaskStatusRecord *record) { case TaskStatusRecordKind::TaskName: break; + // Cancellation has no impact on the time a task spends running. + case TaskStatusRecord::TimeSpentRunning: + break; + // This should never be found, but the compiler complains if we don't check. case TaskStatusRecordKind::First_Reserved: break; @@ -972,6 +976,9 @@ static void performEscalationAction(TaskStatusRecord *record, /// Task names don't matter to priority escalation. case TaskStatusRecordKind::TaskName: return; + // Time spent running doesn't affect priority (outside the scheduler, anyway.) + case TaskStatusRecord::TimeSpentRunning: + return; // This should never be found, but the compiler complains if we don't check. case TaskStatusRecordKind::First_Reserved: break; From 1c365bcba8ff7050e4eadad1850500afcfa78bb8 Mon Sep 17 00:00:00 2001 From: Jonathan Grynspan Date: Sun, 2 Nov 2025 10:12:23 -0500 Subject: [PATCH 09/17] Fix stray static --- stdlib/public/Concurrency/Task.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/stdlib/public/Concurrency/Task.cpp b/stdlib/public/Concurrency/Task.cpp index cfad8cf6ab3e8..592e65f535e26 100644 --- a/stdlib/public/Concurrency/Task.cpp +++ b/stdlib/public/Concurrency/Task.cpp @@ -648,7 +648,7 @@ const void *AsyncTask::getResumeFunctionForLogging(bool isStarting) { std::atomic AsyncTask::_isTimeSpentRunningTracked { false }; -static uint64_t AsyncTask::getNanosecondsOnSuspendingClock(void) { +uint64_t AsyncTask::getNanosecondsOnSuspendingClock(void) { long long seconds = 0; long long nanoseconds = 0; swift_get_time(&seconds, &nanoseconds, swift_clock_id_suspending); From 83336de55dd065757620417ab8a4d2e58221e003 Mon Sep 17 00:00:00 2001 From: Jonathan Grynspan Date: Sun, 2 Nov 2025 13:14:10 -0500 Subject: [PATCH 10/17] Sigh --- stdlib/public/Concurrency/TaskStatus.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/stdlib/public/Concurrency/TaskStatus.cpp b/stdlib/public/Concurrency/TaskStatus.cpp index cebeb2a39183d..5cb7ba063b8bf 100644 --- a/stdlib/public/Concurrency/TaskStatus.cpp +++ b/stdlib/public/Concurrency/TaskStatus.cpp @@ -874,7 +874,7 @@ static void performCancellationAction(TaskStatusRecord *record) { break; // Cancellation has no impact on the time a task spends running. - case TaskStatusRecord::TimeSpentRunning: + case TaskStatusRecordKind::TimeSpentRunning: break; // This should never be found, but the compiler complains if we don't check. @@ -977,7 +977,7 @@ static void performEscalationAction(TaskStatusRecord *record, case TaskStatusRecordKind::TaskName: return; // Time spent running doesn't affect priority (outside the scheduler, anyway.) - case TaskStatusRecord::TimeSpentRunning: + case TaskStatusRecordKind::TimeSpentRunning: return; // This should never be found, but the compiler complains if we don't check. case TaskStatusRecordKind::First_Reserved: From 633094e06a8b6e7a462b002731ee99754e1ad45c Mon Sep 17 00:00:00 2001 From: Jonathan Grynspan Date: Mon, 3 Nov 2025 09:48:14 -0500 Subject: [PATCH 11/17] Add getters at the Swift layer --- include/swift/Runtime/Concurrency.h | 2 +- stdlib/public/Concurrency/Task.swift | 41 ++++++++++++++++++++++++++++ 2 files changed, 42 insertions(+), 1 deletion(-) diff --git a/include/swift/Runtime/Concurrency.h b/include/swift/Runtime/Concurrency.h index 1ef3139045391..805a6da2f8623 100644 --- a/include/swift/Runtime/Concurrency.h +++ b/include/swift/Runtime/Concurrency.h @@ -1082,7 +1082,7 @@ _swift_task_setTimeSpentRunningTracked(bool isTracked); /// /// If `AsyncTask::isTimeSpentRunningTracked()` is `false` (the common case), /// task duration isn't tracked and this function returns `false`. -SWIFT_EXPORT_FROM(swift_Concurrency) +SWIFT_EXPORT_FROM(swift_Concurrency) SWIFT_CC(swift) __attribute__((__cold__)) bool _swift_task_getTimeSpentRunning(AsyncTask *task, uint64_t *outNanoseconds); diff --git a/stdlib/public/Concurrency/Task.swift b/stdlib/public/Concurrency/Task.swift index 3d2b9caaf572a..53e7cd4f42372 100644 --- a/stdlib/public/Concurrency/Task.swift +++ b/stdlib/public/Concurrency/Task.swift @@ -991,6 +991,47 @@ internal func _getCurrentTaskNameString() -> String? { } } +// MARK: - Time spent running (experimental) + +@available(SwiftStdlib 6.3, *) +@_silgen_name("_swift_task_getTimeSpentRunning") +internal func _getTimeSpentRunning( + _ task: Builtin.NativeObject, + _ outNanoseconds: UnsafeMutablePointer +) -> Bool + +@available(SwiftStdlib 6.3, *) +private func _getTimeSpentRunning(_ task: Builtin.NativeObject) -> Duration? { + unsafe withUnsafeCurrentTask { task in + var result = UInt64(0) + guard let task, unsafe _getTimeSpentRunning(task, &result) else { + return nil + } + return .nanoseconds(result) + } +} + +@available(SwiftStdlib 6.3, *) +extension Task { + /// Get the time this task has spent scheduled and running. + /// + /// This interface is experimental. It may be removed in a future release. + public var _timeSpentRunning: Duration? { + unsafe _getTimeSpentRunning(_task) + } +} + +@available(SwiftStdlib 6.3, *) +extension UnsafeCurrentTask { + /// Get the time this task has spent scheduled and running. + /// + /// This interface is experimental. It may be removed in a future release. + public var _timeSpentRunning: Duration? { + unsafe _getTimeSpentRunning(_task) + } +} + +// MARK: - #if SWIFT_STDLIB_TASK_TO_THREAD_MODEL_CONCURRENCY @available(SwiftStdlib 5.8, *) From 026bf574a40b5a159756e7750be8085a36da1fc2 Mon Sep 17 00:00:00 2001 From: Jonathan Grynspan Date: Mon, 3 Nov 2025 12:06:26 -0500 Subject: [PATCH 12/17] Fix typos --- stdlib/public/Concurrency/Task.swift | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/stdlib/public/Concurrency/Task.swift b/stdlib/public/Concurrency/Task.swift index 53e7cd4f42372..dd0f707cb202d 100644 --- a/stdlib/public/Concurrency/Task.swift +++ b/stdlib/public/Concurrency/Task.swift @@ -1001,14 +1001,13 @@ internal func _getTimeSpentRunning( ) -> Bool @available(SwiftStdlib 6.3, *) +@unsafe private func _getTimeSpentRunning(_ task: Builtin.NativeObject) -> Duration? { - unsafe withUnsafeCurrentTask { task in - var result = UInt64(0) - guard let task, unsafe _getTimeSpentRunning(task, &result) else { - return nil - } - return .nanoseconds(result) + var result = UInt64(0) + guard unsafe _getTimeSpentRunning(task, &result) else { + return nil } + return .nanoseconds(result) } @available(SwiftStdlib 6.3, *) From 1e9f823fac0cfe66eb39e91bc04b5baca10e99a9 Mon Sep 17 00:00:00 2001 From: Jonathan Grynspan Date: Mon, 3 Nov 2025 13:37:07 -0500 Subject: [PATCH 13/17] Add hooks to ConcurrencyHooks.def --- include/swift/Runtime/ConcurrencyHooks.def | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/include/swift/Runtime/ConcurrencyHooks.def b/include/swift/Runtime/ConcurrencyHooks.def index f087fd23e13e2..57293120c74ee 100644 --- a/include/swift/Runtime/ConcurrencyHooks.def +++ b/include/swift/Runtime/ConcurrencyHooks.def @@ -64,6 +64,12 @@ SWIFT_CONCURRENCY_HOOK(bool, swift_task_isMainExecutor, SerialExecutorRef); SWIFT_CONCURRENCY_HOOK(void, swift_task_donateThreadToGlobalExecutorUntil, bool (*condition)(void *), void *context); +SWIFT_CONCURRENCY_HOOK(bool, _swift_task_setTimeSpentRunningTracked, + bool isTracked); + +SWIFT_CONCURRENCY_HOOK(bool, _swift_task_getTimeSpentRunning + AsyncTask *task, uint64_t *outNanoseconds); + // ............................................................................. #undef SWIFT_CONCURRENCY_HOOK From f92dc6ab4b0d44b0cdbf47398274434319348d12 Mon Sep 17 00:00:00 2001 From: Jonathan Grynspan Date: Mon, 3 Nov 2025 15:24:32 -0500 Subject: [PATCH 14/17] I'm dying here --- include/swift/Runtime/ConcurrencyHooks.def | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/swift/Runtime/ConcurrencyHooks.def b/include/swift/Runtime/ConcurrencyHooks.def index 57293120c74ee..8075e3a253a81 100644 --- a/include/swift/Runtime/ConcurrencyHooks.def +++ b/include/swift/Runtime/ConcurrencyHooks.def @@ -67,7 +67,7 @@ SWIFT_CONCURRENCY_HOOK(void, swift_task_donateThreadToGlobalExecutorUntil, SWIFT_CONCURRENCY_HOOK(bool, _swift_task_setTimeSpentRunningTracked, bool isTracked); -SWIFT_CONCURRENCY_HOOK(bool, _swift_task_getTimeSpentRunning +SWIFT_CONCURRENCY_HOOK(bool, _swift_task_getTimeSpentRunning, AsyncTask *task, uint64_t *outNanoseconds); // ............................................................................. From cec0464ae5894b5cd9a8fa396c763774e057c4dd Mon Sep 17 00:00:00 2001 From: Jonathan Grynspan Date: Mon, 3 Nov 2025 16:41:46 -0500 Subject: [PATCH 15/17] Include SWIFT_CC annotation --- include/swift/Runtime/Concurrency.h | 2 +- stdlib/public/Concurrency/ConcurrencyHooks.cpp | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/include/swift/Runtime/Concurrency.h b/include/swift/Runtime/Concurrency.h index 805a6da2f8623..24587135c8fb5 100644 --- a/include/swift/Runtime/Concurrency.h +++ b/include/swift/Runtime/Concurrency.h @@ -1074,7 +1074,7 @@ void swift_task_donateThreadToGlobalExecutorUntil(bool (*condition)(void*), /// Set whether or not the concurrency library is tracking the time spent /// running tasks. Returns the old value. -SWIFT_EXPORT_FROM(swift_Concurrency) +SWIFT_EXPORT_FROM(swift_Concurrency) SWIFT_CC(swift) __attribute__((__cold__)) bool _swift_task_setTimeSpentRunningTracked(bool isTracked); diff --git a/stdlib/public/Concurrency/ConcurrencyHooks.cpp b/stdlib/public/Concurrency/ConcurrencyHooks.cpp index 26ff8dd0c6251..7e84b99560040 100644 --- a/stdlib/public/Concurrency/ConcurrencyHooks.cpp +++ b/stdlib/public/Concurrency/ConcurrencyHooks.cpp @@ -226,11 +226,13 @@ swift_task_donateThreadToGlobalExecutorUntil(bool (*condition)(void *), return swift_task_donateThreadToGlobalExecutorUntilOrig(condition, context); } +SWIFT_CC(swift) __attribute__((__cold__)) bool _swift_task_setTimeSpentRunningTracked(bool isTracked) { return AsyncTask::setTimeSpentRunningTracked(isTracked); } +SWIFT_CC(swift) __attribute__((__cold__)) bool _swift_task_getTimeSpentRunning(AsyncTask *task, uint64_t *outNanoseconds) { *outNanoseconds = task->getTimeSpentRunning(); From 6b0be103f7f2724799215348ada3fb7dcb35309c Mon Sep 17 00:00:00 2001 From: Jonathan Grynspan Date: Mon, 3 Nov 2025 20:18:01 -0500 Subject: [PATCH 16/17] facepalm here --- stdlib/public/Concurrency/ConcurrencyHooks.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/stdlib/public/Concurrency/ConcurrencyHooks.cpp b/stdlib/public/Concurrency/ConcurrencyHooks.cpp index 7e84b99560040..87cb2e8841091 100644 --- a/stdlib/public/Concurrency/ConcurrencyHooks.cpp +++ b/stdlib/public/Concurrency/ConcurrencyHooks.cpp @@ -228,13 +228,14 @@ swift_task_donateThreadToGlobalExecutorUntil(bool (*condition)(void *), SWIFT_CC(swift) __attribute__((__cold__)) bool -_swift_task_setTimeSpentRunningTracked(bool isTracked) { +swift::_swift_task_setTimeSpentRunningTracked(bool isTracked) { return AsyncTask::setTimeSpentRunningTracked(isTracked); } SWIFT_CC(swift) __attribute__((__cold__)) bool -_swift_task_getTimeSpentRunning(AsyncTask *task, uint64_t *outNanoseconds) { +swift::_swift_task_getTimeSpentRunning(AsyncTask *task, + uint64_t *outNanoseconds) { *outNanoseconds = task->getTimeSpentRunning(); return AsyncTask::isTimeSpentRunningTracked(); } From 9bb6b990ec0fd7f951d06dd92b675ec20d9409c8 Mon Sep 17 00:00:00 2001 From: Jonathan Grynspan Date: Wed, 5 Nov 2025 08:58:05 -0500 Subject: [PATCH 17/17] Task doesn't like out-of-order frees, so for the sake of the POC, just use malloc/free --- stdlib/public/Concurrency/TaskStatus.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/stdlib/public/Concurrency/TaskStatus.cpp b/stdlib/public/Concurrency/TaskStatus.cpp index 5cb7ba063b8bf..0fd659aed4238 100644 --- a/stdlib/public/Concurrency/TaskStatus.cpp +++ b/stdlib/public/Concurrency/TaskStatus.cpp @@ -1112,8 +1112,7 @@ void TaskDependencyStatusRecord::performEscalationAction( /**************************************************************************/ void AsyncTask::pushTimeSpentRunningRecord(void) { - void *allocation = _swift_task_alloc_specific( - this, sizeof(class TimeSpentRunningStatusRecord)); + void *allocation = std::malloc(sizeof(class TimeSpentRunningStatusRecord)); auto record = ::new (allocation) TimeSpentRunningStatusRecord(); addStatusRecord(this, record, @@ -1125,7 +1124,7 @@ void AsyncTask::pushTimeSpentRunningRecord(void) { void AsyncTask::popTimeSpentRunningRecord(void) { if (auto record = popStatusRecordOfType(this)) { record->~TimeSpentRunningStatusRecord(); - _swift_task_dealloc_specific(this, record); + std::free(record); } }