about summary refs log tree commit diff
diff options
context:
space:
mode:
authordianqk <dianqk@dianqk.net>2025-07-10 21:20:54 +0800
committerdianqk <dianqk@dianqk.net>2025-10-02 14:55:50 +0800
commitcc93132ae4f5477297ecddb0c07d2e8c74075f0c (patch)
tree7ae08a515e4ff846cdcbaa67b557a40fb169500c
parent571412f8190089c36758031fe09fc0ece59be6b7 (diff)
downloadrust-cc93132ae4f5477297ecddb0c07d2e8c74075f0c.tar.gz
rust-cc93132ae4f5477297ecddb0c07d2e8c74075f0c.zip
simplifycfg: Preserve debuginfos when merging bbs
-rw-r--r--compiler/rustc_middle/src/mir/terminator.rs8
-rw-r--r--compiler/rustc_mir_transform/src/simplify.rs30
-rw-r--r--tests/mir-opt/debuginfo/simplifycfg.drop_debuginfo.SimplifyCfg-final.diff2
-rw-r--r--tests/mir-opt/debuginfo/simplifycfg.preserve_debuginfo_1.SimplifyCfg-final.diff3
-rw-r--r--tests/mir-opt/debuginfo/simplifycfg.preserve_debuginfo_2.SimplifyCfg-final.diff3
-rw-r--r--tests/mir-opt/debuginfo/simplifycfg.preserve_debuginfo_3.SimplifyCfg-final.diff40
-rw-r--r--tests/mir-opt/debuginfo/simplifycfg.preserve_debuginfo_identical_succs.SimplifyCfg-final.diff32
-rw-r--r--tests/mir-opt/debuginfo/simplifycfg.rs207
8 files changed, 322 insertions, 3 deletions
diff --git a/compiler/rustc_middle/src/mir/terminator.rs b/compiler/rustc_middle/src/mir/terminator.rs
index 4034a3a06e9..4249914346c 100644
--- a/compiler/rustc_middle/src/mir/terminator.rs
+++ b/compiler/rustc_middle/src/mir/terminator.rs
@@ -444,6 +444,14 @@ impl<'tcx> Terminator<'tcx> {
         self.kind.successors()
     }
 
