Skip to content

Commit 3f28e28

Browse files
authored
Merge pull request #27893 from atrick/fix-temprvalue-destroy
2 parents fd1a2a4 + 9dc59d2 commit 3f28e28

File tree

2 files changed

+144
-2
lines changed

2 files changed

+144
-2
lines changed

lib/SILOptimizer/Transforms/CopyForwarding.cpp

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@
6969
#include "swift/SILOptimizer/PassManager/Passes.h"
7070
#include "swift/SILOptimizer/PassManager/Transforms.h"
7171
#include "swift/SILOptimizer/Utils/CFGOptUtils.h"
72+
#include "swift/SILOptimizer/Utils/ValueLifetime.h"
7273
#include "llvm/ADT/SetVector.h"
7374
#include "llvm/ADT/Statistic.h"
7475
#include "llvm/Support/CommandLine.h"
@@ -1569,6 +1570,8 @@ class TempRValueOptPass : public SILFunctionTransform {
15691570
bool checkNoSourceModification(CopyAddrInst *copyInst,
15701571
const llvm::SmallPtrSetImpl<SILInstruction *> &useInsts);
15711572

1573+
bool checkTempObjectDestroy(AllocStackInst *tempObj, CopyAddrInst *copyInst);
1574+
15721575
bool tryOptimizeCopyIntoTemp(CopyAddrInst *copyInst);
15731576

15741577
void run() override;
@@ -1620,6 +1623,17 @@ void TempRValueOptPass::run() {
16201623

16211624
/// Transitively explore all data flow uses of the given \p address until
16221625
/// 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.
16231637
bool TempRValueOptPass::collectLoads(
16241638
Operand *userOp, SILInstruction *user, SingleValueInstruction *address,
16251639
SILValue srcObject,
@@ -1768,6 +1782,73 @@ bool TempRValueOptPass::checkNoSourceModification(CopyAddrInst *copyInst,
17681782
return false;
17691783
}
17701784

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+
17711852
/// Tries to perform the temporary rvalue copy elimination for \p copyInst
17721853
bool TempRValueOptPass::tryOptimizeCopyIntoTemp(CopyAddrInst *copyInst) {
17731854
if (!copyInst->isInitializationOfDest())
@@ -1803,6 +1884,9 @@ bool TempRValueOptPass::tryOptimizeCopyIntoTemp(CopyAddrInst *copyInst) {
18031884
if (!checkNoSourceModification(copyInst, loadInsts))
18041885
return false;
18051886

1887+
if (!checkTempObjectDestroy(tempObj, copyInst))
1888+
return false;
1889+
18061890
LLVM_DEBUG(llvm::dbgs() << " Success: replace temp" << *tempObj);
18071891

18081892
// Do a "replaceAllUses" by either deleting the users or replacing them with
@@ -1833,6 +1917,9 @@ bool TempRValueOptPass::tryOptimizeCopyIntoTemp(CopyAddrInst *copyInst) {
18331917
use->set(copyInst->getSrc());
18341918
break;
18351919
}
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.
18361923
default:
18371924
use->set(copyInst->getSrc());
18381925
break;

test/SILOptimizer/temp_rvalue_opt.sil

Lines changed: 57 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -106,8 +106,11 @@ bb0(%0 : $*GS<B>, %1 : $*GS<B>):
106106

107107
// CHECK-LABEL: sil @take_from_temp
108108
// CHECK: bb0(%0 : $*B, %1 : $*GS<B>):
109-
// CHECK-NEXT: struct_element_addr
110-
// CHECK-NEXT: copy_addr [take]
109+
// CHECK-NEXT: [[STK:%.*]] = alloc_stack
110+
// CHECK-NEXT: copy_addr %1 to [initialization] [[STK]]
111+
// CHECK-NEXT: [[INNER:%.*]] = struct_element_addr
112+
// CHECK-NEXT: copy_addr [take] [[INNER]]
113+
// CHECK-NEXT: dealloc_stack
111114
// CHECK-NEXT: tuple
112115
// CHECK-NEXT: return
113116
sil @take_from_temp : $@convention(thin) <B> (@inout B, @inout GS<B>) -> () {
@@ -600,3 +603,55 @@ bb3:
600603
dealloc_stack %2 : $*Optional<P>
601604
br bb2
602605
}
606+
607+
///////////////////////////////////////////////////////////////////////////////
608+
// Test checkTempObjectDestroy
609+
// <rdar://problem/56393491> Use-after free crashing an XCTest.
610+
611+
sil @takeGuaranteedObj : $@convention(thin) (@guaranteed Builtin.NativeObject) -> ()
612+
613+
// Do not remove a copy that is released via a load (because
614+
// TempRValueOpt is an address-based optimization does not know how to
615+
// remove releases, and trying to do that would reduce to ARC
616+
// optimization).
617+
// CHECK-LABEL: sil @copyWithLoadRelease : $@convention(thin) (@in_guaranteed Builtin.NativeObject) -> () {
618+
// CHECK: bb0(%0 : $*Builtin.NativeObject):
619+
// CHECK: [[STK:%.*]] = alloc_stack $Builtin.NativeObject
620+
// CHECK: copy_addr %0 to [initialization] [[STK]] : $*Builtin.NativeObject
621+
// CHECK: [[VAL:%.*]] = load [[STK]] : $*Builtin.NativeObject
622+
// CHECK: apply %{{.*}}([[VAL]]) : $@convention(thin) (@guaranteed Builtin.NativeObject) -> ()
623+
// CHECK: release_value [[VAL]] : $Builtin.NativeObject
624+
// CHECK: dealloc_stack [[STK]] : $*Builtin.NativeObject
625+
// CHECK-LABEL: } // end sil function 'copyWithLoadRelease'
626+
sil @copyWithLoadRelease : $@convention(thin) (@in_guaranteed Builtin.NativeObject) -> () {
627+
bb0(%0 : $*Builtin.NativeObject):
628+
%stk = alloc_stack $Builtin.NativeObject
629+
copy_addr %0 to [initialization] %stk : $*Builtin.NativeObject
630+
%obj = load %stk : $*Builtin.NativeObject
631+
%f = function_ref @takeGuaranteedObj : $@convention(thin) (@guaranteed Builtin.NativeObject) -> ()
632+
%call = apply %f(%obj) : $@convention(thin) (@guaranteed Builtin.NativeObject) -> ()
633+
release_value %obj : $Builtin.NativeObject
634+
dealloc_stack %stk : $*Builtin.NativeObject
635+
%v = tuple ()
636+
return %v : $()
637+
}
638+
639+
// Remove a copy that is released via a load as long as it was a copy [take].
640+
// CHECK-LABEL: sil @takeWithLoadRelease : $@convention(thin) (@in Builtin.NativeObject) -> () {
641+
// CHECK: bb0(%0 : $*Builtin.NativeObject):
642+
// CHECK: [[V:%.*]] = load %0 : $*Builtin.NativeObject
643+
// CHECK: apply %{{.*}}([[V]]) : $@convention(thin) (@guaranteed Builtin.NativeObject) -> ()
644+
// CHECK: release_value [[V]] : $Builtin.NativeObject
645+
// CHECK-LABEL: } // end sil function 'takeWithLoadRelease'
646+
sil @takeWithLoadRelease : $@convention(thin) (@in Builtin.NativeObject) -> () {
647+
bb0(%0 : $*Builtin.NativeObject):
648+
%stk = alloc_stack $Builtin.NativeObject
649+
copy_addr [take] %0 to [initialization] %stk : $*Builtin.NativeObject
650+
%obj = load %stk : $*Builtin.NativeObject
651+
%f = function_ref @takeGuaranteedObj : $@convention(thin) (@guaranteed Builtin.NativeObject) -> ()
652+
%call = apply %f(%obj) : $@convention(thin) (@guaranteed Builtin.NativeObject) -> ()
653+
release_value %obj : $Builtin.NativeObject
654+
dealloc_stack %stk : $*Builtin.NativeObject
655+
%v = tuple ()
656+
return %v : $()
657+
}

0 commit comments

Comments
 (0)