[libc-commits] [libc] [libc] Add aligned_alloc (PR #96586)
Paul Kirth via libc-commits
libc-commits at lists.llvm.org
Wed Jun 26 08:40:35 PDT 2024
================
@@ -567,3 +569,201 @@ TEST_FOR_EACH_BLOCK_TYPE(CanGetConstBlockFromUsableSpace) {
const BlockType *block2 = BlockType::from_usable_space(ptr);
EXPECT_EQ(block1, block2);
}
+
+TEST_FOR_EACH_BLOCK_TYPE(CanAllocate) {
+ constexpr size_t kN = 1024;
+
+ // Ensure we can allocate everything up to the block size within this block.
+ for (size_t i = 0; i < kN - BlockType::BLOCK_OVERHEAD; ++i) {
+ alignas(BlockType::ALIGNMENT) array<byte, kN> bytes{};
+ auto result = BlockType::init(bytes);
+ ASSERT_TRUE(result.has_value());
+ BlockType *block = *result;
+
+ constexpr size_t kAlign = 1; // Effectively ignores alignmenr.
+ EXPECT_TRUE(block->can_allocate(kAlign, i));
+
+ // For each can_allocate, we should be able to do a successful call to
+ // allocate.
+ BlockType *prev = nullptr;
+ BlockType *next = nullptr;
+ BlockType::allocate(block, kAlign, i, prev, next);
+ EXPECT_NE(block, static_cast<BlockType *>(nullptr));
+ }
+
+ alignas(BlockType::ALIGNMENT) array<byte, kN> bytes{};
+ auto result = BlockType::init(bytes);
+ ASSERT_TRUE(result.has_value());
+ BlockType *block = *result;
+
+ // Given a block of size kN (assuming it's also a power of two), we should be
+ // able to allocate a block within it that's aligned to half its size. This is
+ // because regardless of where the buffer is located, we can always find a
+ // starting location within it that meets this alignment.
+ EXPECT_TRUE(block->can_allocate(kN / 2, 1));
+}
+
+TEST_FOR_EACH_BLOCK_TYPE(AllocateAlreadyAligned) {
+ 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;
+
+ // This should result in no new blocks.
+ constexpr size_t kAlignment = BlockType::ALIGNMENT;
+ constexpr size_t kExpectedSize = BlockType::ALIGNMENT;
+ EXPECT_TRUE(block->can_allocate(kAlignment, kExpectedSize));
+
+ BlockType *prev = nullptr;
+ BlockType *next = nullptr;
+ BlockType::allocate(block, BlockType::ALIGNMENT, kExpectedSize, prev, next);
+
+ // Since this is already aligned, there should be no previous block.
+ EXPECT_EQ(prev, static_cast<BlockType *>(nullptr));
+
+ // Ensure we the block is aligned and the size we expect.
+ EXPECT_NE(block, static_cast<BlockType *>(nullptr));
+ EXPECT_TRUE(block->usable_space_is_aligned(BlockType::ALIGNMENT));
+ EXPECT_EQ(block->inner_size(), kExpectedSize);
+
+ // Check the next block.
+ EXPECT_NE(next, static_cast<BlockType *>(nullptr));
+ EXPECT_EQ(block->next(), next);
+ EXPECT_EQ(next->next(), static_cast<BlockType *>(nullptr));
+ EXPECT_EQ(reinterpret_cast<byte *>(next) + next->outer_size(), &*bytes.end());
----------------
ilovepi wrote:
But aren't you dereferencing that pointer? I'm petty sure its always UB to deref `end()`, even if it points into a real allocation, since if they don't share the same provenance. Pointer provenance rules are pretty complicated, and C and C++ don't agree on the precise semantics. Newer standards have closed that gap to a large extent, but I don't think they precisely match. Peter Sewel has had a lot to say on the matter (https://www.cl.cam.ac.uk/~pes20/cerberus/cerberus-popl2019.pdf, https://www.cl.cam.ac.uk/~pes20/cerberus/clarifying-provenance-v4.html).
If you want to avoid UB altogether, maybe get the address of the last element w/ `back()`, then create an equivalent pointer to `end()` w/ pointer arithmetic. It may be even better to use `uintptr_t`, and document that you're checking some kind of layout invariant that your data structure is expected to maintain.
https://github.com/llvm/llvm-project/pull/96586
More information about the libc-commits
mailing list