Skip to content

Commit 46970df

Browse files
committed
[CanonOSSALifetime] Run on lexical lifetimes.
Previously, the utility bailed out on lexical lifetimes because it didn't respect deinit barriers. Here, deinit barriers are found and added to liveness if the value is lexical. This enables copies to be propagated without hoisting destroys over deinit barriers.
1 parent 78f453e commit 46970df

File tree

11 files changed

+112
-24
lines changed

11 files changed

+112
-24
lines changed

include/swift/SILOptimizer/Utils/CanonicalizeOSSALifetime.h

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,8 @@
109109

110110
namespace swift {
111111

112+
class BasicCalleeAnalysis;
113+
112114
extern llvm::Statistic NumCopiesAndMovesEliminated;
113115
extern llvm::Statistic NumCopiesGenerated;
114116

@@ -225,6 +227,8 @@ class CanonicalizeOSSALifetime final {
225227

226228
DominanceInfo *domTree;
227229

230+
BasicCalleeAnalysis *calleeAnalysis;
231+
228232
InstructionDeleter &deleter;
229233

230234
/// Original points in the CFG where the current value's lifetime is consumed
@@ -282,11 +286,13 @@ class CanonicalizeOSSALifetime final {
282286

283287
CanonicalizeOSSALifetime(bool pruneDebugMode, bool maximizeLifetime,
284288
NonLocalAccessBlockAnalysis *accessBlockAnalysis,
285-
DominanceInfo *domTree, InstructionDeleter &deleter)
289+
DominanceInfo *domTree,
290+
BasicCalleeAnalysis *calleeAnalysis,
291+
InstructionDeleter &deleter)
286292
: pruneDebugMode(pruneDebugMode), maximizeLifetime(maximizeLifetime),
287293
accessBlockAnalysis(accessBlockAnalysis), domTree(domTree),
288-
deleter(deleter),
289-
liveness(maximizeLifetime ? &discoveredBlocks : nullptr) {}
294+
calleeAnalysis(calleeAnalysis), deleter(deleter),
295+
liveness(&discoveredBlocks) {}
290296

291297
SILValue getCurrentDef() const { return liveness.getDef(); }
292298

@@ -383,6 +389,9 @@ class CanonicalizeOSSALifetime final {
383389
void findExtendedBoundary(PrunedLivenessBoundary const &originalBoundary,
384390
PrunedLivenessBoundary &boundary);
385391

392+
void findFinalDestroys(SmallVectorImpl<SILInstruction *> &destroys);
393+
void extendLivenessToDeinitBarriers();
394+
386395
void extendUnconsumedLiveness(PrunedLivenessBoundary const &boundary);
387396

388397
void insertDestroysOnBoundary(PrunedLivenessBoundary const &boundary);

lib/SILOptimizer/Mandatory/MoveOnlyObjectCheckerUtils.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,8 @@ struct OSSACanonicalizer {
6262
InstructionDeleter &deleter) {
6363
canonicalizer.emplace(false /*pruneDebugMode*/,
6464
!fn->shouldOptimize() /*maximizeLifetime*/,
65-
nullptr /*accessBlockAnalysis*/, domTree, deleter);
65+
nullptr /*accessBlockAnalysis*/, domTree,
66+
nullptr /*calleeAnalysis*/, deleter);
6667
}
6768

6869
void clear() {

lib/SILOptimizer/SILCombiner/SILCombine.cpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,7 @@ SILCombiner::SILCombiner(SILFunctionTransform *trans,
166166
bool removeCondFails, bool enableCopyPropagation) :
167167
parentTransform(trans),
168168
AA(trans->getPassManager()->getAnalysis<AliasAnalysis>(trans->getFunction())),
169+
CA(trans->getPassManager()->getAnalysis<BasicCalleeAnalysis>()),
169170
DA(trans->getPassManager()->getAnalysis<DominanceAnalysis>()),
170171
PCA(trans->getPassManager()->getAnalysis<ProtocolConformanceAnalysis>()),
171172
CHA(trans->getPassManager()->getAnalysis<ClassHierarchyAnalysis>()),
@@ -353,7 +354,7 @@ void SILCombiner::canonicalizeOSSALifetimes(SILInstruction *currentInst) {
353354
CanonicalizeOSSALifetime canonicalizer(
354355
false /*prune debug*/,
355356
!parentTransform->getFunction()->shouldOptimize() /*maximize lifetime*/,
356-
NLABA, domTree, deleter);
357+
NLABA, domTree, CA, deleter);
357358
CanonicalizeBorrowScope borrowCanonicalizer(deleter);
358359

359360
while (!defsToCanonicalize.empty()) {

lib/SILOptimizer/SILCombiner/SILCombiner.h

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,21 +23,22 @@
2323

2424
#include "swift/Basic/Defer.h"
2525
#include "swift/SIL/BasicBlockUtils.h"
26+
#include "swift/SIL/InstructionUtils.h"
2627
#include "swift/SIL/SILBuilder.h"
2728
#include "swift/SIL/SILInstruction.h"
2829
#include "swift/SIL/SILInstructionWorklist.h"
2930
#include "swift/SIL/SILValue.h"
3031
#include "swift/SIL/SILVisitor.h"
31-
#include "swift/SIL/InstructionUtils.h"
32+
#include "swift/SILOptimizer/Analysis/BasicCalleeAnalysis.h"
3233
#include "swift/SILOptimizer/Analysis/ClassHierarchyAnalysis.h"
3334
#include "swift/SILOptimizer/Analysis/NonLocalAccessBlockAnalysis.h"
3435
#include "swift/SILOptimizer/Analysis/ProtocolConformanceAnalysis.h"
3536
#include "swift/SILOptimizer/OptimizerBridging.h"
37+
#include "swift/SILOptimizer/PassManager/PassManager.h"
3638
#include "swift/SILOptimizer/Utils/CastOptimizer.h"
3739
#include "swift/SILOptimizer/Utils/Existential.h"
3840
#include "swift/SILOptimizer/Utils/InstOptUtils.h"
3941
#include "swift/SILOptimizer/Utils/OwnershipOptUtils.h"
40-
#include "swift/SILOptimizer/PassManager/PassManager.h"
4142

4243
#include "llvm/ADT/DenseMap.h"
4344
#include "llvm/ADT/SmallVector.h"
@@ -55,6 +56,8 @@ class SILCombiner :
5556

5657
AliasAnalysis *AA;
5758

59+
BasicCalleeAnalysis *CA;
60+
5861
DominanceAnalysis *DA;
5962

6063
/// Determine the set of types a protocol conforms to in whole-module

lib/SILOptimizer/Transforms/CopyPropagation.cpp

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -400,6 +400,7 @@ void CopyPropagation::run() {
400400
auto *postOrderAnalysis = getAnalysis<PostOrderAnalysis>();
401401
auto *accessBlockAnalysis = getAnalysis<NonLocalAccessBlockAnalysis>();
402402
auto *dominanceAnalysis = getAnalysis<DominanceAnalysis>();
403+
auto *calleeAnalysis = getAnalysis<BasicCalleeAnalysis>();
403404
DominanceInfo *domTree = dominanceAnalysis->get(f);
404405

405406
// Label for unit testing with debug output.
@@ -440,9 +441,7 @@ void CopyPropagation::run() {
440441
// don't need to explicitly check for changes.
441442
CanonicalizeOSSALifetime canonicalizer(
442443
pruneDebug, /*maximizeLifetime=*/!getFunction()->shouldOptimize(),
443-
accessBlockAnalysis, domTree, deleter);
444-
auto *calleeAnalysis = getAnalysis<BasicCalleeAnalysis>();
445-
444+
accessBlockAnalysis, domTree, calleeAnalysis, deleter);
446445
// NOTE: We assume that the function is in reverse post order so visiting the
447446
// blocks and pushing begin_borrows as we see them and then popping them
448447
// off the end will result in shrinking inner borrow scopes first.

lib/SILOptimizer/UtilityPasses/UnitTestRunner.cpp

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -404,13 +404,15 @@ struct CanonicalizeOSSALifetimeTest : UnitTest {
404404
auto *accessBlockAnalysis = getAnalysis<NonLocalAccessBlockAnalysis>();
405405
auto *dominanceAnalysis = getAnalysis<DominanceAnalysis>();
406406
DominanceInfo *domTree = dominanceAnalysis->get(getFunction());
407+
auto *calleeAnalysis = getAnalysis<BasicCalleeAnalysis>();
407408
auto pruneDebug = arguments.takeBool();
408409
auto maximizeLifetimes = arguments.takeBool();
409410
auto respectAccessScopes = arguments.takeBool();
410411
InstructionDeleter deleter;
411412
CanonicalizeOSSALifetime canonicalizer(
412413
pruneDebug, maximizeLifetimes,
413-
respectAccessScopes ? accessBlockAnalysis : nullptr, domTree, deleter);
414+
respectAccessScopes ? accessBlockAnalysis : nullptr, domTree,
415+
calleeAnalysis, deleter);
414416
auto value = arguments.takeValue();
415417
canonicalizer.canonicalizeValueLifetime(value);
416418
getFunction()->dump();
@@ -776,6 +778,7 @@ void UnitTestRunner::withTest(StringRef name, Doit doit) {
776778
ADD_UNIT_TEST_SUBCLASS("function-get-self-argument-index", FunctionGetSelfArgumentIndex)
777779
ADD_UNIT_TEST_SUBCLASS("interior-liveness", InteriorLivenessTest)
778780
ADD_UNIT_TEST_SUBCLASS("is-deinit-barrier", IsDeinitBarrierTest)
781+
ADD_UNIT_TEST_SUBCLASS("lexical-destroy-folding", LexicalDestroyFoldingTest)
779782
ADD_UNIT_TEST_SUBCLASS("linear-liveness", LinearLivenessTest)
780783
ADD_UNIT_TEST_SUBCLASS("multidef-liveness", MultiDefLivenessTest)
781784
ADD_UNIT_TEST_SUBCLASS("pruned-liveness-boundary-with-list-of-last-users-insertion-points", PrunedLivenessBoundaryWithListOfLastUsersInsertionPointsTest)

lib/SILOptimizer/Utils/CanonicalizeOSSALifetime.cpp

Lines changed: 81 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,8 @@
7070
#include "swift/SIL/NodeDatastructures.h"
7171
#include "swift/SIL/OwnershipUtils.h"
7272
#include "swift/SIL/PrunedLiveness.h"
73+
#include "swift/SILOptimizer/Analysis/BasicCalleeAnalysis.h"
74+
#include "swift/SILOptimizer/Analysis/Reachability.h"
7375
#include "swift/SILOptimizer/Utils/CFGOptUtils.h"
7476
#include "swift/SILOptimizer/Utils/DebugOptUtils.h"
7577
#include "swift/SILOptimizer/Utils/InstructionDeleter.h"
@@ -222,6 +224,82 @@ bool CanonicalizeOSSALifetime::computeCanonicalLiveness() {
222224
return true;
223225
}
224226

227+
static bool findDestroyInBlockAfter(SILInstruction *start,
228+
ArrayRef<SILInstruction *> candidates) {
229+
for (auto *inst = start; inst; inst = inst->getNextInstruction()) {
230+
if (llvm::is_contained(candidates, inst))
231+
return true;
232+
}
233+
return false;
234+
}
235+
236+
static bool isFinalDestroy(SILInstruction *inst,
237+
ArrayRef<SILInstruction *> candidates) {
238+
if (findDestroyInBlockAfter(inst->getNextInstruction(), candidates))
239+
return false;
240+
BasicBlockWorklist worklist(inst->getFunction());
241+
auto addSuccessorsToWorklist = [&](SILBasicBlock *block) {
242+
for (auto *successor : block->getSuccessorBlocks()) {
243+
worklist.pushIfNotVisited(successor);
244+
}
245+
};
246+
addSuccessorsToWorklist(inst->getParent());
247+
while (auto *block = worklist.pop()) {
248+
if (findDestroyInBlockAfter(&block->front(), candidates))
249+
return false;
250+
addSuccessorsToWorklist(block);
251+
}
252+
return true;
253+
}
254+
255+
void CanonicalizeOSSALifetime::findFinalDestroys(
256+
SmallVectorImpl<SILInstruction *> &finalDestroys) {
257+
SmallVector<SILInstruction *, 4> finalDestroyCandidates;
258+
for (auto destroy : destroys) {
259+
if (liveness.isWithinBoundary(destroy))
260+
continue;
261+
finalDestroyCandidates.push_back(destroy);
262+
}
263+
// TODO: Make more efficient.
264+
for (auto *candidate : finalDestroyCandidates) {
265+
if (!isFinalDestroy(candidate, finalDestroyCandidates))
266+
continue;
267+
finalDestroys.push_back(candidate);
268+
}
269+
}
270+
271+
void CanonicalizeOSSALifetime::extendLivenessToDeinitBarriers() {
272+
SmallVector<SILInstruction *, 4> finalDestroys;
273+
findFinalDestroys(finalDestroys);
274+
275+
auto *def = getCurrentDef()->getDefiningInstruction();
276+
ReachableBarriers barriers;
277+
findBarriersBeforeInstructions(
278+
finalDestroys, getCurrentDef()->getParentBlock(),
279+
*getCurrentDef()->getFunction(), barriers, [&](auto *inst) {
280+
if (inst == def)
281+
return true;
282+
if (liveness.isInterestingUser(inst) != PrunedLiveness::NonUser)
283+
return true;
284+
return isDeinitBarrier(inst, calleeAnalysis);
285+
});
286+
for (auto *barrier : barriers.instructions) {
287+
liveness.updateForUse(barrier, /*lifetimeEnding*/ false);
288+
}
289+
for (auto *barrier : barriers.phis) {
290+
for (auto *predecessor : barrier->getPredecessorBlocks()) {
291+
liveness.updateForUse(predecessor->getTerminator(),
292+
/*lifetimeEnding*/ false);
293+
}
294+
}
295+
for (auto *barrier : barriers.blocks) {
296+
for (auto *predecessor : barrier->getPredecessorBlocks()) {
297+
liveness.updateForUse(predecessor->getTerminator(),
298+
/*lifetimeEnding*/ false);
299+
}
300+
}
301+
}
302+
225303
// Return true if \p inst is an end_access whose access scope overlaps the end
226304
// of the pruned live range. This means that a hoisted destroy might execute
227305
// within the access scope which previously executed outside the access scope.
@@ -994,9 +1072,6 @@ bool CanonicalizeOSSALifetime::computeLiveness(SILValue def) {
9941072
if (def->getOwnershipKind() != OwnershipKind::Owned)
9951073
return false;
9961074

997-
if (def->isLexical())
998-
return false;
999-
10001075
LLVM_DEBUG(llvm::dbgs() << " Canonicalizing: " << def);
10011076

10021077
// Note: There is no need to register callbacks with this utility. 'onDelete'
@@ -1020,6 +1095,9 @@ bool CanonicalizeOSSALifetime::computeLiveness(SILValue def) {
10201095
clearLiveness();
10211096
return false;
10221097
}
1098+
if (def->isLexical()) {
1099+
extendLivenessToDeinitBarriers();
1100+
}
10231101
if (accessBlockAnalysis) {
10241102
extendLivenessThroughOverlappingAccess();
10251103
}

test/SILOptimizer/copy_propagation.sil

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -894,10 +894,10 @@ exit:
894894

895895
// CHECK-LABEL: sil [ossa] @dont_extend_beyond_nonoverlapping_end_access_after_store_in_consuming_block : {{.*}} {
896896
// CHECK: {{bb[0-9]+}}([[INSTANCE:%[^,]+]] : @owned $C
897-
// CHECK: [[COPY:%[^,]+]] = copy_value [[INSTANCE]]
897+
// CHECK-NOT: copy_value
898898
// CHECK: begin_access
899899
// CHECK-NOT: copy_value
900-
// CHECK: store [[COPY]] to [init] {{%[^,]+}}
900+
// CHECK: store [[INSTANCE]] to [init] {{%[^,]+}}
901901
// CHECK-LABEL: } // end sil function 'dont_extend_beyond_nonoverlapping_end_access_after_store_in_consuming_block'
902902
sil [ossa] @dont_extend_beyond_nonoverlapping_end_access_after_store_in_consuming_block : $@convention(thin) (@owned C) -> () {
903903
bb0(%instance : @owned $C):

test/SILOptimizer/copy_propagation_opaque.sil

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -440,9 +440,7 @@ bb0:
440440
//
441441
// CHECK-LABEL: sil [ossa] @testCopyBorrow : $@convention(thin) <T> (@in T) -> () {
442442
// CHECK: bb0(%0 : @owned $T):
443-
// CHECK: [[COPY:%[^,]+]] = copy_value %0
444443
// CHECK: destroy_value %0 : $T
445-
// CHECK: destroy_value [[COPY]]
446444
// CHECK-NEXT: tuple
447445
// CHECK-NEXT: return
448446
// CHECK-LABEL: } // end sil function 'testCopyBorrow'

test/SILOptimizer/lexical_destroy_hoisting.sil

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -332,10 +332,8 @@ exit:
332332
//
333333
// CHECK-LABEL: sil [ossa] @dont_hoist_over_apply_at_copy : {{.*}} {
334334
// CHECK: {{bb[0-9]+}}([[REGISTER_0:%[^,]+]] :
335-
// CHECK: [[COPY:%[^,]+]] = copy_value [[INSTANCE]]
336335
// CHECK: apply
337336
// CHECK: destroy_value [[INSTANCE]]
338-
// CHECK: destroy_value [[COPY]]
339337
// CHECK-LABEL: } // end sil function 'dont_hoist_over_apply_at_copy'
340338
sil [ossa] @dont_hoist_over_apply_at_copy : $@convention(thin) (@owned C) -> () {
341339
entry(%instance : @owned $C):

test/SILOptimizer/ossa_rauw_tests.sil

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -189,23 +189,21 @@ bb0(%0 : @owned $Klass):
189189
// CHECK-NOT: destroy_value
190190
//
191191
// CHECK: bb2:
192-
// CHECK-NEXT: [[COPY:%.*]] = copy_value [[ARG]]
193192
// CHECK-NEXT: cond_br undef, bb3, bb4
194193
//
195194
// CHECK: bb3:
196-
// CHECK-NEXT: destroy_value [[COPY]]
197195
// CHECK-NEXT: br bb2
198196
//
199197
// CHECK: bb4:
200-
// CHECK-NEXT: [[ENUM_SOME_RESULT:%.*]] = enum $FakeOptional<Klass>, #FakeOptional.some!enumelt, [[COPY]]
198+
// CHECK-NEXT: [[ENUM_SOME_RESULT:%.*]] = enum $FakeOptional<Klass>, #FakeOptional.some!enumelt, [[ARG]]
201199
// CHECK-NEXT: br bb6([[ENUM_SOME_RESULT]] : $FakeOptional<Klass>)
202200
//
203201
// CHECK: bb5:
202+
// CHECK-NEXT: destroy_value [[ARG]]
204203
// CHECK-NEXT: [[ENUM_NONE_RESULT:%.*]] = enum $FakeOptional<Klass>, #FakeOptional.none!enumelt
205204
// CHECK-NEXT: br bb6([[ENUM_NONE_RESULT]] :
206205
//
207206
// CHECK: bb6([[RESULT:%.*]] : @owned $FakeOptional<Klass>):
208-
// CHECK-NEXT: destroy_value [[ARG]]
209207
// CHECK-NEXT: return [[RESULT]]
210208
// CHECK: } // end sil function 'unowned_to_owned_rauw_loop'
211209
sil [ossa] @unowned_to_owned_rauw_loop : $@convention(thin) (@owned Klass) -> @owned FakeOptional<Klass> {

0 commit comments

Comments
 (0)