Files
swift-mirror/SwiftCompilerSources/Sources/Optimizer/FunctionPasses/ClosureSpecialization.swift

804 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,
// Calling `cloneRecursively` from `SpecializationInfo.cloneClosures`
// requires the callee having ownership info. Otherwise, the cloner
// uses `recordFoldedValue` instead of `recordClonedInstruction`, and
// `postProcess` hook is not called, which leads to an assertion
// failure in `BridgedClonerImpl::cloneInst`.
callee.hasOwnership,
// 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,
// Don't support self-recursive functions because that would result in duplicate mapping of values when cloning.
callee != apply.parentFunction
{
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.
withRepresentation: .thin, 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
}
}
}