Skip to content

Commit 97d15f6

Browse files
committed
Use the new visitor to handle multiple borrows in SemanticARCOpts.
By intercepting the walk that the findAccessedStorage walker does, we can find the exact borrow that appeared along a certain access, so we don't need to limit ourselves to a singly-borrowed base.
1 parent 0e3987a commit 97d15f6

File tree

2 files changed

+133
-88
lines changed

2 files changed

+133
-88
lines changed

lib/SILOptimizer/Mandatory/SemanticARCOpts.cpp

Lines changed: 107 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -381,64 +381,93 @@ bool mayFunctionMutateArgument(const AccessedStorage &storage, SILFunction &f) {
381381
return true;
382382
}
383383

384-
bool SemanticARCOptVisitor::isWrittenTo(LoadInst *load) {
385-
auto addr = load->getOperand();
384+
// Then find our accessed storage to determine whether it provides a guarantee
385+
// for the loaded value.
386+
namespace {
387+
class StorageGuaranteesLoadVisitor
388+
: public AccessUseDefChainVisitor<StorageGuaranteesLoadVisitor>
389+
{
390+
// The outer SemanticARCOptVisitor.
391+
SemanticARCOptVisitor &ARCOpt;
386392

387-
// Then find our accessed storage. If we can not find anything, be
388-
// conservative and assume that the value is written to.
389-
const auto &storage = findAccessedStorageNonNested(addr);
390-
if (!storage)
391-
return true;
392-
393-
// Then see if we ever write to this address in a flow insensitive
394-
// way (ignoring stores that are obviously the only initializer to
395-
// memory). We have to do this since load_borrow assumes that the
396-
// underlying memory is never written to.
397-
switch (storage.getKind()) {
398-
case AccessedStorage::Class: {
393+
// The original load instruction.
394+
LoadInst *Load;
395+
396+
// The current address being visited.
397+
SILValue currentAddress;
398+
399+
Optional<bool> isWritten;
400+
401+
public:
402+
StorageGuaranteesLoadVisitor(SemanticARCOptVisitor &arcOpt, LoadInst *load)
403+
: ARCOpt(arcOpt), Load(load), currentAddress(load->getOperand())
404+
{}
405+
406+
void answer(bool written) {
407+
currentAddress = nullptr;
408+
isWritten = written;
409+
}
410+
411+
void next(SILValue address) {
412+
currentAddress = address;
413+
}
414+
415+
void visitNestedAccess(BeginAccessInst *access) {
416+
// Look through nested accesses.
417+
return next(access->getOperand());
418+
}
419+
420+
void visitArgumentAccess(SILFunctionArgument *arg) {
421+
return answer(mayFunctionMutateArgument(
422+
AccessedStorage(arg, AccessedStorage::Argument),
423+
ARCOpt.F));
424+
}
425+
426+
void visitGlobalAccess(SILValue global) {
427+
return answer(!AccessedStorage(global, AccessedStorage::Global)
428+
.isLetAccess(&ARCOpt.F));
429+
}
430+
431+
void visitClassAccess(RefElementAddrInst *field) {
432+
currentAddress = nullptr;
433+
399434
// We know a let property won't be written to if the base object is
400435
// guaranteed for the duration of the access.
401-
if (!storage.isLetAccess(&F))
402-
return true;
403-
404-
auto baseObject = stripCasts(storage.getObject());
436+
// For non-let properties conservatively assume they may be written to.
437+
if (!field->getField()->isLet()) {
438+
return answer(true);
439+
}
440+
441+
// The lifetime of the `let` is guaranteed if it's dominated by the
442+
// guarantee on the base. Check for a borrow.
443+
SILValue baseObject = field->getOperand();
444+
auto beginBorrow = dyn_cast<BeginBorrowInst>(baseObject);
445+
if (beginBorrow)
446+
baseObject = beginBorrow->getOperand();
447+
baseObject = stripCasts(baseObject);
448+
405449
// A guaranteed argument trivially keeps the base alive for the duration of
406450
// the projection.
407451
if (auto *arg = dyn_cast<SILFunctionArgument>(baseObject)) {
408452
if (arg->getArgumentConvention().isGuaranteedConvention()) {
409-
return false;
453+
return answer(false);
410454
}
411455
}
412456

413457
// See if there's a borrow of the base object our load is based on.
414458
SILValue borrowInst;
415-
if (isa<BeginBorrowInst>(baseObject)
416-
|| isa<LoadBorrowInst>(baseObject)) {
459+
if (isa<LoadBorrowInst>(baseObject)) {
417460
borrowInst = baseObject;
418461
} 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;
462+
borrowInst = beginBorrow;
435463
}
436-
464+
// TODO: We could also look at a guaranteed phi argument and see whether
465+
// the loaded copy is dominated by it.
466+
if (!borrowInst)
467+
return answer(true);
468+
437469
// Use the linear lifetime checker to check whether the copied
438470
// value is dominated by the lifetime of the borrow it's based on.
439-
if (!borrowInst)
440-
return true;
441-
442471
SmallVector<BranchPropagatedUser, 4> baseEndBorrows;
443472
for (auto *use : borrowInst->getUses()) {
444473
if (isa<EndBorrowInst>(use->getUser())) {
@@ -447,36 +476,52 @@ bool SemanticARCOptVisitor::isWrittenTo(LoadInst *load) {
447476
}
448477

449478
SmallVector<BranchPropagatedUser, 4> valueDestroys;
450-
for (auto *use : load->getUses()) {
479+
for (auto *use : Load->getUses()) {
451480
if (isa<DestroyValueInst>(use->getUser())) {
452481
valueDestroys.push_back(BranchPropagatedUser(use->getUser()));
453482
}
454483
}
455484

456485
SmallPtrSet<SILBasicBlock *, 4> visitedBlocks;
457486

458-
return valueHasLinearLifetime(baseObject, baseEndBorrows, valueDestroys,
459-
visitedBlocks, getDeadEndBlocks(),
460-
ownership::ErrorBehaviorKind::ReturnFalse)
461-
.getFoundError();
487+
auto result = valueHasLinearLifetime(baseObject, baseEndBorrows,
488+
valueDestroys, visitedBlocks,
489+
ARCOpt.getDeadEndBlocks(),
490+
ownership::ErrorBehaviorKind::ReturnFalse);
491+
return answer(result.getFoundError());
462492
}
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-
469-
case AccessedStorage::Box:
470-
case AccessedStorage::Stack:
471-
case AccessedStorage::Yield:
472-
case AccessedStorage::Nested:
473-
case AccessedStorage::Unidentified:
474-
return true;
475-
476-
case AccessedStorage::Argument:
477-
return mayFunctionMutateArgument(storage, F);
493+
494+
// TODO: Handle other access kinds?
495+
void visitBase(SILValue base, AccessedStorage::Kind kind) {
496+
return answer(true);
478497
}
479-
llvm_unreachable("covered switch");
498+
499+
void visitNonAccess(SILValue addr) {
500+
return answer(true);
501+
}
502+
503+
void visitIncomplete(SILValue projectedAddr, SILValue parentAddr) {
504+
return next(parentAddr);
505+
}
506+
507+
void visitPhi(SILPhiArgument *phi) {
508+
// We shouldn't have address phis in OSSA SIL, so we don't need to recur
509+
// through the predecessors here.
510+
return answer(true);
511+
}
512+
513+
bool doIt() {
514+
while (currentAddress) {
515+
visit(currentAddress);
516+
}
517+
return *isWritten;
518+
}
519+
};
520+
} // namespace
521+
522+
bool SemanticARCOptVisitor::isWrittenTo(LoadInst *load) {
523+
StorageGuaranteesLoadVisitor visitor(*this, load);
524+
return visitor.doIt();
480525
}
481526

482527
// Convert a load [copy] from unique storage [read] that has all uses that can

test/SILOptimizer/semantic-arc-opts.sil

Lines changed: 26 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -561,19 +561,19 @@ bb0(%x : @owned $ClassLet):
561561
}
562562

563563
// CHECK-LABEL: sil [ossa] @dont_copy_let_properties_with_multi_borrowed_base_that_dominates
564-
// TODO: [[OUTER:%.*]] = begin_borrow
565-
// TODO-NEXT: ref_element_addr
566-
// TODO-NEXT: [[INNER:%.*]] = load_borrow
567-
// TODO-NEXT: apply
568-
// TODO-NEXT: end_borrow [[INNER]]
569-
// TODO-NEXT: end_borrow [[OUTER]]
570-
// TODO-NEXT: [[OUTER:%.*]] = begin_borrow
571-
// TODO-NEXT: ref_element_addr
572-
// TODO-NEXT: [[INNER:%.*]] = load_borrow
573-
// TODO-NEXT: apply
574-
// TODO-NEXT: end_borrow [[INNER]]
575-
// TODO-NEXT: end_borrow [[OUTER]]
576-
// TODO-NEXT: destroy_value
564+
// CHECK: [[OUTER:%.*]] = begin_borrow
565+
// CHECK-NEXT: ref_element_addr
566+
// CHECK-NEXT: [[INNER:%.*]] = load_borrow
567+
// CHECK-NEXT: apply
568+
// CHECK-NEXT: end_borrow [[INNER]]
569+
// CHECK-NEXT: end_borrow [[OUTER]]
570+
// CHECK-NEXT: [[OUTER:%.*]] = begin_borrow
571+
// CHECK-NEXT: ref_element_addr
572+
// CHECK-NEXT: [[INNER:%.*]] = load_borrow
573+
// CHECK-NEXT: apply
574+
// CHECK-NEXT: end_borrow [[INNER]]
575+
// CHECK-NEXT: end_borrow [[OUTER]]
576+
// CHECK-NEXT: destroy_value
577577
sil [ossa] @dont_copy_let_properties_with_multi_borrowed_base_that_dominates : $@convention(thin) (@owned ClassLet) -> () {
578578
bb0(%x : @owned $ClassLet):
579579
%f = function_ref @black_hole : $@convention(thin) (@guaranteed Klass) -> ()
@@ -635,19 +635,19 @@ bb0(%x : @owned $ClassLet):
635635

636636
// CHECK-LABEL: sil [ossa] @do_or_dont_copy_let_properties_with_multi_borrowed_base_when_it_dominates
637637
// CHECK: [[OUTER:%.*]] = begin_borrow
638-
// TODO-NEXT: ref_element_addr
639-
// TODO-NEXT: [[INNER:%.*]] = load_borrow
640-
// TODO-NEXT: apply
641-
// TODO-NEXT: end_borrow [[INNER]]
642-
// TODO-NEXT: end_borrow [[OUTER]]
643-
// TODO-NEXT: begin_borrow
644-
// TODO-NEXT: ref_element_addr
645-
// TODO-NEXT: load [copy]
646-
// TODO-NEXT: apply
647-
// TODO-NEXT: end_borrow
648-
// TODO-NEXT: destroy_value
649-
// TODO-NEXT: apply
650-
// TODO-NEXT: destroy_value
638+
// CHECK-NEXT: ref_element_addr
639+
// CHECK-NEXT: [[INNER:%.*]] = load_borrow
640+
// CHECK-NEXT: apply
641+
// CHECK-NEXT: end_borrow [[INNER]]
642+
// CHECK-NEXT: end_borrow [[OUTER]]
643+
// CHECK-NEXT: begin_borrow
644+
// CHECK-NEXT: ref_element_addr
645+
// CHECK-NEXT: load [copy]
646+
// CHECK-NEXT: apply
647+
// CHECK-NEXT: end_borrow
648+
// CHECK-NEXT: destroy_value
649+
// CHECK-NEXT: apply
650+
// CHECK-NEXT: destroy_value
651651
sil [ossa] @do_or_dont_copy_let_properties_with_multi_borrowed_base_when_it_dominates : $@convention(thin) (@owned ClassLet) -> () {
652652
bb0(%x : @owned $ClassLet):
653653
%f = function_ref @black_hole : $@convention(thin) (@guaranteed Klass) -> ()

0 commit comments

Comments
 (0)