Skip to content

Commit f764b8b

Browse files
committed
Fix a bug in CopyForwarding. Bailout during destoy hoisting.
Once the algorithm has begun hoisting destroys globally, there's no way to cleanly bailout. The previous attempt to bailout could result in an assert or lost destroy in release mode. This is continued fall-out from changes in the previous release to upstream SILGen or mandatory passes, such as PredictableMemOps, that no longer preserve natural variable lifetimes. In this case, we end up with SIL like this before CopyForwarding: bb(%arg) %local_addr = alloc_stack store %arg to %local_addr %payload = switch_enum(%arg) retain %arg store %arg to %some_addr destroy_addr %local_addr release_value %arg We're attempting to hoist the destroy_addr to its last use, but can't because the lifetimes of the alloc_stack (%local_addr) and the value being stored on the stack (%arg) have become mixed up by an upstream pass. We actually detect this situation now in order to bail-out of destroy hoisting. Sadly, the bailout might only partially recover in the case of interesting control flow, as happens in the test case's Graph.init function. This triggers an assert, but in release mode it simply drops the destroy. Fixed <rdar://problem/43888666> [SR-8526]: Memory leak after switch in release configuration.
1 parent 4826654 commit f764b8b

File tree

2 files changed

+54
-15
lines changed

2 files changed

+54
-15
lines changed

lib/SILOptimizer/Transforms/CopyForwarding.cpp

Lines changed: 22 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1200,8 +1200,19 @@ bool CopyForwarding::hoistDestroy(SILInstruction *DestroyPoint,
12001200

12011201
// If DestroyPoint is a block terminator, we must hoist.
12021202
bool MustHoist = (DestroyPoint == BB->getTerminator());
1203+
// If we haven't seen anything significant, avoid useless hoisting.
1204+
bool ShouldHoist = MustHoist;
1205+
1206+
auto tryToInsertHoistedDestroyAfter = [&](SILInstruction *afterInst) {
1207+
if (!ShouldHoist)
1208+
return false;
1209+
LLVM_DEBUG(llvm::dbgs() << " Hoisting to Use:" << *afterInst);
1210+
SILBuilderWithScope(std::next(afterInst->getIterator()), afterInst)
1211+
.createDestroyAddr(DestroyLoc, CurrentDef);
1212+
HasChanged = true;
1213+
return true;
1214+
};
12031215

1204-
bool IsWorthHoisting = MustHoist;
12051216
auto SI = DestroyPoint->getIterator(), SE = BB->begin();
12061217
while (SI != SE) {
12071218
--SI;
@@ -1219,10 +1230,10 @@ bool CopyForwarding::hoistDestroy(SILInstruction *DestroyPoint,
12191230
// destroy_addr CurrentDef
12201231
LLVM_DEBUG(llvm::dbgs() << " Cannot hoist above stored value use:"
12211232
<< *Inst);
1222-
return false;
1233+
return tryToInsertHoistedDestroyAfter(Inst);
12231234
}
1224-
if (!IsWorthHoisting && isa<ApplyInst>(Inst))
1225-
IsWorthHoisting = true;
1235+
if (!ShouldHoist && isa<ApplyInst>(Inst))
1236+
ShouldHoist = true;
12261237
continue;
12271238
}
12281239
if (auto *CopyInst = dyn_cast<CopyAddrInst>(Inst)) {
@@ -1233,19 +1244,15 @@ bool CopyForwarding::hoistDestroy(SILInstruction *DestroyPoint,
12331244
return true;
12341245
}
12351246
}
1236-
// We reached a user of CurrentDef. If we haven't seen anything significant,
1237-
// avoid useless hoisting.
1238-
if (!IsWorthHoisting)
1239-
return false;
1240-
1241-
LLVM_DEBUG(llvm::dbgs() << " Hoisting to Use:" << *Inst);
1242-
SILBuilderWithScope(std::next(SI), Inst)
1243-
.createDestroyAddr(DestroyLoc, CurrentDef);
1244-
HasChanged = true;
1245-
return true;
1247+
return tryToInsertHoistedDestroyAfter(Inst);
12461248
}
1247-
if (!DoGlobalHoisting)
1249+
if (!DoGlobalHoisting) {
1250+
// If DoGlobalHoisting is set, then we should never mark a DeadInBlock, so
1251+
// MustHoist should be false.
1252+
assert(!MustHoist &&
1253+
"Cannot hoist above a terminator with global hoisting disabled.");
12481254
return false;
1255+
}
12491256
DeadInBlocks.insert(BB);
12501257
return true;
12511258
}

test/SILOptimizer/copyforward.sil

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -883,3 +883,35 @@ bb0(%0 : $*AClass, %1 : $AClass):
883883
%999 = tuple ()
884884
return %999 : $()
885885
}
886+
887+
// <rdar://problem/43888666> [SR-8526]: Memory leak after switch in release configuration
888+
// CHECK-LABEL: sil @testGlobalHoistToStoredValue : $@convention(thin) (@owned AClass, @inout AClass) -> () {
889+
// CHECK: bb0(%0 : $AClass, %1 : $*AClass):
890+
// CHECK-NEXT: [[LOCAL:%.*]] = alloc_stack $AClass
891+
// CHECK-NEXT: store %0 to [[LOCAL]] : $*AClass
892+
// CHECK-NEXT: copy_addr [[LOCAL]] to %1 : $*AClass
893+
// CHECK-NEXT: retain_value %0 : $AClass
894+
// CHECK-NEXT: store %0 to %1 : $*AClass
895+
// CHECK-NEXT: destroy_addr [[LOCAL]] : $*AClass
896+
// CHECK-NEXT: br bb1
897+
// CHECK: bb1:
898+
// CHECK-NEXT: dealloc_stack [[LOCAL]] : $*AClass
899+
// CHECK-NEXT: release_value %0 : $AClass
900+
// CHECK-NEXT: tuple ()
901+
// CHECK-NEXT: return
902+
// CHECK-LABEL: } // end sil function 'testGlobalHoistToStoredValue'
903+
sil @testGlobalHoistToStoredValue : $@convention(thin) (@owned AClass, @inout AClass) -> () {
904+
bb0(%obj : $AClass, %ptr : $*AClass ):
905+
%local = alloc_stack $AClass
906+
store %obj to %local : $*AClass
907+
copy_addr %local to %ptr : $*AClass
908+
retain_value %obj : $AClass
909+
store %obj to %ptr : $*AClass
910+
br bb1
911+
bb1:
912+
destroy_addr %local : $*AClass
913+
dealloc_stack %local : $*AClass
914+
release_value %obj : $AClass
915+
%v = tuple ()
916+
return %v : $()
917+
}

0 commit comments

Comments
 (0)