Skip to content

Commit 908fc5b

Browse files
committed
Auto merge of rust-lang#99174 - scottmcm:reoptimize-layout-array, r=joshtriplett
Reoptimize layout array This way it's one check instead of two, so hopefully (cc rust-lang#99117) it'll be simpler for rustc perf too 🤞 Quick demonstration: ```rust pub fn demo(n: usize) -> Option<Layout> { Layout::array::<i32>(n).ok() } ``` Nightly: <https://play.rust-lang.org/?version=nightly&mode=release&edition=2021&gist=e97bf33508aa03f38968101cdeb5322d> ```nasm mov rax, rdi mov ecx, 4 mul rcx seto cl movabs rdx, 9223372036854775805 xor esi, esi cmp rax, rdx setb sil shl rsi, 2 xor edx, edx test cl, cl cmove rdx, rsi ret ``` This PR (note no `mul`, in addition to being much shorter): ```nasm xor edx, edx lea rax, [4*rcx] shr rcx, 61 sete dl shl rdx, 2 ret ``` This is built atop `@CAD97` 's rust-lang#99136; the new changes are cb8aba66ef6a0e17f08a0574e4820653e31b45a0. I added a bunch more tests for `Layout::from_size_align` and `Layout::array` too.
2 parents 29e4a9e + a32305a commit 908fc5b

File tree

4 files changed

+115
-10
lines changed

4 files changed

+115
-10
lines changed

library/core/src/alloc/layout.rs

+34-9
Original file line numberDiff line numberDiff line change
@@ -72,9 +72,8 @@ impl Layout {
7272
Layout::from_size_valid_align(size, unsafe { ValidAlign::new_unchecked(align) })
7373
}
7474

