Skip to content

Commit 11ba799

Browse files
committed
LifetimeDependence: diagnose yield and store-to-yield.
1 parent c8fcb72 commit 11ba799

File tree

9 files changed

+227
-83
lines changed

9 files changed

+227
-83
lines changed

SwiftCompilerSources/Sources/Optimizer/FunctionPasses/LifetimeDependenceDiagnostics.swift

Lines changed: 90 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,7 @@ private struct DiagnoseDependence {
131131
reportEscaping(operand: operand)
132132
}
133133

134-
func checkInoutResult(argument inoutArg: FunctionArgument, result operand: Operand) -> WalkResult {
134+
func checkInoutResult(argument inoutArg: FunctionArgument) -> WalkResult {
135135
// Check that the parameter dependence for this inout argument is the same as the current dependence scope.
136136
if let sourceArg = dependence.scope.parentValue as? FunctionArgument {
137137
// If the inout result is also the inout source, then it's always ok.
@@ -140,13 +140,28 @@ private struct DiagnoseDependence {
140140
}
141141
if function.argumentConventions.getDependence(target: inoutArg.index, source: sourceArg.index) != nil {
142142
// The inout result depends on a lifetime that is inherited or borrowed in the caller.
143+
log(" has dependent inout argument: \(inoutArg)")
143144
return .continueWalk
144145
}
145146
}
146-
reportEscaping(operand: operand)
147147
return .abortWalk
148148
}
149149

150+
func checkStoreToYield(address: Value) -> WalkResult {
151+
var walker = DependentAddressUseDefWalker(context: context, diagnostics: self)
152+
return walker.walkUp(address: address)
153+
}
154+
155+
func checkYield(operand: Operand) -> WalkResult {
156+
switch dependence.scope {
157+
case .caller:
158+
return checkFunctionResult(operand: operand)
159+
default:
160+
// local scopes can be yielded without escaping.
161+
return .continueWalk
162+
}
163+
}
164+
150165
func checkFunctionResult(operand: Operand) -> WalkResult {
151166

152167
if function.hasUnsafeNonEscapableResult {
@@ -174,9 +189,9 @@ private struct DiagnoseDependence {
174189
// The returned value depends on a lifetime that is inherited or
175190
// borrowed in the caller. The lifetime of the argument value
176191
// itself is irrelevant here.
192+
log(" has dependent function result")
177193
return .continueWalk
178194
}
179-
reportEscaping(operand: operand)
180195
return .abortWalk
181196
}
182197

@@ -328,6 +343,50 @@ private struct LifetimeVariable {
328343
}
329344
}
330345

346+
/// Walk up an address into which a dependent value has been stored. If any address in the use-def chain is a
347+
/// mark_dependence, follow the depenedence base rather than the forwarded value. If any of the dependence bases in
348+
/// within the current scope is with (either local checkInoutResult), then storing a value into that address is
349+
/// nonescaping.
350+
///
351+
/// This supports store-to-yield. Storing to a yield is an escape unless the yielded memory location depends on another
352+
/// lifetime that already depends on the current scope. When setter depends on 'newValue', 'newValue' is stored to the
353+
/// yielded address, and the yielded addrses depends on the lifetime of 'self'. A mark_dependence should have already
354+
/// been inserted for that lifetime depenence:
355+
///
356+
/// (%a, %t) = begin_apply %f(%self)
357+
/// : $@yield_once @convention(method) (@inout Self) -> _inherit(0) @yields @inout Self.field
358+
/// %dep = mark_dependence [nonescaping] %yield_addr on %self
359+
/// store %newValue to [assign] %dep : $*Self.field
360+
///
361+
private struct DependentAddressUseDefWalker {
362+
let context: Context
363+
var diagnostics: DiagnoseDependence
364+
}
365+
366+
extension DependentAddressUseDefWalker: AddressUseDefWalker {
367+
// Follow the dependence base, not the forwarded value. Similar to the way LifetimeDependenceUseDefWalker handles
368+
// MarkDependenceInst.
369+
mutating func walkUp(address: Value, path: UnusedWalkingPath = UnusedWalkingPath()) -> WalkResult {
370+
if let markDep = address as? MarkDependenceInst, let addressDep = LifetimeDependence(markDep, context) {
371+
switch addressDep.scope {
372+
case let .caller(arg):
373+
return diagnostics.checkInoutResult(argument: arg)
374+
case .owned, .initialized:
375+
// Storing a nonescaping value to local memory cannot escape.
376+
return .abortWalk
377+
default:
378+
break
379+
}
380+
}
381+
return walkUpDefault(address: address, path: UnusedWalkingPath())
382+
}
383+
384+
mutating func rootDef(address: Value, path: UnusedWalkingPath) -> WalkResult {
385+
// This only searches for mark_dependence scopes.
386+
return .continueWalk
387+
}
388+
}
389+
331390
/// Walk down lifetime depenence uses. For each check that all dependent
332391
/// leaf uses are non-escaping and within the dependence scope. The walk
333392
/// starts with add address for .access dependencies. The walk can
@@ -380,20 +439,44 @@ extension DiagnoseDependenceWalker : LifetimeDependenceDefUseWalker {
380439
}
381440

382441
mutating func inoutDependence(argument: FunctionArgument, on operand: Operand) -> WalkResult {
383-
return diagnostics.checkInoutResult(argument: argument, result: operand)
442+
if diagnostics.checkInoutResult(argument: argument) == .abortWalk {
443+
diagnostics.reportEscaping(operand: operand)
444+
return .abortWalk
445+
}
446+
return .continueWalk
384447
}
385448

386449
mutating func returnedDependence(result: Operand) -> WalkResult {
387-
return diagnostics.checkFunctionResult(operand: result)
450+
if diagnostics.checkFunctionResult(operand: result) == .abortWalk {
451+
diagnostics.reportEscaping(operand: result)
452+
return .abortWalk
453+
}
454+
return .continueWalk
388455
}
389456

390457
mutating func returnedDependence(address: FunctionArgument,
391458
on operand: Operand) -> WalkResult {
392-
return diagnostics.checkFunctionResult(operand: operand)
459+
if diagnostics.checkFunctionResult(operand: operand) == .abortWalk {
460+
diagnostics.reportEscaping(operand: operand)
461+
return .abortWalk
462+
}
463+
return .continueWalk
393464
}
394465

395466
mutating func yieldedDependence(result: Operand) -> WalkResult {
396-
return diagnostics.checkFunctionResult(operand: result)
467+
if diagnostics.checkYield(operand: result) == .abortWalk {
468+
diagnostics.reportEscaping(operand: result)
469+
return .abortWalk
470+
}
471+
return .continueWalk
472+
}
473+
474+
mutating func storeToYieldDependence(address: Value, of operand: Operand) -> WalkResult {
475+
if diagnostics.checkStoreToYield(address: address) == .abortWalk {
476+
diagnostics.reportEscaping(operand: operand)
477+
return .abortWalk
478+
}
479+
return .continueWalk
397480
}
398481

399482
// Override AddressUseVisitor here because LifetimeDependenceDefUseWalker

SwiftCompilerSources/Sources/Optimizer/FunctionPasses/LifetimeDependenceScopeFixup.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -253,5 +253,9 @@ private struct LifetimeDependenceScopeFixupWalker : LifetimeDependenceDefUseWalk
253253
mutating func yieldedDependence(result: Operand) -> WalkResult {
254254
return .continueWalk
255255
}
256+
257+
mutating func storeToYieldDependence(address: Value, of operand: Operand) -> WalkResult {
258+
return .continueWalk
259+
}
256260
}
257261

