[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
Mon Jun 8 08:19:05 PDT 2026


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

>From 8c1545899829effde7c9bab7fe822bad0f8023c3 Mon Sep 17 00:00:00 2001
From: Yifan Zhu <yfzhu at google.com>
Date: Fri, 5 Jun 2026 10:33:39 -0700
Subject: [PATCH 1/2] [libc] Use Block as a byte-backed proxy and refactor to
 BlockRef

This squashes the changes from libc/block-proxy branch, which includes:
- Using Block as a byte-backed proxy.
- Renaming Block proxy class to BlockRef.
- Refactoring BlockPtr to inherit privately from BlockRef.
- Inline self-contained BlockRef methods.
- Cleanups and formatting.
---
 libc/fuzzing/__support/freelist_heap_fuzz.cpp |  13 +-
 libc/src/__support/block.h                    | 340 ++++++++------
 libc/src/__support/freelist.cpp               |  11 +-
 libc/src/__support/freelist.h                 |  25 +-
 libc/src/__support/freelist_heap.h            |  95 ++--
 libc/src/__support/freestore.h                |  55 ++-
 libc/src/__support/freetrie.h                 |  17 +-
 libc/test/src/__support/block_test.cpp        | 438 +++++++++---------
 .../test/src/__support/freelist_heap_test.cpp |  13 +-
 libc/test/src/__support/freelist_test.cpp     |  29 +-
 libc/test/src/__support/freestore_test.cpp    |  72 +--
 libc/test/src/__support/freetrie_test.cpp     |  87 ++--
 12 files changed, 647 insertions(+), 548 deletions(-)

diff --git a/libc/fuzzing/__support/freelist_heap_fuzz.cpp b/libc/fuzzing/__support/freelist_heap_fuzz.cpp
index 3675e6c6b7adc..9195119db1edb 100644
--- a/libc/fuzzing/__support/freelist_heap_fuzz.cpp
+++ b/libc/fuzzing/__support/freelist_heap_fuzz.cpp
@@ -1,4 +1,4 @@
-//===-- freelist_heap_fuzz.cpp --------------------------------------------===//
+//===----------------------------------------------------------------------===//
 //
 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
 // See https://llvm.org/LICENSE.txt for license information.
@@ -6,6 +6,7 @@
 //
 //===----------------------------------------------------------------------===//
 ///
+/// \file
 /// Fuzzing test for llvm-libc freelist-based heap implementation.
 ///
 //===----------------------------------------------------------------------===//
@@ -26,7 +27,7 @@ asm(R"(
 __llvm_libc_heap_limit:
 )");
 
-using LIBC_NAMESPACE::Block;
+using LIBC_NAMESPACE::BlockRef;
 using LIBC_NAMESPACE::FreeListHeap;
 using LIBC_NAMESPACE::inline_memset;
 using LIBC_NAMESPACE::cpp::nullopt;
@@ -148,7 +149,7 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t remainder) {
 
       // Perform allocation.
       void *ptr = nullptr;
-      size_t alignment = Block::MIN_ALIGN;
+      size_t alignment = BlockRef::MIN_ALIGN;
       switch (alloc_type) {
       case AllocType::MALLOC:
         ptr = heap.allocate(alloc_size);
@@ -173,7 +174,7 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t remainder) {
                           alloc_size - alloc.size);
           alloc.ptr = ptr;
           alloc.size = alloc_size;
-          alloc.alignment = Block::MIN_ALIGN;
+          alloc.alignment = BlockRef::MIN_ALIGN;
         }
         break;
       }
