mirror of
https://github.com/apple/swift.git
synced 2025-12-14 20:36:38 +01:00
* 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.
266 lines
8.8 KiB
Swift
266 lines
8.8 KiB
Swift
//===--- PhiUpdater.swift -------------------------------------------------===//
|
|
//
|
|
// This source file is part of the Swift.org open source project
|
|
//
|
|
// Copyright (c) 2014 - 2024 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 SILBridging
|
|
|
|
/// Updates the reborrow flags and the borrowed-from instructions for all guaranteed phis in `function`.
|
|
public func updateGuaranteedPhis(in function: Function, _ context: some MutatingContext) {
|
|
updateReborrowFlags(in: function, context)
|
|
updateBorrowedFrom(in: function, context)
|
|
}
|
|
|
|
/// Updates the reborrow flags and the borrowed-from instructions for all `phis`.
|
|
public func updateGuaranteedPhis(phis: some Sequence<Phi>, _ context: some MutatingContext) {
|
|
updateReborrowFlags(for: phis, context)
|
|
updateBorrowedFrom(for: phis, context)
|
|
}
|
|
|
|
/// Update all borrowed-from instructions in the `function`
|
|
public func updateBorrowedFrom(in function: Function, _ context: some MutatingContext) {
|
|
if !function.hasOwnership {
|
|
return
|
|
}
|
|
var guaranteedPhis = Stack<Phi>(context)
|
|
defer { guaranteedPhis.deinitialize() }
|
|
|
|
for block in function.blocks {
|
|
for arg in block.arguments {
|
|
if let phi = Phi(arg), phi.value.ownership == .guaranteed {
|
|
guaranteedPhis.append(phi)
|
|
}
|
|
}
|
|
}
|
|
updateBorrowedFrom(for: guaranteedPhis, context)
|
|
}
|
|
|
|
/// Update borrowed-from instructions for a set of phi arguments.
|
|
public func updateBorrowedFrom(for phis: some Sequence<Phi>, _ context: some MutatingContext) {
|
|
for phi in phis {
|
|
if !phi.value.parentFunction.hasOwnership {
|
|
return
|
|
}
|
|
if phi.value.ownership == .guaranteed {
|
|
createEmptyBorrowedFrom(for: phi, context)
|
|
}
|
|
}
|
|
|
|
var changed: Bool
|
|
repeat {
|
|
changed = false
|
|
|
|
for phi in phis {
|
|
if phi.value.ownership == .guaranteed {
|
|
changed = updateBorrowedFrom(for: phi, context) || changed
|
|
}
|
|
}
|
|
} while changed
|
|
}
|
|
|
|
/// Updates the reborrow flags for all guaranteed phis in `function`.
|
|
public func updateReborrowFlags(in function: Function, _ context: some MutatingContext) {
|
|
if !function.hasOwnership {
|
|
return
|
|
}
|
|
var guaranteedPhis = Stack<Phi>(context)
|
|
defer { guaranteedPhis.deinitialize() }
|
|
|
|
for block in function.blocks.reversed() {
|
|
for arg in block.arguments {
|
|
if let phi = Phi(arg), phi.value.ownership == .guaranteed {
|
|
guaranteedPhis.append(phi)
|
|
}
|
|
}
|
|
}
|
|
updateReborrowFlags(for: guaranteedPhis, context)
|
|
}
|
|
|
|
/// Updates the reborrow flags for all `phis`.
|
|
///
|
|
/// Re-borrow flags are only set, but never cleared. If an optimization creates a dead-end block
|
|
/// by cutting off the control flow before an `end_borrow`, the re-borrow flags still have to remain
|
|
/// without the possibility to re-calculate them from the (now missing) `end_borrow`.
|
|
///
|
|
public func updateReborrowFlags(for phis: some Sequence<Phi>, _ context: some MutatingContext) {
|
|
if let phi = phis.first(where: { phi in true }), !phi.value.parentFunction.hasOwnership {
|
|
return
|
|
}
|
|
|
|
var changed: Bool
|
|
repeat {
|
|
changed = false
|
|
|
|
for phi in phis where phi.value.ownership == .guaranteed {
|
|
if !phi.value.isReborrow && phi.hasBorrowEndingUse {
|
|
phi.value.set(reborrow: true, context)
|
|
changed = true
|
|
}
|
|
}
|
|
} while changed
|
|
}
|
|
|
|
private func updateBorrowedFrom(for phi: Phi, _ context: some MutatingContext) -> Bool {
|
|
var computedEVs = Stack<Value>(context)
|
|
defer { computedEVs.deinitialize() }
|
|
gatherEnclosingValuesFromPredecessors(for: phi, in: &computedEVs, context)
|
|
|
|
let borrowedFrom = phi.borrowedFrom!
|
|
var existingEVs = ValueSet(insertContentsOf: borrowedFrom.enclosingValues, context)
|
|
defer { existingEVs.deinitialize() }
|
|
|
|
if computedEVs.allSatisfy({ existingEVs.contains($0) }) {
|
|
return false
|
|
}
|
|
var evs = Array<Value>(borrowedFrom.enclosingValues)
|
|
evs.append(contentsOf: computedEVs.lazy.filter { !existingEVs.contains($0) })
|
|
|
|
let builder = Builder(before: borrowedFrom, context)
|
|
let newBfi = builder.createBorrowedFrom(borrowedValue: borrowedFrom.borrowedValue, enclosingValues: evs)
|
|
borrowedFrom.replace(with: newBfi, context)
|
|
return true
|
|
}
|
|
|
|
private func createEmptyBorrowedFrom(for phi: Phi, _ context: some MutatingContext) {
|
|
if let existingBfi = phi.borrowedFrom {
|
|
if existingBfi.enclosingValues.isEmpty {
|
|
return
|
|
}
|
|
existingBfi.replace(with: phi.value, context)
|
|
}
|
|
let builder = Builder(atBeginOf: phi.value.parentBlock, context)
|
|
let bfi = builder.createBorrowedFrom(borrowedValue: phi.value, enclosingValues: [])
|
|
phi.value.uses.ignore(usersOfType: BorrowedFromInst.self).replaceAll(with: bfi, context)
|
|
}
|
|
|
|
/// Replaces a phi with the unique incoming value if all incoming values are the same:
|
|
/// ```
|
|
/// bb1:
|
|
/// br bb3(%1)
|
|
/// bb2:
|
|
/// br bb3(%1)
|
|
/// bb3(%2 : $T): // Predecessors: bb1, bb2
|
|
/// use(%2)
|
|
/// ```
|
|
/// ->
|
|
/// ```
|
|
/// bb1:
|
|
/// br bb3
|
|
/// bb2:
|
|
/// br bb3
|
|
/// bb3:
|
|
/// use(%1)
|
|
/// ```
|
|
///
|
|
public func replacePhiWithIncomingValue(phi: Phi, _ context: some MutatingContext) -> Bool {
|
|
if phi.predecessors.isEmpty {
|
|
return false
|
|
}
|
|
let uniqueIncomingValue = phi.incomingValues.first!
|
|
if !uniqueIncomingValue.parentFunction.hasOwnership {
|
|
// For the SSAUpdater it's only required to simplify phis in OSSA.
|
|
// This avoids that we need to handle cond_br instructions below.
|
|
return false
|
|
}
|
|
if phi.incomingValues.contains(where: { $0 != uniqueIncomingValue }) {
|
|
return false
|
|
}
|
|
if let borrowedFrom = phi.borrowedFrom {
|
|
borrowedFrom.replace(with: uniqueIncomingValue, context)
|
|
} else {
|
|
phi.value.uses.replaceAll(with: uniqueIncomingValue, context)
|
|
}
|
|
|
|
let block = phi.value.parentBlock
|
|
for incomingOp in phi.incomingOperands {
|
|
let existingBranch = incomingOp.instruction as! BranchInst
|
|
let argsWithRemovedPhiOp = existingBranch.operands.filter{ $0 != incomingOp }.map{ $0.value }
|
|
Builder(before: existingBranch, context).createBranch(to: block, arguments: argsWithRemovedPhiOp)
|
|
context.erase(instruction: existingBranch)
|
|
}
|
|
block.eraseArgument(at: phi.value.index, context)
|
|
return true
|
|
}
|
|
|
|
/// Replaces phis with the unique incoming values if all incoming values are the same.
|
|
/// This is needed after running the SSAUpdater for an existing OSSA value, because the updater can
|
|
/// insert unnecessary phis in the middle of the original liverange which breaks up the original
|
|
/// liverange into smaller ones:
|
|
/// ```
|
|
/// %1 = def_of_owned_value
|
|
/// %2 = begin_borrow %1
|
|
/// ...
|
|
/// br bb2(%1)
|
|
/// bb2(%3 : @owned $T): // inserted by SSAUpdater
|
|
/// ...
|
|
/// end_borrow %2 // use after end-of-lifetime!
|
|
/// destroy_value %3
|
|
/// ```
|
|
///
|
|
/// It's not needed to run this utility if SSAUpdater is used to create a _new_ OSSA liverange.
|
|
///
|
|
public func replacePhisWithIncomingValues(phis: [Phi], _ context: some MutatingContext) {
|
|
var currentPhis = phis
|
|
// Do this in a loop because replacing one phi might open up the opportunity for another phi
|
|
// and the order of phis in the array can be arbitrary.
|
|
while true {
|
|
var newPhis = [Phi]()
|
|
for phi in currentPhis {
|
|
if !replacePhiWithIncomingValue(phi: phi, context) {
|
|
newPhis.append(phi)
|
|
}
|
|
}
|
|
if newPhis.count == currentPhis.count {
|
|
return
|
|
}
|
|
currentPhis = newPhis
|
|
}
|
|
}
|
|
|
|
func registerPhiUpdater() {
|
|
BridgedUtilities.registerPhiUpdater(
|
|
// updateAllGuaranteedPhis
|
|
{ (bridgedCtxt: BridgedContext, bridgedFunction: BridgedFunction) in
|
|
let context = PhiUpdaterContext(_bridged: bridgedCtxt)
|
|
let function = bridgedFunction.function;
|
|
updateGuaranteedPhis(in: function, context)
|
|
},
|
|
// updateGuaranteedPhis
|
|
{ (bridgedCtxt: BridgedContext, bridgedPhiArray: BridgedArrayRef) in
|
|
let context = PhiUpdaterContext(_bridged: bridgedCtxt)
|
|
var guaranteedPhis = Stack<Phi>(context)
|
|
defer { guaranteedPhis.deinitialize() }
|
|
bridgedPhiArray.withElements(ofType: BridgedValue.self) {
|
|
for bridgedVal in $0 {
|
|
let phi = Phi(bridgedVal.value)!
|
|
if phi.value.ownership == .guaranteed {
|
|
guaranteedPhis.append(phi)
|
|
}
|
|
}
|
|
}
|
|
updateGuaranteedPhis(phis: guaranteedPhis, context)
|
|
},
|
|
// replacePhisWithIncomingValues
|
|
{ (bridgedCtxt: BridgedContext, bridgedPhiArray: BridgedArrayRef) in
|
|
let context = PhiUpdaterContext(_bridged: bridgedCtxt)
|
|
var phis = [Phi]()
|
|
bridgedPhiArray.withElements(ofType: BridgedValue.self) {
|
|
phis = $0.map { Phi($0.value)! }
|
|
}
|
|
replacePhisWithIncomingValues(phis: phis, context)
|
|
}
|
|
)
|
|
|
|
struct PhiUpdaterContext: MutatingContext {
|
|
let _bridged: BridgedContext
|
|
public let notifyInstructionChanged: (Instruction) -> () = { inst in }
|
|
}
|
|
}
|