[01/14] c++: Implement __is_integral built-in trait

Message ID 20240110194031.2384005-2-kmatsui@gcc.gnu.org
State New
Headers
Series Optimize integral-related type traits |

Commit Message

Ken Matsui Jan. 10, 2024, 9:22 a.m. UTC
  This patch implements built-in trait for std::is_integral.

gcc/cp/ChangeLog:

	* cp-trait.def: Define __is_integral.
	* constraint.cc (diagnose_trait_expr): Handle
	CPTK_IS_INTEGRAL.
	* semantics.cc (trait_expr_value): Likewise.
	(finish_trait_expr): Likewise.
	(integral_type_p): New function.

gcc/testsuite/ChangeLog:

	* g++.dg/ext/has-builtin-1.C: Test existence of __is_integral.
	* g++.dg/ext/is_integral.C: New test.

Signed-off-by: Ken Matsui <kmatsui@gcc.gnu.org>
---
 gcc/cp/constraint.cc                     |  3 ++
 gcc/cp/cp-trait.def                      |  1 +
 gcc/cp/semantics.cc                      | 18 +++++++++
 gcc/testsuite/g++.dg/ext/has-builtin-1.C |  3 ++
 gcc/testsuite/g++.dg/ext/is_integral.C   | 49 ++++++++++++++++++++++++
 5 files changed, 74 insertions(+)
 create mode 100644 gcc/testsuite/g++.dg/ext/is_integral.C
  

Comments

Jason Merrill Jan. 17, 2024, 12:30 a.m. UTC | #1
On 1/10/24 04:22, Ken Matsui wrote:
> +/* Return true if T is an integral type.  With __STRICT_ANSI__, __int128 and
> +   unsigned __int128 are not integral types.  */

This really needs a rationale, since they are actually integer types.  I 
know __int128 is considered an extension rather than an extended integer 
type under the standard, but is there a writeup we can point to for why?

And even if we don't want to subject it to all the standard requirements 
of an extended integer type, why not still say it's an integral type? 
flag_iso is only supposed to disable features that could conflict with 
obscure but standard-conforming code, and since __int128 is in the 
reserved namespace, I'd think it should be safe to support (to the 
degree that we do) regardless of flag_iso.

Jason
  
Jonathan Wakely Jan. 17, 2024, 7:42 a.m. UTC | #2
On Wed, 17 Jan 2024 at 00:31, Jason Merrill wrote:
>
> On 1/10/24 04:22, Ken Matsui wrote:
> > +/* Return true if T is an integral type.  With __STRICT_ANSI__, __int128 and
> > +   unsigned __int128 are not integral types.  */
>
> This really needs a rationale, since they are actually integer types.  I
> know __int128 is considered an extension rather than an extended integer
> type under the standard, but is there a writeup we can point to for why?
>
> And even if we don't want to subject it to all the standard requirements
> of an extended integer type, why not still say it's an integral type?
> flag_iso is only supposed to disable features that could conflict with
> obscure but standard-conforming code, and since __int128 is in the
> reserved namespace, I'd think it should be safe to support (to the
> degree that we do) regardless of flag_iso.

The reason for __int128 not being an integral type is because the
standard says that intmax_t must be the largest standard or extended
integer type, and intmax_t is fixed by ABI to be a 64-bit type. As a
result, GCC has historically said that 128-bit integer types are not
part of the "standard or extended integer type"classification, in
Joseph's words they're sui generis types. But C2x and C++23 changed
this, and now we can just do the obvious, simple thing and say that
128-bit integer types are integer types.
This changed with https://cplusplus.github.io/LWG/issue3828 for C++.

So we can remove the dependency on __STRICT_ISO__ for 128-bit integer
types, and implementing std::is_integral with a built-in seems like
the perfect time to do that. But that seems like stage 1 material, as
we need to go through the library and see what needs to change.
  
Joseph Myers Jan. 17, 2024, 11:28 a.m. UTC | #3
On Wed, 17 Jan 2024, Jonathan Wakely wrote:

> So we can remove the dependency on __STRICT_ISO__ for 128-bit integer
> types, and implementing std::is_integral with a built-in seems like
> the perfect time to do that. But that seems like stage 1 material, as
> we need to go through the library and see what needs to change.