@@ -195,8 +196,8 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t remainder) {
 
       if (ptr) {
         // aligned_allocate should automatically apply a minimum alignment.
-        if (alignment < Block::MIN_ALIGN)
-          alignment = Block::MIN_ALIGN;
+        if (alignment < BlockRef::MIN_ALIGN)
+          alignment = BlockRef::MIN_ALIGN;
         // Check alignment.
         if (reinterpret_cast<uintptr_t>(ptr) % alignment)
           __builtin_trap();
diff --git a/libc/src/__support/block.h b/libc/src/__support/block.h
index 25a2240a06435..cec59e952916d 100644
--- a/libc/src/__support/block.h
+++ b/libc/src/__support/block.h
@@ -1,25 +1,31 @@
-//===-- Implementation header for a block of memory -------------*- C++ -*-===//
+//===----------------------------------------------------------------------===//
 //
 // 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 header for a block of memory.
+///
+//===----------------------------------------------------------------------===//
 
 #ifndef LLVM_LIBC_SRC___SUPPORT_BLOCK_H
 #define LLVM_LIBC_SRC___SUPPORT_BLOCK_H
 
 #include "hdr/stdint_proxy.h"
+#include "hdr/types/size_t.h"
 #include "src/__support/CPP/algorithm.h"
 #include "src/__support/CPP/cstddef.h"
 #include "src/__support/CPP/limits.h"
 #include "src/__support/CPP/new.h"
 #include "src/__support/CPP/optional.h"
 #include "src/__support/CPP/span.h"
-#include "src/__support/CPP/type_traits.h"
 #include "src/__support/libc_assert.h"
 #include "src/__support/macros/config.h"
 #include "src/__support/math_extras.h"
+#include "src/string/memory_utils/inline_memcpy.h"
 
 namespace LIBC_NAMESPACE_DECL {
 
@@ -38,15 +44,17 @@ LIBC_INLINE constexpr size_t align_up(size_t value, size_t alignment) {
 using ByteSpan = cpp::span<LIBC_NAMESPACE::cpp::byte>;
 using cpp::optional;
 
-/// Memory region with links to adjacent blocks.
+/// Proxy for a memory region with links to adjacent blocks.
 ///
 /// The blocks store their offsets to the previous and next blocks. The latter
-/// is also the block's size.
+/// is also the block's size. The metadata is stored in raw bytes and accessed
+/// through aligned byte-copy loads and stores so the header can overlap user
+/// storage without creating typed aliasing accesses.
 ///
 /// All blocks have their usable space aligned to some multiple of MIN_ALIGN.
 /// This also implies that block outer sizes are aligned to MIN_ALIGN.
 ///
-/// As an example, the diagram below represents two contiguous `Block`s. The
+/// As an example, the diagram below represents two contiguous blocks. The
 /// indices indicate byte offsets:
 ///
 /// @code{.unparsed}
@@ -90,48 +98,69 @@ using cpp::optional;
 ///
 /// The next offset of a block matches the previous offset of its next block.
 /// The first block in a list is denoted by having a previous offset of `0`.
-class Block {
-  // Masks for the contents of the next_ field.
+class BlockRef {
+  // Masks for the contents of the next field.
   static constexpr size_t PREV_FREE_MASK = 1 << 0;
   static constexpr size_t LAST_MASK = 1 << 1;
   static constexpr size_t SIZE_MASK = ~(PREV_FREE_MASK | LAST_MASK);
 
+  // Header field offsets. The previous offset is only meaningful when this
+  // block's PREV_FREE_MASK bit is set in the next field.
+  static constexpr size_t PREV_OFFSET = 0;
+  static constexpr size_t NEXT_OFFSET = sizeof(size_t);
+
 public:
+  static constexpr size_t HEADER_SIZE = 2 * sizeof(size_t);
+
   // To ensure block sizes have two lower unused bits, ensure usable space is
   // always aligned to at least 4 bytes. (The distances between usable spaces,
   // the outer size, is then always also 4-aligned.)
   static constexpr size_t MIN_ALIGN = cpp::max(size_t{4}, alignof(max_align_t));
-  // No copy or move.
-  Block(const Block &other) = delete;
-  Block &operator=(const Block &other) = delete;
+
+  LIBC_INLINE constexpr BlockRef() = default;
+  LIBC_INLINE explicit constexpr BlockRef(cpp::byte *ptr) : self(ptr) {}
+  LIBC_INLINE explicit constexpr operator bool() const {
+    return self != nullptr;
+  }
+  LIBC_INLINE constexpr bool operator==(BlockRef other) const {
+    return self == other.self;
+  }
+  LIBC_INLINE constexpr bool operator!=(BlockRef other) const {
+    return !(*this == other);
+  }
+
+  LIBC_INLINE cpp::byte *data() const { return self; }
+  LIBC_INLINE uintptr_t addr() const {
+    return reinterpret_cast<uintptr_t>(self);
+  }
 
   /// Initializes a given memory region into a first block and a sentinel last
   /// block. Returns the first block, which has its usable space aligned to
   /// MIN_ALIGN.
-  static optional<Block *> init(ByteSpan region);
+  static optional<BlockRef> init(ByteSpan region);
 
-  /// @returns  A pointer to a `Block`, given a pointer to the start of the
-  ///           usable space inside the block.
+  /// @returns  A pointer to a block, given a pointer to the start of the usable
+  ///           space inside the block.
   ///
   /// This is the inverse of `usable_space()`.
   ///
-  /// @warning  This method does not do any checking; passing a random
-  ///           pointer will return a non-null pointer.
-  LIBC_INLINE static Block *from_usable_space(void *usable_space) {
+  /// @warning  This method does not do any checking; passing a random pointer
+  ///           will return a non-null pointer.
+  LIBC_INLINE static BlockRef from_usable_space(void *usable_space) {
     auto *bytes = reinterpret_cast<cpp::byte *>(usable_space);
-    return reinterpret_cast<Block *>(bytes - sizeof(Block));
+    return BlockRef(bytes - HEADER_SIZE);
   }
-  LIBC_INLINE static const Block *from_usable_space(const void *usable_space) {
+  LIBC_INLINE static BlockRef from_usable_space(const void *usable_space) {
     const auto *bytes = reinterpret_cast<const cpp::byte *>(usable_space);
-    return reinterpret_cast<const Block *>(bytes - sizeof(Block));
+    return BlockRef(const_cast<cpp::byte *>(bytes - HEADER_SIZE));
   }
 
   /// @returns The total size of the block in bytes, including the header.
-  LIBC_INLINE size_t outer_size() const { return next_ & SIZE_MASK; }
+  LIBC_INLINE size_t outer_size() const { return load_next() & SIZE_MASK; }
 
-  LIBC_INLINE static size_t outer_size(size_t inner_size) {
-    // The usable region includes the prev_ field of the next block.
-    return inner_size - sizeof(prev_) + sizeof(Block);
+  LIBC_INLINE static constexpr size_t outer_size(size_t inner_size) {
+    // The usable region includes the prev field of the next block.
+    return inner_size - PREV_FIELD_SIZE + HEADER_SIZE;
   }
 
   /// @returns The number of usable bytes inside the block were it to be
@@ -144,9 +173,9 @@ class Block {
 
   /// @returns The number of usable bytes inside a block with the given outer
   /// size were it to be allocated.
-  LIBC_INLINE static size_t inner_size(size_t outer_size) {
-    // The usable region includes the prev_ field of the next block.
-    return inner_size_free(outer_size) + sizeof(prev_);
+  LIBC_INLINE static constexpr size_t inner_size(size_t outer_size) {
+    // The usable region includes the prev field of the next block.
+    return inner_size_free(outer_size) + PREV_FIELD_SIZE;
   }
 
   /// @returns The number of usable bytes inside the block if it remains free.
@@ -158,87 +187,68 @@ class Block {
 
   /// @returns The number of usable bytes inside a block with the given outer
   /// size if it remains free.
-  LIBC_INLINE static size_t inner_size_free(size_t outer_size) {
-    return outer_size - sizeof(Block);
+  LIBC_INLINE static constexpr size_t inner_size_free(size_t outer_size) {
+    return outer_size - HEADER_SIZE;
   }
 
   /// @returns A pointer to the usable space inside this block.
   ///
   /// Aligned to some multiple of MIN_ALIGN.
-  LIBC_INLINE cpp::byte *usable_space() {
-    auto *s = reinterpret_cast<cpp::byte *>(this) + sizeof(Block);
-    LIBC_ASSERT(reinterpret_cast<uintptr_t>(s) % MIN_ALIGN == 0 &&
-                "usable space must be aligned to MIN_ALIGN");
-    return s;
-  }
-  LIBC_INLINE const cpp::byte *usable_space() const {
-    const auto *s = reinterpret_cast<const cpp::byte *>(this) + sizeof(Block);
+  LIBC_INLINE cpp::byte *usable_space() const {
+    auto *s = self + HEADER_SIZE;
     LIBC_ASSERT(reinterpret_cast<uintptr_t>(s) % MIN_ALIGN == 0 &&
                 "usable space must be aligned to MIN_ALIGN");
     return s;
   }
 
   // @returns The region of memory the block manages, including the header.
-  LIBC_INLINE ByteSpan region() {
-    return {reinterpret_cast<cpp::byte *>(this), outer_size()};
-  }
+  LIBC_INLINE ByteSpan region() const { return {self, outer_size()}; }
 
   /// Attempts to split this block.
   ///
   /// If successful, the block will have an inner size of at least
   /// `new_inner_size`. The remaining space will be returned as a new block,
-  /// with usable space aligned to `usable_space_alignment`. Note that the prev_
+  /// with usable space aligned to `usable_space_alignment`. Note that the prev
   /// 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);
+  optional<BlockRef> split(size_t new_inner_size,
+                           size_t usable_space_alignment = MIN_ALIGN) const;
 
   /// Merges this block with the one that comes after it.
-  bool merge_next();
-
-  /// @returns The block immediately after this one, or a null pointer if this
-  /// is the last block.
-  LIBC_INLINE Block *next() const {
-    if (next_ & LAST_MASK)
-      return nullptr;
-    return reinterpret_cast<Block *>(reinterpret_cast<uintptr_t>(this) +
-                                     outer_size());
+  bool merge_next() const;
+
+  /// @returns The block immediately after this one, or a null block if this is
+  /// the last block.
+  LIBC_INLINE BlockRef next() const {
+    size_t next_value = load_next();
+    if (next_value & LAST_MASK)
+      return BlockRef();
+    return BlockRef(self + (next_value & SIZE_MASK));
   }
 
-  /// @returns The free block immediately before this one, otherwise nullptr.
-  LIBC_INLINE Block *prev_free() const {
-    if (!(next_ & PREV_FREE_MASK))
-      return nullptr;
-    return reinterpret_cast<Block *>(reinterpret_cast<uintptr_t>(this) - prev_);
+  /// @returns The free block immediately before this one, otherwise null.
+  LIBC_INLINE BlockRef prev_free() const {
+    if (!(load_next() & PREV_FREE_MASK))
+      return BlockRef();
+    return BlockRef(self - load_prev());
   }
 
   /// @returns Whether the block is unavailable for allocation.
-  LIBC_INLINE bool used() const { return !next() || !next()->prev_free(); }
+  LIBC_INLINE bool used() const { return !next() || !next().prev_free(); }
 
   /// Marks this block as in use.
-  LIBC_INLINE void mark_used() {
+  LIBC_INLINE void mark_used() const {
     LIBC_ASSERT(next() && "last block is always considered used");
-    next()->next_ &= ~PREV_FREE_MASK;
+    BlockRef next_block = next();
+    next_block.store_next(next_block.load_next() & ~PREV_FREE_MASK);
   }
 
   /// Marks this block as free.
-  LIBC_INLINE void mark_free() {
+  LIBC_INLINE void mark_free() const {
     LIBC_ASSERT(next() && "last block is always considered used");
-    next()->next_ |= PREV_FREE_MASK;
-    // The next block's prev_ field becomes alive, as it is no longer part of
-    // this block's used space.
-    *new (&next()->prev_) size_t = outer_size();
-  }
-
-  LIBC_INLINE Block(size_t outer_size, bool is_last) : next_(outer_size) {
-    // Last blocks are not usable, so they need not have sizes aligned to
-    // MIN_ALIGN.
-    LIBC_ASSERT(outer_size % (is_last ? alignof(Block) : MIN_ALIGN) == 0 &&
-                "block sizes must be aligned");
-    LIBC_ASSERT(is_usable_space_aligned(MIN_ALIGN) &&
-                "usable space must be aligned to a multiple of MIN_ALIGN");
-    if (is_last)
-      next_ |= LAST_MASK;
+    BlockRef next_block = next();
+    next_block.store_next(next_block.load_next() | PREV_FREE_MASK);
+    next_block.store_prev(outer_size());
   }
 
   LIBC_INLINE bool is_usable_space_aligned(size_t alignment) const {
@@ -259,7 +269,7 @@ class Block {
 
     // We must create a new block inside this one (splitting). This requires a
     // block header in addition to the requested size.
-    if (add_overflow(size, sizeof(Block), size))
+    if (add_overflow(size, HEADER_SIZE, size))
       return 0;
 
     // Beyond that, padding space may need to remain in this block to ensure
@@ -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 = self + 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));
   }
 
   /// Offset from this block to the previous block. 0 if this is the first
   /// block. This field is only alive when the previous block is free;
   /// otherwise, its memory is reused as part of the previous block's usable
   /// space.
-  size_t prev_ = 0;
+  LIBC_INLINE size_t load_prev() const { return load_field(PREV_OFFSET); }
 
   /// Offset from this block to the next block. Valid even if this is the last
   /// block, since it equals the size of the block.
-  size_t next_ = 0;
-
+  ///
   /// Information about the current state of the block is stored in the two low
   /// order bits of the next_ value. These are guaranteed free by a minimum
   /// alignment (and thus, alignment of the size) of 4. The lowest bit is the
@@ -357,14 +378,41 @@ class Block {
   ///   previous block is free.
   /// * If the `last` flag is set, the block is the sentinel last block. It is
   ///   summarily considered used and has no next block.
+  LIBC_INLINE size_t load_next() const { return load_field(NEXT_OFFSET); }
 
-public:
-  /// Only for testing.
-  static constexpr size_t PREV_FIELD_SIZE = sizeof(prev_);
+  LIBC_INLINE void store_prev(size_t value) const {
+    store_field(PREV_OFFSET, value);
+  }
+  LIBC_INLINE void store_next(size_t value) const {
+    store_field(NEXT_OFFSET, value);
+  }
+
+  cpp::byte *self = nullptr;
+};
+
+// This is the return type for `allocate` which can split one block into up to
+// three blocks.
+struct BlockRef::BlockInfo {
+  // This is the newly aligned block. It will have the alignment requested by a
+  // call to `allocate` and at most `size`.
+  BlockRef 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
+  // this new "padding" block. `prev` will be null if no new block was created
+  // or we were able to merge the block before the original block with the
+  // "padding" block.
+  BlockRef 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`.
+  BlockRef next;
 };
 
 LIBC_INLINE
-optional<Block *> Block::init(ByteSpan region) {
+optional<BlockRef> BlockRef::init(ByteSpan region) {
   if (!region.data())
     return {};
 
@@ -381,63 +429,64 @@ optional<Block *> Block::init(ByteSpan region) {
   if (last_start >= end)
     return {};
 
-  if (block_start + sizeof(Block) > last_start)
+  if (block_start + HEADER_SIZE > last_start)
     return {};
 
   auto *last_start_ptr = reinterpret_cast<cpp::byte *>(last_start);
-  Block *block =
+  BlockRef block =
       as_block({reinterpret_cast<cpp::byte *>(block_start), last_start_ptr});
   make_last_block(last_start_ptr);
-  block->mark_free();
+  block.mark_free();
   return block;
 }
 
 LIBC_INLINE
-Block::BlockInfo Block::allocate(Block *block, size_t alignment, size_t size) {
+BlockRef::BlockInfo BlockRef::allocate(BlockRef block, size_t alignment,
+                                       size_t size) {
   LIBC_ASSERT(alignment % MIN_ALIGN == 0 &&
               "alignment must be a multiple of MIN_ALIGN");
 
-  BlockInfo info{block, /*prev=*/nullptr, /*next=*/nullptr};
+  BlockInfo info{block, BlockRef(), BlockRef()};
 
-  if (!info.block->is_usable_space_aligned(alignment)) {
-    Block *original = info.block;
+  if (!info.block.is_usable_space_aligned(alignment)) {
+    BlockRef original = info.block;
     // The padding block has no minimum size requirement.
-    optional<Block *> maybe_aligned_block = original->split(0, alignment);
+    optional<BlockRef> maybe_aligned_block = original.split(0, alignment);
     LIBC_ASSERT(maybe_aligned_block.has_value() &&
                 "it should always be possible to split for alignment");
 
-    if (Block *prev = original->prev_free()) {
+    if (BlockRef prev = original.prev_free()) {
       // If there is a free block before this, we can merge the current one with
       // the newly created one.
-      prev->merge_next();
+      prev.merge_next();
     } else {
       info.prev = original;
     }
 
-    Block *aligned_block = *maybe_aligned_block;
-    LIBC_ASSERT(aligned_block->is_usable_space_aligned(alignment) &&
+    BlockRef aligned_block = *maybe_aligned_block;
+    LIBC_ASSERT(aligned_block.is_usable_space_aligned(alignment) &&
                 "The aligned block isn't aligned somehow.");
     info.block = aligned_block;
   }
 
   // Now get a block for the requested size.
-  if (optional<Block *> next = info.block->split(size))
+  if (optional<BlockRef> next = info.block.split(size))
     info.next = *next;
 
   return info;
 }
 
 LIBC_INLINE
-optional<Block *> Block::split(size_t new_inner_size,
-                               size_t usable_space_alignment) {
+optional<BlockRef> BlockRef::split(size_t new_inner_size,
+                                   size_t usable_space_alignment) const {
   LIBC_ASSERT(usable_space_alignment % MIN_ALIGN == 0 &&
               "alignment must be a multiple of MIN_ALIGN");
 
   // Compute the minimum outer size that produces a block of at least
   // `new_inner_size`.
-  size_t min_outer_size = outer_size(cpp::max(new_inner_size, sizeof(prev_)));
+  size_t min_outer_size = outer_size(cpp::max(new_inner_size, PREV_FIELD_SIZE));
 
-  uintptr_t start = reinterpret_cast<uintptr_t>(this);
+  uintptr_t start = addr();
   uintptr_t next_block_start =
       next_possible_block_start(start + min_outer_size, usable_space_alignment);
   if (next_block_start < start)
@@ -447,33 +496,32 @@ 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 < HEADER_SIZE)
     return {};
 
   bool was_free = !used();
 
   ByteSpan new_region = region().subspan(new_outer_size);
-  next_ &= ~SIZE_MASK;
-  next_ |= new_outer_size;
+  store_next((load_next() & ~SIZE_MASK) | new_outer_size);
 
-  Block *new_block = as_block(new_region);
-  new_block->mark_free();
+  BlockRef new_block = as_block(new_region);
+  new_block.mark_free();
   if (was_free)
     mark_free();
 
-  LIBC_ASSERT(new_block->is_usable_space_aligned(usable_space_alignment) &&
+  LIBC_ASSERT(new_block.is_usable_space_aligned(usable_space_alignment) &&
               "usable space must have requested alignment");
   return new_block;
 }
 
 LIBC_INLINE
-bool Block::merge_next() {
-  if (used() || next()->used())
+bool BlockRef::merge_next() const {
+  BlockRef next_block = next();
+  if (used() || next_block.used())
     return false;
-  size_t new_size = outer_size() + next()->outer_size();
-  next_ &= ~SIZE_MASK;
-  next_ |= new_size;
-  next()->prev_ = new_size;
+  size_t new_size = outer_size() + next_block.outer_size();
+  store_next((load_next() & ~SIZE_MASK) | new_size);
+  next().store_prev(new_size);
   return true;
 }
 
diff --git a/libc/src/__support/freelist.cpp b/libc/src/__support/freelist.cpp
index bfb90ae1c4db4..28f73b2726d7d 100644
--- a/libc/src/__support/freelist.cpp
+++ b/libc/src/__support/freelist.cpp
@@ -1,10 +1,15 @@
-//===-- Implementation for freelist ---------------------------------------===//
+//===----------------------------------------------------------------------===//
 //
 // 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"
 
@@ -12,8 +17,8 @@ namespace LIBC_NAMESPACE_DECL {
 
 void FreeList::push(Node *node) {
   if (begin_) {
-    LIBC_ASSERT(Block::from_usable_space(node)->outer_size() ==
-                    begin_->block()->outer_size() &&
+    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;
diff --git a/libc/src/__support/freelist.h b/libc/src/__support/freelist.h
index c51f14fe57ae7..48e70c7c29df6 100644
--- a/libc/src/__support/freelist.h
+++ b/libc/src/__support/freelist.h
@@ -1,10 +1,15 @@
-//===-- Interface for freelist --------------------------------------------===//
+//===----------------------------------------------------------------------===//
 //
 // 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
+/// Interface for freelist.
+///
+//===----------------------------------------------------------------------===//
 
 #ifndef LLVM_LIBC_SRC___SUPPORT_FREELIST_H
 #define LLVM_LIBC_SRC___SUPPORT_FREELIST_H
@@ -26,15 +31,15 @@ class FreeList {
   class Node {
   public:
     /// @returns The block containing this node.
-    LIBC_INLINE const Block *block() const {
-      return Block::from_usable_space(this);
+    LIBC_INLINE BlockRef block() const {
+      return BlockRef::from_usable_space(this);
     }
 
     /// @returns The block containing this node.
-    LIBC_INLINE Block *block() { return Block::from_usable_space(this); }
+    LIBC_INLINE BlockRef block() { return BlockRef::from_usable_space(this); }
 
     /// @returns The inner size of blocks in the list containing this node.
-    LIBC_INLINE size_t size() const { return block()->inner_size(); }
+    LIBC_INLINE size_t size() const { return block().inner_size(); }
 
   private:
     // Circularly linked pointers to adjacent nodes.
@@ -58,16 +63,16 @@ class FreeList {
   LIBC_INLINE Node *begin() { return begin_; }
 
   /// @returns The first block in the list.
-  LIBC_INLINE Block *front() { return begin_->block(); }
+  LIBC_INLINE BlockRef front() { return begin_->block(); }
 
   /// Push a block to the back of the list.
   /// The block must be large enough to contain a node.
-  LIBC_INLINE void push(Block *block) {
-    LIBC_ASSERT(!block->used() &&
+  LIBC_INLINE void push(BlockRef block) {
+    LIBC_ASSERT(!block.used() &&
                 "only free blocks can be placed on free lists");
-    LIBC_ASSERT(block->inner_size_free() >= sizeof(FreeList) &&
+    LIBC_ASSERT(block.inner_size_free() >= sizeof(FreeList) &&
                 "block too small to accomodate free list node");
-    push(new (block->usable_space()) Node);
+    push(new (block.usable_space()) Node);
   }
 
   /// Push an already-constructed node to the back of the list.
diff --git a/libc/src/__support/freelist_heap.h b/libc/src/__support/freelist_heap.h
index 2cce64381faea..05affc38fcb8c 100644
--- a/libc/src/__support/freelist_heap.h
+++ b/libc/src/__support/freelist_heap.h
@@ -1,10 +1,15 @@
-//===-- Interface for freelist_heap ---------------------------------------===//
+//===----------------------------------------------------------------------===//
 //
 // 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
+/// Interface for freelist_heap.
+///
+//===----------------------------------------------------------------------===//
 
 #ifndef LLVM_LIBC_SRC___SUPPORT_FREELIST_HEAP_H
 #define LLVM_LIBC_SRC___SUPPORT_FREELIST_HEAP_H
@@ -53,12 +58,12 @@ 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());
+  span<cpp::byte> block_to_span(BlockRef block) {
+    return span<cpp::byte>(block.usable_space(), block.inner_size());
   }
 
+  bool shrink_in_place(BlockRef block, size_t size);
+
   bool is_valid_ptr(void *ptr) { return ptr >= begin && ptr < end; }
 
   cpp::byte *begin;
@@ -77,9 +82,9 @@ template <size_t BUFF_SIZE> class FreeListHeapBuffer : public FreeListHeap {
 
 LIBC_INLINE void FreeListHeap::init() {
   LIBC_ASSERT(!is_initialized && "duplicate initialization");
-  auto result = Block::init(region());
-  Block *block = *result;
-  free_store.set_range({0, cpp::bit_ceil(block->inner_size())});
+  auto result = BlockRef::init(region());
+  BlockRef block = *result;
+  free_store.set_range({0, cpp::bit_ceil(block.inner_size())});
   free_store.insert(block);
   is_initialized = true;
 }
@@ -91,26 +96,26 @@ LIBC_INLINE void *FreeListHeap::allocate_impl(size_t alignment, size_t size) {
   if (!is_initialized)
     init();
 
-  size_t request_size = Block::min_size_for_allocation(alignment, size);
+  size_t request_size = BlockRef::min_size_for_allocation(alignment, size);
   if (!request_size)
     return nullptr;
 
-  Block *block = free_store.remove_best_fit(request_size);
+  BlockRef block = free_store.remove_best_fit(request_size);
   if (!block)
     return nullptr;
 
-  auto block_info = Block::allocate(block, alignment, size);
+  auto block_info = BlockRef::allocate(block, alignment, size);
   if (block_info.next)
     free_store.insert(block_info.next);
   if (block_info.prev)
     free_store.insert(block_info.prev);
 
-  block_info.block->mark_used();
-  return block_info.block->usable_space();
+  block_info.block.mark_used();
+  return block_info.block.usable_space();
 }
 
 LIBC_INLINE void *FreeListHeap::allocate(size_t size) {
-  return allocate_impl(Block::MIN_ALIGN, size);
+  return allocate_impl(BlockRef::MIN_ALIGN, size);
 }
 
 LIBC_INLINE void *FreeListHeap::aligned_allocate(size_t alignment,
@@ -123,8 +128,8 @@ LIBC_INLINE void *FreeListHeap::aligned_allocate(size_t alignment,
   if (size % alignment != 0)
     return nullptr;
 
-  // The minimum alignment supported by Block is MIN_ALIGN.
-  alignment = cpp::max(alignment, Block::MIN_ALIGN);
+  // The minimum alignment supported by BlockRef is MIN_ALIGN.
+  alignment = cpp::max(alignment, BlockRef::MIN_ALIGN);
 
   return allocate_impl(alignment, size);
 }
@@ -137,53 +142,43 @@ LIBC_INLINE void FreeListHeap::free(void *ptr) {
 
   LIBC_ASSERT(is_valid_ptr(bytes) && "Invalid pointer");
 
-  Block *block = Block::from_usable_space(bytes);
-  LIBC_ASSERT(block->next() && "sentinel last block cannot be freed");
-  LIBC_ASSERT(block->used() && "double free");
-  block->mark_free();
+  BlockRef block = BlockRef::from_usable_space(bytes);
+  LIBC_ASSERT(block.next() && "sentinel last block cannot be freed");
+  LIBC_ASSERT(block.used() && "double free");
+  block.mark_free();
 
   // Can we combine with the left or right blocks?
-  Block *prev_free = block->prev_free();
-  Block *next = block->next();
+  BlockRef prev_free = block.prev_free();
+  BlockRef next = block.next();
 
-  if (prev_free != nullptr) {
+  if (prev_free) {
     // Remove from free store and merge.
     free_store.remove(prev_free);
     block = prev_free;
-    block->merge_next();
+    block.merge_next();
   }
-  if (!next->used()) {
+  if (!next.used()) {
     free_store.remove(next);
-    block->merge_next();
+    block.merge_next();
   }
   // Add back to the freelist
   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);
-  if (block->outer_size() >= new_outer_size) {
-    // 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;
-    LIBC_NAMESPACE::inline_memcpy(&backup, overlap_ptr, sizeof(size_t));
-    optional<Block *> next = block->split(size);
-
-    LIBC_NAMESPACE::inline_memcpy(overlap_ptr, &backup, sizeof(size_t));
-
+LIBC_INLINE bool FreeListHeap::shrink_in_place(BlockRef block, size_t size) {
+  size_t min_outer_size = BlockRef::outer_size(cpp::max(size, sizeof(size_t)));
+  uintptr_t next_block_start = BlockRef::next_possible_block_start(
+      block.addr() + min_outer_size, BlockRef::MIN_ALIGN);
+  size_t new_outer_size = next_block_start - block.addr();
+  if (block.outer_size() >= new_outer_size) {
+    optional<BlockRef> next = block.split(size);
     // 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()) {
+      BlockRef next_block = *next;
+      BlockRef right = next_block.next();
+      if (!right.used()) {
         free_store.remove(right);
-        next_block->merge_next();
+        next_block.merge_next();
       }
       free_store.insert(next_block);
     }
@@ -209,10 +204,10 @@ LIBC_INLINE void *FreeListHeap::realloc(void *ptr, size_t size) {
   if (!is_valid_ptr(bytes))
     return nullptr;
 
-  Block *block = Block::from_usable_space(bytes);
-  if (!block->used())
+  BlockRef block = BlockRef::from_usable_space(bytes);
+  if (!block.used())
     return nullptr;
-  size_t old_size = block->inner_size();
+  size_t old_size = block.inner_size();
 
   if (old_size >= size) {
     shrink_in_place(block, size);
diff --git a/libc/src/__support/freestore.h b/libc/src/__support/freestore.h
index d611c17af4644..adc0e061ace93 100644
--- a/libc/src/__support/freestore.h
+++ b/libc/src/__support/freestore.h
@@ -1,10 +1,15 @@
-//===-- Interface for freestore ------------------------------------------===//
+//===----------------------------------------------------------------------===//
 //
 // 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
+/// Interface for freestore.
+///
+//===----------------------------------------------------------------------===//
 
 #ifndef LLVM_LIBC_SRC___SUPPORT_FREESTORE_H
 #define LLVM_LIBC_SRC___SUPPORT_FREESTORE_H
@@ -31,39 +36,39 @@ class FreeStore {
 
   /// Insert a free block. If the block is too small to be tracked, nothing
   /// happens.
-  void insert(Block *block);
+  void insert(BlockRef block);
 
   /// Remove a free block. If the block is too small to be tracked, nothing
   /// happens.
-  void remove(Block *block);
+  void remove(BlockRef block);
 
   /// Remove a best-fit free block that can contain the given size when
   /// allocated. Returns nullptr if there is no such block.
-  Block *remove_best_fit(size_t size);
+  BlockRef remove_best_fit(size_t size);
 
 private:
-  static constexpr size_t MIN_OUTER_SIZE =
-      align_up(sizeof(Block) + sizeof(FreeList::Node), Block::MIN_ALIGN);
-  static constexpr size_t MIN_LARGE_OUTER_SIZE =
-      align_up(sizeof(Block) + sizeof(FreeTrie::Node), Block::MIN_ALIGN);
+  static constexpr size_t MIN_OUTER_SIZE = align_up(
+      BlockRef::HEADER_SIZE + sizeof(FreeList::Node), BlockRef::MIN_ALIGN);
+  static constexpr size_t MIN_LARGE_OUTER_SIZE = align_up(
+      BlockRef::HEADER_SIZE + sizeof(FreeTrie::Node), BlockRef::MIN_ALIGN);
   static constexpr size_t NUM_SMALL_SIZES =
-      (MIN_LARGE_OUTER_SIZE - MIN_OUTER_SIZE) / Block::MIN_ALIGN;
+      (MIN_LARGE_OUTER_SIZE - MIN_OUTER_SIZE) / BlockRef::MIN_ALIGN;
 
-  LIBC_INLINE static bool too_small(Block *block) {
-    return block->outer_size() < MIN_OUTER_SIZE;
+  LIBC_INLINE static bool too_small(BlockRef block) {
+    return block.outer_size() < MIN_OUTER_SIZE;
   }
-  LIBC_INLINE static bool is_small(Block *block) {
-    return block->outer_size() < MIN_LARGE_OUTER_SIZE;
+  LIBC_INLINE static bool is_small(BlockRef block) {
+    return block.outer_size() < MIN_LARGE_OUTER_SIZE;
   }
 
-  FreeList &small_list(Block *block);
+  FreeList &small_list(BlockRef block);
   FreeList *find_best_small_fit(size_t size);
 
   cpp::array<FreeList, NUM_SMALL_SIZES> small_lists;
   FreeTrie large_trie;
 };
 
-LIBC_INLINE void FreeStore::insert(Block *block) {
+LIBC_INLINE void FreeStore::insert(BlockRef block) {
   if (too_small(block))
     return;
   if (is_small(block))
@@ -72,35 +77,35 @@ LIBC_INLINE void FreeStore::insert(Block *block) {
     large_trie.push(block);
 }
 
-LIBC_INLINE void FreeStore::remove(Block *block) {
+LIBC_INLINE void FreeStore::remove(BlockRef block) {
   if (too_small(block))
     return;
   if (is_small(block)) {
     small_list(block).remove(
-        reinterpret_cast<FreeList::Node *>(block->usable_space()));
+        reinterpret_cast<FreeList::Node *>(block.usable_space()));
   } else {
-    large_trie.remove(
-        reinterpret_cast<FreeTrie::Node *>(block->usable_space()));
+    large_trie.remove(reinterpret_cast<FreeTrie::Node *>(block.usable_space()));
   }
 }
 
-LIBC_INLINE Block *FreeStore::remove_best_fit(size_t size) {
+LIBC_INLINE BlockRef FreeStore::remove_best_fit(size_t size) {
   if (FreeList *list = find_best_small_fit(size)) {
-    Block *block = list->front();
+    BlockRef block = list->front();
     list->pop();
     return block;
   }
   if (FreeTrie::Node *best_fit = large_trie.find_best_fit(size)) {
-    Block *block = best_fit->block();
+    BlockRef block = best_fit->block();
     large_trie.remove(best_fit);
     return block;
   }
-  return nullptr;
+  return BlockRef();
 }
 
-LIBC_INLINE FreeList &FreeStore::small_list(Block *block) {
+LIBC_INLINE FreeList &FreeStore::small_list(BlockRef block) {
   LIBC_ASSERT(is_small(block) && "only legal for small blocks");
-  return small_lists[(block->outer_size() - MIN_OUTER_SIZE) / Block::MIN_ALIGN];
+  return small_lists[(block.outer_size() - MIN_OUTER_SIZE) /
+                     BlockRef::MIN_ALIGN];
 }
 
 LIBC_INLINE FreeList *FreeStore::find_best_small_fit(size_t size) {
diff --git a/libc/src/__support/freetrie.h b/libc/src/__support/freetrie.h
index c1a8306c6f8d2..9e35463462b38 100644
--- a/libc/src/__support/freetrie.h
+++ b/libc/src/__support/freetrie.h
@@ -1,10 +1,15 @@
-//===-- Interface 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
 //
 //===----------------------------------------------------------------------===//
+///
+/// \file
+/// Interface for freetrie.
+///
+//===----------------------------------------------------------------------===//
 
 #ifndef LLVM_LIBC_SRC___SUPPORT_FREETRIE_H
 #define LLVM_LIBC_SRC___SUPPORT_FREETRIE_H
@@ -96,7 +101,7 @@ class FreeTrie {
   LIBC_INLINE bool empty() const { return !root; }
 
   /// Push a block to the trie.
-  void push(Block *block);
+  void push(BlockRef block);
 
   /// Remove a node from this trie node's free list.
   void remove(Node *node);
@@ -117,10 +122,10 @@ class FreeTrie {
   SizeRange range;
 };
 
-LIBC_INLINE void FreeTrie::push(Block *block) {
-  LIBC_ASSERT(block->inner_size_free() >= sizeof(Node) &&
+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();
+  size_t size = block.inner_size();
   LIBC_ASSERT(range.contains(size) && "requested size out of trie range");
 
   // Find the position in the tree to push to.
@@ -139,7 +144,7 @@ LIBC_INLINE void FreeTrie::push(Block *block) {
     }
   }
 
-  Node *node = new (block->usable_space()) Node;
+  Node *node = new (block.usable_space()) Node;
   FreeList list = *cur;
   if (list.empty()) {
     node->parent = parent;
diff --git a/libc/test/src/__support/block_test.cpp b/libc/test/src/__support/block_test.cpp
index 189e3cab1d784..6bd0ab5be24c0 100644
--- a/libc/test/src/__support/block_test.cpp
+++ b/libc/test/src/__support/block_test.cpp
@@ -1,10 +1,15 @@
-//===-- Unittests for a block of memory -------------------------*- C++ -*-===//
+//===----------------------------------------------------------------------===//
 //
 // 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
+/// Unittests for a block of memory.
+///
+//===----------------------------------------------------------------------===//
 #include <stddef.h>
 
 #include "src/__support/CPP/array.h"
@@ -14,7 +19,7 @@
 #include "src/string/memcpy.h"
 #include "test/UnitTest/Test.h"
 
-using LIBC_NAMESPACE::Block;
+using LIBC_NAMESPACE::BlockRef;
 using LIBC_NAMESPACE::cpp::array;
 using LIBC_NAMESPACE::cpp::bit_ceil;
 using LIBC_NAMESPACE::cpp::byte;
@@ -22,54 +27,53 @@ using LIBC_NAMESPACE::cpp::span;
 
 TEST(LlvmLibcBlockTest, CanCreateSingleAlignedBlock) {
   constexpr size_t kN = 1024;
-  alignas(Block::MIN_ALIGN) array<byte, kN> bytes;
+  alignas(BlockRef::MIN_ALIGN) array<byte, kN> bytes;
 
-  auto result = Block::init(bytes);
+  auto result = BlockRef::init(bytes);
   ASSERT_TRUE(result.has_value());
-  Block *block = *result;
-
-  EXPECT_EQ(reinterpret_cast<uintptr_t>(block) % alignof(Block), size_t{0});
-  EXPECT_TRUE(block->is_usable_space_aligned(Block::MIN_ALIGN));
-
-  Block *last = block->next();
-  ASSERT_NE(last, static_cast<Block *>(nullptr));
-  EXPECT_EQ(reinterpret_cast<uintptr_t>(last) % alignof(Block), size_t{0});
-
-  EXPECT_EQ(last->outer_size(), sizeof(Block));
-  EXPECT_EQ(last->prev_free(), block);
-  EXPECT_TRUE(last->used());
-
-  size_t block_outer_size =
-      reinterpret_cast<uintptr_t>(last) - reinterpret_cast<uintptr_t>(block);
-  EXPECT_EQ(block->outer_size(), block_outer_size);
-  EXPECT_EQ(block->inner_size(),
-            block_outer_size - sizeof(Block) + Block::PREV_FIELD_SIZE);
-  EXPECT_EQ(block->prev_free(), static_cast<Block *>(nullptr));
-  EXPECT_FALSE(block->used());
+  BlockRef block = *result;
+
+  EXPECT_EQ(block.addr() % alignof(size_t), size_t{0});
+  EXPECT_TRUE(block.is_usable_space_aligned(BlockRef::MIN_ALIGN));
+
+  BlockRef last = block.next();
+  ASSERT_NE(last.addr(), BlockRef().addr());
+  EXPECT_EQ(last.addr() % alignof(size_t), size_t{0});
+
+  EXPECT_EQ(last.outer_size(), BlockRef::HEADER_SIZE);
+  EXPECT_EQ(last.prev_free().addr(), block.addr());
+  EXPECT_TRUE(last.used());
+
+  size_t block_outer_size = last.addr() - block.addr();
+  EXPECT_EQ(block.outer_size(), block_outer_size);
+  EXPECT_EQ(block.inner_size(), block_outer_size - BlockRef::HEADER_SIZE +
+                                    BlockRef::PREV_FIELD_SIZE);
+  EXPECT_EQ(block.prev_free().addr(), BlockRef().addr());
+  EXPECT_FALSE(block.used());
 }
 
 TEST(LlvmLibcBlockTest, CanCreateUnalignedSingleBlock) {
   constexpr size_t kN = 1024;
 
   // Force alignment, so we can un-force it below
-  alignas(Block::MIN_ALIGN) array<byte, kN> bytes;
+  alignas(BlockRef::MIN_ALIGN) array<byte, kN> bytes;
   span<byte> aligned(bytes);
 
-  auto result = Block::init(aligned.subspan(1));
+  auto result = BlockRef::init(aligned.subspan(1));
   EXPECT_TRUE(result.has_value());
 
-  Block *block = *result;
-  EXPECT_EQ(reinterpret_cast<uintptr_t>(block) % alignof(Block), size_t{0});
-  EXPECT_TRUE(block->is_usable_space_aligned(Block::MIN_ALIGN));
+  BlockRef block = *result;
+  EXPECT_EQ(block.addr() % alignof(size_t), size_t{0});
+  EXPECT_TRUE(block.is_usable_space_aligned(BlockRef::MIN_ALIGN));
 
-  Block *last = block->next();
-  ASSERT_NE(last, static_cast<Block *>(nullptr));
-  EXPECT_EQ(reinterpret_cast<uintptr_t>(last) % alignof(Block), size_t{0});
+  BlockRef last = block.next();
+  ASSERT_NE(last.addr(), BlockRef().addr());
+  EXPECT_EQ(last.addr() % alignof(size_t), size_t{0});
 }
 
 TEST(LlvmLibcBlockTest, CannotCreateTooSmallBlock) {
   array<byte, 2> bytes;
-  auto result = Block::init(bytes);
+  auto result = BlockRef::init(bytes);
   EXPECT_FALSE(result.has_value());
 }
 
@@ -79,55 +83,55 @@ TEST(LlvmLibcBlockTest, CanSplitBlock) {
   // Choose a split position such that the next block's usable space is 512
   // bytes from this one's. This should be sufficient for any machine's
   // alignment.
-  const size_t kSplitN = Block::inner_size(512);
+  const size_t kSplitN = BlockRef::inner_size(512);
 
   array<byte, kN> bytes;
-  auto result = Block::init(bytes);
+  auto result = BlockRef::init(bytes);
   ASSERT_TRUE(result.has_value());
-  auto *block1 = *result;
-  size_t orig_size = block1->outer_size();
+  BlockRef block1 = *result;
+  size_t orig_size = block1.outer_size();
 
-  result = block1->split(kSplitN);
+  result = block1.split(kSplitN);
   ASSERT_TRUE(result.has_value());
-  auto *block2 = *result;
+  BlockRef block2 = *result;
 
-  EXPECT_EQ(block1->inner_size(), kSplitN);
-  EXPECT_EQ(block1->outer_size(),
-            kSplitN - Block::PREV_FIELD_SIZE + sizeof(Block));
+  EXPECT_EQ(block1.inner_size(), kSplitN);
+  EXPECT_EQ(block1.outer_size(),
+            kSplitN - BlockRef::PREV_FIELD_SIZE + BlockRef::HEADER_SIZE);
 
-  EXPECT_EQ(block2->outer_size(), orig_size - block1->outer_size());
-  EXPECT_FALSE(block2->used());
-  EXPECT_EQ(reinterpret_cast<uintptr_t>(block2) % alignof(Block), size_t{0});
-  EXPECT_TRUE(block2->is_usable_space_aligned(Block::MIN_ALIGN));
+  EXPECT_EQ(block2.outer_size(), orig_size - block1.outer_size());
+  EXPECT_FALSE(block2.used());
+  EXPECT_EQ(block2.addr() % alignof(size_t), size_t{0});
+  EXPECT_TRUE(block2.is_usable_space_aligned(BlockRef::MIN_ALIGN));
 
-  EXPECT_EQ(block1->next(), block2);
-  EXPECT_EQ(block2->prev_free(), block1);
+  EXPECT_EQ(block1.next().addr(), block2.addr());
+  EXPECT_EQ(block2.prev_free().addr(), block1.addr());
 }
 
 TEST(LlvmLibcBlockTest, CanSplitBlockUnaligned) {
   constexpr size_t kN = 1024;
 
   array<byte, kN> bytes;
-  auto result = Block::init(bytes);
+  auto result = BlockRef::init(bytes);
   ASSERT_TRUE(result.has_value());
-  Block *block1 = *result;
-  size_t orig_size = block1->outer_size();
+  BlockRef block1 = *result;
+  size_t orig_size = block1.outer_size();
 
   constexpr size_t kSplitN = 513;
 
-  result = block1->split(kSplitN);
+  result = block1.split(kSplitN);
   ASSERT_TRUE(result.has_value());
-  Block *block2 = *result;
+  BlockRef block2 = *result;
 
-  EXPECT_GE(block1->inner_size(), kSplitN);
+  EXPECT_GE(block1.inner_size(), kSplitN);
 
-  EXPECT_EQ(block2->outer_size(), orig_size - block1->outer_size());
-  EXPECT_FALSE(block2->used());
-  EXPECT_EQ(reinterpret_cast<uintptr_t>(block2) % alignof(Block), size_t{0});
-  EXPECT_TRUE(block2->is_usable_space_aligned(Block::MIN_ALIGN));
+  EXPECT_EQ(block2.outer_size(), orig_size - block1.outer_size());
+  EXPECT_FALSE(block2.used());
+  EXPECT_EQ(block2.addr() % alignof(size_t), size_t{0});
+  EXPECT_TRUE(block2.is_usable_space_aligned(BlockRef::MIN_ALIGN));
 
-  EXPECT_EQ(block1->next(), block2);
-  EXPECT_EQ(block2->prev_free(), block1);
+  EXPECT_EQ(block1.next().addr(), block2.addr());
+  EXPECT_EQ(block2.prev_free().addr(), block1.addr());
 }
 
 TEST(LlvmLibcBlockTest, CanSplitMidBlock) {
@@ -135,9 +139,9 @@ TEST(LlvmLibcBlockTest, CanSplitMidBlock) {
   // pointers get rewired properly.
   // I.e.
   // [[             BLOCK 1            ]]
-  // block1->split()
+  // block1.split()
   // [[       BLOCK1       ]][[ BLOCK2 ]]
-  // block1->split()
+  // block1.split()
   // [[ BLOCK1 ]][[ BLOCK3 ]][[ BLOCK2 ]]
 
   constexpr size_t kN = 1024;
@@ -145,33 +149,33 @@ TEST(LlvmLibcBlockTest, CanSplitMidBlock) {
   constexpr size_t kSplit2 = 256;
 
   array<byte, kN> bytes;
-  auto result = Block::init(bytes);
+  auto result = BlockRef::init(bytes);
   ASSERT_TRUE(result.has_value());
-  Block *block1 = *result;
+  BlockRef block1 = *result;
 
-  result = block1->split(kSplit1);
+  result = block1.split(kSplit1);
   ASSERT_TRUE(result.has_value());
-  Block *block2 = *result;
+  BlockRef block2 = *result;
 
-  result = block1->split(kSplit2);
+  result = block1.split(kSplit2);
   ASSERT_TRUE(result.has_value());
-  Block *block3 = *result;
+  BlockRef block3 = *result;
 
-  EXPECT_EQ(block1->next(), block3);
-  EXPECT_EQ(block3->prev_free(), block1);
-  EXPECT_EQ(block3->next(), block2);
-  EXPECT_EQ(block2->prev_free(), block3);
+  EXPECT_EQ(block1.next().addr(), block3.addr());
+  EXPECT_EQ(block3.prev_free().addr(), block1.addr());
+  EXPECT_EQ(block3.next().addr(), block2.addr());
+  EXPECT_EQ(block2.prev_free().addr(), block3.addr());
 }
 
 TEST(LlvmLibcBlockTest, CannotSplitTooSmallBlock) {
   constexpr size_t kN = 64;
 
   array<byte, kN> bytes;
-  auto result = Block::init(bytes);
+  auto result = BlockRef::init(bytes);
   ASSERT_TRUE(result.has_value());
-  Block *block = *result;
+  BlockRef block = *result;
 
-  result = block->split(block->inner_size() + 1);
+  result = block.split(block.inner_size() + 1);
   ASSERT_FALSE(result.has_value());
 }
 
@@ -179,11 +183,11 @@ TEST(LlvmLibcBlockTest, CannotSplitBlockWithoutHeaderSpace) {
   constexpr size_t kN = 1024;
 
   array<byte, kN> bytes;
-  auto result = Block::init(bytes);
+  auto result = BlockRef::init(bytes);
   ASSERT_TRUE(result.has_value());
-  Block *block = *result;
+  BlockRef block = *result;
 
-  result = block->split(block->inner_size() - sizeof(Block) + 1);
+  result = block.split(block.inner_size() - BlockRef::HEADER_SIZE + 1);
   ASSERT_FALSE(result.has_value());
 }
 
@@ -192,11 +196,11 @@ TEST(LlvmLibcBlockTest, CannotMakeBlockLargerInSplit) {
   constexpr size_t kN = 1024;
 
   array<byte, kN> bytes;
-  auto result = Block::init(bytes);
+  auto result = BlockRef::init(bytes);
   ASSERT_TRUE(result.has_value());
-  Block *block = *result;
+  BlockRef block = *result;
 
-  result = block->split(block->inner_size() + 1);
+  result = block.split(block.inner_size() + 1);
   ASSERT_FALSE(result.has_value());
 }
 
@@ -205,13 +209,13 @@ TEST(LlvmLibcBlockTest, CanMakeMinimalSizeFirstBlock) {
   constexpr size_t kN = 1024;
 
   array<byte, kN> bytes;
-  auto result = Block::init(bytes);
+  auto result = BlockRef::init(bytes);
   ASSERT_TRUE(result.has_value());
-  Block *block = *result;
+  BlockRef block = *result;
 
-  result = block->split(0);
+  result = block.split(0);
   ASSERT_TRUE(result.has_value());
-  EXPECT_LE(block->outer_size(), sizeof(Block) + Block::MIN_ALIGN);
+  EXPECT_LE(block.outer_size(), BlockRef::HEADER_SIZE + BlockRef::MIN_ALIGN);
 }
 
 TEST(LlvmLibcBlockTest, CanMakeMinimalSizeSecondBlock) {
@@ -219,33 +223,34 @@ TEST(LlvmLibcBlockTest, CanMakeMinimalSizeSecondBlock) {
   constexpr size_t kN = 1024;
 
   array<byte, kN> bytes;
-  auto result = Block::init(bytes);
+  auto result = BlockRef::init(bytes);
   ASSERT_TRUE(result.has_value());
-  Block *block1 = *result;
+  BlockRef block1 = *result;
 
-  result = block1->split(Block::prev_possible_block_start(
-                             reinterpret_cast<uintptr_t>(block1->next())) -
-                         reinterpret_cast<uintptr_t>(block1->usable_space()) +
-                         Block::PREV_FIELD_SIZE);
+  result =
+      block1.split(BlockRef::prev_possible_block_start(block1.next().addr()) -
+                   reinterpret_cast<uintptr_t>(block1.usable_space()) +
+                   BlockRef::PREV_FIELD_SIZE);
   ASSERT_TRUE(result.has_value());
-  EXPECT_LE((*result)->outer_size(), sizeof(Block) + Block::MIN_ALIGN);
+  EXPECT_LE((*result).outer_size(),
+            BlockRef::HEADER_SIZE + BlockRef::MIN_ALIGN);
 }
 
 TEST(LlvmLibcBlockTest, CanMarkBlockUsed) {
   constexpr size_t kN = 1024;
 
   array<byte, kN> bytes;
-  auto result = Block::init(bytes);
+  auto result = BlockRef::init(bytes);
   ASSERT_TRUE(result.has_value());
-  Block *block = *result;
-  size_t orig_size = block->outer_size();
+  BlockRef block = *result;
+  size_t orig_size = block.outer_size();
 
-  block->mark_used();
-  EXPECT_TRUE(block->used());
-  EXPECT_EQ(block->outer_size(), orig_size);
+  block.mark_used();
+  EXPECT_TRUE(block.used());
+  EXPECT_EQ(block.outer_size(), orig_size);
 
-  block->mark_free();
-  EXPECT_FALSE(block->used());
+  block.mark_free();
+  EXPECT_FALSE(block.used());
 }
 
 TEST(LlvmLibcBlockTest, CanSplitUsedBlock) {
@@ -253,19 +258,19 @@ TEST(LlvmLibcBlockTest, CanSplitUsedBlock) {
   constexpr size_t kSplitN = 512;
 
   array<byte, kN> bytes;
-  auto result = Block::init(bytes);
+  auto result = BlockRef::init(bytes);
   ASSERT_TRUE(result.has_value());
-  Block *block = *result;
+  BlockRef block = *result;
 
-  block->mark_used();
-  result = block->split(kSplitN);
+  block.mark_used();
+  result = block.split(kSplitN);
   ASSERT_TRUE(result.has_value());
-  Block *new_block = *result;
+  BlockRef 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));
+  EXPECT_TRUE(block.used());
+  EXPECT_FALSE(new_block.used());
+  EXPECT_EQ(block.next().addr(), new_block.addr());
+  EXPECT_EQ(new_block.prev_free().addr(), BlockRef().addr());
 }
 
 TEST(LlvmLibcBlockTest, CanMergeWithNextBlock) {
@@ -275,25 +280,25 @@ TEST(LlvmLibcBlockTest, CanMergeWithNextBlock) {
   constexpr size_t kSplit1 = 512;
   constexpr size_t kSplit2 = 256;
   array<byte, kN> bytes;
-  auto result = Block::init(bytes);
+  auto result = BlockRef::init(bytes);
   ASSERT_TRUE(result.has_value());
-  Block *block1 = *result;
-  size_t total_size = block1->outer_size();
+  BlockRef block1 = *result;
+  size_t total_size = block1.outer_size();
 
-  result = block1->split(kSplit1);
+  result = block1.split(kSplit1);
   ASSERT_TRUE(result.has_value());
 
-  result = block1->split(kSplit2);
-  size_t block1_size = block1->outer_size();
+  result = block1.split(kSplit2);
+  size_t block1_size = block1.outer_size();
   ASSERT_TRUE(result.has_value());
-  Block *block3 = *result;
+  BlockRef block3 = *result;
 
-  EXPECT_TRUE(block3->merge_next());
+  EXPECT_TRUE(block3.merge_next());
 
-  EXPECT_EQ(block1->next(), block3);
-  EXPECT_EQ(block3->prev_free(), block1);
-  EXPECT_EQ(block1->outer_size(), block1_size);
-  EXPECT_EQ(block3->outer_size(), total_size - block1->outer_size());
+  EXPECT_EQ(block1.next().addr(), block3.addr());
+  EXPECT_EQ(block3.prev_free().addr(), block1.addr());
+  EXPECT_EQ(block1.outer_size(), block1_size);
+  EXPECT_EQ(block3.outer_size(), total_size - block1.outer_size());
 }
 
 TEST(LlvmLibcBlockTest, CannotMergeWithFirstOrLastBlock) {
@@ -301,16 +306,16 @@ TEST(LlvmLibcBlockTest, CannotMergeWithFirstOrLastBlock) {
   constexpr size_t kSplitN = 512;
 
   array<byte, kN> bytes;
-  auto result = Block::init(bytes);
+  auto result = BlockRef::init(bytes);
   ASSERT_TRUE(result.has_value());
-  Block *block1 = *result;
+  BlockRef block1 = *result;
 
   // Do a split, just to check that the checks on next/prev are different...
-  result = block1->split(kSplitN);
+  result = block1.split(kSplitN);
   ASSERT_TRUE(result.has_value());
-  Block *block2 = *result;
+  BlockRef block2 = *result;
 
-  EXPECT_FALSE(block2->merge_next());
+  EXPECT_FALSE(block2.merge_next());
 }
 
 TEST(LlvmLibcBlockTest, CannotMergeUsedBlock) {
@@ -318,40 +323,40 @@ TEST(LlvmLibcBlockTest, CannotMergeUsedBlock) {
   constexpr size_t kSplitN = 512;
 
   array<byte, kN> bytes;
-  auto result = Block::init(bytes);
+  auto result = BlockRef::init(bytes);
   ASSERT_TRUE(result.has_value());
-  Block *block = *result;
+  BlockRef block = *result;
 
   // Do a split, just to check that the checks on next/prev are different...
-  result = block->split(kSplitN);
+  result = block.split(kSplitN);
   ASSERT_TRUE(result.has_value());
 
-  block->mark_used();
-  EXPECT_FALSE(block->merge_next());
+  block.mark_used();
+  EXPECT_FALSE(block.merge_next());
 }
 
 TEST(LlvmLibcBlockTest, CanGetBlockFromUsableSpace) {
   array<byte, 1024> bytes;
-  auto result = Block::init(bytes);
+  auto result = BlockRef::init(bytes);
   ASSERT_TRUE(result.has_value());
-  Block *block1 = *result;
+  BlockRef block1 = *result;
 
-  void *ptr = block1->usable_space();
-  Block *block2 = Block::from_usable_space(ptr);
-  EXPECT_EQ(block1, block2);
+  void *ptr = block1.usable_space();
+  BlockRef block2 = BlockRef::from_usable_space(ptr);
+  EXPECT_EQ(block1.addr(), block2.addr());
 }
 
 TEST(LlvmLibcBlockTest, CanGetConstBlockFromUsableSpace) {
   constexpr size_t kN = 1024;
 
   array<byte, kN> bytes{};
-  auto result = Block::init(bytes);
+  auto result = BlockRef::init(bytes);
   ASSERT_TRUE(result.has_value());
-  const Block *block1 = *result;
+  BlockRef block1 = *result;
 
-  const void *ptr = block1->usable_space();
-  const Block *block2 = Block::from_usable_space(ptr);
-  EXPECT_EQ(block1, block2);
+  const void *ptr = block1.usable_space();
+  BlockRef block2 = BlockRef::from_usable_space(ptr);
+  EXPECT_EQ(block1.addr(), block2.addr());
 }
 
 TEST(LlvmLibcBlockTest, Allocate) {
@@ -360,30 +365,30 @@ TEST(LlvmLibcBlockTest, Allocate) {
   // Ensure we can allocate everything up to the block size within this block.
   for (size_t i = 0; i < kN; ++i) {
     array<byte, kN> bytes;
-    auto result = Block::init(bytes);
+    auto result = BlockRef::init(bytes);
     ASSERT_TRUE(result.has_value());
-    Block *block = *result;
+    BlockRef block = *result;
 
-    if (i > block->inner_size())
+    if (i > block.inner_size())
       continue;
 
-    auto info = Block::allocate(block, Block::MIN_ALIGN, i);
-    EXPECT_NE(info.block, static_cast<Block *>(nullptr));
+    auto info = BlockRef::allocate(block, BlockRef::MIN_ALIGN, i);
+    EXPECT_NE(info.block.addr(), BlockRef().addr());
   }
 
   // Ensure we can allocate a byte at every guaranteeable alignment.
-  for (size_t i = 1; i < kN / Block::MIN_ALIGN; ++i) {
+  for (size_t i = 1; i < kN / BlockRef::MIN_ALIGN; ++i) {
     array<byte, kN> bytes;
-    auto result = Block::init(bytes);
+    auto result = BlockRef::init(bytes);
     ASSERT_TRUE(result.has_value());
-    Block *block = *result;
+    BlockRef block = *result;
 
-    size_t alignment = i * Block::MIN_ALIGN;
-    if (Block::min_size_for_allocation(alignment, 1) > block->inner_size())
+    size_t alignment = i * BlockRef::MIN_ALIGN;
+    if (BlockRef::min_size_for_allocation(alignment, 1) > block.inner_size())
       continue;
 
-    auto info = Block::allocate(block, alignment, 1);
-    EXPECT_NE(info.block, static_cast<Block *>(nullptr));
+    auto info = BlockRef::allocate(block, alignment, 1);
+    EXPECT_NE(info.block.addr(), BlockRef().addr());
   }
 }
 
@@ -391,98 +396,97 @@ TEST(LlvmLibcBlockTest, AllocateAlreadyAligned) {
   constexpr size_t kN = 1024;
 
   array<byte, kN> bytes;
-  auto result = Block::init(bytes);
+  auto result = BlockRef::init(bytes);
   ASSERT_TRUE(result.has_value());
-  Block *block = *result;
-  uintptr_t orig_end = reinterpret_cast<uintptr_t>(block) + block->outer_size();
+  BlockRef block = *result;
+  uintptr_t orig_end = block.addr() + block.outer_size();
 
-  constexpr size_t SIZE = Block::PREV_FIELD_SIZE + 1;
+  constexpr size_t SIZE = BlockRef::PREV_FIELD_SIZE + 1;
 
   auto [aligned_block, prev, next] =
-      Block::allocate(block, Block::MIN_ALIGN, SIZE);
+      BlockRef::allocate(block, BlockRef::MIN_ALIGN, SIZE);
 
   // Since this is already aligned, there should be no previous block.
-  EXPECT_EQ(prev, static_cast<Block *>(nullptr));
+  EXPECT_EQ(prev.addr(), BlockRef().addr());
 
   // Ensure we the block is aligned and large enough.
-  EXPECT_NE(aligned_block, static_cast<Block *>(nullptr));
-  EXPECT_TRUE(aligned_block->is_usable_space_aligned(Block::MIN_ALIGN));
-  EXPECT_GE(aligned_block->inner_size(), SIZE);
+  EXPECT_NE(aligned_block.addr(), BlockRef().addr());
+  EXPECT_TRUE(aligned_block.is_usable_space_aligned(BlockRef::MIN_ALIGN));
+  EXPECT_GE(aligned_block.inner_size(), SIZE);
 
   // Check the next block.
-  EXPECT_NE(next, static_cast<Block *>(nullptr));
-  EXPECT_EQ(aligned_block->next(), next);
-  EXPECT_EQ(reinterpret_cast<uintptr_t>(next) + next->outer_size(), orig_end);
+  EXPECT_NE(next.addr(), BlockRef().addr());
+  EXPECT_EQ(aligned_block.next().addr(), next.addr());
+  EXPECT_EQ(next.addr() + next.outer_size(), orig_end);
 }
 
 TEST(LlvmLibcBlockTest, AllocateNeedsAlignment) {
   constexpr size_t kN = 1024;
 
   array<byte, kN> bytes;
-  auto result = Block::init(bytes);
+  auto result = BlockRef::init(bytes);
   ASSERT_TRUE(result.has_value());
-  Block *block = *result;
+  BlockRef block = *result;
 
-  uintptr_t orig_end = reinterpret_cast<uintptr_t>(block) + block->outer_size();
+  uintptr_t orig_end = block.addr() + block.outer_size();
 
   // Now pick an alignment such that the usable space is not already aligned to
   // it. We want to explicitly test that the block will split into one before
   // it.
-  size_t alignment = Block::MIN_ALIGN;
-  while (block->is_usable_space_aligned(alignment))
-    alignment += Block::MIN_ALIGN;
+  size_t alignment = BlockRef::MIN_ALIGN;
+  while (block.is_usable_space_aligned(alignment))
+    alignment += BlockRef::MIN_ALIGN;
 
-  auto [aligned_block, prev, next] = Block::allocate(block, alignment, 10);
+  auto [aligned_block, prev, next] = BlockRef::allocate(block, alignment, 10);
 
   // Check the previous block was created appropriately. Since this block is the
   // first block, a new one should be made before this.
-  EXPECT_NE(prev, static_cast<Block *>(nullptr));
-  EXPECT_EQ(aligned_block->prev_free(), prev);
-  EXPECT_EQ(prev->next(), aligned_block);
-  EXPECT_EQ(prev->outer_size(), reinterpret_cast<uintptr_t>(aligned_block) -
-                                    reinterpret_cast<uintptr_t>(prev));
+  EXPECT_NE(prev.addr(), BlockRef().addr());
+  EXPECT_EQ(aligned_block.prev_free().addr(), prev.addr());
+  EXPECT_EQ(prev.next().addr(), aligned_block.addr());
+  EXPECT_EQ(prev.outer_size(), aligned_block.addr() - prev.addr());
 
   // Ensure we the block is aligned and the size we expect.
-  EXPECT_NE(next, static_cast<Block *>(nullptr));
-  EXPECT_TRUE(aligned_block->is_usable_space_aligned(alignment));
+  EXPECT_NE(next.addr(), BlockRef().addr());
+  EXPECT_TRUE(aligned_block.is_usable_space_aligned(alignment));
 
   // Check the next block.
-  EXPECT_NE(next, static_cast<Block *>(nullptr));
-  EXPECT_EQ(aligned_block->next(), next);
-  EXPECT_EQ(reinterpret_cast<uintptr_t>(next) + next->outer_size(), orig_end);
+  EXPECT_NE(next.addr(), BlockRef().addr());
+  EXPECT_EQ(aligned_block.next().addr(), next.addr());
+  EXPECT_EQ(next.addr() + next.outer_size(), orig_end);
 }
 
 TEST(LlvmLibcBlockTest, PreviousBlockMergedIfNotFirst) {
   constexpr size_t kN = 1024;
 
   array<byte, kN> bytes;
-  auto result = Block::init(bytes);
+  auto result = BlockRef::init(bytes);
   ASSERT_TRUE(result.has_value());
-  Block *block = *result;
+  BlockRef block = *result;
 
   // Split the block roughly halfway and work on the second half.
-  auto result2 = block->split(kN / 2);
+  auto result2 = block.split(kN / 2);
   ASSERT_TRUE(result2.has_value());
-  Block *newblock = *result2;
-  ASSERT_EQ(newblock->prev_free(), block);
-  size_t old_prev_size = block->outer_size();
+  BlockRef newblock = *result2;
+  ASSERT_EQ(newblock.prev_free().addr(), block.addr());
+  size_t old_prev_size = block.outer_size();
 
   // Now pick an alignment such that the usable space is not already aligned to
   // it. We want to explicitly test that the block will split into one before
   // it.
-  size_t alignment = Block::MIN_ALIGN;
-  while (newblock->is_usable_space_aligned(alignment))
-    alignment += Block::MIN_ALIGN;
+  size_t alignment = BlockRef::MIN_ALIGN;
+  while (newblock.is_usable_space_aligned(alignment))
+    alignment += BlockRef::MIN_ALIGN;
 
   // Ensure we can allocate in the new block.
-  auto [aligned_block, prev, next] = Block::allocate(newblock, alignment, 1);
+  auto [aligned_block, prev, next] = BlockRef::allocate(newblock, alignment, 1);
 
   // Now there should be no new previous block. Instead, the padding we did
   // create should be merged into the original previous block.
-  EXPECT_EQ(prev, static_cast<Block *>(nullptr));
-  EXPECT_EQ(aligned_block->prev_free(), block);
-  EXPECT_EQ(block->next(), aligned_block);
-  EXPECT_GT(block->outer_size(), old_prev_size);
+  EXPECT_EQ(prev.addr(), BlockRef().addr());
+  EXPECT_EQ(aligned_block.prev_free().addr(), block.addr());
+  EXPECT_EQ(block.next().addr(), aligned_block.addr());
+  EXPECT_GT(block.outer_size(), old_prev_size);
 }
 
 TEST(LlvmLibcBlockTest, CanRemergeBlockAllocations) {
@@ -493,40 +497,40 @@ TEST(LlvmLibcBlockTest, CanRemergeBlockAllocations) {
   constexpr size_t kN = 1024;
 
   array<byte, kN> bytes;
-  auto result = Block::init(bytes);
+  auto result = BlockRef::init(bytes);
   ASSERT_TRUE(result.has_value());
-  Block *block = *result;
+  BlockRef block = *result;
 
-  Block *orig_block = block;
-  size_t orig_size = orig_block->outer_size();
+  BlockRef orig_block = block;
+  size_t orig_size = orig_block.outer_size();
 
-  Block *last = block->next();
+  BlockRef last = block.next();
 
-  ASSERT_EQ(block->prev_free(), static_cast<Block *>(nullptr));
+  ASSERT_EQ(block.prev_free().addr(), BlockRef().addr());
 
   // Now pick an alignment such that the usable space is not already aligned to
   // it. We want to explicitly test that the block will split into one before
   // it.
-  size_t alignment = Block::MIN_ALIGN;
-  while (block->is_usable_space_aligned(alignment))
-    alignment += Block::MIN_ALIGN;
+  size_t alignment = BlockRef::MIN_ALIGN;
+  while (block.is_usable_space_aligned(alignment))
+    alignment += BlockRef::MIN_ALIGN;
 
-  auto [aligned_block, prev, next] = Block::allocate(block, alignment, 1);
+  auto [aligned_block, prev, next] = BlockRef::allocate(block, alignment, 1);
 
   // Check we have the appropriate blocks.
-  ASSERT_NE(prev, static_cast<Block *>(nullptr));
-  ASSERT_EQ(aligned_block->prev_free(), prev);
-  EXPECT_NE(next, static_cast<Block *>(nullptr));
-  EXPECT_EQ(aligned_block->next(), next);
-  EXPECT_EQ(next->next(), last);
+  ASSERT_NE(prev.addr(), BlockRef().addr());
+  ASSERT_EQ(aligned_block.prev_free().addr(), prev.addr());
+  EXPECT_NE(next.addr(), BlockRef().addr());
+  EXPECT_EQ(aligned_block.next().addr(), next.addr());
+  EXPECT_EQ(next.next().addr(), last.addr());
 
   // Now check for successful merges.
-  EXPECT_TRUE(prev->merge_next());
-  EXPECT_EQ(prev->next(), next);
-  EXPECT_TRUE(prev->merge_next());
-  EXPECT_EQ(prev->next(), last);
+  EXPECT_TRUE(prev.merge_next());
+  EXPECT_EQ(prev.next().addr(), next.addr());
+  EXPECT_TRUE(prev.merge_next());
+  EXPECT_EQ(prev.next().addr(), last.addr());
 
   // We should have the original buffer.
-  EXPECT_EQ(prev, orig_block);
-  EXPECT_EQ(prev->outer_size(), orig_size);
+  EXPECT_EQ(prev.addr(), orig_block.addr());
+  EXPECT_EQ(prev.outer_size(), orig_size);
 }
diff --git a/libc/test/src/__support/freelist_heap_test.cpp b/libc/test/src/__support/freelist_heap_test.cpp
index 3002834a51619..68cc30152cd6d 100644
--- a/libc/test/src/__support/freelist_heap_test.cpp
+++ b/libc/test/src/__support/freelist_heap_test.cpp
@@ -1,10 +1,15 @@
-//===-- Unittests for freelist_heap ---------------------------------------===//
+//===----------------------------------------------------------------------===//
 //
 // 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
+/// Unittests for freelist_heap.
+///
+//===----------------------------------------------------------------------===//
 
 #include "src/__support/CPP/span.h"
 #include "src/__support/freelist_heap.h"
@@ -23,7 +28,7 @@ asm(R"(
 __llvm_libc_heap_limit:
 )");
 
-using LIBC_NAMESPACE::Block;
+using LIBC_NAMESPACE::BlockRef;
 using LIBC_NAMESPACE::freelist_heap;
 using LIBC_NAMESPACE::FreeListHeap;
 using LIBC_NAMESPACE::FreeListHeapBuffer;
@@ -124,12 +129,12 @@ TEST_FOR_EACH_ALLOCATOR(ReturnedPointersAreAligned, 2048) {
   void *ptr1 = allocator.allocate(1);
 
   uintptr_t ptr1_start = reinterpret_cast<uintptr_t>(ptr1);
-  EXPECT_EQ(ptr1_start % Block::MIN_ALIGN, static_cast<size_t>(0));
+  EXPECT_EQ(ptr1_start % BlockRef::MIN_ALIGN, static_cast<size_t>(0));
 
   void *ptr2 = allocator.allocate(1);
   uintptr_t ptr2_start = reinterpret_cast<uintptr_t>(ptr2);
 
-  EXPECT_EQ(ptr2_start % Block::MIN_ALIGN, static_cast<size_t>(0));
+  EXPECT_EQ(ptr2_start % BlockRef::MIN_ALIGN, static_cast<size_t>(0));
 }
 
 TEST_FOR_EACH_ALLOCATOR(CanRealloc, 2048) {
diff --git a/libc/test/src/__support/freelist_test.cpp b/libc/test/src/__support/freelist_test.cpp
index bd5ecec45d921..580f59ae62bd8 100644
--- a/libc/test/src/__support/freelist_test.cpp
+++ b/libc/test/src/__support/freelist_test.cpp
@@ -1,53 +1,58 @@
-//===-- Unittests for a freelist --------------------------------*- C++ -*-===//
+//===----------------------------------------------------------------------===//
 //
 // 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
+/// Unittests for a freelist.
+///
+//===----------------------------------------------------------------------===//
 
 #include <stddef.h>
 
 #include "src/__support/freelist.h"
 #include "test/UnitTest/Test.h"
 
-using LIBC_NAMESPACE::Block;
+using LIBC_NAMESPACE::BlockRef;
 using LIBC_NAMESPACE::FreeList;
 using LIBC_NAMESPACE::cpp::byte;
 using LIBC_NAMESPACE::cpp::optional;
 
 TEST(LlvmLibcFreeList, FreeList) {
   byte mem[1024];
-  optional<Block *> maybeBlock = Block::init(mem);
+  optional<BlockRef> maybeBlock = BlockRef::init(mem);
   ASSERT_TRUE(maybeBlock.has_value());
-  Block *block1 = *maybeBlock;
+  BlockRef block1 = *maybeBlock;
 
-  maybeBlock = block1->split(128);
+  maybeBlock = block1.split(128);
   ASSERT_TRUE(maybeBlock.has_value());
-  Block *block2 = *maybeBlock;
+  BlockRef block2 = *maybeBlock;
 
-  maybeBlock = block2->split(128);
+  maybeBlock = block2.split(128);
   ASSERT_TRUE(maybeBlock.has_value());
 
   FreeList list;
   list.push(block1);
   ASSERT_FALSE(list.empty());
-  EXPECT_EQ(list.front(), block1);
+  EXPECT_EQ(list.front().addr(), block1.addr());
 
   list.push(block2);
-  EXPECT_EQ(list.front(), block1);
+  EXPECT_EQ(list.front().addr(), block1.addr());
 
   list.pop();
   ASSERT_FALSE(list.empty());
-  EXPECT_EQ(list.front(), block2);
+  EXPECT_EQ(list.front().addr(), block2.addr());
 
   list.pop();
   ASSERT_TRUE(list.empty());
 
   list.push(block1);
   list.push(block2);
-  list.remove(reinterpret_cast<FreeList::Node *>(block2->usable_space()));
-  EXPECT_EQ(list.front(), block1);
+  list.remove(reinterpret_cast<FreeList::Node *>(block2.usable_space()));
+  EXPECT_EQ(list.front().addr(), block1.addr());
   list.pop();
   ASSERT_TRUE(list.empty());
 }
diff --git a/libc/test/src/__support/freestore_test.cpp b/libc/test/src/__support/freestore_test.cpp
index 7017d6b9ebe93..61103a9126c08 100644
--- a/libc/test/src/__support/freestore_test.cpp
+++ b/libc/test/src/__support/freestore_test.cpp
@@ -1,17 +1,22 @@
-//===-- Unittests for a freestore -------------------------------*- C++ -*-===//
+//===----------------------------------------------------------------------===//
 //
 // 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
+/// Unittests for a freestore.
+///
+//===----------------------------------------------------------------------===//
 
 #include <stddef.h>
 
 #include "src/__support/freestore.h"
 #include "test/UnitTest/Test.h"
 
-using LIBC_NAMESPACE::Block;
+using LIBC_NAMESPACE::BlockRef;
 using LIBC_NAMESPACE::FreeList;
 using LIBC_NAMESPACE::FreeStore;
 using LIBC_NAMESPACE::FreeTrie;
@@ -21,44 +26,46 @@ using LIBC_NAMESPACE::cpp::optional;
 // Inserting or removing blocks too small to be tracked does nothing.
 TEST(LlvmLibcFreeStore, TooSmall) {
   byte mem[1024];
-  optional<Block *> maybeBlock = Block::init(mem);
+  optional<BlockRef> maybeBlock = BlockRef::init(mem);
   ASSERT_TRUE(maybeBlock.has_value());
-  Block *too_small = *maybeBlock;
-  maybeBlock = too_small->split(Block::PREV_FIELD_SIZE);
+  BlockRef too_small = *maybeBlock;
+  maybeBlock = too_small.split(BlockRef::PREV_FIELD_SIZE);
   ASSERT_TRUE(maybeBlock.has_value());
   // On platforms with high alignment the smallest legal block may be large
   // enough for a node.
-  if (too_small->outer_size() >= sizeof(Block) + sizeof(FreeList::Node))
+  if (too_small.outer_size() >= BlockRef::HEADER_SIZE + sizeof(FreeList::Node))
     return;
-  Block *remainder = *maybeBlock;
+  BlockRef remainder = *maybeBlock;
 
   FreeStore store;
   store.set_range({0, 4096});
   store.insert(too_small);
   store.insert(remainder);
 
-  EXPECT_EQ(store.remove_best_fit(too_small->inner_size()), remainder);
+  EXPECT_EQ(store.remove_best_fit(too_small.inner_size()).addr(),
+            remainder.addr());
   store.remove(too_small);
 }
 
 TEST(LlvmLibcFreeStore, RemoveBestFit) {
   byte mem[1024];
-  optional<Block *> maybeBlock = Block::init(mem);
+  optional<BlockRef> maybeBlock = BlockRef::init(mem);
   ASSERT_TRUE(maybeBlock.has_value());
 
-  Block *smallest = *maybeBlock;
-  maybeBlock = smallest->split(sizeof(FreeList::Node) + Block::PREV_FIELD_SIZE);
+  BlockRef smallest = *maybeBlock;
+  maybeBlock =
+      smallest.split(sizeof(FreeList::Node) + BlockRef::PREV_FIELD_SIZE);
   ASSERT_TRUE(maybeBlock.has_value());
 
-  Block *largest_small = *maybeBlock;
-  maybeBlock = largest_small->split(sizeof(FreeTrie::Node) +
-                                    Block::PREV_FIELD_SIZE - Block::MIN_ALIGN);
+  BlockRef largest_small = *maybeBlock;
+  maybeBlock = largest_small.split(
+      sizeof(FreeTrie::Node) + BlockRef::PREV_FIELD_SIZE - BlockRef::MIN_ALIGN);
   ASSERT_TRUE(maybeBlock.has_value());
-  if (largest_small->inner_size() == smallest->inner_size())
+  if (largest_small.inner_size() == smallest.inner_size())
     largest_small = smallest;
-  ASSERT_GE(largest_small->inner_size(), smallest->inner_size());
+  ASSERT_GE(largest_small.inner_size(), smallest.inner_size());
 
-  Block *remainder = *maybeBlock;
+  BlockRef remainder = *maybeBlock;
 
   FreeStore store;
   store.set_range({0, 4096});
@@ -68,32 +75,37 @@ TEST(LlvmLibcFreeStore, RemoveBestFit) {
   store.insert(remainder);
 
   // Find exact match for smallest.
-  ASSERT_EQ(store.remove_best_fit(smallest->inner_size()), smallest);
+  ASSERT_EQ(store.remove_best_fit(smallest.inner_size()).addr(),
+            smallest.addr());
   store.insert(smallest);
 
   // Find exact match for largest.
-  ASSERT_EQ(store.remove_best_fit(largest_small->inner_size()), largest_small);
+  ASSERT_EQ(store.remove_best_fit(largest_small.inner_size()).addr(),
+            largest_small.addr());
   store.insert(largest_small);
 
   // Search small list for best fit.
-  Block *next_smallest = largest_small == smallest ? remainder : largest_small;
-  ASSERT_EQ(store.remove_best_fit(smallest->inner_size() + 1), next_smallest);
+  BlockRef next_smallest =
+      largest_small == smallest ? remainder : largest_small;
+  ASSERT_EQ(store.remove_best_fit(smallest.inner_size() + 1).addr(),
+            next_smallest.addr());
   store.insert(next_smallest);
 
   // Continue search for best fit to large blocks.
-  EXPECT_EQ(store.remove_best_fit(largest_small->inner_size() + 1), remainder);
+  EXPECT_EQ(store.remove_best_fit(largest_small.inner_size() + 1).addr(),
+            remainder.addr());
 }
 
 TEST(LlvmLibcFreeStore, Remove) {
   byte mem[1024];
-  optional<Block *> maybeBlock = Block::init(mem);
+  optional<BlockRef> maybeBlock = BlockRef::init(mem);
   ASSERT_TRUE(maybeBlock.has_value());
 
-  Block *small = *maybeBlock;
-  maybeBlock = small->split(sizeof(FreeList::Node) + Block::PREV_FIELD_SIZE);
+  BlockRef small = *maybeBlock;
+  maybeBlock = small.split(sizeof(FreeList::Node) + BlockRef::PREV_FIELD_SIZE);
   ASSERT_TRUE(maybeBlock.has_value());
 
-  Block *remainder = *maybeBlock;
+  BlockRef remainder = *maybeBlock;
 
   FreeStore store;
   store.set_range({0, 4096});
@@ -101,9 +113,9 @@ TEST(LlvmLibcFreeStore, Remove) {
   store.insert(remainder);
 
   store.remove(remainder);
-  ASSERT_EQ(store.remove_best_fit(remainder->inner_size()),
-            static_cast<Block *>(nullptr));
+  ASSERT_EQ(store.remove_best_fit(remainder.inner_size()).addr(),
+            BlockRef().addr());
   store.remove(small);
-  ASSERT_EQ(store.remove_best_fit(small->inner_size()),
-            static_cast<Block *>(nullptr));
+  ASSERT_EQ(store.remove_best_fit(small.inner_size()).addr(),
+            BlockRef().addr());
 }
diff --git a/libc/test/src/__support/freetrie_test.cpp b/libc/test/src/__support/freetrie_test.cpp
index 5663a01687294..bf3284b2faf64 100644
--- a/libc/test/src/__support/freetrie_test.cpp
+++ b/libc/test/src/__support/freetrie_test.cpp
@@ -1,17 +1,22 @@
-//===-- Unittests for a freetrie --------------------------------*- C++ -*-===//
+//===----------------------------------------------------------------------===//
 //
 // 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
+/// Unittests for a freetrie.
+///
+//===----------------------------------------------------------------------===//
 
 #include <stddef.h>
 
 #include "src/__support/freetrie.h"
 #include "test/UnitTest/Test.h"
 
-using LIBC_NAMESPACE::Block;
+using LIBC_NAMESPACE::BlockRef;
 using LIBC_NAMESPACE::FreeTrie;
 using LIBC_NAMESPACE::cpp::byte;
 using LIBC_NAMESPACE::cpp::optional;
@@ -21,65 +26,67 @@ TEST(LlvmLibcFreeTrie, FindBestFitRoot) {
   EXPECT_EQ(trie.find_best_fit(123), static_cast<FreeTrie::Node *>(nullptr));
 
   byte mem[1024];
-  optional<Block *> maybeBlock = Block::init(mem);
+  optional<BlockRef> maybeBlock = BlockRef::init(mem);
   ASSERT_TRUE(maybeBlock.has_value());
-  Block *block = *maybeBlock;
+  BlockRef block = *maybeBlock;
   trie.push(block);
 
   FreeTrie::Node *root = trie.find_best_fit(0);
-  ASSERT_EQ(root->block(), block);
-  EXPECT_EQ(trie.find_best_fit(block->inner_size() - 1), root);
-  EXPECT_EQ(trie.find_best_fit(block->inner_size()), root);
-  EXPECT_EQ(trie.find_best_fit(block->inner_size() + 1),
+  ASSERT_EQ(root->block().addr(), block.addr());
+  EXPECT_EQ(trie.find_best_fit(block.inner_size() - 1), root);
+  EXPECT_EQ(trie.find_best_fit(block.inner_size()), root);
+  EXPECT_EQ(trie.find_best_fit(block.inner_size() + 1),
             static_cast<FreeTrie::Node *>(nullptr));
   EXPECT_EQ(trie.find_best_fit(4095), static_cast<FreeTrie::Node *>(nullptr));
 }
 
 TEST(LlvmLibcFreeTrie, FindBestFitLower) {
   byte mem[4096];
-  optional<Block *> maybeBlock = Block::init(mem);
+  optional<BlockRef> maybeBlock = BlockRef::init(mem);
   ASSERT_TRUE(maybeBlock.has_value());
-  Block *lower = *maybeBlock;
-  maybeBlock = lower->split(512);
+  BlockRef lower = *maybeBlock;
+  maybeBlock = lower.split(512);
   ASSERT_TRUE(maybeBlock.has_value());
-  Block *root = *maybeBlock;
+  BlockRef root = *maybeBlock;
 
   FreeTrie trie({0, 4096});
   trie.push(root);
   trie.push(lower);
 
-  EXPECT_EQ(trie.find_best_fit(0)->block(), lower);
+  EXPECT_EQ(trie.find_best_fit(0)->block().addr(), lower.addr());
 }
 
 TEST(LlvmLibcFreeTrie, FindBestFitUpper) {
   byte mem[4096];
-  optional<Block *> maybeBlock = Block::init(mem);
+  optional<BlockRef> maybeBlock = BlockRef::init(mem);
   ASSERT_TRUE(maybeBlock.has_value());
-  Block *root = *maybeBlock;
-  maybeBlock = root->split(512);
+  BlockRef root = *maybeBlock;
+  maybeBlock = root.split(512);
   ASSERT_TRUE(maybeBlock.has_value());
-  Block *upper = *maybeBlock;
+  BlockRef upper = *maybeBlock;
 
   FreeTrie trie({0, 4096});
   trie.push(root);
   trie.push(upper);
 
-  EXPECT_EQ(trie.find_best_fit(root->inner_size() + 1)->block(), upper);
+  EXPECT_EQ(trie.find_best_fit(root.inner_size() + 1)->block().addr(),
+            upper.addr());
   // The upper subtrie should be skipped if it could not contain a better fit.
-  EXPECT_EQ(trie.find_best_fit(root->inner_size() - 1)->block(), root);
+  EXPECT_EQ(trie.find_best_fit(root.inner_size() - 1)->block().addr(),
+            root.addr());
 }
 
 TEST(LlvmLibcFreeTrie, FindBestFitLowerAndUpper) {
   byte mem[4096];
-  optional<Block *> maybeBlock = Block::init(mem);
+  optional<BlockRef> maybeBlock = BlockRef::init(mem);
   ASSERT_TRUE(maybeBlock.has_value());
-  Block *root = *maybeBlock;
-  maybeBlock = root->split(1024);
+  BlockRef root = *maybeBlock;
+  maybeBlock = root.split(1024);
   ASSERT_TRUE(maybeBlock.has_value());
-  Block *lower = *maybeBlock;
-  maybeBlock = lower->split(128);
+  BlockRef lower = *maybeBlock;
+  maybeBlock = lower.split(128);
   ASSERT_TRUE(maybeBlock.has_value());
-  Block *upper = *maybeBlock;
+  BlockRef upper = *maybeBlock;
 
   FreeTrie trie({0, 4096});
   trie.push(root);
@@ -87,30 +94,30 @@ TEST(LlvmLibcFreeTrie, FindBestFitLowerAndUpper) {
   trie.push(upper);
 
   // The lower subtrie is examined first.
-  EXPECT_EQ(trie.find_best_fit(0)->block(), lower);
+  EXPECT_EQ(trie.find_best_fit(0)->block().addr(), lower.addr());
   // The upper subtrie is examined if there are no fits found in the upper
   // subtrie.
-  EXPECT_EQ(trie.find_best_fit(2048)->block(), upper);
+  EXPECT_EQ(trie.find_best_fit(2048)->block().addr(), upper.addr());
 }
 
 TEST(LlvmLibcFreeTrie, Remove) {
   byte mem[4096];
-  optional<Block *> maybeBlock = Block::init(mem);
+  optional<BlockRef> maybeBlock = BlockRef::init(mem);
   ASSERT_TRUE(maybeBlock.has_value());
-  Block *small1 = *maybeBlock;
-  maybeBlock = small1->split(512);
+  BlockRef small1 = *maybeBlock;
+  maybeBlock = small1.split(512);
   ASSERT_TRUE(maybeBlock.has_value());
-  Block *small2 = *maybeBlock;
-  maybeBlock = small2->split(512);
+  BlockRef small2 = *maybeBlock;
+  maybeBlock = small2.split(512);
   ASSERT_TRUE(maybeBlock.has_value());
-  ASSERT_EQ(small1->inner_size(), small2->inner_size());
-  Block *large = *maybeBlock;
+  ASSERT_EQ(small1.inner_size(), small2.inner_size());
+  BlockRef large = *maybeBlock;
 
   // Removing the root empties the trie.
   FreeTrie trie({0, 4096});
   trie.push(large);
   FreeTrie::Node *large_node = trie.find_best_fit(0);
-  ASSERT_EQ(large_node->block(), large);
+  ASSERT_EQ(large_node->block().addr(), large.addr());
   trie.remove(large_node);
   ASSERT_TRUE(trie.empty());
 
@@ -118,8 +125,10 @@ TEST(LlvmLibcFreeTrie, Remove) {
   trie.push(small1);
   trie.push(small2);
   trie.push(large);
-  trie.remove(trie.find_best_fit(small1->inner_size()));
-  EXPECT_EQ(trie.find_best_fit(large->inner_size())->block(), large);
-  trie.remove(trie.find_best_fit(small1->inner_size()));
-  EXPECT_EQ(trie.find_best_fit(large->inner_size())->block(), large);
+  trie.remove(trie.find_best_fit(small1.inner_size()));
+  EXPECT_EQ(trie.find_best_fit(large.inner_size())->block().addr(),
+            large.addr());
+  trie.remove(trie.find_best_fit(small1.inner_size()));
+  EXPECT_EQ(trie.find_best_fit(large.inner_size())->block().addr(),
+            large.addr());
 }

>From b658f15d28be09cf9ecd7f44eb974e082ce616d4 Mon Sep 17 00:00:00 2001
From: Yifan Zhu <yfzhu at google.com>
Date: Mon, 8 Jun 2026 08:18:49 -0700
Subject: [PATCH 2/2] address comments

---
 libc/src/__support/block.h         | 32 +++++++++++++++---------------
 libc/src/__support/freelist_heap.h |  5 +++++
 2 files changed, 21 insertions(+), 16 deletions(-)

diff --git a/libc/src/__support/block.h b/libc/src/__support/block.h
index cec59e952916d..ac0987a3ad9a7 100644
--- a/libc/src/__support/block.h
+++ b/libc/src/__support/block.h
@@ -44,7 +44,7 @@ LIBC_INLINE constexpr size_t align_up(size_t value, size_t alignment) {
 using ByteSpan = cpp::span<LIBC_NAMESPACE::cpp::byte>;
 using cpp::optional;
 
-/// Proxy for a memory region with links to adjacent blocks.
+/// Reference to a memory region with links to adjacent blocks.
 ///
 /// The blocks store their offsets to the previous and next blocks. The latter
 /// is also the block's size. The metadata is stored in raw bytes and accessed
@@ -104,13 +104,13 @@ class BlockRef {
   static constexpr size_t LAST_MASK = 1 << 1;
   static constexpr size_t SIZE_MASK = ~(PREV_FREE_MASK | LAST_MASK);
 
-  // Header field offsets. The previous offset is only meaningful when this
-  // block's PREV_FREE_MASK bit is set in the next field.
+  // Header field offsets. The value at PREV_OFFSET is only meaningful when the
+  // PREV_FREE_MASK bit is set in the next field.
   static constexpr size_t PREV_OFFSET = 0;
-  static constexpr size_t NEXT_OFFSET = sizeof(size_t);
+  static constexpr size_t NEXT_OFFSET = PREV_OFFSET + sizeof(size_t);
 
 public:
-  static constexpr size_t HEADER_SIZE = 2 * sizeof(size_t);
+  static constexpr size_t HEADER_SIZE = NEXT_OFFSET + sizeof(size_t);
 
   // To ensure block sizes have two lower unused bits, ensure usable space is
   // always aligned to at least 4 bytes. (The distances between usable spaces,
@@ -118,20 +118,20 @@ class BlockRef {
   static constexpr size_t MIN_ALIGN = cpp::max(size_t{4}, alignof(max_align_t));
 
   LIBC_INLINE constexpr BlockRef() = default;
-  LIBC_INLINE explicit constexpr BlockRef(cpp::byte *ptr) : self(ptr) {}
+  LIBC_INLINE explicit constexpr BlockRef(cpp::byte *ptr) : header_ptr(ptr) {}
   LIBC_INLINE explicit constexpr operator bool() const {
-    return self != nullptr;
+    return header_ptr != nullptr;
   }
   LIBC_INLINE constexpr bool operator==(BlockRef other) const {
-    return self == other.self;
+    return header_ptr == other.header_ptr;
   }
   LIBC_INLINE constexpr bool operator!=(BlockRef other) const {
     return !(*this == other);
   }
 
-  LIBC_INLINE cpp::byte *data() const { return self; }
+  LIBC_INLINE cpp::byte *data() const { return header_ptr; }
   LIBC_INLINE uintptr_t addr() const {
-    return reinterpret_cast<uintptr_t>(self);
+    return reinterpret_cast<uintptr_t>(header_ptr);
   }
 
   /// Initializes a given memory region into a first block and a sentinel last
@@ -195,14 +195,14 @@ class BlockRef {
   ///
   /// Aligned to some multiple of MIN_ALIGN.
   LIBC_INLINE cpp::byte *usable_space() const {
-    auto *s = self + HEADER_SIZE;
+    auto *s = header_ptr + HEADER_SIZE;
     LIBC_ASSERT(reinterpret_cast<uintptr_t>(s) % MIN_ALIGN == 0 &&
                 "usable space must be aligned to MIN_ALIGN");
     return s;
   }
 
   // @returns The region of memory the block manages, including the header.
-  LIBC_INLINE ByteSpan region() const { return {self, outer_size()}; }
+  LIBC_INLINE ByteSpan region() const { return {header_ptr, outer_size()}; }
 
   /// Attempts to split this block.
   ///
@@ -223,14 +223,14 @@ class BlockRef {
     size_t next_value = load_next();
     if (next_value & LAST_MASK)
       return BlockRef();
-    return BlockRef(self + (next_value & SIZE_MASK));
+    return BlockRef(header_ptr + (next_value & SIZE_MASK));
   }
 
   /// @returns The free block immediately before this one, otherwise null.
   LIBC_INLINE BlockRef prev_free() const {
     if (!(load_next() & PREV_FREE_MASK))
       return BlockRef();
-    return BlockRef(self - load_prev());
+    return BlockRef(header_ptr - load_prev());
   }
 
   /// @returns Whether the block is unavailable for allocation.
@@ -335,7 +335,7 @@ class BlockRef {
   }
 
   LIBC_INLINE cpp::byte *field_ptr(size_t offset) const {
-    cpp::byte *ptr = self + offset;
+    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,
@@ -387,7 +387,7 @@ class BlockRef {
     store_field(NEXT_OFFSET, value);
   }
 
-  cpp::byte *self = nullptr;
+  cpp::byte *header_ptr = nullptr;
 };
 
 // This is the return type for `allocate` which can split one block into up to
diff --git a/libc/src/__support/freelist_heap.h b/libc/src/__support/freelist_heap.h
index 05affc38fcb8c..a591d63ddd4e5 100644
--- a/libc/src/__support/freelist_heap.h
+++ b/libc/src/__support/freelist_heap.h
@@ -176,6 +176,11 @@ LIBC_INLINE bool FreeListHeap::shrink_in_place(BlockRef block, size_t size) {
     if (next.has_value()) {
       BlockRef next_block = *next;
       BlockRef right = next_block.next();
+      // Since the original block was not the last block (the sentinel last
+      // block is never split), the split-off remainder block `next_block` is
+      // also not the last block. Thus, its next block `right` is guaranteed
+      // to be non-null.
+      LIBC_ASSERT(right && "right block must be non-null");
       if (!right.used()) {
         free_store.remove(right);
         next_block.merge_next();



More information about the libc-commits mailing list