mirror of
https://github.com/apple/swift.git
synced 2026-06-20 15:42:51 +02:00
152 lines
5.2 KiB
Swift
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)
|
|
}
|
|
}
|
|
|