Skip to content

Commit 49c68bd

Browse files
committed
Auto merge of #68528 - ecstatic-morse:maybe-init-variants, r=oli-obk
Mark other variants as uninitialized after switch on discriminant During drop elaboration, which builds the drop ladder that handles destruction during stack unwinding, we attempt to remove MIR `Drop` terminators that will never be reached in practice. This reduces the number of basic blocks that are passed to LLVM, which should improve performance. In #66753, a user pointed out that unreachable `Drop` terminators are common in functions like `Option::unwrap`, which move out of an `enum`. While discussing possible remedies for that issue, @eddyb suggested moving const-checking after drop elaboration. This would allow the former, which looks for `Drop` terminators and replicates a small amount of drop elaboration to determine whether a dropped local has been moved out, leverage the work done by the latter. However, it turns out that drop elaboration is not as precise as it could be when it comes to eliminating useless drop terminators. For example, let's look at the code for `unwrap_or`. ```rust fn unwrap_or<T>(opt: Option<T>, default: T) -> T { match opt { Some(inner) => inner, None => default, } } ``` `opt` never needs to be dropped, since it is either moved out of (if it is `Some`) or has no drop glue (if it is `None`), and `default` only needs to be dropped if `opt` is `Some`. This is not reflected in the MIR we currently pass to codegen. ![pasted_image](https://user-images.githubusercontent.com/29463364/73384403-109a0d80-4280-11ea-8500-0637b368f2dc.png) @eddyb also suggested the solution to this problem. When we switch on an enum discriminant, we should be marking all fields in other variants as definitely uninitialized. I implemented this on top of alongside a small optimization (split out into #68943) that suppresses drop terminators for enum variants with no fields (e.g. `Option::None`). This is the resulting MIR for `unwrap_or`. ![after](https://user-images.githubusercontent.com/29463364/73384823-e432c100-4280-11ea-84bd-d0bcc3b777b4.png) In concert with #68943, this change speeds up many [optimized and debug builds](https://perf.rust-lang.org/compare.html?start=d55f3e9f1da631c636b54a7c22c1caccbe4bf0db&end=0077a7aa11ebc2462851676f9f464d5221b17d6a). We need to carefully investigate whether I have introduced any miscompilations before merging this. Code that never drops anything would be very fast indeed until memory is exhausted.
2 parents a8437cf + e2c8047 commit 49c68bd

File tree

8 files changed

+201
-25
lines changed

8 files changed

+201
-25
lines changed

src/librustc/mir/mod.rs

+9
Original file line numberDiff line numberDiff line change
@@ -2046,6 +2046,15 @@ impl<'tcx> Operand<'tcx> {
20462046
Operand::Move(place) => Operand::Copy(place),
20472047
}
20482048
}
2049+
2050+
/// Returns the `Place` that is the target of this `Operand`, or `None` if this `Operand` is a
2051+
/// constant.
2052+
pub fn place(&self) -> Option<&Place<'tcx>> {
2053+
match self {
2054+
Operand::Copy(place) | Operand::Move(place) => Some(place),
2055+
Operand::Constant(_) => None,
2056+
}
2057+
}
20492058
}
20502059

20512060
///////////////////////////////////////////////////////////////////////////

