| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,114 @@ | ||
| //===-- Interface for freestore ------------------------------------------===// | ||
| // | ||
| // 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_FREESTORE_H | ||
| #define LLVM_LIBC_SRC___SUPPORT_FREESTORE_H | ||
|
|
||
| #include "freetrie.h" | ||
|
|
||
| namespace LIBC_NAMESPACE_DECL { | ||
|
|
||
| /// A best-fit store of variously-sized free blocks. Blocks can be inserted and | ||
| /// removed in logarithmic time. | ||
| class FreeStore { | ||
| public: | ||
| FreeStore() = default; | ||
| FreeStore(const FreeStore &other) = delete; | ||
| FreeStore &operator=(const FreeStore &other) = delete; | ||
|
|
||
| /// 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) { | ||
| large_trie.set_range(range); | ||
| } | ||
|
|
||
| /// Insert a free block. If the block is too small to be tracked, nothing | ||
| /// happens. | ||
| void insert(Block<> *block); | ||
|
|
||
| /// Remove a free block. If the block is too small to be tracked, nothing | ||
| /// happens. | ||
| void remove(Block<> *block); | ||
|
|
||
| /// Remove a best-fit free block that can contain the given size when | ||
| /// allocated. Returns nullptr if there is no such block. | ||
| Block<> *remove_best_fit(size_t size); | ||
|
|
||
| private: | ||
| static constexpr size_t ALIGNMENT = alignof(max_align_t); | ||
| static constexpr size_t MIN_OUTER_SIZE = | ||
| align_up(Block<>::BLOCK_OVERHEAD + sizeof(FreeList::Node), ALIGNMENT); | ||
| static constexpr size_t MIN_LARGE_OUTER_SIZE = | ||
| align_up(Block<>::BLOCK_OVERHEAD + sizeof(FreeTrie::Node), ALIGNMENT); | ||
| static constexpr size_t NUM_SMALL_SIZES = | ||
| (MIN_LARGE_OUTER_SIZE - MIN_OUTER_SIZE) / ALIGNMENT; | ||
|
|
||
| LIBC_INLINE static bool too_small(Block<> *block) { | ||
| return block->outer_size() < MIN_OUTER_SIZE; | ||
| } | ||
| LIBC_INLINE static bool is_small(Block<> *block) { | ||
| return block->outer_size() < MIN_LARGE_OUTER_SIZE; | ||
| } | ||
|
|
||
| FreeList &small_list(Block<> *block); | ||
| FreeList *find_best_small_fit(size_t size); | ||
|
|
||
| cpp::array<FreeList, NUM_SMALL_SIZES> small_lists; | ||
| FreeTrie large_trie; | ||
| }; | ||
|
|
||
| LIBC_INLINE void FreeStore::insert(Block<> *block) { | ||
| if (too_small(block)) | ||
| return; | ||
| if (is_small(block)) | ||
| small_list(block).push(block); | ||
| else | ||
| large_trie.push(block); | ||
| } | ||
|
|
||
| LIBC_INLINE void FreeStore::remove(Block<> *block) { | ||
| if (too_small(block)) | ||
| return; | ||
| if (is_small(block)) { | ||
| small_list(block).remove( | ||
| reinterpret_cast<FreeList::Node *>(block->usable_space())); | ||
| } else { | ||
| large_trie.remove( | ||
| reinterpret_cast<FreeTrie::Node *>(block->usable_space())); | ||
| } | ||
| } | ||
|
|
||
| LIBC_INLINE Block<> *FreeStore::remove_best_fit(size_t size) { | ||
| if (FreeList *list = find_best_small_fit(size)) { | ||
| Block<> *block = list->front(); | ||
| list->pop(); | ||
| return block; | ||
| } | ||
| if (FreeTrie::Node *best_fit = large_trie.find_best_fit(size)) { | ||
| Block<> *block = best_fit->block(); | ||
| large_trie.remove(best_fit); | ||
| return block; | ||
| } | ||
| return nullptr; | ||
| } | ||
|
|
||
| LIBC_INLINE FreeList &FreeStore::small_list(Block<> *block) { | ||
| LIBC_ASSERT(is_small(block) && "only legal for small blocks"); | ||
| return small_lists[(block->outer_size() - MIN_OUTER_SIZE) / ALIGNMENT]; | ||
| } | ||
|
|
||
| LIBC_INLINE FreeList *FreeStore::find_best_small_fit(size_t size) { | ||
| for (FreeList &list : small_lists) | ||
| if (!list.empty() && list.size() >= size) | ||
| return &list; | ||
| return nullptr; | ||
| } | ||
|
|
||
| } // namespace LIBC_NAMESPACE_DECL | ||
|
|
||
| #endif // LLVM_LIBC_SRC___SUPPORT_FREESTORE_H |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,64 @@ | ||
| //===-- Implementation 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 | ||
| // | ||
| //===----------------------------------------------------------------------===// | ||
|
|
||
| #include "freetrie.h" | ||
|
|
||
| namespace LIBC_NAMESPACE_DECL { | ||
|
|
||
| void FreeTrie::remove(Node *node) { | ||
| LIBC_ASSERT(!empty() && "cannot remove from empty trie"); | ||
| FreeList list = node; | ||
| list.pop(); | ||
| Node *new_node = static_cast<Node *>(list.begin()); | ||
| if (!new_node) { | ||
| // The freelist is empty. Replace the subtrie root with an arbitrary leaf. | ||
| // This is legal because there is no relationship between the size of the | ||
| // root and its children. | ||
| Node *leaf = node; | ||
| while (leaf->lower || leaf->upper) | ||
| leaf = leaf->lower ? leaf->lower : leaf->upper; | ||
| if (leaf == node) { | ||
| // If the root is a leaf, then removing it empties the subtrie. | ||
| replace_node(node, nullptr); | ||
| return; | ||
| } | ||
|
|
||
| replace_node(leaf, nullptr); | ||
| new_node = leaf; | ||
| } | ||
|
|
||
| if (!is_head(node)) | ||
| return; | ||
|
|
||
| // Copy the trie links to the new head. | ||
| new_node->lower = node->lower; | ||
| new_node->upper = node->upper; | ||
| new_node->parent = node->parent; | ||
| replace_node(node, new_node); | ||
| } | ||
|
|
||
| void FreeTrie::replace_node(Node *node, Node *new_node) { | ||
| LIBC_ASSERT(is_head(node) && "only head nodes contain trie links"); | ||
|
|
||
| if (node->parent) { | ||
| Node *&parent_child = | ||
| node->parent->lower == node ? node->parent->lower : node->parent->upper; | ||
| LIBC_ASSERT(parent_child == node && | ||
| "no reference to child node found in parent"); | ||
| parent_child = new_node; | ||
| } else { | ||
| LIBC_ASSERT(root == node && "non-root node had no parent"); | ||
| root = new_node; | ||
| } | ||
| if (node->lower) | ||
| node->lower->parent = new_node; | ||
| if (node->upper) | ||
| node->upper->parent = new_node; | ||
| } | ||
|
|
||
| } // namespace LIBC_NAMESPACE_DECL |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,237 @@ | ||
| //===-- 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 |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,101 @@ | ||
| //===-- Unittests for a freestore -------------------------------*- C++ -*-===// | ||
| // | ||
| // 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 | ||
| // | ||
| //===----------------------------------------------------------------------===// | ||
|
|
||
| #include <stddef.h> | ||
|
|
||
| #include "src/__support/freestore.h" | ||
| #include "test/UnitTest/Test.h" | ||
|
|
||
| using LIBC_NAMESPACE::Block; | ||
| using LIBC_NAMESPACE::FreeList; | ||
| using LIBC_NAMESPACE::FreeStore; | ||
| using LIBC_NAMESPACE::FreeTrie; | ||
| using LIBC_NAMESPACE::cpp::byte; | ||
| using LIBC_NAMESPACE::cpp::optional; | ||
|
|
||
| // Inserting or removing blocks too small to be tracked does nothing. | ||
| TEST(LlvmLibcFreeStore, TooSmall) { | ||
| byte mem[1024]; | ||
| optional<Block<> *> maybeBlock = Block<>::init(mem); | ||
| ASSERT_TRUE(maybeBlock.has_value()); | ||
| Block<> *too_small = *maybeBlock; | ||
| maybeBlock = too_small->split(sizeof(Block<>::offset_type)); | ||
| ASSERT_TRUE(maybeBlock.has_value()); | ||
| Block<> *remainder = *maybeBlock; | ||
|
|
||
| FreeStore store; | ||
| store.set_range({0, 4096}); | ||
| store.insert(too_small); | ||
| store.insert(remainder); | ||
|
|
||
| EXPECT_EQ(store.remove_best_fit(too_small->inner_size()), remainder); | ||
| store.remove(too_small); | ||
| } | ||
|
|
||
| TEST(LlvmLibcFreeStore, RemoveBestFit) { | ||
| byte mem[1024]; | ||
| optional<Block<> *> maybeBlock = Block<>::init(mem); | ||
| ASSERT_TRUE(maybeBlock.has_value()); | ||
|
|
||
| Block<> *smallest = *maybeBlock; | ||
| maybeBlock = smallest->split(sizeof(FreeList) + sizeof(Block<>::offset_type)); | ||
| ASSERT_TRUE(maybeBlock.has_value()); | ||
| Block<> *largest_small = *maybeBlock; | ||
| maybeBlock = | ||
| largest_small->split(sizeof(FreeTrie::Node) + | ||
| sizeof(Block<>::offset_type) - alignof(max_align_t)); | ||
| ASSERT_TRUE(maybeBlock.has_value()); | ||
| ASSERT_GT(largest_small->inner_size(), smallest->inner_size()); | ||
|
|
||
| Block<> *remainder = *maybeBlock; | ||
|
|
||
| FreeStore store; | ||
| store.set_range({0, 4096}); | ||
| store.insert(smallest); | ||
| store.insert(largest_small); | ||
| store.insert(remainder); | ||
|
|
||
| // Find exact match for smallest. | ||
| ASSERT_EQ(store.remove_best_fit(smallest->inner_size()), smallest); | ||
| store.insert(smallest); | ||
|
|
||
| // Find exact match for largest. | ||
| ASSERT_EQ(store.remove_best_fit(largest_small->inner_size()), largest_small); | ||
| store.insert(largest_small); | ||
|
|
||
| // Search smallest for best fit. | ||
| ASSERT_EQ(store.remove_best_fit(smallest->inner_size() + 1), largest_small); | ||
| store.insert(largest_small); | ||
|
|
||
| // Continue search for best fit to large blocks. | ||
| EXPECT_EQ(store.remove_best_fit(largest_small->inner_size() + 1), remainder); | ||
| } | ||
|
|
||
| TEST(LlvmLibcFreeStore, Remove) { | ||
| byte mem[1024]; | ||
| optional<Block<> *> maybeBlock = Block<>::init(mem); | ||
| ASSERT_TRUE(maybeBlock.has_value()); | ||
|
|
||
| Block<> *small = *maybeBlock; | ||
| maybeBlock = small->split(sizeof(FreeList) + sizeof(Block<>::offset_type)); | ||
| ASSERT_TRUE(maybeBlock.has_value()); | ||
|
|
||
| Block<> *remainder = *maybeBlock; | ||
|
|
||
| FreeStore store; | ||
| store.set_range({0, 4096}); | ||
| store.insert(small); | ||
| store.insert(remainder); | ||
|
|
||
| store.remove(remainder); | ||
| ASSERT_EQ(store.remove_best_fit(remainder->inner_size()), | ||
| static_cast<Block<> *>(nullptr)); | ||
| store.remove(small); | ||
| ASSERT_EQ(store.remove_best_fit(small->inner_size()), | ||
| static_cast<Block<> *>(nullptr)); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,125 @@ | ||
| //===-- Unittests for a freetrie --------------------------------*- C++ -*-===// | ||
| // | ||
| // 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 | ||
| // | ||
| //===----------------------------------------------------------------------===// | ||
|
|
||
| #include <stddef.h> | ||
|
|
||
| #include "src/__support/freetrie.h" | ||
| #include "test/UnitTest/Test.h" | ||
|
|
||
| using LIBC_NAMESPACE::Block; | ||
| using LIBC_NAMESPACE::FreeTrie; | ||
| using LIBC_NAMESPACE::cpp::byte; | ||
| using LIBC_NAMESPACE::cpp::optional; | ||
|
|
||
| TEST(LlvmLibcFreeTrie, FindBestFitRoot) { | ||
| FreeTrie trie({0, 4096}); | ||
| EXPECT_EQ(trie.find_best_fit(123), static_cast<FreeTrie::Node *>(nullptr)); | ||
|
|
||
| byte mem[1024]; | ||
| optional<Block<> *> maybeBlock = Block<>::init(mem); | ||
| ASSERT_TRUE(maybeBlock.has_value()); | ||
| Block<> *block = *maybeBlock; | ||
| trie.push(block); | ||
|
|
||
| FreeTrie::Node *root = trie.find_best_fit(0); | ||
| ASSERT_EQ(root->block(), block); | ||
| EXPECT_EQ(trie.find_best_fit(block->inner_size() - 1), root); | ||
| EXPECT_EQ(trie.find_best_fit(block->inner_size()), root); | ||
| EXPECT_EQ(trie.find_best_fit(block->inner_size() + 1), | ||
| static_cast<FreeTrie::Node *>(nullptr)); | ||
| EXPECT_EQ(trie.find_best_fit(4095), static_cast<FreeTrie::Node *>(nullptr)); | ||
| } | ||
|
|
||
| TEST(LlvmLibcFreeTrie, FindBestFitLower) { | ||
| byte mem[4096]; | ||
| optional<Block<> *> maybeBlock = Block<>::init(mem); | ||
| ASSERT_TRUE(maybeBlock.has_value()); | ||
| Block<> *lower = *maybeBlock; | ||
| maybeBlock = lower->split(512); | ||
| ASSERT_TRUE(maybeBlock.has_value()); | ||
| Block<> *root = *maybeBlock; | ||
|
|
||
| FreeTrie trie({0, 4096}); | ||
| trie.push(root); | ||
| trie.push(lower); | ||
|
|
||
| EXPECT_EQ(trie.find_best_fit(0)->block(), lower); | ||
| } | ||
|
|
||
| TEST(LlvmLibcFreeTrie, FindBestFitUpper) { | ||
| byte mem[4096]; | ||
| optional<Block<> *> maybeBlock = Block<>::init(mem); | ||
| ASSERT_TRUE(maybeBlock.has_value()); | ||
| Block<> *root = *maybeBlock; | ||
| maybeBlock = root->split(512); | ||
| ASSERT_TRUE(maybeBlock.has_value()); | ||
| Block<> *upper = *maybeBlock; | ||
|
|
||
| FreeTrie trie({0, 4096}); | ||
| trie.push(root); | ||
| trie.push(upper); | ||
|
|
||
| EXPECT_EQ(trie.find_best_fit(root->inner_size() + 1)->block(), upper); | ||
| // The upper subtrie should be skipped if it could not contain a better fit. | ||
| EXPECT_EQ(trie.find_best_fit(root->inner_size() - 1)->block(), root); | ||
| } | ||
|
|
||
| TEST(LlvmLibcFreeTrie, FindBestFitLowerAndUpper) { | ||
| byte mem[4096]; | ||
| optional<Block<> *> maybeBlock = Block<>::init(mem); | ||
| ASSERT_TRUE(maybeBlock.has_value()); | ||
| Block<> *root = *maybeBlock; | ||
| maybeBlock = root->split(1024); | ||
| ASSERT_TRUE(maybeBlock.has_value()); | ||
| Block<> *lower = *maybeBlock; | ||
| maybeBlock = lower->split(128); | ||
| ASSERT_TRUE(maybeBlock.has_value()); | ||
| Block<> *upper = *maybeBlock; | ||
|
|
||
| FreeTrie trie({0, 4096}); | ||
| trie.push(root); | ||
| trie.push(lower); | ||
| trie.push(upper); | ||
|
|
||
| // The lower subtrie is examined first. | ||
| EXPECT_EQ(trie.find_best_fit(0)->block(), lower); | ||
| // The upper subtrie is examined if there are no fits found in the upper | ||
| // subtrie. | ||
| EXPECT_EQ(trie.find_best_fit(2048)->block(), upper); | ||
| } | ||
|
|
||
| TEST(LlvmLibcFreeTrie, Remove) { | ||
| byte mem[4096]; | ||
| optional<Block<> *> maybeBlock = Block<>::init(mem); | ||
| ASSERT_TRUE(maybeBlock.has_value()); | ||
| Block<> *small1 = *maybeBlock; | ||
| maybeBlock = small1->split(512); | ||
| ASSERT_TRUE(maybeBlock.has_value()); | ||
| Block<> *small2 = *maybeBlock; | ||
| maybeBlock = small2->split(512); | ||
| ASSERT_TRUE(maybeBlock.has_value()); | ||
| ASSERT_EQ(small1->inner_size(), small2->inner_size()); | ||
| Block<> *large = *maybeBlock; | ||
|
|
||
| // Removing the root empties the trie. | ||
| FreeTrie trie({0, 4096}); | ||
| trie.push(large); | ||
| FreeTrie::Node *large_node = trie.find_best_fit(0); | ||
| ASSERT_EQ(large_node->block(), large); | ||
| trie.remove(large_node); | ||
| ASSERT_TRUE(trie.empty()); | ||
|
|
||
| // Removing the head of a trie list preserves the trie structure. | ||
| trie.push(small1); | ||
| trie.push(small2); | ||
| trie.push(large); | ||
| trie.remove(trie.find_best_fit(small1->inner_size())); | ||
| EXPECT_EQ(trie.find_best_fit(large->inner_size())->block(), large); | ||
| trie.remove(trie.find_best_fit(small1->inner_size())); | ||
| EXPECT_EQ(trie.find_best_fit(large->inner_size())->block(), large); | ||
| } |