Skip to content

Commit 843ea00

Browse files
committed
[WIP][libc] Add freelist malloc
1 parent cd94fa7 commit 843ea00

File tree

11 files changed

+290
-155
lines changed

11 files changed

+290
-155
lines changed

libc/config/baremetal/riscv/entrypoints.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,7 @@ set(TARGET_LIBC_ENTRYPOINTS
170170
libc.src.stdlib.ldiv
171171
libc.src.stdlib.llabs
172172
libc.src.stdlib.lldiv
173+
libc.src.stdlib.malloc
173174
libc.src.stdlib.qsort
174175
libc.src.stdlib.rand
175176
libc.src.stdlib.srand

libc/src/__support/threads/thread.cpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,8 @@ class ThreadAtExitCallbackMgr {
115115
public:
116116
constexpr ThreadAtExitCallbackMgr()
117117
: mtx(/*timed=*/false, /*recursive=*/false, /*robust=*/false,
118-
/*pshared=*/false) {}
118+
/*pshared=*/false),
119+
callback_list() {}
119120

120121
int add_callback(AtExitCallback *callback, void *obj) {
121122
cpp::lock_guard lock(mtx);

libc/src/stdlib/CMakeLists.txt

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -417,8 +417,14 @@ else()
417417
libc.src.string.memory_utils.inline_memcpy
418418
libc.src.string.memory_utils.inline_memset
419419
)
420-
add_entrypoint_external(
420+
add_entrypoint_object(
421421
malloc
422+
SRCS
423+
freelist_malloc.cpp
424+
HDRS
425+
malloc.h
426+
DEPENDS
427+
.freelist_heap
422428
)
423429
add_entrypoint_external(
424430
free

libc/src/stdlib/block.h

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -245,7 +245,7 @@ class Block {
245245
void mark_free() { info_.used = 0; }
246246

247247
/// Marks this block as the last one in the chain.
248-
void mark_last() { info_.last = 1; }
248+
constexpr void mark_last() { info_.last = 1; }
249249

250250
/// Clears the last bit from this block.
251251
void clear_last() { info_.last = 1; }
@@ -259,15 +259,15 @@ class Block {
259259
return check_status() == internal::BlockStatus::VALID;
260260
}
261261

262+
constexpr Block(size_t prev_outer_size, size_t outer_size);
263+
262264
private:
263265
/// Consumes the block and returns as a span of bytes.
264266
static ByteSpan as_bytes(Block *&&block);
265267

266268
/// Consumes the span of bytes and uses it to construct and return a block.
267269
static Block *as_block(size_t prev_outer_size, ByteSpan bytes);
268270

269-
Block(size_t prev_outer_size, size_t outer_size);
270-
271271
/// Returns a `BlockStatus` that is either VALID or indicates the reason why
272272
/// the block is invalid.
273273
///
@@ -442,7 +442,9 @@ Block<OffsetType, kAlign> *Block<OffsetType, kAlign>::prev() const {
442442
// Private template method implementations.
443443

444444
template <typename OffsetType, size_t kAlign>
445-
Block<OffsetType, kAlign>::Block(size_t prev_outer_size, size_t outer_size) {
445+
constexpr Block<OffsetType, kAlign>::Block(size_t prev_outer_size,
446+
size_t outer_size)
447+
: info_{} {
446448
prev_ = prev_outer_size / ALIGNMENT;
447449
next_ = outer_size / ALIGNMENT;
448450
info_.used = 0;

libc/src/stdlib/freelist.h

Lines changed: 26 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -68,43 +68,53 @@ template <size_t NUM_BUCKETS = 6> class FreeList {
6868
/// Removes a chunk from this freelist.
6969
bool remove_chunk(cpp::span<cpp::byte> chunk);
7070

71-
private:
72-
// For a given size, find which index into chunks_ the node should be written
73-
// to.
74-
size_t find_chunk_ptr_for_size(size_t size, bool non_null) const;
71+
/// For a given size, find which index into chunks_ the node should be written
72+
/// to.
73+
constexpr size_t find_chunk_ptr_for_size(size_t size, bool non_null) const;
7574

7675
struct FreeListNode {
7776
FreeListNode *next;
7877
size_t size;
7978
};
8079

81-
public:
82-
explicit FreeList(cpp::array<size_t, NUM_BUCKETS> sizes)
80+
constexpr void set_freelist_node(FreeListNode *node,
81+
cpp::span<cpp::byte> chunk);
82+
83+
constexpr explicit FreeList(cpp::array<size_t, NUM_BUCKETS> sizes)
8384
: chunks_(NUM_BUCKETS + 1, 0), sizes_(sizes.begin(), sizes.end()) {}
8485

86+
private:
8587
FixedVector<FreeList::FreeListNode *, NUM_BUCKETS + 1> chunks_;
8688
FixedVector<size_t, NUM_BUCKETS> sizes_;
8789
};
8890

91+
template <size_t NUM_BUCKETS>
92+
constexpr void FreeList<NUM_BUCKETS>::set_freelist_node(FreeListNode *node,
93+
span<cpp::byte> chunk) {
94+
// Add it to the correct list.
95+
size_t chunk_ptr = find_chunk_ptr_for_size(chunk.size(), false);
96+
node->size = chunk.size();
97+
node->next = chunks_[chunk_ptr];
98+
chunks_[chunk_ptr] = node;
99+
}
100+
89101
template <size_t NUM_BUCKETS>
90102
bool FreeList<NUM_BUCKETS>::add_chunk(span<cpp::byte> chunk) {
91103
// Check that the size is enough to actually store what we need
92104
if (chunk.size() < sizeof(FreeListNode))
93105
return false;
94106

107+
// FIXME: This is UB since type punning is not allowed in C++. THe idea here
108+
// is that we write the FreeListNode `size` and `next` onto the start of the
109+
// buffer. Unless the original underlying bytes were a `FreeListNode`, the
110+
// only safe way to do this is with `memcpy`.
95111
union {
96112
FreeListNode *node;
97113
cpp::byte *bytes;
98114
} aliased;
99115

100116
aliased.bytes = chunk.data();
101-
102-
size_t chunk_ptr = find_chunk_ptr_for_size(chunk.size(), false);
103-
104-
// Add it to the correct list.
105-
aliased.node->size = chunk.size();
106-
aliased.node->next = chunks_[chunk_ptr];
107-
chunks_[chunk_ptr] = aliased.node;
117+
set_freelist_node(aliased.node, chunk);
108118

109119
return true;
110120
}
@@ -180,8 +190,9 @@ bool FreeList<NUM_BUCKETS>::remove_chunk(span<cpp::byte> chunk) {
180190
}
181191

182192
template <size_t NUM_BUCKETS>
183-
size_t FreeList<NUM_BUCKETS>::find_chunk_ptr_for_size(size_t size,
184-
bool non_null) const {
193+
constexpr size_t
194+
FreeList<NUM_BUCKETS>::find_chunk_ptr_for_size(size_t size,
195+
bool non_null) const {
185196
size_t chunk_ptr = 0;
186197
for (chunk_ptr = 0u; chunk_ptr < sizes_.size(); chunk_ptr++) {
187198
if (sizes_[chunk_ptr] >= size &&

libc/src/stdlib/freelist_heap.h

Lines changed: 55 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ static constexpr cpp::array<size_t, 6> DEFAULT_BUCKETS{16, 32, 64,
3030
template <size_t NUM_BUCKETS = DEFAULT_BUCKETS.size()> class FreeListHeap {
3131
public:
3232
using BlockType = Block<>;
33+
using FreeListType = FreeList<NUM_BUCKETS>;
3334

3435
struct HeapStats {
3536
size_t total_bytes;
@@ -39,35 +40,73 @@ template <size_t NUM_BUCKETS = DEFAULT_BUCKETS.size()> class FreeListHeap {
3940
size_t total_allocate_calls;
4041
size_t total_free_calls;
4142
};
42-
FreeListHeap(span<cpp::byte> region);
43+
44+
FreeListHeap(span<cpp::byte> region)
45+
: FreeListHeap(&*region.begin(), &*region.end(), region.size()) {
46+
auto result = BlockType::init(region);
47+
BlockType *block = *result;
48+
freelist_.add_chunk(block_to_span(block));
49+
}
50+
51+
constexpr FreeListHeap(void *start, cpp::byte *end, size_t total_bytes)
52+
: block_region_start_(start), block_region_end_(end),
53+
freelist_(DEFAULT_BUCKETS), heap_stats_{} {
54+
heap_stats_.total_bytes = total_bytes;
55+
}
4356

4457
void *allocate(size_t size);
4558
void free(void *ptr);
4659
void *realloc(void *ptr, size_t size);
4760
void *calloc(size_t num, size_t size);
4861

4962
const HeapStats &heap_stats() const { return heap_stats_; }
63+
void reset_heap_stats() { heap_stats_ = {}; }
64+
65+
void *region_start() const { return block_region_start_; }
66+
size_t region_size() const {
67+
return reinterpret_cast<uintptr_t>(block_region_end_) -
68+
reinterpret_cast<uintptr_t>(block_region_start_);
69+
}
70+
71+
protected:
72+
constexpr void set_freelist_node(typename FreeListType::FreeListNode *node,
73+
cpp::span<cpp::byte> chunk) {
74+
freelist_.set_freelist_node(node, chunk);
75+
}
5076

5177
private:
5278
span<cpp::byte> block_to_span(BlockType *block) {
5379
return span<cpp::byte>(block->usable_space(), block->inner_size());
5480
}
5581

56-
span<cpp::byte> region_;
57-
FreeList<NUM_BUCKETS> freelist_;
82+
bool is_valid_ptr(void *ptr) {
83+
return ptr >= block_region_start_ && ptr < block_region_end_;
84+
}
85+
86+
void *block_region_start_;
87+
void *block_region_end_;
88+
FreeListType freelist_;
5889
HeapStats heap_stats_;
5990
};
6091

61-
template <size_t NUM_BUCKETS>
62-
FreeListHeap<NUM_BUCKETS>::FreeListHeap(span<cpp::byte> region)
63-
: region_(region), freelist_(DEFAULT_BUCKETS), heap_stats_() {
64-
auto result = BlockType::init(region);
65-
BlockType *block = *result;
92+
template <size_t BUFF_SIZE, size_t NUM_BUCKETS = DEFAULT_BUCKETS.size()>
93+
struct FreeListHeapBuffer : public FreeListHeap<NUM_BUCKETS> {
94+
using parent = FreeListHeap<NUM_BUCKETS>;
95+
using FreeListNode = typename parent::FreeListType::FreeListNode;
6696

67-
freelist_.add_chunk(block_to_span(block));
97+
constexpr FreeListHeapBuffer()
98+
: FreeListHeap<NUM_BUCKETS>(&block, buffer + sizeof(buffer), BUFF_SIZE),
99+
block(0, BUFF_SIZE), node{}, buffer{} {
100+
block.mark_last();
68101

69-
heap_stats_.total_bytes = region.size();
70-
}
102+
cpp::span<cpp::byte> chunk(buffer, sizeof(buffer));
103+
parent::set_freelist_node(&node, chunk);
104+
}
105+
106+
typename parent::BlockType block;
107+
FreeListNode node;
108+
cpp::byte buffer[BUFF_SIZE - sizeof(block) - sizeof(node)];
109+
};
71110

72111
template <size_t NUM_BUCKETS>
73112
void *FreeListHeap<NUM_BUCKETS>::allocate(size_t size) {
@@ -97,7 +136,7 @@ void *FreeListHeap<NUM_BUCKETS>::allocate(size_t size) {
97136
template <size_t NUM_BUCKETS> void FreeListHeap<NUM_BUCKETS>::free(void *ptr) {
98137
cpp::byte *bytes = static_cast<cpp::byte *>(ptr);
99138

100-
LIBC_ASSERT(bytes >= region_.data() && bytes < region_.data() + region_.size() && "Invalid pointer");
139+
LIBC_ASSERT(is_valid_ptr(bytes) && "Invalid pointer");
101140

102141
BlockType *chunk_block = BlockType::from_usable_space(bytes);
103142

@@ -131,7 +170,7 @@ template <size_t NUM_BUCKETS> void FreeListHeap<NUM_BUCKETS>::free(void *ptr) {
131170
heap_stats_.total_free_calls += 1;
132171
}
133172

134-
// Follows contract of the C standard realloc() function
173+
// Follows constract of the C standard realloc() function
135174
// If ptr is free'd, will return nullptr.
136175
template <size_t NUM_BUCKETS>
137176
void *FreeListHeap<NUM_BUCKETS>::realloc(void *ptr, size_t size) {
@@ -146,7 +185,7 @@ void *FreeListHeap<NUM_BUCKETS>::realloc(void *ptr, size_t size) {
146185

147186
cpp::byte *bytes = static_cast<cpp::byte *>(ptr);
148187

149-
if (bytes < region_.data() || bytes >= region_.data() + region_.size())
188+
if (!is_valid_ptr(bytes))
150189
return nullptr;
151190

152191
BlockType *chunk_block = BlockType::from_usable_space(bytes);
@@ -177,6 +216,8 @@ void *FreeListHeap<NUM_BUCKETS>::calloc(size_t num, size_t size) {
177216
return ptr;
178217
}
179218

219+
extern FreeListHeap<> *freelist_heap;
220+
180221
} // namespace LIBC_NAMESPACE
181222

182223
#endif // LLVM_LIBC_SRC_STDLIB_FREELIST_HEAP_H

libc/src/stdlib/freelist_malloc.cpp

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
//===-- Implementation for freelist_malloc --------------------------------===//
2+
//
3+
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4+
// See https://llvm.org/LICENSE.txt for license information.
5+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6+
//
7+
//===----------------------------------------------------------------------===//
8+
9+
#include "freelist_heap.h"
10+
11+
#include <stddef.h>
12+
13+
namespace LIBC_NAMESPACE {
14+
15+
namespace {
16+
// TODO: We should probably have something akin to what scudo/sanitizer
17+
// allocators do where each platform defines this.
18+
constexpr size_t SIZE = 0x40000000ULL; // 1GB
19+
LIBC_CONSTINIT FreeListHeapBuffer<SIZE> freelist_heap_buffer;
20+
} // namespace
21+
22+
FreeListHeap<> *freelist_heap = &freelist_heap_buffer;
23+
24+
void *malloc(size_t size) { return freelist_heap->allocate(size); }
25+
26+
void free(void *ptr) { freelist_heap->free(ptr); }
27+
28+
void *calloc(size_t num, size_t size) {
29+
return freelist_heap->calloc(num, size);
30+
}
31+
32+
void *realloc(void *ptr, size_t size) {
33+
return freelist_heap->realloc(ptr, size);
34+
}
35+
36+
} // namespace LIBC_NAMESPACE

libc/test/src/CMakeLists.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ add_subdirectory(inttypes)
6161
if(${LIBC_TARGET_OS} STREQUAL "linux")
6262
add_subdirectory(fcntl)
6363
add_subdirectory(sched)
64-
add_subdirectory(sys)
64+
#add_subdirectory(sys)
6565
add_subdirectory(termios)
6666
add_subdirectory(unistd)
6767
endif()

libc/test/src/stdlib/CMakeLists.txt

Lines changed: 18 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,7 @@ add_libc_test(
8888
DEPENDS
8989
libc.src.__support.CPP.span
9090
libc.src.stdlib.freelist_heap
91+
libc.src.stdlib.malloc
9192
libc.src.string.memcmp
9293
libc.src.string.memcpy
9394
)
@@ -437,19 +438,21 @@ if(LLVM_LIBC_FULL_BUILD)
437438
libc.src.stdlib.quick_exit
438439
)
439440

440-
# Only the GPU has an in-tree 'malloc' implementation.
441-
if(LIBC_TARGET_OS_IS_GPU)
442-
add_libc_test(
443-
malloc_test
444-
HERMETIC_TEST_ONLY
445-
SUITE
446-
libc-stdlib-tests
447-
SRCS
448-
malloc_test.cpp
449-
DEPENDS
450-
libc.include.stdlib
451-
libc.src.stdlib.malloc
452-
libc.src.stdlib.free
453-
)
454-
endif()
441+
add_libc_test(
442+
malloc_test
443+
SUITE
444+
libc-stdlib-tests
445+
SRCS
446+
block_test.cpp
447+
malloc_test.cpp
448+
freelist_malloc_test.cpp
449+
freelist_heap_test.cpp
450+
freelist_test.cpp
451+
DEPENDS
452+
libc.include.stdlib
453+
libc.src.stdlib.malloc
454+
libc.src.stdlib.free
455+
libc.src.string.memcmp
456+
libc.src.string.memcpy
457+
)
455458
endif()

0 commit comments

Comments
 (0)