Skip to content

Commit 0c10bdc

Browse files
authored
[libc] Lazily initialize freelist malloc using symbols (#99254)
This requires the user to set the upper bounds of the heap by defining the symbol `__libc_heap_limit`. The heap begins at `_end` and ends `__libc_heap_limit` bytes afterwards. This prevents a completely unused heap from requiring any space, and it prevents the heap from being zeroed at initialization time as part of BSS. It also allows users to customize the available heap location without recompiling libc. I'd think this should eventually be replaced with an implemenation based on a morecore() library. This would allow the same implementation to use sbrk() on POSIX, `_end` and `__libc_heap_limit` on embedded, and a buffer in tests. It would also provide better "wilderness" behavior that tends to decrease heap fragementation (see Wilson et al.) See #98096
1 parent a309215 commit 0c10bdc

File tree

10 files changed

+57
-71
lines changed

10 files changed

+57
-71
lines changed

libc/config/baremetal/config.json

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,6 @@
1818
"value": false
1919
}
2020
},
21-
"malloc": {
22-
"LIBC_CONF_FREELIST_MALLOC_BUFFER_SIZE": {
23-
"value": 102400
24-
}
25-
},
2621
"qsort": {
2722
"LIBC_CONF_QSORT_IMPL": {
2823
"value": "LIBC_QSORT_HEAP_SORT"

libc/config/config.json

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -71,12 +71,6 @@
7171
"doc": "Default number of spins before blocking if a rwlock is in contention (default to 100)."
7272
}
7373
},
74-
"malloc": {
75-
"LIBC_CONF_FREELIST_MALLOC_BUFFER_SIZE": {
76-
"value": 1073741824,
77-
"doc": "Default size for the constinit freelist buffer used for the freelist malloc implementation (default 1o 1GB)."
78-
}
79-
},
8074
"unistd": {
8175
"LIBC_CONF_ENABLE_TID_CACHE": {
8276
"value": true,

libc/docs/configure.rst

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,6 @@ to learn about the defaults for your platform and target.
3030
- ``LIBC_CONF_KEEP_FRAME_POINTER``: Keep frame pointer in functions for better debugging experience.
3131
* **"errno" options**
3232
- ``LIBC_CONF_ERRNO_MODE``: The implementation used for errno, acceptable values are LIBC_ERRNO_MODE_DEFAULT, LIBC_ERRNO_MODE_UNDEFINED, LIBC_ERRNO_MODE_THREAD_LOCAL, LIBC_ERRNO_MODE_SHARED, LIBC_ERRNO_MODE_EXTERNAL, and LIBC_ERRNO_MODE_SYSTEM.
33-
* **"malloc" options**
34-
- ``LIBC_CONF_FREELIST_MALLOC_BUFFER_SIZE``: Default size for the constinit freelist buffer used for the freelist malloc implementation (default 1o 1GB).
3533
* **"math" options**
3634
- ``LIBC_CONF_MATH_OPTIMIZATIONS``: Configures optimizations for math functions. Values accepted are LIBC_MATH_SKIP_ACCURATE_PASS, LIBC_MATH_SMALL_TABLES, LIBC_MATH_NO_ERRNO, LIBC_MATH_NO_EXCEPT, and LIBC_MATH_FAST.
3735
* **"printf" options**

libc/src/__support/freelist_heap.h

Lines changed: 32 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,9 @@
2222

2323
namespace LIBC_NAMESPACE_DECL {
2424

25+
extern "C" cpp::byte _end;
26+
extern "C" cpp::byte __llvm_libc_heap_limit;
27+
2528
using cpp::optional;
2629
using cpp::span;
2730

@@ -47,18 +50,10 @@ template <size_t NUM_BUCKETS = DEFAULT_BUCKETS.size()> class FreeListHeap {
4750
size_t total_free_calls;
4851
};
4952

50-
FreeListHeap(span<cpp::byte> region)
51-
: FreeListHeap(&*region.begin(), &*region.end(), region.size()) {
52-
auto result = BlockType::init(region);
53-
BlockType *block = *result;
54-
freelist_.add_chunk(block_to_span(block));
55-
}
53+
constexpr FreeListHeap() : begin_(&_end), end_(&__llvm_libc_heap_limit) {}
5654

57-
constexpr FreeListHeap(void *start, cpp::byte *end, size_t total_bytes)
58-
: block_region_start_(start), block_region_end_(end),
59-
freelist_(DEFAULT_BUCKETS), heap_stats_{} {
60-
heap_stats_.total_bytes = total_bytes;
61-
}
55+
constexpr FreeListHeap(span<cpp::byte> region)
56+
: begin_(region.begin()), end_(region.end()) {}
6257

6358
void *allocate(size_t size);
6459
void *aligned_allocate(size_t alignment, size_t size);
@@ -69,61 +64,57 @@ template <size_t NUM_BUCKETS = DEFAULT_BUCKETS.size()> class FreeListHeap {
6964
void *calloc(size_t num, size_t size);
7065

7166
const HeapStats &heap_stats() const { return heap_stats_; }
72-
void reset_heap_stats() { heap_stats_ = {}; }
7367

74-
void *region_start() const { return block_region_start_; }
75-
size_t region_size() const {
76-
return reinterpret_cast<uintptr_t>(block_region_end_) -
77-
reinterpret_cast<uintptr_t>(block_region_start_);
78-
}
68+
cpp::span<cpp::byte> region() const { return {begin_, end_}; }
7969

80-
protected:
81-
constexpr void set_freelist_node(typename FreeListType::FreeListNode &node,
82-
cpp::span<cpp::byte> chunk) {
83-
freelist_.set_freelist_node(node, chunk);
84-
}
70+
private:
71+
void init();
8572

8673
void *allocate_impl(size_t alignment, size_t size);
8774

88-
private:
8975
span<cpp::byte> block_to_span(BlockType *block) {
9076
return span<cpp::byte>(block->usable_space(), block->inner_size());
9177
}
9278

93-
bool is_valid_ptr(void *ptr) {
94-
return ptr >= block_region_start_ && ptr < block_region_end_;
95-
}
79+
bool is_valid_ptr(void *ptr) { return ptr >= begin_ && ptr < end_; }
9680

97-
void *block_region_start_;
98-
void *block_region_end_;
99-
FreeListType freelist_;
100-
HeapStats heap_stats_;
81+
bool is_initialized_ = false;
82+
cpp::byte *begin_;
83+
cpp::byte *end_;
84+
FreeListType freelist_{DEFAULT_BUCKETS};
85+
HeapStats heap_stats_{};
10186
};
10287

10388
template <size_t BUFF_SIZE, size_t NUM_BUCKETS = DEFAULT_BUCKETS.size()>
104-
struct FreeListHeapBuffer : public FreeListHeap<NUM_BUCKETS> {
89+
class FreeListHeapBuffer : public FreeListHeap<NUM_BUCKETS> {
10590
using parent = FreeListHeap<NUM_BUCKETS>;
10691
using FreeListNode = typename parent::FreeListType::FreeListNode;
10792

93+
public:
10894
constexpr FreeListHeapBuffer()
109-
: FreeListHeap<NUM_BUCKETS>(&block, buffer + sizeof(buffer), BUFF_SIZE),
110-
block(0, BUFF_SIZE), node{}, buffer{} {
111-
block.mark_last();
112-
113-
cpp::span<cpp::byte> chunk(buffer, sizeof(buffer));
114-
parent::set_freelist_node(node, chunk);
115-
}
95+
: FreeListHeap<NUM_BUCKETS>{buffer}, buffer{} {}
11696

117-
typename parent::BlockType block;
118-
FreeListNode node;
119-
cpp::byte buffer[BUFF_SIZE - sizeof(block) - sizeof(node)];
97+
private:
98+
cpp::byte buffer[BUFF_SIZE];
12099
};
121100

101+
template <size_t NUM_BUCKETS> void FreeListHeap<NUM_BUCKETS>::init() {
102+
LIBC_ASSERT(!is_initialized_ && "duplicate initialization");
103+
heap_stats_.total_bytes = region().size();
104+
auto result = BlockType::init(region());
105+
BlockType *block = *result;
106+
freelist_.add_chunk(block_to_span(block));
107+
is_initialized_ = true;
108+
}
109+
122110
template <size_t NUM_BUCKETS>
123111
void *FreeListHeap<NUM_BUCKETS>::allocate_impl(size_t alignment, size_t size) {
124112
if (size == 0)
125113
return nullptr;
126114

115+
if (!is_initialized_)
116+
init();
117+
127118
// Find a chunk in the freelist. Split it if needed, then return.
128119
auto chunk =
129120
freelist_.find_chunk_if([alignment, size](span<cpp::byte> chunk) {

libc/src/stdlib/CMakeLists.txt

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -392,8 +392,6 @@ if(NOT LIBC_TARGET_OS_IS_GPU)
392392
malloc.h
393393
DEPENDS
394394
libc.src.__support.freelist_heap
395-
COMPILE_OPTIONS
396-
-DLIBC_FREELIST_MALLOC_SIZE=${LIBC_CONF_FREELIST_MALLOC_BUFFER_SIZE}
397395
)
398396
get_target_property(freelist_malloc_is_skipped libc.src.stdlib.freelist_malloc "SKIPPED")
399397
if(LIBC_TARGET_OS_IS_BAREMETAL AND NOT freelist_malloc_is_skipped)

libc/src/stdlib/freelist_malloc.cpp

Lines changed: 2 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -18,17 +18,8 @@
1818

1919
namespace LIBC_NAMESPACE_DECL {
2020

21-
namespace {
22-
#ifdef LIBC_FREELIST_MALLOC_SIZE
23-
// This is set via the LIBC_CONF_FREELIST_MALLOC_BUFFER_SIZE configuration.
24-
constexpr size_t SIZE = LIBC_FREELIST_MALLOC_SIZE;
25-
#else
26-
#error "LIBC_FREELIST_MALLOC_SIZE was not defined for this build."
27-
#endif
28-
LIBC_CONSTINIT FreeListHeapBuffer<SIZE> freelist_heap_buffer;
29-
} // namespace
30-
31-
FreeListHeap<> *freelist_heap = &freelist_heap_buffer;
21+
static LIBC_CONSTINIT FreeListHeap<> freelist_heap_symbols;
22+
FreeListHeap<> *freelist_heap = &freelist_heap_symbols;
3223

3324
LLVM_LIBC_FUNCTION(void *, malloc, (size_t size)) {
3425
return freelist_heap->allocate(size);

libc/test/src/__support/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ if(LLVM_LIBC_FULL_BUILD)
3434
SUITE
3535
libc-support-tests
3636
SRCS
37+
fake_heap.s
3738
freelist_heap_test.cpp
3839
freelist_malloc_test.cpp
3940
DEPENDS

libc/test/src/__support/fake_heap.s

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
//===-- Test fake definition for heap symbols -----------------------------===//
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+
.globl _end, __llvm_libc_heap_limit
10+
11+
.bss
12+
_end:
13+
.fill 1024
14+
__llvm_libc_heap_limit:
15+

libc/test/src/__support/freelist_heap_test.cpp

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,14 +30,19 @@ using LIBC_NAMESPACE::freelist_heap;
3030
#define TEST_FOR_EACH_ALLOCATOR(TestCase, BufferSize) \
3131
class LlvmLibcFreeListHeapTest##TestCase : public testing::Test { \
3232
public: \
33+
FreeListHeapBuffer<BufferSize> fake_global_buffer; \
34+
void SetUp() override { \
35+
freelist_heap = \
36+
new (&fake_global_buffer) FreeListHeapBuffer<BufferSize>; \
37+
} \
3338
void RunTest(FreeListHeap<> &allocator, [[maybe_unused]] size_t N); \
3439
}; \
3540
TEST_F(LlvmLibcFreeListHeapTest##TestCase, TestCase) { \
3641
alignas(FreeListHeap<>::BlockType) \
3742
cpp::byte buf[BufferSize] = {cpp::byte(0)}; \
3843
FreeListHeap<> allocator(buf); \
3944
RunTest(allocator, BufferSize); \
40-
RunTest(*freelist_heap, freelist_heap->region_size()); \
45+
RunTest(*freelist_heap, freelist_heap->region().size()); \
4146
} \
4247
void LlvmLibcFreeListHeapTest##TestCase::RunTest(FreeListHeap<> &allocator, \
4348
size_t N)

libc/test/src/__support/freelist_malloc_test.cpp

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,15 +14,13 @@
1414
#include "test/UnitTest/Test.h"
1515

1616
using LIBC_NAMESPACE::freelist_heap;
17+
using LIBC_NAMESPACE::FreeListHeapBuffer;
1718

1819
TEST(LlvmLibcFreeListMalloc, MallocStats) {
1920
constexpr size_t kAllocSize = 256;
2021
constexpr size_t kCallocNum = 4;
2122
constexpr size_t kCallocSize = 64;
2223

23-
freelist_heap->reset_heap_stats(); // Do this because other tests might've
24-
// called the same global allocator.
25-
2624
void *ptr1 = LIBC_NAMESPACE::malloc(kAllocSize);
2725

2826
const auto &freelist_heap_stats = freelist_heap->heap_stats();

0 commit comments

Comments
 (0)