[v17,2/2] c: Add __countof__ operator

Message ID 936f7945fae5ce7b5ca705a968af7c87474cc64c.1729622352.git.alx@kernel.org
State New
Headers
Series c: Add __countof__ operator |

Checks

Context Check Description
linaro-tcwg-bot/tcwg_gcc_build--master-arm success Build passed
linaro-tcwg-bot/tcwg_gcc_build--master-aarch64 success Build passed

Commit Message

Alejandro Colomar Oct. 22, 2024, 6:48 p.m. UTC
  This operator is similar to sizeof but can only be applied to an array,
and returns its number of elements.

FUTURE DIRECTIONS:

-  We should make it work with array parameters to functions,
   and somehow magically return the number of elements of the array,
   regardless of it being really a pointer.

-  Fix support for [0].

gcc/ChangeLog:

	* doc/extend.texi: Document __countof__ operator.

gcc/c-family/ChangeLog:

	* c-common.h
	* c-common.def
	* c-common.cc (c_countof_type): Add __countof__ operator.

gcc/c/ChangeLog:

	* c-tree.h
	(c_expr_countof_expr, c_expr_countof_type)
	* c-decl.cc
	(start_struct, finish_struct)
	(start_enum, finish_enum)
	* c-parser.cc
	(c_parser_sizeof_expression)
	(c_parser_countof_expression)
	(c_parser_sizeof_or_countof_expression)
	(c_parser_unary_expression)
	* c-typeck.cc
	(build_external_ref)
	(record_maybe_used_decl)
	(pop_maybe_used)
	(is_top_array_vla)
	(c_expr_countof_expr, c_expr_countof_type):
	Add __countof__ operator.

gcc/testsuite/ChangeLog:

	* gcc.dg/countof-compile.c
	* gcc.dg/countof-vla.c
	* gcc.dg/countof.c: Add tests for __countof__ operator.

Link: <https://gcc.gnu.org/bugzilla/show_bug.cgi?id=117025>
Link: <https://inbox.sourceware.org/gcc/M8S4oQy--3-2@tutanota.com/T/>
Link: <https://inbox.sourceware.org/gcc-patches/20240728141547.302478-1-alx@kernel.org/T/#t>
Link: <https://www.open-std.org/jtc1/sc22/wg14/www/docs/n3313.pdf>
Link: <https://www.open-std.org/jtc1/sc22/wg14/www/docs/n3325.pdf>
Link: <https://www.open-std.org/jtc1/sc22/wg14/www/docs/n3369.pdf>
Link: <https://github.com/llvm/llvm-project/issues/102836>
Link: <https://stackoverflow.com/questions/37538/#57537491>
Suggested-by: Xavier Del Campo Romero <xavi.dcr@tutanota.com>
Co-authored-by: Martin Uecker <uecker@tugraz.at>
Acked-by: "James K. Lowden" <jklowden@schemamania.org>
Cc: Joseph Myers <josmyers@redhat.com>
Cc: Gabriel Ravier <gabravier@gmail.com>
Cc: Jakub Jelinek <jakub@redhat.com>
Cc: Kees Cook <keescook@chromium.org>
Cc: Qing Zhao <qing.zhao@oracle.com>
Cc: Jens Gustedt <jens.gustedt@inria.fr>
Cc: David Brown <david.brown@hesbynett.no>
Cc: Florian Weimer <fweimer@redhat.com>
Cc: Andreas Schwab <schwab@linux-m68k.org>
Cc: Timm Baeder <tbaeder@redhat.com>
Cc: Daniel Plakosh <dplakosh@cert.org>
Cc: "A. Jiang" <de34@live.cn>
Cc: Eugene Zelenko <eugene.zelenko@gmail.com>
Cc: Aaron Ballman <aaron.ballman@intel.com>
Cc: Paul Koning <paulkoning@comcast.net>
Cc: Daniel Lundin <daniel.lundin.mail@gmail.com>
Cc: Nikolaos Strimpas <Strnik86@protonmail.com>
Cc: JeanHeyd Meneide <phdofthehouse@gmail.com>
Cc: Fernando Borretti <fernando@borretti.me>
Cc: Jonathan Protzenko <jonathan.protzenko@ens-lyon.org>
Cc: Chris Bazley <Chris.Bazley@arm.com>
Cc: Ville Voutilainen <ville.voutilainen@gmail.com>
Cc: Alex Celeste <alexg.nvfp@gmail.com>
Cc: Jakub Łukasiewicz <jakublukasiewicz@outlook.com>
Cc: Douglas McIlroy <douglas.mcilroy@dartmouth.edu>
Cc: Jason Merrill <jason@redhat.com>
Cc: "Gustavo A. R. Silva" <gustavoars@kernel.org>
Cc: Patrizia Kaye <patrizia@ethernull.org>
Cc: Ori Bernstein <ori@eigenstate.org>
Cc: Robert Seacord <rcseacord@gmail.com>
Cc: Marek Polacek <mpolacek@gcc.gnu.org>
Cc: Sam James <sam@gentoo.org>
Cc: Richard Biener <richard.guenther@gmail.com>
Signed-off-by: Alejandro Colomar <alx@kernel.org>
---
 gcc/c-family/c-common.cc               |  26 +++++
 gcc/c-family/c-common.def              |   3 +
 gcc/c-family/c-common.h                |   2 +
 gcc/c/c-decl.cc                        |  22 +++-
 gcc/c/c-parser.cc                      |  62 +++++++---
 gcc/c/c-tree.h                         |   4 +
 gcc/c/c-typeck.cc                      | 118 ++++++++++++++++++-
 gcc/doc/extend.texi                    |  30 +++++
 gcc/testsuite/gcc.dg/countof-compile.c | 127 +++++++++++++++++++++
 gcc/testsuite/gcc.dg/countof-vla.c     |  46 ++++++++
 gcc/testsuite/gcc.dg/countof.c         | 150 +++++++++++++++++++++++++
 11 files changed, 566 insertions(+), 24 deletions(-)
 create mode 100644 gcc/testsuite/gcc.dg/countof-compile.c
 create mode 100644 gcc/testsuite/gcc.dg/countof-vla.c
 create mode 100644 gcc/testsuite/gcc.dg/countof.c
  

