Skip to content

Commit c40ead4

Browse files
joshlfjswrenn
andcommitted
[macro_util] Add trailing_field_offset! macro
This macro will be used by the derive of KnownLayout for slice DSTs. Makes progress on #29 Co-authored-by: Jack Wrenn <[email protected]>
1 parent 1ab6d68 commit c40ead4

File tree

2 files changed

+176
-0
lines changed

2 files changed

+176
-0
lines changed

src/macro_util.rs

Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,10 @@
1919

2020
use core::{marker::PhantomData, mem::ManuallyDrop};
2121

22+
// remove this `cfg` when `size_of_val_raw` is stabilized
23+
#[cfg(__INTERNAL_USE_ONLY_NIGHLTY_FEATURES_IN_TESTS)]
24+
use core::ptr::{self, NonNull};
25+
2226
/// A compile-time check that should be one particular value.
2327
pub trait ShouldBe<const VALUE: bool> {}
2428

@@ -61,6 +65,133 @@ impl<T, U> MaxAlignsOf<T, U> {
6165
}
6266
}
6367

68+
const _64K: usize = 1 << 16;
69+
70+
// remove this `cfg` when `size_of_val_raw` is stabilized
71+
#[cfg(__INTERNAL_USE_ONLY_NIGHLTY_FEATURES_IN_TESTS)]
72+
#[repr(C, align(65536))]
73+
struct Aligned64kAllocation([u8; _64K]);
74+
75+
/// A pointer to an aligned allocation of size 2^16.
76+
///
77+
/// # Safety
78+
///
79+
/// `ALIGNED_64K_ALLOCATION` is guaranteed to point to the entirety of an
80+
/// allocation with size and alignment 2^16, and to have valid provenance.
81+
// remove this `cfg` when `size_of_val_raw` is stabilized
82+
#[cfg(__INTERNAL_USE_ONLY_NIGHLTY_FEATURES_IN_TESTS)]
83+
pub const ALIGNED_64K_ALLOCATION: NonNull<[u8]> = {
84+
const REF: &Aligned64kAllocation = &Aligned64kAllocation([0; _64K]);
85+
let ptr: *const Aligned64kAllocation = REF;
86+
let ptr: *const [u8] = ptr::slice_from_raw_parts(ptr.cast(), _64K);
87+
// SAFETY:
88+
// - `ptr` is derived from a Rust reference, which is guaranteed to be
89+
// non-null.
90+
// - `ptr` is derived from an `&Aligned64kAllocation`, which has size and
91+
// alignment `_64K` as promised. Its length is initialized to `_64K`,
92+
// which means that it refers to the entire allocation.
93+
// - `ptr` is derived from a Rust reference, which is guaranteed to have
94+
// valid provenance.
95+
//
96+
// TODO(#429): Once `NonNull::new_unchecked` docs document that it preserves
97+
// provenance, cite those docs.
98+
// TODO: Replace this `as` with `ptr.cast_mut()` once our MSRV >= 1.65
99+
#[allow(clippy::as_conversions)]
100+
unsafe {
101+
NonNull::new_unchecked(ptr as *mut _)
102+
}
103+
};
104+
105+
/// Computes the offset of the base of the field `$trailing_field_name` within
106+
/// the type `$ty`.
107+
///
108+
/// `trailing_field_offset!` produces code which is valid in a `const` context.
109+
// remove this `cfg` when `size_of_val_raw` is stabilized
110+
#[cfg(__INTERNAL_USE_ONLY_NIGHLTY_FEATURES_IN_TESTS)]
111+
#[doc(hidden)] // `#[macro_export]` bypasses this module's `#[doc(hidden)]`.
112+
#[macro_export]
113+
macro_rules! trailing_field_offset {
114+
($ty:ty, $trailing_field_name:tt) => {{
115+
let min_size = {
116+
let zero_elems: *const [()] = ::core::ptr::slice_from_raw_parts(
117+
::core::ptr::NonNull::<()>::dangling().as_ptr().cast_const(),
118+
0,
119+
);
120+
// SAFETY:
121+
// - If `$ty` is `Sized`, `size_of_val_raw` is always safe to call.
122+
// - Otherwise:
123+
// - If `$ty` is not a slice DST, this pointer conversion will
124+
// fail due to "mismatched vtable kinds", and compilation will
125+
// fail.
126+
// - If `$ty` is a slice DST, the safety requirement is that "the
127+
// length of the slice tail must be an initialized integer, and
128+
// the size of the entire value (dynamic tail length +
129+
// statically sized prefix) must fit in isize." The length is
130+
// initialized to 0 above, and Rust guarantees that no type's
131+
// minimum size may overflow `isize`. [1]
132+
//
133+
// [1] TODO(#429): Citation for this?
134+
unsafe {
135+
#[allow(clippy::as_conversions)]
136+
::core::mem::size_of_val_raw(zero_elems as *const $ty)
137+
}
138+
};
139+
140+
assert!(min_size <= _64K);
141+
142+
let ptr = ALIGNED_64K_ALLOCATION.as_ptr() as *const $ty;
143+
144+
// SAFETY:
145+
// - Thanks to the preceding `assert!`, we know that the value with zero
146+
// elements fits in `_64K` bytes, and thus in the allocation addressed
147+
// by `ALIGNED_64K_ALLOCATION`. The offset of the trailing field is
148+
// guaranteed to be no larger than this size, so this field projection
149+
// is guaranteed to remain in-bounds of its allocation.
150+
// - Because the minimum size is no larger than `_64K` bytes, and
151+
// because an object's size must always be a multiple of its alignment
152+
// [1], we know that `$ty`'s alignment is no larger than `_64K`. The
153+
// allocation addressed by `ALIGNED_64K_ALLOCATION` is guaranteed to
154+
// be aligned to `_64K`, so `ptr` is guaranteed to satisfy `$ty`'s
155+
// alignment.
156+
//
157+
// Note that, as of [2], this requirement is technically unnecessary,
158+
// but no harm in guaranteeing it anyway.
159+
//
160+
// [1] Per https://doc.rust-lang.org/reference/type-layout.html:
161+
//
162+
// The size of a value is always a multiple of its alignment.
163+
//
164+
// [2] https://github.com/rust-lang/reference/pull/1387
165+
let field = unsafe { ::core::ptr::addr_of!((*ptr).$trailing_field_name) };
166+
// SAFETY:
167+
// - Both `ptr` and `field` are derived from the same allocated object.
168+
// - By the preceding safety comment, `field` is in bounds of that
169+
// allocated object.
170+
// - The distance, in bytes, between `ptr` and `field` is required to be
171+
// a multiple of the size of `u8`, which is trivially true because
172+
// `u8`'s size is 1.
173+
// - The distance, in bytes, cannot overflow `isize`. This is guaranteed
174+
// because no allocated object can have a size larger than can fit in
175+
// `isize`. [1]
176+
// - The distance being in-bounds cannot rely on wrapping around the
177+
// address space. This is guaranteed because the same is guaranteed of
178+
// allocated objects. [1]
179+
//
180+
// [1] TODO(#429), TODO(https://github.com/rust-lang/rust/pull/116675):
181+
// Once these are guaranteed in the Reference, cite it.
182+
let offset = unsafe { field.cast::<u8>().offset_from(ptr.cast::<u8>()) };
183+
// Guaranteed not to be lossy: `field` comes after `ptr`, so the offset
184+
// from `ptr` to `field` is guaranteed to be positive.
185+
assert!(offset >= 0);
186+
Some(
187+
#[allow(clippy::as_conversions)]
188+
{
189+
offset as usize
190+
},
191+
)
192+
}};
193+
}
194+
64195
/// Does the struct type `$t` have padding?
65196
///
66197
/// `$ts` is the list of the type of every field in `$t`. `$t` must be a
@@ -276,6 +407,32 @@ mod tests {
276407
assert_t_align_gteq_u_align!(u8, AU64, false);
277408
}
278409

