Skip to content

slice.get(i) should use a slice projection in MIR, like slice[i] does #139118

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jun 1, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions compiler/rustc_hir_analysis/src/check/intrinsic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -265,6 +265,7 @@ pub(crate) fn check_intrinsic_type(
vec![Ty::new_imm_ptr(tcx, param(0)), tcx.types.isize],
Ty::new_imm_ptr(tcx, param(0)),
),
sym::slice_get_unchecked => (3, 0, vec![param(1), tcx.types.usize], param(0)),
sym::ptr_mask => (
1,
0,
Expand Down
46 changes: 46 additions & 0 deletions compiler/rustc_mir_transform/src/lower_intrinsics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -262,6 +262,52 @@ impl<'tcx> crate::MirPass<'tcx> for LowerIntrinsics {
});
terminator.kind = TerminatorKind::Goto { target };
}
sym::slice_get_unchecked => {
let target = target.unwrap();
let Ok([ptrish, index]) = take_array(args) else {
span_bug!(
terminator.source_info.span,
"Wrong number of arguments for {intrinsic:?}",
);
};

let place = ptrish.node.place().unwrap();
assert!(!place.is_indirect());
let updated_place = place.project_deeper(
&[
ProjectionElem::Deref,
ProjectionElem::Index(
index.node.place().unwrap().as_local().unwrap(),
),
],
tcx,
);

let ret_ty = generic_args.type_at(0);
let rvalue = match *ret_ty.kind() {
ty::RawPtr(_, Mutability::Not) => {
Rvalue::RawPtr(RawPtrKind::Const, updated_place)
}
ty::RawPtr(_, Mutability::Mut) => {
Rvalue::RawPtr(RawPtrKind::Mut, updated_place)
}
ty::Ref(region, _, Mutability::Not) => {
Rvalue::Ref(region, BorrowKind::Shared, updated_place)
}
ty::Ref(region, _, Mutability::Mut) => Rvalue::Ref(
region,
BorrowKind::Mut { kind: MutBorrowKind::Default },
updated_place,
),
_ => bug!("Unknown return type {ret_ty:?}"),
};

block.statements.push(Statement {
source_info: terminator.source_info,
kind: StatementKind::Assign(Box::new((*destination, rvalue))),
});
Comment on lines +287 to +308
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

reminder to @workingjubilee: the StatementKind::Assign is to the fresh SSA variable. you're reading code implementing MIR which is its own thing, not code implementing the Rust surface syntax. it may make sense to be a bit confused after going to sleep, waking up, and rereading this, because slice indexing can be on the lhs or rhs in Rust yet this orders it always in a certain way, but that's because that's not what is happening here. yesterday you were pretty sure things were good precisely because you didn't overthink it, which was Correct, Actually.

terminator.kind = TerminatorKind::Goto { target };
}
sym::transmute | sym::transmute_unchecked => {
let dst_ty = destination.ty(local_decls, tcx).ty;
let Ok([arg]) = take_array(args) else {
Expand Down
1 change: 1 addition & 0 deletions compiler/rustc_span/src/symbol.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2000,6 +2000,7 @@ symbols! {
slice,
slice_from_raw_parts,
slice_from_raw_parts_mut,
slice_get_unchecked,
slice_into_vec,
slice_iter,
slice_len_fn,
Expand Down
39 changes: 39 additions & 0 deletions library/core/src/intrinsics/bounds.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
//! Various traits used to restrict intrinsics to not-completely-wrong types.

/// Types with a built-in dereference operator in runtime MIR,
/// aka references and raw pointers.
///
/// # Safety
/// Must actually *be* such a type.
pub unsafe trait BuiltinDeref: Sized {
type Pointee: ?Sized;
}

unsafe impl<T: ?Sized> BuiltinDeref for &mut T {
type Pointee = T;
}
unsafe impl<T: ?Sized> BuiltinDeref for &T {
type Pointee = T;
}
unsafe impl<T: ?Sized> BuiltinDeref for *mut T {
type Pointee = T;
}
unsafe impl<T: ?Sized> BuiltinDeref for *const T {
type Pointee = T;
}

pub trait ChangePointee<U: ?Sized>: BuiltinDeref {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this trait is purely type-projection hijinx, by comparison, so I think it's fine for it to be safe.

type Output;
}
impl<'a, T: ?Sized + 'a, U: ?Sized + 'a> ChangePointee<U> for &'a mut T {
type Output = &'a mut U;
}
impl<'a, T: ?Sized + 'a, U: ?Sized + 'a> ChangePointee<U> for &'a T {
type Output = &'a U;
}
impl<T: ?Sized, U: ?Sized> ChangePointee<U> for *mut T {
type Output = *mut U;
}
impl<T: ?Sized, U: ?Sized> ChangePointee<U> for *const T {
type Output = *const U;
}
45 changes: 32 additions & 13 deletions library/core/src/intrinsics/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@
use crate::marker::{ConstParamTy, DiscriminantKind, Tuple};
use crate::ptr;

mod bounds;
pub mod fallback;
pub mod mir;
pub mod simd;
Expand Down Expand Up @@ -1730,7 +1731,7 @@ pub const fn needs_drop<T: ?Sized>() -> bool;
#[rustc_intrinsic_const_stable_indirect]
#[rustc_nounwind]
#[rustc_intrinsic]
pub const unsafe fn offset<Ptr, Delta>(dst: Ptr, offset: Delta) -> Ptr;
pub const unsafe fn offset<Ptr: bounds::BuiltinDeref, Delta>(dst: Ptr, offset: Delta) -> Ptr;

/// Calculates the offset from a pointer, potentially wrapping.
///
Expand All @@ -1751,6 +1752,33 @@ pub const unsafe fn offset<Ptr, Delta>(dst: Ptr, offset: Delta) -> Ptr;
#[rustc_intrinsic]
pub const unsafe fn arith_offset<T>(dst: *const T, offset: isize) -> *const T;

/// Projects to the `index`-th element of `slice_ptr`, as the same kind of pointer
/// as the slice was provided -- so `&mut [T] → &mut T`, `&[T] → &T`,
/// `*mut [T] → *mut T`, or `*const [T] → *const T` -- without a bounds check.
///
/// This is exposed via `<usize as SliceIndex>::get(_unchecked)(_mut)`,
/// and isn't intended to be used elsewhere.
///
/// Expands in MIR to `{&, &mut, &raw const, &raw mut} (*slice_ptr)[index]`,
/// depending on the types involved, so no backend support is needed.
///
/// # Safety
///
/// - `index < PtrMetadata(slice_ptr)`, so the indexing is in-bounds for the slice
/// - the resulting offsetting is in-bounds of the allocated object, which is
/// always the case for references, but needs to be upheld manually for pointers
#[cfg(not(bootstrap))]
#[rustc_nounwind]
#[rustc_intrinsic]
pub const unsafe fn slice_get_unchecked<
ItemPtr: bounds::ChangePointee<[T], Pointee = T, Output = SlicePtr>,
SlicePtr,
T,
>(
slice_ptr: SlicePtr,
index: usize,
) -> ItemPtr;

/// Masks out bits of the pointer according to a mask.
///
/// Note that, unlike most intrinsics, this is safe to call;
Expand Down Expand Up @@ -3575,18 +3603,9 @@ pub const fn type_id<T: ?Sized + 'static>() -> u128;
#[unstable(feature = "core_intrinsics", issue = "none")]
#[rustc_intrinsic_const_stable_indirect]
#[rustc_intrinsic]
pub const fn aggregate_raw_ptr<P: AggregateRawPtr<D, Metadata = M>, D, M>(data: D, meta: M) -> P;

#[unstable(feature = "core_intrinsics", issue = "none")]
pub trait AggregateRawPtr<D> {
type Metadata: Copy;
}
impl<P: ?Sized, T: ptr::Thin> AggregateRawPtr<*const T> for *const P {
type Metadata = <P as ptr::Pointee>::Metadata;
}
impl<P: ?Sized, T: ptr::Thin> AggregateRawPtr<*mut T> for *mut P {
type Metadata = <P as ptr::Pointee>::Metadata;
}
pub const fn aggregate_raw_ptr<P: bounds::BuiltinDeref, D, M>(data: D, meta: M) -> P
where
<P as bounds::BuiltinDeref>::Pointee: ptr::Pointee<Metadata = M>;

/// Lowers in MIR to `Rvalue::UnaryOp` with `UnOp::PtrMetadata`.
///
Expand Down
56 changes: 49 additions & 7 deletions library/core/src/slice/index.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
//! Indexing implementations for `[T]`.

#[cfg(not(bootstrap))]
use crate::intrinsics::slice_get_unchecked;
use crate::panic::const_panic;
use crate::ub_checks::assert_unsafe_precondition;
use crate::{ops, range};
Expand Down Expand Up @@ -83,13 +85,15 @@ const fn slice_end_index_overflow_fail() -> ! {
// Both the safe and unsafe public methods share these helpers,
// which use intrinsics directly to get *no* extra checks.

#[cfg(bootstrap)]
#[inline(always)]
const unsafe fn get_noubcheck<T>(ptr: *const [T], index: usize) -> *const T {
let ptr = ptr as *const T;
// SAFETY: The caller already checked these preconditions
unsafe { crate::intrinsics::offset(ptr, index) }
}

#[cfg(bootstrap)]
#[inline(always)]
const unsafe fn get_mut_noubcheck<T>(ptr: *mut [T], index: usize) -> *mut T {
let ptr = ptr as *mut T;
Expand All @@ -103,8 +107,9 @@ const unsafe fn get_offset_len_noubcheck<T>(
offset: usize,
len: usize,
) -> *const [T] {
let ptr = ptr as *const T;
// SAFETY: The caller already checked these preconditions
let ptr = unsafe { get_noubcheck(ptr, offset) };
let ptr = unsafe { crate::intrinsics::offset(ptr, offset) };
crate::intrinsics::aggregate_raw_ptr(ptr, len)
}

Expand All @@ -114,8 +119,9 @@ const unsafe fn get_offset_len_mut_noubcheck<T>(
offset: usize,
len: usize,
) -> *mut [T] {
let ptr = ptr as *mut T;
// SAFETY: The caller already checked these preconditions
let ptr = unsafe { get_mut_noubcheck(ptr, offset) };
let ptr = unsafe { crate::intrinsics::offset(ptr, offset) };
crate::intrinsics::aggregate_raw_ptr(ptr, len)
}

Expand Down Expand Up @@ -224,15 +230,35 @@ unsafe impl<T> SliceIndex<[T]> for usize {

#[inline]
fn get(self, slice: &[T]) -> Option<&T> {
// SAFETY: `self` is checked to be in bounds.
if self < slice.len() { unsafe { Some(&*get_noubcheck(slice, self)) } } else { None }
if self < slice.len() {
#[cfg(bootstrap)]
// SAFETY: `self` is checked to be in bounds.
unsafe {
Some(&*get_noubcheck(slice, self))
}
#[cfg(not(bootstrap))]
// SAFETY: `self` is checked to be in bounds.
unsafe {
Some(slice_get_unchecked(slice, self))
}
} else {
None
}
}

#[inline]
fn get_mut(self, slice: &mut [T]) -> Option<&mut T> {
if self < slice.len() {
#[cfg(bootstrap)]
// SAFETY: `self` is checked to be in bounds.
unsafe {
Some(&mut *get_mut_noubcheck(slice, self))
}
#[cfg(not(bootstrap))]
// SAFETY: `self` is checked to be in bounds.
unsafe { Some(&mut *get_mut_noubcheck(slice, self)) }
unsafe {
Some(slice_get_unchecked(slice, self))
}
} else {
None
}
Expand All @@ -254,7 +280,14 @@ unsafe impl<T> SliceIndex<[T]> for usize {
// Use intrinsics::assume instead of hint::assert_unchecked so that we don't check the
// precondition of this function twice.
crate::intrinsics::assume(self < slice.len());
get_noubcheck(slice, self)
#[cfg(bootstrap)]
{
get_noubcheck(slice, self)
}
#[cfg(not(bootstrap))]
{
slice_get_unchecked(slice, self)
}
}
}

Expand All @@ -267,7 +300,16 @@ unsafe impl<T> SliceIndex<[T]> for usize {
(this: usize = self, len: usize = slice.len()) => this < len
);
// SAFETY: see comments for `get_unchecked` above.
unsafe { get_mut_noubcheck(slice, self) }
unsafe {
#[cfg(bootstrap)]
{
get_mut_noubcheck(slice, self)
}
#[cfg(not(bootstrap))]
{
slice_get_unchecked(slice, self)
}
}
}

#[inline]
Expand Down
21 changes: 21 additions & 0 deletions tests/mir-opt/lower_intrinsics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -263,3 +263,24 @@ pub fn get_metadata(a: *const i32, b: *const [u8], c: *const dyn std::fmt::Debug
let _usize = ptr_metadata(b);
let _vtable = ptr_metadata(c);
}

// EMIT_MIR lower_intrinsics.slice_get.LowerIntrinsics.diff
pub unsafe fn slice_get<'a, 'b>(
r: &'a [i8],
rm: &'b mut [i16],
p: *const [i32],
pm: *mut [i64],
i: usize,
) -> (&'a i8, &'b mut i16, *const i32, *mut i64) {
use std::intrinsics::slice_get_unchecked;
// CHECK: = &(*_{{[0-9]+}})[_{{[0-9]+}}]
// CHECK: = &mut (*_{{[0-9]+}})[_{{[0-9]+}}]
// CHECK: = &raw const (*_{{[0-9]+}})[_{{[0-9]+}}]
// CHECK: = &raw mut (*_{{[0-9]+}})[_{{[0-9]+}}]
(
slice_get_unchecked(r, i),
slice_get_unchecked(rm, i),
slice_get_unchecked(p, i),
slice_get_unchecked(pm, i),
)
}
Loading
Loading