Comments

Joseph Myers Oct. 25, 2024, 8:44 p.m. UTC | #1
I don't see the use of pedwarn_c23 and associated tests (error with 
-std=c23 -pedantic-errors, warning with -std=c23 -pedantic, no diagnostic 
with -std=c23 -pedantic-errors -Wno-c23-c2y-compat, no diagnostic with 
-std=c2y -pedantic-errors, warning with -std=c2y -pedantic-errors 
-Wc23-c2y-compat), previously discussed in comments on v13, that would be 
appropriate before considering this for inclusion with an appropriate 
substitution of names.
  
Alejandro Colomar Oct. 25, 2024, 10:10 p.m. UTC | #2
Hi Joseph,

On Fri, Oct 25, 2024 at 08:44:15PM GMT, Joseph Myers wrote:
> I don't see the use of pedwarn_c23 and associated tests (error with 
> -std=c23 -pedantic-errors, warning with -std=c23 -pedantic, no diagnostic 
> with -std=c23 -pedantic-errors -Wno-c23-c2y-compat, no diagnostic with 
> -std=c2y -pedantic-errors, warning with -std=c2y -pedantic-errors 
> -Wc23-c2y-compat), previously discussed in comments on v13, that would be 
> appropriate before considering this for inclusion with an appropriate 
> substitution of names.

I removed it because I renamed it to __countof__, which is a GNU
extension, and thus should not be warned by -Wpedantic.  As part of my
opposition to _Lengthof, I will not provide you with that part, which
would amount to basically giving you _Lengthof but not.  As part of the
editorialising process, you'll also have to add pedantic warnings, if
that's what you want to do.  Again, I will earnestly ask to once more to
consider __countof__, but it's up to you.

Have a lovely night!
Alex
  
Alejandro Colomar Nov. 8, 2024, 2 p.m. UTC | #3
Hi Joseph,

This is a gentle ping about this patch set, 10 days before the start of
stage 3.

Have a lovely day!
Alex

On Sat, Oct 26, 2024 at 12:10:56AM GMT, Alejandro Colomar wrote:
> Hi Joseph,
> 
> On Fri, Oct 25, 2024 at 08:44:15PM GMT, Joseph Myers wrote:
> > I don't see the use of pedwarn_c23 and associated tests (error with 
> > -std=c23 -pedantic-errors, warning with -std=c23 -pedantic, no diagnostic 
> > with -std=c23 -pedantic-errors -Wno-c23-c2y-compat, no diagnostic with 
> > -std=c2y -pedantic-errors, warning with -std=c2y -pedantic-errors 
> > -Wc23-c2y-compat), previously discussed in comments on v13, that would be 
> > appropriate before considering this for inclusion with an appropriate 
> > substitution of names.
> 
> I removed it because I renamed it to __countof__, which is a GNU
> extension, and thus should not be warned by -Wpedantic.  As part of my
> opposition to _Lengthof, I will not provide you with that part, which
> would amount to basically giving you _Lengthof but not.  As part of the
> editorialising process, you'll also have to add pedantic warnings, if
> that's what you want to do.  Again, I will earnestly ask to once more to
> consider __countof__, but it's up to you.
> 
> Have a lovely night!
> Alex
> 
> -- 
> <https://www.alejandro-colomar.es/>
  
Joseph Myers Nov. 8, 2024, 3:51 p.m. UTC | #4
On Fri, 8 Nov 2024, Alejandro Colomar wrote:

> Hi Joseph,
> 
> This is a gentle ping about this patch set, 10 days before the start of
> stage 3.

It's obviously not ready to include in its current form (using a name 
different from that actually accepted into C2Y).  Since it requires 
significant work to get it into a form corresponding to the actual C2Y 
feature and you've said you won't do that work, it's a very low priority 
for any kind of review at all compared to any submissions whose authors 
are willing to adapt them following review to be ready for inclusion.  
Name changes could be considered in a later development stage if there 
were a decision to change the name in Graz, but that requires a suitable 
submission while new features are under consideration for GCC 15.
  

Patch

diff --git a/gcc/c-family/c-common.cc b/gcc/c-family/c-common.cc
index 7494a2dac0a..9f48fea6543 100644
--- a/gcc/c-family/c-common.cc
+++ b/gcc/c-family/c-common.cc
@@ -466,6 +466,7 @@  const struct c_common_resword c_common_reswords[] =
   { "__inline",		RID_INLINE,	0 },
   { "__inline__",	RID_INLINE,	0 },
   { "__label__",	RID_LABEL,	0 },
