[libc-commits] [libc] [libc] Use Block as a byte-backed proxy (PR #201001)

Schrodinger ZHU Yifan via libc-commits libc-commits at lists.llvm.org
Tue Jun 9 08:35:20 PDT 2026


================
@@ -275,79 +285,90 @@ class Block {
     // So the maximum distance would be G - L. As a special case, if L is 1
     // (unaligned), the max distance is G - 1.
     //
-    // This block's usable space is aligned to MIN_ALIGN >= Block. With zero
-    // padding, the next block's usable space is sizeof(Block) past it, which is
-    // a point aligned to Block. Thus the max padding needed is alignment -
-    // alignof(Block).
-    if (add_overflow(size, alignment - alignof(Block), size))
+    // This block's usable space is aligned to MIN_ALIGN >= header alignment.
+    // With zero padding, the next block's usable space is HEADER_SIZE past it,
+    // which is aligned to header alignment. Thus the max padding needed is
+    // alignment - alignof(size_t).
+    if (add_overflow(size, alignment - alignof(size_t), size))
       return 0;
     return size;
   }
 
-  // This is the return type for `allocate` which can split one block into up to
-  // three blocks.
-  struct BlockInfo {
-    // This is the newly aligned block. It will have the alignment requested by
-    // a call to `allocate` and at most `size`.
-    Block *block;
-
-    // If the usable_space in the new block was not aligned according to the
-    // `alignment` parameter, we will need to split into this block and the
-    // `block` to ensure `block` is properly aligned. In this case, `prev` will
-    // be a pointer to this new "padding" block. `prev` will be nullptr if no
-    // new block was created or we were able to merge the block before the
-    // original block with the "padding" block.
-    Block *prev;
-
-    // This is the remainder of the next block after splitting the `block`
-    // according to `size`. This can happen if there's enough space after the
-    // `block`.
-    Block *next;
-  };
+  struct BlockInfo;
 
   // 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(BlockRef block, size_t alignment, size_t size);
 
   // These two functions may wrap around.
   LIBC_INLINE static uintptr_t
   next_possible_block_start(uintptr_t ptr,
                             size_t usable_space_alignment = MIN_ALIGN) {
-    return align_up(ptr + sizeof(Block), usable_space_alignment) -
-           sizeof(Block);
+    return align_up(ptr + HEADER_SIZE, usable_space_alignment) - HEADER_SIZE;
   }
   LIBC_INLINE static uintptr_t
   prev_possible_block_start(uintptr_t ptr,
                             size_t usable_space_alignment = MIN_ALIGN) {
-    return align_down(ptr, usable_space_alignment) - sizeof(Block);
+    return align_down(ptr, usable_space_alignment) - HEADER_SIZE;
   }
 
+  /// Only for testing.
+  static constexpr size_t PREV_FIELD_SIZE = sizeof(size_t);
+
 private:
   /// Construct a block to represent a span of bytes. Overwrites only enough
   /// memory for the block header; the rest of the span is left alone.
-  LIBC_INLINE static Block *as_block(ByteSpan bytes) {
-    LIBC_ASSERT(reinterpret_cast<uintptr_t>(bytes.data()) % alignof(Block) ==
+  LIBC_INLINE static BlockRef as_block(ByteSpan bytes) {
+    LIBC_ASSERT(reinterpret_cast<uintptr_t>(bytes.data()) % alignof(size_t) ==
                     0 &&
                 "block start must be suitably aligned");
-    return ::new (bytes.data()) Block(bytes.size(), /*is_last=*/false);
+    BlockRef block(bytes.data());
+    block.store_next(bytes.size());
+    return block;
   }
 
   LIBC_INLINE static void make_last_block(cpp::byte *start) {
-    LIBC_ASSERT(reinterpret_cast<uintptr_t>(start) % alignof(Block) == 0 &&
+    LIBC_ASSERT(reinterpret_cast<uintptr_t>(start) % alignof(size_t) == 0 &&
                 "block start must be suitably aligned");
-    ::new (start) Block(sizeof(Block), /*is_last=*/true);
+    BlockRef last(start);
+    last.store_next(HEADER_SIZE | LAST_MASK);
+  }
+
+  LIBC_INLINE cpp::byte *field_ptr(size_t offset) const {
+    cpp::byte *ptr = header_ptr + offset;
+    LIBC_ASSERT(reinterpret_cast<uintptr_t>(ptr) % alignof(size_t) == 0 &&
+                "block metadata fields must be aligned");
+    // BlockRef points to block header which should be well-aligned. However,
+    // the compiler may not know this information. Adding assume aligned to
+    // tell the compiler to emit a single load/store even when unaligned access
+    // is disallowed.
+#if __has_builtin(__builtin_assume_aligned)
+    return reinterpret_cast<cpp::byte *>(
+        __builtin_assume_aligned(ptr, alignof(size_t)));
+#else
+    return ptr;
+#endif
+  }
+
+  LIBC_INLINE size_t load_field(size_t offset) const {
+    size_t value;
+    inline_memcpy(&value, field_ptr(offset), sizeof(value));
+    return value;
+  }
+
+  LIBC_INLINE void store_field(size_t offset, size_t value) const {
+    inline_memcpy(field_ptr(offset), &value, sizeof(value));
----------------
SchrodingerZhu wrote:

Ah, this is really a good point. I was basically considering load/store separately, making them to be correct in dependent of each other. On a second thought, I think placement new can be used here. Previous problems arise because we always placement new a whole block which may create an object overlaps with payload but as we have separated the field, individual placement news should work just fine now.

https://github.com/llvm/llvm-project/pull/201001


More information about the libc-commits mailing list