[libc-commits] [libc] [llvm] [libc] Add floating-point bitwise operations to FPUtil. (PR #233168)
via libc-commits
libc-commits at lists.llvm.org
Mon Oct 19 09:53:28 PDT 2026
https://github.com/lntue updated https://github.com/llvm/llvm-project/pull/233168
>From 282188f7a60b894edcfa6b90775d7b8204ea059e Mon Sep 17 00:00:00 2001
From: Tue Ly <lntue.h at gmail.com>
Date: Mon, 19 Oct 2026 16:52:57 +0000
Subject: [PATCH] [libc] Add floating-point bitwise operations to FPUtil.
Add `fp_and`, `fp_or`, `fp_xor`, `fp_andnot`, and `fp_not` to
`src/__support/FPUtil/fp_bitwise.h` to perform bitwise operations on
floating-point values directly in FP/SIMD registers when SSE2 or AArch64 NEON
is available, avoiding GPR round-trips.
---
libc/src/__support/FPUtil/CMakeLists.txt | 13 ++
libc/src/__support/FPUtil/fp_bitwise.h | 190 ++++++++++++++++++
libc/test/src/__support/FPUtil/CMakeLists.txt | 13 ++
.../src/__support/FPUtil/fp_bitwise_test.cpp | 130 ++++++++++++
.../llvm-project-overlay/libc/BUILD.bazel | 13 ++
.../test/src/__support/FPUtil/BUILD.bazel | 12 ++
6 files changed, 371 insertions(+)
create mode 100644 libc/src/__support/FPUtil/fp_bitwise.h
create mode 100644 libc/test/src/__support/FPUtil/fp_bitwise_test.cpp
diff --git a/libc/src/__support/FPUtil/CMakeLists.txt b/libc/src/__support/FPUtil/CMakeLists.txt
index b3b4dcbd18e82..bb50616d6a28c 100644
--- a/libc/src/__support/FPUtil/CMakeLists.txt
+++ b/libc/src/__support/FPUtil/CMakeLists.txt
@@ -45,6 +45,19 @@ add_header_library(
libc.src.__support.uint128
)
+add_header_library(
+ fp_bitwise
+ HDRS
+ fp_bitwise.h
+ DEPENDS
+ .fp_bits
+ libc.src.__support.CPP.type_traits
+ libc.src.__support.macros.attributes
+ libc.src.__support.macros.config
+ libc.src.__support.macros.properties.architectures
+ libc.src.__support.macros.properties.cpu_features
+)
+
add_header_library(
fpbits_str
HDRS
diff --git a/libc/src/__support/FPUtil/fp_bitwise.h b/libc/src/__support/FPUtil/fp_bitwise.h
new file mode 100644
index 0000000000000..5902b8e30aac5
--- /dev/null
+++ b/libc/src/__support/FPUtil/fp_bitwise.h
@@ -0,0 +1,190 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+///
+/// \file
+/// Bitwise operations on floating-point numbers.
+///
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_LIBC_SRC___SUPPORT_FPUTIL_FP_BITWISE_H
+#define LLVM_LIBC_SRC___SUPPORT_FPUTIL_FP_BITWISE_H
+
+#include "src/__support/CPP/type_traits.h"
+#include "src/__support/FPUtil/FPBits.h"
+#include "src/__support/macros/attributes.h"
+#include "src/__support/macros/config.h"
+#include "src/__support/macros/properties/architectures.h"
+#include "src/__support/macros/properties/cpu_features.h"
+
+#ifdef LIBC_TARGET_CPU_HAS_SSE2
+#include <immintrin.h>
+#elif defined(LIBC_TARGET_ARCH_IS_AARCH64) && defined(__ARM_NEON)
+#include <arm_neon.h>
+#endif // LIBC_TARGET_CPU_HAS_SSE2
+
+namespace LIBC_NAMESPACE_DECL {
+namespace fputil {
+
+// Bitwise AND of two floating-point values (a & b), performed directly in
+// FP/SIMD registers when SSE2 or AArch64 NEON is available to avoid GPR
+// round-trips.
+template <typename T>
+LIBC_ALWAYS_INLINE constexpr cpp::enable_if_t<cpp::is_floating_point_v<T>, T>
+fp_and(T a, T b) {
+ if (!cpp::is_constant_evaluated()) {
+#ifdef LIBC_TARGET_CPU_HAS_SSE2
+ if constexpr (cpp::is_same_v<T, float>)
+ return _mm_cvtss_f32(_mm_and_ps(_mm_set_ss(a), _mm_set_ss(b)));
+ if constexpr (cpp::is_same_v<T, double>)
+ return _mm_cvtsd_f64(_mm_and_pd(_mm_set_sd(a), _mm_set_sd(b)));
+#elif defined(LIBC_TARGET_ARCH_IS_AARCH64) && defined(__ARM_NEON)
+ if constexpr (cpp::is_same_v<T, float>)
+ return vget_lane_f32(
+ vreinterpret_f32_u32(vand_u32(vreinterpret_u32_f32(vdup_n_f32(a)),
+ vreinterpret_u32_f32(vdup_n_f32(b)))),
+ 0);
+ if constexpr (cpp::is_same_v<T, double>)
+ return vget_lane_f64(
+ vreinterpret_f64_u64(vand_u64(vreinterpret_u64_f64(vdup_n_f64(a)),
+ vreinterpret_u64_f64(vdup_n_f64(b)))),
+ 0);
+#endif
+ }
+ using StorageType = typename FPBits<T>::StorageType;
+ return FPBits<T>(static_cast<StorageType>(FPBits<T>(a).uintval() &
+ FPBits<T>(b).uintval()))
+ .get_val();
+}
+
+// Bitwise OR of two floating-point values (a | b), performed directly in
+// FP/SIMD registers when SSE2 or AArch64 NEON is available to avoid GPR
+// round-trips.
+template <typename T>
+LIBC_ALWAYS_INLINE constexpr cpp::enable_if_t<cpp::is_floating_point_v<T>, T>
+fp_or(T a, T b) {
+ if (!cpp::is_constant_evaluated()) {
+#ifdef LIBC_TARGET_CPU_HAS_SSE2
+ if constexpr (cpp::is_same_v<T, float>)
+ return _mm_cvtss_f32(_mm_or_ps(_mm_set_ss(a), _mm_set_ss(b)));
+ if constexpr (cpp::is_same_v<T, double>)
+ return _mm_cvtsd_f64(_mm_or_pd(_mm_set_sd(a), _mm_set_sd(b)));
+#elif defined(LIBC_TARGET_ARCH_IS_AARCH64) && defined(__ARM_NEON)
+ if constexpr (cpp::is_same_v<T, float>)
+ return vget_lane_f32(
+ vreinterpret_f32_u32(vorr_u32(vreinterpret_u32_f32(vdup_n_f32(a)),
+ vreinterpret_u32_f32(vdup_n_f32(b)))),
+ 0);
+ if constexpr (cpp::is_same_v<T, double>)
+ return vget_lane_f64(
+ vreinterpret_f64_u64(vorr_u64(vreinterpret_u64_f64(vdup_n_f64(a)),
+ vreinterpret_u64_f64(vdup_n_f64(b)))),
+ 0);
+#endif
+ }
+ using StorageType = typename FPBits<T>::StorageType;
+ return FPBits<T>(static_cast<StorageType>(FPBits<T>(a).uintval() |
+ FPBits<T>(b).uintval()))
+ .get_val();
+}
+
+// Bitwise XOR of two floating-point values (a ^ b), performed directly in
+// FP/SIMD registers when SSE2 or AArch64 NEON is available to avoid GPR
+// round-trips.
+template <typename T>
+LIBC_ALWAYS_INLINE constexpr cpp::enable_if_t<cpp::is_floating_point_v<T>, T>
+fp_xor(T a, T b) {
+ if (!cpp::is_constant_evaluated()) {
+#ifdef LIBC_TARGET_CPU_HAS_SSE2
+ if constexpr (cpp::is_same_v<T, float>)
+ return _mm_cvtss_f32(_mm_xor_ps(_mm_set_ss(a), _mm_set_ss(b)));
+ if constexpr (cpp::is_same_v<T, double>)
+ return _mm_cvtsd_f64(_mm_xor_pd(_mm_set_sd(a), _mm_set_sd(b)));
+#elif defined(LIBC_TARGET_ARCH_IS_AARCH64) && defined(__ARM_NEON)
+ if constexpr (cpp::is_same_v<T, float>)
+ return vget_lane_f32(
+ vreinterpret_f32_u32(veor_u32(vreinterpret_u32_f32(vdup_n_f32(a)),
+ vreinterpret_u32_f32(vdup_n_f32(b)))),
+ 0);
+ if constexpr (cpp::is_same_v<T, double>)
+ return vget_lane_f64(
+ vreinterpret_f64_u64(veor_u64(vreinterpret_u64_f64(vdup_n_f64(a)),
+ vreinterpret_u64_f64(vdup_n_f64(b)))),
+ 0);
+#endif
+ }
+ using StorageType = typename FPBits<T>::StorageType;
+ return FPBits<T>(static_cast<StorageType>(FPBits<T>(a).uintval() ^
+ FPBits<T>(b).uintval()))
+ .get_val();
+}
+
+// Bitwise AND-NOT of two floating-point values (a & ~b), clearing the bits set
+// in b from a. Performed directly in FP/SIMD registers when SSE2 or AArch64
+// NEON is available to avoid GPR round-trips.
+template <typename T>
+LIBC_ALWAYS_INLINE constexpr cpp::enable_if_t<cpp::is_floating_point_v<T>, T>
+fp_andnot(T a, T b) {
+ if (!cpp::is_constant_evaluated()) {
+#ifdef LIBC_TARGET_CPU_HAS_SSE2
+ if constexpr (cpp::is_same_v<T, float>)
+ return _mm_cvtss_f32(_mm_andnot_ps(_mm_set_ss(b), _mm_set_ss(a)));
+ if constexpr (cpp::is_same_v<T, double>)
+ return _mm_cvtsd_f64(_mm_andnot_pd(_mm_set_sd(b), _mm_set_sd(a)));
+#elif defined(LIBC_TARGET_ARCH_IS_AARCH64) && defined(__ARM_NEON)
+ if constexpr (cpp::is_same_v<T, float>)
+ return vget_lane_f32(
+ vreinterpret_f32_u32(vbic_u32(vreinterpret_u32_f32(vdup_n_f32(a)),
+ vreinterpret_u32_f32(vdup_n_f32(b)))),
+ 0);
+ if constexpr (cpp::is_same_v<T, double>)
+ return vget_lane_f64(
+ vreinterpret_f64_u64(vbic_u64(vreinterpret_u64_f64(vdup_n_f64(a)),
+ vreinterpret_u64_f64(vdup_n_f64(b)))),
+ 0);
+#endif
+ }
+ using StorageType = typename FPBits<T>::StorageType;
+ return FPBits<T>(static_cast<StorageType>(FPBits<T>(a).uintval() &
+ ~FPBits<T>(b).uintval()))
+ .get_val();
+}
+
+// Bitwise NOT of a floating-point value (~a), performed directly in FP/SIMD
+// registers when SSE2 or AArch64 NEON is available to avoid GPR round-trips.
+template <typename T>
+LIBC_ALWAYS_INLINE constexpr cpp::enable_if_t<cpp::is_floating_point_v<T>, T>
+fp_not(T a) {
+ if (!cpp::is_constant_evaluated()) {
+#ifdef LIBC_TARGET_CPU_HAS_SSE2
+ if constexpr (cpp::is_same_v<T, float>)
+ return _mm_cvtss_f32(
+ _mm_xor_ps(_mm_set_ss(a), _mm_castsi128_ps(_mm_set1_epi32(-1))));
+ if constexpr (cpp::is_same_v<T, double>)
+ return _mm_cvtsd_f64(
+ _mm_xor_pd(_mm_set_sd(a), _mm_castsi128_pd(_mm_set1_epi64x(-1))));
+#elif defined(LIBC_TARGET_ARCH_IS_AARCH64) && defined(__ARM_NEON)
+ if constexpr (cpp::is_same_v<T, float>)
+ return vget_lane_f32(
+ vreinterpret_f32_u32(vmvn_u32(vreinterpret_u32_f32(vdup_n_f32(a)))),
+ 0);
+ if constexpr (cpp::is_same_v<T, double>)
+ return vget_lane_f64(
+ vreinterpret_f64_u32(vmvn_u32(vreinterpret_u32_f64(vdup_n_f64(a)))),
+ 0);
+#endif
+ }
+ using StorageType = typename FPBits<T>::StorageType;
+ FPBits<T> res(a);
+ res.set_uintval(static_cast<StorageType>(~res.uintval()));
+ return res.get_val();
+}
+
+} // namespace fputil
+} // namespace LIBC_NAMESPACE_DECL
+
+#endif // LLVM_LIBC_SRC___SUPPORT_FPUTIL_FP_BITWISE_H
diff --git a/libc/test/src/__support/FPUtil/CMakeLists.txt b/libc/test/src/__support/FPUtil/CMakeLists.txt
index 8ce44b2212882..deed4f201dd0a 100644
--- a/libc/test/src/__support/FPUtil/CMakeLists.txt
+++ b/libc/test/src/__support/FPUtil/CMakeLists.txt
@@ -29,6 +29,19 @@ add_libc_test(
libc.src.__support.sign
)
+add_fp_unittest(
+ fp_bitwise_test
+ SUITE
+ libc-fputil-tests
+ SRCS
+ fp_bitwise_test.cpp
+ DEPENDS
+ libc.src.__support.FPUtil.bfloat16
+ libc.src.__support.FPUtil.fp_bits
+ libc.src.__support.FPUtil.fp_bitwise
+ libc.src.__support.macros.properties.types
+)
+
add_fp_unittest(
rounding_mode_test
SUITE
diff --git a/libc/test/src/__support/FPUtil/fp_bitwise_test.cpp b/libc/test/src/__support/FPUtil/fp_bitwise_test.cpp
new file mode 100644
index 0000000000000..db46f804725e8
--- /dev/null
+++ b/libc/test/src/__support/FPUtil/fp_bitwise_test.cpp
@@ -0,0 +1,130 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+///
+/// \file
+/// Unittests for bitwise operations on floating-point numbers.
+///
+//===----------------------------------------------------------------------===//
+
+#include "src/__support/FPUtil/FPBits.h"
+#include "src/__support/FPUtil/bfloat16.h"
+#include "src/__support/FPUtil/fp_bitwise.h"
+#include "src/__support/macros/properties/types.h"
+#include "test/UnitTest/FPMatcher.h"
+#include "test/UnitTest/Test.h"
+
+using LIBC_NAMESPACE::fputil::BFloat16;
+using LIBC_NAMESPACE::fputil::fp_and;
+using LIBC_NAMESPACE::fputil::fp_andnot;
+using LIBC_NAMESPACE::fputil::fp_not;
+using LIBC_NAMESPACE::fputil::fp_or;
+using LIBC_NAMESPACE::fputil::fp_xor;
+using LIBC_NAMESPACE::fputil::FPBits;
+
+template <typename T>
+class LlvmLibcFPBitwiseTest : public LIBC_NAMESPACE::testing::Test {
+ DECLARE_SPECIAL_CONSTANTS(T)
+
+ static constexpr T pos_val = T(1.5);
+ static constexpr T neg_val = T(-1.5);
+ static constexpr T other_val = T(1.75);
+
+ static constexpr StorageType bits_of(T x) { return FPBits(x).uintval(); }
+
+public:
+ void test_and() {
+ EXPECT_EQ(bits_of(fp_and(pos_val, zero)), bits_of(zero));
+ EXPECT_EQ(bits_of(fp_and(neg_val, zero)), bits_of(zero));
+ EXPECT_EQ(bits_of(fp_and(pos_val, neg_zero)), bits_of(zero));
+ EXPECT_EQ(bits_of(fp_and(neg_val, neg_zero)), bits_of(neg_zero));
+ EXPECT_EQ(bits_of(fp_and(pos_val, pos_val)), bits_of(pos_val));
+ EXPECT_EQ(bits_of(fp_and(neg_val, neg_val)), bits_of(neg_val));
+ EXPECT_EQ(bits_of(fp_and(neg_val, pos_val)), bits_of(pos_val));
+ EXPECT_EQ(bits_of(fp_and(pos_val, other_val)),
+ static_cast<StorageType>(bits_of(pos_val) & bits_of(other_val)));
+ }
+
+ void test_or() {
+ EXPECT_EQ(bits_of(fp_or(pos_val, zero)), bits_of(pos_val));
+ EXPECT_EQ(bits_of(fp_or(neg_val, zero)), bits_of(neg_val));
+ EXPECT_EQ(bits_of(fp_or(pos_val, neg_zero)), bits_of(neg_val));
+ EXPECT_EQ(bits_of(fp_or(neg_val, neg_zero)), bits_of(neg_val));
+ EXPECT_EQ(bits_of(fp_or(pos_val, pos_val)), bits_of(pos_val));
+ EXPECT_EQ(bits_of(fp_or(pos_val, other_val)),
+ static_cast<StorageType>(bits_of(pos_val) | bits_of(other_val)));
+ }
+
+ void test_xor() {
+ EXPECT_EQ(bits_of(fp_xor(pos_val, zero)), bits_of(pos_val));
+ EXPECT_EQ(bits_of(fp_xor(neg_val, zero)), bits_of(neg_val));
+ EXPECT_EQ(bits_of(fp_xor(pos_val, neg_zero)), bits_of(neg_val));
+ EXPECT_EQ(bits_of(fp_xor(neg_val, neg_zero)), bits_of(pos_val));
+ EXPECT_EQ(bits_of(fp_xor(neg_val, pos_val)), bits_of(neg_zero));
+ EXPECT_EQ(bits_of(fp_xor(pos_val, pos_val)), bits_of(zero));
+ EXPECT_EQ(bits_of(fp_xor(neg_val, neg_val)), bits_of(zero));
+ EXPECT_EQ(bits_of(fp_xor(pos_val, other_val)),
+ static_cast<StorageType>(bits_of(pos_val) ^ bits_of(other_val)));
+ }
+
+ void test_andnot() {
+ EXPECT_EQ(bits_of(fp_andnot(pos_val, zero)), bits_of(pos_val));
+ EXPECT_EQ(bits_of(fp_andnot(neg_val, zero)), bits_of(neg_val));
+ EXPECT_EQ(bits_of(fp_andnot(pos_val, neg_zero)), bits_of(pos_val));
+ EXPECT_EQ(bits_of(fp_andnot(neg_val, neg_zero)), bits_of(pos_val));
+ EXPECT_EQ(bits_of(fp_andnot(pos_val, pos_val)), bits_of(zero));
+ EXPECT_EQ(bits_of(fp_andnot(neg_val, neg_val)), bits_of(zero));
+ EXPECT_EQ(bits_of(fp_andnot(other_val, pos_val)),
+ static_cast<StorageType>(bits_of(other_val) & ~bits_of(pos_val)));
+ }
+
+ void test_not() {
+ EXPECT_EQ(bits_of(fp_not(fp_not(zero))), bits_of(zero));
+ EXPECT_EQ(bits_of(fp_not(fp_not(neg_zero))), bits_of(neg_zero));
+ EXPECT_EQ(bits_of(fp_not(fp_not(pos_val))), bits_of(pos_val));
+ EXPECT_EQ(bits_of(fp_not(fp_not(neg_val))), bits_of(neg_val));
+ EXPECT_EQ(bits_of(fp_and(pos_val, fp_not(pos_val))), bits_of(zero));
+ EXPECT_EQ(bits_of(fp_and(neg_val, fp_not(neg_zero))), bits_of(pos_val));
+ EXPECT_EQ(bits_of(fp_andnot(pos_val, other_val)),
+ bits_of(fp_and(pos_val, fp_not(other_val))));
+ }
+};
+
+using FPBitwiseTypes = LIBC_NAMESPACE::testing::TypeList<float, double,
+#ifdef LIBC_TYPES_HAS_FLOAT16
+ float16,
+#endif // LIBC_TYPES_HAS_FLOAT16
+#ifdef LIBC_TYPES_HAS_NATIVE_FLOAT128
+ float128,
+#endif // LIBC_TYPES_HAS_NATIVE_FLOAT128
+ BFloat16>;
+
+TYPED_TEST_F(LlvmLibcFPBitwiseTest, And, FPBitwiseTypes) { this->test_and(); }
+TYPED_TEST_F(LlvmLibcFPBitwiseTest, Or, FPBitwiseTypes) { this->test_or(); }
+TYPED_TEST_F(LlvmLibcFPBitwiseTest, Xor, FPBitwiseTypes) { this->test_xor(); }
+TYPED_TEST_F(LlvmLibcFPBitwiseTest, AndNot, FPBitwiseTypes) {
+ this->test_andnot();
+}
+TYPED_TEST_F(LlvmLibcFPBitwiseTest, Not, FPBitwiseTypes) { this->test_not(); }
+
+TEST(LlvmLibcFPBitwiseConstexprTest, ConstexprEval) {
+ static_assert(FPBits<double>(fp_xor(-1.5, 1.5)).uintval() ==
+ FPBits<double>::SIGN_MASK);
+ static_assert(FPBits<double>(fp_andnot(-1.5, -0.0)).uintval() ==
+ FPBits<double>(1.5).uintval());
+ static_assert(FPBits<double>(fp_or(1.5, -0.0)).uintval() ==
+ FPBits<double>(-1.5).uintval());
+ static_assert(FPBits<double>(fp_and(-1.5, -0.0)).uintval() ==
+ FPBits<double>::SIGN_MASK);
+ static_assert(FPBits<double>(fp_not(fp_not(1.5))).uintval() ==
+ FPBits<double>(1.5).uintval());
+
+ static_assert(FPBits<float>(fp_xor(-1.5f, 1.5f)).uintval() ==
+ FPBits<float>::SIGN_MASK);
+ static_assert(FPBits<float>(fp_andnot(-1.5f, -0.0f)).uintval() ==
+ FPBits<float>(1.5f).uintval());
+}
diff --git a/utils/bazel/llvm-project-overlay/libc/BUILD.bazel b/utils/bazel/llvm-project-overlay/libc/BUILD.bazel
index f2df1ef946618..2452f98e046d5 100644
--- a/utils/bazel/llvm-project-overlay/libc/BUILD.bazel
+++ b/utils/bazel/llvm-project-overlay/libc/BUILD.bazel
@@ -3377,6 +3377,19 @@ libc_support_library(
],
)
+libc_support_library(
+ name = "__support_fputil_fp_bitwise",
+ hdrs = ["src/__support/FPUtil/fp_bitwise.h"],
+ deps = [
+ ":__support_cpp_type_traits",
+ ":__support_fputil_fp_bits",
+ ":__support_macros_attributes",
+ ":__support_macros_config",
+ ":__support_macros_properties_architectures",
+ ":__support_macros_properties_cpu_features",
+ ],
+)
+
libc_support_library(
name = "__support_fputil_fpbits_str",
hdrs = ["src/__support/FPUtil/fpbits_str.h"],
diff --git a/utils/bazel/llvm-project-overlay/libc/test/src/__support/FPUtil/BUILD.bazel b/utils/bazel/llvm-project-overlay/libc/test/src/__support/FPUtil/BUILD.bazel
index 5f6b2806adf6a..fcaf22790bb34 100644
--- a/utils/bazel/llvm-project-overlay/libc/test/src/__support/FPUtil/BUILD.bazel
+++ b/utils/bazel/llvm-project-overlay/libc/test/src/__support/FPUtil/BUILD.bazel
@@ -23,6 +23,18 @@ libc_test(
],
)
+libc_test(
+ name = "fp_bitwise_test",
+ srcs = ["fp_bitwise_test.cpp"],
+ deps = [
+ "//libc:__support_fputil_bfloat16",
+ "//libc:__support_fputil_fp_bits",
+ "//libc:__support_fputil_fp_bitwise",
+ "//libc:__support_macros_properties_types",
+ "//libc/test/UnitTest:fp_test_helpers",
+ ],
+)
+
libc_test(
name = "bfloat16_test",
srcs = ["bfloat16_test.cpp"],
More information about the libc-commits
mailing list