Skip to content

[SILOptimizer] Keep lexical lifetime markers. #39883

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions lib/SILOptimizer/SemanticARC/BorrowScopeOpts.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,11 @@ bool SemanticARCOptVisitor::visitBeginBorrowInst(BeginBorrowInst *bbi) {
if (!ctx.shouldPerform(ARCTransformKind::RedundantBorrowScopeElimPeephole))
return false;

// Lexical borrow scopes must remain in order to ensure that value lifetimes
// are not observably shortened.
if (bbi->isLexical())
return false;

auto kind = bbi->getOperand().getOwnershipKind();
SmallVector<EndBorrowInst *, 16> endBorrows;
for (auto *op : bbi->getUses()) {
Expand Down
13 changes: 13 additions & 0 deletions lib/SILOptimizer/Transforms/TempRValueElimination.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -495,6 +495,12 @@ void TempRValueOptPass::tryOptimizeCopyIntoTemp(CopyAddrInst *copyInst) {
if (!tempObj)
return;

// If the storage corresponds to a source-level var, do not optimize here.
// Mem2Reg will transform the lexical alloc_stack into a lexical begin_borrow
// which will ensure that the value's lifetime isn't observably shortened.
if (tempObj->isLexical())
return;

bool isOSSA = copyInst->getFunction()->hasOwnership();

SILValue copySrc = copyInst->getSrc();
Expand Down Expand Up @@ -633,6 +639,13 @@ TempRValueOptPass::tryOptimizeStoreIntoTemp(StoreInst *si) {
return std::next(si->getIterator());
}

// If the storage corresponds to a source-level var, do not optimize here.
// Mem2Reg will transform the lexical alloc_stack into a lexical begin_borrow
// which will ensure that the value's lifetime isn't observably shortened.
if (tempObj->isLexical()) {
return std::next(si->getIterator());
}

// If our tempObj has a dynamic lifetime (meaning it is conditionally
// initialized, conditionally taken, etc), we can not convert its uses to SSA
// while eliminating it simply. So bail.
Expand Down
5 changes: 5 additions & 0 deletions lib/SILOptimizer/Utils/CanonicalizeInstruction.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -450,6 +450,11 @@ static SILBasicBlock::iterator
eliminateSimpleBorrows(BeginBorrowInst *bbi, CanonicalizeInstruction &pass) {
auto next = std::next(bbi->getIterator());

// Never eliminate lexical borrow scopes. They must be kept to ensure that
// value lifetimes aren't observably shortened.
if (bbi->isLexical())
return next;

// We know that our borrow is completely within the lifetime of its base value
// if the borrow is never reborrowed. We check for reborrows and do not
// optimize such cases. Otherwise, we can eliminate our borrow and instead use
Expand Down
54 changes: 54 additions & 0 deletions validation-test/SILOptimizer/lexical-lifetimes.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
// RUN: %target-run-simple-swift -Xfrontend -enable-experimental-lexical-lifetimes -O -Xfrontend -enable-copy-propagation | %FileCheck %s

// =============================================================================
// = Declarations {{ =
// =============================================================================

class C {
init(_ d: D) {
self.d = d
}
weak var d: D?
func foo(_ string: String) {
d?.cWillFoo(self, string)
}
}
class D {
func cWillFoo(_ c: C, _ string: String) {
print(#function, string)
}
}

// =============================================================================
// = Declarations }} =
// =============================================================================

// =============================================================================
// = Tests {{ =
// =============================================================================

func test_localLetKeepsObjectAliveBeyondCallToClassWithWeakReference() {
let d = D()
let c = C(d)
// CHECK: cWillFoo{{.*}} test_localLetKeepsObjectAliveBeyondCallToClassWithWeakReference
c.foo(#function)
}

func test_localVarKeepsObjectAliveBeyondCallToClassWithWeakReference() {
var d = D()
let c = C(d)
// CHECK: cWillFoo{{.*}} test_localVarKeepsObjectAliveBeyondCallToClassWithWeakReference
c.foo(#function)
}

// =============================================================================
// = Tests }} =
// =============================================================================

func run() {
test_localLetKeepsObjectAliveBeyondCallToClassWithWeakReference()
test_localVarKeepsObjectAliveBeyondCallToClassWithWeakReference()
}

run()