Skip to content

Commit e09b620

Browse files
committed
Add fuzzy pointer comparison intrinsics
1 parent 9245ba8 commit e09b620

File tree

14 files changed

+260
-22
lines changed

14 files changed

+260
-22
lines changed

src/libcore/intrinsics.rs

+10
Original file line numberDiff line numberDiff line change
@@ -1948,6 +1948,16 @@ extern "rust-intrinsic" {
19481948
#[cfg(not(bootstrap))]
19491949
#[lang = "count_code_region"]
19501950
pub fn count_code_region(index: u32);
1951+
1952+
/// See documentation of `<*const T>::guaranteed_eq` for details.
1953+
#[rustc_const_unstable(feature = "const_raw_ptr_comparison", issue = "53020")]
1954+
#[cfg(not(bootstrap))]
1955+
pub fn ptr_guaranteed_eq<T>(ptr: *const T, other: *const T) -> bool;
1956+
1957+
/// See documentation of `<*const T>::guaranteed_ne` for details.
1958+
#[rustc_const_unstable(feature = "const_raw_ptr_comparison", issue = "53020")]
1959+
#[cfg(not(bootstrap))]
1960+
pub fn ptr_guaranteed_ne<T>(ptr: *const T, other: *const T) -> bool;
19511961
}
19521962

19531963
// Some functions are defined here because they accidentally got made

src/libcore/lib.rs

+1
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,7 @@
8787
#![feature(const_generics)]
8888
#![feature(const_ptr_offset)]
8989
#![feature(const_ptr_offset_from)]
90+
#![cfg_attr(not(bootstrap), feature(const_raw_ptr_comparison))]
9091
#![feature(const_result)]
9192
#![feature(const_slice_from_raw_parts)]
9293
#![feature(const_slice_ptr_len)]

src/libcore/ptr/const_ptr.rs

+66
Original file line numberDiff line numberDiff line change
@@ -295,6 +295,72 @@ impl<T: ?Sized> *const T {
295295
intrinsics::ptr_offset_from(self, origin)
296296
}
297297

298+
/// Returns whether two pointers are guaranteed equal.
299+
///
300+
/// At runtime this function behaves like `self == other`.
301+
/// However, in some contexts (e.g., compile-time evaluation),
302+
/// it is not always possible to determine equality of two pointers, so this function may
303+
/// spuriously return `false` for pointers that later actually turn out to be equal.
304+
/// But when it returns `true`, the pointers are guaranteed to be equal.
305+
///
306+
/// This function is the mirror of [`guaranteed_ne`], but not its inverse. There are pointer
307+
/// comparisons for which both functions return `false`.
308+
///
309+
/// [`guaranteed_ne`]: #method.guaranteed_ne
310+
///
311+
/// The return value may change depending on the compiler version and unsafe code may not
312+
/// rely on the result of this function for soundness. It is suggested to only use this function
313+
/// for performance optimizations where spurious `false` return values by this function do not
314+
/// affect the outcome, but just the performance.
315+
/// The consequences of using this method to make runtime and compile-time code behave
316+
/// differently have not been explored. This method should not be used to introduce such
317+
/// differences, and it should also not be stabilized before we have a better understanding
318+
/// of this issue.
319+
/// ```
320+
#[unstable(feature = "const_raw_ptr_comparison", issue = "53020")]
321+
#[rustc_const_unstable(feature = "const_raw_ptr_comparison", issue = "53020")]
322+
#[inline]
323+
#[cfg(not(bootstrap))]
324+
pub const fn guaranteed_eq(self, other: *const T) -> bool
325+
where
326+
T: Sized,
327+
{
328+
intrinsics::ptr_guaranteed_eq(self, other)
329+
}
330+
331+
/// Returns whether two pointers are guaranteed not equal.
332+
///
333+
/// At runtime this function behaves like `self != other`.
334+
/// However, in some contexts (e.g., compile-time evaluation),
335+
/// it is not always possible to determine the inequality of two pointers, so this function may
336+
/// spuriously return `false` for pointers that later actually turn out to be inequal.
337+
/// But when it returns `true`, the pointers are guaranteed to be inequal.
338+
///
339+
/// This function is the mirror of [`guaranteed_eq`], but not its inverse. There are pointer
340+
/// comparisons for which both functions return `false`.
341+
///
342+
/// [`guaranteed_eq`]: #method.guaranteed_eq
343+
///
344+
/// The return value may change depending on the compiler version and unsafe code may not
345+
/// rely on the result of this function for soundness. It is suggested to only use this function
346+
/// for performance optimizations where spurious `false` return values by this function do not
347+
/// affect the outcome, but just the performance.
348+
/// The consequences of using this method to make runtime and compile-time code behave
349+
/// differently have not been explored. This method should not be used to introduce such
350+
/// differences, and it should also not be stabilized before we have a better understanding
351+
/// of this issue.
352+
/// ```
353+
#[unstable(feature = "const_raw_ptr_comparison", issue = "53020")]
354+
#[rustc_const_unstable(feature = "const_raw_ptr_comparison", issue = "53020")]
355+
#[inline]
356+
#[cfg(not(bootstrap))]
357+
pub const fn guaranteed_ne(self, other: *const T) -> bool
358+
where
359+
T: Sized,
360+
{
361+
intrinsics::ptr_guaranteed_ne(self, other)
362+
}
363+
298364
/// Calculates the distance between two pointers. The returned value is in
299365
/// units of T: the distance in bytes is divided by `mem::size_of::<T>()`.
300366
///

