Skip to content

Commit 1d2ea98

Browse files
committed
Auto merge of #95837 - scottmcm:ptr-offset-from-unsigned, r=oli-obk
Add `sub_ptr` on pointers (the `usize` version of `offset_from`) We have `add`/`sub` which are the `usize` versions 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.
2 parents 0cd939e + 003b954 commit 1d2ea98

File tree

20 files changed

+307
-28
lines changed

20 files changed

+307
-28
lines changed

compiler/rustc_codegen_cranelift/src/intrinsics/mod.rs

+10-3
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 ULE 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

+14-7
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

+31-8
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,39 @@ 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)?;
354-
let a_offset = ImmTy::from_uint(a_offset.bytes(), usize_layout);
355-
let b_offset = ImmTy::from_uint(b_offset.bytes(), usize_layout);
356-
let (val, _overflowed, _ty) =
363+
let ret_layout = if intrinsic_name == sym::ptr_offset_from {
364+
isize_layout
365+
} else {
366+
usize_layout
367+
};
368+
369+
// The subtraction is always done in `isize` to enforce
370+
// the "no more than `isize::MAX` apart" requirement.
371+
let a_offset = ImmTy::from_uint(a_offset.bytes(), isize_layout);
372+
let b_offset = ImmTy::from_uint(b_offset.bytes(), isize_layout);
373+
let (val, overflowed, _ty) =
357374
self.overflowing_binary_op(BinOp::Sub, &a_offset, &b_offset)?;
375+
if overflowed {
376+
throw_ub_format!("Pointers were too far apart for {}", intrinsic_name);
377+
}
378+
358379
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);
380+
// This re-interprets an isize at ret_layout, but we already checked
381+
// that if ret_layout is usize, then the result must be non-negative.
382+
let val = ImmTy::from_scalar(val, ret_layout);
383+
let size = ImmTy::from_int(pointee_layout.size.bytes(), ret_layout);
361384
self.exact_div(&val, &size, dest)?;
362385
}
363386
}

compiler/rustc_span/src/symbol.rs

+1
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

+3
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

+1
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_sub_ptr)]
130131
#![feature(receiver_trait)]
131132
#![feature(set_ptr_value)]
132133
#![feature(slice_group_by)]

library/alloc/src/slice.rs

+1-1
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.sub_ptr(self.start);
10601060
ptr::copy_nonoverlapping(self.start, self.dest, len);
10611061
}
10621062
}

library/alloc/src/vec/drain.rs

+1-1
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.sub_ptr(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

+1-1
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.sub_ptr(dst_buf) }
254254
}
255255
}
256256

library/alloc/src/vec/in_place_drop.rs

+1-1
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.sub_ptr(self.inner) }
1414
}
1515
}
1616

library/alloc/src/vec/into_iter.rs

+1-1
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.sub_ptr(self.ptr) }
173173
};
174174
(exact, Some(exact))
175175
}

library/core/src/intrinsics.rs

+13
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>::sub_ptr` 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/lib.rs

+1
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,7 @@
126126
#![feature(const_option)]
127127
#![feature(const_option_ext)]
128128
#![feature(const_pin)]
129+
#![feature(const_ptr_sub_ptr)]
129130
#![feature(const_replace)]
130131
#![feature(const_ptr_as_ref)]
131132
#![feature(const_ptr_is_null)]

library/core/src/ptr/const_ptr.rs

+77
Original file line numberDiff line numberDiff line change
@@ -611,6 +611,83 @@ 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 can be though of as recovering the `count` that was passed
626+
/// to [`add`](#method.add) (or, with the parameters in the other order,
627+
/// to [`sub`](#method.sub)). The following are all equivalent, assuming
628+
/// that their safety preconditions are met:
629+
/// ```rust
630+
/// # #![feature(ptr_sub_ptr)]
631+
/// # unsafe fn blah(ptr: *const i32, origin: *const i32, count: usize) -> bool {
632+
/// ptr.sub_ptr(origin) == count
633+
/// # &&
634+
/// origin.add(count) == ptr
635+
/// # &&
636+
/// ptr.sub(count) == origin
637+
/// # }
638+
/// ```
639+
///
640+
/// # Safety
641+
///
642+
/// - The distance between the pointers must be non-negative (`self >= origin`)
643+
///
644+
/// - *All* the safety conditions of [`offset_from`](#method.offset_from)
645+
/// apply to this method as well; see it for the full details.
646+
///
647+
/// Importantly, despite the return type of this method being able to represent
648+
/// a larger offset, it's still *not permitted* to pass pointers which differ
649+
/// by more than `isize::MAX` *bytes*. As such, the result of this method will
650+
/// always be less than or equal to `isize::MAX as usize`.
651+
///
652+
/// # Panics
653+
///
654+
/// This function panics if `T` is a Zero-Sized Type ("ZST").
655+
///
656+
/// # Examples
657+
///
658+
/// ```
659+
/// #![feature(ptr_sub_ptr)]
660+
///
661+
/// let a = [0; 5];
662+
/// let ptr1: *const i32 = &a[1];
663+
/// let ptr2: *const i32 = &a[3];
664+
/// unsafe {
665+
/// assert_eq!(ptr2.sub_ptr(ptr1), 2);
666+
/// assert_eq!(ptr1.add(2), ptr2);
667+
/// assert_eq!(ptr2.sub(2), ptr1);
668+
/// assert_eq!(ptr2.sub_ptr(ptr2), 0);
669+
/// }
670+
///
671+
/// // This would be incorrect, as the pointers are not correctly ordered:
672+
/// // ptr1.offset_from(ptr2)
673+
/// ```
674+
#[unstable(feature = "ptr_sub_ptr", issue = "95892")]
675+
#[rustc_const_unstable(feature = "const_ptr_sub_ptr", issue = "95892")]
676+
#[inline]
677+
pub const unsafe fn sub_ptr(self, origin: *const T) -> usize
678+
where
679+
T: Sized,
680+
{
681+
// SAFETY: The comparison has no side-effects, and the intrinsic
682+
// does this check internally in the CTFE implementation.
683+
unsafe { assert_unsafe_precondition!(self >= origin) };
684+
685+
let pointee_size = mem::size_of::<T>();
686+
assert!(0 < pointee_size && pointee_size <= isize::MAX as usize);
687+
// SAFETY: the caller must uphold the safety contract for `ptr_offset_from_unsigned`.
688+
unsafe { intrinsics::ptr_offset_from_unsigned(self, origin) }
689+
}
690+
614691
/// Returns whether two pointers are guaranteed to be equal.
615692
///
616693
/// At runtime this function behaves like `self == other`.

