//===-- Interface for freetrie --------------------------------------------===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception // //===----------------------------------------------------------------------===// #ifndef LLVM_LIBC_SRC___SUPPORT_FREETRIE_H #define LLVM_LIBC_SRC___SUPPORT_FREETRIE_H #include "freelist.h" namespace LIBC_NAMESPACE_DECL { /// A trie of free lists. /// /// This is an unusual little data structure originally from Doug Lea's malloc. /// Finding the best fit from a set of differently-sized free list typically /// required some kind of ordered map, and these are typically implemented using /// a self-balancing binary search tree. Those are notorious for having a /// relatively large number of special cases, while this trie has relatively /// few, which helps with code size. /// /// Operations on the trie are logarithmic not on the number of nodes within it, /// but rather the fixed range of possible sizes that the trie can contain. This /// means that the data structure would likely actually perform worse than an /// e.g. red-black tree, but its implementation is still much simpler. /// /// Each trie node's children subdivide the range of possible sizes into two /// halves: a lower and an upper. The node itself holds a free list of some size /// within its range. This makes it possible to summarily replace any node with /// any leaf within its subtrie, which makes it very straightforward to remove a /// node. Insertion is also simple; the only real complexity lies with finding /// the best fit. This can still be done in logarithmic time with only a few /// cases to consider. /// /// The trie refers to, but does not own, the Nodes that comprise it. class FreeTrie { public: /// A trie node that is also a free list. Only the head node of each list is /// actually part of the trie. The subtrie contains a continous SizeRange of /// free lists. The lower and upper subtrie's contain the lower and upper half /// of the subtries range. There is no direct relationship between the size of /// this node's free list and the contents of the lower and upper subtries. class Node : public FreeList::Node { /// The child subtrie covering the lower half of this subtrie's size range. /// Undefined if this is not the head of the list. Node *lower; /// The child subtrie covering the upper half of this subtrie's size range. /// Undefined if this is not the head of the list. Node *upper; /// The parent subtrie. nullptr if this is the root or not the head of the /// list. Node *parent; friend class FreeTrie; }; /// Power-of-two range of sizes covered by a subtrie. struct SizeRange { size_t min; size_t width; LIBC_INLINE constexpr SizeRange(size_t min, size_t width) : min(min), width(width) { LIBC_ASSERT(!(width & (width - 1)) && "width must be a power of two"); } /// @returns The lower half of the size range. LIBC_INLINE SizeRange lower() const { return {min, width / 2}; } /// @returns The upper half of the size range. LIBC_INLINE SizeRange upper() const { return {min + width / 2, width / 2}; } /// @returns The largest size in this range. LIBC_INLINE size_t max() const { return min + (width - 1); } /// @returns Whether the range contains the given size. LIBC_INLINE bool contains(size_t size) const { return min <= size && size < min + width; } }; LIBC_INLINE constexpr FreeTrie() : FreeTrie(SizeRange{0, 0}) {} LIBC_INLINE constexpr FreeTrie(SizeRange range) : range(range) {} /// Sets the range of possible block sizes. This can only be called when the /// trie is empty. LIBC_INLINE void set_range(FreeTrie::SizeRange range) { LIBC_ASSERT(empty() && "cannot change the range of a preexisting trie"); this->range = range; } /// @returns Whether the trie contains any blocks. LIBC_INLINE bool empty() const { return !root; } /// Push a block to the trie. void push(Block *block); /// Remove a node from this trie node's free list. void remove(Node *node); /// @returns A smallest node that can allocate the given size; otherwise /// nullptr. Node *find_best_fit(size_t size); private: /// @returns Whether a node is the head of its containing freelist. bool is_head(Node *node) const { return node->parent || node == root; } /// Replaces references to one node with another (or nullptr) in all adjacent /// parent and child nodes. void replace_node(Node *node, Node *new_node); Node *root = nullptr; SizeRange range; }; LIBC_INLINE void FreeTrie::push(Block *block) { LIBC_ASSERT(block->inner_size_free() >= sizeof(Node) && "block too small to accomodate free trie node"); size_t size = block->inner_size(); LIBC_ASSERT(range.contains(size) && "requested size out of trie range"); // Find the position in the tree to push to. Node **cur = &root; Node *parent = nullptr; SizeRange cur_range = range; while (*cur && (*cur)->size() != size) { LIBC_ASSERT(cur_range.contains(size) && "requested size out of trie range"); parent = *cur; if (size <= cur_range.lower().max()) { cur = &(*cur)->lower; cur_range = cur_range.lower(); } else { cur = &(*cur)->upper; cur_range = cur_range.upper(); } } Node *node = new (block->usable_space()) Node; FreeList list = *cur; if (list.empty()) { node->parent = parent; node->lower = node->upper = nullptr; } else { node->parent = nullptr; } list.push(node); *cur = static_cast(list.begin()); } LIBC_INLINE FreeTrie::Node *FreeTrie::find_best_fit(size_t size) { if (empty() || range.max() < size) return nullptr; Node *cur = root; SizeRange cur_range = range; Node *best_fit = nullptr; Node *deferred_upper_trie = nullptr; FreeTrie::SizeRange deferred_upper_range{0, 0}; while (true) { LIBC_ASSERT(cur_range.contains(cur->size()) && "trie node size out of range"); LIBC_ASSERT(cur_range.max() >= size && "range could not fit requested size"); LIBC_ASSERT((!best_fit || cur_range.min < best_fit->size()) && "range could not contain a best fit"); // If the current node is an exact fit, it is a best fit. if (cur->size() == size) return cur; if (cur->size() > size && (!best_fit || cur->size() < best_fit->size())) { // The current node is a better fit. best_fit = cur; // If there is a deferred upper subtrie, then the current node is // somewhere in its lower sibling subtrie. That means that the new best // fit is better than the best fit in the deferred subtrie. LIBC_ASSERT( (!deferred_upper_trie || deferred_upper_range.min > best_fit->size()) && "deferred upper subtrie should be outclassed by new best fit"); deferred_upper_trie = nullptr; } // Determine which subtries might contain the best fit. bool lower_impossible = !cur->lower || cur_range.lower().max() < size; bool upper_impossible = !cur->upper || // If every node in the lower trie fits (!lower_impossible && cur_range.min >= size) || // If every node in the upper trie is worse than the current best (best_fit && cur_range.upper().min >= best_fit->size()); if (lower_impossible && upper_impossible) { if (!deferred_upper_trie) return best_fit; // Scan the deferred upper subtrie and consider whether any element within // provides a better fit. // // This can only ever be reached once. In a deferred upper subtrie, every // node fits, so the higher of two subtries can never contain a best fit. cur = deferred_upper_trie; cur_range = deferred_upper_range; deferred_upper_trie = nullptr; continue; } if (lower_impossible) { cur = cur->upper; cur_range = cur_range.upper(); } else if (upper_impossible) { cur = cur->lower; cur_range = cur_range.lower(); } else { // Both subtries might contain a better fit. Any fit in the lower subtrie // is better than the any fit in the upper subtrie, so scan the lower // and return to the upper only if no better fits were found. (Any better // fit found clears the deferred upper subtrie.) LIBC_ASSERT((!deferred_upper_trie || cur_range.upper().max() < deferred_upper_range.min) && "old deferred upper subtrie should be outclassed by new"); deferred_upper_trie = cur->upper; deferred_upper_range = cur_range.upper(); cur = cur->lower; cur_range = cur_range.lower(); } } } } // namespace LIBC_NAMESPACE_DECL #endif // LLVM_LIBC_SRC___SUPPORT_FREETRIE_H