src/libcore/ptr/mut_ptr.rs

+66
Original file line numberDiff line numberDiff line change
@@ -273,6 +273,72 @@ impl<T: ?Sized> *mut T {
273273
if self.is_null() { None } else { Some(&mut *self) }
274274
}
275275

276+
/// Returns whether two pointers are guaranteed equal.
277+
///
278+
/// At runtime this function behaves like `self == other`.
279+
/// However, in some contexts (e.g., compile-time evaluation),
280+
/// it is not always possible to determine equality of two pointers, so this function may
281+
/// spuriously return `false` for pointers that later actually turn out to be equal.
282+
/// But when it returns `true`, the pointers are guaranteed to be equal.
283+
///
284+
/// This function is the mirror of [`guaranteed_ne`], but not its inverse. There are pointer
285+
/// comparisons for which both functions return `false`.
286+
///
287+
/// [`guaranteed_ne`]: #method.guaranteed_ne
288+
///
289+
/// The return value may change depending on the compiler version and unsafe code may not
290+
/// rely on the result of this function for soundness. It is suggested to only use this function
291+
/// for performance optimizations where spurious `false` return values by this function do not
292+
/// affect the outcome, but just the performance.
293+
/// The consequences of using this method to make runtime and compile-time code behave
294+
/// differently have not been explored. This method should not be used to introduce such
295+
/// differences, and it should also not be stabilized before we have a better understanding
296+
/// of this issue.
297+
/// ```
298+
#[unstable(feature = "const_raw_ptr_comparison", issue = "53020")]
299+
#[rustc_const_unstable(feature = "const_raw_ptr_comparison", issue = "53020")]
300+
#[inline]
301+
#[cfg(not(bootstrap))]
302+
pub const fn guaranteed_eq(self, other: *mut T) -> bool
303+
where
304+
T: Sized,
305+
{
306+
intrinsics::ptr_guaranteed_eq(self as *const _, other as *const _)
307+
}
308+
309+
/// Returns whether two pointers are guaranteed not equal.
310+
///
311+
/// At runtime this function behaves like `self != other`.
312+
/// However, in some contexts (e.g., compile-time evaluation),
313+
/// it is not always possible to determine the inequality of two pointers, so this function may
314+
/// spuriously return `false` for pointers that later actually turn out to be inequal.
315+
/// But when it returns `true`, the pointers are guaranteed to be inequal.
316+
///
317+
/// This function is the mirror of [`guaranteed_eq`], but not its inverse. There are pointer
318+
/// comparisons for which both functions return `false`.
319+
///
320+
/// [`guaranteed_eq`]: #method.guaranteed_eq
321+
///
322+
/// The return value may change depending on the compiler version and unsafe code may not
323+
/// rely on the result of this function for soundness. It is suggested to only use this function
324+
/// for performance optimizations where spurious `false` return values by this function do not
325+
/// affect the outcome, but just the performance.
326+
/// The consequences of using this method to make runtime and compile-time code behave
327+
/// differently have not been explored. This method should not be used to introduce such
328+
/// differences, and it should also not be stabilized before we have a better understanding
329+
/// of this issue.
330+
/// ```
331+
#[unstable(feature = "const_raw_ptr_comparison", issue = "53020")]
332+
#[rustc_const_unstable(feature = "const_raw_ptr_comparison", issue = "53020")]
333+
#[inline]
334+
#[cfg(not(bootstrap))]
335+
pub const unsafe fn guaranteed_ne(self, other: *mut T) -> bool
336+
where
337+
T: Sized,
338+
{
339+
intrinsics::ptr_guaranteed_ne(self as *const _, other as *const _)
340+
}
341+
276342
/// Calculates the distance between two pointers. The returned value is in
277343
/// units of T: the distance in bytes is divided by `mem::size_of::<T>()`.
278344
///

