xref: /llvm-project/libc/src/__support/freetrie.h (revision d121d71fc7fcb8c969959147f5f58b31f9c6b251)
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