[llvm] [mlir] [APFloat] Don't turn a Float8E8M0FNU NaN into an Inf on conversion (PR #214919)

via llvm-commits llvm-commits at lists.llvm.org
Fri Aug 7 19:57:31 PDT 2026


llvmorg-github-actions[bot] wrote:


<!--LLVM PR SUMMARY COMMENT-->

@llvm/pr-subscribers-mlir

Author: 曾鈜寬 Tseng Hung Kuan (Tim096)

<details>
<summary>Changes</summary>

`Float8E8M0FNU` has a precision of 1, so it stores no significand bits and its NaN carries no payload. `IEEEFloat::convert` shifts the (empty) significand into the target format, which leaves the NaN exponent with an all-zero significand — and that is the encoding of an infinity in any format that has infinities.

The `APFloat` object still reports `fcNaN`, so `isNaN()` returns true, but `bitcastToAPInt()` hands back Inf bits. Anything that stores the bit pattern rather than the object — `ConstantFP`, MLIR's `FloatAttr` — therefore ends up with an infinity.

In MLIR this shows up when folding `arith.extf` on an `f8E8M0FNU` constant. The OCP MXFP spec uses the all-ones encoding as the scale of an invalid block, so this silently replaces "this block is invalid" with a value that poisons everything it is multiplied into:

```mlir
func.func @<!-- -->e8m0_nan_to_f32() -> f32 {
  %c = arith.constant 0xFF : f8E8M0FNU
  %0 = arith.extf %c : f8E8M0FNU to f32
  return %0 : f32
}
```

```
$ mlir-opt x.mlir -canonicalize
%cst = arith.constant 0x7F800000 : f32     // +Inf, not a NaN
```

f16 gives `0x7C00`, bf16 `0x7F80` and f64 `0x7FF0000000000000` — all Inf encodings. `convert` reports `opOK` with `losesInfo == false` in every case.

`Float8E8M0FNU` is the only semantics in the table with `precision == 1`, so this is the only source format affected. The fix creates a new NaN in the target semantics when the source has no significand, next to the existing fix-up for `fltNanEncoding::NegativeZero` sources, which handles the analogous NaN-to-`-Inf` case.

Verification: exhaustively over all 256 `Float8E8M0FNU` values against f16, bf16, f32, f64 and f128, checking that the category survives, that a NaN never comes out as an Inf, that the stored bit pattern read back through the target semantics is still a NaN, and that finite scales round-trip unchanged where the target's exponent range allows it. Reverting the `APFloat.cpp` hunk makes the new unit test fail.


---
Full diff: https://github.com/llvm/llvm-project/pull/214919.diff


3 Files Affected:

- (modified) llvm/lib/Support/APFloat.cpp (+5) 
- (modified) llvm/unittests/ADT/APFloatTest.cpp (+30) 
- (modified) mlir/test/Dialect/Arith/canonicalize.mlir (+19) 


``````````diff
diff --git a/llvm/lib/Support/APFloat.cpp b/llvm/lib/Support/APFloat.cpp
index be255bd19964b..63007d88ed4d4 100644
--- a/llvm/lib/Support/APFloat.cpp
+++ b/llvm/lib/Support/APFloat.cpp
@@ -2557,6 +2557,11 @@ APFloat::opStatus IEEEFloat::convert(const fltSemantics &toSemantics,
         semantics->nanEncoding != fltNanEncoding::NegativeZero)
       makeNaN(false, false);
 
+    // If the source has no significand, there are no payload bits to carry
+    // over, and an all-zero significand would encode an Inf. Create a new NaN.
+    if (!APFloat::hasSignificand(fromSemantics))
+      makeNaN(false, sign);
+
     *losesInfo = lostFraction != lfExactlyZero || X86SpecialNan;
 
     // For x87 extended precision, we want to make a NaN, not a special NaN if
diff --git a/llvm/unittests/ADT/APFloatTest.cpp b/llvm/unittests/ADT/APFloatTest.cpp
index 55d39e7f0a414..4ef2814e1a7b3 100644
--- a/llvm/unittests/ADT/APFloatTest.cpp
+++ b/llvm/unittests/ADT/APFloatTest.cpp
@@ -2304,6 +2304,36 @@ TEST(APFloatTest, Float8E8M0FNUValues) {
   EXPECT_EQ(0x1.0p-127, test.convertToDouble());
 }
 
