Skip to content

Commit bb7d57a

Browse files
committed
[WIP][libc] Add freelist malloc
1 parent 1737814 commit bb7d57a

File tree

10 files changed

+586
-20
lines changed

10 files changed

+586
-20
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/stdlib/CMakeLists.txt

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -402,8 +402,22 @@ else()
402402
libc.src.__support.CPP.array
403403
libc.src.__support.CPP.span
404404
)
405-
add_entrypoint_external(
405+
add_entrypoint_object(
406406
malloc
407+
SRCS
408+
freelist_malloc.cpp
409+
HDRS
410+
malloc.h
411+
DEPENDS
412+
.block
413+
.freelist
414+
libc.src.__support.CPP.new
415+
libc.src.__support.CPP.optional
416+
libc.src.__support.CPP.span
417+
libc.src.__support.CPP.type_traits
418+
libc.src.__support.fixedvector
419+
libc.src.string.memcpy
420+
libc.src.string.memset
407421
)
408422
add_entrypoint_external(
409423
free

libc/src/stdlib/freelist.h

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,7 @@ bool FreeList<NUM_BUCKETS>::add_chunk(span<cpp::byte> chunk) {
9999

100100
aliased.bytes = chunk.data();
101101

102-
unsigned short chunk_ptr = find_chunk_ptr_for_size(chunk.size(), false);
102+
size_t chunk_ptr = find_chunk_ptr_for_size(chunk.size(), false);
103103

104104
// Add it to the correct list.
105105
aliased.node->size = chunk.size();
@@ -114,7 +114,7 @@ span<cpp::byte> FreeList<NUM_BUCKETS>::find_chunk(size_t size) const {
114114
if (size == 0)
115115
return span<cpp::byte>();
116116

117-
unsigned short chunk_ptr = find_chunk_ptr_for_size(size, true);
117+
size_t chunk_ptr = find_chunk_ptr_for_size(size, true);
118118

119119
// Check that there's data. This catches the case where we run off the
120120
// end of the array
@@ -144,7 +144,7 @@ span<cpp::byte> FreeList<NUM_BUCKETS>::find_chunk(size_t size) const {
144144

145145
template <size_t NUM_BUCKETS>
146146
bool FreeList<NUM_BUCKETS>::remove_chunk(span<cpp::byte> chunk) {
147-
unsigned short chunk_ptr = find_chunk_ptr_for_size(chunk.size(), true);
147+
size_t chunk_ptr = find_chunk_ptr_for_size(chunk.size(), true);
148148

149149
// Walk that list, finding the chunk.
150150
union {

libc/src/stdlib/freelist_heap.h

Lines changed: 207 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,207 @@
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+
void MallocInit(uint8_t *heap_low_addr, uint8_t *heap_high_addr);
24+
25+
using cpp::optional;
26+
using cpp::span;
27+
28+
static constexpr cpp::array<size_t, 6> defaultBuckets{16, 32, 64,
29+
128, 256, 512};
30+
31+
template <size_t kNumBuckets = defaultBuckets.size()> class FreeListHeap {
32+
public:
33+
using BlockType = Block<>;
34+
35+
template <size_t> friend class FreeListHeapBuffer;
36+
37+
struct HeapStats {
38+
size_t total_bytes;
39+
size_t bytes_allocated;
40+
size_t cumulative_allocated;
41+
size_t cumulative_freed;
42+
size_t total_allocate_calls;
43+
size_t total_free_calls;
44+
};
45+
FreeListHeap(span<cpp::byte> region);
46+
47+
void *Allocate(size_t size);
48+
void Free(void *ptr);
49+
void *Realloc(void *ptr, size_t size);
50+
void *Calloc(size_t num, size_t size);
51+
52+
void LogHeapStats();
53+
const HeapStats &heap_stats() const { return heap_stats_; }
54+
55+
private:
56+
span<cpp::byte> BlockToSpan(BlockType *block) {
57+
return span<cpp::byte>(block->usable_space(), block->inner_size());
58+
}
59+
60+
void InvalidFreeCrash() { __builtin_trap(); }
61+
62+
span<cpp::byte> region_;
63+
FreeList<kNumBuckets> freelist_;
64+
HeapStats heap_stats_;
65+
};
66+
67+
template <size_t kNumBuckets>
68+
FreeListHeap<kNumBuckets>::FreeListHeap(span<cpp::byte> region)
69+
: freelist_(defaultBuckets), heap_stats_() {
70+
auto result = BlockType::init(region);
71+
BlockType *block = *result;
72+
73+
freelist_.add_chunk(BlockToSpan(block));
74+
75+
region_ = region;
76+
heap_stats_.total_bytes = region.size();
77+
}
78+
79+
template <size_t kNumBuckets>
80+
void *FreeListHeap<kNumBuckets>::Allocate(size_t size) {
81+
// Find a chunk in the freelist. Split it if needed, then return
82+
83+
auto chunk = freelist_.find_chunk(size);
84+
85+
if (chunk.data() == nullptr) {
86+
return nullptr;
87+
}
88+
freelist_.remove_chunk(chunk);
89+
90+
BlockType *chunk_block = BlockType::from_usable_space(chunk.data());
91+
92+
// Split that chunk. If there's a leftover chunk, add it to the freelist
93+
optional<BlockType *> result = BlockType::split(chunk_block, size);
94+
if (result) {
95+
freelist_.add_chunk(BlockToSpan(*result));
96+
}
97+
98+
chunk_block->mark_used();
99+
100+
heap_stats_.bytes_allocated += size;
101+
heap_stats_.cumulative_allocated += size;
102+
heap_stats_.total_allocate_calls += 1;
103+
104+
return chunk_block->usable_space();
105+
}
106+
107+
template <size_t kNumBuckets> void FreeListHeap<kNumBuckets>::Free(void *ptr) {
108+
cpp::byte *bytes = static_cast<cpp::byte *>(ptr);
109+
110+
if (bytes < region_.data() || bytes >= region_.data() + region_.size()) {
111+
InvalidFreeCrash();
112+
return;
113+
}
114+
115+
BlockType *chunk_block = BlockType::from_usable_space(bytes);
116+
117+
size_t size_freed = chunk_block->inner_size();
118+
// Ensure that the block is in-use
119+
if (!chunk_block->used()) {
120+
InvalidFreeCrash();
121+
return;
122+
}
123+
chunk_block->mark_free();
124+
// Can we combine with the left or right blocks?
125+
BlockType *prev = chunk_block->prev();
126+
BlockType *next = nullptr;
127+
128+
if (!chunk_block->last()) {
129+
next = chunk_block->next();
130+
}
131+
132+
if (prev != nullptr && !prev->used()) {
133+
// Remove from freelist and merge
134+
freelist_.remove_chunk(BlockToSpan(prev));
135+
chunk_block = chunk_block->prev();
136+
BlockType::merge_next(chunk_block);
137+
}
138+
139+
if (next != nullptr && !next->used()) {
140+
freelist_.remove_chunk(BlockToSpan(next));
141+
BlockType::merge_next(chunk_block);
142+
}
143+
// Add back to the freelist
144+
freelist_.add_chunk(BlockToSpan(chunk_block));
145+
146+
heap_stats_.bytes_allocated -= size_freed;
147+
heap_stats_.cumulative_freed += size_freed;
148+
heap_stats_.total_free_calls += 1;
149+
}
150+
151+
// Follows constract of the C standard realloc() function
152+
// If ptr is free'd, will return nullptr.
153+
template <size_t kNumBuckets>
154+
void *FreeListHeap<kNumBuckets>::Realloc(void *ptr, size_t size) {
155+
if (size == 0) {
156+
Free(ptr);
157+
return nullptr;
158+
}
159+
160+
// If the pointer is nullptr, allocate a new memory.
161+
if (ptr == nullptr) {
162+
return Allocate(size);
163+
}
164+
165+
cpp::byte *bytes = static_cast<cpp::byte *>(ptr);
166+
167+
if (bytes < region_.data() || bytes >= region_.data() + region_.size()) {
168+
return nullptr;
169+
}
170+
171+
BlockType *chunk_block = BlockType::from_usable_space(bytes);
172+
if (!chunk_block->used()) {
173+
return nullptr;
174+
}
175+
size_t old_size = chunk_block->inner_size();
176+
177+
// Do nothing and return ptr if the required memory size is smaller than
178+
// the current size.
179+
if (old_size >= size) {
180+
return ptr;
181+
}
182+
183+
void *new_ptr = Allocate(size);
184+
// Don't invalidate ptr if Allocate(size) fails to initilize the memory.
185+
if (new_ptr == nullptr) {
186+
return nullptr;
187+
}
188+
memcpy(new_ptr, ptr, old_size);
189+
190+
Free(ptr);
191+
return new_ptr;
192+
}
193+
194+
template <size_t kNumBuckets>
195+
void *FreeListHeap<kNumBuckets>::Calloc(size_t num, size_t size) {
196+
void *ptr = Allocate(num * size);
197+
if (ptr != nullptr) {
198+
memset(ptr, 0, num * size);
199+
}
200+
return ptr;
201+
}
202+
203+
extern FreeListHeap<> *freelist_heap;
204+
205+
} // namespace LIBC_NAMESPACE
206+
207+
#endif // LLVM_LIBC_SRC_STDLIB_FREELIST_HEAP_H

libc/src/stdlib/freelist_malloc.cpp

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
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+
#include "src/__support/CPP/new.h"
11+
#include "src/__support/CPP/span.h"
12+
#include "src/__support/CPP/type_traits.h"
13+
#include "src/string/memcpy.h"
14+
#include "src/string/memset.h"
15+
16+
#include <stddef.h>
17+
18+
namespace LIBC_NAMESPACE {
19+
20+
namespace {
21+
cpp::aligned_storage_t<sizeof(FreeListHeap<>), alignof(FreeListHeap<>)> buf;
22+
} // namespace
23+
24+
FreeListHeap<> *freelist_heap;
25+
26+
// Define the global heap variables.
27+
void MallocInit(uint8_t *heap_low_addr, uint8_t *heap_high_addr) {
28+
cpp::span<LIBC_NAMESPACE::cpp::byte> allocator_freelist_raw_heap =
29+
cpp::span<cpp::byte>(reinterpret_cast<cpp::byte *>(heap_low_addr),
30+
heap_high_addr - heap_low_addr);
31+
freelist_heap = new (&buf) FreeListHeap<>(allocator_freelist_raw_heap);
32+
}
33+
34+
void *malloc(size_t size) { return freelist_heap->Allocate(size); }
35+
36+
void free(void *ptr) { freelist_heap->Free(ptr); }
37+
38+
void *calloc(size_t num, size_t size) {
39+
return freelist_heap->Calloc(num, size);
40+
}
41+
42+
} // 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: 16 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -424,19 +424,20 @@ if(LLVM_LIBC_FULL_BUILD)
424424
libc.src.stdlib.quick_exit
425425
)
426426

427-
# Only the GPU has an in-tree 'malloc' implementation.
428-
if(LIBC_TARGET_OS_IS_GPU)
429-
add_libc_test(
430-
malloc_test
431-
HERMETIC_TEST_ONLY
432-
SUITE
433-
libc-stdlib-tests
434-
SRCS
435-
malloc_test.cpp
436-
DEPENDS
437-
libc.include.stdlib
438-
libc.src.stdlib.malloc
439-
libc.src.stdlib.free
440-
)
441-
endif()
427+
add_libc_test(
428+
malloc_test
429+
SUITE
430+
libc-stdlib-tests
431+
SRCS
432+
block_test.cpp
433+
malloc_test.cpp
434+
freelist_malloc_test.cpp
435+
freelist_heap_test.cpp
436+
freelist_test.cpp
437+
DEPENDS
438+
libc.include.stdlib
439+
libc.src.string.memcmp
440+
libc.src.stdlib.malloc
441+
libc.src.stdlib.free
442+
)
442443
endif()

0 commit comments

Comments
 (0)