Skip to content

Commit 2f48442

Browse files
committed
Auto merge of #129283 - saethlin:unreachable-allocas, r=<try>
Don't alloca for unused locals We already have a concept of mono-unreachable basic blocks; this is primarily useful for ensuring that we do not compile code under an `if false`. But since we never gave locals the same analysis, a large local only used under an `if false` will still have stack space allocated for it. There are 3 places we traverse MIR during monomorphization: Inside the collector, `non_ssa_locals`, and the walk to generate code. Unfortunately, #129283 (comment) indicates that we cannot afford the expense of tracking reachable locals during the collector's traversal, so we do need at least two mono-reachable traversals. And of course caching is of no help here because the benchmarks that regress are incr-unchanged; they don't do any codegen. This fixes the second problem in #129282, and brings us anther step toward `const if` at home. try-job: test-various
2 parents c0838c8 + 338965b commit 2f48442

File tree

9 files changed

+149
-54
lines changed

9 files changed

+149
-54
lines changed

compiler/rustc_codegen_ssa/src/mir/analyze.rs

+16-9
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ use crate::traits::*;
1515

1616
pub(crate) fn non_ssa_locals<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>>(
1717
fx: &FunctionCx<'a, 'tcx, Bx>,
18+
traversal_order: &[mir::BasicBlock],
1819
) -> BitSet<mir::Local> {
1920
let mir = fx.mir;
2021
let dominators = mir.basic_blocks.dominators();
@@ -24,13 +25,7 @@ pub(crate) fn non_ssa_locals<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>>(
2425
.map(|decl| {
2526
let ty = fx.monomorphize(decl.ty);
2627
let layout = fx.cx.spanned_layout_of(ty, decl.source_info.span);
27-
if layout.is_zst() {
28-
LocalKind::ZST
29-
} else if fx.cx.is_backend_immediate(layout) || fx.cx.is_backend_scalar_pair(layout) {
30-
LocalKind::Unused
31-
} else {
32-
LocalKind::Memory
33-
}
28+
if layout.is_zst() { LocalKind::ZST } else { LocalKind::Unused }
3429
})
3530
.collect();
3631

@@ -44,7 +39,8 @@ pub(crate) fn non_ssa_locals<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>>(
4439
// If there exists a local definition that dominates all uses of that local,
4540
// the definition should be visited first. Traverse blocks in an order that
4641
// is a topological sort of dominance partial order.
47-
for (bb, data) in traversal::reverse_postorder(mir) {
42+
for bb in traversal_order.iter().copied() {
43+
let data = &mir.basic_blocks[bb];
4844
analyzer.visit_basic_block_data(bb, data);
4945
}
5046

@@ -77,11 +73,22 @@ struct LocalAnalyzer<'a, 'b, 'tcx, Bx: BuilderMethods<'b, 'tcx>> {
7773

7874
impl<'a, 'b, 'tcx, Bx: BuilderMethods<'b, 'tcx>> LocalAnalyzer<'a, 'b, 'tcx, Bx> {
7975
fn define(&mut self, local: mir::Local, location: DefLocation) {
76+
let fx = self.fx;
8077
let kind = &mut self.locals[local];
78+
let decl = &fx.mir.local_decls[local];
8179
match *kind {
8280
LocalKind::ZST => {}
8381
LocalKind::Memory => {}
84-
LocalKind::Unused => *kind = LocalKind::SSA(location),
82+
LocalKind::Unused => {
83+
let ty = fx.monomorphize(decl.ty);
84+
let layout = fx.cx.spanned_layout_of(ty, decl.source_info.span);
85+
*kind =
86+
if fx.cx.is_backend_immediate(layout) || fx.cx.is_backend_scalar_pair(layout) {
87+
LocalKind::SSA(location)
88+
} else {
89+
LocalKind::Memory
90+
};
91+
}
8592
LocalKind::SSA(_) => *kind = LocalKind::Memory,
8693
}
8794
}

compiler/rustc_codegen_ssa/src/mir/mod.rs

+15-11
Original file line numberDiff line numberDiff line change
@@ -218,7 +218,8 @@ pub fn codegen_mir<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>>(
218218

219219
fx.per_local_var_debug_info = fx.compute_per_local_var_debug_info(&mut start_bx);
220220

221-
let memory_locals = analyze::non_ssa_locals(&fx);
221+
let traversal_order = traversal::mono_reachable_reverse_postorder(mir, cx.tcx(), instance);
222+
let memory_locals = analyze::non_ssa_locals(&fx, &traversal_order);
222223

223224
// Allocate variable and temp allocas
224225
let local_values = {
@@ -277,17 +278,20 @@ pub fn codegen_mir<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>>(
277278
// So drop the builder of `start_llbb` to avoid having two at the same time.
278279
drop(start_bx);
279280

280-
let reachable_blocks = traversal::mono_reachable_as_bitset(mir, cx.tcx(), instance);
281+
let mut unreached_blocks = BitSet::new_filled(mir.basic_blocks.len());
282+
// Codegen the body of each reachable block using our reverse postorder list.
283+
for bb in traversal_order {
284+
fx.codegen_block(bb);
285+
unreached_blocks.remove(bb);
286+
}
281287

282-
// Codegen the body of each block using reverse postorder
283-
for (bb, _) in traversal::reverse_postorder(mir) {
284-
if reachable_blocks.contains(bb) {
285-
fx.codegen_block(bb);
286-
} else {
287-
// We want to skip this block, because it's not reachable. But we still create
288-
// the block so terminators in other blocks can reference it.
289-
fx.codegen_block_as_unreachable(bb);
290-
}
288+
// FIXME: These empty unreachable blocks are *mostly* a waste. They are occasionally
289+
// targets for a SwitchInt terminator, but the reimplementation of the mono-reachable
290+
// simplification in SwitchInt lowering sometimes misses cases that
291+
// mono_reachable_reverse_postorder manages to figure out.
292+
// The solution is to do something like post-mono GVN. But for now we have this hack.
293+
for bb in unreached_blocks.iter() {
294+
fx.codegen_block_as_unreachable(bb);
291295
}
292296
}
293297

compiler/rustc_middle/src/mir/basic_blocks.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ impl<'tcx> BasicBlocks<'tcx> {
7171
#[inline]
7272
pub fn reverse_postorder(&self) -> &[BasicBlock] {
7373
self.cache.reverse_postorder.get_or_init(|| {
74-
let mut rpo: Vec<_> = Postorder::new(&self.basic_blocks, START_BLOCK).collect();
74+
let mut rpo: Vec<_> = Postorder::new(&self.basic_blocks, START_BLOCK, ()).collect();
7575
rpo.reverse();
7676
rpo
7777
})

compiler/rustc_middle/src/mir/mod.rs

+13
Original file line numberDiff line numberDiff line change
@@ -1424,6 +1424,19 @@ impl<'tcx> BasicBlockData<'tcx> {
14241424
pub fn is_empty_unreachable(&self) -> bool {
14251425
self.statements.is_empty() && matches!(self.terminator().kind, TerminatorKind::Unreachable)
14261426
}
1427+
1428+
/// Like [`Terminator::successors`] but tries to use information available from the [`Instance`]
1429+
/// to skip successors like the `false` side of an `if const {`.
1430+
///
1431+
/// This is used to implement [`traversal::mono_reachable`] and
1432+
/// [`traversal::mono_reachable_reverse_postorder`].
1433+
pub fn mono_successors(&self, tcx: TyCtxt<'tcx>, instance: Instance<'tcx>) -> Successors<'_> {
1434+
if let Some((bits, targets)) = Body::try_const_mono_switchint(tcx, instance, self) {
1435+
targets.successors_for_value(bits)
1436+
} else {
1437+
self.terminator().successors()
1438+
}
1439+
}
14271440
}
14281441

14291442
///////////////////////////////////////////////////////////////////////////

compiler/rustc_middle/src/mir/terminator.rs

+11
Original file line numberDiff line numberDiff line change
@@ -413,6 +413,17 @@ mod helper {
413413
use super::*;
414414
pub type Successors<'a> = impl DoubleEndedIterator<Item = BasicBlock> + 'a;
415415
pub type SuccessorsMut<'a> = impl DoubleEndedIterator<Item = &'a mut BasicBlock> + 'a;
416+
417+
impl SwitchTargets {
418+
/// Like [`SwitchTargets::target_for_value`], but returning the same type as
419+
/// [`Terminator::successors`].
420+
#[inline]
421+
pub fn successors_for_value(&self, value: u128) -> Successors<'_> {
422+
let target = self.target_for_value(value);
423+
(&[]).into_iter().copied().chain(Some(target))
424+
}
425+
}
426+
416427
impl<'tcx> TerminatorKind<'tcx> {
417428
#[inline]
418429
pub fn successors(&self) -> Successors<'_> {

compiler/rustc_middle/src/mir/traversal.rs

+61-24
Original file line numberDiff line numberDiff line change
@@ -104,36 +104,46 @@ impl<'a, 'tcx> Iterator for Preorder<'a, 'tcx> {
104104
/// ```
105105
///
106106
/// A Postorder traversal of this graph is `D B C A` or `D C B A`
107-
pub struct Postorder<'a, 'tcx> {
107+
pub struct Postorder<'a, 'tcx, C> {
108108
basic_blocks: &'a IndexSlice<BasicBlock, BasicBlockData<'tcx>>,
109109
visited: BitSet<BasicBlock>,
110110
visit_stack: Vec<(BasicBlock, Successors<'a>)>,
111111
root_is_start_block: bool,
112+
extra: C,
112113
}
113114

114-
impl<'a, 'tcx> Postorder<'a, 'tcx> {
115+
impl<'a, 'tcx, C> Postorder<'a, 'tcx, C>
116+
where
117+
C: Customization<'tcx>,
118+
{
115119
pub fn new(
116120
basic_blocks: &'a IndexSlice<BasicBlock, BasicBlockData<'tcx>>,
117121
root: BasicBlock,
118-
) -> Postorder<'a, 'tcx> {
122+
extra: C,
123+
) -> Postorder<'a, 'tcx, C> {
119124
let mut po = Postorder {
120125
basic_blocks,
121126
visited: BitSet::new_empty(basic_blocks.len()),
122127
visit_stack: Vec::new(),
123128
root_is_start_block: root == START_BLOCK,
129+
extra,
124130
};
125131

126-
let data = &po.basic_blocks[root];
127-
128-
if let Some(ref term) = data.terminator {
129-
po.visited.insert(root);
130-
po.visit_stack.push((root, term.successors()));
131-
po.traverse_successor();
132-
}
132+
po.visit(root);
133+
po.traverse_successor();
133134

134135
po
135136
}
136137

138+
fn visit(&mut self, bb: BasicBlock) {
139+
if !self.visited.insert(bb) {
140+
return;
141+
}
142+
let data = &self.basic_blocks[bb];
143+
let successors = C::successors(data, self.extra);
144+
self.visit_stack.push((bb, successors));
145+
}
146+
137147
fn traverse_successor(&mut self) {
138148
// This is quite a complex loop due to 1. the borrow checker not liking it much
139149
// and 2. what exactly is going on is not clear
@@ -183,16 +193,15 @@ impl<'a, 'tcx> Postorder<'a, 'tcx> {
183193
// since we've already visited `E`, that child isn't added to the stack. The last
184194
// two iterations yield `B` and finally `A` for a final traversal of [E, D, C, B, A]
185195
while let Some(bb) = self.visit_stack.last_mut().and_then(|(_, iter)| iter.next_back()) {
186-
if self.visited.insert(bb) {
187-
if let Some(term) = &self.basic_blocks[bb].terminator {
188-
self.visit_stack.push((bb, term.successors()));
189-
}
190-
}
196+
self.visit(bb);
191197
}
192198
}
193199
}
194200

195-
impl<'tcx> Iterator for Postorder<'_, 'tcx> {
201+
impl<'tcx, C> Iterator for Postorder<'_, 'tcx, C>
202+
where
203+
C: Customization<'tcx>,
204+
{
196205
type Item = BasicBlock;
197206

198207
fn next(&mut self) -> Option<BasicBlock> {
@@ -232,6 +241,40 @@ pub fn postorder<'a, 'tcx>(
232241
reverse_postorder(body).rev()
233242
}
234243

244+
/// Lets us plug in some additional logic and data into a Postorder traversal. Or not.
245+
pub trait Customization<'tcx>: Copy {
246+
fn successors<'a>(_: &'a BasicBlockData<'tcx>, _: Self) -> Successors<'a>;
247+
}
248+
249+
impl<'tcx> Customization<'tcx> for () {
250+
fn successors<'a>(data: &'a BasicBlockData<'tcx>, _: ()) -> Successors<'a> {
251+
data.terminator().successors()
252+
}
253+
}
254+
255+
impl<'tcx> Customization<'tcx> for (TyCtxt<'tcx>, Instance<'tcx>) {
256+
fn successors<'a>(
257+
data: &'a BasicBlockData<'tcx>,
258+
(tcx, instance): (TyCtxt<'tcx>, Instance<'tcx>),
259+
) -> Successors<'a> {
260+
data.mono_successors(tcx, instance)
261+
}
262+
}
263+
264+
pub fn mono_reachable_reverse_postorder<'a, 'tcx>(
265+
body: &'a Body<'tcx>,
266+
tcx: TyCtxt<'tcx>,
267+
instance: Instance<'tcx>,
268+
) -> Vec<BasicBlock> {
269+
let mut iter = Postorder::new(&body.basic_blocks, START_BLOCK, (tcx, instance));
270+
let mut items = Vec::with_capacity(body.basic_blocks.len());
271+
while let Some(block) = iter.next() {
272+
items.push(block);
273+
}
274+
items.reverse();
275+
items
276+
}
277+
235278
/// Returns an iterator over all basic blocks reachable from the `START_BLOCK` in no particular
236279
/// order.
237280
///
@@ -358,14 +401,8 @@ impl<'a, 'tcx> Iterator for MonoReachable<'a, 'tcx> {
358401

359402
let data = &self.body[idx];
360403

361-
if let Some((bits, targets)) =
362-
Body::try_const_mono_switchint(self.tcx, self.instance, data)
363-
{
364-
let target = targets.target_for_value(bits);
365-
self.add_work([target]);
366-
} else {
367-
self.add_work(data.terminator().successors());
368-
}
404+
let targets = data.mono_successors(self.tcx, self.instance);
405+
self.add_work(targets);
369406

370407
return Some((idx, data));
371408
}

src/tools/clippy/clippy_utils/src/mir/mod.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ pub fn visit_local_usage(locals: &[Local], mir: &Body<'_>, location: Location) -
3030
locals.len()
3131
];
3232

33-
traversal::Postorder::new(&mir.basic_blocks, location.block)
33+
traversal::Postorder::new(&mir.basic_blocks, location.block, ())
3434
.collect::<Vec<_>>()
3535
.into_iter()
3636
.rev()

tests/codegen/constant-branch.rs

+6-8
Original file line numberDiff line numberDiff line change
@@ -7,18 +7,19 @@
77
// CHECK-LABEL: @if_bool
88
#[no_mangle]
99
pub fn if_bool() {
10-
// CHECK: br label %{{.+}}
10+
// CHECK-NOT: br i1
11+
// CHECK-NOT: switch
1112
_ = if true { 0 } else { 1 };
1213

13-
// CHECK: br label %{{.+}}
1414
_ = if false { 0 } else { 1 };
1515
}
1616

1717
// CHECK-LABEL: @if_constant_int_eq
1818
#[no_mangle]
1919
pub fn if_constant_int_eq() {
20+
// CHECK-NOT: br i1
21+
// CHECK-NOT: switch
2022
let val = 0;
21-
// CHECK: br label %{{.+}}
2223
_ = if val == 0 { 0 } else { 1 };
2324

2425
// CHECK: br label %{{.+}}
@@ -28,23 +29,20 @@ pub fn if_constant_int_eq() {
2829
// CHECK-LABEL: @if_constant_match
2930
#[no_mangle]
3031
pub fn if_constant_match() {
31-
// CHECK: br label %{{.+}}
32+
// CHECK-NOT: br i1
33+
// CHECK-NOT: switch
3234
_ = match 1 {
3335
1 => 2,
3436
2 => 3,
3537
_ => 4,
3638
};
3739

38-
// CHECK: br label %{{.+}}
3940
_ = match 1 {
4041
2 => 3,
4142
_ => 4,
4243
};
4344

44-
// CHECK: br label %[[MINUS1:.+]]
4545
_ = match -1 {
46-
// CHECK: [[MINUS1]]:
47-
// CHECK: store i32 1
4846
-1 => 1,
4947
_ => 0,
5048
}
+25
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
//@ compile-flags: -Cno-prepopulate-passes -Copt-level=0
2+
// Check that there's an alloca for the reference and the vector, but nothing else.
3+
4+
#![crate_type = "lib"]
5+
6+
#[inline(never)]
7+
fn test<const SIZE: usize>() {
8+
// CHECK-LABEL: no_alloca_inside_if_false::test
9+
// CHECK: start:
10+
// CHECK-NEXT: alloca [{{12|24}} x i8]
11+
// CHECK-NOT: alloca
12+
if const { SIZE < 4096 } {
13+
let arr = [0u8; SIZE];
14+
std::hint::black_box(arr);
15+
} else {
16+
let vec = vec![0u8; SIZE];
17+
std::hint::black_box(vec);
18+
}
19+
}
20+
21+
// CHECK-LABEL: @main
22+
#[no_mangle]
23+
pub fn main() {
24+
test::<8192>();
25+
}

0 commit comments

Comments
 (0)