Skip to content

Commit 2779336

Browse files
committed
[WIP][libc] Add freelist malloc
1 parent 85c78d4 commit 2779336

13 files changed

+955
-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: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -392,8 +392,21 @@ else()
392392
libc.src.__support.CPP.span
393393
libc.src.__support.CPP.type_traits
394394
)
395-
add_entrypoint_external(
395+
add_entrypoint_object(
396396
malloc
397+
SRCS
398+
freelist_malloc.cpp
399+
HDRS
400+
malloc.h
401+
DEPENDS
402+
.block
403+
libc.src.__support.CPP.new
404+
libc.src.__support.CPP.optional
405+
libc.src.__support.CPP.span
406+
libc.src.__support.CPP.type_traits
407+
libc.src.__support.fixedvector
408+
libc.src.string.memcpy
409+
libc.src.string.memset
397410
)
398411
add_entrypoint_external(
399412
free

libc/src/stdlib/block.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,8 +39,8 @@ LIBC_INLINE constexpr size_t align_down(size_t value, size_t alignment) {
3939
}
4040

4141
/// Returns the value rounded down to the nearest multiple of alignment.
42-
LIBC_INLINE template <typename T>
43-
constexpr T *align_down(T *value, size_t alignment) {
42+
template <typename T>
43+
LIBC_INLINE constexpr T *align_down(T *value, size_t alignment) {
4444
return reinterpret_cast<T *>(
4545
align_down(reinterpret_cast<size_t>(value), alignment));
4646
}

libc/src/stdlib/free.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,4 +17,4 @@ void free(void *ptr);
1717

1818
} // namespace LIBC_NAMESPACE
1919

20-
#endif // LLVM_LIBC_SRC_STDLIB_LDIV_H
20+
#endif // LLVM_LIBC_SRC_STDLIB_FREE_H

libc/src/stdlib/freelist.h

Lines changed: 203 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,203 @@
1+
//===-- Interface 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+
#ifndef LLVM_LIBC_SRC_STDLIB_FREELIST_H
10+
#define LLVM_LIBC_SRC_STDLIB_FREELIST_H
11+
12+
#include "src/__support/CPP/array.h"
13+
#include "src/__support/CPP/cstddef.h"
14+
#include "src/__support/CPP/span.h"
15+
#include "src/__support/fixedvector.h"
16+
17+
namespace LIBC_NAMESPACE {
18+
19+
using cpp::span;
20+
21+
/// Basic [freelist](https://en.wikipedia.org/wiki/Free_list) implementation
22+
/// for an allocator. This implementation buckets by chunk size, with a list
23+
/// of user-provided buckets. Each bucket is a linked list of storage chunks.
24+
/// Because this freelist uses the added chunks themselves as list nodes, there
25+
/// is a lower bound of `sizeof(FreeList.FreeListNode)` bytes for chunks which
26+
/// can be added to this freelist. There is also an implicit bucket for
27+
/// "everything else", for chunks which do not fit into a bucket.
28+
///
29+
/// Each added chunk will be added to the smallest bucket under which it fits.
30+
/// If it does not fit into any user-provided bucket, it will be added to the
31+
/// default bucket.
32+
///
33+
/// As an example, assume that the `FreeList` is configured with buckets of
34+
/// sizes {64, 128, 256, and 512} bytes. The internal state may look like the
35+
/// following:
36+
///
37+
/// @code{.unparsed}
38+
/// bucket[0] (64B) --> chunk[12B] --> chunk[42B] --> chunk[64B] --> NULL
39+
/// bucket[1] (128B) --> chunk[65B] --> chunk[72B] --> NULL
40+
/// bucket[2] (256B) --> NULL
41+
/// bucket[3] (512B) --> chunk[312B] --> chunk[512B] --> chunk[416B] --> NULL
42+
/// bucket[4] (implicit) --> chunk[1024B] --> chunk[513B] --> NULL
43+
/// @endcode
44+
///
45+
/// Note that added chunks should be aligned to a 4-byte boundary.
46+
template <size_t kNumBuckets = 6> class FreeList {
47+
public:
48+
// Remove copy/move ctors
49+
FreeList(const FreeList &other) = delete;
50+
FreeList(FreeList &&other) = delete;
51+
FreeList &operator=(const FreeList &other) = delete;
52+
FreeList &operator=(FreeList &&other) = delete;
53+
54+
/// Adds a chunk to this freelist.
55+
bool AddChunk(cpp::span<cpp::byte> chunk);
56+
57+
/// Finds an eligible chunk for an allocation of size `size`.
58+
///
59+
/// @note This returns the first allocation possible within a given bucket;
60+
/// It does not currently optimize for finding the smallest chunk.
61+
///
62+
/// @returns
63+
/// * On success - A span representing the chunk.
64+
/// * On failure (e.g. there were no chunks available for that allocation) -
65+
/// A span with a size of 0.
66+
cpp::span<cpp::byte> FindChunk(size_t size) const;
67+
68+
/// Removes a chunk from this freelist.
69+
bool RemoveChunk(cpp::span<cpp::byte> chunk);
70+
71+
private:
72+
// For a given size, find which index into chunks_ the node should be written
73+
// to.
74+
unsigned short FindChunkPtrForSize(size_t size, bool non_null) const;
75+
76+
struct FreeListNode {
77+
FreeListNode *next;
78+
size_t size;
79+
};
80+
81+
public:
82+
explicit FreeList(cpp::array<size_t, kNumBuckets> sizes)
83+
: chunks_(kNumBuckets + 1, 0), sizes_(sizes.begin(), sizes.end()) {}
84+
85+
FixedVector<FreeList::FreeListNode *, kNumBuckets + 1> chunks_;
86+
FixedVector<size_t, kNumBuckets> sizes_;
87+
};
88+
89+
template <size_t kNumBuckets>
90+
bool FreeList<kNumBuckets>::AddChunk(span<cpp::byte> chunk) {
91+
// Check that the size is enough to actually store what we need
92+
if (chunk.size() < sizeof(FreeListNode)) {
93+
return false;
94+
}
95+
96+
union {
97+
FreeListNode *node;
98+
cpp::byte *bytes;
99+
} aliased;
100+
101+
aliased.bytes = chunk.data();
102+
103+
unsigned short chunk_ptr = FindChunkPtrForSize(chunk.size(), false);
104+
105+
// Add it to the correct list.
106+
aliased.node->size = chunk.size();
107+
aliased.node->next = chunks_[chunk_ptr];
108+
chunks_[chunk_ptr] = aliased.node;
109+
110+
return true;
111+
}
112+
113+
template <size_t kNumBuckets>
114+
span<cpp::byte> FreeList<kNumBuckets>::FindChunk(size_t size) const {
115+
if (size == 0) {
116+
return span<cpp::byte>();
117+
}
118+
119+
unsigned short chunk_ptr = FindChunkPtrForSize(size, true);
120+
121+
// Check that there's data. This catches the case where we run off the
122+
// end of the array
123+
if (chunks_[chunk_ptr] == nullptr) {
124+
return span<cpp::byte>();
125+
}
126+
127+
// Now iterate up the buckets, walking each list to find a good candidate
128+
for (size_t i = chunk_ptr; i < chunks_.size(); i++) {
129+
union {
130+
FreeListNode *node;
131+
cpp::byte *data;
132+
} aliased;
133+
aliased.node = chunks_[static_cast<unsigned short>(i)];
134+
135+
while (aliased.node != nullptr) {
136+
if (aliased.node->size >= size) {
137+
return span<cpp::byte>(aliased.data, aliased.node->size);
138+
}
139+
140+
aliased.node = aliased.node->next;
141+
}
142+
}
143+
144+
// If we get here, we've checked every block in every bucket. There's
145+
// nothing that can support this allocation.
146+
return span<cpp::byte>();
147+
}
148+
149+
template <size_t kNumBuckets>
150+
bool FreeList<kNumBuckets>::RemoveChunk(span<cpp::byte> chunk) {
151+
unsigned short chunk_ptr = FindChunkPtrForSize(chunk.size(), true);
152+
153+
// Walk that list, finding the chunk.
154+
union {
155+
FreeListNode *node;
156+
cpp::byte *data;
157+
} aliased, aliased_next;
158+
159+
// Check head first.
160+
if (chunks_[chunk_ptr] == nullptr) {
161+
return false;
162+
}
163+
164+
aliased.node = chunks_[chunk_ptr];
165+
if (aliased.data == chunk.data()) {
166+
chunks_[chunk_ptr] = aliased.node->next;
167+
return true;
168+
}
169+
170+
// No? Walk the nodes.
171+
aliased.node = chunks_[chunk_ptr];
172+
173+
while (aliased.node->next != nullptr) {
174+
aliased_next.node = aliased.node->next;
175+
if (aliased_next.data == chunk.data()) {
176+
// Found it, remove this node out of the chain
177+
aliased.node->next = aliased_next.node->next;
178+
return true;
179+
}
180+
181+
aliased.node = aliased.node->next;
182+
}
183+
184+
return false;
185+
}
186+
187+
template <size_t kNumBuckets>
188+
unsigned short FreeList<kNumBuckets>::FindChunkPtrForSize(size_t size,
189+
bool non_null) const {
190+
unsigned short chunk_ptr = 0;
191+
for (chunk_ptr = 0u; chunk_ptr < sizes_.size(); chunk_ptr++) {
192+
if (sizes_[chunk_ptr] >= size &&
193+
(!non_null || chunks_[chunk_ptr] != nullptr)) {
194+
break;
195+
}
196+
}
197+
198+
return chunk_ptr;
199+
}
200+
201+
} // namespace LIBC_NAMESPACE
202+
203+
#endif // LLVM_LIBC_SRC_STDLIB_FREELIST_H

0 commit comments

Comments
 (0)