Skip to content

Commit 1044723

Browse files
committed
[Concurrency] Initial Task Local Values implementation
1 parent e9ff5fd commit 1044723

File tree

13 files changed

+1010
-24
lines changed

13 files changed

+1010
-24
lines changed

include/swift/ABI/MetadataValues.h

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1936,9 +1936,10 @@ class JobFlags : public FlagSet<size_t> {
19361936

19371937
// Kind-specific flags.
19381938

1939-
Task_IsChildTask = 24,
1940-
Task_IsFuture = 25,
1941-
Task_IsTaskGroup = 26,
1939+
Task_IsChildTask = 24,
1940+
Task_IsFuture = 25,
1941+
Task_IsTaskGroup = 26,
1942+
Task_HasLocalValues = 27
19421943
};
19431944

19441945
explicit JobFlags(size_t bits) : FlagSet(bits) {}
@@ -1965,11 +1966,12 @@ class JobFlags : public FlagSet<size_t> {
19651966
FLAGSET_DEFINE_FLAG_ACCESSORS(Task_IsFuture,
19661967
task_isFuture,
19671968
task_setIsFuture)
1968-
19691969
FLAGSET_DEFINE_FLAG_ACCESSORS(Task_IsTaskGroup,
19701970
task_isTaskGroup,
19711971
task_setIsTaskGroup)
1972-
1972+
FLAGSET_DEFINE_FLAG_ACCESSORS(Task_HasLocalValues,
1973+
task_hasLocalValues,
1974+
task_setHasLocalValues)
19731975
};
19741976

19751977
/// Kinds of task status record.

include/swift/ABI/Task.h

