public inbox for gcc-patches@gcc.gnu.org
 help / color / mirror / Atom feed
* [RFC] Add stdckdint.h header for C23
@ 2023-06-10 10:37 Jakub Jelinek
  2023-06-10 11:37 ` Jakub Jelinek
                   ` (2 more replies)
  0 siblings, 3 replies; 14+ messages in thread
From: Jakub Jelinek @ 2023-06-10 10:37 UTC (permalink / raw)
  To: Joseph S. Myers, Marek Polacek; +Cc: gcc-patches

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.

--- 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


^ permalink raw reply	[flat|nested] 14+ messages in thread

end of thread, other threads:[~2023-06-14 15:51 UTC | newest]

Thread overview: 14+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2023-06-10 10:37 [RFC] Add stdckdint.h header for C23 Jakub Jelinek
2023-06-10 11:37 ` Jakub Jelinek
2023-06-11 14:05   ` Martin Uecker
2023-06-12 10:35 ` Eric Gallager
2023-06-12 21:51 ` Joseph Myers
2023-06-13  6:28   ` Jakub Jelinek
2023-06-13 15:10     ` Joseph Myers
2023-06-13 15:20       ` Jakub Jelinek
2023-06-13 15:45         ` Joseph Myers
2023-06-14  2:54     ` Paul Eggert
2023-06-14  6:49       ` Jakub Jelinek
2023-06-14 11:46       ` Florian Weimer
2023-06-14 14:52       ` Joseph Myers
2023-06-14 15:50         ` Zack Weinberg

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).