Skip to content

Commit 4fc3a05

Browse files
authored
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 #132527. r? cjgillot r? oli-obk
2 parents 2ad6152 + 2048b9c commit 4fc3a05

File tree

7 files changed

+225
-27
lines changed

7 files changed

+225
-27
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
@@ -129,6 +129,7 @@ impl<'tcx> crate::MirPass<'tcx> for GVN {
129129
let ssa = SsaLocals::new(tcx, body, typing_env);
130130
// Clone dominators because we need them while mutating the body.
131131
let dominators = body.basic_blocks.dominators().clone();
132+
let maybe_loop_headers = loops::maybe_loop_headers(body);
132133

133134
let arena = DroplessArena::default();
134135
let mut state =
@@ -141,6 +142,11 @@ impl<'tcx> crate::MirPass<'tcx> for GVN {
141142

142143
let reverse_postorder = body.basic_blocks.reverse_postorder().to_vec();
143144
for bb in reverse_postorder {
145+
// N.B. With loops, reverse postorder cannot produce a valid topological order.
146+
// A statement or terminator from inside the loop, that is not processed yet, may have performed an indirect write.
147+
if maybe_loop_headers.contains(bb) {
148+
state.invalidate_derefs();
149+
}
144150
let data = &mut body.basic_blocks.as_mut_preserves_cfg()[bb];
145151
state.visit_basic_block_data(bb, data);
146152
}

compiler/rustc_mir_transform/src/jump_threading.rs

Lines changed: 1 addition & 27 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-
maybe_loop_headers: maybe_loop_headers(body),
87+
maybe_loop_headers: loops::maybe_loop_headers(body),
8888
opportunities: Vec::new(),
8989
};
9090

@@ -830,29 +830,3 @@ enum Update {
830830
Incr,
831831
Decr,
832832
}
833-
834-
/// Compute the set of loop headers in the given body. A loop header is usually defined as a block
835-
/// which dominates one of its predecessors. This definition is only correct for reducible CFGs.
836-
/// However, computing dominators is expensive, so we approximate according to the post-order
837-
/// traversal order. A loop header for us is a block which is visited after its predecessor in
838-
/// post-order. This is ok as we mostly need a heuristic.
839-
fn maybe_loop_headers(body: &Body<'_>) -> DenseBitSet<BasicBlock> {
840-
let mut maybe_loop_headers = DenseBitSet::new_empty(body.basic_blocks.len());
841-
let mut visited = DenseBitSet::new_empty(body.basic_blocks.len());
842-
for (bb, bbdata) in traversal::postorder(body) {
843-
// Post-order means we visit successors before the block for acyclic CFGs.
844-
// If the successor is not visited yet, consider it a loop header.
845-
for succ in bbdata.terminator().successors() {
846-
if !visited.contains(succ) {
847-
maybe_loop_headers.insert(succ);
848-
}
849-
}
850-
851-
// Only mark `bb` as visited after we checked the successors, in case we have a self-loop.
852-
// bb1: goto -> bb1;
853-
let _new = visited.insert(bb);
854-
debug_assert!(_new);
855-
}
856-
857-
maybe_loop_headers
858-
}
Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
- // MIR for `loop_deref_mut` before GVN
2+
+ // MIR for `loop_deref_mut` after GVN
3+
4+
fn loop_deref_mut(_1: &mut Value) -> Value {
5+
debug val => _1;
6+
let mut _0: Value;
7+
let _2: &Value;
8+
let _3: &Value;
9+
let mut _4: &Value;
10+
let mut _6: !;
11+
let mut _8: isize;
12+
let mut _9: !;
13+
let mut _10: ();
14+
let mut _12: i32;
15+
let _13: ();
16+
let mut _14: bool;
17+
let mut _15: !;
18+
let mut _16: Value;
19+
scope 1 {
20+
debug val_alias => _2;
21+
let mut _5: bool;
22+
scope 2 {
23+
debug stop => _5;
24+
let _7: i32;
25+
scope 3 {
26+
debug v => _7;
27+
let _11: Value;
28+
scope 4 {
29+
debug v => _11;
30+
}
31+
}
32+
}
33+
}
34+
35+
bb0: {
36+
StorageLive(_2);
37+
- StorageLive(_3);
38+
+ nop;
39+
StorageLive(_4);
40+
_4 = &(*_1);
41+
_3 = get::<Value>(move _4) -> [return: bb1, unwind unreachable];
42+
}
43+
44+
bb1: {
45+
_2 = &(*_3);
46+
StorageDead(_4);
47+
- StorageDead(_3);
48+
+ nop;
49+
StorageLive(_5);
50+
_5 = const false;
51+
- _8 = discriminant((*_2));
52+
+ _8 = discriminant((*_3));
53+
switchInt(move _8) -> [0: bb3, otherwise: bb2];
54+
}
55+
56+
bb2: {
57+
unreachable;
58+
}
59+
60+
bb3: {
61+
- StorageLive(_7);
62+
- _7 = copy (((*_2) as V0).0: i32);
63+
+ nop;
64+
+ _7 = copy (((*_3) as V0).0: i32);
65+
StorageLive(_9);
66+
goto -> bb4;
67+
}
68+
69+
bb4: {
70+
StorageLive(_11);
71+
StorageLive(_12);
72+
_12 = copy _7;
73+
- _11 = Value::V0(move _12);
74+
+ _11 = Value::V0(copy _7);
75+
StorageDead(_12);
76+
StorageLive(_13);
77+
StorageLive(_14);
78+
_14 = copy _5;
79+
switchInt(move _14) -> [0: bb6, otherwise: bb5];
80+
}
81+
82+
bb5: {
83+
_0 = move _11;
84+
StorageDead(_14);
85+
StorageDead(_13);
86+
StorageDead(_11);
87+
StorageDead(_9);
88+
- StorageDead(_7);
89+
+ nop;
90+
StorageDead(_5);
91+
StorageDead(_2);
92+
return;
93+
}
94+
95+
bb6: {
96+
_13 = const ();
97+
StorageDead(_14);
98+
StorageDead(_13);
99+
_5 = const true;
100+
StorageLive(_16);
101+
- _16 = Value::V1;
102+
- (*_1) = move _16;
103+
+ _16 = const Value::V1;
104+
+ (*_1) = const Value::V1;
105+
StorageDead(_16);
106+
_10 = const ();
107+
StorageDead(_11);
108+
goto -> bb4;
109+
}
110+
+ }
111+
+
112+
+ ALLOC0 (size: 8, align: 4) {
113+
+ 01 00 00 00 __ __ __ __ │ ....░░░░
114+
}
115+

tests/mir-opt/gvn_loop.rs

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
//@ test-mir-pass: GVN
2+
3+
#![crate_type = "lib"]
4+
#![feature(core_intrinsics, rustc_attrs)]
5+
6+
pub enum Value {
7+
V0(i32),
8+
V1,
9+
}
10+
11+
// Check that we do not use the dereferenced value of `val_alias` when returning.
12+
13+
// EMIT_MIR gvn_loop.loop_deref_mut.GVN.diff
14+
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]]
22+
let val_alias: &Value = get(val);
23+
let mut stop = false;
24+
let Value::V0(v) = *val_alias else { unsafe { core::intrinsics::unreachable() } };
25+
loop {
26+
let v = Value::V0(v);
27+
if stop {
28+
return v;
29+
}
30+
stop = true;
31+
*val = Value::V1;
32+
}
33+
}
34+
35+
#[inline(never)]
36+
#[rustc_nounwind]
37+
fn get<T>(v: &T) -> &T {
38+
v
39+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
//@ compile-flags: -O
2+
//@ run-pass
3+
4+
pub enum Value {
5+
V0(i32),
6+
V1,
7+
}
8+
9+
fn set_discriminant(val: &mut Value) -> Value {
10+
let val_alias: &Value = get(val);
11+
let mut stop = false;
12+
let Value::V0(v) = *val_alias else {
13+
unreachable!();
14+
};
15+
loop {
16+
let v = Value::V0(v);
17+
if stop {
18+
return v;
19+
}
20+
stop = true;
21+
*val = Value::V1;
22+
}
23+
}
24+
25+
fn main() {
26+
let mut v = Value::V0(1);
27+
let v = set_discriminant(&mut v);
28+
assert!(matches!(v, Value::V0(1)));
29+
}
30+
31+
#[inline(never)]
32+
fn get<T>(v: &T) -> &T {
33+
v
34+
}

0 commit comments

Comments
 (0)