Skip to content

Commit c4eab6a

Browse files
authored
Merge pull request #71266 from atrick/lifetime-insertion
Add the LifetimeDependenceInsertion pass.
2 parents 1713693 + 2e622cd commit c4eab6a

Some content is hidden

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

52 files changed

+1928
-667
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

SwiftCompilerSources/Sources/Optimizer/FunctionPasses/LifetimeDependenceDiagnostics.swift

Lines changed: 150 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -43,9 +43,22 @@ let lifetimeDependenceDiagnosticsPass = FunctionPass(
4343
}
4444
}
4545
for instruction in function.instructions {
46-
guard let markDep = instruction as? MarkDependenceInst else { continue }
47-
if let lifetimeDep = LifetimeDependence(markDep, context) {
48-
analyze(dependence: lifetimeDep, context)
46+
if let markDep = instruction as? MarkDependenceInst {
47+
if let lifetimeDep = LifetimeDependence(markDep, context) {
48+
analyze(dependence: lifetimeDep, context)
49+
}
50+
continue
51+
}
52+
if let apply = instruction as? FullApplySite {
53+
// Handle ~Escapable results that do not have a lifetime
54+
// dependence (@_unsafeNonescapableResult).
55+
apply.resultOrYields.forEach {
56+
if let lifetimeDep = LifetimeDependence(unsafeApplyResult: $0,
57+
context) {
58+
analyze(dependence: lifetimeDep, context)
59+
}
60+
}
61+
continue
4962
}
5063
}
5164
}
@@ -63,23 +76,35 @@ private func analyze(dependence: LifetimeDependence,
6376
var range = dependence.computeRange(context)
6477
defer { range?.deinitialize() }
6578

79+
var error = false
6680
let diagnostics =
67-
DiagnoseDependence(dependence: dependence, range: range, context: context)
81+
DiagnoseDependence(dependence: dependence, range: range,
82+
onError: { error = true }, context: context)
6883

6984
// Check each lifetime-dependent use via a def-use visitor
7085
var walker = DiagnoseDependenceWalker(diagnostics, context)
7186
defer { walker.deinitialize() }
72-
_ = walker.walkDown(root: dependence.parentValue)
87+
_ = walker.walkDown(root: dependence.dependentValue)
88+
89+
if !error {
90+
dependence.resolve(context)
91+
}
7392
}
7493

