Skip to content

Simplify align_of_val::<[T]>(…)align_of::<T>() #144566

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 3 commits into from
Jul 29, 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
31 changes: 31 additions & 0 deletions compiler/rustc_mir_transform/src/instsimplify.rs
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ impl<'tcx> crate::MirPass<'tcx> for InstSimplify {

let terminator = block.terminator.as_mut().unwrap();
ctx.simplify_primitive_clone(terminator, &mut block.statements);
ctx.simplify_align_of_slice_val(terminator, &mut block.statements);
ctx.simplify_intrinsic_assert(terminator);
ctx.simplify_nounwind_call(terminator);
simplify_duplicate_switch_targets(terminator);
Expand Down Expand Up @@ -252,6 +253,36 @@ impl<'tcx> InstSimplifyContext<'_, 'tcx> {
terminator.kind = TerminatorKind::Goto { target: *destination_block };
}

// Convert `align_of_val::<[T]>(ptr)` to `align_of::<T>()`, since the
// alignment of a slice doesn't actually depend on metadata at all
// and the element type is always `Sized`.
//
// This is here so it can run after inlining, where it's more useful.
// (LowerIntrinsics is done in cleanup, before the optimization passes.)
fn simplify_align_of_slice_val(
&self,
terminator: &mut Terminator<'tcx>,
statements: &mut Vec<Statement<'tcx>>,
) {
if let TerminatorKind::Call {
func, args, destination, target: Some(destination_block), ..
} = &terminator.kind
&& args.len() == 1
&& let Some((fn_def_id, generics)) = func.const_fn_def()
&& self.tcx.is_intrinsic(fn_def_id, sym::align_of_val)
&& let ty::Slice(elem_ty) = *generics.type_at(0).kind()
{
statements.push(Statement::new(
terminator.source_info,
StatementKind::Assign(Box::new((
*destination,
Rvalue::NullaryOp(NullOp::AlignOf, elem_ty),
))),
));
terminator.kind = TerminatorKind::Goto { target: *destination_block };
}
}

