Skip to content

Commit 89a18cb

Browse files
committed
Add unsigned_offset_from on pointers
Like we have `add`/`sub` which are the `usize` version of `offset`, this adds the `usize` equivalent of `offset_from`. Like how `.add(d)` replaced a whole bunch of `.offset(d as isize)`, you can see from the changes here that it's fairly common that code actually knows the order between the pointers and *wants* a `usize`, not an `isize`. As a bonus, this can do `sub nuw`+`udiv exact`, rather than `sub`+`sdiv exact`, which can be optimized slightly better because it doesn't have to worry about negatives. That's why the slice iterators weren't using `offset_from`, though I haven't updated that code in this PR because slices are so perf-critical that I'll do it as its own change. This is an intrinsic, like `offset_from`, so that it can eventually be allowed in CTFE. It also allows checking the extra safety condition -- see the test confirming that CTFE catches it if you pass the pointers in the wrong order.
1 parent 6dd6840 commit 89a18cb

File tree

19 files changed

+265
-25
lines changed

19 files changed

+265
-25
lines changed

compiler/rustc_codegen_cranelift/src/intrinsics/mod.rs

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -713,14 +713,21 @@ fn codegen_regular_intrinsic_call<'tcx>(
713713
ret.write_cvalue(fx, val);
714714
};
715715

716-
ptr_offset_from, (v ptr, v base) {
716+
ptr_offset_from | ptr_offset_from_unsigned, (v ptr, v base) {
717717
let ty = substs.type_at(0);
718718
let isize_layout = fx.layout_of(fx.tcx.types.isize);
719719

720720
let pointee_size: u64 = fx.layout_of(ty).size.bytes();
721-
let diff = fx.bcx.ins().isub(ptr, base);
721+
let diff_bytes = fx.bcx.ins().isub(ptr, base);
722722
// FIXME this can be an exact division.
723-
let val = CValue::by_val(fx.bcx.ins().sdiv_imm(diff, pointee_size as i64), isize_layout);
723+
let diff = if intrinsic == sym::ptr_offset_from_unsigned {
724+
// Because diff_bytes ULT isize::MAX, this would be fine as signed,
725+
// but unsigned is slightly easier to codegen, so might as well.
726+
fx.bcx.ins().udiv_imm(diff_bytes, pointee_size as i64)
727+
} else {
728+
fx.bcx.ins().sdiv_imm(diff_bytes, pointee_size as i64)
729+
};
730+
let val = CValue::by_val(diff, isize_layout);
724731
ret.write_cvalue(fx, val);
725732
};
726733

compiler/rustc_codegen_ssa/src/mir/intrinsic.rs

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -555,21 +555,28 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
555555
}
556556
}
557557

