From 2acf5b6b6ba0744e769756d42cce99006c6705d6 Mon Sep 17 00:00:00 2001 From: Mykola Pokhylets Date: Tue, 8 Oct 2024 17:01:03 +0200 Subject: [PATCH] Refactoring of the TaskLocal::Item Replace NextLinkType with Item::Kind with more clear definition. Use llvm::PointerIntPair<>. Use hierarchy of classes instead of optional fields inside Item. Combined Storage::copyToOnlyOnlyFromCurrentGroup() into Storage::initializeLinkParent(). Also create parent task marker when copying items created inside withTaskGroup(). Removed Storage::peekHeadLinkType(). --- stdlib/public/Concurrency/Task.cpp | 47 +--- stdlib/public/Concurrency/TaskLocal.cpp | 282 ++++++++++-------------- stdlib/public/Concurrency/TaskLocal.h | 212 +++++++----------- 3 files changed, 206 insertions(+), 335 deletions(-) diff --git a/stdlib/public/Concurrency/Task.cpp b/stdlib/public/Concurrency/Task.cpp index ea8a15c466012..0e66621578913 100644 --- a/stdlib/public/Concurrency/Task.cpp +++ b/stdlib/public/Concurrency/Task.cpp @@ -1027,48 +1027,25 @@ swift_task_create_commonImpl(size_t rawTaskCreateFlags, if ((group && group->isCancelled()) || swift_task_isCancelled(parent)) swift_task_cancel(task); - // Initialize task locals storage - bool taskLocalStorageInitialized = false; - // Inside a task group, we may have to perform some defensive copying, // check if doing so is necessary, and initialize storage using partial // defensive copies if necessary. if (group) { assert(parent && "a task created in a group must be a child task"); - // We are a child task in a task group; and it may happen that we are calling - // addTask specifically in such shape: - // - // $local.withValue(theValue) { addTask {} } - // - // If this is the case, we MUST copy `theValue` (and any other such directly - // wrapping the addTask value bindings), because those values will be popped - // when withValue returns - breaking our structured concurrency guarantees - // that we rely on for the "link directly to parent's task local Item". - // - // Values set outside the task group are not subject to this problem, as - // their structural lifetime guarantee is upheld by the group scope - // out-living any addTask created tasks. - auto ParentLocal = parent->_private().Local; - // If we were going to copy ALL values anyway, we don't need to - // perform this defensive partial copying. In practice, we currently - // do not have child tasks which force copying, but we could. - assert(!taskCreateFlags.copyTaskLocals() && - "Currently we don't have child tasks which force copying task " - "locals; unexpected attempt to combine the two!"); - - if (auto taskLocalHeadLinkType = ParentLocal.peekHeadLinkType()) { - if (taskLocalHeadLinkType == - swift::TaskLocal::NextLinkType::IsNextCreatedInTaskGroupBody) { - ParentLocal.copyToOnlyOnlyFromCurrentGroup(task); - taskLocalStorageInitialized = true; - } - } } - if (!taskLocalStorageInitialized) { - // just initialize the storage normally - task->_private().Local.initializeLinkParent(task, parent); - } + // Initialize task locals with a link to the parent task. + // + // Inside a task group, we may have to perform some defensive copying, + // and initialize storage using partial defensive copies if necessary. + // + // If we were going to copy ALL values anyway, we don't need to + // perform this defensive partial copying. In practice, we currently + // do not have child tasks which force copying, but we could. + assert(!taskCreateFlags.copyTaskLocals() && + "Currently we don't have child tasks which force copying task " + "locals; unexpected attempt to combine the two!"); + task->_private().Local.initializeLinkParent(task, parent); } // Configure the initial context. diff --git a/stdlib/public/Concurrency/TaskLocal.cpp b/stdlib/public/Concurrency/TaskLocal.cpp index f1fc2be0642c7..de7dfa00067b0 100644 --- a/stdlib/public/Concurrency/TaskLocal.cpp +++ b/stdlib/public/Concurrency/TaskLocal.cpp @@ -171,106 +171,107 @@ TaskLocal::Storage::getCurrent(AsyncTask *current) { void TaskLocal::Storage::initializeLinkParent(AsyncTask* task, AsyncTask* parent) { + assert(task && "task must not be null when copying values into it"); assert(!head && "initial task local storage was already initialized"); assert(parent && "parent must be provided to link to it"); - head = TaskLocal::Item::createParentLink(task, parent); -} -TaskLocal::Item* -TaskLocal::Item::createParentLink(AsyncTask *task, AsyncTask *parent) { - size_t amountToAllocate = Item::itemSize(/*valueType*/nullptr); - void *allocation = _swift_task_alloc_specific(task, amountToAllocate); - Item *item = new(allocation) Item(); - - auto parentHead = parent->_private().Local.head; - if (parentHead) { - if (parentHead->isEmpty()) { - switch (parentHead->getNextLinkType()) { - case NextLinkType::IsParent: - // it has no values, and just points to its parent, - // therefore skip also skip pointing to that parent and point - // to whichever parent it was pointing to as well, it may be its - // immediate parent, or some super-parent. - item->next = reinterpret_cast(parentHead->getNext()) | - static_cast(NextLinkType::IsParent); - break; - case NextLinkType::IsNext: - case NextLinkType::IsNextCreatedInTaskGroupBody: - if (parentHead->getNext()) { - assert(false && "empty taskValue head in parent task, yet parent's 'head' is `IsNext`, " - "this should not happen, as it implies the parent must have stored some value."); - } else { - // is terminal pointer - item->next = reinterpret_cast(parentHead->getNext()); - } - break; - } + auto item = parent->_private().Local.head; + + // Don't create parent task marker if there are no values at all. + if (!item) + return; + + auto tail = TaskLocal::ParentTaskMarkerItem::create(task); + head = tail; + + // Set of keys for which we already have copied to the new task. + // We only ever need to copy the *first* encounter of any given key, + // because it is the most "specific"/"recent" binding and any other binding + // of a key does not matter for the target task as it will never be able to + // observe it. + std::set, + swift::cxx_allocator> copied = {}; + + // If we are a child task in a task group, it may happen that we are calling + // addTask specifically in such shape: + // + // $local.withValue(theValue) { addTask {} } + // + // If this is the case, we MUST copy `theValue` (and any other such directly + // wrapping the addTask value bindings), because those values will be popped + // when withValue returns - breaking our structured concurrency guarantees + // that we rely on for the "link directly to parent's task local Item". + // + // Values set outside the task group are not subject to this problem, as + // their structural lifetime guarantee is upheld by the group scope + // out-living any addTask created tasks. + // + // TODO(concurrency): This can be optimized to copy only from the CURRENT + // group, but we need to detect this, e.g. by more flags in the items made + // from a group? + while (item && item->getKind() == Item::Kind::ValueInTaskGroupBody) { + auto valueItem = cast(item); + // we only have to copy an item if it is the most recent binding of a key. + // i.e. if we've already seen an item for this key, we can skip it. + if (copied.emplace(valueItem->key).second) { + valueItem->copyTo(task); } else { - item->next = reinterpret_cast(parentHead) | - static_cast(NextLinkType::IsParent); + SWIFT_TASK_LOCAL_DEBUG_LOG( + valueItem->key, + "skip copy, already copied most recent value, value was [%p]", + valueItem->getStoragePtr()); } - } else { - item->next = reinterpret_cast(parentHead); - } - - return item; -} -TaskLocal::Item* -TaskLocal::Item::createLink(AsyncTask *task, - const HeapObject *key, - const Metadata *valueType, - bool inTaskGroupBody) { - size_t amountToAllocate = Item::itemSize(valueType); - void *allocation = task ? _swift_task_alloc_specific(task, amountToAllocate) - : malloc(amountToAllocate); - Item *item = ::new (allocation) Item(key, valueType); + item = item->getNext(); + } - auto next = task ? task->_private().Local.head - : FallbackTaskLocalStorage::get()->head; - item->next = reinterpret_cast(next) | - static_cast( - inTaskGroupBody ? NextLinkType::IsNextCreatedInTaskGroupBody - : NextLinkType::IsNext); + if (item && item->getKind() == Item::Kind::ParentTaskMarker) { + // it has no values, and just points to its parent, + // therefore skip also skip pointing to that parent and point + // to whichever parent it was pointing to as well, it may be its + // immediate parent, or some super-parent. + item = item->getNext(); + } - return item; + // The next item is not the "risky one" so we can directly link to it, + // as we would have within normal child task relationships. E.g. this is + // a parent or next pointer to a "safe" (withValue { withTaskGroup { ... } }) + // binding, so we re-link our current head to point at this item. + tail->setNext(item); } -TaskLocal::Item* -TaskLocal::Item::createLink(AsyncTask *task, - const HeapObject *key, - const Metadata *valueType) { - return createLink(task, key, valueType, /*=inTaskGroupBody=*/false); +TaskLocal::ParentTaskMarkerItem * +TaskLocal::ParentTaskMarkerItem::create(AsyncTask *task) { + size_t amountToAllocate = sizeof(ParentTaskMarkerItem); + void *allocation = _swift_task_alloc_specific(task, amountToAllocate); + return new (allocation) ParentTaskMarkerItem(nullptr); } +TaskLocal::ValueItem *TaskLocal::ValueItem::create(AsyncTask *task, + const HeapObject *key, + const Metadata *valueType, + bool inTaskGroupBody) { + auto next = task ? task->_private().Local.head + : FallbackTaskLocalStorage::get()->head; -TaskLocal::Item* -TaskLocal::Item::createLinkInTaskGroup(AsyncTask *task, - const HeapObject *key, - const Metadata *valueType) { - return createLink(task, key, valueType, /*=inTaskGroupBody=*/true); + size_t amountToAllocate = ValueItem::itemSize(valueType); + void *allocation = task ? _swift_task_alloc_specific(task, amountToAllocate) + : malloc(amountToAllocate); + return ::new (allocation) ValueItem(next, key, valueType, inTaskGroupBody); } - -TaskLocal::Item* -TaskLocal::Item::copyTo(AsyncTask *target) { +void TaskLocal::ValueItem::copyTo(AsyncTask *target) { assert(target && "TaskLocal item attempt to copy to null target task!"); - // 'parent' pointers are signified by null valueType. - // We must not copy parent pointers, but rather perform a deep copy of all values, - // as such, we skip parent pointers here entirely. - if (isParentPointer()) - return nullptr; - - auto item = Item::createLink(target, key, valueType); + auto item = + ValueItem::create(target, key, valueType, /*inTaskGroupBody=*/false); valueType->vw_initializeWithCopy(item->getStoragePtr(), getStoragePtr()); /// A `copyTo` may ONLY be invoked BEFORE the task is actually scheduled, /// so right now we can safely copy the value into the task without additional /// synchronization. target->_private().Local.head = item; - - return item; } // ============================================================================= @@ -358,39 +359,39 @@ static void swift_task_reportIllegalTaskLocalBindingWithinWithTaskGroupImpl( // ============================================================================= // ==== destroy ---------------------------------------------------------------- -void TaskLocal::Item::destroy(AsyncTask *task) { - // otherwise it was task-local allocated, so we can safely destroy it right away - if (valueType) { - valueType->vw_destroy(getStoragePtr()); +bool TaskLocal::Item::destroy(AsyncTask *task) { + bool stop = false; + switch (getKind()) { + case Kind::Value: + case Kind::ValueInTaskGroupBody: + cast(this)->~ValueItem(); + break; + case Kind::ParentTaskMarker: + cast(this)->~ParentTaskMarkerItem(); + + // we're done here; as we must not proceed into the parent owned values. + // we do have to destroy the item pointing at the parent/edge itself though. + stop = true; + break; } // if task is available, we must have used the task allocator to allocate this item, // so we must deallocate it using the same. Otherwise, we must have used malloc. if (task) _swift_task_dealloc_specific(task, this); else free(this); + + return stop; } void TaskLocal::Storage::destroy(AsyncTask *task) { auto item = head; head = nullptr; - TaskLocal::Item *next; while (item) { - auto linkType = item->getNextLinkType(); - switch (linkType) { - case TaskLocal::NextLinkType::IsNext: { - next = item->getNext(); - item->destroy(task); - item = next; - break; - } - case TaskLocal::NextLinkType::IsNextCreatedInTaskGroupBody: - case TaskLocal::NextLinkType::IsParent: { - // we're done here; as we must not proceed into the parent owned values. - // we do have to destroy the item pointing at the parent/edge itself though. - item->destroy(task); + TaskLocal::Item *next = item->getNext(); + if (item->destroy(task)) { return; } - } + item = next; } } @@ -417,9 +418,8 @@ void TaskLocal::Storage::pushValue(AsyncTask *task, // memory location at this point. bool inTaskGroupBody = swift_task_hasTaskGroupStatusRecord(); - TaskLocal::Item* item = Item::createLink( - task, key, valueType, - inTaskGroupBody); + TaskLocal::ValueItem *item = + ValueItem::create(task, key, valueType, inTaskGroupBody); valueType->vw_initializeWithTake(item->getStoragePtr(), value); head = item; @@ -429,7 +429,10 @@ void TaskLocal::Storage::pushValue(AsyncTask *task, bool TaskLocal::Storage::popValue(AsyncTask *task) { assert(head && "attempted to pop value off empty task-local stack"); - SWIFT_TASK_LOCAL_DEBUG_LOG(head->key, "pop local item:%p, value:%p", head, head->getStoragePtr()); + auto valueItem = cast(head); + (void)valueItem; + SWIFT_TASK_LOCAL_DEBUG_LOG(valueItem->key, "pop local item:%p, value:%p", + head, valueItem->getStoragePtr()); auto old = head; head = head->getNext(); @@ -439,22 +442,16 @@ bool TaskLocal::Storage::popValue(AsyncTask *task) { return head != nullptr; } -std::optional -TaskLocal::Storage::peekHeadLinkType() const { - if (!head) - return {}; - - return head->getNextLinkType(); -} - OpaqueValue* TaskLocal::Storage::getValue(AsyncTask *task, const HeapObject *key) { assert(key && "TaskLocal key must not be null."); auto item = head; while (item) { - if (item->key == key) { - return item->getStoragePtr(); + if (auto valueItem = dyn_cast(item)) { + if (valueItem->key == key) { + return valueItem->getStoragePtr(); + } } item = item->getNext(); @@ -463,7 +460,6 @@ OpaqueValue* TaskLocal::Storage::getValue(AsyncTask *task, return nullptr; } - void TaskLocal::Storage::copyTo(AsyncTask *target) { assert(target && "task must not be null when copying values into it"); assert(!(target->_private().Local.head) && @@ -480,62 +476,18 @@ void TaskLocal::Storage::copyTo(AsyncTask *target) { auto item = head; while (item) { - // we only have to copy an item if it is the most recent binding of a key. - // i.e. if we've already seen an item for this key, we can skip it. - if (copied.emplace(item->key).second) { - item->copyTo(target); - } - - item = item->getNext(); - } -} - -// TODO(concurrency): This can be optimized to copy only from the CURRENT group, -// but we need to detect this, e.g. by more flags in the items made from a group? -void TaskLocal::Storage::copyToOnlyOnlyFromCurrentGroup(AsyncTask *target) { - assert(target && "task must not be null when copying values into it"); - assert(!(target->_private().Local.head) && - "Task must not have any task-local values bound before copying into it"); - - // Set of keys for which we already have copied to the new task. - // We only ever need to copy the *first* encounter of any given key, - // because it is the most "specific"/"recent" binding and any other binding - // of a key does not matter for the target task as it will never be able to - // observe it. - std::set, - swift::cxx_allocator> copied = {}; - - auto item = head; - TaskLocal::Item *copiedHead = nullptr; - while (item) { - // we only have to copy an item if it is the most recent binding of a key. - // i.e. if we've already seen an item for this key, we can skip it. - if (copied.emplace(item->key).second) { - - if (!item->isNextLinkPointerCreatedInTaskGroupBody() && copiedHead) { - // The next item is not the "risky one" so we can directly link to it, - // as we would have within normal child task relationships. E.g. this is - // a parent or next pointer to a "safe" (withValue { withTaskGroup { ... } }) - // binding, so we re-link our current head to point at this item. - copiedHead->relinkTaskGroupLocalHeadToSafeNext(item); - break; - } - - auto copy = item->copyTo(target); - if (!copiedHead) { - copiedHead = copy; + if (auto valueItem = dyn_cast(item)) { + // we only have to copy an item if it is the most recent binding of a key. + // i.e. if we've already seen an item for this key, we can skip it. + if (copied.emplace(valueItem->key).second) { + valueItem->copyTo(target); + } else { + SWIFT_TASK_LOCAL_DEBUG_LOG( + valueItem->key, + "skip copy, already copied most recent value, value was [%p]", + valueItem->getStoragePtr()); } - - // If we didn't copy an item, e.g. because it was a pointer to parent, - // break out of the loop and keep pointing at parent still. - if (!copy) { - break; - } - } else { - SWIFT_TASK_LOCAL_DEBUG_LOG(item->key, "skip copy, already copied most recent value, value was [%p]", item->getStoragePtr()); } - item = item->getNext(); } } diff --git a/stdlib/public/Concurrency/TaskLocal.h b/stdlib/public/Concurrency/TaskLocal.h index c5b176fdb0779..d6eb3e622172a 100644 --- a/stdlib/public/Concurrency/TaskLocal.h +++ b/stdlib/public/Concurrency/TaskLocal.h @@ -20,6 +20,7 @@ #include "swift/ABI/HeapObject.h" #include "swift/ABI/Metadata.h" #include "swift/ABI/MetadataValues.h" +#include "llvm/ADT/PointerIntPair.h" namespace swift { class AsyncTask; @@ -32,136 +33,77 @@ class TaskGroup; class TaskLocal { public: - /// Type of the pointed at `next` task local item. - enum class NextLinkType : uintptr_t { - /// The storage pointer points at the next TaskLocal::Item in this task. - IsNext = 0b00, - /// The storage pointer points at a item stored by another AsyncTask. - /// - /// Note that this may not necessarily be the same as the task's parent - /// task -- we may point to a super-parent if we know / that the parent - /// does not "contribute" any task local values. This is to speed up - /// lookups by skipping empty parent tasks during get(), and explained - /// in depth in `createParentLink`. - IsParent = 0b01, - /// The task local binding was created inside the body of a `withTaskGroup`, - /// and therefore must either copy it, or crash when a child task is created - /// using 'group.addTask' and it would refer to this task local. - /// - /// Items of this kind must be copied by a group child task for access - /// safety reasons, as otherwise the pop would happen before the child task - /// has completed. - IsNextCreatedInTaskGroupBody = 0b10, - }; - class Item { - private: - /// Mask used for the low status bits in a task local chain item. - static const uintptr_t statusMask = 0x03; - - /// Pointer to one of the following: - /// - next task local item as OpaqueValue* if it is task-local allocated - /// - next task local item as HeapObject* if it is heap allocated "heavy" - /// - the parent task's TaskLocal::Storage - /// - /// Low bits encode `NextLinkType`, based on which the type of the pointer - /// is determined. - uintptr_t next; - public: - /// The type of the key with which this value is associated. - const HeapObject *key; - /// The type of the value stored by this item. - const Metadata *valueType; - - // Trailing storage for the value itself. The storage will be - // uninitialized or contain an instance of \c valueType. + enum class Kind { + /// Regular task local binding. + Value = 0, + + /// Task local binding created inside the body of a `withTaskGroup`, + /// and therefore we must either copy it, or crash when a child task is + /// created + /// using 'group.addTask' and it would refer to this task local. + /// + /// Items of this kind must be copied by a group child task for access + /// safety reasons, as otherwise the pop would happen before the child + /// task + /// has completed. + ValueInTaskGroupBody = 1, + + /// Artificial empty item that indicates end of items owned by the current + /// task. + /// The `getNext()` points at a item owned by another AsyncTask. + /// + /// Note that this may not necessarily be the same as the task's parent + /// task -- we may point to a super-parent if we know / that the parent + /// does not "contribute" any task local values. This is to speed up + /// lookups by skipping empty parent tasks during get(), and explained + /// in depth in `initializeLinkParent()`. + ParentTaskMarker = 2 + }; - /// Returns true if this item is a 'parent pointer'. - /// - /// A parent pointer is special kind of `Item` is created when pointing at - /// the parent storage, forming a chain of task local items spanning multiple - /// tasks. - bool isParentPointer() const { - return !valueType; - } + private: + llvm::PointerIntPair nextAndKind; protected: - explicit Item() - : next(0), - key(nullptr), - valueType(nullptr) {} - - explicit Item(const HeapObject *key, const Metadata *valueType) - : next(0), - key(key), - valueType(valueType) {} + explicit Item(Item *next, Kind kind) : nextAndKind(next, kind) {} public: - /// Item which does not by itself store any value, but only points - /// to the nearest task-local-value containing parent's first task item. - /// - /// This item type is used to link to the appropriate parent task's item, - /// when the current task itself does not have any task local values itself. - /// - /// When a task actually has its own task locals, it should rather point - /// to the parent's *first* task-local item in its *last* item, extending - /// the Item linked list into the appropriate parent. - static Item *createParentLink(AsyncTask *task, AsyncTask *parent); - - static Item *createLink(AsyncTask *task, - const HeapObject *key, - const Metadata *valueType, - bool inTaskGroupBody); - - static Item *createLink(AsyncTask *task, - const HeapObject *key, - const Metadata *valueType); + Kind getKind() const { return nextAndKind.getInt(); } - static Item *createLinkInTaskGroup( - AsyncTask *task, - const HeapObject *key, - const Metadata *valueType); + Item *getNext() const { return nextAndKind.getPointer(); } + void setNext(Item *next) { nextAndKind.setPointer(next); } - void destroy(AsyncTask *task); + bool destroy(AsyncTask *task); + }; - Item *getNext() { - return reinterpret_cast(next & ~statusMask); + class ValueItem : public Item { + explicit ValueItem(Item *next, const HeapObject *key, + const Metadata *valueType, bool inTaskGroupBody) + : Item(next, + inTaskGroupBody ? Kind::ValueInTaskGroupBody : Kind::Value), + key(key), valueType(valueType) { + assert(key && valueType); } - void relinkTaskGroupLocalHeadToSafeNext(Item* nextOverride) { - assert(!getNext() && - "Can only relink task local item that was not pointing at anything yet"); - assert((nextOverride->isNextLinkPointer() || - nextOverride->isParentPointer()) && - "Currently relinking is only done within a task group to " - "avoid within-taskgroup next pointers; attempted to point at " - "task local declared within task group body though!"); - - next = reinterpret_cast(nextOverride) | - static_cast((nextOverride->isNextLinkPointer() - ? NextLinkType::IsNextCreatedInTaskGroupBody - : NextLinkType::IsParent)); - } + public: + ~ValueItem() { valueType->vw_destroy(getStoragePtr()); } - NextLinkType getNextLinkType() const { - return static_cast(next & statusMask); - } + /// The type of the key with which this value is associated. + const HeapObject *key; + /// The type of the value stored by this item. + const Metadata *valueType; - bool isNextLinkPointer() const { - return static_cast(next & statusMask) == - NextLinkType::IsNext; - } + // Trailing storage for the value itself. The storage will be + // uninitialized or contain an instance of \c valueType. - bool isNextLinkPointerCreatedInTaskGroupBody() const { - return static_cast(next & statusMask) == - NextLinkType::IsNextCreatedInTaskGroupBody; - } + static ValueItem *create(AsyncTask *task, const HeapObject *key, + const Metadata *valueType, bool inTaskGroupBody); - /// Item does not contain any actual value, and is only used to point at - /// a specific parent item. - bool isEmpty() const { - return !valueType; + void copyTo(AsyncTask *task); + + bool isInTaskGroupBody() const { + return getKind() == Kind::ValueInTaskGroupBody; } /// Retrieve a pointer to the storage of the value. @@ -170,32 +112,39 @@ class TaskLocal { reinterpret_cast(this) + storageOffset(valueType)); } - TaskLocal::Item* copyTo(AsyncTask *task); - /// Compute the offset of the storage from the base of the item. static size_t storageOffset(const Metadata *valueType) { - size_t offset = sizeof(Item); - - if (valueType) { - size_t alignment = valueType->vw_alignment(); - return (offset + alignment - 1) & ~(alignment - 1); - } - - return offset; + size_t alignment = valueType->vw_alignment(); + return (sizeof(ValueItem) + alignment - 1) & ~(alignment - 1); } /// Determine the size of the item given a particular value type. static size_t itemSize(const Metadata *valueType) { size_t offset = storageOffset(valueType); - if (valueType) { - offset += valueType->vw_size(); - } + offset += valueType->vw_size(); return offset; } + + static bool classof(const Item *item) { + return item->getKind() == Kind::Value || + item->getKind() == Kind::ValueInTaskGroupBody; + } + }; + + class ParentTaskMarkerItem : public Item { + ParentTaskMarkerItem(Item *next) : Item(next, Kind::ParentTaskMarker) {} + + public: + static ParentTaskMarkerItem *create(AsyncTask *task); + + static bool classof(const Item *item) { + return item->getKind() == Kind::ParentTaskMarker; + } }; class Storage { - friend class TaskLocal::Item; + friend class TaskLocal::ValueItem; + private: /// A stack (single-linked list) of task local values. /// @@ -247,9 +196,6 @@ class TaskLocal { /// can be safely disposed of. bool popValue(AsyncTask *task); - /// Peek at the head item and get its type. - std::optional peekHeadLinkType() const; - /// Copy all task-local bindings to the target task. /// /// The new bindings allocate their own items and can out-live the current task. @@ -262,10 +208,6 @@ class TaskLocal { /// "pop" of the `B` value - it was spawned from a scope where only B was observable. void copyTo(AsyncTask *target); - // FIXME(concurrency): We currently copy from "all" task groups we encounter - // however in practice we only - void copyToOnlyOnlyFromCurrentGroup(AsyncTask *target); - /// Destroy and deallocate all items stored by this specific task. /// /// Items owned by a parent task are left untouched, since we do not own them.