mbox series

[RFC,v4,0/4] c: Add __lengthof__ operator

Message ID 20240806122218.3827577-1-alx@kernel.org
Headers show
Series c: Add __lengthof__ operator | expand

Message

Alejandro Colomar Aug. 6, 2024, 12:22 p.m. UTC
Hi!

v4:

-  Only evaluate the operand if the top array is VLA.  Inner VLAs are
   ignored.  [Joseph, Martin]
   This proved very useful for compile-time diagnostics, since we have
   more cases that are constant expressions.
-  Document the evaluation rules, which are unique to this operator
   (similar to sizeof, but we ignore inner VLAs).
-  Add tests to the testsuite.  [Joseph]
-  Swap diagnostic cases preference, to give more meaningful
   diagnostics.  [Martin]
-  Document that Xavier was the first one to suggest this feature, and
   provide a link to the mail thread where that happened.
   BTW, while reading that discussion from 2 years ago, I see that it
   was questioned the value of this operator.  Below is a rationale to
   defend it.
-  Document that Martin's help has been crucial for implementing this,
   with 'Co-developed-by'.  Would you mind confirming that I can use
   that tag?
-  CC += Kees, Qing, Jens

Rationale:

-  While compiler extensions already allow implementing ARRAY_SIZE()
   (<https://stackoverflow.com/a/57537491/6872717>), there's still no
   way to get the length of a function parameter which uses array
   notation.  While this first implementation doesn't support those yet
   (because there are some issues that need to be fixed first), the plan
   is to add support to those.  This would be a huge step towards arrays
   being first-class citizens in C.  In those cases, it would reduce the
   chance of programmer errors.  See for example
   <https://lkml.org/lkml/2015/9/3/428>.  That entire class of bugs
   would be over, _and_ programs would become simpler.

Some specific questions or concerns:

-  The tests seem to work as expected if I compile them manually, and
   run (the one that should be run) as a normal program.  The one that
   should not be run also gives the expected diagnostics.
   Can anyone give advice of why it's not running well under the test
   suite?

-  I don't like the fact that [*][n] is internally implemented exactly
   like [0][n], which makes them indistinguishable.  All other cases of
   [0] return a constent expression of value 0, but [0][n] must return a
   variable 0, to keep support for [*][n].
   Could you please change the way [*][n] (and thus [*]) is represented
   internally so that it can be differentiated from [0]?
   Do you have in mind any other way that would be a viable
   implementation of [*] that would allow distinguishing [0][n] and
   [*][n]?  Maybe making it to have one node instead of zero and mark
   that node specially?

At the bottom of this email is a range-diff against v3.

And below is a test program I used while developing the feature.  It is
quite similar to what's on the test suite (patch 4/4), since those are
based on this one.

It has comments where I'd like more diagnostics, but those are not
responsibility of this feature.  Some are fault of the representation
for [*], and others are already being worked on by Martin.  There are
also comments on code that causes compile-time errors as expected
(wanted).  Some assertions about evaluation of the operand are commented
out because due to the problems with [*][n] and [0][n] we have more
evaluation than I'd like.  However, those are only with [0], which is
not yet well supported by GCC, so we don't need to worry much for now.

The program below also compares with sizeof and alignof, which the
test-suite tests do not.

Have a lovely day!
Alex

	$ cat len.c 
	#include <stdalign.h>
	#include <stdio.h>
	#include <assert.h>


	#define memberof(T, member)                                           \
	(                                                                     \
		(T){}.member                                                  \
	)


	struct s {
		int x;
		int y[8];
		int z[];
	};


	struct s2 {
		int x;
		int z[] __attribute__((counted_by(x)));
	};


	extern int x[];


	void array(void);
	void incomplete_err(int inc[]);
	void unspecified_err(void);
	void vla(void);
	void member_array(void);
	void fam_err(void);
	void vla_eval(void);
	void in_vla_noeval(void);
	void in_vla_noeval2(void);
	void array_noeval(void);
	void vla_eval2(void);
	void matrix_0(void);
	void matrix_fixed(void);
	void matrix_vla(void);
	void f_fixed(void);
	void f_zero(void);
	void f_vla(void);
	void f_star(void);


	int
	main(int argc, char *argv[argc + 1])
	{
		(void) argv;

		// Wishlist:
		//n = lengthof(argv);
		//printf("lengthof(argv) == %zu\n", n);

		array();
		incomplete_err(&argc);
		unspecified_err();
		vla();
		member_array();
		fam_err();
		vla_eval();
		in_vla_noeval();
		in_vla_noeval2();
		array_noeval();
		vla_eval2();
		matrix_0();
		matrix_fixed();
		matrix_vla();
		f_fixed();
		f_zero();
		f_vla();
		f_star();
	}

	void
	array(void)
	{
		short   a[42];
		size_t  n;

		puts("");

		n = __lengthof__(a);
		printf("lengthof(a):\t\t %zu\n", n);
		assert(n == 42);

		n = __lengthof__(long [0]);
		printf("lengthof(long [0]):\t %zu\n", n);
		assert(n == 0);

		n = __lengthof__(long [99]);
		printf("lengthof(long [99]):\t %zu\n", n);
		assert(n == 99);
	}

	void
	incomplete_err(int inc[])
	{
		//size_t  n;

		puts("");

		// error: invalid application of ‘lengthof’ to incomplete type ‘int[]’
		//n = lengthof(x);
		//printf("lengthof(x):\t %zu\n", n);

		// error:
		//n = lengthof(inc);
		//printf("lengthof(inc):\t %zu\n", n);
	}

	void
	unspecified_err(void)
	{
		puts("");

		// error:
		//n = lengthof(int [*]);
		//printf("lengthof(int [*])\t %zu\n", n);
	}

	void
	vla(void)
	{
		size_t  n;

		n = 99;
		puts("");

		n = __lengthof__(short [n - 10]);
		printf("lengthof(short [n - 10]):\t %zu\n", n);
		assert(n == 89);

		int  v[n / 2];
		n = __lengthof__(v);
		printf("lengthof(v):\t %zu\n", n);
		assert(n == 89 / 2);

		n = 0;
		int  z[n];
		n = __lengthof__(z);
		printf("lengthof(z):\t %zu\n", n);
		assert(n == 0);
	}

	void
	member_array(void)
	{
		size_t  n;

		puts("");

		n = __lengthof__(memberof(struct s, y));
		printf("lengthof(memberof(struct s, y)):\t %zu\n", n);
		assert(n == 8);
	}

	void
	fam_err(void)
	{
		size_t  n;

		puts("");

		// error:
		//n = lengthof(memberof(struct s, z));
		//printf("lengthof(memberof(struct s, z)):\t %zu\n", n);

		// error:
		//n = sizeof(memberof(struct s, z));
		//printf("sizeof(memberof(struct s, z)):\t %zu\n", n);

		n = alignof(memberof(struct s, z));
		printf("alignof(memberof(struct s, z)):\t %zu\n", n);
	}

	void
	vla_eval(void)
	{
		int     i;
		size_t  n;

		puts("");

		i = 4;
		n = __lengthof__(struct {int x;}[i++]);
		printf("lengthof(struct {int x;}[i++]):\t %zu;  i: %d\n", n, i);
		assert(i == 5);
		assert(n == 4);

		i = 4;
		n = sizeof(struct {int x;}[i++]);
		printf("sizeof(struct {int x;}[i++]):\t %zu; i: %d\n", n, i);
		assert(i == 5);

		i = 4;
		n = alignof(struct {int x;}[i++]);
		printf("alignof(struct {int x;}[i++]):\t %zu;  i: %d\n", n, i);
		assert(i == 4);
	}

	void
	in_vla_noeval(void)
	{
		int     i;
		size_t  n;

		puts("");

		i = 4;
		n = __lengthof__(struct {int x[i++];}[3]);
		printf("lengthof(struct {int x[i++];}[3]):\t %zu;  i: %d\n", n, i);
		assert(i == 4);
		assert(n == 3);

		i = 4;
		n = sizeof(struct {int x[i++];}[3]);
		printf("sizeof(struct {int x[i++];}[3]):\t %zu; i: %d\n", n, i);
		assert(i == 5);

		i = 4;
		n = alignof(struct {int x[i++];}[3]);
		printf("alignof(struct {int x[i++];}[3]):\t %zu;  i: %d\n", n, i);
		assert(i == 4);
	}

	void
	in_vla_noeval2(void)
	{
		int     i;
		size_t  n;

		puts("");

		i = 4;
		n = __lengthof__(struct {int x[(i++, 2)];}[3]);
		printf("lengthof(struct {int x[(i++, 2)];}[3]):\t %zu;  i: %d\n", n, i);
		assert(i == 4);
		assert(n == 3);

		i = 4;
		n = sizeof(struct {int x[(i++, 2)];}[3]);
		printf("sizeof(struct {int x[(i++, 2)];}[3]):\t %zu; i: %d\n", n, i);
		assert(i == 5);

		i = 4;
		n = alignof(struct {int x[(i++, 2)];}[3]);
		printf("alignof(struct {int x[(i++, 2)];}[3]):\t %zu;  i: %d\n", n, i);
		assert(i == 4);
	}

	void
	array_noeval(void)
	{
		short   a[42];
		short   (*p)[42];
		size_t  n;

		puts("");

		p = &a;
		n = __lengthof__(*p++);
		printf("lengthof(*p++):\t %zu; p: %p\n", n, p);
		assert(p == &a);
		assert(n == 42);

		p = &a;
		n = sizeof(*p++);
		printf("lengthof(*p++):\t %zu; p: %p\n", n, p);
		assert(p == &a);

		p = &a;
		n = alignof(*p++);
		printf("alignof(*p++):\t %zu;  p: %p\n", n, p);
		assert(p == &a);
	}

	void
	vla_eval2(void)
	{
		size_t  n;

		n = 33;

		int  v[n / 2];
		int  (*q)[__lengthof__(v)];
		
		puts("");

		q = &v;
		n = __lengthof__(*q++);
		printf("lengthof(*q++):\t %zu; q: %p\n", n, q);
		assert(q - 1 == &v);
		assert(n == 33 / 2);

		q = &v;
		n = sizeof(*q++);
		printf("lengthof(*q++):\t %zu; q: %p\n", n, q);
		assert(q - 1 == &v);

		q = &v;
		n = alignof(*q++);
		printf("alignof(*q++):\t %zu;  q: %p\n", n, q);
		assert(q == &v);
	}

	void
	matrix_0(void)
	{
		int     i;
		size_t  n;

		puts("");

		n = __lengthof__(int [0][4]);
		printf("lengthof(int [0][4]):\t %zu\n", n);
		assert(n == 0);

		i = 5;
		n = __lengthof__(int [0][i++]);
		printf("lengthof(int [0][i++]):\t %zu;  i: %d\n", n, i);
		//assert(i == 5);
		assert(n == 0);

		// error: ‘[*]’ not allowed in other than function prototype scope
		//n = lengthof(int [0][*]);
		//printf("lengthof(int [0][*]):\t %zu\n", n);
		//assert(n == 0);
	}

	void
	matrix_fixed(void)
	{
		int     i;
		size_t  n;


		n = __lengthof__(int [7][4]);
		printf("lengthof(int [7][4]):\t %zu\n", n);
		assert(n == 7);

		i = 5;
		n = __lengthof__(int [7][i++]);
		printf("lengthof(int [7][i++]):\t %zu;  i: %d\n", n, i);
		assert(i == 5);
		assert(n == 7);

		// error: ‘[*]’ not allowed in other than function prototype scope
		//n = lengthof(int [7][*]);
		//printf("lengthof(int [7][*]):\t %zu\n", n);
		//assert(n == 7);
	}

	void
	matrix_vla(void)
	{
		int     i;
		size_t  n;


		i = 7;
		n = __lengthof__(int [i++][4]);
		printf("lengthof(int [i++][4]):\t %zu;  i: %d\n", n, i);
		assert(i == 8);
		assert(n == 7);

		n = __lengthof__(int [i++][n]);
		printf("lengthof(int [i++][n]):\t %zu;  i: %d\n", n, i);
		assert(i == 9);
		assert(n == 8);

		// error: ‘[*]’ not allowed in other than function prototype scope
		//n = lengthof(int [i++][*]);
		//printf("lengthof(int [i++][*]):\t %zu;  i: %d\n", n, i);
		//assert(i == 10);
		//assert(n == 9);
	}

	void l_fixed_1(int i, char (*a)[3][5], int (*x)[__lengthof__(*a)]);
	void l_fixed_2(int i, char (*a)[3][i], int (*x)[__lengthof__(*a)]);
	void l_fixed_3(int i, char (*a)[3][*], int (*x)[__lengthof__(*a)]);

	void s_fixed_1(int i, char (*a)[5][3], int (*x)[sizeof(**a)]);
	void s_fixed_2(int i, char (*a)[i][3], int (*x)[sizeof(**a)]);
	void s_fixed_3(int i, char (*a)[*][3], int (*x)[sizeof(**a)]);

	void
	f_fixed(void)
	{
		int  i3[3];
		int  i5[5];
		char c35[3][5];
		char c53[5][3];

		sizeof(l_fixed_1(5, &c35, &i3));
		//sizeof(l_fixed_1(5, &c35, &i5));  // -Wincompatible-pointer-types

		sizeof(l_fixed_2(5, &c35, &i3));
		//sizeof(l_fixed_2(5, &c35, &i5));  // -Wincompatible-pointer-types

		sizeof(l_fixed_3(5, &c35, &i3));
		//sizeof(l_fixed_3(5, &c35, &i5));  // -Wincompatible-pointer-types

		sizeof(s_fixed_1(5, &c53, &i3));
		//sizeof(s_fixed_1(5, &c53, &i5));  // -Wincompatible-pointer-types

		sizeof(s_fixed_2(5, &c53, &i3));
		//sizeof(s_fixed_2(5, &c53, &i5));  // -Wincompatible-pointer-types

		sizeof(s_fixed_3(5, &c53, &i3));
		//sizeof(s_fixed_3(5, &c53, &i5));  // -Wincompatible-pointer-types
	}

	void l_zero_1(int i, char (*a)[0][5], int (*x)[__lengthof__(*a)]);
	void l_zero_2(int i, char (*a)[0][i], int (*x)[__lengthof__(*a)]);
	void l_zero_3(int i, char (*a)[0][*], int (*x)[__lengthof__(*a)]);

	void s_zero_1(int i, char (*a)[5][0], int (*x)[sizeof(**a)]);
	void s_zero_2(int i, char (*a)[i][0], int (*x)[sizeof(**a)]);
	void s_zero_3(int i, char (*a)[*][0], int (*x)[sizeof(**a)]);

	void
	f_zero(void)
	{
		int  i0[0];
		int  i5[5];
		char c05[0][5];
		char c50[5][0];

		sizeof(l_zero_1(5, &c05, &i0));
		//sizeof(l_zero_1(5, &c05, &i5));  // -Wincompatible-pointer-types

		sizeof(l_zero_2(5, &c05, &i0));
		sizeof(l_zero_2(5, &c05, &i5));  // Wantfail

		sizeof(l_zero_3(5, &c05, &i0));
		sizeof(l_zero_3(5, &c05, &i5));  // Wantfail

		sizeof(s_zero_1(5, &c50, &i0));
		sizeof(s_zero_1(5, &c50, &i5));  // Wantfail

		sizeof(s_zero_2(5, &c50, &i0));
		sizeof(s_zero_2(5, &c50, &i5));  // Wantfail

		sizeof(s_zero_3(5, &c50, &i0));
		sizeof(s_zero_3(5, &c50, &i5));  // Wantfail
	}

	void l_vla_1(int i, int j, char (*a)[i][5], int (*x)[__lengthof__(*a)]);
	void l_vla_2(int i, int j, char (*a)[i][j], int (*x)[__lengthof__(*a)]);
	void l_vla_3(int i, int j, char (*a)[i][*], int (*x)[__lengthof__(*a)]);

	void s_vla_1(int i, int j, char (*a)[5][j], int (*x)[sizeof(**a)]);
	void s_vla_2(int i, int j, char (*a)[i][j], int (*x)[sizeof(**a)]);
	void s_vla_3(int i, int j, char (*a)[*][j], int (*x)[sizeof(**a)]);

	void
	f_vla(void)
	{
		int  i3[3];
		int  i5[5];
		char c35[3][5];
		char c53[5][3];

		sizeof(l_vla_1(3, 5, &c35, &i3));
		sizeof(l_vla_1(3, 5, &c35, &i5));  // Wantwarn

		sizeof(l_vla_2(3, 5, &c35, &i3));
		sizeof(l_vla_2(3, 5, &c35, &i5));  // Wantwarn

		sizeof(l_vla_3(3, 5, &c35, &i3));
		sizeof(l_vla_3(3, 5, &c35, &i5));  // Wantwarn

		sizeof(s_vla_1(5, 3, &c53, &i3));
		sizeof(s_vla_1(5, 3, &c53, &i5));  // Wantwarn

		sizeof(s_vla_2(5, 3, &c53, &i3));
		sizeof(s_vla_2(5, 3, &c53, &i5));  // Wantwarn

		sizeof(s_vla_3(5, 3, &c53, &i3));
		sizeof(s_vla_3(5, 3, &c53, &i5));  // Wantwarn
	}

	void l_star_1(int i, char (*a)[*][5], int (*x)[__lengthof__(*a)]);
	void l_star_2(int i, char (*a)[*][i], int (*x)[__lengthof__(*a)]);
	void l_star_3(int i, char (*a)[*][*], int (*x)[__lengthof__(*a)]);

	void s_star_1(int i, char (*a)[5][*], int (*x)[sizeof(**a)]);
	void s_star_2(int i, char (*a)[i][*], int (*x)[sizeof(**a)]);
	void s_star_3(int i, char (*a)[*][*], int (*x)[sizeof(**a)]);

	void
	f_star(void)
	{
		int  i3[3];
		int  i5[5];
		char c35[3][5];
		char c53[5][3];

		sizeof(l_star_1(5, &c35, &i3));
		sizeof(l_star_1(5, &c35, &i5));  // Wantwarn

		sizeof(l_star_2(5, &c35, &i3));
		sizeof(l_star_2(5, &c35, &i5));  // Wantwarn

		sizeof(l_star_3(5, &c35, &i3));
		sizeof(l_star_3(5, &c35, &i5));  // Wantwarn

		sizeof(s_star_1(5, &c53, &i3));
		sizeof(s_star_1(5, &c53, &i5));  // Wantwarn

		sizeof(s_star_2(5, &c53, &i3));
		sizeof(s_star_2(5, &c53, &i5));  // Wantwarn

		sizeof(s_star_3(5, &c53, &i3));
		sizeof(s_star_3(5, &c53, &i5));  // Wantwarn
	}

And here's how it runs:

	$ /opt/local/gnu/gcc/lengthof/bin/gcc len.c 
	$ ./a.out 

	lengthof(a):		 42
	lengthof(long [0]):	 0
	lengthof(long [99]):	 99



	lengthof(short [n - 10]):	 89
	lengthof(v):	 44
	lengthof(z):	 0

	lengthof(memberof(struct s, y)):	 8

	alignof(memberof(struct s, z)):	 4

	lengthof(struct {int x;}[i++]):	 4;  i: 5
	sizeof(struct {int x;}[i++]):	 16; i: 5
	alignof(struct {int x;}[i++]):	 4;  i: 4

	lengthof(struct {int x[i++];}[3]):	 3;  i: 4
	sizeof(struct {int x[i++];}[3]):	 48; i: 5
	alignof(struct {int x[i++];}[3]):	 4;  i: 4

	lengthof(struct {int x[(i++, 2)];}[3]):	 3;  i: 4
	sizeof(struct {int x[(i++, 2)];}[3]):	 24; i: 5
	alignof(struct {int x[(i++, 2)];}[3]):	 4;  i: 4

	lengthof(*p++):	 42; p: 0x7ffd18a52b30
	lengthof(*p++):	 84; p: 0x7ffd18a52b30
	alignof(*p++):	 2;  p: 0x7ffd18a52b30

	lengthof(*q++):	 16; q: 0x7ffd18a52b60
	lengthof(*q++):	 64; q: 0x7ffd18a52b60
	alignof(*q++):	 4;  q: 0x7ffd18a52b20

	lengthof(int [0][4]):	 0
	lengthof(int [0][i++]):	 0;  i: 6
	lengthof(int [7][4]):	 7
	lengthof(int [7][i++]):	 7;  i: 5
	lengthof(int [i++][4]):	 7;  i: 8
	lengthof(int [i++][n]):	 8;  i: 9



Alejandro Colomar (4):
  gcc/: Rename array_type_nelts() => array_type_nelts_minus_one()
  Merge definitions of array_type_nelts_top()
  c: Add __lengthof__() operator (n2529)
  testsuite: Add tests for __lengthof__

 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                         |  30 +++---
 gcc/c/c-fold.cc                         |   7 +-
 gcc/c/c-parser.cc                       |  61 +++++++++---
 gcc/c/c-tree.h                          |   4 +
 gcc/c/c-typeck.cc                       | 114 ++++++++++++++++++++-
 gcc/config/aarch64/aarch64.cc           |   2 +-
 gcc/config/i386/i386.cc                 |   2 +-
 gcc/cp/cp-tree.h                        |   1 -
 gcc/cp/decl.cc                          |   2 +-
 gcc/cp/init.cc                          |   8 +-
 gcc/cp/lambda.cc                        |   3 +-
 gcc/cp/operators.def                    |   1 +
 gcc/cp/tree.cc                          |  13 ---
 gcc/doc/extend.texi                     |  27 +++++
 gcc/expr.cc                             |   8 +-
 gcc/fortran/trans-array.cc              |   2 +-
 gcc/fortran/trans-openmp.cc             |   4 +-
 gcc/rust/backend/rust-tree.cc           |  13 ---
 gcc/rust/backend/rust-tree.h            |   2 -
 gcc/target.h                            |   3 +
 gcc/testsuite/gcc.dg/lengthof-compile.c |  48 +++++++++
 gcc/testsuite/gcc.dg/lengthof.c         | 126 ++++++++++++++++++++++++
 gcc/tree.cc                             |  17 +++-
 gcc/tree.h                              |   3 +-
 27 files changed, 453 insertions(+), 79 deletions(-)
 create mode 100644 gcc/testsuite/gcc.dg/lengthof-compile.c
 create mode 100644 gcc/testsuite/gcc.dg/lengthof.c

Range-diff against v3:
1:  73010cb4af6 = 1:  73010cb4af6 gcc/: Rename array_type_nelts() => array_type_nelts_minus_one()
2:  2bb966a0a89 = 2:  2bb966a0a89 Merge definitions of array_type_nelts_top()
3:  d22b5e1c015 ! 3:  e2dbfc43b14 c: Add __lengthof__() operator
    @@ Metadata
     Author: Alejandro Colomar <alx@kernel.org>
     
      ## Commit message ##
    -    c: Add __lengthof__() operator
    +    c: Add __lengthof__() operator (n2529)
     
         This operator is similar to sizeof() but can only be applied to an
         array, and returns its length (number of elements).
     
    -    TO BE DECIDED BEFORE MERGING:
    -
    -            It would be better to not evaluate the operand if the top-level
    -            array is not a VLA.
    -
         FUTURE DIRECTIONS:
     
                 We could make it work with array parameters to functions, and
    @@ Commit message
                 regardless of it being really a pointer.
     
         Link: <https://www.open-std.org/jtc1/sc22/wg14/www/docs/n2529.pdf>
    -    Cc: Xavier Del Campo Romero <xavi.dcr@tutanota.com>
    +    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>
         Cc: Gabriel Ravier <gabravier@gmail.com>
    -    Cc: Martin Uecker <uecker@tugraz.at>
         Cc: Joseph Myers <josmyers@redhat.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>
         Signed-off-by: Alejandro Colomar <alx@kernel.org>
     
      ## gcc/c-family/c-common.cc ##
    @@ gcc/c-family/c-common.cc: c_alignof_expr (location_t loc, tree expr)
     +  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,
    @@ gcc/c-family/c-common.cc: c_alignof_expr (location_t loc, tree expr)
     +		type);
     +      return error_mark_node;
     +    }
    -+  if (type_code != ARRAY_TYPE)
    -+    {
    -+      error_at (loc, "invalid application of %<lengthof%> to type %qT", type);
    -+      return error_mark_node;
    -+    }
     +
     +  return array_type_nelts_top (type);
     +}
    @@ gcc/c/c-typeck.cc: c_expr_sizeof_type (location_t loc, struct c_type_name *t)
        return ret;
      }
      
    ++static bool
    ++is_top_array_vla (tree type)
    ++{
    ++  bool zero, 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);
    ++  var = (!zero
    ++	 && (TREE_CODE (TYPE_MIN_VALUE (d)) != INTEGER_CST
    ++	     || TREE_CODE (TYPE_MAX_VALUE (d)) != INTEGER_CST));
    ++  var = var || (zero && C_TYPE_VARIABLE_SIZE (type));
    ++  return var;
    ++}
    ++
     +/* Return the result of lengthof applied to EXPR.  */
     +
     +struct c_expr
    @@ gcc/c/c-typeck.cc: c_expr_sizeof_type (location_t loc, struct c_type_name *t)
     +      ret.original_code = LENGTHOF_EXPR;
     +      ret.original_type = NULL;
     +      ret.m_decimal = 0;
    -+      if (C_TYPE_VARIABLE_SIZE (TREE_TYPE (folded_expr)))
    ++      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),
    @@ gcc/c/c-typeck.cc: c_expr_sizeof_type (location_t loc, struct c_type_name *t)
     +	  C_MAYBE_CONST_EXPR_NON_CONST (ret.value) = !expr_const_operands;
     +	  SET_EXPR_LOCATION (ret.value, loc);
     +	}
    -+      pop_maybe_used (C_TYPE_VARIABLE_SIZE (TREE_TYPE (folded_expr)));
    ++      pop_maybe_used (is_top_array_vla (TREE_TYPE (folded_expr)));
     +    }
     +  return ret;
     +}
    @@ gcc/c/c-typeck.cc: c_expr_sizeof_type (location_t loc, struct c_type_name *t)
     +    }
     +  else
     +  if ((type_expr || TREE_CODE (ret.value) == INTEGER_CST)
    -+      && C_TYPE_VARIABLE_SIZE (type))
    ++      && 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
    @@ gcc/c/c-typeck.cc: c_expr_sizeof_type (location_t loc, struct c_type_name *t)
     +			  type_expr, ret.value);
     +      C_MAYBE_CONST_EXPR_NON_CONST (ret.value) = !type_expr_const;
     +    }
    -+  pop_maybe_used (type != error_mark_node
    -+		  ? C_TYPE_VARIABLE_SIZE (type) : false);
    ++  pop_maybe_used (type != error_mark_node ? is_top_array_vla (type) : false);
     +  return ret;
     +}
     +
    @@ gcc/doc/extend.texi: If the operand of the @code{__alignof__} expression is a fu
      
     +@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 just like @code{sizeof},
    -+and the operand is evaluated following the same rules.
    -+(TODO: We probably want to restrict evaluation to top-level VLAs only.
    -+       This documentation describes the current implementation.)
    ++Its syntax is just like @code{sizeof}.
    ++The operand must be a complete array type.
    ++The operand is not evaluated
    ++if the top-level length designator is an integer constant expression;
    ++and it is evaluated
    ++if the top-level length designator is not an integer constant expression.
    ++
    ++XXX: Do we want to document the following?  I think so.
    ++XXX: It would prevent users from relying on __lengthof__
    ++XXX: for distinguishing arrays from pointers.
    ++XXX: I don't want users to complain in the future
    ++XXX: if this doesn't report errors on function parameters anymore
    ++XXX: and that breaks their assumptions.
    ++In the future,
    ++it might also accept a function parameter with array notation,
    ++an incomplete array whose length is specified by other means,
    ++such as attributes,
    ++or other similar cases.
     +
      @node Inline
      @section An Inline Function is As Fast As a Macro
