-
Notifications
You must be signed in to change notification settings - Fork 14.3k
[libc] Add aligned_alloc #96586
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[libc] Add aligned_alloc #96586
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -16,6 +16,7 @@ | |
#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 <stdint.h> | ||
|
||
|
@@ -261,6 +262,63 @@ class Block { | |
|
||
constexpr Block(size_t prev_outer_size, size_t outer_size); | ||
|
||
bool is_usable_space_aligned(size_t alignment) const { | ||
return reinterpret_cast<uintptr_t>(usable_space()) % alignment == 0; | ||
} | ||
|
||
size_t padding_for_alignment(size_t alignment) const { | ||
if (is_usable_space_aligned(alignment)) | ||
return 0; | ||
|
||
// We need to ensure we can always split this block into a "padding" block | ||
// and the aligned block. To do this, we need enough extra space for at | ||
// least one block. | ||
// | ||
// |block |usable_space | | ||
// |........|......................................| | ||
// ^ | ||
// Alignment requirement | ||
// | ||
// | ||
// |block |space |block |usable_space | | ||
// |........|........|........|....................| | ||
// ^ | ||
// Alignment requirement | ||
// | ||
uintptr_t start = reinterpret_cast<uintptr_t>(usable_space()); | ||
alignment = cpp::max(alignment, ALIGNMENT); | ||
return align_up(start + BLOCK_OVERHEAD, alignment) - start; | ||
} | ||
|
||
// Check that we can `allocate` a block with a given alignment and size from | ||
// this existing block. | ||
bool can_allocate(size_t alignment, size_t size) const; | ||
|
||
// This is the return type for `allocate` which can split one block into up to | ||
// three blocks. | ||
struct BlockInfo { | ||
// This is the newly aligned block. It will have the alignment requested by | ||
// a call to `allocate` and at most `size`. | ||
Block *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 a pointer to this new "padding" block. `prev` will be nullptr if no | ||
// new block was created or we were able to merge the block before the | ||
// original block with the "padding" block. | ||
Block *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`. | ||
Block *next; | ||
}; | ||
|
||
// Divide a block into up to 3 blocks according to `BlockInfo`. This should | ||
// only be called if `can_allocate` returns true. | ||
static BlockInfo allocate(Block *block, size_t alignment, size_t size); | ||
|
||
private: | ||
/// Consumes the block and returns as a span of bytes. | ||
static ByteSpan as_bytes(Block *&&block); | ||
|
@@ -357,6 +415,69 @@ void Block<OffsetType, kAlign>::free(Block *&block) { | |
merge_next(block); | ||
} | ||
|
||
template <typename OffsetType, size_t kAlign> | ||
bool Block<OffsetType, kAlign>::can_allocate(size_t alignment, | ||
size_t size) const { | ||
if (is_usable_space_aligned(alignment) && inner_size() >= size) | ||
return true; // Size and alignment constraints met. | ||
|
||
// Either the alignment isn't met or we don't have enough size. | ||
// If we don't meet alignment, we can always adjust such that we do meet the | ||
// alignment. If we meet the alignment but just don't have enough size. This | ||
// check will fail anyway. | ||
size_t adjustment = padding_for_alignment(alignment); | ||
return inner_size() >= size + adjustment; | ||
} | ||
|
||
template <typename OffsetType, size_t kAlign> | ||
typename Block<OffsetType, kAlign>::BlockInfo | ||
Block<OffsetType, kAlign>::allocate(Block *block, size_t alignment, | ||
size_t size) { | ||
LIBC_ASSERT( | ||
block->can_allocate(alignment, size) && | ||
"Calls to this function for a given alignment and size should only be " | ||
"done if `can_allocate` for these parameters returns true."); | ||
|
||
BlockInfo info{block, /*prev=*/nullptr, /*next=*/nullptr}; | ||
|
||
if (!info.block->is_usable_space_aligned(alignment)) { | ||
size_t adjustment = info.block->padding_for_alignment(alignment); | ||
size_t new_inner_size = adjustment - BLOCK_OVERHEAD; | ||
LIBC_ASSERT(new_inner_size % ALIGNMENT == 0 && | ||
"The adjustment calculation should always return a new size " | ||
"that's a multiple of ALIGNMENT"); | ||
|
||
Block *original = info.block; | ||
optional<Block *> maybe_aligned_block = | ||
Block::split(original, adjustment - BLOCK_OVERHEAD); | ||
LIBC_ASSERT(maybe_aligned_block.has_value() && | ||
"This split should always result in a new block. The check in " | ||
"`can_allocate` ensures that we have enough space here to make " | ||
"two blocks."); | ||
Comment on lines
+453
to
+456
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why don't we triple check There's an implicit requirement on this method that the I almost wonder if we should explicitly check that and return early if There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah the idea is that this function will always succeed if |
||
|
||
if (Block *prev = original->prev()) { | ||
// If there is a block before this, we can merge the current one with the | ||
// newly created one. | ||
merge_next(prev); | ||
} else { | ||
// Otherwise, this was the very first block in the chain. Now we can make | ||
// it the new first block. | ||
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; | ||
} | ||
|
||
// Now get a block for the requested size. | ||
if (optional<Block *> next = Block::split(info.block, size)) | ||
info.next = *next; | ||
|
||
return info; | ||
} | ||
|
||
template <typename OffsetType, size_t kAlign> | ||
optional<Block<OffsetType, kAlign> *> | ||
Block<OffsetType, kAlign>::split(Block *&block, size_t new_inner_size) { | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -66,6 +66,8 @@ template <size_t NUM_BUCKETS = 6> class FreeList { | |
/// A span with a size of 0. | ||
cpp::span<cpp::byte> find_chunk(size_t size) const; | ||
|
||
template <typename Cond> cpp::span<cpp::byte> find_chunk_if(Cond op) const; | ||
|
||
/// Removes a chunk from this freelist. | ||
bool remove_chunk(cpp::span<cpp::byte> chunk); | ||
|
||
|
@@ -111,6 +113,22 @@ bool FreeList<NUM_BUCKETS>::add_chunk(span<cpp::byte> chunk) { | |
return true; | ||
} | ||
|
||
template <size_t NUM_BUCKETS> | ||
template <typename Cond> | ||
span<cpp::byte> FreeList<NUM_BUCKETS>::find_chunk_if(Cond op) const { | ||
for (FreeListNode *node : chunks_) { | ||
while (node != nullptr) { | ||
span<cpp::byte> chunk(reinterpret_cast<cpp::byte *>(node), node->size); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. block.h has:
Perhaps useful to do that in libc/src/__support/freelist.h too. Or add more There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. SG. I'll do that in a separate PR. |
||
if (op(chunk)) | ||
return chunk; | ||
|
||
node = node->next; | ||
} | ||
} | ||
|
||
return {}; | ||
} | ||
|
||
template <size_t NUM_BUCKETS> | ||
span<cpp::byte> FreeList<NUM_BUCKETS>::find_chunk(size_t size) const { | ||
if (size == 0) | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
//===-- Implementation header for aligned_alloc -----------------*- 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> | ||
|
||
#ifndef LLVM_LIBC_SRC_STDLIB_ALIGNED_ALLOC_H | ||
#define LLVM_LIBC_SRC_STDLIB_ALIGNED_ALLOC_H | ||
|
||
namespace LIBC_NAMESPACE { | ||
|
||
void *aligned_alloc(size_t alignment, size_t size); | ||
|
||
} // namespace LIBC_NAMESPACE | ||
|
||
#endif // LLVM_LIBC_SRC_STDLIB_ALIGNED_ALLOC_H |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
hmm, I know I suggested
BlockInfo
, but this looks like some kind of ListNode, or header ... I'm not going to bikeshed on the naming here if you're happy w/ it, but I figured I should point that out, since I'm partially responsible.