Skip to content

Commit 0b4c3f8

Browse files
committed
[Support][APInt] Fix sign extension, exponent and mantissa in APInt::roundToDouble
Conversion of an `APInt` to double via `APInt::roundToDouble` misses several edge cases that result in crashes due to an assertion or in erroneous values due to incorrect values for the exponent and mantissa of the generated double. The assertion is triggered when attempting to convert a multi-word `APInt` without active bits or with active bits only in the first word, e.g., `APInt(65, 0, true).roundToDouble(true)` or `APInt(65, 1, true).roundToDouble(true)` The issue is caused by passing the bit width as the argument to `SignExtend64`, exceeding the maximum expected bit width of 64. Incorrect values for the mantissa or exponent are calculated for any multi-word, unsigned value, e.g., `APInt(65, "18446744073709551616" /* 2^64 */, 10).roundToDouble(false)` This is due to `APInt::roundToDouble` expecting the exponent to correspond to the highest of the fractional part of the double rather than to the highest active bit and due to the missing code clearing the highest bit after generation of the mantissa. This patch solves the issues by simplifying the treatment of single-word integers and by adjusting the exponent and mantissa for multi-word integers. Tests are added for the edge cases and some regular cases.
1 parent a9a2d25 commit 0b4c3f8

2 files changed

Lines changed: 50 additions & 10 deletions

File tree

llvm/lib/Support/APInt.cpp

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -900,12 +900,9 @@ APInt llvm::APIntOps::RoundDoubleToAPInt(double Double, unsigned width) {
900900
double APInt::roundToDouble(bool isSigned) const {
901901
// Handle the simple case where the value is contained in one uint64_t.
902902
// It is wrong to optimize getWord(0) to VAL; there might be more than one word.
903-
if (isSingleWord() || getActiveBits() <= APINT_BITS_PER_WORD) {
904-
if (isSigned) {
905-
int64_t sext = SignExtend64(getWord(0), BitWidth);
906-
return double(sext);
907-
}
908-
return double(getWord(0));
903+
if (isSingleWord()) {
904+
return isSigned ? double(bit_cast<int64_t>(getWord(0)))
905+
: double(getWord(0));
909906
}
910907

911908
// Determine if the value is negative.
@@ -917,10 +914,15 @@ double APInt::roundToDouble(bool isSigned) const {
917914
// Figure out how many bits we're using.
918915
unsigned n = Tmp.getActiveBits();
919916

920-
// The exponent (without bias normalization) is just the number of bits
921-
// we are using. Note that the sign bit is gone since we constructed the
922-
// absolute value.
923-
uint64_t exp = n;
917+
// Early exit for 0 to avoid negative indexes
918+
if (n == 0)
919+
return 0.0;
920+
921+
// The exponent (without bias normalization) is just the number of
922+
// bits we are using (minus 1 to account for the fact that the
923+
// exponent is on 2). Note that the sign bit is gone since we
924+
// constructed the absolute value.
925+
uint64_t exp = n - 1;
924926

925927
// Return infinity for exponent overflow
926928
if (exp > 1023) {
@@ -947,6 +949,7 @@ double APInt::roundToDouble(bool isSigned) const {
947949
}
948950

949951
// The leading bit of mantissa is implicit, so get rid of it.
952+
mantissa &= ~(1ULL << std::min(n - 1, 51U));
950953
uint64_t sign = isNeg ? (1ULL << (APINT_BITS_PER_WORD - 1)) : 0;
951954
uint64_t I = sign | (exp << 52) | mantissa;
952955
return bit_cast<double>(I);

llvm/unittests/ADT/APIntTest.cpp

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3980,4 +3980,41 @@ TEST(APIntTest, clmulh) {
39803980
.getSExtValue(),
39813981
21845);
39823982
}
3983+
3984+
TEST(APIntTest, roundToDouble) {
3985+
// Single-word, positive
3986+
EXPECT_EQ(APInt(64, 0, false).roundToDouble(false), 0.0);
3987+
EXPECT_EQ(APInt(64, 1, false).roundToDouble(false), 1.0);
3988+
EXPECT_EQ(APInt(64, 2, false).roundToDouble(false), 2.0);
3989+
EXPECT_EQ(APInt(64, 1ULL << 63, false).roundToDouble(false),
3990+
9223372036854775808.0);
3991+
3992+
// Single-word, negative
3993+
EXPECT_EQ(APInt(64, 0, true).roundToDouble(true), 0.0);
3994+
EXPECT_EQ(APInt(64, -1, true).roundToDouble(true), -1.0);
3995+
EXPECT_EQ(APInt(64, -2, true).roundToDouble(true), -2.0);
3996+
EXPECT_EQ(APInt(64, 1ULL << 63, true).roundToDouble(true),
3997+
-9223372036854775808.0);
3998+
3999+
// Multi-word, positive, active bits in first word
4000+
EXPECT_EQ(APInt(65, 0, false).roundToDouble(false), 0.0);
4001+
EXPECT_EQ(APInt(65, 1, false).roundToDouble(false), 1.0);
4002+
EXPECT_EQ(APInt(65, 2, false).roundToDouble(false), 2.0);
4003+
EXPECT_EQ(APInt(65, 1ULL << 63, false).roundToDouble(true),
4004+
9223372036854775808.0);
4005+
4006+
// Multi-word, positive, active bits outside first word
4007+
EXPECT_EQ(
4008+
APInt(65, "18446744073709551616" /* 2^64 */, 10).roundToDouble(false),
4009+
18446744073709551616.0);
4010+
4011+
// Multi-word, negative
4012+
EXPECT_EQ(APInt(65, 0, true).roundToDouble(true), 0.0);
4013+
4014+
EXPECT_EQ(APInt(65, -1, true).roundToDouble(true), -1.0);
4015+
EXPECT_EQ(APInt(65, -2, true).roundToDouble(true), -2.0);
4016+
EXPECT_EQ(
4017+
APInt(65, "18446744073709551616" /* 2^64 */, 10).roundToDouble(true),
4018+
-18446744073709551616.0);
4019+
}
39834020
} // end anonymous namespace

0 commit comments

Comments
 (0)