//===--- TaskLocal.h - ABI of task local values -----------------*- C++ -*-===// // // This source file is part of the Swift.org open source project // // Copyright (c) 2014 - 2020 Apple Inc. and the Swift project authors // Licensed under Apache License v2.0 with Runtime Library Exception // // See https://swift.org/LICENSE.txt for license information // See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors // //===----------------------------------------------------------------------===// // // Swift ABI describing task locals. // //===----------------------------------------------------------------------===// #ifndef SWIFT_ABI_TASKLOCAL_BACKDEPLOY56_H #define SWIFT_ABI_TASKLOCAL_BACKDEPLOY56_H #include "swift/ABI/HeapObject.h" #include "swift/ABI/Metadata.h" #include "swift/ABI/MetadataValues.h" namespace swift { class AsyncTask; struct OpaqueValue; struct SwiftError; class TaskStatusRecord; class TaskGroup; // ==== Task Locals Values --------------------------------------------------- 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, }; 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. /// 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; } protected: explicit Item() : next(0), key(nullptr), valueType(nullptr) {} explicit Item(const HeapObject *key, const Metadata *valueType) : next(0), key(key), valueType(valueType) {} 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); void destroy(AsyncTask *task); Item *getNext() { return reinterpret_cast(next & ~statusMask); } NextLinkType getNextLinkType() const { return static_cast(next & statusMask); } /// Item does not contain any actual value, and is only used to point at /// a specific parent item. bool isEmpty() const { return !valueType; } /// Retrieve a pointer to the storage of the value. OpaqueValue *getStoragePtr() { return reinterpret_cast( reinterpret_cast(this) + storageOffset(valueType)); } void 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); } else { return offset; } } /// 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(); } return offset; } }; class Storage { friend class TaskLocal::Item; private: /// A stack (single-linked list) of task local values. /// /// Once task local values within this task are traversed, the list continues /// to the "next parent that contributes task local values," or if no such /// parent exists it terminates with null. /// /// If the TaskLocalValuesFragment was allocated, it is expected that this /// value should be NOT null; it either has own values, or at least one /// parent that has values. If this task does not have any values, the head /// pointer MAY immediately point at this task's parent task which has values. /// /// ### Concurrency /// Access to the head is only performed from the task itself, when it /// creates child tasks, the child during creation will inspect its parent's /// task local value stack head, and point to it. This is done on the calling /// task, and thus needs not to be synchronized. Subsequent traversal is /// performed by child tasks concurrently, however they use their own /// pointers/stack and can never mutate the parent's stack. /// /// The stack is only pushed/popped by the owning task, at the beginning and /// end a `body` block of `withLocal(_:boundTo:body:)` respectively. /// /// Correctness of the stack strongly relies on the guarantee that tasks /// never outline a scope in which they are created. Thanks to this, if /// tasks are created inside the `body` of `withLocal(_:,boundTo:body:)` /// all tasks created inside the `withLocal` body must complete before it /// returns, as such, any child tasks potentially accessing the value stack /// are guaranteed to be completed by the time we pop values off the stack /// (after the body has completed). TaskLocal::Item *head = nullptr; public: void initializeLinkParent(AsyncTask *task, AsyncTask *parent); void pushValue(AsyncTask *task, const HeapObject *key, /* +1 */ OpaqueValue *value, const Metadata *valueType); OpaqueValue* getValue(AsyncTask *task, const HeapObject *key); /// Returns `true` of more bindings remain in this storage, /// and `false` if the just popped value was the last one and the storage /// can be safely disposed of. bool popValue(AsyncTask *task); /// Copy all task-local bindings to the target task. /// /// The new bindings allocate their own items and can out-live the current task. /// /// ### Optimizations /// Only the most recent binding of a value is copied over, i.e. given /// a key bound to `A` and then `B`, only the `B` binding will be copied. /// This is safe and correct because the new task would never have a chance /// to observe the `A` value, because it semantically will never observe a /// "pop" of the `B` value - it was spawned from a scope where only B was observable. void copyTo(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. void destroy(AsyncTask *task); }; }; } // end namespace swift #endif // SWIFT_ABI_TASKLOCAL_BACKDEPLOY56_H