Skip to content

Commit a2d383e

Browse files
authored
Merge pull request #26810 from jckarter/class-let-property-borrow
[WIP] SemanticARCOpts: Don't copy let properties of classes.
2 parents 8da33a9 + 7ca6587 commit a2d383e

File tree

4 files changed

+388
-14
lines changed

4 files changed

+388
-14
lines changed

include/swift/SIL/MemAccessUtils.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -298,6 +298,8 @@ class AccessedStorage {
298298
llvm_unreachable("unhandled kind");
299299
}
300300

301+
bool isLetAccess(SILFunction *F) const;
302+
301303
bool isUniquelyIdentified() const {
302304
switch (getKind()) {
303305
case Box:

lib/SIL/MemAccessUtils.cpp

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -462,15 +462,15 @@ AccessedStorage swift::findAccessedStorageNonNested(SILValue sourceAddr) {
462462
}
463463

464464
// Return true if the given access is on a 'let' lvalue.
465-
static bool isLetAccess(const AccessedStorage &storage, SILFunction *F) {
466-
if (auto *decl = dyn_cast_or_null<VarDecl>(storage.getDecl()))
465+
bool AccessedStorage::isLetAccess(SILFunction *F) const {
466+
if (auto *decl = dyn_cast_or_null<VarDecl>(getDecl()))
467467
return decl->isLet();
468468

469469
// It's unclear whether a global will ever be missing it's varDecl, but
470470
// technically we only preserve it for debug info. So if we don't have a decl,
471471
// check the flag on SILGlobalVariable, which is guaranteed valid,
472-
if (storage.getKind() == AccessedStorage::Global)
473-
return storage.getGlobal()->isLet();
472+
if (getKind() == AccessedStorage::Global)
473+
return getGlobal()->isLet();
474474

475475
return false;
476476
}
@@ -560,7 +560,7 @@ bool swift::isPossibleFormalAccessBase(const AccessedStorage &storage,
560560
// Additional checks that apply to anything that may fall through.
561561

562562
// Immutable values are only accessed for initialization.
563-
if (isLetAccess(storage, F))
563+
if (storage.isLetAccess(F))
564564
return false;
565565

566566
return true;

lib/SILOptimizer/Mandatory/SemanticARCOpts.cpp

Lines changed: 97 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
#define DEBUG_TYPE "sil-semantic-arc-opts"
1414
#include "swift/Basic/STLExtras.h"
1515
#include "swift/SIL/BasicBlockUtils.h"
16+
#include "swift/SIL/BranchPropagatedUser.h"
1617
#include "swift/SIL/MemAccessUtils.h"
1718
#include "swift/SIL/OwnershipUtils.h"
1819
#include "swift/SIL/SILArgument.h"
@@ -122,10 +123,23 @@ namespace {
122123

123124
struct SemanticARCOptVisitor
124125
: SILInstructionVisitor<SemanticARCOptVisitor, bool> {
126+
SILFunction &F;
127+
Optional<DeadEndBlocks> TheDeadEndBlocks;
128+
129+
explicit SemanticARCOptVisitor(SILFunction &F) : F(F) {}
130+
131+
DeadEndBlocks &getDeadEndBlocks() {
132+
if (!TheDeadEndBlocks)
133+
TheDeadEndBlocks.emplace(&F);
134+
return *TheDeadEndBlocks;
135+
}
136+
125137
bool visitSILInstruction(SILInstruction *i) { return false; }
126138
bool visitCopyValueInst(CopyValueInst *cvi);
127139
bool visitBeginBorrowInst(BeginBorrowInst *bbi);
128140
bool visitLoadInst(LoadInst *li);
141+
142+
bool isWrittenTo(LoadInst *li);
129143
};
130144

131145
} // end anonymous namespace
@@ -367,10 +381,12 @@ bool mayFunctionMutateArgument(const AccessedStorage &storage, SILFunction &f) {
367381
return true;
368382
}
369383

370-
static bool isWrittenTo(SILFunction &f, SILValue value) {
384+
bool SemanticARCOptVisitor::isWrittenTo(LoadInst *load) {
385+
auto addr = load->getOperand();
386+
371387
// Then find our accessed storage. If we can not find anything, be
372388
// conservative and assume that the value is written to.
373-
const auto &storage = findAccessedStorageNonNested(value);
389+
const auto &storage = findAccessedStorageNonNested(addr);
374390
if (!storage)
375391
return true;
376392

@@ -379,17 +395,86 @@ static bool isWrittenTo(SILFunction &f, SILValue value) {
379395
// memory). We have to do this since load_borrow assumes that the
380396
// underlying memory is never written to.
381397
switch (storage.getKind()) {
398+
case AccessedStorage::Class: {
399+
// We know a let property won't be written to if the base object is
400+
// guaranteed for the duration of the access.
401+
if (!storage.isLetAccess(&F))
402+
return true;
403+
404+
auto baseObject = stripCasts(storage.getObject());
405+
// A guaranteed argument trivially keeps the base alive for the duration of
406+
// the projection.
407+
if (auto *arg = dyn_cast<SILFunctionArgument>(baseObject)) {
408+
if (arg->getArgumentConvention().isGuaranteedConvention()) {
409+
return false;
410+
}
411+
}
412+
413+
// See if there's a borrow of the base object our load is based on.
414+
SILValue borrowInst;
415+
if (isa<BeginBorrowInst>(baseObject)
416+
|| isa<LoadBorrowInst>(baseObject)) {
417+
borrowInst = baseObject;
418+
} else {
419+
// TODO: We should walk the projection path again to get to the
420+
// originating borrow, if any
421+
422+
BeginBorrowInst *singleBorrow = nullptr;
423+
for (auto *use : baseObject->getUses()) {
424+
if (auto *borrow = dyn_cast<BeginBorrowInst>(use->getUser())) {
425+
if (!singleBorrow) {
426+
singleBorrow = borrow;
427+
} else {
428+
singleBorrow = nullptr;
429+
break;
430+
}
431+
}
432+
}
433+
434+
borrowInst = singleBorrow;
435+
}
436+
437+
// Use the linear lifetime checker to check whether the copied
438+
// value is dominated by the lifetime of the borrow it's based on.
439+
if (!borrowInst)
440+
return true;
441+
442+
SmallVector<BranchPropagatedUser, 4> baseEndBorrows;
443+
for (auto *use : borrowInst->getUses()) {
444+
if (isa<EndBorrowInst>(use->getUser())) {
445+
baseEndBorrows.push_back(BranchPropagatedUser(use->getUser()));
446+
}
447+
}
448+
449+
SmallVector<BranchPropagatedUser, 4> valueDestroys;
450+
for (auto *use : load->getUses()) {
451+
if (isa<DestroyValueInst>(use->getUser())) {
452+
valueDestroys.push_back(BranchPropagatedUser(use->getUser()));
453+
}
454+
}
455+
456+
SmallPtrSet<SILBasicBlock *, 4> visitedBlocks;
457+
458+
return valueHasLinearLifetime(baseObject, baseEndBorrows, valueDestroys,
459+
visitedBlocks, getDeadEndBlocks(),
460+
ownership::ErrorBehaviorKind::ReturnFalse)
461+
.getFoundError();
462+
}
463+
case AccessedStorage::Global:
464+
// Any legal load of a global let should have happened after its
465+
// initialization, at which point it can't be written to again for the
466+
// lifetime of the program.
467+
return !storage.isLetAccess(&F);
468+
382469
case AccessedStorage::Box:
383470
case AccessedStorage::Stack:
384-
case AccessedStorage::Global:
385-
case AccessedStorage::Class:
386471
case AccessedStorage::Yield:
387472
case AccessedStorage::Nested:
388473
case AccessedStorage::Unidentified:
389474
return true;
390-
475+
391476
case AccessedStorage::Argument:
392-
return mayFunctionMutateArgument(storage, f);
477+
return mayFunctionMutateArgument(storage, F);
393478
}
394479
llvm_unreachable("covered switch");
395480
}
@@ -413,7 +498,7 @@ bool SemanticARCOptVisitor::visitLoadInst(LoadInst *li) {
413498

414499
// Then check if our address is ever written to. If it is, then we
415500
// can not use the load_borrow.
416-
if (isWrittenTo(*li->getFunction(), li->getOperand()))
501+
if (isWrittenTo(li))
417502
return false;
418503

419504
// Ok, we can perform our optimization. Convert the load [copy] into a
@@ -462,6 +547,9 @@ struct SemanticARCOpts : SILFunctionTransform {
462547
//
463548
// FIXME: Should we iterate or use a RPOT order here?
464549
bool madeChange = false;
550+
551+
SemanticARCOptVisitor visitor(f);
552+
465553
for (auto &bb : f) {
466554
auto ii = bb.rend();
467555
auto start = bb.rbegin();
@@ -482,7 +570,7 @@ struct SemanticARCOpts : SILFunctionTransform {
482570
// ii. Move ii from the next value back onto the instruction
483571
// after ii's old value in the block instruction list and then
484572
// process that.
485-
if (SemanticARCOptVisitor().visit(&*ii)) {
573+
if (visitor.visit(&*ii)) {
486574
madeChange = true;
487575
ii = std::prev(tmp);
488576
continue;
@@ -493,7 +581,7 @@ struct SemanticARCOpts : SILFunctionTransform {
493581
}
494582

495583
// Finally visit the first instruction of the block.
496-
madeChange |= SemanticARCOptVisitor().visit(&*ii);
584+
madeChange |= visitor.visit(&*ii);
497585
}
498586

499587
if (madeChange) {

0 commit comments

Comments
 (0)