Skip to content

Commit fffabae

Browse files
authored
Merge pull request #77663 from rjmccall/self-consuming-create-task
Add support for creating a task with a self-consuming task function
2 parents ae40265 + d8d70d9 commit fffabae

File tree

3 files changed

+46
-15
lines changed

3 files changed

+46
-15
lines changed

include/swift/ABI/MetadataValues.h

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2654,6 +2654,13 @@ class TaskCreateFlags : public FlagSet<size_t> {
26542654
Task_EnqueueJob = 12,
26552655
Task_AddPendingGroupTaskUnconditionally = 13,
26562656
Task_IsDiscardingTask = 14,
2657+
2658+
/// The task function is consumed by calling it (@callee_owned).
2659+
/// The context pointer should be treated as opaque and non-copyable;
2660+
/// in particular, it should not be retained or released.
2661+
///
2662+
/// Supported starting in Swift 6.1.
2663+
Task_IsTaskFunctionConsumed = 15,
26572664
};
26582665

26592666
explicit constexpr TaskCreateFlags(size_t bits) : FlagSet(bits) {}
@@ -2683,6 +2690,9 @@ class TaskCreateFlags : public FlagSet<size_t> {
26832690
FLAGSET_DEFINE_FLAG_ACCESSORS(Task_IsDiscardingTask,
26842691
isDiscardingTask,
26852692
setIsDiscardingTask)
2693+
FLAGSET_DEFINE_FLAG_ACCESSORS(Task_IsTaskFunctionConsumed,
2694+
isTaskFunctionConsumed,
2695+
setIsTaskFunctionConsumed)
26862696
};
26872697

26882698
/// Flags for schedulable jobs.

include/swift/AST/FeatureAvailability.def

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@ FEATURE(NoncopyableGenerics, (6, 0))
7575
FEATURE(InitRawStructMetadata, (6, 0))
7676

7777
FEATURE(LayoutStringValueWitnesses, (6, 1))
78+
FEATURE(CreateTaskWithConsumedFunction, (6, 1))
7879

7980
FEATURE(TaskExecutor, FUTURE)
8081
FEATURE(Differentiation, FUTURE)

stdlib/public/Concurrency/Task.cpp

Lines changed: 35 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1044,16 +1044,44 @@ swift_task_create_commonImpl(size_t rawTaskCreateFlags,
10441044
" with parent %p at base pri %zu",
10451045
task, task->getTaskId(), parent, basePriority);
10461046

1047+
// Configure the initial context.
1048+
1049+
// Initialize the parent context pointer to null.
1050+
initialContext->Parent = nullptr;
1051+
10471052
#pragma clang diagnostic push
10481053
#pragma clang diagnostic ignored "-Wcast-function-type-mismatch"
1049-
// Initialize the task-local allocator.
1050-
initialContext->ResumeParent =
1051-
runInlineOption ? &completeInlineTask
1052-
: reinterpret_cast<TaskContinuationFunction *>(
1053-
asyncLet ? &completeTask
1054-
: closureContext ? &completeTaskWithClosure
1055-
: &completeTaskAndRelease);
1054+
// Initialize the resumption funclet pointer (async return address) to
1055+
// the final funclet for completing the task.
1056+
1057+
// Inline tasks are unmanaged, non-throwing, and use a non-escaping
1058+
// task function. The final funclet doesn't expect to get passed an error,
1059+
// and it doesn't clean up either the function or the task directly.
1060+
if (runInlineOption) {
1061+
initialContext->ResumeParent = &completeInlineTask;
1062+
1063+
// `async let` tasks are unmanaged and use a non-escaping task function.
1064+
// The final funclet shouldn't release the task or the task function.
1065+
} else if (asyncLet) {
1066+
initialContext->ResumeParent =
1067+
reinterpret_cast<TaskContinuationFunction*>(&completeTask);
1068+
1069+
// If we have a non-null closure context and the task function is not
1070+
// consumed by calling it, use a final funclet that releases both the
1071+
// task and the closure context.
1072+
} else if (closureContext && !taskCreateFlags.isTaskFunctionConsumed()) {
1073+
initialContext->ResumeParent =
1074+
reinterpret_cast<TaskContinuationFunction*>(&completeTaskWithClosure);
1075+
1076+
// Otherwise, just release the task.
1077+
} else {
1078+
initialContext->ResumeParent =
1079+
reinterpret_cast<TaskContinuationFunction*>(&completeTaskAndRelease);
1080+
}
10561081
#pragma clang diagnostic pop
1082+
1083+
// Initialize the task-local allocator and our other private runtime
1084+
// state for the task.
10571085
if ((asyncLet || (runInlineOption && runInlineOption->getAllocation())) &&
10581086
initialSlabSize > 0) {
10591087
assert(parent || (runInlineOption && runInlineOption->getAllocation()));
@@ -1095,14 +1123,6 @@ swift_task_create_commonImpl(size_t rawTaskCreateFlags,
10951123
task->_private().Local.initializeLinkParent(task, parent);
10961124
}
10971125

1098-
// Configure the initial context.
1099-
//
1100-
// FIXME: if we store a null pointer here using the standard ABI for
1101-
// signed null pointers, then we'll have to authenticate context pointers
1102-
// as if they might be null, even though the only time they ever might
1103-
// be is the final hop. Store a signed null instead.
1104-
initialContext->Parent = nullptr;
1105-
11061126
// FIXME: add discarding flag
11071127
// FIXME: add task executor
11081128
concurrency::trace::task_create(

0 commit comments

Comments
 (0)