https://github.com/Lurie97 updated 
https://github.com/llvm/llvm-project/pull/212696

>From bc1d058505a140b143c6f96eadfd7e1914f1d21d Mon Sep 17 00:00:00 2001
From: jiajia Qian <[email protected]>
Date: Thu, 30 Jul 2026 14:03:12 +0800
Subject: [PATCH] [libclc] Produce subnormal results for exp/exp2 on FP32

The FP32 __clc_exp/__clc_exp2 implementations scaled the reduced result
by 2^p using integer bit manipulation (as_int(y) + (p << 23)). That
representation cannot encode a subnormal, so inputs whose result falls
below the smallest normal were flushed to zero via "x < llim ? 0.0f",
using ln(smallest_normal) as the lower limit. On devices that report
CL_FP_DENORM this loses the correct subnormal result (e.g. erfc(), which
is built on exp(), failed CTS math_brute_force with the reference
subnormal vs a returned 0).

Scale with __clc_ldexp instead, which produces subnormals naturally, so
no separate flush/fixup path is needed. The lower saturation limit is
relaxed from ln(smallest_normal) to ln(smallest_subnormal) (2^-149); only
inputs below that genuinely underflow to zero. On flush-to-zero devices
the hardware collapses the subnormal result to zero, matching the
previous behavior.

The exponent handed to ldexp is clamped, because for extreme-magnitude
inputs the float->int reduction is out of range and an ill-formed
exponent would make ldexp return NaN. Such inputs over/underflow and are
saturated to inf/zero by the ulim/llim selects anyway, so the clamp never
affects a representable result.

Verified on i.MX95 Mali-G310 (Panfrost/Rusticl), a flush-to-zero device
(reports no FP32 denormals, FTZ on): OpenCL-CTS math_brute_force exp and
exp2 pass 6/6 sub-tests (fp32, fp32 fast-relaxed, fp16). Without the
exponent clamp, exp/exp2 regressed to NaN for large-magnitude negative
inputs on this path.
---
 libclc/clc/lib/generic/math/clc_exp.cl   |  2 ++
 libclc/clc/lib/generic/math/clc_exp.inc  | 18 +++++++++++++-----
 libclc/clc/lib/generic/math/clc_exp2.cl  |  2 ++
 libclc/clc/lib/generic/math/clc_exp2.inc | 16 ++++++++++++----
 4 files changed, 29 insertions(+), 9 deletions(-)

diff --git a/libclc/clc/lib/generic/math/clc_exp.cl 
b/libclc/clc/lib/generic/math/clc_exp.cl
index 1e7174c7541c5..7eb2b5599939e 100644
--- a/libclc/clc/lib/generic/math/clc_exp.cl
+++ b/libclc/clc/lib/generic/math/clc_exp.cl
@@ -11,9 +11,11 @@
 #include "clc/internal/clc.h"
 #include "clc/math/clc_exp_helper.h"
 #include "clc/math/clc_fma.h"
+#include "clc/math/clc_ldexp.h"
 #include "clc/math/clc_mad.h"
 #include "clc/math/math.h"
 #include "clc/relational/clc_isnan.h"
+#include "clc/shared/clc_clamp.h"
 
 #define __CLC_BODY "clc_exp.inc"
 #include "clc/math/gentype.inc"
diff --git a/libclc/clc/lib/generic/math/clc_exp.inc 
b/libclc/clc/lib/generic/math/clc_exp.inc
index 5057bf8034e92..249a239206c38 100644
--- a/libclc/clc/lib/generic/math/clc_exp.inc
+++ b/libclc/clc/lib/generic/math/clc_exp.inc
@@ -37,15 +37,23 @@ _CLC_OVERLOAD _CLC_DEF __CLC_GENTYPE 
__clc_exp(__CLC_GENTYPE x) {
 
   __CLC_GENTYPE y = 1.0f - (((-lo) - MATH_DIVIDE(t * v, 2.0f - v)) - hi);
 
-  // Scale by 2^p
-  __CLC_GENTYPE r = __CLC_AS_GENTYPE(__CLC_AS_INTN(y) + (p << 23));
+  // Scale by 2^p. Use ldexp so results in the subnormal range are represented
+  // exactly rather than being flushed to zero (an integer as_int(y) + (p << 
23)
+  // scaling cannot encode subnormals). On flush-to-zero devices the hardware
+  // collapses the subnormal result to zero, matching the previous behavior.
+  // Clamp the exponent fed to ldexp: for extreme |x| the float->int reduction
+  // above is out of range, and an ill-formed exponent would make ldexp return
+  // NaN. Any such input over/underflows and is saturated to inf/zero by the
+  // ulim/llim selects below, so the clamp never affects a representable 
result.
+  __CLC_GENTYPE r = __clc_ldexp(y, __clc_clamp(p, (__CLC_INTN)-151, 
(__CLC_INTN)129));
 
   // ln(largest_normal) = 88.72283905206835305366
   const __CLC_GENTYPE ulim = 0x1.62e430p+6f;
