This is an automated email from the ASF dual-hosted git repository.

ruihangl pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/tvm.git


The following commit(s) were added to refs/heads/main by this push:
     new 537923a717 [FFI] Make JSON Parser/Write fastmath safe (#18212)
537923a717 is described below

commit 537923a71766db9e79b7d5bc1bdbb49263c3f3f8
Author: Tianqi Chen <[email protected]>
AuthorDate: Fri Aug 15 10:41:25 2025 -0400

    [FFI] Make JSON Parser/Write fastmath safe (#18212)
    
    This PR adds fallbacks for nan and inf
    detection/creation under fastmath mode.
---
 ffi/src/ffi/extra/json_parser.cc        | 33 ++++++++++++++++++++++++++---
 ffi/src/ffi/extra/json_writer.cc        | 35 +++++++++++++++++++++++++++++--
 ffi/tests/cpp/extra/test_json_parser.cc | 37 ++++++++++++++++++++++++++++++---
 3 files changed, 97 insertions(+), 8 deletions(-)

diff --git a/ffi/src/ffi/extra/json_parser.cc b/ffi/src/ffi/extra/json_parser.cc
index dd3fae351d..3107e4ddf1 100644
--- a/ffi/src/ffi/extra/json_parser.cc
+++ b/ffi/src/ffi/extra/json_parser.cc
@@ -167,7 +167,7 @@ class JSONParserContext {
       ++cur_;
       if (cur_ != end_ && *cur_ == 'I') {
         if (this->MatchLiteral("Infinity", 8)) {
-          *out = -std::numeric_limits<double>::infinity();
+          *out = FastMathSafeNegInf();
           return true;
         } else {
           this->SetCurrentPosForBetterErrorMsg(start_pos);
@@ -177,7 +177,7 @@ class JSONParserContext {
       }
     } else if (*cur_ == 'I') {
       if (this->MatchLiteral("Infinity", 8)) {
-        *out = std::numeric_limits<double>::infinity();
+        *out = FastMathSafePosInf();
         return true;
       } else {
         this->SetCurrentPosForBetterErrorMsg(start_pos);
@@ -186,7 +186,7 @@ class JSONParserContext {
       }
     } else if (*cur_ == 'N') {
       if (this->MatchLiteral("NaN", 3)) {
-        *out = std::numeric_limits<double>::quiet_NaN();
+        *out = FastMathSafeNaN();
         return true;
       } else {
         this->SetCurrentPosForBetterErrorMsg(start_pos);
@@ -296,6 +296,33 @@ class JSONParserContext {
   void SetErrorExpectingComma() { error_msg_ = 
GetSyntaxErrorContext("Expecting \',\' delimiter"); }
 
  private:
+  static double FastMathSafePosInf() {
+#ifdef __FAST_MATH__
+    const uint64_t inf_bits = 0x7FF0000000000000ULL;
+    return *reinterpret_cast<const double*>(&inf_bits);
+#else
+    return std::numeric_limits<double>::infinity();
+#endif
+  }
+
+  static double FastMathSafeNegInf() {
+#ifdef __FAST_MATH__
+    const uint64_t inf_bits = 0xFFF0000000000000ULL;
+    return *reinterpret_cast<const double*>(&inf_bits);
+#else
+    return -std::numeric_limits<double>::infinity();
+#endif
+  }
+
+  static double FastMathSafeNaN() {
+#ifdef __FAST_MATH__
+    const uint64_t nan_bits = 0x7FF8000000000000ULL;
+    return *reinterpret_cast<const double*>(&nan_bits);
+#else
+    return std::numeric_limits<double>::quiet_NaN();
+#endif
+  }
+
   // Full string parsing with escape and unicode handling
   bool NextStringWithFullHandling(Any* out, const char* start_pos) {
     // copy over the prefix that was already parsed
diff --git a/ffi/src/ffi/extra/json_writer.cc b/ffi/src/ffi/extra/json_writer.cc
index 94ba5e4a5a..81d321d9a7 100644
--- a/ffi/src/ffi/extra/json_writer.cc
+++ b/ffi/src/ffi/extra/json_writer.cc
@@ -60,6 +60,37 @@ class JSONWriter {
  private:
   explicit JSONWriter(int indent) : indent_(indent), out_iter_(result_) {}
 
+  static bool FastMathSafeIsNaN(double x) {
+#ifdef __FAST_MATH__
+    // Bit-level NaN detection (IEEE 754 double)
+    // IEEE 754 standard: https://en.wikipedia.org/wiki/IEEE_754
+    // NaN is encoded as all 1s in the exponent and non-zero in the mantissa
+    static_assert(sizeof(double) == sizeof(uint64_t), "Unexpected double 
size");
+    uint64_t bits = *reinterpret_cast<const uint64_t*>(&x);
+    uint64_t exponent = (bits >> 52) & 0x7FF;
+    uint64_t mantissa = bits & 0xFFFFFFFFFFFFFull;
+    return (exponent == 0x7FF) && (mantissa != 0);
+#else
+    // Safe to use std::isnan when fast-math is off
+    return std::isnan(x);
+#endif
+  }
+
+  static bool FastMathSafeIsInf(double x) {
+#ifdef __FAST_MATH__
+    // IEEE 754 standard: https://en.wikipedia.org/wiki/IEEE_754
+    // Inf is encoded as all 1s in the exponent and zero in the mantissa
+    static_assert(sizeof(double) == sizeof(uint64_t), "Unexpected double 
size");
+    uint64_t bits = *reinterpret_cast<const uint64_t*>(&x);
+    uint64_t exponent = (bits >> 52) & 0x7FF;
+    uint64_t mantissa = bits & 0xFFFFFFFFFFFFFull;
+    // inf is encoded as all 1s in the exponent and zero in the mantissa
+    return (exponent == 0x7FF) && (mantissa == 0);
+#else
+    return std::isinf(x);
+#endif
+  }
+
   void WriteValue(const json::Value& value) {
     switch (value.type_index()) {
       case TypeIndex::kTVMFFINone: {
@@ -120,9 +151,9 @@ class JSONWriter {
     // largest possible string representation of a double is around 24 chars 
plus
     // one null terminator keep 32 to be safe
     char buffer[32];
-    if (std::isnan(value)) {
+    if (FastMathSafeIsNaN(value)) {
       WriteLiteral("NaN", 3);
-    } else if (std::isinf(value)) {
+    } else if (FastMathSafeIsInf(value)) {
       if (value < 0) {
         WriteLiteral("-Infinity", 9);
       } else {
diff --git a/ffi/tests/cpp/extra/test_json_parser.cc 
b/ffi/tests/cpp/extra/test_json_parser.cc
index c0332e6f8f..a1cc280009 100644
--- a/ffi/tests/cpp/extra/test_json_parser.cc
+++ b/ffi/tests/cpp/extra/test_json_parser.cc
@@ -28,6 +28,37 @@ namespace {
 
 using namespace tvm::ffi;
 
+inline bool FastMathSafeIsNaN(double x) {
+#ifdef __FAST_MATH__
+  // Bit-level NaN detection (IEEE 754 double)
+  // IEEE 754 standard: https://en.wikipedia.org/wiki/IEEE_754
+  // NaN is encoded as all 1s in the exponent and non-zero in the mantissa
+  static_assert(sizeof(double) == sizeof(uint64_t), "Unexpected double size");
+  uint64_t bits = *reinterpret_cast<const uint64_t*>(&x);
+  uint64_t exponent = (bits >> 52) & 0x7FF;
+  uint64_t mantissa = bits & 0xFFFFFFFFFFFFFull;
+  return (exponent == 0x7FF) && (mantissa != 0);
+#else
+  // Safe to use std::isnan when fast-math is off
+  return std::isnan(x);
+#endif
+}
+
+inline bool FastMathSafeIsInf(double x) {
+#ifdef __FAST_MATH__
+  // IEEE 754 standard: https://en.wikipedia.org/wiki/IEEE_754
+  // Inf is encoded as all 1s in the exponent and zero in the mantissa
+  static_assert(sizeof(double) == sizeof(uint64_t), "Unexpected double size");
+  uint64_t bits = *reinterpret_cast<const uint64_t*>(&x);
+  uint64_t exponent = (bits >> 52) & 0x7FF;
+  uint64_t mantissa = bits & 0xFFFFFFFFFFFFFull;
+  // inf is encoded as all 1s in the exponent and zero in the mantissa
+  return (exponent == 0x7FF) && (mantissa == 0);
+#else
+  return std::isinf(x);
+#endif
+}
+
 TEST(JSONParser, BoolNull) {
   // boolean value
   EXPECT_EQ(json::Parse("true").cast<bool>(), true);
@@ -61,11 +92,11 @@ TEST(JSONParser, Number) {
   // parsing scientific notation
   EXPECT_EQ(json::Parse("1.456e12").cast<double>(), 1.456e12);
   // NaN
-  EXPECT_EQ(std::isnan(json::Parse("NaN").cast<double>()), true);
+  EXPECT_EQ(FastMathSafeIsNaN(json::Parse("NaN").cast<double>()), true);
   // Infinity
-  EXPECT_EQ(std::isinf(json::Parse("Infinity").cast<double>()), true);
+  EXPECT_EQ(FastMathSafeIsInf(json::Parse("Infinity").cast<double>()), true);
   // -Infinity
-  EXPECT_EQ(std::isinf(-json::Parse("-Infinity").cast<double>()), true);
+  EXPECT_EQ(FastMathSafeIsInf(-json::Parse("-Infinity").cast<double>()), true);
 
   // Test zero variants
   EXPECT_EQ(json::Parse("0").cast<int64_t>(), 0);

Reply via email to