Skip to content

[libc] Added support for fixed-points in is_signed and is_unsigned. #133371

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 9 commits into from
Apr 2, 2025

Conversation

kr-2003
Copy link
Contributor

@kr-2003 kr-2003 commented Mar 28, 2025

Fixes #133365

Changes Done

  • Changed the signed checking to
struct is_signed : bool_constant<((is_fixed_point<T> || is_arithmetic_v<T>) && (T(-1) < T(0)))>

in /libc/src/__support/CPP/type_traits/is_signed.h. Added check for fixed-points.

  • But, got to know that this will fail for unsigned _Fract or any unsigned fixed-point because unsigned _Fract can’t represent -1 in T(-1), while unsigned int can handle it via wrapping.
  • That's why I explicity added is_signed check for unsigned fixed-points.
  • Same changes to /libc/src/__support/CPP/type_traits/is_unsigned.h.
  • Added tests for is_signed and is_unsigned.

@llvmbot llvmbot added the libc label Mar 28, 2025
@llvmbot
Copy link
Member

llvmbot commented Mar 28, 2025

@llvm/pr-subscribers-libc

Author: Abhinav Kumar (kr-2003)

Changes

Fixes #133365


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

3 Files Affected:

  • (modified) libc/src/__support/CPP/type_traits/is_signed.h (+67-2)
  • (modified) libc/src/__support/CPP/type_traits/is_unsigned.h (+67-2)
  • (modified) libc/test/src/__support/CPP/type_traits_test.cpp (+59-2)
diff --git a/libc/src/__support/CPP/type_traits/is_signed.h b/libc/src/__support/CPP/type_traits/is_signed.h
index 3f56fb38aabb0..699b017eda183 100644
--- a/libc/src/__support/CPP/type_traits/is_signed.h
+++ b/libc/src/__support/CPP/type_traits/is_signed.h
@@ -8,20 +8,85 @@
 #ifndef LLVM_LIBC_SRC___SUPPORT_CPP_TYPE_TRAITS_IS_SIGNED_H
 #define LLVM_LIBC_SRC___SUPPORT_CPP_TYPE_TRAITS_IS_SIGNED_H
 
+#include "include/llvm-libc-macros/stdfix-macros.h"
 #include "src/__support/CPP/type_traits/bool_constant.h"
 #include "src/__support/CPP/type_traits/is_arithmetic.h"
