tint/reader/wgsl: Lex abstract hex floats

Scale up the hex float parsing to support doubles.

Also make it an error if the hex float resolves to an inf or NaN.
See: https://github.com/gpuweb/gpuweb/issues/2953

Bug: tint:1504
Change-Id: Ie923bb5f660285fb7563cfe8dafa07d87b898a7e
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/91426
Kokoro: Kokoro <noreply+kokoro@google.com>
Commit-Queue: Ben Clayton <bclayton@chromium.org>
Reviewed-by: David Neto <dneto@google.com>
This commit is contained in:
Ben Clayton 2022-05-26 17:01:22 +00:00 committed by Dawn LUCI CQ
parent f45180a428
commit 30f01c1790
3 changed files with 269 additions and 160 deletions

View File

@ -387,17 +387,18 @@ Token Lexer::try_float() {
} }
Token Lexer::try_hex_float() { Token Lexer::try_hex_float() {
constexpr uint32_t kExponentBits = 8; constexpr uint64_t kExponentBits = 11;
constexpr uint32_t kMantissaBits = 23; constexpr uint64_t kMantissaBits = 52;
constexpr uint32_t kTotalBits = 1 + kExponentBits + kMantissaBits; constexpr uint64_t kTotalBits = 1 + kExponentBits + kMantissaBits;
constexpr uint32_t kTotalMsb = kTotalBits - 1; constexpr uint64_t kTotalMsb = kTotalBits - 1;
constexpr uint32_t kMantissaMsb = kMantissaBits - 1; constexpr uint64_t kMantissaMsb = kMantissaBits - 1;
constexpr uint32_t kMantissaShiftRight = kTotalBits - kMantissaBits; constexpr uint64_t kMantissaShiftRight = kTotalBits - kMantissaBits;
constexpr int32_t kExponentBias = 127; constexpr int64_t kExponentBias = 1023;
constexpr uint32_t kExponentMask = (1 << kExponentBits) - 1; constexpr uint64_t kExponentMask = (1 << kExponentBits) - 1;
constexpr int32_t kExponentMax = kExponentMask; // Including NaN / inf constexpr int64_t kExponentMax = kExponentMask; // Including NaN / inf
constexpr uint32_t kExponentLeftShift = kMantissaBits; constexpr uint64_t kExponentLeftShift = kMantissaBits;
constexpr uint32_t kSignBit = kTotalBits - 1; constexpr uint64_t kSignBit = kTotalBits - 1;
constexpr uint64_t kOne = 1;
auto start = pos(); auto start = pos();
auto end = pos(); auto end = pos();
@ -409,7 +410,7 @@ Token Lexer::try_hex_float() {
// clang-format on // clang-format on
// -? // -?
int32_t sign_bit = 0; int64_t sign_bit = 0;
if (matches(end, "-")) { if (matches(end, "-")) {
sign_bit = 1; sign_bit = 1;
end++; end++;
@ -421,8 +422,8 @@ Token Lexer::try_hex_float() {
return {}; return {};
} }
uint32_t mantissa = 0; uint64_t mantissa = 0;
uint32_t exponent = 0; uint64_t exponent = 0;
// TODO(dneto): Values in the normal range for the format do not explicitly // TODO(dneto): Values in the normal range for the format do not explicitly
// store the most significant bit. The algorithm here works hard to eliminate // store the most significant bit. The algorithm here works hard to eliminate
@ -435,7 +436,7 @@ Token Lexer::try_hex_float() {
// `set_next_mantissa_bit_to` sets next `mantissa` bit starting from msb to // `set_next_mantissa_bit_to` sets next `mantissa` bit starting from msb to
// lsb to value 1 if `set` is true, 0 otherwise. Returns true on success, i.e. // lsb to value 1 if `set` is true, 0 otherwise. Returns true on success, i.e.
// when the bit can be accommodated in the available space. // when the bit can be accommodated in the available space.
uint32_t mantissa_next_bit = kTotalMsb; uint64_t mantissa_next_bit = kTotalMsb;
auto set_next_mantissa_bit_to = [&](bool set, bool integer_part) -> bool { auto set_next_mantissa_bit_to = [&](bool set, bool integer_part) -> bool {
// If adding bits for the integer part, we can overflow whether we set the // If adding bits for the integer part, we can overflow whether we set the
// bit or not. For the fractional part, we can only overflow when setting // bit or not. For the fractional part, we can only overflow when setting
@ -447,7 +448,7 @@ Token Lexer::try_hex_float() {
return false; // Overflowed mantissa return false; // Overflowed mantissa
} }
if (set) { if (set) {
mantissa |= (1 << mantissa_next_bit); mantissa |= (kOne << mantissa_next_bit);
} }
--mantissa_next_bit; --mantissa_next_bit;
return true; return true;
@ -503,7 +504,7 @@ Token Lexer::try_hex_float() {
has_zero_integer = false; has_zero_integer = false;
} }
for (int32_t bit = 3; bit >= 0; --bit) { for (int bit = 3; bit >= 0; --bit) {
auto v = 1 & (nibble >> bit); auto v = 1 & (nibble >> bit);
// Skip leading 0s and the first 1 // Skip leading 0s and the first 1
@ -524,7 +525,7 @@ Token Lexer::try_hex_float() {
// [0-9a-fA-F]* // [0-9a-fA-F]*
for (auto i = fractional_range.first; i < fractional_range.second; ++i) { for (auto i = fractional_range.first; i < fractional_range.second; ++i) {
auto nibble = hex_value(at(i)); auto nibble = hex_value(at(i));
for (int32_t bit = 3; bit >= 0; --bit) { for (int bit = 3; bit >= 0; --bit) {
auto v = 1 & (nibble >> bit); auto v = 1 & (nibble >> bit);
if (v == 1) { if (v == 1) {
@ -552,8 +553,8 @@ Token Lexer::try_hex_float() {
// Parse the optional exponent. // Parse the optional exponent.
// ((p|P)(\+|-)?[0-9]+)? // ((p|P)(\+|-)?[0-9]+)?
uint32_t input_exponent = 0; // Defaults to 0 if not present uint64_t input_exponent = 0; // Defaults to 0 if not present
int32_t exponent_sign = 1; int64_t exponent_sign = 1;
// If the 'p' part is present, the rest of the exponent must exist. // If the 'p' part is present, the rest of the exponent must exist.
bool has_f_suffix = false; bool has_f_suffix = false;
if (has_exponent) { if (has_exponent) {
@ -568,7 +569,7 @@ Token Lexer::try_hex_float() {
// Parse exponent from input // Parse exponent from input
// [0-9]+ // [0-9]+
// Allow overflow (in uint32_t) when the floating point value magnitude is // Allow overflow (in uint64_t) when the floating point value magnitude is
// zero. // zero.
bool has_exponent_digits = false; bool has_exponent_digits = false;
while (end < length() && isdigit(at(end))) { while (end < length() && isdigit(at(end))) {
@ -605,14 +606,14 @@ Token Lexer::try_hex_float() {
} else { } else {
// Ensure input exponent is not too large; i.e. that it won't overflow when // Ensure input exponent is not too large; i.e. that it won't overflow when
// adding the exponent bias. // adding the exponent bias.
const uint32_t kIntMax = static_cast<uint32_t>(std::numeric_limits<int32_t>::max()); const uint64_t kIntMax = static_cast<uint64_t>(std::numeric_limits<int64_t>::max());
const uint32_t kMaxInputExponent = kIntMax - kExponentBias; const uint64_t kMaxInputExponent = kIntMax - kExponentBias;
if (input_exponent > kMaxInputExponent) { if (input_exponent > kMaxInputExponent) {
return {Token::Type::kError, source, "exponent is too large for hex float"}; return {Token::Type::kError, source, "exponent is too large for hex float"};
} }
// Compute exponent so far // Compute exponent so far
exponent += static_cast<uint32_t>(static_cast<int32_t>(input_exponent) * exponent_sign); exponent += static_cast<uint64_t>(static_cast<int64_t>(input_exponent) * exponent_sign);
// Bias exponent if non-zero // Bias exponent if non-zero
// After this, if exponent is <= 0, our value is a denormal // After this, if exponent is <= 0, our value is a denormal
@ -631,7 +632,7 @@ Token Lexer::try_hex_float() {
// We can now safely work with exponent as a signed quantity, as there's no // We can now safely work with exponent as a signed quantity, as there's no
// chance to overflow // chance to overflow
int32_t signed_exponent = static_cast<int32_t>(exponent); int64_t signed_exponent = static_cast<int64_t>(exponent);
// Shift mantissa to occupy the low 23 bits // Shift mantissa to occupy the low 23 bits
mantissa >>= kMantissaShiftRight; mantissa >>= kMantissaShiftRight;
@ -642,7 +643,7 @@ Token Lexer::try_hex_float() {
// then shift the mantissa to make exponent zero. // then shift the mantissa to make exponent zero.
if (signed_exponent <= 0) { if (signed_exponent <= 0) {
mantissa >>= 1; mantissa >>= 1;
mantissa |= (1 << kMantissaMsb); mantissa |= (kOne << kMantissaMsb);
} }
while (signed_exponent < 0) { while (signed_exponent < 0) {
@ -656,24 +657,30 @@ Token Lexer::try_hex_float() {
} }
} }
if (signed_exponent > kExponentMax) { if (signed_exponent >= kExponentMax || (signed_exponent == kExponentMax && mantissa != 0)) {
// Overflow: set to infinity std::string type = has_f_suffix ? "f32" : "abstract-float";
signed_exponent = kExponentMax; return {Token::Type::kError, source, "value cannot be represented as '" + type + "'"};
mantissa = 0;
} else if (signed_exponent == kExponentMax && mantissa != 0) {
// NaN: set to infinity
mantissa = 0;
} }
// Combine sign, mantissa, and exponent // Combine sign, mantissa, and exponent
uint32_t result_u32 = sign_bit << kSignBit; uint64_t result_u64 = sign_bit << kSignBit;
result_u32 |= mantissa; result_u64 |= mantissa;
result_u32 |= (static_cast<uint32_t>(signed_exponent) & kExponentMask) << kExponentLeftShift; result_u64 |= (static_cast<uint64_t>(signed_exponent) & kExponentMask) << kExponentLeftShift;
// Reinterpret as float and return // Reinterpret as float and return
float result_f32; double result_f64;
std::memcpy(&result_f32, &result_u32, sizeof(result_f32)); std::memcpy(&result_f64, &result_u64, 8);
double result_f64 = static_cast<double>(result_f32);
if (has_f_suffix) {
// Quantize to f32
// TODO(crbug.com/tint/1564): If the hex-float value is not exactly representable then we
// should be erroring here.
result_f64 = static_cast<double>(static_cast<float>(result_f64));
if (std::isinf(result_f64)) {
return {Token::Type::kError, source, "value cannot be represented as 'f32'"};
}
}
return {has_f_suffix ? Token::Type::kFloatLiteral_F : Token::Type::kFloatLiteral, source, return {has_f_suffix ? Token::Type::kFloatLiteral_F : Token::Type::kFloatLiteral, source,
result_f64}; result_f64};
} }

View File

@ -224,8 +224,12 @@ TEST_P(ParserImplFloatLiteralTest, Parse) {
EXPECT_FALSE(c.errored); EXPECT_FALSE(c.errored);
EXPECT_FALSE(p->has_error()) << p->error(); EXPECT_FALSE(p->has_error()) << p->error();
ASSERT_NE(c.value, nullptr); ASSERT_NE(c.value, nullptr);
ASSERT_TRUE(c->Is<ast::FloatLiteralExpression>()); auto* literal = c->As<ast::FloatLiteralExpression>();
EXPECT_DOUBLE_EQ(c->As<ast::FloatLiteralExpression>()->value, params.expected); ASSERT_NE(literal, nullptr);
EXPECT_DOUBLE_EQ(literal->value, params.expected)
<< "\n"
<< "got: " << std::hexfloat << literal->value << "\n"
<< "expected: " << std::hexfloat << params.expected;
if (params.input.back() == 'f') { if (params.input.back() == 'f') {
EXPECT_EQ(c->As<ast::FloatLiteralExpression>()->suffix, EXPECT_EQ(c->As<ast::FloatLiteralExpression>()->suffix,
ast::FloatLiteralExpression::Suffix::kF); ast::FloatLiteralExpression::Suffix::kF);
@ -290,27 +294,60 @@ FloatLiteralTestCaseList HexFloatCases() {
{"-0x1.02p-3", 1.0 / -1024.0 - 1.0 / 8.0}, {"-0x1.02p-3", 1.0 / -1024.0 - 1.0 / 8.0},
// Near lowest non-denorm // Near lowest non-denorm
{"0x1p-124", std::ldexp(1.0 * 8.0, -127)}, {"0x1p-1020", std::ldexp(1.0 * 8.0, -1023)},
{"0x1p-125", std::ldexp(1.0 * 4.0, -127)}, {"0x1p-1021", std::ldexp(1.0 * 4.0, -1023)},
{"-0x1p-124", -std::ldexp(1.0 * 8.0, -127)}, {"-0x1p-1020", -std::ldexp(1.0 * 8.0, -1023)},
{"-0x1p-125", -std::ldexp(1.0 * 4.0, -127)}, {"-0x1p-1021", -std::ldexp(1.0 * 4.0, -1023)},
{"0x1p-124f", std::ldexp(1.0 * 8.0, -127)},
{"0x1p-125f", std::ldexp(1.0 * 4.0, -127)},
{"-0x1p-124f", -std::ldexp(1.0 * 8.0, -127)},
{"-0x1p-125f", -std::ldexp(1.0 * 4.0, -127)},
// Lowest non-denorm // Lowest non-denorm
{"0x1p-126", std::ldexp(1.0 * 2.0, -127)}, {"0x1p-1022", std::ldexp(1.0 * 2.0, -1023)},
{"-0x1p-126", -std::ldexp(1.0 * 2.0, -127)}, {"-0x1p-1022", -std::ldexp(1.0 * 2.0, -1023)},
{"0x1p-126f", std::ldexp(1.0 * 2.0, -127)},
{"-0x1p-126f", -std::ldexp(1.0 * 2.0, -127)},
// Denormalized values // Denormalized values
{"0x1p-127", std::ldexp(1.0, -127)}, {"0x1p-1023", std::ldexp(1.0, -1023)},
{"0x1p-128", std::ldexp(1.0 / 2.0, -127)}, {"0x1p-1024", std::ldexp(1.0 / 2.0, -1023)},
{"0x1p-129", std::ldexp(1.0 / 4.0, -127)}, {"0x1p-1025", std::ldexp(1.0 / 4.0, -1023)},
{"0x1p-130", std::ldexp(1.0 / 8.0, -127)}, {"0x1p-1026", std::ldexp(1.0 / 8.0, -1023)},
{"-0x1p-127", -std::ldexp(1.0, -127)}, {"-0x1p-1023", -std::ldexp(1.0, -1023)},
{"-0x1p-128", -std::ldexp(1.0 / 2.0, -127)}, {"-0x1p-1024", -std::ldexp(1.0 / 2.0, -1023)},
{"-0x1p-129", -std::ldexp(1.0 / 4.0, -127)}, {"-0x1p-1025", -std::ldexp(1.0 / 4.0, -1023)},
{"-0x1p-130", -std::ldexp(1.0 / 8.0, -127)}, {"-0x1p-1026", -std::ldexp(1.0 / 8.0, -1023)},
{"0x1.8p-1023", std::ldexp(1.0, -1023) + (std::ldexp(1.0, -1023) / 2.0)},
{"0x1.8p-1024", std::ldexp(1.0, -1023) / 2.0 + (std::ldexp(1.0, -1023) / 4.0)},
{"0x1.8p-127", std::ldexp(1.0, -127) + (std::ldexp(1.0, -127) / 2.0)}, {"0x1p-127f", std::ldexp(1.0, -127)},
{"0x1.8p-128", std::ldexp(1.0, -127) / 2.0 + (std::ldexp(1.0, -127) / 4.0)}, {"0x1p-128f", std::ldexp(1.0 / 2.0, -127)},
{"0x1p-129f", std::ldexp(1.0 / 4.0, -127)},
{"0x1p-130f", std::ldexp(1.0 / 8.0, -127)},
{"-0x1p-127f", -std::ldexp(1.0, -127)},
{"-0x1p-128f", -std::ldexp(1.0 / 2.0, -127)},
{"-0x1p-129f", -std::ldexp(1.0 / 4.0, -127)},
{"-0x1p-130f", -std::ldexp(1.0 / 8.0, -127)},
{"0x1.8p-127f", std::ldexp(1.0, -127) + (std::ldexp(1.0, -127) / 2.0)},
{"0x1.8p-128f", std::ldexp(1.0, -127) / 2.0 + (std::ldexp(1.0, -127) / 4.0)},
// F64 extremities
{"0x1p-1074", MakeDouble(0, 0, 1)}, // +SmallestDenormal
{"0x1p-1073", MakeDouble(0, 0, 2)}, // +BiggerDenormal
{"0x1.ffffffffffffp-1027", MakeDouble(0, 0, 0xffffffffffff)}, // +LargestDenormal
{"-0x1p-1074", MakeDouble(1, 0, 1)}, // -SmallestDenormal
{"-0x1p-1073", MakeDouble(1, 0, 2)}, // -BiggerDenormal
{"-0x1.ffffffffffffp-1027", MakeDouble(1, 0, 0xffffffffffff)}, // -LargestDenormal
{"0x0.cafebeeff000dp-1022", MakeDouble(0, 0, 0xcafebeeff000d)}, // +Subnormal
{"-0x0.cafebeeff000dp-1022", MakeDouble(1, 0, 0xcafebeeff000d)}, // -Subnormal
{"0x1.2bfaf8p-1052", MakeDouble(0, 0, 0x4afebe)}, // +Subnormal
{"-0x1.2bfaf8p-1052", MakeDouble(1, 0, 0x4afebe)}, // +Subnormal
{"0x1.55554p-1055", MakeDouble(0, 0, 0xaaaaa)}, // +Subnormal
{"-0x1.55554p-1055", MakeDouble(1, 0, 0xaaaaa)}, // -Subnormal
// F32 extremities // F32 extremities
{"0x1p-149", static_cast<double>(MakeFloat(0, 0, 1))}, // +SmallestDenormal {"0x1p-149", static_cast<double>(MakeFloat(0, 0, 1))}, // +SmallestDenormal
@ -320,46 +357,32 @@ FloatLiteralTestCaseList HexFloatCases() {
{"-0x1p-148", static_cast<double>(MakeFloat(1, 0, 2))}, // -BiggerDenormal {"-0x1p-148", static_cast<double>(MakeFloat(1, 0, 2))}, // -BiggerDenormal
{"-0x1.fffffcp-127", static_cast<double>(MakeFloat(1, 0, 0x7fffff))}, // -LargestDenormal {"-0x1.fffffcp-127", static_cast<double>(MakeFloat(1, 0, 0x7fffff))}, // -LargestDenormal
{"0x1.2bfaf8p-127", static_cast<double>(MakeFloat(0, 0, 0xcafebe))}, // +Subnormal {"0x0.cafebp-129", static_cast<double>(MakeFloat(0, 0, 0xcafeb))}, // +Subnormal
{"-0x1.2bfaf8p-127", static_cast<double>(MakeFloat(1, 0, 0xcafebe))}, // -Subnormal {"-0x0.cafebp-129", static_cast<double>(MakeFloat(1, 0, 0xcafeb))}, // -Subnormal
{"0x1.2bfaf8p-127", static_cast<double>(MakeFloat(0, 0, 0x4afebe))}, // +Subnormal
{"-0x1.2bfaf8p-127", static_cast<double>(MakeFloat(1, 0, 0x4afebe))}, // -Subnormal
{"0x1.55554p-130", static_cast<double>(MakeFloat(0, 0, 0xaaaaa))}, // +Subnormal {"0x1.55554p-130", static_cast<double>(MakeFloat(0, 0, 0xaaaaa))}, // +Subnormal
{"-0x1.55554p-130", static_cast<double>(MakeFloat(1, 0, 0xaaaaa))}, // -Subnormal {"-0x1.55554p-130", static_cast<double>(MakeFloat(1, 0, 0xaaaaa))}, // -Subnormal
// Nan -> Infinity
{"0x1.8p+128", PosInf},
{"0x1.0002p+128", PosInf},
{"0x1.0018p+128", PosInf},
{"0x1.01ep+128", PosInf},
{"0x1.fffffep+128", PosInf},
{"-0x1.8p+128", NegInf},
{"-0x1.0002p+128", NegInf},
{"-0x1.0018p+128", NegInf},
{"-0x1.01ep+128", NegInf},
{"-0x1.fffffep+128", NegInf},
// Infinity
{"0x1p+128", PosInf},
{"-0x1p+128", NegInf},
{"0x32p+127", PosInf},
{"0x32p+500", PosInf},
{"-0x32p+127", NegInf},
{"-0x32p+500", NegInf},
// Overflow -> Infinity
{"0x1p+129", PosInf},
{"0x1.1p+128", PosInf},
{"-0x1p+129", NegInf},
{"-0x1.1p+128", NegInf},
{"0x1.0p2147483520", PosInf}, // INT_MAX - 127 (largest valid exponent)
// Underflow -> Zero // Underflow -> Zero
{"0x1p-500", 0.0}, // Exponent underflows {"0x1p-1074", 0.0}, // Exponent underflows
{"-0x1p-500", -0.0}, {"-0x1p-1074", 0.0},
{"0x0.00000000001p-126", 0.0}, // Fraction causes underflow {"0x1p-5000", 0.0},
{"-0x0.0000000001p-127", -0.0}, {"-0x1p-5000", 0.0},
{"0x0.01p-142", 0.0}, {"0x0.00000000000000000000001p-1022", 0.0}, // Fraction causes underflow
{"-0x0.01p-142", -0.0}, // Fraction causes additional underflow {"-0x0.0000000000000000000001p-1023", -0.0},
{"0x1.0p-2147483520", 0}, // -(INT_MAX - 127) (smallest valid exponent) {"0x0.01p-1073", -0.0},
{"-0x0.01p-1073", -0.0}, // Fraction causes additional underflow
{"0x1p-150f", 0.0}, // Exponent underflows
{"-0x1p-150f", 0.0},
{"0x1p-500f", 0.0},
{"-0x1p-500f", -0.0},
{"0x0.00000000001p-126f", 0.0}, // Fraction causes underflow
{"-0x0.0000000001p-127f", -0.0},
{"0x0.01p-142f", 0.0},
{"-0x0.01p-142f", -0.0}, // Fraction causes additional underflow
{"0x1.0p-9223372036854774784", 0}, // -(INT64_MAX - 1023) (smallest valid exponent)
// Zero with non-zero exponent -> Zero // Zero with non-zero exponent -> Zero
{"0x0p+0", 0.0}, {"0x0p+0", 0.0},
@ -369,22 +392,22 @@ FloatLiteralTestCaseList HexFloatCases() {
{"0x0p-9999999999", 0.0}, {"0x0p-9999999999", 0.0},
// Same, but with very large positive exponents that would cause overflow // Same, but with very large positive exponents that would cause overflow
// if the mantissa were non-zero. // if the mantissa were non-zero.
{"0x0p+4000000000", 0.0}, // 4 billion: {"0x0p+10000000000000000000", 0.0}, // 10 quintillion (10,000,000,000,000,000,000)
{"0x0p+40000000000", 0.0}, // 40 billion {"0x0p+100000000000000000000", 0.0}, // 100 quintillion (100,000,000,000,000,000,000)
{"-0x0p+40000000000", 0.0}, // As above 2, but negative mantissa {"-0x0p+100000000000000000000", 0.0}, // As above 2, but negative mantissa
{"-0x0p+400000000000", 0.0}, {"-0x0p+1000000000000000000000", 0.0},
{"0x0.00p+4000000000", 0.0}, // As above 4, but with fractional part {"0x0.00p+10000000000000000000", 0.0}, // As above 4, but with fractional part
{"0x0.00p+40000000000", 0.0}, {"0x0.00p+100000000000000000000", 0.0},
{"-0x0.00p+40000000000", 0.0}, {"-0x0.00p+100000000000000000000", 0.0},
{"-0x0.00p+400000000000", 0.0}, {"-0x0.00p+1000000000000000000000", 0.0},
{"0x0p-4000000000", 0.0}, // As above 8, but with negative exponents {"0x0p-10000000000000000000", 0.0}, // As above 8, but with negative exponents
{"0x0p-40000000000", 0.0}, {"0x0p-100000000000000000000", 0.0},
{"-0x0p-40000000000", 0.0}, {"-0x0p-100000000000000000000", 0.0},
{"-0x0p-400000000000", 0.0}, {"-0x0p-1000000000000000000000", 0.0},
{"0x0.00p-4000000000", 0.0}, {"0x0.00p-10000000000000000000", 0.0},
{"0x0.00p-40000000000", 0.0}, {"0x0.00p-100000000000000000000", 0.0},
{"-0x0.00p-40000000000", 0.0}, {"-0x0.00p-100000000000000000000", 0.0},
{"-0x0.00p-400000000000", 0.0}, {"-0x0.00p-1000000000000000000000", 0.0},
// Test parsing // Test parsing
{"0x0p0", 0.0}, {"0x0p0", 0.0},
@ -462,65 +485,144 @@ INSTANTIATE_TEST_SUITE_P(ParserImplFloatLiteralTest_HexFloat_UpperCase0X,
ParserImplFloatLiteralTest, ParserImplFloatLiteralTest,
testing::ValuesIn(UpperCase0X(HexFloatCases()))); testing::ValuesIn(UpperCase0X(HexFloatCases())));
struct InvalidLiteralTestCase { // <error, source>
const char* input; using InvalidLiteralTestCase = std::tuple<const char*, const char*>;
const char* error_msg;
};
class ParserImplInvalidLiteralTest : public ParserImplTestWithParam<InvalidLiteralTestCase> {}; class ParserImplInvalidLiteralTest : public ParserImplTestWithParam<InvalidLiteralTestCase> {};
TEST_P(ParserImplInvalidLiteralTest, Parse) { TEST_P(ParserImplInvalidLiteralTest, Parse) {
auto params = GetParam(); auto* error = std::get<0>(GetParam());
SCOPED_TRACE(params.input); auto* source = std::get<1>(GetParam());
auto p = parser(params.input); auto p = parser(source);
auto c = p->const_literal(); auto c = p->const_literal();
EXPECT_FALSE(c.matched); EXPECT_FALSE(c.matched);
EXPECT_TRUE(c.errored); EXPECT_TRUE(c.errored);
EXPECT_EQ(p->error(), params.error_msg); EXPECT_EQ(p->error(), std::string(error));
ASSERT_EQ(c.value, nullptr); ASSERT_EQ(c.value, nullptr);
} }
InvalidLiteralTestCase invalid_hexfloat_mantissa_too_large_cases[] = { INSTANTIATE_TEST_SUITE_P(
{"0x1.ffffffff8p0", "1:1: mantissa is too large for hex float"}, HexFloatMantissaTooLarge,
{"0x1f.fffffff8p0", "1:1: mantissa is too large for hex float"}, ParserImplInvalidLiteralTest,
{"0x1ff.ffffff8p0", "1:1: mantissa is too large for hex float"}, testing::Combine(testing::Values("1:1: mantissa is too large for hex float"),
{"0x1fff.fffff8p0", "1:1: mantissa is too large for hex float"}, testing::ValuesIn(std::vector<const char*>{
{"0x1ffff.ffff8p0", "1:1: mantissa is too large for hex float"}, "0x1.ffffffffffffffff8p0",
{"0x1fffff.fff8p0", "1:1: mantissa is too large for hex float"}, "0x1f.fffffffffffffff8p0",
{"0x1ffffff.ff8p0", "1:1: mantissa is too large for hex float"}, "0x1ff.ffffffffffffff8p0",
{"0x1fffffff.f8p0", "1:1: mantissa is too large for hex float"}, "0x1fff.fffffffffffff8p0",
{"0x1ffffffff.8p0", "1:1: mantissa is too large for hex float"}, "0x1ffff.ffffffffffff8p0",
{"0x1ffffffff8.p0", "1:1: mantissa is too large for hex float"}, "0x1fffff.fffffffffff8p0",
}; "0x1ffffff.ffffffffff8p0",
INSTANTIATE_TEST_SUITE_P(ParserImplInvalidLiteralTest_HexFloatMantissaTooLarge, "0x1fffffff.fffffffff8p0",
ParserImplInvalidLiteralTest, "0x1ffffffff.ffffffff8p0",
testing::ValuesIn(invalid_hexfloat_mantissa_too_large_cases)); "0x1fffffffff.fffffff8p0",
"0x1ffffffffff.ffffff8p0",
"0x1fffffffffff.fffff8p0",
"0x1ffffffffffff.ffff8p0",
"0x1fffffffffffff.fff8p0",
"0x1ffffffffffffff.ff8p0",
"0x1ffffffffffffffff.8p0",
"0x1ffffffffffffffff8.p0",
})));
InvalidLiteralTestCase invalid_hexfloat_exponent_too_large_cases[] = { INSTANTIATE_TEST_SUITE_P(
{"0x1p+2147483521", "1:1: exponent is too large for hex float"}, HexFloatExponentTooLarge,
{"0x1p-2147483521", "1:1: exponent is too large for hex float"}, ParserImplInvalidLiteralTest,
{"0x1p+4294967296", "1:1: exponent is too large for hex float"}, testing::Combine(testing::Values("1:1: exponent is too large for hex float"),
{"0x1p-4294967296", "1:1: exponent is too large for hex float"}, testing::ValuesIn(std::vector<const char*>{
}; "0x1p+9223372036854774785",
INSTANTIATE_TEST_SUITE_P(ParserImplInvalidLiteralTest_HexFloatExponentTooLarge, "0x1p-9223372036854774785",
ParserImplInvalidLiteralTest, "0x1p+18446744073709551616",
testing::ValuesIn(invalid_hexfloat_exponent_too_large_cases)); "0x1p-18446744073709551616",
})));
InvalidLiteralTestCase invalid_hexfloat_exponent_missing_cases[] = { INSTANTIATE_TEST_SUITE_P(
// Lower case p HexFloatMissingExponent,
{"0x0p", "1:1: expected an exponent value for hex float"}, ParserImplInvalidLiteralTest,
{"0x0p+", "1:1: expected an exponent value for hex float"}, testing::Combine(testing::Values("1:1: expected an exponent value for hex float"),
{"0x0p-", "1:1: expected an exponent value for hex float"}, testing::ValuesIn(std::vector<const char*>{
{"0x1.0p", "1:1: expected an exponent value for hex float"}, // Lower case p
{"0x0.1p", "1:1: expected an exponent value for hex float"}, "0x0p",
// Upper case p "0x0p+",
{"0x0P", "1:1: expected an exponent value for hex float"}, "0x0p-",
{"0x0P+", "1:1: expected an exponent value for hex float"}, "0x1.0p",
{"0x0P-", "1:1: expected an exponent value for hex float"}, "0x0.1p",
{"0x1.0P", "1:1: expected an exponent value for hex float"}, // Upper case p
{"0x0.1P", "1:1: expected an exponent value for hex float"}, "0x0P",
}; "0x0P+",
INSTANTIATE_TEST_SUITE_P(ParserImplInvalidLiteralTest_HexFloatExponentMissing, "0x0P-",
ParserImplInvalidLiteralTest, "0x1.0P",
testing::ValuesIn(invalid_hexfloat_exponent_missing_cases)); "0x0.1P",
})));
INSTANTIATE_TEST_SUITE_P(
NaNAFloat,
ParserImplInvalidLiteralTest,
testing::Combine(testing::Values("1:1: value cannot be represented as 'abstract-float'"),
testing::ValuesIn(std::vector<const char*>{
"0x1.8p+1024",
"0x1.0002p+1024",
"0x1.0018p+1024",
"0x1.01ep+1024",
"0x1.fffffep+1024",
"-0x1.8p+1024",
"-0x1.0002p+1024",
"-0x1.0018p+1024",
"-0x1.01ep+1024",
"-0x1.fffffep+1024",
})));
INSTANTIATE_TEST_SUITE_P(
NaNF32,
ParserImplInvalidLiteralTest,
testing::Combine(testing::Values("1:1: value cannot be represented as 'f32'"),
testing::ValuesIn(std::vector<const char*>{
"0x1.8p+128f",
"0x1.0002p+128f",
"0x1.0018p+128f",
"0x1.01ep+128f",
"0x1.fffffep+128f",
"-0x1.8p+128f",
"-0x1.0002p+128f",
"-0x1.0018p+128f",
"-0x1.01ep+128f",
"-0x1.fffffep+128f",
})));
INSTANTIATE_TEST_SUITE_P(
OverflowAFloat,
ParserImplInvalidLiteralTest,
testing::Combine(testing::Values("1:1: value cannot be represented as 'abstract-float'"),
testing::ValuesIn(std::vector<const char*>{
"0x1p+1024",
"-0x1p+1024",
"0x1.1p+1024",
"-0x1.1p+1024",
"0x1p+1025",
"-0x1p+1025",
"0x32p+1023",
"-0x32p+1023",
"0x32p+5000",
"-0x32p+5000",
"0x1.0p9223372036854774784",
"-0x1.0p9223372036854774784",
})));
INSTANTIATE_TEST_SUITE_P(
OverflowF32,
ParserImplInvalidLiteralTest,
testing::Combine(testing::Values("1:1: value cannot be represented as 'f32'"),
testing::ValuesIn(std::vector<const char*>{
"0x1p+128f",
"-0x1p+128f",
"0x1.1p+128f",
"-0x1.1p+128f",
"0x1p+129f",
"-0x1p+129f",
"0x32p+127f",
"-0x32p+127f",
"0x32p+500f",
"-0x32p+500f",
})));
TEST_F(ParserImplTest, ConstLiteral_FloatHighest) { TEST_F(ParserImplTest, ConstLiteral_FloatHighest) {
const auto highest = std::numeric_limits<float>::max(); const auto highest = std::numeric_limits<float>::max();

View File

@ -82,7 +82,7 @@ foobar
TEST_F(ParserImplTest, HandlesBadToken_InMiddle) { TEST_F(ParserImplTest, HandlesBadToken_InMiddle) {
auto p = parser(R"( auto p = parser(R"(
fn main() { fn main() {
let f = 0x1p500000000000; // Exponent too big for hex float let f = 0x1p10000000000000000000; // Exponent too big for hex float
return; return;
})"); })");
@ -96,7 +96,7 @@ TEST_F(ParserImplTest, HandlesBadToken_AtModuleScope) {
fn main() { fn main() {
return; return;
} }
0x1p5000000000000 0x1p10000000000000000000
)"); )");
ASSERT_FALSE(p->Parse()); ASSERT_FALSE(p->Parse());