Skip to content

Commit a14075a

Browse files
authored
Merge pull request #69820 from eeckstein/simplify-begin-borrow
Optimizer: add simplification for begin_borrow
2 parents 3ab2064 + 4f09d29 commit a14075a

19 files changed

+663
-127
lines changed

SwiftCompilerSources/Sources/Optimizer/FunctionPasses/NamedReturnValueOptimization.swift

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ let namedReturnValueOptimization = FunctionPass(name: "named-return-value-optimi
6161
/// Returns a copy_addr which copies from an alloc_stack to the `outArg` at the end of the function.
6262
///
6363
private func findCopyForNRVO(for outArg: FunctionArgument) -> CopyAddrInst? {
64-
guard let singleArgUse = outArg.uses.singleNonDebugUse,
64+
guard let singleArgUse = outArg.uses.ignoreDebugUses.singleUse,
6565
let copyToArg = singleArgUse.instruction as? CopyAddrInst else {
6666
return nil
6767
}
@@ -106,7 +106,7 @@ private func findCopyForNRVO(for outArg: FunctionArgument) -> CopyAddrInst? {
106106
}
107107

108108
private func performNRVO(with copy: CopyAddrInst, _ context: FunctionPassContext) {
109-
copy.source.uses.replaceAllExceptDealloc(with: copy.destination, context)
109+
copy.source.replaceAllUsesExceptDealloc(with: copy.destination, context)
110110
assert(copy.source == copy.destination)
111111
context.erase(instruction: copy)
112112
}
@@ -122,10 +122,8 @@ private func isAnyInstructionWritingToMemory(after: Instruction) -> Bool {
122122
return false
123123
}
124124

125-
private extension UseList {
126-
func replaceAllExceptDealloc(with replacement: Value, _ context: some MutatingContext) {
127-
for use in self where !(use.instruction is Deallocation) {
128-
use.set(to: replacement, context)
129-
}
125+
private extension Value {
126+
func replaceAllUsesExceptDealloc(with replacement: Value, _ context: some MutatingContext) {
127+
uses.lazy.filter{!($0.instruction is Deallocation)}.replaceAll(with: replacement, context)
130128
}
131129
}

SwiftCompilerSources/Sources/Optimizer/InstructionSimplification/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
swift_compiler_sources(Optimizer
1010
SimplifyApply.swift
11+
SimplifyBeginBorrow.swift
1112
SimplifyBeginCOWMutation.swift
1213
SimplifyBranch.swift
1314
SimplifyBuiltin.swift
Lines changed: 211 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,211 @@
1+
//===--- SimplifyBeginBorrow.swift ----------------------------------------===//
2+
//
3+
// This source file is part of the Swift.org open source project
4+
//
5+
// Copyright (c) 2014 - 2023 Apple Inc. and the Swift project authors
6+
// Licensed under Apache License v2.0 with Runtime Library Exception
7+
//
8+
// See https://swift.org/LICENSE.txt for license information
9+
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
10+
//
11+
//===----------------------------------------------------------------------===//
12+
13+
import SIL
14+
15+
extension BeginBorrowInst : OnoneSimplifyable {
16+
func simplify(_ context: SimplifyContext) {
17+
if borrowedValue.ownership == .owned,
18+
// We need to keep lexical lifetimes in place.
19+
!isLexical
20+
{
21+
tryReplaceBorrowWithOwnedOperand(beginBorrow: self, context)
22+
}
23+
}
24+
}
25+
26+
private func tryReplaceBorrowWithOwnedOperand(beginBorrow: BeginBorrowInst, _ context: SimplifyContext) {
27+
// The last value of a (potentially empty) forwarding chain, beginning at the `begin_borrow`.
28+
let forwardedValue = beginBorrow.lookThroughSingleForwardingUses()
29+
if forwardedValue.allUsesCanBeConvertedToOwned {
30+
if tryReplaceCopy(of: forwardedValue, withCopiedOperandOf: beginBorrow, context) {
31+
return
32+
}
33+
if beginBorrow.borrowedValue.isDestroyed(after: beginBorrow) {
34+
convertAllUsesToOwned(of: beginBorrow, context)
35+
}
36+
}
37+
}
38+
39+
/// Replace
40+
/// ```
41+
/// %1 = begin_borrow %0
42+
/// %2 = struct_extract %1 // a chain of forwarding instructions
43+
/// %3 = copy_value %1
44+
/// // ... uses of %3
45+
/// end_borrow %1
46+
/// ```
47+
/// with
48+
/// ```
49+
/// %1 = copy_value %0
50+
/// %3 = destructure_struct %0 // owned version of the forwarding instructions
51+
/// // ... uses of %3
52+
/// ```
53+
private func tryReplaceCopy(
54+
of forwardedValue: Value,
55+
withCopiedOperandOf beginBorrow: BeginBorrowInst,
56+
_ context: SimplifyContext
57+
) -> Bool {
58+
guard let singleUser = forwardedValue.uses.ignoreUsers(ofType: EndBorrowInst.self).singleUse?.instruction,
59+
let copy = singleUser as? CopyValueInst,
60+
copy.parentBlock == beginBorrow.parentBlock else {
61+
return false
62+
}
63+
let builder = Builder(before: beginBorrow, context)
64+
let copiedOperand = builder.createCopyValue(operand: beginBorrow.borrowedValue)
65+
let forwardedOwnedValue = replace(guaranteedValue: beginBorrow, withOwnedValue: copiedOperand, context)
66+
copy.uses.replaceAll(with: forwardedOwnedValue, context)
67+
context.erase(instruction: copy)
68+
context.erase(instructionIncludingAllUsers: beginBorrow)
69+
return true
70+
}
71+
72+
/// Replace
73+
/// ```
74+
/// %1 = begin_borrow %0
75+
/// %2 = struct_extract %1 // a chain of forwarding instructions
76+
/// // ... uses of %2
77+
/// end_borrow %1
78+
/// destroy_value %1 // the only other use of %0 beside begin_borrow
79+
/// ```
80+
/// with
81+
/// ```
82+
/// %2 = destructure_struct %0 // owned version of the forwarding instructions
83+
/// // ... uses of %2
84+
/// destroy_value %2
85+
/// ```
86+
private func convertAllUsesToOwned(of beginBorrow: BeginBorrowInst, _ context: SimplifyContext) {
87+
let forwardedOwnedValue = replace(guaranteedValue: beginBorrow, withOwnedValue: beginBorrow.borrowedValue, context)
88+
beginBorrow.borrowedValue.replaceAllDestroys(with: forwardedOwnedValue, context)
89+
context.erase(instructionIncludingAllUsers: beginBorrow)
90+
}
91+
92+
private extension Value {
93+
/// Returns the last value of a (potentially empty) forwarding chain.
94+
/// For example, returns %3 for the following def-use chain:
95+
/// ```
96+
/// %1 = struct_extract %self, #someField
97+
/// %2 = tuple_extract %1, 0
98+
/// %3 = struct $S(%2) // %3 has no forwarding users
99+
/// ```
100+
/// Returns self if this value has no uses which are ForwardingInstructions.
101+
func lookThroughSingleForwardingUses() -> Value {
102+
if let singleUse = uses.ignoreUsers(ofType: EndBorrowInst.self).singleUse,
103+
let fwdInst = singleUse.instruction as? (SingleValueInstruction & ForwardingInstruction),
104+
fwdInst.canConvertToOwned,
105+
fwdInst.isSingleForwardedOperand(singleUse),
106+
fwdInst.parentBlock == parentBlock
107+
{
108+
return fwdInst.lookThroughSingleForwardingUses()
109+
}
110+
return self
111+
}
112+
113+
var allUsesCanBeConvertedToOwned: Bool {
114+
let relevantUses = uses.ignoreUsers(ofType: EndBorrowInst.self)
115+
return relevantUses.allSatisfy { $0.canAccept(ownership: .owned) }
116+
}
117+
118+
func isDestroyed(after nonDestroyUser: Instruction) -> Bool {
119+
uses.getSingleUser(notOfType: DestroyValueInst.self) == nonDestroyUser
120+
}
121+
122+
func replaceAllDestroys(with replacement: Value, _ context: SimplifyContext) {
123+
uses.filterUsers(ofType: DestroyValueInst.self).replaceAll(with: replacement, context)
124+
}
125+
}
126+
127+
private extension ForwardingInstruction {
128+
func isSingleForwardedOperand(_ operand: Operand) -> Bool {
129+
switch self {
130+
case is StructInst, is TupleInst:
131+
// TODO: we could move that logic to StructInst/TupleInst.singleForwardedOperand.
132+
return operands.lazy.map({ $0.value.type }).hasSingleNonTrivialElement(at: operand.index, in: parentFunction)
133+
default:
134+
if let sfo = singleForwardedOperand {
135+
return sfo == operand
136+
}
137+
return false
138+
}
139+
}
140+
}
141+
142+
/// Replaces a guaranteed value with an owned value.
143+
///
144+
/// If the `guaranteedValue`'s use is a ForwardingInstruction (or forwarding instruction chain),
145+
/// it is converted to an owned version of the forwarding instruction (or instruction chain).
146+
///
147+
/// Returns the last owned value in a forwarding-chain or `ownedValue` if `guaranteedValue` has
148+
/// no forwarding uses.
149+
private func replace(guaranteedValue: Value, withOwnedValue ownedValue: Value, _ context: SimplifyContext) -> Value {
150+
var result = ownedValue
151+
var numForwardingUses = 0
152+
for use in guaranteedValue.uses {
153+
154+
switch use.instruction {
155+
case let tei as TupleExtractInst:
156+
numForwardingUses += 1
157+
let dti = Builder(before: tei, context).createDestructureTuple(tuple: ownedValue)
158+
result = replace(guaranteedValue: tei, withOwnedValue: dti.results[tei.fieldIndex], context)
159+
context.erase(instruction: tei)
160+
case let sei as StructExtractInst:
161+
numForwardingUses += 1
162+
let dsi = Builder(before: sei, context).createDestructureStruct(struct: ownedValue)
163+
result = replace(guaranteedValue: sei, withOwnedValue: dsi.results[sei.fieldIndex], context)
164+
context.erase(instruction: sei)
165+
case let fwdInst as (SingleValueInstruction & ForwardingInstruction) where
166+
fwdInst.isSingleForwardedOperand(use):
167+
// Other forwarding instructions beside tuple_extract and struct_extract
168+
numForwardingUses += 1
169+
use.set(to: ownedValue, context)
170+
fwdInst.setForwardingOwnership(to: .owned, context)
171+
result = replace(guaranteedValue: fwdInst, withOwnedValue: fwdInst, context)
172+
case is EndBorrowInst:
173+
break
174+
default:
175+
precondition(use.canAccept(ownership: .owned))
176+
use.set(to: ownedValue, context)
177+
}
178+
}
179+
precondition(numForwardingUses <= 1, "guaranteed value must not have multiple forwarding uses")
180+
return result
181+
}
182+
183+
private extension ForwardingInstruction {
184+
var canConvertToOwned: Bool {
185+
switch self {
186+
case let si as StructExtractInst:
187+
if si.struct.type.isMoveOnly {
188+
// We cannot easily convert a struct_extract to a destructure_struct of a move-only type, because
189+
// the deinit would get lost.
190+
return false
191+
}
192+
let structFields = si.struct.type.getNominalFields(in: parentFunction)
193+
return structFields.hasSingleNonTrivialElement(at: si.fieldIndex, in: parentFunction)
194+
case let ti as TupleExtractInst:
195+
return ti.tuple.type.tupleElements.hasSingleNonTrivialElement(at: ti.fieldIndex, in: parentFunction)
196+
default:
197+
return canForwardOwnedValues
198+
}
199+
}
200+
}
201+
202+
private extension Collection where Element == Type {
203+
func hasSingleNonTrivialElement(at nonTrivialElementIndex: Int, in function: Function) -> Bool {
204+
for (elementIdx, elementTy) in self.enumerated() {
205+
if elementTy.isTrivial(in: function) != (elementIdx != nonTrivialElementIndex) {
206+
return false
207+
}
208+
}
209+
return true
210+
}
211+
}

SwiftCompilerSources/Sources/Optimizer/InstructionSimplification/SimplifyBeginCOWMutation.swift

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ private extension BeginCOWMutationInst {
5959
if !isEmptyCOWSingleton(instance) {
6060
return
6161
}
62-
if uniquenessResult.nonDebugUses.isEmpty {
62+
if uniquenessResult.uses.ignoreDebugUses.isEmpty {
6363
/// Don't create an integer_literal which would be dead. This would result
6464
/// in an infinite loop in SILCombine.
6565
return
@@ -70,15 +70,15 @@ private extension BeginCOWMutationInst {
7070
}
7171

7272
func optimizeEmptyBeginEndPair(_ context: SimplifyContext) -> Bool {
73-
if !uniquenessResult.nonDebugUses.isEmpty {
73+
if !uniquenessResult.uses.ignoreDebugUses.isEmpty {
7474
return false
7575
}
7676
let buffer = instanceResult
77-
if buffer.nonDebugUses.contains(where: { !($0.instruction is EndCOWMutationInst) }) {
77+
if buffer.uses.ignoreDebugUses.contains(where: { !($0.instruction is EndCOWMutationInst) }) {
7878
return false
7979
}
8080

81-
for use in buffer.nonDebugUses {
81+
for use in buffer.uses.ignoreDebugUses {
8282
let endCOW = use.instruction as! EndCOWMutationInst
8383
endCOW.uses.replaceAll(with: instance, context)
8484
context.erase(instruction: endCOW)
@@ -88,13 +88,13 @@ private extension BeginCOWMutationInst {
8888
}
8989

9090
func optimizeEmptyEndBeginPair(_ context: SimplifyContext) -> Bool {
91-
if !uniquenessResult.nonDebugUses.isEmpty {
91+
if !uniquenessResult.uses.ignoreDebugUses.isEmpty {
9292
return false
9393
}
9494
guard let endCOW = instance as? EndCOWMutationInst else {
9595
return false
9696
}
97-
if endCOW.nonDebugUses.contains(where: { $0.instruction != self }) {
97+
if endCOW.uses.ignoreDebugUses.contains(where: { $0.instruction != self }) {
9898
return false
9999
}
100100

SwiftCompilerSources/Sources/Optimizer/InstructionSimplification/SimplifyDestructure.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ extension DestructureStructInst : OnoneSimplifyable {
8787
private func tryReplaceConstructDestructPair(construct: SingleValueInstruction,
8888
destruct: MultipleValueInstruction,
8989
_ context: SimplifyContext) {
90-
let singleUse = context.preserveDebugInfo ? construct.uses.singleUse : construct.uses.singleNonDebugUse
90+
let singleUse = context.preserveDebugInfo ? construct.uses.singleUse : construct.uses.ignoreDebugUses.singleUse
9191
let canEraseFirst = singleUse?.instruction == destruct
9292

9393
if !canEraseFirst && construct.parentFunction.hasOwnership && construct.ownership == .owned {

SwiftCompilerSources/Sources/Optimizer/InstructionSimplification/SimplifyLoad.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -197,7 +197,7 @@ extension LoadInst : OnoneSimplifyable, SILCombineSimplifyable {
197197
if context.preserveDebugInfo {
198198
return !uses.contains { !($0.instruction is DestroyValueInst) }
199199
} else {
200-
return !nonDebugUses.contains { !($0.instruction is DestroyValueInst) }
200+
return !uses.ignoreDebugUses.contains { !($0.instruction is DestroyValueInst) }
201201
}
202202
}
203203
}

SwiftCompilerSources/Sources/Optimizer/InstructionSimplification/SimplifyRefCasts.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ private extension UnaryInstruction {
6565
}
6666

6767
func tryReplaceSource(withOperandOf inst: SingleValueInstruction, _ context: SimplifyContext) -> Bool {
68-
let singleUse = context.preserveDebugInfo ? inst.uses.singleUse : inst.uses.singleNonDebugUse
68+
let singleUse = context.preserveDebugInfo ? inst.uses.singleUse : inst.uses.ignoreDebugUses.singleUse
6969
let canEraseInst = singleUse?.instruction == self
7070
let replacement = inst.operands[0].value
7171

SwiftCompilerSources/Sources/Optimizer/ModulePasses/MandatoryPerformanceOptimizations.swift

Lines changed: 8 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -265,7 +265,14 @@ private extension Value {
265265
var singleUseValue: any Value = self
266266
var path = SmallProjectionPath()
267267
while true {
268-
guard let use = singleUseValue.uses.singleRelevantUse else {
268+
// The initializer value of a global can contain access instructions if it references another
269+
// global variable by address, e.g.
270+
// var p = Point(x: 10, y: 20)
271+
// let o = UnsafePointer(&p)
272+
// Therefore ignore the `end_access` use of a `begin_access`.
273+
let relevantUses = singleUseValue.uses.ignoreDebugUses.ignoreUsers(ofType: EndAccessInst.self)
274+
275+
guard let use = relevantUses.singleUse else {
269276
return nil
270277
}
271278

@@ -383,27 +390,3 @@ fileprivate struct FunctionWorklist {
383390
}
384391
}
385392
}
386-
387-
private extension UseList {
388-
var singleRelevantUse: Operand? {
389-
var singleUse: Operand?
390-
for use in self {
391-
switch use.instruction {
392-
case is DebugValueInst,
393-
// The initializer value of a global can contain access instructions if it references another
394-
// global variable by address, e.g.
395-
// var p = Point(x: 10, y: 20)
396-
// let o = UnsafePointer(&p)
397-
// Therefore ignore the `end_access` use of a `begin_access`.
398-
is EndAccessInst:
399-
continue
400-
default:
401-
if singleUse != nil {
402-
return nil
403-
}
404-
singleUse = use
405-
}
406-
}
407-
return singleUse
408-
}
409-
}

SwiftCompilerSources/Sources/Optimizer/ModulePasses/StackProtection.swift

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -365,9 +365,7 @@ private struct StackProtectionOptimization {
365365
let builder = Builder(after: beginAccess, location: beginAccess.location.autoGenerated, context)
366366
let temporary = builder.createAllocStack(beginAccess.type)
367367

368-
for use in beginAccess.uses where !(use.instruction is EndAccessInst) {
369-
use.instruction.setOperand(at: use.index, to: temporary, context)
370-
}
368+
beginAccess.uses.ignoreUsers(ofType: EndAccessInst.self).replaceAll(with: temporary, context)
371369

372370
for endAccess in beginAccess.endInstructions {
373371
let endBuilder = Builder(before: endAccess, location: endAccess.location.autoGenerated, context)

0 commit comments

Comments
 (0)