diff mbox

[RFC] Add __array_size keyword

Message ID 20160211200534.GA545@zubnet.me.uk
State New
Headers show

Commit Message

Stuart Brady Feb. 11, 2016, 8:05 p.m. UTC
This patch adds an __array_size keyword for the C, C++, Objective C and
Objective C++ languages which is similar to the sizeof keyword, but
yields the size of the specified array in elements, not bytes, and will
not accept expressions of pointer type.

At the moment, I am only looking for feedback, as I appreciate that this
would have to wait for GCC 7 at the earliest, if it is deemed desirable.

I have searched and been unable to find any previous submissions similar
to this, or previous discussion of the idea.  I have discussed this with
a number of C developers and generally received positive encouragement
for submitting this patch, so it seems odd to me that this is the case,
so perhaps I have simply missed prior submissions, somehow?

The intent is that one can (perhaps conditionally) use a new definition
for the ARRAY_SIZE macro that is commonly used:

   #ifdef FOO
      #define ARRAY_SIZE(arr) __array_size(arr)
   #else
      #define ARRAY_SIZE(arr) (sizeof(arr) / sizeof(*arr))
   #endif

The advantage to using this macro is that it will catch situations where
we attempt to take the array size of a pointer.  The error message that I
have added is as follows:

   arraysize-test.c: In function ‘foo’:
   arraysize-test.c:4:55: error: invalid application of ‘__array_size’ to non-array type
     printf("%i\n", __array_size(arr));
                                ^

There may well be a better wording for this message and I would welcome
suggestions.

It should be noted that GCC's __builtin_types_compatible_p builtin allows
similar type checking to be performed with an expression that results in
evaluation of the size of a bitfield of negative width, in the case where
a pointer type is used.

However, the macro or series of macros to do this is not especially
simple, nor is the error message that is generated especially readable,
even when using appropriately named macros for the purpose[0]:

   arraysize-old.c: In function ‘main’:
   arraysize-old.c:12:20: error: negative width in bit-field ‘<anonymous>’
        (sizeof(struct { int:-!!(e)*1234; }))
                       ^
   arraysize-old.c:17:6: note: in expansion of macro ‘BUILD_BUG_ON_ZERO’
         BUILD_BUG_ON_ZERO(SAME_TYPE((a), &(*a)))
         ^~~~~~~~~~~~~~~~~
   arraysize-old.c:30:8: note: in expansion of macro ‘MUST_BE_ARRAY’
         + MUST_BE_ARRAY(a))
           ^~~~~~~~~~~~~
   arraysize-old.c:36:17: note: in expansion of macro ‘ARRAY_SIZE’
     printf("%i\n", ARRAY_SIZE(b));
                    ^~~~~~~~~~

An additional consideration would be whether a feature test macro would
be beneficial, and whether there are any matters of consideration that
might hypothetically be relevant to ISO/IEC JTC 1/SC 22 / WG14 (although
perhaps I may be getting ahead of myself).

I present this change largely as a diagnostics improvement, rather than
as new functionality.

That said, for C++, this does introduce __array_size as a keyword that
must be handled within the name mangling scheme.  For this, I have used
the string "as", but I would appreciate advice on how appropriate this
encoding is.

Documentation and test code are currently absent from the patch, but I
will add these, should this RFC receive sufficient positive feedback to
be worth following up.

I would like advice as to what test cases I should add, i.e. should I
simply duplicate all of the tests for sizeof, or only target likely
trouble spots where __array_size behaves differently?

I appreciate that I will also need a ChangeLog entry, and to do proper
testing and include test results.  If anyone could help guide me through
this process I would be very grateful.  My patch, if deemed desirable, may
be large enough to require copyright assignment, in which case I would
very much appreciate some help in dealing with this.

Comments

Joseph Myers Feb. 11, 2016, 10:41 p.m. UTC | #1
On Thu, 11 Feb 2016, Stuart Brady wrote:

> Documentation and test code are currently absent from the patch, but I

For proposed features, I find documentation and testcases of much more 
value than the rest of the implementation.  Critical issues to define and 
cover thoroughly in tests include the rules for when operands of sizeof 
are evaluated, as adapted appropriately for this keyword, and for when it 
returns various kinds of constants.  Is the rule for your keyword that the 
operand is evaluated, and the result not an integer constant, iff the 
operand is an array with a variable number of elements (as opposed to an 
array with a constant number of elements that themselves are 
variable-sized, for example)?  C11 6.5.3.4#2 (sizeof) would need testing, 
but so would 6.9#3 (rules about when declared identifiers need to be 
defined), and 6.6#6 and #8 (constant expressions) - and anything else in 
C11 that mentions sizeof.  Presumably this keyword can be applied to an 
array at function prototype scope whose size is explicitly or implicitly 
[*], though nothing useful can be done with the results, as with [*]?  
(Cf. gcc.dg/vla-5.c.)
Stuart Brady Feb. 13, 2016, 3:16 a.m. UTC | #2
On Thu, Feb 11, 2016 at 10:41:02PM +0000, Joseph Myers wrote:
> For proposed features, I find documentation and testcases of much more 
> value than the rest of the implementation.

I can see the logic of that.  This is the part that I find a little
tricky, so please bear with me here.

> Critical issues to define and cover thoroughly in tests include the
> rules for when operands of sizeof are evaluated, as adapted
> appropriately for this keyword, and for when it returns various kinds
> of constants.

So in other words, adapting all of the sizeof tests would be appropriate,
and sizeof tests for non-array types would change from expected passes to
expected failures?

> Is the rule for your keyword that the operand is evaluated, and the
> result not an integer constant, iff the operand is an array with a
> variable number of elements (as opposed to an array with a constant
> number of elements that themselves are variable-sized, for example)?

If I've understood correctly, then yes:

   #include <stdio.h>
   void foo(int i) {
     int a[i], b[__array_size(a)];
     printf("%zi, %zi\n", __array_size(a), __array_size(b));
   };
   int main() { foo(42); }

This will print "42, 42".

> C11 6.5.3.4#2 (sizeof) would need testing, 

Does this section differ from the September 7th draft in any way?

Perhaps I should draw up an equivalent specification for __array_size
based on 6.5.3.4, only I'm unsure if it would be legal for me to do that?

> but so would 6.9#3 (rules about when declared identifiers need to be 
> defined), and 6.6#6 and #8 (constant expressions) - and anything else in 
> C11 that mentions sizeof.

Well, there are some mentions of sizeof such as those in connection with
offsetof where I do not think any tests or documentation would be needed
but yes, tests would be needed for equivalent behaviour with __array_size
instead of sizeof, for those sections which you mention.

> Presumably this keyword can be applied to an array at function prototype
> scope whose size is explicitly or implicitly [*], though nothing useful
> can be done with the results, as with [*]? (Cf. gcc.dg/vla-5.c.)

I'm not sure I quite understand the meaning of an implicit [*].  Does that
just mean __array_size(foo) with an int foo[*] as another parameter?

I have realised that my patch has problems in this area, which I will
fix, but it still makes sense to me to document the desired functionality
and test cases, so I shall continue along this path.

As a brief aside, I do get an ICE with the following source, without any
modifications of my own:

   int bar() { return foo(); }
   void baz(int c[foo()]) { return; }

I will look into submitting a PR for this properly soon, but will not
mind if someone wants to take this task upon themselves instead,
especially as we are into the release phase for GCC 6 and this may be
an issue worth fixing.  (Note that Debian's GCC 5.3.1 is also affected.)

For a hypothetical change to the C standard itself, I think one might use
the name "_ArraySize", but for a non-standard extension this would not be
appropriate.  I think "__array_size" is fine, though, and is consistent
with "__alignof".

Here's a list of places in the standard that reference sizeof, and the
changes that would need to be made to the standard if this keyword were
to become part of it (using the "wrong" name of __array_size):

   6.2.6.2 43) no change
   6.3.2.1#2: no change
          #3: extended to include __array_size
          #4: no change
   6.4.1#1: add __array_size to keyword list
   6.5.3#1: add __array_size unary-expression,
            and __array_size ( type-name )
   6.5.3.4: duplicate the entire section, with considerable changes:
          #1: "shall only be applied to an expression that has array
              type or to the parenthesized name of such a type"
          #2: "yields the number of elements in the array"
          #3: remove entirely
          #5: remove entirely, or provide example using calloc()
          #6: remove entirely, also update original?
          #7: update appropriately
          88) remove?
   6.6#6: extend to include __array_size (both occurrences)
          98) update
      #8: extend to include __array_size (both occurrences)
   6.7.1 103) "only operators [...] are sizeof and __array_size"
   6.7.2.1#17: no change
          #18: no change
          #19: no change
          #20: no change
          #21: no change
          #22: no change
   6.7.3.1#12: no change
   6.7.5.2#5: extend to include __array_size
   6.9#3: extend to include __array_size
   6.9#5: extend to include __array_size
   7.12.3.1#4: no change
   7.17#2: extend to include __array_size
   A.1.2: add __array_size to keyword list
   A.2.1: add __array_size unary-expression,
          and __array_size ( type-name )
   J.1: extend to include __array_size
   J.2: extend to include __array_size (all four occurrences)
   J.3.13: no change

