Skip to content

Commit ec3f005

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 f88ca51 commit ec3f005

File tree

13 files changed

+118
-28
lines changed

13 files changed

+118
-28
lines changed

include/swift/SILOptimizer/Utils/CanonicalizeOSSALifetime.h

Lines changed: 11 additions & 2 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 = nullptr;
227229

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

230234
/// The SILValue to canonicalize.
@@ -296,10 +300,12 @@ class CanonicalizeOSSALifetime final {
296300
CanonicalizeOSSALifetime(bool pruneDebugMode, bool maximizeLifetime,
297301
SILFunction *function,
298302
NonLocalAccessBlockAnalysis *accessBlockAnalysis,
299-
DominanceInfo *domTree, InstructionDeleter &deleter)
303+
DominanceInfo *domTree,
304+
BasicCalleeAnalysis *calleeAnalysis,
305+
InstructionDeleter &deleter)
300306
: pruneDebugMode(pruneDebugMode), maximizeLifetime(maximizeLifetime),
301307
accessBlockAnalysis(accessBlockAnalysis), domTree(domTree),
302-
deleter(deleter) {}
308+
calleeAnalysis(calleeAnalysis), deleter(deleter) {}
303309

304310
SILValue getCurrentDef() const { return currentDef; }
305311

@@ -405,6 +411,9 @@ class CanonicalizeOSSALifetime final {
405411
void findExtendedBoundary(PrunedLivenessBoundary const &originalBoundary,
406412
PrunedLivenessBoundary &boundary);
407413

414+
void findDestroysOutsideBoundary(SmallVectorImpl<SILInstruction *> &destroys);
415+
void extendLivenessToDeinitBarriers();
416+
408417
void extendUnconsumedLiveness(PrunedLivenessBoundary const &boundary);
409418

410419
void insertDestroysOnBoundary(PrunedLivenessBoundary const &boundary);

lib/SILOptimizer/Mandatory/MoveOnlyObjectCheckerUtils.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,8 @@ struct OSSACanonicalizer {
5050
InstructionDeleter &deleter)
5151
: canonicalizer(false /*pruneDebugMode*/,
5252
!fn->shouldOptimize() /*maximizeLifetime*/, fn,
53-
nullptr /*accessBlockAnalysis*/, domTree, deleter) {}
53+
nullptr /*accessBlockAnalysis*/, domTree,
54+
nullptr /*calleeAnalysis*/, deleter) {}
5455

