diff mbox series

[v6,3/3] c: Add __lengthof__ operator

Message ID 20240809135837.1460787-5-alx@kernel.org
State New
Headers show
Series c: Add _Lengthof operator | expand

Commit Message

Alejandro Colomar Aug. 9, 2024, 1:59 p.m. UTC
This operator is similar to sizeof but can only be applied to an array,
and returns its length (number of elements).

FUTURE DIRECTIONS:

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

-  Fix support for [0].

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>

gcc/ChangeLog:

	* doc/extend.texi: Document __lengthof__ operator.
	* target.h (enum type_context_kind): Add __lengthof__ operator.

gcc/c-family/ChangeLog:

	* c-common.h:
	* c-common.def:
	* c-common.cc (c_lengthof_type): Add __lengthof__ operator.

gcc/c/ChangeLog:

	* c-tree.h
	(c_expr_lengthof_expr, c_expr_lengthof_type):
	* c-decl.cc
	(start_struct, finish_struct):
	(start_enum, finish_enum):
	* c-parser.cc
	(c_parser_sizeof_expression):
	(c_parser_lengthof_expression):
	(c_parser_sizeof_or_lengthof_expression):
	(c_parser_unary_expression):
	* c-typeck.cc
	(build_external_ref):
	(record_maybe_used_decl, pop_maybe_used):
	(is_top_array_vla):
	(c_expr_lengthof_expr, c_expr_lengthof_type):
	Add __lengthof__operator.

gcc/cp/ChangeLog:

	* operators.def: Add __lengthof__ operator.

gcc/testsuite/ChangeLog:

	* gcc.dg/lengthof-compile.c:
	* gcc.dg/lengthof-vla.c:
	* gcc.dg/lengthof.c: Add tests for __lengthof__ operator.

Link: https://www.open-std.org/jtc1/sc22/wg14/www/docs/n2529.pdf
Link: https://inbox.sourceware.org/gcc/M8S4oQy--3-2@tutanota.com/T/
Suggested-by: Xavier Del Campo Romero <xavi.dcr@tutanota.com>
Co-developed-by: Martin Uecker <uecker@tugraz.at>
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                         |  20 +++-
 gcc/c/c-parser.cc                       |  61 +++++++---
 gcc/c/c-tree.h                          |   4 +
 gcc/c/c-typeck.cc                       | 118 ++++++++++++++++++-
 gcc/cp/operators.def                    |   1 +
 gcc/doc/extend.texi                     |  31 +++++
 gcc/target.h                            |   3 +
 gcc/testsuite/gcc.dg/lengthof-compile.c | 115 ++++++++++++++++++
 gcc/testsuite/gcc.dg/lengthof-vla.c     |  46 ++++++++
 gcc/testsuite/gcc.dg/lengthof.c         | 150 ++++++++++++++++++++++++
 13 files changed, 556 insertions(+), 24 deletions(-)
 create mode 100644 gcc/testsuite/gcc.dg/lengthof-compile.c
 create mode 100644 gcc/testsuite/gcc.dg/lengthof-vla.c
 create mode 100644 gcc/testsuite/gcc.dg/lengthof.c

Comments

