[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
Tue Jun 2 12:00:33 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/7] [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/7] 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;

>From 49a59a3c6076bfcd23c2fbb73968221e67a3067f Mon Sep 17 00:00:00 2001
From: Yifan Zhu <yfzhu at google.com>
Date: Thu, 28 May 2026 15:04:35 -0700
Subject: [PATCH 3/7] [libc] add a unit test for shrinking

---
 libc/test/src/__support/CMakeLists.txt        |  1 +
 .../test/src/__support/freelist_heap_test.cpp | 43 +++++++++++++++++++
 2 files changed, 44 insertions(+)

diff --git a/libc/test/src/__support/CMakeLists.txt b/libc/test/src/__support/CMakeLists.txt
index 6b9c1b4ac8cc7..ea271513beffb 100644
--- a/libc/test/src/__support/CMakeLists.txt
+++ b/libc/test/src/__support/CMakeLists.txt
@@ -69,6 +69,7 @@ if(LLVM_LIBC_FULL_BUILD AND NOT LIBC_TARGET_OS_IS_GPU)
       libc.src.__support.freelist_heap
       libc.src.string.memcmp
       libc.src.string.memcpy
+      libc.src.string.memory_utils.inline_memset
   )
 endif()
 
diff --git a/libc/test/src/__support/freelist_heap_test.cpp b/libc/test/src/__support/freelist_heap_test.cpp
index 199408f39dac7..f25a62b056c76 100644
--- a/libc/test/src/__support/freelist_heap_test.cpp
+++ b/libc/test/src/__support/freelist_heap_test.cpp
@@ -11,6 +11,7 @@
 #include "src/__support/macros/config.h"
 #include "src/string/memcmp.h"
 #include "src/string/memcpy.h"
+#include "src/string/memory_utils/inline_memset.h"
 #include "test/UnitTest/Test.h"
 
 asm(R"(
@@ -185,6 +186,48 @@ TEST_FOR_EACH_ALLOCATOR(ReallocSmallerSize, 2048) {
   EXPECT_EQ(ptr1, ptr2);
 }
 
+TEST_FOR_EACH_ALLOCATOR(ReallocShrinkAndAllocateTrailing, 512) {
+  constexpr size_t ALLOC_SIZE = 300;
+  constexpr size_t NEW_ALLOC_SIZE = 64;
+  constexpr size_t TRAILING_ALLOC_SIZE = 256;
+
+  void *ptr1 = allocator.allocate(ALLOC_SIZE);
+  ASSERT_NE(ptr1, static_cast<void *>(nullptr));
+
+  // Fill the block with a pattern to verify data preservation.
+  LIBC_NAMESPACE::inline_memset(ptr1, 0x5A, ALLOC_SIZE);
+
+  // Shrink the block in-place.
+  void *ptr2 = allocator.realloc(ptr1, NEW_ALLOC_SIZE);
+  ASSERT_NE(ptr2, static_cast<void *>(nullptr));
+  EXPECT_EQ(ptr1, ptr2);
+
+  // Verify that the data in the shrunk block is preserved.
+  unsigned char *uptr2 = static_cast<unsigned char *>(ptr2);
+  for (size_t i = 0; i < NEW_ALLOC_SIZE; ++i)
+    EXPECT_EQ(uptr2[i], static_cast<unsigned char>(0x5A));
+
+  // Allocate another trailing block from the newly split/freed space.
+  void *ptr3 = allocator.allocate(TRAILING_ALLOC_SIZE);
+  ASSERT_NE(ptr3, static_cast<void *>(nullptr));
+
+  // Verify that the trailing block does not overlap with the shrunk block.
+  uintptr_t addr2 = reinterpret_cast<uintptr_t>(ptr2);
+  uintptr_t addr3 = reinterpret_cast<uintptr_t>(ptr3);
+  EXPECT_GE(addr3, addr2 + NEW_ALLOC_SIZE);
+
+  // Verify that writing to the trailing block works without corrupting the
+  // shrunk block.
+  LIBC_NAMESPACE::inline_memset(ptr3, 0xAA, TRAILING_ALLOC_SIZE);
+
+  for (size_t i = 0; i < NEW_ALLOC_SIZE; ++i)
+    EXPECT_EQ(uptr2[i], static_cast<unsigned char>(0x5A));
+
+  // Free the allocations.
+  allocator.free(ptr2);
+  allocator.free(ptr3);
+}
+
 TEST_FOR_EACH_ALLOCATOR(ReallocTooLarge, 2048) {
   constexpr size_t ALLOC_SIZE = 512;
   size_t kNewAllocSize = N * 2; // Large enough to fail.

>From 7c27e1160c21420bd8ba3ef82aa2002101fde7a1 Mon Sep 17 00:00:00 2001
From: Yifan Zhu <yfzhu at google.com>
Date: Fri, 29 May 2026 09:08:57 -0700
Subject: [PATCH 4/7] address code review comments

---
 libc/src/__support/block.h                    | 25 ++++++++-----------
 libc/src/__support/freelist_heap.h            |  6 ++---
 libc/test/src/__support/block_test.cpp        | 10 ++++++--
 .../test/src/__support/freelist_heap_test.cpp | 11 ++++----
 4 files changed, 27 insertions(+), 25 deletions(-)

diff --git a/libc/src/__support/block.h b/libc/src/__support/block.h
index 1da4fd804fee2..86befc0995dd8 100644
--- a/libc/src/__support/block.h
+++ b/libc/src/__support/block.h
@@ -191,8 +191,7 @@ 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 min_split_size = sizeof(Block));
+                          size_t usable_space_alignment = MIN_ALIGN);
 
   /// Merges this block with the one that comes after it.
   bool merge_next();
