Skip to content

Commit 76e7cb1

Browse files
authored
Merge pull request #60860 from apple/rokhinip/92347604-run-task-upon-await
[Freestanding] Only run tasks when they are awaited up on in task-to-thread model
2 parents 73ace80 + a606892 commit 76e7cb1

File tree

10 files changed

+358
-85
lines changed

10 files changed

+358
-85
lines changed

include/swift/ABI/MetadataValues.h

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2247,7 +2247,9 @@ class TaskCreateFlags : public FlagSet<size_t> {
22472247
RequestedPriority_width = 8,
22482248

22492249
Task_IsChildTask = 8,
2250-
// bit 9 is unused
2250+
// Should only be set in task-to-thread model where Task.runInline is
2251+
// available
2252+
Task_IsInlineTask = 9,
22512253
Task_CopyTaskLocals = 10,
22522254
Task_InheritContext = 11,
22532255
Task_EnqueueJob = 12,
@@ -2263,6 +2265,9 @@ class TaskCreateFlags : public FlagSet<size_t> {
22632265
FLAGSET_DEFINE_FLAG_ACCESSORS(Task_IsChildTask,
22642266
isChildTask,
22652267
setIsChildTask)
2268+
FLAGSET_DEFINE_FLAG_ACCESSORS(Task_IsInlineTask,
2269+
isInlineTask,
2270+
setIsInlineTask)
22662271
FLAGSET_DEFINE_FLAG_ACCESSORS(Task_CopyTaskLocals,
22672272
copyTaskLocals,
22682273
setCopyTaskLocals)

include/swift/Runtime/Concurrency.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@
4141

4242
// Does the runtime integrate with libdispatch?
4343
#ifndef SWIFT_CONCURRENCY_ENABLE_DISPATCH
44-
#if SWIFT_CONCURRENCY_COOPERATIVE_GLOBAL_EXECUTOR
44+
#if SWIFT_CONCURRENCY_COOPERATIVE_GLOBAL_EXECUTOR || SWIFT_CONCURRENCY_TASK_TO_THREAD_MODEL
4545
#define SWIFT_CONCURRENCY_ENABLE_DISPATCH 0
4646
#else
4747
#define SWIFT_CONCURRENCY_ENABLE_DISPATCH 1

stdlib/public/Concurrency/Actor.cpp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -249,6 +249,12 @@ AsyncTask *swift::_swift_task_clearCurrent() {
249249
return task;
250250
}
251251

252+
AsyncTask *swift::_swift_task_setCurrent(AsyncTask *new_task) {
253+
auto task = ActiveTask::get();
254+
ActiveTask::set(new_task);
255+
return task;
256+
}
257+
252258
SWIFT_CC(swift)
253259
static ExecutorRef swift_task_getCurrentExecutorImpl() {
254260
auto currentTracking = ExecutorTrackingInfo::current();

stdlib/public/Concurrency/AsyncLet.cpp

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -167,7 +167,13 @@ void swift::swift_asyncLet_start(AsyncLet *alet,
167167
void *closureEntryPoint,
168168
HeapObject *closureContext) {
169169
auto flags = TaskCreateFlags();
170+
#if SWIFT_CONCURRENCY_TASK_TO_THREAD_MODEL
171+
// In the task to thread model, we don't want tasks to start running on
172+
// separate threads - they will run in the context of the parent
173+
flags.setEnqueueJob(false);
174+
#else
170175
flags.setEnqueueJob(true);
176+
#endif
171177

172178
AsyncLetTaskOptionRecord asyncLetOptionRecord(alet);
173179
asyncLetOptionRecord.Parent = options;
@@ -191,7 +197,14 @@ void swift::swift_asyncLet_begin(AsyncLet *alet,
191197
resultBuffer);
192198

193199
auto flags = TaskCreateFlags();
200+
#if SWIFT_CONCURRENCY_TASK_TO_THREAD_MODEL
201+
// In the task to thread model, we don't want tasks to start running on
202+
// separate threads - they will run in the context of the parent
203+
flags.setEnqueueJob(false);
204+
#else
194205
flags.setEnqueueJob(true);
206+
#endif
207+
195208

196209
AsyncLetWithBufferTaskOptionRecord asyncLetOptionRecord(alet, resultBuffer);
197210
asyncLetOptionRecord.Parent = options;

stdlib/public/Concurrency/Executor.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,7 @@ func _checkExpectedExecutor(_filenameStart: Builtin.RawPointer,
9393
_filenameStart, _filenameLength, _filenameIsASCII, _line, _executor)
9494
}
9595

96-
#if !SWIFT_STDLIB_SINGLE_THREADED_CONCURRENCY
96+
#if !SWIFT_STDLIB_SINGLE_THREADED_CONCURRENCY && !SWIFT_STDLIB_TASK_TO_THREAD_MODEL_CONCURRENCY
9797
// This must take a DispatchQueueShim, not something like AnyObject,
9898
// or else SILGen will emit a retain/release in unoptimized builds,
9999
// which won't work because DispatchQueues aren't actually

stdlib/public/Concurrency/Task.cpp

Lines changed: 75 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -187,6 +187,29 @@ FutureFragment::Status AsyncTask::waitFuture(AsyncTask *waitingTask,
187187
escalatedPriority = waitingStatus.getStoredPriority();
188188
}
189189

190+
#if SWIFT_CONCURRENCY_TASK_TO_THREAD_MODEL
191+
// In the task to thread model, we will execute the task that we are waiting
192+
// on, on the current thread itself. As a result, do not bother adding the
193+
// waitingTask to any thread queue. Instead, we will clear the old task, run
194+
// the new one and then reattempt to continue running the old task
195+
196+
auto oldTask = _swift_task_clearCurrent();
197+
assert(oldTask == waitingTask);
198+
199+
SWIFT_TASK_DEBUG_LOG("[RunInline] Switching away from running %p to now running %p", oldTask, this);
200+
// Run the new task on the same thread now - this should run the new task to
201+
// completion. All swift tasks in task-to-thread model run on generic
202+
// executor
203+
swift_job_run(this, ExecutorRef::generic());
204+
205+
SWIFT_TASK_DEBUG_LOG("[RunInline] Switching back from running %p to now running %p", this, oldTask);
206+
207+
// We now are back in the context of the waiting task and need to reevaluate
208+
// our state
209+
_swift_task_setCurrent(oldTask);
210+
queueHead = fragment->waitQueue.load(std::memory_order_acquire);
211+
continue;
212+
#else
190213
// Put the waiting task at the beginning of the wait queue.
191214
waitingTask->getNextWaitingTask() = queueHead.getTask();
192215
auto newQueueHead = WaitQueueItem::get(Status::Executing, waitingTask);
@@ -198,6 +221,7 @@ FutureFragment::Status AsyncTask::waitFuture(AsyncTask *waitingTask,
198221
_swift_task_clearCurrent();
199222
return FutureFragment::Status::Executing;
200223
}
224+
#endif /* SWIFT_CONCURRENCY_TASK_TO_THREAD_MODEL */
201225
}
202226
}
203227

@@ -255,8 +279,13 @@ void AsyncTask::completeFuture(AsyncContext *context) {
255279
// Schedule every waiting task on the executor.
256280
auto waitingTask = queueHead.getTask();
257281

258-
if (!waitingTask)
282+
if (!waitingTask) {
259283
SWIFT_TASK_DEBUG_LOG("task %p had no waiting tasks", this);
284+
} else {
285+
#if SWIFT_CONCURRENCY_TASK_TO_THREAD_MODEL
286+
assert(false && "Task should have no waiters in task-to-thread model");
287+
#endif
288+
}
260289

261290
while (waitingTask) {
262291
// Find the next waiting task before we invalidate it by resuming
@@ -574,12 +603,16 @@ static inline bool isUnspecified(JobPriority priority) {
574603
return priority == JobPriority::Unspecified;
575604
}
576605

577-
static inline bool taskIsUnstructured(JobFlags jobFlags) {
578-
return !jobFlags.task_isAsyncLetTask() && !jobFlags.task_isGroupChildTask();
606+
static inline bool taskIsStructured(JobFlags jobFlags) {
607+
return jobFlags.task_isAsyncLetTask() || jobFlags.task_isGroupChildTask();
608+
}
609+
610+
static inline bool taskIsUnstructured(TaskCreateFlags createFlags, JobFlags jobFlags) {
611+
return !taskIsStructured(jobFlags) && !createFlags.isInlineTask();
579612
}
580613

581614
static inline bool taskIsDetached(TaskCreateFlags createFlags, JobFlags jobFlags) {
582-
return taskIsUnstructured(jobFlags) && !createFlags.copyTaskLocals();
615+
return taskIsUnstructured(createFlags, jobFlags) && !createFlags.copyTaskLocals();
583616
}
584617

585618
static std::pair<size_t, size_t> amountToAllocateForHeaderAndTask(
@@ -671,6 +704,9 @@ static AsyncTaskAndContext swift_task_create_commonImpl(
671704
}
672705
case TaskOptionRecordKind::RunInline: {
673706
runInlineOption = cast<RunInlineTaskOptionRecord>(option);
707+
// TODO (rokhinip): We seem to be creating runInline tasks like detached
708+
// tasks but they need to maintain the voucher and priority of calling
709+
// thread and therefore need to behave a bit more like SC child tasks.
674710
break;
675711
}
676712
}
@@ -692,20 +728,27 @@ static AsyncTaskAndContext swift_task_create_commonImpl(
692728
// Start with user specified priority at creation time (if any)
693729
JobPriority basePriority = (taskCreateFlags.getRequestedPriority());
694730

695-
if (taskIsDetached(taskCreateFlags, jobFlags)) {
696-
SWIFT_TASK_DEBUG_LOG("Creating a detached task from %p", currentTask);
697-
// Case 1: No priority specified
698-
// Base priority = UN
699-
// Escalated priority = UN
700-
// Case 2: Priority specified
701-
// Base priority = user specified priority
702-
// Escalated priority = UN
703-
//
704-
// Task will be created with max priority = max(base priority, UN) = base
705-
// priority. We shouldn't need to do any additional manipulations here since
706-
// basePriority should already be the right value
731+
if (taskCreateFlags.isInlineTask()) {
732+
SWIFT_TASK_DEBUG_LOG("Creating an inline task from %p", currentTask);
733+
734+
// We'll take the current priority and set it as base and escalated
735+
// priority of the task. No UI->IN downgrade needed.
736+
basePriority = swift_task_getCurrentThreadPriority();
707737

708-
} else if (taskIsUnstructured(jobFlags)) {
738+
} else if (taskIsDetached(taskCreateFlags, jobFlags)) {
739+
SWIFT_TASK_DEBUG_LOG("Creating a detached task from %p", currentTask);
740+
// Case 1: No priority specified
741+
// Base priority = UN
742+
// Escalated priority = UN
743+
// Case 2: Priority specified
744+
// Base priority = user specified priority
745+
// Escalated priority = UN
746+
//
747+
// Task will be created with max priority = max(base priority, UN) = base
748+
// priority. We shouldn't need to do any additional manipulations here since
749+
// basePriority should already be the right value
750+
751+
} else if (taskIsUnstructured(taskCreateFlags, jobFlags)) {
709752
SWIFT_TASK_DEBUG_LOG("Creating an unstructured task from %p", currentTask);
710753

711754
if (isUnspecified(basePriority)) {
@@ -934,6 +977,15 @@ static AsyncTaskAndContext swift_task_create_commonImpl(
934977
// Attach to the group, if needed.
935978
if (group) {
936979
swift_taskGroup_attachChild(group, task);
980+
#if SWIFT_CONCURRENCY_TASK_TO_THREAD_MODEL
981+
// We need to take a retain here to keep the child task for the task group
982+
// alive. In the non-task-to-thread model, we'd always take this retain
983+
// below since we'd enqueue the child task. But since we're not going to be
984+
// enqueueing the child task in this model, we need to take this +1 to
985+
// balance out the release that exists after the task group child task
986+
// creation
987+
swift_retain(task);
988+
#endif
937989
}
938990

939991
// If we're supposed to copy task locals, do so now.
@@ -948,6 +1000,9 @@ static AsyncTaskAndContext swift_task_create_commonImpl(
9481000

9491001
// If we're supposed to enqueue the task, do so now.
9501002
if (taskCreateFlags.enqueueJob()) {
1003+
#if SWIFT_CONCURRENCY_TASK_TO_THREAD_MODEL
1004+
assert(false && "Should not be enqueuing tasks in task-to-thread model");
1005+
#endif
9511006
swift_retain(task);
9521007
task->flagAsAndEnqueueOnExecutor(executor);
9531008
}
@@ -1009,8 +1064,10 @@ void swift::swift_task_run_inline(OpaqueValue *result, void *closureAFP,
10091064
// containing a pointer to the allocation enabling us to provide our stack
10101065
// allocation rather than swift_task_create_common having to malloc it.
10111066
RunInlineTaskOptionRecord option(allocation, allocationBytes);
1067+
size_t taskCreateFlags = 1 << TaskCreateFlags::Task_IsInlineTask;
1068+
10121069
auto taskAndContext = swift_task_create_common(
1013-
/*rawTaskCreateFlags=*/0, &option, futureResultType,
1070+
taskCreateFlags, &option, futureResultType,
10141071
reinterpret_cast<TaskContinuationFunction *>(closure), closureContext,
10151072
/*initialContextSize=*/closureContextSize);
10161073

0 commit comments

Comments
 (0)