Skip to content

Commit 0fc3e10

Browse files
committed
Create SinkConstAssignments pass
1 parent e386217 commit 0fc3e10

9 files changed

+326
-140
lines changed

compiler/rustc_mir_transform/src/const_goto.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ pub struct ConstGoto;
2828

2929
impl<'tcx> MirPass<'tcx> for ConstGoto {
3030
fn is_enabled(&self, sess: &rustc_session::Session) -> bool {
31-
sess.mir_opt_level() >= 4
31+
sess.mir_opt_level() >= 1
3232
}
3333

3434
fn run_pass(&self, tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) {

compiler/rustc_mir_transform/src/lib.rs

+3
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,7 @@ mod ssa;
9494
pub mod simplify;
9595
mod simplify_branches;
9696
mod simplify_comparison_integral;
97+
mod sink_const_assignments;
9798
mod sroa;
9899
mod uninhabited_enum_branching;
99100
mod unreachable_prop;
@@ -574,6 +575,8 @@ fn run_optimization_passes<'tcx>(tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) {
574575
&early_otherwise_branch::EarlyOtherwiseBranch,
575576
&simplify_comparison_integral::SimplifyComparisonIntegral,
576577
&dead_store_elimination::DeadStoreElimination,
578+
&sink_const_assignments::SinkConstAssignments,
579+
&const_goto::ConstGoto,
577580
&dest_prop::DestinationPropagation,
578581
&o1(simplify_branches::SimplifyConstCondition::new("final")),
579582
&o1(remove_noop_landing_pads::RemoveNoopLandingPads),
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,222 @@
1+
use crate::MirPass;
2+
use rustc_data_structures::fx::{FxHashMap, FxHashSet};
3+
use rustc_index::bit_set::BitSet;
4+
use rustc_middle::mir::visit::{NonUseContext, PlaceContext, Visitor};
5+
use rustc_middle::mir::*;
6+
use rustc_middle::ty::TyCtxt;
7+
8+
const SUCCESSOR_LIMIT: usize = 100;
9+
10+
pub struct SinkConstAssignments;
11+
12+
impl<'tcx> MirPass<'tcx> for SinkConstAssignments {
13+
fn is_enabled(&self, sess: &rustc_session::Session) -> bool {
14+
sess.mir_opt_level() >= 1
15+
}
16+
17+
fn run_pass(&self, tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) {
18+
// The primary benefit of this pass is sinking assignments to drop flags and enabling
19+
// ConstGoto and SimplifyCfg to merge the drop flag check into existing control flow.
20+
// If we permit sinking assignments to any local, we will sometimes sink an assignment into
21+
// but not completely through a goto chain, preventing SimplifyCfg from removing the
22+
// blocks.
23+
let mut optimizable_locals = branched_locals(body);
24+
if optimizable_locals.is_empty() {
25+
return;
26+
}
27+
28+
let borrowed_locals = rustc_mir_dataflow::impls::borrowed_locals(body);
29+
optimizable_locals.subtract(&borrowed_locals);
30+
if optimizable_locals.is_empty() {
31+
return;
32+
}
33+
34+
'outer: for block in 0..body.basic_blocks.len() {
35+
let block = block.into();
36+
let block_data = &body.basic_blocks[block];
37+
let Some(terminator) = &block_data.terminator else { continue; };
38+
39+
let mut successors = Vec::new();
40+
for succ in terminator.successors() {
41+
// Successors which are just a Resume are okay
42+
if is_empty_resume(&body.basic_blocks[succ]) {
43+
continue;
44+
}
45+
if body.basic_blocks.predecessors()[succ].len() != 1 {
46+
debug!("Bailing from {block:?} because {succ:?} has multiple predecessors");
47+
continue 'outer;
48+
}
49+
successors.push(succ);
50+
}
51+
52+
if successors.len() > SUCCESSOR_LIMIT {
53+
debug!("Will not sink assignment, its basic block has too many successors");
54+
}
55+
56+
let mut local_uses = None;
57+
for statement_idx in 0..body.basic_blocks[block].statements.len() {
58+
let statement = &body.basic_blocks[block].statements[statement_idx];
59+
if let StatementKind::Assign(box (place, Rvalue::Use(Operand::Constant(_)))) =
60+
&statement.kind
61+
{
62+
let local = place.local;
63+
if !place.projection.is_empty() {
64+
debug!("Nonempty place projection: {statement:?}");
65+
continue;
66+
}
67+
if !optimizable_locals.contains(local) {
68+
continue;
69+
}
70+
71+
let uses = match &local_uses {
72+
Some(uses) => uses,
73+
None => {
74+
let mut visitor = CountUsesVisitor::new();
75+
visitor.visit_basic_block_data(block, &body.basic_blocks[block]);
76+
local_uses = Some(visitor);
77+
local_uses.as_ref().unwrap()
78+
}
79+
};
80+
81+
// If the local dies in this block, don't propagate it
82+
if uses.dead.contains(&local) {
83+
continue;
84+
}
85+
if !uses.is_used_once(local) {
86+
debug!("Local used elsewhere in this block: {statement:?}");
87+
continue;
88+
}
89+
if !tcx.consider_optimizing(|| format!("Sinking const assignment to {local:?}"))
90+
{
91+
debug!("optimization fuel exhausted");
92+
break 'outer;
93+
}
94+
debug!("Sinking const assignment to {local:?}");
95+
let blocks = body.basic_blocks.as_mut_preserves_cfg();
96+
let statement = blocks[block].statements[statement_idx].replace_nop();
97+
98+
for succ in &successors {
99+
let mut successor_uses = SuccessorUsesVisitor::new(local);
100+
successor_uses.visit_basic_block_data(*succ, &blocks[*succ]);
101+
102+
if let Some(used) = successor_uses.first_use() {
103+
if used == blocks[*succ].statements.len() {
104+
blocks[*succ].statements.push(statement.clone());
105+
continue;
106+
}
107+
// If the first use of our local in this block is another const
108+
// assignment to it, do not paste a new assignment right before it
109+
// because that would just create dead code.
110+
if let StatementKind::Assign(box (
111+
place,
112+
Rvalue::Use(Operand::Constant(_)),
113+
)) = &blocks[*succ].statements[used].kind
114+
{
115+
if place.local == local && place.projection.is_empty() {
116+
continue;
117+
}
118+
}
119+
blocks[*succ].statements.insert(used, statement.clone());
120+
} else {
121+
blocks[*succ].statements.push(statement.clone());
122+
}
123+
}
124+
}
125+
}
126+
}
127+
}
128+
}
129+
130+
fn branched_locals(body: &Body<'_>) -> BitSet<Local> {
131+
let mut visitor = BranchedLocals {
132+
branched: BitSet::new_empty(body.local_decls.len()),
133+
gets_const_assign: BitSet::new_empty(body.local_decls.len()),
134+
};
135+
visitor.visit_body(body);
136+
visitor.branched.intersect(&visitor.gets_const_assign);
137+
visitor.branched
138+
}
139+
140+
struct BranchedLocals {
141+
branched: BitSet<Local>,
142+
gets_const_assign: BitSet<Local>,
143+
}
144+
145+
impl Visitor<'_> for BranchedLocals {
146+
fn visit_terminator(&mut self, terminator: &Terminator<'_>, _location: Location) {
147+
let TerminatorKind::SwitchInt { discr, .. } = &terminator.kind else { return; };
148+
if let Some(place) = discr.place() {
149+
self.branched.insert(place.local);
150+
}
151+
}
152+
153+
fn visit_statement(&mut self, statement: &Statement<'_>, _location: Location) {
154+
if let StatementKind::Assign(box (place, Rvalue::Use(Operand::Constant(_)))) =
155+
&statement.kind
156+
{
157+
if place.projection.is_empty() {
158+
self.gets_const_assign.insert(place.local);
159+
}
160+
}
161+
}
162+
}
163+
164+
fn is_empty_resume<'tcx>(block: &BasicBlockData<'tcx>) -> bool {
165+
block.statements.iter().all(|s| matches!(s.kind, StatementKind::Nop))
166+
&& block.terminator.as_ref().map(|t| &t.kind) == Some(&TerminatorKind::Resume)
167+
}
168+
169+
struct CountUsesVisitor {
170+
counts: FxHashMap<Local, usize>,
171+
dead: FxHashSet<Local>,
172+
}
173+
174+
impl CountUsesVisitor {
175+
fn new() -> Self {
176+
Self { counts: FxHashMap::default(), dead: FxHashSet::default() }
177+
}
178+
179+
fn is_used_once(&self, local: Local) -> bool {
180+
self.counts.get(&local) == Some(&1)
181+
}
182+
}
183+
184+
impl Visitor<'_> for CountUsesVisitor {
185+
fn visit_local(&mut self, local: Local, context: PlaceContext, _location: Location) {
186+
match context {
187+
PlaceContext::NonUse(NonUseContext::StorageDead) => {
188+
self.dead.insert(local);
189+
}
190+
PlaceContext::NonUse(_) => {}
191+
PlaceContext::MutatingUse(_) | PlaceContext::NonMutatingUse(_) => {
192+
*self.counts.entry(local).or_default() += 1;
193+
}
194+
};
195+
}
196+
}
197+
198+
struct SuccessorUsesVisitor {
199+
local: Local,
200+
first_use: Option<usize>,
201+
}
202+
203+
impl SuccessorUsesVisitor {
204+
fn new(local: Local) -> Self {
205+
Self { local, first_use: None }
206+
}
207+
208+
fn first_use(&self) -> Option<usize> {
209+
self.first_use
210+
}
211+
}
212+
213+
impl Visitor<'_> for SuccessorUsesVisitor {
214+
fn visit_local(&mut self, local: Local, _context: PlaceContext, location: Location) {
215+
if local == self.local {
216+
match self.first_use {
217+
None => self.first_use = Some(location.statement_index),
218+
Some(first) => self.first_use = Some(first.min(location.statement_index)),
219+
}
220+
}
221+
}
222+
}

