#include <stddef.h>
#include "src/__support/CPP/array.h"
#include "src/__support/CPP/bit.h"
#include "src/__support/CPP/span.h"
#include "src/__support/block.h"
#include "src/string/memcpy.h"
#include "test/UnitTest/Test.h"
using LIBC_NAMESPACE::Block;
using LIBC_NAMESPACE::cpp::array;
using LIBC_NAMESPACE::cpp::bit_ceil;
using LIBC_NAMESPACE::cpp::byte;
using LIBC_NAMESPACE::cpp::span;
TEST(LlvmLibcBlockTest, CanCreateSingleAlignedBlock) {
constexpr size_t kN = 1024;
alignas(max_align_t) array<byte, kN> bytes;
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(alignof(max_align_t)));
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());
}
TEST(LlvmLibcBlockTest, CanCreateUnalignedSingleBlock) {
constexpr size_t kN = 1024;
alignas(max_align_t) array<byte, kN> bytes;
span<byte> aligned(bytes);
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(alignof(max_align_t)));
Block *last = block->next();
ASSERT_NE(last, static_cast<Block *>(nullptr));
EXPECT_EQ(reinterpret_cast<uintptr_t>(last) % alignof(Block), size_t{0});
}
TEST(LlvmLibcBlockTest, CannotCreateTooSmallBlock) {
array<byte, 2> bytes;
auto result = Block::init(bytes);
EXPECT_FALSE(result.has_value());
}
TEST(LlvmLibcBlockTest, CanSplitBlock) {
constexpr size_t kN = 1024;
const size_t kSplitN = Block::inner_size(512);
array<byte, kN> bytes;
auto result = Block::init(bytes);
ASSERT_TRUE(result.has_value());
auto *block1 = *result;
size_t orig_size = block1->outer_size();
result = block1->split(kSplitN);
ASSERT_TRUE(result.has_value());
auto *block2 = *result;
EXPECT_EQ(block1->inner_size(), kSplitN);
EXPECT_EQ(block1->outer_size(),
kSplitN - Block::PREV_FIELD_SIZE + sizeof(Block));
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(alignof(max_align_t)));
EXPECT_EQ(block1->next(), block2);
EXPECT_EQ(block2->prev_free(), block1);
}
TEST(LlvmLibcBlockTest, CanSplitBlockUnaligned) {
constexpr size_t kN = 1024;
array<byte, kN> bytes;
auto result = Block::init(bytes);
ASSERT_TRUE(result.has_value());
Block *block1 = *result;
size_t orig_size = block1->outer_size();
constexpr size_t kSplitN = 513;
result = block1->split(kSplitN);
ASSERT_TRUE(result.has_value());
Block *block2 = *result;
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(alignof(max_align_t)));
EXPECT_EQ(block1->next(), block2);
EXPECT_EQ(block2->prev_free(), block1);
}
TEST(LlvmLibcBlockTest, CanSplitMidBlock) {
constexpr size_t kN = 1024;
constexpr size_t kSplit1 = 512;
constexpr size_t kSplit2 = 256;
array<byte, kN> bytes;
auto result = Block::init(bytes);
ASSERT_TRUE(result.has_value());
Block *block1 = *result;
result = block1->split(kSplit1);
ASSERT_TRUE(result.has_value());
Block *block2 = *result;
result = block1->split(kSplit2);
ASSERT_TRUE(result.has_value());
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);
}
TEST(LlvmLibcBlockTest, CannotSplitTooSmallBlock) {
constexpr size_t kN = 64;
array<byte, kN> bytes;
auto result = Block::init(bytes);
ASSERT_TRUE(result.has_value());
Block *block = *result;
result = block->split(block->inner_size() + 1);
ASSERT_FALSE(result.has_value());
}
TEST(LlvmLibcBlockTest, CannotSplitBlockWithoutHeaderSpace) {
constexpr size_t kN = 1024;
array<byte, kN> bytes;
auto result = Block::init(bytes);
ASSERT_TRUE(result.has_value());
Block *block = *result;
result = block->split(block->inner_size() - sizeof(Block) + 1);
ASSERT_FALSE(result.has_value());
}
TEST(LlvmLibcBlockTest, CannotMakeBlockLargerInSplit) {
constexpr size_t kN = 1024;
array<byte, kN> bytes;
auto result = Block::init(bytes);
ASSERT_TRUE(result.has_value());
Block *block = *result;
result = block->split(block->inner_size() + 1);
ASSERT_FALSE(result.has_value());
}
TEST(LlvmLibcBlockTest, CanMakeMinimalSizeFirstBlock) {
constexpr size_t kN = 1024;
array<byte, kN> bytes;
auto result = Block::init(bytes);
ASSERT_TRUE(result.has_value());
Block *block = *result;
result = block->split(0);
ASSERT_TRUE(result.has_value());
EXPECT_LE(block->outer_size(), sizeof(Block) + alignof(max_align_t));
}
TEST(LlvmLibcBlockTest, CanMakeMinimalSizeSecondBlock) {
constexpr size_t kN = 1024;
array<byte, kN> bytes;
auto result = Block::init(bytes);
ASSERT_TRUE(result.has_value());
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);
ASSERT_TRUE(result.has_value());
EXPECT_LE((*result)->outer_size(), sizeof(Block) + alignof(max_align_t));
}
TEST(LlvmLibcBlockTest, CanMarkBlockUsed) {
constexpr size_t kN = 1024;
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->mark_used();
EXPECT_TRUE(block->used());
EXPECT_EQ(block->outer_size(), orig_size);
block->mark_free();
EXPECT_FALSE(block->used());
}
TEST(LlvmLibcBlockTest, CannotSplitUsedBlock) {
constexpr size_t kN = 1024;
constexpr size_t kSplitN = 512;
array<byte, kN> bytes;
auto result = Block::init(bytes);
ASSERT_TRUE(result.has_value());
Block *block = *result;
block->mark_used();
result = block->split(kSplitN);
ASSERT_FALSE(result.has_value());
}
TEST(LlvmLibcBlockTest, CanMergeWithNextBlock) {
constexpr size_t kN = 1024;
constexpr size_t kSplit1 = 512;
constexpr size_t kSplit2 = 256;
array<byte, kN> bytes;
auto result = Block::init(bytes);
ASSERT_TRUE(result.has_value());
Block *block1 = *result;
size_t total_size = block1->outer_size();
result = block1->split(kSplit1);
ASSERT_TRUE(result.has_value());
result = block1->split(kSplit2);
size_t block1_size = block1->outer_size();
ASSERT_TRUE(result.has_value());
Block *block3 = *result;
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());
}
TEST(LlvmLibcBlockTest, CannotMergeWithFirstOrLastBlock) {
constexpr size_t kN = 1024;
constexpr size_t kSplitN = 512;
array<byte, kN> bytes;
auto result = Block::init(bytes);
ASSERT_TRUE(result.has_value());
Block *block1 = *result;
result = block1->split(kSplitN);
ASSERT_TRUE(result.has_value());
Block *block2 = *result;
EXPECT_FALSE(block2->merge_next());
}
TEST(LlvmLibcBlockTest, CannotMergeUsedBlock) {
constexpr size_t kN = 1024;
constexpr size_t kSplitN = 512;
array<byte, kN> bytes;
auto result = Block::init(bytes);
ASSERT_TRUE(result.has_value());
Block *block = *result;
result = block->split(kSplitN);
ASSERT_TRUE(result.has_value());
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;
void *ptr = block1->usable_space();
Block *block2 = Block::from_usable_space(ptr);
EXPECT_EQ(block1, block2);
}
TEST(LlvmLibcBlockTest, CanGetConstBlockFromUsableSpace) {
constexpr size_t kN = 1024;
array<byte, kN> bytes{};
auto result = Block::init(bytes);
ASSERT_TRUE(result.has_value());
const Block *block1 = *result;
const void *ptr = block1->usable_space();
const Block *block2 = Block::from_usable_space(ptr);
EXPECT_EQ(block1, block2);
}
TEST(LlvmLibcBlockTest, Allocate) {
constexpr size_t kN = 1024;
for (size_t i = 0; i < kN; ++i) {
array<byte, kN> bytes;
auto result = Block::init(bytes);
ASSERT_TRUE(result.has_value());
Block *block = *result;
if (i > block->inner_size())
continue;
auto info = Block::allocate(block, alignof(max_align_t), i);
EXPECT_NE(info.block, static_cast<Block *>(nullptr));
}
for (size_t i = 1; i < kN / alignof(max_align_t); ++i) {
array<byte, kN> bytes;
auto result = Block::init(bytes);
ASSERT_TRUE(result.has_value());
Block *block = *result;
size_t alignment = i * alignof(max_align_t);
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));
}
}
TEST(LlvmLibcBlockTest, AllocateAlreadyAligned) {
constexpr size_t kN = 1024;
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();
constexpr size_t SIZE = Block::PREV_FIELD_SIZE + 1;
auto [aligned_block, prev, next] =
Block::allocate(block, alignof(max_align_t), SIZE);
EXPECT_EQ(prev, static_cast<Block *>(nullptr));
EXPECT_NE(aligned_block, static_cast<Block *>(nullptr));
EXPECT_TRUE(aligned_block->is_usable_space_aligned(alignof(max_align_t)));
EXPECT_GE(aligned_block->inner_size(), SIZE);
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);
}
TEST(LlvmLibcBlockTest, AllocateNeedsAlignment) {
constexpr size_t kN = 1024;
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();
size_t alignment = alignof(max_align_t);
while (block->is_usable_space_aligned(alignment))
alignment += alignof(max_align_t);
auto [aligned_block, prev, next] = Block::allocate(block, alignment, 10);
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(next, static_cast<Block *>(nullptr));
EXPECT_TRUE(aligned_block->is_usable_space_aligned(alignment));
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);
}
TEST(LlvmLibcBlockTest, PreviousBlockMergedIfNotFirst) {
constexpr size_t kN = 1024;
array<byte, kN> bytes;
auto result = Block::init(bytes);
ASSERT_TRUE(result.has_value());
Block *block = *result;
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();
size_t alignment = alignof(max_align_t);
while (newblock->is_usable_space_aligned(alignment))
alignment += alignof(max_align_t);
auto [aligned_block, prev, next] = Block::allocate(newblock, alignment, 1);
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);
}
TEST(LlvmLibcBlockTest, CanRemergeBlockAllocations) {
constexpr size_t kN = 1024;
array<byte, kN> bytes;
auto result = Block::init(bytes);
ASSERT_TRUE(result.has_value());
Block *block = *result;
Block *orig_block = block;
size_t orig_size = orig_block->outer_size();
Block *last = block->next();
ASSERT_EQ(block->prev_free(), static_cast<Block *>(nullptr));
size_t alignment = alignof(max_align_t);
while (block->is_usable_space_aligned(alignment))
alignment += alignof(max_align_t);
auto [aligned_block, prev, next] = Block::allocate(block, alignment, 1);
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);
EXPECT_TRUE(prev->merge_next());
EXPECT_EQ(prev->next(), next);
EXPECT_TRUE(prev->merge_next());
EXPECT_EQ(prev->next(), last);
EXPECT_EQ(prev, orig_block);
EXPECT_EQ(prev->outer_size(), orig_size);
}