Skip to content

Commit c559402

Browse files
committed
[CopyProp] Don't extend lifetime over end_access.
If there is a consuming use of the value inside an access scope, the lifetime should not be extended over the end_access instruction.
1 parent 064b713 commit c559402

File tree

3 files changed

+115
-19
lines changed

3 files changed

+115
-19
lines changed

include/swift/SILOptimizer/Utils/CanonicalOSSALifetime.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -298,6 +298,11 @@ class CanonicalizeOSSALifetime {
298298
/// ending". end_borrows do not end a liverange that may include owned copies.
299299
PrunedLiveness liveness;
300300

301+
/// The destroys of the value. These are not uses, but need to be recorded so
302+
/// that we know when the last use in a consuming block is (without having to
303+
/// repeatedly do use-def walks from destroys).
304+
SmallPtrSet<DestroyValueInst *, 8> destroys;
305+
301306
/// remnantLiveOutBlocks are part of the original extended lifetime that are
302307
/// not in canonical pruned liveness. There is a path from a PrunedLiveness
303308
/// boundary to an original destroy that passes through a remnant block.

lib/SILOptimizer/Utils/CanonicalOSSALifetime.cpp

Lines changed: 61 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -162,8 +162,10 @@ bool CanonicalizeOSSALifetime::computeCanonicalLiveness() {
162162
liveness.updateForUse(user, /*lifetimeEnding*/ true);
163163
break;
164164
case OperandOwnership::DestroyingConsume:
165-
// destroy_value does not force pruned liveness (but store etc. does).
166-
if (!isa<DestroyValueInst>(user)) {
165+
if (auto *dvi = dyn_cast<DestroyValueInst>(user)) {
166+
destroys.insert(dvi);
167+
} else {
168+
// destroy_value does not force pruned liveness (but store etc. does).
167169
liveness.updateForUse(user, /*lifetimeEnding*/ true);
168170
}
169171
recordConsumingUse(use);
@@ -335,18 +337,33 @@ void CanonicalizeOSSALifetime::extendLivenessThroughOverlappingAccess() {
335337
bool changed = true;
336338
while (changed) {
337339
changed = false;
338-
blockWorklist.initializeRange(consumingBlocks);
339-
while (auto *bb = blockWorklist.pop()) {
340+
// The blocks in which we may have to extend liveness over access scopes.
341+
//
342+
// It must be populated first so that we can test membership during the loop
343+
// (see findLastConsume).
344+
BasicBlockSetVector blocksToVisit(currentDef->getFunction());
345+
for (auto *block : consumingBlocks) {
346+
blocksToVisit.insert(block);
347+
}
348+
for (auto iterator = blocksToVisit.begin(); iterator != blocksToVisit.end();
349+
++iterator) {
350+
auto *bb = *iterator;
351+
// If the block isn't dead, then we won't need to extend liveness within
352+
// any of its predecessors (though we may within it).
353+
if (liveness.getBlockLiveness(bb) != PrunedLiveBlocks::Dead)
354+
continue;
355+
// Continue searching upward to find the pruned liveness boundary.
356+
for (auto *predBB : bb->getPredecessorBlocks()) {
357+
blocksToVisit.insert(predBB);
358+
}
359+
}
360+
for (auto *bb : blocksToVisit) {
340361
auto blockLiveness = liveness.getBlockLiveness(bb);
341362
// Ignore blocks within pruned liveness.
342363
if (blockLiveness == PrunedLiveBlocks::LiveOut) {
343364
continue;
344365
}
345366
if (blockLiveness == PrunedLiveBlocks::Dead) {
346-
// Continue searching upward to find the pruned liveness boundary.
347-
for (auto *predBB : bb->getPredecessorBlocks()) {
348-
blockWorklist.insert(predBB);
349-
}
350367
// Otherwise, ignore dead blocks with no nonlocal end_access.
351368
if (!accessBlocks->containsNonLocalEndAccess(bb)) {
352369
continue;
@@ -356,7 +373,43 @@ void CanonicalizeOSSALifetime::extendLivenessThroughOverlappingAccess() {
356373
// Find the latest partially overlapping access scope, if one exists:
357374
// use %def // pruned liveness ends here
358375
// end_access
376+
377+
// Whether to look for the last consume in the block.
378+
//
379+
// If the block is consuming, we need to determine whether any subsequent
380+
// block contains a destroy_value. If any does, then we need to count
381+
// every end_access in this block as a use. If none do, then we only
382+
// need to count end_accesses which occur before the consuming use in
383+
// this block as uses.
384+
bool findLastConsume =
385+
consumingBlocks.contains(bb) &&
386+
llvm::none_of(bb->getSuccessorBlocks(), [&](auto *successor) {
387+
// If a block was originally live, then it either contained or was
388+
// backwards reachable from a destroy or a use. If it was dead,
389+
// then it was only backward reachable from a destroy.
390+
//
391+
// If we've seen a destroy, then we've seen the last consuming use.
392+
return originalLiveBlocks.contains(successor) &&
393+
liveness.getBlockLiveness(successor) ==
394+
PrunedLiveBlocks::Dead;
395+
});
359396
for (auto &inst : llvm::reverse(*bb)) {
397+
if (findLastConsume) {
398+
// The value is consumed in the block, and we haven't already seen the
399+
// consuming use.
400+
//
401+
// If this instruction is the consuming use, note that we've seen it.
402+
//
403+
// We only need to check for destroys because we exit the loop if the
404+
// use is interesting.
405+
if (auto *dvi = dyn_cast<DestroyValueInst>(&inst))
406+
findLastConsume = !destroys.contains(dvi);
407+
// Either this instruction is the consuming use or we haven't seen the
408+
// consuming use yet. If it's the consuming use, it's not an
409+
// overlapping end_access. If we haven't seen it yet, if it's an
410+
// end_access, it doesn't overlap.
411+
continue;
412+
}
360413
// Stop at the latest use. An earlier end_access does not overlap.
361414
if (blockHasUse && liveness.isInterestingUser(&inst)) {
362415
break;

test/SILOptimizer/copy_propagation.sil

Lines changed: 49 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -616,24 +616,21 @@ bb2:
616616

617617
// Original Overlapping (unnecessarilly extends pruned liveness):
618618
//
619-
// TODO: this copy could be avoided but is probably an unusual case,
620-
// and sinking the destroy outside the access scope might help to
621-
// optimize the access itself.
622-
//
623619
// %def
624620
// begin_access // access scope unrelated to def
625621
// use %def // pruned liveness ends here
626622
// destroy %def
627623
// end_access
628624
//
629625
// CHECK-LABEL: sil [ossa] @testOriginalOverlapInLiveBlock : $@convention(thin) () -> () {
630-
// CHECK: [[DEF:%.*]] = apply %{{.*}}() : $@convention(thin) () -> @owned AnyObject
631-
// CHECK: begin_access
632-
// CHECK: [[COPY:%.*]] = copy_value [[DEF]] : $AnyObject
633-
// CHECK: store [[COPY]] to [init] %{{.*}} : $*AnyObject
634-
// CHECK: end_access
635-
// CHECK: destroy_value [[DEF]] : $AnyObject
636-
// CHECK: br bb1
626+
// CHECK: [[DEF:%.*]] = apply %{{.*}}() : $@convention(thin) () -> @owned AnyObject
627+
// CHECK: begin_access
628+
// CHECK-OPT: store [[DEF]] to [init] %{{.*}} : $*AnyObject
629+
// CHECK-ONONE: [[COPY:%[^,]+]] = copy_value [[DEF]]
630+
// CHECK-ONONE: store [[COPY]] to [init] %{{.*}} : $*AnyObject
631+
// CHECK-ONONE: destroy_value [[DEF]]
632+
// CHECK: end_access
633+
// CHECK: br bb1
637634
// CHECK-LABEL: } // end sil function 'testOriginalOverlapInLiveBlock'
638635
sil [ossa] @testOriginalOverlapInLiveBlock : $@convention(thin) () -> () {
639636
bb0:
@@ -901,3 +898,44 @@ exit:
901898
%retval = tuple ()
902899
return %retval : $()
903900
}
901+
902+
// CHECK-LABEL: sil [ossa] @dont_extend_beyond_nonoverlapping_end_access_after_store_in_consuming_block : {{.*}} {
903+
// CHECK: {{bb[0-9]+}}([[INSTANCE:%[^,]+]] : @owned $C
904+
// CHECK: [[COPY:%[^,]+]] = copy_value [[INSTANCE]]
905+
// CHECK: begin_access
906+
// CHECK-NOT: copy_value
907+
// CHECK: store [[COPY]] to [init] {{%[^,]+}}
908+
// CHECK-LABEL: } // end sil function 'dont_extend_beyond_nonoverlapping_end_access_after_store_in_consuming_block'
909+
sil [ossa] @dont_extend_beyond_nonoverlapping_end_access_after_store_in_consuming_block : $@convention(thin) (@owned C) -> () {
910+
bb0(%instance : @owned $C):
911+
%addr = alloc_stack [lexical] $C, var
912+
%borrow = begin_borrow %instance : $C
913+
%copy = copy_value %borrow : $C
914+
end_borrow %borrow : $C
915+
destroy_value %instance : $C
916+
%access = begin_access [static] [modify] %addr : $*C
917+
store %copy to [init] %addr : $*C
918+
end_access %access : $*C
919+
destroy_addr %addr : $*C
920+
dealloc_stack %addr : $*C
921+
%retval = tuple()
922+
return %retval : $()
923+
}
924+
925+
// CHECK-LABEL: sil [ossa] @dont_extend_beyond_nonoverlapping_endaccess_after_destroyvalue_in_consuming_block : $@convention(thin) () -> () {
926+
// CHECK: begin_access
927+
// CHECK: destroy_value
928+
// CHECK: end_access
929+
// CHECK-NOT: destroy_value
930+
// CHECK-LABEL: } // end sil function 'dont_extend_beyond_nonoverlapping_endaccess_after_destroyvalue_in_consuming_block'
931+
sil [ossa] @dont_extend_beyond_nonoverlapping_endaccess_after_destroyvalue_in_consuming_block : $@convention(thin) () -> () {
932+
%instance = apply undef() : $@convention(thin) () -> @owned C
933+
%addr = alloc_stack [lexical] $C, var
934+
%access = begin_access [static] [modify] %addr : $*C
935+
apply undef(%instance) : $@convention(thin) (@guaranteed C) -> ()
936+
destroy_value %instance : $C
937+
end_access %access : $*C
938+
dealloc_stack %addr : $*C
939+
%retval = tuple()
940+
return %retval : $()
941+
}

0 commit comments

Comments
 (0)