50
50
//! Otherwise it drops all the values in scope at the last suspension point.
51
51
52
52
use crate :: dataflow:: impls:: {
53
- MaybeBorrowedLocals , MaybeLiveLocals , MaybeRequiresStorage , MaybeStorageLive ,
53
+ MaybeBorrowedLocals , MaybeInitializedLocals , MaybeLiveLocals , MaybeStorageLive ,
54
54
} ;
55
55
use crate :: dataflow:: { self , Analysis } ;
56
56
use crate :: transform:: no_landing_pads:: no_landing_pads;
@@ -444,86 +444,80 @@ fn locals_live_across_suspend_points(
444
444
movable : bool ,
445
445
) -> LivenessInfo {
446
446
let def_id = source. def_id ( ) ;
447
- let body_ref: & Body < ' _ > = & body;
448
447
449
448
// Calculate when MIR locals have live storage. This gives us an upper bound of their
450
449
// lifetimes.
451
450
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)
453
452
. 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)
457
+ . iterate_to_fixpoint ( )
458
+ . into_results_cursor ( body) ;
459
+
460
+ let mut live = MaybeLiveLocals
461
+ . into_engine ( tcx, body, def_id)
462
+ . iterate_to_fixpoint ( )
463
+ . into_results_cursor ( body) ;
464
+
465
+ let mut borrowed = MaybeBorrowedLocals :: all_borrows ( )
466
+ . into_engine ( tcx, body, def_id)
475
467
. iterate_to_fixpoint ( )
476
- . into_results_cursor ( body_ref) ;
468
+ . into_results_cursor ( body) ;
469
+
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 ( ) ;
480
+
481
+ init. seek_to_block_end ( block) ;
482
+ live_locals. intersect ( init. get ( ) ) ;
483
+
484
+ if !movable {
485
+ borrowed. seek_to_block_end ( block) ;
486
+ live_locals. union ( borrowed. get ( ) ) ;
487
+ }
488
+
489
+ live_locals
490
+ } ;
477
491
478
492
let mut storage_liveness_map = IndexVec :: from_elem ( None , body. basic_blocks ( ) ) ;
479
493
let mut live_locals_at_suspension_points = Vec :: new ( ) ;
480
494
let mut live_locals_at_any_suspension_point = BitSet :: new_empty ( body. local_decls . len ( ) ) ;
481
495
482
496
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
- }
503
-
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 ( ) ) ;
508
-
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 ( ) ) ;
497
+ if !matches ! ( data. terminator( ) . kind, TerminatorKind :: Yield { .. } ) {
498
+ continue ;
499
+ }
514
500
515
- // The generator argument is ignored.
516
- live_locals. remove ( SELF_ARG ) ;
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 ( ) ) ;
517
505
518
- debug ! ( "loc = {:?}, live_locals = {:?}" , loc , live_locals ) ;
506
+ let mut live_locals = locals_live_across_yield_point ( block ) ;
519
507
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) ;
508
+ // The combination of `MaybeInitializedLocals` and `MaybeBorrowedLocals` should be strictly
509
+ // more precise than `MaybeStorageLive` because they handle `StorageDead` themselves. This
510
+ // assumes that the MIR forbids locals from being initialized/borrowed before reaching
511
+ // `StorageLive`.
512
+ debug_assert ! ( storage_live. get( ) . superset( & live_locals) ) ;
523
513
524
- live_locals_at_suspension_points. push ( live_locals) ;
525
- }
514
+ // Ignore the generator's `self` argument since it is handled seperately.
515
+ live_locals. remove ( SELF_ARG ) ;
516
+ debug ! ( "block = {:?}, live_locals = {:?}" , block, live_locals) ;
517
+ live_locals_at_any_suspension_point. union ( & live_locals) ;
518
+ live_locals_at_suspension_points. push ( live_locals) ;
526
519
}
520
+
527
521
debug ! ( "live_locals_anywhere = {:?}" , live_locals_at_any_suspension_point) ;
528
522
529
523
// Renumber our liveness_map bitsets to include only the locals we are
@@ -534,10 +528,11 @@ fn locals_live_across_suspend_points(
534
528
. collect ( ) ;
535
529
536
530
let storage_conflicts = compute_storage_conflicts (
537
- body_ref ,
531
+ body ,
538
532
& live_locals_at_any_suspension_point,
539
533
always_live_locals. clone ( ) ,
540
- requires_storage_results,
534
+ init,
535
+ borrowed,
541
536
) ;
542
537
543
538
LivenessInfo {
@@ -569,6 +564,37 @@ fn renumber_bitset(
569
564
out
570
565
}
571
566
567
+ /// Record conflicts between locals at the current dataflow cursor positions.
568
+ ///
569
+ /// You need to seek the cursors before calling this function.
570
+ fn record_conflicts_at_curr_loc (
571
+ local_conflicts : & mut BitMatrix < Local , Local > ,
572
+ init : & dataflow:: ResultsCursor < ' mir , ' tcx , MaybeInitializedLocals > ,
573
+ borrowed : & dataflow:: ResultsCursor < ' mir , ' tcx , MaybeBorrowedLocals > ,
574
+ ) {
575
+ // A local requires storage if it is initialized or borrowed. For now, a local
576
+ // becomes uninitialized if it is moved from, but is still considered "borrowed".
577
+ //
578
+ // requires_storage := init | borrowed
579
+ //
580
+ // Just like when determining what locals are live at yield points, there is no need
581
+ // to look at storage liveness here, since `init | borrowed` is strictly more precise.
582
+ //
583
+ // FIXME: This function is called in a loop, so it might be better to pass in a temporary
584
+ // bitset rather than cloning here.
585
+ let mut requires_storage = init. get ( ) . clone ( ) ;
586
+ requires_storage. union ( borrowed. get ( ) ) ;
587
+
588
+ for local in requires_storage. iter ( ) {
589
+ local_conflicts. union_row_with ( & requires_storage, local) ;
590
+ }
591
+
592
+ // `>1` because the `self` argument always requires storage.
593
+ if requires_storage. count ( ) > 1 {
594
+ trace ! ( "requires_storage={:?}" , requires_storage) ;
595
+ }
596
+ }
597
+
572
598
/// For every saved local, looks for which locals are StorageLive at the same
573
599
/// time. Generates a bitset for every local of all the other locals that may be
574
600
/// StorageLive simultaneously with that local. This is used in the layout
@@ -577,30 +603,45 @@ fn compute_storage_conflicts(
577
603
body : & ' mir Body < ' tcx > ,
578
604
stored_locals : & BitSet < Local > ,
579
605
always_live_locals : storage:: AlwaysLiveLocals ,
580
- requires_storage : dataflow:: Results < ' tcx , MaybeRequiresStorage < ' mir , ' tcx > > ,
606
+ mut init : dataflow:: ResultsCursor < ' mir , ' tcx , MaybeInitializedLocals > ,
607
+ mut borrowed : dataflow:: ResultsCursor < ' mir , ' tcx , MaybeBorrowedLocals > ,
581
608
) -> BitMatrix < GeneratorSavedLocal , GeneratorSavedLocal > {
582
- assert_eq ! ( body. local_decls. len( ) , stored_locals. domain_size( ) ) ;
583
-
584
609
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) ;
610
+ assert_eq ! ( body. local_decls. len( ) , stored_locals. domain_size( ) ) ;
591
611
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
- } ;
612
+ // Locals that are always live conflict with all other locals.
613
+ //
614
+ // FIXME: Why do we need to handle locals without `Storage{Live,Dead}` specially here?
615
+ // Shouldn't it be enough to know whether they are initialized?
616
+ let always_live_locals = always_live_locals. into_inner ( ) ;
617
+ let mut local_conflicts = BitMatrix :: from_row_n ( & always_live_locals, body. local_decls . len ( ) ) ;
618
+
619
+ // Visit every reachable statement and terminator. The exact order does not matter. When two
620
+ // locals are live at the same point in time, add an entry in the conflict matrix.
621
+ for ( block, data) in traversal:: preorder ( body) {
622
+ // Ignore unreachable blocks.
623
+ if data. terminator ( ) . kind == TerminatorKind :: Unreachable {
624
+ continue ;
625
+ }
598
626
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) ;
627
+ // Observe the dataflow state *before* all possible locations (statement or terminator) in
628
+ // each basic block...
629
+ for statement_index in 0 ..=data. statements . len ( ) {
630
+ let loc = Location { block, statement_index } ;
631
+ trace ! ( "record conflicts at {:?}" , loc) ;
632
+ init. seek_before_primary_effect ( loc) ;
633
+ borrowed. seek_before_primary_effect ( loc) ;
634
+ record_conflicts_at_curr_loc ( & mut local_conflicts, & init, & borrowed) ;
635
+ }
602
636
603
- let local_conflicts = visitor. local_conflicts ;
637
+ // ...and then observe the state *after* the terminator effect is applied. As long as
638
+ // neither `init` nor `borrowed` has a "before" effect, we will observe all possible
639
+ // dataflow states here or in the loop above.
640
+ trace ! ( "record conflicts at end of {:?}" , block) ;
641
+ init. seek_to_block_end ( block) ;
642
+ borrowed. seek_to_block_end ( block) ;
643
+ record_conflicts_at_curr_loc ( & mut local_conflicts, & init, & borrowed) ;
644
+ }
604
645
605
646
// Compress the matrix using only stored locals (Local -> GeneratorSavedLocal).
606
647
//
@@ -612,7 +653,7 @@ fn compute_storage_conflicts(
612
653
let mut storage_conflicts = BitMatrix :: new ( stored_locals. count ( ) , stored_locals. count ( ) ) ;
613
654
for ( idx_a, local_a) in stored_locals. iter ( ) . enumerate ( ) {
614
655
let saved_local_a = GeneratorSavedLocal :: new ( idx_a) ;
615
- if ineligible_locals . contains ( local_a) {
656
+ if always_live_locals . contains ( local_a) {
616
657
// Conflicts with everything.
617
658
storage_conflicts. insert_all_into_row ( saved_local_a) ;
618
659
} else {
@@ -628,56 +669,6 @@ fn compute_storage_conflicts(
628
669
storage_conflicts
629
670
}
630
671
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
-
681
672
fn compute_layout < ' tcx > (
682
673
tcx : TyCtxt < ' tcx > ,
683
674
source : MirSource < ' tcx > ,
0 commit comments