Skip to content

Commit 01279c4

Browse files
committed
Redesign LifetimeDependenceInsertion for coroutines
Generalize the design. Extend it to handle different kinds of coroutine dependencies: address/value, scoped/inherited.
1 parent dc4e466 commit 01279c4

File tree

1 file changed

+114
-80
lines changed

1 file changed

+114
-80
lines changed

SwiftCompilerSources/Sources/Optimizer/FunctionPasses/LifetimeDependenceInsertion.swift

Lines changed: 114 additions & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -98,107 +98,133 @@ extension LifetimeDependentApply {
9898

9999
/// A lifetime argument that either inherits or creates a new scope for the lifetime of the argument value.
100100
struct LifetimeSource {
101+
let targetKind: TargetKind
101102
let convention: LifetimeDependenceConvention
102103
let value: Value
103104
}
104105

105106
/// List of lifetime dependencies for a single target.
106-
struct LifetimeSources {
107-
let targetKind: TargetKind
107+
struct LifetimeSourceInfo {
108108
var sources = SingleInlineArray<LifetimeSource>()
109+
var bases = [Value]()
109110
}
110111

111-
func getResultDependenceSources() -> LifetimeSources? {
112-
guard applySite.hasResultDependence else { return nil }
113-
var sources: LifetimeSources
114-
switch applySite {
115-
case let beginApply as BeginApplyInst:
116-
if beginApply.yieldedValues.contains(where: { $0.type.isAddress }) {
117-
sources = LifetimeSources(targetKind: .yieldAddress)
118-
} else {
119-
sources = LifetimeSources(targetKind: .yield)
112+
func getResultDependenceSources() -> LifetimeSourceInfo? {
113+
guard applySite.hasResultDependence else {
114+
return nil
115+
}
116+
var info = LifetimeSourceInfo()
117+
if let beginApply = applySite as? BeginApplyInst {
118+
return getYieldDependenceSources(beginApply: beginApply)
119+
}
120+
for operand in applySite.parameterOperands {
121+
guard let dep = applySite.resultDependence(on: operand) else {
122+
continue
120123
}
121-
default:
122-
sources = LifetimeSources(targetKind: .result)
124+
info.sources.push(LifetimeSource(targetKind: .result, convention: dep, value: operand.value))
125+
}
126+
return info
127+
}
128+
129+
func getYieldDependenceSources(beginApply: BeginApplyInst) -> LifetimeSourceInfo? {
130+
var info = LifetimeSourceInfo()
131+
let hasScopedYield = applySite.parameterOperands.contains {
132+
if let dep = applySite.resultDependence(on: $0) {
133+
return dep == .scope
134+
}
135+
return false
136+
}
137+
if hasScopedYield {
138+
// for consistency, we you yieldAddress if any yielded value is an address.
139+
let targetKind = beginApply.yieldedValues.contains(where: { $0.type.isAddress })
140+
? TargetKind.yieldAddress : TargetKind.yield
141+
info.sources.push(LifetimeSource(targetKind: targetKind, convention: .scope, value: beginApply.token))
123142
}
124143
for operand in applySite.parameterOperands {
125144
guard let dep = applySite.resultDependence(on: operand) else {
126145
continue
127146
}
128-
sources.sources.push(LifetimeSource(convention: dep, value: operand.value))
147+
switch dep {
148+
case .inherit:
149+
continue
150+
case .scope:
151+
for yieldedValue in beginApply.yieldedValues {
152+
let targetKind = yieldedValue.type.isAddress ? TargetKind.yieldAddress : TargetKind.yield
153+
info.sources.push(LifetimeSource(targetKind: targetKind, convention: .inherit, value: operand.value))
154+
}
155+
}
129156
}
130-
return sources
157+
return info
131158
}
132159

133-
func getParameterDependenceSources(target: Operand) -> LifetimeSources? {
160+
func getParameterDependenceSources(target: Operand) -> LifetimeSourceInfo? {
134161
guard let deps = applySite.parameterDependencies(target: target) else {
135162
return nil
136163
}
137-
var sources: LifetimeSources
138-
let convention = applySite.convention(of: target)!
139-
switch convention {
140-
case .indirectInout, .indirectInoutAliasable, .packInout:
141-
sources = LifetimeSources(targetKind: .inoutParameter)
142-
case .indirectIn, .indirectInGuaranteed, .indirectInCXX, .directOwned, .directUnowned, .directGuaranteed,
143-
.packOwned, .packGuaranteed:
144-
sources = LifetimeSources(targetKind: .inParameter)
145-
case .indirectOut, .packOut:
146-
debugLog("\(applySite)")
147-
fatalError("Lifetime dependencies cannot target \(convention) parameter")
148-
}
164+
var info = LifetimeSourceInfo()
165+
let targetKind = {
166+
let convention = applySite.convention(of: target)!
167+
switch convention {
168+
case .indirectInout, .indirectInoutAliasable, .packInout:
169+
return TargetKind.inoutParameter
170+
case .indirectIn, .indirectInGuaranteed, .indirectInCXX, .directOwned, .directUnowned, .directGuaranteed,
171+
.packOwned, .packGuaranteed:
172+
return TargetKind.inParameter
173+
case .indirectOut, .packOut:
174+
debugLog("\(applySite)")
175+
fatalError("Lifetime dependencies cannot target \(convention) parameter")
176+
}
177+
}()
149178
for (dep, operand) in zip(deps, applySite.parameterOperands) {
150179
guard let dep = dep else {
151180
continue
152181
}
153-
sources.sources.push(LifetimeSource(convention: dep, value: operand.value))
182+
info.sources.push(LifetimeSource(targetKind: targetKind, convention: dep, value: operand.value))
154183
}
155-
return sources
184+
return info
156185
}
186+
}
157187

158-
// Scoped dependencies require a mark_dependence for every variable that introduces this scope.
159-
//
160-
// Inherited dependencies do not require a mark_dependence if the target is a result or yielded value. The inherited
161-
// lifetime is nonescapable, so either
162-
//
163-
// (a) the result or yield is never returned from this function
164-
//
165-
// (b) the inherited lifetime has a dependence root within this function (it comes from a dependent function argument
166-
// or scoped dependence). In this case, when that depedence root is diagnosed, the analysis will find transtive uses
167-
// of this apply's result.
168-
//
169-
// (c) the dependent value is passed to another call with a dependent inout argument, or it is stored to a yielded
170-
// address of a coroutine that has a dependent inout argument. In this case, a mark_dependence will already be created
171-
// for that inout argument.
172-
//
173-
// Parameter dependencies and yielded addresses always require a mark_dependence.
174-
static func findDependenceBases(sources: LifetimeSources, _ context: FunctionPassContext) -> [Value] {
175-
var bases: [Value] = []
176-
for source in sources.sources {
188+
private extension LifetimeDependentApply.LifetimeSourceInfo {
189+
mutating func initializeBases(_ context: FunctionPassContext) {
190+
for source in sources {
191+
// Inherited dependencies do not require a mark_dependence if the target is a result or yielded value. The
192+
// inherited lifetime is nonescapable, so either
193+
//
194+
// (a) the result or yield is never returned from this function
195+
//
196+
// (b) the inherited lifetime has a dependence root within this function (it comes from a dependent function
197+
// argument or scoped dependence). In this case, when that depedence root is diagnosed, the analysis will find
198+
// transtive uses of this apply's result.
199+
//
200+
// (c) the dependent value is passed to another call with a dependent inout argument, or it is stored to a yielded
201+
// address of a coroutine that has a dependent inout argument. In this case, a mark_dependence will already be
202+
// created for that inout argument.
177203
switch source.convention {
178204
case .inherit:
179-
switch sources.targetKind {
180-
case .result, .yield:
181-
continue
182-
case .inParameter, .inoutParameter, .yieldAddress:
183-
_ = LifetimeDependence.visitDependenceRoots(enclosing: source.value, context) { scope in
184-
log("Inherited lifetime from \(source.value)")
185-
log(" scope: \(scope)")
186-
bases.append(scope.parentValue)
187-
return .continueWalk
188-
}
189-
}
205+
break
190206
case .scope:
191-
// Create a new dependence on the apply's access to the argument.
192-
for varIntoducer in gatherVariableIntroducers(for: source.value, context) {
193-
if let scope = LifetimeDependence.Scope(base: varIntoducer, context) {
194-
log("Scoped lifetime from \(source.value)")
195-
log(" scope: \(scope)")
196-
bases.append(scope.parentValue)
197-
}
207+
initializeScopedBases(source: source, context)
208+
}
209+
}
210+
}
211+
212+
// Scoped dependencies require a mark_dependence for every variable that introduces this scope.
213+
mutating func initializeScopedBases(source: LifetimeDependentApply.LifetimeSource, _ context: FunctionPassContext) {
214+
switch source.targetKind {
215+
case .yield, .yieldAddress:
216+
// A coroutine creates its own borrow scope, nested within its borrowed operand.
217+
bases.append(source.value)
218+
case .result, .inParameter, .inoutParameter:
219+
// Create a new dependence on the apply's access to the argument.
220+
for varIntoducer in gatherVariableIntroducers(for: source.value, context) {
221+
if let scope = LifetimeDependence.Scope(base: varIntoducer, context) {
222+
log("Scoped lifetime from \(source.value)")
223+
log(" scope: \(scope)")
224+
bases.append(scope.parentValue)
198225
}
199226
}
200227
}
201-
return bases
202228
}
203229
}
204230

@@ -207,16 +233,17 @@ extension LifetimeDependentApply {
207233
/// result on each argument so that the result is recognized as a
208234
/// dependent value within each scope.
209235
private func insertResultDependencies(for apply: LifetimeDependentApply, _ context: FunctionPassContext ) {
210-
guard let sources = apply.getResultDependenceSources() else {
236+
guard var sources = apply.getResultDependenceSources() else {
211237
return
212238
}
213239
log("Creating dependencies for \(apply.applySite)")
214240

215-
let bases = LifetimeDependentApply.findDependenceBases(sources: sources, context)
241+
// Find the dependence base for each source.
242+
sources.initializeBases(context)
216243

217244
for dependentValue in apply.applySite.resultOrYields {
218245
let builder = Builder(before: dependentValue.nextInstruction, context)
219-
insertMarkDependencies(value: dependentValue, initializer: nil, bases: bases, builder: builder, context)
246+
insertMarkDependencies(value: dependentValue, initializer: nil, bases: sources.bases, builder: builder, context)
220247
}
221248
for resultOper in apply.applySite.indirectResultOperands {
222249
let accessBase = resultOper.value.accessBase
@@ -231,23 +258,23 @@ private func insertResultDependencies(for apply: LifetimeDependentApply, _ conte
231258
}
232259
assert(initializingStore == resultOper.instruction, "an indirect result is a store")
233260
Builder.insert(after: apply.applySite, context) { builder in
234-
insertMarkDependencies(value: initialAddress, initializer: initializingStore, bases: bases, builder: builder,
235-
context)
261+
insertMarkDependencies(value: initialAddress, initializer: initializingStore, bases: sources.bases,
262+
builder: builder, context)
236263
}
237264
}
238265
}
239266

240267
private func insertParameterDependencies(apply: LifetimeDependentApply, target: Operand,
241268
_ context: FunctionPassContext ) {
242-
guard let sources = apply.getParameterDependenceSources(target: target) else {
269+
guard var sources = apply.getParameterDependenceSources(target: target) else {
243270
return
244271
}
245272
log("Creating dependencies for \(apply.applySite)")
246273

247-
let bases = LifetimeDependentApply.findDependenceBases(sources: sources, context)
274+
sources.initializeBases(context)
248275

249276
Builder.insert(after: apply.applySite, context) {
250-
insertMarkDependencies(value: target.value, initializer: nil, bases: bases, builder: $0, context)
277+
insertMarkDependencies(value: target.value, initializer: nil, bases: sources.bases, builder: $0, context)
251278
}
252279
}
253280

@@ -259,11 +286,18 @@ private func insertMarkDependencies(value: Value, initializer: Instruction?,
259286
let markDep = builder.createMarkDependence(
260287
value: currentValue, base: base, kind: .Unresolved)
261288

262-
let uses = currentValue.uses.lazy.filter {
263-
let inst = $0.instruction
264-
return inst != markDep && inst != initializer && !(inst is Deallocation)
289+
// Address dependencies cannot be represented as SSA values, so it doesn not make sense to replace any uses of the
290+
// dependent address. TODO: consider a separate mark_dependence_addr instruction since the semantics are different.
291+
if !value.type.isAddress {
292+
let uses = currentValue.uses.lazy.filter {
293+
if $0.isScopeEndingUse {
294+
return false
295+
}
296+
let inst = $0.instruction
297+
return inst != markDep && inst != initializer && !(inst is Deallocation)
298+
}
299+
uses.replaceAll(with: markDep, context)
265300
}
266-
uses.replaceAll(with: markDep, context)
267301
currentValue = markDep
268302
}
269303
}

0 commit comments

Comments
 (0)