[libc-commits] [libc] [libc] add shrink in-place support for reallocations (PR #200272)
Schrodinger ZHU Yifan via libc-commits
libc-commits at lists.llvm.org
Thu May 28 13:59:56 PDT 2026
https://github.com/SchrodingerZhu updated https://github.com/llvm/llvm-project/pull/200272
>From 6c22212f9b88b6a0991def2bcde4b76fd95273f7 Mon Sep 17 00:00:00 2001
From: Yifan Zhu <yfzhu at google.com>
Date: Thu, 28 May 2026 13:55:52 -0700
Subject: [PATCH 1/2] [libc] add shrink in-place support for reallocations
---
libc/src/__support/block.h | 16 +++---
libc/src/__support/freelist_heap.h | 52 +++++++++++++++++--
libc/src/__support/freestore.h | 2 +
.../test/src/__support/freelist_heap_test.cpp | 3 +-
4 files changed, 62 insertions(+), 11 deletions(-)
diff --git a/libc/src/__support/block.h b/libc/src/__support/block.h
index d45af1079a5bc..399a4db21fa00 100644
--- a/libc/src/__support/block.h
+++ b/libc/src/__support/block.h
@@ -191,7 +191,8 @@ class Block {
/// field of the next block counts as part of the inner size of the block.
/// `usable_space_alignment` must be a multiple of MIN_ALIGN.
optional<Block *> split(size_t new_inner_size,
- size_t usable_space_alignment = MIN_ALIGN);
+ size_t usable_space_alignment = MIN_ALIGN,
+ size_t min_split_size = sizeof(Block));
/// Merges this block with the one that comes after it.
bool merge_next();
@@ -307,7 +308,8 @@ class Block {
// Divide a block into up to 3 blocks according to `BlockInfo`. Behavior is
// undefined if allocation is not possible for the given size and alignment.
- static BlockInfo allocate(Block *block, size_t alignment, size_t size);
+ static BlockInfo allocate(Block *block, size_t alignment, size_t size,
+ size_t min_split_size = sizeof(Block));
// These two functions may wrap around.
LIBC_INLINE static uintptr_t
@@ -393,7 +395,8 @@ optional<Block *> Block::init(ByteSpan region) {
}
LIBC_INLINE
-Block::BlockInfo Block::allocate(Block *block, size_t alignment, size_t size) {
+Block::BlockInfo Block::allocate(Block *block, size_t alignment, size_t size,
+ size_t min_split_size) {
LIBC_ASSERT(alignment % MIN_ALIGN == 0 &&
"alignment must be a multiple of MIN_ALIGN");
@@ -421,7 +424,7 @@ Block::BlockInfo Block::allocate(Block *block, size_t alignment, size_t size) {
}
// Now get a block for the requested size.
- if (optional<Block *> next = info.block->split(size))
+ if (optional<Block *> next = info.block->split(size, MIN_ALIGN, min_split_size))
info.next = *next;
return info;
@@ -429,7 +432,8 @@ Block::BlockInfo Block::allocate(Block *block, size_t alignment, size_t size) {
LIBC_INLINE
optional<Block *> Block::split(size_t new_inner_size,
- size_t usable_space_alignment) {
+ size_t usable_space_alignment,
+ size_t min_split_size) {
LIBC_ASSERT(usable_space_alignment % MIN_ALIGN == 0 &&
"alignment must be a multiple of MIN_ALIGN");
if (used())
@@ -449,7 +453,7 @@ optional<Block *> Block::split(size_t new_inner_size,
"new size must be aligned to MIN_ALIGN");
if (outer_size() < new_outer_size ||
- outer_size() - new_outer_size < sizeof(Block))
+ outer_size() - new_outer_size < min_split_size)
return {};
ByteSpan new_region = region().subspan(new_outer_size);
diff --git a/libc/src/__support/freelist_heap.h b/libc/src/__support/freelist_heap.h
index 66f3739efe214..02d35b1f8ad49 100644
--- a/libc/src/__support/freelist_heap.h
+++ b/libc/src/__support/freelist_heap.h
@@ -53,6 +53,8 @@ class FreeListHeap {
void *allocate_impl(size_t alignment, size_t size);
+ bool shrink_in_place(Block *block, size_t size);
+
span<cpp::byte> block_to_span(Block *block) {
return span<cpp::byte>(block->usable_space(), block->inner_size());
}
@@ -97,7 +99,8 @@ LIBC_INLINE void *FreeListHeap::allocate_impl(size_t alignment, size_t size) {
if (!block)
return nullptr;
- auto block_info = Block::allocate(block, alignment, size);
+ auto block_info =
+ Block::allocate(block, alignment, size, FreeStore::MIN_OUTER_SIZE);
if (block_info.next)
free_store.insert(block_info.next);
if (block_info.prev)
@@ -158,6 +161,47 @@ LIBC_INLINE void FreeListHeap::free(void *ptr) {
free_store.insert(block);
}
+LIBC_INLINE bool FreeListHeap::shrink_in_place(Block *block, size_t size) {
+ size_t min_outer_size = Block::outer_size(cpp::max(size, sizeof(size_t)));
+ uintptr_t next_block_start = Block::next_possible_block_start(
+ reinterpret_cast<uintptr_t>(block) + min_outer_size, Block::MIN_ALIGN);
+ size_t new_outer_size = next_block_start - reinterpret_cast<uintptr_t>(block);
+ // only split the block if the trailing part can be inserted into freelist
+ if (block->outer_size() >= new_outer_size &&
+ block->outer_size() - new_outer_size >= FreeStore::MIN_OUTER_SIZE) {
+ // We must temporarily mark the block as free to allow splitting.
+ // A block's usable space overlaps with the next block's prev_ field. When
+ // the next block is created via splitting, its header constructor writes to
+ // its prev_ field (initializing it to 0). Since the original block is
+ // currently in use, this write will corrupt the last sizeof(size_t) bytes
+ // of active user data. We back up these bytes here and restore them after
+ // the split is completed.
+ cpp::byte *overlap_ptr =
+ reinterpret_cast<cpp::byte *>(block) + new_outer_size;
+ size_t backup;
+ LIBC_NAMESPACE::inline_memcpy(&backup, overlap_ptr, sizeof(size_t));
+ block->mark_free();
+ optional<Block *> next =
+ block->split(size, Block::MIN_ALIGN, FreeStore::MIN_OUTER_SIZE);
+ block->mark_used();
+
+ LIBC_NAMESPACE::inline_memcpy(overlap_ptr, &backup, sizeof(size_t));
+
+ // register the new block on successful split
+ if (next.has_value()) {
+ Block *next_block = *next;
+ Block *right = next_block->next();
+ if (right != nullptr && !right->used()) {
+ free_store.remove(right);
+ next_block->merge_next();
+ }
+ free_store.insert(next_block);
+ }
+ return true;
+ }
+ return false;
+}
+
// Follows constract of the C standard realloc() function
// If ptr is free'd, will return nullptr.
LIBC_INLINE void *FreeListHeap::realloc(void *ptr, size_t size) {
@@ -180,10 +224,10 @@ LIBC_INLINE void *FreeListHeap::realloc(void *ptr, size_t size) {
return nullptr;
size_t old_size = block->inner_size();
- // Do nothing and return ptr if the required memory size is smaller than
- // the current size.
- if (old_size >= size)
+ if (old_size >= size) {
+ shrink_in_place(block, size);
return ptr;
+ }
void *new_ptr = allocate(size);
// Don't invalidate ptr if allocate(size) fails to initilize the memory.
diff --git a/libc/src/__support/freestore.h b/libc/src/__support/freestore.h
index 2dcb4b10b93d5..d611c17af4644 100644
--- a/libc/src/__support/freestore.h
+++ b/libc/src/__support/freestore.h
@@ -16,6 +16,8 @@ namespace LIBC_NAMESPACE_DECL {
/// A best-fit store of variously-sized free blocks. Blocks can be inserted and
/// removed in logarithmic time.
class FreeStore {
+ friend class FreeListHeap;
+
public:
FreeStore() = default;
FreeStore(const FreeStore &other) = delete;
diff --git a/libc/test/src/__support/freelist_heap_test.cpp b/libc/test/src/__support/freelist_heap_test.cpp
index 9d3a6b612555f..199408f39dac7 100644
--- a/libc/test/src/__support/freelist_heap_test.cpp
+++ b/libc/test/src/__support/freelist_heap_test.cpp
@@ -180,7 +180,8 @@ TEST_FOR_EACH_ALLOCATOR(ReallocSmallerSize, 2048) {
void *ptr1 = allocator.allocate(ALLOC_SIZE);
void *ptr2 = allocator.realloc(ptr1, kNewAllocSize);
- // For smaller sizes, realloc will not shrink the block.
+ // For smaller sizes, realloc will shrink the block in-place, returning the
+ // same pointer.
EXPECT_EQ(ptr1, ptr2);
}
>From c12967584f9f68fed12f3db0476d078b37d6152e Mon Sep 17 00:00:00 2001
From: Yifan Zhu <yfzhu at google.com>
Date: Thu, 28 May 2026 13:59:41 -0700
Subject: [PATCH 2/2] fix fmt
---
libc/src/__support/block.h | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/libc/src/__support/block.h b/libc/src/__support/block.h
index 399a4db21fa00..1da4fd804fee2 100644
--- a/libc/src/__support/block.h
+++ b/libc/src/__support/block.h
@@ -424,7 +424,8 @@ Block::BlockInfo Block::allocate(Block *block, size_t alignment, size_t size,
}
// Now get a block for the requested size.
- if (optional<Block *> next = info.block->split(size, MIN_ALIGN, min_split_size))
+ if (optional<Block *> next =
+ info.block->split(size, MIN_ALIGN, min_split_size))
info.next = *next;
return info;
More information about the libc-commits
mailing list