Alejandro Colomar Aug. 9, 2024, 3:51 p.m. UTC | #1
On Fri, Aug 09, 2024 at 03:59:19PM GMT, Alejandro Colomar wrote:
> This operator is similar to sizeof but can only be applied to an array,
> and returns its length (number of elements).
> 
> FUTURE DIRECTIONS:
> 
> -  We should make it work with array parameters to functions,
>    and somehow magically return the length designator of the array,
>    regardless of it being really a pointer.
> 
> -  Fix support for [0].
> 
> 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>
> 
> gcc/ChangeLog:
> 
> 	* doc/extend.texi: Document __lengthof__ operator.
> 	* target.h (enum type_context_kind): Add __lengthof__ operator.
> 
> gcc/c-family/ChangeLog:
> 
> 	* c-common.h:
> 	* c-common.def:
> 	* c-common.cc (c_lengthof_type): Add __lengthof__ operator.
> 
> gcc/c/ChangeLog:
> 
> 	* c-tree.h
> 	(c_expr_lengthof_expr, c_expr_lengthof_type):
> 	* c-decl.cc
> 	(start_struct, finish_struct):
> 	(start_enum, finish_enum):
> 	* c-parser.cc
> 	(c_parser_sizeof_expression):
> 	(c_parser_lengthof_expression):
> 	(c_parser_sizeof_or_lengthof_expression):
> 	(c_parser_unary_expression):
> 	* c-typeck.cc
> 	(build_external_ref):
> 	(record_maybe_used_decl, pop_maybe_used):
> 	(is_top_array_vla):
> 	(c_expr_lengthof_expr, c_expr_lengthof_type):
> 	Add __lengthof__operator.
> 
> gcc/cp/ChangeLog:
> 
> 	* operators.def: Add __lengthof__ operator.
> 
> gcc/testsuite/ChangeLog:
> 
> 	* gcc.dg/lengthof-compile.c:
> 	* gcc.dg/lengthof-vla.c:
> 	* gcc.dg/lengthof.c: Add tests for __lengthof__ operator.
> 
> Link: https://www.open-std.org/jtc1/sc22/wg14/www/docs/n2529.pdf
> Link: https://inbox.sourceware.org/gcc/M8S4oQy--3-2@tutanota.com/T/
> Suggested-by: Xavier Del Campo Romero <xavi.dcr@tutanota.com>
> Co-developed-by: Martin Uecker <uecker@tugraz.at>
> 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                         |  20 +++-
>  gcc/c/c-parser.cc                       |  61 +++++++---
>  gcc/c/c-tree.h                          |   4 +
>  gcc/c/c-typeck.cc                       | 118 ++++++++++++++++++-
>  gcc/cp/operators.def                    |   1 +
>  gcc/doc/extend.texi                     |  31 +++++
>  gcc/target.h                            |   3 +
>  gcc/testsuite/gcc.dg/lengthof-compile.c | 115 ++++++++++++++++++
>  gcc/testsuite/gcc.dg/lengthof-vla.c     |  46 ++++++++
>  gcc/testsuite/gcc.dg/lengthof.c         | 150 ++++++++++++++++++++++++
>  13 files changed, 556 insertions(+), 24 deletions(-)
>  create mode 100644 gcc/testsuite/gcc.dg/lengthof-compile.c
>  create mode 100644 gcc/testsuite/gcc.dg/lengthof-vla.c
>  create mode 100644 gcc/testsuite/gcc.dg/lengthof.c
> 
> diff --git a/gcc/c-family/c-common.cc b/gcc/c-family/c-common.cc
> index e7e371fd26f..9f5feb83345 100644
> --- a/gcc/c-family/c-common.cc
> +++ b/gcc/c-family/c-common.cc
> @@ -465,6 +465,7 @@ const struct c_common_resword c_common_reswords[] =
>    { "__inline",		RID_INLINE,	0 },
>    { "__inline__",	RID_INLINE,	0 },
>    { "__label__",	RID_LABEL,	0 },
> +  { "__lengthof__",	RID_LENGTHOF, 0 },
>    { "__null",		RID_NULL,	0 },
>    { "__real",		RID_REALPART,	0 },
>    { "__real__",		RID_REALPART,	0 },
> @@ -4070,6 +4071,31 @@ c_alignof_expr (location_t loc, tree expr)
>  
>    return fold_convert_loc (loc, size_type_node, t);
>  }
> +
> +/* Implement the lengthof keyword: Return the length of an array,
> +   that is, the number of elements in the array.  */
> +
> +tree
> +c_lengthof_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 %<lengthof%> to type %qT", type);
> +      return error_mark_node;
> +    }
> +  if (!COMPLETE_TYPE_P (type))
> +    {
> +      error_at (loc,
> +		"invalid application of %<lengthof%> 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 5de96e5d4a8..6d162f67104 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 'lengthof' expression.  */
> +DEFTREECODE (LENGTHOF_EXPR, "lengthof_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 2510ee4dbc9..d2e7d7e8c40 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_LENGTHOF,
>    RID_VA_ARG,
>    RID_EXTENSION, RID_IMAGPART, RID_REALPART, RID_LABEL,    RID_CHOOSE_EXPR,
>    RID_TYPES_COMPATIBLE_P,      RID_BUILTIN_COMPLEX,	   RID_BUILTIN_SHUFFLE,
> @@ -885,6 +886,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_lengthof_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 e7c2783e724..6bacf8c8c23 100644
> --- a/gcc/c/c-decl.cc
> +++ b/gcc/c/c-decl.cc
> @@ -8943,12 +8943,16 @@ 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_lengthof))
>      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"
> +		       : "lengthof"))));
>  
>    if (in_underspecified_init)
>      error_at (loc, "%qT defined in underspecified object initializer", ref);
> @@ -9908,7 +9912,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_lengthof)
>  	struct_parse_info->struct_types.safe_push (t);
>       }
>  
> @@ -10082,12 +10086,16 @@ 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_lengthof))
>      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"
> +		       : "lengthof"))));
>  
>    if (in_underspecified_init)
>      error_at (loc, "%qT defined in underspecified object initializer",
> @@ -10281,7 +10289,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_lengthof)
>      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 9b9284b1ba4..b30194d9218 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_lengthof_expression (parser, RID_SIZEOF)                 \
> +)
>  
> +#define c_parser_lengthof_expression(parser)                                  \
> +(                                                                             \
> +  c_parser_sizeof_or_lengthof_expression (parser, RID_LENGTHOF)               \
> +)
> +
>  /* 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
> @@ -1694,7 +1704,7 @@ 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_lengthof_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 *,
> @@ -9909,6 +9919,8 @@ c_parser_unary_expression (c_parser *parser)
>      case CPP_KEYWORD:
>        switch (c_parser_peek_token (parser)->keyword)
>  	{
> +	case RID_LENGTHOF:
> +	  return c_parser_lengthof_expression (parser);
>  	case RID_SIZEOF:
>  	  return c_parser_sizeof_expression (parser);
>  	case RID_ALIGNOF:
> @@ -9948,12 +9960,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_lengthof_expression (c_parser *parser, enum rid rid)
>  {
> +  const char *op_name = (rid == RID_LENGTHOF) ? "lengthof" : "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;
> @@ -9962,7 +9975,10 @@ c_parser_sizeof_expression (c_parser *parser)
>  
>    c_parser_consume_token (parser);
>    c_inhibit_evaluation_warnings++;
> -  in_sizeof++;
> +  if (rid == RID_LENGTHOF)
> +    in_lengthof++;
> +  else
> +    in_sizeof++;
>    if (c_parser_next_token_is (parser, CPP_OPEN_PAREN)
>        && c_token_starts_compound_literal (c_parser_peek_2nd_token (parser)))
>      {
> @@ -9981,7 +9997,10 @@ c_parser_sizeof_expression (c_parser *parser)
>  	{
>  	  struct c_expr ret;
>  	  c_inhibit_evaluation_warnings--;
> -	  in_sizeof--;
> +	  if (rid == RID_LENGTHOF)
> +	    in_lengthof--;
> +	  else
> +	    in_sizeof--;
>  	  ret.set_error ();
>  	  ret.original_code = ERROR_MARK;
>  	  ret.original_type = NULL;
> @@ -9993,31 +10012,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_LENGTHOF)
> +	{
> +	  in_lengthof--;
> +	  result = c_expr_lengthof_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_LENGTHOF)
> +	in_lengthof--;
> +      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_LENGTHOF)
> +	result = c_expr_lengthof_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 3dc6338bf06..81cfe002961 100644
> --- a/gcc/c/c-tree.h
> +++ b/gcc/c/c-tree.h
> @@ -736,6 +736,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_lengthof;
>  extern int in_typeof;
>  extern bool c_in_omp_for;
>  extern bool c_omp_array_section_p;
> @@ -786,6 +787,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_lengthof_expr (location_t, struct c_expr);
> +extern struct c_expr c_expr_lengthof_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 094e41fa202..9a8bbd707e8 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 "lengthof".  */
> +int in_lengthof;
> +
>  /* The level of nesting inside "typeof".  */
>  int in_typeof;
>  
> @@ -3255,7 +3258,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_lengthof)
>  	C_DECL_USED (ref) = 1;
>        else if (DECL_INITIAL (ref) == NULL_TREE
>  	       && DECL_EXTERNAL (ref)
> @@ -3311,7 +3314,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_lengthof).  */
>    int level;
>    /* The next one at this level or above, or NULL.  */
>    struct maybe_used_decl *next;
> @@ -3329,7 +3332,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_lengthof;
>    t->next = maybe_used_decls;
>    maybe_used_decls = t;
>  }
> @@ -3343,7 +3346,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_lengthof;
>    while (p && p->level > cur_level)
>      {
>        if (used)
> @@ -3453,6 +3456,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 lengthof applied to EXPR.  */
> +
> +struct c_expr
> +c_expr_lengthof_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_lengthof_type (loc, TREE_TYPE (folded_expr));
> +      c_last_sizeof_arg = expr.value;
> +      c_last_sizeof_loc = loc;
> +      ret.original_code = LENGTHOF_EXPR;
> +      ret.original_type = NULL;
> +      ret.m_decimal = 0;
> +      if (is_top_array_vla (TREE_TYPE (folded_expr)))
> +	{
> +	  /* lengthof 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 lengthof applied to T, a structure for the type
> +   name passed to _lengthof (rather than the type itself).  LOC is the
> +   location of the original expression.  */
> +
> +struct c_expr
> +c_expr_lengthof_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_lengthof_type (loc, type);
> +  c_last_sizeof_arg = type;
> +  c_last_sizeof_loc = loc;
> +  ret.original_code = LENGTHOF_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 lengthof does not get folded to a constant by
> +	 c_fully_fold, because if the length is evaluated the result is
> +	 not constant and so constraints on zero or negative size
> +	 arrays must not be applied when this lengthof 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/cp/operators.def b/gcc/cp/operators.def
> index d8878923602..d640ed8bd91 100644
> --- a/gcc/cp/operators.def
> +++ b/gcc/cp/operators.def
> @@ -91,6 +91,7 @@ DEF_OPERATOR ("co_await", CO_AWAIT_EXPR, "aw", OVL_OP_FLAG_UNARY)
>  
>  /* These are extensions.  */
>  DEF_OPERATOR ("alignof", ALIGNOF_EXPR, "az", OVL_OP_FLAG_UNARY)
> +DEF_OPERATOR ("__lengthof__", LENGTHOF_EXPR, "lz", OVL_OP_FLAG_UNARY)
>  DEF_OPERATOR ("__imag__", IMAGPART_EXPR, "v18__imag__", OVL_OP_FLAG_UNARY)
>  DEF_OPERATOR ("__real__", REALPART_EXPR, "v18__real__", OVL_OP_FLAG_UNARY)
>  
> diff --git a/gcc/doc/extend.texi b/gcc/doc/extend.texi
> index 89fe5db7aed..7aa6384a528 100644
> --- a/gcc/doc/extend.texi
> +++ b/gcc/doc/extend.texi
> @@ -10466,6 +10466,37 @@ 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 Length
> +@section Determining the Length of Arrays
> +@cindex lengthof
> +@cindex length
> +@cindex array length
> +
> +The keyword @code{__lengthof__} determines the length of an array operand,
> +that is, the number of elements in the array.
> +Its syntax is similar to @code{sizeof}.
> +The operand must be a complete array type or an expression of that type.
> +For example:
> +
> +@smallexample
> +int a[n];
> +__lengthof__ (a);  // returns n
> +__lengthof__ (int [7][3]);  // returns 7
> +@end smallexample
> +
> +The operand is not evaluated
> +if the top-level length designator is an integer constant expression
> +(in this case, the operator results in an integer constant expression);
> +and it is evaluated
> +if the top-level length designator is not an integer constant expression
> +(in this case, the operator results in a run-time value).
> +For example:
> +
> +@smallexample
> +__lengthof__ (int [7][n++]);  // integer constant expression
> +__lengthof__ (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/target.h b/gcc/target.h
> index 837651d273a..a596f3f4b43 100644
> --- a/gcc/target.h
> +++ b/gcc/target.h
> @@ -245,6 +245,9 @@ enum type_context_kind {
>    /* Directly measuring the alignment of T.  */
>    TCTX_ALIGNOF,
>  
> +  /* Directly measuring the length of array T.  */
> +  TCTX_LENGTHOF,
> +
>    /* Creating objects of type T with static storage duration.  */
>    TCTX_STATIC_STORAGE,
>  
> diff --git a/gcc/testsuite/gcc.dg/lengthof-compile.c b/gcc/testsuite/gcc.dg/lengthof-compile.c
> new file mode 100644
> index 00000000000..db2e323181f
> --- /dev/null
> +++ b/gcc/testsuite/gcc.dg/lengthof-compile.c
> @@ -0,0 +1,115 @@
> +/* { 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[__lengthof__(z)];
> +
> +void
> +automatic(void)
> +{
> +  __lengthof__ (w);
> +}
> +
> +void
> +incomplete (int p[])
> +{
> +  __lengthof__ (x);  /* { dg-error "incomplete" } */
> +
> +  /* We want to support the following one in the future,
> +     but for now it should fail.  */
> +  __lengthof__ (p);  /* { dg-error "invalid" } */
> +}
> +
> +void
> +fam (void)
> +{
> +  struct {
> +    int x;
> +    int fam[];
> +  } s;
> +
> +  __lengthof__ (s.fam); /* { dg-error "incomplete" } */
> +}
> +
> +void fix_fix (int i, char (*a)[3][5], int (*x)[__lengthof__ (*a)]);
> +void fix_var (int i, char (*a)[3][i], int (*x)[__lengthof__ (*a)]);
> +void fix_uns (int i, char (*a)[3][*], int (*x)[__lengthof__ (*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;
> +
> +  __lengthof__ (x); /* { dg-error "invalid" } */
> +  __lengthof__ (int); /* { dg-error "invalid" } */
> +  __lengthof__ (s); /* { dg-error "invalid" } */
> +  __lengthof__ (struct s); /* { dg-error "invalid" } */
> +  __lengthof__ (&x); /* { dg-error "invalid" } */
> +  __lengthof__ (p); /* { dg-error "invalid" } */
> +  __lengthof__ (int *); /* { dg-error "invalid" } */
> +  __lengthof__ (&s.x); /* { dg-error "invalid" } */
> +  __lengthof__ (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];
> +
> +  __lengthof__ (a[f1()]);
> +  __lengthof__ (b[f2()]);
> +}
> +
> +void
> +no_parens(void)
> +{
> +  __lengthof__ a;
> +  __lengthof__ *a;
> +  __lengthof__ (int [3]) {};
> +
> +  __lengthof__ int [3]; /* { dg-error "expected expression before" } */
> +}
> +
> +void
> +const_expr(void)
> +{
> +  int n = 7;
> +
> +  _Static_assert (__lengthof__ (int [3][n]) == 3);
> +  _Static_assert (__lengthof__ (int [n][3]) == 7); /* { dg-error "not constant"} */
> +  _Static_assert (__lengthof__ (int [0][3]) == 0);
> +  _Static_assert (__lengthof__ (int [0]) == 0);
> +
> +  /* FIXME: lengthog(int [0][n]) should result in a constant expression.  */
> +  _Static_assert (__lengthof__ (int [0][n]) == 0); /* { dg-error "not constant"} */

There was a small white-space problem with the tests above which I
forgot to include in the version I sent.  It will be included in v7.

diff --git a/gcc/testsuite/gcc.dg/lengthof-compile.c b/gcc/testsuite/gcc.dg/lengthof-compile.c
index db2e323181f..72211fa5fa7 100644
--- a/gcc/testsuite/gcc.dg/lengthof-compile.c
+++ b/gcc/testsuite/gcc.dg/lengthof-compile.c
@@ -106,10 +106,10 @@ const_expr(void)
   int n = 7;
 
   _Static_assert (__lengthof__ (int [3][n]) == 3);
-  _Static_assert (__lengthof__ (int [n][3]) == 7); /* { dg-error "not constant"} */
+  _Static_assert (__lengthof__ (int [n][3]) == 7); /* { dg-error "not constant" } */
   _Static_assert (__lengthof__ (int [0][3]) == 0);
   _Static_assert (__lengthof__ (int [0]) == 0);
 
   /* FIXME: lengthog(int [0][n]) should result in a constant expression.  */
-  _Static_assert (__lengthof__ (int [0][n]) == 0); /* { dg-error "not constant"} */
+  _Static_assert (__lengthof__ (int [0][n]) == 0); /* { dg-error "not constant" } */
 }


> +}
> diff --git a/gcc/testsuite/gcc.dg/lengthof-vla.c b/gcc/testsuite/gcc.dg/lengthof-vla.c
> new file mode 100644
> index 00000000000..f415fcf5a89
> --- /dev/null
> +++ b/gcc/testsuite/gcc.dg/lengthof-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)[__lengthof__ (*a)]);
> +void fix_var (int i,
> +	      char (*a)[3][i], /* dg-warn "variable" */
> +	      int (*x)[__lengthof__ (*a)]);
> +void fix_uns (int i,
> +	      char (*a)[3][*],
> +	      int (*x)[__lengthof__ (*a)]);
> +
> +void zro_fix (int i,
> +	      char (*a)[0][5],
> +	      int (*x)[__lengthof__ (*a)]);
> +void zro_var (int i,
> +	      char (*a)[0][i], /* dg-warn "variable" */
> +	      int (*x)[__lengthof__ (*a)]);
> +void zro_uns (int i,
> +	      char (*a)[0][*],
> +	      int (*x)[__lengthof__ (*a)]);
> +
> +void var_fix (int i,
> +	      char (*a)[i][5], /* dg-warn "variable" */
> +	      int (*x)[__lengthof__ (*a)]); /* dg-warn "variable" */
> +void var_var (int i,
> +	      char (*a)[i][i], /* dg-warn "variable" */
> +	      int (*x)[__lengthof__ (*a)]); /* dg-warn "variable" */
> +void var_uns (int i,
> +	      char (*a)[i][*], /* dg-warn "variable" */
> +	      int (*x)[__lengthof__ (*a)]); /* dg-warn "variable" */
> +
> +void uns_fix (int i,
> +	      char (*a)[*][5],
> +	      int (*x)[__lengthof__ (*a)]);
> +void uns_var (int i,
> +	      char (*a)[*][i], /* dg-warn "variable" */
> +	      int (*x)[__lengthof__ (*a)]);
> +void uns_uns (int i,
> +	      char (*a)[*][*],
> +	      int (*x)[__lengthof__ (*a)]);
> +
> +// Can't test due to bug: <https://gcc.gnu.org/bugzilla/show_bug.cgi?id=116284>
> +//static int z2[0];
> +//static int y2[__lengthof__(z2)];
> diff --git a/gcc/testsuite/gcc.dg/lengthof.c b/gcc/testsuite/gcc.dg/lengthof.c
> new file mode 100644
> index 00000000000..86a530d613f
> --- /dev/null
> +++ b/gcc/testsuite/gcc.dg/lengthof.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 (__lengthof__ (a) == 7);
> +  static_assert (__lengthof__ (long [0]) == 0);
> +  static_assert (__lengthof__ (unsigned [99]) == 99);
> +}
> +
> +void
> +automatic(void)
> +{
> +  int a[] = {1, 2, 3};
> +  int z[] = {};
> +
> +  static_assert (__lengthof__ (a) == 3);
> +  static_assert (__lengthof__ (z) == 0);
> +}
> +
> +void
> +vla (void)
> +{
> +  unsigned n;
> +
> +  n = 99;
> +  assert (__lengthof__ (short [n - 10]) == 99 - 10);
> +
> +  int v[n / 2];
> +  assert (__lengthof__ (v) == 99 / 2);
> +
> +  n = 0;
> +  int z[n];
> +  assert (__lengthof__ (z) == 0);
> +}
> +
> +void
> +member (void)
> +{
> +  struct {
> +    int a[8];
> +  } s;
> +
> +  static_assert (__lengthof__ (s.a) == 8);
> +}
> +
> +void
> +vla_eval (void)
> +{
> +  int i;
> +
> +  i = 7;
> +  assert (__lengthof__ (struct {int x;}[i++]) == 7);
> +  assert (i == 7 + 1);
> +
> +  int v[i];
> +  int (*p)[i];
> +  p = &v;
> +  assert (__lengthof__ (*p++) == i);
> +  assert (p - 1 == &v);
> +}
> +
> +void
> +inner_vla_noeval (void)
> +{
> +  int i;
> +
> +  i = 3;
> +  static_assert (__lengthof__ (struct {int x[i++];}[3]) == 3);
> +  assert (i == 3);
> +}
> +
> +void
> +array_noeval (void)
> +{
> +  long a[5];
> +  long (*p)[__lengthof__ (a)];
> +
> +  p = &a;
> +  static_assert (__lengthof__ (*p++) == 5);
> +  assert (p == &a);
> +}
> +
> +void
> +matrix_zero (void)
> +{
> +  int i;
> +
> +  static_assert (__lengthof__ (int [0][4]) == 0);
> +  i = 3;
> +  assert (__lengthof__ (int [0][i]) == 0);
> +}
> +
> +void
> +matrix_fixed (void)
> +{
> +  int i;
> +
> +  static_assert (__lengthof__ (int [7][4]) == 7);
> +  i = 3;
> +  static_assert (__lengthof__ (int [7][i]) == 7);
> +}
> +
> +void
> +matrix_vla (void)
> +{
> +  int i, j;
> +
> +  i = 7;
> +  assert (__lengthof__ (int [i++][4]) == 7);
> +  assert (i == 7 + 1);
> +
> +  i = 9;
> +  j = 3;
> +  assert (__lengthof__ (int [i++][j]) == 9);
> +  assert (i == 9 + 1);
> +}
> +
> +void
> +no_parens(void)
> +{
> +  int n = 3;
> +  int a[7];
> +  int v[n];
> +
> +  static_assert (__lengthof__ a == 7); 
> +  assert (__lengthof__ v == 3); 
> +}
> +
> +int
> +main (void)
> +{
> +  array ();
> +  automatic ();
> +  vla ();
> +  member ();
> +  vla_eval ();
> +  inner_vla_noeval ();
> +  array_noeval ();
> +  matrix_zero ();
> +  matrix_fixed ();
> +  matrix_vla ();
> +  no_parens ();
> +}
> -- 
> 2.45.2
>
diff mbox series

Patch

diff --git a/gcc/c-family/c-common.cc b/gcc/c-family/c-common.cc
index e7e371fd26f..9f5feb83345 100644
--- a/gcc/c-family/c-common.cc
+++ b/gcc/c-family/c-common.cc
@@ -465,6 +465,7 @@  const struct c_common_resword c_common_reswords[] =
   { "__inline",		RID_INLINE,	0 },
   { "__inline__",	RID_INLINE,	0 },
   { "__label__",	RID_LABEL,	0 },
+  { "__lengthof__",	RID_LENGTHOF, 0 },
   { "__null",		RID_NULL,	0 },
   { "__real",		RID_REALPART,	0 },
   { "__real__",		RID_REALPART,	0 },
@@ -4070,6 +4071,31 @@  c_alignof_expr (location_t loc, tree expr)
 
   return fold_convert_loc (loc, size_type_node, t);
 }
