1385961d7SDaniel Thornburgh //===-- Interface for freetrie --------------------------------------------===// 2385961d7SDaniel Thornburgh // 3385961d7SDaniel Thornburgh // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. 4385961d7SDaniel Thornburgh // See https://llvm.org/LICENSE.txt for license information. 5385961d7SDaniel Thornburgh // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception 6385961d7SDaniel Thornburgh // 7385961d7SDaniel Thornburgh //===----------------------------------------------------------------------===// 8385961d7SDaniel Thornburgh 9385961d7SDaniel Thornburgh #ifndef LLVM_LIBC_SRC___SUPPORT_FREETRIE_H 10385961d7SDaniel Thornburgh #define LLVM_LIBC_SRC___SUPPORT_FREETRIE_H 11385961d7SDaniel Thornburgh 12385961d7SDaniel Thornburgh #include "freelist.h" 13385961d7SDaniel Thornburgh 14385961d7SDaniel Thornburgh namespace LIBC_NAMESPACE_DECL { 15385961d7SDaniel Thornburgh 16385961d7SDaniel Thornburgh /// A trie of free lists. 17385961d7SDaniel Thornburgh /// 18385961d7SDaniel Thornburgh /// This is an unusual little data structure originally from Doug Lea's malloc. 19385961d7SDaniel Thornburgh /// Finding the best fit from a set of differently-sized free list typically 20385961d7SDaniel Thornburgh /// required some kind of ordered map, and these are typically implemented using 21385961d7SDaniel Thornburgh /// a self-balancing binary search tree. Those are notorious for having a 22385961d7SDaniel Thornburgh /// relatively large number of special cases, while this trie has relatively 23385961d7SDaniel Thornburgh /// few, which helps with code size. 24385961d7SDaniel Thornburgh /// 25385961d7SDaniel Thornburgh /// Operations on the trie are logarithmic not on the number of nodes within it, 26385961d7SDaniel Thornburgh /// but rather the fixed range of possible sizes that the trie can contain. This 27385961d7SDaniel Thornburgh /// means that the data structure would likely actually perform worse than an 28385961d7SDaniel Thornburgh /// e.g. red-black tree, but its implementation is still much simpler. 29385961d7SDaniel Thornburgh /// 30385961d7SDaniel Thornburgh /// Each trie node's children subdivide the range of possible sizes into two 31385961d7SDaniel Thornburgh /// halves: a lower and an upper. The node itself holds a free list of some size 32385961d7SDaniel Thornburgh /// within its range. This makes it possible to summarily replace any node with 33385961d7SDaniel Thornburgh /// any leaf within its subtrie, which makes it very straightforward to remove a 34385961d7SDaniel Thornburgh /// node. Insertion is also simple; the only real complexity lies with finding 35385961d7SDaniel Thornburgh /// the best fit. This can still be done in logarithmic time with only a few 36385961d7SDaniel Thornburgh /// cases to consider. 37385961d7SDaniel Thornburgh /// 38385961d7SDaniel Thornburgh /// The trie refers to, but does not own, the Nodes that comprise it. 39385961d7SDaniel Thornburgh class FreeTrie { 40385961d7SDaniel Thornburgh public: 41385961d7SDaniel Thornburgh /// A trie node that is also a free list. Only the head node of each list is 42385961d7SDaniel Thornburgh /// actually part of the trie. The subtrie contains a continous SizeRange of 43385961d7SDaniel Thornburgh /// free lists. The lower and upper subtrie's contain the lower and upper half 44385961d7SDaniel Thornburgh /// of the subtries range. There is no direct relationship between the size of 45385961d7SDaniel Thornburgh /// this node's free list and the contents of the lower and upper subtries. 46385961d7SDaniel Thornburgh class Node : public FreeList::Node { 47385961d7SDaniel Thornburgh /// The child subtrie covering the lower half of this subtrie's size range. 48385961d7SDaniel Thornburgh /// Undefined if this is not the head of the list. 49385961d7SDaniel Thornburgh Node *lower; 50385961d7SDaniel Thornburgh /// The child subtrie covering the upper half of this subtrie's size range. 51385961d7SDaniel Thornburgh /// Undefined if this is not the head of the list. 52385961d7SDaniel Thornburgh Node *upper; 53385961d7SDaniel Thornburgh /// The parent subtrie. nullptr if this is the root or not the head of the 54385961d7SDaniel Thornburgh /// list. 55385961d7SDaniel Thornburgh Node *parent; 56385961d7SDaniel Thornburgh 57385961d7SDaniel Thornburgh friend class FreeTrie; 58385961d7SDaniel Thornburgh }; 59385961d7SDaniel Thornburgh 60385961d7SDaniel Thornburgh /// Power-of-two range of sizes covered by a subtrie. 61385961d7SDaniel Thornburgh struct SizeRange { 62385961d7SDaniel Thornburgh size_t min; 63385961d7SDaniel Thornburgh size_t width; 64385961d7SDaniel Thornburgh 65385961d7SDaniel Thornburgh LIBC_INLINE constexpr SizeRange(size_t min, size_t width) 66385961d7SDaniel Thornburgh : min(min), width(width) { 67385961d7SDaniel Thornburgh LIBC_ASSERT(!(width & (width - 1)) && "width must be a power of two"); 68385961d7SDaniel Thornburgh } 69385961d7SDaniel Thornburgh 70385961d7SDaniel Thornburgh /// @returns The lower half of the size range. 71385961d7SDaniel Thornburgh LIBC_INLINE SizeRange lower() const { return {min, width / 2}; } 72385961d7SDaniel Thornburgh 73385961d7SDaniel Thornburgh /// @returns The upper half of the size range. 74385961d7SDaniel Thornburgh LIBC_INLINE SizeRange upper() const { return {min + width / 2, width / 2}; } 75385961d7SDaniel Thornburgh 76385961d7SDaniel Thornburgh /// @returns The largest size in this range. 77385961d7SDaniel Thornburgh LIBC_INLINE size_t max() const { return min + (width - 1); } 78385961d7SDaniel Thornburgh 79385961d7SDaniel Thornburgh /// @returns Whether the range contains the given size. 80385961d7SDaniel Thornburgh LIBC_INLINE bool contains(size_t size) const { 81385961d7SDaniel Thornburgh return min <= size && size < min + width; 82385961d7SDaniel Thornburgh } 83385961d7SDaniel Thornburgh }; 84385961d7SDaniel Thornburgh 85385961d7SDaniel Thornburgh LIBC_INLINE constexpr FreeTrie() : FreeTrie(SizeRange{0, 0}) {} 86385961d7SDaniel Thornburgh LIBC_INLINE constexpr FreeTrie(SizeRange range) : range(range) {} 87385961d7SDaniel Thornburgh 88385961d7SDaniel Thornburgh /// Sets the range of possible block sizes. This can only be called when the 89385961d7SDaniel Thornburgh /// trie is empty. 90385961d7SDaniel Thornburgh LIBC_INLINE void set_range(FreeTrie::SizeRange range) { 91385961d7SDaniel Thornburgh LIBC_ASSERT(empty() && "cannot change the range of a preexisting trie"); 92385961d7SDaniel Thornburgh this->range = range; 93385961d7SDaniel Thornburgh } 94385961d7SDaniel Thornburgh 95385961d7SDaniel Thornburgh /// @returns Whether the trie contains any blocks. 96385961d7SDaniel Thornburgh LIBC_INLINE bool empty() const { return !root; } 97385961d7SDaniel Thornburgh 98385961d7SDaniel Thornburgh /// Push a block to the trie. 99*d121d71fSDaniel Thornburgh void push(Block *block); 100385961d7SDaniel Thornburgh 101385961d7SDaniel Thornburgh /// Remove a node from this trie node's free list. 102385961d7SDaniel Thornburgh void remove(Node *node); 103385961d7SDaniel Thornburgh 104385961d7SDaniel Thornburgh /// @returns A smallest node that can allocate the given size; otherwise 105385961d7SDaniel Thornburgh /// nullptr. 106385961d7SDaniel Thornburgh Node *find_best_fit(size_t size); 107385961d7SDaniel Thornburgh 108385961d7SDaniel Thornburgh private: 109385961d7SDaniel Thornburgh /// @returns Whether a node is the head of its containing freelist. 110385961d7SDaniel Thornburgh bool is_head(Node *node) const { return node->parent || node == root; } 111385961d7SDaniel Thornburgh 112385961d7SDaniel Thornburgh /// Replaces references to one node with another (or nullptr) in all adjacent 113385961d7SDaniel Thornburgh /// parent and child nodes. 114385961d7SDaniel Thornburgh void replace_node(Node *node, Node *new_node); 115385961d7SDaniel Thornburgh 116385961d7SDaniel Thornburgh Node *root = nullptr; 117385961d7SDaniel Thornburgh SizeRange range; 118385961d7SDaniel Thornburgh }; 119385961d7SDaniel Thornburgh 120*d121d71fSDaniel Thornburgh LIBC_INLINE void FreeTrie::push(Block *block) { 121385961d7SDaniel Thornburgh LIBC_ASSERT(block->inner_size_free() >= sizeof(Node) && 122385961d7SDaniel Thornburgh "block too small to accomodate free trie node"); 123385961d7SDaniel Thornburgh size_t size = block->inner_size(); 124385961d7SDaniel Thornburgh LIBC_ASSERT(range.contains(size) && "requested size out of trie range"); 125385961d7SDaniel Thornburgh 126385961d7SDaniel Thornburgh // Find the position in the tree to push to. 127385961d7SDaniel Thornburgh Node **cur = &root; 128385961d7SDaniel Thornburgh Node *parent = nullptr; 129385961d7SDaniel Thornburgh SizeRange cur_range = range; 130385961d7SDaniel Thornburgh while (*cur && (*cur)->size() != size) { 131385961d7SDaniel Thornburgh LIBC_ASSERT(cur_range.contains(size) && "requested size out of trie range"); 132385961d7SDaniel Thornburgh parent = *cur; 133385961d7SDaniel Thornburgh if (size <= cur_range.lower().max()) { 134385961d7SDaniel Thornburgh cur = &(*cur)->lower; 135385961d7SDaniel Thornburgh cur_range = cur_range.lower(); 136385961d7SDaniel Thornburgh } else { 137385961d7SDaniel Thornburgh cur = &(*cur)->upper; 138385961d7SDaniel Thornburgh cur_range = cur_range.upper(); 139385961d7SDaniel Thornburgh } 140385961d7SDaniel Thornburgh } 141385961d7SDaniel Thornburgh 142385961d7SDaniel Thornburgh Node *node = new (block->usable_space()) Node; 143385961d7SDaniel Thornburgh FreeList list = *cur; 144385961d7SDaniel Thornburgh if (list.empty()) { 145385961d7SDaniel Thornburgh node->parent = parent; 146385961d7SDaniel Thornburgh node->lower = node->upper = nullptr; 147385961d7SDaniel Thornburgh } else { 148385961d7SDaniel Thornburgh node->parent = nullptr; 149385961d7SDaniel Thornburgh } 150385961d7SDaniel Thornburgh list.push(node); 151385961d7SDaniel Thornburgh *cur = static_cast<Node *>(list.begin()); 152385961d7SDaniel Thornburgh } 153385961d7SDaniel Thornburgh 154385961d7SDaniel Thornburgh LIBC_INLINE FreeTrie::Node *FreeTrie::find_best_fit(size_t size) { 155385961d7SDaniel Thornburgh if (empty() || range.max() < size) 156385961d7SDaniel Thornburgh return nullptr; 157385961d7SDaniel Thornburgh 158385961d7SDaniel Thornburgh Node *cur = root; 159385961d7SDaniel Thornburgh SizeRange cur_range = range; 160385961d7SDaniel Thornburgh Node *best_fit = nullptr; 161385961d7SDaniel Thornburgh Node *deferred_upper_trie = nullptr; 162385961d7SDaniel Thornburgh FreeTrie::SizeRange deferred_upper_range{0, 0}; 163385961d7SDaniel Thornburgh 164385961d7SDaniel Thornburgh while (true) { 165385961d7SDaniel Thornburgh LIBC_ASSERT(cur_range.contains(cur->size()) && 166385961d7SDaniel Thornburgh "trie node size out of range"); 167385961d7SDaniel Thornburgh LIBC_ASSERT(cur_range.max() >= size && 168385961d7SDaniel Thornburgh "range could not fit requested size"); 169385961d7SDaniel Thornburgh LIBC_ASSERT((!best_fit || cur_range.min < best_fit->size()) && 170385961d7SDaniel Thornburgh "range could not contain a best fit"); 171385961d7SDaniel Thornburgh 172385961d7SDaniel Thornburgh // If the current node is an exact fit, it is a best fit. 173385961d7SDaniel Thornburgh if (cur->size() == size) 174385961d7SDaniel Thornburgh return cur; 175385961d7SDaniel Thornburgh 176385961d7SDaniel Thornburgh if (cur->size() > size && (!best_fit || cur->size() < best_fit->size())) { 177385961d7SDaniel Thornburgh // The current node is a better fit. 178385961d7SDaniel Thornburgh best_fit = cur; 179385961d7SDaniel Thornburgh 180385961d7SDaniel Thornburgh // If there is a deferred upper subtrie, then the current node is 181385961d7SDaniel Thornburgh // somewhere in its lower sibling subtrie. That means that the new best 182385961d7SDaniel Thornburgh // fit is better than the best fit in the deferred subtrie. 183385961d7SDaniel Thornburgh LIBC_ASSERT( 184385961d7SDaniel Thornburgh (!deferred_upper_trie || 185385961d7SDaniel Thornburgh deferred_upper_range.min > best_fit->size()) && 186385961d7SDaniel Thornburgh "deferred upper subtrie should be outclassed by new best fit"); 187385961d7SDaniel Thornburgh deferred_upper_trie = nullptr; 188385961d7SDaniel Thornburgh } 189385961d7SDaniel Thornburgh 190385961d7SDaniel Thornburgh // Determine which subtries might contain the best fit. 191385961d7SDaniel Thornburgh bool lower_impossible = !cur->lower || cur_range.lower().max() < size; 192385961d7SDaniel Thornburgh bool upper_impossible = 193385961d7SDaniel Thornburgh !cur->upper || 194385961d7SDaniel Thornburgh // If every node in the lower trie fits 195385961d7SDaniel Thornburgh (!lower_impossible && cur_range.min >= size) || 196385961d7SDaniel Thornburgh // If every node in the upper trie is worse than the current best 197385961d7SDaniel Thornburgh (best_fit && cur_range.upper().min >= best_fit->size()); 198385961d7SDaniel Thornburgh 199385961d7SDaniel Thornburgh if (lower_impossible && upper_impossible) { 200385961d7SDaniel Thornburgh if (!deferred_upper_trie) 201385961d7SDaniel Thornburgh return best_fit; 202385961d7SDaniel Thornburgh // Scan the deferred upper subtrie and consider whether any element within 203385961d7SDaniel Thornburgh // provides a better fit. 204385961d7SDaniel Thornburgh // 205385961d7SDaniel Thornburgh // This can only ever be reached once. In a deferred upper subtrie, every 206385961d7SDaniel Thornburgh // node fits, so the higher of two subtries can never contain a best fit. 207385961d7SDaniel Thornburgh cur = deferred_upper_trie; 208385961d7SDaniel Thornburgh cur_range = deferred_upper_range; 209385961d7SDaniel Thornburgh deferred_upper_trie = nullptr; 210385961d7SDaniel Thornburgh continue; 211385961d7SDaniel Thornburgh } 212385961d7SDaniel Thornburgh 213385961d7SDaniel Thornburgh if (lower_impossible) { 214385961d7SDaniel Thornburgh cur = cur->upper; 215385961d7SDaniel Thornburgh cur_range = cur_range.upper(); 216385961d7SDaniel Thornburgh } else if (upper_impossible) { 217385961d7SDaniel Thornburgh cur = cur->lower; 218385961d7SDaniel Thornburgh cur_range = cur_range.lower(); 219385961d7SDaniel Thornburgh } else { 220385961d7SDaniel Thornburgh // Both subtries might contain a better fit. Any fit in the lower subtrie 221385961d7SDaniel Thornburgh // is better than the any fit in the upper subtrie, so scan the lower 222385961d7SDaniel Thornburgh // and return to the upper only if no better fits were found. (Any better 223385961d7SDaniel Thornburgh // fit found clears the deferred upper subtrie.) 224385961d7SDaniel Thornburgh LIBC_ASSERT((!deferred_upper_trie || 225385961d7SDaniel Thornburgh cur_range.upper().max() < deferred_upper_range.min) && 226385961d7SDaniel Thornburgh "old deferred upper subtrie should be outclassed by new"); 227385961d7SDaniel Thornburgh deferred_upper_trie = cur->upper; 228385961d7SDaniel Thornburgh deferred_upper_range = cur_range.upper(); 229385961d7SDaniel Thornburgh cur = cur->lower; 230385961d7SDaniel Thornburgh cur_range = cur_range.lower(); 231385961d7SDaniel Thornburgh } 232385961d7SDaniel Thornburgh } 233385961d7SDaniel Thornburgh } 234385961d7SDaniel Thornburgh 235385961d7SDaniel Thornburgh } // namespace LIBC_NAMESPACE_DECL 236385961d7SDaniel Thornburgh 237385961d7SDaniel Thornburgh #endif // LLVM_LIBC_SRC___SUPPORT_FREETRIE_H 238