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
>

Reply via email to