Skip to content

Commit 83d9781

Browse files
Merge pull request #60121 from nate-chandler/rdar94346482
[SILOpt] Handle reborrows of owned phis in CanonicalizeOSSALifetime.
2 parents b23e5dc + f1108e5 commit 83d9781

File tree

6 files changed

+599
-3
lines changed

6 files changed

+599
-3
lines changed

include/swift/SIL/OwnershipUtils.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1217,6 +1217,14 @@ void findTransitiveReborrowBaseValuePairs(
12171217
BorrowingOperand initialScopeOperand, SILValue origBaseValue,
12181218
function_ref<void(SILPhiArgument *, SILValue)> visitReborrowBaseValuePair);
12191219

1220+
/// Visit the phis in the same block as \p phi which are reborrows of a borrow
1221+
/// of one of the values reaching \p phi.
1222+
///
1223+
/// If the visitor returns false, stops visiting and returns false. Otherwise,
1224+
/// returns true.
1225+
bool visitAdjacentReborrowsOfPhi(SILPhiArgument *phi,
1226+
function_ref<bool(SILPhiArgument *)> visitor);
1227+
12201228
/// Given a begin of a borrow scope, visit all end_borrow users of the borrow or
12211229
/// its reborrows.
12221230
void visitTransitiveEndBorrows(

include/swift/SIL/SILArgument.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -274,6 +274,13 @@ class SILPhiArgument : public SILArgument {
274274
/// If visitor returns false, iteration is stopped and we return false.
275275
bool visitIncomingPhiOperands(function_ref<bool(Operand *)> visitor) const;
276276

277+
/// Visit incoming phi operands and the argument into which they are incoming;
278+
/// if an operand's value is itself a phi, visit that phi's operands.
279+
///
280+
/// Returns false when called on a non-phi and when the visitor returns false.
281+
bool visitTransitiveIncomingPhiOperands(
282+
function_ref<bool(SILPhiArgument *, Operand *)> visitor);
283+
277284
/// Returns true if we were able to find a single terminator operand value for
278285
/// each predecessor of this arguments basic block. The found values are
279286
/// stored in OutArray.

lib/SIL/IR/SILArgument.cpp

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,13 @@
1010
//
1111
//===----------------------------------------------------------------------===//
1212

13-
#include "llvm/ADT/STLExtras.h"
14-
#include "swift/SIL/SILBasicBlock.h"
1513
#include "swift/SIL/SILArgument.h"
14+
#include "swift/Basic/GraphNodeWorklist.h"
15+
#include "swift/SIL/SILBasicBlock.h"
1616
#include "swift/SIL/SILFunction.h"
1717
#include "swift/SIL/SILInstruction.h"
1818
#include "swift/SIL/SILModule.h"
19+
#include "llvm/ADT/STLExtras.h"
1920

2021
using namespace swift;
2122

@@ -226,6 +227,31 @@ bool SILPhiArgument::getIncomingPhiValues(
226227
return true;
227228
}
228229

230+
bool SILPhiArgument::visitTransitiveIncomingPhiOperands(
231+
function_ref<bool(SILPhiArgument *, Operand *)> visitor) {
232+
if (!isPhi())
233+
return false;
234+
235+
GraphNodeWorklist<SILPhiArgument *, 4> worklist;
236+
worklist.initialize(this);
237+
238+
while (auto *argument = worklist.pop()) {
239+
llvm::SmallVector<Operand *> operands;
240+
argument->getIncomingPhiOperands(operands);
241+
242+
for (auto *operand : operands) {
243+
SILPhiArgument *forwarded;
244+
if ((forwarded = dyn_cast<SILPhiArgument>(operand->get())) &&
245+
forwarded->isPhi()) {
246+
worklist.insert(forwarded);
247+
}
248+
if (!visitor(argument, operand))
249+
return false;
250+
}
251+
}
252+
return true;
253+
}
254+
229255
static SILValue
230256
getSingleTerminatorOperandForPred(const SILBasicBlock *parentBlock,
231257
const SILBasicBlock *predBlock,

lib/SIL/Utils/OwnershipUtils.cpp

Lines changed: 217 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1586,6 +1586,223 @@ void swift::findTransitiveReborrowBaseValuePairs(
15861586
}
15871587
}
15881588

1589+
/// Visit the phis in the same block as \p phi which are reborrows of a borrow
1590+
/// of one of the values reaching \p phi.
1591+
///
1592+
/// If the visitor returns false, stops visiting and returns false. Otherwise,
1593+
/// returns true.
1594+
///
1595+
///
1596+
/// When an owned value is passed as a phi argument, it is consumed. So any
1597+
/// open scope borrowing that owned value must be ended no later than in that
1598+
/// branch instruction. Either such a borrow scope is ended beforehand
1599+
/// %lifetime = begin_borrow %value
1600+
/// ...
1601+
/// end_borrow %lifetime <-- borrow scope ended here
1602+
/// br block(%value) <-- before consume
1603+
/// or the borrow scope is ended in the same instruction as the owned value is
1604+
/// consumed
1605+
/// %lifetime = begin_borrow %value
1606+
/// ...
1607+
/// end_borrow %lifetime
1608+
/// br block(%value, %lifetime) <-- borrow scope ended here
1609+
/// <-- in same instruction as consume
1610+
/// In particular, the following is invalid
1611+
/// %lifetime = begin_borrow %value
1612+
/// ...
1613+
/// br block(%value)
1614+
/// block(%value_2 : @owned):
1615+
/// end_borrow %lifetime
1616+
/// destroy_value %value_2
1617+
/// because %lifetime was guaranteed by %value but value is consumed at
1618+
/// `br two`.
1619+
///
1620+
/// Similarly, when a guaranteed value is passed as a phi argument, its borrow
1621+
/// scope ends and a new borrow scope is begun. And so any open nested borrow
1622+
/// of the original outer borrow must be ended no later than in that branch
1623+
/// instruction.
1624+
///
1625+
///
1626+
/// Given an phi argument
1627+
/// block(..., %value : @owned, ...)
1628+
/// this function finds the adjacent reborrow phis
1629+
/// block(..., %lifetime : @guaranteed, ..., %value : @owned, ...)
1630+
/// ^^^^^^^^^^^^^^^^^^^^^^^
1631+
/// one of whose reaching values is a borrow of a reaching value of %value.
1632+
///
1633+
/// Finding these is more complicated than merely looking for guaranteed
1634+
/// operands adjacent to the incoming operands to phi and which are borrows of
1635+
/// the value consumed there. The reason is that they might not be borrows of
1636+
/// that incoming value _directly_ but rather reborrows of some other reborrow
1637+
/// if the incoming value is itself a phi argument:
1638+
/// %lifetime = begin_borrow %value
1639+
/// br one(%value, %lifetime)
1640+
/// one(%value_1 : @owned, %lifetime_1 : @guaranteed)
1641+
/// br two(%value_1, %lifetime_1)
1642+
/// two(%value_2 : @owned, %lifetime_2 : @guaranteed)
1643+
/// end_borrow %lifetime_2
1644+
/// destroy_value %value_2
1645+
///
1646+
/// When called with %value_2, \p visitor is invoked with both:
1647+
/// two(%value_2 : @owned, %lifetime_2 : @guaranteed)
1648+
/// ^^^^^^^^^^^^^^^^^^^^^^^^^
1649+
bool swift::visitAdjacentReborrowsOfPhi(
1650+
SILPhiArgument *phi, function_ref<bool(SILPhiArgument *)> visitor) {
1651+
assert(phi->isPhi());
1652+
1653+
// First, collect all the values that reach \p phi, that is:
1654+
// - operands to the phi
1655+
// - operands to phis which are operands to the phi
1656+
// - and so forth.
1657+
SmallPtrSet<SILValue, 8> reachingValues;
1658+
// At the same time, record all the phis in \p phi's phi web: the phis which
1659+
// are transitively operands to \p phi. This is the subset of \p
1660+
// reachingValues that are phis.
1661+
SmallVector<SILPhiArgument *, 4> phis;
1662+
phi->visitTransitiveIncomingPhiOperands(
1663+
[&](auto *phi, auto *operand) -> bool {
1664+
phis.push_back(phi);
1665+
reachingValues.insert(phi);
1666+
reachingValues.insert(operand->get());
1667+
return true;
1668+
});
1669+
1670+
// Second, find all the guaranteed phis one of whose operands _could_ (by
1671+
// dint of being adjacent to a phi in the phi web with the appropriate
1672+
// ownership and type) be a reborrow of a reaching value of \p phi.
1673+
SmallVector<SILPhiArgument *, 4> candidates;
1674+
for (auto *phi : phis) {
1675+
SILBasicBlock *block = phi->getParentBlock();
1676+
for (auto *uncastAdjacent : block->getArguments()) {
1677+
auto *adjacent = cast<SILPhiArgument>(uncastAdjacent);
1678+
if (adjacent == phi)
1679+
continue;
1680+
if (adjacent->getType() != phi->getType())
1681+
continue;
1682+
if (adjacent->getOwnershipKind() != OwnershipKind::Guaranteed)
1683+
continue;
1684+
candidates.push_back(adjacent);
1685+
}
1686+
}
1687+
1688+
// Finally, look through \p candidates to find those one of whose incoming
1689+
// operands either
1690+
// (1) borrow one of reaching values of \p phi
1691+
// or (2) is itself a guaranteed phi which does so.
1692+
// Because we may discover a reborrow R1 of type (1) after visiting another
1693+
// R2 of type (2) which reborrows R1, we need to iterate to a fixed point.
1694+
//
1695+
// Record all the phis that we see which are borrows or reborrows of a
1696+
// reaching value \p so that we can check for case (2) above.
1697+
//
1698+
// Visit those phis which are both reborrows of a reaching value AND are in
1699+
// the same block as \phi.
1700+
//
1701+
// For example, given
1702+
//
1703+
// %lifetime = begin_borrow %value
1704+
// br one(%value, %lifetime)
1705+
// one(%value_1 : @owned, %lifetime_1 : @guaranteed)
1706+
// br two(%value_1, %lifetime_1)
1707+
// two(%value_2 : @owned, %lifetime_2 : @guaranteed)
1708+
// end_borrow %lifetime_2
1709+
// destroy_value %value_2
1710+
//
1711+
// when visiting the reborrow phis adjacent to %value_2, The following steps
1712+
// would be taken:
1713+
//
1714+
// (1) Look at the first candidate:
1715+
// two(%value_2 : @owned, %lifetime_2 : @guaranteed)
1716+
// ^^^^^^^^^^^^^^^^^^^^^^^^^
1717+
// but see that its one incoming value
1718+
// br two(%value_1, %lifetime_1)
1719+
// ^^^^^^^^^^^
1720+
// although a phi argument itself, is not known (yet!) to be a reborrow phi.
1721+
// So the first candidate is NOT (yet!) added to reborrowPhis.
1722+
//
1723+
// (2) Look at the second candidate:
1724+
// one(%value_1 : @owned, %lifetime_1 : @guaranteed)
1725+
// ^^^^^^^^^^^^^^^^^^^^^^^^^
1726+
// and see that one of its incoming values
1727+
// br one(%value, %lifetime)
1728+
// ^^^^^^^^^
1729+
// is a borrow
1730+
// %lifetime = begin_borrow %value
1731+
// of %value, one of the values reaching %value_2.
1732+
// So the second candidate IS added to reborrowPhis.
1733+
// AND changed is set to true, so we will repeat the outer loop.
1734+
// But this candidate is not adjacent to our phi %value_2, so it is not
1735+
// visited.
1736+
//
1737+
// (4.5) Changed is true: repeat the outer loop. Set changed to false.
1738+
//
1739+
// (3) Look at the first candidate:
1740+
// two(%value_2 : @owned, %lifetime_2 : @guaranteed)
1741+
// ^^^^^^^^^^^^^^^^^^^^^^^^^
1742+
// and see that one of its incoming values
1743+
// br two(%value_1, %lifetime_1)
1744+
// ^^^^^^^^^^^
1745+
// is itself a phi
1746+
// one(%value_1 : @owned, %lifetime_1 : @guaranteed)
1747+
// ^^^^^^^^^^^^^^^^^^^^^^^^^
1748+
// which was added to reborrowPhis in (2).
1749+
// So the first candidate IS added to reborrowPhis.
1750+
// AND changed is set to true.
1751+
// ALSO, see that the first candidate IS adjacent to our phi %value_2, so our
1752+
// visitor is invoked with the first candidate.
1753+
//
1754+
// (4) Look at the second candidate.
1755+
// See that it is already a member of reborrowPhis.
1756+
//
1757+
// (4.5) Changed is true: repeat the outer loop. Set changed to false.
1758+
//
1759+
// (5) Look at the first candidate.
1760+
// See that it is already a member of reborrowPhis.
1761+
//
1762+
// (6) Look at the second candidate.
1763+
// See that it is already a member of reborrowPhis.
1764+
//
1765+
// (6.5) Changed is false: exit the outer loop.
1766+
bool changed = false;
1767+
SmallSetVector<SILPhiArgument *, 4> reborrowPhis;
1768+
do {
1769+
changed = false;
1770+
for (auto *candidate : candidates) {
1771+
if (reborrowPhis.contains(candidate))
1772+
continue;
1773+
auto success = candidate->visitIncomingPhiOperands([&](auto *operand) {
1774+
// If the value being reborrowed is itself a reborrow of a value
1775+
// reaching \p phi, then visit it.
1776+
SILPhiArgument *forwarded;
1777+
if ((forwarded = dyn_cast<SILPhiArgument>(operand->get()))) {
1778+
if (!reborrowPhis.contains(forwarded))
1779+
return true;
1780+
changed = true;
1781+
reborrowPhis.insert(candidate);
1782+
if (candidate->getParentBlock() == phi->getParentBlock())
1783+
return visitor(candidate);
1784+
return true;
1785+
}
1786+
BeginBorrowInst *bbi;
1787+
if (!(bbi = dyn_cast<BeginBorrowInst>(operand->get())))
1788+
return true;
1789+
auto borrowee = bbi->getOperand();
1790+
if (!reachingValues.contains(borrowee))
1791+
return true;
1792+
changed = true;
1793+
reborrowPhis.insert(candidate);
1794+
if (candidate->getParentBlock() == phi->getParentBlock())
1795+
return visitor(candidate);
1796+
return true;
1797+
});
1798+
if (!success)
1799+
return false;
1800+
}
1801+
} while (changed);
1802+
1803+
return true;
1804+
}
1805+
15891806
void swift::visitTransitiveEndBorrows(
15901807
SILValue value,
15911808
function_ref<void(EndBorrowInst *)> visitEndBorrow) {

lib/SILOptimizer/Utils/CanonicalOSSALifetime.cpp

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,13 @@ void swift::copyLiveUse(Operand *use, InstModCallbacks &instModCallbacks) {
114114
bool CanonicalizeOSSALifetime::computeCanonicalLiveness() {
115115
defUseWorklist.initialize(currentDef);
116116
while (SILValue value = defUseWorklist.pop()) {
117+
SILPhiArgument *arg;
118+
if ((arg = dyn_cast<SILPhiArgument>(value)) && arg->isPhi()) {
119+
visitAdjacentReborrowsOfPhi(arg, [&](SILPhiArgument *reborrow) {
120+
defUseWorklist.insert(reborrow);
121+
return true;
122+
});
123+
}
117124
for (Operand *use : value->getUses()) {
118125
auto *user = use->getUser();
119126

@@ -168,8 +175,33 @@ bool CanonicalizeOSSALifetime::computeCanonicalLiveness() {
168175
case OperandOwnership::InteriorPointer:
169176
case OperandOwnership::ForwardingBorrow:
170177
case OperandOwnership::EndBorrow:
178+
// Guaranteed values are considered uses of the value when the value is
179+
// an owned phi and the guaranteed values are adjacent reborrow phis or
180+
// reborrow of such.
181+
liveness.updateForUse(user, /*lifetimeEnding*/ false);
182+
break;
171183
case OperandOwnership::Reborrow:
172-
llvm_unreachable("operand kind cannot take an owned value");
184+
BranchInst *branch;
185+
if (!(branch = dyn_cast<BranchInst>(user))) {
186+
// Non-phi reborrows (tuples, etc) never end the lifetime of the owned
187+
// value.
188+
liveness.updateForUse(user, /*lifetimeEnding*/ false);
189+
defUseWorklist.insert(cast<SingleValueInstruction>(user));
190+
break;
191+
}
192+
if (is_contained(user->getOperandValues(), currentDef)) {
193+
// An adjacent phi consumes the value being reborrowed. Although this
194+
// use doesn't end the lifetime, this user does.
195+
liveness.updateForUse(user, /*lifetimeEnding*/ true);
196+
break;
197+
}
198+
// No adjacent phi consumes the value. This use is not lifetime ending.
199+
liveness.updateForUse(user, /*lifetimeEnding*/ false);
200+
// This branch reborrows a guaranteed phi whose lifetime is dependent on
201+
// currentDef. Uses of the reborrowing phi extend liveness.
202+
auto *reborrow = branch->getArgForOperand(use);
203+
defUseWorklist.insert(reborrow);
204+
break;
173205
}
174206
}
175207
}

0 commit comments

Comments
 (0)