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.
This commit is contained in:
Erik Eckstein
2025-10-16 10:12:33 +02:00
parent 2fceb9a41d
commit 610539a85f
14 changed files with 34 additions and 47 deletions

View File

@@ -363,7 +363,7 @@ private func createAllocStack(for allocBox: AllocBoxInst, flags: Flags, _ contex
isLexical: flags.isLexical, isLexical: flags.isLexical,
isFromVarDecl: flags.isFromVarDecl) isFromVarDecl: flags.isFromVarDecl)
let stackLocation: Value let stackLocation: Value
if let mu = allocBox.uses.getSingleUser(ofType: MarkUninitializedInst.self) { if let mu = allocBox.uses.singleUser(ofType: MarkUninitializedInst.self) {
stackLocation = builder.createMarkUninitialized(value: asi, kind: mu.kind) stackLocation = builder.createMarkUninitialized(value: asi, kind: mu.kind)
} else { } else {
stackLocation = asi stackLocation = asi
@@ -484,7 +484,7 @@ private func hoistMarkUnresolvedInsts(stackAddress: Value,
builder = Builder(atBeginOf: stackAddress.parentBlock, context) builder = Builder(atBeginOf: stackAddress.parentBlock, context)
} }
let mu = builder.createMarkUnresolvedNonCopyableValue(value: stackAddress, checkKind: checkKind, isStrict: false) let mu = builder.createMarkUnresolvedNonCopyableValue(value: stackAddress, checkKind: checkKind, isStrict: false)
stackAddress.uses.ignore(user: mu).ignoreDebugUses.ignoreUses(ofType: DeallocStackInst.self) stackAddress.uses.ignore(user: mu).ignoreDebugUses.ignore(usersOfType: DeallocStackInst.self)
.replaceAll(with: mu, context) .replaceAll(with: mu, context)
} }

View File

