//===--- PackSpecialization.swift - Eliminate packs in function arguments -===// // // This source file is part of the Swift.org open source project // // Copyright (c) 2014 - 2025 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 /// Splits pack arguments (parameters & indirect results) into separate /// arguments for each member of the pack. The argument conventions of the new /// parameters and results are determined based on whether their types are /// trivial or loadable. /// /// A new function is created, with a name generated using the exploded argument /// mangling. This will be a modified version of the original, with local pack /// allocations introduced as substitutes for the exploded pack arguments of the /// original function. Call sites are also modified to extract the elements of /// each pack argument that are passed to (or returned from) the new function /// separately. /// /// See below for SIL examples. let packSpecialization = FunctionPass(name: "pack-specialization") { (function: Function, context: FunctionPassContext) in // This pass requires both the caller and callee to be in OSSA. if !function.hasOwnership { return } // TODO: Support pack specialization for any FullApplySite, not just ApplyInst. for case let apply as ApplyInst in function.instructions { // Only support normal apply instructions for now. guard let callee = apply.referencedFunction, // Only specialize calls to OSSA functions callee.hasOwnership, let specialized = specializeCallee(apply: apply, context: context) else { continue } let callSiteSpecializer = CallSiteSpecializer( apply: apply, specialized: specialized, context: context) callSiteSpecializer.specialize() } } //===----------------------------------------------------------------------===// // Call Site Specialization //===----------------------------------------------------------------------===// /// A context in which to modify call sites during pack specialization. private struct CallSiteSpecializer { let apply: ApplyInst let callee: PackExplodedFunction let context: FunctionPassContext /// Mapping from the indices of the indirect pack arguments of the original /// function to an array of scalar pack indices, used to access their elements typealias PackArgumentIndices = [Int: [ScalarPackIndexInst]] init(apply: ApplyInst, specialized: PackExplodedFunction, context: FunctionPassContext) { self.apply = apply self.callee = specialized self.context = context } /// Perform call-site specialization. func specialize() { let packArgumentIndices = self.createScalarPackIndices() // Populate the arguments list with the appropriate set of indirect results and parameters let results = self.collectNewIndirectResults(using: packArgumentIndices) let parameters = self.collectNewParameters( using: packArgumentIndices) // Emit a call to the specialized function let specializedApply = self.createSpecializedApply(arguments: results + parameters.arguments) self.substituteNewDirectResults(from: specializedApply, using: packArgumentIndices) // Both substituteNewDirectResults and createEndBorrows insert instructions // immediately after specializedApply. We create the end_borrows last to // minimise their borrow scopes. self.createEndBorrows(for: parameters.borrows, after: specializedApply) self.context.erase(instruction: self.apply) } /// Create indices to access elements of the pack arguments that will explode. private func createScalarPackIndices() -> PackArgumentIndices { let builder = Builder(before: self.apply, self.context) // Create scalar pack indices for each pack argument's elements var packArgumentIndices = PackArgumentIndices() for (idx, argument) in self.apply.arguments.enumerated() where argument.type.shouldExplode { var indices = [ScalarPackIndexInst]() let packType = self.callee.packASTTypes[idx]! for elementIdx in argument.type.packElements.indices { indices.append( builder.createScalarPackIndex(componentIndex: elementIdx, indexedPackType: packType)) } packArgumentIndices[idx] = indices } return packArgumentIndices } /// Collect addresses to pass as indirect result arguments to the new function from /// the original call site. /// /// Indirect results must be processed before generating the specialized call, /// since they are passed as operands to the apply, while direct results must /// be processed after generating the specialized call, since they are /// extracted from its result value. See substituteNewDirectResults. /// /// Before: %fn : () -> (@pack_out Pack{C1, T2}, NP) /// /// %non_pack = apply %fn(%pack) /// /// After: %specialized_fn : () -> (@out C1, T2, NP) /// /// %c1_addr = pack_element_get 0 %pack /// (%non_pack, %t2) = apply %specialized_fn(%c1_addr) /// private func collectNewIndirectResults(using packArgumentIndices: PackArgumentIndices) -> [any Value] { let builder = Builder(before: self.apply, self.context) var resultIterator = self.callee.resultMap.makeIterator() var newResults = [any Value]() for (index, resultOperand) in self.apply.indirectResultOperands.enumerated() { let argument = resultOperand.value if !argument.type.shouldExplode { newResults.append(argument) continue } let packIndices = packArgumentIndices[index]! let mapped = resultIterator.next()! assert( mapped.argumentIndex == index, "Iteration over mapped and apply-site indirect result packs is misaligned.") for (packElementIdx, (resultInfo, packIdx)) in zip(mapped.expandedElements, packIndices).enumerated() where resultInfo.isSILIndirect { let indirectResultAddress = builder.createPackElementGet( packIndex: packIdx, pack: argument, elementType: argument.type.packElements[packElementIdx]) newResults.append(indirectResultAddress) } } return newResults } /// Collect values to pass as parameters to the new function from the original /// call site. Also collect any borrows for @guaranteed parameters that need to /// be released after the call. /// /// Non-pack parameters, and pack elements that map to indirect parameters, /// are passed as addresses. Pack elements that map to direct parameters must /// be loaded, if the original pack's elements were stored indirectly /// (isPackElementAddress). We currently only attempt to eliminate indirect /// packs (see TypeProperties.shouldExplode below), so these loads are always /// generated. /// /// TODO: Update this pass to handle direct packs if they are ever generated /// often enough to have a performance impact. /// /// Before: %fn : (@pack_owned Pack{C1, T2}, NP, @pack_guaranteed Pack{C3, T4}) -> () /// /// %result = apply %fn(%pack1, %non_pack, %pack2) /// /// After: %specialized_fn : (@in C1, @owned T2, NP, @in_guaranteed C3, @guaranteed T4) -> () /// /// %arg1addr = pack_element_get 0 %pack1 /// %arg2addr = pack_element_get 1 %pack1 /// %arg2 = load %arg2addr - arg2 is passed @owned or unowned /// %arg3addr = pack_element_get 0 %pack2 /// %arg4addr = pack_element_get 1 %pack2 /// %arg4 = load_borrow %arg4addr - arg4 is passed @guaranteed /// /// %new_result = apply %specialized_fn(%arg1addr, %arg2, %non_pack, %arg3addr, %arg4) /// private func collectNewParameters(using packArgumentIndices: PackArgumentIndices) -> ( arguments: [any Value], borrows: [LoadBorrowInst] ) { let builder = Builder(before: self.apply, self.context) var parameterIterator = self.callee.parameterMap.makeIterator() var newParameters = [any Value]() var parameterBorrows = [LoadBorrowInst]() for (index, argument) in self.apply.arguments.enumerated().dropFirst( self.callee.original.argumentConventions.firstParameterIndex) { if !argument.type.shouldExplode { newParameters.append(argument) continue } let packIndices = packArgumentIndices[index]! let mapped = parameterIterator.next()! assert( mapped.argumentIndex == index, "Iteration over mapped and apply-site indirect parameter packs is misaligned.") for (packElementIdx, (parameterInfo, packIdx)) in zip(mapped.expandedElements, packIndices) .enumerated() { let indirectParameterAddress = builder.createPackElementGet( packIndex: packIdx, pack: argument, elementType: argument.type.packElements[packElementIdx]) switch parameterInfo.convention { case .directUnowned: newParameters.append( builder.createLoad( fromAddress: indirectParameterAddress, ownership: loadOwnership(for: indirectParameterAddress, normal: .trivial))) case .directGuaranteed: let borrow = builder.createLoadBorrow(fromAddress: indirectParameterAddress) newParameters.append(borrow) parameterBorrows.append(borrow) case .directOwned: newParameters.append( builder.createLoad( fromAddress: indirectParameterAddress, ownership: loadOwnership(for: indirectParameterAddress, normal: .take))) case .indirectIn, .indirectInCXX, .indirectInGuaranteed, .indirectInout, .indirectInoutAliasable: newParameters.append(indirectParameterAddress) case .packOut, .packInout, .packOwned, .packGuaranteed, .indirectOut: preconditionFailure("Unsupported exploded parameter pack element convention.") } } } return (newParameters, parameterBorrows) } /// Create an application of the specialized function to the supplied arguments. private func createSpecializedApply(arguments: [any Value]) -> ApplyInst { let builder = Builder(before: self.apply, self.context) // Emit a call to the specialized function let specializedFunctionRef = builder.createFunctionRef(self.callee.specialized) let specializedApply = builder.createApply( function: specializedFunctionRef, self.apply.substitutionMap, arguments: arguments) return specializedApply } /// Map the direct results of the specialized function back to the /// corresponding indirect pack or direct results of the original function. /// /// If an element of an indirect result pack was mapped to a direct return /// value, writeBack is used to write the value back to that original pack /// element. /// /// Also collect the direct return values that correspond to those of the /// original function, and use them to replace all uses of the original /// result. /// /// The stack allocations and accesses for results can then be easily /// eliminated by other passes. private func substituteNewDirectResults( from specializedApply: ApplyInst, using packArgumentIndices: PackArgumentIndices ) { if specializedApply.type == self.apply.type { // No direct return values were added: we just need to replace the original result: // // Before: %fn : (...) -> Int // %1 = apply %fn(...) // // After: %specialized_fn : (...) -> Int // %1 = apply %specialized_fn(...) self.apply.uses.replaceAll(with: specializedApply, self.context) } else if !specializedApply.type.isTuple { assert( self.apply.type.isVoid, "Pack specialized function has fewer direct return values than original function.") // Write back the single direct result: // // Before: %fn : (...) -> @pack_out Pack{Int} // %1 = apply %fn(%pack_addr, ...) // // After: %specialized_fn : (...) -> Int // %result = apply %specialized_fn(...) // %result_addr = pack_element_get 1 %pack_addr // store %result to %result_addr self.replaceOriginalResults(withResultsOf: specializedApply, using: packArgumentIndices) } else { // Write back multiple direct results: // // Before: %fn : () -> (Int, Int, @pack_out Pack{Int, Double}) // // pack_element_set %addr to 0 of %result_pack // ... // %original_result = apply %fn(%result_pack) // // %value = load %addr // operate %original_result // // After: %specialized_fn : () -> (Int, Int, Double) // // pack_element_set %addr to 0 of %result_pack // ... // %result = apply %specialized_fn() // (%original1, %original2, %pack1, %pack2) = destructure_tuple %result ╶┬──╴code inserted by substituteNewDirectResults // %reconstructed_result = tuple (%original1, %original2) ╶┘ // %addr = pack_element_get 0 of %result_pack ╶┬──╴code inserted by replaceOriginalResults // store %result_value to %addr │ // ... etc. for other pack elements ╶┘ // // %value = load %addr // operate %reconstructed_result let builder = Builder(after: specializedApply, self.context) let destructuredTuple = builder.createDestructureTuple(tuple: specializedApply) self.replaceOriginalResults(withResultsOf: destructuredTuple, using: packArgumentIndices) } } /// Replace the results of the original callee that were mapped to direct /// result values with the results of resultInstruction. /// /// See substituteNewDirectResults for code examples. private func replaceOriginalResults( withResultsOf resultInstruction: Instruction, using packArgumentIndices: PackArgumentIndices ) { let builder = Builder(after: resultInstruction, self.context) var directResults = resultInstruction.results.makeIterator() var substitutedResultTupleElements = [any Value]() var mappedResultPacks = self.callee.resultMap.makeIterator() var indirectResultIterator = self.apply.indirectResultOperands.makeIterator() for resultInfo in self.apply.functionConvention.results { // We only need to handle direct and pack results, since indirect results are handled above if !resultInfo.isSILIndirect { // Direct results of the original function are mapped to direct results of the specialized function. substitutedResultTupleElements.append(directResults.next()!) } else { guard let indirectResult = indirectResultIterator.next()?.value, indirectResult.type.shouldExplode else { continue } do { // Some elements of pack results may get mapped to direct results of the specialized function. // We handle those here. let mapped = mappedResultPacks.next()! let packIndices = packArgumentIndices[mapped.argumentIndex]! for (mappedDirectResult, (packIndex, elementType)) in zip( mapped.expandedElements, zip(packIndices, indirectResult.type.packElements) ) where !mappedDirectResult.isSILIndirect { let result = directResults.next()! let outputResultAddress = builder.createPackElementGet( packIndex: packIndex, pack: indirectResult, elementType: elementType) builder.createStore( source: result, destination: outputResultAddress, // The callee is responsible for initializing return pack elements ownership: storeOwnership(for: result, normal: .initialize)) } } } } // Replace all uses of the original apply's result, if necessary. if !self.apply.uses.isEmpty { // Since there could be many uses of the original apply's result tuple, we // construct a new one with the corresponding values from specializedApply. // If exactly one direct result was mapped, don't generate a tuple. if substitutedResultTupleElements.count == 1 { self.apply.uses.replaceAll(with: substitutedResultTupleElements[0], self.context) } else { let substitutedResultTuple = builder.createTuple( type: self.apply.type, elements: substitutedResultTupleElements) self.apply.uses.replaceAll(with: substitutedResultTuple, self.context) } } } /// Release borrows of parameters that were passed as @guaranteed. private func createEndBorrows(for borrows: [LoadBorrowInst], after: Instruction) { let builder = Builder(after: after, self.context) // Release any borrows that were created to pass parameters for borrow in borrows { builder.createEndBorrow(of: borrow) } } } //===----------------------------------------------------------------------===// // Callee Specialization //===----------------------------------------------------------------------===// private func specializeCallee(apply: ApplySite, context: FunctionPassContext) -> PackExplodedFunction? { // Only perform pack specialization when the callee has pack arguments guard let callee = apply.referencedFunction, callee.isDefinition, callee.argumentTypes.contains(where: { $0.shouldExplode }) else { return nil } let exploded = PackExplodedFunction(from: callee, context) return exploded } /// Given a function with parameter pack arguments that can be eliminated (or /// "exploded"), this struct constructs a specialized version of that function, /// with each such argument split into individual parameters or results for each /// member of each pack. /// /// It also produces a mapping between the results and parameters of the /// original and specialized functions, allowing calls to one to be replaced /// with calls to the other, while retaining the same behaviour. /// /// Whether a pack argument can be eliminated is determined using the /// TypeProperties.shouldExplode computed property (see below). /// /// For each pack argument of the original function, a corresponding pack is /// allocated on the stack at the start of the specialized function. Each /// parameter of the specialized function is stored at its corresponding element /// of the appropriate local pack. /// /// Most packs store the addresses of their elements. For pack elements that are /// mapped to direct arguments, we allocate a stack location, store the value in /// it, and store its address in the appropriate local pack. /// /// For result pack elements that are mapped to direct result values, we /// similarly store the address of a dedicated stack location in the /// corresponding local pack. We can then load the value from that stack /// location at the end of the function to return it. /// /// Original: /// /// sil [ossa] @double_up : $@convention(thin) (@pack_guaranteed Pack{Int, String}) -> (@pack_out Pack{Int, String}, @pack_out Pack{Int, String}) { /// bb0(%0 : $*Pack{Int, String}, %1 : $*Pack{Int, String}, %2 : $*Pack{Int, String}): /// debug_value %2, let, name "xs", argno 1, expr op_deref /// ... /// /// Modified: /// /// sil shared [ossa] @double_upTf8xxx_n : $@convention(thin) (Int, @guaranteed String) -> (Int, @owned String, Int, @owned String) { /// bb0(%0 : $Int, %1 : @guaranteed $String): /// %2 = alloc_pack $Pack{Int, String} /// %3 = scalar_pack_index 0 of $Pack{Int, String} /// %4 = alloc_stack $Int /// store %0 to [trivial] %4 /// pack_element_set %4 into %3 of %2 /// %7 = scalar_pack_index 1 of $Pack{Int, String} /// %8 = alloc_stack $String /// %9 = copy_value %1 /// store %9 to [init] %8 /// pack_element_set %8 into %7 of %2 /// /// ... etc. for indirect result arguments of original function (%0 and %1) /// /// debug_value %2, let, name "xs", argno 1, expr op_deref /// ... /// private struct PackExplodedFunction { public let original: Function public let specialized: Function /// Represents the set of results of the new function corresponding to the /// pack argument of the original function at the given index, in ascending /// order. struct MappedResult { /// Index of the indirect pack argument of the original function for this result. let argumentIndex: Int /// Index of this pack in the function's result type tuple. /// For a non-tuple result, this is 0. let resultIndex: Int /// ResultInfo for the results produced by exploding the original result. /// /// NOTE: The expandedElements members of MappedResult & MappedParameter /// correspond to slices of the [ResultInfo] and [ParameterInfo] arrays /// produced at the same time as the ResultMap & ParameterMap respectively. /// Replacing these members with integer ranges or spans referring to those /// full arrays could be an easy performance optimization if this pass /// becomes a bottleneck. let expandedElements: [ResultInfo] } typealias ResultMap = [MappedResult] public let resultMap: ResultMap /// Represents the set of parameters of the new function corresponding to the /// pack argument of the original function at the given index, in ascending /// order. struct MappedParameter { let argumentIndex: Int /// ParameterInfo for the parameters produced by exploding the original parameter. let expandedElements: [ParameterInfo] } typealias ParameterMap = [MappedParameter] public let parameterMap: ParameterMap /// Maps indices of pack arguments of the original function to its /// `approximateFormalPackType` (a Canonical AST PackType). /// Saves recomputing these types every time they are needed, /// which would be unnecessarily expensive. public let packASTTypes: [Int: CanonicalType] init(from original: Function, _ context: FunctionPassContext) { self.original = original var packASTTypes = [Int: CanonicalType]() for (i, argument) in original.arguments.enumerated() where argument.type.shouldExplode { packASTTypes[i] = argument.type.approximateFormalPackType } self.packASTTypes = packASTTypes let (newParameters, parameterMap) = PackExplodedFunction.computeParameters(for: original) self.parameterMap = parameterMap let (newResults, resultMap) = PackExplodedFunction.computeResults(for: original) self.resultMap = resultMap let packArgIndices: [Int] = original.arguments.filter { $0.type.shouldExplode }.map { $0.index } let specializedName = context.mangle(withExplodedPackArguments: packArgIndices, from: original) if let existingFunction = context.lookupFunction(name: specializedName) { specialized = existingFunction } else { specialized = context.createSpecializedFunctionDeclaration( from: original, withName: specializedName, withParams: newParameters, withResults: newResults, // If a method has a dynamic self parameter, it cannot be converted into a thin function (non-method). withRepresentation: original.mayBindDynamicSelf ? nil : .thin) self.buildSpecializedFunction(context) } } // Internal data structures used while building the specialized function. /// A collection of local values used to associate an argument of the /// specialized function with its corresponding pack element. private struct ExplodedArgument { /// The index of this element within its original pack. public let packIdx: ScalarPackIndexInst enum ConventionResources { case indirect case direct(AllocStackInst) case directGuaranteed(AllocStackInst, StoreBorrowInst) } /// The local resources associated with the argument that must be cleaned /// up, determined by its argument convention. public let resources: ConventionResources } /// Information about the local pack created to replace a pack parameter in /// the specialized function. private struct ArgumentMapping { public let allocPack: AllocPackInst public let arguments: [ExplodedArgument] } /// A mapping from the indices of pack arguments of the original function to /// their corresponding local packs in the specialized function. private typealias ArgumentMap = [Int: ArgumentMapping] /// Compute the parameter types for the pack-exploded version of a function, /// and the mapping between the original function's pack parameters, and the /// corresponding exploded parameters of the new function. fileprivate static func computeParameters(for function: Function) -> ( parameters: [ParameterInfo], mapping: ParameterMap ) { var newParameters = [ParameterInfo]() var parameterMap = ParameterMap() for (argument, parameterInfo) in zip(function.parameters, function.convention.parameters) { if argument.type.shouldExplode { let mappedParameterInfos = argument.type.packElements.map { element in ParameterInfo( type: element.hasArchetype ? element.rawType.mapOutOfEnvironment().canonical : element.canonicalType, convention: explodedPackElementArgumentConvention( pack: parameterInfo, type: element, in: function), options: parameterInfo.options, hasLoweredAddresses: parameterInfo.hasLoweredAddresses) } parameterMap.append(MappedParameter(argumentIndex: argument.index, expandedElements: mappedParameterInfos)) newParameters += mappedParameterInfos } else { // Leave the original argument unchanged newParameters.append(parameterInfo) } } return (newParameters, parameterMap) } /// Compute the result types for the pack-exploded version of a function, and /// the mapping between the original function's pack results, and the /// corresponding exploded results of the new function. private static func computeResults(for function: Function) -> ( results: [ResultInfo], mapping: ResultMap ) { var resultMap = ResultMap() var newResults = [ResultInfo]() var indirectResultIterator = function.arguments[0.. (Int, @pack_out Pack{Double, Int}, Double) /// /// pack_element_set 0 of %out_pack to %pack_double /// pack_element_set 1 of %out_pack to %pack_int /// /// return (%int, %double) /// /// After: () -> (Int, Double, Int, Double) /// /// return (%int, %pack_double, %pack_int, %double) /// private func createExplodedReturn( replacing originalReturn: ReturnInst, argumentMap: ArgumentMap, _ context: FunctionPassContext ) { let builder = Builder( before: originalReturn, location: originalReturn.location.asCleanup, context) let originalValue = originalReturn.returnedValue let originalReturnTupleElements: [Value] if originalValue.type.isVoid { originalReturnTupleElements = [] } else if originalValue.type.isTuple { originalReturnTupleElements = [Value]( builder.createDestructureTuple(tuple: originalValue).results) } else { originalReturnTupleElements = [originalValue] } // Thread together the original and exploded direct return values. let theReturnValues: [any Value] do { var returnValues = [any Value]() // The next original result to process. var resultIndex = 0 var originalDirectResultIterator = originalReturnTupleElements.makeIterator() for mappedResult in resultMap { // Collect any direct results before the next mappedResult. while resultIndex < mappedResult.resultIndex { if !self.original.convention.results[resultIndex].isSILIndirect { returnValues.append(originalDirectResultIterator.next()!) } resultIndex += 1 } assert(resultIndex == mappedResult.resultIndex, "The next pack result is not skipped.") let argumentMapping = argumentMap[mappedResult.argumentIndex]! for argument in argumentMapping.arguments { switch argument.resources { case .indirect: break case .direct(let allocStack): returnValues.append( builder.createLoad( fromAddress: allocStack, ownership: loadOwnership(for: allocStack, normal: .take)) ) case .directGuaranteed(_, _): preconditionFailure( "A pack-exploded result value has an associated store_borrow, but there should be no initial value to borrow." ) } } // We have finished processing mappedResult, so step forward. resultIndex += 1 } // Collect any remaining original direct results. while let directResult = originalDirectResultIterator.next() { returnValues.append(directResult) } theReturnValues = returnValues } // Return a single return value directly, rather than constructing a single-element tuple for it. if theReturnValues.count == 1 { builder.createReturn(of: theReturnValues[0]) } else { let tupleElementTypes = theReturnValues.map { $0.type } let tupleType = context.getTupleType(elements: tupleElementTypes).loweredType( in: specialized) let tuple = builder.createTuple(type: tupleType, elements: theReturnValues) builder.createReturn(of: tuple) } context.erase(instruction: originalReturn) } /// Create a local stack-allocated pack to replace the argument at the given /// index. We pass the builder so the order of inserted instructions (notably /// stack allocations) is more predictable. private func prepareExplosion( in function: Function, at index: Int, builder: Builder, _ context: FunctionPassContext ) -> (AllocPackInst, Type) { let entryBlock = function.entryBlock let originalPackArg = entryBlock.arguments[index] let packType = originalPackArg.type.objectType let localPack = builder.createAllocPack(packType) originalPackArg.uses.replaceAll(with: localPack, context) entryBlock.eraseArgument(at: index, context) return (localPack, packType) } /// Explode the types of the entry block's pack arguments, and track the /// correspondence between exploded arguments and local packs. private func explodePackArguments( from original: Function, to specialized: Function, _ context: FunctionPassContext ) -> ArgumentMap { let entryBlock = specialized.entryBlock let builder = Builder(atBeginOf: entryBlock, context) var argumentMap = ArgumentMap() // Explode the arguments in reverse order. This ensures that the index of // each argument is not affected by other arguments exploding before it has // been processed. Parameters come after results, so we explode them first. // Explode parameters. for mapped in parameterMap.reversed() { let (localPack, packType) = prepareExplosion( in: specialized, at: mapped.argumentIndex, builder: builder, context) var mappings = [ExplodedArgument]() for (i, (type, parameterInfo)) in zip(packType.packElements, mapped.expandedElements) .enumerated() { let packIdx = builder.createScalarPackIndex( componentIndex: i, indexedPackType: self.packASTTypes[mapped.argumentIndex]!) let argument = entryBlock.insertFunctionArgument( atPosition: mapped.argumentIndex + i, type: parameterInfo.isSILIndirect ? type.addressType : type, ownership: Ownership(in: specialized, of: type, with: parameterInfo.convention), context) let address: Value let resources: ExplodedArgument.ConventionResources if parameterInfo.isSILIndirect { builder.createPackElementSet(elementValue: argument, packIndex: packIdx, pack: localPack) resources = .indirect } else { let alloc = builder.createAllocStack(type) if parameterInfo.convention == .directGuaranteed { // We do not own @guaranteed arguments, so we must use store_borrow instead of store. let storeBorrow = builder.createStoreBorrow(source: argument, destination: alloc) address = storeBorrow resources = .directGuaranteed(alloc, storeBorrow) } else { let ownership = storeOwnership(for: argument, normal: .initialize) builder.createStore( source: argument, destination: alloc, ownership: ownership) address = alloc resources = .direct(alloc) } builder.createPackElementSet(elementValue: address, packIndex: packIdx, pack: localPack) } mappings.append( ExplodedArgument(packIdx: packIdx, resources: resources)) } argumentMap[mapped.argumentIndex] = ArgumentMapping(allocPack: localPack, arguments: mappings) } // Explode results. for mapped in resultMap.reversed() { let (localPack, packType) = prepareExplosion( in: specialized, at: mapped.argumentIndex, builder: builder, context) var mappings = [ExplodedArgument]() var insertArgumentPosition = mapped.argumentIndex for (i, (type, resultInfo)) in zip(packType.packElements, mapped.expandedElements) .enumerated() { let packIdx = builder.createScalarPackIndex( componentIndex: i, indexedPackType: self.packASTTypes[mapped.argumentIndex]!) let resources: ExplodedArgument.ConventionResources if resultInfo.isSILIndirect { // We only insert arguments for results that must be returned indirectly, so always use the addressType. let argument = entryBlock.insertFunctionArgument( atPosition: insertArgumentPosition, type: type.addressType, ownership: Ownership( in: specialized, of: type, with: ArgumentConvention(result: resultInfo.convention)), context) builder.createPackElementSet(elementValue: argument, packIndex: packIdx, pack: localPack) resources = .indirect insertArgumentPosition += 1 } else { // It is the callee's responsibility to initialize indirect results, // so the original function body already does this. // We do not need to initialize here. let alloc = builder.createAllocStack(type) builder.createPackElementSet(elementValue: alloc, packIndex: packIdx, pack: localPack) resources = .direct(alloc) } // Results have no initial value that could need to be borrowed. mappings.append( ExplodedArgument(packIdx: packIdx, resources: resources)) } argumentMap[mapped.argumentIndex] = ArgumentMapping(allocPack: localPack, arguments: mappings) } return argumentMap } /// Clean up locals, most notably alloc_packs, created as part of pack specialization. private func createCleanup( before terminator: TermInst, argumentMap: ArgumentMap, _ context: FunctionPassContext ) { let builder = Builder(before: terminator, location: terminator.location.asCleanup, context) let createDeallocations = { (idx: Int) in let mapped = argumentMap[idx]! for argument in mapped.arguments.reversed() { switch argument.resources { case .indirect: break case .direct(let allocStack): builder.createDeallocStack(allocStack) case .directGuaranteed(let allocStack, let storeBorrow): builder.createEndBorrow(of: storeBorrow) builder.createDeallocStack(allocStack) } } builder.createDeallocPack(argumentMap[idx]!.allocPack) } // Emit dealloc_packs in the opposite order to the alloc_packs. Since // alloc_packs are created while iterating backwards over the arguments, // iterate forwards. for mapped in resultMap { createDeallocations(mapped.argumentIndex) } for mapped in parameterMap { createDeallocations(mapped.argumentIndex) } } } /// Compute the appropriate argument convention for an exploded member of the given pack parameter with the given type, in the given function private func explodedPackElementArgumentConvention( pack: ParameterInfo, type: Type, in function: Function ) -> ArgumentConvention { // If the pack element type is loadable, then we can pass it directly in the generated function. // TODO: Account for pack.type.isPackElementAddress with packOwned and packGuaranteed packs let trivial = type.isTrivial(in: function) let loadable = type.isLoadable(in: function) switch pack.convention { case .packGuaranteed: if trivial { return .directUnowned } else if loadable { return .directGuaranteed } else { return .indirectInGuaranteed } case .packOwned: if trivial { return .directUnowned } else if loadable { return .directOwned } else { return .indirectIn } case .packInout: return .indirectInout default: preconditionFailure() } } /// Compute the appropriate argument convention for an exploded member of the /// given indirect result pack argument with the given result pack. For a /// return, the convention has to be packOut, so we know its elements are passed /// indirectly. private func explodedPackElementResultConvention(in function: Function, type: Type) -> ResultConvention { // If the pack element type is loadable, then we can return it directly if type.isTrivial(in: function) { return .unowned } else if type.isLoadable(in: function) { return .owned } else { return .indirect } } private func storeOwnership(for value: any Value, normal: StoreInst.StoreOwnership) -> StoreInst.StoreOwnership { let function = value.parentFunction if value.type.isTrivial(in: function) { return .trivial } else { return normal } } private func loadOwnership(for value: any Value, normal: LoadInst.LoadOwnership) -> LoadInst.LoadOwnership { let function = value.parentFunction if value.type.isTrivial(in: function) { return .trivial } else { return normal } } extension Type { /// A pack argument can explode if it contains no pack expansion types fileprivate var shouldExplode: Bool { // For now, we only attempt to explode indirect packs, since these are the most common and inefficient. return isSILPack && !containsSILPackExpansionType && isSILPackElementAddress } }