src/librustc_mir/borrow_check/diagnostics/conflict_errors.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -1340,7 +1340,7 @@ impl<'cx, 'tcx> MirBorrowckCtxt<'cx, 'tcx> {
13401340
// there.
13411341
let mut mpis = vec![mpi];
13421342
let move_paths = &self.move_data.move_paths;
1343-
mpis.extend(move_paths[mpi].parents(move_paths));
1343+
mpis.extend(move_paths[mpi].parents(move_paths).map(|(mpi, _)| mpi));
13441344

13451345
for moi in &self.move_data.loc_map[location] {
13461346
debug!("report_use_of_moved_or_uninitialized: moi={:?}", moi);

src/librustc_mir/borrow_check/mod.rs

+3-4
Original file line numberDiff line numberDiff line change
@@ -1599,9 +1599,9 @@ impl<'cx, 'tcx> MirBorrowckCtxt<'cx, 'tcx> {
15991599
) {
16001600
if let Some(mpi) = self.move_path_for_place(place_span.0) {
16011601
let move_paths = &self.move_data.move_paths;
1602-
let mut child = move_paths[mpi].first_child;
1603-
while let Some(child_mpi) = child {
1604-
let child_move_path = &move_paths[child_mpi];
1602+
1603+
let root_path = &move_paths[mpi];
1604+
for (child_mpi, child_move_path) in root_path.children(move_paths) {
16051605
let last_proj = child_move_path.place.projection.last().unwrap();
16061606
if let ProjectionElem::ConstantIndex { offset, from_end, .. } = last_proj {
16071607
debug_assert!(!from_end, "Array constant indexing shouldn't be `from_end`.");
@@ -1623,7 +1623,6 @@ impl<'cx, 'tcx> MirBorrowckCtxt<'cx, 'tcx> {
16231623
}
16241624
}
16251625
}
1626-
child = child_move_path.next_sibling;
16271626
}
16281627
}
16291628
}

src/librustc_mir/borrow_check/nll.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ fn populate_polonius_move_facts(
9292
for (child, move_path) in move_data.move_paths.iter_enumerated() {
9393
all_facts
9494
.child
95-
.extend(move_path.parents(&move_data.move_paths).iter().map(|&parent| (child, parent)));
95+
.extend(move_path.parents(&move_data.move_paths).map(|(parent, _)| (child, parent)));
9696
}
9797

9898
// initialized_at

src/librustc_mir/dataflow/generic/engine.rs

+70-5
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ use std::fs;
55
use std::path::PathBuf;
66

77
use rustc::mir::{self, traversal, BasicBlock, Location};
8-
use rustc::ty::TyCtxt;
8+
use rustc::ty::{self, TyCtxt};
99
use rustc_data_structures::work_queue::WorkQueue;
1010
use rustc_hir::def_id::DefId;
1111
use rustc_index::bit_set::BitSet;
@@ -238,10 +238,15 @@ where
238238
}
239239
}
240240

241-
SwitchInt { ref targets, .. } => {
242-
for target in targets {
243-
self.propagate_bits_into_entry_set_for(in_out, *target, dirty_list);
244-
}
241+
SwitchInt { ref targets, ref values, ref discr, .. } => {
242+
self.propagate_bits_into_switch_int_successors(
243+
in_out,
244+
(bb, bb_data),
245+
dirty_list,
246+
discr,
247+
&*values,
248+
&*targets,
249+
);
245250
}
246251

247252
Call { cleanup, ref destination, ref func, ref args, .. } => {
@@ -287,6 +292,66 @@ where
287292
dirty_queue.insert(bb);
288293
}
289294
}
295+
296+
fn propagate_bits_into_switch_int_successors(
297+
&mut self,
298+
in_out: &mut BitSet<A::Idx>,
299+
(bb, bb_data): (BasicBlock, &mir::BasicBlockData<'tcx>),
300+
dirty_list: &mut WorkQueue<BasicBlock>,
301+
switch_on: &mir::Operand<'tcx>,
302+
values: &[u128],
303+
targets: &[BasicBlock],
304+
) {
305+
match bb_data.statements.last().map(|stmt| &stmt.kind) {
306+
// Look at the last statement to see if it is an assignment of an enum discriminant to
307+
// the local that determines the target of a `SwitchInt` like so:
308+
// _42 = discriminant(..)
309+
// SwitchInt(_42, ..)
310+
Some(mir::StatementKind::Assign(box (lhs, mir::Rvalue::Discriminant(enum_))))
311+
if Some(lhs) == switch_on.place() =>
312+
{
313+
let adt = match enum_.ty(self.body, self.tcx).ty.kind {
314+
ty::Adt(def, _) => def,
315+
_ => bug!("Switch on discriminant of non-ADT"),
316+
};
317+
318+
// MIR building adds discriminants to the `values` array in the same order as they
319+
// are yielded by `AdtDef::discriminants`. We rely on this to match each
320+
// discriminant in `values` to its corresponding variant in linear time.
321+
let mut tmp = BitSet::new_empty(in_out.domain_size());
322+
let mut discriminants = adt.discriminants(self.tcx);
323+
for (value, target) in values.iter().zip(targets.iter().copied()) {
324+
let (variant_idx, _) =
325+
discriminants.find(|&(_, discr)| discr.val == *value).expect(
326+
"Order of `AdtDef::discriminants` differed \
327+
from that of `SwitchInt::values`",
328+
);
329+
330+
tmp.overwrite(in_out);
331+
self.analysis.apply_discriminant_switch_effect(
332+
&mut tmp,
333+
bb,
334+
enum_,
335+
adt,
336+
variant_idx,
337+
);
338+
self.propagate_bits_into_entry_set_for(&tmp, target, dirty_list);
339+
}
340+
341+
std::mem::drop(tmp);
342+
343+
// Propagate dataflow state along the "otherwise" edge.
344+
let otherwise = targets.last().copied().unwrap();
345+
self.propagate_bits_into_entry_set_for(&in_out, otherwise, dirty_list);
346+
}
347+
348+
_ => {
349+
for target in targets.iter().copied() {
350+
self.propagate_bits_into_entry_set_for(&in_out, target, dirty_list);
351+
}
352+
}
353+
}
354+
}
290355
}
291356

292357
// Graphviz

src/librustc_mir/dataflow/generic/mod.rs

+40-2
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,8 @@
3535
use std::io;
3636

3737
use rustc::mir::{self, BasicBlock, Location};
38-
use rustc::ty::TyCtxt;
38+
use rustc::ty::layout::VariantIdx;
39+
use rustc::ty::{self, TyCtxt};
3940
use rustc_hir::def_id::DefId;
4041
use rustc_index::bit_set::{BitSet, HybridBitSet};
4142
use rustc_index::vec::{Idx, IndexVec};
@@ -172,7 +173,22 @@ pub trait Analysis<'tcx>: AnalysisDomain<'tcx> {
172173
return_place: &mir::Place<'tcx>,
173174
);
174175

175-
/// Calls the appropriate `Engine` constructor to find the fixpoint for this dataflow problem.
176+
/// Updates the current dataflow state with the effect of taking a particular branch in a
177+
/// `SwitchInt` terminator.
178+
///
179+
/// Much like `apply_call_return_effect`, this effect is only propagated along a single
180+
/// outgoing edge from this basic block.
181+
fn apply_discriminant_switch_effect(
182+
&self,
183+
_state: &mut BitSet<Self::Idx>,
184+
_block: BasicBlock,
185+
_enum_place: &mir::Place<'tcx>,
186+
_adt: &ty::AdtDef,
187+
_variant: VariantIdx,
188+
) {
189+
}
190+
191+
/// Creates an `Engine` to find the fixpoint for this dataflow problem.
176192
///
177193
/// You shouldn't need to override this outside this module, since the combination of the
178194
/// default impl and the one for all `A: GenKillAnalysis` will do the right thing.
@@ -249,6 +265,17 @@ pub trait GenKillAnalysis<'tcx>: Analysis<'tcx> {
249265
args: &[mir::Operand<'tcx>],
250266
return_place: &mir::Place<'tcx>,
251267
);
268+
269+
/// See `Analysis::apply_discriminant_switch_effect`.
270+
fn discriminant_switch_effect(
271+
&self,
272+
_state: &mut impl GenKill<Self::Idx>,
273+
_block: BasicBlock,
274+
_enum_place: &mir::Place<'tcx>,
275+
_adt: &ty::AdtDef,
276+
_variant: VariantIdx,
277+
) {
278+
}
252279
}
253280

254281
impl<A> Analysis<'tcx> for A
@@ -302,6 +329,17 @@ where
302329
self.call_return_effect(state, block, func, args, return_place);
303330
}
304331

