Skip to content

Commit 96d77f0

Browse files
committed
Auto merge of #70447 - ecstatic-morse:storage-live-always, r=tmandry
Add utility to find locals that don't use `StorageLive` annotations and use it for `MaybeStorageLive` Addresses #70004 (comment) (cc @RalfJung). The only dataflow analysis that is incorrect in this case is `MaybeStorageLive`. `transform/generator.rs` implemented custom handling for this class of locals, but other consumers of this analysis (there's one in [clippy](https://github.com/rust-lang/rust-clippy/blob/513b46793e98ce5b412d388a91f6371d6a9b290b/clippy_lints/src/redundant_clone.rs#L402)) would be incorrect. r? @tmandry
2 parents 0c835b0 + 209087b commit 96d77f0

File tree

5 files changed

+116
-48
lines changed

5 files changed

+116
-48
lines changed

src/librustc_mir/dataflow/impls/storage_liveness.rs

+17-5
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,21 @@ pub use super::*;
22

33
use crate::dataflow::BottomValue;
44
use crate::dataflow::{self, GenKill, Results, ResultsRefCursor};
5+
use crate::util::storage::AlwaysLiveLocals;
56
use rustc_middle::mir::visit::{NonMutatingUseContext, PlaceContext, Visitor};
67
use rustc_middle::mir::*;
78
use std::cell::RefCell;
89

9-
#[derive(Copy, Clone)]
10-
pub struct MaybeStorageLive;
10+
#[derive(Clone)]
11+
pub struct MaybeStorageLive {
12+
always_live_locals: AlwaysLiveLocals,
13+
}
14+
15+
impl MaybeStorageLive {
16+
pub fn new(always_live_locals: AlwaysLiveLocals) -> Self {
17+
MaybeStorageLive { always_live_locals }
18+
}
19+
}
1120

1221
impl dataflow::AnalysisDomain<'tcx> for MaybeStorageLive {
1322
type Idx = Local;
@@ -19,9 +28,12 @@ impl dataflow::AnalysisDomain<'tcx> for MaybeStorageLive {
1928
}
2029

