Skip to content

Commit 33fdf85

Browse files
committed
[libc] Fix malloc Block alignment issue on riscv32
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().
1 parent a6aa936 commit 33fdf85

File tree

5 files changed

+231
-282
lines changed

5 files changed

+231
-282
lines changed

Diff for: libc/src/__support/CMakeLists.txt

+1
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(

Diff for: libc/src/__support/block.h

+100-117
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

@@ -68,8 +69,8 @@ using cpp::optional;
6869
/// The blocks store their offsets to the previous and next blocks. The latter
6970
/// is also the block's size.
7071
///
71-
/// Blocks will always be aligned to a `ALIGNMENT` boundary. Block sizes will
72-
/// always be rounded up to a multiple of `ALIGNMENT`.
72+
/// All blocks have their usable space aligned to some multiple of max_align_t.
73+
/// This also implies that block outer sizes are aligned to max_align_t.
7374
///
7475
/// As an example, the diagram below represents two contiguous `Block`s. The
7576
/// indices indicate byte offsets:
@@ -129,8 +130,9 @@ class Block {
129130
Block(const Block &other) = delete;
130131
Block &operator=(const Block &other) = delete;
131132

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

136138
/// @returns A pointer to a `Block`, given a pointer to the start of the
@@ -186,6 +188,9 @@ class Block {
186188
}
187189

188190
/// @returns A pointer to the usable space inside this block.
191+
///
192+
/// Unless specifically requested otherwise, this will be aligned to
193+
/// max_align_t.
189194
LIBC_INLINE cpp::byte *usable_space() {
190195
return reinterpret_cast<cpp::byte *>(this) + BLOCK_OVERHEAD;
191196
}
@@ -201,11 +206,12 @@ class Block {
201206
/// Attempts to split this block.
202207
///
203208
/// 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);
209+
/// `new_inner_size`. The remaining space will be returned as a new block,
210+
/// with usable space aligned to `usable_space_alignment`. Note that the prev_
211+
/// field of the next block counts as part of the inner size of the block.
212+
/// `usable_space_alignment` must be a multiple of max_align_t.
213+
optional<Block *> split(size_t new_inner_size,
214+
size_t usable_space_alignment = alignof(max_align_t));
209215

210216
/// Merges this block with the one that comes after it.
211217
bool merge_next();
@@ -248,46 +254,48 @@ class Block {
248254
/// nullptr.
249255
LIBC_INLINE void mark_last() { next_ |= LAST_MASK; }
250256

251-
LIBC_INLINE constexpr Block(size_t outer_size) : next_(outer_size) {
257+
LIBC_INLINE Block(size_t outer_size) : next_(outer_size) {
252258
LIBC_ASSERT(outer_size % ALIGNMENT == 0 && "block sizes must be aligned");
259+
LIBC_ASSERT(is_usable_space_aligned(alignof(max_align_t)) &&
260+
"usable space must be aligned to a multiple of max_align_t");
253261
}
254262

255263
LIBC_INLINE bool is_usable_space_aligned(size_t alignment) const {
256264
return reinterpret_cast<uintptr_t>(usable_space()) % alignment == 0;
257265
}
258266

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))
263-
return 0;
264-
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
273-
//
267+
// Returns the minimum inner size necessary for a block of that size to
268+
// always be able to allocate at the given size and alignment.
269+
//
270+
// Returns 0 if there is no such size.
271+
LIBC_INLINE static size_t min_size_for_allocation(size_t alignment,
272+
size_t size) {
273+
LIBC_ASSERT(alignment >= alignof(max_align_t) &&
274+
alignment % alignof(max_align_t) == 0 &&
275+
"alignment must be multiple of max_align_t");
276+
277+
if (alignment == alignof(max_align_t))
278+
return size;
279+
280+
// We must create a padding block to align the usable space of the next
281+
// block. The next block's usable space can be found by advancing by
282+
// sizeof(Block) then aligning up. The amount advanced is the amount of
283+
// additional inner size required.
274284
//
275-
// |block |space |block |usable_space |
276-
// |........|........|........|....................|
277-
// ^
278-
// Alignment requirement
279-
//
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_);
285+
// The minimum advancment is sizeof(Block), since the resulting position may
286+
// happen to be aligned. What is the maximum? Advancing by sizeof(Block)
287+
// leaves the result still aligned to alignof(Block). So the most additional
288+
// advancement required would be if the point is exactly alignof(Block) past
289+
// alignment. The remaining size to the next alignment would then be
290+
// alignment - alignof(Block). So the total maximum advancement required is
291+
// sizeof(Block) + alignment - alignof(Block).
292+
if (add_overflow(size, sizeof(Block), size))
293+
return 0;
294+
if (add_overflow(size, alignment - alignof(Block), size))
295+
return 0;
296+
return size;
285297
}
286298

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-
291299
// This is the return type for `allocate` which can split one block into up to
292300
// three blocks.
293301
struct BlockInfo {
@@ -309,21 +317,30 @@ class Block {
309317
Block *next;
310318
};
311319

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

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,46 @@ 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())
371371
return {};
372372

373-
region = result.value();
374-
// Two blocks are allocated: a free block and a sentinel last block.
375-
if (region.size() < 2 * BLOCK_OVERHEAD)
376-
return {};
373+
uintptr_t start = reinterpret_cast<uintptr_t>(region.data());
374+
uintptr_t end = start + region.size();
377375

378-
if (cpp::numeric_limits<size_t>::max() < region.size())
376+
uintptr_t block_start = next_possible_block_start(start);
377+
uintptr_t last_start = prev_possible_block_start(end);
378+
if (block_start + sizeof(Block) > last_start)
379379
return {};
380380

381-
Block *block = as_block(region.first(region.size() - BLOCK_OVERHEAD));
382-
Block *last = as_block(region.last(BLOCK_OVERHEAD));
381+
auto *last_start_ptr = reinterpret_cast<cpp::byte *>(last_start);
382+
Block *block =
383+
as_block({reinterpret_cast<cpp::byte *>(block_start), last_start_ptr});
384+
Block *last = as_block({last_start_ptr, sizeof(Block)});
383385
block->mark_free();
384386
last->mark_last();
385387
return block;
386388
}
387389

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-
404390
LIBC_INLINE
405391
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.");
392+
LIBC_ASSERT(alignment % alignof(max_align_t) == 0 &&
393+
"alignment must be a multiple of max_align_t");
410394

411395
BlockInfo info{block, /*prev=*/nullptr, /*next=*/nullptr};
412396

413397
if (!info.block->is_usable_space_aligned(alignment)) {
414398
Block *original = info.block;
415-
optional<Block *> maybe_aligned_block =
416-
original->split(info.block->padding_for_alignment(alignment));
399+
// The padding block has no minimum size requirement.
400+
optional<Block *> maybe_aligned_block = original->split(0, alignment);
417401
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.");
402+
"it should always be possible to split for alignment");
421403

422404
if (Block *prev = original->prev_free()) {
423405
// If there is a free block before this, we can merge the current one with
@@ -441,33 +423,34 @@ Block::BlockInfo Block::allocate(Block *block, size_t alignment, size_t size) {
441423
}
442424

443425
LIBC_INLINE
444-
optional<Block *> Block::split(size_t new_inner_size) {
426+
optional<Block *> Block::split(size_t new_inner_size,
427+
size_t usable_space_alignment) {
428+
LIBC_ASSERT(usable_space_alignment % alignof(max_align_t) == 0 &&
429+
"alignment must be a multiple of max_align_t");
430+
445431
if (used())
446432
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 {};
457433

458-
if (old_inner_size - new_inner_size < BLOCK_OVERHEAD)
459-
return {};
434+
size_t old_outer_size = outer_size();
460435

461-
return split_impl(new_inner_size);
462-
}
436+
// Compute the minimum outer size that produces a block of at least
437+
// `new_inner_size`.
438+
size_t min_outer_size = outer_size(cpp::max(new_inner_size, sizeof(prev_)));
463439

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);
440+
uintptr_t start = reinterpret_cast<uintptr_t>(this);
441+
uintptr_t next_block_start =
442+
next_possible_block_start(start + min_outer_size, usable_space_alignment);
443+
size_t new_outer_size = next_block_start - start;
444+
LIBC_ASSERT(new_outer_size % alignof(max_align_t) == 0 &&
445+
"new size must be aligned to max_align_t");
446+
447+
if (old_outer_size < new_outer_size ||
448+
old_outer_size - new_outer_size < sizeof(Block))
449+
return {};
450+
451+
ByteSpan new_region = region().subspan(new_outer_size);
469452
next_ &= ~SIZE_MASK;
470-
next_ |= outer_size1;
453+
next_ |= new_outer_size;
471454

472455
Block *new_block = as_block(new_region);
473456
mark_free(); // Free status for this block is now stored in new_block.

Diff for: libc/src/__support/freelist_heap.h

+6-17
Original file line numberDiff line numberDiff line change
@@ -89,28 +89,14 @@ LIBC_INLINE void *FreeListHeap::allocate_impl(size_t alignment, size_t size) {
8989
if (!is_initialized)
9090
init();
9191

92-
size_t request_size = size;
93-
94-
// TODO: usable_space should always be aligned to max_align_t.
95-
if (alignment > alignof(max_align_t) ||
96-
(Block::BLOCK_OVERHEAD % alignof(max_align_t) != 0)) {
97-
// TODO: This bound isn't precisely calculated yet. It assumes one extra
98-
// Block::ALIGNMENT to accomodate the possibility for padding block
99-
// overhead. (alignment - 1) ensures that there is an aligned point
100-
// somewhere in usable_space, but this isn't tight either, since
101-
// usable_space is also already somewhat aligned.
102-
if (add_overflow(size, (alignment - 1) + Block::ALIGNMENT, request_size))
103-
return nullptr;
104-
}
92+
size_t request_size = Block::min_size_for_allocation(alignment, size);
93+
if (!request_size)
94+
return nullptr;
10595

10696
Block *block = free_store.remove_best_fit(request_size);
10797
if (!block)
10898
return nullptr;
10999

110-
LIBC_ASSERT(block->can_allocate(alignment, size) &&
111-
"block should always be large enough to allocate at the correct "
112-
"alignment");
113-
114100
auto block_info = Block::allocate(block, alignment, size);
115101
if (block_info.next)
116102
free_store.insert(block_info.next);
@@ -135,6 +121,9 @@ LIBC_INLINE void *FreeListHeap::aligned_allocate(size_t alignment,
135121
if (size % alignment != 0)
136122
return nullptr;
137123

124+
// The minimum alignment supported by Block is max_align_t.
125+
alignment = cpp::max(alignment, alignof(max_align_t));
126+
138127
return allocate_impl(alignment, size);
139128
}
140129

0 commit comments

Comments
 (0)