SwiftCompilerSources/Sources/Optimizer/Utilities/AddressUtils.swift

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,10 @@ protocol AddressUseVisitor {
6464
mutating func loadedAddressUse(of operand: Operand, into address: Operand)
6565
-> WalkResult
6666

67+
/// Yielding an address may modify the value at the yield, but not past the yield. The yielded value may escape, but
68+
/// only if its type is escapable.
69+
mutating func yieldedAddressUse(of operand: Operand) -> WalkResult
70+
6771
/// A non-address owned `value` whose ownership depends on the in-memory
6872
/// value at `address`, such as `mark_dependence %value on %address`.
6973
mutating func dependentAddressUse(of operand: Operand, into value: Value)
@@ -119,7 +123,7 @@ extension AddressUseVisitor {
119123
case let pai as PartialApplyInst where pai.mayEscape:
120124
return escapingAddressUse(of: operand)
121125

122-
case is ReturnInst, is ThrowInst, is YieldInst, is AddressToPointerInst:
126+
case is ThrowInst, is AddressToPointerInst:
123127
return escapingAddressUse(of: operand)
124128

125129
case is StructElementAddrInst, is TupleElementAddrInst,
@@ -149,12 +153,14 @@ extension AddressUseVisitor {
149153
is PackElementSetInst:
150154
return leafAddressUse(of: operand)
151155

152-
case is LoadInst, is LoadUnownedInst, is LoadWeakInst,
153-
is ValueMetatypeInst, is ExistentialMetatypeInst,
156+
case is LoadInst, is LoadUnownedInst, is LoadWeakInst, is ValueMetatypeInst, is ExistentialMetatypeInst,
154157
is PackElementGetInst:
155158
let svi = operand.instruction as! SingleValueInstruction
156159
return loadedAddressUse(of: operand, into: svi)
157160

161+
case is YieldInst:
162+
return yieldedAddressUse(of: operand)
163+
158164
case let sdai as SourceDestAddrInstruction
159165
where sdai.sourceOperand == operand:
160166
return loadedAddressUse(of: operand, into: sdai.destinationOperand)
@@ -334,6 +340,11 @@ extension AddressInitializationWalker {
334340
return .continueWalk
335341
}
336342

343+
mutating func yieldedAddressUse(of operand: Operand) -> WalkResult {
344+
// An inout yield is a partial write.
345+
return .abortWalk
346+
}
347+
337348
mutating func dependentAddressUse(of operand: Operand, into value: Value)
338349
-> WalkResult {
339350
return .continueWalk

0 commit comments

Comments
 (0)