-:  ----------- > 4:  9a691f7f208 testsuite: Add tests for __lengthof__

Comments

Alejandro Colomar Aug. 6, 2024, 12:24 p.m. UTC | #1
On Tue, Aug 06, 2024 at 02:22:38PM GMT, Alejandro Colomar wrote:
> Hi!
> 
> v4:
> 
> -  Only evaluate the operand if the top array is VLA.  Inner VLAs are
>    ignored.  [Joseph, Martin]
>    This proved very useful for compile-time diagnostics, since we have
>    more cases that are constant expressions.
> -  Document the evaluation rules, which are unique to this operator
>    (similar to sizeof, but we ignore inner VLAs).
> -  Add tests to the testsuite.  [Joseph]
> -  Swap diagnostic cases preference, to give more meaningful
>    diagnostics.  [Martin]
> -  Document that Xavier was the first one to suggest this feature, and
>    provide a link to the mail thread where that happened.
>    BTW, while reading that discussion from 2 years ago, I see that it

Self-correction: s/2/4/

>    was questioned the value of this operator.  Below is a rationale to
>    defend it.
> -  Document that Martin's help has been crucial for implementing this,
>    with 'Co-developed-by'.  Would you mind confirming that I can use
>    that tag?
> -  CC += Kees, Qing, Jens
> 
> Rationale:
> 
> -  While compiler extensions already allow implementing ARRAY_SIZE()
>    (<https://stackoverflow.com/a/57537491/6872717>), there's still no
>    way to get the length of a function parameter which uses array
>    notation.  While this first implementation doesn't support those yet
>    (because there are some issues that need to be fixed first), the plan
>    is to add support to those.  This would be a huge step towards arrays
>    being first-class citizens in C.  In those cases, it would reduce the
>    chance of programmer errors.  See for example
>    <https://lkml.org/lkml/2015/9/3/428>.  That entire class of bugs
>    would be over, _and_ programs would become simpler.
> 
> Some specific questions or concerns:
> 
> -  The tests seem to work as expected if I compile them manually, and
>    run (the one that should be run) as a normal program.  The one that
>    should not be run also gives the expected diagnostics.
>    Can anyone give advice of why it's not running well under the test
>    suite?
> 
> -  I don't like the fact that [*][n] is internally implemented exactly
>    like [0][n], which makes them indistinguishable.  All other cases of
>    [0] return a constent expression of value 0, but [0][n] must return a
>    variable 0, to keep support for [*][n].
>    Could you please change the way [*][n] (and thus [*]) is represented
>    internally so that it can be differentiated from [0]?
>    Do you have in mind any other way that would be a viable
>    implementation of [*] that would allow distinguishing [0][n] and
>    [*][n]?  Maybe making it to have one node instead of zero and mark
>    that node specially?
> 
> At the bottom of this email is a range-diff against v3.
> 
> And below is a test program I used while developing the feature.  It is
> quite similar to what's on the test suite (patch 4/4), since those are
> based on this one.
> 
> It has comments where I'd like more diagnostics, but those are not
> responsibility of this feature.  Some are fault of the representation
> for [*], and others are already being worked on by Martin.  There are
> also comments on code that causes compile-time errors as expected
> (wanted).  Some assertions about evaluation of the operand are commented
> out because due to the problems with [*][n] and [0][n] we have more
> evaluation than I'd like.  However, those are only with [0], which is
> not yet well supported by GCC, so we don't need to worry much for now.
> 
> The program below also compares with sizeof and alignof, which the
> test-suite tests do not.
> 
> Have a lovely day!
> Alex
> 
> 	$ cat len.c 
> 	#include <stdalign.h>
> 	#include <stdio.h>
> 	#include <assert.h>
> 
> 
> 	#define memberof(T, member)                                           \
> 	(                                                                     \
> 		(T){}.member                                                  \
> 	)
> 
> 
> 	struct s {
> 		int x;
> 		int y[8];
> 		int z[];
> 	};
> 
> 
> 	struct s2 {
> 		int x;
> 		int z[] __attribute__((counted_by(x)));
> 	};
> 
> 
> 	extern int x[];
> 
> 
> 	void array(void);
> 	void incomplete_err(int inc[]);
> 	void unspecified_err(void);
> 	void vla(void);
> 	void member_array(void);
> 	void fam_err(void);
> 	void vla_eval(void);
> 	void in_vla_noeval(void);
> 	void in_vla_noeval2(void);
> 	void array_noeval(void);
> 	void vla_eval2(void);
> 	void matrix_0(void);
> 	void matrix_fixed(void);
> 	void matrix_vla(void);
> 	void f_fixed(void);
> 	void f_zero(void);
> 	void f_vla(void);
> 	void f_star(void);
> 
> 
> 	int
> 	main(int argc, char *argv[argc + 1])
> 	{
> 		(void) argv;
> 
> 		// Wishlist:
> 		//n = lengthof(argv);
> 		//printf("lengthof(argv) == %zu\n", n);
> 
> 		array();
> 		incomplete_err(&argc);
> 		unspecified_err();
> 		vla();
> 		member_array();
> 		fam_err();
> 		vla_eval();
> 		in_vla_noeval();
> 		in_vla_noeval2();
> 		array_noeval();
> 		vla_eval2();
> 		matrix_0();
> 		matrix_fixed();
> 		matrix_vla();
> 		f_fixed();
> 		f_zero();
> 		f_vla();
> 		f_star();
> 	}
> 
> 	void
> 	array(void)
> 	{
> 		short   a[42];
> 		size_t  n;
> 
> 		puts("");
> 
> 		n = __lengthof__(a);
> 		printf("lengthof(a):\t\t %zu\n", n);
> 		assert(n == 42);
> 
> 		n = __lengthof__(long [0]);
> 		printf("lengthof(long [0]):\t %zu\n", n);
> 		assert(n == 0);
> 
> 		n = __lengthof__(long [99]);
> 		printf("lengthof(long [99]):\t %zu\n", n);
> 		assert(n == 99);
> 	}
> 
> 	void
> 	incomplete_err(int inc[])
> 	{
> 		//size_t  n;
> 
> 		puts("");
> 
> 		// error: invalid application of ‘lengthof’ to incomplete type ‘int[]’
> 		//n = lengthof(x);
> 		//printf("lengthof(x):\t %zu\n", n);
> 
> 		// error:
> 		//n = lengthof(inc);
> 		//printf("lengthof(inc):\t %zu\n", n);
> 	}
> 
> 	void
> 	unspecified_err(void)
> 	{
> 		puts("");
> 
> 		// error:
> 		//n = lengthof(int [*]);
> 		//printf("lengthof(int [*])\t %zu\n", n);
> 	}
> 
> 	void
> 	vla(void)
> 	{
> 		size_t  n;
> 
> 		n = 99;
> 		puts("");
> 
> 		n = __lengthof__(short [n - 10]);
> 		printf("lengthof(short [n - 10]):\t %zu\n", n);
> 		assert(n == 89);
> 
> 		int  v[n / 2];
> 		n = __lengthof__(v);
> 		printf("lengthof(v):\t %zu\n", n);
> 		assert(n == 89 / 2);
> 
> 		n = 0;
> 		int  z[n];
> 		n = __lengthof__(z);
> 		printf("lengthof(z):\t %zu\n", n);
> 		assert(n == 0);
> 	}
> 
> 	void
> 	member_array(void)
> 	{
> 		size_t  n;
> 
> 		puts("");
> 
> 		n = __lengthof__(memberof(struct s, y));
> 		printf("lengthof(memberof(struct s, y)):\t %zu\n", n);
> 		assert(n == 8);
> 	}
> 
> 	void
> 	fam_err(void)
> 	{
> 		size_t  n;
> 
> 		puts("");
> 
> 		// error:
> 		//n = lengthof(memberof(struct s, z));
> 		//printf("lengthof(memberof(struct s, z)):\t %zu\n", n);
> 
> 		// error:
> 		//n = sizeof(memberof(struct s, z));
> 		//printf("sizeof(memberof(struct s, z)):\t %zu\n", n);
> 
> 		n = alignof(memberof(struct s, z));
> 		printf("alignof(memberof(struct s, z)):\t %zu\n", n);
> 	}
> 
> 	void
> 	vla_eval(void)
> 	{
> 		int     i;
> 		size_t  n;
> 
> 		puts("");
> 
> 		i = 4;
> 		n = __lengthof__(struct {int x;}[i++]);
> 		printf("lengthof(struct {int x;}[i++]):\t %zu;  i: %d\n", n, i);
> 		assert(i == 5);
> 		assert(n == 4);
> 
> 		i = 4;
> 		n = sizeof(struct {int x;}[i++]);
> 		printf("sizeof(struct {int x;}[i++]):\t %zu; i: %d\n", n, i);
> 		assert(i == 5);
> 
> 		i = 4;
> 		n = alignof(struct {int x;}[i++]);
> 		printf("alignof(struct {int x;}[i++]):\t %zu;  i: %d\n", n, i);
> 		assert(i == 4);
> 	}
> 
> 	void
> 	in_vla_noeval(void)
> 	{
> 		int     i;
> 		size_t  n;
> 
> 		puts("");
> 
> 		i = 4;
> 		n = __lengthof__(struct {int x[i++];}[3]);
> 		printf("lengthof(struct {int x[i++];}[3]):\t %zu;  i: %d\n", n, i);
> 		assert(i == 4);
> 		assert(n == 3);
> 
> 		i = 4;
> 		n = sizeof(struct {int x[i++];}[3]);
> 		printf("sizeof(struct {int x[i++];}[3]):\t %zu; i: %d\n", n, i);
> 		assert(i == 5);
> 
> 		i = 4;
> 		n = alignof(struct {int x[i++];}[3]);
> 		printf("alignof(struct {int x[i++];}[3]):\t %zu;  i: %d\n", n, i);
> 		assert(i == 4);
> 	}
> 
> 	void
> 	in_vla_noeval2(void)
> 	{
> 		int     i;
> 		size_t  n;
> 
> 		puts("");
> 
> 		i = 4;
> 		n = __lengthof__(struct {int x[(i++, 2)];}[3]);
> 		printf("lengthof(struct {int x[(i++, 2)];}[3]):\t %zu;  i: %d\n", n, i);
> 		assert(i == 4);
> 		assert(n == 3);
> 
> 		i = 4;
> 		n = sizeof(struct {int x[(i++, 2)];}[3]);
> 		printf("sizeof(struct {int x[(i++, 2)];}[3]):\t %zu; i: %d\n", n, i);
> 		assert(i == 5);
> 
> 		i = 4;
> 		n = alignof(struct {int x[(i++, 2)];}[3]);
> 		printf("alignof(struct {int x[(i++, 2)];}[3]):\t %zu;  i: %d\n", n, i);
> 		assert(i == 4);
> 	}
> 
> 	void
> 	array_noeval(void)
> 	{
> 		short   a[42];
> 		short   (*p)[42];
> 		size_t  n;
> 
> 		puts("");
> 
> 		p = &a;
> 		n = __lengthof__(*p++);
> 		printf("lengthof(*p++):\t %zu; p: %p\n", n, p);
> 		assert(p == &a);
> 		assert(n == 42);
> 
> 		p = &a;
> 		n = sizeof(*p++);
> 		printf("lengthof(*p++):\t %zu; p: %p\n", n, p);
> 		assert(p == &a);
> 
> 		p = &a;
> 		n = alignof(*p++);
> 		printf("alignof(*p++):\t %zu;  p: %p\n", n, p);
> 		assert(p == &a);
> 	}
> 
> 	void
> 	vla_eval2(void)
> 	{
> 		size_t  n;
> 
> 		n = 33;
> 
> 		int  v[n / 2];
> 		int  (*q)[__lengthof__(v)];
> 		
> 		puts("");
> 
> 		q = &v;
> 		n = __lengthof__(*q++);
> 		printf("lengthof(*q++):\t %zu; q: %p\n", n, q);
> 		assert(q - 1 == &v);
> 		assert(n == 33 / 2);
> 
> 		q = &v;
> 		n = sizeof(*q++);
> 		printf("lengthof(*q++):\t %zu; q: %p\n", n, q);
> 		assert(q - 1 == &v);
> 
> 		q = &v;
> 		n = alignof(*q++);
> 		printf("alignof(*q++):\t %zu;  q: %p\n", n, q);
> 		assert(q == &v);
> 	}
> 
> 	void
> 	matrix_0(void)
> 	{
> 		int     i;
> 		size_t  n;
> 
> 		puts("");
> 
> 		n = __lengthof__(int [0][4]);
> 		printf("lengthof(int [0][4]):\t %zu\n", n);
> 		assert(n == 0);
> 
> 		i = 5;
> 		n = __lengthof__(int [0][i++]);
> 		printf("lengthof(int [0][i++]):\t %zu;  i: %d\n", n, i);
> 		//assert(i == 5);
> 		assert(n == 0);
> 
> 		// error: ‘[*]’ not allowed in other than function prototype scope
> 		//n = lengthof(int [0][*]);
> 		//printf("lengthof(int [0][*]):\t %zu\n", n);
> 		//assert(n == 0);
> 	}
> 
> 	void
> 	matrix_fixed(void)
> 	{
> 		int     i;
> 		size_t  n;
> 
> 
> 		n = __lengthof__(int [7][4]);
> 		printf("lengthof(int [7][4]):\t %zu\n", n);
> 		assert(n == 7);
> 
> 		i = 5;
> 		n = __lengthof__(int [7][i++]);
> 		printf("lengthof(int [7][i++]):\t %zu;  i: %d\n", n, i);
> 		assert(i == 5);
> 		assert(n == 7);
> 
> 		// error: ‘[*]’ not allowed in other than function prototype scope
> 		//n = lengthof(int [7][*]);
> 		//printf("lengthof(int [7][*]):\t %zu\n", n);
> 		//assert(n == 7);
> 	}
> 
> 	void
> 	matrix_vla(void)
> 	{
> 		int     i;
> 		size_t  n;
> 
> 
> 		i = 7;
> 		n = __lengthof__(int [i++][4]);
> 		printf("lengthof(int [i++][4]):\t %zu;  i: %d\n", n, i);
> 		assert(i == 8);
> 		assert(n == 7);
> 
> 		n = __lengthof__(int [i++][n]);
> 		printf("lengthof(int [i++][n]):\t %zu;  i: %d\n", n, i);
> 		assert(i == 9);
> 		assert(n == 8);
> 
> 		// error: ‘[*]’ not allowed in other than function prototype scope
> 		//n = lengthof(int [i++][*]);
> 		//printf("lengthof(int [i++][*]):\t %zu;  i: %d\n", n, i);
> 		//assert(i == 10);
> 		//assert(n == 9);
> 	}
> 
> 	void l_fixed_1(int i, char (*a)[3][5], int (*x)[__lengthof__(*a)]);
> 	void l_fixed_2(int i, char (*a)[3][i], int (*x)[__lengthof__(*a)]);
> 	void l_fixed_3(int i, char (*a)[3][*], int (*x)[__lengthof__(*a)]);
> 
> 	void s_fixed_1(int i, char (*a)[5][3], int (*x)[sizeof(**a)]);
> 	void s_fixed_2(int i, char (*a)[i][3], int (*x)[sizeof(**a)]);
> 	void s_fixed_3(int i, char (*a)[*][3], int (*x)[sizeof(**a)]);
> 
> 	void
> 	f_fixed(void)
> 	{
> 		int  i3[3];
> 		int  i5[5];
> 		char c35[3][5];
> 		char c53[5][3];
> 
> 		sizeof(l_fixed_1(5, &c35, &i3));
> 		//sizeof(l_fixed_1(5, &c35, &i5));  // -Wincompatible-pointer-types
> 
> 		sizeof(l_fixed_2(5, &c35, &i3));
> 		//sizeof(l_fixed_2(5, &c35, &i5));  // -Wincompatible-pointer-types
> 
> 		sizeof(l_fixed_3(5, &c35, &i3));
> 		//sizeof(l_fixed_3(5, &c35, &i5));  // -Wincompatible-pointer-types
> 
> 		sizeof(s_fixed_1(5, &c53, &i3));
> 		//sizeof(s_fixed_1(5, &c53, &i5));  // -Wincompatible-pointer-types
> 
> 		sizeof(s_fixed_2(5, &c53, &i3));
> 		//sizeof(s_fixed_2(5, &c53, &i5));  // -Wincompatible-pointer-types
> 
> 		sizeof(s_fixed_3(5, &c53, &i3));
> 		//sizeof(s_fixed_3(5, &c53, &i5));  // -Wincompatible-pointer-types
> 	}
> 
> 	void l_zero_1(int i, char (*a)[0][5], int (*x)[__lengthof__(*a)]);
> 	void l_zero_2(int i, char (*a)[0][i], int (*x)[__lengthof__(*a)]);
> 	void l_zero_3(int i, char (*a)[0][*], int (*x)[__lengthof__(*a)]);
> 
> 	void s_zero_1(int i, char (*a)[5][0], int (*x)[sizeof(**a)]);
> 	void s_zero_2(int i, char (*a)[i][0], int (*x)[sizeof(**a)]);
> 	void s_zero_3(int i, char (*a)[*][0], int (*x)[sizeof(**a)]);
> 
> 	void
> 	f_zero(void)
> 	{
> 		int  i0[0];
> 		int  i5[5];
> 		char c05[0][5];
> 		char c50[5][0];
> 
> 		sizeof(l_zero_1(5, &c05, &i0));
> 		//sizeof(l_zero_1(5, &c05, &i5));  // -Wincompatible-pointer-types
> 
> 		sizeof(l_zero_2(5, &c05, &i0));
> 		sizeof(l_zero_2(5, &c05, &i5));  // Wantfail
> 
> 		sizeof(l_zero_3(5, &c05, &i0));
> 		sizeof(l_zero_3(5, &c05, &i5));  // Wantfail
> 
> 		sizeof(s_zero_1(5, &c50, &i0));
> 		sizeof(s_zero_1(5, &c50, &i5));  // Wantfail
> 
> 		sizeof(s_zero_2(5, &c50, &i0));
> 		sizeof(s_zero_2(5, &c50, &i5));  // Wantfail
> 
> 		sizeof(s_zero_3(5, &c50, &i0));
> 		sizeof(s_zero_3(5, &c50, &i5));  // Wantfail
> 	}
> 
> 	void l_vla_1(int i, int j, char (*a)[i][5], int (*x)[__lengthof__(*a)]);
> 	void l_vla_2(int i, int j, char (*a)[i][j], int (*x)[__lengthof__(*a)]);
> 	void l_vla_3(int i, int j, char (*a)[i][*], int (*x)[__lengthof__(*a)]);
> 
> 	void s_vla_1(int i, int j, char (*a)[5][j], int (*x)[sizeof(**a)]);
> 	void s_vla_2(int i, int j, char (*a)[i][j], int (*x)[sizeof(**a)]);
> 	void s_vla_3(int i, int j, char (*a)[*][j], int (*x)[sizeof(**a)]);
> 
> 	void
> 	f_vla(void)
> 	{
> 		int  i3[3];
> 		int  i5[5];
> 		char c35[3][5];
> 		char c53[5][3];
> 
> 		sizeof(l_vla_1(3, 5, &c35, &i3));
> 		sizeof(l_vla_1(3, 5, &c35, &i5));  // Wantwarn
> 
> 		sizeof(l_vla_2(3, 5, &c35, &i3));
> 		sizeof(l_vla_2(3, 5, &c35, &i5));  // Wantwarn
> 
> 		sizeof(l_vla_3(3, 5, &c35, &i3));
> 		sizeof(l_vla_3(3, 5, &c35, &i5));  // Wantwarn
> 
> 		sizeof(s_vla_1(5, 3, &c53, &i3));
> 		sizeof(s_vla_1(5, 3, &c53, &i5));  // Wantwarn
> 
> 		sizeof(s_vla_2(5, 3, &c53, &i3));
> 		sizeof(s_vla_2(5, 3, &c53, &i5));  // Wantwarn
> 
> 		sizeof(s_vla_3(5, 3, &c53, &i3));
> 		sizeof(s_vla_3(5, 3, &c53, &i5));  // Wantwarn
> 	}
> 
> 	void l_star_1(int i, char (*a)[*][5], int (*x)[__lengthof__(*a)]);
> 	void l_star_2(int i, char (*a)[*][i], int (*x)[__lengthof__(*a)]);
> 	void l_star_3(int i, char (*a)[*][*], int (*x)[__lengthof__(*a)]);
> 
> 	void s_star_1(int i, char (*a)[5][*], int (*x)[sizeof(**a)]);
> 	void s_star_2(int i, char (*a)[i][*], int (*x)[sizeof(**a)]);
> 	void s_star_3(int i, char (*a)[*][*], int (*x)[sizeof(**a)]);
> 
> 	void
> 	f_star(void)
> 	{
> 		int  i3[3];
> 		int  i5[5];
> 		char c35[3][5];
> 		char c53[5][3];
> 
> 		sizeof(l_star_1(5, &c35, &i3));
> 		sizeof(l_star_1(5, &c35, &i5));  // Wantwarn
> 
> 		sizeof(l_star_2(5, &c35, &i3));
> 		sizeof(l_star_2(5, &c35, &i5));  // Wantwarn
> 
> 		sizeof(l_star_3(5, &c35, &i3));
> 		sizeof(l_star_3(5, &c35, &i5));  // Wantwarn
> 
> 		sizeof(s_star_1(5, &c53, &i3));
> 		sizeof(s_star_1(5, &c53, &i5));  // Wantwarn
> 
> 		sizeof(s_star_2(5, &c53, &i3));
> 		sizeof(s_star_2(5, &c53, &i5));  // Wantwarn
> 
> 		sizeof(s_star_3(5, &c53, &i3));
> 		sizeof(s_star_3(5, &c53, &i5));  // Wantwarn
> 	}
> 
> And here's how it runs:
> 
> 	$ /opt/local/gnu/gcc/lengthof/bin/gcc len.c 
> 	$ ./a.out 
> 
> 	lengthof(a):		 42
> 	lengthof(long [0]):	 0
> 	lengthof(long [99]):	 99
> 
> 
> 
> 	lengthof(short [n - 10]):	 89
> 	lengthof(v):	 44
> 	lengthof(z):	 0
> 
> 	lengthof(memberof(struct s, y)):	 8
> 
> 	alignof(memberof(struct s, z)):	 4
> 
> 	lengthof(struct {int x;}[i++]):	 4;  i: 5
> 	sizeof(struct {int x;}[i++]):	 16; i: 5
> 	alignof(struct {int x;}[i++]):	 4;  i: 4
> 
> 	lengthof(struct {int x[i++];}[3]):	 3;  i: 4
> 	sizeof(struct {int x[i++];}[3]):	 48; i: 5
> 	alignof(struct {int x[i++];}[3]):	 4;  i: 4
> 
> 	lengthof(struct {int x[(i++, 2)];}[3]):	 3;  i: 4
> 	sizeof(struct {int x[(i++, 2)];}[3]):	 24; i: 5
> 	alignof(struct {int x[(i++, 2)];}[3]):	 4;  i: 4
> 
> 	lengthof(*p++):	 42; p: 0x7ffd18a52b30
> 	lengthof(*p++):	 84; p: 0x7ffd18a52b30
> 	alignof(*p++):	 2;  p: 0x7ffd18a52b30
> 
> 	lengthof(*q++):	 16; q: 0x7ffd18a52b60
> 	lengthof(*q++):	 64; q: 0x7ffd18a52b60
> 	alignof(*q++):	 4;  q: 0x7ffd18a52b20
> 
> 	lengthof(int [0][4]):	 0
> 	lengthof(int [0][i++]):	 0;  i: 6
> 	lengthof(int [7][4]):	 7
> 	lengthof(int [7][i++]):	 7;  i: 5
> 	lengthof(int [i++][4]):	 7;  i: 8
> 	lengthof(int [i++][n]):	 8;  i: 9
> 
> 
> 
> Alejandro Colomar (4):
>   gcc/: Rename array_type_nelts() => array_type_nelts_minus_one()
>   Merge definitions of array_type_nelts_top()
>   c: Add __lengthof__() operator (n2529)
>   testsuite: Add tests for __lengthof__
> 
>  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                         |  30 +++---
>  gcc/c/c-fold.cc                         |   7 +-
>  gcc/c/c-parser.cc                       |  61 +++++++++---
>  gcc/c/c-tree.h                          |   4 +
>  gcc/c/c-typeck.cc                       | 114 ++++++++++++++++++++-
>  gcc/config/aarch64/aarch64.cc           |   2 +-
>  gcc/config/i386/i386.cc                 |   2 +-
>  gcc/cp/cp-tree.h                        |   1 -
>  gcc/cp/decl.cc                          |   2 +-
>  gcc/cp/init.cc                          |   8 +-
>  gcc/cp/lambda.cc                        |   3 +-
>  gcc/cp/operators.def                    |   1 +
>  gcc/cp/tree.cc                          |  13 ---
>  gcc/doc/extend.texi                     |  27 +++++
>  gcc/expr.cc                             |   8 +-
>  gcc/fortran/trans-array.cc              |   2 +-
>  gcc/fortran/trans-openmp.cc             |   4 +-
>  gcc/rust/backend/rust-tree.cc           |  13 ---
>  gcc/rust/backend/rust-tree.h            |   2 -
>  gcc/target.h                            |   3 +
>  gcc/testsuite/gcc.dg/lengthof-compile.c |  48 +++++++++
>  gcc/testsuite/gcc.dg/lengthof.c         | 126 ++++++++++++++++++++++++
>  gcc/tree.cc                             |  17 +++-
>  gcc/tree.h                              |   3 +-
>  27 files changed, 453 insertions(+), 79 deletions(-)
>  create mode 100644 gcc/testsuite/gcc.dg/lengthof-compile.c
>  create mode 100644 gcc/testsuite/gcc.dg/lengthof.c
> 
> Range-diff against v3:
> 1:  73010cb4af6 = 1:  73010cb4af6 gcc/: Rename array_type_nelts() => array_type_nelts_minus_one()
> 2:  2bb966a0a89 = 2:  2bb966a0a89 Merge definitions of array_type_nelts_top()
> 3:  d22b5e1c015 ! 3:  e2dbfc43b14 c: Add __lengthof__() operator
>     @@ Metadata
>      Author: Alejandro Colomar <alx@kernel.org>
>      
>       ## Commit message ##
>     -    c: Add __lengthof__() operator
>     +    c: Add __lengthof__() operator (n2529)
>      
>          This operator is similar to sizeof() but can only be applied to an
>          array, and returns its length (number of elements).
>      
>     -    TO BE DECIDED BEFORE MERGING:
>     -
>     -            It would be better to not evaluate the operand if the top-level
>     -            array is not a VLA.
>     -
>          FUTURE DIRECTIONS:
>      
>                  We could make it work with array parameters to functions, and
>     @@ Commit message
>                  regardless of it being really a pointer.
>      
>          Link: <https://www.open-std.org/jtc1/sc22/wg14/www/docs/n2529.pdf>
>     -    Cc: Xavier Del Campo Romero <xavi.dcr@tutanota.com>
>     +    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>
>          Cc: Gabriel Ravier <gabravier@gmail.com>
>     -    Cc: Martin Uecker <uecker@tugraz.at>
>          Cc: Joseph Myers <josmyers@redhat.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>
>          Signed-off-by: Alejandro Colomar <alx@kernel.org>
>      
>       ## gcc/c-family/c-common.cc ##
>     @@ gcc/c-family/c-common.cc: c_alignof_expr (location_t loc, tree expr)
>      +  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,
>     @@ gcc/c-family/c-common.cc: c_alignof_expr (location_t loc, tree expr)
>      +		type);
>      +      return error_mark_node;
>      +    }
>     -+  if (type_code != ARRAY_TYPE)
>     -+    {
>     -+      error_at (loc, "invalid application of %<lengthof%> to type %qT", type);
>     -+      return error_mark_node;
>     -+    }
>      +
>      +  return array_type_nelts_top (type);
>      +}
>     @@ gcc/c/c-typeck.cc: c_expr_sizeof_type (location_t loc, struct c_type_name *t)
>         return ret;
>       }
>       
>     ++static bool
>     ++is_top_array_vla (tree type)
>     ++{
>     ++  bool zero, 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);
>     ++  var = (!zero
>     ++	 && (TREE_CODE (TYPE_MIN_VALUE (d)) != INTEGER_CST
>     ++	     || TREE_CODE (TYPE_MAX_VALUE (d)) != INTEGER_CST));
>     ++  var = var || (zero && C_TYPE_VARIABLE_SIZE (type));
>     ++  return var;
>     ++}
>     ++
>      +/* Return the result of lengthof applied to EXPR.  */
>      +
>      +struct c_expr
>     @@ gcc/c/c-typeck.cc: c_expr_sizeof_type (location_t loc, struct c_type_name *t)
>      +      ret.original_code = LENGTHOF_EXPR;
>      +      ret.original_type = NULL;
>      +      ret.m_decimal = 0;
>     -+      if (C_TYPE_VARIABLE_SIZE (TREE_TYPE (folded_expr)))
>     ++      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),
>     @@ gcc/c/c-typeck.cc: c_expr_sizeof_type (location_t loc, struct c_type_name *t)
>      +	  C_MAYBE_CONST_EXPR_NON_CONST (ret.value) = !expr_const_operands;
>      +	  SET_EXPR_LOCATION (ret.value, loc);
>      +	}
>     -+      pop_maybe_used (C_TYPE_VARIABLE_SIZE (TREE_TYPE (folded_expr)));
>     ++      pop_maybe_used (is_top_array_vla (TREE_TYPE (folded_expr)));
>      +    }
>      +  return ret;
>      +}
>     @@ gcc/c/c-typeck.cc: c_expr_sizeof_type (location_t loc, struct c_type_name *t)
>      +    }
>      +  else
>      +  if ((type_expr || TREE_CODE (ret.value) == INTEGER_CST)
>     -+      && C_TYPE_VARIABLE_SIZE (type))
>     ++      && 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
>     @@ gcc/c/c-typeck.cc: c_expr_sizeof_type (location_t loc, struct c_type_name *t)
>      +			  type_expr, ret.value);
>      +      C_MAYBE_CONST_EXPR_NON_CONST (ret.value) = !type_expr_const;
>      +    }
>     -+  pop_maybe_used (type != error_mark_node
>     -+		  ? C_TYPE_VARIABLE_SIZE (type) : false);
>     ++  pop_maybe_used (type != error_mark_node ? is_top_array_vla (type) : false);
>      +  return ret;
>      +}
>      +
>     @@ gcc/doc/extend.texi: If the operand of the @code{__alignof__} expression is a fu
>       
>      +@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 just like @code{sizeof},
>     -+and the operand is evaluated following the same rules.
>     -+(TODO: We probably want to restrict evaluation to top-level VLAs only.
>     -+       This documentation describes the current implementation.)
>     ++Its syntax is just like @code{sizeof}.
>     ++The operand must be a complete array type.
>     ++The operand is not evaluated
>     ++if the top-level length designator is an integer constant expression;
>     ++and it is evaluated
>     ++if the top-level length designator is not an integer constant expression.
>     ++
>     ++XXX: Do we want to document the following?  I think so.
>     ++XXX: It would prevent users from relying on __lengthof__
>     ++XXX: for distinguishing arrays from pointers.
>     ++XXX: I don't want users to complain in the future
>     ++XXX: if this doesn't report errors on function parameters anymore
>     ++XXX: and that breaks their assumptions.
>     ++In the future,
>     ++it might also accept a function parameter with array notation,
>     ++an incomplete array whose length is specified by other means,
>     ++such as attributes,
>     ++or other similar cases.
>      +
>       @node Inline
>       @section An Inline Function is As Fast As a Macro
> -:  ----------- > 4:  9a691f7f208 testsuite: Add tests for __lengthof__
> -- 
> 2.45.2
>
Martin Uecker Aug. 6, 2024, 1:37 p.m. UTC | #2
Am Dienstag, dem 06.08.2024 um 14:22 +0200 schrieb Alejandro Colomar:
> Hi!
> 
> -  The tests seem to work as expected if I compile them manually, and
>    run (the one that should be run) as a normal program.  The one that
>    should not be run also gives the expected diagnostics.
>    Can anyone give advice of why it's not running well under the test
>    suite?