+// Test that a Float8E8M0FNU NaN, which has no payload bits, does not
+// become an Inf when converted to a format that has infinities.
+TEST(APFloatTest, Float8E8M0FNUNaNConvert) {
+  APFloat nan = APFloat(APFloat::Float8E8M0FNU(), "nan");
+  EXPECT_TRUE(nan.isNaN());
+  EXPECT_EQ(APInt(8, 0xff), nan.bitcastToAPInt());
+
+  const std::pair<const fltSemantics *, uint64_t> ToSemantics[] = {
+      {&APFloat::IEEEhalf(), 0x7e00},
+      {&APFloat::BFloat(), 0x7fc0},
+      {&APFloat::IEEEsingle(), 0x7fc00000},
+      {&APFloat::IEEEdouble(), 0x7ff8000000000000ULL},
+  };
+
+  for (const auto &[Sem, ExpectedBits] : ToSemantics) {
+    APFloat test = nan;
+    bool losesInfo = true;
+    APFloat::opStatus status =
+        test.convert(*Sem, APFloat::rmNearestTiesToEven, &losesInfo);
+    EXPECT_EQ(status, APFloat::opOK);
+    EXPECT_FALSE(losesInfo);
+    EXPECT_TRUE(test.isNaN());
+    EXPECT_FALSE(test.isInfinity());
+    APInt bits = test.bitcastToAPInt();
+    EXPECT_EQ(APInt(APFloat::getSizeInBits(*Sem), ExpectedBits), bits);
+    // The stored bit pattern, not just the category, has to say NaN.
+    EXPECT_TRUE(APFloat(*Sem, bits).isNaN());
+  }
+}
+
 TEST(APFloatTest, getLargest) {
   EXPECT_EQ(3.402823466e+38f, APFloat::getLargest(APFloat::IEEEsingle()).convertToFloat());
   EXPECT_EQ(1.7976931348623158e+308, APFloat::getLargest(APFloat::IEEEdouble()).convertToDouble());
diff --git a/mlir/test/Dialect/Arith/canonicalize.mlir b/mlir/test/Dialect/Arith/canonicalize.mlir
index 0c06aa6e861a4..89632305fa99d 100644
--- a/mlir/test/Dialect/Arith/canonicalize.mlir
+++ b/mlir/test/Dialect/Arith/canonicalize.mlir
@@ -1120,6 +1120,25 @@ func.func @extFPVectorConstant() -> vector<2xf128> {
   return %0 : vector<2xf128>
 }
 
+// A f8E8M0FNU NaN has no payload bits; folding must not turn it into an Inf.
+// CHECK-LABEL: @extFPConstantE8M0NaN
+//       CHECK:   %[[cres:.+]] = arith.constant 0x7FC00000 : f32
+//       CHECK:   return %[[cres]]
+func.func @extFPConstantE8M0NaN() -> f32 {
+  %cst = arith.constant 0xFF : f8E8M0FNU
+  %0 = arith.extf %cst : f8E8M0FNU to f32
+  return %0 : f32
+}
+
+// CHECK-LABEL: @extFPVectorConstantE8M0NaN
+//       CHECK:   %[[cres:.+]] = arith.constant dense<[1.000000e+00, 0x7FC00000]> : vector<2xf32>
+//       CHECK:   return %[[cres]]
+func.func @extFPVectorConstantE8M0NaN() -> vector<2xf32> {
+  %cst = arith.constant dense<[1.000000e+00, 0xFF]> : vector<2xf8E8M0FNU>
+  %0 = arith.extf %cst : vector<2xf8E8M0FNU> to vector<2xf32>
+  return %0 : vector<2xf32>
+}
+
 // CHECK-LABEL: @truncExtf
 //       CHECK-NOT:  truncf
 //       CHECK:   return  %arg0

``````````

</details>


https://github.com/llvm/llvm-project/pull/214919


More information about the llvm-commits mailing list