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 , MaybeInitializedLocals , MaybeLiveLocals , MaybeStorageLive ,
53
+ MaybeBorrowedLocals , MaybeLiveLocals , MaybeRequiresStorage , MaybeStorageLive ,
54
54
} ;
55
55
use crate :: dataflow:: { self , Analysis } ;
56
56
use crate :: transform:: no_landing_pads:: no_landing_pads;
@@ -444,80 +444,86 @@ 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;
447
448
448
449
// Calculate when MIR locals have live storage. This gives us an upper bound of their
449
450
// lifetimes.
450
451
let mut storage_live = MaybeStorageLive :: new ( always_live_locals. clone ( ) )
451
- . into_engine ( tcx, body , def_id)
452
+ . into_engine ( tcx, body_ref , def_id)
452
453
. iterate_to_fixpoint ( )
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)
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)
467
475
. iterate_to_fixpoint ( )
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
- } ;
476
+ . into_results_cursor ( body_ref) ;
491
477
492
478
let mut storage_liveness_map = IndexVec :: from_elem ( None , body. basic_blocks ( ) ) ;
493
479
let mut live_locals_at_suspension_points = Vec :: new ( ) ;
494
480
let mut live_locals_at_any_suspension_point = BitSet :: new_empty ( body. local_decls . len ( ) ) ;
495
481
496
482
for ( block, data) in body. basic_blocks ( ) . iter_enumerated ( ) {
497
- if !matches ! ( data. terminator( ) . kind, TerminatorKind :: Yield { .. } ) {
498
- continue ;
499
- }
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
+ }
500
503
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 ( ) ) ;
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 ( ) ) ;
505
508
506
- let mut live_locals = locals_live_across_yield_point ( block) ;
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 ( ) ) ;
507
514
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) ) ;
515
+ // The generator argument is ignored.
516
+ live_locals. remove ( SELF_ARG ) ;
513
517
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) ;
519
- }
518
+ debug ! ( "loc = {:?}, live_locals = {:?}" , loc, live_locals) ;
520
519
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) ;
523
+
524
+ live_locals_at_suspension_points. push ( live_locals) ;
525
+ }
526
+ }
521
527
debug ! ( "live_locals_anywhere = {:?}" , live_locals_at_any_suspension_point) ;
522
528
523
529
// Renumber our liveness_map bitsets to include only the locals we are
@@ -528,11 +534,10 @@ fn locals_live_across_suspend_points(
528
534
. collect ( ) ;
529
535
530
536
let storage_conflicts = compute_storage_conflicts (
531
- body ,
537
+ body_ref ,
532
538
& live_locals_at_any_suspension_point,
533
539
always_live_locals. clone ( ) ,
534
- init,
535
- borrowed,
540
+ requires_storage_results,
536
541
) ;
537
542
538
543
LivenessInfo {
@@ -564,37 +569,6 @@ fn renumber_bitset(
564
569
out
565
570
}
566
571
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
-
598
572
/// For every saved local, looks for which locals are StorageLive at the same
599
573
/// time. Generates a bitset for every local of all the other locals that may be
600
574
/// StorageLive simultaneously with that local. This is used in the layout
@@ -603,45 +577,30 @@ fn compute_storage_conflicts(
603
577
body : & ' mir Body < ' tcx > ,
604
578
stored_locals : & BitSet < Local > ,
605
579
always_live_locals : storage:: AlwaysLiveLocals ,
606
- mut init : dataflow:: ResultsCursor < ' mir , ' tcx , MaybeInitializedLocals > ,
607
- mut borrowed : dataflow:: ResultsCursor < ' mir , ' tcx , MaybeBorrowedLocals > ,
580
+ requires_storage : dataflow:: Results < ' tcx , MaybeRequiresStorage < ' mir , ' tcx > > ,
608
581
) -> BitMatrix < GeneratorSavedLocal , GeneratorSavedLocal > {
609
- debug ! ( "compute_storage_conflicts({:?})" , body. span) ;
610
582
assert_eq ! ( body. local_decls. len( ) , stored_locals. domain_size( ) ) ;
611
583
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
- }
584
+ debug ! ( "compute_storage_conflicts({:?})" , body. span) ;
585
+ debug ! ( "always_live = {:?}" , always_live_locals) ;
626
586
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
- }
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) ;
636
591
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
- }
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
+ } ;
598
+
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) ;
602
+
603
+ let local_conflicts = visitor. local_conflicts ;
645
604
646
605
// Compress the matrix using only stored locals (Local -> GeneratorSavedLocal).
647
606
//
@@ -653,7 +612,7 @@ fn compute_storage_conflicts(
653
612
let mut storage_conflicts = BitMatrix :: new ( stored_locals. count ( ) , stored_locals. count ( ) ) ;
654
613
for ( idx_a, local_a) in stored_locals. iter ( ) . enumerate ( ) {
655
614
let saved_local_a = GeneratorSavedLocal :: new ( idx_a) ;
656
- if always_live_locals . contains ( local_a) {
615
+ if ineligible_locals . contains ( local_a) {
657
616
// Conflicts with everything.
658
617
storage_conflicts. insert_all_into_row ( saved_local_a) ;
659
618
} else {
@@ -669,6 +628,56 @@ fn compute_storage_conflicts(
669
628
storage_conflicts
670
629
}
671
630
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
+
672
681
/// Validates the typeck view of the generator against the actual set of types retained between
673
682
/// yield points.
674
683
fn sanitize_witness < ' tcx > (
0 commit comments