What is the output?  You get an additional warning / error.

> 
> -  I don't like the fact that [*][n] is internally implemented exactly
>    like [0][n], which makes them indistinguishable.  All other cases of
>    [0] return a constent expression of value 0, but [0][n] must return a
>    variable 0, to keep support for [*][n].
>    Could you please change the way [*][n] (and thus [*]) is represented
>    internally so that it can be differentiated from [0]?
>    Do you have in mind any other way that would be a viable
>    implementation of [*] that would allow distinguishing [0][n] and
>    [*][n]?  Maybe making it to have one node instead of zero and mark
>    that node specially?

The C++ frontend encodes zero-sized arrays using a range of [0,-1]. 
I have a half-finished patch which implements this for the C FE.


Martin
Alejandro Colomar Aug. 6, 2024, 2:12 p.m. UTC | #3
Hi Martin,

On Tue, Aug 06, 2024 at 03:37:13PM GMT, Martin Uecker wrote:
> Am Dienstag, dem 06.08.2024 um 14:22 +0200 schrieb Alejandro Colomar:
> > Hi!
> > 
> > -  The tests seem to work as expected if I compile them manually, and
> >    run (the one that should be run) as a normal program.  The one that
> >    should not be run also gives the expected diagnostics.
> >    Can anyone give advice of why it's not running well under the test
> >    suite?
> 
> What is the output?  You get an additional warning / error.

	$ /opt/local/gnu/gcc/lengthof/bin/gcc gcc/testsuite/gcc.dg/lengthof-compile.c 
	gcc/testsuite/gcc.dg/lengthof-compile.c: In function ‘incomplete’:
	gcc/testsuite/gcc.dg/lengthof-compile.c:10:19: error: invalid application of ‘lengthof’ to incomplete type ‘int[]’
	   10 |   n = __lengthof__(x);  /* { dg-error "incomplete" } */
	      |                   ^
	gcc/testsuite/gcc.dg/lengthof-compile.c:14:19: error: invalid application of ‘lengthof’ to type ‘int *’
	   14 |   n = __lengthof__(p);  /* { dg-error "invalid" } */
	      |                   ^
	gcc/testsuite/gcc.dg/lengthof-compile.c: In function ‘fam’:
	gcc/testsuite/gcc.dg/lengthof-compile.c:26:19: error: invalid application of ‘lengthof’ to incomplete type ‘int[]’
	   26 |   n = __lengthof__(s.fam); /* { dg-error "incomplete" } */
	      |                   ^
	gcc/testsuite/gcc.dg/lengthof-compile.c: In function ‘func’:
	gcc/testsuite/gcc.dg/lengthof-compile.c:41:20: error: passing argument 3 of ‘fix_fix’ from incompatible pointer type [-Wincompatible-pointer-types]
	   41 |   fix_fix(5, &c35, &i5); /* { dg-error "incompatible-pointer-types" } */
	      |                    ^~~
	      |                    |
	      |                    int (*)[5]
	gcc/testsuite/gcc.dg/lengthof-compile.c:29:44: note: expected ‘int (*)[3]’ but argument is of type ‘int (*)[5]’
	   29 | void fix_fix(int i, char (*a)[3][5], int (*x)[__lengthof__(*a)]);
	      |                                      ~~~~~~^~~~~~~~~~~~~~~~~~~~
	gcc/testsuite/gcc.dg/lengthof-compile.c:44:20: error: passing argument 3 of ‘fix_var’ from incompatible pointer type [-Wincompatible-pointer-types]
	   44 |   fix_var(5, &c35, &i5); /* { dg-error "incompatible-pointer-types" } */
	      |                    ^~~
	      |                    |
	      |                    int (*)[5]
	gcc/testsuite/gcc.dg/lengthof-compile.c:30:44: note: expected ‘int (*)[3]’ but argument is of type ‘int (*)[5]’
	   30 | void fix_var(int i, char (*a)[3][i], int (*x)[__lengthof__(*a)]);
	      |                                      ~~~~~~^~~~~~~~~~~~~~~~~~~~
	gcc/testsuite/gcc.dg/lengthof-compile.c:47:20: error: passing argument 3 of ‘fix_uns’ from incompatible pointer type [-Wincompatible-pointer-types]
	   47 |   fix_uns(5, &c35, &i5); /* { dg-error "incompatible-pointer-types" } */
	      |                    ^~~
	      |                    |
	      |                    int (*)[5]
	gcc/testsuite/gcc.dg/lengthof-compile.c:31:44: note: expected ‘int (*)[3]’ but argument is of type ‘int (*)[5]’
	   31 | void fix_uns(int i, char (*a)[3][*], int (*x)[__lengthof__(*a)]);
	      |                                      ~~~~~~^~~~~~~~~~~~~~~~~~~~
	$ /opt/local/gnu/gcc/lengthof/bin/gcc gcc/testsuite/gcc.dg/lengthof-compile.c |& grep ' error: '
	gcc/testsuite/gcc.dg/lengthof-compile.c:10:19: error: invalid application of ‘lengthof’ to incomplete type ‘int[]’
	gcc/testsuite/gcc.dg/lengthof-compile.c:14:19: error: invalid application of ‘lengthof’ to type ‘int *’
	gcc/testsuite/gcc.dg/lengthof-compile.c:26:19: error: invalid application of ‘lengthof’ to incomplete type ‘int[]’
	gcc/testsuite/gcc.dg/lengthof-compile.c:41:20: error: passing argument 3 of ‘fix_fix’ from incompatible pointer type [-Wincompatible-pointer-types]
	gcc/testsuite/gcc.dg/lengthof-compile.c:44:20: error: passing argument 3 of ‘fix_var’ from incompatible pointer type [-Wincompatible-pointer-types]
	gcc/testsuite/gcc.dg/lengthof-compile.c:47:20: error: passing argument 3 of ‘fix_uns’ from incompatible pointer type [-Wincompatible-pointer-types]
	$ /opt/local/gnu/gcc/lengthof/bin/gcc gcc/testsuite/gcc.dg/lengthof-compile.c |& grep ' error: ' | wc -l
	6

