Skip to content

Commit 5066dd9

Browse files
authored
Merge pull request #78716 from atrick/fix-lifedep-out
Fix LifetimeDependenceDiagnostics for @out dependencies
2 parents c6a2437 + ffb1137 commit 5066dd9

File tree

4 files changed

+66
-27
lines changed

4 files changed

+66
-27
lines changed

SwiftCompilerSources/Sources/Optimizer/Utilities/LifetimeDependenceUtils.swift

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -880,6 +880,17 @@ extension LifetimeDependenceDefUseWalker {
880880
extension LifetimeDependenceDefUseWalker {
881881
mutating func walkDown(root: Value) -> WalkResult {
882882
if root.type.isAddress {
883+
if let md = root as? MarkDependenceInst, !root.isEscapable {
884+
// LifetimeDependence.dependentValue is typically a mark_dependence. If its 'value' address is a non-Escapable
885+
// local variable, then consider all other reachable uses of that local variable to be dependent uses. Remember
886+
// the operand to the mark_dependence as if it was a store. Diagnostics will consider this the point of variable
887+
// initialization.
888+
if visitStoredUses(of: md.valueOperand, into: md.value) == .abortWalk {
889+
return .abortWalk
890+
}
891+
}
892+
// The root address may also be an escapable mark_dependence that guards its address uses (unsafeAddress), or an
893+
// allocation or incoming argument. In all these cases, it is sufficient to walk down the address uses.
883894
return walkDownAddressUses(of: root)
884895
}
885896
return walkDownUses(of: root, using: nil)
@@ -1127,11 +1138,13 @@ extension LifetimeDependenceDefUseWalker {
11271138
}
11281139
}
11291140

1130-
// Visit stores to a local variable (alloc_box), temporary storage
1131-
// (alloc_stack). This handles stores of the entire value and stores
1132-
// to a tuple element. Stores to a field within another nominal
1133-
// value are considered lifetime dependence leaf uses; the type
1134-
// system enforces non-escapability on the aggregate value.
1141+
// Visit stores to a local variable (alloc_box), temporary storage (alloc_stack). This handles stores of the entire
1142+
// value and stores to a tuple element. Stores to a field within another nominal value are considered lifetime
1143+
// dependence leaf uses; the type system enforces non-escapability on the aggregate value.
1144+
//
1145+
// If 'operand' is an address, then the "store" corresponds to initialization via an @out argument. The initial
1146+
// call to visitStoredUses will have 'operand == address' where the "stored value" is the temporary stack
1147+
// allocation for the @out parameter.
11351148
private mutating func visitStoredUses(of operand: Operand,
11361149
into address: Value) -> WalkResult {
11371150
assert(address.type.isAddress)
@@ -1207,6 +1220,11 @@ extension LifetimeDependenceDefUseWalker {
12071220
default:
12081221
return .abortWalk
12091222
}
1223+
case .dependence:
1224+
// An address-forwarding mark_dependence is simply a marker that indicates the start of an in-memory
1225+
// dependent value. Typically, it has no uses. If it does have uses, then they are visited earlier by
1226+
// LocalVariableAccessWalker to record any other local accesses.
1227+
return .continueWalk
12101228
case .store:
12111229
let si = localAccess.operand!.instruction as! StoringInstruction
12121230
assert(si.sourceOperand == initialValue, "the only reachable store should be the current assignment")

SwiftCompilerSources/Sources/Optimizer/Utilities/LocalVariableUtils.swift

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ struct LocalVariableAccess: CustomStringConvertible {
4343
case inoutYield // indirect yield from this accessor
4444
case beginAccess // Reading or reassigning a 'var'
4545
case load // Reading a 'let'. Returning 'var' from an initializer.
46+
case dependence // A mark_dependence after an apply with an indirect result. No effect.
4647
case store // 'var' initialization and destruction
4748
case apply // indirect arguments
4849
case escape // alloc_box captures
@@ -76,7 +77,7 @@ struct LocalVariableAccess: CustomStringConvertible {
7677
case .`init`, .modify:
7778
return true
7879
}
79-
case .load:
80+
case .load, .dependence:
8081
return false
8182
case .incomingArgument, .outgoingArgument, .store, .inoutYield:
8283
return true
@@ -115,6 +116,8 @@ struct LocalVariableAccess: CustomStringConvertible {
115116
str += "beginAccess"
116117
case .load:
117118
str += "load"
119+
case .dependence:
120+
str += "dependence"
118121
case .store:
119122
str += "store"
120123
case .apply:
@@ -149,7 +152,7 @@ class LocalVariableAccessInfo: CustomStringConvertible {
149152
case .`init`, .modify:
150153
break // lazily compute full assignment
151154
}
152-
case .load:
155+
case .load, .dependence:
153156
self._isFullyAssigned = false
154157
case .store:
155158
if let store = localAccess.instruction as? StoringInstruction {
@@ -197,20 +200,18 @@ class LocalVariableAccessInfo: CustomStringConvertible {
197200
}
198201

199202
var description: String {
200-
return "full-assign: \(_isFullyAssigned == nil ? "unknown" : String(describing: _isFullyAssigned!)) "
203+
return "full-assign: \(_isFullyAssigned == nil ? "unknown" : String(describing: _isFullyAssigned!)), "
201204
+ "\(access)"
202205
}
203206

204207
// Does this address correspond to the local variable's base address? Any writes to this address will be a full
205208
// assignment. This should match any instructions that the LocalVariableAccessMap initializer below recognizes as an
206209
// allocation.
207210
static private func isBase(address: Value) -> Bool {
208-
switch address {
209-
case is AllocBoxInst, is AllocStackInst, is BeginAccessInst:
210-
return true
211-
default:
212-
return false
213-
}
211+
// TODO: create an API alternative to 'accessPath' that bails out on the first path component and succeeds on the
212+
// first begin_access.
213+
let path = address.accessPath
214+
return path.base.isLocal && path.projectionPath.isEmpty
214215
}
215216
}
216217

@@ -377,6 +378,10 @@ extension LocalVariableAccessWalker : ForwardingDefUseWalker {
377378
extension LocalVariableAccessWalker: AddressUseVisitor {
378379
private mutating func walkDownAddressUses(address: Value) -> WalkResult {
379380
for operand in address.uses.ignoreTypeDependence {
381+
if let md = operand.instruction as? MarkDependenceInst, operand == md.valueOperand {
382+
// Record the forwarding mark_dependence as a fake access before continuing to walk down.
383+
visit(LocalVariableAccess(.dependence, operand))
384+
}
380385
if classifyAddress(operand: operand) == .abortWalk {
381386
return .abortWalk
382387
}

test/ModuleInterface/Inputs/lifetime_dependence.swift

Lines changed: 28 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,21 @@
1+
@_unsafeNonescapableResult
2+
@_alwaysEmitIntoClient
3+
@_transparent
4+
@lifetime(borrow source)
5+
internal func _overrideLifetime<T: ~Copyable & ~Escapable, U: ~Copyable & ~Escapable>(
6+
_ dependent: consuming T, borrowing source: borrowing U) -> T {
7+
dependent
8+
}
9+
10+
@_unsafeNonescapableResult
11+
@_alwaysEmitIntoClient
12+
@_transparent
13+
@lifetime(source)
14+
internal func _overrideLifetime<T: ~Copyable & ~Escapable, U: ~Copyable & ~Escapable>(
15+
_ dependent: consuming T, copying source: borrowing U) -> T {
16+
dependent
17+
}
18+
119
public struct AnotherView : ~Escapable {
220
@usableFromInline let _ptr: UnsafeRawBufferPointer
321
@usableFromInline let _count: Int
@@ -21,19 +39,23 @@ public struct BufferView : ~Escapable {
2139
@inlinable
2240
@lifetime(borrow a)
2341
internal init(_ ptr: UnsafeRawBufferPointer, _ a: borrowing Array<Int>) {
24-
self.init(ptr, a.count)
42+
let bv = BufferView(ptr, a.count)
43+
self = _overrideLifetime(bv, borrowing: a)
2544
}
2645
@inlinable
2746
@lifetime(a)
2847
internal init(_ ptr: UnsafeRawBufferPointer, _ a: consuming AnotherView) {
29-
self.init(ptr, a._count)
48+
let bv = BufferView(ptr, a._count)
49+
self = _overrideLifetime(bv, copying: a)
3050
}
3151
}
3252

3353
@inlinable
3454
@lifetime(x)
3555
public func derive(_ x: consuming BufferView) -> BufferView {
36-
return BufferView(x._ptr, x._count)
56+
let pointer = x._ptr
57+
let bv = BufferView(pointer, x._count)
58+
return _overrideLifetime(bv, copying: x)
3759
}
3860

3961
@inlinable
@@ -42,7 +64,9 @@ public func use(_ x: consuming BufferView) {}
4264
@inlinable
4365
@lifetime(view)
4466
public func consumeAndCreate(_ view: consuming BufferView) -> BufferView {
45-
return BufferView(view._ptr, view._count)
67+
let pointer = view._ptr
68+
let bv = BufferView(pointer, view._count)
69+
return _overrideLifetime(bv, copying: view)
4670
}
4771

4872
@inlinable
@@ -54,14 +78,6 @@ public func deriveThisOrThat(_ this: consuming BufferView, _ that: consuming Buf
5478
return BufferView(that._ptr, that._count)
5579
}
5680

57-
@_unsafeNonescapableResult
58-
@_transparent
59-
@lifetime(borrow source)
60-
internal func _overrideLifetime<T: ~Copyable & ~Escapable, U: ~Copyable & ~Escapable>(
61-
_ dependent: consuming T, borrowing source: borrowing U) -> T {
62-
dependent
63-
}
64-
6581
public struct Container {
6682
var buffer: UnsafeRawBufferPointer
6783
var object: AnyObject

test/SILOptimizer/lifetime_dependence/scopefixup.sil

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ bb2:
8585
sil @pointeeAddressor : $@convention(method) <τ_0_0 where τ_0_0 : ~Copyable> (UnsafePointer<τ_0_0>) -> UnsafePointer<τ_0_0>
8686

8787
// Test dependence on a loaded trivial value. The dependence scope should not be on the access scope, which is narrower
88-
// than the scoped of the loaded value.
88+
// than the scope of the loaded value.
8989
//
9090
// CHECK-LABEL: sil hidden [ossa] @testTrivialAccess : $@convention(thin) (UnsafePointer<Int64>) -> () {
9191
// CHECK: bb0(%0 : $UnsafePointer<Int64>):

0 commit comments

Comments
 (0)