Skip to content

Commit 989faa0

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. rdar://104630103
1 parent 601c2fb commit 989faa0

File tree

12 files changed

+113
-24
lines changed

12 files changed

+113
-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 findDestroysOutsideBoundary(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
@@ -429,6 +429,7 @@ void CopyPropagation::run() {
429429
auto *postOrderAnalysis = getAnalysis<PostOrderAnalysis>();
430430
auto *accessBlockAnalysis = getAnalysis<NonLocalAccessBlockAnalysis>();
431431
auto *dominanceAnalysis = getAnalysis<DominanceAnalysis>();
432+
auto *calleeAnalysis = getAnalysis<BasicCalleeAnalysis>();
432433
DominanceInfo *domTree = dominanceAnalysis->get(f);
433434

434435
// Label for unit testing with debug output.
@@ -472,9 +473,7 @@ void CopyPropagation::run() {
472473
// don't need to explicitly check for changes.
473474
CanonicalizeOSSALifetime canonicalizer(
474475
pruneDebug, /*maximizeLifetime=*/!getFunction()->shouldOptimize(),
475-
accessBlockAnalysis, domTree, deleter);
476-
auto *calleeAnalysis = getAnalysis<BasicCalleeAnalysis>();
477-
476+
accessBlockAnalysis, domTree, calleeAnalysis, deleter);
478477
// NOTE: We assume that the function is in reverse post order so visiting the
479478
// blocks and pushing begin_borrows as we see them and then popping them
480479
// 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
@@ -422,13 +422,15 @@ struct CanonicalizeOSSALifetimeTest : UnitTest {
422422
auto *accessBlockAnalysis = getAnalysis<NonLocalAccessBlockAnalysis>();
423423
auto *dominanceAnalysis = getAnalysis<DominanceAnalysis>();
424424
DominanceInfo *domTree = dominanceAnalysis->get(getFunction());
425+
auto *calleeAnalysis = getAnalysis<BasicCalleeAnalysis>();
425426
auto pruneDebug = arguments.takeBool();
426427
auto maximizeLifetimes = arguments.takeBool();
427428
auto respectAccessScopes = arguments.takeBool();
428429
InstructionDeleter deleter;
429430
CanonicalizeOSSALifetime canonicalizer(
430431
pruneDebug, maximizeLifetimes,
431-
respectAccessScopes ? accessBlockAnalysis : nullptr, domTree, deleter);
432+
respectAccessScopes ? accessBlockAnalysis : nullptr, domTree,
433+
calleeAnalysis, deleter);
432434
auto value = arguments.takeValue();
433435
canonicalizer.canonicalizeValueLifetime(value);
434436
getFunction()->dump();
@@ -827,6 +829,7 @@ void UnitTestRunner::withTest(StringRef name, Doit doit) {
827829
ADD_UNIT_TEST_SUBCLASS("interior-liveness", InteriorLivenessTest)
828830
ADD_UNIT_TEST_SUBCLASS("is-deinit-barrier", IsDeinitBarrierTest)
829831
ADD_UNIT_TEST_SUBCLASS("is-lexical", IsLexicalTest)
832+
ADD_UNIT_TEST_SUBCLASS("lexical-destroy-folding", LexicalDestroyFoldingTest)
830833
ADD_UNIT_TEST_SUBCLASS("linear-liveness", LinearLivenessTest)
831834
ADD_UNIT_TEST_SUBCLASS("multidef-liveness", MultiDefLivenessTest)
832835
ADD_UNIT_TEST_SUBCLASS("ossa-lifetime-completion", OSSALifetimeCompletionTest)

lib/SILOptimizer/Utils/CanonicalizeOSSALifetime.cpp

Lines changed: 41 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,42 @@ bool CanonicalizeOSSALifetime::computeCanonicalLiveness() {
222224
return true;
223225
}
224226

227+
void CanonicalizeOSSALifetime::findDestroysOutsideBoundary(
228+
SmallVectorImpl<SILInstruction *> &outsideDestroys) {
229+
for (auto destroy : destroys) {
230+
if (liveness.isWithinBoundary(destroy))
231+
continue;
232+
outsideDestroys.push_back(destroy);
233+
}
234+
}
235+
236+
void CanonicalizeOSSALifetime::extendLivenessToDeinitBarriers() {
237+
SmallVector<SILInstruction *, 4> outsideDestroys;
238+
findDestroysOutsideBoundary(outsideDestroys);
239+
240+
auto *def = getCurrentDef()->getDefiningInstruction();
241+
ReachableBarriers barriers;
242+
findBarriersBeforeInstructions(
243+
outsideDestroys, getCurrentDef()->getParentBlock(),
244+
*getCurrentDef()->getFunction(), barriers, [&](auto *inst) {
245+
if (inst == def)
246+
return true;
247+
return isDeinitBarrier(inst, calleeAnalysis);
248+
});
249+
for (auto *barrier : barriers.instructions) {
250+
liveness.updateForUse(barrier, /*lifetimeEnding*/ false);
251+
}
252+
for (auto *barrier : barriers.phis) {
253+
for (auto *predecessor : barrier->getPredecessorBlocks()) {
254+
liveness.updateForUse(predecessor->getTerminator(),
255+
/*lifetimeEnding*/ false);
256+
}
257+
}
258+
// Ignore barriers.edges. The beginning of the targets of such edges should
259+
// not be added to liveness. These edges will be rediscovered when computing
260+
// the liveness boundary.
261+
}
262+
225263
// Return true if \p inst is an end_access whose access scope overlaps the end
226264
// of the pruned live range. This means that a hoisted destroy might execute
227265
// within the access scope which previously executed outside the access scope.
@@ -1005,9 +1043,6 @@ bool CanonicalizeOSSALifetime::computeLiveness(SILValue def) {
10051043
if (def->getOwnershipKind() != OwnershipKind::Owned)
10061044
return false;
10071045

1008-
if (def->isLexical())
1009-
return false;
1010-
10111046
LLVM_DEBUG(llvm::dbgs() << " Canonicalizing: " << def);
10121047

10131048
// Note: There is no need to register callbacks with this utility. 'onDelete'
@@ -1031,6 +1066,9 @@ bool CanonicalizeOSSALifetime::computeLiveness(SILValue def) {
10311066
clearLiveness();
10321067
return false;
10331068
}
1069+
if (def->isLexical()) {
1070+
extendLivenessToDeinitBarriers();
1071+
}
10341072
if (accessBlockAnalysis) {
10351073
extendLivenessThroughOverlappingAccess();
10361074
}

test/SILOptimizer/canonicalize_ossa_lifetime_unit.sil

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
class C {}
44
sil @getOwned : $@convention(thin) () -> @owned C
5+
sil @barrier : $@convention(thin) () -> ()
56

67
// When access scopes are respected, the lifetime which previously extended
78
// beyond the access scope still extends beyond it.
@@ -74,3 +75,43 @@ exit(%out : @owned $C):
7475
destroy_value %instance : $C
7576
return %out : $C
7677
}
78+
79+
// CHECK-LABEL: begin running test 1 of 1 on store_arg_to_out_addr: canonicalize-ossa-lifetime with: true, false, true, @trace
80+
// CHECK-LABEL: sil [ossa] @store_arg_to_out_addr : $@convention(thin) (@owned C) -> @out C {
81+
// CHECK: {{bb[0-9]+}}([[ADDR:%[^,]+]] : $*C, [[INSTANCE:%[^,]+]] :
82+
// CHECK: store [[INSTANCE]] to [init] [[ADDR]]
83+
// CHECK-LABEL: } // end sil function 'store_arg_to_out_addr'
84+
// CHECK-LABEL: end running test 1 of 1 on store_arg_to_out_addr: canonicalize-ossa-lifetime with: true, false, true, @trace
85+
sil [ossa] @store_arg_to_out_addr : $@convention(thin) (@owned C) -> @out C {
86+
bb0(%0 : $*C, %instance : @owned $C):
87+
debug_value [trace] %instance : $C
88+
test_specification "canonicalize-ossa-lifetime true false true @trace"
89+
%copy = copy_value %instance : $C
90+
store %copy to [init] %0 : $*C
91+
destroy_value %instance : $C
92+
%retval = tuple ()
93+
return %retval : $()
94+
}
95+
96+
// CHECK-LABEL: begin running test 1 of 1 on store_arg_to_out_addr_with_barrier: canonicalize-ossa-lifetime with: true, false, true, @trace
97+
// CHECK-LABEL: sil [ossa] @store_arg_to_out_addr_with_barrier : $@convention(thin) (@owned C) -> @out C {
98+
// CHECK: {{bb[0-9]+}}([[ADDR:%[^,]+]] : $*C, [[INSTANCE:%[^,]+]] :
99+
// CHECK: [[BARRIER:%[^,]+]] = function_ref @barrier
100+
// CHECK: [[REGISTER_3:%[^,]+]] = copy_value [[INSTANCE]]
101+
// CHECK: store [[REGISTER_3]] to [init] [[ADDR]]
102+
// CHECK: apply [[BARRIER]]()
103+
// CHECK: destroy_value [[INSTANCE]]
104+
// CHECK-LABEL: } // end sil function 'store_arg_to_out_addr_with_barrier'
105+
// CHECK-LABEL: end running test 1 of 1 on store_arg_to_out_addr_with_barrier: canonicalize-ossa-lifetime with: true, false, true, @trace
106+
sil [ossa] @store_arg_to_out_addr_with_barrier : $@convention(thin) (@owned C) -> @out C {
107+
bb0(%0 : $*C, %instance : @owned $C):
108+
%barrier = function_ref @barrier : $@convention(thin) () -> ()
109+
debug_value [trace] %instance : $C
110+
test_specification "canonicalize-ossa-lifetime true false true @trace"
111+
%copy = copy_value %instance : $C
112+
store %copy to [init] %0 : $*C
113+
apply %barrier() : $@convention(thin) () -> ()
114+
destroy_value %instance : $C
115+
%retval = tuple ()
116+
return %retval : $()
117+
}

test/SILOptimizer/copy_propagation.sil

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

898898
// CHECK-LABEL: sil [ossa] @dont_extend_beyond_nonoverlapping_end_access_after_store_in_consuming_block : {{.*}} {
899899
// CHECK: {{bb[0-9]+}}([[INSTANCE:%[^,]+]] : @owned $C
900-
// CHECK: [[COPY:%[^,]+]] = copy_value [[INSTANCE]]
900+
// CHECK-NOT: copy_value
901901
// CHECK: begin_access
902902
// CHECK-NOT: copy_value
903-
// CHECK: store [[COPY]] to [init] {{%[^,]+}}
903+
// CHECK: store [[INSTANCE]] to [init] {{%[^,]+}}
904904
// CHECK-LABEL: } // end sil function 'dont_extend_beyond_nonoverlapping_end_access_after_store_in_consuming_block'
905905
sil [ossa] @dont_extend_beyond_nonoverlapping_end_access_after_store_in_consuming_block : $@convention(thin) (@owned C) -> () {
906906
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)