Skip to content

Commit f4bc8d6

Browse files
committed
Use OSSALifetimeCompletion in PredictableMemOpt
The current algorithm to complete lifetimes is incorrect in a few cases. Use OSSALifetimeCompletion instead. Fixes rdar://119204768
1 parent 931342c commit f4bc8d6

File tree

6 files changed

+93
-156
lines changed

6 files changed

+93
-156
lines changed

SwiftCompilerSources/Sources/Optimizer/PassManager/Context.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -286,15 +286,15 @@ struct FunctionPassContext : MutatingContext {
286286
}
287287

288288
func optimizeMemoryAccesses(in function: Function) -> Bool {
289-
if BridgedPassContext.optimizeMemoryAccesses(function.bridged) {
289+
if _bridged.optimizeMemoryAccesses(function.bridged) {
290290
notifyInstructionsChanged()
291291
return true
292292
}
293293
return false
294294
}
295295

296296
func eliminateDeadAllocations(in function: Function) -> Bool {
297-
if BridgedPassContext.eliminateDeadAllocations(function.bridged) {
297+
if _bridged.eliminateDeadAllocations(function.bridged) {
298298
notifyInstructionsChanged()
299299
return true
300300
}

include/swift/SILOptimizer/OptimizerBridging.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -216,8 +216,8 @@ struct BridgedPassContext {
216216
bool isPrivate) const;
217217
void inlineFunction(BridgedInstruction apply, bool mandatoryInline) const;
218218
SWIFT_IMPORT_UNSAFE BRIDGED_INLINE BridgedValue getSILUndef(BridgedType type) const;
219-
BRIDGED_INLINE static bool optimizeMemoryAccesses(BridgedFunction f);
220-
BRIDGED_INLINE static bool eliminateDeadAllocations(BridgedFunction f);
219+
BRIDGED_INLINE bool optimizeMemoryAccesses(BridgedFunction f) const;
220+
BRIDGED_INLINE bool eliminateDeadAllocations(BridgedFunction f) const;
221221

222222
// IRGen
223223

include/swift/SILOptimizer/OptimizerBridgingImpl.h

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -218,11 +218,12 @@ BridgedValue BridgedPassContext::getSILUndef(BridgedType type) const {
218218
return {swift::SILUndef::get(type.unbridged(), *invocation->getFunction())};
219219
}
220220

221-
bool BridgedPassContext::optimizeMemoryAccesses(BridgedFunction f) {
222-
return swift::optimizeMemoryAccesses(f.getFunction());
221+
bool BridgedPassContext::optimizeMemoryAccesses(BridgedFunction f) const {
222+
return swift::optimizeMemoryAccesses(f.getFunction(), this->getDomTree().di);
223223
}
224-
bool BridgedPassContext::eliminateDeadAllocations(BridgedFunction f) {
225-
return swift::eliminateDeadAllocations(f.getFunction());
224+
bool BridgedPassContext::eliminateDeadAllocations(BridgedFunction f) const {
225+
return swift::eliminateDeadAllocations(f.getFunction(),
226+
this->getDomTree().di);
226227
}
227228

228229
BridgedBasicBlockSet BridgedPassContext::allocBasicBlockSet() const {

include/swift/SILOptimizer/Utils/InstOptUtils.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -582,12 +582,12 @@ IntegerLiteralInst *optimizeBuiltinCanBeObjCClass(BuiltinInst *bi,
582582
/// Performs "predictable" memory access optimizations.
583583
///
584584
/// See the PredictableMemoryAccessOptimizations pass.
585-
bool optimizeMemoryAccesses(SILFunction *fn);
585+
bool optimizeMemoryAccesses(SILFunction *fn, DominanceInfo *domInfo);
586586

587587
/// Performs "predictable" dead allocation optimizations.
588588
///
589589
/// See the PredictableDeadAllocationElimination pass.
590-
bool eliminateDeadAllocations(SILFunction *fn);
590+
bool eliminateDeadAllocations(SILFunction *fn, DominanceInfo *domInfo);
591591

592592
SILVTable *specializeVTableForType(SILType type, SILModule &mod, SILTransform *transform);
593593

lib/SILOptimizer/Mandatory/PredictableMemOpt.cpp

Lines changed: 30 additions & 146 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
#include "swift/SIL/BasicBlockBits.h"
2121
#include "swift/SIL/BasicBlockUtils.h"
2222
#include "swift/SIL/LinearLifetimeChecker.h"
23+
#include "swift/SIL/OSSALifetimeCompletion.h"
2324
#include "swift/SIL/OwnershipUtils.h"
2425
#include "swift/SIL/SILBuilder.h"
2526
#include "swift/SILOptimizer/PassManager/Passes.h"
@@ -1945,19 +1946,22 @@ class AllocOptimize {
19451946

19461947
InstructionDeleter &deleter;
19471948

1949+
DominanceInfo *domInfo;
1950+
19481951
/// A structure that we use to compute our available values.
19491952
AvailableValueDataflowContext DataflowContext;
19501953

19511954
public:
19521955
AllocOptimize(AllocationInst *memory, SmallVectorImpl<PMOMemoryUse> &uses,
19531956
SmallVectorImpl<SILInstruction *> &releases,
1954-
DeadEndBlocks &deadEndBlocks, InstructionDeleter &deleter)
1957+
DeadEndBlocks &deadEndBlocks, InstructionDeleter &deleter,
1958+
DominanceInfo *domInfo)
19551959
: Module(memory->getModule()), TheMemory(memory),
19561960
MemoryType(getMemoryType(memory)),
19571961
NumMemorySubElements(getNumSubElements(
19581962
MemoryType, Module, TypeExpansionContext(*memory->getFunction()))),
19591963
Uses(uses), Releases(releases), deadEndBlocks(deadEndBlocks),
1960-
deleter(deleter),
1964+
deleter(deleter), domInfo(domInfo),
19611965
DataflowContext(TheMemory, NumMemorySubElements, uses, deleter) {}
19621966

19631967
bool optimizeMemoryAccesses();
@@ -2655,145 +2659,20 @@ bool AllocOptimize::tryToRemoveDeadAllocation() {
26552659
// post-dominating consuming use sets. This can happen if we have an enum that
26562660
// is known dynamically none along a path. This is dynamically correct, but
26572661
// can not be represented in OSSA so we insert these destroys along said path.
2658-
SmallVector<SILBasicBlock *, 32> consumingUseBlocks;
2662+
OSSALifetimeCompletion completion(TheMemory->getFunction(), domInfo);
2663+
26592664
while (!valuesNeedingLifetimeCompletion.empty()) {
26602665
auto optV = valuesNeedingLifetimeCompletion.pop_back_val();
26612666
if (!optV)
26622667
continue;
26632668
SILValue v = *optV;
2664-
if (v->getOwnershipKind() != OwnershipKind::Owned)
2665-
continue;
2666-
2667-
// FIXME: This doesn't handle situations where non-consuming uses are in
2668-
// blocks in which the consuming use is already deleted. Replace the
2669-
// following code with an general OSSA utility for owned lifetime
2670-
// fixup. ValueLifetimeAnalysis does this easily, but we also need to handle
2671-
// reborrows by adding copies. An OwnershipOptUtils utility will handle
2672-
// that soon.
2673-
2674-
// First see if our value doesn't have any uses. In such a case, just
2675-
// insert a destroy_value at the next instruction and return.
2676-
if (v->use_empty()) {
2677-
auto *next = v->getNextInstruction();
2678-
auto loc = RegularLocation::getAutoGeneratedLocation();
2679-
SILBuilderWithScope localBuilder(next);
2680-
localBuilder.createDestroyValue(loc, v);
2681-
continue;
2682-
}
2683-
2684-
// Otherwise, we first see if we have any consuming uses at all. If we do,
2685-
// then we know that any such consuming uses since we have an owned value
2686-
// /must/ be strongly control equivalent to our value and unreachable from
2687-
// each other, so we can just use findJointPostDominatingSet to complete
2688-
// the set.
2689-
consumingUseBlocks.clear();
2690-
for (auto *use : v->getConsumingUses())
2691-
consumingUseBlocks.push_back(use->getParentBlock());
2692-
2693-
if (!consumingUseBlocks.empty()) {
2694-
findJointPostDominatingSet(
2695-
v->getParentBlock(), consumingUseBlocks, [](SILBasicBlock *) {},
2696-
[&](SILBasicBlock *result) {
2697-
auto loc = RegularLocation::getAutoGeneratedLocation();
2698-
SILBuilderWithScope builder(result);
2699-
builder.createDestroyValue(loc, v);
2700-
});
2701-
continue;
2702-
}
2703-
2704-
// If we do not have at least one consuming use, we need to do something
2705-
// different. This situation can occur given a non-trivial enum typed
2706-
// stack allocation that:
2707-
//
2708-
// 1. Had a destroy_addr eliminated along a path where we dynamically know
2709-
// that the stack allocation is storing a trivial case.
2710-
//
2711-
// 2. Had some other paths where due to dead end blocks, no destroy_addr
2712-
// is needed.
2713-
//
2714-
// To fix this, we just treat all uses as consuming blocks and insert
2715-
// destroys using the joint post dominance set computer and insert
2716-
// destroys at the end of all input blocks in the post dom set and at the
2717-
// beginning of any leaking blocks.
2718-
{
2719-
// TODO: Can we just pass this in to findJointPostDominatingSet instead
2720-
// of recomputing it there? Maybe an overload that lets us do this?
2721-
BasicBlockSet foundUseBlocks(v->getFunction());
2722-
for (auto *use : v->getUses()) {
2723-
auto *block = use->getParentBlock();
2724-
if (!foundUseBlocks.insert(block))
2725-
continue;
2726-
consumingUseBlocks.push_back(block);
2727-
}
2728-
}
2729-
findJointPostDominatingSet(
2730-
v->getParentBlock(), consumingUseBlocks,
2731-
[&](SILBasicBlock *foundInputBlock) {
2732-
// This is a block that is reachable from another use. We are not
2733-
// interested in these.
2734-
},
2735-
[&](SILBasicBlock *leakingBlock) {
2736-
auto loc = RegularLocation::getAutoGeneratedLocation();
2737-
SILBuilderWithScope builder(leakingBlock);
2738-
builder.createDestroyValue(loc, v);
2739-
},
2740-
[&](SILBasicBlock *inputBlockInPostDomSet) {
2741-
auto *termInst = inputBlockInPostDomSet->getTerminator();
2742-
switch (termInst->getTermKind()) {
2743-
case TermKind::UnreachableInst:
2744-
// We do not care about input blocks that end in unreachables. We
2745-
// are going to leak down them so do not insert a destroy_value
2746-
// there.
2747-
return;
2748-
2749-
// NOTE: Given that our input value is owned, our branch can only
2750-
// accept the use as a non-consuming use if the branch is forwarding
2751-
// unowned ownership. Luckily for use, we checked early if we had
2752-
// any such uses and bailed, so we know the branch can not use our
2753-
// value. This is just avoiding a corner case that we don't need to
2754-
// handle.
2755-
case TermKind::BranchInst:
2756-
LLVM_FALLTHROUGH;
2757-
// NOTE: We put cond_br here since in OSSA, cond_br can never have
2758-
// a non-trivial value operand, meaning we can insert before.
2759-
case TermKind::CondBranchInst:
2760-
LLVM_FALLTHROUGH;
2761-
case TermKind::ReturnInst:
2762-
case TermKind::ThrowInst:
2763-
case TermKind::ThrowAddrInst:
2764-
case TermKind::UnwindInst:
2765-
case TermKind::YieldInst: {
2766-
// These terminators can never be non-consuming uses of an owned
2767-
// value since we would be leaking the owned value no matter what
2768-
// we do. Given that, we can assume that what ever the
2769-
// non-consuming use actually was, must be before this
2770-
// instruction. So insert the destroy_value at the end of the
2771-
// block, before the terminator.
2772-
auto loc = RegularLocation::getAutoGeneratedLocation();
2773-
SILBuilderWithScope localBuilder(termInst);
2774-
localBuilder.createDestroyValue(loc, v);
2775-
return;
2776-
}
2777-
case TermKind::TryApplyInst:
2778-
case TermKind::SwitchValueInst:
2779-
case TermKind::SwitchEnumInst:
2780-
case TermKind::SwitchEnumAddrInst:
2781-
case TermKind::DynamicMethodBranchInst:
2782-
case TermKind::AwaitAsyncContinuationInst:
2783-
case TermKind::CheckedCastBranchInst:
2784-
case TermKind::CheckedCastAddrBranchInst: {
2785-
// Otherwise, we insert the destroy_addr /after/ the
2786-
// terminator. All of these are guaranteed to have each successor
2787-
// to have the block as its only predecessor block.
2788-
SILBuilderWithScope::insertAfter(termInst, [&](auto &b) {
2789-
auto loc = RegularLocation::getAutoGeneratedLocation();
2790-
b.createDestroyValue(loc, v);
2791-
});
2792-
return;
2793-
}
2794-
}
2795-
llvm_unreachable("Case that did not return in its body?!");
2796-
});
2669+
// Lexical enums can have incomplete lifetimes in non payload paths that
2670+
// don't end in unreachable. Force their lifetime to end immediately after
2671+
// the last use instead.
2672+
bool forceBoundaryCompletion = v->getType().isOrHasEnum();
2673+
LLVM_DEBUG(llvm::dbgs() << "Completing lifetime of: ");
2674+
LLVM_DEBUG(v->dump());
2675+
completion.completeOSSALifetime(v, forceBoundaryCompletion);
27972676
}
27982677

27992678
return true;
@@ -2863,7 +2742,7 @@ static AllocationInst *getOptimizableAllocation(SILInstruction *i) {
28632742
return alloc;
28642743
}
28652744

2866-
bool swift::optimizeMemoryAccesses(SILFunction *fn) {
2745+
bool swift::optimizeMemoryAccesses(SILFunction *fn, DominanceInfo *domInfo) {
28672746
bool changed = false;
28682747
DeadEndBlocks deadEndBlocks(fn);
28692748

@@ -2892,8 +2771,8 @@ bool swift::optimizeMemoryAccesses(SILFunction *fn) {
28922771
// runs. It creates and deletes instructions other than alloc.
28932772
continue;
28942773
}
2895-
AllocOptimize allocOptimize(alloc, uses, destroys, deadEndBlocks,
2896-
deleter);
2774+
AllocOptimize allocOptimize(alloc, uses, destroys, deadEndBlocks, deleter,
2775+
domInfo);
28972776
changed |= allocOptimize.optimizeMemoryAccesses();
28982777

28992778
// Move onto the next instruction. We know this is safe since we do not
@@ -2904,7 +2783,7 @@ bool swift::optimizeMemoryAccesses(SILFunction *fn) {
29042783
return changed;
29052784
}
29062785

2907-
bool swift::eliminateDeadAllocations(SILFunction *fn) {
2786+
bool swift::eliminateDeadAllocations(SILFunction *fn, DominanceInfo *domInfo) {
29082787
if (!fn->hasOwnership())
29092788
return false;
29102789

@@ -2935,8 +2814,8 @@ bool swift::eliminateDeadAllocations(SILFunction *fn) {
29352814
if (!collectPMOElementUsesFrom(memInfo, uses, destroys)) {
29362815
continue;
29372816
}
2938-
AllocOptimize allocOptimize(alloc, uses, destroys, deadEndBlocks,
2939-
deleter);
2817+
AllocOptimize allocOptimize(alloc, uses, destroys, deadEndBlocks, deleter,
2818+
domInfo);
29402819
if (allocOptimize.tryToRemoveDeadAllocation()) {
29412820
deleter.cleanupDeadInstructions();
29422821
++NumAllocRemoved;
@@ -2958,19 +2837,24 @@ class PredictableMemoryAccessOptimizations : public SILFunctionTransform {
29582837
/// either indicates that this pass missing some opportunities the first time,
29592838
/// or has a pass order dependency on other early passes.
29602839
void run() override {
2840+
auto *func = getFunction();
2841+
LLVM_DEBUG(llvm::dbgs() << "Looking at: " << func->getName() << "\n");
2842+
auto *da = getAnalysis<DominanceAnalysis>();
29612843
// TODO: Can we invalidate here just instructions?
2962-
if (optimizeMemoryAccesses(getFunction()))
2844+
if (optimizeMemoryAccesses(func, da->get(func)))
29632845
invalidateAnalysis(SILAnalysis::InvalidationKind::FunctionBody);
29642846
}
29652847
};
29662848

29672849
class PredictableDeadAllocationElimination : public SILFunctionTransform {
29682850
void run() override {
2851+
auto *func = getFunction();
2852+
LLVM_DEBUG(llvm::dbgs() << "Looking at: " << func->getName() << "\n");
2853+
auto *da = getAnalysis<DominanceAnalysis>();
29692854
// If we are already canonical or do not have ownership, just bail.
2970-
if (getFunction()->wasDeserializedCanonical() ||
2971-
!getFunction()->hasOwnership())
2855+
if (func->wasDeserializedCanonical() || !func->hasOwnership())
29722856
return;
2973-
if (eliminateDeadAllocations(getFunction()))
2857+
if (eliminateDeadAllocations(func, da->get(func)))
29742858
invalidateAnalysis(SILAnalysis::InvalidationKind::FunctionBody);
29752859
}
29762860
};

test/SILOptimizer/predictable_deadalloc_elim_ownership.sil

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -697,3 +697,55 @@ bb0(%arg : @owned $Builtin.NativeObject):
697697
destroy_value %1 : $Builtin.NativeObject
698698
unreachable
699699
}
700+
701+
enum ErrorEnum : Error {
702+
case errorCase(label: [any Error])
703+
case other
704+
}
705+
706+
sil [ossa] @test_complete_lifetime : $@convention(thin) (@owned ErrorEnum) -> () {
707+
708+
bb0(%0 : @owned $ErrorEnum):
709+
%1 = alloc_stack $any Error
710+
%2 = alloc_existential_box $any Error, $ErrorEnum
711+
%3 = project_existential_box $ErrorEnum in %2 : $any Error
712+
store %2 to [init] %1 : $*any Error
713+
store %0 to [init] %3 : $*ErrorEnum
714+
%6 = load [take] %1 : $*any Error
715+
dealloc_stack %1 : $*any Error
716+
%8 = alloc_stack $any Error
717+
%9 = copy_value %6 : $any Error
718+
store %9 to [init] %8 : $*any Error
719+
%11 = alloc_stack $ErrorEnum
720+
checked_cast_addr_br copy_on_success any Error in %8 : $*any Error to ErrorEnum in %11 : $*ErrorEnum, bb2, bb3
721+
722+
bb1:
723+
%13 = tuple ()
724+
return %13 : $()
725+
726+
bb2:
727+
%15 = load [take] %11 : $*ErrorEnum
728+
destroy_value %15 : $ErrorEnum
729+
dealloc_stack %11 : $*ErrorEnum
730+
destroy_addr %8 : $*any Error
731+
dealloc_stack %8 : $*any Error
732+
destroy_value %6 : $any Error
733+
br bb1
734+
735+
bb3:
736+
dealloc_stack %11 : $*ErrorEnum
737+
destroy_addr %8 : $*any Error
738+
dealloc_stack %8 : $*any Error
739+
%25 = copy_value %6 : $any Error
740+
cond_br undef, bb4, bb5
741+
742+
bb4:
743+
br bb6
744+
745+
bb5:
746+
br bb6
747+
748+
bb6:
749+
unreachable
750+
}
751+

0 commit comments

Comments
 (0)