@@ -233,7 +233,7 @@ private func rewritePartialApply(_ partialApply: PartialApplyInst, withSpecializ
// leave the key path itself out of the dependency chain, and introduce dependencies on those // leave the key path itself out of the dependency chain, and introduce dependencies on those
// operands instead, so that the key path object itself can be made dead. // operands instead, so that the key path object itself can be made dead.
for md in newClosure.uses.users(ofType: MarkDependenceInst.self) { for md in newClosure.uses.users(ofType: MarkDependenceInst.self) {
if md.base.uses.getSingleUser(ofType: PartialApplyInst.self) == partialApply { if md.base.uses.singleUser(ofType: PartialApplyInst.self) == partialApply {
md.replace(with: newClosure, context) md.replace(with: newClosure, context)
} }
} }

View File

@@ -83,7 +83,7 @@ let destroyHoisting = FunctionPass(name: "destroy-hoisting") {
private func optimize(value: Value, _ context: FunctionPassContext) { private func optimize(value: Value, _ context: FunctionPassContext) {
guard value.ownership == .owned, guard value.ownership == .owned,
// Avoid all the analysis effort if there are no destroys to hoist. // Avoid all the analysis effort if there are no destroys to hoist.
!value.uses.filterUses(ofType: DestroyValueInst.self).isEmpty !value.uses.filter(usersOfType: DestroyValueInst.self).isEmpty
else { else {
return return
} }

View File

@@ -478,7 +478,7 @@ private struct InitValueBuilder: AddressDefUseWalker {
case .PrepareInitialization: case .PrepareInitialization:
return .continueWalk return .continueWalk
case .AddressOfRawLayout: case .AddressOfRawLayout:
if let addr2Ptr = bi.uses.getSingleUser(ofType: PointerToAddressInst.self) { if let addr2Ptr = bi.uses.singleUser(ofType: PointerToAddressInst.self) {
return walkDownUses(ofAddress: addr2Ptr, path: path) return walkDownUses(ofAddress: addr2Ptr, path: path)
} }
return .abortWalk return .abortWalk

View File

@@ -83,7 +83,7 @@ private func tryEliminate(copy: CopyLikeInstruction, keepDebugInfo: Bool, _ cont
removeDestroys(of: allocStack, context) removeDestroys(of: allocStack, context)
} }
allocStack.uses.ignoreUses(ofType: DeallocStackInst.self).replaceAll(with: copy.sourceAddress, context) allocStack.uses.ignore(usersOfType: DeallocStackInst.self).replaceAll(with: copy.sourceAddress, context)
if keepDebugInfo { if keepDebugInfo {
Builder(before: copy, context).createDebugStep() Builder(before: copy, context).createDebugStep()
@@ -247,7 +247,7 @@ private extension AllocStackInst {
var liferange = InstructionRange(begin: self, context) var liferange = InstructionRange(begin: self, context)
defer { liferange.deinitialize() } defer { liferange.deinitialize() }
liferange.insert(contentsOf: uses.ignoreUses(ofType: DeallocStackInst.self).lazy.map { $0.instruction }) liferange.insert(contentsOf: uses.ignore(usersOfType: DeallocStackInst.self).lazy.map { $0.instruction })
for use in uses { for use in uses {
switch use.instruction { switch use.instruction {

View File

@@ -207,7 +207,7 @@ private extension AllocStackInst {
iea.replace(with: newAlloc, context) iea.replace(with: newAlloc, context)
} }
case let oea as OpenExistentialAddrInst: case let oea as OpenExistentialAddrInst:
assert(oea.uses.ignoreUses(ofType: DestroyAddrInst.self).isEmpty) assert(oea.uses.ignore(usersOfType: DestroyAddrInst.self).isEmpty)
oea.replace(with: newAlloc, context) oea.replace(with: newAlloc, context)
case let cab as CheckedCastAddrBranchInst: case let cab as CheckedCastAddrBranchInst:
let builder = Builder(before: cab, context) let builder = Builder(before: cab, context)
@@ -247,7 +247,7 @@ private extension AllocStackInst {
is DebugValueInst: is DebugValueInst:
break break
case let oea as OpenExistentialAddrInst: case let oea as OpenExistentialAddrInst:
if !oea.uses.ignoreUses(ofType: DestroyAddrInst.self).isEmpty { if !oea.uses.ignore(usersOfType: DestroyAddrInst.self).isEmpty {
return nil return nil
} }
case let iea as InitExistentialAddrInst: case let iea as InitExistentialAddrInst:

View File

@@ -29,7 +29,7 @@ extension BeginBorrowInst : OnoneSimplifiable, SILCombineSimplifiable {
extension LoadBorrowInst : Simplifiable, SILCombineSimplifiable { extension LoadBorrowInst : Simplifiable, SILCombineSimplifiable {
func simplify(_ context: SimplifyContext) { func simplify(_ context: SimplifyContext) {
if uses.ignoreDebugUses.ignoreUses(ofType: EndBorrowInst.self).isEmpty { if uses.ignoreDebugUses.ignore(usersOfType: EndBorrowInst.self).isEmpty {
context.erase(instructionIncludingAllUsers: self) context.erase(instructionIncludingAllUsers: self)
return return
} }
@@ -52,7 +52,7 @@ extension LoadBorrowInst : Simplifiable, SILCombineSimplifiable {
private func tryCombineWithCopy(_ context: SimplifyContext) { private func tryCombineWithCopy(_ context: SimplifyContext) {
let forwardedValue = lookThroughSingleForwardingUses() let forwardedValue = lookThroughSingleForwardingUses()
guard let singleUser = forwardedValue.uses.ignoreUses(ofType: EndBorrowInst.self).singleUse?.instruction, guard let singleUser = forwardedValue.uses.ignore(usersOfType: EndBorrowInst.self).singleUse?.instruction,
let copy = singleUser as? CopyValueInst, let copy = singleUser as? CopyValueInst,
copy.parentBlock == self.parentBlock else { copy.parentBlock == self.parentBlock else {
return return
@@ -81,13 +81,13 @@ private func tryReplaceBorrowWithOwnedOperand(beginBorrow: BeginBorrowInst, _ co
private func removeBorrowOfThinFunction(beginBorrow: BeginBorrowInst, _ context: SimplifyContext) { private func removeBorrowOfThinFunction(beginBorrow: BeginBorrowInst, _ context: SimplifyContext) {
guard let thin2thickFn = beginBorrow.borrowedValue as? ThinToThickFunctionInst, guard let thin2thickFn = beginBorrow.borrowedValue as? ThinToThickFunctionInst,
// For simplicity don't go into the trouble of removing reborrow phi arguments. // For simplicity don't go into the trouble of removing reborrow phi arguments.
beginBorrow.uses.filterUses(ofType: BranchInst.self).isEmpty else beginBorrow.uses.filter(usersOfType: BranchInst.self).isEmpty else
{ {
return return
} }
// `thin_to_thick_function` has "none" ownership and is compatible with guaranteed values. // `thin_to_thick_function` has "none" ownership and is compatible with guaranteed values.
// Therefore the `begin_borrow` is not needed. // Therefore the `begin_borrow` is not needed.
beginBorrow.uses.ignoreUses(ofType: EndBorrowInst.self).replaceAll(with: thin2thickFn, context) beginBorrow.uses.ignore(usersOfType: EndBorrowInst.self).replaceAll(with: thin2thickFn, context)
context.erase(instructionIncludingAllUsers: beginBorrow) context.erase(instructionIncludingAllUsers: beginBorrow)
} }
@@ -110,7 +110,7 @@ private func tryReplaceCopy(
withCopiedOperandOf beginBorrow: BeginBorrowInst, withCopiedOperandOf beginBorrow: BeginBorrowInst,
_ context: SimplifyContext _ context: SimplifyContext
) -> Bool { ) -> Bool {
guard let singleUser = forwardedValue.uses.ignoreUses(ofType: EndBorrowInst.self).singleUse?.instruction, guard let singleUser = forwardedValue.uses.ignore(usersOfType: EndBorrowInst.self).singleUse?.instruction,
let copy = singleUser as? CopyValueInst, let copy = singleUser as? CopyValueInst,
copy.parentBlock == beginBorrow.parentBlock else { copy.parentBlock == beginBorrow.parentBlock else {
return false return false
@@ -153,7 +153,7 @@ private extension Value {
/// ``` /// ```
/// Returns self if this value has no uses which are ForwardingInstructions. /// Returns self if this value has no uses which are ForwardingInstructions.
func lookThroughSingleForwardingUses() -> Value { func lookThroughSingleForwardingUses() -> Value {
if let singleUse = uses.ignoreUses(ofType: EndBorrowInst.self).singleUse, if let singleUse = uses.ignore(usersOfType: EndBorrowInst.self).singleUse,
let fwdInst = singleUse.instruction as? (SingleValueInstruction & ForwardingInstruction), let fwdInst = singleUse.instruction as? (SingleValueInstruction & ForwardingInstruction),
fwdInst.canConvertToOwned, fwdInst.canConvertToOwned,
fwdInst.isSingleForwardedOperand(singleUse), fwdInst.isSingleForwardedOperand(singleUse),
@@ -165,17 +165,17 @@ private extension Value {
} }
var allUsesCanBeConvertedToOwned: Bool { var allUsesCanBeConvertedToOwned: Bool {
let relevantUses = uses.ignoreUses(ofType: EndBorrowInst.self) let relevantUses = uses.ignore(usersOfType: EndBorrowInst.self)
return relevantUses.allSatisfy { $0.canAccept(ownership: .owned) } return relevantUses.allSatisfy { $0.canAccept(ownership: .owned) }
} }
func isDestroyed(after nonDestroyUser: Instruction) -> Bool { func isDestroyed(after nonDestroyUser: Instruction) -> Bool {
return uses.getSingleUser(notOfType: DestroyValueInst.self) == nonDestroyUser && return uses.singleUser(notOfType: DestroyValueInst.self) == nonDestroyUser &&
nonDestroyUser.dominates(destroysOf: self) nonDestroyUser.dominates(destroysOf: self)
} }
func replaceAllDestroys(with replacement: Value, _ context: SimplifyContext) { func replaceAllDestroys(with replacement: Value, _ context: SimplifyContext) {
uses.filterUses(ofType: DestroyValueInst.self).replaceAll(with: replacement, context) uses.filter(usersOfType: DestroyValueInst.self).replaceAll(with: replacement, context)
} }
} }
@@ -187,7 +187,7 @@ private extension Instruction {
// The value and instruction are in the same block. All uses are dominated by both. // The value and instruction are in the same block. All uses are dominated by both.
return true return true
} }
let destroys = value.uses.filterUses(ofType: DestroyValueInst.self) let destroys = value.uses.filter(usersOfType: DestroyValueInst.self)
return destroys.allSatisfy({ $0.instruction.parentBlock == parentBlock}) return destroys.allSatisfy({ $0.instruction.parentBlock == parentBlock})
} }
} }

View File

@@ -406,7 +406,7 @@ private extension Value {
// var p = Point(x: 10, y: 20) // var p = Point(x: 10, y: 20)
// let o = UnsafePointer(&p) // let o = UnsafePointer(&p)
// Therefore ignore the `end_access` use of a `begin_access`. // Therefore ignore the `end_access` use of a `begin_access`.
let relevantUses = singleUseValue.uses.ignoreDebugUses.ignoreUses(ofType: EndAccessInst.self) let relevantUses = singleUseValue.uses.ignoreDebugUses.ignore(usersOfType: EndAccessInst.self)
guard let use = relevantUses.singleUse else { guard let use = relevantUses.singleUse else {
return nil return nil

View File

@@ -361,7 +361,7 @@ private struct StackProtectionOptimization {
let builder = Builder(after: beginAccess, location: beginAccess.location.asAutoGenerated, context) let builder = Builder(after: beginAccess, location: beginAccess.location.asAutoGenerated, context)
let temporary = builder.createAllocStack(beginAccess.type) let temporary = builder.createAllocStack(beginAccess.type)
beginAccess.uses.ignoreUses(ofType: EndAccessInst.self).replaceAll(with: temporary, context) beginAccess.uses.ignore(usersOfType: EndAccessInst.self).replaceAll(with: temporary, context)
for endAccess in beginAccess.endInstructions { for endAccess in beginAccess.endInstructions {
let endBuilder = Builder(before: endAccess, location: endAccess.location.asAutoGenerated, context) let endBuilder = Builder(before: endAccess, location: endAccess.location.asAutoGenerated, context)

View File

@@ -432,7 +432,7 @@ extension Instruction {
case .USubOver: case .USubOver:
// Handle StringObjectOr(tuple_extract(usub_with_overflow(x, offset)), bits) // Handle StringObjectOr(tuple_extract(usub_with_overflow(x, offset)), bits)
// This pattern appears in UTF8 String literal construction. // This pattern appears in UTF8 String literal construction.
if let tei = bi.uses.getSingleUser(ofType: TupleExtractInst.self), if let tei = bi.uses.singleUser(ofType: TupleExtractInst.self),
tei.isResultOfOffsetSubtract { tei.isResultOfOffsetSubtract {
return true return true
} }
@@ -446,7 +446,7 @@ extension Instruction {
// Handle StringObjectOr(tuple_extract(usub_with_overflow(x, offset)), bits) // Handle StringObjectOr(tuple_extract(usub_with_overflow(x, offset)), bits)
// This pattern appears in UTF8 String literal construction. // This pattern appears in UTF8 String literal construction.
if tei.isResultOfOffsetSubtract, if tei.isResultOfOffsetSubtract,
let bi = tei.uses.getSingleUser(ofType: BuiltinInst.self), let bi = tei.uses.singleUser(ofType: BuiltinInst.self),
bi.id == .StringObjectOr { bi.id == .StringObjectOr {
return true return true
} }

View File

@@ -717,7 +717,7 @@ extension InteriorUseWalker: AddressUseVisitor {
if handleInner(borrowed: sb) == .abortWalk { if handleInner(borrowed: sb) == .abortWalk {
return .abortWalk return .abortWalk
} }
return sb.uses.filterUses(ofType: EndBorrowInst.self).walk { return sb.uses.filter(usersOfType: EndBorrowInst.self).walk {
useVisitor($0) useVisitor($0)
} }
case let load as LoadBorrowInst: case let load as LoadBorrowInst:

View File

@@ -143,16 +143,7 @@ extension Sequence where Element == Operand {
self.lazy.map { $0.value } self.lazy.map { $0.value }
} }
public var singleUse: Operand? { public var singleUse: Operand? { singleElement }
var result: Operand? = nil
for op in self {
if result != nil {
return nil
}
result = op
}
return result
}
public var isSingleUse: Bool { singleUse != nil } public var isSingleUse: Bool { singleUse != nil }
@@ -161,18 +152,14 @@ extension Sequence where Element == Operand {
} }
public var ignoreDebugUses: LazyFilterSequence<Self> { public var ignoreDebugUses: LazyFilterSequence<Self> {
self.lazy.filter { !($0.instruction is DebugValueInst) } ignore(usersOfType: DebugValueInst.self)
} }
public func filterUses<I: Instruction>(ofType: I.Type) -> LazyFilterSequence<Self> { public func filter<I: Instruction>(usersOfType: I.Type) -> LazyFilterSequence<Self> {
self.lazy.filter { $0.instruction is I } self.lazy.filter { $0.instruction is I }
} }
public func filterUsers<I: Instruction>(ofType: I.Type) -> LazyMapSequence<LazyFilterSequence<Self>, I> { public func ignore<I: Instruction>(usersOfType: I.Type) -> LazyFilterSequence<Self> {
self.lazy.filter { $0.instruction is I }.lazy.map { $0.instruction as! I }
}
public func ignoreUses<I: Instruction>(ofType: I.Type) -> LazyFilterSequence<Self> {
self.lazy.filter { !($0.instruction is I) } self.lazy.filter { !($0.instruction is I) }
} }
@@ -180,12 +167,12 @@ extension Sequence where Element == Operand {
self.lazy.filter { !($0.instruction == user) } self.lazy.filter { !($0.instruction == user) }
} }
public func getSingleUser<I: Instruction>(ofType: I.Type) -> I? { public func singleUser<I: Instruction>(ofType: I.Type) -> I? {
filterUses(ofType: I.self).singleUse?.instruction as? I filter(usersOfType: I.self).singleUse?.instruction as? I
} }
public func getSingleUser<I: Instruction>(notOfType: I.Type) -> Instruction? { public func singleUser<I: Instruction>(notOfType: I.Type) -> Instruction? {
ignoreUses(ofType: I.self).singleUse?.instruction ignore(usersOfType: I.self).singleUse?.instruction
} }
public var endingLifetime: LazyFilterSequence<Self> { public var endingLifetime: LazyFilterSequence<Self> {

View File

@@ -300,7 +300,7 @@ public enum BorrowingInstruction : CustomStringConvertible, Hashable {
extension BorrowingInstruction { extension BorrowingInstruction {
private func visitEndBorrows(value: Value, _ context: Context, _ visitor: @escaping (Operand) -> WalkResult) private func visitEndBorrows(value: Value, _ context: Context, _ visitor: @escaping (Operand) -> WalkResult)
-> WalkResult { -> WalkResult {
return value.lookThroughBorrowedFromUser.uses.filterUses(ofType: EndBorrowInst.self).walk { return value.lookThroughBorrowedFromUser.uses.filter(usersOfType: EndBorrowInst.self).walk {
visitor($0) visitor($0)
} }
} }

View File

@@ -137,7 +137,7 @@ private func createEmptyBorrowedFrom(for phi: Phi, _ context: some MutatingConte
} }
let builder = Builder(atBeginOf: phi.value.parentBlock, context) let builder = Builder(atBeginOf: phi.value.parentBlock, context)
let bfi = builder.createBorrowedFrom(borrowedValue: phi.value, enclosingValues: []) let bfi = builder.createBorrowedFrom(borrowedValue: phi.value, enclosingValues: [])
phi.value.uses.ignoreUses(ofType: BorrowedFromInst.self).replaceAll(with: bfi, context) 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: /// Replaces a phi with the unique incoming value if all incoming values are the same: