Skip to content

SIL: make argument effects more readable in textual SIL #61044

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Sep 12, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
118 changes: 79 additions & 39 deletions SwiftCompilerSources/Sources/SIL/Effects.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,22 +16,22 @@ public struct ArgumentEffect : CustomStringConvertible, CustomReflectable {
public typealias Path = SmallProjectionPath

public enum Kind {
/// The selected argument value does not escape.
/// The argument value does not escape.
///
/// Syntax examples:
/// !%0 // argument 0 does not escape
/// !%0.** // argument 0 and all transitively contained values do not escape
/// [%0: noescape] // argument 0 does not escape
/// [%0: noescape **] // argument 0 and all transitively contained values do not escape
///
case notEscaping

/// The selected argument value escapes to the specified selection (= first payload).
/// The argument value escapes to the function return value.
///
/// Syntax examples:
/// %0.s1 => %r // field 2 of argument 0 exclusively escapes via return.
/// %0.s1 -> %1 // field 2 of argument 0 - and other values - escape to argument 1.
/// [%0: escape s1 => %r] // field 2 of argument 0 exclusively escapes via return.
/// [%0: escape s1 -> %r] // field 2 of argument 0 - and other values - escape via return
///
/// The "exclusive" flag (= second payload) is true if only the selected argument escapes
/// to the specified selection, but nothing else escapes to it.
/// The "exclusive" flag (= second payload) is true if only the argument escapes,
/// but nothing else escapes to the return value.
/// For example, "exclusive" is true for the following function:
///
/// @_effect(escaping c => return)
Expand All @@ -46,6 +46,14 @@ public struct ArgumentEffect : CustomStringConvertible, CustomReflectable {
///
case escapingToReturn(Path, Bool) // toPath, exclusive

/// Like `escapingToReturn`, but the argument escapes to another argument.
///
/// Example: The argument effects of
/// func argToArgEscape(_ r: inout Class, _ c: Class) { r = c }
///
/// would be
/// [%1: escape => %0] // Argument 1 escapes to argument 0
///
case escapingToArgument(Int, Path, Bool) // toArgumentIndex, toPath, exclusive
}

Expand Down Expand Up @@ -111,21 +119,28 @@ public struct ArgumentEffect : CustomStringConvertible, CustomReflectable {
return argumentIndex == rhsArgIdx && rhsPath.matches(pattern: pathPattern)
}

public var description: String {
let selectedArg = "%\(argumentIndex)" + (pathPattern.isEmpty ? "" : ".\(pathPattern)")
public var headerDescription: String {
"%\(argumentIndex)\(isDerived ? "" : "!"): "
}

public var bodyDescription: String {
let patternStr = pathPattern.isEmpty ? "" : " \(pathPattern)"
switch kind {
case .notEscaping:
return "!\(selectedArg)"
return "noescape\(patternStr)"
case .escapingToReturn(let toPath, let exclusive):
let pathStr = (toPath.isEmpty ? "" : ".\(toPath)")
return "\(selectedArg) \(exclusive ? "=>" : "->") %r\(pathStr)"
let toPathStr = (toPath.isEmpty ? "" : ".\(toPath)")
return "escape\(patternStr) \(exclusive ? "=>" : "->") %r\(toPathStr)"
case .escapingToArgument(let toArgIdx, let toPath, let exclusive):
let pathStr = (toPath.isEmpty ? "" : ".\(toPath)")
return "\(selectedArg) \(exclusive ? "=>" : "->") %\(toArgIdx)\(pathStr)"
let toPathStr = (toPath.isEmpty ? "" : ".\(toPath)")
return "escape\(patternStr) \(exclusive ? "=>" : "->") %\(toArgIdx)\(toPathStr)"
}
}

public var description: String {
headerDescription + bodyDescription
}

public var customMirror: Mirror { Mirror(self, children: []) }
}

Expand Down Expand Up @@ -164,8 +179,27 @@ public struct FunctionEffects : CustomStringConvertible, CustomReflectable {
argumentEffects = argumentEffects.filter { !$0.isDerived }
}

public var argumentEffectsDescription: String {
var currentArgIdx = -1
var currentIsDerived = false
var result = ""
for effect in argumentEffects {
if effect.argumentIndex != currentArgIdx || effect.isDerived != currentIsDerived {
if currentArgIdx >= 0 { result += "]\n" }
result += "[\(effect.headerDescription)"
currentArgIdx = effect.argumentIndex
currentIsDerived = effect.isDerived
} else {
result += ", "
}
result += effect.bodyDescription
}
if currentArgIdx >= 0 { result += "]\n" }
return result
}

public var description: String {
return "[" + argumentEffects.map { $0.description }.joined(separator: ", ") + "]"
return argumentEffectsDescription
}

public var customMirror: Mirror { Mirror(self, children: []) }
Expand Down Expand Up @@ -237,24 +271,37 @@ extension StringParser {
return ArgumentEffect.Path()
}

mutating func parseEffectFromSIL(for function: Function, isDerived: Bool) throws -> ArgumentEffect {
if consume("!") {
let argIdx = try parseArgumentIndexFromSIL()
let path = try parsePathPatternFromSIL()
return ArgumentEffect(.notEscaping, argumentIndex: argIdx, pathPattern: path, isDerived: isDerived)
mutating func parseEffectsFromSIL(to effects: inout FunctionEffects) throws {
let argumentIndex = try parseArgumentIndexFromSIL()
let isDerived = !consume("!")
if !consume(":") {
try throwError("expected ':'")
}
let fromArgIdx = try parseArgumentIndexFromSIL()
let fromPath = try parsePathPatternFromSIL()
let exclusive = try parseEscapingArrow()
if consume("%r") {
let toPath = try parsePathPatternFromSIL()
return ArgumentEffect(.escapingToReturn(toPath, exclusive),
argumentIndex: fromArgIdx, pathPattern: fromPath, isDerived: isDerived)
repeat {
let effect = try parseEffectFromSIL(argumentIndex: argumentIndex, isDerived: isDerived)
effects.argumentEffects.append(effect)
} while consume(",")
}

mutating func parseEffectFromSIL(argumentIndex: Int, isDerived: Bool) throws -> ArgumentEffect {
if consume("noescape") {
let path = try parseProjectionPathFromSIL()
return ArgumentEffect(.notEscaping, argumentIndex: argumentIndex, pathPattern: path, isDerived: isDerived)
}
if consume("escape") {
let fromPath = try parseProjectionPathFromSIL()
let exclusive = try parseEscapingArrow()
if consume("%r") {
let toPath = consume(".") ? try parseProjectionPathFromSIL() : ArgumentEffect.Path()
return ArgumentEffect(.escapingToReturn(toPath, exclusive),
argumentIndex: argumentIndex, pathPattern: fromPath, isDerived: isDerived)
}
let toArgIdx = try parseArgumentIndexFromSIL()
let toPath = consume(".") ? try parseProjectionPathFromSIL() : ArgumentEffect.Path()
return ArgumentEffect(.escapingToArgument(toArgIdx, toPath, exclusive),
argumentIndex: argumentIndex, pathPattern: fromPath, isDerived: isDerived)
}
let toArgIdx = try parseArgumentIndexFromSIL()
let toPath = try parsePathPatternFromSIL()
return ArgumentEffect(.escapingToArgument(toArgIdx, toPath, exclusive),
argumentIndex: fromArgIdx, pathPattern: fromPath, isDerived: isDerived)
try throwError("unknown effect")
}

mutating func parseArgumentIndexFromSIL() throws -> Int {
Expand All @@ -267,13 +314,6 @@ extension StringParser {
try throwError("expected parameter")
}

mutating func parsePathPatternFromSIL() throws -> ArgumentEffect.Path {
if consume(".") {
return try parseProjectionPathFromSIL()
}
return ArgumentEffect.Path()
}

private mutating func parseEscapingArrow() throws -> Bool {
if consume("=>") { return true }
if consume("->") { return false }
Expand Down
51 changes: 27 additions & 24 deletions SwiftCompilerSources/Sources/SIL/Function.swift
Original file line number Diff line number Diff line change
Expand Up @@ -133,27 +133,36 @@ final public class Function : CustomStringConvertible, HasShortDescription, Hash
},
// writeFn
{ (f: BridgedFunction, os: BridgedOStream, idx: Int) in
let s = f.function.effects.argumentEffects[idx].description
let s: String
if idx >= 0 {
s = f.function.effects.argumentEffects[idx].bodyDescription
} else {
s = f.function.effects.argumentEffectsDescription
}
s._withStringRef { OStream_write(os, $0) }
},
// parseFn:
{ (f: BridgedFunction, str: llvm.StringRef, fromSIL: Int, isDerived: Int, paramNames: BridgedArrayRef) -> BridgedParsingError in
{ (f: BridgedFunction, str: llvm.StringRef, fromSIL: Int, argumentIndex: Int, isDerived: Int, paramNames: BridgedArrayRef) -> BridgedParsingError in
do {
var parser = StringParser(str.string)
let effect: ArgumentEffect
if fromSIL != 0 {
effect = try parser.parseEffectFromSIL(for: f.function, isDerived: isDerived != 0)

if fromSIL != 0 && argumentIndex < 0 {
try parser.parseEffectsFromSIL(to: &f.function.effects)
} else {
let paramToIdx = paramNames.withElements(ofType: llvm.StringRef.self) {
(buffer: UnsafeBufferPointer<llvm.StringRef>) -> Dictionary<String, Int> in
let keyValPairs = buffer.enumerated().lazy.map { ($0.1.string, $0.0) }
return Dictionary(uniqueKeysWithValues: keyValPairs)
let effect: ArgumentEffect
if fromSIL != 0 {
effect = try parser.parseEffectFromSIL(argumentIndex: argumentIndex, isDerived: isDerived != 0)
} else {
let paramToIdx = paramNames.withElements(ofType: llvm.StringRef.self) {
(buffer: UnsafeBufferPointer<llvm.StringRef>) -> Dictionary<String, Int> in
let keyValPairs = buffer.enumerated().lazy.map { ($0.1.string, $0.0) }
return Dictionary(uniqueKeysWithValues: keyValPairs)
}
effect = try parser.parseEffectFromSource(for: f.function, params: paramToIdx)
}
effect = try parser.parseEffectFromSource(for: f.function, params: paramToIdx)
f.function.effects.argumentEffects.append(effect)
}
if !parser.isEmpty() { try parser.throwError("syntax error") }

f.function.effects.argumentEffects.append(effect)
} catch let error as ParsingError {
return BridgedParsingError(message: error.message.utf8Start, position: error.position)
} catch {
Expand All @@ -179,20 +188,14 @@ final public class Function : CustomStringConvertible, HasShortDescription, Hash
resultArgDelta: destResultArgs - srcResultArgs)
return 1
},
// getEffectFlags
{ (f: BridgedFunction, idx: Int) -> Int in
// getEffectInfo
{ (f: BridgedFunction, idx: Int) -> BridgedEffectInfo in
let argEffects = f.function.effects.argumentEffects
if idx >= argEffects.count { return 0 }
let effect = argEffects[idx]
var flags = 0
switch effect.kind {
case .notEscaping, .escapingToArgument, .escapingToReturn:
flags |= Int(EffectsFlagEscape)
}
if effect.isDerived {
flags |= Int(EffectsFlagDerived)
if idx >= argEffects.count {
return BridgedEffectInfo(argumentIndex: -1, isDerived: false)
}
return flags
let effect = argEffects[idx]
return BridgedEffectInfo(argumentIndex: effect.argumentIndex, isDerived: effect.isDerived)
}
)
}
Expand Down
10 changes: 4 additions & 6 deletions SwiftCompilerSources/Sources/SIL/SmallProjectionPath.swift
Original file line number Diff line number Diff line change
Expand Up @@ -471,7 +471,7 @@ extension StringParser {

mutating func parseProjectionPathFromSIL() throws -> SmallProjectionPath {
var entries: [(SmallProjectionPath.FieldKind, Int)] = []
repeat {
while true {
if consume("**") {
entries.append((.anything, 0))
} else if consume("c*") {
Expand All @@ -497,12 +497,10 @@ extension StringParser {
entries.append((.structField, idx))
} else if let tupleElemIdx = consumeInt() {
entries.append((.tupleField, tupleElemIdx))
} else {
try throwError("expected selection path component")
} else if !consume(".") {
return try createPath(from: entries)
}
} while consume(".")

return try createPath(from: entries)
}
}

private func createPath(from entries: [(SmallProjectionPath.FieldKind, Int)]) throws -> SmallProjectionPath {
Expand Down
56 changes: 31 additions & 25 deletions docs/SIL.rst
Original file line number Diff line number Diff line change
Expand Up @@ -956,7 +956,7 @@ Functions
decl ::= sil-function
sil-function ::= 'sil' sil-linkage? sil-function-attribute+
sil-function-name ':' sil-type
'{' sil-basic-block+ '}'
'{' argument-effect* sil-basic-block* '}'
sil-function-name ::= '@' [A-Za-z_0-9]+

SIL functions are defined with the ``sil`` keyword. SIL function names
Expand All @@ -966,6 +966,8 @@ and is usually the mangled name of the originating Swift declaration.
The ``sil`` syntax declares the function's name and SIL type, and
defines the body of the function inside braces. The declared type must
be a function type, which may be generic.
If there are no `sil-basic-block`s contained in the body, the function
is an external declaration.


Function Attributes
Expand Down Expand Up @@ -1094,30 +1096,6 @@ from the command line.
sil-function-effects ::= 'releasenone'

The specified memory effects of the function.
::

sil-function-attribute ::= '[' 'escapes' escape-list ']'
sil-function-attribute ::= '[' 'defined_escapes' escape-list ']'
escape-list ::= (escape-list ',')? escape
escape ::= '!' arg-selection // not-escaping
escape ::= arg-selection '=>' arg-selection // exclusive escaping
escape ::= arg-selection '->' arg-selection // not-exclusive escaping
arg-selection ::= arg-or-return ('.' projection-path)?
arg-or-return ::= '%' [0-9]+
arg-or-return ::= '%r'
projection-path ::= (projection-path '.')? path-component
path-component ::= 's' [0-9]+ // struct field
path-component ::= 'c' [0-9]+ // class field
path-component ::= 'ct' // class tail element
path-component ::= 'e' [0-9]+ // enum case
path-component ::= [0-9]+ // tuple element
path-component ::= 'v**' // any value fields
path-component ::= 'c*' // any class field
path-component ::= '**' // anything

The escaping effects for function arguments. For details see the documentation
in ``SwiftCompilerSources/Sources/SIL/Effects.swift``.

::

sil-function-attribute ::= '[_semantics "' [A-Za-z._0-9]+ '"]'
Expand Down Expand Up @@ -1147,6 +1125,34 @@ Specifies the performance constraints for the function, which defines which type
of runtime functions are allowed to be called from the function.


Argument Effects
````````````````

The effects for function arguments. For details see the documentation
in ``SwiftCompilerSources/Sources/SIL/Effects.swift``.
::

argument-effect ::= '[' argument-name defined-effect? ':' effect (',' effect)*]'
argument-name ::= '%' [0-9]+
defined-effect ::= '!' // the effect is defined in the source code and not
// derived by the optimizer

effect ::= 'noescape' projection-path?
effect ::= 'escape' projection-path? '=>' arg-or-return // exclusive escape
effect ::= 'escape' projection-path? '->' arg-or-return // not-exclusive escape
arg-or-return ::= argument-name ('.' projection-path)?
arg-or-return ::= '%r' ('.' projection-path)?

projection-path ::= path-component ('.' path-component)*
path-component ::= 's' [0-9]+ // struct field
path-component ::= 'c' [0-9]+ // class field
path-component ::= 'ct' // class tail element
path-component ::= 'e' [0-9]+ // enum case
path-component ::= [0-9]+ // tuple element
path-component ::= 'v**' // any value fields
path-component ::= 'c*' // any class field
path-component ::= '**' // anything

Basic Blocks
~~~~~~~~~~~~
::
Expand Down
12 changes: 6 additions & 6 deletions include/swift/SIL/SILBridging.h
Original file line number Diff line number Diff line change
Expand Up @@ -217,9 +217,9 @@ typedef enum {
#include "swift/AST/Builtins.def"
} BridgedBuiltinID;

enum {
EffectsFlagEscape = 0x1,
EffectsFlagDerived = 0x2
struct BridgedEffectInfo {
SwiftInt argumentIndex;
bool isDerived;
};

void registerBridgedClass(llvm::StringRef className, SwiftMetatype metatype);
Expand All @@ -231,17 +231,17 @@ typedef void (* _Nonnull FunctionWriteFn)(BridgedFunction,
BridgedOStream, SwiftInt);
typedef BridgedParsingError (*_Nonnull FunctionParseFn)(BridgedFunction,
llvm::StringRef,
SwiftInt, SwiftInt,
SwiftInt, SwiftInt, SwiftInt,
BridgedArrayRef);
typedef SwiftInt (* _Nonnull FunctionCopyEffectsFn)(BridgedFunction,
BridgedFunction);
typedef SwiftInt (* _Nonnull FunctionGetEffectFlagsFn)(BridgedFunction, SwiftInt);
typedef BridgedEffectInfo (* _Nonnull FunctionGetEffectInfoFn)(BridgedFunction, SwiftInt);

void Function_register(SwiftMetatype metatype,
FunctionRegisterFn initFn, FunctionRegisterFn destroyFn,
FunctionWriteFn writeFn, FunctionParseFn parseFn,
FunctionCopyEffectsFn copyEffectsFn,
FunctionGetEffectFlagsFn hasEffectsFn);
FunctionGetEffectInfoFn effectInfoFn);

SwiftInt PassContext_continueWithNextSubpassRun(BridgedPassContext passContext,
OptionalBridgedInstruction inst);
Expand Down
Loading