Summary
I’d like to add LLVM-IR-style human-readable floating-point literals to MLIR’s textual assembly format: +inf/-inf, +qnan/-qnan, +nan(0x…)/+snan(0x…), and C-style hexadecimal floats (0x1.8p3), on both the parsing and printing sides. As part of the same change, FloatAttr values are built directly in the target type’s semantics instead of being routed through double, which fixes a latent precision bug for types wider than double (f80, f128).
This mirrors the recent LLVM IR change (RFC / #121838) and is a format change: downstream tooling and tests that string-match the old output will need updating.
Motivation
Today MLIR has no readable way to spell floating-point special values. The only option is the raw bit-pattern hex form, both on input and output:
// Today: what does this mean?
%0 = arith.constant 0x7F800000 : f32
%1 = arith.constant 0xFFF0000000000000 : f64
%2 = arith.constant 0x7FC00000 : f32
You have to decode the bits by hand to see that these are +inf : f32, -inf : f64, and a quiet NaN. This is hard to read, hard to write, and error-prone (the bit width must exactly match the type).
Also, the parser converts every float literal to a double before building the FloatAttr:
Token::getFloatingPointValue() -> double -> FloatAttr::get(type, double)
For types wider than double, this rounding may have unexpected rounding effects.
Proposal
New literal forms (parsed and printed)
%0 = arith.constant +inf : f32
%1 = arith.constant -inf : f64
%2 = arith.constant +qnan : f32 // quiet NaN
%3 = arith.constant -qnan : f16
%4 = arith.constant +nan(0x1) : f32 // quiet NaN with payload
%5 = arith.constant +snan(0x1) : f64 // signaling NaN with payload
%6 = arith.constant 0x1.8p3 : f64 // C-style hex float (== 12.0)
%7 = arith.constant dense<[+inf, -inf]> : tensor<2xf32>
Rules:
- The sign is mandatory for
inf/NaN (+inf, notinf). This keeps bareinf/nanusable as ordinary identifiers/keywords and avoids ambiguity (the same rule LLVM IR uses). - C-style hex floats (
0x1.8p3) are distinguished from the existing bit-pattern form by the presence of a fractional part and/or ap/Pbinary exponent. The plain0x…form (no dot, no exponent) is still parsed as a raw bit pattern, so existing IR keeps working. - Values are built directly in the target type’s semantics via
APFloat::convertFromString, so NaN payloads are preserved and wide types are no longer double-rounded. - A special value literal that is not representable in its target type is an error, e.g.,
+infforf4E2M1FN.
Printing
The printer emits the same forms, mirroring LLVM: infinities as +inf/-inf, quiet NaN as +qnan/-qnan, and other NaNs as +nan(0x…) / +snan(0x…) for IEEE-encoded types; see NaN spellings below for other types. Finite values are unchanged (shortest round-trippable decimal, else the hex bit-pattern fallback).
Special values print in the new format by default. Pass --mlir-print-float-special-literals-as-hex to restore the old hex output for backwards compatibility.
NaN spellings follow the type’s NaN encoding
Not every float type uses the IEEE NaN model. The +qnan / +nan(p) / +snan(p) forms above apply to IEEE-encoded types, where the payload and the quiet/signaling bit are meaningful. For non-IEEE types, we propose:
| Encoding | Example Types | Printed | Also parsed | Rejected |
|---|---|---|---|---|
| IEEE | f16, bf16, f32, f64, f80, f128, f8E5M2, f8E4M3 | +qnan, -qnan, +snan(p), -snan(p), +nan(p), -nan(p) | – | – |
| AllOnes | f8E4M3FN, f8E8M0FNU | +nan, -nan (+nan only for signless types) |
+qnan, -qnan |
snan, any payload, negative on signless |
| NegativeZero | f8E4M3FNUZ, f8E5M2FNUZ, f8E4M3B11FNUZ | -nan |
-qnan |
positive NaN, snan, any payload |
Consistent with the “not representable is an error” rule, the parser rejects a NaN spelling the type cannot represent (a payload or snan on these fp8 types, or a positive NaN on a negative-zero type) rather than silently re-encoding it. This keeps each printed form the only lossless text for its value.
Impact / backwards compatibility
This changes the textual output, so it is not fully transparent:
- Printed output for inf/NaN changes from hex bit patterns to the new textual forms. FileCheck tests and any tooling that scrapes MLIR text for
0x7F800000-style infinities/NaNs will need to be updated. A--mlir-print-float-special-literals-as-hexflag will be added to the tools for the moment to enable using the legacy format on output. - Wider-than-double decimal literals now parse to the correctly-rounded value. This is a (small) numerical behavior change for f80/f128 constants; the in-tree bytecode goldens for
0.1 : f80/0.1 : f128were regenerated. - Input compatibility is preserved: the existing
0x<bits> : <type>bit-pattern form still parses exactly as before.
Relationship to LLVM IR
The design intentionally follows LLVM IR’s textual float literals. A few deliberate differences remain, to fit MLIR:
- Overflow/underflow are tolerated (yielding
inf/0) rather than raising a parse error as LLVM IR does. This preserves long-standing MLIR behavior (e.g.1.0e999 : f64→+inf, used as a sentinel in existing IR/tests). - The non-round-trippable finite fallback prints
0x<bits>, keeping MLIR’s existing bit-pattern form, rather than LLVM IR’s newerf0x<bits>spelling. 0x<hex>without a dot/exponent keeps its MLIR meaning (a raw bit pattern whose width matches the type), not LLVM IR’s legacy double form.
Alternatives considered
- Route all decimals through
convertFromStringwith LLVM-style strict overflow/underflow errors. Cleaner parity, but changes overflow/underflow from lenient to hard errors, breaking existing IR. Rejected in favor of preserving current behavior. Open to change. - Use APFloat for printing/parsing Use same format as APFloat. Rejected to match LLVM syntax.
Implementation
I have a PR for functionality that we can clean up or apply whatever improvement if we agree this is worth it. IR changes are visible already there and seem way more readable.
Round-trip was validated across all builtin float types, over every special-value spelling and a wide range of NaN payloads, checking both textual idempotence and bit-level preservation. The test script is available as a gist.
References
- LLVM IR RFC (prior art this follows): [RFC] Floating-point literals in LLVM IR
- LLVM IR implementation PR: [IR][AsmParser] Revamp how floating-point literals work in LLVM IR. by jcranmer-intel · Pull Request #121838 · llvm/llvm-project · GitHub
- PR: [mlir] Support human-readable float literals in the textual IR by victor-eds · Pull Request #210422 · llvm/llvm-project · GitHub