Skip to content

Commit 9fe4383

Browse files
Merge pull request #83907 from nate-chandler/rdar158149082
[AllocBoxToStack] Don't destroy in dead-ends.
2 parents 44ba366 + a241cf5 commit 9fe4383

File tree

14 files changed

+519
-40
lines changed

14 files changed

+519
-40
lines changed

SwiftCompilerSources/Sources/Optimizer/FunctionPasses/AllocBoxToStack.swift

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -362,9 +362,34 @@ private func createAllocStack(for allocBox: AllocBoxInst, flags: Flags, _ contex
362362
for destroy in getFinalDestroys(of: allocBox, context) {
363363
let loc = allocBox.location.asCleanup.withScope(of: destroy.location)
364364
Builder.insert(after: destroy, location: loc, context) { builder in
365-
if !unboxedType.isTrivial(in: allocBox.parentFunction), !(destroy is DeallocBoxInst) {
365+
if !(destroy is DeallocBoxInst),
366+
context.deadEndBlocks.isDeadEnd(destroy.parentBlock),
367+
!isInLoop(block: destroy.parentBlock, context) {
368+
// "Last" releases in dead-end regions may not actually destroy the box
369+
// and consequently may not actually release the stored value. That's
370+
// because values (including boxes) may be leaked along paths into
371+
// dead-end regions. Thus it is invalid to lower such final releases of
372+
// the box to destroy_addr's/dealloc_box's of the stack-promoted storage.
373+
//
374+
// There is one exception: if the alloc_box is in a dead-end loop. In
375+
// that case SIL invariants require that the final releases actually
376+
// destroy the box; otherwise, a box would leak once per loop. To check
377+
// for this, it is sufficient check that the LastRelease is in a dead-end
378+
// loop: if the alloc_box is not in that loop, then the entire loop is in
379+
// the live range, so no release within the loop would be a "final
380+
// release".
381+
//
382+
// None of this applies to dealloc_box instructions which always destroy
383+
// the box.
384+
return
385+
}
386+
if !unboxedType.isTrivial(in: allocBox.parentFunction), !(destroy is DeallocBoxInst) {
366387
builder.createDestroyAddr(address: stackLocation)
367388
}
389+
if let dbi = destroy as? DeallocBoxInst, dbi.isDeadEnd {
390+
// Don't bother to create dealloc_stack instructions in dead-ends.
391+
return
392+
}
368393
builder.createDeallocStack(asi)
369394
}
370395
}

SwiftCompilerSources/Sources/Optimizer/FunctionPasses/StackPromotion.swift

Lines changed: 0 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -330,17 +330,3 @@ private extension BasicBlockRange {
330330
exits.contains { !deadEndBlocks.isDeadEnd($0) && !$0.hasSinglePredecessor }
331331
}
332332
}
333-
334-
private func isInLoop(block startBlock: BasicBlock, _ context: FunctionPassContext) -> Bool {
335-
var worklist = BasicBlockWorklist(context)
336-
defer { worklist.deinitialize() }
337-
338-
worklist.pushIfNotVisited(contentsOf: startBlock.successors)
339-
while let block = worklist.pop() {
340-
if block == startBlock {
341-
return true
342-
}
343-
worklist.pushIfNotVisited(contentsOf: block.successors)
344-
}
345-
return false
346-
}

SwiftCompilerSources/Sources/Optimizer/Utilities/OptUtils.swift

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1012,3 +1012,17 @@ func eraseIfDead(functions: [Function], _ context: ModulePassContext) {
10121012
toDelete = remaining
10131013
}
10141014
}
1015+
1016+
func isInLoop(block startBlock: BasicBlock, _ context: FunctionPassContext) -> Bool {
1017+
var worklist = BasicBlockWorklist(context)
1018+
defer { worklist.deinitialize() }
1019+
1020+
worklist.pushIfNotVisited(contentsOf: startBlock.successors)
1021+
while let block = worklist.pop() {
1022+
if block == startBlock {
1023+
return true
1024+
}
1025+
worklist.pushIfNotVisited(contentsOf: block.successors)
1026+
}
1027+
return false
1028+
}

SwiftCompilerSources/Sources/SIL/Instruction.swift

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -699,7 +699,9 @@ final public class DeallocRefInst : Instruction, UnaryInstruction, Deallocation
699699

