Skip to content

Commit 3efcc14

Browse files
authored
Merge pull request #81192 from atrick/62-var-walker-recursion
[6.2] LifetimeDependenceDiagnostics: bug fixes and output clarity
2 parents 62cfed6 + 7756a32 commit 3efcc14

File tree

14 files changed

+338
-93
lines changed

14 files changed

+338
-93
lines changed

SwiftCompilerSources/Sources/Optimizer/FunctionPasses/LifetimeDependenceDiagnostics.swift

Lines changed: 77 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -238,37 +238,53 @@ private struct DiagnoseDependence {
238238

239239
// Identify the escaping variable.
240240
let escapingVar = LifetimeVariable(dependent: operand.value, context)
241-
let varName = escapingVar.name
242-
if let varName {
243-
diagnose(escapingVar.sourceLoc, .lifetime_variable_outside_scope,
244-
varName)
241+
if let varDecl = escapingVar.varDecl {
242+
// Use the variable location, not the access location.
243+
diagnose(varDecl.nameLoc, .lifetime_variable_outside_scope, escapingVar.name ?? "")
244+
} else if let sourceLoc = escapingVar.sourceLoc {
245+
diagnose(sourceLoc, .lifetime_value_outside_scope)
245246
} else {
246-
diagnose(escapingVar.sourceLoc, .lifetime_value_outside_scope)
247+
// Always raise an error even if we can't find a source location.
248+
let sourceLoc = function.location.sourceLoc
249+
if let accessorKind = escapingVar.accessorKind {
250+
diagnose(sourceLoc, .lifetime_value_outside_accessor, accessorKind)
251+
} else {
252+
// Thunks do not have a source location, but we try to use the function location anyway.
253+
let thunkSelect = dependence.function.thunkKind == .noThunk ? 0 : 1
254+
diagnose(sourceLoc, .lifetime_value_outside_thunk, thunkSelect, function.name)
255+
}
247256
}
248257
reportScope()
249258
// Identify the use point.
250-
let userSourceLoc = operand.instruction.location.sourceLoc
251-
diagnose(userSourceLoc, diagID)
259+
if let userSourceLoc = operand.instruction.location.sourceLoc {
260+
diagnose(userSourceLoc, diagID)
261+
}
252262
}
253263

254-
// Identify the dependence scope.
264+
// Identify the dependence scope. If no source location is found, bypass this diagnostic.
255265
func reportScope() {
256-
if case let .access(beginAccess) = dependence.scope {
257-
let parentVar = LifetimeVariable(dependent: beginAccess, context)
258-
if let sourceLoc = beginAccess.location.sourceLoc ?? parentVar.sourceLoc {
259-
diagnose(sourceLoc, .lifetime_outside_scope_access,
260-
parentVar.name ?? "")
261-
}
266+
let parentVar = LifetimeVariable(dependent: dependence.parentValue, context)
267+
// First check if the dependency is limited to an access scope. If the access has no source location then
268+
// fall-through to report possible dependence on an argument.
269+
if parentVar.isAccessScope, let accessLoc = parentVar.sourceLoc {
270+
diagnose(accessLoc, .lifetime_outside_scope_access, parentVar.name ?? "")
262271
return
263272
}
264-
if let arg = dependence.parentValue as? Argument,
265-
let varDecl = arg.varDecl,
266-
let sourceLoc = arg.sourceLoc {
267-
diagnose(sourceLoc, .lifetime_outside_scope_argument,
268-
varDecl.userFacingName)
273+
// If the argument does not have a source location (e.g. a synthesized accessor), report the function location. The
274+
// function's source location is sufficient for argument diagnostics, but if the function has no location, don't
275+
// report any scope.
276+
if parentVar.isArgument, let argLoc = parentVar.sourceLoc ?? function.location.sourceLoc {
277+
if parentVar.isClosureCapture {
278+
diagnose(argLoc, .lifetime_outside_scope_capture)
279+
} else if let parentName = parentVar.name {
280+
diagnose(argLoc, .lifetime_outside_scope_argument, parentName)
281+
} else {
282+
diagnose(argLoc, .lifetime_outside_scope_synthesized_argument, parentVar.accessorKind ?? function.name)
283+
}
269284
return
270285
}
271-
let parentVar = LifetimeVariable(dependent: dependence.parentValue, context)
286+
// Now diagnose dependencies on regular variable and value scopes.
287+
// Thunks do not have a function location, so any scopes inside the thunk will be ignored.
272288
if let parentLoc = parentVar.sourceLoc {
273289
if let parentName = parentVar.name {
274290
diagnose(parentLoc, .lifetime_outside_scope_variable, parentName)
@@ -282,24 +298,35 @@ private struct DiagnoseDependence {
282298
// Identify a best-effort variable declaration based on a defining SIL
283299
// value or any lifetime dependent use of that SIL value.
284300
private struct LifetimeVariable {
285-
var varDecl: VarDecl?
286-
var sourceLoc: SourceLoc?
301+
var varDecl: VarDecl? = nil
302+
var sourceLoc: SourceLoc? = nil
303+
var isAccessScope: Bool = false
304+
var isArgument: Bool = false
305+
var isClosureCapture: Bool = false
306+
var accessorKind: String?
307+
var thunkKind: Function.ThunkKind = .noThunk
287308

288309
var name: StringRef? {
289310
return varDecl?.userFacingName
290311
}
291312

292313
init(dependent value: Value, _ context: some Context) {
293-
if value.type.isAddress {
294-
self = Self(accessBase: value.accessBase, context)
314+
guard let introducer = getFirstVariableIntroducer(of: value, context) else {
295315
return
296316
}
297-
if let firstIntroducer = getFirstVariableIntroducer(of: value, context) {
298-
self = Self(introducer: firstIntroducer)
317+
if introducer.type.isAddress {
318+
if let beginAccess = introducer as? BeginAccessInst {
319+
// Recurse through beginAccess to find the variable introducer rather than the variable access.
320+
self = .init(dependent: beginAccess.address, context)
321+
self.isAccessScope = true
322+
// However, remember source location of the innermost access.
323+
self.sourceLoc = beginAccess.location.sourceLoc ?? self.sourceLoc
324+
return
325+
}
326+
self = .init(accessBase: introducer.accessBase, context)
299327
return
300328
}
301-
self.varDecl = nil
302-
self.sourceLoc = nil
329+
self = Self(introducer: introducer, context)
303330
}
304331

305332
private func getFirstVariableIntroducer(of value: Value, _ context: some Context) -> Value? {
@@ -313,15 +340,22 @@ private struct LifetimeVariable {
313340
return introducer
314341
}
315342

316-
private init(introducer: Value) {
317-
if let arg = introducer as? Argument {
343+
private init(introducer: Value, _ context: some Context) {
344+
if let arg = introducer as? FunctionArgument {
318345
self.varDecl = arg.varDecl
319-
} else {
320-
self.sourceLoc = introducer.definingInstruction?.location.sourceLoc
321-
self.varDecl = introducer.definingInstruction?.findVarDecl()
346+
self.sourceLoc = arg.sourceLoc
347+
self.isArgument = true
348+
self.isClosureCapture = arg.isClosureCapture
349+
return
322350
}
323-
if let varDecl {
324-
sourceLoc = varDecl.nameLoc
351+
if let varDecl = introducer.definingInstruction?.findVarDecl() {
352+
self.varDecl = varDecl
353+
self.sourceLoc = varDecl.nameLoc
354+
} else if let sourceLoc = introducer.definingInstruction?.location.sourceLoc {
355+
self.sourceLoc = sourceLoc
356+
} else {
357+
self.accessorKind = introducer.parentFunction.accessorKindName
358+
self.thunkKind = introducer.parentFunction.thunkKind
325359
}
326360
}
327361

@@ -335,32 +369,27 @@ private struct LifetimeVariable {
335369
// never be produced by one of these, except when it is redundant with the `alloc_box` VarDecl. It does not seem
336370
// possible for a box to be moved/borrowed directly into another variable's box. Reassignment always loads/stores
337371
// the value.
338-
self = Self(introducer: projectBox.box.referenceRoot)
372+
self = .init(introducer: projectBox.box.referenceRoot, context)
339373
case .stack(let allocStack):
340-
self = Self(introducer: allocStack)
374+
self = .init(introducer: allocStack, context)
341375
case .global(let globalVar):
342376
self.varDecl = globalVar.varDecl
343377
self.sourceLoc = varDecl?.nameLoc
344378
case .class(let refAddr):
345-
self.varDecl = refAddr.varDecl
346-
self.sourceLoc = refAddr.location.sourceLoc
379+
self = .init(introducer: refAddr, context)
347380
case .tail(let refTail):
348-
self = Self(introducer: refTail.instance)
381+
self = .init(introducer: refTail.instance, context)
349382
case .argument(let arg):
350-
self.varDecl = arg.varDecl
351-
self.sourceLoc = arg.sourceLoc
383+
self = .init(introducer: arg, context)
352384
case .yield(let result):
353385
// TODO: bridge VarDecl for FunctionConvention.Yields
354-
self.varDecl = nil
355-
self.sourceLoc = result.parentInstruction.location.sourceLoc
386+
self = .init(introducer: result, context)
356387
case .storeBorrow(let sb):
357388
self = .init(dependent: sb.source, context)
358389
case .pointer(let ptrToAddr):
359-
self.varDecl = nil
360-
self.sourceLoc = ptrToAddr.location.sourceLoc
390+
self = .init(introducer: ptrToAddr, context)
361391
case .index, .unidentified:
362-
self.varDecl = nil
363-
self.sourceLoc = nil
392+
break
364393
}
365394
}
366395
}

SwiftCompilerSources/Sources/Optimizer/FunctionPasses/LifetimeDependenceInsertion.swift

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -300,8 +300,8 @@ private func insertMarkDependencies(value: Value, initializer: Instruction?,
300300
}
301301
}
302302

303-
/// Walk up the value dependence chain to find the best-effort variable declaration. Typically called while diagnosing
304-
/// an error.
303+
/// Walk up the value dependence chain to find the best-effort variable declaration. Used to find the source of a borrow
304+
/// dependence or to print the source variable in a diagnostic message.
305305
///
306306
/// Returns an array with at least one introducer value.
307307
///
@@ -440,11 +440,12 @@ struct VariableIntroducerUseDefWalker : LifetimeDependenceUseDefValueWalker, Lif
440440

441441
/// Override to check for on-stack variables before following an initializer.
442442
mutating func walkUp(address: Value, access: AccessBaseAndScopes) -> WalkResult {
443-
// Check for stack locations that correspond to an lvalue.
444-
if case let .stack(allocStack) = access.base {
445-
if allocStack.varDecl != nil {
446-
// Report this variable's innermmost access scope.
447-
return addressIntroducer(access.enclosingAccess.address ?? address, access: access)
443+
// Check for stack locations that correspond to an lvalue if there isn't any nested access scope.
444+
if access.innermostAccess == nil {
445+
if case let .stack(allocStack) = access.base {
446+
if allocStack.varDecl != nil {
447+
return addressIntroducer(allocStack, access: access)
448+
}
448449
}
449450
}
450451
return walkUpDefault(address: address, access: access)

SwiftCompilerSources/Sources/Optimizer/Utilities/LifetimeDependenceUtils.swift

Lines changed: 20 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1221,6 +1221,24 @@ extension LifetimeDependenceUseDefAddressWalker {
12211221
}
12221222

12231223
mutating func walkUpDefault(address: Value, access: AccessBaseAndScopes) -> WalkResult {
1224+
if let beginAccess = access.innermostAccess {
1225+
// Skip the access scope for unsafe[Mutable]Address. Treat it like a projection of 'self' rather than a separate
1226+
// variable access.
1227+
if let addressorSelf = beginAccess.unsafeAddressorSelf {
1228+
return walkUp(newLifetime: addressorSelf)
1229+
}
1230+
// Ignore the acces scope for trivial values regardless of whether it is singly-initialized. Trivial values do not
1231+
// need to be kept alive in memory and can be safely be overwritten in the same scope. Lifetime dependence only
1232+
// cares that the loaded value is within the lexical scope of the trivial value's variable declaration. Rather
1233+
// than skipping all access scopes, call 'walkUp' on each nested access in case one of them needs to redirect the
1234+
// walk, as required for 'access.unsafeAddressorSelf' or in case the implementation overrides certain accesses.
1235+
if isTrivialScope {
1236+
return walkUp(newLifetime: beginAccess.address)
1237+
}
1238+
// Generally assume an access scope introduces a variable borrow scope. And generally ignore address forwarding
1239+
// mark_dependence.
1240+
return addressIntroducer(beginAccess, access: access)
1241+
}
12241242
// Continue walking for some kinds of access base.
12251243
switch access.base {
12261244
case .box, .global, .class, .tail, .pointer, .index, .unidentified:
@@ -1229,14 +1247,9 @@ extension LifetimeDependenceUseDefAddressWalker {
12291247
// Ignore stack locations. Their access scopes do not affect lifetime dependence.
12301248
return walkUp(stackInitializer: allocStack, at: address, access: access)
12311249
case let .argument(arg):
1232-
// Ignore access scopes for @in or @in_guaranteed arguments when all scopes are reads. Do not ignore a [read]
1233-
// access of an inout argument or outer [modify]. Mutation later with the outer scope could invalidate the
1234-
// borrowed state in this narrow scope. Do not ignore any mark_depedence on the address.
1235-
if arg.convention.isIndirectIn && access.isOnlyReadAccess {
1250+
if arg.convention.isExclusiveIndirect {
12361251
return addressIntroducer(arg, access: access)
12371252
}
1238-
// @inout arguments may be singly initialized (when no modification exists in this function), but this is not
1239-
// relevant here because they require nested access scopes which can never be ignored.
12401253
case let .yield(yieldedAddress):
12411254
// Ignore access scopes for @in or @in_guaranteed yields when all scopes are reads.
12421255
let apply = yieldedAddress.definingInstruction as! FullApplySite
@@ -1250,27 +1263,6 @@ extension LifetimeDependenceUseDefAddressWalker {
12501263
return walkUp(newLifetime: sb.source)
12511264
}
12521265
}
1253-
// Skip the access scope for unsafe[Mutable]Address. Treat it like a projection of 'self' rather than a separate
1254-
// variable access.
1255-
if case let .access(innerAccess) = access.scopes.first,
1256-
let addressorSelf = innerAccess.unsafeAddressorSelf {
1257-
return walkUp(newLifetime: addressorSelf)
1258-
}
1259-
// Ignore the acces scope for trivial values regardless of whether it is singly-initialized. Trivial values do not
1260-
// need to be kept alive in memory and can be safely be overwritten in the same scope. Lifetime dependence only
1261-
// cares that the loaded value is within the lexical scope of the trivial value's variable declaration. Rather than
1262-
// skipping all access scopes, call 'walkUp' on each nested access in case one of them needs to redirect the walk,
1263-
// as required for 'access.unsafeAddressorSelf'.
1264-
if isTrivialScope {
1265-
switch access.scopes.first {
1266-
case .none, .base:
1267-
break
1268-
case let .access(beginAccess):
1269-
return walkUp(newLifetime: beginAccess.address)
1270-
case let .dependence(markDep):
1271-
return walkUp(newLifetime: markDep.value)
1272-
}
1273-
}
12741266
return addressIntroducer(access.enclosingAccess.address ?? address, access: access)
12751267
}
12761268

@@ -1285,7 +1277,7 @@ extension LifetimeDependenceUseDefAddressWalker {
12851277
case let store as StoringInstruction:
12861278
return walkUp(newLifetime: store.source)
12871279
case let srcDestInst as SourceDestAddrInstruction:
1288-
return walkUp(newLifetime: srcDestInst.destination)
1280+
return walkUp(newLifetime: srcDestInst.source)
12891281
case let apply as FullApplySite:
12901282
if let f = apply.referencedFunction, f.isConvertPointerToPointerArgument {
12911283
return walkUp(newLifetime: apply.parameterOperands[0].value)

SwiftCompilerSources/Sources/SIL/Argument.swift

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,12 @@ public class Argument : Value, Hashable {
3434

3535
public var isLexical: Bool { false }
3636

37-
public var varDecl: VarDecl? { bridged.getVarDecl().getAs(VarDecl.self) }
37+
public var varDecl: VarDecl? {
38+
if let varDecl = bridged.getVarDecl().getAs(VarDecl.self) {
39+
return varDecl
40+
}
41+
return debugUserDecl
42+
}
3843

3944
public var sourceLoc: SourceLoc? { varDecl?.nameLoc }
4045

@@ -56,6 +61,10 @@ final public class FunctionArgument : Argument {
5661
bridged.FunctionArgument_isLexical()
5762
}
5863

64+
public var isClosureCapture: Bool {
65+
bridged.FunctionArgument_isClosureCapture()
66+
}
67+
5968
public var isSelf: Bool {
6069
parentFunction.argumentConventions.selfIndex == index
6170
}

SwiftCompilerSources/Sources/SIL/Function.swift

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -219,6 +219,13 @@ final public class Function : CustomStringConvertible, HasShortDescription, Hash
219219
}
220220
}
221221

222+
public var accessorKindName: String? {
223+
guard bridged.isAccessor() else {
224+
return nil
225+
}
226+
return StringRef(bridged: bridged.getAccessorName()).string
227+
}
228+
222229
/// True, if the function runs with a swift 5.1 runtime.
223230
/// Note that this is function specific, because inlinable functions are de-serialized
224231
/// in a client module, which might be compiled with a different deployment target.

SwiftCompilerSources/Sources/SIL/Instruction.swift

Lines changed: 23 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -452,7 +452,7 @@ public enum VariableScopeInstruction {
452452

453453
// TODO: with SIL verification, we might be able to make varDecl non-Optional.
454454
public var varDecl: VarDecl? {
455-
if let debugVarDecl = instruction.debugVarDecl {
455+
if let debugVarDecl = instruction.debugResultDecl {
456456
return debugVarDecl
457457
}
458458
// SILGen may produce double var_decl instructions for the same variable:
@@ -474,15 +474,31 @@ extension Instruction {
474474
if let varScopeInst = VariableScopeInstruction(self) {
475475
return varScopeInst.varDecl
476476
}
477-
return debugVarDecl
477+
return debugResultDecl
478478
}
479479

480-
var debugVarDecl: VarDecl? {
480+
var debugResultDecl: VarDecl? {
481481
for result in results {
482-
for use in result.uses {
483-
if let debugVal = use.instruction as? DebugValueInst {
484-
return debugVal.varDecl
485-
}
482+
if let varDecl = result.debugUserDecl {
483+
return varDecl
484+
}
485+
}
486+
return nil
487+
}
488+
}
489+
490+
extension Value {
491+
var debugValDecl: VarDecl? {
492+
if let arg = self as? Argument {
493+
return arg.varDecl
494+
}
495+
return debugUserDecl
496+
}
497+
498+
var debugUserDecl: VarDecl? {
499+
for use in uses {
500+
if let debugVal = use.instruction as? DebugValueInst {
501+
return debugVal.varDecl
486502
}
487503
}
488504
return nil

include/swift/AST/ASTBridging.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -393,6 +393,7 @@ struct BridgedDeclObj {
393393
SWIFT_IMPORT_UNSAFE BRIDGED_INLINE BridgedDeclObj Class_getDestructor() const;
394394
BRIDGED_INLINE bool AbstractFunction_isOverridden() const;
395395
BRIDGED_INLINE bool Destructor_isIsolated() const;
396+
SWIFT_IMPORT_UNSAFE BRIDGED_INLINE BridgedStringRef AccessorDecl_getKindName() const;
396397
};
397398

398399
enum ENUM_EXTENSIBILITY_ATTR(closed) BridgedASTNodeKind : uint8_t {

0 commit comments

Comments
 (0)