about summary refs log tree commit diff
diff options
context:
space:
mode:
authorScott McMurray <scottmcm@users.noreply.github.com>2025-01-05 22:38:38 -0800
committerScott McMurray <scottmcm@users.noreply.github.com>2025-01-08 18:46:30 -0800
commit293f8e8941e77c6372abd787fe564e9bf895445e (patch)
tree469985e3302113896102befbb0f9aeafa5e06ab2
parent03650dd0290c3963dfd1f7f8e4325ccb7d3966f6 (diff)
downloadrust-293f8e8941e77c6372abd787fe564e9bf895445e.tar.gz
rust-293f8e8941e77c6372abd787fe564e9bf895445e.zip
Refactor the cast-then-cast cases together, and support transmute-then-transmute
-rw-r--r--compiler/rustc_mir_transform/src/gvn.rs202
-rw-r--r--tests/mir-opt/gvn.aggregate_struct_then_transmute.GVN.panic-abort.diff131
-rw-r--r--tests/mir-opt/gvn.aggregate_struct_then_transmute.GVN.panic-unwind.diff131
-rw-r--r--tests/mir-opt/gvn.rs40
-rw-r--r--tests/mir-opt/gvn.transmute_then_transmute_again.GVN.panic-abort.diff74
-rw-r--r--tests/mir-opt/gvn.transmute_then_transmute_again.GVN.panic-unwind.diff74
-rw-r--r--tests/mir-opt/pre-codegen/slice_iter.enumerated_loop.PreCodegen.after.panic-abort.mir8
-rw-r--r--tests/mir-opt/pre-codegen/slice_iter.enumerated_loop.PreCodegen.after.panic-unwind.mir8
-rw-r--r--tests/mir-opt/pre-codegen/slice_iter.forward_loop.PreCodegen.after.panic-abort.mir8
-rw-r--r--tests/mir-opt/pre-codegen/slice_iter.forward_loop.PreCodegen.after.panic-unwind.mir8
-rw-r--r--tests/mir-opt/pre-codegen/slice_iter.reverse_loop.PreCodegen.after.panic-abort.mir8
-rw-r--r--tests/mir-opt/pre-codegen/slice_iter.reverse_loop.PreCodegen.after.panic-unwind.mir8
12 files changed, 588 insertions, 112 deletions
diff --git a/compiler/rustc_mir_transform/src/gvn.rs b/compiler/rustc_mir_transform/src/gvn.rs
index 0d2b4efe739..7c600fc1ceb 100644
--- a/compiler/rustc_mir_transform/src/gvn.rs
+++ b/compiler/rustc_mir_transform/src/gvn.rs
@@ -1366,110 +1366,112 @@ impl<'body, 'tcx> VnState<'body, 'tcx> {
             return self.new_opaque();
         }
 
