Skip to content

Commit 365edc7

Browse files
committed
Implement MaybeUninitializedLocals analysis for copy_prop mir-opt to remove fewer storage statements
1 parent 9df91d2 commit 365edc7

29 files changed

+642
-36
lines changed

compiler/rustc_mir_dataflow/src/impls/initialized.rs

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -558,6 +558,80 @@ impl<'tcx> Analysis<'tcx> for MaybeUninitializedPlaces<'_, 'tcx> {
558558
}
559559
}
560560

561+
/// A dataflow analysis that tracks locals that are maybe uninitialized.
562+
///
563+
/// This is a simpler analysis than `MaybeUninitializedPlaces`, because it does not track
564+
/// individual fields.
565+
pub struct MaybeUninitializedLocals;
566+
567+
impl MaybeUninitializedLocals {
568+
pub fn new() -> Self {
569+
Self {}
570+
}
571+
}
572+
573+
impl<'tcx> Analysis<'tcx> for MaybeUninitializedLocals {
574+
type Domain = DenseBitSet<mir::Local>;
575+
576+
const NAME: &'static str = "maybe_uninit_locals";
577+
578+
fn bottom_value(&self, body: &Body<'tcx>) -> Self::Domain {
579+
// bottom = all locals are initialized.
580+
DenseBitSet::new_empty(body.local_decls.len())
581+
}
582+
583+
fn initialize_start_block(&self, body: &Body<'tcx>, state: &mut Self::Domain) {
584+
// All locals start as uninitialized...
585+
state.insert_all();
586+
// ...except for arguments, which are definitely initialized.
587+
for arg in body.args_iter() {
588+
state.remove(arg);
589+
}
590+
}
591+
592+
fn apply_primary_statement_effect(
593+
&mut self,
594+
state: &mut Self::Domain,
595+
statement: &mir::Statement<'tcx>,
596+
_location: Location,
597+
) {
598+
match statement.kind {
599+
// An assignment makes a local initialized.
600+
mir::StatementKind::Assign(box (place, _)) => {
601+
if let Some(local) = place.as_local() {
602+
state.remove(local);
603+
}
604+
}
605+
// Deinit makes the local uninitialized.
606+
mir::StatementKind::Deinit(box place) => {
607+
// A deinit makes a local uninitialized.
608+
if let Some(local) = place.as_local() {
609+
state.insert(local);
610+
}
611+
}
612+
// Storage{Live,Dead} makes a local uninitialized.
613+
mir::StatementKind::StorageLive(local) | mir::StatementKind::StorageDead(local) => {
614+
state.insert(local);
615+
}
616+
_ => {}
617+
}
618+
}
619+
620+
fn apply_call_return_effect(
621+
&mut self,
622+
state: &mut Self::Domain,
623+
_block: mir::BasicBlock,
624+
return_places: CallReturnPlaces<'_, 'tcx>,
625+
) {
626+
// The return place of a call is initialized.
627+
return_places.for_each(|place| {
628+
if let Some(local) = place.as_local() {
629+
state.remove(local);
630+
}
631+
});
632+
}
633+
}
634+
561635
/// There can be many more `InitIndex` than there are locals in a MIR body.
562636
/// We use a mixed bitset to avoid paying too high a memory footprint.
563637
pub type EverInitializedPlacesDomain = MixedBitSet<InitIndex>;

