Skip to content

Commit

Permalink
Auto merge of #101483 - oli-obk:guaranteed_opt, r=fee1-dead
Browse files Browse the repository at this point in the history
The `<*const T>::guaranteed_*` methods now return an option for the unknown case

cc #53020 (comment)

I chose `0` for "not equal" and `1` for "equal" and left `2` for the unknown case so backends can just forward to raw pointer equality and it works ✨

r? `@fee1-dead` or `@lcnr`

cc `@rust-lang/wg-const-eval`
  • Loading branch information
bors committed Sep 10, 2022
2 parents db9d86b + f632dbe commit 5197c96
Show file tree
Hide file tree
Showing 12 changed files with 119 additions and 138 deletions.
9 changes: 1 addition & 8 deletions compiler/rustc_codegen_cranelift/src/intrinsics/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -816,20 +816,13 @@ fn codegen_regular_intrinsic_call<'tcx>(
ret.write_cvalue(fx, val);
}

sym::ptr_guaranteed_eq => {
sym::ptr_guaranteed_cmp => {
intrinsic_args!(fx, args => (a, b); intrinsic);

let val = crate::num::codegen_ptr_binop(fx, BinOp::Eq, a, b);
ret.write_cvalue(fx, val);
}

sym::ptr_guaranteed_ne => {
intrinsic_args!(fx, args => (a, b); intrinsic);

let val = crate::num::codegen_ptr_binop(fx, BinOp::Ne, a, b);
ret.write_cvalue(fx, val);
}

sym::caller_location => {
intrinsic_args!(fx, args => (); intrinsic);

Expand Down
8 changes: 2 additions & 6 deletions compiler/rustc_codegen_ssa/src/mir/intrinsic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -551,14 +551,10 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
return;
}

sym::ptr_guaranteed_eq | sym::ptr_guaranteed_ne => {
sym::ptr_guaranteed_cmp => {
let a = args[0].immediate();
let b = args[1].immediate();
if name == sym::ptr_guaranteed_eq {
bx.icmp(IntPredicate::IntEQ, a, b)
} else {
bx.icmp(IntPredicate::IntNE, a, b)
}
bx.icmp(IntPredicate::IntEQ, a, b)
}

sym::ptr_offset_from | sym::ptr_offset_from_unsigned => {
Expand Down
49 changes: 23 additions & 26 deletions compiler/rustc_const_eval/src/const_eval/machine.rs
Original file line number Diff line number Diff line change
Expand Up @@ -191,34 +191,35 @@ impl interpret::MayLeak for ! {
}

impl<'mir, 'tcx: 'mir> CompileTimeEvalContext<'mir, 'tcx> {
fn guaranteed_eq(&mut self, a: Scalar, b: Scalar) -> InterpResult<'tcx, bool> {
/// See documentation on the `ptr_guaranteed_cmp` intrinsic.
fn guaranteed_cmp(&mut self, a: Scalar, b: Scalar) -> InterpResult<'tcx, u8> {
Ok(match (a, b) {
// Comparisons between integers are always known.
(Scalar::Int { .. }, Scalar::Int { .. }) => a == b,
// Equality with integers can never be known for sure.
(Scalar::Int { .. }, Scalar::Ptr(..)) | (Scalar::Ptr(..), Scalar::Int { .. }) => false,
// FIXME: return `true` for when both sides are the same pointer, *except* that
// some things (like functions and vtables) do not have stable addresses
// so we need to be careful around them (see e.g. #73722).
(Scalar::Ptr(..), Scalar::Ptr(..)) => false,
})
}

fn guaranteed_ne(&mut self, a: Scalar, b: Scalar) -> InterpResult<'tcx, bool> {
Ok(match (a, b) {
// Comparisons between integers are always known.
(Scalar::Int(_), Scalar::Int(_)) => a != b,
(Scalar::Int { .. }, Scalar::Int { .. }) => {
if a == b {
1
} else {
0
}
}
// Comparisons of abstract pointers with null pointers are known if the pointer
// is in bounds, because if they are in bounds, the pointer can't be null.
// Inequality with integers other than null can never be known for sure.
(Scalar::Int(int), ptr @ Scalar::Ptr(..))
| (ptr @ Scalar::Ptr(..), Scalar::Int(int)) => {
int.is_null() && !self.scalar_may_be_null(ptr)?
| (ptr @ Scalar::Ptr(..), Scalar::Int(int))
if int.is_null() && !self.scalar_may_be_null(ptr)? =>
{
0
}
// FIXME: return `true` for at least some comparisons where we can reliably
// Equality with integers can never be known for sure.
(Scalar::Int { .. }, Scalar::Ptr(..)) | (Scalar::Ptr(..), Scalar::Int { .. }) => 2,
// FIXME: return a `1` for when both sides are the same pointer, *except* that
// some things (like functions and vtables) do not have stable addresses
// so we need to be careful around them (see e.g. #73722).
// FIXME: return `0` for at least some comparisons where we can reliably
// determine the result of runtime inequality tests at compile-time.
// Examples include comparison of addresses in different static items.
(Scalar::Ptr(..), Scalar::Ptr(..)) => false,
(Scalar::Ptr(..), Scalar::Ptr(..)) => 2,
})
}
}
Expand Down Expand Up @@ -329,15 +330,11 @@ impl<'mir, 'tcx> interpret::Machine<'mir, 'tcx> for CompileTimeInterpreter<'mir,
throw_unsup_format!("intrinsic `{intrinsic_name}` is not supported at compile-time");
};
match intrinsic_name {
sym::ptr_guaranteed_eq | sym::ptr_guaranteed_ne => {
sym::ptr_guaranteed_cmp => {
let a = ecx.read_scalar(&args[0])?;
let b = ecx.read_scalar(&args[1])?;
let cmp = if intrinsic_name == sym::ptr_guaranteed_eq {
ecx.guaranteed_eq(a, b)?
} else {
ecx.guaranteed_ne(a, b)?
};
ecx.write_scalar(Scalar::from_bool(cmp), dest)?;
let cmp = ecx.guaranteed_cmp(a, b)?;
ecx.write_scalar(Scalar::from_u8(cmp), dest)?;
}
sym::const_allocate => {
let size = ecx.read_scalar(&args[0])?.to_machine_usize(ecx)?;
Expand Down
2 changes: 1 addition & 1 deletion compiler/rustc_const_eval/src/const_eval/valtrees.rs
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ pub(crate) fn const_to_valtree_inner<'tcx>(
}

// Raw pointers are not allowed in type level constants, as we cannot properly test them for
// equality at compile-time (see `ptr_guaranteed_eq`/`_ne`).
// equality at compile-time (see `ptr_guaranteed_cmp`).
// Technically we could allow function pointers (represented as `ty::Instance`), but this is not guaranteed to
// agree with runtime equality tests.
ty::FnPtr(_) | ty::RawPtr(_) => Err(ValTreeCreationError::NonSupportedType),
Expand Down
3 changes: 1 addition & 2 deletions compiler/rustc_span/src/symbol.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1117,8 +1117,7 @@ symbols! {
profiler_builtins,
profiler_runtime,
ptr,
ptr_guaranteed_eq,
ptr_guaranteed_ne,
ptr_guaranteed_cmp,
ptr_mask,
ptr_null,
ptr_null_mut,
Expand Down
7 changes: 3 additions & 4 deletions compiler/rustc_typeck/src/check/intrinsic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -95,8 +95,7 @@ pub fn intrinsic_operation_unsafety(intrinsic: Symbol) -> hir::Unsafety {
| sym::type_id
| sym::likely
| sym::unlikely
| sym::ptr_guaranteed_eq
| sym::ptr_guaranteed_ne
| sym::ptr_guaranteed_cmp
| sym::minnumf32
| sym::minnumf64
| sym::maxnumf32
Expand Down Expand Up @@ -302,8 +301,8 @@ pub fn check_intrinsic_type(tcx: TyCtxt<'_>, it: &hir::ForeignItem<'_>) {
(1, vec![param(0), param(0)], tcx.intern_tup(&[param(0), tcx.types.bool]))
}

sym::ptr_guaranteed_eq | sym::ptr_guaranteed_ne => {
(1, vec![tcx.mk_imm_ptr(param(0)), tcx.mk_imm_ptr(param(0))], tcx.types.bool)
sym::ptr_guaranteed_cmp => {
(1, vec![tcx.mk_imm_ptr(param(0)), tcx.mk_imm_ptr(param(0))], tcx.types.u8)
}

sym::const_allocate => {
Expand Down
25 changes: 19 additions & 6 deletions library/core/src/intrinsics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2013,21 +2013,24 @@ extern "rust-intrinsic" {
pub fn ptr_offset_from_unsigned<T>(ptr: *const T, base: *const T) -> usize;

/// See documentation of `<*const T>::guaranteed_eq` for details.
/// Returns `2` if the result is unknown.
/// Returns `1` if the pointers are guaranteed equal
/// Returns `0` if the pointers are guaranteed inequal
///
/// Note that, unlike most intrinsics, this is safe to call;
/// it does not require an `unsafe` block.
/// Therefore, implementations must not require the user to uphold
/// any safety invariants.
#[rustc_const_unstable(feature = "const_raw_ptr_comparison", issue = "53020")]
#[cfg(not(bootstrap))]
pub fn ptr_guaranteed_cmp<T>(ptr: *const T, other: *const T) -> u8;

#[rustc_const_unstable(feature = "const_raw_ptr_comparison", issue = "53020")]
#[cfg(bootstrap)]
pub fn ptr_guaranteed_eq<T>(ptr: *const T, other: *const T) -> bool;

/// See documentation of `<*const T>::guaranteed_ne` for details.
///
/// Note that, unlike most intrinsics, this is safe to call;
/// it does not require an `unsafe` block.
/// Therefore, implementations must not require the user to uphold
/// any safety invariants.
#[rustc_const_unstable(feature = "const_raw_ptr_comparison", issue = "53020")]
#[cfg(bootstrap)]
pub fn ptr_guaranteed_ne<T>(ptr: *const T, other: *const T) -> bool;

/// Allocates a block of memory at compile time.
Expand Down Expand Up @@ -2213,6 +2216,16 @@ pub(crate) fn is_nonoverlapping<T>(src: *const T, dst: *const T, count: usize) -
diff >= size
}

#[cfg(bootstrap)]
pub const fn ptr_guaranteed_cmp(a: *const (), b: *const ()) -> u8 {
match (ptr_guaranteed_eq(a, b), ptr_guaranteed_ne(a, b)) {
(false, false) => 2,
(true, false) => 1,
(false, true) => 0,
(true, true) => unreachable!(),
}
}

/// Copies `count * size_of::<T>()` bytes from `src` to `dst`. The source
/// and destination must *not* overlap.
///
Expand Down
55 changes: 28 additions & 27 deletions library/core/src/ptr/const_ptr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,10 @@ impl<T: ?Sized> *const T {
pub const fn is_null(self) -> bool {
// Compare via a cast to a thin pointer, so fat pointers are only
// considering their "data" part for null-ness.
(self as *const u8).guaranteed_eq(null())
match (self as *const u8).guaranteed_eq(null()) {
None => false,
Some(res) => res,
}
}

/// Casts to a pointer of another type.
Expand Down Expand Up @@ -770,20 +773,16 @@ impl<T: ?Sized> *const T {

/// Returns whether two pointers are guaranteed to be equal.
///
/// At runtime this function behaves like `self == other`.
/// At runtime this function behaves like `Some(self == other)`.
/// However, in some contexts (e.g., compile-time evaluation),
/// it is not always possible to determine equality of two pointers, so this function may
/// spuriously return `false` for pointers that later actually turn out to be equal.
/// But when it returns `true`, the pointers are guaranteed to be equal.
///
/// This function is the mirror of [`guaranteed_ne`], but not its inverse. There are pointer
/// comparisons for which both functions return `false`.
/// spuriously return `None` for pointers that later actually turn out to have its equality known.
/// But when it returns `Some`, the pointers' equality is guaranteed to be known.
///
/// [`guaranteed_ne`]: #method.guaranteed_ne
///
/// The return value may change depending on the compiler version and unsafe code must not
/// The return value may change from `Some` to `None` and vice versa depending on the compiler
/// version and unsafe code must not
/// rely on the result of this function for soundness. It is suggested to only use this function
/// for performance optimizations where spurious `false` return values by this function do not
/// for performance optimizations where spurious `None` return values by this function do not
/// affect the outcome, but just the performance.
/// The consequences of using this method to make runtime and compile-time code behave
/// differently have not been explored. This method should not be used to introduce such
Expand All @@ -792,29 +791,28 @@ impl<T: ?Sized> *const T {
#[unstable(feature = "const_raw_ptr_comparison", issue = "53020")]
#[rustc_const_unstable(feature = "const_raw_ptr_comparison", issue = "53020")]
#[inline]
pub const fn guaranteed_eq(self, other: *const T) -> bool
pub const fn guaranteed_eq(self, other: *const T) -> Option<bool>
where
T: Sized,
{
intrinsics::ptr_guaranteed_eq(self, other)
match intrinsics::ptr_guaranteed_cmp(self as _, other as _) {
2 => None,
other => Some(other == 1),
}
}

/// Returns whether two pointers are guaranteed to be unequal.
/// Returns whether two pointers are guaranteed to be inequal.
///
/// At runtime this function behaves like `self != other`.
/// At runtime this function behaves like `Some(self == other)`.
/// However, in some contexts (e.g., compile-time evaluation),
/// it is not always possible to determine the inequality of two pointers, so this function may
/// spuriously return `false` for pointers that later actually turn out to be unequal.
/// But when it returns `true`, the pointers are guaranteed to be unequal.
/// it is not always possible to determine inequality of two pointers, so this function may
/// spuriously return `None` for pointers that later actually turn out to have its inequality known.
/// But when it returns `Some`, the pointers' inequality is guaranteed to be known.
///
/// This function is the mirror of [`guaranteed_eq`], but not its inverse. There are pointer
/// comparisons for which both functions return `false`.
///
/// [`guaranteed_eq`]: #method.guaranteed_eq
///
/// The return value may change depending on the compiler version and unsafe code must not
/// The return value may change from `Some` to `None` and vice versa depending on the compiler
/// version and unsafe code must not
/// rely on the result of this function for soundness. It is suggested to only use this function
/// for performance optimizations where spurious `false` return values by this function do not
/// for performance optimizations where spurious `None` return values by this function do not
/// affect the outcome, but just the performance.
/// The consequences of using this method to make runtime and compile-time code behave
/// differently have not been explored. This method should not be used to introduce such
Expand All @@ -823,11 +821,14 @@ impl<T: ?Sized> *const T {
#[unstable(feature = "const_raw_ptr_comparison", issue = "53020")]
#[rustc_const_unstable(feature = "const_raw_ptr_comparison", issue = "53020")]
#[inline]
pub const fn guaranteed_ne(self, other: *const T) -> bool
pub const fn guaranteed_ne(self, other: *const T) -> Option<bool>
where
T: Sized,
{
intrinsics::ptr_guaranteed_ne(self, other)
match self.guaranteed_eq(other) {
None => None,
Some(eq) => Some(!eq),
}
}

/// Calculates the offset from a pointer (convenience for `.offset(count as isize)`).
Expand Down
49 changes: 22 additions & 27 deletions library/core/src/ptr/mut_ptr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,10 @@ impl<T: ?Sized> *mut T {
pub const fn is_null(self) -> bool {
// Compare via a cast to a thin pointer, so fat pointers are only
// considering their "data" part for null-ness.
(self as *mut u8).guaranteed_eq(null_mut())
match (self as *mut u8).guaranteed_eq(null_mut()) {
None => false,
Some(res) => res,
}
}

/// Casts to a pointer of another type.
Expand Down Expand Up @@ -697,20 +700,16 @@ impl<T: ?Sized> *mut T {

/// Returns whether two pointers are guaranteed to be equal.
///
/// At runtime this function behaves like `self == other`.
/// At runtime this function behaves like `Some(self == other)`.
/// However, in some contexts (e.g., compile-time evaluation),
/// it is not always possible to determine equality of two pointers, so this function may
/// spuriously return `false` for pointers that later actually turn out to be equal.
/// But when it returns `true`, the pointers are guaranteed to be equal.
///
/// This function is the mirror of [`guaranteed_ne`], but not its inverse. There are pointer
/// comparisons for which both functions return `false`.
///
/// [`guaranteed_ne`]: #method.guaranteed_ne
/// spuriously return `None` for pointers that later actually turn out to have its equality known.
/// But when it returns `Some`, the pointers' equality is guaranteed to be known.
///
/// The return value may change depending on the compiler version and unsafe code might not
/// The return value may change from `Some` to `None` and vice versa depending on the compiler
/// version and unsafe code must not
/// rely on the result of this function for soundness. It is suggested to only use this function
/// for performance optimizations where spurious `false` return values by this function do not
/// for performance optimizations where spurious `None` return values by this function do not
/// affect the outcome, but just the performance.
/// The consequences of using this method to make runtime and compile-time code behave
/// differently have not been explored. This method should not be used to introduce such
Expand All @@ -719,29 +718,25 @@ impl<T: ?Sized> *mut T {
#[unstable(feature = "const_raw_ptr_comparison", issue = "53020")]
#[rustc_const_unstable(feature = "const_raw_ptr_comparison", issue = "53020")]
#[inline]
pub const fn guaranteed_eq(self, other: *mut T) -> bool
pub const fn guaranteed_eq(self, other: *mut T) -> Option<bool>
where
T: Sized,
{
intrinsics::ptr_guaranteed_eq(self as *const _, other as *const _)
(self as *const T).guaranteed_eq(other as _)
}

/// Returns whether two pointers are guaranteed to be unequal.
/// Returns whether two pointers are guaranteed to be inequal.
///
/// At runtime this function behaves like `self != other`.
/// At runtime this function behaves like `Some(self == other)`.
/// However, in some contexts (e.g., compile-time evaluation),
/// it is not always possible to determine the inequality of two pointers, so this function may
/// spuriously return `false` for pointers that later actually turn out to be unequal.
/// But when it returns `true`, the pointers are guaranteed to be unequal.
///
/// This function is the mirror of [`guaranteed_eq`], but not its inverse. There are pointer
/// comparisons for which both functions return `false`.
///
/// [`guaranteed_eq`]: #method.guaranteed_eq
/// it is not always possible to determine inequality of two pointers, so this function may
/// spuriously return `None` for pointers that later actually turn out to have its inequality known.
/// But when it returns `Some`, the pointers' inequality is guaranteed to be known.
///
/// The return value may change depending on the compiler version and unsafe code might not
/// The return value may change from `Some` to `None` and vice versa depending on the compiler
/// version and unsafe code must not
/// rely on the result of this function for soundness. It is suggested to only use this function
/// for performance optimizations where spurious `false` return values by this function do not
/// for performance optimizations where spurious `None` return values by this function do not
/// affect the outcome, but just the performance.
/// The consequences of using this method to make runtime and compile-time code behave
/// differently have not been explored. This method should not be used to introduce such
Expand All @@ -750,11 +745,11 @@ impl<T: ?Sized> *mut T {
#[unstable(feature = "const_raw_ptr_comparison", issue = "53020")]
#[rustc_const_unstable(feature = "const_raw_ptr_comparison", issue = "53020")]
#[inline]
pub const unsafe fn guaranteed_ne(self, other: *mut T) -> bool
pub const fn guaranteed_ne(self, other: *mut T) -> Option<bool>
where
T: Sized,
{
intrinsics::ptr_guaranteed_ne(self as *const _, other as *const _)
(self as *const T).guaranteed_ne(other as _)
}

/// Calculates the distance between two pointers. The returned value is in
Expand Down
Loading

0 comments on commit 5197c96

Please sign in to comment.