[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