Skip to content

Commit

Permalink
Auto merge of #63810 - oli-obk:const_offset_from, r=RalfJung,nikic
Browse files Browse the repository at this point in the history
Make <*const/mut T>::offset_from `const fn`

This reenables offset_of cc @mjbshaw 	after #63075 broke it
  • Loading branch information
bors committed Nov 2, 2019
2 parents 91fd628 + b93f48f commit 6c1b220
Show file tree
Hide file tree
Showing 10 changed files with 252 additions and 3 deletions.
4 changes: 4 additions & 0 deletions src/libcore/intrinsics.rs
Expand Up @@ -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
Expand Down
18 changes: 17 additions & 1 deletion src/libcore/ptr/mod.rs
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];
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
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::common::span_invalid_monomorphization_error;
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());
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 @@ -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.
Expand Down
51 changes: 50 additions & 1 deletion src/librustc_mir/interpret/intrinsics.rs
Expand Up @@ -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;
Expand Down Expand Up @@ -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)?;
}
Expand Down Expand Up @@ -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)
}
}
1 change: 1 addition & 0 deletions src/librustc_mir/transform/qualify_consts.rs
Expand Up @@ -564,6 +564,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
Expand Up @@ -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" |
Expand Down
47 changes: 47 additions & 0 deletions src/test/ui/consts/offset_from.rs
@@ -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
@@ -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
@@ -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
@@ -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);
}
}

0 comments on commit 6c1b220

Please sign in to comment.