Skip to content

Commit 38fdb5b

Browse files
authored
Merge pull request #71597 from atrick/lifetime-nested
LifetimeDependenceScopeFixup: handle nested access
2 parents 61ed95a + 68037fa commit 38fdb5b

File tree

7 files changed

+148
-93
lines changed

7 files changed

+148
-93
lines changed

SwiftCompilerSources/Sources/Optimizer/FunctionPasses/LifetimeDependenceInsertion.swift

Lines changed: 0 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -181,63 +181,3 @@ private func insertMarkDependencies(value: Value, initializer: Instruction?,
181181
currentValue = markDep
182182
}
183183
}
184-
185-
/*
186-
/// Return base values that this return value depends on.
187-
///
188-
/// For lifetime copies, walk up the dependence chain to find the
189-
/// dependence roots, inserting dependencies for any
190-
/// LifetimeDependentApply.
191-
private func recursivelyFindDependenceBases(of apply: LifetimeDependentApply,
192-
_ context: FunctionPassContext)
193-
-> [Value] {
194-
log("Creating dependencies for \(apply.applySite)")
195-
var bases: [Value] = []
196-
for lifetimeArg in apply.getLifetimeArguments() {
197-
switch lifetimeArg.convention {
198-
case .inherit:
199-
// Inherit the argument's lifetime dependence by finding the
200-
// roots. This requires that a mark_dependence [nonescaping]
201-
// already be created for any earlier LifetimeDependentApply.
202-
_ = LifetimeDependence.visitDependenceRoots(enclosing: lifetimeArg.value,
203-
context)
204-
{ (scope: LifetimeDependence.Scope) in
205-
if let updatedScope = recursivelyUpdate(scope: scope, context) {
206-
log("Inherited lifetime from \(lifetimeArg.value)")
207-
log(" depends on: \(updatedScope)")
208-
bases.append(updatedScope.parentValue)
209-
}
210-
return .continueWalk
211-
}
212-
case .scope:
213-
// Create a new dependence on the apply's access to the argument.
214-
if let scope =
215-
LifetimeDependence.Scope(base: lifetimeArg.value, context) {
216-
log("Scoped lifetime from \(lifetimeArg.value)")
217-
log(" scope: \(scope)")
218-
bases.append(scope.parentValue)
219-
}
220-
}
221-
}
222-
return bases
223-
}
224-
225-
// Recursively insert dependencies, assuming no cycle of dependent applies.
226-
//
227-
// TODO: needs unit test.
228-
private func recursivelyUpdate(scope: LifetimeDependence.Scope,
229-
_ context: FunctionPassContext) -> LifetimeDependence.Scope? {
230-
if let dependentApply =
231-
LifetimeDependentApply(withResult: scope.parentValue) {
232-
insertDependencies(for: dependentApply, context)
233-
// If a mark_dependence [nonescaping] was created for this apply,
234-
// then return it as the updated dependence. Otherwise, return the
235-
// original dependence.
236-
if let markDep = scope.parentValue.uses.singleUse?.instruction
237-
as? MarkDependenceInst {
238-
return LifetimeDependence(markDep, context)?.scope
239-
}
240-
}
241-
return scope
242-
}
243-
*/

SwiftCompilerSources/Sources/Optimizer/FunctionPasses/LifetimeDependenceScopeFixup.swift

Lines changed: 36 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -37,18 +37,26 @@ let lifetimeDependenceScopeFixupPass = FunctionPass(
3737
guard let markDep = instruction as? MarkDependenceInst else {
3838
continue
3939
}
40-
if let lifetimeDep = LifetimeDependence(markDep, context) {
41-
fixup(dependence: lifetimeDep, context)
40+
guard let lifetimeDep = LifetimeDependence(markDep, context) else {
41+
continue
42+
}
43+
guard let beginAccess = extendAccessScopes(dependence: lifetimeDep,
44+
context) else {
45+
continue
4246
}
47+
extendDependenceBase(dependenceInstruction: markDep,
48+
beginAccess: beginAccess, context)
4349
}
4450
}
4551

