| |
| |
| |
| |
| |
| |
| |
|
|
| #ifndef LLVM_LIBC_SRC___SUPPORT_BLOCK_H |
| #define LLVM_LIBC_SRC___SUPPORT_BLOCK_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 <stdint.h> |
|
|
| namespace LIBC_NAMESPACE_DECL { |
|
|
| |
| LIBC_INLINE constexpr size_t align_down(size_t value, size_t alignment) { |
| |
| return (value / alignment) * alignment; |
| } |
|
|
| |
| |
| 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; |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| class Block { |
| |
| 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); |
|
|
| public: |
| |
| Block(const Block &other) = delete; |
| Block &operator=(const Block &other) = delete; |
|
|
| |
| |
| |
| static optional<Block *> init(ByteSpan region); |
|
|
| |
| |
| |
| |
| |
| |
| |
| 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)); |
| } |
| LIBC_INLINE static const 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)); |
| } |
|
|
| |
| LIBC_INLINE size_t outer_size() const { return next_ & SIZE_MASK; } |
|
|
| LIBC_INLINE static size_t outer_size(size_t inner_size) { |
| |
| return inner_size - sizeof(prev_) + sizeof(Block); |
| } |
|
|
| |
| |
| LIBC_INLINE size_t inner_size() const { |
| if (!next()) |
| return 0; |
| return inner_size(outer_size()); |
| } |
|
|
| |
| |
| LIBC_INLINE static size_t inner_size(size_t outer_size) { |
| |
| return inner_size_free(outer_size) + sizeof(prev_); |
| } |
|
|
| |
| LIBC_INLINE size_t inner_size_free() const { |
| if (!next()) |
| return 0; |
| return inner_size_free(outer_size()); |
| } |
|
|
| |
| |
| LIBC_INLINE static size_t inner_size_free(size_t outer_size) { |
| return outer_size - sizeof(Block); |
| } |
|
|
| |
| |
| |
| LIBC_INLINE cpp::byte *usable_space() { |
| auto *s = reinterpret_cast<cpp::byte *>(this) + sizeof(Block); |
| LIBC_ASSERT(reinterpret_cast<uintptr_t>(s) % alignof(max_align_t) == 0 && |
| "usable space must be aligned to a multiple of max_align_t"); |
| return s; |
| } |
| LIBC_INLINE const cpp::byte *usable_space() const { |
| const auto *s = reinterpret_cast<const cpp::byte *>(this) + sizeof(Block); |
| LIBC_ASSERT(reinterpret_cast<uintptr_t>(s) % alignof(max_align_t) == 0 && |
| "usable space must be aligned to a multiple of max_align_t"); |
| return s; |
| } |
|
|
| |
| LIBC_INLINE ByteSpan region() { |
| return {reinterpret_cast<cpp::byte *>(this), outer_size()}; |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| optional<Block *> split(size_t new_inner_size, |
| size_t usable_space_alignment = alignof(max_align_t)); |
|
|
| |
| bool merge_next(); |
|
|
| |
| |
| LIBC_INLINE Block *next() const { |
| if (next_ & LAST_MASK) |
| return nullptr; |
| return reinterpret_cast<Block *>(reinterpret_cast<uintptr_t>(this) + |
| outer_size()); |
| } |
|
|
| |
| LIBC_INLINE Block *prev_free() const { |
| if (!(next_ & PREV_FREE_MASK)) |
| return nullptr; |
| return reinterpret_cast<Block *>(reinterpret_cast<uintptr_t>(this) - prev_); |
| } |
|
|
| |
| LIBC_INLINE bool used() const { return !next() || !next()->prev_free(); } |
|
|
| |
| LIBC_INLINE void mark_used() { |
| LIBC_ASSERT(next() && "last block is always considered used"); |
| next()->next_ &= ~PREV_FREE_MASK; |
| } |
|
|
| |
| LIBC_INLINE void mark_free() { |
| LIBC_ASSERT(next() && "last block is always considered used"); |
| next()->next_ |= PREV_FREE_MASK; |
| |
| |
| *new (&next()->prev_) size_t = outer_size(); |
| } |
|
|
| LIBC_INLINE Block(size_t outer_size, bool is_last) : next_(outer_size) { |
| |
| |
| |
| LIBC_ASSERT( |
| outer_size % (is_last ? alignof(Block) : alignof(max_align_t)) == 0 && |
| "block sizes must be aligned"); |
| LIBC_ASSERT(is_usable_space_aligned(alignof(max_align_t)) && |
| "usable space must be aligned to a multiple of max_align_t"); |
| if (is_last) |
| next_ |= LAST_MASK; |
| } |
|
|
| LIBC_INLINE bool is_usable_space_aligned(size_t alignment) const { |
| return reinterpret_cast<uintptr_t>(usable_space()) % alignment == 0; |
| } |
|
|
| |
| |
| |
| |
| LIBC_INLINE static size_t min_size_for_allocation(size_t alignment, |
| size_t size) { |
| LIBC_ASSERT(alignment >= alignof(max_align_t) && |
| alignment % alignof(max_align_t) == 0 && |
| "alignment must be multiple of max_align_t"); |
|
|
| if (alignment == alignof(max_align_t)) |
| return size; |
|
|
| |
| |
| if (add_overflow(size, sizeof(Block), size)) |
| return 0; |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| if (add_overflow(size, alignment - alignof(Block), size)) |
| return 0; |
| return size; |
| } |
|
|
| |
| |
| struct BlockInfo { |
| |
| |
| Block *block; |
|
|
| |
| |
| |
| |
| |
| |
| Block *prev; |
|
|
| |
| |
| |
| Block *next; |
| }; |
|
|
| |
| |
| static BlockInfo allocate(Block *block, size_t alignment, size_t size); |
|
|
| |
| LIBC_INLINE static uintptr_t next_possible_block_start( |
| uintptr_t ptr, size_t usable_space_alignment = alignof(max_align_t)) { |
| return align_up(ptr + sizeof(Block), usable_space_alignment) - |
| sizeof(Block); |
| } |
| LIBC_INLINE static uintptr_t prev_possible_block_start( |
| uintptr_t ptr, size_t usable_space_alignment = alignof(max_align_t)) { |
| return align_down(ptr, usable_space_alignment) - sizeof(Block); |
| } |
|
|
| private: |
| |
| |
| LIBC_INLINE static Block *as_block(ByteSpan bytes) { |
| LIBC_ASSERT(reinterpret_cast<uintptr_t>(bytes.data()) % alignof(Block) == |
| 0 && |
| "block start must be suitably aligned"); |
| return ::new (bytes.data()) Block(bytes.size(), false); |
| } |
|
|
| LIBC_INLINE static void make_last_block(cpp::byte *start) { |
| LIBC_ASSERT(reinterpret_cast<uintptr_t>(start) % alignof(Block) == 0 && |
| "block start must be suitably aligned"); |
| ::new (start) Block(sizeof(Block), true); |
| } |
|
|
| |
| |
| |
| |
| size_t prev_ = 0; |
|
|
| |
| |
| size_t next_ = 0; |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
|
|
| public: |
| |
| static constexpr size_t PREV_FIELD_SIZE = sizeof(prev_); |
| }; |
|
|
| static_assert(alignof(Block) >= 4, |
| "at least 2 bits must be available in block sizes for flags"); |
|
|
| LIBC_INLINE |
| optional<Block *> Block::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 + sizeof(Block) > last_start) |
| 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(); |
| return block; |
| } |
|
|
| LIBC_INLINE |
| Block::BlockInfo Block::allocate(Block *block, size_t alignment, size_t size) { |
| LIBC_ASSERT(alignment % alignof(max_align_t) == 0 && |
| "alignment must be a multiple of max_align_t"); |
|
|
| BlockInfo info{block, nullptr, nullptr}; |
|
|
| if (!info.block->is_usable_space_aligned(alignment)) { |
| Block *original = info.block; |
| |
| 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()) { |
| |
| |
| prev->merge_next(); |
| } else { |
| info.prev = original; |
| } |
|
|
| 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; |
| } |
|
|
| |
| 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) { |
| LIBC_ASSERT(usable_space_alignment % alignof(max_align_t) == 0 && |
| "alignment must be a multiple of max_align_t"); |
| if (used()) |
| return {}; |
|
|
| |
| |
| size_t min_outer_size = outer_size(cpp::max(new_inner_size, sizeof(prev_))); |
|
|
| uintptr_t start = reinterpret_cast<uintptr_t>(this); |
| 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 % alignof(max_align_t) == 0 && |
| "new size must be aligned to max_align_t"); |
|
|
| if (outer_size() < new_outer_size || |
| outer_size() - new_outer_size < sizeof(Block)) |
| return {}; |
|
|
| ByteSpan new_region = region().subspan(new_outer_size); |
| next_ &= ~SIZE_MASK; |
| next_ |= new_outer_size; |
|
|
| Block *new_block = as_block(new_region); |
| mark_free(); |
| new_block->next()->prev_ = new_region.size(); |
|
|
| 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()) |
| return false; |
| size_t new_size = outer_size() + next()->outer_size(); |
| next_ &= ~SIZE_MASK; |
| next_ |= new_size; |
| next()->prev_ = new_size; |
| return true; |
| } |
|
|
| } |
|
|
| #endif |
|
|