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 10, 2024
1 parent 85c78d4 commit 2779336
Show file tree
Hide file tree
Showing 13 changed files with 955 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
15 changes: 14 additions & 1 deletion libc/src/stdlib/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -392,8 +392,21 @@ else()
libc.src.__support.CPP.span
libc.src.__support.CPP.type_traits
)
add_entrypoint_external(
add_entrypoint_object(
malloc
SRCS
freelist_malloc.cpp
HDRS
malloc.h
DEPENDS
.block
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
4 changes: 2 additions & 2 deletions libc/src/stdlib/block.h
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,8 @@ LIBC_INLINE constexpr size_t align_down(size_t value, size_t alignment) {
}

/// Returns the value rounded down to the nearest multiple of alignment.
LIBC_INLINE template <typename T>
constexpr T *align_down(T *value, size_t alignment) {
template <typename T>
LIBC_INLINE constexpr T *align_down(T *value, size_t alignment) {
return reinterpret_cast<T *>(
align_down(reinterpret_cast<size_t>(value), alignment));
}
Expand Down
2 changes: 1 addition & 1 deletion libc/src/stdlib/free.h
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,4 @@ void free(void *ptr);

} // namespace LIBC_NAMESPACE

#endif // LLVM_LIBC_SRC_STDLIB_LDIV_H
#endif // LLVM_LIBC_SRC_STDLIB_FREE_H
203 changes: 203 additions & 0 deletions libc/src/stdlib/freelist.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,203 @@
//===-- 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.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//

#ifndef LLVM_LIBC_SRC_STDLIB_FREELIST_H
#define LLVM_LIBC_SRC_STDLIB_FREELIST_H

#include "src/__support/CPP/array.h"
#include "src/__support/CPP/cstddef.h"
#include "src/__support/CPP/span.h"
#include "src/__support/fixedvector.h"

namespace LIBC_NAMESPACE {

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:
///
/// @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 kNumBuckets = 6> class FreeList {
public:
// 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 AddChunk(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> FindChunk(size_t size) const;

/// Removes a chunk from this freelist.
bool RemoveChunk(cpp::span<cpp::byte> chunk);

private:
// For a given size, find which index into chunks_ the node should be written
// to.
unsigned short FindChunkPtrForSize(size_t size, bool non_null) const;

struct FreeListNode {
FreeListNode *next;
size_t size;
};

public:
explicit FreeList(cpp::array<size_t, kNumBuckets> sizes)
: chunks_(kNumBuckets + 1, 0), sizes_(sizes.begin(), sizes.end()) {}

FixedVector<FreeList::FreeListNode *, kNumBuckets + 1> chunks_;
FixedVector<size_t, kNumBuckets> sizes_;
};

template <size_t kNumBuckets>
bool FreeList<kNumBuckets>::AddChunk(span<cpp::byte> chunk) {
// Check that the size is enough to actually store what we need
if (chunk.size() < sizeof(FreeListNode)) {
return false;
}

union {
FreeListNode *node;
cpp::byte *bytes;
} aliased;

aliased.bytes = chunk.data();

unsigned short chunk_ptr = FindChunkPtrForSize(chunk.size(), false);

// Add it to the correct list.
aliased.node->size = chunk.size();
aliased.node->next = chunks_[chunk_ptr];
chunks_[chunk_ptr] = aliased.node;

return true;
}

template <size_t kNumBuckets>
span<cpp::byte> FreeList<kNumBuckets>::FindChunk(size_t size) const {
if (size == 0) {
return span<cpp::byte>();
}

unsigned short chunk_ptr = FindChunkPtrForSize(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>();
}

// Now iterate up the buckets, walking each list to find a good candidate
for (size_t i = chunk_ptr; i < chunks_.size(); i++) {
union {
FreeListNode *node;
cpp::byte *data;
} aliased;
aliased.node = chunks_[static_cast<unsigned short>(i)];

while (aliased.node != nullptr) {
if (aliased.node->size >= size) {
return span<cpp::byte>(aliased.data, aliased.node->size);
}

aliased.node = aliased.node->next;
}
}

// If we get here, we've checked every block in every bucket. There's
// nothing that can support this allocation.
return span<cpp::byte>();
}

template <size_t kNumBuckets>
bool FreeList<kNumBuckets>::RemoveChunk(span<cpp::byte> chunk) {
unsigned short chunk_ptr = FindChunkPtrForSize(chunk.size(), true);

// Walk that list, finding the chunk.
union {
FreeListNode *node;
cpp::byte *data;
} aliased, aliased_next;

// Check head first.
if (chunks_[chunk_ptr] == nullptr) {
return false;
}

aliased.node = chunks_[chunk_ptr];
if (aliased.data == chunk.data()) {
chunks_[chunk_ptr] = aliased.node->next;
return true;
}

// No? Walk the nodes.
aliased.node = chunks_[chunk_ptr];

while (aliased.node->next != nullptr) {
aliased_next.node = aliased.node->next;
if (aliased_next.data == chunk.data()) {
// Found it, remove this node out of the chain
aliased.node->next = aliased_next.node->next;
return true;
}

aliased.node = aliased.node->next;
}

return false;
}

template <size_t kNumBuckets>
unsigned short FreeList<kNumBuckets>::FindChunkPtrForSize(size_t size,
bool non_null) const {
unsigned short 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

#endif // LLVM_LIBC_SRC_STDLIB_FREELIST_H
Loading

0 comments on commit 2779336

Please sign in to comment.