Skip to content

Commit d7169ed

Browse files
committed
[TaskLocals] Cleanly separate locals impl from Task, no need for fragment
1 parent 53069e2 commit d7169ed

File tree

11 files changed

+381
-335
lines changed

11 files changed

+381
-335
lines changed

include/swift/ABI/Task.h

Lines changed: 21 additions & 234 deletions
Original file line numberDiff line numberDiff line change
@@ -17,15 +17,15 @@
1717
#ifndef SWIFT_ABI_TASK_H
1818
#define SWIFT_ABI_TASK_H
1919

20+
#include "swift/ABI/TaskLocal.h"
2021
#include "swift/ABI/Executor.h"
2122
#include "swift/ABI/HeapObject.h"
2223
#include "swift/ABI/Metadata.h"
2324
#include "swift/ABI/MetadataValues.h"
2425
#include "swift/Runtime/Config.h"
2526
#include "swift/Basic/STLExtras.h"
2627
#include "bitset"
27-
#include "string"
28-
#include "queue"
28+
#include "queue" // TODO: remove and replace with our own mpsc
2929

3030
namespace swift {
3131
class AsyncTask;
@@ -147,7 +147,6 @@ class ActiveTaskStatus {
147147
/// An AsyncTask may have the following fragments:
148148
///
149149
/// +--------------------------+
150-
/// | taskLocalValuesFragment |
151150
/// | childFragment? |
152151
/// | groupChildFragment? |
153152
/// | futureFragment? |*
@@ -173,12 +172,16 @@ class AsyncTask : public HeapObject, public Job {
173172
/// Reserved for the use of the task-local stack allocator.
174173
void *AllocatorPrivate[4];
175174

175+
/// Task local values storage container.
176+
TaskLocal::Storage Local;
177+
176178
AsyncTask(const HeapMetadata *metadata, JobFlags flags,
177179
TaskContinuationFunction *run,
178180
AsyncContext *initialContext)
179181
: HeapObject(metadata), Job(flags, run),
180182
ResumeContext(initialContext),
181-
Status(ActiveTaskStatus()) {
183+
Status(ActiveTaskStatus()),
184+
Local(TaskLocal::Storage()) {
182185
assert(flags.isAsyncTask());
183186
}
184187

@@ -196,237 +199,24 @@ class AsyncTask : public HeapObject, public Job {
196199
return Status.load(std::memory_order_relaxed).isCancelled();
197200
}
198201

199-
// ==== Task Locals Values ---------------------------------------------------
200-
201-
/// Storage fragment for task local values.
202-
class TaskLocalValuesFragment {
203-
public:
204-
/// Type of the pointed at `next` task local item.
205-
enum class NextLinkType : uintptr_t {
206-
/// This task is known to be a "terminal" node in the lookup of task locals.
207-
/// In other words, even if it had a parent, the parent (and its parents)
208-
/// are known to not contain any any more task locals, and thus any further
209-
/// search beyond this task.
210-
IsTerminal = 0b00,
211-
/// The storage pointer points at the next TaskLocalChainItem in this task.
212-
IsNext = 0b01,
213-
/// The storage pointer points at a parent AsyncTask, in which we should
214-
/// continue the lookup.
215-
///
216-
/// Note that this may not necessarily be the same as the task's parent
217-
/// task -- we may point to a super-parent if we know / that the parent
218-
/// does not "contribute" any task local values. This is to speed up
219-
/// lookups by skipping empty parent tasks during get(), and explained
220-
/// in depth in `createParentLink`.
221-
IsParent = 0b11
222-
};
223-
224-
/// Values must match `TaskLocalInheritance` declared in `TaskLocal.swift`.
225-
enum class TaskLocalInheritance : uint8_t {
226-
Default = 0,
227-
Never = 1
228-
};
229-
230-
class TaskLocalItem {
231-
private:
232-
/// Mask used for the low status bits in a task local chain item.
233-
static const uintptr_t statusMask = 0x03;
234-
235-
/// Pointer to the next task local item; be it in this task or in a parent.
236-
/// Low bits encode `NextLinkType`.
237-
/// TaskLocalItem *next = nullptr;
238-
uintptr_t next;
239-
240-
public:
241-
/// The type of the key with which this value is associated.
242-
const Metadata *keyType;
243-
/// The type of the value stored by this item.
244-
const Metadata *valueType;
245-
246-
// Trailing storage for the value itself. The storage will be
247-
// uninitialized or contain an instance of \c valueType.
248-
249-
private:
250-
explicit TaskLocalItem(const Metadata *keyType, const Metadata *valueType)
251-
: next(0),
252-
keyType(keyType),
253-
valueType(valueType) { }
254-
255-
public:
256-
/// TaskLocalItem which does not by itself store any value, but only points
257-
/// to the nearest task-local-value containing parent's first task item.
258-
///
259-
/// This item type is used to link to the appropriate parent task's item,
260-
/// when the current task itself does not have any task local values itself.
261-
///
262-
/// When a task actually has its own task locals, it should rather point
263-
/// to the parent's *first* task-local item in its *last* item, extending
264-
/// the TaskLocalItem linked list into the appropriate parent.
265-
static TaskLocalItem* createParentLink(AsyncTask *task, AsyncTask *parent) {
266-
assert(parent);
267-
size_t amountToAllocate = TaskLocalItem::itemSize(/*valueType*/nullptr);
268-
// assert(amountToAllocate % MaximumAlignment == 0); // TODO: do we need this?
269-
void *allocation = malloc(amountToAllocate); // TODO: use task-local allocator
270-
271-
TaskLocalItem *item =
272-
new(allocation) TaskLocalItem(nullptr, nullptr);
273-
274-
auto parentHead = parent->localValuesFragment()->head;
275-
if (parentHead) {
276-
if (parentHead->isEmpty()) {
277-
switch (parentHead->getNextLinkType()) {
278-
case NextLinkType::IsParent:
279-
// it has no values, and just points to its parent,
280-
// therefore skip also skip pointing to that parent and point
281-
// to whichever parent it was pointing to as well, it may be its
282-
// immediate parent, or some super-parent.
283-
item->next = reinterpret_cast<uintptr_t>(parentHead->getNext()) |
284-
static_cast<uintptr_t>(NextLinkType::IsParent);
285-
break;
286-
case NextLinkType::IsNext:
287-
assert(false && "empty taskValue head in parent task, yet parent's 'head' is `IsNext`, "
288-
"this should not happen, as it implies the parent must have stored some value.");
289-
break;
290-
case NextLinkType::IsTerminal:
291-
item->next = reinterpret_cast<uintptr_t>(parentHead->getNext()) |
292-
static_cast<uintptr_t>(NextLinkType::IsTerminal);
293-
break;
294-
}
295-
} else {
296-
item->next = reinterpret_cast<uintptr_t>(parentHead) |
297-
static_cast<uintptr_t>(NextLinkType::IsParent);
298-
}
299-
} else {
300-
item->next = reinterpret_cast<uintptr_t>(parentHead) |
301-
static_cast<uintptr_t>(NextLinkType::IsTerminal);
302-
}
303-
304-
return item;
305-
}
306-
307-
static TaskLocalItem* createLink(AsyncTask *task,
308-
const Metadata *keyType,
309-
const Metadata *valueType) {
310-
assert(task);
311-
size_t amountToAllocate = TaskLocalItem::itemSize(valueType);
312-
// assert(amountToAllocate % MaximumAlignment == 0); // TODO: do we need this?
313-
void *allocation = malloc(amountToAllocate); // TODO: use task-local allocator rdar://74218679
314-
TaskLocalItem *item =
315-
new(allocation) TaskLocalItem(keyType, valueType);
316-
317-
auto next = task->localValuesFragment()->head;
318-
auto nextLinkType = next ? NextLinkType::IsNext : NextLinkType::IsTerminal;
319-
item->next = reinterpret_cast<uintptr_t>(next) |
320-
static_cast<uintptr_t>(nextLinkType);
321-
322-
return item;
323-
}
324-
325-
void destroy() {
326-
if (valueType) {
327-
valueType->vw_destroy(getStoragePtr());
328-
}
329-
}
330-
331-
TaskLocalItem *getNext() {
332-
return reinterpret_cast<TaskLocalItem *>(next & ~statusMask);
333-
}
334-
335-
NextLinkType getNextLinkType() {
336-
return static_cast<NextLinkType>(next & statusMask);
337-
}
338-
339-
/// Item does not contain any actual value, and is only used to point at
340-
/// a specific parent item.
341-
bool isEmpty() {
342-
return !valueType;
343-
}
344-
345-
/// Retrieve a pointer to the storage of the value.
346-
OpaqueValue *getStoragePtr() {
347-
return reinterpret_cast<OpaqueValue *>(
348-
reinterpret_cast<char *>(this) + storageOffset(valueType));
349-
}
350-
351-
/// Compute the offset of the storage from the base of the item.
352-
static size_t storageOffset(const Metadata *valueType) {
353-
size_t offset = sizeof(TaskLocalItem);
354-
if (valueType) {
355-
size_t alignment = valueType->vw_alignment();
356-
return (offset + alignment - 1) & ~(alignment - 1);
357-
} else {
358-
return offset;
359-
}
360-
}
202+
// ==== Task Local Values ----------------------------------------------------
361203

362-
/// Determine the size of the item given a particular value type.
363-
static size_t itemSize(const Metadata *valueType) {
364-
size_t offset = storageOffset(valueType);
365-
if (valueType) {
366-
offset += valueType->vw_size();
367-
}
368-
return offset;
369-
}
370-
};
371-
372-
private:
373-
/// A stack (single-linked list) of task local values.
374-
///
375-
/// Once task local values within this task are traversed, the list continues
376-
/// to the "next parent that contributes task local values," or if no such
377-
/// parent exists it terminates with null.
378-
///
379-
/// If the TaskLocalValuesFragment was allocated, it is expected that this
380-
/// value should be NOT null; it either has own values, or at least one
381-
/// parent that has values. If this task does not have any values, the head
382-
/// pointer MAY immediately point at this task's parent task which has values.
383-
///
384-
/// ### Concurrency
385-
/// Access to the head is only performed from the task itself, when it
386-
/// creates child tasks, the child during creation will inspect its parent's
387-
/// task local value stack head, and point to it. This is done on the calling
388-
/// task, and thus needs not to be synchronized. Subsequent traversal is
389-
/// performed by child tasks concurrently, however they use their own
390-
/// pointers/stack and can never mutate the parent's stack.
391-
///
392-
/// The stack is only pushed/popped by the owning task, at the beginning and
393-
/// end a `body` block of `withLocal(_:boundTo:body:)` respectively.
394-
///
395-
/// Correctness of the stack strongly relies on the guarantee that tasks
396-
/// never outline a scope in which they are created. Thanks to this, if
397-
/// tasks are created inside the `body` of `withLocal(_:,boundTo:body:)`
398-
/// all tasks created inside the `withLocal` body must complete before it
399-
/// returns, as such, any child tasks potentially accessing the value stack
400-
/// are guaranteed to be completed by the time we pop values off the stack
401-
/// (after the body has completed).
402-
TaskLocalItem *head = nullptr;
403-
404-
public:
405-
TaskLocalValuesFragment() {}
406-
407-
void destroy();
408-
409-
/// If the parent task has task local values defined, point to in
410-
/// the task local values chain.
411-
void initializeLinkParent(AsyncTask* task, AsyncTask* parent);
412-
413-
void pushValue(AsyncTask *task, const Metadata *keyType,
414-
/* +1 */ OpaqueValue *value, const Metadata *valueType);
415-
416-
void popValue(AsyncTask *task);
417-
418-
OpaqueValue* get(const Metadata *keType, TaskLocalInheritance inheritance);
419-
};
204+
void localValueInitializeLinkParent(AsyncTask *parent) {
205+
Local.initializeLinkParent(this, parent);
206+
}
420207

421-
TaskLocalValuesFragment *localValuesFragment() {
422-
auto offset = reinterpret_cast<char*>(this);
423-
offset += sizeof(AsyncTask);
424-
return reinterpret_cast<TaskLocalValuesFragment*>(offset);
208+
void localValuePush(const Metadata *keyType,
209+
/* +1 */ OpaqueValue *value, const Metadata *valueType) {
210+
Local.pushValue(this, keyType, value, valueType);
425211
}
426212

427213
OpaqueValue* localValueGet(const Metadata *keyType,
428-
TaskLocalValuesFragment::TaskLocalInheritance inheritance) {
429-
return localValuesFragment()->get(keyType, inheritance);
214+
TaskLocal::TaskLocalInheritance inherit) {
215+
return Local.getValue(this, keyType, inherit);
216+
}
217+
218+
void localValuePop() {
219+
Local.popValue(this);
430220
}
431221

432222
// ==== Child Fragment -------------------------------------------------------
@@ -476,7 +266,6 @@ class AsyncTask : public HeapObject, public Job {
476266

477267
auto offset = reinterpret_cast<char*>(this);
478268
offset += sizeof(AsyncTask);
479-
offset += sizeof(TaskLocalValuesFragment);
480269

481270
return reinterpret_cast<ChildFragment*>(offset);
482271
}
@@ -516,7 +305,6 @@ class AsyncTask : public HeapObject, public Job {
516305

517306
auto offset = reinterpret_cast<char*>(this);
518307
offset += sizeof(AsyncTask);
519-
offset += sizeof(TaskLocalValuesFragment);
520308
if (hasChildFragment())
521309
offset += sizeof(ChildFragment);
522310

@@ -625,7 +413,6 @@ class AsyncTask : public HeapObject, public Job {
625413
assert(isFuture());
626414
auto offset = reinterpret_cast<char*>(this);
627415
offset += sizeof(AsyncTask);
628-
offset += sizeof(TaskLocalValuesFragment);
629416
if (hasChildFragment())
630417
offset += sizeof(ChildFragment);
631418
if (hasGroupChildFragment())
@@ -665,7 +452,7 @@ class AsyncTask : public HeapObject, public Job {
665452
};
666453

667454
// The compiler will eventually assume these.
668-
static_assert(sizeof(AsyncTask) == 12 * sizeof(void*),
455+
static_assert(sizeof(AsyncTask) == 14 * sizeof(void*),
669456
"AsyncTask size is wrong");
670457
static_assert(alignof(AsyncTask) == 2 * alignof(void*),
671458
"AsyncTask alignment is wrong");

include/swift/ABI/TaskGroup.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,10 @@
1818
#define SWIFT_ABI_TASK_GROUP_H
1919

2020
#include "swift/ABI/Task.h"
21-
#include "swift/Runtime/Concurrency.h"
22-
#include "swift/Basic/RelativePointer.h"
2321
#include "swift/ABI/HeapObject.h"
22+
#include "swift/Runtime/Concurrency.h"
2423
#include "swift/Runtime/Config.h"
24+
#include "swift/Basic/RelativePointer.h"
2525
#include "swift/Basic/STLExtras.h"
2626
#include "bitset"
2727
#include "string"

0 commit comments

Comments
 (0)