[libc-commits] [libc] [libc][stdlib] Make the FreeListHeap constant-initializable (PR #95453)
via libc-commits
libc-commits at lists.llvm.org
Fri Jun 14 12:06:07 PDT 2024
https://github.com/PiJoules updated https://github.com/llvm/llvm-project/pull/95453
>From da5603fbb56425d2b0f91043a62d085149d46d3e Mon Sep 17 00:00:00 2001
From: Leonard Chan <leonardchan at google.com>
Date: Thu, 13 Jun 2024 12:05:55 -0700
Subject: [PATCH] [libc][stdlib] Make the FreeListHeap constant-initializable
This refactors some of the FreeListHeap, FreeList, and Block classes to
have constexpr ctors so we can constinit a global allocator that does
not require running some global function or global ctor to initialize.
This is needed to prevent worrying about initialization order and any
other module-ctor can invoke malloc without worry.
---
libc/src/__support/fixedvector.h | 7 +
libc/src/stdlib/CMakeLists.txt | 8 +-
libc/src/stdlib/block.h | 10 +-
libc/src/stdlib/freelist.h | 37 +++--
libc/src/stdlib/freelist_heap.h | 69 +++++++--
libc/src/stdlib/freelist_malloc.cpp | 48 ++++++
libc/src/stdlib/realloc.h | 20 +++
libc/test/src/stdlib/CMakeLists.txt | 2 +
libc/test/src/stdlib/freelist_heap_test.cpp | 140 ++++++++----------
libc/test/src/stdlib/freelist_malloc_test.cpp | 56 +++++++
10 files changed, 283 insertions(+), 114 deletions(-)
create mode 100644 libc/src/stdlib/freelist_malloc.cpp
create mode 100644 libc/src/stdlib/realloc.h
create mode 100644 libc/test/src/stdlib/freelist_malloc_test.cpp
diff --git a/libc/src/__support/fixedvector.h b/libc/src/__support/fixedvector.h
index 43028a0a84637..403b1620d20df 100644
--- a/libc/src/__support/fixedvector.h
+++ b/libc/src/__support/fixedvector.h
@@ -30,6 +30,13 @@ template <typename T, size_t CAPACITY> class FixedVector {
push_back(*begin);
}
+ using const_iterator = typename cpp::array<T, CAPACITY>::const_iterator;
+ constexpr FixedVector(const_iterator begin, const_iterator end)
+ : store{}, item_count{} {
+ for (; begin != end; ++begin)
+ push_back(*begin);
+ }
+
constexpr FixedVector(size_t count, const T &value) : store{}, item_count{} {
for (size_t i = 0; i < count; ++i)
push_back(value);
diff --git a/libc/src/stdlib/CMakeLists.txt b/libc/src/stdlib/CMakeLists.txt
index 6d2c5acca9605..e26c19f03f5ab 100644
--- a/libc/src/stdlib/CMakeLists.txt
+++ b/libc/src/stdlib/CMakeLists.txt
@@ -418,8 +418,14 @@ else()
libc.src.string.memory_utils.inline_memcpy
libc.src.string.memory_utils.inline_memset
)
- add_entrypoint_external(
+ add_entrypoint_object(
malloc
+ SRCS
+ freelist_malloc.cpp
+ HDRS
+ malloc.h
+ DEPENDS
+ .freelist_heap
)
add_entrypoint_external(
free
diff --git a/libc/src/stdlib/block.h b/libc/src/stdlib/block.h
index afb18c1ef738f..b0462a12afb39 100644
--- a/libc/src/stdlib/block.h
+++ b/libc/src/stdlib/block.h
@@ -245,7 +245,7 @@ class Block {
void mark_free() { info_.used = 0; }
/// Marks this block as the last one in the chain.
- void mark_last() { info_.last = 1; }
+ constexpr void mark_last() { info_.last = 1; }
/// Clears the last bit from this block.
void clear_last() { info_.last = 1; }
@@ -259,6 +259,8 @@ class Block {
return check_status() == internal::BlockStatus::VALID;
}
+ constexpr Block(size_t prev_outer_size, size_t outer_size);
+
private:
/// Consumes the block and returns as a span of bytes.
static ByteSpan as_bytes(Block *&&block);
@@ -266,8 +268,6 @@ class Block {
/// Consumes the span of bytes and uses it to construct and return a block.
static Block *as_block(size_t prev_outer_size, ByteSpan bytes);
- Block(size_t prev_outer_size, size_t outer_size);
-
/// Returns a `BlockStatus` that is either VALID or indicates the reason why
/// the block is invalid.
///
@@ -442,7 +442,9 @@ Block<OffsetType, kAlign> *Block<OffsetType, kAlign>::prev() const {
// Private template method implementations.
template <typename OffsetType, size_t kAlign>
-Block<OffsetType, kAlign>::Block(size_t prev_outer_size, size_t outer_size) {
+constexpr Block<OffsetType, kAlign>::Block(size_t prev_outer_size,
+ size_t outer_size)
+ : info_{} {
prev_ = prev_outer_size / ALIGNMENT;
next_ = outer_size / ALIGNMENT;
info_.used = 0;
diff --git a/libc/src/stdlib/freelist.h b/libc/src/stdlib/freelist.h
index 789bc164fb161..eb5088b25d43f 100644
--- a/libc/src/stdlib/freelist.h
+++ b/libc/src/stdlib/freelist.h
@@ -69,36 +69,44 @@ template <size_t NUM_BUCKETS = 6> class FreeList {
/// Removes a chunk from this freelist.
bool remove_chunk(cpp::span<cpp::byte> chunk);
-private:
- // For a given size, find which index into chunks_ the node should be written
- // to.
- size_t find_chunk_ptr_for_size(size_t size, bool non_null) const;
+ /// 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;
};
-public:
- explicit FreeList(cpp::array<size_t, NUM_BUCKETS> sizes)
+ 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;
- // Add it to the correct list.
- size_t chunk_ptr = find_chunk_ptr_for_size(chunk.size(), false);
-
- FreeListNode *node =
- ::new (chunk.data()) FreeListNode{chunks_[chunk_ptr], chunk.size()};
- chunks_[chunk_ptr] = node;
+ FreeListNode *node = ::new (chunk.data()) FreeListNode;
+ set_freelist_node(*node, chunk);
return true;
}
@@ -163,8 +171,9 @@ bool FreeList<NUM_BUCKETS>::remove_chunk(span<cpp::byte> chunk) {
}
template <size_t NUM_BUCKETS>
-size_t FreeList<NUM_BUCKETS>::find_chunk_ptr_for_size(size_t size,
- bool non_null) const {
+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 &&
diff --git a/libc/src/stdlib/freelist_heap.h b/libc/src/stdlib/freelist_heap.h
index b65d361e9ca73..6357c047021df 100644
--- a/libc/src/stdlib/freelist_heap.h
+++ b/libc/src/stdlib/freelist_heap.h
@@ -30,6 +30,7 @@ static constexpr cpp::array<size_t, 6> DEFAULT_BUCKETS{16, 32, 64,
template <size_t NUM_BUCKETS = DEFAULT_BUCKETS.size()> class FreeListHeap {
public:
using BlockType = Block<>;
+ using FreeListType = FreeList<NUM_BUCKETS>;
struct HeapStats {
size_t total_bytes;
@@ -39,7 +40,19 @@ template <size_t NUM_BUCKETS = DEFAULT_BUCKETS.size()> class FreeListHeap {
size_t total_allocate_calls;
size_t total_free_calls;
};
- FreeListHeap(span<cpp::byte> region);
+
+ FreeListHeap(span<cpp::byte> region)
+ : FreeListHeap(&*region.begin(), &*region.end(), region.size()) {
+ auto result = BlockType::init(region);
+ BlockType *block = *result;
+ freelist_.add_chunk(block_to_span(block));
+ }
+
+ constexpr FreeListHeap(void *start, cpp::byte *end, size_t total_bytes)
+ : block_region_start_(start), block_region_end_(end),
+ freelist_(DEFAULT_BUCKETS), heap_stats_{} {
+ heap_stats_.total_bytes = total_bytes;
+ }
void *allocate(size_t size);
void free(void *ptr);
@@ -47,27 +60,53 @@ template <size_t NUM_BUCKETS = DEFAULT_BUCKETS.size()> class FreeListHeap {
void *calloc(size_t num, size_t size);
const HeapStats &heap_stats() const { return heap_stats_; }
+ void reset_heap_stats() { heap_stats_ = {}; }
+
+ void *region_start() const { return block_region_start_; }
+ size_t region_size() const {
+ return reinterpret_cast<uintptr_t>(block_region_end_) -
+ reinterpret_cast<uintptr_t>(block_region_start_);
+ }
+
+protected:
+ constexpr void set_freelist_node(typename FreeListType::FreeListNode &node,
+ cpp::span<cpp::byte> chunk) {
+ freelist_.set_freelist_node(node, chunk);
+ }
private:
span<cpp::byte> block_to_span(BlockType *block) {
return span<cpp::byte>(block->usable_space(), block->inner_size());
}
- span<cpp::byte> region_;
- FreeList<NUM_BUCKETS> freelist_;
+ bool is_valid_ptr(void *ptr) {
+ return ptr >= block_region_start_ && ptr < block_region_end_;
+ }
+
+ void *block_region_start_;
+ void *block_region_end_;
+ FreeListType freelist_;
HeapStats heap_stats_;
};
-template <size_t NUM_BUCKETS>
-FreeListHeap<NUM_BUCKETS>::FreeListHeap(span<cpp::byte> region)
- : region_(region), freelist_(DEFAULT_BUCKETS), heap_stats_() {
- auto result = BlockType::init(region);
- BlockType *block = *result;
+template <size_t BUFF_SIZE, size_t NUM_BUCKETS = DEFAULT_BUCKETS.size()>
+struct FreeListHeapBuffer : public FreeListHeap<NUM_BUCKETS> {
+ using parent = FreeListHeap<NUM_BUCKETS>;
+ using FreeListNode = typename parent::FreeListType::FreeListNode;
- freelist_.add_chunk(block_to_span(block));
+ constexpr FreeListHeapBuffer()
+ : FreeListHeap<NUM_BUCKETS>(&block, buffer + sizeof(buffer), BUFF_SIZE),
+ block(0, BUFF_SIZE), node{}, buffer{} {
+ block.mark_last();
- heap_stats_.total_bytes = region.size();
-}
+ cpp::span<cpp::byte> chunk(buffer, sizeof(buffer));
+ parent::set_freelist_node(node, chunk);
+ }
+
+ typename parent::BlockType block;
+ FreeListNode node;
+ cpp::byte buffer[BUFF_SIZE - sizeof(block) - sizeof(node)];
+};
template <size_t NUM_BUCKETS>
void *FreeListHeap<NUM_BUCKETS>::allocate(size_t size) {
@@ -97,7 +136,7 @@ void *FreeListHeap<NUM_BUCKETS>::allocate(size_t size) {
template <size_t NUM_BUCKETS> void FreeListHeap<NUM_BUCKETS>::free(void *ptr) {
cpp::byte *bytes = static_cast<cpp::byte *>(ptr);
- LIBC_ASSERT(bytes >= region_.data() && bytes < region_.data() + region_.size() && "Invalid pointer");
+ LIBC_ASSERT(is_valid_ptr(bytes) && "Invalid pointer");
BlockType *chunk_block = BlockType::from_usable_space(bytes);
@@ -131,7 +170,7 @@ template <size_t NUM_BUCKETS> void FreeListHeap<NUM_BUCKETS>::free(void *ptr) {
heap_stats_.total_free_calls += 1;
}
-// Follows contract of the C standard realloc() function
+// Follows constract of the C standard realloc() function
// If ptr is free'd, will return nullptr.
template <size_t NUM_BUCKETS>
void *FreeListHeap<NUM_BUCKETS>::realloc(void *ptr, size_t size) {
@@ -146,7 +185,7 @@ void *FreeListHeap<NUM_BUCKETS>::realloc(void *ptr, size_t size) {
cpp::byte *bytes = static_cast<cpp::byte *>(ptr);
- if (bytes < region_.data() || bytes >= region_.data() + region_.size())
+ if (!is_valid_ptr(bytes))
return nullptr;
BlockType *chunk_block = BlockType::from_usable_space(bytes);
@@ -177,6 +216,8 @@ void *FreeListHeap<NUM_BUCKETS>::calloc(size_t num, size_t size) {
return ptr;
}
+extern FreeListHeap<> *freelist_heap;
+
} // namespace LIBC_NAMESPACE
#endif // LLVM_LIBC_SRC_STDLIB_FREELIST_HEAP_H
diff --git a/libc/src/stdlib/freelist_malloc.cpp b/libc/src/stdlib/freelist_malloc.cpp
new file mode 100644
index 0000000000000..185b36444e371
--- /dev/null
+++ b/libc/src/stdlib/freelist_malloc.cpp
@@ -0,0 +1,48 @@
+//===-- Implementation 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.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "freelist_heap.h"
+#include "src/stdlib/calloc.h"
+#include "src/stdlib/free.h"
+#include "src/stdlib/malloc.h"
+#include "src/stdlib/realloc.h"
+
+#include <stddef.h>
+
+namespace LIBC_NAMESPACE {
+
+namespace {
+// Users can define LIBC_FREELIST_MALLOC_SIZE for setting the default buffer
+// size used by freelist malloc.
+#ifdef LIBC_FREELIST_MALLOC_SIZE
+constexpr size_t SIZE = LIBC_FREELIST_MALLOC_SIZE;
+#else
+// TODO: We should probably have something akin to what scudo/sanitizer
+// allocators do where each platform defines this.
+constexpr size_t SIZE = 0x40000000ULL; // 1GB
+#endif
+LIBC_CONSTINIT FreeListHeapBuffer<SIZE> freelist_heap_buffer;
+} // namespace
+
+FreeListHeap<> *freelist_heap = &freelist_heap_buffer;
+
+LLVM_LIBC_FUNCTION(void *, malloc, (size_t size)) {
+ return freelist_heap->allocate(size);
+}
+
+LLVM_LIBC_FUNCTION(void, free, (void *ptr)) { return freelist_heap->free(ptr); }
+
+LLVM_LIBC_FUNCTION(void *, calloc, (size_t num, size_t size)) {
+ return freelist_heap->calloc(num, size);
+}
+
+LLVM_LIBC_FUNCTION(void *, realloc, (void *ptr, size_t size)) {
+ return freelist_heap->realloc(ptr, size);
+}
+
+} // namespace LIBC_NAMESPACE
diff --git a/libc/src/stdlib/realloc.h b/libc/src/stdlib/realloc.h
new file mode 100644
index 0000000000000..6e025faa7a8ce
--- /dev/null
+++ b/libc/src/stdlib/realloc.h
@@ -0,0 +1,20 @@
+//===-- Implementation header for realloc -----------------------*- 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>
+
+#ifndef LLVM_LIBC_SRC_STDLIB_REALLOC_H
+#define LLVM_LIBC_SRC_STDLIB_REALLOC_H
+
+namespace LIBC_NAMESPACE {
+
+void *realloc(void *ptr, size_t size);
+
+} // namespace LIBC_NAMESPACE
+
+#endif // LLVM_LIBC_SRC_STDLIB_REALLOC_H
diff --git a/libc/test/src/stdlib/CMakeLists.txt b/libc/test/src/stdlib/CMakeLists.txt
index f3033a4d958bf..648404afb5730 100644
--- a/libc/test/src/stdlib/CMakeLists.txt
+++ b/libc/test/src/stdlib/CMakeLists.txt
@@ -85,9 +85,11 @@ add_libc_test(
libc-stdlib-tests
SRCS
freelist_heap_test.cpp
+ freelist_malloc_test.cpp
DEPENDS
libc.src.__support.CPP.span
libc.src.stdlib.freelist_heap
+ libc.src.stdlib.malloc
libc.src.string.memcmp
libc.src.string.memcpy
)
diff --git a/libc/test/src/stdlib/freelist_heap_test.cpp b/libc/test/src/stdlib/freelist_heap_test.cpp
index b89f47f9a2def..e30c23e724a06 100644
--- a/libc/test/src/stdlib/freelist_heap_test.cpp
+++ b/libc/test/src/stdlib/freelist_heap_test.cpp
@@ -14,27 +14,47 @@
namespace LIBC_NAMESPACE {
-TEST(LlvmLibcFreeListHeap, CanAllocate) {
- constexpr size_t N = 2048;
- constexpr size_t ALLOC_SIZE = 512;
- alignas(FreeListHeap<>::BlockType) cpp::byte buf[N] = {cpp::byte(0)};
+using LIBC_NAMESPACE::freelist_heap;
- FreeListHeap<> allocator(buf);
+// Similar to `LlvmLibcBlockTest` in block_test.cpp, we'd like to run the same
+// tests independently for different parameters. In this case, we'd like to test
+// functionality for a `FreeListHeap` and the global `freelist_heap` which was
+// constinit'd. Functionally, it should operate the same if the FreeListHeap
+// were initialized locally at runtime or at compile-time.
+//
+// Note that calls to `allocate` for each test case here don't always explicitly
+// `free` them afterwards, so when testing the global allocator, allocations
+// 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 testing::Test { \
+ public: \
+ void RunTest(FreeListHeap<> &allocator, [[maybe_unused]] size_t N); \
+ }; \
+ TEST_F(LlvmLibcFreeListHeapTest##TestCase, TestCase) { \
+ 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, \
+ size_t N)
+
+TEST_FOR_EACH_ALLOCATOR(CanAllocate, 2048) {
+ constexpr size_t ALLOC_SIZE = 512;
void *ptr = allocator.allocate(ALLOC_SIZE);
ASSERT_NE(ptr, static_cast<void *>(nullptr));
// In this case, the allocator should be returning us the start of the chunk.
EXPECT_EQ(ptr, static_cast<void *>(
- &buf[0] + FreeListHeap<>::BlockType::BLOCK_OVERHEAD));
+ reinterpret_cast<cpp::byte *>(allocator.region_start()) +
+ FreeListHeap<>::BlockType::BLOCK_OVERHEAD));
}
-TEST(LlvmLibcFreeListHeap, AllocationsDontOverlap) {
- constexpr size_t N = 2048;
+TEST_FOR_EACH_ALLOCATOR(AllocationsDontOverlap, 2048) {
constexpr size_t ALLOC_SIZE = 512;
- alignas(FreeListHeap<>::BlockType) cpp::byte buf[N] = {cpp::byte(0)};
-
- FreeListHeap<> allocator(buf);
void *ptr1 = allocator.allocate(ALLOC_SIZE);
void *ptr2 = allocator.allocate(ALLOC_SIZE);
@@ -49,14 +69,10 @@ TEST(LlvmLibcFreeListHeap, AllocationsDontOverlap) {
EXPECT_GT(ptr2_start, ptr1_end);
}
-TEST(LlvmLibcFreeListHeap, CanFreeAndRealloc) {
+TEST_FOR_EACH_ALLOCATOR(CanFreeAndRealloc, 2048) {
// There's not really a nice way to test that free works, apart from to try
// and get that value back again.
- constexpr size_t N = 2048;
constexpr size_t ALLOC_SIZE = 512;
- alignas(FreeListHeap<>::BlockType) cpp::byte buf[N] = {cpp::byte(0)};
-
- FreeListHeap<> allocator(buf);
void *ptr1 = allocator.allocate(ALLOC_SIZE);
allocator.free(ptr1);
@@ -65,15 +81,13 @@ TEST(LlvmLibcFreeListHeap, CanFreeAndRealloc) {
EXPECT_EQ(ptr1, ptr2);
}
-TEST(LlvmLibcFreeListHeap, ReturnsNullWhenAllocationTooLarge) {
- constexpr size_t N = 2048;
- alignas(FreeListHeap<>::BlockType) cpp::byte buf[N] = {cpp::byte(0)};
-
- FreeListHeap<> allocator(buf);
-
+TEST_FOR_EACH_ALLOCATOR(ReturnsNullWhenAllocationTooLarge, 2048) {
EXPECT_EQ(allocator.allocate(N), static_cast<void *>(nullptr));
}
+// NOTE: This doesn't use TEST_FOR_EACH_ALLOCATOR because the first `allocate`
+// here will likely actually return a nullptr since the same global allocator
+// is used for other test cases and we don't explicitly free them.
TEST(LlvmLibcFreeListHeap, ReturnsNullWhenFull) {
constexpr size_t N = 2048;
alignas(FreeListHeap<>::BlockType) cpp::byte buf[N] = {cpp::byte(0)};
@@ -85,12 +99,7 @@ TEST(LlvmLibcFreeListHeap, ReturnsNullWhenFull) {
EXPECT_EQ(allocator.allocate(1), static_cast<void *>(nullptr));
}
-TEST(LlvmLibcFreeListHeap, ReturnedPointersAreAligned) {
- constexpr size_t N = 2048;
- alignas(FreeListHeap<>::BlockType) cpp::byte buf[N] = {cpp::byte(0)};
-
- FreeListHeap<> allocator(buf);
-
+TEST_FOR_EACH_ALLOCATOR(ReturnedPointersAreAligned, 2048) {
void *ptr1 = allocator.allocate(1);
// Should be aligned to native pointer alignment
@@ -105,13 +114,9 @@ TEST(LlvmLibcFreeListHeap, ReturnedPointersAreAligned) {
EXPECT_EQ(ptr2_start % alignment, static_cast<size_t>(0));
}
-TEST(LlvmLibcFreeListHeap, CanRealloc) {
- constexpr size_t N = 2048;
+TEST_FOR_EACH_ALLOCATOR(CanRealloc, 2048) {
constexpr size_t ALLOC_SIZE = 512;
constexpr size_t kNewAllocSize = 768;
- alignas(FreeListHeap<>::BlockType) cpp::byte buf[N] = {cpp::byte(1)};
-
- FreeListHeap<> allocator(buf);
void *ptr1 = allocator.allocate(ALLOC_SIZE);
void *ptr2 = allocator.realloc(ptr1, kNewAllocSize);
@@ -120,23 +125,19 @@ TEST(LlvmLibcFreeListHeap, CanRealloc) {
ASSERT_NE(ptr2, static_cast<void *>(nullptr));
}
-TEST(LlvmLibcFreeListHeap, ReallocHasSameContent) {
- constexpr size_t N = 2048;
+TEST_FOR_EACH_ALLOCATOR(ReallocHasSameContent, 2048) {
constexpr size_t ALLOC_SIZE = sizeof(int);
constexpr size_t kNewAllocSize = sizeof(int) * 2;
- alignas(FreeListHeap<>::BlockType) cpp::byte buf[N] = {cpp::byte(1)};
// Data inside the allocated block.
cpp::byte data1[ALLOC_SIZE];
// Data inside the reallocated block.
cpp::byte data2[ALLOC_SIZE];
- FreeListHeap<> allocator(buf);
-
int *ptr1 = reinterpret_cast<int *>(allocator.allocate(ALLOC_SIZE));
*ptr1 = 42;
- memcpy(data1, ptr1, ALLOC_SIZE);
+ LIBC_NAMESPACE::memcpy(data1, ptr1, ALLOC_SIZE);
int *ptr2 = reinterpret_cast<int *>(allocator.realloc(ptr1, kNewAllocSize));
- memcpy(data2, ptr2, ALLOC_SIZE);
+ LIBC_NAMESPACE::memcpy(data2, ptr2, ALLOC_SIZE);
ASSERT_NE(ptr1, static_cast<int *>(nullptr));
ASSERT_NE(ptr2, static_cast<int *>(nullptr));
@@ -144,13 +145,9 @@ TEST(LlvmLibcFreeListHeap, ReallocHasSameContent) {
EXPECT_EQ(LIBC_NAMESPACE::memcmp(data1, data2, ALLOC_SIZE), 0);
}
-TEST(LlvmLibcFreeListHeap, ReturnsNullReallocFreedPointer) {
- constexpr size_t N = 2048;
+TEST_FOR_EACH_ALLOCATOR(ReturnsNullReallocFreedPointer, 2048) {
constexpr size_t ALLOC_SIZE = 512;
constexpr size_t kNewAllocSize = 256;
- alignas(FreeListHeap<>::BlockType) cpp::byte buf[N] = {cpp::byte(0)};
-
- FreeListHeap<> allocator(buf);
void *ptr1 = allocator.allocate(ALLOC_SIZE);
allocator.free(ptr1);
@@ -159,13 +156,9 @@ TEST(LlvmLibcFreeListHeap, ReturnsNullReallocFreedPointer) {
EXPECT_EQ(static_cast<void *>(nullptr), ptr2);
}
-TEST(LlvmLibcFreeListHeap, ReallocSmallerSize) {
- constexpr size_t N = 2048;
+TEST_FOR_EACH_ALLOCATOR(ReallocSmallerSize, 2048) {
constexpr size_t ALLOC_SIZE = 512;
constexpr size_t kNewAllocSize = 256;
- alignas(FreeListHeap<>::BlockType) cpp::byte buf[N] = {cpp::byte(0)};
-
- FreeListHeap<> allocator(buf);
void *ptr1 = allocator.allocate(ALLOC_SIZE);
void *ptr2 = allocator.realloc(ptr1, kNewAllocSize);
@@ -174,13 +167,9 @@ TEST(LlvmLibcFreeListHeap, ReallocSmallerSize) {
EXPECT_EQ(ptr1, ptr2);
}
-TEST(LlvmLibcFreeListHeap, ReallocTooLarge) {
- constexpr size_t N = 2048;
+TEST_FOR_EACH_ALLOCATOR(ReallocTooLarge, 2048) {
constexpr size_t ALLOC_SIZE = 512;
- constexpr size_t kNewAllocSize = 4096;
- alignas(FreeListHeap<>::BlockType) cpp::byte buf[N] = {cpp::byte(0)};
-
- FreeListHeap<> allocator(buf);
+ size_t kNewAllocSize = N * 2; // Large enough to fail.
void *ptr1 = allocator.allocate(ALLOC_SIZE);
void *ptr2 = allocator.realloc(ptr1, kNewAllocSize);
@@ -190,49 +179,38 @@ TEST(LlvmLibcFreeListHeap, ReallocTooLarge) {
EXPECT_EQ(static_cast<void *>(nullptr), ptr2);
}
-TEST(LlvmLibcFreeListHeap, CanCalloc) {
- constexpr size_t N = 2048;
+TEST_FOR_EACH_ALLOCATOR(CanCalloc, 2048) {
constexpr size_t ALLOC_SIZE = 128;
- constexpr size_t kNum = 4;
- constexpr int size = kNum * ALLOC_SIZE;
- alignas(FreeListHeap<>::BlockType) cpp::byte buf[N] = {cpp::byte(1)};
+ constexpr size_t NUM = 4;
+ constexpr int size = NUM * ALLOC_SIZE;
constexpr cpp::byte zero{0};
- FreeListHeap<> allocator(buf);
-
cpp::byte *ptr1 =
- reinterpret_cast<cpp::byte *>(allocator.calloc(kNum, ALLOC_SIZE));
+ reinterpret_cast<cpp::byte *>(allocator.calloc(NUM, ALLOC_SIZE));
// calloc'd content is zero.
- for (int i = 0; i < size; i++)
+ for (int i = 0; i < size; i++) {
EXPECT_EQ(ptr1[i], zero);
+ }
}
-TEST(LlvmLibcFreeListHeap, CanCallocWeirdSize) {
- constexpr size_t N = 2048;
+TEST_FOR_EACH_ALLOCATOR(CanCallocWeirdSize, 2048) {
constexpr size_t ALLOC_SIZE = 143;
- constexpr size_t kNum = 3;
- constexpr int size = kNum * ALLOC_SIZE;
- alignas(FreeListHeap<>::BlockType) cpp::byte buf[N] = {cpp::byte(132)};
+ constexpr size_t NUM = 3;
+ constexpr int size = NUM * ALLOC_SIZE;
constexpr cpp::byte zero{0};
- FreeListHeap<> allocator(buf);
-
cpp::byte *ptr1 =
- reinterpret_cast<cpp::byte *>(allocator.calloc(kNum, ALLOC_SIZE));
+ reinterpret_cast<cpp::byte *>(allocator.calloc(NUM, ALLOC_SIZE));
// calloc'd content is zero.
- for (int i = 0; i < size; i++)
+ for (int i = 0; i < size; i++) {
EXPECT_EQ(ptr1[i], zero);
+ }
}
-TEST(LlvmLibcFreeListHeap, CallocTooLarge) {
- constexpr size_t N = 2048;
- constexpr size_t ALLOC_SIZE = 2049;
- alignas(FreeListHeap<>::BlockType) cpp::byte buf[N] = {cpp::byte(1)};
-
- FreeListHeap<> allocator(buf);
-
+TEST_FOR_EACH_ALLOCATOR(CallocTooLarge, 2048) {
+ size_t ALLOC_SIZE = N + 1;
EXPECT_EQ(allocator.calloc(1, ALLOC_SIZE), static_cast<void *>(nullptr));
}
diff --git a/libc/test/src/stdlib/freelist_malloc_test.cpp b/libc/test/src/stdlib/freelist_malloc_test.cpp
new file mode 100644
index 0000000000000..b2527c5b571b1
--- /dev/null
+++ b/libc/test/src/stdlib/freelist_malloc_test.cpp
@@ -0,0 +1,56 @@
+//===-- Unittests 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.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "src/stdlib/calloc.h"
+#include "src/stdlib/free.h"
+#include "src/stdlib/freelist_heap.h"
+#include "src/stdlib/malloc.h"
+#include "test/UnitTest/Test.h"
+
+using LIBC_NAMESPACE::freelist_heap;
+
+TEST(LlvmLibcFreeListMalloc, MallocStats) {
+ constexpr size_t kAllocSize = 256;
+ constexpr size_t kCallocNum = 4;
+ constexpr size_t kCallocSize = 64;
+
+ freelist_heap->reset_heap_stats(); // Do this because other tests might've
+ // called the same global allocator.
+
+ void *ptr1 = LIBC_NAMESPACE::malloc(kAllocSize);
+
+ const auto &freelist_heap_stats = freelist_heap->heap_stats();
+
+ ASSERT_NE(ptr1, static_cast<void *>(nullptr));
+ EXPECT_EQ(freelist_heap_stats.bytes_allocated, kAllocSize);
+ EXPECT_EQ(freelist_heap_stats.cumulative_allocated, kAllocSize);
+ EXPECT_EQ(freelist_heap_stats.cumulative_freed, size_t(0));
+
+ LIBC_NAMESPACE::free(ptr1);
+ EXPECT_EQ(freelist_heap_stats.bytes_allocated, size_t(0));
+ EXPECT_EQ(freelist_heap_stats.cumulative_allocated, kAllocSize);
+ EXPECT_EQ(freelist_heap_stats.cumulative_freed, kAllocSize);
+
+ void *ptr2 = LIBC_NAMESPACE::calloc(kCallocNum, kCallocSize);
+ ASSERT_NE(ptr2, static_cast<void *>(nullptr));
+ EXPECT_EQ(freelist_heap_stats.bytes_allocated, kCallocNum * kCallocSize);
+ EXPECT_EQ(freelist_heap_stats.cumulative_allocated,
+ kAllocSize + kCallocNum * kCallocSize);
+ EXPECT_EQ(freelist_heap_stats.cumulative_freed, kAllocSize);
+
+ for (size_t i = 0; i < kCallocNum * kCallocSize; ++i) {
+ EXPECT_EQ(reinterpret_cast<uint8_t *>(ptr2)[i], uint8_t(0));
+ }
+
+ LIBC_NAMESPACE::free(ptr2);
+ EXPECT_EQ(freelist_heap_stats.bytes_allocated, size_t(0));
+ EXPECT_EQ(freelist_heap_stats.cumulative_allocated,
+ kAllocSize + kCallocNum * kCallocSize);
+ EXPECT_EQ(freelist_heap_stats.cumulative_freed,
+ kAllocSize + kCallocNum * kCallocSize);
+}
More information about the libc-commits
mailing list