+  { "__countof__",	RID_COUNTOF,	0 },
   { "__null",		RID_NULL,	0 },
   { "__real",		RID_REALPART,	0 },
   { "__real__",		RID_REALPART,	0 },
@@ -4071,6 +4072,31 @@  c_alignof_expr (location_t loc, tree expr)
 
   return fold_convert_loc (loc, size_type_node, t);
 }
+
+/* Implement the countof keyword:
+   Return the number of elements of an array.  */
+
+tree
+c_countof_type (location_t loc, tree type)
+{
+  enum tree_code type_code;
+
+  type_code = TREE_CODE (type);
+  if (type_code != ARRAY_TYPE)
+    {
+      error_at (loc, "invalid application of %<__countof__%> to type %qT", type);
+      return error_mark_node;
+    }
+  if (!COMPLETE_TYPE_P (type))
+    {
+      error_at (loc,
+		"invalid application of %<__countof__%> to incomplete type %qT",
+		type);
+      return error_mark_node;
+    }
+
+  return array_type_nelts_top (type);
+}
 
 /* Handle C and C++ default attributes.  */
 
diff --git a/gcc/c-family/c-common.def b/gcc/c-family/c-common.def
index dc49ad09e2f..f2ae784cefe 100644
--- a/gcc/c-family/c-common.def
+++ b/gcc/c-family/c-common.def
@@ -50,6 +50,9 @@  DEFTREECODE (EXCESS_PRECISION_EXPR, "excess_precision_expr", tcc_expression, 1)
    number.  */
 DEFTREECODE (USERDEF_LITERAL, "userdef_literal", tcc_exceptional, 3)
 
+/* Represents a 'countof' expression.  */
+DEFTREECODE (COUNTOF_EXPR, "countof_expr", tcc_expression, 1)
+
 /* Represents a 'sizeof' expression during C++ template expansion,
    or for the purpose of -Wsizeof-pointer-memaccess warning.  */
 DEFTREECODE (SIZEOF_EXPR, "sizeof_expr", tcc_expression, 1)
diff --git a/gcc/c-family/c-common.h b/gcc/c-family/c-common.h
index 1e80939d379..6c6ee08925e 100644
--- a/gcc/c-family/c-common.h
+++ b/gcc/c-family/c-common.h
@@ -105,6 +105,7 @@  enum rid
 
   /* C extensions */
   RID_ASM,       RID_TYPEOF,   RID_TYPEOF_UNQUAL, RID_ALIGNOF,  RID_ATTRIBUTE,
+  RID_COUNTOF,
   RID_VA_ARG,
   RID_EXTENSION, RID_IMAGPART, RID_REALPART, RID_LABEL,    RID_CHOOSE_EXPR,
   RID_TYPES_COMPATIBLE_P,      RID_BUILTIN_COMPLEX,	   RID_BUILTIN_SHUFFLE,
@@ -889,6 +890,7 @@  extern tree c_common_truthvalue_conversion (location_t, tree);
 extern void c_apply_type_quals_to_decl (int, tree);
 extern tree c_sizeof_or_alignof_type (location_t, tree, bool, bool, int);
 extern tree c_alignof_expr (location_t, tree);
+extern tree c_countof_type (location_t, tree);
 /* Print an error message for invalid operands to arith operation CODE.
    NOP_EXPR is used as a special case (see truthvalue_conversion).  */
 extern void binary_op_error (rich_location *, enum tree_code, tree, tree);
diff --git a/gcc/c/c-decl.cc b/gcc/c/c-decl.cc
index 3733ecfc13f..23c620bbea7 100644
--- a/gcc/c/c-decl.cc
+++ b/gcc/c/c-decl.cc
@@ -9005,12 +9005,17 @@  start_struct (location_t loc, enum tree_code code, tree name,
      within a statement expr used within sizeof, et. al.  This is not
      terribly serious as C++ doesn't permit statement exprs within
      sizeof anyhow.  */
-  if (warn_cxx_compat && (in_sizeof || in_typeof || in_alignof))
+  if (warn_cxx_compat
+      && (in_sizeof || in_typeof || in_alignof || in_countof))
     warning_at (loc, OPT_Wc___compat,
 		"defining type in %qs expression is invalid in C++",
 		(in_sizeof
 		 ? "sizeof"
-		 : (in_typeof ? "typeof" : "alignof")));
+		 : (in_typeof
+		    ? "typeof"
+		    : (in_alignof
+		       ? "alignof"
+		       : "__countof__"))));
 
   if (in_underspecified_init)
     error_at (loc, "%qT defined in underspecified object initializer", ref);
@@ -9974,7 +9979,7 @@  finish_struct (location_t loc, tree t, tree fieldlist, tree attributes,
 	 struct_types.  */
       if (warn_cxx_compat
 	  && struct_parse_info != NULL
-	  && !in_sizeof && !in_typeof && !in_alignof)
+	  && !in_sizeof && !in_typeof && !in_alignof && !in_countof)
 	struct_parse_info->struct_types.safe_push (t);
      }
 