compiler/rustc_mir_dataflow/src/impls/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ mod storage_liveness;
66
pub use self::borrowed_locals::{MaybeBorrowedLocals, borrowed_locals};
77
pub use self::initialized::{
88
EverInitializedPlaces, EverInitializedPlacesDomain, MaybeInitializedPlaces,
9-
MaybeUninitializedPlaces, MaybeUninitializedPlacesDomain,
9+
MaybeUninitializedLocals, MaybeUninitializedPlaces, MaybeUninitializedPlacesDomain,
1010
};
1111
pub use self::liveness::{
1212
MaybeLiveLocals, MaybeTransitiveLiveLocals, TransferFunction as LivenessTransferFunction,

compiler/rustc_mir_transform/src/copy_prop.rs

Lines changed: 78 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ use rustc_index::bit_set::DenseBitSet;
33
use rustc_middle::mir::visit::*;
44
use rustc_middle::mir::*;
55
use rustc_middle::ty::TyCtxt;
6+
use rustc_mir_dataflow::impls::MaybeUninitializedLocals;
7+
use rustc_mir_dataflow::{Analysis, ResultsCursor};
68
use tracing::{debug, instrument};
79

810
use crate::ssa::SsaLocals;
@@ -16,7 +18,7 @@ use crate::ssa::SsaLocals;
1618
/// _d = move? _c
1719
/// where each of the locals is only assigned once.
1820
///
19-
/// We want to replace all those locals by `_a`, either copied or moved.
21+
/// We want to replace all those locals by `_a` (the "head"), either copied or moved.
2022
pub(super) struct CopyProp;
2123

2224
impl<'tcx> crate::MirPass<'tcx> for CopyProp {
@@ -34,15 +36,41 @@ impl<'tcx> crate::MirPass<'tcx> for CopyProp {
3436
let fully_moved = fully_moved_locals(&ssa, body);
3537
debug!(?fully_moved);
3638

37-
let mut storage_to_remove = DenseBitSet::new_empty(fully_moved.domain_size());
39+
let mut head_storage_to_check = DenseBitSet::new_empty(fully_moved.domain_size());
40+
3841
for (local, &head) in ssa.copy_classes().iter_enumerated() {
3942
if local != head {
40-
storage_to_remove.insert(head);
43+
// We need to determine if we can keep the head's storage statements (which enables better optimizations).
44+
// For every local's usage location, if the head is maybe-uninitialized, we'll need to remove it's storage statements.
45+
head_storage_to_check.insert(head);
4146
}
4247
}
4348

4449
let any_replacement = ssa.copy_classes().iter_enumerated().any(|(l, &h)| l != h);
4550

51+
// Debug builds have no use for the storage statements, so avoid extra work.
52+
let storage_to_remove = if any_replacement && tcx.sess.emit_lifetime_markers() {
53+
let maybe_uninit = MaybeUninitializedLocals::new()
54+
.iterate_to_fixpoint(tcx, body, Some("mir_opt::copy_prop"))
55+
.into_results_cursor(body);
56+
57+
let mut storage_checker = StorageChecker {
58+
maybe_uninit,
59+
copy_classes: ssa.copy_classes(),
60+
head_storage_to_check,
61+
storage_to_remove: DenseBitSet::new_empty(fully_moved.domain_size()),
62+
};
63+
64+
storage_checker.visit_body(body);
65+
66+
storage_checker.storage_to_remove
67+
} else {
68+
// Conservatively remove all storage statements for the head locals.
69+
head_storage_to_check
70+
};
71+
72+
debug!(?storage_to_remove);
73+
4674
Replacer {
4775
tcx,
4876
copy_classes: ssa.copy_classes(),
@@ -172,3 +200,50 @@ impl<'tcx> MutVisitor<'tcx> for Replacer<'_, 'tcx> {
172200
}
173201
}
174202
}
203+
204+
// Marks heads of copy classes that are maybe uninitialized at the location of a local
205+
// as needing storage statement removal.
206+
struct StorageChecker<'a, 'tcx> {
207+
maybe_uninit: ResultsCursor<'a, 'tcx, MaybeUninitializedLocals>,
208+
copy_classes: &'a IndexSlice<Local, Local>,
209+
head_storage_to_check: DenseBitSet<Local>,
210+
storage_to_remove: DenseBitSet<Local>,
211+
}
212+
213+
impl<'a, 'tcx> Visitor<'tcx> for StorageChecker<'a, 'tcx> {
214+
fn visit_local(&mut self, local: Local, context: PlaceContext, loc: Location) {
215+
// We don't need to check storage statements and statements for which the local doesn't need to be initialized.
216+
match context {
217+
PlaceContext::MutatingUse(
218+
MutatingUseContext::Store
219+
| MutatingUseContext::Call
220+
| MutatingUseContext::AsmOutput,
221+
)
222+
| PlaceContext::NonUse(_) => {
223+
return;
224+
}
225+
_ => {}
226+
};
227+
228+
let head = self.copy_classes[local];
229+
230+
// The head must be initialized at the location of the local, otherwise we must remove it's storage statements.
231+
if self.head_storage_to_check.contains(head) {
232+
self.maybe_uninit.seek_before_primary_effect(loc);
233+
234+
if self.maybe_uninit.get().contains(head) {
235+
debug!(
236+
?loc,
237+
?context,
238+
?local,
239+
?head,
240+
"found a head at a location in which it is maybe uninit, marking head for storage statement removal"
241+
);
242+
self.storage_to_remove.insert(head);
243+
244+
// Once we found a use of the head that is maybe uninit, we do not need to check it again.
245+
self.head_storage_to_check.remove(head);
246+
}
247+
}
248+
}
249+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
- // MIR for `dead_twice` before CopyProp
2+
+ // MIR for `dead_twice` after CopyProp
3+
4+
fn dead_twice(_1: T) -> T {
5+
let mut _0: T;
6+
let mut _2: T;
7+
let mut _3: T;
8+
let mut _4: T;
9+
10+
bb0: {
11+
- StorageLive(_2);
12+
_2 = opaque::<T>(move _1) -> [return: bb1, unwind unreachable];
13+
}
14+
15+
bb1: {
16+
- _4 = move _2;
17+
- StorageDead(_2);
18+
- StorageLive(_2);
19+
- _0 = opaque::<T>(move _4) -> [return: bb2, unwind unreachable];
20+
+ _0 = opaque::<T>(move _2) -> [return: bb2, unwind unreachable];
21+
}
22+
23+
bb2: {
24+
- StorageDead(_2);
25+
return;
26+
}
27+
}
28+
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
- // MIR for `live_twice` before CopyProp
2+
+ // MIR for `live_twice` after CopyProp
3+
4+
fn live_twice(_1: T) -> T {
5+
let mut _0: T;
6+
let mut _2: T;
7+
let mut _3: T;
8+
let mut _4: T;
9+
10+
bb0: {
11+
- StorageLive(_2);
12+
_2 = opaque::<T>(move _1) -> [return: bb1, unwind unreachable];
13+
}
14+
15+
bb1: {
16+
- _4 = move _2;
17+
- StorageLive(_2);
18+
- _0 = opaque::<T>(copy _4) -> [return: bb2, unwind unreachable];
19+
+ _0 = opaque::<T>(copy _2) -> [return: bb2, unwind unreachable];
20+
}
21+
22+
bb2: {
23+
- StorageDead(_2);
24+
return;
25+
}
26+
}
27+
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
// skip-filecheck
2+
//@ test-mir-pass: CopyProp
3+
//@ compile-flags: -Zlint-mir=false
4+
5+
#![feature(custom_mir, core_intrinsics)]
6+
7+
// Check that we remove the storage statements if the head
8+
// becomes uninitialized before it is used again.
9+
10+
use std::intrinsics::mir::*;
11+
12+
// EMIT_MIR copy_prop_storage_twice.dead_twice.CopyProp.diff
13+
// EMIT_MIR copy_prop_storage_twice.live_twice.CopyProp.diff
14+
15+
#[custom_mir(dialect = "runtime")]
16+
pub fn dead_twice<T: Copy>(_1: T) -> T {
17+
mir! {
18+
let _2: T;
19+
let _3: T;
20+
{
21+
StorageLive(_2);
22+
Call(_2 = opaque(Move(_1)), ReturnTo(bb1), UnwindUnreachable())
23+
}
24+
bb1 = {
25+
let _3 = Move(_2);
26+
StorageDead(_2);
27+
StorageLive(_2);
28+
Call(RET = opaque(Move(_3)), ReturnTo(bb2), UnwindUnreachable())
29+
}
30+
bb2 = {
31+
StorageDead(_2);
32+
Return()
33+
}
34+
}
35+
}
36+
37+
#[custom_mir(dialect = "runtime")]
38+
pub fn live_twice<T: Copy>(_1: T) -> T {
39+
mir! {
40+
let _2: T;
41+
let _3: T;
42+
{
43+
StorageLive(_2);
44+
Call(_2 = opaque(Move(_1)), ReturnTo(bb1), UnwindUnreachable())
45+
}
46+
bb1 = {
47+
let _3 = Move(_2);
48+
StorageLive(_2);
49+
Call(RET = opaque(_3), ReturnTo(bb2), UnwindUnreachable())
50+
}
51+
bb2 = {
52+
StorageDead(_2);
53+
Return()
54+
}
55+
}
56+
}
57+
58+
#[inline(never)]
59+
fn opaque<T>(a: T) -> T {
60+
a
61+
}

tests/mir-opt/copy-prop/cycle.main.CopyProp.panic-abort.diff

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@
2626
}
2727

2828
bb1: {
29-
- StorageLive(_2);
29+
StorageLive(_2);
3030
_2 = copy _1;
3131
- StorageLive(_3);
3232
- _3 = copy _2;
@@ -46,7 +46,7 @@
4646
StorageDead(_5);
4747
_0 = const ();
4848
- StorageDead(_3);
49-
- StorageDead(_2);
49+
StorageDead(_2);
5050
StorageDead(_1);
5151
return;
5252
}

tests/mir-opt/copy-prop/cycle.main.CopyProp.panic-unwind.diff

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@
2626
}
2727

