diff mbox series

[WIP,RFC] Add support for keyword-based attributes

Message ID mpt4jm6sd0d.fsf@arm.com
State New
Headers show
Series [WIP,RFC] Add support for keyword-based attributes | expand

Commit Message

Richard Sandiford July 14, 2023, 3:56 p.m. UTC
Summary: We'd like to be able to specify some attributes using
keywords, rather than the traditional __attribute__ or [[...]]
syntax.  Would that be OK?

In more detail:

We'd like to add some new target-specific attributes for Arm SME.
These attributes affect semantics and code generation and so they
can't simply be ignored.

Traditionally we've done this kind of thing by adding GNU attributes,
via TARGET_ATTRIBUTE_TABLE in GCC's case.  The problem is that both
GCC and Clang have traditionally only warned about unrecognised GNU
attributes, rather than raising an error.  Older compilers might
therefore be able to look past some uses of the new attributes and
still produce object code, even though that object code is almost
certainly going to be wrong.  (The compilers will also emit a default-on
warning, but that might go unnoticed when building a big project.)

There are some existing attributes that similarly affect semantics
in ways that cannot be ignored.  vector_size is one obvious example.
But that doesn't make it a good thing. :)

Also, C++ says this for standard [[...]] attributes:

  For an attribute-token (including an attribute-scoped-token)
  not specified in this document, the behavior is implementation-defined;
  any such attribute-token that is not recognized by the implementation
  is ignored.

which doubles down on the idea that attributes should not be used
for necessary semantic information.

One of the attributes we'd like to add provides a new way of compiling
existing code.  The attribute doesn't require SME to be available;
it just says that the code must be compiled so that it can run in either
of two modes.  This is probably the most dangerous attribute of the set,
since compilers that ignore it would just produce normal code.  That
code might work in some test scenarios, but it would fail in others.

The feeling from the Clang community was therefore that these SME
attributes should use keywords instead, so that the keywords trigger
an error with older compilers.

However, it seemed wrong to define new SME-specific grammar rules,
since the underlying problem is pretty generic.  We therefore
proposed having a type of keyword that can appear exactly where
a standard [[...]] attribute can appear and that appertains to
exactly what a standard [[...]] attribute would appertain to.
No divergence or cherry-picking is allowed.

For example:

  [[arm::foo]]

would become:

  __arm_foo

and:

  [[arm::bar(args)]]

would become:

  __arm_bar(args)

It wouldn't be possible to retrofit arguments to a keyword that
previously didn't take arguments, since that could lead to parsing
ambiguities.  So when a keyword is first added, a binding decision
would need to be made whether the keyword always takes arguments
or is always standalone.

For that reason, empty argument lists are allowed for keywords,
even though they're not allowed for [[...]] attributes.

The argument-less version was accepted into Clang, and I have a follow-on
patch for handling arguments.  Would the same thing be OK for GCC,
in both the C and C++ frontends?

The patch below is a proof of concept for the C frontend.  It doesn't
bootstrap due to warnings about uninitialised fields.  And it doesn't
have tests.  But I did test it locally with various combinations of
attribute_spec and it seemed to work as expected.

The impact on the C frontend seems to be pretty small.  It looks like
the impact on the C++ frontend would be a bit bigger, but not much.

The patch contains a logically unrelated change: c-common.h set aside
16 keywords for address spaces, but of the in-tree ports, the maximum
number of keywords used is 6 (for amdgcn).  The patch therefore changes
the limit to 8 and uses 8 keywords for the new attributes.  This keeps
the number of reserved ids <= 256.

A real, non-proof-of-concept patch series would:

- Change the address-space keywords separately, and deal with any fallout.

- Clean up the way that attributes are specified, so that it isn't
  necessary to update all definitions when adding a new field.

- Allow more precise attribute requirements, such as "function decl only".

- Add tests :)

WDYT?  Does this approach look OK in principle, or is it a non-starter?

If it is a non-starter, the fallback would be to predefine macros
that expand to [[...]] or __attribute__.  Having the keywords gives
more precise semantics and better error messages though.

Thanks,
Richard
---
 gcc/attribs.cc                | 30 +++++++++++-
 gcc/c-family/c-common.h       | 13 ++----
 gcc/c/c-parser.cc             | 88 +++++++++++++++++++++++++++++++++--
 gcc/config/aarch64/aarch64.cc |  1 +
 gcc/tree-core.h               | 19 ++++++++
 5 files changed, 135 insertions(+), 16 deletions(-)

Comments

Jakub Jelinek July 14, 2023, 5:17 p.m. UTC | #1
On Fri, Jul 14, 2023 at 04:56:18PM +0100, Richard Sandiford via Gcc-patches wrote:
> Summary: We'd like to be able to specify some attributes using
> keywords, rather than the traditional __attribute__ or [[...]]
> syntax.  Would that be OK?

Will defer to C/C++ maintainers, but as you mentioned, there are many
attributes which really can't be ignored and change behavior significantly.
vector_size is one of those, mode attribute another,
no_unique_address another one (changes ABI in various cases),
the OpenMP attributes (omp::directive, omp::sequence) can change
behavior if -fopenmp, etc.
One can easily error with
#ifdef __has_cpp_attribute
#if !__has_cpp_attribute (arm::whatever)
#error arm::whatever attribute unsupported
#endif
#else
#error __has_cpp_attribute unsupported
#endif
Adding keywords instead of attributes seems to be too ugly to me.

	Jakub
Nathan Sidwell July 14, 2023, 9:14 p.m. UTC | #2
On 7/14/23 11:56, Richard Sandiford wrote:
> Summary: We'd like to be able to specify some attributes using
> keywords, rather than the traditional __attribute__ or [[...]]
> syntax.  Would that be OK?
> 
> In more detail:
> 
> We'd like to add some new target-specific attributes for Arm SME.
> These attributes affect semantics and code generation and so they
> can't simply be ignored.
> 
> Traditionally we've done this kind of thing by adding GNU attributes,
> via TARGET_ATTRIBUTE_TABLE in GCC's case.  The problem is that both
> GCC and Clang have traditionally only warned about unrecognised GNU
> attributes, rather than raising an error.  Older compilers might
> therefore be able to look past some uses of the new attributes and
> still produce object code, even though that object code is almost
> certainly going to be wrong.  (The compilers will also emit a default-on
> warning, but that might go unnoticed when building a big project.)
> 
> There are some existing attributes that similarly affect semantics
> in ways that cannot be ignored.  vector_size is one obvious example.
> But that doesn't make it a good thing. :)
> 
> Also, C++ says this for standard [[...]] attributes:
> 
>    For an attribute-token (including an attribute-scoped-token)
>    not specified in this document, the behavior is implementation-defined;
>    any such attribute-token that is not recognized by the implementation
>    is ignored.
> 
> which doubles down on the idea that attributes should not be used
> for necessary semantic information.

There;s been quite a bit of discussion about the practicalities of that.  As you 
say, there are existing, std-specified attributes, [[no_unique_address]] for 
instance, that affect user-visible object layout if ignored.
Further, my understanding is that implementation-specific attributes are 
permitted to affect program semantics -- they're implementatin extensions.

IMHO, attributes are the accepted mechanism for what you're describing. 
Compilers already have a way of dealing with them -- both parsing and, in 
general, representing them.  I would be wary of inventing a different mechanism.

Have you approached C or C++ std bodies for input?