@@ -10148,12 +10153,17 @@  start_enum (location_t loc, struct c_enum_contents *the_enum, tree name,
   /* FIXME: This will issue a warning for a use of a type defined
      within sizeof in a statement expr.  This is not terribly serious
      as C++ doesn't permit statement exprs within sizeof anyhow.  */
-  if (warn_cxx_compat && (in_sizeof || in_typeof || in_alignof))
+  if (warn_cxx_compat
+      && (in_sizeof || in_typeof || in_alignof || in_countof))
     warning_at (loc, OPT_Wc___compat,
 		"defining type in %qs expression is invalid in C++",
 		(in_sizeof
 		 ? "sizeof"
-		 : (in_typeof ? "typeof" : "alignof")));
+		 : (in_typeof
+		    ? "typeof"
+		    : (in_alignof
+		       ? "alignof"
+		       : "__countof__"))));
 
   if (in_underspecified_init)
     error_at (loc, "%qT defined in underspecified object initializer",
@@ -10347,7 +10357,7 @@  finish_enum (tree enumtype, tree values, tree attributes)
      struct_types.  */
   if (warn_cxx_compat
       && struct_parse_info != NULL
-      && !in_sizeof && !in_typeof && !in_alignof)
+      && !in_sizeof && !in_typeof && !in_alignof && !in_countof)
     struct_parse_info->struct_types.safe_push (enumtype);
 
   /* Check for consistency with previous definition */
diff --git a/gcc/c/c-parser.cc b/gcc/c/c-parser.cc
index 090ab1cbc08..73e419ea7ee 100644
--- a/gcc/c/c-parser.cc
+++ b/gcc/c/c-parser.cc
@@ -74,7 +74,17 @@  along with GCC; see the file COPYING3.  If not see
 #include "bitmap.h"
 #include "analyzer/analyzer-language.h"
 #include "toplev.h"
+
+#define c_parser_sizeof_expression(parser)                                    \
+(                                                                             \
+  c_parser_sizeof_or_countof_expression (parser, RID_SIZEOF)                  \
+)
 
+#define c_parser_countof_expression(parser)                                   \
+(                                                                             \
+  c_parser_sizeof_or_countof_expression (parser, RID_COUNTOF)                 \
+)
+
 /* We need to walk over decls with incomplete struct/union/enum types
    after parsing the whole translation unit.
    In finish_decl(), if the decl is static, has incomplete
@@ -1695,7 +1705,8 @@  static struct c_expr c_parser_binary_expression (c_parser *, struct c_expr *,
 						 tree);
 static struct c_expr c_parser_cast_expression (c_parser *, struct c_expr *);
 static struct c_expr c_parser_unary_expression (c_parser *);
-static struct c_expr c_parser_sizeof_expression (c_parser *);
+static struct c_expr c_parser_sizeof_or_countof_expression (c_parser *,
+							    enum rid);
 static struct c_expr c_parser_alignof_expression (c_parser *);
 static struct c_expr c_parser_postfix_expression (c_parser *);
 static struct c_expr c_parser_postfix_expression_after_paren_type (c_parser *,
@@ -10196,6 +10207,8 @@  c_parser_unary_expression (c_parser *parser)
     case CPP_KEYWORD:
       switch (c_parser_peek_token (parser)->keyword)
 	{
+	case RID_COUNTOF:
+	  return c_parser_countof_expression (parser);
 	case RID_SIZEOF:
 	  return c_parser_sizeof_expression (parser);
 	case RID_ALIGNOF:
@@ -10235,12 +10248,13 @@  c_parser_unary_expression (c_parser *parser)
 /* Parse a sizeof expression.  */
 
 static struct c_expr
-c_parser_sizeof_expression (c_parser *parser)
+c_parser_sizeof_or_countof_expression (c_parser *parser, enum rid rid)
 {
+  const char *op_name = (rid == RID_COUNTOF) ? "__countof__" : "sizeof";
   struct c_expr expr;
   struct c_expr result;
   location_t expr_loc;
-  gcc_assert (c_parser_next_token_is_keyword (parser, RID_SIZEOF));
+  gcc_assert (c_parser_next_token_is_keyword (parser, rid));
 
   location_t start;
   location_t finish = UNKNOWN_LOCATION;
@@ -10249,7 +10263,10 @@  c_parser_sizeof_expression (c_parser *parser)
 
   c_parser_consume_token (parser);
   c_inhibit_evaluation_warnings++;
-  in_sizeof++;
+  if (rid == RID_COUNTOF)
+    in_countof++;
+  else
+    in_sizeof++;
   if (c_parser_next_token_is (parser, CPP_OPEN_PAREN)
       && c_token_starts_compound_literal (c_parser_peek_2nd_token (parser)))
     {
@@ -10268,7 +10285,10 @@  c_parser_sizeof_expression (c_parser *parser)
 	{
 	  struct c_expr ret;
 	  c_inhibit_evaluation_warnings--;
-	  in_sizeof--;
+	  if (rid == RID_COUNTOF)
+	    in_countof--;
+	  else
+	    in_sizeof--;
 	  ret.set_error ();
 	  ret.original_code = ERROR_MARK;
 	  ret.original_type = NULL;
@@ -10280,31 +10300,45 @@  c_parser_sizeof_expression (c_parser *parser)
 							       type_name,
 							       expr_loc);
 	  finish = expr.get_finish ();
-	  goto sizeof_expr;
+	  goto Xof_expr;
 	}
       /* sizeof ( type-name ).  */
       if (scspecs)
-	error_at (expr_loc, "storage class specifier in %<sizeof%>");
+	error_at (expr_loc, "storage class specifier in %qs", op_name);
       if (type_name->specs->alignas_p)
 	error_at (type_name->specs->locations[cdw_alignas],
-		  "alignment specified for type name in %<sizeof%>");
+		  "alignment specified for type name in %qs", op_name);
       c_inhibit_evaluation_warnings--;