410+
// remove this `cfg` when `size_of_val_raw` is stabilized
411+
#[cfg(__INTERNAL_USE_ONLY_NIGHLTY_FEATURES_IN_TESTS)]
412+
#[test]
413+
fn test_trailing_field_offset() {
414+
macro_rules! test {
415+
(#[$cfg:meta] ($($ts:ty),* ; $trailing_field_ty:ty) => $expect:expr) => {{
416+
#[$cfg]
417+
struct Test($($ts,)* $trailing_field_ty);
418+
assert_eq!(test!(@offset $($ts),* ; $trailing_field_ty), $expect);
419+
}};
420+
(#[$cfg:meta] $(#[$cfgs:meta])* ($($ts:ty),* ; $trailing_field_ty:ty) => $expect:expr) => {
421+
test!(#[$cfg] ($($ts),* ; $trailing_field_ty) => $expect);
422+
test!($(#[$cfgs])* ($($ts),* ; $trailing_field_ty) => $expect);
423+
};
424+
(@offset ; $_trailing:ty) => { trailing_field_offset!(Test, 0) };
425+
(@offset $_t:ty ; $_trailing:ty) => { trailing_field_offset!(Test, 1) };
426+
}
427+
428+
test!(#[repr(C)] #[repr(transparent)] #[repr(packed)] #[repr(C)] (; u8) => Some(0));
429+
test!(#[repr(C)] #[repr(packed)] #[repr(C)] (u8; u8) => Some(1));
430+
test!(#[repr(C)] (u8; [AU64]) => Some(8));
431+
test!(#[repr(C)] (u8; Nested<u8, AU64>) => Some(8));
432+
433+
// TODO: More tests.
434+
}
435+
279436
#[test]
280437
fn test_struct_has_padding() {
281438
// Test that, for each provided repr, `struct_has_padding!` reports the

src/util.rs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -601,6 +601,25 @@ pub(crate) mod testutil {
601601
Display::fmt(&self.0, f)
602602
}
603603
}
604+
605+
#[derive(
606+
KnownLayout,
607+
FromZeroes,
608+
FromBytes,
609+
Eq,
610+
PartialEq,
611+
Ord,
612+
PartialOrd,
613+
Default,
614+
Debug,
615+
Copy,
616+
Clone,
617+
)]
618+
#[repr(C)]
619+
pub(crate) struct Nested<T, U>{
620+
_t: T,
621+
_u: U,
622+
}
604623
}
605624

606625
#[cfg(test)]

0 commit comments

Comments
 (0)