Skip to content

Commit addae9b

Browse files
committed
[SIL Diagnostics] Add warnings for imprecision during assignment of
floating-point literals to variables of floating-point type.
1 parent 92ecd5f commit addae9b

9 files changed

+562
-98
lines changed

include/swift/AST/DiagnosticsSIL.def

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -320,6 +320,23 @@ ERROR(negative_fp_literal_overflow_unsigned, none,
320320
"negative literal '%0' cannot be converted to %select{|unsigned }2%1",
321321
(StringRef, Type, bool))
322322

323+
// Overflow and underflow warnings for floating-point truncation
324+
WARNING(warning_float_trunc_overflow, none,
325+
"'%0' overflows to %select{|-}2inf during conversion to %1",
326+
(StringRef, Type, bool))
327+
328+
WARNING(warning_float_trunc_underflow, none,
329+
"'%0' underflows and loses precision during conversion to %1",
330+
(StringRef, Type, bool))
331+
332+
WARNING(warning_float_trunc_hex_inexact, none,
333+
"'%0' loses precision during conversion to %1",
334+
(StringRef, Type, bool))
335+
336+
WARNING(warning_float_overflows_maxbuiltin, none,
337+
"'%0' overflows to %select{|-}1inf because its magnitude exceeds "
338+
"the limits of a float literal", (StringRef, bool))
339+
323340
#ifndef DIAG_NO_UNDEF
324341
# if defined(DIAG)
325342
# undef DIAG

include/swift/AST/KnownStdlibTypes.def

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ KNOWN_STDLIB_TYPE_DECL(UInt8, NominalTypeDecl, 0)
4141

4242
KNOWN_STDLIB_TYPE_DECL(Float, NominalTypeDecl, 0)
4343
KNOWN_STDLIB_TYPE_DECL(Double, NominalTypeDecl, 0)
44+
KNOWN_STDLIB_TYPE_DECL(Float80, NominalTypeDecl, 0)
4445

4546
KNOWN_STDLIB_TYPE_DECL(String, NominalTypeDecl, 0)
4647
KNOWN_STDLIB_TYPE_DECL(Substring, NominalTypeDecl, 0)

lib/SILOptimizer/Utils/ConstantFolding.cpp

Lines changed: 220 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -12,15 +12,16 @@
1212

1313
#include "swift/SILOptimizer/Utils/ConstantFolding.h"
1414

15-
#include "swift/AST/Expr.h"
1615
#include "swift/AST/DiagnosticsSIL.h"
16+
#include "swift/AST/Expr.h"
1717
#include "swift/SIL/PatternMatch.h"
1818
#include "swift/SIL/SILBuilder.h"
1919
#include "swift/SILOptimizer/Utils/CastOptimizer.h"
2020
#include "swift/SILOptimizer/Utils/Local.h"
21+
#include "llvm/ADT/APFloat.h"
22+
#include "llvm/ADT/APSInt.h"
2123
#include "llvm/ADT/Statistic.h"
2224
#include "llvm/Support/Debug.h"
23-
#include "llvm/ADT/APSInt.h"
2425

2526
#define DEBUG_TYPE "constant-folding"
2627

@@ -970,6 +971,205 @@ static SILValue foldFPToIntConversion(BuiltinInst *BI,
970971
return B.createIntegerLiteral(BI->getLoc(), BI->getType(), resInt);
971972
}
972973

