@@ -515,6 +515,13 @@ bool PrunedLiveRange<LivenessWithDefs>::isWithinBoundary(
515515 if (isLive && !asImpl ().isDefBlock (block))
516516 return true ;
517517
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 ();
518525 // Check if instruction is between a last use and a definition
519526 for (SILInstruction &it : llvm::reverse (*block)) {
520527 // the def itself is not within the boundary, so cancel liveness before
@@ -532,62 +539,267 @@ bool PrunedLiveRange<LivenessWithDefs>::isWithinBoundary(
532539 llvm_unreachable (" instruction must be in its parent block" );
533540}
534541
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+
540636 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.
541642 return false ;
542643 }
644+ SILBasicBlock *parent = inst->getParent ();
543645 if (!deadEndBlocks->isDeadEnd (parent)) {
646+ // The extended region intersected with the non-dead-end region is equal to
647+ // the pruned live region.
544648 return false ;
545649 }
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)
547670 return false ;
548671 }
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.
551680 //
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.
558686 BasicBlockWorklist worklist (parent->getFunction ());
559687 worklist.push (parent);
560688 while (auto *block = worklist.pop ()) {
561689 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+ }
562699 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 .
565702 for (auto *predecessor : block->getPredecessorBlocks ()) {
566703 worklist.pushIfNotVisited (predecessor);
567704 }
568- break ;
569- }
705+ continue ;
570706 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 ;
574715 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.)
576717 continue ;
577718 }
578719 }
579720 return true ;
580721}
581722
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+
582795template <typename LivenessWithDefs>
583796bool PrunedLiveRange<LivenessWithDefs>::areUsesWithinBoundary(
584797 ArrayRef<Operand *> uses, DeadEndBlocks *deadEndBlocks) const {
585798 assert (asImpl ().isInitialized ());
586799
587800 for (auto *use : uses) {
588801 auto *user = use->getUser ();
589- if (!asImpl ().isWithinBoundary (user) &&
590- !checkDeadEnd (user->getParent (), deadEndBlocks, liveBlocks))
802+ if (!isWithinExtendedBoundary (user, deadEndBlocks))
591803 return false ;
592804 }
593805 return true ;
@@ -600,8 +812,7 @@ bool PrunedLiveRange<LivenessWithDefs>::areUsesOutsideBoundary(
600812
601813 for (auto *use : uses) {
602814 auto *user = use->getUser ();
603- if (asImpl ().isWithinBoundary (user) ||
604- checkDeadEnd (user->getParent (), deadEndBlocks, liveBlocks))
815+ if (isWithinExtendedBoundary (user, deadEndBlocks))
605816 return false ;
606817 }
607818 return true ;
0 commit comments