Skip to content

Commit c05e064

Browse files
authored
Merge pull request #42242 from eeckstein/escapeinfo
Swift Optimizer: add `EscapeInfo`, a new utility for escape analysis
2 parents c1534d5 + 700412b commit c05e064

File tree

79 files changed

+3823
-181
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

79 files changed

+3823
-181
lines changed

SwiftCompilerSources/Sources/Optimizer/Analysis/AliasAnalysis.swift

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,4 +43,21 @@ struct AliasAnalysis {
4343
return false
4444
}
4545
}
46+
47+
/// Returns the correct path for address-alias functions.
48+
static func getPtrOrAddressPath(for value: Value) -> EscapeInfo.Path {
49+
let ty = value.type
50+
if ty.isAddress {
51+
// This is the regular case: the path selects any sub-fields of an address.
52+
return EscapeInfo.Path(.anyValueFields)
53+
}
54+
// Some optimizations use the address-alias APIs with non-address SIL values.
55+
// TODO: this is non-intuitive and we should eliminate those API uses.
56+
if ty.isClass {
57+
// If the value is a (non-address) reference it means: all addresses within the class instance.
58+
return EscapeInfo.Path(.anyValueFields).push(.anyClassField)
59+
}
60+
// Any other non-address value means: all addresses of any referenced class instances within the value.
61+
return EscapeInfo.Path(.anyValueFields).push(.anyClassField).push(.anyValueFields)
62+
}
4663
}

SwiftCompilerSources/Sources/Optimizer/FunctionPasses/CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88

