| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,482 @@ | ||
| //===-- Implementation header for a block of memory -------------*- C++ -*-===// | ||
| // | ||
| // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. | ||
| // See https://llvm.org/LICENSE.txt for license information. | ||
| // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception | ||
| // | ||
| //===----------------------------------------------------------------------===// | ||
|
|
||
| #ifndef LLVM_LIBC_SRC_STDLIB_BLOCK_H | ||
| #define LLVM_LIBC_SRC_STDLIB_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 <stdint.h> | ||
|
|
||
| namespace LIBC_NAMESPACE { | ||
|
|
||
| namespace internal { | ||
| // Types of corrupted blocks, and functions to crash with an error message | ||
| // corresponding to each type. | ||
| enum class BlockStatus { | ||
| VALID, | ||
| MISALIGNED, | ||
| PREV_MISMATCHED, | ||
| NEXT_MISMATCHED, | ||
| }; | ||
| } // namespace internal | ||
|
|
||
| /// 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 down to the nearest multiple of alignment. | ||
| LIBC_INLINE template <typename T> | ||
| constexpr T *align_down(T *value, size_t alignment) { | ||
| return reinterpret_cast<T *>( | ||
| align_down(reinterpret_cast<size_t>(value), alignment)); | ||
| } | ||
|
|
||
| /// Returns the value rounded up to the nearest multiple of alignment. | ||
| LIBC_INLINE constexpr size_t align_up(size_t value, size_t alignment) { | ||
| __builtin_add_overflow(value, alignment - 1, &value); | ||
| return align_down(value, alignment); | ||
| } | ||
|
|
||
| /// Returns the value rounded up to the nearest multiple of alignment. | ||
| template <typename T> | ||
| LIBC_INLINE constexpr T *align_up(T *value, size_t alignment) { | ||
| return reinterpret_cast<T *>( | ||
| align_up(reinterpret_cast<size_t>(value), alignment)); | ||
| } | ||
|
|
||
| using ByteSpan = cpp::span<LIBC_NAMESPACE::cpp::byte>; | ||
| using cpp::optional; | ||
|
|
||
| /// Memory region with links to adjacent blocks. | ||
| /// | ||
| /// The blocks do not encode their size directly. Instead, they encode offsets | ||
| /// to the next and previous blocks using the type given by the `OffsetType` | ||
| /// template parameter. The encoded offsets are simply the offsets divded by the | ||
| /// minimum block alignment, `ALIGNMENT`. | ||
| /// | ||
| /// The `ALIGNMENT` constant provided by the derived block is typically the | ||
| /// minimum value of `alignof(OffsetType)`. Since the addressable range of a | ||
| /// block is given by `std::numeric_limits<OffsetType>::max() * | ||
| /// ALIGNMENT`, it may be advantageous to set a higher alignment if it allows | ||
| /// using a smaller offset type, even if this wastes some bytes in order to | ||
| /// align block headers. | ||
| /// | ||
| /// Blocks will always be aligned to a `ALIGNMENT` boundary. Block sizes will | ||
| /// always be rounded up to a multiple of `ALIGNMENT`. | ||
| /// | ||
| /// As an example, the diagram below represents two contiguous | ||
| /// `Block<uint32_t, 8>`s. The indices indicate byte offsets: | ||
| /// | ||
| /// @code{.unparsed} | ||
| /// Block 1: | ||
| /// +---------------------+------+--------------+ | ||
| /// | Header | Info | Usable space | | ||
| /// +----------+----------+------+--------------+ | ||
| /// | prev | next | | | | ||
| /// | 0......3 | 4......7 | 8..9 | 10.......280 | | ||
| /// | 00000000 | 00000046 | 8008 | <app data> | | ||
| /// +----------+----------+------+--------------+ | ||
| /// Block 2: | ||
| /// +---------------------+------+--------------+ | ||
| /// | Header | Info | Usable space | | ||
| /// +----------+----------+------+--------------+ | ||
| /// | prev | next | | | | ||
| /// | 0......3 | 4......7 | 8..9 | 10......1056 | | ||
| /// | 00000046 | 00000106 | 2008 | f7f7....f7f7 | | ||
| /// +----------+----------+------+--------------+ | ||
| /// @endcode | ||
| /// | ||
| /// The overall size of the block (e.g. 280 bytes) is given by its next offset | ||
| /// multiplied by the alignment (e.g. 0x106 * 4). Also, 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`. | ||
| /// | ||
| /// @tparam OffsetType Unsigned integral type used to encode offsets. Larger | ||
| /// types can address more memory, but consume greater | ||
| /// overhead. | ||
| /// @tparam kAlign Sets the overall alignment for blocks. Minimum is | ||
| /// `alignof(OffsetType)` (the default). Larger values can | ||
| /// address more memory, but consume greater overhead. | ||
| template <typename OffsetType = uintptr_t, size_t kAlign = alignof(OffsetType)> | ||
| class Block { | ||
| public: | ||
| using offset_type = OffsetType; | ||
| static_assert(cpp::is_unsigned_v<offset_type>, | ||
| "offset type must be unsigned"); | ||
|
|
||
| static constexpr size_t ALIGNMENT = cpp::max(kAlign, alignof(offset_type)); | ||
| static constexpr size_t BLOCK_OVERHEAD = align_up(sizeof(Block), ALIGNMENT); | ||
|
|
||
| // No copy or move. | ||
| Block(const Block &other) = delete; | ||
| Block &operator=(const Block &other) = delete; | ||
|
|
||
| /// Creates the first block for a given memory 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. | ||
| /// | ||
| /// 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. | ||
| static Block *from_usable_space(void *usable_space) { | ||
| auto *bytes = reinterpret_cast<cpp::byte *>(usable_space); | ||
| return reinterpret_cast<Block *>(bytes - BLOCK_OVERHEAD); | ||
| } | ||
| 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 - BLOCK_OVERHEAD); | ||
| } | ||
|
|
||
| /// @returns The total size of the block in bytes, including the header. | ||
| size_t outer_size() const { return next_ * ALIGNMENT; } | ||
|
|
||
| /// @returns The number of usable bytes inside the block. | ||
| size_t inner_size() const { return outer_size() - BLOCK_OVERHEAD; } | ||
|
|
||
| /// @returns The number of bytes requested using AllocFirst or AllocLast. | ||
| size_t requested_size() const { return inner_size() - padding_; } | ||
|
|
||
| /// @returns A pointer to the usable space inside this block. | ||
| cpp::byte *usable_space() { | ||
| return reinterpret_cast<cpp::byte *>(this) + BLOCK_OVERHEAD; | ||
| } | ||
| const cpp::byte *usable_space() const { | ||
| return reinterpret_cast<const cpp::byte *>(this) + BLOCK_OVERHEAD; | ||
| } | ||
|
|
||
| /// Marks the block as free and merges it with any free neighbors. | ||
| /// | ||
| /// This method is static in order to consume and replace the given block | ||
| /// pointer. If neither member is free, the returned pointer will point to the | ||
| /// original block. Otherwise, it will point to the new, larger block created | ||
| /// by merging adjacent free blocks together. | ||
| static void free(Block *&block); | ||
|
|
||
| /// Attempts to split this block. | ||
| /// | ||
| /// If successful, the block will have an inner size of `new_inner_size`, | ||
| /// rounded up to a `ALIGNMENT` boundary. The remaining space will be | ||
| /// returned as a new block. | ||
| /// | ||
| /// This method may fail if the remaining space is too small to hold a new | ||
| /// block. If this method fails for any reason, the original block is | ||
| /// unmodified. | ||
| /// | ||
| /// This method is static in order to consume and replace the given block | ||
| /// pointer with a pointer to the new, smaller block. | ||
| static optional<Block *> split(Block *&block, size_t new_inner_size); | ||
|
|
||
| /// Merges this block with the one that comes after it. | ||
| /// | ||
| /// This method is static in order to consume and replace the given block | ||
| /// pointer with a pointer to the new, larger block. | ||
| static bool merge_next(Block *&block); | ||
|
|
||
| /// Fetches the block immediately after this one. | ||
| /// | ||
| /// For performance, this always returns a block pointer, even if the returned | ||
| /// pointer is invalid. The pointer is valid if and only if `last()` is false. | ||
| /// | ||
| /// Typically, after calling `Init` callers may save a pointer past the end of | ||
| /// the list using `next()`. This makes it easy to subsequently iterate over | ||
| /// the list: | ||
| /// @code{.cpp} | ||
| /// auto result = Block<>::init(byte_span); | ||
| /// Block<>* begin = *result; | ||
| /// Block<>* end = begin->next(); | ||
| /// ... | ||
| /// for (auto* block = begin; block != end; block = block->next()) { | ||
| /// // Do something which each block. | ||
| /// } | ||
| /// @endcode | ||
| Block *next() const; | ||
|
|
||
| /// @copydoc `next`. | ||
| static Block *next_block(const Block *block) { | ||
| return block == nullptr ? nullptr : block->next(); | ||
| } | ||
|
|
||
| /// @returns The block immediately before this one, or a null pointer if this | ||
| /// is the first block. | ||
| Block *prev() const; | ||
|
|
||
| /// @copydoc `prev`. | ||
| static Block *prev_block(const Block *block) { | ||
| return block == nullptr ? nullptr : block->prev(); | ||
| } | ||
|
|
||
| /// Returns the current alignment of a block. | ||
| size_t alignment() const { return used() ? info_.alignment : 1; } | ||
|
|
||
| /// Indicates whether the block is in use. | ||
| /// | ||
| /// @returns `true` if the block is in use or `false` if not. | ||
| bool used() const { return info_.used; } | ||
|
|
||
| /// Indicates whether this block is the last block or not (i.e. whether | ||
| /// `next()` points to a valid block or not). This is needed because | ||
| /// `next()` points to the end of this block, whether there is a valid | ||
| /// block there or not. | ||
| /// | ||
| /// @returns `true` is this is the last block or `false` if not. | ||
| bool last() const { return info_.last; } | ||
|
|
||
| /// Marks this block as in use. | ||
| void mark_used() { info_.used = 1; } | ||
|
|
||
| /// Marks this block as free. | ||
| void mark_free() { info_.used = 0; } | ||
|
|
||
| /// Marks this block as the last one in the chain. | ||
| void mark_last() { info_.last = 1; } | ||
|
|
||
| /// Clears the last bit from this block. | ||
| void clear_last() { info_.last = 1; } | ||
|
|
||
| /// @brief Checks if a block is valid. | ||
| /// | ||
| /// @returns `true` if and only if the following conditions are met: | ||
| /// * The block is aligned. | ||
| /// * The prev/next fields match with the previous and next blocks. | ||
| bool is_valid() const { | ||
| return check_status() == internal::BlockStatus::VALID; | ||
| } | ||
|
|
||
| private: | ||
| /// Consumes the block and returns as a span of bytes. | ||
| static ByteSpan as_bytes(Block *&&block); | ||
|
|
||
| /// Consumes the span of bytes and uses it to construct and return a block. | ||
| static Block *as_block(size_t prev_outer_size, ByteSpan bytes); | ||
|
|
||
| Block(size_t prev_outer_size, size_t outer_size); | ||
|
|
||
| /// Returns a `BlockStatus` that is either VALID or indicates the reason why | ||
| /// the block is invalid. | ||
| /// | ||
| /// If the block is invalid at multiple points, this function will only return | ||
| /// one of the reasons. | ||
| internal::BlockStatus check_status() const; | ||
|
|
||
| /// Like `split`, but assumes the caller has already checked to parameters to | ||
| /// ensure the split will succeed. | ||
| static Block *split_impl(Block *&block, size_t new_inner_size); | ||
|
|
||
| /// Offset (in increments of the minimum alignment) from this block to the | ||
| /// previous block. 0 if this is the first block. | ||
| offset_type prev_ = 0; | ||
|
|
||
| /// Offset (in increments of the minimum alignment) from this block to the | ||
| /// next block. Valid even if this is the last block, since it equals the | ||
| /// size of the block. | ||
| offset_type next_ = 0; | ||
|
|
||
| /// Information about the current state of the block: | ||
| /// * If the `used` flag is set, the block's usable memory has been allocated | ||
| /// and is being used. | ||
| /// * If the `last` flag is set, the block does not have a next block. | ||
| /// * If the `used` flag is set, the alignment represents the requested value | ||
| /// when the memory was allocated, which may be less strict than the actual | ||
| /// alignment. | ||
| struct { | ||
| uint16_t used : 1; | ||
| uint16_t last : 1; | ||
| uint16_t alignment : 14; | ||
| } info_; | ||
|
|
||
| /// Number of bytes allocated beyond what was requested. This will be at most | ||
| /// the minimum alignment, i.e. `alignof(offset_type).` | ||
| uint16_t padding_ = 0; | ||
| } __attribute__((packed, aligned(kAlign))); | ||
|
|
||
| // Public template method implementations. | ||
|
|
||
| LIBC_INLINE ByteSpan get_aligned_subspan(ByteSpan bytes, size_t alignment) { | ||
| if (bytes.data() == nullptr) | ||
| return ByteSpan(); | ||
|
|
||
| auto unaligned_start = reinterpret_cast<uintptr_t>(bytes.data()); | ||
| auto aligned_start = align_up(unaligned_start, alignment); | ||
| auto unaligned_end = unaligned_start + bytes.size(); | ||
| auto aligned_end = align_down(unaligned_end, alignment); | ||
|
|
||
| if (aligned_end <= aligned_start) | ||
| return ByteSpan(); | ||
|
|
||
| return bytes.subspan(aligned_start - unaligned_start, | ||
| aligned_end - aligned_start); | ||
| } | ||
|
|
||
| template <typename OffsetType, size_t kAlign> | ||
| optional<Block<OffsetType, kAlign> *> | ||
| Block<OffsetType, kAlign>::init(ByteSpan region) { | ||
| optional<ByteSpan> result = get_aligned_subspan(region, ALIGNMENT); | ||
| if (!result) | ||
| return {}; | ||
|
|
||
| region = result.value(); | ||
| if (region.size() < BLOCK_OVERHEAD) | ||
| return {}; | ||
|
|
||
| if (cpp::numeric_limits<OffsetType>::max() < region.size() / ALIGNMENT) | ||
| return {}; | ||
|
|
||
| Block *block = as_block(0, region); | ||
| block->mark_last(); | ||
| return block; | ||
| } | ||
|
|
||
| template <typename OffsetType, size_t kAlign> | ||
| void Block<OffsetType, kAlign>::free(Block *&block) { | ||
| if (block == nullptr) | ||
| return; | ||
|
|
||
| block->mark_free(); | ||
| Block *prev = block->prev(); | ||
|
|
||
| if (merge_next(prev)) | ||
| block = prev; | ||
|
|
||
| merge_next(block); | ||
| } | ||
|
|
||
| template <typename OffsetType, size_t kAlign> | ||
| optional<Block<OffsetType, kAlign> *> | ||
| Block<OffsetType, kAlign>::split(Block *&block, size_t new_inner_size) { | ||
| if (block == nullptr) | ||
| return {}; | ||
|
|
||
| if (block->used()) | ||
| return {}; | ||
|
|
||
| size_t old_inner_size = block->inner_size(); | ||
| new_inner_size = align_up(new_inner_size, ALIGNMENT); | ||
| if (old_inner_size < new_inner_size) | ||
| return {}; | ||
|
|
||
| if (old_inner_size - new_inner_size < BLOCK_OVERHEAD) | ||
| return {}; | ||
|
|
||
| return split_impl(block, new_inner_size); | ||
| } | ||
|
|
||
| template <typename OffsetType, size_t kAlign> | ||
| Block<OffsetType, kAlign> * | ||
| Block<OffsetType, kAlign>::split_impl(Block *&block, size_t new_inner_size) { | ||
| size_t prev_outer_size = block->prev_ * ALIGNMENT; | ||
| size_t outer_size1 = new_inner_size + BLOCK_OVERHEAD; | ||
| bool is_last = block->last(); | ||
| ByteSpan bytes = as_bytes(cpp::move(block)); | ||
| Block *block1 = as_block(prev_outer_size, bytes.subspan(0, outer_size1)); | ||
| Block *block2 = as_block(outer_size1, bytes.subspan(outer_size1)); | ||
|
|
||
| if (is_last) | ||
| block2->mark_last(); | ||
| else | ||
| block2->next()->prev_ = block2->next_; | ||
|
|
||
| block = cpp::move(block1); | ||
| return block2; | ||
| } | ||
|
|
||
| template <typename OffsetType, size_t kAlign> | ||
| bool Block<OffsetType, kAlign>::merge_next(Block *&block) { | ||
| if (block == nullptr) | ||
| return false; | ||
|
|
||
| if (block->last()) | ||
| return false; | ||
|
|
||
| Block *next = block->next(); | ||
| if (block->used() || next->used()) | ||
| return false; | ||
|
|
||
| size_t prev_outer_size = block->prev_ * ALIGNMENT; | ||
| bool is_last = next->last(); | ||
| ByteSpan prev_bytes = as_bytes(cpp::move(block)); | ||
| ByteSpan next_bytes = as_bytes(cpp::move(next)); | ||
| size_t outer_size = prev_bytes.size() + next_bytes.size(); | ||
| cpp::byte *merged = ::new (prev_bytes.data()) cpp::byte[outer_size]; | ||
| block = as_block(prev_outer_size, ByteSpan(merged, outer_size)); | ||
|
|
||
| if (is_last) | ||
| block->mark_last(); | ||
| else | ||
| block->next()->prev_ = block->next_; | ||
|
|
||
| return true; | ||
| } | ||
|
|
||
| template <typename OffsetType, size_t kAlign> | ||
| Block<OffsetType, kAlign> *Block<OffsetType, kAlign>::next() const { | ||
| uintptr_t addr = | ||
| last() ? 0 : reinterpret_cast<uintptr_t>(this) + outer_size(); | ||
| return reinterpret_cast<Block *>(addr); | ||
| } | ||
|
|
||
| template <typename OffsetType, size_t kAlign> | ||
| Block<OffsetType, kAlign> *Block<OffsetType, kAlign>::prev() const { | ||
| uintptr_t addr = | ||
| (prev_ == 0) ? 0 | ||
| : reinterpret_cast<uintptr_t>(this) - (prev_ * ALIGNMENT); | ||
| return reinterpret_cast<Block *>(addr); | ||
| } | ||
|
|
||
| // Private template method implementations. | ||
|
|
||
| template <typename OffsetType, size_t kAlign> | ||
| Block<OffsetType, kAlign>::Block(size_t prev_outer_size, size_t outer_size) { | ||
| prev_ = prev_outer_size / ALIGNMENT; | ||
| next_ = outer_size / ALIGNMENT; | ||
| info_.used = 0; | ||
| info_.last = 0; | ||
| info_.alignment = ALIGNMENT; | ||
| } | ||
|
|
||
| template <typename OffsetType, size_t kAlign> | ||
| ByteSpan Block<OffsetType, kAlign>::as_bytes(Block *&&block) { | ||
| size_t block_size = block->outer_size(); | ||
| cpp::byte *bytes = new (cpp::move(block)) cpp::byte[block_size]; | ||
| return {bytes, block_size}; | ||
| } | ||
|
|
||
| template <typename OffsetType, size_t kAlign> | ||
| Block<OffsetType, kAlign> * | ||
| Block<OffsetType, kAlign>::as_block(size_t prev_outer_size, ByteSpan bytes) { | ||
| return ::new (bytes.data()) Block(prev_outer_size, bytes.size()); | ||
| } | ||
|
|
||
| template <typename OffsetType, size_t kAlign> | ||
| internal::BlockStatus Block<OffsetType, kAlign>::check_status() const { | ||
| if (reinterpret_cast<uintptr_t>(this) % ALIGNMENT != 0) | ||
| return internal::BlockStatus::MISALIGNED; | ||
|
|
||
| if (!last() && (this >= next() || this != next()->prev())) | ||
| return internal::BlockStatus::NEXT_MISMATCHED; | ||
|
|
||
| if (prev() && (this <= prev() || this != prev()->next())) | ||
| return internal::BlockStatus::PREV_MISMATCHED; | ||
|
|
||
| return internal::BlockStatus::VALID; | ||
| } | ||
|
|
||
| } // namespace LIBC_NAMESPACE | ||
|
|
||
| #endif // LLVM_LIBC_SRC_STDLIB_BLOCK_H |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,570 @@ | ||
| //===-- Unittests for a block of memory -------------------------*- C++ -*-===// | ||
| // | ||
| // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. | ||
| // See https://llvm.org/LICENSE.txt for license information. | ||
| // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception | ||
| // | ||
| //===----------------------------------------------------------------------===// | ||
| #include <stddef.h> | ||
|
|
||
| #include "src/stdlib/block.h" | ||
|
|
||
| #include "src/__support/CPP/array.h" | ||
| #include "src/__support/CPP/span.h" | ||
| #include "src/string/memcpy.h" | ||
| #include "test/UnitTest/Test.h" | ||
|
|
||
| // Block types. | ||
| using LargeOffsetBlock = LIBC_NAMESPACE::Block<uint64_t>; | ||
| using SmallOffsetBlock = LIBC_NAMESPACE::Block<uint16_t>; | ||
|
|
||
| // For each of the block types above, we'd like to run the same tests since | ||
| // they should work independently of the parameter sizes. Rather than re-writing | ||
| // the same test for each case, let's instead create a custom test framework for | ||
| // each test case that invokes the actual testing function for each block type. | ||
| // | ||
| // It's organized this way because the ASSERT/EXPECT macros only work within a | ||
| // `Test` class due to those macros expanding to `test` methods. | ||
| #define TEST_FOR_EACH_BLOCK_TYPE(TestCase) \ | ||
| class LlvmLibcBlockTest##TestCase : public LIBC_NAMESPACE::testing::Test { \ | ||
| public: \ | ||
| template <typename BlockType> void RunTest(); \ | ||
| }; \ | ||
| TEST_F(LlvmLibcBlockTest##TestCase, TestCase) { \ | ||
| RunTest<LargeOffsetBlock>(); \ | ||
| RunTest<SmallOffsetBlock>(); \ | ||
| } \ | ||
| template <typename BlockType> void LlvmLibcBlockTest##TestCase::RunTest() | ||
|
|
||
| using LIBC_NAMESPACE::cpp::array; | ||
| using LIBC_NAMESPACE::cpp::byte; | ||
| using LIBC_NAMESPACE::cpp::span; | ||
|
|
||
| TEST_FOR_EACH_BLOCK_TYPE(CanCreateSingleAlignedBlock) { | ||
| constexpr size_t kN = 1024; | ||
| alignas(BlockType::ALIGNMENT) array<byte, kN> bytes; | ||
|
|
||
| auto result = BlockType::init(bytes); | ||
| ASSERT_TRUE(result.has_value()); | ||
| BlockType *block = *result; | ||
|
|
||
| EXPECT_EQ(block->outer_size(), kN); | ||
| EXPECT_EQ(block->inner_size(), kN - BlockType::BLOCK_OVERHEAD); | ||
| EXPECT_EQ(block->prev(), static_cast<BlockType *>(nullptr)); | ||
| EXPECT_EQ(block->next(), static_cast<BlockType *>(nullptr)); | ||
| EXPECT_FALSE(block->used()); | ||
| EXPECT_TRUE(block->last()); | ||
| } | ||
|
|
||
| TEST_FOR_EACH_BLOCK_TYPE(CanCreateUnalignedSingleBlock) { | ||
| constexpr size_t kN = 1024; | ||
|
|
||
| // Force alignment, so we can un-force it below | ||
| alignas(BlockType::ALIGNMENT) array<byte, kN> bytes; | ||
| span<byte> aligned(bytes); | ||
|
|
||
| auto result = BlockType::init(aligned.subspan(1)); | ||
| EXPECT_TRUE(result.has_value()); | ||
| } | ||
|
|
||
| TEST_FOR_EACH_BLOCK_TYPE(CannotCreateTooSmallBlock) { | ||
| array<byte, 2> bytes; | ||
| auto result = BlockType::init(bytes); | ||
| EXPECT_FALSE(result.has_value()); | ||
| } | ||
|
|
||
| // This test specifically checks that we cannot allocate a block with a size | ||
| // larger than what can be held by the offset type, we don't need to test with | ||
| // multiple block types for this particular check, so we use the normal TEST | ||
| // macro and not the custom framework. | ||
| TEST(LlvmLibcBlockTest, CannotCreateTooLargeBlock) { | ||
| using BlockType = LIBC_NAMESPACE::Block<uint8_t>; | ||
| constexpr size_t kN = 1024; | ||
|
|
||
| alignas(BlockType::ALIGNMENT) array<byte, kN> bytes; | ||
| auto result = BlockType::init(bytes); | ||
| EXPECT_FALSE(result.has_value()); | ||
| } | ||
|
|
||
| TEST_FOR_EACH_BLOCK_TYPE(CanSplitBlock) { | ||
| constexpr size_t kN = 1024; | ||
| constexpr size_t kSplitN = 512; | ||
|
|
||
| alignas(BlockType::ALIGNMENT) array<byte, kN> bytes; | ||
| auto result = BlockType::init(bytes); | ||
| ASSERT_TRUE(result.has_value()); | ||
| auto *block1 = *result; | ||
|
|
||
| result = BlockType::split(block1, kSplitN); | ||
| ASSERT_TRUE(result.has_value()); | ||
|
|
||
| auto *block2 = *result; | ||
|
|
||
| EXPECT_EQ(block1->inner_size(), kSplitN); | ||
| EXPECT_EQ(block1->outer_size(), kSplitN + BlockType::BLOCK_OVERHEAD); | ||
| EXPECT_FALSE(block1->last()); | ||
|
|
||
| EXPECT_EQ(block2->outer_size(), kN - kSplitN - BlockType::BLOCK_OVERHEAD); | ||
| EXPECT_FALSE(block2->used()); | ||
| EXPECT_TRUE(block2->last()); | ||
|
|
||
| EXPECT_EQ(block1->next(), block2); | ||
| EXPECT_EQ(block2->prev(), block1); | ||
| } | ||
|
|
||
| TEST_FOR_EACH_BLOCK_TYPE(CanSplitBlockUnaligned) { | ||
| constexpr size_t kN = 1024; | ||
|
|
||
| alignas(BlockType::ALIGNMENT) array<byte, kN> bytes; | ||
| auto result = BlockType::init(bytes); | ||
| ASSERT_TRUE(result.has_value()); | ||
| BlockType *block1 = *result; | ||
|
|
||
| // We should split at sizeof(BlockType) + kSplitN bytes. Then | ||
| // we need to round that up to an alignof(BlockType) boundary. | ||
| constexpr size_t kSplitN = 513; | ||
| uintptr_t split_addr = reinterpret_cast<uintptr_t>(block1) + kSplitN; | ||
| split_addr += alignof(BlockType) - (split_addr % alignof(BlockType)); | ||
| uintptr_t split_len = split_addr - (uintptr_t)&bytes; | ||
|
|
||
| result = BlockType::split(block1, kSplitN); | ||
| ASSERT_TRUE(result.has_value()); | ||
| BlockType *block2 = *result; | ||
|
|
||
| EXPECT_EQ(block1->inner_size(), split_len); | ||
| EXPECT_EQ(block1->outer_size(), split_len + BlockType::BLOCK_OVERHEAD); | ||
|
|
||
| EXPECT_EQ(block2->outer_size(), kN - block1->outer_size()); | ||
| EXPECT_FALSE(block2->used()); | ||
|
|
||
| EXPECT_EQ(block1->next(), block2); | ||
| EXPECT_EQ(block2->prev(), block1); | ||
| } | ||
|
|
||
| TEST_FOR_EACH_BLOCK_TYPE(CanSplitMidBlock) { | ||
| // split once, then split the original block again to ensure that the | ||
| // pointers get rewired properly. | ||
| // I.e. | ||
| // [[ BLOCK 1 ]] | ||
| // block1->split() | ||
| // [[ BLOCK1 ]][[ BLOCK2 ]] | ||
| // block1->split() | ||
| // [[ BLOCK1 ]][[ BLOCK3 ]][[ BLOCK2 ]] | ||
|
|
||
| constexpr size_t kN = 1024; | ||
| constexpr size_t kSplit1 = 512; | ||
| constexpr size_t kSplit2 = 256; | ||
|
|
||
| alignas(BlockType::ALIGNMENT) array<byte, kN> bytes; | ||
| auto result = BlockType::init(bytes); | ||
| ASSERT_TRUE(result.has_value()); | ||
| BlockType *block1 = *result; | ||
|
|
||
| result = BlockType::split(block1, kSplit1); | ||
| ASSERT_TRUE(result.has_value()); | ||
| BlockType *block2 = *result; | ||
|
|
||
| result = BlockType::split(block1, kSplit2); | ||
| ASSERT_TRUE(result.has_value()); | ||
| BlockType *block3 = *result; | ||
|
|
||
| EXPECT_EQ(block1->next(), block3); | ||
| EXPECT_EQ(block3->prev(), block1); | ||
| EXPECT_EQ(block3->next(), block2); | ||
| EXPECT_EQ(block2->prev(), block3); | ||
| } | ||
|
|
||
| TEST_FOR_EACH_BLOCK_TYPE(CannotSplitTooSmallBlock) { | ||
| constexpr size_t kN = 64; | ||
| constexpr size_t kSplitN = kN + 1; | ||
|
|
||
| alignas(BlockType::ALIGNMENT) array<byte, kN> bytes; | ||
| auto result = BlockType::init(bytes); | ||
| ASSERT_TRUE(result.has_value()); | ||
| BlockType *block = *result; | ||
|
|
||
| result = BlockType::split(block, kSplitN); | ||
| ASSERT_FALSE(result.has_value()); | ||
| } | ||
|
|
||
| TEST_FOR_EACH_BLOCK_TYPE(CannotSplitBlockWithoutHeaderSpace) { | ||
| constexpr size_t kN = 1024; | ||
| constexpr size_t kSplitN = kN - BlockType::BLOCK_OVERHEAD - 1; | ||
|
|
||
| alignas(BlockType::ALIGNMENT) array<byte, kN> bytes; | ||
| auto result = BlockType::init(bytes); | ||
| ASSERT_TRUE(result.has_value()); | ||
| BlockType *block = *result; | ||
|
|
||
| result = BlockType::split(block, kSplitN); | ||
| ASSERT_FALSE(result.has_value()); | ||
| } | ||
|
|
||
| TEST_FOR_EACH_BLOCK_TYPE(CannotSplitNull) { | ||
| BlockType *block = nullptr; | ||
| auto result = BlockType::split(block, 1); | ||
| ASSERT_FALSE(result.has_value()); | ||
| } | ||
|
|
||
| TEST_FOR_EACH_BLOCK_TYPE(CannotMakeBlockLargerInSplit) { | ||
| // Ensure that we can't ask for more space than the block actually has... | ||
| constexpr size_t kN = 1024; | ||
|
|
||
| alignas(BlockType::ALIGNMENT) array<byte, kN> bytes; | ||
| auto result = BlockType::init(bytes); | ||
| ASSERT_TRUE(result.has_value()); | ||
| BlockType *block = *result; | ||
|
|
||
| result = BlockType::split(block, block->inner_size() + 1); | ||
| ASSERT_FALSE(result.has_value()); | ||
| } | ||
|
|
||
| TEST_FOR_EACH_BLOCK_TYPE(CannotMakeSecondBlockLargerInSplit) { | ||
| // Ensure that the second block in split is at least of the size of header. | ||
| constexpr size_t kN = 1024; | ||
|
|
||
| alignas(BlockType::ALIGNMENT) array<byte, kN> bytes; | ||
| auto result = BlockType::init(bytes); | ||
| ASSERT_TRUE(result.has_value()); | ||
| BlockType *block = *result; | ||
|
|
||
| result = BlockType::split(block, block->inner_size() - | ||
| BlockType::BLOCK_OVERHEAD + 1); | ||
| ASSERT_FALSE(result.has_value()); | ||
| } | ||
|
|
||
| TEST_FOR_EACH_BLOCK_TYPE(CanMakeZeroSizeFirstBlock) { | ||
| // This block does support splitting with zero payload size. | ||
| constexpr size_t kN = 1024; | ||
|
|
||
| alignas(BlockType::ALIGNMENT) array<byte, kN> bytes; | ||
| auto result = BlockType::init(bytes); | ||
| ASSERT_TRUE(result.has_value()); | ||
| BlockType *block = *result; | ||
|
|
||
| result = BlockType::split(block, 0); | ||
| ASSERT_TRUE(result.has_value()); | ||
| EXPECT_EQ(block->inner_size(), static_cast<size_t>(0)); | ||
| } | ||
|
|
||
| TEST_FOR_EACH_BLOCK_TYPE(CanMakeZeroSizeSecondBlock) { | ||
| // Likewise, the split block can be zero-width. | ||
| constexpr size_t kN = 1024; | ||
|
|
||
| alignas(BlockType::ALIGNMENT) array<byte, kN> bytes; | ||
| auto result = BlockType::init(bytes); | ||
| ASSERT_TRUE(result.has_value()); | ||
| BlockType *block1 = *result; | ||
|
|
||
| result = BlockType::split(block1, | ||
| block1->inner_size() - BlockType::BLOCK_OVERHEAD); | ||
| ASSERT_TRUE(result.has_value()); | ||
| BlockType *block2 = *result; | ||
|
|
||
| EXPECT_EQ(block2->inner_size(), static_cast<size_t>(0)); | ||
| } | ||
|
|
||
| TEST_FOR_EACH_BLOCK_TYPE(CanMarkBlockUsed) { | ||
| constexpr size_t kN = 1024; | ||
|
|
||
| alignas(BlockType::ALIGNMENT) array<byte, kN> bytes; | ||
| auto result = BlockType::init(bytes); | ||
| ASSERT_TRUE(result.has_value()); | ||
| BlockType *block = *result; | ||
|
|
||
| block->mark_used(); | ||
| EXPECT_TRUE(block->used()); | ||
|
|
||
| // Size should be unaffected. | ||
| EXPECT_EQ(block->outer_size(), kN); | ||
|
|
||
| block->mark_free(); | ||
| EXPECT_FALSE(block->used()); | ||
| } | ||
|
|
||
| TEST_FOR_EACH_BLOCK_TYPE(CannotSplitUsedBlock) { | ||
| constexpr size_t kN = 1024; | ||
| constexpr size_t kSplitN = 512; | ||
|
|
||
| alignas(BlockType::ALIGNMENT) array<byte, kN> bytes; | ||
| auto result = BlockType::init(bytes); | ||
| ASSERT_TRUE(result.has_value()); | ||
| BlockType *block = *result; | ||
|
|
||
| block->mark_used(); | ||
| result = BlockType::split(block, kSplitN); | ||
| ASSERT_FALSE(result.has_value()); | ||
| } | ||
|
|
||
| TEST_FOR_EACH_BLOCK_TYPE(CanMergeWithNextBlock) { | ||
| // Do the three way merge from "CanSplitMidBlock", and let's | ||
| // merge block 3 and 2 | ||
| constexpr size_t kN = 1024; | ||
| constexpr size_t kSplit1 = 512; | ||
| constexpr size_t kSplit2 = 256; | ||
|
|
||
| alignas(BlockType::ALIGNMENT) array<byte, kN> bytes; | ||
| auto result = BlockType::init(bytes); | ||
| ASSERT_TRUE(result.has_value()); | ||
| BlockType *block1 = *result; | ||
|
|
||
| result = BlockType::split(block1, kSplit1); | ||
| ASSERT_TRUE(result.has_value()); | ||
|
|
||
| result = BlockType::split(block1, kSplit2); | ||
| ASSERT_TRUE(result.has_value()); | ||
| BlockType *block3 = *result; | ||
|
|
||
| EXPECT_TRUE(BlockType::merge_next(block3)); | ||
|
|
||
| EXPECT_EQ(block1->next(), block3); | ||
| EXPECT_EQ(block3->prev(), block1); | ||
| EXPECT_EQ(block1->inner_size(), kSplit2); | ||
| EXPECT_EQ(block3->outer_size(), kN - block1->outer_size()); | ||
| } | ||
|
|
||
| TEST_FOR_EACH_BLOCK_TYPE(CannotMergeWithFirstOrLastBlock) { | ||
| constexpr size_t kN = 1024; | ||
| constexpr size_t kSplitN = 512; | ||
|
|
||
| alignas(BlockType::ALIGNMENT) array<byte, kN> bytes; | ||
| auto result = BlockType::init(bytes); | ||
| ASSERT_TRUE(result.has_value()); | ||
| BlockType *block1 = *result; | ||
|
|
||
| // Do a split, just to check that the checks on next/prev are different... | ||
| result = BlockType::split(block1, kSplitN); | ||
| ASSERT_TRUE(result.has_value()); | ||
| BlockType *block2 = *result; | ||
|
|
||
| EXPECT_FALSE(BlockType::merge_next(block2)); | ||
| } | ||
|
|
||
| TEST_FOR_EACH_BLOCK_TYPE(CannotMergeNull) { | ||
| BlockType *block = nullptr; | ||
| EXPECT_FALSE(BlockType::merge_next(block)); | ||
| } | ||
|
|
||
| TEST_FOR_EACH_BLOCK_TYPE(CannotMergeUsedBlock) { | ||
| constexpr size_t kN = 1024; | ||
| constexpr size_t kSplitN = 512; | ||
|
|
||
| alignas(BlockType::ALIGNMENT) array<byte, kN> bytes; | ||
| auto result = BlockType::init(bytes); | ||
| ASSERT_TRUE(result.has_value()); | ||
| BlockType *block = *result; | ||
|
|
||
| // Do a split, just to check that the checks on next/prev are different... | ||
| result = BlockType::split(block, kSplitN); | ||
| ASSERT_TRUE(result.has_value()); | ||
|
|
||
| block->mark_used(); | ||
| EXPECT_FALSE(BlockType::merge_next(block)); | ||
| } | ||
|
|
||
| TEST_FOR_EACH_BLOCK_TYPE(CanFreeSingleBlock) { | ||
| constexpr size_t kN = 1024; | ||
| alignas(BlockType::ALIGNMENT) array<byte, kN> bytes; | ||
|
|
||
| auto result = BlockType::init(bytes); | ||
| ASSERT_TRUE(result.has_value()); | ||
| BlockType *block = *result; | ||
|
|
||
| block->mark_used(); | ||
| BlockType::free(block); | ||
| EXPECT_FALSE(block->used()); | ||
| EXPECT_EQ(block->outer_size(), kN); | ||
| } | ||
|
|
||
| TEST_FOR_EACH_BLOCK_TYPE(CanFreeBlockWithoutMerging) { | ||
| constexpr size_t kN = 1024; | ||
| constexpr size_t kSplit1 = 512; | ||
| constexpr size_t kSplit2 = 256; | ||
|
|
||
| alignas(BlockType::ALIGNMENT) array<byte, kN> bytes; | ||
| auto result = BlockType::init(bytes); | ||
| ASSERT_TRUE(result.has_value()); | ||
| BlockType *block1 = *result; | ||
|
|
||
| result = BlockType::split(block1, kSplit1); | ||
| ASSERT_TRUE(result.has_value()); | ||
| BlockType *block2 = *result; | ||
|
|
||
| result = BlockType::split(block2, kSplit2); | ||
| ASSERT_TRUE(result.has_value()); | ||
| BlockType *block3 = *result; | ||
|
|
||
| block1->mark_used(); | ||
| block2->mark_used(); | ||
| block3->mark_used(); | ||
|
|
||
| BlockType::free(block2); | ||
| EXPECT_FALSE(block2->used()); | ||
| EXPECT_NE(block2->prev(), static_cast<BlockType *>(nullptr)); | ||
| EXPECT_FALSE(block2->last()); | ||
| } | ||
|
|
||
| TEST_FOR_EACH_BLOCK_TYPE(CanFreeBlockAndMergeWithPrev) { | ||
| constexpr size_t kN = 1024; | ||
| constexpr size_t kSplit1 = 512; | ||
| constexpr size_t kSplit2 = 256; | ||
|
|
||
| alignas(BlockType::ALIGNMENT) array<byte, kN> bytes; | ||
| auto result = BlockType::init(bytes); | ||
| ASSERT_TRUE(result.has_value()); | ||
| BlockType *block1 = *result; | ||
|
|
||
| result = BlockType::split(block1, kSplit1); | ||
| ASSERT_TRUE(result.has_value()); | ||
| BlockType *block2 = *result; | ||
|
|
||
| result = BlockType::split(block2, kSplit2); | ||
| ASSERT_TRUE(result.has_value()); | ||
| BlockType *block3 = *result; | ||
|
|
||
| block2->mark_used(); | ||
| block3->mark_used(); | ||
|
|
||
| BlockType::free(block2); | ||
| EXPECT_FALSE(block2->used()); | ||
| EXPECT_EQ(block2->prev(), static_cast<BlockType *>(nullptr)); | ||
| EXPECT_FALSE(block2->last()); | ||
| } | ||
|
|
||
| TEST_FOR_EACH_BLOCK_TYPE(CanFreeBlockAndMergeWithNext) { | ||
| constexpr size_t kN = 1024; | ||
| constexpr size_t kSplit1 = 512; | ||
| constexpr size_t kSplit2 = 256; | ||
|
|
||
| alignas(BlockType::ALIGNMENT) array<byte, kN> bytes; | ||
| auto result = BlockType::init(bytes); | ||
| ASSERT_TRUE(result.has_value()); | ||
| BlockType *block1 = *result; | ||
|
|
||
| result = BlockType::split(block1, kSplit1); | ||
| ASSERT_TRUE(result.has_value()); | ||
| BlockType *block2 = *result; | ||
|
|
||
| result = BlockType::split(block2, kSplit2); | ||
| ASSERT_TRUE(result.has_value()); | ||
|
|
||
| block1->mark_used(); | ||
| block2->mark_used(); | ||
|
|
||
| BlockType::free(block2); | ||
| EXPECT_FALSE(block2->used()); | ||
| EXPECT_NE(block2->prev(), static_cast<BlockType *>(nullptr)); | ||
| EXPECT_TRUE(block2->last()); | ||
| } | ||
|
|
||
| TEST_FOR_EACH_BLOCK_TYPE(CanFreeUsedBlockAndMergeWithBoth) { | ||
| constexpr size_t kN = 1024; | ||
| constexpr size_t kSplit1 = 512; | ||
| constexpr size_t kSplit2 = 256; | ||
|
|
||
| alignas(BlockType::ALIGNMENT) array<byte, kN> bytes; | ||
| auto result = BlockType::init(bytes); | ||
| ASSERT_TRUE(result.has_value()); | ||
| BlockType *block1 = *result; | ||
|
|
||
| result = BlockType::split(block1, kSplit1); | ||
| ASSERT_TRUE(result.has_value()); | ||
| BlockType *block2 = *result; | ||
|
|
||
| result = BlockType::split(block2, kSplit2); | ||
| ASSERT_TRUE(result.has_value()); | ||
|
|
||
| block2->mark_used(); | ||
|
|
||
| BlockType::free(block2); | ||
| EXPECT_FALSE(block2->used()); | ||
| EXPECT_EQ(block2->prev(), static_cast<BlockType *>(nullptr)); | ||
| EXPECT_TRUE(block2->last()); | ||
| } | ||
|
|
||
| TEST_FOR_EACH_BLOCK_TYPE(CanCheckValidBlock) { | ||
| constexpr size_t kN = 1024; | ||
| constexpr size_t kSplit1 = 512; | ||
| constexpr size_t kSplit2 = 256; | ||
|
|
||
| alignas(BlockType::ALIGNMENT) array<byte, kN> bytes; | ||
| auto result = BlockType::init(bytes); | ||
| ASSERT_TRUE(result.has_value()); | ||
| BlockType *block1 = *result; | ||
|
|
||
| result = BlockType::split(block1, kSplit1); | ||
| ASSERT_TRUE(result.has_value()); | ||
| BlockType *block2 = *result; | ||
|
|
||
| result = BlockType::split(block2, kSplit2); | ||
| ASSERT_TRUE(result.has_value()); | ||
| BlockType *block3 = *result; | ||
|
|
||
| EXPECT_TRUE(block1->is_valid()); | ||
| EXPECT_TRUE(block2->is_valid()); | ||
| EXPECT_TRUE(block3->is_valid()); | ||
| } | ||
|
|
||
| TEST_FOR_EACH_BLOCK_TYPE(CanCheckInvalidBlock) { | ||
| constexpr size_t kN = 1024; | ||
| constexpr size_t kSplit1 = 128; | ||
| constexpr size_t kSplit2 = 384; | ||
| constexpr size_t kSplit3 = 256; | ||
|
|
||
| array<byte, kN> bytes{}; | ||
| auto result = BlockType::init(bytes); | ||
| ASSERT_TRUE(result.has_value()); | ||
| BlockType *block1 = *result; | ||
|
|
||
| result = BlockType::split(block1, kSplit1); | ||
| ASSERT_TRUE(result.has_value()); | ||
| BlockType *block2 = *result; | ||
|
|
||
| result = BlockType::split(block2, kSplit2); | ||
| ASSERT_TRUE(result.has_value()); | ||
| BlockType *block3 = *result; | ||
|
|
||
| result = BlockType::split(block3, kSplit3); | ||
| ASSERT_TRUE(result.has_value()); | ||
|
|
||
| // Corrupt a Block header. | ||
| // This must not touch memory outside the original region, or the test may | ||
| // (correctly) abort when run with address sanitizer. | ||
| // To remain as agostic to the internals of `Block` as possible, the test | ||
| // copies a smaller block's header to a larger block. | ||
| EXPECT_TRUE(block1->is_valid()); | ||
| EXPECT_TRUE(block2->is_valid()); | ||
| EXPECT_TRUE(block3->is_valid()); | ||
| auto *src = reinterpret_cast<byte *>(block1); | ||
| auto *dst = reinterpret_cast<byte *>(block2); | ||
| LIBC_NAMESPACE::memcpy(dst, src, sizeof(BlockType)); | ||
| EXPECT_FALSE(block1->is_valid()); | ||
| EXPECT_FALSE(block2->is_valid()); | ||
| EXPECT_FALSE(block3->is_valid()); | ||
| } | ||
|
|
||
| TEST_FOR_EACH_BLOCK_TYPE(CanGetBlockFromUsableSpace) { | ||
| constexpr size_t kN = 1024; | ||
|
|
||
| array<byte, kN> bytes{}; | ||
| auto result = BlockType::init(bytes); | ||
| ASSERT_TRUE(result.has_value()); | ||
| BlockType *block1 = *result; | ||
|
|
||
| void *ptr = block1->usable_space(); | ||
| BlockType *block2 = BlockType::from_usable_space(ptr); | ||
| EXPECT_EQ(block1, block2); | ||
| } | ||
|
|
||
| TEST_FOR_EACH_BLOCK_TYPE(CanGetConstBlockFromUsableSpace) { | ||
| constexpr size_t kN = 1024; | ||
|
|
||
| array<byte, kN> bytes{}; | ||
| auto result = BlockType::init(bytes); | ||
| ASSERT_TRUE(result.has_value()); | ||
| const BlockType *block1 = *result; | ||
|
|
||
| const void *ptr = block1->usable_space(); | ||
| const BlockType *block2 = BlockType::from_usable_space(ptr); | ||
| EXPECT_EQ(block1, block2); | ||
| } |