558-
sym::ptr_offset_from => {
558+
sym::ptr_offset_from | sym::ptr_offset_from_unsigned => {
559559
let ty = substs.type_at(0);
560560
let pointee_size = bx.layout_of(ty).size;
561561

562-
// This is the same sequence that Clang emits for pointer subtraction.
563-
// It can be neither `nsw` nor `nuw` because the input is treated as
564-
// unsigned but then the output is treated as signed, so neither works.
565562
let a = args[0].immediate();
566563
let b = args[1].immediate();
567564
let a = bx.ptrtoint(a, bx.type_isize());
568565
let b = bx.ptrtoint(b, bx.type_isize());
569-
let d = bx.sub(a, b);
570566
let pointee_size = bx.const_usize(pointee_size.bytes());
571-
// this is where the signed magic happens (notice the `s` in `exactsdiv`)
572-
bx.exactsdiv(d, pointee_size)
567+
if name == sym::ptr_offset_from {
568+
// This is the same sequence that Clang emits for pointer subtraction.
569+
// It can be neither `nsw` nor `nuw` because the input is treated as
570+
// unsigned but then the output is treated as signed, so neither works.
571+
let d = bx.sub(a, b);
572+
// this is where the signed magic happens (notice the `s` in `exactsdiv`)
573+
bx.exactsdiv(d, pointee_size)
574+
} else {
575+
// The `_unsigned` version knows the relative ordering of the pointers,
576+
// so can use `sub nuw` and `udiv exact` instead of dealing in signed.
577+
let d = bx.unchecked_usub(a, b);
578+
bx.exactudiv(d, pointee_size)
579+
}
573580
}
574581

575582
_ => {

compiler/rustc_const_eval/src/interpret/intrinsics.rs

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -308,7 +308,7 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
308308
let offset_ptr = ptr.wrapping_signed_offset(offset_bytes, self);
309309
self.write_pointer(offset_ptr, dest)?;
310310
}
311-
sym::ptr_offset_from => {
311+
sym::ptr_offset_from | sym::ptr_offset_from_unsigned => {
312312
let a = self.read_pointer(&args[0])?;
313313
let b = self.read_pointer(&args[1])?;
314314

@@ -330,8 +330,8 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
330330
// Both are pointers. They must be into the same allocation.
331331
if a_alloc_id != b_alloc_id {
332332
throw_ub_format!(
333-
"ptr_offset_from cannot compute offset of pointers into different \
334-
allocations.",
333+
"{} cannot compute offset of pointers into different allocations.",
334+
intrinsic_name,
335335
);
336336
}
337337
// And they must both be valid for zero-sized accesses ("in-bounds or one past the end").
@@ -348,16 +348,30 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
348348
CheckInAllocMsg::OffsetFromTest,
349349
)?;
350350

351+
if intrinsic_name == sym::ptr_offset_from_unsigned && a_offset < b_offset {
352+
throw_ub_format!(
353+
"{} cannot compute a negative offset, but {} < {}",
354+
intrinsic_name,
355+
a_offset.bytes(),
356+
b_offset.bytes(),
357+
);
358+
}
359+
351360
// Compute offset.
352361
let usize_layout = self.layout_of(self.tcx.types.usize)?;
353362
let isize_layout = self.layout_of(self.tcx.types.isize)?;
363+
let ret_layout = if intrinsic_name == sym::ptr_offset_from {
364+
isize_layout
365+
} else {
366+
usize_layout
367+
};
354368
let a_offset = ImmTy::from_uint(a_offset.bytes(), usize_layout);
355369
let b_offset = ImmTy::from_uint(b_offset.bytes(), usize_layout);
356370
let (val, _overflowed, _ty) =
357371
self.overflowing_binary_op(BinOp::Sub, &a_offset, &b_offset)?;
358372
let pointee_layout = self.layout_of(substs.type_at(0))?;
359-
let val = ImmTy::from_scalar(val, isize_layout);
360-
let size = ImmTy::from_int(pointee_layout.size.bytes(), isize_layout);
373+
let val = ImmTy::from_scalar(val, ret_layout);
374+
let size = ImmTy::from_int(pointee_layout.size.bytes(), ret_layout);
361375
self.exact_div(&val, &size, dest)?;
362376
}
363377
}

compiler/rustc_span/src/symbol.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1079,6 +1079,7 @@ symbols! {
10791079
ptr_null,
10801080
ptr_null_mut,
10811081
ptr_offset_from,
1082+
ptr_offset_from_unsigned,
10821083
pub_macro_rules,
10831084
pub_restricted,
10841085
pure,

compiler/rustc_typeck/src/check/intrinsic.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -305,6 +305,9 @@ pub fn check_intrinsic_type(tcx: TyCtxt<'_>, it: &hir::ForeignItem<'_>) {
305305
sym::ptr_offset_from => {
306306
(1, vec![tcx.mk_imm_ptr(param(0)), tcx.mk_imm_ptr(param(0))], tcx.types.isize)
307307
}
308+
sym::ptr_offset_from_unsigned => {
309+
(1, vec![tcx.mk_imm_ptr(param(0)), tcx.mk_imm_ptr(param(0))], tcx.types.usize)
310+
}
308311
sym::unchecked_div | sym::unchecked_rem | sym::exact_div => {
309312
(1, vec![param(0), param(0)], param(0))
310313
}

library/alloc/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,7 @@
127127
#![feature(pattern)]
128128
#![feature(ptr_internals)]
129129
#![feature(ptr_metadata)]
130+
#![feature(ptr_unsigned_offset_from)]
130131
#![feature(receiver_trait)]
131132
#![feature(set_ptr_value)]
132133
#![feature(slice_group_by)]

library/alloc/src/slice.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1056,7 +1056,7 @@ where
10561056
fn drop(&mut self) {
10571057
// `T` is not a zero-sized type, and these are pointers into a slice's elements.
10581058
unsafe {
1059-
let len = self.end.offset_from(self.start) as usize;
1059+
let len = self.end.unsigned_offset_from(self.start);
10601060
ptr::copy_nonoverlapping(self.start, self.dest, len);
10611061
}
10621062
}

library/alloc/src/vec/drain.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -163,7 +163,7 @@ impl<T, A: Allocator> Drop for Drain<'_, T, A> {
163163
// it from the original vec but also avoid creating a &mut to the front since that could
164164
// invalidate raw pointers to it which some unsafe code might rely on.
165165
let vec_ptr = vec.as_mut().as_mut_ptr();
166-
let drop_offset = drop_ptr.offset_from(vec_ptr) as usize;
166+
let drop_offset = drop_ptr.unsigned_offset_from(vec_ptr);
167167
let to_drop = ptr::slice_from_raw_parts_mut(vec_ptr.add(drop_offset), drop_len);
168168
ptr::drop_in_place(to_drop);
169169
}

library/alloc/src/vec/in_place_collect.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -250,7 +250,7 @@ where
250250
let sink =
251251
self.try_fold::<_, _, Result<_, !>>(sink, write_in_place_with_drop(end)).unwrap();
252252
// iteration succeeded, don't drop head
253-
unsafe { ManuallyDrop::new(sink).dst.offset_from(dst_buf) as usize }
253+
unsafe { ManuallyDrop::new(sink).dst.unsigned_offset_from(dst_buf) }
254254
}
255255
}
256256