As noted on IRC, for C23 there would also be library issues in making 
__int128 an extended integer type.  If it's an extended integer type, then 
C23 would require <stdint.h> to define int128_t, uint128_t, int_least128_t 
and uint_least128_t, along with the macros INT128_WIDTH, UINT128_WIDTH, 
INT_LEAST128_WIDTH, UINT_LEAST128_WIDTH (trivial), and INT128_C and 
UINT128_C (require an integer constant suffix), and INT128_MAX, 
INT128_MIN, UINT128_MAX, INT_LEAST128_MAX, INT_LEAST128_MIN, 
UINT_LEAST128_MAX (most simply defined using an integer constant suffix, 
though don't strictly require one).  And <inttypes.h> would have to define 
all the printf and scanf format macros for int128_t, uint128_t, 
int_least128_t and uint_least128_t - so library support would be needed 
for those (the format macros themselves should probably expand to "w128d" 
and similar, a C23 feature already supported for narrower types by glibc 
and by GCC format checking, rather than inventing new features there).

So because an extended integer type (without padding bits) in C23 is 
expected to have all the library support from <stdint.h> and <inttypes.h>, 
you need integer constant suffixes and printf/scanf support before you can 
declare __int128 an extended integer type for C23.

(If adding printf and scanf support for int128_t to glibc, it probably 
makes sense to add user-visible functions such as strtoi128 at the same 
time - no such functions are in the standard, but something like them 
would be needed internally as part of the scanf implementation, and it's 
likely they would be useful for users as well.)
  

Patch

diff --git a/gcc/cp/constraint.cc b/gcc/cp/constraint.cc
index fef68cf7ab2..3a105a2ee2a 100644
--- a/gcc/cp/constraint.cc
+++ b/gcc/cp/constraint.cc
@@ -3755,6 +3755,9 @@  diagnose_trait_expr (tree expr, tree args)
     case CPTK_IS_FUNCTION:
       inform (loc, "  %qT is not a function", t1);
       break;
+    case CPTK_IS_INTEGRAL:
+      inform (loc, "  %qT is not an integral type", t1);
+      break;
     case CPTK_IS_LAYOUT_COMPATIBLE:
       inform (loc, "  %qT is not layout compatible with %qT", t1, t2);
       break;
diff --git a/gcc/cp/cp-trait.def b/gcc/cp/cp-trait.def
index 394f006f20f..2773c3fa10e 100644
--- a/gcc/cp/cp-trait.def
+++ b/gcc/cp/cp-trait.def
@@ -70,6 +70,7 @@  DEFTRAIT_EXPR (IS_EMPTY, "__is_empty", 1)
 DEFTRAIT_EXPR (IS_ENUM, "__is_enum", 1)
 DEFTRAIT_EXPR (IS_FINAL, "__is_final", 1)
 DEFTRAIT_EXPR (IS_FUNCTION, "__is_function", 1)
+DEFTRAIT_EXPR (IS_INTEGRAL, "__is_integral", 1)
 DEFTRAIT_EXPR (IS_LAYOUT_COMPATIBLE, "__is_layout_compatible", 2)
 DEFTRAIT_EXPR (IS_LITERAL_TYPE, "__is_literal_type", 1)
 DEFTRAIT_EXPR (IS_MEMBER_FUNCTION_POINTER, "__is_member_function_pointer", 1)
diff --git a/gcc/cp/semantics.cc b/gcc/cp/semantics.cc
index 3299e270446..1a335f69826 100644
--- a/gcc/cp/semantics.cc
+++ b/gcc/cp/semantics.cc
@@ -12258,6 +12258,20 @@  is_corresponding_member_aggr (location_t loc, tree basetype1, tree membertype1,
   return ret;
 }
 
+/* Return true if T is an integral type.  With __STRICT_ANSI__, __int128 and
+   unsigned __int128 are not integral types.  */
+
+static bool
+integral_type_p (const_tree t)
+{
+  if (flag_iso)
+    return CP_INTEGRAL_TYPE_P (t)
+      && t != intTI_type_node
+      && t != unsigned_intTI_type_node;
+  else
+    return CP_INTEGRAL_TYPE_P (t);
+}
+
 /* Fold __builtin_is_corresponding_member call.  */
 
 tree
@@ -12465,6 +12479,9 @@  trait_expr_value (cp_trait_kind kind, tree type1, tree type2)
     case CPTK_IS_FUNCTION:
       return type_code1 == FUNCTION_TYPE;
 
+    case CPTK_IS_INTEGRAL:
+      return integral_type_p (type1);
+
     case CPTK_IS_LAYOUT_COMPATIBLE:
       return layout_compatible_type_p (type1, type2);
 
@@ -12691,6 +12708,7 @@  finish_trait_expr (location_t loc, cp_trait_kind kind, tree type1, tree type2)
     case CPTK_IS_CLASS:
     case CPTK_IS_ENUM:
     case CPTK_IS_FUNCTION:
+    case CPTK_IS_INTEGRAL:
     case CPTK_IS_MEMBER_FUNCTION_POINTER:
     case CPTK_IS_MEMBER_OBJECT_POINTER:
     case CPTK_IS_MEMBER_POINTER:
diff --git a/gcc/testsuite/g++.dg/ext/has-builtin-1.C b/gcc/testsuite/g++.dg/ext/has-builtin-1.C
index 02b4b4d745d..d621171481c 100644
--- a/gcc/testsuite/g++.dg/ext/has-builtin-1.C
+++ b/gcc/testsuite/g++.dg/ext/has-builtin-1.C
@@ -89,6 +89,9 @@ 
 #if !__has_builtin (__is_function)
 # error "__has_builtin (__is_function) failed"
 #endif
+#if !__has_builtin (__is_integral)
+# error "__has_builtin (__is_integral) failed"
+#endif
 #if !__has_builtin (__is_layout_compatible)
 # error "__has_builtin (__is_layout_compatible) failed"
 #endif
diff --git a/gcc/testsuite/g++.dg/ext/is_integral.C b/gcc/testsuite/g++.dg/ext/is_integral.C
new file mode 100644
index 00000000000..d2c732133dd
--- /dev/null
+++ b/gcc/testsuite/g++.dg/ext/is_integral.C
@@ -0,0 +1,49 @@ 
+// { dg-do compile { target c++11 } }
+
+#define SA(X) static_assert((X),#X)
+
+#define SA_TEST_CATEGORY(TRAIT, TYPE, EXPECT)	\
+  SA(TRAIT(TYPE) == EXPECT);			\
+  SA(TRAIT(const TYPE) == EXPECT);		\
+  SA(TRAIT(volatile TYPE) == EXPECT);		\
+  SA(TRAIT(const volatile TYPE) == EXPECT)
+
+SA_TEST_CATEGORY(__is_integral, void, false);
+
+SA_TEST_CATEGORY(__is_integral, char, true);
+SA_TEST_CATEGORY(__is_integral, signed char, true);
+SA_TEST_CATEGORY(__is_integral, unsigned char, true);
+SA_TEST_CATEGORY(__is_integral, wchar_t, true);
+#ifdef _GLIBCXX_USE_CHAR8_T
+SA_TEST_CATEGORY(__is_integral, char8_t, true);
+#endif
+SA_TEST_CATEGORY(__is_integral, char16_t, true);
+SA_TEST_CATEGORY(__is_integral, char32_t, true);
+SA_TEST_CATEGORY(__is_integral, short, true);
+SA_TEST_CATEGORY(__is_integral, unsigned short, true);
+SA_TEST_CATEGORY(__is_integral, int, true);
+SA_TEST_CATEGORY(__is_integral, unsigned int, true);
+SA_TEST_CATEGORY(__is_integral, long, true);
+SA_TEST_CATEGORY(__is_integral, unsigned long, true);
+SA_TEST_CATEGORY(__is_integral, long long, true);
+SA_TEST_CATEGORY(__is_integral, unsigned long long, true);
+
+SA_TEST_CATEGORY(__is_integral, float, false);
+SA_TEST_CATEGORY(__is_integral, double, false);
+SA_TEST_CATEGORY(__is_integral, long double, false);
+
+#ifndef __STRICT_ANSI__
+// GNU Extensions.
+#ifdef __SIZEOF_INT128__
+SA_TEST_CATEGORY(__is_integral, __int128, true);
+SA_TEST_CATEGORY(__is_integral, unsigned __int128, true);
+#endif
+
+#ifdef _GLIBCXX_USE_FLOAT128
+SA_TEST_CATEGORY(__is_integral, __float128, false);
+#endif
+#endif
+
+// Sanity check.
+class ClassType { };
+SA_TEST_CATEGORY(__is_integral, ClassType, false);