Skip to content

Commit 0d2511d

Browse files
committed
[WIP][libc] Add freelist malloc
1 parent f074500 commit 0d2511d

File tree

11 files changed

+608
-37
lines changed

11 files changed

+608
-37
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: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -401,9 +401,24 @@ else()
401401
libc.src.__support.CPP.cstddef
402402
libc.src.__support.CPP.array
403403
libc.src.__support.CPP.span
404+
libc.src.stdio.printf
404405
)
405-
add_entrypoint_external(
406+
add_entrypoint_object(
406407
malloc
408+
SRCS
409+
freelist_malloc.cpp
410+
HDRS
411+
malloc.h
412+
DEPENDS
413+
.block
414+
.freelist
415+
libc.src.__support.CPP.new
416+
libc.src.__support.CPP.optional
417+
libc.src.__support.CPP.span
418+
libc.src.__support.CPP.type_traits
419+
libc.src.__support.fixedvector
420+
libc.src.string.memcpy
421+
libc.src.string.memset
407422
)
408423
add_entrypoint_external(
409424
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: 230 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,230 @@
1+
//===-- Interface for freelist_heap ---------------------------------------===//
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+
#ifndef LLVM_LIBC_SRC_STDLIB_FREELIST_HEAP_H
10+
#define LLVM_LIBC_SRC_STDLIB_FREELIST_HEAP_H
11+
12+
#include <stddef.h>
13+
14+
#include "block.h"
15+
#include "freelist.h"
16+
#include "src/__support/CPP/optional.h"
17+
#include "src/__support/CPP/span.h"
18+
#include "src/string/memcpy.h"
19+
#include "src/string/memset.h"
20+
21+
namespace LIBC_NAMESPACE {
22+
23+
using cpp::optional;
24+
using cpp::span;
25+
26+
static constexpr cpp::array<size_t, 6> defaultBuckets{16, 32, 64,
27+
128, 256, 512};
28+
29+
template <size_t kNumBuckets = defaultBuckets.size()> class FreeListHeap {
30+
public:
31+
using BlockType = Block<>;
32+
using FreeListType = FreeList<kNumBuckets>;
33+
34+
struct HeapStats {
35+
size_t total_bytes;
36+
size_t bytes_allocated;
37+
size_t cumulative_allocated;
38+
size_t cumulative_freed;
39+
size_t total_allocate_calls;
40+
size_t total_free_calls;
41+
};
42+
FreeListHeap(span<cpp::byte> region)
43+
: FreeListHeap(&*region.begin(), &*region.end(), region.size()) {
44+
auto result = BlockType::init(region);
45+
BlockType *block = *result;
46+
freelist_.add_chunk(BlockToSpan(block));
47+
}
48+
49+
constexpr FreeListHeap(void *start, cpp::byte *end, size_t total_bytes)
50+
: block_region_start_(start), block_region_end_(end),
51+
freelist_(defaultBuckets), heap_stats_{} {
52+
heap_stats_.total_bytes = total_bytes;
53+
}
54+
55+
void *Allocate(size_t size);
56+
void Free(void *ptr);
57+
void *Realloc(void *ptr, size_t size);
58+
void *Calloc(size_t num, size_t size);
59+
60+
void LogHeapStats();
61+
const HeapStats &heap_stats() const { return heap_stats_; }
62+
void reset_heap_stats() { heap_stats_ = {}; }
63+
64+
void *region_start() const { return block_region_start_; }
65+
size_t region_size() const {
66+
return reinterpret_cast<uintptr_t>(block_region_end_) -
67+
reinterpret_cast<uintptr_t>(block_region_start_);
68+
}
69+
70+
protected:
71+
constexpr void set_freelist_node(typename FreeListType::FreeListNode *node,
72+
cpp::span<cpp::byte> chunk) {
73+
freelist_.set_freelist_node(node, chunk);
74+
}
75+
76+
private:
77+
constexpr span<cpp::byte> BlockToSpan(BlockType *block) {
78+
return span<cpp::byte>(block->usable_space(), block->inner_size());
79+
}
80+
81+
void InvalidFreeCrash() { __builtin_trap(); }
82+
83+
bool is_valid_ptr(void *ptr) {
84+
return ptr >= block_region_start_ && ptr < block_region_end_;
85+
}
86+
87+
void *block_region_start_;
88+
void *block_region_end_;
89+
FreeListType freelist_;
90+
HeapStats heap_stats_;
91+
};
92+
93+
template <size_t kBuffSize, size_t kNumBuckets = defaultBuckets.size()>
94+
struct FreeListHeapBuffer : public FreeListHeap<kNumBuckets> {
95+
using parent = FreeListHeap<kNumBuckets>;
96+
using FreeListNode = typename parent::FreeListType::FreeListNode;
97+
98+
constexpr FreeListHeapBuffer()
99+
: FreeListHeap<kNumBuckets>(&block, buffer + sizeof(buffer), kBuffSize),
100+
block(0, kBuffSize), node{}, buffer{} {
101+
block.mark_last();
102+
103+
cpp::span<cpp::byte> chunk(buffer, sizeof(buffer));
104+
parent::set_freelist_node(&node, chunk);
105+
}
106+
107+
typename parent::BlockType block;
108+
FreeListNode node;
109+
cpp::byte buffer[kBuffSize - sizeof(block) - sizeof(node)];
110+
};
111+
112+
template <size_t kNumBuckets>
113+
void *FreeListHeap<kNumBuckets>::Allocate(size_t size) {
114+
// Find a chunk in the freelist. Split it if needed, then return
115+
auto chunk = freelist_.find_chunk(size);
116+
117+
if (chunk.data() == nullptr)
118+
return nullptr;
119+
freelist_.remove_chunk(chunk);
120+
121+
BlockType *chunk_block = BlockType::from_usable_space(chunk.data());
122+
123+
// Split that chunk. If there's a leftover chunk, add it to the freelist
124+
optional<BlockType *> result = BlockType::split(chunk_block, size);
125+
if (result)
126+
freelist_.add_chunk(BlockToSpan(*result));
127+
128+
chunk_block->mark_used();
129+
130+
heap_stats_.bytes_allocated += size;
131+
heap_stats_.cumulative_allocated += size;
132+
heap_stats_.total_allocate_calls += 1;
133+
134+
return chunk_block->usable_space();
135+
}
136+
137+
template <size_t kNumBuckets> void FreeListHeap<kNumBuckets>::Free(void *ptr) {
138+
cpp::byte *bytes = static_cast<cpp::byte *>(ptr);
139+
140+
if (!is_valid_ptr(bytes)) {
141+
InvalidFreeCrash();
142+
return;
143+
}
144+
145+
BlockType *chunk_block = BlockType::from_usable_space(bytes);
146+
147+
size_t size_freed = chunk_block->inner_size();
148+
// Ensure that the block is in-use
149+
if (!chunk_block->used()) {
150+
InvalidFreeCrash();
151+
return;
152+
}
153+
chunk_block->mark_free();
154+
// Can we combine with the left or right blocks?
155+
BlockType *prev = chunk_block->prev();
156+
BlockType *next = nullptr;
157+
158+
if (!chunk_block->last())
159+
next = chunk_block->next();
160+
161+
if (prev != nullptr && !prev->used()) {
162+
// Remove from freelist and merge
163+
freelist_.remove_chunk(BlockToSpan(prev));
164+
chunk_block = chunk_block->prev();
165+
BlockType::merge_next(chunk_block);
166+
}
167+
168+
if (next != nullptr && !next->used()) {
169+
freelist_.remove_chunk(BlockToSpan(next));
170+
BlockType::merge_next(chunk_block);
171+
}
172+
// Add back to the freelist
173+
freelist_.add_chunk(BlockToSpan(chunk_block));
174+
175+
heap_stats_.bytes_allocated -= size_freed;
176+
heap_stats_.cumulative_freed += size_freed;
177+
heap_stats_.total_free_calls += 1;
178+
}
179+
180+
// Follows constract of the C standard realloc() function
181+
// If ptr is free'd, will return nullptr.
182+
template <size_t kNumBuckets>
183+
void *FreeListHeap<kNumBuckets>::Realloc(void *ptr, size_t size) {
184+
if (size == 0) {
185+
Free(ptr);
186+
return nullptr;
187+
}
188+
189+
// If the pointer is nullptr, allocate a new memory.
190+
if (ptr == nullptr)
191+
return Allocate(size);
192+
193+
cpp::byte *bytes = static_cast<cpp::byte *>(ptr);
194+
195+
if (!is_valid_ptr(bytes))
196+
return nullptr;
197+
198+
BlockType *chunk_block = BlockType::from_usable_space(bytes);
199+
if (!chunk_block->used())
200+
return nullptr;
201+
size_t old_size = chunk_block->inner_size();
202+
203+
// Do nothing and return ptr if the required memory size is smaller than
204+
// the current size.
205+
if (old_size >= size)
206+
return ptr;
207+
208+
void *new_ptr = Allocate(size);
209+
// Don't invalidate ptr if Allocate(size) fails to initilize the memory.
210+
if (new_ptr == nullptr)
211+
return nullptr;
212+
memcpy(new_ptr, ptr, old_size);
213+
214+
Free(ptr);
215+
return new_ptr;
216+
}
217+
218+
template <size_t kNumBuckets>
219+
void *FreeListHeap<kNumBuckets>::Calloc(size_t num, size_t size) {
220+
void *ptr = Allocate(num * size);
221+
if (ptr != nullptr)
222+
memset(ptr, 0, num * size);
223+
return ptr;
224+
}
225+
226+
extern FreeListHeap<> *freelist_heap;
227+
228+
} // namespace LIBC_NAMESPACE
229+
230+
#endif // LLVM_LIBC_SRC_STDLIB_FREELIST_HEAP_H

0 commit comments

Comments
 (0)