Skip to content

Commit c25d463

Browse files
authored
Merge pull request #81105 from atrick/rdar146319009-borrow-constraint
Fix LifetimeDependenceDiagnostics: scoped dependence on a copy
2 parents 771cb35 + 8a48cd9 commit c25d463

19 files changed

+276
-36
lines changed

SwiftCompilerSources/Sources/Optimizer/FunctionPasses/LifetimeDependenceDiagnostics.swift

Lines changed: 24 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ let lifetimeDependenceDiagnosticsPass = FunctionPass(
5353
}
5454
}
5555
for instruction in function.instructions {
56-
if let markDep = instruction as? MarkDependenceInst, markDep.isUnresolved {
56+
if let markDep = instruction as? MarkDependenceInstruction, markDep.isUnresolved {
5757
if let lifetimeDep = LifetimeDependence(markDep, context) {
5858
if analyze(dependence: lifetimeDep, context) {
5959
// Note: This promotes the mark_dependence flag but does not invalidate analyses; preserving analyses is good,
@@ -202,12 +202,29 @@ private struct DiagnoseDependence {
202202
// Check that the parameter dependence for this result is the same
203203
// as the current dependence scope.
204204
if let arg = dependence.scope.parentValue as? FunctionArgument,
205-
function.argumentConventions[resultDependsOn: arg.index] != nil {
206-
// The returned value depends on a lifetime that is inherited or
207-
// borrowed in the caller. The lifetime of the argument value
208-
// itself is irrelevant here.
209-
log(" has dependent function result")
210-
return .continueWalk
205+
let argDep = function.argumentConventions[resultDependsOn: arg.index] {
206+
switch argDep {
207+
case .inherit:
208+
if dependence.markDepInst != nil {
209+
// A mark_dependence represents a "borrow" scope. A local borrow scope cannot inherit the caller's dependence
210+
// because the borrow scope depends on the argument value itself, while the caller allows the result to depend
211+
// on a value that the argument was copied from.
212+
break
213+
}
214+
fallthrough
215+
case .scope:
216+
// The returned value depends on a lifetime that is inherited or
217+
// borrowed in the caller. The lifetime of the argument value
218+
// itself is irrelevant here.
219+
log(" has dependent function result")
220+
return .continueWalk
221+
}
222+
// Briefly (April 2025), RawSpan._extracting, Span._extracting, and UTF8Span.span returned a borrowed value that
223+
// depended on a copied argument. Continue to support those interfaces. The implementations were correct but
224+
// needed an explicit _overrideLifetime.
225+
if let sourceFileKind = dependence.function.sourceFileKind, sourceFileKind == .interface {
226+
return .continueWalk
227+
}
211228
}
212229
return .abortWalk
213230
}

SwiftCompilerSources/Sources/Optimizer/FunctionPasses/LifetimeDependenceScopeFixup.swift

Lines changed: 17 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,10 @@ let lifetimeDependenceScopeFixupPass = FunctionPass(
119119
// Recursively sink enclosing end_access, end_borrow, end_apply, and destroy_value. If the scope can be extended
120120
// into the caller, return the function arguments that are the dependency sources.
121121
var scopeExtension = ScopeExtension(localReachabilityCache, context)
122-
let args = scopeExtension.extendScopes(dependence: newLifetimeDep)
122+
guard scopeExtension.extendScopes(dependence: newLifetimeDep) else {
123+
continue
124+
}
125+
let args = scopeExtension.findArgumentDependencies()
123126

124127
// Redirect the dependence base to the function arguments. This may create additional mark_dependence instructions.
125128
markDep.redirectFunctionReturn(to: args, context)
@@ -246,28 +249,25 @@ private struct ScopeExtension {
246249
// `mark_dependence` to the outer access `%0`. This ensures that exclusivity diagnostics correctly reports the
247250
// violation, and that subsequent optimizations do not shrink the inner access `%a1`.
248251
extension ScopeExtension {
249-
mutating func extendScopes(dependence: LifetimeDependence) -> SingleInlineArray<FunctionArgument> {
252+
mutating func extendScopes(dependence: LifetimeDependence) -> Bool {
250253
log("Scope fixup for lifetime dependent instructions: \(dependence)")
251254

252255
gatherExtensions(dependence: dependence)
253256

254-
let noCallerScope = SingleInlineArray<FunctionArgument>()
255-
256257
// computeDependentUseRange initializes scopeExtension.dependsOnCaller.
257258
guard var useRange = computeDependentUseRange(of: dependence) else {
258-
return noCallerScope
259+
return false
259260
}
260261
// tryExtendScopes deinitializes 'useRange'
261262
var scopesToExtend = SingleInlineArray<ExtendableScope>()
262263
guard canExtendScopes(over: &useRange, scopesToExtend: &scopesToExtend) else {
263264
useRange.deinitialize()
264-
return noCallerScope
265+
return false
265266
}
266267
// extend(over:) must receive the original unmodified `useRange`, without intermediate scope ending instructions.
267268
// This deinitializes `useRange` before erasing instructions.
268269
extend(scopesToExtend: scopesToExtend, over: &useRange, context)
269-
270-
return dependsOnArgs
270+
return true
271271
}
272272
}
273273

@@ -448,10 +448,15 @@ extension ScopeExtension {
448448
}
449449

450450
extension ScopeExtension {
451-
/// Return all scope owners as long as they are all function arguments and all nested accesses are compatible with
452-
/// their argument convention. Then, if all nested accesses were extended to the return statement, it is valid to
453-
/// logically combine them into a single access for the purpose of diagnostic lifetime dependence.
454-
var dependsOnArgs: SingleInlineArray<FunctionArgument> {
451+
/// Check if the dependent value depends only on function arguments and can therefore be returned to caller. If so,
452+
/// return the list of arguments that it depends on. If this returns an empty list, then the dependent value cannot be
453+
/// returned.
454+
///
455+
/// The conditions for returning a dependent value are:
456+
/// - The dependent value is returned from this function.
457+
/// - All nested scopes are access scopes that are redundant with the caller's exclusive access scope.
458+
/// - All scope owners are function arguments.
459+
func findArgumentDependencies() -> SingleInlineArray<FunctionArgument> {
455460
let noCallerScope = SingleInlineArray<FunctionArgument>()
456461
// Check that the dependent value is returned by this function.
457462
if !dependsOnCaller! {

SwiftCompilerSources/Sources/Optimizer/Utilities/LifetimeDependenceUtils.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -161,7 +161,7 @@ extension LifetimeDependence {
161161

162162
// Construct a LifetimeDependence from a return value. This only
163163
// constructs a dependence for ~Escapable results that do not have a
164-
// lifetime dependence (@_unsafeNonescapableResult).
164+
// lifetime dependence (@lifetime(immortal), @_unsafeNonescapableResult).
165165
//
166166
// This is necessary because inserting a mark_dependence placeholder for such an unsafe dependence would illegally
167167
// have the same base and value operand.

lib/AST/LifetimeDependence.cpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -790,7 +790,8 @@ class LifetimeDependenceChecker {
790790
auto immortalParam =
791791
std::find_if(afd->getParameters()->begin(),
792792
afd->getParameters()->end(), [](ParamDecl *param) {
793-
return strcmp(param->getName().get(), "immortal") == 0;
793+
return param->getName().nonempty()
794+
&& strcmp(param->getName().get(), "immortal") == 0;
794795
});
795796
if (immortalParam != afd->getParameters()->end()) {
796797
diagnose(*immortalParam,

stdlib/public/core/Span/RawSpan.swift

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -711,7 +711,8 @@ extension RawSpan {
711711
public func _extracting(first maxLength: Int) -> Self {
712712
_precondition(maxLength >= 0, "Can't have a prefix of negative length")
713713
let newCount = min(maxLength, byteCount)
714-
return unsafe Self(_unchecked: _pointer, byteCount: newCount)
714+
let newSpan = unsafe Self(_unchecked: _pointer, byteCount: newCount)
715+
return unsafe _overrideLifetime(newSpan, copying: self)
715716
}
716717

717718
/// Returns a span over all but the given number of trailing bytes.
@@ -734,7 +735,8 @@ extension RawSpan {
734735
_precondition(k >= 0, "Can't drop a negative number of bytes")
735736
let droppedCount = min(k, byteCount)
736737
let count = byteCount &- droppedCount
737-
return unsafe Self(_unchecked: _pointer, byteCount: count)
738+
let newSpan = unsafe Self(_unchecked: _pointer, byteCount: count)
739+
return unsafe _overrideLifetime(newSpan, copying: self)
738740
}
739741

740742
/// Returns a span containing the trailing bytes of the span,

stdlib/public/core/Span/Span.swift

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -764,7 +764,8 @@ extension Span where Element: ~Copyable {
764764
public func _extracting(first maxLength: Int) -> Self {
765765
_precondition(maxLength >= 0, "Can't have a prefix of negative length")
766766
let newCount = min(maxLength, count)
767-
return unsafe Self(_unchecked: _pointer, count: newCount)
767+
let newSpan = unsafe Self(_unchecked: _pointer, count: newCount)
768+
return unsafe _overrideLifetime(newSpan, copying: self)
768769
}
769770

770771
/// Returns a span over all but the given number of trailing elements.
@@ -786,7 +787,8 @@ extension Span where Element: ~Copyable {
786787
public func _extracting(droppingLast k: Int) -> Self {
787788
_precondition(k >= 0, "Can't drop a negative number of elements")
788789
let droppedCount = min(k, count)
789-
return unsafe Self(_unchecked: _pointer, count: count &- droppedCount)
790+
let newSpan = unsafe Self(_unchecked: _pointer, count: count &- droppedCount)
791+
return unsafe _overrideLifetime(newSpan, copying: self)
790792
}
791793

792794
/// Returns a span containing the final elements of the span,

stdlib/public/core/UTF8Span.swift

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -181,7 +181,8 @@ extension UTF8Span {
181181
public var span: Span<UInt8> {
182182
@lifetime(copy self)
183183
get {
184-
unsafe Span(_unchecked: _unsafeBaseAddress, count: self.count)
184+
let newSpan = unsafe Span<UInt8>(_unchecked: _unsafeBaseAddress, count: self.count)
185+
return unsafe _overrideLifetime(newSpan, copying: self)
185186
}
186187
}
187188

test/SIL/explicit_lifetime_dependence_specifiers.swift

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,8 @@ func deriveThisOrThat1(_ this: borrowing BufferView, _ that: borrowing BufferVie
105105
if (Int.random(in: 1..<100) == 0) {
106106
return BufferView(independent: this.ptr)
107107
}
108-
return BufferView(independent: that.ptr)
108+
let newThat = BufferView(independent: that.ptr)
109+
return _overrideLifetime(newThat, copying: that)
109110
}
110111

111112
// CHECK-LABEL: sil hidden @$s39explicit_lifetime_dependence_specifiers17deriveThisOrThat2yAA10BufferViewVAD_ADntF : $@convention(thin) (@guaranteed BufferView, @owned BufferView) -> @lifetime(copy 1, borrow 0) @owned BufferView {

test/SIL/implicit_lifetime_dependence.swift

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -85,12 +85,14 @@ func testBasic() {
8585
// CHECK-LABEL: sil hidden @$s28implicit_lifetime_dependence6deriveyAA10BufferViewVADF : $@convention(thin) (@guaranteed BufferView) -> @lifetime(copy 0) @owned BufferView {
8686
@lifetime(copy x)
8787
func derive(_ x: borrowing BufferView) -> BufferView {
88-
return BufferView(x.ptr, x.c)
88+
let newBV = BufferView(x.ptr, x.c)
89+
return _overrideLifetime(newBV, copying: x)
8990
}
9091

9192
@lifetime(copy x)
9293
func derive(_ unused: Int, _ x: borrowing BufferView) -> BufferView {
93-
return BufferView(independent: x.ptr, x.c)
94+
let newBV = BufferView(independent: x.ptr, x.c)
95+
return _overrideLifetime(newBV, copying: x)
9496
}
9597

9698
// CHECK-LABEL: sil hidden @$s28implicit_lifetime_dependence16consumeAndCreateyAA10BufferViewVADnF : $@convention(thin) (@owned BufferView) -> @lifetime(copy 0) @owned BufferView {
@@ -212,7 +214,9 @@ struct GenericBufferView<Element> : ~Escapable {
212214
// CHECK-LABEL: sil hidden @$s28implicit_lifetime_dependence23tupleLifetimeDependenceyAA10BufferViewV_ADtADF : $@convention(thin) (@guaranteed BufferView) -> @lifetime(copy 0) (@owned BufferView, @owned BufferView) {
213215
@lifetime(copy x)
214216
func tupleLifetimeDependence(_ x: borrowing BufferView) -> (BufferView, BufferView) {
215-
return (BufferView(x.ptr, x.c), BufferView(x.ptr, x.c))
217+
let newX1 = BufferView(x.ptr, x.c)
218+
let newX2 = BufferView(x.ptr, x.c)
219+
return (_overrideLifetime(newX1, copying: x), _overrideLifetime(newX2, copying: x))
216220
}
217221

218222
public struct OuterNE: ~Escapable {
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
// swift-interface-format-version: 1.0
2+
// swift-module-flags: -module-name lifetime_depend_diagnose -enable-experimental-feature LifetimeDependence -swift-version 5 -enable-library-evolution
3+
import Swift
4+
import _Concurrency
5+
import _StringProcessing
6+
import _SwiftConcurrencyShims
7+
8+
#if $LifetimeDependence
9+
public struct NE : ~Swift.Escapable {
10+
@usableFromInline
11+
internal let _pointer: Swift.UnsafeRawPointer?
12+
13+
@lifetime(borrow pointer)
14+
public init(pointer: Swift.UnsafeRawPointer?) {
15+
self._pointer = pointer
16+
}
17+
}
18+
19+
extension NE {
20+
// This is illegal at the source level because NE.init is implicitly @lifetime(borrow),
21+
// so we can't return it as dependent on @lifetime(copy self).
22+
@lifetime(copy self)
23+
@unsafe @_alwaysEmitIntoClient public func forward() -> NE {
24+
return NE(pointer: _pointer)
25+
}
26+
}
27+
#endif
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
// RUN: %target-swift-frontend %s -emit-sil \
2+
// RUN: -o /dev/null \
3+
// RUN: -I %S/Inputs \
4+
// RUN: -verify \
5+
// RUN: -enable-experimental-feature LifetimeDependence
6+
7+
// REQUIRES: swift_in_compiler
8+
// REQUIRES: swift_feature_LifetimeDependence
9+
10+
// Test that lifetime dependence diagnostics continues to older (early
11+
// 2025) .swiftinterface files. Source-level diagnostics are stricter.
12+
13+
import lifetime_depend_diagnose

test/SILOptimizer/lifetime_dependence/lifetime_dependence_borrow.swift

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,16 @@
88
// REQUIRES: swift_in_compiler
99
// REQUIRES: swift_feature_LifetimeDependence
1010

11+
@_unsafeNonescapableResult
12+
@lifetime(copy source)
13+
internal func _overrideLifetime<
14+
T: ~Copyable & ~Escapable, U: ~Copyable & ~Escapable
15+
>(
16+
_ dependent: consuming T, copying source: borrowing U
17+
) -> T {
18+
dependent
19+
}
20+
1121
// Some container-ish thing.
1222
struct CN: ~Copyable {
1323
let p: UnsafeRawPointer
@@ -47,7 +57,8 @@ struct MBV : ~Escapable, ~Copyable {
4757
// Requires a borrow.
4858
@lifetime(copy self)
4959
borrowing func getBV() -> BV {
50-
BV(p, i)
60+
let bv = BV(p, i)
61+
return _overrideLifetime(bv, copying: self)
5162
}
5263
}
5364

test/SILOptimizer/lifetime_dependence/lifetime_dependence_mutate.swift

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,10 @@ struct MutableSpan : ~Escapable, ~Copyable {
9393

9494
var iterator: Iter {
9595
@lifetime(copy self)
96-
get { Iter(base: base, count: count) }
96+
get {
97+
let newIter = Iter(base: base, count: count)
98+
return _overrideLifetime(newIter, copying: self)
99+
}
97100
}
98101
}
99102

test/SILOptimizer/lifetime_dependence/lifetime_dependence_scope_fixup.swift

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,20 @@
55
// REQUIRES: swift_in_compiler
66
// REQUIRES: swift_feature_LifetimeDependence
77

8+
/// Unsafely discard any lifetime dependency on the `dependent` argument. Return
9+
/// a value identical to `dependent` that inherits all lifetime dependencies from
10+
/// the `source` argument.
11+
@_unsafeNonescapableResult
12+
@_transparent
13+
@lifetime(copy source)
14+
internal func _overrideLifetime<
15+
T: ~Copyable & ~Escapable, U: ~Copyable & ~Escapable
16+
>(
17+
_ dependent: consuming T, copying source: borrowing U
18+
) -> T {
19+
dependent
20+
}
21+
822
struct NCContainer : ~Copyable {
923
let ptr: UnsafeRawBufferPointer
1024
let c: Int
@@ -233,7 +247,9 @@ func test9() {
233247
234248
@lifetime(copy x)
235249
func getViewTuple(_ x: borrowing View) -> (View, View) {
236-
return (View(x.ptr, x.c), View(x.ptr, x.c))
250+
let x1 = View(x.ptr, x.c)
251+
let x2 = View(x.ptr, x.c)
252+
return (_overrideLifetime(x1, copying: x), _overrideLifetime(x2, copying: x))
237253
}
238254

239255
public func test10() {

0 commit comments

Comments
 (0)