+    /// Return `Some` if all successors are identical.
+    #[inline]
+    pub fn identical_successor(&self) -> Option<BasicBlock> {
+        let mut successors = self.successors();
+        let first_succ = successors.next()?;
+        if successors.all(|succ| first_succ == succ) { Some(first_succ) } else { None }
+    }
+
     #[inline]
     pub fn successors_mut<'a>(&'a mut self, f: impl FnMut(&'a mut BasicBlock)) {
         self.kind.successors_mut(f)
diff --git a/compiler/rustc_mir_transform/src/simplify.rs b/compiler/rustc_mir_transform/src/simplify.rs
index 9f7bb3b0379..edea5cb2c72 100644
--- a/compiler/rustc_mir_transform/src/simplify.rs
+++ b/compiler/rustc_mir_transform/src/simplify.rs
@@ -144,7 +144,7 @@ impl<'a, 'tcx> CfgSimplifier<'a, 'tcx> {
         // statements itself to avoid moving the (relatively) large statements twice.
         // We do not push the statements directly into the target block (`bb`) as that is slower
         // due to additional reallocations
-        let mut merged_blocks = Vec::new();
+        let mut merged_blocks: Vec<BasicBlock> = Vec::new();
         let mut outer_changed = false;
         loop {
             let mut changed = false;
@@ -159,8 +159,9 @@ impl<'a, 'tcx> CfgSimplifier<'a, 'tcx> {
                 let mut terminator =
                     self.basic_blocks[bb].terminator.take().expect("invalid terminator state");
 
-                terminator
-                    .successors_mut(|successor| self.collapse_goto_chain(successor, &mut changed));
+                terminator.successors_mut(|successor| {
+                    self.collapse_goto_chain(successor, &mut changed);
+                });
 
                 let mut inner_changed = true;
                 merged_blocks.clear();
@@ -177,10 +178,18 @@ impl<'a, 'tcx> CfgSimplifier<'a, 'tcx> {
                 if statements_to_merge > 0 {
                     let mut statements = std::mem::take(&mut self.basic_blocks[bb].statements);
                     statements.reserve(statements_to_merge);
+                    let mut parent_bb_last_debuginfos =
+                        std::mem::take(&mut self.basic_blocks[bb].after_last_stmt_debuginfos);
                     for &from in &merged_blocks {
+                        if let Some(stmt) = self.basic_blocks[from].statements.first_mut() {
+                            stmt.debuginfos.prepend(&mut parent_bb_last_debuginfos);
+                        }
                         statements.append(&mut self.basic_blocks[from].statements);
+                        parent_bb_last_debuginfos =
+                            std::mem::take(&mut self.basic_blocks[from].after_last_stmt_debuginfos);
                     }
                     self.basic_blocks[bb].statements = statements;
+                    self.basic_blocks[bb].after_last_stmt_debuginfos = parent_bb_last_debuginfos;
                 }
 
                 self.basic_blocks[bb].terminator = Some(terminator);
@@ -220,10 +229,14 @@ impl<'a, 'tcx> CfgSimplifier<'a, 'tcx> {
         // goto chains. We should probably benchmark different sizes.
         let mut terminators: SmallVec<[_; 1]> = Default::default();
         let mut current = *start;
+        // If each successor has only one predecessor, it's a trivial goto chain.
+        // We can move all debuginfos to the last basic block.
+        let mut trivial_goto_chain = true;
         while let Some(terminator) = self.take_terminator_if_simple_goto(current) {
             let Terminator { kind: TerminatorKind::Goto { target }, .. } = terminator else {
                 unreachable!();
             };
+            trivial_goto_chain &= self.pred_count[target] == 1;
             terminators.push((current, terminator));
             current = target;
         }
@@ -235,6 +248,17 @@ impl<'a, 'tcx> CfgSimplifier<'a, 'tcx> {
             else {
                 unreachable!();
             };
+            if trivial_goto_chain {
+                let mut pred_debuginfos =
+                    std::mem::take(&mut self.basic_blocks[current].after_last_stmt_debuginfos);
+                let debuginfos = if let Some(stmt) = self.basic_blocks[last].statements.first_mut()
+                {
+                    &mut stmt.debuginfos
+                } else {
+                    &mut self.basic_blocks[last].after_last_stmt_debuginfos
+                };
+                debuginfos.prepend(&mut pred_debuginfos);
+            }
             *changed |= *target != last;
             *target = last;
             debug!("collapsing goto chain from {:?} to {:?}", current, target);
diff --git a/tests/mir-opt/debuginfo/simplifycfg.drop_debuginfo.SimplifyCfg-final.diff b/tests/mir-opt/debuginfo/simplifycfg.drop_debuginfo.SimplifyCfg-final.diff
index d4a73351ee4..7a1ac92f6dd 100644
--- a/tests/mir-opt/debuginfo/simplifycfg.drop_debuginfo.SimplifyCfg-final.diff
+++ b/tests/mir-opt/debuginfo/simplifycfg.drop_debuginfo.SimplifyCfg-final.diff
@@ -14,11 +14,13 @@
 - 
 -     bb1: {
 -         // DBG: _3 = &((*_1).0: i32);
+-         nop;
 -         goto -> bb2;
 -     }
 - 
 -     bb2: {
           // DBG: _4 = &((*_1).1: i64);
+-         nop;
           _0 = copy ((*_1).2: i32);
           return;
       }
diff --git a/tests/mir-opt/debuginfo/simplifycfg.preserve_debuginfo_1.SimplifyCfg-final.diff b/tests/mir-opt/debuginfo/simplifycfg.preserve_debuginfo_1.SimplifyCfg-final.diff
index 1c12358ad89..fa4d3aebf83 100644
--- a/tests/mir-opt/debuginfo/simplifycfg.preserve_debuginfo_1.SimplifyCfg-final.diff
+++ b/tests/mir-opt/debuginfo/simplifycfg.preserve_debuginfo_1.SimplifyCfg-final.diff
@@ -17,13 +17,16 @@
 -     bb1: {
           (*_2) = const true;
           // DBG: _3 = &((*_1).0: i32);
+-         nop;
 -         goto -> bb2;
 -     }
 - 
 -     bb2: {
           // DBG: _4 = &((*_1).1: i64);
+-         nop;
           _0 = copy ((*_1).2: i32);
           // DBG: _5 = &((*_1).2: i32);
+-         nop;
           return;
       }
   }
diff --git a/tests/mir-opt/debuginfo/simplifycfg.preserve_debuginfo_2.SimplifyCfg-final.diff b/tests/mir-opt/debuginfo/simplifycfg.preserve_debuginfo_2.SimplifyCfg-final.diff
index de8e5612c87..b01c91f196b 100644
--- a/tests/mir-opt/debuginfo/simplifycfg.preserve_debuginfo_2.SimplifyCfg-final.diff
+++ b/tests/mir-opt/debuginfo/simplifycfg.preserve_debuginfo_2.SimplifyCfg-final.diff
@@ -16,13 +16,16 @@
 - 
 -     bb1: {
           // DBG: _2 = &((*_1).0: i32);
+-         nop;
 -         goto -> bb2;
 -     }
 - 
 -     bb2: {
           // DBG: _3 = &((*_1).1: i64);
+-         nop;
           _0 = copy ((*_1).2: i32);
           // DBG: _4 = &((*_1).2: i32);
+-         nop;
           return;
       }
   }
diff --git a/tests/mir-opt/debuginfo/simplifycfg.preserve_debuginfo_3.SimplifyCfg-final.diff b/tests/mir-opt/debuginfo/simplifycfg.preserve_debuginfo_3.SimplifyCfg-final.diff
new file mode 100644
index 00000000000..caa90d79a1c
--- /dev/null
+++ b/tests/mir-opt/debuginfo/simplifycfg.preserve_debuginfo_3.SimplifyCfg-final.diff
@@ -0,0 +1,40 @@
+- // MIR for `preserve_debuginfo_3` before SimplifyCfg-final
++ // MIR for `preserve_debuginfo_3` after SimplifyCfg-final
+  
+  fn preserve_debuginfo_3(_1: &Foo, _2: bool) -> i32 {
+      debug foo_a => _3;
+      debug foo_b => _4;
+      debug foo_c => _5;
+      let mut _0: i32;
+      let mut _3: &i32;
+      let mut _4: &i64;
+      let mut _5: &i32;
+  
+      bb0: {
+-         switchInt(copy _2) -> [1: bb1, otherwise: bb2];
++         switchInt(copy _2) -> [1: bb2, otherwise: bb1];
+      }
+  
+      bb1: {
+-         // DBG: _3 = &((*_1).0: i32);
+-         nop;
+-         goto -> bb3;
+-     }
+- 
+-     bb2: {
+          // DBG: _4 = &((*_1).1: i64);
+-         nop;
+          _0 = copy ((*_1).2: i32);
+          return;
+      }
+  
+-     bb3: {
++     bb2: {
++         // DBG: _3 = &((*_1).0: i32);
+          // DBG: _5 = &((*_1).2: i32);
+-         nop;
+          _0 = copy ((*_1).0: i32);
+          return;
+      }
+  }
+  
diff --git a/tests/mir-opt/debuginfo/simplifycfg.preserve_debuginfo_identical_succs.SimplifyCfg-final.diff b/tests/mir-opt/debuginfo/simplifycfg.preserve_debuginfo_identical_succs.SimplifyCfg-final.diff
new file mode 100644
index 00000000000..d8c36990a5f
--- /dev/null
+++ b/tests/mir-opt/debuginfo/simplifycfg.preserve_debuginfo_identical_succs.SimplifyCfg-final.diff
@@ -0,0 +1,32 @@
+- // MIR for `preserve_debuginfo_identical_succs` before SimplifyCfg-final
++ // MIR for `preserve_debuginfo_identical_succs` after SimplifyCfg-final
+  
+  fn preserve_debuginfo_identical_succs(_1: &Foo, _2: bool) -> i32 {
+      debug foo_a => _3;
+      debug foo_b => _4;
+      debug foo_c => _5;
+      let mut _0: i32;
+      let mut _3: &i32;
+      let mut _4: &i64;
+      let mut _5: &i32;
+  
+      bb0: {
+-         switchInt(copy _2) -> [1: bb1, otherwise: bb1];
+-     }
+- 
+-     bb1: {
+          // DBG: _3 = &((*_1).0: i32);
+-         nop;
+-         goto -> bb2;
+-     }
+- 
+-     bb2: {
+          // DBG: _4 = &((*_1).1: i64);
+-         nop;
+          _0 = copy ((*_1).2: i32);
+          // DBG: _5 = &((*_1).2: i32);
+-         nop;
+          return;
+      }
+  }
+  
diff --git a/tests/mir-opt/debuginfo/simplifycfg.rs b/tests/mir-opt/debuginfo/simplifycfg.rs
new file mode 100644
index 00000000000..2bd510fd3b9
--- /dev/null
+++ b/tests/mir-opt/debuginfo/simplifycfg.rs
@@ -0,0 +1,207 @@
+//@ test-mir-pass: SimplifyCfg-final
+//@ compile-flags: -Zmir-enable-passes=+DeadStoreElimination-initial
+
+#![feature(core_intrinsics, custom_mir)]
+#![crate_type = "lib"]
+
+use std::intrinsics::mir::*;
+
+pub struct Foo {
+    a: i32,
+    b: i64,
+    c: i32,
+}
+
+// EMIT_MIR simplifycfg.drop_debuginfo.SimplifyCfg-final.diff
+#[custom_mir(dialect = "runtime", phase = "post-cleanup")]
+pub fn drop_debuginfo(foo: &Foo, c: bool) -> i32 {
+    // CHECK-LABEL: fn drop_debuginfo
+    // CHECK: debug foo_b => [[foo_b:_[0-9]+]];
+    // CHECK: bb0: {
+    // CHECK-NEXT: DBG: [[foo_b]] = &((*_1).1: i64)
+    // CHECK-NEXT: _0 = copy ((*_1).2: i32);
+    // CHECK-NEXT: return;
+    mir! {
+        let _foo_a: &i32;
+        let _foo_b: &i64;
+        debug foo_a => _foo_a;
+        debug foo_b => _foo_b;
+        {
+            match c {
+                true => tmp,
+                _ => ret,
+            }
+        }
+        tmp = {
+            // Because we don't know if `c` is always true, we must drop this debuginfo.
+            _foo_a = &(*foo).a;
+            Goto(ret)
+        }
+        ret = {
+            _foo_b = &(*foo).b;
+            RET = (*foo).c;
+            Return()
+        }
+    }
+}
+
+// EMIT_MIR simplifycfg.preserve_debuginfo_1.SimplifyCfg-final.diff
+#[custom_mir(dialect = "runtime", phase = "post-cleanup")]
+pub fn preserve_debuginfo_1(foo: &Foo, v: &mut bool) -> i32 {
+    // CHECK-LABEL: fn preserve_debuginfo_1
+    // CHECK: debug foo_a => [[foo_a:_[0-9]+]];
+    // CHECK: debug foo_b => [[foo_b:_[0-9]+]];
+    // CHECK: debug foo_c => [[foo_c:_[0-9]+]];
+    // CHECK: bb0: {
+    // CHECK-NEXT: (*_2) = const true;
+    // CHECK-NEXT: DBG: [[foo_a]] = &((*_1).0: i32)
+    // CHECK-NEXT: DBG: [[foo_b]] = &((*_1).1: i64)
+    // CHECK-NEXT: _0 = copy ((*_1).2: i32);
+    // CHECK-NEXT: DBG: [[foo_c]] = &((*_1).2: i32)
+    // CHECK-NEXT: return;
+    mir! {
+        let _foo_a: &i32;
+        let _foo_b: &i64;
+        let _foo_c: &i32;
+        debug foo_a => _foo_a;
+        debug foo_b => _foo_b;
+        debug foo_c => _foo_c;
+        {
+            Goto(tmp)
+        }
+        tmp = {
+            *v = true;
+            _foo_a = &(*foo).a;
+            Goto(ret)
+        }
+        ret = {
+            _foo_b = &(*foo).b;
+            RET = (*foo).c;
+            _foo_c = &(*foo).c;
+            Return()
+        }
+    }
+}
+
+// EMIT_MIR simplifycfg.preserve_debuginfo_2.SimplifyCfg-final.diff
+#[custom_mir(dialect = "runtime", phase = "post-cleanup")]
+pub fn preserve_debuginfo_2(foo: &Foo) -> i32 {
+    // CHECK-LABEL: fn preserve_debuginfo_2
+    // CHECK: debug foo_a => [[foo_a:_[0-9]+]];
+    // CHECK: debug foo_b => [[foo_b:_[0-9]+]];
+    // CHECK: debug foo_c => [[foo_c:_[0-9]+]];
+    // CHECK: bb0: {
+    // CHECK-NEXT: DBG: [[foo_a]] = &((*_1).0: i32)
+    // CHECK-NEXT: DBG: [[foo_b]] = &((*_1).1: i64)
+    // CHECK-NEXT: _0 = copy ((*_1).2: i32);
+    // CHECK-NEXT: DBG: [[foo_c]] = &((*_1).2: i32)
+    // CHECK-NEXT: return;
+    mir! {
+        let _foo_a: &i32;
+        let _foo_b: &i64;
+        let _foo_c: &i32;
+        debug foo_a => _foo_a;
+        debug foo_b => _foo_b;
+        debug foo_c => _foo_c;
+        {
+            Goto(tmp)
+        }
+        tmp = {
+            _foo_a = &(*foo).a;
+            Goto(ret)
+        }
+        ret = {
+            _foo_b = &(*foo).b;
+            RET = (*foo).c;
+            _foo_c = &(*foo).c;
+            Return()
+        }
+    }
+}
+
+// EMIT_MIR simplifycfg.preserve_debuginfo_3.SimplifyCfg-final.diff
+#[custom_mir(dialect = "runtime", phase = "post-cleanup")]
+pub fn preserve_debuginfo_3(foo: &Foo, c: bool) -> i32 {
+    // CHECK-LABEL: fn preserve_debuginfo_3
+    // CHECK: debug foo_a => [[foo_a:_[0-9]+]];
+    // CHECK: debug foo_b => [[foo_b:_[0-9]+]];
+    // CHECK: debug foo_c => [[foo_c:_[0-9]+]];
+    // CHECK: bb0: {
+    // CHECK-NEXT: switchInt(copy _2) -> [1: bb2, otherwise: bb1];
+    // CHECK: bb1: {
+    // CHECK-NEXT: DBG: [[foo_b]] = &((*_1).1: i64)
+    // CHECK-NEXT: _0 = copy ((*_1).2: i32);
+    // CHECK-NEXT: return;
+    // CHECK: bb2: {
+    // CHECK-NEXT: DBG: [[foo_a]] = &((*_1).0: i32)
+    // CHECK-NEXT: DBG: [[foo_c]] = &((*_1).2: i32)
+    // CHECK-NEXT: _0 = copy ((*_1).0: i32);
+    // CHECK-NEXT: return;
+    mir! {
+        let _foo_a: &i32;
+        let _foo_b: &i64;
+        let _foo_c: &i32;
+        debug foo_a => _foo_a;
+        debug foo_b => _foo_b;
+        debug foo_c => _foo_c;
+        {
+            match c {
+                true => tmp,
+                _ => ret,
+            }
+        }
+        tmp = {
+            _foo_a = &(*foo).a;
+            Goto(ret_1)
+        }
+        ret = {
+            _foo_b = &(*foo).b;
+            RET = (*foo).c;
+            Return()
+        }
+        ret_1 = {
+            _foo_c = &(*foo).c;
+            RET = (*foo).a;
+            Return()
+        }
+    }
+}
+
+// EMIT_MIR simplifycfg.preserve_debuginfo_identical_succs.SimplifyCfg-final.diff
+#[custom_mir(dialect = "runtime", phase = "post-cleanup")]
+pub fn preserve_debuginfo_identical_succs(foo: &Foo, c: bool) -> i32 {
+    // CHECK-LABEL: fn preserve_debuginfo_identical_succs
+    // CHECK: debug foo_a => [[foo_a:_[0-9]+]];
+    // CHECK: debug foo_b => [[foo_b:_[0-9]+]];
+    // CHECK: debug foo_c => [[foo_c:_[0-9]+]];
+    // CHECK: bb0: {
+    // CHECK-NEXT: DBG: [[foo_a]] = &((*_1).0: i32)
+    // CHECK-NEXT: DBG: [[foo_b]] = &((*_1).1: i64)
+    // CHECK-NEXT: _0 = copy ((*_1).2: i32);
+    // CHECK-NEXT: DBG: [[foo_c]] = &((*_1).2: i32)
+    // CHECK-NEXT: return;
+    mir! {
+        let _foo_a: &i32;
+        let _foo_b: &i64;
+        let _foo_c: &i32;
+        debug foo_a => _foo_a;
+        debug foo_b => _foo_b;
+        debug foo_c => _foo_c;
+        {
+            match c {
+                true => tmp,
+                _ => tmp,
+            }
+        }
+        tmp = {
+            _foo_a = &(*foo).a;
+            Goto(ret)
+        }
+        ret = {
+            _foo_b = &(*foo).b;
+            RET = (*foo).c;
+            _foo_c = &(*foo).c;
+            Return()
+        }
+    }
+}