-      in_sizeof--;
-      result = c_expr_sizeof_type (expr_loc, type_name);
+      if (rid == RID_COUNTOF)
+	{
+	  in_countof--;
+	  result = c_expr_countof_type (expr_loc, type_name);
+	}
+      else
+	{
+	  in_sizeof--;
+	  result = c_expr_sizeof_type (expr_loc, type_name);
+	}
     }
   else
     {
       expr_loc = c_parser_peek_token (parser)->location;
       expr = c_parser_unary_expression (parser);
       finish = expr.get_finish ();
-    sizeof_expr:
+    Xof_expr:
       c_inhibit_evaluation_warnings--;
-      in_sizeof--;
+      if (rid == RID_COUNTOF)
+	in_countof--;
+      else
+	in_sizeof--;
       mark_exp_read (expr.value);
       if (TREE_CODE (expr.value) == COMPONENT_REF
 	  && DECL_C_BIT_FIELD (TREE_OPERAND (expr.value, 1)))
-	error_at (expr_loc, "%<sizeof%> applied to a bit-field");
-      result = c_expr_sizeof_expr (expr_loc, expr);
+	error_at (expr_loc, "%qs applied to a bit-field", op_name);
+      if (rid == RID_COUNTOF)
+	result = c_expr_countof_expr (expr_loc, expr);
+      else
+	result = c_expr_sizeof_expr (expr_loc, expr);
     }
   if (finish == UNKNOWN_LOCATION)
     finish = start;
diff --git a/gcc/c/c-tree.h b/gcc/c/c-tree.h
index a1435e7cb0c..9bdce690374 100644
--- a/gcc/c/c-tree.h
+++ b/gcc/c/c-tree.h
@@ -764,6 +764,7 @@  extern int c_type_dwarf_attribute (const_tree, int);
 /* in c-typeck.cc */
 extern int in_alignof;
 extern int in_sizeof;
+extern int in_countof;
 extern int in_typeof;
 extern bool c_in_omp_for;
 extern bool c_omp_array_section_p;
@@ -815,6 +816,9 @@  extern tree build_external_ref (location_t, tree, bool, tree *);
 extern void pop_maybe_used (bool);
 extern struct c_expr c_expr_sizeof_expr (location_t, struct c_expr);
 extern struct c_expr c_expr_sizeof_type (location_t, struct c_type_name *);