I count 6, which is what I expect:

	$ grep dg-error gcc/testsuite/gcc.dg/lengthof-compile.c
	  n = __lengthof__(x);  /* { dg-error "incomplete" } */
	  n = __lengthof__(p);  /* { dg-error "invalid" } */
	  n = __lengthof__(s.fam); /* { dg-error "incomplete" } */
	  fix_fix(5, &c35, &i5); /* { dg-error "incompatible-pointer-types" } */
	  fix_var(5, &c35, &i5); /* { dg-error "incompatible-pointer-types" } */
	  fix_uns(5, &c35, &i5); /* { dg-error "incompatible-pointer-types" } */
	$ grep dg-error gcc/testsuite/gcc.dg/lengthof-compile.c | wc -l
	6

When running `make check -j24 -Orecurse |& tee log`, this is what I see:

            FAIL: gcc.dg/lengthof-compile.c (test for excess errors)

Is there any way to see more details?

> > -  I don't like the fact that [*][n] is internally implemented exactly
> >    like [0][n], which makes them indistinguishable.  All other cases of
> >    [0] return a constent expression of value 0, but [0][n] must return a
> >    variable 0, to keep support for [*][n].
> >    Could you please change the way [*][n] (and thus [*]) is represented
> >    internally so that it can be differentiated from [0]?
> >    Do you have in mind any other way that would be a viable
> >    implementation of [*] that would allow distinguishing [0][n] and
> >    [*][n]?  Maybe making it to have one node instead of zero and mark
> >    that node specially?
> 
> The C++ frontend encodes zero-sized arrays using a range of [0,-1]. 
> I have a half-finished patch which implements this for the C FE.

