[libc-commits] [libc] [libc][AArch64] Make mathvec/aarch64/expf.cpp compile big-endian (PR #218945)

Simon Tatham via libc-commits libc-commits at lists.llvm.org
Wed Aug 26 08:56:25 PDT 2026


https://github.com/statham-arm updated https://github.com/llvm/llvm-project/pull/218945

>From 8fe40cd93214b5ab068f9b09eaec76e9b698c15a Mon Sep 17 00:00:00 2001
From: Simon Tatham <simon.tatham at arm.com>
Date: Wed, 26 Aug 2026 15:46:18 +0100
Subject: [PATCH 1/3] [libc][AArch64] Make mathvec/aarch64/expf.cpp compile
 big-endian

The macros `V2` and `V4`, defined in `common.h`, are intended to
construct an initializer expression for a vector of 2 or 4 copies of
the same value, used in constant data initialization. But they provoke
a compiler warning when used in a big-endian build: "vector
initializers are not compatible with NEON intrinsics in big endian
mode". Usually this warning is upgraded to an error, and causes the
libc compilation to fail, or at least the compilation of
`mathvec/aarch64/expf.cpp` in the tests.

ACLE doesn't specify any `constexpr`-compatible way to initialize a
vector. The next best thing is to store the lane value just once in
the expf data structure, and use a `vdupq_n` intrinsic to splat it
across multiple lanes.

In this patch, I've introduced a system of macros that does that in a
big-endian build, but keeps the existing strategy little-endian, in
case it's faster.
---
 libc/src/mathvec/aarch64/common.h | 40 ++++++++++++++++++++--
 libc/src/mathvec/aarch64/expf.cpp | 57 +++++++++++++++++--------------
 2 files changed, 69 insertions(+), 28 deletions(-)

diff --git a/libc/src/mathvec/aarch64/common.h b/libc/src/mathvec/aarch64/common.h
index 9b69167b363ac..68be773b66d6f 100644
--- a/libc/src/mathvec/aarch64/common.h
+++ b/libc/src/mathvec/aarch64/common.h
@@ -26,6 +26,40 @@ using AdvSIMDFP64Vector = LIBC_NAMESPACE::cpp::simd<double, 2>;
     __ptr;                                                                     \
   })
 
