about summary refs log tree commit diff
diff options
context:
space:
mode:
authorOliver Scherer <github35764891676564198441@oli-obk.de>2019-08-20 17:51:54 +0200
committerOliver Scherer <github35764891676564198441@oli-obk.de>2019-10-11 10:40:49 +0200
commit88b5e945e01a4599c60ac406d4949cd0329935cb (patch)
treefe68a964848372eaa5027086e23b2e88a52600f8
parente0436d912df090b0c3cba9a8b818aab408756e49 (diff)
downloadrust-88b5e945e01a4599c60ac406d4949cd0329935cb.tar.gz
rust-88b5e945e01a4599c60ac406d4949cd0329935cb.zip
Make <*const/mut T>::offset_from `const fn`
-rw-r--r--src/libcore/intrinsics.rs4
-rw-r--r--src/libcore/ptr/mod.rs18
-rw-r--r--src/librustc_codegen_llvm/intrinsic.rs19
-rw-r--r--src/librustc_mir/interpret/intrinsics.rs56
-rw-r--r--src/librustc_mir/transform/qualify_consts.rs1
-rw-r--r--src/librustc_typeck/check/intrinsic.rs2
-rw-r--r--src/test/ui/consts/offset_from_ub.rs44
-rw-r--r--src/test/ui/consts/offset_from_ub.stderr81
-rw-r--r--src/test/ui/consts/offset_of.rs39
-rw-r--r--src/test/ui/offset_from.rs15
10 files changed, 276 insertions, 3 deletions
diff --git a/src/libcore/intrinsics.rs b/src/libcore/intrinsics.rs
index b240d059114..bae7ae2884f 100644
--- a/src/libcore/intrinsics.rs
+++ b/src/libcore/intrinsics.rs
@@ -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
diff --git a/src/libcore/ptr/mod.rs b/src/libcore/ptr/mod.rs
index 13ccc9b252a..a7adaa6a509 100644
--- a/src/libcore/ptr/mod.rs
+++ b/src/libcore/ptr/mod.rs
@@ -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)
     }
 
diff --git a/src/librustc_codegen_llvm/intrinsic.rs b/src/librustc_codegen_llvm/intrinsic.rs
index b7a410c3760..e940a42be7d 100644
--- a/src/librustc_codegen_llvm/intrinsic.rs
+++ b/src/librustc_codegen_llvm/intrinsic.rs
@@ -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::*;
 
@@ -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.layout_of(ty).size;
+
+                // 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),
         };
 
@@ -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.
diff --git a/src/librustc_mir/interpret/intrinsics.rs b/src/librustc_mir/interpret/intrinsics.rs
index 5fc23b4a69e..a04bf7c0f1a 100644
--- a/src/librustc_mir/interpret/intrinsics.rs
+++ b/src/librustc_mir/interpret/intrinsics.rs
@@ -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;
@@ -236,6 +236,34 @@ 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, _) = self.overflowing_binary_op(
+                    BinOp::Sub, a_offset, b_offset,
+                )?;
+                if overflowed {
+                    throw_ub_format!(
+                        "second argument to `ptr_offset_from` must be smaller than first",
+                    );
+                }
+                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)?;
             }
@@ -340,4 +368,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)
+    }
 }
diff --git a/src/librustc_mir/transform/qualify_consts.rs b/src/librustc_mir/transform/qualify_consts.rs
index 387540655f0..ec9c7a73de7 100644
--- a/src/librustc_mir/transform/qualify_consts.rs
+++ b/src/librustc_mir/transform/qualify_consts.rs
@@ -560,6 +560,7 @@ impl Qualif for IsNotPromotable {
                             | "transmute"
                             | "simd_insert"
                             | "simd_extract"
+                            | "ptr_offset_from"
                             => return true,
 
                             _ => {}
diff --git a/src/librustc_typeck/check/intrinsic.rs b/src/librustc_typeck/check/intrinsic.rs
index aeb2c40e2ef..0ebdc0672fc 100644
--- a/src/librustc_typeck/check/intrinsic.rs
+++ b/src/librustc_typeck/check/intrinsic.rs
@@ -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" |
diff --git a/src/test/ui/consts/offset_from_ub.rs b/src/test/ui/consts/offset_from_ub.rs
new file mode 100644
index 00000000000..07e1be0480c
--- /dev/null
+++ b/src/test/ui/consts/offset_from_ub.rs
@@ -0,0 +1,44 @@
+#![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
+};
+
+pub const OVERFLOW: usize = {
+    //~^ NOTE
+    let uninit = std::mem::MaybeUninit::<Struct>::uninit();
+    let base_ptr: *const Struct = &uninit as *const _ as *const Struct;
+    let field_ptr = unsafe { &(*base_ptr).field as *const u8 };
+    let offset = unsafe { (base_ptr as *const u8).offset_from(field_ptr) };
+    offset as usize
+};
+
+fn main() {}
diff --git a/src/test/ui/consts/offset_from_ub.stderr b/src/test/ui/consts/offset_from_ub.stderr
new file mode 100644
index 00000000000..12185ab5ccd
--- /dev/null
+++ b/src/test/ui/consts/offset_from_ub.stderr
@@ -0,0 +1,81 @@
+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:17:27
+   | 
+  ::: $DIR/offset_from_ub.rs:11: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:23:14
+   | 
+  ::: $DIR/offset_from_ub.rs:21: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:31:27
+   | 
+  ::: $DIR/offset_from_ub.rs:26: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: any use of this value will cause an error
+  --> $SRC_DIR/libcore/ptr/mod.rs:LL:COL
+   |
+LL |           intrinsics::ptr_offset_from(self, origin)
+   |           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+   |           |
+   |           second argument to `ptr_offset_from` must be smaller than first
+   |           inside call to `std::ptr::<impl *const u8>::offset_from` at $DIR/offset_from_ub.rs:40:27
+   | 
+  ::: $DIR/offset_from_ub.rs:35:1
+   |
+LL | / pub const OVERFLOW: 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 | | };
+   | |__-
+
+error: aborting due to 4 previous errors
+
diff --git a/src/test/ui/consts/offset_of.rs b/src/test/ui/consts/offset_of.rs
new file mode 100644
index 00000000000..7b5b4a77d9a
--- /dev/null
+++ b/src/test/ui/consts/offset_of.rs
@@ -0,0 +1,39 @@
+// 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
+};
+
+fn main() {
+    assert_eq!(OFFSET, 0);
+    assert_eq!(OFFSET_2, 1);
+}
diff --git a/src/test/ui/offset_from.rs b/src/test/ui/offset_from.rs
new file mode 100644
index 00000000000..cbbb2adf15f
--- /dev/null
+++ b/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);
+    }
+}