75-
/// Internal helper constructor to skip revalidating alignment validity.
76-
#[inline]
77-
const fn from_size_valid_align(size: usize, align: ValidAlign) -> Result<Self, LayoutError> {
75+
#[inline(always)]
76+
const fn max_size_for_align(align: ValidAlign) -> usize {
7877
// (power-of-two implies align != 0.)
7978

8079
// Rounded up size is:
@@ -89,7 +88,13 @@ impl Layout {
8988
//
9089
// Above implies that checking for summation overflow is both
9190
// necessary and sufficient.
92-
if size > isize::MAX as usize - (align.as_nonzero().get() - 1) {
91+
isize::MAX as usize - (align.as_usize() - 1)
92+
}
93+
94+
/// Internal helper constructor to skip revalidating alignment validity.
95+
#[inline]
96+
const fn from_size_valid_align(size: usize, align: ValidAlign) -> Result<Self, LayoutError> {
97+
if size > Self::max_size_for_align(align) {
9398
return Err(LayoutError);
9499
}
95100

@@ -128,7 +133,7 @@ impl Layout {
128133
without modifying the layout"]
129134
#[inline]
130135
pub const fn align(&self) -> usize {
131-
self.align.as_nonzero().get()
136+
self.align.as_usize()
132137
}
133138

134139
/// Constructs a `Layout` suitable for holding a value of type `T`.
@@ -410,13 +415,33 @@ impl Layout {
410415

411416
/// Creates a layout describing the record for a `[T; n]`.
412417
///
413-
/// On arithmetic overflow, returns `LayoutError`.
418+
/// On arithmetic overflow or when the total size would exceed
419+
/// `isize::MAX`, returns `LayoutError`.
414420
#[stable(feature = "alloc_layout_manipulation", since = "1.44.0")]
415421
#[inline]
416422
pub fn array<T>(n: usize) -> Result<Self, LayoutError> {
417-
let array_size = mem::size_of::<T>().checked_mul(n).ok_or(LayoutError)?;
418-
// The safe constructor is called here to enforce the isize size limit.
419-
Layout::from_size_valid_align(array_size, ValidAlign::of::<T>())
423+
// Reduce the amount of code we need to monomorphize per `T`.
424+
return inner(mem::size_of::<T>(), ValidAlign::of::<T>(), n);
425+
426+
#[inline]
427+
fn inner(element_size: usize, align: ValidAlign, n: usize) -> Result<Layout, LayoutError> {
428+
// We need to check two things about the size:
429+
// - That the total size won't overflow a `usize`, and
430+
// - That the total size still fits in an `isize`.
431+
// By using division we can check them both with a single threshold.
432+
// That'd usually be a bad idea, but thankfully here the element size
433+
// and alignment are constants, so the compiler will fold all of it.
434+
if element_size != 0 && n > Layout::max_size_for_align(align) / element_size {
435+
return Err(LayoutError);
436+
}
437+
438+
let array_size = element_size * n;
439+
440+
// SAFETY: We just checked above that the `array_size` will not
441+
// exceed `isize::MAX` even when rounded up to the alignment.
442+
// And `ValidAlign` guarantees it's a power of two.
443+
unsafe { Ok(Layout::from_size_align_unchecked(array_size, align.as_usize())) }
444+
}
420445
}
421446
}
422447

library/core/src/mem/valid_align.rs

+6-1
Original file line numberDiff line numberDiff line change
@@ -35,10 +35,15 @@ impl ValidAlign {
3535
unsafe { mem::transmute::<usize, ValidAlign>(align) }
3636
}
3737

38+
#[inline]
39+
pub(crate) const fn as_usize(self) -> usize {
40+
self.0 as usize
41+
}
42+
3843
#[inline]
3944
pub(crate) const fn as_nonzero(self) -> NonZeroUsize {
4045
// SAFETY: All the discriminants are non-zero.
41-
unsafe { NonZeroUsize::new_unchecked(self.0 as usize) }
46+
unsafe { NonZeroUsize::new_unchecked(self.as_usize()) }
4247
}
4348

4449
/// Returns the base 2 logarithm of the alignment.

library/core/tests/alloc.rs

+44
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
use core::alloc::Layout;
2+
use core::mem::size_of;
23
use core::ptr::{self, NonNull};
34

45
#[test]
@@ -12,6 +13,49 @@ fn const_unchecked_layout() {
1213
assert_eq!(Some(DANGLING), NonNull::new(ptr::invalid_mut(ALIGN)));
1314
}
1415

16+
#[test]
17+
fn layout_round_up_to_align_edge_cases() {
18+
const MAX_SIZE: usize = isize::MAX as usize;
19+
20+
for shift in 0..usize::BITS {
21+
let align = 1_usize << shift;
22+
let edge = (MAX_SIZE + 1) - align;
23+
let low = edge.saturating_sub(10);
24+
let high = edge.saturating_add(10);
25+
assert!(Layout::from_size_align(low, align).is_ok());
26+
assert!(Layout::from_size_align(high, align).is_err());
27+
for size in low..=high {
28+
assert_eq!(
29+
Layout::from_size_align(size, align).is_ok(),
30+
size.next_multiple_of(align) <= MAX_SIZE,
31+
);
32+
}
33+
}
34+
}
35+
36+
#[test]
37+
fn layout_array_edge_cases() {
38+
for_type::<i64>();
39+
for_type::<[i32; 0b10101]>();
40+
for_type::<[u8; 0b1010101]>();
41+
42+
// Make sure ZSTs don't lead to divide-by-zero
43+
assert_eq!(Layout::array::<()>(usize::MAX).unwrap(), Layout::from_size_align(0, 1).unwrap());
44+
45+
fn for_type<T>() {
46+
const MAX_SIZE: usize = isize::MAX as usize;
47+
48+
let edge = (MAX_SIZE + 1) / size_of::<T>();
49+
let low = edge.saturating_sub(10);
50+
let high = edge.saturating_add(10);
51+
assert!(Layout::array::<T>(low).is_ok());
52+
assert!(Layout::array::<T>(high).is_err());
53+
for n in low..=high {
54+
assert_eq!(Layout::array::<T>(n).is_ok(), n * size_of::<T>() <= MAX_SIZE);
55+
}
56+
}
57+
}
58+
1559
#[test]
1660
fn layout_debug_shows_log2_of_alignment() {
1761
// `Debug` is not stable, but here's what it does right now
+31
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
// compile-flags: -O
2+
// only-x86_64
3+
// ignore-debug: the debug assertions get in the way
4+
5+
#![crate_type = "lib"]
6+
7+
use std::alloc::Layout;
8+
9+
type RGB48 = [u16; 3];
10+
11+
// CHECK-LABEL: @layout_array_rgb48
12+
#[no_mangle]
13+
pub fn layout_array_rgb48(n: usize) -> Layout {
14+
// CHECK-NOT: llvm.umul.with.overflow.i64
15+
// CHECK: icmp ugt i64 %n, 1537228672809129301
16+
// CHECK-NOT: llvm.umul.with.overflow.i64
17+
// CHECK: mul nuw nsw i64 %n, 6
18+
// CHECK-NOT: llvm.umul.with.overflow.i64
19+
Layout::array::<RGB48>(n).unwrap()
20+
}
21+
22+
// CHECK-LABEL: @layout_array_i32
23+
#[no_mangle]
24+
pub fn layout_array_i32(n: usize) -> Layout {
25+
// CHECK-NOT: llvm.umul.with.overflow.i64
26+
// CHECK: icmp ugt i64 %n, 2305843009213693951
27+
// CHECK-NOT: llvm.umul.with.overflow.i64
28+
// CHECK: shl nuw nsw i64 %n, 2
29+
// CHECK-NOT: llvm.umul.with.overflow.i64
30+
Layout::array::<i32>(n).unwrap()
31+
}

0 commit comments

Comments
 (0)