blob: be2a71f32a23f14c6b585ad3c0e83bed27f8e329 [file]
//===----------------------------------------------------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
///
/// \file
/// Implementation header for a block of memory.
///
//===----------------------------------------------------------------------===//
#ifndef LLVM_LIBC_SRC___SUPPORT_BLOCK_H
#define LLVM_LIBC_SRC___SUPPORT_BLOCK_H
#include "hdr/stdint_proxy.h"
#include "hdr/types/size_t.h"
#include "src/__support/CPP/algorithm.h"
#include "src/__support/CPP/cstddef.h"
#include "src/__support/CPP/limits.h"
#include "src/__support/CPP/new.h"
#include "src/__support/CPP/optional.h"
#include "src/__support/CPP/span.h"
#include "src/__support/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 {
/// Returns the value rounded down to the nearest multiple of alignment.
LIBC_INLINE constexpr size_t align_down(size_t value, size_t alignment) {
// Note this shouldn't overflow since the result will always be <= value.
return (value / alignment) * alignment;
}
/// Returns the value rounded up to the nearest multiple of alignment. May wrap
/// around.
LIBC_INLINE constexpr size_t align_up(size_t value, size_t alignment) {
return align_down(value + alignment - 1, alignment);
}
using ByteSpan = cpp::span<LIBC_NAMESPACE::cpp::byte>;
using cpp::optional;
/// Reference to a memory region with links to adjacent blocks.
///
/// The blocks store their offsets to the previous and next blocks. The latter
/// is also the block's size. The metadata is stored in raw bytes and accessed
/// 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 blocks. 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;
static constexpr size_t LAST_MASK = 1 << 1;
static constexpr size_t SIZE_MASK = ~(PREV_FREE_MASK | LAST_MASK);
// Header field offsets. The value at PREV_OFFSET is only meaningful when the
// PREV_FREE_MASK bit is set in the next field.
static constexpr size_t PREV_OFFSET = 0;
static constexpr size_t NEXT_OFFSET = PREV_OFFSET + sizeof(size_t);
public:
static constexpr size_t HEADER_SIZE = NEXT_OFFSET + sizeof(size_t);
// To ensure block sizes have two lower unused bits, ensure usable space is
// always aligned to at least 4 bytes. (The distances between usable spaces,
// 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 BlockRef() = default;
LIBC_INLINE explicit constexpr BlockRef(cpp::byte *header_ptr)
: header_ptr(header_ptr) {}
LIBC_INLINE explicit constexpr operator bool() const {
return header_ptr != nullptr;
}
LIBC_INLINE constexpr bool operator==(BlockRef other) const {
return header_ptr == other.header_ptr;
}
LIBC_INLINE constexpr bool operator!=(BlockRef other) const {
return !(*this == other);
}
LIBC_INLINE uintptr_t addr() const {
return reinterpret_cast<uintptr_t>(header_ptr);
}
/// 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<BlockRef> init(ByteSpan region);
/// @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 BlockRef from_usable_space(void *usable_space) {
LIBC_ASSERT(usable_space != nullptr && "usable space cannot be null");
auto *bytes = reinterpret_cast<cpp::byte *>(usable_space);
return BlockRef(bytes - HEADER_SIZE);
}
LIBC_INLINE static BlockRef from_usable_space(const void *usable_space) {
LIBC_ASSERT(usable_space != nullptr && "usable space cannot be null");
const auto *bytes = reinterpret_cast<const cpp::byte *>(usable_space);
return BlockRef(const_cast<cpp::byte *>(bytes - HEADER_SIZE));
}
/// @returns The total size of the block in bytes, including the header.
LIBC_INLINE size_t outer_size() const { return 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 = nonnull_header_ptr() + HEADER_SIZE;
LIBC_ASSERT(reinterpret_cast<uintptr_t>(s) % MIN_ALIGN == 0 &&
"usable space must be aligned to MIN_ALIGN");
return s;
}
// @returns The region of memory the block manages, including the header.
LIBC_INLINE ByteSpan region() const {
return {nonnull_header_ptr(), 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<BlockRef> split(size_t new_inner_size,
size_t usable_space_alignment = MIN_ALIGN) const;
/// Merges this block with the one that comes after it.
bool merge_next() const;
/// @returns The block immediately after this one, or a null block if this is
/// the last block.
LIBC_INLINE BlockRef next() const {
size_t next_value = load_next();
if (next_value & LAST_MASK)
return BlockRef();
return BlockRef(nonnull_header_ptr() + (next_value & SIZE_MASK));
}
/// @returns The free block immediately before this one, otherwise null.
LIBC_INLINE BlockRef prev_free() const {
if (!(load_next() & PREV_FREE_MASK))
return BlockRef();
return BlockRef(nonnull_header_ptr() - 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");
BlockRef next_block = next();
next_block.store_next(next_block.load_next() & ~PREV_FREE_MASK);
}
/// Marks this block as free.
LIBC_INLINE void mark_free() const {
LIBC_ASSERT(next() && "last block is always considered used");
BlockRef next_block = next();
next_block.store_next(next_block.load_next() | PREV_FREE_MASK);
next_block.store_prev(outer_size());
}
LIBC_INLINE bool is_usable_space_aligned(size_t alignment) const {
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.
//
// Returns 0 if there is no such size.
LIBC_INLINE static size_t min_size_for_allocation(size_t alignment,
size_t size) {
LIBC_ASSERT(alignment >= MIN_ALIGN && alignment % MIN_ALIGN == 0 &&
"alignment must be multiple of MIN_ALIGN");
if (alignment == MIN_ALIGN)
return size;
// 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, HEADER_SIZE, size))
return 0;
// Beyond that, padding space may need to remain in this block to ensure
// that the usable space of the next block is aligned.
//
// Consider a position P of some lesser alignment, L, with maximal distance
// to the next position of some greater alignment, G, where G is a multiple
// of L. P must be one L unit past a G-aligned point. If it were one L-unit
// earlier, its distance would be zero. If it were one L-unit later, its
// distance would not be maximal. If it were not some integral number of L
// units away, it would not be L-aligned.
//
// 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 >= 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;
}
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(BlockRef block, size_t alignment, size_t size);
// These two functions may wrap around.
LIBC_INLINE static uintptr_t
next_possible_block_start(uintptr_t ptr,
size_t usable_space_alignment = MIN_ALIGN) {
return align_up(ptr + 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) - 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 BlockRef as_block(ByteSpan bytes) {
LIBC_ASSERT(reinterpret_cast<uintptr_t>(bytes.data()) % alignof(size_t) ==
0 &&
"block start must be suitably aligned");
LIBC_ASSERT(bytes.size() % MIN_ALIGN == 0 &&
"block size must be aligned to MIN_ALIGN");
BlockRef block(bytes.data());
block.store_next(bytes.size());
return block;
}
LIBC_INLINE static void make_last_block(cpp::byte *start) {
LIBC_ASSERT(reinterpret_cast<uintptr_t>(start) % alignof(size_t) == 0 &&
"block start must be suitably aligned");
BlockRef last(start);
last.store_next(HEADER_SIZE | LAST_MASK);
}
LIBC_INLINE cpp::byte *field_ptr(size_t offset) const {
cpp::byte *ptr = nonnull_header_ptr() + offset;
LIBC_ASSERT(reinterpret_cast<uintptr_t>(ptr) % alignof(size_t) == 0 &&
"block metadata fields must be aligned");
return ptr;
}
LIBC_INLINE size_t load_field(size_t offset) const {
return *reinterpret_cast<const size_t *>(field_ptr(offset));
}
LIBC_INLINE void store_field(size_t offset, size_t value) const {
new (field_ptr(offset)) size_t(value);
}
/// Offset from this block to the previous block. 0 if this is the first
/// block. This field is only alive when the previous block is free;
/// otherwise, its memory is reused as part of the previous block's usable
/// space.
LIBC_INLINE size_t load_prev() const { return load_field(PREV_OFFSET); }
/// Offset from this block to the next block. Valid even if this is the last
/// block, since it equals the size of the block.
///
/// 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 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 cpp::byte *nonnull_header_ptr() const {
LIBC_ASSERT(header_ptr != nullptr && "operation on an invalid block");
#if __has_builtin(__builtin_assume)
__builtin_assume(header_ptr != nullptr);
#endif
return header_ptr;
}
cpp::byte *header_ptr = nullptr;
};
// This is the return type for `allocate` which can split one block into up to
// three blocks.
struct BlockRef::BlockInfo {
// This is the newly aligned block. It will have the alignment requested by a
// call to `allocate` and at most `size`.
BlockRef block;
// If the usable_space in the new block was not aligned according to the
// `alignment` parameter, we will need to split into this block and the
// `block` to ensure `block` is properly aligned. In this case, `prev` will be
// this new "padding" block. `prev` will be null if no new block was created
// or we were able to merge the block before the original block with the
// "padding" block.
BlockRef prev;
// This is the remainder of the next block after splitting the `block`
// according to `size`. This can happen if there's enough space after the
// `block`.
BlockRef next;
};
LIBC_INLINE
optional<BlockRef> BlockRef::init(ByteSpan region) {
if (!region.data())
return {};
uintptr_t start = reinterpret_cast<uintptr_t>(region.data());
uintptr_t end = start + region.size();
if (end < start)
return {};
uintptr_t block_start = next_possible_block_start(start);
if (block_start < start)
return {};
uintptr_t last_start = prev_possible_block_start(end);
if (last_start >= end)
return {};
if (block_start + HEADER_SIZE > last_start)
return {};
auto *last_start_ptr = reinterpret_cast<cpp::byte *>(last_start);
BlockRef block =
as_block({reinterpret_cast<cpp::byte *>(block_start), last_start_ptr});
make_last_block(last_start_ptr);
block.mark_free();
return block;
}
LIBC_INLINE
BlockRef::BlockInfo BlockRef::allocate(BlockRef block, size_t alignment,
size_t size) {
LIBC_ASSERT(block.header_ptr != nullptr &&
"allocating from an invalid block");
LIBC_ASSERT(alignment % MIN_ALIGN == 0 &&
"alignment must be a multiple of MIN_ALIGN");
BlockInfo info{block, BlockRef(), BlockRef()};
if (!info.block.is_usable_space_aligned(alignment)) {
BlockRef original = info.block;
// The padding block has no minimum size requirement.
optional<BlockRef> maybe_aligned_block = original.split(0, alignment);
LIBC_ASSERT(maybe_aligned_block.has_value() &&
"it should always be possible to split for alignment");
if (BlockRef prev = original.prev_free()) {
// If there is a free block before this, we can merge the current one with
// the newly created one.
prev.merge_next();
} else {
info.prev = original;
}
BlockRef aligned_block = *maybe_aligned_block;
LIBC_ASSERT(aligned_block.is_usable_space_aligned(alignment) &&
"The aligned block isn't aligned somehow.");
info.block = aligned_block;
}
// Now get a block for the requested size.
if (optional<BlockRef> next = info.block.split(size))
info.next = *next;
return info;
}
LIBC_INLINE
optional<BlockRef> BlockRef::split(size_t new_inner_size,
size_t usable_space_alignment) const {
LIBC_ASSERT(usable_space_alignment % MIN_ALIGN == 0 &&
"alignment must be a multiple of MIN_ALIGN");
// Compute the minimum outer size that produces a block of at least
// `new_inner_size`.
size_t min_outer_size = outer_size(cpp::max(new_inner_size, PREV_FIELD_SIZE));
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)
return {};
size_t new_outer_size = next_block_start - start;
LIBC_ASSERT(new_outer_size % MIN_ALIGN == 0 &&
"new size must be aligned to MIN_ALIGN");
if (outer_size() < new_outer_size ||
outer_size() - new_outer_size < HEADER_SIZE)
return {};
bool was_free = !used();
ByteSpan new_region = region().subspan(new_outer_size);
store_next((load_next() & ~SIZE_MASK) | new_outer_size);
BlockRef new_block = as_block(new_region);
new_block.mark_free();
if (was_free)
mark_free();
LIBC_ASSERT(new_block.is_usable_space_aligned(usable_space_alignment) &&
"usable space must have requested alignment");
return new_block;
}
LIBC_INLINE
bool BlockRef::merge_next() const {
BlockRef next_block = next();
if (used() || next_block.used())
return false;
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;
}
} // namespace LIBC_NAMESPACE_DECL
#endif // LLVM_LIBC_SRC___SUPPORT_BLOCK_H