Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
86f3cc0
Optimizer: split SimplifyBeginAndLoadBorrow.swift into SimplifyBeginB…
eeckstein Dec 17, 2025
f4c3fcb
SimplifyBeginBorrow: small refactoring
eeckstein Dec 17, 2025
4e964da
Optimizer: handle debug_value in begin_borrow simplification
eeckstein Nov 26, 2025
e69b7cf
SimplifyBeginBorrow: ignore type-dependent operands when converting b…
eeckstein Dec 17, 2025
92f8c2d
SimplifyBeginBorrow: remove inner borrow scopes and other improvements
eeckstein Dec 17, 2025
555642c
SemanticARCOpts: remove the BorrowScope optimization because this is …
eeckstein Dec 17, 2025
b52cfbd
SIL: add `Type.isHeapObjectReferenceType`
eeckstein Dec 17, 2025
9ad3a78
OwnershipOptUtils: fix a wrong debug location when creating destroy_v…
eeckstein Dec 17, 2025
a3d6cf6
SimplifyLoad/SimplifyLoadBorrow: add two peephole optimizations
eeckstein Dec 17, 2025
3b3406e
Optimizer: rename SimplifyMisc.swift -> SimplifyTypeValue.swift
eeckstein Dec 1, 2025
dd733ef
SimplifyDestructure: convert `destructure_tuple`/`destructure_struct`…
eeckstein Dec 17, 2025
effce27
tests: enable SILOptimizer/copy-to-borrow-optimization.sil for all OSes
eeckstein Dec 17, 2025
8afa971
CopyToBorrowOptimization: need to update borrowed-from instructions w…
eeckstein Dec 17, 2025
0ab9c35
SIL: fix operand ownership for `taskAddCancellationHandler` and `task…
eeckstein Dec 18, 2025
8a5e011
LoadableByAddress: handle the `unchecked_bitwise_cast` instruction
eeckstein Dec 18, 2025
341adb5
SILVerifier: print a readable error message when referencing an undef…
eeckstein Dec 18, 2025
05f115f
SILVerifier: don't crash on verifier errors in the function header
eeckstein Dec 18, 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 @@ -53,49 +53,60 @@ let copyToBorrowOptimization = FunctionPass(name: "copy-to-borrow-optimization")
return
}

var changed = false

for inst in function.instructions {
switch inst {
case let load as LoadInst:
optimize(load: load, context)
if optimize(load: load, context) {
changed = true
}
case let copy as CopyValueInst:
optimize(copy: copy, context)
if optimize(copy: copy, context) {
changed = true
}
default:
break
}
}

if changed {
updateBorrowedFrom(in: function, context)
}
}

private func optimize(load: LoadInst, _ context: FunctionPassContext) {
private func optimize(load: LoadInst, _ context: FunctionPassContext) -> Bool {
if load.loadOwnership != .copy {
return
return false
}

var collectedUses = Uses(context)
defer { collectedUses.deinitialize() }
if !collectedUses.collectUses(of: load) {
return
return false
}

if mayWrite(toAddressOf: load,
within: collectedUses.destroys,
usersInDeadEndBlocks: collectedUses.usersInDeadEndBlocks,
context)
{
return
return false
}

load.replaceWithLoadBorrow(collectedUses: collectedUses)
return true
}

private func optimize(copy: CopyValueInst, _ context: FunctionPassContext) {
private func optimize(copy: CopyValueInst, _ context: FunctionPassContext) -> Bool {
if copy.fromValue.ownership != .guaranteed {
return
return false
}

var collectedUses = Uses(context)
defer { collectedUses.deinitialize() }
if !collectedUses.collectUses(of: copy) {
return
return false
}

var liverange = InstructionRange(begin: copy, context)
Expand All @@ -104,10 +115,11 @@ private func optimize(copy: CopyValueInst, _ context: FunctionPassContext) {
liverange.insert(contentsOf: collectedUses.usersInDeadEndBlocks)

if !liverange.isFullyContainedIn(borrowScopeOf: copy.fromValue.lookThroughForwardingInstructions) {
return
return false
}

remove(copy: copy, collectedUses: collectedUses, liverange: liverange)
return true
}

private struct Uses {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ swift_compiler_sources(Optimizer
SimplifyAllocRefDynamic.swift
SimplifyAllocStack.swift
SimplifyApply.swift
SimplifyBeginAndLoadBorrow.swift
SimplifyBeginBorrow.swift
SimplifyBeginCOWMutation.swift
SimplifyBranch.swift
SimplifyBuiltin.swift
Expand All @@ -31,8 +31,8 @@ swift_compiler_sources(Optimizer
SimplifyInitEnumDataAddr.swift
SimplifyKeyPath.swift
SimplifyLoad.swift
SimplifyLoadBorrow.swift
SimplifyMarkDependence.swift
SimplifyMisc.swift
SimplifyPartialApply.swift
SimplifyPointerToAddress.swift
SimplifyRefCasts.swift
Expand All @@ -43,6 +43,7 @@ swift_compiler_sources(Optimizer
SimplifySwitchEnum.swift
SimplifyTuple.swift
SimplifyTupleExtract.swift
SimplifyTypeValue.swift
SimplifyUncheckedAddrCast.swift
SimplifyUncheckedEnumData.swift
SimplifyValueToBridgeObject.swift
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
//===--- SimplifyBeginAndLoadBorrow.swift ---------------------------------===//
//===--- SimplifyBeginBorrow.swift ----------------------------------------===//
//
// This source file is part of the Swift.org open source project
//
Expand All @@ -14,68 +14,72 @@ import SIL

extension BeginBorrowInst : OnoneSimplifiable, SILCombineSimplifiable {
func simplify(_ context: SimplifyContext) {
if borrowedValue.ownership == .owned,
// We need to keep lexical lifetimes in place.
!isLexical,
// The same for borrow-scopes which encapsulated pointer escapes.
!findPointerEscapingUse(of: borrowedValue)
{
tryReplaceBorrowWithOwnedOperand(beginBorrow: self, context)
} else {
removeBorrowOfThinFunction(beginBorrow: self, context)
if isLexical && context.preserveDebugInfo {
// We must not remove `begin_borrow [lexical] because this is important for diagnostic passes.
return
}
}
}

