Files
swift-mirror/SwiftCompilerSources/Sources/Optimizer/Utilities/Devirtualization.swift
Erik Eckstein 380ea889bc SIL: fix self-argument APIs in Function
`var selfArgument` should return an optional, i.e. nil if the function doesn't have a self argument.
2025-03-26 07:30:10 +01:00

268 lines
9.6 KiB
Swift

//===--- Devirtualization.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
/// Devirtualizes all value-type deinitializers of a `destroy_value`.
///
/// This may be a no-op if the destroy doesn't call any deinitializers.
/// Returns true if all deinitializers could be devirtualized.
func devirtualizeDeinits(of destroy: DestroyValueInst, _ context: some MutatingContext) -> Bool {
return devirtualize(destroy: destroy, context)
}
/// Devirtualizes all value-type deinitializers of a `destroy_addr`.
///
/// This may be a no-op if the destroy doesn't call any deinitializers.
/// Returns true if all deinitializers could be devirtualized.
func devirtualizeDeinits(of destroy: DestroyAddrInst, _ context: some MutatingContext) -> Bool {
return devirtualize(destroy: destroy, context)
}
private func devirtualize(destroy: some DevirtualizableDestroy, _ context: some MutatingContext) -> Bool {
let type = destroy.type
if !type.isMoveOnly {
return true
}
guard let nominal = type.nominal else {
// E.g. a non-copyable generic function parameter
return true
}
// We cannot de-virtualize C++ destructor calls of C++ move-only types because we cannot get
// its destructor (`nominal.valueTypeDestructor` is nil).
if nominal.hasClangNode {
return false
}
if nominal.valueTypeDestructor != nil && !destroy.shouldDropDeinit {
guard let deinitFunc = context.lookupDeinit(ofNominal: nominal) else {
return false
}
if deinitFunc.linkage == .shared && !deinitFunc.isDefinition {
// Make sure to not have an external shared function, which is illegal in SIL.
_ = context.loadFunction(function: deinitFunc, loadCalleesRecursively: false)
}
destroy.createDeinitCall(to: deinitFunc, context)
context.erase(instruction: destroy)
return true
}
// If there is no deinit to be called for the original type we have to recursively visit
// the struct fields or enum cases.
if type.isStruct {
return destroy.devirtualizeStructFields(context)
}
if type.isEnum {
return destroy.devirtualizeEnumPayloads(context)
}
precondition(type.isClass, "unknown non-copyable type")
// A class reference cannot be further de-composed.
return true
}
// Used to dispatch devirtualization tasks to `destroy_value` and `destroy_addr`.
private protocol DevirtualizableDestroy : UnaryInstruction {
var shouldDropDeinit: Bool { get }
func createDeinitCall(to deinitializer: Function, _ context: some MutatingContext)
func devirtualizeStructFields(_ context: some MutatingContext) -> Bool
func devirtualizeEnumPayload(enumCase: EnumCase, in block: BasicBlock, _ context: some MutatingContext) -> Bool
func createSwitchEnum(atEndOf block: BasicBlock, cases: [(Int, BasicBlock)], _ context: some MutatingContext)
}
private extension DevirtualizableDestroy {
var type: Type { operand.value.type }
func devirtualizeEnumPayloads(_ context: some MutatingContext) -> Bool {
guard let cases = type.getEnumCases(in: parentFunction) else {
return false
}
defer {
context.erase(instruction: self)
}
if cases.allPayloadsAreTrivial(in: parentFunction) {
let builder = Builder(before: self, context)
builder.createEndLifetime(of: operand.value)
return true
}
var caseBlocks: [(caseIndex: Int, targetBlock: BasicBlock)] = []
let switchBlock = parentBlock
let endBlock = context.splitBlock(before: self)
var result = true
for enumCase in cases {
let caseBlock = context.createBlock(after: switchBlock)
caseBlocks.append((enumCase.index, caseBlock))
let builder = Builder(atEndOf: caseBlock, location: location, context)
builder.createBranch(to: endBlock)
if !devirtualizeEnumPayload(enumCase: enumCase, in: caseBlock, context) {
result = false
}
}
createSwitchEnum(atEndOf: switchBlock, cases: caseBlocks, context)
return result
}
}
extension DestroyValueInst : DevirtualizableDestroy {
fileprivate var shouldDropDeinit: Bool { operand.value.lookThoughOwnershipInstructions is DropDeinitInst }
fileprivate func createDeinitCall(to deinitializer: Function, _ context: some MutatingContext) {
let builder = Builder(before: self, context)
let subs = context.getContextSubstitutionMap(for: type)
let deinitRef = builder.createFunctionRef(deinitializer)
if deinitializer.argumentConventions[deinitializer.selfArgumentIndex!].isIndirect {
let allocStack = builder.createAllocStack(type)
builder.createStore(source: destroyedValue, destination: allocStack, ownership: .initialize)
builder.createApply(function: deinitRef, subs, arguments: [allocStack])
builder.createDeallocStack(allocStack)
} else {
builder.createApply(function: deinitRef, subs, arguments: [destroyedValue])
}
}
fileprivate func devirtualizeStructFields(_ context: some MutatingContext) -> Bool {
guard let fields = type.getNominalFields(in: parentFunction) else {
return false
}
defer {
context.erase(instruction: self)
}
let builder = Builder(before: self, context)
if fields.allFieldsAreTrivial(in: parentFunction) {
builder.createEndLifetime(of: operand.value)
return true
}
let destructure = builder.createDestructureStruct(struct: destroyedValue)
var result = true
for fieldValue in destructure.results where !fieldValue.type.isTrivial(in: parentFunction) {
let destroyField = builder.createDestroyValue(operand: fieldValue)
if !devirtualizeDeinits(of: destroyField, context) {
result = false
}
}
return result
}
fileprivate func devirtualizeEnumPayload(
enumCase: EnumCase,
in block: BasicBlock,
_ context: some MutatingContext
) -> Bool {
let builder = Builder(atBeginOf: block, location: location, context)
if let payloadTy = enumCase.payload {
let payload = block.addArgument(type: payloadTy, ownership: .owned, context)
if !payloadTy.isTrivial(in: parentFunction) {
let destroyPayload = builder.createDestroyValue(operand: payload)
return devirtualizeDeinits(of: destroyPayload, context)
}
}
return true
}
fileprivate func createSwitchEnum(
atEndOf block: BasicBlock,
cases: [(Int, BasicBlock)],
_ context: some MutatingContext
) {
let builder = Builder(atEndOf: block, location: location, context)
builder.createSwitchEnum(enum: destroyedValue, cases: cases)
}
}
extension DestroyAddrInst : DevirtualizableDestroy {
fileprivate var shouldDropDeinit: Bool {
// The deinit is always called by a destroy_addr. There must not be a `drop_deinit` as operand.
false
}
fileprivate func createDeinitCall(to deinitializer: Function, _ context: some MutatingContext) {
let builder = Builder(before: self, context)
let subs = context.getContextSubstitutionMap(for: destroyedAddress.type)
let deinitRef = builder.createFunctionRef(deinitializer)
if !deinitializer.argumentConventions[deinitializer.selfArgumentIndex!].isIndirect {
let value = builder.createLoad(fromAddress: destroyedAddress, ownership: .take)
builder.createApply(function: deinitRef, subs, arguments: [value])
} else {
builder.createApply(function: deinitRef, subs, arguments: [destroyedAddress])
}
}
fileprivate func devirtualizeStructFields(_ context: some MutatingContext) -> Bool {
let builder = Builder(before: self, context)
guard let fields = type.getNominalFields(in: parentFunction) else {
return false
}
defer {
context.erase(instruction: self)
}
if fields.allFieldsAreTrivial(in: parentFunction) {
builder.createEndLifetime(of: operand.value)
return true
}
var result = true
for (fieldIdx, fieldTy) in fields.enumerated()
where !fieldTy.isTrivial(in: parentFunction)
{
let fieldAddr = builder.createStructElementAddr(structAddress: destroyedAddress, fieldIndex: fieldIdx)
let destroyField = builder.createDestroyAddr(address: fieldAddr)
if !devirtualizeDeinits(of: destroyField, context) {
result = false
}
}
return result
}
fileprivate func devirtualizeEnumPayload(
enumCase: EnumCase,
in block: BasicBlock,
_ context: some MutatingContext
) -> Bool {
let builder = Builder(atBeginOf: block, location: location, context)
if let payloadTy = enumCase.payload,
!payloadTy.isTrivial(in: parentFunction)
{
let caseAddr = builder.createUncheckedTakeEnumDataAddr(enumAddress: destroyedAddress, caseIndex: enumCase.index)
let destroyPayload = builder.createDestroyAddr(address: caseAddr)
return devirtualizeDeinits(of: destroyPayload, context)
}
return true
}
fileprivate func createSwitchEnum(
atEndOf block: BasicBlock,
cases: [(Int, BasicBlock)],
_ context: some MutatingContext
) {
let builder = Builder(atEndOf: block, location: location, context)
builder.createSwitchEnumAddr(enumAddress: destroyedAddress, cases: cases)
}
}
private extension EnumCases {
func allPayloadsAreTrivial(in function: Function) -> Bool {
allSatisfy({ $0.payload?.isTrivial(in: function) ?? true })
}
}
private extension NominalFieldsArray {
func allFieldsAreTrivial(in function: Function) -> Bool {
allSatisfy({ $0.isTrivial(in: function)})
}
}