+#include "src/__support/CPP/type_traits/is_fixed_point.h"
 #include "src/__support/macros/attributes.h"
 #include "src/__support/macros/config.h"
 
 namespace LIBC_NAMESPACE_DECL {
 namespace cpp {
 
-// is_signed
+// Primary template: handles arithmetic and signed fixed-point types
 template <typename T>
-struct is_signed : bool_constant<(is_arithmetic_v<T> && (T(-1) < T(0)))> {
+struct is_signed : bool_constant<((is_fixed_point_v<T> || is_arithmetic_v<T>) && (T(-1) < T(0)))> {
   LIBC_INLINE constexpr operator bool() const { return is_signed::value; }
   LIBC_INLINE constexpr bool operator()() const { return is_signed::value; }
 };
+
+// Specializations for unsigned fixed-point types
+template <>
+struct is_signed<unsigned short fract> : bool_constant<false> {
+  LIBC_INLINE constexpr operator bool() const { return is_signed::value; }
+  LIBC_INLINE constexpr bool operator()() const { return is_signed::value; }
+};
+template <>
+struct is_signed<unsigned fract> : bool_constant<false> {
+  LIBC_INLINE constexpr operator bool() const { return is_signed::value; }
+  LIBC_INLINE constexpr bool operator()() const { return is_signed::value; }
+};
+template <>
+struct is_signed<unsigned long fract> : bool_constant<false> {
+  LIBC_INLINE constexpr operator bool() const { return is_signed::value; }
+  LIBC_INLINE constexpr bool operator()() const { return is_signed::value; }
+};
+template <>
+struct is_signed<unsigned short accum> : bool_constant<false> {
+  LIBC_INLINE constexpr operator bool() const { return is_signed::value; }
+  LIBC_INLINE constexpr bool operator()() const { return is_signed::value; }
+};
+template <>
+struct is_signed<unsigned accum> : bool_constant<false> {
+  LIBC_INLINE constexpr operator bool() const { return is_signed::value; }
+  LIBC_INLINE constexpr bool operator()() const { return is_signed::value; }
+};
+template <>
+struct is_signed<unsigned long accum> : bool_constant<false> {
+  LIBC_INLINE constexpr operator bool() const { return is_signed::value; }
+  LIBC_INLINE constexpr bool operator()() const { return is_signed::value; }
+};
+template <>
+struct is_signed<unsigned short sat fract> : bool_constant<false> {
+  LIBC_INLINE constexpr operator bool() const { return is_signed::value; }
+  LIBC_INLINE constexpr bool operator()() const { return is_signed::value; }
+};
+template <>
+struct is_signed<unsigned sat fract> : bool_constant<false> {
+  LIBC_INLINE constexpr operator bool() const { return is_signed::value; }
+  LIBC_INLINE constexpr bool operator()() const { return is_signed::value; }
+};
+template <>
+struct is_signed<unsigned long sat fract> : bool_constant<false> {
+  LIBC_INLINE constexpr operator bool() const { return is_signed::value; }
+  LIBC_INLINE constexpr bool operator()() const { return is_signed::value; }
+};
+template <>
+struct is_signed<unsigned short sat accum> : bool_constant<false> {
+  LIBC_INLINE constexpr operator bool() const { return is_signed::value; }
+  LIBC_INLINE constexpr bool operator()() const { return is_signed::value; }
+};
+template <>
+struct is_signed<unsigned sat accum> : bool_constant<false> {
+  LIBC_INLINE constexpr operator bool() const { return is_signed::value; }
+  LIBC_INLINE constexpr bool operator()() const { return is_signed::value; }
+};
+template <>
+struct is_signed<unsigned long sat accum> : bool_constant<false> {
+  LIBC_INLINE constexpr operator bool() const { return is_signed::value; }
+  LIBC_INLINE constexpr bool operator()() const { return is_signed::value; }
+};
+
 template <typename T>
 LIBC_INLINE_VAR constexpr bool is_signed_v = is_signed<T>::value;
 
diff --git a/libc/src/__support/CPP/type_traits/is_unsigned.h b/libc/src/__support/CPP/type_traits/is_unsigned.h
index eed519b1c067e..58cd3f0f598a9 100644
--- a/libc/src/__support/CPP/type_traits/is_unsigned.h
+++ b/libc/src/__support/CPP/type_traits/is_unsigned.h
@@ -8,20 +8,85 @@
 #ifndef LLVM_LIBC_SRC___SUPPORT_CPP_TYPE_TRAITS_IS_UNSIGNED_H
 #define LLVM_LIBC_SRC___SUPPORT_CPP_TYPE_TRAITS_IS_UNSIGNED_H
 
+#include "include/llvm-libc-macros/stdfix-macros.h"
 #include "src/__support/CPP/type_traits/bool_constant.h"
 #include "src/__support/CPP/type_traits/is_arithmetic.h"
+#include "src/__support/CPP/type_traits/is_fixed_point.h"
 #include "src/__support/macros/attributes.h"
 #include "src/__support/macros/config.h"
 
 namespace LIBC_NAMESPACE_DECL {
 namespace cpp {
 
-// is_unsigned
+// Primary template: handles arithmetic and signed fixed-point types
 template <typename T>
-struct is_unsigned : bool_constant<(is_arithmetic_v<T> && (T(-1) > T(0)))> {
+struct is_unsigned : bool_constant<((is_fixed_point_v<T> || is_arithmetic_v<T>) && (T(-1) > T(0)))> {
   LIBC_INLINE constexpr operator bool() const { return is_unsigned::value; }
   LIBC_INLINE constexpr bool operator()() const { return is_unsigned::value; }
 };
+
+// Specializations for unsigned fixed-point types
+template <>
+struct is_unsigned<unsigned short fract> : bool_constant<true> {
+  LIBC_INLINE constexpr operator bool() const { return is_unsigned::value; }
+  LIBC_INLINE constexpr bool operator()() const { return is_unsigned::value; }
+};
+template <>
+struct is_unsigned<unsigned fract> : bool_constant<true> {
+  LIBC_INLINE constexpr operator bool() const { return is_unsigned::value; }
+  LIBC_INLINE constexpr bool operator()() const { return is_unsigned::value; }
+};
+template <>
+struct is_unsigned<unsigned long fract> : bool_constant<true> {
+  LIBC_INLINE constexpr operator bool() const { return is_unsigned::value; }
+  LIBC_INLINE constexpr bool operator()() const { return is_unsigned::value; }
+};
+template <>
+struct is_unsigned<unsigned short accum> : bool_constant<true> {
+  LIBC_INLINE constexpr operator bool() const { return is_unsigned::value; }
+  LIBC_INLINE constexpr bool operator()() const { return is_unsigned::value; }
+};
+template <>
+struct is_unsigned<unsigned accum> : bool_constant<true> {
+  LIBC_INLINE constexpr operator bool() const { return is_unsigned::value; }
+  LIBC_INLINE constexpr bool operator()() const { return is_unsigned::value; }
+};
+template <>
+struct is_unsigned<unsigned long accum> : bool_constant<true> {
+  LIBC_INLINE constexpr operator bool() const { return is_unsigned::value; }
+  LIBC_INLINE constexpr bool operator()() const { return is_unsigned::value; }
+};
+template <>
+struct is_unsigned<unsigned short sat fract> : bool_constant<true> {
+  LIBC_INLINE constexpr operator bool() const { return is_unsigned::value; }
+  LIBC_INLINE constexpr bool operator()() const { return is_unsigned::value; }
+};
+template <>
+struct is_unsigned<unsigned sat fract> : bool_constant<true> {
+  LIBC_INLINE constexpr operator bool() const { return is_unsigned::value; }
+  LIBC_INLINE constexpr bool operator()() const { return is_unsigned::value; }
+};
+template <>
+struct is_unsigned<unsigned long sat fract> : bool_constant<true> {
+  LIBC_INLINE constexpr operator bool() const { return is_unsigned::value; }
+  LIBC_INLINE constexpr bool operator()() const { return is_unsigned::value; }
+};
+template <>
+struct is_unsigned<unsigned short sat accum> : bool_constant<true> {
+  LIBC_INLINE constexpr operator bool() const { return is_unsigned::value; }
+  LIBC_INLINE constexpr bool operator()() const { return is_unsigned::value; }
+};
+template <>
+struct is_unsigned<unsigned sat accum> : bool_constant<true> {
+  LIBC_INLINE constexpr operator bool() const { return is_unsigned::value; }
+  LIBC_INLINE constexpr bool operator()() const { return is_unsigned::value; }
+};
+template <>
+struct is_unsigned<unsigned long sat accum> : bool_constant<true> {
+  LIBC_INLINE constexpr operator bool() const { return is_unsigned::value; }
+  LIBC_INLINE constexpr bool operator()() const { return is_unsigned::value; }
+};
+
 template <typename T>
 LIBC_INLINE_VAR constexpr bool is_unsigned_v = is_unsigned<T>::value;
 
diff --git a/libc/test/src/__support/CPP/type_traits_test.cpp b/libc/test/src/__support/CPP/type_traits_test.cpp
index 4b3e48c6a6c0f..338705efa5e80 100644
--- a/libc/test/src/__support/CPP/type_traits_test.cpp
+++ b/libc/test/src/__support/CPP/type_traits_test.cpp
@@ -6,6 +6,7 @@
 //
 //===----------------------------------------------------------------------===//
 
+#include "include/llvm-libc-macros/stdfix-macros.h"
 #include "src/__support/CPP/type_traits.h"
 #include "src/__support/macros/config.h"
 #include "test/UnitTest/Test.h"
@@ -409,7 +410,35 @@ TEST(LlvmLibcTypeTraitsTest, is_object) {
 
 // TODO is_scalar
 
-// TODO is_signed
+TEST(LlvmLibcTypeTraitsTest, is_signed) {
+  EXPECT_TRUE((is_signed_v<int>));
+  EXPECT_TRUE((is_signed_v<long>));
+  EXPECT_TRUE((is_signed_v<long long>));
+  EXPECT_FALSE((is_signed_v<unsigned int>));
+  EXPECT_FALSE((is_signed_v<unsigned long>));
+  EXPECT_FALSE((is_signed_v<unsigned long long>));
+  EXPECT_TRUE((is_signed_v<float>));
+  EXPECT_TRUE((is_signed_v<double>));
+  EXPECT_TRUE((is_signed_v<long double>));
+
+  // for fixed point types
+  EXPECT_TRUE((is_signed_v<fract>));
+  EXPECT_FALSE((is_signed_v<unsigned fract>));
+  EXPECT_TRUE((is_signed_v<accum>));
+  EXPECT_FALSE((is_signed_v<unsigned accum>));
+  EXPECT_TRUE((is_signed_v<sat fract>));
+  EXPECT_FALSE((is_signed_v<unsigned sat fract>));
+  EXPECT_TRUE((is_signed_v<sat accum>));
+  EXPECT_FALSE((is_signed_v<unsigned sat accum>));
+  EXPECT_TRUE((is_signed_v<short fract>));
+  EXPECT_FALSE((is_signed_v<unsigned short fract>));
+  EXPECT_TRUE((is_signed_v<short accum>));
+  EXPECT_FALSE((is_signed_v<unsigned short accum>));
+  EXPECT_TRUE((is_signed_v<long fract>));
+  EXPECT_FALSE((is_signed_v<unsigned long fract>));
+  EXPECT_TRUE((is_signed_v<long accum>));
+  EXPECT_FALSE((is_signed_v<unsigned long accum>));
+}
 
 // TODO is_trivially_constructible
 
@@ -419,7 +448,35 @@ TEST(LlvmLibcTypeTraitsTest, is_object) {
 
 // TODO is_union
 
-// TODO is_unsigned
+TEST(LlvmLibcTypeTraitsTest, is_unsigned) {
+  EXPECT_FALSE((is_unsigned_v<int>));
+  EXPECT_FALSE((is_unsigned_v<long>));
+  EXPECT_FALSE((is_unsigned_v<long long>));
+  EXPECT_TRUE((is_unsigned_v<unsigned int>));
+  EXPECT_TRUE((is_unsigned_v<unsigned long>));
+  EXPECT_TRUE((is_unsigned_v<unsigned long long>));
+  EXPECT_FALSE((is_unsigned_v<float>));
+  EXPECT_FALSE((is_unsigned_v<double>));
+  EXPECT_FALSE((is_unsigned_v<long double>));
+
+  // for fixed point types
+  EXPECT_FALSE((is_unsigned_v<fract>));
+  EXPECT_TRUE((is_unsigned_v<unsigned fract>));
+  EXPECT_FALSE((is_unsigned_v<accum>));
+  EXPECT_TRUE((is_unsigned_v<unsigned accum>));
+  EXPECT_FALSE((is_unsigned_v<sat fract>));
+  EXPECT_TRUE((is_unsigned_v<unsigned sat fract>));
+  EXPECT_FALSE((is_unsigned_v<sat accum>));
+  EXPECT_TRUE((is_unsigned_v<unsigned sat accum>));
+  EXPECT_FALSE((is_unsigned_v<short fract>));
+  EXPECT_TRUE((is_unsigned_v<unsigned short fract>));
+  EXPECT_FALSE((is_unsigned_v<short accum>));
+  EXPECT_TRUE((is_unsigned_v<unsigned short accum>));
+  EXPECT_FALSE((is_unsigned_v<long fract>));
+  EXPECT_TRUE((is_unsigned_v<unsigned long fract>));
+  EXPECT_FALSE((is_unsigned_v<long accum>));
+  EXPECT_TRUE((is_unsigned_v<unsigned long accum>));
+}
 
 // TODO is_void
 

Copy link

github-actions bot commented Mar 28, 2025

✅ With the latest revision this PR passed the C/C++ code formatter.

@kr-2003 kr-2003 changed the title Added support of fixed-points in is_signed and is_unsigned. Added support for fixed-points in is_signed and is_unsigned. Mar 28, 2025
@kr-2003 kr-2003 changed the title Added support for fixed-points in is_signed and is_unsigned. [libv] Added support for fixed-points in is_signed and is_unsigned. Mar 28, 2025
@kr-2003 kr-2003 changed the title [libv] Added support for fixed-points in is_signed and is_unsigned. [libc] Added support for fixed-points in is_signed and is_unsigned. Mar 28, 2025
template <typename T>
struct is_signed : bool_constant<(is_arithmetic_v<T> && (T(-1) < T(0)))> {
struct is_signed : bool_constant<((is_fixed_point_v<T> || is_arithmetic_v<T>) &&
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

probably you could change it to:

(is_fixed_point_v<T> && T(-0.5) < T(0)) || (is_arithmetic_v<T> && T(-1) < T(0))

and remove all the fixed point specializations.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it won't work. even if the value is just less than 0, it will try to wrap around in unsigned type(and its not possible in fixed-points). I have tried it out.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@PiJoules is there problem with constexpr for fixed point type in clang?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's the failure mode? Is it a compilation error or failure in one of the tests?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's the failure mode? Is it a compilation error or failure in one of the tests?

i have explained here #133680

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Replied to the issue.

tl;dr overflow for non-saturating types isn't defined, so unsigned _Fract(-1) can't be constant evaluated. If you want to check if a type is signed, I think you can do FXRep<T>::MIN() < FXRep::ZERO(). I also need to update the diagnostic since it's misleading.

Comment on lines 22 to 26
template <typename T>
struct is_signed : bool_constant<(is_arithmetic_v<T> && (T(-1) < T(0)))> {
LIBC_INLINE constexpr operator bool() const { return is_signed::value; }
LIBC_INLINE constexpr bool operator()() const { return is_signed::value; }
};
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So I just tested locally, and the following seems to work without all the specializations:

struct is_signed : bool_constant<
    (is_fixed_point_v<T> && T(-0.5) < T(0)) ||
    (is_arithmetic_v<T> && (T(-1) < T(0)))> {
    ...
};

Copy link
Contributor Author

@kr-2003 kr-2003 Mar 28, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

really? i think i am missing out on something, because this is what i get when i try this on my machine:

/Users/llvm-project/libc/src/__support/CPP/type_traits/is_signed.h:23:34: error: non-type template argument is not a constant expression
   23 | struct is_signed : bool_constant<(is_fixed_point_v<T> && T(-0.5) < T(0)) ||
      |                                  ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
   24 |                                  (is_arithmetic_v<T> && (T(-1) < T(0)))> {
      |                                  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/Users/llvm-project/libc/src/__support/CPP/type_traits/is_signed.h:105:46: note: in instantiation of template class '__llvm_libc_21_0_0_git::cpp::is_signed<unsigned _Fract>' requested here
  105 | LIBC_INLINE_VAR constexpr bool is_signed_v = is_signed<T>::value;
      |                                              ^
/Users/llvm-project/libc/test/src/__support/CPP/type_traits_test.cpp:427:17: note: in instantiation of variable template specialization '__llvm_libc_21_0_0_git::cpp::is_signed_v<unsigned _Fract>' requested here
  427 |   EXPECT_FALSE((is_signed_v<unsigned fract>));
/Users/llvm-project/libc/src/__support/CPP/type_traits/is_signed.h:23:60: note: value 0.0 is outside the range of representable values of type 'unsigned _Fract'
   23 | struct is_signed : bool_constant<(is_fixed_point_v<T> && T(-0.5) < T(0)) ||

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So it looks like static_assert with that works fine, but not with EXPECT_EQ in the tests that you added. Probably something is missing for fixed point support in constexpr context.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So it looks like static_assert with that works fine, but not with EXPECT_EQ in the tests that you added. Probably something is missing for fixed point support in constexpr context.

opened issue at #133680

@PiJoules
Copy link
Contributor

I think we'll want the saturated version of each of these also

@PiJoules PiJoules self-requested a review March 28, 2025 19:10
Copy link
Contributor

@lntue lntue left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM. Let's wait for @PiJoules comments.

Copy link
Contributor

@PiJoules PiJoules left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM assuming you're not blocked on that compilation issue anymore

@lntue lntue merged commit 51d1c72 into llvm:main Apr 2, 2025
16 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

[libc] std::is_signed_v<T> for any fixed-point always returns false
4 participants