diff --git a/SwiftCompilerSources/Sources/Optimizer/FunctionPasses/LifetimeDependenceDiagnostics.swift b/SwiftCompilerSources/Sources/Optimizer/FunctionPasses/LifetimeDependenceDiagnostics.swift index 145562136fc87..9c529995270a9 100644 --- a/SwiftCompilerSources/Sources/Optimizer/FunctionPasses/LifetimeDependenceDiagnostics.swift +++ b/SwiftCompilerSources/Sources/Optimizer/FunctionPasses/LifetimeDependenceDiagnostics.swift @@ -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) } } @@ -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 { @@ -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 { @@ -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 } diff --git a/SwiftCompilerSources/Sources/Optimizer/Utilities/LifetimeDependenceUtils.swift b/SwiftCompilerSources/Sources/Optimizer/Utilities/LifetimeDependenceUtils.swift index 355c1d33f0c53..b745fb5dff86a 100644 --- a/SwiftCompilerSources/Sources/Optimizer/Utilities/LifetimeDependenceUtils.swift +++ b/SwiftCompilerSources/Sources/Optimizer/Utilities/LifetimeDependenceUtils.swift @@ -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 @@ -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) @@ -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) diff --git a/test/SILOptimizer/lifetime_dependence/verify_diagnostics.swift b/test/SILOptimizer/lifetime_dependence/verify_diagnostics.swift index dffe8c34dc9fb..5cc4abab5e066 100644 --- a/test/SILOptimizer/lifetime_dependence/verify_diagnostics.swift +++ b/test/SILOptimizer/lifetime_dependence/verify_diagnostics.swift @@ -49,6 +49,17 @@ struct Holder { var c: C? = nil } +// Generic non-Escapable for indirect values. +struct GNE : ~Escapable { + let t: T + @lifetime(borrow t) + init(t: borrowing T) { self.t = copy t } +} + +@_silgen_name("forward") +@lifetime(copy arg) +func forward(_ arg: GNE) -> GNE + @_silgen_name("getGeneric") @lifetime(borrow holder) func getGeneric(_ holder: borrowing Holder, _: T.Type) -> T @@ -156,3 +167,26 @@ func testClosureCapture1(_ a: HasMethods) { } */ } + +// ============================================================================= +// Indirect ~Escapable results +// ============================================================================= + +@lifetime(copy arg1) +func testIndirectForwardedResult(arg1: GNE) -> GNE { + forward(arg1) +} + +@lifetime(copy arg1) +func testIndirectNonForwardedResult(arg1: GNE, arg2: GNE) -> GNE { + // 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(f: () -> GNE) -> GNE { + 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}} +}