simplifycfg: Preserve debuginfos when merging bbs

This commit is contained in:
dianqk 2025-07-10 21:20:54 +08:00
parent 571412f819
commit cc93132ae4
No known key found for this signature in database
8 changed files with 322 additions and 3 deletions

View file

@ -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)

View file

@ -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);

View file

@ -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;
}

View file

@ -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;
}
}

View file

@ -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;
}
}

View file

@ -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;
}
}

View file

@ -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;
}
}

View file

@ -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()
}
}
}