From 6bae26065cf1d0742e0013eaeeb61eeb404a1586 Mon Sep 17 00:00:00 2001 From: Oli Scherer Date: Tue, 24 Feb 2026 11:53:43 +0000 Subject: [PATCH 1/4] Turn field accesses into a transmute if followed by a transmute anyway --- compiler/rustc_mir_transform/src/gvn.rs | 25 +++++++++++++++++++ .../transmute.unreachable_box.GVN.32bit.diff | 2 +- .../transmute.unreachable_box.GVN.64bit.diff | 2 +- ...ng_operand.test.GVN.32bit.panic-abort.diff | 3 ++- ...g_operand.test.GVN.32bit.panic-unwind.diff | 3 ++- ...ng_operand.test.GVN.64bit.panic-abort.diff | 3 ++- ...g_operand.test.GVN.64bit.panic-unwind.diff | 3 ++- 7 files changed, 35 insertions(+), 6 deletions(-) diff --git a/compiler/rustc_mir_transform/src/gvn.rs b/compiler/rustc_mir_transform/src/gvn.rs index 2db4502ecc6b6..0f26b7464a979 100644 --- a/compiler/rustc_mir_transform/src/gvn.rs +++ b/compiler/rustc_mir_transform/src/gvn.rs @@ -1547,6 +1547,7 @@ impl<'body, 'a, 'tcx> VnState<'body, 'a, 'tcx> { } } + #[instrument(level = "trace", skip(self), ret)] fn simplify_cast( &mut self, initial_kind: &mut CastKind, @@ -1602,6 +1603,30 @@ impl<'body, 'a, 'tcx> VnState<'body, 'a, 'tcx> { } } + // Field-Access-then-Transmute can just transmute the original value, + // so long as the bytes of a value from only from a single field. + if let Transmute = kind + && let Value::Projection(aggregate_value, ProjectionElem::Field(field_idx, ())) = + self.get(value) + { + if let Value::Projection( + _downcast_value, + ProjectionElem::Downcast(_, _variant_idx), + ) = self.get(aggregate_value) + { + } else if let Some((f_idx, field_ty)) = + self.value_is_all_in_one_field(self.ty(aggregate_value), FIRST_VARIANT) + { + assert_eq!(field_idx, f_idx, "{from} -> {field_ty}"); + from = self.ty(aggregate_value); + value = aggregate_value; + was_updated_this_iteration = true; + if from == to { + return Some(value); + } + } + } + // Aggregate-then-Transmute can just transmute the original field value, // so long as the bytes of a value from only from a single field. if let Transmute = kind diff --git a/tests/mir-opt/const_prop/transmute.unreachable_box.GVN.32bit.diff b/tests/mir-opt/const_prop/transmute.unreachable_box.GVN.32bit.diff index 6ebce526c9833..a16d4df3e379d 100644 --- a/tests/mir-opt/const_prop/transmute.unreachable_box.GVN.32bit.diff +++ b/tests/mir-opt/const_prop/transmute.unreachable_box.GVN.32bit.diff @@ -14,7 +14,7 @@ - _1 = const 1_usize as std::boxed::Box (Transmute); - _2 = copy ((_1.0: std::ptr::Unique).0: std::ptr::NonNull) as *const Never (Transmute); + _1 = const Box::(std::ptr::Unique:: {{ pointer: NonNull:: {{ pointer: {0x1 as *const Never} is !null }}, _marker: PhantomData:: }}, std::alloc::Global); -+ _2 = const std::ptr::NonNull:: {{ pointer: {0x1 as *const Never} is !null }} as *const Never (Transmute); ++ _2 = const std::boxed::Box::(std::ptr::Unique:: {{ pointer: std::ptr::NonNull:: {{ pointer: {0x1 as *const Never} is !null }}, _marker: std::marker::PhantomData:: }}, std::alloc::Global) as *const Never (Transmute); unreachable; } } diff --git a/tests/mir-opt/const_prop/transmute.unreachable_box.GVN.64bit.diff b/tests/mir-opt/const_prop/transmute.unreachable_box.GVN.64bit.diff index 6ebce526c9833..a16d4df3e379d 100644 --- a/tests/mir-opt/const_prop/transmute.unreachable_box.GVN.64bit.diff +++ b/tests/mir-opt/const_prop/transmute.unreachable_box.GVN.64bit.diff @@ -14,7 +14,7 @@ - _1 = const 1_usize as std::boxed::Box (Transmute); - _2 = copy ((_1.0: std::ptr::Unique).0: std::ptr::NonNull) as *const Never (Transmute); + _1 = const Box::(std::ptr::Unique:: {{ pointer: NonNull:: {{ pointer: {0x1 as *const Never} is !null }}, _marker: PhantomData:: }}, std::alloc::Global); -+ _2 = const std::ptr::NonNull:: {{ pointer: {0x1 as *const Never} is !null }} as *const Never (Transmute); ++ _2 = const std::boxed::Box::(std::ptr::Unique:: {{ pointer: std::ptr::NonNull:: {{ pointer: {0x1 as *const Never} is !null }}, _marker: std::marker::PhantomData:: }}, std::alloc::Global) as *const Never (Transmute); unreachable; } } diff --git a/tests/mir-opt/dont_reset_cast_kind_without_updating_operand.test.GVN.32bit.panic-abort.diff b/tests/mir-opt/dont_reset_cast_kind_without_updating_operand.test.GVN.32bit.panic-abort.diff index 9e717a74776d1..7628f591b65b7 100644 --- a/tests/mir-opt/dont_reset_cast_kind_without_updating_operand.test.GVN.32bit.panic-abort.diff +++ b/tests/mir-opt/dont_reset_cast_kind_without_updating_operand.test.GVN.32bit.panic-abort.diff @@ -73,7 +73,8 @@ StorageDead(_2); StorageLive(_5); _10 = no_retag copy (*_1); - _11 = copy ((_10.0: std::ptr::Unique<()>).0: std::ptr::NonNull<()>) as *const () (Transmute); +- _11 = copy ((_10.0: std::ptr::Unique<()>).0: std::ptr::NonNull<()>) as *const () (Transmute); ++ _11 = copy _10 as *const () (Transmute); _5 = &raw const (*_11); StorageLive(_6); StorageLive(_7); diff --git a/tests/mir-opt/dont_reset_cast_kind_without_updating_operand.test.GVN.32bit.panic-unwind.diff b/tests/mir-opt/dont_reset_cast_kind_without_updating_operand.test.GVN.32bit.panic-unwind.diff index f0b1feca4b28e..ca82995924d4f 100644 --- a/tests/mir-opt/dont_reset_cast_kind_without_updating_operand.test.GVN.32bit.panic-unwind.diff +++ b/tests/mir-opt/dont_reset_cast_kind_without_updating_operand.test.GVN.32bit.panic-unwind.diff @@ -53,7 +53,8 @@ StorageDead(_2); StorageLive(_5); _10 = no_retag copy (*_1); - _11 = copy ((_10.0: std::ptr::Unique<()>).0: std::ptr::NonNull<()>) as *const () (Transmute); +- _11 = copy ((_10.0: std::ptr::Unique<()>).0: std::ptr::NonNull<()>) as *const () (Transmute); ++ _11 = copy _10 as *const () (Transmute); _5 = &raw const (*_11); StorageLive(_6); StorageLive(_7); diff --git a/tests/mir-opt/dont_reset_cast_kind_without_updating_operand.test.GVN.64bit.panic-abort.diff b/tests/mir-opt/dont_reset_cast_kind_without_updating_operand.test.GVN.64bit.panic-abort.diff index dc5f656f17a59..2b24575a7a168 100644 --- a/tests/mir-opt/dont_reset_cast_kind_without_updating_operand.test.GVN.64bit.panic-abort.diff +++ b/tests/mir-opt/dont_reset_cast_kind_without_updating_operand.test.GVN.64bit.panic-abort.diff @@ -73,7 +73,8 @@ StorageDead(_2); StorageLive(_5); _10 = no_retag copy (*_1); - _11 = copy ((_10.0: std::ptr::Unique<()>).0: std::ptr::NonNull<()>) as *const () (Transmute); +- _11 = copy ((_10.0: std::ptr::Unique<()>).0: std::ptr::NonNull<()>) as *const () (Transmute); ++ _11 = copy _10 as *const () (Transmute); _5 = &raw const (*_11); StorageLive(_6); StorageLive(_7); diff --git a/tests/mir-opt/dont_reset_cast_kind_without_updating_operand.test.GVN.64bit.panic-unwind.diff b/tests/mir-opt/dont_reset_cast_kind_without_updating_operand.test.GVN.64bit.panic-unwind.diff index f0b1feca4b28e..ca82995924d4f 100644 --- a/tests/mir-opt/dont_reset_cast_kind_without_updating_operand.test.GVN.64bit.panic-unwind.diff +++ b/tests/mir-opt/dont_reset_cast_kind_without_updating_operand.test.GVN.64bit.panic-unwind.diff @@ -53,7 +53,8 @@ StorageDead(_2); StorageLive(_5); _10 = no_retag copy (*_1); - _11 = copy ((_10.0: std::ptr::Unique<()>).0: std::ptr::NonNull<()>) as *const () (Transmute); +- _11 = copy ((_10.0: std::ptr::Unique<()>).0: std::ptr::NonNull<()>) as *const () (Transmute); ++ _11 = copy _10 as *const () (Transmute); _5 = &raw const (*_11); StorageLive(_6); StorageLive(_7); From bba98a4b9e4387e31591fb67a1e997063b32ce15 Mon Sep 17 00:00:00 2001 From: Oli Scherer Date: Wed, 25 Feb 2026 09:37:15 +0000 Subject: [PATCH 2/4] Add test for option transmutes --- .../transmute.option_field.GVN.32bit.diff | 42 +++++++++++++++++++ .../transmute.option_field.GVN.64bit.diff | 42 +++++++++++++++++++ tests/mir-opt/const_prop/transmute.rs | 8 ++++ 3 files changed, 92 insertions(+) create mode 100644 tests/mir-opt/const_prop/transmute.option_field.GVN.32bit.diff create mode 100644 tests/mir-opt/const_prop/transmute.option_field.GVN.64bit.diff diff --git a/tests/mir-opt/const_prop/transmute.option_field.GVN.32bit.diff b/tests/mir-opt/const_prop/transmute.option_field.GVN.32bit.diff new file mode 100644 index 0000000000000..70aa6da086ee5 --- /dev/null +++ b/tests/mir-opt/const_prop/transmute.option_field.GVN.32bit.diff @@ -0,0 +1,42 @@ +- // MIR for `option_field` before GVN ++ // MIR for `option_field` after GVN + + fn option_field(_1: Option>) -> *const () { + debug x => _1; + let mut _0: *const (); + let mut _2: isize; + let mut _4: std::ptr::NonNull<()>; + scope 1 { + debug x => _3; + let _3: std::ptr::NonNull<()>; + } + + bb0: { + _2 = discriminant(_1); + switchInt(move _2) -> [1: bb1, otherwise: bb2]; + } + + bb1: { +- StorageLive(_3); ++ nop; + _3 = copy ((_1 as Some).0: std::ptr::NonNull<()>); + StorageLive(_4); + _4 = copy _3; +- _0 = move _4 as *const () (Transmute); ++ _0 = copy _3 as *const () (Transmute); + StorageDead(_4); +- StorageDead(_3); ++ nop; + goto -> bb3; + } + + bb2: { + _0 = const 0_usize as *const () (PointerWithExposedProvenance); + goto -> bb3; + } + + bb3: { + return; + } + } + diff --git a/tests/mir-opt/const_prop/transmute.option_field.GVN.64bit.diff b/tests/mir-opt/const_prop/transmute.option_field.GVN.64bit.diff new file mode 100644 index 0000000000000..70aa6da086ee5 --- /dev/null +++ b/tests/mir-opt/const_prop/transmute.option_field.GVN.64bit.diff @@ -0,0 +1,42 @@ +- // MIR for `option_field` before GVN ++ // MIR for `option_field` after GVN + + fn option_field(_1: Option>) -> *const () { + debug x => _1; + let mut _0: *const (); + let mut _2: isize; + let mut _4: std::ptr::NonNull<()>; + scope 1 { + debug x => _3; + let _3: std::ptr::NonNull<()>; + } + + bb0: { + _2 = discriminant(_1); + switchInt(move _2) -> [1: bb1, otherwise: bb2]; + } + + bb1: { +- StorageLive(_3); ++ nop; + _3 = copy ((_1 as Some).0: std::ptr::NonNull<()>); + StorageLive(_4); + _4 = copy _3; +- _0 = move _4 as *const () (Transmute); ++ _0 = copy _3 as *const () (Transmute); + StorageDead(_4); +- StorageDead(_3); ++ nop; + goto -> bb3; + } + + bb2: { + _0 = const 0_usize as *const () (PointerWithExposedProvenance); + goto -> bb3; + } + + bb3: { + return; + } + } + diff --git a/tests/mir-opt/const_prop/transmute.rs b/tests/mir-opt/const_prop/transmute.rs index ad971a64370e9..38766da4c2f6a 100644 --- a/tests/mir-opt/const_prop/transmute.rs +++ b/tests/mir-opt/const_prop/transmute.rs @@ -84,4 +84,12 @@ pub unsafe fn unreachable_box() -> ! { match *x {} } +// EMIT_MIR transmute.option_field.GVN.diff +pub unsafe fn option_field(x: Option>) -> *const () { + // CHECK-LABEL: fn option_field( + // CHECK: _3 = copy ((_1 as Some).0: std::ptr::NonNull<()>) + // CHECK: _0 = copy _3 as *const () (Transmute) + if let Some(x) = x { unsafe { transmute(x) } } else { 0 as *const () } +} + enum Never {} From 2bd32ad9a59faf74865573e982e514704de173d1 Mon Sep 17 00:00:00 2001 From: Oli Scherer Date: Wed, 25 Feb 2026 08:50:46 +0000 Subject: [PATCH 3/4] Also skip downcasts if the size of the enum matches the transmute target type --- compiler/rustc_mir_transform/src/gvn.rs | 14 +++++++++++++- .../transmute.option_field.GVN.32bit.diff | 8 +++----- .../transmute.option_field.GVN.64bit.diff | 8 +++----- tests/mir-opt/const_prop/transmute.rs | 2 +- ...nvalid_constant.main.GVN.32bit.panic-abort.diff | 12 ++++++++---- ...nvalid_constant.main.GVN.64bit.panic-abort.diff | 12 ++++++++---- 6 files changed, 36 insertions(+), 20 deletions(-) diff --git a/compiler/rustc_mir_transform/src/gvn.rs b/compiler/rustc_mir_transform/src/gvn.rs index 0f26b7464a979..db46ecbffb7d5 100644 --- a/compiler/rustc_mir_transform/src/gvn.rs +++ b/compiler/rustc_mir_transform/src/gvn.rs @@ -1610,10 +1610,22 @@ impl<'body, 'a, 'tcx> VnState<'body, 'a, 'tcx> { self.get(value) { if let Value::Projection( - _downcast_value, + downcast_value, ProjectionElem::Downcast(_, _variant_idx), ) = self.get(aggregate_value) { + let downcast_ty = self.ty(downcast_value); + if let Ok(downcast_layout) = self.ecx.layout_of(downcast_ty) + && let Ok(projected_layout) = self.ecx.layout_of(from) + && downcast_layout.size == projected_layout.size + { + from = downcast_ty; + value = downcast_value; + was_updated_this_iteration = true; + if from == to { + return Some(value); + } + } } else if let Some((f_idx, field_ty)) = self.value_is_all_in_one_field(self.ty(aggregate_value), FIRST_VARIANT) { diff --git a/tests/mir-opt/const_prop/transmute.option_field.GVN.32bit.diff b/tests/mir-opt/const_prop/transmute.option_field.GVN.32bit.diff index 70aa6da086ee5..99208f5d80273 100644 --- a/tests/mir-opt/const_prop/transmute.option_field.GVN.32bit.diff +++ b/tests/mir-opt/const_prop/transmute.option_field.GVN.32bit.diff @@ -17,16 +17,14 @@ } bb1: { -- StorageLive(_3); -+ nop; + StorageLive(_3); _3 = copy ((_1 as Some).0: std::ptr::NonNull<()>); StorageLive(_4); _4 = copy _3; - _0 = move _4 as *const () (Transmute); -+ _0 = copy _3 as *const () (Transmute); ++ _0 = copy _1 as *const () (Transmute); StorageDead(_4); -- StorageDead(_3); -+ nop; + StorageDead(_3); goto -> bb3; } diff --git a/tests/mir-opt/const_prop/transmute.option_field.GVN.64bit.diff b/tests/mir-opt/const_prop/transmute.option_field.GVN.64bit.diff index 70aa6da086ee5..99208f5d80273 100644 --- a/tests/mir-opt/const_prop/transmute.option_field.GVN.64bit.diff +++ b/tests/mir-opt/const_prop/transmute.option_field.GVN.64bit.diff @@ -17,16 +17,14 @@ } bb1: { -- StorageLive(_3); -+ nop; + StorageLive(_3); _3 = copy ((_1 as Some).0: std::ptr::NonNull<()>); StorageLive(_4); _4 = copy _3; - _0 = move _4 as *const () (Transmute); -+ _0 = copy _3 as *const () (Transmute); ++ _0 = copy _1 as *const () (Transmute); StorageDead(_4); -- StorageDead(_3); -+ nop; + StorageDead(_3); goto -> bb3; } diff --git a/tests/mir-opt/const_prop/transmute.rs b/tests/mir-opt/const_prop/transmute.rs index 38766da4c2f6a..cf4244860832f 100644 --- a/tests/mir-opt/const_prop/transmute.rs +++ b/tests/mir-opt/const_prop/transmute.rs @@ -88,7 +88,7 @@ pub unsafe fn unreachable_box() -> ! { pub unsafe fn option_field(x: Option>) -> *const () { // CHECK-LABEL: fn option_field( // CHECK: _3 = copy ((_1 as Some).0: std::ptr::NonNull<()>) - // CHECK: _0 = copy _3 as *const () (Transmute) + // CHECK: _0 = copy _1 as *const () (Transmute) if let Some(x) = x { unsafe { transmute(x) } } else { 0 as *const () } } diff --git a/tests/mir-opt/pre-codegen/issue_117368_print_invalid_constant.main.GVN.32bit.panic-abort.diff b/tests/mir-opt/pre-codegen/issue_117368_print_invalid_constant.main.GVN.32bit.panic-abort.diff index a0526d279a863..6098a1d8f82a5 100644 --- a/tests/mir-opt/pre-codegen/issue_117368_print_invalid_constant.main.GVN.32bit.panic-abort.diff +++ b/tests/mir-opt/pre-codegen/issue_117368_print_invalid_constant.main.GVN.32bit.panic-abort.diff @@ -68,7 +68,8 @@ StorageLive(_3); StorageLive(_4); StorageLive(_5); - StorageLive(_6); +- StorageLive(_6); ++ nop; StorageLive(_7); - _7 = copy _1; - _6 = std::alloc::Global::alloc_impl_runtime(move _7, const false) -> [return: bb4, unwind unreachable]; @@ -92,10 +93,13 @@ } bb6: { - _5 = move ((_6 as Ok).0: std::ptr::NonNull<[u8]>); +- _5 = move ((_6 as Ok).0: std::ptr::NonNull<[u8]>); ++ _5 = copy ((_6 as Ok).0: std::ptr::NonNull<[u8]>); StorageDead(_10); - StorageDead(_6); - _4 = copy _5 as *mut [u8] (Transmute); +- StorageDead(_6); +- _4 = copy _5 as *mut [u8] (Transmute); ++ nop; ++ _4 = copy _6 as *mut [u8] (Transmute); StorageDead(_5); _3 = copy _4 as *mut u8 (PtrToPtr); StorageDead(_4); diff --git a/tests/mir-opt/pre-codegen/issue_117368_print_invalid_constant.main.GVN.64bit.panic-abort.diff b/tests/mir-opt/pre-codegen/issue_117368_print_invalid_constant.main.GVN.64bit.panic-abort.diff index 3a6b52b0f2496..2a5acb36f29f7 100644 --- a/tests/mir-opt/pre-codegen/issue_117368_print_invalid_constant.main.GVN.64bit.panic-abort.diff +++ b/tests/mir-opt/pre-codegen/issue_117368_print_invalid_constant.main.GVN.64bit.panic-abort.diff @@ -68,7 +68,8 @@ StorageLive(_3); StorageLive(_4); StorageLive(_5); - StorageLive(_6); +- StorageLive(_6); ++ nop; StorageLive(_7); - _7 = copy _1; - _6 = std::alloc::Global::alloc_impl_runtime(move _7, const false) -> [return: bb4, unwind unreachable]; @@ -92,10 +93,13 @@ } bb6: { - _5 = move ((_6 as Ok).0: std::ptr::NonNull<[u8]>); +- _5 = move ((_6 as Ok).0: std::ptr::NonNull<[u8]>); ++ _5 = copy ((_6 as Ok).0: std::ptr::NonNull<[u8]>); StorageDead(_10); - StorageDead(_6); - _4 = copy _5 as *mut [u8] (Transmute); +- StorageDead(_6); +- _4 = copy _5 as *mut [u8] (Transmute); ++ nop; ++ _4 = copy _6 as *mut [u8] (Transmute); StorageDead(_5); _3 = copy _4 as *mut u8 (PtrToPtr); StorageDead(_4); From 545fed631e74b495ecfc58a7b91c8f537f03102c Mon Sep 17 00:00:00 2001 From: Oli Scherer Date: Thu, 25 Jun 2026 12:47:01 +0200 Subject: [PATCH 4/4] Use equivalent variable to match what all the other fast paths are doing --- compiler/rustc_mir_transform/src/gvn.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/rustc_mir_transform/src/gvn.rs b/compiler/rustc_mir_transform/src/gvn.rs index db46ecbffb7d5..72670e1939aa1 100644 --- a/compiler/rustc_mir_transform/src/gvn.rs +++ b/compiler/rustc_mir_transform/src/gvn.rs @@ -1649,7 +1649,7 @@ impl<'body, 'a, 'tcx> VnState<'body, 'a, 'tcx> { from = field_ty; value = field_values[field_idx.as_usize()]; was_updated_this_iteration = true; - if field_ty == to { + if from == to { return Some(value); } }