Skip to content

Commit 408e09e

Browse files
dianqkcuviper
authored andcommitted
GVN: Invalidate derefs at loop headers
(cherry picked from commit 2048b9c)
1 parent 5b36a0a commit 408e09e

File tree

7 files changed

+48
-22
lines changed

7 files changed

+48
-22
lines changed
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
use rustc_index::bit_set::DenseBitSet;
2+
3+
use super::*;
4+
5+
/// Compute the set of loop headers in the given body. A loop header is usually defined as a block
6+
/// which dominates one of its predecessors. This definition is only correct for reducible CFGs.
7+
/// However, computing dominators is expensive, so we approximate according to the post-order
8+
/// traversal order. A loop header for us is a block which is visited after its predecessor in
9+
/// post-order. This is ok as we mostly need a heuristic.
10+
pub fn maybe_loop_headers(body: &Body<'_>) -> DenseBitSet<BasicBlock> {
11+
let mut maybe_loop_headers = DenseBitSet::new_empty(body.basic_blocks.len());
12+
let mut visited = DenseBitSet::new_empty(body.basic_blocks.len());
13+
for (bb, bbdata) in traversal::postorder(body) {
14+
// Post-order means we visit successors before the block for acyclic CFGs.
15+
// If the successor is not visited yet, consider it a loop header.
16+
for succ in bbdata.terminator().successors() {
17+
if !visited.contains(succ) {
18+
maybe_loop_headers.insert(succ);
19+
}
20+
}
21+
22+
// Only mark `bb` as visited after we checked the successors, in case we have a self-loop.
23+
// bb1: goto -> bb1;
24+
let _new = visited.insert(bb);
25+
debug_assert!(_new);
26+
}
27+
28+
maybe_loop_headers
29+
}

compiler/rustc_middle/src/mir/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ mod statement;
5151
mod syntax;
5252
mod terminator;
5353

54+
pub mod loops;
5455
pub mod traversal;
5556
pub mod visit;
5657

compiler/rustc_mir_transform/src/gvn.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,7 @@ impl<'tcx> crate::MirPass<'tcx> for GVN {
126126
let ssa = SsaLocals::new(tcx, body, typing_env);
127127
// Clone dominators because we need them while mutating the body.
128128
let dominators = body.basic_blocks.dominators().clone();
129+
let maybe_loop_headers = loops::maybe_loop_headers(body);
129130

130131
let mut state = VnState::new(tcx, body, typing_env, &ssa, dominators, &body.local_decls);
131132

@@ -136,6 +137,11 @@ impl<'tcx> crate::MirPass<'tcx> for GVN {
136137

137138
let reverse_postorder = body.basic_blocks.reverse_postorder().to_vec();
138139
for bb in reverse_postorder {
140+
// N.B. With loops, reverse postorder cannot produce a valid topological order.
141+
// A statement or terminator from inside the loop, that is not processed yet, may have performed an indirect write.
142+
if maybe_loop_headers.contains(bb) {
143+
state.invalidate_derefs();
144+
}
139145
let data = &mut body.basic_blocks.as_mut_preserves_cfg()[bb];
140146
state.visit_basic_block_data(bb, data);
141147
}

compiler/rustc_mir_transform/src/jump_threading.rs

Lines changed: 1 addition & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ impl<'tcx> crate::MirPass<'tcx> for JumpThreading {
8484
body,
8585
arena,
8686
map: Map::new(tcx, body, Some(MAX_PLACES)),
87-
loop_headers: loop_headers(body),
87+
loop_headers: loops::maybe_loop_headers(body),
8888
opportunities: Vec::new(),
8989
};
9090

@@ -832,21 +832,3 @@ enum Update {
832832
Incr,
833833
Decr,
834834
}
835-
836-
/// Compute the set of loop headers in the given body. We define a loop header as a block which has
837-
/// at least a predecessor which it dominates. This definition is only correct for reducible CFGs.
838-
/// But if the CFG is already irreducible, there is no point in trying much harder.
839-
/// is already irreducible.
840-
fn loop_headers(body: &Body<'_>) -> DenseBitSet<BasicBlock> {
841-
let mut loop_headers = DenseBitSet::new_empty(body.basic_blocks.len());
842-
let dominators = body.basic_blocks.dominators();
843-
// Only visit reachable blocks.
844-
for (bb, bbdata) in traversal::preorder(body) {
845-
for succ in bbdata.terminator().successors() {
846-
if dominators.dominates(succ, bb) {
847-
loop_headers.insert(succ);
848-
}
849-
}
850-
}
851-
loop_headers
852-
}

tests/mir-opt/gvn_loop.loop_deref_mut.GVN.diff

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@
7171
StorageLive(_12);
7272
_12 = copy _7;
7373
- _11 = Value::V0(move _12);
74-
+ _11 = copy (*_3);
74+
+ _11 = Value::V0(copy _7);
7575
StorageDead(_12);
7676
StorageLive(_13);
7777
StorageLive(_14);

tests/mir-opt/gvn_loop.rs

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
// skip-filecheck
21
//@ test-mir-pass: GVN
32

43
#![crate_type = "lib"]
@@ -9,8 +8,17 @@ pub enum Value {
98
V1,
109
}
1110

11+
// Check that we do not use the dereferenced value of `val_alias` when returning.
12+
1213
// EMIT_MIR gvn_loop.loop_deref_mut.GVN.diff
1314
fn loop_deref_mut(val: &mut Value) -> Value {
15+
// CHECK-LABEL: fn loop_deref_mut(
16+
// CHECK: [[VAL_REF:_.*]] = get::<Value>(
17+
// CHECK: [[V:_.*]] = copy (((*[[VAL_REF]]) as V0).0: i32);
18+
// CEHCK-NOT: copy (*[[VAL_REF]]);
19+
// CHECK: [[RET:_*]] = Value::V0(copy [[V]]);
20+
// CEHCK-NOT: copy (*[[VAL_REF]]);
21+
// CHECK: _0 = move [[RET]]
1422
let val_alias: &Value = get(val);
1523
let mut stop = false;
1624
let Value::V0(v) = *val_alias else { unsafe { core::intrinsics::unreachable() } };

tests/ui/mir/gvn-loop-miscompile.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
//@ compile-flags: -O
2-
//@ run-fail
2+
//@ run-pass
33

44
pub enum Value {
55
V0(i32),

0 commit comments

Comments
 (0)