-        let mut was_updated = false;
-
-        // Transmuting `*const T` <=> `*mut T` is just a pointer cast,
-        // which we might be able to merge with other ones later.
-        if let Transmute = kind
-            && let ty::RawPtr(from_pointee, _) = from.kind()
-            && let ty::RawPtr(to_pointee, _) = to.kind()
-            && from_pointee == to_pointee
-        {
-            *kind = PtrToPtr;
-            was_updated = true;
-        }
-
-        // If a cast just casts away the metadata again, then we can get it by
-        // casting the original thin pointer passed to `from_raw_parts`
-        if let PtrToPtr = kind
-            && let Value::Aggregate(AggregateTy::RawPtr { data_pointer_ty, .. }, _, fields) =
-                self.get(value)
-            && let ty::RawPtr(to_pointee, _) = to.kind()
-            && to_pointee.is_sized(self.tcx, self.typing_env())
-        {
-            from = *data_pointer_ty;
-            value = fields[0];
-            was_updated = true;
-            if *data_pointer_ty == to {
-                return Some(fields[0]);
+        let mut was_ever_updated = false;
+        loop {
+            let mut was_updated_this_iteration = false;
+
+            // Transmuting `*const T` <=> `*mut T` is just a pointer cast,
+            // which we might be able to merge with other ones later.
+            if let Transmute = kind
+                && let ty::RawPtr(from_pointee, _) = from.kind()
+                && let ty::RawPtr(to_pointee, _) = to.kind()
+                && from_pointee == to_pointee
+            {
+                *kind = PtrToPtr;
+                was_updated_this_iteration = true;
             }
-        }
 
-        // PtrToPtr-then-PtrToPtr can skip the intermediate step
-        if let PtrToPtr = kind
-            && let Value::Cast { kind: inner_kind, value: inner_value, from: inner_from, to: _ } =
-                *self.get(value)
-            && let PtrToPtr = inner_kind
-        {
-            from = inner_from;
-            value = inner_value;
-            was_updated = true;
-            if inner_from == to {
-                return Some(inner_value);
+            // If a cast just casts away the metadata again, then we can get it by
+            // casting the original thin pointer passed to `from_raw_parts`
+            if let PtrToPtr = kind
+                && let Value::Aggregate(AggregateTy::RawPtr { data_pointer_ty, .. }, _, fields) =
+                    self.get(value)
+                && let ty::RawPtr(to_pointee, _) = to.kind()
+                && to_pointee.is_sized(self.tcx, self.typing_env())
+            {
+                from = *data_pointer_ty;
+                value = fields[0];
+                was_updated_this_iteration = true;
+                if *data_pointer_ty == to {
+                    return Some(fields[0]);
+                }
             }
-        }
 
-        // 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
-            && let Value::Aggregate(
-                AggregateTy::Def(aggregate_did, aggregate_args),
-                variant_idx,
-                field_values,
-            ) = self.get(value)
-            && let aggregate_ty =
-                self.tcx.type_of(aggregate_did).instantiate(self.tcx, aggregate_args)
-            && let Some((field_idx, field_ty)) =
-                self.value_is_all_in_one_field(aggregate_ty, *variant_idx)
-        {
-            from = field_ty;
-            value = field_values[field_idx.as_usize()];
-            was_updated = true;
-            if field_ty == 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
+                && let Value::Aggregate(
+                    AggregateTy::Def(aggregate_did, aggregate_args),
+                    variant_idx,
+                    field_values,
+                ) = self.get(value)
+                && let aggregate_ty =
+                    self.tcx.type_of(aggregate_did).instantiate(self.tcx, aggregate_args)
+                && let Some((field_idx, field_ty)) =
+                    self.value_is_all_in_one_field(aggregate_ty, *variant_idx)
+            {
+                from = field_ty;
+                value = field_values[field_idx.as_usize()];
+                was_updated_this_iteration = true;
+                if field_ty == to {
+                    return Some(value);
+                }
             }
-        }
 
-        // PtrToPtr-then-Transmute can just transmute the original, so long as the
-        // PtrToPtr didn't change metadata (and thus the size of the pointer)
-        if let Transmute = kind
-            && let Value::Cast {
-                kind: PtrToPtr,
+            // Various cast-then-cast cases can be simplified.
+            if let Value::Cast {
+                kind: inner_kind,
                 value: inner_value,
                 from: inner_from,
                 to: inner_to,
             } = *self.get(value)
-            && self.pointers_have_same_metadata(inner_from, inner_to)
-        {
-            from = inner_from;
-            value = inner_value;
-            was_updated = true;
-            if inner_from == to {
-                return Some(inner_value);
+            {
+                let new_kind = match (inner_kind, *kind) {
+                    // Even if there's a narrowing cast in here that's fine, because
+                    // things like `*mut [i32] -> *mut i32 -> *const i32` and
+                    // `*mut [i32] -> *const [i32] -> *const i32` can skip the middle in MIR.
+                    (PtrToPtr, PtrToPtr) => Some(PtrToPtr),
+                    // PtrToPtr-then-Transmute is fine so long as the pointer cast is identity:
+                    // `*const T -> *mut T -> NonNull<T>` is fine, but we need to check for narrowing
+                    // to skip things like `*const [i32] -> *const i32 -> NonNull<T>`.
+                    (PtrToPtr, Transmute)
+                        if self.pointers_have_same_metadata(inner_from, inner_to) =>
+                    {
+                        Some(Transmute)
+                    }
+                    // Similarly, for Transmute-then-PtrToPtr. Note that we need to check different
+                    // variables for their metadata, and thus this can't merge with the previous arm.
+                    (Transmute, PtrToPtr) if self.pointers_have_same_metadata(from, to) => {
+                        Some(Transmute)
+                    }
+                    // If would be legal to always do this, but we don't want to hide information
+                    // from the backend that it'd otherwise be able to use for optimizations.
+                    (Transmute, Transmute)
+                        if !self.type_may_have_niche_of_interest_to_backend(inner_to) =>
+                    {
+                        Some(Transmute)
+                    }
+                    _ => None,
+                };
+                if let Some(new_kind) = new_kind {
+                    *kind = new_kind;
+                    from = inner_from;
+                    value = inner_value;
+                    was_updated_this_iteration = true;
+                    if inner_from == to {
+                        return Some(inner_value);
+                    }
+                }
             }
-        }
 
-        // Transmute-then-PtrToPtr can just transmute the original, so long as the
-        // PtrToPtr won't change metadata (and thus the size of the pointer)
-        if let PtrToPtr = kind
-            && let Value::Cast {
-                kind: Transmute,
-                value: inner_value,
-                from: inner_from,
-                to: _inner_to,
-            } = *self.get(value)
-            && self.pointers_have_same_metadata(from, to)
-        {
-            *kind = Transmute;
-            from = inner_from;
-            value = inner_value;
-            was_updated = true;
-            if inner_from == to {
-                return Some(inner_value);
+            if was_updated_this_iteration {
+                was_ever_updated = true;
+            } else {
+                break;
             }
         }
 
-        if was_updated && let Some(op) = self.try_as_operand(value, location) {
+        if was_ever_updated && let Some(op) = self.try_as_operand(value, location) {
             *operand = op;
         }
 
@@ -1492,6 +1494,28 @@ impl<'body, 'tcx> VnState<'body, 'tcx> {
         }
     }
 
+    /// Returns `false` if we know for sure that this type has no interesting niche,
+    /// and thus we can skip transmuting through it without worrying.
+    ///
+    /// The backend will emit `assume`s when transmuting between types with niches,
+    /// so we want to preserve `i32 -> char -> u32` so that that data is around,
+    /// but it's fine to skip whole-range-is-value steps like `A -> u32 -> B`.
+    fn type_may_have_niche_of_interest_to_backend(&self, ty: Ty<'tcx>) -> bool {
+        let Ok(layout) = self.ecx.layout_of(ty) else {
+            // If it's too generic or something, then assume it might be interesting later.
+            return true;
+        };
+
+        match layout.backend_repr {
+            BackendRepr::Uninhabited => true,
+            BackendRepr::Scalar(a) => !a.is_always_valid(&self.ecx),
+            BackendRepr::ScalarPair(a, b) => {
+                !a.is_always_valid(&self.ecx) || !b.is_always_valid(&self.ecx)
+            }
+            BackendRepr::Vector { .. } | BackendRepr::Memory { .. } => false,
+        }
+    }
+
     fn value_is_all_in_one_field(
         &self,
         ty: Ty<'tcx>,
diff --git a/tests/mir-opt/gvn.aggregate_struct_then_transmute.GVN.panic-abort.diff b/tests/mir-opt/gvn.aggregate_struct_then_transmute.GVN.panic-abort.diff
index 2b234637f4d..1f929180247 100644
--- a/tests/mir-opt/gvn.aggregate_struct_then_transmute.GVN.panic-abort.diff
+++ b/tests/mir-opt/gvn.aggregate_struct_then_transmute.GVN.panic-abort.diff
@@ -17,15 +17,49 @@
       let mut _14: u16;
       let _15: ();
       let mut _16: u16;
-      let mut _17: std::result::Result<aggregate_struct_then_transmute::Never, u16>;
+      let mut _17: std::result::Result<Never, u16>;
+      let mut _19: u16;
+      let _20: ();
+      let mut _21: u32;
+      let mut _22: std::option::Option<u16>;
+      let mut _24: u16;
+      let _25: ();
+      let mut _26: i16;
+      let mut _27: MyId;
+      let mut _29: u16;
+      let mut _30: u16;
+      let _31: ();
+      let mut _32: u32;
+      let mut _33: aggregate_struct_then_transmute::Pair;
+      let mut _35: u16;
+      let mut _36: u16;
+      let _37: ();
+      let mut _38: u16;
+      let mut _39: aggregate_struct_then_transmute::Pair;
       scope 1 {
           debug a => _2;
           let _7: TypedId<std::string::String>;
           scope 2 {
               debug b => _7;
-              let _13: std::result::Result<aggregate_struct_then_transmute::Never, u16>;
+              let _13: std::result::Result<Never, u16>;
               scope 3 {
                   debug c => _13;
+                  let _18: std::option::Option<u16>;
+                  scope 4 {
+                      debug d => _18;
+                      let _23: MyId;
+                      scope 5 {
+                          debug e => _23;
+                          let _28: aggregate_struct_then_transmute::Pair;
+                          scope 6 {
+                              debug f => _28;
+                              let _34: aggregate_struct_then_transmute::Pair;
+                              scope 7 {
+                                  debug g => _34;
+                              }
+                          }
+                      }
+                  }
               }
           }
       }
@@ -101,13 +135,106 @@
       bb3: {
           StorageDead(_16);
           StorageDead(_15);
+-         StorageLive(_18);
++         nop;
+          StorageLive(_19);
+          _19 = copy _1;
+-         _18 = Option::<u16>::Some(move _19);
++         _18 = Option::<u16>::Some(copy _1);
+          StorageDead(_19);
+          StorageLive(_20);
+          StorageLive(_21);
+          StorageLive(_22);
+          _22 = copy _18;
+-         _21 = move _22 as u32 (Transmute);
++         _21 = copy _18 as u32 (Transmute);
+          StorageDead(_22);
+          _20 = opaque::<u32>(move _21) -> [return: bb4, unwind unreachable];
+      }
+  
+      bb4: {
+          StorageDead(_21);
+          StorageDead(_20);
+          StorageLive(_23);
+          StorageLive(_24);
+          _24 = copy _1;
+-         _23 = MyId(move _24);
++         _23 = copy _2;
+          StorageDead(_24);
+          StorageLive(_25);
+          StorageLive(_26);
+          StorageLive(_27);
+-         _27 = move _23;
+-         _26 = move _27 as i16 (Transmute);
++         _27 = copy _2;
++         _26 = copy _1 as i16 (Transmute);
+          StorageDead(_27);
+          _25 = opaque::<i16>(move _26) -> [return: bb5, unwind unreachable];
+      }
+  
+      bb5: {
+          StorageDead(_26);
+          StorageDead(_25);
+-         StorageLive(_28);
++         nop;
+          StorageLive(_29);
+          _29 = copy _1;
+          StorageLive(_30);
+          _30 = copy _1;
+-         _28 = Pair(move _29, move _30);
++         _28 = Pair(copy _1, copy _1);
+          StorageDead(_30);
+          StorageDead(_29);
+          StorageLive(_31);
+          StorageLive(_32);
+          StorageLive(_33);
+-         _33 = move _28;
+-         _32 = move _33 as u32 (Transmute);
++         _33 = copy _28;
++         _32 = copy _28 as u32 (Transmute);
+          StorageDead(_33);
+          _31 = opaque::<u32>(move _32) -> [return: bb6, unwind unreachable];
+      }
+  
+      bb6: {
+          StorageDead(_32);
+          StorageDead(_31);
+          StorageLive(_34);
+          StorageLive(_35);
+          _35 = copy _1;
+          StorageLive(_36);
+          _36 = copy _1;
+-         _34 = Pair(move _35, move _36);
++         _34 = copy _28;
+          StorageDead(_36);
+          StorageDead(_35);
+          StorageLive(_37);
+          StorageLive(_38);
+          StorageLive(_39);
+-         _39 = move _34;
+-         _38 = move _39 as u16 (Transmute);
++         _39 = copy _28;
++         _38 = copy _28 as u16 (Transmute);
+          StorageDead(_39);
+          _37 = opaque::<u16>(move _38) -> [return: bb7, unwind unreachable];
+      }
+  
+      bb7: {
+          StorageDead(_38);
+          StorageDead(_37);
           _0 = const ();
+          StorageDead(_34);
+-         StorageDead(_28);
++         nop;
+          StorageDead(_23);
+-         StorageDead(_18);
 -         StorageDead(_13);
 -         StorageDead(_7);
 -         StorageDead(_2);
 +         nop;
 +         nop;
 +         nop;
++         nop;
           return;
       }
   }
diff --git a/tests/mir-opt/gvn.aggregate_struct_then_transmute.GVN.panic-unwind.diff b/tests/mir-opt/gvn.aggregate_struct_then_transmute.GVN.panic-unwind.diff
index 2f36a3c3939..b6b6b1627b7 100644
--- a/tests/mir-opt/gvn.aggregate_struct_then_transmute.GVN.panic-unwind.diff
+++ b/tests/mir-opt/gvn.aggregate_struct_then_transmute.GVN.panic-unwind.diff
@@ -17,15 +17,49 @@
       let mut _14: u16;
       let _15: ();
       let mut _16: u16;
-      let mut _17: std::result::Result<aggregate_struct_then_transmute::Never, u16>;
+      let mut _17: std::result::Result<Never, u16>;
+      let mut _19: u16;
+      let _20: ();
+      let mut _21: u32;
+      let mut _22: std::option::Option<u16>;
+      let mut _24: u16;
+      let _25: ();
+      let mut _26: i16;
+      let mut _27: MyId;
+      let mut _29: u16;
+      let mut _30: u16;
+      let _31: ();
+      let mut _32: u32;
+      let mut _33: aggregate_struct_then_transmute::Pair;
+      let mut _35: u16;
+      let mut _36: u16;
+      let _37: ();
+      let mut _38: u16;
+      let mut _39: aggregate_struct_then_transmute::Pair;
       scope 1 {
           debug a => _2;
           let _7: TypedId<std::string::String>;
           scope 2 {
               debug b => _7;
-              let _13: std::result::Result<aggregate_struct_then_transmute::Never, u16>;
+              let _13: std::result::Result<Never, u16>;
               scope 3 {
                   debug c => _13;
+                  let _18: std::option::Option<u16>;
+                  scope 4 {
+                      debug d => _18;
+                      let _23: MyId;
+                      scope 5 {
+                          debug e => _23;
+                          let _28: aggregate_struct_then_transmute::Pair;
+                          scope 6 {
+                              debug f => _28;
+                              let _34: aggregate_struct_then_transmute::Pair;
+                              scope 7 {
+                                  debug g => _34;
+                              }
+                          }
+                      }
+                  }
               }
           }
       }
@@ -101,13 +135,106 @@
       bb3: {
           StorageDead(_16);
           StorageDead(_15);
+-         StorageLive(_18);
++         nop;
+          StorageLive(_19);
+          _19 = copy _1;
+-         _18 = Option::<u16>::Some(move _19);
++         _18 = Option::<u16>::Some(copy _1);
+          StorageDead(_19);
+          StorageLive(_20);
+          StorageLive(_21);
+          StorageLive(_22);
+          _22 = copy _18;
+-         _21 = move _22 as u32 (Transmute);
++         _21 = copy _18 as u32 (Transmute);
+          StorageDead(_22);
+          _20 = opaque::<u32>(move _21) -> [return: bb4, unwind continue];
+      }
+  
+      bb4: {
+          StorageDead(_21);
+          StorageDead(_20);
+          StorageLive(_23);
+          StorageLive(_24);
+          _24 = copy _1;
+-         _23 = MyId(move _24);
++         _23 = copy _2;
+          StorageDead(_24);
+          StorageLive(_25);
+          StorageLive(_26);
+          StorageLive(_27);
+-         _27 = move _23;
+-         _26 = move _27 as i16 (Transmute);
++         _27 = copy _2;
++         _26 = copy _1 as i16 (Transmute);
+          StorageDead(_27);
+          _25 = opaque::<i16>(move _26) -> [return: bb5, unwind continue];
+      }
+  
+      bb5: {
+          StorageDead(_26);
+          StorageDead(_25);
+-         StorageLive(_28);
++         nop;
+          StorageLive(_29);
+          _29 = copy _1;
+          StorageLive(_30);
+          _30 = copy _1;
+-         _28 = Pair(move _29, move _30);
++         _28 = Pair(copy _1, copy _1);
+          StorageDead(_30);
+          StorageDead(_29);
+          StorageLive(_31);
+          StorageLive(_32);
+          StorageLive(_33);
+-         _33 = move _28;
+-         _32 = move _33 as u32 (Transmute);
++         _33 = copy _28;
++         _32 = copy _28 as u32 (Transmute);
+          StorageDead(_33);
+          _31 = opaque::<u32>(move _32) -> [return: bb6, unwind continue];
+      }
+  
+      bb6: {
+          StorageDead(_32);
+          StorageDead(_31);
+          StorageLive(_34);
+          StorageLive(_35);
+          _35 = copy _1;
+          StorageLive(_36);
+          _36 = copy _1;
+-         _34 = Pair(move _35, move _36);
++         _34 = copy _28;
+          StorageDead(_36);
+          StorageDead(_35);
+          StorageLive(_37);
+          StorageLive(_38);
+          StorageLive(_39);
+-         _39 = move _34;
+-         _38 = move _39 as u16 (Transmute);
++         _39 = copy _28;
++         _38 = copy _28 as u16 (Transmute);
+          StorageDead(_39);
+          _37 = opaque::<u16>(move _38) -> [return: bb7, unwind continue];
+      }
+  
+      bb7: {
+          StorageDead(_38);
+          StorageDead(_37);
           _0 = const ();
+          StorageDead(_34);
+-         StorageDead(_28);
++         nop;
+          StorageDead(_23);
+-         StorageDead(_18);
 -         StorageDead(_13);
 -         StorageDead(_7);
 -         StorageDead(_2);
 +         nop;
 +         nop;
 +         nop;
++         nop;
           return;
       }
   }
diff --git a/tests/mir-opt/gvn.rs b/tests/mir-opt/gvn.rs
index 3f9f0a0be37..17357fed5a5 100644
--- a/tests/mir-opt/gvn.rs
+++ b/tests/mir-opt/gvn.rs
@@ -946,7 +946,42 @@ unsafe fn aggregate_struct_then_transmute(id: u16) {
     let c = Err::<Never, u16>(id);
     opaque(std::intrinsics::transmute::<_, u16>(c));
 
-    enum Never {}
+    // CHECK: [[TEMP1:_[0-9]+]] = Option::<u16>::Some(copy _1);
+    // CHECK: [[TEMP2:_[0-9]+]] = copy [[TEMP1]] as u32 (Transmute);
+    // CHECK: opaque::<u32>(move [[TEMP2]])
+    let d = Some(id);
+    opaque(std::intrinsics::transmute::<_, u32>(d));
+
+    // Still need the transmute, but the aggregate can be skipped
+    // CHECK: [[TEMP:_[0-9]+]] = copy _1 as i16 (Transmute);
+    // CHECK: opaque::<i16>(move [[TEMP]])
+    let e = MyId(id);
+    opaque(std::intrinsics::transmute::<_, i16>(e));
+
+    // CHECK: [[PAIR:_[0-9]+]] = Pair(copy _1, copy _1);
+    // CHECK: [[TEMP:_[0-9]+]] = copy [[PAIR]] as u32 (Transmute);
+    // CHECK: opaque::<u32>(move [[TEMP]])
+    struct Pair(u16, u16);
+    let f = Pair(id, id);
+    opaque(std::intrinsics::transmute::<_, u32>(f));
+
+    // CHECK: [[TEMP:_[0-9]+]] = copy [[PAIR]] as u16 (Transmute);
+    // CHECK: opaque::<u16>(move [[TEMP]])
+    let g = Pair(id, id);
+    opaque(std::intrinsics::transmute_unchecked::<_, u16>(g));
+}
+
+unsafe fn transmute_then_transmute_again(a: u32, c: char) {
+    // CHECK: [[TEMP1:_[0-9]+]] = copy _1 as char (Transmute);
+    // CHECK: [[TEMP2:_[0-9]+]] = copy [[TEMP1]] as i32 (Transmute);
+    // CHECK: opaque::<i32>(move [[TEMP2]])
+    let x = std::intrinsics::transmute::<u32, char>(a);
+    opaque(std::intrinsics::transmute::<char, i32>(x));
+
+    // CHECK: [[TEMP:_[0-9]+]] = copy _2 as i32 (Transmute);
+    // CHECK: opaque::<i32>(move [[TEMP]])
+    let x = std::intrinsics::transmute::<char, u32>(c);
+    opaque(std::intrinsics::transmute::<u32, i32>(x));
 }
 
 // Transmuting can skip a pointer cast so long as it wasn't a fat-to-thin cast.
@@ -1050,6 +1085,8 @@ struct MyId(u16);
 #[repr(transparent)]
 struct TypedId<T>(u16, PhantomData<T>);
 
+enum Never {}
+
 // EMIT_MIR gvn.subexpression_elimination.GVN.diff
 // EMIT_MIR gvn.wrap_unwrap.GVN.diff
 // EMIT_MIR gvn.repeated_index.GVN.diff
@@ -1083,6 +1120,7 @@ struct TypedId<T>(u16, PhantomData<T>);
 // EMIT_MIR gvn.generic_cast_metadata.GVN.diff
 // EMIT_MIR gvn.cast_pointer_eq.GVN.diff
 // EMIT_MIR gvn.aggregate_struct_then_transmute.GVN.diff
+// EMIT_MIR gvn.transmute_then_transmute_again.GVN.diff
 // EMIT_MIR gvn.cast_pointer_then_transmute.GVN.diff
 // EMIT_MIR gvn.transmute_then_cast_pointer.GVN.diff
 // EMIT_MIR gvn.remove_casts_must_change_both_sides.GVN.diff
diff --git a/tests/mir-opt/gvn.transmute_then_transmute_again.GVN.panic-abort.diff b/tests/mir-opt/gvn.transmute_then_transmute_again.GVN.panic-abort.diff
new file mode 100644
index 00000000000..962fecd2586
--- /dev/null
+++ b/tests/mir-opt/gvn.transmute_then_transmute_again.GVN.panic-abort.diff
@@ -0,0 +1,74 @@
+- // MIR for `transmute_then_transmute_again` before GVN
++ // MIR for `transmute_then_transmute_again` after GVN
+  
+  fn transmute_then_transmute_again(_1: u32, _2: char) -> () {
+      debug a => _1;
+      debug c => _2;
+      let mut _0: ();
+      let _3: char;
+      let mut _4: u32;
+      let _5: ();
+      let mut _6: i32;
+      let mut _7: char;
+      let mut _9: char;
+      let _10: ();
+      let mut _11: i32;
+      let mut _12: u32;
+      scope 1 {
+          debug x => _3;
+          let _8: u32;
+          scope 2 {
+              debug x => _8;
+          }
+      }
+  
+      bb0: {
+-         StorageLive(_3);
++         nop;
+          StorageLive(_4);
+          _4 = copy _1;
+-         _3 = move _4 as char (Transmute);
++         _3 = copy _1 as char (Transmute);
+          StorageDead(_4);
+          StorageLive(_5);
+          StorageLive(_6);
+          StorageLive(_7);
+          _7 = copy _3;
+-         _6 = move _7 as i32 (Transmute);
++         _6 = copy _3 as i32 (Transmute);
+          StorageDead(_7);
+          _5 = opaque::<i32>(move _6) -> [return: bb1, unwind unreachable];
+      }
+  
+      bb1: {
+          StorageDead(_6);
+          StorageDead(_5);
+-         StorageLive(_8);
++         nop;
+          StorageLive(_9);
+          _9 = copy _2;
+-         _8 = move _9 as u32 (Transmute);
++         _8 = copy _2 as u32 (Transmute);
+          StorageDead(_9);
+          StorageLive(_10);
+          StorageLive(_11);
+          StorageLive(_12);
+          _12 = copy _8;
+-         _11 = move _12 as i32 (Transmute);
++         _11 = copy _2 as i32 (Transmute);
+          StorageDead(_12);
+          _10 = opaque::<i32>(move _11) -> [return: bb2, unwind unreachable];
+      }
+  
+      bb2: {
+          StorageDead(_11);
+          StorageDead(_10);
+          _0 = const ();
+-         StorageDead(_8);
+-         StorageDead(_3);
++         nop;
++         nop;
+          return;
+      }
+  }
+  
diff --git a/tests/mir-opt/gvn.transmute_then_transmute_again.GVN.panic-unwind.diff b/tests/mir-opt/gvn.transmute_then_transmute_again.GVN.panic-unwind.diff
new file mode 100644
index 00000000000..e32397c1aed
--- /dev/null
+++ b/tests/mir-opt/gvn.transmute_then_transmute_again.GVN.panic-unwind.diff
@@ -0,0 +1,74 @@
+- // MIR for `transmute_then_transmute_again` before GVN
++ // MIR for `transmute_then_transmute_again` after GVN
+  
+  fn transmute_then_transmute_again(_1: u32, _2: char) -> () {
+      debug a => _1;
+      debug c => _2;
+      let mut _0: ();
+      let _3: char;
+      let mut _4: u32;
+      let _5: ();
+      let mut _6: i32;
+      let mut _7: char;
+      let mut _9: char;
+      let _10: ();
+      let mut _11: i32;
+      let mut _12: u32;
+      scope 1 {
+          debug x => _3;
+          let _8: u32;
+          scope 2 {
+              debug x => _8;
+          }
+      }
+  
+      bb0: {
+-         StorageLive(_3);
++         nop;
+          StorageLive(_4);
+          _4 = copy _1;
+-         _3 = move _4 as char (Transmute);
++         _3 = copy _1 as char (Transmute);
+          StorageDead(_4);
+          StorageLive(_5);
+          StorageLive(_6);
+          StorageLive(_7);
+          _7 = copy _3;
+-         _6 = move _7 as i32 (Transmute);
++         _6 = copy _3 as i32 (Transmute);
+          StorageDead(_7);
+          _5 = opaque::<i32>(move _6) -> [return: bb1, unwind continue];
+      }
+  
+      bb1: {
+          StorageDead(_6);
+          StorageDead(_5);
+-         StorageLive(_8);
++         nop;
+          StorageLive(_9);
+          _9 = copy _2;
+-         _8 = move _9 as u32 (Transmute);
++         _8 = copy _2 as u32 (Transmute);
+          StorageDead(_9);
+          StorageLive(_10);
+          StorageLive(_11);
+          StorageLive(_12);
+          _12 = copy _8;
+-         _11 = move _12 as i32 (Transmute);
++         _11 = copy _2 as i32 (Transmute);
+          StorageDead(_12);
+          _10 = opaque::<i32>(move _11) -> [return: bb2, unwind continue];
+      }
+  
+      bb2: {
+          StorageDead(_11);
+          StorageDead(_10);
+          _0 = const ();
+-         StorageDead(_8);
+-         StorageDead(_3);
++         nop;
++         nop;
+          return;
+      }
+  }
+  
diff --git a/tests/mir-opt/pre-codegen/slice_iter.enumerated_loop.PreCodegen.after.panic-abort.mir b/tests/mir-opt/pre-codegen/slice_iter.enumerated_loop.PreCodegen.after.panic-abort.mir
index 9494b8a2add..a2ef53e0e13 100644
--- a/tests/mir-opt/pre-codegen/slice_iter.enumerated_loop.PreCodegen.after.panic-abort.mir
+++ b/tests/mir-opt/pre-codegen/slice_iter.enumerated_loop.PreCodegen.after.panic-abort.mir
@@ -90,11 +90,13 @@ fn enumerated_loop(_1: &[T], _2: impl Fn(usize, &T)) -> () {
         StorageLive(_11);
         StorageLive(_3);
         StorageLive(_6);
-        StorageLive(_5);
+        StorageLive(_4);
         _3 = PtrMetadata(copy _1);
         _4 = &raw const (*_1);
+        StorageLive(_5);
         _5 = copy _4 as *const T (PtrToPtr);
-        _6 = NonNull::<T> { pointer: copy _5 };
+        _6 = NonNull::<T> { pointer: move _5 };
+        StorageDead(_5);
         StorageLive(_9);
         switchInt(const <T as std::mem::SizedTypeProperties>::IS_ZST) -> [0: bb1, otherwise: bb2];
     }
@@ -121,7 +123,7 @@ fn enumerated_loop(_1: &[T], _2: impl Fn(usize, &T)) -> () {
         _11 = std::slice::Iter::<'_, T> { ptr: copy _6, end_or_len: move _10, _marker: const ZeroSized: PhantomData<&T> };
         StorageDead(_10);
         StorageDead(_9);
-        StorageDead(_5);
+        StorageDead(_4);
         StorageDead(_6);
         StorageDead(_3);
         _12 = Enumerate::<std::slice::Iter<'_, T>> { iter: copy _11, count: const 0_usize };
diff --git a/tests/mir-opt/pre-codegen/slice_iter.enumerated_loop.PreCodegen.after.panic-unwind.mir b/tests/mir-opt/pre-codegen/slice_iter.enumerated_loop.PreCodegen.after.panic-unwind.mir
index c9b9798bf33..c1b846e662b 100644
--- a/tests/mir-opt/pre-codegen/slice_iter.enumerated_loop.PreCodegen.after.panic-unwind.mir
+++ b/tests/mir-opt/pre-codegen/slice_iter.enumerated_loop.PreCodegen.after.panic-unwind.mir
@@ -65,11 +65,13 @@ fn enumerated_loop(_1: &[T], _2: impl Fn(usize, &T)) -> () {
         StorageLive(_11);
         StorageLive(_3);
         StorageLive(_6);
-        StorageLive(_5);
+        StorageLive(_4);
         _3 = PtrMetadata(copy _1);
         _4 = &raw const (*_1);
+        StorageLive(_5);
         _5 = copy _4 as *const T (PtrToPtr);
-        _6 = NonNull::<T> { pointer: copy _5 };
+        _6 = NonNull::<T> { pointer: move _5 };
+        StorageDead(_5);
         StorageLive(_9);
         switchInt(const <T as std::mem::SizedTypeProperties>::IS_ZST) -> [0: bb1, otherwise: bb2];
     }
@@ -96,7 +98,7 @@ fn enumerated_loop(_1: &[T], _2: impl Fn(usize, &T)) -> () {
         _11 = std::slice::Iter::<'_, T> { ptr: copy _6, end_or_len: move _10, _marker: const ZeroSized: PhantomData<&T> };
         StorageDead(_10);
         StorageDead(_9);
-        StorageDead(_5);
+        StorageDead(_4);
         StorageDead(_6);
         StorageDead(_3);
         _12 = Enumerate::<std::slice::Iter<'_, T>> { iter: copy _11, count: const 0_usize };
diff --git a/tests/mir-opt/pre-codegen/slice_iter.forward_loop.PreCodegen.after.panic-abort.mir b/tests/mir-opt/pre-codegen/slice_iter.forward_loop.PreCodegen.after.panic-abort.mir
index ac6fe0ac547..8cebf2c6bac 100644
--- a/tests/mir-opt/pre-codegen/slice_iter.forward_loop.PreCodegen.after.panic-abort.mir
+++ b/tests/mir-opt/pre-codegen/slice_iter.forward_loop.PreCodegen.after.panic-abort.mir
@@ -57,11 +57,13 @@ fn forward_loop(_1: &[T], _2: impl Fn(&T)) -> () {
     bb0: {
         StorageLive(_3);
         StorageLive(_6);
-        StorageLive(_5);
+        StorageLive(_4);
         _3 = PtrMetadata(copy _1);
         _4 = &raw const (*_1);
+        StorageLive(_5);
         _5 = copy _4 as *const T (PtrToPtr);
-        _6 = NonNull::<T> { pointer: copy _5 };
+        _6 = NonNull::<T> { pointer: move _5 };
+        StorageDead(_5);
         StorageLive(_9);
         switchInt(const <T as std::mem::SizedTypeProperties>::IS_ZST) -> [0: bb1, otherwise: bb2];
     }
@@ -88,7 +90,7 @@ fn forward_loop(_1: &[T], _2: impl Fn(&T)) -> () {
         _11 = std::slice::Iter::<'_, T> { ptr: copy _6, end_or_len: move _10, _marker: const ZeroSized: PhantomData<&T> };
         StorageDead(_10);
         StorageDead(_9);
-        StorageDead(_5);
+        StorageDead(_4);
         StorageDead(_6);
         StorageDead(_3);
         StorageLive(_12);
diff --git a/tests/mir-opt/pre-codegen/slice_iter.forward_loop.PreCodegen.after.panic-unwind.mir b/tests/mir-opt/pre-codegen/slice_iter.forward_loop.PreCodegen.after.panic-unwind.mir
index bc2403fc544..e7e39240fed 100644
--- a/tests/mir-opt/pre-codegen/slice_iter.forward_loop.PreCodegen.after.panic-unwind.mir
+++ b/tests/mir-opt/pre-codegen/slice_iter.forward_loop.PreCodegen.after.panic-unwind.mir
@@ -57,11 +57,13 @@ fn forward_loop(_1: &[T], _2: impl Fn(&T)) -> () {
     bb0: {
         StorageLive(_3);
         StorageLive(_6);
-        StorageLive(_5);
+        StorageLive(_4);
         _3 = PtrMetadata(copy _1);
         _4 = &raw const (*_1);
+        StorageLive(_5);
         _5 = copy _4 as *const T (PtrToPtr);
-        _6 = NonNull::<T> { pointer: copy _5 };
+        _6 = NonNull::<T> { pointer: move _5 };
+        StorageDead(_5);
         StorageLive(_9);
         switchInt(const <T as std::mem::SizedTypeProperties>::IS_ZST) -> [0: bb1, otherwise: bb2];
     }
@@ -88,7 +90,7 @@ fn forward_loop(_1: &[T], _2: impl Fn(&T)) -> () {
         _11 = std::slice::Iter::<'_, T> { ptr: copy _6, end_or_len: move _10, _marker: const ZeroSized: PhantomData<&T> };
         StorageDead(_10);
         StorageDead(_9);
-        StorageDead(_5);
+        StorageDead(_4);
         StorageDead(_6);
         StorageDead(_3);
         StorageLive(_12);
diff --git a/tests/mir-opt/pre-codegen/slice_iter.reverse_loop.PreCodegen.after.panic-abort.mir b/tests/mir-opt/pre-codegen/slice_iter.reverse_loop.PreCodegen.after.panic-abort.mir
index 0eac3342ff7..58f95d0a432 100644
--- a/tests/mir-opt/pre-codegen/slice_iter.reverse_loop.PreCodegen.after.panic-abort.mir
+++ b/tests/mir-opt/pre-codegen/slice_iter.reverse_loop.PreCodegen.after.panic-abort.mir
@@ -65,11 +65,13 @@ fn reverse_loop(_1: &[T], _2: impl Fn(&T)) -> () {
         StorageLive(_11);
         StorageLive(_3);
         StorageLive(_6);
-        StorageLive(_5);
+        StorageLive(_4);
         _3 = PtrMetadata(copy _1);
         _4 = &raw const (*_1);
+        StorageLive(_5);
         _5 = copy _4 as *const T (PtrToPtr);
-        _6 = NonNull::<T> { pointer: copy _5 };
+        _6 = NonNull::<T> { pointer: move _5 };
+        StorageDead(_5);
         StorageLive(_9);
         switchInt(const <T as std::mem::SizedTypeProperties>::IS_ZST) -> [0: bb1, otherwise: bb2];
     }
@@ -96,7 +98,7 @@ fn reverse_loop(_1: &[T], _2: impl Fn(&T)) -> () {
         _11 = std::slice::Iter::<'_, T> { ptr: copy _6, end_or_len: move _10, _marker: const ZeroSized: PhantomData<&T> };
         StorageDead(_10);
         StorageDead(_9);
-        StorageDead(_5);
+        StorageDead(_4);
         StorageDead(_6);
         StorageDead(_3);
         _12 = Rev::<std::slice::Iter<'_, T>> { iter: copy _11 };
diff --git a/tests/mir-opt/pre-codegen/slice_iter.reverse_loop.PreCodegen.after.panic-unwind.mir b/tests/mir-opt/pre-codegen/slice_iter.reverse_loop.PreCodegen.after.panic-unwind.mir
index 376dc716382..e7ddacf3144 100644
--- a/tests/mir-opt/pre-codegen/slice_iter.reverse_loop.PreCodegen.after.panic-unwind.mir
+++ b/tests/mir-opt/pre-codegen/slice_iter.reverse_loop.PreCodegen.after.panic-unwind.mir
@@ -65,11 +65,13 @@ fn reverse_loop(_1: &[T], _2: impl Fn(&T)) -> () {
         StorageLive(_11);
         StorageLive(_3);
         StorageLive(_6);
-        StorageLive(_5);
+        StorageLive(_4);
         _3 = PtrMetadata(copy _1);
         _4 = &raw const (*_1);
+        StorageLive(_5);
         _5 = copy _4 as *const T (PtrToPtr);
-        _6 = NonNull::<T> { pointer: copy _5 };
+        _6 = NonNull::<T> { pointer: move _5 };
+        StorageDead(_5);
         StorageLive(_9);
         switchInt(const <T as std::mem::SizedTypeProperties>::IS_ZST) -> [0: bb1, otherwise: bb2];
     }
@@ -96,7 +98,7 @@ fn reverse_loop(_1: &[T], _2: impl Fn(&T)) -> () {
         _11 = std::slice::Iter::<'_, T> { ptr: copy _6, end_or_len: move _10, _marker: const ZeroSized: PhantomData<&T> };
         StorageDead(_10);
         StorageDead(_9);
-        StorageDead(_5);
+        StorageDead(_4);
         StorageDead(_6);
         StorageDead(_3);
         _12 = Rev::<std::slice::Iter<'_, T>> { iter: copy _11 };