@@ -137,14 +137,15 @@ 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// /
146- // / The future fragment is dynamic in size, based on the future result type
147- // / it can hold, and thus must be the *last* fragment.
147+ // / * The future fragment is dynamic in size, based on the future result type
148+ // / it can hold, and thus must be the *last* fragment.
148149class AsyncTask : public HeapObject , public Job {
149150public:
150151 // / The context for resuming the job. When a task is scheduled
@@ -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,252 @@ 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, in which we should
234+ // / continue the lookup.
235+ // /
236+ // / Note that this may not necessarily be the same as the task's parent
237+ // / task -- we may point to a super-parent if we know / that the parent
238+ // / does not "contribute" any task local values. This is to speed up
239+ // / lookups by skipping empty parent tasks during get(), and explained
240+ // / in depth in `createParentLink`.
241+ IsParent = 0b11
242+ };
243+
244+ // / Values must match `TaskLocalInheritance` declared in `TaskLocal.swift`.
245+ enum class TaskLocalInheritance : uint8_t {
246+ Default = 0 ,
247+ Never = 1
248+ };
249+
250+ class TaskLocalItem {
251+ private:
252+ // / Mask used for the low status bits in a task local chain item.
253+ static const uintptr_t statusMask = 0x03 ;
254+
255+ // / Pointer to the next task local item; be it in this task or in a parent.
256+ // / Low bits encode `NextLinkType`.
257+ // / TaskLocalItem *next = nullptr;
258+ uintptr_t next;
259+
260+ public:
261+ // / The type of the key with which this value is associated.
262+ const Metadata *keyType;
263+ // / The type of the value stored by this item.
264+ const Metadata *valueType;
265+
266+ // Trailing storage for the value itself. The storage will be
267+ // uninitialized or contain an instance of \c valueType.
268+
269+ private:
270+ explicit TaskLocalItem (const Metadata *keyType, const Metadata *valueType)
271+ : keyType(keyType),
272+ valueType(valueType),
273+ next(0 ) { }
274+
275+ public:
276+ // / TaskLocalItem which does not by itself store any value, but only points
277+ // / to the nearest task-local-value containing parent's first task item.
278+ // /
279+ // / This item type is used to link to the appropriate parent task's item,
280+ // / when the current task itself does not have any task local values itself.
281+ // /
282+ // / When a task actually has its own task locals, it should rather point
283+ // / to the parent's *first* task-local item in its *last* item, extending
284+ // / the TaskLocalItem linked list into the appropriate parent.
285+ static TaskLocalItem* createParentLink (AsyncTask *task, AsyncTask *parent) {
286+ assert (parent);
287+ size_t amountToAllocate = TaskLocalItem::itemSize (/* valueType*/ nullptr );
288+ // assert(amountToAllocate % MaximumAlignment == 0); // TODO: do we need this?
289+ void *allocation = malloc (amountToAllocate); // TODO: use task-local allocator
290+
291+ TaskLocalItem *item =
292+ new (allocation) TaskLocalItem (nullptr , nullptr );
293+
294+ auto parentHead = parent->localValuesFragment ()->head ;
295+ if (parentHead) {
296+ if (parentHead->isEmpty ()) {
297+ switch (parentHead->getNextLinkType ()) {
298+ case NextLinkType::IsParent:
299+ // it has no values, and just points to its parent,
300+ // therefore skip also skip pointing to that parent and point
301+ // to whichever parent it was pointing to as well, it may be its
302+ // immediate parent, or some super-parent.
303+ item->next = reinterpret_cast <uintptr_t >(parentHead->getNext ());
304+ static_cast <uintptr_t >(NextLinkType::IsParent);
305+ break ;
306+ case NextLinkType::IsNext:
307+ assert (false && " empty taskValue head in parent task, yet parent's 'head' is `IsNext`, "
308+ " this should not happen, as it implies the parent must have stored some value." );
309+ break ;
310+ case NextLinkType::IsTerminal:
311+ item->next = reinterpret_cast <uintptr_t >(parentHead->getNext ());
312+ static_cast <uintptr_t >(NextLinkType::IsTerminal);
313+ break ;
314+ }
315+ } else {
316+ item->next = reinterpret_cast <uintptr_t >(parentHead) |
317+ static_cast <uintptr_t >(NextLinkType::IsParent);
318+ }
319+ } else {
320+ item->next = reinterpret_cast <uintptr_t >(parentHead) |
321+ static_cast <uintptr_t >(NextLinkType::IsTerminal);
322+ }
323+
324+ return item;
325+ }
326+
327+ static TaskLocalItem* createLink (AsyncTask *task,
328+ const Metadata *keyType,
329+ const Metadata *valueType) {
330+ assert (task);
331+ size_t amountToAllocate = TaskLocalItem::itemSize (valueType);
332+ // assert(amountToAllocate % MaximumAlignment == 0); // TODO: do we need this?
333+ void *allocation = malloc (amountToAllocate); // TODO: use task-local allocator
334+ TaskLocalItem *item =
335+ new (allocation) TaskLocalItem (keyType, valueType);
336+
337+ auto next = task->localValuesFragment ()->head ;
338+ auto nextLinkType = next ? NextLinkType::IsNext : NextLinkType::IsTerminal;
339+ item->next = reinterpret_cast <uintptr_t >(next) |
340+ static_cast <uintptr_t >(nextLinkType);
341+
342+ return item;
343+ }
344+
345+ void destroy () {
346+ if (valueType) {
347+ valueType->vw_destroy (getStoragePtr ());
348+ }
349+ }
350+
351+ TaskLocalItem *getNext () {
352+ return reinterpret_cast <TaskLocalItem *>(next & ~statusMask);
353+ }
354+
355+ NextLinkType getNextLinkType () {
356+ return static_cast <NextLinkType>(next & statusMask);
357+ }
358+
359+ // / Item does not contain any actual value, and is only used to point at
360+ // / a specific parent item.
361+ bool isEmpty () {
362+ return !valueType;
363+ }
364+
365+ // / Retrieve a pointer to the storage of the value.
366+ OpaqueValue *getStoragePtr () {
367+ return reinterpret_cast <OpaqueValue *>(
368+ reinterpret_cast <char *>(this ) + storageOffset (valueType));
369+ }
370+
371+ // / Compute the offset of the storage from the base of the item.
372+ static size_t storageOffset (const Metadata *valueType) {
373+ size_t offset = sizeof (TaskLocalItem);
374+ if (valueType) {
375+ size_t alignment = valueType->vw_alignment ();
376+ return (offset + alignment - 1 ) & ~(alignment - 1 );
377+ } else {
378+ return offset;
379+ }
380+ }
381+
382+ // / Determine the size of the item given a particular value type.
383+ static size_t itemSize (const Metadata *valueType) {
384+ size_t offset = storageOffset (valueType);
385+ if (valueType) {
386+ offset += valueType->vw_size ();
387+ }
388+ return offset;
389+ }
390+ };
391+
392+ private:
393+ // / A stack (single-linked list) of task local values.
394+ // /
395+ // / Once task local values within this task are traversed, the list continues
396+ // / to the "next parent that contributes task local values," or if no such
397+ // / parent exists it terminates with null.
398+ // /
399+ // / If the TaskLocalValuesFragment was allocated, it is expected that this
400+ // / value should be NOT null; it either has own values, or at least one
401+ // / parent that has values. If this task does not have any values, the head
402+ // / pointer MAY immediately point at this task's parent task which has values.
403+ // /
404+ // / ### Concurrency
405+ // / Access to the head is only performed from the task itself, when it
406+ // / creates child tasks, the child during creation will inspect its parent's
407+ // / task local value stack head, and point to it. This is done on the calling
408+ // / task, and thus needs not to be synchronized. Subsequent traversal is
409+ // / performed by child tasks concurrently, however they use their own
410+ // / pointers/stack and can never mutate the parent's stack.
411+ // /
412+ // / The stack is only pushed/popped by the owning task, at the beginning and
413+ // / end a `body` block of `withLocal(_:boundTo:body:)` respectively.
414+ // /
415+ // / Correctness of the stack strongly relies on the guarantee that tasks
416+ // / never outline a scope in which they are created. Thanks to this, if
417+ // / tasks are created inside the `body` of `withLocal(_:,boundTo:body:)`
418+ // / all tasks created inside the `withLocal` body must complete before it
419+ // / returns, as such, any child tasks potentially accessing the value stack
420+ // / are guaranteed to be completed by the time we pop values off the stack
421+ // / (after the body has completed).
422+ TaskLocalItem *head = nullptr ;
423+
424+ public:
425+ TaskLocalValuesFragment () {}
426+
427+ void destroy ();
428+
429+ // / If the parent task has task local values defined, point to in
430+ // / the task local values chain.
431+ void initializeLinkParent (AsyncTask* task, AsyncTask* parent);
432+
433+ void pushValue (AsyncTask *task, const Metadata *keyType,
434+ /* +1 */ OpaqueValue *value, const Metadata *valueType);
435+
436+ void popValue (AsyncTask *task);
437+
438+ OpaqueValue* get (const Metadata *keType, TaskLocalInheritance inheritance);
439+ };
440+
441+ TaskLocalValuesFragment *localValuesFragment () {
442+ auto offset = reinterpret_cast <char *>(this );
443+ offset += sizeof (AsyncTask);
444+
445+ if (hasChildFragment ()) {
446+ offset += sizeof (ChildFragment);
447+ }
448+
449+ return reinterpret_cast <TaskLocalValuesFragment*>(offset);
450+ }
451+
452+ OpaqueValue* localValueGet (const Metadata *keyType,
453+ TaskLocalValuesFragment::TaskLocalInheritance inheritance) {
454+ return localValuesFragment ()->get (keyType, inheritance);
455+ }
456+
216457 // ==== TaskGroup ------------------------------------------------------------
217458
218459 class GroupFragment {
@@ -516,12 +757,16 @@ class AsyncTask : public HeapObject, public Job {
516757 GroupFragment *groupFragment () {
517758 assert (isTaskGroup ());
518759
760+ auto offset = reinterpret_cast <char *>(this );
761+ offset += sizeof (AsyncTask);
762+
519763 if (hasChildFragment ()) {
520- return reinterpret_cast <GroupFragment *>(
521- reinterpret_cast <ChildFragment*>(this + 1 ) + 1 );
764+ offset += sizeof (ChildFragment);
522765 }
523766
524- return reinterpret_cast <GroupFragment *>(this + 1 );
767+ offset += sizeof (TaskLocalValuesFragment);
768+
769+ return reinterpret_cast <GroupFragment *>(offset);
525770 }
526771
527772 // / Offer result of a task into this channel.
@@ -647,13 +892,15 @@ class AsyncTask : public HeapObject, public Job {
647892 FutureFragment *futureFragment () {
648893 assert (isFuture ());
649894
650- auto offset = reinterpret_cast <uintptr_t >(this ); // TODO: char* instead?
895+ auto offset = reinterpret_cast <char * >(this );
651896 offset += sizeof (AsyncTask);
652897
653898 if (hasChildFragment ()) {
654899 offset += sizeof (ChildFragment);
655900 }
656901
902+ offset += sizeof (TaskLocalValuesFragment);
903+
657904 if (isTaskGroup ()) {
658905 offset += sizeof (GroupFragment);
659906 }
0 commit comments