mirror of
https://github.com/apple/swift.git
synced 2025-12-14 20:36:38 +01:00
The assertion is hit through `TypeValueInst.simplify` when constructing an integer literal instruction with a negative 64-bit `Swift.Int` and a bit width of 32 (the target pointer bit width for arm64_32 watchOS). This happens because we tell the `llvm::APInt` constructor to treat the input integer as unsigned by default in `getAPInt`, and a negative 64-bit signed integer does not fit into 32 bits when interpreted as unsigned. Fix this by flipping the default signedness assumption for the Swift API and introducing a convenience method for constructing a 1-bit integer literal instruction, where the correct signedness assumption depends on whether you want to use 1 or -1 for 'true'. In the context of using an integer to construct an `llvm::APInt`, there are 2 other cases where signedness matters that come to mind: 1. A non-decimal integer literal narrower than 64 bits, such as `0xABCD`, is used. 2. The desired bit width is >64, since `llvm::APInt` can either zero-extend or sign-extend the 64-bit integer it accepts. Neither of these appear to be exercised in SwiftCompilerSources, and if we ever do, the caller should be responsible for either (1) appropriately extending the literal manually, e.g. `Int(Int16(bitPattern: 0xABCD))`, or (2) passing along the appropriate signedness.
330 lines
13 KiB
Swift
330 lines
13 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)
|
|
}
|
|
|
|
func devirtualizeDeinits(of builtin: BuiltinInst, _ context: some MutatingContext) -> Bool {
|
|
switch builtin.id {
|
|
case .DestroyArray:
|
|
return devirtualize(builtinDestroyArray: builtin, context)
|
|
default:
|
|
return true
|
|
}
|
|
}
|
|
|
|
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 func devirtualize(builtinDestroyArray: BuiltinInst, _ context: some MutatingContext) -> Bool {
|
|
let function = builtinDestroyArray.parentFunction
|
|
let elementType = builtinDestroyArray.substitutionMap.replacementType.loweredType(in: function)
|
|
guard elementType.isMoveOnly,
|
|
// This avoids lowering the loop if the element is a non-copyable generic type.
|
|
(elementType.isStruct || elementType.isEnum)
|
|
else {
|
|
return true
|
|
}
|
|
|
|
// Lower the `builtin "destroyArray" to a loop which destroys all elements
|
|
|
|
let basePointer = builtinDestroyArray.arguments[1]
|
|
let arrayCount = builtinDestroyArray.arguments[2]
|
|
let indexType = arrayCount.type
|
|
let boolType = context.getBuiltinIntegerType(bitWidth: 1)
|
|
|
|
let preheaderBlock = builtinDestroyArray.parentBlock
|
|
let exitBlock = context.splitBlock(after: builtinDestroyArray)
|
|
let headerBlock = context.createBlock(after: preheaderBlock)
|
|
let bodyBlock = context.createBlock(after: headerBlock)
|
|
|
|
let preheaderBuilder = Builder(atEndOf: preheaderBlock, location: builtinDestroyArray.location, context)
|
|
let zero = preheaderBuilder.createIntegerLiteral(0, type: indexType)
|
|
let one = preheaderBuilder.createIntegerLiteral(1, type: indexType)
|
|
let falseValue = preheaderBuilder.createBoolLiteral(false);
|
|
let baseAddress = preheaderBuilder.createPointerToAddress(pointer: basePointer,
|
|
addressType: elementType.addressType,
|
|
isStrict: true, isInvariant: false)
|
|
preheaderBuilder.createBranch(to: headerBlock, arguments: [zero])
|
|
|
|
let inductionVariable = headerBlock.addArgument(type: indexType, ownership: .none, context)
|
|
let headerBuilder = Builder(atEndOf: headerBlock, location: builtinDestroyArray.location, context)
|
|
let cmp = headerBuilder.createBuiltinBinaryFunction(name: "cmp_slt", operandType: indexType, resultType: boolType,
|
|
arguments: [inductionVariable, arrayCount])
|
|
headerBuilder.createCondBranch(condition: cmp, trueBlock: bodyBlock, falseBlock: exitBlock)
|
|
|
|
let bodyBuilder = Builder(atEndOf: bodyBlock, location: builtinDestroyArray.location, context)
|
|
let elementAddr = bodyBuilder.createIndexAddr(base: baseAddress, index: inductionVariable,
|
|
needStackProtection: false)
|
|
let destroy = bodyBuilder.createDestroyAddr(address: elementAddr)
|
|
let resultType = context.getTupleType(elements: [indexType, boolType]).loweredType(in: function)
|
|
let increment = bodyBuilder.createBuiltinBinaryFunction(name: "sadd_with_overflow", operandType: indexType,
|
|
resultType: resultType,
|
|
arguments: [inductionVariable, one, falseValue])
|
|
let incrResult = bodyBuilder.createTupleExtract(tuple: increment, elementIndex: 0)
|
|
bodyBuilder.createBranch(to: headerBlock, arguments: [incrResult])
|
|
|
|
context.erase(instruction: builtinDestroyArray)
|
|
|
|
return devirtualize(destroy: destroy, context)
|
|
}
|
|
|
|
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)})
|
|
}
|
|
}
|