Skip to content

Commit 60de7dc

Browse files
authored
[libc] Fix malloc Block alignment issue on riscv32 (llvm#117815)
Aligning blocks to max_align_t is neither necessary nor sufficient to ensure that the usable_space() is so aligned. Instead, we make this an invariant of Block and maintain it in init() and split(). This allows targets like riscv32 with small pointers and large max_align_t to maintain the property that all available blocks are aligned for malloc(). This change adjusts the tests to match and also updates them closer to llvm-libc style.
1 parent acba822 commit 60de7dc

File tree

5 files changed

+255
-294
lines changed

5 files changed

+255
-294
lines changed

libc/src/__support/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ add_header_library(
1212
libc.src.__support.CPP.optional
1313
libc.src.__support.CPP.span
1414
libc.src.__support.CPP.type_traits
15+
libc.src.__support.math_extras
1516
)
1617

1718
add_object_library(

libc/src/__support/block.h

Lines changed: 123 additions & 130 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
#include "src/__support/CPP/type_traits.h"
1919
#include "src/__support/libc_assert.h"
2020
#include "src/__support/macros/config.h"
21+
#include "src/__support/math_extras.h"
2122

2223
#include <stdint.h>
2324

@@ -40,24 +41,10 @@ LIBC_INLINE constexpr size_t align_down(size_t value, size_t alignment) {
4041
return (value / alignment) * alignment;
4142
}
4243

43-
/// Returns the value rounded down to the nearest multiple of alignment.
44-
template <typename T>
45-
LIBC_INLINE constexpr T *align_down(T *value, size_t alignment) {
46-
return reinterpret_cast<T *>(
47-
align_down(reinterpret_cast<size_t>(value), alignment));
48-
}
49-
50-
/// Returns the value rounded up to the nearest multiple of alignment.
44+
/// Returns the value rounded up to the nearest multiple of alignment. May wrap
45+
/// around.
5146
LIBC_INLINE constexpr size_t align_up(size_t value, size_t alignment) {
52-
__builtin_add_overflow(value, alignment - 1, &value);
53-
return align_down(value, alignment);
54-
}
55-
56-
/// Returns the value rounded up to the nearest multiple of alignment.
57-
template <typename T>
58-
LIBC_INLINE constexpr T *align_up(T *value, size_t alignment) {
59-
return reinterpret_cast<T *>(
60-
align_up(reinterpret_cast<size_t>(value), alignment));
47+
return align_down(value + alignment - 1, alignment);
6148
}
6249

6350
using ByteSpan = cpp::span<LIBC_NAMESPACE::cpp::byte>;
@@ -68,8 +55,8 @@ using cpp::optional;
6855
/// The blocks store their offsets to the previous and next blocks. The latter
6956
/// is also the block's size.
7057
///
71-
/// Blocks will always be aligned to a `ALIGNMENT` boundary. Block sizes will
72-
/// always be rounded up to a multiple of `ALIGNMENT`.
58+
/// All blocks have their usable space aligned to some multiple of max_align_t.
59+
/// This also implies that block outer sizes are aligned to max_align_t.
7360
///
7461
/// As an example, the diagram below represents two contiguous `Block`s. The
7562
/// indices indicate byte offsets:
@@ -129,8 +116,9 @@ class Block {
129116
Block(const Block &other) = delete;
130117
Block &operator=(const Block &other) = delete;
131118

132-
/// Creates the first block for a given memory region, followed by a sentinel
133-
/// last block. Returns the first block.
119+
/// Initializes a given memory region into a first block and a sentinel last
120+
/// block. Returns the first block, which has its usable space aligned to
121+
/// max_align_t.
134122
static optional<Block *> init(ByteSpan region);
135123

136124
/// @returns A pointer to a `Block`, given a pointer to the start of the
@@ -186,11 +174,19 @@ class Block {
186174
}
187175

188176
/// @returns A pointer to the usable space inside this block.
177+
///
178+
/// Aligned to some multiple of max_align_t.
189179
LIBC_INLINE cpp::byte *usable_space() {
190-
return reinterpret_cast<cpp::byte *>(this) + BLOCK_OVERHEAD;
180+
auto *s = reinterpret_cast<cpp::byte *>(this) + BLOCK_OVERHEAD;
181+
LIBC_ASSERT(reinterpret_cast<uintptr_t>(s) % alignof(max_align_t) == 0 &&
182+
"usable space must be aligned to a multiple of max_align_t");
183+
return s;
191184
}
192185
LIBC_INLINE const cpp::byte *usable_space() const {
193-
return reinterpret_cast<const cpp::byte *>(this) + BLOCK_OVERHEAD;
186+
const auto *s = reinterpret_cast<const cpp::byte *>(this) + BLOCK_OVERHEAD;
187+
LIBC_ASSERT(reinterpret_cast<uintptr_t>(s) % alignof(max_align_t) == 0 &&
188+
"usable space must be aligned to a multiple of max_align_t");
189+
return s;
194190
}
195191

196192
// @returns The region of memory the block manages, including the header.
@@ -201,11 +197,12 @@ class Block {
201197
/// Attempts to split this block.
202198
///
203199
/// If successful, the block will have an inner size of at least
204-
/// `new_inner_size`, rounded to ensure that the split point is on an
205-
/// ALIGNMENT boundary. The remaining space will be returned as a new block.
206-
/// Note that the prev_ field of the next block counts as part of the inner
207-
/// size of the returnd block.
208-
optional<Block *> split(size_t new_inner_size);
200+
/// `new_inner_size`. The remaining space will be returned as a new block,
201+
/// with usable space aligned to `usable_space_alignment`. Note that the prev_
202+
/// field of the next block counts as part of the inner size of the block.
203+
/// `usable_space_alignment` must be a multiple of max_align_t.
204+
optional<Block *> split(size_t new_inner_size,
205+
size_t usable_space_alignment = alignof(max_align_t));
209206

210207
/// Merges this block with the one that comes after it.
211208
bool merge_next();
@@ -248,46 +245,56 @@ class Block {
248245
/// nullptr.
249246
LIBC_INLINE void mark_last() { next_ |= LAST_MASK; }
250247

251-
LIBC_INLINE constexpr Block(size_t outer_size) : next_(outer_size) {
248+
LIBC_INLINE Block(size_t outer_size) : next_(outer_size) {
252249
LIBC_ASSERT(outer_size % ALIGNMENT == 0 && "block sizes must be aligned");
250+
LIBC_ASSERT(is_usable_space_aligned(alignof(max_align_t)) &&
251+
"usable space must be aligned to a multiple of max_align_t");
253252
}
254253

255254
LIBC_INLINE bool is_usable_space_aligned(size_t alignment) const {
256255
return reinterpret_cast<uintptr_t>(usable_space()) % alignment == 0;
257256
}
258257

259-
/// @returns The new inner size of this block that would give the usable
260-
/// space of the next block the given alignment.
261-
LIBC_INLINE size_t padding_for_alignment(size_t alignment) const {
262-
if (is_usable_space_aligned(alignment))
258+
// Returns the minimum inner size necessary for a block of that size to
259+
// always be able to allocate at the given size and alignment.
260+
//
261+
// Returns 0 if there is no such size.
262+
LIBC_INLINE static size_t min_size_for_allocation(size_t alignment,
263+
size_t size) {
264+
LIBC_ASSERT(alignment >= alignof(max_align_t) &&
265+
alignment % alignof(max_align_t) == 0 &&
266+
"alignment must be multiple of max_align_t");
267+
268+
if (alignment == alignof(max_align_t))
269+
return size;
270+
271+
// We must create a new block inside this one (splitting). This requires a
272+
// block header in addition to the requested size.
273+
if (add_overflow(size, sizeof(Block), size))
263274
return 0;
264275

265-
// We need to ensure we can always split this block into a "padding" block
266-
// and the aligned block. To do this, we need enough extra space for at
267-
// least one block.
268-
//
269-
// |block |usable_space |
270-
// |........|......................................|
271-
// ^
272-
// Alignment requirement
276+
// Beyond that, padding space may need to remain in this block to ensure
277+
// that the usable space of the next block is aligned.
273278
//
279+
// Consider a position P of some lesser alignment, L, with maximal distance
280+
// to the next position of some greater alignment, G, where G is a multiple
281+
// of L. P must be one L unit past a G-aligned point. If it were one L-unit
282+
// earlier, its distance would be zero. If it were one L-unit later, its
283+
// distance would not be maximal. If it were not some integral number of L
284+
// units away, it would not be L-aligned.
274285
//
275-
// |block |space |block |usable_space |
276-
// |........|........|........|....................|
277-
// ^
278-
// Alignment requirement
286+
// So the maximum distance would be G - L. As a special case, if L is 1
287+
// (unaligned), the max distance is G - 1.
279288
//
280-
alignment = cpp::max(alignment, ALIGNMENT);
281-
uintptr_t start = reinterpret_cast<uintptr_t>(usable_space());
282-
uintptr_t next_usable_space = align_up(start + BLOCK_OVERHEAD, alignment);
283-
uintptr_t next_block = next_usable_space - BLOCK_OVERHEAD;
284-
return next_block - start + sizeof(prev_);
289+
// This block's usable space is aligned to max_align_t >= Block. With zero
290+
// padding, the next block's usable space is sizeof(Block) past it, which is
291+
// a point aligned to Block. Thus the max padding needed is alignment -
292+
// alignof(Block).
293+
if (add_overflow(size, alignment - alignof(Block), size))
294+
return 0;
295+
return size;
285296
}
286297

287-
// Check that we can `allocate` a block with a given alignment and size from
288-
// this existing block.
289-
bool can_allocate(size_t alignment, size_t size) const;
290-
291298
// This is the return type for `allocate` which can split one block into up to
292299
// three blocks.
293300
struct BlockInfo {
@@ -309,21 +316,31 @@ class Block {
309316
Block *next;
310317
};
311318

312-
// Divide a block into up to 3 blocks according to `BlockInfo`. This should
313-
// only be called if `can_allocate` returns true.
319+
// Divide a block into up to 3 blocks according to `BlockInfo`. Behavior is
320+
// undefined if allocation is not possible for the given size and alignment.
314321
static BlockInfo allocate(Block *block, size_t alignment, size_t size);
315322

323+
// These two functions may wrap around.
324+
LIBC_INLINE static uintptr_t next_possible_block_start(
325+
uintptr_t ptr, size_t usable_space_alignment = alignof(max_align_t)) {
326+
return align_up(ptr + sizeof(Block), usable_space_alignment) -
327+
sizeof(Block);
328+
}
329+
LIBC_INLINE static uintptr_t prev_possible_block_start(
330+
uintptr_t ptr, size_t usable_space_alignment = alignof(max_align_t)) {
331+
return align_down(ptr, usable_space_alignment) - sizeof(Block);
332+
}
333+
316334
private:
317335
/// Construct a block to represent a span of bytes. Overwrites only enough
318336
/// memory for the block header; the rest of the span is left alone.
319337
LIBC_INLINE static Block *as_block(ByteSpan bytes) {
338+
LIBC_ASSERT(reinterpret_cast<uintptr_t>(bytes.data()) % alignof(Block) ==
339+
0 &&
340+
"block start must be suitably aligned");
320341
return ::new (bytes.data()) Block(bytes.size());
321342
}
322343

323-
/// Like `split`, but assumes the caller has already checked to parameters to
324-
/// ensure the split will succeed.
325-
Block *split_impl(size_t new_inner_size);
326-
327344
/// Offset from this block to the previous block. 0 if this is the first
328345
/// block. This field is only alive when the previous block is free;
329346
/// otherwise, its memory is reused as part of the previous block's usable
@@ -343,81 +360,54 @@ class Block {
343360
/// previous block is free.
344361
/// * If the `last` flag is set, the block is the sentinel last block. It is
345362
/// summarily considered used and has no next block.
346-
} __attribute__((packed, aligned(cpp::max(alignof(max_align_t), size_t{4}))));
363+
};
347364

348365
inline constexpr size_t Block::BLOCK_OVERHEAD =
349366
align_up(sizeof(Block), ALIGNMENT);
350367

351-
LIBC_INLINE ByteSpan get_aligned_subspan(ByteSpan bytes, size_t alignment) {
352-
if (bytes.data() == nullptr)
353-
return ByteSpan();
354-
355-
auto unaligned_start = reinterpret_cast<uintptr_t>(bytes.data());
356-
auto aligned_start = align_up(unaligned_start, alignment);
357-
auto unaligned_end = unaligned_start + bytes.size();
358-
auto aligned_end = align_down(unaligned_end, alignment);
359-
360-
if (aligned_end <= aligned_start)
361-
return ByteSpan();
362-
363-
return bytes.subspan(aligned_start - unaligned_start,
364-
aligned_end - aligned_start);
365-
}
366-
367368
LIBC_INLINE
368369
optional<Block *> Block::init(ByteSpan region) {
369-
optional<ByteSpan> result = get_aligned_subspan(region, ALIGNMENT);
370-
if (!result)
370+
if (!region.data())
371+
return {};
372+
373+
uintptr_t start = reinterpret_cast<uintptr_t>(region.data());
374+
uintptr_t end = start + region.size();
375+
if (end < start)
376+
return {};
377+
378+
uintptr_t block_start = next_possible_block_start(start);
379+
if (block_start < start)
371380
return {};
372381

373-
region = result.value();
374-
// Two blocks are allocated: a free block and a sentinel last block.
375-
if (region.size() < 2 * BLOCK_OVERHEAD)
382+
uintptr_t last_start = prev_possible_block_start(end);
383+
if (last_start >= end)
376384
return {};
377385

378-
if (cpp::numeric_limits<size_t>::max() < region.size())
386+
if (block_start + sizeof(Block) > last_start)
379387
return {};
380388

381-
Block *block = as_block(region.first(region.size() - BLOCK_OVERHEAD));
382-
Block *last = as_block(region.last(BLOCK_OVERHEAD));
389+
auto *last_start_ptr = reinterpret_cast<cpp::byte *>(last_start);
390+
Block *block =
391+
as_block({reinterpret_cast<cpp::byte *>(block_start), last_start_ptr});
392+
Block *last = as_block({last_start_ptr, sizeof(Block)});
383393
block->mark_free();
384394
last->mark_last();
385395
return block;
386396
}
387397

388-
LIBC_INLINE
389-
bool Block::can_allocate(size_t alignment, size_t size) const {
390-
if (inner_size() < size)
391-
return false;
392-
if (is_usable_space_aligned(alignment))
393-
return true;
394-
395-
// Alignment isn't met, so a padding block is needed. Determine amount of
396-
// inner_size() consumed by the padding block.
397-
size_t padding_size = padding_for_alignment(alignment) - sizeof(prev_);
398-
399-
// Check that there is room for the allocation in the following aligned block.
400-
size_t aligned_inner_size = inner_size() - padding_size - BLOCK_OVERHEAD;
401-
return size <= aligned_inner_size;
402-
}
403-
404398
LIBC_INLINE
405399
Block::BlockInfo Block::allocate(Block *block, size_t alignment, size_t size) {
406-
LIBC_ASSERT(
407-
block->can_allocate(alignment, size) &&
408-
"Calls to this function for a given alignment and size should only be "
409-
"done if `can_allocate` for these parameters returns true.");
400+
LIBC_ASSERT(alignment % alignof(max_align_t) == 0 &&
401+
"alignment must be a multiple of max_align_t");
410402

411403
BlockInfo info{block, /*prev=*/nullptr, /*next=*/nullptr};
412404

413405
if (!info.block->is_usable_space_aligned(alignment)) {
414406
Block *original = info.block;
415-
optional<Block *> maybe_aligned_block =
416-
original->split(info.block->padding_for_alignment(alignment));
407+
// The padding block has no minimum size requirement.
408+
optional<Block *> maybe_aligned_block = original->split(0, alignment);
417409
LIBC_ASSERT(maybe_aligned_block.has_value() &&
418-
"This split should always result in a new block. The check in "
419-
"`can_allocate` ensures that we have enough space here to make "
420-
"two blocks.");
410+
"it should always be possible to split for alignment");
421411

422412
if (Block *prev = original->prev_free()) {
423413
// If there is a free block before this, we can merge the current one with
@@ -441,37 +431,40 @@ Block::BlockInfo Block::allocate(Block *block, size_t alignment, size_t size) {
441431
}
442432

443433
LIBC_INLINE
444-
optional<Block *> Block::split(size_t new_inner_size) {
434+
optional<Block *> Block::split(size_t new_inner_size,
435+
size_t usable_space_alignment) {
436+
LIBC_ASSERT(usable_space_alignment % alignof(max_align_t) == 0 &&
437+
"alignment must be a multiple of max_align_t");
445438
if (used())
446439
return {};
447-
// The prev_ field of the next block is always available, so there is a
448-
// minimum size to a block created through splitting.
449-
if (new_inner_size < sizeof(prev_))
450-
new_inner_size = sizeof(prev_);
451-
452-
size_t old_inner_size = inner_size();
453-
new_inner_size =
454-
align_up(new_inner_size - sizeof(prev_), ALIGNMENT) + sizeof(prev_);
455-
if (old_inner_size < new_inner_size)
456-
return {};
457440

458-
if (old_inner_size - new_inner_size < BLOCK_OVERHEAD)
441+
// Compute the minimum outer size that produces a block of at least
442+
// `new_inner_size`.
443+
size_t min_outer_size = outer_size(cpp::max(new_inner_size, sizeof(prev_)));
444+
445+
uintptr_t start = reinterpret_cast<uintptr_t>(this);
446+
uintptr_t next_block_start =
447+
next_possible_block_start(start + min_outer_size, usable_space_alignment);
448+
if (next_block_start < start)
459449
return {};
450+
size_t new_outer_size = next_block_start - start;
451+
LIBC_ASSERT(new_outer_size % alignof(max_align_t) == 0 &&
452+
"new size must be aligned to max_align_t");
460453

461-
return split_impl(new_inner_size);
462-
}
454+
if (outer_size() < new_outer_size ||
455+
outer_size() - new_outer_size < sizeof(Block))
456+
return {};
463457

464-
LIBC_INLINE
465-
Block *Block::split_impl(size_t new_inner_size) {
466-
size_t outer_size1 = outer_size(new_inner_size);
467-
LIBC_ASSERT(outer_size1 % ALIGNMENT == 0 && "new size must be aligned");
468-
ByteSpan new_region = region().subspan(outer_size1);
458+
ByteSpan new_region = region().subspan(new_outer_size);
469459
next_ &= ~SIZE_MASK;
470-
next_ |= outer_size1;
460+
next_ |= new_outer_size;
471461

472462
Block *new_block = as_block(new_region);
473463
mark_free(); // Free status for this block is now stored in new_block.
474464
new_block->next()->prev_ = new_region.size();
465+
466+
LIBC_ASSERT(new_block->is_usable_space_aligned(usable_space_alignment) &&
467+
"usable space must have requested alignment");
475468
return new_block;
476469
}
477470

0 commit comments

Comments
 (0)