Skip to content

Commit 700412b

Browse files
committed
add the ComputeEffects pass
The ComputeEffects pass derives escape information for function arguments and adds those effects in the function. This needs a lot of changes in check-lines in the tests, because the effects are printed in SIL
1 parent 4d5987c commit 700412b

File tree

64 files changed

+787
-172
lines changed

Some content is hidden

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

64 files changed

+787
-172
lines changed

SwiftCompilerSources/Sources/Optimizer/FunctionPasses/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
swift_compiler_sources(Optimizer
1010
AssumeSingleThreaded.swift
11+
ComputeEffects.swift
1112
EscapeInfoDumper.swift
1213
SILPrinter.swift
1314
MergeCondFails.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+
}

SwiftCompilerSources/Sources/Optimizer/PassManager/PassRegistration.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ private func registerSwiftPasses() {
4848
registerPass(mergeCondFailsPass, { mergeCondFailsPass.run($0) })
4949
registerPass(escapeInfoDumper, { escapeInfoDumper.run($0) })
5050
registerPass(addressEscapeInfoDumper, { addressEscapeInfoDumper.run($0) })
51+
registerPass(computeEffects, { computeEffects.run($0) })
5152
registerPass(simplifyBeginCOWMutationPass, { simplifyBeginCOWMutationPass.run($0) })
5253
registerPass(simplifyGlobalValuePass, { simplifyGlobalValuePass.run($0) })
5354
registerPass(simplifyStrongRetainPass, { simplifyStrongRetainPass.run($0) })

include/swift/SILOptimizer/PassManager/Passes.def

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -228,6 +228,8 @@ SWIFT_FUNCTION_PASS(EscapeInfoDumper, "dump-escape-info",
228228
"Dumps escape information")
229229
SWIFT_FUNCTION_PASS(AddressEscapeInfoDumper, "dump-addr-escape-info",
230230
"Dumps address escape information")
231+
SWIFT_FUNCTION_PASS(ComputeEffects, "compute-effects",
232+
"Computes function effects")
231233
PASS(FlowIsolation, "flow-isolation",
232234
"Enforces flow-sensitive actor isolation rules")
233235
PASS(FunctionOrderPrinter, "function-order-printer",

lib/SILOptimizer/PassManager/PassPipeline.cpp

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -618,6 +618,7 @@ static void addHighLevelFunctionPipeline(SILPassPipelinePlan &P) {
618618
addHighLevelLoopOptPasses(P);
619619

620620
P.addStringOptimization();
621+
P.addComputeEffects();
621622
}
622623

623624
// After "high-level" function passes have processed the entire call tree, run
@@ -632,6 +633,7 @@ static void addHighLevelModulePipeline(SILPassPipelinePlan &P) {
632633
// Do the first stack promotion on high-level SIL before serialization.
633634
//
634635
// FIXME: why does StackPromotion need to run in the module pipeline?
636+
P.addComputeEffects();
635637
P.addStackPromotion();
636638

637639
P.addGlobalOpt();
@@ -668,6 +670,10 @@ static void addClosureSpecializePassPipeline(SILPassPipelinePlan &P) {
668670
P.addSILCombine();
669671
P.addPerformanceConstantPropagation();
670672
P.addSimplifyCFG();
673+
674+
// ComputeEffects should be done at the end of a function-pipeline. The next
675+
// pass (GlobalOpt) is a module pass, so this is the end of a function-pipeline.
676+
P.addComputeEffects();
671677

672678
// Hoist globals out of loops.
673679
// Global-init functions should not be inlined GlobalOpt is done.
@@ -698,6 +704,8 @@ static void addClosureSpecializePassPipeline(SILPassPipelinePlan &P) {
698704
// passes can expose more inlining opportunities.
699705
addSimplifyCFGSILCombinePasses(P);
700706

707+
P.addComputeEffects();
708+
701709
// We do this late since it is a pass like the inline caches that we only want
702710
// to run once very late. Make sure to run at least one round of the ARC
703711
// optimizer after this.
@@ -717,6 +725,7 @@ static void addLowLevelPassPipeline(SILPassPipelinePlan &P) {
717725

718726
// We've done a lot of optimizations on this function, attempt to FSO.
719727
P.addFunctionSignatureOpts();
728+
P.addComputeEffects();
720729
}
721730

722731
static void addLateLoopOptPassPipeline(SILPassPipelinePlan &P) {
@@ -745,6 +754,7 @@ static void addLateLoopOptPassPipeline(SILPassPipelinePlan &P) {
745754

746755
// Sometimes stack promotion can catch cases only at this late stage of the
747756
// pipeline, after FunctionSignatureOpts.
757+
P.addComputeEffects();
748758
P.addStackPromotion();
749759

750760
// Optimize overflow checks.

test/DebugInfo/optimizer_pipeline.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import Swift
1010
// CHECK: sil_scope [[S1:[0-9]+]] { {{.*}} parent @$s18optimizer_pipeline1AVyACs13KeyValuePairsVyypypGcfC
1111
// CHECK: sil_scope [[S2:[0-9]+]] { {{.*}} parent [[S1]] }
1212
//
13-
// CHECK-LABEL: sil @$s18optimizer_pipeline1AVyACs13KeyValuePairsVyypypGcfC : $@convention(method) (@owned KeyValuePairs<Any, Any>, @thin A.Type) -> A {
13+
// CHECK-LABEL: sil {{.*}}@$s18optimizer_pipeline1AVyACs13KeyValuePairsVyypypGcfC : $@convention(method) (@owned KeyValuePairs<Any, Any>, @thin A.Type) -> A {
1414
// CHECK: bb0(%0 : $KeyValuePairs<Any, Any>, %1 : $@thin A.Type):
1515
// CHECK: unreachable , scope [[S2]]
1616
// CHECK-LABEL: } // end sil function '$s18optimizer_pipeline1AVyACs13KeyValuePairsVyypypGcfC'

test/SIL/Parser/default_witness_tables.sil

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,15 +15,15 @@ public protocol ResilientProtocol {
1515
func defaultE()
1616
}
1717

18-
// CHECK-LABEL: sil @defaultC : $@convention(witness_method: ResilientProtocol) <Self where Self : ResilientProtocol> (@in_guaranteed Self) -> ()
18+
// CHECK-LABEL: sil {{.*}}@defaultC : $@convention(witness_method: ResilientProtocol) <Self where Self : ResilientProtocol> (@in_guaranteed Self) -> ()
1919
sil @defaultC : $@convention(witness_method: ResilientProtocol) <Self where Self : ResilientProtocol> (@in_guaranteed Self) -> () {
2020
bb0(%0 : $*Self):
2121
%result = tuple ()
2222
return %result : $()
2323
}
2424

2525

26-
// CHECK-LABEL: sil @defaultD : $@convention(witness_method: ResilientProtocol) <Self where Self : ResilientProtocol> (@in_guaranteed Self) -> ()
26+
// CHECK-LABEL: sil {{.*}}@defaultD : $@convention(witness_method: ResilientProtocol) <Self where Self : ResilientProtocol> (@in_guaranteed Self) -> ()
2727
sil @defaultD : $@convention(witness_method: ResilientProtocol) <Self where Self : ResilientProtocol> (@in_guaranteed Self) -> () {
2828
bb0(%0 : $*Self):
2929
%result = tuple ()
@@ -39,7 +39,7 @@ protocol InternalProtocol {
3939
func defaultF()
4040
}
4141

42-
// CHECK-LABEL: sil hidden @defaultF
42+
// CHECK-LABEL: sil hidden {{.*}}@defaultF
4343
sil hidden @defaultF : $@convention(witness_method: InternalProtocol) <Self where Self : InternalProtocol> (@in_guaranteed Self) -> () {
4444
bb0(%0 : $*Self):
4545
%result = tuple ()

test/SIL/Serialization/function_param_convention.sil

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import Swift
66
import FunctionInput
77

88
// Make sure we can deserialize a SIL function with these various attributes.
9-
// CHECK: sil public_external [serialized] [canonical] @foo : $@convention(thin) (@in X, @inout X, @in_guaranteed X, @owned X, X, @guaranteed X) -> @out X {
9+
// CHECK: sil public_external [serialized] {{.*}}[canonical] @foo : $@convention(thin) (@in X, @inout X, @in_guaranteed X, @owned X, X, @guaranteed X) -> @out X {
1010

1111
sil @foo : $@convention(thin) (@in X, @inout X, @in_guaranteed X, @owned X, X, @guaranteed X) -> @out X
1212

test/SIL/Serialization/perf_inline_without_inline_all.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import Swift
66

77
// Make sure we inline everything.
88

9-
// CHECK-LABEL: sil @main
9+
// CHECK-LABEL: sil {{.*}}@main
1010
// CHECK: bb0({{.*}}):
1111
// CHECK-NEXT: alloc_global
1212
// CHECK-NEXT: global_addr

test/SIL/Serialization/specializer_can_deserialize.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import Swift
66

77
// Make sure the specializer can deserialize code.
88

9-
// CHECK-LABEL: sil @main
9+
// CHECK-LABEL: sil {{.*}}@main
1010
// CHECK: bb0({{.*}}):
1111
// CHECK: function_ref @$ss9ContainerVAByxGycfCBi32__Tg5{{.*}}
1212
// CHECK: function_ref @$ss9ContainerV11doSomethingyyFBi32__Tg5{{.*}}

test/SILGen/copy_operator.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ class Klass {}
5050
// CHECK-SIL-NEXT: return [[VALUE]] : $Klass
5151
// CHECK-SIL: } // end sil function '$s8moveonly7useCopyyAA5KlassCADF'
5252

53-
// CHECK-SIL-OPT-LABEL: sil @$s8moveonly7useCopyyAA5KlassCADF : $@convention(thin) (@guaranteed Klass) -> @owned Klass {
53+
// CHECK-SIL-OPT-LABEL: sil {{.*}}@$s8moveonly7useCopyyAA5KlassCADF : $@convention(thin) (@guaranteed Klass) -> @owned Klass {
5454
// CHECK-SIL-OPT: bb0([[ARG:%.*]] : $Klass):
5555
// CHECK-SIL-OPT-NEXT: debug_value
5656
// CHECK-SIL-OPT-NEXT: strong_retain [[ARG]]
@@ -91,7 +91,7 @@ public func useCopy(_ k: Klass) -> Klass {
9191
// CHECK-SIL-NEXT: return [[VALUE]] : $T
9292
// CHECK-SIL: } // end sil function '$s8moveonly7useCopyyxxRlzClF'
9393

94-
// CHECK-SIL-OPT-LABEL: sil @$s8moveonly7useCopyyxxRlzClF : $@convention(thin) <T where T : AnyObject> (@guaranteed T) -> @owned T {
94+
// CHECK-SIL-OPT-LABEL: sil {{.*}}@$s8moveonly7useCopyyxxRlzClF : $@convention(thin) <T where T : AnyObject> (@guaranteed T) -> @owned T {
9595
// CHECK-SIL-OPT: bb0([[ARG:%.*]] :
9696
// CHECK-SIL-OPT-NEXT: debug_value
9797
// CHECK-SIL-OPT-NEXT: strong_retain [[ARG]]

0 commit comments

Comments
 (0)