Skip to content

Commit 005758e

Browse files
authored
[libc][stdlib] Make the FreeListHeap constant-initializable (#95453)
This refactors some of the FreeListHeap, FreeList, and Block classes to have constexpr ctors so we can constinit a global allocator that does not require running some global function or global ctor to initialize. This is needed to prevent worrying about initialization order and any other module-ctor can invoke malloc without worry.
1 parent 1af1c9f commit 005758e

10 files changed

+283
-114
lines changed

libc/src/__support/fixedvector.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,13 @@ template <typename T, size_t CAPACITY> class FixedVector {
3030
push_back(*begin);
3131
}
3232

33+
using const_iterator = typename cpp::array<T, CAPACITY>::const_iterator;
34+
constexpr FixedVector(const_iterator begin, const_iterator end)
35+
: store{}, item_count{} {
36+
for (; begin != end; ++begin)
37+
push_back(*begin);
38+
}
39+
3340
constexpr FixedVector(size_t count, const T &value) : store{}, item_count{} {
3441
for (size_t i = 0; i < count; ++i)
3542
push_back(value);

libc/src/stdlib/CMakeLists.txt

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -418,8 +418,14 @@ else()
418418
libc.src.string.memory_utils.inline_memcpy
419419
libc.src.string.memory_utils.inline_memset
420420
)
421-
add_entrypoint_external(
421+
add_entrypoint_object(
422422
malloc
423+
SRCS
424+
freelist_malloc.cpp
425+
HDRS
426+
malloc.h
427+
DEPENDS
428+
.freelist_heap
423429
)
424430
add_entrypoint_external(
425431
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: 23 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -69,36 +69,44 @@ template <size_t NUM_BUCKETS = 6> class FreeList {
6969
/// Removes a chunk from this freelist.
7070
bool remove_chunk(cpp::span<cpp::byte> chunk);
7171

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

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

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

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

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

96-
// Add it to the correct list.
97-
size_t chunk_ptr = find_chunk_ptr_for_size(chunk.size(), false);
98-
99-
FreeListNode *node =
100-
::new (chunk.data()) FreeListNode{chunks_[chunk_ptr], chunk.size()};
101-
chunks_[chunk_ptr] = node;
108+
FreeListNode *node = ::new (chunk.data()) FreeListNode;
109+
set_freelist_node(*node, chunk);
102110

103111
return true;
104112
}
@@ -163,8 +171,9 @@ bool FreeList<NUM_BUCKETS>::remove_chunk(span<cpp::byte> chunk) {
163171
}
164172

165173
template <size_t NUM_BUCKETS>
166-
size_t FreeList<NUM_BUCKETS>::find_chunk_ptr_for_size(size_t size,
167-
bool non_null) const {
174+
constexpr size_t
175+
FreeList<NUM_BUCKETS>::find_chunk_ptr_for_size(size_t size,
176+
bool non_null) const {
168177
size_t chunk_ptr = 0;
169178
for (chunk_ptr = 0u; chunk_ptr < sizes_.size(); chunk_ptr++) {
170179
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: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
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/stdlib/calloc.h"
11+
#include "src/stdlib/free.h"
12+
#include "src/stdlib/malloc.h"
13+
#include "src/stdlib/realloc.h"
14+
15+
#include <stddef.h>
16+
17+
namespace LIBC_NAMESPACE {
18+
19+
namespace {
20+
// Users can define LIBC_FREELIST_MALLOC_SIZE for setting the default buffer
21+
// size used by freelist malloc.
22+
#ifdef LIBC_FREELIST_MALLOC_SIZE
23+
constexpr size_t SIZE = LIBC_FREELIST_MALLOC_SIZE;
24+
#else
25+
// TODO: We should probably have something akin to what scudo/sanitizer
26+
// allocators do where each platform defines this.
27+
constexpr size_t SIZE = 0x40000000ULL; // 1GB
28+
#endif
29+
LIBC_CONSTINIT FreeListHeapBuffer<SIZE> freelist_heap_buffer;
30+
} // namespace
31+
32+
FreeListHeap<> *freelist_heap = &freelist_heap_buffer;
33+
34+
LLVM_LIBC_FUNCTION(void *, malloc, (size_t size)) {
35+
return freelist_heap->allocate(size);
36+
}
37+
38+
LLVM_LIBC_FUNCTION(void, free, (void *ptr)) { return freelist_heap->free(ptr); }
39+
40+
LLVM_LIBC_FUNCTION(void *, calloc, (size_t num, size_t size)) {
41+
return freelist_heap->calloc(num, size);
42+
}
43+
44+
LLVM_LIBC_FUNCTION(void *, realloc, (void *ptr, size_t size)) {
45+
return freelist_heap->realloc(ptr, size);
46+
}
47+
48+
} // namespace LIBC_NAMESPACE

libc/src/stdlib/realloc.h

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
//===-- Implementation header for realloc -----------------------*- C++ -*-===//
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 <stddef.h>
10+
11+
#ifndef LLVM_LIBC_SRC_STDLIB_REALLOC_H
12+
#define LLVM_LIBC_SRC_STDLIB_REALLOC_H
13+
14+
namespace LIBC_NAMESPACE {
15+
16+
void *realloc(void *ptr, size_t size);
17+
18+
} // namespace LIBC_NAMESPACE
19+
20+
#endif // LLVM_LIBC_SRC_STDLIB_REALLOC_H

libc/test/src/stdlib/CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,9 +85,11 @@ add_libc_test(
8585
libc-stdlib-tests
8686
SRCS
8787
freelist_heap_test.cpp
88+
freelist_malloc_test.cpp
8889
DEPENDS
8990
libc.src.__support.CPP.span
9091
libc.src.stdlib.freelist_heap
92+
libc.src.stdlib.malloc
9193
libc.src.string.memcmp
9294
libc.src.string.memcpy
9395
)

0 commit comments

Comments
 (0)