+extern struct c_expr c_expr_countof_expr (location_t, struct c_expr);
+extern struct c_expr c_expr_countof_type (location_t loc,
+					  struct c_type_name *);
 extern struct c_expr parser_build_unary_op (location_t, enum tree_code,
     					    struct c_expr);
 extern struct c_expr parser_build_binary_op (location_t,
diff --git a/gcc/c/c-typeck.cc b/gcc/c/c-typeck.cc
index 108ea5ca3e8..14313e9d649 100644
--- a/gcc/c/c-typeck.cc
+++ b/gcc/c/c-typeck.cc
@@ -71,6 +71,9 @@  int in_alignof;
 /* The level of nesting inside "sizeof".  */
 int in_sizeof;
 
+/* The level of nesting inside "countof".  */
+int in_countof;
+
 /* The level of nesting inside "typeof".  */
 int in_typeof;
 
@@ -3273,7 +3276,7 @@  build_external_ref (location_t loc, tree id, bool fun, tree *type)
 
   if (TREE_CODE (ref) == FUNCTION_DECL && !in_alignof)
     {
-      if (!in_sizeof && !in_typeof)
+      if (!in_sizeof && !in_typeof && !in_countof)
 	C_DECL_USED (ref) = 1;
       else if (DECL_INITIAL (ref) == NULL_TREE
 	       && DECL_EXTERNAL (ref)
@@ -3329,7 +3332,7 @@  struct maybe_used_decl
 {
   /* The decl.  */
   tree decl;
-  /* The level seen at (in_sizeof + in_typeof).  */
+  /* The level seen at (in_sizeof + in_typeof + in_countof).  */
   int level;
   /* The next one at this level or above, or NULL.  */
   struct maybe_used_decl *next;
@@ -3347,7 +3350,7 @@  record_maybe_used_decl (tree decl)
 {
   struct maybe_used_decl *t = XOBNEW (&parser_obstack, struct maybe_used_decl);
   t->decl = decl;
-  t->level = in_sizeof + in_typeof;
+  t->level = in_sizeof + in_typeof + in_countof;
   t->next = maybe_used_decls;
   maybe_used_decls = t;
 }
@@ -3361,7 +3364,7 @@  void
 pop_maybe_used (bool used)
 {
   struct maybe_used_decl *p = maybe_used_decls;
-  int cur_level = in_sizeof + in_typeof;
+  int cur_level = in_sizeof + in_typeof + in_countof;
   while (p && p->level > cur_level)
     {
       if (used)
@@ -3471,6 +3474,113 @@  c_expr_sizeof_type (location_t loc, struct c_type_name *t)
   return ret;
 }
 
+static bool
+is_top_array_vla (tree type)
+{
+  bool zero, star, var;
+  tree d;
+
+  if (TREE_CODE (type) != ARRAY_TYPE)
+    return false;
+  if (!COMPLETE_TYPE_P (type))
+    return false;
+
+  d = TYPE_DOMAIN (type);
+  zero = !TYPE_MAX_VALUE (d);
+  star = (zero && C_TYPE_VARIABLE_SIZE (type));
+  if (star)
+    return true;
+  if (zero)
+    return false;
+
+  var = (TREE_CODE (TYPE_MIN_VALUE (d)) != INTEGER_CST
+	 || TREE_CODE (TYPE_MAX_VALUE (d)) != INTEGER_CST);
+  return var;
+}
+
+/* Return the result of countof applied to EXPR.  */
+
+struct c_expr
+c_expr_countof_expr (location_t loc, struct c_expr expr)
+{
+  struct c_expr ret;
+  if (expr.value == error_mark_node)
+    {
+      ret.value = error_mark_node;
+      ret.original_code = ERROR_MARK;
+      ret.original_type = NULL;
+      ret.m_decimal = 0;
+      pop_maybe_used (false);
+    }
+  else
+    {
+      bool expr_const_operands = true;
+
+      tree folded_expr = c_fully_fold (expr.value, require_constant_value,
+				       &expr_const_operands);
+      ret.value = c_countof_type (loc, TREE_TYPE (folded_expr));
+      c_last_sizeof_arg = expr.value;
+      c_last_sizeof_loc = loc;
+      ret.original_code = COUNTOF_EXPR;
+      ret.original_type = NULL;
+      ret.m_decimal = 0;
+      if (is_top_array_vla (TREE_TYPE (folded_expr)))
+	{
+	  /* countof is evaluated when given a vla.  */
+	  ret.value = build2 (C_MAYBE_CONST_EXPR, TREE_TYPE (ret.value),
+			      folded_expr, ret.value);
+	  C_MAYBE_CONST_EXPR_NON_CONST (ret.value) = !expr_const_operands;
+	  SET_EXPR_LOCATION (ret.value, loc);
+	}
+      pop_maybe_used (is_top_array_vla (TREE_TYPE (folded_expr)));
+    }
+  return ret;
+}
+
+/* Return the result of countof applied to T, a structure for the type
+   name passed to countof (rather than the type itself).  LOC is the
+   location of the original expression.  */
+
+struct c_expr
+c_expr_countof_type (location_t loc, struct c_type_name *t)
+{
+  tree type;
+  struct c_expr ret;
+  tree type_expr = NULL_TREE;
+  bool type_expr_const = true;
+  type = groktypename (t, &type_expr, &type_expr_const);
+  ret.value = c_countof_type (loc, type);
+  c_last_sizeof_arg = type;
+  c_last_sizeof_loc = loc;
+  ret.original_code = COUNTOF_EXPR;
+  ret.original_type = NULL;
+  ret.m_decimal = 0;
+  if (type == error_mark_node)
+    {
+      ret.value = error_mark_node;
+      ret.original_code = ERROR_MARK;
+    }
+  else
+  if ((type_expr || TREE_CODE (ret.value) == INTEGER_CST)
+      && is_top_array_vla (type))
+    {
+      /* If the type is a [*] array, it is a VLA but is represented as
+	 having a size of zero.  In such a case we must ensure that
+	 the result of countof does not get folded to a constant by
+	 c_fully_fold, because if the number of elements is evaluated
+	 the result is not constant and so
+	 constraints on zero or negative size arrays must not be applied
+	 when this countof call is inside another array declarator.  */
+      if (!type_expr)
+	type_expr = integer_zero_node;
+      ret.value = build2 (C_MAYBE_CONST_EXPR, TREE_TYPE (ret.value),
+			  type_expr, ret.value);
+      C_MAYBE_CONST_EXPR_NON_CONST (ret.value) = !type_expr_const;
+    }
+  pop_maybe_used (type != error_mark_node ? is_top_array_vla (type) : false);
+  return ret;
+}
+
 /* Build a function call to function FUNCTION with parameters PARAMS.
    The function call is at LOC.
    PARAMS is a list--a chain of TREE_LIST nodes--in which the
diff --git a/gcc/doc/extend.texi b/gcc/doc/extend.texi
index 42bd567119d..4bd1f637657 100644
--- a/gcc/doc/extend.texi
+++ b/gcc/doc/extend.texi
@@ -10555,6 +10555,36 @@  If the operand of the @code{__alignof__} expression is a function,
 the expression evaluates to the alignment of the function which may
 be specified by attribute @code{aligned} (@pxref{Common Function Attributes}).
 
+@node __countof__
+@section Determining the Number of Elements of Arrays
+@cindex __countof__
+@cindex number of elements
+
+The keyword @code{__countof__} determines
+the number of elements of an array operand.
+Its syntax is similar to @code{sizeof}.
+The operand must be
+a parenthesized complete array type name
+or an expression of such a type.
+For example:
+
+@smallexample
+int a[n];
+__countof__ (a);  // returns n
+__countof__ (int [7][3]);  // returns 7
+@end smallexample
+
+The result of this operator is an integer constant expression,
+unless the array has a variable number of elements.
+The operand is only evaluated
+if the array has a variable number of elements.
+For example:
+
+@smallexample
+__countof__ (int [7][n++]);  // integer constant expression
+__countof__ (int [n++][7]);  // run-time value; n++ is evaluated
+@end smallexample
+
 @node Inline
 @section An Inline Function is As Fast As a Macro
 @cindex inline functions
diff --git a/gcc/testsuite/gcc.dg/countof-compile.c b/gcc/testsuite/gcc.dg/countof-compile.c
new file mode 100644
index 00000000000..bfb51109496
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/countof-compile.c
@@ -0,0 +1,127 @@ 
+/* { dg-do compile } */
+/* { dg-options "-Wno-declaration-after-statement -Wno-pedantic -Wno-vla" } */
+
+extern int x[];
+
+static int w[] = {1, 2, 3};
+
+static int z[0];
+static int y[__countof__(z)];
+
+void
+completed (void)
+{
+  int i = 42;
+  int a[] = {1, 2, i};
+
+  _Static_assert(__countof__ (w) == 3);
+  __countof__ (a);
+}
+
+void
+incomplete (int p[])
+{
+  __countof__ (x);  /* { dg-error "incomplete" } */
+
+  /* We want to support array parameters in the future,
+     which should change this from "invalid" to "incomplete".  */
+  __countof__ (p);  /* { dg-error "invalid" } */
+}
+
+void
+fam (void)
+{
+  struct {
+    int x;
+    int fam[];
+  } s;
+
+  __countof__ (s.fam); /* { dg-error "incomplete" } */
+}
+
+void
+param (int n, int p[n])
+{
+  /* We want to support array parameters in the future,
+     which would make this work.  */
+  __countof__ (p);  /* { dg-error "invalid" } */
+}
+
+void fix_fix (int i, char (*a)[3][5], int (*x)[__countof__ (*a)]);
+void fix_var (int i, char (*a)[3][i], int (*x)[__countof__ (*a)]);
+void fix_uns (int i, char (*a)[3][*], int (*x)[__countof__ (*a)]);
+
+void
+func (void)
+{
+  int  i3[3];
+  int  i5[5];
+  char c35[3][5];
+
+  fix_fix (5, &c35, &i3);
+  fix_fix (5, &c35, &i5); /* { dg-error "incompatible-pointer-types" } */
+
+  fix_var (5, &c35, &i3);
+  fix_var (5, &c35, &i5); /* { dg-error "incompatible-pointer-types" } */
+
+  fix_uns (5, &c35, &i3);
+  fix_uns (5, &c35, &i5); /* { dg-error "incompatible-pointer-types" } */
+}
+
+void
+non_arr(void)
+{
+  int x;
+  int *p;
+  struct s {
+    int x[3];
+  } s;
+
+  __countof__ (x); /* { dg-error "invalid" } */
+  __countof__ (int); /* { dg-error "invalid" } */
+  __countof__ (s); /* { dg-error "invalid" } */
+  __countof__ (struct s); /* { dg-error "invalid" } */
+  __countof__ (&x); /* { dg-error "invalid" } */
+  __countof__ (p); /* { dg-error "invalid" } */
+  __countof__ (int *); /* { dg-error "invalid" } */
+  __countof__ (&s.x); /* { dg-error "invalid" } */
+  __countof__ (int (*)[3]); /* { dg-error "invalid" } */
+}
+
+static int f1();
+static int f2(); /* { dg-warning "never defined" } */
+int a[10][10];
+int n;
+
+void
+syms(void)
+{
+  int b[n][n];
+
+  __countof__ (a[f1()]);
+  __countof__ (b[f2()]);
+}
+
+void
+no_parens(void)
+{
+  __countof__ a;
+  __countof__ *a;
+  __countof__ (int [3]) {};
+
+  __countof__ int [3]; /* { dg-error "expected expression before" } */
+}
+
+void
+const_expr(void)
+{
+  int n = 7;
+
+  _Static_assert (__countof__ (int [3][n]) == 3);
+  _Static_assert (__countof__ (int [n][3]) == 7); /* { dg-error "not constant" } */
+  _Static_assert (__countof__ (int [0][3]) == 0);
+  _Static_assert (__countof__ (int [0]) == 0);
+
+  /* FIXME: countof(int [0][n]) should result in a constant expression.  */
+  _Static_assert (__countof__ (int [0][n]) == 0); /* { dg-error "not constant" } */
+}
diff --git a/gcc/testsuite/gcc.dg/countof-vla.c b/gcc/testsuite/gcc.dg/countof-vla.c
new file mode 100644
index 00000000000..5a82aeed782
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/countof-vla.c
@@ -0,0 +1,46 @@ 
+/* { dg-do compile } */
+/* { dg-options "-Wno-pedantic -Wvla-parameter" } */
+
+void fix_fix (int i,
+	      char (*a)[3][5],
+	      int (*x)[__countof__ (*a)]);
+void fix_var (int i,
+	      char (*a)[3][i], /* dg-warn "variable" */
+	      int (*x)[__countof__ (*a)]);
+void fix_uns (int i,
+	      char (*a)[3][*],
+	      int (*x)[__countof__ (*a)]);
+
+void zro_fix (int i,
+	      char (*a)[0][5],
+	      int (*x)[__countof__ (*a)]);
+void zro_var (int i,
+	      char (*a)[0][i], /* dg-warn "variable" */
+	      int (*x)[__countof__ (*a)]);
+void zro_uns (int i,
+	      char (*a)[0][*],
+	      int (*x)[__countof__ (*a)]);
+
+void var_fix (int i,
+	      char (*a)[i][5], /* dg-warn "variable" */
+	      int (*x)[__countof__ (*a)]); /* dg-warn "variable" */
+void var_var (int i,
+	      char (*a)[i][i], /* dg-warn "variable" */
+	      int (*x)[__countof__ (*a)]); /* dg-warn "variable" */
+void var_uns (int i,
+	      char (*a)[i][*], /* dg-warn "variable" */
+	      int (*x)[__countof__ (*a)]); /* dg-warn "variable" */
+
+void uns_fix (int i,
+	      char (*a)[*][5],
+	      int (*x)[__countof__ (*a)]);
+void uns_var (int i,
+	      char (*a)[*][i], /* dg-warn "variable" */
+	      int (*x)[__countof__ (*a)]);
+void uns_uns (int i,
+	      char (*a)[*][*],
+	      int (*x)[__countof__ (*a)]);
+
+// Can't test due to bug: <https://gcc.gnu.org/bugzilla/show_bug.cgi?id=116284>
+//static int z2[0];
+//static int y2[__countof__(z2)];
diff --git a/gcc/testsuite/gcc.dg/countof.c b/gcc/testsuite/gcc.dg/countof.c
new file mode 100644
index 00000000000..063a207fb6b
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/countof.c
@@ -0,0 +1,150 @@ 
+/* { dg-do run } */
+/* { dg-options "-Wno-declaration-after-statement -Wno-pedantic -Wno-vla" } */
+
+#undef NDEBUG
+#include <assert.h>
+
+void
+array (void)
+{
+  short a[7];
+
+  static_assert (__countof__ (a) == 7);
+  static_assert (__countof__ (long [0]) == 0);
+  static_assert (__countof__ (unsigned [99]) == 99);
+}
+
+void
+automatic(void)
+{
+  int a[] = {1, 2, 3};
+  int z[] = {};
+
+  static_assert (__countof__ (a) == 3);
+  static_assert (__countof__ (z) == 0);
+}
+
+void
+vla (void)
+{
+  unsigned n;
+
+  n = 99;
+  assert (__countof__ (short [n - 10]) == 99 - 10);
+
+  int v[n / 2];
+  assert (__countof__ (v) == 99 / 2);
+
+  n = 0;
+  int z[n];
+  assert (__countof__ (z) == 0);
+}
+
+void
+member (void)
+{
+  struct {
+    int a[8];
+  } s;
+
+  static_assert (__countof__ (s.a) == 8);
+}
+
+void
+vla_eval (void)
+{
+  int i;
+
+  i = 7;
+  assert (__countof__ (struct {int x;}[i++]) == 7);
+  assert (i == 7 + 1);
+
+  int v[i];
+  int (*p)[i];
+  p = &v;
+  assert (__countof__ (*p++) == i);
+  assert (p - 1 == &v);
+}
+
+void
+inner_vla_noeval (void)
+{
+  int i;
+
+  i = 3;
+  static_assert (__countof__ (struct {int x[i++];}[3]) == 3);
+  assert (i == 3);
+}
+
+void
+array_noeval (void)
+{
+  long a[5];
+  long (*p)[__countof__ (a)];
+
+  p = &a;
+  static_assert (__countof__ (*p++) == 5);
+  assert (p == &a);
+}
+
+void
+matrix_zero (void)
+{
+  int i;
+
+  static_assert (__countof__ (int [0][4]) == 0);
+  i = 3;
+  assert (__countof__ (int [0][i]) == 0);
+}
+
+void
+matrix_fixed (void)
+{
+  int i;
+
+  static_assert (__countof__ (int [7][4]) == 7);
+  i = 3;
+  static_assert (__countof__ (int [7][i]) == 7);
+}
+
+void
+matrix_vla (void)
+{
+  int i, j;
+
+  i = 7;
+  assert (__countof__ (int [i++][4]) == 7);
+  assert (i == 7 + 1);
+
+  i = 9;
+  j = 3;
+  assert (__countof__ (int [i++][j]) == 9);
+  assert (i == 9 + 1);
+}
+
+void
+no_parens(void)
+{
+  int n = 3;
+  int a[7];
+  int v[n];
+
+  static_assert (__countof__ a == 7); 
+  assert (__countof__ v == 3); 
+}
+
+int
+main (void)
+{
+  array ();
+  automatic ();
+  vla ();
+  member ();
+  vla_eval ();
+  inner_vla_noeval ();
+  array_noeval ();
+  matrix_zero ();
+  matrix_fixed ();
+  matrix_vla ();
+  no_parens ();
+}