| //===-- PerThreadTable.h -- PerThread Storage Structure ----*- C++ -*-===// | |
| // | |
| // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. | |
| // See https://llvm.org/LICENSE.txt for license information. | |
| // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception | |
| // | |
| //===----------------------------------------------------------------------===// | |
| // | |
| // Table indexed with one entry per thread. | |
| // | |
| //===----------------------------------------------------------------------===// | |
| #ifndef OFFLOAD_PERTHREADTABLE_H | |
| #define OFFLOAD_PERTHREADTABLE_H | |
| #include <list> | |
| #include <llvm/ADT/SmallVector.h> | |
| #include <llvm/Support/Error.h> | |
| #include <memory> | |
| #include <mutex> | |
| #include <type_traits> | |
| template <typename ObjectType> class PerThread { | |
| std::mutex Mutex; | |
| llvm::SmallVector<std::shared_ptr<ObjectType>> ThreadDataList; | |
| ObjectType &getThreadData() { | |
| static thread_local std::shared_ptr<ObjectType> ThreadData = nullptr; | |
| if (!ThreadData) { | |
| ThreadData = std::make_shared<ObjectType>(); | |
| std::lock_guard<std::mutex> Lock(Mutex); | |
| ThreadDataList.push_back(ThreadData); | |
| } | |
| return *ThreadData; | |
| } | |
| public: | |
| // Define default constructors, disable copy and move constructors. | |
| PerThread() = default; | |
| PerThread(const PerThread &) = delete; | |
| PerThread(PerThread &&) = delete; | |
| PerThread &operator=(const PerThread &) = delete; | |
| PerThread &operator=(PerThread &&) = delete; | |
| ~PerThread() { | |
| assert(Mutex.try_lock() && (Mutex.unlock(), true) && | |
| "Cannot be deleted while other threads are adding entries"); | |
| ThreadDataList.clear(); | |
| } | |
| ObjectType &get() { return getThreadData(); } | |
| template <class ClearFuncTy> void clear(ClearFuncTy ClearFunc) { | |
| assert(Mutex.try_lock() && (Mutex.unlock(), true) && | |
| "Clear cannot be called while other threads are adding entries"); | |
| for (std::shared_ptr<ObjectType> ThreadData : ThreadDataList) { | |
| if (!ThreadData) | |
| continue; | |
| ClearFunc(*ThreadData); | |
| } | |
| ThreadDataList.clear(); | |
| } | |
| }; | |
| template <typename ContainerTy> struct ContainerConcepts { | |
| template <typename, template <typename> class, typename = std::void_t<>> | |
| struct has : std::false_type {}; | |
| template <typename Ty, template <typename> class Op> | |
| struct has<Ty, Op, std::void_t<Op<Ty>>> : std::true_type {}; | |
| template <typename Ty> using IteratorTypeCheck = typename Ty::iterator; | |
| template <typename Ty> using MappedTypeCheck = typename Ty::mapped_type; | |
| template <typename Ty> using ValueTypeCheck = typename Ty::value_type; | |
| template <typename Ty> using KeyTypeCheck = typename Ty::key_type; | |
| template <typename Ty> using SizeTypeCheck = typename Ty::size_type; | |
| template <typename Ty> | |
| using ClearCheck = decltype(std::declval<Ty>().clear()); | |
| template <typename Ty> | |
| using ReserveCheck = decltype(std::declval<Ty>().reserve(1)); | |
| template <typename Ty> | |
| using ResizeCheck = decltype(std::declval<Ty>().resize(1)); | |
| static constexpr bool hasIterator = | |
| has<ContainerTy, IteratorTypeCheck>::value; | |
| static constexpr bool hasClear = has<ContainerTy, ClearCheck>::value; | |
| static constexpr bool isAssociative = | |
| has<ContainerTy, MappedTypeCheck>::value; | |
| static constexpr bool hasReserve = has<ContainerTy, ReserveCheck>::value; | |
| static constexpr bool hasResize = has<ContainerTy, ResizeCheck>::value; | |
| template <typename, template <typename> class, typename = std::void_t<>> | |
| struct has_type { | |
| using type = void; | |
| }; | |
| template <typename Ty, template <typename> class Op> | |
| struct has_type<Ty, Op, std::void_t<Op<Ty>>> { | |
| using type = Op<Ty>; | |
| }; | |
| using iterator = typename has_type<ContainerTy, IteratorTypeCheck>::type; | |
| using value_type = typename std::conditional_t< | |
| isAssociative, typename has_type<ContainerTy, MappedTypeCheck>::type, | |
| typename has_type<ContainerTy, ValueTypeCheck>::type>; | |
| using key_type = typename std::conditional_t< | |
| isAssociative, typename has_type<ContainerTy, KeyTypeCheck>::type, | |
| typename has_type<ContainerTy, SizeTypeCheck>::type>; | |
| }; | |
| // Using an STL container (such as std::vector) indexed by thread ID has | |
| // too many race conditions issues so we store each thread entry into a | |
| // thread_local variable. | |
| // ContainerType is the container type used to store the objects, e.g., | |
| // std::vector, std::set, etc. by each thread. ObjectType is the type of the | |
| // stored objects e.g., omp_interop_val_t *, ... | |
| template <typename ContainerType, typename ObjectType> class PerThreadTable { | |
| using iterator = typename ContainerConcepts<ContainerType>::iterator; | |
| struct PerThreadData { | |
| size_t Size = 0; | |
| std::unique_ptr<ContainerType> ThreadEntry; | |
| }; | |
| std::mutex Mutex; | |
| llvm::SmallVector<std::shared_ptr<PerThreadData>> ThreadDataList; | |
| PerThreadData &getThreadData() { | |
| static thread_local std::shared_ptr<PerThreadData> ThreadData = nullptr; | |
| if (!ThreadData) { | |
| ThreadData = std::make_shared<PerThreadData>(); | |
| std::lock_guard<std::mutex> Lock(Mutex); | |
| ThreadDataList.push_back(ThreadData); | |
| } | |
| return *ThreadData; | |
| } | |
| protected: | |
| ContainerType &getThreadEntry() { | |
| PerThreadData &ThreadData = getThreadData(); | |
| if (ThreadData.ThreadEntry) | |
| return *ThreadData.ThreadEntry; | |
| ThreadData.ThreadEntry = std::make_unique<ContainerType>(); | |
| return *ThreadData.ThreadEntry; | |
| } | |
| size_t &getThreadSize() { | |
| PerThreadData &ThreadData = getThreadData(); | |
| return ThreadData.Size; | |
| } | |
| void setSize(size_t Size) { | |
| size_t &SizeRef = getThreadSize(); | |
| SizeRef = Size; | |
| } | |
| public: | |
| // define default constructors, disable copy and move constructors. | |
| PerThreadTable() = default; | |
| PerThreadTable(const PerThreadTable &) = delete; | |
| PerThreadTable(PerThreadTable &&) = delete; | |
| PerThreadTable &operator=(const PerThreadTable &) = delete; | |
| PerThreadTable &operator=(PerThreadTable &&) = delete; | |
| ~PerThreadTable() { | |
| assert(Mutex.try_lock() && (Mutex.unlock(), true) && | |
| "Cannot be deleted while other threads are adding entries"); | |
| ThreadDataList.clear(); | |
| } | |
| void add(ObjectType obj) { | |
| ContainerType &Entry = getThreadEntry(); | |
| size_t &SizeRef = getThreadSize(); | |
| SizeRef++; | |
| Entry.add(obj); | |
| } | |
| iterator erase(iterator it) { | |
| ContainerType &Entry = getThreadEntry(); | |
| size_t &SizeRef = getThreadSize(); | |
| SizeRef--; | |
| return Entry.erase(it); | |
| } | |
| size_t size() { return getThreadSize(); } | |
| // Iterators to traverse objects owned by | |
| // the current thread. | |
| iterator begin() { | |
| ContainerType &Entry = getThreadEntry(); | |
| return Entry.begin(); | |
| } | |
| iterator end() { | |
| ContainerType &Entry = getThreadEntry(); | |
| return Entry.end(); | |
| } | |
| template <class ClearFuncTy> void clear(ClearFuncTy ClearFunc) { | |
| assert(Mutex.try_lock() && (Mutex.unlock(), true) && | |
| "Clear cannot be called while other threads are adding entries"); | |
| for (std::shared_ptr<PerThreadData> ThreadData : ThreadDataList) { | |
| if (!ThreadData->ThreadEntry || ThreadData->Size == 0) | |
| continue; | |
| if constexpr (ContainerConcepts<ContainerType>::hasIterator && | |
| ContainerConcepts<ContainerType>::hasClear) { | |
| for (auto &Obj : *ThreadData->ThreadEntry) { | |
| if constexpr (ContainerConcepts<ContainerType>::isAssociative) { | |
| ClearFunc(Obj.second); | |
| } else { | |
| ClearFunc(Obj); | |
| } | |
| } | |
| ThreadData->ThreadEntry->clear(); | |
| } else { | |
| static_assert(true, "Container type not supported"); | |
| } | |
| ThreadData->Size = 0; | |
| } | |
| ThreadDataList.clear(); | |
| } | |
| template <class DeinitFuncTy> llvm::Error deinit(DeinitFuncTy DeinitFunc) { | |
| assert(Mutex.try_lock() && (Mutex.unlock(), true) && | |
| "Deinit cannot be called while other threads are adding entries"); | |
| for (std::shared_ptr<PerThreadData> ThreadData : ThreadDataList) { | |
| if (!ThreadData->ThreadEntry || ThreadData->Size == 0) | |
| continue; | |
| for (auto &Obj : *ThreadData->ThreadEntry) { | |
| if constexpr (ContainerConcepts<ContainerType>::isAssociative) { | |
| if (auto Err = DeinitFunc(Obj.second)) | |
| return Err; | |
| } else { | |
| if (auto Err = DeinitFunc(Obj)) | |
| return Err; | |
| } | |
| } | |
| } | |
| return llvm::Error::success(); | |
| } | |
| }; | |
| template <typename ContainerType, size_t ReserveSize = 0> | |
| class PerThreadContainer | |
| : public PerThreadTable<ContainerType, typename ContainerConcepts< | |
| ContainerType>::value_type> { | |
| using IndexType = typename ContainerConcepts<ContainerType>::key_type; | |
| using ObjectType = typename ContainerConcepts<ContainerType>::value_type; | |
| public: | |
| // Get the object for the given index in the current thread. | |
| ObjectType &get(IndexType Index) { | |
| ContainerType &Entry = this->getThreadEntry(); | |
| // Specialized code for vector-like containers. | |
| if constexpr (ContainerConcepts<ContainerType>::hasResize) { | |
| if (Index >= Entry.size()) { | |
| if constexpr (ContainerConcepts<ContainerType>::hasReserve && | |
| ReserveSize > 0) | |
| Entry.reserve(ReserveSize); | |
| // If the index is out of bounds, try resize the container. | |
| Entry.resize(Index + 1); | |
| } | |
| } | |
| ObjectType &Ret = Entry[Index]; | |
| this->setSize(Entry.size()); | |
| return Ret; | |
| } | |
| }; | |
| #endif |