[libc-commits] [libc] [WIP][libc] Add freelist malloc (PR #94270)
via libc-commits
libc-commits at lists.llvm.org
Mon Jun 10 17:46:35 PDT 2024
https://github.com/PiJoules updated https://github.com/llvm/llvm-project/pull/94270
>From bb7d57a1d7eb302a0bf3eacf79611e1427989337 Mon Sep 17 00:00:00 2001
From: Leonard Chan <leonardchan at google.com>
Date: Fri, 31 May 2024 14:27:16 -0700
Subject: [PATCH] [WIP][libc] Add freelist malloc
---
libc/config/baremetal/riscv/entrypoints.txt | 1 +
libc/src/stdlib/CMakeLists.txt | 16 +-
libc/src/stdlib/freelist.h | 6 +-
libc/src/stdlib/freelist_heap.h | 207 +++++++++++++++
libc/src/stdlib/freelist_malloc.cpp | 42 +++
libc/test/src/CMakeLists.txt | 2 +-
libc/test/src/stdlib/CMakeLists.txt | 31 +--
libc/test/src/stdlib/freelist_heap_test.cpp | 241 ++++++++++++++++++
libc/test/src/stdlib/freelist_malloc_test.cpp | 56 ++++
libc/test/src/stdlib/malloc_test.cpp | 4 +
10 files changed, 586 insertions(+), 20 deletions(-)
create mode 100644 libc/src/stdlib/freelist_heap.h
create mode 100644 libc/src/stdlib/freelist_malloc.cpp
create mode 100644 libc/test/src/stdlib/freelist_heap_test.cpp
create mode 100644 libc/test/src/stdlib/freelist_malloc_test.cpp
diff --git a/libc/config/baremetal/riscv/entrypoints.txt b/libc/config/baremetal/riscv/entrypoints.txt
index b769b43f03a2c..363a762909c3a 100644
--- a/libc/config/baremetal/riscv/entrypoints.txt
+++ b/libc/config/baremetal/riscv/entrypoints.txt
@@ -170,6 +170,7 @@ set(TARGET_LIBC_ENTRYPOINTS
libc.src.stdlib.ldiv
libc.src.stdlib.llabs
libc.src.stdlib.lldiv
+ libc.src.stdlib.malloc
libc.src.stdlib.qsort
libc.src.stdlib.rand
libc.src.stdlib.srand
diff --git a/libc/src/stdlib/CMakeLists.txt b/libc/src/stdlib/CMakeLists.txt
index d4aa50a43d186..7a3a6b8a28e8e 100644
--- a/libc/src/stdlib/CMakeLists.txt
+++ b/libc/src/stdlib/CMakeLists.txt
@@ -402,8 +402,22 @@ else()
libc.src.__support.CPP.array
libc.src.__support.CPP.span
)
- add_entrypoint_external(
+ add_entrypoint_object(
malloc
+ SRCS
+ freelist_malloc.cpp
+ HDRS
+ malloc.h
+ DEPENDS
+ .block
+ .freelist
+ libc.src.__support.CPP.new
+ libc.src.__support.CPP.optional
+ libc.src.__support.CPP.span
+ libc.src.__support.CPP.type_traits
+ libc.src.__support.fixedvector
+ libc.src.string.memcpy
+ libc.src.string.memset
)
add_entrypoint_external(
free
diff --git a/libc/src/stdlib/freelist.h b/libc/src/stdlib/freelist.h
index 20b4977835bef..c01ed6eddb7d4 100644
--- a/libc/src/stdlib/freelist.h
+++ b/libc/src/stdlib/freelist.h
@@ -99,7 +99,7 @@ bool FreeList<NUM_BUCKETS>::add_chunk(span<cpp::byte> chunk) {
aliased.bytes = chunk.data();
- unsigned short chunk_ptr = find_chunk_ptr_for_size(chunk.size(), false);
+ size_t chunk_ptr = find_chunk_ptr_for_size(chunk.size(), false);
// Add it to the correct list.
aliased.node->size = chunk.size();
@@ -114,7 +114,7 @@ span<cpp::byte> FreeList<NUM_BUCKETS>::find_chunk(size_t size) const {
if (size == 0)
return span<cpp::byte>();
- unsigned short chunk_ptr = find_chunk_ptr_for_size(size, true);
+ 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
@@ -144,7 +144,7 @@ span<cpp::byte> FreeList<NUM_BUCKETS>::find_chunk(size_t size) const {
template <size_t NUM_BUCKETS>
bool FreeList<NUM_BUCKETS>::remove_chunk(span<cpp::byte> chunk) {
- unsigned short chunk_ptr = find_chunk_ptr_for_size(chunk.size(), true);
+ size_t chunk_ptr = find_chunk_ptr_for_size(chunk.size(), true);
// Walk that list, finding the chunk.
union {
diff --git a/libc/src/stdlib/freelist_heap.h b/libc/src/stdlib/freelist_heap.h
new file mode 100644
index 0000000000000..3d567c606a4e0
--- /dev/null
+++ b/libc/src/stdlib/freelist_heap.h
@@ -0,0 +1,207 @@
+//===-- Interface for freelist_heap ---------------------------------------===//
+//
+// 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_STDLIB_FREELIST_HEAP_H
+#define LLVM_LIBC_SRC_STDLIB_FREELIST_HEAP_H
+
+#include <stddef.h>
+
+#include "block.h"
+#include "freelist.h"
+#include "src/__support/CPP/optional.h"
+#include "src/__support/CPP/span.h"
+#include "src/string/memcpy.h"
+#include "src/string/memset.h"
+
+namespace LIBC_NAMESPACE {
+
+void MallocInit(uint8_t *heap_low_addr, uint8_t *heap_high_addr);
+
+using cpp::optional;
+using cpp::span;
+
+static constexpr cpp::array<size_t, 6> defaultBuckets{16, 32, 64,
+ 128, 256, 512};
+
+template <size_t kNumBuckets = defaultBuckets.size()> class FreeListHeap {
+public:
+ using BlockType = Block<>;
+
+ template <size_t> friend class FreeListHeapBuffer;
+
+ struct HeapStats {
+ size_t total_bytes;
+ size_t bytes_allocated;
+ size_t cumulative_allocated;
+ size_t cumulative_freed;
+ size_t total_allocate_calls;
+ size_t total_free_calls;
+ };
+ FreeListHeap(span<cpp::byte> region);
+
+ void *Allocate(size_t size);
+ void Free(void *ptr);
+ void *Realloc(void *ptr, size_t size);
+ void *Calloc(size_t num, size_t size);
+
+ void LogHeapStats();
+ const HeapStats &heap_stats() const { return heap_stats_; }
+
+private:
+ span<cpp::byte> BlockToSpan(BlockType *block) {
+ return span<cpp::byte>(block->usable_space(), block->inner_size());
+ }
+
+ void InvalidFreeCrash() { __builtin_trap(); }
+
+ span<cpp::byte> region_;
+ FreeList<kNumBuckets> freelist_;
+ HeapStats heap_stats_;
+};
+
+template <size_t kNumBuckets>
+FreeListHeap<kNumBuckets>::FreeListHeap(span<cpp::byte> region)
+ : freelist_(defaultBuckets), heap_stats_() {
+ auto result = BlockType::init(region);
+ BlockType *block = *result;
+
+ freelist_.add_chunk(BlockToSpan(block));
+
+ region_ = region;
+ heap_stats_.total_bytes = region.size();
+}
+
+template <size_t kNumBuckets>
+void *FreeListHeap<kNumBuckets>::Allocate(size_t size) {
+ // Find a chunk in the freelist. Split it if needed, then return
+
+ auto chunk = freelist_.find_chunk(size);
+
+ if (chunk.data() == nullptr) {
+ return nullptr;
+ }
+ freelist_.remove_chunk(chunk);
+
+ BlockType *chunk_block = BlockType::from_usable_space(chunk.data());
+
+ // Split that chunk. If there's a leftover chunk, add it to the freelist
+ optional<BlockType *> result = BlockType::split(chunk_block, size);
+ if (result) {
+ freelist_.add_chunk(BlockToSpan(*result));
+ }
+
+ chunk_block->mark_used();
+
+ heap_stats_.bytes_allocated += size;
+ heap_stats_.cumulative_allocated += size;
+ heap_stats_.total_allocate_calls += 1;
+
+ return chunk_block->usable_space();
+}
+
+template <size_t kNumBuckets> void FreeListHeap<kNumBuckets>::Free(void *ptr) {
+ cpp::byte *bytes = static_cast<cpp::byte *>(ptr);
+
+ if (bytes < region_.data() || bytes >= region_.data() + region_.size()) {
+ InvalidFreeCrash();
+ return;
+ }
+
+ BlockType *chunk_block = BlockType::from_usable_space(bytes);
+
+ size_t size_freed = chunk_block->inner_size();
+ // Ensure that the block is in-use
+ if (!chunk_block->used()) {
+ InvalidFreeCrash();
+ return;
+ }
+ chunk_block->mark_free();
+ // Can we combine with the left or right blocks?
+ BlockType *prev = chunk_block->prev();
+ BlockType *next = nullptr;
+
+ if (!chunk_block->last()) {
+ next = chunk_block->next();
+ }
+
+ if (prev != nullptr && !prev->used()) {
+ // Remove from freelist and merge
+ freelist_.remove_chunk(BlockToSpan(prev));
+ chunk_block = chunk_block->prev();
+ BlockType::merge_next(chunk_block);
+ }
+
+ if (next != nullptr && !next->used()) {
+ freelist_.remove_chunk(BlockToSpan(next));
+ BlockType::merge_next(chunk_block);
+ }
+ // Add back to the freelist
+ freelist_.add_chunk(BlockToSpan(chunk_block));
+
+ heap_stats_.bytes_allocated -= size_freed;
+ heap_stats_.cumulative_freed += size_freed;
+ heap_stats_.total_free_calls += 1;
+}
+
+// Follows constract of the C standard realloc() function
+// If ptr is free'd, will return nullptr.
+template <size_t kNumBuckets>
+void *FreeListHeap<kNumBuckets>::Realloc(void *ptr, size_t size) {
+ if (size == 0) {
+ Free(ptr);
+ return nullptr;
+ }
+
+ // If the pointer is nullptr, allocate a new memory.
+ if (ptr == nullptr) {
+ return Allocate(size);
+ }
+
+ cpp::byte *bytes = static_cast<cpp::byte *>(ptr);
+
+ if (bytes < region_.data() || bytes >= region_.data() + region_.size()) {
+ return nullptr;
+ }
+
+ BlockType *chunk_block = BlockType::from_usable_space(bytes);
+ if (!chunk_block->used()) {
+ return nullptr;
+ }
+ size_t old_size = chunk_block->inner_size();
+
+ // Do nothing and return ptr if the required memory size is smaller than
+ // the current size.
+ if (old_size >= size) {
+ return ptr;
+ }
+
+ void *new_ptr = Allocate(size);
+ // Don't invalidate ptr if Allocate(size) fails to initilize the memory.
+ if (new_ptr == nullptr) {
+ return nullptr;
+ }
+ memcpy(new_ptr, ptr, old_size);
+
+ Free(ptr);
+ return new_ptr;
+}
+
+template <size_t kNumBuckets>
+void *FreeListHeap<kNumBuckets>::Calloc(size_t num, size_t size) {
+ void *ptr = Allocate(num * size);
+ if (ptr != nullptr) {
+ memset(ptr, 0, num * 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..ee0c9aae771eb
--- /dev/null
+++ b/libc/src/stdlib/freelist_malloc.cpp
@@ -0,0 +1,42 @@
+//===-- 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/__support/CPP/new.h"
+#include "src/__support/CPP/span.h"
+#include "src/__support/CPP/type_traits.h"
+#include "src/string/memcpy.h"
+#include "src/string/memset.h"
+
+#include <stddef.h>
+
+namespace LIBC_NAMESPACE {
+
+namespace {
+cpp::aligned_storage_t<sizeof(FreeListHeap<>), alignof(FreeListHeap<>)> buf;
+} // namespace
+
+FreeListHeap<> *freelist_heap;
+
+// Define the global heap variables.
+void MallocInit(uint8_t *heap_low_addr, uint8_t *heap_high_addr) {
+ cpp::span<LIBC_NAMESPACE::cpp::byte> allocator_freelist_raw_heap =
+ cpp::span<cpp::byte>(reinterpret_cast<cpp::byte *>(heap_low_addr),
+ heap_high_addr - heap_low_addr);
+ freelist_heap = new (&buf) FreeListHeap<>(allocator_freelist_raw_heap);
+}
+
+void *malloc(size_t size) { return freelist_heap->Allocate(size); }
+
+void free(void *ptr) { freelist_heap->Free(ptr); }
+
+void *calloc(size_t num, size_t size) {
+ return freelist_heap->Calloc(num, size);
+}
+
+} // namespace LIBC_NAMESPACE
diff --git a/libc/test/src/CMakeLists.txt b/libc/test/src/CMakeLists.txt
index a5e7a2a4dee72..935feb59ecdf6 100644
--- a/libc/test/src/CMakeLists.txt
+++ b/libc/test/src/CMakeLists.txt
@@ -61,7 +61,7 @@ add_subdirectory(inttypes)
if(${LIBC_TARGET_OS} STREQUAL "linux")
add_subdirectory(fcntl)
add_subdirectory(sched)
- add_subdirectory(sys)
+ #add_subdirectory(sys)
add_subdirectory(termios)
add_subdirectory(unistd)
endif()
diff --git a/libc/test/src/stdlib/CMakeLists.txt b/libc/test/src/stdlib/CMakeLists.txt
index d3954f077a219..572f46f595761 100644
--- a/libc/test/src/stdlib/CMakeLists.txt
+++ b/libc/test/src/stdlib/CMakeLists.txt
@@ -424,19 +424,20 @@ if(LLVM_LIBC_FULL_BUILD)
libc.src.stdlib.quick_exit
)
- # Only the GPU has an in-tree 'malloc' implementation.
- if(LIBC_TARGET_OS_IS_GPU)
- add_libc_test(
- malloc_test
- HERMETIC_TEST_ONLY
- SUITE
- libc-stdlib-tests
- SRCS
- malloc_test.cpp
- DEPENDS
- libc.include.stdlib
- libc.src.stdlib.malloc
- libc.src.stdlib.free
- )
- endif()
+ add_libc_test(
+ malloc_test
+ SUITE
+ libc-stdlib-tests
+ SRCS
+ block_test.cpp
+ malloc_test.cpp
+ freelist_malloc_test.cpp
+ freelist_heap_test.cpp
+ freelist_test.cpp
+ DEPENDS
+ libc.include.stdlib
+ libc.src.string.memcmp
+ libc.src.stdlib.malloc
+ libc.src.stdlib.free
+ )
endif()
diff --git a/libc/test/src/stdlib/freelist_heap_test.cpp b/libc/test/src/stdlib/freelist_heap_test.cpp
new file mode 100644
index 0000000000000..73419d25c6e37
--- /dev/null
+++ b/libc/test/src/stdlib/freelist_heap_test.cpp
@@ -0,0 +1,241 @@
+//===-- Unittests for freelist_heap ---------------------------------------===//
+//
+// 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/__support/CPP/span.h"
+#include "src/stdlib/freelist_heap.h"
+#include "src/string/memcmp.h"
+#include "src/string/memcpy.h"
+#include "test/UnitTest/Test.h"
+
+namespace LIBC_NAMESPACE {
+
+TEST(LlvmLibcFreeListHeap, CanAllocate) {
+ constexpr size_t N = 2048;
+ constexpr size_t kAllocSize = 512;
+ alignas(FreeListHeap<>::BlockType) cpp::byte buf[N] = {cpp::byte(0)};
+
+ FreeListHeap<> allocator(buf);
+
+ void *ptr = allocator.Allocate(kAllocSize);
+
+ 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));
+}
+
+TEST(LlvmLibcFreeListHeap, AllocationsDontOverlap) {
+ constexpr size_t N = 2048;
+ constexpr size_t kAllocSize = 512;
+ alignas(FreeListHeap<>::BlockType) cpp::byte buf[N] = {cpp::byte(0)};
+
+ FreeListHeap<> allocator(buf);
+
+ void *ptr1 = allocator.Allocate(kAllocSize);
+ void *ptr2 = allocator.Allocate(kAllocSize);
+
+ ASSERT_NE(ptr1, static_cast<void *>(nullptr));
+ ASSERT_NE(ptr2, static_cast<void *>(nullptr));
+
+ uintptr_t ptr1_start = reinterpret_cast<uintptr_t>(ptr1);
+ uintptr_t ptr1_end = ptr1_start + kAllocSize;
+ uintptr_t ptr2_start = reinterpret_cast<uintptr_t>(ptr2);
+
+ EXPECT_GT(ptr2_start, ptr1_end);
+}
+
+TEST(LlvmLibcFreeListHeap, CanFreeAndRealloc) {
+ // 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 kAllocSize = 512;
+ alignas(FreeListHeap<>::BlockType) cpp::byte buf[N] = {cpp::byte(0)};
+
+ FreeListHeap<> allocator(buf);
+
+ void *ptr1 = allocator.Allocate(kAllocSize);
+ allocator.Free(ptr1);
+ void *ptr2 = allocator.Allocate(kAllocSize);
+
+ 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);
+
+ EXPECT_EQ(allocator.Allocate(N), static_cast<void *>(nullptr));
+}
+
+TEST(LlvmLibcFreeListHeap, ReturnsNullWhenFull) {
+ constexpr size_t N = 2048;
+ alignas(FreeListHeap<>::BlockType) cpp::byte buf[N] = {cpp::byte(0)};
+
+ FreeListHeap<> allocator(buf);
+
+ EXPECT_NE(allocator.Allocate(N - FreeListHeap<>::BlockType::BLOCK_OVERHEAD),
+ static_cast<void *>(nullptr));
+ 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);
+
+ void *ptr1 = allocator.Allocate(1);
+
+ // Should be aligned to native pointer alignment
+ uintptr_t ptr1_start = reinterpret_cast<uintptr_t>(ptr1);
+ size_t alignment = alignof(void *);
+
+ EXPECT_EQ(ptr1_start % alignment, static_cast<size_t>(0));
+
+ void *ptr2 = allocator.Allocate(1);
+ uintptr_t ptr2_start = reinterpret_cast<uintptr_t>(ptr2);
+
+ EXPECT_EQ(ptr2_start % alignment, static_cast<size_t>(0));
+}
+
+TEST(LlvmLibcFreeListHeap, CanRealloc) {
+ constexpr size_t N = 2048;
+ constexpr size_t kAllocSize = 512;
+ constexpr size_t kNewAllocSize = 768;
+ alignas(FreeListHeap<>::BlockType) cpp::byte buf[N] = {cpp::byte(1)};
+
+ FreeListHeap<> allocator(buf);
+
+ void *ptr1 = allocator.Allocate(kAllocSize);
+ void *ptr2 = allocator.Realloc(ptr1, kNewAllocSize);
+
+ ASSERT_NE(ptr1, static_cast<void *>(nullptr));
+ ASSERT_NE(ptr2, static_cast<void *>(nullptr));
+}
+
+TEST(LlvmLibcFreeListHeap, ReallocHasSameContent) {
+ constexpr size_t N = 2048;
+ constexpr size_t kAllocSize = 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[kAllocSize];
+ // Data inside the reallocated block.
+ cpp::byte data2[kAllocSize];
+
+ FreeListHeap<> allocator(buf);
+
+ int *ptr1 = reinterpret_cast<int *>(allocator.Allocate(kAllocSize));
+ *ptr1 = 42;
+ memcpy(data1, ptr1, kAllocSize);
+ int *ptr2 = reinterpret_cast<int *>(allocator.Realloc(ptr1, kNewAllocSize));
+ memcpy(data2, ptr2, kAllocSize);
+
+ ASSERT_NE(ptr1, static_cast<int *>(nullptr));
+ ASSERT_NE(ptr2, static_cast<int *>(nullptr));
+ // Verify that data inside the allocated and reallocated chunks are the same.
+ EXPECT_EQ(LIBC_NAMESPACE::memcmp(data1, data2, kAllocSize), 0);
+}
+
+TEST(LlvmLibcFreeListHeap, ReturnsNullReallocFreedPointer) {
+ constexpr size_t N = 2048;
+ constexpr size_t kAllocSize = 512;
+ constexpr size_t kNewAllocSize = 256;
+ alignas(FreeListHeap<>::BlockType) cpp::byte buf[N] = {cpp::byte(0)};
+
+ FreeListHeap<> allocator(buf);
+
+ void *ptr1 = allocator.Allocate(kAllocSize);
+ allocator.Free(ptr1);
+ void *ptr2 = allocator.Realloc(ptr1, kNewAllocSize);
+
+ EXPECT_EQ(static_cast<void *>(nullptr), ptr2);
+}
+
+TEST(LlvmLibcFreeListHeap, ReallocSmallerSize) {
+ constexpr size_t N = 2048;
+ constexpr size_t kAllocSize = 512;
+ constexpr size_t kNewAllocSize = 256;
+ alignas(FreeListHeap<>::BlockType) cpp::byte buf[N] = {cpp::byte(0)};
+
+ FreeListHeap<> allocator(buf);
+
+ void *ptr1 = allocator.Allocate(kAllocSize);
+ void *ptr2 = allocator.Realloc(ptr1, kNewAllocSize);
+
+ // For smaller sizes, Realloc will not shrink the block.
+ EXPECT_EQ(ptr1, ptr2);
+}
+
+TEST(LlvmLibcFreeListHeap, ReallocTooLarge) {
+ constexpr size_t N = 2048;
+ constexpr size_t kAllocSize = 512;
+ constexpr size_t kNewAllocSize = 4096;
+ alignas(FreeListHeap<>::BlockType) cpp::byte buf[N] = {cpp::byte(0)};
+
+ FreeListHeap<> allocator(buf);
+
+ void *ptr1 = allocator.Allocate(kAllocSize);
+ void *ptr2 = allocator.Realloc(ptr1, kNewAllocSize);
+
+ // Realloc() will not invalidate the original pointer if Reallc() fails
+ EXPECT_NE(static_cast<void *>(nullptr), ptr1);
+ EXPECT_EQ(static_cast<void *>(nullptr), ptr2);
+}
+
+TEST(LlvmLibcFreeListHeap, CanCalloc) {
+ constexpr size_t N = 2048;
+ constexpr size_t kAllocSize = 128;
+ constexpr size_t kNum = 4;
+ constexpr int size = kNum * kAllocSize;
+ alignas(FreeListHeap<>::BlockType) cpp::byte buf[N] = {cpp::byte(1)};
+ constexpr cpp::byte zero{0};
+
+ FreeListHeap<> allocator(buf);
+
+ cpp::byte *ptr1 =
+ reinterpret_cast<cpp::byte *>(allocator.Calloc(kNum, kAllocSize));
+
+ // Calloc'd content is zero.
+ for (int i = 0; i < size; i++) {
+ EXPECT_EQ(ptr1[i], zero);
+ }
+}
+
+TEST(LlvmLibcFreeListHeap, CanCallocWeirdSize) {
+ constexpr size_t N = 2048;
+ constexpr size_t kAllocSize = 143;
+ constexpr size_t kNum = 3;
+ constexpr int size = kNum * kAllocSize;
+ alignas(FreeListHeap<>::BlockType) cpp::byte buf[N] = {cpp::byte(132)};
+ constexpr cpp::byte zero{0};
+
+ FreeListHeap<> allocator(buf);
+
+ cpp::byte *ptr1 =
+ reinterpret_cast<cpp::byte *>(allocator.Calloc(kNum, kAllocSize));
+
+ // Calloc'd content is zero.
+ for (int i = 0; i < size; i++) {
+ EXPECT_EQ(ptr1[i], zero);
+ }
+}
+
+TEST(LlvmLibcFreeListHeap, CallocTooLarge) {
+ constexpr size_t N = 2048;
+ constexpr size_t kAllocSize = 2049;
+ alignas(FreeListHeap<>::BlockType) cpp::byte buf[N] = {cpp::byte(1)};
+
+ FreeListHeap<> allocator(buf);
+
+ EXPECT_EQ(allocator.Calloc(1, kAllocSize), static_cast<void *>(nullptr));
+}
+
+} // namespace LIBC_NAMESPACE
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..4844ed0127a32
--- /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, ReplacingMalloc) {
+ constexpr size_t kAllocSize = 256;
+ constexpr size_t kCallocNum = 4;
+ constexpr size_t kCallocSize = 64;
+
+ uint8_t kBuff[4096];
+ LIBC_NAMESPACE::MallocInit(kBuff, kBuff + sizeof(kBuff));
+
+ 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);
+}
diff --git a/libc/test/src/stdlib/malloc_test.cpp b/libc/test/src/stdlib/malloc_test.cpp
index d9023cf56d9fe..6fb07bec9f336 100644
--- a/libc/test/src/stdlib/malloc_test.cpp
+++ b/libc/test/src/stdlib/malloc_test.cpp
@@ -7,10 +7,14 @@
//===----------------------------------------------------------------------===//
#include "src/stdlib/free.h"
+#include "src/stdlib/freelist_heap.h"
#include "src/stdlib/malloc.h"
#include "test/UnitTest/Test.h"
TEST(LlvmLibcMallocTest, Allocate) {
+ uint8_t kBuff[1024];
+ LIBC_NAMESPACE::MallocInit(kBuff, kBuff + sizeof(kBuff));
+
int *ptr = reinterpret_cast<int *>(LIBC_NAMESPACE::malloc(sizeof(int)));
EXPECT_NE(reinterpret_cast<void *>(ptr), static_cast<void *>(nullptr));
*ptr = 1;
More information about the libc-commits
mailing list