src/libcore/slice/mod.rs

+48-2
Original file line numberDiff line numberDiff line change
@@ -5946,7 +5946,8 @@ where
59465946
}
59475947
}
59485948

5949-
// Use an equal-pointer optimization when types are `Eq`
5949+
// Remove after boostrap bump
5950+
#[cfg(bootstrap)]
59505951
impl<A> SlicePartialEq<A> for [A]
59515952
where
59525953
A: PartialEq<A> + Eq,
@@ -5964,7 +5965,8 @@ where
59645965
}
59655966
}
59665967

5967-
// Use memcmp for bytewise equality when the types allow
5968+
// Remove after boostrap bump
5969+
#[cfg(bootstrap)]
59685970
impl<A> SlicePartialEq<A> for [A]
59695971
where
59705972
A: PartialEq<A> + BytewiseEquality,
@@ -5983,6 +5985,50 @@ where
59835985
}
59845986
}
59855987

5988+
// Use an equal-pointer optimization when types are `Eq`
5989+
#[cfg(not(bootstrap))]
5990+
impl<A> SlicePartialEq<A> for [A]
5991+
where
5992+
A: PartialEq<A> + Eq,
5993+
{
5994+
default fn equal(&self, other: &[A]) -> bool {
5995+
if self.len() != other.len() {
5996+
return false;
5997+
}
5998+
5999+
// While performance would suffer if `guaranteed_eq` just returned `false`
6000+
// for all arguments, correctness and return value of this function are not affected.
6001+
if self.as_ptr().guaranteed_eq(other.as_ptr()) {
6002+
return true;
6003+
}
6004+
6005+
self.iter().zip(other.iter()).all(|(x, y)| x == y)
6006+
}
6007+
}
6008+
6009+
// Use memcmp for bytewise equality when the types allow
6010+
#[cfg(not(bootstrap))]
6011+
impl<A> SlicePartialEq<A> for [A]
6012+
where
6013+
A: PartialEq<A> + BytewiseEquality,
6014+
{
6015+
fn equal(&self, other: &[A]) -> bool {
6016+
if self.len() != other.len() {
6017+
return false;
6018+
}
6019+
6020+
// While performance would suffer if `guaranteed_eq` just returned `false`
6021+
// for all arguments, correctness and return value of this function are not affected.
6022+
if self.as_ptr().guaranteed_eq(other.as_ptr()) {
6023+
return true;
6024+
}
6025+
unsafe {
6026+
let size = mem::size_of_val(self);
6027+
memcmp(self.as_ptr() as *const u8, other.as_ptr() as *const u8, size) == 0
6028+
}
6029+
}
6030+
}
6031+
59866032
#[doc(hidden)]
59876033
// intermediate trait for specialization of slice's PartialOrd
59886034
trait SlicePartialOrd: Sized {

src/librustc_codegen_llvm/intrinsic.rs

+13-1
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ use log::debug;
1212
use rustc_ast::ast;
1313
use rustc_codegen_ssa::base::{compare_simd_types, to_immediate, wants_msvc_seh};
1414
use rustc_codegen_ssa::common::span_invalid_monomorphization_error;
15-
use rustc_codegen_ssa::common::TypeKind;
15+
use rustc_codegen_ssa::common::{IntPredicate, TypeKind};
1616
use rustc_codegen_ssa::glue;
1717
use rustc_codegen_ssa::mir::operand::{OperandRef, OperandValue};
1818
use rustc_codegen_ssa::mir::place::PlaceRef;
@@ -731,6 +731,18 @@ impl IntrinsicCallMethods<'tcx> for Builder<'a, 'll, 'tcx> {
731731
return;
732732
}
733733

734+
"ptr_guaranteed_eq" | "ptr_guaranteed_ne" => {
735+
let a = args[0].immediate();
736+
let b = args[1].immediate();
737+
let a = self.ptrtoint(a, self.type_isize());
738+
let b = self.ptrtoint(b, self.type_isize());
739+
if name == "ptr_guaranteed_eq" {
740+
self.icmp(IntPredicate::IntEQ, a, b)
741+
} else {
742+
self.icmp(IntPredicate::IntNE, a, b)
743+
}
744+
}
745+
734746
"ptr_offset_from" => {
735747
let ty = substs.type_at(0);
736748
let pointee_size = self.size_of(ty);

src/librustc_mir/interpret/intrinsics.rs

+5
Original file line numberDiff line numberDiff line change
@@ -291,6 +291,11 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
291291
let offset_ptr = ptr.ptr_wrapping_signed_offset(offset_bytes, self);
292292
self.write_scalar(offset_ptr, dest)?;
293293
}
294+
sym::ptr_guaranteed_eq | sym::ptr_guaranteed_ne => {
295+
// FIXME: return `true` for at least some comparisons where we can reliably
296+
// determine the result of runtime (in)equality tests at compile-time.
297+
self.write_scalar(Scalar::from_bool(false), dest)?;
298+
}
294299
sym::ptr_offset_from => {
295300
let a = self.read_immediate(args[0])?.to_scalar()?;
296301
let b = self.read_immediate(args[1])?.to_scalar()?;

src/librustc_mir/transform/check_consts/ops.rs

+14-8
Original file line numberDiff line numberDiff line change
@@ -290,17 +290,23 @@ impl NonConstOp for RawPtrComparison {
290290
"pointers cannot be compared in a meaningful way during const eval.",
291291
);
292292
err.note(
293-
"It is conceptually impossible for const eval to know in all cases whether two \
294-
pointers are equal. While sometimes it is clear (the address of a static item \
295-
is never equal to the address of another static item), comparing an integer \
296-
address with any allocation's address is impossible to do at compile-time.",
293+
"see issue #53020 <https://github.com/rust-lang/rust/issues/53020> \
294+
for more information",
297295
);
298296
err.note(
299-
"That said, there's the `ptr_maybe_eq` intrinsic which returns `true` for all \
300-
comparisons where CTFE isn't sure whether two addresses are equal. The mirror \
301-
intrinsic `ptr_maybe_ne` returns `true` for all comparisons where CTFE isn't \
302-
sure whether two addresses are inequal.",
297+
"It is conceptually impossible for const eval to know in all cases whether two \
298+
pointers are equal. While sometimes it is clear (the address of a non-zst static item \
299+
is never equal to the address of another non-zst static item), comparing an integer \
300+
address with any allocation's address is impossible to do at compile-time.",
303301
);
302+
if ccx.tcx.sess.parse_sess.unstable_features.is_nightly_build() {
303+
err.note(
304+
"That said, there's the `<*const T>::guaranteed_eq` intrinsic which returns `true` \
305+
for all comparisons where CTFE is sure that two addresses are equal. The mirror \
306+
intrinsic `<*const T>::guaranteed_ne` returns `true` for all comparisons where \
307+
CTFE is sure that two addresses are inequal.",
308+
);
309+
}
304310
err.emit();
305311
}
306312
}

