blob: 42363c2c9e2f4ed5979646b4bb7495634ce3ad8a [file] [log] [blame]
//===-- 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<Node *>(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