Files

152 lines
5.2 KiB
Swift

//===--- SimplifyAllocStack.swift -----------------------------------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2026 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 SIL
/// Eliminate an alloc_pack with a fully concrete indirect pack type. To do this
/// we (currently) require the following:
///
/// - The pack is indirect (pack elements are addresses). This is currently true
/// for (almost) all packs.
///
/// - The pack contains no pack expansion types.
///
/// - The only users of the pack are pack_element_get, pack_element_set and
/// dealloc_pack instructions.
///
/// - Every pack_element_{get,set} instruction that accesses the pack uses a
/// scalar_pack_index. This means the accessed pack element is statically
/// known, so we can replace each pack_element_get with the corresponding
/// address that was stored by pack_element_set.
///
/// - Each element of the pack is set by exactly one pack_element_set
/// instruction. This is currently always true for SIL generated by the
/// compiler.
///
/// Before:
///
/// %0 = alloc_pack $Pack{Int, Float}
/// %1 = scalar_pack_index 0 of $Pack{Int, Float}
/// pack_element_set %intptr into %1 of %0
/// %2 = scalar_pack_index 1 of $Pack{Int, Float}
/// pack_element_set %floatptr into %2 of %0
/// ...
/// %intptr2 = pack_element_get %1 of %0
/// use_int_ptr %intptr2
/// %floatptr2 = pack_element_get %2 of %0
/// use_float_ptr %floatptr2
/// dealloc_pack %0
///
/// After:
///
/// use_int_ptr %intptr
/// use_float_ptr %floatptr
///
extension AllocPackInst : Simplifiable, SILCombineSimplifiable {
func simplify(_ context: SimplifyContext) {
let packType = self.packType.silType!
if !packType.isSILPackElementAddress || packType.containsSILPackExpansionType {
return
}
// Collect users.
var packElementGets: [(index: Int, instruction: PackElementGetInst)] = []
var packElementSets: [(index: Int, instruction: PackElementSetInst)] = []
var deallocPacks: [DeallocPackInst] = []
var dynamicSet = false
var dynamicGet = false
for user in self.users {
switch user {
case let peg as PackElementGetInst:
// Can only eliminate a pack that is accessed with scalar indices.
if let spi = peg.indexOperand.value as? ScalarPackIndexInst {
packElementGets.append((index: spi.componentIndex, instruction: peg))
} else {
dynamicGet = true
}
case let pes as PackElementSetInst:
// Can only eliminate a pack that is accessed with scalar indices.
if let spi = pes.indexOperand.value as? ScalarPackIndexInst {
packElementSets.append((index: spi.componentIndex, instruction: pes))
} else {
dynamicSet = true
}
case let dealloc as DeallocPackInst:
deallocPacks.append(dealloc)
case is DebugValueInst:
// TODO: Salvage debug info.
continue
default:
// The pack cannot be eliminated if any other type of instruction uses it.
return
}
}
// If there are no gets, any sets are dead, so we can immediately erase all
// other instructions.
if packElementGets.isEmpty && !dynamicGet {
context.erase(instructionIncludingAllUsers: self)
return
}
// If one of the gets or sets used a non-scalar pack index, we cannot
// eliminate the pack.
if dynamicGet || dynamicSet {
return
}
// Verify that each pack element is set by exactly one pack_element_set.
if packElementSets.count != packType.packElements.count {
// Either a pack element is not set (undefined behaviour), or a pack
// element is set more than once (which we do not currently handle).
return
}
packElementSets.sort(by: { $0.index < $1.index })
for (i, entry) in packElementSets.enumerated() {
if i != entry.index {
// Either a pack element is not set (undefined behaviour), or a pack
// element is set more than once (which we do not currently handle).
return
}
}
// Now we know that the pack element at index i always has value
// packElementSets[i].value, so we can replace all corresponding gets with
// it. Note that this requires no control flow analysis because it is
// undefined behaviour to get a pack element before it is set.
for (index, peg) in packElementGets {
peg.replace(with: packElementSets[index].instruction.valueOperand.value, context)
}
// Salvage any debug info by creating a debug_value with an
// op_tuple_fragment for each pack_element set.
//
// TODO: Bridge the APIs for building DI expressions so we can do this
// directly within the pass, rather than calling out to salvageDebugInfo.
for (_, pes) in packElementSets {
context.salvageDebugInfo(of: pes)
}
// Erase the alloc_pack and all its associated instructions.
context.erase(instructionIncludingAllUsers: self)
}
}