tint/reader/wgsl: Error if a hex float is not exactly representable

Fixed: tint:1564

Change-Id: I3ba8d13055fd279868fcca9e7f8576a279b6902c
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/91429
Reviewed-by: David Neto <dneto@google.com>
Kokoro: Kokoro <noreply+kokoro@google.com>
Commit-Queue: Ben Clayton <bclayton@google.com>
diff --git a/src/tint/reader/wgsl/lexer.cc b/src/tint/reader/wgsl/lexer.cc
index bfec1d1..a97b162 100644
--- a/src/tint/reader/wgsl/lexer.cc
+++ b/src/tint/reader/wgsl/lexer.cc
@@ -17,6 +17,7 @@
 #include <cctype>
 #include <cmath>
 #include <cstring>
+#include <functional>
 #include <limits>
 #include <optional>  // NOLINT(build/include_order)
 #include <tuple>
@@ -658,18 +659,20 @@
     result_u64 |= mantissa;
     result_u64 |= (static_cast<uint64_t>(signed_exponent) & kExponentMask) << kExponentLeftShift;
 
-    // Reinterpret as float and return
+    // Reinterpret as f16 and return
     double result_f64;
     std::memcpy(&result_f64, &result_u64, 8);
 
     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)) {
+        // Check value fits in f32
+        if (result_f64 < static_cast<double>(f32::kLowest) ||
+            result_f64 > static_cast<double>(f32::kHighest)) {
             return {Token::Type::kError, source, "value cannot be represented as 'f32'"};
         }
+        // Check the value can be exactly represented (low 29 mantissa bits must be 0)
+        if (result_u64 & 0x1fffffff) {
+            return {Token::Type::kError, source, "value cannot be exactly represented as 'f32'"};
+        }
     }
 
     return {has_f_suffix ? Token::Type::kFloatLiteral_F : Token::Type::kFloatLiteral, source,
diff --git a/src/tint/reader/wgsl/parser_impl_const_literal_test.cc b/src/tint/reader/wgsl/parser_impl_const_literal_test.cc
index 8ce5178..b07ec1b 100644
--- a/src/tint/reader/wgsl/parser_impl_const_literal_test.cc
+++ b/src/tint/reader/wgsl/parser_impl_const_literal_test.cc
@@ -317,6 +317,14 @@
         {"0x1.55554p-130", 0x1.55554p-130},      // +Subnormal
         {"-0x1.55554p-130", -0x1.55554p-130},    // -Subnormal
 
+        // F32 exactly representable
+        {"0x1.000002p+0f", 0x1.000002p+0},
+        {"0x8.0000fp+0f", 0x8.0000fp+0},
+        {"0x8.fffffp+0f", 0x8.fffffp+0},
+        {"0x8.00003p+0f", 0x8.00003p+0},
+        {"0x2.123p+0f", 0x2.123p+0},
+        {"0x2.cafefp+0f", 0x2.cafefp+0},
+
         // Underflow -> Zero
         {"0x1p-1074", 0.0},  // Exponent underflows
         {"-0x1p-1074", 0.0},
@@ -327,14 +335,6 @@
         {"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
@@ -578,6 +578,17 @@
                      })));
 
 INSTANTIATE_TEST_SUITE_P(
+    HexNotExactlyRepresentableF32,
+    ParserImplInvalidLiteralTest,
+    testing::Combine(testing::Values("1:1: value cannot be exactly represented as 'f32'"),
+                     testing::ValuesIn(std::vector<const char*>{
+                         "0x1.000001p+0f",    // Quantizes to 0x1.0p+0
+                         "0x8.0000f8p+0f",    // Quantizes to 0x8.0000fp+0
+                         "0x8.000038p+0f",    // Quantizes to 0x8.00003p+0
+                         "0x2.cafef00dp+0f",  // Quantizes to 0x2.cafefp+0
+                     })));
+
+INSTANTIATE_TEST_SUITE_P(
     DecOverflowAFloat,
     ParserImplInvalidLiteralTest,
     testing::Combine(testing::Values("1:1: value cannot be represented as 'abstract-float'"),