-// Helpers for declaring vector constants.
-#define V2(X) {X, X}
-#define V4(X) {X, X, X, X}
+// Helpers for declaring vector constants containing all lanes the same, in a
+// way that varies between little- and big-endian AArch64. Use as follows:
+//
+//  - define a type for the constant using V2_SPLAT_TYPE or V4_SPLAT_TYPE,
+//    with a parameter like `float64` or `int32` which the macro will extend
+//    into a full scalar or vector type name.
+//
+//  - define the constant using V2_SPLAT_INITIALIZER or V4_SPLAT_INITIALIZER
+//
+//  - to get the actual vector, use MAKE_SPLAT_VECTOR(constant, type), where
+//    'type' this time is something like `f64` or `u32` which can be used as a
+//    suffix on a NEON intrinsic.
+
+#if __LITTLE_ENDIAN__
+
+// Use the gcc language extension of defining a vector using an initializer
+// list, which allows the whole vector to be statically defined in const data.
+#define V2_SPLAT_TYPE(TYPE_PREFIX) TYPE_PREFIX##x2_t
+#define V2_SPLAT_INITIALIZER(LANE)                                             \
+  { LANE, LANE }
+#define V4_SPLAT_TYPE(TYPE_PREFIX) TYPE_PREFIX##x4_t
+#define V4_SPLAT_INITIALIZER(LANE)                                             \
+  { LANE, LANE, LANE, LANE }
+#define MAKE_SPLAT_VECTOR(VEC, FN_SUFFIX) VEC
+
+#else
+
+// Big-endian, that gcc language extension provokes a compiler diagnostic, so
+// instead we store just one copy of the data to be duplicated across lanes,
+// and perform the duplication using vdupq when loading it.
+#define V2_SPLAT_TYPE(TYPE_PREFIX) TYPE_PREFIX##_t
+#define V2_SPLAT_INITIALIZER(LANE) LANE
+#define V4_SPLAT_TYPE(TYPE_PREFIX) TYPE_PREFIX##_t
+#define V4_SPLAT_INITIALIZER(LANE) LANE
+#define MAKE_SPLAT_VECTOR(LANE, FN_SUFFIX) vdupq_n_##FN_SUFFIX(LANE)
+
+#endif
diff --git a/libc/src/mathvec/aarch64/expf.cpp b/libc/src/mathvec/aarch64/expf.cpp
index b7050f44b751c..e7576e38a8db6 100644
--- a/libc/src/mathvec/aarch64/expf.cpp
+++ b/libc/src/mathvec/aarch64/expf.cpp
@@ -20,35 +20,36 @@
 namespace LIBC_NAMESPACE_DECL {
 
 struct expf_data {
-  float64x2_t shift, inv_ln2;
+  V2_SPLAT_TYPE(float64) shift, inv_ln2;
   double ln2_hi, ln2_lo;
   double c1, c3;
-  float64x2_t c0, c2;
-  float32x4_t range_val;
-  uint32x4_t inf;
-  uint64x2_t idx_mask;
+  V2_SPLAT_TYPE(float64) c0, c2;
+  V4_SPLAT_TYPE(float32) range_val;
+  V4_SPLAT_TYPE(uint32) inf;
+  V2_SPLAT_TYPE(uint64) idx_mask;
   const uint64_t *mantissa;
 };
 
 static constexpr expf_data EXPF_DATA = {
-    V2(0x1.800000000ffc0p+46), // shift
-    V2(0x1.71547652b82fep+0),  // inv_ln2
-    0x1.62e42fefa39efp-1,      // ln2_hi
-    0x1.abc9e3b39803fp-56,     // ln2_lo
-    0x1.55555555543c2p-3,      // c1
-    0x1.111126b4eff73p-7,      // c3
-    V2(0x1.fffffffffdbcep-2),  // c0
-    V2(0x1.555573c64f2e3p-5),  // c2
-    V4(0x1p+9),                // range_val
-    V4(0x7f800000),            // inf
-    V2(0x3f),                  // idx_mask
-    mathvec::EXP_MANTISSA,     // mantissa
+    V2_SPLAT_INITIALIZER(0x1.800000000ffc0p+46), // shift
+    V2_SPLAT_INITIALIZER(0x1.71547652b82fep+0),  // inv_ln2
+    0x1.62e42fefa39efp-1,                        // ln2_hi
+    0x1.abc9e3b39803fp-56,                       // ln2_lo
+    0x1.55555555543c2p-3,                        // c1
+    0x1.111126b4eff73p-7,                        // c3
+    V2_SPLAT_INITIALIZER(0x1.fffffffffdbcep-2),  // c0
+    V2_SPLAT_INITIALIZER(0x1.555573c64f2e3p-5),  // c2
+    V4_SPLAT_INITIALIZER(0x1p+9),                // range_val
+    V4_SPLAT_INITIALIZER(0x7f800000),            // inf
+    V2_SPLAT_INITIALIZER(0x3f),                  // idx_mask
+    mathvec::EXP_MANTISSA,                       // mantissa
 };
 
 LIBC_INLINE static float64x2_t exp_lookup(uint64x2_t u, const expf_data &data) {
   // The low 6 bits of u index the 64 element mantissa table.
-  uint64_t idx0 = vgetq_lane_u64(u & data.idx_mask, 0);
-  uint64_t idx1 = vgetq_lane_u64(u & data.idx_mask, 1);
+  uint64x2_t vidx_mask = MAKE_SPLAT_VECTOR(data.idx_mask, u64);
+  uint64_t idx0 = vgetq_lane_u64(u & vidx_mask, 0);
+  uint64_t idx1 = vgetq_lane_u64(u & vidx_mask, 1);
 
   uint64_t mant0 = data.mantissa[idx0];
   uint64_t mant1 = data.mantissa[idx1];
@@ -67,8 +68,10 @@ LIBC_INLINE static float64x2_t exp_lookup(uint64x2_t u, const expf_data &data) {
 
 LIBC_INLINE static float64x2_t inline_exp(float64x2_t x,
                                           const expf_data &data) {
-  float64x2_t z = vfmaq_f64(data.shift, x, data.inv_ln2);
-  float64x2_t n = vsubq_f64(z, data.shift);
+  float64x2_t vshift = MAKE_SPLAT_VECTOR(data.shift, f64);
+  float64x2_t vinv_ln2 = MAKE_SPLAT_VECTOR(data.inv_ln2, f64);
+  float64x2_t z = vfmaq_f64(vshift, x, vinv_ln2);
+  float64x2_t n = vsubq_f64(z, vshift);
 
   float64x2_t ln2 = vld1q_f64(&data.ln2_hi);
 
@@ -80,8 +83,10 @@ LIBC_INLINE static float64x2_t inline_exp(float64x2_t x,
 
   // poly(r) = exp(r) - 1 ~= r + c0*r^2 + c1*r^3 + c2*r^4 + c3*r^5
   float64x2_t r2 = r * r;
-  float64x2_t p01 = vfmaq_laneq_f64(data.c0, r, coeffs, 0);
-  float64x2_t p23 = vfmaq_laneq_f64(data.c2, r, coeffs, 1);
+  float64x2_t vc0 = MAKE_SPLAT_VECTOR(data.c0, f64);
+  float64x2_t vc2 = MAKE_SPLAT_VECTOR(data.c2, f64);
+  float64x2_t p01 = vfmaq_laneq_f64(vc0, r, coeffs, 0);
+  float64x2_t p23 = vfmaq_laneq_f64(vc2, r, coeffs, 1);
   float64x2_t p04 = vfmaq_f64(p01, r2, p23);
   float64x2_t y = vfmaq_f64(r, r2, p04);
 
@@ -107,11 +112,13 @@ LLVM_LIBC_FUNCTION(AdvSIMDFP32Vector, expf, (AdvSIMDFP32Vector x),
   float32x4_t ret = vcombine_f32(vcvt_f32_f64(y_lo), vcvt_f32_f64(y_hi));
 
   // Handle special cases for overflow and underflow.
-  uint32x4_t special = vcagtq_f32(x, data.range_val);
+  float32x4_t vrange_val = MAKE_SPLAT_VECTOR(data.range_val, f32);
+  uint32x4_t special = vcagtq_f32(x, vrange_val);
   bool has_special = vmaxvq_u32(special) != 0;
   if (LIBC_UNLIKELY(has_special)) {
     uint32x4_t is_inf = vcgtzq_f32(x);
-    uint32x4_t inf_or_zero = vandq_u32(is_inf, data.inf);
+    uint32x4_t vinf = MAKE_SPLAT_VECTOR(data.inf, u32);
+    uint32x4_t inf_or_zero = vandq_u32(is_inf, vinf);
     float32x4_t special_res = vreinterpretq_f32_u32(inf_or_zero);
 
     // Combine the results for normal and special cases and return.

>From eaa52d8a3265adcdb48909a9ca8fec30c0dc43da Mon Sep 17 00:00:00 2001
From: Simon Tatham <simon.tatham at arm.com>
Date: Wed, 26 Aug 2026 16:03:39 +0100
Subject: [PATCH 2/3] Fix clang-format (my downstream one disagreed with this!)

---
 libc/src/mathvec/aarch64/common.h | 6 ++----
 1 file changed, 2 insertions(+), 4 deletions(-)

diff --git a/libc/src/mathvec/aarch64/common.h b/libc/src/mathvec/aarch64/common.h
index 68be773b66d6f..1bade946ce5b1 100644
--- a/libc/src/mathvec/aarch64/common.h
+++ b/libc/src/mathvec/aarch64/common.h
@@ -44,11 +44,9 @@ using AdvSIMDFP64Vector = LIBC_NAMESPACE::cpp::simd<double, 2>;
 // Use the gcc language extension of defining a vector using an initializer
 // list, which allows the whole vector to be statically defined in const data.
 #define V2_SPLAT_TYPE(TYPE_PREFIX) TYPE_PREFIX##x2_t
-#define V2_SPLAT_INITIALIZER(LANE)                                             \
-  { LANE, LANE }
+#define V2_SPLAT_INITIALIZER(LANE) {LANE, LANE}
 #define V4_SPLAT_TYPE(TYPE_PREFIX) TYPE_PREFIX##x4_t
-#define V4_SPLAT_INITIALIZER(LANE)                                             \
-  { LANE, LANE, LANE, LANE }
+#define V4_SPLAT_INITIALIZER(LANE) {LANE, LANE, LANE, LANE}
 #define MAKE_SPLAT_VECTOR(VEC, FN_SUFFIX) VEC
 
 #else

>From 32b2a80c9db3c3b4d18a0c7da5eeec39ff487222 Mon Sep 17 00:00:00 2001
From: Simon Tatham <simon.tatham at arm.com>
Date: Wed, 26 Aug 2026 16:55:47 +0100
Subject: [PATCH 3/3] Improve consistency of macro param names in comment

---
 libc/src/mathvec/aarch64/common.h | 12 ++++++------
 1 file changed, 6 insertions(+), 6 deletions(-)

diff --git a/libc/src/mathvec/aarch64/common.h b/libc/src/mathvec/aarch64/common.h
index 1bade946ce5b1..0fbeb74fa01e8 100644
--- a/libc/src/mathvec/aarch64/common.h
+++ b/libc/src/mathvec/aarch64/common.h
@@ -29,15 +29,15 @@ using AdvSIMDFP64Vector = LIBC_NAMESPACE::cpp::simd<double, 2>;
 // Helpers for declaring vector constants containing all lanes the same, in a
 // way that varies between little- and big-endian AArch64. Use as follows:
 //
-//  - define a type for the constant using V2_SPLAT_TYPE or V4_SPLAT_TYPE,
-//    with a parameter like `float64` or `int32` which the macro will extend
-//    into a full scalar or vector type name.
+//  - define a type for the constant using V2_SPLAT_TYPE or V4_SPLAT_TYPE, with
+//    a type-prefix parameter like `float64` or `int32` which the macro will
+//    extend into a full scalar or vector type name.
 //
 //  - define the constant using V2_SPLAT_INITIALIZER or V4_SPLAT_INITIALIZER
 //
-//  - to get the actual vector, use MAKE_SPLAT_VECTOR(constant, type), where
-//    'type' this time is something like `f64` or `u32` which can be used as a
-//    suffix on a NEON intrinsic.
+//  - to get the actual vector, use MAKE_SPLAT_VECTOR(constant, suffix), where
+//    'suffix' is something like `f64` or `u32` which appears in the name of a
+//    NEON intrinsic to specify its element type.
 
 #if __LITTLE_ENDIAN__
 



More information about the libc-commits mailing list