Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
f67a310
Optimizer: split SimplifyBeginAndLoadBorrow.swift into SimplifyBeginB…
eeckstein Dec 17, 2025
ec8fe83
SimplifyBeginBorrow: small refactoring
eeckstein Dec 17, 2025
2868663
Optimizer: handle debug_value in begin_borrow simplification
eeckstein Nov 26, 2025
0271a4f
SimplifyBeginBorrow: ignore type-dependent operands when converting b…
eeckstein Dec 17, 2025
a250a30
SimplifyBeginBorrow: remove inner borrow scopes and other improvements
eeckstein Dec 17, 2025
ccc19e0
SemanticARCOpts: remove the BorrowScope optimization because this is …
eeckstein Dec 17, 2025
439bda6
SIL: add `Type.isHeapObjectReferenceType`
eeckstein Dec 17, 2025
cc22290
OwnershipOptUtils: fix a wrong debug location when creating destroy_v…
eeckstein Dec 17, 2025
9631d6c
SimplifyLoad/SimplifyLoadBorrow: add two peephole optimizations
eeckstein Dec 17, 2025
d5afd16
Optimizer: rename SimplifyMisc.swift -> SimplifyTypeValue.swift
eeckstein Dec 1, 2025
fb50158
SimplifyDestructure: convert `destructure_tuple`/`destructure_struct`…
eeckstein Dec 17, 2025
7e61773
tests: enable SILOptimizer/copy-to-borrow-optimization.sil for all OSes
eeckstein Dec 17, 2025
7ace2c5
CopyToBorrowOptimization: need to update borrowed-from instructions w…
eeckstein Dec 17, 2025
0ac0cbd
SIL: fix operand ownership for `taskAddCancellationHandler` and `task…
eeckstein Dec 18, 2025
daa8850
LoadableByAddress: handle the `unchecked_bitwise_cast` instruction
eeckstein Dec 18, 2025
ceafe65
SILVerifier: print a readable error message when referencing an undef…
eeckstein Dec 18, 2025
e265eb0
SILVerifier: don't crash on verifier errors in the function header
eeckstein Dec 18, 2025
a06733b
SILCombine: disable `unchecked_bitwise_cast` -> `unchekced_ref_cast` …
eeckstein Dec 19, 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?

Copy link
Contributor Author

@eeckstein eeckstein Dec 19, 2025

Choose a reason for hiding this comment

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

The lexical check is only done in the mandatory pipeline. So, when running in the optimization pipeline, lexical begin_borrrows will be removed, anyway.

Can we remove an inner lexical borrow with an outer non lexical borrow?

I don't think so (in the mandatory pipeline) because then the lexical flag gets lost and the outer, non-lexical, borrow scope could be removed as well

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