| 1 | //===-- Interface for freetrie --------------------------------------------===// |
| 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_LIBC_SRC___SUPPORT_FREETRIE_H |
| 10 | #define LLVM_LIBC_SRC___SUPPORT_FREETRIE_H |
| 11 | |
| 12 | #include "freelist.h" |
| 13 | |
| 14 | namespace LIBC_NAMESPACE_DECL { |
| 15 | |
| 16 | /// A trie of free lists. |
| 17 | /// |
| 18 | /// This is an unusual little data structure originally from Doug Lea's malloc. |
| 19 | /// Finding the best fit from a set of differently-sized free list typically |
| 20 | /// required some kind of ordered map, and these are typically implemented using |
| 21 | /// a self-balancing binary search tree. Those are notorious for having a |
| 22 | /// relatively large number of special cases, while this trie has relatively |
| 23 | /// few, which helps with code size. |
| 24 | /// |
| 25 | /// Operations on the trie are logarithmic not on the number of nodes within it, |
| 26 | /// but rather the fixed range of possible sizes that the trie can contain. This |
| 27 | /// means that the data structure would likely actually perform worse than an |
| 28 | /// e.g. red-black tree, but its implementation is still much simpler. |
| 29 | /// |
| 30 | /// Each trie node's children subdivide the range of possible sizes into two |
| 31 | /// halves: a lower and an upper. The node itself holds a free list of some size |
| 32 | /// within its range. This makes it possible to summarily replace any node with |
| 33 | /// any leaf within its subtrie, which makes it very straightforward to remove a |
| 34 | /// node. Insertion is also simple; the only real complexity lies with finding |
| 35 | /// the best fit. This can still be done in logarithmic time with only a few |
| 36 | /// cases to consider. |
| 37 | /// |
| 38 | /// The trie refers to, but does not own, the Nodes that comprise it. |
| 39 | class FreeTrie { |
| 40 | public: |
| 41 | /// A trie node that is also a free list. Only the head node of each list is |
| 42 | /// actually part of the trie. The subtrie contains a continous SizeRange of |
| 43 | /// free lists. The lower and upper subtrie's contain the lower and upper half |
| 44 | /// of the subtries range. There is no direct relationship between the size of |
| 45 | /// this node's free list and the contents of the lower and upper subtries. |
| 46 | class Node : public FreeList::Node { |
| 47 | /// The child subtrie covering the lower half of this subtrie's size range. |
| 48 | /// Undefined if this is not the head of the list. |
| 49 | Node *lower; |
| 50 | /// The child subtrie covering the upper half of this subtrie's size range. |
| 51 | /// Undefined if this is not the head of the list. |
| 52 | Node *upper; |
| 53 | /// The parent subtrie. nullptr if this is the root or not the head of the |
| 54 | /// list. |
| 55 | Node *parent; |
| 56 | |
| 57 | friend class FreeTrie; |
| 58 | }; |
| 59 | |
| 60 | /// Power-of-two range of sizes covered by a subtrie. |
| 61 | struct SizeRange { |
| 62 | size_t min; |
| 63 | size_t width; |
| 64 | |
| 65 | LIBC_INLINE constexpr SizeRange(size_t min, size_t width) |
| 66 | : min(min), width(width) { |
| 67 | LIBC_ASSERT(!(width & (width - 1)) && "width must be a power of two" ); |
| 68 | } |
| 69 | |
| 70 | /// @returns The lower half of the size range. |
| 71 | LIBC_INLINE SizeRange lower() const { return {min, width / 2}; } |
| 72 | |
| 73 | /// @returns The upper half of the size range. |
| 74 | LIBC_INLINE SizeRange upper() const { return {min + width / 2, width / 2}; } |
| 75 | |
| 76 | /// @returns The largest size in this range. |
| 77 | LIBC_INLINE size_t max() const { return min + (width - 1); } |
| 78 | |
| 79 | /// @returns Whether the range contains the given size. |
| 80 | LIBC_INLINE bool contains(size_t size) const { |
| 81 | return min <= size && size < min + width; |
| 82 | } |
| 83 | }; |
| 84 | |
| 85 | LIBC_INLINE constexpr FreeTrie() : FreeTrie(SizeRange{0, 0}) {} |
| 86 | LIBC_INLINE constexpr FreeTrie(SizeRange range) : range(range) {} |
| 87 | |
| 88 | /// Sets the range of possible block sizes. This can only be called when the |
| 89 | /// trie is empty. |
| 90 | LIBC_INLINE void set_range(FreeTrie::SizeRange range) { |
| 91 | LIBC_ASSERT(empty() && "cannot change the range of a preexisting trie" ); |
| 92 | this->range = range; |
| 93 | } |
| 94 | |
| 95 | /// @returns Whether the trie contains any blocks. |
| 96 | LIBC_INLINE bool empty() const { return !root; } |
| 97 | |
| 98 | /// Push a block to the trie. |
| 99 | void push(Block *block); |
| 100 | |
| 101 | /// Remove a node from this trie node's free list. |
| 102 | void remove(Node *node); |
| 103 | |
| 104 | /// @returns A smallest node that can allocate the given size; otherwise |
| 105 | /// nullptr. |
| 106 | Node *find_best_fit(size_t size); |
| 107 | |
| 108 | private: |
| 109 | /// @returns Whether a node is the head of its containing freelist. |
| 110 | bool is_head(Node *node) const { return node->parent || node == root; } |
| 111 | |
| 112 | /// Replaces references to one node with another (or nullptr) in all adjacent |
| 113 | /// parent and child nodes. |
| 114 | void replace_node(Node *node, Node *new_node); |
| 115 | |
| 116 | Node *root = nullptr; |
| 117 | SizeRange range; |
| 118 | }; |
| 119 | |
| 120 | LIBC_INLINE void FreeTrie::push(Block *block) { |
| 121 | LIBC_ASSERT(block->inner_size_free() >= sizeof(Node) && |
| 122 | "block too small to accomodate free trie node" ); |
| 123 | size_t size = block->inner_size(); |
| 124 | LIBC_ASSERT(range.contains(size) && "requested size out of trie range" ); |
| 125 | |
| 126 | // Find the position in the tree to push to. |
| 127 | Node **cur = &root; |
| 128 | Node *parent = nullptr; |
| 129 | SizeRange cur_range = range; |
| 130 | while (*cur && (*cur)->size() != size) { |
| 131 | LIBC_ASSERT(cur_range.contains(size) && "requested size out of trie range" ); |
| 132 | parent = *cur; |
| 133 | if (size <= cur_range.lower().max()) { |
| 134 | cur = &(*cur)->lower; |
| 135 | cur_range = cur_range.lower(); |
| 136 | } else { |
| 137 | cur = &(*cur)->upper; |
| 138 | cur_range = cur_range.upper(); |
| 139 | } |
| 140 | } |
| 141 | |
| 142 | Node *node = new (block->usable_space()) Node; |
| 143 | FreeList list = *cur; |
| 144 | if (list.empty()) { |
| 145 | node->parent = parent; |
| 146 | node->lower = node->upper = nullptr; |
| 147 | } else { |
| 148 | node->parent = nullptr; |
| 149 | } |
| 150 | list.push(node); |
| 151 | *cur = static_cast<Node *>(list.begin()); |
| 152 | } |
| 153 | |
| 154 | LIBC_INLINE FreeTrie::Node *FreeTrie::find_best_fit(size_t size) { |
| 155 | if (empty() || range.max() < size) |
| 156 | return nullptr; |
| 157 | |
| 158 | Node *cur = root; |
| 159 | SizeRange cur_range = range; |
| 160 | Node *best_fit = nullptr; |
| 161 | Node *deferred_upper_trie = nullptr; |
| 162 | FreeTrie::SizeRange deferred_upper_range{0, 0}; |
| 163 | |
| 164 | while (true) { |
| 165 | LIBC_ASSERT(cur_range.contains(cur->size()) && |
| 166 | "trie node size out of range" ); |
| 167 | LIBC_ASSERT(cur_range.max() >= size && |
| 168 | "range could not fit requested size" ); |
| 169 | LIBC_ASSERT((!best_fit || cur_range.min < best_fit->size()) && |
| 170 | "range could not contain a best fit" ); |
| 171 | |
| 172 | // If the current node is an exact fit, it is a best fit. |
| 173 | if (cur->size() == size) |
| 174 | return cur; |
| 175 | |
| 176 | if (cur->size() > size && (!best_fit || cur->size() < best_fit->size())) { |
| 177 | // The current node is a better fit. |
| 178 | best_fit = cur; |
| 179 | |
| 180 | // If there is a deferred upper subtrie, then the current node is |
| 181 | // somewhere in its lower sibling subtrie. That means that the new best |
| 182 | // fit is better than the best fit in the deferred subtrie. |
| 183 | LIBC_ASSERT( |
| 184 | (!deferred_upper_trie || |
| 185 | deferred_upper_range.min > best_fit->size()) && |
| 186 | "deferred upper subtrie should be outclassed by new best fit" ); |
| 187 | deferred_upper_trie = nullptr; |
| 188 | } |
| 189 | |
| 190 | // Determine which subtries might contain the best fit. |
| 191 | bool lower_impossible = !cur->lower || cur_range.lower().max() < size; |
| 192 | bool upper_impossible = |
| 193 | !cur->upper || |
| 194 | // If every node in the lower trie fits |
| 195 | (!lower_impossible && cur_range.min >= size) || |
| 196 | // If every node in the upper trie is worse than the current best |
| 197 | (best_fit && cur_range.upper().min >= best_fit->size()); |
| 198 | |
| 199 | if (lower_impossible && upper_impossible) { |
| 200 | if (!deferred_upper_trie) |
| 201 | return best_fit; |
| 202 | // Scan the deferred upper subtrie and consider whether any element within |
| 203 | // provides a better fit. |
| 204 | // |
| 205 | // This can only ever be reached once. In a deferred upper subtrie, every |
| 206 | // node fits, so the higher of two subtries can never contain a best fit. |
| 207 | cur = deferred_upper_trie; |
| 208 | cur_range = deferred_upper_range; |
| 209 | deferred_upper_trie = nullptr; |
| 210 | continue; |
| 211 | } |
| 212 | |
| 213 | if (lower_impossible) { |
| 214 | cur = cur->upper; |
| 215 | cur_range = cur_range.upper(); |
| 216 | } else if (upper_impossible) { |
| 217 | cur = cur->lower; |
| 218 | cur_range = cur_range.lower(); |
| 219 | } else { |
| 220 | // Both subtries might contain a better fit. Any fit in the lower subtrie |
| 221 | // is better than the any fit in the upper subtrie, so scan the lower |
| 222 | // and return to the upper only if no better fits were found. (Any better |
| 223 | // fit found clears the deferred upper subtrie.) |
| 224 | LIBC_ASSERT((!deferred_upper_trie || |
| 225 | cur_range.upper().max() < deferred_upper_range.min) && |
| 226 | "old deferred upper subtrie should be outclassed by new" ); |
| 227 | deferred_upper_trie = cur->upper; |
| 228 | deferred_upper_range = cur_range.upper(); |
| 229 | cur = cur->lower; |
| 230 | cur_range = cur_range.lower(); |
| 231 | } |
| 232 | } |
| 233 | } |
| 234 | |
| 235 | } // namespace LIBC_NAMESPACE_DECL |
| 236 | |
| 237 | #endif // LLVM_LIBC_SRC___SUPPORT_FREETRIE_H |
| 238 | |