Skip to content

Commit 73f7f8f

Browse files
committed
Add NonEscapingClosureDefUseWalker
To find all effective applications of partial_apply [on-stack].
1 parent a3fe978 commit 73f7f8f

File tree

1 file changed

+69
-0
lines changed

1 file changed

+69
-0
lines changed

SwiftCompilerSources/Sources/Optimizer/Utilities/ForwardingUtils.swift

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -328,6 +328,75 @@ private struct VisitForwardedUses : ForwardingDefUseWalker {
328328
}
329329
}
330330

331+
/// Walk all uses of partial_apply [on_stack] that may propagate the closure context. Gather each FullApplySite that
332+
/// either invokes this closure as its callee, or passes the closure as an argument to another function.
333+
///
334+
/// Start walk:
335+
/// walkDown(closure:)
336+
///
337+
/// This is a subset of the functionality in LifetimeDependenceDefUseWalker, but significantly simpler. This avoids
338+
/// traversing lifetime dependencies that do not propagate context. For example, a mark_dependence on a closure extends
339+
/// its lifetime but cannot introduce any new uses of the closure context.
340+
struct NonEscapingClosureDefUseWalker {
341+
let context: Context
342+
var visitedValues: ValueSet
343+
var applyOperandStack: Stack<Operand>
344+
345+
/// `visitor` takes an operand whose instruction is always a FullApplySite.
346+
init(_ context: Context) {
347+
self.context = context
348+
self.visitedValues = ValueSet(context)
349+
self.applyOperandStack = Stack(context)
350+
}
351+
352+
mutating func deinitialize() {
353+
visitedValues.deinitialize()
354+
applyOperandStack.deinitialize()
355+
}
356+
357+
mutating func walkDown(closure: PartialApplyInst) -> WalkResult {
358+
assert(closure.isOnStack)
359+
return walkDownUses(of: closure, using: nil)
360+
}
361+
362+
mutating func closureContextLeafUse(of operand: Operand) -> WalkResult {
363+
switch operand.instruction {
364+
case is FullApplySite:
365+
applyOperandStack.push(operand)
366+
return .continueWalk
367+
case is MarkDependenceInst, is FixLifetimeInst, is DestroyValueInst:
368+
return .continueWalk
369+
default:
370+
if operand.instruction.isIncidentalUse {
371+
return .continueWalk
372+
}
373+
// Escaping or unexpected closure use. Expected escaping uses include ReturnInst with a lifetime-dependent result.
374+
//
375+
// TODO: Check in the SIL verifier that all uses are expected.
376+
return .abortWalk
377+
}
378+
}
379+
}
380+
381+
extension NonEscapingClosureDefUseWalker: ForwardingDefUseWalker {
382+
mutating func needWalk(for value: Value) -> Bool {
383+
visitedValues.insert(value)
384+
}
385+
386+
mutating func nonForwardingUse(of operand: Operand) -> WalkResult {
387+
// Nonescaping closures may be moved, copied, or borrowed.
388+
if let transition = operand.instruction as? OwnershipTransitionInstruction {
389+
return walkDownUses(of: transition, using: operand)
390+
}
391+
// Otherwise, assume the use cannot propagate the closure context.
392+
return closureContextLeafUse(of: operand)
393+
}
394+
395+
mutating func deadValue(_ value: Value, using operand: Operand?) -> WalkResult {
396+
return .continueWalk
397+
}
398+
}
399+
331400
let forwardingUseDefTest = FunctionTest("forwarding_use_def_test") {
332401
function, arguments, context in
333402
let value = arguments.takeValue()

0 commit comments

Comments
 (0)