Thanks!  I guess your patch will be merged before mine, so please ping
me when that happens so I update mine for it.

BTW, do you allow me to use Co-developed-by: you?

Have a lovely day!
Alex
Martin Uecker Aug. 6, 2024, 2:43 p.m. UTC | #4
Am Dienstag, dem 06.08.2024 um 16:12 +0200 schrieb Alejandro Colomar:
> Hi Martin,
> 
> On Tue, Aug 06, 2024 at 03:37:13PM GMT, Martin Uecker wrote:
> > Am Dienstag, dem 06.08.2024 um 14:22 +0200 schrieb Alejandro Colomar:
> > > Hi!
> > > 
> > > -  The tests seem to work as expected if I compile them manually, and
> > >    run (the one that should be run) as a normal program.  The one that
> > >    should not be run also gives the expected diagnostics.
> > >    Can anyone give advice of why it's not running well under the test
> > >    suite?
> > 
> > What is the output?  You get an additional warning / error.
> 
> 	$ /opt/local/gnu/gcc/lengthof/bin/gcc gcc/testsuite/gcc.dg/lengthof-compile.c 
> 	gcc/testsuite/gcc.dg/lengthof-compile.c: In function ‘incomplete’:
> 	gcc/testsuite/gcc.dg/lengthof-compile.c:10:19: error: invalid application of ‘lengthof’ to incomplete type ‘int[]’
> 	   10 |   n = __lengthof__(x);  /* { dg-error "incomplete" } */
> 	      |                   ^
> 	gcc/testsuite/gcc.dg/lengthof-compile.c:14:19: error: invalid application of ‘lengthof’ to type ‘int *’
> 	   14 |   n = __lengthof__(p);  /* { dg-error "invalid" } */
> 	      |                   ^
> 	gcc/testsuite/gcc.dg/lengthof-compile.c: In function ‘fam’:
> 	gcc/testsuite/gcc.dg/lengthof-compile.c:26:19: error: invalid application of ‘lengthof’ to incomplete type ‘int[]’
> 	   26 |   n = __lengthof__(s.fam); /* { dg-error "incomplete" } */
> 	      |                   ^
> 	gcc/testsuite/gcc.dg/lengthof-compile.c: In function ‘func’:
> 	gcc/testsuite/gcc.dg/lengthof-compile.c:41:20: error: passing argument 3 of ‘fix_fix’ from incompatible pointer type [-Wincompatible-pointer-types]
> 	   41 |   fix_fix(5, &c35, &i5); /* { dg-error "incompatible-pointer-types" } */
> 	      |                    ^~~
> 	      |                    |
> 	      |                    int (*)[5]
> 	gcc/testsuite/gcc.dg/lengthof-compile.c:29:44: note: expected ‘int (*)[3]’ but argument is of type ‘int (*)[5]’
> 	   29 | void fix_fix(int i, char (*a)[3][5], int (*x)[__lengthof__(*a)]);
> 	      |                                      ~~~~~~^~~~~~~~~~~~~~~~~~~~
> 	gcc/testsuite/gcc.dg/lengthof-compile.c:44:20: error: passing argument 3 of ‘fix_var’ from incompatible pointer type [-Wincompatible-pointer-types]
> 	   44 |   fix_var(5, &c35, &i5); /* { dg-error "incompatible-pointer-types" } */
> 	      |                    ^~~
> 	      |                    |
> 	      |                    int (*)[5]
> 	gcc/testsuite/gcc.dg/lengthof-compile.c:30:44: note: expected ‘int (*)[3]’ but argument is of type ‘int (*)[5]’
> 	   30 | void fix_var(int i, char (*a)[3][i], int (*x)[__lengthof__(*a)]);
> 	      |                                      ~~~~~~^~~~~~~~~~~~~~~~~~~~
> 	gcc/testsuite/gcc.dg/lengthof-compile.c:47:20: error: passing argument 3 of ‘fix_uns’ from incompatible pointer type [-Wincompatible-pointer-types]
> 	   47 |   fix_uns(5, &c35, &i5); /* { dg-error "incompatible-pointer-types" } */
> 	      |                    ^~~
> 	      |                    |
> 	      |                    int (*)[5]
> 	gcc/testsuite/gcc.dg/lengthof-compile.c:31:44: note: expected ‘int (*)[3]’ but argument is of type ‘int (*)[5]’
> 	   31 | void fix_uns(int i, char (*a)[3][*], int (*x)[__lengthof__(*a)]);
> 	      |                                      ~~~~~~^~~~~~~~~~~~~~~~~~~~
> 	$ /opt/local/gnu/gcc/lengthof/bin/gcc gcc/testsuite/gcc.dg/lengthof-compile.c |& grep ' error: '
> 	gcc/testsuite/gcc.dg/lengthof-compile.c:10:19: error: invalid application of ‘lengthof’ to incomplete type ‘int[]’
> 	gcc/testsuite/gcc.dg/lengthof-compile.c:14:19: error: invalid application of ‘lengthof’ to type ‘int *’
> 	gcc/testsuite/gcc.dg/lengthof-compile.c:26:19: error: invalid application of ‘lengthof’ to incomplete type ‘int[]’
> 	gcc/testsuite/gcc.dg/lengthof-compile.c:41:20: error: passing argument 3 of ‘fix_fix’ from incompatible pointer type [-Wincompatible-pointer-types]
> 	gcc/testsuite/gcc.dg/lengthof-compile.c:44:20: error: passing argument 3 of ‘fix_var’ from incompatible pointer type [-Wincompatible-pointer-types]
> 	gcc/testsuite/gcc.dg/lengthof-compile.c:47:20: error: passing argument 3 of ‘fix_uns’ from incompatible pointer type [-Wincompatible-pointer-types]
> 	$ /opt/local/gnu/gcc/lengthof/bin/gcc gcc/testsuite/gcc.dg/lengthof-compile.c |& grep ' error: ' | wc -l
> 	6
> 
> I count 6, which is what I expect:
> 
> 	$ grep dg-error gcc/testsuite/gcc.dg/lengthof-compile.c
> 	  n = __lengthof__(x);  /* { dg-error "incomplete" } */
> 	  n = __lengthof__(p);  /* { dg-error "invalid" } */
> 	  n = __lengthof__(s.fam); /* { dg-error "incomplete" } */
> 	  fix_fix(5, &c35, &i5); /* { dg-error "incompatible-pointer-types" } */
> 	  fix_var(5, &c35, &i5); /* { dg-error "incompatible-pointer-types" } */
> 	  fix_uns(5, &c35, &i5); /* { dg-error "incompatible-pointer-types" } */
> 	$ grep dg-error gcc/testsuite/gcc.dg/lengthof-compile.c | wc -l
> 	6
> 
> When running `make check -j24 -Orecurse |& tee log`, this is what I see:
> 
>             FAIL: gcc.dg/lengthof-compile.c (test for excess errors)
> 
> Is there any way to see more details?

See gcc/testsuite/gcc/gcc.log 

There are also *.sum files which you can diff against a build
without your patch to see whether there are any regressions.

> 
> > > -  I don't like the fact that [*][n] is internally implemented exactly
> > >    like [0][n], which makes them indistinguishable.  All other cases of
> > >    [0] return a constent expression of value 0, but [0][n] must return a
> > >    variable 0, to keep support for [*][n].
> > >    Could you please change the way [*][n] (and thus [*]) is represented
> > >    internally so that it can be differentiated from [0]?
> > >    Do you have in mind any other way that would be a viable
> > >    implementation of [*] that would allow distinguishing [0][n] and
> > >    [*][n]?  Maybe making it to have one node instead of zero and mark
> > >    that node specially?
> > 
> > The C++ frontend encodes zero-sized arrays using a range of [0,-1]. 
> > I have a half-finished patch which implements this for the C FE.
> 
> Thanks!  I guess your patch will be merged before mine, so please ping
> me when that happens so I update mine for it.

Not sure about this...
> 
> BTW, do you allow me to use Co-developed-by: you?

ok,

Martin
> 
> Have a lovely day!
> Alex
>
Alejandro Colomar Aug. 6, 2024, 3:40 p.m. UTC | #5
Hi Martin,

On Tue, Aug 06, 2024 at 04:43:27PM GMT, Martin Uecker wrote:
> > When running `make check -j24 -Orecurse |& tee log`, this is what I see:
> > 
> >             FAIL: gcc.dg/lengthof-compile.c (test for excess errors)
> > 
> > Is there any way to see more details?
> 
> See gcc/testsuite/gcc/gcc.log 

Ahhh, thanks!  It seems it was only the obvious C90-compat warnings that
I need to turn off.  It all seems good after that.

FAIL: gcc.dg/lengthof-compile.c (test for excess errors)
Excess errors:
/home/alx/src/gnu/gcc/len/gcc/testsuite/gcc.dg/lengthof-compile.c:22:9: error: ISO C90 does not support flexible array members [-Wpedantic]
/home/alx/src/gnu/gcc/len/gcc/testsuite/gcc.dg/lengthof-compile.c:30:1: error: ISO C90 forbids variable length array 'a' [-Wvla]
/home/alx/src/gnu/gcc/len/gcc/testsuite/gcc.dg/lengthof-compile.c:31:33: error: ISO C90 does not support '[*]' array declarators [-Wpedantic]

> 
> There are also *.sum files which you can diff against a build
> without your patch to see whether there are any regressions.

Good.  I'll check.

> > > > -  I don't like the fact that [*][n] is internally implemented exactly
> > > >    like [0][n], which makes them indistinguishable.  All other cases of
> > > >    [0] return a constent expression of value 0, but [0][n] must return a
> > > >    variable 0, to keep support for [*][n].
> > > >    Could you please change the way [*][n] (and thus [*]) is represented
> > > >    internally so that it can be differentiated from [0]?
> > > >    Do you have in mind any other way that would be a viable
> > > >    implementation of [*] that would allow distinguishing [0][n] and
> > > >    [*][n]?  Maybe making it to have one node instead of zero and mark
> > > >    that node specially?
> > > 
> > > The C++ frontend encodes zero-sized arrays using a range of [0,-1]. 
> > > I have a half-finished patch which implements this for the C FE.
> > 
> > Thanks!  I guess your patch will be merged before mine, so please ping
> > me when that happens so I update mine for it.
> 
> Not sure about this...

