[libc-commits] [libc] [libc][hermetic] use mutex-protected freelist heap for hermetic test (PR #208587)
Schrodinger ZHU Yifan via libc-commits
libc-commits at lists.llvm.org
Thu Jul 16 13:54:54 PDT 2026
https://github.com/SchrodingerZhu updated https://github.com/llvm/llvm-project/pull/208587
>From 67d810ac873048132dc2dc83e26cf2740eb500b2 Mon Sep 17 00:00:00 2001
From: Yifan Zhu <yfzhu at google.com>
Date: Thu, 9 Jul 2026 14:55:16 -0700
Subject: [PATCH 1/3] [libc][freetrie] add dynamic expansion support
---
libc/src/__support/CMakeLists.txt | 1 +
libc/src/__support/freetrie.h | 26 ++++++++++++++
libc/test/src/__support/freetrie_test.cpp | 43 +++++++++++++++++++++++
3 files changed, 70 insertions(+)
diff --git a/libc/src/__support/CMakeLists.txt b/libc/src/__support/CMakeLists.txt
index f4cb283976bc3..94d9cc8baa6d1 100644
--- a/libc/src/__support/CMakeLists.txt
+++ b/libc/src/__support/CMakeLists.txt
@@ -49,6 +49,7 @@ add_object_library(
DEPENDS
.block
.freelist
+ libc.src.__support.math_extras
)
add_header_library(
diff --git a/libc/src/__support/freetrie.h b/libc/src/__support/freetrie.h
index 9e35463462b38..3dbb11eb3a063 100644
--- a/libc/src/__support/freetrie.h
+++ b/libc/src/__support/freetrie.h
@@ -15,6 +15,7 @@
#define LLVM_LIBC_SRC___SUPPORT_FREETRIE_H
#include "freelist.h"
+#include "src/__support/math_extras.h"
namespace LIBC_NAMESPACE_DECL {
@@ -126,6 +127,31 @@ LIBC_INLINE void FreeTrie::push(BlockRef block) {
LIBC_ASSERT(block.inner_size_free() >= sizeof(Node) &&
"block too small to accomodate free trie node");
size_t size = block.inner_size();
+
+ if (!range.contains(size)) {
+ if (empty()) {
+ range = SizeRange(
+ range.min,
+ cpp::max(range.width, cpp::bit_ceil(size - range.min + 1)));
+ } else {
+ // Dynamically expand the trie upwards by doubling the range and creating a
+ // new root node using the pushed block. The previous root becomes the
+ // lower child of the new root.
+ Node *node = new (block.usable_space()) Node;
+ node->parent = nullptr;
+ node->lower = root;
+ node->upper = nullptr;
+ FreeList list;
+ list.push(node);
+ root->parent = node;
+ root = node;
+ range = SizeRange(range.min, range.width * 2);
+ LIBC_ASSERT(range.contains(size) &&
+ "pushed block size exceeds dynamic trie expansion limit (at most 2x current range when non-empty)");
+ return;
+ }
+ }
+
LIBC_ASSERT(range.contains(size) && "requested size out of trie range");
// Find the position in the tree to push to.
diff --git a/libc/test/src/__support/freetrie_test.cpp b/libc/test/src/__support/freetrie_test.cpp
index bf3284b2faf64..9cc0f9edf583f 100644
--- a/libc/test/src/__support/freetrie_test.cpp
+++ b/libc/test/src/__support/freetrie_test.cpp
@@ -132,3 +132,46 @@ TEST(LlvmLibcFreeTrie, Remove) {
EXPECT_EQ(trie.find_best_fit(large.inner_size())->block().addr(),
large.addr());
}
+
+TEST(LlvmLibcFreeTrie, DynamicExpansion) {
+ byte mem[4096];
+ optional<BlockRef> maybe_block = BlockRef::init(mem);
+ ASSERT_TRUE(maybe_block.has_value());
+ BlockRef block1 = *maybe_block;
+ maybe_block = block1.split(256);
+ ASSERT_TRUE(maybe_block.has_value());
+ BlockRef block2 = *maybe_block;
+ maybe_block = block2.split(1500);
+ ASSERT_TRUE(maybe_block.has_value());
+
+ FreeTrie trie({0, 1024});
+ trie.push(block1);
+ EXPECT_EQ(trie.find_best_fit(block1.inner_size())->block().addr(),
+ block1.addr());
+
+ // Pushing block2 (inner_size >= 1500) exceeds the initial range of 1024,
+ // triggering dynamic trie expansion to 2048 and creating a new root.
+ trie.push(block2);
+ EXPECT_EQ(trie.find_best_fit(block2.inner_size())->block().addr(),
+ block2.addr());
+ EXPECT_EQ(trie.find_best_fit(block1.inner_size())->block().addr(),
+ block1.addr());
+
+ trie.remove(trie.find_best_fit(block2.inner_size()));
+ EXPECT_EQ(trie.find_best_fit(block1.inner_size())->block().addr(),
+ block1.addr());
+ trie.remove(trie.find_best_fit(block1.inner_size()));
+ EXPECT_TRUE(trie.empty());
+}
+
+TEST(LlvmLibcFreeTrie, DynamicExpansionFromEmpty) {
+ byte mem[4096];
+ optional<BlockRef> maybe_block = BlockRef::init(mem);
+ ASSERT_TRUE(maybe_block.has_value());
+ BlockRef block = *maybe_block;
+
+ FreeTrie trie; // Default constructed with {0, 0}
+ trie.push(block);
+ EXPECT_EQ(trie.find_best_fit(block.inner_size())->block().addr(),
+ block.addr());
+}
>From 3ff98a298036601935c1640fe67f10d8e9f2e483 Mon Sep 17 00:00:00 2001
From: Yifan Zhu <yfzhu at google.com>
Date: Thu, 9 Jul 2026 15:04:57 -0700
Subject: [PATCH 2/3] [libc][freelist-heap] add adopt API and mmap tests
---
libc/src/__support/freelist_heap.h | 14 +++++++++++
.../test/src/__support/freelist_heap_test.cpp | 25 +++++++++++++++++++
2 files changed, 39 insertions(+)
diff --git a/libc/src/__support/freelist_heap.h b/libc/src/__support/freelist_heap.h
index 73a80754050dd..a5b834e0a2f81 100644
--- a/libc/src/__support/freelist_heap.h
+++ b/libc/src/__support/freelist_heap.h
@@ -43,6 +43,8 @@ class FreeListHeap {
constexpr FreeListHeap(span<cpp::byte> region)
: begin(region.begin()), end(region.end()) {}
+ bool adopt(span<cpp::byte> mem);
+
void *allocate(size_t size);
void *aligned_allocate(size_t alignment, size_t size);
// NOTE: All pointers passed to free must come from one of the other
@@ -90,6 +92,18 @@ LIBC_INLINE void FreeListHeap::init() {
is_initialized = true;
}
+LIBC_INLINE bool FreeListHeap::adopt(span<cpp::byte> mem) {
+ if (!is_initialized)
+ init();
+ LIBC_ASSERT(end == mem.begin() && "Adopted region must be contiguous");
+ auto result = BlockRef::init(mem);
+ if (!result.has_value())
+ return false;
+ end = mem.end();
+ free_store.insert(*result);
+ return true;
+}
+
LIBC_INLINE void *FreeListHeap::allocate_impl(size_t alignment, size_t size) {
if (size == 0)
return nullptr;
diff --git a/libc/test/src/__support/freelist_heap_test.cpp b/libc/test/src/__support/freelist_heap_test.cpp
index 1ee6bf0ce4ab4..273671822b29b 100644
--- a/libc/test/src/__support/freelist_heap_test.cpp
+++ b/libc/test/src/__support/freelist_heap_test.cpp
@@ -366,3 +366,28 @@ TEST_FOR_EACH_ALLOCATOR(AllocationSize, 2048) {
allocator.free(ptr);
EXPECT_EQ(allocator.allocation_size(ptr), size_t(0));
}
+
+TEST(LlvmLibcFreeListHeap, Adopt) {
+ constexpr size_t N = 2048;
+ byte buf[N * 2] = {byte(0)};
+ span<byte> buf1(&buf[0], N);
+ span<byte> buf2(&buf[N], N);
+
+ FreeListHeap allocator(buf1);
+ void *ptr1 = allocator.allocate(1500);
+ EXPECT_NE(ptr1, static_cast<void *>(nullptr));
+
+ // Initial buffer is now almost full; allocating another 1500 bytes should fail.
+ void *ptr_fail = allocator.allocate(1500);
+ EXPECT_EQ(ptr_fail, static_cast<void *>(nullptr));
+
+ // Adopt the second buffer into the heap.
+ EXPECT_TRUE(allocator.adopt(buf2));
+
+ // Now allocating 1500 bytes should succeed from the adopted buffer.
+ void *ptr2 = allocator.allocate(1500);
+ EXPECT_NE(ptr2, static_cast<void *>(nullptr));
+
+ allocator.free(ptr1);
+ allocator.free(ptr2);
+}
>From 59a24f310755d834c98611a5c7722972ee64ee8b Mon Sep 17 00:00:00 2001
From: yfzhu <yfzhu at google.com>
Date: Thu, 9 Jul 2026 16:44:33 -0700
Subject: [PATCH 3/3] [libc][hermetic] use mutex-protected freelist heap for
hermetic test
TAG=agy
CONV=c9abb6ae-50f2-4602-87aa-fb0c972e80f0
---
libc/src/__support/CMakeLists.txt | 8 +-
libc/src/__support/freelist.cpp | 49 ------
libc/src/__support/freelist.h | 29 +++-
libc/src/__support/freetrie.cpp | 64 --------
libc/src/__support/freetrie.h | 51 ++++++
libc/test/UnitTest/CMakeLists.txt | 10 ++
libc/test/UnitTest/HermeticHeap.cpp | 30 ++++
libc/test/UnitTest/HermeticTestUtils.cpp | 44 +-----
libc/test/UnitTest/sbrk_heap.h | 173 +++++++++++++++++++++
libc/test/src/__support/CMakeLists.txt | 15 ++
libc/test/src/__support/sbrk_heap_test.cpp | 56 +++++++
11 files changed, 367 insertions(+), 162 deletions(-)
delete mode 100644 libc/src/__support/freelist.cpp
delete mode 100644 libc/src/__support/freetrie.cpp
create mode 100644 libc/test/UnitTest/HermeticHeap.cpp
create mode 100644 libc/test/UnitTest/sbrk_heap.h
create mode 100644 libc/test/src/__support/sbrk_heap_test.cpp
diff --git a/libc/src/__support/CMakeLists.txt b/libc/src/__support/CMakeLists.txt
index 94d9cc8baa6d1..8b623ee11d970 100644
--- a/libc/src/__support/CMakeLists.txt
+++ b/libc/src/__support/CMakeLists.txt
@@ -25,12 +25,10 @@ add_header_library(
libc.src.__support.math_extras
)
-add_object_library(
+add_header_library(
freelist
HDRS
freelist.h
- SRCS
- freelist.cpp
DEPENDS
.block
libc.src.__support.fixedvector
@@ -40,12 +38,10 @@ add_object_library(
libc.src.__support.CPP.span
)
-add_object_library(
+add_header_library(
freetrie
HDRS
freetrie.h
- SRCS
- freetrie.cpp
DEPENDS
.block
.freelist
diff --git a/libc/src/__support/freelist.cpp b/libc/src/__support/freelist.cpp
deleted file mode 100644
index 2fdcaadbdb554..0000000000000
--- a/libc/src/__support/freelist.cpp
+++ /dev/null
@@ -1,49 +0,0 @@
-//===----------------------------------------------------------------------===//
-//
-// 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
-//
-//===----------------------------------------------------------------------===//
-///
-/// \file
-/// Implementation for freelist.
-///
-//===----------------------------------------------------------------------===//
-
-#include "freelist.h"
-
-namespace LIBC_NAMESPACE_DECL {
-
-void FreeList::push(Node *node) {
- if (begin_) {
- LIBC_ASSERT(BlockRef::from_usable_space(node).outer_size() ==
- begin_->block().outer_size() &&
- "freelist entries must have the same size");
- // Since the list is circular, insert the node immediately before begin_.
- node->prev = begin_->prev;
- node->next = begin_;
- begin_->prev->next = node;
- begin_->prev = node;
- } else {
- begin_ = node->prev = node->next = node;
- }
-}
-
-void FreeList::remove(Node *node) {
- LIBC_ASSERT(begin_ && "cannot remove from empty list");
- Node *next = node->next;
- if (node == next) {
- LIBC_ASSERT(node == begin_ &&
- "a self-referential node must be the only element");
- begin_ = nullptr;
- } else {
- Node *prev = node->prev;
- prev->next = next;
- next->prev = prev;
- if (begin_ == node)
- begin_ = next;
- }
-}
-
-} // namespace LIBC_NAMESPACE_DECL
diff --git a/libc/src/__support/freelist.h b/libc/src/__support/freelist.h
index 48e70c7c29df6..b61da261c1ec3 100644
--- a/libc/src/__support/freelist.h
+++ b/libc/src/__support/freelist.h
@@ -77,13 +77,38 @@ class FreeList {
/// Push an already-constructed node to the back of the list.
/// This allows pushing derived node types with additional data.
- void push(Node *node);
+ LIBC_INLINE void push(Node *node) {
+ if (begin_) {
+ LIBC_ASSERT(BlockRef::from_usable_space(node).outer_size() ==
+ begin_->block().outer_size() &&
+ "freelist entries must have the same size");
+ // Since the list is circular, insert the node immediately before begin_.
+ node->prev = begin_->prev;
+ node->next = begin_;
+ begin_->prev->next = node;
+ begin_->prev = node;
+ } else {
+ begin_ = node->prev = node->next = node;
+ }
+ }
/// Pop the first node from the list.
LIBC_INLINE void pop() { remove(begin_); }
/// Remove an arbitrary node from the list.
- void remove(Node *node);
+ LIBC_INLINE void remove(Node *node) {
+ LIBC_ASSERT(begin_ && "cannot remove from empty list");
+ if (node == node->next) {
+ LIBC_ASSERT(node == begin_ &&
+ "a self-referential node must be the only element");
+ begin_ = nullptr;
+ } else {
+ node->prev->next = node->next;
+ node->next->prev = node->prev;
+ if (begin_ == node)
+ begin_ = node->next;
+ }
+ }
private:
Node *begin_;
diff --git a/libc/src/__support/freetrie.cpp b/libc/src/__support/freetrie.cpp
deleted file mode 100644
index e76efe717f215..0000000000000
--- a/libc/src/__support/freetrie.cpp
+++ /dev/null
@@ -1,64 +0,0 @@
-//===-- Implementation for freetrie ---------------------------------------===//
-//
-// 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
-//
-//===----------------------------------------------------------------------===//
-
-#include "freetrie.h"
-
-namespace LIBC_NAMESPACE_DECL {
-
-void FreeTrie::remove(Node *node) {
- LIBC_ASSERT(!empty() && "cannot remove from empty trie");
- FreeList list = node;
- list.pop();
- Node *new_node = static_cast<Node *>(list.begin());
- if (!new_node) {
- // The freelist is empty. Replace the subtrie root with an arbitrary leaf.
- // This is legal because there is no relationship between the size of the
- // root and its children.
- Node *leaf = node;
- while (leaf->lower || leaf->upper)
- leaf = leaf->lower ? leaf->lower : leaf->upper;
- if (leaf == node) {
- // If the root is a leaf, then removing it empties the subtrie.
- replace_node(node, nullptr);
- return;
- }
-
- replace_node(leaf, nullptr);
- new_node = leaf;
- }
-
- if (!is_head(node))
- return;
-
- // Copy the trie links to the new head.
- new_node->lower = node->lower;
- new_node->upper = node->upper;
- new_node->parent = node->parent;
- replace_node(node, new_node);
-}
-
-void FreeTrie::replace_node(Node *node, Node *new_node) {
- LIBC_ASSERT(is_head(node) && "only head nodes contain trie links");
-
- if (node->parent) {
- Node *&parent_child =
- node->parent->lower == node ? node->parent->lower : node->parent->upper;
- LIBC_ASSERT(parent_child == node &&
- "no reference to child node found in parent");
- parent_child = new_node;
- } else {
- LIBC_ASSERT(root == node && "non-root node had no parent");
- root = new_node;
- }
- if (node->lower)
- node->lower->parent = new_node;
- if (node->upper)
- node->upper->parent = new_node;
-}
-
-} // namespace LIBC_NAMESPACE_DECL
diff --git a/libc/src/__support/freetrie.h b/libc/src/__support/freetrie.h
index 3dbb11eb3a063..78e1940f08769 100644
--- a/libc/src/__support/freetrie.h
+++ b/libc/src/__support/freetrie.h
@@ -263,6 +263,57 @@ LIBC_INLINE FreeTrie::Node *FreeTrie::find_best_fit(size_t size) {
}
}
+LIBC_INLINE void FreeTrie::remove(Node *node) {
+ LIBC_ASSERT(!empty() && "cannot remove from empty trie");
+ FreeList list = node;
+ list.pop();
+ Node *new_node = static_cast<Node *>(list.begin());
+ if (!new_node) {
+ // The freelist is empty. Replace the subtrie root with an arbitrary leaf.
+ // This is legal because there is no relationship between the size of the
+ // root and its children.
+ Node *leaf = node;
+ while (leaf->lower || leaf->upper)
+ leaf = leaf->lower ? leaf->lower : leaf->upper;
+ if (leaf == node) {
+ // If the root is a leaf, then removing it empties the subtrie.
+ replace_node(node, nullptr);
+ return;
+ }
+
+ replace_node(leaf, nullptr);
+ new_node = leaf;
+ }
+
+ if (!is_head(node))
+ return;
+
+ // Copy the trie links to the new head.
+ new_node->lower = node->lower;
+ new_node->upper = node->upper;
+ new_node->parent = node->parent;
+ replace_node(node, new_node);
+}
+
+LIBC_INLINE void FreeTrie::replace_node(Node *node, Node *new_node) {
+ LIBC_ASSERT(is_head(node) && "only head nodes contain trie links");
+
+ if (node->parent) {
+ Node *&parent_child =
+ node->parent->lower == node ? node->parent->lower : node->parent->upper;
+ LIBC_ASSERT(parent_child == node &&
+ "no reference to child node found in parent");
+ parent_child = new_node;
+ } else {
+ LIBC_ASSERT(root == node && "non-root node had no parent");
+ root = new_node;
+ }
+ if (node->lower)
+ node->lower->parent = new_node;
+ if (node->upper)
+ node->upper->parent = new_node;
+}
+
} // namespace LIBC_NAMESPACE_DECL
#endif // LLVM_LIBC_SRC___SUPPORT_FREETRIE_H
diff --git a/libc/test/UnitTest/CMakeLists.txt b/libc/test/UnitTest/CMakeLists.txt
index 4a47597ae3a45..52d8ffa99d70b 100644
--- a/libc/test/UnitTest/CMakeLists.txt
+++ b/libc/test/UnitTest/CMakeLists.txt
@@ -114,9 +114,19 @@ add_unittest_framework_library(
add_unittest_framework_library(
LibcHermeticTestSupport
SRCS
+ HermeticHeap.cpp
HermeticTestUtils.cpp
DEPENDS
libc.hdr.stdint_proxy
+ libc.src.__support.block
+ libc.src.__support.freelist_heap
+ libc.src.__support.CPP.mutex
+ libc.src.__support.CPP.new
+ libc.src.__support.CPP.span
+ libc.src.__support.OSUtil.osutil
+ libc.src.__support.math_extras
+ libc.src.__support.threads.raw_mutex
+ libc.src.string.memory_utils.inline_memset
)
add_header_library(
diff --git a/libc/test/UnitTest/HermeticHeap.cpp b/libc/test/UnitTest/HermeticHeap.cpp
new file mode 100644
index 0000000000000..f62eac5881b82
--- /dev/null
+++ b/libc/test/UnitTest/HermeticHeap.cpp
@@ -0,0 +1,30 @@
+//===-- Implementation of hermetic heap functions -------------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+
+#include "src/__support/common.h"
+#include "src/__support/macros/config.h"
+#include "test/UnitTest/sbrk_heap.h"
+#include <stddef.h>
+
+namespace LIBC_NAMESPACE_DECL {
+static SbrkHeap hermetic_heap(4096);
+} // namespace LIBC_NAMESPACE_DECL
+
+extern "C" {
+
+void *malloc(size_t s) noexcept {
+ return LIBC_NAMESPACE::hermetic_heap.allocate(s);
+}
+
+void free(void *ptr) noexcept { LIBC_NAMESPACE::hermetic_heap.free(ptr); }
+
+void *realloc(void *mem, size_t s) noexcept {
+ return LIBC_NAMESPACE::hermetic_heap.realloc(mem, s);
+}
+
+} // extern "C"
diff --git a/libc/test/UnitTest/HermeticTestUtils.cpp b/libc/test/UnitTest/HermeticTestUtils.cpp
index 3c82d1e963643..1e3e03be5c25c 100644
--- a/libc/test/UnitTest/HermeticTestUtils.cpp
+++ b/libc/test/UnitTest/HermeticTestUtils.cpp
@@ -35,22 +35,6 @@ int atexit(void (*func)(void));
} // namespace LIBC_NAMESPACE_DECL
-constexpr uint64_t ALIGNMENT = alignof(uintptr_t);
-
-namespace {
-
-// Integration tests cannot use the SCUDO standalone allocator as SCUDO pulls
-// various other parts of the libc. Since SCUDO development does not use
-// LLVM libc build rules, it is very hard to keep track or pull all that SCUDO
-// requires. Hence, as a work around for this problem, we use a simple allocator
-// which just hands out continuous blocks from a statically allocated chunk of
-// memory.
-static constexpr uint64_t MEMORY_SIZE = 65336;
-alignas(ALIGNMENT) static uint8_t memory[MEMORY_SIZE];
-static uint8_t *ptr = memory;
-
-} // anonymous namespace
-
extern "C" {
// Hermetic tests rely on the following memory functions. This is because the
@@ -78,31 +62,9 @@ void *memset(void *ptr, int value, size_t count) {
// This is needed if the test was compiled with '-fno-use-cxa-atexit'.
int atexit(void (*func)(void)) { return LIBC_NAMESPACE::atexit(func); }
-void *malloc(size_t s) {
- // Keep the bump pointer aligned on an eight byte boundary.
- s = ((s + ALIGNMENT - 1) / ALIGNMENT) * ALIGNMENT;
- void *mem = ptr;
- ptr += s;
- return static_cast<uint64_t>(ptr - memory) >= MEMORY_SIZE ? nullptr : mem;
-}
-
-void free(void *) {}
-
-void *realloc(void *mem, size_t s) {
- if (mem == nullptr)
- return malloc(s);
- uint8_t *newmem = reinterpret_cast<uint8_t *>(malloc(s));
- if (newmem == nullptr)
- return nullptr;
- uint8_t *oldmem = reinterpret_cast<uint8_t *>(mem);
- // We use a simple for loop to copy the data over.
- // If |s| is less the previous alloc size, the copy works as expected.
- // If |s| is greater than the previous alloc size, then garbage is copied
- // over to the additional part in the new memory block.
- for (size_t i = 0; i < s; ++i)
- newmem[i] = oldmem[i];
- return newmem;
-}
+void *malloc(size_t s) noexcept;
+void free(void *ptr) noexcept;
+void *realloc(void *mem, size_t s) noexcept;
// The unit test framework uses pure virtual functions. Since hermetic tests
// cannot depend C++ runtime libraries, implement dummy functions to support
diff --git a/libc/test/UnitTest/sbrk_heap.h b/libc/test/UnitTest/sbrk_heap.h
new file mode 100644
index 0000000000000..9310fe3e2f1f5
--- /dev/null
+++ b/libc/test/UnitTest/sbrk_heap.h
@@ -0,0 +1,173 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+///
+/// \file
+/// A RawMutex protected FreeListHeap backed by sbrk.
+///
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_LIBC_SRC___SUPPORT_SBRK_HEAP_H
+#define LLVM_LIBC_SRC___SUPPORT_SBRK_HEAP_H
+
+#include <stddef.h>
+
+#include "src/__support/CPP/mutex.h"
+#include "src/__support/CPP/new.h"
+#include "src/__support/CPP/span.h"
+#include "src/__support/block.h"
+#include "src/__support/freelist_heap.h"
+#include "src/__support/macros/attributes.h"
+#include "src/__support/macros/config.h"
+#include "src/__support/macros/properties/os.h"
+#include "src/__support/math_extras.h"
+#include "src/__support/threads/raw_mutex.h"
+#include "src/string/memory_utils/inline_memset.h"
+
+#if defined(LIBC_TARGET_OS_IS_LINUX)
+#include "src/__support/OSUtil/linux/syscall.h" // syscall_impl
+#include <sys/syscall.h> // SYS_brk
+#endif
+
+namespace LIBC_NAMESPACE_DECL {
+
+class SbrkHeap {
+public:
+ LIBC_INLINE constexpr SbrkHeap(size_t initial_size = 4096)
+ : break_ptr(nullptr), current_heap_size(initial_size), heap_storage{},
+ mtx() {}
+
+ LIBC_INLINE void *allocate(size_t size) {
+ cpp::lock_guard lock(mtx);
+ if (LIBC_UNLIKELY(break_ptr == nullptr))
+ if (!grow_heap())
+ return nullptr;
+ void *ptr = heap()->allocate(size);
+ while (ptr == nullptr) {
+ if (!grow_heap())
+ return nullptr;
+ ptr = heap()->allocate(size);
+ }
+ return ptr;
+ }
+
+ LIBC_INLINE void *aligned_allocate(size_t alignment, size_t size) {
+ cpp::lock_guard lock(mtx);
+ if (LIBC_UNLIKELY(break_ptr == nullptr)) {
+ if (!grow_heap())
+ return nullptr;
+ }
+ void *ptr = heap()->aligned_allocate(alignment, size);
+ while (ptr == nullptr) {
+ if (!grow_heap())
+ return nullptr;
+ ptr = heap()->aligned_allocate(alignment, size);
+ }
+ return ptr;
+ }
+
+ LIBC_INLINE void free(void *ptr) {
+ cpp::lock_guard lock(mtx);
+ if (LIBC_LIKELY(break_ptr != nullptr))
+ heap()->free(ptr);
+ }
+
+ LIBC_INLINE void *realloc(void *ptr, size_t size) {
+ cpp::lock_guard lock(mtx);
+ if (LIBC_UNLIKELY(break_ptr == nullptr))
+ if (!grow_heap())
+ return nullptr;
+
+ void *new_ptr = heap()->realloc(ptr, size);
+ while (new_ptr == nullptr && size != 0) {
+ if (!grow_heap())
+ return nullptr;
+ new_ptr = heap()->realloc(ptr, size);
+ }
+ return new_ptr;
+ }
+
+ LIBC_INLINE void *calloc(size_t num, size_t size) {
+ size_t bytes;
+ if (__builtin_mul_overflow(num, size, &bytes))
+ return nullptr;
+ void *ptr = allocate(bytes);
+ if (ptr != nullptr)
+ LIBC_NAMESPACE::inline_memset(ptr, 0, bytes);
+ return ptr;
+ }
+
+ LIBC_INLINE span<cpp::byte> region() const {
+ if (break_ptr == nullptr)
+ return {};
+ return heap()->region();
+ }
+
+private:
+ LIBC_INLINE FreeListHeap *heap() const {
+ return reinterpret_cast<FreeListHeap *>(
+ const_cast<cpp::byte *>(heap_storage));
+ }
+
+ LIBC_INLINE cpp::byte *sbrk(ptrdiff_t increment) {
+#if defined(SYS_brk) && defined(LIBC_TARGET_OS_IS_LINUX)
+ long curr_brk = syscall_impl<long>(SYS_brk, 0);
+ if (curr_brk < 0)
+ return nullptr;
+ if (increment == 0)
+ return reinterpret_cast<cpp::byte *>(curr_brk);
+ long new_brk = syscall_impl<long>(SYS_brk, curr_brk + increment);
+ if (new_brk != curr_brk + increment)
+ return nullptr;
+ return reinterpret_cast<cpp::byte *>(curr_brk);
+#else
+ static constexpr size_t VIRTUAL_HEAP_SIZE = 131072;
+ alignas(FreeListHeap) static cpp::byte virtual_heap[VIRTUAL_HEAP_SIZE];
+ static cpp::byte *virtual_brk = virtual_heap;
+ if (static_cast<size_t>(increment) >
+ VIRTUAL_HEAP_SIZE - static_cast<size_t>(virtual_brk - virtual_heap))
+ return nullptr;
+ cpp::byte *old_brk = virtual_brk;
+ virtual_brk += increment;
+ return old_brk;
+#endif
+ }
+
+ LIBC_INLINE bool grow_heap() {
+ bool first_time = (break_ptr == nullptr);
+ if (first_time) {
+ break_ptr = sbrk(0);
+ if (break_ptr == nullptr)
+ return false;
+ }
+
+ size_t increment = current_heap_size;
+ cpp::byte *new_brk = sbrk(increment);
+ if (new_brk == nullptr || new_brk != break_ptr)
+ return false;
+
+ span<cpp::byte> new_mem(break_ptr, increment);
+ if (first_time)
+ new (heap_storage) FreeListHeap(new_mem);
+ else if (!heap()->adopt(new_mem))
+ return false;
+
+ break_ptr += increment;
+ if (break_ptr != nullptr)
+ current_heap_size += increment;
+ return true;
+ }
+
+ cpp::byte *break_ptr;
+ size_t current_heap_size;
+ alignas(FreeListHeap) cpp::byte heap_storage[sizeof(FreeListHeap)];
+ RawMutex mtx;
+};
+
+} // namespace LIBC_NAMESPACE_DECL
+
+#endif // LLVM_LIBC_SRC___SUPPORT_SBRK_HEAP_H
diff --git a/libc/test/src/__support/CMakeLists.txt b/libc/test/src/__support/CMakeLists.txt
index 8233529266326..60214fbf5eae1 100644
--- a/libc/test/src/__support/CMakeLists.txt
+++ b/libc/test/src/__support/CMakeLists.txt
@@ -71,6 +71,21 @@ if(LLVM_LIBC_FULL_BUILD AND NOT LIBC_TARGET_OS_IS_GPU)
libc.src.string.memcpy
libc.src.string.memory_utils.inline_memset
)
+ add_libc_test(
+ sbrk_heap_test
+ HERMETIC_TEST_ONLY
+ SUITE
+ libc-support-tests
+ SRCS
+ sbrk_heap_test.cpp
+ DEPENDS
+ libc.src.__support.CPP.span
+ libc.src.__support.freelist_heap
+ libc.src.__support.block
+ libc.src.__support.CPP.mutex
+ libc.src.__support.threads.raw_mutex
+ libc.src.string.memory_utils.inline_memset
+ )
endif()
add_libc_test(
diff --git a/libc/test/src/__support/sbrk_heap_test.cpp b/libc/test/src/__support/sbrk_heap_test.cpp
new file mode 100644
index 0000000000000..a22f74b8b120f
--- /dev/null
+++ b/libc/test/src/__support/sbrk_heap_test.cpp
@@ -0,0 +1,56 @@
+//===-- Unittests for SbrkHeap --------------------------------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+
+#include "test/UnitTest/sbrk_heap.h"
+#include "test/UnitTest/Test.h"
+
+asm(R"(
+.globl _end, __llvm_libc_heap_limit
+
+.bss
+_end:
+ .fill 1024
+__llvm_libc_heap_limit:
+)");
+
+TEST(LlvmLibcSbrkHeapTest, BasicAllocationAndDoubling) {
+ // Start with a very small initial heap (e.g. 512 bytes)
+ LIBC_NAMESPACE::SbrkHeap heap(512);
+
+ // Allocate 300 bytes; this should fit in the initial 512-byte heap.
+ void *ptr1 = heap.allocate(300);
+ EXPECT_NE(ptr1, static_cast<void *>(nullptr));
+
+ // Allocate another 400 bytes; this exceeds the initial 512-byte heap,
+ // triggering SYS_brk growth (doubling the heap by adopting another 512 bytes).
+ void *ptr2 = heap.allocate(400);
+ EXPECT_NE(ptr2, static_cast<void *>(nullptr));
+
+ // Allocate a larger block (2048 bytes), triggering multiple doublings via SYS_brk.
+ void *ptr3 = heap.allocate(2048);
+ EXPECT_NE(ptr3, static_cast<void *>(nullptr));
+
+ heap.free(ptr1);
+ heap.free(ptr2);
+ heap.free(ptr3);
+}
+
+TEST(LlvmLibcSbrkHeapTest, ReallocAndCalloc) {
+ LIBC_NAMESPACE::SbrkHeap heap(1024);
+
+ void *ptr = heap.calloc(10, 100); // 1000 bytes
+ ASSERT_NE(ptr, static_cast<void *>(nullptr));
+ for (int i = 0; i < 1000; ++i)
+ EXPECT_EQ(static_cast<char *>(ptr)[i], char(0));
+
+ // Realloc to a larger size that requires growing the heap via SYS_brk.
+ void *new_ptr = heap.realloc(ptr, 3000);
+ ASSERT_NE(new_ptr, static_cast<void *>(nullptr));
+
+ heap.free(new_ptr);
+}
More information about the libc-commits
mailing list