Skip to content

Commit 0216eca

Browse files
committed
[libc][stdlib] Add freelist class
This implements a traditional freelist to be used by the freelist allocator. It operates on spans of bytes which can be anything. The freelist allocator will store Blocks inside them. This is a part of #94270 to land in smaller patches.
1 parent fd4a740 commit 0216eca

File tree

4 files changed

+386
-0
lines changed

4 files changed

+386
-0
lines changed

libc/src/stdlib/CMakeLists.txt

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -392,6 +392,16 @@ else()
392392
libc.src.__support.CPP.span
393393
libc.src.__support.CPP.type_traits
394394
)
395+
add_header_library(
396+
freelist
397+
HDRS
398+
freelist.h
399+
DEPENDS
400+
libc.src.__support.fixedvector
401+
libc.src.__support.CPP.cstddef
402+
libc.src.__support.CPP.array
403+
libc.src.__support.CPP.span
404+
)
395405
add_entrypoint_external(
396406
malloc
397407
)

libc/src/stdlib/freelist.h

Lines changed: 198 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,198 @@
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 NUM_BUCKETS = 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 add_chunk(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> find_chunk(size_t size) const;
67+
68+
/// Removes a chunk from this freelist.
69+
bool remove_chunk(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+
size_t find_chunk_ptr_for_size(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, NUM_BUCKETS> sizes)
83+
: chunks_(NUM_BUCKETS + 1, 0), sizes_(sizes.begin(), sizes.end()) {}
84+
85+
FixedVector<FreeList::FreeListNode *, NUM_BUCKETS + 1> chunks_;
86+
FixedVector<size_t, NUM_BUCKETS> sizes_;
87+
};
88+
89+
template <size_t NUM_BUCKETS>
90+
bool FreeList<NUM_BUCKETS>::add_chunk(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+
union {
96+
FreeListNode *node;
97+
cpp::byte *bytes;
98+
} aliased;
99+
100+
aliased.bytes = chunk.data();
101+
102+
unsigned short 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;
108+
109+
return true;
110+
}
111+
112+
template <size_t NUM_BUCKETS>
113+
span<cpp::byte> FreeList<NUM_BUCKETS>::find_chunk(size_t size) const {
114+
if (size == 0)
115+
return span<cpp::byte>();
116+
117+
unsigned short chunk_ptr = find_chunk_ptr_for_size(size, true);
118+
119+
// Check that there's data. This catches the case where we run off the
120+
// end of the array
121+
if (chunks_[chunk_ptr] == nullptr)
122+
return span<cpp::byte>();
123+
124+
// Now iterate up the buckets, walking each list to find a good candidate
125+
for (size_t i = chunk_ptr; i < chunks_.size(); i++) {
126+
union {
127+
FreeListNode *node;
128+
cpp::byte *data;
129+
} aliased;
130+
aliased.node = chunks_[static_cast<unsigned short>(i)];
131+
132+
while (aliased.node != nullptr) {
133+
if (aliased.node->size >= size)
134+
return span<cpp::byte>(aliased.data, aliased.node->size);
135+
136+
aliased.node = aliased.node->next;
137+
}
138+
}
139+
140+
// If we get here, we've checked every block in every bucket. There's
141+
// nothing that can support this allocation.
142+
return span<cpp::byte>();
143+
}
144+
145+
template <size_t NUM_BUCKETS>
146+
bool FreeList<NUM_BUCKETS>::remove_chunk(span<cpp::byte> chunk) {
147+
unsigned short chunk_ptr = find_chunk_ptr_for_size(chunk.size(), true);
148+
149+
// Walk that list, finding the chunk.
150+
union {
151+
FreeListNode *node;
152+
cpp::byte *data;
153+
} aliased, aliased_next;
154+
155+
// Check head first.
156+
if (chunks_[chunk_ptr] == nullptr)
157+
return false;
158+
159+
aliased.node = chunks_[chunk_ptr];
160+
if (aliased.data == chunk.data()) {
161+
chunks_[chunk_ptr] = aliased.node->next;
162+
return true;
163+
}
164+
165+
// No? Walk the nodes.
166+
aliased.node = chunks_[chunk_ptr];
167+
168+
while (aliased.node->next != nullptr) {
169+
aliased_next.node = aliased.node->next;
170+
if (aliased_next.data == chunk.data()) {
171+
// Found it, remove this node out of the chain
172+
aliased.node->next = aliased_next.node->next;
173+
return true;
174+
}
175+
176+
aliased.node = aliased.node->next;
177+
}
178+
179+
return false;
180+
}
181+
182+
template <size_t NUM_BUCKETS>
183+
size_t FreeList<NUM_BUCKETS>::find_chunk_ptr_for_size(size_t size,
184+
bool non_null) const {
185+
size_t chunk_ptr = 0;
186+
for (chunk_ptr = 0u; chunk_ptr < sizes_.size(); chunk_ptr++) {
187+
if (sizes_[chunk_ptr] >= size &&
188+
(!non_null || chunks_[chunk_ptr] != nullptr)) {
189+
break;
190+
}
191+
}
192+
193+
return chunk_ptr;
194+
}
195+
196+
} // namespace LIBC_NAMESPACE
197+
198+
#endif // LLVM_LIBC_SRC_STDLIB_FREELIST_H

libc/test/src/stdlib/CMakeLists.txt

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,18 @@ add_libc_test(
6767
libc.src.string.memcpy
6868
)
6969

70+
add_libc_test(
71+
freelist_test
72+
SUITE
73+
libc-stdlib-tests
74+
SRCS
75+
freelist_test.cpp
76+
DEPENDS
77+
libc.src.stdlib.freelist
78+
libc.src.__support.CPP.array
79+
libc.src.__support.CPP.span
80+
)
81+
7082
add_fp_unittest(
7183
strtod_test
7284
SUITE

0 commit comments

Comments
 (0)