@@ -308,8 +307,7 @@ 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,
-                            size_t min_split_size = sizeof(Block));
+  static BlockInfo allocate(Block *block, size_t alignment, size_t size);
 
   // These two functions may wrap around.
   LIBC_INLINE static uintptr_t
@@ -395,8 +393,7 @@ optional<Block *> Block::init(ByteSpan region) {
 }
 
 LIBC_INLINE
-Block::BlockInfo Block::allocate(Block *block, size_t alignment, size_t size,
-                                 size_t min_split_size) {
+Block::BlockInfo Block::allocate(Block *block, size_t alignment, size_t size) {
   LIBC_ASSERT(alignment % MIN_ALIGN == 0 &&
               "alignment must be a multiple of MIN_ALIGN");
 
@@ -425,7 +422,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, MIN_ALIGN, min_split_size))
+          info.block->split(size, MIN_ALIGN))
     info.next = *next;
 
   return info;
@@ -433,12 +430,9 @@ 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 min_split_size) {
+                               size_t usable_space_alignment) {
   LIBC_ASSERT(usable_space_alignment % MIN_ALIGN == 0 &&
               "alignment must be a multiple of MIN_ALIGN");
-  if (used())
-    return {};
 
   // Compute the minimum outer size that produces a block of at least
   // `new_inner_size`.
@@ -454,16 +448,19 @@ 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 < min_split_size)
+      outer_size() - new_outer_size < sizeof(Block))
     return {};
 
+  bool was_free = !used();
+
   ByteSpan new_region = region().subspan(new_outer_size);
   next_ &= ~SIZE_MASK;
   next_ |= new_outer_size;
 
   Block *new_block = as_block(new_region);
-  mark_free(); // Free status for this block is now stored in new_block.
-  new_block->next()->prev_ = new_region.size();
+  new_block->mark_free();
+  if (was_free)
+    mark_free();
 
   LIBC_ASSERT(new_block->is_usable_space_aligned(usable_space_alignment) &&
               "usable space must have requested alignment");
