Skip to content

Commit 3952423

Browse files
committed
SILOptimizer: fix a bug in the TempRValue optimization which causes a miscompile.
The problematic scenario is that a function receives an @in_guaranteed and @inout parameter where one is a copy of the other value. For example, when appending a container to itself. In this case the optimization removed the copy_addr, resulting in passing the same stack location to both parameters. rdar://problem/47632890
1 parent c19dfb5 commit 3952423

File tree

3 files changed

+77
-10
lines changed

3 files changed

+77
-10
lines changed

include/swift/SIL/SILArgumentConvention.h

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,24 @@ struct SILArgumentConvention {
8787
return Value <= SILArgumentConvention::Indirect_Out;
8888
}
8989

90+
bool isInoutConvention() const {
91+
switch (Value) {
92+
case SILArgumentConvention::Indirect_Inout:
93+
case SILArgumentConvention::Indirect_InoutAliasable:
94+
return true;
95+
case SILArgumentConvention::Indirect_In_Guaranteed:
96+
case SILArgumentConvention::Indirect_In:
97+
case SILArgumentConvention::Indirect_In_Constant:
98+
case SILArgumentConvention::Indirect_Out:
99+
case SILArgumentConvention::Direct_Unowned:
100+
case SILArgumentConvention::Direct_Owned:
101+
case SILArgumentConvention::Direct_Deallocating:
102+
case SILArgumentConvention::Direct_Guaranteed:
103+
return false;
104+
}
105+
llvm_unreachable("covered switch isn't covered?!");
106+
}
107+
90108
bool isOwnedConvention() const {
91109
switch (Value) {
92110
case SILArgumentConvention::Indirect_In:

lib/SILOptimizer/Transforms/CopyForwarding.cpp

Lines changed: 32 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1555,8 +1555,9 @@ class CopyForwardingPass : public SILFunctionTransform
15551555
class TempRValueOptPass : public SILFunctionTransform {
15561556
AliasAnalysis *AA = nullptr;
15571557

1558-
static bool collectLoads(Operand *UserOp, SILInstruction *UserInst,
1558+
bool collectLoads(Operand *UserOp, SILInstruction *UserInst,
15591559
SingleValueInstruction *Addr,
1560+
SILValue srcObject,
15601561
llvm::SmallPtrSetImpl<SILInstruction *> &LoadInsts);
15611562

15621563
bool checkNoSourceModification(CopyAddrInst *copyInst,
@@ -1615,6 +1616,7 @@ void TempRValueOptPass::run() {
16151616
/// reaching a load or returning false.
16161617
bool TempRValueOptPass::collectLoads(
16171618
Operand *userOp, SILInstruction *user, SingleValueInstruction *address,
1619+
SILValue srcObject,
16181620
llvm::SmallPtrSetImpl<SILInstruction *> &loadInsts) {
16191621
// All normal uses (loads) must be in the initialization block.
16201622
// (The destroy and dealloc are commonly in a different block though.)
@@ -1639,22 +1641,42 @@ bool TempRValueOptPass::collectLoads(
16391641

16401642
case SILInstructionKind::ApplyInst: {
16411643
ApplySite apply(user);
1644+
1645+
// Check if the function can just read from userOp.
16421646
auto Convention = apply.getArgumentConvention(*userOp);
1643-
if (Convention.isGuaranteedConvention()) {
1644-
loadInsts.insert(user);
1645-
return true;
1647+
if (!Convention.isGuaranteedConvention()) {
1648+
LLVM_DEBUG(llvm::dbgs() << " Temp consuming use may write/destroy "
1649+
"its source" << *user);
1650+
return false;
16461651
}
1647-
LLVM_DEBUG(llvm::dbgs() << " Temp consuming use may write/destroy "
1648-
"its source"
1649-
<< *user);
1650-
return false;
1652+
1653+
// Check if there is another function argument, which is inout which might
1654+
// modify the source of the copy_addr.
1655+
// If we would remove the copy_addr in this case, it would result in an
1656+
// exclusivity violation.
1657+
auto calleeConv = apply.getSubstCalleeConv();
1658+
unsigned calleeArgIdx = apply.getCalleeArgIndexOfFirstAppliedArg();
1659+
for (Operand &operand : apply.getArgumentOperands()) {
1660+
auto argConv = calleeConv.getSILArgumentConvention(calleeArgIdx);
1661+
if (argConv.isInoutConvention()) {
1662+
if (!AA->isNoAlias(operand.get(), srcObject)) {
1663+
return false;
1664+
}
1665+
}
1666+
++calleeArgIdx;
1667+
}
1668+
1669+
// Everything is okay with the function call. Register it as a "load".
1670+
loadInsts.insert(user);
1671+
return true;
16511672
}
16521673
case SILInstructionKind::StructElementAddrInst:
16531674
case SILInstructionKind::TupleElementAddrInst: {
16541675
// Transitively look through projections on stack addresses.
16551676
auto proj = cast<SingleValueInstruction>(user);
16561677
for (auto *projUseOper : proj->getUses()) {
1657-
if (!collectLoads(projUseOper, projUseOper->getUser(), proj, loadInsts))
1678+
if (!collectLoads(projUseOper, projUseOper->getUser(), proj, srcObject,
1679+
loadInsts))
16581680
return false;
16591681
}
16601682
return true;
@@ -1744,7 +1766,7 @@ bool TempRValueOptPass::tryOptimizeCopyIntoTemp(CopyAddrInst *copyInst) {
17441766
if (isa<DestroyAddrInst>(user) || isa<DeallocStackInst>(user))
17451767
continue;
17461768

1747-
if (!collectLoads(useOper, user, tempObj, loadInsts))
1769+
if (!collectLoads(useOper, user, tempObj, copyInst->getSrc(), loadInsts))
17481770
return false;
17491771
}
17501772

test/SILOptimizer/temp_rvalue_opt.sil

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -444,3 +444,30 @@ bb0(%0 : $*Klass):
444444
%9999 = tuple()
445445
return %9999 : $()
446446
}
447+
448+
sil @$createKlass : $@convention(thin) () -> @out Klass
449+
sil @$appendKlass : $@convention(method) (@in_guaranteed Klass, @inout Klass) -> ()
450+
451+
// CHECK-LABEL: sil @$overlapping_lifetime_in_function_all : $@convention(thin) () -> @out Klass {
452+
// CHECK: [[S1:%.*]] = alloc_stack $Klass
453+
// CHECK: [[S2:%.*]] = alloc_stack $Klass
454+
// CHECK: copy_addr [[S1]] to [initialization] [[S2]]
455+
// CHECK: apply {{%.*}}([[S2]], [[S1]])
456+
// CHECK: }
457+
sil @$overlapping_lifetime_in_function_all : $@convention(thin) () -> @out Klass {
458+
bb0(%0 : $*Klass):
459+
%1 = alloc_stack $Klass
460+
%2 = function_ref @$createKlass : $@convention(thin) () -> @out Klass
461+
%3 = apply %2(%1) : $@convention(thin) () -> @out Klass
462+
%4 = alloc_stack $Klass
463+
copy_addr %1 to [initialization] %4 : $*Klass
464+
%6 = function_ref @$appendKlass : $@convention(method) (@in_guaranteed Klass, @inout Klass) -> ()
465+
%7 = apply %6(%4, %1) : $@convention(method) (@in_guaranteed Klass, @inout Klass) -> ()
466+
destroy_addr %4 : $*Klass
467+
dealloc_stack %4 : $*Klass
468+
copy_addr [take] %1 to [initialization] %0 : $*Klass
469+
dealloc_stack %1 : $*Klass
470+
%12 = tuple ()
471+
return %12 : $()
472+
}
473+

0 commit comments

Comments
 (0)