1 | //===- ArrayList.h ----------------------------------------------*- C++ -*-===// |
2 | // |
3 | // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
4 | // See https://llvm.org/LICENSE.txt for license information. |
5 | // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
6 | // |
7 | //===----------------------------------------------------------------------===// |
8 | |
9 | #ifndef LLVM_LIB_DWARFLINKER_PARALLEL_ARRAYLIST_H |
10 | #define LLVM_LIB_DWARFLINKER_PARALLEL_ARRAYLIST_H |
11 | |
12 | #include "llvm/Support/PerThreadBumpPtrAllocator.h" |
13 | #include <atomic> |
14 | |
15 | namespace llvm { |
16 | namespace dwarf_linker { |
17 | namespace parallel { |
18 | |
19 | /// This class is a simple list of T structures. It keeps elements as |
20 | /// pre-allocated groups to save memory for each element's next pointer. |
21 | /// It allocates internal data using specified per-thread BumpPtrAllocator. |
22 | /// Method add() can be called asynchronously. |
23 | template <typename T, size_t ItemsGroupSize = 512> class ArrayList { |
24 | public: |
25 | ArrayList(llvm::parallel::PerThreadBumpPtrAllocator *Allocator) |
26 | : Allocator(Allocator) {} |
27 | |
28 | /// Add specified \p Item to the list. |
29 | T &add(const T &Item) { |
30 | assert(Allocator); |
31 | |
32 | // Allocate head group if it is not allocated yet. |
33 | while (!LastGroup) { |
34 | if (allocateNewGroup(AtomicGroup&: GroupsHead)) |
35 | LastGroup = GroupsHead.load(); |
36 | } |
37 | |
38 | ItemsGroup *CurGroup; |
39 | size_t CurItemsCount; |
40 | do { |
41 | CurGroup = LastGroup; |
42 | CurItemsCount = CurGroup->ItemsCount.fetch_add(1); |
43 | |
44 | // Check whether current group is full. |
45 | if (CurItemsCount < ItemsGroupSize) |
46 | break; |
47 | |
48 | // Allocate next group if necessary. |
49 | if (!CurGroup->Next) |
50 | allocateNewGroup(AtomicGroup&: CurGroup->Next); |
51 | |
52 | LastGroup.compare_exchange_weak(CurGroup, CurGroup->Next); |
53 | } while (true); |
54 | |
55 | // Store item into the current group. |
56 | CurGroup->Items[CurItemsCount] = Item; |
57 | return CurGroup->Items[CurItemsCount]; |
58 | } |
59 | |
60 | using ItemHandlerTy = function_ref<void(T &)>; |
61 | |
62 | /// Enumerate all items and apply specified \p Handler to each. |
63 | void forEach(ItemHandlerTy Handler) { |
64 | for (ItemsGroup *CurGroup = GroupsHead; CurGroup; |
65 | CurGroup = CurGroup->Next) { |
66 | for (T &Item : *CurGroup) |
67 | Handler(Item); |
68 | } |
69 | } |
70 | |
71 | /// Check whether list is empty. |
72 | bool empty() { return !GroupsHead; } |
73 | |
74 | /// Erase list. |
75 | void erase() { |
76 | GroupsHead = nullptr; |
77 | LastGroup = nullptr; |
78 | } |
79 | |
80 | void sort(function_ref<bool(const T &LHS, const T &RHS)> Comparator) { |
81 | SmallVector<T> SortedItems; |
82 | forEach(Handler: [&](T &Item) { SortedItems.push_back(Item); }); |
83 | |
84 | if (SortedItems.size()) { |
85 | std::sort(SortedItems.begin(), SortedItems.end(), Comparator); |
86 | |
87 | size_t SortedItemIdx = 0; |
88 | forEach(Handler: [&](T &Item) { Item = SortedItems[SortedItemIdx++]; }); |
89 | assert(SortedItemIdx == SortedItems.size()); |
90 | } |
91 | } |
92 | |
93 | size_t size() { |
94 | size_t Result = 0; |
95 | |
96 | for (ItemsGroup *CurGroup = GroupsHead; CurGroup != nullptr; |
97 | CurGroup = CurGroup->Next) |
98 | Result += CurGroup->getItemsCount(); |
99 | |
100 | return Result; |
101 | } |
102 | |
103 | protected: |
104 | struct ItemsGroup { |
105 | using ArrayTy = std::array<T, ItemsGroupSize>; |
106 | |
107 | // Array of items kept by this group. |
108 | ArrayTy Items; |
109 | |
110 | // Pointer to the next items group. |
111 | std::atomic<ItemsGroup *> Next = nullptr; |
112 | |
113 | // Number of items in this group. |
114 | // NOTE: ItemsCount could be inaccurate as it might be incremented by |
115 | // several threads. Use getItemsCount() method to get real number of items |
116 | // inside ItemsGroup. |
117 | std::atomic<size_t> ItemsCount = 0; |
118 | |
119 | size_t getItemsCount() const { |
120 | return std::min(a: ItemsCount.load(), b: ItemsGroupSize); |
121 | } |
122 | |
123 | typename ArrayTy::iterator begin() { return Items.begin(); } |
124 | typename ArrayTy::iterator end() { return Items.begin() + getItemsCount(); } |
125 | }; |
126 | |
127 | // Allocate new group. Put allocated group into the \p AtomicGroup if |
128 | // it is empty. If \p AtomicGroup is filled by another thread then |
129 | // put allocated group into the end of groups list. |
130 | // \returns true if allocated group is put into the \p AtomicGroup. |
131 | bool allocateNewGroup(std::atomic<ItemsGroup *> &AtomicGroup) { |
132 | ItemsGroup *CurGroup = nullptr; |
133 | |
134 | // Allocate new group. |
135 | ItemsGroup *NewGroup = Allocator->Allocate<ItemsGroup>(); |
136 | NewGroup->ItemsCount = 0; |
137 | NewGroup->Next = nullptr; |
138 | |
139 | // Try to replace current group with allocated one. |
140 | if (AtomicGroup.compare_exchange_weak(CurGroup, NewGroup)) |
141 | return true; |
142 | |
143 | // Put allocated group as last group. |
144 | while (CurGroup) { |
145 | ItemsGroup *NextGroup = CurGroup->Next; |
146 | |
147 | if (!NextGroup) { |
148 | if (CurGroup->Next.compare_exchange_weak(NextGroup, NewGroup)) |
149 | break; |
150 | } |
151 | |
152 | CurGroup = NextGroup; |
153 | } |
154 | |
155 | return false; |
156 | } |
157 | |
158 | std::atomic<ItemsGroup *> GroupsHead = nullptr; |
159 | std::atomic<ItemsGroup *> LastGroup = nullptr; |
160 | llvm::parallel::PerThreadBumpPtrAllocator *Allocator = nullptr; |
161 | }; |
162 | |
163 | } // end of namespace parallel |
164 | } // end of namespace dwarf_linker |
165 | } // end of namespace llvm |
166 | |
167 | #endif // LLVM_LIB_DWARFLINKER_PARALLEL_ARRAYLIST_H |
168 | |