Skip to content

Commit fe43531

Browse files
committed
[OwnershipUtils] Visit owned phi's reborrows.
The new utility, given an phi, visits all adjacent phis (i.e. arguments to the same block) which are (potentially iterated) reborrows of a value reaching the given phi.
1 parent 8c44d36 commit fe43531

File tree

2 files changed

+225
-0
lines changed

2 files changed

+225
-0
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(

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) {

0 commit comments

Comments
 (0)