332+
fn apply_discriminant_switch_effect(
333+
&self,
334+
state: &mut BitSet<Self::Idx>,
335+
block: BasicBlock,
336+
enum_place: &mir::Place<'tcx>,
337+
adt: &ty::AdtDef,
338+
variant: VariantIdx,
339+
) {
340+
self.discriminant_switch_effect(state, block, enum_place, adt, variant);
341+
}
342+
305343
fn into_engine(
306344
self,
307345
tcx: TyCtxt<'tcx>,

src/librustc_mir/dataflow/impls/mod.rs

+35-2
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@
33
//! zero-sized structure.
44
55
use rustc::mir::{self, Body, Location};
6-
use rustc::ty::TyCtxt;
6+
use rustc::ty::layout::VariantIdx;
7+
use rustc::ty::{self, TyCtxt};
78
use rustc_index::bit_set::BitSet;
89
use rustc_index::vec::Idx;
910

@@ -12,12 +13,13 @@ use super::MoveDataParamEnv;
1213
use crate::util::elaborate_drops::DropFlagState;
1314

1415
use super::generic::{AnalysisDomain, GenKill, GenKillAnalysis};
15-
use super::move_paths::{HasMoveData, InitIndex, InitKind, MoveData, MovePathIndex};
16+
use super::move_paths::{HasMoveData, InitIndex, InitKind, LookupResult, MoveData, MovePathIndex};
1617
use super::{BottomValue, GenKillSet};
1718

1819
use super::drop_flag_effects_for_function_entry;
1920
use super::drop_flag_effects_for_location;
2021
use super::on_lookup_result_bits;
22+
use crate::dataflow::drop_flag_effects;
2123

2224
mod borrowed_locals;
2325
mod storage_liveness;
@@ -336,6 +338,37 @@ impl<'tcx> GenKillAnalysis<'tcx> for MaybeInitializedPlaces<'_, 'tcx> {
336338
},
337339
);
338340
}
341+
342+
fn discriminant_switch_effect(
343+
&self,
344+
trans: &mut impl GenKill<Self::Idx>,
345+
_block: mir::BasicBlock,
346+
enum_place: &mir::Place<'tcx>,
347+
_adt: &ty::AdtDef,
348+
variant: VariantIdx,
349+
) {
350+
let enum_mpi = match self.move_data().rev_lookup.find(enum_place.as_ref()) {
351+
LookupResult::Exact(mpi) => mpi,
352+
LookupResult::Parent(_) => return,
353+
};
354+
355+
// Kill all move paths that correspond to variants other than this one
356+
let move_paths = &self.move_data().move_paths;
357+
let enum_path = &move_paths[enum_mpi];
358+
for (mpi, variant_path) in enum_path.children(move_paths) {
359+
trans.kill(mpi);
360+
match variant_path.place.projection.last().unwrap() {
361+
mir::ProjectionElem::Downcast(_, idx) if *idx == variant => continue,
362+
_ => drop_flag_effects::on_all_children_bits(
363+
self.tcx,
364+
self.body,
365+
self.move_data(),
366+
mpi,
367+
|mpi| trans.kill(mpi),
368+
),
369+
}
370+
}
371+
}
339372
}
340373

