//===--- 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 { 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 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() 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 { 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 = [] return checkRecursivelyIfClosureIsApplied(closure, &handledFuncs) } private func checkRecursivelyIfClosureIsApplied(_ closure: Value, _ handledFuncs: inout Set) -> 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 } } }