From 23d8306d13e441c5afad5dfa48d344ad5865fde8 Mon Sep 17 00:00:00 2001 From: "Duncan P. N. Exon Smith" Date: Sun, 11 Sep 2016 22:40:40 +0000 Subject: [PATCH] ADT: Add AllocatorList, and use it for yaml::Token - Add AllocatorList, a non-intrusive list that owns an LLVM-style allocator and provides a std::list-like interface (trivially built on top of simple_ilist), - add a typedef (and unit tests) for BumpPtrList, and - use BumpPtrList for the list of llvm::yaml::Token (i.e., TokenQueueT). TokenQueueT has no need for the complexity of an intrusive list. The only reason to inherit from ilist was to customize the allocator. TokenQueueT was the only example in-tree of using ilist<> in a truly non-intrusive way. Moreover, this removes the final use of the non-intrusive ilist_traits<>::createNode (after r280573, r281177, and r281181). I have a WIP patch that removes this customization point (and the API that relies on it) that I plan to commit soon. Note: AllocatorList owns the allocator, which limits the viable API (e.g., splicing must be on the same list). For now I've left out any problematic API. It wouldn't be hard to split AllocatorList into two layers: an Impl class that calls DerivedT::getAlloc (via CRTP), and derived classes that handle Allocator ownership/reference/etc semantics; and then implement splice with appropriate assertions; but TBH we should probably just customize the std::list allocators at that point. llvm-svn: 281182 --- llvm/include/llvm/ADT/AllocatorList.h | 226 +++++++++++++++++++++++ llvm/include/llvm/ADT/ilist.h | 3 + llvm/include/llvm/ADT/simple_ilist.h | 8 + llvm/lib/Support/YAMLParser.cpp | 23 +-- llvm/unittests/ADT/BumpPtrListTest.cpp | 243 +++++++++++++++++++++++++ llvm/unittests/ADT/CMakeLists.txt | 1 + 6 files changed, 486 insertions(+), 18 deletions(-) create mode 100644 llvm/include/llvm/ADT/AllocatorList.h create mode 100644 llvm/unittests/ADT/BumpPtrListTest.cpp diff --git a/llvm/include/llvm/ADT/AllocatorList.h b/llvm/include/llvm/ADT/AllocatorList.h new file mode 100644 index 000000000000..604de2b7ad89 --- /dev/null +++ b/llvm/include/llvm/ADT/AllocatorList.h @@ -0,0 +1,226 @@ +//===- llvm/ADT/AllocatorList.h - Custom allocator list ---------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_ADT_ALLOCATORLIST_H +#define LLVM_ADT_ALLOCATORLIST_H + +#include "llvm/ADT/iterator.h" +#include "llvm/ADT/simple_ilist.h" +#include "llvm/Support/Allocator.h" +#include + +namespace llvm { + +/// A linked-list with a custom, local allocator. +/// +/// Expose a std::list-like interface that owns and uses a custom LLVM-style +/// allocator (e.g., BumpPtrAllocator), leveraging \a simple_ilist for the +/// implementation details. +/// +/// Because this list owns the allocator, calling \a splice() with a different +/// list isn't generally safe. As such, \a splice has been left out of the +/// interface entirely. +template class AllocatorList : AllocatorT { + struct Node : ilist_node { + Node(Node &&) = delete; + Node(const Node &) = delete; + Node &operator=(Node &&) = delete; + Node &operator=(const Node &) = delete; + + Node(T &&V) : V(std::move(V)) {} + Node(const T &V) : V(V) {} + template Node(Ts &&... Vs) : V(std::forward(Vs)...) {} + T V; + }; + + typedef simple_ilist list_type; + list_type List; + + AllocatorT &getAlloc() { return *this; } + const AllocatorT &getAlloc() const { return *this; } + + template Node *create(ArgTs &&... Args) { + return new (getAlloc()) Node(std::forward(Args)...); + } + + struct Cloner { + AllocatorList &AL; + Cloner(AllocatorList &AL) : AL(AL) {} + Node *operator()(const Node &N) const { return AL.create(N.V); } + }; + + struct Disposer { + AllocatorList &AL; + Disposer(AllocatorList &AL) : AL(AL) {} + void operator()(Node *N) const { + N->~Node(); + AL.getAlloc().Deallocate(N); + } + }; + +public: + typedef T value_type; + typedef T *pointer; + typedef T &reference; + typedef const T *const_pointer; + typedef const T &const_reference; + typedef typename list_type::size_type size_type; + typedef typename list_type::difference_type difference_type; + +private: + template + class IteratorImpl + : public iterator_adaptor_base, + IteratorBase, + std::bidirectional_iterator_tag, ValueT> { + template + friend class IteratorImpl; + friend AllocatorList; + + typedef iterator_adaptor_base, + IteratorBase, std::bidirectional_iterator_tag, + ValueT> + iterator_adaptor_base; + + public: + typedef ValueT value_type; + typedef ValueT *pointer; + typedef ValueT &reference; + + IteratorImpl() = default; + IteratorImpl(const IteratorImpl &) = default; + IteratorImpl &operator=(const IteratorImpl &) = default; + ~IteratorImpl() = default; + + explicit IteratorImpl(const IteratorBase &I) : iterator_adaptor_base(I) {} + + template + IteratorImpl(const IteratorImpl &X, + typename std::enable_if::value>::type * = nullptr) + : iterator_adaptor_base(X.wrapped()) {} + + reference operator*() const { return iterator_adaptor_base::wrapped()->V; } + pointer operator->() const { return &operator*(); } + + friend bool operator==(const IteratorImpl &L, const IteratorImpl &R) { + return L.wrapped() == R.wrapped(); + } + friend bool operator!=(const IteratorImpl &L, const IteratorImpl &R) { + return !(L == R); + } + }; + +public: + typedef IteratorImpl iterator; + typedef IteratorImpl + reverse_iterator; + typedef IteratorImpl + const_iterator; + typedef IteratorImpl + const_reverse_iterator; + + AllocatorList() = default; + AllocatorList(AllocatorList &&X) + : AllocatorT(std::move(X.getAlloc())), List(std::move(X.List)) {} + AllocatorList(const AllocatorList &X) { + List.cloneFrom(X.List, Cloner(*this), Disposer(*this)); + } + AllocatorList &operator=(AllocatorList &&X) { + clear(); // Dispose of current nodes explicitly. + List = std::move(X.List); + getAlloc() = std::move(X.getAlloc()); + return *this; + } + AllocatorList &operator=(const AllocatorList &X) { + List.cloneFrom(X.List, Cloner(*this), Disposer(*this)); + return *this; + } + ~AllocatorList() { clear(); } + + void swap(AllocatorList &RHS) { + List.swap(RHS.List); + std::swap(getAlloc(), RHS.getAlloc()); + } + + bool empty() { return List.empty(); } + size_t size() { return List.size(); } + + iterator begin() { return iterator(List.begin()); } + iterator end() { return iterator(List.end()); } + const_iterator begin() const { return const_iterator(List.begin()); } + const_iterator end() const { return const_iterator(List.end()); } + reverse_iterator rbegin() { return reverse_iterator(List.rbegin()); } + reverse_iterator rend() { return reverse_iterator(List.rend()); } + const_reverse_iterator rbegin() const { + return const_reverse_iterator(List.rbegin()); + } + const_reverse_iterator rend() const { + return const_reverse_iterator(List.rend()); + } + + T &back() { return List.back().V; } + T &front() { return List.front().V; } + const T &back() const { return List.back().V; } + const T &front() const { return List.front().V; } + + template iterator emplace(iterator I, Ts &&... Vs) { + return iterator(List.insert(I.wrapped(), *create(std::forward(Vs)...))); + } + + iterator insert(iterator I, T &&V) { + return iterator(List.insert(I.wrapped(), *create(std::move(V)))); + } + iterator insert(iterator I, const T &V) { + return iterator(List.insert(I.wrapped(), *create(V))); + } + + template + void insert(iterator I, Iterator First, Iterator Last) { + for (; First != Last; ++First) + List.insert(I.wrapped(), *create(*First)); + } + + iterator erase(iterator I) { + return iterator(List.eraseAndDispose(I.wrapped(), Disposer(*this))); + } + + iterator erase(iterator First, iterator Last) { + return iterator( + List.eraseAndDispose(First.wrapped(), Last.wrapped(), Disposer(*this))); + } + + void clear() { List.clearAndDispose(Disposer(*this)); } + void pop_back() { List.eraseAndDispose(--List.end(), Disposer(*this)); } + void pop_front() { List.eraseAndDispose(List.begin(), Disposer(*this)); } + void push_back(T &&V) { insert(end(), std::move(V)); } + void push_front(T &&V) { insert(begin(), std::move(V)); } + void push_back(const T &V) { insert(end(), V); } + void push_front(const T &V) { insert(begin(), V); } + template void emplace_back(Ts &&... Vs) { + emplace(end(), std::forward(Vs)...); + } + template void emplace_front(Ts &&... Vs) { + emplace(begin(), std::forward(Vs)...); + } + + /// Reset the underlying allocator. + /// + /// \pre \c empty() + void resetAlloc() { + assert(empty() && "Cannot reset allocator if not empty"); + getAlloc().Reset(); + } +}; + +template using BumpPtrList = AllocatorList; + +} // end namespace llvm + +#endif // LLVM_ADT_ALLOCATORLIST_H diff --git a/llvm/include/llvm/ADT/ilist.h b/llvm/include/llvm/ADT/ilist.h index 0f7b37b3765f..d0569c9eae29 100644 --- a/llvm/include/llvm/ADT/ilist.h +++ b/llvm/include/llvm/ADT/ilist.h @@ -40,6 +40,9 @@ namespace llvm { /// /// \see ilist_noalloc_traits template struct ilist_alloc_traits { + /// Clone a node. + /// + /// TODO: Remove this and API that relies on it (it's dead code). static NodeTy *createNode(const NodeTy &V) { return new NodeTy(V); } static void deleteNode(NodeTy *V) { delete V; } }; diff --git a/llvm/include/llvm/ADT/simple_ilist.h b/llvm/include/llvm/ADT/simple_ilist.h index 0ac5cefc1f93..9235bad6ab32 100644 --- a/llvm/include/llvm/ADT/simple_ilist.h +++ b/llvm/include/llvm/ADT/simple_ilist.h @@ -164,6 +164,14 @@ public: insert(I, *First); } + /// Clone another list. + template + void cloneFrom(const simple_ilist &L2, Cloner clone, Disposer dispose) { + clearAndDispose(dispose); + for (const_reference V : L2) + push_back(*clone(V)); + } + /// Remove a node by reference; never deletes. /// /// \see \a erase() for removing by iterator. diff --git a/llvm/lib/Support/YAMLParser.cpp b/llvm/lib/Support/YAMLParser.cpp index c083e1f1ebdf..0d169af26be8 100644 --- a/llvm/lib/Support/YAMLParser.cpp +++ b/llvm/lib/Support/YAMLParser.cpp @@ -17,8 +17,7 @@ #include "llvm/ADT/SmallVector.h" #include "llvm/ADT/StringExtras.h" #include "llvm/ADT/Twine.h" -#include "llvm/ADT/ilist.h" -#include "llvm/ADT/ilist_node.h" +#include "llvm/ADT/AllocatorList.h" #include "llvm/Support/ErrorHandling.h" #include "llvm/Support/MemoryBuffer.h" #include "llvm/Support/SourceMgr.h" @@ -109,7 +108,7 @@ void SequenceNode::anchor() {} void AliasNode::anchor() {} /// Token - A single YAML token. -struct Token : ilist_node { +struct Token { enum TokenKind { TK_Error, // Uninitialized token. TK_StreamStart, @@ -148,18 +147,7 @@ struct Token : ilist_node { } } -namespace llvm { -template <> struct ilist_alloc_traits { - Token *createNode(const Token &V) { - return new (Alloc.Allocate()) Token(V); - } - static void deleteNode(Token *V) { V->~Token(); } - - BumpPtrAllocator Alloc; -}; -} // end namespace llvm - -typedef ilist TokenQueueT; +typedef llvm::BumpPtrList TokenQueueT; namespace { /// @brief This struct is used to track simple keys. @@ -797,9 +785,8 @@ Token Scanner::getNext() { // There cannot be any referenced Token's if the TokenQueue is empty. So do a // quick deallocation of them all. - if (TokenQueue.empty()) { - TokenQueue.Alloc.Reset(); - } + if (TokenQueue.empty()) + TokenQueue.resetAlloc(); return Ret; } diff --git a/llvm/unittests/ADT/BumpPtrListTest.cpp b/llvm/unittests/ADT/BumpPtrListTest.cpp new file mode 100644 index 000000000000..be34a71373ce --- /dev/null +++ b/llvm/unittests/ADT/BumpPtrListTest.cpp @@ -0,0 +1,243 @@ +//===- unittests/ADT/BumpPtrListTest.cpp - BumpPtrList unit tests ---------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "llvm/ADT/AllocatorList.h" +#include "llvm/ADT/STLExtras.h" +#include "gtest/gtest.h" + +using namespace llvm; + +namespace { + +struct CountsDestructors { + static unsigned NumCalls; + ~CountsDestructors() { ++NumCalls; } +}; +unsigned CountsDestructors::NumCalls = 0; + +struct MoveOnly { + int V; + explicit MoveOnly(int V) : V(V) {} + MoveOnly() = delete; + MoveOnly(MoveOnly &&X) { V = X.V; } + MoveOnly(const MoveOnly &X) = delete; + MoveOnly &operator=(MoveOnly &&X) = delete; + MoveOnly &operator=(const MoveOnly &X) = delete; +}; + +struct EmplaceOnly { + int V1, V2; + explicit EmplaceOnly(int V1, int V2) : V1(V1), V2(V2) {} + EmplaceOnly() = delete; + EmplaceOnly(EmplaceOnly &&X) = delete; + EmplaceOnly(const EmplaceOnly &X) = delete; + EmplaceOnly &operator=(EmplaceOnly &&X) = delete; + EmplaceOnly &operator=(const EmplaceOnly &X) = delete; +}; + +TEST(BumpPtrListTest, DefaultConstructor) { + BumpPtrList L; + EXPECT_TRUE(L.empty()); +} + +TEST(BumpPtrListTest, pushPopBack) { + // Build a list with push_back. + BumpPtrList L; + int Ns[] = {1, 3, 9, 5, 7}; + for (const int N : Ns) + L.push_back(N); + + // Use iterators to check contents. + auto I = L.begin(); + for (int N : Ns) + EXPECT_EQ(N, *I++); + EXPECT_EQ(I, L.end()); + + // Unbuild the list with pop_back. + for (int N : llvm::reverse(Ns)) { + EXPECT_EQ(N, L.back()); + L.pop_back(); + } + EXPECT_TRUE(L.empty()); +} + +TEST(BumpPtrListTest, pushPopFront) { + // Build a list with push_front. + BumpPtrList L; + int Ns[] = {1, 3, 9, 5, 7}; + for (const int N : Ns) + L.push_front(N); + + // Use reverse iterators to check contents. + auto I = L.rbegin(); + for (int N : Ns) + EXPECT_EQ(N, *I++); + EXPECT_EQ(I, L.rend()); + + // Unbuild the list with pop_front. + for (int N : llvm::reverse(Ns)) { + EXPECT_EQ(N, L.front()); + L.pop_front(); + } + EXPECT_TRUE(L.empty()); +} + +TEST(BumpPtrListTest, pushBackMoveOnly) { + BumpPtrList L; + int Ns[] = {1, 3, 9, 5, 7}; + for (const int N : Ns) { + L.push_back(MoveOnly(N)); + EXPECT_EQ(N, L.back().V); + } + // Instantiate with MoveOnly. + while (!L.empty()) + L.pop_back(); +} + +TEST(BumpPtrListTest, pushFrontMoveOnly) { + BumpPtrList L; + int Ns[] = {1, 3, 9, 5, 7}; + for (const int N : Ns) { + L.push_front(MoveOnly(N)); + EXPECT_EQ(N, L.front().V); + } + // Instantiate with MoveOnly. + while (!L.empty()) + L.pop_front(); +} + +TEST(BumpPtrListTest, emplaceBack) { + BumpPtrList L; + int N1s[] = {1, 3, 9, 5, 7}; + int N2s[] = {7, 3, 1, 8, 2}; + for (int I = 0; I != 5; ++I) { + L.emplace_back(N1s[I], N2s[I]); + EXPECT_EQ(N1s[I], L.back().V1); + EXPECT_EQ(N2s[I], L.back().V2); + } + // Instantiate with EmplaceOnly. + while (!L.empty()) + L.pop_back(); +} + +TEST(BumpPtrListTest, emplaceFront) { + BumpPtrList L; + int N1s[] = {1, 3, 9, 5, 7}; + int N2s[] = {7, 3, 1, 8, 2}; + for (int I = 0; I != 5; ++I) { + L.emplace_front(N1s[I], N2s[I]); + EXPECT_EQ(N1s[I], L.front().V1); + EXPECT_EQ(N2s[I], L.front().V2); + } + // Instantiate with EmplaceOnly. + while (!L.empty()) + L.pop_front(); +} + +TEST(BumpPtrListTest, swap) { + // Build two lists with different lifetimes and swap them. + int N1s[] = {1, 3, 5, 7, 9}; + int N2s[] = {2, 4, 6, 8, 10}; + + BumpPtrList L1; + L1.insert(L1.end(), std::begin(N1s), std::end(N1s)); + { + BumpPtrList L2; + L2.insert(L2.end(), std::begin(N2s), std::end(N2s)); + + // Swap the lists. + L1.swap(L2); + + // Check L2's contents before it goes out of scope. + auto I = L2.begin(); + for (int N : N1s) + EXPECT_EQ(N, *I++); + EXPECT_EQ(I, L2.end()); + } + + // Check L1's contents now that L2 is out of scope (with its allocation + // blocks). + auto I = L1.begin(); + for (int N : N2s) + EXPECT_EQ(N, *I++); + EXPECT_EQ(I, L1.end()); +} + +TEST(BumpPtrListTest, clear) { + CountsDestructors::NumCalls = 0; + CountsDestructors N; + BumpPtrList L; + L.push_back(N); + L.push_back(N); + L.push_back(N); + EXPECT_EQ(3u, L.size()); + EXPECT_EQ(0u, CountsDestructors::NumCalls); + L.pop_back(); + EXPECT_EQ(1u, CountsDestructors::NumCalls); + L.clear(); + EXPECT_EQ(3u, CountsDestructors::NumCalls); +} + +TEST(BumpPtrListTest, move) { + BumpPtrList L1, L2; + L1.push_back(1); + L2.push_back(2); + L1 = std::move(L2); + EXPECT_EQ(1u, L1.size()); + EXPECT_EQ(2, L1.front()); + EXPECT_EQ(0u, L2.size()); +} + +TEST(BumpPtrListTest, moveCallsDestructors) { + CountsDestructors::NumCalls = 0; + BumpPtrList L1, L2; + L1.emplace_back(); + EXPECT_EQ(0u, CountsDestructors::NumCalls); + L1 = std::move(L2); + EXPECT_EQ(1u, CountsDestructors::NumCalls); +} + +TEST(BumpPtrListTest, copy) { + BumpPtrList L1, L2; + L1.push_back(1); + L2.push_back(2); + L1 = L2; + EXPECT_EQ(1u, L1.size()); + EXPECT_EQ(2, L1.front()); + EXPECT_EQ(1u, L2.size()); + EXPECT_EQ(2, L2.front()); +} + +TEST(BumpPtrListTest, copyCallsDestructors) { + CountsDestructors::NumCalls = 0; + BumpPtrList L1, L2; + L1.emplace_back(); + EXPECT_EQ(0u, CountsDestructors::NumCalls); + L1 = L2; + EXPECT_EQ(1u, CountsDestructors::NumCalls); +} + +TEST(BumpPtrListTest, resetAlloc) { + // Resetting an empty list should work. + BumpPtrList L; + + // Resetting an empty list that has allocated should also work. + L.resetAlloc(); + L.push_back(5); + L.erase(L.begin()); + L.resetAlloc(); + + // Resetting a non-empty list should crash. + L.push_back(5); +#if defined(GTEST_HAS_DEATH_TEST) && !defined(NDEBUG) + EXPECT_DEATH(L.resetAlloc(), "Cannot reset allocator if not empty"); +#endif +} + +} // end namespace diff --git a/llvm/unittests/ADT/CMakeLists.txt b/llvm/unittests/ADT/CMakeLists.txt index c8cadc560a62..686e6562af68 100644 --- a/llvm/unittests/ADT/CMakeLists.txt +++ b/llvm/unittests/ADT/CMakeLists.txt @@ -9,6 +9,7 @@ set(ADTSources ArrayRefTest.cpp BitmaskEnumTest.cpp BitVectorTest.cpp + BumpPtrListTest.cpp DAGDeltaAlgorithmTest.cpp DeltaAlgorithmTest.cpp DenseMapTest.cpp