Skip to content

Commit cc07b4b

Browse files
committed
Fix LifetimeDependenceDiagnostics for @out dependencies.
Record a forwarding mark_dependence as a local access. This is necessary because we now emit a mark_dependence for @out arguments, which will be the starting point for diagnostics: %out = alloc_stack apply %f(%owned, %out) : $(Owner) -> @Lifetime(borrow 0) @out View %unused = mark_dependence [unresolved] %out on %owner %dependentValue = load %out This mark_dependence has no uses. Instead, it simply records the dependency of the in-memory value on the owner. Consequently, simply walking the uses of LifetimeDependence.dependentValue does fails to diagnose any escapes. Instead, if the dependentValue is an address-type mark_dependence, treat it as a local access to the address that it forwards. Then we find any reachable uses of that local variable as a potential escape. Fixes rdar://143040479 (Borrow diagnostics not triggered for @out return values)
1 parent afda645 commit cc07b4b

File tree

3 files changed

+33
-8
lines changed

3 files changed

+33
-8
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: 9 additions & 2 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 {
@@ -375,6 +378,10 @@ extension LocalVariableAccessWalker : ForwardingDefUseWalker {
375378
extension LocalVariableAccessWalker: AddressUseVisitor {
376379
private mutating func walkDownAddressUses(address: Value) -> WalkResult {
377380
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+
}
378385
if classifyAddress(operand: operand) == .abortWalk {
379386
return .abortWalk
380387
}

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)