Rollup merge of #147607 - dianqk:gvn-deref-loop, r=cjgillot

GVN: Invalidate derefs at loop headers

Fix a miscompiled case I found when re-reviewing rust-lang/rust#132527.

r? cjgillot
r? oli-obk
This commit is contained in:
Stuart Cook
2025-10-14 16:31:02 +11:00
committed by GitHub
7 changed files with 225 additions and 27 deletions

View File

@@ -129,6 +129,7 @@ impl<'tcx> crate::MirPass<'tcx> for GVN {
let ssa = SsaLocals::new(tcx, body, typing_env);
// Clone dominators because we need them while mutating the body.
let dominators = body.basic_blocks.dominators().clone();
let maybe_loop_headers = loops::maybe_loop_headers(body);
let arena = DroplessArena::default();
let mut state =
@@ -141,6 +142,11 @@ impl<'tcx> crate::MirPass<'tcx> for GVN {
let reverse_postorder = body.basic_blocks.reverse_postorder().to_vec();
for bb in reverse_postorder {
// N.B. With loops, reverse postorder cannot produce a valid topological order.
// A statement or terminator from inside the loop, that is not processed yet, may have performed an indirect write.
if maybe_loop_headers.contains(bb) {
state.invalidate_derefs();
}
let data = &mut body.basic_blocks.as_mut_preserves_cfg()[bb];
state.visit_basic_block_data(bb, data);
}

View File

@@ -84,7 +84,7 @@ impl<'tcx> crate::MirPass<'tcx> for JumpThreading {
body,
arena,
map: Map::new(tcx, body, Some(MAX_PLACES)),
maybe_loop_headers: maybe_loop_headers(body),
maybe_loop_headers: loops::maybe_loop_headers(body),
opportunities: Vec::new(),
};
@@ -830,29 +830,3 @@ enum Update {
Incr,
Decr,
}
/// Compute the set of loop headers in the given body. A loop header is usually defined as a block
/// which dominates one of its predecessors. This definition is only correct for reducible CFGs.
/// However, computing dominators is expensive, so we approximate according to the post-order
/// traversal order. A loop header for us is a block which is visited after its predecessor in
/// post-order. This is ok as we mostly need a heuristic.
fn maybe_loop_headers(body: &Body<'_>) -> DenseBitSet<BasicBlock> {
let mut maybe_loop_headers = DenseBitSet::new_empty(body.basic_blocks.len());
let mut visited = DenseBitSet::new_empty(body.basic_blocks.len());
for (bb, bbdata) in traversal::postorder(body) {
// Post-order means we visit successors before the block for acyclic CFGs.
// If the successor is not visited yet, consider it a loop header.
for succ in bbdata.terminator().successors() {
if !visited.contains(succ) {
maybe_loop_headers.insert(succ);
}
}
// Only mark `bb` as visited after we checked the successors, in case we have a self-loop.
// bb1: goto -> bb1;
let _new = visited.insert(bb);
debug_assert!(_new);
}
maybe_loop_headers
}