Skip to content

Commit b2babff

Browse files
authored
Rollup merge of #95361 - scottmcm:valid-align, r=Mark-Simulacrum
Make non-power-of-two alignments a validity error in `Layout` Inspired by the zulip conversation about how `Layout` should better enforce `size <= isize::MAX as usize`, this uses an N-variant enum on N-bit platforms to require at the validity level that the existing invariant of "must be a power of two" is upheld. This was MIRI can catch it, and means there's a more-specific type for `Layout` to store than just `NonZeroUsize`. It's left as `pub(crate)` here; a future PR could consider giving it a tracking issue for non-internal usage.
2 parents a896124 + 7a61ecf commit b2babff

File tree

4 files changed

+269
-6
lines changed

4 files changed

+269
-6
lines changed

core/src/alloc/layout.rs

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
use crate::cmp;
22
use crate::fmt;
3-
use crate::mem;
4-
use crate::num::NonZeroUsize;
3+
use crate::mem::{self, ValidAlign};
54
use crate::ptr::NonNull;
65

76
// While this function is used in one place and its implementation
@@ -40,7 +39,7 @@ pub struct Layout {
4039
//
4140
// (However, we do not analogously require `align >= sizeof(void*)`,
4241
// even though that is *also* a requirement of `posix_memalign`.)
43-
align_: NonZeroUsize,
42+
align_: ValidAlign,
4443
}
4544