src/librustc_span/symbol.rs

+2
Original file line numberDiff line numberDiff line change
@@ -588,6 +588,8 @@ symbols! {
588588
proc_macro_non_items,
589589
proc_macro_path_invoc,
590590
profiler_runtime,
591+
ptr_guaranteed_eq,
592+
ptr_guaranteed_ne,
591593
ptr_offset_from,
592594
pub_restricted,
593595
pure,

src/librustc_typeck/check/intrinsic.rs

+6-3
Original file line numberDiff line numberDiff line change
@@ -74,9 +74,8 @@ pub fn intrinsic_operation_unsafety(intrinsic: &str) -> hir::Unsafety {
7474
| "wrapping_add" | "wrapping_sub" | "wrapping_mul" | "saturating_add"
7575
| "saturating_sub" | "rotate_left" | "rotate_right" | "ctpop" | "ctlz" | "cttz"
7676
| "bswap" | "bitreverse" | "discriminant_value" | "type_id" | "likely" | "unlikely"
77-
| "minnumf32" | "minnumf64" | "maxnumf32" | "maxnumf64" | "type_name" => {
78-
hir::Unsafety::Normal
79-
}
77+
| "ptr_guaranteed_eq" | "ptr_guaranteed_ne" | "minnumf32" | "minnumf64" | "maxnumf32"
78+
| "maxnumf64" | "type_name" => hir::Unsafety::Normal,
8079
_ => hir::Unsafety::Unsafe,
8180
}
8281
}
@@ -258,6 +257,10 @@ pub fn check_intrinsic_type(tcx: TyCtxt<'_>, it: &hir::ForeignItem<'_>) {
258257
(1, vec![param(0), param(0)], tcx.intern_tup(&[param(0), tcx.types.bool]))
259258
}
260259

