Skip to content

Commit 4f9cd6e

Browse files
committed
[libc][malloc] Ensure a minimum block alignment of 4
Most platforms inherently have a size_t alignment of 4, but this isn't true on every platform LLVM has some degree of backend support for. Accordingly, it's simple enough to just set the min alignment of each Block's usable space to 4 and lose the static_assert.
1 parent a27bb38 commit 4f9cd6e

File tree

7 files changed

+69
-70
lines changed

7 files changed

+69
-70
lines changed

libc/fuzzing/__support/freelist_heap_fuzz.cpp

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -147,7 +147,7 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t remainder) {
147147

148148
// Perform allocation.
149149
void *ptr = nullptr;
150-
size_t alignment = alignof(max_align_t);
150+
size_t alignment = Block::MIN_ALIGN;
151151
switch (alloc_type) {
152152
case AllocType::MALLOC:
153153
ptr = heap.allocate(alloc_size);
@@ -172,7 +172,7 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t remainder) {
172172
alloc_size - alloc.size);
173173
alloc.ptr = ptr;
174174
alloc.size = alloc_size;
175-
alloc.alignment = alignof(max_align_t);
175+
alloc.alignment = Block::MIN_ALIGN;
176176
}
177177
break;
178178
}
@@ -194,8 +194,8 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t remainder) {
194194

195195
if (ptr) {
196196
// aligned_allocate should automatically apply a minimum alignment.
197-
if (alignment < alignof(max_align_t))
198-
alignment = alignof(max_align_t);
197+
if (alignment < Block::MIN_ALIGN)
198+
alignment = Block::MIN_ALIGN;
199199
// Check alignment.
200200
if (reinterpret_cast<uintptr_t>(ptr) % alignment)
201201
__builtin_trap();

libc/src/__support/block.h

Lines changed: 35 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -43,8 +43,8 @@ using cpp::optional;
4343
/// The blocks store their offsets to the previous and next blocks. The latter
4444
/// is also the block's size.
4545
///
46-
/// All blocks have their usable space aligned to some multiple of max_align_t.
47-
/// This also implies that block outer sizes are aligned to max_align_t.
46+
/// All blocks have their usable space aligned to some multiple of MIN_ALIGN.
47+
/// This also implies that block outer sizes are aligned to MIN_ALIGN.
4848
///
4949
/// As an example, the diagram below represents two contiguous `Block`s. The
5050
/// indices indicate byte offsets:
@@ -97,13 +97,17 @@ class Block {
9797
static constexpr size_t SIZE_MASK = ~(PREV_FREE_MASK | LAST_MASK);
9898

9999
public:
100+
// To ensure block sizes have two lower unused bits, ensure usable space is
101+
// always aligned to at least 4 bytes. (The distances between usable spaces,
102+
// the outer size, is then always also 4-aligned.)
103+
static constexpr size_t MIN_ALIGN = cpp::max(size_t{4}, alignof(max_align_t));
100104
// No copy or move.
101105
Block(const Block &other) = delete;
102106
Block &operator=(const Block &other) = delete;
103107

104108
/// Initializes a given memory region into a first block and a sentinel last
105109
/// block. Returns the first block, which has its usable space aligned to
106-
/// max_align_t.
110+
/// MIN_ALIGN.
107111
static optional<Block *> init(ByteSpan region);
108112

109113
/// @returns A pointer to a `Block`, given a pointer to the start of the
@@ -160,17 +164,17 @@ class Block {
160164

161165
/// @returns A pointer to the usable space inside this block.
162166
///
163-
/// Aligned to some multiple of max_align_t.
167+
/// Aligned to some multiple of MIN_ALIGN.
164168
LIBC_INLINE cpp::byte *usable_space() {
165169
auto *s = reinterpret_cast<cpp::byte *>(this) + sizeof(Block);
166-
LIBC_ASSERT(reinterpret_cast<uintptr_t>(s) % alignof(max_align_t) == 0 &&
167-
"usable space must be aligned to a multiple of max_align_t");
170+
LIBC_ASSERT(reinterpret_cast<uintptr_t>(s) % MIN_ALIGN == 0 &&
171+
"usable space must be aligned to MIN_ALIGN");
168172
return s;
169173
}
170174
LIBC_INLINE const cpp::byte *usable_space() const {
171175
const auto *s = reinterpret_cast<const cpp::byte *>(this) + sizeof(Block);
172-
LIBC_ASSERT(reinterpret_cast<uintptr_t>(s) % alignof(max_align_t) == 0 &&
173-
"usable space must be aligned to a multiple of max_align_t");
176+
LIBC_ASSERT(reinterpret_cast<uintptr_t>(s) % MIN_ALIGN == 0 &&
177+
"usable space must be aligned to MIN_ALIGN");
174178
return s;
175179
}
176180

@@ -185,9 +189,9 @@ class Block {
185189
/// `new_inner_size`. The remaining space will be returned as a new block,
186190
/// with usable space aligned to `usable_space_alignment`. Note that the prev_
187191
/// field of the next block counts as part of the inner size of the block.
188-
/// `usable_space_alignment` must be a multiple of max_align_t.
192+
/// `usable_space_alignment` must be a multiple of MIN_ALIGN.
189193
optional<Block *> split(size_t new_inner_size,
190-
size_t usable_space_alignment = alignof(max_align_t));
194+
size_t usable_space_alignment = MIN_ALIGN);
191195

192196
/// Merges this block with the one that comes after it.
193197
bool merge_next();
@@ -228,13 +232,11 @@ class Block {
228232

229233
LIBC_INLINE Block(size_t outer_size, bool is_last) : next_(outer_size) {
230234
// Last blocks are not usable, so they need not have sizes aligned to
231-
// max_align_t. Their lower bits must still be free, so they must be aligned
232-
// to Block.
233-
LIBC_ASSERT(
234-
outer_size % (is_last ? alignof(Block) : alignof(max_align_t)) == 0 &&
235-
"block sizes must be aligned");
236-
LIBC_ASSERT(is_usable_space_aligned(alignof(max_align_t)) &&
237-
"usable space must be aligned to a multiple of max_align_t");
235+
// MIN_ALIGN.
236+
LIBC_ASSERT(outer_size % (is_last ? alignof(Block) : MIN_ALIGN) == 0 &&
237+
"block sizes must be aligned");
238+
LIBC_ASSERT(is_usable_space_aligned(MIN_ALIGN) &&
239+
"usable space must be aligned to a multiple of MIN_ALIGN");
238240
if (is_last)
239241
next_ |= LAST_MASK;
240242
}
@@ -249,11 +251,10 @@ class Block {
249251
// Returns 0 if there is no such size.
250252
LIBC_INLINE static size_t min_size_for_allocation(size_t alignment,
251253
size_t size) {
252-
LIBC_ASSERT(alignment >= alignof(max_align_t) &&
253-
alignment % alignof(max_align_t) == 0 &&
254-
"alignment must be multiple of max_align_t");
254+
LIBC_ASSERT(alignment >= MIN_ALIGN && alignment % MIN_ALIGN == 0 &&
255+
"alignment must be multiple of MIN_ALIGN");
255256

256-
if (alignment == alignof(max_align_t))
257+
if (alignment == MIN_ALIGN)
257258
return size;
258259

259260
// We must create a new block inside this one (splitting). This requires a
@@ -274,7 +275,7 @@ class Block {
274275
// So the maximum distance would be G - L. As a special case, if L is 1
275276
// (unaligned), the max distance is G - 1.
276277
//
277-
// This block's usable space is aligned to max_align_t >= Block. With zero
278+
// This block's usable space is aligned to MIN_ALIGN >= Block. With zero
278279
// padding, the next block's usable space is sizeof(Block) past it, which is
279280
// a point aligned to Block. Thus the max padding needed is alignment -
280281
// alignof(Block).
@@ -309,13 +310,15 @@ class Block {
309310
static BlockInfo allocate(Block *block, size_t alignment, size_t size);
310311

311312
// These two functions may wrap around.
312-
LIBC_INLINE static uintptr_t next_possible_block_start(
313-
uintptr_t ptr, size_t usable_space_alignment = alignof(max_align_t)) {
313+
LIBC_INLINE static uintptr_t
314+
next_possible_block_start(uintptr_t ptr,
315+
size_t usable_space_alignment = MIN_ALIGN) {
314316
return align_up(ptr + sizeof(Block), usable_space_alignment) -
315317
sizeof(Block);
316318
}
317-
LIBC_INLINE static uintptr_t prev_possible_block_start(
318-
uintptr_t ptr, size_t usable_space_alignment = alignof(max_align_t)) {
319+
LIBC_INLINE static uintptr_t
320+
prev_possible_block_start(uintptr_t ptr,
321+
size_t usable_space_alignment = MIN_ALIGN) {
319322
return align_down(ptr, usable_space_alignment) - sizeof(Block);
320323
}
321324

@@ -360,9 +363,6 @@ class Block {
360363
static constexpr size_t PREV_FIELD_SIZE = sizeof(prev_);
361364
};
362365

363-
static_assert(alignof(Block) >= 4,
364-
"at least 2 bits must be available in block sizes for flags");
365-
366366
LIBC_INLINE
367367
optional<Block *> Block::init(ByteSpan region) {
368368
if (!region.data())
@@ -394,8 +394,8 @@ optional<Block *> Block::init(ByteSpan region) {
394394

395395
LIBC_INLINE
396396
Block::BlockInfo Block::allocate(Block *block, size_t alignment, size_t size) {
397-
LIBC_ASSERT(alignment % alignof(max_align_t) == 0 &&
398-
"alignment must be a multiple of max_align_t");
397+
LIBC_ASSERT(alignment % MIN_ALIGN == 0 &&
398+
"alignment must be a multiple of MIN_ALIGN");
399399

400400
BlockInfo info{block, /*prev=*/nullptr, /*next=*/nullptr};
401401

@@ -430,8 +430,8 @@ Block::BlockInfo Block::allocate(Block *block, size_t alignment, size_t size) {
430430
LIBC_INLINE
431431
optional<Block *> Block::split(size_t new_inner_size,
432432
size_t usable_space_alignment) {
433-
LIBC_ASSERT(usable_space_alignment % alignof(max_align_t) == 0 &&
434-
"alignment must be a multiple of max_align_t");
433+
LIBC_ASSERT(usable_space_alignment % MIN_ALIGN == 0 &&
434+
"alignment must be a multiple of MIN_ALIGN");
435435
if (used())
436436
return {};
437437

@@ -445,8 +445,8 @@ optional<Block *> Block::split(size_t new_inner_size,
445445
if (next_block_start < start)
446446
return {};
447447
size_t new_outer_size = next_block_start - start;
448-
LIBC_ASSERT(new_outer_size % alignof(max_align_t) == 0 &&
449-
"new size must be aligned to max_align_t");
448+
LIBC_ASSERT(new_outer_size % MIN_ALIGN == 0 &&
449+
"new size must be aligned to MIN_ALIGN");
450450

451451
if (outer_size() < new_outer_size ||
452452
outer_size() - new_outer_size < sizeof(Block))

libc/src/__support/freelist_heap.h

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,7 @@ LIBC_INLINE void *FreeListHeap::allocate_impl(size_t alignment, size_t size) {
108108
}
109109

110110
LIBC_INLINE void *FreeListHeap::allocate(size_t size) {
111-
return allocate_impl(alignof(max_align_t), size);
111+
return allocate_impl(Block::MIN_ALIGN, size);
112112
}
113113

114114
LIBC_INLINE void *FreeListHeap::aligned_allocate(size_t alignment,
@@ -121,8 +121,8 @@ LIBC_INLINE void *FreeListHeap::aligned_allocate(size_t alignment,
121121
if (size % alignment != 0)
122122
return nullptr;
123123

124-
// The minimum alignment supported by Block is max_align_t.
125-
alignment = cpp::max(alignment, alignof(max_align_t));
124+
// The minimum alignment supported by Block is MIN_ALIGN.
125+
alignment = cpp::max(alignment, Block::MIN_ALIGN);
126126

127127
return allocate_impl(alignment, size);
128128
}

libc/src/__support/freestore.h

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -41,11 +41,11 @@ class FreeStore {
4141

4242
private:
4343
static constexpr size_t MIN_OUTER_SIZE =
44-
align_up(sizeof(Block) + sizeof(FreeList::Node), alignof(max_align_t));
44+
align_up(sizeof(Block) + sizeof(FreeList::Node), Block::MIN_ALIGN);
4545
static constexpr size_t MIN_LARGE_OUTER_SIZE =
46-
align_up(sizeof(Block) + sizeof(FreeTrie::Node), alignof(max_align_t));
46+
align_up(sizeof(Block) + sizeof(FreeTrie::Node), Block::MIN_ALIGN);
4747
static constexpr size_t NUM_SMALL_SIZES =
48-
(MIN_LARGE_OUTER_SIZE - MIN_OUTER_SIZE) / alignof(max_align_t);
48+
(MIN_LARGE_OUTER_SIZE - MIN_OUTER_SIZE) / Block::MIN_ALIGN;
4949

5050
LIBC_INLINE static bool too_small(Block *block) {
5151
return block->outer_size() < MIN_OUTER_SIZE;
@@ -98,8 +98,7 @@ LIBC_INLINE Block *FreeStore::remove_best_fit(size_t size) {
9898

9999
LIBC_INLINE FreeList &FreeStore::small_list(Block *block) {
100100
LIBC_ASSERT(is_small(block) && "only legal for small blocks");
101-
return small_lists[(block->outer_size() - MIN_OUTER_SIZE) /
102-
alignof(max_align_t)];
101+
return small_lists[(block->outer_size() - MIN_OUTER_SIZE) / Block::MIN_ALIGN];
103102
}
104103

105104
LIBC_INLINE FreeList *FreeStore::find_best_small_fit(size_t size) {

libc/test/src/__support/block_test.cpp

Lines changed: 19 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -22,14 +22,14 @@ using LIBC_NAMESPACE::cpp::span;
2222

2323
TEST(LlvmLibcBlockTest, CanCreateSingleAlignedBlock) {
2424
constexpr size_t kN = 1024;
25-
alignas(max_align_t) array<byte, kN> bytes;
25+
alignas(Block::MIN_ALIGN) array<byte, kN> bytes;
2626

2727
auto result = Block::init(bytes);
2828
ASSERT_TRUE(result.has_value());
2929
Block *block = *result;
3030

3131
EXPECT_EQ(reinterpret_cast<uintptr_t>(block) % alignof(Block), size_t{0});
32-
EXPECT_TRUE(block->is_usable_space_aligned(alignof(max_align_t)));
32+
EXPECT_TRUE(block->is_usable_space_aligned(Block::MIN_ALIGN));
3333

3434
Block *last = block->next();
3535
ASSERT_NE(last, static_cast<Block *>(nullptr));
@@ -52,15 +52,15 @@ TEST(LlvmLibcBlockTest, CanCreateUnalignedSingleBlock) {
5252
constexpr size_t kN = 1024;
5353

5454
// Force alignment, so we can un-force it below
55-
alignas(max_align_t) array<byte, kN> bytes;
55+
alignas(Block::MIN_ALIGN) array<byte, kN> bytes;
5656
span<byte> aligned(bytes);
5757

5858
auto result = Block::init(aligned.subspan(1));
5959
EXPECT_TRUE(result.has_value());
6060

6161
Block *block = *result;
6262
EXPECT_EQ(reinterpret_cast<uintptr_t>(block) % alignof(Block), size_t{0});
63-
EXPECT_TRUE(block->is_usable_space_aligned(alignof(max_align_t)));
63+
EXPECT_TRUE(block->is_usable_space_aligned(Block::MIN_ALIGN));
6464

6565
Block *last = block->next();
6666
ASSERT_NE(last, static_cast<Block *>(nullptr));
@@ -98,7 +98,7 @@ TEST(LlvmLibcBlockTest, CanSplitBlock) {
9898
EXPECT_EQ(block2->outer_size(), orig_size - block1->outer_size());
9999
EXPECT_FALSE(block2->used());
100100
EXPECT_EQ(reinterpret_cast<uintptr_t>(block2) % alignof(Block), size_t{0});
101-
EXPECT_TRUE(block2->is_usable_space_aligned(alignof(max_align_t)));
101+
EXPECT_TRUE(block2->is_usable_space_aligned(Block::MIN_ALIGN));
102102

103103
EXPECT_EQ(block1->next(), block2);
104104
EXPECT_EQ(block2->prev_free(), block1);
@@ -124,7 +124,7 @@ TEST(LlvmLibcBlockTest, CanSplitBlockUnaligned) {
124124
EXPECT_EQ(block2->outer_size(), orig_size - block1->outer_size());
125125
EXPECT_FALSE(block2->used());
126126
EXPECT_EQ(reinterpret_cast<uintptr_t>(block2) % alignof(Block), size_t{0});
127-
EXPECT_TRUE(block2->is_usable_space_aligned(alignof(max_align_t)));
127+
EXPECT_TRUE(block2->is_usable_space_aligned(Block::MIN_ALIGN));
128128

129129
EXPECT_EQ(block1->next(), block2);
130130
EXPECT_EQ(block2->prev_free(), block1);
@@ -211,7 +211,7 @@ TEST(LlvmLibcBlockTest, CanMakeMinimalSizeFirstBlock) {
211211

212212
result = block->split(0);
213213
ASSERT_TRUE(result.has_value());
214-
EXPECT_LE(block->outer_size(), sizeof(Block) + alignof(max_align_t));
214+
EXPECT_LE(block->outer_size(), sizeof(Block) + Block::MIN_ALIGN);
215215
}
216216

217217
TEST(LlvmLibcBlockTest, CanMakeMinimalSizeSecondBlock) {
@@ -228,7 +228,7 @@ TEST(LlvmLibcBlockTest, CanMakeMinimalSizeSecondBlock) {
228228
reinterpret_cast<uintptr_t>(block1->usable_space()) +
229229
Block::PREV_FIELD_SIZE);
230230
ASSERT_TRUE(result.has_value());
231-
EXPECT_LE((*result)->outer_size(), sizeof(Block) + alignof(max_align_t));
231+
EXPECT_LE((*result)->outer_size(), sizeof(Block) + Block::MIN_ALIGN);
232232
}
233233

234234
TEST(LlvmLibcBlockTest, CanMarkBlockUsed) {
@@ -361,18 +361,18 @@ TEST(LlvmLibcBlockTest, Allocate) {
361361
if (i > block->inner_size())
362362
continue;
363363

364-
auto info = Block::allocate(block, alignof(max_align_t), i);
364+
auto info = Block::allocate(block, Block::MIN_ALIGN, i);
365365
EXPECT_NE(info.block, static_cast<Block *>(nullptr));
366366
}
367367

368368
// Ensure we can allocate a byte at every guaranteeable alignment.
369-
for (size_t i = 1; i < kN / alignof(max_align_t); ++i) {
369+
for (size_t i = 1; i < kN / Block::MIN_ALIGN; ++i) {
370370
array<byte, kN> bytes;
371371
auto result = Block::init(bytes);
372372
ASSERT_TRUE(result.has_value());
373373
Block *block = *result;
374374

375-
size_t alignment = i * alignof(max_align_t);
375+
size_t alignment = i * Block::MIN_ALIGN;
376376
if (Block::min_size_for_allocation(alignment, 1) > block->inner_size())
377377
continue;
378378

@@ -393,14 +393,14 @@ TEST(LlvmLibcBlockTest, AllocateAlreadyAligned) {
393393
constexpr size_t SIZE = Block::PREV_FIELD_SIZE + 1;
394394

395395
auto [aligned_block, prev, next] =
396-
Block::allocate(block, alignof(max_align_t), SIZE);
396+
Block::allocate(block, Block::MIN_ALIGN, SIZE);
397397

398398
// Since this is already aligned, there should be no previous block.
399399
EXPECT_EQ(prev, static_cast<Block *>(nullptr));
400400

401401
// Ensure we the block is aligned and large enough.
402402
EXPECT_NE(aligned_block, static_cast<Block *>(nullptr));
403-
EXPECT_TRUE(aligned_block->is_usable_space_aligned(alignof(max_align_t)));
403+
EXPECT_TRUE(aligned_block->is_usable_space_aligned(Block::MIN_ALIGN));
404404
EXPECT_GE(aligned_block->inner_size(), SIZE);
405405

406406
// Check the next block.
@@ -422,9 +422,9 @@ TEST(LlvmLibcBlockTest, AllocateNeedsAlignment) {
422422
// Now pick an alignment such that the usable space is not already aligned to
423423
// it. We want to explicitly test that the block will split into one before
424424
// it.
425-
size_t alignment = alignof(max_align_t);
425+
size_t alignment = Block::MIN_ALIGN;
426426
while (block->is_usable_space_aligned(alignment))
427-
alignment += alignof(max_align_t);
427+
alignment += Block::MIN_ALIGN;
428428

429429
auto [aligned_block, prev, next] = Block::allocate(block, alignment, 10);
430430

@@ -464,9 +464,9 @@ TEST(LlvmLibcBlockTest, PreviousBlockMergedIfNotFirst) {
464464
// Now pick an alignment such that the usable space is not already aligned to
465465
// it. We want to explicitly test that the block will split into one before
466466
// it.
467-
size_t alignment = alignof(max_align_t);
467+
size_t alignment = Block::MIN_ALIGN;
468468
while (newblock->is_usable_space_aligned(alignment))
469-
alignment += alignof(max_align_t);
469+
alignment += Block::MIN_ALIGN;
470470

471471
// Ensure we can allocate in the new block.
472472
auto [aligned_block, prev, next] = Block::allocate(newblock, alignment, 1);
@@ -501,9 +501,9 @@ TEST(LlvmLibcBlockTest, CanRemergeBlockAllocations) {
501501
// Now pick an alignment such that the usable space is not already aligned to
502502
// it. We want to explicitly test that the block will split into one before
503503
// it.
504-
size_t alignment = alignof(max_align_t);
504+
size_t alignment = Block::MIN_ALIGN;
505505
while (block->is_usable_space_aligned(alignment))
506-
alignment += alignof(max_align_t);
506+
alignment += Block::MIN_ALIGN;
507507

508508
auto [aligned_block, prev, next] = Block::allocate(block, alignment, 1);
509509

0 commit comments

Comments
 (0)