[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
Thu Jun 4 14:49:23 PDT 2026


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

>From b4fe4d372414063df58f0e274ab22e0c7162c2c3 Mon Sep 17 00:00:00 2001
From: Schrodinger ZHU Yifan <yfzhu at google.com>
Date: Tue, 2 Jun 2026 00:27:24 -0400
Subject: [PATCH 1/7] [libc] Use Block as a byte-backed proxy

---
 libc/src/__support/block.h                 | 336 +++++++++++----------
 libc/src/__support/freelist.cpp            |   4 +-
 libc/src/__support/freelist.h              |  18 +-
 libc/src/__support/freelist_heap.h         |  40 +--
 libc/src/__support/freestore.h             |  43 +--
 libc/src/__support/freetrie.h              |  10 +-
 libc/test/src/__support/block_test.cpp     | 333 ++++++++++----------
 libc/test/src/__support/freelist_test.cpp  |  20 +-
 libc/test/src/__support/freestore_test.cpp |  60 ++--
 libc/test/src/__support/freetrie_test.cpp  |  78 ++---
 10 files changed, 483 insertions(+), 459 deletions(-)

diff --git a/libc/src/__support/block.h b/libc/src/__support/block.h
index d45af1079a5bc..47ea7f75e25f0 100644
--- a/libc/src/__support/block.h
+++ b/libc/src/__support/block.h
@@ -10,16 +10,17 @@
 #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,10 +39,12 @@ 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.
@@ -91,47 +94,68 @@ 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.
+  // 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 Block() = default;
+  LIBC_INLINE explicit constexpr Block(cpp::byte *ptr) : self(ptr) {}
+  LIBC_INLINE explicit constexpr operator bool() const {
+    return self != nullptr;
+  }
+  LIBC_INLINE constexpr bool operator==(Block other) const {
+    return self == other.self;
+  }
+  LIBC_INLINE constexpr bool operator!=(Block 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<Block> 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 Block from_usable_space(void *usable_space) {
     auto *bytes = reinterpret_cast<cpp::byte *>(usable_space);
-    return reinterpret_cast<Block *>(bytes - sizeof(Block));
+    return Block(bytes - HEADER_SIZE);
   }
-  LIBC_INLINE static const Block *from_usable_space(const void *usable_space) {
+  LIBC_INLINE static Block 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 Block(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 +168,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 +182,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<Block> 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 Block next() const {
+    size_t next_value = load_next();
+    if (next_value & LAST_MASK)
+      return Block();
+    return Block(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 Block prev_free() const {
+    if (!(load_next() & PREV_FREE_MASK))
+      return Block();
+    return Block(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;
+    Block 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;
+    Block 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 +264,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,96 +280,112 @@ 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(Block 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 Block 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);
+    Block 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);
+    Block last(start);
+    last.store_next(HEADER_SIZE | LAST_MASK);
   }
 
-  /// 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 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");
+#if __has_builtin(__builtin_assume_aligned)
+    return reinterpret_cast<cpp::byte *>(
+        __builtin_assume_aligned(ptr, alignof(size_t)));
+#else
+    return ptr;
+#endif
+  }
 
-  /// 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;
+  LIBC_INLINE size_t load_field(size_t offset) const {
+    size_t value;
+    inline_memcpy(&value, field_ptr(offset), sizeof(value));
+    return value;
+  }
 
-  /// 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
-  /// `prev_free` flag, and the other bit is the `last` flag.
-  ///
-  /// * If the `prev_free` flag is set, the block isn't the first and the
-  ///   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 void store_field(size_t offset, size_t value) const {
+    inline_memcpy(field_ptr(offset), &value, sizeof(value));
+  }
 
-public:
-  /// Only for testing.
-  static constexpr size_t PREV_FIELD_SIZE = sizeof(prev_);
+  LIBC_INLINE size_t load_prev() const { return load_field(PREV_OFFSET); }
+  LIBC_INLINE size_t load_next() const { return load_field(NEXT_OFFSET); }
+  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 Block::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
+  // 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.
+  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;
 };
 
 LIBC_INLINE
-optional<Block *> Block::init(ByteSpan region) {
+optional<Block> Block::init(ByteSpan region) {
   if (!region.data())
     return {};
 
@@ -381,55 +402,55 @@ 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 =
+  Block 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) {
+Block::BlockInfo Block::allocate(Block block, size_t alignment, size_t size) {
   LIBC_ASSERT(alignment % MIN_ALIGN == 0 &&
               "alignment must be a multiple of MIN_ALIGN");
 
-  BlockInfo info{block, /*prev=*/nullptr, /*next=*/nullptr};
+  BlockInfo info{block, Block(), Block()};
 
-  if (!info.block->is_usable_space_aligned(alignment)) {
-    Block *original = info.block;
+  if (!info.block.is_usable_space_aligned(alignment)) {
+    Block original = info.block;
     // The padding block has no minimum size requirement.
-    optional<Block *> maybe_aligned_block = original->split(0, alignment);
+    optional<Block> 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 (Block 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) &&
+    Block 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<Block> 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<Block> Block::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");
   if (used())
@@ -437,9 +458,9 @@ optional<Block *> Block::split(size_t new_inner_size,
 
   // 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)
@@ -449,30 +470,29 @@ 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 {};
 
   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);
-  mark_free(); // Free status for this block is now stored in new_block.
-  new_block->next()->prev_ = new_region.size();
+  Block new_block = as_block(new_region);
+  new_block.mark_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 Block::merge_next() const {
+  Block 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..da0f28f09735b 100644
--- a/libc/src/__support/freelist.cpp
+++ b/libc/src/__support/freelist.cpp
@@ -12,8 +12,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(Block::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..7a307a6fe87d0 100644
--- a/libc/src/__support/freelist.h
+++ b/libc/src/__support/freelist.h
@@ -26,15 +26,13 @@ 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 Block block() const { return Block::from_usable_space(this); }
 
     /// @returns The block containing this node.
-    LIBC_INLINE Block *block() { return Block::from_usable_space(this); }
+    LIBC_INLINE Block block() { return Block::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 +56,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 Block 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(Block 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 66f3739efe214..ae3f3d164a51e 100644
--- a/libc/src/__support/freelist_heap.h
+++ b/libc/src/__support/freelist_heap.h
@@ -53,8 +53,8 @@ class FreeListHeap {
 
   void *allocate_impl(size_t alignment, 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(Block block) {
+    return span<cpp::byte>(block.usable_space(), block.inner_size());
   }
 
   bool is_valid_ptr(void *ptr) { return ptr >= begin && ptr < end; }
@@ -76,8 +76,8 @@ 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())});
+  Block block = *result;
+  free_store.set_range({0, cpp::bit_ceil(block.inner_size())});
   free_store.insert(block);
   is_initialized = true;
 }
@@ -93,7 +93,7 @@ LIBC_INLINE void *FreeListHeap::allocate_impl(size_t alignment, size_t size) {
   if (!request_size)
     return nullptr;
 
-  Block *block = free_store.remove_best_fit(request_size);
+  Block block = free_store.remove_best_fit(request_size);
   if (!block)
     return nullptr;
 
@@ -103,8 +103,8 @@ LIBC_INLINE void *FreeListHeap::allocate_impl(size_t alignment, size_t size) {
   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) {
@@ -135,24 +135,24 @@ 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();
+  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();
 
   // Can we combine with the left or right blocks?
-  Block *prev_free = block->prev_free();
-  Block *next = block->next();
+  Block prev_free = block.prev_free();
+  Block 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);
@@ -175,10 +175,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())
+  Block block = Block::from_usable_space(bytes);
+  if (!block.used())
     return nullptr;
-  size_t old_size = block->inner_size();
+  size_t old_size = block.inner_size();
 
   // Do nothing and return ptr if the required memory size is smaller than
   // the current size.
diff --git a/libc/src/__support/freestore.h b/libc/src/__support/freestore.h
index 2dcb4b10b93d5..ef6495081d123 100644
--- a/libc/src/__support/freestore.h
+++ b/libc/src/__support/freestore.h
@@ -16,6 +16,8 @@ namespace LIBC_NAMESPACE_DECL {
 /// A best-fit store of variously-sized free blocks. Blocks can be inserted and
 /// removed in logarithmic time.
 class FreeStore {
+  friend class FreeListHeap;
+
 public:
   FreeStore() = default;
   FreeStore(const FreeStore &other) = delete;
@@ -29,39 +31,39 @@ class FreeStore {
 
   /// Insert a free block. If the block is too small to be tracked, nothing
   /// happens.
-  void insert(Block *block);
+  void insert(Block block);
 
   /// Remove a free block. If the block is too small to be tracked, nothing
   /// happens.
-  void remove(Block *block);
+  void remove(Block 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);
+  Block remove_best_fit(size_t size);
 
 private:
   static constexpr size_t MIN_OUTER_SIZE =
-      align_up(sizeof(Block) + sizeof(FreeList::Node), Block::MIN_ALIGN);
+      align_up(Block::HEADER_SIZE + sizeof(FreeList::Node), Block::MIN_ALIGN);
   static constexpr size_t MIN_LARGE_OUTER_SIZE =
-      align_up(sizeof(Block) + sizeof(FreeTrie::Node), Block::MIN_ALIGN);
+      align_up(Block::HEADER_SIZE + sizeof(FreeTrie::Node), Block::MIN_ALIGN);
   static constexpr size_t NUM_SMALL_SIZES =
       (MIN_LARGE_OUTER_SIZE - MIN_OUTER_SIZE) / Block::MIN_ALIGN;
 
-  LIBC_INLINE static bool too_small(Block *block) {
-    return block->outer_size() < MIN_OUTER_SIZE;
+  LIBC_INLINE static bool too_small(Block 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(Block block) {
+    return block.outer_size() < MIN_LARGE_OUTER_SIZE;
   }
 
-  FreeList &small_list(Block *block);
+  FreeList &small_list(Block 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(Block block) {
   if (too_small(block))
     return;
   if (is_small(block))
@@ -70,35 +72,34 @@ LIBC_INLINE void FreeStore::insert(Block *block) {
     large_trie.push(block);
 }
 
-LIBC_INLINE void FreeStore::remove(Block *block) {
+LIBC_INLINE void FreeStore::remove(Block 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 Block FreeStore::remove_best_fit(size_t size) {
   if (FreeList *list = find_best_small_fit(size)) {
-    Block *block = list->front();
+    Block block = list->front();
     list->pop();
     return block;
   }
   if (FreeTrie::Node *best_fit = large_trie.find_best_fit(size)) {
-    Block *block = best_fit->block();
+    Block block = best_fit->block();
     large_trie.remove(best_fit);
     return block;
   }
-  return nullptr;
+  return Block();
 }
 
-LIBC_INLINE FreeList &FreeStore::small_list(Block *block) {
+LIBC_INLINE FreeList &FreeStore::small_list(Block 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) / Block::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..7d9dccb3ba21d 100644
--- a/libc/src/__support/freetrie.h
+++ b/libc/src/__support/freetrie.h
@@ -96,7 +96,7 @@ class FreeTrie {
   LIBC_INLINE bool empty() const { return !root; }
 
   /// Push a block to the trie.
-  void push(Block *block);
+  void push(Block block);
 
   /// Remove a node from this trie node's free list.
   void remove(Node *node);
@@ -117,10 +117,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(Block 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 +139,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 3029cde834a5d..ce5e441746ccf 100644
--- a/libc/test/src/__support/block_test.cpp
+++ b/libc/test/src/__support/block_test.cpp
@@ -26,26 +26,25 @@ TEST(LlvmLibcBlockTest, CanCreateSingleAlignedBlock) {
 
   auto result = Block::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());
+  Block block = *result;
+
+  EXPECT_EQ(block.addr() % alignof(size_t), size_t{0});
+  EXPECT_TRUE(block.is_usable_space_aligned(Block::MIN_ALIGN));
+
+  Block last = block.next();
+  ASSERT_NE(last.addr(), Block().addr());
+  EXPECT_EQ(last.addr() % alignof(size_t), size_t{0});
+
+  EXPECT_EQ(last.outer_size(), Block::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 - Block::HEADER_SIZE + Block::PREV_FIELD_SIZE);
+  EXPECT_EQ(block.prev_free().addr(), Block().addr());
+  EXPECT_FALSE(block.used());
 }
 
 TEST(LlvmLibcBlockTest, CanCreateUnalignedSingleBlock) {
@@ -58,13 +57,13 @@ TEST(LlvmLibcBlockTest, CanCreateUnalignedSingleBlock) {
   auto result = Block::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));
+  Block block = *result;
+  EXPECT_EQ(block.addr() % alignof(size_t), 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});
+  Block last = block.next();
+  ASSERT_NE(last.addr(), Block().addr());
+  EXPECT_EQ(last.addr() % alignof(size_t), size_t{0});
 }
 
 TEST(LlvmLibcBlockTest, CannotCreateTooSmallBlock) {
@@ -84,24 +83,24 @@ TEST(LlvmLibcBlockTest, CanSplitBlock) {
   array<byte, kN> bytes;
   auto result = Block::init(bytes);
   ASSERT_TRUE(result.has_value());
-  auto *block1 = *result;
-  size_t orig_size = block1->outer_size();
+  Block 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;
+  Block 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 - Block::PREV_FIELD_SIZE + Block::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(Block::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) {
@@ -110,24 +109,24 @@ TEST(LlvmLibcBlockTest, CanSplitBlockUnaligned) {
   array<byte, kN> bytes;
   auto result = Block::init(bytes);
   ASSERT_TRUE(result.has_value());
-  Block *block1 = *result;
-  size_t orig_size = block1->outer_size();
+  Block 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;
+  Block 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(Block::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 +134,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;
@@ -147,20 +146,20 @@ TEST(LlvmLibcBlockTest, CanSplitMidBlock) {
   array<byte, kN> bytes;
   auto result = Block::init(bytes);
   ASSERT_TRUE(result.has_value());
-  Block *block1 = *result;
+  Block block1 = *result;
 
-  result = block1->split(kSplit1);
+  result = block1.split(kSplit1);
   ASSERT_TRUE(result.has_value());
-  Block *block2 = *result;
+  Block block2 = *result;
 
-  result = block1->split(kSplit2);
+  result = block1.split(kSplit2);
   ASSERT_TRUE(result.has_value());
-  Block *block3 = *result;
+  Block 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) {
@@ -169,9 +168,9 @@ TEST(LlvmLibcBlockTest, CannotSplitTooSmallBlock) {
   array<byte, kN> bytes;
   auto result = Block::init(bytes);
   ASSERT_TRUE(result.has_value());
-  Block *block = *result;
+  Block block = *result;
 
-  result = block->split(block->inner_size() + 1);
+  result = block.split(block.inner_size() + 1);
   ASSERT_FALSE(result.has_value());
 }
 
@@ -181,9 +180,9 @@ TEST(LlvmLibcBlockTest, CannotSplitBlockWithoutHeaderSpace) {
   array<byte, kN> bytes;
   auto result = Block::init(bytes);
   ASSERT_TRUE(result.has_value());
-  Block *block = *result;
+  Block block = *result;
 
-  result = block->split(block->inner_size() - sizeof(Block) + 1);
+  result = block.split(block.inner_size() - Block::HEADER_SIZE + 1);
   ASSERT_FALSE(result.has_value());
 }
 
@@ -194,9 +193,9 @@ TEST(LlvmLibcBlockTest, CannotMakeBlockLargerInSplit) {
   array<byte, kN> bytes;
   auto result = Block::init(bytes);
   ASSERT_TRUE(result.has_value());
-  Block *block = *result;
+  Block block = *result;
 
-  result = block->split(block->inner_size() + 1);
+  result = block.split(block.inner_size() + 1);
   ASSERT_FALSE(result.has_value());
 }
 
@@ -207,11 +206,11 @@ TEST(LlvmLibcBlockTest, CanMakeMinimalSizeFirstBlock) {
   array<byte, kN> bytes;
   auto result = Block::init(bytes);
   ASSERT_TRUE(result.has_value());
-  Block *block = *result;
+  Block 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(), Block::HEADER_SIZE + Block::MIN_ALIGN);
 }
 
 TEST(LlvmLibcBlockTest, CanMakeMinimalSizeSecondBlock) {
@@ -221,14 +220,13 @@ TEST(LlvmLibcBlockTest, CanMakeMinimalSizeSecondBlock) {
   array<byte, kN> bytes;
   auto result = Block::init(bytes);
   ASSERT_TRUE(result.has_value());
-  Block *block1 = *result;
+  Block 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(Block::prev_possible_block_start(block1.next().addr()) -
+                        reinterpret_cast<uintptr_t>(block1.usable_space()) +
+                        Block::PREV_FIELD_SIZE);
   ASSERT_TRUE(result.has_value());
-  EXPECT_LE((*result)->outer_size(), sizeof(Block) + Block::MIN_ALIGN);
+  EXPECT_LE((*result).outer_size(), Block::HEADER_SIZE + Block::MIN_ALIGN);
 }
 
 TEST(LlvmLibcBlockTest, CanMarkBlockUsed) {
@@ -237,15 +235,15 @@ TEST(LlvmLibcBlockTest, CanMarkBlockUsed) {
   array<byte, kN> bytes;
   auto result = Block::init(bytes);
   ASSERT_TRUE(result.has_value());
-  Block *block = *result;
-  size_t orig_size = block->outer_size();
+  Block 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, CannotSplitUsedBlock) {
@@ -255,10 +253,10 @@ TEST(LlvmLibcBlockTest, CannotSplitUsedBlock) {
   array<byte, kN> bytes;
   auto result = Block::init(bytes);
   ASSERT_TRUE(result.has_value());
-  Block *block = *result;
+  Block block = *result;
 
-  block->mark_used();
-  result = block->split(kSplitN);
+  block.mark_used();
+  result = block.split(kSplitN);
   ASSERT_FALSE(result.has_value());
 }
 
@@ -271,23 +269,23 @@ TEST(LlvmLibcBlockTest, CanMergeWithNextBlock) {
   array<byte, kN> bytes;
   auto result = Block::init(bytes);
   ASSERT_TRUE(result.has_value());
-  Block *block1 = *result;
-  size_t total_size = block1->outer_size();
+  Block 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;
+  Block 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) {
@@ -297,14 +295,14 @@ TEST(LlvmLibcBlockTest, CannotMergeWithFirstOrLastBlock) {
   array<byte, kN> bytes;
   auto result = Block::init(bytes);
   ASSERT_TRUE(result.has_value());
-  Block *block1 = *result;
+  Block 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;
+  Block block2 = *result;
 
-  EXPECT_FALSE(block2->merge_next());
+  EXPECT_FALSE(block2.merge_next());
 }
 
 TEST(LlvmLibcBlockTest, CannotMergeUsedBlock) {
@@ -314,25 +312,25 @@ TEST(LlvmLibcBlockTest, CannotMergeUsedBlock) {
   array<byte, kN> bytes;
   auto result = Block::init(bytes);
   ASSERT_TRUE(result.has_value());
-  Block *block = *result;
+  Block 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);
   ASSERT_TRUE(result.has_value());
-  Block *block1 = *result;
+  Block block1 = *result;
 
-  void *ptr = block1->usable_space();
-  Block *block2 = Block::from_usable_space(ptr);
-  EXPECT_EQ(block1, block2);
+  void *ptr = block1.usable_space();
+  Block block2 = Block::from_usable_space(ptr);
+  EXPECT_EQ(block1.addr(), block2.addr());
 }
 
 TEST(LlvmLibcBlockTest, CanGetConstBlockFromUsableSpace) {
@@ -341,11 +339,11 @@ TEST(LlvmLibcBlockTest, CanGetConstBlockFromUsableSpace) {
   array<byte, kN> bytes{};
   auto result = Block::init(bytes);
   ASSERT_TRUE(result.has_value());
-  const Block *block1 = *result;
+  Block 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();
+  Block block2 = Block::from_usable_space(ptr);
+  EXPECT_EQ(block1.addr(), block2.addr());
 }
 
 TEST(LlvmLibcBlockTest, Allocate) {
@@ -356,13 +354,13 @@ TEST(LlvmLibcBlockTest, Allocate) {
     array<byte, kN> bytes;
     auto result = Block::init(bytes);
     ASSERT_TRUE(result.has_value());
-    Block *block = *result;
+    Block 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));
+    EXPECT_NE(info.block.addr(), Block().addr());
   }
 
   // Ensure we can allocate a byte at every guaranteeable alignment.
@@ -370,14 +368,14 @@ TEST(LlvmLibcBlockTest, Allocate) {
     array<byte, kN> bytes;
     auto result = Block::init(bytes);
     ASSERT_TRUE(result.has_value());
-    Block *block = *result;
+    Block block = *result;
 
     size_t alignment = i * Block::MIN_ALIGN;
-    if (Block::min_size_for_allocation(alignment, 1) > block->inner_size())
+    if (Block::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));
+    EXPECT_NE(info.block.addr(), Block().addr());
   }
 }
 
@@ -387,8 +385,8 @@ TEST(LlvmLibcBlockTest, AllocateAlreadyAligned) {
   array<byte, kN> bytes;
   auto result = Block::init(bytes);
   ASSERT_TRUE(result.has_value());
-  Block *block = *result;
-  uintptr_t orig_end = reinterpret_cast<uintptr_t>(block) + block->outer_size();
+  Block block = *result;
+  uintptr_t orig_end = block.addr() + block.outer_size();
 
   constexpr size_t SIZE = Block::PREV_FIELD_SIZE + 1;
 
@@ -396,17 +394,17 @@ TEST(LlvmLibcBlockTest, AllocateAlreadyAligned) {
       Block::allocate(block, Block::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(), Block().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(), Block().addr());
+  EXPECT_TRUE(aligned_block.is_usable_space_aligned(Block::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(), Block().addr());
+  EXPECT_EQ(aligned_block.next().addr(), next.addr());
+  EXPECT_EQ(next.addr() + next.outer_size(), orig_end);
 }
 
 TEST(LlvmLibcBlockTest, AllocateNeedsAlignment) {
@@ -415,35 +413,34 @@ TEST(LlvmLibcBlockTest, AllocateNeedsAlignment) {
   array<byte, kN> bytes;
   auto result = Block::init(bytes);
   ASSERT_TRUE(result.has_value());
-  Block *block = *result;
+  Block 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))
+  while (block.is_usable_space_aligned(alignment))
     alignment += Block::MIN_ALIGN;
 
   auto [aligned_block, prev, next] = Block::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(), Block().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(), Block().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(), Block().addr());
+  EXPECT_EQ(aligned_block.next().addr(), next.addr());
+  EXPECT_EQ(next.addr() + next.outer_size(), orig_end);
 }
 
 TEST(LlvmLibcBlockTest, PreviousBlockMergedIfNotFirst) {
@@ -452,20 +449,20 @@ TEST(LlvmLibcBlockTest, PreviousBlockMergedIfNotFirst) {
   array<byte, kN> bytes;
   auto result = Block::init(bytes);
   ASSERT_TRUE(result.has_value());
-  Block *block = *result;
+  Block 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();
+  Block 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))
+  while (newblock.is_usable_space_aligned(alignment))
     alignment += Block::MIN_ALIGN;
 
   // Ensure we can allocate in the new block.
@@ -473,10 +470,10 @@ TEST(LlvmLibcBlockTest, PreviousBlockMergedIfNotFirst) {
 
   // 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(), Block().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) {
@@ -489,38 +486,38 @@ TEST(LlvmLibcBlockTest, CanRemergeBlockAllocations) {
   array<byte, kN> bytes;
   auto result = Block::init(bytes);
   ASSERT_TRUE(result.has_value());
-  Block *block = *result;
+  Block block = *result;
 
-  Block *orig_block = block;
-  size_t orig_size = orig_block->outer_size();
+  Block orig_block = block;
+  size_t orig_size = orig_block.outer_size();
 
-  Block *last = block->next();
+  Block last = block.next();
 
-  ASSERT_EQ(block->prev_free(), static_cast<Block *>(nullptr));
+  ASSERT_EQ(block.prev_free().addr(), Block().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))
+  while (block.is_usable_space_aligned(alignment))
     alignment += Block::MIN_ALIGN;
 
   auto [aligned_block, prev, next] = Block::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(), Block().addr());
+  ASSERT_EQ(aligned_block.prev_free().addr(), prev.addr());
+  EXPECT_NE(next.addr(), Block().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_test.cpp b/libc/test/src/__support/freelist_test.cpp
index bd5ecec45d921..ba879eb651771 100644
--- a/libc/test/src/__support/freelist_test.cpp
+++ b/libc/test/src/__support/freelist_test.cpp
@@ -18,36 +18,36 @@ using LIBC_NAMESPACE::cpp::optional;
 
 TEST(LlvmLibcFreeList, FreeList) {
   byte mem[1024];
-  optional<Block *> maybeBlock = Block::init(mem);
+  optional<Block> maybeBlock = Block::init(mem);
   ASSERT_TRUE(maybeBlock.has_value());
-  Block *block1 = *maybeBlock;
+  Block block1 = *maybeBlock;
 
-  maybeBlock = block1->split(128);
+  maybeBlock = block1.split(128);
   ASSERT_TRUE(maybeBlock.has_value());
-  Block *block2 = *maybeBlock;
+  Block 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..6fbfd97f82c00 100644
--- a/libc/test/src/__support/freestore_test.cpp
+++ b/libc/test/src/__support/freestore_test.cpp
@@ -21,44 +21,45 @@ 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<Block> maybeBlock = Block::init(mem);
   ASSERT_TRUE(maybeBlock.has_value());
-  Block *too_small = *maybeBlock;
-  maybeBlock = too_small->split(Block::PREV_FIELD_SIZE);
+  Block too_small = *maybeBlock;
+  maybeBlock = too_small.split(Block::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() >= Block::HEADER_SIZE + sizeof(FreeList::Node))
     return;
-  Block *remainder = *maybeBlock;
+  Block 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<Block> maybeBlock = Block::init(mem);
   ASSERT_TRUE(maybeBlock.has_value());
 
-  Block *smallest = *maybeBlock;
-  maybeBlock = smallest->split(sizeof(FreeList::Node) + Block::PREV_FIELD_SIZE);
+  Block smallest = *maybeBlock;
+  maybeBlock = smallest.split(sizeof(FreeList::Node) + Block::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);
+  Block largest_small = *maybeBlock;
+  maybeBlock = largest_small.split(sizeof(FreeTrie::Node) +
+                                   Block::PREV_FIELD_SIZE - Block::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;
+  Block remainder = *maybeBlock;
 
   FreeStore store;
   store.set_range({0, 4096});
@@ -68,32 +69,36 @@ 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);
+  Block 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<Block> maybeBlock = Block::init(mem);
   ASSERT_TRUE(maybeBlock.has_value());
 
-  Block *small = *maybeBlock;
-  maybeBlock = small->split(sizeof(FreeList::Node) + Block::PREV_FIELD_SIZE);
+  Block small = *maybeBlock;
+  maybeBlock = small.split(sizeof(FreeList::Node) + Block::PREV_FIELD_SIZE);
   ASSERT_TRUE(maybeBlock.has_value());
 
-  Block *remainder = *maybeBlock;
+  Block remainder = *maybeBlock;
 
   FreeStore store;
   store.set_range({0, 4096});
@@ -101,9 +106,8 @@ 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(),
+            Block().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(), Block().addr());
 }
diff --git a/libc/test/src/__support/freetrie_test.cpp b/libc/test/src/__support/freetrie_test.cpp
index 5663a01687294..91d437c2b1c59 100644
--- a/libc/test/src/__support/freetrie_test.cpp
+++ b/libc/test/src/__support/freetrie_test.cpp
@@ -21,65 +21,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<Block> maybeBlock = Block::init(mem);
   ASSERT_TRUE(maybeBlock.has_value());
-  Block *block = *maybeBlock;
+  Block 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<Block> maybeBlock = Block::init(mem);
   ASSERT_TRUE(maybeBlock.has_value());
-  Block *lower = *maybeBlock;
-  maybeBlock = lower->split(512);
+  Block lower = *maybeBlock;
+  maybeBlock = lower.split(512);
   ASSERT_TRUE(maybeBlock.has_value());
-  Block *root = *maybeBlock;
+  Block 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<Block> maybeBlock = Block::init(mem);
   ASSERT_TRUE(maybeBlock.has_value());
-  Block *root = *maybeBlock;
-  maybeBlock = root->split(512);
+  Block root = *maybeBlock;
+  maybeBlock = root.split(512);
   ASSERT_TRUE(maybeBlock.has_value());
-  Block *upper = *maybeBlock;
+  Block 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<Block> maybeBlock = Block::init(mem);
   ASSERT_TRUE(maybeBlock.has_value());
-  Block *root = *maybeBlock;
-  maybeBlock = root->split(1024);
+  Block root = *maybeBlock;
+  maybeBlock = root.split(1024);
   ASSERT_TRUE(maybeBlock.has_value());
-  Block *lower = *maybeBlock;
-  maybeBlock = lower->split(128);
+  Block lower = *maybeBlock;
+  maybeBlock = lower.split(128);
   ASSERT_TRUE(maybeBlock.has_value());
-  Block *upper = *maybeBlock;
+  Block upper = *maybeBlock;
 
   FreeTrie trie({0, 4096});
   trie.push(root);
@@ -87,30 +89,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<Block> maybeBlock = Block::init(mem);
   ASSERT_TRUE(maybeBlock.has_value());
-  Block *small1 = *maybeBlock;
-  maybeBlock = small1->split(512);
+  Block small1 = *maybeBlock;
+  maybeBlock = small1.split(512);
   ASSERT_TRUE(maybeBlock.has_value());
-  Block *small2 = *maybeBlock;
-  maybeBlock = small2->split(512);
+  Block 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());
+  Block 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 +120,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 658812c8a3119d067bc2d246a856ada8092e9f2f Mon Sep 17 00:00:00 2001
From: Yifan Zhu <yfzhu at google.com>
Date: Thu, 4 Jun 2026 14:23:12 -0700
Subject: [PATCH 2/7] move to BlockPtr/BlockRef

---
 libc/fuzzing/__support/freelist_heap_fuzz.cpp |  10 +-
 libc/src/__support/block.h                    | 319 +++++++-------
 libc/src/__support/freelist.cpp               |   4 +-
 libc/src/__support/freelist.h                 |  16 +-
 libc/src/__support/freelist_heap.h            |  48 +--
 libc/src/__support/freestore.h                |  42 +-
 libc/src/__support/freetrie.h                 |  10 +-
 libc/test/src/__support/block_test.cpp        | 408 +++++++++---------
 .../test/src/__support/freelist_heap_test.cpp |   6 +-
 libc/test/src/__support/freestore_test.cpp    |  66 +--
 10 files changed, 477 insertions(+), 452 deletions(-)

diff --git a/libc/fuzzing/__support/freelist_heap_fuzz.cpp b/libc/fuzzing/__support/freelist_heap_fuzz.cpp
index 3675e6c6b7adc..9b4cf3ab3d7b8 100644
--- a/libc/fuzzing/__support/freelist_heap_fuzz.cpp
+++ b/libc/fuzzing/__support/freelist_heap_fuzz.cpp
@@ -26,7 +26,7 @@ asm(R"(
 __llvm_libc_heap_limit:
 )");
 
-using LIBC_NAMESPACE::Block;
+using LIBC_NAMESPACE::BlockPtr;
 using LIBC_NAMESPACE::FreeListHeap;
 using LIBC_NAMESPACE::inline_memset;
 using LIBC_NAMESPACE::cpp::nullopt;
@@ -148,7 +148,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 = BlockPtr::MIN_ALIGN;
       switch (alloc_type) {
       case AllocType::MALLOC:
         ptr = heap.allocate(alloc_size);
@@ -173,7 +173,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 = BlockPtr::MIN_ALIGN;
         }
         break;
       }
@@ -195,8 +195,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 < BlockPtr::MIN_ALIGN)
+          alignment = BlockPtr::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 47ea7f75e25f0..929302df08055 100644
--- a/libc/src/__support/block.h
+++ b/libc/src/__support/block.h
@@ -93,16 +93,15 @@ 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.
-  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);
+class BlockRef;
 
-  // 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);
+class BlockPtr {
+  friend class BlockRef;
+
+protected:
+  cpp::byte *self = nullptr;
+
+  LIBC_INLINE explicit constexpr BlockPtr(cpp::byte *ptr) : self(ptr) {}
 
 public:
   static constexpr size_t HEADER_SIZE = 2 * sizeof(size_t);
@@ -112,27 +111,35 @@ class Block {
   // the outer size, is then always also 4-aligned.)
   static constexpr size_t MIN_ALIGN = cpp::max(size_t{4}, alignof(max_align_t));
 
-  LIBC_INLINE constexpr Block() = default;
-  LIBC_INLINE explicit constexpr Block(cpp::byte *ptr) : self(ptr) {}
+  LIBC_INLINE constexpr BlockPtr() = default;
+
   LIBC_INLINE explicit constexpr operator bool() const {
     return self != nullptr;
   }
-  LIBC_INLINE constexpr bool operator==(Block other) const {
+  LIBC_INLINE constexpr bool operator==(BlockPtr other) const {
     return self == other.self;
   }
-  LIBC_INLINE constexpr bool operator!=(Block other) const {
+  LIBC_INLINE constexpr bool operator!=(BlockPtr 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);
+  LIBC_INLINE BlockRef &operator*() {
+    return *reinterpret_cast<BlockRef *>(this);
+  }
+  LIBC_INLINE const BlockRef &operator*() const {
+    return *reinterpret_cast<const BlockRef *>(this);
+  }
+  LIBC_INLINE BlockRef *operator->() {
+    return reinterpret_cast<BlockRef *>(this);
+  }
+  LIBC_INLINE const BlockRef *operator->() const {
+    return reinterpret_cast<const BlockRef *>(this);
   }
 
   /// 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<BlockPtr> init(ByteSpan region);
 
   /// @returns  A pointer to a block, given a pointer to the start of the usable
   ///           space inside the block.
@@ -141,115 +148,29 @@ class Block {
   ///
   /// @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) {
+  LIBC_INLINE static BlockPtr from_usable_space(void *usable_space) {
     auto *bytes = reinterpret_cast<cpp::byte *>(usable_space);
-    return Block(bytes - HEADER_SIZE);
+    return BlockPtr(bytes - HEADER_SIZE);
   }
-  LIBC_INLINE static Block from_usable_space(const void *usable_space) {
+  LIBC_INLINE static BlockPtr from_usable_space(const void *usable_space) {
     const auto *bytes = reinterpret_cast<const cpp::byte *>(usable_space);
-    return Block(const_cast<cpp::byte *>(bytes - HEADER_SIZE));
+    return BlockPtr(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 load_next() & SIZE_MASK; }
-
   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
-  /// allocated.
-  LIBC_INLINE size_t inner_size() const {
-    if (!next())
-      return 0;
-    return inner_size(outer_size());
-  }
-
-  /// @returns The number of usable bytes inside a block with the given outer
-  /// size were it to be allocated.
   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.
-  LIBC_INLINE size_t inner_size_free() const {
-    if (!next())
-      return 0;
-    return inner_size_free(outer_size());
-  }
-
-  /// @returns The number of usable bytes inside a block with the given outer
-  /// size if it remains free.
   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() 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() 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
-  /// 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) const;
-
-  /// Merges this block with the one that comes after it.
-  bool merge_next() const;
-
-  /// @returns The block immediately after this one, or a null block if this is
-  /// the last block.
-  LIBC_INLINE Block next() const {
-    size_t next_value = load_next();
-    if (next_value & LAST_MASK)
-      return Block();
-    return Block(self + (next_value & SIZE_MASK));
-  }
-
-  /// @returns The free block immediately before this one, otherwise null.
-  LIBC_INLINE Block prev_free() const {
-    if (!(load_next() & PREV_FREE_MASK))
-      return Block();
-    return Block(self - load_prev());
-  }
-
-  /// @returns Whether the block is unavailable for allocation.
-  LIBC_INLINE bool used() const { return !next() || !next().prev_free(); }
-
-  /// Marks this block as in use.
-  LIBC_INLINE void mark_used() const {
-    LIBC_ASSERT(next() && "last block is always considered used");
-    Block next_block = next();
-    next_block.store_next(next_block.load_next() & ~PREV_FREE_MASK);
-  }
-
-  /// Marks this block as free.
-  LIBC_INLINE void mark_free() const {
-    LIBC_ASSERT(next() && "last block is always considered used");
-    Block 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 {
-    return reinterpret_cast<uintptr_t>(usable_space()) % alignment == 0;
-  }
-
   // Returns the minimum inner size necessary for a block of that size to
   // always be able to allocate at the given size and alignment.
   //
@@ -293,7 +214,7 @@ class Block {
 
   // Divide a block into up to 3 blocks according to `BlockInfo`. Behavior is
   // undefined if allocation is not possible for the given size and alignment.
-  static BlockInfo allocate(Block block, size_t alignment, size_t size);
+  static BlockInfo allocate(BlockPtr block, size_t alignment, size_t size);
 
   // These two functions may wrap around.
   LIBC_INLINE static uintptr_t
@@ -309,24 +230,36 @@ class Block {
 
   /// 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) {
+class BlockRef : public BlockPtr {
+  // 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);
+
+  LIBC_INLINE explicit constexpr BlockRef(cpp::byte *ptr) : BlockPtr(ptr) {}
+  friend class BlockPtr;
+
+  LIBC_INLINE static BlockPtr as_block(ByteSpan bytes) {
     LIBC_ASSERT(reinterpret_cast<uintptr_t>(bytes.data()) % alignof(size_t) ==
                     0 &&
                 "block start must be suitably aligned");
-    Block block(bytes.data());
-    block.store_next(bytes.size());
+    BlockPtr 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(size_t) == 0 &&
                 "block start must be suitably aligned");
-    Block last(start);
-    last.store_next(HEADER_SIZE | LAST_MASK);
+    BlockPtr last(start);
+    last->store_next(HEADER_SIZE | LAST_MASK);
   }
 
   LIBC_INLINE cpp::byte *field_ptr(size_t offset) const {
@@ -360,15 +293,107 @@ class Block {
     store_field(NEXT_OFFSET, value);
   }
 
-  cpp::byte *self = nullptr;
+public:
+  using BlockPtr::inner_size;
+  using BlockPtr::inner_size_free;
+  using BlockPtr::outer_size;
+
+  BlockRef() = delete;
+  BlockRef(const BlockRef &) = delete;
+  BlockRef &operator=(const BlockRef &) = delete;
+
+  LIBC_INLINE cpp::byte *data() const { return self; }
+  LIBC_INLINE uintptr_t addr() const {
+    return reinterpret_cast<uintptr_t>(self);
+  }
+
+  /// @returns The total size of the block in bytes, including the header.
+  LIBC_INLINE size_t outer_size() const { return load_next() & SIZE_MASK; }
+
+  /// @returns The number of usable bytes inside the block were it to be
+  /// allocated.
+  LIBC_INLINE size_t inner_size() const {
+    if (!next())
+      return 0;
+    return inner_size(outer_size());
+  }
+
+  /// @returns The number of usable bytes inside the block if it remains free.
+  LIBC_INLINE size_t inner_size_free() const {
+    if (!next())
+      return 0;
+    return inner_size_free(outer_size());
+  }
+
+  /// @returns A pointer to the usable space inside this block.
+  ///
+  /// Aligned to some multiple of MIN_ALIGN.
+  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() 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
+  /// 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<BlockPtr> 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() const;
+
+  /// @returns The block immediately after this one, or a null block if this is
+  /// the last block.
+  LIBC_INLINE BlockPtr next() const {
+    size_t next_value = load_next();
+    if (next_value & LAST_MASK)
+      return BlockPtr();
+    return BlockPtr(self + (next_value & SIZE_MASK));
+  }
+
+  /// @returns The free block immediately before this one, otherwise null.
+  LIBC_INLINE BlockPtr prev_free() const {
+    if (!(load_next() & PREV_FREE_MASK))
+      return BlockPtr();
+    return BlockPtr(self - load_prev());
+  }
+
+  /// @returns Whether the block is unavailable for allocation.
+  LIBC_INLINE bool used() const { return !next() || !next()->prev_free(); }
+
+  /// Marks this block as in use.
+  LIBC_INLINE void mark_used() const {
+    LIBC_ASSERT(next() && "last block is always considered used");
+    BlockPtr next_block = next();
+    next_block->store_next(next_block->load_next() & ~PREV_FREE_MASK);
+  }
+
+  /// Marks this block as free.
+  LIBC_INLINE void mark_free() const {
+    LIBC_ASSERT(next() && "last block is always considered used");
+    BlockPtr 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 {
+    return reinterpret_cast<uintptr_t>(usable_space()) % alignment == 0;
+  }
 };
 
-// This is the return type for `allocate` which can split one block into up to
-// three blocks.
-struct Block::BlockInfo {
+struct BlockPtr::BlockInfo {
   // This is the newly aligned block. It will have the alignment requested by a
   // call to `allocate` and at most `size`.
-  Block block;
+  BlockPtr 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
@@ -376,16 +401,16 @@ struct Block::BlockInfo {
   // 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.
-  Block prev;
+  BlockPtr 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;
+  BlockPtr next;
 };
 
 LIBC_INLINE
-optional<Block> Block::init(ByteSpan region) {
+optional<BlockPtr> BlockPtr::init(ByteSpan region) {
   if (!region.data())
     return {};
 
@@ -406,50 +431,50 @@ optional<Block> Block::init(ByteSpan region) {
     return {};
 
   auto *last_start_ptr = reinterpret_cast<cpp::byte *>(last_start);
-  Block block =
-      as_block({reinterpret_cast<cpp::byte *>(block_start), last_start_ptr});
-  make_last_block(last_start_ptr);
-  block.mark_free();
+  BlockPtr block =
+      BlockRef::as_block({reinterpret_cast<cpp::byte *>(block_start), last_start_ptr});
+  BlockRef::make_last_block(last_start_ptr);
+  block->mark_free();
   return block;
 }
 
 LIBC_INLINE
-Block::BlockInfo Block::allocate(Block block, size_t alignment, size_t size) {
+BlockPtr::BlockInfo BlockPtr::allocate(BlockPtr block, size_t alignment, size_t size) {
   LIBC_ASSERT(alignment % MIN_ALIGN == 0 &&
               "alignment must be a multiple of MIN_ALIGN");
 
-  BlockInfo info{block, Block(), Block()};
+  BlockInfo info{block, BlockPtr(), BlockPtr()};
 
-  if (!info.block.is_usable_space_aligned(alignment)) {
-    Block original = info.block;
+  if (!info.block->is_usable_space_aligned(alignment)) {
+    BlockPtr original = info.block;
     // The padding block has no minimum size requirement.
-    optional<Block> maybe_aligned_block = original.split(0, alignment);
+    optional<BlockPtr> 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 (BlockPtr 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) &&
+    BlockPtr 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<BlockPtr> next = info.block->split(size))
     info.next = *next;
 
   return info;
 }
 
 LIBC_INLINE
-optional<Block> Block::split(size_t new_inner_size,
+optional<BlockPtr> 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");
@@ -476,23 +501,23 @@ optional<Block> Block::split(size_t new_inner_size,
   ByteSpan new_region = region().subspan(new_outer_size);
   store_next((load_next() & ~SIZE_MASK) | new_outer_size);
 
-  Block new_block = as_block(new_region);
-  new_block.mark_free();
+  BlockPtr new_block = as_block(new_region);
+  new_block->mark_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() const {
-  Block next_block = next();
-  if (used() || next_block.used())
+bool BlockRef::merge_next() const {
+  BlockPtr next_block = next();
+  if (used() || next_block->used())
     return false;
-  size_t new_size = outer_size() + next_block.outer_size();
+  size_t new_size = outer_size() + next_block->outer_size();
   store_next((load_next() & ~SIZE_MASK) | new_size);
-  next().store_prev(new_size);
+  next()->store_prev(new_size);
   return true;
 }
 
diff --git a/libc/src/__support/freelist.cpp b/libc/src/__support/freelist.cpp
index da0f28f09735b..363924de947ee 100644
--- a/libc/src/__support/freelist.cpp
+++ b/libc/src/__support/freelist.cpp
@@ -12,8 +12,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(BlockPtr::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 7a307a6fe87d0..90dd592ecaf45 100644
--- a/libc/src/__support/freelist.h
+++ b/libc/src/__support/freelist.h
@@ -26,13 +26,13 @@ class FreeList {
   class Node {
   public:
     /// @returns The block containing this node.
-    LIBC_INLINE Block block() const { return Block::from_usable_space(this); }
+    LIBC_INLINE BlockPtr block() const { return BlockPtr::from_usable_space(this); }
 
     /// @returns The block containing this node.
-    LIBC_INLINE Block block() { return Block::from_usable_space(this); }
+    LIBC_INLINE BlockPtr block() { return BlockPtr::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.
@@ -56,16 +56,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 BlockPtr 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(BlockPtr 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 ae3f3d164a51e..f7eb8bcf7fa90 100644
--- a/libc/src/__support/freelist_heap.h
+++ b/libc/src/__support/freelist_heap.h
@@ -53,8 +53,8 @@ class FreeListHeap {
 
   void *allocate_impl(size_t alignment, 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(BlockPtr block) {
+    return span<cpp::byte>(block->usable_space(), block->inner_size());
   }
 
   bool is_valid_ptr(void *ptr) { return ptr >= begin && ptr < end; }
@@ -75,9 +75,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 = BlockPtr::init(region());
+  BlockPtr block = *result;
+  free_store.set_range({0, cpp::bit_ceil(block->inner_size())});
   free_store.insert(block);
   is_initialized = true;
 }
@@ -89,26 +89,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 = BlockPtr::min_size_for_allocation(alignment, size);
   if (!request_size)
     return nullptr;
 
-  Block block = free_store.remove_best_fit(request_size);
+  BlockPtr block = free_store.remove_best_fit(request_size);
   if (!block)
     return nullptr;
 
-  auto block_info = Block::allocate(block, alignment, size);
+  auto block_info = BlockPtr::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(BlockPtr::MIN_ALIGN, size);
 }
 
 LIBC_INLINE void *FreeListHeap::aligned_allocate(size_t alignment,
@@ -122,7 +122,7 @@ LIBC_INLINE void *FreeListHeap::aligned_allocate(size_t alignment,
     return nullptr;
 
   // The minimum alignment supported by Block is MIN_ALIGN.
-  alignment = cpp::max(alignment, Block::MIN_ALIGN);
+  alignment = cpp::max(alignment, BlockPtr::MIN_ALIGN);
 
   return allocate_impl(alignment, size);
 }
@@ -135,24 +135,24 @@ 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();
+  BlockPtr block = BlockPtr::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();
+  BlockPtr prev_free = block->prev_free();
+  BlockPtr next = block->next();
 
   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);
@@ -175,10 +175,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())
+  BlockPtr block = BlockPtr::from_usable_space(bytes);
+  if (!block->used())
     return nullptr;
-  size_t old_size = block.inner_size();
+  size_t old_size = block->inner_size();
 
   // Do nothing and return ptr if the required memory size is smaller than
   // the current size.
diff --git a/libc/src/__support/freestore.h b/libc/src/__support/freestore.h
index ef6495081d123..443d0e79c4357 100644
--- a/libc/src/__support/freestore.h
+++ b/libc/src/__support/freestore.h
@@ -31,39 +31,39 @@ class FreeStore {
 
   /// Insert a free block. If the block is too small to be tracked, nothing
   /// happens.
-  void insert(Block block);
+  void insert(BlockPtr block);
 
   /// Remove a free block. If the block is too small to be tracked, nothing
   /// happens.
-  void remove(Block block);
+  void remove(BlockPtr 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);
+  BlockPtr remove_best_fit(size_t size);
 
 private:
   static constexpr size_t MIN_OUTER_SIZE =
-      align_up(Block::HEADER_SIZE + sizeof(FreeList::Node), Block::MIN_ALIGN);
+      align_up(BlockPtr::HEADER_SIZE + sizeof(FreeList::Node), BlockPtr::MIN_ALIGN);
   static constexpr size_t MIN_LARGE_OUTER_SIZE =
-      align_up(Block::HEADER_SIZE + sizeof(FreeTrie::Node), Block::MIN_ALIGN);
+      align_up(BlockPtr::HEADER_SIZE + sizeof(FreeTrie::Node), BlockPtr::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) / BlockPtr::MIN_ALIGN;
 
-  LIBC_INLINE static bool too_small(Block block) {
-    return block.outer_size() < MIN_OUTER_SIZE;
+  LIBC_INLINE static bool too_small(BlockPtr 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(BlockPtr block) {
+    return block->outer_size() < MIN_LARGE_OUTER_SIZE;
   }
 
-  FreeList &small_list(Block block);
+  FreeList &small_list(BlockPtr 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(BlockPtr block) {
   if (too_small(block))
     return;
   if (is_small(block))
@@ -72,34 +72,34 @@ LIBC_INLINE void FreeStore::insert(Block block) {
     large_trie.push(block);
 }
 
-LIBC_INLINE void FreeStore::remove(Block block) {
+LIBC_INLINE void FreeStore::remove(BlockPtr 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 BlockPtr FreeStore::remove_best_fit(size_t size) {
   if (FreeList *list = find_best_small_fit(size)) {
-    Block block = list->front();
+    BlockPtr block = list->front();
     list->pop();
     return block;
   }
   if (FreeTrie::Node *best_fit = large_trie.find_best_fit(size)) {
-    Block block = best_fit->block();
+    BlockPtr block = best_fit->block();
     large_trie.remove(best_fit);
     return block;
   }
-  return Block();
+  return BlockPtr();
 }
 
-LIBC_INLINE FreeList &FreeStore::small_list(Block block) {
+LIBC_INLINE FreeList &FreeStore::small_list(BlockPtr 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) / BlockPtr::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 7d9dccb3ba21d..dc7aaef26ac95 100644
--- a/libc/src/__support/freetrie.h
+++ b/libc/src/__support/freetrie.h
@@ -96,7 +96,7 @@ class FreeTrie {
   LIBC_INLINE bool empty() const { return !root; }
 
   /// Push a block to the trie.
-  void push(Block block);
+  void push(BlockPtr block);
 
   /// Remove a node from this trie node's free list.
   void remove(Node *node);
@@ -117,10 +117,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(BlockPtr 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 +139,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 ce5e441746ccf..211aeb6aa6e7d 100644
--- a/libc/test/src/__support/block_test.cpp
+++ b/libc/test/src/__support/block_test.cpp
@@ -14,7 +14,7 @@
 #include "src/string/memcpy.h"
 #include "test/UnitTest/Test.h"
 
-using LIBC_NAMESPACE::Block;
+using LIBC_NAMESPACE::BlockPtr;
 using LIBC_NAMESPACE::cpp::array;
 using LIBC_NAMESPACE::cpp::bit_ceil;
 using LIBC_NAMESPACE::cpp::byte;
@@ -22,53 +22,53 @@ using LIBC_NAMESPACE::cpp::span;
 
 TEST(LlvmLibcBlockTest, CanCreateSingleAlignedBlock) {
   constexpr size_t kN = 1024;
-  alignas(Block::MIN_ALIGN) array<byte, kN> bytes;
+  alignas(BlockPtr::MIN_ALIGN) array<byte, kN> bytes;
 
-  auto result = Block::init(bytes);
+  auto result = BlockPtr::init(bytes);
   ASSERT_TRUE(result.has_value());
-  Block block = *result;
+  BlockPtr block = *result;
 
-  EXPECT_EQ(block.addr() % alignof(size_t), size_t{0});
-  EXPECT_TRUE(block.is_usable_space_aligned(Block::MIN_ALIGN));
+  EXPECT_EQ(block->addr() % alignof(size_t), size_t{0});
+  EXPECT_TRUE(block->is_usable_space_aligned(BlockPtr::MIN_ALIGN));
 
-  Block last = block.next();
-  ASSERT_NE(last.addr(), Block().addr());
-  EXPECT_EQ(last.addr() % alignof(size_t), size_t{0});
+  BlockPtr last = block->next();
+  ASSERT_NE(last->addr(), BlockPtr()->addr());
+  EXPECT_EQ(last->addr() % alignof(size_t), size_t{0});
 
-  EXPECT_EQ(last.outer_size(), Block::HEADER_SIZE);
-  EXPECT_EQ(last.prev_free().addr(), block.addr());
-  EXPECT_TRUE(last.used());
+  EXPECT_EQ(last->outer_size(), BlockPtr::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 - Block::HEADER_SIZE + Block::PREV_FIELD_SIZE);
-  EXPECT_EQ(block.prev_free().addr(), Block().addr());
-  EXPECT_FALSE(block.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 - BlockPtr::HEADER_SIZE + BlockPtr::PREV_FIELD_SIZE);
+  EXPECT_EQ(block->prev_free()->addr(), BlockPtr()->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(BlockPtr::MIN_ALIGN) array<byte, kN> bytes;
   span<byte> aligned(bytes);
 
-  auto result = Block::init(aligned.subspan(1));
+  auto result = BlockPtr::init(aligned.subspan(1));
   EXPECT_TRUE(result.has_value());
 
-  Block block = *result;
-  EXPECT_EQ(block.addr() % alignof(size_t), size_t{0});
-  EXPECT_TRUE(block.is_usable_space_aligned(Block::MIN_ALIGN));
+  BlockPtr block = *result;
+  EXPECT_EQ(block->addr() % alignof(size_t), size_t{0});
+  EXPECT_TRUE(block->is_usable_space_aligned(BlockPtr::MIN_ALIGN));
 
-  Block last = block.next();
-  ASSERT_NE(last.addr(), Block().addr());
-  EXPECT_EQ(last.addr() % alignof(size_t), size_t{0});
+  BlockPtr last = block->next();
+  ASSERT_NE(last->addr(), BlockPtr()->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 = BlockPtr::init(bytes);
   EXPECT_FALSE(result.has_value());
 }
 
@@ -78,55 +78,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 = BlockPtr::inner_size(512);
 
   array<byte, kN> bytes;
-  auto result = Block::init(bytes);
+  auto result = BlockPtr::init(bytes);
   ASSERT_TRUE(result.has_value());
-  Block block1 = *result;
-  size_t orig_size = block1.outer_size();
+  BlockPtr block1 = *result;
+  size_t orig_size = block1->outer_size();
 
-  result = block1.split(kSplitN);
+  result = block1->split(kSplitN);
   ASSERT_TRUE(result.has_value());
-  Block block2 = *result;
+  BlockPtr block2 = *result;
 
-  EXPECT_EQ(block1.inner_size(), kSplitN);
-  EXPECT_EQ(block1.outer_size(),
-            kSplitN - Block::PREV_FIELD_SIZE + Block::HEADER_SIZE);
+  EXPECT_EQ(block1->inner_size(), kSplitN);
+  EXPECT_EQ(block1->outer_size(),
+            kSplitN - BlockPtr::PREV_FIELD_SIZE + BlockPtr::HEADER_SIZE);
 
-  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(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(BlockPtr::MIN_ALIGN));
 
-  EXPECT_EQ(block1.next().addr(), block2.addr());
-  EXPECT_EQ(block2.prev_free().addr(), block1.addr());
+  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 = BlockPtr::init(bytes);
   ASSERT_TRUE(result.has_value());
-  Block block1 = *result;
-  size_t orig_size = block1.outer_size();
+  BlockPtr 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;
+  BlockPtr 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(block2.addr() % alignof(size_t), 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(BlockPtr::MIN_ALIGN));
 
-  EXPECT_EQ(block1.next().addr(), block2.addr());
-  EXPECT_EQ(block2.prev_free().addr(), block1.addr());
+  EXPECT_EQ(block1->next()->addr(), block2->addr());
+  EXPECT_EQ(block2->prev_free()->addr(), block1->addr());
 }
 
 TEST(LlvmLibcBlockTest, CanSplitMidBlock) {
@@ -134,9 +134,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;
@@ -144,33 +144,33 @@ TEST(LlvmLibcBlockTest, CanSplitMidBlock) {
   constexpr size_t kSplit2 = 256;
 
   array<byte, kN> bytes;
-  auto result = Block::init(bytes);
+  auto result = BlockPtr::init(bytes);
   ASSERT_TRUE(result.has_value());
-  Block block1 = *result;
+  BlockPtr block1 = *result;
 
-  result = block1.split(kSplit1);
+  result = block1->split(kSplit1);
   ASSERT_TRUE(result.has_value());
-  Block block2 = *result;
+  BlockPtr block2 = *result;
 
-  result = block1.split(kSplit2);
+  result = block1->split(kSplit2);
   ASSERT_TRUE(result.has_value());
-  Block block3 = *result;
+  BlockPtr block3 = *result;
 
-  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());
+  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 = BlockPtr::init(bytes);
   ASSERT_TRUE(result.has_value());
-  Block block = *result;
+  BlockPtr block = *result;
 
-  result = block.split(block.inner_size() + 1);
+  result = block->split(block->inner_size() + 1);
   ASSERT_FALSE(result.has_value());
 }
 
@@ -178,11 +178,11 @@ TEST(LlvmLibcBlockTest, CannotSplitBlockWithoutHeaderSpace) {
   constexpr size_t kN = 1024;
 
   array<byte, kN> bytes;
-  auto result = Block::init(bytes);
+  auto result = BlockPtr::init(bytes);
   ASSERT_TRUE(result.has_value());
-  Block block = *result;
+  BlockPtr block = *result;
 
-  result = block.split(block.inner_size() - Block::HEADER_SIZE + 1);
+  result = block->split(block->inner_size() - BlockPtr::HEADER_SIZE + 1);
   ASSERT_FALSE(result.has_value());
 }
 
@@ -191,11 +191,11 @@ TEST(LlvmLibcBlockTest, CannotMakeBlockLargerInSplit) {
   constexpr size_t kN = 1024;
 
   array<byte, kN> bytes;
-  auto result = Block::init(bytes);
+  auto result = BlockPtr::init(bytes);
   ASSERT_TRUE(result.has_value());
-  Block block = *result;
+  BlockPtr block = *result;
 
-  result = block.split(block.inner_size() + 1);
+  result = block->split(block->inner_size() + 1);
   ASSERT_FALSE(result.has_value());
 }
 
@@ -204,13 +204,13 @@ TEST(LlvmLibcBlockTest, CanMakeMinimalSizeFirstBlock) {
   constexpr size_t kN = 1024;
 
   array<byte, kN> bytes;
-  auto result = Block::init(bytes);
+  auto result = BlockPtr::init(bytes);
   ASSERT_TRUE(result.has_value());
-  Block block = *result;
+  BlockPtr block = *result;
 
-  result = block.split(0);
+  result = block->split(0);
   ASSERT_TRUE(result.has_value());
-  EXPECT_LE(block.outer_size(), Block::HEADER_SIZE + Block::MIN_ALIGN);
+  EXPECT_LE(block->outer_size(), BlockPtr::HEADER_SIZE + BlockPtr::MIN_ALIGN);
 }
 
 TEST(LlvmLibcBlockTest, CanMakeMinimalSizeSecondBlock) {
@@ -218,32 +218,32 @@ TEST(LlvmLibcBlockTest, CanMakeMinimalSizeSecondBlock) {
   constexpr size_t kN = 1024;
 
   array<byte, kN> bytes;
-  auto result = Block::init(bytes);
+  auto result = BlockPtr::init(bytes);
   ASSERT_TRUE(result.has_value());
-  Block block1 = *result;
+  BlockPtr block1 = *result;
 
-  result = block1.split(Block::prev_possible_block_start(block1.next().addr()) -
-                        reinterpret_cast<uintptr_t>(block1.usable_space()) +
-                        Block::PREV_FIELD_SIZE);
+  result = block1->split(BlockPtr::prev_possible_block_start(block1->next()->addr()) -
+                        reinterpret_cast<uintptr_t>(block1->usable_space()) +
+                        BlockPtr::PREV_FIELD_SIZE);
   ASSERT_TRUE(result.has_value());
-  EXPECT_LE((*result).outer_size(), Block::HEADER_SIZE + Block::MIN_ALIGN);
+  EXPECT_LE((*result)->outer_size(), BlockPtr::HEADER_SIZE + BlockPtr::MIN_ALIGN);
 }
 
 TEST(LlvmLibcBlockTest, CanMarkBlockUsed) {
   constexpr size_t kN = 1024;
 
   array<byte, kN> bytes;
-  auto result = Block::init(bytes);
+  auto result = BlockPtr::init(bytes);
   ASSERT_TRUE(result.has_value());
-  Block block = *result;
-  size_t orig_size = block.outer_size();
+  BlockPtr 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, CannotSplitUsedBlock) {
@@ -251,12 +251,12 @@ TEST(LlvmLibcBlockTest, CannotSplitUsedBlock) {
   constexpr size_t kSplitN = 512;
 
   array<byte, kN> bytes;
-  auto result = Block::init(bytes);
+  auto result = BlockPtr::init(bytes);
   ASSERT_TRUE(result.has_value());
-  Block block = *result;
+  BlockPtr block = *result;
 
-  block.mark_used();
-  result = block.split(kSplitN);
+  block->mark_used();
+  result = block->split(kSplitN);
   ASSERT_FALSE(result.has_value());
 }
 
@@ -267,25 +267,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 = BlockPtr::init(bytes);
   ASSERT_TRUE(result.has_value());
-  Block block1 = *result;
-  size_t total_size = block1.outer_size();
+  BlockPtr 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;
+  BlockPtr block3 = *result;
 
-  EXPECT_TRUE(block3.merge_next());
+  EXPECT_TRUE(block3->merge_next());
 
-  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());
+  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) {
@@ -293,16 +293,16 @@ TEST(LlvmLibcBlockTest, CannotMergeWithFirstOrLastBlock) {
   constexpr size_t kSplitN = 512;
 
   array<byte, kN> bytes;
-  auto result = Block::init(bytes);
+  auto result = BlockPtr::init(bytes);
   ASSERT_TRUE(result.has_value());
-  Block block1 = *result;
+  BlockPtr 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;
+  BlockPtr block2 = *result;
 
-  EXPECT_FALSE(block2.merge_next());
+  EXPECT_FALSE(block2->merge_next());
 }
 
 TEST(LlvmLibcBlockTest, CannotMergeUsedBlock) {
@@ -310,40 +310,40 @@ TEST(LlvmLibcBlockTest, CannotMergeUsedBlock) {
   constexpr size_t kSplitN = 512;
 
   array<byte, kN> bytes;
-  auto result = Block::init(bytes);
+  auto result = BlockPtr::init(bytes);
   ASSERT_TRUE(result.has_value());
-  Block block = *result;
+  BlockPtr 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 = BlockPtr::init(bytes);
   ASSERT_TRUE(result.has_value());
-  Block block1 = *result;
+  BlockPtr block1 = *result;
 
-  void *ptr = block1.usable_space();
-  Block block2 = Block::from_usable_space(ptr);
-  EXPECT_EQ(block1.addr(), block2.addr());
+  void *ptr = block1->usable_space();
+  BlockPtr block2 = BlockPtr::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 = BlockPtr::init(bytes);
   ASSERT_TRUE(result.has_value());
-  Block block1 = *result;
+  BlockPtr block1 = *result;
 
-  const void *ptr = block1.usable_space();
-  Block block2 = Block::from_usable_space(ptr);
-  EXPECT_EQ(block1.addr(), block2.addr());
+  const void *ptr = block1->usable_space();
+  BlockPtr block2 = BlockPtr::from_usable_space(ptr);
+  EXPECT_EQ(block1->addr(), block2->addr());
 }
 
 TEST(LlvmLibcBlockTest, Allocate) {
@@ -352,30 +352,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 = BlockPtr::init(bytes);
     ASSERT_TRUE(result.has_value());
-    Block block = *result;
+    BlockPtr 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.addr(), Block().addr());
+    auto info = BlockPtr::allocate(block, BlockPtr::MIN_ALIGN, i);
+    EXPECT_NE(info.block->addr(), BlockPtr()->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 / BlockPtr::MIN_ALIGN; ++i) {
     array<byte, kN> bytes;
-    auto result = Block::init(bytes);
+    auto result = BlockPtr::init(bytes);
     ASSERT_TRUE(result.has_value());
-    Block block = *result;
+    BlockPtr block = *result;
 
-    size_t alignment = i * Block::MIN_ALIGN;
-    if (Block::min_size_for_allocation(alignment, 1) > block.inner_size())
+    size_t alignment = i * BlockPtr::MIN_ALIGN;
+    if (BlockPtr::min_size_for_allocation(alignment, 1) > block->inner_size())
       continue;
 
-    auto info = Block::allocate(block, alignment, 1);
-    EXPECT_NE(info.block.addr(), Block().addr());
+    auto info = BlockPtr::allocate(block, alignment, 1);
+    EXPECT_NE(info.block->addr(), BlockPtr()->addr());
   }
 }
 
@@ -383,97 +383,97 @@ TEST(LlvmLibcBlockTest, AllocateAlreadyAligned) {
   constexpr size_t kN = 1024;
 
   array<byte, kN> bytes;
-  auto result = Block::init(bytes);
+  auto result = BlockPtr::init(bytes);
   ASSERT_TRUE(result.has_value());
-  Block block = *result;
-  uintptr_t orig_end = block.addr() + block.outer_size();
+  BlockPtr block = *result;
+  uintptr_t orig_end = block->addr() + block->outer_size();
 
-  constexpr size_t SIZE = Block::PREV_FIELD_SIZE + 1;
+  constexpr size_t SIZE = BlockPtr::PREV_FIELD_SIZE + 1;
 
   auto [aligned_block, prev, next] =
-      Block::allocate(block, Block::MIN_ALIGN, SIZE);
+      BlockPtr::allocate(block, BlockPtr::MIN_ALIGN, SIZE);
 
   // Since this is already aligned, there should be no previous block.
-  EXPECT_EQ(prev.addr(), Block().addr());
+  EXPECT_EQ(prev->addr(), BlockPtr()->addr());
 
   // Ensure we the block is aligned and large enough.
-  EXPECT_NE(aligned_block.addr(), Block().addr());
-  EXPECT_TRUE(aligned_block.is_usable_space_aligned(Block::MIN_ALIGN));
-  EXPECT_GE(aligned_block.inner_size(), SIZE);
+  EXPECT_NE(aligned_block->addr(), BlockPtr()->addr());
+  EXPECT_TRUE(aligned_block->is_usable_space_aligned(BlockPtr::MIN_ALIGN));
+  EXPECT_GE(aligned_block->inner_size(), SIZE);
 
   // Check the next block.
-  EXPECT_NE(next.addr(), Block().addr());
-  EXPECT_EQ(aligned_block.next().addr(), next.addr());
-  EXPECT_EQ(next.addr() + next.outer_size(), orig_end);
+  EXPECT_NE(next->addr(), BlockPtr()->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 = BlockPtr::init(bytes);
   ASSERT_TRUE(result.has_value());
-  Block block = *result;
+  BlockPtr block = *result;
 
-  uintptr_t orig_end = block.addr() + 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 = BlockPtr::MIN_ALIGN;
+  while (block->is_usable_space_aligned(alignment))
+    alignment += BlockPtr::MIN_ALIGN;
 
-  auto [aligned_block, prev, next] = Block::allocate(block, alignment, 10);
+  auto [aligned_block, prev, next] = BlockPtr::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.addr(), Block().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());
+  EXPECT_NE(prev->addr(), BlockPtr()->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.addr(), Block().addr());
-  EXPECT_TRUE(aligned_block.is_usable_space_aligned(alignment));
+  EXPECT_NE(next->addr(), BlockPtr()->addr());
+  EXPECT_TRUE(aligned_block->is_usable_space_aligned(alignment));
 
   // Check the next block.
-  EXPECT_NE(next.addr(), Block().addr());
-  EXPECT_EQ(aligned_block.next().addr(), next.addr());
-  EXPECT_EQ(next.addr() + next.outer_size(), orig_end);
+  EXPECT_NE(next->addr(), BlockPtr()->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 = BlockPtr::init(bytes);
   ASSERT_TRUE(result.has_value());
-  Block block = *result;
+  BlockPtr 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().addr(), block.addr());
-  size_t old_prev_size = block.outer_size();
+  BlockPtr 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 = BlockPtr::MIN_ALIGN;
+  while (newblock->is_usable_space_aligned(alignment))
+    alignment += BlockPtr::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] = BlockPtr::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.addr(), Block().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);
+  EXPECT_EQ(prev->addr(), BlockPtr()->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) {
@@ -484,40 +484,40 @@ TEST(LlvmLibcBlockTest, CanRemergeBlockAllocations) {
   constexpr size_t kN = 1024;
 
   array<byte, kN> bytes;
-  auto result = Block::init(bytes);
+  auto result = BlockPtr::init(bytes);
   ASSERT_TRUE(result.has_value());
-  Block block = *result;
+  BlockPtr block = *result;
 
-  Block orig_block = block;
-  size_t orig_size = orig_block.outer_size();
+  BlockPtr orig_block = block;
+  size_t orig_size = orig_block->outer_size();
 
-  Block last = block.next();
+  BlockPtr last = block->next();
 
-  ASSERT_EQ(block.prev_free().addr(), Block().addr());
+  ASSERT_EQ(block->prev_free()->addr(), BlockPtr()->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 = BlockPtr::MIN_ALIGN;
+  while (block->is_usable_space_aligned(alignment))
+    alignment += BlockPtr::MIN_ALIGN;
 
-  auto [aligned_block, prev, next] = Block::allocate(block, alignment, 1);
+  auto [aligned_block, prev, next] = BlockPtr::allocate(block, alignment, 1);
 
   // Check we have the appropriate blocks.
-  ASSERT_NE(prev.addr(), Block().addr());
-  ASSERT_EQ(aligned_block.prev_free().addr(), prev.addr());
-  EXPECT_NE(next.addr(), Block().addr());
-  EXPECT_EQ(aligned_block.next().addr(), next.addr());
-  EXPECT_EQ(next.next().addr(), last.addr());
+  ASSERT_NE(prev->addr(), BlockPtr()->addr());
+  ASSERT_EQ(aligned_block->prev_free()->addr(), prev->addr());
+  EXPECT_NE(next->addr(), BlockPtr()->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().addr(), next.addr());
-  EXPECT_TRUE(prev.merge_next());
-  EXPECT_EQ(prev.next().addr(), last.addr());
+  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.addr(), orig_block.addr());
-  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 9d3a6b612555f..af83edfedd87b 100644
--- a/libc/test/src/__support/freelist_heap_test.cpp
+++ b/libc/test/src/__support/freelist_heap_test.cpp
@@ -22,7 +22,7 @@ asm(R"(
 __llvm_libc_heap_limit:
 )");
 
-using LIBC_NAMESPACE::Block;
+using LIBC_NAMESPACE::BlockPtr;
 using LIBC_NAMESPACE::freelist_heap;
 using LIBC_NAMESPACE::FreeListHeap;
 using LIBC_NAMESPACE::FreeListHeapBuffer;
@@ -123,12 +123,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 % BlockPtr::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 % BlockPtr::MIN_ALIGN, static_cast<size_t>(0));
 }
 
 TEST_FOR_EACH_ALLOCATOR(CanRealloc, 2048) {
diff --git a/libc/test/src/__support/freestore_test.cpp b/libc/test/src/__support/freestore_test.cpp
index 6fbfd97f82c00..35c469ce74b87 100644
--- a/libc/test/src/__support/freestore_test.cpp
+++ b/libc/test/src/__support/freestore_test.cpp
@@ -11,7 +11,7 @@
 #include "src/__support/freestore.h"
 #include "test/UnitTest/Test.h"
 
-using LIBC_NAMESPACE::Block;
+using LIBC_NAMESPACE::BlockPtr;
 using LIBC_NAMESPACE::FreeList;
 using LIBC_NAMESPACE::FreeStore;
 using LIBC_NAMESPACE::FreeTrie;
@@ -21,45 +21,45 @@ 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<BlockPtr> maybeBlock = BlockPtr::init(mem);
   ASSERT_TRUE(maybeBlock.has_value());
-  Block too_small = *maybeBlock;
-  maybeBlock = too_small.split(Block::PREV_FIELD_SIZE);
+  BlockPtr too_small = *maybeBlock;
+  maybeBlock = too_small->split(BlockPtr::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() >= Block::HEADER_SIZE + sizeof(FreeList::Node))
+  if (too_small->outer_size() >= BlockPtr::HEADER_SIZE + sizeof(FreeList::Node))
     return;
-  Block remainder = *maybeBlock;
+  BlockPtr 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()).addr(),
-            remainder.addr());
+  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<BlockPtr> maybeBlock = BlockPtr::init(mem);
   ASSERT_TRUE(maybeBlock.has_value());
 
-  Block smallest = *maybeBlock;
-  maybeBlock = smallest.split(sizeof(FreeList::Node) + Block::PREV_FIELD_SIZE);
+  BlockPtr smallest = *maybeBlock;
+  maybeBlock = smallest->split(sizeof(FreeList::Node) + BlockPtr::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);
+  BlockPtr largest_small = *maybeBlock;
+  maybeBlock = largest_small->split(sizeof(FreeTrie::Node) +
+                                   BlockPtr::PREV_FIELD_SIZE - BlockPtr::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;
+  BlockPtr remainder = *maybeBlock;
 
   FreeStore store;
   store.set_range({0, 4096});
@@ -69,36 +69,36 @@ TEST(LlvmLibcFreeStore, RemoveBestFit) {
   store.insert(remainder);
 
   // Find exact match for smallest.
-  ASSERT_EQ(store.remove_best_fit(smallest.inner_size()).addr(),
-            smallest.addr());
+  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()).addr(),
-            largest_small.addr());
+  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).addr(),
-            next_smallest.addr());
+  BlockPtr 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).addr(),
-            remainder.addr());
+  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<BlockPtr> maybeBlock = BlockPtr::init(mem);
   ASSERT_TRUE(maybeBlock.has_value());
 
-  Block small = *maybeBlock;
-  maybeBlock = small.split(sizeof(FreeList::Node) + Block::PREV_FIELD_SIZE);
+  BlockPtr small = *maybeBlock;
+  maybeBlock = small->split(sizeof(FreeList::Node) + BlockPtr::PREV_FIELD_SIZE);
   ASSERT_TRUE(maybeBlock.has_value());
 
-  Block remainder = *maybeBlock;
+  BlockPtr remainder = *maybeBlock;
 
   FreeStore store;
   store.set_range({0, 4096});
@@ -106,8 +106,8 @@ TEST(LlvmLibcFreeStore, Remove) {
   store.insert(remainder);
 
   store.remove(remainder);
-  ASSERT_EQ(store.remove_best_fit(remainder.inner_size()).addr(),
-            Block().addr());
+  ASSERT_EQ(store.remove_best_fit(remainder->inner_size())->addr(),
+            BlockPtr()->addr());
   store.remove(small);
-  ASSERT_EQ(store.remove_best_fit(small.inner_size()).addr(), Block().addr());
+  ASSERT_EQ(store.remove_best_fit(small->inner_size())->addr(), BlockPtr()->addr());
 }

>From 00fe4db163625892e8fb1a0e469fa915cadeea55 Mon Sep 17 00:00:00 2001
From: Yifan Zhu <yfzhu at google.com>
Date: Thu, 4 Jun 2026 14:32:02 -0700
Subject: [PATCH 3/7] refactor

---
 libc/src/__support/block.h | 444 +++++++++++++++++--------------------
 1 file changed, 204 insertions(+), 240 deletions(-)

diff --git a/libc/src/__support/block.h b/libc/src/__support/block.h
index 929302df08055..64329440201cb 100644
--- a/libc/src/__support/block.h
+++ b/libc/src/__support/block.h
@@ -39,102 +39,162 @@ 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.
-///
-/// 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
-/// 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
-/// indices indicate byte offsets:
-///
-/// @code{.unparsed}
-/// Block 1:
-/// +---------------------+--------------+
-/// | Header              | Usable space |
-/// +----------+----------+--------------+
-/// | prev     | next     |              |
-/// | 0......3 | 4......7 | 8........227 |
-/// | 00000000 | 00000230 |  <app data>  |
-/// +----------+----------+--------------+
-/// Block 2:
-/// +---------------------+--------------+
-/// | Header              | Usable space |
-/// +----------+----------+--------------+
-/// | prev     | next     |              |
-/// | 0......3 | 4......7 | 8........827 |
-/// | 00000230 | 00000830 | f7f7....f7f7 |
-/// +----------+----------+--------------+
-/// @endcode
-///
-/// As a space optimization, when a block is allocated, it consumes the prev
-/// field of the following block:
-///
-/// Block 1 (used):
-/// +---------------------+--------------+
-/// | Header              | Usable space |
-/// +----------+----------+--------------+
-/// | prev     | next     |              |
-/// | 0......3 | 4......7 | 8........230 |
-/// | 00000000 | 00000230 |  <app data>  |
-/// +----------+----------+--------------+
-/// Block 2:
-/// +---------------------+--------------+
-/// | B1       | Header   | Usable space |
-/// +----------+----------+--------------+
-/// |          | next     |              |
-/// | 0......3 | 4......7 | 8........827 |
-/// | xxxxxxxx | 00000830 | f7f7....f7f7 |
-/// +----------+----------+--------------+
-///
-/// 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 BlockRef;
+class BlockPtr;
 
-class BlockPtr {
-  friend class BlockRef;
+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);
 
-protected:
   cpp::byte *self = nullptr;
 
-  LIBC_INLINE explicit constexpr BlockPtr(cpp::byte *ptr) : self(ptr) {}
+  LIBC_INLINE constexpr BlockRef() = default;
+  LIBC_INLINE explicit constexpr BlockRef(cpp::byte *ptr) : self(ptr) {}
+
+  friend class BlockPtr;
+
+  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");
+#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));
+  }
+
+  LIBC_INLINE size_t load_prev() const { return load_field(PREV_OFFSET); }
+  LIBC_INLINE size_t load_next() const { return load_field(NEXT_OFFSET); }
+  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);
+  }
+
+  LIBC_INLINE static BlockPtr as_block(ByteSpan bytes);
+  LIBC_INLINE static void make_last_block(cpp::byte *start);
 
 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));
+  static constexpr size_t PREV_FIELD_SIZE = sizeof(size_t);
+
+  LIBC_INLINE constexpr BlockRef(const BlockRef &) = default;
+  LIBC_INLINE constexpr BlockRef(BlockRef &&) = default;
+  LIBC_INLINE BlockRef &operator=(const BlockRef &) = default;
+  LIBC_INLINE BlockRef &operator=(BlockRef &&) = default;
+
+  LIBC_INLINE cpp::byte *data() const { return self; }
+  LIBC_INLINE uintptr_t addr() const {
+    return reinterpret_cast<uintptr_t>(self);
+  }
+
+  /// @returns The total size of the block in bytes, including the header.
+  LIBC_INLINE size_t outer_size() const { return load_next() & SIZE_MASK; }
+
+  /// @returns The number of usable bytes inside the block were it to be
+  /// allocated.
+  LIBC_INLINE size_t inner_size() const;
+
+  /// @returns The number of usable bytes inside the block if it remains free.
+  LIBC_INLINE size_t inner_size_free() const;
+
+  /// @returns A pointer to the usable space inside this block.
+  ///
+  /// Aligned to some multiple of MIN_ALIGN.
+  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() 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
+  /// 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<BlockPtr> 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() const;
+
+  /// @returns The block immediately after this one, or a null block if this is
+  /// the last block.
+  LIBC_INLINE BlockPtr next() const;
+
+  /// @returns The free block immediately before this one, otherwise null.
+  LIBC_INLINE BlockPtr prev_free() const;
+
+  /// @returns Whether the block is unavailable for allocation.
+  LIBC_INLINE bool used() const;
+
+  /// Marks this block as in use.
+  LIBC_INLINE void mark_used() const;
+
+  /// Marks this block as free.
+  LIBC_INLINE void mark_free() const;
+
+  LIBC_INLINE bool is_usable_space_aligned(size_t alignment) const {
+    return reinterpret_cast<uintptr_t>(usable_space()) % alignment == 0;
+  }
+};
+
+class BlockPtr {
+  BlockRef block;
+
+  LIBC_INLINE explicit constexpr BlockPtr(cpp::byte *ptr) : block(ptr) {}
+  LIBC_INLINE explicit constexpr BlockPtr(BlockRef b) : block(b.self) {}
+
+  friend class BlockRef;
+
+public:
+  static constexpr size_t HEADER_SIZE = BlockRef::HEADER_SIZE;
+  static constexpr size_t MIN_ALIGN = BlockRef::MIN_ALIGN;
+  static constexpr size_t PREV_FIELD_SIZE = BlockRef::PREV_FIELD_SIZE;
 
   LIBC_INLINE constexpr BlockPtr() = default;
 
   LIBC_INLINE explicit constexpr operator bool() const {
-    return self != nullptr;
+    return block.self != nullptr;
   }
   LIBC_INLINE constexpr bool operator==(BlockPtr other) const {
-    return self == other.self;
+    return block.self == other.block.self;
   }
   LIBC_INLINE constexpr bool operator!=(BlockPtr other) const {
     return !(*this == other);
   }
 
-  LIBC_INLINE BlockRef &operator*() {
-    return *reinterpret_cast<BlockRef *>(this);
-  }
-  LIBC_INLINE const BlockRef &operator*() const {
-    return *reinterpret_cast<const BlockRef *>(this);
-  }
-  LIBC_INLINE BlockRef *operator->() {
-    return reinterpret_cast<BlockRef *>(this);
-  }
-  LIBC_INLINE const BlockRef *operator->() const {
-    return reinterpret_cast<const BlockRef *>(this);
-  }
+  LIBC_INLINE BlockRef &operator*() { return block; }
+  LIBC_INLINE const BlockRef &operator*() const { return block; }
+
+  LIBC_INLINE BlockRef *operator->() { return █ }
+  LIBC_INLINE const BlockRef *operator->() const { return █ }
 
   /// 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
@@ -227,167 +287,6 @@ class BlockPtr {
                             size_t usable_space_alignment = MIN_ALIGN) {
     return align_down(ptr, usable_space_alignment) - HEADER_SIZE;
   }
-
-  /// Only for testing.
-  static constexpr size_t PREV_FIELD_SIZE = sizeof(size_t);
-};
-
-class BlockRef : public BlockPtr {
-  // 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);
-
-  LIBC_INLINE explicit constexpr BlockRef(cpp::byte *ptr) : BlockPtr(ptr) {}
-  friend class BlockPtr;
-
-  LIBC_INLINE static BlockPtr as_block(ByteSpan bytes) {
-    LIBC_ASSERT(reinterpret_cast<uintptr_t>(bytes.data()) % alignof(size_t) ==
-                    0 &&
-                "block start must be suitably aligned");
-    BlockPtr 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(size_t) == 0 &&
-                "block start must be suitably aligned");
-    BlockPtr 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");
-#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));
-  }
-
-  LIBC_INLINE size_t load_prev() const { return load_field(PREV_OFFSET); }
-  LIBC_INLINE size_t load_next() const { return load_field(NEXT_OFFSET); }
-  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);
-  }
-
-public:
-  using BlockPtr::inner_size;
-  using BlockPtr::inner_size_free;
-  using BlockPtr::outer_size;
-
-  BlockRef() = delete;
-  BlockRef(const BlockRef &) = delete;
-  BlockRef &operator=(const BlockRef &) = delete;
-
-  LIBC_INLINE cpp::byte *data() const { return self; }
-  LIBC_INLINE uintptr_t addr() const {
-    return reinterpret_cast<uintptr_t>(self);
-  }
-
-  /// @returns The total size of the block in bytes, including the header.
-  LIBC_INLINE size_t outer_size() const { return load_next() & SIZE_MASK; }
-
-  /// @returns The number of usable bytes inside the block were it to be
-  /// allocated.
-  LIBC_INLINE size_t inner_size() const {
-    if (!next())
-      return 0;
-    return inner_size(outer_size());
-  }
-
-  /// @returns The number of usable bytes inside the block if it remains free.
-  LIBC_INLINE size_t inner_size_free() const {
-    if (!next())
-      return 0;
-    return inner_size_free(outer_size());
-  }
-
-  /// @returns A pointer to the usable space inside this block.
-  ///
-  /// Aligned to some multiple of MIN_ALIGN.
-  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() 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
-  /// 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<BlockPtr> 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() const;
-
-  /// @returns The block immediately after this one, or a null block if this is
-  /// the last block.
-  LIBC_INLINE BlockPtr next() const {
-    size_t next_value = load_next();
-    if (next_value & LAST_MASK)
-      return BlockPtr();
-    return BlockPtr(self + (next_value & SIZE_MASK));
-  }
-
-  /// @returns The free block immediately before this one, otherwise null.
-  LIBC_INLINE BlockPtr prev_free() const {
-    if (!(load_next() & PREV_FREE_MASK))
-      return BlockPtr();
-    return BlockPtr(self - load_prev());
-  }
-
-  /// @returns Whether the block is unavailable for allocation.
-  LIBC_INLINE bool used() const { return !next() || !next()->prev_free(); }
-
-  /// Marks this block as in use.
-  LIBC_INLINE void mark_used() const {
-    LIBC_ASSERT(next() && "last block is always considered used");
-    BlockPtr next_block = next();
-    next_block->store_next(next_block->load_next() & ~PREV_FREE_MASK);
-  }
-
-  /// Marks this block as free.
-  LIBC_INLINE void mark_free() const {
-    LIBC_ASSERT(next() && "last block is always considered used");
-    BlockPtr 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 {
-    return reinterpret_cast<uintptr_t>(usable_space()) % alignment == 0;
-  }
 };
 
 struct BlockPtr::BlockInfo {
@@ -409,6 +308,71 @@ struct BlockPtr::BlockInfo {
   BlockPtr next;
 };
 
+LIBC_INLINE
+BlockPtr BlockRef::as_block(ByteSpan bytes) {
+  LIBC_ASSERT(reinterpret_cast<uintptr_t>(bytes.data()) % alignof(size_t) ==
+                  0 &&
+              "block start must be suitably aligned");
+  BlockPtr block(bytes.data());
+  block->store_next(bytes.size());
+  return block;
+}
+
+LIBC_INLINE
+void BlockRef::make_last_block(cpp::byte *start) {
+  LIBC_ASSERT(reinterpret_cast<uintptr_t>(start) % alignof(size_t) == 0 &&
+              "block start must be suitably aligned");
+  BlockPtr last(start);
+  last->store_next(HEADER_SIZE | LAST_MASK);
+}
+
+LIBC_INLINE
+size_t BlockRef::inner_size() const {
+  if (!next())
+    return 0;
+  return BlockPtr::inner_size(outer_size());
+}
+
+LIBC_INLINE
+size_t BlockRef::inner_size_free() const {
+  if (!next())
+    return 0;
+  return BlockPtr::inner_size_free(outer_size());
+}
+
+LIBC_INLINE
+BlockPtr BlockRef::next() const {
+  size_t next_value = load_next();
+  if (next_value & LAST_MASK)
+    return BlockPtr();
+  return BlockPtr(self + (next_value & SIZE_MASK));
+}
+
+LIBC_INLINE
+BlockPtr BlockRef::prev_free() const {
+  if (!(load_next() & PREV_FREE_MASK))
+    return BlockPtr();
+  return BlockPtr(self - load_prev());
+}
+
+LIBC_INLINE
+bool BlockRef::used() const { return !next() || !next()->prev_free(); }
+
+LIBC_INLINE
+void BlockRef::mark_used() const {
+  LIBC_ASSERT(next() && "last block is always considered used");
+  BlockPtr next_block = next();
+  next_block->store_next(next_block->load_next() & ~PREV_FREE_MASK);
+}
+
+LIBC_INLINE
+void BlockRef::mark_free() const {
+  LIBC_ASSERT(next() && "last block is always considered used");
+  BlockPtr next_block = next();
+  next_block->store_next(next_block->load_next() | PREV_FREE_MASK);
+  next_block->store_prev(outer_size());
+}
+
 LIBC_INLINE
 optional<BlockPtr> BlockPtr::init(ByteSpan region) {
   if (!region.data())
@@ -483,11 +447,11 @@ optional<BlockPtr> BlockRef::split(size_t new_inner_size,
 
   // 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, PREV_FIELD_SIZE));
+  size_t min_outer_size = BlockPtr::outer_size(cpp::max(new_inner_size, PREV_FIELD_SIZE));
 
   uintptr_t start = addr();
   uintptr_t next_block_start =
-      next_possible_block_start(start + min_outer_size, usable_space_alignment);
+      BlockPtr::next_possible_block_start(start + min_outer_size, usable_space_alignment);
   if (next_block_start < start)
     return {};
   size_t new_outer_size = next_block_start - start;

>From 1db733c74cf17b2aeb7e028c424eb17481669d0d Mon Sep 17 00:00:00 2001
From: Yifan Zhu <yfzhu at google.com>
Date: Thu, 4 Jun 2026 14:35:21 -0700
Subject: [PATCH 4/7] refactor

---
 libc/src/__support/block.h | 57 ++++++++++++++++++++++++++++++++++++++
 1 file changed, 57 insertions(+)

diff --git a/libc/src/__support/block.h b/libc/src/__support/block.h
index 64329440201cb..4dbcd52d9f23d 100644
--- a/libc/src/__support/block.h
+++ b/libc/src/__support/block.h
@@ -41,6 +41,60 @@ using cpp::optional;
 
 class BlockPtr;
 
+/// 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. 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
+/// indices indicate byte offsets:
+///
+/// @code{.unparsed}
+/// Block 1:
+/// +---------------------+--------------+
+/// | Header              | Usable space |
+/// +----------+----------+--------------+
+/// | prev     | next     |              |
+/// | 0......3 | 4......7 | 8........227 |
+/// | 00000000 | 00000230 |  <app data>  |
+/// +----------+----------+--------------+
+/// Block 2:
+/// +---------------------+--------------+
+/// | Header              | Usable space |
+/// +----------+----------+--------------+
+/// | prev     | next     |              |
+/// | 0......3 | 4......7 | 8........827 |
+/// | 00000230 | 00000830 | f7f7....f7f7 |
+/// +----------+----------+--------------+
+/// @endcode
+///
+/// As a space optimization, when a block is allocated, it consumes the prev
+/// field of the following block:
+///
+/// Block 1 (used):
+/// +---------------------+--------------+
+/// | Header              | Usable space |
+/// +----------+----------+--------------+
+/// | prev     | next     |              |
+/// | 0......3 | 4......7 | 8........230 |
+/// | 00000000 | 00000230 |  <app data>  |
+/// +----------+----------+--------------+
+/// Block 2:
+/// +---------------------+--------------+
+/// | B1       | Header   | Usable space |
+/// +----------+----------+--------------+
+/// |          | next     |              |
+/// | 0......3 | 4......7 | 8........827 |
+/// | xxxxxxxx | 00000830 | f7f7....f7f7 |
+/// +----------+----------+--------------+
+///
+/// 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 BlockRef {
   // Masks for the contents of the next field.
   static constexpr size_t PREV_FREE_MASK = 1 << 0;
@@ -90,7 +144,10 @@ class BlockRef {
     store_field(NEXT_OFFSET, value);
   }
 
+  /// 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 BlockPtr as_block(ByteSpan bytes);
+
   LIBC_INLINE static void make_last_block(cpp::byte *start);
 
 public:

>From 7b32aa9d23df69bf6295dfcad16c5568040d26d7 Mon Sep 17 00:00:00 2001
From: Yifan Zhu <yfzhu at google.com>
Date: Thu, 4 Jun 2026 14:39:29 -0700
Subject: [PATCH 5/7] Refactor: Inline self-contained BlockRef methods to
 minimize git diff noise

---
 libc/src/__support/block.h | 95 +++++++++++++++++++++++---------------
 1 file changed, 57 insertions(+), 38 deletions(-)

diff --git a/libc/src/__support/block.h b/libc/src/__support/block.h
index 4dbcd52d9f23d..897f5482f19a6 100644
--- a/libc/src/__support/block.h
+++ b/libc/src/__support/block.h
@@ -168,12 +168,38 @@ class BlockRef {
   /// @returns The total size of the block in bytes, including the header.
   LIBC_INLINE size_t outer_size() const { return load_next() & SIZE_MASK; }
 
+  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 a block with the given outer
+  /// size were it to be allocated.
+  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 a block with the given outer
+  /// size if it remains free.
+  LIBC_INLINE static constexpr size_t inner_size_free(size_t outer_size) {
+    return outer_size - HEADER_SIZE;
+  }
+
   /// @returns The number of usable bytes inside the block were it to be
   /// allocated.
-  LIBC_INLINE size_t inner_size() const;
+  LIBC_INLINE size_t inner_size() const {
+    if (load_next() & LAST_MASK)
+      return 0;
+    return inner_size(outer_size());
+  }
 
   /// @returns The number of usable bytes inside the block if it remains free.
-  LIBC_INLINE size_t inner_size_free() const;
+  LIBC_INLINE size_t inner_size_free() const {
+    if (load_next() & LAST_MASK)
+      return 0;
+    return inner_size_free(outer_size());
+  }
 
   /// @returns A pointer to the usable space inside this block.
   ///
@@ -209,13 +235,39 @@ class BlockRef {
   LIBC_INLINE BlockPtr prev_free() const;
 
   /// @returns Whether the block is unavailable for allocation.
-  LIBC_INLINE bool used() const;
+  LIBC_INLINE bool used() const {
+    size_t next_value = load_next();
+    if (next_value & LAST_MASK)
+      return true;
+    cpp::byte *next_ptr = self + (next_value & SIZE_MASK);
+    size_t next_next_value;
+    inline_memcpy(&next_next_value, next_ptr + NEXT_OFFSET, sizeof(next_next_value));
+    return !(next_next_value & PREV_FREE_MASK);
+  }
 
   /// Marks this block as in use.
-  LIBC_INLINE void mark_used() const;
+  LIBC_INLINE void mark_used() const {
+    LIBC_ASSERT(!(load_next() & LAST_MASK) && "last block is always considered used");
+    size_t next_value = load_next();
+    cpp::byte *next_ptr = self + (next_value & SIZE_MASK);
+    size_t next_next_value;
+    inline_memcpy(&next_next_value, next_ptr + NEXT_OFFSET, sizeof(next_next_value));
+    next_next_value &= ~PREV_FREE_MASK;
+    inline_memcpy(next_ptr + NEXT_OFFSET, &next_next_value, sizeof(next_next_value));
+  }
 
   /// Marks this block as free.
-  LIBC_INLINE void mark_free() const;
+  LIBC_INLINE void mark_free() const {
+    LIBC_ASSERT(!(load_next() & LAST_MASK) && "last block is always considered used");
+    size_t next_value = load_next();
+    size_t outer = next_value & SIZE_MASK;
+    cpp::byte *next_ptr = self + outer;
+    size_t next_next_value;
+    inline_memcpy(&next_next_value, next_ptr + NEXT_OFFSET, sizeof(next_next_value));
+    next_next_value |= PREV_FREE_MASK;
+    inline_memcpy(next_ptr + NEXT_OFFSET, &next_next_value, sizeof(next_next_value));
+    inline_memcpy(next_ptr + PREV_OFFSET, &outer, sizeof(outer));
+  }
 
   LIBC_INLINE bool is_usable_space_aligned(size_t alignment) const {
     return reinterpret_cast<uintptr_t>(usable_space()) % alignment == 0;
@@ -382,21 +434,6 @@ void BlockRef::make_last_block(cpp::byte *start) {
   BlockPtr last(start);
   last->store_next(HEADER_SIZE | LAST_MASK);
 }
-
-LIBC_INLINE
-size_t BlockRef::inner_size() const {
-  if (!next())
-    return 0;
-  return BlockPtr::inner_size(outer_size());
-}
-
-LIBC_INLINE
-size_t BlockRef::inner_size_free() const {
-  if (!next())
-    return 0;
-  return BlockPtr::inner_size_free(outer_size());
-}
-
 LIBC_INLINE
 BlockPtr BlockRef::next() const {
   size_t next_value = load_next();
@@ -412,24 +449,6 @@ BlockPtr BlockRef::prev_free() const {
   return BlockPtr(self - load_prev());
 }
 
-LIBC_INLINE
-bool BlockRef::used() const { return !next() || !next()->prev_free(); }
-
-LIBC_INLINE
-void BlockRef::mark_used() const {
-  LIBC_ASSERT(next() && "last block is always considered used");
-  BlockPtr next_block = next();
-  next_block->store_next(next_block->load_next() & ~PREV_FREE_MASK);
-}
-
-LIBC_INLINE
-void BlockRef::mark_free() const {
-  LIBC_ASSERT(next() && "last block is always considered used");
-  BlockPtr next_block = next();
-  next_block->store_next(next_block->load_next() | PREV_FREE_MASK);
-  next_block->store_prev(outer_size());
-}
-
 LIBC_INLINE
 optional<BlockPtr> BlockPtr::init(ByteSpan region) {
   if (!region.data())

>From 47fedbd265653fc28e6e17da6fef26bddea5aaef Mon Sep 17 00:00:00 2001
From: Yifan Zhu <yfzhu at google.com>
Date: Thu, 4 Jun 2026 14:42:55 -0700
Subject: [PATCH 6/7] Refactor: Use BlockRef helper functions in mark_used,
 mark_free, used to avoid raw inline_memcpy

---
 libc/src/__support/block.h | 25 +++++++------------------
 1 file changed, 7 insertions(+), 18 deletions(-)

diff --git a/libc/src/__support/block.h b/libc/src/__support/block.h
index 897f5482f19a6..2b78c76261fc9 100644
--- a/libc/src/__support/block.h
+++ b/libc/src/__support/block.h
@@ -239,34 +239,23 @@ class BlockRef {
     size_t next_value = load_next();
     if (next_value & LAST_MASK)
       return true;
-    cpp::byte *next_ptr = self + (next_value & SIZE_MASK);
-    size_t next_next_value;
-    inline_memcpy(&next_next_value, next_ptr + NEXT_OFFSET, sizeof(next_next_value));
-    return !(next_next_value & PREV_FREE_MASK);
+    BlockRef next_block(self + (next_value & SIZE_MASK));
+    return !(next_block.load_next() & PREV_FREE_MASK);
   }
 
   /// Marks this block as in use.
   LIBC_INLINE void mark_used() const {
     LIBC_ASSERT(!(load_next() & LAST_MASK) && "last block is always considered used");
-    size_t next_value = load_next();
-    cpp::byte *next_ptr = self + (next_value & SIZE_MASK);
-    size_t next_next_value;
-    inline_memcpy(&next_next_value, next_ptr + NEXT_OFFSET, sizeof(next_next_value));
-    next_next_value &= ~PREV_FREE_MASK;
-    inline_memcpy(next_ptr + NEXT_OFFSET, &next_next_value, sizeof(next_next_value));
+    BlockRef next_block(self + outer_size());
+    next_block.store_next(next_block.load_next() & ~PREV_FREE_MASK);
   }
 
   /// Marks this block as free.
   LIBC_INLINE void mark_free() const {
     LIBC_ASSERT(!(load_next() & LAST_MASK) && "last block is always considered used");
-    size_t next_value = load_next();
-    size_t outer = next_value & SIZE_MASK;
-    cpp::byte *next_ptr = self + outer;
-    size_t next_next_value;
-    inline_memcpy(&next_next_value, next_ptr + NEXT_OFFSET, sizeof(next_next_value));
-    next_next_value |= PREV_FREE_MASK;
-    inline_memcpy(next_ptr + NEXT_OFFSET, &next_next_value, sizeof(next_next_value));
-    inline_memcpy(next_ptr + PREV_OFFSET, &outer, sizeof(outer));
+    BlockRef next_block(self + outer_size());
+    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 {

>From 30476448138281e50c1a05ebed0d1946c9f4dc73 Mon Sep 17 00:00:00 2001
From: Yifan Zhu <yfzhu at google.com>
Date: Thu, 4 Jun 2026 14:47:43 -0700
Subject: [PATCH 7/7] Refactor: Make BlockPtr inherit privately from BlockRef
 and restore standard next() usages out-of-line

---
 libc/src/__support/block.h | 111 +++++++++++++++++++------------------
 1 file changed, 57 insertions(+), 54 deletions(-)

diff --git a/libc/src/__support/block.h b/libc/src/__support/block.h
index 2b78c76261fc9..905b9a0596836 100644
--- a/libc/src/__support/block.h
+++ b/libc/src/__support/block.h
@@ -106,6 +106,7 @@ class BlockRef {
   static constexpr size_t PREV_OFFSET = 0;
   static constexpr size_t NEXT_OFFSET = sizeof(size_t);
 
+protected:
   cpp::byte *self = nullptr;
 
   LIBC_INLINE constexpr BlockRef() = default;
@@ -168,39 +169,27 @@ class BlockRef {
   /// @returns The total size of the block in bytes, including the header.
   LIBC_INLINE size_t outer_size() const { return load_next() & SIZE_MASK; }
 
+  /// @returns The number of usable bytes inside the block were it to be
+  /// allocated.
+  LIBC_INLINE size_t inner_size() const;
+
+  /// @returns The number of usable bytes inside the block if it remains free.
+  LIBC_INLINE size_t inner_size_free() const;
+
   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 a block with the given outer
-  /// size were it to be allocated.
   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 a block with the given outer
-  /// size if it remains free.
   LIBC_INLINE static constexpr size_t inner_size_free(size_t outer_size) {
     return outer_size - HEADER_SIZE;
   }
 
-  /// @returns The number of usable bytes inside the block were it to be
-  /// allocated.
-  LIBC_INLINE size_t inner_size() const {
-    if (load_next() & LAST_MASK)
-      return 0;
-    return inner_size(outer_size());
-  }
-
-  /// @returns The number of usable bytes inside the block if it remains free.
-  LIBC_INLINE size_t inner_size_free() const {
-    if (load_next() & LAST_MASK)
-      return 0;
-    return inner_size_free(outer_size());
-  }
-
   /// @returns A pointer to the usable space inside this block.
   ///
   /// Aligned to some multiple of MIN_ALIGN.
@@ -235,42 +224,25 @@ class BlockRef {
   LIBC_INLINE BlockPtr prev_free() const;
 
   /// @returns Whether the block is unavailable for allocation.
-  LIBC_INLINE bool used() const {
-    size_t next_value = load_next();
-    if (next_value & LAST_MASK)
-      return true;
-    BlockRef next_block(self + (next_value & SIZE_MASK));
-    return !(next_block.load_next() & PREV_FREE_MASK);
-  }
+  LIBC_INLINE bool used() const;
 
   /// Marks this block as in use.
-  LIBC_INLINE void mark_used() const {
-    LIBC_ASSERT(!(load_next() & LAST_MASK) && "last block is always considered used");
-    BlockRef next_block(self + outer_size());
-    next_block.store_next(next_block.load_next() & ~PREV_FREE_MASK);
-  }
+  LIBC_INLINE void mark_used() const;
 
   /// Marks this block as free.
-  LIBC_INLINE void mark_free() const {
-    LIBC_ASSERT(!(load_next() & LAST_MASK) && "last block is always considered used");
-    BlockRef next_block(self + outer_size());
-    next_block.store_next(next_block.load_next() | PREV_FREE_MASK);
-    next_block.store_prev(outer_size());
-  }
+  LIBC_INLINE void mark_free() const;
 
   LIBC_INLINE bool is_usable_space_aligned(size_t alignment) const {
     return reinterpret_cast<uintptr_t>(usable_space()) % alignment == 0;
   }
 };
 
-class BlockPtr {
-  BlockRef block;
-
-  LIBC_INLINE explicit constexpr BlockPtr(cpp::byte *ptr) : block(ptr) {}
-  LIBC_INLINE explicit constexpr BlockPtr(BlockRef b) : block(b.self) {}
-
+class BlockPtr : private BlockRef {
   friend class BlockRef;
 
+  LIBC_INLINE explicit constexpr BlockPtr(cpp::byte *ptr) : BlockRef(ptr) {}
+  LIBC_INLINE explicit constexpr BlockPtr(BlockRef b) : BlockRef(b.self) {}
+
 public:
   static constexpr size_t HEADER_SIZE = BlockRef::HEADER_SIZE;
   static constexpr size_t MIN_ALIGN = BlockRef::MIN_ALIGN;
@@ -279,20 +251,20 @@ class BlockPtr {
   LIBC_INLINE constexpr BlockPtr() = default;
 
   LIBC_INLINE explicit constexpr operator bool() const {
-    return block.self != nullptr;
+    return self != nullptr;
   }
   LIBC_INLINE constexpr bool operator==(BlockPtr other) const {
-    return block.self == other.block.self;
+    return self == other.self;
   }
   LIBC_INLINE constexpr bool operator!=(BlockPtr other) const {
     return !(*this == other);
   }
 
-  LIBC_INLINE BlockRef &operator*() { return block; }
-  LIBC_INLINE const BlockRef &operator*() const { return block; }
+  LIBC_INLINE BlockRef &operator*() { return *this; }
+  LIBC_INLINE const BlockRef &operator*() const { return *this; }
 
-  LIBC_INLINE BlockRef *operator->() { return █ }
-  LIBC_INLINE const BlockRef *operator->() const { return █ }
+  LIBC_INLINE BlockRef *operator->() { return this; }
+  LIBC_INLINE const BlockRef *operator->() const { return this; }
 
   /// 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
@@ -316,17 +288,15 @@ class BlockPtr {
   }
 
   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;
+    return BlockRef::outer_size(inner_size);
   }
 
   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;
+    return BlockRef::inner_size(outer_size);
   }
 
   LIBC_INLINE static constexpr size_t inner_size_free(size_t outer_size) {
-    return outer_size - HEADER_SIZE;
+    return BlockRef::inner_size_free(outer_size);
   }
 
   // Returns the minimum inner size necessary for a block of that size to
@@ -423,6 +393,21 @@ void BlockRef::make_last_block(cpp::byte *start) {
   BlockPtr last(start);
   last->store_next(HEADER_SIZE | LAST_MASK);
 }
+
+LIBC_INLINE
+size_t BlockRef::inner_size() const {
+  if (!next())
+    return 0;
+  return inner_size(outer_size());
+}
+
+LIBC_INLINE
+size_t BlockRef::inner_size_free() const {
+  if (!next())
+    return 0;
+  return inner_size_free(outer_size());
+}
+
 LIBC_INLINE
 BlockPtr BlockRef::next() const {
   size_t next_value = load_next();
@@ -438,6 +423,24 @@ BlockPtr BlockRef::prev_free() const {
   return BlockPtr(self - load_prev());
 }
 
+LIBC_INLINE
+bool BlockRef::used() const { return !next() || !next()->prev_free(); }
+
+LIBC_INLINE
+void BlockRef::mark_used() const {
+  LIBC_ASSERT(next() && "last block is always considered used");
+  BlockPtr next_block = next();
+  next_block->store_next(next_block->load_next() & ~PREV_FREE_MASK);
+}
+
+LIBC_INLINE
+void BlockRef::mark_free() const {
+  LIBC_ASSERT(next() && "last block is always considered used");
+  BlockPtr next_block = next();
+  next_block->store_next(next_block->load_next() | PREV_FREE_MASK);
+  next_block->store_prev(outer_size());
+}
+
 LIBC_INLINE
 optional<BlockPtr> BlockPtr::init(ByteSpan region) {
   if (!region.data())



More information about the libc-commits mailing list