Skip to content

Commit 39f8ec2

Browse files
authored
Merge pull request #61433 from eeckstein/side-effects
Swift Optimizer: add side effect analysis
2 parents b926c18 + 741c6c3 commit 39f8ec2

Some content is hidden

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

56 files changed

+2924
-519
lines changed

SwiftCompilerSources/Sources/Basic/Utils.swift

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,19 +40,31 @@ public func assert(_ condition: Bool, file: StaticString = #fileID, line: UInt =
4040
}
4141
}
4242

43+
//===----------------------------------------------------------------------===//
44+
// Debugging Utilities
45+
//===----------------------------------------------------------------------===//
46+
47+
/// Let's lldb's `po` command not print any "internal" properties of the conforming type.
48+
///
49+
/// This is useful if the `description` already contains all the information of a type instance.
50+
public protocol NoReflectionChildren : CustomReflectable { }
51+
52+
public extension NoReflectionChildren {
53+
var customMirror: Mirror { Mirror(self, children: []) }
54+
}
55+
4356

4457
//===----------------------------------------------------------------------===//
4558
// StringRef
4659
//===----------------------------------------------------------------------===//
4760

48-
public struct StringRef : CustomStringConvertible, CustomReflectable {
61+
public struct StringRef : CustomStringConvertible, NoReflectionChildren {
4962
let _bridged: llvm.StringRef
5063

5164
public init(bridged: llvm.StringRef) { self._bridged = bridged }
5265

5366
public var string: String { _bridged.string }
5467
public var description: String { string }
55-
public var customMirror: Mirror { Mirror(self, children: []) }
5668

5769
public static func ==(lhs: StringRef, rhs: StaticString) -> Bool {
5870
let lhsBuffer = UnsafeBufferPointer<UInt8>(

SwiftCompilerSources/Sources/Optimizer/DataStructures/BasicBlockRange.swift

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ import SIL
4646
/// This type should be a move-only type, but unfortunately we don't have move-only
4747
/// types yet. Therefore it's needed to call `deinitialize()` explicitly to
4848
/// destruct this data structure, e.g. in a `defer {}` block.
49-
struct BasicBlockRange : CustomStringConvertible, CustomReflectable {
49+
struct BasicBlockRange : CustomStringConvertible, NoReflectionChildren {
5050

5151
/// The dominating begin block.
5252
let begin: BasicBlock
@@ -149,8 +149,6 @@ struct BasicBlockRange : CustomStringConvertible, CustomReflectable {
149149
"""
150150
}
151151

152-
var customMirror: Mirror { Mirror(self, children: []) }
153-
154152
/// TODO: once we have move-only types, make this a real deinit.
155153
mutating func deinitialize() {
156154
worklist.deinitialize()

SwiftCompilerSources/Sources/Optimizer/DataStructures/BasicBlockWorklist.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ import SIL
2020
/// This type should be a move-only type, but unfortunately we don't have move-only
2121
/// types yet. Therefore it's needed to call `deinitialize()` explicitly to
2222
/// destruct this data structure, e.g. in a `defer {}` block.
23-
struct BasicBlockWorklist : CustomStringConvertible, CustomReflectable {
23+
struct BasicBlockWorklist : CustomStringConvertible, NoReflectionChildren {
2424
private var worklist: Stack<BasicBlock>
2525
private var pushedBlocks: BasicBlockSet
2626

@@ -56,7 +56,7 @@ struct BasicBlockWorklist : CustomStringConvertible, CustomReflectable {
5656
"""
5757
}
5858

59-
var customMirror: Mirror { Mirror(self, children: []) }
59+
var function: Function { pushedBlocks.function }
6060

6161
/// TODO: once we have move-only types, make this a real deinit.
6262
mutating func deinitialize() {

SwiftCompilerSources/Sources/Optimizer/DataStructures/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
swift_compiler_sources(Optimizer
1010
BasicBlockRange.swift
1111
BasicBlockWorklist.swift
12+
DeadEndBlocks.swift
1213
FunctionUses.swift
1314
InstructionRange.swift
1415
Set.swift
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
//===--- DeadEndBlocks.swift ----------------------------------------------===//
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+
/// A utility for finding dead-end blocks.
16+
///
17+
/// Dead-end blocks are blocks from which there is no path to the function exit
18+
/// (`return`, `throw` or unwind). These are blocks which end with an unreachable
19+
/// instruction and blocks from which all paths end in "unreachable" blocks.
20+
struct DeadEndBlocks : CustomStringConvertible, NoReflectionChildren {
21+
private var worklist: BasicBlockWorklist
22+
23+
init(function: Function, _ context: PassContext) {
24+
self.worklist = BasicBlockWorklist(context)
25+
26+
// Initialize the worklist with all function-exiting blocks.
27+
for block in function.blocks where block.terminator.isFunctionExiting {
28+
worklist.pushIfNotVisited(block)
29+
}
30+
31+
// Propagate lifeness up the control flow.
32+
while let block = worklist.pop() {
33+
worklist.pushIfNotVisited(contentsOf: block.predecessors)
34+
}
35+
}
36+
37+
/// Returns true if `block` is a dead-end block.
38+
func isDeadEnd(block: BasicBlock) -> Bool {
39+
return !worklist.hasBeenPushed(block)
40+
}
41+
42+
var description: String {
43+
let blockNames = worklist.function.blocks.filter(isDeadEnd).map(\.name)
44+
return "[" + blockNames.joined(separator: ",") + "]"
45+
}
46+
47+
/// TODO: once we have move-only types, make this a real deinit.
48+
mutating func deinitialize() {
49+
worklist.deinitialize()
50+
}
51+
}

SwiftCompilerSources/Sources/Optimizer/DataStructures/InstructionRange.swift

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ import SIL
3838
/// This type should be a move-only type, but unfortunately we don't have move-only
3939
/// types yet. Therefore it's needed to call `deinitialize()` explicitly to
4040
/// destruct this data structure, e.g. in a `defer {}` block.
41-
struct InstructionRange : CustomStringConvertible, CustomReflectable {
41+
struct InstructionRange : CustomStringConvertible, NoReflectionChildren {
4242

4343
/// The dominating begin instruction.
4444
let begin: Instruction
@@ -139,8 +139,6 @@ struct InstructionRange : CustomStringConvertible, CustomReflectable {
139139
"""
140140
}
141141

142-
var customMirror: Mirror { Mirror(self, children: []) }
143-
144142
/// TODO: once we have move-only types, make this a real deinit.
145143
mutating func deinitialize() {
146144
insertedInsts.deinitialize()

SwiftCompilerSources/Sources/Optimizer/DataStructures/Set.swift

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ import OptimizerBridging
2121
/// This type should be a move-only type, but unfortunately we don't have move-only
2222
/// types yet. Therefore it's needed to call `deinitialize()` explicitly to
2323
/// destruct this data structure, e.g. in a `defer {}` block.
24-
struct BasicBlockSet : CustomStringConvertible, CustomReflectable {
24+
struct BasicBlockSet : CustomStringConvertible, NoReflectionChildren {
2525

2626
private let context: PassContext
2727
private let bridged: BridgedBasicBlockSet
@@ -46,13 +46,12 @@ struct BasicBlockSet : CustomStringConvertible, CustomReflectable {
4646
}
4747

4848
var description: String {
49-
let function = BasicBlockSet_getFunction(bridged).function
5049
let blockNames = function.blocks.enumerated().filter { contains($0.1) }
5150
.map { "bb\($0.0)"}
5251
return "{" + blockNames.joined(separator: ", ") + "}"
5352
}
5453

55-
var customMirror: Mirror { Mirror(self, children: []) }
54+
var function: Function { BasicBlockSet_getFunction(bridged).function }
5655

5756
/// TODO: once we have move-only types, make this a real deinit.
5857
mutating func deinitialize() {
@@ -68,7 +67,7 @@ struct BasicBlockSet : CustomStringConvertible, CustomReflectable {
6867
/// This type should be a move-only type, but unfortunately we don't have move-only
6968
/// types yet. Therefore it's needed to call `deinitialize()` explicitly to
7069
/// destruct this data structure, e.g. in a `defer {}` block.
71-
struct ValueSet : CustomStringConvertible, CustomReflectable {
70+
struct ValueSet : CustomStringConvertible, NoReflectionChildren {
7271

7372
private let context: PassContext
7473
private let bridged: BridgedNodeSet
@@ -113,8 +112,6 @@ struct ValueSet : CustomStringConvertible, CustomReflectable {
113112
return d
114113
}
115114

116-
var customMirror: Mirror { Mirror(self, children: []) }
117-
118115
/// TODO: once we have move-only types, make this a real deinit.
119116
mutating func deinitialize() {
120117
PassContext_freeNodeSet(context._bridged, bridged)
@@ -129,7 +126,7 @@ struct ValueSet : CustomStringConvertible, CustomReflectable {
129126
/// This type should be a move-only type, but unfortunately we don't have move-only
130127
/// types yet. Therefore it's needed to call `deinitialize()` explicitly to
131128
/// destruct this data structure, e.g. in a `defer {}` block.
132-
struct InstructionSet : CustomStringConvertible, CustomReflectable {
129+
struct InstructionSet : CustomStringConvertible, NoReflectionChildren {
133130

134131
private let context: PassContext
135132
private let bridged: BridgedNodeSet
@@ -165,8 +162,6 @@ struct InstructionSet : CustomStringConvertible, CustomReflectable {
165162
return d
166163
}
167164

168-
var customMirror: Mirror { Mirror(self, children: []) }
169-
170165
/// TODO: once we have move-only types, make this a real deinit.
171166
mutating func deinitialize() {
172167
PassContext_freeNodeSet(context._bridged, bridged)

SwiftCompilerSources/Sources/Optimizer/FunctionPasses/CMakeLists.txt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,8 @@
88

99
swift_compiler_sources(Optimizer
1010
AssumeSingleThreaded.swift
11-
ComputeEffects.swift
11+
ComputeEscapeEffects.swift
12+
ComputeSideEffects.swift
1213
ObjCBridgingOptimization.swift
1314
MergeCondFails.swift
1415
ReleaseDevirtualizer.swift

SwiftCompilerSources/Sources/Optimizer/FunctionPasses/ComputeEffects.swift renamed to SwiftCompilerSources/Sources/Optimizer/FunctionPasses/ComputeEscapeEffects.swift

Lines changed: 35 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
//===--- ComputeEffects.swift - Compute function effects ------------------===//
1+
//===--- ComputeEscapeEffects.swift ----------------------------------------==//
22
//
33
// This source file is part of the Swift.org open source project
44
//
@@ -12,43 +12,47 @@
1212

1313
import SIL
1414

15-
/// Computes effects for function arguments.
15+
/// Computes escape effects for function arguments.
1616
///
1717
/// For example, if an argument does not escape, adds a non-escaping effect,
18-
/// e.g. "[escapes !%0.**]":
19-
///
20-
/// sil [escapes !%0.**] @foo : $@convention(thin) (@guaranteed X) -> () {
18+
/// ```
19+
/// sil @foo : $@convention(thin) (@guaranteed X) -> () {
20+
/// [%0: noecape **]
2121
/// bb0(%0 : $X):
2222
/// %1 = tuple ()
2323
/// return %1 : $()
2424
/// }
25-
///
25+
/// ```
2626
/// The pass does not try to change or re-compute _defined_ effects.
27-
/// Currently, only escaping effects are handled.
28-
/// In future, this pass may also add other effects, like memory side effects.
29-
let computeEffects = FunctionPass(name: "compute-effects", {
27+
///
28+
let computeEscapeEffects = FunctionPass(name: "compute-escape-effects", {
3029
(function: Function, context: PassContext) in
31-
var argsWithDefinedEffects = getArgIndicesWithDefinedEffects(of: function)
3230

33-
struct IgnoreRecursiveCallVisitor : EscapeVisitor {
34-
func visitUse(operand: Operand, path: EscapePath) -> UseResult {
35-
return isOperandOfRecursiveCall(operand) ? .ignore : .continueWalk
36-
}
37-
}
38-
var newEffects = Stack<ArgumentEffect>(context)
31+
var newEffects = Stack<EscapeEffects.ArgumentEffect>(context)
32+
defer { newEffects.deinitialize() }
33+
3934
let returnInst = function.returnInstruction
35+
let argsWithDefinedEffects = getArgIndicesWithDefinedEscapingEffects(of: function)
4036

4137
for arg in function.arguments {
4238
// We are not interested in arguments with trivial types.
4339
if !arg.type.isNonTrivialOrContainsRawPointer(in: function) { continue }
4440

4541
// Also, we don't want to override defined effects.
4642
if argsWithDefinedEffects.contains(arg.index) { continue }
47-
43+
44+
struct IgnoreRecursiveCallVisitor : EscapeVisitor {
45+
func visitUse(operand: Operand, path: EscapePath) -> UseResult {
46+
return isOperandOfRecursiveCall(operand) ? .ignore : .continueWalk
47+
}
48+
}
49+
4850
// First check: is the argument (or a projected value of it) escaping at all?
4951
if !arg.at(.anything).isEscapingWhenWalkingDown(using: IgnoreRecursiveCallVisitor(),
5052
context) {
51-
newEffects.push(ArgumentEffect(.notEscaping, argumentIndex: arg.index, pathPattern: SmallProjectionPath(.anything)))
53+
let effect = EscapeEffects.ArgumentEffect(.notEscaping, argumentIndex: arg.index,
54+
pathPattern: SmallProjectionPath(.anything))
55+
newEffects.push(effect)
5256
continue
5357
}
5458

@@ -62,17 +66,16 @@ let computeEffects = FunctionPass(name: "compute-effects", {
6266
}
6367

6468
context.modifyEffects(in: function) { (effects: inout FunctionEffects) in
65-
effects.removeDerivedEffects()
66-
effects.argumentEffects.append(contentsOf: newEffects)
69+
effects.escapeEffects.arguments = effects.escapeEffects.arguments.filter { !$0.isDerived }
70+
effects.escapeEffects.arguments.append(contentsOf: newEffects)
6771
}
68-
newEffects.removeAll()
6972
})
7073

7174

7275
/// Returns true if an argument effect was added.
7376
private
7477
func addArgEffects(_ arg: FunctionArgument, argPath ap: SmallProjectionPath,
75-
to newEffects: inout Stack<ArgumentEffect>,
78+
to newEffects: inout Stack<EscapeEffects.ArgumentEffect>,
7679
_ returnInst: ReturnInst?, _ context: PassContext) -> Bool {
7780
// Correct the path if the argument is not a class reference itself, but a value type
7881
// containing one or more references.
@@ -141,39 +144,36 @@ func addArgEffects(_ arg: FunctionArgument, argPath ap: SmallProjectionPath,
141144
return false
142145
}
143146

144-
let effect: ArgumentEffect
147+
let effect: EscapeEffects.ArgumentEffect
145148
switch result {
146149
case .notSet:
147-
effect = ArgumentEffect(.notEscaping, argumentIndex: arg.index, pathPattern: argPath)
150+
effect = EscapeEffects.ArgumentEffect(.notEscaping, argumentIndex: arg.index, pathPattern: argPath)
148151
case .toReturn(let toPath):
149152
let exclusive = isExclusiveEscapeToReturn(fromArgument: arg, fromPath: argPath,
150153
toPath: toPath, returnInst: returnInst, context)
151-
effect = ArgumentEffect(.escapingToReturn(toPath, exclusive),
152-
argumentIndex: arg.index, pathPattern: argPath)
154+
effect = EscapeEffects.ArgumentEffect(.escapingToReturn(toPath, exclusive),
155+
argumentIndex: arg.index, pathPattern: argPath)
153156
case .toArgument(let toArgIdx, let toPath):
154157
let exclusive = isExclusiveEscapeToArgument(fromArgument: arg, fromPath: argPath,
155158
toArgumentIndex: toArgIdx, toPath: toPath, context)
156-
effect = ArgumentEffect(.escapingToArgument(toArgIdx, toPath, exclusive),
157-
argumentIndex: arg.index, pathPattern: argPath)
159+
effect = EscapeEffects.ArgumentEffect(.escapingToArgument(toArgIdx, toPath, exclusive),
160+
argumentIndex: arg.index, pathPattern: argPath)
158161
}
159162
newEffects.push(effect)
160163
return true
161164
}
162165

163166
/// Returns a set of argument indices for which there are "defined" effects (as opposed to derived effects).
164-
private func getArgIndicesWithDefinedEffects(of function: Function) -> Set<Int> {
167+
private func getArgIndicesWithDefinedEscapingEffects(of function: Function) -> Set<Int> {
165168
var argsWithDefinedEffects = Set<Int>()
166169

167-
for effect in function.effects.argumentEffects {
170+
for effect in function.effects.escapeEffects.arguments {
168171
if effect.isDerived { continue }
169172

170173
argsWithDefinedEffects.insert(effect.argumentIndex)
171-
172174
switch effect.kind {
173-
case .notEscaping, .escapingToReturn:
174-
break
175-
case .escapingToArgument(let toArgIdx, _, _):
176-
argsWithDefinedEffects.insert(toArgIdx)
175+
case .notEscaping, .escapingToReturn: break
176+
case .escapingToArgument(let toArgIdx, _, _): argsWithDefinedEffects.insert(toArgIdx)
177177
}
178178
}
179179
return argsWithDefinedEffects

0 commit comments

Comments
 (0)