[Mlir-commits] [llvm] [mlir] [mlir][arith] Expand narrow-float truncf/extf with one generic engine (PR #236002)

llvmlistbot at llvm.org llvmlistbot at llvm.org
Mon Oct 26 22:04:54 PDT 2026


llvmorg-github-actions[bot] wrote:


<!--LLVM PR SUMMARY COMMENT-->

@llvm/pr-subscribers-mlir-arith

Author: yifeihe07

<details>
<summary>Changes</summary>

arith-expand has one hand-written truncf/extf converter per narrow format (f4E2M1FN, f8E5M2, f8E4M3FN). This does not scale: every format adds code that redoes rounding and special-value handling by hand, which is error-prone and hard to verify.

Replace them with one generic expansion that reads each format from llvm::fltSemantics and emits integer arith ops. It supports every narrow APFloat format and is bit-exact with APFloat::convert in all five rounding modes, so results change where the old converters were inexact. New formats need a table entry instead of a converter; the existing options and populate functions are kept.

---

Patch is 252.78 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/236002.diff


16 Files Affected:

- (modified) mlir/include/mlir/Dialect/Arith/Transforms/Passes.h (+14) 
- (modified) mlir/include/mlir/Dialect/Arith/Transforms/Passes.td (+19) 
- (added) mlir/include/mlir/Dialect/Arith/Utils/NarrowFloatExpansion.h (+84) 
- (modified) mlir/lib/Dialect/Arith/IR/ArithOps.cpp (+5) 
- (modified) mlir/lib/Dialect/Arith/Transforms/CMakeLists.txt (+1) 
- (modified) mlir/lib/Dialect/Arith/Transforms/ExpandOps.cpp (+443-511) 
- (modified) mlir/lib/Dialect/Arith/Utils/CMakeLists.txt (+1) 
- (added) mlir/lib/Dialect/Arith/Utils/NarrowFloatExpansion.cpp (+570) 
- (modified) mlir/test/Dialect/Arith/canonicalize.mlir (+41) 
- (added) mlir/test/Dialect/Arith/expand-narrow-float.mlir (+827) 
- (modified) mlir/test/Dialect/Arith/expand-ops.mlir (+308-102) 
- (added) mlir/test/Integration/Dialect/Arith/CPU/test-arith-expand-narrow-float.mlir (+1166) 
- (modified) mlir/test/Integration/Dialect/Arith/CPU/test-arith-expand-truncf-extf.mlir (+33-3) 
- (modified) mlir/unittests/Dialect/Arith/CMakeLists.txt (+5) 
- (added) mlir/unittests/Dialect/Arith/NarrowFloatExpansionTest.cpp (+556) 
- (modified) utils/bazel/llvm-project-overlay/mlir/BUILD.bazel (+4-1) 


``````````diff
diff --git a/mlir/include/mlir/Dialect/Arith/Transforms/Passes.h b/mlir/include/mlir/Dialect/Arith/Transforms/Passes.h
index 7d7909c5f8231f..9708435832ff8e 100644
--- a/mlir/include/mlir/Dialect/Arith/Transforms/Passes.h
+++ b/mlir/include/mlir/Dialect/Arith/Transforms/Passes.h
@@ -56,6 +56,18 @@ void populateEmulateUnsupportedFloatsLegality(ConversionTarget &target,
 /// Add patterns to expand Arith bf16 patterns to lower level bitcasts/shifts.
 void populateExpandBFloat16Patterns(RewritePatternSet &patterns);
 
+/// Add patterns to expand `arith.truncf` and `arith.extf` on the narrow float
+/// types in `narrowTypes` (at most 8 bits, with a zero and subnormals or, as a
+/// truncf destination with a rounding mode, an unsigned precision-1 format with
+/// neither; others are ignored) into integer arith ops plus, for some pairs,
+/// arith.extf or arith.truncf bridges. Results are APFloat::convert's in the
+/// op's rounding mode, rounded once (from f80 or f128 via f64 rounded to odd);
+/// a format without Inf and NaN saturates (NaN gives +max-finite), and an
+/// unsigned destination gets the input rounded with its sign, the sign then
+/// dropped. Dynamic shapes are left alone.
+void populateExpandNarrowFloatPatterns(RewritePatternSet &patterns,
+                                       ArrayRef<TypeID> narrowTypes);
+
 /// Add patterns to expand Arith f4e2m1 patterns to lower level bitcasts/shifts.
 void populateExpandF4E2M1Patterns(RewritePatternSet &patterns);
 
@@ -67,6 +79,8 @@ void populateExpandF8E5M2Patterns(RewritePatternSet &patterns);
 void populateExpandF8E4M3FNPatterns(RewritePatternSet &patterns);
 
 /// Add patterns to expand Arith f8e8m0 patterns to lower level bitcasts/shifts.
+/// Without a rounding mode a truncf into f8E8M0FNU takes the exponent (toward
+/// zero); with one it is expanded as by populateExpandNarrowFloatPatterns.
 void populateExpandF8E8M0Patterns(RewritePatternSet &patterns);
 
 /// Add patterns to expand scaling ExtF/TruncF ops to equivalent arith ops
diff --git a/mlir/include/mlir/Dialect/Arith/Transforms/Passes.td b/mlir/include/mlir/Dialect/Arith/Transforms/Passes.td
index 98e549f53040a4..9a0a4484154389 100644
--- a/mlir/include/mlir/Dialect/Arith/Transforms/Passes.td
+++ b/mlir/include/mlir/Dialect/Arith/Transforms/Passes.td
@@ -25,6 +25,25 @@ def ArithExpandOpsPass : Pass<"arith-expand"> {
               "Enable the F8E5M2 expansion patterns">,
        Option<"includeF8E4M3FN", "include-f8e4m3fn", "bool", /*default=*/"false",
               "Enable the F8E4M3FN expansion patterns">,
+       Option<"includeF8E4M3", "include-f8e4m3", "bool", /*default=*/"false",
+              "Enable the F8E4M3 expansion patterns">,
+       Option<"includeF8E4M3FNUZ", "include-f8e4m3fnuz", "bool",
+              /*default=*/"false",
+              "Enable the F8E4M3FNUZ expansion patterns">,
+       Option<"includeF8E4M3B11FNUZ", "include-f8e4m3b11fnuz", "bool",
+              /*default=*/"false",
+              "Enable the F8E4M3B11FNUZ expansion patterns">,
+       Option<"includeF8E5M2FNUZ", "include-f8e5m2fnuz", "bool",
+              /*default=*/"false",
+              "Enable the F8E5M2FNUZ expansion patterns">,
+       Option<"includeF8E3M4", "include-f8e3m4", "bool", /*default=*/"false",
+              "Enable the F8E3M4 expansion patterns">,
+       Option<"includeF6E2M3FN", "include-f6e2m3fn", "bool",
+              /*default=*/"false",
+              "Enable the F6E2M3FN expansion patterns">,
+       Option<"includeF6E3M2FN", "include-f6e3m2fn", "bool",
+              /*default=*/"false",
+              "Enable the F6E3M2FN expansion patterns">,
        Option<"includeFlushDenormals", "include-flush-denormals", "bool",
               /*default=*/"false",
               "Enable expansion of `arith.flush_denormals` on IEEE-like "
diff --git a/mlir/include/mlir/Dialect/Arith/Utils/NarrowFloatExpansion.h b/mlir/include/mlir/Dialect/Arith/Utils/NarrowFloatExpansion.h
new file mode 100644
index 00000000000000..dc10b9270f12eb
--- /dev/null
+++ b/mlir/include/mlir/Dialect/Arith/Utils/NarrowFloatExpansion.h
@@ -0,0 +1,84 @@
+//===- NarrowFloatExpansion.h - Narrow float truncf/extf --------*- C++ -*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef MLIR_DIALECT_ARITH_UTILS_NARROWFLOATEXPANSION_H
+#define MLIR_DIALECT_ARITH_UTILS_NARROWFLOATEXPANSION_H
+
+#include "mlir/Dialect/Arith/IR/Arith.h"
+#include "mlir/IR/Builders.h"
+#include <cstdint>
+
+namespace llvm {
+struct fltSemantics;
+} // namespace llvm
+
+namespace mlir {
+namespace arith {
+/// The encoding of a float format, as floatFormatSpecFromSemantics derives it.
+struct FloatFormatSpec {
+  unsigned sizeInBits = 0;
+  unsigned mantBits = 0;
+  unsigned expBits = 0;
+  /// UINT_MAX for a format without a sign.
+  unsigned signBitPos = 0;
+  /// 1 - the minimum exponent.
+  int bias = 0;
+  unsigned maxStoredExp = 0;
+  /// APFloat::getNaN's encoding; max-finite for a format without a NaN.
+  uint64_t quietNaNBits = 0;
+  uint64_t maxFiniteBitsAbs = 0;
+  bool hasInf = false;
+  bool isSigned = true;
+  bool hasAllOnesNaN = false;
+  bool isFNUZ = false;
+  /// Without a zero (unsigned, precision 1 only), exponent field 0 is a normal
+  /// binade: code c is what code c + 1 would be with a zero, 2^(c + 1 - bias).
+  bool hasZero = true;
+
+  bool hasNaN() const { return hasInf || hasAllOnesNaN || isFNUZ; }
+};
+
+/// Returns the spec of `sem`, one of APFloat's semantics, which must have a
+/// zero and subnormals (or, if unsigned with precision 1, neither), an implicit
+/// integer bit and at most 64 bits (asserted).
+FloatFormatSpec floatFormatSpecFromSemantics(const llvm::fltSemantics &sem);
+
+/// Returns floatFormatSpecFromSemantics of `floatTy`'s semantics.
+FloatFormatSpec floatFormatSpecFromFloatType(FloatType floatTy);
+
+/// Whether `a` and `b` are signed IEEE-encoded formats with the same exponent
+/// width and bias (e.g. f8E5M2 and f16), so the encodings differ by a shift.
+bool haveSameExponentLayout(const FloatFormatSpec &a, const FloatFormatSpec &b);
+
+// Both emitters take a carrier `srcBits` of i32 or i64 (or a static or scalable
+// shaped type of it) with the source encoding in its low bits, higher bits
+// ignored, and return the destination encoding zero-extended in the same type.
+// The truncation needs src.mantBits + dst.mantBits + 4 carrier bits.
+
+/// Emits the truncation of `src` (f16, bf16, f32 or f64) to the narrower `dst`,
+/// rounded once in `rnd`, as APFloat::convert does (NaN sign and payload
+/// included); a `dst` without Inf and NaN saturates (NaN gives +max-finite).
+/// An unsigned `dst` gets x rounded with its sign, the sign then dropped, as
+/// APFloat does (it asserts where that is a negative NaN; here that is the
+/// NaN, or max-finite downward). `dst`'s exponent range must lie inside
+/// `src`'s (asserted).
+Value emitNarrowFloatTrunc(ImplicitLocOpBuilder &b, Value srcBits,
+                           const FloatFormatSpec &src,
+                           const FloatFormatSpec &dst, RoundingMode rnd);
+
+/// Emits the exact extension of the narrow `src` (which must have a zero) to
+/// `dst` (f16, bf16, f32 or f64), which must have `src`'s precision and
+/// smallest subnormal (asserted); a value above its range gives +-Inf, and NaNs
+/// are quieted as by APFloat.
+Value emitNarrowFloatExt(ImplicitLocOpBuilder &b, Value srcBits,
+                         const FloatFormatSpec &src,
+                         const FloatFormatSpec &dst);
+} // namespace arith
+} // namespace mlir
+
+#endif // MLIR_DIALECT_ARITH_UTILS_NARROWFLOATEXPANSION_H
diff --git a/mlir/lib/Dialect/Arith/IR/ArithOps.cpp b/mlir/lib/Dialect/Arith/IR/ArithOps.cpp
index 19e9caca6567a6..54c2ce8471be09 100644
--- a/mlir/lib/Dialect/Arith/IR/ArithOps.cpp
+++ b/mlir/lib/Dialect/Arith/IR/ArithOps.cpp
@@ -1839,6 +1839,11 @@ convertFloatValue(APFloat sourceValue,
   if (sourceValue.isNaN() &&
       targetSemantics.nonFiniteBehavior == fltNonfiniteBehavior::FiniteOnly)
     return failure();
+  // A format without a sign (e.g. f8E8M0FNU) has no negative values, and
+  // APFloat::convert reaches llvm_unreachable on a negative NaN or overflow.
+  if (sourceValue.isNegative() &&
+      !APFloat::semanticsHasSignedRepr(targetSemantics))
+    return failure();
 
   bool losesInfo = false;
   auto status = sourceValue.convert(targetSemantics, roundingMode, &losesInfo);
diff --git a/mlir/lib/Dialect/Arith/Transforms/CMakeLists.txt b/mlir/lib/Dialect/Arith/Transforms/CMakeLists.txt
index 8e84edc05bb857..bda5a50d86f250 100644
--- a/mlir/lib/Dialect/Arith/Transforms/CMakeLists.txt
+++ b/mlir/lib/Dialect/Arith/Transforms/CMakeLists.txt
@@ -21,6 +21,7 @@ add_mlir_dialect_library(MLIRArithTransforms
   MLIRAnalysisDataFlow
   MLIRArithCommonConversion
   MLIRArithDialect
+  MLIRArithUtils
   MLIRBufferizationDialect
   MLIRBufferizationTransforms
   MLIRFuncDialect
diff --git a/mlir/lib/Dialect/Arith/Transforms/ExpandOps.cpp b/mlir/lib/Dialect/Arith/Transforms/ExpandOps.cpp
index fd8e8e8368f6cc..98df6236a07e79 100644
--- a/mlir/lib/Dialect/Arith/Transforms/ExpandOps.cpp
+++ b/mlir/lib/Dialect/Arith/Transforms/ExpandOps.cpp
@@ -9,6 +9,7 @@
 #include "mlir/Conversion/ArithCommon/CeilFloorDivExpansion.h"
 #include "mlir/Dialect/Arith/IR/Arith.h"
 #include "mlir/Dialect/Arith/Transforms/Passes.h"
+#include "mlir/Dialect/Arith/Utils/NarrowFloatExpansion.h"
 #include "mlir/Dialect/Vector/IR/VectorDialect.h"
 #include "mlir/IR/BuiltinTypeInterfaces.h"
 #include "mlir/IR/Location.h"
@@ -46,18 +47,6 @@ static Value createAPIntConst(Location loc, Type type, const APInt &value,
   return arith::ConstantOp::create(rewriter, loc, attr);
 }
 
-/// Create a float constant.
-static Value createFloatConst(Location loc, Type type, const APFloat &value,
-                              PatternRewriter &rewriter) {
-  auto attr = rewriter.getFloatAttr(getElementTypeOrSelf(type), value);
-  if (auto shapedTy = dyn_cast<ShapedType>(type)) {
-    return arith::ConstantOp::create(rewriter, loc,
-                                     DenseElementsAttr::get(shapedTy, attr));
-  }
-
-  return arith::ConstantOp::create(rewriter, loc, attr);
-}
-
 /// Creates shapedType using shape from cloneFrom and base type from cloneTo
 static Type cloneToShapedType(Type cloneFrom, Type cloneTo) {
   if (auto shapedTy = dyn_cast<ShapedType>(cloneFrom)) {
@@ -66,6 +55,332 @@ static Type cloneToShapedType(Type cloneFrom, Type cloneTo) {
   return cloneTo;
 }
 
+/// Returns the exact f32 bits of the f8E8M0FNU `code`: code << 23, except 0x00
+/// (2^-127, 0x00400000) and, if `withNaN`, the NaN (0x7FC00000).
+static Value emitF8E8M0FNUToF32Bits(ImplicitLocOpBuilder &b,
+                                    PatternRewriter &rewriter, Value code,
+                                    bool withNaN) {
+  Location loc = b.getLoc();
+  Type i8Ty = code.getType();
+  Type i32Ty = cloneToShapedType(i8Ty, b.getI32Type());
+  Value cF32MantissaWidth = createConst(loc, i32Ty, 23, rewriter);
+  Value exti = arith::ExtUIOp::create(b, i32Ty, code);
+  Value f32Bits = arith::ShLIOp::create(b, exti, cF32MantissaWidth);
+  Value cF8Min = createConst(loc, i8Ty, 0, rewriter);
+  Value cF32Min = createConst(loc, i32Ty, 0x00400000, rewriter);
+  Value min = arith::CmpIOp::create(b, arith::CmpIPredicate::eq, code, cF8Min);
+  f32Bits = arith::SelectOp::create(b, min, cF32Min, f32Bits);
+  if (!withNaN)
+    return f32Bits;
+  Value cF8NaN = createConst(loc, i8Ty, 0xff, rewriter);
+  Value cF32NaN = createConst(loc, i32Ty, 0x7fc00000, rewriter);
+  Value isNan =
+      arith::CmpIOp::create(b, arith::CmpIPredicate::eq, code, cF8NaN);
+  return arith::SelectOp::create(b, isNan, cF32NaN, f32Bits);
+}
+
+static bool isEmitterWideType(Type ty) {
+  return ty.isF16() || ty.isBF16() || ty.isF32() || ty.isF64();
+}
+
+/// A float the emitters take: at most 8 bits, with a zero and subnormals or,
+/// as a truncation destination only, an unsigned precision-1 format with
+/// neither.
+static bool isNarrowFloat(Type ty, bool asTruncDest = false) {
+  auto floatTy = dyn_cast<FloatType>(ty);
+  if (!floatTy || floatTy.getWidth() > 8)
+    return false;
+  const llvm::fltSemantics &sem = floatTy.getFloatSemantics();
+  if (sem.hasZero && sem.hasDenormals)
+    return true;
+  return asTruncDest && !sem.hasZero && !sem.hasDenormals &&
+         !sem.hasSignedRepr && APFloat::semanticsPrecision(sem) == 1;
+}
+
+/// Whether every finite `from` value is exact in `to` (or, if `allowOverflow`,
+/// above its range); given precision and sign, the extremes decide.
+static bool isExactIn(FloatType from, FloatType to,
+                      bool allowOverflow = false) {
+  const llvm::fltSemantics &fromSem = from.getFloatSemantics();
+  const llvm::fltSemantics &toSem = to.getFloatSemantics();
+  if (APFloat::semanticsPrecision(fromSem) >
+          APFloat::semanticsPrecision(toSem) ||
+      (APFloat::semanticsHasSignedRepr(fromSem) &&
+       !APFloat::semanticsHasSignedRepr(toSem)))
+    return false;
+  SmallVector<APFloat, 2> values = {APFloat::getSmallest(fromSem)};
+  if (!allowOverflow)
+    values.push_back(APFloat::getLargest(fromSem));
+  for (APFloat &value : values) {
+    bool losesInfo = false;
+    (void)value.convert(toSem, APFloat::rmNearestTiesToEven, &losesInfo);
+    if (losesInfo)
+      return false;
+  }
+  return true;
+}
+
+static bool isExactNarrowPair(FloatType src, FloatType dst) {
+  return isNarrowFloat(dst) && isExactIn(src, dst);
+}
+
+/// Whether emitNarrowFloatTrunc's exponent-range and carrier asserts hold.
+static bool canRoundFrom(FloatType src, FloatType dst) {
+  const llvm::fltSemantics &srcSem = src.getFloatSemantics();
+  const llvm::fltSemantics &dstSem = dst.getFloatSemantics();
+  return APFloat::semanticsMinExponent(dstSem) >=
+             APFloat::semanticsMinExponent(srcSem) &&
+         APFloat::semanticsMaxExponent(dstSem) <=
+             APFloat::semanticsMaxExponent(srcSem) &&
+         APFloat::semanticsPrecision(srcSem) +
+                 APFloat::semanticsPrecision(dstSem) + 2 <=
+             64;
+}
+
+/// Returns the type emitNarrowFloatTrunc rounds from: `src`; f64 for f80 and
+/// f128 (see emitRoundToOddF64Bits); else f32, or f64 if f32's range is too
+/// small, reached exactly (extf, or a decode); or null.
+static FloatType getTruncSourceType(FloatType src, FloatType dst) {
+  if (isEmitterWideType(src) && canRoundFrom(src, dst))
+    return src;
+  FloatType f64 = Float64Type::get(src.getContext());
+  if (isa<Float80Type, Float128Type>(src))
+    return canRoundFrom(f64, dst) ? f64 : FloatType();
+  FloatType f32 = Float32Type::get(src.getContext());
+  if (isa<Float8E8M0FNUType>(src))
+    return canRoundFrom(f32, dst) ? f32 : FloatType();
+  FloatType wide = canRoundFrom(f32, dst) ? f32 : f64;
+  if (!canRoundFrom(wide, dst))
+    return FloatType();
+  bool decodable = isNarrowFloat(src);
+  bool extendable = src.getWidth() > 8 && src.getWidth() < wide.getWidth();
+  return (decodable || extendable) && isExactIn(src, wide) ? wide : FloatType();
+}
+
+/// Returns the type emitNarrowFloatExt writes for `src` to `dst`, or null: f16
+/// if `src` has its exponent layout and `dst` is wider; else `dst`; f32 (then a
+/// truncf) for a `dst` of at most 8 bits without a zero, subnormals or `src`'s
+/// sign; else the widest of f64, f32 and f16 holding `src`, exact in `dst`.
+static FloatType getExtResultType(FloatType src, FloatType dst) {
+  Builder builder(dst.getContext());
+  FloatType f16 = builder.getF16Type();
+  if (dst.getWidth() > 16 && isExactIn(f16, dst) &&
+      arith::haveSameExponentLayout(arith::floatFormatSpecFromFloatType(src),
+                                    arith::floatFormatSpecFromFloatType(f16)))
+    return f16;
+  if (isEmitterWideType(dst))
+    return isExactIn(src, dst, /*allowOverflow=*/true) ? dst : FloatType();
+  if (dst.getWidth() <= 8) {
+    bool losesSign = APFloat::semanticsHasSignedRepr(src.getFloatSemantics()) &&
+                     !APFloat::semanticsHasSignedRepr(dst.getFloatSemantics());
+    FloatType f32 = builder.getF32Type();
+    return (!isNarrowFloat(dst) || losesSign) && isExactIn(src, f32)
+               ? f32
+               : FloatType();
+  }
+  for (FloatType wide :
+       {builder.getF64Type(), builder.getF32Type(), builder.getF16Type()})
+    if (wide.getWidth() < dst.getWidth() && isExactIn(wide, dst) &&
+        isExactIn(src, wide))
+      return wide;
+  return FloatType();
+}
+
+/// The gate shared by the converters and the pass legality. A truncf into a
+/// listed type (which may lack a zero), or an exact extf between narrow floats
+/// one of them listed, is a rounding; any other extf from a listed type an
+/// extension. Dynamic shapes are left alone (no splat constants).
+static bool canExpandNarrowFloat(bool isExt, Type srcTy, Type dstTy, Type opTy,
+                                 ArrayRef<TypeID> narrowTypes) {
+  if (auto shapedTy = dyn_cast<ShapedType>(opTy);
+      shapedTy && !shapedTy.hasStaticShape())
+    return false;
+  auto src = dyn_cast<FloatType>(srcTy);
+  auto dst = dyn_cast<FloatType>(dstTy);
+  if (!src || !dst)
+    return false;
+  auto isListed = [&](FloatType ty, bool asTruncDest = false) {
+    return isNarrowFloat(ty, asTruncDest) &&
+           llvm::is_contained(narrowTypes, ty.getTypeID());
+  };
+  if (!isExt)
+    return isListed(dst, /*asTruncDest=*/true) && getTruncSourceType(src, dst);
+  if (isExactNarrowPair(src, dst))
+    return (isListed(dst) || isListed(src)) && getTruncSourceType(src, dst);
+  return isListed(src) && getExtResultType(src, dst);
+}
+
+/// i32, or i64 if a format or the truncation's rounding needs more bits.
+static IntegerType getCarrierIntType(Builder &b,
+                                     const arith::FloatFormatSpec &src,
+                                     const arith::FloatFormatSpec &dst) {
+  bool needsI64 = src.sizeInBits > 32 || dst.sizeInBits > 32 ||
+                  src.mantBits + dst.mantBits + 4 > 32;
+  return b.getIntegerType(needsI64 ? 64 : 32);
+}
+
+/// Returns the op's nnan flag alone, or null: the only flag of the float ops
+/// created before the rounding. The f64 value of an f128 can be Inf or -0
+/// where the f128 is neither (ninf, nsz), and with contract ExtFOp::fold would
+/// fold the widening of a contract truncf back to the truncf's operand.
+static arith::FastMathFlagsAttr getNNaNOnly(MLIRContext *ctx,
+                                            arith::FastMathFlagsAttr fastmath) {
+  if (fastmath && arith::bitEnumContainsAll(fastmath.getValue(),
+                                            arith::FastMathFlags::nnan))
+    return arith::FastMathFlagsAttr::get(ctx, arith::FastMathFlags::nnan);
+  return {};
+}
+
+/// Returns the f64 bits of the f128 or f80 `operand` rounded to odd: toward
+/// zero, with the LSB set if inexact. f64 has at least two more bits than any
+/// precision p the emitter writes (53 >= p + 2) and a wider range, so rounding
+/// this value once more in any mode equals rounding `operand` directly. An f80
+/// is extended to f128 first (exact for canonical x87 encodings; a
+/// non-canonical one is read as the lowered arith.extf reads it). A NaN gives
+/// APFloat's f64 NaN; `nnan` (the op's nnan flag or null) drops that step.
+static Value emitRoundToOddF64Bits(ImplicitLocOpBuilder &b,
+                                   PatternRewriter &rewriter, Value operand,
+                                   arith::FastMathFlagsAttr nnan) {
+  Location loc = b.getLoc();
+  Type operandTy = operand.getType();
+  Type f128Ty = cloneToShapedType(operandTy, b.getF128Type());
+  Type i128Ty = cloneToShapedType(operandTy, b.getIntegerType(128));
+  Type f64Ty = cloneToShapedType(operandTy, b.getF64Type());
+  Type i64Ty = cloneToShapedType(operandTy, b.getI64Type());
+  Value x = operand;
+  if (operandTy != f128Ty)
+    x = arith::ExtFOp::create(b, f128Ty, operand, nnan);
+  Value r =
+      arith::TruncFOp::create(b, f64Ty, x, /*roundingmode=*/nullptr, nnan);
+  Value back = arith::ExtFOp::create(b, f128Ty, r, nnan);
+  // `back` has the sign of `x`, so their bits order by magnitude.
+  Value xBits = ari...
[truncated]

``````````

</details>


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


More information about the Mlir-commits mailing list