+
+/* Implement the lengthof keyword: Return the length of an array,
+   that is, the number of elements in the array.  */
+
+tree
+c_lengthof_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 %<lengthof%> to type %qT", type);
+      return error_mark_node;
+    }
+  if (!COMPLETE_TYPE_P (type))
+    {
+      error_at (loc,
+		"invalid application of %<lengthof%> 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 5de96e5d4a8..6d162f67104 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 'lengthof' expression.  */
+DEFTREECODE (LENGTHOF_EXPR, "lengthof_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 2510ee4dbc9..d2e7d7e8c40 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_LENGTHOF,
   RID_VA_ARG,
   RID_EXTENSION, RID_IMAGPART, RID_REALPART, RID_LABEL,    RID_CHOOSE_EXPR,
   RID_TYPES_COMPATIBLE_P,      RID_BUILTIN_COMPLEX,	   RID_BUILTIN_SHUFFLE,
@@ -885,6 +886,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_lengthof_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 e7c2783e724..6bacf8c8c23 100644
--- a/gcc/c/c-decl.cc
+++ b/gcc/c/c-decl.cc
@@ -8943,12 +8943,16 @@  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_lengthof))
     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"
+		       : "lengthof"))));
 
   if (in_underspecified_init)
     error_at (loc, "%qT defined in underspecified object initializer", ref);
