|  | 
| 1 | 1 | //@ compile-flags: --crate-type=lib | 
| 2 | 2 | //@ check-pass | 
|  | 3 | +//@ edition: 2024 | 
|  | 4 | +#![feature(const_raw_ptr_comparison)] | 
|  | 5 | +#![feature(fn_align)] | 
|  | 6 | +// Generally: | 
|  | 7 | +// For any `Some` return, `None` would also be valid, unless otherwise noted. | 
|  | 8 | +// For any `None` return, only `None` is valid, unless otherwise noted. | 
| 3 | 9 | 
 | 
| 4 |  | -#![feature( | 
| 5 |  | -    core_intrinsics, | 
| 6 |  | -    const_raw_ptr_comparison, | 
| 7 |  | -)] | 
|  | 10 | +macro_rules! do_test { | 
|  | 11 | +    ($a:expr, $b:expr, $expected:pat) => { | 
|  | 12 | +        const _: () = { | 
|  | 13 | +            let a: *const _ = $a; | 
|  | 14 | +            let b: *const _ = $b; | 
|  | 15 | +            assert!(matches!(<*const u8>::guaranteed_eq(a.cast(), b.cast()), $expected)); | 
|  | 16 | +        }; | 
|  | 17 | +    }; | 
|  | 18 | +} | 
| 8 | 19 | 
 | 
