Files
swift-mirror/SwiftCompilerSources/Sources/Optimizer/FunctionPasses/ClosureSpecialization.swift
Erik Eckstein df20d36255 ClosureSpecialization: support for OSSA and a big overhaul
Beside supporting OSSA, this change significantly simplifies the pass.
The main change is that instead of starting at a closure (e.g. `partial_apply`) and finding all call sites, we now start at a call site and look for closures for all arguments. This makes a lot of things much simpler, e.g. not so many intermediate data structures are required to track all the states.

I needed to remove the 3 unit tests because the things those tests were testing are not there anymore. However, the pass is tested with a lot of sil tests (and I added quite a few), which should give good test coverage.

The old ClosureSpecializer pass is still kept in place, because at that point in the pipeline we don't have OSSA, yet. Once we have that, we can replace the old pass withe the new one.
However, the autodiff closure specializer already runs in the OSSA pipeline and there the new changes take effect.
2025-10-06 12:02:48 +02:00

794 lines
30 KiB
Swift

//===--- ClosureSpecialization.swift ---------------------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2024 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===-----------------------------------------------------------------------===//
import AST
import SIL
/// Closure Specialization
/// ----------------------
/// Specializes functions which take a closure (a `partial_apply` or `thin_to_thick_function` as argument.
/// The closure is created directly in the specialized function whereas the captured arguments are passed
/// as additional arguments to the specialized function. As a heuristic, this is only done if the closure
/// is actually called in the function because after specialization, the closure and its call can be
/// further optimized, e.g. the closure can be inlined.
///
/// ```
/// %3 = function_ref @closure
/// %4 = partial_apply %3(%1, %2) : (Float, Int, Bool) -> ()
/// %5 = function_ref @closure_user
/// apply %5(%4)
/// ...
///
/// sil @closure_user : ((Float) -> (), Float) -> () {
/// bb0(%0 : $(Float) -> (), %1 : $Float):
/// apply %0(%1)
/// ...
/// ```
/// ->
/// ```
/// %5 = function_ref @specialized_closure_user
/// apply %5(%1, %2)
/// ...
///
/// sil @specialized_closure_user : (Float, Int, Bool) -> () {
/// bb0(%0 : $Float, %1 : $Int, %2 : $Bool):
/// %3 = function_ref @closure
/// %4 = partial_apply %3(%1, %2)
/// apply %3(%0)
/// ...
/// ```
///
let closureSpecialization = FunctionPass(name: "closure-specialization") {
(function: Function, context: FunctionPassContext) in
guard function.hasOwnership else {
return
}
for inst in function.instructions {
if let apply = inst as? FullApplySite {
_ = trySpecialize(apply: apply, context)
}
}
if context.needFixStackNesting {
context.fixStackNesting(in: function)
}
}
/// AutoDiff Closure Specialization
/// -------------------------------
/// This optimization performs closure specialization tailored for the patterns seen in Swift Autodiff. In principle,
/// the optimization does the same thing as the general closure specialization pass. However, it is tailored to the
/// patterns of Swift Autodiff.
///
/// The compiler performs reverse-mode differentiation on functions marked with `@differentiable(reverse)`. In doing so,
/// it generates corresponding VJP and Pullback functions, which perform the forward and reverse pass respectively. You
/// can think of VJPs as functions that "differentiate" an original function and Pullbacks as the calculated
/// "derivative" of the original function.
///
/// VJPs always return a tuple of 2 values -- the original result and the Pullback. Pullbacks are essentially a chain
/// of closures, where the closure-contexts are implicitly used as the so-called "tape" during the reverse
/// differentiation process. It is this chain of closures contained within the Pullbacks that this optimization aims
/// to optimize via closure specialization.
///
/// The code patterns that this optimization targets, look similar to the one below:
/// ``` swift
///
/// // Since `foo` is marked with the `differentiable(reverse)` attribute the compiler
/// // will generate corresponding VJP and Pullback functions in SIL. Let's assume that
/// // these functions are called `vjp_foo` and `pb_foo` respectively.
/// @differentiable(reverse)
/// func foo(_ x: Float) -> Float {
/// return sin(x)
/// }
///
/// //============== Before closure specialization ==============//
/// // VJP of `foo`. Returns the original result and the Pullback of `foo`.
/// sil @vjp_foo: $(Float) -> (originalResult: Float, pullback: (Float) -> Float) {
/// bb0(%0: $Float):
/// // __Inlined__ `vjp_sin`: It is important for all intermediate VJPs to have
/// // been inlined in `vjp_foo`, otherwise `vjp_foo` will not be able to determine
/// // that `pb_foo` is closing over other closures and no specialization will happen.
/// \
/// %originalResult = apply @sin(%0): $(Float) -> Float \__ Inlined `vjp_sin`
/// %partially_applied_pb_sin = partial_apply pb_sin(%0): $(Float) -> Float /
/// /
///
/// %pb_foo = function_ref @pb_foo: $@convention(thin) (Float, (Float) -> Float) -> Float
/// %partially_applied_pb_foo = partial_apply %pb_foo(%partially_applied_pb_sin): $(Float, (Float) -> Float) -> Float
///
/// return (%originalResult, %partially_applied_pb_foo)
/// }
///
/// // Pullback of `foo`.
/// //
/// // It receives what are called as intermediate closures that represent
/// // the calculations that the Pullback needs to perform to calculate a function's
/// // derivative.
/// //
/// // The intermediate closures may themselves contain intermediate closures and
/// // that is why the Pullback for a function differentiated at the "top" level
/// // may end up being a "chain" of closures.
/// sil @pb_foo: $(Float, (Float) -> Float) -> Float {
/// bb0(%0: $Float, %pb_sin: $(Float) -> Float):
/// %derivative_of_sin = apply %pb_sin(%0): $(Float) -> Float
/// return %derivative_of_sin: Float
/// }
///
/// //============== After closure specialization ==============//
/// sil @vjp_foo: $(Float) -> (originalResult: Float, pullback: (Float) -> Float) {
/// bb0(%0: $Float):
/// %originalResult = apply @sin(%0): $(Float) -> Float
///
/// // Before the optimization, pullback of `foo` used to take a closure for computing
/// // pullback of `sin`. Now, the specialized pullback of `foo` takes the arguments that
/// // pullback of `sin` used to close over and pullback of `sin` is instead copied over
/// // inside pullback of `foo`.
/// %specialized_pb_foo = function_ref @specialized_pb_foo: $@convention(thin) (Float, Float) -> Float
/// %partially_applied_pb_foo = partial_apply %specialized_pb_foo(%0): $(Float, Float) -> Float
///
/// return (%originalResult, %partially_applied_pb_foo)
/// }
///
/// sil @specialized_pb_foo: $(Float, Float) -> Float {
/// bb0(%0: $Float, %1: $Float):
/// %2 = partial_apply @pb_sin(%1): $(Float) -> Float
/// %3 = apply %2(): $() -> Float
/// return %3: $Float
/// }
/// ```
///
let autodiffClosureSpecialization = FunctionPass(name: "autodiff-closure-specialization") {
(function: Function, context: FunctionPassContext) in
guard function.hasOwnership else {
return
}
guard !function.isDefinedExternally,
function.isAutodiffVJP
else {
return
}
var remainingSpecializationRounds = 5
repeat {
var changed = false
for inst in function.instructions {
if let partialApply = inst as? PartialApplyInst,
partialApply.isPullbackInResultOfAutodiffVJP
{
if trySpecialize(apply: partialApply, context) {
changed = true
}
}
}
if context.needFixStackNesting {
context.fixStackNesting(in: function)
}
if !changed {
break
}
remainingSpecializationRounds -= 1
} while remainingSpecializationRounds > 0
}
// ===================== Utility functions and extensions ===================== //
private func trySpecialize(apply: ApplySite, _ context: FunctionPassContext) -> Bool {
guard isCalleeSpecializable(of: apply),
let specialization = analyzeArguments(of: apply, context)
else {
return false
}
// We need to make each captured argument of all closures a unique Value. Otherwise the Cloner would
// wrongly map added capture arguments to the re-generated closure in the specialized function:
//
// %4 = partial_apply %3(%1, %1) : (Int, Int) -> () // %1 is captured twice
//
// sil @specialized_closure_user : (Int, Int) -> () {
// bb0(%0 : $Int, %1 : $Int):
// %3 = function_ref @closure
// %4 = partial_apply %3(%0, %0) <- instead of an argument list of `(%0, %1)`!
//
// This wouldn't be a problem per se - the code is correct. However, this is not reflected in the mangling.
// And the same specialized function could be re-used at a call-site where not two identical values are
// passed to the closure.
//
specialization.uniqueCaptureArguments(context)
let specializedFunction = specialization.getOrCreateSpecializedFunction(context)
specialization.unUniqueCaptureArguments(context)
specialization.rewriteApply(for: specializedFunction, context)
specialization.deleteDeadClosures(context)
return true
}
private func isCalleeSpecializable(of apply: ApplySite) -> Bool {
if let callee = apply.referencedFunction,
callee.isDefinition,
// We don't support generic functions (yet)
!apply.hasSubstitutions,
// Don't specialize non-fragile (read as non-serialized) callees if the caller is fragile; the
// specialized callee will have shared linkage, and thus cannot be referenced from the fragile caller.
!(apply.parentFunction.isSerialized && !callee.isSerialized),
// If the callee uses a dynamic Self, we cannot specialize it, since the resulting specialization
// might no longer have 'self' as the last parameter.
//
// TODO: Keep the self argument the last when appending arguments.
!callee.mayBindDynamicSelf
{
return true
}
return false
}
private func analyzeArguments(of apply: ApplySite, _ context: FunctionPassContext) -> SpecializationInfo? {
var argumentsToSpecialize = [(Operand, Closure)]()
var rootClosures = [PartialApplyInst]()
var rootClosuresAdded = InstructionSet(context)
defer { rootClosuresAdded.deinitialize() }
for argOp in apply.argumentOperands {
var visited = ValueSet(context)
defer { visited.deinitialize() }
if let closure = findSpecializableClosure(of: argOp.value, &visited),
// Ok, we know that we can perform the optimization but not whether or not the optimization
// is profitable. Check if the closure is actually called in the callee (or in a function
// called by the callee). This opens optimization opportunities, like inlining.
isClosureApplied(apply.calleeArgument(of: argOp, in: apply.referencedFunction!)!)
{
argumentsToSpecialize.append((argOp, closure))
if let partialApply = closure as? PartialApplyInst,
rootClosuresAdded.insert(partialApply)
{
rootClosures.append(partialApply)
}
}
}
if argumentsToSpecialize.isEmpty {
return nil
}
return SpecializationInfo(apply: apply, closureArguments: argumentsToSpecialize, rootClosures: rootClosures)
}
// Walks down the use-def chain of a function argument, recursively, to find a rootClosure.
private func findSpecializableClosure(of value: Value, _ visited: inout ValueSet) -> Closure? {
visited.insert(value)
let specializationLevelLimit = 2
switch value {
case is ConvertFunctionInst,
is ConvertEscapeToNoEscapeInst,
is MoveValueInst,
is CopyValueInst:
return findSpecializableClosure(of: (value as! UnaryInstruction).operand.value, &visited)
case let mdi as MarkDependenceInst:
guard mdi.value.type.isNoEscapeFunction, mdi.value.type.isThickFunction else {
return nil
}
guard let operandClosure = findSpecializableClosure(of: mdi.value, &visited) else {
return nil
}
// Make sure that the mark_dependence's base is part of the use-def chain and will therefore be cloned as well.
if !visited.contains(mdi.base) {
return nil
}
return operandClosure
case let partialApply as PartialApplyInst:
// Don't specialize for re-abstractions via a partial_apply, but treat such re-abstractions like
// closure "conversions". E.g.
// ```
// %1 = partial_apply // root closure
// %2 = function_ref @thunk
// %3 = partial_apply %2(%1) // re-abstraction
// apply %f(%3)
// ```
if partialApply.isPartialApplyOfThunk,
let argumentClosure = findSpecializableClosure(of: partialApply.arguments[0], &visited)
{
return argumentClosure
}
guard let callee = partialApply.referencedFunction,
!partialApply.hasSubstitutions,
// Avoid an infinite specialization loop caused by repeated runs of ClosureSpecialization and
// ConstantCapturePropagation.
// ConstantCapturePropagation propagates constant function-literals. Such function specializations
// can then be optimized again by ClosureSpecialization and so on. This happens if a closure argument
// is called _and_ referenced in another closure, which is passed to a recursive call. E.g.
//
// func foo(_ c: @escaping () -> ()) {
// c() foo({ c() })
// }
//
// A limit of 2 is good enough and will not be exceed in "regular" optimization scenarios.
callee.specializationLevel <= specializationLevelLimit,
// Functions with a readnone, readonly or releasenone effect and a consumed captures cannot be
// specialized because the captured arguments are passed as owned arguments to the specialized
// function. Destroy for such arguments in the specialized function violates the effect.
(partialApply.isOnStack || callee.effectAllowsSpecialization),
// TODO: handle other kind of indirect arguments
partialApply.hasOnlyInoutIndirectArguments,
(partialApply.isOnStack || partialApply.allArgumentsCanBeCopied)
else {
return nil
}
return partialApply
case let tttfi as ThinToThickFunctionInst:
guard let callee = tttfi.referencedFunction,
callee.specializationLevel <= specializationLevelLimit
else {
return nil
}
return tttfi
default:
return nil
}
}
/// Either a `partial_apply` or a `thin_to_thick_function`
private typealias Closure = SingleValueInstruction
/// Information about the function to be specialized and for which closure arguments.
private struct SpecializationInfo {
// The apply which we want to specialize
let apply: ApplySite
// All closure arguments of the apply which we want to replace (usually there is one, but there can be
// multiple).
// Note that the `Closure` is not necessarily the value of the `Operand`. There can be function conventions
// and re-abstractions (via a thunk) in-between:
//
// %4 = partial_apply %3(%1) : (Int) -> () // %4 = rootClosure
// %5 = convert_function %4
// %6 = copy_value %5
// apply %5(%5) // %5 = closureArgument
//
let closureArguments: [(closureArgument: Operand, rootClosure: Closure)]
// All rootClosures of `closureArguments` which are `partial_apply`s, and uniqued: if a rootClosure
// appears multiple times in `closureArguments`, it's only added a single time here.
let rootClosures: [PartialApplyInst]
// The function to specialize
var callee: Function { apply.referencedFunction! }
private typealias Cloner = SIL.Cloner<FunctionPassContext>
func getOrCreateSpecializedFunction(_ context: FunctionPassContext) -> Function {
let specializedFunctionName = getSpecializedFunctionName(context)
if let existingSpecializedFunction = context.lookupFunction(name: specializedFunctionName) {
return existingSpecializedFunction
}
let specializedParameters = getSpecializedParameters()
let specializedFunction =
context.createSpecializedFunctionDeclaration(
from: callee, withName: specializedFunctionName,
withParams: specializedParameters,
// The specialized function is always a thin function. This is important because we add additional
// parameters after the Self parameter of witness methods. In this case the new function is not a
// method anymore.
makeThin: true, makeBare: true)
context.buildSpecializedFunction(
specializedFunction: specializedFunction,
buildFn: { (specializedFunction, specializedContext) in
var cloner = Cloner(cloneToEmptyFunction: specializedFunction, specializedContext)
defer { cloner.deinitialize() }
cloneAndSpecializeFunctionBody(using: &cloner)
})
context.notifyNewFunction(function: specializedFunction, derivedFrom: callee)
return specializedFunction
}
private func getSpecializedFunctionName(_ context: FunctionPassContext) -> String {
var visited = Dictionary<Closure, Int>()
let argumentManglings = closureArguments.map { (argOp, closure) in
let argIdx = apply.calleeArgumentIndex(of: argOp)!
if let prevArgIdx = visited[closure] {
// If the same closure is passed multiple times to a function, we need to reflect this in the mangling:
//
// %3 = function_ref @closure
// %4 = partial_apply %3(%1) : (Int) -> ()
// apply %6(%4, %4)
//
// sil @specialized_closure_user : (Int) -> () {
//
// is different than
//
// %3 = function_ref @closure
// %4 = partial_apply %3(%1) : (Int) -> ()
// %5 = partial_apply %3(%1) : (Int) -> ()
// apply %6(%4, %5)
//
// sil @specialized_closure_user : (Int, Int) -> () {
//
return (argIdx, FunctionPassContext.ClosureArgumentMangling.previousArgumentIndex(prevArgIdx))
} else {
visited[closure] = argIdx
return (argIdx, FunctionPassContext.ClosureArgumentMangling.closure(closure))
}
}
return context.mangle(withClosureArguments: argumentManglings, from: callee)
}
private func getSpecializedParameters() -> [ParameterInfo] {
var specializedParamInfoList: [ParameterInfo] = []
// Start by adding all original parameters except for the closure parameters.
let firstParamIndex = callee.argumentConventions.firstParameterIndex
for (index, paramInfo) in callee.convention.parameters.enumerated() {
let argIndex = index + firstParamIndex
if !closureArguments.contains(where: { apply.calleeArgumentIndex(of: $0.0) == argIndex}) {
specializedParamInfoList.append(paramInfo)
}
}
// Now, append parameters captured by each of the root closures.
for partialApply in rootClosures {
let closureConvention = partialApply.functionConvention
let unappliedArgumentCount = partialApply.unappliedArgumentCount - closureConvention.indirectSILResultCount
for paramInfo in closureConvention.parameters[unappliedArgumentCount...] {
let newParamInfo = paramInfo.withSpecializedConvention(for: partialApply, in: callee)
specializedParamInfoList.append(newParamInfo)
}
}
return specializedParamInfoList
}
private func cloneAndSpecializeFunctionBody(using cloner: inout Cloner) {
addFunctionArgumentsWithoutClosures(using: &cloner)
for rootClosure in rootClosures {
addFunctionArgumentsForCaptures(of: rootClosure, using: &cloner)
}
let clonedClosureArguments = cloneClosures(using: &cloner)
cloner.cloneFunctionBody(from: callee)
addMissingDestroysAtFunctionExits(for: clonedClosureArguments, cloner.context)
}
private func addFunctionArgumentsWithoutClosures(using cloner: inout Cloner) {
let clonedEntryBlock = cloner.getOrCreateEntryBlock()
for originalArg in callee.arguments where !isClosureArgument(calleeArgument: originalArg) {
let argType = originalArg.type.getLoweredType(in: cloner.targetFunction)
let clonedArg = clonedEntryBlock.addFunctionArgument(type: argType, cloner.context)
clonedArg.copyFlags(from: originalArg, cloner.context)
cloner.recordFoldedValue(originalArg, mappedTo: clonedArg)
}
}
private func addFunctionArgumentsForCaptures(of closure: PartialApplyInst, using cloner: inout Cloner) {
for originalClosureArg in closure.arguments {
let capturedArg = cloner.targetFunction.entryBlock.addFunctionArgument(
type: originalClosureArg.type.getLoweredType(in: cloner.targetFunction),
cloner.context)
if !cloner.isCloned(value: originalClosureArg) {
cloner.recordFoldedValue(originalClosureArg, mappedTo: capturedArg)
}
}
}
private func cloneClosures(using cloner: inout Cloner) -> [Value] {
return closureArguments.map { (closureArgOp, _) in
let clonedArg = cloner.cloneRecursively(value: closureArgOp.value)
let originalArg = apply.calleeArgument(of: closureArgOp, in: callee)!
cloner.recordFoldedValue(originalArg, mappedTo: clonedArg)
return clonedArg
}
}
func rewriteApply(for specializedFunction: Function, _ context: FunctionPassContext) {
insertCompensatingDestroysForOwnedClosureArguments(context)
let newApplyArgs = getNewApplyArguments(context)
let builder = Builder(before: apply, context)
let funcRef = builder.createFunctionRef(specializedFunction)
switch apply {
case let oldPartialApply as PartialApplyInst:
let newPartialApply = builder.createPartialApply(
function: funcRef, substitutionMap: SubstitutionMap(),
capturedArguments: newApplyArgs, calleeConvention: oldPartialApply.calleeConvention,
hasUnknownResultIsolation: oldPartialApply.hasUnknownResultIsolation,
isOnStack: oldPartialApply.isOnStack)
oldPartialApply.replace(with: newPartialApply, context)
case let oldApply as ApplyInst:
let newApply = builder.createApply(function: funcRef, SubstitutionMap(), arguments: newApplyArgs,
isNonThrowing: oldApply.isNonThrowing,
isNonAsync: oldApply.isNonAsync)
oldApply.replace(with: newApply, context)
case let oldTryApply as TryApplyInst:
builder.createTryApply(function: funcRef, SubstitutionMap(), arguments: newApplyArgs,
normalBlock: oldTryApply.normalBlock, errorBlock: oldTryApply.errorBlock,
isNonAsync: oldTryApply.isNonAsync)
context.erase(instruction: oldTryApply)
case let oldBeginApply as BeginApplyInst:
let newApply = builder.createBeginApply(function: funcRef, SubstitutionMap(), arguments: newApplyArgs,
isNonThrowing: oldBeginApply.isNonThrowing,
isNonAsync: oldBeginApply.isNonAsync)
oldBeginApply.replace(with: newApply, context)
default:
fatalError("unknown apply")
}
}
private func insertCompensatingDestroysForOwnedClosureArguments(_ context: FunctionPassContext) {
let builder = Builder(before: apply, context)
for (argOp, _) in closureArguments where argOp.endsLifetime {
builder.createDestroyValue(operand: argOp.value)
}
}
private func getNewApplyArguments(_ context: FunctionPassContext) -> [Value] {
let newCapturedArguments = rootClosures.flatMap { partialApply in
partialApply.arguments.map { capturedArg in
if partialApply.isOnStack || capturedArg.ownership == .none {
// Non-escaping closures don't consume their captures. Therefore we pass them also as "guaranteed"
// arguments to the specialized function.
// Note that because the non-escaping closure was passed to the original function, this guarantees
// that the lifetime of the captured arguments also extend to at least the apply of the function.
capturedArg
} else {
// Escaping closures consume their captures. Therefore we pass them as "owned" arguments to the
// specialized function.
capturedArg.copy(at: partialApply, andMakeAvailableIn: apply.parentBlock, context)
}
}
}
return nonClosureArguments.values + newCapturedArguments
}
func uniqueCaptureArguments(_ context: FunctionPassContext) {
let builder = Builder(before: apply, context)
// Insert identity cast instructions for all closure arguments to make them unique. We could use any kind
// of forwarding instruction - we'll delete them afterwards, anyway.
//
// %4 = partial_apply %3(%1, %1) : (Int, Int) -> () // %1 is captured twice
// ->
// %2 = unchecked_value_cast %1
// %3 = unchecked_value_cast %1
// %4 = partial_apply %3(%2, %3) : (Int, Int) -> () // all arguments are unique values now!
//
for closure in rootClosures {
for argOp in closure.argumentOperands {
let cast = builder.createUncheckedValueCast(from: argOp.value, to: argOp.value.type)
argOp.set(to: cast, context)
}
}
}
func unUniqueCaptureArguments(_ context: FunctionPassContext) {
// Remove the inserted identity casts again.
for closure in rootClosures {
for argOp in closure.argumentOperands {
let cast = argOp.value as! UncheckedValueCastInst
cast.replace(with: cast.fromValue, context)
}
}
}
func deleteDeadClosures(_ context: FunctionPassContext) {
for (_, closure) in closureArguments where !closure.isDeleted {
if context.tryDeleteDeadClosure(closure: closure) {
context.notifyInvalidatedStackNesting()
}
}
}
private var nonClosureArguments: LazyFilterSequence<OperandArray> {
apply.argumentOperands.lazy.filter{ argOp in !closureArguments.contains{ $0.0 == argOp } }
}
private func isClosureArgument(calleeArgument: FunctionArgument) -> Bool {
closureArguments.contains { apply.calleeArgument(of: $0.0, in: callee) == calleeArgument }
}
}
private func isClosureApplied(_ closure: Value) -> Bool {
var handledFuncs: Set<Function> = []
return checkRecursivelyIfClosureIsApplied(closure, &handledFuncs)
}
private func checkRecursivelyIfClosureIsApplied(_ closure: Value, _ handledFuncs: inout Set<Function>) -> Bool {
for use in closure.uses {
switch use.instruction {
case let apply as FullApplySite:
if apply.callee == closure {
return true
}
let recursionBudget = 8
// Recurse into called function
if let callee = apply.referencedFunction,
callee.isDefinition,
handledFuncs.insert(callee).inserted,
handledFuncs.count <= recursionBudget,
let calleeArg = apply.calleeArgument(of: use, in: callee)
{
if checkRecursivelyIfClosureIsApplied(calleeArg, &handledFuncs) {
return true
}
}
case is CopyValueInst, is MoveValueInst:
if checkRecursivelyIfClosureIsApplied(use.instruction as! SingleValueInstruction, &handledFuncs) {
return true
}
default:
break
}
}
return false
}
/// Add destroys for values which are not consumed, yet.
/// There are two cases of values for which we need this:
/// 1. guaranteed closure arguments
/// 2. operands of copies at the call site
///
/// ```
/// %1 = partial_apply %closure // is cloned to the specialized function
/// %2 = copy_value %1 // is cloned to the specialized function
/// apply %function(%2) : (@guaranteed () -> ()) -> ()
/// destroy_value %1
///
/// sil @specializedFunction() -> () {
/// bb0:
/// %1 = partial_apply %closure
/// %2 = copy_value %1
/// ... // body
/// destroy_value %2 // case 1: destroy for the guaranteed closure argument
/// destroy_value %1 // case 2: destroy for the operand of the copy
/// return
/// ```
private func addMissingDestroysAtFunctionExits(for clonedArguments: [Value], _ context: FunctionPassContext) {
var needDestroy = ValueWorklist(context)
defer { needDestroy.deinitialize() }
for clonedArg in clonedArguments {
findValuesWhichNeedDestroyRecursively(value: clonedArg, needDestroy: &needDestroy)
}
while let valueToDestroy = needDestroy.pop() {
Builder.insertCleanupAtFunctionExits(of: valueToDestroy.parentFunction, context) { builder in
builder.createDestroyValue(operand: valueToDestroy)
}
}
}
private func findValuesWhichNeedDestroyRecursively(value: Value, needDestroy: inout ValueWorklist) {
if let svi = value as? SingleValueInstruction {
for op in svi.operands {
findValuesWhichNeedDestroyRecursively(value: op.value, needDestroy: &needDestroy)
}
}
if value.ownership == .owned && value.uses.endingLifetime.isEmpty {
needDestroy.pushIfNotVisited(value)
}
}
private extension ParameterInfo {
func withSpecializedConvention(for partialApply: PartialApplyInst, in callee: Function) -> Self {
let argType = type.loweredType(in: partialApply.parentFunction)
let specializedParamConvention = if self.convention.isIndirect {
self.convention
} else {
if argType.isTrivial(in: callee) {
ArgumentConvention.directUnowned
} else {
if partialApply.isOnStack {
ArgumentConvention.directGuaranteed
} else {
ArgumentConvention.directOwned
}
}
}
return ParameterInfo(
type: self.type, convention: specializedParamConvention, options: self.options,
hasLoweredAddresses: self.hasLoweredAddresses)
}
}
private extension PartialApplyInst {
/// True, if the closure obtained from this partial_apply is the
/// pullback returned from an autodiff VJP
var isPullbackInResultOfAutodiffVJP: Bool {
if self.parentFunction.isAutodiffVJP,
let use = self.uses.singleUse,
let tupleInst = use.instruction as? TupleInst,
let returnInst = self.parentFunction.returnInstruction,
tupleInst == returnInst.returnedValue
{
return true
}
return false
}
var isPartialApplyOfThunk: Bool {
if self.numArguments == 1,
let fun = self.referencedFunction,
fun.thunkKind == .reabstractionThunk || fun.thunkKind == .thunk,
self.arguments[0].type.isLoweredFunction,
self.arguments[0].type.isReferenceCounted(in: self.parentFunction) || self.callee.type.isThickFunction
{
return true
}
return false
}
var hasOnlyInoutIndirectArguments: Bool {
self.argumentOperands
.filter { !$0.value.type.isObject }
.allSatisfy { self.convention(of: $0)!.isInout }
}
var allArgumentsCanBeCopied: Bool {
arguments.allSatisfy { !$0.type.isMoveOnly }
}
}
private extension Function {
var effectAllowsSpecialization: Bool {
switch self.effectAttribute {
case .readNone, .readOnly, .releaseNone: return false
default: return true
}
}
}