[flang-commits] [flang] [flang] ieee intrinsics shall preserve NaN semantics. (PR #215928)
Valery Dmitriev via flang-commits
flang-commits at lists.llvm.org
Wed Aug 12 17:14:06 PDT 2026
https://github.com/valerydmit created https://github.com/llvm/llvm-project/pull/215928
The following motivating test case:
```fortran
Program repro
Use Ieee_Arithmetic
Real nan
nan = Ieee_Value(nan,Ieee_Quiet_NaN)
Call test(1.0,nan)
Call test(nan,1.0)
Call test(nan,-1.0)
Contains
Subroutine test(a,b)
Real x
x = Ieee_Min_Num_Mag(a,b)
Print *,'x=', x
End Subroutine test
End Program
```
even with -Ofast [-O3 -ffast-math] is not expected to print NaN, but it does. That happens basically due to attaching fast math flags to the intrinsic lowering code that set incorrect assumptions about operand values and lead to improper optimizations. Hence the solution is exactly to strip the flags when lowering ieee intrinsics. The patch strips all the fast math flags except 'contract' to minimize impact on some existing tests for a few ieee intrinsics. The flag is considered benign.
Assisted-by: Claude
Assisted-by: Grok
>From 0d3581a39ff1a3ad436193e9b89e5ffff2fa3eba Mon Sep 17 00:00:00 2001
From: Valery Dmitriev <valeryd at nvidia.com>
Date: Mon, 3 Aug 2026 08:53:52 -0700
Subject: [PATCH] [flang] ieee intrinsics shall preserve NaN semantics.
The following motivating test case:
```fortran
Program repro
Use Ieee_Arithmetic
Real nan
nan = Ieee_Value(nan,Ieee_Quiet_NaN)
Call test(1.0,nan)
Call test(nan,1.0)
Call test(nan,-1.0)
Contains
Subroutine test(a,b)
Real x
x = Ieee_Min_Num_Mag(a,b)
Print *,'x=', x
End Subroutine test
End Program
```
even with -Ofast [-O3 -ffast-math] is not expected to print NaN, but it does.
That happens basically due to attaching fast math flags to the intrinsic lowering code
that set incorrect assumptions about operand values and lead to improper optimizations.
Hence the solution is exactly to strip the flags when lowering ieee_* intrinsics.
The patch strips all the fast math flags except 'contract' to minimize impact on some
existing tests for a few ieee intrinsics. The flag is considered benign.
Assisted-by: Claude
Assisted-by: Grok
---
.../flang/Optimizer/Builder/FIRBuilder.h | 17 ++++
flang/lib/Optimizer/Builder/IntrinsicCall.cpp | 39 +++++++--
.../test/Lower/Intrinsics/ieee_fast_math.f90 | 84 +++++++++++++++++++
3 files changed, 132 insertions(+), 8 deletions(-)
create mode 100644 flang/test/Lower/Intrinsics/ieee_fast_math.f90
diff --git a/flang/include/flang/Optimizer/Builder/FIRBuilder.h b/flang/include/flang/Optimizer/Builder/FIRBuilder.h
index c5db7f56788ee..fa695a73e6c54 100644
--- a/flang/include/flang/Optimizer/Builder/FIRBuilder.h
+++ b/flang/include/flang/Optimizer/Builder/FIRBuilder.h
@@ -617,6 +617,23 @@ class FirOpBuilder : public mlir::OpBuilder, public mlir::OpBuilder::Listener {
return fmfString;
}
+ /// RAII helper to set FastMathFlags for a scope and restore the previous
+ /// value on destruction.
+ class FastMathFlagGuard {
+ public:
+ FastMathFlagGuard(FirOpBuilder &builder, mlir::arith::FastMathFlags flags)
+ : builder{builder}, savedFlags{builder.getFastMathFlags()} {
+ builder.setFastMathFlags(flags);
+ }
+ FastMathFlagGuard(const FastMathFlagGuard &) = delete;
+ FastMathFlagGuard &operator=(const FastMathFlagGuard &) = delete;
+ ~FastMathFlagGuard() { builder.setFastMathFlags(savedFlags); }
+
+ private:
+ FirOpBuilder &builder;
+ mlir::arith::FastMathFlags savedFlags;
+ };
+
/// Set default IntegerOverflowFlags value for all operations
/// supporting mlir::arith::IntegerOverflowFlagsAttr that will be created
/// by this builder.
diff --git a/flang/lib/Optimizer/Builder/IntrinsicCall.cpp b/flang/lib/Optimizer/Builder/IntrinsicCall.cpp
index f349d41888780..726319b11a720 100644
--- a/flang/lib/Optimizer/Builder/IntrinsicCall.cpp
+++ b/flang/lib/Optimizer/Builder/IntrinsicCall.cpp
@@ -1879,9 +1879,27 @@ mlir::Value toValue(const fir::ExtendedValue &val, fir::FirOpBuilder &builder,
// IntrinsicLibrary
//===----------------------------------------------------------------------===//
+static bool isIeeeIntrinsic(llvm::StringRef name) {
+ return name.starts_with("ieee_");
+}
+
static bool isIntrinsicModuleProcedure(llvm::StringRef name) {
return name.starts_with("c_") || name.starts_with("compiler_") ||
- name.starts_with("ieee_") || name.starts_with("__ppc_");
+ isIeeeIntrinsic(name) || name.starts_with("__ppc_");
+}
+
+/// IEEE_ARITHMETIC and IEEE_EXCEPTIONS procedures are defined in terms of the
+/// IEEE 754 operations they name. Their expansions encode NaN, infinity, and
+/// signed zero behavior explicitly, so relaxed floating-point assumptions from
+/// the surrounding code must not reach the operations that implement them.
+/// Contraction is kept: none of these expansions contain contractable
+/// arithmetic.
+static mlir::arith::FastMathFlags
+fastMathFlagsForIntrinsic(llvm::StringRef name,
+ mlir::arith::FastMathFlags flags) {
+ if (!isIeeeIntrinsic(name))
+ return flags;
+ return flags & mlir::arith::FastMathFlags::contract;
}
static bool isCoarrayIntrinsic(llvm::StringRef name) {
@@ -2095,13 +2113,16 @@ static std::pair<fir::ExtendedValue, bool> genIntrinsicCallHelper(
llvm::ArrayRef<fir::ExtendedValue> args, IntrinsicLibrary &lib) {
assert(handler && "must be set");
bool outline = handler->outline || outlineAllIntrinsics;
- return {Fortran::common::visit(
- [&](auto &generator) -> fir::ExtendedValue {
- return invokeHandler(generator, *handler, resultType, args,
- outline, lib);
- },
- handler->generator),
- lib.resultMustBeFreed};
+ fir::FirOpBuilder::FastMathFlagGuard fmfGuard(
+ lib.builder,
+ fastMathFlagsForIntrinsic(handler->name, lib.builder.getFastMathFlags()));
+ auto result = Fortran::common::visit(
+ [&](auto &generator) -> fir::ExtendedValue {
+ return invokeHandler(generator, *handler, resultType, args, outline,
+ lib);
+ },
+ handler->generator);
+ return {result, lib.resultMustBeFreed};
}
static IntrinsicLibrary::RuntimeCallGenerator getRuntimeCallGeneratorHelper(
@@ -2117,6 +2138,8 @@ static std::pair<fir::ExtendedValue, bool> genIntrinsicCallHelper(
fir::FirOpBuilder &builder = lib.builder;
mlir::Location loc = lib.loc;
llvm::StringRef name = range.first->key;
+ fir::FirOpBuilder::FastMathFlagGuard fmfGuard(
+ builder, fastMathFlagsForIntrinsic(name, builder.getFastMathFlags()));
// FIXME: using toValue to get the type won't work with array arguments.
llvm::SmallVector<mlir::Value> mlirArgs;
for (const fir::ExtendedValue &extendedVal : args) {
diff --git a/flang/test/Lower/Intrinsics/ieee_fast_math.f90 b/flang/test/Lower/Intrinsics/ieee_fast_math.f90
new file mode 100644
index 0000000000000..dbaa2e633595e
--- /dev/null
+++ b/flang/test/Lower/Intrinsics/ieee_fast_math.f90
@@ -0,0 +1,84 @@
+! The IEEE_ARITHMETIC and IEEE_EXCEPTIONS procedures are expansions that encode
+! NaN, infinity, and signed zero behavior explicitly. The relaxed floating point
+! assumptions requested for the surrounding code must not reach the operations
+! implementing them. Contraction is the one flag that still applies, since none
+! of these expansions contain contractable arithmetic.
+
+! RUN: %flang_fc1 -emit-fir -ffast-math %s -o %t.fir
+! RUN: FileCheck %s --input-file=%t.fir
+! RUN: FileCheck %s --check-prefix=NOFAST --input-file=%t.fir
+! RUN: %flang_fc1 -emit-fir -menable-no-nans -menable-no-infs -fno-signed-zeros \
+! RUN: -mreassociate -fapprox-func -freciprocal-math -ffp-contract=off %s -o - \
+! RUN: | FileCheck %s --check-prefix=NOFMF
+! RUN: %flang_fc1 -emit-llvm -O3 -ffast-math %s -o - | FileCheck %s --check-prefix=LLVM
+
+! Ordinary arithmetic in the same file still gets everything that was asked for.
+! CHECK-LABEL: func.func @_QPplain_arith(
+subroutine plain_arith(x, y, r)
+ real(4) :: x, y, r
+ ! CHECK: arith.mulf %{{[^ ]*}}, %{{[^ ]*}} fastmath<fast> : f32
+ r = x * y
+end subroutine
+
+! CHECK-LABEL: func.func @_QPmin_num_mag(
+! NOFAST-LABEL: func.func @_QPmin_num_mag(
+! NOFAST-NOT: fastmath<{{.*(nnan|ninf|nsz|arcp|afn|reassoc|fast).*}}>
+! NOFMF-LABEL: func.func @_QPmin_num_mag(
+! NOFMF-NOT: fastmath
+! LLVM-LABEL: define {{.*}} @min_num_mag_(
+subroutine min_num_mag(x, y, r)
+ use ieee_arithmetic
+ real(4) :: x, y, r
+ ! CHECK: %[[X:.*]] = fir.load %{{[^ ]*}} : !fir.ref<f32>
+ ! CHECK: %[[Y:.*]] = fir.load %{{[^ ]*}} : !fir.ref<f32>
+ ! CHECK: %[[AX:.*]] = math.copysign %[[X]], %{{[^ ]*}} fastmath<contract> : f32
+ ! CHECK: %[[AY:.*]] = math.copysign %[[Y]], %{{[^ ]*}} fastmath<contract> : f32
+ ! CHECK: arith.cmpf olt, %[[AX]], %[[AY]] fastmath<contract> : f32
+ ! CHECK: arith.cmpf ogt, %[[AX]], %[[AY]] fastmath<contract> : f32
+ ! CHECK: arith.cmpf oeq, %[[AX]], %[[AY]] fastmath<contract> : f32
+ ! These two ordered compares select the non-NaN operand. Under nnan they fold
+ ! to true and the NaN arm of the expansion disappears.
+ ! CHECK: arith.cmpf ord, %[[X]], %[[X]] fastmath<contract> : f32
+ ! CHECK: arith.cmpf ord, %[[Y]], %[[Y]] fastmath<contract> : f32
+ ! LLVM-NOT: fcmp {{.*}}fast
+ ! LLVM: fcmp contract olt
+ ! LLVM: fcmp contract ogt
+ ! LLVM: fcmp contract oeq
+ ! LLVM: fcmp contract ord
+ ! LLVM: fcmp contract ord
+ r = ieee_min_num_mag(x, y)
+end subroutine
+
+! CHECK-LABEL: func.func @_QPunordered(
+! NOFAST-LABEL: func.func @_QPunordered(
+! NOFAST-NOT: fastmath<{{.*(nnan|ninf|nsz|arcp|afn|reassoc|fast).*}}>
+! NOFMF-LABEL: func.func @_QPunordered(
+! NOFMF-NOT: fastmath
+! LLVM-LABEL: define {{.*}} @unordered_(
+subroutine unordered(x, y, l)
+ use ieee_arithmetic
+ real(4) :: x, y
+ logical :: l
+ ! Under nnan this unordered compare folds to false.
+ ! CHECK: arith.cmpf uno, %{{[^ ]*}}, %{{[^ ]*}} fastmath<contract> : f32
+ ! LLVM: fcmp contract uno
+ l = ieee_unordered(x, y)
+end subroutine
+
+! CHECK-LABEL: func.func @_QPrem_and_rint(
+! NOFAST-LABEL: func.func @_QPrem_and_rint(
+! NOFAST-NOT: fastmath<{{.*(nnan|ninf|nsz|arcp|afn|reassoc|fast).*}}>
+! NOFMF-LABEL: func.func @_QPrem_and_rint(
+! NOFMF-NOT: fastmath
+subroutine rem_and_rint(x, y, r, s)
+ use ieee_arithmetic
+ real(4) :: x, y, r, s
+ ! An afn call to remainderf would be free to use an approximate variant.
+ ! CHECK: fir.call @remainderf(%{{[^ ]*}}, %{{[^ ]*}}) fastmath<contract> : (f32, f32) -> f32
+ r = ieee_rem(x, y)
+ ! CHECK: %[[RINT:.*]] = fir.call @llvm.nearbyint.f32(%[[ARG:[^ ]*]]) fastmath<contract> : (f32) -> f32
+ ! CHECK: arith.cmpf one, %[[ARG]], %[[RINT]] fastmath<contract> : f32
+ s = ieee_rint(x)
+end subroutine
+! NOFAST: return
+! NOFMF: return
More information about the flang-commits
mailing list