Path: blob/main/contrib/llvm-project/libc/src/__support/freetrie.h
213766 views
//===-- Interface for freetrie --------------------------------------------===//1//2// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.3// See https://llvm.org/LICENSE.txt for license information.4// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception5//6//===----------------------------------------------------------------------===//78#ifndef LLVM_LIBC_SRC___SUPPORT_FREETRIE_H9#define LLVM_LIBC_SRC___SUPPORT_FREETRIE_H1011#include "freelist.h"1213namespace LIBC_NAMESPACE_DECL {1415/// A trie of free lists.16///17/// This is an unusual little data structure originally from Doug Lea's malloc.18/// Finding the best fit from a set of differently-sized free list typically19/// required some kind of ordered map, and these are typically implemented using20/// a self-balancing binary search tree. Those are notorious for having a21/// relatively large number of special cases, while this trie has relatively22/// few, which helps with code size.23///24/// Operations on the trie are logarithmic not on the number of nodes within it,25/// but rather the fixed range of possible sizes that the trie can contain. This26/// means that the data structure would likely actually perform worse than an27/// e.g. red-black tree, but its implementation is still much simpler.28///29/// Each trie node's children subdivide the range of possible sizes into two30/// halves: a lower and an upper. The node itself holds a free list of some size31/// within its range. This makes it possible to summarily replace any node with32/// any leaf within its subtrie, which makes it very straightforward to remove a33/// node. Insertion is also simple; the only real complexity lies with finding34/// the best fit. This can still be done in logarithmic time with only a few35/// cases to consider.36///37/// The trie refers to, but does not own, the Nodes that comprise it.38class FreeTrie {39public:40/// A trie node that is also a free list. Only the head node of each list is41/// actually part of the trie. The subtrie contains a continous SizeRange of42/// free lists. The lower and upper subtrie's contain the lower and upper half43/// of the subtries range. There is no direct relationship between the size of44/// this node's free list and the contents of the lower and upper subtries.45class Node : public FreeList::Node {46/// The child subtrie covering the lower half of this subtrie's size range.47/// Undefined if this is not the head of the list.48Node *lower;49/// The child subtrie covering the upper half of this subtrie's size range.50/// Undefined if this is not the head of the list.51Node *upper;52/// The parent subtrie. nullptr if this is the root or not the head of the53/// list.54Node *parent;5556friend class FreeTrie;57};5859/// Power-of-two range of sizes covered by a subtrie.60struct SizeRange {61size_t min;62size_t width;6364LIBC_INLINE constexpr SizeRange(size_t min, size_t width)65: min(min), width(width) {66LIBC_ASSERT(!(width & (width - 1)) && "width must be a power of two");67}6869/// @returns The lower half of the size range.70LIBC_INLINE SizeRange lower() const { return {min, width / 2}; }7172/// @returns The upper half of the size range.73LIBC_INLINE SizeRange upper() const { return {min + width / 2, width / 2}; }7475/// @returns The largest size in this range.76LIBC_INLINE size_t max() const { return min + (width - 1); }7778/// @returns Whether the range contains the given size.79LIBC_INLINE bool contains(size_t size) const {80return min <= size && size < min + width;81}82};8384LIBC_INLINE constexpr FreeTrie() : FreeTrie(SizeRange{0, 0}) {}85LIBC_INLINE constexpr FreeTrie(SizeRange range) : range(range) {}8687/// Sets the range of possible block sizes. This can only be called when the88/// trie is empty.89LIBC_INLINE void set_range(FreeTrie::SizeRange range) {90LIBC_ASSERT(empty() && "cannot change the range of a preexisting trie");91this->range = range;92}9394/// @returns Whether the trie contains any blocks.95LIBC_INLINE bool empty() const { return !root; }9697/// Push a block to the trie.98void push(Block *block);99100/// Remove a node from this trie node's free list.101void remove(Node *node);102103/// @returns A smallest node that can allocate the given size; otherwise104/// nullptr.105Node *find_best_fit(size_t size);106107private:108/// @returns Whether a node is the head of its containing freelist.109bool is_head(Node *node) const { return node->parent || node == root; }110111/// Replaces references to one node with another (or nullptr) in all adjacent112/// parent and child nodes.113void replace_node(Node *node, Node *new_node);114115Node *root = nullptr;116SizeRange range;117};118119LIBC_INLINE void FreeTrie::push(Block *block) {120LIBC_ASSERT(block->inner_size_free() >= sizeof(Node) &&121"block too small to accomodate free trie node");122size_t size = block->inner_size();123LIBC_ASSERT(range.contains(size) && "requested size out of trie range");124125// Find the position in the tree to push to.126Node **cur = &root;127Node *parent = nullptr;128SizeRange cur_range = range;129while (*cur && (*cur)->size() != size) {130LIBC_ASSERT(cur_range.contains(size) && "requested size out of trie range");131parent = *cur;132if (size <= cur_range.lower().max()) {133cur = &(*cur)->lower;134cur_range = cur_range.lower();135} else {136cur = &(*cur)->upper;137cur_range = cur_range.upper();138}139}140141Node *node = new (block->usable_space()) Node;142FreeList list = *cur;143if (list.empty()) {144node->parent = parent;145node->lower = node->upper = nullptr;146} else {147node->parent = nullptr;148}149list.push(node);150*cur = static_cast<Node *>(list.begin());151}152153LIBC_INLINE FreeTrie::Node *FreeTrie::find_best_fit(size_t size) {154if (empty() || range.max() < size)155return nullptr;156157Node *cur = root;158SizeRange cur_range = range;159Node *best_fit = nullptr;160Node *deferred_upper_trie = nullptr;161FreeTrie::SizeRange deferred_upper_range{0, 0};162163while (true) {164LIBC_ASSERT(cur_range.contains(cur->size()) &&165"trie node size out of range");166LIBC_ASSERT(cur_range.max() >= size &&167"range could not fit requested size");168LIBC_ASSERT((!best_fit || cur_range.min < best_fit->size()) &&169"range could not contain a best fit");170171// If the current node is an exact fit, it is a best fit.172if (cur->size() == size)173return cur;174175if (cur->size() > size && (!best_fit || cur->size() < best_fit->size())) {176// The current node is a better fit.177best_fit = cur;178179// If there is a deferred upper subtrie, then the current node is180// somewhere in its lower sibling subtrie. That means that the new best181// fit is better than the best fit in the deferred subtrie.182LIBC_ASSERT(183(!deferred_upper_trie ||184deferred_upper_range.min > best_fit->size()) &&185"deferred upper subtrie should be outclassed by new best fit");186deferred_upper_trie = nullptr;187}188189// Determine which subtries might contain the best fit.190bool lower_impossible = !cur->lower || cur_range.lower().max() < size;191bool upper_impossible =192!cur->upper ||193// If every node in the lower trie fits194(!lower_impossible && cur_range.min >= size) ||195// If every node in the upper trie is worse than the current best196(best_fit && cur_range.upper().min >= best_fit->size());197198if (lower_impossible && upper_impossible) {199if (!deferred_upper_trie)200return best_fit;201// Scan the deferred upper subtrie and consider whether any element within202// provides a better fit.203//204// This can only ever be reached once. In a deferred upper subtrie, every205// node fits, so the higher of two subtries can never contain a best fit.206cur = deferred_upper_trie;207cur_range = deferred_upper_range;208deferred_upper_trie = nullptr;209continue;210}211212if (lower_impossible) {213cur = cur->upper;214cur_range = cur_range.upper();215} else if (upper_impossible) {216cur = cur->lower;217cur_range = cur_range.lower();218} else {219// Both subtries might contain a better fit. Any fit in the lower subtrie220// is better than the any fit in the upper subtrie, so scan the lower221// and return to the upper only if no better fits were found. (Any better222// fit found clears the deferred upper subtrie.)223LIBC_ASSERT((!deferred_upper_trie ||224cur_range.upper().max() < deferred_upper_range.min) &&225"old deferred upper subtrie should be outclassed by new");226deferred_upper_trie = cur->upper;227deferred_upper_range = cur_range.upper();228cur = cur->lower;229cur_range = cur_range.lower();230}231}232}233234} // namespace LIBC_NAMESPACE_DECL235236#endif // LLVM_LIBC_SRC___SUPPORT_FREETRIE_H237238239