:)

> > 
> > BTW, do you allow me to use Co-developed-by: you?
> 
> ok,

Thanks!

Cheers,
Alex

> Martin
> > 
> > Have a lovely day!
> > Alex
Qing Zhao Aug. 6, 2024, 3:59 p.m. UTC | #6
Hi, Alex,
I noticed that all your 4 versions of the patches and the corresponding discussion are all in the same email thread, it’s very inconvenient to read. Can you start a new email thread for each of the new version of the patch? (i.e, Please not reply to the previous version when you have a new version of the patch).
Some more questions and comments below:

On Aug 6, 2024, at 08:22, Alejandro Colomar <alx@kernel.org> wrote:

Hi!

v4:

-  Only evaluate the operand if the top array is VLA.  Inner VLAs are
  ignored.  [Joseph, Martin]
  This proved very useful for compile-time diagnostics, since we have
  more cases that are constant expressions.
-  Document the evaluation rules, which are unique to this operator
  (similar to sizeof, but we ignore inner VLAs).
-  Add tests to the testsuite.  [Joseph]
-  Swap diagnostic cases preference, to give more meaningful
  diagnostics.  [Martin]
-  Document that Xavier was the first one to suggest this feature, and
  provide a link to the mail thread where that happened.
  BTW, while reading that discussion from 2 years ago, I see that it
  was questioned the value of this operator.  Below is a rationale to
  defend it.
I briefly read the two links you provided as the background of your patch:

https://www.open-std.org/jtc1/sc22/wg14/www/docs/n2529.pdf

This is a proposal submitted on 6/4/2020, do you know the current status of this proposal?

https://inbox.sourceware.org/gcc/M8S4oQy--3-2@tutanota.com/T/

This is some discussion within GCC community on this proposal around the same time (the end of May of 2020, before the submission date of the proposal).

From the discussion, I didn’t see a consistent positive opinion on the proposal itself.

So, I am wondering whether you have any new background information since then? What’s the major motivation to bring up this proposal again this time after 4 years?

-  Document that Martin's help has been crucial for implementing this,
  with 'Co-developed-by'.  Would you mind confirming that I can use
  that tag?
-  CC += Kees, Qing, Jens

Rationale:

