[Mlir-commits] [mlir] [mlir] [arith] Fold trunc(extremum(ext(lhs), ext(rhs))) (PR #214658)
Chuanqi Xu
llvmlistbot at llvm.org
Tue Aug 11 02:26:12 PDT 2026
https://github.com/ChuanqiXu9 updated https://github.com/llvm/llvm-project/pull/214658
>From abc8469e3fee5325dfb5d9e81625881274b417b7 Mon Sep 17 00:00:00 2001
From: Chuanqi Xu <chuanqi.xcq at alibaba-inc.com>
Date: Fri, 7 Aug 2026 15:54:51 +0800
Subject: [PATCH] [mlir] [arith] Fold trunc(extremum(ext(lhs), ext(rhs)))
For example, now we can't fold the following case:
```
%lhs = arith.extf %arg0 : f16 to f32
%rhs = arith.extf %arg1 : f16 to f32
%maximum = arith.maximumf %lhs, %rhs fastmath<nnan> : f32
%maximumTrunc = arith.truncf %maximum : f32 to f16
```
to
```
%maximum = arith.maximumf %arg0, %arg1 fastmath<nnan> : f16
```
And this patch made this optimization. We can extend the op list to
arith.select, arith.cmpf and so on in the future.
I was told this may be intentional as some old hardware doesn't have
instructions to deal with maximum with fp16. But I think in the higher
level we should do such optimizations. And specific hardware can
make their own transformations according to their specific details.
AI assisted.
---
mlir/lib/Dialect/Arith/IR/ArithOps.cpp | 166 +++++++++++++++++-
mlir/test/Dialect/Arith/canonicalize.mlir | 203 +++++++++++++++++++++-
2 files changed, 354 insertions(+), 15 deletions(-)
diff --git a/mlir/lib/Dialect/Arith/IR/ArithOps.cpp b/mlir/lib/Dialect/Arith/IR/ArithOps.cpp
index ff6a5d4a0c29a..b3cc5b5706d3c 100644
--- a/mlir/lib/Dialect/Arith/IR/ArithOps.cpp
+++ b/mlir/lib/Dialect/Arith/IR/ArithOps.cpp
@@ -9,6 +9,7 @@
#include <cassert>
#include <cstdint>
#include <functional>
+#include <type_traits>
#include <utility>
#include "mlir/Dialect/Arith/IR/Arith.h"
@@ -1364,6 +1365,149 @@ void arith::SubFOp::getCanonicalizationPatterns(RewritePatternSet &patterns,
patterns.add<SubFOfNegZero>(context);
}
+namespace {
+
+// A helper for the conversion:
+//
+// trunc(extremum(ext(lhs), ext(rhs))) -> extremum(lhs, rhs)
+//
+// To make sure that type conversion won't lose information.
+//
+// For floating point conversion, we can't allow conversion between
+// -0 and +0. More importantly, we can't allow a conversion from
+// sNaN to qNaN. That changes semantics.
+static bool isLosslesslyConvertibleTo(const llvm::fltSemantics &from,
+ const llvm::fltSemantics &to) {
+ if (&from == &to)
+ return true;
+
+ if (!llvm::APFloatBase::isRepresentableBy(from, to))
+ return false;
+
+ if ((from.hasZero && !to.hasZero) ||
+ (from.hasSignedRepr && !to.hasSignedRepr))
+ return false;
+
+ // NegativeZero NaN encoding repurposes the negative-zero bit pattern, so a
+ // conversion to such a format cannot preserve a source negative zero.
+ bool fromHasSignedZero =
+ from.hasZero && from.hasSignedRepr &&
+ from.nanEncoding != llvm::fltNanEncoding::NegativeZero;
+ bool toHasSignedZero = to.hasZero && to.hasSignedRepr &&
+ to.nanEncoding != llvm::fltNanEncoding::NegativeZero;
+ if (fromHasSignedZero && !toHasSignedZero)
+ return false;
+
+ // isRepresentableBy compares the normalized exponent ranges. Also ensure
+ // that the smallest source value, which may be denormal, is represented
+ // exactly by the destination semantics.
+ llvm::APFloat smallestFrom = llvm::APFloat::getSmallest(from);
+ bool losesInfo = false;
+ (void)smallestFrom.convert(to, llvm::APFloat::rmNearestTiesToEven,
+ &losesInfo);
+ if (losesInfo)
+ return false;
+
+ if (from.nonFiniteBehavior == llvm::fltNonfiniteBehavior::FiniteOnly)
+ return true;
+
+ if (from.nonFiniteBehavior == llvm::fltNonfiniteBehavior::IEEE754) {
+ // Converting an IEEE signaling NaN to another semantics quiets it, so the
+ // original value cannot be recovered by converting it back.
+ return false;
+ }
+
+ // NanOnly formats have no signaling NaNs. IEEE semantics can represent
+ // their quiet NaNs; conversions between NanOnly formats are conservatively
+ // accepted only when they use the same NaN encoding.
+ if (to.nonFiniteBehavior == llvm::fltNonfiniteBehavior::IEEE754)
+ return true;
+ return to.nonFiniteBehavior == llvm::fltNonfiniteBehavior::NanOnly &&
+ from.nanEncoding == to.nanEncoding;
+}
+
+static bool
+isLosslesslyConvertibleToIgnoringNaNs(const llvm::fltSemantics &from,
+ const llvm::fltSemantics &to) {
+ llvm::fltSemantics finiteFrom = from;
+ llvm::fltSemantics finiteTo = to;
+ finiteFrom.nonFiniteBehavior = llvm::fltNonfiniteBehavior::FiniteOnly;
+ finiteTo.nonFiniteBehavior = llvm::fltNonfiniteBehavior::FiniteOnly;
+ return isLosslesslyConvertibleTo(finiteFrom, finiteTo);
+}
+
+/// Narrow an extremum whose operands were extended from the result type:
+///
+/// trunc(extremum(ext(lhs), ext(rhs))) -> extremum(lhs, rhs)
+///
+/// The concrete extension is part of the pattern so each extremum is only
+/// registered with extensions that preserve its ordering.
+/// For floating-point types, also require the extension to represent every
+/// source value exactly.
+template <typename TruncOp, typename ExtOp, typename ExtremumOp>
+struct NarrowExtremum final : OpRewritePattern<TruncOp> {
+ using OpRewritePattern<TruncOp>::OpRewritePattern;
+
+ LogicalResult matchAndRewrite(TruncOp truncOp,
+ PatternRewriter &rewriter) const override {
+ auto extremumOp = truncOp.getIn().template getDefiningOp<ExtremumOp>();
+ if (!extremumOp || !extremumOp->hasOneUse())
+ return failure();
+
+ auto lhsExt = extremumOp.getLhs().template getDefiningOp<ExtOp>();
+ auto rhsExt = extremumOp.getRhs().template getDefiningOp<ExtOp>();
+ if (!lhsExt || !rhsExt)
+ return failure();
+
+ Value lhs = lhsExt.getIn();
+ Value rhs = rhsExt.getIn();
+ Type narrowType = truncOp.getType();
+ if (lhs.getType() != narrowType || rhs.getType() != narrowType)
+ return failure();
+
+ // A floating-point extension is not necessarily lossless between arbitrary
+ // floating-point semantics, even when the destination has a larger bit
+ // width. In particular, it may lose the sign of zero or quiet a signaling
+ // NaN, either of which can change an extremum's result. `nnan` lets us
+ // disregard the latter, but all other source values must round-trip.
+ //
+ // See the discussion in https://github.com/llvm/llvm-project/pull/214658.
+ if (auto narrowFloatType =
+ dyn_cast<FloatType>(getElementTypeOrSelf(narrowType))) {
+ auto wideFloatType =
+ dyn_cast<FloatType>(getElementTypeOrSelf(extremumOp.getType()));
+ if (!wideFloatType)
+ return failure();
+
+ const llvm::fltSemantics &narrowSemantics =
+ narrowFloatType.getFloatSemantics();
+ const llvm::fltSemantics &wideSemantics =
+ wideFloatType.getFloatSemantics();
+ if (!isLosslesslyConvertibleTo(narrowSemantics, wideSemantics)) {
+ if constexpr (std::is_same_v<TruncOp, TruncFOp>) {
+ // TODO: We can tolerant qNaN here. But we don't have a mode
+ // to exclude sNaN. If we're able to do that, we can be more
+ // tolerant here.
+ if (!bitEnumContainsAll(extremumOp.getFastmath(),
+ FastMathFlags::nnan) ||
+ !isLosslesslyConvertibleToIgnoringNaNs(narrowSemantics,
+ wideSemantics))
+ return failure();
+ } else {
+ return failure();
+ }
+ }
+ }
+
+ rewriter.replaceOpWithNewOp<ExtremumOp>(truncOp, TypeRange{narrowType},
+ ValueRange{lhs, rhs},
+ extremumOp->getAttrs());
+ return success();
+ }
+};
+
+} // namespace
+
//===----------------------------------------------------------------------===//
// MaximumFOp
//===----------------------------------------------------------------------===//
@@ -1876,9 +2020,11 @@ bool arith::TruncIOp::areCastCompatible(TypeRange inputs, TypeRange outputs) {
void arith::TruncIOp::getCanonicalizationPatterns(RewritePatternSet &patterns,
MLIRContext *context) {
- patterns
- .add<TruncIExtSIToExtSI, TruncIExtUIToExtUI, TruncIShrSIToTrunciShrUI>(
- context);
+ patterns.add<NarrowExtremum<TruncIOp, ExtSIOp, MaxSIOp>,
+ NarrowExtremum<TruncIOp, ExtSIOp, MinSIOp>,
+ NarrowExtremum<TruncIOp, ExtUIOp, MaxUIOp>,
+ NarrowExtremum<TruncIOp, ExtUIOp, MinUIOp>, TruncIExtSIToExtSI,
+ TruncIExtUIToExtUI, TruncIShrSIToTrunciShrUI>(context);
}
LogicalResult arith::TruncIOp::verify() {
@@ -1898,10 +2044,10 @@ OpFoldResult arith::TruncFOp::fold(FoldAdaptor adaptor) {
auto srcType = cast<FloatType>(getElementTypeOrSelf(src.getType()));
auto intermediateType =
cast<FloatType>(getElementTypeOrSelf(extOp.getType()));
- // Check if the srcType is representable in the intermediateType.
- if (llvm::APFloatBase::isRepresentableBy(
- srcType.getFloatSemantics(),
- intermediateType.getFloatSemantics())) {
+ // Check whether every source value round-trips through the intermediate
+ // type, including signaling NaNs and signed zero.
+ if (isLosslesslyConvertibleTo(srcType.getFloatSemantics(),
+ intermediateType.getFloatSemantics())) {
// truncf(extf(a)) -> truncf(a)
if (srcType.getWidth() > resElemType.getWidth()) {
setOperand(src);
@@ -1932,7 +2078,11 @@ OpFoldResult arith::TruncFOp::fold(FoldAdaptor adaptor) {
void arith::TruncFOp::getCanonicalizationPatterns(RewritePatternSet &patterns,
MLIRContext *context) {
- patterns.add<TruncFSIToFPToSIToFP, TruncFUIToFPToUIToFP>(context);
+ patterns.add<NarrowExtremum<TruncFOp, ExtFOp, MaximumFOp>,
+ NarrowExtremum<TruncFOp, ExtFOp, MaxNumFOp>,
+ NarrowExtremum<TruncFOp, ExtFOp, MinimumFOp>,
+ NarrowExtremum<TruncFOp, ExtFOp, MinNumFOp>,
+ TruncFSIToFPToSIToFP, TruncFUIToFPToUIToFP>(context);
}
bool arith::TruncFOp::areCastCompatible(TypeRange inputs, TypeRange outputs) {
diff --git a/mlir/test/Dialect/Arith/canonicalize.mlir b/mlir/test/Dialect/Arith/canonicalize.mlir
index 0c06aa6e861a4..75352dadf04de 100644
--- a/mlir/test/Dialect/Arith/canonicalize.mlir
+++ b/mlir/test/Dialect/Arith/canonicalize.mlir
@@ -1121,8 +1121,9 @@ func.func @extFPVectorConstant() -> vector<2xf128> {
}
// CHECK-LABEL: @truncExtf
-// CHECK-NOT: truncf
-// CHECK: return %arg0
+// CHECK: %[[EXT:.*]] = arith.extf %arg0 : f32 to f64
+// CHECK: %[[TRUNC:.*]] = arith.truncf %[[EXT]] : f64 to f32
+// CHECK: return %[[TRUNC]]
func.func @truncExtf(%arg0: f32) -> f32 {
%extf = arith.extf %arg0 : f32 to f64
%trunc = arith.truncf %extf : f64 to f32
@@ -1130,8 +1131,9 @@ func.func @truncExtf(%arg0: f32) -> f32 {
}
// CHECK-LABEL: @truncExtf1
-// CHECK-NOT: truncf
-// CHECK: return %arg0
+// CHECK: %[[EXT:.*]] = arith.extf %arg0 : bf16 to f32
+// CHECK: %[[TRUNC:.*]] = arith.truncf %[[EXT]] : f32 to bf16
+// CHECK: return %[[TRUNC]]
func.func @truncExtf1(%arg0: bf16) -> bf16 {
%extf = arith.extf %arg0 : bf16 to f32
%trunc = arith.truncf %extf : f32 to bf16
@@ -1150,15 +1152,202 @@ func.func @truncExtf2(%arg0: bf16) -> f16 {
}
// CHECK-LABEL: @truncExtf3
-// CHECK: %[[ARG0:.+]]: f32
-// CHECK: %[[CST:.*]] = arith.truncf %[[ARG0:.+]] : f32 to f16
-// CHECK: return %[[CST:.*]]
+// CHECK: %[[EXT:.*]] = arith.extf %arg0 : f32 to f64
+// CHECK: %[[TRUNC:.*]] = arith.truncf %[[EXT]] : f64 to f16
+// CHECK: return %[[TRUNC]]
func.func @truncExtf3(%arg0: f32) -> f16 {
%extf = arith.extf %arg0 : f32 to f64
%truncf = arith.truncf %extf : f64 to f16
return %truncf : f16
}
+// CHECK-LABEL: @narrowExtremaOfExtf
+// CHECK-NOT: arith.extf
+// CHECK-NOT: arith.truncf
+// CHECK: %[[MAXIMUM:.*]] = arith.maximumf %arg0, %arg1 fastmath<nnan> : f16
+// CHECK: %[[MAXNUM:.*]] = arith.maxnumf %arg0, %arg1 fastmath<nnan> : f16
+// CHECK: %[[MINIMUM:.*]] = arith.minimumf %arg0, %arg1 fastmath<nnan> : f16
+// CHECK: %[[MINNUM:.*]] = arith.minnumf %arg0, %arg1 fastmath<nnan> : f16
+// CHECK: return %[[MAXIMUM]], %[[MAXNUM]], %[[MINIMUM]], %[[MINNUM]]
+func.func @narrowExtremaOfExtf(%arg0: f16, %arg1: f16)
+ -> (f16, f16, f16, f16) {
+ %lhs = arith.extf %arg0 : f16 to f32
+ %rhs = arith.extf %arg1 : f16 to f32
+ %maximum = arith.maximumf %lhs, %rhs fastmath<nnan> : f32
+ %maxnum = arith.maxnumf %lhs, %rhs fastmath<nnan> : f32
+ %minimum = arith.minimumf %lhs, %rhs fastmath<nnan> : f32
+ %minnum = arith.minnumf %lhs, %rhs fastmath<nnan> : f32
+ %maximumTrunc = arith.truncf %maximum : f32 to f16
+ %maxnumTrunc = arith.truncf %maxnum : f32 to f16
+ %minimumTrunc = arith.truncf %minimum : f32 to f16
+ %minnumTrunc = arith.truncf %minnum : f32 to f16
+ return %maximumTrunc, %maxnumTrunc, %minimumTrunc, %minnumTrunc
+ : f16, f16, f16, f16
+}
+
+// CHECK-LABEL: @narrowIntegerExtrema
+// CHECK-NOT: arith.extsi
+// CHECK-NOT: arith.extui
+// CHECK-NOT: arith.trunci
+// CHECK: %[[SMAX:.*]] = arith.maxsi %arg0, %arg1 : i8
+// CHECK: %[[SMIN:.*]] = arith.minsi %arg0, %arg1 : i8
+// CHECK: %[[UMAX:.*]] = arith.maxui %arg0, %arg1 : i8
+// CHECK: %[[UMIN:.*]] = arith.minui %arg0, %arg1 : i8
+// CHECK: return %[[SMAX]], %[[SMIN]], %[[UMAX]], %[[UMIN]]
+func.func @narrowIntegerExtrema(%arg0: i8, %arg1: i8)
+ -> (i8, i8, i8, i8) {
+ %slhs = arith.extsi %arg0 : i8 to i32
+ %srhs = arith.extsi %arg1 : i8 to i32
+ %ulhs = arith.extui %arg0 : i8 to i32
+ %urhs = arith.extui %arg1 : i8 to i32
+ %smax = arith.maxsi %slhs, %srhs : i32
+ %smin = arith.minsi %slhs, %srhs : i32
+ %umax = arith.maxui %ulhs, %urhs : i32
+ %umin = arith.minui %ulhs, %urhs : i32
+ %smaxTrunc = arith.trunci %smax : i32 to i8
+ %sminTrunc = arith.trunci %smin : i32 to i8
+ %umaxTrunc = arith.trunci %umax : i32 to i8
+ %uminTrunc = arith.trunci %umin : i32 to i8
+ return %smaxTrunc, %sminTrunc, %umaxTrunc, %uminTrunc : i8, i8, i8, i8
+}
+
+// CHECK-LABEL: @doNotNarrowIntegerExtremumWithWideUse
+// CHECK: %[[LHS:.*]] = arith.extsi %arg0 : i8 to i32
+// CHECK: %[[RHS:.*]] = arith.extsi %arg1 : i8 to i32
+// CHECK: %[[MAX:.*]] = arith.maxsi %[[LHS]], %[[RHS]] : i32
+// CHECK: %[[TRUNC:.*]] = arith.trunci %[[MAX]] : i32 to i8
+// CHECK: return %[[TRUNC]], %[[MAX]]
+func.func @doNotNarrowIntegerExtremumWithWideUse(%arg0: i8, %arg1: i8)
+ -> (i8, i32) {
+ %lhs = arith.extsi %arg0 : i8 to i32
+ %rhs = arith.extsi %arg1 : i8 to i32
+ %max = arith.maxsi %lhs, %rhs : i32
+ %trunc = arith.trunci %max : i32 to i8
+ return %trunc, %max : i8, i32
+}
+
+// Zero extension does not preserve signed ordering across the sign boundary.
+// CHECK-LABEL: @doNotNarrowSignedExtremumOfZeroExtension
+// CHECK: %[[LHS:.*]] = arith.extui %arg0 : i8 to i32
+// CHECK: %[[RHS:.*]] = arith.extui %arg1 : i8 to i32
+// CHECK: %[[MAX:.*]] = arith.maxsi %[[LHS]], %[[RHS]] : i32
+// CHECK: %[[TRUNC:.*]] = arith.trunci %[[MAX]] : i32 to i8
+// CHECK: return %[[TRUNC]]
+func.func @doNotNarrowSignedExtremumOfZeroExtension(
+ %arg0: i8, %arg1: i8) -> i8 {
+ %lhs = arith.extui %arg0 : i8 to i32
+ %rhs = arith.extui %arg1 : i8 to i32
+ %max = arith.maxsi %lhs, %rhs : i32
+ %trunc = arith.trunci %max : i32 to i8
+ return %trunc : i8
+}
+
+// CHECK-LABEL: @doNotNarrowExtremumWithWideUse
+// CHECK: %[[LHS:.*]] = arith.extf %arg0 : f16 to f32
+// CHECK: %[[RHS:.*]] = arith.extf %arg1 : f16 to f32
+// CHECK: %[[MAXIMUM:.*]] = arith.maximumf %[[LHS]], %[[RHS]] : f32
+// CHECK: %[[TRUNC:.*]] = arith.truncf %[[MAXIMUM]] : f32 to f16
+// CHECK: return %[[TRUNC]], %[[MAXIMUM]]
+func.func @doNotNarrowExtremumWithWideUse(%arg0: f16, %arg1: f16)
+ -> (f16, f32) {
+ %lhs = arith.extf %arg0 : f16 to f32
+ %rhs = arith.extf %arg1 : f16 to f32
+ %maximum = arith.maximumf %lhs, %rhs : f32
+ %trunc = arith.truncf %maximum : f32 to f16
+ return %trunc, %maximum : f16, f32
+}
+
+// CHECK-LABEL: @doNotNarrowExtremumThroughInexactExtension
+// CHECK: %[[LHS:.*]] = arith.extf %arg0 : f8E8M0FNU to f16
+// CHECK: %[[RHS:.*]] = arith.extf %arg1 : f8E8M0FNU to f16
+// CHECK: %[[MAXIMUM:.*]] = arith.maximumf %[[LHS]], %[[RHS]] : f16
+// CHECK: %[[TRUNC:.*]] = arith.truncf %[[MAXIMUM]] : f16 to f8E8M0FNU
+// CHECK: return %[[TRUNC]]
+func.func @doNotNarrowExtremumThroughInexactExtension(
+ %arg0: f8E8M0FNU, %arg1: f8E8M0FNU) -> f8E8M0FNU {
+ %lhs = arith.extf %arg0 : f8E8M0FNU to f16
+ %rhs = arith.extf %arg1 : f8E8M0FNU to f16
+ %maximum = arith.maximumf %lhs, %rhs : f16
+ %trunc = arith.truncf %maximum : f16 to f8E8M0FNU
+ return %trunc : f8E8M0FNU
+}
+
+// f8E4M3FNUZ has no negative zero. For two f4E2M1FN negative-zero arguments
+// (bits 0x8), the wide computation returns +0 (bits 0x0) while the narrow
+// computation returns -0 (bits 0x8).
+// CHECK-LABEL: @doNotNarrowExtremumThroughFNUZ
+// CHECK: %[[LHS:.*]] = arith.extf %arg0 : f4E2M1FN to f8E4M3FNUZ
+// CHECK: %[[RHS:.*]] = arith.extf %arg1 : f4E2M1FN to f8E4M3FNUZ
+// CHECK: %[[MAXIMUM:.*]] = arith.maximumf %[[LHS]], %[[RHS]] : f8E4M3FNUZ
+// CHECK: %[[TRUNC:.*]] = arith.truncf %[[MAXIMUM]] : f8E4M3FNUZ to f4E2M1FN
+// CHECK: return %[[TRUNC]]
+func.func @doNotNarrowExtremumThroughFNUZ(
+ %arg0: f4E2M1FN, %arg1: f4E2M1FN) -> f4E2M1FN {
+ %lhs = arith.extf %arg0 : f4E2M1FN to f8E4M3FNUZ
+ %rhs = arith.extf %arg1 : f4E2M1FN to f8E4M3FNUZ
+ %maximum = arith.maximumf %lhs, %rhs : f8E4M3FNUZ
+ %trunc = arith.truncf %maximum : f8E4M3FNUZ to f4E2M1FN
+ return %trunc : f4E2M1FN
+}
+
+// Extending an f8E5M2 signaling NaN (bits 0x7d) to f16 quiets it. With 1.0
+// (bits 0x3c) as the other operand, the wide maxnum returns 1.0 while the
+// narrow operation returns the quieted NaN (bits 0x7f).
+// CHECK-LABEL: @doNotNarrowMaxNumThroughSNaNQuieting
+// CHECK: %[[LHS:.*]] = arith.extf %arg0 : f8E5M2 to f16
+// CHECK: %[[RHS:.*]] = arith.extf %arg1 : f8E5M2 to f16
+// CHECK: %[[MAXNUM:.*]] = arith.maxnumf %[[LHS]], %[[RHS]] : f16
+// CHECK: %[[MAXTRUNC:.*]] = arith.truncf %[[MAXNUM]] : f16 to f8E5M2
+// CHECK: return %[[MAXTRUNC]]
+func.func @doNotNarrowMaxNumThroughSNaNQuieting(
+ %arg0: f8E5M2, %arg1: f8E5M2) -> f8E5M2 {
+ %lhs = arith.extf %arg0 : f8E5M2 to f16
+ %rhs = arith.extf %arg1 : f8E5M2 to f16
+ %maxnum = arith.maxnumf %lhs, %rhs : f16
+ %trunc = arith.truncf %maxnum : f16 to f8E5M2
+ return %trunc : f8E5M2
+}
+
+// Extending an f8E5M2 signaling NaN (bits 0x7d) to f16 quiets it. With 1.0
+// (bits 0x3c) as the other operand, the wide minnum returns 1.0 while the
+// narrow operation returns the quieted NaN (bits 0x7f).
+// CHECK-LABEL: @doNotNarrowMinNumThroughSNaNQuieting
+// CHECK: %[[LHS:.*]] = arith.extf %arg0 : f8E5M2 to f16
+// CHECK: %[[RHS:.*]] = arith.extf %arg1 : f8E5M2 to f16
+// CHECK: %[[MINNUM:.*]] = arith.minnumf %[[LHS]], %[[RHS]] : f16
+// CHECK: %[[MINTRUNC:.*]] = arith.truncf %[[MINNUM]] : f16 to f8E5M2
+// CHECK: return %[[MINTRUNC]]
+func.func @doNotNarrowMinNumThroughSNaNQuieting(
+ %arg0: f8E5M2, %arg1: f8E5M2) -> f8E5M2 {
+ %lhs = arith.extf %arg0 : f8E5M2 to f16
+ %rhs = arith.extf %arg1 : f8E5M2 to f16
+ %minnum = arith.minnumf %lhs, %rhs : f16
+ %trunc = arith.truncf %minnum : f16 to f8E5M2
+ return %trunc : f8E5M2
+}
+
+// The same representational differences also prevent folding the bare
+// truncf(extf(x)) round trips.
+// CHECK-LABEL: @doNotFoldFNUZRoundTrip
+// CHECK: %[[EXT:.*]] = arith.extf %arg0 : f4E2M1FN to f8E4M3FNUZ
+// CHECK: %[[TRUNC:.*]] = arith.truncf %[[EXT]] : f8E4M3FNUZ to f4E2M1FN
+// CHECK: return %[[TRUNC]]
+func.func @doNotFoldFNUZRoundTrip(%arg0: f4E2M1FN) -> f4E2M1FN {
+ %ext = arith.extf %arg0 : f4E2M1FN to f8E4M3FNUZ
+ %trunc = arith.truncf %ext : f8E4M3FNUZ to f4E2M1FN
+ return %trunc : f4E2M1FN
+}
+
+// CHECK-LABEL: @doNotFoldSNaNRoundTrip
+// CHECK: %[[EXT:.*]] = arith.extf %arg0 : f8E5M2 to f16
+// CHECK: %[[TRUNC:.*]] = arith.truncf %[[EXT]] : f16 to f8E5M2
+// CHECK: return %[[TRUNC]]
+func.func @doNotFoldSNaNRoundTrip(%arg0: f8E5M2) -> f8E5M2 {
+ %ext = arith.extf %arg0 : f8E5M2 to f16
+ %trunc = arith.truncf %ext : f16 to f8E5M2
+ return %trunc : f8E5M2
+}
+
// CHECK-LABEL: @truncSitofp
// CHECK: %[[SITOFP:.*]] = arith.sitofp %[[ARG0:.*]] : i32 to f32
// CHECK-NOT: truncf
More information about the Mlir-commits
mailing list