974+
/// Captures the layout of IEEE754 floating point values.
975+
struct IEEESemantics {
976+
uint8_t bitWidth;
977+
uint8_t exponentBitWidth;
978+
uint8_t significandBitWidth; // Ignores the integer part.
979+
bool explicitIntegerPart;
980+
int minExponent;
981+
982+
public:
983+
IEEESemantics(uint8_t bits, uint8_t expBits, uint8_t sigBits,
984+
bool explicitIntPart) {
985+
bitWidth = bits;
986+
exponentBitWidth = expBits;
987+
significandBitWidth = sigBits;
988+
explicitIntegerPart = explicitIntPart;
989+
minExponent = -(1 << (exponentBitWidth - 1)) + 2;
990+
}
991+
};
992+
993+
IEEESemantics getFPSemantics(BuiltinFloatType *fpType) {
994+
switch (fpType->getFPKind()) {
995+
case BuiltinFloatType::IEEE32:
996+
return IEEESemantics(32, 8, 23, false);
997+
case BuiltinFloatType::IEEE64:
998+
return IEEESemantics(64, 11, 52, false);
999+
case BuiltinFloatType::IEEE80:
1000+
return IEEESemantics(80, 15, 63, true);
1001+
default:
1002+
llvm_unreachable("Unexpected semantics");
1003+
}
1004+
}
1005+
1006+
/// This function, given the exponent and significand of a binary fraction
1007+
/// equalling 1.srcSignificand x 2^srcExponent,
1008+
/// determines whether converting the value to a given destination semantics
1009+
/// results in an underflow and whether the significand precision is reduced
1010+
/// because of the underflow.
1011+
bool isLossyUnderflow(int srcExponent, uint64_t srcSignificand,
1012+
IEEESemantics srcSem, IEEESemantics destSem) {
1013+
if (srcExponent >= destSem.minExponent)
1014+
return false;
1015+
1016+
// Is the value smaller than the smallest non-zero value of destSem?
1017+
if (srcExponent < destSem.minExponent - destSem.significandBitWidth)
1018+
return true;
1019+
1020+
// Truncate the significand to the significand width of destSem.
1021+
uint8_t bitWidthDecrease =
1022+
srcSem.significandBitWidth - destSem.significandBitWidth;
1023+
uint64_t truncSignificand = bitWidthDecrease > 0
1024+
? (srcSignificand >> bitWidthDecrease)
1025+
: srcSignificand;
1026+
1027+
// Compute the significand bits lost due to subnormal form. Note that the
1028+
// integer part: 1 will use up a significand bit in denormal form.
1029+
unsigned additionalLoss = destSem.minExponent - srcExponent + 1;
1030+
1031+
// Check whether a set LSB is lost due to subnormal representation.
1032+
unsigned lostLSBBitMask = (1 << additionalLoss) - 1;
1033+
return (truncSignificand & lostLSBBitMask);
1034+
}
1035+
1036+
/// This function, given an IEEE floating-point value (srcVal), determines
1037+
/// whether the conversion to a given destination semantics results
1038+
/// in an underflow and whether the significand precision is reduced
1039+
/// because of the underflow.
1040+
bool isLossyUnderflow(APFloat srcVal, BuiltinFloatType *srcType,
1041+
BuiltinFloatType *destType) {
1042+
if (srcVal.isNaN() || srcVal.isZero() || srcVal.isInfinity())
1043+
return false;
1044+
1045+
IEEESemantics srcSem = getFPSemantics(srcType);
1046+
IEEESemantics destSem = getFPSemantics(destType);
1047+
1048+
if (srcSem.bitWidth <= destSem.bitWidth)
1049+
return false;
1050+
1051+
if (srcVal.isDenormal()) {
1052+
// A denormal value of a larger IEEE FP type will definitely
1053+
// reduce to zero when truncated to smaller IEEE FP type.
1054+
return true;
1055+
}
1056+
1057+
APInt bitPattern = srcVal.bitcastToAPInt();
1058+
uint64_t significand =
1059+
bitPattern.getLoBits(srcSem.significandBitWidth).getZExtValue();
1060+
return isLossyUnderflow(ilogb(srcVal), significand, srcSem, destSem);
1061+
}
1062+
1063+
/// This function determines whether the float literal in the given
1064+
/// SIL instruction is specified using hex-float notation in the Swift source.
1065+
bool isHexLiteralInSource(FloatLiteralInst *flitInst) {
1066+
Expr *expr = flitInst->getLoc().getAsASTNode<Expr>();
1067+
if (!expr)
1068+
return false;
1069+
1070+
// Iterate through a sequence of folded implicit constructors if any, and
1071+
// try to extract the FloatLiteralExpr.
1072+
while (auto *callExpr = dyn_cast<CallExpr>(expr)) {
1073+
if (!callExpr->isImplicit() || callExpr->getNumArguments() != 1 ||
1074+
!dyn_cast<ConstructorRefCallExpr>(callExpr->getFn()))
1075+
break;
1076+
1077+
auto *tupleExpr = dyn_cast<TupleExpr>(callExpr->getArg());
1078+
if (!tupleExpr)
1079+
break;
1080+
1081+
expr = tupleExpr->getElement(0);
1082+
}
1083+
auto *flitExpr = dyn_cast<FloatLiteralExpr>(expr);
1084+
if (!flitExpr)
1085+
return false;
1086+
return flitExpr->getDigitsText().startswith("0x");
1087+
}
1088+
1089+
bool maybeExplicitFPCons(BuiltinInst *BI, const BuiltinInfo &Builtin) {
1090+
assert(Builtin.ID == BuiltinValueKind::FPTrunc);
1091+
1092+
auto *callExpr = BI->getLoc().getAsASTNode<CallExpr>();
1093+
if (!callExpr || !dyn_cast<ConstructorRefCallExpr>(callExpr->getFn()))
1094+
return true; // not enough information here, so err on the safer side.
1095+
1096+
if (!callExpr->isImplicit())
1097+
return true;
1098+
1099+
// Here, the 'callExpr' is an implicit FP construction. However, if it is
1100+
// constructing a Double it could be a part of an explicit construction of
1101+
// another FP type, which uses an implicit conversion to Double as an
1102+
// intermediate step. So we conservatively assume that an implicit
1103+
// construction of Double could be a part of an explicit conversion
1104+
// and suppress the warning.
1105+
auto &astCtx = BI->getModule().getASTContext();
1106+
auto *typeDecl = callExpr->getType()->getCanonicalType().getAnyNominal();
1107+
return (typeDecl && typeDecl == astCtx.getDoubleDecl());
1108+
}
1109+
1110+
static SILValue foldFPTrunc(BuiltinInst *BI, const BuiltinInfo &Builtin,
1111+
Optional<bool> &ResultsInError) {
1112+
1113+
assert(Builtin.ID == BuiltinValueKind::FPTrunc);
1114+
1115+
auto *flitInst = dyn_cast<FloatLiteralInst>(BI->getArguments()[0]);
1116+
if (!flitInst)
1117+
return nullptr; // We can fold only compile-time constant arguments.
1118+
1119+
SILLocation Loc = BI->getLoc();
1120+
auto *srcType = Builtin.Types[0]->castTo<BuiltinFloatType>();
1121+
auto *destType = Builtin.Types[1]->castTo<BuiltinFloatType>();
1122+
bool losesInfo;
1123+
APFloat truncVal = flitInst->getValue();
1124+
APFloat::opStatus opStatus =
1125+
truncVal.convert(destType->getAPFloatSemantics(),
1126+
APFloat::rmNearestTiesToEven, &losesInfo);
1127+
1128+
// Emit a warning if one of the following conditions hold: (a) the source
1129+
// value overflows the destination type, or (b) the source value is tiny and
1130+
// the tininess results in additional loss of precision when converted to the
1131+
// destination type beyond what would result in the normal scenario, or
1132+
// (c) the source value is a hex-float literal that cannot be precisely
1133+
// represented in the destination type.
1134+
// Suppress all warnings if the conversion is made through an explicit
1135+
// constructor invocation.
1136+
if (ResultsInError.hasValue() && !maybeExplicitFPCons(BI, Builtin)) {
1137+
bool overflow = opStatus & APFloat::opStatus::opOverflow;
1138+
bool tinynInexact =
1139+
isLossyUnderflow(flitInst->getValue(), srcType, destType);
1140+
bool hexnInexact =
1141+
(opStatus != APFloat::opStatus::opOK) && isHexLiteralInSource(flitInst);
1142+
1143+
if (overflow || tinynInexact || hexnInexact) {
1144+
SILModule &M = BI->getModule();
1145+
const ApplyExpr *CE = Loc.getAsASTNode<ApplyExpr>();
1146+
1147+
SmallString<10> fplitStr;
1148+
tryExtractLiteralText(flitInst, fplitStr);
1149+
1150+
auto userType = CE ? CE->getType() : destType;
1151+
auto diagId = overflow
1152+
? diag::warning_float_trunc_overflow
1153+
: (hexnInexact ? diag::warning_float_trunc_hex_inexact
1154+
: diag::warning_float_trunc_underflow);
1155+
diagnose(M.getASTContext(), Loc.getSourceLoc(), diagId, fplitStr,
1156+
userType, truncVal.isNegative());
1157+
1158+
ResultsInError = Optional<bool>(true);
1159+
}
1160+
}
1161+
// Abort folding if we have subnormality, NaN or opInvalid status.
1162+
if ((opStatus & APFloat::opStatus::opInvalidOp) ||
1163+
(opStatus & APFloat::opStatus::opDivByZero) ||
1164+
(opStatus & APFloat::opStatus::opUnderflow) || truncVal.isDenormal()) {
1165+
return nullptr;
1166+
}
1167+
// Allow folding if there is no loss, overflow or normal imprecision
1168+
// (i.e., opOverflow, opOk, or opInexact).
1169+
SILBuilderWithScope B(BI);
1170+
return B.createFloatLiteral(Loc, BI->getType(), truncVal);
1171+
}
1172+
9731173
static SILValue constantFoldBuiltin(BuiltinInst *BI,
9741174
Optional<bool> &ResultsInError) {
9751175
const IntrinsicInfo &Intrinsic = BI->getIntrinsicInfo();
@@ -1044,10 +1244,9 @@ case BuiltinValueKind::id:
10441244
if (!V)
10451245
return nullptr;
10461246
APInt SrcVal = V->getValue();
1047-
Type DestTy = Builtin.Types[1];
1247+
auto *DestTy = Builtin.Types[1]->castTo<BuiltinFloatType>();
10481248

1049-
APFloat TruncVal(
1050-
DestTy->castTo<BuiltinFloatType>()->getAPFloatSemantics());
1249+
APFloat TruncVal(DestTy->getAPFloatSemantics());
10511250
APFloat::opStatus ConversionStatus = TruncVal.convertFromAPInt(
10521251
SrcVal, /*IsSigned=*/true, APFloat::rmNearestTiesToEven);
10531252

@@ -1077,27 +1276,7 @@ case BuiltinValueKind::id:
10771276
}
10781277

