Skip to content

Commit af17772

Browse files
authored
Merge pull request #81004 from atrick/nested-modify-read
Fix LifetimeDependenceScopeFixup: read access can depend on 'inout'
2 parents 93ad267 + 33fbe11 commit af17772

File tree

5 files changed

+60
-40
lines changed

5 files changed

+60
-40
lines changed

SwiftCompilerSources/Sources/Optimizer/FunctionPasses/LifetimeDependenceScopeFixup.swift

+14-14
Original file line numberDiff line numberDiff line change
@@ -453,30 +453,30 @@ extension ScopeExtension {
453453
/// logically combine them into a single access for the purpose of diagnostic lifetime dependence.
454454
var dependsOnArgs: SingleInlineArray<FunctionArgument> {
455455
let noCallerScope = SingleInlineArray<FunctionArgument>()
456+
// Check that the dependent value is returned by this function.
456457
if !dependsOnCaller! {
457458
return noCallerScope
458459
}
459-
// All owners must be arguments to depend on the caller's scope.
460+
// Check that all nested scopes that it depends on can be covered by exclusive access in the caller.
461+
for extScope in scopes {
462+
switch extScope.scope {
463+
case .access:
464+
break
465+
default:
466+
return noCallerScope
467+
}
468+
}
469+
// All owners must be arguments with exclusive access to depend on the caller's scope (inout_aliasable arguments do
470+
// not have exclusivity).
460471
var compatibleArgs = SingleInlineArray<FunctionArgument>()
461472
for owner in owners {
462473
guard let arg = owner as? FunctionArgument else {
463474
return noCallerScope
464475
}
465-
compatibleArgs.push(arg)
466-
}
467-
// The only local scopes that are compatible with the caller's scope are access scopes that either read an
468-
// immutable argument or modify a mutable argument.
469-
for extScope in scopes {
470-
switch extScope.scope {
471-
case let .access(beginAccess):
472-
for arg in compatibleArgs {
473-
if !beginAccess.accessKind.isCompatible(with: arg.convention) {
474-
return noCallerScope
475-
}
476-
}
477-
default:
476+
guard arg.convention.isIndirectIn || arg.convention.isInout else {
478477
return noCallerScope
479478
}
479+
compatibleArgs.push(arg)
480480
}
481481
return compatibleArgs
482482
}

SwiftCompilerSources/Sources/SIL/Argument.swift

-13
Original file line numberDiff line numberDiff line change
@@ -607,19 +607,6 @@ public enum ArgumentConvention : CustomStringConvertible {
607607
}
608608
}
609609

610-
extension BeginAccessInst.AccessKind {
611-
public func isCompatible(with convention: ArgumentConvention) -> Bool {
612-
switch self {
613-
case .`init`, .deinit:
614-
return false
615-
case .read:
616-
return convention.isIndirectIn
617-
case .modify:
618-
return convention.isInout
619-
}
620-
}
621-
}
622-
623610
// Bridging utilities
624611

625612
extension BridgedArgument {

test/SILOptimizer/lifetime_dependence/scope_fixup.sil

+26-2
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,8 @@ sil @useA : $@convention(thin) (A) -> ()
6565

6666
sil @getPtr : $@convention(thin) () -> @out UnsafeRawPointer
6767

68-
sil @getOwnedNE : $@convention(thin) (@inout Holder) -> @lifetime(borrow 0) @owned NE
68+
sil @getOwnedNE : $@convention(thin) (@guaranteed Holder) -> @lifetime(borrow 0) @owned NE
69+
sil @getOwnedNEFromInout : $@convention(thin) (@inout Holder) -> @lifetime(borrow 0) @owned NE
6970

7071
sil @useNE : $@convention(thin) (@guaranteed NE) -> ()
7172

@@ -470,7 +471,7 @@ bb1:
470471

471472
bb2:
472473
%access = begin_access [modify] [unknown] %0
473-
%f = function_ref @getOwnedNE : $@convention(thin) (@inout Holder) -> @lifetime(borrow 0) @owned NE
474+
%f = function_ref @getOwnedNEFromInout : $@convention(thin) (@inout Holder) -> @lifetime(borrow 0) @owned NE
474475
%ne = apply %f(%access) : $@convention(thin) (@inout Holder) -> @lifetime(borrow 0) @owned NE
475476
%md = mark_dependence [unresolved] %ne on %access
476477
end_access %access
@@ -480,3 +481,26 @@ bb2:
480481
bb3(%ret : @owned $Optional<NE>):
481482
return %ret
482483
}
484+
485+
// Allow a [read] access to depend on a caller's [modify] access.
486+
//
487+
// CHECK-LABEL: sil hidden [noinline] [ossa] @testNestedModRead : $@convention(thin) (@inout Holder) -> @lifetime(borrow 0) @owned NE {
488+
// CHECK: bb0(%0 : $*Holder):
489+
// CHECK: [[ACCESS:%.*]] = begin_access [read] [unknown] %0
490+
// CHECK: [[LD:%.*]] = load [copy] [[ACCESS]]
491+
// CHECK: [[MD:%.*]] = mark_dependence [unresolved] %{{.*}} on %0
492+
// CHECK: destroy_value [[LD]]
493+
// CHECK: end_access [[ACCESS]]
494+
// CHECK: return [[MD]]
495+
// CHECK-LABEL: } // end sil function 'testNestedModRead'
496+
sil hidden [noinline] [ossa] @testNestedModRead : $@convention(thin) (@inout Holder) -> @lifetime(borrow 0) @owned NE {
497+
bb0(%0 : $*Holder):
498+
%access = begin_access [read] [unknown] %0
499+
%holder = load [copy] %access
500+
end_access %access
501+
%f = function_ref @getOwnedNE : $@convention(thin) (@guaranteed Holder) -> @lifetime(borrow 0) @owned NE
502+
%ne = apply %f(%holder) : $@convention(thin) (@guaranteed Holder) -> @lifetime(borrow 0) @owned NE
503+
%md = mark_dependence [unresolved] %ne on %access
504+
destroy_value %holder
505+
return %md
506+
}

test/SILOptimizer/lifetime_dependence/semantics.swift

+6-9
Original file line numberDiff line numberDiff line change
@@ -434,9 +434,8 @@ func testGlobal(local: InnerTrivial) -> Span<Int> {
434434

435435
@lifetime(&a)
436436
func testInoutBorrow(a: inout [Int]) -> Span<Int> {
437-
a.span() // expected-error {{lifetime-dependent value escapes its scope}}
438-
// expected-note @-1{{it depends on this scoped access to variable 'a'}}
439-
} // expected-note {{this use causes the lifetime-dependent value to escape}}
437+
a.span() // OK
438+
}
440439

441440
@lifetime(&a)
442441
func testInoutMutableBorrow(a: inout [Int]) -> MutableSpan<Int> {
@@ -462,12 +461,10 @@ extension Container {
462461

463462
@lifetime(&self)
464463
mutating func mutableView() -> MutableView {
465-
// Reading 'self.owner' creates a local borrow scope. This new MutableView
466-
// depends on a the local borrow scope for 'self.owner', so it cannot be
467-
// returned.
468-
MutableView(owner: self.owner) // expected-error {{lifetime-dependent value escapes its scope}}
469-
// expected-note @-1{{it depends on this scoped access to variable 'self'}}
470-
} // expected-note {{this use causes the lifetime-dependent value to escape}}
464+
// Reading 'self.owner' creates a local borrow scope. The caller's exclusive access on 'self' is sufficient for the
465+
// result.
466+
MutableView(owner: self.owner) // OK
467+
}
471468

472469
@lifetime(&self)
473470
mutating func mutableViewModifiesOwner() -> MutableView {

test/SILOptimizer/lifetime_dependence/verify_diagnostics.swift

+14-2
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
// RUN: -o /dev/null \
33
// RUN: -verify \
44
// RUN: -sil-verify-all \
5+
// RUN: -module-name test \
6+
// RUN: -define-availability "Span 0.1:macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, visionOS 9999" \
57
// RUN: -enable-experimental-feature LifetimeDependence
68

79
// REQUIRES: swift_in_compiler
@@ -13,13 +15,23 @@
1315
// Test that conditionally returning an Optional succeeds.
1416
//
1517
// See scope_fixup.sil: testReturnPhi.
16-
@available(SwiftStdlib 6.2, *)
18+
@available(Span 0.1, *)
1719
extension Array {
1820
@lifetime(&self)
19-
mutating func dumb() -> MutableSpan<Element>? {
21+
mutating func getOptionalMutableSpan() -> MutableSpan<Element>? {
2022
if count == 0 {
2123
return nil
2224
}
2325
return mutableSpan
2426
}
2527
}
28+
29+
// Test that returning an immutable Span from an inout Array.
30+
//
31+
// See scope_fixup.sil: testNestedModRead.
32+
@available(Span 0.1, *)
33+
@inline(never)
34+
@lifetime(&array)
35+
func getImmutableSpan(_ array: inout [Int]) -> Span<Int> {
36+
return array.span
37+
}

0 commit comments

Comments
 (0)