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,74 @@ 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)
475
457
. iterate_to_fixpoint ( )
476
- . into_results_cursor ( body_ref ) ;
458
+ . into_results_cursor ( body ) ;
477
459
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) ;
481
464
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) ;
503
469
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 ( ) ;
508
480
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 ( ) ) ;
514
483
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
+ }
517
488
518
- debug ! ( "loc = {:?}, live_locals = {:?}" , loc, live_locals) ;
489
+ live_locals
490
+ } ;
519
491
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 ( ) ) ;
523
495
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 ;
525
499
}
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) ;
526
513
}
514
+
527
515
debug ! ( "live_locals_anywhere = {:?}" , live_locals_at_any_suspension_point) ;
528
516
529
517
// Renumber our liveness_map bitsets to include only the locals we are
@@ -534,10 +522,11 @@ fn locals_live_across_suspend_points(
534
522
. collect ( ) ;
535
523
536
524
let storage_conflicts = compute_storage_conflicts (
537
- body_ref ,
525
+ body ,
538
526
& live_locals_at_any_suspension_point,
539
527
always_live_locals. clone ( ) ,
540
- requires_storage_results,
528
+ init,
529
+ borrowed,
541
530
) ;
542
531
543
532
LivenessInfo {
@@ -569,6 +558,33 @@ fn renumber_bitset(
569
558
out
570
559
}
571
560
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
+
572
588
/// For every saved local, looks for which locals are StorageLive at the same
573
589
/// time. Generates a bitset for every local of all the other locals that may be
574
590
/// StorageLive simultaneously with that local. This is used in the layout
@@ -577,30 +593,40 @@ fn compute_storage_conflicts(
577
593
body : & ' mir Body < ' tcx > ,
578
594
stored_locals : & BitSet < Local > ,
579
595
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 > ,
581
598
) -> BitMatrix < GeneratorSavedLocal , GeneratorSavedLocal > {
582
- assert_eq ! ( body. local_decls. len( ) , stored_locals. domain_size( ) ) ;
583
-
584
599
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( ) ) ;
591
601
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
+ }
598
616
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
+ }
602
624
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
+ }
604
630
605
631
// Compress the matrix using only stored locals (Local -> GeneratorSavedLocal).
606
632
//
@@ -612,7 +638,7 @@ fn compute_storage_conflicts(
612
638
let mut storage_conflicts = BitMatrix :: new ( stored_locals. count ( ) , stored_locals. count ( ) ) ;
613
639
for ( idx_a, local_a) in stored_locals. iter ( ) . enumerate ( ) {
614
640
let saved_local_a = GeneratorSavedLocal :: new ( idx_a) ;
615
- if ineligible_locals . contains ( local_a) {
641
+ if always_live_locals . contains ( local_a) {
616
642
// Conflicts with everything.
617
643
storage_conflicts. insert_all_into_row ( saved_local_a) ;
618
644
} else {
@@ -628,56 +654,6 @@ fn compute_storage_conflicts(
628
654
storage_conflicts
629
655
}
630
656
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
657
fn compute_layout < ' tcx > (
682
658
tcx : TyCtxt < ' tcx > ,
683
659
source : MirSource < ' tcx > ,
0 commit comments