700700
final public class DeallocPartialRefInst : Instruction, Deallocation {}
701701

702-
final public class DeallocBoxInst : Instruction, UnaryInstruction, Deallocation {}
702+
final public class DeallocBoxInst : Instruction, UnaryInstruction, Deallocation {
703+
public var isDeadEnd: Bool { bridged.DeallocBoxInst_isDeadEnd() }
704+
}
703705

704706
final public class DeallocExistentialBoxInst : Instruction, UnaryInstruction, Deallocation {}
705707

include/swift/SIL/SILBridging.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -828,6 +828,7 @@ struct BridgedInstruction {
828828
BRIDGED_INLINE bool CopyAddrInst_isInitializationOfDest() const;
829829
BRIDGED_INLINE void CopyAddrInst_setIsTakeOfSrc(bool isTakeOfSrc) const;
830830
BRIDGED_INLINE void CopyAddrInst_setIsInitializationOfDest(bool isInitializationOfDest) const;
831+
BRIDGED_INLINE bool DeallocBoxInst_isDeadEnd() const;
831832
BRIDGED_INLINE bool ExplicitCopyAddrInst_isTakeOfSrc() const;
832833
BRIDGED_INLINE bool ExplicitCopyAddrInst_isInitializationOfDest() const;
833834
BRIDGED_INLINE SwiftInt MarkUninitializedInst_getKind() const;

include/swift/SIL/SILBridgingImpl.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1511,6 +1511,10 @@ void BridgedInstruction::CopyAddrInst_setIsInitializationOfDest(bool isInitializ
15111511
isInitializationOfDest ? swift::IsInitialization : swift::IsNotInitialization);
15121512
}
15131513

1514+
bool BridgedInstruction::DeallocBoxInst_isDeadEnd() const {
1515+
return getAs<swift::DeallocBoxInst>()->isDeadEnd();
1516+
}
1517+
15141518
bool BridgedInstruction::ExplicitCopyAddrInst_isTakeOfSrc() const {
15151519
return getAs<swift::ExplicitCopyAddrInst>()->isTakeOfSrc();
15161520
}

include/swift/SIL/SILFunction.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1697,7 +1697,8 @@ class SILFunction
16971697
}
16981698

16991699
/// Verifies the lifetime of memory locations in the function.
1700-
void verifyMemoryLifetime(CalleeCache *calleeCache);
1700+
void verifyMemoryLifetime(CalleeCache *calleeCache,
1701+
DeadEndBlocks *deadEndBlocks);
17011702

17021703
/// Verifies ownership of the function.
17031704
/// Since we don't have complete lifetimes everywhere, computes DeadEndBlocks

lib/SIL/Verifier/MemoryLifetimeVerifier.cpp

Lines changed: 20 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,14 @@
1212

1313
#define DEBUG_TYPE "sil-memory-lifetime-verifier"
1414
#include "swift/Basic/Assertions.h"
15-
#include "swift/SIL/MemoryLocations.h"
15+
#include "swift/SIL/ApplySite.h"
16+
#include "swift/SIL/BasicBlockDatastructures.h"
17+
#include "swift/SIL/BasicBlockUtils.h"
1618
#include "swift/SIL/BitDataflow.h"
1719
#include "swift/SIL/CalleeCache.h"
20+
#include "swift/SIL/MemoryLocations.h"
1821
#include "swift/SIL/SILBasicBlock.h"
1922
#include "swift/SIL/SILFunction.h"
20-
#include "swift/SIL/ApplySite.h"
21-
#include "swift/SIL/BasicBlockDatastructures.h"
2223
#include "llvm/Support/CommandLine.h"
2324