4645
impl Layout {
@@ -97,8 +96,8 @@ impl Layout {
9796
#[must_use]
9897
#[inline]
9998
pub const unsafe fn from_size_align_unchecked(size: usize, align: usize) -> Self {
100-
// SAFETY: the caller must ensure that `align` is greater than zero.
101-
Layout { size_: size, align_: unsafe { NonZeroUsize::new_unchecked(align) } }
99+
// SAFETY: the caller must ensure that `align` is a power of two.
100+
Layout { size_: size, align_: unsafe { ValidAlign::new_unchecked(align) } }
102101
}
103102

104103
/// The minimum size in bytes for a memory block of this layout.
@@ -117,7 +116,7 @@ impl Layout {
117116
without modifying the layout"]
118117
#[inline]
119118
pub const fn align(&self) -> usize {
120-
self.align_.get()
119+
self.align_.as_nonzero().get()
121120
}
122121

123122
/// Constructs a `Layout` suitable for holding a value of type `T`.

core/src/mem/mod.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,12 @@ mod maybe_uninit;
2121
#[stable(feature = "maybe_uninit", since = "1.36.0")]
2222
pub use maybe_uninit::MaybeUninit;
2323

24+
mod valid_align;
25+
// For now this type is left crate-local. It could potentially make sense to expose
26+
// it publicly, as it would be a nice parameter type for methods which need to take
27+
// alignment as a parameter, such as `Layout::padding_needed_for`.
28+
pub(crate) use valid_align::ValidAlign;
29+
2430
#[stable(feature = "rust1", since = "1.0.0")]
2531
#[doc(inline)]
2632
pub use crate::intrinsics::transmute;

core/src/mem/valid_align.rs

Lines changed: 240 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,240 @@
1+
use crate::convert::TryFrom;
2+
use crate::num::NonZeroUsize;
3+
use crate::{cmp, fmt, mem, num};
4+
5+
/// A type storing a `usize` which is a power of two, and thus
6+
/// represents a possible alignment in the rust abstract machine.
7+
///
8+
/// Note that particularly large alignments, while representable in this type,
9+
/// are likely not to be supported by actual allocators and linkers.
10+
#[derive(Copy, Clone)]
11+
#[repr(transparent)]
12+
pub(crate) struct ValidAlign(ValidAlignEnum);
13+
14+
// ValidAlign is `repr(usize)`, but via extra steps.
15+
const _: () = assert!(mem::size_of::<ValidAlign>() == mem::size_of::<usize>());
16+
const _: () = assert!(mem::align_of::<ValidAlign>() == mem::align_of::<usize>());
17+
18+
impl ValidAlign {
19+
/// Creates a `ValidAlign` from a power-of-two `usize`.
20+
///
21+
/// # Safety
22+
///
23+
/// `align` must be a power of two.
24+
///
25+
/// Equivalently, it must be `1 << exp` for some `exp` in `0..usize::BITS`.
26+
/// It must *not* be zero.
27+
#[inline]
28+
pub(crate) const unsafe fn new_unchecked(align: usize) -> Self {
29+
debug_assert!(align.is_power_of_two());
30+
31+
// SAFETY: By precondition, this must be a power of two, and
32+
// our variants encompass all possible powers of two.
33+
unsafe { mem::transmute::<usize, ValidAlign>(align) }
34+
}
35+
36+
#[inline]
37+
pub(crate) const fn as_nonzero(self) -> NonZeroUsize {
38+
// SAFETY: All the discriminants are non-zero.
39+
unsafe { NonZeroUsize::new_unchecked(self.0 as usize) }
40+
}
41+
42+
/// Returns the base 2 logarithm of the alignment.
43+
///
44+
/// This is always exact, as `self` represents a power of two.
45+
#[inline]
46+
pub(crate) fn log2(self) -> u32 {
47+
self.as_nonzero().trailing_zeros()
48+
}
49+
}
50+
51+
impl fmt::Debug for ValidAlign {
52+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
53+
write!(f, "{:?} (1 << {:?})", self.as_nonzero(), self.log2())
54+
}
55+
}
56+
57+
impl TryFrom<NonZeroUsize> for ValidAlign {
58+
type Error = num::TryFromIntError;
59+
60+
#[inline]
61+
fn try_from(align: NonZeroUsize) -> Result<ValidAlign, Self::Error> {
62+
if align.is_power_of_two() {
63+
// SAFETY: Just checked for power-of-two
64+
unsafe { Ok(ValidAlign::new_unchecked(align.get())) }
65+
} else {
66+
Err(num::TryFromIntError(()))
67+
}
68+
}
69+
}
70+
71+
impl TryFrom<usize> for ValidAlign {
72+
type Error = num::TryFromIntError;
73+
74+
#[inline]
75+
fn try_from(align: usize) -> Result<ValidAlign, Self::Error> {
76+
if align.is_power_of_two() {
77+
// SAFETY: Just checked for power-of-two
78+
unsafe { Ok(ValidAlign::new_unchecked(align)) }
79+
} else {
80+
Err(num::TryFromIntError(()))
81+
}
82+
}
83+
}
84+
85+
impl cmp::Eq for ValidAlign {}
86+
87+
impl cmp::PartialEq for ValidAlign {
88+
#[inline]
89+
fn eq(&self, other: &Self) -> bool {
90+
self.as_nonzero() == other.as_nonzero()
91+
}
92+
}
93+
94+
impl cmp::Ord for ValidAlign {
95+
#[inline]
96+
fn cmp(&self, other: &Self) -> cmp::Ordering {
97+
self.as_nonzero().cmp(&other.as_nonzero())
98+
}
99+
}
100+
101+
impl cmp::PartialOrd for ValidAlign {
102+
#[inline]
103+
fn partial_cmp(&self, other: &Self) -> Option<cmp::Ordering> {
104+
Some(self.cmp(other))
105+
}
106+
}
107+
108+
#[cfg(target_pointer_width = "16")]
109+
type ValidAlignEnum = ValidAlignEnum16;
110+
#[cfg(target_pointer_width = "32")]
111+
type ValidAlignEnum = ValidAlignEnum32;
112+
#[cfg(target_pointer_width = "64")]
113+
type ValidAlignEnum = ValidAlignEnum64;
114+
115+
#[derive(Copy, Clone)]
116+
#[repr(u16)]
117+
enum ValidAlignEnum16 {
118+
_Align1Shl0 = 1 << 0,
119+
_Align1Shl1 = 1 << 1,
120+
_Align1Shl2 = 1 << 2,
121+
_Align1Shl3 = 1 << 3,
122+
_Align1Shl4 = 1 << 4,
123+
_Align1Shl5 = 1 << 5,
124+
_Align1Shl6 = 1 << 6,
125+
_Align1Shl7 = 1 << 7,
126+
_Align1Shl8 = 1 << 8,
127+
_Align1Shl9 = 1 << 9,
128+
_Align1Shl10 = 1 << 10,
129+
_Align1Shl11 = 1 << 11,
130+
_Align1Shl12 = 1 << 12,
131+
_Align1Shl13 = 1 << 13,
132+
_Align1Shl14 = 1 << 14,
133+
_Align1Shl15 = 1 << 15,
134+
}
135+
136+
#[derive(Copy, Clone)]
137+
#[repr(u32)]
138+
enum ValidAlignEnum32 {
139+
_Align1Shl0 = 1 << 0,
140+
_Align1Shl1 = 1 << 1,
141+
_Align1Shl2 = 1 << 2,
142+
_Align1Shl3 = 1 << 3,
143+
_Align1Shl4 = 1 << 4,
144+
_Align1Shl5 = 1 << 5,
145+
_Align1Shl6 = 1 << 6,
146+
_Align1Shl7 = 1 << 7,
147+
_Align1Shl8 = 1 << 8,
148+
_Align1Shl9 = 1 << 9,
149+
_Align1Shl10 = 1 << 10,
150+
_Align1Shl11 = 1 << 11,
151+
_Align1Shl12 = 1 << 12,
152+
_Align1Shl13 = 1 << 13,
153+
_Align1Shl14 = 1 << 14,
154+
_Align1Shl15 = 1 << 15,
155+
_Align1Shl16 = 1 << 16,
156+
_Align1Shl17 = 1 << 17,
157+
_Align1Shl18 = 1 << 18,
158+
_Align1Shl19 = 1 << 19,
159+
_Align1Shl20 = 1 << 20,
160+
_Align1Shl21 = 1 << 21,
161+
_Align1Shl22 = 1 << 22,
162+
_Align1Shl23 = 1 << 23,
163+
_Align1Shl24 = 1 << 24,
164+
_Align1Shl25 = 1 << 25,
165+
_Align1Shl26 = 1 << 26,
166+
_Align1Shl27 = 1 << 27,
167+
_Align1Shl28 = 1 << 28,
168+
_Align1Shl29 = 1 << 29,
169+
_Align1Shl30 = 1 << 30,
170+
_Align1Shl31 = 1 << 31,
171+
}
172+
173+
#[derive(Copy, Clone)]
174+
#[repr(u64)]
175+
enum ValidAlignEnum64 {
176+
_Align1Shl0 = 1 << 0,
177+
_Align1Shl1 = 1 << 1,
178+
_Align1Shl2 = 1 << 2,
179+
_Align1Shl3 = 1 << 3,
180+
_Align1Shl4 = 1 << 4,
181+
_Align1Shl5 = 1 << 5,
182+
_Align1Shl6 = 1 << 6,
183+
_Align1Shl7 = 1 << 7,
184+
_Align1Shl8 = 1 << 8,
185+
_Align1Shl9 = 1 << 9,
186+
_Align1Shl10 = 1 << 10,
187+
_Align1Shl11 = 1 << 11,
188+
_Align1Shl12 = 1 << 12,
189+
_Align1Shl13 = 1 << 13,
190+
_Align1Shl14 = 1 << 14,
191+
_Align1Shl15 = 1 << 15,
192+
_Align1Shl16 = 1 << 16,
193+
_Align1Shl17 = 1 << 17,
194+
_Align1Shl18 = 1 << 18,
195+
_Align1Shl19 = 1 << 19,
196+
_Align1Shl20 = 1 << 20,
197+
_Align1Shl21 = 1 << 21,
198+
_Align1Shl22 = 1 << 22,
199+
_Align1Shl23 = 1 << 23,
200+
_Align1Shl24 = 1 << 24,
201+
_Align1Shl25 = 1 << 25,
202+
_Align1Shl26 = 1 << 26,
203+
_Align1Shl27 = 1 << 27,
204+
_Align1Shl28 = 1 << 28,
205+
_Align1Shl29 = 1 << 29,
206+
_Align1Shl30 = 1 << 30,
207+
_Align1Shl31 = 1 << 31,
208+
_Align1Shl32 = 1 << 32,
209+
_Align1Shl33 = 1 << 33,
210+
_Align1Shl34 = 1 << 34,
211+
_Align1Shl35 = 1 << 35,
212+
_Align1Shl36 = 1 << 36,
213+
_Align1Shl37 = 1 << 37,
214+
_Align1Shl38 = 1 << 38,
215+
_Align1Shl39 = 1 << 39,
216+
_Align1Shl40 = 1 << 40,
217+
_Align1Shl41 = 1 << 41,
218+
_Align1Shl42 = 1 << 42,
219+
_Align1Shl43 = 1 << 43,
220+
_Align1Shl44 = 1 << 44,
221+
_Align1Shl45 = 1 << 45,
222+
_Align1Shl46 = 1 << 46,
223+
_Align1Shl47 = 1 << 47,
224+
_Align1Shl48 = 1 << 48,
225+
_Align1Shl49 = 1 << 49,
226+
_Align1Shl50 = 1 << 50,
227+
_Align1Shl51 = 1 << 51,
228+
_Align1Shl52 = 1 << 52,
229+
_Align1Shl53 = 1 << 53,
230+
_Align1Shl54 = 1 << 54,
231+
_Align1Shl55 = 1 << 55,
232+
_Align1Shl56 = 1 << 56,
233+
_Align1Shl57 = 1 << 57,
234+
_Align1Shl58 = 1 << 58,
235+
_Align1Shl59 = 1 << 59,
236+
_Align1Shl60 = 1 << 60,
237+
_Align1Shl61 = 1 << 61,
238+
_Align1Shl62 = 1 << 62,
239+
_Align1Shl63 = 1 << 63,
240+
}

core/tests/alloc.rs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,3 +11,21 @@ fn const_unchecked_layout() {
1111
assert_eq!(LAYOUT.align(), ALIGN);
1212
assert_eq!(Some(DANGLING), NonNull::new(ALIGN as *mut u8));
1313
}
14+
15+
#[test]
16+
fn layout_debug_shows_log2_of_alignment() {
17+
// `Debug` is not stable, but here's what it does right now
18+
let layout = Layout::from_size_align(24576, 8192).unwrap();
19+
let s = format!("{:?}", layout);
20+
assert_eq!(s, "Layout { size_: 24576, align_: 8192 (1 << 13) }");
21+
}
22+
23+
// Running this normally doesn't do much, but it's also run in Miri, which
24+
// will double-check that these are allowed by the validity invariants.
25+
#[test]
26+
fn layout_accepts_all_valid_alignments() {
27+
for align in 0..usize::BITS {
28+
let layout = Layout::from_size_align(0, 1_usize << align).unwrap();
29+
assert_eq!(layout.align(), 1_usize << align);
30+
}
31+
}

0 commit comments

Comments
 (0)