|
19 | 19 |
|
20 | 20 | use core::{marker::PhantomData, mem::ManuallyDrop}; |
21 | 21 |
|
| 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 | + |
22 | 26 | /// A compile-time check that should be one particular value. |
23 | 27 | pub trait ShouldBe<const VALUE: bool> {} |
24 | 28 |
|
@@ -61,6 +65,133 @@ impl<T, U> MaxAlignsOf<T, U> { |
61 | 65 | } |
62 | 66 | } |
63 | 67 |
|
| 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 | + |
64 | 195 | /// Does the struct type `$t` have padding? |
65 | 196 | /// |
66 | 197 | /// `$ts` is the list of the type of every field in `$t`. `$t` must be a |
@@ -276,6 +407,32 @@ mod tests { |
276 | 407 | assert_t_align_gteq_u_align!(u8, AU64, false); |
277 | 408 | } |
278 | 409 |
|
| 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 | + |
279 | 436 | #[test] |
280 | 437 | fn test_struct_has_padding() { |
281 | 438 | // Test that, for each provided repr, `struct_has_padding!` reports the |
|
0 commit comments