@@ -137,14 +137,15 @@ class ActiveTaskStatus {
137
137
// / ### Fragments
138
138
// / An AsyncTask may have the following fragments:
139
139
// /
140
- // / +------------------+
141
- // / | childFragment? |
142
- // / | groupFragment? |
143
- // / | futureFragment? |*
144
- // / +------------------+
140
+ // / +--------------------------+
141
+ // / | childFragment? |
142
+ // / | taskLocalValuesFragment? |
143
+ // / | groupFragment? |
144
+ // / | futureFragment? |*
145
+ // / +--------------------------+
145
146
// /
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.
148
149
class AsyncTask : public HeapObject , public Job {
149
150
public:
150
151
// / The context for resuming the job. When a task is scheduled
@@ -175,13 +176,15 @@ class AsyncTask : public HeapObject, public Job {
175
176
void run (ExecutorRef currentExecutor) {
176
177
ResumeTask (this , currentExecutor, ResumeContext);
177
178
}
178
-
179
+
179
180
// / Check whether this task has been cancelled.
180
181
// / Checking this is, of course, inherently race-prone on its own.
181
182
bool isCancelled () const {
182
183
return Status.load (std::memory_order_relaxed).isCancelled ();
183
184
}
184
185
186
+ // ==== Child Fragment -------------------------------------------------------
187
+
185
188
// / A fragment of an async task structure that happens to be a child task.
186
189
class ChildFragment {
187
190
// / The parent task of this task.
@@ -205,14 +208,252 @@ class AsyncTask : public HeapObject, public Job {
205
208
}
206
209
};
207
210
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
+ }
210
214
211
215
ChildFragment *childFragment () {
212
216
assert (hasChildFragment ());
213
217
return reinterpret_cast <ChildFragment*>(this + 1 );
214
218
}
215
219
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
+
216
457
// ==== TaskGroup ------------------------------------------------------------
217
458
218
459
class GroupFragment {
@@ -516,12 +757,16 @@ class AsyncTask : public HeapObject, public Job {
516
757
GroupFragment *groupFragment () {
517
758
assert (isTaskGroup ());
518
759
760
+ auto offset = reinterpret_cast <char *>(this );
761
+ offset += sizeof (AsyncTask);
762
+
519
763
if (hasChildFragment ()) {
520
- return reinterpret_cast <GroupFragment *>(
521
- reinterpret_cast <ChildFragment*>(this + 1 ) + 1 );
764
+ offset += sizeof (ChildFragment);
522
765
}
523
766
524
- return reinterpret_cast <GroupFragment *>(this + 1 );
767
+ offset += sizeof (TaskLocalValuesFragment);
768
+
769
+ return reinterpret_cast <GroupFragment *>(offset);
525
770
}
526
771
527
772
// / Offer result of a task into this channel.
@@ -647,13 +892,15 @@ class AsyncTask : public HeapObject, public Job {
647
892
FutureFragment *futureFragment () {
648
893
assert (isFuture ());
649
894
650
- auto offset = reinterpret_cast <uintptr_t >(this ); // TODO: char* instead?
895
+ auto offset = reinterpret_cast <char * >(this );
651
896
offset += sizeof (AsyncTask);
652
897
653
898
if (hasChildFragment ()) {
654
899
offset += sizeof (ChildFragment);
655
900
}
656
901
902
+ offset += sizeof (TaskLocalValuesFragment);
903
+
657
904
if (isTaskGroup ()) {
658
905
offset += sizeof (GroupFragment);
659
906
}
0 commit comments