mirror of
https://github.com/apple/swift.git
synced 2025-12-14 20:36:38 +01:00
250 lines
9.1 KiB
Swift
250 lines
9.1 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
|
|
}
|
|
|
|
if !type.isNominal {
|
|
// E.g. a non-copyable generic function parameter
|
|
return true
|
|
}
|
|
|
|
let result: Bool
|
|
if type.nominal.hasValueDeinit && !destroy.shouldDropDeinit {
|
|
guard let deinitFunc = context.lookupDeinit(ofNominal: type.nominal) else {
|
|
return false
|
|
}
|
|
destroy.createDeinitCall(to: deinitFunc, context)
|
|
result = true
|
|
} else {
|
|
// 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 {
|
|
result = destroy.devirtualizeStructFields(context)
|
|
} else if type.isEnum {
|
|
result = destroy.devirtualizeEnumPayloads(context)
|
|
} else {
|
|
precondition(type.isClass, "unknown non-copyable type")
|
|
// A class reference cannot be further de-composed.
|
|
return true
|
|
}
|
|
}
|
|
context.erase(instruction: destroy)
|
|
return result
|
|
}
|
|
|
|
// 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
|
|
}
|
|
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 {
|
|
let builder = Builder(before: self, context)
|
|
|
|
guard let fields = type.getNominalFields(in: parentFunction) else {
|
|
return false
|
|
}
|
|
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
|
|
}
|
|
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)})
|
|
}
|
|
}
|