[libcxx-commits] [libcxx] [libc++][math] Add `constexpr` for `std::signbit()` (PR #105946)

via libcxx-commits libcxx-commits at lists.llvm.org
Wed Aug 28 00:28:37 PDT 2024


llvmbot wrote:


<!--LLVM PR SUMMARY COMMENT-->

@llvm/pr-subscribers-libcxx

Author: Robin Caloudis (robincaloudis)

<details>
<summary>Changes</summary>

## Why
Since a few weeks (https://github.com/llvm/llvm-project/pull/94118), the floating point comparison builtin ``__builtin_signbit`` can be used in constant expressions.

## What
Implement `constexpr` for `std::signbit()` as defined by [P0533R9](https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2021/p0533r9.pdf) (new C++23 feature).

## Details
* Make use of `_LIBCPP_PREFERRED_OVERLOAD` as the universal C runtime (UCRT) needed for Clang-Cl comes with overloads for all cv-unqualified floating point types (float, double, long double) for `std::signbit()` [in the WinSDK](https://github.com/microsoft/win32metadata/blob/e012b29924c53aa941fc010850b68331b0c3ea80/generation/WinSDK/RecompiledIdlHeaders/ucrt/corecrt_math.h#L309-L322). In a certain way, this can be seen as a deviation from the C standard. We need to work around it as the compilation would otherwise error out due to duplicated definitions.
* Use `signcopy`-workaround for Clang versions that do not have a constexpr builtin of `signbit`

---
Full diff: https://github.com/llvm/llvm-project/pull/105946.diff


6 Files Affected:

- (modified) libcxx/docs/Status/Cxx23.rst (+1-1) 
- (modified) libcxx/include/__math/traits.h (+50-3) 
- (modified) libcxx/test/libcxx/numerics/c.math/constexpr-cxx23-clang.pass.cpp (+3-3) 
- (modified) libcxx/test/libcxx/numerics/c.math/constexpr-cxx23-gcc.pass.cpp (+3-3) 
- (added) libcxx/test/std/numerics/c.math/signbit.pass.cpp (+131) 
- (modified) libcxx/test/support/type_algorithms.h (+13-7) 


``````````diff
diff --git a/libcxx/docs/Status/Cxx23.rst b/libcxx/docs/Status/Cxx23.rst
index 1a8d43bff74752..e977da82884269 100644
--- a/libcxx/docs/Status/Cxx23.rst
+++ b/libcxx/docs/Status/Cxx23.rst
@@ -38,7 +38,7 @@ Paper Status
 
 .. note::
 
-   .. [#note-P0533R9] P0533R9: ``isfinite``, ``isinf``, ``isnan`` and ``isnormal`` are implemented.
+   .. [#note-P0533R9] P0533R9: ``isfinite``, ``isinf``, ``isnan``, ``isnormal`` and ``signbit`` are implemented.
    .. [#note-P1413R3] P1413R3: ``std::aligned_storage_t`` and ``std::aligned_union_t`` are marked deprecated, but
       clang doesn't issue a diagnostic for deprecated using template declarations.
    .. [#note-P2520R0] P2520R0: Libc++ implemented this paper as a DR in C++20 as well.
diff --git a/libcxx/include/__math/traits.h b/libcxx/include/__math/traits.h
index 35c283cc9e21ce..598ec4e35f88ea 100644
--- a/libcxx/include/__math/traits.h
+++ b/libcxx/include/__math/traits.h
@@ -29,17 +29,64 @@ namespace __math {
 // signbit
 
 template <class _A1, __enable_if_t<is_floating_point<_A1>::value, int> = 0>
-_LIBCPP_NODISCARD inline _LIBCPP_HIDE_FROM_ABI bool signbit(_A1 __x) _NOEXCEPT {
+_LIBCPP_NODISCARD inline _LIBCPP_CONSTEXPR_SINCE_CXX23 _LIBCPP_HIDE_FROM_ABI bool signbit(_A1 __x) _NOEXCEPT {
+// TODO(LLVM 22): Remove `__builtin_copysign`-workaround once support for Clang 19 is dropped.
+#if !__has_constexpr_builtin(__builtin_signbit) && _LIBCPP_STD_VER >= 23
+  return __builtin_copysign(1.0, __x) == -1.0;
+#else
   return __builtin_signbit(__x);
+#endif
+}
+
+_LIBCPP_NODISCARD inline _LIBCPP_CONSTEXPR_SINCE_CXX23 _LIBCPP_HIDE_FROM_ABI
+#ifdef _LIBCPP_PREFERRED_OVERLOAD
+_LIBCPP_PREFERRED_OVERLOAD
+#endif
+    bool
+    signbit(float __x) _NOEXCEPT {
+// TODO(LLVM 22): Remove `__builtin_copysign`-workaround once support for Clang 19 is dropped.
+#if !__has_constexpr_builtin(__builtin_signbit) && _LIBCPP_STD_VER >= 23
+  return __builtin_copysign(1.0, __x) == -1.0;
+#else
+  return __builtin_signbit(__x);
+#endif
+}
+
+_LIBCPP_NODISCARD inline _LIBCPP_CONSTEXPR_SINCE_CXX23 _LIBCPP_HIDE_FROM_ABI
+#ifdef _LIBCPP_PREFERRED_OVERLOAD
+_LIBCPP_PREFERRED_OVERLOAD
+#endif
+    bool
+    signbit(double __x) _NOEXCEPT {
+// TODO(LLVM 22): Remove `__builtin_copysign`-workaround once support for Clang 19 is dropped.
+#if !__has_constexpr_builtin(__builtin_signbit) && _LIBCPP_STD_VER >= 23
+  return __builtin_copysign(1.0, __x) == -1.0;
+#else
+  return __builtin_signbit(__x);
+#endif
+}
+
+_LIBCPP_NODISCARD inline _LIBCPP_CONSTEXPR_SINCE_CXX23 _LIBCPP_HIDE_FROM_ABI
+#ifdef _LIBCPP_PREFERRED_OVERLOAD
+_LIBCPP_PREFERRED_OVERLOAD
+#endif
+    bool
+    signbit(long double __x) _NOEXCEPT {
+// TODO(LLVM 22): Remove `__builtin_copysign`-workaround once support for Clang 19 is dropped.
+#if !__has_constexpr_builtin(__builtin_signbit) && _LIBCPP_STD_VER >= 23
+  return __builtin_copysign(1.0, __x) == -1.0;
+#else
+  return __builtin_signbit(__x);
+#endif
 }
 
 template <class _A1, __enable_if_t<is_integral<_A1>::value && is_signed<_A1>::value, int> = 0>
-_LIBCPP_NODISCARD inline _LIBCPP_HIDE_FROM_ABI bool signbit(_A1 __x) _NOEXCEPT {
+_LIBCPP_NODISCARD inline _LIBCPP_CONSTEXPR_SINCE_CXX23 _LIBCPP_HIDE_FROM_ABI bool signbit(_A1 __x) _NOEXCEPT {
   return __x < 0;
 }
 
 template <class _A1, __enable_if_t<is_integral<_A1>::value && !is_signed<_A1>::value, int> = 0>
-_LIBCPP_NODISCARD inline _LIBCPP_HIDE_FROM_ABI bool signbit(_A1) _NOEXCEPT {
+_LIBCPP_NODISCARD inline _LIBCPP_CONSTEXPR_SINCE_CXX23 _LIBCPP_HIDE_FROM_ABI bool signbit(_A1) _NOEXCEPT {
   return false;
 }
 
diff --git a/libcxx/test/libcxx/numerics/c.math/constexpr-cxx23-clang.pass.cpp b/libcxx/test/libcxx/numerics/c.math/constexpr-cxx23-clang.pass.cpp
index a07260a34516f1..20887b8cf2678b 100644
--- a/libcxx/test/libcxx/numerics/c.math/constexpr-cxx23-clang.pass.cpp
+++ b/libcxx/test/libcxx/numerics/c.math/constexpr-cxx23-clang.pass.cpp
@@ -220,9 +220,9 @@ int main(int, char**) {
   ASSERT_CONSTEXPR_CXX23(std::isnormal(-1.0) == 1);
   ASSERT_CONSTEXPR_CXX23(std::isnormal(-1.0L) == 1);
 
-  ASSERT_NOT_CONSTEXPR_CXX23(std::signbit(-1.0f) == 1);
-  ASSERT_NOT_CONSTEXPR_CXX23(std::signbit(-1.0) == 1);
-  ASSERT_NOT_CONSTEXPR_CXX23(std::signbit(-1.0L) == 1);
+  ASSERT_CONSTEXPR_CXX23(std::signbit(-1.0f) == 1);
+  ASSERT_CONSTEXPR_CXX23(std::signbit(-1.0) == 1);
+  ASSERT_CONSTEXPR_CXX23(std::signbit(-1.0L) == 1);
 
   ASSERT_NOT_CONSTEXPR_CXX23(std::isgreater(-1.0f, 0.0f) == 0);
   ASSERT_NOT_CONSTEXPR_CXX23(std::isgreater(-1.0, 0.0) == 0);
diff --git a/libcxx/test/libcxx/numerics/c.math/constexpr-cxx23-gcc.pass.cpp b/libcxx/test/libcxx/numerics/c.math/constexpr-cxx23-gcc.pass.cpp
index 8c481f41a945ed..d8779706bcee22 100644
--- a/libcxx/test/libcxx/numerics/c.math/constexpr-cxx23-gcc.pass.cpp
+++ b/libcxx/test/libcxx/numerics/c.math/constexpr-cxx23-gcc.pass.cpp
@@ -217,9 +217,9 @@ int main(int, char**) {
   ASSERT_CONSTEXPR_CXX23(std::isnormal(-1.0) == 1);
   ASSERT_CONSTEXPR_CXX23(std::isnormal(-1.0L) == 1);
 
-  ASSERT_NOT_CONSTEXPR_CXX23(std::signbit(-1.0f) == 1);
-  ASSERT_NOT_CONSTEXPR_CXX23(std::signbit(-1.0) == 1);
-  ASSERT_NOT_CONSTEXPR_CXX23(std::signbit(-1.0L) == 1);
+  ASSERT_CONSTEXPR_CXX23(std::signbit(-1.0f) == 1);
+  ASSERT_CONSTEXPR_CXX23(std::signbit(-1.0) == 1);
+  ASSERT_CONSTEXPR_CXX23(std::signbit(-1.0L) == 1);
 
   ASSERT_NOT_CONSTEXPR_CXX23(std::isgreater(-1.0f, 0.0f) == 0);
   ASSERT_NOT_CONSTEXPR_CXX23(std::isgreater(-1.0, 0.0) == 0);
diff --git a/libcxx/test/std/numerics/c.math/signbit.pass.cpp b/libcxx/test/std/numerics/c.math/signbit.pass.cpp
new file mode 100644
index 00000000000000..cdeab92e0836e2
--- /dev/null
+++ b/libcxx/test/std/numerics/c.math/signbit.pass.cpp
@@ -0,0 +1,131 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+
+// bool signbit(floating-point-type x); // constexpr since C++23
+
+// We don't control the implementation on windows
+// UNSUPPORTED: windows
+
+#include <cassert>
+#include <cmath>
+#include <limits>
+
+#include "test_macros.h"
+#include "type_algorithms.h"
+
+struct TestFloat {
+  template <class T>
+  static TEST_CONSTEXPR_CXX23 bool test() {
+    assert(!std::signbit(T(0)));
+    assert(!std::signbit(std::numeric_limits<T>::min()));
+    assert(!std::signbit(std::numeric_limits<T>::denorm_min()));
+    assert(!std::signbit(std::numeric_limits<T>::max()));
+    assert(!std::signbit(std::numeric_limits<T>::infinity()));
+    assert(!std::signbit(std::numeric_limits<T>::quiet_NaN()));
+    assert(!std::signbit(std::numeric_limits<T>::signaling_NaN()));
+    assert(std::signbit(-T(0)));
+    assert(std::signbit(-std::numeric_limits<T>::infinity()));
+    assert(std::signbit(std::numeric_limits<T>::lowest()));
+
+    return true;
+  }
+
+  template <class T>
+  TEST_CONSTEXPR_CXX23 void operator()() {
+    test<T>();
+#if TEST_STD_VER >= 23
+    static_assert(test<T>());
+#endif
+  }
+};
+
+struct TestUnsignedIntAndFixedWidthChar {
+  template <class T>
+  static TEST_CONSTEXPR_CXX23 bool test() {
+    assert(!std::signbit(std::numeric_limits<T>::max()));
+    assert(!std::signbit(T(0)));
+    assert(!std::signbit(std::numeric_limits<T>::lowest()));
+
+    return true;
+  }
+
+  template <class T>
+  TEST_CONSTEXPR_CXX23 void operator()() {
+    test<T>();
+#if TEST_STD_VER >= 23
+    static_assert(test<T>());
+#endif
+  }
+};
+
+struct TestSignedInt {
+  template <class T>
+  static TEST_CONSTEXPR_CXX23 bool test() {
+    assert(!std::signbit(std::numeric_limits<T>::max()));
+    assert(!std::signbit(T(0)));
+    assert(std::signbit(std::numeric_limits<T>::lowest()));
+
+    return true;
+  }
+
+  template <class T>
+  TEST_CONSTEXPR_CXX23 void operator()() {
+    test<T>();
+#if TEST_STD_VER >= 23
+    static_assert(test<T>());
+#endif
+  }
+};
+
+struct TestVariableWidthChar {
+  template <class T>
+  static TEST_CONSTEXPR_CXX23 bool test() {
+    assert(!std::signbit(std::numeric_limits<T>::max()));
+    assert(!std::signbit(T(0)));
+    // Signed or unsigned depending on the architecture and platform.
+    if (std::is_unsigned<T>::value) {
+      assert(!std::signbit(std::numeric_limits<T>::lowest()));
+    } else {
+      assert(std::signbit(std::numeric_limits<T>::lowest()));
+    }
+
+    return true;
+  }
+
+  template <class T>
+  TEST_CONSTEXPR_CXX23 void operator()() {
+    test<T>();
+#if TEST_STD_VER >= 23
+    static_assert(test<T>());
+#endif
+  }
+};
+
+template <typename T>
+struct ConvertibleTo {
+  operator T() const { return T(); }
+};
+
+int main(int, char**) {
+  types::for_each(types::floating_point_types(), TestFloat());
+  types::for_each(types::concatenate_t<types::unsigned_integer_types, types::fixed_width_character_types>(),
+                  TestUnsignedIntAndFixedWidthChar());
+  types::for_each(types::signed_integer_types(), TestSignedInt());
+  types::for_each(types::variable_width_character_types(), TestVariableWidthChar());
+
+  // Make sure we can call `std::signbit` with convertible types. This checks
+  // whether overloads for all cv-unqualified floating-point types are working
+  // as expected.
+  {
+    assert(!std::signbit(ConvertibleTo<float>()));
+    assert(!std::signbit(ConvertibleTo<double>()));
+    assert(!std::signbit(ConvertibleTo<long double>()));
+  }
+
+  return 0;
+}
diff --git a/libcxx/test/support/type_algorithms.h b/libcxx/test/support/type_algorithms.h
index da3d0add4d0c45..6b3b9aba11e5ec 100644
--- a/libcxx/test/support/type_algorithms.h
+++ b/libcxx/test/support/type_algorithms.h
@@ -82,22 +82,28 @@ struct partial_instantiation {
 
 // type categories defined in [basic.fundamental] plus extensions (without CV-qualifiers)
 
-using character_types =
+using variable_width_character_types =
     type_list<char
 #ifndef TEST_HAS_NO_WIDE_CHARACTERS
               ,
               wchar_t
 #endif
+              >;
+
+using fixed_width_character_types = type_list<
 #ifndef TEST_HAS_NO_CHAR8_T
-              ,
-              char8_t
+    char8_t
 #endif
 #if TEST_STD_VER >= 11
-              ,
-              char16_t,
-              char32_t
+#  ifndef TEST_HAS_NO_CHAR8_T
+    ,
+#  endif
+    char16_t,
+    char32_t
 #endif
-              >;
+    >;
+
+using character_types = concatenate_t<variable_width_character_types, fixed_width_character_types>;
 
 using signed_integer_types =
     type_list<signed char,

``````````

</details>


https://github.com/llvm/llvm-project/pull/105946


More information about the libcxx-commits mailing list