That leaves the matter of dealing with this for C++, Objective C and also
Objective C++, but I'd very much prefer to deal with C first, as that
seems the most straightforward, as is fundamental to the others.
Marek Polacek Feb. 15, 2016, 2:05 p.m. UTC | #3
On Sat, Feb 13, 2016 at 03:16:49AM +0000, Stuart Brady wrote:
> As a brief aside, I do get an ICE with the following source, without any
> modifications of my own:
> 
>    int bar() { return foo(); }
>    void baz(int c[foo()]) { return; }
> 
> I will look into submitting a PR for this properly soon, but will not
> mind if someone wants to take this task upon themselves instead,
> especially as we are into the release phase for GCC 6 and this may be
> an issue worth fixing.  (Note that Debian's GCC 5.3.1 is also affected.)

Sure, I've just filed this:
https://gcc.gnu.org/bugzilla/show_bug.cgi?id=69824

> For a hypothetical change to the C standard itself, I think one might use
> the name "_ArraySize", but for a non-standard extension this would not be
> appropriate.  I think "__array_size" is fine, though, and is consistent
> with "__alignof".

Please no CamelCase, that's ugly ;).  I also think that "__array_size" is fine.
(I haven't had time so far to read the patch.)

	Marek
Stuart Brady Feb. 15, 2016, 3:51 p.m. UTC | #4
On Mon, Feb 15, 2016 at 03:05:36PM +0100, Marek Polacek wrote:
> On Sat, Feb 13, 2016 at 03:16:49AM +0000, Stuart Brady wrote:
> > I will look into submitting a PR for this properly soon, but will not
> > mind if someone wants to take this task upon themselves instead,
> > especially as we are into the release phase for GCC 6 and this may be
> > an issue worth fixing.  (Note that Debian's GCC 5.3.1 is also affected.)
> 
> Sure, I've just filed this:
> https://gcc.gnu.org/bugzilla/show_bug.cgi?id=69824

Thank you!

> > For a hypothetical change to the C standard itself, I think one might use
> > the name "_ArraySize", but for a non-standard extension this would not be
> > appropriate.  I think "__array_size" is fine, though, and is consistent
> > with "__alignof".
> 
> Please no CamelCase, that's ugly ;).  I also think that "__array_size" is fine.

Okay.  I was wondering about __arraysize, __array_len, or some such
thing but I suppose one just needs to pick something.  ARRAY_SIZE seems
common as macro name that I find __array_size to be immediately obvious.
Certainly, the name is something that doesn't need to be decided just now
although it's better to consider alternatives sooner rather than later.

Hypothetically, I suppose _Array_Size, _Array_size or _Arraysize would
all be better than the CamelCase verison.


> (I haven't had time so far to read the patch.)

Jospeh's point that documentation and testcases are of much more value is
good one, and reveals a problem with my patch.  It does not work in the
following case:

   vla-array-size.c:19:55: error: invalid use of flexible array member
    int foo6(int a, int b[*][*], int c[static __array_size(*b)]);
                                                          ^

sizeof() *does* work in this case -- my test is based on gcc.dg/vla-5.c
which uses sizeof() instead.  __array_size(*b) works fine the function
body but not in the prototype, and also works fine with C99 variable
length arrays.

This message has been borrowed from c_incomplete_type_error(), along with
the test for TYPE_DOMAIN (type) && TYPE_MAX_VALUE (TYPE_DOMAIN (type)).

Unfortunately, I'm at a loss to say whether TYPE_MAX_VALUE should be
set in this instance, or whether I should be going about things
differently.  It seems interesting that TYPE_SIZE_UNIT (type) is set
but TYPE_MAX_VALUE (TYPE_DOMAIN (type)) is not.  Can anyone help?

Also, it's not obvious to me whether I need to consider the case of
TYPE_MIN_VALUE (TYPE_DOMAIN (type)) being nonzero.
Joseph Myers Feb. 15, 2016, 11:11 p.m. UTC | #5
On Sat, 13 Feb 2016, Stuart Brady wrote:

> > Critical issues to define and cover thoroughly in tests include the
> > rules for when operands of sizeof are evaluated, as adapted
> > appropriately for this keyword, and for when it returns various kinds
> > of constants.
> 
> So in other words, adapting all of the sizeof tests would be appropriate,
> and sizeof tests for non-array types would change from expected passes to
> expected failures?

It's not very clear what's a sizeof test, but all that are testing sizeof 
(as opposed to incidentally using it) and applicable to this keyword, yes.

> > Is the rule for your keyword that the operand is evaluated, and the
> > result not an integer constant, iff the operand is an array with a
> > variable number of elements (as opposed to an array with a constant
> > number of elements that themselves are variable-sized, for example)?
> 
> If I've understood correctly, then yes:
> 
>    #include <stdio.h>
>    void foo(int i) {
>      int a[i], b[__array_size(a)];
>      printf("%zi, %zi\n", __array_size(a), __array_size(b));
>    };
>    int main() { foo(42); }

That test doesn't relate to my question, which is about when arguments are 
evaluated and when results are or are not integer constant expressions.

For whether arguments are evaluated, you need __array_size with arguments 
that have side effects, and then test whether those side effects occurred.  
For whether results are integer constant expressions, you can test e.g. 
whether __array_size (a) - __array_size (a) is accepted in a context 
requiring a pointer (whether it acts as a valid null pointer constant).

> > C11 6.5.3.4#2 (sizeof) would need testing, 
> 
> Does this section differ from the September 7th draft in any way?

I don't know.

> > Presumably this keyword can be applied to an array at function prototype
> > scope whose size is explicitly or implicitly [*], though nothing useful
> > can be done with the results, as with [*]? (Cf. gcc.dg/vla-5.c.)
> 
> I'm not sure I quite understand the meaning of an implicit [*].  Does that
> just mean __array_size(foo) with an int foo[*] as another parameter?

Implicit [*] is e.g.

void f (int a, int (*b)[a], int (*c)[__array_size (*b)]);

where the VLA *b is at function prototype scope and so gets treated as [*] 
- and then __array_size (*) effectively means "an indeterminate value of 
type size_t" (but since that value only ever gets used in ways that end up 
with it being discarded, possibly through another implicit conversion to 
[*] as here, manipulating such indeterminate values is never a problem).
Stuart Brady Feb. 16, 2016, 3:20 p.m. UTC | #6
On Mon, Feb 15, 2016 at 11:11:54PM +0000, Joseph Myers wrote:
> On Sat, 13 Feb 2016, Stuart Brady wrote:
> 
> > So in other words, adapting all of the sizeof tests would be appropriate,
> > and sizeof tests for non-array types would change from expected passes to
> > expected failures?
> 
> It's not very clear what's a sizeof test, but all that are testing sizeof 
> (as opposed to incidentally using it) and applicable to this keyword, yes.

Okay.  I will go through all of the gcc.dg tests that mention sizeof and
provide an opinion as to whether I think a corresponding __array_size
test would be needed, then.

> > > Is the rule for your keyword that the operand is evaluated, and the
> > > result not an integer constant, iff the operand is an array with a
> > > variable number of elements (as opposed to an array with a constant
> > > number of elements that themselves are variable-sized, for example)?
> > 
> > If I've understood correctly, then yes:
> > 
> >    #include <stdio.h>
> >    void foo(int i) {
> >      int a[i], b[__array_size(a)];
> >      printf("%zi, %zi\n", __array_size(a), __array_size(b));
> >    };
> >    int main() { foo(42); }
> 
> That test doesn't relate to my question, which is about when arguments are 
> evaluated and when results are or are not integer constant expressions.

Ah, I think I understand, now.

> For whether arguments are evaluated, you need __array_size with arguments 
> that have side effects, and then test whether those side effects occurred.  
> For whether results are integer constant expressions, you can test e.g. 
> whether __array_size (a) - __array_size (a) is accepted in a context 
> requiring a pointer (whether it acts as a valid null pointer constant).

With my patch, void *p = __array_size(a) - __array_size(a) sets p to be
null.  That behaviour seems desirable to me.

I will look into this in some more depth and get back to you, but my
thinking is that the requirement should be for any implementation defined
behaviour relating to sizeof to have equivalent behaviour for
__array_size().  This means that in situations where sizeof(expr) /
sizeof(*(expr)) would have side-effects, __array_size(expr) would cause
those side-effects to occur only once.

My view is that __array_size should result in an integer constant
expression iff sizeof would do so, and that __array_size should evaluate
its operand with side-effects iff sizeof would do so.  This is purely in
accordance with the principle of least surprise.  I would welcome any
discussion as to whether some alternative behaviour might be preferred.

> > > C11 6.5.3.4#2 (sizeof) would need testing, 
> > 
> > Does this section differ from the September 7th draft in any way?
> 
> I don't know.

I now see that this draft really was rather old.  I can now refer to
N1570 (April 12, 2011) and will use that, instead.  Apologies for the
noise, there -- I will redo my list of amendments to the draft using
N1570 instead.

> > > Presumably this keyword can be applied to an array at function prototype
> > > scope whose size is explicitly or implicitly [*], though nothing useful
> > > can be done with the results, as with [*]? (Cf. gcc.dg/vla-5.c.)
> > 
> > I'm not sure I quite understand the meaning of an implicit [*].  Does that
> > just mean __array_size(foo) with an int foo[*] as another parameter?
> 
> Implicit [*] is e.g.
> 
> void f (int a, int (*b)[a], int (*c)[__array_size (*b)]);
> 
> where the VLA *b is at function prototype scope and so gets treated as [*] 
> - and then __array_size (*) effectively means "an indeterminate value of 
> type size_t" (but since that value only ever gets used in ways that end up 
> with it being discarded, possibly through another implicit conversion to 
> [*] as here, manipulating such indeterminate values is never a problem).

Yes, I can see no reason for this to differ in this regard to sizeof,
and I will make sure that I include this in my tests.

Unfortunately, I do find that TYPE_MAX_VALUE (TYPE_DOMAIN (type)) is
unset in that scope so your example results in a compilation failure.
I imagine this is possibly as it would get treated as [*] as you say.
TYPE_SIZE_UNIT is set, which is why this works when using sizeof.
However, I will follow your advice, and try not to focus too much on the
implementation just yet, but will instead assume we can deal with this at
a later stage.
Stuart Brady Feb. 16, 2016, 3:26 p.m. UTC | #7
On Mon, Feb 15, 2016 at 03:51:43PM +0000, Stuart Brady wrote:
> On Mon, Feb 15, 2016 at 03:05:36PM +0100, Marek Polacek wrote:
> > On Sat, Feb 13, 2016 at 03:16:49AM +0000, Stuart Brady wrote:
> > > For a hypothetical change to the C standard itself, I think one might use
> > > the name "_ArraySize", but for a non-standard extension this would not be
> > > appropriate.  I think "__array_size" is fine, though, and is consistent
> > > with "__alignof".
> > 
> > Please no CamelCase, that's ugly ;).  I also think that "__array_size" is fine.

[...]

> Hypothetically, I suppose _Array_Size, _Array_size or _Arraysize would
> all be better than the CamelCase verison.

On further consideration, referring to N1570, I see now that _Array_size
would be consistent in style with _Static_assert and _Thread_local, if
such a keyword were ever to be added as part of the standard.
Joseph Myers Feb. 16, 2016, 11:10 p.m. UTC | #8
On Tue, 16 Feb 2016, Stuart Brady wrote:

> > For whether arguments are evaluated, you need __array_size with arguments 
> > that have side effects, and then test whether those side effects occurred.  
> > For whether results are integer constant expressions, you can test e.g. 
> > whether __array_size (a) - __array_size (a) is accepted in a context 
> > requiring a pointer (whether it acts as a valid null pointer constant).
> 
> With my patch, void *p = __array_size(a) - __array_size(a) sets p to be
> null.  That behaviour seems desirable to me.

It sets it to be null - but does it diagnose conversion from integer to 
pointer without a cast (it should do so if __array_size is not evaluating 
to an integer constant expression, but not if it is evaluating to an 
integer constant expression - hence this being one way to test whether 
the result is an integer constant expression)?
Stuart Brady Feb. 16, 2016, 11:28 p.m. UTC | #9
On Tue, Feb 16, 2016 at 11:10:08PM +0000, Joseph Myers wrote:
> It sets it to be null - but does it diagnose conversion from integer to 
> pointer without a cast (it should do so if __array_size is not evaluating 
> to an integer constant expression, but not if it is evaluating to an 
> integer constant expression - hence this being one way to test whether 
> the result is an integer constant expression)?

With -Wall -Wextra, I do not see any such warning.  IIUC, the warning
would be given by -Wint-conversion, included in -Wall.  FWIW, if I use
"int a=1; void *p=a-a;" I do get the expected warning, so I think that
covers this case.  Is this right?

If I use __array_size in an enumeration, this works fine, rather than
giving me "error: enumerator value for ‘foo’ is not an integer constant".
I am not sure if this is not also a valid test.
Joseph Myers Feb. 16, 2016, 11:35 p.m. UTC | #10
On Tue, 16 Feb 2016, Stuart Brady wrote:

> On Tue, Feb 16, 2016 at 11:10:08PM +0000, Joseph Myers wrote:
> > It sets it to be null - but does it diagnose conversion from integer to 
> > pointer without a cast (it should do so if __array_size is not evaluating 
> > to an integer constant expression, but not if it is evaluating to an 
> > integer constant expression - hence this being one way to test whether 
> > the result is an integer constant expression)?
> 
> With -Wall -Wextra, I do not see any such warning.  IIUC, the warning
> would be given by -Wint-conversion, included in -Wall.  FWIW, if I use
> "int a=1; void *p=a-a;" I do get the expected warning, so I think that
> covers this case.  Is this right?

What's right is:

* In cases where it should return an integer constant (you've said that's 
when the argument is not a VLA, as for sizeof), there should be no 
diagnostic.

* In cases where it should not return an integer constant, there should be 
such a diagnostic (enabled by default).

The case I raised in my original message was e.g.

int a;
int b[2][a];

- should __array_size (b) be an integer constant (size_t)2, or should it 
be non-constant (size_t)2 because the argument is a VLA (albeit a VLA 
whose top-level dimension is an integer constant expression)?

> If I use __array_size in an enumeration, this works fine, rather than
> giving me "error: enumerator value for ‘foo’ is not an integer constant".
> I am not sure if this is not also a valid test.

It's a valid test, *but* in various contexts, such as in an enumeration, 
you need -pedantic to enable diagnostics for some cases of invalid integer 
constant expressions (but for the null pointer constant case, you don't).
Stuart Brady Feb. 17, 2016, 12:29 a.m. UTC | #11
On Tue, Feb 16, 2016 at 11:35:24PM +0000, Joseph Myers wrote:
> What's right is:
> 
> * In cases where it should return an integer constant (you've said that's 
> when the argument is not a VLA, as for sizeof), there should be no 
> diagnostic.

Right.

> * In cases where it should not return an integer constant, there should be 
> such a diagnostic (enabled by default).

Good point.  Indeed, for a VLA, I do get such a message.

> The case I raised in my original message was e.g.
> 
> int a;
> int b[2][a];
> 
> - should __array_size (b) be an integer constant (size_t)2, or should it 
> be non-constant (size_t)2 because the argument is a VLA (albeit a VLA 
> whose top-level dimension is an integer constant expression)?

Ouch.  I would say it should be an integer constant (size_t)2, simply as
that seems to me to be a reasonable expectation.  Unfortunately, this is
not what happens with my patch, as I get a -Wint-conversion warning. :-(

Oddly with "int b[a][2];" I find that __array_size(*b) *is* an integer
constant expression.

This leads me to wonder *why* I am not getting an integer constant
expression, and whether this might be a problem for other reasons.
I'm concerned GCC may not be able to give warnings pertaining to the
outer subscript for VLAs, as these may also be dependent on the same
underlying domain max value expression as __array_size.

The fact that __array_size(b) would be an integer constant expression
in this case, whereas sizeof(b) / sizeof(*b) would not seems to be an
advantage to this keyword that I had not originally considered.

Personally, I could tolerate this not returning an integer constant
expression, but I would regard that as a bug.  As extra confusion would
be introduced when fixing this, I would rather just get it right.

> On Tue, 16 Feb 2016, Stuart Brady wrote:
> > If I use __array_size in an enumeration, this works fine, rather than
> > giving me "error: enumerator value for ‘foo’ is not an integer constant".
> > I am not sure if this is not also a valid test.
> 
> It's a valid test, *but* in various contexts, such as in an enumeration, 
> you need -pedantic to enable diagnostics for some cases of invalid integer 
> constant expressions (but for the null pointer constant case, you don't).

Thanks, that's helpful to know.
Stuart Brady Feb. 23, 2016, 5:21 p.m. UTC | #12
On Wed, Feb 17, 2016 at 12:29:34AM +0000, Stuart Brady wrote:
> > - should __array_size (b) be an integer constant (size_t)2, or should it 
> > be non-constant (size_t)2 because the argument is a VLA (albeit a VLA 
> > whose top-level dimension is an integer constant expression)?
> 
> Ouch.  I would say it should be an integer constant (size_t)2, simply as
> that seems to me to be a reasonable expectation.  Unfortunately, this is
> not what happens with my patch, as I get a -Wint-conversion warning. :-(

[snip]

Okay.  So, unsurprisingly, it turns out the problem here was in my code.
When I added c_expr_array_size_expr() and c_expr_array_size_type() it
seems I had not understood that C_TYPE_VARIABLE_SIZE and therefore also
c_vla_type_p are non-zero (true) for VLAs where the outermost subscript
is not variable, behaviour I can now clearly see in grokdeclarator().

This certainly supports the notion that test cases and documentation are
of greater importance than the patch itself, at this stage.

I now seem to have an __array_size keyword that behaves as I would expect
in this case, too.  I'll resubmit the patch once I have gone through the
final draft of C11.

It is still not entirely clear to me whether I must do something to
prevent constant folding for use of __array_size with VLAs, but I am not
so highly concerned just at the moment.
diff mbox

Patch

diff --git a/gcc/c-family/c-common.c b/gcc/c-family/c-common.c
index 3d84316..8033d14 100644
--- a/gcc/c-family/c-common.c
+++ b/gcc/c-family/c-common.c
@@ -445,6 +445,8 @@  const struct c_common_resword c_common_reswords[] =
   { "__PRETTY_FUNCTION__", RID_PRETTY_FUNCTION_NAME, 0 },
   { "__alignof",	RID_ALIGNOF,	0 },
   { "__alignof__",	RID_ALIGNOF,	0 },
+  { "__array_size",	RID_ARRAY_SIZE,	0 },
+  { "__array_size__",	RID_ARRAY_SIZE,	0 },
   { "__asm",		RID_ASM,	0 },
   { "__asm__",		RID_ASM,	0 },
   { "__attribute",	RID_ATTRIBUTE,	0 },
@@ -4929,6 +4931,58 @@  c_sizeof_or_alignof_type (location_t loc,
   return value;
 }
 
+/* Compute '__array_size (TYPE)'.  The COMPLAIN flag controls whether we
+   should diagnose possibly ill-formed constructs or not.  LOC is the
+   location of the __array_size operator.  */
+
+tree
+c_array_size_type (location_t loc, tree type, int complain)
+{
+  tree value = NULL;
+  enum tree_code type_code = TREE_CODE (type);
+  const char *op_name = "__array_size";
+
+  if (type_code != ARRAY_TYPE)
+    {
+      if (complain)
+	error_at (loc, "invalid application of %qs to non-array type",
+		  op_name);
+      return error_mark_node;
+    }
+  else if (c_dialect_cxx () && !COMPLETE_TYPE_P (TREE_TYPE (type)))
+    {
+      if (complain)
+	error_at (loc, "invalid application of %qs to array type %qT of "
+		  "incomplete element type", op_name, type);
+      return error_mark_node;
+    }
+  else if (!TYPE_DOMAIN (type))
+    {
+      if (complain)
+	error_at (loc, "invalid use of array with unspecified bounds");
+      return error_mark_node;
+    }
+  else
+    {
+      value = TYPE_MAX_VALUE (TYPE_DOMAIN (type));
+      if (value == NULL)
+	{
+	  if (complain)
+	    error_at (loc, "invalid use of flexible array member");
+	  return error_mark_node;
+	}
+
+      value = size_binop (PLUS_EXPR, value, size_one_node);
+    }
+
+  /* VALUE will have the middle-end integer type sizetype.
+     However, we should really return a value of type `size_t',
+     which is just a typedef for an ordinary integer type.  */
+  value = fold_convert_loc (loc, size_type_node, value);
+
+  return value;
+}
+
 /* Implement the __alignof keyword: Return the minimum required
    alignment of EXPR, measured in bytes.  For VAR_DECLs,
    FUNCTION_DECLs and FIELD_DECLs return DECL_ALIGN (which can be set
diff --git a/gcc/c-family/c-common.def b/gcc/c-family/c-common.def
index 1d3ee53..7bbf40e 100644
--- a/gcc/c-family/c-common.def
+++ b/gcc/c-family/c-common.def
@@ -55,6 +55,9 @@  DEFTREECODE (USERDEF_LITERAL, "userdef_literal", tcc_exceptional, 3)
    or for the purpose of -Wsizeof-pointer-memaccess warning.  */
 DEFTREECODE (SIZEOF_EXPR, "sizeof_expr", tcc_expression, 1)
 
+/* Represents a '__array_size' expression during C++ template expansion */
+DEFTREECODE (ARRAY_SIZE_EXPR, "array_size_expr", tcc_expression, 1)
+
 /* Array Notation expression.
    Operand 0 is the array.
    Operand 1 is the starting array index.
diff --git a/gcc/c-family/c-common.h b/gcc/c-family/c-common.h
index fa3746c..bd6512d 100644
--- a/gcc/c-family/c-common.h
+++ b/gcc/c-family/c-common.h
@@ -103,6 +103,7 @@  enum rid
   RID_TYPES_COMPATIBLE_P,      RID_BUILTIN_COMPLEX,	     RID_BUILTIN_SHUFFLE,
   RID_DFLOAT32, RID_DFLOAT64, RID_DFLOAT128,
   RID_FRACT, RID_ACCUM, RID_AUTO_TYPE, RID_BUILTIN_CALL_WITH_STATIC_CHAIN,
+  RID_ARRAY_SIZE,
 
   /* C11 */
   RID_ALIGNAS, RID_GENERIC,
@@ -814,6 +815,7 @@  extern tree c_save_expr (tree);
 extern tree c_common_truthvalue_conversion (location_t, tree);
 extern void c_apply_type_quals_to_decl (int, tree);
 extern tree c_sizeof_or_alignof_type (location_t, tree, bool, bool, int);
+extern tree c_array_size_type (location_t, tree, int);
 extern tree c_alignof_expr (location_t, tree);
 /* Print an error message for invalid operands to arith operation CODE.
    NOP_EXPR is used as a special case (see truthvalue_conversion).  */
@@ -851,6 +853,7 @@  extern bool pointer_to_zero_sized_aggr_p (tree);
 
 #define c_sizeof(LOC, T)  c_sizeof_or_alignof_type (LOC, T, true, false, 1)
 #define c_alignof(LOC, T) c_sizeof_or_alignof_type (LOC, T, false, false, 1)
+#define c_array_size(LOC, T) c_array_size_type (LOC, T, 1)
 
 /* Subroutine of build_binary_op, used for certain operations.  */
 extern tree shorten_binary_op (tree result_type, tree op0, tree op1, bool bitwise);
diff --git a/gcc/c/c-parser.c b/gcc/c/c-parser.c
index eede3a7..c38fc79 100644
--- a/gcc/c/c-parser.c
+++ b/gcc/c/c-parser.c
@@ -1321,6 +1321,7 @@  static struct c_expr c_parser_cast_expression (c_parser *, struct c_expr *);
 static struct c_expr c_parser_unary_expression (c_parser *);
 static struct c_expr c_parser_sizeof_expression (c_parser *);
 static struct c_expr c_parser_alignof_expression (c_parser *);
+static struct c_expr c_parser_array_size_expression (c_parser *);
 static struct c_expr c_parser_postfix_expression (c_parser *);
 static struct c_expr c_parser_postfix_expression_after_paren_type (c_parser *,
 								   struct c_type_name *,
@@ -6717,6 +6718,8 @@  c_parser_cast_expression (c_parser *parser, struct c_expr *after)
    unary-expression:
      __alignof__ unary-expression
      __alignof__ ( type-name )
+     __array_size unary-expression
+     __array_size ( type-name )
      && identifier
 
    (C11 permits _Alignof with type names only.)
@@ -6841,6 +6844,8 @@  c_parser_unary_expression (c_parser *parser)
 	  return c_parser_sizeof_expression (parser);
 	case RID_ALIGNOF:
 	  return c_parser_alignof_expression (parser);
+	case RID_ARRAY_SIZE:
+	  return c_parser_array_size_expression (parser);
 	case RID_EXTENSION:
 	  c_parser_consume_token (parser);
 	  ext = disable_extension_diagnostics ();
@@ -7030,6 +7035,62 @@  c_parser_alignof_expression (c_parser *parser)
     }
 }
 
+static struct c_expr
+c_parser_array_size_expression (c_parser *parser)
+{
+  struct c_expr expr;
+  location_t expr_loc;
+  gcc_assert (c_parser_next_token_is_keyword (parser, RID_ARRAY_SIZE));
+  c_parser_consume_token (parser);
+  c_inhibit_evaluation_warnings++;
+  in_array_size++;
+  if (c_parser_next_token_is (parser, CPP_OPEN_PAREN)
+      && c_token_starts_typename (c_parser_peek_2nd_token (parser)))
+    {
+      /* Either array_size ( type-name ) or array_size unary-expression
+	 starting with a compound literal.  */
+      struct c_type_name *type_name;
+      c_parser_consume_token (parser);
+      expr_loc = c_parser_peek_token (parser)->location;
+      type_name = c_parser_type_name (parser);
+      c_parser_skip_until_found (parser, CPP_CLOSE_PAREN, "expected %<)%>");
+      if (type_name == NULL)
+	{
+	  struct c_expr ret;
+	  c_inhibit_evaluation_warnings--;
+	  in_array_size--;
+	  ret.value = error_mark_node;
+	  ret.original_code = ERROR_MARK;
+	  ret.original_type = NULL;
+	  return ret;
+	}
+      if (c_parser_next_token_is (parser, CPP_OPEN_BRACE))
+	{
+	  expr = c_parser_postfix_expression_after_paren_type (parser,
+							       type_name,
+							       expr_loc);
+	  goto array_size_expr;
+	}
+      /* array_size ( type-name ).  */
+      c_inhibit_evaluation_warnings--;
+      in_array_size--;
+      return c_expr_array_size_type (expr_loc, type_name);
+    }
+  else
+    {
+      expr_loc = c_parser_peek_token (parser)->location;
+      expr = c_parser_unary_expression (parser);
+    array_size_expr:
+      c_inhibit_evaluation_warnings--;
+      in_array_size--;
+      mark_exp_read (expr.value);
+      if (TREE_CODE (expr.value) == COMPONENT_REF
+	  && DECL_C_BIT_FIELD (TREE_OPERAND (expr.value, 1)))
+	error_at (expr_loc, "%<__array_size%> applied to a bit-field");
+      return c_expr_array_size_expr (expr_loc, expr);
+    }
+}
+
 /* Helper function to read arguments of builtins which are interfaces
    for the middle-end nodes like COMPLEX_EXPR, VEC_PERM_EXPR and
    others.  The name of the builtin is passed using BNAME parameter.
@@ -9479,6 +9540,7 @@  c_parser_objc_selector (c_parser *parser)
     case RID_SIZEOF:
     case RID_TYPEOF:
     case RID_ALIGNOF:
+    case RID_ARRAY_SIZE:
     case RID_UNSIGNED:
     case RID_LONG:
     case RID_CONST:
diff --git a/gcc/c/c-tree.h b/gcc/c/c-tree.h
index cf79ba7..dc083c1 100644
--- a/gcc/c/c-tree.h
+++ b/gcc/c/c-tree.h
@@ -580,6 +580,7 @@  extern bool c_vla_unspec_p (tree x, tree fn);
 
 /* in c-typeck.c */
 extern int in_alignof;
+extern int in_array_size;
 extern int in_sizeof;
 extern int in_typeof;
 
@@ -608,6 +609,8 @@  extern tree build_component_ref (location_t, tree, tree);
 extern tree build_array_ref (location_t, tree, tree);
 extern tree build_external_ref (location_t, tree, int, tree *);
 extern void pop_maybe_used (bool);
+extern struct c_expr c_expr_array_size_expr (location_t, struct c_expr);
+extern struct c_expr c_expr_array_size_type (location_t, struct c_type_name *);
 extern struct c_expr c_expr_sizeof_expr (location_t, struct c_expr);
 extern struct c_expr c_expr_sizeof_type (location_t, struct c_type_name *);
 extern struct c_expr parser_build_unary_op (location_t, enum tree_code,
diff --git a/gcc/c/c-typeck.c b/gcc/c/c-typeck.c
index 65925cb..faae55fc 100644
--- a/gcc/c/c-typeck.c
+++ b/gcc/c/c-typeck.c
@@ -62,6 +62,9 @@  enum impl_conv {
 /* The level of nesting inside "__alignof__".  */
 int in_alignof;
 
+/* The level of nesting inside "__array_size".  */
+int in_array_size;
+
 /* The level of nesting inside "sizeof".  */
 int in_sizeof;
 
@@ -2900,6 +2903,72 @@  c_expr_sizeof_type (location_t loc, struct c_type_name *t)
   return ret;
 }
 
+/* Return the result of sizeof applied to EXPR.  */
+
+struct c_expr
+c_expr_array_size_expr (location_t loc, struct c_expr expr)
+{
+  struct c_expr ret;
+  if (expr.value == error_mark_node)
+    {
+      ret.value = error_mark_node;
+      ret.original_code = ERROR_MARK;
+      ret.original_type = NULL;
+      pop_maybe_used (false);
+    }
+  else
+    {
+      bool expr_const_operands = true;
+      tree folded_expr = c_fully_fold (expr.value, require_constant_value,
+				       &expr_const_operands);
+      ret.value = c_array_size (loc, TREE_TYPE (folded_expr));
+      ret.original_code = ARRAY_SIZE_EXPR;
+      ret.original_type = NULL;
+      if (c_vla_type_p (TREE_TYPE (folded_expr)))
+	{
+	  /* sizeof is evaluated when given a vla (C99 6.5.3.4p2).  */
+	  ret.value = build2 (C_MAYBE_CONST_EXPR, TREE_TYPE (ret.value),
+			      folded_expr, ret.value);
+	  C_MAYBE_CONST_EXPR_NON_CONST (ret.value) = !expr_const_operands;
+	  SET_EXPR_LOCATION (ret.value, loc);
+	}
+      pop_maybe_used (C_TYPE_VARIABLE_SIZE (TREE_TYPE (folded_expr)));
+    }
+  return ret;
+}
+
+struct c_expr
+c_expr_array_size_type (location_t loc, struct c_type_name *t)
+{
+  tree type;
+  struct c_expr ret;
+  tree type_expr = NULL_TREE;
+  bool type_expr_const = true;
+  type = groktypename (t, &type_expr, &type_expr_const);
+  ret.value = c_array_size (loc, type);
+  ret.original_code = ARRAY_SIZE_EXPR;
+  ret.original_type = NULL;
+  if ((type_expr || TREE_CODE (ret.value) == INTEGER_CST)
+      && c_vla_type_p (type))
+    {
+      /* If the type is a [*] array, it is a VLA but is represented as
+	 having a size of zero.  In such a case we must ensure that
+	 the result of sizeof does not get folded to a constant by
+	 c_fully_fold, because if the size is evaluated the result is
+	 not constant and so constraints on zero or negative size
+	 arrays must not be applied when this sizeof call is inside
+	 another array declarator.  */
+      if (!type_expr)
+	type_expr = integer_zero_node;
+      ret.value = build2 (C_MAYBE_CONST_EXPR, TREE_TYPE (ret.value),
+			  type_expr, ret.value);
+      C_MAYBE_CONST_EXPR_NON_CONST (ret.value) = !type_expr_const;
+    }
+  pop_maybe_used (type != error_mark_node
+		  ? C_TYPE_VARIABLE_SIZE (type) : false);
+  return ret;
+}
+
 /* Build a function call to function FUNCTION with parameters PARAMS.
    The function call is at LOC.
    PARAMS is a list--a chain of TREE_LIST nodes--in which the
diff --git a/gcc/cp/constexpr.c b/gcc/cp/constexpr.c
index 85fc64e..0ec254e 100644
--- a/gcc/cp/constexpr.c
+++ b/gcc/cp/constexpr.c
@@ -4275,6 +4275,7 @@  potential_constant_expression_1 (tree t, bool want_rval, bool strict,
     case CONST_DECL:
     case SIZEOF_EXPR:
     case ALIGNOF_EXPR:
+    case ARRAY_SIZE_EXPR:
     case OFFSETOF_EXPR:
     case NOEXCEPT_EXPR:
     case TEMPLATE_PARM_INDEX:
diff --git a/gcc/cp/cp-objcp-common.c b/gcc/cp/cp-objcp-common.c
index f7ddb00..4d87bff 100644
--- a/gcc/cp/cp-objcp-common.c
+++ b/gcc/cp/cp-objcp-common.c
@@ -271,6 +271,7 @@  cp_common_init_ts (void)
   MARK_TS_TYPED (ARROW_EXPR);
   MARK_TS_TYPED (SIZEOF_EXPR);
   MARK_TS_TYPED (ALIGNOF_EXPR);
+  MARK_TS_TYPED (ARRAY_SIZE_EXPR);
   MARK_TS_TYPED (AT_ENCODE_EXPR);
   MARK_TS_TYPED (UNARY_PLUS_EXPR);
   MARK_TS_TYPED (TRAIT_EXPR);
diff --git a/gcc/cp/cp-tree.h b/gcc/cp/cp-tree.h
index 3b91089..b902748 100644
--- a/gcc/cp/cp-tree.h
+++ b/gcc/cp/cp-tree.h
@@ -139,6 +139,7 @@  operator == (const cp_expr &lhs, tree rhs)
       OVL_ARG_DEPENDENT (in OVERLOAD)
       PACK_EXPANSION_LOCAL_P (in *_PACK_EXPANSION)
       TINFO_HAS_ACCESS_ERRORS (in TEMPLATE_INFO)
+      ARRAY_SIZE_EXPR_TYPE_P (in ARRAY_SIZE_EXPR)
       SIZEOF_EXPR_TYPE_P (in SIZEOF_EXPR)
       COMPOUND_REQ_NOEXCEPT_P (in COMPOUND_REQ)
       WILDCARD_PACK_P (in WILDCARD_DECL)
@@ -172,6 +173,7 @@  operator == (const cp_expr &lhs, tree rhs)
       REF_PARENTHESIZED_P (in COMPONENT_REF, INDIRECT_REF)
       AGGR_INIT_ZERO_FIRST (in AGGR_INIT_EXPR)
       CONSTRUCTOR_MUTABLE_POISON (in CONSTRUCTOR)
+      PACK_EXPANSION_ARRAY_SIZE_P (in *_PACK_EXPANSION)
    3: (TREE_REFERENCE_EXPR) (in NON_LVALUE_EXPR) (commented-out).
       ICS_BAD_FLAG (in _CONV)
       FN_TRY_BLOCK_P (in TRY_BLOCK)
@@ -3266,6 +3268,9 @@  extern void decl_shadowed_for_var_insert (tree, tree);
 /* True iff this pack expansion is for sizeof....  */
 #define PACK_EXPANSION_SIZEOF_P(NODE) TREE_LANG_FLAG_1 (NODE)
 
+/* True iff this pack expansion is for array_size....  */
+#define PACK_EXPANSION_ARRAY_SIZE_P(NODE) TREE_LANG_FLAG_2 (NODE)
+
 /* True iff the wildcard can match a template parameter pack.  */
 #define WILDCARD_PACK_P(NODE) TREE_LANG_FLAG_0 (NODE)
 
@@ -4583,6 +4588,10 @@  more_aggr_init_expr_args_p (const aggr_init_expr_arg_iterator *iter)
 #define SIZEOF_EXPR_TYPE_P(NODE) \
   TREE_LANG_FLAG_0 (SIZEOF_EXPR_CHECK (NODE))
 
+/* True if ARRAY_SIZE_EXPR argument is type.  */
+#define ARRAY_SIZE_EXPR_TYPE_P(NODE) \
+  TREE_LANG_FLAG_0 (ARRAY_SIZE_EXPR_CHECK (NODE))
+
 /* An enumeration of the kind of tags that C++ accepts.  */
 enum tag_types {
   none_type = 0, /* Not a tag type.  */
diff --git a/gcc/cp/cxx-pretty-print.c b/gcc/cp/cxx-pretty-print.c
index cc28045..3ccc48a 100644
--- a/gcc/cp/cxx-pretty-print.c
+++ b/gcc/cp/cxx-pretty-print.c
@@ -771,9 +771,10 @@  cxx_pretty_printer::unary_expression (tree t)
       break;
 
     case SIZEOF_EXPR:
+    case ARRAY_SIZE_EXPR:
       if (PACK_EXPANSION_P (TREE_OPERAND (t, 0)))
 	{
-	  pp_cxx_ws_string (this, "sizeof");
+	  pp_cxx_ws_string (this, code == SIZEOF_EXPR ? "sizeof" : "__array_size__");
 	  pp_cxx_ws_string (this, "...");
 	  pp_cxx_whitespace (this);
 	  pp_cxx_left_paren (this);
@@ -787,9 +788,12 @@  cxx_pretty_printer::unary_expression (tree t)
       /* Fall through  */
 
     case ALIGNOF_EXPR:
-      pp_cxx_ws_string (this, code == SIZEOF_EXPR ? "sizeof" : "__alignof__");
+      pp_cxx_ws_string (this, code == SIZEOF_EXPR ? "sizeof" :
+			      code == ARRAY_SIZE_EXPR ? "__array_size__" :
+			      "__alignof__");
       pp_cxx_whitespace (this);
-      if (TREE_CODE (t) == SIZEOF_EXPR && SIZEOF_EXPR_TYPE_P (t))
+      if ((TREE_CODE (t) == SIZEOF_EXPR && SIZEOF_EXPR_TYPE_P (t))
+	  || (TREE_CODE (t) == ARRAY_SIZE_EXPR && ARRAY_SIZE_EXPR_TYPE_P (t)))
 	{
 	  pp_cxx_left_paren (this);
 	  type_id (TREE_TYPE (TREE_OPERAND (t, 0)));
@@ -1098,6 +1102,7 @@  cxx_pretty_printer::expression (tree t)
 
     case SIZEOF_EXPR:
     case ALIGNOF_EXPR:
+    case ARRAY_SIZE_EXPR:
     case NOEXCEPT_EXPR:
       unary_expression (t);
       break;
diff --git a/gcc/cp/error.c b/gcc/cp/error.c
index 3f9cf4a..1cf8929 100644
--- a/gcc/cp/error.c
+++ b/gcc/cp/error.c
@@ -2510,8 +2510,11 @@  dump_expr (cxx_pretty_printer *pp, tree t, int flags)
 
     case SIZEOF_EXPR:
     case ALIGNOF_EXPR:
+    case ARRAY_SIZE_EXPR:
       if (TREE_CODE (t) == SIZEOF_EXPR)
 	pp_cxx_ws_string (pp, "sizeof");
+      else if (TREE_CODE (t) == ARRAY_SIZE_EXPR)
+	pp_cxx_ws_string (pp, "__array_size__");
       else
 	{
 	  gcc_assert (TREE_CODE (t) == ALIGNOF_EXPR);
@@ -2525,7 +2528,8 @@  dump_expr (cxx_pretty_printer *pp, tree t, int flags)
 	}
       pp_cxx_whitespace (pp);
       pp_cxx_left_paren (pp);
-      if (TREE_CODE (t) == SIZEOF_EXPR && SIZEOF_EXPR_TYPE_P (t))
+      if ((TREE_CODE (t) == SIZEOF_EXPR && SIZEOF_EXPR_TYPE_P (t))
+          || (TREE_CODE (t) == ARRAY_SIZE_EXPR && ARRAY_SIZE_EXPR_TYPE_P (t)))
 	dump_type (pp, TREE_TYPE (op), flags);
       else if (TYPE_P (TREE_OPERAND (t, 0)))
 	dump_type (pp, op, flags);
diff --git a/gcc/cp/mangle.c b/gcc/cp/mangle.c
index 410c7f4..896a063 100644
--- a/gcc/cp/mangle.c
+++ b/gcc/cp/mangle.c
@@ -2771,6 +2771,12 @@  write_expression (tree expr)
       write_string ("st");
       write_type (TREE_TYPE (TREE_OPERAND (expr, 0)));
     }
+  else if (TREE_CODE (expr) == ARRAY_SIZE_EXPR
+	   && ARRAY_SIZE_EXPR_TYPE_P (expr))
+    {
+      write_string ("as");
+      write_type (TREE_TYPE (TREE_OPERAND (expr, 0)));
+    }
   else if (TREE_CODE (expr) == SIZEOF_EXPR
 	   && TYPE_P (TREE_OPERAND (expr, 0)))
     {
@@ -2783,6 +2789,12 @@  write_expression (tree expr)
       write_string ("at");
       write_type (TREE_OPERAND (expr, 0));
     }
+  else if (TREE_CODE (expr) == ARRAY_SIZE_EXPR
+	   && TYPE_P (TREE_OPERAND (expr, 0)))
+    {
+      write_string ("as");
+      write_type (TREE_OPERAND (expr, 0));
+    }
   else if (code == SCOPE_REF
 	   || code == BASELINK)
     {
diff --git a/gcc/cp/operators.def b/gcc/cp/operators.def
index aa657fa..8eb6a9a 100644
--- a/gcc/cp/operators.def
+++ b/gcc/cp/operators.def
@@ -94,6 +94,7 @@  DEF_SIMPLE_OPERATOR ("--", PREDECREMENT_EXPR, "mm", 1)
 DEF_SIMPLE_OPERATOR ("sizeof", SIZEOF_EXPR, "sz", 1)
 /* These are extensions.  */
 DEF_SIMPLE_OPERATOR ("alignof", ALIGNOF_EXPR, "az", 1)
+DEF_SIMPLE_OPERATOR ("array_size", ARRAY_SIZE_EXPR, "as", 1)
 DEF_SIMPLE_OPERATOR ("__imag__", IMAGPART_EXPR, "v18__imag__", 1)
 DEF_SIMPLE_OPERATOR ("__real__", REALPART_EXPR, "v18__real__", 1)
 
diff --git a/gcc/cp/parser.c b/gcc/cp/parser.c
index 07d1821..b188ab5 100644
--- a/gcc/cp/parser.c
+++ b/gcc/cp/parser.c
@@ -7686,6 +7686,7 @@  cp_parser_unary_expression (cp_parser *parser, cp_id_kind * pidk,
 
       switch (keyword)
 	{
+	case RID_ARRAY_SIZE:
 	case RID_ALIGNOF:
 	case RID_SIZEOF:
 	  {
@@ -7693,7 +7694,20 @@  cp_parser_unary_expression (cp_parser *parser, cp_id_kind * pidk,
 	    enum tree_code op;
 	    location_t first_loc;
 
-	    op = keyword == RID_ALIGNOF ? ALIGNOF_EXPR : SIZEOF_EXPR;
+	    switch (keyword)
+	      {
+	      case RID_ARRAY_SIZE:
+		op = ARRAY_SIZE_EXPR;
+		break;
+	      case RID_ALIGNOF:
+		op = ALIGNOF_EXPR;
+		break;
+	      case RID_SIZEOF:
+		op = SIZEOF_EXPR;
+		break;
+	      default:
+		gcc_unreachable ();
+	      }
 	    /* Consume the token.  */
 	    cp_lexer_consume_token (parser->lexer);
 	    first_loc = cp_lexer_peek_token (parser->lexer)->location;
diff --git a/gcc/cp/pt.c b/gcc/cp/pt.c
index 6780172..364131b 100644
--- a/gcc/cp/pt.c
+++ b/gcc/cp/pt.c
@@ -10966,6 +10966,7 @@  tsubst_pack_expansion (tree t, tree args, tsubst_flags_t complain,
 	 some pack expansion args we won't do anything yet.  */
       if (TREE_CODE (t) == TYPE_PACK_EXPANSION
 	  || PACK_EXPANSION_SIZEOF_P (t)
+	  || PACK_EXPANSION_ARRAY_SIZE_P (t)
 	  || pack_expansion_args_count (args))
 	return args;
       /* Also optimize expression pack expansions if we can tell that the
@@ -14050,12 +14051,14 @@  tsubst_copy (tree t, tree args, tsubst_flags_t complain, tree in_decl)
       }
 
     case SIZEOF_EXPR:
+    case ARRAY_SIZE_EXPR:
       if (PACK_EXPANSION_P (TREE_OPERAND (t, 0)))
         {
           tree expanded, op = TREE_OPERAND (t, 0);
 	  int len = 0;
 
-	  if (SIZEOF_EXPR_TYPE_P (t))
+	  if ((code == SIZEOF_EXPR && SIZEOF_EXPR_TYPE_P (t)) ||
+	      (code == ARRAY_SIZE_EXPR && ARRAY_SIZE_EXPR_TYPE_P (t)))
 	    op = TREE_TYPE (op);
 
 	  ++cp_unevaluated_operand;
@@ -14082,27 +14085,33 @@  tsubst_copy (tree t, tree args, tsubst_flags_t complain, tree in_decl)
 	    {
 	      if (TREE_CODE (expanded) == TREE_VEC)
 		expanded = TREE_VEC_ELT (expanded, len - 1);
-	      else
+	      else if (code == SIZEOF_EXPR)
 		PACK_EXPANSION_SIZEOF_P (expanded) = true;
+	      else
+		PACK_EXPANSION_ARRAY_SIZE_P (expanded) = true;
 
 	      if (TYPE_P (expanded))
-		return cxx_sizeof_or_alignof_type (expanded, SIZEOF_EXPR, 
+		return cxx_sizeof_or_alignof_type (expanded, code, 
 						   complain & tf_error);
 	      else
-		return cxx_sizeof_or_alignof_expr (expanded, SIZEOF_EXPR,
+		return cxx_sizeof_or_alignof_expr (expanded, code,
                                                    complain & tf_error);
 	    }
 	  else
 	    return build_int_cst (size_type_node, len);
         }
-      if (SIZEOF_EXPR_TYPE_P (t))
+      if ((code == SIZEOF_EXPR && SIZEOF_EXPR_TYPE_P (t)) ||
+	  (code == ARRAY_SIZE_EXPR && ARRAY_SIZE_EXPR_TYPE_P (t)))
 	{
 	  r = tsubst (TREE_TYPE (TREE_OPERAND (t, 0)),
 		      args, complain, in_decl);
 	  r = build1 (NOP_EXPR, r, error_mark_node);
-	  r = build1 (SIZEOF_EXPR,
+	  r = build1 (code,
 		      tsubst (TREE_TYPE (t), args, complain, in_decl), r);
-	  SIZEOF_EXPR_TYPE_P (r) = 1;
+	  if (code == SIZEOF_EXPR)
+	    SIZEOF_EXPR_TYPE_P (r) = 1;
+	  else
+	    ARRAY_SIZE_EXPR_TYPE_P (r) = 1;
 	  return r;
 	}
       /* Fall through */
@@ -16150,6 +16159,7 @@  tsubst_copy_and_build (tree t,
 					  length, stride, TREE_TYPE (op1)));
       }
     case SIZEOF_EXPR:
+    case ARRAY_SIZE_EXPR:
       if (PACK_EXPANSION_P (TREE_OPERAND (t, 0)))
 	RETURN (tsubst_copy (t, args, complain, in_decl));
       /* Fall through */
@@ -22635,7 +22645,12 @@  value_dependent_expression_p (tree expression)
       }
 
     case SIZEOF_EXPR:
-      if (SIZEOF_EXPR_TYPE_P (expression))
+    case ARRAY_SIZE_EXPR:
+      if (((TREE_CODE (expression) == SIZEOF_EXPR) &&
+	   SIZEOF_EXPR_TYPE_P (expression)) ||
+	  ((TREE_CODE (expression) == ARRAY_SIZE_EXPR) &&
+	   ARRAY_SIZE_EXPR_TYPE_P (expression)))
+
 	return dependent_type_p (TREE_TYPE (TREE_OPERAND (expression, 0)));
       /* FALLTHRU */
     case ALIGNOF_EXPR:
@@ -22827,6 +22842,7 @@  type_dependent_expression_p (tree expression)
   /* Some expression forms are never type-dependent.  */
   if (TREE_CODE (expression) == PSEUDO_DTOR_EXPR
       || TREE_CODE (expression) == SIZEOF_EXPR
+      || TREE_CODE (expression) == ARRAY_SIZE_EXPR
       || TREE_CODE (expression) == ALIGNOF_EXPR
       || TREE_CODE (expression) == AT_ENCODE_EXPR
       || TREE_CODE (expression) == NOEXCEPT_EXPR
@@ -23040,6 +23056,7 @@  instantiation_dependent_r (tree *tp, int *walk_subtrees,
 
       /* Handle expressions with type operands.  */
     case SIZEOF_EXPR:
+    case ARRAY_SIZE_EXPR:
     case ALIGNOF_EXPR:
     case TYPEID_EXPR:
     case AT_ENCODE_EXPR:
@@ -23047,6 +23064,8 @@  instantiation_dependent_r (tree *tp, int *walk_subtrees,
 	tree op = TREE_OPERAND (*tp, 0);
 	if (code == SIZEOF_EXPR && SIZEOF_EXPR_TYPE_P (*tp))
 	  op = TREE_TYPE (op);
+	else if (code == ARRAY_SIZE_EXPR && ARRAY_SIZE_EXPR_TYPE_P (*tp))
+	  op = TREE_TYPE (op);
 	if (TYPE_P (op))
 	  {
 	    if (dependent_type_p (op))
diff --git a/gcc/cp/tree.c b/gcc/cp/tree.c
index 3203aca..9c4919f 100644
--- a/gcc/cp/tree.c
+++ b/gcc/cp/tree.c
@@ -3136,6 +3136,7 @@  cp_tree_equal (tree t1, tree t2)
 
     case SIZEOF_EXPR:
     case ALIGNOF_EXPR:
+    case ARRAY_SIZE_EXPR:
       {
 	tree o1 = TREE_OPERAND (t1, 0);
 	tree o2 = TREE_OPERAND (t2, 0);
@@ -3147,6 +3148,13 @@  cp_tree_equal (tree t1, tree t2)
 	    if (SIZEOF_EXPR_TYPE_P (t2))
 	      o2 = TREE_TYPE (o2);
 	  }
+	else if (code1 == ARRAY_SIZE_EXPR)
+	  {
+	    if (ARRAY_SIZE_EXPR_TYPE_P (t1))
+	      o1 = TREE_TYPE (o1);
+	    if (ARRAY_SIZE_EXPR_TYPE_P (t2))
+	      o2 = TREE_TYPE (o2);
+	  }
 	if (TREE_CODE (o1) != TREE_CODE (o2))
 	  return false;
 	if (TYPE_P (o1))
diff --git a/gcc/cp/typeck.c b/gcc/cp/typeck.c
index c9fa112..a07eadb 100644
--- a/gcc/cp/typeck.c
+++ b/gcc/cp/typeck.c
@@ -1550,7 +1550,7 @@  cxx_sizeof_or_alignof_type (tree type, enum tree_code op, bool complain)
   tree value;
   bool dependent_p;
 
-  gcc_assert (op == SIZEOF_EXPR || op == ALIGNOF_EXPR);
+  gcc_assert (op == SIZEOF_EXPR || op == ALIGNOF_EXPR || op == ARRAY_SIZE_EXPR);
   if (type == error_mark_node)
     return error_mark_node;
 
@@ -1585,9 +1585,13 @@  cxx_sizeof_or_alignof_type (tree type, enum tree_code op, bool complain)
       return value;
     }
 
-  return c_sizeof_or_alignof_type (input_location, complete_type (type),
-				   op == SIZEOF_EXPR, false,
-				   complain);
+  if (op == ARRAY_SIZE_EXPR)
+    return c_array_size_type (input_location, complete_type (type),
+			      complain);
+  else
+    return c_sizeof_or_alignof_type (input_location, complete_type (type),
+				     op == SIZEOF_EXPR, false,
+				     complain);
 }
 
 /* Return the size of the type, without producing any warnings for
@@ -1741,16 +1745,82 @@  cxx_alignof_expr (tree e, tsubst_flags_t complain)
   return fold_convert (size_type_node, t);
 }
 
+/* Process an array_size expression where the operand is an expression.  */
+
+static tree
+cxx_array_size_expr (tree e, tsubst_flags_t complain)
+{
+  if (e == error_mark_node)
+    return error_mark_node;
+
+  if (processing_template_decl)
+    {
+      e = build_min (ARRAY_SIZE_EXPR, size_type_node, e);
+      TREE_SIDE_EFFECTS (e) = 0;
+      TREE_READONLY (e) = 1;
+
+      return e;
+    }
+
+  /* To get the size of a static data member declared as an array of
+     unknown bound, we need to instantiate it.  */
+  if (VAR_P (e)
+      && VAR_HAD_UNKNOWN_BOUND (e)
+      && DECL_TEMPLATE_INSTANTIATION (e))
+    instantiate_decl (e, /*defer_ok*/true, /*expl_inst_mem*/false);
+
+  e = mark_type_use (e);
+
+  if (TREE_CODE (e) == COMPONENT_REF
+      && TREE_CODE (TREE_OPERAND (e, 1)) == FIELD_DECL
+      && DECL_C_BIT_FIELD (TREE_OPERAND (e, 1)))
+    {
+      if (complain & tf_error)
+        error ("invalid application of %<__array_size%> to a bit-field");
+      else
+        return error_mark_node;
+      e = char_type_node;
+    }
+  else if (is_overloaded_fn (e))
+    {
+      if (complain & tf_error)
+        permerror (input_location, "invalid application of %<__array_size%> to an expression of "
+                   "function type");
+      else
+        return error_mark_node;
+      e = char_type_node;
+    }
+  else if (type_unknown_p (e))
+    {
+      if (complain & tf_error)
+        cxx_incomplete_type_error (e, TREE_TYPE (e));
+      else
+        return error_mark_node;
+      e = char_type_node;
+    }
+  else
+    e = TREE_TYPE (e);
+
+  return cxx_sizeof_or_alignof_type (e, ARRAY_SIZE_EXPR, complain & tf_error);
+}
+
 /* Process a sizeof or alignof expression E with code OP where the operand
    is an expression.  */
 
 tree
 cxx_sizeof_or_alignof_expr (tree e, enum tree_code op, bool complain)
 {
-  if (op == SIZEOF_EXPR)
-    return cxx_sizeof_expr (e, complain? tf_warning_or_error : tf_none);
-  else
-    return cxx_alignof_expr (e, complain? tf_warning_or_error : tf_none);
+  switch (op)
+    {
+    case SIZEOF_EXPR:
+      return cxx_sizeof_expr (e, complain? tf_warning_or_error : tf_none);
+    case ALIGNOF_EXPR:
+      return cxx_alignof_expr (e, complain? tf_warning_or_error : tf_none);
+    case ARRAY_SIZE_EXPR:
+      return cxx_array_size_expr (e, complain? tf_warning_or_error : tf_none);
+    default:
+      gcc_unreachable ();
+    }
 }
 
 /*  Build a representation of an expression 'alignas(E).'  Return the