2828
bb1: {
29-
- StorageLive(_2);
29+
StorageLive(_2);
3030
_2 = copy _1;
3131
- StorageLive(_3);
3232
- _3 = copy _2;
@@ -46,7 +46,7 @@
4646
StorageDead(_5);
4747
_0 = const ();
4848
- StorageDead(_3);
49-
- StorageDead(_2);
49+
StorageDead(_2);
5050
StorageDead(_1);
5151
return;
5252
}

tests/mir-opt/copy-prop/dead_stores_79191.f.CopyProp.after.panic-abort.mir

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ fn f(_1: usize) -> usize {
1111
}
1212

1313
bb0: {
14+
StorageLive(_2);
1415
_2 = copy _1;
1516
_1 = const 5_usize;
1617
_1 = copy _2;
@@ -21,6 +22,7 @@ fn f(_1: usize) -> usize {
2122

2223
bb1: {
2324
StorageDead(_4);
25+
StorageDead(_2);
2426
return;
2527
}
2628
}

tests/mir-opt/copy-prop/dead_stores_79191.f.CopyProp.after.panic-unwind.mir

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ fn f(_1: usize) -> usize {
1111
}
1212

1313
bb0: {
14+
StorageLive(_2);
1415
_2 = copy _1;
1516
_1 = const 5_usize;
1617
_1 = copy _2;
@@ -21,6 +22,7 @@ fn f(_1: usize) -> usize {
2122

2223
bb1: {
2324
StorageDead(_4);
25+
StorageDead(_2);
2426
return;
2527
}
2628
}

0 commit comments

Comments
 (0)