library/alloc/src/vec/in_place_drop.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ pub(super) struct InPlaceDrop<T> {
1010

1111
impl<T> InPlaceDrop<T> {
1212
fn len(&self) -> usize {
13-
unsafe { self.dst.offset_from(self.inner) as usize }
13+
unsafe { self.dst.unsigned_offset_from(self.inner) }
1414
}
1515
}
1616

library/alloc/src/vec/into_iter.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -169,7 +169,7 @@ impl<T, A: Allocator> Iterator for IntoIter<T, A> {
169169
let exact = if mem::size_of::<T>() == 0 {
170170
self.end.addr().wrapping_sub(self.ptr.addr())
171171
} else {
172-
unsafe { self.end.offset_from(self.ptr) as usize }
172+
unsafe { self.end.unsigned_offset_from(self.ptr) }
173173
};
174174
(exact, Some(exact))
175175
}

library/core/src/intrinsics.rs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1903,6 +1903,11 @@ extern "rust-intrinsic" {
19031903
#[rustc_const_unstable(feature = "const_ptr_offset_from", issue = "92980")]
19041904
pub fn ptr_offset_from<T>(ptr: *const T, base: *const T) -> isize;
19051905

1906+
/// See documentation of `<*const T>::unsigned_offset_from` for details.
1907+
#[rustc_const_unstable(feature = "const_ptr_offset_from", issue = "92980")]
1908+
#[cfg(not(bootstrap))]
1909+
pub fn ptr_offset_from_unsigned<T>(ptr: *const T, base: *const T) -> usize;
1910+
19061911
/// See documentation of `<*const T>::guaranteed_eq` for details.
19071912
///
19081913
/// Note that, unlike most intrinsics, this is safe to call;
@@ -2385,3 +2390,11 @@ where
23852390
{
23862391
called_in_const.call_once(arg)
23872392
}
2393+
2394+
/// Bootstrap polyfill
2395+
#[cfg(bootstrap)]
2396+
pub const unsafe fn ptr_offset_from_unsigned<T>(ptr: *const T, base: *const T) -> usize {
2397+
// SAFETY: we have stricter preconditions than `ptr_offset_from`, so can
2398+
// call it, and its output has to be positive, so we can just cast.
2399+
unsafe { ptr_offset_from(ptr, base) as _ }
2400+
}

library/core/src/ptr/const_ptr.rs

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -611,6 +611,67 @@ impl<T: ?Sized> *const T {
611611
unsafe { intrinsics::ptr_offset_from(self, origin) }
612612
}
613613

614+
/// Calculates the distance between two pointers, *where it's known that
615+
/// `self` is equal to or greater than `origin`*. The returned value is in
616+
/// units of T: the distance in bytes is divided by `mem::size_of::<T>()`.
617+
///
618+
/// This computes the same value that [`offset_from`](#method.offset_from)
619+
/// would compute, but with the added precondition that that the offset is
620+
/// guaranteed to be non-negative. This method is equivalent to
621+
/// `usize::from(self.offset_from(origin)).unwrap_unchecked()`,
622+
/// but it provides slightly more information to the optimizer, which can
623+
/// sometimes allow it to optimize slightly better with some backends.
624+
///
625+
/// This method is the inverse of [`add`](#method.add) (and, with the parameters
626+
/// in the other order, of [`sub`](#method.sub)).
627+
///
628+
/// # Safety
629+
///
630+
/// - The distance between the pointers must be non-negative (`self >= origin`)
631+
///
632+
/// - *All* the safety conditions of [`offset_from`](#method.offset_from)
633+
/// apply to this method as well; see it for the full details.
634+
///
635+
/// Importantly, despite the return type of this method being able to represent
636+
/// a larger offset, it's still *not permitted* to pass pointers which differ
637+
/// by more than `isize::MAX` *bytes*. As such, the result of this method will
638+
/// always be less than or equal to `isize::MAX as usize`.
639+
///
640+
/// # Panics
641+
///
642+
/// This function panics if `T` is a Zero-Sized Type ("ZST").
643+
///
644+
/// # Examples
645+
///
646+
/// ```
647+
/// #![feature(ptr_unsigned_offset_from)]
648+
///
649+
/// let a = [0; 5];
650+
/// let ptr1: *const i32 = &a[1];
651+
/// let ptr2: *const i32 = &a[3];
652+
/// unsafe {
653+
/// assert_eq!(ptr2.unsigned_offset_from(ptr1), 2);
654+
/// assert_eq!(ptr1.add(2), ptr2);
655+
/// assert_eq!(ptr2.sub(2), ptr1);
656+
/// assert_eq!(ptr2.unsigned_offset_from(ptr2), 0);
657+
/// }
658+
///
659+
/// // This would be incorrect, as the pointers are not correctly ordered:
660+
/// // ptr1.offset_from(ptr2)
661+
/// ```
662+
#[unstable(feature = "ptr_unsigned_offset_from", issue = "88888888")]
663+
#[rustc_const_unstable(feature = "const_ptr_offset_from", issue = "92980")]
664+
#[inline]
665+
pub const unsafe fn unsigned_offset_from(self, origin: *const T) -> usize
666+
where
667+
T: Sized,
668+
{
669+
let pointee_size = mem::size_of::<T>();
670+
assert!(0 < pointee_size && pointee_size <= isize::MAX as usize);
671+
// SAFETY: the caller must uphold the safety contract for `ptr_offset_from_unsigned`.
672+
unsafe { intrinsics::ptr_offset_from_unsigned(self, origin) }
673+
}
674+
614675
/// Returns whether two pointers are guaranteed to be equal.
615676
///
616677
/// At runtime this function behaves like `self == other`.

library/core/src/ptr/mut_ptr.rs

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -787,6 +787,66 @@ impl<T: ?Sized> *mut T {
787787
unsafe { (self as *const T).offset_from(origin) }
788788
}
789789

790+
/// Calculates the distance between two pointers, *where it's known that
791+
/// `self` is equal to or greater than `origin`*. The returned value is in
792+
/// units of T: the distance in bytes is divided by `mem::size_of::<T>()`.
793+
///
794+
/// This computes the same value that [`offset_from`](#method.offset_from)
795+
/// would compute, but with the added precondition that that the offset is
796+
/// guaranteed to be non-negative. This method is equivalent to
797+
/// `usize::from(self.offset_from(origin)).unwrap_unchecked()`,
798+
/// but it provides slightly more information to the optimizer, which can
799+
/// sometimes allow it to optimize slightly better with some backends.
800+
///
801+
/// This method is the inverse of [`add`](#method.add) (and, with the parameters
802+
/// in the other order, of [`sub`](#method.sub)).
803+
///
804+
/// # Safety
805+
///
806+
/// - The distance between the pointers must be non-negative (`self >= origin`)
807+
///
808+
/// - *All* the safety conditions of [`offset_from`](#method.offset_from)
809+
/// apply to this method as well; see it for the full details.
810+
///
811+
/// Importantly, despite the return type of this method being able to represent
812+
/// a larger offset, it's still *not permitted* to pass pointers which differ
813+
/// by more than `isize::MAX` *bytes*. As such, the result of this method will
814+
/// always be less than or equal to `isize::MAX as usize`.
815+
///
816+
/// # Panics
817+
///
818+
/// This function panics if `T` is a Zero-Sized Type ("ZST").
819+
///
820+
/// # Examples
821+
///
822+
/// ```
823+
/// #![feature(ptr_unsigned_offset_from)]
824+
///
825+
/// let mut a = [0; 5];
826+
/// let p: *mut i32 = a.as_mut_ptr();
827+
/// unsafe {
828+
/// let ptr1: *mut i32 = p.add(1);
829+
/// let ptr2: *mut i32 = p.add(3);
830+
///
831+
/// assert_eq!(ptr2.unsigned_offset_from(ptr1), 2);
832+
/// assert_eq!(ptr1.add(2), ptr2);
833+
/// assert_eq!(ptr2.sub(2), ptr1);
834+
/// assert_eq!(ptr2.unsigned_offset_from(ptr2), 0);
835+
/// }
836+
///
837+
/// // This would be incorrect, as the pointers are not correctly ordered:
838+
/// // ptr1.offset_from(ptr2)
839+
#[unstable(feature = "ptr_unsigned_offset_from", issue = "88888888")]
840+
#[rustc_const_unstable(feature = "const_ptr_offset_from", issue = "92980")]
841+
#[inline]
842+
pub const unsafe fn unsigned_offset_from(self, origin: *const T) -> usize
843+
where
844+
T: Sized,
845+
{
846+
// SAFETY: the caller must uphold the safety contract for `unsigned_offset_from`.
847+
unsafe { (self as *const T).unsigned_offset_from(origin) }
848+
}
849+
790850
/// Calculates the offset from a pointer (convenience for `.offset(count as isize)`).
791851
///
792852
/// `count` is in units of T; e.g., a `count` of 3 represents a pointer

library/core/src/slice/raw.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -215,7 +215,7 @@ pub const fn from_mut<T>(s: &mut T) -> &mut [T] {
215215
#[unstable(feature = "slice_from_ptr_range", issue = "89792")]
216216
pub unsafe fn from_ptr_range<'a, T>(range: Range<*const T>) -> &'a [T] {
217217
// SAFETY: the caller must uphold the safety contract for `from_ptr_range`.
218-
unsafe { from_raw_parts(range.start, range.end.offset_from(range.start) as usize) }
218+
unsafe { from_raw_parts(range.start, range.end.unsigned_offset_from(range.start)) }
219219
}
220220

221221
/// Performs the same functionality as [`from_ptr_range`], except that a
@@ -265,5 +265,5 @@ pub unsafe fn from_ptr_range<'a, T>(range: Range<*const T>) -> &'a [T] {
265265
#[unstable(feature = "slice_from_ptr_range", issue = "89792")]
266266
pub unsafe fn from_mut_ptr_range<'a, T>(range: Range<*mut T>) -> &'a mut [T] {
267267
// SAFETY: the caller must uphold the safety contract for `from_mut_ptr_range`.
268-
unsafe { from_raw_parts_mut(range.start, range.end.offset_from(range.start) as usize) }
268+
unsafe { from_raw_parts_mut(range.start, range.end.unsigned_offset_from(range.start)) }
269269
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
// compile-flags: -C opt-level=1
2+
// only-64bit (because we're using [ui]size)
3+
4+
#![crate_type = "lib"]
5+
#![feature(core_intrinsics)]
6+
7+
//! Basic optimizations are enabled because otherwise `x86_64-gnu-nopt` had an alloca.
8+
//! Uses a type with non-power-of-two size to avoid normalizations to shifts.
9+
10+
use std::intrinsics::*;
11+
12+
type RGB = [u8; 3];
13+
14+
// CHECK-LABEL: @offset_from_odd_size
15+
#[no_mangle]
16+
pub unsafe fn offset_from_odd_size(a: *const RGB, b: *const RGB) -> isize {
17+
// CHECK: start
18+
// CHECK-NEXT: ptrtoint
19+
// CHECK-NEXT: ptrtoint
20+
// CHECK-NEXT: sub i64
21+
// CHECK-NEXT: sdiv exact i64 %{{[0-9]+}}, 3
22+
// CHECK-NEXT: ret i64
23+
ptr_offset_from(a, b)
24+
}
25+
26+
// CHECK-LABEL: @offset_from_unsigned_odd_size
27+
#[no_mangle]
28+
pub unsafe fn offset_from_unsigned_odd_size(a: *const RGB, b: *const RGB) -> usize {
29+
// CHECK: start
30+
// CHECK-NEXT: ptrtoint
31+
// CHECK-NEXT: ptrtoint
32+
// CHECK-NEXT: sub nuw i64
33+
// CHECK-NEXT: udiv exact i64 %{{[0-9]+}}, 3
34+
// CHECK-NEXT: ret i64
35+
ptr_offset_from_unsigned(a, b)
36+
}

0 commit comments

Comments
 (0)