2425
using namespace swift;
@@ -43,6 +44,7 @@ class MemoryLifetimeVerifier {
4344

4445
SILFunction *function;
4546
CalleeCache *calleeCache;
47+
DeadEndBlocks *deadEndBlocks;
4648
MemoryLocations locations;
4749

4850
/// alloc_stack memory locations which are used for store_borrow.
@@ -140,11 +142,12 @@ class MemoryLifetimeVerifier {
140142
}
141143

142144
public:
143-
MemoryLifetimeVerifier(SILFunction *function, CalleeCache *calleeCache) :
144-
function(function),
145-
calleeCache(calleeCache),
146-
locations(/*handleNonTrivialProjections*/ true,
147-
/*handleTrivialLocations*/ true) {}
145+
MemoryLifetimeVerifier(SILFunction *function, CalleeCache *calleeCache,
146+
DeadEndBlocks *deadEndBlocks)
147+
: function(function), calleeCache(calleeCache),
148+
deadEndBlocks(deadEndBlocks),
149+
locations(/*handleNonTrivialProjections*/ true,
150+
/*handleTrivialLocations*/ true) {}
148151

149152
/// The main entry point to verify the lifetime of all memory locations in
150153
/// the function.
@@ -884,7 +887,12 @@ void MemoryLifetimeVerifier::checkBlock(SILBasicBlock *block, Bits &bits) {
884887
}
885888
case SILInstructionKind::DeallocStackInst: {
886889
SILValue opVal = cast<DeallocStackInst>(&I)->getOperand();
887-
requireBitsClear(bits & nonTrivialLocations, opVal, &I);
890+
if (!deadEndBlocks->isDeadEnd(I.getParent())) {
891+
// TODO: rdar://159311784: Maybe at some point the invariant will be
892+
// enforced that values stored into addresses
893+
// don't leak in dead-ends.
894+
requireBitsClear(bits & nonTrivialLocations, opVal, &I);
895+
}
888896
// Needed to clear any bits of trivial locations (which are not required
889897
// to be zero).
890898
locations.clearBits(bits, opVal);
@@ -974,7 +982,8 @@ void MemoryLifetimeVerifier::verify() {
974982

975983
} // anonymous namespace
976984

977-
void SILFunction::verifyMemoryLifetime(CalleeCache *calleeCache) {
978-
MemoryLifetimeVerifier verifier(this, calleeCache);
985+
void SILFunction::verifyMemoryLifetime(CalleeCache *calleeCache,
986+
DeadEndBlocks *deadEndBlocks) {
987+
MemoryLifetimeVerifier verifier(this, calleeCache, deadEndBlocks);
979988
verifier.verify();
980989
}

lib/SIL/Verifier/SILVerifier.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7385,7 +7385,7 @@ class SILVerifier : public SILVerifierBase<SILVerifier> {
73857385

73867386
if (F->hasOwnership() && F->shouldVerifyOwnership() &&
73877387
!mod.getASTContext().hadError()) {
7388-
F->verifyMemoryLifetime(calleeCache);
7388+
F->verifyMemoryLifetime(calleeCache, &getDeadEndBlocks());
73897389
}
73907390
}
73917391

lib/SILOptimizer/Transforms/AllocBoxToStack.cpp

Lines changed: 35 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@
2525
#include "swift/SIL/SILArgument.h"
2626
#include "swift/SIL/SILBuilder.h"
2727
#include "swift/SIL/SILCloner.h"
28+
#include "swift/SILOptimizer/Analysis/DeadEndBlocksAnalysis.h"
29+
#include "swift/SILOptimizer/Analysis/LoopAnalysis.h"
2830
#include "swift/SILOptimizer/PassManager/Passes.h"
2931
#include "swift/SILOptimizer/PassManager/Transforms.h"
3032
#include "swift/SILOptimizer/Utils/InstOptUtils.h"
@@ -601,7 +603,9 @@ static void hoistMarkUnresolvedNonCopyableValueInsts(
601603

602604
/// rewriteAllocBoxAsAllocStack - Replace uses of the alloc_box with a
603605
/// new alloc_stack, but do not delete the alloc_box yet.
604-
static bool rewriteAllocBoxAsAllocStack(AllocBoxInst *ABI) {
606+
static bool rewriteAllocBoxAsAllocStack(AllocBoxInst *ABI,
607+
DeadEndBlocksAnalysis &deba,
608+
SILLoopAnalysis &la) {
605609
LLVM_DEBUG(llvm::dbgs() << "*** Promoting alloc_box to stack: " << *ABI);
606610

607611
SILValue HeapBox = ABI;
@@ -693,9 +697,31 @@ static bool rewriteAllocBoxAsAllocStack(AllocBoxInst *ABI) {
693697
ABI->getBoxType(), ABI->getModule().Types, 0));
694698
auto Loc = CleanupLocation(ABI->getLoc());
695699

700+
auto *deb = deba.get(ABI->getFunction());
696701
for (auto LastRelease : FinalReleases) {
702+
auto *dbi = dyn_cast<DeallocBoxInst>(LastRelease);
703+
if (!dbi && deb->isDeadEnd(LastRelease->getParent()) &&
704+
!la.get(ABI->getFunction())->getLoopFor(LastRelease->getParent())) {
705+
// "Last" releases in dead-end regions may not actually destroy the box
706+
// and consequently may not actually release the stored value. That's
707+
// because values (including boxes) may be leaked along paths into
708+
// dead-end regions. Thus it is invalid to lower such final releases of
709+
// the box to destroy_addr's/dealloc_box's of the stack-promoted storage.
710+
//
711+
// There is one exception: if the alloc_box is in a dead-end loop. In
712+
// that case SIL invariants require that the final releases actually
713+
// destroy the box; otherwise, a box would leak once per loop. To check
714+
// for this, it is sufficient check that the LastRelease is in a dead-end
715+
// loop: if the alloc_box is not in that loop, then the entire loop is in
716+
// the live range, so no release within the loop would be a "final
717+
// release".
718+
//
719+
// None of this applies to dealloc_box instructions which always destroy
720+
// the box.
721+
continue;
722+
}
697723
SILBuilderWithScope Builder(LastRelease);
698-
if (!isa<DeallocBoxInst>(LastRelease)&& !Lowering.isTrivial()) {
724+
if (!dbi && !Lowering.isTrivial()) {
699725
// If we have a mark_unresolved_non_copyable_value use of our stack box,
700726
// we want to destroy that.
701727
SILValue valueToDestroy = StackBox;
@@ -709,7 +735,6 @@ static bool rewriteAllocBoxAsAllocStack(AllocBoxInst *ABI) {
709735
// instruction we found that isn't an explicit dealloc_box.
710736
Builder.emitDestroyAddrAndFold(Loc, valueToDestroy);
711737
}
712-
auto *dbi = dyn_cast<DeallocBoxInst>(LastRelease);
713738
if (dbi && dbi->isDeadEnd()) {
714739
// Don't bother to create dealloc_stack instructions in dead-ends.
715740
continue;
@@ -1265,7 +1290,9 @@ static void rewriteApplySites(AllocBoxToStackState &pass) {
12651290

12661291
/// Clone closure bodies and rewrite partial applies. Returns the number of
12671292
/// alloc_box allocations promoted.
1268-
static unsigned rewritePromotedBoxes(AllocBoxToStackState &pass) {
1293+
static unsigned rewritePromotedBoxes(AllocBoxToStackState &pass,
1294+
DeadEndBlocksAnalysis &deba,
1295+
SILLoopAnalysis &la) {
12691296
// First we'll rewrite any ApplySite that we can to remove
12701297
// the box container pointer from the operands.
12711298
rewriteApplySites(pass);
@@ -1274,7 +1301,7 @@ static unsigned rewritePromotedBoxes(AllocBoxToStackState &pass) {
12741301
auto rend = pass.Promotable.rend();
12751302
for (auto I = pass.Promotable.rbegin(); I != rend; ++I) {
12761303
auto *ABI = *I;
1277-
if (rewriteAllocBoxAsAllocStack(ABI)) {
1304+
if (rewriteAllocBoxAsAllocStack(ABI, deba, la)) {
12781305
++Count;
12791306
ABI->eraseFromParent();
12801307
}
@@ -1299,7 +1326,9 @@ class AllocBoxToStack : public SILFunctionTransform {
12991326
}
13001327

13011328
if (!pass.Promotable.empty()) {
1302-
auto Count = rewritePromotedBoxes(pass);
1329+
auto *deba = getAnalysis<DeadEndBlocksAnalysis>();
1330+
auto *la = getAnalysis<SILLoopAnalysis>();
1331+
auto Count = rewritePromotedBoxes(pass, *deba, *la);
13031332
NumStackPromoted += Count;
13041333
if (Count) {
13051334
if (StackNesting::fixNesting(getFunction()) == StackNesting::Changes::CFG)

0 commit comments

Comments
 (0)