|
1 |
| -//===-- Interface for freelist_malloc -------------------------------------===// |
| 1 | +//===-- Interface for freelist --------------------------------------------===// |
2 | 2 | //
|
3 | 3 | // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
|
4 | 4 | // See https://llvm.org/LICENSE.txt for license information.
|
|
9 | 9 | #ifndef LLVM_LIBC_SRC___SUPPORT_FREELIST_H
|
10 | 10 | #define LLVM_LIBC_SRC___SUPPORT_FREELIST_H
|
11 | 11 |
|
12 |
| -#include "src/__support/CPP/array.h" |
13 |
| -#include "src/__support/CPP/cstddef.h" |
14 |
| -#include "src/__support/CPP/new.h" |
15 |
| -#include "src/__support/CPP/span.h" |
16 |
| -#include "src/__support/fixedvector.h" |
17 |
| -#include "src/__support/macros/config.h" |
| 12 | +#include "block.h" |
18 | 13 |
|
19 | 14 | namespace LIBC_NAMESPACE_DECL {
|
20 | 15 |
|
21 |
| -using cpp::span; |
22 |
| - |
23 |
| -/// Basic [freelist](https://en.wikipedia.org/wiki/Free_list) implementation |
24 |
| -/// for an allocator. This implementation buckets by chunk size, with a list |
25 |
| -/// of user-provided buckets. Each bucket is a linked list of storage chunks. |
26 |
| -/// Because this freelist uses the added chunks themselves as list nodes, there |
27 |
| -/// is a lower bound of `sizeof(FreeList.FreeListNode)` bytes for chunks which |
28 |
| -/// can be added to this freelist. There is also an implicit bucket for |
29 |
| -/// "everything else", for chunks which do not fit into a bucket. |
30 |
| -/// |
31 |
| -/// Each added chunk will be added to the smallest bucket under which it fits. |
32 |
| -/// If it does not fit into any user-provided bucket, it will be added to the |
33 |
| -/// default bucket. |
34 |
| -/// |
35 |
| -/// As an example, assume that the `FreeList` is configured with buckets of |
36 |
| -/// sizes {64, 128, 256, and 512} bytes. The internal state may look like the |
37 |
| -/// following: |
38 |
| -/// |
39 |
| -/// @code{.unparsed} |
40 |
| -/// bucket[0] (64B) --> chunk[12B] --> chunk[42B] --> chunk[64B] --> NULL |
41 |
| -/// bucket[1] (128B) --> chunk[65B] --> chunk[72B] --> NULL |
42 |
| -/// bucket[2] (256B) --> NULL |
43 |
| -/// bucket[3] (512B) --> chunk[312B] --> chunk[512B] --> chunk[416B] --> NULL |
44 |
| -/// bucket[4] (implicit) --> chunk[1024B] --> chunk[513B] --> NULL |
45 |
| -/// @endcode |
| 16 | +/// A circularly-linked FIFO list storing free Blocks. All Blocks on a list |
| 17 | +/// are the same size. |
46 | 18 | ///
|
47 |
| -/// Note that added chunks should be aligned to a 4-byte boundary. |
48 |
| -template <size_t NUM_BUCKETS = 6> class FreeList { |
| 19 | +/// Allocating free blocks in FIFO order maximizes the amount of time before a |
| 20 | +/// free block is reused. This in turn maximizes the number of opportunities for |
| 21 | +/// it to be coalesced with an adjacent block, which tends to reduce heap |
| 22 | +/// fragmentation. |
| 23 | +class FreeList { |
49 | 24 | public:
|
50 |
| - // Remove copy/move ctors |
51 |
| - FreeList(const FreeList &other) = delete; |
52 |
| - FreeList(FreeList &&other) = delete; |
53 |
| - FreeList &operator=(const FreeList &other) = delete; |
54 |
| - FreeList &operator=(FreeList &&other) = delete; |
55 |
| - |
56 |
| - /// Adds a chunk to this freelist. |
57 |
| - bool add_chunk(cpp::span<cpp::byte> chunk); |
58 |
| - |
59 |
| - /// Finds an eligible chunk for an allocation of size `size`. |
60 |
| - /// |
61 |
| - /// @note This returns the first allocation possible within a given bucket; |
62 |
| - /// It does not currently optimize for finding the smallest chunk. |
63 |
| - /// |
64 |
| - /// @returns |
65 |
| - /// * On success - A span representing the chunk. |
66 |
| - /// * On failure (e.g. there were no chunks available for that allocation) - |
67 |
| - /// A span with a size of 0. |
68 |
| - cpp::span<cpp::byte> find_chunk(size_t size) const; |
69 |
| - |
70 |
| - template <typename Cond> cpp::span<cpp::byte> find_chunk_if(Cond op) const; |
71 |
| - |
72 |
| - /// Removes a chunk from this freelist. |
73 |
| - bool remove_chunk(cpp::span<cpp::byte> chunk); |
74 |
| - |
75 |
| - /// For a given size, find which index into chunks_ the node should be written |
76 |
| - /// to. |
77 |
| - constexpr size_t find_chunk_ptr_for_size(size_t size, bool non_null) const; |
78 |
| - |
79 |
| - struct FreeListNode { |
80 |
| - FreeListNode *next; |
81 |
| - size_t size; |
82 |
| - }; |
83 |
| - |
84 |
| - constexpr void set_freelist_node(FreeListNode &node, |
85 |
| - cpp::span<cpp::byte> chunk); |
86 |
| - |
87 |
| - constexpr explicit FreeList(const cpp::array<size_t, NUM_BUCKETS> &sizes) |
88 |
| - : chunks_(NUM_BUCKETS + 1, 0), sizes_(sizes.begin(), sizes.end()) {} |
89 |
| - |
90 |
| -private: |
91 |
| - FixedVector<FreeList::FreeListNode *, NUM_BUCKETS + 1> chunks_; |
92 |
| - FixedVector<size_t, NUM_BUCKETS> sizes_; |
93 |
| -}; |
94 |
| - |
95 |
| -template <size_t NUM_BUCKETS> |
96 |
| -constexpr void FreeList<NUM_BUCKETS>::set_freelist_node(FreeListNode &node, |
97 |
| - span<cpp::byte> chunk) { |
98 |
| - // Add it to the correct list. |
99 |
| - size_t chunk_ptr = find_chunk_ptr_for_size(chunk.size(), false); |
100 |
| - node.size = chunk.size(); |
101 |
| - node.next = chunks_[chunk_ptr]; |
102 |
| - chunks_[chunk_ptr] = &node; |
103 |
| -} |
| 25 | + class Node { |
| 26 | + public: |
| 27 | + /// @returns The block containing this node. |
| 28 | + Block<> *block() const { |
| 29 | + return const_cast<Block<> *>(Block<>::from_usable_space(this)); |
| 30 | + } |
104 | 31 |
|
105 |
| -template <size_t NUM_BUCKETS> |
106 |
| -bool FreeList<NUM_BUCKETS>::add_chunk(span<cpp::byte> chunk) { |
107 |
| - // Check that the size is enough to actually store what we need |
108 |
| - if (chunk.size() < sizeof(FreeListNode)) |
109 |
| - return false; |
| 32 | + /// @returns The inner size of blocks in the list containing this node. |
| 33 | + size_t size() const { return block()->inner_size(); } |
110 | 34 |
|
111 |
| - FreeListNode *node = ::new (chunk.data()) FreeListNode; |
112 |
| - set_freelist_node(*node, chunk); |
| 35 | + private: |
| 36 | + // Circularly linked pointers to adjacent nodes. |
| 37 | + Node *prev; |
| 38 | + Node *next; |
| 39 | + friend class FreeList; |
| 40 | + }; |
113 | 41 |
|
114 |
| - return true; |
115 |
| -} |
| 42 | + constexpr FreeList() : FreeList(nullptr) {} |
| 43 | + constexpr FreeList(Node *begin) : begin_(begin) {} |
116 | 44 |
|
117 |
| -template <size_t NUM_BUCKETS> |
118 |
| -template <typename Cond> |
119 |
| -span<cpp::byte> FreeList<NUM_BUCKETS>::find_chunk_if(Cond op) const { |
120 |
| - for (FreeListNode *node : chunks_) { |
121 |
| - while (node != nullptr) { |
122 |
| - span<cpp::byte> chunk(reinterpret_cast<cpp::byte *>(node), node->size); |
123 |
| - if (op(chunk)) |
124 |
| - return chunk; |
| 45 | + bool empty() const { return !begin_; } |
125 | 46 |
|
126 |
| - node = node->next; |
127 |
| - } |
| 47 | + /// @returns The inner size of blocks in the list. |
| 48 | + size_t size() const { |
| 49 | + LIBC_ASSERT(begin_ && "empty lists have no size"); |
| 50 | + return begin_->size(); |
128 | 51 | }
|
129 | 52 |
|
130 |
| - return {}; |
131 |
| -} |
| 53 | + /// @returns The first node in the list. |
| 54 | + Node *begin() { return begin_; } |
132 | 55 |
|
133 |
| -template <size_t NUM_BUCKETS> |
134 |
| -span<cpp::byte> FreeList<NUM_BUCKETS>::find_chunk(size_t size) const { |
135 |
| - if (size == 0) |
136 |
| - return span<cpp::byte>(); |
| 56 | + /// @returns The first block in the list. |
| 57 | + Block<> *front() { return begin_->block(); } |
137 | 58 |
|
138 |
| - size_t chunk_ptr = find_chunk_ptr_for_size(size, true); |
| 59 | + /// Push a block to the back of the list. |
| 60 | + /// The block must be large enough to contain a node. |
| 61 | + void push(Block<> *block); |
139 | 62 |
|
140 |
| - // Check that there's data. This catches the case where we run off the |
141 |
| - // end of the array |
142 |
| - if (chunks_[chunk_ptr] == nullptr) |
143 |
| - return span<cpp::byte>(); |
| 63 | + /// Push an already-constructed node to the back of the list. |
| 64 | + /// This allows pushing derived node types with additional data. |
| 65 | + void push(Node *node); |
144 | 66 |
|
145 |
| - // Now iterate up the buckets, walking each list to find a good candidate |
146 |
| - for (size_t i = chunk_ptr; i < chunks_.size(); i++) { |
147 |
| - FreeListNode *node = chunks_[static_cast<unsigned short>(i)]; |
| 67 | + /// Pop the first node from the list. |
| 68 | + void pop(); |
148 | 69 |
|
149 |
| - while (node != nullptr) { |
150 |
| - if (node->size >= size) |
151 |
| - return span<cpp::byte>(reinterpret_cast<cpp::byte *>(node), node->size); |
| 70 | + /// Remove an arbitrary node from the list. |
| 71 | + void remove(Node *node); |
152 | 72 |
|
153 |
| - node = node->next; |
154 |
| - } |
155 |
| - } |
| 73 | +private: |
| 74 | + Node *begin_; |
| 75 | +}; |
156 | 76 |
|
157 |
| - // If we get here, we've checked every block in every bucket. There's |
158 |
| - // nothing that can support this allocation. |
159 |
| - return span<cpp::byte>(); |
| 77 | +LIBC_INLINE void FreeList::push(Block<> *block) { |
| 78 | + LIBC_ASSERT(!block->used() && "only free blocks can be placed on free lists"); |
| 79 | + LIBC_ASSERT(block->inner_size_free() >= sizeof(FreeList) && |
| 80 | + "block too small to accomodate free list node"); |
| 81 | + push(new (block->usable_space()) Node); |
160 | 82 | }
|
161 | 83 |
|
162 |
| -template <size_t NUM_BUCKETS> |
163 |
| -bool FreeList<NUM_BUCKETS>::remove_chunk(span<cpp::byte> chunk) { |
164 |
| - size_t chunk_ptr = find_chunk_ptr_for_size(chunk.size(), true); |
165 |
| - |
166 |
| - // Check head first. |
167 |
| - if (chunks_[chunk_ptr] == nullptr) |
168 |
| - return false; |
169 |
| - |
170 |
| - FreeListNode *node = chunks_[chunk_ptr]; |
171 |
| - if (reinterpret_cast<cpp::byte *>(node) == chunk.data()) { |
172 |
| - chunks_[chunk_ptr] = node->next; |
173 |
| - return true; |
| 84 | +LIBC_INLINE void FreeList::push(Node *node) { |
| 85 | + if (begin_) { |
| 86 | + LIBC_ASSERT(Block<>::from_usable_space(node)->outer_size() == |
| 87 | + begin_->block()->outer_size() && |
| 88 | + "freelist entries must have the same size"); |
| 89 | + // Since the list is circular, insert the node immediately before begin_. |
| 90 | + node->prev = begin_->prev; |
| 91 | + node->next = begin_; |
| 92 | + begin_->prev->next = node; |
| 93 | + begin_->prev = node; |
| 94 | + } else { |
| 95 | + begin_ = node->prev = node->next = node; |
174 | 96 | }
|
175 |
| - |
176 |
| - // No? Walk the nodes. |
177 |
| - node = chunks_[chunk_ptr]; |
178 |
| - |
179 |
| - while (node->next != nullptr) { |
180 |
| - if (reinterpret_cast<cpp::byte *>(node->next) == chunk.data()) { |
181 |
| - // Found it, remove this node out of the chain |
182 |
| - node->next = node->next->next; |
183 |
| - return true; |
184 |
| - } |
185 |
| - |
186 |
| - node = node->next; |
187 |
| - } |
188 |
| - |
189 |
| - return false; |
190 | 97 | }
|
191 | 98 |
|
192 |
| -template <size_t NUM_BUCKETS> |
193 |
| -constexpr size_t |
194 |
| -FreeList<NUM_BUCKETS>::find_chunk_ptr_for_size(size_t size, |
195 |
| - bool non_null) const { |
196 |
| - size_t chunk_ptr = 0; |
197 |
| - for (chunk_ptr = 0u; chunk_ptr < sizes_.size(); chunk_ptr++) { |
198 |
| - if (sizes_[chunk_ptr] >= size && |
199 |
| - (!non_null || chunks_[chunk_ptr] != nullptr)) { |
200 |
| - break; |
201 |
| - } |
| 99 | +LIBC_INLINE void FreeList::pop() { remove(begin_); } |
| 100 | + |
| 101 | +LIBC_INLINE void FreeList::remove(Node *node) { |
| 102 | + LIBC_ASSERT(begin_ && "cannot remove from empty list"); |
| 103 | + if (node == node->next) { |
| 104 | + LIBC_ASSERT(node == begin_ && |
| 105 | + "a self-referential node must be the only element"); |
| 106 | + begin_ = nullptr; |
| 107 | + } else { |
| 108 | + node->prev->next = node->next; |
| 109 | + node->next->prev = node->prev; |
| 110 | + if (begin_ == node) |
| 111 | + begin_ = node->next; |
202 | 112 | }
|
203 |
| - |
204 |
| - return chunk_ptr; |
205 | 113 | }
|
206 | 114 |
|
207 | 115 | } // namespace LIBC_NAMESPACE_DECL
|
|
0 commit comments