Skip to content

Commit 88b5e94

Browse files
committed
Make <*const/mut T>::offset_from const fn
1 parent e0436d9 commit 88b5e94

File tree

10 files changed

+276
-3
lines changed

10 files changed

+276
-3
lines changed

src/libcore/intrinsics.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1339,6 +1339,10 @@ extern "rust-intrinsic" {
13391339
/// Emits a `!nontemporal` store according to LLVM (see their docs).
13401340
/// Probably will never become stable.
13411341
pub fn nontemporal_store<T>(ptr: *mut T, val: T);
1342+
1343+
/// See documentation of `<*const T>::offset_from` for details.
1344+
#[cfg(not(bootstrap))]
1345+
pub fn ptr_offset_from<T>(ptr: *const T, base: *const T) -> isize;
13421346
}
13431347

13441348
// Some functions are defined here because they accidentally got made

src/libcore/ptr/mod.rs

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1286,7 +1286,22 @@ impl<T: ?Sized> *const T {
12861286
/// }
12871287
/// ```
12881288
#[unstable(feature = "ptr_offset_from", issue = "41079")]
1289+
#[cfg(not(bootstrap))]
1290+
#[rustc_const_unstable(feature = "const_ptr_offset_from")]
12891291
#[inline]
1292+
pub const unsafe fn offset_from(self, origin: *const T) -> isize where T: Sized {
1293+
let pointee_size = mem::size_of::<T>();
1294+
let ok = 0 < pointee_size && pointee_size <= isize::max_value() as usize;
1295+
// assert that the pointee size is valid in a const eval compatible way
1296+
// FIXME: do this with a real assert at some point
1297+
[()][(!ok) as usize];
1298+
intrinsics::ptr_offset_from(self, origin)
1299+
}
1300+
1301+
#[unstable(feature = "ptr_offset_from", issue = "41079")]
1302+
#[inline]
1303+
#[cfg(bootstrap)]
1304+
/// bootstrap
12901305
pub unsafe fn offset_from(self, origin: *const T) -> isize where T: Sized {
12911306
let pointee_size = mem::size_of::<T>();
12921307
assert!(0 < pointee_size && pointee_size <= isize::max_value() as usize);
@@ -2013,8 +2028,9 @@ impl<T: ?Sized> *mut T {
20132028
/// }
20142029
/// ```
20152030
#[unstable(feature = "ptr_offset_from", issue = "41079")]
2031+
#[rustc_const_unstable(feature = "const_ptr_offset_from")]
20162032
#[inline]
2017-
pub unsafe fn offset_from(self, origin: *const T) -> isize where T: Sized {
2033+
pub const unsafe fn offset_from(self, origin: *const T) -> isize where T: Sized {
20182034
(self as *const T).offset_from(origin)
20192035
}
20202036

src/librustc_codegen_llvm/intrinsic.rs

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ use rustc::mir::interpret::GlobalId;
1919
use rustc_codegen_ssa::common::{IntPredicate, TypeKind};
2020
use rustc::hir;
2121
use syntax::ast::{self, FloatTy};
22+
use rustc_target::abi::HasDataLayout;
2223

2324
use rustc_codegen_ssa::traits::*;
2425

@@ -694,6 +695,23 @@ impl IntrinsicCallMethods<'tcx> for Builder<'a, 'll, 'tcx> {
694695
return;
695696
}
696697

698+
"ptr_offset_from" => {
699+
let ty = substs.type_at(0);
700+
let pointee_size = self.layout_of(ty).size;
701+
702+
// This is the same sequence that Clang emits for pointer subtraction.
703+
// It can be neither `nsw` nor `nuw` because the input is treated as
704+
// unsigned but then the output is treated as signed, so neither works.
705+
let a = args[0].immediate();
706+
let b = args[1].immediate();
707+
let a = self.ptrtoint(a, self.type_isize());
708+
let b = self.ptrtoint(b, self.type_isize());
709+
let d = self.sub(a, b);
710+
let pointee_size = self.const_usize(pointee_size.bytes());
711+
// this is where the signed magic happens (notice the `s` in `exactsdiv`)
712+
self.exactsdiv(d, pointee_size)
713+
}
714+
697715
_ => bug!("unknown intrinsic '{}'", name),
698716
};
699717

@@ -1218,7 +1236,6 @@ fn generic_simd_intrinsic(
12181236
// The `fn simd_bitmask(vector) -> unsigned integer` intrinsic takes a
12191237
// vector mask and returns an unsigned integer containing the most
12201238
// significant bit (MSB) of each lane.
1221-
use rustc_target::abi::HasDataLayout;
12221239

12231240
// If the vector has less than 8 lanes, an u8 is returned with zeroed
12241241
// trailing bits.

src/librustc_mir/interpret/intrinsics.rs

Lines changed: 55 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ use rustc::mir::BinOp;
1212
use rustc::mir::interpret::{InterpResult, Scalar, GlobalId, ConstValue};
1313

1414
use super::{
15-
Machine, PlaceTy, OpTy, InterpCx,
15+
Machine, PlaceTy, OpTy, InterpCx, ImmTy,
1616
};
1717

1818
mod type_name;
@@ -236,6 +236,34 @@ impl<'mir, 'tcx, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
236236
let result = Scalar::from_uint(truncated_bits, layout.size);
237237
self.write_scalar(result, dest)?;
238238
}
239+
240+
"ptr_offset_from" => {
241+
let a = self.read_immediate(args[0])?.to_scalar()?.to_ptr()?;
242+
let b = self.read_immediate(args[1])?.to_scalar()?.to_ptr()?;
243+
if a.alloc_id != b.alloc_id {
244+
throw_ub_format!(
245+
"ptr_offset_from cannot compute offset of pointers into different \
246+
allocations.",
247+
);
248+
}
249+
let usize_layout = self.layout_of(self.tcx.types.usize)?;
250+
let a_offset = ImmTy::from_uint(a.offset.bytes(), usize_layout);
251+
let b_offset = ImmTy::from_uint(b.offset.bytes(), usize_layout);
252+
let (val, overflowed, _) = self.overflowing_binary_op(
253+
BinOp::Sub, a_offset, b_offset,
254+
)?;
255+
if overflowed {
256+
throw_ub_format!(
257+
"second argument to `ptr_offset_from` must be smaller than first",
258+
);
259+
}
260+
let pointee_layout = self.layout_of(substs.type_at(0))?;
261+
let isize_layout = self.layout_of(self.tcx.types.isize)?;
262+
let val = ImmTy::from_scalar(val, isize_layout);
263+
let size = ImmTy::from_int(pointee_layout.size.bytes(), isize_layout);
264+
self.exact_div(val, size, dest)?;
265+
}
266+
239267
"transmute" => {
240268
self.copy_op_transmute(args[0], dest)?;
241269
}
@@ -340,4 +368,30 @@ impl<'mir, 'tcx, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
340368
return Ok(false);
341369
}
342370
}
371+
372+
pub fn exact_div(
373+
&mut self,
374+
a: ImmTy<'tcx, M::PointerTag>,
375+
b: ImmTy<'tcx, M::PointerTag>,
376+
dest: PlaceTy<'tcx, M::PointerTag>,
377+
) -> InterpResult<'tcx> {
378+
// Performs an exact division, resulting in undefined behavior where
379+
// `x % y != 0` or `y == 0` or `x == T::min_value() && y == -1`.
380+
// First, check x % y != 0.
381+
if self.binary_op(BinOp::Rem, a, b)?.to_bits()? != 0 {
382+
// Then, check if `b` is -1, which is the "min_value / -1" case.
383+
let minus1 = Scalar::from_int(-1, dest.layout.size);
384+
let b = b.to_scalar().unwrap();
385+
if b == minus1 {
386+
throw_ub_format!("exact_div: result of dividing MIN by -1 cannot be represented")
387+
} else {
388+
throw_ub_format!(
389+
"exact_div: {} cannot be divided by {} without remainder",
390+
a.to_scalar().unwrap(),
391+
b,
392+
)
393+
}
394+
}
395+
self.binop_ignore_overflow(BinOp::Div, a, b, dest)
396+
}
343397
}