| 9 |  | -const FOO: &usize = &42; | 
|  | 20 | +#[repr(align(2))] | 
|  | 21 | +struct T(#[allow(unused)] u16); | 
| 10 | 22 | 
 | 
| 11 |  | -macro_rules! check { | 
| 12 |  | -    (eq, $a:expr, $b:expr) => { | 
| 13 |  | -        pub const _: () = | 
| 14 |  | -            assert!(std::intrinsics::ptr_guaranteed_cmp($a as *const u8, $b as *const u8) == 1); | 
| 15 |  | -    }; | 
| 16 |  | -    (ne, $a:expr, $b:expr) => { | 
| 17 |  | -        pub const _: () = | 
| 18 |  | -            assert!(std::intrinsics::ptr_guaranteed_cmp($a as *const u8, $b as *const u8) == 0); | 
|  | 23 | +#[repr(align(2))] | 
|  | 24 | +struct AlignedZst; | 
|  | 25 | + | 
|  | 26 | +static A: T = T(42); | 
|  | 27 | +static B: T = T(42); | 
|  | 28 | +static mut MUT_STATIC: T = T(42); | 
|  | 29 | +static ZST: () = (); | 
|  | 30 | +static ALIGNED_ZST: AlignedZst = AlignedZst; | 
|  | 31 | +static LARGE_WORD_ALIGNED: [usize; 2] = [0, 1]; | 
|  | 32 | +static mut MUT_LARGE_WORD_ALIGNED: [usize; 2] = [0, 1]; | 
|  | 33 | + | 
|  | 34 | +const FN_PTR: *const () = { | 
|  | 35 | +    fn foo() {} | 
|  | 36 | +    unsafe { std::mem::transmute(foo as fn()) } | 
|  | 37 | +}; | 
|  | 38 | + | 
|  | 39 | +const ALIGNED_FN_PTR: *const () = { | 
|  | 40 | +    #[rustc_align(2)] | 
|  | 41 | +    fn aligned_foo() {} | 
|  | 42 | +    unsafe { std::mem::transmute(aligned_foo as fn()) } | 
|  | 43 | +}; | 
|  | 44 | + | 
|  | 45 | +trait Trait { | 
|  | 46 | +    #[allow(unused)] | 
|  | 47 | +    fn method(&self) -> u8; | 
|  | 48 | +} | 
|  | 49 | +impl Trait for u32 { | 
|  | 50 | +    fn method(&self) -> u8 { 1 } | 
|  | 51 | +} | 
|  | 52 | +impl Trait for i32 { | 
|  | 53 | +    fn method(&self) -> u8 { 2 } | 
|  | 54 | +} | 
|  | 55 | + | 
|  | 56 | +const VTABLE_PTR_1: *const () = { | 
|  | 57 | +    let [_data, vtable] = unsafe { | 
|  | 58 | +        std::mem::transmute::<&dyn Trait, [*const (); 2]>(&42_u32 as &dyn Trait) | 
| 19 | 59 |     }; | 
| 20 |  | -    (!, $a:expr, $b:expr) => { | 
| 21 |  | -        pub const _: () = | 
| 22 |  | -            assert!(std::intrinsics::ptr_guaranteed_cmp($a as *const u8, $b as *const u8) == 2); | 
|  | 60 | +    vtable | 
|  | 61 | +}; | 
|  | 62 | +const VTABLE_PTR_2: *const () = { | 
|  | 63 | +    let [_data, vtable] = unsafe { | 
|  | 64 | +        std::mem::transmute::<&dyn Trait, [*const (); 2]>(&42_i32 as &dyn Trait) | 
| 23 | 65 |     }; | 
| 24 |  | -} | 
|  | 66 | +    vtable | 
|  | 67 | +}; | 
| 25 | 68 | 
 | 
| 26 |  | -check!(eq, 0, 0); | 
| 27 |  | -check!(ne, 0, 1); | 
| 28 |  | -check!(ne, FOO as *const _, 0); | 
| 29 |  | -check!(ne, unsafe { (FOO as *const usize).offset(1) }, 0); | 
| 30 |  | -check!(ne, unsafe { (FOO as *const usize as *const u8).offset(3) }, 0); | 
|  | 69 | +// Cannot be `None`: `is_null` is stable with strong guarantees about integer-valued pointers. | 
|  | 70 | +do_test!(0 as *const u8, 0 as *const u8, Some(true)); | 
|  | 71 | +do_test!(0 as *const u8, 1 as *const u8, Some(false)); | 
| 31 | 72 | 
 | 
| 32 |  | -// We want pointers to be equal to themselves, but aren't checking this yet because | 
| 33 |  | -// there are some open questions (e.g. whether function pointers to the same function | 
| 34 |  | -// compare equal: they don't necessarily do at runtime). | 
| 35 |  | -check!(!, FOO as *const _, FOO as *const _); | 
|  | 73 | +// Integer-valued pointers can always be compared. | 
|  | 74 | +do_test!(1 as *const u8, 1 as *const u8, Some(true)); | 
|  | 75 | +do_test!(1 as *const u8, 2 as *const u8, Some(false)); | 
|  | 76 | + | 
|  | 77 | +// Cannot be `None`: `static`s' addresses, references, (and within and one-past-the-end of those), | 
|  | 78 | +// and `fn` pointers cannot be null, and `is_null` is stable with strong guarantees, and | 
|  | 79 | +// `is_null` is implemented using `guaranteed_cmp`. | 
|  | 80 | +do_test!(&A, 0 as *const u8, Some(false)); | 
|  | 81 | +do_test!((&raw const A).cast::<u8>().wrapping_add(1), 0 as *const u8, Some(false)); | 
|  | 82 | +do_test!((&raw const A).wrapping_add(1), 0 as *const u8, Some(false)); | 
|  | 83 | +do_test!(&ZST, 0 as *const u8, Some(false)); | 
|  | 84 | +do_test!(&(), 0 as *const u8, Some(false)); | 
|  | 85 | +do_test!(const { &() }, 0 as *const u8, Some(false)); | 
|  | 86 | +do_test!(FN_PTR, 0 as *const u8, Some(false)); | 
|  | 87 | + | 
|  | 88 | +// This pointer is out-of-bounds, but still cannot be equal to 0 because of alignment. | 
|  | 89 | +do_test!((&raw const A).cast::<u8>().wrapping_add(size_of::<T>() + 1), 0 as *const u8, Some(false)); | 
| 36 | 90 | 
 | 
| 37 | 91 | // aside from 0, these pointers might end up pretty much anywhere. | 
| 38 |  | -check!(!, FOO as *const _, 1); // this one could be `ne` by taking into account alignment | 
| 39 |  | -check!(!, FOO as *const _, 1024); | 
|  | 92 | +do_test!(&A, align_of::<T>() as *const u8, None); | 
|  | 93 | +do_test!((&raw const A).wrapping_byte_add(1), (align_of::<T>() + 1) as *const u8, None); | 
|  | 94 | + | 
|  | 95 | +// except that they must still be aligned | 
|  | 96 | +do_test!(&A, 1 as *const u8, Some(false)); | 
|  | 97 | +do_test!((&raw const A).wrapping_byte_add(1), align_of::<T>() as *const u8, Some(false)); | 
|  | 98 | + | 
|  | 99 | +// If `ptr.wrapping_sub(int)` cannot be null (because it is in-bounds or one-past-the-end of | 
|  | 100 | +// `ptr`'s allocation, or because it is misaligned from `ptr`'s allocation), then we know that | 
|  | 101 | +// `ptr != int`, even if `ptr` itself is out-of-bounds or one-past-the-end of its allocation. | 
|  | 102 | +do_test!((&raw const A).wrapping_byte_add(1), 1 as *const u8, Some(false)); | 
|  | 103 | +do_test!((&raw const A).wrapping_byte_add(2), 2 as *const u8, Some(false)); | 
|  | 104 | +do_test!((&raw const A).wrapping_byte_add(3), 1 as *const u8, Some(false)); | 
|  | 105 | +do_test!((&raw const ZST).wrapping_byte_add(1), 1 as *const u8, Some(false)); | 
|  | 106 | +do_test!(VTABLE_PTR_1.wrapping_byte_add(1), 1 as *const u8, Some(false)); | 
|  | 107 | +do_test!(FN_PTR.wrapping_byte_add(1), 1 as *const u8, Some(false)); | 
|  | 108 | +do_test!(&A, size_of::<T>().wrapping_neg() as *const u8, Some(false)); | 
|  | 109 | +do_test!(&LARGE_WORD_ALIGNED, size_of::<usize>().wrapping_neg() as *const u8, Some(false)); | 
|  | 110 | +// (`ptr - int != 0` due to misalignment) | 
|  | 111 | +do_test!((&raw const A).wrapping_byte_add(2), 1 as *const u8, Some(false)); | 
|  | 112 | +do_test!((&raw const ALIGNED_ZST).wrapping_byte_add(2), 1 as *const u8, Some(false)); | 
| 40 | 113 | 
 | 
| 41 | 114 | // When pointers go out-of-bounds, they *might* become null, so these comparions cannot work. | 
| 42 |  | -check!(!, unsafe { (FOO as *const usize).wrapping_add(2) }, 0); | 
| 43 |  | -check!(!, unsafe { (FOO as *const usize).wrapping_sub(1) }, 0); | 
|  | 115 | +do_test!((&raw const A).wrapping_add(2), 0 as *const u8, None); | 
|  | 116 | +do_test!((&raw const A).wrapping_sub(1), 0 as *const u8, None); | 
|  | 117 | + | 
|  | 118 | +// Statics cannot be duplicated | 
|  | 119 | +do_test!(&A, &A, Some(true)); | 
|  | 120 | + | 
|  | 121 | +// Two non-ZST statics cannot have the same address | 
|  | 122 | +do_test!(&A, &B, Some(false)); | 
|  | 123 | +do_test!(&A, &raw const MUT_STATIC, Some(false)); | 
|  | 124 | + | 
|  | 125 | +// One-past-the-end of one static can be equal to the address of another static. | 
|  | 126 | +do_test!(&A, (&raw const B).wrapping_add(1), None); | 
|  | 127 | + | 
|  | 128 | +// Cannot know if ZST static is at the same address with anything non-null (if alignment allows). | 
|  | 129 | +do_test!(&A, &ZST, None); | 
|  | 130 | +do_test!(&A, &ALIGNED_ZST, None); | 
|  | 131 | + | 
|  | 132 | +// Unclear if ZST statics can be placed "in the middle of" non-ZST statics. | 
|  | 133 | +// For now, we conservatively say they could, and return None here. | 
|  | 134 | +do_test!(&ZST, (&raw const A).wrapping_byte_add(1), None); | 
|  | 135 | + | 
|  | 136 | +// As per https://doc.rust-lang.org/nightly/reference/items/static-items.html#r-items.static.storage-disjointness | 
|  | 137 | +// immutable statics are allowed to overlap with const items and promoteds. | 
|  | 138 | +do_test!(&A, &T(42), None); | 
|  | 139 | +do_test!(&A, const { &T(42) }, None); | 
|  | 140 | +do_test!(&A, { const X: T = T(42); &X }, None); | 
|  | 141 | + | 
|  | 142 | +// These could return Some(false), since only immutable statics can overlap with const items | 
|  | 143 | +// and promoteds. | 
|  | 144 | +do_test!(&raw const MUT_STATIC, &T(42), None); | 
|  | 145 | +do_test!(&raw const MUT_STATIC, const { &T(42) }, None); | 
|  | 146 | +do_test!(&raw const MUT_STATIC, { const X: T = T(42); &X }, None); | 
|  | 147 | + | 
|  | 148 | +// An odd offset from a 2-aligned allocation can never be equal to an even offset from a | 
|  | 149 | +// 2-aligned allocation, even if the offsets are out-of-bounds. | 
|  | 150 | +do_test!(&A, (&raw const B).wrapping_byte_add(1), Some(false)); | 
|  | 151 | +do_test!(&A, (&raw const B).wrapping_byte_add(5), Some(false)); | 
|  | 152 | +do_test!(&A, (&raw const ALIGNED_ZST).wrapping_byte_add(1), Some(false)); | 
|  | 153 | +do_test!(&ALIGNED_ZST, (&raw const A).wrapping_byte_add(1), Some(false)); | 
|  | 154 | +do_test!(&A, (&T(42) as *const T).wrapping_byte_add(1), Some(false)); | 
|  | 155 | +do_test!(&A, (const { &T(42) } as *const T).wrapping_byte_add(1), Some(false)); | 
|  | 156 | +do_test!(&A, ({ const X: T = T(42); &X } as *const T).wrapping_byte_add(1), Some(false)); | 
|  | 157 | + | 
|  | 158 | +// We could return `Some(false)` for these, as pointers to different statics can never be equal if | 
|  | 159 | +// that would require the statics to overlap, even if the pointers themselves are offset out of | 
|  | 160 | +// bounds or one-past-the-end. We currently only check strictly in-bounds pointers when comparing | 
|  | 161 | +// pointers to different statics, however. | 
|  | 162 | +do_test!((&raw const A).wrapping_add(1), (&raw const B).wrapping_add(1), None); | 
|  | 163 | +do_test!( | 
|  | 164 | +    (&raw const LARGE_WORD_ALIGNED).cast::<usize>().wrapping_add(2), | 
|  | 165 | +    (&raw const MUT_LARGE_WORD_ALIGNED).cast::<usize>().wrapping_add(1), | 
|  | 166 | +    None | 
|  | 167 | +); | 
|  | 168 | + | 
|  | 169 | +// Pointers into the same static are equal if and only if their offset is the same, | 
|  | 170 | +// even if either is out-of-bounds. | 
|  | 171 | +do_test!(&A, &A, Some(true)); | 
|  | 172 | +do_test!(&A, &A.0, Some(true)); | 
|  | 173 | +do_test!(&A, (&raw const A).wrapping_byte_add(1), Some(false)); | 
|  | 174 | +do_test!(&A, (&raw const A).wrapping_byte_add(2), Some(false)); | 
|  | 175 | +do_test!(&A, (&raw const A).wrapping_byte_add(51), Some(false)); | 
|  | 176 | +do_test!((&raw const A).wrapping_byte_add(51), (&raw const A).wrapping_byte_add(51), Some(true)); | 
|  | 177 | + | 
|  | 178 | +// Pointers to the same fn may be unequal, since `fn`s can be duplicated. | 
|  | 179 | +do_test!(FN_PTR, FN_PTR, None); | 
|  | 180 | +do_test!(ALIGNED_FN_PTR, ALIGNED_FN_PTR, None); | 
|  | 181 | + | 
|  | 182 | +// Pointers to different fns may be equal, since `fn`s can be deduplicated. | 
|  | 183 | +do_test!(FN_PTR, ALIGNED_FN_PTR, None); | 
|  | 184 | + | 
|  | 185 | +// Pointers to the same vtable may be unequal, since vtables can be duplicated. | 
|  | 186 | +do_test!(VTABLE_PTR_1, VTABLE_PTR_1, None); | 
|  | 187 | + | 
|  | 188 | +// Pointers to different vtables may be equal, since vtables can be deduplicated. | 
|  | 189 | +do_test!(VTABLE_PTR_1, VTABLE_PTR_2, None); | 
|  | 190 | + | 
|  | 191 | +// Function pointers to aligned function allocations are not necessarily actually aligned, | 
|  | 192 | +// due to platform-specific semantics. | 
|  | 193 | +// See https://github.com/rust-lang/rust/issues/144661 | 
|  | 194 | +// FIXME: This could return `Some` on platforms where function pointers' addresses actually | 
|  | 195 | +// correspond to function addresses including alignment, or on platforms where all functions | 
|  | 196 | +// are aligned to some amount (e.g. ARM where a32 function pointers are at least 4-aligned, | 
|  | 197 | +// and t32 function pointers are 2-aligned-offset-by-1). | 
|  | 198 | +do_test!(ALIGNED_FN_PTR, ALIGNED_FN_PTR.wrapping_byte_offset(1), None); | 
|  | 199 | + | 
|  | 200 | +// Conservatively say we don't know. | 
|  | 201 | +do_test!(FN_PTR, VTABLE_PTR_1, None); | 
|  | 202 | +do_test!((&raw const LARGE_WORD_ALIGNED).cast::<usize>().wrapping_add(1), VTABLE_PTR_1, None); | 
|  | 203 | +do_test!((&raw const MUT_LARGE_WORD_ALIGNED).cast::<usize>().wrapping_add(1), VTABLE_PTR_1, None); | 
|  | 204 | +do_test!((&raw const LARGE_WORD_ALIGNED).cast::<usize>().wrapping_add(1), FN_PTR, None); | 
|  | 205 | +do_test!((&raw const MUT_LARGE_WORD_ALIGNED).cast::<usize>().wrapping_add(1), FN_PTR, None); | 
0 commit comments