Skip to content

Commit 25f0381

Browse files
authored
[libc][malloc] Reduce block overhead by 4 bytes plus alignment effects (#99945)
The unused padding and alignment fields were removed. The used and last bits were stashed into the lower two bits of the next chunk offset. (This is a very typical trick for Knuth boundary tags.) The chunk offsets were recast as counting bytes rather than multiples of the alignment. To ensure that the lowest two bits are not significant, the minimum alignment was bumped to 4. This shouldn't affect anything in practice, since alignof(max_align_t) is overwhelmingly likely to be 8. See #98096
1 parent fb55db5 commit 25f0381

File tree

1 file changed

+55
-76
lines changed

1 file changed

+55
-76
lines changed

libc/src/__support/block.h

Lines changed: 55 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -65,62 +65,58 @@ using cpp::optional;
6565

6666
/// Memory region with links to adjacent blocks.
6767
///
68-
/// The blocks do not encode their size directly. Instead, they encode offsets
69-
/// to the next and previous blocks using the type given by the `OffsetType`
70-
/// template parameter. The encoded offsets are simply the offsets divded by the
71-
/// minimum block alignment, `ALIGNMENT`.
68+
/// The blocks store their offsets to the previous and next blocks. The latter
69+
/// is also the block's size.
7270
///
7371
/// The `ALIGNMENT` constant provided by the derived block is typically the
74-
/// minimum value of `alignof(OffsetType)`. Since the addressable range of a
75-
/// block is given by `std::numeric_limits<OffsetType>::max() *
76-
/// ALIGNMENT`, it may be advantageous to set a higher alignment if it allows
77-
/// using a smaller offset type, even if this wastes some bytes in order to
78-
/// align block headers.
79-
///
80-
/// Blocks will always be aligned to a `ALIGNMENT` boundary. Block sizes will
81-
/// always be rounded up to a multiple of `ALIGNMENT`.
72+
/// minimum value of `alignof(OffsetType)`. Blocks will always be aligned to a
73+
/// `ALIGNMENT` boundary. Block sizes will always be rounded up to a multiple of
74+
/// `ALIGNMENT`.
8275
///
8376
/// As an example, the diagram below represents two contiguous
8477
/// `Block<uint32_t, 8>`s. The indices indicate byte offsets:
8578
///
8679
/// @code{.unparsed}
8780
/// Block 1:
88-
/// +---------------------+------+--------------+
89-
/// | Header | Info | Usable space |
90-
/// +----------+----------+------+--------------+
91-
/// | prev | next | | |
92-
/// | 0......3 | 4......7 | 8..9 | 10.......280 |
93-
/// | 00000000 | 00000046 | 8008 | <app data> |
94-
/// +----------+----------+------+--------------+
81+
/// +---------------------+--------------+
82+
/// | Header | Usable space |
83+
/// +----------+----------+--------------+
84+
/// | prev | next | |
85+
/// | 0......3 | 4......7 | 8........227 |
86+
/// | 00000000 | 00000230 | <app data> |
87+
/// +----------+----------+--------------+
9588
/// Block 2:
96-
/// +---------------------+------+--------------+
97-
/// | Header | Info | Usable space |
98-
/// +----------+----------+------+--------------+
99-
/// | prev | next | | |
100-
/// | 0......3 | 4......7 | 8..9 | 10......1056 |
101-
/// | 00000046 | 00000106 | 2008 | f7f7....f7f7 |
102-
/// +----------+----------+------+--------------+
89+
/// +---------------------+--------------+
90+
/// | Header | Usable space |
91+
/// +----------+----------+--------------+
92+
/// | prev | next | |
93+
/// | 0......3 | 4......7 | 8........827 |
94+
/// | 00000230 | 00000830 | f7f7....f7f7 |
95+
/// +----------+----------+--------------+
10396
/// @endcode
10497
///
105-
/// The overall size of the block (e.g. 280 bytes) is given by its next offset
106-
/// multiplied by the alignment (e.g. 0x106 * 4). Also, the next offset of a
107-
/// block matches the previous offset of its next block. The first block in a
108-
/// list is denoted by having a previous offset of `0`.
98+
/// The next offset of a block matches the previous offset of its next block.
99+
/// The first block in a list is denoted by having a previous offset of `0`.
109100
///
110101
/// @tparam OffsetType Unsigned integral type used to encode offsets. Larger
111102
/// types can address more memory, but consume greater
112103
/// overhead.
113104
/// @tparam kAlign Sets the overall alignment for blocks. Minimum is
114-
/// `alignof(OffsetType)` (the default). Larger values can
115-
/// address more memory, but consume greater overhead.
105+
/// `alignof(OffsetType)` (the default). Larger values
106+
/// cause greater overhead.
116107
template <typename OffsetType = uintptr_t, size_t kAlign = alignof(OffsetType)>
117108
class Block {
109+
// Masks for the contents of the next_ field.
110+
static constexpr size_t USED_MASK = 1 << 0;
111+
static constexpr size_t LAST_MASK = 1 << 1;
112+
static constexpr size_t SIZE_MASK = ~(USED_MASK | LAST_MASK);
113+
118114
public:
119115
using offset_type = OffsetType;
120116
static_assert(cpp::is_unsigned_v<offset_type>,
121117
"offset type must be unsigned");
122-
123-
static constexpr size_t ALIGNMENT = cpp::max(kAlign, alignof(offset_type));
118+
static constexpr size_t ALIGNMENT =
119+
cpp::max(cpp::max(kAlign, alignof(offset_type)), size_t{4});
124120
static constexpr size_t BLOCK_OVERHEAD = align_up(sizeof(Block), ALIGNMENT);
125121

126122
// No copy or move.
@@ -147,14 +143,11 @@ class Block {
147143
}
148144

149145
/// @returns The total size of the block in bytes, including the header.
150-
size_t outer_size() const { return next_ * ALIGNMENT; }
146+
size_t outer_size() const { return next_ & SIZE_MASK; }
151147

152148
/// @returns The number of usable bytes inside the block.
153149
size_t inner_size() const { return outer_size() - BLOCK_OVERHEAD; }
154150

155-
/// @returns The number of bytes requested using AllocFirst or AllocLast.
156-
size_t requested_size() const { return inner_size() - padding_; }
157-
158151
/// @returns A pointer to the usable space inside this block.
159152
cpp::byte *usable_space() {
160153
return reinterpret_cast<cpp::byte *>(this) + BLOCK_OVERHEAD;
@@ -224,33 +217,30 @@ class Block {
224217
return block == nullptr ? nullptr : block->prev();
225218
}
226219

227-
/// Returns the current alignment of a block.
228-
size_t alignment() const { return used() ? info_.alignment : 1; }
229-
230220
/// Indicates whether the block is in use.
231221
///
232222
/// @returns `true` if the block is in use or `false` if not.
233-
bool used() const { return info_.used; }
223+
bool used() const { return next_ & USED_MASK; }
234224

235225
/// Indicates whether this block is the last block or not (i.e. whether
236226
/// `next()` points to a valid block or not). This is needed because
237227
/// `next()` points to the end of this block, whether there is a valid
238228
/// block there or not.
239229
///
240230
/// @returns `true` is this is the last block or `false` if not.
241-
bool last() const { return info_.last; }
231+
bool last() const { return next_ & LAST_MASK; }
242232

243233
/// Marks this block as in use.
244-
void mark_used() { info_.used = 1; }
234+
void mark_used() { next_ |= USED_MASK; }
245235

246236
/// Marks this block as free.
247-
void mark_free() { info_.used = 0; }
237+
void mark_free() { next_ &= ~USED_MASK; }
248238

249239
/// Marks this block as the last one in the chain.
250-
constexpr void mark_last() { info_.last = 1; }
240+
constexpr void mark_last() { next_ |= LAST_MASK; }
251241

252242
/// Clears the last bit from this block.
253-
void clear_last() { info_.last = 1; }
243+
void clear_last() { next_ &= ~LAST_MASK; }
254244

255245
/// @brief Checks if a block is valid.
256246
///
@@ -338,32 +328,26 @@ class Block {
338328
/// ensure the split will succeed.
339329
static Block *split_impl(Block *&block, size_t new_inner_size);
340330

341-
/// Offset (in increments of the minimum alignment) from this block to the
342-
/// previous block. 0 if this is the first block.
331+
/// Offset from this block to the previous block. 0 if this is the first
332+
/// block.
343333
offset_type prev_ = 0;
344334

345-
/// Offset (in increments of the minimum alignment) from this block to the
346-
/// next block. Valid even if this is the last block, since it equals the
347-
/// size of the block.
335+
/// Offset from this block to the next block. Valid even if this is the last
336+
/// block, since it equals the size of the block.
348337
offset_type next_ = 0;
349338

350-
/// Information about the current state of the block:
339+
/// Information about the current state of the block is stored in the two low
340+
/// order bits of the next_ value. These are guaranteed free by a minimum
341+
/// alignment (and thus, alignment of the size) of 4. The lowest bit is the
342+
/// `used` flag, and the other bit is the `last` flag.
343+
///
351344
/// * If the `used` flag is set, the block's usable memory has been allocated
352345
/// and is being used.
353346
/// * If the `last` flag is set, the block does not have a next block.
354347
/// * If the `used` flag is set, the alignment represents the requested value
355348
/// when the memory was allocated, which may be less strict than the actual
356349
/// alignment.
357-
struct {
358-
uint16_t used : 1;
359-
uint16_t last : 1;
360-
uint16_t alignment : 14;
361-
} info_;
362-
363-
/// Number of bytes allocated beyond what was requested. This will be at most
364-
/// the minimum alignment, i.e. `alignof(offset_type).`
365-
uint16_t padding_ = 0;
366-
} __attribute__((packed, aligned(kAlign)));
350+
} __attribute__((packed, aligned(cpp::max(kAlign, size_t{4}))));
367351

368352
// Public template method implementations.
369353

@@ -394,7 +378,7 @@ Block<OffsetType, kAlign>::init(ByteSpan region) {
394378
if (region.size() < BLOCK_OVERHEAD)
395379
return {};
396380

397-
if (cpp::numeric_limits<OffsetType>::max() < region.size() / ALIGNMENT)
381+
if (cpp::numeric_limits<OffsetType>::max() < region.size())
398382
return {};
399383

400384
Block *block = as_block(0, region);
@@ -501,7 +485,7 @@ Block<OffsetType, kAlign>::split(Block *&block, size_t new_inner_size) {
501485
template <typename OffsetType, size_t kAlign>
502486
Block<OffsetType, kAlign> *
503487
Block<OffsetType, kAlign>::split_impl(Block *&block, size_t new_inner_size) {
504-
size_t prev_outer_size = block->prev_ * ALIGNMENT;
488+
size_t prev_outer_size = block->prev_;
505489
size_t outer_size1 = new_inner_size + BLOCK_OVERHEAD;
506490
bool is_last = block->last();
507491
ByteSpan bytes = as_bytes(cpp::move(block));
@@ -529,7 +513,7 @@ bool Block<OffsetType, kAlign>::merge_next(Block *&block) {
529513
if (block->used() || next->used())
530514
return false;
531515

532-
size_t prev_outer_size = block->prev_ * ALIGNMENT;
516+
size_t prev_outer_size = block->prev_;
533517
bool is_last = next->last();
534518
ByteSpan prev_bytes = as_bytes(cpp::move(block));
535519
ByteSpan next_bytes = as_bytes(cpp::move(next));
@@ -554,23 +538,18 @@ Block<OffsetType, kAlign> *Block<OffsetType, kAlign>::next() const {
554538

555539
template <typename OffsetType, size_t kAlign>
556540
Block<OffsetType, kAlign> *Block<OffsetType, kAlign>::prev() const {
557-
uintptr_t addr =
558-
(prev_ == 0) ? 0
559-
: reinterpret_cast<uintptr_t>(this) - (prev_ * ALIGNMENT);
541+
uintptr_t addr = (prev_ == 0) ? 0 : reinterpret_cast<uintptr_t>(this) - prev_;
560542
return reinterpret_cast<Block *>(addr);
561543
}
562544

563545
// Private template method implementations.
564546

565547
template <typename OffsetType, size_t kAlign>
566548
constexpr Block<OffsetType, kAlign>::Block(size_t prev_outer_size,
567-
size_t outer_size)
568-
: info_{} {
569-
prev_ = prev_outer_size / ALIGNMENT;
570-
next_ = outer_size / ALIGNMENT;
571-
info_.used = 0;
572-
info_.last = 0;
573-
info_.alignment = ALIGNMENT;
549+
size_t outer_size) {
550+
prev_ = prev_outer_size;
551+
LIBC_ASSERT(outer_size % ALIGNMENT == 0 && "block sizes must be aligned");
552+
next_ = outer_size;
574553
}
575554

576555
template <typename OffsetType, size_t kAlign>

0 commit comments

Comments
 (0)