src/librustc_mir/transform/qualify_consts.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -560,6 +560,7 @@ impl Qualif for IsNotPromotable {
560560
| "transmute"
561561
| "simd_insert"
562562
| "simd_extract"
563+
| "ptr_offset_from"
563564
=> return true,
564565

565566
_ => {}

src/librustc_typeck/check/intrinsic.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -307,6 +307,8 @@ pub fn check_intrinsic_type(tcx: TyCtxt<'_>, it: &hir::ForeignItem) {
307307
(1, vec![param(0), param(0)],
308308
tcx.intern_tup(&[param(0), tcx.types.bool])),
309309

310+
"ptr_offset_from" =>
311+
(1, vec![ tcx.mk_imm_ptr(param(0)), tcx.mk_imm_ptr(param(0)) ], tcx.types.isize),
310312
"unchecked_div" | "unchecked_rem" | "exact_div" =>
311313
(1, vec![param(0), param(0)], param(0)),
312314
"unchecked_shl" | "unchecked_shr" |

src/test/ui/consts/offset_from_ub.rs

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
#![feature(const_raw_ptr_deref)]
2+
#![feature(const_ptr_offset_from)]
3+
#![feature(ptr_offset_from)]
4+
5+
#[repr(C)]
6+
struct Struct {
7+
data: u8,
8+
field: u8,
9+
}
10+
11+
pub const DIFFERENT_ALLOC: usize = {
12+
//~^ NOTE
13+
let uninit = std::mem::MaybeUninit::<Struct>::uninit();
14+
let base_ptr: *const Struct = &uninit as *const _ as *const Struct;
15+
let uninit2 = std::mem::MaybeUninit::<Struct>::uninit();
16+
let field_ptr: *const Struct = &uninit2 as *const _ as *const Struct;
17+
let offset = unsafe { field_ptr.offset_from(base_ptr) };
18+
offset as usize
19+
};
20+
21+
pub const NOT_PTR: usize = {
22+
//~^ NOTE
23+
unsafe { (42 as *const u8).offset_from(&5u8) as usize }
24+
};
25+
26+
pub const NOT_MULTIPLE_OF_SIZE: usize = {
27+
//~^ NOTE
28+
let data = [5u8, 6, 7];
29+
let base_ptr = data.as_ptr();
30+
let field_ptr = &data[1] as *const u8 as *const u16;
31+
let offset = unsafe { field_ptr.offset_from(base_ptr as *const u16) };
32+
offset as usize
33+
};
34+
35+
pub const OVERFLOW: usize = {
36+
//~^ NOTE
37+
let uninit = std::mem::MaybeUninit::<Struct>::uninit();
38+
let base_ptr: *const Struct = &uninit as *const _ as *const Struct;
39+
let field_ptr = unsafe { &(*base_ptr).field as *const u8 };
40+
let offset = unsafe { (base_ptr as *const u8).offset_from(field_ptr) };
41+
offset as usize
42+
};
43+
44+
fn main() {}
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
error: any use of this value will cause an error
2+
--> $SRC_DIR/libcore/ptr/mod.rs:LL:COL
3+
|
4+
LL | intrinsics::ptr_offset_from(self, origin)
5+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
6+
| |
7+
| ptr_offset_from cannot compute offset of pointers into different allocations.
8+
| inside call to `std::ptr::<impl *const Struct>::offset_from` at $DIR/offset_from_ub.rs:17:27
9+
|
10+
::: $DIR/offset_from_ub.rs:11:1
11+
|
12+
LL | / pub const DIFFERENT_ALLOC: usize = {
13+
LL | |
14+
LL | | let uninit = std::mem::MaybeUninit::<Struct>::uninit();
15+
LL | | let base_ptr: *const Struct = &uninit as *const _ as *const Struct;
16+
... |
17+
LL | | offset as usize
18+
LL | | };
19+
| |__-
20+
|
21+
= note: `#[deny(const_err)]` on by default
22+
23+
error: any use of this value will cause an error
24+
--> $SRC_DIR/libcore/ptr/mod.rs:LL:COL
25+
|
26+
LL | intrinsics::ptr_offset_from(self, origin)
27+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
28+
| |
29+
| a memory access tried to interpret some bytes as a pointer
30+
| inside call to `std::ptr::<impl *const u8>::offset_from` at $DIR/offset_from_ub.rs:23:14
31+
|
32+
::: $DIR/offset_from_ub.rs:21:1
33+
|
34+
LL | / pub const NOT_PTR: usize = {
35+
LL | |
36+
LL | | unsafe { (42 as *const u8).offset_from(&5u8) as usize }
37+
LL | | };
38+
| |__-
39+
40+
error: any use of this value will cause an error
41+
--> $SRC_DIR/libcore/ptr/mod.rs:LL:COL
42+
|
43+
LL | intrinsics::ptr_offset_from(self, origin)
44+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
45+
| |
46+
| exact_div: 1 cannot be divided by 2 without remainder
47+
| inside call to `std::ptr::<impl *const u16>::offset_from` at $DIR/offset_from_ub.rs:31:27
48+
|
49+
::: $DIR/offset_from_ub.rs:26:1
50+
|
51+
LL | / pub const NOT_MULTIPLE_OF_SIZE: usize = {
52+
LL | |
53+
LL | | let data = [5u8, 6, 7];
54+
LL | | let base_ptr = data.as_ptr();
55+
... |
56+
LL | | offset as usize
57+
LL | | };
58+
| |__-
59+
60+
error: any use of this value will cause an error
61+
--> $SRC_DIR/libcore/ptr/mod.rs:LL:COL
62+
|
63+
LL | intrinsics::ptr_offset_from(self, origin)
64+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
65+
| |
66+
| second argument to `ptr_offset_from` must be smaller than first
67+
| inside call to `std::ptr::<impl *const u8>::offset_from` at $DIR/offset_from_ub.rs:40:27
68+
|
69+
::: $DIR/offset_from_ub.rs:35:1
70+
|
71+
LL | / pub const OVERFLOW: usize = {
72+
LL | |
73+
LL | | let uninit = std::mem::MaybeUninit::<Struct>::uninit();
74+
LL | | let base_ptr: *const Struct = &uninit as *const _ as *const Struct;
75+
... |
76+
LL | | offset as usize
77+
LL | | };
78+
| |__-
79+
80+
error: aborting due to 4 previous errors
81+

src/test/ui/consts/offset_of.rs

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
// run-pass
2+
3+
#![feature(const_raw_ptr_deref)]
4+
#![feature(const_ptr_offset_from)]
5+
#![feature(ptr_offset_from)]
6+
7+
struct Struct {
8+
field: (),
9+
}
10+
11+
#[repr(C)]
12+
struct Struct2 {
13+
data: u8,
14+
field: u8,
15+
}
16+
17+
pub const OFFSET: usize = {
18+
let uninit = std::mem::MaybeUninit::<Struct>::uninit();
19+
let base_ptr: *const Struct = &uninit as *const _ as *const Struct;
20+
// The following statement is UB (taking the address of an uninitialized field).
21+
// Const eval doesn't detect this right now, but it may stop compiling at some point
22+
// in the future.
23+
let field_ptr = unsafe { &(*base_ptr).field as *const () as *const u8 };
24+
let offset = unsafe { field_ptr.offset_from(base_ptr as *const u8) };
25+
offset as usize
26+
};
27+
28+
pub const OFFSET_2: usize = {
29+
let uninit = std::mem::MaybeUninit::<Struct2>::uninit();
30+
let base_ptr: *const Struct2 = &uninit as *const _ as *const Struct2;
31+
let field_ptr = unsafe { &(*base_ptr).field as *const u8 };
32+
let offset = unsafe { field_ptr.offset_from(base_ptr as *const u8) };
33+
offset as usize
34+
};
35+
36+
fn main() {
37+
assert_eq!(OFFSET, 0);
38+
assert_eq!(OFFSET_2, 1);
39+
}

src/test/ui/offset_from.rs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
// run-pass
2+
3+
#![feature(ptr_offset_from)]
4+
5+
fn main() {
6+
let mut a = [0; 5];
7+
let ptr1: *mut i32 = &mut a[1];
8+
let ptr2: *mut i32 = &mut a[3];
9+
unsafe {
10+
assert_eq!(ptr2.offset_from(ptr1), 2);
11+
assert_eq!(ptr1.offset_from(ptr2), -2);
12+
assert_eq!(ptr1.offset(2), ptr2);
13+
assert_eq!(ptr2.offset(-2), ptr1);
14+
}
15+
}

0 commit comments

Comments
 (0)