Files
swift-mirror/SwiftCompilerSources/Sources/Optimizer/InstructionSimplification/SimplifyBeginAndLoadBorrow.swift
Erik Eckstein 610539a85f SIL: streamline Operand Sequence APIs
* remove `filterUsers(ofType:)`, because it's a duplication of `users(ofType:)`
* rename `filterUses(ofType:)` -> `filter(usersOfType:)`
* rename `ignoreUses(ofType:)` -> `ignore(usersOfType:)`
* rename `getSingleUser` -> `singleUser`
* implement `singleUse` with `Sequence.singleElement`
* implement `ignoreDebugUses` with `ignore(usersOfType:)`

This is a follow-up of eb1d5f484c.
2025-10-16 10:12:33 +02:00

280 lines
11 KiB
Swift

//===--- SimplifyBeginAndLoadBorrow.swift ---------------------------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2023 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//
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)
}
}
}
extension LoadBorrowInst : Simplifiable, SILCombineSimplifiable {
func simplify(_ context: SimplifyContext) {
if uses.ignoreDebugUses.ignore(usersOfType: EndBorrowInst.self).isEmpty {
context.erase(instructionIncludingAllUsers: self)
return
}
// 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)
}
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)
}
}
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)
}
}
}
private func removeBorrowOfThinFunction(beginBorrow: BeginBorrowInst, _ context: SimplifyContext) {
guard let thin2thickFn = beginBorrow.borrowedValue as? ThinToThickFunctionInst,
// For simplicity don't go into the trouble of removing reborrow phi arguments.
beginBorrow.uses.filter(usersOfType: BranchInst.self).isEmpty else
{
return
}
// `thin_to_thick_function` has "none" ownership and is compatible with guaranteed values.
// Therefore the `begin_borrow` is not needed.
beginBorrow.uses.ignore(usersOfType: EndBorrowInst.self).replaceAll(with: thin2thickFn, context)
context.erase(instructionIncludingAllUsers: beginBorrow)
}
/// Replace
/// ```
/// %1 = begin_borrow %0
/// %2 = struct_extract %1 // a chain of forwarding instructions
/// %3 = copy_value %1
/// // ... uses of %3
/// end_borrow %1
/// ```
/// with
/// ```
/// %1 = copy_value %0
/// %3 = destructure_struct %0 // owned version of the forwarding instructions
/// // ... uses of %3
/// ```
private func tryReplaceCopy(
of forwardedValue: Value,
withCopiedOperandOf beginBorrow: BeginBorrowInst,
_ context: SimplifyContext
) -> Bool {
guard let singleUser = forwardedValue.uses.ignore(usersOfType: EndBorrowInst.self).singleUse?.instruction,
let copy = singleUser as? CopyValueInst,
copy.parentBlock == beginBorrow.parentBlock else {
return false
}
let builder = Builder(before: beginBorrow, context)
let copiedOperand = builder.createCopyValue(operand: beginBorrow.borrowedValue)
let forwardedOwnedValue = replaceGuaranteed(value: beginBorrow, withOwnedValue: copiedOperand, context)
copy.replace(with: forwardedOwnedValue, context)
context.erase(instructionIncludingAllUsers: beginBorrow)
return true
}
/// Replace
/// ```
/// %1 = begin_borrow %0
/// %2 = struct_extract %1 // a chain of forwarding instructions
/// // ... uses of %2
/// end_borrow %1
/// destroy_value %1 // the only other use of %0 beside begin_borrow
/// ```
/// with
/// ```
/// %2 = destructure_struct %0 // owned version of the forwarding instructions
/// // ... uses of %2
/// destroy_value %2
/// ```
private func convertAllUsesToOwned(of beginBorrow: BeginBorrowInst, _ context: SimplifyContext) {
let forwardedOwnedValue = replaceGuaranteed(value: beginBorrow, withOwnedValue: beginBorrow.borrowedValue, context)
beginBorrow.borrowedValue.replaceAllDestroys(with: forwardedOwnedValue, context)
context.erase(instructionIncludingAllUsers: beginBorrow)
}
private extension Value {
/// Returns the last value of a (potentially empty) forwarding chain.
/// 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,
let fwdInst = singleUse.instruction as? (SingleValueInstruction & ForwardingInstruction),
fwdInst.canConvertToOwned,
fwdInst.isSingleForwardedOperand(singleUse),
fwdInst.parentBlock == parentBlock
{
return fwdInst.lookThroughSingleForwardingUses()
}
return self
}
var allUsesCanBeConvertedToOwned: Bool {
let relevantUses = uses.ignore(usersOfType: EndBorrowInst.self)
return relevantUses.allSatisfy { $0.canAccept(ownership: .owned) }
}
func isDestroyed(after nonDestroyUser: Instruction) -> Bool {
return uses.singleUser(notOfType: DestroyValueInst.self) == nonDestroyUser &&
nonDestroyUser.dominates(destroysOf: self)
}
func replaceAllDestroys(with replacement: Value, _ context: SimplifyContext) {
uses.filter(usersOfType: DestroyValueInst.self).replaceAll(with: replacement, context)
}
}
private extension Instruction {
func dominates(destroysOf value: Value) -> Bool {
// In instruction simplification we don't have a domtree. Therefore do a simple dominance
// check based on same-block relations.
if parentBlock == value.parentBlock {
// The value and instruction are in the same block. All uses are dominated by both.
return true
}
let destroys = value.uses.filter(usersOfType: DestroyValueInst.self)
return destroys.allSatisfy({ $0.instruction.parentBlock == parentBlock})
}
}
private extension ForwardingInstruction {
func isSingleForwardedOperand(_ operand: Operand) -> Bool {
switch self {
case is StructInst, is TupleInst:
// TODO: we could move that logic to StructInst/TupleInst.singleForwardedOperand.
return operands.lazy.map({ $0.value.type }).hasSingleNonTrivialElement(at: operand.index, in: parentFunction)
default:
if let sfo = singleForwardedOperand {
return sfo == operand
}
return false
}
}
}
/// Replaces a guaranteed value with an owned value.
///
/// If the `guaranteedValue`'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
/// no forwarding uses.
private func replaceGuaranteed(value: Value, withOwnedValue ownedValue: Value, _ context: SimplifyContext) -> Value {
var result = ownedValue
var numForwardingUses = 0
for use in value.uses {
switch use.instruction {
case let tei as TupleExtractInst:
numForwardingUses += 1
let dti = Builder(before: tei, context).createDestructureTuple(tuple: ownedValue)
result = replaceGuaranteed(value: tei, withOwnedValue: dti.results[tei.fieldIndex], context)
context.erase(instruction: tei)
case let sei as StructExtractInst:
numForwardingUses += 1
let dsi = Builder(before: sei, context).createDestructureStruct(struct: ownedValue)
result = replaceGuaranteed(value: sei, withOwnedValue: dsi.results[sei.fieldIndex], context)
context.erase(instruction: sei)
case let fwdInst as (SingleValueInstruction & ForwardingInstruction) where
fwdInst.isSingleForwardedOperand(use):
// Other forwarding instructions beside tuple_extract and struct_extract
numForwardingUses += 1
use.set(to: ownedValue, context)
fwdInst.setForwardingOwnership(to: .owned, context)
result = replaceGuaranteed(value: fwdInst, withOwnedValue: fwdInst, context)
case is EndBorrowInst:
break
default:
precondition(use.canAccept(ownership: .owned))
use.set(to: ownedValue, context)
}
}
precondition(numForwardingUses <= 1, "guaranteed value must not have multiple forwarding uses")
return result
}
private extension ForwardingInstruction {
var canConvertToOwned: Bool {
switch self {
case let si as StructExtractInst:
if si.struct.type.isMoveOnly {
// We cannot easily convert a struct_extract to a destructure_struct of a move-only type, because
// the deinit would get lost.
return false
}
let structFields = si.struct.type.getNominalFields(in: parentFunction)
return structFields?.hasSingleNonTrivialElement(at: si.fieldIndex, in: parentFunction) ?? false
case let ti as TupleExtractInst:
return ti.tuple.type.tupleElements.hasSingleNonTrivialElement(at: ti.fieldIndex, in: parentFunction)
default:
return canForwardOwnedValues
}
}
}
private extension Collection where Element == Type {
func hasSingleNonTrivialElement(at nonTrivialElementIndex: Int, in function: Function) -> Bool {
for (elementIdx, elementTy) in self.enumerated() {
if elementTy.isTrivial(in: function) != (elementIdx != nonTrivialElementIndex) {
return false
}
}
return true
}
}