extension LoadBorrowInst : Simplifiable, SILCombineSimplifiable {
func simplify(_ context: SimplifyContext) {
if uses.ignoreDebugUses.ignore(usersOfType: EndBorrowInst.self).isEmpty {
context.erase(instructionIncludingAllUsers: self)
return
switch borrowedValue.ownership {
case .owned:
if tryReplaceBorrowWithOwnedOperand(beginBorrow: self, context) {
return
}
case .guaranteed:
if tryReplaceInnerBorrowScope(beginBorrow: self, context) {
return
}
default:
// Note that the operand of `begin_borrow` can have "none" ownership, e.g. in case of
// ```
// %1 = enum $NonTrivialEnum, #NonTrivialEnum.trivialCase!enumelt // ownership = none
// %2 = begin_borrow %1
// ```
break
}
removeBorrowOfThinFunction(beginBorrow: self, context)
}
}

// If the load_borrow is followed by a copy_value, combine both into a `load [copy]`:
// ```
// %1 = load_borrow %0
// %2 = some_forwarding_instruction %1 // zero or more forwarding instructions
// %3 = copy_value %2
// end_borrow %1
// ```
// ->
// ```
// %1 = load [copy] %0
// %3 = some_forwarding_instruction %1 // zero or more forwarding instructions
// ```
//
tryCombineWithCopy(context)
// See comments of `tryReplaceCopy` and `convertAllUsesToOwned`
private func tryReplaceBorrowWithOwnedOperand(beginBorrow: BeginBorrowInst, _ context: SimplifyContext) -> Bool {
if findPointerEscapingUse(of: beginBorrow.borrowedValue) {
return false
}

private func tryCombineWithCopy(_ context: SimplifyContext) {
let forwardedValue = lookThroughSingleForwardingUses()
guard let singleUser = forwardedValue.uses.ignore(usersOfType: EndBorrowInst.self).singleUse?.instruction,
let copy = singleUser as? CopyValueInst,
copy.parentBlock == self.parentBlock else {
return
}
let builder = Builder(before: self, context)
let loadCopy = builder.createLoad(fromAddress: address, ownership: .copy)
let forwardedOwnedValue = replaceGuaranteed(value: self, withOwnedValue: loadCopy, context)
copy.replace(with: forwardedOwnedValue, context)
context.erase(instructionIncludingAllUsers: self)
// The last value of a (potentially empty) forwarding chain, beginning at the `begin_borrow`.
let forwardedValue = beginBorrow.lookThroughOwnedConvertibaleForwardingChain()
guard forwardedValue.allUsesCanBeConvertedToOwned else {
return false
}
if tryReplaceCopy(of: forwardedValue, withCopiedOperandOf: beginBorrow, context) {
return true
}
if beginBorrow.borrowedValue.isDestroyed(after: beginBorrow) {
convertAllUsesToOwned(of: beginBorrow, context)
return true
}
return false
}

private func tryReplaceBorrowWithOwnedOperand(beginBorrow: BeginBorrowInst, _ context: SimplifyContext) {
// The last value of a (potentially empty) forwarding chain, beginning at the `begin_borrow`.
let forwardedValue = beginBorrow.lookThroughSingleForwardingUses()
if forwardedValue.allUsesCanBeConvertedToOwned {
if tryReplaceCopy(of: forwardedValue, withCopiedOperandOf: beginBorrow, context) {
return
}
if beginBorrow.borrowedValue.isDestroyed(after: beginBorrow) {
convertAllUsesToOwned(of: beginBorrow, context)
}
/// Removes a borrow scope if the borrowed operand is already a guaranteed value.
/// ```
/// bb0(%0 : @guaranteed $T):
/// %1 = begin_borrow %0
/// // ... uses of %1
/// end_borrow %1
/// ```
/// ->
/// ```
/// bb0(%0 : @guaranteed $T):
/// // ... uses of %0
/// ```
private func tryReplaceInnerBorrowScope(beginBorrow: BeginBorrowInst, _ context: SimplifyContext) -> Bool {
Copy link
Contributor

@meg-gupta meg-gupta Dec 18, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need a lexical check here? Can we remove an inner lexical borrow with an outer non lexical borrow?

guard beginBorrow.scopeEndingOperands.allSatisfy({ $0.instruction is EndBorrowInst }) else {
return false
}
beginBorrow.uses.ignore(usersOfType: EndBorrowInst.self).replaceAll(with: beginBorrow.borrowedValue, context)
context.erase(instructionIncludingAllUsers: beginBorrow)
return true
}

private func removeBorrowOfThinFunction(beginBorrow: BeginBorrowInst, _ context: SimplifyContext) {
Expand Down Expand Up @@ -143,27 +147,30 @@ private func convertAllUsesToOwned(of beginBorrow: BeginBorrowInst, _ context: S
context.erase(instructionIncludingAllUsers: beginBorrow)
}

private extension Value {
/// Returns the last value of a (potentially empty) forwarding chain.
extension Value {
/// Returns the last value of a (potentially empty) forwarding chain where all operands can be
/// converted to "owned" ownership.
/// For example, returns %3 for the following def-use chain:
/// ```
/// %1 = struct_extract %self, #someField
/// %2 = tuple_extract %1, 0
/// %3 = struct $S(%2) // %3 has no forwarding users
/// ```
/// Returns self if this value has no uses which are ForwardingInstructions.
func lookThroughSingleForwardingUses() -> Value {
if let singleUse = uses.ignore(usersOfType: EndBorrowInst.self).singleUse,
func lookThroughOwnedConvertibaleForwardingChain() -> Value {
if let singleUse = uses.ignore(usersOfType: EndBorrowInst.self).ignoreDebugUses.ignoreTypeDependence.singleUse,
let fwdInst = singleUse.instruction as? (SingleValueInstruction & ForwardingInstruction),
fwdInst.canConvertToOwned,
fwdInst.isSingleForwardedOperand(singleUse),
fwdInst.parentBlock == parentBlock
{
return fwdInst.lookThroughSingleForwardingUses()
return fwdInst.lookThroughOwnedConvertibaleForwardingChain()
}
return self
}
}

private extension Value {
var allUsesCanBeConvertedToOwned: Bool {
let relevantUses = uses.ignore(usersOfType: EndBorrowInst.self)
return relevantUses.allSatisfy { $0.canAccept(ownership: .owned) }
Expand Down Expand Up @@ -209,12 +216,12 @@ private extension ForwardingInstruction {

/// Replaces a guaranteed value with an owned value.
///
/// If the `guaranteedValue`'s use is a ForwardingInstruction (or forwarding instruction chain),
/// If the `value`'s use is a ForwardingInstruction (or forwarding instruction chain),
/// it is converted to an owned version of the forwarding instruction (or instruction chain).
///
/// Returns the last owned value in a forwarding-chain or `ownedValue` if `guaranteedValue` has
/// Returns the last owned value in a forwarding-chain or `ownedValue` if `value` has
/// no forwarding uses.
private func replaceGuaranteed(value: Value, withOwnedValue ownedValue: Value, _ context: SimplifyContext) -> Value {
func replaceGuaranteed(value: SingleValueInstruction, withOwnedValue ownedValue: Value, _ context: SimplifyContext) -> Value {
var result = ownedValue
var numForwardingUses = 0
for use in value.uses {
Expand All @@ -239,6 +246,11 @@ private func replaceGuaranteed(value: Value, withOwnedValue ownedValue: Value, _
result = replaceGuaranteed(value: fwdInst, withOwnedValue: fwdInst, context)
case is EndBorrowInst:
break
case let dv as DebugValueInst where dv != value.next:
// Move the debug_value immediatly after the value definition to avoid a use-after-consume
// in case the debug_value is originally located after the forwarding instruction.
dv.move(before: value.next!, context)
fallthrough
default:
precondition(use.canAccept(ownership: .owned))
use.set(to: ownedValue, context)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,19 +50,6 @@ private extension DestructureInstruction {

func foldWithAggregateConstruction(_ context: SimplifyContext) {

if aggregate.type.isTrivial(in: parentFunction) {
// ```
// (%1, %2) = destructure_tuple %t
// ```
// ->
// ```
// %1 = tuple_extract %t, 0
// %2 = tuple_extract %t, 1
// ```
replaceWithAggregateExtract(context)
return
}

switch aggregate {
case let constructInst as ConstructureInstruction:
// Eliminate the redundant instruction pair
Expand Down Expand Up @@ -99,6 +86,21 @@ private extension DestructureInstruction {
default:
break
}

if !isDeleted,
aggregate.type.isTrivial(in: parentFunction) || aggregate.ownership == .guaranteed
{
// ```
// (%1, %2) = destructure_tuple %t
// ```
// ->
// ```
// %1 = tuple_extract %t, 0
// %2 = tuple_extract %t, 1
// ```
replaceWithAggregateExtract(context)
return
}
}

private func replaceWithAggregateExtract(_ context: SimplifyContext) {
Expand Down
Loading