Skip to content

Commit 3e6e03e

Browse files
committed
Auto merge of rust-lang#129845 - scottmcm:redo-layout, r=<try>
Take more advantage of the `isize::MAX` limit in `Layout` Things like `padding_needed_for` are current implemented being super careful to handle things like `Layout::size` potentially being `usize::MAX`. But now that rust-lang#95295 has happened, that's no longer a concern. It's possible to add two `Layout::size`s together without risking overflow now. So this PR adds a wrapper type to allow doing that kind of thing in safe code while still telling LLVM it can't overflow.
2 parents 78d5c04 + 3fcde40 commit 3e6e03e

8 files changed

+222
-84
lines changed

library/core/src/alloc/layout.rs

+126-68
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,12 @@
44
// collections, resulting in having to optimize down excess IR multiple times.
55
// Your performance intuition is useless. Run perf.
66

7+
use super::size_in_bytes::SizeInBytes;
78
use crate::error::Error;
9+
use crate::intrinsics::unchecked_sub;
10+
use crate::mem::SizedTypeProperties;
811
use crate::ptr::{Alignment, NonNull};
9-
use crate::{assert_unsafe_precondition, cmp, fmt, mem};
12+
use crate::{assert_unsafe_precondition, fmt, mem};
1013

1114
// While this function is used in one place and its implementation
1215
// could be inlined, the previous attempts to do so made rustc
@@ -37,7 +40,7 @@ const fn size_align<T>() -> (usize, usize) {
3740
#[lang = "alloc_layout"]
3841
pub struct Layout {
3942
// size of the requested block of memory, measured in bytes.
40-
size: usize,
43+
size: SizeInBytes,
4144

4245
// alignment of the requested block of memory, measured in bytes.
4346
// we ensure that this is always a power-of-two, because API's
@@ -68,22 +71,22 @@ impl Layout {
6871
pub const fn from_size_align(size: usize, align: usize) -> Result<Self, LayoutError> {
6972
if Layout::is_size_align_valid(size, align) {
7073
// SAFETY: Layout::is_size_align_valid checks the preconditions for this call.
71-
unsafe { Ok(Layout { size, align: mem::transmute(align) }) }
74+
unsafe { Ok(Layout { size: mem::transmute(size), align: mem::transmute(align) }) }
7275
} else {
7376
Err(LayoutError)
7477
}
7578
}
7679

7780
const fn is_size_align_valid(size: usize, align: usize) -> bool {
7881
let Some(align) = Alignment::new(align) else { return false };
79-
if size > Self::max_size_for_align(align) {
82+
if size > Self::max_size_for_align(align).as_usize() {
8083
return false;
8184
}
8285
true
8386
}
8487

8588
#[inline(always)]
86-
const fn max_size_for_align(align: Alignment) -> usize {
89+
const fn max_size_for_align(align: Alignment) -> SizeInBytes {
8790
// (power-of-two implies align != 0.)
8891

8992
// Rounded up size is:
@@ -98,18 +101,28 @@ impl Layout {
98101
//
99102
// Above implies that checking for summation overflow is both
100103
// necessary and sufficient.
101-
isize::MAX as usize - (align.as_usize() - 1)
104+
105+
// SAFETY: the maximum possible alignment is `isize::MAX + 1`,
106+
// so the first subtraction cannot overflow. The minimum possible
107+
// alignment is `1`, so the subtraction returns as most `isize::MAX`,
108+
// and thus the calculated `max_size` is guaranteed in-range.
109+
unsafe {
110+
let max_size = unchecked_sub(isize::MAX as usize + 1, align.as_usize());
111+
SizeInBytes::new_unchecked(max_size)
112+
}
102113
}
103114

104-
/// Internal helper constructor to skip revalidating alignment validity.
115+
/// Internal helper constructor to check only the inter-field invariant,
116+
/// trusting the types to enforce the per-field invariants.
105117
#[inline]
106-
const fn from_size_alignment(size: usize, align: Alignment) -> Result<Self, LayoutError> {
107-
if size > Self::max_size_for_align(align) {
108-
return Err(LayoutError);
118+
const fn from_size_alignment(size: SizeInBytes, align: Alignment) -> Result<Self, LayoutError> {
119+
// FIXME: remove the `as_usize`s once we can use `const PartialOrd`
120+
if size.as_usize() <= Self::max_size_for_align(align).as_usize() {
121+
// SAFETY: Layout::size invariants checked above.
122+
Ok(Layout { size, align })
123+
} else {
124+
Err(LayoutError)
109125
}
110-
111-
// SAFETY: Layout::size invariants checked above.
112-
Ok(Layout { size, align })
113126
}
114127

115128
/// Creates a layout, bypassing all checks.
@@ -134,7 +147,7 @@ impl Layout {
134147
) => Layout::is_size_align_valid(size, align)
135148
);
136149
// SAFETY: the caller is required to uphold the preconditions.
137-
unsafe { Layout { size, align: mem::transmute(align) } }
150+
unsafe { Layout { size: mem::transmute(size), align: mem::transmute(align) } }
138151
}
139152

140153
/// The minimum size in bytes for a memory block of this layout.
@@ -143,7 +156,7 @@ impl Layout {
143156
#[must_use]
144157
#[inline]
145158
pub const fn size(&self) -> usize {
146-
self.size
159+
self.size.as_usize()
147160
}
148161

149162
/// The minimum byte alignment for a memory block of this layout.
@@ -252,9 +265,14 @@ impl Layout {
252265
/// Returns an error if the combination of `self.size()` and the given
253266
/// `align` violates the conditions listed in [`Layout::from_size_align`].
254267
#[stable(feature = "alloc_layout_manipulation", since = "1.44.0")]
268+
#[rustc_const_unstable(feature = "const_alloc_layout", issue = "67521")]
255269
#[inline]
256-
pub fn align_to(&self, align: usize) -> Result<Self, LayoutError> {
257-
Layout::from_size_align(self.size(), cmp::max(self.align(), align))
270+
pub const fn align_to(&self, align: usize) -> Result<Self, LayoutError> {
271+
if let Some(align) = Alignment::new(align) {
272+
Layout::from_size_alignment(self.size, Alignment::max(self.align, align))
273+
} else {
274+
Err(LayoutError)
275+
}
258276
}
259277

260278
/// Returns the amount of padding we must insert after `self`
@@ -279,29 +297,42 @@ impl Layout {
279297
without modifying the `Layout`"]
280298
#[inline]
281299
pub const fn padding_needed_for(&self, align: usize) -> usize {
282-
let len = self.size();
300+
// FIXME: Can we just change the type on this to `Alignment`?
301+
let Some(align) = Alignment::new(align) else { return usize::MAX };
302+
self.padding_bytes_needed_for(align).as_usize()
303+
}
304+
305+
const fn padding_bytes_needed_for(&self, align: Alignment) -> SizeInBytes {
306+
let len = self.size;
307+
let align_m1 = SizeInBytes::alignment_minus_one(align);
308+
let len_rounded_up = len.add_wide(align_m1) & !align_m1.as_usize();
283309

310+
// SAFETY:
284311
// Rounded up value is:
285312
// len_rounded_up = (len + align - 1) & !(align - 1);
286313
// and then we return the padding difference: `len_rounded_up - len`.
287314
//
288-
// We use modular arithmetic throughout:
315+
// The arithmetic we do here can never overflow:
289316
//
290317
// 1. align is guaranteed to be > 0, so align - 1 is always
291318
// valid.
292319
//
293-
// 2. `len + align - 1` can overflow by at most `align - 1`,
294-
// so the &-mask with `!(align - 1)` will ensure that in the
295-
// case of overflow, `len_rounded_up` will itself be 0.
296-
// Thus the returned padding, when added to `len`, yields 0,
297-
// which trivially satisfies the alignment `align`.
320+
// 2. len is at most `isize::MAX`, so adding `align - 1` can never
321+
// overflow a `usize`.
298322
//
299-
// (Of course, attempts to allocate blocks of memory whose
300-
// size and padding overflow in the above manner should cause
301-
// the allocator to yield an error anyway.)
302-
303-
let len_rounded_up = len.wrapping_add(align).wrapping_sub(1) & !align.wrapping_sub(1);
304-
len_rounded_up.wrapping_sub(len)
323+
// 3. masking by the alignment can remove at most `align - 1`,
324+
// which is what we just added, so the subtraction cannot overflow.
325+
//
326+
// 4. the resulting padding is thus at most `align - 1`, but the largest
327+
// possible alignment is `isize::MAX + 1`, and thus the padding
328+
// will always fit in `SizeInBytes`.
329+
//
330+
// (Size 0 Align MAX is already aligned, so doesn't need any padding,
331+
// but Size 1 Align MAX has the largest padding requirement: `isize::MAX`.)
332+
unsafe {
333+
let padding = unchecked_sub(len_rounded_up, len.as_usize());
334+
SizeInBytes::new_unchecked(padding)
335+
}
305336
}
306337

307338
/// Creates a layout by rounding the size of this layout up to a multiple
@@ -315,12 +346,12 @@ impl Layout {
315346
without modifying the original"]
316347
#[inline]
317348
pub const fn pad_to_align(&self) -> Layout {
318-
let pad = self.padding_needed_for(self.align());
349+
let pad = self.padding_bytes_needed_for(self.align);
319350
// This cannot overflow. Quoting from the invariant of Layout:
320351
// > `size`, when rounded up to the nearest multiple of `align`,
321352
// > must not overflow isize (i.e., the rounded value must be
322353
// > less than or equal to `isize::MAX`)
323-
let new_size = self.size() + pad;
354+
let new_size = self.size.add_wide(pad);
324355

325356
// SAFETY: padded size is guaranteed to not exceed `isize::MAX`.
326357
unsafe { Layout::from_size_align_unchecked(new_size, self.align()) }
@@ -333,20 +364,36 @@ impl Layout {
333364
/// layout of the array and `offs` is the distance between the start
334365
/// of each element in the array.
335366
///
367+
/// (That distance between elements is sometimes known as "stride".)
368+
///
336369
/// On arithmetic overflow, returns `LayoutError`.
370+
///
371+
/// # Examples
372+
///
373+
/// ```
374+
/// #![feature(alloc_layout_extra)]
375+
/// use std::alloc::Layout;
376+
///
377+
/// // All rust types have a size that's a multiple of their alignment.
378+
/// let normal = Layout::from_size_align(12, 4).unwrap();
379+
/// let repeated = normal.repeat(3).unwrap();
380+
/// assert_eq!(repeated, (Layout::from_size_align(36, 4).unwrap(), 12));
381+
///
382+
/// // But you can manually make layouts which don't meet that rule.
383+
/// let padding_needed = Layout::from_size_align(6, 4).unwrap();
384+
/// let repeated = padding_needed.repeat(3).unwrap();
385+
/// assert_eq!(repeated, (Layout::from_size_align(24, 4).unwrap(), 8));
386+
/// ```
337387
#[unstable(feature = "alloc_layout_extra", issue = "55724")]
388+
#[rustc_const_unstable(feature = "const_alloc_layout", issue = "67521")]
338389
#[inline]
339-
pub fn repeat(&self, n: usize) -> Result<(Self, usize), LayoutError> {
340-
// This cannot overflow. Quoting from the invariant of Layout:
341-
// > `size`, when rounded up to the nearest multiple of `align`,
342-
// > must not overflow isize (i.e., the rounded value must be
343-
// > less than or equal to `isize::MAX`)
344-
let padded_size = self.size() + self.padding_needed_for(self.align());
345-
let alloc_size = padded_size.checked_mul(n).ok_or(LayoutError)?;
346-
347-
// The safe constructor is called here to enforce the isize size limit.
348-
let layout = Layout::from_size_alignment(alloc_size, self.align)?;
349-
Ok((layout, padded_size))
390+
pub const fn repeat(&self, n: usize) -> Result<(Self, usize), LayoutError> {
391+
let padded = self.pad_to_align();
392+
if let Ok(repeated) = padded.repeat_packed(n) {
393+
Ok((repeated, padded.size()))
394+
} else {
395+
Err(LayoutError)
396+
}
350397
}
351398

352399
/// Creates a layout describing the record for `self` followed by
@@ -395,17 +442,20 @@ impl Layout {
395442
/// # assert_eq!(repr_c(&[u64, u32, u16, u32]), Ok((s, vec![0, 8, 12, 16])));
396443
/// ```
397444
#[stable(feature = "alloc_layout_manipulation", since = "1.44.0")]
445+
#[rustc_const_unstable(feature = "const_alloc_layout", issue = "67521")]
398446
#[inline]
399-
pub fn extend(&self, next: Self) -> Result<(Self, usize), LayoutError> {
400-
let new_align = cmp::max(self.align, next.align);
401-
let pad = self.padding_needed_for(next.align());
402-
403-
let offset = self.size().checked_add(pad).ok_or(LayoutError)?;
404-
let new_size = offset.checked_add(next.size()).ok_or(LayoutError)?;
405-
406-
// The safe constructor is called here to enforce the isize size limit.
407-
let layout = Layout::from_size_alignment(new_size, new_align)?;
408-
Ok((layout, offset))
447+
pub const fn extend(&self, next: Self) -> Result<(Self, usize), LayoutError> {
448+
let new_align = Alignment::max(self.align, next.align);
449+
let pad = self.padding_bytes_needed_for(next.align);
450+
451+
if let Some(offset) = self.size.checked_add(pad)
452+
&& let Some(new_size) = offset.checked_add(next.size)
453+
&& let Ok(layout) = Layout::from_size_alignment(new_size, new_align)
454+
{
455+
Ok((layout, offset.as_usize()))
456+
} else {
457+
Err(LayoutError)
458+
}
409459
}
410460

411461
/// Creates a layout describing the record for `n` instances of
@@ -421,11 +471,15 @@ impl Layout {
421471
///
422472
/// On arithmetic overflow, returns `LayoutError`.
423473
#[unstable(feature = "alloc_layout_extra", issue = "55724")]
474+
#[rustc_const_unstable(feature = "const_alloc_layout", issue = "67521")]
424475
#[inline]
425-
pub fn repeat_packed(&self, n: usize) -> Result<Self, LayoutError> {
426-
let size = self.size().checked_mul(n).ok_or(LayoutError)?;
427-
// The safe constructor is called here to enforce the isize size limit.
428-
Layout::from_size_alignment(size, self.align)
476+
pub const fn repeat_packed(&self, n: usize) -> Result<Self, LayoutError> {
477+
if let Some(size) = self.size.checked_mul(n) {
478+
// The safe constructor is called here to enforce the isize size limit.
479+
Layout::from_size_alignment(size, self.align)
480+
} else {
481+
Err(LayoutError)
482+
}
429483
}
430484

431485
/// Creates a layout describing the record for `self` followed by
@@ -435,11 +489,15 @@ impl Layout {
435489
///
436490
/// On arithmetic overflow, returns `LayoutError`.
437491
#[unstable(feature = "alloc_layout_extra", issue = "55724")]
492+
#[rustc_const_unstable(feature = "const_alloc_layout", issue = "67521")]
438493
#[inline]
439-
pub fn extend_packed(&self, next: Self) -> Result<Self, LayoutError> {
440-
let new_size = self.size().checked_add(next.size()).ok_or(LayoutError)?;
441-
// The safe constructor is called here to enforce the isize size limit.
442-
Layout::from_size_alignment(new_size, self.align)
494+
pub const fn extend_packed(&self, next: Self) -> Result<Self, LayoutError> {
495+
if let Some(new_size) = self.size.checked_add(next.size) {
496+
// The safe constructor is called here to enforce the isize size limit.
497+
Layout::from_size_alignment(new_size, self.align)
498+
} else {
499+
Err(LayoutError)
500+
}
443501
}
444502

445503
/// Creates a layout describing the record for a `[T; n]`.
@@ -451,21 +509,21 @@ impl Layout {
451509
#[inline]
452510
pub const fn array<T>(n: usize) -> Result<Self, LayoutError> {
453511
// Reduce the amount of code we need to monomorphize per `T`.
454-
return inner(mem::size_of::<T>(), Alignment::of::<T>(), n);
512+
return inner(T::LAYOUT, n);
455513

456514
#[inline]
457-
const fn inner(
458-
element_size: usize,
459-
align: Alignment,
460-
n: usize,
461-
) -> Result<Layout, LayoutError> {
515+
const fn inner(element_layout: Layout, n: usize) -> Result<Layout, LayoutError> {
516+
let Layout { size, align } = element_layout;
517+
let element_size = size.as_usize();
518+
462519
// We need to check two things about the size:
463520
// - That the total size won't overflow a `usize`, and
464521
// - That the total size still fits in an `isize`.
465522
// By using division we can check them both with a single threshold.
466523
// That'd usually be a bad idea, but thankfully here the element size
467524
// and alignment are constants, so the compiler will fold all of it.
468-
if element_size != 0 && n > Layout::max_size_for_align(align) / element_size {
525+
if element_size != 0 && n > Layout::max_size_for_align(align).as_usize() / element_size
526+
{
469527
return Err(LayoutError);
470528
}
471529

library/core/src/alloc/mod.rs

+1
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
mod global;
66
mod layout;
7+
mod size_in_bytes;
78

89
#[stable(feature = "global_alloc", since = "1.28.0")]
910
pub use self::global::GlobalAlloc;

0 commit comments

Comments
 (0)