library/core/src/ptr/mut_ptr.rs

+72
Original file line numberDiff line numberDiff line change
@@ -787,6 +787,78 @@ 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 can be though of as recovering the `count` that was passed
802+
/// to [`add`](#method.add) (or, with the parameters in the other order,
803+
/// to [`sub`](#method.sub)). The following are all equivalent, assuming
804+
/// that their safety preconditions are met:
805+
/// ```rust
806+
/// # #![feature(ptr_sub_ptr)]
807+
/// # unsafe fn blah(ptr: *mut i32, origin: *mut i32, count: usize) -> bool {
808+
/// ptr.sub_ptr(origin) == count
809+
/// # &&
810+
/// origin.add(count) == ptr
811+
/// # &&
812+
/// ptr.sub(count) == origin
813+
/// # }
814+
/// ```
815+
///
816+
/// # Safety
817+
///
818+
/// - The distance between the pointers must be non-negative (`self >= origin`)
819+
///
820+
/// - *All* the safety conditions of [`offset_from`](#method.offset_from)
821+
/// apply to this method as well; see it for the full details.
822+
///
823+
/// Importantly, despite the return type of this method being able to represent
824+
/// a larger offset, it's still *not permitted* to pass pointers which differ
825+
/// by more than `isize::MAX` *bytes*. As such, the result of this method will
826+
/// always be less than or equal to `isize::MAX as usize`.
827+
///
828+
/// # Panics
829+
///
830+
/// This function panics if `T` is a Zero-Sized Type ("ZST").
831+
///
832+
/// # Examples
833+
///
834+
/// ```
835+
/// #![feature(ptr_sub_ptr)]
836+
///
837+
/// let mut a = [0; 5];
838+
/// let p: *mut i32 = a.as_mut_ptr();
839+
/// unsafe {
840+
/// let ptr1: *mut i32 = p.add(1);
841+
/// let ptr2: *mut i32 = p.add(3);
842+
///
843+
/// assert_eq!(ptr2.sub_ptr(ptr1), 2);
844+
/// assert_eq!(ptr1.add(2), ptr2);
845+
/// assert_eq!(ptr2.sub(2), ptr1);
846+
/// assert_eq!(ptr2.sub_ptr(ptr2), 0);
847+
/// }
848+
///
849+
/// // This would be incorrect, as the pointers are not correctly ordered:
850+
/// // ptr1.offset_from(ptr2)
851+
#[unstable(feature = "ptr_sub_ptr", issue = "95892")]
852+
#[rustc_const_unstable(feature = "const_ptr_sub_ptr", issue = "95892")]
853+
#[inline]
854+
pub const unsafe fn sub_ptr(self, origin: *const T) -> usize
855+
where
856+
T: Sized,
857+
{
858+
// SAFETY: the caller must uphold the safety contract for `sub_ptr`.
859+
unsafe { (self as *const T).sub_ptr(origin) }
860+
}
861+
790862
/// Calculates the offset from a pointer (convenience for `.offset(count as isize)`).
791863
///
792864
/// `count` is in units of T; e.g., a `count` of 3 represents a pointer

0 commit comments

Comments
 (0)