Merge pull request #83995 from nate-chandler/cherrypick/release/6.2.1/rdar158149082

6.2.1: [AllocBoxToStack] Don't destroy in dead-ends.
This commit is contained in:
nate-chandler
2025-08-29 16:35:54 -07:00
committed by GitHub
7 changed files with 473 additions and 25 deletions

View File

@@ -12,13 +12,14 @@
#define DEBUG_TYPE "sil-memory-lifetime-verifier"
#include "swift/Basic/Assertions.h"
#include "swift/SIL/MemoryLocations.h"
#include "swift/SIL/BitDataflow.h"
#include "swift/SIL/CalleeCache.h"
#include "swift/SIL/SILBasicBlock.h"
#include "swift/SIL/SILFunction.h"
#include "swift/SIL/ApplySite.h"
#include "swift/SIL/BasicBlockDatastructures.h"
#include "swift/SIL/BasicBlockUtils.h"
#include "swift/SIL/BitDataflow.h"
#include "swift/SIL/CalleeCache.h"
#include "swift/SIL/MemoryLocations.h"
#include "swift/SIL/SILBasicBlock.h"
#include "swift/SIL/SILFunction.h"
#include "llvm/Support/CommandLine.h"
using namespace swift;
@@ -43,6 +44,7 @@ class MemoryLifetimeVerifier {
SILFunction *function;
CalleeCache *calleeCache;
DeadEndBlocks *deadEndBlocks;
MemoryLocations locations;
/// alloc_stack memory locations which are used for store_borrow.
@@ -140,11 +142,12 @@ class MemoryLifetimeVerifier {
}
public:
MemoryLifetimeVerifier(SILFunction *function, CalleeCache *calleeCache) :
function(function),
calleeCache(calleeCache),
locations(/*handleNonTrivialProjections*/ true,
/*handleTrivialLocations*/ true) {}
MemoryLifetimeVerifier(SILFunction *function, CalleeCache *calleeCache,
DeadEndBlocks *deadEndBlocks)
: function(function), calleeCache(calleeCache),
deadEndBlocks(deadEndBlocks),
locations(/*handleNonTrivialProjections*/ true,
/*handleTrivialLocations*/ true) {}
/// The main entry point to verify the lifetime of all memory locations in
/// the function.
@@ -883,7 +886,12 @@ void MemoryLifetimeVerifier::checkBlock(SILBasicBlock *block, Bits &bits) {
}
case SILInstructionKind::DeallocStackInst: {
SILValue opVal = cast<DeallocStackInst>(&I)->getOperand();
requireBitsClear(bits & nonTrivialLocations, opVal, &I);
if (!deadEndBlocks->isDeadEnd(I.getParent())) {
// TODO: rdar://159311784: Maybe at some point the invariant will be
// enforced that values stored into addresses
// don't leak in dead-ends.
requireBitsClear(bits & nonTrivialLocations, opVal, &I);
}
// Needed to clear any bits of trivial locations (which are not required
// to be zero).
locations.clearBits(bits, opVal);
@@ -973,7 +981,8 @@ void MemoryLifetimeVerifier::verify() {
} // anonymous namespace
void SILFunction::verifyMemoryLifetime(CalleeCache *calleeCache) {
MemoryLifetimeVerifier verifier(this, calleeCache);
void SILFunction::verifyMemoryLifetime(CalleeCache *calleeCache,
DeadEndBlocks *deadEndBlocks) {
MemoryLifetimeVerifier verifier(this, calleeCache, deadEndBlocks);
verifier.verify();
}

View File

@@ -7380,7 +7380,7 @@ public:
if (F->hasOwnership() && F->shouldVerifyOwnership() &&
!mod.getASTContext().hadError()) {
F->verifyMemoryLifetime(calleeCache);
F->verifyMemoryLifetime(calleeCache, &getDeadEndBlocks());
}
}

View File

@@ -25,6 +25,8 @@
#include "swift/SIL/SILArgument.h"
#include "swift/SIL/SILBuilder.h"
#include "swift/SIL/SILCloner.h"
#include "swift/SILOptimizer/Analysis/DeadEndBlocksAnalysis.h"
#include "swift/SILOptimizer/Analysis/LoopAnalysis.h"
#include "swift/SILOptimizer/PassManager/Passes.h"
#include "swift/SILOptimizer/PassManager/Transforms.h"
#include "swift/SILOptimizer/Utils/InstOptUtils.h"
@@ -601,7 +603,9 @@ static void hoistMarkUnresolvedNonCopyableValueInsts(
/// rewriteAllocBoxAsAllocStack - Replace uses of the alloc_box with a
/// new alloc_stack, but do not delete the alloc_box yet.
static bool rewriteAllocBoxAsAllocStack(AllocBoxInst *ABI) {
static bool rewriteAllocBoxAsAllocStack(AllocBoxInst *ABI,
DeadEndBlocksAnalysis &deba,
SILLoopAnalysis &la) {
LLVM_DEBUG(llvm::dbgs() << "*** Promoting alloc_box to stack: " << *ABI);
SILValue HeapBox = ABI;
@@ -693,9 +697,31 @@ static bool rewriteAllocBoxAsAllocStack(AllocBoxInst *ABI) {
ABI->getBoxType(), ABI->getModule().Types, 0));
auto Loc = CleanupLocation(ABI->getLoc());
auto *deb = deba.get(ABI->getFunction());
for (auto LastRelease : FinalReleases) {
auto *dbi = dyn_cast<DeallocBoxInst>(LastRelease);
if (!dbi && deb->isDeadEnd(LastRelease->getParent()) &&
!la.get(ABI->getFunction())->getLoopFor(LastRelease->getParent())) {
// "Last" releases in dead-end regions may not actually destroy the box
// and consequently may not actually release the stored value. That's
// because values (including boxes) may be leaked along paths into
// dead-end regions. Thus it is invalid to lower such final releases of
// the box to destroy_addr's/dealloc_box's of the stack-promoted storage.
//
// There is one exception: if the alloc_box is in a dead-end loop. In
// that case SIL invariants require that the final releases actually
// destroy the box; otherwise, a box would leak once per loop. To check
// for this, it is sufficient check that the LastRelease is in a dead-end
// loop: if the alloc_box is not in that loop, then the entire loop is in
// the live range, so no release within the loop would be a "final
// release".
//
// None of this applies to dealloc_box instructions which always destroy
// the box.
continue;
}
SILBuilderWithScope Builder(LastRelease);
if (!isa<DeallocBoxInst>(LastRelease)&& !Lowering.isTrivial()) {
if (!dbi && !Lowering.isTrivial()) {
// If we have a mark_unresolved_non_copyable_value use of our stack box,
// we want to destroy that.
SILValue valueToDestroy = StackBox;
@@ -709,7 +735,6 @@ static bool rewriteAllocBoxAsAllocStack(AllocBoxInst *ABI) {
// instruction we found that isn't an explicit dealloc_box.
Builder.emitDestroyAddrAndFold(Loc, valueToDestroy);
}
auto *dbi = dyn_cast<DeallocBoxInst>(LastRelease);
if (dbi && dbi->isDeadEnd()) {
// Don't bother to create dealloc_stack instructions in dead-ends.
continue;
@@ -1265,7 +1290,9 @@ static void rewriteApplySites(AllocBoxToStackState &pass) {
/// Clone closure bodies and rewrite partial applies. Returns the number of
/// alloc_box allocations promoted.
static unsigned rewritePromotedBoxes(AllocBoxToStackState &pass) {
static unsigned rewritePromotedBoxes(AllocBoxToStackState &pass,
DeadEndBlocksAnalysis &deba,
SILLoopAnalysis &la) {
// First we'll rewrite any ApplySite that we can to remove
// the box container pointer from the operands.
rewriteApplySites(pass);
@@ -1274,7 +1301,7 @@ static unsigned rewritePromotedBoxes(AllocBoxToStackState &pass) {
auto rend = pass.Promotable.rend();
for (auto I = pass.Promotable.rbegin(); I != rend; ++I) {
auto *ABI = *I;
if (rewriteAllocBoxAsAllocStack(ABI)) {
if (rewriteAllocBoxAsAllocStack(ABI, deba, la)) {
++Count;
ABI->eraseFromParent();
}
@@ -1299,7 +1326,9 @@ class AllocBoxToStack : public SILFunctionTransform {
}
if (!pass.Promotable.empty()) {
auto Count = rewritePromotedBoxes(pass);
auto *deba = getAnalysis<DeadEndBlocksAnalysis>();
auto *la = getAnalysis<SILLoopAnalysis>();
auto Count = rewritePromotedBoxes(pass, *deba, *la);
NumStackPromoted += Count;
if (Count) {
if (StackNesting::fixNesting(getFunction()) == StackNesting::Changes::CFG)