Skip to content

Commit 1034d65

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 98fdc61 commit 1034d65

File tree

13 files changed

+178
-65
lines changed

13 files changed

+178
-65
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/MoveOnlyObjectChecker.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,8 @@ struct OSSACanonicalizer {
6060
InstructionDeleter &deleter) {
6161
canonicalizer.emplace(false /*pruneDebugMode*/,
6262
!fn->shouldOptimize() /*maximizeLifetime*/,
63-
nullptr /*accessBlockAnalysis*/, domTree, deleter);
63+
nullptr /*accessBlockAnalysis*/, domTree,
64+
nullptr /*calleeAnalysis*/, deleter);
6465
}
6566

6667
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: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -270,6 +270,23 @@ struct TestSpecificationTest : UnitTest {
270270
// MARK: OSSA Lifetime Unit Tests
271271
//===----------------------------------------------------------------------===//
272272

273+
// Arguments:
274+
// - the lexical borrow to fold
275+
// Dumpts:
276+
// - the function
277+
struct LexicalDestroyFoldingTest : UnitTest {
278+
LexicalDestroyFoldingTest(UnitTestRunner *pass) : UnitTest(pass) {}
279+
void invoke(Arguments &arguments) override {
280+
auto *dominanceAnalysis = getAnalysis<DominanceAnalysis>();
281+
DominanceInfo *domTree = dominanceAnalysis->get(getFunction());
282+
auto value = arguments.takeValue();
283+
auto *bbi = cast<BeginBorrowInst>(value);
284+
InstructionDeleter deleter;
285+
foldDestroysOfCopiedLexicalBorrow(bbi, *domTree, deleter);
286+
getFunction()->dump();
287+
}
288+
};
289+
273290
// Arguments:
274291
// - variadic list of - instruction: a last user
275292
// Dumps:
@@ -387,13 +404,15 @@ struct CanonicalizeOSSALifetimeTest : UnitTest {
387404
auto *accessBlockAnalysis = getAnalysis<NonLocalAccessBlockAnalysis>();
388405
auto *dominanceAnalysis = getAnalysis<DominanceAnalysis>();
389406
DominanceInfo *domTree = dominanceAnalysis->get(getFunction());
407+
auto *calleeAnalysis = getAnalysis<BasicCalleeAnalysis>();
390408
auto pruneDebug = arguments.takeBool();
391409
auto maximizeLifetimes = arguments.takeBool();
392410
auto respectAccessScopes = arguments.takeBool();
393411
InstructionDeleter deleter;
394412
CanonicalizeOSSALifetime canonicalizer(
395413
pruneDebug, maximizeLifetimes,
396-
respectAccessScopes ? accessBlockAnalysis : nullptr, domTree, deleter);
414+
respectAccessScopes ? accessBlockAnalysis : nullptr, domTree,
415+
calleeAnalysis, deleter);
397416
auto value = arguments.takeValue();
398417
canonicalizer.canonicalizeValueLifetime(value);
399418
getFunction()->dump();
@@ -759,6 +778,7 @@ void UnitTestRunner::withTest(StringRef name, Doit doit) {
759778
ADD_UNIT_TEST_SUBCLASS("function-get-self-argument-index", FunctionGetSelfArgumentIndex)
760779
ADD_UNIT_TEST_SUBCLASS("interior-liveness", InteriorLivenessTest)
761780
ADD_UNIT_TEST_SUBCLASS("is-deinit-barrier", IsDeinitBarrierTest)
781+
ADD_UNIT_TEST_SUBCLASS("lexical-destroy-folding", LexicalDestroyFoldingTest)
762782
ADD_UNIT_TEST_SUBCLASS("linear-liveness", LinearLivenessTest)
763783
ADD_UNIT_TEST_SUBCLASS("multidef-liveness", MultiDefLivenessTest)
764784
ADD_UNIT_TEST_SUBCLASS("pruned-liveness-boundary-with-list-of-last-users-insertion-points", PrunedLivenessBoundaryWithListOfLastUsersInsertionPointsTest)

lib/SILOptimizer/Utils/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ target_sources(swiftSILOptimizer PRIVATE
99
CompileTimeInterpolationUtils.cpp
1010
ConstantFolding.cpp
1111
ConstExpr.cpp
12+
DeinitBarrierFinding.cpp
1213
Devirtualize.cpp
1314
DifferentiationMangler.cpp
1415
DistributedActor.cpp

lib/SILOptimizer/Utils/CanonicalizeOSSALifetime.cpp

Lines changed: 79 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@
7272
#include "swift/SIL/PrunedLiveness.h"
7373
#include "swift/SILOptimizer/Utils/CFGOptUtils.h"
7474
#include "swift/SILOptimizer/Utils/DebugOptUtils.h"
75+
#include "swift/SILOptimizer/Utils/DeinitBarrierFinding.h"
7576
#include "swift/SILOptimizer/Utils/InstructionDeleter.h"
7677
#include "swift/SILOptimizer/Utils/ValueLifetime.h"
7778
#include "llvm/ADT/Statistic.h"
@@ -222,6 +223,81 @@ bool CanonicalizeOSSALifetime::computeCanonicalLiveness() {
222223
return true;
223224
}
224225

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

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

10021075
// Note: There is no need to register callbacks with this utility. 'onDelete'
@@ -1020,6 +1093,9 @@ bool CanonicalizeOSSALifetime::computeLiveness(SILValue def) {
10201093
clearLiveness();
10211094
return false;
10221095
}
1096+
if (def->isLexical()) {
1097+
extendLivenessToDeinitBarriers();
1098+
}
10231099
if (accessBlockAnalysis) {
10241100
extendLivenessThroughOverlappingAccess();
10251101
}

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_folding.sil

Lines changed: 0 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -519,51 +519,6 @@ exit:
519519
return %retval : $()
520520
}
521521

522-
// Fold apply when guaranteed lexical value used in one but not two branches
523-
// and the lexical scope ends before the use on the non-lexical branch.
524-
//
525-
// CHECK-LABEL: sil [ossa] @nofold_two_parallel_owned_uses_one_lexical___scope_ends_before_use : {{.*}} {
526-
// CHECK: {{bb[0-9]+}}([[INSTANCE:%[^,]+]] :
527-
// CHECK: [[COPY:%[^,]+]] = copy_value [[INSTANCE]]
528-
// CHECK: [[MOVE:%[^,]+]] = move_value [lexical] [[INSTANCE]]
529-
// CHECK: [[CALLEE_OWNED:%[^,]+]] = function_ref @callee_owned
530-
// CHECK: cond_br undef, [[LEFT:bb[0-9]+]], [[RIGHT:bb[0-9]+]]
531-
// CHECK: [[LEFT]]:
532-
// CHECK: apply [[CALLEE_OWNED]]([[MOVE]])
533-
// CHECK: destroy_value [[COPY]]
534-
// CHECK: br [[EXIT:bb[0-9]+]]
535-
// CHECK: [[RIGHT]]:
536-
// CHECK: destroy_value [[MOVE]]
537-
// CHECK: apply [[CALLEE_OWNED]]([[COPY]])
538-
// CHECK: br [[EXIT]]
539-
// CHECK: [[EXIT]]:
540-
// CHECK-LABEL: } // end sil function 'nofold_two_parallel_owned_uses_one_lexical___scope_ends_before_use'
541-
sil [ossa] @nofold_two_parallel_owned_uses_one_lexical___scope_ends_before_use : $@convention(thin) (@owned C) -> () {
542-
entry(%instance : @owned $C):
543-
%copy_2 = copy_value %instance : $C
544-
%lifetime = begin_borrow [lexical] %instance : $C
545-
%callee_owned = function_ref @callee_owned : $@convention(thin) (@owned C) -> ()
546-
cond_br undef, left, right
547-
548-
left:
549-
%copy_1 = copy_value %lifetime : $C
550-
apply %callee_owned(%copy_1) : $@convention(thin) (@owned C) -> ()
551-
end_borrow %lifetime : $C
552-
destroy_value %instance : $C
553-
destroy_value %copy_2 : $C
554-
br exit
555-
556-
right:
557-
end_borrow %lifetime : $C
558-
destroy_value %instance : $C
559-
apply %callee_owned(%copy_2) : $@convention(thin) (@owned C) -> ()
560-
br exit
561-
562-
exit:
563-
%retval = tuple ()
564-
return %retval : $()
565-
}
566-
567522
// Fold even if there is a noncanonicalized copy which is otherwise used.
568523
// CHECK-LABEL: sil [ossa] @fold_noncanonical_copy_with_other_use : {{.*}} {
569524
// CHECK: {{bb[0-9]+}}:

0 commit comments

Comments
 (0)