Files
swift-mirror/lib/SILOptimizer/Utils/ConstantFolding.cpp
Erik Eckstein 7ea1b1b23e SIL: add a flag [projection] to index_addr
The `projection` flag indicates that `index_addr` projects an element address from an array base address, as opposed to being used for general pointer arithmetic.
When this flag is set, the result address can only reach the single element at the given index — it is not possible to chain multiple `index_addr` instructions to reach other array elements from the result.
Without this flag, the result may be used as the base of another `index_addr`, allowing arithmetic across element boundaries (e.g. an `index_addr` with index 1 followed by an `index_addr` with index 2 reaches the element at offset 3).

An `index_addr [projection]` is mandatory to go from an array base address to an element - even if it's the first element, i.e. the index is zero.
This means that the optimizer must not remove `index_addr [projection]` with a zero index.
2026-06-09 16:17:53 +02:00

2155 lines
82 KiB
C++

//===--- ConstantFolding.cpp - Utils for SIL constant folding -------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2017 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
//
//===----------------------------------------------------------------------===//
#include "swift/SILOptimizer/Utils/ConstantFolding.h"
#include "swift/AST/DiagnosticsSIL.h"
#include "swift/AST/Expr.h"
#include "swift/AST/SemanticAttrs.h"
#include "swift/Basic/Assertions.h"
#include "swift/SIL/InstructionUtils.h"
#include "swift/SIL/PatternMatch.h"
#include "swift/SIL/SILBuilder.h"
#include "swift/SILOptimizer/Utils/CastOptimizer.h"
#include "swift/SILOptimizer/Utils/InstOptUtils.h"
#include "swift/SILOptimizer/Utils/InstructionDeleter.h"
#include "llvm/ADT/APFloat.h"
#include "llvm/ADT/APSInt.h"
#include "llvm/ADT/Statistic.h"
#include "llvm/ADT/StringExtras.h"
#include "llvm/IR/Intrinsics.h"
#include "llvm/Support/Debug.h"
#define DEBUG_TYPE "sil-constant-folding"
using namespace swift;
APInt swift::constantFoldBitOperation(APInt lhs, APInt rhs, BuiltinValueKind ID) {
switch (ID) {
default: llvm_unreachable("Not all cases are covered!");
case BuiltinValueKind::And:
return lhs & rhs;
case BuiltinValueKind::AShr:
return lhs.ashr(rhs);
case BuiltinValueKind::LShr:
return lhs.lshr(rhs);
case BuiltinValueKind::Or:
return lhs | rhs;
case BuiltinValueKind::Shl:
return lhs.shl(rhs);
case BuiltinValueKind::Xor:
return lhs ^ rhs;
}
}
APInt swift::constantFoldComparisonFloat(APFloat lhs, APFloat rhs,
BuiltinValueKind ID) {
bool result;
bool isOrdered = !lhs.isNaN() && !rhs.isNaN();
switch (ID) {
default: llvm_unreachable("Invalid float compare kind");
// Ordered comparisons
case BuiltinValueKind::FCMP_OEQ: result = isOrdered && lhs == rhs; break;
case BuiltinValueKind::FCMP_OGT: result = isOrdered && lhs > rhs; break;
case BuiltinValueKind::FCMP_OGE: result = isOrdered && lhs >= rhs; break;
case BuiltinValueKind::FCMP_OLT: result = isOrdered && lhs < rhs; break;
case BuiltinValueKind::FCMP_OLE: result = isOrdered && lhs <= rhs; break;
case BuiltinValueKind::FCMP_ONE: result = isOrdered && lhs != rhs; break;
case BuiltinValueKind::FCMP_ORD: result = isOrdered; break;
// Unordered comparisons
case BuiltinValueKind::FCMP_UEQ: result = !isOrdered || lhs == rhs; break;
case BuiltinValueKind::FCMP_UGT: result = !isOrdered || lhs > rhs; break;
case BuiltinValueKind::FCMP_UGE: result = !isOrdered || lhs >= rhs; break;
case BuiltinValueKind::FCMP_ULT: result = !isOrdered || lhs < rhs; break;
case BuiltinValueKind::FCMP_ULE: result = !isOrdered || lhs <= rhs; break;
case BuiltinValueKind::FCMP_UNE: result = !isOrdered || lhs != rhs; break;
case BuiltinValueKind::FCMP_UNO: result = !isOrdered; break;
}
return APInt(1, result);
}
APInt swift::constantFoldComparisonInt(APInt lhs, APInt rhs,
BuiltinValueKind ID) {
bool result;
switch (ID) {
default: llvm_unreachable("Invalid integer compare kind");
case BuiltinValueKind::ICMP_EQ: result = lhs == rhs; break;
case BuiltinValueKind::ICMP_NE: result = lhs != rhs; break;
case BuiltinValueKind::ICMP_SLT: result = lhs.slt(rhs); break;
case BuiltinValueKind::ICMP_SGT: result = lhs.sgt(rhs); break;
case BuiltinValueKind::ICMP_SLE: result = lhs.sle(rhs); break;
case BuiltinValueKind::ICMP_SGE: result = lhs.sge(rhs); break;
case BuiltinValueKind::ICMP_ULT: result = lhs.ult(rhs); break;
case BuiltinValueKind::ICMP_UGT: result = lhs.ugt(rhs); break;
case BuiltinValueKind::ICMP_ULE: result = lhs.ule(rhs); break;
case BuiltinValueKind::ICMP_UGE: result = lhs.uge(rhs); break;
}
return APInt(1, result);
}
APInt swift::constantFoldBinaryWithOverflow(APInt lhs, APInt rhs,
bool &Overflow,
llvm::Intrinsic::ID ID) {
switch (ID) {
default: llvm_unreachable("Invalid case");
case llvm::Intrinsic::sadd_with_overflow:
return lhs.sadd_ov(rhs, Overflow);
case llvm::Intrinsic::uadd_with_overflow:
return lhs.uadd_ov(rhs, Overflow);
case llvm::Intrinsic::ssub_with_overflow:
return lhs.ssub_ov(rhs, Overflow);
case llvm::Intrinsic::usub_with_overflow:
return lhs.usub_ov(rhs, Overflow);
case llvm::Intrinsic::smul_with_overflow:
return lhs.smul_ov(rhs, Overflow);
case llvm::Intrinsic::umul_with_overflow:
return lhs.umul_ov(rhs, Overflow);
}
}
APInt swift::constantFoldDiv(APInt lhs, APInt rhs, bool &Overflow,
BuiltinValueKind ID) {
assert(rhs != 0 && "division by zero");
switch (ID) {
default : llvm_unreachable("Invalid case");
case BuiltinValueKind::SDiv:
return lhs.sdiv_ov(rhs, Overflow);
case BuiltinValueKind::SRem: {
// Check for overflow
APInt Div = lhs.sdiv_ov(rhs, Overflow);
(void)Div;
return lhs.srem(rhs);
}
case BuiltinValueKind::UDiv:
Overflow = false;
return lhs.udiv(rhs);
case BuiltinValueKind::URem:
Overflow = false;
return lhs.urem(rhs);
}
}
APInt swift::constantFoldCast(APInt val, const BuiltinInfo &BI) {
// Get the cast result.
Type SrcTy = BI.Types[0];
Type DestTy = BI.Types.size() == 2 ? BI.Types[1] : Type();
uint32_t SrcBitWidth =
SrcTy->castTo<BuiltinIntegerType>()->getGreatestWidth();
uint32_t DestBitWidth =
DestTy->castTo<BuiltinIntegerType>()->getGreatestWidth();
APInt CastResV;
if (SrcBitWidth == DestBitWidth) {
return val;
} else switch (BI.ID) {
default : llvm_unreachable("Invalid case.");
case BuiltinValueKind::Trunc:
case BuiltinValueKind::TruncOrBitCast:
return val.trunc(DestBitWidth);
case BuiltinValueKind::ZExt:
case BuiltinValueKind::ZExtOrBitCast:
return val.zext(DestBitWidth);
break;
case BuiltinValueKind::SExt:
case BuiltinValueKind::SExtOrBitCast:
return val.sext(DestBitWidth);
}
}
//===----------------------------------------------------------------------===//
// ConstantFolder
//===----------------------------------------------------------------------===//
STATISTIC(NumInstFolded, "Number of constant folded instructions");
template<typename...T, typename...U>
static InFlightDiagnostic
diagnose(ASTContext &Context, SourceLoc loc, Diag<T...> diag, U &&...args) {
return Context.Diags.diagnose(loc, diag, std::forward<U>(args)...);
}
/// Construct (int, overflow) result tuple.
static SILValue constructResultWithOverflowTuple(BuiltinInst *BI,
APInt Res, bool Overflow) {
// Get the SIL subtypes of the returned tuple type.
SILType FuncResType = BI->getType();
assert(FuncResType.castTo<TupleType>()->getNumElements() == 2);
SILType ResTy1 = FuncResType.getTupleElementType(0);
SILType ResTy2 = FuncResType.getTupleElementType(1);
// Construct the folded instruction - a tuple of two literals, the
// result and overflow.
SILBuilderWithScope B(BI);
SILLocation Loc = BI->getLoc();
SILValue Result[] = {
B.createIntegerLiteral(Loc, ResTy1, Res),
B.createIntegerLiteral(Loc, ResTy2, Overflow)
};
return B.createTuple(Loc, FuncResType, Result);
}
/// Fold arithmetic intrinsics with overflow.
static SILValue
constantFoldBinaryWithOverflow(BuiltinInst *BI, llvm::Intrinsic::ID ID,
bool ReportOverflow,
std::optional<bool> &ResultsInError) {
OperandValueArrayRef Args = BI->getArguments();
assert(Args.size() >= 2);
auto *Op1 = dyn_cast<IntegerLiteralInst>(Args[0]);
auto *Op2 = dyn_cast<IntegerLiteralInst>(Args[1]);
// If either Op1 or Op2 is not a literal, we cannot do anything.
if (!Op1 || !Op2)
return nullptr;
// Calculate the result.
APInt LHSInt = Op1->getValue();
APInt RHSInt = Op2->getValue();
bool Overflow;
APInt Res = constantFoldBinaryWithOverflow(LHSInt, RHSInt, Overflow, ID);
// If we can statically determine that the operation overflows,
// warn about it if warnings are not disabled by ResultsInError being null.
if (ResultsInError.has_value() && Overflow && ReportOverflow) {
if (BI->getFunction()->isSpecialization()) {
// Do not report any constant propagation issues in specializations,
// because they are eventually not present in the original function.
return nullptr;
}
// Try to infer the type of the constant expression that the user operates
// on. If the intrinsic was lowered from a call to a function that takes
// two arguments of the same type, use the type of the LHS argument.
// This would detect '+'/'+=' and such.
Type OpType;
SILLocation Loc = BI->getLoc();
const ApplyExpr *CE = Loc.getAsASTNode<ApplyExpr>();
SourceRange LHSRange, RHSRange;
if (CE) {
const auto *Args = CE->getArgs();
if (Args->size() == 2) {
// Look through inout types in order to handle += well.
CanType LHSTy = Args->getExpr(0)->getType()->getInOutObjectType()->
getCanonicalType();
CanType RHSTy = Args->getExpr(1)->getType()->getCanonicalType();
if (LHSTy == RHSTy)
OpType = Args->getExpr(1)->getType();
LHSRange = Args->getExpr(0)->getSourceRange();
RHSRange = Args->getExpr(1)->getSourceRange();
}
}
bool Signed = false;
StringRef Operator = "+";
switch (ID) {
default: llvm_unreachable("Invalid case");
case llvm::Intrinsic::sadd_with_overflow:
Signed = true;
break;
case llvm::Intrinsic::uadd_with_overflow:
break;
case llvm::Intrinsic::ssub_with_overflow:
Operator = "-";
Signed = true;
break;
case llvm::Intrinsic::usub_with_overflow:
Operator = "-";
break;
case llvm::Intrinsic::smul_with_overflow:
Operator = "*";
Signed = true;
break;
case llvm::Intrinsic::umul_with_overflow:
Operator = "*";
break;
}
SmallString<10> LhsStr;
SmallString<10> RhsStr;
LHSInt.toString(LhsStr, /*Radix*/ 10, Signed);
RHSInt.toString(RhsStr, /*Radix*/ 10, Signed);
if (!OpType.isNull()) {
diagnose(BI->getModule().getASTContext(), Loc.getSourceLoc(),
diag::arithmetic_operation_overflow, LhsStr, Operator, RhsStr,
OpType)
.highlight(LHSRange)
.highlight(RHSRange);
} else {
// If we cannot get the type info in an expected way, describe the type.
diagnose(BI->getModule().getASTContext(), Loc.getSourceLoc(),
diag::arithmetic_operation_overflow_generic_type, LhsStr,
Operator, RhsStr, Signed, LHSInt.getBitWidth())
.highlight(LHSRange)
.highlight(RHSRange);
}
ResultsInError = std::optional<bool>(true);
}
return constructResultWithOverflowTuple(BI, Res, Overflow);
}
static SILValue
constantFoldBinaryWithOverflow(BuiltinInst *BI, BuiltinValueKind ID,
std::optional<bool> &ResultsInError) {
OperandValueArrayRef Args = BI->getArguments();
auto *ShouldReportFlag = dyn_cast<IntegerLiteralInst>(Args[2]);
return constantFoldBinaryWithOverflow(BI,
getLLVMIntrinsicIDForBuiltinWithOverflow(ID),
ShouldReportFlag && (ShouldReportFlag->getValue() == 1),
ResultsInError);
}
/// Constant fold a cttz or ctlz builtin inst of an integer literal.
/// If \p countLeadingZeros is set to true, then we assume \p bi must be ctlz.
/// If false, \p bi must be cttz.
///
/// NOTE: We assert that \p bi is either cttz or ctlz.
static SILValue
constantFoldCountLeadingOrTrialingZeroIntrinsic(BuiltinInst *bi,
bool countLeadingZeros) {
assert((bi->getIntrinsicID() == (llvm::Intrinsic::ID)llvm::Intrinsic::ctlz ||
bi->getIntrinsicID() == (llvm::Intrinsic::ID)llvm::Intrinsic::cttz) &&
"Invalid Intrinsic - expected Ctlz/Cllz");
OperandValueArrayRef args = bi->getArguments();
// Fold for integer constant arguments.
auto *lhs = dyn_cast<IntegerLiteralInst>(args[0]);
if (!lhs) {
return nullptr;
}
APInt lhsi = lhs->getValue();
unsigned lz = [&] {
if (lhsi == 0) {
// Check corner-case of source == zero
return lhsi.getBitWidth();
}
if (countLeadingZeros) {
return lhsi.countLeadingZeros();
}
return lhsi.countTrailingZeros();
}();
APInt lzAsAPInt = APInt(lhsi.getBitWidth(), lz);
SILBuilderWithScope builder(bi);
return builder.createIntegerLiteral(bi->getLoc(), lhs->getType(), lzAsAPInt);
}
static SILValue constantFoldIntrinsic(BuiltinInst *BI, llvm::Intrinsic::ID ID,
std::optional<bool> &ResultsInError) {
switch (ID) {
default: break;
case llvm::Intrinsic::expect: {
// An expect of an integral constant is the constant itself.
assert(BI->getArguments().size() == 2 && "Expect should have 2 args.");
auto *Op1 = dyn_cast<IntegerLiteralInst>(BI->getArguments()[0]);
if (!Op1)
return nullptr;
return Op1;
}
case llvm::Intrinsic::ctlz: {
return constantFoldCountLeadingOrTrialingZeroIntrinsic(
BI, true /*countLeadingZeros*/);
}
case llvm::Intrinsic::cttz: {
return constantFoldCountLeadingOrTrialingZeroIntrinsic(
BI, false /*countLeadingZeros*/);
}
case llvm::Intrinsic::sadd_with_overflow:
case llvm::Intrinsic::uadd_with_overflow:
case llvm::Intrinsic::ssub_with_overflow:
case llvm::Intrinsic::usub_with_overflow:
case llvm::Intrinsic::smul_with_overflow:
case llvm::Intrinsic::umul_with_overflow:
return constantFoldBinaryWithOverflow(BI, ID,
/* ReportOverflow */ false,
ResultsInError);
case llvm::Intrinsic::rint:
if (auto *floatLiteral = dyn_cast<FloatLiteralInst>(BI->getArguments()[0])) {
SILBuilderWithScope builder(BI);
APFloat result = floatLiteral->getValue();
// The following code is taken from LLVM's constant folder.
result.roundToIntegral(APFloat::rmNearestTiesToEven);
return builder.createFloatLiteral(BI->getLoc(), BI->getType(), result);
}
}
return nullptr;
}
static bool isNanLiteral(SILValue v) {
if (auto *literal = dyn_cast<FloatLiteralInst>(v)) {
return literal->getValue().isNaN();
}
return false;
}
static SILValue constantFoldCompareFloat(BuiltinInst *BI, BuiltinValueKind ID) {
OperandValueArrayRef Args = BI->getArguments();
// Fold for floating point constant arguments.
auto *LHS = dyn_cast<FloatLiteralInst>(Args[0]);
auto *RHS = dyn_cast<FloatLiteralInst>(Args[1]);
if (LHS && RHS) {
APInt Res =
constantFoldComparisonFloat(LHS->getValue(), RHS->getValue(), ID);
SILBuilderWithScope B(BI);
return B.createIntegerLiteral(BI->getLoc(), BI->getType(), Res);
}
if (isNanLiteral(Args[0]) || isNanLiteral(Args[1])) {
switch (BI->getBuiltinInfo().ID) {
// Ordered comparisons with NaN always return false
case BuiltinValueKind::FCMP_OEQ: // ==
case BuiltinValueKind::FCMP_OGT: // >=
case BuiltinValueKind::FCMP_OGE: // <
case BuiltinValueKind::FCMP_OLT: // <
case BuiltinValueKind::FCMP_OLE: // <=
case BuiltinValueKind::FCMP_ONE: { // !=
SILBuilderWithScope B(BI);
return B.createIntegerLiteral(BI->getLoc(), BI->getType(), APInt(1, 0));
}
// Unordered comparisons with NaN always return true
case BuiltinValueKind::FCMP_UEQ: // ==
case BuiltinValueKind::FCMP_UGT: // >=
case BuiltinValueKind::FCMP_UGE: // <
case BuiltinValueKind::FCMP_ULT: // <
case BuiltinValueKind::FCMP_ULE: // <=
case BuiltinValueKind::FCMP_UNE: { // !=
SILBuilderWithScope B(BI);
return B.createIntegerLiteral(BI->getLoc(), BI->getType(), APInt(1, 1));
}
default:
break;
}
}
return nullptr;
}
static SILValue constantFoldCompareInt(BuiltinInst *BI, BuiltinValueKind ID) {
OperandValueArrayRef Args = BI->getArguments();
// Fold for integer constant arguments.
auto *LHS = dyn_cast<IntegerLiteralInst>(Args[0]);
auto *RHS = dyn_cast<IntegerLiteralInst>(Args[1]);
if (LHS && RHS) {
APInt Res = constantFoldComparisonInt(LHS->getValue(), RHS->getValue(), ID);
SILBuilderWithScope B(BI);
return B.createIntegerLiteral(BI->getLoc(), BI->getType(), Res);
}
using namespace swift::PatternMatch;
// Comparisons of an unsigned value with 0.
SILValue Other;
auto MatchNonNegative =
m_BuiltinInst(BuiltinValueKind::AssumeNonNegative, m_ValueBase());
if (match(BI, m_CombineOr(m_BuiltinInst(BuiltinValueKind::ICMP_ULT,
m_SILValue(Other), m_Zero()),
m_BuiltinInst(BuiltinValueKind::ICMP_UGT, m_Zero(),
m_SILValue(Other)))) ||
match(BI, m_CombineOr(m_BuiltinInst(BuiltinValueKind::ICMP_SLT,
MatchNonNegative, m_Zero()),
m_BuiltinInst(BuiltinValueKind::ICMP_SGT, m_Zero(),
MatchNonNegative)))) {
SILBuilderWithScope B(BI);
return B.createIntegerLiteral(BI->getLoc(), BI->getType(), APInt());
}
if (match(BI, m_CombineOr(m_BuiltinInst(BuiltinValueKind::ICMP_UGE,
m_SILValue(Other), m_Zero()),
m_BuiltinInst(BuiltinValueKind::ICMP_ULE, m_Zero(),
m_SILValue(Other)))) ||
match(BI, m_CombineOr(m_BuiltinInst(BuiltinValueKind::ICMP_SGE,
MatchNonNegative, m_Zero()),
m_BuiltinInst(BuiltinValueKind::ICMP_SLE, m_Zero(),
MatchNonNegative)))) {
SILBuilderWithScope B(BI);
return B.createIntegerLiteral(BI->getLoc(), BI->getType(), APInt(1, 1));
}
// Comparisons with Int.Max.
IntegerLiteralInst *IntMax;
// Check signed comparisons.
if (match(BI,
m_CombineOr(
// Int.max < x
m_BuiltinInst(BuiltinValueKind::ICMP_SLT,
m_IntegerLiteralInst(IntMax), m_SILValue(Other)),
// x > Int.max
m_BuiltinInst(BuiltinValueKind::ICMP_SGT, m_SILValue(Other),
m_IntegerLiteralInst(IntMax)))) &&
IntMax->getValue().isMaxSignedValue()) {
// Any signed number should be <= then IntMax.
SILBuilderWithScope B(BI);
return B.createIntegerLiteral(BI->getLoc(), BI->getType(), APInt());
}
if (match(BI,
m_CombineOr(
m_BuiltinInst(BuiltinValueKind::ICMP_SGE,
m_IntegerLiteralInst(IntMax), m_SILValue(Other)),
m_BuiltinInst(BuiltinValueKind::ICMP_SLE, m_SILValue(Other),
m_IntegerLiteralInst(IntMax)))) &&
IntMax->getValue().isMaxSignedValue()) {
// Any signed number should be <= then IntMax.
SILBuilderWithScope B(BI);
return B.createIntegerLiteral(BI->getLoc(), BI->getType(), APInt(1, 1));
}
// For any x of the same size as Int.max and n>=1 , (x>>n) is always <= Int.max,
// that is (x>>n) <= Int.max and Int.max >= (x>>n) are true.
if (match(BI,
m_CombineOr(
// Int.max >= x
m_BuiltinInst(BuiltinValueKind::ICMP_UGE,
m_IntegerLiteralInst(IntMax), m_SILValue(Other)),
// x <= Int.max
m_BuiltinInst(BuiltinValueKind::ICMP_ULE, m_SILValue(Other),
m_IntegerLiteralInst(IntMax)),
// Int.max >= x
m_BuiltinInst(BuiltinValueKind::ICMP_SGE,
m_IntegerLiteralInst(IntMax), m_SILValue(Other)),
// x <= Int.max
m_BuiltinInst(BuiltinValueKind::ICMP_SLE, m_SILValue(Other),
m_IntegerLiteralInst(IntMax)))) &&
IntMax->getValue().isMaxSignedValue()) {
// Check if other is a result of a logical shift right by a strictly
// positive number of bits.
IntegerLiteralInst *ShiftCount;
if (match(Other, m_BuiltinInst(BuiltinValueKind::LShr, m_ValueBase(),
m_IntegerLiteralInst(ShiftCount))) &&
ShiftCount->getValue().isStrictlyPositive()) {
SILBuilderWithScope B(BI);
return B.createIntegerLiteral(BI->getLoc(), BI->getType(), APInt(1, 1));
}
}
// At the same time (x>>n) > Int.max and Int.max < (x>>n) is false.
if (match(BI,
m_CombineOr(
// Int.max < x
m_BuiltinInst(BuiltinValueKind::ICMP_ULT,
m_IntegerLiteralInst(IntMax), m_SILValue(Other)),
// x > Int.max
m_BuiltinInst(BuiltinValueKind::ICMP_UGT, m_SILValue(Other),
m_IntegerLiteralInst(IntMax)),
// Int.max < x
m_BuiltinInst(BuiltinValueKind::ICMP_SLT,
m_IntegerLiteralInst(IntMax), m_SILValue(Other)),
// x > Int.max
m_BuiltinInst(BuiltinValueKind::ICMP_SGT, m_SILValue(Other),
m_IntegerLiteralInst(IntMax)))) &&
IntMax->getValue().isMaxSignedValue()) {
// Check if other is a result of a logical shift right by a strictly
// positive number of bits.
IntegerLiteralInst *ShiftCount;
if (match(Other, m_BuiltinInst(BuiltinValueKind::LShr, m_ValueBase(),
m_IntegerLiteralInst(ShiftCount))) &&
ShiftCount->getValue().isStrictlyPositive()) {
SILBuilderWithScope B(BI);
return B.createIntegerLiteral(BI->getLoc(), BI->getType(), APInt());
}
}
return nullptr;
}
static SILValue constantFoldCompare(BuiltinInst *BI, BuiltinValueKind ID) {
// Try folding integer comparison
if (auto result = constantFoldCompareInt(BI, ID))
return result;
// Try folding floating point comparison
if (auto result = constantFoldCompareFloat(BI, ID))
return result;
// Else, return nullptr
return nullptr;
}
static SILValue
constantFoldAndCheckDivision(BuiltinInst *BI, BuiltinValueKind ID,
std::optional<bool> &ResultsInError) {
assert(ID == BuiltinValueKind::SDiv ||
ID == BuiltinValueKind::SRem ||
ID == BuiltinValueKind::UDiv ||
ID == BuiltinValueKind::URem);
OperandValueArrayRef Args = BI->getArguments();
SILModule &M = BI->getModule();
// Get the denominator.
auto *Denom = dyn_cast<IntegerLiteralInst>(Args[1]);
if (!Denom)
return nullptr;
APInt DenomVal = Denom->getValue();
// If the denominator is zero...
if (DenomVal == 0) {
// And if we are not asked to report errors, just return nullptr.
if (!ResultsInError.has_value())
return nullptr;
// Otherwise emit a diagnosis error and set ResultsInError to true.
diagnose(M.getASTContext(), BI->getLoc().getSourceLoc(),
diag::division_by_zero);
ResultsInError = std::optional<bool>(true);
return nullptr;
}
// Get the numerator.
auto *Num = dyn_cast<IntegerLiteralInst>(Args[0]);
if (!Num)
return nullptr;
APInt NumVal = Num->getValue();
bool Overflowed;
APInt ResVal = constantFoldDiv(NumVal, DenomVal, Overflowed, ID);
// If we overflowed...
if (Overflowed) {
// And we are not asked to produce diagnostics, just return nullptr...
if (!ResultsInError.has_value())
return nullptr;
bool IsRem = ID == BuiltinValueKind::SRem || ID == BuiltinValueKind::URem;
// Otherwise emit the diagnostic, set ResultsInError to be true, and return
// nullptr.
diagnose(M.getASTContext(), BI->getLoc().getSourceLoc(),
diag::division_overflow,
llvm::toString(NumVal, /*Radix*/ 10, /*Signed*/ true),
IsRem ? "%" : "/",
llvm::toString(DenomVal, /*Radix*/ 10, /*Signed*/ true));
ResultsInError = std::optional<bool>(true);
return nullptr;
}
// Add the literal instruction to represent the result of the division.
SILBuilderWithScope B(BI);
return B.createIntegerLiteral(BI->getLoc(), BI->getType(), ResVal);
}
static SILValue specializePolymorphicBuiltin(BuiltinInst *bi,
BuiltinValueKind id) {
// If we are not a polymorphic builtin, return an empty SILValue()
// so we keep on scanning.
if (!isPolymorphicBuiltin(id))
return SILValue();
// Otherwise, try to perform the mapping.
if (auto newBuiltin = getStaticOverloadForSpecializedPolymorphicBuiltin(bi))
return newBuiltin;
return SILValue();
}
/// Fold binary operations.
///
/// The list of operations we constant fold might not be complete. Start with
/// folding the operations used by the standard library.
static SILValue constantFoldBinary(BuiltinInst *BI, BuiltinValueKind ID,
std::optional<bool> &ResultsInError) {
switch (ID) {
default:
return nullptr;
// Not supported yet (not easily computable for APInt).
case BuiltinValueKind::ExactSDiv:
case BuiltinValueKind::ExactUDiv:
return nullptr;
// Not supported now.
case BuiltinValueKind::FRem:
return nullptr;
// Fold constant division operations and report div by zero.
case BuiltinValueKind::SDiv:
case BuiltinValueKind::SRem:
case BuiltinValueKind::UDiv:
case BuiltinValueKind::URem: {
return constantFoldAndCheckDivision(BI, ID, ResultsInError);
}
// Are there valid uses for these in stdlib?
case BuiltinValueKind::Add:
case BuiltinValueKind::Mul:
case BuiltinValueKind::Sub: {
OperandValueArrayRef Args = BI->getArguments();
auto *LHS = dyn_cast<IntegerLiteralInst>(Args[0]);
auto *RHS = dyn_cast<IntegerLiteralInst>(Args[1]);
if (!RHS || !LHS)
return nullptr;
APInt LHSI = LHS->getValue();
APInt RHSI = RHS->getValue();
switch (ID) {
default: llvm_unreachable("Not all cases are covered!");
case BuiltinValueKind::Add:
LHSI += RHSI;
break;
case BuiltinValueKind::Mul:
LHSI *= RHSI;
break;
case BuiltinValueKind::Sub:
LHSI -= RHSI;
break;
}
SILBuilderWithScope B(BI);
return B.createIntegerLiteral(BI->getLoc(), BI->getType(), LHSI);
}
case BuiltinValueKind::And:
case BuiltinValueKind::AShr:
case BuiltinValueKind::LShr:
case BuiltinValueKind::Or:
case BuiltinValueKind::Shl:
case BuiltinValueKind::Xor: {
OperandValueArrayRef Args = BI->getArguments();
auto *LHS = dyn_cast<IntegerLiteralInst>(Args[0]);
auto *RHS = dyn_cast<IntegerLiteralInst>(Args[1]);
if (!RHS || !LHS)
return nullptr;
APInt LHSI = LHS->getValue();
APInt RHSI = RHS->getValue();
bool IsShift = ID == BuiltinValueKind::AShr ||
ID == BuiltinValueKind::LShr ||
ID == BuiltinValueKind::Shl;
// Reject shifting all significant bits
if (IsShift && RHSI.getZExtValue() >= LHSI.getBitWidth()) {
diagnose(BI->getModule().getASTContext(),
RHS->getLoc().getSourceLoc(),
diag::shifting_all_significant_bits);
ResultsInError = std::optional<bool>(true);
return nullptr;
}
APInt ResI = constantFoldBitOperation(LHSI, RHSI, ID);
// Add the literal instruction to represent the result.
SILBuilderWithScope B(BI);
return B.createIntegerLiteral(BI->getLoc(), BI->getType(), ResI);
}
case BuiltinValueKind::FAdd:
case BuiltinValueKind::FDiv:
case BuiltinValueKind::FMul:
case BuiltinValueKind::FSub: {
OperandValueArrayRef Args = BI->getArguments();
auto *LHS = dyn_cast<FloatLiteralInst>(Args[0]);
auto *RHS = dyn_cast<FloatLiteralInst>(Args[1]);
if (!RHS || !LHS)
return nullptr;
APFloat LHSF = LHS->getValue();
APFloat RHSF = RHS->getValue();
switch (ID) {
default: llvm_unreachable("Not all cases are covered!");
case BuiltinValueKind::FAdd:
LHSF.add(RHSF, APFloat::rmNearestTiesToEven);
break;
case BuiltinValueKind::FDiv:
LHSF.divide(RHSF, APFloat::rmNearestTiesToEven);
break;
case BuiltinValueKind::FMul:
LHSF.multiply(RHSF, APFloat::rmNearestTiesToEven);
break;
case BuiltinValueKind::FSub:
LHSF.subtract(RHSF, APFloat::rmNearestTiesToEven);
break;
}
// Add the literal instruction to represent the result.
SILBuilderWithScope B(BI);
return B.createFloatLiteral(BI->getLoc(), BI->getType(), LHSF);
}
}
}
static SILValue
constantFoldAndCheckIntegerConversions(BuiltinInst *BI,
const BuiltinInfo &Builtin,
std::optional<bool> &ResultsInError) {
assert(Builtin.ID == BuiltinValueKind::SToSCheckedTrunc ||
Builtin.ID == BuiltinValueKind::UToUCheckedTrunc ||
Builtin.ID == BuiltinValueKind::SToUCheckedTrunc ||
Builtin.ID == BuiltinValueKind::UToSCheckedTrunc);
// Check if we are converting a constant integer.
OperandValueArrayRef Args = BI->getArguments();
auto *V = dyn_cast<IntegerLiteralInst>(Args[0]);
if (!V)
return nullptr;
APInt SrcVal = V->getValue();
auto SrcBitWidth = SrcVal.getBitWidth();
bool DstTySigned = (Builtin.ID == BuiltinValueKind::SToSCheckedTrunc ||
Builtin.ID == BuiltinValueKind::UToSCheckedTrunc);
bool SrcTySigned = (Builtin.ID == BuiltinValueKind::SToSCheckedTrunc ||
Builtin.ID == BuiltinValueKind::SToUCheckedTrunc);
// Get source type and bit width.
auto SrcTy = Builtin.Types[0]->castTo<AnyBuiltinIntegerType>();
assert((SrcTySigned || !isa<BuiltinIntegerLiteralType>(SrcTy)) &&
"only the signed intrinsics can be used with integer literals");
// Compute the destination (for SrcBitWidth < DestBitWidth) and enough info
// to check for overflow.
APInt Result;
bool OverflowError;
Type DstTy;
assert(Builtin.Types.size() == 2);
DstTy = Builtin.Types[1];
uint32_t DstBitWidth =
DstTy->castTo<BuiltinIntegerType>()->getGreatestWidth();
assert((DstBitWidth < SrcBitWidth || !SrcTy->getWidth().isFixedWidth()) &&
"preconditions on builtin trunc operations should prevent"
"fixed-width truncations that actually extend");
// The only way a true extension can overflow is if the value is
// negative and the result is unsigned.
if (DstBitWidth > SrcBitWidth) {
OverflowError = (SrcTySigned && !DstTySigned && SrcVal.isNegative());
Result = (SrcTySigned ? SrcVal.sext(DstBitWidth)
: SrcVal.zext(DstBitWidth));
// A same-width change can overflow if the top bit disagrees.
} else if (DstBitWidth == SrcBitWidth) {
OverflowError = (SrcTySigned != DstTySigned && SrcVal.isNegative());
Result = SrcVal;
// A truncation can overflow if the value changes.
} else {
Result = SrcVal.trunc(DstBitWidth);
APInt Ext = (DstTySigned ? Result.sext(SrcBitWidth)
: Result.zext(SrcBitWidth));
OverflowError = (SrcVal != Ext);
}
// Check for overflow.
if (OverflowError) {
// If we are not asked to emit overflow diagnostics, just return nullptr on
// overflow.
if (!ResultsInError.has_value())
return nullptr;
SILLocation Loc = BI->getLoc();
SILModule &M = BI->getModule();
const ApplyExpr *CE = Loc.getAsASTNode<ApplyExpr>();
Type UserSrcTy;
Type UserDstTy;
// Primitive heuristics to get the user-written type.
// Eventually we might be able to use SILLocation (when it contains info
// about inlined call chains).
if (CE) {
if (auto *unaryArg = CE->getArgs()->getUnaryExpr()) {
UserSrcTy = unaryArg->getType();
UserDstTy = CE->getType();
}
} else if (auto *E = Loc.getAsASTNode<Expr>()) {
UserDstTy = E->getType();
}
// Assume that we're converting from a literal if the source type is
// IntegerLiteral. Is there a better way to identify this if we start
// using Builtin.IntegerLiteral in an exposed type?
bool Literal = isa<BuiltinIntegerLiteralType>(SrcTy);
// FIXME: This will prevent hard error in cases the error is coming
// from ObjC interoperability code. Currently, we treat NSUInteger as
// Int.
if (Loc.getSourceLoc().isInvalid()) {
// Otherwise emit the appropriate diagnostic and set ResultsInError.
if (Literal)
diagnose(M.getASTContext(), Loc.getSourceLoc(),
diag::integer_literal_overflow_warn,
UserDstTy.isNull() ? DstTy : UserDstTy);
else
diagnose(M.getASTContext(), Loc.getSourceLoc(),
diag::integer_conversion_overflow_warn,
UserSrcTy.isNull() ? SrcTy : UserSrcTy,
UserDstTy.isNull() ? DstTy : UserDstTy);
ResultsInError = std::optional<bool>(true);
return nullptr;
}
// Otherwise report the overflow error.
if (Literal) {
SmallString<10> SrcAsString;
SrcVal.toString(SrcAsString, /*radix*/10, SrcTySigned);
// Try to print user-visible types if they are available.
if (!UserDstTy.isNull()) {
auto diagID = diag::integer_literal_overflow;
// If this is a negative literal in an unsigned type, use a specific
// diagnostic.
if (!DstTySigned && SrcVal.isNegative())
diagID = diag::negative_integer_literal_overflow_unsigned;
diagnose(M.getASTContext(), Loc.getSourceLoc(),
diagID, UserDstTy, SrcAsString);
// Otherwise, print the Builtin Types.
} else {
diagnose(M.getASTContext(), Loc.getSourceLoc(),
diag::integer_literal_overflow_builtin_types,
DstTySigned, DstTy, SrcAsString);
}
} else {
// Try to print user-visible types if they are available.
if (!UserSrcTy.isNull()) {
diagnose(M.getASTContext(), Loc.getSourceLoc(),
diag::integer_conversion_overflow,
UserSrcTy, UserDstTy);
// Otherwise, print the Builtin Types.
} else {
// Since builtin types are sign-agnostic, print the signedness
// separately.
diagnose(M.getASTContext(), Loc.getSourceLoc(),
diag::integer_conversion_overflow_builtin_types,
SrcTySigned, SrcTy, DstTySigned, DstTy);
}
}
ResultsInError = std::optional<bool>(true);
return nullptr;
}
// The call to the builtin should be replaced with the constant value.
return constructResultWithOverflowTuple(BI, Result, false);
}
/// A utility function that extracts the literal text corresponding
/// to a given FloatLiteralInst the way it appears in the AST.
/// This function can be used on FloatLiteralInsts generated by the
/// constant folding phase.
/// If the extraction is successful, the function returns true and
/// 'fpStr' contains the literal the way it appears in the AST.
/// If the extraction is unsuccessful, e.g. because there is no AST
/// for the FloatLiteralInst, the function returns false.
template<unsigned N>
static bool tryExtractLiteralText(FloatLiteralInst *flitInst,
SmallString<N> &fpStr) {
auto *flitExpr = flitInst->getLoc().getAsASTNode<FloatLiteralExpr>();
if (!flitExpr)
return false;
if (flitExpr->isNegative())
fpStr += '-';
fpStr += flitExpr->getDigitsText();
return true;
}
static SILValue foldFPToIntConversion(BuiltinInst *BI,
const BuiltinInfo &Builtin,
std::optional<bool> &ResultsInError) {
assert(Builtin.ID == BuiltinValueKind::FPToSI ||
Builtin.ID == BuiltinValueKind::FPToUI);
OperandValueArrayRef Args = BI->getArguments();
bool conversionToUnsigned = (Builtin.ID == BuiltinValueKind::FPToUI);
auto *flitInst = dyn_cast<FloatLiteralInst>(Args[0]);
if (!flitInst)
return nullptr;
APFloat fpVal = flitInst->getValue();
auto *destTy = Builtin.Types[1]->castTo<BuiltinIntegerType>();
// Check non-negativeness of 'fpVal' for conversion to unsigned int.
if (conversionToUnsigned && fpVal.isNegative() && !fpVal.isZero()) {
// Stop folding and emit diagnostics if enabled.
if (ResultsInError.has_value()) {
SILModule &M = BI->getModule();
const ApplyExpr *CE = BI->getLoc().getAsASTNode<ApplyExpr>();
SmallString<10> fpStr;
if (!tryExtractLiteralText(flitInst, fpStr))
flitInst->getValue().toString(fpStr);
diagnose(M.getASTContext(), BI->getLoc().getSourceLoc(),
diag::negative_fp_literal_overflow_unsigned, fpStr,
CE ? CE->getType() : destTy,
CE ? false : conversionToUnsigned);
ResultsInError = std::optional<bool>(true);
}
return nullptr;
}
llvm::APSInt resInt(destTy->getFixedWidth(), conversionToUnsigned);
bool isExact = false;
APFloat::opStatus status =
fpVal.convertToInteger(resInt, APFloat::rmTowardZero, &isExact);
if (status & APFloat::opStatus::opInvalidOp) {
// Stop folding and emit diagnostics if enabled.
if (ResultsInError.has_value()) {
SILModule &M = BI->getModule();
const ApplyExpr *CE = BI->getLoc().getAsASTNode<ApplyExpr>();
SmallString<10> fpStr;
if (!tryExtractLiteralText(flitInst, fpStr))
flitInst->getValue().toString(fpStr);
diagnose(M.getASTContext(), BI->getLoc().getSourceLoc(),
diag::float_to_int_overflow, fpStr,
CE ? CE->getType() : destTy,
CE ? CE->isImplicit() : false);
ResultsInError = std::optional<bool>(true);
}
return nullptr;
}
if (status != APFloat::opStatus::opOK &&
status != APFloat::opStatus::opInexact) {
return nullptr;
}
// The call to the builtin should be replaced with the constant value.
SILBuilderWithScope B(BI);
return B.createIntegerLiteral(BI->getLoc(), BI->getType(), resInt);
}
/// Captures the layout of IEEE754 floating point values.
struct IEEESemantics {
uint8_t bitWidth;
uint8_t exponentBitWidth;
uint8_t significandBitWidth; // Ignores the integer part.
bool explicitIntegerPart;
int minExponent;
public:
IEEESemantics(uint8_t bits, uint8_t expBits, uint8_t sigBits,
bool explicitIntPart) {
bitWidth = bits;
exponentBitWidth = expBits;
significandBitWidth = sigBits;
explicitIntegerPart = explicitIntPart;
minExponent = -(1 << (exponentBitWidth - 1)) + 2;
}
};
IEEESemantics getFPSemantics(BuiltinFloatType *fpType) {
switch (fpType->getFPKind()) {
case BuiltinFloatType::IEEE16:
return IEEESemantics(16, 5, 10, false);
case BuiltinFloatType::IEEE32:
return IEEESemantics(32, 8, 23, false);
case BuiltinFloatType::IEEE64:
return IEEESemantics(64, 11, 52, false);
case BuiltinFloatType::IEEE80:
return IEEESemantics(80, 15, 63, true);
case BuiltinFloatType::IEEE128:
return IEEESemantics(128, 15, 112, false);
case BuiltinFloatType::PPC128:
llvm_unreachable("ppc128 is not supported");
}
llvm_unreachable("invalid floating point kind");
}
/// This function, given the exponent and significand of a binary fraction
/// equalling 1.srcSignificand x 2^srcExponent,
/// determines whether converting the value to a given destination semantics
/// results in an underflow and whether the significand precision is reduced
/// because of the underflow.
bool isLossyUnderflow(int srcExponent, uint64_t srcSignificand,
IEEESemantics srcSem, IEEESemantics destSem) {
if (srcExponent >= destSem.minExponent)
return false;
// Is the value smaller than the smallest non-zero value of destSem?
if (srcExponent < destSem.minExponent - destSem.significandBitWidth)
return true;
// Truncate the significand to the significand width of destSem.
uint8_t bitWidthDecrease =
srcSem.significandBitWidth - destSem.significandBitWidth;
uint64_t truncSignificand = bitWidthDecrease > 0
? (srcSignificand >> bitWidthDecrease)
: srcSignificand;
// Compute the significand bits lost due to subnormal form. Note that the
// integer part: 1 will use up a significand bit in subnormal form.
unsigned additionalLoss = destSem.minExponent - srcExponent + 1;
// Lost bits cannot exceed Double.minExponent - Double.significandWidth = 53.
// This can happen when truncating from Float80 to Double.
assert(additionalLoss <= 53);
// Check whether a set LSB is lost due to subnormal representation.
uint64_t lostLSBBitMask = ((uint64_t)1 << additionalLoss) - 1;
return (truncSignificand & lostLSBBitMask);
}
/// This function, given an IEEE floating-point value (srcVal), determines
/// whether the conversion to a given destination semantics results
/// in an underflow and whether the significand precision is reduced
/// because of the underflow.
bool isLossyUnderflow(APFloat srcVal, BuiltinFloatType *srcType,
BuiltinFloatType *destType) {
if (srcVal.isNaN() || srcVal.isZero() || srcVal.isInfinity())
return false;
IEEESemantics srcSem = getFPSemantics(srcType);
IEEESemantics destSem = getFPSemantics(destType);
if (srcSem.bitWidth <= destSem.bitWidth)
return false;
if (srcVal.isDenormal()) {
// A denormal value of a larger IEEE FP type will definitely
// reduce to zero when truncated to smaller IEEE FP type.
return true;
}
APInt bitPattern = srcVal.bitcastToAPInt();
uint64_t significand =
bitPattern.getLoBits(srcSem.significandBitWidth).getZExtValue();
return isLossyUnderflow(ilogb(srcVal), significand, srcSem, destSem);
}
/// This function determines whether the float literal in the given
/// SIL instruction is specified using hex-float notation in the Swift source.
bool isHexLiteralInSource(FloatLiteralInst *flitInst) {
auto *flitExpr = flitInst->getLoc().getAsASTNode<FloatLiteralExpr>();
return flitExpr && flitExpr->getDigitsText().starts_with("0x");
}
bool maybeExplicitFPCons(BuiltinInst *BI, const BuiltinInfo &Builtin) {
assert(Builtin.ID == BuiltinValueKind::FPTrunc ||
Builtin.ID == BuiltinValueKind::IntToFPWithOverflow);
if (auto *literal = BI->getLoc().getAsASTNode<NumberLiteralExpr>()) {
return literal->isExplicitConversion();
}
auto *callExpr = BI->getLoc().getAsASTNode<CallExpr>();
if (!callExpr || !dyn_cast<ConstructorRefCallExpr>(callExpr->getFn()))
return true; // not enough information here, so err on the safer side.
if (!callExpr->isImplicit())
return true;
// Here, the 'callExpr' is an implicit FP construction. However, if it is
// constructing a Double it could be a part of an explicit construction of
// another FP type, which uses an implicit conversion to Double as an
// intermediate step. So we conservatively assume that an implicit
// construction of Double could be a part of an explicit conversion
// and suppress the warning.
return callExpr->getType()->isDouble();
}
static SILValue foldFPTrunc(BuiltinInst *BI, const BuiltinInfo &Builtin,
std::optional<bool> &ResultsInError) {
assert(Builtin.ID == BuiltinValueKind::FPTrunc);
auto *flitInst = dyn_cast<FloatLiteralInst>(BI->getArguments()[0]);
if (!flitInst)
return nullptr; // We can fold only compile-time constant arguments.
SILLocation Loc = BI->getLoc();
auto *srcType = Builtin.Types[0]->castTo<BuiltinFloatType>();
auto *destType = Builtin.Types[1]->castTo<BuiltinFloatType>();
bool losesInfo;
APFloat truncVal = flitInst->getValue();
APFloat::opStatus opStatus =
truncVal.convert(destType->getAPFloatSemantics(),
APFloat::rmNearestTiesToEven, &losesInfo);
// Emit a warning if one of the following conditions hold: (a) the source
// value overflows the destination type, or (b) the source value is tiny and
// the tininess results in additional loss of precision when converted to the
// destination type beyond what would result in the normal scenario, or
// (c) the source value is a hex-float literal that cannot be precisely
// represented in the destination type.
// Suppress all warnings if the conversion is made through an explicit
// constructor invocation.
if (ResultsInError.has_value() && !maybeExplicitFPCons(BI, Builtin)) {
bool overflow = opStatus & APFloat::opStatus::opOverflow;
bool tinynInexact =
isLossyUnderflow(flitInst->getValue(), srcType, destType);
bool hexnInexact =
(opStatus != APFloat::opStatus::opOK) && isHexLiteralInSource(flitInst);
if (overflow || tinynInexact || hexnInexact) {
SILModule &M = BI->getModule();
const ApplyExpr *CE = Loc.getAsASTNode<ApplyExpr>();
SmallString<10> fplitStr;
tryExtractLiteralText(flitInst, fplitStr);
auto userType = CE ? CE->getType() : destType;
if (auto *FLE = Loc.getAsASTNode<FloatLiteralExpr>()) {
userType = FLE->getType();
}
auto diagId = overflow
? diag::warning_float_trunc_overflow
: (hexnInexact ? diag::warning_float_trunc_hex_inexact
: diag::warning_float_trunc_underflow);
diagnose(M.getASTContext(), Loc.getSourceLoc(), diagId, fplitStr,
userType, truncVal.isNegative());
ResultsInError = std::optional<bool>(true);
}
}
// Abort folding if we have subnormality, NaN or opInvalid status.
if ((opStatus & APFloat::opStatus::opInvalidOp) ||
(opStatus & APFloat::opStatus::opDivByZero) ||
(opStatus & APFloat::opStatus::opUnderflow) || truncVal.isDenormal()) {
return nullptr;
}
// Allow folding if there is no loss, overflow or normal imprecision
// (i.e., opOverflow, opOk, or opInexact).
SILBuilderWithScope B(BI);
return B.createFloatLiteral(Loc, BI->getType(), truncVal);
}
static SILValue constantFoldIsConcrete(BuiltinInst *BI) {
if (BI->getOperand(0)->getType().hasArchetype()) {
return SILValue();
}
SILBuilderWithScope builder(BI);
auto *inst = builder.createIntegerLiteral(
BI->getLoc(), SILType::getBuiltinIntegerType(1, builder.getASTContext()),
true);
BI->replaceAllUsesWith(inst);
return inst;
}
SILValue swift::constantFoldBuiltin(BuiltinInst *BI,
std::optional<bool> &ResultsInError) {
const IntrinsicInfo &Intrinsic = BI->getIntrinsicInfo();
SILModule &M = BI->getModule();
// If it's an llvm intrinsic, fold the intrinsic.
if (Intrinsic.ID != llvm::Intrinsic::not_intrinsic)
return constantFoldIntrinsic(BI, Intrinsic.ID, ResultsInError);
// Otherwise, it should be one of the builtin functions.
OperandValueArrayRef Args = BI->getArguments();
const BuiltinInfo &Builtin = BI->getBuiltinInfo();
switch (Builtin.ID) {
default: break;
// Check and fold binary arithmetic with overflow.
#define BUILTIN(id, name, Attrs)
#define BUILTIN_BINARY_OPERATION_WITH_OVERFLOW(id, name, _, attrs, overload) \
case BuiltinValueKind::id:
#include "swift/AST/Builtins.def"
return constantFoldBinaryWithOverflow(BI, Builtin.ID, ResultsInError);
#define BUILTIN(id, name, attrs)
#define BUILTIN_BINARY_OPERATION(id, name, attrs) case BuiltinValueKind::id:
#include "swift/AST/Builtins.def"
return constantFoldBinary(BI, Builtin.ID, ResultsInError);
// Fold comparison predicates.
#define BUILTIN(id, name, Attrs)
#define BUILTIN_BINARY_PREDICATE(id, name, attrs, overload) \
case BuiltinValueKind::id:
#include "swift/AST/Builtins.def"
return constantFoldCompare(BI, Builtin.ID);
case BuiltinValueKind::Trunc:
case BuiltinValueKind::ZExt:
case BuiltinValueKind::SExt:
case BuiltinValueKind::TruncOrBitCast:
case BuiltinValueKind::ZExtOrBitCast:
case BuiltinValueKind::SExtOrBitCast: {
// We can fold if the value being cast is a constant.
auto *V = dyn_cast<IntegerLiteralInst>(Args[0]);
if (!V)
return nullptr;
APInt CastResV = constantFoldCast(V->getValue(), Builtin);
// Add the literal instruction to represent the result of the cast.
SILBuilderWithScope B(BI);
return B.createIntegerLiteral(BI->getLoc(), BI->getType(), CastResV);
}
// Process special builtins that are designed to check for overflows in
// integer conversions.
case BuiltinValueKind::SToSCheckedTrunc:
case BuiltinValueKind::UToUCheckedTrunc:
case BuiltinValueKind::SToUCheckedTrunc:
case BuiltinValueKind::UToSCheckedTrunc: {
return constantFoldAndCheckIntegerConversions(BI, Builtin, ResultsInError);
}
case BuiltinValueKind::SIToFP: {
auto *intLiteral = dyn_cast<IntegerLiteralInst>(Args[0]);
if (!intLiteral)
return nullptr;
APInt api = intLiteral->getValue();
auto *destTy = Builtin.Types[1]->castTo<BuiltinFloatType>();
// The following code is taken from LLVM's constant folder.
APFloat apf(destTy->getAPFloatSemantics(),
APInt::getZero(destTy->getBitWidth()));
apf.convertFromAPInt(api, Builtin.ID==BuiltinValueKind::SIToFP,
APFloat::rmNearestTiesToEven);
SILBuilderWithScope B(BI);
return B.createFloatLiteral(BI->getLoc(), BI->getType(), apf);
}
case BuiltinValueKind::BitCast: {
auto destTy = Builtin.Types[1];
// The following code is taken from LLVM's constant folder.
if (auto *intLiteral = dyn_cast<IntegerLiteralInst>(Args[0])) {
if (auto *floatDestTy = destTy->getAs<BuiltinFloatType>()) {
SILBuilderWithScope B(BI);
return B.createFloatLiteral(BI->getLoc(), BI->getType(),
APFloat(floatDestTy->getAPFloatSemantics(), intLiteral->getValue()));
}
} else if (auto *floatLiteral = dyn_cast<FloatLiteralInst>(Args[0])) {
if (destTy->is<BuiltinIntegerType>()) {
SILBuilderWithScope B(BI);
return B.createIntegerLiteral(BI->getLoc(), BI->getType(), floatLiteral->getValue().bitcastToAPInt());
}
}
return nullptr;
}
case BuiltinValueKind::IntToFPWithOverflow: {
// Get the value. It should be a constant in most cases.
// Note, this will not always be a constant, for example, when analyzing
// _convertFromBuiltinIntegerLiteral function itself.
auto *V = dyn_cast<IntegerLiteralInst>(Args[0]);
if (!V)
return nullptr;
APInt SrcVal = V->getValue();
auto *DestTy = Builtin.Types[1]->castTo<BuiltinFloatType>();
APFloat TruncVal(DestTy->getAPFloatSemantics());
APFloat::opStatus ConversionStatus = TruncVal.convertFromAPInt(
SrcVal, /*IsSigned=*/true, APFloat::rmNearestTiesToEven);
SILLocation Loc = BI->getLoc();
const Expr *CE = Loc.getAsASTNode<ApplyExpr>();
if (!CE)
CE = Loc.getAsASTNode<LiteralExpr>();
bool overflow = ConversionStatus & APFloat::opOverflow;
bool inexact = ConversionStatus & APFloat::opInexact;
if (overflow || inexact) {
// Check if diagnostics is enabled. If so, make sure to suppress
// warnings for conversions through explicit initializers,
// but do not suppress errors.
if (ResultsInError.has_value() &&
(overflow || !maybeExplicitFPCons(BI, Builtin))) {
SmallString<10> SrcAsString;
SrcVal.toString(SrcAsString, /*radix*/ 10, true /*isSigned*/);
if (overflow) {
diagnose(M.getASTContext(), Loc.getSourceLoc(),
diag::integer_literal_overflow, CE ? CE->getType() : DestTy,
SrcAsString);
} else {
SmallString<10> destStr;
unsigned srcBitWidth = SrcVal.getBitWidth();
// Display the 'TruncVal' like an integer in order to make the
// imprecision due to floating-point representation obvious.
TruncVal.toString(destStr, srcBitWidth, srcBitWidth);
diagnose(M.getASTContext(), Loc.getSourceLoc(),
diag::warning_int_to_fp_inexact, CE ? CE->getType() : DestTy,
SrcAsString, destStr);
}
ResultsInError = std::optional<bool>(true);
}
// If there is an overflow, just return nullptr as this is undefined
// behavior. Otherwise, continue folding as in the normal workflow.
if (overflow)
return nullptr;
}
// The call to the builtin should be replaced with the constant value.
SILBuilderWithScope B(BI);
return B.createFloatLiteral(Loc, BI->getType(), TruncVal);
}
case BuiltinValueKind::FPTrunc: {
return foldFPTrunc(BI, Builtin, ResultsInError);
}
// Conversions from floating point to integer,
case BuiltinValueKind::FPToSI:
case BuiltinValueKind::FPToUI: {
return foldFPToIntConversion(BI, Builtin, ResultsInError);
}
case BuiltinValueKind::IntToPtr: {
if (auto *op = dyn_cast<BuiltinInst>(BI->getOperand(0))) {
if (auto kind = op->getBuiltinKind()) {
// If we have a single int_to_ptr user and all of the types line up, we
// can simplify this instruction.
if (*kind == BuiltinValueKind::PtrToInt &&
op->getOperand(0)->getType() == BI->getResult(0)->getType()) {
return op->getOperand(0);
}
}
}
break;
}
case BuiltinValueKind::PtrToInt: {
if (auto *op = dyn_cast<BuiltinInst>(BI->getOperand(0))) {
if (auto kind = op->getBuiltinKind()) {
// If we have a single int_to_ptr user and all of the types line up, we
// can simplify this instruction.
if (*kind == BuiltinValueKind::IntToPtr &&
op->getOperand(0)->getType() == BI->getResult(0)->getType()) {
return op->getOperand(0);
}
}
}
break;
}
case BuiltinValueKind::AssumeNonNegative: {
auto *V = dyn_cast<IntegerLiteralInst>(Args[0]);
if (!V)
return nullptr;
APInt VInt = V->getValue();
if (VInt.isNegative() && ResultsInError.has_value()) {
diagnose(M.getASTContext(), BI->getLoc().getSourceLoc(),
diag::wrong_non_negative_assumption,
llvm::toString(VInt, /*Radix*/ 10, /*Signed*/ true));
ResultsInError = std::optional<bool>(true);
}
return V;
}
}
return nullptr;
}
/// On success this places a new value for each result of Op->getUser() into
/// Results. Results is guaranteed on success to have the same number of entries
/// as results of User. If we could only simplify /some/ of an instruction's
/// results, we still return true, but signal that we couldn't simplify by
/// placing SILValue() in that position instead.
static bool constantFoldInstruction(Operand *Op,
std::optional<bool> &ResultsInError,
SmallVectorImpl<SILValue> &Results) {
auto *User = Op->getUser();
// Constant fold builtin invocations.
if (auto *BI = dyn_cast<BuiltinInst>(User)) {
Results.push_back(constantFoldBuiltin(BI, ResultsInError));
return true;
}
// Constant fold extraction of a constant element.
if (auto *TEI = dyn_cast<TupleExtractInst>(User)) {
if (auto *TheTuple = dyn_cast<TupleInst>(TEI->getOperand())) {
Results.push_back(TheTuple->getElement(TEI->getFieldIndex()));
return true;
}
}
// Constant fold extraction of a constant struct element.
if (auto *SEI = dyn_cast<StructExtractInst>(User)) {
if (auto *Struct = dyn_cast<StructInst>(SEI->getOperand())) {
Results.push_back(Struct->getOperandForField(SEI->getField())->get());
return true;
}
}
// Constant fold struct destructuring of a trivial value or a guaranteed
// non-trivial value.
//
// We can not do this for non-trivial owned values without knowing that we
// will eliminate the underlying struct since we would be introducing a
// "use-after-free" from an ownership model perspective.
if (auto *DSI = dyn_cast<DestructureStructInst>(User)) {
if (auto *Struct = dyn_cast<StructInst>(DSI->getOperand())) {
llvm::transform(
Struct->getAllOperands(), std::back_inserter(Results),
[&](Operand &op) -> SILValue {
SILValue operandValue = op.get();
auto ownershipKind = operandValue->getOwnershipKind();
// First check if we are not compatible with guaranteed. This means
// we would be Owned or Unowned. If so, return SILValue().
if (!ownershipKind.isCompatibleWith(OwnershipKind::Guaranteed))
return SILValue();
// Otherwise check if our operand is non-trivial and None. In cases
// like that, the non-trivial type could be replacing an owned value
// where we lost that our underlying value is None due to
// intermediate aggregate literal operations. In that case, we /do
// not/ want to eliminate the destructure.
if (ownershipKind == OwnershipKind::None &&
!operandValue->getType().isTrivial(*Struct->getFunction()))
return SILValue();
return operandValue;
});
return true;
}
}
// Constant fold tuple destructuring of a trivial value or a guaranteed
// non-trivial value.
//
// We can not do this for non-trivial owned values without knowing that we
// will eliminate the underlying tuple since we would be introducing a
// "use-after-free" from the ownership model perspective.
if (auto *DTI = dyn_cast<DestructureTupleInst>(User)) {
if (auto *Tuple = dyn_cast<TupleInst>(DTI->getOperand())) {
llvm::transform(
Tuple->getAllOperands(), std::back_inserter(Results),
[&](Operand &op) -> SILValue {
SILValue operandValue = op.get();
auto ownershipKind = operandValue->getOwnershipKind();
// First check if we are not compatible with guaranteed. This means
// we would be Owned or Unowned. If so, return SILValue().
if (!ownershipKind.isCompatibleWith(OwnershipKind::Guaranteed))
return SILValue();
// Otherwise check if our operand is non-trivial and None. In cases
// like that, the non-trivial type could be replacing an owned value
// where we lost that our underlying value is None due to
// intermediate aggregate literal operations. In that case, we /do
// not/ want to eliminate the destructure.
if (ownershipKind == OwnershipKind::None &&
!operandValue->getType().isTrivial(*Tuple->getFunction()))
return SILValue();
return operandValue;
});
return true;
}
}
return false;
}
static bool isApplyOfBuiltin(SILInstruction &I, BuiltinValueKind kind) {
if (auto *BI = dyn_cast<BuiltinInst>(&I))
if (BI->getBuiltinInfo().ID == kind)
return true;
return false;
}
static bool isApplyOfKnownAvailability(SILInstruction &I) {
// Inlinable functions can be deserialized in other modules which can be
// compiled with a different deployment target.
if (I.getFunction()->getResilienceExpansion() != ResilienceExpansion::Maximal)
return false;
auto apply = FullApplySite::isa(&I);
if (!apply)
return false;
auto callee = apply.getReferencedFunctionOrNull();
if (!callee)
return false;
if (!callee->hasSemanticsAttr("availability.osversion"))
return false;
auto &context = I.getFunction()->getASTContext();
auto deploymentAvailability = AvailabilityRange::forDeploymentTarget(context);
if (apply.getNumArguments() != 3)
return false;
auto arg0 = dyn_cast<IntegerLiteralInst>(apply.getArgument(0));
if (!arg0)
return false;
auto arg1 = dyn_cast<IntegerLiteralInst>(apply.getArgument(1));
if (!arg1)
return false;
auto arg2 = dyn_cast<IntegerLiteralInst>(apply.getArgument(2));
if (!arg2)
return false;
auto version = VersionRange::allGTE(llvm::VersionTuple(
arg0->getValue().getLimitedValue(), arg1->getValue().getLimitedValue(),
arg2->getValue().getLimitedValue()));
auto callAvailability = AvailabilityRange(version);
return deploymentAvailability.isContainedIn(callAvailability);
}
static bool isApplyOfStringConcat(SILInstruction &I) {
if (auto *AI = dyn_cast<ApplyInst>(&I))
if (auto *Fn = AI->getReferencedFunctionOrNull())
if (Fn->hasSemanticsAttr(semantics::STRING_CONCAT))
return true;
return false;
}
static bool isFoldable(SILInstruction *I) {
return isa<IntegerLiteralInst>(I) || isa<FloatLiteralInst>(I) ||
isa<StringLiteralInst>(I);
}
/// Given a buitin instruction calling globalStringTablePointer, check whether
/// the string passed to the builtin is constructed from a literal and if so,
/// replace the uses of the builtin instruction with the string_literal inst.
/// Otherwise, emit diagnostics if the function containing the builtin is not a
/// transparent function. Transparent functions will be handled in their
/// callers.
static bool
constantFoldGlobalStringTablePointerBuiltin(BuiltinInst *bi,
bool enableDiagnostics) {
// Look through string initializer to extract the string_literal instruction.
//
// We can look through ownership instructions to get to the string value that
// is passed to this builtin.
SILValue builtinOperand = lookThroughOwnershipInsts(bi->getOperand(0));
SILFunction *caller = bi->getFunction();
FullApplySite stringInitSite = FullApplySite::isa(builtinOperand);
if (!stringInitSite || !stringInitSite.getReferencedFunctionOrNull() ||
!stringInitSite.getReferencedFunctionOrNull()->hasSemanticsAttr(
semantics::STRING_MAKE_UTF8)) {
// Emit diagnostics only on non-transparent functions.
if (enableDiagnostics && !caller->isTransparent()) {
diagnose(caller->getASTContext(), bi->getLoc().getSourceLoc(),
diag::global_string_pointer_on_non_constant);
}
return false;
}
// Replace the builtin by the first argument of the "string.makeUTF8"
// initializer which must be a string_literal instruction.
SILValue stringLiteral = stringInitSite.getArgument(0);
assert(isa<StringLiteralInst>(stringLiteral));
bi->replaceAllUsesWith(stringLiteral);
return true;
}
/// Initialize the worklist to all of the constant instructions.
void ConstantFolder::initializeWorklist(SILFunction &f) {
for (auto &block : f) {
for (auto ii = block.begin(), ie = block.end(); ii != ie; ) {
auto *inst = &*ii;
++ii;
// TODO: Eliminate trivially dead instructions here.
// If `I` is a floating-point literal instruction where the literal is
// inf, it means the input has a literal that overflows even
// MaxBuiltinFloatType. Diagnose this error, but allow this instruction
// to be folded, if needed.
if (auto *floatLit = dyn_cast<FloatLiteralInst>(inst)) {
APFloat fpVal = floatLit->getValue();
if (EnableDiagnostics && fpVal.isInfinity() &&
floatLit->getLoc().getAsASTNode<FloatLiteralExpr>()) {
SmallString<10> litStr;
tryExtractLiteralText(floatLit, litStr);
diagnose(inst->getModule().getASTContext(), inst->getLoc().getSourceLoc(),
diag::warning_float_overflows_maxbuiltin, litStr,
fpVal.isNegative());
}
}
if (isFoldable(inst) && inst->hasUsesOfAnyResult()) {
WorkList.insert(inst);
continue;
}
// - Should we replace calls to assert_configuration by the assert
// configuration and fold calls to any cond_unreachable.
if (AssertConfiguration != SILOptions::DisableReplacement &&
(isApplyOfBuiltin(*inst, BuiltinValueKind::AssertConf) ||
isApplyOfBuiltin(*inst, BuiltinValueKind::CondUnreachable))) {
WorkList.insert(inst);
continue;
}
if (isApplyOfBuiltin(*inst, BuiltinValueKind::GlobalStringTablePointer) ||
isApplyOfBuiltin(*inst, BuiltinValueKind::IsConcrete)) {
WorkList.insert(inst);
continue;
}
// Builtin.ifdef only replaced when not building the stdlib.
if (isApplyOfBuiltin(*inst, BuiltinValueKind::Ifdef)) {
if (!inst->getModule().getASTContext().SILOpts.ParseStdlib) {
WorkList.insert(inst);
continue;
}
}
if (isApplyOfKnownAvailability(*inst)) {
WorkList.insert(inst);
continue;
}
if (isa<CheckedCastBranchInst>(inst) ||
isa<CheckedCastAddrBranchInst>(inst) ||
isa<UnconditionalCheckedCastInst>(inst) ||
isa<UnconditionalCheckedCastAddrInst>(inst)) {
WorkList.insert(inst);
continue;
}
if (isApplyOfStringConcat(*inst)) {
WorkList.insert(inst);
continue;
}
if (auto *bi = dyn_cast<BuiltinInst>(inst)) {
if (auto kind = bi->getBuiltinKind()) {
if (isPolymorphicBuiltin(kind.value())) {
WorkList.insert(bi);
continue;
}
}
}
// If we have nominal type literals like struct, tuple, enum visit them
// like we do in the worklist to see if we can fold any projection
// manipulation operations.
if (isa<StructInst>(inst) || isa<TupleInst>(inst)) {
// TODO: Enum.
WorkList.insert(inst);
continue;
}
// ...
}
}
}
/// Returns true if \p i is an instruction that has a stateless inverse. We want
/// to visit such instructions to eliminate such round-trip unnecessary
/// operations.
///
/// As an example, consider casts, inttoptr, ptrtoint and friends.
static bool isReadNoneAndInvertible(SILInstruction *i) {
if (auto *bi = dyn_cast<BuiltinInst>(i)) {
// Look for ptrtoint and inttoptr for now.
if (auto kind = bi->getBuiltinKind()) {
switch (*kind) {
default:
return false;
case BuiltinValueKind::PtrToInt:
case BuiltinValueKind::IntToPtr:
return true;
}
}
}
// Be conservative and return false if we do not have any information.
return false;
}
SILAnalysis::InvalidationKind
ConstantFolder::processWorkList() {
LLVM_DEBUG(llvm::dbgs() << "*** ConstPropagation processing: \n");
// This is the list of traits that this transformation might preserve.
bool InvalidateBranches = false;
bool InvalidateCalls = false;
bool InvalidateInstructions = false;
// The list of instructions whose evaluation resulted in error or warning.
// This is used to avoid duplicate error reporting in case we reach the same
// instruction from different entry points in the WorkList.
llvm::DenseSet<SILInstruction *> ErrorSet;
CastOptimizer CastOpt(FuncBuilder, nullptr /*SILBuilderContext*/,
/* replaceValueUsesAction */
[&](SILValue oldValue, SILValue newValue) {
InvalidateInstructions = true;
oldValue->replaceAllUsesWith(newValue);
},
/* ReplaceInstUsesAction */
[&](SingleValueInstruction *I, ValueBase *V) {
InvalidateInstructions = true;
I->replaceAllUsesWith(V);
},
/* EraseAction */
[&](SILInstruction *I) {
auto *TI = dyn_cast<TermInst>(I);
if (TI) {
// Invalidate analysis information related to
// branches. Replacing
// unconditional_check_branch type instructions
// by a trap will also invalidate branches/the
// CFG.
InvalidateBranches = true;
}
InvalidateInstructions = true;
WorkList.remove(I);
I->eraseFromParent();
});
auto callbacks =
InstModCallbacks().onDelete([&](SILInstruction *instToDelete) {
WorkList.remove(instToDelete);
InvalidateInstructions = true;
instToDelete->eraseFromParent();
});
InstructionDeleter deleter(std::move(callbacks), /*assumeFixedLifetimes=*/ !EnableDiagnostics);
// An out parameter array that we use to return new simplified results from
// constantFoldInstruction.
SmallVector<SILValue, 8> ConstantFoldedResults;
while (!WorkList.empty()) {
SILInstruction *I = WorkList.pop_back_val();
assert(I->getParent() && "SILInstruction must have parent.");
LLVM_DEBUG(llvm::dbgs() << "Visiting: " << *I);
// Replace assert_configuration instructions by their constant value. We
// want them to be replace even if we can't fully propagate the constant.
if (AssertConfiguration != SILOptions::DisableReplacement)
if (auto *BI = dyn_cast<BuiltinInst>(I)) {
if (isApplyOfBuiltin(*BI, BuiltinValueKind::AssertConf)) {
// Instantiate the constant.
SILBuilderWithScope B(BI);
auto AssertConfInt = B.createIntegerLiteral(
BI->getLoc(), BI->getType(), AssertConfiguration);
BI->replaceAllUsesWith(AssertConfInt);
// Schedule users for constant folding.
WorkList.insert(AssertConfInt);
// Delete the call.
eliminateDeadInstruction(BI, deleter.getCallbacks(),
/*assumeFixedLifetimes=*/ !EnableDiagnostics);
continue;
}
// Kill calls to conditionallyUnreachable if we've folded assert
// configuration calls.
if (isApplyOfBuiltin(*BI, BuiltinValueKind::CondUnreachable)) {
assert(BI->use_empty() && "use of conditionallyUnreachable?!");
recursivelyDeleteTriviallyDeadInstructions(BI, /*force*/ true,
deleter.getCallbacks());
InvalidateInstructions = true;
continue;
}
}
// Replace a known availability.version semantic call.
if (isApplyOfKnownAvailability(*I)) {
if (auto apply = dyn_cast<ApplyInst>(I)) {
SILBuilderWithScope B(I);
auto tru = B.createIntegerLiteral(apply->getLoc(), apply->getType(), 1);
apply->replaceAllUsesWith(tru);
eliminateDeadInstruction(I, deleter.getCallbacks(),
/*assumeFixedLifetimes=*/ !EnableDiagnostics);
WorkList.insert(tru);
InvalidateInstructions = true;
}
continue;
}
// If we have a cast instruction, try to optimize it.
if (isa<CheckedCastBranchInst>(I) || isa<CheckedCastAddrBranchInst>(I) ||
isa<UnconditionalCheckedCastInst>(I) ||
isa<UnconditionalCheckedCastAddrInst>(I)) {
// Try to perform cast optimizations. Invalidation is handled by a
// callback inside the cast optimizer.
SILInstruction *Result = nullptr;
switch(I->getKind()) {
default:
llvm_unreachable("Unexpected instruction for cast optimizations");
case SILInstructionKind::CheckedCastBranchInst:
Result = CastOpt.simplifyCheckedCastBranchInst(cast<CheckedCastBranchInst>(I));
break;
case SILInstructionKind::CheckedCastAddrBranchInst:
Result = CastOpt.simplifyCheckedCastAddrBranchInst(cast<CheckedCastAddrBranchInst>(I));
break;
case SILInstructionKind::UnconditionalCheckedCastInst: {
auto Value =
CastOpt.optimizeUnconditionalCheckedCastInst(cast<UnconditionalCheckedCastInst>(I));
if (Value) Result = Value->getDefiningInstruction();
break;
}
case SILInstructionKind::UnconditionalCheckedCastAddrInst:
Result = CastOpt.optimizeUnconditionalCheckedCastAddrInst(cast<UnconditionalCheckedCastAddrInst>(I));
break;
}
if (Result) {
if (isa<CheckedCastBranchInst>(Result) ||
isa<CheckedCastAddrBranchInst>(Result) ||
isa<UnconditionalCheckedCastInst>(Result) ||
isa<UnconditionalCheckedCastAddrInst>(Result))
WorkList.insert(Result);
}
continue;
}
// Constant fold uses of globalStringTablePointer builtin.
if (isApplyOfBuiltin(*I, BuiltinValueKind::GlobalStringTablePointer)) {
if (constantFoldGlobalStringTablePointerBuiltin(cast<BuiltinInst>(I),
EnableDiagnostics)) {
// Here, the builtin instruction got folded, so clean it up.
eliminateDeadInstruction(I, deleter.getCallbacks(),
/*assumeFixedLifetimes=*/ !EnableDiagnostics);
}
continue;
}
// See if we have a CondFailMessage that we can canonicalize.
if (isApplyOfBuiltin(*I, BuiltinValueKind::CondFailMessage)) {
// See if our operand is a string literal inst. In such a case, fold into
// cond_fail instruction.
if (auto *sli = dyn_cast<StringLiteralInst>(I->getOperand(1))) {
if (sli->getEncoding() == StringLiteralInst::Encoding::UTF8) {
SILBuilderWithScope builder(I);
auto *cfi = builder.createCondFail(I->getLoc(), I->getOperand(0),
sli->getValue());
WorkList.insert(cfi);
recursivelyDeleteTriviallyDeadInstructions(I, /*force*/ true,
deleter.getCallbacks());
}
}
continue;
}
if (isApplyOfBuiltin(*I, BuiltinValueKind::IsConcrete)) {
if (constantFoldIsConcrete(cast<BuiltinInst>(I))) {
// Here, the builtin instruction got folded, so clean it up.
recursivelyDeleteTriviallyDeadInstructions(I, /*force*/ true,
deleter.getCallbacks());
}
continue;
}
// Builtin.ifdef_... is expected to stay unresolved when building the stdlib
// and must be only used in @_alwaysEmitIntoClient exported functions, which
// means we never generate IR for it (when building stdlib). Client code is
// then always constant-folding this builtin based on the compilation flags
// of the client module.
if (isApplyOfBuiltin(*I, BuiltinValueKind::Ifdef)) {
if (!I->getModule().getASTContext().SILOpts.ParseStdlib) {
auto *BI = cast<BuiltinInst>(I);
StringRef flagName = BI->getName().str().drop_front(strlen("ifdef_"));
const LangOptions &langOpts = I->getModule().getASTContext().LangOpts;
bool val = langOpts.isCustomConditionalCompilationFlagSet(flagName);
SILBuilderWithScope builder(BI);
auto *inst = builder.createIntegerLiteral(
BI->getLoc(),
SILType::getBuiltinIntegerType(1, builder.getASTContext()), val);
BI->replaceAllUsesWith(inst);
eliminateDeadInstruction(I, deleter.getCallbacks(),
/*assumeFixedLifetimes=*/ !EnableDiagnostics);
continue;
}
}
if (auto *bi = dyn_cast<BuiltinInst>(I)) {
if (auto kind = bi->getBuiltinKind()) {
if (SILValue v = specializePolymorphicBuiltin(bi, kind.value())) {
// If bi had a result, RAUW.
if (bi->getResult(0)->getType() !=
bi->getModule().Types.getEmptyTupleType())
bi->replaceAllUsesWith(v);
// Then delete no matter what.
bi->eraseFromParent();
InvalidateInstructions = true;
continue;
}
}
}
// Go through all users of the constant and try to fold them.
for (auto Result : I->getResults()) {
for (auto *Use : Result->getUses()) {
SILInstruction *User = Use->getUser();
LLVM_DEBUG(llvm::dbgs() << " User: " << *User);
// It is possible that we had processed this user already. Do not try to
// fold it again if we had previously produced an error while folding
// it. It is not always possible to fold an instruction in case of
// error.
if (ErrorSet.count(User))
continue;
// Some constant users may indirectly cause folding of their users.
if (isa<StructInst>(User) || isa<TupleInst>(User)) {
WorkList.insert(User);
continue;
}
// Always consider cond_fail instructions as potential for DCE. If the
// expression feeding them is false, they are dead. We can't handle
// this as part of the constant folding logic, because there is no value
// they can produce (other than empty tuple, which is wasteful).
if (isa<CondFailInst>(User))
deleter.trackIfDead(User);
// See if we have an instruction that is read none and has a stateless
// inverse. If we do, add it to the worklist so we can check its users
// for the inverse operation and see if we can perform constant folding
// on the inverse operation. This can eliminate annoying "round trip"s.
//
// NOTE: We are assuming on purpose that our inverse will be read none,
// since otherwise we wouldn't be able to constant fold it this way.
if (isReadNoneAndInvertible(User)) {
WorkList.insert(User);
}
// See if we have a CondFailMessage of a string_Literal. If we do, add
// it to the worklist, so we can clean it up.
if (isApplyOfBuiltin(*User, BuiltinValueKind::CondFailMessage)) {
if (auto *sli = dyn_cast<StringLiteralInst>(I)) {
if (sli->getEncoding() == StringLiteralInst::Encoding::UTF8) {
WorkList.insert(User);
}
}
}
// If the user is a bitcast, we may be able to constant
// fold its users.
if (isApplyOfBuiltin(*User, BuiltinValueKind::BitCast)) {
WorkList.insert(User);
}
// Initialize ResultsInError as a None optional.
//
// We are essentially using this optional to represent 3 states: true,
// false, and n/a.
std::optional<bool> ResultsInError;
// If we are asked to emit diagnostics, override ResultsInError with a
// Some optional initialized to false.
if (EnableDiagnostics)
ResultsInError = false;
// Try to fold the user. If ResultsInError is None, we do not emit any
// diagnostics. If ResultsInError is some, we use it as our return
// value.
ConstantFoldedResults.clear();
bool Success =
constantFoldInstruction(Use, ResultsInError, ConstantFoldedResults);
// If we did not pass in a None and the optional is set to true, add the
// user to our error set.
if (ResultsInError.has_value() && ResultsInError.value())
ErrorSet.insert(User);
// We failed to constant propagate... continue...
if (!Success || llvm::none_of(ConstantFoldedResults,
[](SILValue v) { return bool(v); }))
continue;
// Now iterate over our new results.
for (auto pair : llvm::enumerate(ConstantFoldedResults)) {
SILValue C = pair.value();
unsigned Index = pair.index();
// Skip any values that we couldn't simplify.
if (!C)
continue;
// Handle a corner case: if this instruction is an unreachable CFG
// loop there is no defined dominance order and we can end up with
// loops in the use-def chain. Just bail in this case.
if (C->getDefiningInstruction() == User)
continue;
// Ok, we have succeeded.
++NumInstFolded;
// We were able to fold, so all users should use the new folded
// value. If we don't have any such users, continue.
//
// NOTE: The reason why we check if our result has uses is that if
// User is a MultipleValueInstruction an infinite loop can result if
// User has a result different than the one at Index that we can not
// constant fold and if C's defining instruction is an aggregate that
// defines an operand of I.
//
// As an elucidating example, consider the following SIL:
//
// %w = integer_literal $Builtin.Word, 1
// %0 = struct $Int (%w : $Builtin.Word) (*)
// %1 = apply %f() : $@convention(thin) () -> @owned Klass
// %2 = tuple (%0 : $Int, %1 : $Klass)
// (%3, %4) = destructure_tuple %2 : $(Int, Klass)
// store %4 to [init] %mem2: %*Klass
//
// Without this check, we would infinite loop by processing our
// worklist as follows:
//
// 1. We visit %w and add %0 to the worklist unconditionally since it
// is a StructInst.
//
// 2. We visit %0 and then since %2 is a tuple, we add %2 to the
// worklist unconditionally.
//
// 3. We visit %2 and see that it has a destructure_tuple user. We see
// that we can simplify %3 -> %0, but cannot simplify %4. This
// means that if we just assume success if we can RAUW %3 without
// checking if we will actually replace anything, we will add %0's
// defining instruction (*) to the worklist. Q.E.D.
//
// In contrast, if we realize that RAUWing %3 does nothing and skip
// it, we exit the worklist as expected.
SILValue r = User->getResult(Index);
if (r->use_empty()) {
deleter.trackIfDead(User);
continue;
}
// Otherwise, do the RAUW.
User->getResult(Index)->replaceAllUsesWith(C);
// Record the user if it is dead to perform the necessary cleanups
// later.
deleter.trackIfDead(User);
// The new constant could be further folded now, add it to the
// worklist.
if (auto *Inst = C->getDefiningInstruction())
WorkList.insert(Inst);
}
}
}
// Eagerly DCE. We do this after visiting all users to ensure we don't
// invalidate the uses iterator.
deleter.cleanupDeadInstructions();
}
// TODO: refactor this code outside of the method. Passes should not merge
// invalidation kinds themselves.
using InvalidationKind = SILAnalysis::InvalidationKind;
unsigned Inv = InvalidationKind::Nothing;
if (InvalidateInstructions) Inv |= (unsigned) InvalidationKind::Instructions;
if (InvalidateCalls) Inv |= (unsigned) InvalidationKind::Calls;
if (InvalidateBranches) Inv |= (unsigned) InvalidationKind::Branches;
return InvalidationKind(Inv);
}
void ConstantFolder::dumpWorklist() const {
#ifndef NDEBUG
llvm::dbgs() << "*** Dumping Constant Folder Worklist ***\n";
for (auto *i : WorkList) {
llvm::dbgs() << *i;
}
llvm::dbgs() << "\n";
#endif
}