Skip to content

Commit 274c478

Browse files
committed
LifetimeDepenenceDiagnostics improvements
Better handling of @_unsafeNonescapableResult Improve diagnostic clarity.
1 parent af39b0f commit 274c478

File tree

2 files changed

+146
-52
lines changed

2 files changed

+146
-52
lines changed

SwiftCompilerSources/Sources/Optimizer/FunctionPasses/LifetimeDependenceDiagnostics.swift

Lines changed: 137 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -52,8 +52,9 @@ let lifetimeDependenceDiagnosticsPass = FunctionPass(
5252
if let apply = instruction as? FullApplySite {
5353
// Handle ~Escapable results that do not have a lifetime
5454
// dependence (@_unsafeNonescapableResult).
55-
apply.dependentValues.forEach {
56-
if let lifetimeDep = LifetimeDependence(applyResult: $0, context) {
55+
apply.resultOrYields.forEach {
56+
if let lifetimeDep = LifetimeDependence(unsafeApplyResult: $0,
57+
context) {
5758
analyze(dependence: lifetimeDep, context)
5859
}
5960
}
@@ -75,23 +76,35 @@ private func analyze(dependence: LifetimeDependence,
7576
var range = dependence.computeRange(context)
7677
defer { range?.deinitialize() }
7778

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

8184
// Check each lifetime-dependent use via a def-use visitor
8285
var walker = DiagnoseDependenceWalker(diagnostics, context)
8386
defer { walker.deinitialize() }
84-
_ = walker.walkDown(root: dependence.parentValue)
87+
_ = walker.walkDown(root: dependence.dependentValue)
88+
89+
if !error {
90+
dependence.resolve(context)
91+
}
8592
}
8693

8794
/// Analyze and diagnose a single LifetimeDependence.
8895
private struct DiagnoseDependence {
8996
let dependence: LifetimeDependence
9097
let range: InstructionRange?
98+
let onError: ()->()
9199
let context: FunctionPassContext
92100

93101
var function: Function { dependence.function }
94102

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

116129
func checkFunctionResult(operand: Operand) -> WalkResult {
117-
// TODO: Get the argument dependence for this result. Check that it is the
118-
// same as the current dependence scope
119130

120131
if function.hasUnsafeNonEscapableResult {
121132
return .continueWalk
122133
}
123-
// TODO: Take ResultInfo as an argument and provide better
124-
// 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+
}
125150
reportEscaping(operand: operand)
126151
return .abortWalk
127152
}
128153

129154
func reportError(operand: Operand, diagID: DiagID) {
155+
onError()
156+
130157
// Identify the escaping variable.
131158
let escapingVar = LifetimeVariable(dependent: operand.value, context)
132159
let varName = escapingVar.name
133160
if let varName {
134-
context.diagnosticEngine.diagnose(escapingVar.sourceLoc,
135-
.lifetime_variable_outside_scope,
136-
varName)
161+
diagnose(escapingVar.sourceLoc, .lifetime_variable_outside_scope,
162+
varName)
137163
} else {
138-
context.diagnosticEngine.diagnose(escapingVar.sourceLoc,
139-
.lifetime_value_outside_scope)
140-
}
141-
// Identify the dependence scope.
142-
//
143-
// TODO: add bridging for function argument locations
144-
// [SILArgument.getDecl().getLoc()]
145-
//
146-
// TODO: For clear diagnostics: switch on dependence.scope.
147-
// For an access, report both the accessed variable, and the access.
148-
if let parentSourceLoc =
149-
dependence.parentValue.definingInstruction?.location.sourceLoc {
150-
context.diagnosticEngine.diagnose(parentSourceLoc,
151-
.lifetime_outside_scope_parent)
164+
diagnose(escapingVar.sourceLoc, .lifetime_value_outside_scope)
152165
}
166+
reportScope()
153167
// Identify the use point.
154168
let userSourceLoc = operand.instruction.location.sourceLoc
155-
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+
}
156197
}
157198
}
158199

@@ -182,18 +223,41 @@ private struct LifetimeVariable {
182223
return varDecl?.userFacingName
183224
}
184225

185-
init(introducer: Value) {
186-
if introducer.type.isAddress {
187-
switch introducer.enclosingAccessScope {
188-
case let .scope(beginAccess):
189-
// TODO: report both the access point and original variable.
190-
self = LifetimeVariable(introducer: beginAccess.operand.value)
191-
return
192-
case .base(_):
193-
// TODO: use an address walker to get the allocation point.
194-
break
195-
}
226+
init(dependent value: Value, _ context: some Context) {
227+
if value.type.isAddress {
228+
self = Self(accessBase: value.accessBase, context)
229+
return
196230
}
231+
if let firstIntroducer = getFirstBorrowIntroducer(of: value, context) {
232+
self = Self(introducer: firstIntroducer)
233+
return
234+
}
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) {
197261
if let arg = introducer as? Argument {
198262
self.varDecl = arg.varDecl
199263
} else {
@@ -205,17 +269,42 @@ private struct LifetimeVariable {
205269
}
206270
}
207271

208-
init(dependent value: Value, _ context: Context) {
209-
// TODO: consider diagnosing multiple variable introducers. It's
210-
// unclear how more than one can happen.
211-
var introducers = Stack<Value>(context)
212-
gatherBorrowIntroducers(for: value, in: &introducers, context)
213-
if let firstIntroducer = introducers.pop() {
214-
self = LifetimeVariable(introducer: firstIntroducer)
215-
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
216307
}
217-
self.varDecl = nil
218-
self.sourceLoc = nil
219308
}
220309
}
221310

@@ -229,8 +318,8 @@ private struct LifetimeVariable {
229318
///
230319
/// TODO: handle stores to singly initialized temporaries like copies using a standard reaching-def analysis.
231320
private struct DiagnoseDependenceWalker {
232-
let diagnostics: DiagnoseDependence
233321
let context: Context
322+
var diagnostics: DiagnoseDependence
234323
var visitedValues: ValueSet
235324

236325
var function: Function { diagnostics.function }

include/swift/AST/DiagnosticsSIL.def

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -958,9 +958,6 @@ NOTE(regionbasedisolation_named_stronglytransferred_binding, none,
958958
ERROR(deinit_not_visible, none,
959959
"deinit of non-copyable type not visible in the current module", ())
960960

961-
ERROR(lifetime_value_outside_scope, none,
962-
"lifetime-dependent value escapes its scope", ())
963-
964961
ERROR(vector_capacity_not_constant, none,
965962
"vector capacity needs to be constant", ())
966963

@@ -969,7 +966,15 @@ ERROR(fixed_arrays_not_available, none,
969966

970967
ERROR(lifetime_variable_outside_scope, none,
971968
"lifetime-dependent variable '%0' escapes its scope", (Identifier))
972-
NOTE(lifetime_outside_scope_parent, none,
969+
ERROR(lifetime_value_outside_scope, none,
970+
"lifetime-dependent value escapes its scope", ())
971+
NOTE(lifetime_outside_scope_argument, none,
972+
"it depends on the lifetime of argument '%0'", (Identifier))
973+
NOTE(lifetime_outside_scope_access, none,
974+
"it depends on this scoped access to variable '%0'", (Identifier))
975+
NOTE(lifetime_outside_scope_variable, none,
976+
"it depends on the lifetime of variable '%0'", (Identifier))
977+
NOTE(lifetime_outside_scope_value, none,
973978
"it depends on the lifetime of this parent value", ())
974979
NOTE(lifetime_outside_scope_use, none,
975980
"this use of the lifetime-dependent value is out of scope", ())

0 commit comments

Comments
 (0)