Lines changed: 223 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -137,11 +137,12 @@ class ActiveTaskStatus {
137137
/// ### Fragments
138138
/// An AsyncTask may have the following fragments:
139139
///
140-
/// +------------------+
141-
/// | childFragment? |
142-
/// | groupFragment? |
143-
/// | futureFragment? |*
144-
/// +------------------+
140+
/// +--------------------------+
141+
/// | childFragment? |
142+
/// | taskLocalValuesFragment |
143+
/// | groupFragment? |
144+
/// | futureFragment? |*
145+
/// +--------------------------+
145146
///
146147
/// The future fragment is dynamic in size, based on the future result type
147148
/// it can hold, and thus must be the *last* fragment.
@@ -175,13 +176,15 @@ class AsyncTask : public HeapObject, public Job {
175176
void run(ExecutorRef currentExecutor) {
176177
ResumeTask(this, currentExecutor, ResumeContext);
177178
}
178-
179+
179180
/// Check whether this task has been cancelled.
180181
/// Checking this is, of course, inherently race-prone on its own.
181182
bool isCancelled() const {
182183
return Status.load(std::memory_order_relaxed).isCancelled();
183184
}
184185

186+
// ==== Child Fragment -------------------------------------------------------
187+
185188
/// A fragment of an async task structure that happens to be a child task.
186189
class ChildFragment {
187190
/// The parent task of this task.
@@ -205,14 +208,212 @@ class AsyncTask : public HeapObject, public Job {
205208
}
206209
};
207210

208-
// TODO: rename? all other functions are `is...` rather than `has...Fragment`
209-
bool hasChildFragment() const { return Flags.task_isChildTask(); }
211+
bool hasChildFragment() const {
212+
return Flags.task_isChildTask();
213+
}
210214

211215
ChildFragment *childFragment() {
212216
assert(hasChildFragment());
213217
return reinterpret_cast<ChildFragment*>(this + 1);
214218
}
215219

220+
// ==== Task Locals Values-- -------------------------------------------------
221+
222+
class TaskLocalValuesFragment {
223+
public:
224+
/// Type of the pointed at `next` task local item.
225+
enum class NextLinkType : uintptr_t {
226+
/// This task is known to be a "terminal" node in the lookup of task locals.
227+
/// In other words, even if it had a parent, the parent (and its parents)
228+
/// are known to not contain any any more task locals, and thus any further
229+
/// search beyond this task.
230+
IsTerminal = 0b00,
231+
/// The storage pointer points at the next TaskLocalChainItem in this task.
232+
IsNext = 0b01,
233+
/// The storage pointer points at a parent AsyncTask,
234+
/// in which we should continue the lookup.
235+
IsParent = 0b11
236+
};
237+
238+
class TaskLocalItem {
239+
private:
240+
/// Mask used for the low status bits in a task local chain item.
241+
static const uintptr_t statusMask = 0x03;
242+
243+
/// Pointer to the next task local item; be it in this task or in a parent.
244+
/// Low bits encode `NextLinkType`.
245+
/// TaskLocalItem *next = nullptr;
246+
uintptr_t next;
247+
248+
public:
249+
/// The type of the key with which this value is associated.
250+
const Metadata *keyType;
251+
/// The type of the value stored by this item.
252+
const Metadata *valueType;
253+
254+
// Trailing storage for the value itself. The storage will be
255+
// uninitialized or contain an instance of \c valueType.
256+
257+
private:
258+
explicit TaskLocalItem(const Metadata *keyType, const Metadata *valueType)
259+
: keyType(keyType),
260+
valueType(valueType),
261+
next(0) { }
262+
263+
public:
264+
/// TaskLocalItem which does not by itself store any value, but only points
265+
/// to the nearest task-local-value containing parent's first task item.
266+
///
267+
/// This item type is used to link to the appropriate parent task's item,
268+
/// when the current task itself does not have any task local values itself.
269+
///
270+
/// When a task actually has its own task locals, it should rather point
271+
/// to the parent's *first* task-local item in its *last* item, extending
272+
/// the TaskLocalItem linked list into the appropriate parent.
273+
static TaskLocalItem* createParentLink(AsyncTask *task, AsyncTask *parent) {
274+
assert(parent);
275+
assert(parent->hasTaskLocalValues());
276+
assert(task->hasTaskLocalValues());
277+
size_t amountToAllocate = TaskLocalItem::itemSize(/*valueType*/nullptr);
278+
// assert(amountToAllocate % MaximumAlignment == 0); // TODO: do we need this?
279+
void *allocation = malloc(amountToAllocate); // TODO: use task-local allocator
280+
fprintf(stderr, "MALLOC parent link item: %d\n", allocation);
281+
282+
TaskLocalItem *item =
283+
new(allocation) TaskLocalItem(nullptr, nullptr);
284+
285+
auto next = parent->localValuesFragment()->head;
286+
auto nextLinkType = next ? NextLinkType::IsParent : NextLinkType::IsTerminal;
287+
item->next = reinterpret_cast<uintptr_t>(next) |
288+
static_cast<uintptr_t>(nextLinkType);
289+
290+
fprintf(stderr, "error: %s [%s:%d] created parent item: task=%d -> parentTask=%d :: item=%d -> item->getNext()=%d\n", __FUNCTION__, __FILE_NAME__, __LINE__,
291+
task, parent, item, item->getNext());
292+
293+
return item;
294+
}
295+
296+
static TaskLocalItem* createLink(AsyncTask *task,
297+
const Metadata *keyType,
298+
const Metadata *valueType) {
299+
assert(task);
300+
assert(task->hasTaskLocalValues());
301+
size_t amountToAllocate = TaskLocalItem::itemSize(valueType);
302+
// assert(amountToAllocate % MaximumAlignment == 0); // TODO: do we need this?
303+
void *allocation = malloc(amountToAllocate); // TODO: use task-local allocator
304+
fprintf(stderr, "MALLOC link item: %d\n", allocation);
305+
TaskLocalItem *item =
306+
new(allocation) TaskLocalItem(keyType, valueType);
307+
308+
auto next = task->localValuesFragment()->head;
309+
auto nextLinkType = next ? NextLinkType::IsNext : NextLinkType::IsTerminal;
310+
item->next = reinterpret_cast<uintptr_t>(next) |
311+
static_cast<uintptr_t>(nextLinkType);
312+
313+
return item;
314+
}
315+
316+
void destroy() {
317+
if (valueType) {
318+
valueType->vw_destroy(getStoragePtr());
319+
}
320+
}
321+
322+
TaskLocalItem *getNext() {
323+
return reinterpret_cast<TaskLocalItem *>(next & ~statusMask);
324+
}
325+
326+
NextLinkType getNextLinkType() {
327+
return static_cast<NextLinkType>(next & statusMask);
328+
}
329+
330+
/// Retrieve a pointer to the storage of the value.
331+
OpaqueValue *getStoragePtr() {
332+
// assert(valueType && "valueType must be set before accessing storage pointer.");
333+
return reinterpret_cast<OpaqueValue *>(
334+
reinterpret_cast<char *>(this) + storageOffset(valueType));
335+
}
336+
337+
/// Compute the offset of the storage from the base of the item.
338+
static size_t storageOffset(const Metadata *valueType) {
339+
size_t offset = sizeof(TaskLocalItem);
340+
if (valueType) {
341+
size_t alignment = valueType->vw_alignment();
342+
return (offset + alignment - 1) & ~(alignment - 1);
343+
} else {
344+
return offset;
345+
}
346+
}
347+
348+
/// Determine the size of the item given a particular value type.
349+
static size_t itemSize(const Metadata *valueType) {
350+
size_t offset = storageOffset(valueType);
351+
if (valueType) {
352+
offset += valueType->vw_size();
353+
}
354+
return offset;
355+
}
356+
};
357+
358+
private:
359+
/// Single-linked list of task local values.
360+
/// Once task local values within this task are traversed, the list continues
361+
/// to the "next parent that contributes task local values," or if no such
362+
/// parent exists it terminates with null.
363+
///
364+
/// If the TaskLocalValuesFragment was allocated, it is expected that this
365+
/// value should be NOT null; it either has own values, or at least one
366+
/// parent that has values. If this task does not have any values, the head
367+
/// pointer MAY immediately point at this task's parent task which has values.
368+
///
369+
/// NOTE: Check the higher bits to know if this is a self or parent value.
370+
TaskLocalItem *head = nullptr;
371+
372+
public:
373+
TaskLocalValuesFragment() {}
374+
375+
void destroy();
376+
377+
/// If the parent task has task local values defined, point to in
378+
/// the task local values chain.
379+
void initializeLinkParent(AsyncTask* task, AsyncTask* parent);
380+
381+
void pushValue(AsyncTask *task, const Metadata *keyType,
382+
/* +1 */ OpaqueValue *value, const Metadata *valueType);
383+
384+
void popValue(AsyncTask *task);
385+
386+
OpaqueValue* get(const Metadata *keyType);
387+
};
388+
389+
bool hasTaskLocalValues() const {
390+
return Flags.task_hasLocalValues();
391+
}
392+
393+
TaskLocalValuesFragment *localValuesFragment() {
394+
assert(hasTaskLocalValues());
395+
396+
auto offset = reinterpret_cast<char*>(this);
397+
offset += sizeof(AsyncTask);
398+
399+
if (hasChildFragment()) {
400+
offset += sizeof(ChildFragment);
401+
}
402+
403+
return reinterpret_cast<TaskLocalValuesFragment*>(offset);
404+
}
405+
406+
OpaqueValue* localValueGet(const Metadata *keyType) {
407+
if (hasTaskLocalValues()) {
408+
return localValuesFragment()->get(keyType);
409+
} else {
410+
// We are guaranteed to have a task-local fragment even if this task has
411+
// no bindings, but its parent tasks do. Thus, if no fragment, we can
412+
// immediately return null.
413+
return nullptr;
414+
}
415+
}
416+
216417
// ==== TaskGroup ------------------------------------------------------------
217418

218419
class GroupFragment {
@@ -516,12 +717,18 @@ class AsyncTask : public HeapObject, public Job {
516717
GroupFragment *groupFragment() {
517718
assert(isTaskGroup());
518719

720+
auto offset = reinterpret_cast<char*>(this);
721+
offset += sizeof(AsyncTask);
722+
519723
if (hasChildFragment()) {
520-
return reinterpret_cast<GroupFragment *>(
521-
reinterpret_cast<ChildFragment*>(this + 1) + 1);
724+
offset += sizeof(ChildFragment);
522725
}
523726

524-
return reinterpret_cast<GroupFragment *>(this + 1);
727+
if (hasTaskLocalValues()) {
728+
offset += sizeof(TaskLocalValuesFragment);
729+
}
730+
731+
return reinterpret_cast<GroupFragment *>(offset);
525732
}
526733

527734
/// Offer result of a task into this channel.
@@ -647,13 +854,17 @@ class AsyncTask : public HeapObject, public Job {
647854
FutureFragment *futureFragment() {
648855
assert(isFuture());
649856

650-
auto offset = reinterpret_cast<uintptr_t>(this); // TODO: char* instead?
857+
auto offset = reinterpret_cast<char*>(this);
651858
offset += sizeof(AsyncTask);
652859

653860
if (hasChildFragment()) {
654861
offset += sizeof(ChildFragment);
655862
}
656863

864+
if (hasTaskLocalValues()) {
865+
offset += sizeof(TaskLocalValuesFragment);
866+
}
867+
657868
if (isTaskGroup()) {
658869
offset += sizeof(GroupFragment);
659870
}

include/swift/Runtime/Concurrency.h

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
#define SWIFT_RUNTIME_CONCURRENCY_H
1919

2020
#include "swift/ABI/TaskStatus.h"
21+
#include "swift/Runtime/ExistentialContainer.h"
2122

2223
namespace swift {
2324
class DefaultActor;
@@ -225,6 +226,63 @@ size_t swift_task_getJobFlags(AsyncTask* task);
225226
SWIFT_EXPORT_FROM(swift_Concurrency) SWIFT_CC(swift)
226227
bool swift_task_isCancelled(AsyncTask* task);
227228

229+
/// Get a task local value from the passed in task. Its Swift signature is
230+
///
231+
/// \code
232+
/// func _taskLocalValueGet<Key>(
233+
/// _ task: Builtin.NativeObject,
234+
/// keyType: Any.Type /*Key.Type*/
235+
/// ) -> UnsafeMutableRawPointer? where Key: TaskLocalKey
236+
/// \endcode
237+
SWIFT_EXPORT_FROM(swift_Concurrency) SWIFT_CC(swift)
238+
OpaqueValue* swift_task_localValueGet(AsyncTask* task,
239+
const Metadata *keyType);
240+
241+
/// Add a task local value to the passed in task.
242+
///
243+
/// This must be only invoked by the task itself to avoid concurrent writes.
244+
///
245+
/// Its Swift signature is
246+
///
247+
/// \code
248+
/// public func _taskLocalValuePush<Value>(
249+
/// _ task: Builtin.NativeObject,
250+
/// keyType: Any.Type/*Key.Type*/,
251+
/// value: __owned Value
252+
/// )
253+
/// \endcode
254+
SWIFT_EXPORT_FROM(swift_Concurrency) SWIFT_CC(swift)
255+
void swift_task_localValuePush(AsyncTask* task,
256+
const Metadata *keyType,
257+
/* +1 */ OpaqueValue *value,
258+
const Metadata *valueType);
259+
260+
/// Remove task `count` local bindings from the task local binding stack.
261+
/// Crashes if `count` is greater if the number of task locals stored in the task.
262+
///
263+
/// This must be only invoked by the task itself to avoid concurrent writes.
264+
///
265+
/// Its Swift signature is
266+
///
267+
/// \code
268+
/// public func _taskLocalValuePop(
269+
/// _ task: Builtin.NativeObject,
270+
/// count: Int
271+
/// )
272+
/// \endcode
273+
SWIFT_EXPORT_FROM(swift_Concurrency) SWIFT_CC(swift)
274+
void swift_task_localValuePop(AsyncTask* task, int count);
275+
276+
/// Checks if task (or any of its parent tasks) has task local values.
277+
///
278+
/// \code
279+
/// func swift_task_hasTaskLocalValues<Key>(
280+
/// _ task: Builtin.NativeObject,
281+
/// ) -> Bool
282+
/// \endcode
283+
SWIFT_EXPORT_FROM(swift_Concurrency) SWIFT_CC(swift)
284+
bool swift_task_hasTaskLocalValues(AsyncTask* task);
285+
228286
/// This should have the same representation as an enum like this:
229287
/// enum NearestTaskDeadline {
230288
/// case none

0 commit comments

Comments
 (0)