Auto merge of #49297 - scottmcm:offset-from, r=dtolnay
Introduce unsafe offset_from on pointers Adds intrinsics::exact_div to take advantage of the unsafe, which reduces the implementation from ```asm sub rcx, rdx mov rax, rcx sar rax, 63 shr rax, 62 lea rax, [rax + rcx] sar rax, 2 ret ``` down to ```asm sub rcx, rdx sar rcx, 2 mov rax, rcx ret ``` (for `*const i32`) See discussion on the `offset_to` tracking issue https://github.com/rust-lang/rust/issues/41079 Some open questions - Would you rather I split the intrinsic PR from the library PR? - Do we even want the safe version of the API? https://github.com/rust-lang/rust/issues/41079#issuecomment-374426786 I've added some text to its documentation that even if it's not UB, it's useless to use it between pointers into different objects. and todos - [x] ~~I need to make a codegen test~~ Done - [x] ~~Can the subtraction use nsw/nuw?~~ No, it can't https://github.com/rust-lang/rust/pull/49297#discussion_r176697574 - [x] ~~Should there be `usize` variants of this, like there are now `add` and `sub` that you almost always want over `offset`? For example, I imagine `sub_ptr` that returns `usize` and where it's UB if the distance is negative.~~ Can wait for later; C gives a signed result https://github.com/rust-lang/rust/issues/41079#issuecomment-375842235, so we might as well, and this existing to go with `offset` makes sense.
This commit is contained in:
commit
39ee3aaa13
@ -1314,6 +1314,11 @@ extern "rust-intrinsic" {
|
||||
/// [`std::u32::overflowing_mul`](../../std/primitive.u32.html#method.overflowing_mul)
|
||||
pub fn mul_with_overflow<T>(x: T, y: T) -> (T, bool);
|
||||
|
||||
/// Performs an exact division, resulting in undefined behavior where
|
||||
/// `x % y != 0` or `y == 0` or `x == T::min_value() && y == -1`
|
||||
#[cfg(not(stage0))]
|
||||
pub fn exact_div<T>(x: T, y: T) -> T;
|
||||
|
||||
/// Performs an unchecked division, resulting in undefined behavior
|
||||
/// where y = 0 or x = `T::min_value()` and y = -1
|
||||
pub fn unchecked_div<T>(x: T, y: T) -> T;
|
||||
@ -1396,3 +1401,8 @@ extern "rust-intrinsic" {
|
||||
/// Probably will never become stable.
|
||||
pub fn nontemporal_store<T>(ptr: *mut T, val: T);
|
||||
}
|
||||
|
||||
#[cfg(stage0)]
|
||||
pub unsafe fn exact_div<T>(a: T, b: T) -> T {
|
||||
unchecked_div(a, b)
|
||||
}
|
||||
|
@ -669,7 +669,7 @@ impl<T: ?Sized> *const T {
|
||||
/// `mem::size_of::<T>()` then the result of the division is rounded towards
|
||||
/// zero.
|
||||
///
|
||||
/// This function returns `None` if `T` is a zero-sized typed.
|
||||
/// This function returns `None` if `T` is a zero-sized type.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
@ -700,6 +700,124 @@ impl<T: ?Sized> *const T {
|
||||
}
|
||||
}
|
||||
|
||||
/// Calculates the distance between two pointers. The returned value is in
|
||||
/// units of T: the distance in bytes is divided by `mem::size_of::<T>()`.
|
||||
///
|
||||
/// This function is the inverse of [`offset`].
|
||||
///
|
||||
/// [`offset`]: #method.offset
|
||||
/// [`wrapping_offset_from`]: #method.wrapping_offset_from
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// If any of the following conditions are violated, the result is Undefined
|
||||
/// Behavior:
|
||||
///
|
||||
/// * Both the starting and other pointer must be either in bounds or one
|
||||
/// byte past the end of the same allocated object.
|
||||
///
|
||||
/// * The distance between the pointers, **in bytes**, cannot overflow an `isize`.
|
||||
///
|
||||
/// * The distance between the pointers, in bytes, must be an exact multiple
|
||||
/// of the size of `T`.
|
||||
///
|
||||
/// * The distance being in bounds cannot rely on "wrapping around" the address space.
|
||||
///
|
||||
/// The compiler and standard library generally try to ensure allocations
|
||||
/// never reach a size where an offset is a concern. For instance, `Vec`
|
||||
/// and `Box` ensure they never allocate more than `isize::MAX` bytes, so
|
||||
/// `ptr_into_vec.offset_from(vec.as_ptr())` is always safe.
|
||||
///
|
||||
/// Most platforms fundamentally can't even construct such an allocation.
|
||||
/// For instance, no known 64-bit platform can ever serve a request
|
||||
/// for 2<sup>63</sup> bytes due to page-table limitations or splitting the address space.
|
||||
/// However, some 32-bit and 16-bit platforms may successfully serve a request for
|
||||
/// more than `isize::MAX` bytes with things like Physical Address
|
||||
/// Extension. As such, memory acquired directly from allocators or memory
|
||||
/// mapped files *may* be too large to handle with this function.
|
||||
///
|
||||
/// Consider using [`wrapping_offset_from`] instead if these constraints are
|
||||
/// difficult to satisfy. The only advantage of this method is that it
|
||||
/// enables more aggressive compiler optimizations.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// This function panics if `T` is a Zero-Sized Type ("ZST").
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// Basic usage:
|
||||
///
|
||||
/// ```
|
||||
/// #![feature(ptr_offset_from)]
|
||||
///
|
||||
/// let a = [0; 5];
|
||||
/// let ptr1: *const i32 = &a[1];
|
||||
/// let ptr2: *const i32 = &a[3];
|
||||
/// unsafe {
|
||||
/// assert_eq!(ptr2.offset_from(ptr1), 2);
|
||||
/// assert_eq!(ptr1.offset_from(ptr2), -2);
|
||||
/// assert_eq!(ptr1.offset(2), ptr2);
|
||||
/// assert_eq!(ptr2.offset(-2), ptr1);
|
||||
/// }
|
||||
/// ```
|
||||
#[unstable(feature = "ptr_offset_from", issue = "41079")]
|
||||
#[inline]
|
||||
pub unsafe fn offset_from(self, origin: *const T) -> isize where T: Sized {
|
||||
let pointee_size = mem::size_of::<T>();
|
||||
assert!(0 < pointee_size && pointee_size <= isize::max_value() as usize);
|
||||
|
||||
// This is the same sequence that Clang emits for pointer subtraction.
|
||||
// It can be neither `nsw` nor `nuw` because the input is treated as
|
||||
// unsigned but then the output is treated as signed, so neither works.
|
||||
let d = isize::wrapping_sub(self as _, origin as _);
|
||||
intrinsics::exact_div(d, pointee_size as _)
|
||||
}
|
||||
|
||||
/// Calculates the distance between two pointers. The returned value is in
|
||||
/// units of T: the distance in bytes is divided by `mem::size_of::<T>()`.
|
||||
///
|
||||
/// If the address different between the two pointers is not a multiple of
|
||||
/// `mem::size_of::<T>()` then the result of the division is rounded towards
|
||||
/// zero.
|
||||
///
|
||||
/// Though this method is safe for any two pointers, note that its result
|
||||
/// will be mostly useless if the two pointers aren't into the same allocated
|
||||
/// object, for example if they point to two different local variables.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// This function panics if `T` is a zero-sized type.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// Basic usage:
|
||||
///
|
||||
/// ```
|
||||
/// #![feature(ptr_wrapping_offset_from)]
|
||||
///
|
||||
/// let a = [0; 5];
|
||||
/// let ptr1: *const i32 = &a[1];
|
||||
/// let ptr2: *const i32 = &a[3];
|
||||
/// assert_eq!(ptr2.wrapping_offset_from(ptr1), 2);
|
||||
/// assert_eq!(ptr1.wrapping_offset_from(ptr2), -2);
|
||||
/// assert_eq!(ptr1.wrapping_offset(2), ptr2);
|
||||
/// assert_eq!(ptr2.wrapping_offset(-2), ptr1);
|
||||
///
|
||||
/// let ptr1: *const i32 = 3 as _;
|
||||
/// let ptr2: *const i32 = 13 as _;
|
||||
/// assert_eq!(ptr2.wrapping_offset_from(ptr1), 2);
|
||||
/// ```
|
||||
#[unstable(feature = "ptr_wrapping_offset_from", issue = "41079")]
|
||||
#[inline]
|
||||
pub fn wrapping_offset_from(self, origin: *const T) -> isize where T: Sized {
|
||||
let pointee_size = mem::size_of::<T>();
|
||||
assert!(0 < pointee_size && pointee_size <= isize::max_value() as usize);
|
||||
|
||||
let d = isize::wrapping_sub(self as _, origin as _);
|
||||
d.wrapping_div(pointee_size as _)
|
||||
}
|
||||
|
||||
/// Calculates the offset from a pointer (convenience for `.offset(count as isize)`).
|
||||
///
|
||||
/// `count` is in units of T; e.g. a `count` of 3 represents a pointer
|
||||
@ -1316,7 +1434,7 @@ impl<T: ?Sized> *mut T {
|
||||
/// `mem::size_of::<T>()` then the result of the division is rounded towards
|
||||
/// zero.
|
||||
///
|
||||
/// This function returns `None` if `T` is a zero-sized typed.
|
||||
/// This function returns `None` if `T` is a zero-sized type.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
@ -1347,6 +1465,113 @@ impl<T: ?Sized> *mut T {
|
||||
}
|
||||
}
|
||||
|
||||
/// Calculates the distance between two pointers. The returned value is in
|
||||
/// units of T: the distance in bytes is divided by `mem::size_of::<T>()`.
|
||||
///
|
||||
/// This function is the inverse of [`offset`].
|
||||
///
|
||||
/// [`offset`]: #method.offset-1
|
||||
/// [`wrapping_offset_from`]: #method.wrapping_offset_from-1
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// If any of the following conditions are violated, the result is Undefined
|
||||
/// Behavior:
|
||||
///
|
||||
/// * Both the starting and other pointer must be either in bounds or one
|
||||
/// byte past the end of the same allocated object.
|
||||
///
|
||||
/// * The distance between the pointers, **in bytes**, cannot overflow an `isize`.
|
||||
///
|
||||
/// * The distance between the pointers, in bytes, must be an exact multiple
|
||||
/// of the size of `T`.
|
||||
///
|
||||
/// * The distance being in bounds cannot rely on "wrapping around" the address space.
|
||||
///
|
||||
/// The compiler and standard library generally try to ensure allocations
|
||||
/// never reach a size where an offset is a concern. For instance, `Vec`
|
||||
/// and `Box` ensure they never allocate more than `isize::MAX` bytes, so
|
||||
/// `ptr_into_vec.offset_from(vec.as_ptr())` is always safe.
|
||||
///
|
||||
/// Most platforms fundamentally can't even construct such an allocation.
|
||||
/// For instance, no known 64-bit platform can ever serve a request
|
||||
/// for 2<sup>63</sup> bytes due to page-table limitations or splitting the address space.
|
||||
/// However, some 32-bit and 16-bit platforms may successfully serve a request for
|
||||
/// more than `isize::MAX` bytes with things like Physical Address
|
||||
/// Extension. As such, memory acquired directly from allocators or memory
|
||||
/// mapped files *may* be too large to handle with this function.
|
||||
///
|
||||
/// Consider using [`wrapping_offset_from`] instead if these constraints are
|
||||
/// difficult to satisfy. The only advantage of this method is that it
|
||||
/// enables more aggressive compiler optimizations.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// This function panics if `T` is a Zero-Sized Type ("ZST").
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// Basic usage:
|
||||
///
|
||||
/// ```
|
||||
/// #![feature(ptr_offset_from)]
|
||||
///
|
||||
/// let mut a = [0; 5];
|
||||
/// let ptr1: *mut i32 = &mut a[1];
|
||||
/// let ptr2: *mut i32 = &mut a[3];
|
||||
/// unsafe {
|
||||
/// assert_eq!(ptr2.offset_from(ptr1), 2);
|
||||
/// assert_eq!(ptr1.offset_from(ptr2), -2);
|
||||
/// assert_eq!(ptr1.offset(2), ptr2);
|
||||
/// assert_eq!(ptr2.offset(-2), ptr1);
|
||||
/// }
|
||||
/// ```
|
||||
#[unstable(feature = "ptr_offset_from", issue = "41079")]
|
||||
#[inline]
|
||||
pub unsafe fn offset_from(self, origin: *const T) -> isize where T: Sized {
|
||||
(self as *const T).offset_from(origin)
|
||||
}
|
||||
|
||||
/// Calculates the distance between two pointers. The returned value is in
|
||||
/// units of T: the distance in bytes is divided by `mem::size_of::<T>()`.
|
||||
///
|
||||
/// If the address different between the two pointers is not a multiple of
|
||||
/// `mem::size_of::<T>()` then the result of the division is rounded towards
|
||||
/// zero.
|
||||
///
|
||||
/// Though this method is safe for any two pointers, note that its result
|
||||
/// will be mostly useless if the two pointers aren't into the same allocated
|
||||
/// object, for example if they point to two different local variables.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// This function panics if `T` is a zero-sized type.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// Basic usage:
|
||||
///
|
||||
/// ```
|
||||
/// #![feature(ptr_wrapping_offset_from)]
|
||||
///
|
||||
/// let mut a = [0; 5];
|
||||
/// let ptr1: *mut i32 = &mut a[1];
|
||||
/// let ptr2: *mut i32 = &mut a[3];
|
||||
/// assert_eq!(ptr2.wrapping_offset_from(ptr1), 2);
|
||||
/// assert_eq!(ptr1.wrapping_offset_from(ptr2), -2);
|
||||
/// assert_eq!(ptr1.wrapping_offset(2), ptr2);
|
||||
/// assert_eq!(ptr2.wrapping_offset(-2), ptr1);
|
||||
///
|
||||
/// let ptr1: *mut i32 = 3 as _;
|
||||
/// let ptr2: *mut i32 = 13 as _;
|
||||
/// assert_eq!(ptr2.wrapping_offset_from(ptr1), 2);
|
||||
/// ```
|
||||
#[unstable(feature = "ptr_wrapping_offset_from", issue = "41079")]
|
||||
#[inline]
|
||||
pub fn wrapping_offset_from(self, origin: *const T) -> isize where T: Sized {
|
||||
(self as *const T).wrapping_offset_from(origin)
|
||||
}
|
||||
|
||||
/// Computes the byte offset that needs to be applied in order to
|
||||
/// make the pointer aligned to `align`.
|
||||
/// If it is not possible to align the pointer, the implementation returns
|
||||
|
@ -935,6 +935,11 @@ extern "C" {
|
||||
RHS: ValueRef,
|
||||
Name: *const c_char)
|
||||
-> ValueRef;
|
||||
pub fn LLVMBuildExactUDiv(B: BuilderRef,
|
||||
LHS: ValueRef,
|
||||
RHS: ValueRef,
|
||||
Name: *const c_char)
|
||||
-> ValueRef;
|
||||
pub fn LLVMBuildSDiv(B: BuilderRef,
|
||||
LHS: ValueRef,
|
||||
RHS: ValueRef,
|
||||
|
@ -344,6 +344,13 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn exactudiv(&self, lhs: ValueRef, rhs: ValueRef) -> ValueRef {
|
||||
self.count_insn("exactudiv");
|
||||
unsafe {
|
||||
llvm::LLVMBuildExactUDiv(self.llbuilder, lhs, rhs, noname())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn sdiv(&self, lhs: ValueRef, rhs: ValueRef) -> ValueRef {
|
||||
self.count_insn("sdiv");
|
||||
unsafe {
|
||||
|
@ -289,7 +289,7 @@ pub fn trans_intrinsic_call<'a, 'tcx>(bx: &Builder<'a, 'tcx>,
|
||||
"ctlz" | "ctlz_nonzero" | "cttz" | "cttz_nonzero" | "ctpop" | "bswap" |
|
||||
"bitreverse" | "add_with_overflow" | "sub_with_overflow" |
|
||||
"mul_with_overflow" | "overflowing_add" | "overflowing_sub" | "overflowing_mul" |
|
||||
"unchecked_div" | "unchecked_rem" | "unchecked_shl" | "unchecked_shr" => {
|
||||
"unchecked_div" | "unchecked_rem" | "unchecked_shl" | "unchecked_shr" | "exact_div" => {
|
||||
let ty = arg_tys[0];
|
||||
match int_type_width_signed(ty, cx) {
|
||||
Some((width, signed)) =>
|
||||
@ -343,6 +343,12 @@ pub fn trans_intrinsic_call<'a, 'tcx>(bx: &Builder<'a, 'tcx>,
|
||||
"overflowing_add" => bx.add(args[0].immediate(), args[1].immediate()),
|
||||
"overflowing_sub" => bx.sub(args[0].immediate(), args[1].immediate()),
|
||||
"overflowing_mul" => bx.mul(args[0].immediate(), args[1].immediate()),
|
||||
"exact_div" =>
|
||||
if signed {
|
||||
bx.exactsdiv(args[0].immediate(), args[1].immediate())
|
||||
} else {
|
||||
bx.exactudiv(args[0].immediate(), args[1].immediate())
|
||||
},
|
||||
"unchecked_div" =>
|
||||
if signed {
|
||||
bx.sdiv(args[0].immediate(), args[1].immediate())
|
||||
|
@ -283,7 +283,7 @@ pub fn check_intrinsic_type<'a, 'tcx>(tcx: TyCtxt<'a, 'tcx, 'tcx>,
|
||||
(1, vec![param(0), param(0)],
|
||||
tcx.intern_tup(&[param(0), tcx.types.bool])),
|
||||
|
||||
"unchecked_div" | "unchecked_rem" =>
|
||||
"unchecked_div" | "unchecked_rem" | "exact_div" =>
|
||||
(1, vec![param(0), param(0)], param(0)),
|
||||
"unchecked_shl" | "unchecked_shr" =>
|
||||
(1, vec![param(0), param(0)], param(0)),
|
||||
|
@ -1492,3 +1492,11 @@ LLVMRustBuildVectorReduceFMax(LLVMBuilderRef, LLVMValueRef, bool) {
|
||||
return nullptr;
|
||||
}
|
||||
#endif
|
||||
|
||||
#if LLVM_VERSION_LT(4, 0)
|
||||
extern "C" LLVMValueRef
|
||||
LLVMBuildExactUDiv(LLVMBuilderRef B, LLVMValueRef LHS,
|
||||
LLVMValueRef RHS, const char *Name) {
|
||||
return wrap(unwrap(B)->CreateExactUDiv(unwrap(LHS), unwrap(RHS), Name));
|
||||
}
|
||||
#endif
|
||||
|
30
src/test/codegen/exact_div.rs
Normal file
30
src/test/codegen/exact_div.rs
Normal file
@ -0,0 +1,30 @@
|
||||
// Copyright 2018 The Rust Project Developers. See the COPYRIGHT
|
||||
// file at the top-level directory of this distribution and at
|
||||
// http://rust-lang.org/COPYRIGHT.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
|
||||
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
|
||||
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
|
||||
// option. This file may not be copied, modified, or distributed
|
||||
// except according to those terms.
|
||||
|
||||
// compile-flags: -C no-prepopulate-passes
|
||||
|
||||
#![crate_type = "lib"]
|
||||
#![feature(core_intrinsics)]
|
||||
|
||||
use std::intrinsics::exact_div;
|
||||
|
||||
// CHECK-LABEL: @exact_sdiv
|
||||
#[no_mangle]
|
||||
pub unsafe fn exact_sdiv(x: i32, y: i32) -> i32 {
|
||||
// CHECK: sdiv exact
|
||||
exact_div(x, y)
|
||||
}
|
||||
|
||||
// CHECK-LABEL: @exact_udiv
|
||||
#[no_mangle]
|
||||
pub unsafe fn exact_udiv(x: u32, y: u32) -> u32 {
|
||||
// CHECK: udiv exact
|
||||
exact_div(x, y)
|
||||
}
|
Loading…
Reference in New Issue
Block a user