Skip to content

Commit c8fcb72

Browse files
committed
LifetimeDependenceInsertion for inout dependencies.
1 parent 50ee84a commit c8fcb72

File tree

1 file changed

+131
-44
lines changed

1 file changed

+131
-44
lines changed

SwiftCompilerSources/Sources/Optimizer/FunctionPasses/LifetimeDependenceInsertion.swift

Lines changed: 131 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,10 @@ let lifetimeDependenceInsertionPass = FunctionPass(
4040

4141
for instruction in function.instructions {
4242
if let dependentApply = LifetimeDependentApply(instruction) {
43-
insertDependencies(for: dependentApply, context)
43+
for operand in dependentApply.applySite.parameterOperands {
44+
insertParameterDependencies(apply: dependentApply, target: operand, context)
45+
}
46+
insertResultDependencies(for: dependentApply, context)
4447
}
4548
}
4649
}
@@ -53,7 +56,7 @@ private struct LifetimeDependentApply {
5356
guard let apply = instruction as? FullApplySite else {
5457
return nil
5558
}
56-
if !apply.hasResultDependence {
59+
if !apply.hasLifetimeDependence {
5760
return nil
5861
}
5962
self.applySite = apply
@@ -85,50 +88,145 @@ private struct LifetimeDependentApply {
8588
}
8689

8790
extension LifetimeDependentApply {
88-
/// A lifetime argument copies, borrows, or mutatably borrows the
89-
/// lifetime of the argument value.
90-
struct LifetimeArgument {
91+
enum TargetKind {
92+
case result
93+
case inParameter
94+
case inoutParameter
95+
case yield
96+
case yieldAddress
97+
}
98+
99+
/// A lifetime argument that either inherits or creates a new scope for the lifetime of the argument value.
100+
struct LifetimeSource {
91101
let convention: LifetimeDependenceConvention
92102
let value: Value
93103
}
94104

95-
func getLifetimeArguments() -> SingleInlineArray<LifetimeArgument> {
96-
var args = SingleInlineArray<LifetimeArgument>()
105+
/// List of lifetime dependencies for a single target.
106+
struct LifetimeSources {
107+
let targetKind: TargetKind
108+
var sources = SingleInlineArray<LifetimeSource>()
109+
}
110+
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)
120+
}
121+
default:
122+
sources = LifetimeSources(targetKind: .result)
123+
}
97124
for operand in applySite.parameterOperands {
98125
guard let dep = applySite.resultDependence(on: operand) else {
99126
continue
100127
}
101-
args.push(LifetimeArgument(convention: dep, value: operand.value))
128+
sources.sources.push(LifetimeSource(convention: dep, value: operand.value))
129+
}
130+
return sources
131+
}
132+
133+
func getParameterDependenceSources(target: Operand) -> LifetimeSources? {
134+
guard let deps = applySite.parameterDependencies(target: target) else {
135+
return nil
136+
}
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+
}
149+
for (dep, operand) in zip(deps, applySite.parameterOperands) {
150+
guard let dep = dep else {
151+
continue
152+
}
153+
sources.sources.push(LifetimeSource(convention: dep, value: operand.value))
154+
}
155+
return sources
156+
}
157+
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 {
177+
switch source.convention {
178+
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+
}
190+
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+
}
198+
}
199+
}
102200
}
103-
return args
201+
return bases
104202
}
105203
}
106204

107205
/// If the result of this apply depends on the scope of one or more
108206
/// arguments, then insert a mark_dependence [unresolved] from the
109207
/// result on each argument so that the result is recognized as a
110208
/// dependent value within each scope.
111-
private func insertDependencies(for apply: LifetimeDependentApply,
112-
_ context: FunctionPassContext ) {
113-
let bases = findDependenceBases(of: apply, context)
209+
private func insertResultDependencies(for apply: LifetimeDependentApply, _ context: FunctionPassContext ) {
210+
guard let sources = apply.getResultDependenceSources() else {
211+
return
212+
}
213+
log("Creating dependencies for \(apply.applySite)")
214+
215+
let bases = LifetimeDependentApply.findDependenceBases(sources: sources, context)
216+
114217
for dependentValue in apply.applySite.resultOrYields {
115218
let builder = Builder(before: dependentValue.nextInstruction, context)
116-
insertMarkDependencies(value: dependentValue, initializer: nil,
117-
bases: bases, builder: builder, context)
219+
insertMarkDependencies(value: dependentValue, initializer: nil, bases: bases, builder: builder, context)
118220
}
119221
for resultOper in apply.applySite.indirectResultOperands {
120222
let accessBase = resultOper.value.accessBase
121-
guard let (initialAddress, initializingStore) =
122-
accessBase.findSingleInitializer(context) else {
223+
guard let (initialAddress, initializingStore) = accessBase.findSingleInitializer(context) else {
123224
continue
124225
}
125-
// TODO: This is currently too strict for a diagnostic pass. We
126-
// should handle/cleanup projections and casts that occur before
127-
// the initializingStore. Or check in the SIL verifier that all
128-
// stores without an access scope follow this form. Then convert
129-
// this bail-out to an assert.
130-
guard initialAddress.usesOccurOnOrAfter(instruction: initializingStore,
131-
context) else {
226+
// TODO: This might bail-out on SIL that should be diagnosed. We should handle/cleanup projections and casts that
227+
// occur before the initializingStore. Or check in the SIL verifier that all stores without an access scope follow
228+
// this form. Then convert this bail-out to an assert.
229+
guard initialAddress.usesOccurOnOrAfter(instruction: initializingStore, context) else {
132230
continue
133231
}
134232
assert(initializingStore == resultOper.instruction, "an indirect result is a store")
@@ -139,29 +237,18 @@ private func insertDependencies(for apply: LifetimeDependentApply,
139237
}
140238
}
141239

142-
private func findDependenceBases(of apply: LifetimeDependentApply,
143-
_ context: FunctionPassContext)
144-
-> [Value] {
240+
private func insertParameterDependencies(apply: LifetimeDependentApply, target: Operand,
241+
_ context: FunctionPassContext ) {
242+
guard let sources = apply.getParameterDependenceSources(target: target) else {
243+
return
244+
}
145245
log("Creating dependencies for \(apply.applySite)")
146-
var bases: [Value] = []
147-
for lifetimeArg in apply.getLifetimeArguments() {
148-
switch lifetimeArg.convention {
149-
case .inherit:
150-
continue
151-
case .scope:
152-
// Create a new dependence on the apply's access to the argument.
153-
for varIntoducer in gatherVariableIntroducers(for: lifetimeArg.value,
154-
context) {
155-
if let scope =
156-
LifetimeDependence.Scope(base: varIntoducer, context) {
157-
log("Scoped lifetime from \(lifetimeArg.value)")
158-
log(" scope: \(scope)")
159-
bases.append(scope.parentValue)
160-
}
161-
}
162-
}
246+
247+
let bases = LifetimeDependentApply.findDependenceBases(sources: sources, context)
248+
249+
Builder.insert(after: apply.applySite, context) {
250+
insertMarkDependencies(value: target.value, initializer: nil, bases: bases, builder: $0, context)
163251
}
164-
return bases
165252
}
166253

167254
private func insertMarkDependencies(value: Value, initializer: Instruction?,

0 commit comments

Comments
 (0)