-  While compiler extensions already allow implementing ARRAY_SIZE()
  (<https://stackoverflow.com/a/57537491/6872717>), there's still no
  way to get the length of a function parameter which uses array
  notation.


Is this one of major benefits from this new __lenghth__ operator?
If so, any rough idea now on how to implement this (i.e, the length of a function parameter array).


 While this first implementation doesn't support those yet
  (because there are some issues that need to be fixed first), the plan
  is to add support to those.


What kind of issues are? What’s the plan to resolve those issues?


 This would be a huge step towards arrays
  being first-class citizens in C.  In those cases, it would reduce the
  chance of programmer errors.  See for example
  <https://lkml.org/lkml/2015/9/3/428>.  That entire class of bugs
  would be over, _and_ programs would become simpler.

Some specific questions or concerns:

-  The tests seem to work as expected if I compile them manually, and
  run (the one that should be run) as a normal program.  The one that
  should not be run also gives the expected diagnostics.
  Can anyone give advice of why it's not running well under the test
  suite?

You might want to check some existing testing cases in GCC’s testsuite first to see what kind of directives you are missing in your test case. (For example, any testing case in gcc/testsuite/gcc.dg/).

The documentation of the test suite is here:
https://gcc.gnu.org/onlinedocs/gccint/Testsuites.html

Adding testing case correctly into GCC’s testing suite is very important for any patch. And adding them in the beginning of the development also is very important and will save you a lot of time.

Qing


-  I don't like the fact that [*][n] is internally implemented exactly
  like [0][n], which makes them indistinguishable.  All other cases of
  [0] return a constent expression of value 0, but [0][n] must return a
  variable 0, to keep support for [*][n].
  Could you please change the way [*][n] (and thus [*]) is represented
  internally so that it can be differentiated from [0]?
  Do you have in mind any other way that would be a viable
  implementation of [*] that would allow distinguishing [0][n] and
  [*][n]?  Maybe making it to have one node instead of zero and mark
  that node specially?

At the bottom of this email is a range-diff against v3.

And below is a test program I used while developing the feature.  It is
quite similar to what's on the test suite (patch 4/4), since those are
based on this one.

It has comments where I'd like more diagnostics, but those are not
responsibility of this feature.  Some are fault of the representation
for [*], and others are already being worked on by Martin.  There are
also comments on code that causes compile-time errors as expected
(wanted).  Some assertions about evaluation of the operand are commented
out because due to the problems with [*][n] and [0][n] we have more
evaluation than I'd like.  However, those are only with [0], which is
not yet well supported by GCC, so we don't need to worry much for now.

The program below also compares with sizeof and alignof, which the
test-suite tests do not.

Have a lovely day!
Alex

$ cat len.c
#include <stdalign.h>
#include <stdio.h>
#include <assert.h>


#define memberof(T, member)                                           \
(                                                                     \
(T){}.member                                                  \
)


struct s {
int x;
int y[8];
int z[];
};


struct s2 {
int x;
int z[] __attribute__((counted_by(x)));
};


extern int x[];


void array(void);
void incomplete_err(int inc[]);
void unspecified_err(void);
void vla(void);
void member_array(void);
void fam_err(void);
void vla_eval(void);
void in_vla_noeval(void);
void in_vla_noeval2(void);
void array_noeval(void);
void vla_eval2(void);
void matrix_0(void);
void matrix_fixed(void);
void matrix_vla(void);
void f_fixed(void);
void f_zero(void);
void f_vla(void);
void f_star(void);


int
main(int argc, char *argv[argc + 1])
{
(void) argv;

// Wishlist:
//n = lengthof(argv);
//printf("lengthof(argv) == %zu\n", n);

array();
incomplete_err(&argc);
unspecified_err();
vla();
member_array();
fam_err();
vla_eval();
in_vla_noeval();
in_vla_noeval2();
array_noeval();
vla_eval2();
matrix_0();
matrix_fixed();
matrix_vla();
f_fixed();
f_zero();
f_vla();
f_star();
}

void
array(void)
{
short   a[42];
size_t  n;

puts("");

n = __lengthof__(a);
printf("lengthof(a):\t\t %zu\n", n);
assert(n == 42);

n = __lengthof__(long [0]);
printf("lengthof(long [0]):\t %zu\n", n);
assert(n == 0);

n = __lengthof__(long [99]);
printf("lengthof(long [99]):\t %zu\n", n);
assert(n == 99);
}

void
incomplete_err(int inc[])
{
//size_t  n;

puts("");

// error: invalid application of ‘lengthof’ to incomplete type ‘int[]’
//n = lengthof(x);
//printf("lengthof(x):\t %zu\n", n);

// error:
//n = lengthof(inc);
//printf("lengthof(inc):\t %zu\n", n);
}

void
unspecified_err(void)
{
puts("");

// error:
//n = lengthof(int [*]);
//printf("lengthof(int [*])\t %zu\n", n);
}

void
vla(void)
{
size_t  n;

n = 99;
puts("");

n = __lengthof__(short [n - 10]);
printf("lengthof(short [n - 10]):\t %zu\n", n);
assert(n == 89);

int  v[n / 2];
n = __lengthof__(v);
printf("lengthof(v):\t %zu\n", n);
assert(n == 89 / 2);

n = 0;
int  z[n];
n = __lengthof__(z);
printf("lengthof(z):\t %zu\n", n);
assert(n == 0);
}

void
member_array(void)
{
size_t  n;

puts("");

n = __lengthof__(memberof(struct s, y));
printf("lengthof(memberof(struct s, y)):\t %zu\n", n);
assert(n == 8);
}

void
fam_err(void)
{
size_t  n;

puts("");

// error:
//n = lengthof(memberof(struct s, z));
//printf("lengthof(memberof(struct s, z)):\t %zu\n", n);

// error:
//n = sizeof(memberof(struct s, z));
//printf("sizeof(memberof(struct s, z)):\t %zu\n", n);

n = alignof(memberof(struct s, z));
printf("alignof(memberof(struct s, z)):\t %zu\n", n);
}

void
vla_eval(void)
{
int     i;
size_t  n;

puts("");

i = 4;
n = __lengthof__(struct {int x;}[i++]);
printf("lengthof(struct {int x;}[i++]):\t %zu;  i: %d\n", n, i);
assert(i == 5);
assert(n == 4);

i = 4;
n = sizeof(struct {int x;}[i++]);
printf("sizeof(struct {int x;}[i++]):\t %zu; i: %d\n", n, i);
assert(i == 5);

i = 4;
n = alignof(struct {int x;}[i++]);
printf("alignof(struct {int x;}[i++]):\t %zu;  i: %d\n", n, i);
assert(i == 4);
}

void
in_vla_noeval(void)
{
int     i;
size_t  n;

puts("");

i = 4;
n = __lengthof__(struct {int x[i++];}[3]);
printf("lengthof(struct {int x[i++];}[3]):\t %zu;  i: %d\n", n, i);
assert(i == 4);
assert(n == 3);

i = 4;
n = sizeof(struct {int x[i++];}[3]);
printf("sizeof(struct {int x[i++];}[3]):\t %zu; i: %d\n", n, i);
assert(i == 5);

i = 4;
n = alignof(struct {int x[i++];}[3]);
printf("alignof(struct {int x[i++];}[3]):\t %zu;  i: %d\n", n, i);
assert(i == 4);
}

void
in_vla_noeval2(void)
{
int     i;
size_t  n;

puts("");

i = 4;
n = __lengthof__(struct {int x[(i++, 2)];}[3]);
printf("lengthof(struct {int x[(i++, 2)];}[3]):\t %zu;  i: %d\n", n, i);
assert(i == 4);
assert(n == 3);

i = 4;
n = sizeof(struct {int x[(i++, 2)];}[3]);
printf("sizeof(struct {int x[(i++, 2)];}[3]):\t %zu; i: %d\n", n, i);
assert(i == 5);

i = 4;
n = alignof(struct {int x[(i++, 2)];}[3]);
printf("alignof(struct {int x[(i++, 2)];}[3]):\t %zu;  i: %d\n", n, i);
assert(i == 4);
}

void
array_noeval(void)
{
short   a[42];
short   (*p)[42];
size_t  n;

puts("");

p = &a;
n = __lengthof__(*p++);
printf("lengthof(*p++):\t %zu; p: %p\n", n, p);
assert(p == &a);
assert(n == 42);

p = &a;
n = sizeof(*p++);
printf("lengthof(*p++):\t %zu; p: %p\n", n, p);
assert(p == &a);

p = &a;
n = alignof(*p++);
printf("alignof(*p++):\t %zu;  p: %p\n", n, p);
assert(p == &a);
}

void
vla_eval2(void)
{
size_t  n;

n = 33;

int  v[n / 2];
int  (*q)[__lengthof__(v)];

puts("");

q = &v;
n = __lengthof__(*q++);
printf("lengthof(*q++):\t %zu; q: %p\n", n, q);
assert(q - 1 == &v);
assert(n == 33 / 2);

q = &v;
n = sizeof(*q++);
printf("lengthof(*q++):\t %zu; q: %p\n", n, q);
assert(q - 1 == &v);

q = &v;
n = alignof(*q++);
printf("alignof(*q++):\t %zu;  q: %p\n", n, q);
assert(q == &v);
}

void
matrix_0(void)
{
int     i;
size_t  n;

puts("");

n = __lengthof__(int [0][4]);
printf("lengthof(int [0][4]):\t %zu\n", n);
assert(n == 0);

i = 5;
n = __lengthof__(int [0][i++]);
printf("lengthof(int [0][i++]):\t %zu;  i: %d\n", n, i);
//assert(i == 5);
assert(n == 0);

// error: ‘[*]’ not allowed in other than function prototype scope
//n = lengthof(int [0][*]);
//printf("lengthof(int [0][*]):\t %zu\n", n);
//assert(n == 0);
}

void
matrix_fixed(void)
{
int     i;
size_t  n;


n = __lengthof__(int [7][4]);
printf("lengthof(int [7][4]):\t %zu\n", n);
assert(n == 7);

i = 5;
n = __lengthof__(int [7][i++]);
printf("lengthof(int [7][i++]):\t %zu;  i: %d\n", n, i);
assert(i == 5);
assert(n == 7);

// error: ‘[*]’ not allowed in other than function prototype scope
//n = lengthof(int [7][*]);
//printf("lengthof(int [7][*]):\t %zu\n", n);
//assert(n == 7);
}

void
matrix_vla(void)
{
int     i;
size_t  n;


i = 7;
n = __lengthof__(int [i++][4]);
printf("lengthof(int [i++][4]):\t %zu;  i: %d\n", n, i);
assert(i == 8);
assert(n == 7);

n = __lengthof__(int [i++][n]);
printf("lengthof(int [i++][n]):\t %zu;  i: %d\n", n, i);
assert(i == 9);
assert(n == 8);

// error: ‘[*]’ not allowed in other than function prototype scope
//n = lengthof(int [i++][*]);
//printf("lengthof(int [i++][*]):\t %zu;  i: %d\n", n, i);
//assert(i == 10);
//assert(n == 9);
}

void l_fixed_1(int i, char (*a)[3][5], int (*x)[__lengthof__(*a)]);
void l_fixed_2(int i, char (*a)[3][i], int (*x)[__lengthof__(*a)]);
void l_fixed_3(int i, char (*a)[3][*], int (*x)[__lengthof__(*a)]);

void s_fixed_1(int i, char (*a)[5][3], int (*x)[sizeof(**a)]);
void s_fixed_2(int i, char (*a)[i][3], int (*x)[sizeof(**a)]);
void s_fixed_3(int i, char (*a)[*][3], int (*x)[sizeof(**a)]);

void
f_fixed(void)
{
int  i3[3];
int  i5[5];
char c35[3][5];
char c53[5][3];

sizeof(l_fixed_1(5, &c35, &i3));
//sizeof(l_fixed_1(5, &c35, &i5));  // -Wincompatible-pointer-types

sizeof(l_fixed_2(5, &c35, &i3));
//sizeof(l_fixed_2(5, &c35, &i5));  // -Wincompatible-pointer-types

sizeof(l_fixed_3(5, &c35, &i3));
//sizeof(l_fixed_3(5, &c35, &i5));  // -Wincompatible-pointer-types

sizeof(s_fixed_1(5, &c53, &i3));
//sizeof(s_fixed_1(5, &c53, &i5));  // -Wincompatible-pointer-types

sizeof(s_fixed_2(5, &c53, &i3));
//sizeof(s_fixed_2(5, &c53, &i5));  // -Wincompatible-pointer-types

sizeof(s_fixed_3(5, &c53, &i3));
//sizeof(s_fixed_3(5, &c53, &i5));  // -Wincompatible-pointer-types
}

void l_zero_1(int i, char (*a)[0][5], int (*x)[__lengthof__(*a)]);
void l_zero_2(int i, char (*a)[0][i], int (*x)[__lengthof__(*a)]);
void l_zero_3(int i, char (*a)[0][*], int (*x)[__lengthof__(*a)]);

void s_zero_1(int i, char (*a)[5][0], int (*x)[sizeof(**a)]);
void s_zero_2(int i, char (*a)[i][0], int (*x)[sizeof(**a)]);
void s_zero_3(int i, char (*a)[*][0], int (*x)[sizeof(**a)]);

void
f_zero(void)
{
int  i0[0];
int  i5[5];
char c05[0][5];
char c50[5][0];

sizeof(l_zero_1(5, &c05, &i0));
//sizeof(l_zero_1(5, &c05, &i5));  // -Wincompatible-pointer-types

sizeof(l_zero_2(5, &c05, &i0));
sizeof(l_zero_2(5, &c05, &i5));  // Wantfail

sizeof(l_zero_3(5, &c05, &i0));
sizeof(l_zero_3(5, &c05, &i5));  // Wantfail

sizeof(s_zero_1(5, &c50, &i0));
sizeof(s_zero_1(5, &c50, &i5));  // Wantfail

sizeof(s_zero_2(5, &c50, &i0));
sizeof(s_zero_2(5, &c50, &i5));  // Wantfail

sizeof(s_zero_3(5, &c50, &i0));
sizeof(s_zero_3(5, &c50, &i5));  // Wantfail
}

void l_vla_1(int i, int j, char (*a)[i][5], int (*x)[__lengthof__(*a)]);
void l_vla_2(int i, int j, char (*a)[i][j], int (*x)[__lengthof__(*a)]);
void l_vla_3(int i, int j, char (*a)[i][*], int (*x)[__lengthof__(*a)]);

void s_vla_1(int i, int j, char (*a)[5][j], int (*x)[sizeof(**a)]);
void s_vla_2(int i, int j, char (*a)[i][j], int (*x)[sizeof(**a)]);
void s_vla_3(int i, int j, char (*a)[*][j], int (*x)[sizeof(**a)]);

void
f_vla(void)
{
int  i3[3];
int  i5[5];
char c35[3][5];
char c53[5][3];

sizeof(l_vla_1(3, 5, &c35, &i3));
sizeof(l_vla_1(3, 5, &c35, &i5));  // Wantwarn

sizeof(l_vla_2(3, 5, &c35, &i3));
sizeof(l_vla_2(3, 5, &c35, &i5));  // Wantwarn

sizeof(l_vla_3(3, 5, &c35, &i3));
sizeof(l_vla_3(3, 5, &c35, &i5));  // Wantwarn

sizeof(s_vla_1(5, 3, &c53, &i3));
sizeof(s_vla_1(5, 3, &c53, &i5));  // Wantwarn

sizeof(s_vla_2(5, 3, &c53, &i3));
sizeof(s_vla_2(5, 3, &c53, &i5));  // Wantwarn

sizeof(s_vla_3(5, 3, &c53, &i3));
sizeof(s_vla_3(5, 3, &c53, &i5));  // Wantwarn
}

void l_star_1(int i, char (*a)[*][5], int (*x)[__lengthof__(*a)]);
void l_star_2(int i, char (*a)[*][i], int (*x)[__lengthof__(*a)]);
void l_star_3(int i, char (*a)[*][*], int (*x)[__lengthof__(*a)]);

void s_star_1(int i, char (*a)[5][*], int (*x)[sizeof(**a)]);
void s_star_2(int i, char (*a)[i][*], int (*x)[sizeof(**a)]);
void s_star_3(int i, char (*a)[*][*], int (*x)[sizeof(**a)]);

void
f_star(void)
{
int  i3[3];
int  i5[5];
char c35[3][5];
char c53[5][3];

sizeof(l_star_1(5, &c35, &i3));
sizeof(l_star_1(5, &c35, &i5));  // Wantwarn

sizeof(l_star_2(5, &c35, &i3));
sizeof(l_star_2(5, &c35, &i5));  // Wantwarn

sizeof(l_star_3(5, &c35, &i3));
sizeof(l_star_3(5, &c35, &i5));  // Wantwarn

sizeof(s_star_1(5, &c53, &i3));
sizeof(s_star_1(5, &c53, &i5));  // Wantwarn

sizeof(s_star_2(5, &c53, &i3));
sizeof(s_star_2(5, &c53, &i5));  // Wantwarn

sizeof(s_star_3(5, &c53, &i3));
sizeof(s_star_3(5, &c53, &i5));  // Wantwarn
}

And here's how it runs:

$ /opt/local/gnu/gcc/lengthof/bin/gcc len.c
$ ./a.out

lengthof(a): 42
lengthof(long [0]): 0
lengthof(long [99]): 99



lengthof(short [n - 10]): 89
lengthof(v): 44
lengthof(z): 0

lengthof(memberof(struct s, y)): 8

alignof(memberof(struct s, z)): 4

lengthof(struct {int x;}[i++]): 4;  i: 5
sizeof(struct {int x;}[i++]): 16; i: 5
alignof(struct {int x;}[i++]): 4;  i: 4

lengthof(struct {int x[i++];}[3]): 3;  i: 4
sizeof(struct {int x[i++];}[3]): 48; i: 5
alignof(struct {int x[i++];}[3]): 4;  i: 4

lengthof(struct {int x[(i++, 2)];}[3]): 3;  i: 4
sizeof(struct {int x[(i++, 2)];}[3]): 24; i: 5
alignof(struct {int x[(i++, 2)];}[3]): 4;  i: 4

lengthof(*p++): 42; p: 0x7ffd18a52b30
lengthof(*p++): 84; p: 0x7ffd18a52b30
alignof(*p++): 2;  p: 0x7ffd18a52b30

lengthof(*q++): 16; q: 0x7ffd18a52b60
lengthof(*q++): 64; q: 0x7ffd18a52b60
alignof(*q++): 4;  q: 0x7ffd18a52b20

lengthof(int [0][4]): 0
lengthof(int [0][i++]): 0;  i: 6
lengthof(int [7][4]): 7
lengthof(int [7][i++]): 7;  i: 5
lengthof(int [i++][4]): 7;  i: 8
lengthof(int [i++][n]): 8;  i: 9



Alejandro Colomar (4):
 gcc/: Rename array_type_nelts() => array_type_nelts_minus_one()
 Merge definitions of array_type_nelts_top()
 c: Add __lengthof__() operator (n2529)
 testsuite: Add tests for __lengthof__

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                         |  30 +++---
gcc/c/c-fold.cc                         |   7 +-
gcc/c/c-parser.cc                       |  61 +++++++++---
gcc/c/c-tree.h                          |   4 +
gcc/c/c-typeck.cc                       | 114 ++++++++++++++++++++-
gcc/config/aarch64/aarch64.cc           |   2 +-
gcc/config/i386/i386.cc                 |   2 +-
gcc/cp/cp-tree.h                        |   1 -
gcc/cp/decl.cc                          |   2 +-
gcc/cp/init.cc                          |   8 +-
gcc/cp/lambda.cc                        |   3 +-
gcc/cp/operators.def                    |   1 +
gcc/cp/tree.cc                          |  13 ---
gcc/doc/extend.texi                     |  27 +++++
gcc/expr.cc                             |   8 +-
gcc/fortran/trans-array.cc              |   2 +-
gcc/fortran/trans-openmp.cc             |   4 +-
gcc/rust/backend/rust-tree.cc           |  13 ---
gcc/rust/backend/rust-tree.h            |   2 -
gcc/target.h                            |   3 +
gcc/testsuite/gcc.dg/lengthof-compile.c |  48 +++++++++
gcc/testsuite/gcc.dg/lengthof.c         | 126 ++++++++++++++++++++++++
gcc/tree.cc                             |  17 +++-
gcc/tree.h                              |   3 +-
27 files changed, 453 insertions(+), 79 deletions(-)
create mode 100644 gcc/testsuite/gcc.dg/lengthof-compile.c
create mode 100644 gcc/testsuite/gcc.dg/lengthof.c

Range-diff against v3:
1:  73010cb4af6 = 1:  73010cb4af6 gcc/: Rename array_type_nelts() => array_type_nelts_minus_one()
2:  2bb966a0a89 = 2:  2bb966a0a89 Merge definitions of array_type_nelts_top()
3:  d22b5e1c015 ! 3:  e2dbfc43b14 c: Add __lengthof__() operator
   @@ Metadata
    Author: Alejandro Colomar <alx@kernel.org>

     ## Commit message ##
   -    c: Add __lengthof__() operator
   +    c: Add __lengthof__() operator (n2529)

        This operator is similar to sizeof() but can only be applied to an
        array, and returns its length (number of elements).

   -    TO BE DECIDED BEFORE MERGING:
   -
   -            It would be better to not evaluate the operand if the top-level
   -            array is not a VLA.
   -
        FUTURE DIRECTIONS:

                We could make it work with array parameters to functions, and
   @@ Commit message
                regardless of it being really a pointer.

        Link: <https://www.open-std.org/jtc1/sc22/wg14/www/docs/n2529.pdf>
   -    Cc: Xavier Del Campo Romero <xavi.dcr@tutanota.com>
   +    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>
        Cc: Gabriel Ravier <gabravier@gmail.com>
   -    Cc: Martin Uecker <uecker@tugraz.at>
        Cc: Joseph Myers <josmyers@redhat.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>
        Signed-off-by: Alejandro Colomar <alx@kernel.org>

     ## gcc/c-family/c-common.cc ##
   @@ gcc/c-family/c-common.cc: c_alignof_expr (location_t loc, tree expr)
    +  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,
   @@ gcc/c-family/c-common.cc: c_alignof_expr (location_t loc, tree expr)
    + type);
    +      return error_mark_node;
    +    }
   -+  if (type_code != ARRAY_TYPE)
   -+    {
   -+      error_at (loc, "invalid application of %<lengthof%> to type %qT", type);
   -+      return error_mark_node;
   -+    }
    +
    +  return array_type_nelts_top (type);
    +}
   @@ gcc/c/c-typeck.cc: c_expr_sizeof_type (location_t loc, struct c_type_name *t)
       return ret;
     }

   ++static bool
   ++is_top_array_vla (tree type)
   ++{
   ++  bool zero, 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);
   ++  var = (!zero
   ++ && (TREE_CODE (TYPE_MIN_VALUE (d)) != INTEGER_CST
   ++     || TREE_CODE (TYPE_MAX_VALUE (d)) != INTEGER_CST));
   ++  var = var || (zero && C_TYPE_VARIABLE_SIZE (type));
   ++  return var;
   ++}
   ++
    +/* Return the result of lengthof applied to EXPR.  */
    +
    +struct c_expr
   @@ gcc/c/c-typeck.cc: c_expr_sizeof_type (location_t loc, struct c_type_name *t)
    +      ret.original_code = LENGTHOF_EXPR;
    +      ret.original_type = NULL;
    +      ret.m_decimal = 0;
   -+      if (C_TYPE_VARIABLE_SIZE (TREE_TYPE (folded_expr)))
   ++      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),
   @@ gcc/c/c-typeck.cc: c_expr_sizeof_type (location_t loc, struct c_type_name *t)
    +  C_MAYBE_CONST_EXPR_NON_CONST (ret.value) = !expr_const_operands;
    +  SET_EXPR_LOCATION (ret.value, loc);
    + }
   -+      pop_maybe_used (C_TYPE_VARIABLE_SIZE (TREE_TYPE (folded_expr)));
   ++      pop_maybe_used (is_top_array_vla (TREE_TYPE (folded_expr)));
    +    }
    +  return ret;
    +}
   @@ gcc/c/c-typeck.cc: c_expr_sizeof_type (location_t loc, struct c_type_name *t)
    +    }
    +  else
    +  if ((type_expr || TREE_CODE (ret.value) == INTEGER_CST)
   -+      && C_TYPE_VARIABLE_SIZE (type))
   ++      && 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
   @@ gcc/c/c-typeck.cc: c_expr_sizeof_type (location_t loc, struct c_type_name *t)
    +  type_expr, ret.value);
    +      C_MAYBE_CONST_EXPR_NON_CONST (ret.value) = !type_expr_const;
    +    }
   -+  pop_maybe_used (type != error_mark_node
   -+  ? C_TYPE_VARIABLE_SIZE (type) : false);
   ++  pop_maybe_used (type != error_mark_node ? is_top_array_vla (type) : false);
    +  return ret;
    +}
    +
   @@ gcc/doc/extend.texi: If the operand of the @code{__alignof__} expression is a fu

    +@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 just like @code{sizeof},
   -+and the operand is evaluated following the same rules.
   -+(TODO: We probably want to restrict evaluation to top-level VLAs only.
   -+       This documentation describes the current implementation.)
   ++Its syntax is just like @code{sizeof}.
   ++The operand must be a complete array type.
   ++The operand is not evaluated
   ++if the top-level length designator is an integer constant expression;
   ++and it is evaluated
   ++if the top-level length designator is not an integer constant expression.
   ++
   ++XXX: Do we want to document the following?  I think so.
   ++XXX: It would prevent users from relying on __lengthof__
   ++XXX: for distinguishing arrays from pointers.
   ++XXX: I don't want users to complain in the future
   ++XXX: if this doesn't report errors on function parameters anymore
   ++XXX: and that breaks their assumptions.
   ++In the future,
   ++it might also accept a function parameter with array notation,
   ++an incomplete array whose length is specified by other means,
   ++such as attributes,
   ++or other similar cases.
    +
     @node Inline
     @section An Inline Function is As Fast As a Macro
-:  ----------- > 4:  9a691f7f208 testsuite: Add tests for __lengthof__
--
2.45.2
Alejandro Colomar Aug. 6, 2024, 4:48 p.m. UTC | #7
[CC += David, Florian, Andreas]

On Tue, Aug 06, 2024 at 03:59:11PM GMT, Qing Zhao wrote:
> Hi, Alex,

Hi Qing,

> I noticed that all your 4 versions of the patches and the
> corresponding discussion are all in the same email thread, it’s very
> inconvenient to read. Can you start a new email thread for each of the
> new version of the patch? (i.e, Please not reply to the previous
> version when you have a new version of the patch).

Hmmm; I have the opposite opinion in projects that I maintain.  I prefer
when successive iterations of the same patch set are replies to the same
thread, which allows to easily go back to the previous iterations.

Is there consensus in gcc-patches@ that I should start new threads for
each revision?

I very much prefer to keep using a single thread to keep my sanity, but
I'll do whatever gcc-patches@ maintainers prefer.

> Some more questions and comments below:

Could you please use quoting character?  I find it hard to distinguish
the quoted parts from your own.

> I briefly read the two links you provided as the background of your patch:
> 
> https://www.open-std.org/jtc1/sc22/wg14/www/docs/n2529.pdf
> 
> This is a proposal submitted on 6/4/2020, do you know the current
> status of this proposal?

I CCed the author of that proposal, but he didn't say anything.  The
proposal probably died.

> 
> https://inbox.sourceware.org/gcc/M8S4oQy--3-2@tutanota.com/T/
> 
> This is some discussion within GCC community on this proposal around
> the same time (the end of May of 2020, before the submission date of
> the proposal).
> 
> From the discussion, I didn’t see a consistent positive opinion on the
> proposal itself.

I've read it.  The feedback was basically that _Lengthof() would be
redundant with ARRAY_SIZE() for those careful enough to use it, and a
dead language feature for the cowboys that don't like seat belts.

However, it didn't take into account the possibility of including array
length information in function parameters declared with array notation,
which is a net improvement for everyone.

I've CCed David (the author of 2020's negative feedback) in case he has
any comments.

From what I've seen in these 4 revisions, feedback is not bad.  I've
also been discussing several array features lately, and it seems like
the way forward.  Hopefully, the general opinion has changed.

