@@ -139,13 +139,13 @@ class ActiveTaskStatus {
139
139
// /
140
140
// / +--------------------------+
141
141
// / | childFragment? |
142
- // / | taskLocalValuesFragment |
142
+ // / | taskLocalValuesFragment? |
143
143
// / | groupFragment? |
144
144
// / | futureFragment? |*
145
145
// / +--------------------------+
146
146
// /
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.
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.
149
149
class AsyncTask : public HeapObject , public Job {
150
150
public:
151
151
// / The context for resuming the job. When a task is scheduled
@@ -217,7 +217,7 @@ class AsyncTask : public HeapObject, public Job {
217
217
return reinterpret_cast <ChildFragment*>(this + 1 );
218
218
}
219
219
220
- // ==== Task Locals Values-- -------------------------------------------------
220
+ // ==== Task Locals Values -- -------------------------------------------------
221
221
222
222
class TaskLocalValuesFragment {
223
223
public:
@@ -230,8 +230,14 @@ class AsyncTask : public HeapObject, public Job {
230
230
IsTerminal = 0b00 ,
231
231
// / The storage pointer points at the next TaskLocalChainItem in this task.
232
232
IsNext = 0b01 ,
233
- // / The storage pointer points at a parent AsyncTask,
234
- // / in which we should continue the lookup.
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`.
235
241
IsParent = 0b11
236
242
};
237
243
@@ -272,23 +278,42 @@ class AsyncTask : public HeapObject, public Job {
272
278
// / the TaskLocalItem linked list into the appropriate parent.
273
279
static TaskLocalItem* createParentLink (AsyncTask *task, AsyncTask *parent) {
274
280
assert (parent);
275
- assert (parent->hasTaskLocalValues ());
276
- assert (task->hasTaskLocalValues ());
277
281
size_t amountToAllocate = TaskLocalItem::itemSize (/* valueType*/ nullptr );
278
282
// assert(amountToAllocate % MaximumAlignment == 0); // TODO: do we need this?
279
283
void *allocation = malloc (amountToAllocate); // TODO: use task-local allocator
280
- fprintf (stderr, " MALLOC parent link item: %d\n " , allocation);
281
284
282
285
TaskLocalItem *item =
283
286
new (allocation) TaskLocalItem (nullptr , nullptr );
284
287
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 ());
288
+ auto parentHead = parent->localValuesFragment ()->head ;
289
+ if (parentHead) {
290
+ if (parentHead->isEmpty ()) {
291
+ switch (parentHead->getNextLinkType ()) {
292
+ case NextLinkType::IsParent:
293
+ // it has no values, and just points to its parent,
294
+ // therefore skip also skip pointing to that parent and point
295
+ // to whichever parent it was pointing to as well, it may be its
296
+ // immediate parent, or some super-parent.
297
+ item->next = reinterpret_cast <uintptr_t >(parentHead->getNext ());
298
+ static_cast <uintptr_t >(NextLinkType::IsParent);
299
+ break ;
300
+ case NextLinkType::IsNext:
301
+ assert (false && " empty taskValue head in parent task, yet parent's 'head' is `IsNext`, "
302
+ " this should not happen, as it implies the parent must have stored some value." );
303
+ break ;
304
+ case NextLinkType::IsTerminal:
305
+ item->next = reinterpret_cast <uintptr_t >(parentHead->getNext ());
306
+ static_cast <uintptr_t >(NextLinkType::IsTerminal);
307
+ break ;
308
+ }
309
+ } else {
310
+ item->next = reinterpret_cast <uintptr_t >(parentHead) |
311
+ static_cast <uintptr_t >(NextLinkType::IsParent);
312
+ }
313
+ } else {
314
+ item->next = reinterpret_cast <uintptr_t >(parentHead) |
315
+ static_cast <uintptr_t >(NextLinkType::IsTerminal);
316
+ }
292
317
293
318
return item;
294
319
}
@@ -297,7 +322,6 @@ class AsyncTask : public HeapObject, public Job {
297
322
const Metadata *keyType,
298
323
const Metadata *valueType) {
299
324
assert (task);
300
- assert (task->hasTaskLocalValues ());
301
325
size_t amountToAllocate = TaskLocalItem::itemSize (valueType);
302
326
// assert(amountToAllocate % MaximumAlignment == 0); // TODO: do we need this?
303
327
void *allocation = malloc (amountToAllocate); // TODO: use task-local allocator
@@ -327,9 +351,14 @@ class AsyncTask : public HeapObject, public Job {
327
351
return static_cast <NextLinkType>(next & statusMask);
328
352
}
329
353
354
+ // / Item does not contain any actual value, and is only used to point at
355
+ // / a specific parent item.
356
+ bool isEmpty () {
357
+ return !valueType;
358
+ }
359
+
330
360
// / Retrieve a pointer to the storage of the value.
331
361
OpaqueValue *getStoragePtr () {
332
- // assert(valueType && "valueType must be set before accessing storage pointer.");
333
362
return reinterpret_cast <OpaqueValue *>(
334
363
reinterpret_cast <char *>(this ) + storageOffset (valueType));
335
364
}
@@ -356,7 +385,8 @@ class AsyncTask : public HeapObject, public Job {
356
385
};
357
386
358
387
private:
359
- // / Single-linked list of task local values.
388
+ // / A stack (single-linked list) of task local values.
389
+ // /
360
390
// / Once task local values within this task are traversed, the list continues
361
391
// / to the "next parent that contributes task local values," or if no such
362
392
// / parent exists it terminates with null.
@@ -366,11 +396,28 @@ class AsyncTask : public HeapObject, public Job {
366
396
// / parent that has values. If this task does not have any values, the head
367
397
// / pointer MAY immediately point at this task's parent task which has values.
368
398
// /
369
- // / NOTE: Check the higher bits to know if this is a self or parent value.
399
+ // / ### Concurrency
400
+ // / Access to the head is only performed from the task itself, when it
401
+ // / creates child tasks, the child during creation will inspect its parent's
402
+ // / task local value stack head, and point to it. This is done on the calling
403
+ // / task, and thus needs not to be synchronized. Subsequent traversal is
404
+ // / performed by child tasks concurrently, however they use their own
405
+ // / pointers/stack and can never mutate the parent's stack.
406
+ // /
407
+ // / The stack is only pushed/popped by the owning task, at the beginning and
408
+ // / end a `body` block of `withLocal(_:boundTo:body:)` respectively.
409
+ // /
410
+ // / Correctness of the stack strongly relies on the guarantee that tasks
411
+ // / never outline a scope in which they are created. Thanks to this, if
412
+ // / tasks are created inside the `body` of `withLocal(_:,boundTo:body:)`
413
+ // / all tasks created inside the `withLocal` body must complete before it
414
+ // / returns, as such, any child tasks potentially accessing the value stack
415
+ // / are guaranteed to be completed by the time we pop values off the stack
416
+ // / (after the body has completed).
370
417
TaskLocalItem *head = nullptr ;
371
418
372
419
public:
373
- TaskLocalValuesFragment () {}
420
+ TaskLocalValuesFragment () {}
374
421
375
422
void destroy ();
376
423
@@ -386,13 +433,7 @@ class AsyncTask : public HeapObject, public Job {
386
433
OpaqueValue* get (const Metadata *keyType);
387
434
};
388
435
389
- bool hasTaskLocalValues () const {
390
- return Flags.task_hasLocalValues ();
391
- }
392
-
393
436
TaskLocalValuesFragment *localValuesFragment () {
394
- assert (hasTaskLocalValues ());
395
-
396
437
auto offset = reinterpret_cast <char *>(this );
397
438
offset += sizeof (AsyncTask);
398
439
@@ -404,14 +445,7 @@ class AsyncTask : public HeapObject, public Job {
404
445
}
405
446
406
447
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
- }
448
+ return localValuesFragment ()->get (keyType);
415
449
}
416
450
417
451
// ==== TaskGroup ------------------------------------------------------------
@@ -724,9 +758,7 @@ class AsyncTask : public HeapObject, public Job {
724
758
offset += sizeof (ChildFragment);
725
759
}
726
760
727
- if (hasTaskLocalValues ()) {
728
- offset += sizeof (TaskLocalValuesFragment);
729
- }
761
+ offset += sizeof (TaskLocalValuesFragment);
730
762
731
763
return reinterpret_cast <GroupFragment *>(offset);
732
764
}
@@ -861,9 +893,7 @@ class AsyncTask : public HeapObject, public Job {
861
893
offset += sizeof (ChildFragment);
862
894
}
863
895
864
- if (hasTaskLocalValues ()) {
865
- offset += sizeof (TaskLocalValuesFragment);
866
- }
896
+ offset += sizeof (TaskLocalValuesFragment);
867
897
868
898
if (isTaskGroup ()) {
869
899
offset += sizeof (GroupFragment);
0 commit comments