-
Notifications
You must be signed in to change notification settings - Fork 11.9k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[libc] Lazily initialize freelist malloc using symbols #99254
Conversation
8afd90d
to
802dfdb
Compare
✅ With the latest revision this PR passed the C/C++ code formatter. |
d643410
to
caf079f
Compare
4655096
to
57bfe5c
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM. Should probably get a libc owner to also approve.
@llvm/pr-subscribers-libc Author: Daniel Thornburgh (mysterymath) ChangesThis requires the user to set the upper bounds of the heap by defining the symbol 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, Full diff: https://github.com/llvm/llvm-project/pull/99254.diff 10 Files Affected:
diff --git a/libc/config/baremetal/config.json b/libc/config/baremetal/config.json
index 12f4c2aa3a805..f0ff268b56275 100644
--- a/libc/config/baremetal/config.json
+++ b/libc/config/baremetal/config.json
@@ -18,11 +18,6 @@
"value": false
}
},
- "malloc": {
- "LIBC_CONF_FREELIST_MALLOC_BUFFER_SIZE": {
- "value": 102400
- }
- },
"qsort": {
"LIBC_CONF_QSORT_IMPL": {
"value": "LIBC_QSORT_HEAP_SORT"
diff --git a/libc/config/config.json b/libc/config/config.json
index 2005f4297bfc1..178cedba6056a 100644
--- a/libc/config/config.json
+++ b/libc/config/config.json
@@ -71,12 +71,6 @@
"doc": "Default number of spins before blocking if a rwlock is in contention (default to 100)."
}
},
- "malloc": {
- "LIBC_CONF_FREELIST_MALLOC_BUFFER_SIZE": {
- "value": 1073741824,
- "doc": "Default size for the constinit freelist buffer used for the freelist malloc implementation (default 1o 1GB)."
- }
- },
"unistd": {
"LIBC_CONF_ENABLE_TID_CACHE": {
"value": true,
diff --git a/libc/docs/configure.rst b/libc/docs/configure.rst
index 5c55e4ab0f181..151ecd0d23a3e 100644
--- a/libc/docs/configure.rst
+++ b/libc/docs/configure.rst
@@ -30,8 +30,6 @@ to learn about the defaults for your platform and target.
- ``LIBC_CONF_KEEP_FRAME_POINTER``: Keep frame pointer in functions for better debugging experience.
* **"errno" options**
- ``LIBC_CONF_ERRNO_MODE``: The implementation used for errno, acceptable values are LIBC_ERRNO_MODE_UNDEFINED, LIBC_ERRNO_MODE_THREAD_LOCAL, LIBC_ERRNO_MODE_SHARED, LIBC_ERRNO_MODE_EXTERNAL, and LIBC_ERRNO_MODE_SYSTEM.
-* **"malloc" options**
- - ``LIBC_CONF_FREELIST_MALLOC_BUFFER_SIZE``: Default size for the constinit freelist buffer used for the freelist malloc implementation (default 1o 1GB).
* **"math" options**
- ``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.
* **"printf" options**
diff --git a/libc/src/__support/freelist_heap.h b/libc/src/__support/freelist_heap.h
index ce4f14b431585..1391bc8a07b03 100644
--- a/libc/src/__support/freelist_heap.h
+++ b/libc/src/__support/freelist_heap.h
@@ -22,6 +22,9 @@
namespace LIBC_NAMESPACE_DECL {
+extern "C" cpp::byte _end;
+extern "C" cpp::byte __llvm_libc_heap_limit;
+
using cpp::optional;
using cpp::span;
@@ -47,18 +50,10 @@ template <size_t NUM_BUCKETS = DEFAULT_BUCKETS.size()> class FreeListHeap {
size_t total_free_calls;
};
- FreeListHeap(span<cpp::byte> region)
- : FreeListHeap(&*region.begin(), &*region.end(), region.size()) {
- auto result = BlockType::init(region);
- BlockType *block = *result;
- freelist_.add_chunk(block_to_span(block));
- }
+ constexpr FreeListHeap() : begin_(&_end), end_(&__llvm_libc_heap_limit) {}
- constexpr FreeListHeap(void *start, cpp::byte *end, size_t total_bytes)
- : block_region_start_(start), block_region_end_(end),
- freelist_(DEFAULT_BUCKETS), heap_stats_{} {
- heap_stats_.total_bytes = total_bytes;
- }
+ constexpr FreeListHeap(span<cpp::byte> region)
+ : begin_(region.begin()), end_(region.end()) {}
void *allocate(size_t size);
void *aligned_allocate(size_t alignment, size_t size);
@@ -69,61 +64,57 @@ template <size_t NUM_BUCKETS = DEFAULT_BUCKETS.size()> class FreeListHeap {
void *calloc(size_t num, size_t size);
const HeapStats &heap_stats() const { return heap_stats_; }
- void reset_heap_stats() { heap_stats_ = {}; }
- void *region_start() const { return block_region_start_; }
- size_t region_size() const {
- return reinterpret_cast<uintptr_t>(block_region_end_) -
- reinterpret_cast<uintptr_t>(block_region_start_);
- }
+ cpp::span<cpp::byte> region() const { return {begin_, end_}; }
-protected:
- constexpr void set_freelist_node(typename FreeListType::FreeListNode &node,
- cpp::span<cpp::byte> chunk) {
- freelist_.set_freelist_node(node, chunk);
- }
+private:
+ void init();
void *allocate_impl(size_t alignment, size_t size);
-private:
span<cpp::byte> block_to_span(BlockType *block) {
return span<cpp::byte>(block->usable_space(), block->inner_size());
}
- bool is_valid_ptr(void *ptr) {
- return ptr >= block_region_start_ && ptr < block_region_end_;
- }
+ bool is_valid_ptr(void *ptr) { return ptr >= begin_ && ptr < end_; }
- void *block_region_start_;
- void *block_region_end_;
- FreeListType freelist_;
- HeapStats heap_stats_;
+ bool is_initialized_ = false;
+ cpp::byte *begin_;
+ cpp::byte *end_;
+ FreeListType freelist_{DEFAULT_BUCKETS};
+ HeapStats heap_stats_{};
};
template <size_t BUFF_SIZE, size_t NUM_BUCKETS = DEFAULT_BUCKETS.size()>
-struct FreeListHeapBuffer : public FreeListHeap<NUM_BUCKETS> {
+class FreeListHeapBuffer : public FreeListHeap<NUM_BUCKETS> {
using parent = FreeListHeap<NUM_BUCKETS>;
using FreeListNode = typename parent::FreeListType::FreeListNode;
+public:
constexpr FreeListHeapBuffer()
- : FreeListHeap<NUM_BUCKETS>(&block, buffer + sizeof(buffer), BUFF_SIZE),
- block(0, BUFF_SIZE), node{}, buffer{} {
- block.mark_last();
-
- cpp::span<cpp::byte> chunk(buffer, sizeof(buffer));
- parent::set_freelist_node(node, chunk);
- }
+ : FreeListHeap<NUM_BUCKETS>{buffer}, buffer{} {}
- typename parent::BlockType block;
- FreeListNode node;
- cpp::byte buffer[BUFF_SIZE - sizeof(block) - sizeof(node)];
+private:
+ cpp::byte buffer[BUFF_SIZE];
};
+template <size_t NUM_BUCKETS> void FreeListHeap<NUM_BUCKETS>::init() {
+ LIBC_ASSERT(!is_initialized_ && "duplicate initialization");
+ heap_stats_.total_bytes = region().size();
+ auto result = BlockType::init(region());
+ BlockType *block = *result;
+ freelist_.add_chunk(block_to_span(block));
+ is_initialized_ = true;
+}
+
template <size_t NUM_BUCKETS>
void *FreeListHeap<NUM_BUCKETS>::allocate_impl(size_t alignment, size_t size) {
if (size == 0)
return nullptr;
+ if (!is_initialized_)
+ init();
+
// Find a chunk in the freelist. Split it if needed, then return.
auto chunk =
freelist_.find_chunk_if([alignment, size](span<cpp::byte> chunk) {
diff --git a/libc/src/stdlib/CMakeLists.txt b/libc/src/stdlib/CMakeLists.txt
index 513f6ad723d56..52c1eb2606d08 100644
--- a/libc/src/stdlib/CMakeLists.txt
+++ b/libc/src/stdlib/CMakeLists.txt
@@ -385,8 +385,6 @@ if(NOT LIBC_TARGET_OS_IS_GPU)
malloc.h
DEPENDS
libc.src.__support.freelist_heap
- COMPILE_OPTIONS
- -DLIBC_FREELIST_MALLOC_SIZE=${LIBC_CONF_FREELIST_MALLOC_BUFFER_SIZE}
)
get_target_property(freelist_malloc_is_skipped libc.src.stdlib.freelist_malloc "SKIPPED")
if(LIBC_TARGET_OS_IS_BAREMETAL AND NOT freelist_malloc_is_skipped)
diff --git a/libc/src/stdlib/freelist_malloc.cpp b/libc/src/stdlib/freelist_malloc.cpp
index cfffa0425ff66..47240bc53aa37 100644
--- a/libc/src/stdlib/freelist_malloc.cpp
+++ b/libc/src/stdlib/freelist_malloc.cpp
@@ -18,17 +18,8 @@
namespace LIBC_NAMESPACE_DECL {
-namespace {
-#ifdef LIBC_FREELIST_MALLOC_SIZE
-// This is set via the LIBC_CONF_FREELIST_MALLOC_BUFFER_SIZE configuration.
-constexpr size_t SIZE = LIBC_FREELIST_MALLOC_SIZE;
-#else
-#error "LIBC_FREELIST_MALLOC_SIZE was not defined for this build."
-#endif
-LIBC_CONSTINIT FreeListHeapBuffer<SIZE> freelist_heap_buffer;
-} // namespace
-
-FreeListHeap<> *freelist_heap = &freelist_heap_buffer;
+static LIBC_CONSTINIT FreeListHeap<> freelist_heap_symbols;
+FreeListHeap<> *freelist_heap = &freelist_heap_symbols;
LLVM_LIBC_FUNCTION(void *, malloc, (size_t size)) {
return freelist_heap->allocate(size);
diff --git a/libc/test/src/__support/CMakeLists.txt b/libc/test/src/__support/CMakeLists.txt
index f994f65e08cbb..90de520405981 100644
--- a/libc/test/src/__support/CMakeLists.txt
+++ b/libc/test/src/__support/CMakeLists.txt
@@ -34,6 +34,7 @@ if(LLVM_LIBC_FULL_BUILD)
SUITE
libc-support-tests
SRCS
+ fake_heap.s
freelist_heap_test.cpp
freelist_malloc_test.cpp
DEPENDS
diff --git a/libc/test/src/__support/fake_heap.s b/libc/test/src/__support/fake_heap.s
new file mode 100644
index 0000000000000..69522f53c8b1f
--- /dev/null
+++ b/libc/test/src/__support/fake_heap.s
@@ -0,0 +1,15 @@
+//===-- Test fake definition for heap symbols -----------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+.globl _end, __llvm_libc_heap_limit
+
+.bss
+_end:
+.fill 1024
+__llvm_libc_heap_limit:
+
diff --git a/libc/test/src/__support/freelist_heap_test.cpp b/libc/test/src/__support/freelist_heap_test.cpp
index 5815d5dfc01ab..fc4348aed6b56 100644
--- a/libc/test/src/__support/freelist_heap_test.cpp
+++ b/libc/test/src/__support/freelist_heap_test.cpp
@@ -30,6 +30,11 @@ using LIBC_NAMESPACE::freelist_heap;
#define TEST_FOR_EACH_ALLOCATOR(TestCase, BufferSize) \
class LlvmLibcFreeListHeapTest##TestCase : public testing::Test { \
public: \
+ FreeListHeapBuffer<BufferSize> fake_global_buffer; \
+ void SetUp() override { \
+ freelist_heap = \
+ new (&fake_global_buffer) FreeListHeapBuffer<BufferSize>; \
+ } \
void RunTest(FreeListHeap<> &allocator, [[maybe_unused]] size_t N); \
}; \
TEST_F(LlvmLibcFreeListHeapTest##TestCase, TestCase) { \
@@ -37,7 +42,7 @@ using LIBC_NAMESPACE::freelist_heap;
cpp::byte buf[BufferSize] = {cpp::byte(0)}; \
FreeListHeap<> allocator(buf); \
RunTest(allocator, BufferSize); \
- RunTest(*freelist_heap, freelist_heap->region_size()); \
+ RunTest(*freelist_heap, freelist_heap->region().size()); \
} \
void LlvmLibcFreeListHeapTest##TestCase::RunTest(FreeListHeap<> &allocator, \
size_t N)
diff --git a/libc/test/src/__support/freelist_malloc_test.cpp b/libc/test/src/__support/freelist_malloc_test.cpp
index e9d7c63a4d438..66a923f778218 100644
--- a/libc/test/src/__support/freelist_malloc_test.cpp
+++ b/libc/test/src/__support/freelist_malloc_test.cpp
@@ -14,15 +14,13 @@
#include "test/UnitTest/Test.h"
using LIBC_NAMESPACE::freelist_heap;
+using LIBC_NAMESPACE::FreeListHeapBuffer;
TEST(LlvmLibcFreeListMalloc, MallocStats) {
constexpr size_t kAllocSize = 256;
constexpr size_t kCallocNum = 4;
constexpr size_t kCallocSize = 64;
- freelist_heap->reset_heap_stats(); // Do this because other tests might've
- // called the same global allocator.
-
void *ptr1 = LIBC_NAMESPACE::malloc(kAllocSize);
const auto &freelist_heap_stats = freelist_heap->heap_stats();
|
This allows the user to customize the size of the heap provided by overriding the weak symbol __libc_heap_limit at link time. The heap begins at _end and ends __libc_heap_limit bytes afterwards. It also prevents a completely unused heap from requiring any space. (It's reasonably common to link in malloc/free to handle exceptional situations that can never actually be encountered.) I'd think this should eventually be replaced with an implemenation based on sbrk(), with sbrk() implemented generically in terms of brk(), which would be a system call on e.g. Linux and a bump pointer from _end up to __libc_heap_limit on baremetal. This would allow the allocator to be more flexible (e.g., an implementation could swap out brk() and do dynamic memory availability checks), to manage the heap better by keeping better track of "wilderness" and to work properly as a malloc on Linux. Incidentally, this sets the default heap limit to 128KiB, from 102400 bytes, which had been reported as "1GiB".
- Move preprocessor check to assembly file - Add LIBC_CONSTINIT back to heap definition; make non-constexpr cast from &__heap_limit to size_t occur at init time to restore constexpr init.
This is more conventional and it dodges the issue of needing to provide a default size entirely. Setting up a baremetal heap intrinsically requires some linker script work, and __libc_heap_limit will be an undefined reference hinting to the user that this work must be done. Once we have a morecore function, a baremetal morecore() would use __libc_heap_limit, a Linux one would use sbrk(), and a test one would use a buffer.
- __libc_heap_limit -> __llvm_libc_heap_limit
df58810
to
300cdf8
Compare
@michaelrj-google @lntue, is there anything left to do on this one? |
no, I was just waiting for you to sync the PR to HEAD and the presubmit bot to finish. |
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