99
swift_compiler_sources(Optimizer
1010
AssumeSingleThreaded.swift
11+
ComputeEffects.swift
12+
EscapeInfoDumper.swift
1113
SILPrinter.swift
1214
MergeCondFails.swift
1315
RangeDumper.swift
Lines changed: 235 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,235 @@
1+
//===--- ComputeEffects.swift - Compute function effects ------------------===//
2+
//
3+
// This source file is part of the Swift.org open source project
4+
//
5+
// Copyright (c) 2014 - 2022 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+
fileprivate typealias Selection = ArgumentEffect.Selection
16+
fileprivate typealias Path = ArgumentEffect.Path
17+
18+
/// Computes effects for function arguments.
19+
///
20+
/// For example, if an argument does not escape, adds a non-escaping effect,
21+
/// e.g. "[escapes !%0.**]":
22+
///
23+
/// sil [escapes !%0.**] @foo : $@convention(thin) (@guaranteed X) -> () {
24+
/// bb0(%0 : $X):
25+
/// %1 = tuple ()
26+
/// return %1 : $()
27+
/// }
28+
///
29+
/// The pass does not try to change or re-compute _defined_ effects.
30+
/// Currently, only escaping effects are handled.
31+
/// In future, this pass may also add other effects, like memory side effects.
32+
let computeEffects = FunctionPass(name: "compute-effects", {
33+
(function: Function, context: PassContext) in
34+
35+
var argsWithDefinedEffects = getArgIndicesWithDefinedEffects(of: function)
36+
37+
var escapeInfo = EscapeInfo(calleeAnalysis: context.calleeAnalysis)
38+
var newEffects = Stack<ArgumentEffect>(context)
39+
let returnInst = function.returnInstruction
40+
41+
for arg in function.arguments {
42+
// We are not interested in arguments with trivial types.
43+
if !arg.type.isNonTrivialOrContainsRawPointer(in: function) { continue }
44+
45+
// Also, we don't want to override defined effects.
46+
if argsWithDefinedEffects.contains(arg.index) { continue }
47+
48+
// First check: is the argument (or a projected value of it) escaping at all?
49+
if !escapeInfo.isEscapingWhenWalkingDown(object: arg, path: Path(.anything),
50+
visitUse: { op, _, _ in
51+
isOperandOfRecursiveCall(op) ? .ignore : .continueWalking
52+
}) {
53+
let selectedArg = Selection(arg, pathPattern: Path(.anything))
54+
newEffects.push(ArgumentEffect(.notEscaping, selectedArg: selectedArg))
55+
continue
56+
}
57+
58+
// Now compute effects for two important cases:
59+
// * the argument itself + any value projections, and...
60+
if addArgEffects(arg, argPath: Path(), to: &newEffects, returnInst, &escapeInfo) {
61+
// * single class indirections
62+
_ = addArgEffects(arg, argPath: Path(.anyValueFields).push(.anyClassField),
63+
to: &newEffects, returnInst, &escapeInfo)
64+
}
65+
}
66+
67+
context.modifyEffects(in: function) { (effects: inout FunctionEffects) in
68+
effects.removeDerivedEffects()
69+
effects.argumentEffects.append(contentsOf: newEffects)
70+
}
71+
newEffects.removeAll()
72+
})
73+
74+
/// Returns true if an argument effect was added.
75+
private
76+
func addArgEffects(_ arg: FunctionArgument, argPath ap: Path,
77+
to newEffects: inout Stack<ArgumentEffect>,
78+
_ returnInst: ReturnInst?,
79+
_ escapeInfo: inout EscapeInfo) -> Bool {
80+
81+
var toSelection: Selection?
82+
// Correct the path if the argument is not a class reference itself, but a value type
83+
// containing one or more references.
84+
let argPath = arg.type.isClass ? ap : ap.push(.anyValueFields)
85+
86+
if escapeInfo.isEscapingWhenWalkingDown(object: arg, path: argPath,
87+
visitUse: { op, path, followStores in
88+
if op.instruction == returnInst {
89+
// The argument escapes to the function return
90+
if followStores {
91+
// The escaping path must not introduce a followStores.
92+
return .markEscaping
93+
}
94+
if let ta = toSelection {
95+
if ta.value != .returnValue { return .markEscaping }
96+
toSelection = Selection(.returnValue, pathPattern: path.merge(with: ta.pathPattern))
97+
} else {
98+
toSelection = Selection(.returnValue, pathPattern: path)
99+
}
100+
return .ignore
101+
}
102+
if isOperandOfRecursiveCall(op) {
103+
return .ignore
104+
}
105+
return .continueWalking
106+
},
107+
visitDef: { def, path, followStores in
108+
guard let destArg = def as? FunctionArgument else {
109+
return .continueWalkingUp
110+
}
111+
// The argument escapes to another argument (e.g. an out or inout argument)
112+
if followStores {
113+
// The escaping path must not introduce a followStores.
114+
return .markEscaping
115+
}
116+
let argIdx = destArg.index
117+
if let ta = toSelection {
118+
if ta.value != .argument(argIdx) { return .markEscaping }
119+
toSelection = Selection(.argument(argIdx), pathPattern: path.merge(with: ta.pathPattern))
120+
} else {
121+
toSelection = Selection(.argument(argIdx), pathPattern: path)
122+
}
123+
return .continueWalkingDown
124+
}) {
125+
return false
126+
}
127+
128+
let fromSelection = Selection(arg, pathPattern: argPath)
129+
130+
guard let toSelection = toSelection else {
131+
newEffects.push(ArgumentEffect(.notEscaping, selectedArg: fromSelection))
132+
return true
133+
}
134+
135+
// If the function never returns, the argument can not escape to another arg/return.
136+
guard let returnInst = returnInst else {
137+
return false
138+
}
139+
140+
let exclusive = isExclusiveEscape(fromArgument: arg, fromPath: argPath, to: toSelection, returnInst, &escapeInfo)
141+
142+
newEffects.push(ArgumentEffect(.escaping(toSelection, exclusive), selectedArg: fromSelection))
143+
return true
144+
}
145+
146+
/// Returns a set of argument indices for which there are "defined" effects (as opposed to derived effects).
147+
private func getArgIndicesWithDefinedEffects(of function: Function) -> Set<Int> {
148+
var argsWithDefinedEffects = Set<Int>()
149+
150+
for effect in function.effects.argumentEffects {
151+
if effect.isDerived { continue }
152+
153+
if case .argument(let argIdx) = effect.selectedArg.value {
154+
argsWithDefinedEffects.insert(argIdx)
155+
}
156+
157+
switch effect.kind {
158+
case .notEscaping:
159+
break
160+
case .escaping(let to, _):
161+
if case .argument(let toArgIdx) = to.value {
162+
argsWithDefinedEffects.insert(toArgIdx)
163+
}
164+
}
165+
}
166+
return argsWithDefinedEffects
167+
}
168+
169+
/// Returns true if `op` is passed to a recursive call to the current function -
170+
/// at the same argument index.
171+
private func isOperandOfRecursiveCall(_ op: Operand) -> Bool {
172+
let inst = op.instruction
173+
if let applySite = inst as? FullApplySite,
174+
let callee = applySite.referencedFunction,
175+
callee == inst.function,
176+
let argIdx = applySite.argumentIndex(of: op),
177+
op.value == callee.arguments[argIdx] {
178+
return true
179+
}
180+
return false
181+
}
182+
183+
/// Returns true if when walking from the `toSelection` to the `fromArgument`,
184+
/// there are no other arguments or escape points than `fromArgument`. Also, the
185+
/// path at the `fromArgument` must match with `fromPath`.
186+
private
187+
func isExclusiveEscape(fromArgument: Argument, fromPath: Path, to toSelection: Selection,
188+
_ returnInst: ReturnInst, _ escapeInfo: inout EscapeInfo) -> Bool {
189+
switch toSelection.value {
190+
191+
// argument -> return
192+
case .returnValue:
193+
if escapeInfo.isEscaping(
194+
object: returnInst.operand, path: toSelection.pathPattern,
195+
visitUse: { op, path, followStores in
196+
if op.instruction == returnInst {
197+
if followStores { return .markEscaping }
198+
if path.matches(pattern: toSelection.pathPattern) {
199+
return .ignore
200+
}
201+
return .markEscaping
202+
}
203+
return .continueWalking
204+
},
205+
visitDef: { def, path, followStores in
206+
guard let arg = def as? FunctionArgument else {
207+
return .continueWalkingUp
208+
}
209+
if followStores { return .markEscaping }
210+
if arg == fromArgument && path.matches(pattern: fromPath) {
211+
return .continueWalkingDown
212+
}
213+
return .markEscaping
214+
}) {
215+
return false
216+
}
217+
218+
// argument -> argument
219+
case .argument(let toArgIdx):
220+
let toArg = returnInst.function.arguments[toArgIdx]
221+
if escapeInfo.isEscaping(object: toArg, path: toSelection.pathPattern,
222+
visitDef: { def, path, followStores in
223+
guard let arg = def as? FunctionArgument else {
224+
return .continueWalkingUp
225+
}
226+
if followStores { return .markEscaping }
227+
if arg == fromArgument && path.matches(pattern: fromPath) { return .continueWalkingDown }
228+
if arg == toArg && path.matches(pattern: toSelection.pathPattern) { return .continueWalkingDown }
229+
return .markEscaping
230+
}) {
231+
return false
232+
}
233+
}
234+
return true
235+
}

0 commit comments

Comments
 (0)