Skip to content

Commit 97b7c35

Browse files
committed
PackSpecialization: Fix result & type parameter handling
Refactor certain functions to make them simpler. and avoid calling AST.Type.loweredType, which can fail. Instead, access the types of the function's (SIL) arguments directly. Correctly handle exploding packs that contain generic or opaque types by using AST.Type.mapOutOfEnvironment(). @Substituted types cause the shouldExplode predicate to be unreliable for AST types, so restrict it to just SIL.Type. Add test cases for functions that have @Substituted types. Re-enable PackSpecialization in FunctionPass pipeline. Add a check to avoid emitting a destructure_tuple of the original function's return tuple when it is void/().
1 parent 2c9013b commit 97b7c35

File tree

4 files changed

+318
-77
lines changed

4 files changed

+318
-77
lines changed

SwiftCompilerSources/Sources/Optimizer/FunctionPasses/PackSpecialization.swift

Lines changed: 106 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -353,36 +353,45 @@ private struct CallSiteSpecializer {
353353
var directResults = resultInstruction.results.makeIterator()
354354
var substitutedResultTupleElements = [any Value]()
355355
var mappedResultPacks = self.callee.resultMap.makeIterator()
356+
var indirectResultIterator = self.apply.indirectResultOperands.makeIterator()
356357

357358
for resultInfo in self.apply.functionConvention.results {
358359
// We only need to handle direct and pack results, since indirect results are handled above
359360
if !resultInfo.isSILIndirect {
360361
// Direct results of the original function are mapped to direct results of the specialized function.
361362
substitutedResultTupleElements.append(directResults.next()!)
362363

363-
} else if resultInfo.type.shouldExplode {
364-
// Some elements of pack results may get mapped to direct results of the specialized function.
365-
// We handle those here.
366-
let mapped = mappedResultPacks.next()!
367-
368-
let originalPackArgument = self.apply.arguments[mapped.argumentIndex]
369-
let packIndices = packArgumentIndices[mapped.argumentIndex]!
370-
371-
for (mappedDirectResult, (packIndex, elementType)) in zip(
372-
mapped.expandedElements, zip(packIndices, originalPackArgument.type.packElements)
373-
)
374-
where !mappedDirectResult.isSILIndirect
375-
{
376-
377-
let result = directResults.next()!
378-
let outputResultAddress = builder.createPackElementGet(
379-
packIndex: packIndex, pack: originalPackArgument,
380-
elementType: elementType)
381-
382-
builder.createStore(
383-
source: result, destination: outputResultAddress,
384-
// The callee is responsible for initializing return pack elements
385-
ownership: storeOwnership(for: result, normal: .initialize))
364+
} else {
365+
guard let indirectResult = indirectResultIterator.next()?.value,
366+
indirectResult.type.shouldExplode
367+
else {
368+
continue
369+
}
370+
371+
do {
372+
// Some elements of pack results may get mapped to direct results of the specialized function.
373+
// We handle those here.
374+
let mapped = mappedResultPacks.next()!
375+
376+
377+
let packIndices = packArgumentIndices[mapped.argumentIndex]!
378+
379+
for (mappedDirectResult, (packIndex, elementType)) in zip(
380+
mapped.expandedElements, zip(packIndices, indirectResult.type.packElements)
381+
)
382+
where !mappedDirectResult.isSILIndirect
383+
{
384+
385+
let result = directResults.next()!
386+
let outputResultAddress = builder.createPackElementGet(
387+
packIndex: packIndex, pack: indirectResult,
388+
elementType: elementType)
389+
390+
builder.createStore(
391+
source: result, destination: outputResultAddress,
392+
// The callee is responsible for initializing return pack elements
393+
ownership: storeOwnership(for: result, normal: .initialize))
394+
}
386395
}
387396
}
388397
}
@@ -499,6 +508,14 @@ private struct PackExplodedFunction {
499508
/// Index of this pack in the function's result type tuple.
500509
/// For a non-tuple result, this is 0.
501510
let resultIndex: Int
511+
/// ResultInfo for the results produced by exploding the original result.
512+
///
513+
/// NOTE: The expandedElements members of MappedResult & MappedParameter
514+
/// correspond to slices of the [ResultInfo] and [ParameterInfo] arrays
515+
/// produced at the same time as the ResultMap & ParameterMap respectively.
516+
/// Replacing these members with integer ranges or spans referring to those
517+
/// full arrays could be an easy performance optimization if this pass
518+
/// becomes a bottleneck.
502519
let expandedElements: [ResultInfo]
503520
}
504521

@@ -510,6 +527,7 @@ private struct PackExplodedFunction {
510527
/// order.
511528
struct MappedParameter {
512529
let argumentIndex: Int
530+
/// ParameterInfo for the parameters produced by exploding the original parameter.
513531
let expandedElements: [ParameterInfo]
514532
}
515533

@@ -600,10 +618,9 @@ private struct PackExplodedFunction {
600618

601619
for (argument, parameterInfo) in zip(function.parameters, function.convention.parameters) {
602620
if argument.type.shouldExplode {
603-
604621
let mappedParameterInfos = argument.type.packElements.map { element in
605622
ParameterInfo(
606-
type: element.canonicalType,
623+
type: element.hasArchetype ? element.rawType.mapOutOfEnvironment().canonical : element.canonicalType,
607624
convention: explodedPackElementArgumentConvention(
608625
pack: parameterInfo, type: element, in: function),
609626
options: parameterInfo.options,
@@ -631,36 +648,42 @@ private struct PackExplodedFunction {
631648
var resultMap = ResultMap()
632649
var newResults = [ResultInfo]()
633650

634-
var indirectResultIdx = 0
635-
for (resultIndex, resultInfo) in function.convention.results.enumerated() {
636-
let silType = resultInfo.type.loweredType(in: function)
637-
if silType.shouldExplode {
638-
let mappedResultInfos = silType.packElements.map { elem in
639-
// TODO: Determine correct values for options and hasLoweredAddress
640-
ResultInfo(
641-
type: elem.canonicalType,
642-
convention: explodedPackElementResultConvention(in: function, type: elem),
643-
options: resultInfo.options,
644-
hasLoweredAddresses: resultInfo.hasLoweredAddresses)
645-
}
651+
var indirectResultIterator = function.arguments[0..<function.convention.indirectSILResultCount]
652+
.lazy.enumerated().makeIterator()
646653

647-
resultMap.append(
648-
MappedResult(
649-
argumentIndex: indirectResultIdx, resultIndex: resultIndex,
650-
expandedElements: mappedResultInfos))
651-
newResults += mappedResultInfos
652-
} else {
653-
// Leave the original result unchanged
654+
for (resultIndex, resultInfo) in function.convention.results.enumerated() {
655+
assert(
656+
!resultInfo.isSILIndirect || !indirectResultIterator.isEmpty,
657+
"There must be exactly as many indirect results in the function convention and argument list."
658+
)
659+
660+
guard resultInfo.isSILIndirect,
661+
// There should always be a value here (expressed by the assert above).
662+
let (indirectResultIdx, indirectResult) = indirectResultIterator.next(),
663+
indirectResult.type.shouldExplode
664+
else {
654665
newResults.append(resultInfo)
666+
continue
655667
}
656668

657-
if resultInfo.isSILIndirect {
658-
indirectResultIdx += 1
669+
let mappedResultInfos = indirectResult.type.packElements.map { element in
670+
ResultInfo(
671+
type: element.hasArchetype ? element.rawType.mapOutOfEnvironment().canonical : element.canonicalType,
672+
convention: explodedPackElementResultConvention(in: function, type: element),
673+
options: resultInfo.options,
674+
hasLoweredAddresses: resultInfo.hasLoweredAddresses)
659675
}
676+
677+
resultMap.append(
678+
MappedResult(
679+
argumentIndex: indirectResultIdx, resultIndex: resultIndex,
680+
expandedElements: mappedResultInfos))
681+
newResults += mappedResultInfos
682+
660683
}
661684

662685
assert(
663-
indirectResultIdx == function.argumentConventions.firstParameterIndex,
686+
indirectResultIterator.isEmpty,
664687
"We should have walked through all the indirect results, and no further.")
665688

666689
return (newResults, resultMap)
@@ -726,31 +749,36 @@ private struct PackExplodedFunction {
726749
let originalValue = originalReturn.returnedValue
727750

728751
let originalReturnTupleElements: [Value]
729-
if originalValue.type.isTuple {
752+
if originalValue.type.isVoid {
753+
originalReturnTupleElements = []
754+
} else if originalValue.type.isTuple {
730755
originalReturnTupleElements = [Value](
731756
builder.createDestructureTuple(tuple: originalValue).results)
732757
} else {
733758
originalReturnTupleElements = [originalValue]
734759
}
735760

736-
var returnValues = [any Value]()
737-
738761
// Thread together the original and exploded direct return values.
739-
var resultMapIndex = 0
740-
var originalReturnIndex = 0
741-
for (i, originalResult) in self.original.convention.results.enumerated()
742-
where originalResult.type.shouldExplode
743-
|| !originalResult.isSILIndirect
744-
{
745-
if !resultMap.indices.contains(resultMapIndex) || resultMap[resultMapIndex].resultIndex != i {
746-
returnValues.append(originalReturnTupleElements[originalReturnIndex])
747-
originalReturnIndex += 1
748-
749-
} else {
762+
let theReturnValues: [any Value]
763+
do {
764+
var returnValues = [any Value]()
765+
// The next original result to process.
766+
var resultIndex = 0
767+
var originalDirectResultIterator = originalReturnTupleElements.makeIterator()
768+
769+
for mappedResult in resultMap {
770+
771+
// Collect any direct results before the next mappedResult.
772+
while resultIndex < mappedResult.resultIndex {
773+
if !self.original.convention.results[resultIndex].isSILIndirect {
774+
returnValues.append(originalDirectResultIterator.next()!)
775+
}
776+
resultIndex += 1
777+
}
750778

751-
let mapped = resultMap[resultMapIndex]
779+
assert(resultIndex == mappedResult.resultIndex, "The next pack result is not skipped.")
752780

753-
let argumentMapping = argumentMap[mapped.argumentIndex]!
781+
let argumentMapping = argumentMap[mappedResult.argumentIndex]!
754782
for argument in argumentMapping.arguments {
755783

756784
switch argument.resources {
@@ -769,18 +797,26 @@ private struct PackExplodedFunction {
769797
}
770798
}
771799

772-
resultMapIndex += 1
800+
// We have finished processing mappedResult, so step forward.
801+
resultIndex += 1
773802
}
803+
804+
// Collect any remaining original direct results.
805+
while let directResult = originalDirectResultIterator.next() {
806+
returnValues.append(directResult)
807+
}
808+
809+
theReturnValues = returnValues
774810
}
775811

776-
// Return the single value directly, rather than constructing a single-element tuple for it.
777-
if returnValues.count == 1 {
778-
builder.createReturn(of: returnValues[0])
812+
// Return a single return value directly, rather than constructing a single-element tuple for it.
813+
if theReturnValues.count == 1 {
814+
builder.createReturn(of: theReturnValues[0])
779815
} else {
780-
let tupleElementTypes = returnValues.map { $0.type }
816+
let tupleElementTypes = theReturnValues.map { $0.type }
781817
let tupleType = context.getTupleType(elements: tupleElementTypes).loweredType(
782818
in: specialized)
783-
let tuple = builder.createTuple(type: tupleType, elements: returnValues)
819+
let tuple = builder.createTuple(type: tupleType, elements: theReturnValues)
784820
builder.createReturn(of: tuple)
785821
}
786822

@@ -1026,7 +1062,7 @@ private func loadOwnership(for value: any Value, normal: LoadInst.LoadOwnership)
10261062
}
10271063
}
10281064

1029-
extension TypeProperties {
1065+
extension Type {
10301066
/// A pack argument can explode if it contains no pack expansion types
10311067
fileprivate var shouldExplode: Bool {
10321068
// For now, we only attempt to explode indirect packs, since these are the most common and inefficient.

lib/SILOptimizer/PassManager/PassPipeline.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -527,7 +527,7 @@ void addFunctionPasses(SILPassPipelinePlan &P,
527527
// of embedded Swift.
528528
if (!P.getOptions().EmbeddedSwift) {
529529
P.addGenericSpecializer();
530-
// P.addPackSpecialization();
530+
P.addPackSpecialization();
531531
// Run devirtualizer after the specializer, because many
532532
// class_method/witness_method instructions may use concrete types now.
533533
P.addDevirtualizer();

0 commit comments

Comments
 (0)