Skip to content

[6.2] LifetimeDependenceDiagnostics: diagnose indirect closure results. #81993

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -72,12 +72,16 @@ let lifetimeDependenceDiagnosticsPass = FunctionPass(
markDep.settleToEscaping()
continue
}
if let apply = instruction as? FullApplySite {
// Handle ~Escapable results that do not have a lifetime dependence. This includes implicit initializers and
// @_unsafeNonescapableResult.
if let apply = instruction as? FullApplySite, !apply.hasResultDependence {
// Handle ~Escapable results that do not have a lifetime dependence. This includes implicit initializers, calls to
// closures, and @_unsafeNonescapableResult.
apply.resultOrYields.forEach {
if let lifetimeDep = LifetimeDependence(unsafeApplyResult: $0,
context) {
if let lifetimeDep = LifetimeDependence(unsafeApplyResult: $0, apply: apply, context) {
_ = analyze(dependence: lifetimeDep, context)
}
}
apply.indirectResultOperands.forEach {
if let lifetimeDep = LifetimeDependence(unsafeApplyResult: $0.value, apply: apply, context) {
_ = analyze(dependence: lifetimeDep, context)
}
}
Expand Down Expand Up @@ -237,10 +241,12 @@ private struct DiagnoseDependence {
onError()

// Identify the escaping variable.
let escapingVar = LifetimeVariable(dependent: operand.value, context)
let escapingVar = LifetimeVariable(usedBy: operand, context)
if let varDecl = escapingVar.varDecl {
// Use the variable location, not the access location.
diagnose(varDecl.nameLoc, .lifetime_variable_outside_scope, escapingVar.name ?? "")
// Variable names like $return_value and $implicit_value don't have source locations.
let sourceLoc = varDecl.nameLoc ?? escapingVar.sourceLoc
diagnose(sourceLoc, .lifetime_variable_outside_scope, escapingVar.name ?? "")
} else if let sourceLoc = escapingVar.sourceLoc {
diagnose(sourceLoc, .lifetime_value_outside_scope)
} else {
Expand All @@ -263,7 +269,7 @@ private struct DiagnoseDependence {

// Identify the dependence scope. If no source location is found, bypass this diagnostic.
func reportScope() {
let parentVar = LifetimeVariable(dependent: dependence.parentValue, context)
let parentVar = LifetimeVariable(definedBy: dependence.parentValue, context)
// First check if the dependency is limited to an access scope. If the access has no source location then
// fall-through to report possible dependence on an argument.
if parentVar.isAccessScope, let accessLoc = parentVar.sourceLoc {
Expand Down Expand Up @@ -310,7 +316,22 @@ private struct LifetimeVariable {
return varDecl?.userFacingName
}

init(dependent value: Value, _ context: some Context) {
init(usedBy operand: Operand, _ context: some Context) {
self = .init(dependent: operand.value, context)
// variable names like $return_value and $implicit_value don't have source locations.
// For @out arguments, the operand's location is the best answer.
// Otherwise, fall back to the function's location.
self.sourceLoc = self.sourceLoc ?? operand.instruction.location.sourceLoc
?? operand.instruction.parentFunction.location.sourceLoc
}

init(definedBy value: Value, _ context: some Context) {
self = .init(dependent: value, context)
// Fall back to the function's location.
self.sourceLoc = self.sourceLoc ?? value.parentFunction.location.sourceLoc
}

private init(dependent value: Value, _ context: some Context) {
guard let introducer = getFirstVariableIntroducer(of: value, context) else {
return
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -165,16 +165,12 @@ extension LifetimeDependence {
//
// This is necessary because inserting a mark_dependence placeholder for such an unsafe dependence would illegally
// have the same base and value operand.
//
// TODO: handle indirect results
init?(unsafeApplyResult value: Value, _ context: some Context) {
init?(unsafeApplyResult value: Value, apply: FullApplySite, _ context: some Context) {
if value.isEscapable {
return nil
}
if (value.definingInstructionOrTerminator as! FullApplySite).hasResultDependence {
return nil
}
assert(value.ownership == .owned, "unsafe apply result must be owned")
assert(!apply.hasResultDependence, "mark_dependence should be used instead")
assert(value.ownership == .owned || value.type.isAddress, "unsafe apply result must be owned")
self.scope = Scope(base: value, context)
self.dependentValue = value
self.markDepInst = nil
Expand Down Expand Up @@ -578,8 +574,8 @@ extension LifetimeDependenceDefUseWalker {
}
let root = dependence.dependentValue
if root.type.isAddress {
// The root address may be an escapable mark_dependence that guards its address uses (unsafeAddress), or an
// allocation or incoming argument. In all these cases, it is sufficient to walk down the address uses.
// The root address may be an escapable mark_dependence that guards its address uses (unsafeAddress), an
// allocation, an incoming argument, or an outgoing argument. In all these cases, walk down the address uses.
return walkDownAddressUses(of: root)
}
return walkDownUses(of: root, using: nil)
Expand Down Expand Up @@ -974,7 +970,8 @@ extension LifetimeDependenceDefUseWalker {

private mutating func visitAppliedUse(of operand: Operand, by apply: FullApplySite) -> WalkResult {
if let conv = apply.convention(of: operand), conv.isIndirectOut {
return leafUse(of: operand)
// This apply initializes an allocation.
return dependentUse(of: operand, dependentAddress: operand.value)
}
if apply.isCallee(operand: operand) {
return leafUse(of: operand)
Expand Down
34 changes: 34 additions & 0 deletions test/SILOptimizer/lifetime_dependence/verify_diagnostics.swift
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,17 @@ struct Holder {
var c: C? = nil
}

// Generic non-Escapable for indirect values.
struct GNE<T> : ~Escapable {
let t: T
@lifetime(borrow t)
init(t: borrowing T) { self.t = copy t }
}

@_silgen_name("forward")
@lifetime(copy arg)
func forward<T>(_ arg: GNE<T>) -> GNE<T>

@_silgen_name("getGeneric")
@lifetime(borrow holder)
func getGeneric<T: ~Escapable>(_ holder: borrowing Holder, _: T.Type) -> T
Expand Down Expand Up @@ -156,3 +167,26 @@ func testClosureCapture1(_ a: HasMethods) {
}
*/
}

// =============================================================================
// Indirect ~Escapable results
// =============================================================================

@lifetime(copy arg1)
func testIndirectForwardedResult<T>(arg1: GNE<T>) -> GNE<T> {
forward(arg1)
}

@lifetime(copy arg1)
func testIndirectNonForwardedResult<T>(arg1: GNE<T>, arg2: GNE<T>) -> GNE<T> {
// expected-error @-1{{lifetime-dependent variable 'arg2' escapes its scope}}
// expected-note @-2{{it depends on the lifetime of argument 'arg2'}}
forward(arg2) // expected-note {{this use causes the lifetime-dependent value to escape}}
}

func testIndirectClosureResult<T>(f: () -> GNE<T>) -> GNE<T> {
f()
// expected-error @-1{{lifetime-dependent variable '$return_value' escapes its scope}}
// expected-note @-3{{it depends on the lifetime of argument '$return_value'}}
// expected-note @-3{{this use causes the lifetime-dependent value to escape}}
}