Message ID | 20240806122218.3827577-1-alx@kernel.org |
---|---|
Headers | show |
Series | c: Add __lengthof__ operator | expand |
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 >
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
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
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 >
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
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
[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
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.)
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
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. }
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
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 ===
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__