Skip to content

Commit 7052e66

Browse files
authored
Merge pull request #37599 from ktoso/pick-task-locals
[5.5] pick Task Locals in sync functions, reapply AsyncTask layout
2 parents 4a78ff7 + 7004aff commit 7052e66

15 files changed

+451
-148
lines changed

include/swift/ABI/Task.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -271,7 +271,7 @@ class AsyncTask : public Job {
271271
OpaqueValue *localValueGet(const HeapObject *key);
272272

273273
/// Returns true if storage has still more bindings.
274-
void localValuePop();
274+
bool localValuePop();
275275

276276
// ==== Child Fragment -------------------------------------------------------
277277

include/swift/ABI/TaskLocal.h

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,8 @@ class TaskLocal {
118118
reinterpret_cast<char *>(this) + storageOffset(valueType));
119119
}
120120

121+
void copyTo(AsyncTask *task);
122+
121123
/// Compute the offset of the storage from the base of the item.
122124
static size_t storageOffset(const Metadata *valueType) {
123125
size_t offset = sizeof(Item);
@@ -184,7 +186,22 @@ class TaskLocal {
184186

185187
OpaqueValue* getValue(AsyncTask *task, const HeapObject *key);
186188

187-
void popValue(AsyncTask *task);
189+
/// Returns `true` of more bindings remain in this storage,
190+
/// and `false` if the just popped value was the last one and the storage
191+
/// can be safely disposed of.
192+
bool popValue(AsyncTask *task);
193+
194+
/// Copy all task-local bindings to the target task.
195+
///
196+
/// The new bindings allocate their own items and can out-live the current task.
197+
///
198+
/// ### Optimizations
199+
/// Only the most recent binding of a value is copied over, i.e. given
200+
/// a key bound to `A` and then `B`, only the `B` binding will be copied.
201+
/// This is safe and correct because the new task would never have a chance
202+
/// to observe the `A` value, because it semantically will never observe a
203+
/// "pop" of the `B` value - it was spawned from a scope where only B was observable.
204+
void copyTo(AsyncTask *target);
188205

189206
/// Destroy and deallocate all items stored by this specific task.
190207
///

include/swift/Runtime/Concurrency.h

Lines changed: 29 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -420,50 +420,62 @@ void swift_task_reportIllegalTaskLocalBindingWithinWithTaskGroup(
420420
const unsigned char *file, uintptr_t fileLength,
421421
bool fileIsASCII, uintptr_t line);
422422

423-
/// Get a task local value from the passed in task. Its Swift signature is
423+
/// Get a task local value from either the current task, or fallback task-local
424+
/// storage.
425+
///
426+
/// Its Swift signature is
424427
///
425428
/// \code
426429
/// func _taskLocalValueGet<Key>(
427-
/// _ task: Builtin.NativeObject,
428430
/// keyType: Any.Type /*Key.Type*/
429431
/// ) -> UnsafeMutableRawPointer? where Key: TaskLocalKey
430432
/// \endcode
431433
SWIFT_EXPORT_FROM(swift_Concurrency) SWIFT_CC(swift)
432434
OpaqueValue*
433-
swift_task_localValueGet(AsyncTask* task, const HeapObject *key);
435+
swift_task_localValueGet(const HeapObject *key);
434436

435-
/// Add a task local value to the passed in task.
436-
///
437-
/// This must be only invoked by the task itself to avoid concurrent writes.
437+
/// Bind a task local key to a value in the context of either the current
438+
/// AsyncTask if present, or in the thread-local fallback context if no task
439+
/// available.
438440
///
439441
/// Its Swift signature is
440442
///
441443
/// \code
442444
/// public func _taskLocalValuePush<Value>(
443-
/// _ task: Builtin.NativeObject,
444445
/// keyType: Any.Type/*Key.Type*/,
445446
/// value: __owned Value
446447
/// )
447448
/// \endcode
448449
SWIFT_EXPORT_FROM(swift_Concurrency) SWIFT_CC(swift)
449-
void swift_task_localValuePush(AsyncTask* task,
450-
const HeapObject *key,
451-
/* +1 */ OpaqueValue *value,
452-
const Metadata *valueType);
450+
void swift_task_localValuePush(const HeapObject *key,
451+
/* +1 */ OpaqueValue *value,
452+
const Metadata *valueType);
453453

454-
/// Remove task a local binding from the task local values stack.
454+
/// Pop a single task local binding from the binding stack of the current task,
455+
/// or the fallback thread-local storage if no task is available.
455456
///
456-
/// This must be only invoked by the task itself to avoid concurrent writes.
457+
/// This operation must be paired up with a preceding "push" operation, as otherwise
458+
/// it may attempt to "pop" off an empty value stuck which will lead to a crash.
459+
///
460+
/// The Swift surface API ensures proper pairing of push and pop operations.
457461
///
458462
/// Its Swift signature is
459463
///
460464
/// \code
461-
/// public func _taskLocalValuePop(
462-
/// _ task: Builtin.NativeObject
463-
/// )
465+
/// public func _taskLocalValuePop()
466+
/// \endcode
467+
SWIFT_EXPORT_FROM(swift_Concurrency) SWIFT_CC(swift)
468+
void swift_task_localValuePop();
469+
470+
/// Copy all task locals from the current context to the target task.
471+
///
472+
/// Its Swift signature is
473+
///
474+
/// \code
475+
/// func _taskLocalValueGet<Key>(AsyncTask* task)
464476
/// \endcode
465477
SWIFT_EXPORT_FROM(swift_Concurrency) SWIFT_CC(swift)
466-
void swift_task_localValuePop(AsyncTask* task);
478+
void swift_task_localsCopyTo(AsyncTask* target);
467479

468480
/// This should have the same representation as an enum like this:
469481
/// enum NearestTaskDeadline {

include/swift/Runtime/ThreadLocal.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ namespace swift {
6565
// itself thread-local, and no internal support is required.
6666
//
6767
// Note that this includes platforms that set
68-
// SWIFT_STDLIB_SINGLE_THREADED_RUNTIME, for whhch
68+
// SWIFT_STDLIB_SINGLE_THREADED_RUNTIME, for which
6969
// SWIFT_RUNTIME_ATTRIBUTE_THREAD_LOCAL is empty;
7070
// thread-local declarations then create an ordinary global.
7171
//

stdlib/public/CompatibilityOverride/CompatibilityOverrideConcurrency.def

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -233,19 +233,25 @@ OVERRIDE_TASK_LOCAL(task_reportIllegalTaskLocalBindingWithinWithTaskGroup, void,
233233
OVERRIDE_TASK_LOCAL(task_localValuePush, void,
234234
SWIFT_EXPORT_FROM(swift_Concurrency), SWIFT_CC(swift),
235235
swift::,
236-
(AsyncTask *task, const HeapObject *key,
237-
OpaqueValue *value, const Metadata *valueType),
238-
(task, key, value, valueType))
236+
(const HeapObject *key, OpaqueValue *value,
237+
const Metadata *valueType),
238+
(key, value, valueType))
239239

240240
OVERRIDE_TASK_LOCAL(task_localValueGet, OpaqueValue *,
241241
SWIFT_EXPORT_FROM(swift_Concurrency), SWIFT_CC(swift),
242242
swift::,
243-
(AsyncTask *task, const HeapObject *key),
244-
(task, key))
243+
(const HeapObject *key),
244+
(key))
245245

246246
OVERRIDE_TASK_LOCAL(task_localValuePop, void,
247247
SWIFT_EXPORT_FROM(swift_Concurrency), SWIFT_CC(swift),
248-
swift::, (AsyncTask *task), (task))
248+
swift::, ,)
249+
250+
OVERRIDE_TASK_LOCAL(task_localsCopyTo, void,
251+
SWIFT_EXPORT_FROM(swift_Concurrency), SWIFT_CC(swift),
252+
swift::,
253+
(AsyncTask *target),
254+
(target))
249255

250256
OVERRIDE_TASK_STATUS(task_addStatusRecord, bool,
251257
SWIFT_EXPORT_FROM(swift_Concurrency), SWIFT_CC(swift),

stdlib/public/Concurrency/Task.swift

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -591,6 +591,15 @@ public func async<T>(
591591
// Create the asynchronous task future.
592592
let (task, _) = Builtin.createAsyncTaskFuture(Int(flags.bits), operation)
593593

594+
// Copy all task locals to the newly created task.
595+
// We must copy them rather than point to the current task since the new task
596+
// is not structured and may out-live the current task.
597+
//
598+
// WARNING: This MUST be done BEFORE we enqueue the task,
599+
// because it acts as-if it was running inside the task and thus does not
600+
// take any extra steps to synchronize the task-local operations.
601+
_taskLocalsCopy(to: task)
602+
594603
// Enqueue the resulting job.
595604
_enqueueJobGlobal(Builtin.convertTaskToJob(task))
596605

0 commit comments

Comments
 (0)