2130
fn initialize_start_block(&self, body: &mir::Body<'tcx>, on_entry: &mut BitSet<Self::Idx>) {
22-
// The resume argument is live on function entry (we don't care about
23-
// the `self` argument)
24-
for arg in body.args_iter().skip(1) {
31+
assert_eq!(body.local_decls.len(), self.always_live_locals.domain_size());
32+
for local in self.always_live_locals.iter() {
33+
on_entry.insert(local);
34+
}
35+
36+
for arg in body.args_iter() {
2537
on_entry.insert(arg);
2638
}
2739
}

src/librustc_mir/interpret/eval_context.rs

+10-9
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ use super::{
2424
Immediate, MPlaceTy, Machine, MemPlace, MemPlaceMeta, Memory, OpTy, Operand, Place, PlaceTy,
2525
ScalarMaybeUndef, StackPopJump,
2626
};
27+
use crate::util::storage::AlwaysLiveLocals;
2728

2829
pub struct InterpCx<'mir, 'tcx, M: Machine<'mir, 'tcx>> {
2930
/// Stores the `Machine` instance.
@@ -610,17 +611,17 @@ impl<'mir, 'tcx, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
610611
// Now mark those locals as dead that we do not want to initialize
611612
match self.tcx.def_kind(instance.def_id()) {
612613
// statics and constants don't have `Storage*` statements, no need to look for them
614+
//
615+
// FIXME: The above is likely untrue. See
616+
// <https://github.com/rust-lang/rust/pull/70004#issuecomment-602022110>. Is it
617+
// okay to ignore `StorageDead`/`StorageLive` annotations during CTFE?
613618
Some(DefKind::Static) | Some(DefKind::Const) | Some(DefKind::AssocConst) => {}
614619
_ => {
615-
for block in body.basic_blocks() {
616-
for stmt in block.statements.iter() {
617-
use rustc_middle::mir::StatementKind::{StorageDead, StorageLive};
618-
match stmt.kind {
619-
StorageLive(local) | StorageDead(local) => {
620-
locals[local].value = LocalValue::Dead;
621-
}
622-
_ => {}
623-
}
620+
// Mark locals that use `Storage*` annotations as dead on function entry.
621+
let always_live = AlwaysLiveLocals::new(self.body());
622+
for local in locals.indices() {
623+
if !always_live.contains(local) {
624+
locals[local].value = LocalValue::Dead;
624625
}
625626
}
626627
}

src/librustc_mir/transform/generator.rs

+41-34
Original file line numberDiff line numberDiff line change
@@ -56,12 +56,13 @@ use crate::transform::simplify;
5656
use crate::transform::{MirPass, MirSource};
5757
use crate::util::dump_mir;
5858
use crate::util::liveness;
59+
use crate::util::storage;
5960
use rustc_data_structures::fx::FxHashMap;
6061
use rustc_hir as hir;
6162
use rustc_hir::def_id::DefId;
6263
use rustc_index::bit_set::{BitMatrix, BitSet};
6364
use rustc_index::vec::{Idx, IndexVec};
64-
use rustc_middle::mir::visit::{MutVisitor, PlaceContext, Visitor};
65+
use rustc_middle::mir::visit::{MutVisitor, PlaceContext};
6566
use rustc_middle::mir::*;
6667
use rustc_middle::ty::subst::SubstsRef;
6768
use rustc_middle::ty::GeneratorSubsts;
@@ -222,6 +223,9 @@ struct TransformVisitor<'tcx> {
222223
// A list of suspension points, generated during the transform
223224
suspension_points: Vec<SuspensionPoint<'tcx>>,
224225

226+
// The set of locals that have no `StorageLive`/`StorageDead` annotations.
227+
always_live_locals: storage::AlwaysLiveLocals,
228+
225229
// The original RETURN_PLACE local
226230
new_ret_local: Local,
227231
}
@@ -416,19 +420,6 @@ fn replace_local<'tcx>(
416420
new_local
417421
}
418422

419-
struct StorageIgnored(liveness::LiveVarSet);
420-
421-
impl<'tcx> Visitor<'tcx> for StorageIgnored {
422-
fn visit_statement(&mut self, statement: &Statement<'tcx>, _location: Location) {
423-
match statement.kind {
424-
StatementKind::StorageLive(l) | StatementKind::StorageDead(l) => {
425-
self.0.remove(l);
426-
}
427-
_ => (),
428-
}
429-
}
430-
}
431-
432423
struct LivenessInfo {
433424
/// Which locals are live across any suspension point.
434425
///
@@ -454,23 +445,19 @@ fn locals_live_across_suspend_points(
454445
tcx: TyCtxt<'tcx>,
455446
body: ReadOnlyBodyAndCache<'_, 'tcx>,
456447
source: MirSource<'tcx>,
448+
always_live_locals: &storage::AlwaysLiveLocals,
457449
movable: bool,
458450
) -> LivenessInfo {
459451
let def_id = source.def_id();
460452
let body_ref: &Body<'_> = &body;
461453

462454
// Calculate when MIR locals have live storage. This gives us an upper bound of their
463455
// lifetimes.
464-
let mut storage_live = MaybeStorageLive
456+
let mut storage_live = MaybeStorageLive::new(always_live_locals.clone())
465457
.into_engine(tcx, body_ref, def_id)
466458
.iterate_to_fixpoint()
467459
.into_results_cursor(body_ref);
468460

469-
// Find the MIR locals which do not use StorageLive/StorageDead statements.
470-
// The storage of these locals are always live.
471-
let mut ignored = StorageIgnored(BitSet::new_filled(body.local_decls.len()));
472-
ignored.visit_body(&body);
473-
474461
// Calculate the MIR locals which have been previously
475462
// borrowed (even if they are still active).
476463
let borrowed_locals_results =
@@ -515,11 +502,14 @@ fn locals_live_across_suspend_points(
515502
}
516503

517504
storage_live.seek_before(loc);
518-
let storage_liveness = storage_live.get();
505+
let mut storage_liveness = storage_live.get().clone();
506+
507+
// Later passes handle the generator's `self` argument separately.
508+
storage_liveness.remove(SELF_ARG);
519509

520510
// Store the storage liveness for later use so we can restore the state
521511
// after a suspension point
522-
storage_liveness_map.insert(block, storage_liveness.clone());
512+
storage_liveness_map.insert(block, storage_liveness);
523513

524514
requires_storage_cursor.seek_before(loc);
525515
let storage_required = requires_storage_cursor.get().clone();
@@ -551,8 +541,12 @@ fn locals_live_across_suspend_points(
551541
.map(|live_here| renumber_bitset(&live_here, &live_locals))
552542
.collect();
553543

554-
let storage_conflicts =
555-
compute_storage_conflicts(body_ref, &live_locals, &ignored, requires_storage_results);
544+
let storage_conflicts = compute_storage_conflicts(
545+
body_ref,
546+
&live_locals,
547+
always_live_locals.clone(),
548+
requires_storage_results,
549+
);
556550

557551
LivenessInfo {
558552
live_locals,
@@ -590,18 +584,18 @@ fn renumber_bitset(
590584
fn compute_storage_conflicts(
591585
body: &'mir Body<'tcx>,
592586
stored_locals: &liveness::LiveVarSet,
593-
ignored: &StorageIgnored,
587+
always_live_locals: storage::AlwaysLiveLocals,
594588
requires_storage: dataflow::Results<'tcx, MaybeRequiresStorage<'mir, 'tcx>>,
595589
) -> BitMatrix<GeneratorSavedLocal, GeneratorSavedLocal> {
596-
assert_eq!(body.local_decls.len(), ignored.0.domain_size());
597590
assert_eq!(body.local_decls.len(), stored_locals.domain_size());
591+
598592
debug!("compute_storage_conflicts({:?})", body.span);
599-
debug!("ignored = {:?}", ignored.0);
593+
debug!("always_live = {:?}", always_live_locals);
600594

601-
// Storage ignored locals are not eligible for overlap, since their storage
602-
// is always live.
603-
let mut ineligible_locals = ignored.0.clone();
604-
ineligible_locals.intersect(&stored_locals);
595+
// Locals that are always live or ones that need to be stored across
596+
// suspension points are not eligible for overlap.
597+
let mut ineligible_locals = always_live_locals.into_inner();
598+
ineligible_locals.intersect(stored_locals);
605599

606600
// Compute the storage conflicts for all eligible locals.
607601
let mut visitor = StorageConflictVisitor {
@@ -697,6 +691,7 @@ fn compute_layout<'tcx>(
697691
source: MirSource<'tcx>,
698692
upvars: &Vec<Ty<'tcx>>,
699693
interior: Ty<'tcx>,
694+
always_live_locals: &storage::AlwaysLiveLocals,
700695
movable: bool,
701696
body: &mut BodyAndCache<'tcx>,
702697
) -> (
@@ -710,7 +705,13 @@ fn compute_layout<'tcx>(
710705
live_locals_at_suspension_points,
711706
storage_conflicts,
712707
storage_liveness,
713-
} = locals_live_across_suspend_points(tcx, read_only!(body), source, movable);
708+
} = locals_live_across_suspend_points(
709+
tcx,
710+
read_only!(body),
711+
source,
712+
always_live_locals,
713+
movable,
714+
);
714715

715716
// Erase regions from the types passed in from typeck so we can compare them with
716717
// MIR types
@@ -1180,7 +1181,10 @@ fn create_cases<'tcx>(
11801181
}
11811182

11821183
let l = Local::new(i);
1183-
if point.storage_liveness.contains(l) && !transform.remap.contains_key(&l) {
1184+
let needs_storage_live = point.storage_liveness.contains(l)
1185+
&& !transform.remap.contains_key(&l)
1186+
&& !transform.always_live_locals.contains(l);
1187+
if needs_storage_live {
11841188
statements
11851189
.push(Statement { source_info, kind: StatementKind::StorageLive(l) });
11861190
}
@@ -1276,11 +1280,13 @@ impl<'tcx> MirPass<'tcx> for StateTransform {
12761280
},
12771281
);
12781282

1283+
let always_live_locals = storage::AlwaysLiveLocals::new(&body);
1284+
12791285
// Extract locals which are live across suspension point into `layout`
12801286
// `remap` gives a mapping from local indices onto generator struct indices
12811287
// `storage_liveness` tells us which locals have live storage at suspension points
12821288
let (remap, layout, storage_liveness) =
1283-
compute_layout(tcx, source, &upvars, interior, movable, body);
1289+
compute_layout(tcx, source, &upvars, interior, &always_live_locals, movable, body);
12841290

12851291
let can_return = can_return(tcx, body);
12861292

@@ -1294,6 +1300,7 @@ impl<'tcx> MirPass<'tcx> for StateTransform {
12941300
state_substs,
12951301
remap,
12961302
storage_liveness,
1303+
always_live_locals,
12971304
suspension_points: Vec::new(),
12981305
new_ret_local,
12991306
discr_ty,

src/librustc_mir/util/mod.rs

+1
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ pub mod borrowck_errors;
33
pub mod def_use;
44
pub mod elaborate_drops;
55
pub mod patch;
6+
pub mod storage;
67

78
mod alignment;
89
pub mod collect_writes;

src/librustc_mir/util/storage.rs

+47
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
use rustc_index::bit_set::BitSet;
2+
use rustc_middle::mir::visit::Visitor;
3+
use rustc_middle::mir::{self, Local, Location};
4+
5+
/// The set of locals in a MIR body that do not have `StorageLive`/`StorageDead` annotations.
6+
///
7+
/// These locals have fixed storage for the duration of the body.
8+
//
9+
// FIXME: Currently, we need to traverse the entire MIR to compute this. We should instead store it
10+
// as a field in the `LocalDecl` for each `Local`.
11+
#[derive(Debug, Clone)]
12+
pub struct AlwaysLiveLocals(BitSet<Local>);
13+
14+
impl AlwaysLiveLocals {
15+
pub fn new(body: &mir::Body<'tcx>) -> Self {
16+
let mut ret = AlwaysLiveLocals(BitSet::new_filled(body.local_decls.len()));
17+
18+
let mut vis = StorageAnnotationVisitor(&mut ret);
19+
vis.visit_body(body);
20+
21+
ret
22+
}
23+
24+
pub fn into_inner(self) -> BitSet<Local> {
25+
self.0
26+
}
27+
}
28+
29+
impl std::ops::Deref for AlwaysLiveLocals {
30+
type Target = BitSet<Local>;
31+
32+
fn deref(&self) -> &Self::Target {
33+
&self.0
34+
}
35+
}
36+
37+
/// Removes locals that have `Storage*` annotations from `AlwaysLiveLocals`.
38+
struct StorageAnnotationVisitor<'a>(&'a mut AlwaysLiveLocals);
39+
40+
impl Visitor<'tcx> for StorageAnnotationVisitor<'_> {
41+
fn visit_statement(&mut self, statement: &mir::Statement<'tcx>, _location: Location) {
42+
use mir::StatementKind::{StorageDead, StorageLive};
43+
if let StorageLive(l) | StorageDead(l) = statement.kind {
44+
(self.0).0.remove(l);
45+
}
46+
}
47+
}

0 commit comments

Comments
 (0)