Auto merge of #63810 - oli-obk:const_offset_from, r=RalfJung,nikic
Make <*const/mut T>::offset_from `const fn` This reenables offset_of cc @mjbshaw after https://github.com/rust-lang/rust/pull/63075 broke it
This commit is contained in:
commit
6c1b220fd7
10 changed files with 252 additions and 3 deletions
|
@ -1344,6 +1344,10 @@ extern "rust-intrinsic" {
|
|||
/// Emits a `!nontemporal` store according to LLVM (see their docs).
|
||||
/// Probably will never become stable.
|
||||
pub fn nontemporal_store<T>(ptr: *mut T, val: T);
|
||||
|
||||
/// See documentation of `<*const T>::offset_from` for details.
|
||||
#[cfg(not(bootstrap))]
|
||||
pub fn ptr_offset_from<T>(ptr: *const T, base: *const T) -> isize;
|
||||
}
|
||||
|
||||
// Some functions are defined here because they accidentally got made
|
||||
|
|
|
@ -1286,7 +1286,22 @@ impl<T: ?Sized> *const T {
|
|||
/// }
|
||||
/// ```
|
||||
#[unstable(feature = "ptr_offset_from", issue = "41079")]
|
||||
#[cfg(not(bootstrap))]
|
||||
#[rustc_const_unstable(feature = "const_ptr_offset_from")]
|
||||
#[inline]
|
||||
pub const unsafe fn offset_from(self, origin: *const T) -> isize where T: Sized {
|
||||
let pointee_size = mem::size_of::<T>();
|
||||
let ok = 0 < pointee_size && pointee_size <= isize::max_value() as usize;
|
||||
// assert that the pointee size is valid in a const eval compatible way
|
||||
// FIXME: do this with a real assert at some point
|
||||
[()][(!ok) as usize];
|
||||
intrinsics::ptr_offset_from(self, origin)
|
||||
}
|
||||
|
||||
#[unstable(feature = "ptr_offset_from", issue = "41079")]
|
||||
#[inline]
|
||||
#[cfg(bootstrap)]
|
||||
/// bootstrap
|
||||
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);
|
||||
|
@ -2013,8 +2028,9 @@ impl<T: ?Sized> *mut T {
|
|||
/// }
|
||||
/// ```
|
||||
#[unstable(feature = "ptr_offset_from", issue = "41079")]
|
||||
#[rustc_const_unstable(feature = "const_ptr_offset_from")]
|
||||
#[inline]
|
||||
pub unsafe fn offset_from(self, origin: *const T) -> isize where T: Sized {
|
||||
pub const unsafe fn offset_from(self, origin: *const T) -> isize where T: Sized {
|
||||
(self as *const T).offset_from(origin)
|
||||
}
|
||||
|
||||
|
|
|
@ -19,6 +19,7 @@ use rustc::mir::interpret::GlobalId;
|
|||
use rustc_codegen_ssa::common::{IntPredicate, TypeKind};
|
||||
use rustc::hir;
|
||||
use syntax::ast::{self, FloatTy};
|
||||
use rustc_target::abi::HasDataLayout;
|
||||
|
||||
use rustc_codegen_ssa::common::span_invalid_monomorphization_error;
|
||||
use rustc_codegen_ssa::traits::*;
|
||||
|
@ -694,6 +695,23 @@ impl IntrinsicCallMethods<'tcx> for Builder<'a, 'll, 'tcx> {
|
|||
return;
|
||||
}
|
||||
|
||||
"ptr_offset_from" => {
|
||||
let ty = substs.type_at(0);
|
||||
let pointee_size = self.size_of(ty);
|
||||
|
||||
// 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 a = args[0].immediate();
|
||||
let b = args[1].immediate();
|
||||
let a = self.ptrtoint(a, self.type_isize());
|
||||
let b = self.ptrtoint(b, self.type_isize());
|
||||
let d = self.sub(a, b);
|
||||
let pointee_size = self.const_usize(pointee_size.bytes());
|
||||
// this is where the signed magic happens (notice the `s` in `exactsdiv`)
|
||||
self.exactsdiv(d, pointee_size)
|
||||
}
|
||||
|
||||
_ => bug!("unknown intrinsic '{}'", name),
|
||||
};
|
||||
|
||||
|
@ -1224,7 +1242,6 @@ fn generic_simd_intrinsic(
|
|||
// The `fn simd_bitmask(vector) -> unsigned integer` intrinsic takes a
|
||||
// vector mask and returns an unsigned integer containing the most
|
||||
// significant bit (MSB) of each lane.
|
||||
use rustc_target::abi::HasDataLayout;
|
||||
|
||||
// If the vector has less than 8 lanes, an u8 is returned with zeroed
|
||||
// trailing bits.
|
||||
|
|
|
@ -13,7 +13,7 @@ use rustc::mir::BinOp;
|
|||
use rustc::mir::interpret::{InterpResult, Scalar, GlobalId, ConstValue};
|
||||
|
||||
use super::{
|
||||
Machine, PlaceTy, OpTy, InterpCx,
|
||||
Machine, PlaceTy, OpTy, InterpCx, ImmTy,
|
||||
};
|
||||
|
||||
mod caller_location;
|
||||
|
@ -249,6 +249,29 @@ impl<'mir, 'tcx, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
|
|||
let result = Scalar::from_uint(truncated_bits, layout.size);
|
||||
self.write_scalar(result, dest)?;
|
||||
}
|
||||
|
||||
"ptr_offset_from" => {
|
||||
let a = self.read_immediate(args[0])?.to_scalar()?.to_ptr()?;
|
||||
let b = self.read_immediate(args[1])?.to_scalar()?.to_ptr()?;
|
||||
if a.alloc_id != b.alloc_id {
|
||||
throw_ub_format!(
|
||||
"ptr_offset_from cannot compute offset of pointers into different \
|
||||
allocations.",
|
||||
);
|
||||
}
|
||||
let usize_layout = self.layout_of(self.tcx.types.usize)?;
|
||||
let a_offset = ImmTy::from_uint(a.offset.bytes(), usize_layout);
|
||||
let b_offset = ImmTy::from_uint(b.offset.bytes(), usize_layout);
|
||||
let (val, _overflowed, _ty) = self.overflowing_binary_op(
|
||||
BinOp::Sub, a_offset, b_offset,
|
||||
)?;
|
||||
let pointee_layout = self.layout_of(substs.type_at(0))?;
|
||||
let isize_layout = self.layout_of(self.tcx.types.isize)?;
|
||||
let val = ImmTy::from_scalar(val, isize_layout);
|
||||
let size = ImmTy::from_int(pointee_layout.size.bytes(), isize_layout);
|
||||
self.exact_div(val, size, dest)?;
|
||||
}
|
||||
|
||||
"transmute" => {
|
||||
self.copy_op_transmute(args[0], dest)?;
|
||||
}
|
||||
|
@ -354,4 +377,30 @@ impl<'mir, 'tcx, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
|
|||
return Ok(false);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn exact_div(
|
||||
&mut self,
|
||||
a: ImmTy<'tcx, M::PointerTag>,
|
||||
b: ImmTy<'tcx, M::PointerTag>,
|
||||
dest: PlaceTy<'tcx, M::PointerTag>,
|
||||
) -> InterpResult<'tcx> {
|
||||
// Performs an exact division, resulting in undefined behavior where
|
||||
// `x % y != 0` or `y == 0` or `x == T::min_value() && y == -1`.
|
||||
// First, check x % y != 0.
|
||||
if self.binary_op(BinOp::Rem, a, b)?.to_bits()? != 0 {
|
||||
// Then, check if `b` is -1, which is the "min_value / -1" case.
|
||||
let minus1 = Scalar::from_int(-1, dest.layout.size);
|
||||
let b = b.to_scalar().unwrap();
|
||||
if b == minus1 {
|
||||
throw_ub_format!("exact_div: result of dividing MIN by -1 cannot be represented")
|
||||
} else {
|
||||
throw_ub_format!(
|
||||
"exact_div: {} cannot be divided by {} without remainder",
|
||||
a.to_scalar().unwrap(),
|
||||
b,
|
||||
)
|
||||
}
|
||||
}
|
||||
self.binop_ignore_overflow(BinOp::Div, a, b, dest)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -564,6 +564,7 @@ impl Qualif for IsNotPromotable {
|
|||
| "transmute"
|
||||
| "simd_insert"
|
||||
| "simd_extract"
|
||||
| "ptr_offset_from"
|
||||
=> return true,
|
||||
|
||||
_ => {}
|
||||
|
|
|
@ -317,6 +317,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])),
|
||||
|
||||
"ptr_offset_from" =>
|
||||
(1, vec![ tcx.mk_imm_ptr(param(0)), tcx.mk_imm_ptr(param(0)) ], tcx.types.isize),
|
||||
"unchecked_div" | "unchecked_rem" | "exact_div" =>
|
||||
(1, vec![param(0), param(0)], param(0)),
|
||||
"unchecked_shl" | "unchecked_shr" |
|
||||
|
|
47
src/test/ui/consts/offset_from.rs
Normal file
47
src/test/ui/consts/offset_from.rs
Normal file
|
@ -0,0 +1,47 @@
|
|||
// run-pass
|
||||
|
||||
#![feature(const_raw_ptr_deref)]
|
||||
#![feature(const_ptr_offset_from)]
|
||||
#![feature(ptr_offset_from)]
|
||||
|
||||
struct Struct {
|
||||
field: (),
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
struct Struct2 {
|
||||
data: u8,
|
||||
field: u8,
|
||||
}
|
||||
|
||||
pub const OFFSET: usize = {
|
||||
let uninit = std::mem::MaybeUninit::<Struct>::uninit();
|
||||
let base_ptr: *const Struct = &uninit as *const _ as *const Struct;
|
||||
// The following statement is UB (taking the address of an uninitialized field).
|
||||
// Const eval doesn't detect this right now, but it may stop compiling at some point
|
||||
// in the future.
|
||||
let field_ptr = unsafe { &(*base_ptr).field as *const () as *const u8 };
|
||||
let offset = unsafe { field_ptr.offset_from(base_ptr as *const u8) };
|
||||
offset as usize
|
||||
};
|
||||
|
||||
pub const OFFSET_2: usize = {
|
||||
let uninit = std::mem::MaybeUninit::<Struct2>::uninit();
|
||||
let base_ptr: *const Struct2 = &uninit as *const _ as *const Struct2;
|
||||
let field_ptr = unsafe { &(*base_ptr).field as *const u8 };
|
||||
let offset = unsafe { field_ptr.offset_from(base_ptr as *const u8) };
|
||||
offset as usize
|
||||
};
|
||||
|
||||
pub const OVERFLOW: isize = {
|
||||
let uninit = std::mem::MaybeUninit::<Struct2>::uninit();
|
||||
let base_ptr: *const Struct2 = &uninit as *const _ as *const Struct2;
|
||||
let field_ptr = unsafe { &(*base_ptr).field as *const u8 };
|
||||
unsafe { (base_ptr as *const u8).offset_from(field_ptr) }
|
||||
};
|
||||
|
||||
fn main() {
|
||||
assert_eq!(OFFSET, 0);
|
||||
assert_eq!(OFFSET_2, 1);
|
||||
assert_eq!(OVERFLOW, -1);
|
||||
}
|
37
src/test/ui/consts/offset_from_ub.rs
Normal file
37
src/test/ui/consts/offset_from_ub.rs
Normal file
|
@ -0,0 +1,37 @@
|
|||
// ignore-x86 FIXME: missing sysroot spans (#53081)
|
||||
|
||||
#![feature(const_raw_ptr_deref)]
|
||||
#![feature(const_ptr_offset_from)]
|
||||
#![feature(ptr_offset_from)]
|
||||
|
||||
#[repr(C)]
|
||||
struct Struct {
|
||||
data: u8,
|
||||
field: u8,
|
||||
}
|
||||
|
||||
pub const DIFFERENT_ALLOC: usize = {
|
||||
//~^ NOTE
|
||||
let uninit = std::mem::MaybeUninit::<Struct>::uninit();
|
||||
let base_ptr: *const Struct = &uninit as *const _ as *const Struct;
|
||||
let uninit2 = std::mem::MaybeUninit::<Struct>::uninit();
|
||||
let field_ptr: *const Struct = &uninit2 as *const _ as *const Struct;
|
||||
let offset = unsafe { field_ptr.offset_from(base_ptr) };
|
||||
offset as usize
|
||||
};
|
||||
|
||||
pub const NOT_PTR: usize = {
|
||||
//~^ NOTE
|
||||
unsafe { (42 as *const u8).offset_from(&5u8) as usize }
|
||||
};
|
||||
|
||||
pub const NOT_MULTIPLE_OF_SIZE: usize = {
|
||||
//~^ NOTE
|
||||
let data = [5u8, 6, 7];
|
||||
let base_ptr = data.as_ptr();
|
||||
let field_ptr = &data[1] as *const u8 as *const u16;
|
||||
let offset = unsafe { field_ptr.offset_from(base_ptr as *const u16) };
|
||||
offset as usize
|
||||
};
|
||||
|
||||
fn main() {}
|
61
src/test/ui/consts/offset_from_ub.stderr
Normal file
61
src/test/ui/consts/offset_from_ub.stderr
Normal file
|
@ -0,0 +1,61 @@
|
|||
error: any use of this value will cause an error
|
||||
--> $SRC_DIR/libcore/ptr/mod.rs:LL:COL
|
||||
|
|
||||
LL | intrinsics::ptr_offset_from(self, origin)
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
| |
|
||||
| ptr_offset_from cannot compute offset of pointers into different allocations.
|
||||
| inside call to `std::ptr::<impl *const Struct>::offset_from` at $DIR/offset_from_ub.rs:19:27
|
||||
|
|
||||
::: $DIR/offset_from_ub.rs:13:1
|
||||
|
|
||||
LL | / pub const DIFFERENT_ALLOC: usize = {
|
||||
LL | |
|
||||
LL | | let uninit = std::mem::MaybeUninit::<Struct>::uninit();
|
||||
LL | | let base_ptr: *const Struct = &uninit as *const _ as *const Struct;
|
||||
... |
|
||||
LL | | offset as usize
|
||||
LL | | };
|
||||
| |__-
|
||||
|
|
||||
= note: `#[deny(const_err)]` on by default
|
||||
|
||||
error: any use of this value will cause an error
|
||||
--> $SRC_DIR/libcore/ptr/mod.rs:LL:COL
|
||||
|
|
||||
LL | intrinsics::ptr_offset_from(self, origin)
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
| |
|
||||
| a memory access tried to interpret some bytes as a pointer
|
||||
| inside call to `std::ptr::<impl *const u8>::offset_from` at $DIR/offset_from_ub.rs:25:14
|
||||
|
|
||||
::: $DIR/offset_from_ub.rs:23:1
|
||||
|
|
||||
LL | / pub const NOT_PTR: usize = {
|
||||
LL | |
|
||||
LL | | unsafe { (42 as *const u8).offset_from(&5u8) as usize }
|
||||
LL | | };
|
||||
| |__-
|
||||
|
||||
error: any use of this value will cause an error
|
||||
--> $SRC_DIR/libcore/ptr/mod.rs:LL:COL
|
||||
|
|
||||
LL | intrinsics::ptr_offset_from(self, origin)
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
| |
|
||||
| exact_div: 1 cannot be divided by 2 without remainder
|
||||
| inside call to `std::ptr::<impl *const u16>::offset_from` at $DIR/offset_from_ub.rs:33:27
|
||||
|
|
||||
::: $DIR/offset_from_ub.rs:28:1
|
||||
|
|
||||
LL | / pub const NOT_MULTIPLE_OF_SIZE: usize = {
|
||||
LL | |
|
||||
LL | | let data = [5u8, 6, 7];
|
||||
LL | | let base_ptr = data.as_ptr();
|
||||
... |
|
||||
LL | | offset as usize
|
||||
LL | | };
|
||||
| |__-
|
||||
|
||||
error: aborting due to 3 previous errors
|
||||
|
15
src/test/ui/offset_from.rs
Normal file
15
src/test/ui/offset_from.rs
Normal file
|
@ -0,0 +1,15 @@
|
|||
// run-pass
|
||||
|
||||
#![feature(ptr_offset_from)]
|
||||
|
||||
fn main() {
|
||||
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);
|
||||
}
|
||||
}
|
Loading…
Add table
Reference in a new issue