Skip to content

Commit 82e91b7

Browse files
committed
[TaskLocals] Enable sync functions to bind task-locals; Keep Storage in TLS
1 parent 7ee2a5c commit 82e91b7

File tree

12 files changed

+241
-134
lines changed

12 files changed

+241
-134
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: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -184,7 +184,10 @@ class TaskLocal {
184184

185185
OpaqueValue* getValue(AsyncTask *task, const HeapObject *key);
186186

187-
void popValue(AsyncTask *task);
187+
/// Returns `true` of more bindings remain in this storage,
188+
/// and `false` if the just popped value was the last one and the storage
189+
/// can be safely disposed of.
190+
bool popValue(AsyncTask *task);
188191

189192
/// Destroy and deallocate all items stored by this specific task.
190193
///

include/swift/Runtime/Concurrency.h

Lines changed: 19 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -433,50 +433,52 @@ 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()
477479
/// \endcode
478480
SWIFT_EXPORT_FROM(swift_Concurrency) SWIFT_CC(swift)
479-
void swift_task_localValuePop(AsyncTask* task);
481+
void swift_task_localValuePop();
480482

481483
/// This should have the same representation as an enum like this:
482484
/// 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: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -233,19 +233,19 @@ 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::, ,)
249249

250250
OVERRIDE_TASK_STATUS(task_addStatusRecord, bool,
251251
SWIFT_EXPORT_FROM(swift_Concurrency), SWIFT_CC(swift),

stdlib/public/Concurrency/TaskLocal.cpp

Lines changed: 108 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,18 @@
1111
//===----------------------------------------------------------------------===//
1212

1313
#include "../CompatibilityOverride/CompatibilityOverride.h"
14-
#include "swift/ABI/TaskLocal.h"
15-
#include "swift/ABI/Actor.h"
16-
#include "swift/ABI/Task.h"
17-
#include "swift/ABI/Metadata.h"
14+
#include "../runtime/ThreadLocalStorage.h"
15+
#include "swift/Runtime/Atomic.h"
16+
#include "swift/Runtime/Casting.h"
1817
#include "swift/Runtime/Once.h"
1918
#include "swift/Runtime/Mutex.h"
2019
#include "swift/Runtime/Concurrency.h"
20+
#include "swift/Runtime/ThreadLocal.h"
21+
#include "swift/ABI/TaskLocal.h"
22+
#include "swift/ABI/Task.h"
23+
#include "swift/ABI/Actor.h"
24+
#include "swift/ABI/Metadata.h"
25+
#include "llvm/ADT/PointerIntPair.h"
2126
#include "TaskPrivate.h"
2227

2328
#if defined(__APPLE__)
@@ -26,32 +31,107 @@
2631
#include <android/log.h>
2732
#endif
2833

34+
#if HAVE_PTHREAD_H
35+
#include <pthread.h>
36+
#endif
37+
2938
#if defined(_WIN32)
3039
#include <io.h>
40+
#include <handleapi.h>
41+
#include <processthreadsapi.h>
3142
#endif
3243

3344
using namespace swift;
3445

3546
// =============================================================================
47+
48+
/// An extremely silly class which exists to make pointer
49+
/// default-initialization constexpr.
50+
template <class T> struct Pointer {
51+
T *Value;
52+
constexpr Pointer() : Value(nullptr) {}
53+
constexpr Pointer(T *value) : Value(value) {}
54+
operator T *() const { return Value; }
55+
T *operator->() const { return Value; }
56+
};
57+
58+
/// THIS IS RUNTIME INTERNAL AND NOT ABI.
59+
class FallbackTaskLocalStorage {
60+
static SWIFT_RUNTIME_DECLARE_THREAD_LOCAL(
61+
Pointer<TaskLocal::Storage>,
62+
Value);
63+
64+
public:
65+
static void set(TaskLocal::Storage *task) { Value.set(task); }
66+
static TaskLocal::Storage *get() { return Value.get(); }
67+
};
68+
69+
/// Define the thread-locals.
70+
SWIFT_RUNTIME_DECLARE_THREAD_LOCAL(
71+
Pointer<TaskLocal::Storage>,
72+
FallbackTaskLocalStorage::Value);
73+
3674
// ==== ABI --------------------------------------------------------------------
3775

3876
SWIFT_CC(swift)
39-
static void swift_task_localValuePushImpl(AsyncTask *task,
40-
const HeapObject *key,
41-
/* +1 */ OpaqueValue *value,
42-
const Metadata *valueType) {
43-
task->localValuePush(key, value, valueType);
77+
static void swift_task_localValuePushImpl(const HeapObject *key,
78+
/* +1 */ OpaqueValue *value,
79+
const Metadata *valueType) {
80+
if (AsyncTask *task = swift_task_getCurrent()) {
81+
task->localValuePush(key, value, valueType);
82+
return;
83+
}
84+
85+
// no AsyncTask available so we must check the fallback
86+
TaskLocal::Storage *Local = nullptr;
87+
if (auto storage = FallbackTaskLocalStorage::get()) {
88+
Local = storage;
89+
} else {
90+
void *allocation = malloc(sizeof(TaskLocal::Storage));
91+
auto *freshStorage = new(allocation) TaskLocal::Storage();
92+
93+
FallbackTaskLocalStorage::set(freshStorage);
94+
Local = freshStorage;
95+
}
96+
97+
Local->pushValue(/*task=*/nullptr, key, value, valueType);
4498
}
4599

46100
SWIFT_CC(swift)
47-
static OpaqueValue* swift_task_localValueGetImpl(AsyncTask *task,
48-
const HeapObject *key) {
49-
return task->localValueGet(key);
101+
static OpaqueValue* swift_task_localValueGetImpl(const HeapObject *key) {
102+
if (AsyncTask *task = swift_task_getCurrent()) {
103+
// we're in the context of a task and can use the task's storage
104+
return task->localValueGet(key);
105+
}
106+
107+
// no AsyncTask available so we must check the fallback
108+
if (auto Local = FallbackTaskLocalStorage::get()) {
109+
return Local->getValue(/*task*/nullptr, key);
110+
}
111+
112+
// no value found in task-local or fallback thread-local storage.
113+
return nullptr;
50114
}
51115

52116
SWIFT_CC(swift)
53-
static void swift_task_localValuePopImpl(AsyncTask *task) {
54-
task->localValuePop();
117+
static void swift_task_localValuePopImpl() {
118+
if (AsyncTask *task = swift_task_getCurrent()) {
119+
task->localValuePop();
120+
return;
121+
}
122+
123+
if (TaskLocal::Storage *Local = FallbackTaskLocalStorage::get()) {
124+
bool hasRemainingBindings = Local->popValue(nullptr);
125+
if (!hasRemainingBindings) {
126+
// We clean up eagerly, it may be that this non-swift-concurrency thread
127+
// never again will use task-locals, and as such we better remove the storage.
128+
FallbackTaskLocalStorage::set(nullptr);
129+
free(Local);
130+
}
131+
return;
132+
}
133+
134+
assert(false && "Attempted to pop value but no task or thread-local storage available!");
55135
}
56136

57137
// =============================================================================
@@ -179,17 +259,14 @@ static void swift_task_reportIllegalTaskLocalBindingWithinWithTaskGroupImpl(
179259

180260
TaskLocal::Item*
181261
TaskLocal::Item::createLink(AsyncTask *task,
182-
const HeapObject *key,
183-
const Metadata *valueType) {
184-
assert(task);
185-
262+
const HeapObject *key,
263+
const Metadata *valueType) {
186264
size_t amountToAllocate = Item::itemSize(valueType);
187-
// assert(amountToAllocate % MaximumAlignment == 0); // TODO: do we need this?
188-
189-
void *allocation = _swift_task_alloc_specific(task, amountToAllocate);
190-
Item *item = new(allocation) Item(key, valueType);
265+
void *allocation = task ? _swift_task_alloc_specific(task, amountToAllocate)
266+
: malloc(amountToAllocate);
267+
Item *item = new (allocation) Item(key, valueType);
191268

192-
auto next = task->Local.head;
269+
auto next = task ? task->Local.head : FallbackTaskLocalStorage::get()->head;
193270
item->next = reinterpret_cast<uintptr_t>(next) |
194271
static_cast<uintptr_t>(NextLinkType::IsNext);
195272

@@ -205,7 +282,10 @@ void TaskLocal::Item::destroy(AsyncTask *task) {
205282
valueType->vw_destroy(getStoragePtr());
206283
}
207284

208-
_swift_task_dealloc_specific(task, this);
285+
// if task is available, we must have used the task allocator to allocate this item,
286+
// so we must deallocate it using the same. Otherwise, we must have used malloc.
287+
if (task) _swift_task_dealloc_specific(task, this);
288+
else free(this);
209289
}
210290

211291
void TaskLocal::Storage::destroy(AsyncTask *task) {
@@ -244,11 +324,14 @@ void TaskLocal::Storage::pushValue(AsyncTask *task,
244324
head = item;
245325
}
246326

247-
void TaskLocal::Storage::popValue(AsyncTask *task) {
327+
bool TaskLocal::Storage::popValue(AsyncTask *task) {
248328
assert(head && "attempted to pop value off empty task-local stack");
249329
auto old = head;
250330
head = head->getNext();
251331
old->destroy(task);
332+
333+
/// if pointing at not-null next item, there are remaining bindings.
334+
return head != nullptr;
252335
}
253336

254337
OpaqueValue* TaskLocal::Storage::getValue(AsyncTask *task,

0 commit comments

Comments
 (0)