> 
> One of the attributes we'd like to add provides a new way of compiling
> existing code.  The attribute doesn't require SME to be available;
> it just says that the code must be compiled so that it can run in either
> of two modes.  This is probably the most dangerous attribute of the set,
> since compilers that ignore it would just produce normal code.  That
> code might work in some test scenarios, but it would fail in others.
> 
> The feeling from the Clang community was therefore that these SME
> attributes should use keywords instead, so that the keywords trigger
> an error with older compilers.
> 
> However, it seemed wrong to define new SME-specific grammar rules,
> since the underlying problem is pretty generic.  We therefore
> proposed having a type of keyword that can appear exactly where
> a standard [[...]] attribute can appear and that appertains to
> exactly what a standard [[...]] attribute would appertain to.
> No divergence or cherry-picking is allowed.
> 
> For example:
> 
>    [[arm::foo]]
> 
> would become:
> 
>    __arm_foo
> 
> and:
> 
>    [[arm::bar(args)]]
> 
> would become:
> 
>    __arm_bar(args)
> 
> It wouldn't be possible to retrofit arguments to a keyword that
> previously didn't take arguments, since that could lead to parsing
> ambiguities.  So when a keyword is first added, a binding decision
> would need to be made whether the keyword always takes arguments
> or is always standalone.
> 
> For that reason, empty argument lists are allowed for keywords,
> even though they're not allowed for [[...]] attributes.
> 
> The argument-less version was accepted into Clang, and I have a follow-on
> patch for handling arguments.  Would the same thing be OK for GCC,
> in both the C and C++ frontends?
> 
> The patch below is a proof of concept for the C frontend.  It doesn't
> bootstrap due to warnings about uninitialised fields.  And it doesn't
> have tests.  But I did test it locally with various combinations of
> attribute_spec and it seemed to work as expected.
> 
> The impact on the C frontend seems to be pretty small.  It looks like
> the impact on the C++ frontend would be a bit bigger, but not much.
> 
> The patch contains a logically unrelated change: c-common.h set aside
> 16 keywords for address spaces, but of the in-tree ports, the maximum
> number of keywords used is 6 (for amdgcn).  The patch therefore changes
> the limit to 8 and uses 8 keywords for the new attributes.  This keeps
> the number of reserved ids <= 256.
> 
> A real, non-proof-of-concept patch series would:
> 
> - Change the address-space keywords separately, and deal with any fallout.
> 
> - Clean up the way that attributes are specified, so that it isn't
>    necessary to update all definitions when adding a new field.
> 
> - Allow more precise attribute requirements, such as "function decl only".
> 
> - Add tests :)
> 
> WDYT?  Does this approach look OK in principle, or is it a non-starter?
> 
> If it is a non-starter, the fallback would be to predefine macros
> that expand to [[...]] or __attribute__.  Having the keywords gives
> more precise semantics and better error messages though.
> 
> Thanks,
> Richard
> ---
>   gcc/attribs.cc                | 30 +++++++++++-
>   gcc/c-family/c-common.h       | 13 ++----
>   gcc/c/c-parser.cc             | 88 +++++++++++++++++++++++++++++++++--
>   gcc/config/aarch64/aarch64.cc |  1 +
>   gcc/tree-core.h               | 19 ++++++++
>   5 files changed, 135 insertions(+), 16 deletions(-)
> 
> diff --git a/gcc/attribs.cc b/gcc/attribs.cc
> index b8cb55b97df..706cd81329c 100644
> --- a/gcc/attribs.cc
> +++ b/gcc/attribs.cc
> @@ -752,6 +752,11 @@ decl_attributes (tree *node, tree attributes, int flags,
>   
>         if (spec->decl_required && !DECL_P (*anode))
>   	{
> +	  if (spec->is_keyword)
> +	    {
> +	      error ("%qE does not apply to types", name);
> +	      continue;
> +	    }
>   	  if (flags & ((int) ATTR_FLAG_DECL_NEXT
>   		       | (int) ATTR_FLAG_FUNCTION_NEXT
>   		       | (int) ATTR_FLAG_ARRAY_NEXT))
> @@ -775,6 +780,11 @@ decl_attributes (tree *node, tree attributes, int flags,
>   	 the decl's type in place here.  */
>         if (spec->type_required && DECL_P (*anode))
>   	{
> +	  if (spec->is_keyword)
> +	    {
> +	      error ("%qE only applies to types", name);
> +	      continue;
> +	    }
>   	  anode = &TREE_TYPE (*anode);
>   	  flags &= ~(int) ATTR_FLAG_TYPE_IN_PLACE;
>   	}
> @@ -782,6 +792,11 @@ decl_attributes (tree *node, tree attributes, int flags,
>         if (spec->function_type_required && TREE_CODE (*anode) != FUNCTION_TYPE
>   	  && TREE_CODE (*anode) != METHOD_TYPE)
>   	{
> +	  if (spec->is_keyword)
> +	    {
> +	      error ("%qE only applies to function types", name);
> +	      continue;
> +	    }
>   	  if (TREE_CODE (*anode) == POINTER_TYPE
>   	      && FUNC_OR_METHOD_TYPE_P (TREE_TYPE (*anode)))
>   	    {
> @@ -821,7 +836,12 @@ decl_attributes (tree *node, tree attributes, int flags,
>   	  && (flags & (int) ATTR_FLAG_TYPE_IN_PLACE)
>   	  && COMPLETE_TYPE_P (*anode))
>   	{
> -	  warning (OPT_Wattributes, "type attributes ignored after type is already defined");
> +	  if (spec->is_keyword)
> +	    error ("cannot apply %qE to a type that has already been"
> +		   " defined", name);
> +	  else
> +	    warning (OPT_Wattributes, "type attributes ignored after type"
> +		     " is already defined");
>   	  continue;
>   	}
>   
> @@ -895,7 +915,13 @@ decl_attributes (tree *node, tree attributes, int flags,
>   	  *anode = cur_and_last_decl[0];
>   	  if (ret == error_mark_node)
>   	    {
> -	      warning (OPT_Wattributes, "%qE attribute ignored", name);
> +	      if (spec->is_keyword)
> +		/* This error is only a last resort, Hopefully the
> +		   handler will report a better error in most cases,
> +		   return NULL_TREE, and set no_add_attrs.  */
> +		error ("invalid %qE attribute", name);
> +	      else
> +		warning (OPT_Wattributes, "%qE attribute ignored", name);
>   	      no_add_attrs = true;
>   	    }
>   	  else
> diff --git a/gcc/c-family/c-common.h b/gcc/c-family/c-common.h
> index b5ef5ff6b2c..4443ccb874d 100644
> --- a/gcc/c-family/c-common.h
> +++ b/gcc/c-family/c-common.h
> @@ -222,17 +222,9 @@ enum rid
>     RID_ADDR_SPACE_5,
>     RID_ADDR_SPACE_6,
>     RID_ADDR_SPACE_7,
> -  RID_ADDR_SPACE_8,
> -  RID_ADDR_SPACE_9,
> -  RID_ADDR_SPACE_10,
> -  RID_ADDR_SPACE_11,
> -  RID_ADDR_SPACE_12,
> -  RID_ADDR_SPACE_13,
> -  RID_ADDR_SPACE_14,
> -  RID_ADDR_SPACE_15,
>   
>     RID_FIRST_ADDR_SPACE = RID_ADDR_SPACE_0,
> -  RID_LAST_ADDR_SPACE = RID_ADDR_SPACE_15,
> +  RID_LAST_ADDR_SPACE = RID_ADDR_SPACE_7,
>   
>     /* __intN keywords.  The _N_M here doesn't correspond to the intN
>        in the keyword; use the bitsize in int_n_t_data_t[M] for that.
> @@ -251,6 +243,9 @@ enum rid
>     RID_FIRST_INT_N = RID_INT_N_0,
>     RID_LAST_INT_N = RID_INT_N_3,
>   
> +  RID_FIRST_KEYWORD_ATTR,
> +  RID_LAST_KEYWORD_ATTR = RID_FIRST_KEYWORD_ATTR + 7,
> +
>     RID_MAX,
>   
>     RID_FIRST_MODIFIER = RID_STATIC,
> diff --git a/gcc/c/c-parser.cc b/gcc/c/c-parser.cc
> index 24a6eb6e459..b02b082b140 100644
> --- a/gcc/c/c-parser.cc
> +++ b/gcc/c/c-parser.cc
> @@ -104,6 +104,17 @@ set_c_expr_source_range (c_expr *expr,
>   }
>   
>   
> +/* Register keyword attribute AS with reserved identifier code RID.  */
> +
> +void
> +c_register_keyword_attribute (const attribute_spec *as, int rid)
> +{
> +  tree id = get_identifier (as->name);
> +  C_SET_RID_CODE (id, rid);
> +  C_IS_RESERVED_WORD (id) = 1;
> +  ridpointers [rid] = id;
> +}
> +
>   /* Initialization routine for this file.  */
>   
>   void
> @@ -180,6 +191,16 @@ c_parse_init (void)
>         C_IS_RESERVED_WORD (id) = 1;
>         ridpointers [RID_OMP_ALL_MEMORY] = id;
>       }
> +
> +  int rid = RID_FIRST_KEYWORD_ATTR;
> +  if (const attribute_spec *attrs = targetm.attribute_table)
> +    for (const attribute_spec *attr = attrs; attr->name; ++attr)
> +      if (attr->is_keyword)
> +	{
> +	  gcc_assert (rid <= RID_LAST_KEYWORD_ATTR);
> +	  c_register_keyword_attribute (attr, rid);
> +	  rid += 1;
> +	}
>   }
>   
>   /* A parser structure recording information about the state and
> @@ -4951,7 +4972,8 @@ c_parser_gnu_attribute_any_word (c_parser *parser)
>       {
>         /* ??? See comment above about what keywords are accepted here.  */
>         bool ok;
> -      switch (c_parser_peek_token (parser)->keyword)
> +      int rid = c_parser_peek_token (parser)->keyword;
> +      switch (rid)
>   	{
>   	case RID_STATIC:
>   	case RID_UNSIGNED:
> @@ -4994,7 +5016,9 @@ c_parser_gnu_attribute_any_word (c_parser *parser)
>   	  ok = true;
>   	  break;
>   	default:
> -	  ok = false;
> +	  /* Accept these now so that we can reject them with a specific
> +	     error later.  */
> +	  ok = (rid >= RID_FIRST_KEYWORD_ATTR && rid <= RID_LAST_KEYWORD_ATTR);
>   	  break;
>   	}
>         if (!ok)
> @@ -5156,6 +5180,10 @@ c_parser_gnu_attribute (c_parser *parser, tree attrs,
>       return NULL_TREE;
>   
>     attr_name = canonicalize_attr_name (attr_name);
> +  const attribute_spec *as = lookup_attribute_spec (attr_name);
> +  if (as && as->is_keyword)
> +    error ("%qE cannot be used in %<__attribute__%> constructs", attr_name);
> +
>     c_parser_consume_token (parser);
>   
>     tree attr;
> @@ -5330,6 +5358,42 @@ c_parser_balanced_token_sequence (c_parser *parser)
>       }
>   }
>   
> +/* Parse a keyword attribute.  This is simply:
> +
> +     keyword
> +
> +   if the attribute never takes arguments, otherwise it is:
> +
> +     keyword ( balanced-token-sequence[opt] )
> +*/
> +
> +static tree
> +c_parser_keyword_attribute (c_parser *parser)
> +{
> +  c_token *token = c_parser_peek_token (parser);
> +  gcc_assert (token->type == CPP_KEYWORD);
> +  tree name = canonicalize_attr_name (token->value);
> +  c_parser_consume_token (parser);
> +
> +  tree attribute = build_tree_list (name, NULL_TREE);
> +  const attribute_spec *as = lookup_attribute_spec (name);
> +  gcc_assert (as && as->is_keyword);
> +  if (as->max_length > 0)
> +    {
> +      matching_parens parens;
> +      if (!parens.require_open (parser))
> +	return error_mark_node;
> +      /* Allow zero-length arguments regardless of as->min_length, since
> +	 the usual error "parentheses must be omitted if attribute argument
> +	 list is empty" suggests an invalid change.  We'll reject incorrect
> +	 argument lists later.  */
> +      TREE_VALUE (attribute)
> +	= c_parser_attribute_arguments (parser, false, false, false, true);
> +      parens.require_close (parser);
> +    }
> +  return attribute;
> +}
> +
>   /* Parse standard (C2X) attributes (including GNU attributes in the
>      gnu:: namespace).
>   
> @@ -5396,8 +5460,11 @@ c_parser_std_attribute (c_parser *parser, bool for_tm)
>       ns = NULL_TREE;
>     attribute = build_tree_list (build_tree_list (ns, name), NULL_TREE);
>   
> -  /* Parse the arguments, if any.  */
>     const attribute_spec *as = lookup_attribute_spec (TREE_PURPOSE (attribute));
> +  if (as && as->is_keyword)
> +    error ("%qE cannot be used in %<[[...]]%> constructs", name);
> +
> +  /* Parse the arguments, if any.  */
>     if (c_parser_next_token_is_not (parser, CPP_OPEN_PAREN))
>       goto out;
>     {
> @@ -5456,7 +5523,13 @@ c_parser_std_attribute (c_parser *parser, bool for_tm)
>   static tree
>   c_parser_std_attribute_specifier (c_parser *parser, bool for_tm)
>   {
> -  location_t loc = c_parser_peek_token (parser)->location;
> +  auto first_token = c_parser_peek_token (parser);
> +  location_t loc = first_token->location;
> +  if (first_token->type == CPP_KEYWORD
> +      && first_token->keyword >= RID_FIRST_KEYWORD_ATTR
> +      && first_token->keyword <= RID_LAST_KEYWORD_ATTR)
> +    return c_parser_keyword_attribute (parser);
> +
>     if (!c_parser_require (parser, CPP_OPEN_SQUARE, "expected %<[%>"))
>       return NULL_TREE;
>     if (!c_parser_require (parser, CPP_OPEN_SQUARE, "expected %<[%>"))
> @@ -5571,7 +5644,12 @@ c_parser_check_balanced_raw_token_sequence (c_parser *parser, unsigned int *n)
>   static bool
>   c_parser_nth_token_starts_std_attributes (c_parser *parser, unsigned int n)
>   {
> -  if (!(c_parser_peek_nth_token (parser, n)->type == CPP_OPEN_SQUARE
> +  auto token_n = c_parser_peek_nth_token (parser, n);
> +  if (token_n->type == CPP_KEYWORD
> +      && token_n->keyword >= RID_FIRST_KEYWORD_ATTR
> +      && token_n->keyword <= RID_LAST_KEYWORD_ATTR)
> +    return true;
> +  if (!(token_n->type == CPP_OPEN_SQUARE
>   	&& c_parser_peek_nth_token (parser, n + 1)->type == CPP_OPEN_SQUARE))
>       return false;
>     /* In C, '[[' must start attributes.  In Objective-C, we need to
> diff --git a/gcc/config/aarch64/aarch64.cc b/gcc/config/aarch64/aarch64.cc
> index 560e5431636..50698439104 100644
> --- a/gcc/config/aarch64/aarch64.cc
> +++ b/gcc/config/aarch64/aarch64.cc
> @@ -2802,6 +2802,7 @@ static const struct attribute_spec aarch64_attribute_table[] =
>     { "arm_sve_vector_bits", 1, 1, false, true,  false, true,
>   			  aarch64_sve::handle_arm_sve_vector_bits_attribute,
>   			  NULL },
> +  { "__arm_streaming",    0, 0, false, true,  true,  true,  NULL, NULL, true },
>     { "Advanced SIMD type", 1, 1, false, true,  false, true,  NULL, NULL },
>     { "SVE type",		  3, 3, false, true,  false, true,  NULL, NULL },
>     { "SVE sizeless type",  0, 0, false, true,  false, true,  NULL, NULL },
> diff --git a/gcc/tree-core.h b/gcc/tree-core.h
> index 668808a29d0..e6700509b9e 100644
> --- a/gcc/tree-core.h
> +++ b/gcc/tree-core.h
> @@ -2167,6 +2167,25 @@ struct attribute_spec {
>     /* An array of attribute exclusions describing names of other attributes
>        that this attribute is mutually exclusive with.  */
>     const exclusions *exclude;
> +
> +  /* Whether the attribute is a C/C++ "regular keyword attribute".
> +     When true for an attribute "foo", this means that:
> +
> +     - The keyword foo can appear exactly where the standard attribute syntax
> +       [[...]] can appear, but without the same minimum language requirements.
> +       The link is intended to be automatic: there should be no exceptions.
> +
> +     - The attribute appertains to whatever a standard attribute in the
> +       same location would appertain to.  There is no "sliding" from decls
> +       to types, as sometimes happens for GNU-style attributes.
> +
> +     - When MAX_LENGTH > 0, the keyword is followed by an argument list.
> +       This argument list is parsed in the same way as arguments to [[...]]
> +       attributes, except that the list can be empty if MIN_LENGTH == 0.
> +
> +     - In C and C++, the attribute cannot appear in __attribute__ or [[...]];
> +       it can only appear as a simple keyword.  */
> +  bool is_keyword;
>   };
>   
>   /* These functions allow a front-end to perform a manual layout of a
Richard Sandiford July 16, 2023, 10:18 a.m. UTC | #3
Thanks for the feedback.

Nathan Sidwell <nathan@acm.org> writes:
> On 7/14/23 11:56, Richard Sandiford wrote:
>> Summary: We'd like to be able to specify some attributes using
>> keywords, rather than the traditional __attribute__ or [[...]]
>> syntax.  Would that be OK?
>> 
>> In more detail:
>> 
>> We'd like to add some new target-specific attributes for Arm SME.
>> These attributes affect semantics and code generation and so they
>> can't simply be ignored.
>> 
>> Traditionally we've done this kind of thing by adding GNU attributes,
>> via TARGET_ATTRIBUTE_TABLE in GCC's case.  The problem is that both
>> GCC and Clang have traditionally only warned about unrecognised GNU
>> attributes, rather than raising an error.  Older compilers might
>> therefore be able to look past some uses of the new attributes and
>> still produce object code, even though that object code is almost
>> certainly going to be wrong.  (The compilers will also emit a default-on
>> warning, but that might go unnoticed when building a big project.)
>> 
>> There are some existing attributes that similarly affect semantics
>> in ways that cannot be ignored.  vector_size is one obvious example.
>> But that doesn't make it a good thing. :)
>> 
>> Also, C++ says this for standard [[...]] attributes:
>> 
>>    For an attribute-token (including an attribute-scoped-token)
>>    not specified in this document, the behavior is implementation-defined;
>>    any such attribute-token that is not recognized by the implementation
>>    is ignored.
>> 
>> which doubles down on the idea that attributes should not be used
>> for necessary semantic information.
>
> There;s been quite a bit of discussion about the practicalities of that.  As you 
> say, there are existing, std-specified attributes, [[no_unique_address]] for 
> instance, that affect user-visible object layout if ignored.
> Further, my understanding is that implementation-specific attributes are 
> permitted to affect program semantics -- they're implementatin extensions.
>
> IMHO, attributes are the accepted mechanism for what you're describing. 
> Compilers already have a way of dealing with them -- both parsing and, in 
> general, representing them.  I would be wary of inventing a different mechanism.
>
> Have you approached C or C++ std bodies for input?

Not directly, although members of the C++ WG contributed to the discussion
on the Clang side.

But I'm not sure how much the WG can help.  The main constraint here is
what existing compilers do, and that can't be changed.  Similarly, any
future Arm extension that hasn't been invented yet might need new semantic
attributes, and those attributes would be ignored by GCC 14, etc.

Thanks,
Richard
Richard Sandiford July 16, 2023, 10:50 a.m. UTC | #4
Jakub Jelinek <jakub@redhat.com> writes:
> On Fri, Jul 14, 2023 at 04:56:18PM +0100, Richard Sandiford via Gcc-patches wrote:
>> Summary: We'd like to be able to specify some attributes using
>> keywords, rather than the traditional __attribute__ or [[...]]
>> syntax.  Would that be OK?
>
> Will defer to C/C++ maintainers, but as you mentioned, there are many
> attributes which really can't be ignored and change behavior significantly.
> vector_size is one of those, mode attribute another,
> no_unique_address another one (changes ABI in various cases),
> the OpenMP attributes (omp::directive, omp::sequence) can change
> behavior if -fopenmp, etc.
> One can easily error with
> #ifdef __has_cpp_attribute
> #if !__has_cpp_attribute (arm::whatever)
> #error arm::whatever attribute unsupported
> #endif
> #else
> #error __has_cpp_attribute unsupported
> #endif

Yeah.  It's easy to detect whether a particular ACLE feature is supported,
since there are predefined macros for each one.  But IMO it's a failing
if we have to recommend that any compilation that uses arm::foo should
also have:

#ifndef __ARM_FEATURE_FOO
#error arm::foo not supported
#endif

It ought to be the compiler's job to diagnose its limitations, rather
than the user's.

The feature macros are more for conditional usage of features, where
there's a fallback available.

I suppose we could say that users have to include a particular header
file before using an attribute, and use a pragma in that header file to
tell the compiler to enable the attribute.  But then there would need to
be a separate header file for each distinct set of attributes (in terms
of historical timeline), which would get ugly.  I'm not sure that it's
better than using keywords, or even whether it's better than predefining
the "keyword" as a macro that expands to a compiler-internal attribute.

Thanks,
Richard
Richard Biener July 17, 2023, 6:38 a.m. UTC | #5
On Fri, Jul 14, 2023 at 5:58 PM Richard Sandiford via Gcc-patches
<gcc-patches@gcc.gnu.org> wrote:
>
> Summary: We'd like to be able to specify some attributes using
> keywords, rather than the traditional __attribute__ or [[...]]
> syntax.  Would that be OK?
>
> In more detail:
>
> We'd like to add some new target-specific attributes for Arm SME.
> These attributes affect semantics and code generation and so they
> can't simply be ignored.
>
> Traditionally we've done this kind of thing by adding GNU attributes,
> via TARGET_ATTRIBUTE_TABLE in GCC's case.  The problem is that both
> GCC and Clang have traditionally only warned about unrecognised GNU
> attributes, rather than raising an error.  Older compilers might
> therefore be able to look past some uses of the new attributes and
> still produce object code, even though that object code is almost
> certainly going to be wrong.  (The compilers will also emit a default-on
> warning, but that might go unnoticed when building a big project.)
>
> There are some existing attributes that similarly affect semantics
> in ways that cannot be ignored.  vector_size is one obvious example.
> But that doesn't make it a good thing. :)
>
> Also, C++ says this for standard [[...]] attributes:
>
>   For an attribute-token (including an attribute-scoped-token)
>   not specified in this document, the behavior is implementation-defined;
>   any such attribute-token that is not recognized by the implementation
>   is ignored.
>
> which doubles down on the idea that attributes should not be used
> for necessary semantic information.
>
> One of the attributes we'd like to add provides a new way of compiling
> existing code.  The attribute doesn't require SME to be available;
> it just says that the code must be compiled so that it can run in either
> of two modes.  This is probably the most dangerous attribute of the set,
> since compilers that ignore it would just produce normal code.  That
> code might work in some test scenarios, but it would fail in others.
>
> The feeling from the Clang community was therefore that these SME
> attributes should use keywords instead, so that the keywords trigger
> an error with older compilers.
>
> However, it seemed wrong to define new SME-specific grammar rules,
> since the underlying problem is pretty generic.  We therefore
> proposed having a type of keyword that can appear exactly where
> a standard [[...]] attribute can appear and that appertains to
> exactly what a standard [[...]] attribute would appertain to.
> No divergence or cherry-picking is allowed.
>
> For example:
>
>   [[arm::foo]]
>
> would become:
>
>   __arm_foo
>
> and:
>
>   [[arm::bar(args)]]
>
> would become:
>
>   __arm_bar(args)
>
> It wouldn't be possible to retrofit arguments to a keyword that
> previously didn't take arguments, since that could lead to parsing
> ambiguities.  So when a keyword is first added, a binding decision
> would need to be made whether the keyword always takes arguments
> or is always standalone.
>
> For that reason, empty argument lists are allowed for keywords,
> even though they're not allowed for [[...]] attributes.
>
> The argument-less version was accepted into Clang, and I have a follow-on
> patch for handling arguments.  Would the same thing be OK for GCC,
> in both the C and C++ frontends?
>
> The patch below is a proof of concept for the C frontend.  It doesn't
> bootstrap due to warnings about uninitialised fields.  And it doesn't
> have tests.  But I did test it locally with various combinations of
> attribute_spec and it seemed to work as expected.
>
> The impact on the C frontend seems to be pretty small.  It looks like
> the impact on the C++ frontend would be a bit bigger, but not much.
>
> The patch contains a logically unrelated change: c-common.h set aside
> 16 keywords for address spaces, but of the in-tree ports, the maximum
> number of keywords used is 6 (for amdgcn).  The patch therefore changes
> the limit to 8 and uses 8 keywords for the new attributes.  This keeps
> the number of reserved ids <= 256.

If you had added __arm(bar(args)) instead of __arm_bar(args) you would only
need one additional keyword - we could set aside a similar one for each
target then.  I realize that double-nesting of arguments might prove a bit
challenging but still.

In any case I also think that attributes are what you want and their
ugliness/issues are not worse than the ugliness/issues of the keyword
approach IMHO.

Richard.

> A real, non-proof-of-concept patch series would:
>
> - Change the address-space keywords separately, and deal with any fallout.
>
> - Clean up the way that attributes are specified, so that it isn't
>   necessary to update all definitions when adding a new field.
>
> - Allow more precise attribute requirements, such as "function decl only".
>
> - Add tests :)
>
> WDYT?  Does this approach look OK in principle, or is it a non-starter?
>
> If it is a non-starter, the fallback would be to predefine macros
> that expand to [[...]] or __attribute__.  Having the keywords gives
> more precise semantics and better error messages though.
>
> Thanks,
> Richard
> ---
>  gcc/attribs.cc                | 30 +++++++++++-
>  gcc/c-family/c-common.h       | 13 ++----
>  gcc/c/c-parser.cc             | 88 +++++++++++++++++++++++++++++++++--
>  gcc/config/aarch64/aarch64.cc |  1 +
>  gcc/tree-core.h               | 19 ++++++++
>  5 files changed, 135 insertions(+), 16 deletions(-)
>
> diff --git a/gcc/attribs.cc b/gcc/attribs.cc
> index b8cb55b97df..706cd81329c 100644
> --- a/gcc/attribs.cc
> +++ b/gcc/attribs.cc
> @@ -752,6 +752,11 @@ decl_attributes (tree *node, tree attributes, int flags,
>
>        if (spec->decl_required && !DECL_P (*anode))
>         {
> +         if (spec->is_keyword)
> +           {
> +             error ("%qE does not apply to types", name);
> +             continue;
> +           }
>           if (flags & ((int) ATTR_FLAG_DECL_NEXT
>                        | (int) ATTR_FLAG_FUNCTION_NEXT
>                        | (int) ATTR_FLAG_ARRAY_NEXT))
> @@ -775,6 +780,11 @@ decl_attributes (tree *node, tree attributes, int flags,
>          the decl's type in place here.  */
>        if (spec->type_required && DECL_P (*anode))
>         {
> +         if (spec->is_keyword)
> +           {
> +             error ("%qE only applies to types", name);
> +             continue;
> +           }
>           anode = &TREE_TYPE (*anode);
>           flags &= ~(int) ATTR_FLAG_TYPE_IN_PLACE;
>         }
> @@ -782,6 +792,11 @@ decl_attributes (tree *node, tree attributes, int flags,
>        if (spec->function_type_required && TREE_CODE (*anode) != FUNCTION_TYPE
>           && TREE_CODE (*anode) != METHOD_TYPE)
>         {
> +         if (spec->is_keyword)
> +           {
> +             error ("%qE only applies to function types", name);
> +             continue;
> +           }
>           if (TREE_CODE (*anode) == POINTER_TYPE
>               && FUNC_OR_METHOD_TYPE_P (TREE_TYPE (*anode)))
>             {
> @@ -821,7 +836,12 @@ decl_attributes (tree *node, tree attributes, int flags,
>           && (flags & (int) ATTR_FLAG_TYPE_IN_PLACE)
>           && COMPLETE_TYPE_P (*anode))
>         {
> -         warning (OPT_Wattributes, "type attributes ignored after type is already defined");
> +         if (spec->is_keyword)
> +           error ("cannot apply %qE to a type that has already been"
> +                  " defined", name);
> +         else
> +           warning (OPT_Wattributes, "type attributes ignored after type"
> +                    " is already defined");
>           continue;
>         }
>
> @@ -895,7 +915,13 @@ decl_attributes (tree *node, tree attributes, int flags,
>           *anode = cur_and_last_decl[0];
>           if (ret == error_mark_node)
>             {
> -             warning (OPT_Wattributes, "%qE attribute ignored", name);
> +             if (spec->is_keyword)
> +               /* This error is only a last resort, Hopefully the
> +                  handler will report a better error in most cases,
> +                  return NULL_TREE, and set no_add_attrs.  */
> +               error ("invalid %qE attribute", name);
> +             else
> +               warning (OPT_Wattributes, "%qE attribute ignored", name);
>               no_add_attrs = true;
>             }
>           else
> diff --git a/gcc/c-family/c-common.h b/gcc/c-family/c-common.h
> index b5ef5ff6b2c..4443ccb874d 100644
> --- a/gcc/c-family/c-common.h
> +++ b/gcc/c-family/c-common.h
> @@ -222,17 +222,9 @@ enum rid
>    RID_ADDR_SPACE_5,
>    RID_ADDR_SPACE_6,
>    RID_ADDR_SPACE_7,
> -  RID_ADDR_SPACE_8,
> -  RID_ADDR_SPACE_9,
> -  RID_ADDR_SPACE_10,
> -  RID_ADDR_SPACE_11,
> -  RID_ADDR_SPACE_12,
> -  RID_ADDR_SPACE_13,
> -  RID_ADDR_SPACE_14,
> -  RID_ADDR_SPACE_15,
>
>    RID_FIRST_ADDR_SPACE = RID_ADDR_SPACE_0,
> -  RID_LAST_ADDR_SPACE = RID_ADDR_SPACE_15,
> +  RID_LAST_ADDR_SPACE = RID_ADDR_SPACE_7,
>
>    /* __intN keywords.  The _N_M here doesn't correspond to the intN
>       in the keyword; use the bitsize in int_n_t_data_t[M] for that.
> @@ -251,6 +243,9 @@ enum rid
>    RID_FIRST_INT_N = RID_INT_N_0,
>    RID_LAST_INT_N = RID_INT_N_3,
>
> +  RID_FIRST_KEYWORD_ATTR,
> +  RID_LAST_KEYWORD_ATTR = RID_FIRST_KEYWORD_ATTR + 7,
> +
>    RID_MAX,
>
>    RID_FIRST_MODIFIER = RID_STATIC,
> diff --git a/gcc/c/c-parser.cc b/gcc/c/c-parser.cc
> index 24a6eb6e459..b02b082b140 100644
> --- a/gcc/c/c-parser.cc
> +++ b/gcc/c/c-parser.cc
> @@ -104,6 +104,17 @@ set_c_expr_source_range (c_expr *expr,
>  }
>
>
> +/* Register keyword attribute AS with reserved identifier code RID.  */
> +
> +void
> +c_register_keyword_attribute (const attribute_spec *as, int rid)
> +{
> +  tree id = get_identifier (as->name);
> +  C_SET_RID_CODE (id, rid);
> +  C_IS_RESERVED_WORD (id) = 1;
> +  ridpointers [rid] = id;
> +}
> +
>  /* Initialization routine for this file.  */
>
>  void
> @@ -180,6 +191,16 @@ c_parse_init (void)
>        C_IS_RESERVED_WORD (id) = 1;
>        ridpointers [RID_OMP_ALL_MEMORY] = id;
>      }
> +
> +  int rid = RID_FIRST_KEYWORD_ATTR;
> +  if (const attribute_spec *attrs = targetm.attribute_table)
> +    for (const attribute_spec *attr = attrs; attr->name; ++attr)
> +      if (attr->is_keyword)
> +       {
> +         gcc_assert (rid <= RID_LAST_KEYWORD_ATTR);
> +         c_register_keyword_attribute (attr, rid);
> +         rid += 1;
> +       }
>  }
>
>  /* A parser structure recording information about the state and
> @@ -4951,7 +4972,8 @@ c_parser_gnu_attribute_any_word (c_parser *parser)
>      {
>        /* ??? See comment above about what keywords are accepted here.  */
>        bool ok;
> -      switch (c_parser_peek_token (parser)->keyword)
> +      int rid = c_parser_peek_token (parser)->keyword;
> +      switch (rid)
>         {
>         case RID_STATIC:
>         case RID_UNSIGNED:
> @@ -4994,7 +5016,9 @@ c_parser_gnu_attribute_any_word (c_parser *parser)
>           ok = true;
>           break;
>         default:
> -         ok = false;
> +         /* Accept these now so that we can reject them with a specific
> +            error later.  */
> +         ok = (rid >= RID_FIRST_KEYWORD_ATTR && rid <= RID_LAST_KEYWORD_ATTR);
>           break;
>         }
>        if (!ok)
> @@ -5156,6 +5180,10 @@ c_parser_gnu_attribute (c_parser *parser, tree attrs,
>      return NULL_TREE;
>
>    attr_name = canonicalize_attr_name (attr_name);
> +  const attribute_spec *as = lookup_attribute_spec (attr_name);
> +  if (as && as->is_keyword)
> +    error ("%qE cannot be used in %<__attribute__%> constructs", attr_name);
> +
>    c_parser_consume_token (parser);
>
>    tree attr;
> @@ -5330,6 +5358,42 @@ c_parser_balanced_token_sequence (c_parser *parser)
>      }
>  }
>
> +/* Parse a keyword attribute.  This is simply:
> +
> +     keyword
> +
> +   if the attribute never takes arguments, otherwise it is:
> +
> +     keyword ( balanced-token-sequence[opt] )
> +*/
> +
> +static tree
> +c_parser_keyword_attribute (c_parser *parser)
> +{
> +  c_token *token = c_parser_peek_token (parser);
> +  gcc_assert (token->type == CPP_KEYWORD);
> +  tree name = canonicalize_attr_name (token->value);
> +  c_parser_consume_token (parser);
> +
> +  tree attribute = build_tree_list (name, NULL_TREE);
> +  const attribute_spec *as = lookup_attribute_spec (name);
> +  gcc_assert (as && as->is_keyword);
> +  if (as->max_length > 0)
> +    {
> +      matching_parens parens;
> +      if (!parens.require_open (parser))
> +       return error_mark_node;
> +      /* Allow zero-length arguments regardless of as->min_length, since
> +        the usual error "parentheses must be omitted if attribute argument
> +        list is empty" suggests an invalid change.  We'll reject incorrect
> +        argument lists later.  */
> +      TREE_VALUE (attribute)
> +       = c_parser_attribute_arguments (parser, false, false, false, true);
> +      parens.require_close (parser);
> +    }
> +  return attribute;
> +}
> +
>  /* Parse standard (C2X) attributes (including GNU attributes in the
>     gnu:: namespace).
>
> @@ -5396,8 +5460,11 @@ c_parser_std_attribute (c_parser *parser, bool for_tm)
>      ns = NULL_TREE;
>    attribute = build_tree_list (build_tree_list (ns, name), NULL_TREE);
>
> -  /* Parse the arguments, if any.  */
>    const attribute_spec *as = lookup_attribute_spec (TREE_PURPOSE (attribute));
> +  if (as && as->is_keyword)
> +    error ("%qE cannot be used in %<[[...]]%> constructs", name);
> +
> +  /* Parse the arguments, if any.  */
>    if (c_parser_next_token_is_not (parser, CPP_OPEN_PAREN))
>      goto out;
>    {
> @@ -5456,7 +5523,13 @@ c_parser_std_attribute (c_parser *parser, bool for_tm)
>  static tree
>  c_parser_std_attribute_specifier (c_parser *parser, bool for_tm)
>  {
> -  location_t loc = c_parser_peek_token (parser)->location;
> +  auto first_token = c_parser_peek_token (parser);
> +  location_t loc = first_token->location;
> +  if (first_token->type == CPP_KEYWORD
> +      && first_token->keyword >= RID_FIRST_KEYWORD_ATTR
> +      && first_token->keyword <= RID_LAST_KEYWORD_ATTR)
> +    return c_parser_keyword_attribute (parser);
> +
>    if (!c_parser_require (parser, CPP_OPEN_SQUARE, "expected %<[%>"))
>      return NULL_TREE;
>    if (!c_parser_require (parser, CPP_OPEN_SQUARE, "expected %<[%>"))
> @@ -5571,7 +5644,12 @@ c_parser_check_balanced_raw_token_sequence (c_parser *parser, unsigned int *n)
>  static bool
>  c_parser_nth_token_starts_std_attributes (c_parser *parser, unsigned int n)
>  {
> -  if (!(c_parser_peek_nth_token (parser, n)->type == CPP_OPEN_SQUARE
> +  auto token_n = c_parser_peek_nth_token (parser, n);
> +  if (token_n->type == CPP_KEYWORD
> +      && token_n->keyword >= RID_FIRST_KEYWORD_ATTR
> +      && token_n->keyword <= RID_LAST_KEYWORD_ATTR)
> +    return true;
> +  if (!(token_n->type == CPP_OPEN_SQUARE
>         && c_parser_peek_nth_token (parser, n + 1)->type == CPP_OPEN_SQUARE))
>      return false;
>    /* In C, '[[' must start attributes.  In Objective-C, we need to
> diff --git a/gcc/config/aarch64/aarch64.cc b/gcc/config/aarch64/aarch64.cc
> index 560e5431636..50698439104 100644
> --- a/gcc/config/aarch64/aarch64.cc
> +++ b/gcc/config/aarch64/aarch64.cc
> @@ -2802,6 +2802,7 @@ static const struct attribute_spec aarch64_attribute_table[] =
>    { "arm_sve_vector_bits", 1, 1, false, true,  false, true,
>                           aarch64_sve::handle_arm_sve_vector_bits_attribute,
>                           NULL },
> +  { "__arm_streaming",    0, 0, false, true,  true,  true,  NULL, NULL, true },
>    { "Advanced SIMD type", 1, 1, false, true,  false, true,  NULL, NULL },
>    { "SVE type",                  3, 3, false, true,  false, true,  NULL, NULL },
>    { "SVE sizeless type",  0, 0, false, true,  false, true,  NULL, NULL },
> diff --git a/gcc/tree-core.h b/gcc/tree-core.h
> index 668808a29d0..e6700509b9e 100644
> --- a/gcc/tree-core.h
> +++ b/gcc/tree-core.h
> @@ -2167,6 +2167,25 @@ struct attribute_spec {
>    /* An array of attribute exclusions describing names of other attributes
>       that this attribute is mutually exclusive with.  */
>    const exclusions *exclude;
> +
> +  /* Whether the attribute is a C/C++ "regular keyword attribute".
> +     When true for an attribute "foo", this means that:
> +
> +     - The keyword foo can appear exactly where the standard attribute syntax
> +       [[...]] can appear, but without the same minimum language requirements.
> +       The link is intended to be automatic: there should be no exceptions.
> +
> +     - The attribute appertains to whatever a standard attribute in the
> +       same location would appertain to.  There is no "sliding" from decls
> +       to types, as sometimes happens for GNU-style attributes.
> +
> +     - When MAX_LENGTH > 0, the keyword is followed by an argument list.
> +       This argument list is parsed in the same way as arguments to [[...]]
> +       attributes, except that the list can be empty if MIN_LENGTH == 0.
> +
> +     - In C and C++, the attribute cannot appear in __attribute__ or [[...]];
> +       it can only appear as a simple keyword.  */
> +  bool is_keyword;
>  };
>
>  /* These functions allow a front-end to perform a manual layout of a
> --
> 2.25.1
>
Richard Sandiford July 17, 2023, 8:21 a.m. UTC | #6
Richard Biener <richard.guenther@gmail.com> writes:
> On Fri, Jul 14, 2023 at 5:58 PM Richard Sandiford via Gcc-patches
> <gcc-patches@gcc.gnu.org> wrote:
>>
>> Summary: We'd like to be able to specify some attributes using
>> keywords, rather than the traditional __attribute__ or [[...]]
>> syntax.  Would that be OK?
>>
>> In more detail:
>>
>> We'd like to add some new target-specific attributes for Arm SME.
>> These attributes affect semantics and code generation and so they
>> can't simply be ignored.
>>
>> Traditionally we've done this kind of thing by adding GNU attributes,
>> via TARGET_ATTRIBUTE_TABLE in GCC's case.  The problem is that both
>> GCC and Clang have traditionally only warned about unrecognised GNU
>> attributes, rather than raising an error.  Older compilers might
>> therefore be able to look past some uses of the new attributes and
>> still produce object code, even though that object code is almost
>> certainly going to be wrong.  (The compilers will also emit a default-on
>> warning, but that might go unnoticed when building a big project.)
>>
>> There are some existing attributes that similarly affect semantics
>> in ways that cannot be ignored.  vector_size is one obvious example.
>> But that doesn't make it a good thing. :)
>>
>> Also, C++ says this for standard [[...]] attributes:
>>
>>   For an attribute-token (including an attribute-scoped-token)
>>   not specified in this document, the behavior is implementation-defined;
>>   any such attribute-token that is not recognized by the implementation
>>   is ignored.
>>
>> which doubles down on the idea that attributes should not be used
>> for necessary semantic information.
>>
>> One of the attributes we'd like to add provides a new way of compiling
>> existing code.  The attribute doesn't require SME to be available;
>> it just says that the code must be compiled so that it can run in either
>> of two modes.  This is probably the most dangerous attribute of the set,
>> since compilers that ignore it would just produce normal code.  That
>> code might work in some test scenarios, but it would fail in others.
>>
>> The feeling from the Clang community was therefore that these SME
>> attributes should use keywords instead, so that the keywords trigger
>> an error with older compilers.
>>
>> However, it seemed wrong to define new SME-specific grammar rules,
>> since the underlying problem is pretty generic.  We therefore
>> proposed having a type of keyword that can appear exactly where
>> a standard [[...]] attribute can appear and that appertains to
>> exactly what a standard [[...]] attribute would appertain to.
>> No divergence or cherry-picking is allowed.
>>
>> For example:
>>
>>   [[arm::foo]]
>>
>> would become:
>>
>>   __arm_foo
>>
>> and:
>>
>>   [[arm::bar(args)]]
>>
>> would become:
>>
>>   __arm_bar(args)
>>
>> It wouldn't be possible to retrofit arguments to a keyword that
>> previously didn't take arguments, since that could lead to parsing
>> ambiguities.  So when a keyword is first added, a binding decision
>> would need to be made whether the keyword always takes arguments
>> or is always standalone.
>>
>> For that reason, empty argument lists are allowed for keywords,
>> even though they're not allowed for [[...]] attributes.
>>
>> The argument-less version was accepted into Clang, and I have a follow-on
>> patch for handling arguments.  Would the same thing be OK for GCC,
>> in both the C and C++ frontends?
>>
>> The patch below is a proof of concept for the C frontend.  It doesn't
>> bootstrap due to warnings about uninitialised fields.  And it doesn't
>> have tests.  But I did test it locally with various combinations of
>> attribute_spec and it seemed to work as expected.
>>
>> The impact on the C frontend seems to be pretty small.  It looks like
>> the impact on the C++ frontend would be a bit bigger, but not much.
>>
>> The patch contains a logically unrelated change: c-common.h set aside
>> 16 keywords for address spaces, but of the in-tree ports, the maximum
>> number of keywords used is 6 (for amdgcn).  The patch therefore changes
>> the limit to 8 and uses 8 keywords for the new attributes.  This keeps
>> the number of reserved ids <= 256.
>
> If you had added __arm(bar(args)) instead of __arm_bar(args) you would only
> need one additional keyword - we could set aside a similar one for each
> target then.  I realize that double-nesting of arguments might prove a bit
> challenging but still.

Yeah, that would work.

> In any case I also think that attributes are what you want and their
> ugliness/issues are not worse than the ugliness/issues of the keyword
> approach IMHO.

I guess the ugliness of keywords is the double underscore?
What are the issues with the keyword approach though?

If it's two underscores vs miscompilation then it's not obvious
that two underscores should lose.

Richard
Richard Biener July 17, 2023, 9:05 a.m. UTC | #7
On Mon, Jul 17, 2023 at 10:21 AM Richard Sandiford
<richard.sandiford@arm.com> wrote:
>
> Richard Biener <richard.guenther@gmail.com> writes:
> > On Fri, Jul 14, 2023 at 5:58 PM Richard Sandiford via Gcc-patches
> > <gcc-patches@gcc.gnu.org> wrote:
> >>
> >> Summary: We'd like to be able to specify some attributes using
> >> keywords, rather than the traditional __attribute__ or [[...]]
> >> syntax.  Would that be OK?
> >>
> >> In more detail:
> >>
> >> We'd like to add some new target-specific attributes for Arm SME.
> >> These attributes affect semantics and code generation and so they
> >> can't simply be ignored.
> >>
> >> Traditionally we've done this kind of thing by adding GNU attributes,
> >> via TARGET_ATTRIBUTE_TABLE in GCC's case.  The problem is that both
> >> GCC and Clang have traditionally only warned about unrecognised GNU
> >> attributes, rather than raising an error.  Older compilers might
> >> therefore be able to look past some uses of the new attributes and
> >> still produce object code, even though that object code is almost
> >> certainly going to be wrong.  (The compilers will also emit a default-on
> >> warning, but that might go unnoticed when building a big project.)
> >>
> >> There are some existing attributes that similarly affect semantics
> >> in ways that cannot be ignored.  vector_size is one obvious example.
> >> But that doesn't make it a good thing. :)
> >>
> >> Also, C++ says this for standard [[...]] attributes:
> >>
> >>   For an attribute-token (including an attribute-scoped-token)
> >>   not specified in this document, the behavior is implementation-defined;
> >>   any such attribute-token that is not recognized by the implementation
> >>   is ignored.
> >>
> >> which doubles down on the idea that attributes should not be used
> >> for necessary semantic information.
> >>
> >> One of the attributes we'd like to add provides a new way of compiling
> >> existing code.  The attribute doesn't require SME to be available;
> >> it just says that the code must be compiled so that it can run in either
> >> of two modes.  This is probably the most dangerous attribute of the set,
> >> since compilers that ignore it would just produce normal code.  That
> >> code might work in some test scenarios, but it would fail in others.
> >>
> >> The feeling from the Clang community was therefore that these SME
> >> attributes should use keywords instead, so that the keywords trigger
> >> an error with older compilers.
> >>
> >> However, it seemed wrong to define new SME-specific grammar rules,
> >> since the underlying problem is pretty generic.  We therefore
> >> proposed having a type of keyword that can appear exactly where
> >> a standard [[...]] attribute can appear and that appertains to
> >> exactly what a standard [[...]] attribute would appertain to.
> >> No divergence or cherry-picking is allowed.
> >>
> >> For example:
> >>
> >>   [[arm::foo]]
> >>
> >> would become:
> >>
> >>   __arm_foo
> >>
> >> and:
> >>
> >>   [[arm::bar(args)]]
> >>
> >> would become:
> >>
> >>   __arm_bar(args)
> >>
> >> It wouldn't be possible to retrofit arguments to a keyword that
> >> previously didn't take arguments, since that could lead to parsing
> >> ambiguities.  So when a keyword is first added, a binding decision
> >> would need to be made whether the keyword always takes arguments
> >> or is always standalone.
> >>
> >> For that reason, empty argument lists are allowed for keywords,
> >> even though they're not allowed for [[...]] attributes.
> >>
> >> The argument-less version was accepted into Clang, and I have a follow-on
> >> patch for handling arguments.  Would the same thing be OK for GCC,
> >> in both the C and C++ frontends?
> >>
> >> The patch below is a proof of concept for the C frontend.  It doesn't
> >> bootstrap due to warnings about uninitialised fields.  And it doesn't
> >> have tests.  But I did test it locally with various combinations of
> >> attribute_spec and it seemed to work as expected.
> >>
> >> The impact on the C frontend seems to be pretty small.  It looks like
> >> the impact on the C++ frontend would be a bit bigger, but not much.
> >>
> >> The patch contains a logically unrelated change: c-common.h set aside
> >> 16 keywords for address spaces, but of the in-tree ports, the maximum
> >> number of keywords used is 6 (for amdgcn).  The patch therefore changes
> >> the limit to 8 and uses 8 keywords for the new attributes.  This keeps
> >> the number of reserved ids <= 256.
> >
> > If you had added __arm(bar(args)) instead of __arm_bar(args) you would only
> > need one additional keyword - we could set aside a similar one for each
> > target then.  I realize that double-nesting of arguments might prove a bit
> > challenging but still.
>
> Yeah, that would work.
>
> > In any case I also think that attributes are what you want and their
> > ugliness/issues are not worse than the ugliness/issues of the keyword
> > approach IMHO.
>
> I guess the ugliness of keywords is the double underscore?
> What are the issues with the keyword approach though?

The issue is the non-standard syntax which will confuse 3rd party
tools like IDEs, static analyzers, etc.  I'd also add that esp.
my suggestion to use __arm will likely clash with pre-existing
macros from (some) implementations.  That can be solved with
_ArmKWD or choosing a less "common" identifier.  A quick
check shows GCC on arm only defines __arm__, not __arm though.

Richard.

> If it's two underscores vs miscompilation then it's not obvious
> that two underscores should lose.
>
> Richard
Jason Merrill July 17, 2023, 1:39 p.m. UTC | #8
On Sun, Jul 16, 2023 at 6:50 AM Richard Sandiford <richard.sandiford@arm.com>
wrote:

> Jakub Jelinek <jakub@redhat.com> writes:
> > On Fri, Jul 14, 2023 at 04:56:18PM +0100, Richard Sandiford via
> Gcc-patches wrote:
> >> Summary: We'd like to be able to specify some attributes using
> >> keywords, rather than the traditional __attribute__ or [[...]]
> >> syntax.  Would that be OK?
> >
> > Will defer to C/C++ maintainers, but as you mentioned, there are many
> > attributes which really can't be ignored and change behavior
> significantly.
> > vector_size is one of those, mode attribute another,
> > no_unique_address another one (changes ABI in various cases),
> > the OpenMP attributes (omp::directive, omp::sequence) can change
> > behavior if -fopenmp, etc.
> > One can easily error with
> > #ifdef __has_cpp_attribute
> > #if !__has_cpp_attribute (arm::whatever)
> > #error arm::whatever attribute unsupported
> > #endif
> > #else
> > #error __has_cpp_attribute unsupported
> > #endif
>
> Yeah.  It's easy to detect whether a particular ACLE feature is supported,
> since there are predefined macros for each one.  But IMO it's a failing
> if we have to recommend that any compilation that uses arm::foo should
> also have:
>
> #ifndef __ARM_FEATURE_FOO
> #error arm::foo not supported
> #endif
>
> It ought to be the compiler's job to diagnose its limitations, rather
> than the user's.
>
> The feature macros are more for conditional usage of features, where
> there's a fallback available.
>
> I suppose we could say that users have to include a particular header
> file before using an attribute, and use a pragma in that header file to
> tell the compiler to enable the attribute.  But then there would need to
> be a separate header file for each distinct set of attributes (in terms
> of historical timeline), which would get ugly.  I'm not sure that it's
> better than using keywords, or even whether it's better than predefining
> the "keyword" as a macro that expands to a compiler-internal attribute.
>

With a combination of those approaches it can be a single header:

#ifdef __ARM_FEATURE_FOO
#define __arm_foo [[arm::foo]]
// else use of __arm_foo will fail
#endif
Michael Matz July 17, 2023, 1:53 p.m. UTC | #9
Hello,

On Mon, 17 Jul 2023, Richard Sandiford via Gcc-patches wrote:

> >> There are some existing attributes that similarly affect semantics
> >> in ways that cannot be ignored.  vector_size is one obvious example.
> >> But that doesn't make it a good thing. :)
> >...
> > If you had added __arm(bar(args)) instead of __arm_bar(args) you would only
> > need one additional keyword - we could set aside a similar one for each
> > target then.  I realize that double-nesting of arguments might prove a bit
> > challenging but still.
> 
> Yeah, that would work.

So, essentially you want unignorable attributes, right?  Then implement 
exactly that: add one new keyword "__known_attribute__" (invent a better 
name, maybe :) ), semantics exactly as with __attribute__ (including using 
the same underlying lists in our data structures), with only one single 
deviation: instead of the warning you give an error for unhandled 
attributes.  Done.

(Old compilers will barf of the unknown new keyword, new compilers will 
error on unknown values used within such attribute list)

> > In any case I also think that attributes are what you want and their 
> > ugliness/issues are not worse than the ugliness/issues of the keyword 
> > approach IMHO.
> 
> I guess the ugliness of keywords is the double underscore? What are the 
> issues with the keyword approach though?

There are _always_ problems with new keywords, the more new keywords the 
more problems :-)  Is the keyword context-sensitive or not?  What about 
existing system headers that use it right now?  Is it recognized in 
free-standing or not?  Is it only recognized for some targets?  Is it 
recognized only for certain configurations of the target?

So, let's define one new mechanism, for all targets, all configs, and all 
language standards.  Let's use __attribute__ with a twist :)


Ciao,
Michael.
Richard Sandiford July 17, 2023, 2:06 p.m. UTC | #10
Jason Merrill <jason@redhat.com> writes:
> On Sun, Jul 16, 2023 at 6:50 AM Richard Sandiford <richard.sandiford@arm.com>
> wrote:
>
>> Jakub Jelinek <jakub@redhat.com> writes:
>> > On Fri, Jul 14, 2023 at 04:56:18PM +0100, Richard Sandiford via
>> Gcc-patches wrote:
>> >> Summary: We'd like to be able to specify some attributes using
>> >> keywords, rather than the traditional __attribute__ or [[...]]
>> >> syntax.  Would that be OK?
>> >
>> > Will defer to C/C++ maintainers, but as you mentioned, there are many
>> > attributes which really can't be ignored and change behavior
>> significantly.
>> > vector_size is one of those, mode attribute another,
>> > no_unique_address another one (changes ABI in various cases),
>> > the OpenMP attributes (omp::directive, omp::sequence) can change
>> > behavior if -fopenmp, etc.
>> > One can easily error with
>> > #ifdef __has_cpp_attribute
>> > #if !__has_cpp_attribute (arm::whatever)
>> > #error arm::whatever attribute unsupported
>> > #endif
>> > #else
>> > #error __has_cpp_attribute unsupported
>> > #endif
>>
>> Yeah.  It's easy to detect whether a particular ACLE feature is supported,
>> since there are predefined macros for each one.  But IMO it's a failing
>> if we have to recommend that any compilation that uses arm::foo should
>> also have:
>>
>> #ifndef __ARM_FEATURE_FOO
>> #error arm::foo not supported
>> #endif
>>
>> It ought to be the compiler's job to diagnose its limitations, rather
>> than the user's.
>>
>> The feature macros are more for conditional usage of features, where
>> there's a fallback available.
>>
>> I suppose we could say that users have to include a particular header
>> file before using an attribute, and use a pragma in that header file to
>> tell the compiler to enable the attribute.  But then there would need to
>> be a separate header file for each distinct set of attributes (in terms
>> of historical timeline), which would get ugly.  I'm not sure that it's
>> better than using keywords, or even whether it's better than predefining
>> the "keyword" as a macro that expands to a compiler-internal attribute.
>>
>
> With a combination of those approaches it can be a single header:
>
> #ifdef __ARM_FEATURE_FOO
> #define __arm_foo [[arm::foo]]
> // else use of __arm_foo will fail
> #endif

If we did that, would it be a defined part of the interface that
__arm_foo expands to exactly arm::foo, rather than to an obfuscated
or compiler-dependent attribute?

In other words, would it be a case of providing both the attribute
and the macro, and leaving users to choose whether they use the
attribute directly (and run the risk of miscompilation) or whether
they use the macros, based on their risk appetite?  If so, the risk of
miscompliation is mostly borne by the people who build the deployed code
rather than the people who initially wrote it.

If instead we say that the expansion of the macros is compiler-dependent
and that the macros must always be used, then I'm not sure the header
file provides a better interface than predefining the macros in the
compiler (which was the fallback option if the keywords were rejected).

But the diagnostics using these macros would be worse than diagnostics
based on keywords, not least because the diagnostics about invalid
use of the macros (from compilers that understood them) would refer
to the underlying attribute rather than the macro.

Thanks,
Richard
Joseph Myers July 21, 2023, 11:25 p.m. UTC | #11
On Mon, 17 Jul 2023, Michael Matz via Gcc-patches wrote:

> So, essentially you want unignorable attributes, right?  Then implement 
> exactly that: add one new keyword "__known_attribute__" (invent a better 
> name, maybe :) ), semantics exactly as with __attribute__ (including using 
> the same underlying lists in our data structures), with only one single 
> deviation: instead of the warning you give an error for unhandled 
> attributes.  Done.

Assuming you also want the better-defined standard rules about how [[]] 
attributes appertain to particular entities, rather than the different 
__attribute__ rules, that would suggest something like [[!some::attr]] for 
the case of attributes that can't be ignored but otherwise are handled 
like standard [[]] attributes.
Richard Sandiford Aug. 16, 2023, 10:36 a.m. UTC | #12
Joseph Myers <joseph@codesourcery.com> writes:
> On Mon, 17 Jul 2023, Michael Matz via Gcc-patches wrote:
>
>> So, essentially you want unignorable attributes, right?  Then implement 
>> exactly that: add one new keyword "__known_attribute__" (invent a better 
>> name, maybe :) ), semantics exactly as with __attribute__ (including using 
>> the same underlying lists in our data structures), with only one single 
>> deviation: instead of the warning you give an error for unhandled 
>> attributes.  Done.
>
> Assuming you also want the better-defined standard rules about how [[]] 
> attributes appertain to particular entities, rather than the different 
> __attribute__ rules, that would suggest something like [[!some::attr]] for 
> the case of attributes that can't be ignored but otherwise are handled 
> like standard [[]] attributes.

Yeah, that would work.  But I'd rather not gate the SME work on getting
an extension like that into C and C++.

As it stands, some clang maintainers pushed back against the use of
attributes for important semantics, and preferred keywords instead.
It's clear from this threads that the GCC maintainers prefer attributes
to keywords.  (And it turns out that some other clang maintainers do too,
though not as strongly.)

So I think the easiest way of keeping both constituencies happy(-ish)
is to provide both standard attributes and "keywords", but allow
the "keywords" to be macros that expand to standard attributes.

Would it be OK to add support for:

  [[__extension__ ...]]

to suppress the pedwarn about using [[]] prior to C2X?  Then we can
predefine __arm_streaming to [[__extension__ arm::streaming]], etc.

Thanks,
Richard
Joseph Myers Aug. 16, 2023, 1:22 p.m. UTC | #13
On Wed, 16 Aug 2023, Richard Sandiford via Gcc-patches wrote:

> Would it be OK to add support for:
> 
>   [[__extension__ ...]]
> 
> to suppress the pedwarn about using [[]] prior to C2X?  Then we can

That seems like a plausible feature to add.
diff mbox series

Patch

diff --git a/gcc/attribs.cc b/gcc/attribs.cc
index b8cb55b97df..706cd81329c 100644
--- a/gcc/attribs.cc
+++ b/gcc/attribs.cc
@@ -752,6 +752,11 @@  decl_attributes (tree *node, tree attributes, int flags,
 
       if (spec->decl_required && !DECL_P (*anode))
 	{
+	  if (spec->is_keyword)
+	    {
+	      error ("%qE does not apply to types", name);
+	      continue;
+	    }
 	  if (flags & ((int) ATTR_FLAG_DECL_NEXT
 		       | (int) ATTR_FLAG_FUNCTION_NEXT
 		       | (int) ATTR_FLAG_ARRAY_NEXT))
@@ -775,6 +780,11 @@  decl_attributes (tree *node, tree attributes, int flags,
 	 the decl's type in place here.  */
       if (spec->type_required && DECL_P (*anode))
 	{
+	  if (spec->is_keyword)
+	    {
+	      error ("%qE only applies to types", name);
+	      continue;
+	    }
 	  anode = &TREE_TYPE (*anode);
 	  flags &= ~(int) ATTR_FLAG_TYPE_IN_PLACE;
 	}
@@ -782,6 +792,11 @@  decl_attributes (tree *node, tree attributes, int flags,
       if (spec->function_type_required && TREE_CODE (*anode) != FUNCTION_TYPE
 	  && TREE_CODE (*anode) != METHOD_TYPE)
 	{
+	  if (spec->is_keyword)
+	    {
+	      error ("%qE only applies to function types", name);
+	      continue;
+	    }
 	  if (TREE_CODE (*anode) == POINTER_TYPE
 	      && FUNC_OR_METHOD_TYPE_P (TREE_TYPE (*anode)))
 	    {
@@ -821,7 +836,12 @@  decl_attributes (tree *node, tree attributes, int flags,
 	  && (flags & (int) ATTR_FLAG_TYPE_IN_PLACE)
 	  && COMPLETE_TYPE_P (*anode))
 	{
-	  warning (OPT_Wattributes, "type attributes ignored after type is already defined");
+	  if (spec->is_keyword)
+	    error ("cannot apply %qE to a type that has already been"
+		   " defined", name);
+	  else
+	    warning (OPT_Wattributes, "type attributes ignored after type"
+		     " is already defined");
 	  continue;
 	}
 
@@ -895,7 +915,13 @@  decl_attributes (tree *node, tree attributes, int flags,
 	  *anode = cur_and_last_decl[0];
 	  if (ret == error_mark_node)
 	    {
-	      warning (OPT_Wattributes, "%qE attribute ignored", name);
+	      if (spec->is_keyword)
+		/* This error is only a last resort, Hopefully the
+		   handler will report a better error in most cases,
+		   return NULL_TREE, and set no_add_attrs.  */
+		error ("invalid %qE attribute", name);
+	      else
+		warning (OPT_Wattributes, "%qE attribute ignored", name);
 	      no_add_attrs = true;
 	    }
 	  else
diff --git a/gcc/c-family/c-common.h b/gcc/c-family/c-common.h
index b5ef5ff6b2c..4443ccb874d 100644
--- a/gcc/c-family/c-common.h
+++ b/gcc/c-family/c-common.h
@@ -222,17 +222,9 @@  enum rid
   RID_ADDR_SPACE_5,
   RID_ADDR_SPACE_6,
   RID_ADDR_SPACE_7,
-  RID_ADDR_SPACE_8,
-  RID_ADDR_SPACE_9,
-  RID_ADDR_SPACE_10,
-  RID_ADDR_SPACE_11,
-  RID_ADDR_SPACE_12,
-  RID_ADDR_SPACE_13,
-  RID_ADDR_SPACE_14,
-  RID_ADDR_SPACE_15,
 
   RID_FIRST_ADDR_SPACE = RID_ADDR_SPACE_0,
-  RID_LAST_ADDR_SPACE = RID_ADDR_SPACE_15,
+  RID_LAST_ADDR_SPACE = RID_ADDR_SPACE_7,
 
   /* __intN keywords.  The _N_M here doesn't correspond to the intN
      in the keyword; use the bitsize in int_n_t_data_t[M] for that.
@@ -251,6 +243,9 @@  enum rid
   RID_FIRST_INT_N = RID_INT_N_0,
   RID_LAST_INT_N = RID_INT_N_3,
 
+  RID_FIRST_KEYWORD_ATTR,
+  RID_LAST_KEYWORD_ATTR = RID_FIRST_KEYWORD_ATTR + 7,
+
   RID_MAX,
 
   RID_FIRST_MODIFIER = RID_STATIC,
diff --git a/gcc/c/c-parser.cc b/gcc/c/c-parser.cc
index 24a6eb6e459..b02b082b140 100644
--- a/gcc/c/c-parser.cc
+++ b/gcc/c/c-parser.cc
@@ -104,6 +104,17 @@  set_c_expr_source_range (c_expr *expr,
 }
 
 
+/* Register keyword attribute AS with reserved identifier code RID.  */
+
+void
+c_register_keyword_attribute (const attribute_spec *as, int rid)
+{
+  tree id = get_identifier (as->name);
+  C_SET_RID_CODE (id, rid);
+  C_IS_RESERVED_WORD (id) = 1;
+  ridpointers [rid] = id;
+}
+
 /* Initialization routine for this file.  */
 
 void
@@ -180,6 +191,16 @@  c_parse_init (void)
       C_IS_RESERVED_WORD (id) = 1;
       ridpointers [RID_OMP_ALL_MEMORY] = id;
     }
+
+  int rid = RID_FIRST_KEYWORD_ATTR;
+  if (const attribute_spec *attrs = targetm.attribute_table)
+    for (const attribute_spec *attr = attrs; attr->name; ++attr)
+      if (attr->is_keyword)
+	{
+	  gcc_assert (rid <= RID_LAST_KEYWORD_ATTR);
+	  c_register_keyword_attribute (attr, rid);
+	  rid += 1;
+	}
 }
 
 /* A parser structure recording information about the state and
@@ -4951,7 +4972,8 @@  c_parser_gnu_attribute_any_word (c_parser *parser)
     {
       /* ??? See comment above about what keywords are accepted here.  */
       bool ok;
-      switch (c_parser_peek_token (parser)->keyword)
+      int rid = c_parser_peek_token (parser)->keyword;
+      switch (rid)
 	{
 	case RID_STATIC:
 	case RID_UNSIGNED:
@@ -4994,7 +5016,9 @@  c_parser_gnu_attribute_any_word (c_parser *parser)
 	  ok = true;
 	  break;
 	default:
-	  ok = false;
+	  /* Accept these now so that we can reject them with a specific
+	     error later.  */
+	  ok = (rid >= RID_FIRST_KEYWORD_ATTR && rid <= RID_LAST_KEYWORD_ATTR);
 	  break;
 	}
       if (!ok)
@@ -5156,6 +5180,10 @@  c_parser_gnu_attribute (c_parser *parser, tree attrs,
     return NULL_TREE;
 
   attr_name = canonicalize_attr_name (attr_name);
+  const attribute_spec *as = lookup_attribute_spec (attr_name);
+  if (as && as->is_keyword)
+    error ("%qE cannot be used in %<__attribute__%> constructs", attr_name);
+
   c_parser_consume_token (parser);
 
   tree attr;
@@ -5330,6 +5358,42 @@  c_parser_balanced_token_sequence (c_parser *parser)
     }
 }
 
+/* Parse a keyword attribute.  This is simply:
+
+     keyword
+
+   if the attribute never takes arguments, otherwise it is:
+
+     keyword ( balanced-token-sequence[opt] )
+*/
+
+static tree
+c_parser_keyword_attribute (c_parser *parser)
+{
+  c_token *token = c_parser_peek_token (parser);
+  gcc_assert (token->type == CPP_KEYWORD);
+  tree name = canonicalize_attr_name (token->value);
+  c_parser_consume_token (parser);
+
+  tree attribute = build_tree_list (name, NULL_TREE);
+  const attribute_spec *as = lookup_attribute_spec (name);
+  gcc_assert (as && as->is_keyword);
+  if (as->max_length > 0)
+    {
+      matching_parens parens;
+      if (!parens.require_open (parser))
+	return error_mark_node;
+      /* Allow zero-length arguments regardless of as->min_length, since
+	 the usual error "parentheses must be omitted if attribute argument
+	 list is empty" suggests an invalid change.  We'll reject incorrect
+	 argument lists later.  */
+      TREE_VALUE (attribute)
+	= c_parser_attribute_arguments (parser, false, false, false, true);
+      parens.require_close (parser);
+    }
+  return attribute;
+}
+
 /* Parse standard (C2X) attributes (including GNU attributes in the
    gnu:: namespace).
 
@@ -5396,8 +5460,11 @@  c_parser_std_attribute (c_parser *parser, bool for_tm)
     ns = NULL_TREE;
   attribute = build_tree_list (build_tree_list (ns, name), NULL_TREE);
 
-  /* Parse the arguments, if any.  */
   const attribute_spec *as = lookup_attribute_spec (TREE_PURPOSE (attribute));
+  if (as && as->is_keyword)
+    error ("%qE cannot be used in %<[[...]]%> constructs", name);
+
+  /* Parse the arguments, if any.  */
   if (c_parser_next_token_is_not (parser, CPP_OPEN_PAREN))
     goto out;
   {
@@ -5456,7 +5523,13 @@  c_parser_std_attribute (c_parser *parser, bool for_tm)
 static tree
 c_parser_std_attribute_specifier (c_parser *parser, bool for_tm)
 {
-  location_t loc = c_parser_peek_token (parser)->location;
+  auto first_token = c_parser_peek_token (parser);
+  location_t loc = first_token->location;
+  if (first_token->type == CPP_KEYWORD
+      && first_token->keyword >= RID_FIRST_KEYWORD_ATTR
+      && first_token->keyword <= RID_LAST_KEYWORD_ATTR)
+    return c_parser_keyword_attribute (parser);
+
   if (!c_parser_require (parser, CPP_OPEN_SQUARE, "expected %<[%>"))
     return NULL_TREE;
   if (!c_parser_require (parser, CPP_OPEN_SQUARE, "expected %<[%>"))
@@ -5571,7 +5644,12 @@  c_parser_check_balanced_raw_token_sequence (c_parser *parser, unsigned int *n)
 static bool
 c_parser_nth_token_starts_std_attributes (c_parser *parser, unsigned int n)
 {
-  if (!(c_parser_peek_nth_token (parser, n)->type == CPP_OPEN_SQUARE
+  auto token_n = c_parser_peek_nth_token (parser, n);
+  if (token_n->type == CPP_KEYWORD
+      && token_n->keyword >= RID_FIRST_KEYWORD_ATTR
+      && token_n->keyword <= RID_LAST_KEYWORD_ATTR)
+    return true;
+  if (!(token_n->type == CPP_OPEN_SQUARE
 	&& c_parser_peek_nth_token (parser, n + 1)->type == CPP_OPEN_SQUARE))
     return false;
   /* In C, '[[' must start attributes.  In Objective-C, we need to
diff --git a/gcc/config/aarch64/aarch64.cc b/gcc/config/aarch64/aarch64.cc
index 560e5431636..50698439104 100644
--- a/gcc/config/aarch64/aarch64.cc
+++ b/gcc/config/aarch64/aarch64.cc
@@ -2802,6 +2802,7 @@  static const struct attribute_spec aarch64_attribute_table[] =
   { "arm_sve_vector_bits", 1, 1, false, true,  false, true,
 			  aarch64_sve::handle_arm_sve_vector_bits_attribute,
 			  NULL },
+  { "__arm_streaming",    0, 0, false, true,  true,  true,  NULL, NULL, true },
   { "Advanced SIMD type", 1, 1, false, true,  false, true,  NULL, NULL },
   { "SVE type",		  3, 3, false, true,  false, true,  NULL, NULL },
   { "SVE sizeless type",  0, 0, false, true,  false, true,  NULL, NULL },
diff --git a/gcc/tree-core.h b/gcc/tree-core.h
index 668808a29d0..e6700509b9e 100644
--- a/gcc/tree-core.h
+++ b/gcc/tree-core.h
@@ -2167,6 +2167,25 @@  struct attribute_spec {
   /* An array of attribute exclusions describing names of other attributes
      that this attribute is mutually exclusive with.  */
   const exclusions *exclude;
+
+  /* Whether the attribute is a C/C++ "regular keyword attribute".
+     When true for an attribute "foo", this means that:
+
+     - The keyword foo can appear exactly where the standard attribute syntax
+       [[...]] can appear, but without the same minimum language requirements.
+       The link is intended to be automatic: there should be no exceptions.
+
+     - The attribute appertains to whatever a standard attribute in the
+       same location would appertain to.  There is no "sliding" from decls
+       to types, as sometimes happens for GNU-style attributes.
+
+     - When MAX_LENGTH > 0, the keyword is followed by an argument list.
+       This argument list is parsed in the same way as arguments to [[...]]
+       attributes, except that the list can be empty if MIN_LENGTH == 0.
+
+     - In C and C++, the attribute cannot appear in __attribute__ or [[...]];
+       it can only appear as a simple keyword.  */
+  bool is_keyword;
 };
 
 /* These functions allow a front-end to perform a manual layout of a