fn simplify_nounwind_call(&self, terminator: &mut Terminator<'tcx>) {
let TerminatorKind::Call { ref func, ref mut unwind, .. } = terminator.kind else {
return;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
- // MIR for `of_val_slice` before InstSimplify-after-simplifycfg
+ // MIR for `of_val_slice` after InstSimplify-after-simplifycfg

fn of_val_slice(_1: &[T]) -> usize {
debug slice => _1;
let mut _0: usize;
let mut _2: *const [T];

bb0: {
StorageLive(_2);
_2 = &raw const (*_1);
- _0 = std::intrinsics::align_of_val::<[T]>(move _2) -> [return: bb1, unwind unreachable];
+ _0 = AlignOf(T);
+ goto -> bb1;
}

bb1: {
StorageDead(_2);
return;
}
}

12 changes: 12 additions & 0 deletions tests/mir-opt/instsimplify/align_of_slice.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
//@ test-mir-pass: InstSimplify-after-simplifycfg
//@ needs-unwind

#![crate_type = "lib"]
#![feature(core_intrinsics)]

// EMIT_MIR align_of_slice.of_val_slice.InstSimplify-after-simplifycfg.diff
pub fn of_val_slice<T>(slice: &[T]) -> usize {
// CHECK-LABEL: fn of_val_slice(_1: &[T])
// CHECK: _0 = AlignOf(T);
unsafe { core::intrinsics::align_of_val(slice) }
}
Comment on lines +8 to +12
Copy link
Contributor

Choose a reason for hiding this comment

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

Since you're explicitly mentioning this running after inlining, maybe change it to

Suggested change
pub fn of_val_slice<T>(slice: &[T]) -> usize {
// CHECK-LABEL: fn of_val_slice(_1: &[T])
// CHECK: _0 = AlignOf(T);
unsafe { core::intrinsics::align_of_val(slice) }
}
pub fn of_val_slice<T>(slice: &[T]) -> usize {
// CHECK-LABEL: fn of_val_slice(_1: &[T])
// CHECK: _0 = AlignOf(T);
something_inlined(slice)
}
#[inline(always)]
fn something_inlined<T: ?Sized>(val: &T) -> usize {
unsafe { core::intrinsics::align_of_val(slice) }
}

or use the mem wrapper

Copy link
Member Author

Choose a reason for hiding this comment

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

Hmm, that wouldn't work with test-mir-pass -- this folder is more unit testy, usually.

I guess I can just bring over the test from https://github.com/rust-lang/rust/pull/144572/files#diff-9e6b893e458a80c489851369ec09247ad188228d94c4c92d3ba28fea1702fdc8 though.

Copy link
Contributor

Choose a reason for hiding this comment

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

Just merging is fine with me, I was mostly wondering why it wasn't this way. r=me at your discretion what you think is best

Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
// MIR for `generic_in_place` after PreCodegen

fn generic_in_place(_1: *mut Box<[T]>) -> () {
debug ptr => _1;
let mut _0: ();
scope 1 (inlined drop_in_place::<Box<[T]>> - shim(Some(Box<[T]>))) {
scope 2 (inlined <Box<[T]> as Drop>::drop) {
let _2: std::ptr::NonNull<[T]>;
let mut _3: *mut [T];
let mut _4: *const [T];
let _12: ();
scope 3 {
let _8: std::ptr::alignment::AlignmentEnum;
scope 4 {
scope 12 (inlined Layout::size) {
}
scope 13 (inlined Unique::<[T]>::cast::<u8>) {
scope 14 (inlined NonNull::<[T]>::cast::<u8>) {
scope 15 (inlined NonNull::<[T]>::as_ptr) {
}
}
}
scope 16 (inlined <NonNull<u8> as From<Unique<u8>>>::from) {
scope 17 (inlined Unique::<u8>::as_non_null_ptr) {
}
}
scope 18 (inlined <std::alloc::Global as Allocator>::deallocate) {
let mut _9: *mut u8;
scope 19 (inlined Layout::size) {
}
scope 20 (inlined NonNull::<u8>::as_ptr) {
}
scope 21 (inlined std::alloc::dealloc) {
let mut _11: usize;
scope 22 (inlined Layout::size) {
}
scope 23 (inlined Layout::align) {
scope 24 (inlined std::ptr::Alignment::as_usize) {
let mut _10: u32;
Copy link
Member Author

Choose a reason for hiding this comment

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

annot: this u32 is why the test needs to be split by bit width.

}
}
}
}
}
scope 5 (inlined Unique::<[T]>::as_ptr) {
scope 6 (inlined NonNull::<[T]>::as_ptr) {
}
}
scope 7 (inlined Layout::for_value_raw::<[T]>) {
let mut _5: usize;
let mut _6: usize;
scope 8 {
scope 11 (inlined #[track_caller] Layout::from_size_align_unchecked) {
let mut _7: std::ptr::Alignment;
}
}
scope 9 (inlined size_of_val_raw::<[T]>) {
}
scope 10 (inlined align_of_val_raw::<[T]>) {
}
}
}
}
}

bb0: {
StorageLive(_2);
_2 = copy (((*_1).0: std::ptr::Unique<[T]>).0: std::ptr::NonNull<[T]>);
StorageLive(_4);
_3 = copy _2 as *mut [T] (Transmute);
_4 = copy _2 as *const [T] (Transmute);
StorageLive(_6);
_5 = std::intrinsics::size_of_val::<[T]>(move _4) -> [return: bb1, unwind unreachable];
}

bb1: {
_6 = AlignOf(T);
StorageLive(_7);
_7 = copy _6 as std::ptr::Alignment (Transmute);
_8 = move (_7.0: std::ptr::alignment::AlignmentEnum);
StorageDead(_7);
StorageDead(_6);
StorageDead(_4);
switchInt(copy _5) -> [0: bb4, otherwise: bb2];
}

bb2: {
StorageLive(_9);
_9 = copy _3 as *mut u8 (PtrToPtr);
StorageLive(_11);
StorageLive(_10);
_10 = discriminant(_8);
_11 = move _10 as usize (IntToInt);
StorageDead(_10);
_12 = alloc::alloc::__rust_dealloc(move _9, move _5, move _11) -> [return: bb3, unwind unreachable];
}

bb3: {
StorageDead(_11);
StorageDead(_9);
goto -> bb4;
}

bb4: {
StorageDead(_2);
return;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
// MIR for `generic_in_place` after PreCodegen

fn generic_in_place(_1: *mut Box<[T]>) -> () {
debug ptr => _1;
let mut _0: ();
scope 1 (inlined drop_in_place::<Box<[T]>> - shim(Some(Box<[T]>))) {
scope 2 (inlined <Box<[T]> as Drop>::drop) {
let _2: std::ptr::NonNull<[T]>;
let mut _3: *mut [T];
let mut _4: *const [T];
let _12: ();
scope 3 {
let _8: std::ptr::alignment::AlignmentEnum;
scope 4 {
scope 12 (inlined Layout::size) {
}
scope 13 (inlined Unique::<[T]>::cast::<u8>) {
scope 14 (inlined NonNull::<[T]>::cast::<u8>) {
scope 15 (inlined NonNull::<[T]>::as_ptr) {
}
}
}
scope 16 (inlined <NonNull<u8> as From<Unique<u8>>>::from) {
scope 17 (inlined Unique::<u8>::as_non_null_ptr) {
}
}
scope 18 (inlined <std::alloc::Global as Allocator>::deallocate) {
let mut _9: *mut u8;
scope 19 (inlined Layout::size) {
}
scope 20 (inlined NonNull::<u8>::as_ptr) {
}
scope 21 (inlined std::alloc::dealloc) {
let mut _11: usize;
scope 22 (inlined Layout::size) {
}
scope 23 (inlined Layout::align) {
scope 24 (inlined std::ptr::Alignment::as_usize) {
let mut _10: u32;
}
}
}
}
}
scope 5 (inlined Unique::<[T]>::as_ptr) {
scope 6 (inlined NonNull::<[T]>::as_ptr) {
}
}
scope 7 (inlined Layout::for_value_raw::<[T]>) {
let mut _5: usize;
let mut _6: usize;
scope 8 {
scope 11 (inlined #[track_caller] Layout::from_size_align_unchecked) {
let mut _7: std::ptr::Alignment;
}
}
scope 9 (inlined size_of_val_raw::<[T]>) {
}
scope 10 (inlined align_of_val_raw::<[T]>) {
}
}
}
}
}

bb0: {
StorageLive(_2);
_2 = copy (((*_1).0: std::ptr::Unique<[T]>).0: std::ptr::NonNull<[T]>);
StorageLive(_4);
_3 = copy _2 as *mut [T] (Transmute);
_4 = copy _2 as *const [T] (Transmute);
StorageLive(_6);
_5 = std::intrinsics::size_of_val::<[T]>(move _4) -> [return: bb1, unwind unreachable];
}

bb1: {
_6 = AlignOf(T);
StorageLive(_7);
_7 = copy _6 as std::ptr::Alignment (Transmute);
_8 = move (_7.0: std::ptr::alignment::AlignmentEnum);
StorageDead(_7);
StorageDead(_6);
StorageDead(_4);
switchInt(copy _5) -> [0: bb4, otherwise: bb2];
}

bb2: {
StorageLive(_9);
_9 = copy _3 as *mut u8 (PtrToPtr);
StorageLive(_11);
StorageLive(_10);
_10 = discriminant(_8);
_11 = move _10 as usize (IntToInt);
StorageDead(_10);
_12 = alloc::alloc::__rust_dealloc(move _9, move _5, move _11) -> [return: bb3, unwind unreachable];
}

bb3: {
StorageDead(_11);
StorageDead(_9);
goto -> bb4;
}

bb4: {
StorageDead(_2);
return;
}
}
Loading
Loading