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.
392 lines
13 KiB
Swift
392 lines
13 KiB
Swift
//===--- SimplifyBuiltin.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 BuiltinInst : OnoneSimplifiable, SILCombineSimplifiable {
|
|
func simplify(_ context: SimplifyContext) {
|
|
switch id {
|
|
case .IsConcrete:
|
|
// Don't constant fold a Builtin.isConcrete of a type with archetypes in the middle
|
|
// of the pipeline, because a generic specializer might run afterwards which turns the
|
|
// type into a concrete type.
|
|
optimizeIsConcrete(allowArchetypes: false, context)
|
|
case .IsSameMetatype:
|
|
optimizeIsSameMetatype(context)
|
|
case .Once:
|
|
optimizeBuiltinOnce(context)
|
|
case .CanBeObjCClass:
|
|
optimizeCanBeClass(context)
|
|
case .AssertConf:
|
|
optimizeAssertConfig(context)
|
|
case .Sizeof,
|
|
.Strideof,
|
|
.Alignof:
|
|
optimizeTargetTypeConst(context)
|
|
case .DestroyArray:
|
|
let elementType = substitutionMap.replacementType.loweredType(in: parentFunction, maximallyAbstracted: true)
|
|
if elementType.isTrivial(in: parentFunction) {
|
|
context.erase(instruction: self)
|
|
return
|
|
}
|
|
optimizeArgumentToThinMetatype(argument: 0, context)
|
|
case .CopyArray,
|
|
.TakeArrayNoAlias,
|
|
.TakeArrayFrontToBack,
|
|
.TakeArrayBackToFront,
|
|
.AssignCopyArrayNoAlias,
|
|
.AssignCopyArrayFrontToBack,
|
|
.AssignCopyArrayBackToFront,
|
|
.AssignTakeArray,
|
|
.IsPOD:
|
|
optimizeArgumentToThinMetatype(argument: 0, context)
|
|
case .ICMP_EQ:
|
|
constantFoldIntegerEquality(isEqual: true, context)
|
|
case .ICMP_NE:
|
|
constantFoldIntegerEquality(isEqual: false, context)
|
|
case .Xor:
|
|
simplifyNegation(context)
|
|
default:
|
|
if let literal = context.constantFold(builtin: self) {
|
|
uses.replaceAll(with: literal, context)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
extension BuiltinInst : LateOnoneSimplifiable {
|
|
func simplifyLate(_ context: SimplifyContext) {
|
|
if id == .IsConcrete {
|
|
// At the end of the pipeline we can be sure that the isConcrete's type doesn't get "more" concrete.
|
|
optimizeIsConcrete(allowArchetypes: true, context)
|
|
} else {
|
|
simplify(context)
|
|
}
|
|
}
|
|
}
|
|
|
|
private extension BuiltinInst {
|
|
func optimizeIsConcrete(allowArchetypes: Bool, _ context: SimplifyContext) {
|
|
let hasArchetype = operands[0].value.type.hasArchetype
|
|
if hasArchetype && !allowArchetypes {
|
|
return
|
|
}
|
|
let builder = Builder(before: self, context)
|
|
let result = builder.createBoolLiteral(!hasArchetype)
|
|
uses.replaceAll(with: result, context)
|
|
context.erase(instruction: self)
|
|
}
|
|
|
|
func optimizeIsSameMetatype(_ context: SimplifyContext) {
|
|
let lhs = operands[0].value
|
|
let rhs = operands[1].value
|
|
|
|
guard let equal = typesOfValuesAreEqual(lhs, rhs, in: parentFunction) else {
|
|
return
|
|
}
|
|
let builder = Builder(before: self, context)
|
|
let result = builder.createBoolLiteral(equal)
|
|
|
|
uses.replaceAll(with: result, context)
|
|
}
|
|
|
|
func optimizeBuiltinOnce(_ context: SimplifyContext) {
|
|
guard let callee = calleeOfOnce, callee.isDefinition else {
|
|
return
|
|
}
|
|
context.notifyDependency(onBodyOf: callee)
|
|
|
|
// If the callee is side effect-free we can remove the whole builtin "once".
|
|
// We don't use the callee's memory effects but instead look at all callee instructions
|
|
// because memory effects are not computed in the Onone pipeline, yet.
|
|
// This is no problem because the callee (usually a global init function )is mostly very small,
|
|
// or contains the side-effect instruction `alloc_global` right at the beginning.
|
|
if callee.instructions.contains(where: hasSideEffectForBuiltinOnce) {
|
|
return
|
|
}
|
|
for use in uses {
|
|
let ga = use.instruction as! GlobalAddrInst
|
|
ga.clearToken(context)
|
|
}
|
|
context.erase(instruction: self)
|
|
}
|
|
|
|
var calleeOfOnce: Function? {
|
|
let callee = operands[1].value
|
|
if let fri = callee as? FunctionRefInst {
|
|
return fri.referencedFunction
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func optimizeCanBeClass(_ context: SimplifyContext) {
|
|
let literal: IntegerLiteralInst
|
|
switch substitutionMap.replacementType.canonical.canBeClass {
|
|
case .isNot:
|
|
let builder = Builder(before: self, context)
|
|
literal = builder.createIntegerLiteral(0, type: type)
|
|
case .is:
|
|
let builder = Builder(before: self, context)
|
|
literal = builder.createIntegerLiteral(1, type: type)
|
|
case .canBe:
|
|
return
|
|
}
|
|
self.replace(with: literal, context)
|
|
}
|
|
|
|
func optimizeAssertConfig(_ context: SimplifyContext) {
|
|
// The values for the assert_configuration call are:
|
|
// 0: Debug
|
|
// 1: Release
|
|
// 2: Fast / Unchecked
|
|
let config = context.options.assertConfiguration
|
|
switch config {
|
|
case .debug, .release, .unchecked:
|
|
let builder = Builder(before: self, context)
|
|
let literal = builder.createIntegerLiteral(config.integerValue, type: type)
|
|
uses.replaceAll(with: literal, context)
|
|
context.erase(instruction: self)
|
|
case .unknown:
|
|
return
|
|
}
|
|
}
|
|
|
|
func optimizeTargetTypeConst(_ context: SimplifyContext) {
|
|
let ty = substitutionMap.replacementType.loweredType(in: parentFunction, maximallyAbstracted: true)
|
|
let value: Int?
|
|
switch id {
|
|
case .Sizeof:
|
|
value = ty.getStaticSize(context: context)
|
|
case .Strideof:
|
|
if isUsedAsStrideOfIndexRawPointer(context) {
|
|
// Constant folding `stride` would prevent index_raw_pointer simplification.
|
|
// See `simplifyIndexRawPointer` in SimplifyPointerToAddress.swift.
|
|
return
|
|
}
|
|
value = ty.getStaticStride(context: context)
|
|
case .Alignof:
|
|
value = ty.getStaticAlignment(context: context)
|
|
default:
|
|
fatalError()
|
|
}
|
|
|
|
guard let value else {
|
|
return
|
|
}
|
|
|
|
let builder = Builder(before: self, context)
|
|
let literal = builder.createIntegerLiteral(value, type: type)
|
|
uses.replaceAll(with: literal, context)
|
|
context.erase(instruction: self)
|
|
}
|
|
|
|
private func isUsedAsStrideOfIndexRawPointer(_ context: SimplifyContext) -> Bool {
|
|
var worklist = ValueWorklist(context)
|
|
defer { worklist.deinitialize() }
|
|
worklist.pushIfNotVisited(self)
|
|
while let v = worklist.pop() {
|
|
for use in v.uses {
|
|
switch use.instruction {
|
|
case let builtin as BuiltinInst:
|
|
switch builtin.id {
|
|
case .SMulOver, .TruncOrBitCast, .SExtOrBitCast, .ZExtOrBitCast:
|
|
worklist.pushIfNotVisited(builtin)
|
|
default:
|
|
break
|
|
}
|
|
case let tupleExtract as TupleExtractInst where tupleExtract.fieldIndex == 0:
|
|
worklist.pushIfNotVisited(tupleExtract)
|
|
case is IndexRawPointerInst:
|
|
return true
|
|
default:
|
|
break
|
|
}
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func optimizeArgumentToThinMetatype(argument: Int, _ context: SimplifyContext) {
|
|
let type: Type
|
|
|
|
if let metatypeInst = operands[argument].value as? MetatypeInst {
|
|
type = metatypeInst.type
|
|
} else if let initExistentialInst = operands[argument].value as? InitExistentialMetatypeInst {
|
|
type = initExistentialInst.metatype.type
|
|
} else {
|
|
return
|
|
}
|
|
|
|
guard type.representationOfMetatype == .thick else {
|
|
return
|
|
}
|
|
|
|
let builder = Builder(before: self, context)
|
|
let newMetatype = builder.createMetatype(ofInstanceType: type.canonicalType.instanceTypeOfMetatype,
|
|
representation: .thin)
|
|
operands[argument].set(to: newMetatype, context)
|
|
}
|
|
|
|
func constantFoldIntegerEquality(isEqual: Bool, _ context: SimplifyContext) {
|
|
if constantFoldStringNullPointerCheck(isEqual: isEqual, context) {
|
|
return
|
|
}
|
|
if let literal = context.constantFold(builtin: self) {
|
|
uses.replaceAll(with: literal, context)
|
|
}
|
|
}
|
|
|
|
func constantFoldStringNullPointerCheck(isEqual: Bool, _ context: SimplifyContext) -> Bool {
|
|
if operands[1].value.isZeroInteger &&
|
|
operands[0].value.lookThroughScalarCasts is StringLiteralInst
|
|
{
|
|
let builder = Builder(before: self, context)
|
|
let result = builder.createBoolLiteral(!isEqual)
|
|
uses.replaceAll(with: result, context)
|
|
context.erase(instruction: self)
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
|
|
/// Replaces a builtin "xor", which negates its operand comparison
|
|
/// ```
|
|
/// %3 = builtin "cmp_slt_Int64"(%1, %2) : $Builtin.Int1
|
|
/// %4 = integer_literal $Builtin.Int1, -1
|
|
/// %5 = builtin "xor_Int1"(%3, %4) : $Builtin.Int1
|
|
/// ```
|
|
/// with the negated comparison
|
|
/// ```
|
|
/// %5 = builtin "cmp_ge_Int64"(%1, %2) : $Builtin.Int1
|
|
/// ```
|
|
func simplifyNegation(_ context: SimplifyContext) {
|
|
assert(id == .Xor)
|
|
guard let one = arguments[1] as? IntegerLiteralInst,
|
|
let oneValue = one.value,
|
|
oneValue == -1,
|
|
let lhsBuiltin = arguments[0] as? BuiltinInst,
|
|
lhsBuiltin.type.isBuiltinInteger,
|
|
let negatedBuiltinName = lhsBuiltin.negatedComparisonBuiltinName
|
|
else {
|
|
return
|
|
}
|
|
|
|
let builder = Builder(before: lhsBuiltin, context)
|
|
let negated = builder.createBuiltinBinaryFunction(name: negatedBuiltinName,
|
|
operandType: lhsBuiltin.arguments[0].type,
|
|
resultType: lhsBuiltin.type,
|
|
arguments: Array(lhsBuiltin.arguments))
|
|
self.replace(with: negated, context)
|
|
}
|
|
|
|
private var negatedComparisonBuiltinName: String? {
|
|
switch id {
|
|
case .ICMP_EQ: return "cmp_ne"
|
|
case .ICMP_NE: return "cmp_eq"
|
|
case .ICMP_SLE: return "cmp_sgt"
|
|
case .ICMP_SLT: return "cmp_sge"
|
|
case .ICMP_SGE: return "cmp_slt"
|
|
case .ICMP_SGT: return "cmp_sle"
|
|
case .ICMP_ULE: return "cmp_ugt"
|
|
case .ICMP_ULT: return "cmp_uge"
|
|
case .ICMP_UGE: return "cmp_ult"
|
|
case .ICMP_UGT: return "cmp_ule"
|
|
default:
|
|
return nil
|
|
}
|
|
}
|
|
}
|
|
|
|
private extension Value {
|
|
var isZeroInteger: Bool {
|
|
if let literal = self as? IntegerLiteralInst,
|
|
let value = literal.value
|
|
{
|
|
return value == 0
|
|
}
|
|
return false
|
|
}
|
|
|
|
var lookThroughScalarCasts: Value {
|
|
guard let bi = self as? BuiltinInst else {
|
|
return self
|
|
}
|
|
switch bi.id {
|
|
case .ZExt, .ZExtOrBitCast, .PtrToInt:
|
|
return bi.operands[0].value.lookThroughScalarCasts
|
|
default:
|
|
return self
|
|
}
|
|
}
|
|
}
|
|
|
|
private func hasSideEffectForBuiltinOnce(_ instruction: Instruction) -> Bool {
|
|
switch instruction {
|
|
case is DebugStepInst, is DebugValueInst:
|
|
return false
|
|
default:
|
|
return instruction.mayReadOrWriteMemory ||
|
|
instruction.hasUnspecifiedSideEffects
|
|
}
|
|
}
|
|
|
|
private func typesOfValuesAreEqual(_ lhs: Value, _ rhs: Value, in function: Function) -> Bool? {
|
|
if lhs == rhs {
|
|
return true
|
|
}
|
|
|
|
guard let lhsExistential = lhs as? InitExistentialMetatypeInst,
|
|
let rhsExistential = rhs as? InitExistentialMetatypeInst else {
|
|
return nil
|
|
}
|
|
|
|
let lhsTy = lhsExistential.metatype.type.canonicalType.instanceTypeOfMetatype
|
|
let rhsTy = rhsExistential.metatype.type.canonicalType.instanceTypeOfMetatype
|
|
if lhsTy.isDynamicSelf != rhsTy.isDynamicSelf {
|
|
return nil
|
|
}
|
|
|
|
// Do we know the exact types? This is not the case e.g. if a type is passed as metatype
|
|
// to the function.
|
|
let typesAreExact = lhsExistential.metatype is MetatypeInst &&
|
|
rhsExistential.metatype is MetatypeInst
|
|
|
|
if typesAreExact {
|
|
// We need to compare the not lowered types, because function types may differ in their original version
|
|
// but are equal in the lowered version, e.g.
|
|
// ((Int, Int) -> ())
|
|
// (((Int, Int)) -> ())
|
|
//
|
|
if lhsTy == rhsTy {
|
|
return true
|
|
}
|
|
// Comparing types of different classes which are in a sub-class relation is not handled by the
|
|
// cast optimizer (below).
|
|
if lhsTy.isClass && rhsTy.isClass && lhsTy.nominal != rhsTy.nominal {
|
|
return false
|
|
}
|
|
|
|
// Failing function casts are not supported by the cast optimizer (below).
|
|
// (Reason: "Be conservative about function type relationships we may add in the future.")
|
|
if lhsTy.isFunction && rhsTy.isFunction && lhsTy != rhsTy && !lhsTy.hasArchetype && !rhsTy.hasArchetype {
|
|
return false
|
|
}
|
|
}
|
|
|
|
// If casting in either direction doesn't work, the types cannot be equal.
|
|
if !(canDynamicallyCast(from: lhsTy, to: rhsTy, in: function, sourceTypeIsExact: typesAreExact) ?? true) ||
|
|
!(canDynamicallyCast(from: rhsTy, to: lhsTy, in: function, sourceTypeIsExact: typesAreExact) ?? true) {
|
|
return false
|
|
}
|
|
return nil
|
|
}
|