Skip to content

Commit f09dfc9

Browse files
committed
Swift SIL: escape effects for function arguments.
Store a list of argument effects in a function, which specify if and how arguments escape. Such effects can be specified in the Swift source code (for details see docs/ReferenceGuides/UnderscoredAttributes.md) or derived in an optimization pass. For details see the documentation in SwiftCompilerSources/Sources/SIL/Effects.swift.
1 parent 72fc4e3 commit f09dfc9

File tree

21 files changed

+836
-35
lines changed

21 files changed

+836
-35
lines changed

SwiftCompilerSources/Sources/Optimizer/PassManager/PassUtils.swift

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,11 @@ struct PassContext {
123123
func getContextSubstitutionMap(for type: Type) -> SubstitutionMap {
124124
SubstitutionMap(PassContext_getContextSubstitutionMap(_bridged, type.bridged))
125125
}
126+
127+
func modifyEffects(in function: Function, _ body: (inout FunctionEffects) -> ()) {
128+
function._modifyEffects(body)
129+
// TODO: do we need to notify any changes?
130+
}
126131
}
127132

128133
struct FunctionPass {

SwiftCompilerSources/Sources/SIL/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ add_swift_compiler_module(SIL
1111
Argument.swift
1212
BasicBlock.swift
1313
Builder.swift
14+
Effects.swift
1415
Function.swift
1516
GlobalVariable.swift
1617
Instruction.swift
Lines changed: 326 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,326 @@
1+
//===--- Effects.swift - Defines function effects -------------------------===//
2+
//
3+
// This source file is part of the Swift.org open source project
4+
//
5+
// Copyright (c) 2014 - 2021 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+
/// An effect on a function argument.
14+
public struct ArgumentEffect : CustomStringConvertible, CustomReflectable {
15+
16+
public typealias Path = SmallProjectionPath
17+
18+
/// Selects which argument (or return value) and which projection of the argument
19+
/// or return value.
20+
///
21+
/// For example, the selection `%0.s1` would select the second struct field of `arg` in
22+
/// func foo(_ arg: Mystruct) { ... }
23+
public struct Selection : CustomStringConvertible {
24+
public enum ArgumentOrReturn : Equatable {
25+
case argument(Int)
26+
case returnValue
27+
}
28+
29+
// Which argument or return value.
30+
public let value: ArgumentOrReturn
31+
32+
/// Which projection(s) of the argument or return value.
33+
public let pathPattern: Path
34+
35+
public init(_ value: ArgumentOrReturn, pathPattern: Path) {
36+
self.value = value
37+
self.pathPattern = pathPattern
38+
}
39+
40+
public init(_ arg: Argument, pathPattern: Path) {
41+
self.init(.argument(arg.index), pathPattern: pathPattern)
42+
}
43+
44+
/// Copy the selection by applying a delta on the argument index.
45+
///
46+
/// This is used when copying argument effects for specialized functions where
47+
/// the indirect result is converted to a direct return value (in this case the
48+
/// `resultArgDelta` is -1).
49+
public init(copiedFrom src: Selection, resultArgDelta: Int) {
50+
switch (src.value, resultArgDelta) {
51+
case (let a, 0):
52+
self.value = a
53+
case (.returnValue, 1):
54+
self.value = .argument(0)
55+
case (.argument(0), -1):
56+
self.value = .returnValue
57+
case (.argument(let a), let d):
58+
self.value = .argument(a + d)
59+
default:
60+
fatalError()
61+
}
62+
self.pathPattern = src.pathPattern
63+
}
64+
65+
public var argumentIndex: Int {
66+
switch value {
67+
case .argument(let argIdx): return argIdx
68+
default: fatalError("expected argument")
69+
}
70+
}
71+
72+
public var description: String {
73+
let posStr: String
74+
switch value {
75+
case .argument(let argIdx):
76+
posStr = "%\(argIdx)"
77+
case .returnValue:
78+
posStr = "%r"
79+
}
80+
let pathStr = (pathPattern.isEmpty ? "" : ".\(pathPattern)")
81+
return "\(posStr)\(pathStr)"
82+
}
83+
84+
public func matches(_ rhsValue: ArgumentOrReturn, _ rhsPath: Path) -> Bool {
85+
return value == rhsValue && rhsPath.matches(pattern: pathPattern)
86+
}
87+
}
88+
89+
//----------------------------------------------------------------------//
90+
// Members of ArgumentEffect
91+
//----------------------------------------------------------------------//
92+
93+
public enum Kind {
94+
/// The selected argument value does not escape.
95+
///
96+
/// Synatx examples:
97+
/// !%0 // argument 0 does not escape
98+
/// !%0.** // argument 0 and all transitively contained values do not escape
99+
///
100+
case notEscaping
101+
102+
/// The selected argument value escapes to the specified selection (= first payload).
103+
///
104+
/// Synatx examples:
105+
/// %0.s1 => %r // field 2 of argument 0 exclusively escapes via return.
106+
/// %0.s1 -> %1 // field 2 of argument 0 - and other values - escape to argument 1.
107+
///
108+
/// The "exclusive" flag (= second payload) is true if only the selected argument escapes
109+
/// to the specified selection, but nothing else escapes to it.
110+
/// For example, "exclusive" is true for the following function:
111+
///
112+
/// @_effect(escaping c => return)
113+
/// func exclusiveEscape(_ c: Class) -> Class { return c }
114+
///
115+
/// but not in this case:
116+
///
117+
/// var global: Class
118+
///
119+
/// @_effect(escaping c -> return)
120+
/// func notExclusiveEscape(_ c: Class) -> Class { return cond ? c : global }
121+
///
122+
case escaping(Selection, Bool) // to, exclusive
123+
}
124+
125+
/// To which argument (and projection) does this effect apply to?
126+
public let selectedArg: Selection
127+
128+
/// The kind of effect.
129+
public let kind: Kind
130+
131+
/// True, if this effect is derived in an optimization pass.
132+
/// False, if this effect is defined in the Swift source code.
133+
public let isDerived: Bool
134+
135+
public init(_ kind: Kind, selectedArg: Selection, isDerived: Bool = true) {
136+
self.selectedArg = selectedArg
137+
self.kind = kind
138+
self.isDerived = isDerived
139+
}
140+
141+
/// Copy the ArgumentEffect by applying a delta on the argument index.
142+
///
143+
/// This is used when copying argument effects for specialized functions where
144+
/// the indirect result is converted to a direct return value (in this case the
145+
/// `resultArgDelta` is -1).
146+
init(copiedFrom srcEffect: ArgumentEffect, resultArgDelta: Int) {
147+
148+
func copy(_ srcSelection: Selection) -> Selection {
149+
return Selection(copiedFrom: srcSelection, resultArgDelta: resultArgDelta)
150+
}
151+
152+
self.selectedArg = copy(srcEffect.selectedArg)
153+
self.isDerived = srcEffect.isDerived
154+
switch srcEffect.kind {
155+
case .notEscaping:
156+
self.kind = .notEscaping
157+
case .escaping(let toSelectedArg, let exclusive):
158+
self.kind = .escaping(copy(toSelectedArg), exclusive)
159+
}
160+
}
161+
162+
public var description: String {
163+
switch kind {
164+
case .notEscaping:
165+
return "!\(selectedArg)"
166+
case .escaping(let toSelectedArg, let exclusive):
167+
return "\(selectedArg) \(exclusive ? "=>" : "->") \(toSelectedArg)"
168+
}
169+
}
170+
171+
public var customMirror: Mirror { Mirror(self, children: []) }
172+
}
173+
174+
/// All argument effects for a function.
175+
///
176+
/// In future we might add non-argument-specific effects, too, like `readnone`, `readonly`.
177+
public struct FunctionEffects : CustomStringConvertible, CustomReflectable {
178+
public var argumentEffects: [ArgumentEffect] = []
179+
180+
public init() {}
181+
182+
public init(copiedFrom src: FunctionEffects, resultArgDelta: Int) {
183+
self.argumentEffects = src.argumentEffects.map {
184+
ArgumentEffect(copiedFrom: $0, resultArgDelta: resultArgDelta)
185+
}
186+
}
187+
188+
public func canEscape(path: ArgumentEffect.Path) -> Bool {
189+
return !argumentEffects.contains(where: {
190+
if case .notEscaping = $0.kind,
191+
$0.selectedArg.matches(.argument(0), path) {
192+
return true
193+
}
194+
return false
195+
})
196+
}
197+
198+
199+
public mutating func removeDerivedEffects() {
200+
argumentEffects = argumentEffects.filter { !$0.isDerived }
201+
}
202+
203+
public var description: String {
204+
return "[" + argumentEffects.map { $0.description }.joined(separator: ", ") + "]"
205+
}
206+
207+
public var customMirror: Mirror { Mirror(self, children: []) }
208+
}
209+
210+
//===----------------------------------------------------------------------===//
211+
// Parsing
212+
//===----------------------------------------------------------------------===//
213+
214+
extension StringParser {
215+
216+
mutating func parseEffectFromSource(for function: Function,
217+
params: Dictionary<String, Int>) throws -> ArgumentEffect {
218+
if consume("notEscaping") {
219+
let selectedArg = try parseSelectionFromSource(for: function, params: params)
220+
return ArgumentEffect(.notEscaping, selectedArg: selectedArg, isDerived: false)
221+
}
222+
if consume("escaping") {
223+
let from = try parseSelectionFromSource(for: function, params: params)
224+
let exclusive = try parseEscapingArrow()
225+
let to = try parseSelectionFromSource(for: function, params: params, acceptReturn: true)
226+
return ArgumentEffect(.escaping(to, exclusive), selectedArg: from, isDerived: false)
227+
}
228+
try throwError("unknown effect")
229+
}
230+
231+
mutating func parseEffectFromSIL(for function: Function, isDerived: Bool) throws -> ArgumentEffect {
232+
if consume("!") {
233+
let selectedArg = try parseSelectionFromSIL(for: function)
234+
return ArgumentEffect(.notEscaping, selectedArg: selectedArg, isDerived: isDerived)
235+
}
236+
let from = try parseSelectionFromSIL(for: function)
237+
let exclusive = try parseEscapingArrow()
238+
let to = try parseSelectionFromSIL(for: function, acceptReturn: true)
239+
return ArgumentEffect(.escaping(to, exclusive), selectedArg: from, isDerived: isDerived)
240+
}
241+
242+
private mutating func parseEscapingArrow() throws -> Bool {
243+
if consume("=>") { return true }
244+
if consume("->") { return false }
245+
try throwError("expected '=>' or '->'")
246+
}
247+
248+
249+
mutating func parseSelectionFromSource(for function: Function,
250+
params: Dictionary<String, Int>,
251+
acceptReturn: Bool = false) throws -> ArgumentEffect.Selection {
252+
let value: ArgumentEffect.Selection.ArgumentOrReturn
253+
254+
if consume("self") {
255+
if !function.hasSelfArgument {
256+
try throwError("function does not have a self argument")
257+
}
258+
value = .argument(function.selfArgumentIndex)
259+
} else if consume("return") {
260+
if !acceptReturn {
261+
try throwError("return not allowed here")
262+
}
263+
if function.numIndirectResultArguments > 0 {
264+
if function.numIndirectResultArguments != 1 {
265+
try throwError("mutli-value returns not supported yet")
266+
}
267+
value = .argument(0)
268+
} else {
269+
value = .returnValue
270+
}
271+
} else if let name = consumeIdentifier() {
272+
guard let argIdx = params[name] else {
273+
try throwError("parameter not found")
274+
}
275+
value = .argument(argIdx + function.numIndirectResultArguments)
276+
} else {
277+
try throwError("paramter name or return expected")
278+
}
279+
280+
let valueType: Type
281+
switch value {
282+
case .argument(let argIdx):
283+
valueType = function.argumentTypes[argIdx]
284+
case .returnValue:
285+
valueType = function.resultType
286+
}
287+
288+
if consume(".") {
289+
let path = try parseProjectionPathFromSource(for: function, type: valueType)
290+
return ArgumentEffect.Selection(value, pathPattern: path)
291+
}
292+
if !valueType.isClass {
293+
switch value {
294+
case .argument:
295+
try throwError("the argument is not a class - add 'anyValueFields'")
296+
case .returnValue:
297+
try throwError("the return value is not a class - add 'anyValueFields'")
298+
}
299+
}
300+
return ArgumentEffect.Selection(value, pathPattern: ArgumentEffect.Path())
301+
}
302+
303+
mutating func parseSelectionFromSIL(for function: Function,
304+
acceptReturn: Bool = false) throws -> ArgumentEffect.Selection {
305+
let value: ArgumentEffect.Selection.ArgumentOrReturn
306+
307+
if consume("%r") {
308+
if !acceptReturn {
309+
try throwError("return not allowed here")
310+
}
311+
value = .returnValue
312+
} else if consume("%") {
313+
guard let argIdx = consumeInt() else {
314+
try throwError("expected argument index")
315+
}
316+
value = .argument(argIdx)
317+
} else {
318+
try throwError("expected parameter or return")
319+
}
320+
321+
if consume(".") {
322+
return try ArgumentEffect.Selection(value, pathPattern: parseProjectionPathFromSIL())
323+
}
324+
return ArgumentEffect.Selection(value, pathPattern: ArgumentEffect.Path())
325+
}
326+
}

0 commit comments

Comments
 (0)