diff --git a/libc/src/__support/freelist_heap.h b/libc/src/__support/freelist_heap.h
index 02d35b1f8ad49..3bd6df2e8042b 100644
--- a/libc/src/__support/freelist_heap.h
+++ b/libc/src/__support/freelist_heap.h
@@ -100,7 +100,7 @@ LIBC_INLINE void *FreeListHeap::allocate_impl(size_t alignment, size_t size) {
     return nullptr;
 
   auto block_info =
-      Block::allocate(block, alignment, size, FreeStore::MIN_OUTER_SIZE);
+      Block::allocate(block, alignment, size);
   if (block_info.next)
     free_store.insert(block_info.next);
   if (block_info.prev)
@@ -180,10 +180,8 @@ LIBC_INLINE bool FreeListHeap::shrink_in_place(Block *block, size_t size) {
         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();
+        block->split(size, Block::MIN_ALIGN);
 
     LIBC_NAMESPACE::inline_memcpy(overlap_ptr, &backup, sizeof(size_t));
 
diff --git a/libc/test/src/__support/block_test.cpp b/libc/test/src/__support/block_test.cpp
index 3029cde834a5d..189e3cab1d784 100644
--- a/libc/test/src/__support/block_test.cpp
+++ b/libc/test/src/__support/block_test.cpp
@@ -248,7 +248,7 @@ TEST(LlvmLibcBlockTest, CanMarkBlockUsed) {
   EXPECT_FALSE(block->used());
 }
 
-TEST(LlvmLibcBlockTest, CannotSplitUsedBlock) {
+TEST(LlvmLibcBlockTest, CanSplitUsedBlock) {
   constexpr size_t kN = 1024;
   constexpr size_t kSplitN = 512;
 
@@ -259,7 +259,13 @@ TEST(LlvmLibcBlockTest, CannotSplitUsedBlock) {
 
   block->mark_used();
   result = block->split(kSplitN);
-  ASSERT_FALSE(result.has_value());
+  ASSERT_TRUE(result.has_value());
+  Block *new_block = *result;
+
+  EXPECT_TRUE(block->used());
+  EXPECT_FALSE(new_block->used());
+  EXPECT_EQ(block->next(), new_block);
+  EXPECT_EQ(new_block->prev_free(), static_cast<Block *>(nullptr));
 }
 
 TEST(LlvmLibcBlockTest, CanMergeWithNextBlock) {
diff --git a/libc/test/src/__support/freelist_heap_test.cpp b/libc/test/src/__support/freelist_heap_test.cpp
index f25a62b056c76..3002834a51619 100644
--- a/libc/test/src/__support/freelist_heap_test.cpp
+++ b/libc/test/src/__support/freelist_heap_test.cpp
@@ -186,10 +186,12 @@ TEST_FOR_EACH_ALLOCATOR(ReallocSmallerSize, 2048) {
   EXPECT_EQ(ptr1, ptr2);
 }
 
-TEST_FOR_EACH_ALLOCATOR(ReallocShrinkAndAllocateTrailing, 512) {
-  constexpr size_t ALLOC_SIZE = 300;
-  constexpr size_t NEW_ALLOC_SIZE = 64;
-  constexpr size_t TRAILING_ALLOC_SIZE = 256;
+constexpr size_t SMALL_HEAP_SIZE = 512;
+
+TEST_FOR_EACH_ALLOCATOR(ReallocShrinkAndAllocateTrailing, SMALL_HEAP_SIZE) {
+  constexpr size_t ALLOC_SIZE = (SMALL_HEAP_SIZE * 3) / 4;
+  constexpr size_t NEW_ALLOC_SIZE = SMALL_HEAP_SIZE / 4;
+  constexpr size_t TRAILING_ALLOC_SIZE = SMALL_HEAP_SIZE / 2;
 
   void *ptr1 = allocator.allocate(ALLOC_SIZE);
   ASSERT_NE(ptr1, static_cast<void *>(nullptr));
@@ -199,7 +201,6 @@ TEST_FOR_EACH_ALLOCATOR(ReallocShrinkAndAllocateTrailing, 512) {
 
   // Shrink the block in-place.
   void *ptr2 = allocator.realloc(ptr1, NEW_ALLOC_SIZE);
-  ASSERT_NE(ptr2, static_cast<void *>(nullptr));
   EXPECT_EQ(ptr1, ptr2);
 
   // Verify that the data in the shrunk block is preserved.

>From 284cf59591f7b2a35070da5ff9c7965f4b61000e Mon Sep 17 00:00:00 2001
From: Yifan Zhu <yfzhu at google.com>
Date: Fri, 29 May 2026 09:12:26 -0700
Subject: [PATCH 5/7] address format issues

---
 libc/src/__support/block.h         | 3 +--
 libc/src/__support/freelist_heap.h | 6 ++----
 2 files changed, 3 insertions(+), 6 deletions(-)

diff --git a/libc/src/__support/block.h b/libc/src/__support/block.h
index 86befc0995dd8..9be1869652607 100644
--- a/libc/src/__support/block.h
+++ b/libc/src/__support/block.h
@@ -421,8 +421,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, MIN_ALIGN))
+  if (optional<Block *> next = info.block->split(size, MIN_ALIGN))
     info.next = *next;
 
   return info;
diff --git a/libc/src/__support/freelist_heap.h b/libc/src/__support/freelist_heap.h
index 3bd6df2e8042b..631eca2d88ac0 100644
--- a/libc/src/__support/freelist_heap.h
+++ b/libc/src/__support/freelist_heap.h
@@ -99,8 +99,7 @@ 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);
   if (block_info.next)
     free_store.insert(block_info.next);
   if (block_info.prev)
@@ -180,8 +179,7 @@ LIBC_INLINE bool FreeListHeap::shrink_in_place(Block *block, size_t size) {
         reinterpret_cast<cpp::byte *>(block) + new_outer_size;
     size_t backup;
     LIBC_NAMESPACE::inline_memcpy(&backup, overlap_ptr, sizeof(size_t));
-    optional<Block *> next =
-        block->split(size, Block::MIN_ALIGN);
+    optional<Block *> next = block->split(size, Block::MIN_ALIGN);
 
     LIBC_NAMESPACE::inline_memcpy(overlap_ptr, &backup, sizeof(size_t));
 

>From a5f2e9e722b9e0cb03111fdd2d1a6a1293ea64f9 Mon Sep 17 00:00:00 2001
From: Schrodinger ZHU Yifan <yfzhu at google.com>
Date: Mon, 1 Jun 2026 21:51:56 -0400
Subject: [PATCH 6/7] address CRs partially

---
 libc/src/__support/block.h         | 2 +-
 libc/src/__support/freelist_heap.h | 7 ++-----
 2 files changed, 3 insertions(+), 6 deletions(-)

diff --git a/libc/src/__support/block.h b/libc/src/__support/block.h
index 9be1869652607..25a2240a06435 100644
--- a/libc/src/__support/block.h
+++ b/libc/src/__support/block.h
@@ -421,7 +421,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, MIN_ALIGN))
+  if (optional<Block *> next = info.block->split(size))
     info.next = *next;
 
   return info;
diff --git a/libc/src/__support/freelist_heap.h b/libc/src/__support/freelist_heap.h
index 631eca2d88ac0..f84df817e6886 100644
--- a/libc/src/__support/freelist_heap.h
+++ b/libc/src/__support/freelist_heap.h
@@ -165,10 +165,7 @@ LIBC_INLINE bool FreeListHeap::shrink_in_place(Block *block, size_t size) {
   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.
+  if (block->outer_size() >= new_outer_size) {
     // 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
@@ -179,7 +176,7 @@ LIBC_INLINE bool FreeListHeap::shrink_in_place(Block *block, size_t size) {
         reinterpret_cast<cpp::byte *>(block) + new_outer_size;
     size_t backup;
     LIBC_NAMESPACE::inline_memcpy(&backup, overlap_ptr, sizeof(size_t));
-    optional<Block *> next = block->split(size, Block::MIN_ALIGN);
+    optional<Block *> next = block->split(size);
 
     LIBC_NAMESPACE::inline_memcpy(overlap_ptr, &backup, sizeof(size_t));
 

>From c3a5833e249770f8c51f763527f02d1b73a4b24f Mon Sep 17 00:00:00 2001
From: Yifan Zhu <yfzhu at google.com>
Date: Tue, 2 Jun 2026 12:00:08 -0700
Subject: [PATCH 7/7] mark as TODO

---
 libc/src/__support/freelist_heap.h | 9 +++------
 1 file changed, 3 insertions(+), 6 deletions(-)

diff --git a/libc/src/__support/freelist_heap.h b/libc/src/__support/freelist_heap.h
index f84df817e6886..2cce64381faea 100644
--- a/libc/src/__support/freelist_heap.h
+++ b/libc/src/__support/freelist_heap.h
@@ -166,12 +166,9 @@ LIBC_INLINE bool FreeListHeap::shrink_in_place(Block *block, size_t size) {
       reinterpret_cast<uintptr_t>(block) + min_outer_size, Block::MIN_ALIGN);
   size_t new_outer_size = next_block_start - reinterpret_cast<uintptr_t>(block);
   if (block->outer_size() >= new_outer_size) {
-    // 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.
+    // TODO: this is a temporary workaround due to Block's setting prev_
+    // in its constructor. This code should be deleted once we finishing
+    // refactoring.
     cpp::byte *overlap_ptr =
         reinterpret_cast<cpp::byte *>(block) + new_outer_size;
     size_t backup;



More information about the libc-commits mailing list