260+
"ptr_guaranteed_eq" | "ptr_guaranteed_ne" => {
261+
(1, vec![tcx.mk_imm_ptr(param(0)), tcx.mk_imm_ptr(param(0))], tcx.types.bool)
262+
}
263+
261264
"ptr_offset_from" => {
262265
(1, vec![tcx.mk_imm_ptr(param(0)), tcx.mk_imm_ptr(param(0))], tcx.types.isize)
263266
}

src/test/ui/consts/const-eval/const_raw_ptr_ops.stderr

+6-4
Original file line numberDiff line numberDiff line change
@@ -4,17 +4,19 @@ error: pointers cannot be compared in a meaningful way during const eval.
44
LL | const X: bool = unsafe { &1 as *const i32 == &2 as *const i32 };
55
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
66
|
7-
= note: It is conceptually impossible for const eval to know in all cases whether two pointers are equal. While sometimes it is clear (the address of a static item is never equal to the address of another static item), comparing an integer address with any allocation's address is impossible to do at compile-time.
8-
= note: That said, there's the `ptr_maybe_eq` intrinsic which returns `true` for all comparisons where CTFE isn't sure whether two addresses are equal. The mirror intrinsic `ptr_maybe_ne` returns `true` for all comparisons where CTFE isn't sure whether two addresses are inequal.
7+
= note: see issue #53020 <https://github.com/rust-lang/rust/issues/53020> for more information
8+
= note: It is conceptually impossible for const eval to know in all cases whether two pointers are equal. While sometimes it is clear (the address of a non-zst static item is never equal to the address of another non-zst static item), comparing an integer address with any allocation's address is impossible to do at compile-time.
9+
= note: That said, there's the `<*const T>::guaranteed_eq` intrinsic which returns `true` for all comparisons where CTFE is sure that two addresses are equal. The mirror intrinsic `<*const T>::guaranteed_ne` returns `true` for all comparisons where CTFE is sure that two addresses are inequal.
910

1011
error: pointers cannot be compared in a meaningful way during const eval.
1112
--> $DIR/const_raw_ptr_ops.rs:6:27
1213
|
1314
LL | const X2: bool = unsafe { 42 as *const i32 == 43 as *const i32 };
1415
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
1516
|
16-
= note: It is conceptually impossible for const eval to know in all cases whether two pointers are equal. While sometimes it is clear (the address of a static item is never equal to the address of another static item), comparing an integer address with any allocation's address is impossible to do at compile-time.
17-
= note: That said, there's the `ptr_maybe_eq` intrinsic which returns `true` for all comparisons where CTFE isn't sure whether two addresses are equal. The mirror intrinsic `ptr_maybe_ne` returns `true` for all comparisons where CTFE isn't sure whether two addresses are inequal.
17+
= note: see issue #53020 <https://github.com/rust-lang/rust/issues/53020> for more information
18+
= note: It is conceptually impossible for const eval to know in all cases whether two pointers are equal. While sometimes it is clear (the address of a non-zst static item is never equal to the address of another non-zst static item), comparing an integer address with any allocation's address is impossible to do at compile-time.
19+
= note: That said, there's the `<*const T>::guaranteed_eq` intrinsic which returns `true` for all comparisons where CTFE is sure that two addresses are equal. The mirror intrinsic `<*const T>::guaranteed_ne` returns `true` for all comparisons where CTFE is sure that two addresses are inequal.
1820

1921
error: aborting due to 2 previous errors
2022

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
// compile-flags: -Zunleash-the-miri-inside-of-you
2+
// run-pass
3+
4+
#![feature(const_raw_ptr_comparison)]
5+
6+
const EMPTY_SLICE: &[i32] = &[];
7+
const EMPTY_EQ: bool = EMPTY_SLICE.as_ptr().guaranteed_eq(&[] as *const _);
8+
const EMPTY_EQ2: bool = EMPTY_SLICE.as_ptr().guaranteed_ne(&[] as *const _);
9+
const EMPTY_NE: bool = EMPTY_SLICE.as_ptr().guaranteed_ne(&[1] as *const _);
10+
const EMPTY_NE2: bool = EMPTY_SLICE.as_ptr().guaranteed_eq(&[1] as *const _);
11+
12+
fn main() {
13+
assert!(!EMPTY_EQ);
14+
assert!(!EMPTY_EQ2);
15+
assert!(!EMPTY_NE);
16+
assert!(!EMPTY_NE2);
17+
}

0 commit comments

Comments
 (0)