Skip to content

Commit

Permalink
[WIP][libc] Add freelist malloc
Browse files Browse the repository at this point in the history
  • Loading branch information
PiJoules committed Jun 11, 2024
1 parent 1737814 commit bb7d57a
Show file tree
Hide file tree
Showing 10 changed files with 586 additions and 20 deletions.
1 change: 1 addition & 0 deletions libc/config/baremetal/riscv/entrypoints.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
16 changes: 15 additions & 1 deletion libc/src/stdlib/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
6 changes: 3 additions & 3 deletions libc/src/stdlib/freelist.h
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand All @@ -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
Expand Down Expand Up @@ -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 {
Expand Down
207 changes: 207 additions & 0 deletions libc/src/stdlib/freelist_heap.h
Original file line number Diff line number Diff line change
@@ -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
42 changes: 42 additions & 0 deletions libc/src/stdlib/freelist_malloc.cpp
Original file line number Diff line number Diff line change
@@ -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
2 changes: 1 addition & 1 deletion libc/test/src/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
31 changes: 16 additions & 15 deletions libc/test/src/stdlib/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Loading

0 comments on commit bb7d57a

Please sign in to comment.