diff --git a/llvm/include/llvm/DWARFLinkerParallel/DWARFLinker.h b/llvm/include/llvm/DWARFLinkerParallel/DWARFLinker.h --- a/llvm/include/llvm/DWARFLinkerParallel/DWARFLinker.h +++ b/llvm/include/llvm/DWARFLinkerParallel/DWARFLinker.h @@ -9,6 +9,8 @@ #ifndef LLVM_DWARFLINKERPARALLEL_DWARFLINKER_H #define LLVM_DWARFLINKERPARALLEL_DWARFLINKER_H +#include "llvm/DWARFLinkerParallel/NonAllocatingList.h" + namespace llvm { namespace dwarflinker_parallel {} // end namespace dwarflinker_parallel } // end namespace llvm diff --git a/llvm/include/llvm/DWARFLinkerParallel/NonAllocatingList.h b/llvm/include/llvm/DWARFLinkerParallel/NonAllocatingList.h new file mode 100644 --- /dev/null +++ b/llvm/include/llvm/DWARFLinkerParallel/NonAllocatingList.h @@ -0,0 +1,114 @@ +//===- NonAllocatingList.h --------------------------------------*- 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 +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_DWARFLINKERPARALLEL_NONALLOCATINGLIST_H +#define LLVM_DWARFLINKERPARALLEL_NONALLOCATINGLIST_H + +#include "llvm/ADT/STLFunctionalExtras.h" +#include +#include +#include +#include + +namespace llvm { +namespace dwarflinker_parallel { + +/// Mixin adding "Next" field for the list entry. +template class NonAllocatingListItemMixin { +public: + T *Next = nullptr; +}; + +/// Simple list with lock free insertions. Other methods are not thread-safe. +/// ASSUMPTION: nodes are allocated and deallocated outside the list. +template class NonAllocatingList { +public: + NonAllocatingList() { + static_assert( + std::is_base_of, T>::value, + "type parameter of List must derive from NonAllocatingListItemMixin"); + } + + /// \returns current head of the list. + T *front() { return Head; } + const T *front() const { return Head; } + + /// \returns true if the list is empty. + bool empty() const { return Head == nullptr; } + + /// Adds \p NewItem into the list. Item added at front of the list. + void pushFront(T *NewItem) { + T *CurHead = nullptr; + do { + CurHead = Head; + NewItem->Next = CurHead; + } while (!Head.compare_exchange_weak(CurHead, NewItem)); + } + + /// Enumerates the items in the list and calls the \p Handler for each item. + void enumerate(function_ref Handler) { + T *CurItem = Head; + while (CurItem != nullptr) { + T *NextItem = CurItem->Next; + Handler(*CurItem); + + CurItem = NextItem; + } + } + + /// Enumerates the items in the list and calls the \p handler for each item. + void enumerate(function_ref Handler) const { + T *CurItem = Head; + while (CurItem != nullptr) { + T *NextItem = CurItem->Next; + Handler(*CurItem); + + CurItem = NextItem; + } + } + + /// Sets list head to null. + void clear() { Head = nullptr; } + + /// Sorts list items according to the \p Comparator. + void sort(function_ref Comparator) { + // TODO: It might be beneficial to implement sorting of items + // using merge sort. + std::vector Types; + enumerate([&](T &Type) { Types.push_back(&Type); }); + + if (Types.size()) { + std::sort(Types.begin(), Types.end(), Comparator); + + Head = *Types.begin(); + Types.back()->Next = nullptr; + + for (size_t CurIdx = 1; CurIdx < Types.size(); CurIdx++) { + Types[CurIdx - 1]->Next = Types[CurIdx]; + } + } + } + + /// Reverse list items. + void reverse() { + T *nextItem = nullptr; + enumerate([&](T &Type) { + Type.Next = nextItem; + nextItem = &Type; + }); + Head = nextItem; + } + +protected: + std::atomic Head = {nullptr}; +}; + +} // end of namespace dwarflinker_parallel +} // end namespace llvm + +#endif // LLVM_DWARFLINKERPARALLEL_NONALLOCATINGLIST_H diff --git a/llvm/unittests/DWARFLinkerParallel/CMakeLists.txt b/llvm/unittests/DWARFLinkerParallel/CMakeLists.txt --- a/llvm/unittests/DWARFLinkerParallel/CMakeLists.txt +++ b/llvm/unittests/DWARFLinkerParallel/CMakeLists.txt @@ -5,6 +5,7 @@ add_llvm_unittest(DWARFLinkerParallelTests DWARFLinkerTest.cpp + NonAllocatingListTest.cpp ) target_link_libraries(DWARFLinkerParallelTests PRIVATE LLVMTestingSupport) diff --git a/llvm/unittests/DWARFLinkerParallel/NonAllocatingListTest.cpp b/llvm/unittests/DWARFLinkerParallel/NonAllocatingListTest.cpp new file mode 100644 --- /dev/null +++ b/llvm/unittests/DWARFLinkerParallel/NonAllocatingListTest.cpp @@ -0,0 +1,112 @@ +//===- llvm/unittest/DWARFLinkerParallel/NonAllocatingListTest.cpp --------===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +#include "llvm/DWARFLinkerParallel/NonAllocatingList.h" +#include "llvm/Support/Parallel.h" +#include "gtest/gtest.h" +#include + +using namespace llvm; +using namespace dwarflinker_parallel; + +namespace { + +struct Number : public NonAllocatingListItemMixin { + int Num = 0; +}; + +inline bool operator==(const Number &LHS, const Number &RHS) { + return LHS.Num == RHS.Num; +} + +TEST(NonAllocatingListTest, TestList) { + + Number N1; + N1.Num = 1; + + NonAllocatingList L; + + EXPECT_TRUE(L.empty()); + EXPECT_TRUE(L.front() == nullptr); + + // Add first item. + L.pushFront(&N1); + EXPECT_FALSE(L.empty()); + EXPECT_TRUE(L.front() == &N1); + + Number N2; + N2.Num = 2; + + // Add second item. + L.pushFront(&N2); + EXPECT_FALSE(L.empty()); + EXPECT_TRUE(L.front() == &N2); + EXPECT_TRUE(L.front()->Next == &N1); + + Number N3; + N3.Num = 3; + + // Add third item. + L.pushFront(&N3); + EXPECT_FALSE(L.empty()); + EXPECT_TRUE(L.front() == &N3); + EXPECT_TRUE(L.front()->Next == &N2); + EXPECT_TRUE(L.front()->Next->Next == &N1); + + // Check sorting. + L.sort([](Number *LHS, Number *RHS) -> bool { return LHS->Num < RHS->Num; }); + + EXPECT_TRUE(L.front()->Num == 1); + EXPECT_TRUE(L.front()->Next->Num == 2); + EXPECT_TRUE(L.front()->Next->Next->Num == 3); + + // Check reverse items. + L.reverse(); + + EXPECT_TRUE(L.front()->Num == 3); + EXPECT_TRUE(L.front()->Next->Num == 2); + EXPECT_TRUE(L.front()->Next->Next->Num == 1); + + // Check items enumerations. + L.enumerate( + [](Number &N) { EXPECT_TRUE(N.Num == 1 || N.Num == 2 || N.Num == 3); }); + + // Check cleanup. + L.clear(); + EXPECT_TRUE(L.empty()); + EXPECT_TRUE(L.front() == nullptr); +} + +TEST(NonAllocatingListTest, TestListParallel) { + std::vector Data; + + // Create data. + for (int Idx = 10; Idx < 1000; Idx++) { + Number N; + N.Num = Idx; + Data.push_back(N); + } + + NonAllocatingList L; + + // Add data into the list. + parallelFor(0, Data.size(), [&](size_t Idx) { L.pushFront(&Data[Idx]); }); + + EXPECT_FALSE(L.empty()); + + // Check that list contains exactly source data. + size_t NumElements = 0; + L.enumerate([&](const Number &N) { + NumElements++; + + EXPECT_TRUE(std::find(Data.begin(), Data.end(), N) != Data.end()); + }); + EXPECT_TRUE(NumElements == Data.size()); +} + +} // anonymous namespace