-  // ln(smallest_normal) = -87.33654475055310898657
-  const __CLC_GENTYPE llim = -0x1.5d589ep+6f;
+  // ln(smallest_subnormal) = ln(2^-149) = -103.27892990343184. Below this the
+  // result genuinely underflows to zero.
+  const __CLC_GENTYPE llim = -0x1.9d1dap+6f;
 
-  r = x < llim ? 0.0f : r;
+  r = x < llim ? (__CLC_GENTYPE)0.0f : r;
   r = x < ulim ? r : __CLC_AS_GENTYPE((__CLC_UINTN)0x7f800000);
   return __clc_isnan(x) ? x : r;
 }
diff --git a/libclc/clc/lib/generic/math/clc_exp2.cl 
b/libclc/clc/lib/generic/math/clc_exp2.cl
index 0516963ab620f..8abb966cca045 100644
--- a/libclc/clc/lib/generic/math/clc_exp2.cl
+++ b/libclc/clc/lib/generic/math/clc_exp2.cl
@@ -11,10 +11,12 @@
 #include "clc/internal/clc.h"
 #include "clc/math/clc_exp_helper.h"
 #include "clc/math/clc_fma.h"
+#include "clc/math/clc_ldexp.h"
 #include "clc/math/clc_mad.h"
 #include "clc/math/clc_rint.h"
 #include "clc/math/math.h"
 #include "clc/relational/clc_isnan.h"
+#include "clc/shared/clc_clamp.h"
 
 #define __CLC_BODY "clc_exp2.inc"
 #include "clc/math/gentype.inc"
diff --git a/libclc/clc/lib/generic/math/clc_exp2.inc 
b/libclc/clc/lib/generic/math/clc_exp2.inc
index 6da361a43ed4c..8678b7510ba26 100644
--- a/libclc/clc/lib/generic/math/clc_exp2.inc
+++ b/libclc/clc/lib/generic/math/clc_exp2.inc
@@ -36,13 +36,21 @@ _CLC_OVERLOAD _CLC_DEF __CLC_GENTYPE 
__clc_exp2(__CLC_GENTYPE x) {
 
   __CLC_GENTYPE y = 1.0f - (((-lo) - MATH_DIVIDE(t * v, 2.0f - v)) - hi);
 
-  // Scale by 2^p
-  __CLC_GENTYPE r = __CLC_AS_FLOATN(__CLC_AS_INTN(y) + (p << 23));
+  // Scale by 2^p. Use ldexp so results in the subnormal range are represented
+  // exactly rather than being flushed to zero (an integer as_int(y) + (p << 
23)
+  // scaling cannot encode subnormals). On flush-to-zero devices the hardware
+  // collapses the subnormal result to zero, matching the previous behavior.
+  // Clamp the exponent fed to ldexp: for extreme |x| the float->int reduction
+  // above is out of range, and an ill-formed exponent would make ldexp return
+  // NaN. Any such input over/underflows and is saturated to inf/zero by the
+  // ulim/llim selects below, so the clamp never affects a representable 
result.
+  __CLC_GENTYPE r = __clc_ldexp(y, __clc_clamp(p, (__CLC_INTN)-151, 
(__CLC_INTN)129));
 
   const __CLC_GENTYPE ulim = 128.0f;
-  const __CLC_GENTYPE llim = -126.0f;
+  // Smallest subnormal is 2^-149; below this the result underflows to zero.
+  const __CLC_GENTYPE llim = -149.0f;
 
-  r = x < llim ? 0.0f : r;
+  r = x < llim ? (__CLC_GENTYPE)0.0f : r;
   r = x < ulim ? r : __CLC_AS_FLOATN((__CLC_UINTN)0x7f800000);
   return __clc_isnan(x) ? x : r;
 }

_______________________________________________
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to