BTW, the linux kernel is starting to use macros that magically get the
array length:
<https://lore.kernel.org/all/CAHk-=wgXYkMueFpxgSY_vfCzdcCnyoaPcjS8e0BXiRfgceRHfQ@mail.gmail.com/>
This is also what shadow utils is doing (done by me).  By having
__lengthof__ work on function parameters, these macros will be usable in
more places.

> So, I am wondering whether you have any new background information
> since then? What’s the major motivation to bring up this proposal
> again this time after 4 years?

When I proposed this a couple of years ago (before knowing about n2539),
there was some positive feedback.  I didn't have the time back then to
implement it, but I have now.  So far, I've only seen positive feedback
about it.

> Rationale:
> 
> -  While compiler extensions already allow implementing ARRAY_SIZE()
>   (<https://stackoverflow.com/a/57537491/6872717>), there's still no
>   way to get the length of a function parameter which uses array
>   notation.
> 
> 
> Is this one of major benefits from this new __lenghth__ operator?

I'd say the main one, yes.  As in, I think C++ might have not been
invented or not have developed their own arrays if we had this
functionality in C back then.

> If so, any rough idea now on how to implement this (i.e, the length of
> a function parameter array).

By implementing the function parameters as actual arrays inside the
compiler instead of just pointers.

<https://www.open-std.org/jtc1/sc22/wg14/www/docs/n2906.pdf>

Martin is working on several array features at the moment.

>  While this first implementation doesn't support those yet
>   (because there are some issues that need to be fixed first), the plan
>   is to add support to those.
> 
> 
> What kind of issues are? What’s the plan to resolve those issues?

n2906.  When n2906 is implemented by Martin, __lengthof__ will be able
to work on function parameters.  He may be able to talk more about it.

> -  The tests seem to work as expected if I compile them manually, and
>   run (the one that should be run) as a normal program.  The one that
>   should not be run also gives the expected diagnostics.
>   Can anyone give advice of why it's not running well under the test
>   suite?
> 
> You might want to check some existing testing cases in GCC’s testsuite
> first to see what kind of directives you are missing in your test
> case. (For example, any testing case in gcc/testsuite/gcc.dg/).

It was some spurious warnings.  Martin helped me with those, and it's
already solved in my working copy.

Have a lovely day!
Alex
Joseph Myers Aug. 6, 2024, 5:38 p.m. UTC | #8
On Tue, 6 Aug 2024, Alejandro Colomar wrote:

> -  The tests seem to work as expected if I compile them manually, and
>    run (the one that should be run) as a normal program.  The one that
>    should not be run also gives the expected diagnostics.
>    Can anyone give advice of why it's not running well under the test
>    suite?

You almost certainly want to specify dg-options in the tests rather than 
using the default "-ansi -pedantic" for gcc.dg.

Next question for the specification, implementation and tests: how does 
this feature interact with the rules on external definitions (the contexts 
in which it's OK to refer to an identifier with internal or external 
linkage that's never defined - for example, a function, static or extern, 
with a declaration but no definition; see 6.9.1)?  My expectation would be 
that the rules are analogous to those for sizeof and typeof: in the cases 
where this operator does not evaluate its operand, it's also OK to 
reference an identifier that's declared but not defined, but in the cases 
where the operand is evaluated, that's not OK.  (See gcc.dg/c99-static-1.c 
for example tests of these rules for sizeof.)
Alejandro Colomar Aug. 6, 2024, 8:22 p.m. UTC | #9
Hi Joseph!

On Tue, Aug 06, 2024 at 05:38:50PM GMT, Joseph Myers wrote:
> On Tue, 6 Aug 2024, Alejandro Colomar wrote:
> 
> > -  The tests seem to work as expected if I compile them manually, and
> >    run (the one that should be run) as a normal program.  The one that
> >    should not be run also gives the expected diagnostics.
> >    Can anyone give advice of why it's not running well under the test
> >    suite?
> 
> You almost certainly want to specify dg-options in the tests rather than 
> using the default "-ansi -pedantic" for gcc.dg.

That was the solution; yup.  :)

> Next question for the specification, implementation and tests: how does 
> this feature interact with the rules on external definitions (the contexts 
> in which it's OK to refer to an identifier with internal or external 
> linkage that's never defined - for example, a function, static or extern, 
> with a declaration but no definition; see 6.9.1)?

Functions are not array types, so they don't apply.  It's a constraint
violation --mandatory error-- to call __lengthof__(func).

VLAs cannot have static storage duration or file scope.  Any array that
has static storage duration or extern linkage must be a normal array.
Since normal arrays operands are not evaluated, __lengthof__ returns a
constant expression, and there's no issue at all.

I can't think of any case where we'd have problems; do you have any
other example?

>  My expectation would be 
> that the rules are analogous to those for sizeof and typeof: in the cases 
> where this operator does not evaluate its operand, it's also OK to 
> reference an identifier that's declared but not defined, but in the cases 
> where the operand is evaluated, that's not OK.  (See gcc.dg/c99-static-1.c 
> for example tests of these rules for sizeof.)

-  Non-arrays are not valid operands to __lengthof__, of course.
-  We only evaluate the operand if the top-level array is a VLA, which
   cannot happen at file scope, nor with objects with static storage
   duration, nor with objects with external linkage.
-  So, everything is a constexpr in these cases, which is fine.

Thanks for being so pedantic!  It helps a lot.  :-)

Have a lovely night!
Alex
Joseph Myers Aug. 6, 2024, 8:50 p.m. UTC | #10
On Tue, 6 Aug 2024, Alejandro Colomar wrote:

> > Next question for the specification, implementation and tests: how does 
> > this feature interact with the rules on external definitions (the contexts 
> > in which it's OK to refer to an identifier with internal or external 
> > linkage that's never defined - for example, a function, static or extern, 
> > with a declaration but no definition; see 6.9.1)?
> 
> Functions are not array types, so they don't apply.  It's a constraint
> violation --mandatory error-- to call __lengthof__(func).
> 
> VLAs cannot have static storage duration or file scope.  Any array that
> has static storage duration or extern linkage must be a normal array.
> Since normal arrays operands are not evaluated, __lengthof__ returns a
> constant expression, and there's no issue at all.
> 
> I can't think of any case where we'd have problems; do you have any
> other example?

static int f(), f2();
int a[10][10];
int x;

void
g()
{
  __lengthof__ (a[f()]); // Should be valid that f is not defined.
  int b[x][x];
  __lengthof__ (b[f2()]); // Should be invalid that f2 is not defined.
}
Alejandro Colomar Aug. 6, 2024, 9:09 p.m. UTC | #11
Hi Joseph,

On Tue, Aug 06, 2024 at 08:50:19PM GMT, Joseph Myers wrote:
> static int f(), f2();
> int a[10][10];
> int x;
> 
> void
> g()
> {
>   __lengthof__ (a[f()]); // Should be valid that f is not defined.
>   int b[x][x];
>   __lengthof__ (b[f2()]); // Should be invalid that f2 is not defined.
> }

Thanks!  That makes sense.  It works as you expect, I think.

	alx@debian:~/tmp/gcc$ cat ext.c 
	static int f(), f2();
	int a[10][10];
	int x;

	int
	main(void)
	{
		int b[x][x];

		__lengthof__(a[f()]);
		__lengthof__(b[f2()]);
	}
	alx@debian:~/tmp/gcc$ /opt/local/gnu/gcc/lengthof/bin/gcc ext.c 
	ext.c:1:17: warning: ‘f2’ used but never defined
	    1 | static int f(), f2();
	      |                 ^~
	/usr/bin/ld: /tmp/ccqlTYWo.o: in function `main':
	ext.c:(.text+0xe0): undefined reference to `f2'
	collect2: error: ld returned 1 exit status

Cheers,
Alex
Alejandro Colomar Aug. 6, 2024, 9:46 p.m. UTC | #12
Hi Martin,

On Tue, Aug 06, 2024 at 04:43:27PM GMT, Martin Uecker wrote:
> There are also *.sum files which you can diff against a build
> without your patch to see whether there are any regressions.

In my working copy, it all looks good.  Thanks!  I'll mention that in v5.

Have a lovely night!
Alex


alx@debian:~/src/gnu/gcc$ find len0 -type f
len0/host-x86_64-pc-linux-gnu/gcc/testsuite/gcc/gcc.sum
len0/host-x86_64-pc-linux-gnu/gcc/testsuite/gfortran/gfortran.sum
len0/host-x86_64-pc-linux-gnu/gcc/testsuite/objc/objc.sum
len0/host-x86_64-pc-linux-gnu/gcc/testsuite/g++/g++.sum
len0/x86_64-pc-linux-gnu/libitm/testsuite/libitm.sum
len0/x86_64-pc-linux-gnu/libgomp/testsuite/libgomp.sum
len0/x86_64-pc-linux-gnu/libatomic/testsuite/libatomic.sum
len0/x86_64-pc-linux-gnu/libstdc++-v3/testsuite/libstdc++.sum
alx@debian:~/src/gnu/gcc$ find len1 -type f
len1/host-x86_64-pc-linux-gnu/gcc/testsuite/gcc/gcc.sum
len1/host-x86_64-pc-linux-gnu/gcc/testsuite/gfortran/gfortran.sum
len1/host-x86_64-pc-linux-gnu/gcc/testsuite/objc/objc.sum
len1/host-x86_64-pc-linux-gnu/gcc/testsuite/g++/g++.sum
len1/x86_64-pc-linux-gnu/libitm/testsuite/libitm.sum
len1/x86_64-pc-linux-gnu/libgomp/testsuite/libgomp.sum
len1/x86_64-pc-linux-gnu/libatomic/testsuite/libatomic.sum
len1/x86_64-pc-linux-gnu/libstdc++-v3/testsuite/libstdc++.sum
alx@debian:~/src/gnu/gcc$ cat <(cd len0; find -type f) \
			| while read f; do
				diff -u "len0/$f" "len1/$f";
			done;
--- len0/./host-x86_64-pc-linux-gnu/gcc/testsuite/gcc/gcc.sum	2024-08-06 22:22:44.514175252 +0200
+++ len1/./host-x86_64-pc-linux-gnu/gcc/testsuite/gcc/gcc.sum	2024-08-06 23:29:53.693730123 +0200
@@ -1,4 +1,4 @@
-Test run by alx on Tue Aug  6 19:28:53 2024
+Test run by alx on Tue Aug  6 22:49:12 2024
 Native configuration is x86_64-pc-linux-gnu
 
 		=== gcc tests ===
@@ -86504,6 +86504,15 @@
 PASS: gcc.dg/large-size-array.c  (test for errors, line 19)
 PASS: gcc.dg/large-size-array.c (test for excess errors)
 UNSUPPORTED: gcc.dg/lazy-ptr-test.c
+PASS: gcc.dg/lengthof-compile.c  (test for errors, line 11)
+PASS: gcc.dg/lengthof-compile.c  (test for errors, line 15)
+PASS: gcc.dg/lengthof-compile.c  (test for errors, line 27)
+PASS: gcc.dg/lengthof-compile.c  (test for errors, line 42)
+PASS: gcc.dg/lengthof-compile.c  (test for errors, line 45)
+PASS: gcc.dg/lengthof-compile.c  (test for errors, line 48)
+PASS: gcc.dg/lengthof-compile.c (test for excess errors)
+PASS: gcc.dg/lengthof.c (test for excess errors)
+PASS: gcc.dg/lengthof.c execution test
 PASS: gcc.dg/limits-width-1.c (test for excess errors)
 PASS: gcc.dg/limits-width-2.c (test for excess errors)
 PASS: gcc.dg/live-patching-1.c (test for excess errors)
@@ -204639,7 +204648,7 @@
 
 		=== gcc Summary ===
 
-# of expected passes		199780
+# of expected passes		199789
 # of unexpected failures	31
 # of unexpected successes	2
 # of expected failures		1463
--- len0/./host-x86_64-pc-linux-gnu/gcc/testsuite/gfortran/gfortran.sum	2024-08-06 22:22:44.546175561 +0200
+++ len1/./host-x86_64-pc-linux-gnu/gcc/testsuite/gfortran/gfortran.sum	2024-08-06 23:29:53.877731933 +0200
@@ -1,4 +1,4 @@
-Test run by alx on Tue Aug  6 19:28:53 2024
+Test run by alx on Tue Aug  6 22:49:12 2024
 Native configuration is x86_64-pc-linux-gnu
 
 		=== gfortran tests ===
--- len0/./host-x86_64-pc-linux-gnu/gcc/testsuite/objc/objc.sum	2024-08-06 22:22:44.462174752 +0200
+++ len1/./host-x86_64-pc-linux-gnu/gcc/testsuite/objc/objc.sum	2024-08-06 23:29:53.845731618 +0200
@@ -1,4 +1,4 @@
-Test run by alx on Tue Aug  6 19:28:53 2024
+Test run by alx on Tue Aug  6 22:49:12 2024
 Native configuration is x86_64-pc-linux-gnu
 
 		=== objc tests ===
--- len0/./host-x86_64-pc-linux-gnu/gcc/testsuite/g++/g++.sum	2024-08-06 22:22:44.622176292 +0200
+++ len1/./host-x86_64-pc-linux-gnu/gcc/testsuite/g++/g++.sum	2024-08-06 23:29:53.781730989 +0200
@@ -1,4 +1,4 @@
-Test run by alx on Tue Aug  6 19:28:53 2024
+Test run by alx on Tue Aug  6 22:49:12 2024
 Native configuration is x86_64-pc-linux-gnu
 
 		=== g++ tests ===
--- len0/./x86_64-pc-linux-gnu/libitm/testsuite/libitm.sum	2024-08-06 22:22:46.206191541 +0200
+++ len1/./x86_64-pc-linux-gnu/libitm/testsuite/libitm.sum	2024-08-06 23:29:55.573748613 +0200
@@ -1,4 +1,4 @@
-Test run by alx on Tue Aug  6 19:28:53 2024
+Test run by alx on Tue Aug  6 22:49:12 2024
 Native configuration is x86_64-pc-linux-gnu
 
 		=== libitm tests ===
--- len0/./x86_64-pc-linux-gnu/libgomp/testsuite/libgomp.sum	2024-08-06 22:22:45.694186612 +0200
+++ len1/./x86_64-pc-linux-gnu/libgomp/testsuite/libgomp.sum	2024-08-06 23:29:54.717740194 +0200
@@ -1,4 +1,4 @@
-Test run by alx on Tue Aug  6 19:28:53 2024
+Test run by alx on Tue Aug  6 22:49:12 2024
 Native configuration is x86_64-pc-linux-gnu
 
 		=== libgomp tests ===
--- len0/./x86_64-pc-linux-gnu/libatomic/testsuite/libatomic.sum	2024-08-06 22:22:46.110190617 +0200
+++ len1/./x86_64-pc-linux-gnu/libatomic/testsuite/libatomic.sum	2024-08-06 23:29:55.393746843 +0200
@@ -1,4 +1,4 @@
-Test run by alx on Tue Aug  6 19:28:53 2024
+Test run by alx on Tue Aug  6 22:49:12 2024
 Native configuration is x86_64-pc-linux-gnu
 
 		=== libatomic tests ===
--- len0/./x86_64-pc-linux-gnu/libstdc++-v3/testsuite/libstdc++.sum	2024-08-06 22:22:45.998189539 +0200
+++ len1/./x86_64-pc-linux-gnu/libstdc++-v3/testsuite/libstdc++.sum	2024-08-06 23:29:55.137744325 +0200
@@ -1,4 +1,4 @@
-Test run by alx on Tue Aug  6 19:28:54 2024
+Test run by alx on Tue Aug  6 22:49:13 2024
 Native configuration is x86_64-pc-linux-gnu
 
 		=== libstdc++ tests ===