Skip to content

Commit fc964c5

Browse files
Clean up generator live locals analysis
Instead of using a bespoke dataflow analysis, `MaybeRequiresStorage`, for computing locals that need to be stored across yield points and that have conflicting storage, use a combination of simple, generally applicable dataflow analyses. In this case, the formula for locals that are live at a yield point is: live_across_yield := (live & init) | (!movable & borrowed) and the formula for locals that require storage (and thus may conflict with others) at a given point is: requires_storage := init | borrowed `init` is `MaybeInitializedLocals`, a direct equivalent of `MaybeInitializedPlaces` that works only on whole `Local`s. `borrowed` and `live` are the pre-existing `MaybeBorrowedLocals` and `MaybeLiveLocals` analyses respectively.
1 parent daea09c commit fc964c5

File tree

1 file changed

+113
-137
lines changed

1 file changed

+113
-137
lines changed

src/librustc_mir/transform/generator.rs

Lines changed: 113 additions & 137 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@
5050
//! Otherwise it drops all the values in scope at the last suspension point.
5151
5252
use crate::dataflow::impls::{
53-
MaybeBorrowedLocals, MaybeLiveLocals, MaybeRequiresStorage, MaybeStorageLive,
53+
MaybeBorrowedLocals, MaybeInitializedLocals, MaybeLiveLocals, MaybeStorageLive,
5454
};
5555
use crate::dataflow::{self, Analysis};
5656
use crate::transform::no_landing_pads::no_landing_pads;
@@ -444,86 +444,74 @@ fn locals_live_across_suspend_points(
444444
movable: bool,
445445
) -> LivenessInfo {
446446
let def_id = source.def_id();
447-
let body_ref: &Body<'_> = &body;
448447

449448
// Calculate when MIR locals have live storage. This gives us an upper bound of their
450449
// lifetimes.
451450
let mut storage_live = MaybeStorageLive::new(always_live_locals.clone())
452-
.into_engine(tcx, body_ref, def_id)
451+
.into_engine(tcx, body, def_id)
453452
.iterate_to_fixpoint()
454-
.into_results_cursor(body_ref);
455-
456-
// Calculate the MIR locals which have been previously
457-
// borrowed (even if they are still active).
458-
let borrowed_locals_results =
459-
MaybeBorrowedLocals::all_borrows().into_engine(tcx, body_ref, def_id).iterate_to_fixpoint();
460-
461-
let mut borrowed_locals_cursor =
462-
dataflow::ResultsCursor::new(body_ref, &borrowed_locals_results);
463-
464-
// Calculate the MIR locals that we actually need to keep storage around
465-
// for.
466-
let requires_storage_results = MaybeRequiresStorage::new(body, &borrowed_locals_results)
467-
.into_engine(tcx, body_ref, def_id)
468-
.iterate_to_fixpoint();
469-
let mut requires_storage_cursor =
470-
dataflow::ResultsCursor::new(body_ref, &requires_storage_results);
471-
472-
// Calculate the liveness of MIR locals ignoring borrows.
473-
let mut liveness = MaybeLiveLocals
474-
.into_engine(tcx, body_ref, def_id)
453+
.into_results_cursor(body);
454+
455+
let mut init = MaybeInitializedLocals
456+
.into_engine(tcx, body, def_id)
475457
.iterate_to_fixpoint()
476-
.into_results_cursor(body_ref);
458+
.into_results_cursor(body);
477459

478-
let mut storage_liveness_map = IndexVec::from_elem(None, body.basic_blocks());
479-
let mut live_locals_at_suspension_points = Vec::new();
480-
let mut live_locals_at_any_suspension_point = BitSet::new_empty(body.local_decls.len());
460+
let mut live = MaybeLiveLocals
461+
.into_engine(tcx, body, def_id)
462+
.iterate_to_fixpoint()
463+
.into_results_cursor(body);
481464

482-
for (block, data) in body.basic_blocks().iter_enumerated() {
483-
if let TerminatorKind::Yield { .. } = data.terminator().kind {
484-
let loc = Location { block, statement_index: data.statements.len() };
485-
486-
liveness.seek_to_block_end(block);
487-
let mut live_locals = liveness.get().clone();
488-
489-
if !movable {
490-
// The `liveness` variable contains the liveness of MIR locals ignoring borrows.
491-
// This is correct for movable generators since borrows cannot live across
492-
// suspension points. However for immovable generators we need to account for
493-
// borrows, so we conseratively assume that all borrowed locals are live until
494-
// we find a StorageDead statement referencing the locals.
495-
// To do this we just union our `liveness` result with `borrowed_locals`, which
496-
// contains all the locals which has been borrowed before this suspension point.
497-
// If a borrow is converted to a raw reference, we must also assume that it lives
498-
// forever. Note that the final liveness is still bounded by the storage liveness
499-
// of the local, which happens using the `intersect` operation below.
500-
borrowed_locals_cursor.seek_before_primary_effect(loc);
501-
live_locals.union(borrowed_locals_cursor.get());
502-
}
465+
let mut borrowed = MaybeBorrowedLocals::all_borrows()
466+
.into_engine(tcx, body, def_id)
467+
.iterate_to_fixpoint()
468+
.into_results_cursor(body);
503469

504-
// Store the storage liveness for later use so we can restore the state
505-
// after a suspension point
506-
storage_live.seek_before_primary_effect(loc);
507-
storage_liveness_map[block] = Some(storage_live.get().clone());
470+
// Liveness across yield points is determined by the following boolean equation, where `live`,
471+
// `init` and `borrowed` come from dataflow and `movable` is a property of the generator.
472+
// Movable generators do not allow borrows to live across yield points, so they don't need to
473+
// store a local simply because it is borrowed.
474+
//
475+
// live_across_yield := (live & init) | (!movable & borrowed)
476+
//
477+
let mut locals_live_across_yield_point = |block| {
478+
live.seek_to_block_end(block);
479+
let mut live_locals = live.get().clone();
508480

509-
// Locals live are live at this point only if they are used across
510-
// suspension points (the `liveness` variable)
511-
// and their storage is required (the `storage_required` variable)
512-
requires_storage_cursor.seek_before_primary_effect(loc);
513-
live_locals.intersect(requires_storage_cursor.get());
481+
init.seek_to_block_end(block);
482+
live_locals.intersect(init.get());
514483

515-
// The generator argument is ignored.
516-
live_locals.remove(SELF_ARG);
484+
if !movable {
485+
borrowed.seek_to_block_end(block);
486+
live_locals.union(borrowed.get());
487+
}
517488

518-
debug!("loc = {:?}, live_locals = {:?}", loc, live_locals);
489+
live_locals
490+
};
519491

520-
// Add the locals live at this suspension point to the set of locals which live across
521-
// any suspension points
522-
live_locals_at_any_suspension_point.union(&live_locals);
492+
let mut storage_liveness_map = IndexVec::from_elem(None, body.basic_blocks());
493+
let mut live_locals_at_suspension_points = Vec::new();
494+
let mut live_locals_at_any_suspension_point = BitSet::new_empty(body.local_decls.len());
523495

524-
live_locals_at_suspension_points.push(live_locals);
496+
for (block, data) in body.basic_blocks().iter_enumerated() {
497+
if !matches!(data.terminator().kind, TerminatorKind::Yield { .. }) {
498+
continue;
525499
}
500+
501+
// Store the storage liveness for later use so we can restore the state
502+
// after a suspension point
503+
storage_live.seek_to_block_end(block);
504+
storage_liveness_map[block] = Some(storage_live.get().clone());
505+
506+
let mut live_locals = locals_live_across_yield_point(block);
507+
508+
// Ignore the generator's `self` argument since it is handled seperately.
509+
live_locals.remove(SELF_ARG);
510+
debug!("block = {:?}, live_locals = {:?}", block, live_locals);
511+
live_locals_at_any_suspension_point.union(&live_locals);
512+
live_locals_at_suspension_points.push(live_locals);
526513
}
514+
527515
debug!("live_locals_anywhere = {:?}", live_locals_at_any_suspension_point);
528516

529517
// Renumber our liveness_map bitsets to include only the locals we are
@@ -534,10 +522,11 @@ fn locals_live_across_suspend_points(
534522
.collect();
535523

536524
let storage_conflicts = compute_storage_conflicts(
537-
body_ref,
525+
body,
538526
&live_locals_at_any_suspension_point,
539527
always_live_locals.clone(),
540-
requires_storage_results,
528+
init,
529+
borrowed,
541530
);
542531

543532
LivenessInfo {
@@ -569,6 +558,33 @@ fn renumber_bitset(
569558
out
570559
}
571560

561+
/// Record conflicts between locals at the current dataflow cursor positions.
562+
///
563+
/// You need to seek the cursors before calling this function.
564+
fn record_conflicts_at_curr_loc(
565+
local_conflicts: &mut BitMatrix<Local, Local>,
566+
init: &dataflow::ResultsCursor<'mir, 'tcx, MaybeInitializedLocals>,
567+
borrowed: &dataflow::ResultsCursor<'mir, 'tcx, MaybeBorrowedLocals>,
568+
) {
569+
// A local requires storage if it is initialized or borrowed. For now, a local
570+
// becomes uninitialized if it is moved from, but is still considered "borrowed".
571+
//
572+
// requires_storage := init | borrowed
573+
//
574+
// FIXME: This function is called in a loop, so it might be better to pass in a temporary
575+
// bitset rather than cloning here.
576+
let mut requires_storage = init.get().clone();
577+
requires_storage.union(borrowed.get());
578+
579+
for local in requires_storage.iter() {
580+
local_conflicts.union_row_with(&requires_storage, local);
581+
}
582+
583+
if requires_storage.count() > 1 {
584+
trace!("requires_storage={:?}", requires_storage);
585+
}
586+
}
587+
572588
/// For every saved local, looks for which locals are StorageLive at the same
573589
/// time. Generates a bitset for every local of all the other locals that may be
574590
/// StorageLive simultaneously with that local. This is used in the layout
@@ -577,30 +593,40 @@ fn compute_storage_conflicts(
577593
body: &'mir Body<'tcx>,
578594
stored_locals: &BitSet<Local>,
579595
always_live_locals: storage::AlwaysLiveLocals,
580-
requires_storage: dataflow::Results<'tcx, MaybeRequiresStorage<'mir, 'tcx>>,
596+
mut init: dataflow::ResultsCursor<'mir, 'tcx, MaybeInitializedLocals>,
597+
mut borrowed: dataflow::ResultsCursor<'mir, 'tcx, MaybeBorrowedLocals>,
581598
) -> BitMatrix<GeneratorSavedLocal, GeneratorSavedLocal> {
582-
assert_eq!(body.local_decls.len(), stored_locals.domain_size());
583-
584599
debug!("compute_storage_conflicts({:?})", body.span);
585-
debug!("always_live = {:?}", always_live_locals);
586-
587-
// Locals that are always live or ones that need to be stored across
588-
// suspension points are not eligible for overlap.
589-
let mut ineligible_locals = always_live_locals.into_inner();
590-
ineligible_locals.intersect(stored_locals);
600+
assert_eq!(body.local_decls.len(), stored_locals.domain_size());
591601

592-
// Compute the storage conflicts for all eligible locals.
593-
let mut visitor = StorageConflictVisitor {
594-
body,
595-
stored_locals: &stored_locals,
596-
local_conflicts: BitMatrix::from_row_n(&ineligible_locals, body.local_decls.len()),
597-
};
602+
// Locals that are always live conflict with all other locals.
603+
//
604+
// FIXME: Why do we need to handle locals without `Storage{Live,Dead}` specially here?
605+
// Shouldn't it be enough to know whether they are initialized?
606+
let always_live_locals = always_live_locals.into_inner();
607+
let mut local_conflicts = BitMatrix::from_row_n(&always_live_locals, body.local_decls.len());
608+
609+
// Visit every reachable statement and terminator. The exact order does not matter. When two
610+
// locals are live at the same point in time, add an entry in the conflict matrix.
611+
for (block, data) in traversal::preorder(body) {
612+
// Ignore unreachable blocks.
613+
if data.terminator().kind == TerminatorKind::Unreachable {
614+
continue;
615+
}
598616

599-
// Visit only reachable basic blocks. The exact order is not important.
600-
let reachable_blocks = traversal::preorder(body).map(|(bb, _)| bb);
601-
requires_storage.visit_with(body, reachable_blocks, &mut visitor);
617+
for (statement_index, _) in data.statements.iter().enumerate() {
618+
let loc = Location { block, statement_index };
619+
trace!("record conflicts at {:?}", loc);
620+
init.seek_before_primary_effect(loc);
621+
borrowed.seek_before_primary_effect(loc);
622+
record_conflicts_at_curr_loc(&mut local_conflicts, &init, &borrowed);
623+
}
602624

603-
let local_conflicts = visitor.local_conflicts;
625+
trace!("record conflicts at end of {:?}", block);
626+
init.seek_to_block_end(block);
627+
borrowed.seek_to_block_end(block);
628+
record_conflicts_at_curr_loc(&mut local_conflicts, &init, &borrowed);
629+
}
604630

605631
// Compress the matrix using only stored locals (Local -> GeneratorSavedLocal).
606632
//
@@ -612,7 +638,7 @@ fn compute_storage_conflicts(
612638
let mut storage_conflicts = BitMatrix::new(stored_locals.count(), stored_locals.count());
613639
for (idx_a, local_a) in stored_locals.iter().enumerate() {
614640
let saved_local_a = GeneratorSavedLocal::new(idx_a);
615-
if ineligible_locals.contains(local_a) {
641+
if always_live_locals.contains(local_a) {
616642
// Conflicts with everything.
617643
storage_conflicts.insert_all_into_row(saved_local_a);
618644
} else {
@@ -628,56 +654,6 @@ fn compute_storage_conflicts(
628654
storage_conflicts
629655
}
630656

631-
struct StorageConflictVisitor<'mir, 'tcx, 's> {
632-
body: &'mir Body<'tcx>,
633-
stored_locals: &'s BitSet<Local>,
634-
// FIXME(tmandry): Consider using sparse bitsets here once we have good
635-
// benchmarks for generators.
636-
local_conflicts: BitMatrix<Local, Local>,
637-
}
638-
639-
impl dataflow::ResultsVisitor<'mir, 'tcx> for StorageConflictVisitor<'mir, 'tcx, '_> {
640-
type FlowState = BitSet<Local>;
641-
642-
fn visit_statement_before_primary_effect(
643-
&mut self,
644-
state: &Self::FlowState,
645-
_statement: &'mir Statement<'tcx>,
646-
loc: Location,
647-
) {
648-
self.apply_state(state, loc);
649-
}
650-
651-
fn visit_terminator_before_primary_effect(
652-
&mut self,
653-
state: &Self::FlowState,
654-
_terminator: &'mir Terminator<'tcx>,
655-
loc: Location,
656-
) {
657-
self.apply_state(state, loc);
658-
}
659-
}
660-
661-
impl<'body, 'tcx, 's> StorageConflictVisitor<'body, 'tcx, 's> {
662-
fn apply_state(&mut self, flow_state: &BitSet<Local>, loc: Location) {
663-
// Ignore unreachable blocks.
664-
if self.body.basic_blocks()[loc.block].terminator().kind == TerminatorKind::Unreachable {
665-
return;
666-
}
667-
668-
let mut eligible_storage_live = flow_state.clone();
669-
eligible_storage_live.intersect(&self.stored_locals);
670-
671-
for local in eligible_storage_live.iter() {
672-
self.local_conflicts.union_row_with(&eligible_storage_live, local);
673-
}
674-
675-
if eligible_storage_live.count() > 1 {
676-
trace!("at {:?}, eligible_storage_live={:?}", loc, eligible_storage_live);
677-
}
678-
}
679-
}
680-
681657
fn compute_layout<'tcx>(
682658
tcx: TyCtxt<'tcx>,
683659
source: MirSource<'tcx>,

0 commit comments

Comments
 (0)