|
16 | 16 | #include "src/__support/CPP/optional.h"
|
17 | 17 | #include "src/__support/CPP/span.h"
|
18 | 18 | #include "src/__support/CPP/type_traits.h"
|
| 19 | +#include "src/__support/libc_assert.h" |
19 | 20 |
|
20 | 21 | #include <stdint.h>
|
21 | 22 |
|
@@ -261,6 +262,63 @@ class Block {
|
261 | 262 |
|
262 | 263 | constexpr Block(size_t prev_outer_size, size_t outer_size);
|
263 | 264 |
|
| 265 | + bool is_usable_space_aligned(size_t alignment) const { |
| 266 | + return reinterpret_cast<uintptr_t>(usable_space()) % alignment == 0; |
| 267 | + } |
| 268 | + |
| 269 | + size_t padding_for_alignment(size_t alignment) const { |
| 270 | + if (is_usable_space_aligned(alignment)) |
| 271 | + return 0; |
| 272 | + |
| 273 | + // We need to ensure we can always split this block into a "padding" block |
| 274 | + // and the aligned block. To do this, we need enough extra space for at |
| 275 | + // least one block. |
| 276 | + // |
| 277 | + // |block |usable_space | |
| 278 | + // |........|......................................| |
| 279 | + // ^ |
| 280 | + // Alignment requirement |
| 281 | + // |
| 282 | + // |
| 283 | + // |block |space |block |usable_space | |
| 284 | + // |........|........|........|....................| |
| 285 | + // ^ |
| 286 | + // Alignment requirement |
| 287 | + // |
| 288 | + uintptr_t start = reinterpret_cast<uintptr_t>(usable_space()); |
| 289 | + alignment = cpp::max(alignment, ALIGNMENT); |
| 290 | + return align_up(start + BLOCK_OVERHEAD, alignment) - start; |
| 291 | + } |
| 292 | + |
| 293 | + // Check that we can `allocate` a block with a given alignment and size from |
| 294 | + // this existing block. |
| 295 | + bool can_allocate(size_t alignment, size_t size) const; |
| 296 | + |
| 297 | + // This is the return type for `allocate` which can split one block into up to |
| 298 | + // three blocks. |
| 299 | + struct BlockInfo { |
| 300 | + // This is the newly aligned block. It will have the alignment requested by |
| 301 | + // a call to `allocate` and at most `size`. |
| 302 | + Block *block; |
| 303 | + |
| 304 | + // If the usable_space in the new block was not aligned according to the |
| 305 | + // `alignment` parameter, we will need to split into this block and the |
| 306 | + // `block` to ensure `block` is properly aligned. In this case, `prev` will |
| 307 | + // be a pointer to this new "padding" block. `prev` will be nullptr if no |
| 308 | + // new block was created or we were able to merge the block before the |
| 309 | + // original block with the "padding" block. |
| 310 | + Block *prev; |
| 311 | + |
| 312 | + // This is the remainder of the next block after splitting the `block` |
| 313 | + // according to `size`. This can happen if there's enough space after the |
| 314 | + // `block`. |
| 315 | + Block *next; |
| 316 | + }; |
| 317 | + |
| 318 | + // Divide a block into up to 3 blocks according to `BlockInfo`. This should |
| 319 | + // only be called if `can_allocate` returns true. |
| 320 | + static BlockInfo allocate(Block *block, size_t alignment, size_t size); |
| 321 | + |
264 | 322 | private:
|
265 | 323 | /// Consumes the block and returns as a span of bytes.
|
266 | 324 | static ByteSpan as_bytes(Block *&&block);
|
@@ -357,6 +415,69 @@ void Block<OffsetType, kAlign>::free(Block *&block) {
|
357 | 415 | merge_next(block);
|
358 | 416 | }
|
359 | 417 |
|
| 418 | +template <typename OffsetType, size_t kAlign> |
| 419 | +bool Block<OffsetType, kAlign>::can_allocate(size_t alignment, |
| 420 | + size_t size) const { |
| 421 | + if (is_usable_space_aligned(alignment) && inner_size() >= size) |
| 422 | + return true; // Size and alignment constraints met. |
| 423 | + |
| 424 | + // Either the alignment isn't met or we don't have enough size. |
| 425 | + // If we don't meet alignment, we can always adjust such that we do meet the |
| 426 | + // alignment. If we meet the alignment but just don't have enough size. This |
| 427 | + // check will fail anyway. |
| 428 | + size_t adjustment = padding_for_alignment(alignment); |
| 429 | + return inner_size() >= size + adjustment; |
| 430 | +} |
| 431 | + |
| 432 | +template <typename OffsetType, size_t kAlign> |
| 433 | +typename Block<OffsetType, kAlign>::BlockInfo |
| 434 | +Block<OffsetType, kAlign>::allocate(Block *block, size_t alignment, |
| 435 | + size_t size) { |
| 436 | + LIBC_ASSERT( |
| 437 | + block->can_allocate(alignment, size) && |
| 438 | + "Calls to this function for a given alignment and size should only be " |
| 439 | + "done if `can_allocate` for these parameters returns true."); |
| 440 | + |
| 441 | + BlockInfo info{block, /*prev=*/nullptr, /*next=*/nullptr}; |
| 442 | + |
| 443 | + if (!info.block->is_usable_space_aligned(alignment)) { |
| 444 | + size_t adjustment = info.block->padding_for_alignment(alignment); |
| 445 | + size_t new_inner_size = adjustment - BLOCK_OVERHEAD; |
| 446 | + LIBC_ASSERT(new_inner_size % ALIGNMENT == 0 && |
| 447 | + "The adjustment calculation should always return a new size " |
| 448 | + "that's a multiple of ALIGNMENT"); |
| 449 | + |
| 450 | + Block *original = info.block; |
| 451 | + optional<Block *> maybe_aligned_block = |
| 452 | + Block::split(original, adjustment - BLOCK_OVERHEAD); |
| 453 | + LIBC_ASSERT(maybe_aligned_block.has_value() && |
| 454 | + "This split should always result in a new block. The check in " |
| 455 | + "`can_allocate` ensures that we have enough space here to make " |
| 456 | + "two blocks."); |
| 457 | + |
| 458 | + if (Block *prev = original->prev()) { |
| 459 | + // If there is a block before this, we can merge the current one with the |
| 460 | + // newly created one. |
| 461 | + merge_next(prev); |
| 462 | + } else { |
| 463 | + // Otherwise, this was the very first block in the chain. Now we can make |
| 464 | + // it the new first block. |
| 465 | + info.prev = original; |
| 466 | + } |
| 467 | + |
| 468 | + Block *aligned_block = *maybe_aligned_block; |
| 469 | + LIBC_ASSERT(aligned_block->is_usable_space_aligned(alignment) && |
| 470 | + "The aligned block isn't aligned somehow."); |
| 471 | + info.block = aligned_block; |
| 472 | + } |
| 473 | + |
| 474 | + // Now get a block for the requested size. |
| 475 | + if (optional<Block *> next = Block::split(info.block, size)) |
| 476 | + info.next = *next; |
| 477 | + |
| 478 | + return info; |
| 479 | +} |
| 480 | + |
360 | 481 | template <typename OffsetType, size_t kAlign>
|
361 | 482 | optional<Block<OffsetType, kAlign> *>
|
362 | 483 | Block<OffsetType, kAlign>::split(Block *&block, size_t new_inner_size) {
|
|
0 commit comments