Skip to content

Commit 04be498

Browse files
authored
Merge pull request swiftlang#63686 from eeckstein/effects-refactoring
some refactoring in Effects and ComputeEscapeEffects
2 parents 5f38f8e + 490c8fa commit 04be498

File tree

7 files changed

+160
-129
lines changed

7 files changed

+160
-129
lines changed

SwiftCompilerSources/Sources/Optimizer/FunctionPasses/ComputeEscapeEffects.swift

Lines changed: 103 additions & 103 deletions
Original file line numberDiff line numberDiff line change
@@ -85,61 +85,7 @@ func addArgEffects(_ arg: FunctionArgument, argPath ap: SmallProjectionPath,
8585
// containing one or more references.
8686
let argPath = arg.type.isClass ? ap : ap.push(.anyValueFields)
8787

88-
struct ArgEffectsVisitor : EscapeVisitorWithResult {
89-
enum EscapeDestination {
90-
case notSet
91-
case toReturn(SmallProjectionPath)
92-
case toArgument(Int, SmallProjectionPath) // argument index, path
93-
}
94-
var result = EscapeDestination.notSet
95-
96-
mutating func visitUse(operand: Operand, path: EscapePath) -> UseResult {
97-
if operand.instruction is ReturnInst {
98-
// The argument escapes to the function return
99-
if path.followStores {
100-
// The escaping path must not introduce a followStores.
101-
return .abort
102-
}
103-
switch result {
104-
case .notSet:
105-
result = .toReturn(path.projectionPath)
106-
case .toReturn(let oldPath):
107-
result = .toReturn(oldPath.merge(with: path.projectionPath))
108-
case .toArgument:
109-
return .abort
110-
}
111-
return .ignore
112-
}
113-
if isOperandOfRecursiveCall(operand) {
114-
return .ignore
115-
}
116-
return .continueWalk
117-
}
118-
119-
mutating func visitDef(def: Value, path: EscapePath) -> DefResult {
120-
guard let destArg = def as? FunctionArgument else {
121-
return .continueWalkUp
122-
}
123-
// The argument escapes to another argument (e.g. an out or inout argument)
124-
if path.followStores {
125-
// The escaping path must not introduce a followStores.
126-
return .abort
127-
}
128-
let argIdx = destArg.index
129-
switch result {
130-
case .notSet:
131-
result = .toArgument(argIdx, path.projectionPath)
132-
case .toArgument(let oldArgIdx, let oldPath) where oldArgIdx == argIdx:
133-
result = .toArgument(argIdx, oldPath.merge(with: path.projectionPath))
134-
default:
135-
return .abort
136-
}
137-
return .walkDown
138-
}
139-
}
140-
141-
guard let result = arg.at(argPath).visitByWalkingDown(using: ArgEffectsVisitor(),
142-
context) else {
88+
guard let result = arg.at(argPath).visitByWalkingDown(using: ArgEffectsVisitor(), context) else {
14389
return false
14490
}
14591

@@ -152,15 +98,17 @@ func addArgEffects(_ arg: FunctionArgument, argPath ap: SmallProjectionPath,
15298
switch result {
15399
case .notSet:
154100
effect = EscapeEffects.ArgumentEffect(.notEscaping, argumentIndex: arg.index, pathPattern: argPath)
101+
155102
case .toReturn(let toPath):
156-
let exclusive = isExclusiveEscapeToReturn(fromArgument: arg, fromPath: argPath,
157-
toPath: toPath, returnInst: returnInst, context)
158-
effect = EscapeEffects.ArgumentEffect(.escapingToReturn(toPath, exclusive),
103+
let visitor = IsExclusiveReturnEscapeVisitor(argument: arg, argumentPath: argPath, returnPath: toPath)
104+
let exclusive = visitor.isExclusiveEscape(returnInst: returnInst, context)
105+
effect = EscapeEffects.ArgumentEffect(.escapingToReturn(toPath: toPath, isExclusive: exclusive),
159106
argumentIndex: arg.index, pathPattern: argPath)
107+
160108
case .toArgument(let toArgIdx, let toPath):
161109
// Exclusive argument -> argument effects cannot appear because such an effect would
162110
// involve a store which is not permitted for exclusive escapes.
163-
effect = EscapeEffects.ArgumentEffect(.escapingToArgument(toArgIdx, toPath, /*exclusive*/ false),
111+
effect = EscapeEffects.ArgumentEffect(.escapingToArgument(toArgumentIndex: toArgIdx, toPath: toPath),
164112
argumentIndex: arg.index, pathPattern: argPath)
165113
}
166114
newEffects.append(effect)
@@ -176,8 +124,10 @@ private func getArgIndicesWithDefinedEscapingEffects(of function: Function) -> S
176124

177125
argsWithDefinedEffects.insert(effect.argumentIndex)
178126
switch effect.kind {
179-
case .notEscaping, .escapingToReturn: break
180-
case .escapingToArgument(let toArgIdx, _, _): argsWithDefinedEffects.insert(toArgIdx)
127+
case .notEscaping, .escapingToReturn:
128+
break
129+
case .escapingToArgument(let toArgIdx, _):
130+
argsWithDefinedEffects.insert(toArgIdx)
181131
}
182132
}
183133
return argsWithDefinedEffects
@@ -197,55 +147,105 @@ private func isOperandOfRecursiveCall(_ op: Operand) -> Bool {
197147
return false
198148
}
199149

200-
/// Returns true if when walking up from the `returnInst`, the `fromArgument` is the one
201-
/// and only argument which is reached - with a matching `fromPath`.
202-
private
203-
func isExclusiveEscapeToReturn(fromArgument: Argument, fromPath: SmallProjectionPath,
204-
toPath: SmallProjectionPath,
205-
returnInst: ReturnInst, _ context: FunctionPassContext) -> Bool {
206-
struct IsExclusiveReturnEscapeVisitor : EscapeVisitorWithResult {
207-
let fromArgument: Argument
208-
let fromPath: SmallProjectionPath
209-
let toPath: SmallProjectionPath
210-
var result = false
211-
212-
mutating func visitUse(operand: Operand, path: EscapePath) -> UseResult {
213-
switch operand.instruction {
214-
case is ReturnInst:
215-
if path.followStores { return .abort }
216-
if path.projectionPath.matches(pattern: toPath) {
217-
return .ignore
218-
}
150+
private struct ArgEffectsVisitor : EscapeVisitorWithResult {
151+
enum EscapeDestination {
152+
case notSet
153+
case toReturn(SmallProjectionPath)
154+
case toArgument(Int, SmallProjectionPath) // argument index, path
155+
}
156+
var result = EscapeDestination.notSet
157+
158+
mutating func visitUse(operand: Operand, path: EscapePath) -> UseResult {
159+
if operand.instruction is ReturnInst {
160+
// The argument escapes to the function return
161+
if path.followStores {
162+
// The escaping path must not introduce a followStores.
219163
return .abort
220-
case let si as StoringInstruction:
221-
// Don't allow store instructions because this allows the EscapeUtils to walk up
222-
// an apply result with `followStores`.
223-
if operand == si.destinationOperand {
224-
return .abort
225-
}
226-
case let ca as CopyAddrInst:
227-
// `copy_addr` is like a store.
228-
if operand == ca.destinationOperand {
164+
}
165+
switch result {
166+
case .notSet:
167+
result = .toReturn(path.projectionPath)
168+
case .toReturn(let oldPath):
169+
result = .toReturn(oldPath.merge(with: path.projectionPath))
170+
case .toArgument:
229171
return .abort
230-
}
231-
default:
232-
break
233172
}
234-
return .continueWalk
173+
return .ignore
235174
}
236-
237-
mutating func visitDef(def: Value, path: EscapePath) -> DefResult {
238-
guard let arg = def as? FunctionArgument else {
239-
return .continueWalkUp
240-
}
175+
if isOperandOfRecursiveCall(operand) {
176+
return .ignore
177+
}
178+
return .continueWalk
179+
}
180+
181+
mutating func visitDef(def: Value, path: EscapePath) -> DefResult {
182+
guard let destArg = def as? FunctionArgument else {
183+
return .continueWalkUp
184+
}
185+
// The argument escapes to another argument (e.g. an out or inout argument)
186+
if path.followStores {
187+
// The escaping path must not introduce a followStores.
188+
return .abort
189+
}
190+
let argIdx = destArg.index
191+
switch result {
192+
case .notSet:
193+
result = .toArgument(argIdx, path.projectionPath)
194+
case .toArgument(let oldArgIdx, let oldPath) where oldArgIdx == argIdx:
195+
result = .toArgument(argIdx, oldPath.merge(with: path.projectionPath))
196+
default:
197+
return .abort
198+
}
199+
return .walkDown
200+
}
201+
}
202+
203+
/// Returns true if when walking up from the return instruction, the `fromArgument`
204+
/// is the one and only argument which is reached - with a matching `fromPath`.
205+
private struct IsExclusiveReturnEscapeVisitor : EscapeVisitorWithResult {
206+
let argument: Argument
207+
let argumentPath: SmallProjectionPath
208+
let returnPath: SmallProjectionPath
209+
var result = false
210+
211+
func isExclusiveEscape(returnInst: ReturnInst, _ context: FunctionPassContext) -> Bool {
212+
return returnInst.operand.at(returnPath).visit(using: self, context) ?? false
213+
}
214+
215+
mutating func visitUse(operand: Operand, path: EscapePath) -> UseResult {
216+
switch operand.instruction {
217+
case is ReturnInst:
241218
if path.followStores { return .abort }
242-
if arg == fromArgument && path.projectionPath.matches(pattern: fromPath) {
243-
result = true
244-
return .walkDown
219+
if path.projectionPath.matches(pattern: returnPath) {
220+
return .ignore
245221
}
246222
return .abort
223+
case let si as StoringInstruction:
224+
// Don't allow store instructions because this allows the EscapeUtils to walk up
225+
// an apply result with `followStores`.
226+
if operand == si.destinationOperand {
227+
return .abort
228+
}
229+
case let ca as CopyAddrInst:
230+
// `copy_addr` is like a store.
231+
if operand == ca.destinationOperand {
232+
return .abort
233+
}
234+
default:
235+
break
236+
}
237+
return .continueWalk
238+
}
239+
240+
mutating func visitDef(def: Value, path: EscapePath) -> DefResult {
241+
guard let arg = def as? FunctionArgument else {
242+
return .continueWalkUp
243+
}
244+
if path.followStores { return .abort }
245+
if arg == argument && path.projectionPath.matches(pattern: argumentPath) {
246+
result = true
247+
return .walkDown
247248
}
249+
return .abort
248250
}
249-
let visitor = IsExclusiveReturnEscapeVisitor(fromArgument: fromArgument, fromPath: fromPath, toPath: toPath)
250-
return returnInst.operand.at(toPath).visit(using: visitor, context) ?? false
251251
}

SwiftCompilerSources/Sources/Optimizer/Utilities/EscapeUtils.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -594,7 +594,7 @@ fileprivate struct EscapeWalker<V: EscapeVisitor> : ValueDefUseWalker,
594594
var matched = false
595595
for effect in effects.escapeEffects.arguments {
596596
switch effect.kind {
597-
case .escapingToArgument(let toArgIdx, let toPath, _):
597+
case .escapingToArgument(let toArgIdx, let toPath):
598598
// Note: exclusive argument -> argument effects cannot appear, so we don't need to handle them here.
599599
if effect.matches(calleeArgIdx, argPath.projectionPath) {
600600
guard let callerToIdx = apply.callerArgIndex(calleeArgIndex: toArgIdx) else {

SwiftCompilerSources/Sources/SIL/Effects.swift

Lines changed: 48 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -240,9 +240,9 @@ public struct EscapeEffects : CustomStringConvertible, NoReflectionChildren {
240240
/// [%0: escape s1 => %r] // field 2 of argument 0 exclusively escapes via return.
241241
/// [%0: escape s1 -> %r] // field 2 of argument 0 - and other values - escape via return
242242
///
243-
/// The "exclusive" flag (= second payload) is true if only the argument escapes,
244-
/// but nothing else escapes to the return value.
245-
/// For example, "exclusive" is true for the following function:
243+
/// The `isExclusive` flag is true if only the argument escapes, but nothing else escapes to
244+
/// the return value.
245+
/// For example, `isExclusive` is true for the following function:
246246
///
247247
/// @_effect(escaping c => return)
248248
/// func exclusiveEscape(_ c: Class) -> Class { return c }
@@ -254,17 +254,40 @@ public struct EscapeEffects : CustomStringConvertible, NoReflectionChildren {
254254
/// @_effect(escaping c -> return)
255255
/// func notExclusiveEscape(_ c: Class) -> Class { return cond ? c : global }
256256
///
257-
case escapingToReturn(SmallProjectionPath, Bool) // toPath, exclusive
257+
/// Also, if `isExclusive` is true, there must not be a store in the chain from the argument to
258+
/// the return, e.g.
259+
///
260+
/// @_effect(escaping x -> return)
261+
/// func notExclusiveEscape(_ s: String) -> Class {
262+
/// c = new Class()
263+
/// c.s = s // Store, which prevents the effect to be `isExclusive`
264+
/// return s
265+
/// }
266+
case escapingToReturn(toPath: SmallProjectionPath, isExclusive: Bool)
258267

259268
/// Like `escapingToReturn`, but the argument escapes to another argument.
260269
///
261270
/// Example: The argument effects of
271+
///
262272
/// func argToArgEscape(_ r: inout Class, _ c: Class) { r = c }
263273
///
264274
/// would be
265275
/// [%1: escape => %0] // Argument 1 escapes to argument 0
266276
///
267-
case escapingToArgument(Int, SmallProjectionPath, Bool) // toArgumentIndex, toPath, exclusive
277+
/// It's not allowed that the argument (or a derived value) is _stored_ to an object which is loaded from somewhere.
278+
/// In the following example `c` is loaded from the indirect inout argument and `s` is stored to a field of `c`:
279+
///
280+
/// func noEscapeEffect(_ c: inout Class, s: String) {
281+
/// c.s = s
282+
/// }
283+
///
284+
/// In this case there is no escaping effect from `s` to `c`.
285+
/// Note that theoretically this rule also applies to the `escapingToReturn` effect, but it's impossible
286+
/// to construct such a situation for an argument which is only escaping to the function return.
287+
///
288+
/// The `escapingToArgument` doesn't have an `isExclusive` flag, because an argument-to-argument escape
289+
/// always involves a store, which makes an exclusive escape impossible.
290+
case escapingToArgument(toArgumentIndex: Int, toPath: SmallProjectionPath)
268291
}
269292

270293
/// To which argument does this effect apply to?
@@ -309,19 +332,19 @@ public struct EscapeEffects : CustomStringConvertible, NoReflectionChildren {
309332
if resultArgDelta != 1 {
310333
return nil
311334
}
312-
self.kind = .escapingToArgument(0, toPath, exclusive)
335+
self.kind = .escapingToArgument(toArgumentIndex: 0, toPath: toPath)
313336
} else {
314-
self.kind = .escapingToReturn(toPath, exclusive)
337+
self.kind = .escapingToReturn(toPath: toPath, isExclusive: exclusive)
315338
}
316-
case .escapingToArgument(let toArgIdx, let toPath, let exclusive):
339+
case .escapingToArgument(let toArgIdx, let toPath):
317340
let resultingToArgIdx = toArgIdx + resultArgDelta
318341
if resultingToArgIdx < 0 {
319342
if resultingToArgIdx != -1 {
320343
return nil
321344
}
322-
self.kind = .escapingToReturn(toPath, exclusive)
345+
self.kind = .escapingToReturn(toPath: toPath, isExclusive: false)
323346
} else {
324-
self.kind = .escapingToArgument(resultingToArgIdx, toPath, exclusive)
347+
self.kind = .escapingToArgument(toArgumentIndex: resultingToArgIdx, toPath: toPath)
325348
}
326349
}
327350
}
@@ -341,9 +364,9 @@ public struct EscapeEffects : CustomStringConvertible, NoReflectionChildren {
341364
case .escapingToReturn(let toPath, let exclusive):
342365
let toPathStr = (toPath.isEmpty ? "" : ".\(toPath)")
343366
return "escape\(patternStr) \(exclusive ? "=>" : "->") %r\(toPathStr)"
344-
case .escapingToArgument(let toArgIdx, let toPath, let exclusive):
367+
case .escapingToArgument(let toArgIdx, let toPath):
345368
let toPathStr = (toPath.isEmpty ? "" : ".\(toPath)")
346-
return "escape\(patternStr) \(exclusive ? "=>" : "->") %\(toArgIdx)\(toPathStr)"
369+
return "escape\(patternStr) -> %\(toArgIdx)\(toPathStr)"
347370
}
348371
}
349372

@@ -642,16 +665,21 @@ extension StringParser {
642665
try throwError("multi-value returns not supported yet")
643666
}
644667
let toPath = try parsePathPatternFromSource(for: function, type: function.argumentTypes[0])
645-
return EscapeEffects.ArgumentEffect(.escapingToArgument(0, toPath, exclusive),
668+
669+
// Exclusive escapes are ignored for indirect return values.
670+
return EscapeEffects.ArgumentEffect(.escapingToArgument(toArgumentIndex: 0, toPath: toPath),
646671
argumentIndex: fromArgIdx, pathPattern: fromPath, isDerived: false)
647672
}
648673
let toPath = try parsePathPatternFromSource(for: function, type: function.resultType)
649-
return EscapeEffects.ArgumentEffect(.escapingToReturn(toPath, exclusive),
674+
return EscapeEffects.ArgumentEffect(.escapingToReturn(toPath: toPath, isExclusive: exclusive),
650675
argumentIndex: fromArgIdx, pathPattern: fromPath, isDerived: false)
651676
}
677+
if exclusive {
678+
try throwError("exclusive escapes to arguments are not supported")
679+
}
652680
let toArgIdx = try parseArgumentIndexFromSource(for: function, params: params)
653681
let toPath = try parsePathPatternFromSource(for: function, type: function.argumentTypes[toArgIdx])
654-
return EscapeEffects.ArgumentEffect(.escapingToArgument(toArgIdx, toPath, exclusive),
682+
return EscapeEffects.ArgumentEffect(.escapingToArgument(toArgumentIndex: toArgIdx, toPath: toPath),
655683
argumentIndex: fromArgIdx, pathPattern: fromPath, isDerived: false)
656684
}
657685
try throwError("unknown effect")
@@ -715,12 +743,15 @@ extension StringParser {
715743
let effect: EscapeEffects.ArgumentEffect
716744
if consume("%r") {
717745
let toPath = consume(".") ? try parseProjectionPathFromSIL() : SmallProjectionPath()
718-
effect = EscapeEffects.ArgumentEffect(.escapingToReturn(toPath, exclusive),
746+
effect = EscapeEffects.ArgumentEffect(.escapingToReturn(toPath: toPath, isExclusive: exclusive),
719747
argumentIndex: argumentIndex, pathPattern: fromPath, isDerived: isDerived)
720748
} else {
749+
if exclusive {
750+
try throwError("exclusive escapes to arguments are not supported")
751+
}
721752
let toArgIdx = try parseArgumentIndexFromSIL()
722753
let toPath = consume(".") ? try parseProjectionPathFromSIL() : SmallProjectionPath()
723-
effect = EscapeEffects.ArgumentEffect(.escapingToArgument(toArgIdx, toPath, exclusive),
754+
effect = EscapeEffects.ArgumentEffect(.escapingToArgument(toArgumentIndex: toArgIdx, toPath: toPath),
724755
argumentIndex: argumentIndex, pathPattern: fromPath, isDerived: isDerived)
725756
}
726757
effects.escapeEffects.arguments.append(effect)

test/SILGen/effectsattr.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
@_effects(notEscaping t.**) @_silgen_name("func5") func func5<T>(_ t: T) { }
2121

2222
//CHECK-LABEL: sil hidden [ossa] @func6
23-
//CHECK-NEXT: [%1: escape! v**.c* => %0.v**]
23+
//CHECK-NEXT: [%1: escape! v**.c* -> %0.v**]
2424
//CHECK-NEXT: {{^[^[]}}
2525
@_effects(escaping t.value**.class* => return.value**) @_silgen_name("func6") func func6<T>(_ t: T) -> T { }
2626

0 commit comments

Comments
 (0)