[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