Skip to content

Commit d1220fd

Browse files
authored
Merge pull request #60072 from eeckstein/walkers-in-release-devirtualizer
Some improvements in WalkUtils and use the ValueUseDefWalker in the ReleaseDevirtualizer pass
2 parents 57d504c + 872ded9 commit d1220fd

File tree

9 files changed

+649
-567
lines changed

9 files changed

+649
-567
lines changed

SwiftCompilerSources/Sources/Optimizer/FunctionPasses/ComputeEffects.swift

Lines changed: 27 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@
1313
import SIL
1414

1515
fileprivate typealias Selection = ArgumentEffect.Selection
16-
fileprivate typealias Path = ArgumentEffect.Path
1716

1817
/// Computes effects for function arguments.
1918
///
@@ -34,7 +33,7 @@ let computeEffects = FunctionPass(name: "compute-effects", {
3433
var argsWithDefinedEffects = getArgIndicesWithDefinedEffects(of: function)
3534

3635
struct IgnoreRecursiveCallVisitor : EscapeInfoVisitor {
37-
func visitUse(operand: Operand, path: Path, state: State) -> UseResult {
36+
func visitUse(operand: Operand, path: EscapePath) -> UseResult {
3837
return isOperandOfRecursiveCall(operand) ? .ignore : .continueWalk
3938
}
4039
}
@@ -50,17 +49,17 @@ let computeEffects = FunctionPass(name: "compute-effects", {
5049
if argsWithDefinedEffects.contains(arg.index) { continue }
5150

5251
// First check: is the argument (or a projected value of it) escaping at all?
53-
if !escapeInfo.isEscapingWhenWalkingDown(object: arg, path: Path(.anything)) {
54-
let selectedArg = Selection(arg, pathPattern: Path(.anything))
52+
if !escapeInfo.isEscapingWhenWalkingDown(object: arg, path: SmallProjectionPath(.anything)) {
53+
let selectedArg = Selection(arg, pathPattern: SmallProjectionPath(.anything))
5554
newEffects.push(ArgumentEffect(.notEscaping, selectedArg: selectedArg))
5655
continue
5756
}
5857

5958
// Now compute effects for two important cases:
6059
// * the argument itself + any value projections, and...
61-
if addArgEffects(context: context, arg, argPath: Path(), to: &newEffects, returnInst) {
60+
if addArgEffects(context: context, arg, argPath: SmallProjectionPath(), to: &newEffects, returnInst) {
6261
// * single class indirections
63-
_ = addArgEffects(context: context, arg, argPath: Path(.anyValueFields).push(.anyClassField),
62+
_ = addArgEffects(context: context, arg, argPath: SmallProjectionPath(.anyValueFields).push(.anyClassField),
6463
to: &newEffects, returnInst)
6564
}
6665
}
@@ -75,7 +74,7 @@ let computeEffects = FunctionPass(name: "compute-effects", {
7574

7675
/// Returns true if an argument effect was added.
7776
private
78-
func addArgEffects(context: PassContext, _ arg: FunctionArgument, argPath ap: Path,
77+
func addArgEffects(context: PassContext, _ arg: FunctionArgument, argPath ap: SmallProjectionPath,
7978
to newEffects: inout Stack<ArgumentEffect>,
8079
_ returnInst: ReturnInst?) -> Bool {
8180
// Correct the path if the argument is not a class reference itself, but a value type
@@ -91,18 +90,18 @@ func addArgEffects(context: PassContext, _ arg: FunctionArgument, argPath ap: Pa
9190
var toSelection: Selection?
9291
var returnInst: ReturnInst?
9392

94-
mutating func visitUse(operand: Operand, path: Path, state: State) -> UseResult {
93+
mutating func visitUse(operand: Operand, path: EscapePath) -> UseResult {
9594
if operand.instruction == returnInst {
9695
// The argument escapes to the function return
97-
if state.followStores {
96+
if path.followStores {
9897
// The escaping path must not introduce a followStores.
9998
return .abort
10099
}
101100
if let ta = toSelection {
102101
if ta.value != .returnValue { return .abort }
103-
toSelection = Selection(.returnValue, pathPattern: path.merge(with: ta.pathPattern))
102+
toSelection = Selection(.returnValue, pathPattern: path.projectionPath.merge(with: ta.pathPattern))
104103
} else {
105-
toSelection = Selection(.returnValue, pathPattern: path)
104+
toSelection = Selection(.returnValue, pathPattern: path.projectionPath)
106105
}
107106
return .ignore
108107
}
@@ -112,21 +111,21 @@ func addArgEffects(context: PassContext, _ arg: FunctionArgument, argPath ap: Pa
112111
return .continueWalk
113112
}
114113

115-
mutating func visitDef(def: Value, path: Path, state: State) -> DefResult {
114+
mutating func visitDef(def: Value, path: EscapePath) -> DefResult {
116115
guard let destArg = def as? FunctionArgument else {
117116
return .continueWalkUp
118117
}
119118
// The argument escapes to another argument (e.g. an out or inout argument)
120-
if state.followStores {
119+
if path.followStores {
121120
// The escaping path must not introduce a followStores.
122121
return .abort
123122
}
124123
let argIdx = destArg.index
125124
if let ta = toSelection {
126125
if ta.value != .argument(argIdx) { return .abort }
127-
toSelection = Selection(.argument(argIdx), pathPattern: path.merge(with: ta.pathPattern))
126+
toSelection = Selection(.argument(argIdx), pathPattern: path.projectionPath.merge(with: ta.pathPattern))
128127
} else {
129-
toSelection = Selection(.argument(argIdx), pathPattern: path)
128+
toSelection = Selection(.argument(argIdx), pathPattern: path.projectionPath)
130129
}
131130
return .walkDown
132131
}
@@ -197,7 +196,7 @@ private func isOperandOfRecursiveCall(_ op: Operand) -> Bool {
197196
/// there are no other arguments or escape points than `fromArgument`. Also, the
198197
/// path at the `fromArgument` must match with `fromPath`.
199198
private
200-
func isExclusiveEscape(context: PassContext, fromArgument: Argument, fromPath: Path, to toSelection: Selection,
199+
func isExclusiveEscape(context: PassContext, fromArgument: Argument, fromPath: SmallProjectionPath, to toSelection: Selection,
201200
_ returnInst: ReturnInst) -> Bool {
202201
switch toSelection.value {
203202

@@ -207,25 +206,25 @@ func isExclusiveEscape(context: PassContext, fromArgument: Argument, fromPath: P
207206
let fromArgument: Argument
208207
let toSelection: Selection
209208
let returnInst: ReturnInst
210-
let fromPath: Path
209+
let fromPath: SmallProjectionPath
211210

212-
mutating func visitUse(operand: Operand, path: Path, state: State) -> UseResult {
211+
mutating func visitUse(operand: Operand, path: EscapePath) -> UseResult {
213212
if operand.instruction == returnInst {
214-
if state.followStores { return .abort }
215-
if path.matches(pattern: toSelection.pathPattern) {
213+
if path.followStores { return .abort }
214+
if path.projectionPath.matches(pattern: toSelection.pathPattern) {
216215
return .ignore
217216
}
218217
return .abort
219218
}
220219
return .continueWalk
221220
}
222221

223-
mutating func visitDef(def: Value, path: Path, state: State) -> DefResult {
222+
mutating func visitDef(def: Value, path: EscapePath) -> DefResult {
224223
guard let arg = def as? FunctionArgument else {
225224
return .continueWalkUp
226225
}
227-
if state.followStores { return .abort }
228-
if arg == fromArgument && path.matches(pattern: fromPath) {
226+
if path.followStores { return .abort }
227+
if arg == fromArgument && path.projectionPath.matches(pattern: fromPath) {
229228
return .walkDown
230229
}
231230
return .abort
@@ -240,17 +239,17 @@ func isExclusiveEscape(context: PassContext, fromArgument: Argument, fromPath: P
240239
case .argument(let toArgIdx):
241240
struct IsExclusiveArgumentEscapeVisitor : EscapeInfoVisitor {
242241
let fromArgument: Argument
243-
let fromPath: Path
242+
let fromPath: SmallProjectionPath
244243
let toSelection: Selection
245244
let toArg: FunctionArgument
246245

247-
mutating func visitDef(def: Value, path: Path, state: State) -> DefResult {
246+
mutating func visitDef(def: Value, path: EscapePath) -> DefResult {
248247
guard let arg = def as? FunctionArgument else {
249248
return .continueWalkUp
250249
}
251-
if state.followStores { return .abort }
252-
if arg == fromArgument && path.matches(pattern: fromPath) { return .walkDown }
253-
if arg == toArg && path.matches(pattern: toSelection.pathPattern) { return .walkDown }
250+
if path.followStores { return .abort }
251+
if arg == fromArgument && path.projectionPath.matches(pattern: fromPath) { return .walkDown }
252+
if arg == toArg && path.projectionPath.matches(pattern: toSelection.pathPattern) { return .walkDown }
254253
return .abort
255254
}
256255
}

SwiftCompilerSources/Sources/Optimizer/FunctionPasses/EscapeInfoDumper.swift

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -25,19 +25,19 @@ let escapeInfoDumper = FunctionPass(name: "dump-escape-info", {
2525
struct Visitor : EscapeInfoVisitor {
2626
var results: Set<String> = Set()
2727

28-
mutating func visitUse(operand: Operand, path: Path, state: State) -> UseResult {
28+
mutating func visitUse(operand: Operand, path: EscapePath) -> UseResult {
2929
if operand.instruction is ReturnInst {
30-
results.insert("return[\(path)]")
30+
results.insert("return[\(path.projectionPath)]")
3131
return .ignore
3232
}
3333
return .continueWalk
3434
}
3535

36-
mutating func visitDef(def: Value, path: Path, state: State) -> DefResult {
36+
mutating func visitDef(def: Value, path: EscapePath) -> DefResult {
3737
guard let arg = def as? FunctionArgument else {
3838
return .continueWalkUp
3939
}
40-
results.insert("arg\(arg.index)[\(path)]")
40+
results.insert("arg\(arg.index)[\(path.projectionPath)]")
4141
return .walkDown
4242
}
4343
}
@@ -96,7 +96,7 @@ let addressEscapeInfoDumper = FunctionPass(name: "dump-addr-escape-info", {
9696

9797
struct Visitor : EscapeInfoVisitor {
9898
let apply: Instruction
99-
mutating func visitUse(operand: Operand, path: Path, state: State) -> UseResult {
99+
mutating func visitUse(operand: Operand, path: EscapePath) -> UseResult {
100100
let user = operand.instruction
101101
if user == apply {
102102
return .abort

SwiftCompilerSources/Sources/Optimizer/FunctionPasses/ReleaseDevirtualizer.swift

Lines changed: 47 additions & 109 deletions
Original file line numberDiff line numberDiff line change
@@ -74,12 +74,11 @@ private func tryDevirtualizeReleaseOfObject(
7474
_ deallocStackRef: DeallocStackRefInst
7575
) {
7676
let allocRefInstruction = deallocStackRef.allocRef
77-
var root = release.operands[0].value
78-
while let newRoot = stripRCIdentityPreservingInsts(root) {
79-
root = newRoot
80-
}
8177

82-
if root != allocRefInstruction {
78+
// Check if the release instruction right before the `dealloc_stack_ref` really releases
79+
// the allocated object (and not something else).
80+
var finder = FindAllocationOfRelease(allocation: allocRefInstruction)
81+
if !finder.allocationIsRoot(of: release.operand) {
8382
return
8483
}
8584

@@ -109,119 +108,58 @@ private func tryDevirtualizeReleaseOfObject(
109108
context.erase(instruction: release)
110109
}
111110

112-
private func stripRCIdentityPreservingInsts(_ value: Value) -> Value? {
113-
guard let inst = value as? Instruction else { return nil }
114-
115-
switch inst {
116-
// First strip off RC identity preserving casts.
117-
case is UpcastInst,
118-
is UncheckedRefCastInst,
119-
is InitExistentialRefInst,
120-
is OpenExistentialRefInst,
121-
is RefToBridgeObjectInst,
122-
is BridgeObjectToRefInst,
123-
is ConvertFunctionInst,
124-
is UncheckedEnumDataInst:
125-
return inst.operands[0].value
126-
127-
// Then if we have a struct_extract that is extracting a non-trivial member
128-
// from a struct with no other non-trivial members, a ref count operation on
129-
// the struct is equivalent to a ref count operation on the extracted
130-
// member. Strip off the extract.
131-
case let sei as StructExtractInst where sei.isFieldOnlyNonTrivialField:
132-
return sei.operand
133-
134-
// If we have a struct or tuple instruction with only one non-trivial operand, the
135-
// only reference count that can be modified is the non-trivial operand. Return
136-
// the non-trivial operand.
137-
case is StructInst, is TupleInst:
138-
return inst.uniqueNonTrivialOperand
139-
140-
// If we have an enum instruction with a payload, strip off the enum to
141-
// expose the enum's payload.
142-
case let ei as EnumInst where !ei.operands.isEmpty:
143-
return ei.operand
144-
145-
// If we have a tuple_extract that is extracting the only non trivial member
146-
// of a tuple, a retain_value on the tuple is equivalent to a retain_value on
147-
// the extracted value.
148-
case let tei as TupleExtractInst where tei.isEltOnlyNonTrivialElt:
149-
return tei.operand
150-
151-
default:
152-
return nil
153-
}
154-
}
111+
// Up-walker to find the root of a release instruction.
112+
private struct FindAllocationOfRelease : ValueUseDefWalker {
113+
private let allocInst: AllocRefInstBase
114+
private var allocFound = false
155115

156-
private extension Instruction {
157-
/// Search the operands of this tuple for a unique non-trivial elt. If we find
158-
/// it, return it. Otherwise return `nil`.
159-
var uniqueNonTrivialOperand: Value? {
160-
var candidateElt: Value?
161-
let function = self.function
162-
163-
for op in operands {
164-
if !op.value.type.isTrivial(in: function) {
165-
if candidateElt == nil {
166-
candidateElt = op.value
167-
continue
168-
}
116+
var walkUpCache = WalkerCache<SmallProjectionPath>()
169117

170-
// Otherwise, we have two values that are non-trivial. Bail.
171-
return nil
172-
}
173-
}
118+
init(allocation: AllocRefInstBase) { allocInst = allocation }
174119

175-
return candidateElt
120+
/// The top-level entry point: returns true if the root of `value` is the `allocInst`.
121+
mutating func allocationIsRoot(of value: Value) -> Bool {
122+
let path = SmallProjectionPath().push(.anyValueFields)
123+
return walkUp(value: value, path: path) != .abortWalk &&
124+
allocFound
176125
}
177-
}
178126

179-
private extension TupleExtractInst {
180-
var isEltOnlyNonTrivialElt: Bool {
181-
let function = self.function
182-
183-
if type.isTrivial(in: function) {
184-
return false
127+
mutating func rootDef(value: Value, path: SmallProjectionPath) -> WalkResult {
128+
if value == allocInst {
129+
allocFound = true
130+
return .continueWalk
185131
}
186-
187-
let opType = operand.type
188-
189-
var nonTrivialEltsCount = 0
190-
for elt in opType.tupleElements {
191-
if elt.isTrivial(in: function) {
192-
nonTrivialEltsCount += 1
193-
}
194-
195-
if nonTrivialEltsCount > 1 {
196-
return false
197-
}
198-
}
199-
200-
return true
132+
return .abortWalk
201133
}
202-
}
203-
204-
private extension StructExtractInst {
205-
var isFieldOnlyNonTrivialField: Bool {
206-
let function = self.function
207-
208-
if type.isTrivial(in: function) {
209-
return false
210-
}
211134

212-
let structType = operand.type
213-
214-
var nonTrivialFieldsCount = 0
215-
for field in structType.getNominalFields(in: function) {
216-
if field.isTrivial(in: function) {
217-
nonTrivialFieldsCount += 1
218-
}
219-
220-
if nonTrivialFieldsCount > 1 {
221-
return false
135+
// This function is called for `struct` and `tuple` instructions in case the `path` doesn't select
136+
// a specific operand but all operands.
137+
mutating func walkUpAllOperands(of def: Instruction, path: SmallProjectionPath) -> WalkResult {
138+
139+
// Instead of walking up _all_ operands (which would be the default behavior), require that only a
140+
// _single_ operand is not trivial and walk up that operand.
141+
// We need to check this because the released value must not contain multiple copies of the
142+
// allocated object. We can only replace a _single_ release with the destructor call and not
143+
// multiple releases of the same object. E.g.
144+
//
145+
// %x = alloc_ref [stack] $X
146+
// strong_retain %x
147+
// %t = tuple (%x, %x)
148+
// release_value %t // -> releases %x two times!
149+
// dealloc_stack_ref %x
150+
//
151+
var nonTrivialOperandFound = false
152+
for operand in def.operands {
153+
if !operand.value.type.isTrivial(in: def.function) {
154+
if nonTrivialOperandFound {
155+
return .abortWalk
156+
}
157+
nonTrivialOperandFound = true
158+
if walkUp(value: operand.value, path: path) == .abortWalk {
159+
return .abortWalk
160+
}
222161
}
223162
}
224-
225-
return true
163+
return .continueWalk
226164
}
227165
}

SwiftCompilerSources/Sources/Optimizer/FunctionPasses/StackPromotion.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -201,7 +201,7 @@ private func getDominatingBlockOfAllUsePoints(context: PassContext,
201201
struct Visitor : EscapeInfoVisitor {
202202
var dominatingBlock: BasicBlock
203203
let domTree: DominatorTree
204-
mutating func visitUse(operand: Operand, path: Path, state: State) -> UseResult {
204+
mutating func visitUse(operand: Operand, path: EscapePath) -> UseResult {
205205
let defBlock = operand.value.definingBlock
206206
if defBlock.dominates(dominatingBlock, domTree) {
207207
dominatingBlock = defBlock
@@ -232,7 +232,7 @@ func computeInnerAndOuterLiferanges(instruction: SingleValueInstruction, in domB
232232
self.domTree = domTree
233233
}
234234

235-
mutating func visitUse(operand: Operand, path: Path, state: State) -> UseResult {
235+
mutating func visitUse(operand: Operand, path: EscapePath) -> UseResult {
236236
let user = operand.instruction
237237
if innerRange.blockRange.begin.dominates(user.block, domTree) {
238238
innerRange.insert(user)

0 commit comments

Comments
 (0)