[libc-commits] [libc] 93b8364 - Revert "[libc] Use best-fit binary trie to make malloc logarithmic (#117065)"
Daniel Thornburgh via libc-commits
libc-commits at lists.llvm.org
Thu Nov 21 11:56:21 PST 2024
Author: Daniel Thornburgh
Date: 2024-11-21T11:56:05-08:00
New Revision: 93b83642ee34d0092b94776728dad0117c2b72a1
URL: https://github.com/llvm/llvm-project/commit/93b83642ee34d0092b94776728dad0117c2b72a1
DIFF: https://github.com/llvm/llvm-project/commit/93b83642ee34d0092b94776728dad0117c2b72a1.diff
LOG: Revert "[libc] Use best-fit binary trie to make malloc logarithmic (#117065)"
This reverts commit b05600d96f46697e21f6b1b7ad901391326243a8.
riscv32 unit test still broken
Added:
Modified:
libc/fuzzing/__support/CMakeLists.txt
libc/src/__support/CMakeLists.txt
libc/src/__support/block.h
libc/src/__support/freelist.h
libc/src/__support/freelist_heap.h
libc/src/stdlib/freelist_malloc.cpp
libc/test/src/__support/CMakeLists.txt
libc/test/src/__support/block_test.cpp
libc/test/src/__support/freelist_heap_test.cpp
libc/test/src/__support/freelist_malloc_test.cpp
libc/test/src/__support/freelist_test.cpp
Removed:
libc/fuzzing/__support/freelist_heap_fuzz.cpp
libc/src/__support/freelist.cpp
libc/src/__support/freestore.h
libc/src/__support/freetrie.cpp
libc/src/__support/freetrie.h
libc/test/src/__support/freestore_test.cpp
libc/test/src/__support/freetrie_test.cpp
################################################################################
diff --git a/libc/fuzzing/__support/CMakeLists.txt b/libc/fuzzing/__support/CMakeLists.txt
index 9d6589d78fb819..b088761f4586a7 100644
--- a/libc/fuzzing/__support/CMakeLists.txt
+++ b/libc/fuzzing/__support/CMakeLists.txt
@@ -23,11 +23,3 @@ add_libc_fuzzer(
COMPILE_OPTIONS
-D__LIBC_EXPLICIT_SIMD_OPT
)
-
-add_libc_fuzzer(
- freelist_heap_fuzz
- SRCS
- freelist_heap_fuzz.cpp
- DEPENDS
- libc.src.__support.freelist_heap
-)
diff --git a/libc/fuzzing/__support/freelist_heap_fuzz.cpp b/libc/fuzzing/__support/freelist_heap_fuzz.cpp
deleted file mode 100644
index d152d0f35499f8..00000000000000
--- a/libc/fuzzing/__support/freelist_heap_fuzz.cpp
+++ /dev/null
@@ -1,227 +0,0 @@
-//===-- freelist_heap_fuzz.cpp --------------------------------------------===//
-//
-// 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
-//
-//===----------------------------------------------------------------------===//
-///
-/// Fuzzing test for llvm-libc freelist-based heap implementation.
-///
-//===----------------------------------------------------------------------===//
-
-#include "src/__support/CPP/bit.h"
-#include "src/__support/CPP/optional.h"
-#include "src/__support/freelist_heap.h"
-#include "src/string/memory_utils/inline_memcpy.h"
-#include "src/string/memory_utils/inline_memmove.h"
-#include "src/string/memory_utils/inline_memset.h"
-
-using LIBC_NAMESPACE::FreeListHeap;
-using LIBC_NAMESPACE::inline_memset;
-using LIBC_NAMESPACE::cpp::nullopt;
-using LIBC_NAMESPACE::cpp::optional;
-
-// Record of an outstanding allocation.
-struct Alloc {
- void *ptr;
- size_t size;
- size_t alignment;
- uint8_t canary; // Byte written to the allocation
-};
-
-// A simple vector that tracks allocations using the heap.
-class AllocVec {
-public:
- AllocVec(FreeListHeap &heap) : heap(&heap), size_(0), capacity(0) {
- allocs = nullptr;
- }
-
- bool empty() const { return !size_; }
-
- size_t size() const { return size_; }
-
- bool push_back(Alloc alloc) {
- if (size_ == capacity) {
- size_t new_cap = capacity ? capacity * 2 : 1;
- Alloc *new_allocs = reinterpret_cast<Alloc *>(
- heap->realloc(allocs, new_cap * sizeof(Alloc)));
- if (!new_allocs)
- return false;
- allocs = new_allocs;
- capacity = new_cap;
- }
- allocs[size_++] = alloc;
- return true;
- }
-
- Alloc &operator[](size_t idx) { return allocs[idx]; }
-
- void erase_idx(size_t idx) {
- LIBC_NAMESPACE::inline_memmove(&allocs[idx], &allocs[idx + 1],
- sizeof(Alloc) * (size_ - idx - 1));
- --size_;
- }
-
-private:
- FreeListHeap *heap;
- Alloc *allocs;
- size_t size_;
- size_t capacity;
-};
-
-// Choose a T value by casting libfuzzer data or exit.
-template <typename T>
-optional<T> choose(const uint8_t *&data, size_t &remainder) {
- if (sizeof(T) > remainder)
- return nullopt;
- T out;
- LIBC_NAMESPACE::inline_memcpy(&out, data, sizeof(T));
- data += sizeof(T);
- remainder -= sizeof(T);
- return out;
-}
-
-// The type of allocation to perform
-enum class AllocType : uint8_t {
- MALLOC,
- ALIGNED_ALLOC,
- REALLOC,
- CALLOC,
- NUM_ALLOC_TYPES,
-};
-
-template <>
-optional<AllocType> choose<AllocType>(const uint8_t *&data, size_t &remainder) {
- auto raw = choose<uint8_t>(data, remainder);
- if (!raw)
- return nullopt;
- return static_cast<AllocType>(
- *raw % static_cast<uint8_t>(AllocType::NUM_ALLOC_TYPES));
-}
-
-constexpr size_t heap_size = 64 * 1024;
-
-optional<size_t> choose_size(const uint8_t *&data, size_t &remainder) {
- auto raw = choose<size_t>(data, remainder);
- if (!raw)
- return nullopt;
- return *raw % heap_size;
-}
-
-optional<size_t> choose_alloc_idx(const AllocVec &allocs, const uint8_t *&data,
- size_t &remainder) {
- if (allocs.empty())
- return nullopt;
- auto raw = choose<size_t>(data, remainder);
- if (!raw)
- return nullopt;
- return *raw % allocs.size();
-}
-
-#define ASSIGN_OR_RETURN(TYPE, NAME, EXPR) \
- auto maybe_##NAME = EXPR; \
- if (!maybe_##NAME) \
- return 0; \
- TYPE NAME = *maybe_##NAME
-
-extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t remainder) {
- LIBC_NAMESPACE::FreeListHeapBuffer<heap_size> heap;
- AllocVec allocs(heap);
-
- uint8_t canary = 0;
- while (true) {
- ASSIGN_OR_RETURN(auto, should_alloc, choose<bool>(data, remainder));
- if (should_alloc) {
- ASSIGN_OR_RETURN(auto, alloc_type, choose<AllocType>(data, remainder));
- ASSIGN_OR_RETURN(size_t, alloc_size, choose_size(data, remainder));
-
- // Perform allocation.
- void *ptr = nullptr;
- size_t alignment = alignof(max_align_t);
- switch (alloc_type) {
- case AllocType::MALLOC:
- ptr = heap.allocate(alloc_size);
- break;
- case AllocType::ALIGNED_ALLOC: {
- ASSIGN_OR_RETURN(size_t, alignment, choose_size(data, remainder));
- alignment = LIBC_NAMESPACE::cpp::bit_ceil(alignment);
- ptr = heap.aligned_allocate(alignment, alloc_size);
- break;
- }
- case AllocType::REALLOC: {
- if (!alloc_size)
- return 0;
- ASSIGN_OR_RETURN(size_t, idx,
- choose_alloc_idx(allocs, data, remainder));
- Alloc &alloc = allocs[idx];
- ptr = heap.realloc(alloc.ptr, alloc_size);
- if (ptr) {
- // Extend the canary region if necessary.
- if (alloc_size > alloc.size)
- inline_memset(static_cast<char *>(ptr) + alloc.size, alloc.canary,
- alloc_size - alloc.size);
- alloc.ptr = ptr;
- alloc.size = alloc_size;
- alloc.alignment = alignof(max_align_t);
- }
- break;
- }
- case AllocType::CALLOC: {
- ASSIGN_OR_RETURN(size_t, count, choose_size(data, remainder));
- size_t total;
- if (__builtin_mul_overflow(count, alloc_size, &total))
- return 0;
- ptr = heap.calloc(count, alloc_size);
- if (ptr)
- for (size_t i = 0; i < total; ++i)
- if (static_cast<char *>(ptr)[i] != 0)
- __builtin_trap();
- break;
- }
- case AllocType::NUM_ALLOC_TYPES:
- __builtin_unreachable();
- }
-
- if (ptr) {
- // aligned_allocate should automatically apply a minimum alignment.
- if (alignment < alignof(max_align_t))
- alignment = alignof(max_align_t);
- // Check alignment.
- if (reinterpret_cast<uintptr_t>(ptr) % alignment)
- __builtin_trap();
-
- // Reallocation is treated specially above, since we would otherwise
- // lose the original size.
- if (alloc_type != AllocType::REALLOC) {
- // Fill the object with a canary byte.
- inline_memset(ptr, canary, alloc_size);
-
- // Track the allocation.
- if (!allocs.push_back({ptr, alloc_size, alignment, canary}))
- return 0;
- ++canary;
- }
- }
- } else {
- // Select a random allocation.
- ASSIGN_OR_RETURN(size_t, idx, choose_alloc_idx(allocs, data, remainder));
- Alloc &alloc = allocs[idx];
-
- // Check alignment.
- if (reinterpret_cast<uintptr_t>(alloc.ptr) % alloc.alignment)
- __builtin_trap();
-
- // Check the canary.
- uint8_t *ptr = reinterpret_cast<uint8_t *>(alloc.ptr);
- for (size_t i = 0; i < alloc.size; ++i)
- if (ptr[i] != alloc.canary)
- __builtin_trap();
-
- // Free the allocation and untrack it.
- heap.free(alloc.ptr);
- allocs.erase_idx(idx);
- }
- }
- return 0;
-}
diff --git a/libc/src/__support/CMakeLists.txt b/libc/src/__support/CMakeLists.txt
index 8f85740f70a06e..6637ff9d56f4bc 100644
--- a/libc/src/__support/CMakeLists.txt
+++ b/libc/src/__support/CMakeLists.txt
@@ -14,14 +14,11 @@ add_header_library(
libc.src.__support.CPP.type_traits
)
-add_object_library(
+add_header_library(
freelist
HDRS
freelist.h
- SRCS
- freelist.cpp
DEPENDS
- .block
libc.src.__support.fixedvector
libc.src.__support.CPP.array
libc.src.__support.CPP.cstddef
@@ -29,32 +26,13 @@ add_object_library(
libc.src.__support.CPP.span
)
-add_object_library(
- freetrie
- HDRS
- freetrie.h
- SRCS
- freetrie.cpp
- DEPENDS
- .block
- .freelist
-)
-
-add_header_library(
- freestore
- HDRS
- freestore.h
- DEPENDS
- .freetrie
-)
-
add_header_library(
freelist_heap
HDRS
freelist_heap.h
DEPENDS
.block
- .freestore
+ .freelist
libc.src.__support.CPP.cstddef
libc.src.__support.CPP.array
libc.src.__support.CPP.optional
diff --git a/libc/src/__support/block.h b/libc/src/__support/block.h
index e63801301ac752..96021b99587c87 100644
--- a/libc/src/__support/block.h
+++ b/libc/src/__support/block.h
@@ -174,32 +174,16 @@ class Block {
return inner_size - sizeof(prev_) + BLOCK_OVERHEAD;
}
- /// @returns The number of usable bytes inside the block were it to be
- /// allocated.
+ /// @returns The number of usable bytes inside the block.
size_t inner_size() const {
if (!next())
return 0;
return inner_size(outer_size());
}
- /// @returns The number of usable bytes inside a block with the given outer
- /// size were it to be allocated.
static size_t inner_size(size_t outer_size) {
// The usable region includes the prev_ field of the next block.
- return inner_size_free(outer_size) + sizeof(prev_);
- }
-
- /// @returns The number of usable bytes inside the block if it remains free.
- size_t inner_size_free() const {
- if (!next())
- return 0;
- return inner_size_free(outer_size());
- }
-
- /// @returns The number of usable bytes inside a block with the given outer
- /// size if it remains free.
- static size_t inner_size_free(size_t outer_size) {
- return outer_size - BLOCK_OVERHEAD;
+ return outer_size - BLOCK_OVERHEAD + sizeof(prev_);
}
/// @returns A pointer to the usable space inside this block.
@@ -217,11 +201,14 @@ class Block {
/// Attempts to split this block.
///
- /// If successful, the block will have an inner size of at least
- /// `new_inner_size`, rounded to ensure that the split point is on an
- /// ALIGNMENT boundary. The remaining space will be returned as a new block.
- /// Note that the prev_ field of the next block counts as part of the inner
- /// size of the returnd block.
+ /// If successful, the block will have an inner size of `new_inner_size`,
+ /// rounded to ensure that the split point is on an ALIGNMENT boundary. The
+ /// remaining space will be returned as a new block. Note that the prev_ field
+ /// of the next block counts as part of the inner size of the returnd block.
+ ///
+ /// This method may fail if the remaining space is too small to hold a new
+ /// block. If this method fails for any reason, the original block is
+ /// unmodified.
optional<Block *> split(size_t new_inner_size);
/// Merges this block with the one that comes after it.
@@ -455,7 +442,7 @@ Block<OffsetType, kAlign>::split(size_t new_inner_size) {
// The prev_ field of the next block is always available, so there is a
// minimum size to a block created through splitting.
if (new_inner_size < sizeof(prev_))
- new_inner_size = sizeof(prev_);
+ return {};
size_t old_inner_size = inner_size();
new_inner_size =
diff --git a/libc/src/__support/freelist.cpp b/libc/src/__support/freelist.cpp
deleted file mode 100644
index d3dd44895130cd..00000000000000
--- a/libc/src/__support/freelist.cpp
+++ /dev/null
@@ -1,42 +0,0 @@
-//===-- Implementation for freelist ---------------------------------------===//
-//
-// 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 "freelist.h"
-
-namespace LIBC_NAMESPACE_DECL {
-
-void FreeList::push(Node *node) {
- if (begin_) {
- LIBC_ASSERT(Block<>::from_usable_space(node)->outer_size() ==
- begin_->block()->outer_size() &&
- "freelist entries must have the same size");
- // Since the list is circular, insert the node immediately before begin_.
- node->prev = begin_->prev;
- node->next = begin_;
- begin_->prev->next = node;
- begin_->prev = node;
- } else {
- begin_ = node->prev = node->next = node;
- }
-}
-
-void FreeList::remove(Node *node) {
- LIBC_ASSERT(begin_ && "cannot remove from empty list");
- if (node == node->next) {
- LIBC_ASSERT(node == begin_ &&
- "a self-referential node must be the only element");
- begin_ = nullptr;
- } else {
- node->prev->next = node->next;
- node->next->prev = node->prev;
- if (begin_ == node)
- begin_ = node->next;
- }
-}
-
-} // namespace LIBC_NAMESPACE_DECL
diff --git a/libc/src/__support/freelist.h b/libc/src/__support/freelist.h
index eaeaeb013eeaec..a54cf953fe7ab6 100644
--- a/libc/src/__support/freelist.h
+++ b/libc/src/__support/freelist.h
@@ -1,4 +1,4 @@
-//===-- Interface for freelist --------------------------------------------===//
+//===-- Interface for freelist_malloc -------------------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
@@ -9,80 +9,200 @@
#ifndef LLVM_LIBC_SRC___SUPPORT_FREELIST_H
#define LLVM_LIBC_SRC___SUPPORT_FREELIST_H
-#include "block.h"
+#include "src/__support/CPP/array.h"
+#include "src/__support/CPP/cstddef.h"
+#include "src/__support/CPP/new.h"
+#include "src/__support/CPP/span.h"
+#include "src/__support/fixedvector.h"
+#include "src/__support/macros/config.h"
namespace LIBC_NAMESPACE_DECL {
-/// A circularly-linked FIFO list storing free Blocks. All Blocks on a list
-/// are the same size. The blocks are referenced by Nodes in the list; the list
-/// refers to these, but it does not own them.
+using cpp::span;
+
+/// Basic [freelist](https://en.wikipedia.org/wiki/Free_list) implementation
+/// for an allocator. This implementation buckets by chunk size, with a list
+/// of user-provided buckets. Each bucket is a linked list of storage chunks.
+/// Because this freelist uses the added chunks themselves as list nodes, there
+/// is a lower bound of `sizeof(FreeList.FreeListNode)` bytes for chunks which
+/// can be added to this freelist. There is also an implicit bucket for
+/// "everything else", for chunks which do not fit into a bucket.
+///
+/// Each added chunk will be added to the smallest bucket under which it fits.
+/// If it does not fit into any user-provided bucket, it will be added to the
+/// default bucket.
+///
+/// As an example, assume that the `FreeList` is configured with buckets of
+/// sizes {64, 128, 256, and 512} bytes. The internal state may look like the
+/// following:
///
-/// Allocating free blocks in FIFO order maximizes the amount of time before a
-/// free block is reused. This in turn maximizes the number of opportunities for
-/// it to be coalesced with an adjacent block, which tends to reduce heap
-/// fragmentation.
-class FreeList {
+/// @code{.unparsed}
+/// bucket[0] (64B) --> chunk[12B] --> chunk[42B] --> chunk[64B] --> NULL
+/// bucket[1] (128B) --> chunk[65B] --> chunk[72B] --> NULL
+/// bucket[2] (256B) --> NULL
+/// bucket[3] (512B) --> chunk[312B] --> chunk[512B] --> chunk[416B] --> NULL
+/// bucket[4] (implicit) --> chunk[1024B] --> chunk[513B] --> NULL
+/// @endcode
+///
+/// Note that added chunks should be aligned to a 4-byte boundary.
+template <size_t NUM_BUCKETS = 6> class FreeList {
public:
- class Node {
- public:
- /// @returns The block containing this node.
- LIBC_INLINE const Block<> *block() const {
- return Block<>::from_usable_space(this);
+ // Remove copy/move ctors
+ FreeList(const FreeList &other) = delete;
+ FreeList(FreeList &&other) = delete;
+ FreeList &operator=(const FreeList &other) = delete;
+ FreeList &operator=(FreeList &&other) = delete;
+
+ /// Adds a chunk to this freelist.
+ bool add_chunk(cpp::span<cpp::byte> chunk);
+
+ /// Finds an eligible chunk for an allocation of size `size`.
+ ///
+ /// @note This returns the first allocation possible within a given bucket;
+ /// It does not currently optimize for finding the smallest chunk.
+ ///
+ /// @returns
+ /// * On success - A span representing the chunk.
+ /// * On failure (e.g. there were no chunks available for that allocation) -
+ /// A span with a size of 0.
+ cpp::span<cpp::byte> find_chunk(size_t size) const;
+
+ template <typename Cond> cpp::span<cpp::byte> find_chunk_if(Cond op) const;
+
+ /// Removes a chunk from this freelist.
+ bool remove_chunk(cpp::span<cpp::byte> chunk);
+
+ /// For a given size, find which index into chunks_ the node should be written
+ /// to.
+ constexpr size_t find_chunk_ptr_for_size(size_t size, bool non_null) const;
+
+ struct FreeListNode {
+ FreeListNode *next;
+ size_t size;
+ };
+
+ constexpr void set_freelist_node(FreeListNode &node,
+ cpp::span<cpp::byte> chunk);
+
+ constexpr explicit FreeList(const cpp::array<size_t, NUM_BUCKETS> &sizes)
+ : chunks_(NUM_BUCKETS + 1, 0), sizes_(sizes.begin(), sizes.end()) {}
+
+private:
+ FixedVector<FreeList::FreeListNode *, NUM_BUCKETS + 1> chunks_;
+ FixedVector<size_t, NUM_BUCKETS> sizes_;
+};
+
+template <size_t NUM_BUCKETS>
+constexpr void FreeList<NUM_BUCKETS>::set_freelist_node(FreeListNode &node,
+ span<cpp::byte> chunk) {
+ // Add it to the correct list.
+ size_t chunk_ptr = find_chunk_ptr_for_size(chunk.size(), false);
+ node.size = chunk.size();
+ node.next = chunks_[chunk_ptr];
+ chunks_[chunk_ptr] = &node;
+}
+
+template <size_t NUM_BUCKETS>
+bool FreeList<NUM_BUCKETS>::add_chunk(span<cpp::byte> chunk) {
+ // Check that the size is enough to actually store what we need
+ if (chunk.size() < sizeof(FreeListNode))
+ return false;
+
+ FreeListNode *node = ::new (chunk.data()) FreeListNode;
+ set_freelist_node(*node, chunk);
+
+ return true;
+}
+
+template <size_t NUM_BUCKETS>
+template <typename Cond>
+span<cpp::byte> FreeList<NUM_BUCKETS>::find_chunk_if(Cond op) const {
+ for (FreeListNode *node : chunks_) {
+ while (node != nullptr) {
+ span<cpp::byte> chunk(reinterpret_cast<cpp::byte *>(node), node->size);
+ if (op(chunk))
+ return chunk;
+
+ node = node->next;
}
+ }
- /// @returns The block containing this node.
- LIBC_INLINE Block<> *block() { return Block<>::from_usable_space(this); }
+ return {};
+}
- /// @returns The inner size of blocks in the list containing this node.
- LIBC_INLINE size_t size() const { return block()->inner_size(); }
+template <size_t NUM_BUCKETS>
+span<cpp::byte> FreeList<NUM_BUCKETS>::find_chunk(size_t size) const {
+ if (size == 0)
+ return span<cpp::byte>();
- private:
- // Circularly linked pointers to adjacent nodes.
- Node *prev;
- Node *next;
- friend class FreeList;
- };
+ size_t chunk_ptr = find_chunk_ptr_for_size(size, true);
+
+ // Check that there's data. This catches the case where we run off the
+ // end of the array
+ if (chunks_[chunk_ptr] == nullptr)
+ return span<cpp::byte>();
- LIBC_INLINE constexpr FreeList() : FreeList(nullptr) {}
- LIBC_INLINE constexpr FreeList(Node *begin) : begin_(begin) {}
+ // Now iterate up the buckets, walking each list to find a good candidate
+ for (size_t i = chunk_ptr; i < chunks_.size(); i++) {
+ FreeListNode *node = chunks_[static_cast<unsigned short>(i)];
- LIBC_INLINE bool empty() const { return !begin_; }
+ while (node != nullptr) {
+ if (node->size >= size)
+ return span<cpp::byte>(reinterpret_cast<cpp::byte *>(node), node->size);
- /// @returns The inner size of blocks in the list.
- LIBC_INLINE size_t size() const {
- LIBC_ASSERT(begin_ && "empty lists have no size");
- return begin_->size();
+ node = node->next;
+ }
}
- /// @returns The first node in the list.
- LIBC_INLINE Node *begin() { return begin_; }
+ // If we get here, we've checked every block in every bucket. There's
+ // nothing that can support this allocation.
+ return span<cpp::byte>();
+}
- /// @returns The first block in the list.
- LIBC_INLINE Block<> *front() { return begin_->block(); }
+template <size_t NUM_BUCKETS>
+bool FreeList<NUM_BUCKETS>::remove_chunk(span<cpp::byte> chunk) {
+ size_t chunk_ptr = find_chunk_ptr_for_size(chunk.size(), true);
- /// Push a block to the back of the list.
- /// The block must be large enough to contain a node.
- LIBC_INLINE void push(Block<> *block) {
- LIBC_ASSERT(!block->used() &&
- "only free blocks can be placed on free lists");
- LIBC_ASSERT(block->inner_size_free() >= sizeof(FreeList) &&
- "block too small to accomodate free list node");
- push(new (block->usable_space()) Node);
+ // Check head first.
+ if (chunks_[chunk_ptr] == nullptr)
+ return false;
+
+ FreeListNode *node = chunks_[chunk_ptr];
+ if (reinterpret_cast<cpp::byte *>(node) == chunk.data()) {
+ chunks_[chunk_ptr] = node->next;
+ return true;
}
- /// Push an already-constructed node to the back of the list.
- /// This allows pushing derived node types with additional data.
- void push(Node *node);
+ // No? Walk the nodes.
+ node = chunks_[chunk_ptr];
- /// Pop the first node from the list.
- LIBC_INLINE void pop() { remove(begin_); }
+ while (node->next != nullptr) {
+ if (reinterpret_cast<cpp::byte *>(node->next) == chunk.data()) {
+ // Found it, remove this node out of the chain
+ node->next = node->next->next;
+ return true;
+ }
- /// Remove an arbitrary node from the list.
- void remove(Node *node);
+ node = node->next;
+ }
-private:
- Node *begin_;
-};
+ return false;
+}
+
+template <size_t NUM_BUCKETS>
+constexpr size_t
+FreeList<NUM_BUCKETS>::find_chunk_ptr_for_size(size_t size,
+ bool non_null) const {
+ size_t chunk_ptr = 0;
+ for (chunk_ptr = 0u; chunk_ptr < sizes_.size(); chunk_ptr++) {
+ if (sizes_[chunk_ptr] >= size &&
+ (!non_null || chunks_[chunk_ptr] != nullptr)) {
+ break;
+ }
+ }
+
+ return chunk_ptr;
+}
} // namespace LIBC_NAMESPACE_DECL
diff --git a/libc/src/__support/freelist_heap.h b/libc/src/__support/freelist_heap.h
index dd9069ce91430d..6c860d039553ab 100644
--- a/libc/src/__support/freelist_heap.h
+++ b/libc/src/__support/freelist_heap.h
@@ -12,12 +12,11 @@
#include <stddef.h>
#include "block.h"
-#include "freestore.h"
+#include "freelist.h"
#include "src/__support/CPP/optional.h"
#include "src/__support/CPP/span.h"
#include "src/__support/libc_assert.h"
#include "src/__support/macros/config.h"
-#include "src/__support/math_extras.h"
#include "src/string/memory_utils/inline_memcpy.h"
#include "src/string/memory_utils/inline_memset.h"
@@ -29,14 +28,23 @@ extern "C" cpp::byte __llvm_libc_heap_limit;
using cpp::optional;
using cpp::span;
-LIBC_INLINE constexpr bool IsPow2(size_t x) { return x && (x & (x - 1)) == 0; }
+inline constexpr bool IsPow2(size_t x) { return x && (x & (x - 1)) == 0; }
-class FreeListHeap {
+static constexpr cpp::array<size_t, 6> DEFAULT_BUCKETS{16, 32, 64,
+ 128, 256, 512};
+
+template <size_t NUM_BUCKETS = DEFAULT_BUCKETS.size()> class FreeListHeap {
public:
- constexpr FreeListHeap() : begin(&_end), end(&__llvm_libc_heap_limit) {}
+ using BlockType = Block<>;
+ using FreeListType = FreeList<NUM_BUCKETS>;
+
+ static constexpr size_t MIN_ALIGNMENT =
+ cpp::max(BlockType::ALIGNMENT, alignof(max_align_t));
+
+ constexpr FreeListHeap() : begin_(&_end), end_(&__llvm_libc_heap_limit) {}
constexpr FreeListHeap(span<cpp::byte> region)
- : begin(region.begin()), end(region.end()) {}
+ : begin_(region.begin()), end_(region.end()) {}
void *allocate(size_t size);
void *aligned_allocate(size_t alignment, size_t size);
@@ -46,79 +54,89 @@ class FreeListHeap {
void *realloc(void *ptr, size_t size);
void *calloc(size_t num, size_t size);
- cpp::span<cpp::byte> region() const { return {begin, end}; }
+ cpp::span<cpp::byte> region() const { return {begin_, end_}; }
private:
void init();
void *allocate_impl(size_t alignment, size_t size);
- span<cpp::byte> block_to_span(Block<> *block) {
+ span<cpp::byte> block_to_span(BlockType *block) {
return span<cpp::byte>(block->usable_space(), block->inner_size());
}
- bool is_valid_ptr(void *ptr) { return ptr >= begin && ptr < end; }
+ bool is_valid_ptr(void *ptr) { return ptr >= begin_ && ptr < end_; }
- cpp::byte *begin;
- cpp::byte *end;
- bool is_initialized = false;
- FreeStore free_store;
+ bool is_initialized_ = false;
+ cpp::byte *begin_;
+ cpp::byte *end_;
+ FreeListType freelist_{DEFAULT_BUCKETS};
};
-template <size_t BUFF_SIZE> class FreeListHeapBuffer : public FreeListHeap {
+template <size_t BUFF_SIZE, size_t NUM_BUCKETS = DEFAULT_BUCKETS.size()>
+class FreeListHeapBuffer : public FreeListHeap<NUM_BUCKETS> {
+ using parent = FreeListHeap<NUM_BUCKETS>;
+ using FreeListNode = typename parent::FreeListType::FreeListNode;
+
public:
- constexpr FreeListHeapBuffer() : FreeListHeap{buffer}, buffer{} {}
+ constexpr FreeListHeapBuffer()
+ : FreeListHeap<NUM_BUCKETS>{buffer}, buffer{} {}
private:
cpp::byte buffer[BUFF_SIZE];
};
-LIBC_INLINE void FreeListHeap::init() {
- LIBC_ASSERT(!is_initialized && "duplicate initialization");
- auto result = Block<>::init(region());
- Block<> *block = *result;
- free_store.set_range({0, cpp::bit_ceil(block->inner_size())});
- free_store.insert(block);
- is_initialized = true;
+template <size_t NUM_BUCKETS> void FreeListHeap<NUM_BUCKETS>::init() {
+ LIBC_ASSERT(!is_initialized_ && "duplicate initialization");
+ auto result = BlockType::init(region());
+ BlockType *block = *result;
+ freelist_.add_chunk(block_to_span(block));
+ is_initialized_ = true;
}
-LIBC_INLINE void *FreeListHeap::allocate_impl(size_t alignment, size_t size) {
+template <size_t NUM_BUCKETS>
+void *FreeListHeap<NUM_BUCKETS>::allocate_impl(size_t alignment, size_t size) {
if (size == 0)
return nullptr;
- if (!is_initialized)
+ if (!is_initialized_)
init();
- size_t request_size = size;
- if (alignment > alignof(max_align_t)) {
- if (add_overflow(size, alignment - 1, request_size))
- return nullptr;
- }
+ // Find a chunk in the freelist. Split it if needed, then return.
+ auto chunk =
+ freelist_.find_chunk_if([alignment, size](span<cpp::byte> chunk) {
+ BlockType *block = BlockType::from_usable_space(chunk.data());
+ return block->can_allocate(alignment, size);
+ });
- Block<> *block = free_store.remove_best_fit(request_size);
- if (!block)
+ if (chunk.data() == nullptr)
return nullptr;
+ freelist_.remove_chunk(chunk);
- LIBC_ASSERT(block->can_allocate(alignment, size) &&
- "block should always be large enough to allocate at the correct "
- "alignment");
+ BlockType *chunk_block = BlockType::from_usable_space(chunk.data());
+ LIBC_ASSERT(!chunk_block->used());
- auto block_info = Block<>::allocate(block, alignment, size);
+ // Split that chunk. If there's a leftover chunk, add it to the freelist
+ auto block_info = BlockType::allocate(chunk_block, alignment, size);
if (block_info.next)
- free_store.insert(block_info.next);
+ freelist_.add_chunk(block_to_span(block_info.next));
if (block_info.prev)
- free_store.insert(block_info.prev);
+ freelist_.add_chunk(block_to_span(block_info.prev));
+ chunk_block = block_info.block;
+
+ chunk_block->mark_used();
- block_info.block->mark_used();
- return block_info.block->usable_space();
+ return chunk_block->usable_space();
}
-LIBC_INLINE void *FreeListHeap::allocate(size_t size) {
- return allocate_impl(alignof(max_align_t), size);
+template <size_t NUM_BUCKETS>
+void *FreeListHeap<NUM_BUCKETS>::allocate(size_t size) {
+ return allocate_impl(MIN_ALIGNMENT, size);
}
-LIBC_INLINE void *FreeListHeap::aligned_allocate(size_t alignment,
- size_t size) {
+template <size_t NUM_BUCKETS>
+void *FreeListHeap<NUM_BUCKETS>::aligned_allocate(size_t alignment,
+ size_t size) {
// The alignment must be an integral power of two.
if (!IsPow2(alignment))
return nullptr;
@@ -130,37 +148,38 @@ LIBC_INLINE void *FreeListHeap::aligned_allocate(size_t alignment,
return allocate_impl(alignment, size);
}
-LIBC_INLINE void FreeListHeap::free(void *ptr) {
+template <size_t NUM_BUCKETS> void FreeListHeap<NUM_BUCKETS>::free(void *ptr) {
cpp::byte *bytes = static_cast<cpp::byte *>(ptr);
LIBC_ASSERT(is_valid_ptr(bytes) && "Invalid pointer");
- Block<> *block = Block<>::from_usable_space(bytes);
- LIBC_ASSERT(block->next() && "sentinel last block cannot be freed");
- LIBC_ASSERT(block->used() && "double free");
- block->mark_free();
+ BlockType *chunk_block = BlockType::from_usable_space(bytes);
+ LIBC_ASSERT(chunk_block->next() && "sentinel last block cannot be freed");
+ LIBC_ASSERT(chunk_block->used() && "The block is not in-use");
+ chunk_block->mark_free();
// Can we combine with the left or right blocks?
- Block<> *prev_free = block->prev_free();
- Block<> *next = block->next();
+ BlockType *prev_free = chunk_block->prev_free();
+ BlockType *next = chunk_block->next();
if (prev_free != nullptr) {
- // Remove from free store and merge.
- free_store.remove(prev_free);
- block = prev_free;
- block->merge_next();
+ // Remove from freelist and merge
+ freelist_.remove_chunk(block_to_span(prev_free));
+ chunk_block = prev_free;
+ chunk_block->merge_next();
}
if (!next->used()) {
- free_store.remove(next);
- block->merge_next();
+ freelist_.remove_chunk(block_to_span(next));
+ chunk_block->merge_next();
}
// Add back to the freelist
- free_store.insert(block);
+ freelist_.add_chunk(block_to_span(chunk_block));
}
// Follows constract of the C standard realloc() function
// If ptr is free'd, will return nullptr.
-LIBC_INLINE void *FreeListHeap::realloc(void *ptr, size_t size) {
+template <size_t NUM_BUCKETS>
+void *FreeListHeap<NUM_BUCKETS>::realloc(void *ptr, size_t size) {
if (size == 0) {
free(ptr);
return nullptr;
@@ -175,10 +194,10 @@ LIBC_INLINE void *FreeListHeap::realloc(void *ptr, size_t size) {
if (!is_valid_ptr(bytes))
return nullptr;
- Block<> *block = Block<>::from_usable_space(bytes);
- if (!block->used())
+ BlockType *chunk_block = BlockType::from_usable_space(bytes);
+ if (!chunk_block->used())
return nullptr;
- size_t old_size = block->inner_size();
+ size_t old_size = chunk_block->inner_size();
// Do nothing and return ptr if the required memory size is smaller than
// the current size.
@@ -195,17 +214,15 @@ LIBC_INLINE void *FreeListHeap::realloc(void *ptr, size_t size) {
return new_ptr;
}
-LIBC_INLINE void *FreeListHeap::calloc(size_t num, size_t size) {
- size_t bytes;
- if (__builtin_mul_overflow(num, size, &bytes))
- return nullptr;
- void *ptr = allocate(bytes);
+template <size_t NUM_BUCKETS>
+void *FreeListHeap<NUM_BUCKETS>::calloc(size_t num, size_t size) {
+ void *ptr = allocate(num * size);
if (ptr != nullptr)
- LIBC_NAMESPACE::inline_memset(ptr, 0, bytes);
+ LIBC_NAMESPACE::inline_memset(ptr, 0, num * size);
return ptr;
}
-extern FreeListHeap *freelist_heap;
+extern FreeListHeap<> *freelist_heap;
} // namespace LIBC_NAMESPACE_DECL
diff --git a/libc/src/__support/freestore.h b/libc/src/__support/freestore.h
deleted file mode 100644
index f04b561f5d91dc..00000000000000
--- a/libc/src/__support/freestore.h
+++ /dev/null
@@ -1,114 +0,0 @@
-//===-- 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
diff --git a/libc/src/__support/freetrie.cpp b/libc/src/__support/freetrie.cpp
deleted file mode 100644
index e76efe717f2154..00000000000000
--- a/libc/src/__support/freetrie.cpp
+++ /dev/null
@@ -1,64 +0,0 @@
-//===-- 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
diff --git a/libc/src/__support/freetrie.h b/libc/src/__support/freetrie.h
deleted file mode 100644
index ff1912ee94f884..00000000000000
--- a/libc/src/__support/freetrie.h
+++ /dev/null
@@ -1,237 +0,0 @@
-//===-- 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
diff erently-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
diff --git a/libc/src/stdlib/freelist_malloc.cpp b/libc/src/stdlib/freelist_malloc.cpp
index fe56fad769378a..47240bc53aa37b 100644
--- a/libc/src/stdlib/freelist_malloc.cpp
+++ b/libc/src/stdlib/freelist_malloc.cpp
@@ -18,8 +18,8 @@
namespace LIBC_NAMESPACE_DECL {
-static LIBC_CONSTINIT FreeListHeap freelist_heap_symbols;
-FreeListHeap *freelist_heap = &freelist_heap_symbols;
+static LIBC_CONSTINIT FreeListHeap<> freelist_heap_symbols;
+FreeListHeap<> *freelist_heap = &freelist_heap_symbols;
LLVM_LIBC_FUNCTION(void *, malloc, (size_t size)) {
return freelist_heap->allocate(size);
diff --git a/libc/test/src/__support/CMakeLists.txt b/libc/test/src/__support/CMakeLists.txt
index bcc86effd9a52c..c3a7d87d8281f9 100644
--- a/libc/test/src/__support/CMakeLists.txt
+++ b/libc/test/src/__support/CMakeLists.txt
@@ -24,34 +24,7 @@ if(NOT LIBC_TARGET_OS_IS_GPU)
DEPENDS
libc.src.__support.CPP.array
libc.src.__support.CPP.span
- libc.src.__support.block
- libc.src.__support.freelist
- )
-
- add_libc_test(
- freetrie_test
- SUITE
- libc-support-tests
- SRCS
- freetrie_test.cpp
- DEPENDS
- libc.src.__support.CPP.optional
- libc.src.__support.block
- libc.src.__support.freetrie
- )
-
- add_libc_test(
- freestore_test
- SUITE
- libc-support-tests
- SRCS
- freestore_test.cpp
- DEPENDS
- libc.src.__support.CPP.optional
- libc.src.__support.block
libc.src.__support.freelist
- libc.src.__support.freestore
- libc.src.__support.freetrie
)
endif()
diff --git a/libc/test/src/__support/block_test.cpp b/libc/test/src/__support/block_test.cpp
index 4d23861155502a..62d7fae67bdc3d 100644
--- a/libc/test/src/__support/block_test.cpp
+++ b/libc/test/src/__support/block_test.cpp
@@ -238,6 +238,20 @@ TEST_FOR_EACH_BLOCK_TYPE(CannotMakeSecondBlockLargerInSplit) {
ASSERT_FALSE(result.has_value());
}
+TEST_FOR_EACH_BLOCK_TYPE(CannotMakeZeroSizeFirstBlock) {
+ // This block doesn't support splitting with zero payload size, since the
+ // prev_ field of the next block is always available.
+ constexpr size_t kN = 1024;
+
+ alignas(BlockType::ALIGNMENT) array<byte, kN> bytes;
+ auto result = BlockType::init(bytes);
+ ASSERT_TRUE(result.has_value());
+ BlockType *block = *result;
+
+ result = block->split(0);
+ EXPECT_FALSE(result.has_value());
+}
+
TEST_FOR_EACH_BLOCK_TYPE(CanMakeMinimalSizeFirstBlock) {
// This block does support splitting with minimal payload size.
constexpr size_t kN = 1024;
diff --git a/libc/test/src/__support/freelist_heap_test.cpp b/libc/test/src/__support/freelist_heap_test.cpp
index 59ebf4e50974b7..973900dfdf56ea 100644
--- a/libc/test/src/__support/freelist_heap_test.cpp
+++ b/libc/test/src/__support/freelist_heap_test.cpp
@@ -13,12 +13,9 @@
#include "src/string/memcpy.h"
#include "test/UnitTest/Test.h"
-using LIBC_NAMESPACE::Block;
+namespace LIBC_NAMESPACE_DECL {
+
using LIBC_NAMESPACE::freelist_heap;
-using LIBC_NAMESPACE::FreeListHeap;
-using LIBC_NAMESPACE::FreeListHeapBuffer;
-using LIBC_NAMESPACE::cpp::byte;
-using LIBC_NAMESPACE::cpp::span;
// Similar to `LlvmLibcBlockTest` in block_test.cpp, we'd like to run the same
// tests independently for
diff erent parameters. In this case, we'd like to test
@@ -31,23 +28,23 @@ using LIBC_NAMESPACE::cpp::span;
// made in tests leak and aren't free'd. This is fine for the purposes of this
// test file.
#define TEST_FOR_EACH_ALLOCATOR(TestCase, BufferSize) \
- class LlvmLibcFreeListHeapTest##TestCase \
- : public LIBC_NAMESPACE::testing::Test { \
+ class LlvmLibcFreeListHeapTest##TestCase : public testing::Test { \
public: \
FreeListHeapBuffer<BufferSize> fake_global_buffer; \
void SetUp() override { \
freelist_heap = \
new (&fake_global_buffer) FreeListHeapBuffer<BufferSize>; \
} \
- void RunTest(FreeListHeap &allocator, [[maybe_unused]] size_t N); \
+ void RunTest(FreeListHeap<> &allocator, [[maybe_unused]] size_t N); \
}; \
TEST_F(LlvmLibcFreeListHeapTest##TestCase, TestCase) { \
- alignas(Block<>) byte buf[BufferSize] = {byte(0)}; \
- FreeListHeap allocator(buf); \
+ alignas(FreeListHeap<>::BlockType) \
+ cpp::byte buf[BufferSize] = {cpp::byte(0)}; \
+ FreeListHeap<> allocator(buf); \
RunTest(allocator, BufferSize); \
RunTest(*freelist_heap, freelist_heap->region().size()); \
} \
- void LlvmLibcFreeListHeapTest##TestCase::RunTest(FreeListHeap &allocator, \
+ void LlvmLibcFreeListHeapTest##TestCase::RunTest(FreeListHeap<> &allocator, \
size_t N)
TEST_FOR_EACH_ALLOCATOR(CanAllocate, 2048) {
@@ -95,13 +92,14 @@ TEST_FOR_EACH_ALLOCATOR(ReturnsNullWhenAllocationTooLarge, 2048) {
// is used for other test cases and we don't explicitly free them.
TEST(LlvmLibcFreeListHeap, ReturnsNullWhenFull) {
constexpr size_t N = 2048;
- alignas(Block<>) byte buf[N] = {byte(0)};
+ alignas(FreeListHeap<>::BlockType) cpp::byte buf[N] = {cpp::byte(0)};
- FreeListHeap allocator(buf);
+ FreeListHeap<> allocator(buf);
// Use aligned_allocate so we don't need to worry about ensuring the `buf`
// being aligned to max_align_t.
- EXPECT_NE(allocator.aligned_allocate(1, N - 2 * Block<>::BLOCK_OVERHEAD),
+ EXPECT_NE(allocator.aligned_allocate(
+ 1, N - 2 * FreeListHeap<>::BlockType::BLOCK_OVERHEAD),
static_cast<void *>(nullptr));
EXPECT_EQ(allocator.allocate(1), static_cast<void *>(nullptr));
}
@@ -136,9 +134,9 @@ TEST_FOR_EACH_ALLOCATOR(ReallocHasSameContent, 2048) {
constexpr size_t ALLOC_SIZE = sizeof(int);
constexpr size_t kNewAllocSize = sizeof(int) * 2;
// Data inside the allocated block.
- byte data1[ALLOC_SIZE];
+ cpp::byte data1[ALLOC_SIZE];
// Data inside the reallocated block.
- byte data2[ALLOC_SIZE];
+ cpp::byte data2[ALLOC_SIZE];
int *ptr1 = reinterpret_cast<int *>(allocator.allocate(ALLOC_SIZE));
*ptr1 = 42;
@@ -190,9 +188,10 @@ TEST_FOR_EACH_ALLOCATOR(CanCalloc, 2048) {
constexpr size_t ALLOC_SIZE = 128;
constexpr size_t NUM = 4;
constexpr int size = NUM * ALLOC_SIZE;
- constexpr byte zero{0};
+ constexpr cpp::byte zero{0};
- byte *ptr1 = reinterpret_cast<byte *>(allocator.calloc(NUM, ALLOC_SIZE));
+ cpp::byte *ptr1 =
+ reinterpret_cast<cpp::byte *>(allocator.calloc(NUM, ALLOC_SIZE));
// calloc'd content is zero.
for (int i = 0; i < size; i++) {
@@ -204,9 +203,10 @@ TEST_FOR_EACH_ALLOCATOR(CanCallocWeirdSize, 2048) {
constexpr size_t ALLOC_SIZE = 143;
constexpr size_t NUM = 3;
constexpr int size = NUM * ALLOC_SIZE;
- constexpr byte zero{0};
+ constexpr cpp::byte zero{0};
- byte *ptr1 = reinterpret_cast<byte *>(allocator.calloc(NUM, ALLOC_SIZE));
+ cpp::byte *ptr1 =
+ reinterpret_cast<cpp::byte *>(allocator.calloc(NUM, ALLOC_SIZE));
// calloc'd content is zero.
for (int i = 0; i < size; i++) {
@@ -241,16 +241,17 @@ TEST_FOR_EACH_ALLOCATOR(AlignedAlloc, 2048) {
// This test is not part of the TEST_FOR_EACH_ALLOCATOR since we want to
// explicitly ensure that the buffer can still return aligned allocations even
-// if the underlying buffer is at most aligned to the Block<> alignment. This
+// if the underlying buffer is at most aligned to the BlockType alignment. This
// is so we can check that we can still get aligned allocations even if the
// underlying buffer is not aligned to the alignments we request.
-TEST(LlvmLibcFreeListHeap, AlignedAllocOnlyBlockAligned) {
+TEST(LlvmLibcFreeListHeap, AlignedAllocOnlyBlockTypeAligned) {
constexpr size_t BUFFER_SIZE = 4096;
- constexpr size_t BUFFER_ALIGNMENT = alignof(Block<>) * 2;
- alignas(BUFFER_ALIGNMENT) byte buf[BUFFER_SIZE] = {byte(0)};
+ constexpr size_t BUFFER_ALIGNMENT = alignof(FreeListHeap<>::BlockType) * 2;
+ alignas(BUFFER_ALIGNMENT) cpp::byte buf[BUFFER_SIZE] = {cpp::byte(0)};
// Ensure the underlying buffer is at most aligned to the block type.
- FreeListHeap allocator(span<byte>(buf).subspan(alignof(Block<>)));
+ FreeListHeap<> allocator(
+ span<cpp::byte>(buf).subspan(alignof(FreeListHeap<>::BlockType)));
constexpr size_t ALIGNMENTS[] = {1, 2, 4, 8, 16, 32, 64, 128, 256};
constexpr size_t SIZE_SCALES[] = {1, 2, 3, 4, 5};
@@ -288,3 +289,5 @@ TEST_FOR_EACH_ALLOCATOR(InvalidAlignedAllocAlignment, 2048) {
ptr = allocator.aligned_allocate(0, 8);
EXPECT_EQ(ptr, static_cast<void *>(nullptr));
}
+
+} // namespace LIBC_NAMESPACE_DECL
diff --git a/libc/test/src/__support/freelist_malloc_test.cpp b/libc/test/src/__support/freelist_malloc_test.cpp
index 583e40d9478223..9cbdec89f6576e 100644
--- a/libc/test/src/__support/freelist_malloc_test.cpp
+++ b/libc/test/src/__support/freelist_malloc_test.cpp
@@ -13,7 +13,6 @@
#include "src/stdlib/malloc.h"
#include "test/UnitTest/Test.h"
-using LIBC_NAMESPACE::Block;
using LIBC_NAMESPACE::freelist_heap;
using LIBC_NAMESPACE::FreeListHeap;
using LIBC_NAMESPACE::FreeListHeapBuffer;
@@ -23,13 +22,15 @@ TEST(LlvmLibcFreeListMalloc, Malloc) {
constexpr size_t kCallocNum = 4;
constexpr size_t kCallocSize = 64;
+ typedef FreeListHeap<>::BlockType Block;
+
void *ptr1 = LIBC_NAMESPACE::malloc(kAllocSize);
- auto *block = Block<>::from_usable_space(ptr1);
+ auto *block = Block::from_usable_space(ptr1);
EXPECT_GE(block->inner_size(), kAllocSize);
LIBC_NAMESPACE::free(ptr1);
- ASSERT_NE(block->next(), static_cast<Block<> *>(nullptr));
- ASSERT_EQ(block->next()->next(), static_cast<Block<> *>(nullptr));
+ ASSERT_NE(block->next(), static_cast<Block *>(nullptr));
+ ASSERT_EQ(block->next()->next(), static_cast<Block *>(nullptr));
size_t heap_size = block->inner_size();
void *ptr2 = LIBC_NAMESPACE::calloc(kCallocNum, kCallocSize);
@@ -46,7 +47,7 @@ TEST(LlvmLibcFreeListMalloc, Malloc) {
void *ptr3 = LIBC_NAMESPACE::aligned_alloc(ALIGN, kAllocSize);
EXPECT_NE(ptr3, static_cast<void *>(nullptr));
EXPECT_EQ(reinterpret_cast<uintptr_t>(ptr3) % ALIGN, size_t(0));
- auto *aligned_block = reinterpret_cast<Block<> *>(ptr3);
+ auto *aligned_block = reinterpret_cast<Block *>(ptr3);
EXPECT_GE(aligned_block->inner_size(), kAllocSize);
LIBC_NAMESPACE::free(ptr3);
diff --git a/libc/test/src/__support/freelist_test.cpp b/libc/test/src/__support/freelist_test.cpp
index 1f310761bee180..cae0ed470315c1 100644
--- a/libc/test/src/__support/freelist_test.cpp
+++ b/libc/test/src/__support/freelist_test.cpp
@@ -8,46 +8,159 @@
#include <stddef.h>
+#include "src/__support/CPP/array.h"
+#include "src/__support/CPP/span.h"
#include "src/__support/freelist.h"
#include "test/UnitTest/Test.h"
-using LIBC_NAMESPACE::Block;
using LIBC_NAMESPACE::FreeList;
+using LIBC_NAMESPACE::cpp::array;
using LIBC_NAMESPACE::cpp::byte;
-using LIBC_NAMESPACE::cpp::optional;
-
-TEST(LlvmLibcFreeList, FreeList) {
- byte mem[1024];
- optional<Block<> *> maybeBlock = Block<>::init(mem);
- ASSERT_TRUE(maybeBlock.has_value());
- Block<> *block1 = *maybeBlock;
-
- maybeBlock = block1->split(128);
- ASSERT_TRUE(maybeBlock.has_value());
- Block<> *block2 = *maybeBlock;
-
- maybeBlock = block2->split(128);
- ASSERT_TRUE(maybeBlock.has_value());
-
- FreeList list;
- list.push(block1);
- ASSERT_FALSE(list.empty());
- EXPECT_EQ(list.front(), block1);
-
- list.push(block2);
- EXPECT_EQ(list.front(), block1);
-
- list.pop();
- ASSERT_FALSE(list.empty());
- EXPECT_EQ(list.front(), block2);
-
- list.pop();
- ASSERT_TRUE(list.empty());
-
- list.push(block1);
- list.push(block2);
- list.remove(reinterpret_cast<FreeList::Node *>(block2->usable_space()));
- EXPECT_EQ(list.front(), block1);
- list.pop();
- ASSERT_TRUE(list.empty());
+using LIBC_NAMESPACE::cpp::span;
+
+static constexpr size_t SIZE = 8;
+static constexpr array<size_t, SIZE> example_sizes = {64, 128, 256, 512,
+ 1024, 2048, 4096, 8192};
+
+TEST(LlvmLibcFreeList, EmptyListHasNoMembers) {
+ FreeList<SIZE> list(example_sizes);
+
+ auto item = list.find_chunk(4);
+ EXPECT_EQ(item.size(), static_cast<size_t>(0));
+ item = list.find_chunk(128);
+ EXPECT_EQ(item.size(), static_cast<size_t>(0));
+}
+
+TEST(LlvmLibcFreeList, CanRetrieveAddedMember) {
+ FreeList<SIZE> list(example_sizes);
+ constexpr size_t N = 512;
+
+ byte data[N] = {byte(0)};
+
+ bool ok = list.add_chunk(span<byte>(data, N));
+ EXPECT_TRUE(ok);
+
+ auto item = list.find_chunk(N);
+ EXPECT_EQ(item.size(), N);
+ EXPECT_EQ(item.data(), data);
+}
+
+TEST(LlvmLibcFreeList, CanRetrieveAddedMemberForSmallerSize) {
+ FreeList<SIZE> list(example_sizes);
+ constexpr size_t N = 512;
+
+ byte data[N] = {byte(0)};
+
+ ASSERT_TRUE(list.add_chunk(span<byte>(data, N)));
+ auto item = list.find_chunk(N / 2);
+ EXPECT_EQ(item.size(), N);
+ EXPECT_EQ(item.data(), data);
+}
+
+TEST(LlvmLibcFreeList, CanRemoveItem) {
+ FreeList<SIZE> list(example_sizes);
+ constexpr size_t N = 512;
+
+ byte data[N] = {byte(0)};
+
+ ASSERT_TRUE(list.add_chunk(span<byte>(data, N)));
+ EXPECT_TRUE(list.remove_chunk(span<byte>(data, N)));
+
+ auto item = list.find_chunk(N);
+ EXPECT_EQ(item.size(), static_cast<size_t>(0));
+}
+
+TEST(LlvmLibcFreeList, FindReturnsSmallestChunk) {
+ FreeList<SIZE> list(example_sizes);
+ constexpr size_t kN1 = 512;
+ constexpr size_t kN2 = 1024;
+
+ byte data1[kN1] = {byte(0)};
+ byte data2[kN2] = {byte(0)};
+
+ ASSERT_TRUE(list.add_chunk(span<byte>(data1, kN1)));
+ ASSERT_TRUE(list.add_chunk(span<byte>(data2, kN2)));
+
+ auto chunk = list.find_chunk(kN1 / 2);
+ EXPECT_EQ(chunk.size(), kN1);
+ EXPECT_EQ(chunk.data(), data1);
+
+ chunk = list.find_chunk(kN1);
+ EXPECT_EQ(chunk.size(), kN1);
+ EXPECT_EQ(chunk.data(), data1);
+
+ chunk = list.find_chunk(kN1 + 1);
+ EXPECT_EQ(chunk.size(), kN2);
+ EXPECT_EQ(chunk.data(), data2);
+}
+
+TEST(LlvmLibcFreeList, FindReturnsCorrectChunkInSameBucket) {
+ // If we have two values in the same bucket, ensure that the allocation will
+ // pick an appropriately sized one.
+ FreeList<SIZE> list(example_sizes);
+ constexpr size_t kN1 = 512;
+ constexpr size_t kN2 = 257;
+
+ byte data1[kN1] = {byte(0)};
+ byte data2[kN2] = {byte(0)};
+
+ // List should now be 257 -> 512 -> NULL
+ ASSERT_TRUE(list.add_chunk(span<byte>(data1, kN1)));
+ ASSERT_TRUE(list.add_chunk(span<byte>(data2, kN2)));
+
+ auto chunk = list.find_chunk(kN2 + 1);
+ EXPECT_EQ(chunk.size(), kN1);
+}
+
+TEST(LlvmLibcFreeList, FindCanMoveUpThroughBuckets) {
+ // Ensure that finding a chunk will move up through buckets if no appropriate
+ // chunks were found in a given bucket
+ FreeList<SIZE> list(example_sizes);
+ constexpr size_t kN1 = 257;
+ constexpr size_t kN2 = 513;
+
+ byte data1[kN1] = {byte(0)};
+ byte data2[kN2] = {byte(0)};
+
+ // List should now be:
+ // bkt[3] (257 bytes up to 512 bytes) -> 257 -> NULL
+ // bkt[4] (513 bytes up to 1024 bytes) -> 513 -> NULL
+ ASSERT_TRUE(list.add_chunk(span<byte>(data1, kN1)));
+ ASSERT_TRUE(list.add_chunk(span<byte>(data2, kN2)));
+
+ // Request a 300 byte chunk. This should return the 513 byte one
+ auto chunk = list.find_chunk(kN1 + 1);
+ EXPECT_EQ(chunk.size(), kN2);
+}
+
+TEST(LlvmLibcFreeList, RemoveUnknownChunkReturnsNotFound) {
+ FreeList<SIZE> list(example_sizes);
+ constexpr size_t N = 512;
+
+ byte data[N] = {byte(0)};
+ byte data2[N] = {byte(0)};
+
+ ASSERT_TRUE(list.add_chunk(span<byte>(data, N)));
+ EXPECT_FALSE(list.remove_chunk(span<byte>(data2, N)));
+}
+
+TEST(LlvmLibcFreeList, CanStoreMultipleChunksPerBucket) {
+ FreeList<SIZE> list(example_sizes);
+ constexpr size_t N = 512;
+
+ byte data1[N] = {byte(0)};
+ byte data2[N] = {byte(0)};
+
+ ASSERT_TRUE(list.add_chunk(span<byte>(data1, N)));
+ ASSERT_TRUE(list.add_chunk(span<byte>(data2, N)));
+
+ auto chunk1 = list.find_chunk(N);
+ ASSERT_TRUE(list.remove_chunk(chunk1));
+ auto chunk2 = list.find_chunk(N);
+ ASSERT_TRUE(list.remove_chunk(chunk2));
+
+ // Ordering of the chunks doesn't matter
+ EXPECT_TRUE(chunk1.data() != chunk2.data());
+ EXPECT_TRUE(chunk1.data() == data1 || chunk1.data() == data2);
+ EXPECT_TRUE(chunk2.data() == data1 || chunk2.data() == data2);
}
diff --git a/libc/test/src/__support/freestore_test.cpp b/libc/test/src/__support/freestore_test.cpp
deleted file mode 100644
index 06f0d753b382d8..00000000000000
--- a/libc/test/src/__support/freestore_test.cpp
+++ /dev/null
@@ -1,103 +0,0 @@
-//===-- 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::Node) + 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_GE(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::Node) + 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));
-}
diff --git a/libc/test/src/__support/freetrie_test.cpp b/libc/test/src/__support/freetrie_test.cpp
deleted file mode 100644
index 1e3caceb7293bb..00000000000000
--- a/libc/test/src/__support/freetrie_test.cpp
+++ /dev/null
@@ -1,125 +0,0 @@
-//===-- 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);
-}
More information about the libc-commits
mailing list