@@ -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+
15891806void swift::visitTransitiveEndBorrows (
15901807 SILValue value,
15911808 function_ref<void (EndBorrowInst *)> visitEndBorrow) {
0 commit comments