Files
swift-mirror/SwiftCompilerSources/Sources/Optimizer/InstructionSimplification/SimplifyBuiltin.swift
Anthony Latsis ac619010e3 SwiftCompilerSources/SIL: Fix APInt assertion failure on rebranch
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.
2025-09-24 09:37:42 +01:00

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
}