7594
/// Analyze and diagnose a single LifetimeDependence.
7695
private struct DiagnoseDependence {
7796
let dependence: LifetimeDependence
7897
let range: InstructionRange?
98+
let onError: ()->()
7999
let context: FunctionPassContext
80100

81101
var function: Function { dependence.function }
82102

103+
func diagnose(_ position: SourceLoc?, _ id: DiagID,
104+
_ args: DiagnosticArgument...) {
105+
context.diagnosticEngine.diagnose(position, id, args)
106+
}
107+
83108
/// Check that this use is inside the dependence scope.
84109
func checkInScope(operand: Operand) -> WalkResult {
85110
if let range, !range.inclusiveRangeContains(operand.instruction) {
@@ -102,45 +127,73 @@ private struct DiagnoseDependence {
102127
}
103128

104129
func checkFunctionResult(operand: Operand) -> WalkResult {
105-
// TODO: Get the argument dependence for this result. Check that it is the
106-
// same as the current dependence scope
107130

108131
if function.hasUnsafeNonEscapableResult {
109132
return .continueWalk
110133
}
111-
// TODO: Take ResultInfo as an argument and provide better
112-
// diagnostics for missing lifetime dependencies.
134+
// Allow returning an apply result (@_unsafeNonescapableResult) if
135+
// the calling function has a dependence. This implicitly makes
136+
// the unsafe nonescapable result dependent on the calling
137+
// function's lifetime dependence arguments.
138+
if dependence.isUnsafeApplyResult, function.hasResultDependence {
139+
return .continueWalk
140+
}
141+
// Check that the argument dependence for this result is the same
142+
// as the current dependence scope.
143+
if let arg = dependence.scope.parentValue as? FunctionArgument,
144+
function.argumentConventions[resultDependsOn: arg.index] != nil {
145+
// The returned value depends on a lifetime that is inherited or
146+
// borrowed in the caller. The lifetime of the argument value
147+
// itself is irrelevant here.
148+
return .continueWalk
149+
}
113150
reportEscaping(operand: operand)
114151
return .abortWalk
115152
}
116153

117154
func reportError(operand: Operand, diagID: DiagID) {
155+
onError()
156+
118157
// Identify the escaping variable.
119158
let escapingVar = LifetimeVariable(dependent: operand.value, context)
120159
let varName = escapingVar.name
121160
if let varName {
122-
context.diagnosticEngine.diagnose(escapingVar.sourceLoc,
123-
.lifetime_variable_outside_scope,
124-
varName)
161+
diagnose(escapingVar.sourceLoc, .lifetime_variable_outside_scope,
162+
varName)
125163
} else {
126-
context.diagnosticEngine.diagnose(escapingVar.sourceLoc,
127-
.lifetime_value_outside_scope)
128-
}
129-
// Identify the dependence scope.
130-
//
131-
// TODO: add bridging for function argument locations
132-
// [SILArgument.getDecl().getLoc()]
133-
//
134-
// TODO: For clear diagnostics: switch on dependence.scope.
135-
// For an access, report both the accessed variable, and the access.
136-
if let parentSourceLoc =
137-
dependence.parentValue.definingInstruction?.location.sourceLoc {
138-
context.diagnosticEngine.diagnose(parentSourceLoc,
139-
.lifetime_outside_scope_parent)
164+
diagnose(escapingVar.sourceLoc, .lifetime_value_outside_scope)
140165
}
166+
reportScope()
141167
// Identify the use point.
142168
let userSourceLoc = operand.instruction.location.sourceLoc
143-
context.diagnosticEngine.diagnose(userSourceLoc, diagID)
169+
diagnose(userSourceLoc, diagID)
170+
}
171+
172+
// Identify the dependence scope.
173+
func reportScope() {
174+
if case let .access(beginAccess) = dependence.scope {
175+
let parentVar = LifetimeVariable(dependent: beginAccess, context)
176+
if let sourceLoc = beginAccess.location.sourceLoc ?? parentVar.sourceLoc {
177+
diagnose(sourceLoc, .lifetime_outside_scope_access,
178+
parentVar.name ?? "")
179+
}
180+
return
181+
}
182+
if let arg = dependence.parentValue as? Argument,
183+
let varDecl = arg.varDecl,
184+
let sourceLoc = arg.sourceLoc {
185+
diagnose(sourceLoc, .lifetime_outside_scope_argument,
186+
varDecl.userFacingName)
187+
return
188+
}
189+
let parentVar = LifetimeVariable(dependent: dependence.parentValue, context)
190+
if let parentLoc = parentVar.sourceLoc {
191+
if let parentName = parentVar.name {
192+
diagnose(parentLoc, .lifetime_outside_scope_variable, parentName)
193+
} else {
194+
diagnose(parentLoc, .lifetime_outside_scope_value)
195+
}
196+
}
144197
}
145198
}
146199

@@ -170,18 +223,41 @@ private struct LifetimeVariable {
170223
return varDecl?.userFacingName
171224
}
172225

173-
init(introducer: Value) {
174-
if introducer.type.isAddress {
175-
switch introducer.enclosingAccessScope {
176-
case let .scope(beginAccess):
177-
// TODO: report both the access point and original variable.
178-
self = LifetimeVariable(introducer: beginAccess.operand.value)
179-
return
180-
case .base(_):
181-
// TODO: use an address walker to get the allocation point.
182-
break
183-
}
226+
init(dependent value: Value, _ context: some Context) {
227+
if value.type.isAddress {
228+
self = Self(accessBase: value.accessBase, context)
229+
return
230+
}
231+
if let firstIntroducer = getFirstBorrowIntroducer(of: value, context) {
232+
self = Self(introducer: firstIntroducer)
233+
return
184234
}
235+
self.varDecl = nil
236+
self.sourceLoc = nil
237+
}
238+
239+
// FUTURE: consider diagnosing multiple variable introducers. It's
240+
// unclear how more than one can happen.
241+
private func getFirstBorrowIntroducer(of value: Value,
242+
_ context: some Context)
243+
-> Value? {
244+
var introducers = Stack<Value>(context)
245+
gatherBorrowIntroducers(for: value, in: &introducers, context)
246+
return introducers.pop()
247+
}
248+
249+
private func getFirstLifetimeIntroducer(of value: Value,
250+
_ context: some Context)
251+
-> Value? {
252+
var introducer: Value?
253+
_ = visitLifetimeIntroducers(for: value, context) {
254+
introducer = $0
255+
return .abortWalk
256+
}
257+
return introducer
258+
}
259+
260+
private init(introducer: Value) {
185261
if let arg = introducer as? Argument {
186262
self.varDecl = arg.varDecl
187263
} else {
@@ -193,17 +269,42 @@ private struct LifetimeVariable {
193269
}
194270
}
195271

196-
init(dependent value: Value, _ context: Context) {
197-
// TODO: consider diagnosing multiple variable introducers. It's
198-
// unclear how more than one can happen.
199-
var introducers = Stack<Value>(context)
200-
gatherBorrowIntroducers(for: value, in: &introducers, context)
201-
if let firstIntroducer = introducers.pop() {
202-
self = LifetimeVariable(introducer: firstIntroducer)
203-
return
272+
// Record the source location of the variable decl if possible. The
273+
// caller will already have a source location for the formal access,
274+
// which is more relevant for diagnostics.
275+
private init(accessBase: AccessBase, _ context: some Context) {
276+
switch accessBase {
277+
case .box(let projectBox):
278+
if let box = getFirstLifetimeIntroducer(of: projectBox.box, context) {
279+
self = Self(introducer: box)
280+
}
281+
// We should always find an introducer since boxes are nontrivial.
282+
self.varDecl = nil
283+
self.sourceLoc = nil
284+
case .stack(let allocStack):
285+
self = Self(introducer: allocStack)
286+
case .global(let globalVar):
287+
self.varDecl = globalVar.varDecl
288+
self.sourceLoc = nil
289+
case .class(let refAddr):
290+
self.varDecl = refAddr.varDecl
291+
self.sourceLoc = refAddr.location.sourceLoc
292+
case .tail(let refTail):
293+
self = Self(introducer: refTail.instance)
294+
case .argument(let arg):
295+
self.varDecl = arg.varDecl
296+
self.sourceLoc = arg.sourceLoc
297+
case .yield(let result):
298+
// TODO: bridge VarDecl for FunctionConvention.Yields
299+
self.varDecl = nil
300+
self.sourceLoc = result.parentInstruction.location.sourceLoc
301+
case .pointer(let ptrToAddr):
302+
self.varDecl = nil
303+
self.sourceLoc = ptrToAddr.location.sourceLoc
304+
case .unidentified:
305+
self.varDecl = nil
306+
self.sourceLoc = nil
204307
}
205-
self.varDecl = nil
206-
self.sourceLoc = nil
207308
}
208309
}
209310

@@ -217,8 +318,8 @@ private struct LifetimeVariable {
217318
///
218319
/// TODO: handle stores to singly initialized temporaries like copies using a standard reaching-def analysis.
219320
private struct DiagnoseDependenceWalker {
220-
let diagnostics: DiagnoseDependence
221321
let context: Context
322+
var diagnostics: DiagnoseDependence
222323
var visitedValues: ValueSet
223324

224325
var function: Function { diagnostics.function }

0 commit comments

Comments
 (0)