tests/mir-opt/const_goto.issue_77355_opt.ConstGoto.diff

+7-31
Original file line numberDiff line numberDiff line change
@@ -4,48 +4,24 @@
44
fn issue_77355_opt(_1: Foo) -> u64 {
55
debug num => _1; // in scope 0 at $DIR/const_goto.rs:+0:20: +0:23
66
let mut _0: u64; // return place in scope 0 at $DIR/const_goto.rs:+0:33: +0:36
7-
- let mut _2: bool; // in scope 0 at $SRC_DIR/core/src/macros/mod.rs:LL:COL
8-
- let mut _3: isize; // in scope 0 at $DIR/const_goto.rs:+1:22: +1:28
9-
+ let mut _2: isize; // in scope 0 at $DIR/const_goto.rs:+1:22: +1:28
7+
let mut _2: isize; // in scope 0 at $DIR/const_goto.rs:+1:22: +1:28
108

119
bb0: {
12-
- StorageLive(_2); // scope 0 at $SRC_DIR/core/src/macros/mod.rs:LL:COL
13-
- _3 = discriminant(_1); // scope 0 at $DIR/const_goto.rs:+1:17: +1:20
14-
- switchInt(move _3) -> [1: bb2, 2: bb2, otherwise: bb1]; // scope 0 at $SRC_DIR/core/src/macros/mod.rs:LL:COL
15-
+ _2 = discriminant(_1); // scope 0 at $DIR/const_goto.rs:+1:17: +1:20
16-
+ switchInt(move _2) -> [1: bb2, 2: bb2, otherwise: bb1]; // scope 0 at $SRC_DIR/core/src/macros/mod.rs:LL:COL
10+
_2 = discriminant(_1); // scope 0 at $DIR/const_goto.rs:+1:17: +1:20
11+
switchInt(move _2) -> [1: bb2, 2: bb2, otherwise: bb1]; // scope 0 at $SRC_DIR/core/src/macros/mod.rs:LL:COL
1712
}
1813

1914
bb1: {
20-
- _2 = const false; // scope 0 at $SRC_DIR/core/src/macros/mod.rs:LL:COL
21-
- goto -> bb3; // scope 0 at $SRC_DIR/core/src/macros/mod.rs:LL:COL
22-
+ _0 = const 42_u64; // scope 0 at $DIR/const_goto.rs:+1:53: +1:55
23-
+ goto -> bb3; // scope 0 at $DIR/const_goto.rs:+1:5: +1:57
15+
_0 = const 42_u64; // scope 0 at $DIR/const_goto.rs:+1:53: +1:55
16+
goto -> bb3; // scope 0 at $DIR/const_goto.rs:+1:5: +1:57
2417
}
2518

2619
bb2: {
27-
- _2 = const true; // scope 0 at $SRC_DIR/core/src/macros/mod.rs:LL:COL
28-
- goto -> bb3; // scope 0 at $SRC_DIR/core/src/macros/mod.rs:LL:COL
29-
- }
30-
-
31-
- bb3: {
32-
- switchInt(move _2) -> [0: bb5, otherwise: bb4]; // scope 0 at $SRC_DIR/core/src/macros/mod.rs:LL:COL
33-
- }
34-
-
35-
- bb4: {
3620
_0 = const 23_u64; // scope 0 at $DIR/const_goto.rs:+1:41: +1:43
37-
- goto -> bb6; // scope 0 at $DIR/const_goto.rs:+1:5: +1:57
38-
+ goto -> bb3; // scope 0 at $DIR/const_goto.rs:+1:5: +1:57
21+
goto -> bb3; // scope 0 at $DIR/const_goto.rs:+1:5: +1:57
3922
}
4023

41-
- bb5: {
42-
- _0 = const 42_u64; // scope 0 at $DIR/const_goto.rs:+1:53: +1:55
43-
- goto -> bb6; // scope 0 at $DIR/const_goto.rs:+1:5: +1:57
44-
- }
45-
-
46-
- bb6: {
47-
- StorageDead(_2); // scope 0 at $DIR/const_goto.rs:+1:56: +1:57
48-
+ bb3: {
24+
bb3: {
4925
return; // scope 0 at $DIR/const_goto.rs:+2:2: +2:2
5026
}
5127
}

tests/mir-opt/const_goto_const_eval_fail.f.ConstGoto.diff

+5-15
Original file line numberDiff line numberDiff line change
@@ -14,35 +14,25 @@
1414
}
1515

1616
bb1: {
17-
_1 = const true; // scope 0 at $DIR/const_goto_const_eval_fail.rs:+4:18: +4:22
1817
goto -> bb3; // scope 0 at $DIR/const_goto_const_eval_fail.rs:+4:18: +4:22
1918
}
2019

2120
bb2: {
2221
_1 = const B; // scope 0 at $DIR/const_goto_const_eval_fail.rs:+3:26: +3:27
23-
- goto -> bb3; // scope 0 at $DIR/const_goto_const_eval_fail.rs:+3:26: +3:27
24-
+ switchInt(_1) -> [0: bb4, otherwise: bb3]; // scope 0 at $DIR/const_goto_const_eval_fail.rs:+1:5: +6:6
22+
switchInt(_1) -> [0: bb4, otherwise: bb3]; // scope 0 at $DIR/const_goto_const_eval_fail.rs:+1:5: +6:6
2523
}
2624

2725
bb3: {
28-
- switchInt(_1) -> [0: bb5, otherwise: bb4]; // scope 0 at $DIR/const_goto_const_eval_fail.rs:+1:5: +6:6
29-
- }
30-
-
31-
- bb4: {
3226
_0 = const 2_u64; // scope 0 at $DIR/const_goto_const_eval_fail.rs:+8:17: +8:18
33-
- goto -> bb6; // scope 0 at $DIR/const_goto_const_eval_fail.rs:+8:17: +8:18
34-
+ goto -> bb5; // scope 0 at $DIR/const_goto_const_eval_fail.rs:+8:17: +8:18
27+
goto -> bb5; // scope 0 at $DIR/const_goto_const_eval_fail.rs:+8:17: +8:18
3528
}
3629

37-
- bb5: {
38-
+ bb4: {
30+
bb4: {
3931
_0 = const 1_u64; // scope 0 at $DIR/const_goto_const_eval_fail.rs:+7:18: +7:19
40-
- goto -> bb6; // scope 0 at $DIR/const_goto_const_eval_fail.rs:+7:18: +7:19
41-
+ goto -> bb5; // scope 0 at $DIR/const_goto_const_eval_fail.rs:+7:18: +7:19
32+
goto -> bb5; // scope 0 at $DIR/const_goto_const_eval_fail.rs:+7:18: +7:19
4233
}
4334

44-
- bb6: {
45-
+ bb5: {
35+
bb5: {
4636
StorageDead(_2); // scope 0 at $DIR/const_goto_const_eval_fail.rs:+10:1: +10:2
4737
StorageDead(_1); // scope 0 at $DIR/const_goto_const_eval_fail.rs:+10:1: +10:2
4838
return; // scope 0 at $DIR/const_goto_const_eval_fail.rs:+10:2: +10:2

0 commit comments

Comments
 (0)