46-
private func fixup(dependence: LifetimeDependence,
47-
_ context: FunctionPassContext) {
52+
// Extend all access scopes that enclose `dependence` and return the
53+
// outermost access.
54+
private func extendAccessScopes(dependence: LifetimeDependence,
55+
_ context: FunctionPassContext) -> BeginAccessInst? {
4856
log("Scope fixup for lifetime dependent instructions: \(dependence)")
4957

5058
guard case .access(let bai) = dependence.scope else {
51-
return
59+
return nil
5260
}
5361
var range = InstructionRange(begin: bai, context)
5462
var walker = LifetimeDependenceScopeFixupWalker(bai.parentFunction, context) {
@@ -89,6 +97,29 @@ private func fixup(dependence: LifetimeDependence,
8997
}
9098
beginAccess = enclosingBeginAccess
9199
}
100+
return beginAccess
101+
}
102+
103+
/// Rewrite the mark_dependence to depend on the outermost access
104+
/// scope now that the nested scopes have all been extended.
105+
private func extendDependenceBase(dependenceInstruction: MarkDependenceInst,
106+
beginAccess: BeginAccessInst,
107+
_ context: FunctionPassContext) {
108+
guard case let .base(accessBase) = beginAccess.address.enclosingAccessScope
109+
else {
110+
fatalError("this must be the outer-most access scope")
111+
}
112+
// If the outermost access is in the caller, then depende on the
113+
// address argument.
114+
let baseAddress: Value
115+
switch accessBase {
116+
case let .argument(arg):
117+
assert(arg.type.isAddress)
118+
baseAddress = arg
119+
default:
120+
baseAddress = beginAccess
121+
}
122+
dependenceInstruction.baseOperand.set(to: baseAddress, context)
92123
}
93124

94125
private struct LifetimeDependenceScopeFixupWalker : LifetimeDependenceDefUseWalker {

SwiftCompilerSources/Sources/Optimizer/Utilities/LifetimeDependenceUtils.swift

Lines changed: 57 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -233,6 +233,9 @@ extension LifetimeDependence {
233233

234234
/// Construct LifetimeDependence from mark_dependence [unresolved]
235235
///
236+
/// For any LifetimeDependence constructed from a mark_dependence,
237+
/// its `dependentValue` will be the result of the mark_dependence.
238+
///
236239
/// TODO: Add SIL verification that all mark_depedence [unresolved]
237240
/// have a valid LifetimeDependence.
238241
init?(_ markDep: MarkDependenceInst, _ context: some Context) {
@@ -485,8 +488,7 @@ extension LifetimeDependence {
485488
-> WalkResult {
486489
var useDefVisitor = UseDefVisitor(context, visitor)
487490
defer { useDefVisitor.deinitialize() }
488-
let owner = value.ownership == .owned ? value : nil
489-
return useDefVisitor.walkUp(value: value, owner)
491+
return useDefVisitor.walkUp(valueOrAddress: value)
490492
}
491493

492494
private struct UseDefVisitor : LifetimeDependenceUseDefWalker {
@@ -534,6 +536,18 @@ extension LifetimeDependence {
534536
}
535537
}
536538

539+
/// Walk up the lifetime dependence
540+
///
541+
/// This uses LifetimeDependenceUseDefWalker to find the introducers
542+
/// of a dependence chain, which represent the value's "inherited"
543+
/// dependencies. This stops at an address, unless the address refers
544+
/// to a singly-initialized temprorary, in which case it continues to
545+
/// walk up the stored value.
546+
///
547+
/// This overrides LifetimeDependenceUseDefWalker to stop at a value
548+
/// that introduces an immutable variable: move_value [var_decl] or
549+
/// begin_borrow [var_decl], and to stop at an access of a mutable
550+
/// variable: begin_access.
537551
struct VariableIntroducerUseDefWalker : LifetimeDependenceUseDefWalker {
538552
let context: Context
539553
// This visited set is only really needed for instructions with
@@ -576,24 +590,36 @@ struct VariableIntroducerUseDefWalker : LifetimeDependenceUseDefWalker {
576590
}
577591
return walkUpDefault(dependent: value, owner: owner)
578592
}
593+
594+
mutating func walkUp(address: Value) -> WalkResult {
595+
if let beginAccess = address.definingInstruction as? BeginAccessInst {
596+
return introducer(beginAccess, nil)
597+
}
598+
return walkUpDefault(address: address)
599+
}
579600
}
580601

581-
/// Walk up dominating dependent values.
602+
/// Walk up the lifetime dependence chain.
582603
///
583-
/// Find the roots of a dependence chain stopping at phis. These root
584-
/// LifeDependence instances are the value's "inherited"
585-
/// dependencies. In this example, the dependence root is copied,
586-
/// borrowed, and forwarded before being used as the base operand of
587-
/// `mark_dependence`. The dependence "root" is the parent of the
588-
/// outer-most dependence scope.
604+
/// This finds the introducers of a dependence chain. which represent
605+
/// the value's "inherited" dependencies. This stops at phis; all
606+
/// introducers dominate. This stops at addresses in general, but if
607+
/// the value is loaded from a singly-initialized location, then it
608+
/// continues walking up the value stored by the initializer. This
609+
/// bypasses the copies to temporary memory locations emitted by SILGen.
610+
///
611+
/// In this example, the dependence root is
612+
/// copied, borrowed, and forwarded before being used as the base
613+
/// operand of `mark_dependence`. The dependence "root" is the parent
614+
/// of the outer-most dependence scope.
589615
///
590616
/// %root = apply // lifetime dependence root
591617
/// %copy = copy_value %root
592618
/// %parent = begin_borrow %copy // lifetime dependence parent value
593619
/// %base = struct_extract %parent // lifetime dependence base value
594620
/// %dependent = mark_dependence [nonescaping] %value on %base
595621
///
596-
/// This extends the ForwardingDefUseWalker, which finds the
622+
/// This extends the ForwardingUseDefWalker, which finds the
597623
/// forward-extended lifetime introducers. Certain forward-extended
598624
/// lifetime introducers can inherit a lifetime dependency from their
599625
/// operand: namely copies, moves, and borrows. These introducers are
@@ -631,7 +657,7 @@ struct VariableIntroducerUseDefWalker : LifetimeDependenceUseDefWalker {
631657
/// needWalk(for value: Value) -> Bool
632658
///
633659
/// Start walking:
634-
/// walkUp(value: Value) -> WalkResult
660+
/// walkUp(valueOrAddress: Value) -> WalkResult
635661
protocol LifetimeDependenceUseDefWalker : ForwardingUseDefWalker where PathContext == Value? {
636662
var context: Context { get }
637663

@@ -642,14 +668,28 @@ protocol LifetimeDependenceUseDefWalker : ForwardingUseDefWalker where PathConte
642668
mutating func needWalk(for value: Value, _ owner: Value?) -> Bool
643669

644670
mutating func walkUp(value: Value, _ owner: Value?) -> WalkResult
671+
672+
mutating func walkUp(address: Value) -> WalkResult
645673
}
646674

647675
// Implement ForwardingUseDefWalker
648676
extension LifetimeDependenceUseDefWalker {
677+
mutating func walkUp(valueOrAddress: Value) -> WalkResult {
678+
if valueOrAddress.type.isAddress {
679+
return walkUp(address: valueOrAddress)
680+
}
681+
let owner = valueOrAddress.ownership == .owned ? valueOrAddress : nil
682+
return walkUp(value: valueOrAddress, owner)
683+
}
684+
649685
mutating func walkUp(value: Value, _ owner: Value?) -> WalkResult {
650686
walkUpDefault(dependent: value, owner: owner)
651687
}
652688

689+
mutating func walkUp(address: Value) -> WalkResult {
690+
walkUpDefault(address: address)
691+
}
692+
653693
// Extend ForwardingUseDefWalker to handle copies, moves, and
654694
// borrows. Also transitively walk up other lifetime dependencies to
655695
// find the roots.
@@ -691,17 +731,12 @@ extension LifetimeDependenceUseDefWalker {
691731
return walkUpDefault(forwarded: value, owner)
692732
}
693733

694-
private mutating func walkUp(newLifetime: Value) -> WalkResult {
695-
let newOwner = newLifetime.ownership == .owned ? newLifetime : nil
696-
return walkUp(value: newLifetime, newOwner)
697-
}
698-
699734
// Walk up from a load of a singly-initialized address to find the
700735
// dependence root of the stored value. This ignores mutable
701736
// variables, which require an access scope. This ignores applies
702737
// because an lifetime dependence will already be expressed as a
703738
// mark_dependence.
704-
private mutating func walkUp(address: Value) -> WalkResult {
739+
mutating func walkUpDefault(address: Value) -> WalkResult {
705740
if let (_, initializingStore) =
706741
address.accessBase.findSingleInitializer(context) {
707742
switch initializingStore {
@@ -715,6 +750,11 @@ extension LifetimeDependenceUseDefWalker {
715750
}
716751
return introducer(address, nil)
717752
}
753+
754+
private mutating func walkUp(newLifetime: Value) -> WalkResult {
755+
let newOwner = newLifetime.ownership == .owned ? newLifetime : nil
756+
return walkUp(value: newLifetime, newOwner)
757+
}
718758
}
719759

720760
/// Walk down dependent values.

test/SILOptimizer/lifetime_dependence_borrow.swift

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,3 +42,8 @@ func bv_get_borrow(container: borrowing NC) -> _borrow(container) BV {
4242
func bv_get_copy(container: borrowing NC) -> _copy(container) BV {
4343
return container.getBV()
4444
}
45+
46+
// Recognize nested accesses as part of the same dependence scope.
47+
func bv_get_mutate(container: inout NC) -> _mutate(container) BV {
48+
container.getBV()
49+
}

test/SILOptimizer/lifetime_dependence_inherit.swift

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,8 +39,6 @@ struct NE {
3939
}
4040

4141
// Test lifetime inheritance through chained consumes.
42-
//
43-
// This requires an inherit_lifetime marker on the argument.
4442
func bv_derive(bv: consuming BV) -> _consume(bv) BV {
4543
bv.derive()
4644
}
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
// RUN: %target-swift-frontend %s -emit-sil \
2+
// RUN: -sil-verify-all \
3+
// RUN: -module-name test \
4+
// RUN: -disable-experimental-parser-round-trip \
5+
// RUN: -enable-experimental-feature NonescapableTypes \
6+
// RUN: -Xllvm -enable-lifetime-dependence-diagnostics \
7+
// RUN: 2>&1 | %FileCheck %s
8+
9+
// REQUIRES: swift_in_compiler
10+
11+
// Test LifetimeDependenceScopeFixup.
12+
13+
@_nonescapable
14+
struct BV {
15+
let p: UnsafeRawPointer
16+
let c: Int
17+
18+
public var isEmpty: Bool { c == 0 }
19+
20+
@_unsafeNonescapableResult
21+
init(_ p: UnsafeRawPointer, _ c: Int) {
22+
self.p = p
23+
self.c = c
24+
}
25+
}
26+
27+
struct NC : ~Copyable {
28+
let p: UnsafeRawPointer
29+
let c: Int
30+
31+
// Requires a borrow.
32+
borrowing func getBV() -> _borrow(self) BV {
33+
BV(p, c)
34+
}
35+
}
36+
37+
// Rewrite the mark_dependence to depende on the incoming argument rather than the nested access.
38+
//
39+
// CHECK-LABEL: sil hidden @$s4test13bv_get_mutate9containerAA2BVVAA2NCVz_tF : $@convention(thin) (@inout NC) -> _scope(1) @owned BV {
40+
// CHECK: bb0(%0 : $*NC):
41+
// CHECK: [[A:%.*]] = begin_access [read] [static] %0 : $*NC
42+
// CHECK: [[L:%.*]] = load [[A]] : $*NC
43+
// CHECK: [[R:%.*]] = apply %{{.*}}([[L]]) : $@convention(method) (@guaranteed NC) -> _scope(0) @owned BV
44+
// CHECK: [[M:%.*]] = mark_dependence [nonescaping] [[R]] : $BV on %0 : $*NC
45+
// CHECK-LABEL: } // end sil function '$s4test13bv_get_mutate9containerAA2BVVAA2NCVz_tF'
46+
func bv_get_mutate(container: inout NC) -> _mutate(container) BV {
47+
container.getBV()
48+
}

test/SILOptimizer/lifetime_dependence_todo.swift

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -11,18 +11,11 @@
1111
// REQUIRES: disabled
1212

1313

14-
// =============================================================================
15-
// Diagnostics that should not fail.
16-
17-
// Recognize nested accesses as part of the same dependence scope.
18-
func bv_get_mutate(container: inout NC) -> _mutate(container) BV {
19-
container.getBV()
20-
}
21-
2214
// =============================================================================
2315
// Diagnostics that should fail.
2416

25-
// Test that an unsafe dependence requires Builtin.lifetime_dependence.
17+
// @_unsafeResultDependsOn: Test that an unsafe dependence requires
18+
// Builtin.lifetime_dependence.
2619
//
2720
func bv_derive_local(bv: consuming BV) -> _consume(bv) BV {
2821
let bv2 = BV(bv.p, bv.i)

0 commit comments

Comments
 (0)