Skip to content

Commit e0da318

Browse files
committed
[PrunedLiveness] Fix extended boundary check.
The areUsesWithinBoundary/areUsesOutsideBoundary methods take the dead-ends because the region that they're checking for containment within contains more than just (is a (non-strict) superset of) the pruned live region. On the other hand, the region also contains _less_ than (is a (non-strict) subset of) the available region. Instructions are in this extended region only if they're in a dead-end block which is forwards reachable from the non-lifetime-ending liveness boundary.
1 parent aa8ccaf commit e0da318

File tree

3 files changed

+507
-26
lines changed

3 files changed

+507
-26
lines changed

include/swift/SIL/PrunedLiveness.h

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -590,6 +590,8 @@ struct PrunedLivenessBoundary {
590590
///
591591
/// bool isDef(SILInstruction *inst) const
592592
///
593+
/// bool isDef(SILArgument *arg) const
594+
///
593595
/// bool isDefBlock(SILBasicBlock *block) const
594596
///
595597
template <typename LivenessWithDefs>
@@ -611,6 +613,15 @@ class PrunedLiveRange : public PrunedLiveness {
611613
ValueSet &visited,
612614
SILValue value);
613615

616+
bool isInstructionLive(SILInstruction *instruction, bool liveOut) const;
617+
bool isAvailableOut(SILBasicBlock *block, DeadEndBlocks &deadEndBlocks) const;
618+
bool isInstructionAvailable(SILInstruction *user,
619+
DeadEndBlocks &deadEndBlocks) const;
620+
/// Whether \p user is within the boundary extended from live regions into
621+
/// dead-end regions up to the availability boundary.
622+
bool isWithinExtendedBoundary(SILInstruction *user,
623+
DeadEndBlocks *deadEndBlocks) const;
624+
614625
public:
615626
/// Add \p inst to liveness which uses the def as indicated by \p usage.
616627
void updateForUse(SILInstruction *inst, LifetimeEnding usage);
@@ -735,6 +746,8 @@ class SSAPrunedLiveness : public PrunedLiveRange<SSAPrunedLiveness> {
735746

736747
bool isDef(SILInstruction *inst) const { return inst == defInst; }
737748

749+
bool isDef(SILArgument *arg) const { return def == arg; }
750+
738751
bool isDefBlock(SILBasicBlock *block) const {
739752
return def->getParentBlock() == block;
740753
}

lib/SIL/Utils/PrunedLiveness.cpp

Lines changed: 237 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -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() << "\tdef: " << def;
748+
if (arguments.takeString() != "liveness-uses:") {
749+
llvm::report_fatal_error("test expects the 'def:' label\n");
750+
}
751+
llvm::outs() << "\tuses:\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() << "\noperands:\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+
582795
template <typename LivenessWithDefs>
583796
bool 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

Comments
 (0)