Tell LLVM that the negation in <*const T>::sub cannot overflow

Today it's just `sub` <https://rust.godbolt.org/z/8EzEPnMr5>; with this PR it's `sub nsw`.
This commit is contained in:
Scott McMurray 2023-08-10 23:00:39 -07:00
parent 8e7fd55131
commit ab6e2bc3d0
3 changed files with 58 additions and 5 deletions

View File

@ -1,7 +1,7 @@
use super::*; use super::*;
use crate::cmp::Ordering::{self, Equal, Greater, Less}; use crate::cmp::Ordering::{self, Equal, Greater, Less};
use crate::intrinsics::{self, const_eval_select}; use crate::intrinsics::{self, const_eval_select};
use crate::mem; use crate::mem::{self, SizedTypeProperties};
use crate::slice::{self, SliceIndex}; use crate::slice::{self, SliceIndex};
impl<T: ?Sized> *const T { impl<T: ?Sized> *const T {
@ -995,14 +995,23 @@ pub const fn guaranteed_ne(self, other: *const T) -> Option<bool>
#[stable(feature = "pointer_methods", since = "1.26.0")] #[stable(feature = "pointer_methods", since = "1.26.0")]
#[must_use = "returns a new pointer rather than modifying its argument"] #[must_use = "returns a new pointer rather than modifying its argument"]
#[rustc_const_stable(feature = "const_ptr_offset", since = "1.61.0")] #[rustc_const_stable(feature = "const_ptr_offset", since = "1.61.0")]
// We could always go back to wrapping if unchecked becomes unacceptable
#[rustc_allow_const_fn_unstable(const_int_unchecked_arith)]
#[inline(always)] #[inline(always)]
#[cfg_attr(miri, track_caller)] // even without panics, this helps for Miri backtraces #[cfg_attr(miri, track_caller)] // even without panics, this helps for Miri backtraces
pub const unsafe fn sub(self, count: usize) -> Self pub const unsafe fn sub(self, count: usize) -> Self
where where
T: Sized, T: Sized,
{ {
if T::IS_ZST {
// Pointer arithmetic does nothing when the pointee is a ZST.
self
} else {
// SAFETY: the caller must uphold the safety contract for `offset`. // SAFETY: the caller must uphold the safety contract for `offset`.
unsafe { self.offset((count as isize).wrapping_neg()) } // Because the pointee is *not* a ZST, that means that `count` is
// at most `isize::MAX`, and thus the negation cannot overflow.
unsafe { self.offset(intrinsics::unchecked_sub(0, count as isize)) }
}
} }
/// Calculates the offset from a pointer in bytes (convenience for /// Calculates the offset from a pointer in bytes (convenience for

View File

@ -1,6 +1,7 @@
use super::*; use super::*;
use crate::cmp::Ordering::{self, Equal, Greater, Less}; use crate::cmp::Ordering::{self, Equal, Greater, Less};
use crate::intrinsics::{self, const_eval_select}; use crate::intrinsics::{self, const_eval_select};
use crate::mem::SizedTypeProperties;
use crate::slice::{self, SliceIndex}; use crate::slice::{self, SliceIndex};
impl<T: ?Sized> *mut T { impl<T: ?Sized> *mut T {
@ -1095,14 +1096,23 @@ pub const fn guaranteed_ne(self, other: *mut T) -> Option<bool>
#[stable(feature = "pointer_methods", since = "1.26.0")] #[stable(feature = "pointer_methods", since = "1.26.0")]
#[must_use = "returns a new pointer rather than modifying its argument"] #[must_use = "returns a new pointer rather than modifying its argument"]
#[rustc_const_stable(feature = "const_ptr_offset", since = "1.61.0")] #[rustc_const_stable(feature = "const_ptr_offset", since = "1.61.0")]
// We could always go back to wrapping if unchecked becomes unacceptable
#[rustc_allow_const_fn_unstable(const_int_unchecked_arith)]
#[inline(always)] #[inline(always)]
#[cfg_attr(miri, track_caller)] // even without panics, this helps for Miri backtraces #[cfg_attr(miri, track_caller)] // even without panics, this helps for Miri backtraces
pub const unsafe fn sub(self, count: usize) -> Self pub const unsafe fn sub(self, count: usize) -> Self
where where
T: Sized, T: Sized,
{ {
if T::IS_ZST {
// Pointer arithmetic does nothing when the pointee is a ZST.
self
} else {
// SAFETY: the caller must uphold the safety contract for `offset`. // SAFETY: the caller must uphold the safety contract for `offset`.
unsafe { self.offset((count as isize).wrapping_neg()) } // Because the pointee is *not* a ZST, that means that `count` is
// at most `isize::MAX`, and thus the negation cannot overflow.
unsafe { self.offset(intrinsics::unchecked_sub(0, count as isize)) }
}
} }
/// Calculates the offset from a pointer in bytes (convenience for /// Calculates the offset from a pointer in bytes (convenience for

View File

@ -0,0 +1,34 @@
// compile-flags: -O -Z merge-functions=disabled
// ignore-debug (the extra assertions get in the way)
#![crate_type = "lib"]
// CHECK-LABEL: ptr @i32_add(
// CHECK-SAME: [[WORD:i[0-9]+]] noundef %n)
#[no_mangle]
pub unsafe fn i32_add(p: *const i32, n: usize) -> *const i32 {
// CHECK: %[[TEMP:.+]] = getelementptr inbounds i32, ptr %p, [[WORD]] %n
// CHECK: ret ptr %[[TEMP]]
p.add(n)
}
// Ensure we tell LLVM that the negation in `sub` can't overflow.
// CHECK-LABEL: ptr @i32_sub(
// CHECK-SAME: [[WORD:i[0-9]+]] noundef %n)
#[no_mangle]
pub unsafe fn i32_sub(p: *const i32, n: usize) -> *const i32 {
// CHECK: %[[DELTA:.+]] = sub nsw [[WORD]] 0, %n
// CHECK: %[[TEMP:.+]] = getelementptr inbounds i32, ptr %p, [[WORD]] %[[DELTA]]
// CHECK: ret ptr %[[TEMP]]
p.sub(n)
}
// CHECK-LABEL: ptr @i32_offset(
// CHECK-SAME: [[WORD:i[0-9]+]] noundef %d)
#[no_mangle]
pub unsafe fn i32_offset(p: *const i32, d: isize) -> *const i32 {
// CHECK: %[[TEMP:.+]] = getelementptr inbounds i32, ptr %p, [[WORD]] %d
// CHECK: ret ptr %[[TEMP]]
p.offset(d)
}