Skip to content

Commit 8c09291

Browse files
committed
Add the LifetimeDependenceInsertion pass.
Insert mark_dependence [nonescaping] markers at every lifetime introducer that produces a lifetime-dependent value.
1 parent 274c478 commit 8c09291

File tree

5 files changed

+222
-1
lines changed

5 files changed

+222
-1
lines changed

SwiftCompilerSources/Sources/Optimizer/FunctionPasses/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ swift_compiler_sources(Optimizer
1919
InitializeStaticGlobals.swift
2020
LetPropertyLowering.swift
2121
LifetimeDependenceDiagnostics.swift
22+
LifetimeDependenceInsertion.swift
2223
ObjectOutliner.swift
2324
ObjCBridgingOptimization.swift
2425
MergeCondFails.swift
Lines changed: 214 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,214 @@
1+
//===--- LifetimeDependenceInsertion.swift - insert lifetime dependence ---===//
2+
//
3+
// This source file is part of the Swift.org open source project
4+
//
5+
// Copyright (c) 2014 - 2024 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+
/// Insert mark_dependence [nonescaping] markers on the owned returned
14+
/// or yielded value of a call whose return type is non-escaping.
15+
///
16+
/// Pass dependencies: This must run as a SILGen cleanup pass before
17+
/// any lifetime canonicalization or optimization can be performed.
18+
///
19+
//===----------------------------------------------------------------------===//
20+
21+
import SIL
22+
23+
private let verbose = false
24+
25+
private func log(_ message: @autoclosure () -> String) {
26+
if verbose {
27+
print("### \(message())")
28+
}
29+
}
30+
31+
let lifetimeDependenceInsertionPass = FunctionPass(
32+
name: "lifetime-dependence-insertion")
33+
{ (function: Function, context: FunctionPassContext) in
34+
if !context.options.hasFeature(.NonescapableTypes) {
35+
return
36+
}
37+
log("Inserting lifetime dependence markers in \(function.name)")
38+
39+
for instruction in function.instructions {
40+
if let dependentApply = LifetimeDependentApply(instruction) {
41+
insertDependencies(for: dependentApply, context)
42+
}
43+
}
44+
}
45+
46+
/// An apply that produces a non-escapable value, linking it to a parent value.
47+
private struct LifetimeDependentApply {
48+
let applySite: FullApplySite
49+
50+
init?(_ instruction: Instruction) {
51+
guard let apply = instruction as? FullApplySite else {
52+
return nil
53+
}
54+
if !apply.hasResultDependence {
55+
return nil
56+
}
57+
self.applySite = apply
58+
}
59+
60+
init?(withResult value: Value) {
61+
switch value {
62+
case let apply as ApplyInst:
63+
if let dependentApply = LifetimeDependentApply(apply) {
64+
self = dependentApply
65+
}
66+
case let arg as Argument:
67+
guard let termResult = TerminatorResult(arg) else { return nil }
68+
switch termResult.terminator {
69+
case let ta as TryApplyInst:
70+
if termResult.successor == ta.errorBlock {
71+
if let dependentApply = LifetimeDependentApply(ta) {
72+
self = dependentApply
73+
}
74+
}
75+
default:
76+
break
77+
}
78+
default:
79+
break
80+
}
81+
return nil
82+
}
83+
}
84+
85+
extension LifetimeDependentApply {
86+
/// A lifetime argument copies, borrows, or mutatably borrows the
87+
/// lifetime of the argument value.
88+
struct LifetimeArgument {
89+
let convention: LifetimeDependenceConvention
90+
let value: Value
91+
}
92+
93+
func getLifetimeArguments() -> SingleInlineArray<LifetimeArgument> {
94+
var args = SingleInlineArray<LifetimeArgument>()
95+
for operand in applySite.parameterOperands {
96+
guard let dep = applySite.resultDependence(on: operand) else {
97+
continue
98+
}
99+
args.push(LifetimeArgument(convention: dep, value: operand.value))
100+
}
101+
return args
102+
}
103+
}
104+
105+
/// Replace the each dependent apply result with a chain of
106+
/// mark_dependence [nonescaping] instructions; one for each base.
107+
private func insertDependencies(for apply: LifetimeDependentApply,
108+
_ context: FunctionPassContext ) {
109+
precondition(apply.applySite.results.count > 0,
110+
"a lifetime-dependent instruction must have at least one result")
111+
112+
let bases = recursivelyFindDependenceBases(of: apply, context)
113+
let builder = Builder(after: apply.applySite, context)
114+
for dependentValue in apply.applySite.resultOrYields {
115+
insertMarkDependencies(value: dependentValue, initializer: nil,
116+
bases: bases, builder: builder, context)
117+
}
118+
for resultOper in apply.applySite.indirectResultOperands {
119+
let accessBase = resultOper.value.accessBase
120+
guard let (initialAddress, initializingStore) =
121+
accessBase.findSingleInitializer(context) else {
122+
continue
123+
}
124+
// TODO: This is currently too strict for a diagnostic pass. We
125+
// should handle/cleanup projections and casts that occur before
126+
// the initializingStore. Or check in the SIL verifier that all
127+
// stores without an access scope follow this form. Then convert
128+
// this bail-out to an assert.
129+
guard initialAddress.usesOccurOnOrAfter(instruction: initializingStore,
130+
context) else {
131+
continue
132+
}
133+
assert(initializingStore == resultOper.instruction,
134+
"an indirect result is a store")
135+
insertMarkDependencies(value: initialAddress,
136+
initializer: initializingStore, bases: bases,
137+
builder: builder, context)
138+
}
139+
}
140+
141+
private func insertMarkDependencies(value: Value, initializer: Instruction?,
142+
bases: [Value], builder: Builder,
143+
_ context: FunctionPassContext) {
144+
var currentValue = value
145+
for base in bases {
146+
let markDep = builder.createMarkDependence(
147+
value: currentValue, base: base, kind: .Unresolved)
148+
149+
let uses = currentValue.uses.lazy.filter {
150+
let inst = $0.instruction
151+
return inst != markDep && inst != initializer && !(inst is Deallocation)
152+
}
153+
uses.replaceAll(with: markDep, context)
154+
currentValue = markDep
155+
}
156+
}
157+
158+
/// Return base values that this return value depends on.
159+
///
160+
/// For lifetime copies, walk up the dependence chain to find the
161+
/// dependence roots, inserting dependencies for any
162+
/// LifetimeDependentApply.
163+
private func recursivelyFindDependenceBases(of apply: LifetimeDependentApply,
164+
_ context: FunctionPassContext)
165+
-> [Value] {
166+
log("Creating dependencies for \(apply.applySite)")
167+
var bases: [Value] = []
168+
for lifetimeArg in apply.getLifetimeArguments() {
169+
switch lifetimeArg.convention {
170+
case .inherit:
171+
// Inherit the argument's lifetime dependence by finding the
172+
// roots. This requires that a mark_dependence [nonescaping]
173+
// already be created for any earlier LifetimeDependentApply.
174+
_ = LifetimeDependence.visitDependenceRoots(enclosing: lifetimeArg.value,
175+
context)
176+
{ (scope: LifetimeDependence.Scope) in
177+
if let updatedScope = recursivelyUpdate(scope: scope, context) {
178+
log("Inherited lifetime from \(lifetimeArg.value)")
179+
log(" depends on: \(updatedScope)")
180+
bases.append(updatedScope.parentValue)
181+
}
182+
return .continueWalk
183+
}
184+
case .scope:
185+
// Create a new dependence on the apply's access to the argument.
186+
if let scope =
187+
LifetimeDependence.Scope(base: lifetimeArg.value, context) {
188+
log("Scoped lifetime from \(lifetimeArg.value)")
189+
log(" scope: \(scope)")
190+
bases.append(scope.parentValue)
191+
}
192+
}
193+
}
194+
return bases
195+
}
196+
197+
// Recursively insert dependencies, assuming no cycle of dependent applies.
198+
//
199+
// TODO: needs unit test.
200+
private func recursivelyUpdate(scope: LifetimeDependence.Scope,
201+
_ context: FunctionPassContext) -> LifetimeDependence.Scope? {
202+
if let dependentApply =
203+
LifetimeDependentApply(withResult: scope.parentValue) {
204+
insertDependencies(for: dependentApply, context)
205+
// If a mark_dependence [nonescaping] was created for this apply,
206+
// then return it as the updated dependence. Otherwise, return the
207+
// original dependence.
208+
if let markDep = scope.parentValue.uses.singleUse?.instruction
209+
as? MarkDependenceInst {
210+
return LifetimeDependence(markDep, context)?.scope
211+
}
212+
}
213+
return scope
214+
}

SwiftCompilerSources/Sources/Optimizer/PassManager/PassRegistration.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,7 @@ private func registerSwiftPasses() {
9090
registerPass(earlyRedundantLoadElimination, { earlyRedundantLoadElimination.run($0) })
9191
registerPass(deinitDevirtualizer, { deinitDevirtualizer.run($0) })
9292
registerPass(lifetimeDependenceDiagnosticsPass, { lifetimeDependenceDiagnosticsPass.run($0) })
93+
registerPass(lifetimeDependenceInsertionPass, { lifetimeDependenceInsertionPass.run($0) })
9394

9495
// Instruction passes
9596
registerForSILCombine(BeginCOWMutationInst.self, { run(BeginCOWMutationInst.self, $0) })

include/swift/SILOptimizer/PassManager/Passes.def

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -287,6 +287,9 @@ PASS(LateDeadFunctionAndGlobalElimination, "late-deadfuncelim",
287287
SWIFT_FUNCTION_PASS(LifetimeDependenceDiagnostics,
288288
"lifetime-dependence-diagnostics",
289289
"Diagnose Lifetime Dependence")
290+
SWIFT_FUNCTION_PASS(LifetimeDependenceInsertion,
291+
"lifetime-dependence-insertion",
292+
"Insert Lifetime Dependence Markers")
290293
PASS(LoopCanonicalizer, "loop-canonicalizer",
291294
"Loop Canonicalization")
292295
PASS(LoopInfoPrinter, "loop-info-printer",

lib/SILOptimizer/PassManager/PassPipeline.cpp

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -291,7 +291,9 @@ SILPassPipelinePlan::getSILGenPassPipeline(const SILOptions &Options) {
291291
P.startPipeline("SILGen Passes");
292292

293293
P.addSILGenCleanup();
294-
294+
if (EnableLifetimeDependenceDiagnostics) {
295+
P.addLifetimeDependenceInsertion();
296+
}
295297
if (SILViewSILGenCFG) {
296298
addCFGPrinterPipeline(P, "SIL View SILGen CFG");
297299
}

0 commit comments

Comments
 (0)