5556
void clear() {
5657
consumingUsesNeedingCopy.clear();

lib/SILOptimizer/SILCombiner/SILCombine.cpp

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

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
@@ -432,6 +432,7 @@ void CopyPropagation::run() {
432432
auto *postOrderAnalysis = getAnalysis<PostOrderAnalysis>();
433433
auto *accessBlockAnalysis = getAnalysis<NonLocalAccessBlockAnalysis>();
434434
auto *dominanceAnalysis = getAnalysis<DominanceAnalysis>();
435+
auto *calleeAnalysis = getAnalysis<BasicCalleeAnalysis>();
435436
DominanceInfo *domTree = dominanceAnalysis->get(f);
436437

437438
// Label for unit testing with debug output.
@@ -475,9 +476,7 @@ void CopyPropagation::run() {
475476
// don't need to explicitly check for changes.
476477
CanonicalizeOSSALifetime canonicalizer(
477478
pruneDebug, /*maximizeLifetime=*/!getFunction()->shouldOptimize(),
478-
getFunction(), accessBlockAnalysis, domTree, deleter);
479-
auto *calleeAnalysis = getAnalysis<BasicCalleeAnalysis>();
480-
479+
getFunction(), accessBlockAnalysis, domTree, calleeAnalysis, deleter);
481480
// NOTE: We assume that the function is in reverse post order so visiting the
482481
// blocks and pushing begin_borrows as we see them and then popping them
483482
// 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
@@ -495,13 +495,15 @@ struct CanonicalizeOSSALifetimeTest : UnitTest {
495495
auto *accessBlockAnalysis = getAnalysis<NonLocalAccessBlockAnalysis>();
496496
auto *dominanceAnalysis = getAnalysis<DominanceAnalysis>();
497497
DominanceInfo *domTree = dominanceAnalysis->get(getFunction());
498+
auto *calleeAnalysis = getAnalysis<BasicCalleeAnalysis>();
498499
auto pruneDebug = arguments.takeBool();
499500
auto maximizeLifetimes = arguments.takeBool();
500501
auto respectAccessScopes = arguments.takeBool();
501502
InstructionDeleter deleter;
502503
CanonicalizeOSSALifetime canonicalizer(
503504
pruneDebug, maximizeLifetimes, getFunction(),
504-
respectAccessScopes ? accessBlockAnalysis : nullptr, domTree, deleter);
505+
respectAccessScopes ? accessBlockAnalysis : nullptr, domTree,
506+
calleeAnalysis, deleter);
505507
auto value = arguments.takeValue();
506508
canonicalizer.canonicalizeValueLifetime(value);
507509
getFunction()->dump();
@@ -900,6 +902,7 @@ void UnitTestRunner::withTest(StringRef name, Doit doit) {
900902
ADD_UNIT_TEST_SUBCLASS("interior-liveness", InteriorLivenessTest)
901903
ADD_UNIT_TEST_SUBCLASS("is-deinit-barrier", IsDeinitBarrierTest)
902904
ADD_UNIT_TEST_SUBCLASS("is-lexical", IsLexicalTest)
905+
ADD_UNIT_TEST_SUBCLASS("lexical-destroy-folding", LexicalDestroyFoldingTest)
903906
ADD_UNIT_TEST_SUBCLASS("linear-liveness", LinearLivenessTest)
904907
ADD_UNIT_TEST_SUBCLASS("multidef-liveness", MultiDefLivenessTest)
905908
ADD_UNIT_TEST_SUBCLASS("multidefuse-liveness", MultiDefUseLivenessTest)

lib/SILOptimizer/Utils/CanonicalizeOSSALifetime.cpp

Lines changed: 44 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"
@@ -232,6 +234,45 @@ bool CanonicalizeOSSALifetime::computeCanonicalLiveness() {
232234
return true;
233235
}
234236

237+
void CanonicalizeOSSALifetime::findDestroysOutsideBoundary(
238+
SmallVectorImpl<SILInstruction *> &outsideDestroys) {
239+
for (auto destroy : destroys) {
240+
if (liveness->isWithinBoundary(destroy))
241+
continue;
242+
outsideDestroys.push_back(destroy);
243+
}
244+
}
245+
246+
void CanonicalizeOSSALifetime::extendLivenessToDeinitBarriers() {
247+
SmallVector<SILInstruction *, 4> outsideDestroys;
248+
findDestroysOutsideBoundary(outsideDestroys);
249+
250+
auto *def = getCurrentDef()->getDefiningInstruction();
251+
using InitialBlocks = ArrayRef<SILBasicBlock *>;
252+
auto *defBlock = getCurrentDef()->getParentBlock();
253+
auto initialBlocks = defBlock ? InitialBlocks(defBlock) : InitialBlocks();
254+
ReachableBarriers barriers;
255+
findBarriersBackward(outsideDestroys, initialBlocks,
256+
*getCurrentDef()->getFunction(), barriers,
257+
[&](auto *inst) {
258+
if (inst == def)
259+
return true;
260+
return isDeinitBarrier(inst, calleeAnalysis);
261+
});
262+
for (auto *barrier : barriers.instructions) {
263+
liveness->updateForUse(barrier, /*lifetimeEnding*/ false);
264+
}
265+
for (auto *barrier : barriers.phis) {
266+
for (auto *predecessor : barrier->getPredecessorBlocks()) {
267+
liveness->updateForUse(predecessor->getTerminator(),
268+
/*lifetimeEnding*/ false);
269+
}
270+
}
271+
// Ignore barriers.edges. The beginning of the targets of such edges should
272+
// not be added to liveness. These edges will be rediscovered when computing
273+
// the liveness boundary.
274+
}
275+
235276
// Return true if \p inst is an end_access whose access scope overlaps the end
236277
// of the pruned live range. This means that a hoisted destroy might execute
237278
// within the access scope which previously executed outside the access scope.
@@ -1032,9 +1073,6 @@ bool CanonicalizeOSSALifetime::computeLiveness() {
10321073
if (currentDef->getOwnershipKind() != OwnershipKind::Owned)
10331074
return false;
10341075

1035-
if (currentDef->isLexical())
1036-
return false;
1037-
10381076
LLVM_DEBUG(llvm::dbgs() << " Canonicalizing: " << currentDef);
10391077

10401078
// Note: There is no need to register callbacks with this utility. 'onDelete'
@@ -1057,6 +1095,9 @@ bool CanonicalizeOSSALifetime::computeLiveness() {
10571095
invalidateLiveness();
10581096
return false;
10591097
}
1098+
if (currentDef->isLexical()) {
1099+
extendLivenessToDeinitBarriers();
1100+
}
10601101
if (accessBlockAnalysis) {
10611102
extendLivenessThroughOverlappingAccess();
10621103
}

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
@@ -917,10 +917,10 @@ exit:
917917

918918
// CHECK-LABEL: sil [ossa] @dont_extend_beyond_nonoverlapping_end_access_after_store_in_consuming_block : {{.*}} {
919919
// CHECK: {{bb[0-9]+}}([[INSTANCE:%[^,]+]] : @owned $C
920-
// CHECK: [[COPY:%[^,]+]] = copy_value [[INSTANCE]]
920+
// CHECK-NOT: copy_value
921921
// CHECK: begin_access
922922
// CHECK-NOT: copy_value
923-
// CHECK: store [[COPY]] to [init] {{%[^,]+}}
923+
// CHECK: store [[INSTANCE]] to [init] {{%[^,]+}}
924924
// CHECK-LABEL: } // end sil function 'dont_extend_beyond_nonoverlapping_end_access_after_store_in_consuming_block'
925925
sil [ossa] @dont_extend_beyond_nonoverlapping_end_access_after_store_in_consuming_block : $@convention(thin) (@owned C) -> () {
926926
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/copy_propagation_value.sil

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -26,18 +26,16 @@ class CompileError : Error {}
2626
// This test case used to have an invalid boundary extension.
2727
// CHECK-LABEL: sil [ossa] @canonicalize_borrow_of_copy_with_interesting_boundary : $@convention(thin) (@owned C) -> (@owned NonTrivialStruct, @error any Error) {
2828
// CHECK: {{bb[0-9]+}}([[INSTANCE:%[^,]+]] :
29-
// CHECK: [[COPY:%[^,]+]] = copy_value [[INSTANCE]]
30-
// CHECK: destroy_value [[INSTANCE]]
3129
// CHECK: cond_br undef, [[SUCCESS:bb[0-9]+]], [[FAILURE:bb[0-9]+]]
3230
// CHECK: [[SUCCESS]]:
33-
// CHECK: [[BORROW:%[^,]+]] = begin_borrow [[COPY]]
31+
// CHECK: [[BORROW:%[^,]+]] = begin_borrow [[INSTANCE]]
3432
// CHECK: [[STRUCT:%[^,]+]] = struct $NonTrivialStruct ([[BORROW]] : $C)
3533
// CHECK: [[STRUCT_OUT:%[^,]+]] = copy_value [[STRUCT]]
3634
// CHECK: end_borrow [[BORROW]]
37-
// CHECK: destroy_value [[COPY]]
35+
// CHECK: destroy_value [[INSTANCE]]
3836
// CHECK: return [[STRUCT_OUT]]
3937
// CHECK: [[FAILURE]]:
40-
// CHECK: destroy_value [[COPY]]
38+
// CHECK: destroy_value [[INSTANCE]]
4139
// CHECK: [[BOX:%[^,]+]] = alloc_existential_box
4240
// CHECK: throw [[BOX]]
4341
// CHECK-LABEL: } // end sil function 'canonicalize_borrow_of_copy_with_interesting_boundary'

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)