@@ -9908,7 +9912,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_lengthof)
 	struct_parse_info->struct_types.safe_push (t);
      }
 
@@ -10082,12 +10086,16 @@  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_lengthof))
     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"
+		       : "lengthof"))));
 
   if (in_underspecified_init)
     error_at (loc, "%qT defined in underspecified object initializer",
@@ -10281,7 +10289,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_lengthof)
     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 9b9284b1ba4..b30194d9218 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_lengthof_expression (parser, RID_SIZEOF)                 \
+)
 
+#define c_parser_lengthof_expression(parser)                                  \
+(                                                                             \
+  c_parser_sizeof_or_lengthof_expression (parser, RID_LENGTHOF)               \
+)
+
 /* 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
@@ -1694,7 +1704,7 @@  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_lengthof_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 *,
@@ -9909,6 +9919,8 @@  c_parser_unary_expression (c_parser *parser)
     case CPP_KEYWORD:
       switch (c_parser_peek_token (parser)->keyword)
 	{
+	case RID_LENGTHOF:
+	  return c_parser_lengthof_expression (parser);
 	case RID_SIZEOF:
 	  return c_parser_sizeof_expression (parser);
 	case RID_ALIGNOF:
@@ -9948,12 +9960,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_lengthof_expression (c_parser *parser, enum rid rid)
 {
+  const char *op_name = (rid == RID_LENGTHOF) ? "lengthof" : "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;
@@ -9962,7 +9975,10 @@  c_parser_sizeof_expression (c_parser *parser)
 
   c_parser_consume_token (parser);
   c_inhibit_evaluation_warnings++;
-  in_sizeof++;
+  if (rid == RID_LENGTHOF)
+    in_lengthof++;
+  else
+    in_sizeof++;
   if (c_parser_next_token_is (parser, CPP_OPEN_PAREN)
       && c_token_starts_compound_literal (c_parser_peek_2nd_token (parser)))
     {
@@ -9981,7 +9997,10 @@  c_parser_sizeof_expression (c_parser *parser)
 	{
 	  struct c_expr ret;
 	  c_inhibit_evaluation_warnings--;
-	  in_sizeof--;
+	  if (rid == RID_LENGTHOF)
+	    in_lengthof--;
+	  else
+	    in_sizeof--;
 	  ret.set_error ();
 	  ret.original_code = ERROR_MARK;
 	  ret.original_type = NULL;
@@ -9993,31 +10012,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_LENGTHOF)
+	{
+	  in_lengthof--;
+	  result = c_expr_lengthof_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_LENGTHOF)
+	in_lengthof--;
+      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_LENGTHOF)
+	result = c_expr_lengthof_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 3dc6338bf06..81cfe002961 100644
--- a/gcc/c/c-tree.h
+++ b/gcc/c/c-tree.h
@@ -736,6 +736,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_lengthof;
 extern int in_typeof;
 extern bool c_in_omp_for;
 extern bool c_omp_array_section_p;
@@ -786,6 +787,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_lengthof_expr (location_t, struct c_expr);
+extern struct c_expr c_expr_lengthof_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 094e41fa202..9a8bbd707e8 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 "lengthof".  */
+int in_lengthof;
+
 /* The level of nesting inside "typeof".  */
 int in_typeof;
 
