[RFC] Add stdckdint.h header for C23
Checks
Commit Message
Hi!
The following patch is an attempt to implement the C23 stdckdint.h
header on top of our GNU extension - __builtin_{add,sub,mul}_overflow
builtins.
I have looked at gnulib stdckdint.h and they are full of workarounds
for various compilers, EDG doesn't do this, clang <= 14 can't multiply
__int128, ..., so I think the header belongs into the compiler rather
than C library, because it would be a nightmare to maintain it there.
What I'm struggling with is enforcing the weird restrictions
C23 imposes on these.
The builtins error on the result pointer not being writable, or
having boolean or enumeral type (the reason for disallowing bool
was that it would be questionable whether it should act as if
storing to an unsigned 1-bit precision type which would overflow
if result is not in [0,1] or whether it would never overflow
for bool * result and simply store false if the infinite precision
result is 0 and true otherwise, and for enums because of the
uncertainities on just the enumerators vs. range from smallest to
largest enumerator vs. strict enum precision with underlying type).
They do allow storing result in plain char. And the source operands
can have any integral types, including plain char, including booleans
and including enumeral types. The plain is to allow even _BitInt(N)
as both source and result later on.
Now, C23 says that suitable types for both type2/type3 and type1
are integral types other than plain char, bool, a bit-precise integer type,
or an enumerated type.
And it also says:
It is recommended to produce a diagnostic message if type2 or type3 are
not suitable integer types, or if *result is not a modifiable lvalue of
a suitable integer type.
I've tried to first check it with:
static_assert (_Generic ((a), char: 0, const char: 0, volatile char: 0, const volatile char: 0,
default: __builtin_classify_type (a) - 1 <= 1U),
"...")
but that only catches plain char and doesn't catch _Bool/bool and
doesn't catch enumerated types (note, for the *result we diagnose
it for the builtins, but not for the other args), because
__builtin_classify_type sadly promotes its argument.
The _Generic in the patch below is slightly better, it catches
also _Bool/bool, but doesn't catch enumerated types, comptypes
used by _Generic says enumeral type is compatible with the underlying
integer type. But catching just plain char and bool would be
also doable with just _Generic listing the non-allowed types.
I think changing __builtin_classify_type behavior after 35 years
would be dangerous, shall we introduce a new similar builtin which
would just never promote the argument/perform array/function/enum
conversions on it, so that
__builtin_type_classify (true) == boolean_type_class
enum E { E1, E2 } e;
__builtin_type_classify (e) == enumeral_type_class
int a[2];
__builtin_type_classify (a) == array_type_class
etc.?
Seems clang changed __builtin_type_classify at some point
so that it e.g. returns enumeral_type_class for enum arguments
and array_type_class for arrays, but doesn't return boolean_type_class
for _Bool argument.
Also, shall we introduce __typeof_unqual__ keyword which could be used in
< C23 modes and perhaps C++?
2023-06-10 Jakub Jelinek <jakub@redhat.com>
* Makefile.in (USER_H): Add stdckdint.h.
* ginclude/stdckdint.h: New file.
* gcc.dg/stdckdint-1.c: New test.
* gcc.dg/stdckdint-2.c: New test.
Jakub
Comments
On Sat, Jun 10, 2023 at 12:37:40PM +0200, Jakub Jelinek via Gcc-patches wrote:
> I think changing __builtin_classify_type behavior after 35 years
> would be dangerous, shall we introduce a new similar builtin which
> would just never promote the argument/perform array/function/enum
> conversions on it, so that
> __builtin_type_classify (true) == boolean_type_class
> enum E { E1, E2 } e;
> __builtin_type_classify (e) == enumeral_type_class
> int a[2];
> __builtin_type_classify (a) == array_type_class
> etc.?
> Seems clang changed __builtin_type_classify at some point
> so that it e.g. returns enumeral_type_class for enum arguments
> and array_type_class for arrays, but doesn't return boolean_type_class
> for _Bool argument.
Another option would be just extend the current __builtin_classify_type
to be more sizeof like, that the argument could be either expression with
current behavior, or type, and so one could use
__builtin_classify_type (int)
or
__builtin_classify_type (0)
or
__builtin_classify_type (typeof (expr))
and the last way would ensure no argument promotion happens.
Jakub
Hi Jakup,
two comments which may or may not be helpful:
Clang extended _Generic in a similar way:
https://github.com/llvm/llvm-project/commit/12728e144994efe84715f4e5dbb8c3104e9f0b5a
Although for _Generic you can achieve the same with checking
for compatiblilty of pointer to the type, and I do not think
this helps with the classification problem.
If I am not missing something, you should be able to check
for an enumerated type using _Generic by checking that the
type is not compatible to another enum type:
enum type_check { _X = 1 };
#define type_is_enum(x) \
_Generic(x, unsigned int: _Generic(x, enum type_check: 0, default: 1), default: 0)
https://godbolt.org/z/j6z4a4Mdn
For C23 with fixed underlying type this may become more
complicated. Maybe this becomes to messy.
Martin
On Sat, Jun 10, 2023 at 6:38 AM Jakub Jelinek via Gcc-patches
<gcc-patches@gcc.gnu.org> wrote:
>
> Hi!
>
> The following patch is an attempt to implement the C23 stdckdint.h
> header on top of our GNU extension - __builtin_{add,sub,mul}_overflow
> builtins.
>
> I have looked at gnulib stdckdint.h and they are full of workarounds
> for various compilers, EDG doesn't do this, clang <= 14 can't multiply
> __int128, ..., so I think the header belongs into the compiler rather
> than C library, because it would be a nightmare to maintain it there.
>
> What I'm struggling with is enforcing the weird restrictions
> C23 imposes on these.
>
> The builtins error on the result pointer not being writable, or
> having boolean or enumeral type (the reason for disallowing bool
> was that it would be questionable whether it should act as if
> storing to an unsigned 1-bit precision type which would overflow
> if result is not in [0,1] or whether it would never overflow
> for bool * result and simply store false if the infinite precision
> result is 0 and true otherwise, and for enums because of the
> uncertainities on just the enumerators vs. range from smallest to
> largest enumerator vs. strict enum precision with underlying type).
> They do allow storing result in plain char. And the source operands
> can have any integral types, including plain char, including booleans
> and including enumeral types. The plain is to allow even _BitInt(N)
> as both source and result later on.
>
> Now, C23 says that suitable types for both type2/type3 and type1
> are integral types other than plain char, bool, a bit-precise integer type,
> or an enumerated type.
>
> And it also says:
> It is recommended to produce a diagnostic message if type2 or type3 are
> not suitable integer types, or if *result is not a modifiable lvalue of
> a suitable integer type.
>
> I've tried to first check it with:
> static_assert (_Generic ((a), char: 0, const char: 0, volatile char: 0, const volatile char: 0,
> default: __builtin_classify_type (a) - 1 <= 1U),
> "...")
> but that only catches plain char and doesn't catch _Bool/bool and
> doesn't catch enumerated types (note, for the *result we diagnose
> it for the builtins, but not for the other args), because
> __builtin_classify_type sadly promotes its argument.
>
> The _Generic in the patch below is slightly better, it catches
> also _Bool/bool, but doesn't catch enumerated types, comptypes
> used by _Generic says enumeral type is compatible with the underlying
> integer type. But catching just plain char and bool would be
> also doable with just _Generic listing the non-allowed types.
>
> I think changing __builtin_classify_type behavior after 35 years
> would be dangerous, shall we introduce a new similar builtin which
> would just never promote the argument/perform array/function/enum
> conversions on it, so that
> __builtin_type_classify (true) == boolean_type_class
> enum E { E1, E2 } e;
> __builtin_type_classify (e) == enumeral_type_class
> int a[2];
> __builtin_type_classify (a) == array_type_class
> etc.?
> Seems clang changed __builtin_type_classify at some point
> so that it e.g. returns enumeral_type_class for enum arguments
> and array_type_class for arrays, but doesn't return boolean_type_class
> for _Bool argument.
>
> Also, shall we introduce __typeof_unqual__ keyword which could be used in
> < C23 modes and perhaps C++?
>
I think I remember a desire for a __typeof_unqual__ keyword in other
contexts as well, too, so it would probably be worthwhile anyways...
> 2023-06-10 Jakub Jelinek <jakub@redhat.com>
>
> * Makefile.in (USER_H): Add stdckdint.h.
> * ginclude/stdckdint.h: New file.
>
> * gcc.dg/stdckdint-1.c: New test.
> * gcc.dg/stdckdint-2.c: New test.
>
> --- gcc/Makefile.in.jj 2023-06-06 20:02:35.581211930 +0200
> +++ gcc/Makefile.in 2023-06-10 10:17:05.062270115 +0200
> @@ -466,6 +466,7 @@ USER_H = $(srcdir)/ginclude/float.h \
> $(srcdir)/ginclude/stdnoreturn.h \
> $(srcdir)/ginclude/stdalign.h \
> $(srcdir)/ginclude/stdatomic.h \
> + $(srcdir)/ginclude/stdckdint.h \
> $(EXTRA_HEADERS)
>
> USER_H_INC_NEXT_PRE = @user_headers_inc_next_pre@
> --- gcc/ginclude/stdckdint.h.jj 2023-06-10 09:20:39.154053338 +0200
> +++ gcc/ginclude/stdckdint.h 2023-06-10 12:02:33.454947780 +0200
> @@ -0,0 +1,78 @@
> +/* Copyright (C) 2023 Free Software Foundation, Inc.
> +
> +This file is part of GCC.
> +
> +GCC is free software; you can redistribute it and/or modify
> +it under the terms of the GNU General Public License as published by
> +the Free Software Foundation; either version 3, or (at your option)
> +any later version.
> +
> +GCC is distributed in the hope that it will be useful,
> +but WITHOUT ANY WARRANTY; without even the implied warranty of
> +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
> +GNU General Public License for more details.
> +
> +Under Section 7 of GPL version 3, you are granted additional
> +permissions described in the GCC Runtime Library Exception, version
> +3.1, as published by the Free Software Foundation.
> +
> +You should have received a copy of the GNU General Public License and
> +a copy of the GCC Runtime Library Exception along with this program;
> +see the files COPYING3 and COPYING.RUNTIME respectively. If not, see
> +<http://www.gnu.org/licenses/>. */
> +
> +/* ISO C23: 7.20 Checked Integer Arithmetic <stdckdint.h>. */
> +
> +#ifndef _STDCKDINT_H
> +#define _STDCKDINT_H
> +
> +#define __STDC_VERSION_STDCKDINT_H__ 202311L
> +
> +#if __STDC_VERSION__ > 201710L
> +# ifdef __SIZEOF_INT20__
> +# define __ckd_type_int20 __int20: 1, unsigned __int20: 1,
> +# else
> +# define __ckd_type_int20
> +# endif
> +# ifdef __SIZEOF_INT128__
> +# define __ckd_type_int128 __int128: 1, unsigned __int128: 1,
> +# else
> +# define __ckd_type_int128
> +# endif
> +# define __ckd_type_check_1(a, n) \
> + static_assert (_Generic (((typeof_unqual (a)) 0), \
> + signed char: 1, \
> + unsigned char: 1, \
> + short: 1, \
> + unsigned short: 1, \
> + int: 1, \
> + unsigned int: 1, \
> + long: 1, \
> + unsigned long: 1, \
> + long long: 1, \
> + unsigned long long: 1, \
> + __ckd_type_int20 \
> + __ckd_type_int128 \
> + default: 0), \
> + "types used in " n " should be integral other than plain " \
> + "char, bool, bit-precise integer or enumerated type")
> +# define __ckd_type_check(r, a, b, n) \
> + (__extension__ ({ __ckd_type_check_1 ((r)[0], n); \
> + __ckd_type_check_1 (a, n); \
> + __ckd_type_check_1 (b, n); \
> + (r); }))
> +#else
> +# define __ckd_type_check(r, a, b, n) r
> +#endif
> +
> +#define ckd_add(r, a, b) \
> + ((_Bool) __builtin_add_overflow (a, b, \
> + __ckd_type_check (r, a, b, "ckd_add")))
> +#define ckd_sub(r, a, b) \
> + ((_Bool) __builtin_sub_overflow (a, b, \
> + __ckd_type_check (r, a, b, "ckd_sub")))
> +#define ckd_mul(r, a, b) \
> + ((_Bool) __builtin_mul_overflow (a, b, \
> + __ckd_type_check (r, a, b, "ckd_mul")))
> +
> +#endif /* stdckdint.h */
> --- gcc/testsuite/gcc.dg/stdckdint-1.c.jj 2023-06-10 10:04:23.547792318 +0200
> +++ gcc/testsuite/gcc.dg/stdckdint-1.c 2023-06-10 10:50:29.748579415 +0200
> @@ -0,0 +1,61 @@
> +/* Test C23 Checked Integer Arithmetic macros in <stdckdint.h>. */
> +/* { dg-do run } */
> +/* { dg-options "-std=c2x" } */
> +
> +#include <stdckdint.h>
> +
> +#if __STDC_VERSION_STDCKDINT_H__ != 202311L
> +# error __STDC_VERSION_STDCKDINT_H__ not defined to 202311L
> +#endif
> +
> +extern void abort (void);
> +
> +int
> +main ()
> +{
> + unsigned int a;
> + if (ckd_add (&a, 1, 2) || a != 3)
> + abort ();
> + if (ckd_add (&a, ~2U, 2) || a != ~0U)
> + abort ();
> + if (!ckd_add (&a, ~2U, 4) || a != 1)
> + abort ();
> + if (ckd_sub (&a, 42, 2) || a != 40)
> + abort ();
> + if (!ckd_sub (&a, 11, ~0ULL) || a != 12)
> + abort ();
> + if (ckd_mul (&a, 42, 16U) || a != 672)
> + abort ();
> + if (ckd_mul (&a, ~0UL, 0) || a != 0)
> + abort ();
> + if (ckd_mul (&a, 1, ~0U) || a != ~0U)
> + abort ();
> + if (ckd_mul (&a, ~0UL, 1) != (~0UL > ~0U) || a != ~0U)
> + abort ();
> + static_assert (_Generic (ckd_add (&a, 1, 1), bool: 1, default: 0));
> + static_assert (_Generic (ckd_sub (&a, 1, 1), bool: 1, default: 0));
> + static_assert (_Generic (ckd_mul (&a, 1, 1), bool: 1, default: 0));
> + signed char b;
> + if (ckd_add (&b, 8, 12) || b != 20)
> + abort ();
> + if (ckd_sub (&b, 8UL, 12ULL) || b != -4)
> + abort ();
> + if (ckd_mul (&b, 2, 3) || b != 6)
> + abort ();
> + unsigned char c;
> + if (ckd_add (&c, 8, 12) || c != 20)
> + abort ();
> + if (ckd_sub (&c, 8UL, 12ULL) != (-4ULL > (unsigned char) -4U)
> + || c != (unsigned char) -4U)
> + abort ();
> + if (ckd_mul (&c, 2, 3) || c != 6)
> + abort ();
> + long long d;
> + if (ckd_add (&d, ~0U, ~0U) != (~0U + 1ULL < ~0U)
> + || d != (long long) (2 * (unsigned long long) ~0U))
> + abort ();
> + if (ckd_sub (&d, 0, 0) || d != 0)
> + abort ();
> + if (ckd_mul (&d, 16, 1) || d != 16)
> + abort ();
> +}
> --- gcc/testsuite/gcc.dg/stdckdint-2.c.jj 2023-06-10 10:04:31.600681050 +0200
> +++ gcc/testsuite/gcc.dg/stdckdint-2.c 2023-06-10 12:00:25.560711350 +0200
> @@ -0,0 +1,53 @@
> +/* Test C23 Checked Integer Arithmetic macros in <stdckdint.h>. */
> +/* { dg-do run } */
> +/* { dg-options "-std=c2x" } */
> +
> +#include <stdckdint.h>
> +
> +int
> +main ()
> +{
> + char a;
> + bool b;
> + enum E { E1, E2 } c = E1;
> + int d;
> + ckd_add (&a, 1, 1); /* { dg-error "types used in ckd_add should be integral other than plain char, bool, bit-precise integer or enumerated type" } */
> + ckd_sub (&a, 1, 1); /* { dg-error "types used in ckd_sub should be integral other than plain char, bool, bit-precise integer or enumerated type" } */
> + ckd_mul (&a, 1, 1); /* { dg-error "types used in ckd_mul should be integral other than plain char, bool, bit-precise integer or enumerated type" } */
> + ckd_add (&b, 1, 1); /* { dg-error "types used in ckd_add should be integral other than plain char, bool, bit-precise integer or enumerated type" } */
> + /* { dg-error "has pointer to boolean type" "" { target *-*-* } .-1 } */
> + ckd_sub (&b, 1, 1); /* { dg-error "types used in ckd_sub should be integral other than plain char, bool, bit-precise integer or enumerated type" } */
> + /* { dg-error "has pointer to boolean type" "" { target *-*-* } .-1 } */
> + ckd_mul (&b, 1, 1); /* { dg-error "types used in ckd_mul should be integral other than plain char, bool, bit-precise integer or enumerated type" } */
> + /* { dg-error "has pointer to boolean type" "" { target *-*-* } .-1 } */
> + ckd_add (&c, 1, 1); /* { dg-error "types used in ckd_add should be integral other than plain char, bool, bit-precise integer or enumerated type" "" { xfail *-*-* } } */
> + /* { dg-error "has pointer to enumerated type" "" { target *-*-* } .-1 } */
> + ckd_sub (&c, 1, 1); /* { dg-error "types used in ckd_sub should be integral other than plain char, bool, bit-precise integer or enumerated type" "" { xfail *-*-* } } */
> + /* { dg-error "has pointer to enumerated type" "" { target *-*-* } .-1 } */
> + ckd_mul (&c, 1, 1); /* { dg-error "types used in ckd_mul should be integral other than plain char, bool, bit-precise integer or enumerated type" "" { xfail *-*-* } } */
> + /* { dg-error "has pointer to enumerated type" "" { target *-*-* } .-1 } */
> + ckd_add (&d, (char) 1, 1); /* { dg-error "types used in ckd_add should be integral other than plain char, bool, bit-precise integer or enumerated type" } */
> + ckd_sub (&d, (char) 1, 1); /* { dg-error "types used in ckd_sub should be integral other than plain char, bool, bit-precise integer or enumerated type" } */
> + ckd_mul (&d, (char) 1, 1); /* { dg-error "types used in ckd_mul should be integral other than plain char, bool, bit-precise integer or enumerated type" } */
> + ckd_add (&d, false, 1); /* { dg-error "types used in ckd_add should be integral other than plain char, bool, bit-precise integer or enumerated type" } */
> + ckd_sub (&d, false, 1); /* { dg-error "types used in ckd_sub should be integral other than plain char, bool, bit-precise integer or enumerated type" } */
> + ckd_mul (&d, false, 1); /* { dg-error "types used in ckd_mul should be integral other than plain char, bool, bit-precise integer or enumerated type" } */
> + ckd_add (&d, true, 1); /* { dg-error "types used in ckd_add should be integral other than plain char, bool, bit-precise integer or enumerated type" } */
> + ckd_sub (&d, true, 1); /* { dg-error "types used in ckd_sub should be integral other than plain char, bool, bit-precise integer or enumerated type" } */
> + ckd_mul (&d, true, 1); /* { dg-error "types used in ckd_mul should be integral other than plain char, bool, bit-precise integer or enumerated type" } */
> + ckd_add (&d, c, 1); /* { dg-error "types used in ckd_add should be integral other than plain char, bool, bit-precise integer or enumerated type" "" { xfail *-*-* } } */
> + ckd_sub (&d, c, 1); /* { dg-error "types used in ckd_sub should be integral other than plain char, bool, bit-precise integer or enumerated type" "" { xfail *-*-* } } */
> + ckd_mul (&d, c, 1); /* { dg-error "types used in ckd_mul should be integral other than plain char, bool, bit-precise integer or enumerated type" "" { xfail *-*-* } } */
> + ckd_add (&d, 1, (char) 1); /* { dg-error "types used in ckd_add should be integral other than plain char, bool, bit-precise integer or enumerated type" } */
> + ckd_sub (&d, 1, (char) 1); /* { dg-error "types used in ckd_sub should be integral other than plain char, bool, bit-precise integer or enumerated type" } */
> + ckd_mul (&d, 1, (char) 1); /* { dg-error "types used in ckd_mul should be integral other than plain char, bool, bit-precise integer or enumerated type" } */
> + ckd_add (&d, 1, false); /* { dg-error "types used in ckd_add should be integral other than plain char, bool, bit-precise integer or enumerated type" } */
> + ckd_sub (&d, 1, false); /* { dg-error "types used in ckd_sub should be integral other than plain char, bool, bit-precise integer or enumerated type" } */
> + ckd_mul (&d, 1, false); /* { dg-error "types used in ckd_mul should be integral other than plain char, bool, bit-precise integer or enumerated type" } */
> + ckd_add (&d, 1, true); /* { dg-error "types used in ckd_add should be integral other than plain char, bool, bit-precise integer or enumerated type" } */
> + ckd_sub (&d, 1, true); /* { dg-error "types used in ckd_sub should be integral other than plain char, bool, bit-precise integer or enumerated type" } */
> + ckd_mul (&d, 1, true); /* { dg-error "types used in ckd_mul should be integral other than plain char, bool, bit-precise integer or enumerated type" } */
> + ckd_add (&d, 1, c); /* { dg-error "types used in ckd_add should be integral other than plain char, bool, bit-precise integer or enumerated type" "" { xfail *-*-* } } */
> + ckd_sub (&d, 1, c); /* { dg-error "types used in ckd_sub should be integral other than plain char, bool, bit-precise integer or enumerated type" "" { xfail *-*-* } } */
> + ckd_mul (&d, 1, c); /* { dg-error "types used in ckd_mul should be integral other than plain char, bool, bit-precise integer or enumerated type" "" { xfail *-*-* } } */
> +}
>
> Jakub
>
On Sat, 10 Jun 2023, Jakub Jelinek via Gcc-patches wrote:
> I have looked at gnulib stdckdint.h and they are full of workarounds
> for various compilers, EDG doesn't do this, clang <= 14 can't multiply
> __int128, ..., so I think the header belongs into the compiler rather
> than C library, because it would be a nightmare to maintain it there.
While C2x only has type-generic macros in this header, there's a proposal
N2868 (which didn't get consensus for C2x but may come back for a future
standard version) for additional interfaces for structure types with a
sticky overflow flag, including some functions that are expected to be
defined with external linkage as usual for library functions. So if that
gets adopted in future, we'd need to arrange to provide those library
functions with external linkage - which is mostly not something we do in
GCC, although there are a few atomic_* functions in libatomic in addition
to the __atomic_* functions underlying type-generic macros.
> What I'm struggling with is enforcing the weird restrictions
> C23 imposes on these.
It's not clear all those restrictions need to be enforced - this
definitely seems like a case of undefined behavior to provide useful
extension space, where for various of those restrictions there are unique
sensible semantics to provide if the types in question are supported.
On Mon, Jun 12, 2023 at 09:51:02PM +0000, Joseph Myers wrote:
> On Sat, 10 Jun 2023, Jakub Jelinek via Gcc-patches wrote:
>
> > I have looked at gnulib stdckdint.h and they are full of workarounds
> > for various compilers, EDG doesn't do this, clang <= 14 can't multiply
> > __int128, ..., so I think the header belongs into the compiler rather
> > than C library, because it would be a nightmare to maintain it there.
>
> While C2x only has type-generic macros in this header, there's a proposal
> N2868 (which didn't get consensus for C2x but may come back for a future
> standard version) for additional interfaces for structure types with a
> sticky overflow flag, including some functions that are expected to be
> defined with external linkage as usual for library functions. So if that
> gets adopted in future, we'd need to arrange to provide those library
> functions with external linkage - which is mostly not something we do in
> GCC, although there are a few atomic_* functions in libatomic in addition
> to the __atomic_* functions underlying type-generic macros.
There is always the possibility to have the header co-owned by both
the compiler and C library, limits.h style.
Just
#if __has_include_next(<stdckdint.h>)
# include_next <stdckdint.h>
#endif
perhaps guarded with some macro at the end of the GCC version and
do the same at the start of the glibc version again perhaps with some macro.
And leave the compiler specific part to the compiler (perhaps with some
fallback in the libc version if the compiler specific part is missing) and
have the library related part be provided by the C library?
But if you want it solely in glibc, I can withdraw my patch (though, perhaps
the 2 preparation patches, __typeof_unqual__ and __builtin_classify_type
(typeof (...)) could be still of help for it).
> > What I'm struggling with is enforcing the weird restrictions
> > C23 imposes on these.
>
> It's not clear all those restrictions need to be enforced - this
> definitely seems like a case of undefined behavior to provide useful
> extension space, where for various of those restrictions there are unique
> sensible semantics to provide if the types in question are supported.
So why does C2X say
Recommended practice
It is recommended to produce a diagnostic message if type2 or type3 are
not suitable integer types, or if *result is not a modifiable lvalue of
a suitable integer type.
?
Or is it meant that a suitable integer type doesn't need to be necessarily
one that is listed in the previous paragraph?
Perhaps the checking could be guarded on #ifdef __STRICT_ANSI__, sure...
Jakub
On Tue, 13 Jun 2023, Jakub Jelinek via Gcc-patches wrote:
> There is always the possibility to have the header co-owned by both
> the compiler and C library, limits.h style.
> Just
> #if __has_include_next(<stdckdint.h>)
> # include_next <stdckdint.h>
> #endif
> perhaps guarded with some macro at the end of the GCC version and
> do the same at the start of the glibc version again perhaps with some macro.
> And leave the compiler specific part to the compiler (perhaps with some
> fallback in the libc version if the compiler specific part is missing) and
> have the library related part be provided by the C library?
This seems a reasonable approach. If the structure types do make it into
future standard versions, we'd need to work out exactly where the division
is between compiler and library header responsibilities (where those
pieces involving structure types but not library functions go, for
example). For operations using structure types with overflow flag we'd
need also to work out to what extent it's appropriate to implement those
purely in the header with _Generic versus adding new built-in functions or
extending what the existing ones can do.
> > > What I'm struggling with is enforcing the weird restrictions
> > > C23 imposes on these.
> >
> > It's not clear all those restrictions need to be enforced - this
> > definitely seems like a case of undefined behavior to provide useful
> > extension space, where for various of those restrictions there are unique
> > sensible semantics to provide if the types in question are supported.
>
> So why does C2X say
> Recommended practice
> It is recommended to produce a diagnostic message if type2 or type3 are
> not suitable integer types, or if *result is not a modifiable lvalue of
> a suitable integer type.
> ?
> Or is it meant that a suitable integer type doesn't need to be necessarily
> one that is listed in the previous paragraph?
> Perhaps the checking could be guarded on #ifdef __STRICT_ANSI__, sure...
Diagnostics are better than doing something completely random - but making
it conditional when there are sensible semantics also makes sense.
It seems likely a future standard version will support these operations
for bit-precise types, at least. (Bit-precise types are generally tricky
for type-generic operations; there's no standard way to select on them
with _Generic beyond listing individual types with specific widths, and no
standard way to determine the width of the bit-precise type of an
argument. So implementing some type-generic operations for such types may
need new language extensions, prompting WG14 caution about requiring such
support - but this also makes support for such types in standard
type-generic macros in headers particularly valuable, precisely because
they can't be implemented purely in user code using standard language
features.)
On Tue, Jun 13, 2023 at 03:10:48PM +0000, Joseph Myers wrote:
> > So why does C2X say
> > Recommended practice
> > It is recommended to produce a diagnostic message if type2 or type3 are
> > not suitable integer types, or if *result is not a modifiable lvalue of
> > a suitable integer type.
> > ?
> > Or is it meant that a suitable integer type doesn't need to be necessarily
> > one that is listed in the previous paragraph?
> > Perhaps the checking could be guarded on #ifdef __STRICT_ANSI__, sure...
>
> Diagnostics are better than doing something completely random - but making
> it conditional when there are sensible semantics also makes sense.
>
> It seems likely a future standard version will support these operations
> for bit-precise types, at least. (Bit-precise types are generally tricky
> for type-generic operations; there's no standard way to select on them
> with _Generic beyond listing individual types with specific widths, and no
> standard way to determine the width of the bit-precise type of an
> argument. So implementing some type-generic operations for such types may
> need new language extensions, prompting WG14 caution about requiring such
> support - but this also makes support for such types in standard
> type-generic macros in headers particularly valuable, precisely because
> they can't be implemented purely in user code using standard language
> features.)
Yeah, having say __builtin_{clz,ctz,ffs,popcount,parity} variants which would
be typegeneric and would allow say any normal integral or _BitInt type
(or just unsigned versions thereof?) would be useful. Even without _BitInt
we have the problem that we don't have builtins for __uint128_t.
One question is if we should keep them UB on zero input or hardcode some particular
behavior for clz/ctz. The backend defaults might not be appropriate, I
think if we'd make it non-UB, using the precision of the type would be
reasonable, whatever it is (__builtin_clzb ((unsigned _BitInt(126)) 0)
might be 126 etc.).
Jakub
On Tue, 13 Jun 2023, Jakub Jelinek via Gcc-patches wrote:
> Yeah, having say __builtin_{clz,ctz,ffs,popcount,parity} variants which would
> be typegeneric and would allow say any normal integral or _BitInt type
> (or just unsigned versions thereof?) would be useful. Even without _BitInt
> we have the problem that we don't have builtins for __uint128_t.
>
> One question is if we should keep them UB on zero input or hardcode some particular
> behavior for clz/ctz. The backend defaults might not be appropriate, I
> think if we'd make it non-UB, using the precision of the type would be
> reasonable, whatever it is (__builtin_clzb ((unsigned _BitInt(126)) 0)
> might be 126 etc.).
FWIW the C2x stdbit.h operations all have defined semantics on special
cases, except for the stdc_bit_ceil operations (where there's an NB
comment on CD2 to be considered at next week's WG14 meeting requesting
defined semantics there as well). They're also all for unsigned
arguments. (Note there are also NB comments requesting removal of some of
the operations as duplicates or near-duplicates of others.)
The stdbit.h header does seem naturally to be something for libc, given
that (a) it has a lot of functions, not just type-generic macros, and (b)
the type-generic macros are generally easy to implement (at least for the
types supported in the standard) in a way that doesn't depend on any
compiler extensions (or even on _Generic, many of them can be implemented
just to call the function for unsigned long long). It makes sense in due
course for GCC to know the names there (after any get removed) as built-in
functions, but mapping in a header to existing __builtin_* is generally
easy until then.
On 6/12/23 23:28, Jakub Jelinek via Libc-alpha wrote:
> On Mon, Jun 12, 2023 at 09:51:02PM +0000, Joseph Myers wrote:
>> On Sat, 10 Jun 2023, Jakub Jelinek via Gcc-patches wrote:
>>
>>> I have looked at gnulib stdckdint.h and they are full of workarounds
>>> for various compilers, EDG doesn't do this, clang <= 14 can't multiply
>>> __int128, ..., so I think the header belongs into the compiler rather
>>> than C library, because it would be a nightmare to maintain it there.
I tend to agree. I don't see how to implement <stdckdint.h> in the C
library, at least not for the C library's users.
It would be possible to implement <stdckdint.h> for C library internal
use only, because then we could assume #include_next, and we could use
the Gnulib implementation safely (that implementation is already present
glibc internals, just under a different name). This could well be worth
doing, because glibc internally needs ckd_add (or something equivalent)
but glibc can't yet assume that it's built with GCC 14 (or whatever GCC
version eventually supports <stdckdint.h>).
> There is always the possibility to have the header co-owned by both
> the compiler and C library, limits.h style.
> Just
> #if __has_include_next(<stdckdint.h>)
> # include_next <stdckdint.h>
> #endif
I don't see how you could implement __has_include_next(<stdckdint.h>)
for arbitrary non-GCC compilers, which is what we'd need for glibc
users. For glibc internals we can use "#include_next" more readily,
since we assume a new-enough GCC. I.e. we could do something like this:
#if 14 <= __GNUC__
# include_next <stdckdint.h>
#else
# define ckd_add(r, a, b) INT_ADD_WRAPV (a, b, &(r))
#endif
where INT_ADD_WRAPV is the already-existing glibc internal macro, and
where we invoke ckd_add only with arguments free of side effects.
On Tue, Jun 13, 2023 at 07:54:25PM -0700, Paul Eggert wrote:
> I don't see how you could implement __has_include_next(<stdckdint.h>) for
> arbitrary non-GCC compilers, which is what we'd need for glibc users. For
> glibc internals we can use "#include_next" more readily, since we assume a
> new-enough GCC. I.e. we could do something like this:
Indeed, limits.h uses
#if defined __GNUC__ && !defined _GCC_LIMITS_H_
/* `_GCC_LIMITS_H_' is what GCC's file defines. */
# include_next <limits.h>
#endif
I'd just do:
#if defined (__has_include_next) && !defined (ckd_add)
# if __has_include_next (<stdckdint.h>)
# include_next <stdckdint.h>
# endif
#endif
and then deal with the fallback (if the C library really needs one,
does it need to support a C23 feature in arbitrary compiler which
doesn't support C23?).
A fallback should start with using __builtin_{add,sub,mul}_overflow
without extra checking if compiler supports it, with or without
use of __has_builtin. GCC 6 and later has __builtin_{add,sub,mul}_overflow,
only GCC 10 has __has_builtin, I think clang has added those shortly
after they were added in GCC 6 (though, __builtin_{add,sub,mul}_overflow_p
which has been added in GCC 7 wasn't added to clang).
Jakub
* Paul Eggert:
> I don't see how you could implement __has_include_next(<stdckdint.h>)
> for arbitrary non-GCC compilers, which is what we'd need for glibc
> users.
This is not a requirement for glibc in general. For example, <math.h>
only works with compilers to which it has been ported.
Thanks,
Florian
On Tue, 13 Jun 2023, Paul Eggert wrote:
> > There is always the possibility to have the header co-owned by both
> > the compiler and C library, limits.h style.
> > Just
> > #if __has_include_next(<stdckdint.h>)
> > # include_next <stdckdint.h>
> > #endif
>
> I don't see how you could implement __has_include_next(<stdckdint.h>) for
> arbitrary non-GCC compilers, which is what we'd need for glibc users. For
> glibc internals we can use "#include_next" more readily, since we assume a
> new-enough GCC. I.e. we could do something like this:
Given the possibility of library functions being included in <stdckdint.h>
in future standard versions, it seems important to look at ways of
splitting responsibility for the header between the compiler and library,
whether with __has_include_next, or compiler version conditionals, or some
other such variation.
On Wed, Jun 14, 2023, at 10:52 AM, Joseph Myers wrote:
> On Tue, 13 Jun 2023, Paul Eggert wrote:
>
>> > There is always the possibility to have the header co-owned by both
>> > the compiler and C library, limits.h style. Just
>> > #if __has_include_next(<stdckdint.h>)
>> > # include_next <stdckdint.h>
>> > #endif
>>
>> I don't see how you could implement
>> __has_include_next(<stdckdint.h>) for arbitrary non-GCC compilers,
>> which is what we'd need for glibc users. For glibc internals we can
>> use "#include_next" more readily, since we assume a new-enough GCC.
>> I.e. we could do something like this:
>
> Given the possibility of library functions being included in
> <stdckdint.h> in future standard versions, it seems important to look
> at ways of splitting responsibility for the header between the
> compiler and library, whether with __has_include_next, or compiler
> version conditionals, or some other such variation.
limits.h is a horrible mess, with both the compiler and the library
trying to get the last word, and I don't think we should take it as a
model. I suggest a better model is GCC 12's stdint.h, where the compiler
provides "stdint-gcc.h" that's used *only* in freestanding mode, and a
wrapper header that defers to the C library (using #include_next, which
is safe in GCC-provided headers) when __STDC_HOSTED__ is defined;
meanwhile, glibc's stdint.h doesn't look for compiler-provided headers
at all.
In this case it sounds like glibc needs the compiler to provide some of
the definitions, and I suggest this should be done via private predefined
macros, in the mode of __INTx_TYPE__ and friends.
zw
@@ -466,6 +466,7 @@ USER_H = $(srcdir)/ginclude/float.h \
$(srcdir)/ginclude/stdnoreturn.h \
$(srcdir)/ginclude/stdalign.h \
$(srcdir)/ginclude/stdatomic.h \
+ $(srcdir)/ginclude/stdckdint.h \
$(EXTRA_HEADERS)
USER_H_INC_NEXT_PRE = @user_headers_inc_next_pre@
@@ -0,0 +1,78 @@
+/* Copyright (C) 2023 Free Software Foundation, Inc.
+
+This file is part of GCC.
+
+GCC is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 3, or (at your option)
+any later version.
+
+GCC is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+Under Section 7 of GPL version 3, you are granted additional
+permissions described in the GCC Runtime Library Exception, version
+3.1, as published by the Free Software Foundation.
+
+You should have received a copy of the GNU General Public License and
+a copy of the GCC Runtime Library Exception along with this program;
+see the files COPYING3 and COPYING.RUNTIME respectively. If not, see
+<http://www.gnu.org/licenses/>. */
+
+/* ISO C23: 7.20 Checked Integer Arithmetic <stdckdint.h>. */
+
+#ifndef _STDCKDINT_H
+#define _STDCKDINT_H
+
+#define __STDC_VERSION_STDCKDINT_H__ 202311L
+
+#if __STDC_VERSION__ > 201710L
+# ifdef __SIZEOF_INT20__
+# define __ckd_type_int20 __int20: 1, unsigned __int20: 1,
+# else
+# define __ckd_type_int20
+# endif
+# ifdef __SIZEOF_INT128__
+# define __ckd_type_int128 __int128: 1, unsigned __int128: 1,
+# else
+# define __ckd_type_int128
+# endif
+# define __ckd_type_check_1(a, n) \
+ static_assert (_Generic (((typeof_unqual (a)) 0), \
+ signed char: 1, \
+ unsigned char: 1, \
+ short: 1, \
+ unsigned short: 1, \
+ int: 1, \
+ unsigned int: 1, \
+ long: 1, \
+ unsigned long: 1, \
+ long long: 1, \
+ unsigned long long: 1, \
+ __ckd_type_int20 \
+ __ckd_type_int128 \
+ default: 0), \
+ "types used in " n " should be integral other than plain " \
+ "char, bool, bit-precise integer or enumerated type")
+# define __ckd_type_check(r, a, b, n) \
+ (__extension__ ({ __ckd_type_check_1 ((r)[0], n); \
+ __ckd_type_check_1 (a, n); \
+ __ckd_type_check_1 (b, n); \
+ (r); }))
+#else
+# define __ckd_type_check(r, a, b, n) r
+#endif
+
+#define ckd_add(r, a, b) \
+ ((_Bool) __builtin_add_overflow (a, b, \
+ __ckd_type_check (r, a, b, "ckd_add")))
+#define ckd_sub(r, a, b) \
+ ((_Bool) __builtin_sub_overflow (a, b, \
+ __ckd_type_check (r, a, b, "ckd_sub")))
+#define ckd_mul(r, a, b) \
+ ((_Bool) __builtin_mul_overflow (a, b, \
+ __ckd_type_check (r, a, b, "ckd_mul")))
+
+#endif /* stdckdint.h */
@@ -0,0 +1,61 @@
+/* Test C23 Checked Integer Arithmetic macros in <stdckdint.h>. */
+/* { dg-do run } */
+/* { dg-options "-std=c2x" } */
+
+#include <stdckdint.h>
+
+#if __STDC_VERSION_STDCKDINT_H__ != 202311L
+# error __STDC_VERSION_STDCKDINT_H__ not defined to 202311L
+#endif
+
+extern void abort (void);
+
+int
+main ()
+{
+ unsigned int a;
+ if (ckd_add (&a, 1, 2) || a != 3)
+ abort ();
+ if (ckd_add (&a, ~2U, 2) || a != ~0U)
+ abort ();
+ if (!ckd_add (&a, ~2U, 4) || a != 1)
+ abort ();
+ if (ckd_sub (&a, 42, 2) || a != 40)
+ abort ();
+ if (!ckd_sub (&a, 11, ~0ULL) || a != 12)
+ abort ();
+ if (ckd_mul (&a, 42, 16U) || a != 672)
+ abort ();
+ if (ckd_mul (&a, ~0UL, 0) || a != 0)
+ abort ();
+ if (ckd_mul (&a, 1, ~0U) || a != ~0U)
+ abort ();
+ if (ckd_mul (&a, ~0UL, 1) != (~0UL > ~0U) || a != ~0U)
+ abort ();
+ static_assert (_Generic (ckd_add (&a, 1, 1), bool: 1, default: 0));
+ static_assert (_Generic (ckd_sub (&a, 1, 1), bool: 1, default: 0));
+ static_assert (_Generic (ckd_mul (&a, 1, 1), bool: 1, default: 0));
+ signed char b;
+ if (ckd_add (&b, 8, 12) || b != 20)
+ abort ();
+ if (ckd_sub (&b, 8UL, 12ULL) || b != -4)
+ abort ();
+ if (ckd_mul (&b, 2, 3) || b != 6)
+ abort ();
+ unsigned char c;
+ if (ckd_add (&c, 8, 12) || c != 20)
+ abort ();
+ if (ckd_sub (&c, 8UL, 12ULL) != (-4ULL > (unsigned char) -4U)
+ || c != (unsigned char) -4U)
+ abort ();
+ if (ckd_mul (&c, 2, 3) || c != 6)
+ abort ();
+ long long d;
+ if (ckd_add (&d, ~0U, ~0U) != (~0U + 1ULL < ~0U)
+ || d != (long long) (2 * (unsigned long long) ~0U))
+ abort ();
+ if (ckd_sub (&d, 0, 0) || d != 0)
+ abort ();
+ if (ckd_mul (&d, 16, 1) || d != 16)
+ abort ();
+}
@@ -0,0 +1,53 @@
+/* Test C23 Checked Integer Arithmetic macros in <stdckdint.h>. */
+/* { dg-do run } */
+/* { dg-options "-std=c2x" } */
+
+#include <stdckdint.h>
+
+int
+main ()
+{
+ char a;
+ bool b;
+ enum E { E1, E2 } c = E1;
+ int d;
+ ckd_add (&a, 1, 1); /* { dg-error "types used in ckd_add should be integral other than plain char, bool, bit-precise integer or enumerated type" } */
+ ckd_sub (&a, 1, 1); /* { dg-error "types used in ckd_sub should be integral other than plain char, bool, bit-precise integer or enumerated type" } */
+ ckd_mul (&a, 1, 1); /* { dg-error "types used in ckd_mul should be integral other than plain char, bool, bit-precise integer or enumerated type" } */
+ ckd_add (&b, 1, 1); /* { dg-error "types used in ckd_add should be integral other than plain char, bool, bit-precise integer or enumerated type" } */
+ /* { dg-error "has pointer to boolean type" "" { target *-*-* } .-1 } */
+ ckd_sub (&b, 1, 1); /* { dg-error "types used in ckd_sub should be integral other than plain char, bool, bit-precise integer or enumerated type" } */
+ /* { dg-error "has pointer to boolean type" "" { target *-*-* } .-1 } */
+ ckd_mul (&b, 1, 1); /* { dg-error "types used in ckd_mul should be integral other than plain char, bool, bit-precise integer or enumerated type" } */
+ /* { dg-error "has pointer to boolean type" "" { target *-*-* } .-1 } */
+ ckd_add (&c, 1, 1); /* { dg-error "types used in ckd_add should be integral other than plain char, bool, bit-precise integer or enumerated type" "" { xfail *-*-* } } */
+ /* { dg-error "has pointer to enumerated type" "" { target *-*-* } .-1 } */
+ ckd_sub (&c, 1, 1); /* { dg-error "types used in ckd_sub should be integral other than plain char, bool, bit-precise integer or enumerated type" "" { xfail *-*-* } } */
+ /* { dg-error "has pointer to enumerated type" "" { target *-*-* } .-1 } */
+ ckd_mul (&c, 1, 1); /* { dg-error "types used in ckd_mul should be integral other than plain char, bool, bit-precise integer or enumerated type" "" { xfail *-*-* } } */
+ /* { dg-error "has pointer to enumerated type" "" { target *-*-* } .-1 } */
+ ckd_add (&d, (char) 1, 1); /* { dg-error "types used in ckd_add should be integral other than plain char, bool, bit-precise integer or enumerated type" } */
+ ckd_sub (&d, (char) 1, 1); /* { dg-error "types used in ckd_sub should be integral other than plain char, bool, bit-precise integer or enumerated type" } */
+ ckd_mul (&d, (char) 1, 1); /* { dg-error "types used in ckd_mul should be integral other than plain char, bool, bit-precise integer or enumerated type" } */
+ ckd_add (&d, false, 1); /* { dg-error "types used in ckd_add should be integral other than plain char, bool, bit-precise integer or enumerated type" } */
+ ckd_sub (&d, false, 1); /* { dg-error "types used in ckd_sub should be integral other than plain char, bool, bit-precise integer or enumerated type" } */
+ ckd_mul (&d, false, 1); /* { dg-error "types used in ckd_mul should be integral other than plain char, bool, bit-precise integer or enumerated type" } */
+ ckd_add (&d, true, 1); /* { dg-error "types used in ckd_add should be integral other than plain char, bool, bit-precise integer or enumerated type" } */
+ ckd_sub (&d, true, 1); /* { dg-error "types used in ckd_sub should be integral other than plain char, bool, bit-precise integer or enumerated type" } */
+ ckd_mul (&d, true, 1); /* { dg-error "types used in ckd_mul should be integral other than plain char, bool, bit-precise integer or enumerated type" } */
+ ckd_add (&d, c, 1); /* { dg-error "types used in ckd_add should be integral other than plain char, bool, bit-precise integer or enumerated type" "" { xfail *-*-* } } */
+ ckd_sub (&d, c, 1); /* { dg-error "types used in ckd_sub should be integral other than plain char, bool, bit-precise integer or enumerated type" "" { xfail *-*-* } } */
+ ckd_mul (&d, c, 1); /* { dg-error "types used in ckd_mul should be integral other than plain char, bool, bit-precise integer or enumerated type" "" { xfail *-*-* } } */
+ ckd_add (&d, 1, (char) 1); /* { dg-error "types used in ckd_add should be integral other than plain char, bool, bit-precise integer or enumerated type" } */
+ ckd_sub (&d, 1, (char) 1); /* { dg-error "types used in ckd_sub should be integral other than plain char, bool, bit-precise integer or enumerated type" } */
+ ckd_mul (&d, 1, (char) 1); /* { dg-error "types used in ckd_mul should be integral other than plain char, bool, bit-precise integer or enumerated type" } */
+ ckd_add (&d, 1, false); /* { dg-error "types used in ckd_add should be integral other than plain char, bool, bit-precise integer or enumerated type" } */
+ ckd_sub (&d, 1, false); /* { dg-error "types used in ckd_sub should be integral other than plain char, bool, bit-precise integer or enumerated type" } */
+ ckd_mul (&d, 1, false); /* { dg-error "types used in ckd_mul should be integral other than plain char, bool, bit-precise integer or enumerated type" } */
+ ckd_add (&d, 1, true); /* { dg-error "types used in ckd_add should be integral other than plain char, bool, bit-precise integer or enumerated type" } */
+ ckd_sub (&d, 1, true); /* { dg-error "types used in ckd_sub should be integral other than plain char, bool, bit-precise integer or enumerated type" } */
+ ckd_mul (&d, 1, true); /* { dg-error "types used in ckd_mul should be integral other than plain char, bool, bit-precise integer or enumerated type" } */
+ ckd_add (&d, 1, c); /* { dg-error "types used in ckd_add should be integral other than plain char, bool, bit-precise integer or enumerated type" "" { xfail *-*-* } } */
+ ckd_sub (&d, 1, c); /* { dg-error "types used in ckd_sub should be integral other than plain char, bool, bit-precise integer or enumerated type" "" { xfail *-*-* } } */
+ ckd_mul (&d, 1, c); /* { dg-error "types used in ckd_mul should be integral other than plain char, bool, bit-precise integer or enumerated type" "" { xfail *-*-* } } */
+}