341374
impl<'tcx> AnalysisDomain<'tcx> for MaybeUninitializedPlaces<'_, 'tcx> {

src/librustc_mir/dataflow/move_paths/mod.rs

+42-10
Original file line numberDiff line numberDiff line change
@@ -58,19 +58,32 @@ pub struct MovePath<'tcx> {
5858
}
5959

6060
impl<'tcx> MovePath<'tcx> {
61-
pub fn parents(
61+
/// Returns an iterator over the parents of `self`.
62+
pub fn parents<'a>(
6263
&self,
63-
move_paths: &IndexVec<MovePathIndex, MovePath<'_>>,
64-
) -> Vec<MovePathIndex> {
65-
let mut parents = Vec::new();
66-
67-
let mut curr_parent = self.parent;
68-
while let Some(parent_mpi) = curr_parent {
69-
parents.push(parent_mpi);
70-
curr_parent = move_paths[parent_mpi].parent;
64+
move_paths: &'a IndexVec<MovePathIndex, MovePath<'tcx>>,
65+
) -> impl 'a + Iterator<Item = (MovePathIndex, &'a MovePath<'tcx>)> {
66+
let first = self.parent.map(|mpi| (mpi, &move_paths[mpi]));
67+
MovePathLinearIter {
68+
next: first,
69+
fetch_next: move |_, parent: &MovePath<'_>| {
70+
parent.parent.map(|mpi| (mpi, &move_paths[mpi]))
71+
},
7172
}
73+
}
7274

73-
parents
75+
/// Returns an iterator over the immediate children of `self`.
76+
pub fn children<'a>(
77+
&self,
78+
move_paths: &'a IndexVec<MovePathIndex, MovePath<'tcx>>,
79+
) -> impl 'a + Iterator<Item = (MovePathIndex, &'a MovePath<'tcx>)> {
80+
let first = self.first_child.map(|mpi| (mpi, &move_paths[mpi]));
81+
MovePathLinearIter {
82+
next: first,
83+
fetch_next: move |_, child: &MovePath<'_>| {
84+
child.next_sibling.map(|mpi| (mpi, &move_paths[mpi]))
85+
},
86+
}
7487
}
7588

7689
/// Finds the closest descendant of `self` for which `f` returns `true` using a breadth-first
@@ -131,6 +144,25 @@ impl<'tcx> fmt::Display for MovePath<'tcx> {
131144
}
132145
}
133146

147+
#[allow(unused)]
148+
struct MovePathLinearIter<'a, 'tcx, F> {
149+
next: Option<(MovePathIndex, &'a MovePath<'tcx>)>,
150+
fetch_next: F,
151+
}
152+
153+
impl<'a, 'tcx, F> Iterator for MovePathLinearIter<'a, 'tcx, F>
154+
where
155+
F: FnMut(MovePathIndex, &'a MovePath<'tcx>) -> Option<(MovePathIndex, &'a MovePath<'tcx>)>,
156+
{
157+
type Item = (MovePathIndex, &'a MovePath<'tcx>);
158+
159+
fn next(&mut self) -> Option<Self::Item> {
160+
let ret = self.next.take()?;
161+
self.next = (self.fetch_next)(ret.0, ret.1);
162+
Some(ret)
163+
}
164+
}
165+
134166
#[derive(Debug)]
135167
pub struct MoveData<'tcx> {
136168
pub move_paths: IndexVec<MovePathIndex, MovePath<'tcx>>,

0 commit comments

Comments
 (0)