@@ -515,6 +515,13 @@ bool PrunedLiveRange<LivenessWithDefs>::isWithinBoundary(
515
515
if (isLive && !asImpl ().isDefBlock (block))
516
516
return true ;
517
517
518
+ return isInstructionLive (inst, isLive);
519
+ }
520
+
521
+ template <typename LivenessWithDefs>
522
+ bool PrunedLiveRange<LivenessWithDefs>::isInstructionLive(SILInstruction *inst,
523
+ bool isLive) const {
524
+ auto *block = inst->getParent ();
518
525
// Check if instruction is between a last use and a definition
519
526
for (SILInstruction &it : llvm::reverse (*block)) {
520
527
// the def itself is not within the boundary, so cancel liveness before
@@ -532,62 +539,267 @@ bool PrunedLiveRange<LivenessWithDefs>::isWithinBoundary(
532
539
llvm_unreachable (" instruction must be in its parent block" );
533
540
}
534
541
535
- // / Whether \p parent is a dead (reported to be dead by `liveBlocks`), dead-end
536
- // / (such as an infinite loop) block within the availability boundary (where
537
- // / the value has not been consumed).
538
- static bool checkDeadEnd (SILBasicBlock *parent, DeadEndBlocks *deadEndBlocks,
539
- PrunedLiveBlocks const &liveBlocks) {
542
+ template <typename LivenessWithDefs>
543
+ bool PrunedLiveRange<LivenessWithDefs>::isAvailableOut(
544
+ SILBasicBlock *block, DeadEndBlocks &deadEndBlocks) const {
545
+ assert (getBlockLiveness (block) == PrunedLiveBlocks::LiveWithin);
546
+ assert (deadEndBlocks.isDeadEnd (block));
547
+ for (SILInstruction &inst : llvm::reverse (*block)) {
548
+ if (asImpl ().isDef (&inst)) {
549
+ return true ;
550
+ }
551
+ switch (isInterestingUser (&inst)) {
552
+ case PrunedLiveness::NonUser:
553
+ continue ;
554
+ case PrunedLiveness::NonLifetimeEndingUse:
555
+ return true ;
556
+ case PrunedLiveness::LifetimeEndingUse:
557
+ return false ;
558
+ }
559
+ }
560
+ assert (asImpl ().isDefBlock (block));
561
+ assert (llvm::any_of (block->getArguments (), [this ](SILArgument *arg) {
562
+ return asImpl ().isDef (arg);
563
+ }));
564
+ return true ;
565
+ }
566
+
567
+ template <typename LivenessWithDefs>
568
+ bool PrunedLiveRange<LivenessWithDefs>::isInstructionAvailable(
569
+ SILInstruction *user, DeadEndBlocks &deadEndBlocks) const {
570
+ auto *parent = user->getParent ();
571
+ assert (getBlockLiveness (parent) == PrunedLiveBlocks::LiveWithin);
572
+ assert (deadEndBlocks.isDeadEnd (parent));
573
+ return isInstructionLive (user, isAvailableOut (parent, deadEndBlocks));
574
+ }
575
+
576
+ template <typename LivenessWithDefs>
577
+ bool PrunedLiveRange<LivenessWithDefs>::isWithinExtendedBoundary(
578
+ SILInstruction *inst, DeadEndBlocks *deadEndBlocks) const {
579
+ // A value has a pruned live region, a live region and an available region.
580
+ // (Note: PrunedLiveness does not distinguish between the pruned live region
581
+ // and the live region; the pruned live region coincides with the live region
582
+ // whenever consuming uses are considered.) This method refers to a FOURTH
583
+ // region: the "extended region" which MAY be different from the others.
584
+ // (Terminological note: this isn't intended to gain regular usage, hence its
585
+ // lack of specificity.)
586
+ //
587
+ // Before _defining_ the extended region, consider the following example:
588
+ //
589
+ // def = ...
590
+ // inst_1
591
+ // use %def // added to pruned liveness
592
+ // inst_2
593
+ // cond_br %c1, die, normal
594
+ // die:
595
+ // inst_3
596
+ // unreachable
597
+ // normal:
598
+ // inst_4
599
+ // destroy %def // NOT added to pruned liveness
600
+ // inst_5
601
+ //
602
+ // This table describes which regions the `inst_i`s are in:
603
+ // +------+----+------+--------+---------+
604
+ // | |live|pruned|extended|available|
605
+ // +------+----+------+--------+---------+
606
+ // |inst_1| yes| yes | yes | yes |
607
+ // +------+----+------+--------+---------+
608
+ // |inst_2| yes| no | yes | yes |
609
+ // +------+----+------+--------+---------+
610
+ // |inst_3| no | no | yes | yes |
611
+ // +------+----+------+--------+---------+
612
+ // |inst_4| yes| no | no | yes |
613
+ // +------+----+------+--------+---------+
614
+ // |inst_5| no | no | no | no |
615
+ // +------+----+------+--------+---------+
616
+ //
617
+ // This example demonstrates that
618
+ // pruned live ≠ extended ≠ available
619
+ // and indicates the fact that
620
+ // pruned live ⊆ extended ⊆ available
621
+ //
622
+ // The "extended region" is the pruned live region availability-extended into
623
+ // dead-end regions. In more detail, it's obtained by (1) unioning the
624
+ // dead-end regions adjacent to the pruned live region (the portions of those
625
+ // adjacent dead-end regions which are forward reachable from the pruned live
626
+ // region) and (2) intersecting the result with the availability region.
627
+ //
628
+ // That this region is of interest is another result of lacking complete
629
+ // OSSA lifetimes.
630
+
631
+ if (asImpl ().isWithinBoundary (inst)) {
632
+ // The extended region is a superset of the pruned live region.
633
+ return true ;
634
+ }
635
+
540
636
if (!deadEndBlocks) {
637
+ // Without knowledge of the dead-end region, the extended region can't be
638
+ // determined. It could, of course, be rediscovered here, but that would
639
+ // be silly; instead, allowing a nullable pointer provides a mechanism for
640
+ // the client to indicate what invariants hold. Specifically, omitting
641
+ // dead-end blocks is equivalent to asserting that lifetimes are complete.
541
642
return false ;
542
643
}
644
+ SILBasicBlock *parent = inst->getParent ();
543
645
if (!deadEndBlocks->isDeadEnd (parent)) {
646
+ // The extended region intersected with the non-dead-end region is equal to
647
+ // the pruned live region.
544
648
return false ;
545
649
}
546
- if (liveBlocks.getBlockLiveness (parent) != PrunedLiveBlocks::Dead) {
650
+ switch (liveBlocks.getBlockLiveness (parent)) {
651
+ case PrunedLiveBlocks::Dead:
652
+ break ;
653
+ case PrunedLiveBlocks::LiveWithin:
654
+ // Dead defs may result in LiveWithin but AvailableOut blocks.
655
+ return isInstructionAvailable (inst, *deadEndBlocks);
656
+ case PrunedLiveBlocks::LiveOut:
657
+ // The instruction is not within the boundary, but its parent is LiveOut;
658
+ // therefore it must be a def block.
659
+ assert (asImpl ().isDefBlock (parent));
660
+
661
+ // Where within the block might the instruction be?
662
+ // - before the first def: return false (outside the extended region).
663
+ // - between a def and a use: unreachable (withinBoundary would have
664
+ // returned true).
665
+ // - between a def and another def: unreachable (withinBoundary would have
666
+ // returned true)
667
+ // - between a use and a def: return false (outside the extended region).
668
+ // - after the final def: unreachable (withinBoundary would have returned
669
+ // true)
547
670
return false ;
548
671
}
549
- // Check whether the value is available in `parent` (i.e. not consumed on any
550
- // path to it):
672
+ // Check whether `parent` is in the extended region: walk backwards within
673
+ // the dead portion of the dead-end region up _through_ the first block which
674
+ // is either not dead or not dead-end.
675
+ //
676
+ // During the walk, if ANY reached block satisfies one of
677
+ // (1) dead-end, LiveWithin, !AvailableOut
678
+ // (2) NOT dead-end, NOT LiveOut
679
+ // then the `parent` is not in the extended region.
551
680
//
552
- // Search backward until LiveOut or LiveWithin blocks are reached.
553
- // (1) If ALL the reached blocks are LiveOut, then `parent` IS within the
554
- // availability boundary.
555
- // (2) If ANY reached block is LiveWithin, the value was consumed in that
556
- // reached block, preventing the value from being available at `parent`,
557
- // so `parent` is NOT within the availability boundary.
681
+ // Otherwise, ALL reached blocks satisfied one of the following:
682
+ // (a) dead-end, Dead
683
+ // (b) dead-end, LiveWithin, AvailableOut
684
+ // (b) MAYBE dead-end, LiveOut
685
+ // In this case, `parent` is in the extended region.
558
686
BasicBlockWorklist worklist (parent->getFunction ());
559
687
worklist.push (parent);
560
688
while (auto *block = worklist.pop ()) {
561
689
auto isLive = liveBlocks.getBlockLiveness (block);
690
+ if (!deadEndBlocks->isDeadEnd (block)) {
691
+ // The first block beyond the dead-end region has been reached.
692
+ if (isLive != PrunedLiveBlocks::LiveOut) {
693
+ // Cases (2) above.
694
+ return false ;
695
+ }
696
+ // Stop walking. (No longer in the dead portion of the dead-end region.)
697
+ continue ;
698
+ }
562
699
switch (isLive) {
563
- case PrunedLiveBlocks::Dead: {
564
- // Availability is unchanged; continue the backwards walk .
700
+ case PrunedLiveBlocks::Dead:
701
+ // Still within the dead portion of the dead-end region. Keep walking .
565
702
for (auto *predecessor : block->getPredecessorBlocks ()) {
566
703
worklist.pushIfNotVisited (predecessor);
567
704
}
568
- break ;
569
- }
705
+ continue ;
570
706
case PrunedLiveBlocks::LiveWithin:
571
- // Availability ended in this block. Some path to `parent` consumed the
572
- // value. Case (2) above.
573
- return false ;
707
+ // Availability may have ended in this block. Check whether the block is
708
+ // "AvailableOut".
709
+ if (!isAvailableOut (block, *deadEndBlocks)) {
710
+ // Case (1) above.
711
+ return false ;
712
+ }
713
+ // Stop walking. (No longer in the dead portion of the dead-end region.)
714
+ continue ;
574
715
case PrunedLiveBlocks::LiveOut:
575
- // Availability continued out of this block. Case (1) above.
716
+ // Stop walking. (No longer in the dead portion of the dead-end region.)
576
717
continue ;
577
718
}
578
719
}
579
720
return true ;
580
721
}
581
722
723
+ namespace swift ::test {
724
+ // Arguments:
725
+ // - string: "def:"
726
+ // - SILValue: value to be analyzed
727
+ // - string: "liveness-uses:"
728
+ // - variadic list of - SILInstruction: user to pass to updateForUse
729
+ // - string: non-ending/ending/non-use
730
+ // - string: "uses:"
731
+ // - variadic list of - SILInstruction: the instruction to pass to
732
+ // areUsesWithinBoundary Dumps:
733
+ // - true/false
734
+ static FunctionTest SSAPrunedLiveness__areUsesWithinBoundary (
735
+ " SSAPrunedLiveness__areUsesWithinBoundary" ,
736
+ [](auto &function, auto &arguments, auto &test) {
737
+ SmallVector<SILBasicBlock *, 8 > discoveredBlocks;
738
+ SSAPrunedLiveness liveness (&function, &discoveredBlocks);
739
+
740
+ llvm::outs () << " SSAPrunedLiveness:\n " ;
741
+
742
+ if (arguments.takeString () != " def:" ) {
743
+ llvm::report_fatal_error (" test expects the 'def:' label\n " );
744
+ }
745
+ auto def = arguments.takeValue ();
746
+ liveness.initializeDef (def);
747
+ llvm::outs () << " \t def: " << def;
748
+ if (arguments.takeString () != " liveness-uses:" ) {
749
+ llvm::report_fatal_error (" test expects the 'def:' label\n " );
750
+ }
751
+ llvm::outs () << " \t uses:\n " ;
752
+ while (true ) {
753
+ auto argument = arguments.takeArgument ();
754
+ if (isa<StringArgument>(argument)) {
755
+ auto string = cast<StringArgument>(argument);
756
+ if (string.getValue () != " uses:" ) {
757
+ llvm::report_fatal_error (" test expects the 'inst:' label\n " );
758
+ }
759
+ break ;
760
+ }
761
+ auto *instruction = cast<InstructionArgument>(argument).getValue ();
762
+ auto string = arguments.takeString ();
763
+ PrunedLiveness::LifetimeEnding::Value kind =
764
+ llvm::StringSwitch<PrunedLiveness::LifetimeEnding::Value>(string)
765
+ .Case (" non-ending" ,
766
+ PrunedLiveness::LifetimeEnding::Value::NonEnding)
767
+ .Case (" ending" , PrunedLiveness::LifetimeEnding::Value::Ending)
768
+ .Case (" non-use" , PrunedLiveness::LifetimeEnding::Value::NonUse);
769
+
770
+ llvm::outs () << " \t\t " << string << " " << *instruction;
771
+ liveness.updateForUse (instruction, kind);
772
+ }
773
+ liveness.print (llvm::outs ());
774
+
775
+ PrunedLivenessBoundary boundary;
776
+ liveness.computeBoundary (boundary);
777
+ boundary.print (llvm::outs ());
778
+
779
+ llvm::outs () << " \n operands:\n " ;
780
+ SmallVector<Operand *, 4 > operands;
781
+ while (arguments.hasUntaken ()) {
782
+ auto *operand = arguments.takeOperand ();
783
+ operands.push_back (operand);
784
+ operand->print (llvm::outs ());
785
+ }
786
+
787
+ auto result =
788
+ liveness.areUsesWithinBoundary (operands, test.getDeadEndBlocks ());
789
+
790
+ llvm::outs () << " RESULT: " << StringRef (result ? " true" : " false" )
791
+ << " \n " ;
792
+ });
793
+ } // end namespace swift::test
794
+
582
795
template <typename LivenessWithDefs>
583
796
bool PrunedLiveRange<LivenessWithDefs>::areUsesWithinBoundary(
584
797
ArrayRef<Operand *> uses, DeadEndBlocks *deadEndBlocks) const {
585
798
assert (asImpl ().isInitialized ());
586
799
587
800
for (auto *use : uses) {
588
801
auto *user = use->getUser ();
589
- if (!asImpl ().isWithinBoundary (user) &&
590
- !checkDeadEnd (user->getParent (), deadEndBlocks, liveBlocks))
802
+ if (!isWithinExtendedBoundary (user, deadEndBlocks))
591
803
return false ;
592
804
}
593
805
return true ;
@@ -600,8 +812,7 @@ bool PrunedLiveRange<LivenessWithDefs>::areUsesOutsideBoundary(
600
812
601
813
for (auto *use : uses) {
602
814
auto *user = use->getUser ();
603
- if (asImpl ().isWithinBoundary (user) ||
604
- checkDeadEnd (user->getParent (), deadEndBlocks, liveBlocks))
815
+ if (isWithinExtendedBoundary (user, deadEndBlocks))
605
816
return false ;
606
817
}
607
818
return true ;
0 commit comments