Skip to content

Commit 643cbd1

Browse files
authored
Merge pull request #78228 from atrick/lifedep-scopes-trivial
LifetimeDependence: redesign dependence scope handling
2 parents 22b31dc + c05ddd6 commit 643cbd1

Some content is hidden

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

44 files changed

+1393
-320
lines changed

SwiftCompilerSources/Sources/Optimizer/FunctionPasses/LifetimeDependenceDiagnostics.swift

Lines changed: 25 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -65,13 +65,16 @@ let lifetimeDependenceDiagnosticsPass = FunctionPass(
6565
// but the change won't appear in -sil-print-function. Ideally, we could notify context of a flag change
6666
// without invalidating analyses.
6767
lifetimeDep.resolve(context)
68+
continue
6869
}
69-
} else {
70-
// For now, if the mark_dependence wasn't recognized as a lifetime dependence, conservatively settle it as
71-
// escaping. In the future, we should not need this because, for escapable types, mark_dependence [unresolved]
72-
// will all be settled during an early LifetimeNormalization pass.
73-
markDep.settleToEscaping()
7470
}
71+
// For now, if the mark_dependence wasn't recognized as a lifetime dependency, or if the dependencies uses are not
72+
// in scope, conservatively settle it as escaping. For example, it is not uncommon for the pointer value returned
73+
// by `unsafeAddress` to outlive its `self` argument. This will not be diagnosed as an error, but the
74+
// mark_dependence will hanceforth be treated as an unknown use by the optimizer. In the future, we should not
75+
// need to set this flag during diagnostics because, for escapable types, mark_dependence [unresolved] will all be
76+
// settled during an early LifetimeNormalization pass.
77+
markDep.settleToEscaping()
7578
continue
7679
}
7780
if let apply = instruction as? FullApplySite {
@@ -97,7 +100,15 @@ let lifetimeDependenceDiagnosticsPass = FunctionPass(
97100
/// Return true on success.
98101
private func analyze(dependence: LifetimeDependence, _ context: FunctionPassContext) -> Bool {
99102
log("Dependence scope:\n\(dependence)")
100-
103+
104+
// Early versions of Span in the standard library violate trivial lifetimes. Contemporary versions of the compiler
105+
// simply ignored dependencies on trivial values.
106+
if !context.options.hasFeature(.LifetimeDependenceDiagnoseTrivial) {
107+
if dependence.parentValue.type.objectType.isTrivial(in: dependence.function) {
108+
return true
109+
}
110+
}
111+
101112
// Compute this dependence scope.
102113
var range = dependence.computeRange(context)
103114
defer { range?.deinitialize() }
@@ -110,8 +121,10 @@ private func analyze(dependence: LifetimeDependence, _ context: FunctionPassCont
110121
// Check each lifetime-dependent use via a def-use visitor
111122
var walker = DiagnoseDependenceWalker(diagnostics, context)
112123
defer { walker.deinitialize() }
113-
_ = walker.walkDown(root: dependence.dependentValue)
114-
return !error
124+
let result = walker.walkDown(root: dependence.dependentValue)
125+
// The walk may abort without a diagnostic error.
126+
assert(!error || result == .abortWalk)
127+
return result == .continueWalk
115128
}
116129

117130
/// Analyze and diagnose a single LifetimeDependence.
@@ -214,6 +227,10 @@ private struct DiagnoseDependence {
214227
}
215228

216229
func reportError(operand: Operand, diagID: DiagID) {
230+
// If the dependent value is Escapable, then mark_dependence resolution fails, but this is not a diagnostic error.
231+
if dependence.dependentValue.isEscapable {
232+
return
233+
}
217234
onError()
218235

219236
// Identify the escaping variable.

SwiftCompilerSources/Sources/Optimizer/FunctionPasses/LifetimeDependenceInsertion.swift

Lines changed: 114 additions & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -98,107 +98,132 @@ 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-
}
198-
}
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+
let scope = LifetimeDependence.Scope(base: varIntoducer, context)
222+
log("Scoped lifetime from \(source.value)")
223+
log(" scope: \(scope)")
224+
bases.append(scope.parentValue)
199225
}
200226
}
201-
return bases
202227
}
203228
}
204229

@@ -207,16 +232,17 @@ extension LifetimeDependentApply {
207232
/// result on each argument so that the result is recognized as a
208233
/// dependent value within each scope.
209234
private func insertResultDependencies(for apply: LifetimeDependentApply, _ context: FunctionPassContext ) {
210-
guard let sources = apply.getResultDependenceSources() else {
235+
guard var sources = apply.getResultDependenceSources() else {
211236
return
212237
}
213238
log("Creating dependencies for \(apply.applySite)")
214239

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

217243
for dependentValue in apply.applySite.resultOrYields {
218244
let builder = Builder(before: dependentValue.nextInstruction, context)
219-
insertMarkDependencies(value: dependentValue, initializer: nil, bases: bases, builder: builder, context)
245+
insertMarkDependencies(value: dependentValue, initializer: nil, bases: sources.bases, builder: builder, context)
220246
}
221247
for resultOper in apply.applySite.indirectResultOperands {
222248
let accessBase = resultOper.value.accessBase
@@ -231,23 +257,23 @@ private func insertResultDependencies(for apply: LifetimeDependentApply, _ conte
231257
}
232258
assert(initializingStore == resultOper.instruction, "an indirect result is a store")
233259
Builder.insert(after: apply.applySite, context) { builder in
234-
insertMarkDependencies(value: initialAddress, initializer: initializingStore, bases: bases, builder: builder,
235-
context)
260+
insertMarkDependencies(value: initialAddress, initializer: initializingStore, bases: sources.bases,
261+
builder: builder, context)
236262
}
237263
}
238264
}
239265

240266
private func insertParameterDependencies(apply: LifetimeDependentApply, target: Operand,
241267
_ context: FunctionPassContext ) {
242-
guard let sources = apply.getParameterDependenceSources(target: target) else {
268+
guard var sources = apply.getParameterDependenceSources(target: target) else {
243269
return
244270
}
245271
log("Creating dependencies for \(apply.applySite)")
246272

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

249275
Builder.insert(after: apply.applySite, context) {
250-
insertMarkDependencies(value: target.value, initializer: nil, bases: bases, builder: $0, context)
276+
insertMarkDependencies(value: target.value, initializer: nil, bases: sources.bases, builder: $0, context)
251277
}
252278
}
253279

@@ -259,11 +285,18 @@ private func insertMarkDependencies(value: Value, initializer: Instruction?,
259285
let markDep = builder.createMarkDependence(
260286
value: currentValue, base: base, kind: .Unresolved)
261287

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

0 commit comments

Comments
 (0)