@@ -3255,7 +3258,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_lengthof)
 	C_DECL_USED (ref) = 1;
       else if (DECL_INITIAL (ref) == NULL_TREE
 	       && DECL_EXTERNAL (ref)
@@ -3311,7 +3314,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_lengthof).  */
   int level;
   /* The next one at this level or above, or NULL.  */
   struct maybe_used_decl *next;
@@ -3329,7 +3332,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_lengthof;
   t->next = maybe_used_decls;
   maybe_used_decls = t;
 }
@@ -3343,7 +3346,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_lengthof;
   while (p && p->level > cur_level)
     {
       if (used)
@@ -3453,6 +3456,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 lengthof applied to EXPR.  */
+
+struct c_expr
+c_expr_lengthof_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_lengthof_type (loc, TREE_TYPE (folded_expr));
+      c_last_sizeof_arg = expr.value;
+      c_last_sizeof_loc = loc;
+      ret.original_code = LENGTHOF_EXPR;
+      ret.original_type = NULL;
+      ret.m_decimal = 0;
+      if (is_top_array_vla (TREE_TYPE (folded_expr)))
+	{
+	  /* lengthof 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 lengthof applied to T, a structure for the type
+   name passed to _lengthof (rather than the type itself).  LOC is the
+   location of the original expression.  */
+
+struct c_expr
+c_expr_lengthof_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_lengthof_type (loc, type);
+  c_last_sizeof_arg = type;
+  c_last_sizeof_loc = loc;
+  ret.original_code = LENGTHOF_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 lengthof does not get folded to a constant by
+	 c_fully_fold, because if the length is evaluated the result is
+	 not constant and so constraints on zero or negative size
+	 arrays must not be applied when this lengthof 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/cp/operators.def b/gcc/cp/operators.def
index d8878923602..d640ed8bd91 100644
--- a/gcc/cp/operators.def
+++ b/gcc/cp/operators.def
@@ -91,6 +91,7 @@  DEF_OPERATOR ("co_await", CO_AWAIT_EXPR, "aw", OVL_OP_FLAG_UNARY)
 
 /* These are extensions.  */
 DEF_OPERATOR ("alignof", ALIGNOF_EXPR, "az", OVL_OP_FLAG_UNARY)
+DEF_OPERATOR ("__lengthof__", LENGTHOF_EXPR, "lz", OVL_OP_FLAG_UNARY)
 DEF_OPERATOR ("__imag__", IMAGPART_EXPR, "v18__imag__", OVL_OP_FLAG_UNARY)
 DEF_OPERATOR ("__real__", REALPART_EXPR, "v18__real__", OVL_OP_FLAG_UNARY)
 
diff --git a/gcc/doc/extend.texi b/gcc/doc/extend.texi
index 89fe5db7aed..7aa6384a528 100644
--- a/gcc/doc/extend.texi
+++ b/gcc/doc/extend.texi
@@ -10466,6 +10466,37 @@  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 Length
+@section Determining the Length of Arrays
+@cindex lengthof
+@cindex length
+@cindex array length
+
+The keyword @code{__lengthof__} determines the length of an array operand,
+that is, the number of elements in the array.
+Its syntax is similar to @code{sizeof}.
+The operand must be a complete array type or an expression of that type.
+For example:
+
+@smallexample
+int a[n];
+__lengthof__ (a);  // returns n
+__lengthof__ (int [7][3]);  // returns 7
+@end smallexample
+
+The operand is not evaluated
+if the top-level length designator is an integer constant expression
+(in this case, the operator results in an integer constant expression);
+and it is evaluated
+if the top-level length designator is not an integer constant expression
+(in this case, the operator results in a run-time value).
+For example:
+
+@smallexample
+__lengthof__ (int [7][n++]);  // integer constant expression
+__lengthof__ (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/target.h b/gcc/target.h
index 837651d273a..a596f3f4b43 100644
--- a/gcc/target.h
+++ b/gcc/target.h
@@ -245,6 +245,9 @@  enum type_context_kind {
   /* Directly measuring the alignment of T.  */
   TCTX_ALIGNOF,
 
+  /* Directly measuring the length of array T.  */
+  TCTX_LENGTHOF,
+
   /* Creating objects of type T with static storage duration.  */
   TCTX_STATIC_STORAGE,
 
diff --git a/gcc/testsuite/gcc.dg/lengthof-compile.c b/gcc/testsuite/gcc.dg/lengthof-compile.c
new file mode 100644
index 00000000000..db2e323181f
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/lengthof-compile.c
@@ -0,0 +1,115 @@ 
+/* { 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[__lengthof__(z)];
+
+void
+automatic(void)
+{
+  __lengthof__ (w);
+}
+
+void
+incomplete (int p[])
+{
+  __lengthof__ (x);  /* { dg-error "incomplete" } */
+
+  /* We want to support the following one in the future,
+     but for now it should fail.  */
+  __lengthof__ (p);  /* { dg-error "invalid" } */
+}
+
+void
+fam (void)
+{
+  struct {
+    int x;
+    int fam[];
+  } s;
+
+  __lengthof__ (s.fam); /* { dg-error "incomplete" } */
+}
+
+void fix_fix (int i, char (*a)[3][5], int (*x)[__lengthof__ (*a)]);
+void fix_var (int i, char (*a)[3][i], int (*x)[__lengthof__ (*a)]);
+void fix_uns (int i, char (*a)[3][*], int (*x)[__lengthof__ (*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;
+
+  __lengthof__ (x); /* { dg-error "invalid" } */
+  __lengthof__ (int); /* { dg-error "invalid" } */
+  __lengthof__ (s); /* { dg-error "invalid" } */
+  __lengthof__ (struct s); /* { dg-error "invalid" } */
+  __lengthof__ (&x); /* { dg-error "invalid" } */
+  __lengthof__ (p); /* { dg-error "invalid" } */
+  __lengthof__ (int *); /* { dg-error "invalid" } */
+  __lengthof__ (&s.x); /* { dg-error "invalid" } */
+  __lengthof__ (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];
+
+  __lengthof__ (a[f1()]);
+  __lengthof__ (b[f2()]);
+}
+
+void
+no_parens(void)
+{
+  __lengthof__ a;
+  __lengthof__ *a;
+  __lengthof__ (int [3]) {};
+
+  __lengthof__ int [3]; /* { dg-error "expected expression before" } */
+}
+
+void
+const_expr(void)
+{
+  int n = 7;
+
+  _Static_assert (__lengthof__ (int [3][n]) == 3);
+  _Static_assert (__lengthof__ (int [n][3]) == 7); /* { dg-error "not constant"} */
+  _Static_assert (__lengthof__ (int [0][3]) == 0);
+  _Static_assert (__lengthof__ (int [0]) == 0);
+
+  /* FIXME: lengthog(int [0][n]) should result in a constant expression.  */
+  _Static_assert (__lengthof__ (int [0][n]) == 0); /* { dg-error "not constant"} */
+}
diff --git a/gcc/testsuite/gcc.dg/lengthof-vla.c b/gcc/testsuite/gcc.dg/lengthof-vla.c
new file mode 100644
index 00000000000..f415fcf5a89
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/lengthof-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)[__lengthof__ (*a)]);
+void fix_var (int i,
+	      char (*a)[3][i], /* dg-warn "variable" */
+	      int (*x)[__lengthof__ (*a)]);
+void fix_uns (int i,
+	      char (*a)[3][*],
+	      int (*x)[__lengthof__ (*a)]);
+
+void zro_fix (int i,
+	      char (*a)[0][5],
+	      int (*x)[__lengthof__ (*a)]);
+void zro_var (int i,
+	      char (*a)[0][i], /* dg-warn "variable" */
+	      int (*x)[__lengthof__ (*a)]);
+void zro_uns (int i,
+	      char (*a)[0][*],
+	      int (*x)[__lengthof__ (*a)]);
+
+void var_fix (int i,
+	      char (*a)[i][5], /* dg-warn "variable" */
+	      int (*x)[__lengthof__ (*a)]); /* dg-warn "variable" */
+void var_var (int i,
+	      char (*a)[i][i], /* dg-warn "variable" */
+	      int (*x)[__lengthof__ (*a)]); /* dg-warn "variable" */
+void var_uns (int i,
+	      char (*a)[i][*], /* dg-warn "variable" */
+	      int (*x)[__lengthof__ (*a)]); /* dg-warn "variable" */
+
+void uns_fix (int i,
+	      char (*a)[*][5],
+	      int (*x)[__lengthof__ (*a)]);
+void uns_var (int i,
+	      char (*a)[*][i], /* dg-warn "variable" */
+	      int (*x)[__lengthof__ (*a)]);
+void uns_uns (int i,
+	      char (*a)[*][*],
+	      int (*x)[__lengthof__ (*a)]);
+
+// Can't test due to bug: <https://gcc.gnu.org/bugzilla/show_bug.cgi?id=116284>
+//static int z2[0];
+//static int y2[__lengthof__(z2)];
diff --git a/gcc/testsuite/gcc.dg/lengthof.c b/gcc/testsuite/gcc.dg/lengthof.c
new file mode 100644
index 00000000000..86a530d613f
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/lengthof.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 (__lengthof__ (a) == 7);
+  static_assert (__lengthof__ (long [0]) == 0);
+  static_assert (__lengthof__ (unsigned [99]) == 99);
+}
+
+void
+automatic(void)
+{
+  int a[] = {1, 2, 3};
+  int z[] = {};
+
+  static_assert (__lengthof__ (a) == 3);
+  static_assert (__lengthof__ (z) == 0);
+}
+
+void
+vla (void)
+{
+  unsigned n;
+
+  n = 99;
+  assert (__lengthof__ (short [n - 10]) == 99 - 10);
+
+  int v[n / 2];
+  assert (__lengthof__ (v) == 99 / 2);
+
+  n = 0;
+  int z[n];
+  assert (__lengthof__ (z) == 0);
+}
+
+void
+member (void)
+{
+  struct {
+    int a[8];
+  } s;
+
+  static_assert (__lengthof__ (s.a) == 8);
+}
+
+void
+vla_eval (void)
+{
+  int i;
+
+  i = 7;
+  assert (__lengthof__ (struct {int x;}[i++]) == 7);
+  assert (i == 7 + 1);
+
+  int v[i];
+  int (*p)[i];
+  p = &v;
+  assert (__lengthof__ (*p++) == i);
+  assert (p - 1 == &v);
+}
+
+void
+inner_vla_noeval (void)
+{
+  int i;
+
+  i = 3;
+  static_assert (__lengthof__ (struct {int x[i++];}[3]) == 3);
+  assert (i == 3);
+}
+
+void
+array_noeval (void)
+{
+  long a[5];
+  long (*p)[__lengthof__ (a)];
+
+  p = &a;
+  static_assert (__lengthof__ (*p++) == 5);
+  assert (p == &a);
+}
+
+void
+matrix_zero (void)
+{
+  int i;
+
+  static_assert (__lengthof__ (int [0][4]) == 0);
+  i = 3;
+  assert (__lengthof__ (int [0][i]) == 0);
+}
+
+void
+matrix_fixed (void)
+{
+  int i;
+
+  static_assert (__lengthof__ (int [7][4]) == 7);
+  i = 3;
+  static_assert (__lengthof__ (int [7][i]) == 7);
+}
+
+void
+matrix_vla (void)
+{
+  int i, j;
+
+  i = 7;
+  assert (__lengthof__ (int [i++][4]) == 7);
+  assert (i == 7 + 1);
+
+  i = 9;
+  j = 3;
+  assert (__lengthof__ (int [i++][j]) == 9);
+  assert (i == 9 + 1);
+}
+
+void
+no_parens(void)
+{
+  int n = 3;
+  int a[7];
+  int v[n];
+
+  static_assert (__lengthof__ a == 7); 
+  assert (__lengthof__ v == 3); 
+}
+
+int
+main (void)
+{
+  array ();
+  automatic ();
+  vla ();
+  member ();
+  vla_eval ();
+  inner_vla_noeval ();
+  array_noeval ();
+  matrix_zero ();
+  matrix_fixed ();
+  matrix_vla ();
+  no_parens ();
+}