|
69 | 69 | #include "swift/SILOptimizer/PassManager/Passes.h"
|
70 | 70 | #include "swift/SILOptimizer/PassManager/Transforms.h"
|
71 | 71 | #include "swift/SILOptimizer/Utils/CFGOptUtils.h"
|
| 72 | +#include "swift/SILOptimizer/Utils/ValueLifetime.h" |
72 | 73 | #include "llvm/ADT/SetVector.h"
|
73 | 74 | #include "llvm/ADT/Statistic.h"
|
74 | 75 | #include "llvm/Support/CommandLine.h"
|
@@ -1569,6 +1570,8 @@ class TempRValueOptPass : public SILFunctionTransform {
|
1569 | 1570 | bool checkNoSourceModification(CopyAddrInst *copyInst,
|
1570 | 1571 | const llvm::SmallPtrSetImpl<SILInstruction *> &useInsts);
|
1571 | 1572 |
|
| 1573 | + bool checkTempObjectDestroy(AllocStackInst *tempObj, CopyAddrInst *copyInst); |
| 1574 | + |
1572 | 1575 | bool tryOptimizeCopyIntoTemp(CopyAddrInst *copyInst);
|
1573 | 1576 |
|
1574 | 1577 | void run() override;
|
@@ -1620,6 +1623,17 @@ void TempRValueOptPass::run() {
|
1620 | 1623 |
|
1621 | 1624 | /// Transitively explore all data flow uses of the given \p address until
|
1622 | 1625 | /// reaching a load or returning false.
|
| 1626 | +/// |
| 1627 | +/// Any user opcode recognized by collectLoads must be replaced correctly later |
| 1628 | +/// during tryOptimizeCopyIntoTemp. If it is possible for any use to destroy the |
| 1629 | +/// value in \p address, then that use must be removed or made non-destructive |
| 1630 | +/// after the copy is removed and its operand is replaced. |
| 1631 | +/// |
| 1632 | +/// Warning: To preserve the original object lifetime, tryOptimizeCopyIntoTemp |
| 1633 | +/// must assume that there are no holes in lifetime of the temporary stack |
| 1634 | +/// location at \address. The temporary must be initialized by the original copy |
| 1635 | +/// and never written to again. Therefore, collectLoads disallows any operation |
| 1636 | +/// that may write to memory at \p address. |
1623 | 1637 | bool TempRValueOptPass::collectLoads(
|
1624 | 1638 | Operand *userOp, SILInstruction *user, SingleValueInstruction *address,
|
1625 | 1639 | SILValue srcObject,
|
@@ -1768,6 +1782,73 @@ bool TempRValueOptPass::checkNoSourceModification(CopyAddrInst *copyInst,
|
1768 | 1782 | return false;
|
1769 | 1783 | }
|
1770 | 1784 |
|
| 1785 | +/// Return true if the \p tempObj, which is initialized by \p copyInst, is |
| 1786 | +/// destroyed in an orthodox way. |
| 1787 | +/// |
| 1788 | +/// When tryOptimizeCopyIntoTemp replaces all of tempObj's uses, it assumes that |
| 1789 | +/// the object is initialized by the original copy and directly destroyed on all |
| 1790 | +/// paths by one of the recognized 'destroy_addr' or 'copy_addr [take]' |
| 1791 | +/// operations. This assumption must be checked. For example, in non-OSSA, |
| 1792 | +/// it is legal to destroy an in-memory object by loading the value and |
| 1793 | +/// releasing it. Rather than detecting unbalanced load releases, simply check |
| 1794 | +/// that tempObj is destroyed directly on all paths. |
| 1795 | +bool TempRValueOptPass::checkTempObjectDestroy(AllocStackInst *tempObj, |
| 1796 | + CopyAddrInst *copyInst) { |
| 1797 | + // If the original copy was a take, then replacing all uses cannot affect |
| 1798 | + // the lifetime. |
| 1799 | + if (copyInst->isTakeOfSrc()) |
| 1800 | + return true; |
| 1801 | + |
| 1802 | + // ValueLifetimeAnalysis is not normally used for address types. It does not |
| 1803 | + // reason about the lifetime of the in-memory object. However the utility can |
| 1804 | + // be abused here to check that the address is directly destroyed on all |
| 1805 | + // paths. collectLoads has already guaranteed that tempObj's lifetime has no |
| 1806 | + // holes/reinitializations. |
| 1807 | + SmallVector<SILInstruction *, 8> users; |
| 1808 | + for (auto result : tempObj->getResults()) { |
| 1809 | + for (Operand *operand : result->getUses()) { |
| 1810 | + SILInstruction *user = operand->getUser(); |
| 1811 | + if (user == copyInst) |
| 1812 | + continue; |
| 1813 | + if (isa<DeallocStackInst>(user)) |
| 1814 | + continue; |
| 1815 | + users.push_back(user); |
| 1816 | + } |
| 1817 | + } |
| 1818 | + // Find the boundary of tempObj's address lifetime, starting at copyInst. |
| 1819 | + ValueLifetimeAnalysis vla(copyInst, users); |
| 1820 | + ValueLifetimeAnalysis::Frontier tempAddressFrontier; |
| 1821 | + if (!vla.computeFrontier(tempAddressFrontier, |
| 1822 | + ValueLifetimeAnalysis::DontModifyCFG)) { |
| 1823 | + return false; |
| 1824 | + } |
| 1825 | + // Check that the lifetime boundary ends at direct destroy points. |
| 1826 | + for (SILInstruction *frontierInst : tempAddressFrontier) { |
| 1827 | + auto pos = frontierInst->getIterator(); |
| 1828 | + // If the frontier is at the head of a block, then either it is an |
| 1829 | + // unexpected lifetime exit, or the lifetime ended at a |
| 1830 | + // terminator. TempRValueOptPass does not handle either case. |
| 1831 | + if (pos == frontierInst->getParent()->begin()) |
| 1832 | + return false; |
| 1833 | + |
| 1834 | + // Look for a known destroy point as described in the funciton level |
| 1835 | + // comment. This whitelist can be expanded as more cases are handled in |
| 1836 | + // tryOptimizeCopyIntoTemp during copy replacement. |
| 1837 | + SILInstruction *lastUser = &*std::prev(pos); |
| 1838 | + if (isa<DestroyAddrInst>(lastUser)) |
| 1839 | + continue; |
| 1840 | + |
| 1841 | + if (auto *cai = dyn_cast<CopyAddrInst>(lastUser)) { |
| 1842 | + assert(cai->getSrc() == tempObj && "collectLoads checks for writes"); |
| 1843 | + assert(!copyInst->isTakeOfSrc() && "checked above"); |
| 1844 | + if (cai->isTakeOfSrc()) |
| 1845 | + continue; |
| 1846 | + } |
| 1847 | + return false; |
| 1848 | + } |
| 1849 | + return true; |
| 1850 | +} |
| 1851 | + |
1771 | 1852 | /// Tries to perform the temporary rvalue copy elimination for \p copyInst
|
1772 | 1853 | bool TempRValueOptPass::tryOptimizeCopyIntoTemp(CopyAddrInst *copyInst) {
|
1773 | 1854 | if (!copyInst->isInitializationOfDest())
|
@@ -1803,6 +1884,9 @@ bool TempRValueOptPass::tryOptimizeCopyIntoTemp(CopyAddrInst *copyInst) {
|
1803 | 1884 | if (!checkNoSourceModification(copyInst, loadInsts))
|
1804 | 1885 | return false;
|
1805 | 1886 |
|
| 1887 | + if (!checkTempObjectDestroy(tempObj, copyInst)) |
| 1888 | + return false; |
| 1889 | + |
1806 | 1890 | LLVM_DEBUG(llvm::dbgs() << " Success: replace temp" << *tempObj);
|
1807 | 1891 |
|
1808 | 1892 | // Do a "replaceAllUses" by either deleting the users or replacing them with
|
@@ -1833,6 +1917,9 @@ bool TempRValueOptPass::tryOptimizeCopyIntoTemp(CopyAddrInst *copyInst) {
|
1833 | 1917 | use->set(copyInst->getSrc());
|
1834 | 1918 | break;
|
1835 | 1919 | }
|
| 1920 | + // ASSUMPTION: no operations that may be handled by this default clause can |
| 1921 | + // destroy tempObj. This includes operations that load the value from memory |
| 1922 | + // and release it. |
1836 | 1923 | default:
|
1837 | 1924 | use->set(copyInst->getSrc());
|
1838 | 1925 | break;
|
|
0 commit comments