Files
swift-mirror/SwiftCompilerSources/Sources/Optimizer/InstructionSimplification/SimplifyLoad.swift
2025-08-28 20:57:57 +01:00

450 lines
14 KiB
Swift

//===--- SimplifyLoad.swift -----------------------------------------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2023 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
extension LoadInst : OnoneSimplifiable, SILCombineSimplifiable {
func simplify(_ context: SimplifyContext) {
if optimizeLoadOfAddrUpcast(context) {
return
}
if optimizeLoadFromStringLiteral(context) {
return
}
if optimizeLoadFromEmptyCollection(context) {
return
}
if replaceLoadOfGlobalLet(context) {
return
}
removeIfDead(context)
}
/// ```
/// %1 = unchecked_addr_cast %0 : $*DerivedClass to $*BaseClass
/// %2 = load %1
/// ```
/// is transformed to
/// ```
/// %1 = load %0 : $*BaseClass
/// %2 = upcast %1 : $DerivedClass to $BaseClass
/// ```
private func optimizeLoadOfAddrUpcast(_ context: SimplifyContext) -> Bool {
if let uac = address as? UncheckedAddrCastInst,
uac.type.isExactSuperclass(of: uac.fromAddress.type),
uac.type != uac.fromAddress.type {
operand.set(to: uac.fromAddress, context)
let builder = Builder(before: self, context)
let newLoad = builder.createLoad(fromAddress: uac.fromAddress, ownership: loadOwnership)
let cast = builder.createUpcast(from: newLoad, to: type)
uses.replaceAll(with: cast, context)
context.erase(instruction: self)
return true
}
return false
}
/// The load from a string literal element, e.g.
/// ```
/// %0 = string_literal utf8 "abc"
/// %1 = integer_literal $Builtin.Word, 1
/// %2 = pointer_to_address %0 : $Builtin.RawPointer to [strict] $*UInt8
/// %3 = index_addr %2 : $*UInt8, %1 : $Builtin.Word
/// %4 = struct_element_addr %3 : $*UInt8, #UInt8._value
/// %5 = load %4 : $*Builtin.Int8
/// ```
/// is replaced by the character value, e.g. `98` in this example.
///
private func optimizeLoadFromStringLiteral(_ context: SimplifyContext) -> Bool {
if self.type.isBuiltinInteger(withFixedWidth: 8),
let sea = self.address as? StructElementAddrInst,
let (baseAddr, index) = sea.struct.getBaseAddressAndOffset(),
let pta = baseAddr as? PointerToAddressInst,
let stringLiteral = pta.pointer as? StringLiteralInst,
stringLiteral.encoding == .UTF8,
index < stringLiteral.value.count {
let builder = Builder(before: self, context)
let charLiteral = builder.createIntegerLiteral(Int(stringLiteral.value[index]), type: type)
uses.replaceAll(with: charLiteral, context)
context.erase(instruction: self)
return true
}
return false
}
/// Loading `count` or `capacity` from the empty `Array`, `Set` or `Dictionary` singleton
/// is replaced by a 0 literal.
private func optimizeLoadFromEmptyCollection(_ context: SimplifyContext) -> Bool {
if self.isZeroLoadFromEmptyCollection() {
let builder = Builder(before: self, context)
let zeroLiteral = builder.createIntegerLiteral(0, type: type)
uses.replaceAll(with: zeroLiteral, context)
context.erase(instruction: self)
return true
}
return false
}
/// The load of a global let variable is replaced by its static initializer value.
private func replaceLoadOfGlobalLet(_ context: SimplifyContext) -> Bool {
guard let globalInitVal = getGlobalInitValue(address: address, context) else {
return false
}
if !globalInitVal.canBeCopied(into: parentFunction, context) {
return false
}
var cloner = Cloner(cloneBefore: self, context)
defer { cloner.deinitialize() }
let initVal = cloner.cloneRecursivelyToGlobal(value: globalInitVal)
uses.replaceAll(with: initVal, context)
// Also erases a builtin "once" on which the global_addr depends on. This is fine
// because we only replace the load if the global init function doesn't have any side effect.
transitivelyErase(load: self, context)
return true
}
private func isZeroLoadFromEmptyCollection() -> Bool {
if !type.isBuiltinInteger {
return false
}
var addr = address
// Find the root object of the load-address.
while true {
switch addr {
case let ga as GlobalAddrInst:
switch ga.global.name {
case "_swiftEmptyArrayStorage",
"_swiftEmptyDictionarySingleton",
"_swiftEmptySetSingleton":
return true
default:
return false
}
case let sea as StructElementAddrInst:
let structType = sea.struct.type
if structType.nominal!.name == "_SwiftArrayBodyStorage" {
guard let fields = structType.getNominalFields(in: parentFunction) else {
return false
}
switch fields.getNameOfField(withIndex: sea.fieldIndex) {
case "count":
break
case "_capacityAndFlags":
if uses.contains(where: { !$0.instruction.isShiftRightByAtLeastOne }) {
return false
}
default:
return false
}
}
addr = sea.struct
case let rea as RefElementAddrInst:
let classType = rea.instance.type
switch classType.nominal!.name {
case "__RawDictionaryStorage",
"__RawSetStorage":
// For Dictionary and Set we support "count" and "capacity".
guard let fields = classType.getNominalFields(in: parentFunction) else {
return false
}
switch fields.getNameOfField(withIndex: rea.fieldIndex) {
case "_count", "_capacity":
break
default:
return false
}
default:
break
}
addr = rea.instance
case is UncheckedRefCastInst,
is UpcastInst,
is RawPointerToRefInst,
is AddressToPointerInst,
is BeginBorrowInst,
is CopyValueInst,
is EndCOWMutationInst:
addr = (addr as! UnaryInstruction).operand.value
case let mviResult as MultipleValueInstructionResult:
guard let bci = mviResult.parentInstruction as? BeginCOWMutationInst else {
return false
}
addr = bci.instance
default:
return false
}
}
}
/// Removes the `load [copy]` if the loaded value is just destroyed.
private func removeIfDead(_ context: SimplifyContext) {
if loadOwnership == .copy,
loadedValueIsDead(context) {
for use in uses {
context.erase(instruction: use.instruction)
}
context.erase(instruction: self)
}
}
private func loadedValueIsDead(_ context: SimplifyContext) -> Bool {
if context.preserveDebugInfo {
return !uses.contains { !($0.instruction is DestroyValueInst) }
} else {
return !uses.ignoreDebugUses.contains { !($0.instruction is DestroyValueInst) }
}
}
}
/// Returns the init value of a global which is loaded from `address`.
private func getGlobalInitValue(address: Value, _ context: SimplifyContext) -> Value? {
switch address {
case let gai as GlobalAddrInst:
if gai.global.isLet {
if let staticInitValue = gai.global.staticInitValue {
return staticInitValue
}
if let staticInitValue = getInitializerFromInitFunction(of: gai, context) {
return staticInitValue
}
}
case let pta as PointerToAddressInst:
return globalLoadedViaAddressor(pointer: pta.pointer)?.staticInitValue
case let sea as StructElementAddrInst:
if let structVal = getGlobalInitValue(address: sea.struct, context) as? StructInst {
return structVal.operands[sea.fieldIndex].value
}
case let tea as TupleElementAddrInst:
if let tupleVal = getGlobalInitValue(address: tea.tuple, context) as? TupleInst {
return tupleVal.operands[tea.fieldIndex].value
}
case let bai as BeginAccessInst:
return getGlobalInitValue(address: bai.address, context)
case let rta as RefTailAddrInst:
return getGlobalTailElement(of: rta, index: 0)
case let ia as IndexAddrInst:
if let rta = ia.base as? RefTailAddrInst,
let literal = ia.index as? IntegerLiteralInst,
let index = literal.value
{
return getGlobalTailElement(of: rta, index: index)
}
case let rea as RefElementAddrInst:
if let object = rea.instance.immutableGlobalObjectRoot {
return object.baseOperands[rea.fieldIndex].value
}
default:
break
}
return nil
}
private func getGlobalTailElement(of refTailAddr: RefTailAddrInst, index: Int) -> Value? {
if let object = refTailAddr.instance.immutableGlobalObjectRoot,
index >= 0 && index < object.tailOperands.count
{
return object.tailOperands[index].value
}
return nil
}
private func getInitializerFromInitFunction(of globalAddr: GlobalAddrInst, _ context: SimplifyContext) -> Value? {
guard let dependentOn = globalAddr.dependencyToken,
let builtinOnce = dependentOn as? BuiltinInst,
builtinOnce.id == .Once,
let initFnRef = builtinOnce.operands[1].value as? FunctionRefInst else
{
return nil
}
let initFn = initFnRef.referencedFunction
context.notifyDependency(onBodyOf: initFn)
guard let (_, storeToGlobal) = getGlobalInitialization(of: initFn, context, handleUnknownInstruction: {
// Accept `global_value` because the class header can be initialized at runtime by the `global_value` instruction.
return $0 is GlobalValueInst
}) else {
return nil
}
return storeToGlobal.source
}
private func globalLoadedViaAddressor(pointer: Value) -> GlobalVariable? {
if let ai = pointer as? ApplyInst,
let callee = ai.referencedFunction,
let global = callee.globalOfGlobalInitFunction,
global.isLet {
return global
}
return nil
}
private func transitivelyErase(load: LoadInst, _ context: SimplifyContext) {
var inst: SingleValueInstruction = load
while inst.uses.isEmpty {
if inst.operands.count != 1 {
context.erase(instruction: inst)
return
}
guard let operandInst = inst.operands[0].value as? SingleValueInstruction else {
return
}
context.erase(instruction: inst)
inst = operandInst
}
}
private extension Value {
func canBeCopied(into function: Function, _ context: SimplifyContext) -> Bool {
// Can't use `ValueSet` because the this value is inside a global initializer and
// not inside a function.
var worklist = Stack<Value>(context)
defer { worklist.deinitialize() }
var handled = Set<ObjectIdentifier>()
worklist.push(self)
handled.insert(ObjectIdentifier(self))
while let value = worklist.pop() {
if value is VectorInst {
return false
}
if let fri = value as? FunctionRefInst {
if function.isAnySerialized,
!fri.referencedFunction.hasValidLinkageForFragileRef(function.serializedKind)
{
return false
}
}
for op in value.definingInstruction!.operands {
if handled.insert(ObjectIdentifier(op.value)).inserted {
worklist.push(op.value)
}
}
}
return true
}
func getBaseAddressAndOffset() -> (baseAddress: Value, offset: Int)? {
if let indexAddr = self as? IndexAddrInst {
guard let indexLiteral = indexAddr.index as? IntegerLiteralInst,
let indexValue = indexLiteral.value else
{
return nil
}
return (baseAddress: indexAddr.base, offset: indexValue)
}
return (baseAddress: self, offset: 0)
}
// If the reference-root of self references a global object, returns the `object` instruction of the
// global's initializer. But only if the global is a let-global.
var immutableGlobalObjectRoot: ObjectInst? {
if let gv = self.referenceRoot as? GlobalValueInst,
gv.global.isLet,
let initval = gv.global.staticInitValue,
let object = initval as? ObjectInst
{
return object
}
return nil
}
}
private extension Instruction {
var isShiftRightByAtLeastOne: Bool {
guard let bi = self as? BuiltinInst,
bi.id == .LShr,
let shiftLiteral = bi.operands[1].value as? IntegerLiteralInst,
let shiftValue = shiftLiteral.value else
{
return false
}
return shiftValue > 0
}
}
/// Analyses the global initializer function and returns the `alloc_global` and `store`
/// instructions which initialize the global.
/// Returns nil if `function` has any side-effects beside initializing the global.
///
/// The function's single basic block must contain following code pattern:
/// ```
/// alloc_global @the_global
/// %a = global_addr @the_global
/// %i = some_const_initializer_insts
/// store %i to %a
/// ```
///
/// For all other instructions `handleUnknownInstruction` is called and such an instruction
/// is accepted if `handleUnknownInstruction` returns true.
private func getGlobalInitialization(
of function: Function,
_ context: some Context,
handleUnknownInstruction: (Instruction) -> Bool
) -> (allocInst: AllocGlobalInst, storeToGlobal: StoreInst)? {
guard let block = function.blocks.singleElement else {
return nil
}
var allocInst: AllocGlobalInst? = nil
var globalAddr: GlobalAddrInst? = nil
var store: StoreInst? = nil
for inst in block.instructions {
switch inst {
case is ReturnInst,
is DebugValueInst,
is DebugStepInst,
is BeginAccessInst,
is EndAccessInst:
continue
case let agi as AllocGlobalInst:
if allocInst == nil {
allocInst = agi
continue
}
case let ga as GlobalAddrInst:
if let agi = allocInst, agi.global == ga.global {
globalAddr = ga
}
continue
case let si as StoreInst:
if store == nil,
let ga = globalAddr,
si.destination == ga
{
store = si
continue
}
// Note that the initializer must not contain a `global_value` because `global_value` needs to
// initialize the class metadata at runtime.
default:
if inst.isValidInStaticInitializerOfGlobal(context) {
continue
}
}
if handleUnknownInstruction(inst) {
continue
}
return nil
}
if let store = store {
return (allocInst: allocInst!, storeToGlobal: store)
}
return nil
}