Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make <*const/mut T>::offset_from const fn #63810

Merged
merged 7 commits into from
Nov 3, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions src/libcore/intrinsics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1339,6 +1339,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
Expand Down
18 changes: 17 additions & 1 deletion src/libcore/ptr/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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];
RalfJung marked this conversation as resolved.
Show resolved Hide resolved
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);
Expand Down Expand Up @@ -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)
}

Expand Down
19 changes: 18 additions & 1 deletion src/librustc_codegen_llvm/intrinsic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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::traits::*;

Expand Down Expand Up @@ -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());
oli-obk marked this conversation as resolved.
Show resolved Hide resolved
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),
};

Expand Down Expand Up @@ -1218,7 +1236,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.
Expand Down
51 changes: 50 additions & 1 deletion src/librustc_mir/interpret/intrinsics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,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 type_name;
Expand Down Expand Up @@ -236,6 +236,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()?;
Comment on lines +241 to +242
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, I just realized this is wrong... we need to support integers here as well.

This caused a regression in https://github.com/RalfJung/miri-test-libstd.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

#66083 fixes that.

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)?;
}
Expand Down Expand Up @@ -340,4 +363,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)
}
}
1 change: 1 addition & 0 deletions src/librustc_mir/transform/qualify_consts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -560,6 +560,7 @@ impl Qualif for IsNotPromotable {
| "transmute"
| "simd_insert"
| "simd_extract"
| "ptr_offset_from"
=> return true,

_ => {}
Expand Down
2 changes: 2 additions & 0 deletions src/librustc_typeck/check/intrinsic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -307,6 +307,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" |
Expand Down
47 changes: 47 additions & 0 deletions src/test/ui/consts/offset_from.rs
Original file line number Diff line number Diff line change
@@ -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 changes: 37 additions & 0 deletions src/test/ui/consts/offset_from_ub.rs
Original file line number Diff line number Diff line change
@@ -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 changes: 61 additions & 0 deletions src/test/ui/consts/offset_from_ub.stderr
Original file line number Diff line number Diff line change
@@ -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 changes: 15 additions & 0 deletions src/test/ui/offset_from.rs
Original file line number Diff line number Diff line change
@@ -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);
}
}