[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