From: Nathan Sidwell <nathan@acm.org>
To: gcc-patches@gcc.gnu.org, joseph@codesourcery.com,
polacek@redhat.com, jason@redhat.com, richard.sandiford@arm.com
Subject: Re: [WIP RFC] Add support for keyword-based attributes
Date: Fri, 14 Jul 2023 17:14:31 -0400 [thread overview]
Message-ID: <798b12e4-8305-d800-86a2-1d0c5b87fc2a@acm.org> (raw)
In-Reply-To: <mpt4jm6sd0d.fsf@arm.com>
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,
> }
>
> \f
> +/* 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;
> + }
> }
> \f
> /* 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
--
Nathan Sidwell
next prev parent reply other threads:[~2023-07-14 21:14 UTC|newest]
Thread overview: 19+ messages / expand[flat|nested] mbox.gz Atom feed top
2023-07-14 15:56 Richard Sandiford
2023-07-14 17:17 ` Jakub Jelinek
2023-07-16 10:50 ` Richard Sandiford
2023-07-17 13:39 ` Jason Merrill
2023-07-17 14:06 ` Richard Sandiford
2023-07-14 21:14 ` Nathan Sidwell [this message]
2023-07-16 10:18 ` Richard Sandiford
2023-07-17 6:38 ` Richard Biener
2023-07-17 8:21 ` Richard Sandiford
2023-07-17 9:05 ` Richard Biener
2023-07-17 13:53 ` Michael Matz
2023-07-21 23:25 ` Joseph Myers
2023-08-16 10:36 ` Richard Sandiford
2023-08-16 13:22 ` Joseph Myers
2023-08-17 11:24 ` [PATCH] c: Add support for [[__extension__ ...]] Richard Sandiford
2023-08-17 17:07 ` Richard Biener
2023-08-17 18:36 ` Richard Sandiford
2023-08-18 9:51 ` Richard Sandiford
2023-08-18 19:51 ` Joseph Myers
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=798b12e4-8305-d800-86a2-1d0c5b87fc2a@acm.org \
--to=nathan@acm.org \
--cc=gcc-patches@gcc.gnu.org \
--cc=jason@redhat.com \
--cc=joseph@codesourcery.com \
--cc=polacek@redhat.com \
--cc=richard.sandiford@arm.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).