[Mlir-commits] [mlir] 794aa0f - [APFloat] Don't turn a Float8E8M0FNU NaN into an Inf on conversion (#214919)

llvmlistbot at llvm.org llvmlistbot at llvm.org
Mon Aug 10 04:09:48 PDT 2026


Author: 曾鈜寬 Tseng Hung Kuan
Date: 2026-08-10T13:09:43+02:00
New Revision: 794aa0fd923acc744f7086d2c77a336dcca6256d

URL: https://github.com/llvm/llvm-project/commit/794aa0fd923acc744f7086d2c77a336dcca6256d
DIFF: https://github.com/llvm/llvm-project/commit/794aa0fd923acc744f7086d2c77a336dcca6256d.diff

LOG: [APFloat] Don't turn a Float8E8M0FNU NaN into an Inf on conversion (#214919)

`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.

Added: 
    

Modified: 
    llvm/lib/Support/APFloat.cpp
    llvm/unittests/ADT/APFloatTest.cpp
    mlir/test/Dialect/Arith/canonicalize.mlir

Removed: 
    


################################################################################
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


        


More information about the Mlir-commits mailing list