10791278
case BuiltinValueKind::FPTrunc: {
1080-
// Get the value. It should be a constant in most cases.
1081-
auto *V = dyn_cast<FloatLiteralInst>(Args[0]);
1082-
if (!V)
1083-
return nullptr;
1084-
APFloat TruncVal = V->getValue();
1085-
Type DestTy = Builtin.Types[1];
1086-
bool losesInfo;
1087-
APFloat::opStatus ConversionStatus = TruncVal.convert(
1088-
DestTy->castTo<BuiltinFloatType>()->getAPFloatSemantics(),
1089-
APFloat::rmNearestTiesToEven, &losesInfo);
1090-
SILLocation Loc = BI->getLoc();
1091-
1092-
// Check if conversion was successful.
1093-
if (ConversionStatus != APFloat::opStatus::opOK &&
1094-
ConversionStatus != APFloat::opStatus::opInexact) {
1095-
return nullptr;
1096-
}
1097-
1098-
// The call to the builtin should be replaced with the constant value.
1099-
SILBuilderWithScope B(BI);
1100-
return B.createFloatLiteral(Loc, BI->getType(), TruncVal);
1279+
return foldFPTrunc(BI, Builtin, ResultsInError);
11011280
}
11021281

11031282
// Conversions from floating point to integer,
@@ -1216,6 +1395,21 @@ bool ConstantFolder::constantFoldStringConcatenation(ApplyInst *AI) {
12161395
void ConstantFolder::initializeWorklist(SILFunction &F) {
12171396
for (auto &BB : F) {
12181397
for (auto &I : BB) {
1398+
// If `I` is a floating-point literal instruction where the literal is
1399+
// inf, it means the input has a literal that overflows even
1400+
// MaxBuiltinFloatType. Diagnose this error, but allow this instruction
1401+
// to be folded, if needed.
1402+
if (auto floatLit = dyn_cast<FloatLiteralInst>(&I)) {
1403+
APFloat fpVal = floatLit->getValue();
1404+
if (EnableDiagnostics && fpVal.isInfinity()) {
1405+
SmallString<10> litStr;
1406+
tryExtractLiteralText(floatLit, litStr);
1407+
diagnose(I.getModule().getASTContext(), I.getLoc().getSourceLoc(),
1408+
diag::warning_float_overflows_maxbuiltin, litStr,
1409+
fpVal.isNegative());
1410+
}
1411+
}
1412+
12191413
if (isFoldable(&I) && I.hasUsesOfAnyResult()) {
12201414
WorkList.insert(&I);
12211415
continue;

test/SILOptimizer/constant_propagation_floats.sil

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,3 +136,31 @@ return %1 : $Builtin.Int64
136136
// CHECK: [[RES:%.*]] = integer_literal $Builtin.Int64, 1844674407370955
137137
// CHECK-NEXT: return [[RES]] : $Builtin.Int64
138138
}
139+
140+
// The following tests check folding of FPTrunc instructions.
141+
142+
sil @foldDoubleToFloat: $@convention(thin) () -> Builtin.FPIEEE32 {
143+
bb0:
144+
%0 = float_literal $Builtin.FPIEEE64, 0x47D2CED32A16A1B1 // 9.9999999999999997E+37
145+
%1 = builtin "fptrunc_FPIEEE64_FPIEEE32"(%0 : $Builtin.FPIEEE64) : $Builtin.FPIEEE32
146+
return %1 : $Builtin.FPIEEE32
147+
148+
// CHECK-LABEL: sil @foldDoubleToFloat
149+
// CHECK-NOT: float_literal $Builtin.FPIEEE64
150+
// CHECK-NOT: builtin
151+
// CHECK: [[RES:%.*]] = float_literal $Builtin.FPIEEE32, 0x7E967699 // 9.99999968E+37
152+
// CHECK-NEXT: return [[RES]] : $Builtin.FPIEEE32
153+
}
154+
155+
sil @foldDoubleToFloat2: $@convention(thin) () -> Builtin.FPIEEE32 {
156+
bb0:
157+
%0 = float_literal $Builtin.FPIEEE64, 0x3841039D428A8B8F // 1.0000000000000001E-37
158+
%1 = builtin "fptrunc_FPIEEE64_FPIEEE32"(%0 : $Builtin.FPIEEE64) : $Builtin.FPIEEE32
159+
return %1 : $Builtin.FPIEEE32
160+
161+
// CHECK-LABEL: sil @foldDoubleToFloat2
162+
// CHECK-NOT: float_literal $Builtin.FPIEEE64
163+
// CHECK-NOT: builtin
164+
// CHECK: [[RES:%.*]] = float_literal $Builtin.FPIEEE32, 0x2081CEA // 9.99999991E-38
165+
// CHECK-NEXT: return [[RES]] : $Builtin.FPIEEE32
166+
}

test/SILOptimizer/constant_propagation_floats_x86.sil

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,3 +54,44 @@ return %1 : $Builtin.Int64
5454
// CHECK: [[RES:%.*]] = integer_literal $Builtin.Int64, -1
5555
// CHECK-NEXT: return [[RES]] : $Builtin.Int64
5656
}
57+
58+
// The following tests check folding of FPTrunc instructions.
59+
60+
sil @foldFloat80ToFloat: $@convention(thin) () -> Builtin.FPIEEE32 {
61+
bb0:
62+
%0 = float_literal $Builtin.FPIEEE80, 0x407D96769950B50D88F4 // 9.99999999999999999993E+37
63+
%1 = builtin "fptrunc_FPIEEE80_FPIEEE32"(%0 : $Builtin.FPIEEE80) : $Builtin.FPIEEE32
64+
return %1 : $Builtin.FPIEEE32
65+
66+
// CHECK-LABEL: sil @foldFloat80ToFloat
67+
// CHECK-NOT: float_literal $Builtin.FPIEEE80
68+
// CHECK-NOT: builtin
69+
// CHECK: [[RES:%.*]] = float_literal $Builtin.FPIEEE32, 0x7E967699 // 9.99999968E+37
70+
// CHECK-NEXT: return [[RES]] : $Builtin.FPIEEE32
71+
}
72+
73+
sil @foldFloat80ToDouble: $@convention(thin) () -> Builtin.FPIEEE64 {
74+
bb0:
75+
%0 = float_literal $Builtin.FPIEEE80, 0x43FE8E679C2F5E44FF8F // 9.99999999999999999966E+307
76+
%1 = builtin "fptrunc_FPIEEE80_FPIEEE64"(%0 : $Builtin.FPIEEE80) : $Builtin.FPIEEE64
77+
return %1 : $Builtin.FPIEEE64
78+
79+
// CHECK-LABEL: sil @foldFloat80ToDouble
80+
// CHECK-NOT: float_literal $Builtin.FPIEEE80
81+
// CHECK-NOT: builtin
82+
// CHECK: [[RES:%.*]] = float_literal $Builtin.FPIEEE64, 0x7FE1CCF385EBC8A0 // 1.0E+308
83+
// CHECK-NEXT: return [[RES]] : $Builtin.FPIEEE64
84+
}
85+
86+
sil @foldFloat80ToFloat2: $@convention(thin) () -> Builtin.FPIEEE32 {
87+
bb0:
88+
%0 = float_literal $Builtin.FPIEEE80, 0x3F84881CEA14545C7575 // 9.9999999999999999995E-38
89+
%1 = builtin "fptrunc_FPIEEE80_FPIEEE32"(%0 : $Builtin.FPIEEE80) : $Builtin.FPIEEE32
90+
return %1 : $Builtin.FPIEEE32
91+
92+
// CHECK-LABEL: sil @foldFloat80ToFloat2
93+
// CHECK-NOT: float_literal $Builtin.FPIEEE80
94+
// CHECK-NOT: builtin
95+
// CHECK: [[RES:%.*]] = float_literal $Builtin.FPIEEE32, 0x2081CEA // 9.99999991E-38
96+
// CHECK-NEXT: return [[RES]] : $Builtin.FPIEEE32
97+
}

0 commit comments

Comments
 (0)