Skip to content

Commit 6b81674

Browse files
authored
Merge pull request #37340 from ktoso/wip-locals-sync
[TaskLocals] Enable sync functions to bind task-locals; Keep Storage in TLS
2 parents 5af7b68 + 6cbb792 commit 6b81674

15 files changed

+457
-147
lines changed

include/swift/ABI/Task.h

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -271,8 +271,9 @@ class AsyncTask : public Job {
271271
return Local.getValue(this, key);
272272
}
273273

274-
void localValuePop() {
275-
Local.popValue(this);
274+
/// Returns true if storage has still more bindings.
275+
bool localValuePop() {
276+
return Local.popValue(this);
276277
}
277278

278279
// ==== Child Fragment -------------------------------------------------------

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
@@ -433,50 +433,62 @@ void swift_task_reportIllegalTaskLocalBindingWithinWithTaskGroup(
433433
const unsigned char *file, uintptr_t fileLength,
434434
bool fileIsASCII, uintptr_t line);
435435

436-
/// Get a task local value from the passed in task. Its Swift signature is
436+
/// Get a task local value from either the current task, or fallback task-local
437+
/// storage.
438+
///
439+
/// Its Swift signature is
437440
///
438441
/// \code
439442
/// func _taskLocalValueGet<Key>(
440-
/// _ task: Builtin.NativeObject,
441443
/// keyType: Any.Type /*Key.Type*/
442444
/// ) -> UnsafeMutableRawPointer? where Key: TaskLocalKey
443445
/// \endcode
444446
SWIFT_EXPORT_FROM(swift_Concurrency) SWIFT_CC(swift)
445447
OpaqueValue*
446-
swift_task_localValueGet(AsyncTask* task, const HeapObject *key);
448+
swift_task_localValueGet(const HeapObject *key);
447449

448-
/// Add a task local value to the passed in task.
449-
///
450-
/// This must be only invoked by the task itself to avoid concurrent writes.
450+
/// Bind a task local key to a value in the context of either the current
451+
/// AsyncTask if present, or in the thread-local fallback context if no task
452+
/// available.
451453
///
452454
/// Its Swift signature is
453455
///
454456
/// \code
455457
/// public func _taskLocalValuePush<Value>(
456-
/// _ task: Builtin.NativeObject,
457458
/// keyType: Any.Type/*Key.Type*/,
458459
/// value: __owned Value
459460
/// )
460461
/// \endcode
461462
SWIFT_EXPORT_FROM(swift_Concurrency) SWIFT_CC(swift)
462-
void swift_task_localValuePush(AsyncTask* task,
463-
const HeapObject *key,
464-
/* +1 */ OpaqueValue *value,
465-
const Metadata *valueType);
463+
void swift_task_localValuePush(const HeapObject *key,
464+
/* +1 */ OpaqueValue *value,
465+
const Metadata *valueType);
466466

467-
/// Remove task a local binding from the task local values stack.
467+
/// Pop a single task local binding from the binding stack of the current task,
468+
/// or the fallback thread-local storage if no task is available.
468469
///
469-
/// This must be only invoked by the task itself to avoid concurrent writes.
470+
/// This operation must be paired up with a preceding "push" operation, as otherwise
471+
/// it may attempt to "pop" off an empty value stuck which will lead to a crash.
472+
///
473+
/// The Swift surface API ensures proper pairing of push and pop operations.
470474
///
471475
/// Its Swift signature is
472476
///
473477
/// \code
474-
/// public func _taskLocalValuePop(
475-
/// _ task: Builtin.NativeObject
476-
/// )
478+
/// public func _taskLocalValuePop()
479+
/// \endcode
480+
SWIFT_EXPORT_FROM(swift_Concurrency) SWIFT_CC(swift)
481+
void swift_task_localValuePop();
482+
483+
/// Copy all task locals from the current context to the target task.
484+
///
485+
/// Its Swift signature is
486+
///
487+
/// \code
488+
/// func _taskLocalValueGet<Key>(AsyncTask* task)
477489
/// \endcode
478490
SWIFT_EXPORT_FROM(swift_Concurrency) SWIFT_CC(swift)
479-
void swift_task_localValuePop(AsyncTask* task);
491+
void swift_task_localsCopyTo(AsyncTask* target);
480492

481493
/// This should have the same representation as an enum like this:
482494
/// 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.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -195,6 +195,10 @@ AsyncTask::~AsyncTask() {
195195
}
196196

197197
// Release any objects potentially held as task local values.
198+
//
199+
// This must be called last when destroying a task - to keep stack discipline of the allocator.
200+
// because it may have created some task-locals immediately upon creation,
201+
// e.g. if the task is spawned with async{} and inherited some task-locals.
198202
Local.destroy(this);
199203
}
200204

stdlib/public/Concurrency/Task.swift

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

538+
// Copy all task locals to the newly created task.
539+
// We must copy them rather than point to the current task since the new task
540+
// is not structured and may out-live the current task.
541+
//
542+
// WARNING: This MUST be done BEFORE we enqueue the task,
543+
// because it acts as-if it was running inside the task and thus does not
544+
// take any extra steps to synchronize the task-local operations.
545+
_taskLocalsCopy(to: task)
546+
538547
// Enqueue the resulting job.
539548
_enqueueJobGlobal(Builtin.convertTaskToJob(task))
540549

0 commit comments

Comments
 (0)