Skip to content

Fix BorrowedFromInst operand ownership. #79605

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
merged 37 commits into from
Feb 27, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
75e84f1
Rename InteriorLiveness methods for clarity.
atrick Feb 23, 2025
6c99017
Minor comment cleanup and code formatting.
atrick Feb 25, 2025
0871c96
Fix BorrowedFromInst operand ownership.
atrick Feb 25, 2025
fda5392
Add support for borrowed-from to BorrowingInstruction.
atrick Feb 25, 2025
e225415
Remove unused computeInterorLiveness API.
atrick Feb 25, 2025
6188d29
Handle borrowed-from in OwnershipUseVisitor.
atrick Feb 25, 2025
a4a675e
Remove adjacent phi handling from InteriorUseWalker.
atrick Feb 25, 2025
f1792d8
Fix BorrowingOperand.visitScopeEndingUses
atrick Feb 25, 2025
7f6962a
Fix OwnershipUseVisitor.dependentUse()
atrick Feb 25, 2025
5a1a487
Fix InteriorUseWalker to handle dependent lifetimes as inner scopes
atrick Feb 25, 2025
b3a445f
Fix BorrowingOperand::getBorrowIntroducingUserResult()
atrick Feb 25, 2025
e78aa38
Remove BorrowingOperand.getImplicitUses.
atrick Feb 25, 2025
6fa0b2e
Add BorrowedFromInst::isReborrow.
atrick Feb 25, 2025
1cb4d50
Fix BorrowingOperand::visitScopeEndingUses() invariants.
atrick Feb 25, 2025
e706f7e
Update unit tests for fixed borrowed-from ownership and liveness.
atrick Feb 25, 2025
9104443
Add several missing unit tests for borrowed-from liveness.
atrick Feb 25, 2025
5864004
SwiftCompilerSources: remove adjacent phis from InteriorUseWalker.
atrick Feb 26, 2025
e8af007
Remove adjacent phis from OwnershipLiveness.
atrick Feb 26, 2025
dc8778f
Add MarkDependenstInst.hasScopedLifetime
atrick Feb 26, 2025
c364181
Add MarkDependenceInst::hasScopedLifetime
atrick Feb 26, 2025
9070974
Add BorrowingInstruction.dependentValue and .scopedValue
atrick Feb 26, 2025
b854ab1
OwnershipUseVisitor.visitInnerBorrowUses: support dependent values
atrick Feb 26, 2025
f29bb44
OwnershipUseVisitor::visitInnerBorrowUses: support dependent values
atrick Feb 26, 2025
5f8b93c
Fix interior liveness to report the escaping instruction as a use.
atrick Feb 26, 2025
edcaecc
Fix BorrowedValue::visitInteriorPointerOperandHelper
atrick Feb 26, 2025
fc9a20f
Update ownership liveness unit tests.
atrick Feb 26, 2025
083ab98
Simplify OwnershipLiveness.swift, remove reborrowingUse.
atrick Feb 26, 2025
8decfa4
Add BorrowingInstruction.innerValue
atrick Feb 26, 2025
406afc7
SwiftCompilerSources: Fix InteriorUseWalker with visitInnerUses.
atrick Feb 26, 2025
38137c5
OwnershipLiveness.swift: add comments
atrick Feb 26, 2025
353bef7
Fix DestroyHoisting to visit all interior uses.
atrick Feb 26, 2025
0f728f0
Fix BorrowingInstruction.visitEndBorrows to handle borrowed-from
atrick Feb 26, 2025
747b1ac
Fix OwnershipLivenes.swift visitInnerBorrowUses
atrick Feb 26, 2025
ec6f9b5
Update unit test: ownership_liveness_unit.sil
atrick Feb 26, 2025
e280c52
Fix DestroyHoisting: don't ignore pointer escapes.
atrick Feb 26, 2025
2542515
OwnershipLiveness.swift fix: don't ignore trivial types
atrick Feb 26, 2025
6e75813
OwnershipLiveness fix: support visitInnerUses
atrick Feb 26, 2025
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
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,9 @@ private func optimize(value: Value, _ context: FunctionPassContext) {
var hoistableDestroys = selectHoistableDestroys(of: value, context)
defer { hoistableDestroys.deinitialize() }

var minimalLiverange = InstructionRange(withLiverangeOf: value, ignoring: hoistableDestroys, context)
guard var minimalLiverange = InstructionRange(withLiverangeOf: value, ignoring: hoistableDestroys, context) else {
return
}
defer { minimalLiverange.deinitialize() }

hoistDestroys(of: value, toEndOf: minimalLiverange, restrictingTo: &hoistableDestroys, context)
Expand Down Expand Up @@ -177,10 +179,10 @@ private func removeDestroys(

private extension InstructionRange {

init(withLiverangeOf initialDef: Value, ignoring ignoreDestroys: InstructionSet, _ context: FunctionPassContext)
init?(withLiverangeOf initialDef: Value, ignoring ignoreDestroys: InstructionSet, _ context: FunctionPassContext)
{
var liverange = InstructionRange(for: initialDef, context)
var visitor = InteriorUseWalker(definingValue: initialDef, ignoreEscape: true, visitInnerUses: false, context) {
var visitor = InteriorUseWalker(definingValue: initialDef, ignoreEscape: false, visitInnerUses: true, context) {
if !ignoreDestroys.contains($0.instruction) {
liverange.insert($0.instruction)
}
Expand All @@ -197,7 +199,10 @@ private extension InstructionRange {
return .continueWalk
}

_ = visitor.visitUses()
guard visitor.visitUses() == .continueWalk else {
liverange.deinitialize()
return nil
}
self = liverange
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -554,8 +554,8 @@ extension AddressOwnershipLiveRange {
///
/// For address values, use AccessBase.computeOwnershipRange.
///
/// FIXME: This should use computeLinearLiveness rather than computeKnownLiveness as soon as lifetime completion
/// runs immediately after SILGen.
/// FIXME: This should use computeLinearLiveness rather than computeKnownLiveness as soon as complete OSSA lifetimes
/// are verified.
private static func computeValueLiveRange(of value: Value, _ context: FunctionPassContext)
-> AddressOwnershipLiveRange? {
switch value.ownership {
Expand Down
194 changes: 127 additions & 67 deletions SwiftCompilerSources/Sources/Optimizer/Utilities/BorrowUtils.swift
Original file line number Diff line number Diff line change
Expand Up @@ -134,27 +134,26 @@ import SIL

/// A scoped instruction that borrows one or more operands.
///
/// If this instruction produces a borrowed value, then
/// BeginBorrowValue(resultOf: self) != nil.
/// If this instruction produces a borrowed value, then BeginBorrowValue(resultOf: self) != nil.
///
/// This does not include instructions like `apply` and `try_apply` that
/// instantaneously borrow a value from the caller.
/// This does not include instructions like `apply` and `try_apply` that instantaneously borrow a value from the caller.
///
/// This does not include `load_borrow` because it borrows a memory
/// location, not the value of its operand.
/// This does not include `load_borrow` because it borrows a memory location, not the value of its operand.
///
/// Note: This must handle all instructions with a .borrow operand ownership.
///
/// Note: mark_dependence is a BorrowingInstruction because it creates
/// a borrow scope for its base operand. Its result, however, is not a
/// BeginBorrowValue. It is instead a ForwardingInstruction relative
/// to its value operand.
/// Note: borrowed_from is a BorrowingInstruction because it creates a borrow scope for its enclosing operands. Its
/// result, however, is only a BeginBorrowValue (.reborrow) if it forwards a reborrow phi. Otherwise, it simply forwards
/// a guaranteed value and does not introduce a separate borrow scope.
///
/// TODO: replace BorrowIntroducingInstruction
/// Note: mark_dependence [nonescaping] is a BorrowingInstruction because it creates a borrow scope for its base
/// operand. Its result, however, is not a BeginBorrowValue. Instead it is a ForwardingInstruction relative to its value
/// operand.
///
/// TODO: Add non-escaping MarkDependence.
/// TODO: replace BorrowIntroducingInstruction with this.
enum BorrowingInstruction : CustomStringConvertible, Hashable {
case beginBorrow(BeginBorrowInst)
case borrowedFrom(BorrowedFromInst)
case storeBorrow(StoreBorrowInst)
case beginApply(BeginApplyInst)
case partialApply(PartialApplyInst)
Expand All @@ -165,13 +164,18 @@ enum BorrowingInstruction : CustomStringConvertible, Hashable {
switch inst {
case let bbi as BeginBorrowInst:
self = .beginBorrow(bbi)
case let bfi as BorrowedFromInst:
self = .borrowedFrom(bfi)
case let sbi as StoreBorrowInst:
self = .storeBorrow(sbi)
case let bai as BeginApplyInst:
self = .beginApply(bai)
case let pai as PartialApplyInst where !pai.mayEscape:
self = .partialApply(pai)
case let mdi as MarkDependenceInst:
guard mdi.isNonEscaping else {
return nil
}
self = .markDependence(mdi)
case let bi as BuiltinInst
where bi.id == .StartAsyncLetWithLocalBuffer:
Expand All @@ -185,6 +189,8 @@ enum BorrowingInstruction : CustomStringConvertible, Hashable {
switch self {
case .beginBorrow(let bbi):
return bbi
case .borrowedFrom(let bfi):
return bfi
case .storeBorrow(let sbi):
return sbi
case .beginApply(let bai):
Expand All @@ -198,65 +204,88 @@ enum BorrowingInstruction : CustomStringConvertible, Hashable {
}
}

/// Visit the operands that end the local borrow scope.
///
/// Note: When this instruction's result is BeginBorrowValue the
/// scopeEndingOperand may include reborrows. To find all uses that
/// contribute to liveness, the caller needs to determine whether an
/// incoming value dominates or is consumed by an outer adjacent
/// phi. See InteriorLiveness.
///
/// FIXME: To generate conservatively correct liveness, this should return
/// .abortWalk if this is a mark_dependence and the scope-ending use is not
/// the last in the function (e.g. a store rather than a destroy or return).
/// The client needs to use LifetimeDependenceDefUseWalker to do better.
///
/// TODO: to handle reborrow-extended uses, migrate ExtendedLiveness
/// to SwiftCompilerSources.
///
/// TODO: Handle .partialApply and .markDependence forwarded uses
/// that are phi operands. Currently, partial_apply [on_stack]
/// and mark_dependence [nonescaping] cannot be cloned, so walking
/// through the phi safely returns dominated scope-ending operands.
/// Instead, this could report the phi as a scope-ending use, and
/// the client could decide whether to walk through them or to
/// construct reborrow-extended liveness.
///
/// TODO: For instructions that are not a BeginBorrowValue, verify
/// that scope ending instructions exist on all paths. These
/// instructions should be complete after SILGen and never cloned to
/// produce phis.
func visitScopeEndingOperands(_ context: Context,
visitor: @escaping (Operand) -> WalkResult)
-> WalkResult {
var innerValue: Value? {
if let dependent = dependentValue {
return dependent
}
return scopedValue
}

var dependentValue: Value? {
switch self {
case .borrowedFrom(let bfi):
let phi = bfi.borrowedPhi
if phi.isReborrow {
return nil
}
return phi.value
case .markDependence(let mdi):
if mdi.hasScopedLifetime {
return nil
}
return mdi
case .beginBorrow, .storeBorrow, .beginApply, .partialApply, .startAsyncLet:
return nil
}
}

/// If this is valid, then visitScopeEndingOperands succeeds.
var scopedValue: Value? {
switch self {
case .beginBorrow, .storeBorrow:
let svi = instruction as! SingleValueInstruction
return svi.uses.filterUsers(ofType: EndBorrowInst.self).walk {
visitor($0)
return instruction as! SingleValueInstruction
case let .borrowedFrom(bfi):
let phi = bfi.borrowedPhi
guard phi.isReborrow else {
return nil
}
return phi.value
case .beginApply(let bai):
return bai.token.uses.walk { return visitor($0) }
case .partialApply, .markDependence:
let svi = instruction as! SingleValueInstruction
assert(svi.ownership == .owned)
return visitForwardedUses(introducer: svi, context) {
switch $0 {
case let .operand(operand):
if operand.endsLifetime {
return visitor(operand)
}
return .continueWalk
case let .deadValue(_, operand):
if let operand = operand {
assert(!operand.endsLifetime,
"a dead forwarding instruction cannot end a lifetime")
}
return .continueWalk
}
return bai.token
case .partialApply(let pai):
// We currently assume that closure lifetimes are always complete (destroyed on all paths).
return pai
case .markDependence(let mdi):
guard mdi.hasScopedLifetime else {
return nil
}
return mdi
case .startAsyncLet(let builtin):
return builtin.uses.walk {
return builtin
}
}

/// Visit the operands that end the local borrow scope.
///
/// Returns .abortWalk if the borrow scope cannot be determined from lifetime-ending uses. For example:
/// - borrowed_from where 'borrowedPhi.isReborrow == false'
/// - non-owned mark_dependence [nonescaping]
/// - owned mark_dependence [nonescaping] with a ~Escapable result (LifetimeDependenceDefUseWalker is needed).
///
/// Note: .partialApply and .markDependence cannot currently be forwarded to phis because partial_apply [on_stack] and
/// mark_dependence [nonescaping] cannot be cloned. Walking through the phi therefore safely returns dominated
/// scope-ending operands. Handling phis here requires the equivalent of borrowed_from for owned values.
///
/// TODO: For instructions that are not a BeginBorrowValue, verify that scope ending instructions exist on all
/// paths. These instructions should be complete after SILGen and never cloned to produce phis.
func visitScopeEndingOperands(_ context: Context, visitor: @escaping (Operand) -> WalkResult) -> WalkResult {
guard let val = scopedValue else {
return .abortWalk
}
switch self {
case .beginBorrow, .storeBorrow:
return visitEndBorrows(value: val, context, visitor)
case .borrowedFrom:
return visitEndBorrows(value: val, context, visitor)
case .beginApply:
return val.uses.walk { return visitor($0) }
case .partialApply:
// We currently assume that closure lifetimes are always complete (destroyed on all paths).
return visitOwnedDependent(value: val, context, visitor)
case .markDependence:
return visitOwnedDependent(value: val, context, visitor)
case .startAsyncLet:
return val.uses.walk {
if let builtinUser = $0.instruction as? BuiltinInst,
builtinUser.id == .EndAsyncLetLifetime {
return visitor($0)
Expand All @@ -265,6 +294,34 @@ enum BorrowingInstruction : CustomStringConvertible, Hashable {
}
}
}
}

extension BorrowingInstruction {
private func visitEndBorrows(value: Value, _ context: Context, _ visitor: @escaping (Operand) -> WalkResult)
-> WalkResult {
return value.lookThroughBorrowedFromUser.uses.filterUsers(ofType: EndBorrowInst.self).walk {
visitor($0)
}
}

private func visitOwnedDependent(value: Value, _ context: Context, _ visitor: @escaping (Operand) -> WalkResult)
-> WalkResult {
return visitForwardedUses(introducer: value, context) {
switch $0 {
case let .operand(operand):
if operand.endsLifetime {
return visitor(operand)
}
return .continueWalk
case let .deadValue(_, operand):
if let operand = operand {
assert(!operand.endsLifetime,
"a dead forwarding instruction cannot end a lifetime")
}
return .continueWalk
}
}
}

var description: String { instruction.description }
}
Expand Down Expand Up @@ -341,9 +398,12 @@ enum BeginBorrowValue {
init?(resultOf borrowInstruction: BorrowingInstruction) {
switch borrowInstruction {
case let .beginBorrow(beginBorrow):
self = BeginBorrowValue(beginBorrow)!
self.init(beginBorrow)
case let .borrowedFrom(borrowedFrom):
// only returns non-nil if borrowedPhi is a reborrow
self.init(borrowedFrom.borrowedPhi.value)
case let .beginApply(beginApply):
self = BeginBorrowValue(beginApply.token)!
self.init(beginApply.token)
case .storeBorrow, .partialApply, .markDependence, .startAsyncLet:
return nil
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -771,12 +771,14 @@ extension LifetimeDependenceDefUseWalker {
switch borrowInst {
case let .beginBorrow(bbi):
return walkDownUses(of: bbi, using: operand)
case let .borrowedFrom(bfi):
return walkDownUses(of: bfi, using: operand)
case let .storeBorrow(sbi):
return walkDownAddressUses(of: sbi)
case .beginApply:
// Skip the borrow scope; the type system enforces non-escapable
// arguments.
return visitInnerBorrowUses(of: borrowInst)
return visitInnerBorrowUses(of: borrowInst, operand: operand)
case .partialApply, .markDependence:
fatalError("OwnershipUseVisitor should bypass partial_apply [on_stack] "
+ "and mark_dependence [nonescaping]")
Expand Down
Loading