[libc-commits] [libc] [libc] add table based freelist heap implementation (PR #200497)
via libc-commits
libc-commits at lists.llvm.org
Fri May 29 14:00:51 PDT 2026
github-actions[bot] wrote:
<!--LLVM CODE FORMAT COMMENT: {clang-format}-->
:warning: C/C++ code formatter, clang-format found issues in your code. :warning:
<details>
<summary>
You can test this locally with the following command:
</summary>
``````````bash
git-clang-format --diff origin/main HEAD --extensions h,cpp -- libc/fuzzing/__support/table_freelist_heap_fuzz.cpp libc/fuzzing/__support/trie_freelist_heap_fuzz.cpp libc/src/__support/freetable.h libc/test/src/__support/freetable_test.cpp libc/src/__support/big_int.h libc/src/__support/block.h libc/src/__support/freelist.cpp libc/src/__support/freelist.h libc/src/__support/freelist_heap.cpp libc/src/__support/freelist_heap.h libc/src/__support/freestore.h libc/src/__support/math_extras.h libc/test/src/__support/block_test.cpp libc/test/src/__support/freelist_heap_test.cpp libc/test/src/__support/freestore_test.cpp libc/test/src/__support/math_extras_test.cpp libc/fuzzing/__support/allocator_fuzz.h --diff_from_common_commit
``````````
:warning:
The reproduction instructions above might return results for more than one PR
in a stack if you are using a stacked PR workflow. You can limit the results by
changing `origin/main` to the base branch/commit you want to compare against.
:warning:
</details>
<details>
<summary>
View the diff from clang-format here.
</summary>
``````````diff
diff --git a/libc/fuzzing/__support/allocator_fuzz.h b/libc/fuzzing/__support/allocator_fuzz.h
index c7bd46179..a8e6cb857 100644
--- a/libc/fuzzing/__support/allocator_fuzz.h
+++ b/libc/fuzzing/__support/allocator_fuzz.h
@@ -39,8 +39,7 @@ struct Alloc {
};
// A simple vector that tracks allocations using the heap.
-template <typename HeapType>
-class AllocVec {
+template <typename HeapType> class AllocVec {
public:
AllocVec(HeapType &heap) : heap(&heap), size_(0), capacity(0) {
allocs = nullptr;
@@ -101,7 +100,8 @@ enum class AllocType : uint8_t {
};
template <typename AllocTypeEnum>
-LIBC_INLINE cpp::optional<AllocTypeEnum> choose_alloc_type(const uint8_t *&data, size_t &remainder) {
+LIBC_INLINE cpp::optional<AllocTypeEnum> choose_alloc_type(const uint8_t *&data,
+ size_t &remainder) {
auto raw = choose<uint8_t>(data, remainder);
if (!raw)
return cpp::nullopt;
@@ -111,7 +111,8 @@ LIBC_INLINE cpp::optional<AllocTypeEnum> choose_alloc_type(const uint8_t *&data,
constexpr size_t heap_size = 64 * 1024;
-LIBC_INLINE cpp::optional<size_t> choose_size(const uint8_t *&data, size_t &remainder) {
+LIBC_INLINE cpp::optional<size_t> choose_size(const uint8_t *&data,
+ size_t &remainder) {
auto raw = choose<size_t>(data, remainder);
if (!raw)
return cpp::nullopt;
@@ -119,8 +120,9 @@ LIBC_INLINE cpp::optional<size_t> choose_size(const uint8_t *&data, size_t &rema
}
template <typename AllocVecType>
-LIBC_INLINE cpp::optional<size_t> choose_alloc_idx(const AllocVecType &allocs, const uint8_t *&data,
- size_t &remainder) {
+LIBC_INLINE cpp::optional<size_t> choose_alloc_idx(const AllocVecType &allocs,
+ const uint8_t *&data,
+ size_t &remainder) {
if (allocs.empty())
return cpp::nullopt;
auto raw = choose<size_t>(data, remainder);
@@ -144,7 +146,8 @@ LIBC_INLINE int fuzz_one_input(const uint8_t *data, size_t remainder) {
while (true) {
ASSIGN_OR_RETURN(auto, should_alloc, choose<bool>(data, remainder));
if (should_alloc) {
- ASSIGN_OR_RETURN(auto, alloc_type, choose_alloc_type<AllocType>(data, remainder));
+ ASSIGN_OR_RETURN(auto, alloc_type,
+ choose_alloc_type<AllocType>(data, remainder));
ASSIGN_OR_RETURN(size_t, alloc_size, choose_size(data, remainder));
// Perform allocation.
diff --git a/libc/src/__support/big_int.h b/libc/src/__support/big_int.h
index 8bc0a33bf..c606c504e 100644
--- a/libc/src/__support/big_int.h
+++ b/libc/src/__support/big_int.h
@@ -1395,7 +1395,8 @@ template <typename T>
floor_ilog2(T value) {
if (value == 0)
return 0;
- return static_cast<T>(cpp::numeric_limits<T>::digits - cpp::countl_zero(value) - 1);
+ return static_cast<T>(cpp::numeric_limits<T>::digits -
+ cpp::countl_zero(value) - 1);
}
} // namespace LIBC_NAMESPACE_DECL
diff --git a/libc/src/__support/freelist_heap.h b/libc/src/__support/freelist_heap.h
index b254079c8..01b17e8b2 100644
--- a/libc/src/__support/freelist_heap.h
+++ b/libc/src/__support/freelist_heap.h
@@ -29,16 +29,14 @@ using cpp::span;
LIBC_INLINE constexpr bool IsPow2(size_t x) { return x && (x & (x - 1)) == 0; }
-template <typename T>
-LIBC_INLINE void init_free_store(T &, size_t) {}
+template <typename T> LIBC_INLINE void init_free_store(T &, size_t) {}
template <>
LIBC_INLINE void init_free_store<FreeStore>(FreeStore &store, size_t size) {
store.set_range({0, cpp::bit_ceil(size)});
}
-template <typename FreeStoreType = FreeStore>
-class FreeListHeap {
+template <typename FreeStoreType = FreeStore> class FreeListHeap {
public:
constexpr FreeListHeap() : begin(&_end), end(&__llvm_libc_heap_limit) {}
@@ -81,7 +79,8 @@ FreeListHeap(span<cpp::byte>) -> FreeListHeap<FreeStore>;
template <size_t BUFF_SIZE, typename FreeStoreType = FreeStore>
class FreeListHeapBuffer : public FreeListHeap<FreeStoreType> {
public:
- constexpr FreeListHeapBuffer() : FreeListHeap<FreeStoreType>{buffer}, buffer{} {}
+ constexpr FreeListHeapBuffer()
+ : FreeListHeap<FreeStoreType>{buffer}, buffer{} {}
private:
cpp::byte buffer[BUFF_SIZE];
@@ -110,7 +109,8 @@ LIBC_INLINE void FreeListHeap<FreeStoreType>::init() {
}
template <typename FreeStoreType>
-LIBC_INLINE void *FreeListHeap<FreeStoreType>::allocate_impl(size_t alignment, size_t size) {
+LIBC_INLINE void *FreeListHeap<FreeStoreType>::allocate_impl(size_t alignment,
+ size_t size) {
if (size == 0)
return nullptr;
@@ -141,8 +141,8 @@ LIBC_INLINE void *FreeListHeap<FreeStoreType>::allocate(size_t size) {
}
template <typename FreeStoreType>
-LIBC_INLINE void *FreeListHeap<FreeStoreType>::aligned_allocate(size_t alignment,
- size_t size) {
+LIBC_INLINE void *
+FreeListHeap<FreeStoreType>::aligned_allocate(size_t alignment, size_t size) {
// The alignment must be an integral power of two.
if (!IsPow2(alignment))
return nullptr;
@@ -190,7 +190,8 @@ LIBC_INLINE void FreeListHeap<FreeStoreType>::free(void *ptr) {
}
template <typename FreeStoreType>
-LIBC_INLINE bool FreeListHeap<FreeStoreType>::shrink_in_place(Block *block, size_t size) {
+LIBC_INLINE bool FreeListHeap<FreeStoreType>::shrink_in_place(Block *block,
+ size_t size) {
size_t min_outer_size = Block::outer_size(cpp::max(size, sizeof(size_t)));
uintptr_t next_block_start = Block::next_possible_block_start(
reinterpret_cast<uintptr_t>(block) + min_outer_size, Block::MIN_ALIGN);
diff --git a/libc/src/__support/freestore.h b/libc/src/__support/freestore.h
index 3516836ff..ea749129c 100644
--- a/libc/src/__support/freestore.h
+++ b/libc/src/__support/freestore.h
@@ -50,7 +50,6 @@ public:
(MIN_LARGE_OUTER_SIZE - MIN_OUTER_SIZE) / Block::MIN_ALIGN;
private:
-
LIBC_INLINE static bool too_small(Block *block) {
return block->outer_size() < MIN_OUTER_SIZE;
}
diff --git a/libc/src/__support/freetable.h b/libc/src/__support/freetable.h
index a3db6d4c4..acf58d888 100644
--- a/libc/src/__support/freetable.h
+++ b/libc/src/__support/freetable.h
@@ -103,12 +103,12 @@ protected:
cpp::numeric_limits<uintptr_t>::digits;
constexpr static size_t TOTAL_BITS =
CONFIG::NUM_TABLE_ENTRIES * BITS_PER_ENTRY;
+
public:
constexpr static size_t MIN_OUTER_SIZE =
align_up(sizeof(Block) + sizeof(FreeList::Node), Block::MIN_ALIGN);
protected:
-
LIBC_INLINE static bool too_small(Block *block) {
return block->outer_size() < MIN_OUTER_SIZE;
}
@@ -211,11 +211,11 @@ LIBC_INLINE void FreeTableImpl<CONFIG>::remove(Block *block) {
template <typename CONFIG>
LIBC_INLINE Block *
FreeTableImpl<CONFIG>::remove_first_fit_in_list(size_t index, size_t size) {
- // Performs a linear search on the free list to find the first block that fits.
- // Note that this linear search only ever happens during large allocations when
- // falling back to searching the exact-fit size class (or the overflow bin).
- // For standard small allocations, we always find a guaranteed fit in a larger
- // oversized freelist, allowing a one-shot O(1) pop.
+ // Performs a linear search on the free list to find the first block that
+ // fits. Note that this linear search only ever happens during large
+ // allocations when falling back to searching the exact-fit size class (or the
+ // overflow bin). For standard small allocations, we always find a guaranteed
+ // fit in a larger oversized freelist, allowing a one-shot O(1) pop.
FreeList::Node *begin_node = free_lists[index].begin();
if (begin_node == nullptr)
return nullptr;
diff --git a/libc/test/src/__support/freestore_test.cpp b/libc/test/src/__support/freestore_test.cpp
index 12cd0fcc6..515648e83 100644
--- a/libc/test/src/__support/freestore_test.cpp
+++ b/libc/test/src/__support/freestore_test.cpp
@@ -72,16 +72,19 @@ TEST(LlvmLibcFreeStore, RemoveBestFit) {
store.insert(smallest);
// Find exact match for largest.
- ASSERT_EQ(store.find_and_remove_fit(largest_small->inner_size()), largest_small);
+ ASSERT_EQ(store.find_and_remove_fit(largest_small->inner_size()),
+ largest_small);
store.insert(largest_small);
// Search small list for best fit.
Block *next_smallest = largest_small == smallest ? remainder : largest_small;
- ASSERT_EQ(store.find_and_remove_fit(smallest->inner_size() + 1), next_smallest);
+ ASSERT_EQ(store.find_and_remove_fit(smallest->inner_size() + 1),
+ next_smallest);
store.insert(next_smallest);
// Continue search for best fit to large blocks.
- EXPECT_EQ(store.find_and_remove_fit(largest_small->inner_size() + 1), remainder);
+ EXPECT_EQ(store.find_and_remove_fit(largest_small->inner_size() + 1),
+ remainder);
}
TEST(LlvmLibcFreeStore, Remove) {
diff --git a/libc/test/src/__support/freetable_test.cpp b/libc/test/src/__support/freetable_test.cpp
index f465f2154..ce3391d1c 100644
--- a/libc/test/src/__support/freetable_test.cpp
+++ b/libc/test/src/__support/freetable_test.cpp
@@ -23,19 +23,22 @@ using LIBC_NAMESPACE::FreeTable;
// internal helper methods to the unit tests.
template <size_t UNIT_SIZE, size_t STEP_SIZE_BITS, size_t NUM_STEP_BITS,
size_t NUM_TABLE_ENTRIES>
-struct TestTable : public FreeTable<UNIT_SIZE, STEP_SIZE_BITS, NUM_STEP_BITS, NUM_TABLE_ENTRIES> {
- using Base = FreeTable<UNIT_SIZE, STEP_SIZE_BITS, NUM_STEP_BITS, NUM_TABLE_ENTRIES>;
- using Base::TOTAL_BITS;
- using Base::size_to_bit_index;
- using Base::get_bit;
- using Base::find_first_bit_set_after;
- using Base::set_bit;
+struct TestTable : public FreeTable<UNIT_SIZE, STEP_SIZE_BITS, NUM_STEP_BITS,
+ NUM_TABLE_ENTRIES> {
+ using Base =
+ FreeTable<UNIT_SIZE, STEP_SIZE_BITS, NUM_STEP_BITS, NUM_TABLE_ENTRIES>;
using Base::clear_bit;
+ using Base::find_first_bit_set_after;
+ using Base::get_bit;
using Base::remove_first_fit_in_list;
+ using Base::set_bit;
+ using Base::size_to_bit_index;
+ using Base::TOTAL_BITS;
};
TEST(LlvmLibcFreeTableTest, SizeToBitIndex) {
- constexpr size_t BITS_PER_ENTRY = LIBC_NAMESPACE::cpp::numeric_limits<uintptr_t>::digits;
+ constexpr size_t BITS_PER_ENTRY =
+ LIBC_NAMESPACE::cpp::numeric_limits<uintptr_t>::digits;
constexpr size_t NUM_TABLE_ENTRIES = 192 / BITS_PER_ENTRY;
// Parameters matching our visual/mathematical layout example:
@@ -88,7 +91,8 @@ TEST(LlvmLibcFreeTableTest, SizeToBitIndex) {
}
TEST(LlvmLibcFreeTableTest, BitManipulation) {
- constexpr size_t BITS_PER_ENTRY = LIBC_NAMESPACE::cpp::numeric_limits<uintptr_t>::digits;
+ constexpr size_t BITS_PER_ENTRY =
+ LIBC_NAMESPACE::cpp::numeric_limits<uintptr_t>::digits;
constexpr size_t NUM_TABLE_ENTRIES = 192 / BITS_PER_ENTRY;
// 192-bit table
@@ -111,8 +115,10 @@ TEST(LlvmLibcFreeTableTest, BitManipulation) {
EXPECT_EQ(table.find_first_bit_set_after(9), size_t(10));
EXPECT_EQ(table.find_first_bit_set_after(10), size_t(150));
EXPECT_EQ(table.find_first_bit_set_after(149), size_t(150));
- EXPECT_EQ(table.find_first_bit_set_after(150), Table::TOTAL_BITS); // TOTAL_BITS
- EXPECT_EQ(table.find_first_bit_set_after(151), Table::TOTAL_BITS); // TOTAL_BITS
+ EXPECT_EQ(table.find_first_bit_set_after(150),
+ Table::TOTAL_BITS); // TOTAL_BITS
+ EXPECT_EQ(table.find_first_bit_set_after(151),
+ Table::TOTAL_BITS); // TOTAL_BITS
// Test clear_bit
table.clear_bit(10);
@@ -125,7 +131,8 @@ TEST(LlvmLibcFreeTableTest, BitManipulation) {
}
TEST(LlvmLibcFreeTableTest, InsertAndRemove) {
- constexpr size_t BITS_PER_ENTRY = LIBC_NAMESPACE::cpp::numeric_limits<uintptr_t>::digits;
+ constexpr size_t BITS_PER_ENTRY =
+ LIBC_NAMESPACE::cpp::numeric_limits<uintptr_t>::digits;
constexpr size_t NUM_TABLE_ENTRIES = 192 / BITS_PER_ENTRY;
// 192-bit table
@@ -159,7 +166,8 @@ TEST(LlvmLibcFreeTableTest, InsertAndRemove) {
}
TEST(LlvmLibcFreeTableTest, RemoveFirstFitInList) {
- constexpr size_t BITS_PER_ENTRY = LIBC_NAMESPACE::cpp::numeric_limits<uintptr_t>::digits;
+ constexpr size_t BITS_PER_ENTRY =
+ LIBC_NAMESPACE::cpp::numeric_limits<uintptr_t>::digits;
constexpr size_t NUM_TABLE_ENTRIES = 192 / BITS_PER_ENTRY;
// 192-bit table
@@ -214,7 +222,8 @@ TEST(LlvmLibcFreeTableTest, RemoveFirstFitInList) {
}
TEST(LlvmLibcFreeTableTest, FindAndRemoveFit) {
- constexpr size_t BITS_PER_ENTRY = LIBC_NAMESPACE::cpp::numeric_limits<uintptr_t>::digits;
+ constexpr size_t BITS_PER_ENTRY =
+ LIBC_NAMESPACE::cpp::numeric_limits<uintptr_t>::digits;
constexpr size_t NUM_TABLE_ENTRIES = 192 / BITS_PER_ENTRY;
// 192-bit table
@@ -257,7 +266,8 @@ TEST(LlvmLibcFreeTableTest, FindAndRemoveFit) {
table.insert(block2);
EXPECT_TRUE(table.get_bit(36));
- // Request 1050 B (maps to 32). Bin 32 is empty, so it will find and pop the next set bit (36)
+ // Request 1050 B (maps to 32). Bin 32 is empty, so it will find and pop the
+ // next set bit (36)
removed = table.find_and_remove_fit(1050);
EXPECT_EQ(removed, block2);
EXPECT_FALSE(table.get_bit(36));
``````````
</details>
https://github.com/llvm/llvm-project/pull/200497
More information about the libc-commits
mailing list