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 <ja...@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 >