[flang-commits] [flang] [flang][PPC] Implement ieee_set_flag for Linux (PR #224039)
Kelvin Li via flang-commits
flang-commits at lists.llvm.org
Wed Sep 16 07:56:45 PDT 2026
https://github.com/kkwli created https://github.com/llvm/llvm-project/pull/224039
This patch implements the Linux PPC specific lowering for `ieee_set_flag`.
Calling `feraiseexcept()` may deliver `SIGFPE` when the corresponding floating-point exception trap is enabled. To avoid this, the lowering updates the FPSCR exception status bits directly using `llvm.ppc.readflm` and `llvm.ppc.setflm`.
Assisted-By: IBM Bob
>From 9af6e96ad8d101a6ea33ef909a2fdb79e4a04c6b Mon Sep 17 00:00:00 2001
From: Kelvin Li <kli at ca.ibm.com>
Date: Tue, 25 Aug 2026 16:25:37 -0400
Subject: [PATCH] [flang][PPC] Implement ieee_set_flag for Linux
This patch implements the Linux PPC specific lowering for `ieee_set_flag`.
Calling `feraiseexcept()` may deliver `SIGFPE` when the corresponding
floating-point exception trap is enabled. To avoid this, the lowering
updates the FPSCR exception status bits directly using
`llvm.ppc.readflm` and `llvm.ppc.setflm`.
Assisted-By: IBM Bob
---
flang/lib/Optimizer/Builder/IntrinsicCall.cpp | 164 +++++++++++++++++-
flang/test/Lower/Intrinsics/ieee_flag.f90 | 1 +
flang/test/Lower/Intrinsics/ieee_logb.f90 | 1 +
flang/test/Lower/Intrinsics/ieee_max_min.f90 | 1 +
.../Lower/Intrinsics/ieee_set_flag_ppc.f90 | 90 ++++++++++
5 files changed, 251 insertions(+), 6 deletions(-)
create mode 100644 flang/test/Lower/Intrinsics/ieee_set_flag_ppc.f90
diff --git a/flang/lib/Optimizer/Builder/IntrinsicCall.cpp b/flang/lib/Optimizer/Builder/IntrinsicCall.cpp
index a139af9a29e57..a4e5e9991e503 100644
--- a/flang/lib/Optimizer/Builder/IntrinsicCall.cpp
+++ b/flang/lib/Optimizer/Builder/IntrinsicCall.cpp
@@ -6023,23 +6023,175 @@ void IntrinsicLibrary::genIeeeSetFlagOrHaltingMode(
llvm::ArrayRef<fir::ExtendedValue> args) {
// IEEE_SET_FLAG: Set an exception FLAG to a FLAG_VALUE.
// IEEE_SET_HALTING: Set an exception halting mode FLAG to a HALTING value.
+ //
+ // On Linux PPC, feraiseexcept may deliver SIGFPE when the corresponding
+ // exception trap is enabled, including under PR_FP_EXC_PRECISE. Update
+ // the FPSCR exception-status bits directly with mffs/mtfsf.
assert(args.size() == 2);
mlir::Type i1Ty = builder.getI1Type();
mlir::Type i32Ty = builder.getIntegerType(32);
auto [fieldRef, ignore] = getFieldRef(builder, loc, getBase(args[0]));
mlir::Value field = fir::LoadOp::create(builder, loc, fieldRef);
- mlir::Value except = fir::runtime::genMapExcept(
- builder, loc, fir::ConvertOp::create(builder, loc, i32Ty, field));
+ mlir::Value fieldVal = fir::ConvertOp::create(builder, loc, i32Ty, field);
+
+ llvm::Triple triple = fir::getTargetTriple(builder.getModule());
+ const bool isLinuxPPC = triple.isOSLinux() && triple.isPPC();
+
+ auto getExcept = [&]() -> mlir::Value {
+ return fir::runtime::genMapExcept(builder, loc, fieldVal);
+ };
+
+ // Inline OR/AND masks for FPSCR sticky bits (lower 32 bits of mffs output).
+ //
+ // The Fortran ieee_flag_type internal encoding (magic-numbers.h) and the
+ // PPC FPSCR sticky bit positions are both distinct from the fenv.h FE_*
+ // values used by feraiseexcept/feclearexcept. There is no libm call that
+ // writes raw FPSCR sticky bits without risking SIGFPE when trapping is
+ // armed (PR_FP_EXC_PRECISE). The OR/AND masks are computed directly from
+ // the Fortran flag encoding here.
+ //
+ // Fortran encoding (magic-numbers.h):
+ // IEEE_INVALID=1, IEEE_DENORM=2, IEEE_DIVIDE_BY_ZERO=4,
+ // IEEE_OVERFLOW=8, IEEE_UNDERFLOW=16, IEEE_INEXACT=32
+ //
+ // FPSCR sticky-bit positions (lower 32 of mffs):
+ // FP_INVALID summary + VXSOFT = 0x20000400 (IEEE_INVALID)
+ // FP_OVERFLOW = 0x10000000 (IEEE_OVERFLOW)
+ // FP_UNDERFLOW = 0x08000000 (IEEE_UNDERFLOW)
+ // FP_DIV_BY_ZERO = 0x04000000 (IEEE_DIVIDE_BY_ZERO)
+ // FP_INEXACT = 0x02000000 (IEEE_INEXACT)
+ // IEEE_DENORM = 0 (no PPC sticky bit)
+ //
+ // Clear mask for IEEE_INVALID is wider (0x21f80700) to wipe the summary bit
+ // and all detail bits (VXSNAN, VXISI, VXIDI, VXZDZ, VXIMZ, VXVC, VXSOFT,
+ // VXSQRT, VXCVI); clearing only VXSOFT would leave the summary bit set.
+
+ // Compute the OR-mask to SET the sticky bits for one exception flag.
+ auto makePPCStickySetMask = [&](mlir::Value excepts) -> mlir::Value {
+ // Test each Fortran flag bit and accumulate the corresponding FPSCR bits.
+ // Bits are ORed together; unused (IEEE_DENORM) contributes 0.
+ auto bit = [&](int flagBit, uint32_t fpscrBits) -> mlir::Value {
+ mlir::Value test = mlir::arith::AndIOp::create(
+ builder, loc, excepts,
+ builder.createIntegerConstant(loc, i32Ty, flagBit));
+ mlir::Value nonzero = mlir::arith::CmpIOp::create(
+ builder, loc, mlir::arith::CmpIPredicate::ne, test,
+ builder.createIntegerConstant(loc, i32Ty, 0));
+ return mlir::arith::SelectOp::create(
+ builder, loc, nonzero,
+ builder.createIntegerConstant(loc, i32Ty,
+ static_cast<int32_t>(fpscrBits)),
+ builder.createIntegerConstant(loc, i32Ty, 0));
+ };
+ mlir::Value mask = bit(1, 0x20000400u); // IEEE_INVALID
+ mask = mlir::arith::OrIOp::create(
+ builder, loc, mask, bit(4, 0x04000000u)); // IEEE_DIVIDE_BY_ZERO
+ mask = mlir::arith::OrIOp::create(builder, loc, mask,
+ bit(8, 0x10000000u)); // IEEE_OVERFLOW
+ mask = mlir::arith::OrIOp::create(builder, loc, mask,
+ bit(16, 0x08000000u)); // IEEE_UNDERFLOW
+ mask = mlir::arith::OrIOp::create(builder, loc, mask,
+ bit(32, 0x02000000u)); // IEEE_INEXACT
+ return mask;
+ };
+
+ // Compute the AND-mask to CLEAR the sticky bits for one exception flag.
+ // Returns NOT(status_bits); caller ANDs this into FPSCR lower-32.
+ auto makePPCStickyClearMask = [&](mlir::Value excepts) -> mlir::Value {
+ auto bit = [&](int flagBit, uint32_t statusBits) -> mlir::Value {
+ mlir::Value test = mlir::arith::AndIOp::create(
+ builder, loc, excepts,
+ builder.createIntegerConstant(loc, i32Ty, flagBit));
+ mlir::Value nonzero = mlir::arith::CmpIOp::create(
+ builder, loc, mlir::arith::CmpIPredicate::ne, test,
+ builder.createIntegerConstant(loc, i32Ty, 0));
+ return mlir::arith::SelectOp::create(
+ builder, loc, nonzero,
+ builder.createIntegerConstant(loc, i32Ty,
+ static_cast<int32_t>(statusBits)),
+ builder.createIntegerConstant(loc, i32Ty, 0));
+ };
+ // IEEE_INVALID: clear summary + all detail bits (VXSNAN...VXCVI).
+ mlir::Value status = bit(1, 0x21f80700u);
+ status = mlir::arith::OrIOp::create(
+ builder, loc, status, bit(4, 0x04000000u)); // IEEE_DIVIDE_BY_ZERO
+ status = mlir::arith::OrIOp::create(builder, loc, status,
+ bit(8, 0x10000000u)); // IEEE_OVERFLOW
+ status = mlir::arith::OrIOp::create(builder, loc, status,
+ bit(16, 0x08000000u)); // IEEE_UNDERFLOW
+ status = mlir::arith::OrIOp::create(builder, loc, status,
+ bit(32, 0x02000000u)); // IEEE_INEXACT
+ // Return NOT(status) - caller ANDs this into FPSCR to clear the bits.
+ return mlir::arith::XOrIOp::create(
+ builder, loc, status, builder.createIntegerConstant(loc, i32Ty, ~0u));
+ };
+
+ // Emit readflm/binary-op/setflm as a single RMW on the FPSCR.
+ // doOr=true: fpscr |= mask32 (set sticky bit via OR)
+ // doOr=false: fpscr &= (mask32 | upper32ones) (clear sticky bit via AND;
+ // mask32 from makePPCStickyClearMask holds NOT(status) in bits[31:0];
+ // ORing in upper32ones ensures the AND leaves bits[63:32] untouched).
+ auto emitPPCFpscrRMW = [&](mlir::Value mask32, bool doOr) {
+ mlir::Type i64Ty = builder.getIntegerType(64);
+ mlir::Type f64Ty = builder.getF64Type();
+ mlir::func::FuncOp readFlm = fir::factory::getLlvmPpcReadflm(builder);
+ mlir::func::FuncOp setFlm = fir::factory::getLlvmPpcSetflm(builder);
+ mlir::Value fpscr = fir::CallOp::create(builder, loc, readFlm).getResult(0);
+ mlir::Value fpscr64 =
+ mlir::arith::BitcastOp::create(builder, loc, i64Ty, fpscr);
+ mlir::Value mask64 = builder.createConvert(loc, i64Ty, mask32);
+ mlir::Value newFpscr64;
+ if (doOr) {
+ newFpscr64 = mlir::arith::OrIOp::create(builder, loc, fpscr64, mask64);
+ } else {
+ // Zero-extend mask32 to 64 bits, then OR in the upper 32 ones so that
+ // AND only clears the intended lower-32 sticky bits.
+ mlir::Value upper32ones = builder.createIntegerConstant(
+ loc, i64Ty, static_cast<int64_t>(0xFFFFFFFF00000000LL));
+ mask64 = mlir::arith::OrIOp::create(builder, loc, mask64, upper32ones);
+ newFpscr64 = mlir::arith::AndIOp::create(builder, loc, fpscr64, mask64);
+ }
+ mlir::Value newFpscr =
+ mlir::arith::BitcastOp::create(builder, loc, f64Ty, newFpscr64);
+ fir::CallOp::create(builder, loc, setFlm, newFpscr);
+ };
+
+ mlir::Value except = (isLinuxPPC && isFlag) ? mlir::Value{} : getExcept();
+
auto ifOp = fir::IfOp::create(
builder, loc,
fir::ConvertOp::create(builder, loc, i1Ty, getBase(args[1])),
/*withElseRegion=*/true);
+
+ // --- then branch (set flag / enable halting) ---
builder.setInsertionPointToStart(&ifOp.getThenRegion().front());
- (isFlag ? fir::runtime::genFeraiseexcept : fir::runtime::genFeenableexcept)(
- builder, loc, fir::ConvertOp::create(builder, loc, i32Ty, except));
+ if constexpr (isFlag) {
+ if (isLinuxPPC) {
+ emitPPCFpscrRMW(makePPCStickySetMask(fieldVal), /*doOr=*/true);
+ // No prctl needed: setting a sticky bit does not change trap-enable bits.
+ } else {
+ fir::runtime::genFeraiseexcept(
+ builder, loc, fir::ConvertOp::create(builder, loc, i32Ty, except));
+ }
+ } else {
+ fir::runtime::genFeenableexcept(
+ builder, loc, fir::ConvertOp::create(builder, loc, i32Ty, except));
+ }
+
+ // --- else branch (clear flag / disable halting) ---
builder.setInsertionPointToStart(&ifOp.getElseRegion().front());
- (isFlag ? fir::runtime::genFeclearexcept : fir::runtime::genFedisableexcept)(
- builder, loc, fir::ConvertOp::create(builder, loc, i32Ty, except));
+ if constexpr (isFlag) {
+ if (isLinuxPPC) {
+ emitPPCFpscrRMW(makePPCStickyClearMask(fieldVal), /*doOr=*/false);
+ } else {
+ fir::runtime::genFeclearexcept(
+ builder, loc, fir::ConvertOp::create(builder, loc, i32Ty, except));
+ }
+ } else {
+ fir::runtime::genFedisableexcept(
+ builder, loc, fir::ConvertOp::create(builder, loc, i32Ty, except));
+ }
+
builder.setInsertionPointAfter(ifOp);
}
diff --git a/flang/test/Lower/Intrinsics/ieee_flag.f90 b/flang/test/Lower/Intrinsics/ieee_flag.f90
index acf3fedae4181..12f62df19f3e1 100644
--- a/flang/test/Lower/Intrinsics/ieee_flag.f90
+++ b/flang/test/Lower/Intrinsics/ieee_flag.f90
@@ -1,3 +1,4 @@
+! UNSUPPORTED: target=powerpc{{.*}}
! RUN: bbc -emit-fir -o - %s | FileCheck %s
! CHECK-LABEL: c.func @_QQmain
diff --git a/flang/test/Lower/Intrinsics/ieee_logb.f90 b/flang/test/Lower/Intrinsics/ieee_logb.f90
index fd4144e516118..f1f7208c5943c 100644
--- a/flang/test/Lower/Intrinsics/ieee_logb.f90
+++ b/flang/test/Lower/Intrinsics/ieee_logb.f90
@@ -1,3 +1,4 @@
+! UNSUPPORTED: target=powerpc{{.*}}
! RUN: bbc -emit-fir -o - %s | FileCheck %s
! CHECK-LABEL: c.func @_QPout
diff --git a/flang/test/Lower/Intrinsics/ieee_max_min.f90 b/flang/test/Lower/Intrinsics/ieee_max_min.f90
index 53371415b1d6d..6654da1785d92 100644
--- a/flang/test/Lower/Intrinsics/ieee_max_min.f90
+++ b/flang/test/Lower/Intrinsics/ieee_max_min.f90
@@ -1,3 +1,4 @@
+! UNSUPPORTED: target=powerpc{{.*}}
! RUN: bbc -emit-fir -o - %s | FileCheck %s
function tag(x)
diff --git a/flang/test/Lower/Intrinsics/ieee_set_flag_ppc.f90 b/flang/test/Lower/Intrinsics/ieee_set_flag_ppc.f90
new file mode 100644
index 0000000000000..f969d16ac384b
--- /dev/null
+++ b/flang/test/Lower/Intrinsics/ieee_set_flag_ppc.f90
@@ -0,0 +1,90 @@
+! REQUIRES: powerpc-registered-target
+! RUN: %flang_fc1 -triple powerpc64-ibm-aix -emit-hlfir -o - %s \
+! RUN: | FileCheck %s --check-prefix=CHECK-AIX
+! RUN: %flang_fc1 -triple powerpc64le-unknown-linux-gnu -emit-hlfir -o - %s \
+! RUN: | FileCheck %s --check-prefix=CHECK-LNX
+
+! On Linux PPC the sticky bits are written directly via mffs/mtfsf
+! (llvm.ppc.readflm/llvm.ppc.setflm) instead of feraiseexcept/
+! feclearexcept, to avoid spurious SIGFPE when trapping is armed.
+! On AIX feraiseexcept/feclearexcept path is called.
+
+program test
+ use ieee_arithmetic
+ logical :: flag_val
+
+! CHECK-AIX-LABEL: func.func @_QQmain()
+! CHECK-LNX-LABEL: func.func @_QQmain()
+
+ ! ------------------------------------------------------------------
+ ! ieee_set_flag(ieee_invalid, .false.)
+ ! AIX: feraiseexcept/feclearexcept call
+ ! Linux PPC: inline arith mask + readflm/setflm
+ ! ------------------------------------------------------------------
+
+ ! CHECK-AIX: fir.convert %false
+ ! CHECK-AIX: fir.if
+ ! CHECK-AIX: fir.call @_FortranAferaiseexcept({{.*}}) {{.*}} : (i32) -> ()
+ ! CHECK-AIX: } else {
+ ! CHECK-AIX: fir.call @_FortranAfeclearexcept({{.*}}) {{.*}} : (i32) -> ()
+ ! CHECK-AIX: }
+
+ ! CHECK-LNX: %false = arith.constant false
+ ! CHECK-LNX: fir.if
+ ! CHECK-LNX: fir.call @llvm.ppc.readflm() {{.*}} : () -> f64
+ ! CHECK-LNX: fir.call @llvm.ppc.setflm({{.*}}) {{.*}} : (f64) -> f64
+ ! CHECK-LNX: } else {
+ ! CHECK-LNX: fir.call @llvm.ppc.readflm() {{.*}} : () -> f64
+ ! CHECK-LNX: fir.call @llvm.ppc.setflm({{.*}}) {{.*}} : (f64) -> f64
+ ! CHECK-LNX: }
+ call ieee_set_flag(ieee_invalid, .false.)
+
+ ! ------------------------------------------------------------------
+ ! ieee_set_flag(ieee_overflow, .true.)
+ ! ------------------------------------------------------------------
+
+ ! CHECK-AIX: fir.convert %true
+ ! CHECK-AIX: fir.if
+ ! CHECK-AIX: fir.call @_FortranAferaiseexcept({{.*}}) {{.*}} : (i32) -> ()
+ ! CHECK-AIX: } else {
+ ! CHECK-AIX: fir.call @_FortranAfeclearexcept({{.*}}) {{.*}} : (i32) -> ()
+ ! CHECK-AIX: }
+
+ ! CHECK-LNX: %true = arith.constant true
+ ! CHECK-LNX: fir.if
+ ! CHECK-LNX: fir.call @llvm.ppc.readflm() {{.*}} : () -> f64
+ ! CHECK-LNX: fir.call @llvm.ppc.setflm({{.*}}) {{.*}} : (f64) -> f64
+ ! CHECK-LNX: } else {
+ ! CHECK-LNX: fir.call @llvm.ppc.readflm() {{.*}} : () -> f64
+ ! CHECK-LNX: fir.call @llvm.ppc.setflm({{.*}}) {{.*}} : (f64) -> f64
+ ! CHECK-LNX: }
+ call ieee_set_flag(ieee_overflow, .true.)
+
+ ! ------------------------------------------------------------------
+ ! ieee_set_flag(ieee_divide_by_zero, flag_val) -- runtime boolean
+ ! ------------------------------------------------------------------
+
+ ! CHECK-AIX: fir.if
+ ! CHECK-AIX: fir.call @_FortranAferaiseexcept({{.*}}) {{.*}} : (i32) -> ()
+ ! CHECK-AIX: } else {
+ ! CHECK-AIX: fir.call @_FortranAfeclearexcept({{.*}}) {{.*}} : (i32) -> ()
+ ! CHECK-AIX: }
+
+ ! CHECK-LNX: fir.load {{.*}} : !fir.ref<!fir.logical<4>>
+ ! CHECK-LNX: fir.if
+ ! CHECK-LNX: fir.call @llvm.ppc.readflm() {{.*}} : () -> f64
+ ! CHECK-LNX: fir.call @llvm.ppc.setflm({{.*}}) {{.*}} : (f64) -> f64
+ ! CHECK-LNX: } else {
+ ! CHECK-LNX: fir.call @llvm.ppc.readflm() {{.*}} : () -> f64
+ ! CHECK-LNX: fir.call @llvm.ppc.setflm({{.*}}) {{.*}} : (f64) -> f64
+ ! CHECK-LNX: }
+ call ieee_set_flag(ieee_divide_by_zero, flag_val)
+
+ ! ------------------------------------------------------------------
+ ! On Linux PPC, feraiseexcept/feclearexcept must NOT appear for
+ ! ieee_set_flag.
+ ! ------------------------------------------------------------------
+ ! CHECK-LNX-NOT: fir.call {{.*}}feraiseexcept
+ ! CHECK-LNX-NOT: fir.call {{.*}}feclearexcept
+
+end program
More information about the flang-commits
mailing list